androidconfigurations.cpp 22.8 KB
Newer Older
BogDan Vatra's avatar
BogDan Vatra committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2012 BogDan Vatra <bog_dan_ro@yahoo.com>
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
**
** GNU Lesser General Public License Usage
**
** 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, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**************************************************************************/

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

#include <coreplugin/icore.h>
#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
55
56
#else
#include <unistd.h>
BogDan Vatra's avatar
BogDan Vatra committed
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#endif

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");
    const QLatin1String NDKGccVersionRegExp("\\d\\.\\d\\.\\d");
    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");

    static QString settingsFileName()
    {
89
90
        return QString::fromLatin1("%1/qtcreator/android.xml").arg(
            QFileInfo(Core::ICore::settings(QSettings::SystemScope)->fileName()).absolutePath());
BogDan Vatra's avatar
BogDan Vatra committed
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
    }

    bool androidDevicesLessThan(const AndroidDevice &dev1, const AndroidDevice &dev2)
    {
        return dev1.sdk < dev2.sdk;
    }
}

QLatin1String AndroidConfigurations::toolchainPrefix(ProjectExplorer::Abi::Architecture architecture)
{
    switch (architecture) {
    case ProjectExplorer::Abi::ArmArchitecture:
        return ArmToolchainPrefix;
    case ProjectExplorer::Abi::X86Architecture:
        return X86ToolchainPrefix;
    default:
        return Unknown;
    }
}


QLatin1String AndroidConfigurations::toolsPrefix(ProjectExplorer::Abi::Architecture architecture)
{
    switch (architecture) {
    case ProjectExplorer::Abi::ArmArchitecture:
        return ArmToolsPrefix;
    case ProjectExplorer::Abi::X86Architecture:
        return X86ToolsPrefix;
    default:
        return Unknown;
    }
}

AndroidConfig::AndroidConfig(const QSettings &settings)
{
    // user settings
Tobias Hunger's avatar
Tobias Hunger committed
127
128
129
130
    armGdbLocation = Utils::FileName::fromString(settings.value(ArmGdbLocationKey).toString());
    armGdbserverLocation = Utils::FileName::fromString(settings.value(ArmGdbserverLocationKey).toString());
    x86GdbLocation = Utils::FileName::fromString(settings.value(X86GdbLocationKey).toString());
    x86GdbserverLocation = Utils::FileName::fromString(settings.value(X86GdbserverLocationKey).toString());
BogDan Vatra's avatar
BogDan Vatra committed
131
    partitionSize = settings.value(PartitionSizeKey, 1024).toInt();
Tobias Hunger's avatar
Tobias Hunger committed
132
133
134
135
136
    sdkLocation = Utils::FileName::fromString(settings.value(SDKLocationKey).toString());
    ndkLocation = Utils::FileName::fromString(settings.value(NDKLocationKey).toString());
    antLocation = Utils::FileName::fromString(settings.value(AntLocationKey).toString());
    openJDKLocation = Utils::FileName::fromString(settings.value(OpenJDKLocationKey).toString());
    keystoreLocation = Utils::FileName::fromString(settings.value(KeystoreLocationKey).toString());
BogDan Vatra's avatar
BogDan Vatra committed
137
138
139
140
141
142
143
144
145
146
147
148
149

    QRegExp versionRegExp(NDKGccVersionRegExp);
    const QString &value = settings.value(NDKToolchainVersionKey).toString();
    if (versionRegExp.exactMatch(value))
        ndkToolchainVersion = value;
    else
        ndkToolchainVersion = value.mid(versionRegExp.indexIn(value));
    // user settings

    PersistentSettingsReader reader;
    if (reader.load(settingsFileName())
            && settings.value(changeTimeStamp).toInt() != QFileInfo(settingsFileName()).lastModified().toMSecsSinceEpoch() / 1000) {
        // persisten settings
Tobias Hunger's avatar
Tobias Hunger committed
150
151
152
153
154
        sdkLocation = Utils::FileName::fromString(reader.restoreValue(SDKLocationKey).toString());
        ndkLocation = Utils::FileName::fromString(reader.restoreValue(NDKLocationKey).toString());
        antLocation = Utils::FileName::fromString(reader.restoreValue(AntLocationKey).toString());
        openJDKLocation = Utils::FileName::fromString(reader.restoreValue(OpenJDKLocationKey).toString());
        keystoreLocation = Utils::FileName::fromString(reader.restoreValue(KeystoreLocationKey).toString());
BogDan Vatra's avatar
BogDan Vatra committed
155
156
157
158
159
160
161
162

        QRegExp versionRegExp(NDKGccVersionRegExp);
        const QString &value = reader.restoreValue(NDKToolchainVersionKey).toString();
        if (versionRegExp.exactMatch(value))
            ndkToolchainVersion = value;
        else
            ndkToolchainVersion = value.mid(versionRegExp.indexIn(value));

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

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

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

Tobias Hunger's avatar
Tobias Hunger committed
172
173
        if (x86GdbserverLocation.isEmpty())
            x86GdbserverLocation = Utils::FileName::fromString(reader.restoreValue(X86GdbserverLocationKey).toString());
BogDan Vatra's avatar
BogDan Vatra committed
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
        // persistent settings
    }

}

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

void AndroidConfig::save(QSettings &settings) const
{
    QFileInfo fileInfo(settingsFileName());
    if (fileInfo.exists())
        settings.setValue(changeTimeStamp, fileInfo.lastModified().toMSecsSinceEpoch() / 1000);

    // user settings
Tobias Hunger's avatar
Tobias Hunger committed
191
192
    settings.setValue(SDKLocationKey, sdkLocation.toString());
    settings.setValue(NDKLocationKey, ndkLocation.toString());
BogDan Vatra's avatar
BogDan Vatra committed
193
    settings.setValue(NDKToolchainVersionKey, ndkToolchainVersion);
Tobias Hunger's avatar
Tobias Hunger committed
194
195
196
197
198
199
200
    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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
    settings.setValue(PartitionSizeKey, partitionSize);
    // user settings

}

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

void AndroidConfigurations::updateAvailablePlatforms()
{
    m_availablePlatforms.clear();
BogDan Vatra's avatar
BogDan Vatra committed
217
218
    Utils::FileName path = m_config.ndkLocation;
    QDirIterator it(path.appendPath(QLatin1String("platforms")).toString(), QStringList() << QLatin1String("android-*"), QDir::Dirs);
BogDan Vatra's avatar
BogDan Vatra committed
219
220
221
222
223
224
225
226
227
228
229
    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
230
    proc.start(androidToolPath().toString(), QStringList() << QLatin1String("list") << QLatin1String("target")); // list avaialbe AVDs
BogDan Vatra's avatar
BogDan Vatra committed
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
    if (!proc.waitForFinished(-1)) {
        proc.terminate();
        return targets;
    }
    QList<QByteArray> avds = proc.readAll().trimmed().split('\n');
    for (int i = 0; i < avds.size(); i++) {
        QString line = QLatin1String(avds[i]);
        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;
Tobias Hunger's avatar
Tobias Hunger committed
252
253
    Utils::FileName path = m_config.ndkLocation;
    QDirIterator it(path.appendPath(QLatin1String("toolchains")).toString(),
BogDan Vatra's avatar
BogDan Vatra committed
254
255
256
257
258
259
260
261
262
263
264
265
266
                    QStringList() << QLatin1String("*"), QDir::Dirs);
    while (it.hasNext()) {
        const QString &fileName = it.next();
        int idx = versionRegExp.indexIn(fileName);
        if (idx == -1)
            continue;
        QString version = fileName.mid(idx);
        if (!result.contains(version))
            result.append(version);
    }
    return result;
}

Tobias Hunger's avatar
Tobias Hunger committed
267
Utils::FileName AndroidConfigurations::adbToolPath() const
BogDan Vatra's avatar
BogDan Vatra committed
268
{
Tobias Hunger's avatar
Tobias Hunger committed
269
270
    Utils::FileName path = m_config.sdkLocation;
    return path.appendPath(QLatin1String("platform-tools/adb"ANDROID_EXE_SUFFIX));
BogDan Vatra's avatar
BogDan Vatra committed
271
272
}

Tobias Hunger's avatar
Tobias Hunger committed
273
Utils::FileName AndroidConfigurations::androidToolPath() const
BogDan Vatra's avatar
BogDan Vatra committed
274
275
276
277
{
#ifdef Q_OS_WIN32
    // 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.
Tobias Hunger's avatar
Tobias Hunger committed
278
279
280
281
282
283
    Utils::FileName path = m_config.sdkLocation;
    path.appendPath(QLatin1String("tools/android"ANDROID_EXE_SUFFIX));
    if (path.toFileInfo().exists())
        return path;
    path = m_config.sdkLocation;
    return path.appendPath(QLatin1String("tools/android"ANDROID_BAT_SUFFIX));
BogDan Vatra's avatar
BogDan Vatra committed
284
#else
Tobias Hunger's avatar
Tobias Hunger committed
285
    Utils::FileName path = m_config.sdkLocation;
BogDan Vatra's avatar
BogDan Vatra committed
286
    return path.appendPath(QLatin1String("tools/android"));
BogDan Vatra's avatar
BogDan Vatra committed
287
288
289
#endif
}

Tobias Hunger's avatar
Tobias Hunger committed
290
Utils::FileName AndroidConfigurations::antToolPath() const
BogDan Vatra's avatar
BogDan Vatra committed
291
{
Tobias Hunger's avatar
Tobias Hunger committed
292
    if (!m_config.antLocation.isEmpty())
BogDan Vatra's avatar
BogDan Vatra committed
293
294
        return m_config.antLocation;
    else
Tobias Hunger's avatar
Tobias Hunger committed
295
        return Utils::FileName::fromString(QLatin1String("ant"));
BogDan Vatra's avatar
BogDan Vatra committed
296
297
}

Tobias Hunger's avatar
Tobias Hunger committed
298
Utils::FileName AndroidConfigurations::emulatorToolPath() const
BogDan Vatra's avatar
BogDan Vatra committed
299
{
Tobias Hunger's avatar
Tobias Hunger committed
300
301
    Utils::FileName path = m_config.sdkLocation;
    return path.appendPath(QLatin1String("tools/emulator"ANDROID_EXE_SUFFIX));
BogDan Vatra's avatar
BogDan Vatra committed
302
303
}

Tobias Hunger's avatar
Tobias Hunger committed
304
Utils::FileName AndroidConfigurations::toolPath(ProjectExplorer::Abi::Architecture architecture) const
BogDan Vatra's avatar
BogDan Vatra committed
305
{
Tobias Hunger's avatar
Tobias Hunger committed
306
307
    Utils::FileName path = m_config.ndkLocation;
    return path.appendPath(QString::fromLatin1("toolchains/%1-%2/prebuilt/%3/bin/%4")
BogDan Vatra's avatar
BogDan Vatra committed
308
309
310
            .arg(toolchainPrefix(architecture))
            .arg(m_config.ndkToolchainVersion)
            .arg(ToolchainHost)
Tobias Hunger's avatar
Tobias Hunger committed
311
            .arg(toolsPrefix(architecture)));
BogDan Vatra's avatar
BogDan Vatra committed
312
313
}

Tobias Hunger's avatar
Tobias Hunger committed
314
Utils::FileName AndroidConfigurations::stripPath(ProjectExplorer::Abi::Architecture architecture) const
BogDan Vatra's avatar
BogDan Vatra committed
315
{
Tobias Hunger's avatar
Tobias Hunger committed
316
    return toolPath(architecture).append(QLatin1String("-strip"ANDROID_EXE_SUFFIX));
BogDan Vatra's avatar
BogDan Vatra committed
317
318
}

Tobias Hunger's avatar
Tobias Hunger committed
319
Utils::FileName AndroidConfigurations::readelfPath(ProjectExplorer::Abi::Architecture architecture) const
BogDan Vatra's avatar
BogDan Vatra committed
320
{
Tobias Hunger's avatar
Tobias Hunger committed
321
    return toolPath(architecture).append(QLatin1String("-readelf"ANDROID_EXE_SUFFIX));
BogDan Vatra's avatar
BogDan Vatra committed
322
323
}

Tobias Hunger's avatar
Tobias Hunger committed
324
Utils::FileName AndroidConfigurations::gccPath(ProjectExplorer::Abi::Architecture architecture) const
BogDan Vatra's avatar
BogDan Vatra committed
325
{
Tobias Hunger's avatar
Tobias Hunger committed
326
    return toolPath(architecture).append(QLatin1String("-gcc"ANDROID_EXE_SUFFIX));
BogDan Vatra's avatar
BogDan Vatra committed
327
328
}

Tobias Hunger's avatar
Tobias Hunger committed
329
Utils::FileName AndroidConfigurations::gdbServerPath(ProjectExplorer::Abi::Architecture architecture) const
BogDan Vatra's avatar
BogDan Vatra committed
330
{
Tobias Hunger's avatar
Tobias Hunger committed
331
    Utils::FileName gdbServerPath;
BogDan Vatra's avatar
BogDan Vatra committed
332
333
334
335
336
337
338
339
    switch (architecture) {
    case ProjectExplorer::Abi::ArmArchitecture:
        gdbServerPath = m_config.armGdbserverLocation;
        break;
    case ProjectExplorer::Abi::X86Architecture:
        gdbServerPath = m_config.x86GdbserverLocation;
        break;
    default:
Tobias Hunger's avatar
Tobias Hunger committed
340
        gdbServerPath = Utils::FileName::fromString(Unknown);
BogDan Vatra's avatar
BogDan Vatra committed
341
342
343
        break;
    }

Tobias Hunger's avatar
Tobias Hunger committed
344
    if (!gdbServerPath.isEmpty())
BogDan Vatra's avatar
BogDan Vatra committed
345
        return gdbServerPath;
Tobias Hunger's avatar
Tobias Hunger committed
346
347
348
349
    Utils::FileName path = m_config.ndkLocation;
    return path.appendPath(QString::fromLatin1("toolchains/%1-%2/prebuilt/gdbserver")
                           .arg(toolchainPrefix(architecture))
                           .arg(m_config.ndkToolchainVersion));
BogDan Vatra's avatar
BogDan Vatra committed
350
351
}

Tobias Hunger's avatar
Tobias Hunger committed
352
Utils::FileName AndroidConfigurations::gdbPath(ProjectExplorer::Abi::Architecture architecture) const
BogDan Vatra's avatar
BogDan Vatra committed
353
{
Tobias Hunger's avatar
Tobias Hunger committed
354
    Utils::FileName gdbPath;
BogDan Vatra's avatar
BogDan Vatra committed
355
356
357
358
359
360
361
362
    switch (architecture) {
    case ProjectExplorer::Abi::ArmArchitecture:
        gdbPath = m_config.armGdbLocation;
        break;
    case ProjectExplorer::Abi::X86Architecture:
        gdbPath = m_config.x86GdbLocation;
        break;
    default:
Tobias Hunger's avatar
Tobias Hunger committed
363
        gdbPath = Utils::FileName::fromString(Unknown);
BogDan Vatra's avatar
BogDan Vatra committed
364
365
366
367
        break;
    }
    if (!gdbPath.isEmpty())
        return gdbPath;
Tobias Hunger's avatar
Tobias Hunger committed
368
    return toolPath(architecture).append(QLatin1String("-gdb"ANDROID_EXE_SUFFIX));
BogDan Vatra's avatar
BogDan Vatra committed
369
370
}

Tobias Hunger's avatar
Tobias Hunger committed
371
Utils::FileName AndroidConfigurations::openJDKPath() const
BogDan Vatra's avatar
BogDan Vatra committed
372
373
374
375
{
    return m_config.openJDKLocation;
}

Tobias Hunger's avatar
Tobias Hunger committed
376
Utils::FileName AndroidConfigurations::openJDKBinPath() const
BogDan Vatra's avatar
BogDan Vatra committed
377
{
Tobias Hunger's avatar
Tobias Hunger committed
378
379
380
381
    Utils::FileName path = m_config.openJDKLocation;
    if (!path.isEmpty())
        return path.appendPath(QLatin1String("bin"));
    return path;
BogDan Vatra's avatar
BogDan Vatra committed
382
383
}

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

Tobias Hunger's avatar
Tobias Hunger committed
389
Utils::FileName AndroidConfigurations::jarsignerPath() const
BogDan Vatra's avatar
BogDan Vatra committed
390
{
Tobias Hunger's avatar
Tobias Hunger committed
391
    return openJDKBinPath().appendPath(jarsignerName);
BogDan Vatra's avatar
BogDan Vatra committed
392
393
394
395
396
397
}

QString AndroidConfigurations::getDeployDeviceSerialNumber(int *apiLevel) const
{
    QVector<AndroidDevice> devices = connectedDevices();

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

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

QVector<AndroidDevice> AndroidConfigurations::androidVirtualDevices() const
{
    QVector<AndroidDevice> devices;
    QProcess proc;
Tobias Hunger's avatar
Tobias Hunger committed
488
    proc.start(androidToolPath().toString(),
BogDan Vatra's avatar
BogDan Vatra committed
489
490
491
492
493
494
495
496
497
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
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
               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();
    AndroidDevice dev;
    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
{
    QProcess *m_avdProcess = new QProcess();
    connect(this, SIGNAL(destroyed()), m_avdProcess, SLOT(deleteLater()));
    connect(m_avdProcess, SIGNAL(finished(int)), m_avdProcess, SLOT(deleteLater()));

    QString avdName = name;
    QVector<AndroidDevice> devices;
    bool createAVDOnce = false;
    while (true) {
        if (avdName.isEmpty()) {
            devices = androidVirtualDevices();
            foreach (AndroidDevice device, devices)
                if (device.sdk >= *apiLevel) { // take first emulator how supports this package
                    *apiLevel = device.sdk;
                    avdName = device.serialNumber;
                    break;
                }
        }
        // if no emulators found try to create one once
        if (avdName.isEmpty() && !createAVDOnce) {
            createAVDOnce = true;
            QMetaObject::invokeMethod(const_cast<QObject*>(static_cast<const QObject*>(this)), "createAVD", Qt::AutoConnection,
                                      Q_ARG(int, *apiLevel));
        } else {
            break;
        }
    }

    if (avdName.isEmpty())// stop here if no emulators found
        return avdName;

    // start the emulator
Tobias Hunger's avatar
Tobias Hunger committed
553
    m_avdProcess->start(emulatorToolPath().toString(),
BogDan Vatra's avatar
BogDan Vatra committed
554
555
556
557
558
559
560
561
562
                        QStringList() << QLatin1String("-partition-size") << QString::number(config().partitionSize)
                        << QLatin1String("-avd") << avdName);
    if (!m_avdProcess->waitForStarted(-1)) {
        delete m_avdProcess;
        return QString();
    }

    // wait until the emulator is online
    QProcess proc;
Tobias Hunger's avatar
Tobias Hunger committed
563
    proc.start(adbToolPath().toString(), QStringList() << QLatin1String("-e") << QLatin1String("wait-for-device"));
BogDan Vatra's avatar
BogDan Vatra committed
564
565
566
567
568
569
570
    if (!proc.waitForFinished(-1)) {
        proc.terminate();
        return QString();
    }
    sleep(5);// wait for pm to start

    // workaround for stupid adb bug
Tobias Hunger's avatar
Tobias Hunger committed
571
    proc.start(adbToolPath().toString(), QStringList() << QLatin1String("devices"));
BogDan Vatra's avatar
BogDan Vatra committed
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
    if (!proc.waitForFinished(-1)) {
        proc.terminate();
        return QString();
    }

    // get connected devices
    devices = connectedDevices(*apiLevel);
    foreach (AndroidDevice device, devices)
        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
590
    adbProc.start(adbToolPath().toString(),
BogDan Vatra's avatar
BogDan Vatra committed
591
592
593
594
595
596
597
598
599
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
                  QStringList() << QLatin1String("-s") << device
                  << QLatin1String("shell") << QLatin1String("getprop")
                  << QLatin1String("ro.build.version.sdk"));
    if (!adbProc.waitForFinished(-1)) {
        adbProc.terminate();
        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