forked from qt-creator/qt-creator
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:
@@ -16,6 +16,16 @@
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Target Api:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
@@ -26,23 +36,7 @@
|
||||
</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">
|
||||
<property name="text">
|
||||
<string>Target:</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,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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user