1 /* 2 * 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.safetycenter; 18 19 import static android.Manifest.permission.READ_SAFETY_CENTER_STATUS; 20 import static android.Manifest.permission.SEND_SAFETY_CENTER_UPDATE; 21 import static android.content.Intent.FLAG_INCLUDE_STOPPED_PACKAGES; 22 import static android.content.Intent.FLAG_RECEIVER_FOREGROUND; 23 import static android.os.PowerExemptionManager.REASON_REFRESH_SAFETY_SOURCES; 24 import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED; 25 import static android.safetycenter.SafetyCenterManager.ACTION_REFRESH_SAFETY_SOURCES; 26 import static android.safetycenter.SafetyCenterManager.ACTION_SAFETY_CENTER_ENABLED_CHANGED; 27 import static android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID; 28 import static android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE; 29 import static android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOURCE_IDS; 30 import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_PAGE_OPEN; 31 import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_SAFETY_CENTER_ENABLED; 32 33 import static java.util.Collections.unmodifiableList; 34 35 import android.annotation.UserIdInt; 36 import android.app.BroadcastOptions; 37 import android.content.Context; 38 import android.content.Intent; 39 import android.os.Binder; 40 import android.os.UserHandle; 41 import android.safetycenter.SafetyCenterManager; 42 import android.safetycenter.SafetyCenterManager.RefreshReason; 43 import android.safetycenter.SafetySourceData; 44 import android.util.ArraySet; 45 import android.util.Log; 46 import android.util.SparseArray; 47 48 import androidx.annotation.Nullable; 49 50 import com.android.permission.util.PackageUtils; 51 import com.android.safetycenter.SafetyCenterConfigReader.Broadcast; 52 import com.android.safetycenter.UserProfileGroup.ProfileType; 53 import com.android.safetycenter.data.SafetyCenterDataManager; 54 55 import java.time.Duration; 56 import java.util.ArrayList; 57 import java.util.Collections; 58 import java.util.List; 59 import java.util.Set; 60 61 import javax.annotation.concurrent.NotThreadSafe; 62 63 /** 64 * A class that dispatches SafetyCenter broadcasts. 65 * 66 * <p>This class isn't thread safe. Thread safety must be handled by the caller. 67 */ 68 @NotThreadSafe 69 final class SafetyCenterBroadcastDispatcher { 70 private static final String TAG = "SafetyCenterBroadcastDi"; 71 72 private final Context mContext; 73 private final SafetyCenterConfigReader mSafetyCenterConfigReader; 74 private final SafetyCenterRefreshTracker mSafetyCenterRefreshTracker; 75 private final SafetyCenterDataManager mSafetyCenterDataManager; 76 SafetyCenterBroadcastDispatcher( Context context, SafetyCenterConfigReader safetyCenterConfigReader, SafetyCenterRefreshTracker safetyCenterRefreshTracker, SafetyCenterDataManager safetyCenterDataManager)77 SafetyCenterBroadcastDispatcher( 78 Context context, 79 SafetyCenterConfigReader safetyCenterConfigReader, 80 SafetyCenterRefreshTracker safetyCenterRefreshTracker, 81 SafetyCenterDataManager safetyCenterDataManager) { 82 mContext = context; 83 mSafetyCenterConfigReader = safetyCenterConfigReader; 84 mSafetyCenterRefreshTracker = safetyCenterRefreshTracker; 85 mSafetyCenterDataManager = safetyCenterDataManager; 86 } 87 88 /** 89 * Triggers a refresh of safety sources by sending them broadcasts with action {@link 90 * SafetyCenterManager#ACTION_REFRESH_SAFETY_SOURCES}, and returns the associated broadcast id. 91 * 92 * <p>Returns {@code null} if no broadcast was sent. 93 * 94 * @param safetySourceIds list of IDs to specify the safety sources to be refreshed or a {@code 95 * null} value to refresh all safety sources. 96 */ 97 @Nullable sendRefreshSafetySources( @efreshReason int refreshReason, UserProfileGroup userProfileGroup, @Nullable List<String> safetySourceIds)98 String sendRefreshSafetySources( 99 @RefreshReason int refreshReason, 100 UserProfileGroup userProfileGroup, 101 @Nullable List<String> safetySourceIds) { 102 List<Broadcast> broadcasts = mSafetyCenterConfigReader.getBroadcasts(); 103 BroadcastOptions broadcastOptions = createBroadcastOptions(); 104 105 String broadcastId = 106 mSafetyCenterRefreshTracker.reportRefreshInProgress( 107 refreshReason, userProfileGroup); 108 boolean hasSentAtLeastOneBroadcast = false; 109 110 for (int i = 0; i < broadcasts.size(); i++) { 111 Broadcast broadcast = broadcasts.get(i); 112 113 hasSentAtLeastOneBroadcast |= 114 sendRefreshSafetySourcesBroadcast( 115 broadcast, 116 broadcastOptions, 117 refreshReason, 118 userProfileGroup, 119 broadcastId, 120 safetySourceIds); 121 } 122 123 if (!hasSentAtLeastOneBroadcast) { 124 mSafetyCenterRefreshTracker.clearRefresh(broadcastId); 125 return null; 126 } 127 128 return broadcastId; 129 } 130 sendRefreshSafetySourcesBroadcast( Broadcast broadcast, BroadcastOptions broadcastOptions, @RefreshReason int refreshReason, UserProfileGroup userProfileGroup, String broadcastId, @Nullable List<String> requiredSourceIds)131 private boolean sendRefreshSafetySourcesBroadcast( 132 Broadcast broadcast, 133 BroadcastOptions broadcastOptions, 134 @RefreshReason int refreshReason, 135 UserProfileGroup userProfileGroup, 136 String broadcastId, 137 @Nullable List<String> requiredSourceIds) { 138 boolean hasSentAtLeastOneBroadcast = false; 139 String packageName = broadcast.getPackageName(); 140 Set<String> deniedSourceIds = getRefreshDeniedSourceIds(refreshReason); 141 SparseArray<List<String>> userIdsToSourceIds = 142 getUserIdsToSourceIds(broadcast, userProfileGroup, refreshReason); 143 144 for (int i = 0; i < userIdsToSourceIds.size(); i++) { 145 int userId = userIdsToSourceIds.keyAt(i); 146 List<String> sourceIds = userIdsToSourceIds.valueAt(i); 147 148 if (!deniedSourceIds.isEmpty()) { 149 sourceIds = new ArrayList<>(sourceIds); 150 sourceIds.removeAll(deniedSourceIds); 151 } 152 153 if (requiredSourceIds != null) { 154 sourceIds = new ArrayList<>(sourceIds); 155 sourceIds.retainAll(requiredSourceIds); 156 } 157 158 if (sourceIds.isEmpty()) { 159 continue; 160 } 161 162 Intent intent = createRefreshIntent(refreshReason, packageName, sourceIds, broadcastId); 163 boolean broadcastWasSent = 164 sendBroadcastIfResolves(intent, UserHandle.of(userId), broadcastOptions); 165 if (broadcastWasSent) { 166 mSafetyCenterRefreshTracker.reportSourceRefreshesInFlight( 167 broadcastId, sourceIds, userId); 168 } 169 hasSentAtLeastOneBroadcast |= broadcastWasSent; 170 } 171 172 return hasSentAtLeastOneBroadcast; 173 } 174 175 /** 176 * Triggers an {@link SafetyCenterManager#ACTION_SAFETY_CENTER_ENABLED_CHANGED} broadcast for 177 * all safety sources. 178 * 179 * <p>This method also sends an implicit broadcast globally (which requires the {@link 180 * android.Manifest.permission#READ_SAFETY_CENTER_STATUS} permission). 181 */ 182 // TODO(b/227310195): Consider adding a boolean extra to the intent instead of having clients 183 // rely on SafetyCenterManager#isSafetyCenterEnabled()? sendEnabledChanged()184 void sendEnabledChanged() { 185 List<Broadcast> broadcasts = mSafetyCenterConfigReader.getBroadcasts(); 186 BroadcastOptions broadcastOptions = createBroadcastOptions(); 187 List<UserProfileGroup> userProfileGroups = 188 UserProfileGroup.getAllUserProfileGroups(mContext); 189 190 for (int i = 0; i < broadcasts.size(); i++) { 191 Broadcast broadcast = broadcasts.get(i); 192 193 sendEnabledChangedBroadcast(broadcast, broadcastOptions, userProfileGroups); 194 } 195 196 Intent implicitIntent = createImplicitEnabledChangedIntent(); 197 sendBroadcast( 198 implicitIntent, 199 UserHandle.SYSTEM, 200 READ_SAFETY_CENTER_STATUS, 201 /* broadcastOptions= */ null); 202 } 203 sendEnabledChangedBroadcast( Broadcast broadcast, BroadcastOptions broadcastOptions, List<UserProfileGroup> userProfileGroups)204 private void sendEnabledChangedBroadcast( 205 Broadcast broadcast, 206 BroadcastOptions broadcastOptions, 207 List<UserProfileGroup> userProfileGroups) { 208 Intent intent = createExplicitEnabledChangedIntent(broadcast.getPackageName()); 209 210 for (int i = 0; i < userProfileGroups.size(); i++) { 211 UserProfileGroup userProfileGroup = userProfileGroups.get(i); 212 SparseArray<List<String>> userIdsToSourceIds = 213 getUserIdsToSourceIds( 214 broadcast, 215 userProfileGroup, 216 // The same ENABLED reason is used here for both enable and disable 217 // events. It is not sent externally and is only used internally to 218 // filter safety sources in the methods of the Broadcast class. 219 REFRESH_REASON_SAFETY_CENTER_ENABLED); 220 221 for (int j = 0; j < userIdsToSourceIds.size(); j++) { 222 int userId = userIdsToSourceIds.keyAt(j); 223 224 sendBroadcastIfResolves(intent, UserHandle.of(userId), broadcastOptions); 225 } 226 } 227 } 228 sendBroadcastIfResolves( Intent intent, UserHandle userHandle, @Nullable BroadcastOptions broadcastOptions)229 private boolean sendBroadcastIfResolves( 230 Intent intent, UserHandle userHandle, @Nullable BroadcastOptions broadcastOptions) { 231 if (!doesBroadcastResolve(intent, userHandle)) { 232 Log.w( 233 TAG, 234 "No receiver for intent targeting: " 235 + intent.getPackage() 236 + ", and user id: " 237 + userHandle.getIdentifier()); 238 return false; 239 } 240 Log.v( 241 TAG, 242 "Found receiver for intent targeting: " 243 + intent.getPackage() 244 + ", and user id: " 245 + userHandle.getIdentifier()); 246 sendBroadcast(intent, userHandle, SEND_SAFETY_CENTER_UPDATE, broadcastOptions); 247 return true; 248 } 249 sendBroadcast( Intent intent, UserHandle userHandle, String permission, @Nullable BroadcastOptions broadcastOptions)250 private void sendBroadcast( 251 Intent intent, 252 UserHandle userHandle, 253 String permission, 254 @Nullable BroadcastOptions broadcastOptions) { 255 // This call requires the INTERACT_ACROSS_USERS permission. 256 final long callingId = Binder.clearCallingIdentity(); 257 try { 258 mContext.sendBroadcastAsUser( 259 intent, 260 userHandle, 261 permission, 262 broadcastOptions == null ? null : broadcastOptions.toBundle()); 263 } finally { 264 Binder.restoreCallingIdentity(callingId); 265 } 266 } 267 doesBroadcastResolve(Intent broadcastIntent, UserHandle userHandle)268 private boolean doesBroadcastResolve(Intent broadcastIntent, UserHandle userHandle) { 269 return !PackageUtils.queryUnfilteredBroadcastReceiversAsUser( 270 broadcastIntent, /* flags= */ 0, userHandle.getIdentifier(), mContext) 271 .isEmpty(); 272 } 273 createExplicitEnabledChangedIntent(String packageName)274 private static Intent createExplicitEnabledChangedIntent(String packageName) { 275 return createImplicitEnabledChangedIntent().setPackage(packageName); 276 } 277 createImplicitEnabledChangedIntent()278 private static Intent createImplicitEnabledChangedIntent() { 279 return createBroadcastIntent(ACTION_SAFETY_CENTER_ENABLED_CHANGED); 280 } 281 createRefreshIntent( @efreshReason int refreshReason, String packageName, List<String> sourceIdsToRefresh, String broadcastId)282 private static Intent createRefreshIntent( 283 @RefreshReason int refreshReason, 284 String packageName, 285 List<String> sourceIdsToRefresh, 286 String broadcastId) { 287 String[] sourceIdsArray = sourceIdsToRefresh.toArray(new String[0]); 288 int requestType = RefreshReasons.toRefreshRequestType(refreshReason); 289 Intent refreshIntent = 290 createBroadcastIntent(ACTION_REFRESH_SAFETY_SOURCES) 291 .putExtra(EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE, requestType) 292 .putExtra(EXTRA_REFRESH_SAFETY_SOURCE_IDS, sourceIdsArray) 293 .putExtra(EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID, broadcastId) 294 .setPackage(packageName); 295 boolean isUserInitiated = !RefreshReasons.isBackgroundRefresh(refreshReason); 296 if (isUserInitiated) { 297 return refreshIntent.addFlags(FLAG_INCLUDE_STOPPED_PACKAGES); 298 } 299 return refreshIntent; 300 } 301 createBroadcastIntent(String intentAction)302 private static Intent createBroadcastIntent(String intentAction) { 303 return new Intent(intentAction).addFlags(FLAG_RECEIVER_FOREGROUND); 304 } 305 createBroadcastOptions()306 private static BroadcastOptions createBroadcastOptions() { 307 BroadcastOptions broadcastOptions = BroadcastOptions.makeBasic(); 308 Duration allowListDuration = SafetyCenterFlags.getFgsAllowlistDuration(); 309 // This call requires the START_FOREGROUND_SERVICES_FROM_BACKGROUND permission. 310 final long callingId = Binder.clearCallingIdentity(); 311 try { 312 broadcastOptions.setTemporaryAppAllowlist( 313 allowListDuration.toMillis(), 314 TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED, 315 REASON_REFRESH_SAFETY_SOURCES, 316 "Safety Center is requesting data from safety sources"); 317 } finally { 318 Binder.restoreCallingIdentity(callingId); 319 } 320 return broadcastOptions; 321 } 322 323 /** Returns the list of source IDs for which refreshing is denied for the given reason. */ getRefreshDeniedSourceIds(@efreshReason int refreshReason)324 private static Set<String> getRefreshDeniedSourceIds(@RefreshReason int refreshReason) { 325 if (RefreshReasons.isBackgroundRefresh(refreshReason)) { 326 return SafetyCenterFlags.getBackgroundRefreshDeniedSourceIds(); 327 } else { 328 return Collections.emptySet(); 329 } 330 } 331 332 /** 333 * Returns a flattened mapping from user IDs to lists of source IDs for those users. The map is 334 * in the form of a {@link SparseArray} where the int keys are user IDs and the values are the 335 * lists of source IDs. 336 * 337 * <p>The set of user IDs (keys) is the profile parent user ID of {@code userProfileGroup} plus 338 * all the other types of running profiles: 339 * <ol> 340 * <li>The (possibly empty) set of running managed profile user IDs in that group. 341 * <li>The (possibly empty) set of running private profile user ID in that group. 342 * </ol> 343 * <p>Every value present is a non-empty list, but the overall result may be empty. 344 */ getUserIdsToSourceIds( Broadcast broadcast, UserProfileGroup userProfileGroup, @RefreshReason int refreshReason)345 private SparseArray<List<String>> getUserIdsToSourceIds( 346 Broadcast broadcast, 347 UserProfileGroup userProfileGroup, 348 @RefreshReason int refreshReason) { 349 SparseArray<List<String>> result = 350 new SparseArray<>(userProfileGroup.getNumRunningProfiles()); 351 for (int profilTypeIdx = 0; 352 profilTypeIdx < ProfileType.ALL_PROFILE_TYPES.length; 353 ++profilTypeIdx) { 354 @ProfileType int profileType = ProfileType.ALL_PROFILE_TYPES[profilTypeIdx]; 355 int[] runningProfiles = userProfileGroup.getRunningProfilesOfType(profileType); 356 for (int profileIdx = 0; profileIdx < runningProfiles.length; ++profileIdx) { 357 List<String> profileSources = 358 getSourceIdsForRefreshReason( 359 refreshReason, 360 broadcast.getSourceIdsForProfileType(profileType), 361 broadcast.getSourceIdsOnPageOpenForProfileType(profileType), 362 runningProfiles[profileIdx]); 363 364 if (!profileSources.isEmpty()) { 365 result.put(runningProfiles[profileIdx], profileSources); 366 } 367 } 368 } 369 370 return result; 371 } 372 373 /** 374 * Returns the sources to refresh for the given {@code refreshReason}. 375 * 376 * <p>For {@link SafetyCenterManager#REFRESH_REASON_PAGE_OPEN}, returns a copy of {@code 377 * allSourceIds} filtered to contain only sources that have refreshOnPageOpenAllowed in the XML 378 * config, or are in the safety_center_override_refresh_on_page_open_sources flag, or don't have 379 * any {@link SafetySourceData} provided. 380 */ getSourceIdsForRefreshReason( @efreshReason int refreshReason, List<String> allSourceIds, List<String> pageOpenSourceIds, @UserIdInt int userId)381 private List<String> getSourceIdsForRefreshReason( 382 @RefreshReason int refreshReason, 383 List<String> allSourceIds, 384 List<String> pageOpenSourceIds, 385 @UserIdInt int userId) { 386 if (refreshReason != REFRESH_REASON_PAGE_OPEN) { 387 return allSourceIds; 388 } 389 390 List<String> sourceIds = new ArrayList<>(); 391 392 ArraySet<String> flagAllowListedSourceIds = 393 SafetyCenterFlags.getOverrideRefreshOnPageOpenSourceIds(); 394 395 for (int i = 0; i < allSourceIds.size(); i++) { 396 String sourceId = allSourceIds.get(i); 397 if (pageOpenSourceIds.contains(sourceId) 398 || flagAllowListedSourceIds.contains(sourceId) 399 || mSafetyCenterDataManager.getSafetySourceDataInternal( 400 SafetySourceKey.of(sourceId, userId)) 401 == null) { 402 sourceIds.add(sourceId); 403 } 404 } 405 406 return unmodifiableList(sourceIds); 407 } 408 } 409