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