From 20a0bea246b0f91452f57c5591dd378d4e055b85 Mon Sep 17 00:00:00 2001 From: grrfe Date: Thu, 15 Jun 2023 17:28:05 +0200 Subject: [PATCH] Implement Shizuku installer support --- app/build.gradle | 12 +++ app/src/main/AndroidManifest.xml | 12 +++ app/src/main/assets/installers.json | 8 +- .../com/aurora/store/AuroraApplication.kt | 9 ++ .../store/data/installer/AppInstaller.kt | 26 +++++ .../store/data/installer/ShizukuInstaller.kt | 99 +++++++++++++++++++ .../view/ui/onboarding/InstallerFragment.kt | 44 +++++++++ .../ui/preferences/InstallationPreference.kt | 19 ++++ app/src/main/res/values/arrays.xml | 3 + app/src/main/res/values/strings.xml | 3 + build.gradle | 2 + 11 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/com/aurora/store/data/installer/ShizukuInstaller.kt diff --git a/app/build.gradle b/app/build.gradle index 1d3b2a54f..e84d48fb4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,6 +2,7 @@ * Aurora Store * Copyright (C) 2021, Rahul Kumar Patel * Copyright (C) 2022, The Calyx Institute + * Copyright (C) 2023, grrfe * * Aurora Store is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,6 +26,7 @@ plugins { id 'androidx.navigation.safeargs.kotlin' id 'org.jlleitschuh.gradle.ktlint' id 'com.starter.easylauncher' + id 'dev.rikka.tools.refine' } java { @@ -177,6 +179,16 @@ dependencies { //Browser implementation 'androidx.browser:browser:1.5.0' + + //Shizuku + def shizuku_version = '13.1.1' + compileOnly "dev.rikka.hidden:stub:4.2.0" + implementation "dev.rikka.tools.refine:runtime:4.3.0" + implementation "dev.rikka.shizuku:api:${shizuku_version}" + implementation "dev.rikka.shizuku:provider:${shizuku_version}" + + implementation 'org.lsposed.hiddenapibypass:hiddenapibypass:4.3' + //Test testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b8ace1ebc..088687e26 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,7 @@ ~ Aurora Store ~ Copyright (C) 2021, Rahul Kumar Patel ~ Copyright (C) 2022, The Calyx Institute + ~ Copyright (C) 2023, grrfe ~ ~ Aurora Store is free software: you can redistribute it and/or modify ~ it under the terms of the GNU General Public License as published by @@ -21,6 +22,8 @@ + + @@ -180,6 +183,15 @@ android:resource="@xml/paths" /> + + diff --git a/app/src/main/assets/installers.json b/app/src/main/assets/installers.json index b8f86f8f6..75c44d9ee 100755 --- a/app/src/main/assets/installers.json +++ b/app/src/main/assets/installers.json @@ -30,5 +30,11 @@ "title": "AM Installer", "subtitle": "Install use App Manager", "description": "You must have App Manager. Need adb/root mode to install when miui optimization is on." + }, + { + "id": "5", + "title": "Shizuku Installer", + "subtitle": "Install using Shizuku", + "description": "Requires an active Shizuku installation, supports Android 8.0+" } -] \ No newline at end of file +] diff --git a/app/src/main/java/com/aurora/store/AuroraApplication.kt b/app/src/main/java/com/aurora/store/AuroraApplication.kt index 4b9d170ad..29a5e1c54 100644 --- a/app/src/main/java/com/aurora/store/AuroraApplication.kt +++ b/app/src/main/java/com/aurora/store/AuroraApplication.kt @@ -1,6 +1,7 @@ /* * Aurora Store * Copyright (C) 2021, Rahul Kumar Patel + * Copyright (C) 2023, grrfe * * Aurora Store is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,6 +26,7 @@ import android.os.Build import androidx.appcompat.app.AppCompatDelegate import androidx.multidex.MultiDexApplication import com.aurora.Constants +import com.aurora.extensions.isPAndAbove import com.aurora.store.data.downloader.DownloadManager import com.aurora.store.data.providers.NetworkProvider import com.aurora.store.data.receiver.PackageManagerReceiver @@ -35,6 +37,7 @@ import com.tonyodev.fetch2.Fetch import java.util.ArrayList import nl.komponents.kovenant.android.startKovenant import nl.komponents.kovenant.android.stopKovenant +import org.lsposed.hiddenapibypass.HiddenApiBypass class AuroraApplication : MultiDexApplication() { @@ -48,6 +51,12 @@ class AuroraApplication : MultiDexApplication() { override fun onCreate() { super.onCreate() + // TODO: Only exempt required APIs + // Required for Shizuku installer + if (isPAndAbove()) { + HiddenApiBypass.addHiddenApiExemptions("") + } + AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) //Create Notification Channels : General & Alert diff --git a/app/src/main/java/com/aurora/store/data/installer/AppInstaller.kt b/app/src/main/java/com/aurora/store/data/installer/AppInstaller.kt index d7ab30fb4..7b352899b 100644 --- a/app/src/main/java/com/aurora/store/data/installer/AppInstaller.kt +++ b/app/src/main/java/com/aurora/store/data/installer/AppInstaller.kt @@ -1,6 +1,7 @@ /* * Aurora Store * Copyright (C) 2021, Rahul Kumar Patel + * Copyright (C) 2023, grrfe * * Aurora Store is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,13 +22,16 @@ package com.aurora.store.data.installer import android.content.Context import android.content.pm.PackageInstaller +import android.content.pm.PackageManager import com.aurora.extensions.isLAndAbove +import com.aurora.extensions.isOAndAbove import com.aurora.store.BuildConfig import com.aurora.store.R import com.aurora.store.util.PackageUtil import com.aurora.store.util.Preferences import com.aurora.store.util.Preferences.PREFERENCE_INSTALLER_ID import com.topjohnwu.superuser.Shell +import rikka.shizuku.Shizuku open class AppInstaller private constructor(var context: Context) { @@ -75,6 +79,15 @@ open class AppInstaller private constructor(var context: Context) { return PackageUtil.isInstalled(context, AMInstaller.AM_PACKAGE_NAME) or PackageUtil.isInstalled(context, AMInstaller.AM_DEBUG_PACKAGE_NAME) } + + fun hasShizuku(context: Context): Boolean { + return PackageUtil.isInstalled(context, ShizukuInstaller.SHIZUKU_PACKAGE_NAME) + } + + fun hasShizukuPerm(): Boolean { + return Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED + } + } val choiceAndInstaller = HashMap() @@ -122,6 +135,19 @@ open class AppInstaller private constructor(var context: Context) { choiceAndInstaller[prefValue] = installer installer } + 5 -> { + if (isOAndAbove()) { + val installer = if (hasShizuku(context) && hasShizukuPerm()) { + ShizukuInstaller(context) + } else { + getDefaultInstaller(context) + } + choiceAndInstaller[prefValue] = installer + installer + } else { + getDefaultInstaller(context) + } + } else -> { val installer = getDefaultInstaller(context) choiceAndInstaller[prefValue] = installer diff --git a/app/src/main/java/com/aurora/store/data/installer/ShizukuInstaller.kt b/app/src/main/java/com/aurora/store/data/installer/ShizukuInstaller.kt new file mode 100644 index 000000000..434c1e638 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/installer/ShizukuInstaller.kt @@ -0,0 +1,99 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.installer + +import android.content.Context +import android.content.pm.* +import android.content.pm.PackageInstaller.SessionParams +import android.os.Build +import android.os.IBinder +import android.os.IInterface +import androidx.annotation.RequiresApi +import com.aurora.extensions.isOAndAbove +import com.aurora.extensions.isSAndAbove +import com.aurora.store.R +import com.aurora.store.util.Log +import dev.rikka.tools.refine.Refine +import rikka.shizuku.ShizukuBinderWrapper +import rikka.shizuku.SystemServiceHelper + + +class ShizukuInstaller(context: Context) : SessionInstallerBase(context) { + + companion object { + const val SHIZUKU_PACKAGE_NAME = "moe.shizuku.privileged.api" + } + + // Taken from LSPatch (https://github.com/LSPosed/LSPatch) + private fun IBinder.wrap() = ShizukuBinderWrapper(this) + private fun IInterface.asShizukuBinder() = this.asBinder().wrap() + + private val iPackageManager: IPackageManager by lazy { + IPackageManager.Stub.asInterface(SystemServiceHelper.getSystemService("package").wrap()) + } + + private val iPackageInstaller: IPackageInstaller by lazy { + IPackageInstaller.Stub.asInterface(iPackageManager.packageInstaller.asShizukuBinder()) + } + + private val packageInstaller: PackageInstaller? by lazy { + if (isSAndAbove()) { + Refine.unsafeCast( + PackageInstallerHidden(iPackageInstaller, "com.android.vending", null, 0) + ) + } else if (isOAndAbove()) { + Refine.unsafeCast( + PackageInstallerHidden(iPackageInstaller, "com.android.vending", 0) + ) + } else null + } + + @RequiresApi(Build.VERSION_CODES.O) + override fun install(packageName: String, files: List) { + if (isAlreadyQueued(packageName)) { + Log.i("$packageName already queued") + } else { + Log.i("Received session install request for $packageName") + + val (sessionId, session) = kotlin.runCatching { + val sessionId = + packageInstaller!!.createSession(SessionParams(SessionParams.MODE_FULL_INSTALL)) + val iSession = IPackageInstallerSession.Stub.asInterface( + iPackageInstaller.openSession(sessionId).asShizukuBinder() + ) + val session = Refine.unsafeCast( + PackageInstallerHidden.SessionHidden(iSession) + ) + + sessionId to session + }.getOrElse { ex -> + ex.printStackTrace() + postError( + packageName, + context.getString(R.string.installer_status_failure), + context.getString(R.string.installer_shizuku_unavailable) + ) + return + } + + xInstall(sessionId, session, packageName, files) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/onboarding/InstallerFragment.kt b/app/src/main/java/com/aurora/store/view/ui/onboarding/InstallerFragment.kt index e3396c04f..e96d08521 100644 --- a/app/src/main/java/com/aurora/store/view/ui/onboarding/InstallerFragment.kt +++ b/app/src/main/java/com/aurora/store/view/ui/onboarding/InstallerFragment.kt @@ -19,17 +19,21 @@ package com.aurora.store.view.ui.onboarding +import android.content.pm.PackageManager import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.aurora.extensions.isMIUI import com.aurora.extensions.isMiuiOptimizationDisabled +import com.aurora.extensions.isOAndAbove import com.aurora.extensions.showDialog import com.aurora.store.R import com.aurora.store.data.installer.AppInstaller.Companion.hasAppManager import com.aurora.store.data.installer.AppInstaller.Companion.hasRootAccess import com.aurora.store.data.installer.AppInstaller.Companion.hasAuroraService +import com.aurora.store.data.installer.AppInstaller.Companion.hasShizuku +import com.aurora.store.data.installer.AppInstaller.Companion.hasShizukuPerm import com.aurora.store.data.model.Installer import com.aurora.store.databinding.FragmentOnboardingInstallerBinding import com.aurora.store.util.Preferences @@ -39,6 +43,7 @@ import com.aurora.store.view.epoxy.views.preference.InstallerViewModel_ import com.aurora.store.view.ui.commons.BaseFragment import com.aurora.store.view.ui.sheets.DeviceMiuiSheet import com.google.gson.reflect.TypeToken +import rikka.shizuku.Shizuku import java.nio.charset.StandardCharsets @@ -48,11 +53,25 @@ class InstallerFragment : BaseFragment() { var installerId: Int = 0 + private val shizukuResultListener = + Shizuku.OnRequestPermissionResultListener { _: Int, result: Int -> + if (result == PackageManager.PERMISSION_GRANTED) { + this.installerId = 5 + save(PREFERENCE_INSTALLER_ID, 5) + } else { + showDialog( + R.string.action_installations, + R.string.installer_shizuku_unavailable + ) + } + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { + Shizuku.addRequestPermissionResultListener(shizukuResultListener) B = FragmentOnboardingInstallerBinding.bind( inflater.inflate( R.layout.fragment_onboarding_installer, @@ -76,6 +95,11 @@ class InstallerFragment : BaseFragment() { updateController(installerList) } + override fun onDestroy() { + Shizuku.removeRequestPermissionResultListener(shizukuResultListener) + super.onDestroy() + } + private fun updateController(installerList: List) { B.epoxyRecycler.withModels { setFilterDuplicates(true) @@ -140,6 +164,26 @@ class InstallerFragment : BaseFragment() { ) } } + 5 -> { + if (hasShizuku(requireContext()) && isOAndAbove()) { + if (hasShizukuPerm()) { + this.installerId = installerId + save(PREFERENCE_INSTALLER_ID, installerId) + } else if (Shizuku.shouldShowRequestPermissionRationale()) { + Shizuku.requestPermission(9000) + } else { + showDialog( + R.string.action_installations, + R.string.installer_shizuku_unavailable + ) + } + } else { + showDialog( + R.string.action_installations, + R.string.installer_shizuku_unavailable + ) + } + } else -> { this.installerId = installerId save(PREFERENCE_INSTALLER_ID, installerId) diff --git a/app/src/main/java/com/aurora/store/view/ui/preferences/InstallationPreference.kt b/app/src/main/java/com/aurora/store/view/ui/preferences/InstallationPreference.kt index b6b553f2e..2abbcb240 100644 --- a/app/src/main/java/com/aurora/store/view/ui/preferences/InstallationPreference.kt +++ b/app/src/main/java/com/aurora/store/view/ui/preferences/InstallationPreference.kt @@ -19,6 +19,7 @@ package com.aurora.store.view.ui.preferences +import android.content.pm.PackageManager import android.os.Bundle import android.view.View import androidx.preference.Preference @@ -30,12 +31,14 @@ import com.aurora.store.BuildConfig import com.aurora.store.R import com.aurora.store.data.installer.AMInstaller import com.aurora.store.data.installer.ServiceInstaller +import com.aurora.store.data.installer.ShizukuInstaller import com.aurora.store.util.CommonUtil import com.aurora.store.util.PackageUtil import com.aurora.store.util.Preferences import com.aurora.store.util.save import com.aurora.store.view.custom.preference.AuroraListPreference import com.topjohnwu.superuser.Shell +import rikka.shizuku.Shizuku class InstallationPreference : PreferenceFragmentCompat() { @@ -100,6 +103,17 @@ class InstallationPreference : PreferenceFragmentCompat() { ) false } + } else if (selectedId == 5) { + if (checkShizukuAvailability()) { + save(Preferences.PREFERENCE_INSTALLER_ID, selectedId) + true + } else { + showDialog( + R.string.action_installations, + R.string.installer_shizuku_unavailable + ) + false + } } else { save(Preferences.PREFERENCE_INSTALLER_ID, selectedId) true @@ -137,4 +151,9 @@ class InstallationPreference : PreferenceFragmentCompat() { AMInstaller.AM_DEBUG_PACKAGE_NAME ) } + + private fun checkShizukuAvailability(): Boolean { + return PackageUtil.isInstalled(requireContext(), ShizukuInstaller.SHIZUKU_PACKAGE_NAME) && + Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED + } } diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 88440c8f5..ec6592b14 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -1,6 +1,7 @@