androidconfigurations.cpp 23 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
    bool androidDevicesLessThan(const AndroidDeviceInfo &dev1, const AndroidDeviceInfo &dev2)
BogDan Vatra's avatar
BogDan Vatra committed
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
    {
        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
    if (!proc.waitForFinished(-1)) {
        proc.terminate();
        return targets;
    }
235
    while (proc.canReadLine()) {
236
        QString line = proc.readLine().trimmed();
BogDan Vatra's avatar
BogDan Vatra committed
237
238
239
240
241
242
243
244
245
246
247
248
249
250
        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
251
252
    Utils::FileName path = m_config.ndkLocation;
    QDirIterator it(path.appendPath(QLatin1String("toolchains")).toString(),
BogDan Vatra's avatar
BogDan Vatra committed
253
254
255
256
257
258
259
260
261
262
263
264
265
                    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
266
Utils::FileName AndroidConfigurations::adbToolPath() const
BogDan Vatra's avatar
BogDan Vatra committed
267
{
Tobias Hunger's avatar
Tobias Hunger committed
268
    Utils::FileName path = m_config.sdkLocation;
269
    return path.appendPath(QLatin1String("platform-tools/adb" ANDROID_EXE_SUFFIX));
BogDan Vatra's avatar
BogDan Vatra committed
270
271
}

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

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

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

Tobias Hunger's avatar
Tobias Hunger committed
303
Utils::FileName AndroidConfigurations::toolPath(ProjectExplorer::Abi::Architecture architecture) const
BogDan Vatra's avatar
BogDan Vatra committed
304
{
Tobias Hunger's avatar
Tobias Hunger committed
305
306
    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
307
308
309
            .arg(toolchainPrefix(architecture))
            .arg(m_config.ndkToolchainVersion)
            .arg(ToolchainHost)
Tobias Hunger's avatar
Tobias Hunger committed
310
            .arg(toolsPrefix(architecture)));
BogDan Vatra's avatar
BogDan Vatra committed
311
312
}

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

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

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

Tobias Hunger's avatar
Tobias Hunger committed
328
Utils::FileName AndroidConfigurations::gdbServerPath(ProjectExplorer::Abi::Architecture architecture) const
BogDan Vatra's avatar
BogDan Vatra committed
329
{
Tobias Hunger's avatar
Tobias Hunger committed
330
    Utils::FileName gdbServerPath;
BogDan Vatra's avatar
BogDan Vatra committed
331
332
333
334
335
336
337
338
    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
339
        gdbServerPath = Utils::FileName::fromString(Unknown);
BogDan Vatra's avatar
BogDan Vatra committed
340
341
342
        break;
    }

Tobias Hunger's avatar
Tobias Hunger committed
343
    if (!gdbServerPath.isEmpty())
BogDan Vatra's avatar
BogDan Vatra committed
344
        return gdbServerPath;
Tobias Hunger's avatar
Tobias Hunger committed
345
346
347
348
    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
349
350
}

Tobias Hunger's avatar
Tobias Hunger committed
351
Utils::FileName AndroidConfigurations::gdbPath(ProjectExplorer::Abi::Architecture architecture) const
BogDan Vatra's avatar
BogDan Vatra committed
352
{
Tobias Hunger's avatar
Tobias Hunger committed
353
    Utils::FileName gdbPath;
BogDan Vatra's avatar
BogDan Vatra committed
354
355
356
357
358
359
360
361
    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
362
        gdbPath = Utils::FileName::fromString(Unknown);
BogDan Vatra's avatar
BogDan Vatra committed
363
364
365
366
        break;
    }
    if (!gdbPath.isEmpty())
        return gdbPath;
367
    return toolPath(architecture).append(QLatin1String("-gdb" ANDROID_EXE_SUFFIX));
BogDan Vatra's avatar
BogDan Vatra committed
368
369
}

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

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

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

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

393
394
395
396
397
398
Utils::FileName AndroidConfigurations::zipalignPath() const
{
    Utils::FileName path = m_config.sdkLocation;
    return path.appendPath(QLatin1String("tools/zipalign" ANDROID_EXE_SUFFIX));
}

BogDan Vatra's avatar
BogDan Vatra committed
399
400
QString AndroidConfigurations::getDeployDeviceSerialNumber(int *apiLevel) const
{
401
    QVector<AndroidDeviceInfo> devices = connectedDevices();
BogDan Vatra's avatar
BogDan Vatra committed
402

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

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

489
QVector<AndroidDeviceInfo> AndroidConfigurations::androidVirtualDevices() const
BogDan Vatra's avatar
BogDan Vatra committed
490
{
491
    QVector<AndroidDeviceInfo> devices;
BogDan Vatra's avatar
BogDan Vatra committed
492
    QProcess proc;
Tobias Hunger's avatar
Tobias Hunger committed
493
    proc.start(androidToolPath().toString(),
BogDan Vatra's avatar
BogDan Vatra committed
494
495
496
497
498
499
500
               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();
501
    AndroidDeviceInfo dev;
BogDan Vatra's avatar
BogDan Vatra committed
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
    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;
532
    QVector<AndroidDeviceInfo> devices;
BogDan Vatra's avatar
BogDan Vatra committed
533
534
535
536
    bool createAVDOnce = false;
    while (true) {
        if (avdName.isEmpty()) {
            devices = androidVirtualDevices();
537
            foreach (AndroidDeviceInfo device, devices)
BogDan Vatra's avatar
BogDan Vatra committed
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
                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
558
    m_avdProcess->start(emulatorToolPath().toString(),
BogDan Vatra's avatar
BogDan Vatra committed
559
560
561
562
563
564
565
566
567
                        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
568
    proc.start(adbToolPath().toString(), QStringList() << QLatin1String("-e") << QLatin1String("wait-for-device"));
BogDan Vatra's avatar
BogDan Vatra committed
569
570
571
572
573
574
575
    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
576
    proc.start(adbToolPath().toString(), QStringList() << QLatin1String("devices"));
BogDan Vatra's avatar
BogDan Vatra committed
577
578
579
580
581
582
583
    if (!proc.waitForFinished(-1)) {
        proc.terminate();
        return QString();
    }

    // get connected devices
    devices = connectedDevices(*apiLevel);
584
    foreach (AndroidDeviceInfo device, devices)
BogDan Vatra's avatar
BogDan Vatra committed
585
586
587
588
589
590
591
592
593
594
        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
595
    adbProc.start(adbToolPath().toString(),
BogDan Vatra's avatar
BogDan Vatra committed
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
645
646
647
648
649
                  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