Commit 3bb7339f authored by Daniel Teske's avatar Daniel Teske

Android: Implement device selection dialog

Change-Id: I1f4f3065a09837414429bbfc68110ada85ea174a
Reviewed-by: default avatarBogDan Vatra <bogdan@kde.org>
parent 6669e8ea
......@@ -41,7 +41,8 @@ HEADERS += \
androidmanifesteditorfactory.h \
androidmanifesteditor.h \
androidmanifesteditorwidget.h \
androidmanifestdocument.h
androidmanifestdocument.h \
androiddevicedialog.h
SOURCES += \
androidconfigurations.cpp \
......@@ -77,14 +78,16 @@ SOURCES += \
androidmanifesteditorfactory.cpp \
androidmanifesteditor.cpp \
androidmanifesteditorwidget.cpp \
androidmanifestdocument.cpp
androidmanifestdocument.cpp \
androiddevicedialog.cpp
FORMS += \
androidsettingswidget.ui \
androidpackagecreationwidget.ui \
androiddeploystepwidget.ui \
addnewavddialog.ui \
androidcreatekeystorecertificate.ui
androidcreatekeystorecertificate.ui \
androiddevicedialog.ui
exists(../../shared/qbs/qbs.pro) {
HEADERS += \
......
......@@ -38,6 +38,9 @@ QtcPlugin {
"androidcreatekeystorecertificate.ui",
"androiddebugsupport.cpp",
"androiddebugsupport.h",
"androiddevicedialog.cpp",
"androiddevicedialog.h",
"androiddevicedialog.ui",
"androiddeployconfiguration.cpp",
"androiddeployconfiguration.h",
"androiddeploystep.cpp",
......
......@@ -34,6 +34,7 @@
#include "androidgdbserverkitinformation.h"
#include "ui_addnewavddialog.h"
#include "androidqtversion.h"
#include "androiddevicedialog.h"
#include <coreplugin/icore.h>
#include <utils/hostosinfo.h>
......@@ -42,6 +43,7 @@
#include <projectexplorer/kitinformation.h>
#include <projectexplorer/devicesupport/devicemanager.h>
#include <projectexplorer/toolchainmanager.h>
#include <projectexplorer/session.h>
#include <debugger/debuggerkitinformation.h>
#include <qtsupport/baseqtversion.h>
#include <qtsupport/qtkitinformation.h>
......@@ -76,6 +78,7 @@ namespace {
const QLatin1String KeystoreLocationKey("KeystoreLocation");
const QLatin1String AutomaticKitCreationKey("AutomatiKitCreation");
const QLatin1String MakeExtraSearchDirectory("MakeExtraSearchDirectory");
const QLatin1String DefaultDevice("DefaultDevice");
const QLatin1String PartitionSizeKey("PartitionSize");
const QLatin1String ToolchainHostKey("ToolchainHost");
const QLatin1String ArmToolchainPrefix("arm-linux-androideabi");
......@@ -99,12 +102,11 @@ namespace {
{
if (dev1.serialNumber.contains(QLatin1String("????")) == dev2.serialNumber.contains(QLatin1String("????")))
return !dev1.serialNumber.contains(QLatin1String("????"));
bool dev1IsEmulator = dev1.serialNumber.startsWith(QLatin1String("emulator"));
bool dev2IsEmulator = dev2.serialNumber.startsWith(QLatin1String("emulator"));
if (dev1IsEmulator != dev2IsEmulator)
return !dev1IsEmulator;
if (dev1.type != dev2.type)
return dev1.type == AndroidDeviceInfo::Hardware;
if (dev1.sdk != dev2.sdk)
return dev1.sdk < dev2.sdk;
return dev1.serialNumber < dev2.serialNumber;
}
}
......@@ -380,38 +382,53 @@ FileName AndroidConfigurations::zipalignPath() const
return path.appendPath(QLatin1String("tools/zipalign" QTC_HOST_EXE_SUFFIX));
}
QString AndroidConfigurations::getDeployDeviceSerialNumber(int *apiLevel, const QString &abi, QString *error) const
AndroidDeviceInfo AndroidConfigurations::showDeviceDialog(ProjectExplorer::Project *project, int apiLevel, const QString &abi)
{
QVector<AndroidDeviceInfo> devices = connectedDevices(error);
QString serialNumber = defaultDevice(project, abi);
if (!serialNumber.isEmpty()) {
// search for that device
foreach (const AndroidDeviceInfo &info, AndroidConfigurations::instance().connectedDevices())
if (info.serialNumber == serialNumber
&& info.sdk >= apiLevel)
return info;
foreach (AndroidDeviceInfo device, devices) {
if (device.unauthorized) {
if (error) {
*error += tr("Skipping %1: Unauthorized. Please check the confirmation dialog on your device..").arg(device.serialNumber);
*error += QLatin1Char('\n');
}
} else if (!device.cpuAbi.contains(abi)) {
if (error) {
*error += tr("Skipping %1: ABI is incompatible, device supports ABIs: %2.")
.arg(getProductModel(device.serialNumber))
.arg(device.cpuAbi.join(QLatin1String(" ")));
*error += QLatin1Char('\n');
}
} else if (device.sdk < *apiLevel) {
if (error) {
*error += tr("Skipping %1: API Level of device is: %2.")
.arg(getProductModel(device.serialNumber))
.arg(device.sdk);
*error += QLatin1Char('\n');
}
} else {
if (error)
error->clear(); // no errors if we found a device
*apiLevel = device.sdk;
return device.serialNumber;
foreach (const AndroidDeviceInfo &info, AndroidConfigurations::instance().androidVirtualDevices())
if (info.serialNumber == serialNumber
&& info.sdk >= apiLevel)
return info;
}
AndroidDeviceDialog dialog(apiLevel, abi);
if (dialog.exec() == QDialog::Accepted) {
AndroidDeviceInfo info = dialog.device();
if (dialog.saveDeviceSelection()) {
if (!info.serialNumber.isEmpty())
AndroidConfigurations::instance().setDefaultDevice(project, abi, info.serialNumber);
}
return info;
}
return QString();
return AndroidDeviceInfo();
}
void AndroidConfigurations::clearDefaultDevices(ProjectExplorer::Project *project)
{
if (m_defaultDeviceForAbi.contains(project))
m_defaultDeviceForAbi.remove(project);
}
void AndroidConfigurations::setDefaultDevice(ProjectExplorer::Project *project, const QString &abi, const QString &serialNumber)
{
m_defaultDeviceForAbi[project][abi] = serialNumber;
}
QString AndroidConfigurations::defaultDevice(Project *project, const QString &abi) const
{
if (!m_defaultDeviceForAbi.contains(project))
return QString();
const QMap<QString, QString> &map = m_defaultDeviceForAbi.value(project);
if (!map.contains(abi))
return QString();
return map.value(abi);
}
QVector<AndroidDeviceInfo> AndroidConfigurations::connectedDevices(QString *error) const
......@@ -435,6 +452,7 @@ QVector<AndroidDeviceInfo> AndroidConfigurations::connectedDevices(QString *erro
const QString deviceType = QString::fromLatin1(device.mid(device.indexOf('\t'))).trimmed();
AndroidDeviceInfo dev;
dev.serialNumber = serialNo;
dev.type = serialNo.startsWith(QLatin1String("emulator")) ? AndroidDeviceInfo::Emulator : AndroidDeviceInfo::Hardware;
dev.sdk = getSDKVersion(dev.serialNumber);
dev.cpuAbi = getAbis(dev.serialNumber);
dev.unauthorized = (deviceType == QLatin1String("unauthorized"));
......@@ -546,6 +564,7 @@ QVector<AndroidDeviceInfo> AndroidConfigurations::androidVirtualDevices() const
if (dev.cpuAbi == QStringList(QLatin1String("armeabi-v7a")))
dev.cpuAbi << QLatin1String("armeabi");
dev.unauthorized = false;
dev.type = AndroidDeviceInfo::Emulator;
devices.push_back(dev);
}
qSort(devices.begin(), devices.end(), androidDevicesLessThan);
......@@ -553,19 +572,6 @@ QVector<AndroidDeviceInfo> AndroidConfigurations::androidVirtualDevices() const
return devices;
}
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))
......@@ -643,6 +649,8 @@ int AndroidConfigurations::getSDKVersion(const QString &device) const
//!
QString AndroidConfigurations::getProductModel(const QString &device) const
{
if (m_serialNumberToDeviceName.contains(device))
return m_serialNumberToDeviceName.value(device);
// workaround for '????????????' serial numbers
QStringList arguments = AndroidDeviceInfo::adbSelector(device);
arguments << QLatin1String("shell") << QLatin1String("getprop")
......@@ -657,6 +665,8 @@ QString AndroidConfigurations::getProductModel(const QString &device) const
QString model = QString::fromLocal8Bit(adbProc.readAll().trimmed());
if (model.isEmpty())
return device;
if (!device.startsWith(QLatin1String("????")))
m_serialNumberToDeviceName.insert(device, model);
return model;
}
......@@ -856,6 +866,9 @@ AndroidConfigurations::AndroidConfigurations(QObject *parent)
{
load();
updateAvailablePlatforms();
connect(ProjectExplorer::SessionManager::instance(), SIGNAL(projectRemoved(ProjectExplorer::Project*)),
this, SLOT(clearDefaultDevices(ProjectExplorer::Project*)));
}
void AndroidConfigurations::load()
......
......@@ -34,6 +34,8 @@
#include <QString>
#include <QStringList>
#include <QVector>
#include <QHash>
#include <QMap>
#include <projectexplorer/abi.h>
#include <utils/fileutils.h>
......@@ -41,6 +43,8 @@ QT_BEGIN_NAMESPACE
class QSettings;
QT_END_NAMESPACE
namespace ProjectExplorer { class Project; }
namespace Android {
namespace Internal {
......@@ -68,6 +72,8 @@ struct AndroidDeviceInfo
QStringList cpuAbi;
int sdk;
bool unauthorized;
enum AndroidDeviceType { Hardware, Emulator };
AndroidDeviceType type;
static QStringList adbSelector(const QString &serialNumber);
};
......@@ -93,13 +99,11 @@ public:
Utils::FileName zipalignPath() const;
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;
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 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;
......@@ -117,6 +121,12 @@ public:
QString getProductModel(const QString &device) const;
bool hasFinishedBooting(const QString &device) const;
AndroidDeviceInfo showDeviceDialog(ProjectExplorer::Project *project, int apiLevel, const QString &abi);
void setDefaultDevice(ProjectExplorer::Project *project, const QString &abi, const QString &serialNumber); // serial number or avd name
QString defaultDevice(ProjectExplorer::Project *project, const QString &abi) const; // serial number or avd name
public slots:
void clearDefaultDevices(ProjectExplorer::Project *project);
signals:
void updated();
......@@ -140,6 +150,9 @@ private:
static AndroidConfigurations *m_instance;
AndroidConfig m_config;
QVector<int> m_availablePlatforms;
mutable QHash<QString, QString> m_serialNumberToDeviceName;
QMap<ProjectExplorer::Project *, QMap<QString, QString> > m_defaultDeviceForAbi;
};
} // namespace Internal
......
......@@ -36,6 +36,7 @@
#include "androidrunconfiguration.h"
#include "androidmanager.h"
#include "androidtoolchain.h"
#include "androiddevicedialog.h"
#include <coreplugin/messagemanager.h>
#include <projectexplorer/buildconfiguration.h>
......@@ -102,31 +103,15 @@ bool AndroidDeployStep::init()
m_deviceAPILevel = AndroidManager::minimumSDK(target());
m_targetArch = AndroidManager::targetArch(target());
if (m_deviceAPILevel == 0) // minimum api level is unset
writeOutput(tr("Please wait, searching for a suitable device for target: ABI:%2").arg(m_targetArch));
else
writeOutput(tr("Please wait, searching for a suitable device for target: API %1, ABI:%2").arg(m_deviceAPILevel).arg(m_targetArch));
QString 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_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.isEmpty() && m_avdName.isEmpty()) {
m_deviceSerialNumber.clear();
raiseError(tr("Cannot deploy: no devices or emulators found for your package."));
AndroidDeviceInfo info = AndroidConfigurations::instance().showDeviceDialog(project(), m_deviceAPILevel, m_targetArch);
if (info.serialNumber.isEmpty()) // aborted
return false;
}
m_deviceAPILevel = info.sdk;
m_deviceSerialNumber = info.serialNumber;
if (info.type == AndroidDeviceInfo::Emulator)
m_avdName = m_deviceSerialNumber;
QtSupport::BaseQtVersion *version = QtSupport::QtKitInformation::qtVersion(target()->kit());
if (!version)
......@@ -212,20 +197,20 @@ void AndroidDeployStep::cleanLibsOnDevice()
{
const QString targetArch = AndroidManager::targetArch(target());
int deviceAPILevel = AndroidManager::minimumSDK(target());
QString deviceSerialNumber = AndroidConfigurations::instance().getDeployDeviceSerialNumber(&deviceAPILevel, targetArch);
if (deviceSerialNumber.isEmpty()) {
QString avdName = AndroidConfigurations::instance().findAvd(&deviceAPILevel, targetArch);
if (avdName.isEmpty()) {
// No avd found, don't create one just error out
MessageManager::write(tr("Could not find a device."));
return;
}
deviceSerialNumber = AndroidConfigurations::instance().startAVD(avdName, deviceAPILevel, targetArch);
}
if (!deviceSerialNumber.length()) {
MessageManager::write(tr("Could not run adb. No device found."));
AndroidDeviceInfo info = AndroidConfigurations::instance().showDeviceDialog(project(), m_deviceAPILevel, m_targetArch);
if (info.serialNumber.isEmpty()) // aborted
return;
deviceAPILevel = info.sdk;
QString deviceSerialNumber = info.serialNumber;
if (info.type == AndroidDeviceInfo::Emulator) {
deviceSerialNumber = AndroidConfigurations::instance().startAVD(deviceSerialNumber, deviceAPILevel, targetArch);
if (deviceSerialNumber.isEmpty())
MessageManager::write(tr("Starting android virtual device failed."));
}
QProcess *process = new QProcess(this);
QStringList arguments = AndroidDeviceInfo::adbSelector(deviceSerialNumber);
arguments << QLatin1String("shell") << QLatin1String("rm") << QLatin1String("-r") << QLatin1String("/data/local/tmp/qt");
......@@ -268,18 +253,17 @@ void AndroidDeployStep::installQASIPackage(const QString &packagePath)
{
const QString targetArch = AndroidManager::targetArch(target());
int deviceAPILevel = AndroidManager::minimumSDK(target());
QString deviceSerialNumber = AndroidConfigurations::instance().getDeployDeviceSerialNumber(&deviceAPILevel, targetArch);
if (deviceSerialNumber.isEmpty()) {
QString avdName = AndroidConfigurations::instance().findAvd(&deviceAPILevel, targetArch);
if (avdName.isEmpty()) {
MessageManager::write(tr("No device found."));
return;
}
deviceSerialNumber = AndroidConfigurations::instance().startAVD(avdName, deviceAPILevel, targetArch);
}
if (!deviceSerialNumber.length()) {
MessageManager::write(tr("Could not run adb. No device found."));
AndroidDeviceInfo info = AndroidConfigurations::instance().showDeviceDialog(project(), m_deviceAPILevel, m_targetArch);
if (info.serialNumber.isEmpty()) // aborted
return;
deviceAPILevel = info.sdk;
QString deviceSerialNumber = info.serialNumber;
if (info.type == AndroidDeviceInfo::Emulator) {
deviceSerialNumber = AndroidConfigurations::instance().startAVD(deviceSerialNumber, deviceAPILevel, targetArch);
if (deviceSerialNumber.isEmpty())
MessageManager::write(tr("Starting android virtual device failed."));
}
QProcess *process = new QProcess(this);
......
......@@ -55,6 +55,7 @@ AndroidDeployStepWidget::AndroidDeployStepWidget(AndroidDeployStep *step) :
connect(ui->chooseButton, SIGNAL(clicked()), SLOT(setQASIPackagePath()));
connect(ui->cleanLibsPushButton, SIGNAL(clicked()), SLOT(cleanLibsOnDevice()));
connect(ui->resetDefaultDevices, SIGNAL(clicked()), SLOT(resetDefaultDevices()));
connect(m_step, SIGNAL(deployOptionsChanged()),
this, SLOT(deployOptionsChanged()));
......@@ -125,5 +126,10 @@ void AndroidDeployStepWidget::cleanLibsOnDevice()
m_step->cleanLibsOnDevice();
}
void AndroidDeployStepWidget::resetDefaultDevices()
{
AndroidConfigurations::instance().clearDefaultDevices(m_step->project());
}
} // namespace Internal
} // namespace Qt4ProjectManager
......@@ -55,6 +55,7 @@ private slots:
void setQASIPackagePath();
void cleanLibsOnDevice();
void resetDefaultDevices();
void deployOptionsChanged();
private:
......
......@@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>678</width>
<width>682</width>
<height>155</height>
</rect>
</property>
......@@ -69,21 +69,21 @@ The APK will not be usable on any other device.</string>
<string>Advanced Actions</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<item row="1" column="0">
<widget class="QPushButton" name="cleanLibsPushButton">
<property name="text">
<string>Clean Temporary Libraries Directory on Device</string>
</property>
</widget>
</item>
<item row="1" column="0">
<item row="2" column="0">
<widget class="QPushButton" name="chooseButton">
<property name="text">
<string>Install Ministro from APK</string>
</property>
</widget>
</item>
<item row="2" column="0">
<item row="3" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
......@@ -96,6 +96,13 @@ The APK will not be usable on any other device.</string>
</property>
</spacer>
</item>
<item row="0" column="0">
<widget class="QPushButton" name="resetDefaultDevices">
<property name="text">
<string>Reset Default Devices</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
......
This diff is collapsed.
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** 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.
**
****************************************************************************/
#ifndef ANDROIDDEVICEDIALOG_H
#define ANDROIDDEVICEDIALOG_H
#include "androidconfigurations.h"
#include <QVector>
#include <QDialog>
QT_BEGIN_NAMESPACE
class QModelIndex;
QT_END_NAMESPACE
namespace Ui {
class AndroidDeviceDialog;
}
namespace Android {
namespace Internal {
class AndroidDeviceModel;
class AndroidDeviceDialog : public QDialog
{
Q_OBJECT
public:
explicit AndroidDeviceDialog(int apiLevel, const QString &abi, QWidget *parent = 0);
~AndroidDeviceDialog();
AndroidDeviceInfo device();
void accept();
bool saveDeviceSelection();
private slots:
void refreshDeviceList();
void createAvd();
void clickedOnView(const QModelIndex &idx);
private:
AndroidDeviceModel *m_model;
Ui::AndroidDeviceDialog *m_ui;
int m_apiLevel;
QString m_abi;
};
}
}
#endif // ANDROIDDEVICEDIALOG_H
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AndroidDeviceDialog</class>
<widget class="QDialog" name="AndroidDeviceDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>618</width>
<height>400</height>
</rect>
</property>
<property name="windowTitle">
<string>Select Android Device</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<widget class="QPushButton" name="refreshDevicesButton">
<property name="text">
<string>Refresh Device List</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="createAVDButton">
<property name="text">
<string>Create Android Virtual Device</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="defaultDeviceCheckBox">
<property name="text">
<string>Always use this device for architecture %1</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="0" column="0" colspan="3">
<widget class="QTreeView" name="deviceView">
<property name="minimumSize">
<size>
<width>600</width>
<height>300</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>AndroidDeviceDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>