| 
									
										
										
										
											2017-04-03 11:11:17 +02:00
										 |  |  | /****************************************************************************
 | 
					
						
							|  |  |  | ** | 
					
						
							|  |  |  | ** Copyright (C) 2016 The Qt Company Ltd. | 
					
						
							|  |  |  | ** Contact: https://www.qt.io/licensing/
 | 
					
						
							|  |  |  | ** | 
					
						
							|  |  |  | ** This file is part of Qt Creator. | 
					
						
							|  |  |  | ** | 
					
						
							|  |  |  | ** Commercial License Usage | 
					
						
							|  |  |  | ** Licensees holding valid commercial Qt licenses may use this file in | 
					
						
							|  |  |  | ** accordance with the commercial license agreement provided with the | 
					
						
							|  |  |  | ** Software or, alternatively, in accordance with the terms contained in | 
					
						
							|  |  |  | ** a written agreement between you and The Qt Company. For licensing terms | 
					
						
							|  |  |  | ** and conditions see https://www.qt.io/terms-conditions. For further
 | 
					
						
							|  |  |  | ** information use the contact form at https://www.qt.io/contact-us.
 | 
					
						
							|  |  |  | ** | 
					
						
							|  |  |  | ** GNU General Public License Usage | 
					
						
							|  |  |  | ** Alternatively, this file may be used under the terms of the GNU | 
					
						
							|  |  |  | ** General Public License version 3 as published by the Free Software | 
					
						
							|  |  |  | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT | 
					
						
							|  |  |  | ** included in the packaging of this file. Please review the following | 
					
						
							|  |  |  | ** information to ensure the GNU General Public License requirements will | 
					
						
							|  |  |  | ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
 | 
					
						
							|  |  |  | ** | 
					
						
							|  |  |  | ****************************************************************************/ | 
					
						
							|  |  |  | #include "androidavdmanager.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "androidtoolmanager.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-08 16:05:57 +01:00
										 |  |  | #include <coreplugin/icore.h>
 | 
					
						
							|  |  |  | #include <utils/algorithm.h>
 | 
					
						
							|  |  |  | #include <utils/qtcassert.h>
 | 
					
						
							|  |  |  | #include <utils/runextensions.h>
 | 
					
						
							|  |  |  | #include <utils/synchronousprocess.h>
 | 
					
						
							| 
									
										
										
										
											2017-04-03 11:11:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | #include <QApplication>
 | 
					
						
							|  |  |  | #include <QFileInfo>
 | 
					
						
							|  |  |  | #include <QLoggingCategory>
 | 
					
						
							| 
									
										
										
										
											2018-04-18 12:23:02 +02:00
										 |  |  | #include <QMessageBox>
 | 
					
						
							| 
									
										
										
										
											2017-04-03 11:11:17 +02:00
										 |  |  | #include <QSettings>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <chrono>
 | 
					
						
							| 
									
										
										
										
											2018-04-19 09:18:42 +02:00
										 |  |  | #include <functional>
 | 
					
						
							| 
									
										
										
										
											2017-04-03 11:11:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | namespace { | 
					
						
							| 
									
										
										
										
											2018-10-12 09:33:30 +03:00
										 |  |  | Q_LOGGING_CATEGORY(avdManagerLog, "qtc.android.avdManager", QtWarningMsg) | 
					
						
							| 
									
										
										
										
											2017-04-03 11:11:17 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | namespace Android { | 
					
						
							|  |  |  | namespace Internal { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | using namespace std; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Avd list keys to parse avd
 | 
					
						
							|  |  |  | const char avdInfoNameKey[] = "Name:"; | 
					
						
							|  |  |  | const char avdInfoPathKey[] = "Path:"; | 
					
						
							|  |  |  | const char avdInfoAbiKey[] = "abi.type"; | 
					
						
							|  |  |  | const char avdInfoTargetKey[] = "target"; | 
					
						
							|  |  |  | const char avdInfoErrorKey[] = "Error:"; | 
					
						
							| 
									
										
										
										
											2018-04-18 10:40:54 +02:00
										 |  |  | const char googleApiTag[] = "google_apis"; | 
					
						
							| 
									
										
										
										
											2017-04-03 11:11:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | const int avdCreateTimeoutMs = 30000; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*!
 | 
					
						
							|  |  |  |     Runs the \c avdmanager tool specific to configuration \a config with arguments \a args. Returns | 
					
						
							|  |  |  |     \c true if the command is successfully executed. Output is copied into \a output. The function | 
					
						
							|  |  |  |     blocks the calling thread. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static bool avdManagerCommand(const AndroidConfig config, const QStringList &args, QString *output) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     QString avdManagerToolPath = config.avdManagerToolPath().toString(); | 
					
						
							|  |  |  |     Utils::SynchronousProcess proc; | 
					
						
							| 
									
										
										
										
											2018-04-19 12:19:03 +02:00
										 |  |  |     auto env = AndroidConfigurations::toolsEnvironment(config).toStringList(); | 
					
						
							|  |  |  |     proc.setEnvironment(env); | 
					
						
							| 
									
										
										
										
											2017-04-03 11:11:17 +02:00
										 |  |  |     Utils::SynchronousProcessResponse response = proc.runBlocking(avdManagerToolPath, args); | 
					
						
							|  |  |  |     if (response.result == Utils::SynchronousProcessResponse::Finished) { | 
					
						
							|  |  |  |         if (output) | 
					
						
							|  |  |  |             *output = response.allOutput(); | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*!
 | 
					
						
							|  |  |  |     Parses the \a line for a [spaces]key[spaces]value[spaces] pattern and returns | 
					
						
							|  |  |  |     \c true if the key is found, \c false otherwise. The value is copied into \a value. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static bool valueForKey(QString key, const QString &line, QString *value = nullptr) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     auto trimmedInput = line.trimmed(); | 
					
						
							|  |  |  |     if (trimmedInput.startsWith(key)) { | 
					
						
							|  |  |  |         if (value) | 
					
						
							|  |  |  |             *value = trimmedInput.section(key, 1, 1).trimmed(); | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static bool checkForTimeout(const chrono::steady_clock::time_point &start, | 
					
						
							|  |  |  |                             int msecs = 3000) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     bool timedOut = false; | 
					
						
							|  |  |  |     auto end = chrono::steady_clock::now(); | 
					
						
							|  |  |  |     if (chrono::duration_cast<chrono::milliseconds>(end-start).count() > msecs) | 
					
						
							|  |  |  |         timedOut = true; | 
					
						
							|  |  |  |     return timedOut; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-18 08:22:34 +02:00
										 |  |  | static CreateAvdInfo createAvdCommand(const AndroidConfig config, const CreateAvdInfo &info) | 
					
						
							| 
									
										
										
										
											2017-04-03 11:11:17 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2017-08-18 08:22:34 +02:00
										 |  |  |     CreateAvdInfo result = info; | 
					
						
							| 
									
										
										
										
											2017-04-03 11:11:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (!result.isValid()) { | 
					
						
							|  |  |  |         qCDebug(avdManagerLog) << "AVD Create failed. Invalid CreateAvdInfo" << result.name | 
					
						
							| 
									
										
										
										
											2017-08-18 08:22:34 +02:00
										 |  |  |                                << result.sdkPlatform->displayText() << result.sdkPlatform->apiLevel(); | 
					
						
							| 
									
										
										
										
											2017-04-03 11:11:17 +02:00
										 |  |  |         result.error = QApplication::translate("AndroidAvdManager", | 
					
						
							|  |  |  |                                                "Cannot create AVD. Invalid input."); | 
					
						
							|  |  |  |         return result; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-18 10:40:54 +02:00
										 |  |  |     QStringList arguments({"create", "avd", "-n", result.name}); | 
					
						
							| 
									
										
										
										
											2017-04-03 11:11:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (!result.abi.isEmpty()) { | 
					
						
							| 
									
										
										
										
											2017-08-18 08:22:34 +02:00
										 |  |  |         SystemImage *image = Utils::findOrDefault(result.sdkPlatform->systemImages(), | 
					
						
							| 
									
										
										
										
											2017-04-03 11:11:17 +02:00
										 |  |  |                                                  Utils::equal(&SystemImage::abiName, result.abi)); | 
					
						
							| 
									
										
										
										
											2017-08-18 08:22:34 +02:00
										 |  |  |         if (image && image->isValid()) { | 
					
						
							|  |  |  |             arguments << "-k" << image->sdkStylePath(); | 
					
						
							| 
									
										
										
										
											2018-04-18 10:40:54 +02:00
										 |  |  |             // Google api system images requires explicit abi as
 | 
					
						
							|  |  |  |             // google-apis/ABI or --tag "google-apis"
 | 
					
						
							|  |  |  |             if (image->sdkStylePath().contains(googleApiTag)) | 
					
						
							|  |  |  |                 arguments << "--tag" << googleApiTag; | 
					
						
							| 
									
										
										
										
											2017-04-03 11:11:17 +02:00
										 |  |  |         } else { | 
					
						
							| 
									
										
										
										
											2017-08-18 08:22:34 +02:00
										 |  |  |             QString name = result.sdkPlatform->displayText(); | 
					
						
							| 
									
										
										
										
											2017-04-03 11:11:17 +02:00
										 |  |  |             qCDebug(avdManagerLog) << "AVD Create failed. Cannot find system image for the platform" | 
					
						
							| 
									
										
										
										
											2017-08-18 08:22:34 +02:00
										 |  |  |                                    << result.abi << name; | 
					
						
							| 
									
										
										
										
											2017-04-03 11:11:17 +02:00
										 |  |  |             result.error = QApplication::translate("AndroidAvdManager", | 
					
						
							|  |  |  |                                                    "Cannot create AVD. Cannot find system image for " | 
					
						
							| 
									
										
										
										
											2017-08-18 08:22:34 +02:00
										 |  |  |                                                    "the ABI %1(%2).").arg(result.abi).arg(name); | 
					
						
							| 
									
										
										
										
											2017-04-03 11:11:17 +02:00
										 |  |  |             return result; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     } else { | 
					
						
							| 
									
										
										
										
											2017-08-18 08:22:34 +02:00
										 |  |  |         arguments << "-k" << result.sdkPlatform->sdkStylePath(); | 
					
						
							| 
									
										
										
										
											2017-04-03 11:11:17 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (result.sdcardSize > 0) | 
					
						
							|  |  |  |         arguments << "-c" << QString::fromLatin1("%1M").arg(result.sdcardSize); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     QProcess proc; | 
					
						
							|  |  |  |     proc.start(config.avdManagerToolPath().toString(), arguments); | 
					
						
							|  |  |  |     if (!proc.waitForStarted()) { | 
					
						
							|  |  |  |         result.error = QApplication::translate("AndroidAvdManager", | 
					
						
							|  |  |  |                                                "Could not start process \"%1 %2\"") | 
					
						
							|  |  |  |                 .arg(config.avdManagerToolPath().toString(), arguments.join(' ')); | 
					
						
							|  |  |  |         return result; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     QTC_CHECK(proc.state() == QProcess::Running); | 
					
						
							|  |  |  |     proc.write(QByteArray("yes\n")); // yes to "Do you wish to create a custom hardware profile"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     auto start = chrono::steady_clock::now(); | 
					
						
							|  |  |  |     QString errorOutput; | 
					
						
							|  |  |  |     QByteArray question; | 
					
						
							|  |  |  |     while (errorOutput.isEmpty()) { | 
					
						
							|  |  |  |         proc.waitForReadyRead(500); | 
					
						
							|  |  |  |         question += proc.readAllStandardOutput(); | 
					
						
							|  |  |  |         if (question.endsWith(QByteArray("]:"))) { | 
					
						
							|  |  |  |             // truncate to last line
 | 
					
						
							|  |  |  |             int index = question.lastIndexOf(QByteArray("\n")); | 
					
						
							|  |  |  |             if (index != -1) | 
					
						
							|  |  |  |                 question = question.mid(index); | 
					
						
							|  |  |  |             if (question.contains("hw.gpu.enabled")) | 
					
						
							|  |  |  |                 proc.write(QByteArray("yes\n")); | 
					
						
							|  |  |  |             else | 
					
						
							|  |  |  |                 proc.write(QByteArray("\n")); | 
					
						
							|  |  |  |             question.clear(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         // The exit code is always 0, so we need to check stderr
 | 
					
						
							|  |  |  |         // For now assume that any output at all indicates a error
 | 
					
						
							|  |  |  |         errorOutput = QString::fromLocal8Bit(proc.readAllStandardError()); | 
					
						
							|  |  |  |         if (proc.state() != QProcess::Running) | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // For a sane input and command, process should finish before timeout.
 | 
					
						
							|  |  |  |         if (checkForTimeout(start, avdCreateTimeoutMs)) { | 
					
						
							|  |  |  |             result.error = QApplication::translate("AndroidAvdManager", | 
					
						
							|  |  |  |                                                    "Cannot create AVD. Command timed out."); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Kill the running process.
 | 
					
						
							|  |  |  |     if (proc.state() != QProcess::NotRunning) { | 
					
						
							|  |  |  |         proc.terminate(); | 
					
						
							|  |  |  |         if (!proc.waitForFinished(3000)) | 
					
						
							|  |  |  |             proc.kill(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     QTC_CHECK(proc.state() == QProcess::NotRunning); | 
					
						
							|  |  |  |     result.error = errorOutput; | 
					
						
							|  |  |  |     return result; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-19 09:18:42 +02:00
										 |  |  | static void avdProcessFinished(int exitCode, QProcess *p) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     QTC_ASSERT(p, return); | 
					
						
							|  |  |  |     if (exitCode) { | 
					
						
							|  |  |  |         QString title = QCoreApplication::translate("Android::Internal::AndroidAvdManager", | 
					
						
							|  |  |  |                                                     "AVD Start Error"); | 
					
						
							|  |  |  |         QMessageBox::critical(Core::ICore::dialogParent(), title, | 
					
						
							|  |  |  |                               QString::fromLatin1(p->readAll())); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     p->deleteLater(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-03 11:11:17 +02:00
										 |  |  | /*!
 | 
					
						
							|  |  |  |     \class AvdManagerOutputParser | 
					
						
							|  |  |  |     \brief The AvdManagerOutputParser class is a helper class to parse the output of the avdmanager | 
					
						
							|  |  |  |     commands. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | class AvdManagerOutputParser | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | public: | 
					
						
							|  |  |  |     AndroidDeviceInfoList listVirtualDevices(const AndroidConfig &config); | 
					
						
							|  |  |  |     AndroidDeviceInfoList parseAvdList(const QString &output); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | private: | 
					
						
							|  |  |  |     bool parseAvd(const QStringList &deviceInfo, AndroidDeviceInfo *avd); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | AndroidAvdManager::AndroidAvdManager(const AndroidConfig &config): | 
					
						
							|  |  |  |     m_config(config), | 
					
						
							|  |  |  |     m_androidTool(new AndroidToolManager(m_config)), | 
					
						
							|  |  |  |     m_parser(new AvdManagerOutputParser) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-25 12:19:15 +02:00
										 |  |  | AndroidAvdManager::~AndroidAvdManager() = default; | 
					
						
							| 
									
										
										
										
											2017-04-03 11:11:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | void AndroidAvdManager::launchAvdManagerUiTool() const | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2017-07-14 19:35:48 +02:00
										 |  |  |     if (m_config.useNativeUiTools()) { | 
					
						
							| 
									
										
										
										
											2017-04-03 11:11:17 +02:00
										 |  |  |         m_androidTool->launchAvdManager(); | 
					
						
							|  |  |  |      } else { | 
					
						
							|  |  |  |         qCDebug(avdManagerLog) << "AVD Ui tool launch failed. UI tool not available" | 
					
						
							|  |  |  |                                << m_config.sdkToolsVersion(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-18 08:22:34 +02:00
										 |  |  | QFuture<CreateAvdInfo> AndroidAvdManager::createAvd(CreateAvdInfo info) const | 
					
						
							| 
									
										
										
										
											2017-04-03 11:11:17 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2017-07-14 19:35:48 +02:00
										 |  |  |     if (m_config.useNativeUiTools()) | 
					
						
							| 
									
										
										
										
											2017-04-03 11:11:17 +02:00
										 |  |  |         return m_androidTool->createAvd(info); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return Utils::runAsync(&createAvdCommand, m_config, info); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool AndroidAvdManager::removeAvd(const QString &name) const | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2017-07-14 19:35:48 +02:00
										 |  |  |     if (m_config.useNativeUiTools()) | 
					
						
							| 
									
										
										
										
											2017-04-03 11:11:17 +02:00
										 |  |  |         return m_androidTool->removeAvd(name); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Utils::SynchronousProcess proc; | 
					
						
							|  |  |  |     proc.setTimeoutS(5); | 
					
						
							|  |  |  |     Utils::SynchronousProcessResponse response | 
					
						
							|  |  |  |             = proc.runBlocking(m_config.avdManagerToolPath().toString(), | 
					
						
							|  |  |  |                                QStringList({"delete", "avd", "-n", name})); | 
					
						
							|  |  |  |     return response.result == Utils::SynchronousProcessResponse::Finished && response.exitCode == 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | QFuture<AndroidDeviceInfoList> AndroidAvdManager::avdList() const | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2017-07-14 19:35:48 +02:00
										 |  |  |     if (m_config.useNativeUiTools()) | 
					
						
							| 
									
										
										
										
											2017-04-03 11:11:17 +02:00
										 |  |  |         return m_androidTool->androidVirtualDevicesFuture(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return Utils::runAsync(&AvdManagerOutputParser::listVirtualDevices, m_parser.get(), m_config); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | QString AndroidAvdManager::startAvd(const QString &name) const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (!findAvd(name).isEmpty() || startAvdAsync(name)) | 
					
						
							|  |  |  |         return waitForAvd(name); | 
					
						
							|  |  |  |     return QString(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool AndroidAvdManager::startAvdAsync(const QString &avdName) const | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2018-04-18 12:23:02 +02:00
										 |  |  |     QFileInfo info(m_config.emulatorToolPath().toString()); | 
					
						
							|  |  |  |     if (!info.exists()) { | 
					
						
							|  |  |  |         QMessageBox::critical(Core::ICore::dialogParent(), | 
					
						
							|  |  |  |                               tr("Emulator Tool Is Missing"), | 
					
						
							|  |  |  |                               tr("Install the missing emulator tool (%1) to the" | 
					
						
							|  |  |  |                                  " installed Android SDK.") | 
					
						
							|  |  |  |                               .arg(m_config.emulatorToolPath().toString())); | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-07-25 12:19:15 +02:00
										 |  |  |     auto avdProcess = new QProcess(); | 
					
						
							| 
									
										
										
										
											2018-05-18 15:05:46 +02:00
										 |  |  |     avdProcess->setReadChannelMode(QProcess::MergedChannels); | 
					
						
							| 
									
										
										
										
											2018-04-19 09:18:42 +02:00
										 |  |  |     QObject::connect(avdProcess, | 
					
						
							|  |  |  |                      static_cast<void (QProcess::*)(int)>(&QProcess::finished), | 
					
						
							|  |  |  |                      avdProcess, | 
					
						
							|  |  |  |                      std::bind(&avdProcessFinished, std::placeholders::_1, avdProcess)); | 
					
						
							| 
									
										
										
										
											2017-04-03 11:11:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // start the emulator
 | 
					
						
							|  |  |  |     QStringList arguments; | 
					
						
							|  |  |  |     if (AndroidConfigurations::force32bitEmulator()) | 
					
						
							|  |  |  |         arguments << "-force-32bit"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     arguments << "-partition-size" << QString::number(m_config.partitionSize()) | 
					
						
							|  |  |  |               << "-avd" << avdName; | 
					
						
							|  |  |  |     avdProcess->start(m_config.emulatorToolPath().toString(), arguments); | 
					
						
							|  |  |  |     if (!avdProcess->waitForStarted(-1)) { | 
					
						
							|  |  |  |         delete avdProcess; | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | QString AndroidAvdManager::findAvd(const QString &avdName) const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     QVector<AndroidDeviceInfo> devices = m_config.connectedDevices(); | 
					
						
							|  |  |  |     foreach (AndroidDeviceInfo device, devices) { | 
					
						
							|  |  |  |         if (device.type != AndroidDeviceInfo::Emulator) | 
					
						
							|  |  |  |             continue; | 
					
						
							|  |  |  |         if (device.avdname == avdName) | 
					
						
							|  |  |  |             return device.serialNumber; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return QString(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | QString AndroidAvdManager::waitForAvd(const QString &avdName, const QFutureInterface<bool> &fi) const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     // we cannot use adb -e wait-for-device, since that doesn't work if a emulator is already running
 | 
					
						
							|  |  |  |     // 60 rounds of 2s sleeping, two minutes for the avd to start
 | 
					
						
							|  |  |  |     QString serialNumber; | 
					
						
							|  |  |  |     for (int i = 0; i < 60; ++i) { | 
					
						
							|  |  |  |         if (fi.isCanceled()) | 
					
						
							|  |  |  |             return QString(); | 
					
						
							|  |  |  |         serialNumber = findAvd(avdName); | 
					
						
							|  |  |  |         if (!serialNumber.isEmpty()) | 
					
						
							|  |  |  |             return waitForBooted(serialNumber, fi) ?  serialNumber : QString(); | 
					
						
							|  |  |  |         QThread::sleep(2); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return QString(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool AndroidAvdManager::isAvdBooted(const QString &device) const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     QStringList arguments = AndroidDeviceInfo::adbSelector(device); | 
					
						
							|  |  |  |     arguments << "shell" << "getprop" << "init.svc.bootanim"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Utils::SynchronousProcess adbProc; | 
					
						
							|  |  |  |     adbProc.setTimeoutS(10); | 
					
						
							|  |  |  |     Utils::SynchronousProcessResponse response = | 
					
						
							|  |  |  |             adbProc.runBlocking(m_config.adbToolPath().toString(), arguments); | 
					
						
							|  |  |  |     if (response.result != Utils::SynchronousProcessResponse::Finished) | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |     QString value = response.allOutput().trimmed(); | 
					
						
							|  |  |  |     return value == "stopped"; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool AndroidAvdManager::waitForBooted(const QString &serialNumber, const QFutureInterface<bool> &fi) const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     // found a serial number, now wait until it's done booting...
 | 
					
						
							|  |  |  |     for (int i = 0; i < 60; ++i) { | 
					
						
							|  |  |  |         if (fi.isCanceled()) | 
					
						
							|  |  |  |             return false; | 
					
						
							|  |  |  |         if (isAvdBooted(serialNumber)) { | 
					
						
							|  |  |  |             return true; | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             QThread::sleep(2); | 
					
						
							|  |  |  |             if (!m_config.isConnected(serialNumber)) // device was disconnected
 | 
					
						
							|  |  |  |                 return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | AndroidDeviceInfoList AvdManagerOutputParser::listVirtualDevices(const AndroidConfig &config) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     QString output; | 
					
						
							|  |  |  |     if (!avdManagerCommand(config, QStringList({"list", "avd"}), &output)) { | 
					
						
							|  |  |  |         qCDebug(avdManagerLog) << "Avd list command failed" << output << config.sdkToolsVersion(); | 
					
						
							|  |  |  |         return {}; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return parseAvdList(output); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | AndroidDeviceInfoList AvdManagerOutputParser::parseAvdList(const QString &output) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     AndroidDeviceInfoList avdList; | 
					
						
							|  |  |  |     QStringList avdInfo; | 
					
						
							|  |  |  |     auto parseAvdInfo = [&avdInfo, &avdList, this] () { | 
					
						
							|  |  |  |         AndroidDeviceInfo avd; | 
					
						
							|  |  |  |         if (parseAvd(avdInfo, &avd)) { | 
					
						
							|  |  |  |             // armeabi-v7a devices can also run armeabi code
 | 
					
						
							|  |  |  |             if (avd.cpuAbi.contains("armeabi-v7a")) | 
					
						
							|  |  |  |                 avd.cpuAbi << "armeabi"; | 
					
						
							|  |  |  |             avd.state = AndroidDeviceInfo::OkState; | 
					
						
							|  |  |  |             avd.type = AndroidDeviceInfo::Emulator; | 
					
						
							|  |  |  |             avdList << avd; | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             qCDebug(avdManagerLog) << "Avd Parsing: Parsing failed: " << avdInfo; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         avdInfo.clear(); | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     foreach (QString line,  output.split('\n')) { | 
					
						
							|  |  |  |         if (line.startsWith("---------") || line.isEmpty()) { | 
					
						
							|  |  |  |             parseAvdInfo(); | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             avdInfo << line; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!avdInfo.isEmpty()) | 
					
						
							|  |  |  |         parseAvdInfo(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Utils::sort(avdList); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return avdList; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool AvdManagerOutputParser::parseAvd(const QStringList &deviceInfo, AndroidDeviceInfo *avd) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     QTC_ASSERT(avd, return false); | 
					
						
							|  |  |  |     foreach (const QString &line, deviceInfo) { | 
					
						
							|  |  |  |         QString value; | 
					
						
							|  |  |  |         if (valueForKey(avdInfoErrorKey, line)) { | 
					
						
							|  |  |  |             qCDebug(avdManagerLog) << "Avd Parsing: Skip avd device. Error key found:" << line; | 
					
						
							|  |  |  |             return false; | 
					
						
							|  |  |  |         } else if (valueForKey(avdInfoNameKey, line, &value)) { | 
					
						
							|  |  |  |             avd->avdname = value; | 
					
						
							|  |  |  |         } else if (valueForKey(avdInfoPathKey, line, &value)) { | 
					
						
							|  |  |  |             const Utils::FileName avdPath = Utils::FileName::fromString(value); | 
					
						
							|  |  |  |             if (avdPath.exists()) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 // Get ABI.
 | 
					
						
							|  |  |  |                 Utils::FileName configFile = avdPath; | 
					
						
							|  |  |  |                 configFile.appendPath("config.ini"); | 
					
						
							|  |  |  |                 QSettings config(configFile.toString(), QSettings::IniFormat); | 
					
						
							|  |  |  |                 value = config.value(avdInfoAbiKey).toString(); | 
					
						
							|  |  |  |                 if (!value.isEmpty()) | 
					
						
							|  |  |  |                     avd->cpuAbi << value; | 
					
						
							|  |  |  |                 else | 
					
						
							|  |  |  |                    qCDebug(avdManagerLog) << "Avd Parsing: Cannot find ABI:" << configFile; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 // Get Target
 | 
					
						
							|  |  |  |                 Utils::FileName avdInfoFile = avdPath.parentDir(); | 
					
						
							|  |  |  |                 QString avdInfoFileName = avdPath.toFileInfo().baseName() + ".ini"; | 
					
						
							|  |  |  |                 avdInfoFile.appendPath(avdInfoFileName); | 
					
						
							|  |  |  |                 QSettings avdInfo(avdInfoFile.toString(), QSettings::IniFormat); | 
					
						
							|  |  |  |                 value = avdInfo.value(avdInfoTargetKey).toString(); | 
					
						
							|  |  |  |                 if (!value.isEmpty()) | 
					
						
							|  |  |  |                     avd->sdk = value.section('-', -1).toInt(); | 
					
						
							|  |  |  |                 else | 
					
						
							|  |  |  |                    qCDebug(avdManagerLog) << "Avd Parsing: Cannot find sdk API:" << avdInfoFile.toString(); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } // namespace Internal
 | 
					
						
							|  |  |  | } // namespace Android
 |