diff --git a/components/waydroidintegrationplugin/kauth/waydroidhelper.actions b/components/waydroidintegrationplugin/kauth/waydroidhelper.actions index ae5346e1..15706aea 100644 --- a/components/waydroidintegrationplugin/kauth/waydroidhelper.actions +++ b/components/waydroidintegrationplugin/kauth/waydroidhelper.actions @@ -38,3 +38,10 @@ Description[x-test]=xxAllow initialization of Waydroidxx Policy=yes PolicyInactive=yes Persistence=session + +[org.kde.plasma.mobileshell.waydroidhelper.getandroidid] +Name=Fetch Android ID for Google Services +Description=Allow get Android ID of Waydroid container to activate Google services +Policy=yes +PolicyInactive=yes +Persistence=session diff --git a/components/waydroidintegrationplugin/kauth/waydroidhelper.cpp b/components/waydroidintegrationplugin/kauth/waydroidhelper.cpp index 66829261..8b8e7abf 100644 --- a/components/waydroidintegrationplugin/kauth/waydroidhelper.cpp +++ b/components/waydroidintegrationplugin/kauth/waydroidhelper.cpp @@ -25,6 +25,7 @@ class WaydroidHelper : public QObject Q_OBJECT public Q_SLOTS: KAuth::ActionReply initialize(const QVariantMap &args); + KAuth::ActionReply getandroidid(const QVariantMap &args); }; KAuth::ActionReply WaydroidHelper::initialize(const QVariantMap &args) @@ -56,6 +57,30 @@ KAuth::ActionReply WaydroidHelper::initialize(const QVariantMap &args) } } +KAuth::ActionReply WaydroidHelper::getandroidid(const QVariantMap &args) +{ + Q_UNUSED(args); + + QStringList arguments = {u"shell"_s, + u"sqlite3"_s, + u"/data/data/com.google.android.gsf/databases/gservices.db"_s, + u"select value from main where name = \"android_id\""_s}; + + QProcess *process = new QProcess(this); + process->start(WAYDROID_COMMAND, arguments); + process->waitForFinished(); + const QString androidId = process->readAllStandardOutput().trimmed(); + + if (process->exitCode() == 0) { + KAuth::ActionReply reply; + reply.addData("android_id"_L1, androidId); + return reply; + } else { + qCWarning(WAYDROIDHELPER) << "Failed to get Android ID: " << process->readAllStandardError(); + return KAuth::ActionReply::HelperErrorReply(); + } +} + KAUTH_HELPER_MAIN("org.kde.plasma.mobileshell.waydroidhelper", WaydroidHelper) #include "waydroidhelper.moc" \ No newline at end of file diff --git a/components/waydroidintegrationplugin/waydroidstate.cpp b/components/waydroidintegrationplugin/waydroidstate.cpp index 1a771d2a..95fcb984 100644 --- a/components/waydroidintegrationplugin/waydroidstate.cpp +++ b/components/waydroidintegrationplugin/waydroidstate.cpp @@ -89,6 +89,31 @@ void WaydroidState::refreshSessionInfo() Q_EMIT ipAddressChanged(); } +void WaydroidState::refreshAndroidId() +{ + if (m_status != Initialized) { + return; + } + + KAuth::Action writeAction(u"org.kde.plasma.mobileshell.waydroidhelper.getandroidid"_s); + writeAction.setHelperId(u"org.kde.plasma.mobileshell.waydroidhelper"_s); + + KAuth::ExecuteJob *job = writeAction.execute(); + job->start(); + + connect(job, &KAuth::ExecuteJob::finished, this, [this](KJob *job, auto) { + KAuth::ExecuteJob *executeJob = dynamic_cast(job); + if (executeJob->error() == 0) { + m_androidId = executeJob->data()["android_id"].toString(); + } else { + m_androidId = ""; + qCWarning(WAYDROIDINTEGRATIONPLUGIN) << "KAuth returned an error code:" << executeJob->error(); + } + + Q_EMIT androidIdChanged(); + }); +} + void WaydroidState::refreshPropsInfo() { if (m_sessionStatus != SessionRunning) { @@ -267,6 +292,11 @@ QString WaydroidState::errorMessage() const return m_errorMessage; } +QString WaydroidState::androidId() const +{ + return m_androidId; +} + bool WaydroidState::multiWindows() const { return m_multiWindows; diff --git a/components/waydroidintegrationplugin/waydroidstate.h b/components/waydroidintegrationplugin/waydroidstate.h index 1c285a75..4f82bca5 100644 --- a/components/waydroidintegrationplugin/waydroidstate.h +++ b/components/waydroidintegrationplugin/waydroidstate.h @@ -26,6 +26,7 @@ class WaydroidState : public QObject Q_PROPERTY(Status status READ status NOTIFY statusChanged) Q_PROPERTY(SessionStatus sessionStatus READ sessionStatus NOTIFY sessionStatusChanged) Q_PROPERTY(QString ipAddress READ ipAddress NOTIFY ipAddressChanged) + Q_PROPERTY(QString androidId READ androidId NOTIFY androidIdChanged) Q_PROPERTY(QString errorTitle READ errorTitle NOTIFY errorTitleChanged) Q_PROPERTY(QString errorMessage READ errorMessage NOTIFY errorMessageChanged) Q_PROPERTY(bool multiWindows READ multiWindows WRITE setMultiWindows NOTIFY multiWindowsChanged) @@ -83,6 +84,7 @@ public: Q_INVOKABLE void refreshSupportsInfo(); Q_INVOKABLE void refreshSessionInfo(); + Q_INVOKABLE void refreshAndroidId(); Q_INVOKABLE void refreshPropsInfo(); Q_INVOKABLE void resetError(); Q_INVOKABLE void initialize(const SystemType systemType, const RomType romType, const bool forced = false); @@ -93,6 +95,7 @@ public: Status status() const; SessionStatus sessionStatus() const; QString ipAddress() const; + QString androidId() const; QString errorTitle() const; QString errorMessage() const; bool multiWindows() const; @@ -111,6 +114,7 @@ Q_SIGNALS: void ueventChanged(); void errorTitleChanged(); void errorMessageChanged(); + void androidIdChanged(); private: Status m_status{NotInitialized}; @@ -118,6 +122,7 @@ private: QString m_ipAddress{""}; QString m_errorTitle{""}; QString m_errorMessage{""}; + QString m_androidId{""}; // Waydroid props. See https://docs.waydro.id/usage/waydroid-prop-options bool m_multiWindows{false}; diff --git a/kcms/waydroidintegration/ui/WaydroidConfigurationForm.qml b/kcms/waydroidintegration/ui/WaydroidConfigurationForm.qml index e7e8e2e6..47180f1a 100644 --- a/kcms/waydroidintegration/ui/WaydroidConfigurationForm.qml +++ b/kcms/waydroidintegration/ui/WaydroidConfigurationForm.qml @@ -24,6 +24,7 @@ ColumnLayout { text: i18n("IP address") description: AIP.WaydroidState.ipAddress trailing: PC3.Button { + visible: AIP.WaydroidState.ipAddress !== "" text: i18n('Copy') icon.name: 'edit-copy-symbolic' onClicked: AIP.WaydroidState.copyToClipboard(AIP.WaydroidState.ipAddress) @@ -39,6 +40,12 @@ ColumnLayout { onClicked: AIP.WaydroidState.stopSession() } } + + FormCard.FormButtonDelegate { + id: quickSettingsButton + text: i18n("Certify my device for Google Play Protect") + onClicked: kcm.push("WaydroidGooglePlayProtectConfigurationPage.qml") + } } // Some informations as IP address can take time to be set by Waydroid diff --git a/kcms/waydroidintegration/ui/WaydroidGooglePlayProtectConfigurationPage.qml b/kcms/waydroidintegration/ui/WaydroidGooglePlayProtectConfigurationPage.qml new file mode 100644 index 00000000..0cc2b0dd --- /dev/null +++ b/kcms/waydroidintegration/ui/WaydroidGooglePlayProtectConfigurationPage.qml @@ -0,0 +1,60 @@ +/* + * SPDX-FileCopyrightText: 2025 Florian RICHER + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls as QQC2 + +import org.kde.kirigami 2.19 as Kirigami +import org.kde.kcmutils as KCM +import org.kde.kirigamiaddons.formcard 1.0 as FormCard +import org.kde.plasma.private.mobileshell.waydroidintegrationplugin as AIP + +KCM.SimpleKCM { + id: root + + topPadding: 0 + bottomPadding: 0 + leftPadding: 0 + rightPadding: 0 + + title: i18n("Google Play Protect configuration") + + Component.onCompleted: { + if (AIP.WaydroidState.androidId === "") { + AIP.WaydroidState.refreshAndroidId() + } + } + + WaydroidLoader { + visible: AIP.WaydroidState.androidId === "" + text: i18n("We fetching your Android ID.\nIt can take a few seconds.") + } + + ColumnLayout { + visible: AIP.WaydroidState.androidId !== "" + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent + anchors.leftMargin: Kirigami.Units.largeSpacing + anchors.right: parent + anchors.rightMargin: Kirigami.Units.largeSpacing + spacing: Kirigami.Units.largeSpacing + + Kirigami.PlaceholderMessage { + explanation: i18n("When launching waydroid with GAPPS for the first time you will be notified that the device is not certified for Google Play Protect. To self certify your device, paste the Android ID on the field in the website. Then, give the Google services some minutes to reflect the change and restart waydroid.") + Layout.fillWidth: true + } + + QQC2.Button { + text: i18nc("@action:button", "Copy Android ID and open the website") + icon.name: 'edit-copy-symbolic' + Layout.alignment: Qt.AlignHCenter + onClicked: { + AIP.WaydroidState.copyToClipboard(AIP.WaydroidState.androidId) + Qt.openUrlExternally("https://www.google.com/android/uncertified") + } + } + } +} \ No newline at end of file