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: BogDan Vatra <bogdan@kde.org>
This commit is contained in:
Daniel Teske
2013-07-05 18:54:42 +02:00
parent 14d05eade8
commit 480f7c09fc
6 changed files with 168 additions and 93 deletions

View File

@@ -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,72 +554,70 @@ QVector<AndroidDeviceInfo> AndroidConfigurations::androidVirtualDevices() const
return devices;
}
QString AndroidConfigurations::startAVD(int *apiLevel, const QString &name) const
QString AndroidConfigurations::findAvd(int *apiLevel, const QString &cpuAbi)
{
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();
}
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<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;
}
}
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<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;