1 /* <lambda>null2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.spa.app.appinfo 18 19 import android.app.AppOpsManager.MODE_ALLOWED 20 import android.app.AppOpsManager.MODE_DEFAULT 21 import android.app.AppOpsManager.MODE_IGNORED 22 import android.app.AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED 23 import android.content.Context 24 import android.content.pm.ApplicationInfo 25 import android.os.Build 26 import android.permission.PermissionControllerManager.HIBERNATION_ELIGIBILITY_EXEMPT_BY_SYSTEM 27 import android.permission.PermissionControllerManager.HIBERNATION_ELIGIBILITY_UNKNOWN 28 import android.provider.DeviceConfig 29 import android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION 30 import androidx.compose.runtime.Composable 31 import androidx.compose.runtime.derivedStateOf 32 import androidx.compose.runtime.remember 33 import androidx.compose.ui.platform.LocalContext 34 import com.android.settings.R 35 import com.android.settings.Utils.PROPERTY_APP_HIBERNATION_ENABLED 36 import com.android.settings.Utils.PROPERTY_HIBERNATION_TARGETS_PRE_S_APPS 37 import com.android.settingslib.spa.framework.compose.OverridableFlow 38 import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi 39 import androidx.lifecycle.compose.collectAsStateWithLifecycle 40 import com.android.settingslib.spa.framework.compose.stateOf 41 import com.android.settingslib.spa.widget.preference.SwitchPreference 42 import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel 43 import com.android.settingslib.spaprivileged.framework.common.appHibernationManager 44 import com.android.settingslib.spaprivileged.framework.common.appOpsManager 45 import com.android.settingslib.spaprivileged.framework.common.asUser 46 import com.android.settingslib.spaprivileged.framework.common.permissionControllerManager 47 import com.android.settingslib.spaprivileged.model.app.userHandle 48 import kotlinx.coroutines.Dispatchers 49 import kotlinx.coroutines.asExecutor 50 import kotlinx.coroutines.flow.flow 51 import kotlinx.coroutines.withContext 52 import kotlin.coroutines.resume 53 import kotlin.coroutines.suspendCoroutine 54 55 @OptIn(ExperimentalLifecycleComposeApi::class) 56 @Composable 57 fun HibernationSwitchPreference(app: ApplicationInfo) { 58 val context = LocalContext.current 59 val presenter = remember { HibernationSwitchPresenter(context, app) } 60 if (!presenter.isAvailable()) return 61 62 val isEligibleState = presenter.isEligibleFlow.collectAsStateWithLifecycle(initialValue = false) 63 val isCheckedState = presenter.isCheckedFlow.collectAsStateWithLifecycle(initialValue = null) 64 SwitchPreference(remember { 65 object : SwitchPreferenceModel { 66 override val title = context.getString(R.string.unused_apps_switch) 67 override val summary = stateOf(context.getString(R.string.unused_apps_switch_summary)) 68 override val changeable = isEligibleState 69 70 override val checked = derivedStateOf { 71 if (!changeable.value) false else isCheckedState.value 72 } 73 74 override val onCheckedChange = presenter::onCheckedChange 75 } 76 }) 77 } 78 79 private class HibernationSwitchPresenter(context: Context, private val app: ApplicationInfo) { 80 private val appOpsManager = context.appOpsManager 81 private val permissionControllerManager = 82 context.asUser(app.userHandle).permissionControllerManager 83 private val appHibernationManager = context.appHibernationManager 84 private val executor = Dispatchers.IO.asExecutor() 85 isAvailablenull86 fun isAvailable() = 87 DeviceConfig.getBoolean(NAMESPACE_APP_HIBERNATION, PROPERTY_APP_HIBERNATION_ENABLED, true) 88 89 val isEligibleFlow = flow { 90 val eligibility = getEligibility() 91 emit( 92 eligibility != HIBERNATION_ELIGIBILITY_EXEMPT_BY_SYSTEM && 93 eligibility != HIBERNATION_ELIGIBILITY_UNKNOWN 94 ) 95 } 96 getEligibilitynull97 private suspend fun getEligibility(): Int = suspendCoroutine { continuation -> 98 permissionControllerManager.getHibernationEligibility(app.packageName, executor) { 99 continuation.resume(it) 100 } 101 } 102 <lambda>null103 private val isChecked = OverridableFlow(flow { 104 emit(!isExempt()) 105 }) 106 107 val isCheckedFlow = isChecked.flow 108 <lambda>null109 private suspend fun isExempt(): Boolean = withContext(Dispatchers.IO) { 110 val mode = appOpsManager.checkOpNoThrow( 111 OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, app.uid, app.packageName 112 ) 113 if (mode == MODE_DEFAULT) isExemptByDefault() else mode != MODE_ALLOWED 114 } 115 isExemptByDefaultnull116 private fun isExemptByDefault() = 117 !hibernationTargetsPreSApps() && app.targetSdkVersion <= Build.VERSION_CODES.Q 118 119 private fun hibernationTargetsPreSApps() = DeviceConfig.getBoolean( 120 NAMESPACE_APP_HIBERNATION, PROPERTY_HIBERNATION_TARGETS_PRE_S_APPS, false 121 ) 122 123 fun onCheckedChange(newChecked: Boolean) { 124 try { 125 appOpsManager.setUidMode( 126 OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, 127 app.uid, 128 if (newChecked) MODE_ALLOWED else MODE_IGNORED, 129 ) 130 if (!newChecked) { 131 appHibernationManager.setHibernatingForUser(app.packageName, false) 132 appHibernationManager.setHibernatingGlobally(app.packageName, false) 133 } 134 isChecked.override(newChecked) 135 } catch (_: RuntimeException) { 136 } 137 } 138 } 139