Merge pull request #995 from ManiaciaChao/feat/custom-legacy-installer
Add support for specifying custom installer (`LegacyInstaller` only)
This commit is contained in:
commit
582d526faf
@ -14,6 +14,7 @@ import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.core.stringSetPreferencesKey
|
||||
import com.looker.droidify.datastore.model.AutoSync
|
||||
import com.looker.droidify.datastore.model.InstallerType
|
||||
import com.looker.droidify.datastore.model.LegacyInstallerComponent
|
||||
import com.looker.droidify.datastore.model.ProxyPreference
|
||||
import com.looker.droidify.datastore.model.ProxyType
|
||||
import com.looker.droidify.datastore.model.SortOrder
|
||||
@ -85,6 +86,31 @@ class PreferenceSettingsRepository(
|
||||
override suspend fun setInstallerType(installerType: InstallerType) =
|
||||
INSTALLER_TYPE.update(installerType.name)
|
||||
|
||||
override suspend fun setLegacyInstallerComponent(component: LegacyInstallerComponent?) {
|
||||
when (component) {
|
||||
null -> {
|
||||
LEGACY_INSTALLER_COMPONENT_TYPE.update("")
|
||||
LEGACY_INSTALLER_COMPONENT_CLASS.update("")
|
||||
LEGACY_INSTALLER_COMPONENT_ACTIVITY.update("")
|
||||
}
|
||||
is LegacyInstallerComponent.Component -> {
|
||||
LEGACY_INSTALLER_COMPONENT_TYPE.update("component")
|
||||
LEGACY_INSTALLER_COMPONENT_CLASS.update(component.clazz)
|
||||
LEGACY_INSTALLER_COMPONENT_ACTIVITY.update(component.activity)
|
||||
}
|
||||
LegacyInstallerComponent.Unspecified -> {
|
||||
LEGACY_INSTALLER_COMPONENT_TYPE.update("unspecified")
|
||||
LEGACY_INSTALLER_COMPONENT_CLASS.update("")
|
||||
LEGACY_INSTALLER_COMPONENT_ACTIVITY.update("")
|
||||
}
|
||||
LegacyInstallerComponent.AlwaysChoose -> {
|
||||
LEGACY_INSTALLER_COMPONENT_TYPE.update("always_choose")
|
||||
LEGACY_INSTALLER_COMPONENT_CLASS.update("")
|
||||
LEGACY_INSTALLER_COMPONENT_ACTIVITY.update("")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setAutoUpdate(allow: Boolean) =
|
||||
AUTO_UPDATE.update(allow)
|
||||
|
||||
@ -125,6 +151,18 @@ class PreferenceSettingsRepository(
|
||||
private fun mapSettings(preferences: Preferences): Settings {
|
||||
val installerType =
|
||||
InstallerType.valueOf(preferences[INSTALLER_TYPE] ?: InstallerType.Default.name)
|
||||
val legacyInstallerComponent = when (preferences[LEGACY_INSTALLER_COMPONENT_TYPE]) {
|
||||
"component" -> {
|
||||
preferences[LEGACY_INSTALLER_COMPONENT_CLASS]?.takeIf { it.isNotBlank() }?.let { cls ->
|
||||
preferences[LEGACY_INSTALLER_COMPONENT_ACTIVITY]?.takeIf { it.isNotBlank() }?.let { act ->
|
||||
LegacyInstallerComponent.Component(cls, act)
|
||||
}
|
||||
}
|
||||
}
|
||||
"unspecified" -> LegacyInstallerComponent.Unspecified
|
||||
"always_choose" -> LegacyInstallerComponent.AlwaysChoose
|
||||
else -> null
|
||||
}
|
||||
|
||||
val language = preferences[LANGUAGE] ?: "system"
|
||||
val incompatibleVersions = preferences[INCOMPATIBLE_VERSIONS] ?: false
|
||||
@ -154,6 +192,7 @@ class PreferenceSettingsRepository(
|
||||
theme = theme,
|
||||
dynamicTheme = dynamicTheme,
|
||||
installerType = installerType,
|
||||
legacyInstallerComponent = legacyInstallerComponent,
|
||||
autoUpdate = autoUpdate,
|
||||
autoSync = autoSync,
|
||||
sortOrder = sortOrder,
|
||||
@ -185,6 +224,9 @@ class PreferenceSettingsRepository(
|
||||
val LAST_CLEAN_UP = longPreferencesKey("key_last_clean_up_time")
|
||||
val FAVOURITE_APPS = stringSetPreferencesKey("key_favourite_apps")
|
||||
val HOME_SCREEN_SWIPING = booleanPreferencesKey("key_home_swiping")
|
||||
val LEGACY_INSTALLER_COMPONENT_CLASS = stringPreferencesKey("key_legacy_installer_component_class")
|
||||
val LEGACY_INSTALLER_COMPONENT_ACTIVITY = stringPreferencesKey("key_legacy_installer_component_activity")
|
||||
val LEGACY_INSTALLER_COMPONENT_TYPE = stringPreferencesKey("key_legacy_installer_component_type")
|
||||
|
||||
// Enums
|
||||
val THEME = stringPreferencesKey("key_theme")
|
||||
@ -200,6 +242,28 @@ class PreferenceSettingsRepository(
|
||||
set(UNSTABLE_UPDATES, settings.unstableUpdate)
|
||||
set(THEME, settings.theme.name)
|
||||
set(DYNAMIC_THEME, settings.dynamicTheme)
|
||||
when (settings.legacyInstallerComponent) {
|
||||
is LegacyInstallerComponent.Component -> {
|
||||
set(LEGACY_INSTALLER_COMPONENT_TYPE, "component")
|
||||
set(LEGACY_INSTALLER_COMPONENT_CLASS, settings.legacyInstallerComponent.clazz)
|
||||
set(LEGACY_INSTALLER_COMPONENT_ACTIVITY, settings.legacyInstallerComponent.activity)
|
||||
}
|
||||
LegacyInstallerComponent.Unspecified -> {
|
||||
set(LEGACY_INSTALLER_COMPONENT_TYPE, "unspecified")
|
||||
set(LEGACY_INSTALLER_COMPONENT_CLASS, "")
|
||||
set(LEGACY_INSTALLER_COMPONENT_ACTIVITY, "")
|
||||
}
|
||||
LegacyInstallerComponent.AlwaysChoose -> {
|
||||
set(LEGACY_INSTALLER_COMPONENT_TYPE, "always_choose")
|
||||
set(LEGACY_INSTALLER_COMPONENT_CLASS, "")
|
||||
set(LEGACY_INSTALLER_COMPONENT_ACTIVITY, "")
|
||||
}
|
||||
null -> {
|
||||
set(LEGACY_INSTALLER_COMPONENT_TYPE, "")
|
||||
set(LEGACY_INSTALLER_COMPONENT_CLASS, "")
|
||||
set(LEGACY_INSTALLER_COMPONENT_ACTIVITY, "")
|
||||
}
|
||||
}
|
||||
set(INSTALLER_TYPE, settings.installerType.name)
|
||||
set(AUTO_UPDATE, settings.autoUpdate)
|
||||
set(AUTO_SYNC, settings.autoSync.name)
|
||||
|
@ -3,6 +3,7 @@ package com.looker.droidify.datastore
|
||||
import androidx.datastore.core.Serializer
|
||||
import com.looker.droidify.datastore.model.AutoSync
|
||||
import com.looker.droidify.datastore.model.InstallerType
|
||||
import com.looker.droidify.datastore.model.LegacyInstallerComponent
|
||||
import com.looker.droidify.datastore.model.ProxyPreference
|
||||
import com.looker.droidify.datastore.model.SortOrder
|
||||
import com.looker.droidify.datastore.model.Theme
|
||||
@ -29,6 +30,7 @@ data class Settings(
|
||||
val theme: Theme = Theme.SYSTEM,
|
||||
val dynamicTheme: Boolean = false,
|
||||
val installerType: InstallerType = InstallerType.Default,
|
||||
val legacyInstallerComponent: LegacyInstallerComponent? = null,
|
||||
val autoUpdate: Boolean = false,
|
||||
val autoSync: AutoSync = AutoSync.WIFI_ONLY,
|
||||
val sortOrder: SortOrder = SortOrder.UPDATED,
|
||||
|
@ -3,6 +3,7 @@ package com.looker.droidify.datastore
|
||||
import android.net.Uri
|
||||
import com.looker.droidify.datastore.model.AutoSync
|
||||
import com.looker.droidify.datastore.model.InstallerType
|
||||
import com.looker.droidify.datastore.model.LegacyInstallerComponent
|
||||
import com.looker.droidify.datastore.model.ProxyType
|
||||
import com.looker.droidify.datastore.model.SortOrder
|
||||
import com.looker.droidify.datastore.model.Theme
|
||||
@ -37,6 +38,8 @@ interface SettingsRepository {
|
||||
|
||||
suspend fun setInstallerType(installerType: InstallerType)
|
||||
|
||||
suspend fun setLegacyInstallerComponent(component: LegacyInstallerComponent?)
|
||||
|
||||
suspend fun setAutoUpdate(allow: Boolean)
|
||||
|
||||
suspend fun setAutoSync(autoSync: AutoSync)
|
||||
|
@ -0,0 +1,26 @@
|
||||
package com.looker.droidify.datastore.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
sealed class LegacyInstallerComponent {
|
||||
@Serializable
|
||||
object Unspecified : LegacyInstallerComponent()
|
||||
|
||||
@Serializable
|
||||
object AlwaysChoose : LegacyInstallerComponent()
|
||||
|
||||
@Serializable
|
||||
data class Component(
|
||||
val clazz: String,
|
||||
val activity: String,
|
||||
) : LegacyInstallerComponent() {
|
||||
fun update(
|
||||
newClazz: String? = null,
|
||||
newActivity: String? = null,
|
||||
): Component = copy(
|
||||
clazz = newClazz ?: clazz,
|
||||
activity = newActivity ?: activity
|
||||
)
|
||||
}
|
||||
}
|
@ -32,7 +32,7 @@ import kotlinx.coroutines.sync.withLock
|
||||
|
||||
class InstallManager(
|
||||
private val context: Context,
|
||||
settingsRepository: SettingsRepository
|
||||
private val settingsRepository: SettingsRepository
|
||||
) {
|
||||
|
||||
private val installItems = Channel<InstallItem>()
|
||||
@ -115,7 +115,7 @@ class InstallManager(
|
||||
private suspend fun setInstaller(installerType: InstallerType) {
|
||||
lock.withLock {
|
||||
_installer = when (installerType) {
|
||||
InstallerType.LEGACY -> LegacyInstaller(context)
|
||||
InstallerType.LEGACY -> LegacyInstaller(context, settingsRepository)
|
||||
InstallerType.SESSION -> SessionInstaller(context)
|
||||
InstallerType.SHIZUKU -> ShizukuInstaller(context)
|
||||
InstallerType.ROOT -> RootInstaller(context)
|
||||
|
@ -1,20 +1,29 @@
|
||||
package com.looker.droidify.installer.installers
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.AndroidRuntimeException
|
||||
import androidx.core.net.toUri
|
||||
import com.looker.droidify.R
|
||||
import com.looker.droidify.datastore.SettingsRepository
|
||||
import com.looker.droidify.datastore.get
|
||||
import com.looker.droidify.datastore.model.LegacyInstallerComponent
|
||||
import com.looker.droidify.domain.model.PackageName
|
||||
import com.looker.droidify.installer.model.InstallItem
|
||||
import com.looker.droidify.installer.model.InstallState
|
||||
import com.looker.droidify.utility.common.SdkCheck
|
||||
import com.looker.droidify.utility.common.cache.Cache
|
||||
import com.looker.droidify.utility.common.extension.intent
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
class LegacyInstaller(private val context: Context) : Installer {
|
||||
class LegacyInstaller(
|
||||
private val context: Context,
|
||||
private val settingsRepository: SettingsRepository
|
||||
) : Installer {
|
||||
|
||||
companion object {
|
||||
private const val APK_MIME = "application/vnd.android.package-archive"
|
||||
@ -22,29 +31,50 @@ class LegacyInstaller(private val context: Context) : Installer {
|
||||
|
||||
override suspend fun install(
|
||||
installItem: InstallItem,
|
||||
): InstallState = suspendCancellableCoroutine { cont ->
|
||||
): InstallState {
|
||||
val installFlag = if (SdkCheck.isNougat) Intent.FLAG_GRANT_READ_URI_PERMISSION else 0
|
||||
val fileUri = if (SdkCheck.isNougat) {
|
||||
Cache.getReleaseUri(
|
||||
context,
|
||||
installItem.installFileName
|
||||
)
|
||||
Cache.getReleaseUri(context, installItem.installFileName)
|
||||
} else {
|
||||
Cache.getReleaseFile(context, installItem.installFileName).toUri()
|
||||
}
|
||||
val installIntent = intent(Intent.ACTION_INSTALL_PACKAGE) {
|
||||
setDataAndType(fileUri, APK_MIME)
|
||||
flags = installFlag
|
||||
}
|
||||
try {
|
||||
context.startActivity(installIntent)
|
||||
cont.resume(InstallState.Installed)
|
||||
} catch (e: AndroidRuntimeException) {
|
||||
installIntent.flags = installFlag or Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
context.startActivity(installIntent)
|
||||
cont.resume(InstallState.Installed)
|
||||
} catch (e: Exception) {
|
||||
cont.resume(InstallState.Failed)
|
||||
|
||||
val comp = settingsRepository.get { legacyInstallerComponent }.firstOrNull()
|
||||
|
||||
return suspendCancellableCoroutine { cont ->
|
||||
val intent = Intent(Intent.ACTION_INSTALL_PACKAGE).apply {
|
||||
setDataAndType(fileUri, APK_MIME)
|
||||
flags = installFlag
|
||||
when (comp) {
|
||||
is LegacyInstallerComponent.Component -> {
|
||||
component = ComponentName(comp.clazz, comp.activity)
|
||||
}
|
||||
else -> {
|
||||
// For Unspecified and AlwaysChoose, don't set component
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val installIntent = when (comp) {
|
||||
LegacyInstallerComponent.AlwaysChoose -> Intent.createChooser(intent, context.getString(
|
||||
R.string.select_installer))
|
||||
else -> intent
|
||||
}
|
||||
|
||||
try {
|
||||
context.startActivity(installIntent)
|
||||
cont.resume(InstallState.Installed)
|
||||
} catch (e: AndroidRuntimeException) {
|
||||
installIntent.flags = installFlag or Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
try {
|
||||
context.startActivity(installIntent)
|
||||
cont.resume(InstallState.Installed)
|
||||
} catch (e: Exception) {
|
||||
cont.resume(InstallState.Failed)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
cont.resume(InstallState.Failed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ package com.looker.droidify.ui.settings
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
@ -36,6 +37,7 @@ import com.looker.droidify.datastore.extension.themeName
|
||||
import com.looker.droidify.datastore.extension.toTime
|
||||
import com.looker.droidify.datastore.model.AutoSync
|
||||
import com.looker.droidify.datastore.model.InstallerType
|
||||
import com.looker.droidify.datastore.model.LegacyInstallerComponent
|
||||
import com.looker.droidify.datastore.model.ProxyType
|
||||
import com.looker.droidify.datastore.model.Theme
|
||||
import com.looker.droidify.utility.common.SdkCheck
|
||||
@ -53,6 +55,7 @@ import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.days
|
||||
import kotlin.time.Duration.Companion.hours
|
||||
import com.google.android.material.R as MaterialR
|
||||
import androidx.core.net.toUri
|
||||
|
||||
@AndroidEntryPoint
|
||||
class SettingsFragment : Fragment() {
|
||||
@ -230,6 +233,56 @@ class SettingsFragment : Fragment() {
|
||||
onClick = { viewModel.setInstaller(requireContext(), it) }
|
||||
)
|
||||
}
|
||||
val pm = requireContext().packageManager
|
||||
legacyInstallerComponent.connect(
|
||||
titleText = getString(R.string.legacyInstallerComponent),
|
||||
setting = viewModel.getSetting { legacyInstallerComponent },
|
||||
map = {
|
||||
when (it) {
|
||||
is LegacyInstallerComponent.Component -> {
|
||||
val component = it
|
||||
val appLabel = runCatching {
|
||||
val info = pm.getApplicationInfo(component.clazz, 0)
|
||||
pm.getApplicationLabel(info).toString()
|
||||
}.getOrElse { component.clazz }
|
||||
"$appLabel (${component.activity})"
|
||||
}
|
||||
LegacyInstallerComponent.Unspecified -> getString(R.string.unspecified)
|
||||
LegacyInstallerComponent.AlwaysChoose -> getString(R.string.always_choose)
|
||||
null -> getString(R.string.unspecified)
|
||||
}
|
||||
},
|
||||
) { component, valueToString ->
|
||||
val installerOptions = run {
|
||||
var contentProtocol = "content://"
|
||||
val intent = Intent(Intent.ACTION_INSTALL_PACKAGE).apply {
|
||||
setDataAndType(contentProtocol.toUri(), "application/vnd.android.package-archive")
|
||||
}
|
||||
val activities = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
|
||||
listOf(
|
||||
LegacyInstallerComponent.Unspecified,
|
||||
LegacyInstallerComponent.AlwaysChoose
|
||||
) + activities.map {
|
||||
LegacyInstallerComponent.Component(
|
||||
clazz = it.activityInfo.packageName,
|
||||
activity = it.activityInfo.name,
|
||||
)
|
||||
}
|
||||
}
|
||||
addSingleCorrectDialog(
|
||||
initialValue = component ?: LegacyInstallerComponent.Unspecified,
|
||||
values = installerOptions,
|
||||
title = R.string.legacyInstallerComponent,
|
||||
iconRes = R.drawable.ic_apk_install,
|
||||
valueToString = valueToString,
|
||||
onClick = { viewModel.setLegacyInstallerComponentComponent(it) },
|
||||
)
|
||||
}
|
||||
incompatibleUpdates.connect(
|
||||
titleText = getString(R.string.incompatible_versions),
|
||||
contentText = getString(R.string.incompatible_versions_summary),
|
||||
setting = viewModel.getInitialSetting { incompatibleVersions },
|
||||
)
|
||||
proxyType.connect(
|
||||
titleText = getString(R.string.proxy_type),
|
||||
setting = viewModel.getSetting { proxy.type },
|
||||
@ -389,6 +442,9 @@ class SettingsFragment : Fragment() {
|
||||
proxyHost.root.isVisible = allowProxies
|
||||
proxyPort.root.isVisible = allowProxies
|
||||
forceCleanUp.root.isVisible = settings.cleanUpInterval == Duration.INFINITE
|
||||
|
||||
val useLegacyInstaller = settings.installerType == InstallerType.LEGACY
|
||||
legacyInstallerComponent.root.isVisible = useLegacyInstaller
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@ import com.looker.droidify.datastore.model.AutoSync
|
||||
import com.looker.droidify.datastore.model.InstallerType
|
||||
import com.looker.droidify.datastore.model.InstallerType.ROOT
|
||||
import com.looker.droidify.datastore.model.InstallerType.SHIZUKU
|
||||
import com.looker.droidify.datastore.model.LegacyInstallerComponent
|
||||
import com.looker.droidify.datastore.model.ProxyType
|
||||
import com.looker.droidify.datastore.model.Theme
|
||||
import com.looker.droidify.installer.installers.isMagiskGranted
|
||||
@ -191,6 +192,12 @@ class SettingsViewModel
|
||||
}
|
||||
}
|
||||
|
||||
fun setLegacyInstallerComponentComponent(component: LegacyInstallerComponent?) {
|
||||
viewModelScope.launch {
|
||||
settingsRepository.setLegacyInstallerComponent(component)
|
||||
}
|
||||
}
|
||||
|
||||
fun exportSettings(file: Uri) {
|
||||
viewModelScope.launch {
|
||||
settingsRepository.export(file)
|
||||
|
@ -145,6 +145,9 @@
|
||||
<include
|
||||
android:id="@+id/installer"
|
||||
layout="@layout/enum_type" />
|
||||
<include
|
||||
android:id="@+id/legacy_installer_component"
|
||||
layout="@layout/enum_type" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
|
@ -103,12 +103,15 @@
|
||||
<string name="install">Install</string>
|
||||
<string name="install_types">Installation Types</string>
|
||||
<string name="installer">Installer</string>
|
||||
<string name="legacyInstallerComponent">Legacy Installer Component</string>
|
||||
<string name="unspecified">Unspecified</string>
|
||||
<string name="insufficient_storage">Insufficient Space</string>
|
||||
<string name="insufficient_storage_DESC">There is not enough free space on the device to install this application. Try clearing some space</string>
|
||||
<string name="legacy_installer">Legacy Installer</string>
|
||||
<string name="session_installer">Session Installer</string>
|
||||
<string name="root_installer">Root Installer</string>
|
||||
<string name="shizuku_installer">Shizuku Installer</string>
|
||||
<string name="shizuku_legacy_installer">Shizuku Legacy Installer</string>
|
||||
<string name="shizuku_not_alive">Shizuku is not running</string>
|
||||
<string name="shizuku_not_installed">Shizuku is not installed</string>
|
||||
<string name="installed">Installed</string>
|
||||
@ -238,4 +241,6 @@
|
||||
<string name="label_open_video">Video</string>
|
||||
<string name="label_targets_sdk">Targets: Android %s</string>
|
||||
<string name="label_unknown_sdk">Unknown (%d)</string>
|
||||
<string name="always_choose">Always Choose</string>
|
||||
<string name="select_installer">Select installer</string>
|
||||
</resources>
|
||||
|
Loading…
x
Reference in New Issue
Block a user