1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 BogDan Vatra <bog_dan_ro@yahoo.com>
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "androidconfigurations.h"
27 #include "androidconstants.h"
28 #include "androidtoolchain.h"
29 #include "androiddevice.h"
30 #include "androidmanager.h"
31 #include "androidqtversion.h"
32 #include "androiddevicedialog.h"
33 #include "avddialog.h"
34 
35 #include <coreplugin/icore.h>
36 #include <coreplugin/messagemanager.h>
37 
38 #include <projectexplorer/devicesupport/devicemanager.h>
39 #include <projectexplorer/kitinformation.h>
40 #include <projectexplorer/kitmanager.h>
41 #include <projectexplorer/project.h>
42 #include <projectexplorer/projectexplorerconstants.h>
43 #include <projectexplorer/session.h>
44 #include <projectexplorer/toolchainmanager.h>
45 
46 #include <debugger/debuggeritemmanager.h>
47 #include <debugger/debuggeritem.h>
48 #include <debugger/debuggerkitinformation.h>
49 
50 #include <qtsupport/baseqtversion.h>
51 #include <qtsupport/qtkitinformation.h>
52 #include <qtsupport/qtversionmanager.h>
53 
54 #include <utils/algorithm.h>
55 #include <utils/environment.h>
56 #include <utils/environment.h>
57 #include <utils/hostosinfo.h>
58 #include <utils/persistentsettings.h>
59 #include <utils/qtcassert.h>
60 #include <utils/qtcprocess.h>
61 #include <utils/runextensions.h>
62 #include <utils/stringutils.h>
63 
64 #include <QApplication>
65 #include <QDirIterator>
66 #include <QFileInfo>
67 #include <QHostAddress>
68 #include <QJsonArray>
69 #include <QJsonDocument>
70 #include <QJsonObject>
71 #include <QLoggingCategory>
72 #include <QProcess>
73 #include <QRegularExpression>
74 #include <QSettings>
75 #include <QStandardPaths>
76 #include <QStringList>
77 #include <QTcpSocket>
78 #include <QThread>
79 
80 #include <functional>
81 #include <memory>
82 
83 using namespace QtSupport;
84 using namespace ProjectExplorer;
85 using namespace Utils;
86 
87 namespace {
88 static Q_LOGGING_CATEGORY(avdConfigLog, "qtc.android.androidconfig", QtWarningMsg)
89 }
90 
91 namespace Android {
92 using namespace Internal;
93 
94 const char JsonFilePath[] = "android/sdk_definitions.json";
95 const char SdkToolsUrlKey[] = "sdk_tools_url";
96 const char CommonKey[] = "common";
97 const char SdkEssentialPkgsKey[] = "sdk_essential_packages";
98 const char VersionsKey[] = "versions";
99 const char NdkPathKey[] = "ndk_path";
100 const char SpecificQtVersionsKey[] = "specific_qt_versions";
101 const char DefaultVersionKey[] = "default";
102 const char LinuxOsKey[] = "linux";
103 const char WindowsOsKey[] = "windows";
104 const char macOsKey[] = "mac";
105 
106 
107 namespace {
108     const char jdk8SettingsPath[] = "HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit";
109     const char jdkLatestSettingsPath[] = "HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\JDK\\";
110 
111     const QLatin1String SettingsGroup("AndroidConfigurations");
112     const QLatin1String SDKLocationKey("SDKLocation");
113     const QLatin1String CustomNdkLocationsKey("CustomNdkLocations");
114     const QLatin1String SdkFullyConfiguredKey("AllEssentialsInstalled");
115     const QLatin1String SDKManagerToolArgsKey("SDKManagerToolArgs");
116     const QLatin1String OpenJDKLocationKey("OpenJDKLocation");
117     const QLatin1String OpenSslPriLocationKey("OpenSSLPriLocation");
118     const QLatin1String AutomaticKitCreationKey("AutomatiKitCreation");
119     const QLatin1String EmulatorArgsKey("EmulatorArgs");
120 
121     const QLatin1String ArmToolchainPrefix("arm-linux-androideabi");
122     const QLatin1String X86ToolchainPrefix("x86");
123     const QLatin1String AArch64ToolchainPrefix("aarch64-linux-android");
124     const QLatin1String X86_64ToolchainPrefix("x86_64");
125 
126     const QLatin1String ArmToolsPrefix("arm-linux-androideabi");
127     const QLatin1String X86ToolsPrefix("i686-linux-android");
128     const QLatin1String AArch64ToolsPrefix("aarch64-linux-android");
129     const QLatin1String X86_64ToolsPrefix("x86_64-linux-android");
130 
131     const QLatin1String ArmToolsDisplayName("arm");
132     const QLatin1String X86ToolsDisplayName("i686");
133     const QLatin1String AArch64ToolsDisplayName("aarch64");
134     const QLatin1String X86_64ToolsDisplayName("x86_64");
135 
136     const QLatin1String Unknown("unknown");
137     const QLatin1String keytoolName("keytool");
138     const QLatin1String changeTimeStamp("ChangeTimeStamp");
139 
140     const QLatin1String sdkToolsVersionKey("Pkg.Revision");
141     const QLatin1String ndkRevisionKey("Pkg.Revision");
142 
sdkSettingsFileName()143     static QString sdkSettingsFileName()
144     {
145         return Core::ICore::installerResourcePath("android.xml").toString();
146     }
147 
is32BitUserSpace()148     static bool is32BitUserSpace()
149     {
150         // Do the exact same check as android's emulator is doing:
151         if (HostOsInfo::isLinuxHost()) {
152             if (QSysInfo::WordSize == 32 ) {
153                 Environment env = Environment::systemEnvironment();
154                 QString executable = env.searchInPath(QLatin1String("file")).toString();
155                 QString shell = env.value(QLatin1String("SHELL"));
156                 if (executable.isEmpty() || shell.isEmpty())
157                     return true; // we can't detect, but creator is 32bit so assume 32bit
158 
159                 QtcProcess proc;
160                 proc.setProcessChannelMode(QProcess::MergedChannels);
161                 proc.setTimeoutS(30);
162                 proc.setCommand({executable, {shell}});
163                 proc.runBlocking();
164                 if (proc.result() != QtcProcess::FinishedWithSuccess)
165                     return true;
166                 return !proc.allOutput().contains("x86-64");
167             }
168         }
169         return false;
170     }
171 }
172 
173 //////////////////////////////////
174 // AndroidConfig
175 //////////////////////////////////
176 
toolchainPrefix(const Abi & abi)177 QLatin1String AndroidConfig::toolchainPrefix(const Abi &abi)
178 {
179     switch (abi.architecture()) {
180     case Abi::ArmArchitecture:
181         if (abi.wordWidth() == 64)
182             return AArch64ToolchainPrefix;
183         return ArmToolchainPrefix;
184     case Abi::X86Architecture:
185         if (abi.wordWidth() == 64)
186             return X86_64ToolchainPrefix;
187         return X86ToolchainPrefix;
188     default:
189         return Unknown;
190     }
191 }
192 
toolsPrefix(const Abi & abi)193 QLatin1String AndroidConfig::toolsPrefix(const Abi &abi)
194 {
195     switch (abi.architecture()) {
196     case Abi::ArmArchitecture:
197         if (abi.wordWidth() == 64)
198             return AArch64ToolsPrefix;
199         return ArmToolsPrefix;
200     case Abi::X86Architecture:
201         if (abi.wordWidth() == 64)
202             return X86_64ToolsPrefix;
203         return X86ToolsPrefix;
204     default:
205         return Unknown;
206     }
207 }
208 
displayName(const Abi & abi)209 QLatin1String AndroidConfig::displayName(const Abi &abi)
210 {
211     switch (abi.architecture()) {
212     case Abi::ArmArchitecture:
213         if (abi.wordWidth() == 64)
214             return AArch64ToolsDisplayName;
215         return ArmToolsDisplayName;
216     case Abi::X86Architecture:
217         if (abi.wordWidth() == 64)
218             return X86_64ToolsDisplayName;
219         return X86ToolsDisplayName;
220     default:
221         return Unknown;
222     }
223 }
224 
load(const QSettings & settings)225 void AndroidConfig::load(const QSettings &settings)
226 {
227     // user settings
228     m_emulatorArgs = settings.value(EmulatorArgsKey,
229                          QStringList({"-netdelay", "none", "-netspeed", "full"})).toStringList();
230     m_sdkLocation = FilePath::fromUserInput(settings.value(SDKLocationKey).toString()).cleanPath();
231     m_customNdkList = settings.value(CustomNdkLocationsKey).toStringList();
232     m_sdkManagerToolArgs = settings.value(SDKManagerToolArgsKey).toStringList();
233     m_openJDKLocation = FilePath::fromString(settings.value(OpenJDKLocationKey).toString());
234     m_openSslLocation = FilePath::fromString(settings.value(OpenSslPriLocationKey).toString());
235     m_automaticKitCreation = settings.value(AutomaticKitCreationKey, true).toBool();
236     m_sdkFullyConfigured = settings.value(SdkFullyConfiguredKey, false).toBool();
237 
238     PersistentSettingsReader reader;
239     if (reader.load(FilePath::fromString(sdkSettingsFileName()))
240             && settings.value(changeTimeStamp).toInt() != QFileInfo(sdkSettingsFileName()).lastModified().toMSecsSinceEpoch() / 1000) {
241         // persisten settings
242         m_sdkLocation = FilePath::fromUserInput(reader.restoreValue(SDKLocationKey, m_sdkLocation.toString()).toString()).cleanPath();
243         m_customNdkList = reader.restoreValue(CustomNdkLocationsKey).toStringList();
244         m_sdkManagerToolArgs = reader.restoreValue(SDKManagerToolArgsKey, m_sdkManagerToolArgs).toStringList();
245         m_openJDKLocation = FilePath::fromString(reader.restoreValue(OpenJDKLocationKey, m_openJDKLocation.toString()).toString());
246         m_openSslLocation = FilePath::fromString(reader.restoreValue(OpenSslPriLocationKey, m_openSslLocation.toString()).toString());
247         m_automaticKitCreation = reader.restoreValue(AutomaticKitCreationKey, m_automaticKitCreation).toBool();
248         m_sdkFullyConfigured = reader.restoreValue(SdkFullyConfiguredKey, m_sdkFullyConfigured).toBool();
249         // persistent settings
250     }
251     m_customNdkList.removeAll("");
252     parseDependenciesJson();
253 }
254 
save(QSettings & settings) const255 void AndroidConfig::save(QSettings &settings) const
256 {
257     QFileInfo fileInfo(sdkSettingsFileName());
258     if (fileInfo.exists())
259         settings.setValue(changeTimeStamp, fileInfo.lastModified().toMSecsSinceEpoch() / 1000);
260 
261     // user settings
262     settings.setValue(SDKLocationKey, m_sdkLocation.toString());
263     settings.setValue(CustomNdkLocationsKey, m_customNdkList);
264     settings.setValue(SDKManagerToolArgsKey, m_sdkManagerToolArgs);
265     settings.setValue(OpenJDKLocationKey, m_openJDKLocation.toString());
266     settings.setValue(OpenSslPriLocationKey, m_openSslLocation.toString());
267     settings.setValue(EmulatorArgsKey, m_emulatorArgs);
268     settings.setValue(AutomaticKitCreationKey, m_automaticKitCreation);
269     settings.setValue(SdkFullyConfiguredKey, m_sdkFullyConfigured);
270 }
271 
parseDependenciesJson()272 void AndroidConfig::parseDependenciesJson()
273 {
274     const FilePath sdkConfigUserFile = Core::ICore::userResourcePath(JsonFilePath);
275     const FilePath sdkConfigFile = Core::ICore::resourcePath(JsonFilePath);
276 
277     if (!sdkConfigUserFile.exists()) {
278         QDir(sdkConfigUserFile.toFileInfo().absolutePath()).mkpath(".");
279         QFile::copy(sdkConfigFile.toString(), sdkConfigUserFile.toString());
280     }
281 
282     if (sdkConfigFile.lastModified() > sdkConfigUserFile.lastModified()) {
283         const QString oldUserFile = (sdkConfigUserFile + ".old").toString();
284         QFile::remove(oldUserFile);
285         QFile::rename(sdkConfigUserFile.toString(), oldUserFile);
286         QFile::copy(sdkConfigFile.toString(), sdkConfigUserFile.toString());
287     }
288 
289     QFile jsonFile(sdkConfigUserFile.toString());
290     if (!jsonFile.open(QIODevice::ReadOnly)) {
291         qCDebug(avdConfigLog, "Couldn't open JSON config file %s.", qPrintable(jsonFile.fileName()));
292         return;
293     }
294 
295     QJsonObject jsonObject = QJsonDocument::fromJson(jsonFile.readAll()).object();
296 
297     if (jsonObject.contains(CommonKey) && jsonObject[CommonKey].isObject()) {
298         QJsonObject commonObject = jsonObject[CommonKey].toObject();
299         // Parse SDK Tools URL
300         if (commonObject.contains(SdkToolsUrlKey) && commonObject[SdkToolsUrlKey].isObject()) {
301             QJsonObject sdkToolsObj(commonObject[SdkToolsUrlKey].toObject());
302             if (Utils::HostOsInfo::isMacHost()) {
303                 m_sdkToolsUrl = sdkToolsObj[macOsKey].toString();
304                 m_sdkToolsSha256 = QByteArray::fromHex(sdkToolsObj["mac_sha256"].toString().toUtf8());
305             } else if (Utils::HostOsInfo::isWindowsHost()) {
306                 m_sdkToolsUrl = sdkToolsObj[WindowsOsKey].toString();
307                 m_sdkToolsSha256 = QByteArray::fromHex(sdkToolsObj["windows_sha256"].toString().toUtf8());
308             } else {
309                 m_sdkToolsUrl = sdkToolsObj[LinuxOsKey].toString();
310                 m_sdkToolsSha256 = QByteArray::fromHex(sdkToolsObj["linux_sha256"].toString().toUtf8());
311             }
312         }
313 
314         // Parse common essential packages
315         auto appendEssentialsFromArray = [this](QJsonArray array) {
316             for (const QJsonValueRef &pkg : array)
317                 m_commonEssentialPkgs.append(pkg.toString());
318         };
319 
320         QJsonObject commonEssentials = commonObject[SdkEssentialPkgsKey].toObject();
321         appendEssentialsFromArray(commonEssentials[DefaultVersionKey].toArray());
322 
323         if (Utils::HostOsInfo::isWindowsHost())
324             appendEssentialsFromArray(commonEssentials[WindowsOsKey].toArray());
325         if (Utils::HostOsInfo::isMacHost())
326             appendEssentialsFromArray(commonEssentials[macOsKey].toArray());
327         else
328             appendEssentialsFromArray(commonEssentials[LinuxOsKey].toArray());
329     }
330 
331     auto fillQtVersionsRange = [](const QString &shortVersion) {
332         QList<QtVersionNumber> versions;
333         QRegularExpression re("([0-9]\\.[0-9]*\\.)\\[([0-9])\\-([0-9])\\]");
334         QRegularExpressionMatch match = re.match(shortVersion);
335         if (match.hasMatch() && match.lastCapturedIndex() == 3)
336             for (int i = match.captured(2).toInt(); i <= match.captured(3).toInt(); ++i)
337                 versions.append(QtVersionNumber(match.captured(1) + QString::number(i)));
338         else
339             versions.append(QtVersionNumber(shortVersion));
340 
341         return versions;
342     };
343 
344     if (jsonObject.contains(SpecificQtVersionsKey) && jsonObject[SpecificQtVersionsKey].isArray()) {
345         const QJsonArray versionsArray = jsonObject[SpecificQtVersionsKey].toArray();
346         for (const QJsonValue &item : versionsArray) {
347             QJsonObject itemObj = item.toObject();
348             SdkForQtVersions specificVersion;
349             specificVersion.ndkPath = itemObj[NdkPathKey].toString();
350             const auto pkgs = itemObj[SdkEssentialPkgsKey].toArray();
351             for (const QJsonValue &pkg : pkgs)
352                 specificVersion.essentialPackages.append(pkg.toString());
353             const auto versions = itemObj[VersionsKey].toArray();
354             for (const QJsonValue &pkg : versions)
355                 specificVersion.versions.append(fillQtVersionsRange(pkg.toString()));
356 
357             if (itemObj[VersionsKey].toArray().first().toString() == DefaultVersionKey)
358                 m_defaultSdkDepends = specificVersion;
359             else
360                 m_specificQtVersions.append(specificVersion);
361         }
362     }
363 }
364 
availableNdkPlatforms(const BaseQtVersion * qtVersion) const365 QVector<int> AndroidConfig::availableNdkPlatforms(const BaseQtVersion *qtVersion) const
366 {
367     QVector<int> availableNdkPlatforms;
368     QDirIterator it(ndkLocation(qtVersion).pathAppended("platforms").toString(),
369                     QStringList("android-*"),
370                     QDir::Dirs);
371     while (it.hasNext()) {
372         const QString &fileName = it.next();
373         availableNdkPlatforms.push_back(
374             fileName.mid(fileName.lastIndexOf(QLatin1Char('-')) + 1).toInt());
375     }
376     Utils::sort(availableNdkPlatforms, std::greater<>());
377 
378     return availableNdkPlatforms;
379 }
380 
getCustomNdkList() const381 QStringList AndroidConfig::getCustomNdkList() const
382 {
383     return m_customNdkList;
384 }
385 
addCustomNdk(const QString & customNdk)386 void AndroidConfig::addCustomNdk(const QString &customNdk)
387 {
388     if (!m_customNdkList.contains(customNdk))
389         m_customNdkList.append(customNdk);
390 }
391 
removeCustomNdk(const QString & customNdk)392 void AndroidConfig::removeCustomNdk(const QString &customNdk)
393 {
394     m_customNdkList.removeAll(customNdk);
395 }
396 
openSslLocation() const397 Utils::FilePath AndroidConfig::openSslLocation() const
398 {
399     return m_openSslLocation;
400 }
401 
setOpenSslLocation(const Utils::FilePath & openSslLocation)402 void AndroidConfig::setOpenSslLocation(const Utils::FilePath &openSslLocation)
403 {
404     m_openSslLocation = openSslLocation;
405 }
406 
apiLevelNamesFor(const SdkPlatformList & platforms)407 QStringList AndroidConfig::apiLevelNamesFor(const SdkPlatformList &platforms)
408 {
409     return Utils::transform(platforms, AndroidConfig::apiLevelNameFor);
410 }
411 
apiLevelNameFor(const SdkPlatform * platform)412 QString AndroidConfig::apiLevelNameFor(const SdkPlatform *platform)
413 {
414     return platform && platform->apiLevel() > 0 ?
415                 QString("android-%1").arg(platform->apiLevel()) : "";
416 }
417 
isCmdlineSdkToolsInstalled() const418 bool AndroidConfig::isCmdlineSdkToolsInstalled() const
419 {
420     QString toolPath("cmdline-tools/latest/bin/sdkmanager");
421     if (HostOsInfo::isWindowsHost())
422         toolPath += ANDROID_BAT_SUFFIX;
423 
424     return m_sdkLocation.pathAppended(toolPath).exists();
425 }
426 
adbToolPath() const427 FilePath AndroidConfig::adbToolPath() const
428 {
429     return m_sdkLocation / "platform-tools/adb" QTC_HOST_EXE_SUFFIX;
430 }
431 
emulatorToolPath() const432 FilePath AndroidConfig::emulatorToolPath() const
433 {
434     QString relativePath = "emulator/emulator";
435     if (sdkToolsVersion() < QVersionNumber(25, 3, 0) && !isCmdlineSdkToolsInstalled())
436         relativePath = "tools/emulator";
437     return m_sdkLocation / (relativePath + QTC_HOST_EXE_SUFFIX);
438 }
439 
sdkManagerToolPath() const440 FilePath AndroidConfig::sdkManagerToolPath() const
441 {
442     QStringList sdkmanagerPaths = {"cmdline-tools/latest/bin/sdkmanager",
443                                    "tools/bin/sdkmanager"};
444 
445     for (QString &toolPath : sdkmanagerPaths) {
446         if (HostOsInfo::isWindowsHost())
447             toolPath += ANDROID_BAT_SUFFIX;
448 
449         const FilePath sdkmanagerPath = m_sdkLocation / toolPath;
450         if (sdkmanagerPath.exists())
451             return sdkmanagerPath;
452     }
453 
454     return FilePath();
455 }
456 
avdManagerToolPath() const457 FilePath AndroidConfig::avdManagerToolPath() const
458 {
459     QStringList sdkmanagerPaths = {"cmdline-tools/latest/bin/avdmanager",
460                                    "tools/bin/avdmanager"};
461 
462     for (QString &toolPath : sdkmanagerPaths) {
463         if (HostOsInfo::isWindowsHost())
464             toolPath += ANDROID_BAT_SUFFIX;
465 
466         const FilePath sdkmanagerPath = m_sdkLocation / toolPath;
467         if (sdkmanagerPath.exists())
468             return sdkmanagerPath;
469     }
470 
471     return FilePath();
472 }
473 
toolchainPathFromNdk(const Utils::FilePath & ndkLocation) const474 FilePath AndroidConfig::toolchainPathFromNdk(const Utils::FilePath &ndkLocation) const
475 {
476     const FilePath tcPath = ndkLocation / "toolchains/";
477     FilePath toolchainPath;
478     QDirIterator llvmIter(tcPath.toString(), {"llvm*"}, QDir::Dirs);
479     if (llvmIter.hasNext()) {
480         llvmIter.next();
481         toolchainPath = tcPath / llvmIter.fileName() / "prebuilt/";
482     }
483 
484     // detect toolchain host
485     QStringList hostPatterns;
486     switch (HostOsInfo::hostOs()) {
487     case OsTypeLinux:
488         hostPatterns << QLatin1String("linux*");
489         break;
490     case OsTypeWindows:
491         hostPatterns << QLatin1String("windows*");
492         break;
493     case OsTypeMac:
494         hostPatterns << QLatin1String("darwin*");
495         break;
496     default: /* unknown host */ return FilePath();
497     }
498 
499     QDirIterator iter(toolchainPath.toString(), hostPatterns, QDir::Dirs);
500     if (iter.hasNext()) {
501         iter.next();
502         return toolchainPath / iter.fileName();
503     }
504 
505     return {};
506 }
507 
toolchainPath(const BaseQtVersion * qtVersion) const508 FilePath AndroidConfig::toolchainPath(const BaseQtVersion *qtVersion) const
509 {
510     return toolchainPathFromNdk(ndkLocation(qtVersion));
511 }
512 
clangPathFromNdk(const Utils::FilePath & ndkLocation) const513 FilePath AndroidConfig::clangPathFromNdk(const Utils::FilePath &ndkLocation) const
514 {
515     const FilePath path = toolchainPathFromNdk(ndkLocation);
516     if (path.isEmpty())
517         return {};
518     return path / HostOsInfo::withExecutableSuffix("bin/clang");
519 }
520 
gdbPath(const ProjectExplorer::Abi & abi,const BaseQtVersion * qtVersion) const521 FilePath AndroidConfig::gdbPath(const ProjectExplorer::Abi &abi, const BaseQtVersion *qtVersion) const
522 {
523     return gdbPathFromNdk(abi, ndkLocation(qtVersion));
524 }
525 
gdbPathFromNdk(const Abi & abi,const FilePath & ndkLocation) const526 FilePath AndroidConfig::gdbPathFromNdk(const Abi &abi, const FilePath &ndkLocation) const
527 {
528     const FilePath path = ndkLocation.pathAppended(
529         QString("prebuilt/%1/bin/gdb%2").arg(toolchainHostFromNdk(ndkLocation),
530                                              QString(QTC_HOST_EXE_SUFFIX)));
531     if (path.exists())
532         return path;
533     // fallback for old NDKs (e.g. 10e)
534     return ndkLocation.pathAppended(QString("toolchains/%1-4.9/prebuilt/%2/bin/%3-gdb%4")
535                                                    .arg(toolchainPrefix(abi),
536                                                         toolchainHostFromNdk(ndkLocation),
537                                                         toolsPrefix(abi),
538                                                         QString(QTC_HOST_EXE_SUFFIX)));
539 }
540 
makePathFromNdk(const FilePath & ndkLocation) const541 FilePath AndroidConfig::makePathFromNdk(const FilePath &ndkLocation) const
542 {
543     return ndkLocation.pathAppended(
544                 QString("prebuilt/%1/bin/make%2").arg(toolchainHostFromNdk(ndkLocation),
545                                                       QString(QTC_HOST_EXE_SUFFIX)));
546 }
547 
openJDKBinPath() const548 FilePath AndroidConfig::openJDKBinPath() const
549 {
550     const FilePath path = m_openJDKLocation;
551     if (!path.isEmpty())
552         return path.pathAppended("bin");
553     return path;
554 }
555 
keytoolPath() const556 FilePath AndroidConfig::keytoolPath() const
557 {
558     return openJDKBinPath().pathAppended(keytoolName);
559 }
560 
connectedDevices(QString * error) const561 QVector<AndroidDeviceInfo> AndroidConfig::connectedDevices(QString *error) const
562 {
563     return connectedDevices(adbToolPath(), error);
564 }
565 
connectedDevices(const FilePath & adbToolPath,QString * error)566 QVector<AndroidDeviceInfo> AndroidConfig::connectedDevices(const FilePath &adbToolPath, QString *error)
567 {
568     QVector<AndroidDeviceInfo> devices;
569     QtcProcess adbProc;
570     adbProc.setTimeoutS(30);
571     CommandLine cmd{adbToolPath, {"devices"}};
572     adbProc.setCommand(cmd);
573     adbProc.runBlocking();
574     if (adbProc.result() != QtcProcess::FinishedWithSuccess) {
575         if (error)
576             *error = QApplication::translate("AndroidConfiguration", "Could not run: %1")
577                 .arg(cmd.toUserOutput());
578         return devices;
579     }
580     QStringList adbDevs = adbProc.allOutput().split('\n', Qt::SkipEmptyParts);
581     if (adbDevs.empty())
582         return devices;
583 
584     for (const QString &line : adbDevs) // remove the daemon logs
585         if (line.startsWith("* daemon"))
586             adbDevs.removeOne(line);
587     adbDevs.removeFirst(); // remove "List of devices attached" header line
588 
589     // workaround for '????????????' serial numbers:
590     // can use "adb -d" when only one usb device attached
591     foreach (const QString &device, adbDevs) {
592         const QString serialNo = device.left(device.indexOf('\t')).trimmed();
593         const QString deviceType = device.mid(device.indexOf('\t')).trimmed();
594         AndroidDeviceInfo dev;
595         dev.serialNumber = serialNo;
596         dev.type = serialNo.startsWith(QLatin1String("emulator")) ? AndroidDeviceInfo::Emulator : AndroidDeviceInfo::Hardware;
597         dev.sdk = getSDKVersion(adbToolPath, dev.serialNumber);
598         dev.cpuAbi = getAbis(adbToolPath, dev.serialNumber);
599         if (deviceType == QLatin1String("unauthorized"))
600             dev.state = AndroidDeviceInfo::UnAuthorizedState;
601         else if (deviceType == QLatin1String("offline"))
602             dev.state = AndroidDeviceInfo::OfflineState;
603         else
604             dev.state = AndroidDeviceInfo::OkState;
605 
606         if (dev.type == AndroidDeviceInfo::Emulator) {
607             dev.avdname = getAvdName(dev.serialNumber);
608             if (dev.avdname.isEmpty())
609                 dev.avdname = serialNo;
610         }
611 
612         devices.push_back(dev);
613     }
614 
615     Utils::sort(devices);
616     if (devices.isEmpty() && error)
617         *error = QApplication::translate("AndroidConfiguration",
618                                          "No devices found in output of: %1")
619             .arg(cmd.toUserOutput());
620     return devices;
621 }
622 
isConnected(const QString & serialNumber) const623 bool AndroidConfig::isConnected(const QString &serialNumber) const
624 {
625     QVector<AndroidDeviceInfo> devices = connectedDevices();
626     foreach (AndroidDeviceInfo device, devices) {
627         if (device.serialNumber == serialNumber)
628             return true;
629     }
630     return false;
631 }
632 
getDeviceProperty(const FilePath & adbToolPath,const QString & device,const QString & property)633 QString AndroidConfig::getDeviceProperty(const FilePath &adbToolPath, const QString &device, const QString &property)
634 {
635     // workaround for '????????????' serial numbers
636     CommandLine cmd(adbToolPath, AndroidDeviceInfo::adbSelector(device));
637     cmd.addArgs({"shell", "getprop", property});
638 
639     QtcProcess adbProc;
640     adbProc.setTimeoutS(10);
641     adbProc.setCommand(cmd);
642     adbProc.runBlocking();
643     if (adbProc.result() != QtcProcess::FinishedWithSuccess)
644         return QString();
645 
646     return adbProc.allOutput();
647 }
648 
getSDKVersion(const FilePath & adbToolPath,const QString & device)649 int AndroidConfig::getSDKVersion(const FilePath &adbToolPath, const QString &device)
650 {
651     QString tmp = getDeviceProperty(adbToolPath, device, "ro.build.version.sdk");
652     if (tmp.isEmpty())
653         return -1;
654     return tmp.trimmed().toInt();
655 }
656 
getAvdName(const QString & serialnumber)657 QString AndroidConfig::getAvdName(const QString &serialnumber)
658 {
659     int index = serialnumber.indexOf(QLatin1String("-"));
660     if (index == -1)
661         return QString();
662     bool ok;
663     int port = serialnumber.mid(index + 1).toInt(&ok);
664     if (!ok)
665         return QString();
666 
667     const QByteArray avdName = "avd name\n";
668 
669     QTcpSocket tcpSocket;
670     tcpSocket.connectToHost(QHostAddress(QHostAddress::LocalHost), port);
671     if (!tcpSocket.waitForConnected(100)) // Don't wait more than 100ms for a local connection
672         return QString{};
673 
674     tcpSocket.write(avdName + "exit\n");
675     tcpSocket.waitForDisconnected(500);
676 
677     QByteArray name;
678     const QByteArrayList response = tcpSocket.readAll().split('\n');
679     // The input "avd name" might not be echoed as-is, but contain ASCII
680     // control sequences.
681     for (int i = response.size() - 1; i > 1; --i) {
682         if (response.at(i).startsWith("OK")) {
683             name = response.at(i - 1);
684             break;
685         }
686     }
687     return QString::fromLatin1(name).trimmed();
688 }
689 
getOpenGLEnabled(const QString & emulator) const690 AndroidConfig::OpenGl AndroidConfig::getOpenGLEnabled(const QString &emulator) const
691 {
692     QDir dir = QDir::home();
693     if (!dir.cd(QLatin1String(".android")))
694         return OpenGl::Unknown;
695     if (!dir.cd(QLatin1String("avd")))
696         return OpenGl::Unknown;
697     if (!dir.cd(emulator + QLatin1String(".avd")))
698         return OpenGl::Unknown;
699     QFile file(dir.filePath(QLatin1String("config.ini")));
700     if (!file.exists())
701         return OpenGl::Unknown;
702     if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
703         return OpenGl::Unknown;
704     while (!file.atEnd()) {
705         QByteArray line = file.readLine();
706         if (line.contains("hw.gpu.enabled") && line.contains("yes"))
707             return OpenGl::Enabled;
708     }
709     return OpenGl::Disabled;
710 }
711 
712 //!
713 //! \brief AndroidConfigurations::getProductModel
714 //! \param device serial number
715 //! \return the produce model of the device or if that cannot be read the serial number
716 //!
getProductModel(const QString & device) const717 QString AndroidConfig::getProductModel(const QString &device) const
718 {
719     if (m_serialNumberToDeviceName.contains(device))
720         return m_serialNumberToDeviceName.value(device);
721 
722     QString model = getDeviceProperty(adbToolPath(), device, "ro.product.model").trimmed();
723     if (model.isEmpty())
724         return device;
725 
726     if (!device.startsWith(QLatin1String("????")))
727         m_serialNumberToDeviceName.insert(device, model);
728     return model;
729 }
730 
getAbis(const FilePath & adbToolPath,const QString & device)731 QStringList AndroidConfig::getAbis(const FilePath &adbToolPath, const QString &device)
732 {
733     QStringList result;
734     // First try via ro.product.cpu.abilist
735     QStringList arguments = AndroidDeviceInfo::adbSelector(device);
736     arguments << "shell" << "getprop" << "ro.product.cpu.abilist";
737     QtcProcess adbProc;
738     adbProc.setTimeoutS(10);
739     adbProc.setCommand({adbToolPath, arguments});
740     adbProc.runBlocking();
741     if (adbProc.result() != QtcProcess::FinishedWithSuccess)
742         return result;
743 
744     QString output = adbProc.allOutput().trimmed();
745     if (!output.isEmpty()) {
746         QStringList result = output.split(QLatin1Char(','));
747         if (!result.isEmpty())
748             return result;
749     }
750 
751     // Fall back to ro.product.cpu.abi, ro.product.cpu.abi2 ...
752     for (int i = 1; i < 6; ++i) {
753         QStringList arguments = AndroidDeviceInfo::adbSelector(device);
754         arguments << QLatin1String("shell") << QLatin1String("getprop");
755         if (i == 1)
756             arguments << QLatin1String("ro.product.cpu.abi");
757         else
758             arguments << QString::fromLatin1("ro.product.cpu.abi%1").arg(i);
759 
760         QtcProcess abiProc;
761         abiProc.setTimeoutS(10);
762         abiProc.setCommand({adbToolPath, arguments});
763         abiProc.runBlocking();
764         if (abiProc.result() != QtcProcess::FinishedWithSuccess)
765             return result;
766 
767         QString abi = abiProc.allOutput().trimmed();
768         if (abi.isEmpty())
769             break;
770         result << abi;
771     }
772     return result;
773 }
774 
isValidNdk(const QString & ndkLocation) const775 bool AndroidConfig::isValidNdk(const QString &ndkLocation) const
776 {
777     auto ndkPath = Utils::FilePath::fromUserInput(ndkLocation);
778     const Utils::FilePath ndkPlatformsDir = ndkPath.pathAppended("platforms");
779 
780     return ndkPath.exists() && ndkPath.pathAppended("toolchains").exists()
781            && ndkPlatformsDir.exists() && !ndkPlatformsDir.toString().contains(' ')
782            && !ndkVersion(ndkPath).isNull();
783 }
784 
bestNdkPlatformMatch(int target,const BaseQtVersion * qtVersion) const785 QString AndroidConfig::bestNdkPlatformMatch(int target, const BaseQtVersion *qtVersion) const
786 {
787     target = std::max(AndroidManager::defaultMinimumSDK(qtVersion), target);
788     foreach (int apiLevel, availableNdkPlatforms(qtVersion)) {
789         if (apiLevel <= target)
790             return QString::fromLatin1("android-%1").arg(apiLevel);
791     }
792     return QString("android-%1").arg(AndroidManager::defaultMinimumSDK(qtVersion));
793 }
794 
sdkLocation() const795 FilePath AndroidConfig::sdkLocation() const
796 {
797     return m_sdkLocation;
798 }
799 
setSdkLocation(const FilePath & sdkLocation)800 void AndroidConfig::setSdkLocation(const FilePath &sdkLocation)
801 {
802     m_sdkLocation = sdkLocation;
803 }
804 
sdkToolsVersion() const805 QVersionNumber AndroidConfig::sdkToolsVersion() const
806 {
807     QVersionNumber version;
808     if (m_sdkLocation.exists()) {
809         FilePath sdkToolsPropertiesPath;
810         if (isCmdlineSdkToolsInstalled())
811             sdkToolsPropertiesPath = m_sdkLocation / "cmdline-tools/latest/source.properties";
812         else
813             sdkToolsPropertiesPath = m_sdkLocation / "tools/source.properties";
814         QSettings settings(sdkToolsPropertiesPath.toString(), QSettings::IniFormat);
815         auto versionStr = settings.value(sdkToolsVersionKey).toString();
816         version = QVersionNumber::fromString(versionStr);
817     }
818     return version;
819 }
820 
buildToolsVersion() const821 QVersionNumber AndroidConfig::buildToolsVersion() const
822 {
823     //TODO: return version according to qt version
824     QVersionNumber maxVersion;
825     QDir buildToolsDir(m_sdkLocation.pathAppended("build-tools").toString());
826     const auto files = buildToolsDir.entryInfoList(QDir::Dirs|QDir::NoDotAndDotDot);
827     for (const QFileInfo &file: files)
828         maxVersion = qMax(maxVersion, QVersionNumber::fromString(file.fileName()));
829     return maxVersion;
830 }
831 
sdkManagerToolArgs() const832 QStringList AndroidConfig::sdkManagerToolArgs() const
833 {
834     return m_sdkManagerToolArgs;
835 }
836 
setSdkManagerToolArgs(const QStringList & args)837 void AndroidConfig::setSdkManagerToolArgs(const QStringList &args)
838 {
839     m_sdkManagerToolArgs = args;
840 }
841 
ndkLocation(const BaseQtVersion * qtVersion) const842 FilePath AndroidConfig::ndkLocation(const BaseQtVersion *qtVersion) const
843 {
844     return sdkLocation().pathAppended(ndkPathFromQtVersion(*qtVersion));
845 }
846 
ndkVersion(const BaseQtVersion * qtVersion) const847 QVersionNumber AndroidConfig::ndkVersion(const BaseQtVersion *qtVersion) const
848 {
849     return ndkVersion(ndkLocation(qtVersion));
850 }
851 
ndkVersion(const FilePath & ndkPath) const852 QVersionNumber AndroidConfig::ndkVersion(const FilePath &ndkPath) const
853 {
854     QVersionNumber version;
855     if (!ndkPath.exists()) {
856         qCDebug(avdConfigLog) << "Cannot find ndk version. Check NDK path."
857                               << ndkPath.toString();
858         return version;
859     }
860 
861     const FilePath ndkPropertiesPath = ndkPath.pathAppended("source.properties");
862     if (ndkPropertiesPath.exists()) {
863         // source.properties files exists in NDK version > 11
864         QSettings settings(ndkPropertiesPath.toString(), QSettings::IniFormat);
865         auto versionStr = settings.value(ndkRevisionKey).toString();
866         version = QVersionNumber::fromString(versionStr);
867     } else {
868         // No source.properties. There should be a file named RELEASE.TXT
869         const FilePath ndkReleaseTxtPath = ndkPath.pathAppended("RELEASE.TXT");
870         Utils::FileReader reader;
871         QString errorString;
872         if (reader.fetch(ndkReleaseTxtPath, &errorString)) {
873             // RELEASE.TXT contains the ndk version in either of the following formats:
874             // r6a
875             // r10e (64 bit)
876             QString content = QString::fromUtf8(reader.data());
877             QRegularExpression re("(r)(?<major>[0-9]{1,2})(?<minor>[a-z]{1,1})");
878             QRegularExpressionMatch match = re.match(content);
879             if (match.hasMatch()) {
880                 QString major = match.captured("major");
881                 QString minor = match.captured("minor");
882                 // Minor version: a = 0, b = 1, c = 2 and so on.
883                 // Int equivalent = minorVersionChar - 'a'. i.e. minorVersionChar - 97.
884                 version = QVersionNumber::fromString(QString("%1.%2.0").arg(major)
885                                                      .arg((int)minor[0].toLatin1() - 97));
886             } else {
887                 qCDebug(avdConfigLog) << "Cannot find ndk version. Cannot parse RELEASE.TXT."
888                                       << content;
889             }
890         } else {
891             qCDebug(avdConfigLog) << "Cannot find ndk version." << errorString;
892         }
893     }
894     return version;
895 }
896 
allEssentials() const897 QStringList AndroidConfig::allEssentials() const
898 {
899     QList<BaseQtVersion *> installedVersions = QtVersionManager::versions(
900         [](const BaseQtVersion *v) {
901             return v->targetDeviceTypes().contains(Android::Constants::ANDROID_DEVICE_TYPE);
902         });
903 
904     QStringList allPackages(defaultEssentials());
905     for (const BaseQtVersion *version : installedVersions)
906         allPackages.append(essentialsFromQtVersion(*version));
907     allPackages.removeDuplicates();
908 
909     return allPackages;
910 }
911 
allEssentialsInstalled(AndroidSdkManager * sdkManager)912 bool AndroidConfig::allEssentialsInstalled(AndroidSdkManager *sdkManager)
913 {
914     QStringList essentialPkgs(allEssentials());
915     const auto installedPkgs = sdkManager->installedSdkPackages();
916     for (const AndroidSdkPackage *pkg : installedPkgs) {
917         if (essentialPkgs.contains(pkg->sdkStylePath()))
918             essentialPkgs.removeOne(pkg->sdkStylePath());
919         if (essentialPkgs.isEmpty())
920             break;
921     }
922     return essentialPkgs.isEmpty() ? true : false;
923 }
924 
sdkToolsOk() const925 bool AndroidConfig::sdkToolsOk() const
926 {
927     bool exists = sdkLocation().exists();
928     bool writable = sdkLocation().isWritablePath();
929     bool sdkToolsExist = !sdkToolsVersion().isNull();
930     return exists && writable && sdkToolsExist;
931 }
932 
essentialsFromQtVersion(const BaseQtVersion & version) const933 QStringList AndroidConfig::essentialsFromQtVersion(const BaseQtVersion &version) const
934 {
935     QtVersionNumber qtVersion = version.qtVersion();
936     for (const SdkForQtVersions &item : m_specificQtVersions)
937         if (item.containsVersion(qtVersion))
938             return item.essentialPackages;
939 
940     return m_defaultSdkDepends.essentialPackages;
941 }
942 
ndkPathFromQtVersion(const BaseQtVersion & version) const943 QString AndroidConfig::ndkPathFromQtVersion(const BaseQtVersion &version) const
944 {
945     QtVersionNumber qtVersion(version.qtVersionString());
946     for (const SdkForQtVersions &item : m_specificQtVersions)
947         if (item.containsVersion(qtVersion))
948             return item.ndkPath;
949 
950     return m_defaultSdkDepends.ndkPath;
951 }
952 
defaultEssentials() const953 QStringList AndroidConfig::defaultEssentials() const
954 {
955     return m_defaultSdkDepends.essentialPackages + m_commonEssentialPkgs;
956 }
957 
containsVersion(const QtVersionNumber & qtVersion) const958 bool SdkForQtVersions::containsVersion(const QtVersionNumber &qtVersion) const
959 {
960     return versions.contains(qtVersion)
961            || versions.contains(QtVersionNumber(qtVersion.majorVersion, qtVersion.minorVersion));
962 }
963 
openJDKLocation() const964 FilePath AndroidConfig::openJDKLocation() const
965 {
966     return m_openJDKLocation;
967 }
968 
setOpenJDKLocation(const FilePath & openJDKLocation)969 void AndroidConfig::setOpenJDKLocation(const FilePath &openJDKLocation)
970 {
971     m_openJDKLocation = openJDKLocation;
972 }
973 
toolchainHost(const BaseQtVersion * qtVersion) const974 QString AndroidConfig::toolchainHost(const BaseQtVersion *qtVersion) const
975 {
976     return toolchainHostFromNdk(ndkLocation(qtVersion));
977 }
978 
toolchainHostFromNdk(const FilePath & ndkPath) const979 QString AndroidConfig::toolchainHostFromNdk(const FilePath &ndkPath) const
980 {
981     // detect toolchain host
982     QString toolchainHost;
983     QStringList hostPatterns;
984     switch (HostOsInfo::hostOs()) {
985     case OsTypeLinux:
986         hostPatterns << QLatin1String("linux*");
987         break;
988     case OsTypeWindows:
989         hostPatterns << QLatin1String("windows*");
990         break;
991     case OsTypeMac:
992         hostPatterns << QLatin1String("darwin*");
993         break;
994     default: /* unknown host */
995         return toolchainHost;
996     }
997 
998     QDirIterator jt(ndkPath.pathAppended("prebuilt").toString(),
999                     hostPatterns,
1000                     QDir::Dirs);
1001     if (jt.hasNext()) {
1002         jt.next();
1003         toolchainHost = jt.fileName();
1004     }
1005 
1006     return toolchainHost;
1007 }
1008 
emulatorArgs() const1009 QStringList AndroidConfig::emulatorArgs() const
1010 {
1011     return m_emulatorArgs;
1012 }
1013 
setEmulatorArgs(const QStringList & args)1014 void AndroidConfig::setEmulatorArgs(const QStringList &args)
1015 {
1016     m_emulatorArgs = args;
1017 }
1018 
automaticKitCreation() const1019 bool AndroidConfig::automaticKitCreation() const
1020 {
1021     return m_automaticKitCreation;
1022 }
1023 
setAutomaticKitCreation(bool b)1024 void AndroidConfig::setAutomaticKitCreation(bool b)
1025 {
1026     m_automaticKitCreation = b;
1027 }
1028 
defaultSdkPath()1029 FilePath AndroidConfig::defaultSdkPath()
1030 {
1031     QString sdkFromEnvVar = QString::fromLocal8Bit(getenv("ANDROID_SDK_ROOT"));
1032     if (!sdkFromEnvVar.isEmpty())
1033         return FilePath::fromUserInput(sdkFromEnvVar).cleanPath();
1034 
1035     // Set default path of SDK as used by Android Studio
1036     if (Utils::HostOsInfo::isMacHost()) {
1037         return Utils::FilePath::fromString(
1038             QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/Library/Android/sdk");
1039     }
1040 
1041     if (Utils::HostOsInfo::isWindowsHost()) {
1042         return Utils::FilePath::fromString(
1043             QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/Android/Sdk");
1044     }
1045 
1046     return Utils::FilePath::fromString(
1047         QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/Android/Sdk");
1048 }
1049 
1050 ///////////////////////////////////
1051 // AndroidConfigurations
1052 ///////////////////////////////////
setConfig(const AndroidConfig & devConfigs)1053 void AndroidConfigurations::setConfig(const AndroidConfig &devConfigs)
1054 {
1055     emit m_instance->aboutToUpdate();
1056     m_instance->m_config = devConfigs;
1057 
1058     m_instance->save();
1059     updateAndroidDevice();
1060     registerNewToolChains();
1061     updateAutomaticKitList();
1062     removeOldToolChains();
1063     emit m_instance->updated();
1064 }
1065 
showDeviceDialog(Project * project,int apiLevel,const QStringList & abis)1066 AndroidDeviceInfo AndroidConfigurations::showDeviceDialog(Project *project,
1067                                                           int apiLevel, const QStringList &abis)
1068 {
1069     QString serialNumber;
1070     for (const QString &abi : abis) {
1071         serialNumber = defaultDevice(project, abi);
1072         if (!serialNumber.isEmpty())
1073             break;
1074     }
1075 
1076     const AndroidDeviceInfo defaultDevice = AndroidDeviceDialog::defaultDeviceInfo(serialNumber);
1077     if (defaultDevice.isValid())
1078         return defaultDevice;
1079 
1080     AndroidDeviceDialog dialog(apiLevel, abis, serialNumber, Core::ICore::dialogParent());
1081     AndroidDeviceInfo info = dialog.showAndGetSelectedDevice();
1082     if (dialog.saveDeviceSelection() && info.isValid()) {
1083         const QString newSerialNumber = info.type == AndroidDeviceInfo::Hardware ?
1084                     info.serialNumber : info.avdname;
1085         if (!newSerialNumber.isEmpty()) {
1086             const QString preferredAbi = AndroidManager::devicePreferredAbi(info.cpuAbi, abis);
1087             AndroidConfigurations::setDefaultDevice(project, preferredAbi, newSerialNumber);
1088         }
1089     }
1090     return info;
1091 }
1092 
clearDefaultDevices(Project * project)1093 void AndroidConfigurations::clearDefaultDevices(Project *project)
1094 {
1095     if (m_instance->m_defaultDeviceForAbi.contains(project))
1096         m_instance->m_defaultDeviceForAbi.remove(project);
1097 }
1098 
setDefaultDevice(Project * project,const QString & abi,const QString & serialNumber)1099 void AndroidConfigurations::setDefaultDevice(Project *project, const QString &abi, const QString &serialNumber)
1100 {
1101     m_instance->m_defaultDeviceForAbi[project][abi] = serialNumber;
1102 }
1103 
defaultDevice(Project * project,const QString & abi)1104 QString AndroidConfigurations::defaultDevice(Project *project, const QString &abi)
1105 {
1106     if (!m_instance->m_defaultDeviceForAbi.contains(project))
1107         return QString();
1108     const QMap<QString, QString> &map = m_instance->m_defaultDeviceForAbi.value(project);
1109     if (!map.contains(abi))
1110         return QString();
1111     return map.value(abi);
1112 }
1113 
matchToolChain(const ToolChain * atc,const ToolChain * btc)1114 static bool matchToolChain(const ToolChain *atc, const ToolChain *btc)
1115 {
1116     if (atc == btc)
1117         return true;
1118 
1119     if (!atc || !btc)
1120         return false;
1121 
1122     if (atc->typeId() != Constants::ANDROID_TOOLCHAIN_TYPEID || btc->typeId() != Constants::ANDROID_TOOLCHAIN_TYPEID)
1123         return false;
1124 
1125     auto aatc = static_cast<const AndroidToolChain *>(atc);
1126     auto abtc = static_cast<const AndroidToolChain *>(btc);
1127     return aatc->targetAbi() == abtc->targetAbi();
1128 }
1129 
registerNewToolChains()1130 void AndroidConfigurations::registerNewToolChains()
1131 {
1132     const QList<ToolChain *> existingAndroidToolChains
1133             = ToolChainManager::toolChains(Utils::equal(&ToolChain::typeId,
1134                                                         Utils::Id(Constants::ANDROID_TOOLCHAIN_TYPEID)));
1135     QList<ToolChain *> newToolchains = AndroidToolChainFactory::autodetectToolChains(
1136         existingAndroidToolChains);
1137 
1138     foreach (ToolChain *tc, newToolchains)
1139         ToolChainManager::registerToolChain(tc);
1140 
1141     registerCustomToolChainsAndDebuggers();
1142 }
1143 
removeOldToolChains()1144 void AndroidConfigurations::removeOldToolChains()
1145 {
1146     foreach (ToolChain *tc, ToolChainManager::toolChains(Utils::equal(&ToolChain::typeId, Utils::Id(Constants::ANDROID_TOOLCHAIN_TYPEID)))) {
1147         if (!tc->isValid())
1148             ToolChainManager::deregisterToolChain(tc);
1149     }
1150 }
1151 
removeUnusedDebuggers()1152 void AndroidConfigurations::removeUnusedDebuggers()
1153 {
1154     const QList<QtSupport::BaseQtVersion *> qtVersions = QtSupport::QtVersionManager::versions(
1155                 [](const QtSupport::BaseQtVersion *v) {
1156         return v->type() == Constants::ANDROIDQT;
1157     });
1158 
1159     QVector<FilePath> uniqueNdks;
1160     for (const QtSupport::BaseQtVersion *qt : qtVersions) {
1161         FilePath ndkLocation = currentConfig().ndkLocation(qt);
1162         if (!uniqueNdks.contains(ndkLocation))
1163             uniqueNdks.append(ndkLocation);
1164     }
1165 
1166     uniqueNdks.append(Utils::transform(currentConfig().getCustomNdkList(),
1167                                        FilePath::fromString).toVector());
1168 
1169     const QList<Debugger::DebuggerItem> allDebuggers = Debugger::DebuggerItemManager::debuggers();
1170     for (const Debugger::DebuggerItem &debugger : allDebuggers) {
1171         if (!debugger.displayName().contains("Android"))
1172             continue;
1173 
1174         bool isChildOfNdk = false;
1175         for (const FilePath &path : uniqueNdks) {
1176             if (debugger.command().isChildOf(path)) {
1177                 isChildOfNdk = true;
1178                 break;
1179             }
1180         }
1181 
1182         const bool isMultiAbiNdkGdb = debugger.command().fileName().startsWith("gdb");
1183         const bool hasMultiAbiName = debugger.displayName().contains("Multi-Abi");
1184 
1185         if (debugger.isAutoDetected() && (!isChildOfNdk || (isMultiAbiNdkGdb && !hasMultiAbiName)))
1186             Debugger::DebuggerItemManager::deregisterDebugger(debugger.id());
1187     }
1188 }
1189 
allSupportedAbis()1190 static QStringList allSupportedAbis()
1191 {
1192     return QStringList{
1193         ProjectExplorer::Constants::ANDROID_ABI_ARMEABI_V7A,
1194         ProjectExplorer::Constants::ANDROID_ABI_ARM64_V8A,
1195         ProjectExplorer::Constants::ANDROID_ABI_X86,
1196         ProjectExplorer::Constants::ANDROID_ABI_X86_64,
1197     };
1198 }
1199 
containsAllAbis(const QStringList & abis)1200 static bool containsAllAbis(const QStringList &abis)
1201 {
1202     QStringList supportedAbis{allSupportedAbis()};
1203     for (const QString &abi : abis)
1204         if (supportedAbis.contains(abi))
1205             supportedAbis.removeOne(abi);
1206 
1207     return supportedAbis.isEmpty();
1208 }
1209 
getMultiOrSingleAbiString(const QStringList & abis)1210 static QString getMultiOrSingleAbiString(const QStringList &abis)
1211 {
1212     return containsAllAbis(abis) ? "Multi-Abi" : abis.join(",");
1213 }
1214 
findOrRegisterDebugger(ToolChain * tc,const QStringList & abisList,bool customDebugger=false)1215 static QVariant findOrRegisterDebugger(ToolChain *tc,
1216                                        const QStringList &abisList,
1217                                        bool customDebugger = false)
1218 {
1219     const auto &currentConfig = AndroidConfigurations::currentConfig();
1220     const FilePath ndk = static_cast<AndroidToolChain *>(tc)->ndkLocation();
1221     const FilePath command = currentConfig.gdbPathFromNdk(tc->targetAbi(), ndk);
1222 
1223     // check if the debugger is already registered, but ignoring the display name
1224     const Debugger::DebuggerItem *existing = Debugger::DebuggerItemManager::findByCommand(command);
1225 
1226     // Return existing debugger with same command
1227     if (existing && existing->engineType() == Debugger::GdbEngineType
1228             && existing->isAutoDetected()) {
1229         return existing->id();
1230     }
1231 
1232     // debugger not found, register a new one
1233     Debugger::DebuggerItem debugger;
1234     debugger.setCommand(command);
1235     debugger.setEngineType(Debugger::GdbEngineType);
1236 
1237     // NDK 10 and older have multiple gdb versions per ABI, so check for that.
1238     const bool oldNdkVersion = currentConfig.ndkVersion(ndk) <= QVersionNumber{11};
1239     QString mainName = AndroidConfigurations::tr("Android Debugger (%1, NDK %2)");
1240     if (customDebugger)
1241         mainName.prepend("Custom ");
1242     debugger.setUnexpandedDisplayName(mainName
1243             .arg(getMultiOrSingleAbiString(oldNdkVersion ? abisList : allSupportedAbis()))
1244             .arg(AndroidConfigurations::currentConfig().ndkVersion(ndk).toString()));
1245     debugger.setAutoDetected(true);
1246     debugger.reinitializeFromFile();
1247     return Debugger::DebuggerItemManager::registerDebugger(debugger);
1248 }
1249 
registerCustomToolChainsAndDebuggers()1250 void AndroidConfigurations::registerCustomToolChainsAndDebuggers()
1251 {
1252     const QList<ToolChain *> existingAndroidToolChains = ToolChainManager::toolChains(
1253         Utils::equal(&ToolChain::typeId, Utils::Id(Constants::ANDROID_TOOLCHAIN_TYPEID)));
1254     QList<FilePath> customNdks = Utils::transform(currentConfig().getCustomNdkList(),
1255                                                   FilePath::fromString);
1256     QList<ToolChain *> customToolchains
1257         = AndroidToolChainFactory::autodetectToolChainsFromNdks(existingAndroidToolChains,
1258                                                                 customNdks,
1259                                                                 true);
1260     for (ToolChain *tc : customToolchains) {
1261         ToolChainManager::registerToolChain(tc);
1262         const auto androidToolChain = static_cast<AndroidToolChain *>(tc);
1263         QString abiStr;
1264         if (androidToolChain)
1265             abiStr = androidToolChain->platformLinkerFlags().at(1).split('-').first();
1266         findOrRegisterDebugger(tc, {abiStr}, true);
1267     }
1268 }
updateAutomaticKitList()1269 void AndroidConfigurations::updateAutomaticKitList()
1270 {
1271     for (Kit *k : KitManager::kits()) {
1272         if (DeviceTypeKitAspect::deviceTypeId(k) == Constants::ANDROID_DEVICE_TYPE) {
1273             if (k->value(Constants::ANDROID_KIT_NDK).isNull() || k->value(Constants::ANDROID_KIT_SDK).isNull()) {
1274                 if (BaseQtVersion *qt = QtKitAspect::qtVersion(k)) {
1275                     k->setValueSilently(Constants::ANDROID_KIT_NDK, currentConfig().ndkLocation(qt).toString());
1276                     k->setValue(Constants::ANDROID_KIT_SDK, currentConfig().sdkLocation().toString());
1277                 }
1278             }
1279         }
1280     }
1281 
1282     const QList<Kit *> existingKits = Utils::filtered(KitManager::kits(), [](Kit *k) {
1283         Utils::Id deviceTypeId = DeviceTypeKitAspect::deviceTypeId(k);
1284         if (k->isAutoDetected() && !k->isSdkProvided()
1285                 && deviceTypeId == Utils::Id(Constants::ANDROID_DEVICE_TYPE)) {
1286             return true;
1287         }
1288         return false;
1289     });
1290 
1291     removeUnusedDebuggers();
1292 
1293     QHash<Abi, QList<const QtSupport::BaseQtVersion *> > qtVersionsForArch;
1294     const QList<QtSupport::BaseQtVersion *> qtVersions
1295             = QtSupport::QtVersionManager::versions([](const QtSupport::BaseQtVersion *v) {
1296         return v->type() == Constants::ANDROIDQT;
1297     });
1298     for (const QtSupport::BaseQtVersion *qtVersion : qtVersions) {
1299         const Abis qtAbis = qtVersion->qtAbis();
1300         if (qtAbis.empty())
1301             continue;
1302         qtVersionsForArch[qtAbis.first()].append(qtVersion);
1303     }
1304 
1305     DeviceManager *dm = DeviceManager::instance();
1306     IDevice::ConstPtr device = dm->find(Utils::Id(Constants::ANDROID_DEVICE_ID));
1307     if (device.isNull()) {
1308         // no device, means no sdk path
1309         for (Kit *k : existingKits)
1310             KitManager::deregisterKit(k);
1311         return;
1312     }
1313 
1314     // register new kits
1315     const QList<ToolChain *> toolchains = ToolChainManager::toolChains([](const ToolChain *tc) {
1316         return tc->isAutoDetected()
1317             && tc->isValid()
1318             && tc->typeId() == Constants::ANDROID_TOOLCHAIN_TYPEID;
1319     });
1320     QList<Kit *> unhandledKits = existingKits;
1321     for (ToolChain *tc : toolchains) {
1322         if (tc->language() != Utils::Id(ProjectExplorer::Constants::CXX_LANGUAGE_ID))
1323             continue;
1324 
1325         for (const QtSupport::BaseQtVersion *qt : qtVersionsForArch.value(tc->targetAbi())) {
1326             FilePath tcNdk = static_cast<const AndroidToolChain *>(tc)->ndkLocation();
1327             if (tcNdk != currentConfig().ndkLocation(qt))
1328                 continue;
1329 
1330             const QList<ToolChain *> allLanguages
1331                 = Utils::filtered(toolchains, [tc, tcNdk](ToolChain *otherTc) {
1332                       FilePath otherNdk = static_cast<const AndroidToolChain *>(otherTc)->ndkLocation();
1333                       return tc->targetAbi() == otherTc->targetAbi() && tcNdk == otherNdk;
1334                   });
1335 
1336             QHash<Utils::Id, ToolChain *> toolChainForLanguage;
1337             for (ToolChain *tc : allLanguages)
1338                 toolChainForLanguage[tc->language()] = tc;
1339 
1340             Kit *existingKit = Utils::findOrDefault(existingKits, [&](const Kit *b) {
1341                 if (qt != QtSupport::QtKitAspect::qtVersion(b))
1342                     return false;
1343                 return matchToolChain(toolChainForLanguage[ProjectExplorer::Constants::CXX_LANGUAGE_ID],
1344                                       ToolChainKitAspect::cxxToolChain(b))
1345                         && matchToolChain(toolChainForLanguage[ProjectExplorer::Constants::C_LANGUAGE_ID],
1346                                           ToolChainKitAspect::cToolChain(b));
1347             });
1348 
1349             const auto initializeKit = [allLanguages, device, tc, qt](Kit *k) {
1350                 k->setAutoDetected(true);
1351                 k->setAutoDetectionSource("AndroidConfiguration");
1352                 DeviceTypeKitAspect::setDeviceTypeId(k, Utils::Id(Constants::ANDROID_DEVICE_TYPE));
1353                 for (ToolChain *tc : allLanguages)
1354                     ToolChainKitAspect::setToolChain(k, tc);
1355                 QtSupport::QtKitAspect::setQtVersion(k, qt);
1356                 DeviceKitAspect::setDevice(k, device);
1357                 QStringList abis = static_cast<const AndroidQtVersion *>(qt)->androidAbis();
1358                 Debugger::DebuggerKitAspect::setDebugger(k, findOrRegisterDebugger(tc, abis));
1359 
1360                 k->setSticky(ToolChainKitAspect::id(), true);
1361                 k->setSticky(QtSupport::QtKitAspect::id(), true);
1362                 k->setSticky(DeviceKitAspect::id(), true);
1363                 k->setSticky(DeviceTypeKitAspect::id(), true);
1364 
1365                 QString versionStr = QLatin1String("Qt %{Qt:Version}");
1366                 if (!qt->isAutodetected())
1367                     versionStr = QString("%1").arg(qt->displayName());
1368                 k->setUnexpandedDisplayName(tr("Android %1 Clang %2")
1369                                                 .arg(versionStr)
1370                                                 .arg(getMultiOrSingleAbiString(abis)));
1371                 k->setValueSilently(Constants::ANDROID_KIT_NDK, currentConfig().ndkLocation(qt).toString());
1372                 k->setValueSilently(Constants::ANDROID_KIT_SDK, currentConfig().sdkLocation().toString());
1373             };
1374 
1375             if (existingKit) {
1376                 initializeKit(existingKit); // Update the existing kit with new data.
1377                 unhandledKits.removeOne(existingKit);
1378             } else {
1379                 KitManager::registerKit(initializeKit);
1380             }
1381         }
1382     }
1383     // cleanup any mess that might have existed before, by removing all Android kits that
1384     // existed before, but weren't re-used
1385     for (Kit *k : unhandledKits)
1386         KitManager::deregisterKit(k);
1387 }
1388 
force32bitEmulator()1389 bool AndroidConfigurations::force32bitEmulator()
1390 {
1391     return m_instance->m_force32bit;
1392 }
1393 
toolsEnvironment(const AndroidConfig & config)1394 Environment AndroidConfigurations::toolsEnvironment(const AndroidConfig &config)
1395 {
1396     Environment env = Environment::systemEnvironment();
1397     FilePath jdkLocation = config.openJDKLocation();
1398     if (!jdkLocation.isEmpty()) {
1399         env.set("JAVA_HOME", jdkLocation.toUserOutput());
1400         env.prependOrSetPath(jdkLocation.pathAppended("bin").toUserOutput());
1401     }
1402     return env;
1403 }
1404 
currentConfig()1405 const AndroidConfig &AndroidConfigurations::currentConfig()
1406 {
1407     return m_instance->m_config; // ensure that m_instance is initialized
1408 }
1409 
sdkManager()1410 AndroidSdkManager *AndroidConfigurations::sdkManager()
1411 {
1412     return m_instance->m_sdkManager.get();
1413 }
1414 
instance()1415 AndroidConfigurations *AndroidConfigurations::instance()
1416 {
1417     return m_instance;
1418 }
1419 
save()1420 void AndroidConfigurations::save()
1421 {
1422     QSettings *settings = Core::ICore::settings();
1423     settings->beginGroup(SettingsGroup);
1424     m_config.save(*settings);
1425     settings->endGroup();
1426 }
1427 
AndroidConfigurations()1428 AndroidConfigurations::AndroidConfigurations()
1429     : m_sdkManager(new AndroidSdkManager(m_config))
1430 {
1431     load();
1432 
1433     connect(SessionManager::instance(), &SessionManager::projectRemoved,
1434             this, &AndroidConfigurations::clearDefaultDevices);
1435     connect(DeviceManager::instance(), &DeviceManager::devicesLoaded,
1436             this, &AndroidConfigurations::updateAndroidDevice);
1437 
1438     m_force32bit = is32BitUserSpace();
1439 
1440     m_instance = this;
1441 }
1442 
1443 AndroidConfigurations::~AndroidConfigurations() = default;
1444 
androidStudioPath()1445 static Utils::FilePath androidStudioPath()
1446 {
1447 #if defined(Q_OS_WIN)
1448     const QLatin1String registryKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\Android Studio");
1449     const QLatin1String valueName("Path");
1450     const QSettings settings64(registryKey, QSettings::Registry64Format);
1451     const QSettings settings32(registryKey, QSettings::Registry32Format);
1452     return Utils::FilePath::fromUserInput(
1453                 settings64.value(valueName, settings32.value(valueName).toString()).toString());
1454 #endif
1455     return {}; // TODO non-Windows
1456 }
1457 
getJdkPath()1458 FilePath AndroidConfig::getJdkPath()
1459 {
1460     FilePath jdkHome;
1461 
1462     if (HostOsInfo::isWindowsHost()) {
1463         QStringList allVersions;
1464         std::unique_ptr<QSettings> settings(
1465             new QSettings(jdk8SettingsPath, QSettings::NativeFormat));
1466         allVersions = settings->childGroups();
1467 #ifdef Q_OS_WIN
1468         if (allVersions.isEmpty()) {
1469             settings.reset(new QSettings(jdk8SettingsPath, QSettings::Registry64Format));
1470             allVersions = settings->childGroups();
1471         }
1472 #endif // Q_OS_WIN
1473 
1474         // If no jdk 1.8 can be found, look for jdk versions above 1.8
1475         // Android section would warn if sdkmanager cannot run with newer jdk versions
1476         if (allVersions.isEmpty()) {
1477             settings.reset(new QSettings(jdkLatestSettingsPath, QSettings::NativeFormat));
1478             allVersions = settings->childGroups();
1479 #ifdef Q_OS_WIN
1480             if (allVersions.isEmpty()) {
1481                 settings.reset(new QSettings(jdkLatestSettingsPath, QSettings::Registry64Format));
1482                 allVersions = settings->childGroups();
1483             }
1484 #endif // Q_OS_WIN
1485         }
1486 
1487         for (const QString &version : qAsConst(allVersions)) {
1488             settings->beginGroup(version);
1489             jdkHome = FilePath::fromUserInput(settings->value("JavaHome").toString());
1490             settings->endGroup();
1491             if (version.startsWith("1.8")) {
1492                 if (!jdkHome.exists())
1493                     continue;
1494                 break;
1495             }
1496         }
1497 
1498         // Nothing found yet? Let's try finding Android Studio's jdk
1499         if (jdkHome.isEmpty()) {
1500             const Utils::FilePath androidStudioSdkPath = androidStudioPath();
1501             if (!androidStudioSdkPath.isEmpty()) {
1502                 const Utils::FilePath androidStudioSdkJrePath = androidStudioSdkPath / "jre";
1503                 if (androidStudioSdkJrePath.exists())
1504                     jdkHome = androidStudioSdkJrePath;
1505             }
1506         }
1507     } else {
1508         QStringList args;
1509         if (HostOsInfo::isMacHost())
1510             args << "-c"
1511                  << "/usr/libexec/java_home";
1512         else
1513             args << "-c"
1514                  << "readlink -f $(which java)";
1515 
1516         QProcess findJdkPathProc;
1517         findJdkPathProc.start("sh", args);
1518         findJdkPathProc.waitForFinished();
1519         QByteArray jdkPath = findJdkPathProc.readAllStandardOutput().trimmed();
1520 
1521         if (HostOsInfo::isMacHost()) {
1522             jdkHome = FilePath::fromUtf8(jdkPath);
1523         } else {
1524             jdkPath.replace("bin/java", ""); // For OpenJDK 11
1525             jdkPath.replace("jre", "");
1526             jdkPath.replace("//", "/");
1527             jdkHome = FilePath::fromUtf8(jdkPath);
1528         }
1529     }
1530 
1531     return jdkHome;
1532 }
1533 
load()1534 void AndroidConfigurations::load()
1535 {
1536     QSettings *settings = Core::ICore::settings();
1537     settings->beginGroup(SettingsGroup);
1538     m_config.load(*settings);
1539     settings->endGroup();
1540 }
1541 
updateAndroidDevice()1542 void AndroidConfigurations::updateAndroidDevice()
1543 {
1544     DeviceManager * const devMgr = DeviceManager::instance();
1545     if (m_instance->m_config.adbToolPath().exists())
1546         devMgr->addDevice(AndroidDevice::create());
1547     else if (devMgr->find(Constants::ANDROID_DEVICE_ID))
1548         devMgr->removeDevice(Utils::Id(Constants::ANDROID_DEVICE_ID));
1549 }
1550 
1551 AndroidConfigurations *AndroidConfigurations::m_instance = nullptr;
1552 
1553 } // namespace Android
1554