forked from qt-creator/qt-creator
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:
@@ -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);
|
QThread::msleep(sleepTimeMs);
|
||||||
|
}
|
||||||
|
|
||||||
timer.restart();
|
while (!m_pidFutureWatcher.isCanceled()) {
|
||||||
|
if (!isPreviewRunning(m_viewerPid)) {
|
||||||
|
stop();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
QThread::msleep(100);
|
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) {
|
void AndroidQmlPreviewWorker::filterLogcatAndAppendMessage(const QString &stdOut)
|
||||||
//Get the application output
|
{
|
||||||
QStringList logcatCmd = {"logcat", QString("--pid=%1").arg(initialPid), "-t"};
|
for (const QString &line : stdOut.split('\n')) {
|
||||||
if (!timeFilter.isEmpty())
|
QStringList splittedLine = line.split(QLatin1String("%1: ").arg(apkInfo()->name));
|
||||||
logcatCmd.append(QString("%1").arg(timeFilter));
|
if (splittedLine.count() == 1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const QString outLine = splittedLine.last();
|
||||||
|
const QString firstPart = splittedLine.first();
|
||||||
|
if (firstPart.contains(" I ") || firstPart.contains(" D "))
|
||||||
|
appendMessage(outLine, NormalMessageFormat);
|
||||||
else
|
else
|
||||||
logcatCmd.append(QString("1000")); //show last 1000 lines (but for the 1st time)
|
appendMessage(outLine, ErrorMessageFormat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const SdkToolResult logcatResult = runAdbCommand(m_devInfo.serialNumber, logcatCmd);
|
AndroidQmlPreviewWorker::AndroidQmlPreviewWorker(ProjectExplorer::RunControl *runControl)
|
||||||
if (runCondition()) {
|
: ProjectExplorer::RunWorker(runControl),
|
||||||
const QStringList output = logcatResult.stdOut().split('\n');
|
m_rc(runControl),
|
||||||
const QStringList filtered = filterAppLog(logLines, output);
|
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);
|
||||||
|
|
||||||
if (!filtered.isEmpty()){
|
connect(this, &RunWorker::stopped, &m_logcatProcess, &Utils::QtcProcess::stopProcess);
|
||||||
const QString lastLine = filtered.last();
|
m_logcatProcess.setStdOutCallback([this](const QString &stdOut) {
|
||||||
timeFilter = lastLine.left(lastLine.indexOf(" ", lastLine.indexOf(" ") + 1));
|
filterLogcatAndAppendMessage(stdOut);
|
||||||
QMetaObject::invokeMethod(this, appendLogLinesCall);
|
|
||||||
logLines = filtered;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
timer.restart();
|
|
||||||
}
|
|
||||||
QThread::msleep(100);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AndroidQmlPreviewWorker::prepareUpload(UploadInfo &transfer)
|
return res.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
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,72 +393,55 @@ 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,
|
|
||||||
{"push", qresPath.resolvePath(QString()).toString(),
|
|
||||||
apkInfo()->uploadDir});
|
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().
|
|
||||||
toBase64())};
|
|
||||||
const SdkToolResult res = runAdbShellCommand(m_devInfo.serialNumber, command);
|
|
||||||
if (!res.success()) {
|
|
||||||
appendMessage(res.stdErr(), ErrorMessageFormat);
|
|
||||||
return res.success();
|
|
||||||
}
|
|
||||||
appendMessage(tr("%1 is running.").arg(apkInfo()->name), NormalMessageFormat);
|
appendMessage(tr("%1 is running.").arg(apkInfo()->name), NormalMessageFormat);
|
||||||
m_viewerPid = pidofPreview(m_devInfo.serialNumber);
|
else
|
||||||
return true;
|
appendMessage(result.stdErr(), ErrorMessageFormat);
|
||||||
|
|
||||||
|
return result.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
} // namespace Android
|
} // namespace Android
|
||||||
|
@@ -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
|
||||||
|
Reference in New Issue
Block a user