1 /*
2  * 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.settingslib.spaprivileged.template.app
18 
19 import android.app.AppOpsManager.MODE_ALLOWED
20 import android.app.AppOpsManager.MODE_DEFAULT
21 import android.app.AppOpsManager.MODE_ERRORED
22 import android.content.Context
23 import android.content.pm.ApplicationInfo
24 import androidx.compose.runtime.Composable
25 import androidx.compose.runtime.State
26 import androidx.compose.runtime.derivedStateOf
27 import androidx.compose.runtime.livedata.observeAsState
28 import androidx.compose.runtime.remember
29 import com.android.settingslib.spa.framework.compose.stateOf
30 import com.android.settingslib.spa.framework.util.asyncMapItem
31 import com.android.settingslib.spa.framework.util.filterItem
32 import com.android.settingslib.spaprivileged.model.app.AppOpsController
33 import com.android.settingslib.spaprivileged.model.app.AppRecord
34 import com.android.settingslib.spaprivileged.model.app.IAppOpsController
35 import com.android.settingslib.spaprivileged.model.app.IPackageManagers
36 import com.android.settingslib.spaprivileged.model.app.PackageManagers
37 import kotlinx.coroutines.flow.Flow
38 import kotlinx.coroutines.flow.combine
39 import kotlinx.coroutines.flow.map
40 
41 data class AppOpPermissionRecord(
42     override val app: ApplicationInfo,
43     val hasRequestBroaderPermission: Boolean,
44     val hasRequestPermission: Boolean,
45     var appOpsController: IAppOpsController,
46 ) : AppRecord
47 
48 abstract class AppOpPermissionListModel(
49     protected val context: Context,
50     private val packageManagers: IPackageManagers = PackageManagers,
51 ) : TogglePermissionAppListModel<AppOpPermissionRecord> {
52 
53     abstract val appOp: Int
54     abstract val permission: String
55 
56     /**
57      * When set, specifies the broader permission who trumps the [permission].
58      *
59      * When trumped, the [permission] is not changeable and model shows the [permission] as allowed.
60      */
61     open val broaderPermission: String? = null
62 
63     /**
64      * Indicates whether [permission] has protection level appop flag.
65      *
66      * If true, it uses getAppOpPermissionPackages() to fetch bits to decide whether the permission
67      * is requested.
68      */
69     open val permissionHasAppOpFlag: Boolean = true
70 
71     open val modeForNotAllowed: Int = MODE_ERRORED
72 
73     /**
74      * Use AppOpsManager#setUidMode() instead of AppOpsManager#setMode() when set allowed.
75      *
76      * Security or privacy related app-ops should be set with setUidMode() instead of setMode().
77      */
78     open val setModeByUid = false
79 
80     /** These not changeable packages will also be hidden from app list. */
81     private val notChangeablePackages =
82         setOf("android", "com.android.systemui", context.packageName)
83 
84     private fun createAppOpsController(app: ApplicationInfo) =
85         AppOpsController(
86             context = context,
87             app = app,
88             op = appOp,
89             setModeByUid = setModeByUid,
90             modeForNotAllowed = modeForNotAllowed,
91         )
92 
93     private fun createRecord(
94         app: ApplicationInfo,
95         hasRequestPermission: Boolean
96     ): AppOpPermissionRecord =
97         with(packageManagers) {
98             AppOpPermissionRecord(
99                 app = app,
100                 hasRequestBroaderPermission =
101                     broaderPermission?.let { app.hasRequestPermission(it) } ?: false,
102                 hasRequestPermission = hasRequestPermission,
103                 appOpsController = createAppOpsController(app),
104             )
105         }
106 
107     override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
108         if (permissionHasAppOpFlag) {
109             userIdFlow
110                 .map { userId -> packageManagers.getAppOpPermissionPackages(userId, permission) }
111                 .combine(appListFlow) { packageNames, appList ->
112                     appList.map { app ->
113                         createRecord(
114                             app = app,
115                             hasRequestPermission = app.packageName in packageNames,
116                         )
117                     }
118                 }
119         } else {
120             appListFlow.asyncMapItem { app ->
121                 with(packageManagers) { createRecord(app, app.hasRequestPermission(permission)) }
122             }
123         }
124 
125     override fun transformItem(app: ApplicationInfo) =
126         with(packageManagers) {
127             createRecord(
128                 app = app,
129                 hasRequestPermission = app.hasRequestPermission(permission),
130             )
131         }
132 
133     override fun filter(userIdFlow: Flow<Int>, recordListFlow: Flow<List<AppOpPermissionRecord>>) =
134         recordListFlow.filterItem(::isChangeable)
135 
136     /**
137      * Defining the default behavior as permissible as long as the package requested this permission
138      * (This means pre-M gets approval during install time; M apps gets approval during runtime).
139      */
140     @Composable
141     override fun isAllowed(record: AppOpPermissionRecord): State<Boolean?> {
142         if (record.hasRequestBroaderPermission) {
143             // Broader permission trumps the specific permission.
144             return stateOf(true)
145         }
146 
147         val mode = record.appOpsController.mode.observeAsState()
148         return remember {
149             derivedStateOf {
150                 when (mode.value) {
151                     null -> null
152                     MODE_ALLOWED -> true
153                     MODE_DEFAULT ->
154                         with(packageManagers) { record.app.hasGrantPermission(permission) }
155                     else -> false
156                 }
157             }
158         }
159     }
160 
161     override fun isChangeable(record: AppOpPermissionRecord) =
162         record.hasRequestPermission &&
163             !record.hasRequestBroaderPermission &&
164             record.app.packageName !in notChangeablePackages
165 
166     override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) {
167         record.appOpsController.setAllowed(newAllowed)
168     }
169 }
170