Android: Make fixes to androidqmlpreview

This amends 261a39cbbd with fixes
to issues noticed after merging the initial patch.

Change-Id: I5f859374cbba3a2e020e6ca0789cc2b387d2739a
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
This commit is contained in:
Assam Boudjelthia
2021-09-23 17:11:19 +03:00
parent b1d5abd8eb
commit b7c15d4d8d
2 changed files with 184 additions and 210 deletions

View File

@@ -23,12 +23,13 @@
** **
****************************************************************************/ ****************************************************************************/
#include "androidqmlpreviewworker.h"
#include "androidavdmanager.h" #include "androidavdmanager.h"
#include "androiddevice.h" #include "androiddevice.h"
#include "androiddeviceinfo.h" #include "androiddeviceinfo.h"
#include "androidglobal.h" #include "androidglobal.h"
#include "androidmanager.h" #include "androidmanager.h"
#include "androidqmlpreviewworker.h"
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
@@ -44,10 +45,10 @@
#include <qtsupport/baseqtversion.h> #include <qtsupport/baseqtversion.h>
#include <qtsupport/qtkitinformation.h> #include <qtsupport/qtkitinformation.h>
#include <utils/runextensions.h>
#include <QDateTime>
#include <QThread> #include <QThread>
#include <QTemporaryDir>
#include <QImageReader>
#include <QtConcurrent>
namespace Android { namespace Android {
namespace Internal { namespace Internal {
@@ -80,173 +81,143 @@ ApkInfo::ApkInfo() :
Q_GLOBAL_STATIC(ApkInfo, apkInfo) Q_GLOBAL_STATIC(ApkInfo, apkInfo)
const char packageSuffix[] = ".qmlrc"; static const char packageSuffix[] = ".qmlrc";
static inline bool isMainThread() FilePath AndroidQmlPreviewWorker::designViewerApkPath(const QString &abi) const
{ {
return QCoreApplication::instance()->thread() == QThread::currentThread(); if (abi.isEmpty())
}
static FilePath viewerApkPath(const QString &avdAbi)
{
if (avdAbi.isEmpty())
return {}; return {};
if (apkInfo()->abis.contains(avdAbi)) if (apkInfo()->abis.contains(abi)) {
return Core::ICore::resourcePath(QString("android/qtdesignviewer/designviewer_%1.apk"). return Core::ICore::resourcePath(QString("android/qtdesignviewer/designviewer_%1.apk")
arg(avdAbi)); .arg(abi));
}
return {}; return {};
} }
static SdkToolResult runAdbCommandAsyncAndWait(const QString &dev, const QStringList &arguments) SdkToolResult AndroidQmlPreviewWorker::runAdbCommand(const QStringList &arguments) const
{ {
QStringList args; QStringList args;
if (!dev.isEmpty()) if (!m_serialNumber.isEmpty())
args << AndroidDeviceInfo::adbSelector(dev); args << AndroidDeviceInfo::adbSelector(m_serialNumber);
args << arguments;
QFuture<SdkToolResult> asyncResult = QtConcurrent::run([args] {
return AndroidManager::runAdbCommand(args);});
while (asyncResult.isRunning()) {
QCoreApplication::instance()->processEvents(QEventLoop::AllEvents, 100);
}
return asyncResult.result();
}
static SdkToolResult runAdbCommand(const QString &dev, const QStringList &arguments)
{
if (isMainThread())
return runAdbCommandAsyncAndWait(dev, arguments);
QStringList args;
if (!dev.isEmpty())
args << AndroidDeviceInfo::adbSelector(dev);
args << arguments; args << arguments;
return AndroidManager::runAdbCommand(args); return AndroidManager::runAdbCommand(args);
} }
static SdkToolResult runAdbShellCommand(const QString &dev, const QStringList &arguments) SdkToolResult AndroidQmlPreviewWorker::runAdbShellCommand(const QStringList &arguments) const
{ {
const QStringList shellCmd{"shell"}; return runAdbCommand(QStringList() << "shell" << arguments);
return runAdbCommand(dev, shellCmd + arguments);
} }
static QString startAvd(const AndroidAvdManager &avd, const QString &name) int AndroidQmlPreviewWorker::pidofPreview() const
{
QFuture<QString> asyncRes = QtConcurrent::run([avd, name] {
return avd.startAvd(name);
});
while (asyncRes.isRunning())
QCoreApplication::instance()->processEvents(QEventLoop::AllEvents, 100);
return asyncRes.result();
}
static int pidofPreview(const QString &dev)
{ {
const QStringList command{"pidof", apkInfo()->appId}; const QStringList command{"pidof", apkInfo()->appId};
const SdkToolResult res = runAdbShellCommand(dev, command); const SdkToolResult res = runAdbShellCommand(command);
return res.success() ? res.stdOut().toInt() : -1; return res.success() ? res.stdOut().toInt() : -1;
} }
static bool isPreviewRunning(const QString &dev, int lastKnownPid = -1) bool AndroidQmlPreviewWorker::isPreviewRunning(int lastKnownPid) const
{ {
const int pid = pidofPreview(dev); const int pid = pidofPreview();
return (lastKnownPid > 1) ? lastKnownPid == pid : pid > 1; return (lastKnownPid > 1) ? lastKnownPid == pid : pid > 1;
} }
AndroidQmlPreviewWorker::AndroidQmlPreviewWorker(ProjectExplorer::RunControl *runControl) void AndroidQmlPreviewWorker::startPidWatcher()
: ProjectExplorer::RunWorker(runControl)
, m_rc(runControl)
, m_config(AndroidConfigurations::currentConfig())
{ {
} m_pidFutureWatcher.setFuture(Utils::runAsync([this]() {
// wait for started
QStringList filterAppLog(const QStringList& oldList, const QStringList& newList) const int sleepTimeMs = 2000;
{ QDeadlineTimer deadline(20000);
QStringList list = Utils::filtered(newList, while (!m_pidFutureWatcher.isCanceled() && !deadline.hasExpired()) {
[](const auto & arg){return arg.contains(apkInfo()->name);});
for (const auto &oldEntry : oldList) {
list.removeAll(oldEntry);
}
return list;
}
void AndroidQmlPreviewWorker::start()
{
UploadInfo transfer;
const bool res = ensureAvdIsRunning()
&& checkAndInstallPreviewApp()
&& prepareUpload(transfer)
&& uploadFiles(transfer)
&& runPreviewApp(transfer);
if (!res) {
reportFailure();
return;
}
reportStarted();
//Thread to monitor preview life
QtConcurrent::run([this]() {
QElapsedTimer timer;
timer.start();
while (runControl() && runControl()->isRunning()) {
if (m_viewerPid == -1) { if (m_viewerPid == -1) {
m_viewerPid = pidofPreview(m_devInfo.serialNumber); m_viewerPid = pidofPreview();
if (m_viewerPid > 0) if (m_viewerPid > 0) {
QMetaObject::invokeMethod(this, &AndroidQmlPreviewWorker::startLogcat); emit previewPidChanged();
} else if (timer.elapsed() > 2000) { break;
//Get the application output }
if (!isPreviewRunning(m_devInfo.serialNumber, m_viewerPid))
QMetaObject::invokeMethod(this, &AndroidQmlPreviewWorker::stop);
timer.restart();
} }
QThread::msleep(100); QThread::msleep(sleepTimeMs);
} }
});
while (!m_pidFutureWatcher.isCanceled()) {
if (!isPreviewRunning(m_viewerPid)) {
stop();
break;
}
QThread::msleep(sleepTimeMs);
}
}));
} }
void AndroidQmlPreviewWorker::startLogcat() void AndroidQmlPreviewWorker::startLogcat()
{ {
QtConcurrent::run([this]() { QString args = QString("logcat --pid=%1").arg(m_viewerPid);
QElapsedTimer timer; if (!m_logcatStartTimeStamp.isEmpty())
timer.start(); args += QString(" -T '%1'").arg(m_logcatStartTimeStamp);
int initialPid = m_viewerPid; // to check if our initial process is still alive Utils::CommandLine cmd(AndroidConfigurations::currentConfig().adbToolPath());
QStringList logLines; cmd.setArguments(args);
auto appendLogLinesCall = [&logLines, this](){ appendLogLines(logLines); }; m_logcatProcess.setCommand(cmd);
auto runCondition = [this, initialPid](){ return (runControl() && runControl()->isRunning()) m_logcatProcess.setUseCtrlCStub(true);
&& initialPid == m_viewerPid;}; m_logcatProcess.start();
QString timeFilter; }
while (runCondition()) {
if (timer.elapsed() > 2000) {
//Get the application output
QStringList logcatCmd = {"logcat", QString("--pid=%1").arg(initialPid), "-t"};
if (!timeFilter.isEmpty())
logcatCmd.append(QString("%1").arg(timeFilter));
else
logcatCmd.append(QString("1000")); //show last 1000 lines (but for the 1st time)
const SdkToolResult logcatResult = runAdbCommand(m_devInfo.serialNumber, logcatCmd); void AndroidQmlPreviewWorker::filterLogcatAndAppendMessage(const QString &stdOut)
if (runCondition()) { {
const QStringList output = logcatResult.stdOut().split('\n'); for (const QString &line : stdOut.split('\n')) {
const QStringList filtered = filterAppLog(logLines, output); QStringList splittedLine = line.split(QLatin1String("%1: ").arg(apkInfo()->name));
if (splittedLine.count() == 1)
continue;
if (!filtered.isEmpty()){ const QString outLine = splittedLine.last();
const QString lastLine = filtered.last(); const QString firstPart = splittedLine.first();
timeFilter = lastLine.left(lastLine.indexOf(" ", lastLine.indexOf(" ") + 1)); if (firstPart.contains(" I ") || firstPart.contains(" D "))
QMetaObject::invokeMethod(this, appendLogLinesCall); appendMessage(outLine, NormalMessageFormat);
logLines = filtered; else
} appendMessage(outLine, ErrorMessageFormat);
} }
timer.restart(); }
}
QThread::msleep(100); AndroidQmlPreviewWorker::AndroidQmlPreviewWorker(ProjectExplorer::RunControl *runControl)
} : ProjectExplorer::RunWorker(runControl),
m_rc(runControl),
m_androidConfig(AndroidConfigurations::currentConfig())
{
connect(this, &RunWorker::started, this, &AndroidQmlPreviewWorker::startPidWatcher);
connect(this, &RunWorker::stopped, &m_pidFutureWatcher, &QFutureWatcher<void>::cancel);
connect(this, &AndroidQmlPreviewWorker::previewPidChanged,
this, &AndroidQmlPreviewWorker::startLogcat);
connect(this, &RunWorker::stopped, &m_logcatProcess, &Utils::QtcProcess::stopProcess);
m_logcatProcess.setStdOutCallback([this](const QString &stdOut) {
filterLogcatAndAppendMessage(stdOut);
}); });
} }
AndroidQmlPreviewWorker::~AndroidQmlPreviewWorker()
{
m_pidFutureWatcher.cancel();
m_pidFutureWatcher.waitForFinished();
}
void AndroidQmlPreviewWorker::start()
{
const SdkToolResult dateResult = runAdbCommand({"shell", "date", "+%s"});
if (dateResult.success()) {
m_logcatStartTimeStamp = QDateTime::fromSecsSinceEpoch(dateResult.stdOut().toInt())
.toString("MM-dd hh:mm:ss.mmm");
}
const bool previewStarted = ensureAvdIsRunning()
&& checkAndInstallPreviewApp()
&& uploadPreviewArtefacts()
&& preparePreviewArtefacts()
&& startPreviewApp();
previewStarted ? reportStarted() : reportStopped();
}
void AndroidQmlPreviewWorker::stop() void AndroidQmlPreviewWorker::stop()
{ {
if (!isPreviewRunning(m_devInfo.serialNumber, m_viewerPid) || stopPreviewApp()) if (!isPreviewRunning(m_viewerPid) || stopPreviewApp())
appendMessage(tr("%1 has been stopped.").arg(apkInfo()->name), NormalMessageFormat); appendMessage(tr("%1 has been stopped.").arg(apkInfo()->name), NormalMessageFormat);
m_viewerPid = -1; m_viewerPid = -1;
reportStopped(); reportStopped();
@@ -254,28 +225,35 @@ void AndroidQmlPreviewWorker::stop()
bool AndroidQmlPreviewWorker::ensureAvdIsRunning() bool AndroidQmlPreviewWorker::ensureAvdIsRunning()
{ {
AndroidAvdManager avdMan(m_config); AndroidAvdManager avdMananager(m_androidConfig);
QString devSN = AndroidManager::deviceSerialNumber(m_rc->target()); QString devSN = AndroidManager::deviceSerialNumber(m_rc->target());
if (devSN.isEmpty()) if (devSN.isEmpty())
devSN = m_devInfo.serialNumber; devSN = m_serialNumber;
if (!avdMan.isAvdBooted(devSN)) { if (!avdMananager.isAvdBooted(devSN)) {
m_devInfo = {};
using namespace ProjectExplorer; using namespace ProjectExplorer;
const IDevice *dev = DeviceKitAspect::device(m_rc->target()->kit()).data(); const IDevice *dev = DeviceKitAspect::device(m_rc->target()->kit()).data();
if (!dev) {
appendMessage(tr("Selected device is invalid."), ErrorMessageFormat);
return false;
}
if (dev->deviceState() == IDevice::DeviceDisconnected) {
appendMessage(tr("Selected device is disconnected."), ErrorMessageFormat);
return false;
}
AndroidDeviceInfo devInfoLocal = AndroidDevice::androidDeviceInfoFromIDevice(dev); AndroidDeviceInfo devInfoLocal = AndroidDevice::androidDeviceInfoFromIDevice(dev);
if (devInfoLocal.isValid()) { if (devInfoLocal.isValid()) {
if (devInfoLocal.type == AndroidDeviceInfo::Emulator) { if (dev->machineType() == IDevice::Emulator) {
appendMessage(tr("Launching AVD."), NormalMessageFormat); appendMessage(tr("Launching AVD."), NormalMessageFormat);
devInfoLocal.serialNumber = startAvd(avdMan, devInfoLocal.avdname); devInfoLocal.serialNumber = avdMananager.startAvd(devInfoLocal.avdname);
} }
if (devInfoLocal.serialNumber.isEmpty()) { if (devInfoLocal.serialNumber.isEmpty()) {
appendMessage(tr("Could not run AVD."), ErrorMessageFormat); appendMessage(tr("Could not start AVD."), ErrorMessageFormat);
} else { } else {
m_devInfo = devInfoLocal; m_serialNumber = devInfoLocal.serialNumber;
m_avdAbis = m_config.getAbis(m_config.adbToolPath(), m_devInfo.serialNumber); m_avdAbis = m_androidConfig.getAbis(m_androidConfig.adbToolPath(), m_serialNumber);
} }
return !devInfoLocal.serialNumber.isEmpty(); return !devInfoLocal.serialNumber.isEmpty();
} else { } else {
@@ -283,7 +261,7 @@ bool AndroidQmlPreviewWorker::ensureAvdIsRunning()
} }
return false; return false;
} }
m_avdAbis = m_config.getAbis(m_config.adbToolPath(), m_devInfo.serialNumber); m_avdAbis = m_androidConfig.getAbis(m_androidConfig.adbToolPath(), m_serialNumber);
return true; return true;
} }
@@ -291,7 +269,7 @@ bool AndroidQmlPreviewWorker::checkAndInstallPreviewApp()
{ {
const QStringList command {"pm", "list", "packages", apkInfo()->appId}; const QStringList command {"pm", "list", "packages", apkInfo()->appId};
appendMessage(tr("Checking if %1 app is installed.").arg(apkInfo()->name), NormalMessageFormat); appendMessage(tr("Checking if %1 app is installed.").arg(apkInfo()->name), NormalMessageFormat);
const SdkToolResult res = runAdbShellCommand(m_devInfo.serialNumber, command); const SdkToolResult res = runAdbShellCommand(command);
if (!res.success()) { if (!res.success()) {
appendMessage(res.stdErr(), ErrorMessageFormat); appendMessage(res.stdErr(), ErrorMessageFormat);
return false; return false;
@@ -303,7 +281,7 @@ bool AndroidQmlPreviewWorker::checkAndInstallPreviewApp()
ErrorMessageFormat); ErrorMessageFormat);
return false; return false;
} }
const FilePath apkPath = viewerApkPath(m_avdAbis.first()); const FilePath apkPath = designViewerApkPath(m_avdAbis.first());
if (!apkPath.exists()) { if (!apkPath.exists()) {
appendMessage(tr("Cannot install %1 app for %2 architecture. " appendMessage(tr("Cannot install %1 app for %2 architecture. "
"The appropriate APK was not found in resources folders."). "The appropriate APK was not found in resources folders.").
@@ -313,32 +291,29 @@ bool AndroidQmlPreviewWorker::checkAndInstallPreviewApp()
appendMessage(tr("Installing %1 APK.").arg(apkInfo()->name), NormalMessageFormat); appendMessage(tr("Installing %1 APK.").arg(apkInfo()->name), NormalMessageFormat);
const SdkToolResult res = runAdbCommand({"install", apkPath.toString()});
const SdkToolResult res = runAdbCommand(m_devInfo.serialNumber, {"install", if (!res.success())
apkPath.toString()});
if (!res.success()) {
appendMessage(res.stdErr(), StdErrFormat); appendMessage(res.stdErr(), StdErrFormat);
return false;
}
} }
return true;
return res.success();
} }
bool AndroidQmlPreviewWorker::prepareUpload(UploadInfo &transfer) bool AndroidQmlPreviewWorker::preparePreviewArtefacts()
{ {
if (m_rc->project()->id() == QmlProjectManager::Constants::QML_PROJECT_ID) { if (m_rc->project()->id() == QmlProjectManager::Constants::QML_PROJECT_ID) {
const auto bs = m_rc->target()->buildSystem(); const auto bs = m_rc->target()->buildSystem();
if (bs) { if (bs) {
transfer.uploadPackage = FilePath::fromString( m_uploadInfo.uploadPackage = FilePath::fromString(
bs->additionalData(QmlProjectManager::Constants::mainFilePath).toString()); bs->additionalData(QmlProjectManager::Constants::mainFilePath).toString());
transfer.projectFolder = bs->projectDirectory(); m_uploadInfo.projectFolder = bs->projectDirectory();
return true; return true;
} }
} else { } else {
const FilePaths allFiles = m_rc->project()->files(m_rc->project()->SourceFiles); const FilePaths allFiles = m_rc->project()->files(m_rc->project()->SourceFiles);
const FilePaths filesToExport = Utils::filtered(allFiles,[](const FilePath &path) { const FilePaths filesToExport = Utils::filtered(allFiles,[](const FilePath &path) {
return path.suffix() == "qmlproject";}); return path.suffix() == "qmlproject";
});
if (filesToExport.size() > 1) { if (filesToExport.size() > 1) {
appendMessage(tr("Too many .qmlproject files in your project. Open directly the " appendMessage(tr("Too many .qmlproject files in your project. Open directly the "
@@ -348,10 +323,9 @@ bool AndroidQmlPreviewWorker::prepareUpload(UploadInfo &transfer)
appendMessage(tr("No .qmlproject file found among project files."), ErrorMessageFormat); appendMessage(tr("No .qmlproject file found among project files."), ErrorMessageFormat);
} else { } else {
const FilePath qmlprojectFile = filesToExport.first(); const FilePath qmlprojectFile = filesToExport.first();
transfer.uploadPackage = transfer. m_uploadInfo.uploadPackage = m_uploadInfo.projectFolder.resolvePath(
projectFolder. qmlprojectFile.fileName());
resolvePath(qmlprojectFile.fileName()); m_uploadInfo.projectFolder = qmlprojectFile.parentDir();
transfer.projectFolder = qmlprojectFile.parentDir();
return true; return true;
} }
} }
@@ -366,16 +340,15 @@ FilePath AndroidQmlPreviewWorker::createQmlrcFile(const FilePath &workFolder,
const FilePath rccBinary = qtVersion->rccFilePath(); const FilePath rccBinary = qtVersion->rccFilePath();
QtcProcess rccProcess; QtcProcess rccProcess;
FilePath qrcPath = FilePath::fromString(basename) + ".qrc4viewer"; FilePath qrcPath = FilePath::fromString(basename) + ".qrc4viewer";
const FilePath qmlrcPath = FilePath::fromString(QDir::tempPath() + "/" + basename + const FilePath qmlrcPath = FilePath::fromString(QDir::tempPath()) / basename + packageSuffix;
packageSuffix);
rccProcess.setWorkingDirectory(workFolder); rccProcess.setWorkingDirectory(workFolder);
const QStringList arguments[2] = {{"--project", "--output", qrcPath.fileName()}, const QStringList arguments[2] = {{"--project", "--output", qrcPath.fileName()},
{"--binary", "--output", qmlrcPath.path(), {"--binary", "--output", qmlrcPath.path(),
qrcPath.fileName()}}; qrcPath.fileName()}};
for (const auto &arguments : arguments) { for (const QStringList &args : arguments) {
rccProcess.setCommand({rccBinary, arguments}); rccProcess.setCommand({rccBinary, args});
rccProcess.start(); rccProcess.start();
if (!rccProcess.waitForStarted()) { if (!rccProcess.waitForStarted()) {
appendMessage(tr("Could not create file for %1 \"%2\""). appendMessage(tr("Could not create file for %1 \"%2\"").
@@ -420,71 +393,54 @@ FilePath AndroidQmlPreviewWorker::createQmlrcFile(const FilePath &workFolder,
return qmlrcPath; return qmlrcPath;
} }
bool AndroidQmlPreviewWorker::uploadFiles(const UploadInfo &transfer) bool AndroidQmlPreviewWorker::uploadPreviewArtefacts()
{ {
appendMessage(tr("Uploading files."), NormalMessageFormat); appendMessage(tr("Uploading files."), NormalMessageFormat);
const FilePath qresPath = createQmlrcFile(m_uploadInfo.projectFolder,
const FilePath qresPath = createQmlrcFile(FilePath::fromString(transfer.projectFolder.path()), m_uploadInfo.uploadPackage.baseName());
transfer.uploadPackage.baseName());
if (!qresPath.exists()) if (!qresPath.exists())
return false; return false;
runAdbShellCommand(m_devInfo.serialNumber, {"mkdir", "-p", apkInfo()->uploadDir}); runAdbShellCommand({"mkdir", "-p", apkInfo()->uploadDir});
const SdkToolResult res = runAdbCommand({"push", qresPath.resolvePath(QString()).toString(),
const SdkToolResult res = runAdbCommand(m_devInfo.serialNumber, apkInfo()->uploadDir});
{"push", qresPath.resolvePath(QString()).toString(),
apkInfo()->uploadDir});
if (!res.success()) { if (!res.success()) {
appendMessage(res.stdOut(), ErrorMessageFormat); appendMessage(res.stdOut(), ErrorMessageFormat);
if (res.stdOut().contains("Permission denied")) if (res.stdOut().contains("Permission denied")) {
appendMessage("'Permission denied' error detected. Try restarting your device " appendMessage("'Permission denied' error detected. Try restarting your device "
"and then running the preview.", NormalMessageFormat); "and then running the preview.", NormalMessageFormat);
}
} }
qresPath.removeFile(); qresPath.removeFile();
return res.success(); return res.success();
} }
bool AndroidQmlPreviewWorker::runPreviewApp(const UploadInfo &transfer) bool AndroidQmlPreviewWorker::startPreviewApp()
{ {
stopPreviewApp(); stopPreviewApp();
appendMessage(tr("Starting %1.").arg(apkInfo()->name), NormalMessageFormat); appendMessage(tr("Starting %1.").arg(apkInfo()->name), NormalMessageFormat);
const QDir destDir(apkInfo()->uploadDir); const QDir destDir(apkInfo()->uploadDir);
const QString qmlrcPath = destDir.filePath(m_uploadInfo.uploadPackage.baseName()
+ packageSuffix);
const QStringList command{"am", "start", const QStringList command{"am", "start",
"-n", apkInfo()->activityId, "-n", apkInfo()->activityId,
"-e", "extraappparams", "-e", "extraappparams", QLatin1String(qmlrcPath.toUtf8().toBase64())};
QString::fromLatin1( const SdkToolResult result = runAdbShellCommand(command);
destDir.filePath(transfer.uploadPackage.baseName() + packageSuffix). if (result.success())
toUtf8(). appendMessage(tr("%1 is running.").arg(apkInfo()->name), NormalMessageFormat);
toBase64())}; else
const SdkToolResult res = runAdbShellCommand(m_devInfo.serialNumber, command); appendMessage(result.stdErr(), ErrorMessageFormat);
if (!res.success()) {
appendMessage(res.stdErr(), ErrorMessageFormat); return result.success();
return res.success();
}
appendMessage(tr("%1 is running.").arg(apkInfo()->name), NormalMessageFormat);
m_viewerPid = pidofPreview(m_devInfo.serialNumber);
return true;
} }
bool AndroidQmlPreviewWorker::stopPreviewApp() bool AndroidQmlPreviewWorker::stopPreviewApp()
{ {
const QStringList command{"am", "force-stop", apkInfo()->appId}; const QStringList command{"am", "force-stop", apkInfo()->appId};
const SdkToolResult res = runAdbShellCommand(m_devInfo.serialNumber, command); const SdkToolResult res = runAdbShellCommand(command);
if (!res.success()) { if (!res.success())
appendMessage(res.stdErr(), ErrorMessageFormat); appendMessage(res.stdErr(), ErrorMessageFormat);
return res.success(); return res.success();
}
return true;
}
void AndroidQmlPreviewWorker::appendLogLines(const QStringList & lines)
{
for (const QString& line : lines) {
const int charsToSkip = apkInfo()->name.length() + 2; // strlen(": ") == 2
const QString formatted = line.mid(line.indexOf(apkInfo()->name) + charsToSkip);
// TODO: See AndroidRunnerWorker::logcatProcess() - filtering for logs to decide format.
appendMessage(formatted, StdOutFormat);
}
} }
} // namespace Internal } // namespace Internal

View File

@@ -25,9 +25,12 @@
#pragma once #pragma once
#include "androidconfigurations.h" #include "androidconfigurations.h"
#include <projectexplorer/runcontrol.h> #include <projectexplorer/runcontrol.h>
#include <utils/environment.h> #include <utils/environment.h>
#include <QFutureWatcher>
namespace Android { namespace Android {
class SdkToolResult; class SdkToolResult;
@@ -45,6 +48,10 @@ class AndroidQmlPreviewWorker : public ProjectExplorer::RunWorker
Q_OBJECT Q_OBJECT
public: public:
AndroidQmlPreviewWorker(ProjectExplorer::RunControl *runControl); AndroidQmlPreviewWorker(ProjectExplorer::RunControl *runControl);
~AndroidQmlPreviewWorker();
signals:
void previewPidChanged();
private: private:
void start() override; void start() override;
@@ -52,22 +59,33 @@ private:
bool ensureAvdIsRunning(); bool ensureAvdIsRunning();
bool checkAndInstallPreviewApp(); bool checkAndInstallPreviewApp();
bool prepareUpload(UploadInfo &transfer); bool preparePreviewArtefacts();
bool uploadFiles(const UploadInfo &transfer); bool uploadPreviewArtefacts();
bool runPreviewApp(const UploadInfo &transfer); SdkToolResult runAdbCommand(const QStringList &arguments) const;
SdkToolResult runAdbShellCommand(const QStringList &arguments) const;
int pidofPreview() const;
bool isPreviewRunning(int lastKnownPid = -1) const;
void startPidWatcher();
void startLogcat();
void filterLogcatAndAppendMessage(const QString &stdOut);
bool startPreviewApp();
bool stopPreviewApp(); bool stopPreviewApp();
void startLogcat(); Utils::FilePath designViewerApkPath(const QString &abi) const;
void appendLogLines(const QStringList &lines);
Utils::FilePath createQmlrcFile(const Utils::FilePath &workFolder, const QString &basename); Utils::FilePath createQmlrcFile(const Utils::FilePath &workFolder, const QString &basename);
ProjectExplorer::RunControl *m_rc = nullptr; ProjectExplorer::RunControl *m_rc = nullptr;
AndroidConfig m_config; AndroidConfig m_androidConfig;
AndroidDeviceInfo m_devInfo; QString m_serialNumber;
QStringList m_avdAbis; QStringList m_avdAbis;
int m_viewerPid = -1; int m_viewerPid = -1;
QFutureWatcher<void> m_pidFutureWatcher;
Utils::QtcProcess m_logcatProcess;
QString m_logcatStartTimeStamp;
UploadInfo m_uploadInfo;
}; };
} // namespace Internal } // namespace Internal