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:
Mark Kim
2023-12-13 15:42:52 +00:00
parent ac90fd15c7
commit 5528e94455
8 changed files with 72 additions and 18 deletions

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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)) {

View File

@@ -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
} }
}) })

View File

@@ -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
} }

View File

@@ -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()

View File

@@ -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)
} }
} }
} }