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