Implements previewing qml preview for a qmlproject

To run it requires either designpreview.apk installed
on the device or apks placed in
<QTCREATATORDIR>/share/qtcreator/android/qtdesignviewer/
Apk filename should follow designpreview_$ARCH.apk

Task-number: QAA-512
Change-Id: Ida955b0fac519112d4623166677a7ba8e9afb1f4
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
Piotr Mikolajczyk
2021-08-16 13:48:26 +02:00
parent c2aeec7e86
commit 261a39cbbd
10 changed files with 586 additions and 17 deletions

View File

@@ -27,6 +27,7 @@ add_qtc_plugin(Android
androidpackageinstallationstep.cpp androidpackageinstallationstep.h androidpackageinstallationstep.cpp androidpackageinstallationstep.h
androidplugin.cpp androidplugin.h androidplugin.cpp androidplugin.h
androidpotentialkit.cpp androidpotentialkit.h androidpotentialkit.cpp androidpotentialkit.h
androidqmlpreviewworker.cpp androidqmlpreviewworker.h
androidqmltoolingsupport.cpp androidqmltoolingsupport.h androidqmltoolingsupport.cpp androidqmltoolingsupport.h
androidqtversion.cpp androidqtversion.h androidqtversion.cpp androidqtversion.h
androidrunconfiguration.cpp androidrunconfiguration.h androidrunconfiguration.cpp androidrunconfiguration.h

View File

@@ -11,6 +11,7 @@ HEADERS += \
androidmanager.h \ androidmanager.h \
androidmanifesteditoriconcontainerwidget.h \ androidmanifesteditoriconcontainerwidget.h \
androidmanifesteditoriconwidget.h \ androidmanifesteditoriconwidget.h \
androidqmlpreviewworker.h \
androidrunconfiguration.h \ androidrunconfiguration.h \
androidruncontrol.h \ androidruncontrol.h \
androidservicewidget.h \ androidservicewidget.h \
@@ -61,6 +62,7 @@ SOURCES += \
androidmanager.cpp \ androidmanager.cpp \
androidmanifesteditoriconcontainerwidget.cpp \ androidmanifesteditoriconcontainerwidget.cpp \
androidmanifesteditoriconwidget.cpp \ androidmanifesteditoriconwidget.cpp \
androidqmlpreviewworker.cpp \
androidrunconfiguration.cpp \ androidrunconfiguration.cpp \
androidruncontrol.cpp \ androidruncontrol.cpp \
androidservicewidget.cpp \ androidservicewidget.cpp \

View File

@@ -64,6 +64,8 @@ Project {
"androidplugin.h", "androidplugin.h",
"androidpotentialkit.cpp", "androidpotentialkit.cpp",
"androidpotentialkit.h", "androidpotentialkit.h",
"androidqmlpreviewworker.h",
"androidqmlpreviewworker.cpp",
"androidqmltoolingsupport.cpp", "androidqmltoolingsupport.cpp",
"androidqmltoolingsupport.h", "androidqmltoolingsupport.h",
"androidqtversion.cpp", "androidqtversion.cpp",

View File

@@ -170,6 +170,7 @@ public:
void setOpenSslLocation(const Utils::FilePath &openSslLocation); void setOpenSslLocation(const Utils::FilePath &openSslLocation);
static Utils::FilePath getJdkPath(); static Utils::FilePath getJdkPath();
static QStringList getAbis(const Utils::FilePath &adbToolPath, const QString &device);
static QStringList getRunningAvdsFromDevices(const QVector<AndroidDeviceInfo> &devs); static QStringList getRunningAvdsFromDevices(const QVector<AndroidDeviceInfo> &devs);
@@ -179,7 +180,6 @@ private:
Utils::FilePath openJDKBinPath() const; Utils::FilePath openJDKBinPath() const;
static int getSDKVersion(const Utils::FilePath &adbToolPath, const QString &device); static int getSDKVersion(const Utils::FilePath &adbToolPath, const QString &device);
static QStringList getAbis(const Utils::FilePath &adbToolPath, const QString &device);
static QString getAvdName(const QString &serialnumber); static QString getAvdName(const QString &serialnumber);
void parseDependenciesJson(); void parseDependenciesJson();

View File

@@ -34,6 +34,7 @@
#include "androidmanifesteditorfactory.h" #include "androidmanifesteditorfactory.h"
#include "androidpackageinstallationstep.h" #include "androidpackageinstallationstep.h"
#include "androidpotentialkit.h" #include "androidpotentialkit.h"
#include "androidqmlpreviewworker.h"
#include "androidqmltoolingsupport.h" #include "androidqmltoolingsupport.h"
#include "androidqtversion.h" #include "androidqtversion.h"
#include "androidrunconfiguration.h" #include "androidrunconfiguration.h"
@@ -96,14 +97,6 @@ public:
} }
}; };
class AndroidQmlPreviewWorker : public AndroidQmlToolingSupport
{
public:
AndroidQmlPreviewWorker(RunControl *runControl)
: AndroidQmlToolingSupport(runControl, runControl->runnable().command.executable().toString())
{}
};
class AndroidPluginPrivate : public QObject class AndroidPluginPrivate : public QObject
{ {
public: public:
@@ -136,14 +129,9 @@ public:
{runConfigFactory.runConfigurationId()} {runConfigFactory.runConfigurationId()}
}; };
RunWorkerFactory qmlPreviewWorkerFactory{ RunWorkerFactory qmlPreviewWorkerFactory{
RunWorkerFactory::make<AndroidQmlToolingSupport>(),
{QML_PREVIEW_RUN_MODE},
{runConfigFactory.runConfigurationId()}
};
RunWorkerFactory qmlPreviewWorkerFactory2{
RunWorkerFactory::make<AndroidQmlPreviewWorker>(), RunWorkerFactory::make<AndroidQmlPreviewWorker>(),
{QML_PREVIEW_RUN_MODE}, {QML_PREVIEW_RUN_MODE},
{"QmlProjectManager.QmlRunConfiguration"}, {"QmlProjectManager.QmlRunConfiguration.Qml", runConfigFactory.runConfigurationId()},
{Android::Constants::ANDROID_DEVICE_TYPE} {Android::Constants::ANDROID_DEVICE_TYPE}
}; };

View File

@@ -0,0 +1,487 @@
/****************************************************************************
**
** Copyright (C) 2021 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 "androiddevice.h"
#include "androiddeviceinfo.h"
#include "androidglobal.h"
#include "androidmanager.h"
#include "androidqmlpreviewworker.h"
#include <coreplugin/icore.h>
#include <projectexplorer/buildsystem.h>
#include <projectexplorer/kit.h>
#include <projectexplorer/project.h>
#include <projectexplorer/target.h>
#include <qmlprojectmanager/qmlprojectconstants.h>
#include <qmlprojectmanager/qmlprojectmanagerconstants.h>
#include <qtsupport/baseqtversion.h>
#include <qtsupport/qtkitinformation.h>
#include <QThread>
#include <QTemporaryDir>
#include <QImageReader>
#include <QtConcurrent>
namespace Android {
namespace Internal {
using namespace Utils;
#define APP_ID "io.qt.designviewer"
class ApkInfo {
public:
ApkInfo();
const QStringList abis;
const QString appId;
const QString uploadDir;
const QString activityId;
const QString name;
};
ApkInfo::ApkInfo() :
abis({ProjectExplorer::Constants::ANDROID_ABI_X86,
ProjectExplorer::Constants::ANDROID_ABI_X86_64,
ProjectExplorer::Constants::ANDROID_ABI_ARM64_V8A,
ProjectExplorer::Constants::ANDROID_ABI_ARMEABI_V7A}),
appId(APP_ID),
uploadDir("/data/local/tmp/" APP_ID "/"),
activityId(APP_ID "/org.qtproject.qt5.android.bindings.QtActivity"),
name("Qt Design Viewer")
{
}
Q_GLOBAL_STATIC(ApkInfo, apkInfo)
const char packageSuffix[] = ".qmlrc";
static inline bool isMainThread()
{
return QCoreApplication::instance()->thread() == QThread::currentThread();
}
static FilePath viewerApkPath(const QString &avdAbi)
{
if (avdAbi.isEmpty())
return {};
if (apkInfo()->abis.contains(avdAbi))
return Core::ICore::resourcePath(QString("android/qtdesignviewer/designviewer_%1.apk").
arg(avdAbi));
return {};
}
static SdkToolResult runAdbCommandAsyncAndWait(const QString &dev, const QStringList &arguments)
{
QStringList args;
if (!dev.isEmpty())
args << AndroidDeviceInfo::adbSelector(dev);
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;
return AndroidManager::runAdbCommand(args);
}
static SdkToolResult runAdbShellCommand(const QString &dev, const QStringList &arguments)
{
const QStringList shellCmd{"shell"};
return runAdbCommand(dev, shellCmd + arguments);
}
static QString startAvd(const AndroidAvdManager &avd, const QString &name)
{
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 SdkToolResult res = runAdbShellCommand(dev, command);
return res.success() ? res.stdOut().toInt() : -1;
}
static bool isPreviewRunning(const QString &dev, int lastKnownPid = -1)
{
const int pid = pidofPreview(dev);
return (lastKnownPid > 1) ? lastKnownPid == pid : pid > 1;
}
AndroidQmlPreviewWorker::AndroidQmlPreviewWorker(ProjectExplorer::RunControl *runControl)
: ProjectExplorer::RunWorker(runControl)
, m_rc(runControl)
, m_config(AndroidConfigurations::currentConfig())
{
}
QStringList filterAppLog(const QStringList& oldList, const QStringList& newList)
{
QStringList list = Utils::filtered(newList,
[](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) {
m_viewerPid = pidofPreview(m_devInfo.serialNumber);
if (m_viewerPid > 0)
QMetaObject::invokeMethod(this, &AndroidQmlPreviewWorker::startLogcat);
} else if (timer.elapsed() > 2000) {
//Get the application output
if (!isPreviewRunning(m_devInfo.serialNumber, m_viewerPid))
QMetaObject::invokeMethod(this, &AndroidQmlPreviewWorker::stop);
timer.restart();
}
QThread::msleep(100);
}
});
}
void AndroidQmlPreviewWorker::startLogcat()
{
QtConcurrent::run([this]() {
QElapsedTimer timer;
timer.start();
int initialPid = m_viewerPid; // to check if our initial process is still alive
QStringList logLines;
auto appendLogLinesCall = [&logLines, this](){ appendLogLines(logLines); };
auto runCondition = [this, initialPid](){ return (runControl() && runControl()->isRunning())
&& initialPid == m_viewerPid;};
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);
if (runCondition()) {
const QStringList output = logcatResult.stdOut().split('\n');
const QStringList filtered = filterAppLog(logLines, output);
if (!filtered.isEmpty()){
const QString lastLine = filtered.last();
timeFilter = lastLine.left(lastLine.indexOf(" ", lastLine.indexOf(" ") + 1));
QMetaObject::invokeMethod(this, appendLogLinesCall);
logLines = filtered;
}
}
timer.restart();
}
QThread::msleep(100);
}
});
}
void AndroidQmlPreviewWorker::stop()
{
if (!isPreviewRunning(m_devInfo.serialNumber, m_viewerPid) || stopPreviewApp())
appendMessage(tr("%1 has been stopped.").arg(apkInfo()->name), NormalMessageFormat);
m_viewerPid = -1;
reportStopped();
}
bool AndroidQmlPreviewWorker::ensureAvdIsRunning()
{
AndroidAvdManager avdMan(m_config);
QString devSN = AndroidManager::deviceSerialNumber(m_rc->target());
if (devSN.isEmpty())
devSN = m_devInfo.serialNumber;
if (!avdMan.isAvdBooted(devSN)) {
m_devInfo = {};
int minTargetApi = AndroidManager::minimumSDK(m_rc->target());
AndroidDeviceInfo devInfoLocal = AndroidConfigurations::showDeviceDialog(m_rc->project(),
minTargetApi,
apkInfo()->abis);
if (devInfoLocal.isValid()) {
if (devInfoLocal.type == AndroidDeviceInfo::Emulator) {
appendMessage(tr("Launching AVD."), NormalMessageFormat);
devInfoLocal.serialNumber = startAvd(avdMan, devInfoLocal.avdname);
}
if (devInfoLocal.serialNumber.isEmpty()) {
appendMessage(tr("Could not run AVD."), ErrorMessageFormat);
} else {
m_devInfo = devInfoLocal;
m_avdAbis = m_config.getAbis(m_config.adbToolPath(), m_devInfo.serialNumber);
}
return !devInfoLocal.serialNumber.isEmpty();
} else {
appendMessage(tr("No valid AVD has been selected."), ErrorMessageFormat);
}
return false;
}
m_avdAbis = m_config.getAbis(m_config.adbToolPath(), m_devInfo.serialNumber);
return true;
}
bool AndroidQmlPreviewWorker::checkAndInstallPreviewApp()
{
const QStringList command {"pm", "list", "packages", apkInfo()->appId};
appendMessage(tr("Checking if %1 app is installed.").arg(apkInfo()->name), NormalMessageFormat);
const SdkToolResult res = runAdbShellCommand(m_devInfo.serialNumber, command);
if (!res.success()) {
appendMessage(res.stdErr(), ErrorMessageFormat);
return false;
}
if (res.stdOut().isEmpty()) {
if (m_avdAbis.isEmpty()) {
appendMessage(tr("ABI of the selected device is unknown. Cannot install APK."),
ErrorMessageFormat);
return false;
}
const FilePath apkPath = viewerApkPath(m_avdAbis.first());
if (!apkPath.exists()) {
appendMessage(tr("Cannot install %1 app for %2 architecture. "
"The appropriate APK was not found in resources folders.").
arg(apkInfo()->name, m_avdAbis.first()), ErrorMessageFormat);
return false;
}
appendMessage(tr("Installing %1 APK.").arg(apkInfo()->name), NormalMessageFormat);
const SdkToolResult res = runAdbCommand(m_devInfo.serialNumber, {"install",
apkPath.toString()});
if (!res.success()) {
appendMessage(res.stdErr(), StdErrFormat);
return false;
}
}
return true;
}
bool AndroidQmlPreviewWorker::prepareUpload(UploadInfo &transfer)
{
if (m_rc->project()->id() == QmlProjectManager::Constants::QML_PROJECT_ID) {
const auto bs = m_rc->target()->buildSystem();
if (bs) {
transfer.uploadPackage = FilePath::fromString(
bs->additionalData(QmlProjectManager::Constants::mainFilePath).toString());
transfer.projectFolder = bs->projectDirectory();
return true;
}
} else {
const FilePaths allFiles = m_rc->project()->files(m_rc->project()->SourceFiles);
const FilePaths filesToExport = Utils::filtered(allFiles,[](const FilePath &path) {
return path.suffix() == "qmlproject";});
if (filesToExport.size() > 1) {
appendMessage(tr("Too many .qmlproject files in your project. Open directly the "
".qmlproject file you want to work with and then run the preview."),
ErrorMessageFormat);
} else if (filesToExport.size() < 1) {
appendMessage(tr("No .qmlproject file found among project files."), ErrorMessageFormat);
} else {
const FilePath qmlprojectFile = filesToExport.first();
transfer.uploadPackage = transfer.
projectFolder.
resolvePath(qmlprojectFile.fileName());
transfer.projectFolder = qmlprojectFile.parentDir();
return true;
}
}
appendMessage(tr("Could not gather information on project files."), ErrorMessageFormat);
return false;
}
FilePath AndroidQmlPreviewWorker::createQmlrcFile(const FilePath &workFolder,
const QString &basename)
{
const QtSupport::BaseQtVersion *qtVersion = QtSupport::QtKitAspect::qtVersion(m_rc->kit());
const FilePath rccBinary = qtVersion->rccFilePath();
QtcProcess rccProcess;
FilePath qrcPath = FilePath::fromString(basename) + ".qrc4viewer";
const FilePath qmlrcPath = FilePath::fromString(QDir::tempPath() + "/" + basename +
packageSuffix);
rccProcess.setWorkingDirectory(workFolder);
const QStringList arguments[2] = {{"--project", "--output", qrcPath.fileName()},
{"--binary", "--output", qmlrcPath.path(),
qrcPath.fileName()}};
for (const auto &arguments : arguments) {
rccProcess.setCommand({rccBinary, arguments});
rccProcess.start();
if (!rccProcess.waitForStarted()) {
appendMessage(tr("Could not create file for %1 \"%2\"").
arg(apkInfo()->name, rccProcess.commandLine().toUserOutput()),
StdErrFormat);
qrcPath.removeFile();
return {};
}
QByteArray stdOut;
QByteArray stdErr;
if (!rccProcess.readDataFromProcess(30, &stdOut, &stdErr, true)) {
rccProcess.stopProcess();
appendMessage(tr("A timeout occurred running \"%1\"").
arg(rccProcess.commandLine().toUserOutput()), StdErrFormat);
qrcPath.removeFile();
return {};
}
if (!stdOut.trimmed().isEmpty())
appendMessage(QString::fromLocal8Bit(stdOut), StdErrFormat);
if (!stdErr.trimmed().isEmpty())
appendMessage(QString::fromLocal8Bit(stdErr), StdErrFormat);
if (rccProcess.exitStatus() != QProcess::NormalExit) {
appendMessage(tr("Crash while creating file for %1 \"%2\"").
arg(apkInfo()->name, rccProcess.commandLine().toUserOutput()),
StdErrFormat);
qrcPath.removeFile();
return {};
}
if (rccProcess.exitCode() != 0) {
appendMessage(tr("Creating file for %1 failed. \"%2\" (exit code %3).").
arg(apkInfo()->name).
arg(rccProcess.commandLine().toUserOutput()).
arg(rccProcess.exitCode()),
StdErrFormat);
qrcPath.removeFile();
return {};
}
}
return qmlrcPath;
}
bool AndroidQmlPreviewWorker::uploadFiles(const UploadInfo &transfer)
{
appendMessage(tr("Uploading files."), NormalMessageFormat);
const FilePath qresPath = createQmlrcFile(FilePath::fromString(transfer.projectFolder.path()),
transfer.uploadPackage.baseName());
if (!qresPath.exists())
return false;
runAdbShellCommand(m_devInfo.serialNumber, {"mkdir", "-p", apkInfo()->uploadDir});
const SdkToolResult res = runAdbCommand(m_devInfo.serialNumber,
{"push", qresPath.resolvePath(QString()).toString(),
apkInfo()->uploadDir});
if (!res.success()) {
appendMessage(res.stdOut(), ErrorMessageFormat);
if (res.stdOut().contains("Permission denied"))
appendMessage("'Permission denied' error detected. Try restarting your device "
"and then running the preview.", NormalMessageFormat);
}
qresPath.removeFile();
return res.success();
}
bool AndroidQmlPreviewWorker::runPreviewApp(const UploadInfo &transfer)
{
stopPreviewApp();
appendMessage(tr("Starting %1.").arg(apkInfo()->name), NormalMessageFormat);
const QDir destDir(apkInfo()->uploadDir);
const QStringList command{"am", "start",
"-n", apkInfo()->activityId,
"-e", "extraappparams",
QString::fromLatin1(
destDir.filePath(transfer.uploadPackage.baseName() + packageSuffix).
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);
m_viewerPid = pidofPreview(m_devInfo.serialNumber);
return true;
}
bool AndroidQmlPreviewWorker::stopPreviewApp()
{
const QStringList command{"am", "force-stop", apkInfo()->appId};
const SdkToolResult res = runAdbShellCommand(m_devInfo.serialNumber, command);
if (!res.success()) {
appendMessage(res.stdErr(), ErrorMessageFormat);
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 Android

View File

@@ -0,0 +1,74 @@
/****************************************************************************
**
** Copyright (C) 2021 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.
**
****************************************************************************/
#pragma once
#include "androidconfigurations.h"
#include <projectexplorer/runcontrol.h>
#include <utils/environment.h>
namespace Android {
class SdkToolResult;
namespace Internal {
class UploadInfo
{
public:
Utils::FilePath uploadPackage;
Utils::FilePath projectFolder;
};
class AndroidQmlPreviewWorker : public ProjectExplorer::RunWorker
{
Q_OBJECT
public:
AndroidQmlPreviewWorker(ProjectExplorer::RunControl *runControl);
private:
void start() override;
void stop() override;
bool ensureAvdIsRunning();
bool checkAndInstallPreviewApp();
bool prepareUpload(UploadInfo &transfer);
bool uploadFiles(const UploadInfo &transfer);
bool runPreviewApp(const UploadInfo &transfer);
bool stopPreviewApp();
void startLogcat();
void appendLogLines(const QStringList &lines);
Utils::FilePath createQmlrcFile(const Utils::FilePath &workFolder, const QString &basename);
ProjectExplorer::RunControl *m_rc = nullptr;
AndroidConfig m_config;
AndroidDeviceInfo m_devInfo;
QStringList m_avdAbis;
int m_viewerPid = -1;
};
} // namespace Internal
} // namespace Android

View File

@@ -60,6 +60,8 @@
#include <qtsupport/qtversionmanager.h> #include <qtsupport/qtversionmanager.h>
#include <qtsupport/baseqtversion.h> #include <qtsupport/baseqtversion.h>
#include <android/androidconstants.h>
#include <QAction> #include <QAction>
using namespace ProjectExplorer; using namespace ProjectExplorer;
@@ -217,8 +219,12 @@ QmlPreviewPluginPrivate::QmlPreviewPluginPrivate(QmlPreviewPlugin *parent)
connect(action, &QAction::triggered, this, [this]() { connect(action, &QAction::triggered, this, [this]() {
if (auto multiLanguageAspect = QmlProjectManager::QmlMultiLanguageAspect::current()) if (auto multiLanguageAspect = QmlProjectManager::QmlMultiLanguageAspect::current())
m_localeIsoCode = multiLanguageAspect->currentLocale(); m_localeIsoCode = multiLanguageAspect->currentLocale();
bool skipDeploy = false;
ProjectExplorerPlugin::runStartupProject(Constants::QML_PREVIEW_RUN_MODE); const Kit *kit = SessionManager::startupTarget()->kit();
if (SessionManager::startupTarget() && kit)
skipDeploy = kit->
supportedPlatforms().contains(Android::Constants::ANDROID_DEVICE_TYPE);
ProjectExplorerPlugin::runStartupProject(Constants::QML_PREVIEW_RUN_MODE, skipDeploy);
}); });
menu->addAction( menu->addAction(
Core::ActionManager::registerAction(action, "QmlPreview.RunPreview"), Core::ActionManager::registerAction(action, "QmlPreview.RunPreview"),

View File

@@ -537,6 +537,12 @@ QVariant QmlBuildSystem::additionalData(Id id) const
return qtForMCUs(); return qtForMCUs();
if (id == Constants::customQt6Project) if (id == Constants::customQt6Project)
return qt6Project(); return qt6Project();
if (id == Constants::mainFilePath)
return mainFilePath().toString();
if (id == Constants::customImportPaths)
return customImportPaths();
if (id == Constants::canonicalProjectDir)
return canonicalProjectDir().toString();
return {}; return {};
} }

View File

@@ -36,5 +36,8 @@ const char customForceFreeTypeData[] = "CustomForceFreeType";
const char customQtForMCUs[] = "CustomQtForMCUs"; const char customQtForMCUs[] = "CustomQtForMCUs";
const char customQt6Project[] = "CustomQt6Project"; const char customQt6Project[] = "CustomQt6Project";
const char mainFilePath[] = "MainFilePath";
const char customImportPaths[] = "CustomImportPaths";
const char canonicalProjectDir[] ="CanonicalProjectDir";
} // namespace Constants } // namespace Constants
} // namespace QmlProjectManager } // namespace QmlProjectManager