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