androidconfigurations.cpp 26.7 KB
Newer Older
BogDan Vatra's avatar
BogDan Vatra committed
1 2
/**************************************************************************
**
3
** Copyright (c) 2013 BogDan Vatra <bog_dan_ro@yahoo.com>
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
BogDan Vatra's avatar
BogDan Vatra committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
BogDan Vatra's avatar
BogDan Vatra committed
7
**
hjk's avatar
hjk committed
8 9 10 11 12 13 14
** 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.
BogDan Vatra's avatar
BogDan Vatra committed
15 16
**
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17 18 19 20 21 22 23 24 25
** 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
BogDan Vatra's avatar
BogDan Vatra committed
26 27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
28
****************************************************************************/
BogDan Vatra's avatar
BogDan Vatra committed
29 30 31

#include "androidconfigurations.h"
#include "androidconstants.h"
Daniel Teske's avatar
Daniel Teske committed
32 33 34
#include "androidtoolchain.h"
#include "androiddevice.h"
#include "androidgdbserverkitinformation.h"
BogDan Vatra's avatar
BogDan Vatra committed
35 36 37
#include "ui_addnewavddialog.h"

#include <coreplugin/icore.h>
38
#include <utils/hostosinfo.h>
BogDan Vatra's avatar
BogDan Vatra committed
39
#include <utils/persistentsettings.h>
Daniel Teske's avatar
Daniel Teske committed
40 41 42 43 44 45 46 47
#include <projectexplorer/kitmanager.h>
#include <projectexplorer/kitinformation.h>
#include <projectexplorer/devicesupport/devicemanager.h>
#include <projectexplorer/toolchainmanager.h>
#include <debugger/debuggerkitinformation.h>
#include <qtsupport/baseqtversion.h>
#include <qtsupport/qtkitinformation.h>
#include <qtsupport/qtversionmanager.h>
BogDan Vatra's avatar
BogDan Vatra committed
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63

#include <QDateTime>
#include <QSettings>
#include <QStringList>
#include <QProcess>
#include <QFileInfo>
#include <QDirIterator>
#include <QMetaObject>

#include <QStringListModel>
#include <QMessageBox>

#if defined(_WIN32)
#include <iostream>
#include <windows.h>
#define sleep(_n) Sleep(1000 * (_n))
Thiago Macieira's avatar
Thiago Macieira committed
64 65
#else
#include <unistd.h>
BogDan Vatra's avatar
BogDan Vatra committed
66 67
#endif

hjk's avatar
hjk committed
68
using namespace ProjectExplorer;
BogDan Vatra's avatar
BogDan Vatra committed
69 70 71 72 73 74 75 76 77 78 79 80 81
using namespace Utils;

namespace Android {
namespace Internal {

namespace {
    const QLatin1String SettingsGroup("AndroidConfigurations");
    const QLatin1String SDKLocationKey("SDKLocation");
    const QLatin1String NDKLocationKey("NDKLocation");
    const QLatin1String NDKToolchainVersionKey("NDKToolchainVersion");
    const QLatin1String AntLocationKey("AntLocation");
    const QLatin1String OpenJDKLocationKey("OpenJDKLocation");
    const QLatin1String KeystoreLocationKey("KeystoreLocation");
Daniel Teske's avatar
Daniel Teske committed
82
    const QLatin1String AutomaticKitCreationKey("AutomatiKitCreation");
BogDan Vatra's avatar
BogDan Vatra committed
83
    const QLatin1String PartitionSizeKey("PartitionSize");
84
    const QLatin1String ToolchainHostKey("ToolchainHost");
BogDan Vatra's avatar
BogDan Vatra committed
85 86
    const QLatin1String ArmToolchainPrefix("arm-linux-androideabi");
    const QLatin1String X86ToolchainPrefix("x86");
BogDan Vatra's avatar
BogDan Vatra committed
87
    const QLatin1String MipsToolchainPrefix("mipsel-linux-android");
BogDan Vatra's avatar
BogDan Vatra committed
88
    const QLatin1String ArmToolsPrefix("arm-linux-androideabi");
BogDan Vatra's avatar
BogDan Vatra committed
89
    const QLatin1String X86ToolsPrefix("i686-linux-android");
BogDan Vatra's avatar
BogDan Vatra committed
90
    const QLatin1String MipsToolsPrefix("mipsel-linux-android");
BogDan Vatra's avatar
BogDan Vatra committed
91 92 93 94 95
    const QLatin1String Unknown("unknown");
    const QLatin1String keytoolName("keytool");
    const QLatin1String jarsignerName("jarsigner");
    const QLatin1String changeTimeStamp("ChangeTimeStamp");

96
    static QString sdkSettingsFileName()
BogDan Vatra's avatar
BogDan Vatra committed
97
    {
98 99
        return QFileInfo(Core::ICore::settings(QSettings::SystemScope)->fileName()).absolutePath()
                + QLatin1String("/qtcreator/android.xml");
BogDan Vatra's avatar
BogDan Vatra committed
100 101
    }

102
    bool androidDevicesLessThan(const AndroidDeviceInfo &dev1, const AndroidDeviceInfo &dev2)
BogDan Vatra's avatar
BogDan Vatra committed
103 104 105 106 107
    {
        return dev1.sdk < dev2.sdk;
    }
}

108 109 110 111 112 113
Abi::Architecture AndroidConfigurations::architectureForToolChainPrefix(const QString& toolchainprefix)
{
    if (toolchainprefix == ArmToolchainPrefix)
        return Abi::ArmArchitecture;
    if (toolchainprefix == X86ToolchainPrefix)
        return Abi::X86Architecture;
BogDan Vatra's avatar
BogDan Vatra committed
114 115
    if (toolchainprefix == MipsToolchainPrefix)
        return Abi::MipsArchitecture;
116 117 118
    return Abi::UnknownArchitecture;
}

hjk's avatar
hjk committed
119
QLatin1String AndroidConfigurations::toolchainPrefix(Abi::Architecture architecture)
BogDan Vatra's avatar
BogDan Vatra committed
120 121
{
    switch (architecture) {
hjk's avatar
hjk committed
122
    case Abi::ArmArchitecture:
BogDan Vatra's avatar
BogDan Vatra committed
123
        return ArmToolchainPrefix;
hjk's avatar
hjk committed
124
    case Abi::X86Architecture:
BogDan Vatra's avatar
BogDan Vatra committed
125
        return X86ToolchainPrefix;
BogDan Vatra's avatar
BogDan Vatra committed
126 127
    case Abi::MipsArchitecture:
        return MipsToolchainPrefix;
BogDan Vatra's avatar
BogDan Vatra committed
128 129 130 131 132
    default:
        return Unknown;
    }
}

hjk's avatar
hjk committed
133
QLatin1String AndroidConfigurations::toolsPrefix(Abi::Architecture architecture)
BogDan Vatra's avatar
BogDan Vatra committed
134 135
{
    switch (architecture) {
hjk's avatar
hjk committed
136
    case Abi::ArmArchitecture:
BogDan Vatra's avatar
BogDan Vatra committed
137
        return ArmToolsPrefix;
hjk's avatar
hjk committed
138
    case Abi::X86Architecture:
BogDan Vatra's avatar
BogDan Vatra committed
139
        return X86ToolsPrefix;
BogDan Vatra's avatar
BogDan Vatra committed
140 141
    case Abi::MipsArchitecture:
        return MipsToolsPrefix;
BogDan Vatra's avatar
BogDan Vatra committed
142 143 144 145 146 147 148 149 150
    default:
        return Unknown;
    }
}

AndroidConfig::AndroidConfig(const QSettings &settings)
{
    // user settings
    partitionSize = settings.value(PartitionSizeKey, 1024).toInt();
hjk's avatar
hjk committed
151 152 153 154 155
    sdkLocation = FileName::fromString(settings.value(SDKLocationKey).toString());
    ndkLocation = FileName::fromString(settings.value(NDKLocationKey).toString());
    antLocation = FileName::fromString(settings.value(AntLocationKey).toString());
    openJDKLocation = FileName::fromString(settings.value(OpenJDKLocationKey).toString());
    keystoreLocation = FileName::fromString(settings.value(KeystoreLocationKey).toString());
156
    toolchainHost = settings.value(ToolchainHostKey).toString();
Daniel Teske's avatar
Daniel Teske committed
157
    automaticKitCreation = settings.value(AutomaticKitCreationKey, true).toBool();
BogDan Vatra's avatar
BogDan Vatra committed
158 159

    PersistentSettingsReader reader;
160 161
    if (reader.load(FileName::fromString(sdkSettingsFileName()))
            && settings.value(changeTimeStamp).toInt() != QFileInfo(sdkSettingsFileName()).lastModified().toMSecsSinceEpoch() / 1000) {
BogDan Vatra's avatar
BogDan Vatra committed
162
        // persisten settings
hjk's avatar
hjk committed
163 164 165 166 167
        sdkLocation = FileName::fromString(reader.restoreValue(SDKLocationKey).toString());
        ndkLocation = FileName::fromString(reader.restoreValue(NDKLocationKey).toString());
        antLocation = FileName::fromString(reader.restoreValue(AntLocationKey).toString());
        openJDKLocation = FileName::fromString(reader.restoreValue(OpenJDKLocationKey).toString());
        keystoreLocation = FileName::fromString(reader.restoreValue(KeystoreLocationKey).toString());
168
        toolchainHost = reader.restoreValue(ToolchainHostKey).toString();
Daniel Teske's avatar
Daniel Teske committed
169 170 171
        QVariant v = reader.restoreValue(AutomaticKitCreationKey);
        if (v.isValid())
            automaticKitCreation = v.toBool();
BogDan Vatra's avatar
BogDan Vatra committed
172 173 174 175 176 177 178 179 180 181 182 183
        // persistent settings
    }

}

AndroidConfig::AndroidConfig()
{
    partitionSize = 1024;
}

void AndroidConfig::save(QSettings &settings) const
{
184
    QFileInfo fileInfo(sdkSettingsFileName());
BogDan Vatra's avatar
BogDan Vatra committed
185 186 187 188
    if (fileInfo.exists())
        settings.setValue(changeTimeStamp, fileInfo.lastModified().toMSecsSinceEpoch() / 1000);

    // user settings
Tobias Hunger's avatar
Tobias Hunger committed
189 190 191 192 193
    settings.setValue(SDKLocationKey, sdkLocation.toString());
    settings.setValue(NDKLocationKey, ndkLocation.toString());
    settings.setValue(AntLocationKey, antLocation.toString());
    settings.setValue(OpenJDKLocationKey, openJDKLocation.toString());
    settings.setValue(KeystoreLocationKey, keystoreLocation.toString());
BogDan Vatra's avatar
BogDan Vatra committed
194
    settings.setValue(PartitionSizeKey, partitionSize);
Daniel Teske's avatar
Daniel Teske committed
195
    settings.setValue(AutomaticKitCreationKey, automaticKitCreation);
196
    settings.setValue(ToolchainHostKey, toolchainHost);
BogDan Vatra's avatar
BogDan Vatra committed
197 198 199 200 201
}

void AndroidConfigurations::setConfig(const AndroidConfig &devConfigs)
{
    m_config = devConfigs;
202 203 204 205

    if (m_config.toolchainHost.isEmpty())
        detectToolchainHost();

BogDan Vatra's avatar
BogDan Vatra committed
206 207
    save();
    updateAvailablePlatforms();
Daniel Teske's avatar
Daniel Teske committed
208
    updateAutomaticKitList();
BogDan Vatra's avatar
BogDan Vatra committed
209 210 211 212 213 214
    emit updated();
}

void AndroidConfigurations::updateAvailablePlatforms()
{
    m_availablePlatforms.clear();
hjk's avatar
hjk committed
215
    FileName path = m_config.ndkLocation;
BogDan Vatra's avatar
BogDan Vatra committed
216
    QDirIterator it(path.appendPath(QLatin1String("platforms")).toString(), QStringList() << QLatin1String("android-*"), QDir::Dirs);
BogDan Vatra's avatar
BogDan Vatra committed
217 218 219 220 221 222 223 224 225 226 227
    while (it.hasNext()) {
        const QString &fileName = it.next();
        m_availablePlatforms.push_back(fileName.mid(fileName.lastIndexOf(QLatin1Char('-')) + 1).toInt());
    }
    qSort(m_availablePlatforms.begin(), m_availablePlatforms.end(), qGreater<int>());
}

QStringList AndroidConfigurations::sdkTargets(int minApiLevel) const
{
    QStringList targets;
    QProcess proc;
Tobias Hunger's avatar
Tobias Hunger committed
228
    proc.start(androidToolPath().toString(), QStringList() << QLatin1String("list") << QLatin1String("target")); // list avaialbe AVDs
BogDan Vatra's avatar
BogDan Vatra committed
229 230 231 232
    if (!proc.waitForFinished(-1)) {
        proc.terminate();
        return targets;
    }
233
    while (proc.canReadLine()) {
234
        const QString line = QString::fromLocal8Bit(proc.readLine().trimmed());
BogDan Vatra's avatar
BogDan Vatra committed
235 236 237 238 239 240 241 242 243 244
        int index = line.indexOf(QLatin1String("\"android-"));
        if (index == -1)
            continue;
        QString apiLevel = line.mid(index + 1, line.length() - index - 2);
        if (apiLevel.mid(apiLevel.lastIndexOf(QLatin1Char('-')) + 1).toInt() >= minApiLevel)
            targets.push_back(apiLevel);
    }
    return targets;
}

hjk's avatar
hjk committed
245
FileName AndroidConfigurations::adbToolPath() const
BogDan Vatra's avatar
BogDan Vatra committed
246
{
hjk's avatar
hjk committed
247
    FileName path = m_config.sdkLocation;
248
    return path.appendPath(QLatin1String("platform-tools/adb" QTC_HOST_EXE_SUFFIX));
BogDan Vatra's avatar
BogDan Vatra committed
249 250
}

hjk's avatar
hjk committed
251
FileName AndroidConfigurations::androidToolPath() const
BogDan Vatra's avatar
BogDan Vatra committed
252
{
253 254 255 256
    if (HostOsInfo::isWindowsHost()) {
        // I want to switch from using android.bat to using an executable. All it really does is call
        // Java and I've made some progress on it. So if android.exe exists, return that instead.
        FileName path = m_config.sdkLocation;
257
        path.appendPath(QLatin1String("tools/android" QTC_HOST_EXE_SUFFIX));
258 259 260
        if (path.toFileInfo().exists())
            return path;
        path = m_config.sdkLocation;
261
        return path.appendPath(QLatin1String("tools/android" ANDROID_BAT_SUFFIX));
262 263 264 265
    } else {
        FileName path = m_config.sdkLocation;
        return path.appendPath(QLatin1String("tools/android"));
    }
BogDan Vatra's avatar
BogDan Vatra committed
266 267
}

hjk's avatar
hjk committed
268
FileName AndroidConfigurations::antToolPath() const
BogDan Vatra's avatar
BogDan Vatra committed
269
{
Tobias Hunger's avatar
Tobias Hunger committed
270
    if (!m_config.antLocation.isEmpty())
BogDan Vatra's avatar
BogDan Vatra committed
271 272
        return m_config.antLocation;
    else
hjk's avatar
hjk committed
273
        return FileName::fromString(QLatin1String("ant"));
BogDan Vatra's avatar
BogDan Vatra committed
274 275
}

hjk's avatar
hjk committed
276
FileName AndroidConfigurations::emulatorToolPath() const
BogDan Vatra's avatar
BogDan Vatra committed
277
{
hjk's avatar
hjk committed
278
    FileName path = m_config.sdkLocation;
279
    return path.appendPath(QLatin1String("tools/emulator" QTC_HOST_EXE_SUFFIX));
BogDan Vatra's avatar
BogDan Vatra committed
280 281
}

282
FileName AndroidConfigurations::toolPath(Abi::Architecture architecture, const QString &ndkToolChainVersion) const
BogDan Vatra's avatar
BogDan Vatra committed
283
{
hjk's avatar
hjk committed
284
    FileName path = m_config.ndkLocation;
Tobias Hunger's avatar
Tobias Hunger committed
285
    return path.appendPath(QString::fromLatin1("toolchains/%1-%2/prebuilt/%3/bin/%4")
BogDan Vatra's avatar
BogDan Vatra committed
286
            .arg(toolchainPrefix(architecture))
287
            .arg(ndkToolChainVersion)
288
            .arg(m_config.toolchainHost)
Tobias Hunger's avatar
Tobias Hunger committed
289
            .arg(toolsPrefix(architecture)));
BogDan Vatra's avatar
BogDan Vatra committed
290 291
}

292
FileName AndroidConfigurations::stripPath(Abi::Architecture architecture, const QString &ndkToolChainVersion) const
BogDan Vatra's avatar
BogDan Vatra committed
293
{
294
    return toolPath(architecture, ndkToolChainVersion).append(QLatin1String("-strip" QTC_HOST_EXE_SUFFIX));
BogDan Vatra's avatar
BogDan Vatra committed
295 296
}

297
FileName AndroidConfigurations::readelfPath(Abi::Architecture architecture, const QString &ndkToolChainVersion) const
BogDan Vatra's avatar
BogDan Vatra committed
298
{
299
    return toolPath(architecture, ndkToolChainVersion).append(QLatin1String("-readelf" QTC_HOST_EXE_SUFFIX));
BogDan Vatra's avatar
BogDan Vatra committed
300 301
}

302 303 304 305 306
FileName AndroidConfigurations::gccPath(Abi::Architecture architecture, const QString &ndkToolChainVersion) const
{
    return toolPath(architecture, ndkToolChainVersion).append(QLatin1String("-gcc" QTC_HOST_EXE_SUFFIX));
}

307
FileName AndroidConfigurations::gdbPath(Abi::Architecture architecture, const QString &ndkToolChainVersion) const
BogDan Vatra's avatar
BogDan Vatra committed
308
{
309
    return toolPath(architecture, ndkToolChainVersion).append(QLatin1String("-gdb" QTC_HOST_EXE_SUFFIX));
BogDan Vatra's avatar
BogDan Vatra committed
310 311
}

hjk's avatar
hjk committed
312
FileName AndroidConfigurations::openJDKPath() const
BogDan Vatra's avatar
BogDan Vatra committed
313 314 315 316
{
    return m_config.openJDKLocation;
}

317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
void AndroidConfigurations::detectToolchainHost()
{
    QStringList hostPatterns;
    switch (HostOsInfo::hostOs()) {
    case HostOsInfo::HostOsLinux:
        hostPatterns << QLatin1String("linux*");
        break;
    case HostOsInfo::HostOsWindows:
        hostPatterns << QLatin1String("windows*");
        break;
    case HostOsInfo::HostOsMac:
        hostPatterns << QLatin1String("darwin*");
        break;
    default: /* unknown host */ return;
    }

    FileName path = m_config.ndkLocation;
    QDirIterator it(path.appendPath(QLatin1String("prebuilt")).toString(), hostPatterns, QDir::Dirs);
    if (it.hasNext()) {
        it.next();
        m_config.toolchainHost = it.fileName();
    }
}

hjk's avatar
hjk committed
341
FileName AndroidConfigurations::openJDKBinPath() const
BogDan Vatra's avatar
BogDan Vatra committed
342
{
hjk's avatar
hjk committed
343
    FileName path = m_config.openJDKLocation;
Tobias Hunger's avatar
Tobias Hunger committed
344 345 346
    if (!path.isEmpty())
        return path.appendPath(QLatin1String("bin"));
    return path;
BogDan Vatra's avatar
BogDan Vatra committed
347 348
}

hjk's avatar
hjk committed
349
FileName AndroidConfigurations::keytoolPath() const
BogDan Vatra's avatar
BogDan Vatra committed
350
{
Tobias Hunger's avatar
Tobias Hunger committed
351
    return openJDKBinPath().appendPath(keytoolName);
BogDan Vatra's avatar
BogDan Vatra committed
352 353
}

hjk's avatar
hjk committed
354
FileName AndroidConfigurations::jarsignerPath() const
BogDan Vatra's avatar
BogDan Vatra committed
355
{
Tobias Hunger's avatar
Tobias Hunger committed
356
    return openJDKBinPath().appendPath(jarsignerName);
BogDan Vatra's avatar
BogDan Vatra committed
357 358
}

hjk's avatar
hjk committed
359
FileName AndroidConfigurations::zipalignPath() const
360 361
{
    Utils::FileName path = m_config.sdkLocation;
362
    return path.appendPath(QLatin1String("tools/zipalign" QTC_HOST_EXE_SUFFIX));
363 364
}

BogDan Vatra's avatar
BogDan Vatra committed
365 366
QString AndroidConfigurations::getDeployDeviceSerialNumber(int *apiLevel) const
{
367
    QVector<AndroidDeviceInfo> devices = connectedDevices();
BogDan Vatra's avatar
BogDan Vatra committed
368

369
    foreach (AndroidDeviceInfo device, devices) {
BogDan Vatra's avatar
BogDan Vatra committed
370 371 372 373
        if (device.sdk >= *apiLevel) {
            *apiLevel = device.sdk;
            return device.serialNumber;
        }
Tobias Hunger's avatar
Tobias Hunger committed
374
    }
BogDan Vatra's avatar
BogDan Vatra committed
375 376 377
    return startAVD(apiLevel);
}

378
QVector<AndroidDeviceInfo> AndroidConfigurations::connectedDevices(int apiLevel) const
BogDan Vatra's avatar
BogDan Vatra committed
379
{
380
    QVector<AndroidDeviceInfo> devices;
BogDan Vatra's avatar
BogDan Vatra committed
381
    QProcess adbProc;
Tobias Hunger's avatar
Tobias Hunger committed
382
    adbProc.start(adbToolPath().toString(), QStringList() << QLatin1String("devices"));
BogDan Vatra's avatar
BogDan Vatra committed
383
    if (!adbProc.waitForFinished(-1)) {
384
        adbProc.kill();
BogDan Vatra's avatar
BogDan Vatra committed
385 386 387 388
        return devices;
    }
    QList<QByteArray> adbDevs = adbProc.readAll().trimmed().split('\n');
    adbDevs.removeFirst();
389
    AndroidDeviceInfo dev;
390 391 392 393 394

    // workaround for '????????????' serial numbers:
    // can use "adb -d" when only one usb device attached
    int usbDevicesNum = 0;
    QStringList serialNumbers;
BogDan Vatra's avatar
BogDan Vatra committed
395
    foreach (const QByteArray &device, adbDevs) {
396 397 398 399 400 401 402 403 404 405 406
        const QString serialNo = QString::fromLatin1(device.left(device.indexOf('\t')).trimmed());;
        if (!serialNo.startsWith(QLatin1String("emulator")))
            ++usbDevicesNum;
        serialNumbers << serialNo;
    }

    foreach (const QString &serialNo, serialNumbers) {
        if (serialNo.contains(QLatin1String("????")) && usbDevicesNum > 1)
            continue;

        dev.serialNumber = serialNo;
BogDan Vatra's avatar
BogDan Vatra committed
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423
        dev.sdk = getSDKVersion(dev.serialNumber);
        if (apiLevel != -1 && dev.sdk != apiLevel)
            continue;
        devices.push_back(dev);
    }
    qSort(devices.begin(), devices.end(), androidDevicesLessThan);
    return devices;
}

bool AndroidConfigurations::createAVD(int minApiLevel) const
{
    QDialog d;
    Ui::AddNewAVDDialog avdDialog;
    avdDialog.setupUi(&d);
    QStringListModel model(sdkTargets(minApiLevel));
    avdDialog.targetComboBox->setModel(&model);
    if (!model.rowCount()) {
Friedemann Kleint's avatar
Friedemann Kleint committed
424
        QMessageBox::critical(0, tr("Error Creating AVD"),
425
                              tr("Cannot create a new AVD. No sufficiently recent Android SDK available.\n"
Friedemann Kleint's avatar
Friedemann Kleint committed
426
                                 "Please install an SDK of at least API version %1.").
Friedemann Kleint's avatar
Friedemann Kleint committed
427
                              arg(minApiLevel));
BogDan Vatra's avatar
BogDan Vatra committed
428 429 430 431 432 433 434 435 436 437 438 439 440 441
        return false;
    }

    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());
}

bool AndroidConfigurations::createAVD(const QString &target, const QString &name, int sdcardSize ) const
{
    QProcess proc;
Tobias Hunger's avatar
Tobias Hunger committed
442
    proc.start(androidToolPath().toString(),
BogDan Vatra's avatar
BogDan Vatra committed
443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459
               QStringList() << QLatin1String("create") << QLatin1String("avd")
               << QLatin1String("-a") << QLatin1String("-t") << target
               << QLatin1String("-n") << name
               << QLatin1String("-c") << QString::fromLatin1("%1M").arg(sdcardSize));
    if (!proc.waitForStarted())
        return false;
    proc.write(QByteArray("no\n"));
    if (!proc.waitForFinished(-1)) {
        proc.terminate();
        return false;
    }
    return !proc.exitCode();
}

bool AndroidConfigurations::removeAVD(const QString &name) const
{
    QProcess proc;
Tobias Hunger's avatar
Tobias Hunger committed
460
    proc.start(androidToolPath().toString(),
BogDan Vatra's avatar
BogDan Vatra committed
461 462 463 464 465 466 467 468 469
               QStringList() << QLatin1String("delete") << QLatin1String("avd")
               << QLatin1String("-n") << name);
    if (!proc.waitForFinished(-1)) {
        proc.terminate();
        return false;
    }
    return !proc.exitCode();
}

470
QVector<AndroidDeviceInfo> AndroidConfigurations::androidVirtualDevices() const
BogDan Vatra's avatar
BogDan Vatra committed
471
{
472
    QVector<AndroidDeviceInfo> devices;
BogDan Vatra's avatar
BogDan Vatra committed
473
    QProcess proc;
Tobias Hunger's avatar
Tobias Hunger committed
474
    proc.start(androidToolPath().toString(),
BogDan Vatra's avatar
BogDan Vatra committed
475 476 477 478 479 480 481
               QStringList() << QLatin1String("list") << QLatin1String("avd")); // list available AVDs
    if (!proc.waitForFinished(-1)) {
        proc.terminate();
        return devices;
    }
    QList<QByteArray> avds = proc.readAll().trimmed().split('\n');
    avds.removeFirst();
482
    AndroidDeviceInfo dev;
BogDan Vatra's avatar
BogDan Vatra committed
483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507
    for (int i = 0; i < avds.size(); i++) {
        QString line = QLatin1String(avds[i]);
        if (!line.contains(QLatin1String("Name:")))
            continue;

        dev.serialNumber = line.mid(line.indexOf(QLatin1Char(':')) + 2).trimmed();
        ++i;
        for (; i < avds.size(); ++i) {
            line = QLatin1String(avds[i]);
            if (line.contains(QLatin1String("---------")))
                break;
            if (line.contains(QLatin1String("Target:")))
                dev.sdk = line.mid(line.lastIndexOf(QLatin1Char(' '))).remove(QLatin1Char(')')).toInt();
            if (line.contains(QLatin1String("ABI:")))
                dev.cpuABI = line.mid(line.lastIndexOf(QLatin1Char(' '))).trimmed();
        }
        devices.push_back(dev);
    }
    qSort(devices.begin(), devices.end(), androidDevicesLessThan);

    return devices;
}

QString AndroidConfigurations::startAVD(int *apiLevel, const QString &name) const
{
Orgad Shaneh's avatar
Orgad Shaneh committed
508 509 510
    QProcess *avdProcess = new QProcess();
    connect(this, SIGNAL(destroyed()), avdProcess, SLOT(deleteLater()));
    connect(avdProcess, SIGNAL(finished(int)), avdProcess, SLOT(deleteLater()));
BogDan Vatra's avatar
BogDan Vatra committed
511 512

    QString avdName = name;
513
    QVector<AndroidDeviceInfo> devices;
BogDan Vatra's avatar
BogDan Vatra committed
514 515 516 517
    bool createAVDOnce = false;
    while (true) {
        if (avdName.isEmpty()) {
            devices = androidVirtualDevices();
Orgad Shaneh's avatar
Orgad Shaneh committed
518
            foreach (const AndroidDeviceInfo &device, devices)
BogDan Vatra's avatar
BogDan Vatra committed
519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538
                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
Orgad Shaneh's avatar
Orgad Shaneh committed
539
    avdProcess->start(emulatorToolPath().toString(),
BogDan Vatra's avatar
BogDan Vatra committed
540 541
                        QStringList() << QLatin1String("-partition-size") << QString::number(config().partitionSize)
                        << QLatin1String("-avd") << avdName);
Orgad Shaneh's avatar
Orgad Shaneh committed
542 543
    if (!avdProcess->waitForStarted(-1)) {
        delete avdProcess;
BogDan Vatra's avatar
BogDan Vatra committed
544 545 546 547 548
        return QString();
    }

    // wait until the emulator is online
    QProcess proc;
Tobias Hunger's avatar
Tobias Hunger committed
549
    proc.start(adbToolPath().toString(), QStringList() << QLatin1String("-e") << QLatin1String("wait-for-device"));
Orgad Shaneh's avatar
Orgad Shaneh committed
550 551
    while (!proc.waitForFinished(500)) {
        if (avdProcess->waitForFinished(0)) {
552
            proc.kill();
Orgad Shaneh's avatar
Orgad Shaneh committed
553 554 555
            proc.waitForFinished(-1);
            return QString();
        }
BogDan Vatra's avatar
BogDan Vatra committed
556 557 558 559
    }
    sleep(5);// wait for pm to start

    // workaround for stupid adb bug
Tobias Hunger's avatar
Tobias Hunger committed
560
    proc.start(adbToolPath().toString(), QStringList() << QLatin1String("devices"));
BogDan Vatra's avatar
BogDan Vatra committed
561
    if (!proc.waitForFinished(-1)) {
562
        proc.kill();
BogDan Vatra's avatar
BogDan Vatra committed
563 564 565 566 567
        return QString();
    }

    // get connected devices
    devices = connectedDevices(*apiLevel);
568
    foreach (AndroidDeviceInfo device, devices)
BogDan Vatra's avatar
BogDan Vatra committed
569 570 571 572 573 574 575 576
        if (device.sdk == *apiLevel)
            return device.serialNumber;
    // this should not happen, but ...
    return QString();
}

int AndroidConfigurations::getSDKVersion(const QString &device) const
{
577 578 579 580
    // workaround for '????????????' serial numbers
    QStringList arguments = AndroidDeviceInfo::adbSelector(device);
    arguments << QLatin1String("shell") << QLatin1String("getprop")
              << QLatin1String("ro.build.version.sdk");
BogDan Vatra's avatar
BogDan Vatra committed
581 582

    QProcess adbProc;
583
    adbProc.start(adbToolPath().toString(), arguments);
BogDan Vatra's avatar
BogDan Vatra committed
584
    if (!adbProc.waitForFinished(-1)) {
585
        adbProc.kill();
BogDan Vatra's avatar
BogDan Vatra committed
586 587 588 589 590 591 592 593 594 595 596 597 598 599 600
        return -1;
    }
    return adbProc.readAll().trimmed().toInt();
}

QString AndroidConfigurations::bestMatch(const QString &targetAPI) const
{
    int target = targetAPI.mid(targetAPI.lastIndexOf(QLatin1Char('-')) + 1).toInt();
    foreach (int apiLevel, m_availablePlatforms) {
        if (apiLevel <= target)
            return QString::fromLatin1("android-%1").arg(apiLevel);
    }
    return QLatin1String("android-8");
}

Daniel Teske's avatar
Daniel Teske committed
601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628
bool equalKits(Kit *a, Kit *b)
{
    return ToolChainKitInformation::toolChain(a) == ToolChainKitInformation::toolChain(b)
            && QtSupport::QtKitInformation::qtVersion(a) == QtSupport::QtKitInformation::qtVersion(b);
}

void AndroidConfigurations::updateAutomaticKitList()
{
    QList<AndroidToolChain *> toolchains;
    if (AndroidConfigurations::instance().config().automaticKitCreation) {
        // having a empty toolchains list will remove all autodetected kits for android
        // exactly what we want in that case
        foreach (ProjectExplorer::ToolChain *tc, ProjectExplorer::ToolChainManager::instance()->toolChains()) {
            if (!tc->isAutoDetected())
                continue;
            if (tc->type() != QLatin1String(Constants::ANDROID_TOOLCHAIN_TYPE))
                continue;
            toolchains << static_cast<AndroidToolChain *>(tc);
        }
    }

    QList<Kit *> existingKits;

    foreach (ProjectExplorer::Kit *k, ProjectExplorer::KitManager::instance()->kits()) {
        if (ProjectExplorer::DeviceKitInformation::deviceId(k) != Core::Id(Constants::ANDROID_DEVICE_ID))
            continue;
        if (!k->isAutoDetected())
            continue;
Daniel Teske's avatar
Daniel Teske committed
629 630
        if (k->isSdkProvided())
            continue;
Daniel Teske's avatar
Daniel Teske committed
631 632 633 634 635 636 637 638

        existingKits << k;
    }

    QMap<ProjectExplorer::Abi::Architecture, QList<QtSupport::BaseQtVersion *> > qtVersionsForArch;
    foreach (QtSupport::BaseQtVersion *qtVersion, QtSupport::QtVersionManager::instance()->versions()) {
        if (qtVersion->type() != QLatin1String(Constants::ANDROIDQT))
            continue;
639 640 641 642
        QList<ProjectExplorer::Abi> qtAbis = qtVersion->qtAbis();
        if (qtAbis.empty())
            continue;
        qtVersionsForArch[qtAbis.first().architecture()].append(qtVersion);
Daniel Teske's avatar
Daniel Teske committed
643 644 645 646 647 648 649 650 651 652 653 654
    }

    ProjectExplorer::DeviceManager *dm = ProjectExplorer::DeviceManager::instance();
    IDevice::ConstPtr device = dm->find(Core::Id(Constants::ANDROID_DEVICE_ID)); // should always exist

    // register new kits
    QList<Kit *> newKits;
    foreach (AndroidToolChain *tc, toolchains) {
        QList<QtSupport::BaseQtVersion *> qtVersions = qtVersionsForArch.value(tc->targetAbi().architecture());
        foreach (QtSupport::BaseQtVersion *qt, qtVersions) {
            Kit *newKit = new Kit;
            newKit->setAutoDetected(true);
Daniel Teske's avatar
Daniel Teske committed
655
            newKit->setIconPath(QLatin1String(Constants::ANDROID_SETTINGS_CATEGORY_ICON));
Daniel Teske's avatar
Daniel Teske committed
656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685
            DeviceTypeKitInformation::setDeviceTypeId(newKit, Core::Id(Constants::ANDROID_DEVICE_TYPE));
            ToolChainKitInformation::setToolChain(newKit, tc);
            QtSupport::QtKitInformation::setQtVersion(newKit, qt);
            DeviceKitInformation::setDevice(newKit, device);
            Debugger::DebuggerKitInformation::DebuggerItem item;
            item.engineType = Debugger::GdbEngineType;
            item.binary = tc->suggestedDebugger();
            Debugger::DebuggerKitInformation::setDebuggerItem(newKit, item);
            AndroidGdbServerKitInformation::setGdbSever(newKit, tc->suggestedGdbServer());
            newKits << newKit;
        }
    }

    for (int i = existingKits.count() - 1; i >= 0; --i) {
        Kit *existingKit = existingKits.at(i);
        for (int j = 0; j < newKits.count(); ++j) {
            Kit *newKit = newKits.at(j);
            if (equalKits(existingKit, newKit)) {
                // Kit is already registered, nothing to do
                newKits.removeAt(j);
                existingKits.removeAt(i);
                KitManager::deleteKit(newKit);
                j = newKits.count();
            }
        }
    }

    foreach (Kit *k, existingKits)
        KitManager::instance()->deregisterKit(k);

686 687 688 689 690 691 692 693
    foreach (Kit *kit, newKits) {
        AndroidToolChain *tc = static_cast<AndroidToolChain *>(ToolChainKitInformation::toolChain(kit));
        QString arch = ProjectExplorer::Abi::toString(tc->targetAbi().architecture());
        QtSupport::BaseQtVersion *qt = QtSupport::QtKitInformation::qtVersion(kit);
        kit->setDisplayName(tr("Android for %1 (GCC %2, Qt %3)")
                            .arg(arch)
                            .arg(tc->ndkToolChainVersion())
                            .arg(qt->qtVersionString()));
Daniel Teske's avatar
Daniel Teske committed
694
        KitManager::instance()->registerKit(kit);
695
    }
Daniel Teske's avatar
Daniel Teske committed
696 697
}

698 699 700 701 702 703 704 705 706 707 708
/**
 * Workaround for '????????????' serial numbers
 * @return ("-d") for buggy devices, ("-s", <serial no>) for normal
 */
QStringList AndroidDeviceInfo::adbSelector(const QString &serialNumber)
{
    if (serialNumber.startsWith(QLatin1String("????")))
        return QStringList() << QLatin1String("-d");
    return QStringList() << QLatin1String("-s") << serialNumber;
}

BogDan Vatra's avatar
BogDan Vatra committed
709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742
AndroidConfigurations &AndroidConfigurations::instance(QObject *parent)
{
    if (m_instance == 0)
        m_instance = new AndroidConfigurations(parent);
    return *m_instance;
}

void AndroidConfigurations::save()
{
    QSettings *settings = Core::ICore::instance()->settings();
    settings->beginGroup(SettingsGroup);
    m_config.save(*settings);
    settings->endGroup();
}

AndroidConfigurations::AndroidConfigurations(QObject *parent)
    : QObject(parent)
{
    load();
    updateAvailablePlatforms();
}

void AndroidConfigurations::load()
{
    QSettings *settings = Core::ICore::instance()->settings();
    settings->beginGroup(SettingsGroup);
    m_config = AndroidConfig(*settings);
    settings->endGroup();
}

AndroidConfigurations *AndroidConfigurations::m_instance = 0;

} // namespace Internal
} // namespace Android