diff --git a/src/main/kotlin/com/looker/droidify/content/Preferences.kt b/src/main/kotlin/com/looker/droidify/content/Preferences.kt index 1b283d38..5ed18456 100644 --- a/src/main/kotlin/com/looker/droidify/content/Preferences.kt +++ b/src/main/kotlin/com/looker/droidify/content/Preferences.kt @@ -33,6 +33,7 @@ object Preferences { Key.ProxyPort, Key.ProxyType, Key.RootPermission, + Key.RootSessionInstaller, Key.SortOrder, Key.Theme, Key.DefaultTab, @@ -158,6 +159,7 @@ object Preferences { ) object RootPermission : Key("root_permission", Value.BooleanValue(false)) + object RootSessionInstaller : Key("root_session_installer", Value.BooleanValue(false)) object SortOrder : Key( "sort_order", diff --git a/src/main/kotlin/com/looker/droidify/installer/BaseInstaller.kt b/src/main/kotlin/com/looker/droidify/installer/BaseInstaller.kt index d9225ecc..bb000ed3 100644 --- a/src/main/kotlin/com/looker/droidify/installer/BaseInstaller.kt +++ b/src/main/kotlin/com/looker/droidify/installer/BaseInstaller.kt @@ -5,6 +5,9 @@ import android.content.Context abstract class BaseInstaller(val context: Context) : InstallationEvents { companion object { const val ROOT_INSTALL_PACKAGE = "cat %s | pm install -i %s --user %s -t -r -S %s" + const val ROOT_INSTALL_PACKAGE_SESSION_CREATE = "pm install-create -i %s --user %s -r -S %s" + const val ROOT_INSTALL_PACKAGE_SESSION_WRITE = "cat %s | pm install-write -S %s %s %s" + const val ROOT_INSTALL_PACKAGE_SESSION_COMMIT = "pm install-commit %s" const val ROOT_UNINSTALL_PACKAGE = "pm uninstall --user %s %s" const val DELETE_PACKAGE = "%s rm %s" } diff --git a/src/main/kotlin/com/looker/droidify/installer/RootInstaller.kt b/src/main/kotlin/com/looker/droidify/installer/RootInstaller.kt index 05fd1e25..30267c43 100644 --- a/src/main/kotlin/com/looker/droidify/installer/RootInstaller.kt +++ b/src/main/kotlin/com/looker/droidify/installer/RootInstaller.kt @@ -1,12 +1,15 @@ package com.looker.droidify.installer import android.content.Context +import com.looker.droidify.BuildConfig import com.looker.droidify.content.Cache +import com.looker.droidify.content.Preferences import com.looker.droidify.utility.extension.android.Android import com.topjohnwu.superuser.Shell import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.io.File +import java.util.regex.Pattern class RootInstaller(context: Context) : BaseInstaller(context) { @@ -41,6 +44,27 @@ class RootInstaller(context: Context) : BaseInstaller(context) { length() ) + val File.session_install_create + get() = String.format( + ROOT_INSTALL_PACKAGE_SESSION_CREATE, + BuildConfig.APPLICATION_ID, + getCurrentUserState, + length() + ) + + fun File.sessionInstallWrite(session_id: Int) = String.format( + ROOT_INSTALL_PACKAGE_SESSION_WRITE, + absolutePath, + length(), + session_id, + name + ) + + fun sessionInstallCommit(session_id: Int) = String.format( + ROOT_INSTALL_PACKAGE_SESSION_COMMIT, + session_id + ) + val String.uninstall get() = String.format( ROOT_UNINSTALL_PACKAGE, @@ -66,14 +90,34 @@ class RootInstaller(context: Context) : BaseInstaller(context) { mRootInstaller(cacheFile) } - override suspend fun install(packageName: String, cacheFile: File) = mRootInstaller(cacheFile) + override suspend fun install(packageName: String, cacheFile: File) = + mRootInstaller(cacheFile) override suspend fun uninstall(packageName: String) = mRootUninstaller(packageName) private suspend fun mRootInstaller(cacheFile: File) { withContext(Dispatchers.Default) { - Shell.su(cacheFile.install) - .submit { if (it.isSuccess) Shell.su(cacheFile.deletePackage).submit() } + if (Preferences[Preferences.Key.RootSessionInstaller]) { + Shell.su(cacheFile.session_install_create) + .submit { + val sessionIdPattern = Pattern.compile("(\\d+)") + val sessionIdMatcher = sessionIdPattern.matcher(it.out[0]) + val found = sessionIdMatcher.find() + + if (found) { + val sessionId = sessionIdMatcher.group(1).toInt() + Shell.su(cacheFile.sessionInstallWrite(sessionId)) + .submit { + Shell.su(sessionInstallCommit(sessionId)).exec() + if (it.isSuccess) Shell.su(cacheFile.deletePackage).submit() + } + } + } + + } else { + Shell.su(cacheFile.install) + .submit { if (it.isSuccess) Shell.su(cacheFile.deletePackage).submit() } + } } } diff --git a/src/main/kotlin/com/looker/droidify/ui/fragments/PrefsNavFragmentX.kt b/src/main/kotlin/com/looker/droidify/ui/fragments/PrefsNavFragmentX.kt index 5805e4c0..8489ede5 100644 --- a/src/main/kotlin/com/looker/droidify/ui/fragments/PrefsNavFragmentX.kt +++ b/src/main/kotlin/com/looker/droidify/ui/fragments/PrefsNavFragmentX.kt @@ -29,7 +29,12 @@ import com.looker.droidify.R import com.looker.droidify.content.Preferences import com.looker.droidify.databinding.FragmentPrefsBinding import com.looker.droidify.databinding.PreferenceItemBinding -import com.looker.droidify.utility.extension.resources.* +import com.looker.droidify.utility.Utils +import com.looker.droidify.utility.extension.resources.TypefaceExtra +import com.looker.droidify.utility.extension.resources.getColorFromAttr +import com.looker.droidify.utility.extension.resources.inflate +import com.looker.droidify.utility.extension.resources.setTextSizeScaled +import com.looker.droidify.utility.extension.resources.sizeScaled import com.topjohnwu.superuser.Shell import kotlinx.coroutines.launch @@ -108,11 +113,12 @@ abstract class PrefsNavFragmentX : Fragment() { preferences[Preferences.Key.ProxyHost]?.setEnabled(enabled) preferences[Preferences.Key.ProxyPort]?.setEnabled(enabled) } - if (key == Preferences.Key.RootPermission) { + if (key == null || key == Preferences.Key.RootPermission) { preferences[Preferences.Key.RootPermission]?.setEnabled( Shell.getCachedShell()?.isRoot ?: Shell.getShell().isRoot ) + preferences[Preferences.Key.RootSessionInstaller]?.setEnabled(Utils.rootInstallerEnabled) } if (key == Preferences.Key.Theme) { requireActivity().recreate() diff --git a/src/main/kotlin/com/looker/droidify/ui/fragments/PrefsUpdatesFragment.kt b/src/main/kotlin/com/looker/droidify/ui/fragments/PrefsUpdatesFragment.kt index 9ee823d6..8db14a8c 100644 --- a/src/main/kotlin/com/looker/droidify/ui/fragments/PrefsUpdatesFragment.kt +++ b/src/main/kotlin/com/looker/droidify/ui/fragments/PrefsUpdatesFragment.kt @@ -51,6 +51,10 @@ class PrefsUpdatesFragment : PrefsNavFragmentX() { Preferences.Key.RootPermission, getString(R.string.root_permission), getString(R.string.root_permission_description) ) + addSwitch( + Preferences.Key.RootSessionInstaller, getString(R.string.root_session_installer), + getString(R.string.root_session_installer_description) + ) } } } diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 95246c5d..07d416b5 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -194,4 +194,6 @@ The device doesn\'t support ignoring battery optimizations! Don\'t need it Let\'s do it! + Use root session installer + The session installer is a bit less performant that the legacy one, but avoids issues with installed apps in Android +13.