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.settings.spa.app.appinfo
18 
19 import android.app.AppOpsManager
20 import android.app.ecm.EnhancedConfirmationManager
21 import android.content.Context
22 import android.content.pm.ApplicationInfo
23 import android.content.pm.PackageManager
24 import android.os.UserManager
25 import android.widget.Toast
26 import androidx.compose.runtime.Composable
27 import androidx.compose.runtime.State
28 import androidx.compose.runtime.getValue
29 import androidx.compose.runtime.mutableStateOf
30 import androidx.compose.runtime.produceState
31 import androidx.compose.runtime.saveable.rememberSaveable
32 import androidx.compose.runtime.setValue
33 import androidx.compose.ui.platform.LocalContext
34 import androidx.compose.ui.res.stringResource
35 import com.android.settings.R
36 import com.android.settings.Utils
37 import com.android.settings.applications.appinfo.AppInfoDashboardFragment
38 import com.android.settingslib.spa.widget.scaffold.MoreOptionsAction
39 import com.android.settingslib.spaprivileged.framework.common.appOpsManager
40 import com.android.settingslib.spaprivileged.framework.common.devicePolicyManager
41 import com.android.settingslib.spaprivileged.framework.common.userManager
42 import com.android.settingslib.spaprivileged.model.app.IPackageManagers
43 import com.android.settingslib.spaprivileged.model.app.PackageManagers
44 import com.android.settingslib.spaprivileged.model.app.userId
45 import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
46 import com.android.settingslib.spaprivileged.template.scaffold.RestrictedMenuItem
47 import kotlinx.coroutines.Dispatchers
48 import kotlinx.coroutines.async
49 import kotlinx.coroutines.coroutineScope
50 import kotlinx.coroutines.withContext
51 
52 @Composable
AppInfoSettingsMoreOptionsnull53 fun AppInfoSettingsMoreOptions(
54     packageInfoPresenter: PackageInfoPresenter,
55     app: ApplicationInfo,
56     packageManagers: IPackageManagers = PackageManagers,
57 ) {
58     val state = app.produceState(packageManagers).value ?: return
59     var restrictedSettingsAllowed by rememberSaveable { mutableStateOf(false) }
60     if (!state.shownUninstallUpdates &&
61         !state.shownUninstallForAllUsers &&
62         !(state.shouldShowAccessRestrictedSettings && !restrictedSettingsAllowed)
63     ) return
64     MoreOptionsAction {
65         val restrictions =
66             Restrictions(userId = app.userId, keys = listOf(UserManager.DISALLOW_APPS_CONTROL))
67         if (state.shownUninstallUpdates) {
68             RestrictedMenuItem(
69                 text = stringResource(R.string.app_factory_reset),
70                 restrictions = restrictions,
71             ) {
72                 packageInfoPresenter.startUninstallActivity(forAllUsers = false)
73             }
74         }
75         if (state.shownUninstallForAllUsers) {
76             RestrictedMenuItem(
77                 text = stringResource(R.string.uninstall_all_users_text),
78                 restrictions = restrictions,
79             ) {
80                 packageInfoPresenter.startUninstallActivity(forAllUsers = true)
81             }
82         }
83         if (state.shouldShowAccessRestrictedSettings && !restrictedSettingsAllowed) {
84             MenuItem(text = stringResource(R.string.app_restricted_settings_lockscreen_title)) {
85                 app.allowRestrictedSettings(packageInfoPresenter.context) {
86                     restrictedSettingsAllowed = true
87                 }
88             }
89         }
90     }
91 }
92 
allowRestrictedSettingsnull93 private fun ApplicationInfo.allowRestrictedSettings(context: Context, onSuccess: () -> Unit) {
94     AppInfoDashboardFragment.showLockScreen(context) {
95         if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()
96                 && android.security.Flags.extendEcmToAllSettings()) {
97             val manager = context.getSystemService(EnhancedConfirmationManager::class.java)!!
98             manager.clearRestriction(packageName)
99         } else {
100             context.appOpsManager.setMode(
101                 AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
102                 uid,
103                 packageName,
104                 AppOpsManager.MODE_ALLOWED,
105             )
106         }
107         onSuccess()
108         val toastString = context.getString(
109             R.string.toast_allows_restricted_settings_successfully,
110             loadLabel(context.packageManager),
111         )
112         Toast.makeText(context, toastString, Toast.LENGTH_LONG).show()
113     }
114 }
115 
116 private data class AppInfoSettingsMoreOptionsState(
117     val shownUninstallUpdates: Boolean,
118     val shownUninstallForAllUsers: Boolean,
119     val shouldShowAccessRestrictedSettings: Boolean,
120 )
121 
122 @Composable
produceStatenull123 private fun ApplicationInfo.produceState(
124     packageManagers: IPackageManagers,
125 ): State<AppInfoSettingsMoreOptionsState?> {
126     val context = LocalContext.current
127     return produceState<AppInfoSettingsMoreOptionsState?>(initialValue = null, this) {
128         withContext(Dispatchers.IO) {
129             value = getMoreOptionsState(context, packageManagers)
130         }
131     }
132 }
133 
getMoreOptionsStatenull134 private suspend fun ApplicationInfo.getMoreOptionsState(
135     context: Context,
136     packageManagers: IPackageManagers,
137 ) = coroutineScope {
138     val shownUninstallUpdatesDeferred = async {
139         isShowUninstallUpdates(context)
140     }
141     val shownUninstallForAllUsersDeferred = async {
142         isShowUninstallForAllUsers(
143             userManager = context.userManager,
144             packageManagers = packageManagers,
145         )
146     }
147     val shouldShowAccessRestrictedSettingsDeferred = async {
148         shouldShowAccessRestrictedSettings(context)
149     }
150     val isProfileOrDeviceOwner =
151         Utils.isProfileOrDeviceOwner(context.userManager, context.devicePolicyManager, packageName)
152     AppInfoSettingsMoreOptionsState(
153         // We don't allow uninstalling update for DO/PO if it's a system app, because it will clear
154         // data on all users.
155         shownUninstallUpdates = !isProfileOrDeviceOwner && shownUninstallUpdatesDeferred.await(),
156         // We also don't allow uninstalling for all users if it's DO/PO for any user.
157         shownUninstallForAllUsers =
158             !isProfileOrDeviceOwner && shownUninstallForAllUsersDeferred.await(),
159         shouldShowAccessRestrictedSettings = shouldShowAccessRestrictedSettingsDeferred.await(),
160     )
161 }
162 
ApplicationInfonull163 private fun ApplicationInfo.isShowUninstallUpdates(context: Context): Boolean =
164     isUpdatedSystemApp && context.userManager.isUserAdmin(userId) &&
165         !context.resources.getBoolean(R.bool.config_disable_uninstall_update)
166 
167 private fun ApplicationInfo.isShowUninstallForAllUsers(
168     userManager: UserManager,
169     packageManagers: IPackageManagers,
170 ): Boolean = userId == 0 && !isSystemApp && !isInstantApp &&
171     isOtherUserHasInstallPackage(userManager, packageManagers)
172 
173 private fun ApplicationInfo.isOtherUserHasInstallPackage(
174     userManager: UserManager,
175     packageManagers: IPackageManagers,
176 ): Boolean = userManager.aliveUsers
177     .filter { it.id != userId }
<lambda>null178     .filter { !Utils.shouldHideUser(it.userHandle, userManager) }
<lambda>null179     .any { packageManagers.isPackageInstalledAsUser(packageName, it.id) }
180 
shouldShowAccessRestrictedSettingsnull181 private fun ApplicationInfo.shouldShowAccessRestrictedSettings(context: Context): Boolean {
182     return if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()
183             && android.security.Flags.extendEcmToAllSettings()) {
184         val manager = context.getSystemService(EnhancedConfirmationManager::class.java)!!
185         try {
186             manager.isClearRestrictionAllowed(packageName)
187         } catch (e: PackageManager.NameNotFoundException) {
188             // Package might have been archived
189             false
190         }
191     } else {
192         context.appOpsManager.noteOpNoThrow(
193             AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, uid, packageName, null, null
194         ) == AppOpsManager.MODE_IGNORED
195     }
196 }
197