1 /*
<lambda>null2  * Copyright (C) 2022 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.privacysources
18 
19 import android.content.BroadcastReceiver
20 import android.content.ComponentName
21 import android.content.Context
22 import android.content.Intent
23 import android.content.Intent.ACTION_BOOT_COMPLETED
24 import android.content.pm.PackageManager
25 import android.os.Build
26 import android.provider.DeviceConfig
27 import android.safetycenter.SafetyCenterManager
28 import android.safetycenter.SafetyCenterManager.ACTION_REFRESH_SAFETY_SOURCES
29 import android.safetycenter.SafetyCenterManager.ACTION_SAFETY_CENTER_ENABLED_CHANGED
30 import android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOURCE_IDS
31 import androidx.annotation.RequiresApi
32 import com.android.modules.utils.build.SdkLevel
33 import com.android.permissioncontroller.Constants.UNUSED_APPS_SAFETY_CENTER_SOURCE_ID
34 import com.android.permissioncontroller.PermissionControllerApplication
35 import com.android.permissioncontroller.permission.service.LocationAccessCheck.BG_LOCATION_SOURCE_ID
36 import com.android.permissioncontroller.permission.service.v33.SafetyCenterQsTileService
37 import com.android.permissioncontroller.permission.service.v33.SafetyCenterQsTileService.Companion.QS_TILE_COMPONENT_SETTING_FLAGS
38 import com.android.permissioncontroller.permission.utils.Utils
39 import com.android.permissioncontroller.privacysources.WorkPolicyInfo.Companion.WORK_POLICY_INFO_SOURCE_ID
40 import com.android.permissioncontroller.privacysources.v34.AppDataSharingUpdatesPrivacySource
41 import com.android.permissioncontroller.privacysources.v34.AppDataSharingUpdatesPrivacySource.Companion.APP_DATA_SHARING_UPDATES_SOURCE_ID
42 import kotlinx.coroutines.CoroutineDispatcher
43 import kotlinx.coroutines.CoroutineScope
44 import kotlinx.coroutines.Dispatchers.Default
45 import kotlinx.coroutines.launch
46 
47 private fun createMapOfSourceIdsToSources(context: Context): Map<String, PrivacySource> {
48     val sourceMap: MutableMap<String, PrivacySource> = mutableMapOf()
49 
50     if (SdkLevel.isAtLeastT()) {
51         sourceMap[SC_NLS_SOURCE_ID] = NotificationListenerPrivacySource()
52         sourceMap[WORK_POLICY_INFO_SOURCE_ID] = WorkPolicyInfo.create(context)
53         sourceMap[SC_ACCESSIBILITY_SOURCE_ID] = AccessibilitySourceService(context)
54         sourceMap[BG_LOCATION_SOURCE_ID] = LocationAccessPrivacySource()
55         sourceMap[UNUSED_APPS_SAFETY_CENTER_SOURCE_ID] = AutoRevokePrivacySource()
56     }
57 
58     if (SdkLevel.isAtLeastU()) {
59         sourceMap[APP_DATA_SHARING_UPDATES_SOURCE_ID] = AppDataSharingUpdatesPrivacySource()
60     }
61 
62     return sourceMap
63 }
64 
65 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
66 class SafetyCenterReceiver(
67     private val getMapOfSourceIdsToSources: (Context) -> Map<String, PrivacySource> =
68         ::createMapOfSourceIdsToSources,
69     private val dispatcher: CoroutineDispatcher = Default
70 ) : BroadcastReceiver() {
71 
72     enum class RefreshEvent {
73         UNKNOWN,
74         EVENT_DEVICE_REBOOTED,
75         EVENT_REFRESH_REQUESTED
76     }
77 
onReceivenull78     override fun onReceive(context: Context, intent: Intent) {
79         if (!SdkLevel.isAtLeastT()) {
80             return
81         }
82         val safetyCenterManager: SafetyCenterManager =
83             Utils.getSystemServiceSafe(
84                 PermissionControllerApplication.get().applicationContext,
85                 SafetyCenterManager::class.java
86             )
87 
88         val mapOfSourceIdsToSources = getMapOfSourceIdsToSources(context)
89 
90         when (intent.action) {
91             ACTION_SAFETY_CENTER_ENABLED_CHANGED -> {
92                 safetyCenterEnabledChanged(
93                     context,
94                     safetyCenterManager.isSafetyCenterEnabled,
95                     mapOfSourceIdsToSources.values
96                 )
97             }
98             ACTION_REFRESH_SAFETY_SOURCES -> {
99                 if (safetyCenterManager.isSafetyCenterEnabled) {
100                     val sourceIdsExtra = intent.getStringArrayExtra(EXTRA_REFRESH_SAFETY_SOURCE_IDS)
101                     if (sourceIdsExtra != null && sourceIdsExtra.isNotEmpty()) {
102                         refreshSafetySources(
103                             context,
104                             intent,
105                             RefreshEvent.EVENT_REFRESH_REQUESTED,
106                             mapOfSourceIdsToSources,
107                             sourceIdsExtra.toList()
108                         )
109                     }
110                 }
111             }
112             ACTION_BOOT_COMPLETED -> {
113                 updateTileVisibility(context, safetyCenterManager.isSafetyCenterEnabled)
114                 if (safetyCenterManager.isSafetyCenterEnabled) {
115                     refreshSafetySources(
116                         context,
117                         intent,
118                         RefreshEvent.EVENT_DEVICE_REBOOTED,
119                         mapOfSourceIdsToSources,
120                         mapOfSourceIdsToSources.keys.toList()
121                     )
122                 }
123             }
124         }
125     }
126 
safetyCenterEnabledChangednull127     private fun safetyCenterEnabledChanged(
128         context: Context,
129         enabled: Boolean,
130         privacySources: Collection<PrivacySource>
131     ) {
132         privacySources.forEach { source ->
133             CoroutineScope(dispatcher).launch {
134                 if (source.shouldProcessRequest(context)) {
135                     source.safetyCenterEnabledChanged(context, enabled)
136                 }
137             }
138         }
139         updateTileVisibility(context, enabled)
140     }
141 
updateTileVisibilitynull142     private fun updateTileVisibility(context: Context, enabled: Boolean) {
143         val tileComponent = ComponentName(context, SafetyCenterQsTileService::class.java)
144         val wasEnabled =
145             context.packageManager?.getComponentEnabledSetting(tileComponent) !=
146                 PackageManager.COMPONENT_ENABLED_STATE_DISABLED
147         val qsTileComponentSettingFlags =
148             DeviceConfig.getInt(
149                 DeviceConfig.NAMESPACE_PRIVACY,
150                 QS_TILE_COMPONENT_SETTING_FLAGS,
151                 PackageManager.DONT_KILL_APP
152             )
153         if (enabled && !wasEnabled) {
154             context.packageManager.setComponentEnabledSetting(
155                 tileComponent,
156                 PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
157                 qsTileComponentSettingFlags
158             )
159         } else if (!enabled && wasEnabled) {
160             context.packageManager.setComponentEnabledSetting(
161                 tileComponent,
162                 PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
163                 qsTileComponentSettingFlags
164             )
165         }
166     }
167 
refreshSafetySourcesnull168     private fun refreshSafetySources(
169         context: Context,
170         intent: Intent,
171         refreshEvent: RefreshEvent,
172         mapOfSourceIdsToSources: Map<String, PrivacySource>,
173         sourceIdsToRefresh: List<String>
174     ) {
175         for (sourceId in sourceIdsToRefresh) {
176             CoroutineScope(dispatcher).launch {
177                 val privacySource = mapOfSourceIdsToSources[sourceId] ?: return@launch
178                 if (privacySource.shouldProcessRequest(context)) {
179                     privacySource.rescanAndPushSafetyCenterData(context, intent, refreshEvent)
180                 }
181             }
182         }
183     }
184 
shouldProcessRequestnull185     private fun PrivacySource.shouldProcessRequest(context: Context): Boolean {
186         if (!isProfile(context)) {
187             return true
188         }
189         return shouldProcessProfileRequest
190     }
191 }
192