diff --git a/src/plugins/android/android.pro b/src/plugins/android/android.pro index 109359a84e0..afa65777231 100644 --- a/src/plugins/android/android.pro +++ b/src/plugins/android/android.pro @@ -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 += \ diff --git a/src/plugins/android/android.qbs b/src/plugins/android/android.qbs index 8a58a60d950..8abc6e81107 100644 --- a/src/plugins/android/android.qbs +++ b/src/plugins/android/android.qbs @@ -38,6 +38,9 @@ QtcPlugin { "androidcreatekeystorecertificate.ui", "androiddebugsupport.cpp", "androiddebugsupport.h", + "androiddevicedialog.cpp", + "androiddevicedialog.h", + "androiddevicedialog.ui", "androiddeployconfiguration.cpp", "androiddeployconfiguration.h", "androiddeploystep.cpp", diff --git a/src/plugins/android/androidconfigurations.cpp b/src/plugins/android/androidconfigurations.cpp index 13f4b5dc373..59b8e9b1421 100644 --- a/src/plugins/android/androidconfigurations.cpp +++ b/src/plugins/android/androidconfigurations.cpp @@ -34,6 +34,7 @@ #include "androidgdbserverkitinformation.h" #include "ui_addnewavddialog.h" #include "androidqtversion.h" +#include "androiddevicedialog.h" #include #include @@ -42,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -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 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; } - return QString(); + + 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 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 &map = m_defaultDeviceForAbi.value(project); + if (!map.contains(abi)) + return QString(); + return map.value(abi); } QVector AndroidConfigurations::connectedDevices(QString *error) const @@ -435,6 +452,7 @@ QVector 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 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 AndroidConfigurations::androidVirtualDevices() const return devices; } -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)) @@ -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() diff --git a/src/plugins/android/androidconfigurations.h b/src/plugins/android/androidconfigurations.h index cdeabbf5b58..0b89957a4b7 100644 --- a/src/plugins/android/androidconfigurations.h +++ b/src/plugins/android/androidconfigurations.h @@ -34,6 +34,8 @@ #include #include #include +#include +#include #include #include @@ -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 connectedDevices(QString *error = 0) const; QVector 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 m_availablePlatforms; + mutable QHash m_serialNumberToDeviceName; + + QMap > m_defaultDeviceForAbi; }; } // namespace Internal diff --git a/src/plugins/android/androiddeploystep.cpp b/src/plugins/android/androiddeploystep.cpp index cb2f553747b..e004e120328 100644 --- a/src/plugins/android/androiddeploystep.cpp +++ b/src/plugins/android/androiddeploystep.cpp @@ -36,6 +36,7 @@ #include "androidrunconfiguration.h" #include "androidmanager.h" #include "androidtoolchain.h" +#include "androiddevicedialog.h" #include #include @@ -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); diff --git a/src/plugins/android/androiddeploystepwidget.cpp b/src/plugins/android/androiddeploystepwidget.cpp index 93140abf3c2..b661bae5cf1 100644 --- a/src/plugins/android/androiddeploystepwidget.cpp +++ b/src/plugins/android/androiddeploystepwidget.cpp @@ -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 diff --git a/src/plugins/android/androiddeploystepwidget.h b/src/plugins/android/androiddeploystepwidget.h index 94bea1c6dee..00781658e98 100644 --- a/src/plugins/android/androiddeploystepwidget.h +++ b/src/plugins/android/androiddeploystepwidget.h @@ -55,6 +55,7 @@ private slots: void setQASIPackagePath(); void cleanLibsOnDevice(); + void resetDefaultDevices(); void deployOptionsChanged(); private: diff --git a/src/plugins/android/androiddeploystepwidget.ui b/src/plugins/android/androiddeploystepwidget.ui index 96ed9eb5330..2c44ffc55d2 100644 --- a/src/plugins/android/androiddeploystepwidget.ui +++ b/src/plugins/android/androiddeploystepwidget.ui @@ -6,7 +6,7 @@ 0 0 - 678 + 682 155 @@ -69,21 +69,21 @@ The APK will not be usable on any other device. Advanced Actions - + Clean Temporary Libraries Directory on Device - + Install Ministro from APK - + Qt::Vertical @@ -96,6 +96,13 @@ The APK will not be usable on any other device. + + + + Reset Default Devices + + + diff --git a/src/plugins/android/androiddevicedialog.cpp b/src/plugins/android/androiddevicedialog.cpp new file mode 100644 index 00000000000..eefd39f4c91 --- /dev/null +++ b/src/plugins/android/androiddevicedialog.cpp @@ -0,0 +1,472 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#include "androiddevicedialog.h" +#include "androidmanager.h" +#include "ui_androiddevicedialog.h" + +#include +#include + +using namespace Android; +using namespace Android::Internal; + +namespace Android { +namespace Internal { + +// yeah, writing tree models is fun! +class AndroidDeviceModelNode +{ +public: + AndroidDeviceModelNode(AndroidDeviceModelNode *parent, const AndroidDeviceInfo &info, const QString &incompatibleReason = QString()) + : m_parent(parent), m_info(info), m_incompatibleReason(incompatibleReason) + { + if (m_parent) + m_parent->m_children.append(this); + } + + AndroidDeviceModelNode(AndroidDeviceModelNode *parent, const QString &displayName) + : m_parent(parent), m_displayName(displayName) + { + if (m_parent) + m_parent->m_children.append(this); + } + + ~AndroidDeviceModelNode() + { + if (m_parent) + m_parent->m_children.removeOne(this); + QList children = m_children; + qDeleteAll(children); + } + + AndroidDeviceModelNode *parent() const + { + return m_parent; + } + + QList children() const + { + return m_children; + } + + AndroidDeviceInfo deviceInfo() const + { + return m_info; + } + + QString displayName() const + { + return m_displayName; + } + + QString incompatibleReason() const + { + return m_incompatibleReason; + } + +private: + AndroidDeviceModelNode *m_parent; + AndroidDeviceInfo m_info; + QString m_incompatibleReason; + QString m_displayName; + QList m_children; +}; + +class AndroidDeviceModelDelegate : public QStyledItemDelegate +{ +public: + AndroidDeviceModelDelegate(QObject * parent = 0) + : QStyledItemDelegate(parent) + { + + } + + ~AndroidDeviceModelDelegate() + { + } + + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const + { + QStyleOptionViewItemV4 opt = option; + initStyleOption(&opt, index); + painter->save(); + + AndroidDeviceModelNode *node = static_cast(index.internalPointer()); + AndroidDeviceInfo device = node->deviceInfo(); + + painter->setPen(Qt::NoPen); + + // Paint Background + QPalette palette = opt.palette; // we always draw enabled + palette.setCurrentColorGroup(QPalette::Active); + bool selected = opt.state & QStyle::State_Selected; + QColor backgroundColor = selected ? palette.highlight().color() + : palette.background().color(); + painter->setBrush(backgroundColor); + + painter->drawRect(0, opt.rect.top(), opt.rect.width() + opt.rect.left(), opt.rect.height()); + + QColor textColor; + // Set Text Color + if (opt.state & QStyle::State_Selected) + textColor = palette.highlightedText().color(); + else + textColor = palette.text().color(); + painter->setPen(textColor); + + if (!node->displayName().isEmpty()) { + QIcon icon(QLatin1String(":/core/images/darkarrowdown.png")); + int size = opt.rect.bottom() - opt.rect.top(); + QPixmap pixmap = icon.pixmap(size, size); + painter->drawPixmap((size - pixmap.width()) / 2, opt.rect.top() + (size - pixmap.height()) / 2, pixmap); + + // We have a top level node + QFont font = opt.font; + font.setPointSizeF(font.pointSizeF() * 1.2); + font.setBold(true); + + QFontMetrics fm(font); + painter->setFont(font); + int top = (opt.rect.bottom() + opt.rect.top() - fm.height()) / 2 + fm.ascent(); + painter->drawText(size, top, node->displayName()); + } else { + QIcon icon(device.type == AndroidDeviceInfo::Hardware ? QLatin1String(":/projectexplorer/images/MaemoDevice.png") + : QLatin1String(":/projectexplorer/images/Simulator.png")); + int size = opt.rect.bottom() - opt.rect.top() - 12; + QPixmap pixmap = icon.pixmap(size, size); + painter->drawPixmap(6 + (size - pixmap.width()) / 2, opt.rect.top() + 6 + (size - pixmap.width()) / 2, pixmap); + + QFontMetrics fm(opt.font); + // TopLeft + QString topLeft = device.serialNumber; + if (device.type == AndroidDeviceInfo::Hardware) + topLeft = AndroidConfigurations::instance().getProductModel(device.serialNumber); + painter->drawText(size + 12, 2 + opt.rect.top() + fm.ascent(), topLeft); + + QString topRight = device.serialNumber; + // topRight + if (device.type == AndroidDeviceInfo::Hardware) // otherwise it's not very informative + painter->drawText(opt.rect.right() - fm.width(topRight) - 6 , 2 + opt.rect.top() + fm.ascent(), topRight); + + // Directory + QColor mix; + mix.setRgbF(0.7 * textColor.redF() + 0.3 * backgroundColor.redF(), + 0.7 * textColor.greenF() + 0.3 * backgroundColor.greenF(), + 0.7 * textColor.blueF() + 0.3 * backgroundColor.blueF()); + painter->setPen(mix); + + QString lineText; + if (node->incompatibleReason().isEmpty()) { + lineText = AndroidManager::androidNameForApiLevel(device.sdk) + QLatin1String(" "); + lineText += tr("ABI:") + device.cpuAbi.join(QLatin1String(" ")); + } else { + lineText = node->incompatibleReason(); + } + painter->drawText(size + 12, opt.rect.top() + fm.ascent() + fm.height() + 6, lineText); + } + + // Separator lines + painter->setPen(QColor::fromRgb(150,150,150)); + painter->drawLine(0, opt.rect.bottom(), opt.rect.right(), opt.rect.bottom()); + painter->restore(); + } + + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const + { + QStyleOptionViewItemV4 opt = option; + initStyleOption(&opt, index); + + QFontMetrics fm(option.font); + QSize s; + s.setWidth(option.rect.width()); + s.setHeight(fm.height() * 2 + 10); + return s; + } +}; + +class AndroidDeviceModel : public QAbstractItemModel +{ +public: + AndroidDeviceModel(int apiLevel, const QString &abi); + QModelIndex index(int row, int column, + const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &child) const; + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + + AndroidDeviceInfo device(QModelIndex index); + void setDevices(const QVector &devices); + + QModelIndex indexFor(const QString &serial); +private: + int m_apiLevel; + QString m_abi; + AndroidDeviceModelNode *m_root; +}; + +} +} +///////////////// +// AndroidDeviceModel +///////////////// +AndroidDeviceModel::AndroidDeviceModel(int apiLevel, const QString &abi) + : m_apiLevel(apiLevel), m_abi(abi), m_root(0) +{ +} + +QModelIndex AndroidDeviceModel::index(int row, int column, const QModelIndex &parent) const +{ + if (column != 0) + return QModelIndex(); + + if (!m_root) + return QModelIndex(); + + if (!parent.isValid()) { + if (row < 0 || row >= m_root->children().count()) + return QModelIndex(); + return createIndex(row, column, m_root->children().at(row)); + } + + AndroidDeviceModelNode *node = static_cast(parent.internalPointer()); + if (row < node->children().count()) + return createIndex(row, column, node->children().at(row)); + + return QModelIndex(); +} + +QModelIndex AndroidDeviceModel::parent(const QModelIndex &child) const +{ + if (!child.isValid()) + return QModelIndex(); + if (!m_root) + return QModelIndex(); + AndroidDeviceModelNode *node = static_cast(child.internalPointer()); + if (node == m_root) + return QModelIndex(); + AndroidDeviceModelNode *parent = node->parent(); + + if (parent == m_root) + return QModelIndex(); + + AndroidDeviceModelNode *grandParent = parent->parent(); + return createIndex(grandParent->children().indexOf(parent), 0, parent); +} + +int AndroidDeviceModel::rowCount(const QModelIndex &parent) const +{ + if (!m_root) + return 0; + if (!parent.isValid()) + return m_root->children().count(); + AndroidDeviceModelNode *node = static_cast(parent.internalPointer()); + return node->children().count(); +} + +int AndroidDeviceModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 1; +} + +QVariant AndroidDeviceModel::data(const QModelIndex &index, int role) const +{ + if (role != Qt::DisplayRole) + return QVariant(); + AndroidDeviceModelNode *node = static_cast(index.internalPointer()); + if (!node) + return QVariant(); + return node->deviceInfo().serialNumber; +} + +Qt::ItemFlags AndroidDeviceModel::flags(const QModelIndex &index) const +{ + AndroidDeviceModelNode *node = static_cast(index.internalPointer()); + if (node) + if (node->displayName().isEmpty() && node->incompatibleReason().isEmpty()) + return Qt::ItemIsSelectable | Qt::ItemIsEnabled; + + return Qt::NoItemFlags; +} + +AndroidDeviceInfo AndroidDeviceModel::device(QModelIndex index) +{ + AndroidDeviceModelNode *node = static_cast(index.internalPointer()); + if (!node) + return AndroidDeviceInfo(); + return node->deviceInfo(); +} + +void AndroidDeviceModel::setDevices(const QVector &devices) +{ + beginResetModel(); + delete m_root; + m_root = new AndroidDeviceModelNode(0, QString()); + + AndroidDeviceModelNode *compatibleDevices = new AndroidDeviceModelNode(m_root, tr("Compatible devices")); + AndroidDeviceModelNode *incompatibleDevices = 0; // created on demand + foreach (const AndroidDeviceInfo &device, devices) { + QString error; + if (device.unauthorized) { + error = tr("Unauthorized. Please check the confirmation dialog on your device..").arg(device.serialNumber); + } else if (!device.cpuAbi.contains(m_abi)) { + error = tr("ABI is incompatible, device supports ABIs: %2.") + .arg(AndroidConfigurations::instance().getProductModel(device.serialNumber)) + .arg(device.cpuAbi.join(QLatin1String(" "))); + } else if (device.sdk < m_apiLevel) { + error = tr("API Level of device is: %2.") + .arg(AndroidConfigurations::instance().getProductModel(device.serialNumber)) + .arg(device.sdk); + } else { + new AndroidDeviceModelNode(compatibleDevices, device); + continue; + } + if (!incompatibleDevices) + incompatibleDevices = new AndroidDeviceModelNode(m_root, tr("Incompatible devices")); + new AndroidDeviceModelNode(incompatibleDevices, device, error); + } + endResetModel(); +} + +QModelIndex AndroidDeviceModel::indexFor(const QString &serial) +{ + foreach (AndroidDeviceModelNode *topLevelNode, m_root->children()) { + QList deviceNodes = topLevelNode->children(); + for (int i = 0; i < deviceNodes.size(); ++i) { + if (deviceNodes.at(i)->deviceInfo().serialNumber == serial) + return createIndex(i, 0, deviceNodes.at(i)); + } + } + return QModelIndex(); +} + +///////////////// +// AndroidDeviceDialog +///////////////// +AndroidDeviceDialog::AndroidDeviceDialog(int apiLevel, const QString &abi, QWidget *parent) : + QDialog(parent), + m_model(new AndroidDeviceModel(apiLevel, abi)), + m_ui(new Ui::AndroidDeviceDialog), + m_apiLevel(apiLevel), + m_abi(abi) +{ + m_ui->setupUi(this); + m_ui->deviceView->setModel(m_model); + m_ui->deviceView->setItemDelegate(new AndroidDeviceModelDelegate(m_ui->deviceView)); + m_ui->deviceView->setHeaderHidden(true); + m_ui->deviceView->setRootIsDecorated(false); + m_ui->deviceView->setUniformRowHeights(true); + + m_ui->defaultDeviceCheckBox->setText(tr("Always use this device for architecture %1").arg(abi)); + + connect(m_ui->refreshDevicesButton, SIGNAL(clicked()), + this, SLOT(refreshDeviceList())); + + connect(m_ui->createAVDButton, SIGNAL(clicked()), + this, SLOT(createAvd())); + + refreshDeviceList(); +} + +AndroidDeviceDialog::~AndroidDeviceDialog() +{ + delete m_ui; +} + +AndroidDeviceInfo AndroidDeviceDialog::device() +{ + if (result() == QDialog::Accepted) + return m_model->device(m_ui->deviceView->currentIndex()); + return AndroidDeviceInfo(); +} + +void AndroidDeviceDialog::accept() +{ + QDialog::accept(); +} + +bool AndroidDeviceDialog::saveDeviceSelection() +{ + return m_ui->defaultDeviceCheckBox->isChecked(); +} + +void AndroidDeviceDialog::refreshDeviceList() +{ + QString serialNumber; + QModelIndex currentIndex = m_ui->deviceView->currentIndex(); + if (currentIndex.isValid()) + serialNumber = m_model->device(currentIndex).serialNumber; + + QVector devices; + foreach (const AndroidDeviceInfo &info, AndroidConfigurations::instance().connectedDevices()) + if (info.type == AndroidDeviceInfo::Hardware) + devices << info; + + devices += AndroidConfigurations::instance().androidVirtualDevices(); + m_model->setDevices(devices); + + m_ui->deviceView->expand(m_model->index(0, 0)); + + // Smartly select a index + QModelIndex newIndex; + if (!serialNumber.isEmpty()) + newIndex = m_model->indexFor(serialNumber); + + if (!newIndex.isValid() && !devices.isEmpty()) + newIndex = m_model->indexFor(devices.first().serialNumber); + + m_ui->deviceView->setCurrentIndex(newIndex); +} + +void AndroidDeviceDialog::createAvd() +{ + QString avd = AndroidConfigurations::instance().createAVD(m_apiLevel, m_abi); + if (avd.isEmpty()) + return; + refreshDeviceList(); + QModelIndex index = m_model->indexFor(avd); + m_ui->deviceView->setCurrentIndex(index); +} + +// Does not work. +void AndroidDeviceDialog::clickedOnView(const QModelIndex &idx) +{ + if (idx.isValid()) { + AndroidDeviceModelNode *node = static_cast(idx.internalPointer()); + if (!node->displayName().isEmpty()) { + if (m_ui->deviceView->isExpanded(idx)) + m_ui->deviceView->collapse(idx); + else + m_ui->deviceView->expand(idx); + } + } +} diff --git a/src/plugins/android/androiddevicedialog.h b/src/plugins/android/androiddevicedialog.h new file mode 100644 index 00000000000..f42a71d2ac6 --- /dev/null +++ b/src/plugins/android/androiddevicedialog.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** 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 +#include + +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 diff --git a/src/plugins/android/androiddevicedialog.ui b/src/plugins/android/androiddevicedialog.ui new file mode 100644 index 00000000000..15e2ce29cf4 --- /dev/null +++ b/src/plugins/android/androiddevicedialog.ui @@ -0,0 +1,95 @@ + + + AndroidDeviceDialog + + + + 0 + 0 + 618 + 400 + + + + Select Android Device + + + + + + Refresh Device List + + + + + + + Create Android Virtual Device + + + + + + + Always use this device for architecture %1 + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + 600 + 300 + + + + + + + + + + buttonBox + accepted() + AndroidDeviceDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + AndroidDeviceDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +