Commit 480f7c09 authored by Daniel Teske's avatar Daniel Teske

Android virtual devices: Fix starting a avd

This cleans up various bits of the avd support in Creator.
- Adds a abi combobox to the create avd dialog.
- Moves the startAvd code into a separate thread, so that
  starting a avd while deploying doesn't block creator anymore.
- Implements a better waitForAvd function that works even
  if a emulator is already running and accurately can
  detect that it has finished booting.

Note: There are still many problems in the avd support in creator.
- The "clean libs on device" and "install qasi" functionality block
  the gui thread if they are run on a avd.
- If no avd exists and no suitable hardware is attached, the user gets
  a create Avd dialog, which doesn't tell him why he needs to create a
  avd. That information is hidden in the compile output.

Still this fixes the main use case of hitting run on a newly created
project with no actual device attached.

Change-Id: I76b3fdb1bdf3eadac07f82ad7d145ce6af453326
Reviewed-by: default avatarBogDan Vatra <bogdan@kde.org>
parent 14d05ead
......@@ -16,33 +16,27 @@
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Name:</string>
<string>Target Api:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="nameLineEdit"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Target:</string>
<string>Name:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="targetComboBox"/>
</item>
<item row="2" column="0">
<item row="3" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>SD card size:</string>
......@@ -52,7 +46,10 @@
</property>
</widget>
</item>
<item row="2" column="1">
<item row="0" column="1">
<widget class="QLineEdit" name="nameLineEdit"/>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="sizeSpinBox">
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
......@@ -68,6 +65,22 @@
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="targetComboBox"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Abi:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="abiComboBox"/>
</item>
</layout>
</item>
<item>
......
......@@ -449,44 +449,57 @@ QVector<AndroidDeviceInfo> 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<AndroidDeviceInfo> 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,37 +554,31 @@ QVector<AndroidDeviceInfo> AndroidConfigurations::androidVirtualDevices() const
return devices;
}
QString AndroidConfigurations::startAVD(int *apiLevel, const QString &name) const
QString AndroidConfigurations::findAvd(int *apiLevel, const QString &cpuAbi)
{
QProcess *avdProcess = new QProcess();
connect(this, SIGNAL(destroyed()), avdProcess, SLOT(deleteLater()));
connect(avdProcess, SIGNAL(finished(int)), avdProcess, SLOT(deleteLater()));
QString avdName = name;
QVector<AndroidDeviceInfo> 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<QObject*>(static_cast<const QObject*>(this)), "createAVD", Qt::AutoConnection,
Q_ARG(int, *apiLevel));
} else {
break;
QVector<AndroidDeviceInfo> 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();
}
if (avdName.isEmpty())// stop here if no emulators found
return avdName;
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()));
// start the emulator
avdProcess->start(emulatorToolPath().toString(),
......@@ -576,34 +586,38 @@ QString AndroidConfigurations::startAVD(int *apiLevel, const QString &name) cons
<< 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<AndroidDeviceInfo> 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;
......
......@@ -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<AndroidDeviceInfo> connectedDevices(QString *error = 0) const;
QVector<AndroidDeviceInfo> 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:
......
......@@ -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<DeployItem> &
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()));
......
......@@ -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;
......
......@@ -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)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment