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