Add HibernationSwitchPreference for Spa
Bug: 236346018 Test: Manual with App Info page Test: Settings Unit tests Change-Id: I23140a2a16b3b5a4b569623504b1838a641611fe
This commit is contained in:
@@ -100,6 +100,10 @@ private fun AppInfoSettings(packageInfoPresenter: PackageInfoPresenter) {
|
|||||||
AppOpenByDefaultPreference(app)
|
AppOpenByDefaultPreference(app)
|
||||||
DefaultAppShortcuts(app)
|
DefaultAppShortcuts(app)
|
||||||
|
|
||||||
|
Category(title = stringResource(R.string.unused_apps_category)) {
|
||||||
|
HibernationSwitchPreference(app)
|
||||||
|
}
|
||||||
|
|
||||||
Category(title = stringResource(R.string.advanced_apps)) {
|
Category(title = stringResource(R.string.advanced_apps)) {
|
||||||
DisplayOverOtherAppsAppListProvider.InfoPageEntryItem(app)
|
DisplayOverOtherAppsAppListProvider.InfoPageEntryItem(app)
|
||||||
ModifySystemSettingsAppListProvider.InfoPageEntryItem(app)
|
ModifySystemSettingsAppListProvider.InfoPageEntryItem(app)
|
||||||
|
@@ -0,0 +1,136 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.settings.spa.app.appinfo
|
||||||
|
|
||||||
|
import android.app.AppOpsManager.MODE_ALLOWED
|
||||||
|
import android.app.AppOpsManager.MODE_DEFAULT
|
||||||
|
import android.app.AppOpsManager.MODE_IGNORED
|
||||||
|
import android.app.AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.ApplicationInfo
|
||||||
|
import android.os.Build
|
||||||
|
import android.permission.PermissionControllerManager.HIBERNATION_ELIGIBILITY_EXEMPT_BY_SYSTEM
|
||||||
|
import android.permission.PermissionControllerManager.HIBERNATION_ELIGIBILITY_UNKNOWN
|
||||||
|
import android.provider.DeviceConfig
|
||||||
|
import android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import com.android.settings.R
|
||||||
|
import com.android.settings.Utils.PROPERTY_APP_HIBERNATION_ENABLED
|
||||||
|
import com.android.settings.Utils.PROPERTY_HIBERNATION_TARGETS_PRE_S_APPS
|
||||||
|
import com.android.settingslib.spa.framework.compose.OverridableFlow
|
||||||
|
import com.android.settingslib.spa.framework.compose.collectAsStateWithLifecycle
|
||||||
|
import com.android.settingslib.spa.framework.compose.stateOf
|
||||||
|
import com.android.settingslib.spa.widget.preference.SwitchPreference
|
||||||
|
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
|
||||||
|
import com.android.settingslib.spaprivileged.framework.common.appHibernationManager
|
||||||
|
import com.android.settingslib.spaprivileged.framework.common.appOpsManager
|
||||||
|
import com.android.settingslib.spaprivileged.framework.common.asUser
|
||||||
|
import com.android.settingslib.spaprivileged.framework.common.permissionControllerManager
|
||||||
|
import com.android.settingslib.spaprivileged.model.app.userHandle
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.asExecutor
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun HibernationSwitchPreference(app: ApplicationInfo) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val presenter = remember { HibernationSwitchPresenter(context, app) }
|
||||||
|
if (!presenter.isAvailable()) return
|
||||||
|
|
||||||
|
val isEligibleState = presenter.isEligibleFlow.collectAsStateWithLifecycle(initialValue = false)
|
||||||
|
val isCheckedState = presenter.isCheckedFlow.collectAsStateWithLifecycle(initialValue = null)
|
||||||
|
SwitchPreference(remember {
|
||||||
|
object : SwitchPreferenceModel {
|
||||||
|
override val title = context.getString(R.string.unused_apps_switch)
|
||||||
|
override val summary = stateOf(context.getString(R.string.unused_apps_switch_summary))
|
||||||
|
override val changeable = isEligibleState
|
||||||
|
|
||||||
|
override val checked = derivedStateOf {
|
||||||
|
if (!changeable.value) false else isCheckedState.value
|
||||||
|
}
|
||||||
|
|
||||||
|
override val onCheckedChange = presenter::onCheckedChange
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private class HibernationSwitchPresenter(context: Context, private val app: ApplicationInfo) {
|
||||||
|
private val appOpsManager = context.appOpsManager
|
||||||
|
private val permissionControllerManager =
|
||||||
|
context.asUser(app.userHandle).permissionControllerManager
|
||||||
|
private val appHibernationManager = context.appHibernationManager
|
||||||
|
private val executor = Dispatchers.IO.asExecutor()
|
||||||
|
|
||||||
|
fun isAvailable() =
|
||||||
|
DeviceConfig.getBoolean(NAMESPACE_APP_HIBERNATION, PROPERTY_APP_HIBERNATION_ENABLED, true)
|
||||||
|
|
||||||
|
val isEligibleFlow = flow {
|
||||||
|
val eligibility = getEligibility()
|
||||||
|
emit(
|
||||||
|
eligibility != HIBERNATION_ELIGIBILITY_EXEMPT_BY_SYSTEM &&
|
||||||
|
eligibility != HIBERNATION_ELIGIBILITY_UNKNOWN
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getEligibility(): Int = suspendCoroutine { continuation ->
|
||||||
|
permissionControllerManager.getHibernationEligibility(app.packageName, executor) {
|
||||||
|
continuation.resume(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val isChecked = OverridableFlow(flow {
|
||||||
|
emit(!isExempt())
|
||||||
|
})
|
||||||
|
|
||||||
|
val isCheckedFlow = isChecked.flow
|
||||||
|
|
||||||
|
private suspend fun isExempt(): Boolean = withContext(Dispatchers.IO) {
|
||||||
|
val mode = appOpsManager.checkOpNoThrow(
|
||||||
|
OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, app.uid, app.packageName
|
||||||
|
)
|
||||||
|
if (mode == MODE_DEFAULT) isExemptByDefault() else mode != MODE_ALLOWED
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isExemptByDefault() =
|
||||||
|
!hibernationTargetsPreSApps() && app.targetSdkVersion <= Build.VERSION_CODES.Q
|
||||||
|
|
||||||
|
private fun hibernationTargetsPreSApps() = DeviceConfig.getBoolean(
|
||||||
|
NAMESPACE_APP_HIBERNATION, PROPERTY_HIBERNATION_TARGETS_PRE_S_APPS, false
|
||||||
|
)
|
||||||
|
|
||||||
|
fun onCheckedChange(newChecked: Boolean) {
|
||||||
|
try {
|
||||||
|
appOpsManager.setUidMode(
|
||||||
|
OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
|
||||||
|
app.uid,
|
||||||
|
if (newChecked) MODE_ALLOWED else MODE_IGNORED,
|
||||||
|
)
|
||||||
|
if (!newChecked) {
|
||||||
|
appHibernationManager.setHibernatingForUser(app.packageName, false)
|
||||||
|
appHibernationManager.setHibernatingGlobally(app.packageName, false)
|
||||||
|
}
|
||||||
|
isChecked.override(newChecked)
|
||||||
|
} catch (_: RuntimeException) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package com.android.settings.spa.app.specialaccess
|
package com.android.settings.spa.app.specialaccess
|
||||||
|
|
||||||
import android.app.AlarmManager
|
|
||||||
import android.app.AppOpsManager
|
import android.app.AppOpsManager
|
||||||
import android.app.AppOpsManager.MODE_ALLOWED
|
import android.app.AppOpsManager.MODE_ALLOWED
|
||||||
import android.app.AppOpsManager.MODE_ERRORED
|
import android.app.AppOpsManager.MODE_ERRORED
|
||||||
@@ -24,21 +23,23 @@ import android.content.Context
|
|||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import com.android.settingslib.spaprivileged.framework.common.alarmManager
|
||||||
|
import com.android.settingslib.spaprivileged.framework.common.appOpsManager
|
||||||
import com.android.settingslib.spaprivileged.model.app.userId
|
import com.android.settingslib.spaprivileged.model.app.userId
|
||||||
|
|
||||||
class AlarmsAndRemindersController(
|
class AlarmsAndRemindersController(
|
||||||
context: Context,
|
context: Context,
|
||||||
private val app: ApplicationInfo,
|
private val app: ApplicationInfo,
|
||||||
) {
|
) {
|
||||||
private val alarmManager = context.getSystemService(AlarmManager::class.java)!!
|
private val alarmManager = context.alarmManager
|
||||||
private val appOpsManager = context.getSystemService(AppOpsManager::class.java)!!
|
private val appOpsManager = context.appOpsManager
|
||||||
|
|
||||||
val isAllowed: LiveData<Boolean>
|
val isAllowed: LiveData<Boolean>
|
||||||
get() = _allowed
|
get() = _allowed
|
||||||
|
|
||||||
fun setAllowed(allowed: Boolean) {
|
fun setAllowed(allowed: Boolean) {
|
||||||
val mode = if (allowed) MODE_ALLOWED else MODE_ERRORED
|
val mode = if (allowed) MODE_ALLOWED else MODE_ERRORED
|
||||||
appOpsManager.setUidMode(AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM, app.uid, mode)
|
appOpsManager.setUidMode(AppOpsManager.OP_SCHEDULE_EXACT_ALARM, app.uid, mode)
|
||||||
_allowed.postValue(allowed)
|
_allowed.postValue(allowed)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,8 +47,5 @@ class AlarmsAndRemindersController(
|
|||||||
override fun onActive() {
|
override fun onActive() {
|
||||||
postValue(alarmManager.hasScheduleExactAlarm(app.packageName, app.userId))
|
postValue(alarmManager.hasScheduleExactAlarm(app.packageName, app.userId))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onInactive() {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,6 +19,8 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="com.android.settings.tests.spa_unit">
|
package="com.android.settings.tests.spa_unit">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" />
|
||||||
|
|
||||||
<application android:debuggable="true">
|
<application android:debuggable="true">
|
||||||
<provider android:name="com.android.settings.slices.SettingsSliceProvider"
|
<provider android:name="com.android.settings.slices.SettingsSliceProvider"
|
||||||
android:authorities="${applicationId}.slices"
|
android:authorities="${applicationId}.slices"
|
||||||
|
@@ -0,0 +1,251 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.settings.spa.app.appinfo
|
||||||
|
|
||||||
|
import android.app.AppOpsManager
|
||||||
|
import android.app.AppOpsManager.MODE_ALLOWED
|
||||||
|
import android.app.AppOpsManager.MODE_DEFAULT
|
||||||
|
import android.app.AppOpsManager.MODE_IGNORED
|
||||||
|
import android.app.AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED
|
||||||
|
import android.apphibernation.AppHibernationManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.ApplicationInfo
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.UserHandle
|
||||||
|
import android.permission.PermissionControllerManager
|
||||||
|
import android.permission.PermissionControllerManager.HIBERNATION_ELIGIBILITY_ELIGIBLE
|
||||||
|
import android.permission.PermissionControllerManager.HIBERNATION_ELIGIBILITY_EXEMPT_BY_SYSTEM
|
||||||
|
import android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.test.assertIsDisplayed
|
||||||
|
import androidx.compose.ui.test.assertIsEnabled
|
||||||
|
import androidx.compose.ui.test.assertIsNotDisplayed
|
||||||
|
import androidx.compose.ui.test.assertIsNotEnabled
|
||||||
|
import androidx.compose.ui.test.assertIsOff
|
||||||
|
import androidx.compose.ui.test.assertIsOn
|
||||||
|
import androidx.compose.ui.test.isToggleable
|
||||||
|
import androidx.compose.ui.test.junit4.createComposeRule
|
||||||
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
|
import androidx.compose.ui.test.onRoot
|
||||||
|
import androidx.compose.ui.test.performClick
|
||||||
|
import androidx.test.core.app.ApplicationProvider
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import com.android.settings.R
|
||||||
|
import com.android.settings.Utils.PROPERTY_APP_HIBERNATION_ENABLED
|
||||||
|
import com.android.settings.Utils.PROPERTY_HIBERNATION_TARGETS_PRE_S_APPS
|
||||||
|
import com.android.settings.testutils.TestDeviceConfig
|
||||||
|
import com.android.settingslib.spaprivileged.framework.common.appHibernationManager
|
||||||
|
import com.android.settingslib.spaprivileged.framework.common.appOpsManager
|
||||||
|
import com.android.settingslib.spaprivileged.framework.common.permissionControllerManager
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito.any
|
||||||
|
import org.mockito.Mockito.anyBoolean
|
||||||
|
import org.mockito.Mockito.anyString
|
||||||
|
import org.mockito.Mockito.doAnswer
|
||||||
|
import org.mockito.Mockito.doReturn
|
||||||
|
import org.mockito.Mockito.eq
|
||||||
|
import org.mockito.Mockito.never
|
||||||
|
import org.mockito.Mockito.verify
|
||||||
|
import org.mockito.Spy
|
||||||
|
import org.mockito.junit.MockitoJUnit
|
||||||
|
import org.mockito.junit.MockitoRule
|
||||||
|
import java.util.function.IntConsumer
|
||||||
|
import org.mockito.Mockito.`when` as whenever
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class HibernationSwitchPreferenceTest {
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
@Rule
|
||||||
|
val mockito: MockitoRule = MockitoJUnit.rule()
|
||||||
|
|
||||||
|
@get:Rule
|
||||||
|
val composeTestRule = createComposeRule()
|
||||||
|
|
||||||
|
@Spy
|
||||||
|
private val context: Context = ApplicationProvider.getApplicationContext()
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var permissionControllerManager: PermissionControllerManager
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var appOpsManager: AppOpsManager
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var appHibernationManager: AppHibernationManager
|
||||||
|
|
||||||
|
private val hibernationEnabledConfig =
|
||||||
|
TestDeviceConfig(NAMESPACE_APP_HIBERNATION, PROPERTY_APP_HIBERNATION_ENABLED)
|
||||||
|
|
||||||
|
private val hibernationTargetsPreSConfig =
|
||||||
|
TestDeviceConfig(NAMESPACE_APP_HIBERNATION, PROPERTY_HIBERNATION_TARGETS_PRE_S_APPS)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
hibernationEnabledConfig.override(true)
|
||||||
|
hibernationTargetsPreSConfig.override(false)
|
||||||
|
doReturn(context)
|
||||||
|
.`when`(context).createContextAsUser(UserHandle.getUserHandleForUid(UID), 0)
|
||||||
|
whenever(context.permissionControllerManager).thenReturn(permissionControllerManager)
|
||||||
|
whenever(context.appOpsManager).thenReturn(appOpsManager)
|
||||||
|
whenever(context.appHibernationManager).thenReturn(appHibernationManager)
|
||||||
|
mockHibernationEligibility(HIBERNATION_ELIGIBILITY_ELIGIBLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun cleanUp() {
|
||||||
|
hibernationEnabledConfig.reset()
|
||||||
|
hibernationTargetsPreSConfig.reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mockHibernationEligibility(eligibility: Int) {
|
||||||
|
doAnswer {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
(it.arguments[2] as IntConsumer).accept(eligibility)
|
||||||
|
}.`when`(permissionControllerManager).getHibernationEligibility(
|
||||||
|
eq(PACKAGE_NAME), any(), any()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mockOpsMode(mode: Int) {
|
||||||
|
whenever(
|
||||||
|
appOpsManager.checkOpNoThrow(OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, UID, PACKAGE_NAME)
|
||||||
|
).thenReturn(mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Hibernation disabled - not display`() {
|
||||||
|
hibernationEnabledConfig.override(false)
|
||||||
|
|
||||||
|
setContent()
|
||||||
|
|
||||||
|
composeTestRule.onRoot().assertIsNotDisplayed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Not eligible - displayed but disabled`() {
|
||||||
|
mockHibernationEligibility(HIBERNATION_ELIGIBILITY_EXEMPT_BY_SYSTEM)
|
||||||
|
|
||||||
|
setContent()
|
||||||
|
|
||||||
|
composeTestRule.onNodeWithText(context.getString(R.string.unused_apps_switch))
|
||||||
|
.assertIsDisplayed()
|
||||||
|
.assertIsNotEnabled()
|
||||||
|
.assertIsOff()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `An app targets Q with ops mode default when hibernation targets pre S - not exempted`() {
|
||||||
|
mockOpsMode(MODE_DEFAULT)
|
||||||
|
hibernationTargetsPreSConfig.override(true)
|
||||||
|
|
||||||
|
setContent(TARGET_Q_APP)
|
||||||
|
|
||||||
|
composeTestRule.onNode(isToggleable()).assertIsEnabled().assertIsOn()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `An app targets Q with ops mode default when hibernation targets R - exempted`() {
|
||||||
|
mockOpsMode(MODE_DEFAULT)
|
||||||
|
hibernationTargetsPreSConfig.override(false)
|
||||||
|
|
||||||
|
setContent(TARGET_Q_APP)
|
||||||
|
|
||||||
|
composeTestRule.onNode(isToggleable()).assertIsEnabled().assertIsOff()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `An app targets R with ops mode default - not exempted`() {
|
||||||
|
mockOpsMode(MODE_DEFAULT)
|
||||||
|
|
||||||
|
setContent(TARGET_R_APP)
|
||||||
|
|
||||||
|
composeTestRule.onNode(isToggleable()).assertIsEnabled().assertIsOn()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `An app with ops mode allowed - not exempted`() {
|
||||||
|
mockOpsMode(MODE_ALLOWED)
|
||||||
|
|
||||||
|
setContent()
|
||||||
|
|
||||||
|
composeTestRule.onNode(isToggleable()).assertIsEnabled().assertIsOn()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `An app with ops mode ignored - exempted`() {
|
||||||
|
mockOpsMode(MODE_IGNORED)
|
||||||
|
|
||||||
|
setContent()
|
||||||
|
|
||||||
|
composeTestRule.onNode(isToggleable()).assertIsEnabled().assertIsOff()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `An app is exempted - on click`() {
|
||||||
|
mockOpsMode(MODE_IGNORED)
|
||||||
|
|
||||||
|
setContent()
|
||||||
|
composeTestRule.onRoot().performClick()
|
||||||
|
|
||||||
|
verify(appOpsManager).setUidMode(OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, UID, MODE_ALLOWED)
|
||||||
|
verify(appHibernationManager, never()).setHibernatingForUser(anyString(), anyBoolean())
|
||||||
|
verify(appHibernationManager, never()).setHibernatingGlobally(anyString(), anyBoolean())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `An app is not exempted - on click`() {
|
||||||
|
mockOpsMode(MODE_ALLOWED)
|
||||||
|
|
||||||
|
setContent()
|
||||||
|
composeTestRule.onRoot().performClick()
|
||||||
|
|
||||||
|
verify(appOpsManager).setUidMode(OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, UID, MODE_IGNORED)
|
||||||
|
verify(appHibernationManager).setHibernatingForUser(PACKAGE_NAME, false)
|
||||||
|
verify(appHibernationManager).setHibernatingGlobally(PACKAGE_NAME, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setContent(app: ApplicationInfo = TARGET_R_APP) {
|
||||||
|
composeTestRule.setContent {
|
||||||
|
CompositionLocalProvider(LocalContext provides context) {
|
||||||
|
HibernationSwitchPreference(app)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
const val PACKAGE_NAME = "package name"
|
||||||
|
const val UID = 123
|
||||||
|
|
||||||
|
val TARGET_R_APP = ApplicationInfo().apply {
|
||||||
|
packageName = PACKAGE_NAME
|
||||||
|
uid = UID
|
||||||
|
targetSdkVersion = Build.VERSION_CODES.R
|
||||||
|
}
|
||||||
|
val TARGET_Q_APP = ApplicationInfo().apply {
|
||||||
|
packageName = PACKAGE_NAME
|
||||||
|
uid = UID
|
||||||
|
targetSdkVersion = Build.VERSION_CODES.Q
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.settings.testutils
|
||||||
|
|
||||||
|
import android.provider.DeviceConfig
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A util class used to override [DeviceConfig] value for testing purpose.
|
||||||
|
*/
|
||||||
|
class TestDeviceConfig(private val namespace: String, private val name: String) {
|
||||||
|
private val initialValue = DeviceConfig.getProperty(namespace, name)
|
||||||
|
|
||||||
|
/** Overrides the property value. */
|
||||||
|
fun override(value: Boolean) {
|
||||||
|
DeviceConfig.setProperty(namespace, name, value.toString(), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Resets the property to its initial value before the testing. */
|
||||||
|
fun reset() {
|
||||||
|
DeviceConfig.setProperty(namespace, name, initialValue, false)
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user