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