1 /*
<lambda>null2  * Copyright (C) 2021 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.systemui.privacy
18 
19 import android.Manifest
20 import android.app.ActivityManager
21 import android.app.Dialog
22 import android.content.ComponentName
23 import android.content.Context
24 import android.content.Intent
25 import android.content.pm.PackageManager
26 import android.location.LocationManager
27 import android.os.UserHandle
28 import android.permission.PermissionGroupUsage
29 import android.permission.PermissionManager
30 import android.util.Log
31 import androidx.annotation.MainThread
32 import androidx.annotation.VisibleForTesting
33 import androidx.annotation.WorkerThread
34 import com.android.internal.logging.UiEventLogger
35 import com.android.systemui.appops.AppOpsController
36 import com.android.systemui.dagger.SysUISingleton
37 import com.android.systemui.dagger.qualifiers.Background
38 import com.android.systemui.dagger.qualifiers.Main
39 import com.android.systemui.plugins.ActivityStarter
40 import com.android.systemui.privacy.logging.PrivacyLogger
41 import com.android.systemui.settings.UserTracker
42 import com.android.systemui.statusbar.policy.KeyguardStateController
43 import java.util.concurrent.Executor
44 import javax.inject.Inject
45 
46 private val defaultDialogProvider = object : PrivacyDialogController.DialogProvider {
47     override fun makeDialog(
48         context: Context,
49         list: List<PrivacyDialog.PrivacyElement>,
50         starter: (String, Int, CharSequence?, Intent?) -> Unit
51     ): PrivacyDialog {
52         return PrivacyDialog(context, list, starter)
53     }
54 }
55 
56 /**
57  * Controller for [PrivacyDialog].
58  *
59  * This controller shows and dismissed the dialog, as well as determining the information to show in
60  * it.
61  */
62 @SysUISingleton
63 class PrivacyDialogController(
64     private val permissionManager: PermissionManager,
65     private val packageManager: PackageManager,
66     private val locationManager: LocationManager,
67     private val privacyItemController: PrivacyItemController,
68     private val userTracker: UserTracker,
69     private val activityStarter: ActivityStarter,
70     private val backgroundExecutor: Executor,
71     private val uiExecutor: Executor,
72     private val privacyLogger: PrivacyLogger,
73     private val keyguardStateController: KeyguardStateController,
74     private val appOpsController: AppOpsController,
75     private val uiEventLogger: UiEventLogger,
76     @VisibleForTesting private val dialogProvider: DialogProvider
77 ) {
78 
79     @Inject
80     constructor(
81         permissionManager: PermissionManager,
82         packageManager: PackageManager,
83         locationManager: LocationManager,
84         privacyItemController: PrivacyItemController,
85         userTracker: UserTracker,
86         activityStarter: ActivityStarter,
87         @Background backgroundExecutor: Executor,
88         @Main uiExecutor: Executor,
89         privacyLogger: PrivacyLogger,
90         keyguardStateController: KeyguardStateController,
91         appOpsController: AppOpsController,
92         uiEventLogger: UiEventLogger
93     ) : this(
94             permissionManager,
95             packageManager,
96             locationManager,
97             privacyItemController,
98             userTracker,
99             activityStarter,
100             backgroundExecutor,
101             uiExecutor,
102             privacyLogger,
103             keyguardStateController,
104             appOpsController,
105             uiEventLogger,
106             defaultDialogProvider
107     )
108 
109     companion object {
110         private const val TAG = "PrivacyDialogController"
111     }
112 
113     private var dialog: Dialog? = null
114 
115     private val onDialogDismissed = object : PrivacyDialog.OnDialogDismissed {
onDialogDismissednull116         override fun onDialogDismissed() {
117             privacyLogger.logPrivacyDialogDismissed()
118             uiEventLogger.log(PrivacyDialogEvent.PRIVACY_DIALOG_DISMISSED)
119             dialog = null
120         }
121     }
122 
123     @MainThread
startActivitynull124     private fun startActivity(
125         packageName: String,
126         userId: Int,
127         attributionTag: CharSequence?,
128         navigationIntent: Intent?
129     ) {
130         val intent = if (navigationIntent == null) {
131             getDefaultManageAppPermissionsIntent(packageName, userId)
132         } else {
133             navigationIntent
134         }
135         uiEventLogger.log(PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS,
136             userId, packageName)
137         privacyLogger.logStartSettingsActivityFromDialog(packageName, userId)
138         if (!keyguardStateController.isUnlocked) {
139             // If we are locked, hide the dialog so the user can unlock
140             dialog?.hide()
141         }
142         // startActivity calls internally startActivityDismissingKeyguard
143         activityStarter.startActivity(intent, true) {
144             if (ActivityManager.isStartResultSuccessful(it)) {
145                 dismissDialog()
146             } else {
147                 dialog?.show()
148             }
149         }
150     }
151 
152     @WorkerThread
getManagePermissionIntentnull153     private fun getManagePermissionIntent(
154         context: Context,
155         packageName: String,
156         userId: Int,
157         permGroupName: CharSequence,
158         attributionTag: CharSequence?,
159         isAttributionSupported: Boolean
160     ): Intent {
161         lateinit var intent: Intent
162         // We should only limit this intent to location provider
163         if (attributionTag != null && isAttributionSupported &&
164             locationManager.isProviderPackage(null, packageName, attributionTag.toString())) {
165             intent = Intent(Intent.ACTION_MANAGE_PERMISSION_USAGE)
166             intent.setPackage(packageName)
167             intent.putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, permGroupName.toString())
168             intent.putExtra(Intent.EXTRA_ATTRIBUTION_TAGS, arrayOf(attributionTag.toString()))
169             intent.putExtra(Intent.EXTRA_SHOWING_ATTRIBUTION, true)
170             val resolveInfo = packageManager.resolveActivity(
171                     intent, PackageManager.ResolveInfoFlags.of(0))
172             if (resolveInfo != null && resolveInfo.activityInfo != null &&
173                     resolveInfo.activityInfo.permission ==
174                     android.Manifest.permission.START_VIEW_PERMISSION_USAGE) {
175                 intent.component = ComponentName(packageName, resolveInfo.activityInfo.name)
176                 return intent
177             }
178         }
179         return getDefaultManageAppPermissionsIntent(packageName, userId)
180     }
181 
getDefaultManageAppPermissionsIntentnull182     fun getDefaultManageAppPermissionsIntent(packageName: String, userId: Int): Intent {
183         val intent = Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS)
184         intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
185         intent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId))
186         return intent
187     }
188 
189     @WorkerThread
permGroupUsagenull190     private fun permGroupUsage(): List<PermissionGroupUsage> {
191         return permissionManager.getIndicatorAppOpUsageData(appOpsController.isMicMuted)
192     }
193 
194     /**
195      * Show the [PrivacyDialog]
196      *
197      * This retrieves the permission usage from [PermissionManager] and creates a new
198      * [PrivacyDialog] with a list of [PrivacyDialog.PrivacyElement] to show.
199      *
200      * This list will be filtered by [filterAndSelect]. Only types available by
201      * [PrivacyItemController] will be shown.
202      *
203      * @param context A context to use to create the dialog.
204      * @see filterAndSelect
205      */
showDialognull206     fun showDialog(context: Context) {
207         dismissDialog()
208         backgroundExecutor.execute {
209             val usage = permGroupUsage()
210             val userInfos = userTracker.userProfiles
211             privacyLogger.logUnfilteredPermGroupUsage(usage)
212             val items = usage.mapNotNull {
213                 val type = filterType(permGroupToPrivacyType(it.permissionGroupName))
214                 val userInfo = userInfos.firstOrNull { ui -> ui.id == UserHandle.getUserId(it.uid) }
215                 if (userInfo != null || it.isPhoneCall) {
216                     type?.let { t ->
217                         // Only try to get the app name if we actually need it
218                         val appName = if (it.isPhoneCall) {
219                             ""
220                         } else {
221                             getLabelForPackage(it.packageName, it.uid)
222                         }
223                         val userId = UserHandle.getUserId(it.uid)
224                         PrivacyDialog.PrivacyElement(
225                                 t,
226                                 it.packageName,
227                                 userId,
228                                 appName,
229                                 it.attributionTag,
230                                 it.attributionLabel,
231                                 it.proxyLabel,
232                                 it.lastAccessTimeMillis,
233                                 it.isActive,
234                                 // If there's no user info, we're in a phoneCall in secondary user
235                                 userInfo?.isManagedProfile ?: false,
236                                 it.isPhoneCall,
237                                 it.permissionGroupName,
238                                 getManagePermissionIntent(
239                                         context,
240                                         it.packageName,
241                                         userId,
242                                         it.permissionGroupName,
243                                         it.attributionTag,
244                                         // attributionLabel is set only when subattribution policies
245                                         // are supported and satisfied
246                                         it.attributionLabel != null
247                                 )
248                         )
249                     }
250                 } else {
251                     // No matching user or phone call
252                     null
253                 }
254             }
255             uiExecutor.execute {
256                 val elements = filterAndSelect(items)
257                 if (elements.isNotEmpty()) {
258                     val d = dialogProvider.makeDialog(context, elements, this::startActivity)
259                     d.setShowForAllUsers(true)
260                     d.addOnDismissListener(onDialogDismissed)
261                     d.show()
262                     privacyLogger.logShowDialogContents(elements)
263                     dialog = d
264                 } else {
265                     Log.w(TAG, "Trying to show empty dialog")
266                 }
267             }
268         }
269     }
270 
271     /**
272      * Dismisses the dialog
273      */
dismissDialognull274     fun dismissDialog() {
275         dialog?.dismiss()
276     }
277 
278     @WorkerThread
getLabelForPackagenull279     private fun getLabelForPackage(packageName: String, uid: Int): CharSequence {
280         return try {
281             packageManager
282                 .getApplicationInfoAsUser(packageName, 0, UserHandle.getUserId(uid))
283                 .loadLabel(packageManager)
284         } catch (_: PackageManager.NameNotFoundException) {
285             Log.w(TAG, "Label not found for: $packageName")
286             packageName
287         }
288     }
289 
permGroupToPrivacyTypenull290     private fun permGroupToPrivacyType(group: String): PrivacyType? {
291         return when (group) {
292             Manifest.permission_group.CAMERA -> PrivacyType.TYPE_CAMERA
293             Manifest.permission_group.MICROPHONE -> PrivacyType.TYPE_MICROPHONE
294             Manifest.permission_group.LOCATION -> PrivacyType.TYPE_LOCATION
295             else -> null
296         }
297     }
298 
filterTypenull299     private fun filterType(type: PrivacyType?): PrivacyType? {
300         return type?.let {
301             if ((it == PrivacyType.TYPE_CAMERA || it == PrivacyType.TYPE_MICROPHONE) &&
302                 privacyItemController.micCameraAvailable) {
303                 it
304             } else if (it == PrivacyType.TYPE_LOCATION && privacyItemController.locationAvailable) {
305                 it
306             } else {
307                 null
308             }
309         }
310     }
311 
312     /**
313      * Filters the list of elements to show.
314      *
315      * For each privacy type, it'll return all active elements. If there are no active elements,
316      * it'll return the most recent access
317      */
filterAndSelectnull318     private fun filterAndSelect(
319         list: List<PrivacyDialog.PrivacyElement>
320     ): List<PrivacyDialog.PrivacyElement> {
321         return list.groupBy { it.type }.toSortedMap().flatMap { (_, elements) ->
322             val actives = elements.filter { it.active }
323             if (actives.isNotEmpty()) {
324                 actives.sortedByDescending { it.lastActiveTimestamp }
325             } else {
326                 elements.maxByOrNull { it.lastActiveTimestamp }?.let {
327                     listOf(it)
328                 } ?: emptyList()
329             }
330         }
331     }
332 
333     /**
334      * Interface to create a [PrivacyDialog].
335      *
336      * Can be used to inject a mock creator.
337      */
338     interface DialogProvider {
339         /**
340          * Create a [PrivacyDialog].
341          */
makeDialognull342         fun makeDialog(
343             context: Context,
344             list: List<PrivacyDialog.PrivacyElement>,
345             starter: (String, Int, CharSequence?, Intent?) -> Unit
346         ): PrivacyDialog
347     }
348 }
349