1 /* <lambda>null2 * Copyright (C) 2024 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.permissioncontroller.ecm 18 19 import android.annotation.SuppressLint 20 import android.app.AlertDialog 21 import android.app.AppOpsManager 22 import android.app.Dialog 23 import android.app.ecm.EnhancedConfirmationManager 24 import android.content.Context 25 import android.content.DialogInterface 26 import android.content.Intent 27 import android.content.pm.PackageManager 28 import android.os.Build 29 import android.os.Bundle 30 import android.os.Process 31 import android.os.UserHandle 32 import android.permission.flags.Flags 33 import android.text.Html 34 import android.text.method.LinkMovementMethod 35 import android.view.LayoutInflater 36 import android.view.View 37 import android.widget.TextView 38 import androidx.annotation.Keep 39 import androidx.annotation.RequiresApi 40 import androidx.fragment.app.DialogFragment 41 import androidx.fragment.app.FragmentActivity 42 import com.android.modules.utils.build.SdkLevel 43 import com.android.permissioncontroller.Constants.EXTRA_IS_ECM_IN_APP 44 import com.android.permissioncontroller.DeviceUtils 45 import com.android.permissioncontroller.R 46 import com.android.permissioncontroller.ecm.EnhancedConfirmationStatsLogUtils.DialogResult 47 import com.android.permissioncontroller.permission.ui.wear.WearEnhancedConfirmationDialogFragment 48 import com.android.permissioncontroller.permission.utils.KotlinUtils 49 import com.android.permissioncontroller.permission.utils.PermissionMapping 50 import com.android.permissioncontroller.permission.utils.Utils 51 import com.android.role.controller.model.Roles 52 53 @Keep 54 @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) 55 class EnhancedConfirmationDialogActivity : FragmentActivity() { 56 companion object { 57 private const val KEY_WAS_CLEAR_RESTRICTION_ALLOWED = "KEY_WAS_CLEAR_RESTRICTION_ALLOWED" 58 } 59 60 private var wasClearRestrictionAllowed: Boolean = false 61 private var dialogResult: DialogResult = DialogResult.Cancelled 62 63 override fun onCreate(savedInstanceState: Bundle?) { 64 super.onCreate(savedInstanceState) 65 if (!SdkLevel.isAtLeastV() || !Flags.enhancedConfirmationModeApisEnabled()) { 66 finish() 67 return 68 } 69 70 if (savedInstanceState != null) { 71 wasClearRestrictionAllowed = 72 savedInstanceState.getBoolean(KEY_WAS_CLEAR_RESTRICTION_ALLOWED) 73 return 74 } 75 76 val uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID) 77 val packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME) 78 val settingIdentifier = intent.getStringExtra(Intent.EXTRA_SUBJECT) 79 val isEcmInApp = intent.getBooleanExtra(EXTRA_IS_ECM_IN_APP, false) 80 81 require(uid != Process.INVALID_UID) { "EXTRA_UID cannot be null or invalid" } 82 require(!packageName.isNullOrEmpty()) { "EXTRA_PACKAGE_NAME cannot be null or empty" } 83 require(!settingIdentifier.isNullOrEmpty()) { "EXTRA_SUBJECT cannot be null or empty" } 84 wasClearRestrictionAllowed = 85 setClearRestrictionAllowed(packageName, UserHandle.getUserHandleForUid(uid)) 86 87 val setting = Setting.fromIdentifier(this, settingIdentifier, isEcmInApp) 88 if ( 89 SettingType.fromIdentifier(this, settingIdentifier, isEcmInApp) == 90 SettingType.BLOCKED_DUE_TO_PHONE_STATE && 91 !Flags.unknownCallPackageInstallBlockingEnabled() 92 ) { 93 finish() 94 return 95 } 96 97 if (DeviceUtils.isWear(this)) { 98 WearEnhancedConfirmationDialogFragment.newInstance(setting.title, setting.message) 99 .show(supportFragmentManager, WearEnhancedConfirmationDialogFragment.TAG) 100 } else { 101 EnhancedConfirmationDialogFragment.newInstance(setting.title, setting.message) 102 .show(supportFragmentManager, EnhancedConfirmationDialogFragment.TAG) 103 } 104 } 105 106 override fun onSaveInstanceState(outState: Bundle) { 107 super.onSaveInstanceState(outState) 108 outState.putBoolean(KEY_WAS_CLEAR_RESTRICTION_ALLOWED, wasClearRestrictionAllowed) 109 } 110 111 @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) 112 private fun setClearRestrictionAllowed(packageName: String, user: UserHandle): Boolean { 113 val userContext = createContextAsUser(user, 0) 114 val ecm = Utils.getSystemServiceSafe(userContext, EnhancedConfirmationManager::class.java) 115 try { 116 val wasClearRestrictionAllowed = ecm.isClearRestrictionAllowed(packageName) 117 ecm.setClearRestrictionAllowed(packageName) 118 return wasClearRestrictionAllowed 119 } catch (e: PackageManager.NameNotFoundException) { 120 throw IllegalArgumentException("unknown package: $packageName") 121 } 122 } 123 124 private data class Setting(val title: String?, val message: CharSequence?) { 125 companion object { 126 fun fromIdentifier( 127 context: Context, 128 settingIdentifier: String, 129 isEcmInApp: Boolean, 130 ): Setting { 131 val settingType = SettingType.fromIdentifier(context, settingIdentifier, isEcmInApp) 132 val label = 133 when (settingType) { 134 SettingType.PLATFORM_PERMISSION -> 135 KotlinUtils.getPermGroupLabel( 136 context, 137 PermissionMapping.getGroupOfPlatformPermission(settingIdentifier)!!, 138 ) 139 SettingType.PLATFORM_PERMISSION_GROUP -> 140 KotlinUtils.getPermGroupLabel(context, settingIdentifier) 141 SettingType.ROLE -> 142 context.getString( 143 Roles.get(context)[settingIdentifier]!!.shortLabelResource 144 ) 145 SettingType.BLOCKED_DUE_TO_PHONE_STATE, 146 SettingType.OTHER -> null 147 } 148 var title: String? 149 var message: CharSequence? 150 if (settingType == SettingType.BLOCKED_DUE_TO_PHONE_STATE) { 151 title = settingType.titleRes?.let { context.getString(it) } 152 message = settingType.messageRes?.let { context.getString(it) } 153 } else { 154 val url = 155 context.getString(R.string.help_url_action_disabled_by_restricted_settings) 156 title = (settingType.titleRes?.let { context.getString(it, label) }) 157 message = 158 settingType.messageRes?.let { Html.fromHtml(context.getString(it, url), 0) } 159 } 160 return Setting(title, message) 161 } 162 } 163 } 164 165 private enum class SettingType(val titleRes: Int?, val messageRes: Int?) { 166 PLATFORM_PERMISSION( 167 R.string.enhanced_confirmation_dialog_title_permission, 168 R.string.enhanced_confirmation_dialog_desc_permission, 169 ), 170 PLATFORM_PERMISSION_GROUP( 171 R.string.enhanced_confirmation_dialog_title_permission, 172 R.string.enhanced_confirmation_dialog_desc_permission, 173 ), 174 ROLE( 175 R.string.enhanced_confirmation_dialog_title_role, 176 R.string.enhanced_confirmation_dialog_desc_role, 177 ), 178 OTHER( 179 R.string.enhanced_confirmation_dialog_title_settings_default, 180 R.string.enhanced_confirmation_dialog_desc_settings_default, 181 ), 182 BLOCKED_DUE_TO_PHONE_STATE( 183 R.string.enhanced_confirmation_phone_state_dialog_title, 184 R.string.enhanced_confirmation_phone_state_dialog_desc, 185 ); 186 187 companion object { 188 fun fromIdentifier( 189 context: Context, 190 settingIdentifier: String, 191 isEcmInApp: Boolean, 192 ): SettingType { 193 return when { 194 settingIdentifier == AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES -> 195 BLOCKED_DUE_TO_PHONE_STATE 196 !isEcmInApp -> OTHER 197 PermissionMapping.isRuntimePlatformPermission(settingIdentifier) && 198 PermissionMapping.getGroupOfPlatformPermission(settingIdentifier) != null -> 199 PLATFORM_PERMISSION 200 PermissionMapping.isPlatformPermissionGroup(settingIdentifier) -> 201 PLATFORM_PERMISSION_GROUP 202 settingIdentifier.startsWith("android.app.role.") && 203 Roles.get(context).containsKey(settingIdentifier) -> ROLE 204 else -> OTHER 205 } 206 } 207 } 208 } 209 210 fun onDialogResult(dialogResult: DialogResult) { 211 this.dialogResult = dialogResult 212 setResult( 213 RESULT_OK, 214 Intent().apply { putExtra(Intent.EXTRA_RETURN_RESULT, dialogResult.statsLogValue) }, 215 ) 216 finish() 217 } 218 219 override fun onDestroy() { 220 super.onDestroy() 221 if (isFinishing) { 222 EnhancedConfirmationStatsLogUtils.logDialogResultReported( 223 uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID), 224 settingIdentifier = intent.getStringExtra(Intent.EXTRA_SUBJECT)!!, 225 firstShowForApp = !wasClearRestrictionAllowed, 226 dialogResult = dialogResult, 227 ) 228 } 229 } 230 231 class EnhancedConfirmationDialogFragment() : DialogFragment() { 232 companion object { 233 val TAG = EnhancedConfirmationDialogFragment::class.simpleName 234 private const val KEY_TITLE = "KEY_TITLE" 235 private const val KEY_MESSAGE = "KEY_MESSAGE" 236 237 fun newInstance(title: String? = null, message: CharSequence? = null) = 238 EnhancedConfirmationDialogFragment().apply { 239 arguments = 240 Bundle().apply { 241 putString(KEY_TITLE, title) 242 putCharSequence(KEY_MESSAGE, message) 243 } 244 } 245 } 246 247 private lateinit var dialogActivity: EnhancedConfirmationDialogActivity 248 249 override fun onAttach(context: Context) { 250 super.onAttach(context) 251 dialogActivity = context as EnhancedConfirmationDialogActivity 252 } 253 254 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 255 val title = arguments!!.getString(KEY_TITLE) 256 val message = arguments!!.getCharSequence(KEY_MESSAGE) 257 258 return AlertDialog.Builder(dialogActivity) 259 .setView(createDialogView(dialogActivity, title, message)) 260 .setPositiveButton(R.string.enhanced_confirmation_dialog_ok) { _, _ -> 261 dialogActivity.onDialogResult(DialogResult.Okay) 262 } 263 .create() 264 } 265 266 override fun onCancel(dialog: DialogInterface) { 267 super.onCancel(dialog) 268 dialogActivity.onDialogResult(DialogResult.Cancelled) 269 } 270 271 @SuppressLint("InflateParams") 272 private fun createDialogView( 273 context: Context, 274 title: String?, 275 message: CharSequence?, 276 ): View = 277 LayoutInflater.from(context) 278 .inflate(R.layout.enhanced_confirmation_dialog, null) 279 .apply { 280 title?.let { 281 requireViewById<TextView>(R.id.enhanced_confirmation_dialog_title).text = it 282 } 283 message?.let { 284 val descTextView = 285 requireViewById<TextView>(R.id.enhanced_confirmation_dialog_desc) 286 descTextView.text = it 287 descTextView.movementMethod = LinkMovementMethod.getInstance() 288 } 289 } 290 } 291 } 292