• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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