androiddeployqtstep.cpp 20.9 KB
Newer Older
1
2
/**************************************************************************
**
Eike Ziller's avatar
Eike Ziller committed
3
4
5
** Copyright (C) 2015 BogDan Vatra <bog_dan_ro@yahoo.com>
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing
6
7
8
9
10
11
12
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
Eike Ziller's avatar
Eike Ziller committed
13
14
** a written agreement between you and The Qt Company.  For licensing terms and
** conditions see http://www.qt.io/terms-conditions.  For further information
Eike Ziller's avatar
Eike Ziller committed
15
** use the contact form at http://www.qt.io/contact-us.
16
17
18
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
Eike Ziller's avatar
Eike Ziller committed
19
20
21
22
23
24
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25
**
Eike Ziller's avatar
Eike Ziller committed
26
27
** In addition, as a special exception, The Qt Company gives you certain additional
** rights.  These rights are described in The Qt Company LGPL Exception
28
29
30
31
32
33
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/

#include "androiddeployqtstep.h"
#include "androiddeployqtwidget.h"
34
#include "androidqtsupport.h"
35
36
37
38
#include "certificatesmodel.h"

#include "javaparser.h"
#include "androidmanager.h"
39
#include "androidconstants.h"
40
#include "androidglobal.h"
41
42
43
44

#include <coreplugin/fileutils.h>
#include <coreplugin/icore.h>
#include <coreplugin/messagemanager.h>
45
46

#include <projectexplorer/buildconfiguration.h>
47
48
49
#include <projectexplorer/buildsteplist.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/project.h>
50
51
#include <projectexplorer/target.h>

52
#include <qtsupport/qtkitinformation.h>
53
54
55
56

#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>

57
58
59
#include <QInputDialog>
#include <QMessageBox>

60

61
62
63
using namespace Android;
using namespace Android::Internal;

64
const QLatin1String UninstallPreviousPackageKey("UninstallPreviousPackage");
65
66
67
68
69
const QLatin1String KeystoreLocationKey("KeystoreLocation");
const QLatin1String SignPackageKey("SignPackage");
const QLatin1String BuildTargetSdkKey("BuildTargetSdk");
const QLatin1String VerboseOutputKey("VerboseOutput");
const QLatin1String InputFile("InputFile");
70
const QLatin1String ProFilePathForInputFile("ProFilePathForInputFile");
71
const QLatin1String InstallFailedInconsistentCertificatesString("INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES");
72
const QLatin1String InstallFailedInconsistentCertificatesString2("INSTALL_FAILED_UPDATE_INCOMPATIBLE");
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
const Core::Id AndroidDeployQtStep::Id("Qt4ProjectManager.AndroidDeployQtStep");

//////////////////
// AndroidDeployQtStepFactory
/////////////////

AndroidDeployQtStepFactory::AndroidDeployQtStepFactory(QObject *parent)
    : IBuildStepFactory(parent)
{
}

QList<Core::Id> AndroidDeployQtStepFactory::availableCreationIds(ProjectExplorer::BuildStepList *parent) const
{
    if (parent->id() != ProjectExplorer::Constants::BUILDSTEPS_DEPLOY)
        return QList<Core::Id>();
    if (!AndroidManager::supportsAndroid(parent->target()))
        return QList<Core::Id>();
    if (parent->contains(AndroidDeployQtStep::Id))
        return QList<Core::Id>();
    return QList<Core::Id>() << AndroidDeployQtStep::Id;
}

95
QString AndroidDeployQtStepFactory::displayNameForId(Core::Id id) const
96
97
98
99
100
101
{
    if (id == AndroidDeployQtStep::Id)
        return tr("Deploy to Android device or emulator");
    return QString();
}

102
bool AndroidDeployQtStepFactory::canCreate(ProjectExplorer::BuildStepList *parent, Core::Id id) const
103
104
105
106
{
    return availableCreationIds(parent).contains(id);
}

107
ProjectExplorer::BuildStep *AndroidDeployQtStepFactory::create(ProjectExplorer::BuildStepList *parent, Core::Id id)
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
{
    Q_ASSERT(canCreate(parent, id));
    Q_UNUSED(id);
    return new AndroidDeployQtStep(parent);
}

bool AndroidDeployQtStepFactory::canRestore(ProjectExplorer::BuildStepList *parent, const QVariantMap &map) const
{
    return canCreate(parent, ProjectExplorer::idFromMap(map));
}

ProjectExplorer::BuildStep *AndroidDeployQtStepFactory::restore(ProjectExplorer::BuildStepList *parent, const QVariantMap &map)
{
    Q_ASSERT(canRestore(parent, map));
    AndroidDeployQtStep * const step = new AndroidDeployQtStep(parent);
    if (!step->fromMap(map)) {
        delete step;
        return 0;
    }
    return step;
}

bool AndroidDeployQtStepFactory::canClone(ProjectExplorer::BuildStepList *parent, ProjectExplorer::BuildStep *product) const
{
    return canCreate(parent, product->id());
}

ProjectExplorer::BuildStep *AndroidDeployQtStepFactory::clone(ProjectExplorer::BuildStepList *parent, ProjectExplorer::BuildStep *product)
{
    Q_ASSERT(canClone(parent, product));
    return new AndroidDeployQtStep(parent, static_cast<AndroidDeployQtStep *>(product));
}

//////////////////
// AndroidDeployQtStep
/////////////////

AndroidDeployQtStep::AndroidDeployQtStep(ProjectExplorer::BuildStepList *parent)
146
    : ProjectExplorer::BuildStep(parent, Id)
147
148
149
150
151
152
{
    ctor();
}

AndroidDeployQtStep::AndroidDeployQtStep(ProjectExplorer::BuildStepList *parent,
    AndroidDeployQtStep *other)
153
    : ProjectExplorer::BuildStep(parent, other)
154
155
156
157
158
159
{
    ctor();
}

void AndroidDeployQtStep::ctor()
{
160
    m_uninstallPreviousPackage = QtSupport::QtKitInformation::qtVersion(target()->kit())->qtVersion() < QtSupport::QtVersionNumber(5, 4, 0);
161
162
    m_uninstallPreviousPackageRun = false;

163
164
    //: AndroidDeployQtStep default display name
    setDefaultDisplayName(tr("Deploy to Android device"));
165
166
167
168
169
170
171

    connect(this, &AndroidDeployQtStep::askForUninstall,
            this, &AndroidDeployQtStep::slotAskForUninstall,
            Qt::BlockingQueuedConnection);

    connect(this, &AndroidDeployQtStep::setSerialNumber,
            this, &AndroidDeployQtStep::slotSetSerialNumber);
172
173
174
175
}

bool AndroidDeployQtStep::init()
{
BogDan Vatra's avatar
BogDan Vatra committed
176
177
    m_androiddeployqtArgs.clear();

178
    if (AndroidManager::checkForQt51Files(project()->projectDirectory()))
179
        emit addOutput(tr("Found old folder \"android\" in source directory. Qt 5.2 does not use that folder by default."), ErrorOutput);
180
181

    m_targetArch = AndroidManager::targetArch(target());
182
183
184
185
    if (m_targetArch.isEmpty()) {
        emit addOutput(tr("No Android arch set by the .pro file."), ErrorOutput);
        return false;
    }
186
187
188
189
190
191
192
193

    AndroidBuildApkStep *androidBuildApkStep
        = AndroidGlobal::buildStep<AndroidBuildApkStep>(target()->activeBuildConfiguration());
    if (!androidBuildApkStep) {
        emit addOutput(tr("Cannot find the android build step."), ErrorOutput);
        return false;
    }

194
    int deviceAPILevel = AndroidManager::minimumSDK(target());
195
196
197
    AndroidConfigurations::Options options = AndroidConfigurations::None;
    if (androidBuildApkStep->deployAction() == AndroidBuildApkStep::DebugDeployment)
        options = AndroidConfigurations::FilterAndroid5;
198
199
    AndroidDeviceInfo info = AndroidConfigurations::showDeviceDialog(project(), deviceAPILevel, m_targetArch, options);
    if (info.serialNumber.isEmpty() && info.avdname.isEmpty()) // aborted
200
201
        return false;

202
203
204
    m_avdName = info.avdname;
    m_serialNumber = info.serialNumber;

205
    AndroidManager::setDeviceSerialNumber(target(), m_serialNumber);
206

207
    ProjectExplorer::BuildConfiguration *bc = target()->activeBuildConfiguration();
208
209
210
211
212

    QtSupport::BaseQtVersion *version = QtSupport::QtKitInformation::qtVersion(target()->kit());
    if (!version)
        return false;

213
214
    m_uninstallPreviousPackageRun = m_uninstallPreviousPackage;
    if (m_uninstallPreviousPackageRun)
215
        m_manifestName = AndroidManager::manifestPath(target());
216

217
218
219
220
221
222
223
224
    m_useAndroiddeployqt = version->qtVersion() >= QtSupport::QtVersionNumber(5, 4, 0);
    if (m_useAndroiddeployqt) {
        Utils::FileName tmp = AndroidManager::androidQtSupport(target())->androiddeployqtPath(target());
        if (tmp.isEmpty()) {
            emit addOutput(tr("Cannot find the androiddeployqt tool."), ErrorOutput);
            return false;
        }

225
226
        m_command = tmp.toString();
        m_workingDirectory = bc->buildDirectory().appendPath(QLatin1String(Constants::ANDROID_BUILDDIRECTORY)).toString();
227
228
229

        Utils::QtcProcess::addArg(&m_androiddeployqtArgs, QLatin1String("--verbose"));
        Utils::QtcProcess::addArg(&m_androiddeployqtArgs, QLatin1String("--output"));
230
        Utils::QtcProcess::addArg(&m_androiddeployqtArgs, m_workingDirectory);
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
        Utils::QtcProcess::addArg(&m_androiddeployqtArgs, QLatin1String("--no-build"));
        Utils::QtcProcess::addArg(&m_androiddeployqtArgs, QLatin1String("--input"));
        tmp = AndroidManager::androidQtSupport(target())->androiddeployJsonPath(target());
        if (tmp.isEmpty()) {
            emit addOutput(tr("Cannot find the androiddeploy Json file."), ErrorOutput);
            return false;
        }
        Utils::QtcProcess::addArg(&m_androiddeployqtArgs, tmp.toString());

        Utils::QtcProcess::addArg(&m_androiddeployqtArgs, QLatin1String("--deployment"));
        switch (androidBuildApkStep->deployAction()) {
            case AndroidBuildApkStep::MinistroDeployment:
                Utils::QtcProcess::addArg(&m_androiddeployqtArgs, QLatin1String("ministro"));
                break;
            case AndroidBuildApkStep::DebugDeployment:
                Utils::QtcProcess::addArg(&m_androiddeployqtArgs, QLatin1String("debug"));
                break;
            case AndroidBuildApkStep::BundleLibrariesDeployment:
                Utils::QtcProcess::addArg(&m_androiddeployqtArgs, QLatin1String("bundled"));
                break;
        }
BogDan Vatra's avatar
BogDan Vatra committed
252
253
        if (androidBuildApkStep->useGradle())
            Utils::QtcProcess::addArg(&m_androiddeployqtArgs, QLatin1String("--gradle"));
254
255
256
257
258
259
260
261
262
263

        if (androidBuildApkStep->signPackage()) {
            // The androiddeployqt tool is not really written to do stand-alone installations.
            // This hack forces it to use the correct filename for the apk file when installing
            // as a temporary fix until androiddeployqt gets the support. Since the --sign is
            // only used to get the correct file name of the apk, its parameters are ignored.
            Utils::QtcProcess::addArg(&m_androiddeployqtArgs, QLatin1String("--sign"));
            Utils::QtcProcess::addArg(&m_androiddeployqtArgs, QLatin1String("foo"));
            Utils::QtcProcess::addArg(&m_androiddeployqtArgs, QLatin1String("bar"));
        }
264
    } else {
265
        m_uninstallPreviousPackageRun = true;
266
        m_command = AndroidConfigurations::currentConfig().adbToolPath().toString();
BogDan Vatra's avatar
BogDan Vatra committed
267
        m_apkPath = AndroidManager::androidQtSupport(target())->apkPath(target()).toString();
268
        m_workingDirectory = bc->buildDirectory().toString();
269
    }
270
271
    m_environment = bc->environment();

272
    m_buildDirectory = bc->buildDirectory().toString();
273
274

    m_adbPath = AndroidConfigurations::currentConfig().adbToolPath().toString();
275

276
    if (AndroidConfigurations::currentConfig().findAvd(m_avdName).isEmpty())
Daniel Teske's avatar
Daniel Teske committed
277
        AndroidConfigurations::currentConfig().startAVDAsync(m_avdName);
278
279
280
    return true;
}

281
AndroidDeployQtStep::DeployResult AndroidDeployQtStep::runDeploy(QFutureInterface<bool> &fi)
282
{
283
    m_installOk = true;
284
    QString args;
285
    if (m_useAndroiddeployqt) {
286
287
288
289
290
291
        args = m_androiddeployqtArgs;
        if (m_uninstallPreviousPackageRun)
            Utils::QtcProcess::addArg(&args, QLatin1String("--install"));
        else
            Utils::QtcProcess::addArg(&args, QLatin1String("--reinstall"));

292
        if (!m_serialNumber.isEmpty() && !m_serialNumber.startsWith(QLatin1String("????"))) {
293
294
            Utils::QtcProcess::addArg(&args, QLatin1String("--device"));
            Utils::QtcProcess::addArg(&args, m_serialNumber);
295
296
297
        }
    } else {
        if (m_uninstallPreviousPackageRun) {
298
            const QString packageName = AndroidManager::packageName(m_manifestName);
299
            if (packageName.isEmpty()) {
300
                emit addOutput(tr("Cannot find the package name."), ErrorOutput);
301
                return Failure;
302
303
304
            }

            emit addOutput(tr("Uninstall previous package %1.").arg(packageName), MessageOutput);
305
            runCommand(m_adbPath,
306
                       AndroidDeviceInfo::adbSelector(m_serialNumber)
307
                       << QLatin1String("uninstall") << packageName);
308
309
310
311
312
313
314
315
316
        }

        foreach (const QString &arg, AndroidDeviceInfo::adbSelector(m_serialNumber))
            Utils::QtcProcess::addArg(&args, arg);

        Utils::QtcProcess::addArg(&args, QLatin1String("install"));
        Utils::QtcProcess::addArg(&args, QLatin1String("-r"));
        Utils::QtcProcess::addArg(&args, m_apkPath);
    }
317

318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
    m_process = new Utils::QtcProcess;
    m_process->setCommand(m_command, args);
    m_process->setWorkingDirectory(m_workingDirectory);
    m_process->setEnvironment(m_environment);

    if (Utils::HostOsInfo::isWindowsHost())
        m_process->setUseCtrlCStub(true);

    connect(m_process, &Utils::QtcProcess::readyReadStandardOutput,
            this, &AndroidDeployQtStep::processReadyReadStdOutput, Qt::DirectConnection);
    connect(m_process, &Utils::QtcProcess::readyReadStandardError,
            this, &AndroidDeployQtStep::processReadyReadStdError, Qt::DirectConnection);

    m_process->start();

    emit addOutput(tr("Starting: \"%1\" %2")
                   .arg(QDir::toNativeSeparators(m_command), args),
                   BuildStep::MessageOutput);

    while (!m_process->waitForFinished(200)) {
        if (fi.isCanceled()) {
            m_process->kill();
            m_process->waitForFinished();
        }
    }

    QString line = QString::fromLocal8Bit(m_process->readAllStandardError());
    if (!line.isEmpty())
        stdError(line);

    line = QString::fromLocal8Bit(m_process->readAllStandardOutput());
    if (!line.isEmpty())
        stdOutput(line);

    QProcess::ExitStatus exitStatus = m_process->exitStatus();
    int exitCode = m_process->exitCode();
    delete m_process;
    m_process = 0;

    if (exitStatus == QProcess::NormalExit && exitCode == 0) {
        emit addOutput(tr("The process \"%1\" exited normally.").arg(m_command),
                       BuildStep::MessageOutput);
    } else if (exitStatus == QProcess::NormalExit) {
        emit addOutput(tr("The process \"%1\" exited with code %2.")
                       .arg(m_command, QString::number(exitCode)),
                       BuildStep::ErrorMessageOutput);
    } else {
        emit addOutput(tr("The process \"%1\" crashed.").arg(m_command), BuildStep::ErrorMessageOutput);
    }

    if (exitCode == 0 && exitStatus == QProcess::NormalExit) {
        if (!m_installOk) {
            if (!m_uninstallPreviousPackageRun)
                return AskUinstall;
            else
                return Failure;
        }
        return Success;
    }
    return Failure;
}

void AndroidDeployQtStep::slotAskForUninstall()
{
    int button = QMessageBox::critical(0, tr("Install failed"),
                                       tr("Another application with the same package id but signed with "
                                          "different certificate already exists.\n"
                                          "Do you want to uninstall the existing package?"),
                                       QMessageBox::Yes, QMessageBox::No);
    m_askForUinstall = button == QMessageBox::Yes;
}

void AndroidDeployQtStep::slotSetSerialNumber(const QString &serialNumber)
{
    AndroidManager::setDeviceSerialNumber(target(), serialNumber);
}

void AndroidDeployQtStep::run(QFutureInterface<bool> &fi)
{
    if (!m_avdName.isEmpty()) {
398
        QString serialNumber = AndroidConfigurations::currentConfig().waitForAvd(m_avdName, fi);
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
        if (serialNumber.isEmpty()) {
            fi.reportResult(false);
            emit finished();
            return;
        }
        m_serialNumber = serialNumber;
        emit setSerialNumber(serialNumber);
    }

    DeployResult returnValue = runDeploy(fi);
    if (returnValue == AskUinstall) {
        emit askForUninstall();
        if (m_askForUinstall) {
            m_uninstallPreviousPackageRun = true;
            returnValue = runDeploy(fi);
        }
    }
416
417

    emit addOutput(tr("Pulling files necessary for debugging."), MessageOutput);
418

419
    const QString remoteAppProcessFile = systemAppProcessFilePath();
420
    QString localAppProcessFile = QString::fromLatin1("%1/app_process").arg(m_buildDirectory);
421
    runCommand(m_adbPath,
422
               AndroidDeviceInfo::adbSelector(m_serialNumber)
423
               << QLatin1String("pull") << remoteAppProcessFile
424
425
               << localAppProcessFile);
    if (!QFileInfo::exists(localAppProcessFile)) {
426
427
428
        returnValue = Failure;
        emit addOutput(tr("Package deploy: Failed to pull \"%1\" to \"%2\".")
                       .arg(remoteAppProcessFile).arg(localAppProcessFile), ErrorMessageOutput);
429
    }
430
    runCommand(m_adbPath,
431
432
433
               AndroidDeviceInfo::adbSelector(m_serialNumber) << QLatin1String("pull")
               << QLatin1String("/system/lib/libc.so")
               << QString::fromLatin1("%1/libc.so").arg(m_buildDirectory));
434
435
436

    fi.reportResult(returnValue == Success ? true : false);
    fi.reportFinished();
437
438
439
440
441
}

void AndroidDeployQtStep::runCommand(const QString &program, const QStringList &arguments)
{
    QProcess buildProc;
hjk's avatar
hjk committed
442
    emit addOutput(tr("Package deploy: Running command \"%1 %2\".").arg(program).arg(arguments.join(QLatin1Char(' '))), BuildStep::MessageOutput);
443
444
    buildProc.start(program, arguments);
    if (!buildProc.waitForStarted()) {
445
        emit addOutput(tr("Packaging error: Could not start command \"%1 %2\". Reason: %3")
hjk's avatar
hjk committed
446
            .arg(program).arg(arguments.join(QLatin1Char(' '))).arg(buildProc.errorString()), BuildStep::ErrorMessageOutput);
447
448
449
450
451
        return;
    }
    if (!buildProc.waitForFinished(2 * 60 * 1000)
            || buildProc.error() != QProcess::UnknownError
            || buildProc.exitCode() != 0) {
Robert Loehning's avatar
Robert Loehning committed
452
        QString mainMessage = tr("Packaging error: Command \"%1 %2\" failed.")
hjk's avatar
hjk committed
453
                .arg(program).arg(arguments.join(QLatin1Char(' ')));
454
        if (buildProc.error() != QProcess::UnknownError)
455
            mainMessage += QLatin1Char(' ') + tr("Reason: %1").arg(buildProc.errorString());
456
457
458
459
460
461
        else
            mainMessage += tr("Exit code: %1").arg(buildProc.exitCode());
        emit addOutput(mainMessage, BuildStep::ErrorMessageOutput);
    }
}

462
463
464
465
QString AndroidDeployQtStep::systemAppProcessFilePath() const
{
    QProcess proc;
    const QStringList args =
466
            QStringList() << AndroidDeviceInfo::adbSelector(m_serialNumber) << QLatin1String("shell")
467
468
469
470
471
472
                          << QLatin1String("readlink -f -s /system/bin/app_process");
    proc.start(m_adbPath, args);
    proc.waitForFinished();
    return QString::fromUtf8(proc.readAll()).trimmed();
}

473
ProjectExplorer::BuildStepConfigWidget *AndroidDeployQtStep::createConfigWidget()
474
{
475
    return new AndroidDeployQtWidget(this);
476
477
}

478
void AndroidDeployQtStep::processReadyReadStdOutput()
479
{
480
481
482
483
484
    m_process->setReadChannel(QProcess::StandardOutput);
    while (m_process->canReadLine()) {
        QString line = QString::fromLocal8Bit(m_process->readLine());
        stdOutput(line);
    }
485
486
}

487
void AndroidDeployQtStep::stdOutput(const QString &line)
488
{
489
490
    if (line.contains(InstallFailedInconsistentCertificatesString)
            || line.contains(InstallFailedInconsistentCertificatesString2))
491
        m_installOk = false;
492
    emit addOutput(line, BuildStep::NormalOutput, BuildStep::DontAppendNewline);
493
494
}

495
void AndroidDeployQtStep::processReadyReadStdError()
496
{
497
498
499
500
    m_process->setReadChannel(QProcess::StandardError);
    while (m_process->canReadLine()) {
        QString line = QString::fromLocal8Bit(m_process->readLine());
        stdError(line);
501
    }
502
503
504
505
}

void AndroidDeployQtStep::stdError(const QString &line)
{
506
507
    if (line.contains(InstallFailedInconsistentCertificatesString)
            || line.contains(InstallFailedInconsistentCertificatesString2))
508
509
        m_installOk = false;
    emit addOutput(line, BuildStep::ErrorOutput, BuildStep::DontAppendNewline);
510
511
512
513
}

bool AndroidDeployQtStep::fromMap(const QVariantMap &map)
{
514
    m_uninstallPreviousPackage = map.value(UninstallPreviousPackageKey, m_uninstallPreviousPackage).toBool();
515
516
517
518
519
520
    return ProjectExplorer::BuildStep::fromMap(map);
}

QVariantMap AndroidDeployQtStep::toMap() const
{
    QVariantMap map = ProjectExplorer::BuildStep::toMap();
521
    map.insert(UninstallPreviousPackageKey, m_uninstallPreviousPackage);
522
523
524
    return map;
}

525
void AndroidDeployQtStep::setUninstallPreviousPackage(bool uninstall)
526
{
527
    m_uninstallPreviousPackage = uninstall;
528
529
}

530
531
bool AndroidDeployQtStep::runInGuiThread() const
{
532
    return false;
533
534
}

535
AndroidDeployQtStep::UninstallType AndroidDeployQtStep::uninstallPreviousPackage()
536
{
537
538
539
    if (QtSupport::QtKitInformation::qtVersion(target()->kit())->qtVersion() < QtSupport::QtVersionNumber(5, 4, 0))
        return ForceUnintall;
    return m_uninstallPreviousPackage ? Uninstall : Keep;
540
}