androidconfigurations.cpp 22.9 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 32 33 34

#include "androidconfigurations.h"
#include "androidconstants.h"
#include "ui_addnewavddialog.h"

#include <coreplugin/icore.h>
35
#include <utils/hostosinfo.h>
BogDan Vatra's avatar
BogDan Vatra committed
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
#include <utils/persistentsettings.h>

#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
53 54
#else
#include <unistd.h>
BogDan Vatra's avatar
BogDan Vatra committed
55 56
#endif

hjk's avatar
hjk committed
57
using namespace ProjectExplorer;
BogDan Vatra's avatar
BogDan Vatra committed
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
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 ArmGdbLocationKey("GdbLocation");
    const QLatin1String ArmGdbserverLocationKey("GdbserverLocation");
    const QLatin1String X86GdbLocationKey("X86GdbLocation");
    const QLatin1String X86GdbserverLocationKey("X86GdbserverLocation");
    const QLatin1String OpenJDKLocationKey("OpenJDKLocation");
    const QLatin1String KeystoreLocationKey("KeystoreLocation");
    const QLatin1String PartitionSizeKey("PartitionSize");
76
    const QLatin1String NDKGccVersionRegExp("-\\d[\\.\\d]+");
BogDan Vatra's avatar
BogDan Vatra committed
77 78 79 80 81 82 83 84 85
    const QLatin1String ArmToolchainPrefix("arm-linux-androideabi");
    const QLatin1String X86ToolchainPrefix("x86");
    const QLatin1String ArmToolsPrefix("arm-linux-androideabi");
    const QLatin1String X86ToolsPrefix("i686-android-linux");
    const QLatin1String Unknown("unknown");
    const QLatin1String keytoolName("keytool");
    const QLatin1String jarsignerName("jarsigner");
    const QLatin1String changeTimeStamp("ChangeTimeStamp");

86
    static QString sdkSettingsFileName()
BogDan Vatra's avatar
BogDan Vatra committed
87
    {
88 89
        return QString::fromLatin1("%1/android.xml")
                                           .arg(QFileInfo(Core::ICore::settings(QSettings::SystemScope)->fileName()).absolutePath());
BogDan Vatra's avatar
BogDan Vatra committed
90 91
    }

92
    bool androidDevicesLessThan(const AndroidDeviceInfo &dev1, const AndroidDeviceInfo &dev2)
BogDan Vatra's avatar
BogDan Vatra committed
93 94 95 96 97
    {
        return dev1.sdk < dev2.sdk;
    }
}

hjk's avatar
hjk committed
98
QLatin1String AndroidConfigurations::toolchainPrefix(Abi::Architecture architecture)
BogDan Vatra's avatar
BogDan Vatra committed
99 100
{
    switch (architecture) {
hjk's avatar
hjk committed
101
    case Abi::ArmArchitecture:
BogDan Vatra's avatar
BogDan Vatra committed
102
        return ArmToolchainPrefix;
hjk's avatar
hjk committed
103
    case Abi::X86Architecture:
BogDan Vatra's avatar
BogDan Vatra committed
104 105 106 107 108 109
        return X86ToolchainPrefix;
    default:
        return Unknown;
    }
}

hjk's avatar
hjk committed
110
QLatin1String AndroidConfigurations::toolsPrefix(Abi::Architecture architecture)
BogDan Vatra's avatar
BogDan Vatra committed
111 112
{
    switch (architecture) {
hjk's avatar
hjk committed
113
    case Abi::ArmArchitecture:
BogDan Vatra's avatar
BogDan Vatra committed
114
        return ArmToolsPrefix;
hjk's avatar
hjk committed
115
    case Abi::X86Architecture:
BogDan Vatra's avatar
BogDan Vatra committed
116 117 118 119 120 121 122 123 124
        return X86ToolsPrefix;
    default:
        return Unknown;
    }
}

AndroidConfig::AndroidConfig(const QSettings &settings)
{
    // user settings
hjk's avatar
hjk committed
125 126 127 128
    armGdbLocation = FileName::fromString(settings.value(ArmGdbLocationKey).toString());
    armGdbserverLocation = FileName::fromString(settings.value(ArmGdbserverLocationKey).toString());
    x86GdbLocation = FileName::fromString(settings.value(X86GdbLocationKey).toString());
    x86GdbserverLocation = FileName::fromString(settings.value(X86GdbserverLocationKey).toString());
BogDan Vatra's avatar
BogDan Vatra committed
129
    partitionSize = settings.value(PartitionSizeKey, 1024).toInt();
hjk's avatar
hjk committed
130 131 132 133 134
    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());
BogDan Vatra's avatar
BogDan Vatra committed
135 136 137 138 139 140

    QRegExp versionRegExp(NDKGccVersionRegExp);
    const QString &value = settings.value(NDKToolchainVersionKey).toString();
    if (versionRegExp.exactMatch(value))
        ndkToolchainVersion = value;
    else
141
        ndkToolchainVersion = value.mid(versionRegExp.indexIn(value)+1);
BogDan Vatra's avatar
BogDan Vatra committed
142 143 144
    // user settings

    PersistentSettingsReader reader;
145 146
    if (reader.load(FileName::fromString(sdkSettingsFileName()))
            && settings.value(changeTimeStamp).toInt() != QFileInfo(sdkSettingsFileName()).lastModified().toMSecsSinceEpoch() / 1000) {
BogDan Vatra's avatar
BogDan Vatra committed
147
        // persisten settings
hjk's avatar
hjk committed
148 149 150 151 152
        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());
BogDan Vatra's avatar
BogDan Vatra committed
153 154 155 156 157 158

        QRegExp versionRegExp(NDKGccVersionRegExp);
        const QString &value = reader.restoreValue(NDKToolchainVersionKey).toString();
        if (versionRegExp.exactMatch(value))
            ndkToolchainVersion = value;
        else
159
            ndkToolchainVersion = value.mid(versionRegExp.indexIn(value)+1);
BogDan Vatra's avatar
BogDan Vatra committed
160

Tobias Hunger's avatar
Tobias Hunger committed
161
        if (armGdbLocation.isEmpty())
hjk's avatar
hjk committed
162
            armGdbLocation = FileName::fromString(reader.restoreValue(ArmGdbLocationKey).toString());
BogDan Vatra's avatar
BogDan Vatra committed
163

Tobias Hunger's avatar
Tobias Hunger committed
164
        if (armGdbserverLocation.isEmpty())
hjk's avatar
hjk committed
165
            armGdbserverLocation = FileName::fromString(reader.restoreValue(ArmGdbserverLocationKey).toString());
BogDan Vatra's avatar
BogDan Vatra committed
166

Tobias Hunger's avatar
Tobias Hunger committed
167
        if (x86GdbLocation.isEmpty())
hjk's avatar
hjk committed
168
            x86GdbLocation = FileName::fromString(reader.restoreValue(X86GdbLocationKey).toString());
BogDan Vatra's avatar
BogDan Vatra committed
169

Tobias Hunger's avatar
Tobias Hunger committed
170
        if (x86GdbserverLocation.isEmpty())
hjk's avatar
hjk committed
171
            x86GdbserverLocation = FileName::fromString(reader.restoreValue(X86GdbserverLocationKey).toString());
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
    settings.setValue(SDKLocationKey, sdkLocation.toString());
    settings.setValue(NDKLocationKey, ndkLocation.toString());
BogDan Vatra's avatar
BogDan Vatra committed
191
    settings.setValue(NDKToolchainVersionKey, ndkToolchainVersion);
Tobias Hunger's avatar
Tobias Hunger committed
192 193 194 195 196 197 198
    settings.setValue(AntLocationKey, antLocation.toString());
    settings.setValue(OpenJDKLocationKey, openJDKLocation.toString());
    settings.setValue(KeystoreLocationKey, keystoreLocation.toString());
    settings.setValue(ArmGdbLocationKey, armGdbLocation.toString());
    settings.setValue(ArmGdbserverLocationKey, armGdbserverLocation.toString());
    settings.setValue(X86GdbLocationKey, x86GdbLocation.toString());
    settings.setValue(X86GdbserverLocationKey, x86GdbserverLocation.toString());
BogDan Vatra's avatar
BogDan Vatra committed
199 200 201 202 203 204 205 206 207 208 209 210 211 212
    settings.setValue(PartitionSizeKey, partitionSize);
}

void AndroidConfigurations::setConfig(const AndroidConfig &devConfigs)
{
    m_config = devConfigs;
    save();
    updateAvailablePlatforms();
    emit updated();
}

void AndroidConfigurations::updateAvailablePlatforms()
{
    m_availablePlatforms.clear();
hjk's avatar
hjk committed
213
    FileName path = m_config.ndkLocation;
BogDan Vatra's avatar
BogDan Vatra committed
214
    QDirIterator it(path.appendPath(QLatin1String("platforms")).toString(), QStringList() << QLatin1String("android-*"), QDir::Dirs);
BogDan Vatra's avatar
BogDan Vatra committed
215 216 217 218 219 220 221 222 223 224 225
    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
226
    proc.start(androidToolPath().toString(), QStringList() << QLatin1String("list") << QLatin1String("target")); // list avaialbe AVDs
BogDan Vatra's avatar
BogDan Vatra committed
227 228 229 230
    if (!proc.waitForFinished(-1)) {
        proc.terminate();
        return targets;
    }
231
    while (proc.canReadLine()) {
232
        const QString line = QString::fromLocal8Bit(proc.readLine().trimmed());
BogDan Vatra's avatar
BogDan Vatra committed
233 234 235 236 237 238 239 240 241 242 243 244 245 246
        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;
}

QStringList AndroidConfigurations::ndkToolchainVersions() const
{
    QRegExp versionRegExp(NDKGccVersionRegExp);
    QStringList result;
hjk's avatar
hjk committed
247
    FileName path = m_config.ndkLocation;
Tobias Hunger's avatar
Tobias Hunger committed
248
    QDirIterator it(path.appendPath(QLatin1String("toolchains")).toString(),
BogDan Vatra's avatar
BogDan Vatra committed
249 250 251 252 253 254
                    QStringList() << QLatin1String("*"), QDir::Dirs);
    while (it.hasNext()) {
        const QString &fileName = it.next();
        int idx = versionRegExp.indexIn(fileName);
        if (idx == -1)
            continue;
255
        QString version = fileName.mid(idx+1);
BogDan Vatra's avatar
BogDan Vatra committed
256 257 258 259 260 261
        if (!result.contains(version))
            result.append(version);
    }
    return result;
}

hjk's avatar
hjk committed
262
FileName AndroidConfigurations::adbToolPath() const
BogDan Vatra's avatar
BogDan Vatra committed
263
{
hjk's avatar
hjk committed
264
    FileName path = m_config.sdkLocation;
265
    return path.appendPath(QLatin1String("platform-tools/adb" QTC_HOST_EXE_SUFFIX));
BogDan Vatra's avatar
BogDan Vatra committed
266 267
}

hjk's avatar
hjk committed
268
FileName AndroidConfigurations::androidToolPath() const
BogDan Vatra's avatar
BogDan Vatra committed
269
{
270 271 272 273
    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;
274
        path.appendPath(QLatin1String("tools/android" QTC_HOST_EXE_SUFFIX));
275 276 277
        if (path.toFileInfo().exists())
            return path;
        path = m_config.sdkLocation;
278
        return path.appendPath(QLatin1String("tools/android" ANDROID_BAT_SUFFIX));
279 280 281 282
    } else {
        FileName path = m_config.sdkLocation;
        return path.appendPath(QLatin1String("tools/android"));
    }
BogDan Vatra's avatar
BogDan Vatra committed
283 284
}

hjk's avatar
hjk committed
285
FileName AndroidConfigurations::antToolPath() const
BogDan Vatra's avatar
BogDan Vatra committed
286
{
Tobias Hunger's avatar
Tobias Hunger committed
287
    if (!m_config.antLocation.isEmpty())
BogDan Vatra's avatar
BogDan Vatra committed
288 289
        return m_config.antLocation;
    else
hjk's avatar
hjk committed
290
        return FileName::fromString(QLatin1String("ant"));
BogDan Vatra's avatar
BogDan Vatra committed
291 292
}

hjk's avatar
hjk committed
293
FileName AndroidConfigurations::emulatorToolPath() const
BogDan Vatra's avatar
BogDan Vatra committed
294
{
hjk's avatar
hjk committed
295
    FileName path = m_config.sdkLocation;
296
    return path.appendPath(QLatin1String("tools/emulator" QTC_HOST_EXE_SUFFIX));
BogDan Vatra's avatar
BogDan Vatra committed
297 298
}

hjk's avatar
hjk committed
299
FileName AndroidConfigurations::toolPath(Abi::Architecture architecture) const
BogDan Vatra's avatar
BogDan Vatra committed
300
{
hjk's avatar
hjk committed
301
    FileName path = m_config.ndkLocation;
Tobias Hunger's avatar
Tobias Hunger committed
302
    return path.appendPath(QString::fromLatin1("toolchains/%1-%2/prebuilt/%3/bin/%4")
BogDan Vatra's avatar
BogDan Vatra committed
303 304 305
            .arg(toolchainPrefix(architecture))
            .arg(m_config.ndkToolchainVersion)
            .arg(ToolchainHost)
Tobias Hunger's avatar
Tobias Hunger committed
306
            .arg(toolsPrefix(architecture)));
BogDan Vatra's avatar
BogDan Vatra committed
307 308
}

hjk's avatar
hjk committed
309
FileName AndroidConfigurations::stripPath(Abi::Architecture architecture) const
BogDan Vatra's avatar
BogDan Vatra committed
310
{
311
    return toolPath(architecture).append(QLatin1String("-strip" QTC_HOST_EXE_SUFFIX));
BogDan Vatra's avatar
BogDan Vatra committed
312 313
}

hjk's avatar
hjk committed
314
FileName AndroidConfigurations::readelfPath(Abi::Architecture architecture) const
BogDan Vatra's avatar
BogDan Vatra committed
315
{
316
    return toolPath(architecture).append(QLatin1String("-readelf" QTC_HOST_EXE_SUFFIX));
BogDan Vatra's avatar
BogDan Vatra committed
317 318
}

hjk's avatar
hjk committed
319
FileName AndroidConfigurations::gccPath(Abi::Architecture architecture) const
BogDan Vatra's avatar
BogDan Vatra committed
320
{
321
    return toolPath(architecture).append(QLatin1String("-gcc" QTC_HOST_EXE_SUFFIX));
BogDan Vatra's avatar
BogDan Vatra committed
322 323
}

hjk's avatar
hjk committed
324
FileName AndroidConfigurations::gdbServerPath(Abi::Architecture architecture) const
BogDan Vatra's avatar
BogDan Vatra committed
325
{
hjk's avatar
hjk committed
326
    FileName gdbServerPath;
BogDan Vatra's avatar
BogDan Vatra committed
327
    switch (architecture) {
hjk's avatar
hjk committed
328
    case Abi::ArmArchitecture:
BogDan Vatra's avatar
BogDan Vatra committed
329 330
        gdbServerPath = m_config.armGdbserverLocation;
        break;
hjk's avatar
hjk committed
331
    case Abi::X86Architecture:
BogDan Vatra's avatar
BogDan Vatra committed
332 333 334
        gdbServerPath = m_config.x86GdbserverLocation;
        break;
    default:
hjk's avatar
hjk committed
335
        gdbServerPath = FileName::fromString(Unknown);
BogDan Vatra's avatar
BogDan Vatra committed
336 337 338
        break;
    }

Tobias Hunger's avatar
Tobias Hunger committed
339
    if (!gdbServerPath.isEmpty())
BogDan Vatra's avatar
BogDan Vatra committed
340
        return gdbServerPath;
hjk's avatar
hjk committed
341
    FileName path = m_config.ndkLocation;
Tobias Hunger's avatar
Tobias Hunger committed
342 343 344
    return path.appendPath(QString::fromLatin1("toolchains/%1-%2/prebuilt/gdbserver")
                           .arg(toolchainPrefix(architecture))
                           .arg(m_config.ndkToolchainVersion));
BogDan Vatra's avatar
BogDan Vatra committed
345 346
}

hjk's avatar
hjk committed
347
FileName AndroidConfigurations::gdbPath(Abi::Architecture architecture) const
BogDan Vatra's avatar
BogDan Vatra committed
348
{
hjk's avatar
hjk committed
349
    FileName gdbPath;
BogDan Vatra's avatar
BogDan Vatra committed
350
    switch (architecture) {
hjk's avatar
hjk committed
351
    case Abi::ArmArchitecture:
BogDan Vatra's avatar
BogDan Vatra committed
352 353
        gdbPath = m_config.armGdbLocation;
        break;
hjk's avatar
hjk committed
354
    case Abi::X86Architecture:
BogDan Vatra's avatar
BogDan Vatra committed
355 356 357
        gdbPath = m_config.x86GdbLocation;
        break;
    default:
hjk's avatar
hjk committed
358
        gdbPath = FileName::fromString(Unknown);
BogDan Vatra's avatar
BogDan Vatra committed
359 360 361 362
        break;
    }
    if (!gdbPath.isEmpty())
        return gdbPath;
363
    return toolPath(architecture).append(QLatin1String("-gdb" QTC_HOST_EXE_SUFFIX));
BogDan Vatra's avatar
BogDan Vatra committed
364 365
}

hjk's avatar
hjk committed
366
FileName AndroidConfigurations::openJDKPath() const
BogDan Vatra's avatar
BogDan Vatra committed
367 368 369 370
{
    return m_config.openJDKLocation;
}

hjk's avatar
hjk committed
371
FileName AndroidConfigurations::openJDKBinPath() const
BogDan Vatra's avatar
BogDan Vatra committed
372
{
hjk's avatar
hjk committed
373
    FileName path = m_config.openJDKLocation;
Tobias Hunger's avatar
Tobias Hunger committed
374 375 376
    if (!path.isEmpty())
        return path.appendPath(QLatin1String("bin"));
    return path;
BogDan Vatra's avatar
BogDan Vatra committed
377 378
}

hjk's avatar
hjk committed
379
FileName AndroidConfigurations::keytoolPath() const
BogDan Vatra's avatar
BogDan Vatra committed
380
{
Tobias Hunger's avatar
Tobias Hunger committed
381
    return openJDKBinPath().appendPath(keytoolName);
BogDan Vatra's avatar
BogDan Vatra committed
382 383
}

hjk's avatar
hjk committed
384
FileName AndroidConfigurations::jarsignerPath() const
BogDan Vatra's avatar
BogDan Vatra committed
385
{
Tobias Hunger's avatar
Tobias Hunger committed
386
    return openJDKBinPath().appendPath(jarsignerName);
BogDan Vatra's avatar
BogDan Vatra committed
387 388
}

hjk's avatar
hjk committed
389
FileName AndroidConfigurations::zipalignPath() const
390 391
{
    Utils::FileName path = m_config.sdkLocation;
392
    return path.appendPath(QLatin1String("tools/zipalign" QTC_HOST_EXE_SUFFIX));
393 394
}

BogDan Vatra's avatar
BogDan Vatra committed
395 396
QString AndroidConfigurations::getDeployDeviceSerialNumber(int *apiLevel) const
{
397
    QVector<AndroidDeviceInfo> devices = connectedDevices();
BogDan Vatra's avatar
BogDan Vatra committed
398

399
    foreach (AndroidDeviceInfo device, devices) {
BogDan Vatra's avatar
BogDan Vatra committed
400 401 402 403
        if (device.sdk >= *apiLevel) {
            *apiLevel = device.sdk;
            return device.serialNumber;
        }
Tobias Hunger's avatar
Tobias Hunger committed
404
    }
BogDan Vatra's avatar
BogDan Vatra committed
405 406 407
    return startAVD(apiLevel);
}

408
QVector<AndroidDeviceInfo> AndroidConfigurations::connectedDevices(int apiLevel) const
BogDan Vatra's avatar
BogDan Vatra committed
409
{
410
    QVector<AndroidDeviceInfo> devices;
BogDan Vatra's avatar
BogDan Vatra committed
411
    QProcess adbProc;
Tobias Hunger's avatar
Tobias Hunger committed
412
    adbProc.start(adbToolPath().toString(), QStringList() << QLatin1String("devices"));
BogDan Vatra's avatar
BogDan Vatra committed
413
    if (!adbProc.waitForFinished(-1)) {
414
        adbProc.kill();
BogDan Vatra's avatar
BogDan Vatra committed
415 416 417 418
        return devices;
    }
    QList<QByteArray> adbDevs = adbProc.readAll().trimmed().split('\n');
    adbDevs.removeFirst();
419
    AndroidDeviceInfo dev;
BogDan Vatra's avatar
BogDan Vatra committed
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438
    foreach (const QByteArray &device, adbDevs) {
        dev.serialNumber = QString::fromLatin1(device.left(device.indexOf('\t')).trimmed());
        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
439
        QMessageBox::critical(0, tr("Error Creating AVD"),
440
                              tr("Cannot create a new AVD. No sufficiently recent Android SDK available.\n"
Friedemann Kleint's avatar
Friedemann Kleint committed
441
                                 "Please install an SDK of at least API version %1.").
Friedemann Kleint's avatar
Friedemann Kleint committed
442
                              arg(minApiLevel));
BogDan Vatra's avatar
BogDan Vatra committed
443 444 445 446 447 448 449 450 451 452 453 454 455 456
        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
457
    proc.start(androidToolPath().toString(),
BogDan Vatra's avatar
BogDan Vatra committed
458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474
               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
475
    proc.start(androidToolPath().toString(),
BogDan Vatra's avatar
BogDan Vatra committed
476 477 478 479 480 481 482 483 484
               QStringList() << QLatin1String("delete") << QLatin1String("avd")
               << QLatin1String("-n") << name);
    if (!proc.waitForFinished(-1)) {
        proc.terminate();
        return false;
    }
    return !proc.exitCode();
}

485
QVector<AndroidDeviceInfo> AndroidConfigurations::androidVirtualDevices() const
BogDan Vatra's avatar
BogDan Vatra committed
486
{
487
    QVector<AndroidDeviceInfo> devices;
BogDan Vatra's avatar
BogDan Vatra committed
488
    QProcess proc;
Tobias Hunger's avatar
Tobias Hunger committed
489
    proc.start(androidToolPath().toString(),
BogDan Vatra's avatar
BogDan Vatra committed
490 491 492 493 494 495 496
               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();
497
    AndroidDeviceInfo dev;
BogDan Vatra's avatar
BogDan Vatra committed
498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522
    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
523 524 525
    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
526 527

    QString avdName = name;
528
    QVector<AndroidDeviceInfo> devices;
BogDan Vatra's avatar
BogDan Vatra committed
529 530 531 532
    bool createAVDOnce = false;
    while (true) {
        if (avdName.isEmpty()) {
            devices = androidVirtualDevices();
Orgad Shaneh's avatar
Orgad Shaneh committed
533
            foreach (const AndroidDeviceInfo &device, devices)
BogDan Vatra's avatar
BogDan Vatra committed
534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553
                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
554
    avdProcess->start(emulatorToolPath().toString(),
BogDan Vatra's avatar
BogDan Vatra committed
555 556
                        QStringList() << QLatin1String("-partition-size") << QString::number(config().partitionSize)
                        << QLatin1String("-avd") << avdName);
Orgad Shaneh's avatar
Orgad Shaneh committed
557 558
    if (!avdProcess->waitForStarted(-1)) {
        delete avdProcess;
BogDan Vatra's avatar
BogDan Vatra committed
559 560 561 562 563
        return QString();
    }

    // wait until the emulator is online
    QProcess proc;
Tobias Hunger's avatar
Tobias Hunger committed
564
    proc.start(adbToolPath().toString(), QStringList() << QLatin1String("-e") << QLatin1String("wait-for-device"));
Orgad Shaneh's avatar
Orgad Shaneh committed
565 566
    while (!proc.waitForFinished(500)) {
        if (avdProcess->waitForFinished(0)) {
567
            proc.kill();
Orgad Shaneh's avatar
Orgad Shaneh committed
568 569 570
            proc.waitForFinished(-1);
            return QString();
        }
BogDan Vatra's avatar
BogDan Vatra committed
571 572 573 574
    }
    sleep(5);// wait for pm to start

    // workaround for stupid adb bug
Tobias Hunger's avatar
Tobias Hunger committed
575
    proc.start(adbToolPath().toString(), QStringList() << QLatin1String("devices"));
BogDan Vatra's avatar
BogDan Vatra committed
576
    if (!proc.waitForFinished(-1)) {
577
        proc.kill();
BogDan Vatra's avatar
BogDan Vatra committed
578 579 580 581 582
        return QString();
    }

    // get connected devices
    devices = connectedDevices(*apiLevel);
583
    foreach (AndroidDeviceInfo device, devices)
BogDan Vatra's avatar
BogDan Vatra committed
584 585 586 587 588 589 590 591 592 593
        if (device.sdk == *apiLevel)
            return device.serialNumber;
    // this should not happen, but ...
    return QString();
}

int AndroidConfigurations::getSDKVersion(const QString &device) const
{

    QProcess adbProc;
Tobias Hunger's avatar
Tobias Hunger committed
594
    adbProc.start(adbToolPath().toString(),
BogDan Vatra's avatar
BogDan Vatra committed
595 596 597 598
                  QStringList() << QLatin1String("-s") << device
                  << QLatin1String("shell") << QLatin1String("getprop")
                  << QLatin1String("ro.build.version.sdk"));
    if (!adbProc.waitForFinished(-1)) {
599
        adbProc.kill();
BogDan Vatra's avatar
BogDan Vatra committed
600 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 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648
        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");
}

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