Update archive's button enable
property whenever hibernation toggle
has changed its value Test: AppArchiveButtonTest Bug: 304257274 Change-Id: I95b8a2219a7d04437b5c7d217a520e8c8b05d395
This commit is contained in:
@@ -33,10 +33,15 @@ import com.android.settings.R
|
|||||||
import com.android.settingslib.spa.widget.button.ActionButton
|
import com.android.settingslib.spa.widget.button.ActionButton
|
||||||
import com.android.settingslib.spaprivileged.framework.compose.DisposableBroadcastReceiverAsUser
|
import com.android.settingslib.spaprivileged.framework.compose.DisposableBroadcastReceiverAsUser
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
class AppArchiveButton(packageInfoPresenter: PackageInfoPresenter) {
|
class AppArchiveButton(
|
||||||
|
packageInfoPresenter: PackageInfoPresenter,
|
||||||
|
private val isHibernationSwitchEnabledStateFlow: MutableStateFlow<Boolean>,
|
||||||
|
) {
|
||||||
private companion object {
|
private companion object {
|
||||||
private const val LOG_TAG = "AppArchiveButton"
|
private const val LOG_TAG = "AppArchiveButton"
|
||||||
private const val INTENT_ACTION = "com.android.settings.archive.action"
|
private const val INTENT_ACTION = "com.android.settings.archive.action"
|
||||||
@@ -65,18 +70,20 @@ class AppArchiveButton(packageInfoPresenter: PackageInfoPresenter) {
|
|||||||
text = context.getString(R.string.archive),
|
text = context.getString(R.string.archive),
|
||||||
imageVector = Icons.Outlined.CloudUpload,
|
imageVector = Icons.Outlined.CloudUpload,
|
||||||
enabled = remember(app) {
|
enabled = remember(app) {
|
||||||
flow {
|
isHibernationSwitchEnabledStateFlow.asStateFlow().map {
|
||||||
emit(
|
it && isActionButtonEnabledForApp(app)
|
||||||
app.isActionButtonEnabled() && appButtonRepository.isAllowUninstallOrArchive(
|
|
||||||
context,
|
|
||||||
app
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}.flowOn(Dispatchers.Default)
|
}.flowOn(Dispatchers.Default)
|
||||||
}.collectAsStateWithLifecycle(false).value
|
}.collectAsStateWithLifecycle(false).value
|
||||||
) { onArchiveClicked(app) }
|
) { onArchiveClicked(app) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isActionButtonEnabledForApp(app: ApplicationInfo): Boolean {
|
||||||
|
return app.isActionButtonEnabled() && appButtonRepository.isAllowUninstallOrArchive(
|
||||||
|
context,
|
||||||
|
app
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun ApplicationInfo.isActionButtonEnabled(): Boolean {
|
private fun ApplicationInfo.isActionButtonEnabled(): Boolean {
|
||||||
return !isArchived
|
return !isArchived
|
||||||
&& userPackageManager.isAppArchivable(packageName)
|
&& userPackageManager.isAppArchivable(packageName)
|
||||||
|
@@ -23,6 +23,7 @@ import android.content.om.OverlayManager
|
|||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.pm.ResolveInfo
|
import android.content.pm.ResolveInfo
|
||||||
|
import android.util.Log
|
||||||
import com.android.settingslib.RestrictedLockUtils
|
import com.android.settingslib.RestrictedLockUtils
|
||||||
import com.android.settingslib.RestrictedLockUtilsInternal
|
import com.android.settingslib.RestrictedLockUtilsInternal
|
||||||
import com.android.settingslib.Utils
|
import com.android.settingslib.Utils
|
||||||
|
@@ -25,6 +25,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||||||
import com.android.settingslib.applications.AppUtils
|
import com.android.settingslib.applications.AppUtils
|
||||||
import com.android.settingslib.spa.widget.button.ActionButton
|
import com.android.settingslib.spa.widget.button.ActionButton
|
||||||
import com.android.settingslib.spa.widget.button.ActionButtons
|
import com.android.settingslib.spa.widget.button.ActionButtons
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
/**
|
/**
|
||||||
@@ -32,10 +33,17 @@ import com.android.settingslib.spa.widget.button.ActionButtons
|
|||||||
*/
|
*/
|
||||||
fun AppButtons(
|
fun AppButtons(
|
||||||
packageInfoPresenter: PackageInfoPresenter,
|
packageInfoPresenter: PackageInfoPresenter,
|
||||||
|
isHibernationSwitchEnabledStateFlow: MutableStateFlow<Boolean>,
|
||||||
featureFlags: FeatureFlags = FeatureFlagsImpl()
|
featureFlags: FeatureFlags = FeatureFlagsImpl()
|
||||||
) {
|
) {
|
||||||
if (remember(packageInfoPresenter) { packageInfoPresenter.isMainlineModule() }) return
|
if (remember(packageInfoPresenter) { packageInfoPresenter.isMainlineModule() }) return
|
||||||
val presenter = remember { AppButtonsPresenter(packageInfoPresenter, featureFlags) }
|
val presenter = remember {
|
||||||
|
AppButtonsPresenter(
|
||||||
|
packageInfoPresenter,
|
||||||
|
isHibernationSwitchEnabledStateFlow,
|
||||||
|
featureFlags
|
||||||
|
)
|
||||||
|
}
|
||||||
ActionButtons(actionButtons = presenter.getActionButtons())
|
ActionButtons(actionButtons = presenter.getActionButtons())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,6 +52,7 @@ private fun PackageInfoPresenter.isMainlineModule(): Boolean =
|
|||||||
|
|
||||||
private class AppButtonsPresenter(
|
private class AppButtonsPresenter(
|
||||||
private val packageInfoPresenter: PackageInfoPresenter,
|
private val packageInfoPresenter: PackageInfoPresenter,
|
||||||
|
isHibernationSwitchEnabledStateFlow: MutableStateFlow<Boolean>,
|
||||||
private val featureFlags: FeatureFlags
|
private val featureFlags: FeatureFlags
|
||||||
) {
|
) {
|
||||||
private val appLaunchButton = AppLaunchButton(packageInfoPresenter)
|
private val appLaunchButton = AppLaunchButton(packageInfoPresenter)
|
||||||
@@ -52,7 +61,8 @@ private class AppButtonsPresenter(
|
|||||||
private val appUninstallButton = AppUninstallButton(packageInfoPresenter)
|
private val appUninstallButton = AppUninstallButton(packageInfoPresenter)
|
||||||
private val appClearButton = AppClearButton(packageInfoPresenter)
|
private val appClearButton = AppClearButton(packageInfoPresenter)
|
||||||
private val appForceStopButton = AppForceStopButton(packageInfoPresenter)
|
private val appForceStopButton = AppForceStopButton(packageInfoPresenter)
|
||||||
private val appArchiveButton = AppArchiveButton(packageInfoPresenter)
|
private val appArchiveButton =
|
||||||
|
AppArchiveButton(packageInfoPresenter, isHibernationSwitchEnabledStateFlow)
|
||||||
private val appRestoreButton = AppRestoreButton(packageInfoPresenter)
|
private val appRestoreButton = AppRestoreButton(packageInfoPresenter)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@@ -50,6 +50,7 @@ import com.android.settingslib.spa.widget.scaffold.RegularScaffold
|
|||||||
import com.android.settingslib.spa.widget.ui.Category
|
import com.android.settingslib.spa.widget.ui.Category
|
||||||
import com.android.settingslib.spaprivileged.model.app.toRoute
|
import com.android.settingslib.spaprivileged.model.app.toRoute
|
||||||
import com.android.settingslib.spaprivileged.template.app.AppInfoProvider
|
import com.android.settingslib.spaprivileged.template.app.AppInfoProvider
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
private const val PACKAGE_NAME = "packageName"
|
private const val PACKAGE_NAME = "packageName"
|
||||||
private const val USER_ID = "userId"
|
private const val USER_ID = "userId"
|
||||||
@@ -133,10 +134,11 @@ private fun AppInfoSettings(packageInfoPresenter: PackageInfoPresenter) {
|
|||||||
val packageInfo = packageInfoState.value ?: return@RegularScaffold
|
val packageInfo = packageInfoState.value ?: return@RegularScaffold
|
||||||
val app = packageInfo.applicationInfo ?: return@RegularScaffold
|
val app = packageInfo.applicationInfo ?: return@RegularScaffold
|
||||||
val appInfoProvider = remember(packageInfo) { AppInfoProvider(packageInfo) }
|
val appInfoProvider = remember(packageInfo) { AppInfoProvider(packageInfo) }
|
||||||
|
val isHibernationSwitchEnabledStateFlow = MutableStateFlow(false)
|
||||||
|
|
||||||
appInfoProvider.AppInfo()
|
appInfoProvider.AppInfo()
|
||||||
|
|
||||||
AppButtons(packageInfoPresenter)
|
AppButtons(packageInfoPresenter, isHibernationSwitchEnabledStateFlow)
|
||||||
|
|
||||||
AppSettingsPreference(app)
|
AppSettingsPreference(app)
|
||||||
AppAllServicesPreference(app)
|
AppAllServicesPreference(app)
|
||||||
@@ -152,7 +154,7 @@ private fun AppInfoSettings(packageInfoPresenter: PackageInfoPresenter) {
|
|||||||
DefaultAppShortcuts(app)
|
DefaultAppShortcuts(app)
|
||||||
|
|
||||||
Category(title = stringResource(R.string.unused_apps_category)) {
|
Category(title = stringResource(R.string.unused_apps_category)) {
|
||||||
HibernationSwitchPreference(app)
|
HibernationSwitchPreference(app, isHibernationSwitchEnabledStateFlow)
|
||||||
}
|
}
|
||||||
|
|
||||||
Category(title = stringResource(R.string.advanced_apps)) {
|
Category(title = stringResource(R.string.advanced_apps)) {
|
||||||
|
@@ -48,11 +48,15 @@ import kotlin.coroutines.resume
|
|||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.asExecutor
|
import kotlinx.coroutines.asExecutor
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HibernationSwitchPreference(app: ApplicationInfo) {
|
fun HibernationSwitchPreference(
|
||||||
|
app: ApplicationInfo,
|
||||||
|
isHibernationSwitchEnabledStateFlow: MutableStateFlow<Boolean>
|
||||||
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val presenter = remember(app) { HibernationSwitchPresenter(context, app) }
|
val presenter = remember(app) { HibernationSwitchPresenter(context, app) }
|
||||||
if (!presenter.isAvailable()) return
|
if (!presenter.isAvailable()) return
|
||||||
@@ -73,7 +77,14 @@ fun HibernationSwitchPreference(app: ApplicationInfo) {
|
|||||||
context.getString(R.string.unused_apps_switch_summary)
|
context.getString(R.string.unused_apps_switch_summary)
|
||||||
}
|
}
|
||||||
override val changeable = { isEligibleState }
|
override val changeable = { isEligibleState }
|
||||||
override val checked = { if (changeable()) isCheckedState.value else false }
|
override val checked = {
|
||||||
|
val result = if (changeable()) isCheckedState.value else false
|
||||||
|
result.also { isChecked ->
|
||||||
|
isChecked?.let {
|
||||||
|
isHibernationSwitchEnabledStateFlow.value = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
override val onCheckedChange = presenter::onCheckedChange
|
override val onCheckedChange = presenter::onCheckedChange
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@@ -26,8 +26,10 @@ import androidx.compose.ui.test.junit4.createComposeRule
|
|||||||
import androidx.test.core.app.ApplicationProvider
|
import androidx.test.core.app.ApplicationProvider
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import com.android.settings.R
|
import com.android.settings.R
|
||||||
|
import com.android.settingslib.spa.testutils.delay
|
||||||
import com.android.settingslib.spa.widget.button.ActionButton
|
import com.android.settingslib.spa.widget.button.ActionButton
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -52,6 +54,8 @@ class AppArchiveButtonTest {
|
|||||||
|
|
||||||
private val packageInstaller = mock<PackageInstaller>()
|
private val packageInstaller = mock<PackageInstaller>()
|
||||||
|
|
||||||
|
private val isHibernationSwitchEnabledStateFlow = MutableStateFlow(true)
|
||||||
|
|
||||||
private lateinit var appArchiveButton: AppArchiveButton
|
private lateinit var appArchiveButton: AppArchiveButton
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@@ -60,7 +64,7 @@ class AppArchiveButtonTest {
|
|||||||
whenever(packageInfoPresenter.userPackageManager).thenReturn(userPackageManager)
|
whenever(packageInfoPresenter.userPackageManager).thenReturn(userPackageManager)
|
||||||
whenever(userPackageManager.packageInstaller).thenReturn(packageInstaller)
|
whenever(userPackageManager.packageInstaller).thenReturn(packageInstaller)
|
||||||
whenever(packageInfoPresenter.packageName).thenReturn(PACKAGE_NAME)
|
whenever(packageInfoPresenter.packageName).thenReturn(PACKAGE_NAME)
|
||||||
appArchiveButton = AppArchiveButton(packageInfoPresenter)
|
appArchiveButton = AppArchiveButton(packageInfoPresenter, isHibernationSwitchEnabledStateFlow)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -89,6 +93,20 @@ class AppArchiveButtonTest {
|
|||||||
assertThat(actionButton.enabled).isFalse()
|
assertThat(actionButton.enabled).isFalse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun appArchiveButton_whenIsHibernationSwitchDisabled_isDisabled() {
|
||||||
|
val app = ApplicationInfo().apply {
|
||||||
|
packageName = PACKAGE_NAME
|
||||||
|
isArchived = false
|
||||||
|
flags = ApplicationInfo.FLAG_INSTALLED
|
||||||
|
}
|
||||||
|
whenever(userPackageManager.isAppArchivable(app.packageName)).thenReturn(true)
|
||||||
|
isHibernationSwitchEnabledStateFlow.value = false
|
||||||
|
val enabledActionButton = setContent(app)
|
||||||
|
|
||||||
|
assertThat(enabledActionButton.enabled).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun appArchiveButton_displaysRightTextAndIcon() {
|
fun appArchiveButton_displaysRightTextAndIcon() {
|
||||||
val app = ApplicationInfo().apply {
|
val app = ApplicationInfo().apply {
|
||||||
@@ -126,6 +144,7 @@ class AppArchiveButtonTest {
|
|||||||
composeTestRule.setContent {
|
composeTestRule.setContent {
|
||||||
actionButton = appArchiveButton.getActionButton(app)
|
actionButton = appArchiveButton.getActionButton(app)
|
||||||
}
|
}
|
||||||
|
composeTestRule.delay()
|
||||||
return actionButton
|
return actionButton
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -68,6 +68,7 @@ class AppButtonsTest {
|
|||||||
private lateinit var packageInstaller: PackageInstaller
|
private lateinit var packageInstaller: PackageInstaller
|
||||||
|
|
||||||
private val featureFlags = FakeFeatureFlagsImpl()
|
private val featureFlags = FakeFeatureFlagsImpl()
|
||||||
|
private val isHibernationSwitchEnabledStateFlow = MutableStateFlow(true)
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
@@ -175,7 +176,7 @@ class AppButtonsTest {
|
|||||||
private fun setContent(packageInfo: PackageInfo = PACKAGE_INFO) {
|
private fun setContent(packageInfo: PackageInfo = PACKAGE_INFO) {
|
||||||
whenever(packageInfoPresenter.flow).thenReturn(MutableStateFlow(packageInfo))
|
whenever(packageInfoPresenter.flow).thenReturn(MutableStateFlow(packageInfo))
|
||||||
composeTestRule.setContent {
|
composeTestRule.setContent {
|
||||||
AppButtons(packageInfoPresenter, featureFlags)
|
AppButtons(packageInfoPresenter, isHibernationSwitchEnabledStateFlow, featureFlags)
|
||||||
}
|
}
|
||||||
|
|
||||||
composeTestRule.delay()
|
composeTestRule.delay()
|
||||||
|
@@ -70,6 +70,7 @@ import org.mockito.Spy
|
|||||||
import org.mockito.junit.MockitoJUnit
|
import org.mockito.junit.MockitoJUnit
|
||||||
import org.mockito.junit.MockitoRule
|
import org.mockito.junit.MockitoRule
|
||||||
import java.util.function.IntConsumer
|
import java.util.function.IntConsumer
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import org.mockito.Mockito.`when` as whenever
|
import org.mockito.Mockito.`when` as whenever
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@@ -99,6 +100,8 @@ class HibernationSwitchPreferenceTest {
|
|||||||
private val hibernationTargetsPreSConfig =
|
private val hibernationTargetsPreSConfig =
|
||||||
TestDeviceConfig(NAMESPACE_APP_HIBERNATION, PROPERTY_HIBERNATION_TARGETS_PRE_S_APPS)
|
TestDeviceConfig(NAMESPACE_APP_HIBERNATION, PROPERTY_HIBERNATION_TARGETS_PRE_S_APPS)
|
||||||
|
|
||||||
|
private val isHibernationSwitchEnabledStateFlow = MutableStateFlow(true)
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
hibernationEnabledConfig.override(true)
|
hibernationEnabledConfig.override(true)
|
||||||
@@ -234,7 +237,7 @@ class HibernationSwitchPreferenceTest {
|
|||||||
private fun setContent(app: ApplicationInfo = TARGET_R_APP) {
|
private fun setContent(app: ApplicationInfo = TARGET_R_APP) {
|
||||||
composeTestRule.setContent {
|
composeTestRule.setContent {
|
||||||
CompositionLocalProvider(LocalContext provides context) {
|
CompositionLocalProvider(LocalContext provides context) {
|
||||||
HibernationSwitchPreference(app)
|
HibernationSwitchPreference(app, isHibernationSwitchEnabledStateFlow)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user