Skip to content
Snippets Groups Projects
androidpackagecreationstep.cpp 34.12 KiB
/**************************************************************************
**
** Copyright (c) 2013 BogDan Vatra <bog_dan_ro@yahoo.com>
** Contact: http://www.qt-project.org/legal
**
** 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
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/

#include "androidpackagecreationstep.h"

#include "androidconstants.h"
#include "androiddeploystep.h"
#include "androidglobal.h"
#include "androidpackagecreationwidget.h"
#include "androidmanager.h"
#include "androidgdbserverkitinformation.h"
#include "androidtoolchain.h"

#include <projectexplorer/buildsteplist.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/runconfiguration.h>
#include <projectexplorer/target.h>
#include <qt4projectmanager/qt4buildconfiguration.h>
#include <qt4projectmanager/qt4project.h>
#include <qt4projectmanager/qt4nodes.h>
#include <qtsupport/qtkitinformation.h>

#include <coreplugin/icore.h>
#include <coreplugin/fileutils.h>

#include <QAbstractListModel>
#include <QProcess>
#include <QVector>
#include <QPair>
#include <QWidget>
#include <QMessageBox>
#include <QInputDialog>
#include <QMainWindow>

using namespace ProjectExplorer;
using namespace ProjectExplorer::Constants;

namespace Android {
namespace Internal {

namespace {
    const QLatin1String KeystoreLocationKey("KeystoreLocation");
    const QLatin1String AliasString("Alias name:");
    const QLatin1String CertificateSeparator("*******************************************");
}

using namespace Qt4ProjectManager;

class CertificatesModel: public QAbstractListModel
{
public:
    CertificatesModel(const QString &rowCertificates, QObject *parent)
        : QAbstractListModel(parent)
    {
        int from = rowCertificates.indexOf(AliasString);
        QPair<QString, QString> item;
        while (from > -1) {
            from += 11;// strlen(AliasString);
            const int eol = rowCertificates.indexOf(QLatin1Char('\n'), from);
            item.first = rowCertificates.mid(from, eol - from).trimmed();
            const int eoc = rowCertificates.indexOf(CertificateSeparator, eol);
            item.second = rowCertificates.mid(eol + 1, eoc - eol - 2).trimmed();
            from = rowCertificates.indexOf(AliasString, eoc);
            m_certs.push_back(item);
        }
    }

protected:
    int rowCount(const QModelIndex &parent = QModelIndex()) const
    {
        if (parent.isValid())
            return 0;
        return m_certs.size();
    }

    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const
    {
        if (!index.isValid() || (role != Qt::DisplayRole && role != Qt::ToolTipRole))
            return QVariant();
        if (role == Qt::DisplayRole)
            return m_certs[index.row()].first;
        return m_certs[index.row()].second;
    }

private:
    QVector<QPair<QString, QString> > m_certs;
};


AndroidPackageCreationStep::AndroidPackageCreationStep(BuildStepList *bsl)
    : BuildStep(bsl, CreatePackageId)
{
    ctor();
}

AndroidPackageCreationStep::AndroidPackageCreationStep(BuildStepList *bsl,
    AndroidPackageCreationStep *other)
    : BuildStep(bsl, other)
{
    ctor();
}

void AndroidPackageCreationStep::ctor()
{
    setDefaultDisplayName(tr("Packaging for Android"));
    m_openPackageLocation = true;
    m_bundleQt = false;
    connect(&m_outputParser, SIGNAL(addTask(ProjectExplorer::Task)), this, SIGNAL(addTask(ProjectExplorer::Task)));
}

bool AndroidPackageCreationStep::init()
{
    const Qt4BuildConfiguration *bc = qobject_cast<Qt4BuildConfiguration *>(target()->activeBuildConfiguration());
    if (!bc) {
        raiseError(tr("Cannot create Android package: current build configuration is not Qt 4."));
        return false;
    }
    Qt4Project *project = static_cast<Qt4Project *>(target()->project());
    m_outputParser.setProjectFileList(project->files(Project::AllFiles));

    // Copying
    m_androidDir = AndroidManager::dirPath(target());
    Utils::FileName path = m_androidDir;
    QString androidTargetArch = project->rootQt4ProjectNode()->singleVariableValue(Qt4ProjectManager::AndroidArchVar);
    if (androidTargetArch.isEmpty()) {
        raiseError(tr("Cannot create Android package: No ANDROID_TARGET_ARCH set in make spec."));
        return false;
    }

    Utils::FileName androidLibPath = path.appendPath(QLatin1String("libs/") + androidTargetArch);
    m_gdbServerDestination = androidLibPath.appendPath(QLatin1String("gdbserver"));
    m_gdbServerSource = AndroidGdbServerKitInformation::gdbServer(target()->kit());
    m_debugBuild = bc->qmakeBuildConfiguration() & QtSupport::BaseQtVersion::DebugBuild;

    if (!AndroidManager::createAndroidTemplatesIfNecessary(target()))
        return false;

    AndroidManager::updateTarget(target(), AndroidManager::targetSDK(target()), AndroidManager::applicationName(target()));
    m_antToolPath = AndroidConfigurations::instance().antToolPath();
    m_apkPathUnsigned = AndroidManager::apkPath(target(), AndroidManager::ReleaseBuildUnsigned);
    m_apkPathSigned = AndroidManager::apkPath(target(), AndroidManager::ReleaseBuildSigned);
    m_keystorePathForRun = m_keystorePath;
    m_certificatePasswdForRun = m_certificatePasswd;
    m_jarSigner = AndroidConfigurations::instance().jarsignerPath();
    m_zipAligner = AndroidConfigurations::instance().zipalignPath();

    ProjectExplorer::ToolChain *tc = ProjectExplorer::ToolChainKitInformation::toolChain(target()->kit());
    if (tc->type() != QLatin1String(Constants::ANDROID_TOOLCHAIN_TYPE))
        return false;

    initCheckRequiredLibrariesForRun();
    return true;
}

void AndroidPackageCreationStep::run(QFutureInterface<bool> &fi)
{
    fi.reportResult(createPackage());
}

BuildStepConfigWidget *AndroidPackageCreationStep::createConfigWidget()
{
    return new AndroidPackageCreationWidget(this);
}

static inline QString msgCannotFindElfInformation()
{
    return AndroidPackageCreationStep::tr("Cannot find ELF information");
}

static inline QString msgCannotFindExecutable(const QString &appPath)
{
    return AndroidPackageCreationStep::tr("Cannot find '%1'.\n"
        "Please make sure your application is "
        "built successfully and is selected in Application tab ('Run option').").arg(appPath);
}

static void parseSharedLibs(const QByteArray &buffer, QStringList *libs)
{
#if defined(_WIN32)
    QList<QByteArray> lines = buffer.trimmed().split('\r');
#else
    QList<QByteArray> lines = buffer.trimmed().split('\n');
#endif
    foreach (QByteArray line, lines) {
        if (line.contains("(NEEDED)") && line.contains("Shared library:") ) {
            const int pos = line.lastIndexOf('[') + 1;
            (*libs) << QString::fromLatin1(line.mid(pos, line.length() - pos - 1));
        }
    }
}

void markNeeded(const QString &library,
                const QVector<AndroidManager::Library> &dependencies,
                QMap<QString, bool> *neededMap)
{
    if (!neededMap->contains(library))
        return;
    if (neededMap->value(library))
        return;
    neededMap->insert(library, true);
    for (int i = 0; i < dependencies.size(); ++i) {
        if (dependencies.at(i).name == library) {
            foreach (const QString &dependency, dependencies.at(i).dependencies)
                markNeeded(dependency, dependencies, neededMap);
            break;
        }
    }
}

QStringList requiredLibraries(QVector<AndroidManager::Library> availableLibraries,
                      const QStringList &checkedLibs, const QStringList &dependencies)
{
    QMap<QString, bool> neededLibraries;
    QVector<AndroidManager::Library>::const_iterator it, end;
    it = availableLibraries.constBegin();
    end = availableLibraries.constEnd();

    for (; it != end; ++it)
        neededLibraries[(*it).name] = false;

    // Checked items are always needed
    foreach (const QString &lib, checkedLibs)
        markNeeded(lib, availableLibraries, &neededLibraries);

    foreach (const QString &lib, dependencies) {
        if (lib.startsWith(QLatin1String("lib"))
                && lib.endsWith(QLatin1String(".so")))
            markNeeded(lib.mid(3, lib.size() - 6), availableLibraries, &neededLibraries);
    }

    for (int i = availableLibraries.size() - 1; i>= 0; --i)
        if (!neededLibraries.value(availableLibraries.at(i).name))
            availableLibraries.remove(i);

    QStringList requiredLibraries;
    foreach (const AndroidManager::Library &lib, availableLibraries) {
        if (neededLibraries.value(lib.name))
            requiredLibraries << lib.name;
    }
    return requiredLibraries;
}

void AndroidPackageCreationStep::checkRequiredLibraries()
{
    QProcess readelfProc;
    QString appPath = AndroidManager::targetApplicationPath(target());
    if (!QFile::exists(appPath)) {
        raiseError(msgCannotFindElfInformation(), msgCannotFindExecutable(appPath));
        return;
    }

    ProjectExplorer::ToolChain *tc = ProjectExplorer::ToolChainKitInformation::toolChain(target()->kit());
    if (tc->type() != QLatin1String(Constants::ANDROID_TOOLCHAIN_TYPE))
        return;
    AndroidToolChain *atc = static_cast<AndroidToolChain *>(tc);

    readelfProc.start(AndroidConfigurations::instance().readelfPath(target()->activeRunConfiguration()->abi().architecture(), atc->ndkToolChainVersion()).toString(),
                      QStringList() << QLatin1String("-d") << QLatin1String("-W") << appPath);
    if (!readelfProc.waitForFinished(-1)) {
        readelfProc.kill();
        return;
    }
    QStringList libs;
    parseSharedLibs(readelfProc.readAll(), &libs);
    AndroidManager::setQtLibs(target(), requiredLibraries(AndroidManager::availableQtLibsWithDependencies(target()),
                                                          AndroidManager::qtLibs(target()), libs));

    QStringList checkedLibs = AndroidManager::prebundledLibs(target());
    QStringList prebundledLibraries;
    foreach (const QString &qtLib, AndroidManager::availableQtLibs(target())) {
        if (libs.contains(qtLib) || checkedLibs.contains(qtLib))
            prebundledLibraries << qtLib;
    }
    AndroidManager::setPrebundledLibs(target(), prebundledLibraries);
    emit updateRequiredLibrariesModels();
}

void AndroidPackageCreationStep::initCheckRequiredLibrariesForRun()
{
    ProjectExplorer::ToolChain *tc = ProjectExplorer::ToolChainKitInformation::toolChain(target()->kit());
    if (tc->type() != QLatin1String(Constants::ANDROID_TOOLCHAIN_TYPE))
        return;
    AndroidToolChain *atc = static_cast<AndroidToolChain *>(tc);

    m_appPath = Utils::FileName::fromString(AndroidManager::targetApplicationPath(target()));
    m_readElf = AndroidConfigurations::instance().readelfPath(target()->activeRunConfiguration()->abi().architecture(),
                                                              atc->ndkToolChainVersion());
    m_qtLibs = AndroidManager::qtLibs(target());
    m_availableQtLibs = AndroidManager::availableQtLibsWithDependencies(target());
    m_prebundledLibs = AndroidManager::prebundledLibs(target());
}

void AndroidPackageCreationStep::getBundleInformation()
{
    m_bundleQt = AndroidManager::bundleQt(target());
    if (m_bundleQt) {
        m_bundledJars = AndroidManager::loadLocalJars(target()).split(QLatin1Char(':'),
                                                                      QString::SkipEmptyParts);
        m_otherBundledFiles = AndroidManager::loadLocalBundledFiles(target()).split(QLatin1Char(':'),
                                                                                    QString::SkipEmptyParts);
    }
}

void AndroidPackageCreationStep::checkRequiredLibrariesForRun()
{
    QProcess readelfProc;
    if (!m_appPath.toFileInfo().exists()) {
        raiseError(msgCannotFindElfInformation(), msgCannotFindExecutable(m_appPath.toUserOutput()));
        return;
    }
    readelfProc.start(m_readElf.toString(), QStringList() << QLatin1String("-d") << QLatin1String("-W") << m_appPath.toUserOutput());
    if (!readelfProc.waitForFinished(-1)) {
        readelfProc.kill();
        return;
    }
    QStringList libs;
    parseSharedLibs(readelfProc.readAll(), &libs);

    m_qtLibsWithDependencies = requiredLibraries(m_availableQtLibs, m_qtLibs, libs);
    QMetaObject::invokeMethod(this, "setQtLibs",Qt::BlockingQueuedConnection,
                              Q_ARG(QStringList, m_qtLibsWithDependencies));

    QMetaObject::invokeMethod(this, "getBundleInformation", Qt::BlockingQueuedConnection);
    QStringList prebundledLibraries;
    foreach (const AndroidManager::Library &qtLib, m_availableQtLibs) {
        if (libs.contains(qtLib.name) || m_prebundledLibs.contains(qtLib.name))
            prebundledLibraries << qtLib.name;
    }

    QMetaObject::invokeMethod(this, "setPrebundledLibs", Qt::BlockingQueuedConnection,
                              Q_ARG(QStringList, prebundledLibraries));
    emit updateRequiredLibrariesModels();
}

void AndroidPackageCreationStep::setQtLibs(const QStringList &qtLibs)
{
    AndroidManager::setQtLibs(target(), qtLibs);
}

void AndroidPackageCreationStep::setPrebundledLibs(const QStringList &prebundledLibs)
{
    AndroidManager::setPrebundledLibs(target(), prebundledLibs);
}

Utils::FileName AndroidPackageCreationStep::keystorePath()
{
    return m_keystorePath;
}

void AndroidPackageCreationStep::setKeystorePath(const Utils::FileName &path)
{
    m_keystorePath = path;
    m_certificatePasswd.clear();
    m_keystorePasswd.clear();
}

void AndroidPackageCreationStep::setKeystorePassword(const QString &pwd)
{
    m_keystorePasswd = pwd;
}

void AndroidPackageCreationStep::setCertificateAlias(const QString &alias)
{
    m_certificateAlias = alias;
}

void AndroidPackageCreationStep::setCertificatePassword(const QString &pwd)
{
    m_certificatePasswd = pwd;
}

void AndroidPackageCreationStep::setOpenPackageLocation(bool open)
{
    m_openPackageLocation = open;
}

QAbstractItemModel *AndroidPackageCreationStep::keystoreCertificates()
{
    QString rawCerts;
    QProcess keytoolProc;
    while (!rawCerts.length() || !m_keystorePasswd.length()) {
        QStringList params;
        params << QLatin1String("-list") << QLatin1String("-v") << QLatin1String("-keystore") << m_keystorePath.toUserOutput() << QLatin1String("-storepass");
        if (!m_keystorePasswd.length())
            keystorePassword();
        if (!m_keystorePasswd.length())
            return 0;
        params << m_keystorePasswd;
        keytoolProc.start(AndroidConfigurations::instance().keytoolPath().toString(), params);
        if (!keytoolProc.waitForStarted() || !keytoolProc.waitForFinished()) {
            QMessageBox::critical(0, tr("Error"),
                                  tr("Failed to run keytool"));
            return 0;
        }

        if (keytoolProc.exitCode()) {
            QMessageBox::critical(0, tr("Error"),
                                  tr("Invalid password"));
            m_keystorePasswd.clear();
        }
        rawCerts = QString::fromLatin1(keytoolProc.readAllStandardOutput());
    }
    return new CertificatesModel(rawCerts, this);
}

bool AndroidPackageCreationStep::fromMap(const QVariantMap &map)
{
    if (!BuildStep::fromMap(map))
        return false;
    m_keystorePath = Utils::FileName::fromString(map.value(KeystoreLocationKey).toString());
    return true;
}

QVariantMap AndroidPackageCreationStep::toMap() const
{
    QVariantMap map(BuildStep::toMap());
    map.insert(KeystoreLocationKey, m_keystorePath.toString());
    return map;
}

QStringList AndroidPackageCreationStep::collectRelativeFilePaths(const QString &parentPath)
{
    QStringList relativeFilePaths;

    QDirIterator libsIt(parentPath, QDir::NoFilter, QDirIterator::Subdirectories);
    int pos = parentPath.size();
    while (libsIt.hasNext()) {
        libsIt.next();
        if (!libsIt.fileInfo().isDir())
            relativeFilePaths.append(libsIt.filePath().mid(pos));
    }

    return relativeFilePaths;
}

void AndroidPackageCreationStep::collectFiles(QList<DeployItem> *deployList,
                                              QList<DeployItem> *pluginsAndImportsList)
{
    Q_ASSERT(deployList != 0);
    QtSupport::BaseQtVersion *version = QtSupport::QtKitInformation::qtVersion(target()->kit());
    if (!version)
        return;

    Qt4Project *project = static_cast<Qt4Project *>(target()->project());
    QString androidTargetArch = project->rootQt4ProjectNode()->singleVariableValue(Qt4ProjectManager::AndroidArchVar);

    QString androidAssetsPath = m_androidDir.toString() + QLatin1String("/assets/");
    QString androidJarPath = m_androidDir.toString() + QLatin1String("/libs/");
    QString androidLibPath = m_androidDir.toString() + QLatin1String("/libs/") + androidTargetArch;

    QString qtVersionSourcePath = version->sourcePath().toString();

    foreach (QString qtLib, m_qtLibsWithDependencies) {
        QString fullPath = qtVersionSourcePath
            + QLatin1String("/lib/lib")
            + qtLib
            + QLatin1String(".so");
        QString destinationPath = androidLibPath
                + QLatin1String("/lib")
                + qtLib
                + QLatin1String(".so");

        // If the Qt lib/ folder contains libgnustl_shared.so, don't deploy it from there, since
        // it will be deployed directly from the NDK instead.
        if (qtLib != QLatin1String("gnustl_shared")) {
            DeployItem deployItem(fullPath, 0, destinationPath, true);
            deployList->append(deployItem);
        }
    }

    if (!androidTargetArch.isEmpty()) {
        ProjectExplorer::ToolChain *tc = ProjectExplorer::ToolChainKitInformation::toolChain(target()->kit());
        if (tc->type() != QLatin1String(Constants::ANDROID_TOOLCHAIN_TYPE))
            return;

        AndroidToolChain *atc = static_cast<AndroidToolChain *>(tc);

        QString libgnustl = AndroidManager::libGnuStl(androidTargetArch, atc->ndkToolChainVersion());
        DeployItem deployItem(libgnustl, 0, androidLibPath + QLatin1String("/libgnustl_shared.so"), false);
        deployList->append(deployItem);
    }

    foreach (QString jar, m_bundledJars) {
        QString fullPath = qtVersionSourcePath + QLatin1Char('/') + jar;
        QFileInfo fileInfo(fullPath);
        if (fileInfo.exists()) {
            QString destinationPath = androidJarPath
                    + AndroidManager::libraryPrefix()
                    + fileInfo.fileName();
            deployList->append(DeployItem(fullPath, 0, destinationPath, true));
        }
    }

    QSet<QString> alreadyListed;
    foreach (QString bundledFile, m_otherBundledFiles) {
        QStringList allFiles;
        if (QFileInfo(qtVersionSourcePath + QLatin1Char('/') + bundledFile).isDir()) {
            if (!bundledFile.endsWith(QLatin1Char('/')))
                bundledFile.append(QLatin1Char('/'));

            allFiles = collectRelativeFilePaths(qtVersionSourcePath + QLatin1Char('/') + bundledFile);
        } else {
            // If we need to bundle a specific file, we just add an empty string and the file
            // names and data will be prepared correctly in the loop below.
            allFiles = QStringList(QString());
        }

        foreach (QString file, allFiles) {
            QString fullPath = qtVersionSourcePath + QLatin1Char('/') + bundledFile + file;
            if (alreadyListed.contains(fullPath))
                continue;

            alreadyListed.insert(fullPath);

            QString garbledFileName;
            QString destinationPath;
            bool shouldStrip = false;

            QString fullFileName = bundledFile + file;
            if (fullFileName.endsWith(QLatin1String(".so"))) {
                if (fullFileName.startsWith(QLatin1String("lib/"))) {
                    // Special case when the destination folder is lib/
                    // Since this is also the source folder, there is no need to garble the file
                    // name and copy it. We also won't have write access to this folder, so we
                    // couldn't if we wanted to.
                    garbledFileName = fullFileName.mid(sizeof("lib/") - 1);
                } else {
                    garbledFileName = QLatin1String("lib")
                        + AndroidManager::libraryPrefix()
                        + QString(fullFileName).replace(QLatin1Char('/'), QLatin1Char('_'));
                }
                destinationPath = androidLibPath + QLatin1Char('/') + garbledFileName;
                shouldStrip = true;
            } else {
                garbledFileName = AndroidManager::libraryPrefix() + QLatin1Char('/') + fullFileName;
                destinationPath = androidAssetsPath + garbledFileName;
            }

            deployList->append(DeployItem(fullPath, 0, destinationPath, shouldStrip));
            pluginsAndImportsList->append(DeployItem(garbledFileName,
                                                     0,
                                                     fullFileName,
                                                     shouldStrip));
        }
    }
}

void AndroidPackageCreationStep::removeManagedFilesFromPackage()
{
    // Clean up all files managed by Qt Creator
    {
        QString androidLibPath = m_androidDir.toString() + QLatin1String("/libs/");
        QDirIterator dirIt(m_androidDir.toString(), QDirIterator::Subdirectories);
        while (dirIt.hasNext()) {
            dirIt.next();

            if (!dirIt.fileInfo().isDir()) {
                bool isQtLibrary = dirIt.fileInfo().path().startsWith(androidLibPath)
                        && dirIt.fileName().startsWith(QLatin1String("libQt5"))
                        && dirIt.fileName().endsWith(QLatin1String(".so"));

                if (dirIt.filePath().contains(AndroidManager::libraryPrefix()) || isQtLibrary)
                    QFile::remove(dirIt.filePath());
            }
        }
    }

    removeDirectory(m_androidDir.toString() + QLatin1String("/assets/") + AndroidManager::libraryPrefix());
}

void AndroidPackageCreationStep::copyFilesIntoPackage(const QList<DeployItem> &deployList)
{
    foreach (DeployItem item, deployList) {
        QFileInfo info(item.remoteFileName);
        if (info.exists())
            QFile::remove(item.remoteFileName);
        else
            QDir().mkpath(info.absolutePath());

        QFile::copy(item.localFileName, item.remoteFileName);
    }
}

void AndroidPackageCreationStep::stripFiles(const QList<DeployItem> &deployList)
{

    QStringList fileList;
    foreach (DeployItem item, deployList)
        if (item.needsStrip)
            fileList.append(item.remoteFileName);

    ProjectExplorer::ToolChain *tc = ProjectExplorer::ToolChainKitInformation::toolChain(target()->kit());
    if (tc->type() != QLatin1String(Constants::ANDROID_TOOLCHAIN_TYPE))
        return;

    AndroidToolChain *atc = static_cast<AndroidToolChain *>(tc);
    stripAndroidLibs(fileList,
                     target()->activeRunConfiguration()->abi().architecture(),
                     atc->ndkToolChainVersion());
}

void AndroidPackageCreationStep::updateXmlForFiles(const QStringList &inLibList,
                                                   const QStringList &inAssetsList)
{
    AndroidManager::setBundledInLib(target(), inLibList);
    AndroidManager::setBundledInAssets(target(), inAssetsList);
}


bool AndroidPackageCreationStep::createPackage()
{
    checkRequiredLibrariesForRun();

    emit addOutput(tr("Copy Qt app & libs to Android package ..."), MessageOutput);

    QStringList build;
    build << QLatin1String("clean");
    QFile::remove(m_gdbServerDestination.toString());
    if (m_debugBuild || !m_certificateAlias.length()) {
        build << QLatin1String("debug");
        QDir dir;
        dir.mkpath(m_gdbServerDestination.toFileInfo().absolutePath());
        if (!QFile::copy(m_gdbServerSource.toString(), m_gdbServerDestination.toString())) {
            raiseError(tr("Can't copy gdbserver from '%1' to '%2'").arg(m_gdbServerSource.toUserOutput())
                       .arg(m_gdbServerDestination.toUserOutput()));
            return false;
        }
    } else {
        build << QLatin1String("release");
    }

    QList<DeployItem> deployFiles;
    QList<DeployItem> importsAndPlugins;

    QtSupport::BaseQtVersion *version = QtSupport::QtKitInformation::qtVersion(target()->kit());

    // Qt 5 supports bundling libraries inside the apk. We guard the code for Qt 5 to be sure we
    // do not disrupt existing projects.
    if (version && version->qtVersion() >= QtSupport::QtVersionNumber(5, 0, 0)) {
        bool bundleQt = AndroidManager::bundleQt(target());

        // Collect the files to bundle in the package
        if (bundleQt)
            collectFiles(&deployFiles, &importsAndPlugins);

        // Remove files from package if they are not needed
        removeManagedFilesFromPackage();

        // Deploy files to package
        if (bundleQt) {
            copyFilesIntoPackage(deployFiles);
            stripFiles(deployFiles);

            QStringList inLibList;
            QStringList inAssetsList;
            foreach (DeployItem deployItem, importsAndPlugins) {
                QString conversionInfo = deployItem.localFileName
                        + QLatin1Char(':')
                        + deployItem.remoteFileName;

                if (deployItem.localFileName.endsWith(QLatin1String(".so")))
                    inLibList.append(conversionInfo);
                else
                    inAssetsList.append(conversionInfo);
            }

            QMetaObject::invokeMethod(this,
                                      "updateXmlForFiles",
                                      Qt::BlockingQueuedConnection,
                                      Q_ARG(QStringList, inLibList),
                                      Q_ARG(QStringList, inAssetsList));
        }
    }

    emit addOutput(tr("Creating package file ..."), MessageOutput);

    QProcess *const buildProc = new QProcess;

    connect(buildProc, SIGNAL(readyReadStandardOutput()), this,
        SLOT(handleBuildStdOutOutput()));
    connect(buildProc, SIGNAL(readyReadStandardError()), this,
        SLOT(handleBuildStdErrOutput()));

    buildProc->setWorkingDirectory(m_androidDir.toString());

    if (!runCommand(buildProc, m_antToolPath.toString(), build)) {
        disconnect(buildProc, 0, this, 0);
        buildProc->deleteLater();
        return false;
    }

    if (!(m_debugBuild) && m_certificateAlias.length()) {
        emit addOutput(tr("Signing package ..."), MessageOutput);
        while (true) {
            if (m_certificatePasswdForRun.isEmpty())
                QMetaObject::invokeMethod(this, "certificatePassword", Qt::BlockingQueuedConnection);

            if (m_certificatePasswdForRun.isEmpty()) {
                disconnect(buildProc, 0, this, 0);
                buildProc->deleteLater();
                return false;
            }

            QByteArray keyPass = m_certificatePasswdForRun.toUtf8();
            build.clear();
            build << QLatin1String("-verbose") << QLatin1String("-keystore") << m_keystorePathForRun.toUserOutput()
                  << QLatin1String("-storepass") << m_keystorePasswd
                  << m_apkPathUnsigned.toUserOutput()
                  << m_certificateAlias;
            buildProc->start(m_jarSigner.toString(), build);
            if (!buildProc->waitForStarted()) {
                disconnect(buildProc, 0, this, 0);
                buildProc->deleteLater();
                return false;
            }

            keyPass += '\n';
            buildProc->write(keyPass);
            buildProc->waitForBytesWritten();
            buildProc->waitForFinished();

            if (!buildProc->exitCode())
                break;
            emit addOutput(tr("Failed, try again"), ErrorMessageOutput);
            m_certificatePasswdForRun.clear();
        }
        build.clear();
        build << QLatin1String("-f") << QLatin1String("-v") << QLatin1String("4") << m_apkPathUnsigned.toString() << m_apkPathSigned.toString();
        buildProc->start(m_zipAligner.toString(), build);
        buildProc->waitForFinished();
        if (!buildProc->exitCode()) {
            QFile::remove(m_apkPathUnsigned.toString());
            emit addOutput(tr("Release signed package created to %1")
                           .arg(m_apkPathSigned.toUserOutput())
                           , MessageOutput);

            if (m_openPackageLocation)
                QMetaObject::invokeMethod(this, "showInGraphicalShell", Qt::QueuedConnection);
        }
    }
    emit addOutput(tr("Package created."), BuildStep::MessageOutput);
    disconnect(buildProc, 0, this, 0);
    buildProc->deleteLater();
    return true;
}

void AndroidPackageCreationStep::stripAndroidLibs(const QStringList & files, Abi::Architecture architecture, const QString &ndkToolchainVersion)
{
    QProcess stripProcess;
    foreach (const QString &file, files) {
        stripProcess.start(AndroidConfigurations::instance().stripPath(architecture, ndkToolchainVersion).toString(),
                           QStringList()<<QLatin1String("--strip-unneeded") << file);
        stripProcess.waitForStarted();
        if (!stripProcess.waitForFinished())
            stripProcess.kill();
    }
}

bool AndroidPackageCreationStep::removeDirectory(const QString &dirPath)
{
    QDir dir(dirPath);
    if (!dir.exists())
        return true;

    const QStringList &files
        = dir.entryList(QDir::Files | QDir::Hidden | QDir::System);
    foreach (const QString &fileName, files) {
        if (!dir.remove(fileName))
            return false;
    }

    const QStringList &subDirs
        = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
    foreach (const QString &subDirName, subDirs) {
        if (!removeDirectory(dirPath + QLatin1Char('/') + subDirName))
            return false;
    }

    return dir.rmdir(dirPath);
}

bool AndroidPackageCreationStep::runCommand(QProcess *buildProc
    , const QString &program, const QStringList &arguments)
{
    emit addOutput(tr("Package deploy: Running command '%1 %2'.").arg(program).arg(arguments.join(QLatin1String(" "))), BuildStep::MessageOutput);
    buildProc->start(program, arguments);
    if (!buildProc->waitForStarted()) {
        raiseError(tr("Packaging failed."),
                   tr("Packaging error: Could not start command '%1 %2'. Reason: %3")
                               .arg(program).arg(arguments.join(QLatin1String(" "))).arg(buildProc->errorString()));
        return false;
    }
    buildProc->waitForFinished(-1);

    handleProcessOutput(buildProc, false);
    handleProcessOutput(buildProc, true);

    if (buildProc->error() != QProcess::UnknownError
        || buildProc->exitCode() != 0) {
        QString mainMessage = tr("Packaging Error: Command '%1 %2' failed.")
                .arg(program).arg(arguments.join(QLatin1String(" ")));
        if (buildProc->error() != QProcess::UnknownError)
            mainMessage += tr(" Reason: %1").arg(buildProc->errorString());
        else
            mainMessage += tr("Exit code: %1").arg(buildProc->exitCode());
        raiseError(mainMessage);
        return false;
    }
    return true;
}

void AndroidPackageCreationStep::handleBuildStdOutOutput()
{
    QProcess *const process = qobject_cast<QProcess *>(sender());
    if (!process)
        return;
    handleProcessOutput(process, false);
}

void AndroidPackageCreationStep::handleBuildStdErrOutput()
{
    QProcess *const process = qobject_cast<QProcess *>(sender());
    if (!process)
        return;

    handleProcessOutput(process, true);
}

void AndroidPackageCreationStep::handleProcessOutput(QProcess *process, bool stdErr)
{
    process->setReadChannel(stdErr ? QProcess::StandardError : QProcess::StandardOutput);
    while (process->canReadLine()) {
        QString line = QString::fromLocal8Bit(process->readLine());
        if (stdErr)
            m_outputParser.stdError(line);
        else
            m_outputParser.stdOutput(line);
        emit addOutput(line, stdErr ? BuildStep::ErrorOutput
                                    : BuildStep::NormalOutput,
                       BuildStep::DontAppendNewline);
    }
}

void AndroidPackageCreationStep::keystorePassword()
{
    m_keystorePasswd.clear();
    bool ok;
    QString text = QInputDialog::getText(0, tr("Keystore"),
                                         tr("Keystore password:"), QLineEdit::Password,
                                         QString(), &ok);
    if (ok && !text.isEmpty())
        m_keystorePasswd = text;
}

void AndroidPackageCreationStep::certificatePassword()
{
    m_certificatePasswdForRun.clear();
    bool ok;
    QString text = QInputDialog::getText(0, tr("Certificate"),
                                         tr("Certificate password (%1):").arg(m_certificateAlias), QLineEdit::Password,
                                         QString(), &ok);
    if (ok && !text.isEmpty())
        m_certificatePasswdForRun = text;
}

void AndroidPackageCreationStep::showInGraphicalShell()
{
    Core::FileUtils::showInGraphicalShell(Core::ICore::instance()->mainWindow(), m_apkPathSigned.toString());
}

void AndroidPackageCreationStep::raiseError(const QString &shortMsg,
                                            const QString &detailedMsg)
{
    emit addOutput(detailedMsg.isNull() ? shortMsg : detailedMsg, BuildStep::ErrorOutput);
    emit addTask(Task(Task::Error, shortMsg, Utils::FileName::fromString(QString()), -1,
                      TASK_CATEGORY_BUILDSYSTEM));
}

const Core::Id AndroidPackageCreationStep::CreatePackageId("Qt4ProjectManager.AndroidPackageCreationStep");

} // namespace Internal
} // namespace Qt4ProjectManager