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