1 /*
2  * Copyright (C) 2023 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.safetylabel
18 
19 import android.Manifest
20 import android.content.BroadcastReceiver
21 import android.content.Context
22 import android.content.Intent
23 import android.content.Intent.ACTION_PACKAGE_ADDED
24 import android.content.pm.PackageManager
25 import android.os.Build
26 import android.os.Process
27 import android.os.UserHandle
28 import android.os.UserManager
29 import android.util.Log
30 import androidx.annotation.MainThread
31 import androidx.annotation.RequiresApi
32 import com.android.permission.safetylabel.SafetyLabel as AppMetadataSafetyLabel
33 import com.android.permissioncontroller.permission.data.LightPackageInfoLiveData
34 import com.android.permissioncontroller.permission.data.get
35 import com.android.permissioncontroller.permission.data.v34.LightInstallSourceInfoLiveData
36 import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo
37 import com.android.permissioncontroller.permission.utils.KotlinUtils
38 import com.android.permissioncontroller.permission.utils.PermissionMapping
39 import com.android.permissioncontroller.permission.utils.Utils
40 import com.android.permissioncontroller.permission.utils.v34.SafetyLabelUtils
41 import com.android.permissioncontroller.safetylabel.AppsSafetyLabelHistory.SafetyLabel as SafetyLabelForPersistence
42 import java.time.Instant
43 import kotlinx.coroutines.Dispatchers
44 import kotlinx.coroutines.GlobalScope
45 import kotlinx.coroutines.launch
46 
47 /**
48  * Listens for package installs and updates, and updates the [AppsSafetyLabelHistoryPersistence] if
49  * the app safety label has changed.
50  */
51 // TODO(b/264884476): Remove excess logging from this class once feature is stable.
52 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
53 class SafetyLabelChangedBroadcastReceiver : BroadcastReceiver() {
54 
onReceivenull55     override fun onReceive(context: Context, intent: Intent) {
56         if (!KotlinUtils.isSafetyLabelChangeNotificationsEnabled(context)) {
57             return
58         }
59 
60         val packageChangeEvent = getPackageChangeEvent(intent)
61         if (
62             !(packageChangeEvent == PackageChangeEvent.NEW_INSTALL ||
63                 packageChangeEvent == PackageChangeEvent.UPDATE)
64         ) {
65             return
66         }
67 
68         val packageName = intent.data?.schemeSpecificPart
69         if (packageName == null) {
70             Log.w(TAG, "Received broadcast without package name")
71             return
72         }
73 
74         val currentUser = Process.myUserHandle()
75         if (DEBUG) {
76             Log.i(
77                 TAG,
78                 "received broadcast packageName: $packageName, current user: $currentUser," +
79                     " packageChangeEvent: $packageChangeEvent, intent user:" +
80                     " ${intent.getParcelableExtra(Intent.EXTRA_USER, UserHandle::class.java)
81                                     ?: currentUser}"
82             )
83         }
84         val userManager = Utils.getSystemServiceSafe(context, UserManager::class.java)
85         if (userManager.isProfile) {
86             forwardBroadcastToParentUser(context, userManager, intent)
87             return
88         }
89 
90         val user: UserHandle =
91             intent.getParcelableExtra(Intent.EXTRA_USER, UserHandle::class.java) ?: currentUser
92 
93         val pendingResult: PendingResult = goAsync()
94 
95         GlobalScope.launch(Dispatchers.Main) {
96             processPackageChange(context, packageName, user)
97             pendingResult.finish()
98         }
99     }
100 
101     /** Processes the package change for the given package and user. */
processPackageChangenull102     private suspend fun processPackageChange(
103         context: Context,
104         packageName: String,
105         user: UserHandle,
106     ) {
107         val lightPackageInfo =
108             LightPackageInfoLiveData[Pair(packageName, user)].getInitializedValue() ?: return
109         if (!isAppRequestingLocationPermission(lightPackageInfo)) {
110             return
111         }
112 
113         if (!isSafetyLabelSupported(Pair(packageName, user))) {
114             return
115         }
116         writeSafetyLabel(context, lightPackageInfo, user)
117     }
118 
119     /**
120      * Retrieves and writes the safety label for a package to the safety labels store.
121      *
122      * As I/O operations are invoked, we run this method on the main thread.
123      */
124     @MainThread
writeSafetyLabelnull125     private fun writeSafetyLabel(
126         context: Context,
127         lightPackageInfo: LightPackageInfo,
128         user: UserHandle,
129     ) {
130         val packageName = lightPackageInfo.packageName
131         if (DEBUG) {
132             Log.i(
133                 TAG,
134                 "writeSafetyLabel called for packageName: $packageName, currentUser:" +
135                     " ${Process.myUserHandle()}"
136             )
137         }
138 
139         // Get the context for the user in which the app is installed.
140         val userContext =
141             if (user == Process.myUserHandle()) {
142                 context
143             } else {
144                 context.createContextAsUser(user, 0)
145             }
146 
147         // Asl in Apk (V+) is not supported by permissions
148         if (!SafetyLabelUtils.isAppMetadataSourceSupported(userContext, packageName)) {
149             return
150         }
151 
152         val appMetadataBundle =
153             try {
154                 @Suppress("MissingPermission")
155                 userContext.packageManager.getAppMetadata(packageName)
156             } catch (e: PackageManager.NameNotFoundException) {
157                 Log.w(TAG, "Package $packageName not found while retrieving app metadata")
158                 return
159             }
160 
161         if (DEBUG) {
162             Log.i(TAG, "appMetadataBundle $appMetadataBundle")
163         }
164         val safetyLabel: AppMetadataSafetyLabel =
165             AppMetadataSafetyLabel.getSafetyLabelFromMetadata(appMetadataBundle) ?: return
166 
167         val receivedAtMs: Long = lightPackageInfo.lastUpdateTime
168 
169         val safetyLabelForPersistence: SafetyLabelForPersistence =
170             AppsSafetyLabelHistory.SafetyLabel.extractLocationSharingSafetyLabel(
171                 packageName,
172                 Instant.ofEpochMilli(receivedAtMs),
173                 safetyLabel
174             )
175         val historyFile = AppsSafetyLabelHistoryPersistence.getSafetyLabelHistoryFile(context)
176 
177         AppsSafetyLabelHistoryPersistence.recordSafetyLabel(safetyLabelForPersistence, historyFile)
178     }
179 
getPackageChangeEventnull180     private fun getPackageChangeEvent(intent: Intent): PackageChangeEvent {
181         val action = intent.action
182         val replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)
183 
184         return if (isPackageAddedBroadcast(action) && replacing) {
185             PackageChangeEvent.UPDATE
186         } else if (isPackageAddedBroadcast(action)) {
187             PackageChangeEvent.NEW_INSTALL
188         } else {
189             PackageChangeEvent.INSIGNIFICANT
190         }
191     }
192 
193     /** Companion object for [SafetyLabelChangedBroadcastReceiver]. */
194     companion object {
195         private val TAG = SafetyLabelChangedBroadcastReceiver::class.simpleName
196         private const val ACTION_PACKAGE_ADDED_PERMISSIONCONTROLLER_FORWARDED =
197             "com.android.permissioncontroller.action.PACKAGE_ADDED_PERMISSIONCONTROLLER_FORWARDED"
198         private const val DEBUG = true
199         private val LOCATION_PERMISSIONS =
200             PermissionMapping.getPlatformPermissionNamesOfGroup(Manifest.permission_group.LOCATION)
201 
isAppRequestingLocationPermissionnull202         private fun isAppRequestingLocationPermission(lightPackageInfo: LightPackageInfo): Boolean {
203             return lightPackageInfo.requestedPermissions.any { LOCATION_PERMISSIONS.contains(it) }
204         }
205 
isSafetyLabelSupportednull206         private suspend fun isSafetyLabelSupported(packageUser: Pair<String, UserHandle>): Boolean {
207             val lightInstallSourceInfo =
208                 LightInstallSourceInfoLiveData[packageUser].getInitializedValue() ?: return false
209             return lightInstallSourceInfo.supportsSafetyLabel
210         }
211 
isPackageAddedBroadcastnull212         private fun isPackageAddedBroadcast(intentAction: String?) =
213             intentAction == ACTION_PACKAGE_ADDED ||
214                 intentAction == ACTION_PACKAGE_ADDED_PERMISSIONCONTROLLER_FORWARDED
215 
216         @Suppress("MissingPermission")
217         private fun forwardBroadcastToParentUser(
218             context: Context,
219             userManager: UserManager,
220             intent: Intent
221         ) {
222             val currentUser = Process.myUserHandle()
223             val profileParent = userManager.getProfileParent(currentUser)
224             if (profileParent == null) {
225                 Log.w(TAG, "Could not find profile parent for $currentUser")
226                 return
227             }
228 
229             Log.i(
230                 TAG,
231                 "Forwarding intent from current user: $currentUser to profile parent" +
232                     " $profileParent"
233             )
234             context.sendBroadcastAsUser(
235                 Intent(intent)
236                     .setAction(ACTION_PACKAGE_ADDED_PERMISSIONCONTROLLER_FORWARDED)
237                     .putExtra(Intent.EXTRA_USER, currentUser),
238                 profileParent
239             )
240         }
241 
242         /** Types of package change events. */
243         enum class PackageChangeEvent {
244             INSIGNIFICANT,
245             NEW_INSTALL,
246             UPDATE,
247         }
248     }
249 }
250