diff --git a/src/plugins/android/addnewavddialog.ui b/src/plugins/android/addnewavddialog.ui index 149f79dfcf9..2ae66101d09 100644 --- a/src/plugins/android/addnewavddialog.ui +++ b/src/plugins/android/addnewavddialog.ui @@ -16,6 +16,16 @@ + + + + Target Api: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + @@ -26,23 +36,7 @@ - - - - - - - Target: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - + SD card size: @@ -52,7 +46,10 @@ - + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -68,6 +65,22 @@ + + + + + + + Abi: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + diff --git a/src/plugins/android/androidconfigurations.cpp b/src/plugins/android/androidconfigurations.cpp index 7774983b369..a1a51912bd5 100644 --- a/src/plugins/android/androidconfigurations.cpp +++ b/src/plugins/android/androidconfigurations.cpp @@ -449,44 +449,57 @@ QVector AndroidConfigurations::connectedDevices(QString *erro return devices; } -bool AndroidConfigurations::createAVD(int minApiLevel) const +QString AndroidConfigurations::createAVD(int minApiLevel, QString targetArch) const { QDialog d; Ui::AddNewAVDDialog avdDialog; avdDialog.setupUi(&d); avdDialog.targetComboBox->addItems(sdkTargets(minApiLevel)); + + if (targetArch.isEmpty()) + avdDialog.abiComboBox->addItems(QStringList() + << QLatin1String("armeabi-v7a") + << QLatin1String("armeabi") + << QLatin1String("x86") + << QLatin1String("mips")); + else + avdDialog.abiComboBox->addItems(QStringList(targetArch)); + if (!avdDialog.targetComboBox->count()) { QMessageBox::critical(0, tr("Error Creating AVD"), tr("Cannot create a new AVD. No sufficiently recent Android SDK available.\n" "Please install an SDK of at least API version %1."). arg(minApiLevel)); - return false; + return QString(); } QRegExp rx(QLatin1String("\\S+")); QRegExpValidator v(rx, 0); avdDialog.nameLineEdit->setValidator(&v); if (d.exec() != QDialog::Accepted) - return false; - return createAVD(avdDialog.targetComboBox->currentText(), avdDialog.nameLineEdit->text(), avdDialog.sizeSpinBox->value()); + return QString(); + return createAVD(avdDialog.targetComboBox->currentText(), avdDialog.nameLineEdit->text(), avdDialog.abiComboBox->currentText(), avdDialog.sizeSpinBox->value()); } -bool AndroidConfigurations::createAVD(const QString &target, const QString &name, int sdcardSize ) const +QString AndroidConfigurations::createAVD(const QString &target, const QString &name, const QString &abi, int sdcardSize ) const { QProcess proc; proc.start(androidToolPath().toString(), QStringList() << QLatin1String("create") << QLatin1String("avd") << QLatin1String("-a") << QLatin1String("-t") << target << QLatin1String("-n") << name + << QLatin1String("-b") << abi << QLatin1String("-c") << QString::fromLatin1("%1M").arg(sdcardSize)); if (!proc.waitForStarted()) - return false; + return QString(); proc.write(QByteArray("no\n")); if (!proc.waitForFinished(-1)) { proc.terminate(); - return false; + return QString(); } - return !proc.exitCode(); + if (proc.exitCode()) // error! + return QString(); + return name; } bool AndroidConfigurations::removeAVD(const QString &name) const @@ -531,6 +544,9 @@ QVector AndroidConfigurations::androidVirtualDevices() const if (line.contains(QLatin1String("ABI:"))) dev.cpuAbi = QStringList() << line.mid(line.lastIndexOf(QLatin1Char(' '))).trimmed(); } + // armeabi-v7a devices can also run armeabi code + if (dev.cpuAbi == QStringList(QLatin1String("armeabi-v7a"))) + dev.cpuAbi << QLatin1String("armeabi"); devices.push_back(dev); } qSort(devices.begin(), devices.end(), androidDevicesLessThan); @@ -538,72 +554,70 @@ QVector AndroidConfigurations::androidVirtualDevices() const return devices; } -QString AndroidConfigurations::startAVD(int *apiLevel, const QString &name) const +QString AndroidConfigurations::findAvd(int *apiLevel, const QString &cpuAbi) +{ + QVector devices = androidVirtualDevices(); + foreach (const AndroidDeviceInfo &device, devices) { + // take first emulator how supports this package + if (device.sdk >= *apiLevel && device.cpuAbi.contains(cpuAbi)) { + *apiLevel = device.sdk; + return device.serialNumber; + } + } + return QString(); +} + +QString AndroidConfigurations::startAVD(const QString &name, int apiLevel, QString cpuAbi) const +{ + if (startAVDAsync(name)) + return waitForAvd(apiLevel, cpuAbi); + return QString(); +} + +bool AndroidConfigurations::startAVDAsync(const QString &avdName) const { QProcess *avdProcess = new QProcess(); connect(this, SIGNAL(destroyed()), avdProcess, SLOT(deleteLater())); connect(avdProcess, SIGNAL(finished(int)), avdProcess, SLOT(deleteLater())); - QString avdName = name; - QVector devices; - bool createAVDOnce = false; - while (true) { - if (avdName.isEmpty()) { - devices = androidVirtualDevices(); - foreach (const AndroidDeviceInfo &device, devices) - if (device.sdk >= *apiLevel) { // take first emulator how supports this package - *apiLevel = device.sdk; - avdName = device.serialNumber; - break; - } - } - // if no emulators found try to create one once - if (avdName.isEmpty() && !createAVDOnce) { - createAVDOnce = true; - QMetaObject::invokeMethod(const_cast(static_cast(this)), "createAVD", Qt::AutoConnection, - Q_ARG(int, *apiLevel)); - } else { - break; - } - } - - if (avdName.isEmpty())// stop here if no emulators found - return avdName; - // start the emulator avdProcess->start(emulatorToolPath().toString(), QStringList() << QLatin1String("-partition-size") << QString::number(config().partitionSize) << QLatin1String("-avd") << avdName); if (!avdProcess->waitForStarted(-1)) { delete avdProcess; - return QString(); + return false; } + return true; +} - // wait until the emulator is online - QProcess proc; - proc.start(adbToolPath().toString(), QStringList() << QLatin1String("-e") << QLatin1String("wait-for-device")); - while (!proc.waitForFinished(500)) { - if (avdProcess->waitForFinished(0)) { - proc.kill(); - proc.waitForFinished(-1); +QString AndroidConfigurations::waitForAvd(int apiLevel, const QString &cpuAbi) const +{ + // we cannot use adb -e wait-for-device, since that doesn't work if a emulator is already running + + // 15 rounds of 8s sleeping, a minute for the avd to start + QString serialNumber; + for (int i = 0; i < 15; ++i) { + QVector devices = connectedDevices(); + foreach (AndroidDeviceInfo device, devices) { + if (!device.serialNumber.startsWith(QLatin1String("emulator"))) + continue; + if (!device.cpuAbi.contains(cpuAbi)) + continue; + if (!device.sdk == apiLevel) + continue; + serialNumber = device.serialNumber; + // found a serial number, now wait until it's done booting... + for (int i = 0; i < 15; ++i) { + if (hasFinishedBooting(serialNumber)) + return serialNumber; + else + sleep(8); + } return QString(); } + sleep(8); } - sleep(5);// wait for pm to start - - // workaround for stupid adb bug - proc.start(adbToolPath().toString(), QStringList() << QLatin1String("devices")); - if (!proc.waitForFinished(-1)) { - proc.kill(); - return QString(); - } - - // get connected devices - devices = connectedDevices(); - foreach (AndroidDeviceInfo device, devices) - if (device.sdk == *apiLevel) - return device.serialNumber; - // this should not happen, but ... return QString(); } @@ -647,6 +661,24 @@ QString AndroidConfigurations::getProductModel(const QString &device) const return model; } +bool AndroidConfigurations::hasFinishedBooting(const QString &device) const +{ + QStringList arguments = AndroidDeviceInfo::adbSelector(device); + arguments << QLatin1String("shell") << QLatin1String("getprop") + << QLatin1String("init.svc.bootanim"); + + QProcess adbProc; + adbProc.start(adbToolPath().toString(), arguments); + if (!adbProc.waitForFinished(-1)) { + adbProc.kill(); + return false; + } + QString value = QString::fromLocal8Bit(adbProc.readAll().trimmed()); + if (value == QLatin1String("stopped")) + return true; + return false; +} + QStringList AndroidConfigurations::getAbis(const QString &device) const { QStringList result; diff --git a/src/plugins/android/androidconfigurations.h b/src/plugins/android/androidconfigurations.h index 0531e323ab1..d5588b2377c 100644 --- a/src/plugins/android/androidconfigurations.h +++ b/src/plugins/android/androidconfigurations.h @@ -93,11 +93,15 @@ public: Utils::FileName stripPath(ProjectExplorer::Abi::Architecture architecture, const QString &ndkToolChainVersion) const; Utils::FileName readelfPath(ProjectExplorer::Abi::Architecture architecture, const QString &ndkToolChainVersion) const; QString getDeployDeviceSerialNumber(int *apiLevel, const QString &abi, QString *error = 0) const; - bool createAVD(const QString &target, const QString &name, int sdcardSize) const; + QString createAVD(int minApiLevel = 0, QString targetArch = QString()) const; + QString createAVD(const QString &target, const QString &name, const QString &abi, int sdcardSize) const; bool removeAVD(const QString &name) const; QVector connectedDevices(QString *error = 0) const; QVector androidVirtualDevices() const; - QString startAVD(int *apiLevel, const QString &name = QString()) const; + QString findAvd(int *apiLevel, const QString &cpuAbi); + QString startAVD(const QString &name, int apiLevel, QString cpuAbi) const; + bool startAVDAsync(const QString &avdName) const; + QString waitForAvd(int apiLevel, const QString &cpuAbi) const; QString bestMatch(const QString &targetAPI) const; QStringList makeExtraSearchDirectories() const; @@ -110,12 +114,12 @@ public: void updateAndroidDevice(); QString getProductModel(const QString &device) const; + bool hasFinishedBooting(const QString &device) const; signals: void updated(); public slots: - bool createAVD(int minApiLevel = 0) const; void updateAutomaticKitList(); private: diff --git a/src/plugins/android/androiddeploystep.cpp b/src/plugins/android/androiddeploystep.cpp index 38be4d91dad..8b5e976db7d 100644 --- a/src/plugins/android/androiddeploystep.cpp +++ b/src/plugins/android/androiddeploystep.cpp @@ -97,21 +97,26 @@ bool AndroidDeployStep::init() { m_packageName = AndroidManager::packageName(target()); const QString targetSDK = AndroidManager::targetSDK(target()); - const QString targetArch = AndroidManager::targetArch(target()); + m_targetArch = AndroidManager::targetArch(target()); - writeOutput(tr("Please wait, searching for a suitable device for target:%1, ABI:%2").arg(targetSDK).arg(targetArch)); + writeOutput(tr("Please wait, searching for a suitable device for target:%1, ABI:%2").arg(targetSDK).arg(m_targetArch)); m_deviceAPILevel = targetSDK.mid(targetSDK.indexOf(QLatin1Char('-')) + 1).toInt(); QString error; - m_deviceSerialNumber = AndroidConfigurations::instance().getDeployDeviceSerialNumber(&m_deviceAPILevel, targetArch, &error); + m_deviceSerialNumber = AndroidConfigurations::instance().getDeployDeviceSerialNumber(&m_deviceAPILevel, m_targetArch, &error); if (!error.isEmpty()) writeOutput(error); + m_avdName.clear(); if (m_deviceSerialNumber.isEmpty()) { writeOutput(tr("Falling back to Android virtual machine device.")); - m_deviceSerialNumber = AndroidConfigurations::instance().startAVD(&m_deviceAPILevel); + m_avdName = AndroidConfigurations::instance().findAvd(&m_deviceAPILevel, m_targetArch); + if (m_avdName.isEmpty()) + m_avdName = AndroidConfigurations::instance().createAVD(m_deviceAPILevel, m_targetArch); + if (m_avdName.isEmpty()) // user canceled + return false; } - if (!m_deviceSerialNumber.length()) { + if (m_deviceSerialNumber.isEmpty() && m_avdName.isEmpty()) { m_deviceSerialNumber.clear(); raiseError(tr("Cannot deploy: no devices or emulators found for your package.")); return false; @@ -196,8 +201,15 @@ void AndroidDeployStep::cleanLibsOnDevice() int deviceAPILevel = targetSDK.mid(targetSDK.indexOf(QLatin1Char('-')) + 1).toInt(); QString deviceSerialNumber = AndroidConfigurations::instance().getDeployDeviceSerialNumber(&deviceAPILevel, targetArch); - if (deviceSerialNumber.isEmpty()) - deviceSerialNumber = AndroidConfigurations::instance().startAVD(&deviceAPILevel); + if (deviceSerialNumber.isEmpty()) { + QString avdName = AndroidConfigurations::instance().findAvd(&deviceAPILevel, targetArch); + if (avdName.isEmpty()) { + // No avd found, don't create one just error out + Core::MessageManager::instance()->printToOutputPane(tr("Could not find a device."), Core::MessageManager::NoModeSwitch); + return; + } + deviceSerialNumber = AndroidConfigurations::instance().startAVD(avdName, deviceAPILevel, targetArch); + } if (!deviceSerialNumber.length()) { Core::MessageManager::instance()->printToOutputPane(tr("Could not run adb. No device found."), Core::MessageManager::NoModeSwitch); return; @@ -249,8 +261,15 @@ void AndroidDeployStep::installQASIPackage(const QString &packagePath) const QString targetSDK = AndroidManager::targetSDK(target()); int deviceAPILevel = targetSDK.mid(targetSDK.indexOf(QLatin1Char('-')) + 1).toInt(); QString deviceSerialNumber = AndroidConfigurations::instance().getDeployDeviceSerialNumber(&deviceAPILevel, targetArch); - if (deviceSerialNumber.isEmpty()) - deviceSerialNumber = AndroidConfigurations::instance().startAVD(&deviceAPILevel); + if (deviceSerialNumber.isEmpty()) { + QString avdName = AndroidConfigurations::instance().findAvd(&deviceAPILevel, targetArch); + if (avdName.isEmpty()) { + Core::MessageManager::instance()->printToOutputPane(tr("No device found."), + Core::MessageManager::NoModeSwitch); + return; + } + deviceSerialNumber = AndroidConfigurations::instance().startAVD(avdName, deviceAPILevel, targetArch); + } if (!deviceSerialNumber.length()) { Core::MessageManager::instance()->printToOutputPane(tr("Could not run adb. No device found."), Core::MessageManager::NoModeSwitch); return; @@ -292,8 +311,8 @@ bool AndroidDeployStep::runCommand(QProcess *buildProc, .arg(program).arg(arguments.join(QLatin1String(" "))).arg(buildProc->errorString()), BuildStep::ErrorMessageOutput); return false; } - buildProc->waitForFinished(-1); - if (buildProc->error() != QProcess::UnknownError + if (!buildProc->waitForFinished(2 * 60 * 1000) + || buildProc->error() != QProcess::UnknownError || buildProc->exitCode() != 0) { QString mainMessage = tr("Packaging Error: Command '%1 %2' failed.") .arg(program).arg(arguments.join(QLatin1String(" "))); @@ -452,6 +471,12 @@ void AndroidDeployStep::deployFiles(QProcess *process, const QList & bool AndroidDeployStep::deployPackage() { + if (!m_avdName.isEmpty()) { + if (!AndroidConfigurations::instance().startAVDAsync(m_avdName)) + return false; + m_deviceSerialNumber = AndroidConfigurations::instance().waitForAvd(m_deviceAPILevel, m_targetArch); + } + QProcess *const deployProc = new QProcess; connect(deployProc, SIGNAL(readyReadStandardOutput()), this, SLOT(handleBuildOutput())); diff --git a/src/plugins/android/androiddeploystep.h b/src/plugins/android/androiddeploystep.h index 49d9c8265f4..2b4358f3f23 100644 --- a/src/plugins/android/androiddeploystep.h +++ b/src/plugins/android/androiddeploystep.h @@ -138,10 +138,12 @@ private: private: QString m_deviceSerialNumber; int m_deviceAPILevel; + QString m_targetArch; AndroidDeployAction m_deployAction; // members to transfer data from init() to run + QString m_avdName; QString m_packageName; QString m_qtVersionSourcePath; QtSupport::BaseQtVersion::QmakeBuildConfigs m_qtVersionQMakeBuildConfig; diff --git a/src/plugins/android/androidsettingswidget.cpp b/src/plugins/android/androidsettingswidget.cpp index ea85eb6abc7..a0edc54a54f 100644 --- a/src/plugins/android/androidsettingswidget.cpp +++ b/src/plugins/android/androidsettingswidget.cpp @@ -75,7 +75,7 @@ QVariant AvdModel::data(const QModelIndex &index, int role) const case 1: return QString::fromLatin1("API %1").arg(m_list[index.row()].sdk); case 2: - return m_list[index.row()].cpuAbi; + return m_list[index.row()].cpuAbi.first(); } return QVariant(); } @@ -401,8 +401,7 @@ void AndroidSettingsWidget::removeAVD() void AndroidSettingsWidget::startAVD() { - int tempApiLevel = -1; - AndroidConfigurations::instance().startAVD(&tempApiLevel, m_AVDModel.avdName(m_ui->AVDTableView->currentIndex())); + AndroidConfigurations::instance().startAVDAsync(m_AVDModel.avdName(m_ui->AVDTableView->currentIndex())); } void AndroidSettingsWidget::avdActivated(QModelIndex index)