Refresh the App Info Settings

When apk upgraded or downgraded.

And only close the page when the package is fully removed.

Bug: 314562958
Test: manual - on App Info Settings
Test: unit test
Change-Id: Ifdff714da99e31f9c5f237a0c3342de7a0797ec4
This commit is contained in:
Chaohui Wang
2023-12-03 18:00:51 +08:00
parent 0a32ca2bbc
commit de3fe3744f
5 changed files with 93 additions and 138 deletions

View File

@@ -23,7 +23,9 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Report
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.settings.R
import com.android.settingslib.RestrictedLockUtils
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
@@ -35,6 +37,9 @@ import com.android.settingslib.spa.widget.dialog.rememberAlertDialogPresenter
import com.android.settingslib.spaprivileged.model.app.hasFlag
import com.android.settingslib.spaprivileged.model.app.isActiveAdmin
import com.android.settingslib.spaprivileged.model.app.userId
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
class AppForceStopButton(
private val packageInfoPresenter: PackageInfoPresenter,
@@ -47,9 +52,13 @@ class AppForceStopButton(
fun getActionButton(app: ApplicationInfo): ActionButton {
val dialogPresenter = confirmDialogPresenter()
return ActionButton(
text = context.getString(R.string.force_stop),
text = stringResource(R.string.force_stop),
imageVector = Icons.Outlined.Report,
enabled = isForceStopButtonEnable(app),
enabled = remember(app) {
flow {
emit(isForceStopButtonEnable(app))
}.flowOn(Dispatchers.Default)
}.collectAsStateWithLifecycle(false).value,
) { onForceStopButtonClicked(app, dialogPresenter) }
}

View File

@@ -32,10 +32,10 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavType
import androidx.navigation.navArgument
import com.android.settings.flags.Flags
import com.android.settings.R
import com.android.settings.applications.AppInfoBase
import com.android.settings.applications.appinfo.AppInfoDashboardFragment
import com.android.settings.flags.Flags
import com.android.settings.spa.SpaActivity.Companion.startSpaActivity
import com.android.settings.spa.app.appcompat.UserAspectRatioAppPreference
import com.android.settings.spa.app.specialaccess.AlarmsAndRemindersAppListProvider
@@ -45,7 +45,6 @@ import com.android.settings.spa.app.specialaccess.ModifySystemSettingsAppListPro
import com.android.settings.spa.app.specialaccess.PictureInPictureListProvider
import com.android.settings.spa.app.specialaccess.VoiceActivationAppsListProvider
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.compose.LifecycleEffect
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
import com.android.settingslib.spa.widget.ui.Category
@@ -75,7 +74,7 @@ object AppInfoSettingsProvider : SettingsPageProvider {
PackageInfoPresenter(context, packageName, userId, coroutineScope)
}
AppInfoSettings(packageInfoPresenter)
packageInfoPresenter.PackageRemoveDetector()
packageInfoPresenter.PackageFullyRemovedEffect()
}
@Composable
@@ -120,8 +119,7 @@ object AppInfoSettingsProvider : SettingsPageProvider {
@Composable
private fun AppInfoSettings(packageInfoPresenter: PackageInfoPresenter) {
LifecycleEffect(onStart = { packageInfoPresenter.reloadPackageInfo() })
val packageInfo = packageInfoPresenter.flow.collectAsStateWithLifecycle().value ?: return
val packageInfo = packageInfoPresenter.flow.collectAsStateWithLifecycle().value ?:return
val app = checkNotNull(packageInfo.applicationInfo)
val featureFlags: FeatureFlags = FeatureFlagsImpl()
RegularScaffold(
@@ -131,7 +129,7 @@ private fun AppInfoSettings(packageInfoPresenter: PackageInfoPresenter) {
AppInfoSettingsMoreOptions(packageInfoPresenter, app)
}
) {
val appInfoProvider = remember { AppInfoProvider(packageInfo) }
val appInfoProvider = remember(packageInfo) { AppInfoProvider(packageInfo) }
appInfoProvider.AppInfo()

View File

@@ -28,7 +28,6 @@ import androidx.navigation.NavType
import androidx.navigation.navArgument
import com.android.settings.R
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.compose.LifecycleEffect
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
import com.android.settingslib.spaprivileged.model.app.toRoute
import com.android.settingslib.spaprivileged.template.app.AppInfoProvider
@@ -54,7 +53,7 @@ object CloneAppInfoSettingsProvider : SettingsPageProvider {
PackageInfoPresenter(context, packageName, userId, coroutineScope)
}
CloneAppInfoSettings(packageInfoPresenter)
packageInfoPresenter.PackageRemoveDetector()
packageInfoPresenter.PackageFullyRemovedEffect()
}
@Composable
@@ -70,7 +69,6 @@ object CloneAppInfoSettingsProvider : SettingsPageProvider {
@Composable
private fun CloneAppInfoSettings(packageInfoPresenter: PackageInfoPresenter) {
LifecycleEffect(onStart = { packageInfoPresenter.reloadPackageInfo() })
val packageInfo = packageInfoPresenter.flow.collectAsStateWithLifecycle().value ?: return
RegularScaffold(
title = stringResource(R.string.application_info_label),

View File

@@ -32,14 +32,20 @@ import com.android.settings.spa.app.startUninstallActivity
import com.android.settingslib.spa.framework.compose.LocalNavController
import com.android.settingslib.spaprivileged.framework.common.activityManager
import com.android.settingslib.spaprivileged.framework.common.asUser
import com.android.settingslib.spaprivileged.framework.common.broadcastReceiverAsUserFlow
import com.android.settingslib.spaprivileged.framework.compose.DisposableBroadcastReceiverAsUser
import com.android.settingslib.spaprivileged.model.app.IPackageManagers
import com.android.settingslib.spaprivileged.model.app.PackageManagers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
private const val TAG = "PackageInfoPresenter"
@@ -58,34 +64,33 @@ class PackageInfoPresenter(
private val userHandle = UserHandle.of(userId)
val userContext by lazy { context.asUser(userHandle) }
val userPackageManager: PackageManager by lazy { userContext.packageManager }
private val _flow: MutableStateFlow<PackageInfo?> = MutableStateFlow(null)
val flow: StateFlow<PackageInfo?> = _flow
fun reloadPackageInfo() {
coroutineScope.launch(Dispatchers.IO) {
_flow.value = getPackageInfo()
}
}
val flow: StateFlow<PackageInfo?> = merge(
flowOf(null), // kick an initial value
context.broadcastReceiverAsUserFlow(
intentFilter = IntentFilter().apply {
addAction(Intent.ACTION_PACKAGE_CHANGED)
addAction(Intent.ACTION_PACKAGE_REPLACED)
addAction(Intent.ACTION_PACKAGE_RESTARTED)
addDataScheme("package")
},
userHandle = userHandle,
),
).map { getPackageInfo() }
.stateIn(coroutineScope + Dispatchers.Default, SharingStarted.WhileSubscribed(), null)
/**
* Detects the package removed event.
* Detects the package fully removed event, and close the current page.
*/
@Composable
fun PackageRemoveDetector() {
val intentFilter = IntentFilter(Intent.ACTION_PACKAGE_REMOVED).apply {
fun PackageFullyRemovedEffect() {
val intentFilter = IntentFilter(Intent.ACTION_PACKAGE_FULLY_REMOVED).apply {
addDataScheme("package")
}
val navController = LocalNavController.current
DisposableBroadcastReceiverAsUser(intentFilter, userHandle) { intent ->
if (packageName == intent.data?.schemeSpecificPart) {
val packageInfo = flow.value
if (packageInfo != null && packageInfo.applicationInfo?.isSystemApp == true) {
// System app still exists after uninstalling the updates, refresh the page.
reloadPackageInfo()
} else {
navController.navigateBack()
}
navController.navigateBack()
}
}
}
@@ -97,7 +102,6 @@ class PackageInfoPresenter(
userPackageManager.setApplicationEnabledSetting(
packageName, PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, 0
)
reloadPackageInfo()
}
}
@@ -108,7 +112,6 @@ class PackageInfoPresenter(
userPackageManager.setApplicationEnabledSetting(
packageName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER, 0
)
reloadPackageInfo()
}
}
@@ -123,7 +126,6 @@ class PackageInfoPresenter(
logAction(SettingsEnums.ACTION_SETTINGS_CLEAR_INSTANT_APP)
coroutineScope.launch(Dispatchers.IO) {
userPackageManager.deletePackageAsUser(packageName, null, 0, userId)
reloadPackageInfo()
}
}
@@ -133,7 +135,6 @@ class PackageInfoPresenter(
coroutineScope.launch(Dispatchers.Default) {
Log.d(TAG, "Stopping package $packageName")
context.activityManager.forceStopPackageAsUser(packageName, userId)
reloadPackageInfo()
}
}
@@ -141,7 +142,7 @@ class PackageInfoPresenter(
metricsFeatureProvider.action(context, category, packageName)
}
private fun getPackageInfo() =
private fun getPackageInfo(): PackageInfo? =
packageManagers.getPackageInfoAsUser(
packageName = packageName,
flags = PackageManager.MATCH_ANY_USER.toLong() or