Merge "Add HibernationSwitchPreference for Spa"

This commit is contained in:
Chaohui Wang
2022-10-25 03:45:09 +00:00
committed by Android (Google) Code Review
6 changed files with 434 additions and 7 deletions

View File

@@ -100,6 +100,10 @@ private fun AppInfoSettings(packageInfoPresenter: PackageInfoPresenter) {
AppOpenByDefaultPreference(app)
DefaultAppShortcuts(app)
Category(title = stringResource(R.string.unused_apps_category)) {
HibernationSwitchPreference(app)
}
Category(title = stringResource(R.string.advanced_apps)) {
DisplayOverOtherAppsAppListProvider.InfoPageEntryItem(app)
ModifySystemSettingsAppListProvider.InfoPageEntryItem(app)

View File

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

View File

@@ -16,7 +16,6 @@
package com.android.settings.spa.app.specialaccess
import android.app.AlarmManager
import android.app.AppOpsManager
import android.app.AppOpsManager.MODE_ALLOWED
import android.app.AppOpsManager.MODE_ERRORED
@@ -24,21 +23,23 @@ import android.content.Context
import android.content.pm.ApplicationInfo
import androidx.lifecycle.LiveData
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
class AlarmsAndRemindersController(
context: Context,
private val app: ApplicationInfo,
) {
private val alarmManager = context.getSystemService(AlarmManager::class.java)!!
private val appOpsManager = context.getSystemService(AppOpsManager::class.java)!!
private val alarmManager = context.alarmManager
private val appOpsManager = context.appOpsManager
val isAllowed: LiveData<Boolean>
get() = _allowed
fun setAllowed(allowed: Boolean) {
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)
}
@@ -46,8 +47,5 @@ class AlarmsAndRemindersController(
override fun onActive() {
postValue(alarmManager.hasScheduleExactAlarm(app.packageName, app.userId))
}
override fun onInactive() {
}
}
}