1 /* 2 * Copyright (C) 2021 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 android.safetycenter; 18 19 import static android.Manifest.permission.MANAGE_SAFETY_CENTER; 20 import static android.Manifest.permission.READ_SAFETY_CENTER_STATUS; 21 import static android.Manifest.permission.SEND_SAFETY_CENTER_UPDATE; 22 import static android.annotation.SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION; 23 import static android.os.Build.VERSION_CODES.TIRAMISU; 24 import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; 25 26 import static java.util.Objects.requireNonNull; 27 28 import android.annotation.CallbackExecutor; 29 import android.annotation.IntDef; 30 import android.annotation.NonNull; 31 import android.annotation.Nullable; 32 import android.annotation.RequiresPermission; 33 import android.annotation.SdkConstant; 34 import android.annotation.SystemApi; 35 import android.annotation.SystemService; 36 import android.annotation.TargetApi; 37 import android.content.Context; 38 import android.content.Intent; 39 import android.os.Binder; 40 import android.os.RemoteException; 41 import android.safetycenter.config.SafetyCenterConfig; 42 import android.util.ArrayMap; 43 44 import androidx.annotation.RequiresApi; 45 46 import com.android.internal.annotations.GuardedBy; 47 import com.android.modules.utils.build.SdkLevel; 48 49 import java.lang.annotation.Retention; 50 import java.lang.annotation.RetentionPolicy; 51 import java.util.List; 52 import java.util.Map; 53 import java.util.concurrent.Executor; 54 55 /** 56 * Interface for communicating with the Safety Center, which consolidates UI for security and 57 * privacy features on the device. 58 * 59 * <p>These APIs are intended to be used by the following clients: 60 * 61 * <ul> 62 * <li>Safety sources represented in Safety Center UI 63 * <li>Dependents on the state of Safety Center UI 64 * <li>Managers of Safety Center UI 65 * </ul> 66 * 67 * @hide 68 */ 69 @SystemService(Context.SAFETY_CENTER_SERVICE) 70 @SystemApi 71 @RequiresApi(TIRAMISU) 72 public final class SafetyCenterManager { 73 74 /** 75 * Broadcast Action: A broadcast sent by the system to indicate that the value returned by 76 * {@link SafetyCenterManager#isSafetyCenterEnabled()} has changed. 77 * 78 * <p>This broadcast will inform receivers about changes to {@link 79 * SafetyCenterManager#isSafetyCenterEnabled()}, should they want to check the new value and 80 * enable/disable components accordingly. 81 * 82 * <p>This broadcast is sent explicitly to safety sources by targeting intents to a specified 83 * set of packages in the {@link SafetyCenterConfig}. The receiving components must hold the 84 * {@link android.Manifest.permission#SEND_SAFETY_CENTER_UPDATE} permission, and can use a 85 * manifest-registered receiver to be woken up by Safety Center. 86 * 87 * <p>This broadcast is also sent implicitly system-wide. The receiving components must hold the 88 * {@link android.Manifest.permission#READ_SAFETY_CENTER_STATUS} permission. 89 * 90 * <p>This broadcast is not sent out if the device does not support Safety Center. 91 * 92 * <p class="note">This is a protected intent that can only be sent by the system. 93 */ 94 @SdkConstant(BROADCAST_INTENT_ACTION) 95 public static final String ACTION_SAFETY_CENTER_ENABLED_CHANGED = 96 "android.safetycenter.action.SAFETY_CENTER_ENABLED_CHANGED"; 97 98 /** 99 * Broadcast Action: A broadcast sent by the system to indicate that {@link SafetyCenterManager} 100 * is requesting data from safety sources regarding their safety state. 101 * 102 * <p>This broadcast is sent when a user triggers a data refresh from the Safety Center UI or 103 * when Safety Center detects that its stored safety information is stale and needs to be 104 * updated. 105 * 106 * <p>This broadcast is sent explicitly to safety sources by targeting intents to a specified 107 * set of packages provided by the safety sources in the {@link SafetyCenterConfig}. The 108 * receiving components must hold the {@link 109 * android.Manifest.permission#SEND_SAFETY_CENTER_UPDATE} permission, and can use a 110 * manifest-registered receiver to be woken up by Safety Center. 111 * 112 * <p>On receiving this broadcast, safety sources should determine their safety state according 113 * to the parameters specified in the intent extras (see below) and set {@link SafetySourceData} 114 * using {@link #setSafetySourceData}, along with a {@link SafetyEvent} with {@link 115 * SafetyEvent#getType()} set to {@link SafetyEvent#SAFETY_EVENT_TYPE_REFRESH_REQUESTED} and 116 * {@link SafetyEvent#getRefreshBroadcastId()} set to the value of broadcast intent extra {@link 117 * #EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID}. If the safety source is unable to provide data, 118 * it can set a {@code null} {@link SafetySourceData}, which will clear any existing {@link 119 * SafetySourceData} stored by Safety Center, and Safety Center will fall back to any 120 * placeholder data specified in {@link SafetyCenterConfig}. 121 * 122 * <p class="note">This is a protected intent that can only be sent by the system. 123 * 124 * <p>Includes the following extras: 125 * 126 * <ul> 127 * <li>{@link #EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE}: An int representing the type of 128 * data being requested. Possible values are {@link 129 * #EXTRA_REFRESH_REQUEST_TYPE_FETCH_FRESH_DATA} and {@link 130 * #EXTRA_REFRESH_REQUEST_TYPE_GET_DATA}. 131 * <li>{@link #EXTRA_REFRESH_SAFETY_SOURCE_IDS}: A {@code String[]} of ids representing the 132 * safety sources being requested for data. This extra exists for disambiguation in the 133 * case that a single component is responsible for receiving refresh requests for multiple 134 * safety sources. 135 * <li>{@link #EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID}: An unique identifier for the 136 * refresh request broadcast. This extra should be used to specify {@link 137 * SafetyEvent#getRefreshBroadcastId()} when the safety source responds to the broadcast 138 * using {@link #setSafetySourceData}. 139 * </ul> 140 */ 141 @SdkConstant(BROADCAST_INTENT_ACTION) 142 public static final String ACTION_REFRESH_SAFETY_SOURCES = 143 "android.safetycenter.action.REFRESH_SAFETY_SOURCES"; 144 145 /** 146 * Used as a {@code String[]} extra field in {@link #ACTION_REFRESH_SAFETY_SOURCES} intents to 147 * specify the safety source ids of the safety sources being requested for data by Safety 148 * Center. 149 * 150 * <p>When this extra field is not specified in the intent, it is assumed that Safety Center is 151 * requesting data from all safety sources supported by the component receiving the broadcast. 152 */ 153 public static final String EXTRA_REFRESH_SAFETY_SOURCE_IDS = 154 "android.safetycenter.extra.REFRESH_SAFETY_SOURCE_IDS"; 155 156 /** 157 * Used as an {@code int} extra field in {@link #ACTION_REFRESH_SAFETY_SOURCES} intents to 158 * specify the type of data request from Safety Center. 159 * 160 * <p>Possible values are {@link #EXTRA_REFRESH_REQUEST_TYPE_FETCH_FRESH_DATA} and {@link 161 * #EXTRA_REFRESH_REQUEST_TYPE_GET_DATA} 162 */ 163 public static final String EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE = 164 "android.safetycenter.extra.REFRESH_SAFETY_SOURCES_REQUEST_TYPE"; 165 166 /** 167 * Used as a {@code String} extra field in {@link #ACTION_REFRESH_SAFETY_SOURCES} intents to 168 * specify a string identifier for the broadcast. 169 */ 170 public static final String EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID = 171 "android.safetycenter.extra.REFRESH_SAFETY_SOURCES_BROADCAST_ID"; 172 173 /** 174 * Used as a {@code String} extra field in {@link Intent#ACTION_SAFETY_CENTER} intents to 175 * specify an issue ID to redirect to, if applicable. 176 * 177 * <p>This extra must be used in conjunction with {@link #EXTRA_SAFETY_SOURCE_ID} as an issue ID 178 * does not uniquely identify a {@link SafetySourceIssue}. Otherwise, no redirection will occur. 179 */ 180 public static final String EXTRA_SAFETY_SOURCE_ISSUE_ID = 181 "android.safetycenter.extra.SAFETY_SOURCE_ISSUE_ID"; 182 183 /** 184 * Used as a {@code String} extra field in {@link Intent#ACTION_SAFETY_CENTER} intents to 185 * specify a source ID for the {@link SafetySourceIssue} to redirect to, if applicable. 186 * 187 * <p>This extra must be used in conjunction with {@link #EXTRA_SAFETY_SOURCE_ISSUE_ID}. 188 * Otherwise, no redirection will occur. 189 */ 190 public static final String EXTRA_SAFETY_SOURCE_ID = 191 "android.safetycenter.extra.SAFETY_SOURCE_ID"; 192 193 /** 194 * Used as a {@link android.os.UserHandle} extra field in {@link Intent#ACTION_SAFETY_CENTER} 195 * intents to specify a user for a given {@link SafetySourceIssue} to redirect to, if 196 * applicable. 197 * 198 * <p>This extra can be used if the same issue ID is created for multiple users (e.g. to 199 * disambiguate personal profile vs. managed profiles issues). 200 * 201 * <p>This extra can be used in conjunction with {@link #EXTRA_SAFETY_SOURCE_ISSUE_ID} and 202 * {@link #EXTRA_SAFETY_SOURCE_ID}. Otherwise, the device's primary user will be used. 203 */ 204 public static final String EXTRA_SAFETY_SOURCE_USER_HANDLE = 205 "android.safetycenter.extra.SAFETY_SOURCE_USER_HANDLE"; 206 207 /** 208 * Used as a {@code String} extra field in {@link Intent#ACTION_SAFETY_CENTER} intents to 209 * specify the ID for a group of safety sources. If applicable, this will redirect to the 210 * group's corresponding subpage in the UI. 211 */ 212 @RequiresApi(UPSIDE_DOWN_CAKE) 213 public static final String EXTRA_SAFETY_SOURCES_GROUP_ID = 214 "android.safetycenter.extra.SAFETY_SOURCES_GROUP_ID"; 215 216 /** 217 * Used as an int value for {@link #EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE} to indicate that 218 * the safety source should fetch fresh data relating to their safety state upon receiving a 219 * broadcast with intent action {@link #ACTION_REFRESH_SAFETY_SOURCES} and provide it to Safety 220 * Center. 221 * 222 * <p>The term "fresh" here means that the sources should ensure that the safety data is 223 * accurate as possible at the time of providing it to Safety Center, even if it involves 224 * performing an expensive and/or slow process. 225 */ 226 public static final int EXTRA_REFRESH_REQUEST_TYPE_FETCH_FRESH_DATA = 0; 227 228 /** 229 * Used as an int value for {@link #EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE} to indicate that 230 * upon receiving a broadcast with intent action {@link #ACTION_REFRESH_SAFETY_SOURCES}, the 231 * safety source should provide data relating to their safety state to Safety Center. 232 * 233 * <p>If the source already has its safety data cached, it may provide it without triggering a 234 * process to fetch state which may be expensive and/or slow. 235 */ 236 public static final int EXTRA_REFRESH_REQUEST_TYPE_GET_DATA = 1; 237 238 /** 239 * All possible types of data refresh requests in broadcasts with intent action {@link 240 * #ACTION_REFRESH_SAFETY_SOURCES}. 241 * 242 * @hide 243 */ 244 @IntDef( 245 prefix = {"EXTRA_REFRESH_REQUEST_TYPE_"}, 246 value = { 247 EXTRA_REFRESH_REQUEST_TYPE_FETCH_FRESH_DATA, 248 EXTRA_REFRESH_REQUEST_TYPE_GET_DATA, 249 }) 250 @Retention(RetentionPolicy.SOURCE) 251 public @interface RefreshRequestType {} 252 253 /** Indicates that the Safety Center UI has been opened by the user. */ 254 public static final int REFRESH_REASON_PAGE_OPEN = 100; 255 256 /** Indicates that the rescan button in the Safety Center UI has been clicked on by the user. */ 257 public static final int REFRESH_REASON_RESCAN_BUTTON_CLICK = 200; 258 259 /** Indicates that the device was rebooted. */ 260 public static final int REFRESH_REASON_DEVICE_REBOOT = 300; 261 262 /** Indicates that the device locale was changed. */ 263 public static final int REFRESH_REASON_DEVICE_LOCALE_CHANGE = 400; 264 265 /** Indicates that the Safety Center feature was enabled. */ 266 public static final int REFRESH_REASON_SAFETY_CENTER_ENABLED = 500; 267 268 /** Indicates a generic reason for Safety Center refresh. */ 269 public static final int REFRESH_REASON_OTHER = 600; 270 271 /** Indicates a periodic background refresh. */ 272 @RequiresApi(UPSIDE_DOWN_CAKE) 273 public static final int REFRESH_REASON_PERIODIC = 700; 274 275 /** 276 * The reason for requesting a refresh of {@link SafetySourceData} from safety sources. 277 * 278 * @hide 279 */ 280 @IntDef( 281 prefix = {"REFRESH_REASON_"}, 282 value = { 283 REFRESH_REASON_PAGE_OPEN, 284 REFRESH_REASON_RESCAN_BUTTON_CLICK, 285 REFRESH_REASON_DEVICE_REBOOT, 286 REFRESH_REASON_DEVICE_LOCALE_CHANGE, 287 REFRESH_REASON_SAFETY_CENTER_ENABLED, 288 REFRESH_REASON_OTHER, 289 REFRESH_REASON_PERIODIC 290 }) 291 @Retention(RetentionPolicy.SOURCE) 292 @TargetApi(UPSIDE_DOWN_CAKE) 293 public @interface RefreshReason {} 294 295 /** Listener for changes to {@link SafetyCenterData}. */ 296 public interface OnSafetyCenterDataChangedListener { 297 298 /** 299 * Called when {@link SafetyCenterData} tracked by the manager changes. 300 * 301 * @param data the updated data 302 */ onSafetyCenterDataChanged(@onNull SafetyCenterData data)303 void onSafetyCenterDataChanged(@NonNull SafetyCenterData data); 304 305 /** 306 * Called when the Safety Center should display an error related to changes in its data. 307 * 308 * @param errorDetails details of an error that should be displayed to the user 309 */ onError(@onNull SafetyCenterErrorDetails errorDetails)310 default void onError(@NonNull SafetyCenterErrorDetails errorDetails) {} 311 } 312 313 private final Object mListenersLock = new Object(); 314 315 @GuardedBy("mListenersLock") 316 private final Map<OnSafetyCenterDataChangedListener, ListenerDelegate> mListenersToDelegates = 317 new ArrayMap<>(); 318 319 @NonNull private final Context mContext; 320 @NonNull private final ISafetyCenterManager mService; 321 322 /** 323 * Creates a new instance of the {@link SafetyCenterManager}. 324 * 325 * @param context the {@link Context} 326 * @param service the {@link ISafetyCenterManager} service 327 * @hide 328 */ SafetyCenterManager(@onNull Context context, @NonNull ISafetyCenterManager service)329 public SafetyCenterManager(@NonNull Context context, @NonNull ISafetyCenterManager service) { 330 this.mContext = context; 331 this.mService = service; 332 } 333 334 /** 335 * Returns whether the Safety Center feature is enabled. 336 * 337 * <p>If this returns {@code false}, all the other methods in this class will no-op and/or 338 * return default values. 339 */ 340 @RequiresPermission(anyOf = {READ_SAFETY_CENTER_STATUS, SEND_SAFETY_CENTER_UPDATE}) isSafetyCenterEnabled()341 public boolean isSafetyCenterEnabled() { 342 try { 343 return mService.isSafetyCenterEnabled(); 344 } catch (RemoteException e) { 345 throw e.rethrowFromSystemServer(); 346 } 347 } 348 349 /** 350 * Set the latest {@link SafetySourceData} for a safety source, to be displayed in Safety Center 351 * UI. 352 * 353 * <p>Each {@code safetySourceId} uniquely identifies the {@link SafetySourceData} for the 354 * calling user. 355 * 356 * <p>This call will rewrite any existing {@link SafetySourceData} already set for the given 357 * {@code safetySourceId} for the calling user. 358 * 359 * @param safetySourceId the unique identifier for a safety source in the calling user 360 * @param safetySourceData the latest safety data for the safety source in the calling user. If 361 * a safety source does not have any data to set, it can set its {@link SafetySourceData} to 362 * {@code null}, in which case Safety Center will fall back to any placeholder data 363 * specified in the safety source xml configuration. 364 * @param safetyEvent the event that triggered the safety source to set safety data 365 */ 366 @RequiresPermission(SEND_SAFETY_CENTER_UPDATE) setSafetySourceData( @onNull String safetySourceId, @Nullable SafetySourceData safetySourceData, @NonNull SafetyEvent safetyEvent)367 public void setSafetySourceData( 368 @NonNull String safetySourceId, 369 @Nullable SafetySourceData safetySourceData, 370 @NonNull SafetyEvent safetyEvent) { 371 requireNonNull(safetySourceId, "safetySourceId cannot be null"); 372 requireNonNull(safetyEvent, "safetyEvent cannot be null"); 373 374 try { 375 mService.setSafetySourceData( 376 safetySourceId, 377 safetySourceData, 378 safetyEvent, 379 mContext.getPackageName(), 380 mContext.getUser().getIdentifier()); 381 } catch (RemoteException e) { 382 throw e.rethrowFromSystemServer(); 383 } 384 } 385 386 /** 387 * Returns the latest {@link SafetySourceData} set through {@link #setSafetySourceData} for the 388 * given {@code safetySourceId} and calling user. 389 * 390 * <p>Returns {@code null} if there never was any data sent for the given {@code safetySourceId} 391 * and user. 392 */ 393 @RequiresPermission(SEND_SAFETY_CENTER_UPDATE) 394 @Nullable getSafetySourceData(@onNull String safetySourceId)395 public SafetySourceData getSafetySourceData(@NonNull String safetySourceId) { 396 requireNonNull(safetySourceId, "safetySourceId cannot be null"); 397 398 try { 399 return mService.getSafetySourceData( 400 safetySourceId, mContext.getPackageName(), mContext.getUser().getIdentifier()); 401 } catch (RemoteException e) { 402 throw e.rethrowFromSystemServer(); 403 } 404 } 405 406 /** 407 * Notifies the Safety Center of an error related to a given safety source. 408 * 409 * <p>Safety sources should use this API to notify Safety Center when Safety Center requested or 410 * expected them to perform an action or provide data, but they were unable to do so. 411 * 412 * @param safetySourceId the id of the safety source that provided the issue 413 * @param safetySourceErrorDetails details of the error that occurred 414 */ 415 @RequiresPermission(SEND_SAFETY_CENTER_UPDATE) reportSafetySourceError( @onNull String safetySourceId, @NonNull SafetySourceErrorDetails safetySourceErrorDetails)416 public void reportSafetySourceError( 417 @NonNull String safetySourceId, 418 @NonNull SafetySourceErrorDetails safetySourceErrorDetails) { 419 requireNonNull(safetySourceId, "safetySourceId cannot be null"); 420 requireNonNull(safetySourceErrorDetails, "safetySourceErrorDetails cannot be null"); 421 422 try { 423 mService.reportSafetySourceError( 424 safetySourceId, 425 safetySourceErrorDetails, 426 mContext.getPackageName(), 427 mContext.getUser().getIdentifier()); 428 } catch (RemoteException e) { 429 throw e.rethrowFromSystemServer(); 430 } 431 } 432 433 /** 434 * Requests safety sources to set their latest {@link SafetySourceData} for Safety Center. 435 * 436 * <p>This API sends a broadcast to all safety sources with action {@link 437 * #ACTION_REFRESH_SAFETY_SOURCES}. See {@link #ACTION_REFRESH_SAFETY_SOURCES} for details on 438 * how safety sources should respond to receiving these broadcasts. 439 * 440 * @param refreshReason the reason for the refresh 441 */ 442 @RequiresPermission(MANAGE_SAFETY_CENTER) refreshSafetySources(@efreshReason int refreshReason)443 public void refreshSafetySources(@RefreshReason int refreshReason) { 444 try { 445 mService.refreshSafetySources(refreshReason, mContext.getUser().getIdentifier()); 446 } catch (RemoteException e) { 447 throw e.rethrowFromSystemServer(); 448 } 449 } 450 451 /** 452 * Requests a specific subset of safety sources to set their latest {@link SafetySourceData} for 453 * Safety Center. 454 * 455 * <p>This API sends a broadcast to safety sources with action {@link 456 * #ACTION_REFRESH_SAFETY_SOURCES} and {@link #EXTRA_REFRESH_SAFETY_SOURCE_IDS} to specify the 457 * IDs of safety sources being requested for data by Safety Center. 458 * 459 * <p>This API is an overload of {@link #refreshSafetySources(int)} and is used to request data 460 * from safety sources that are part of a subpage in the Safety Center UI. 461 * 462 * @see #refreshSafetySources(int) 463 * @param refreshReason the reason for the refresh 464 * @param safetySourceIds list of IDs for the safety sources being refreshed 465 * @throws UnsupportedOperationException if accessed from a version lower than {@link 466 * UPSIDE_DOWN_CAKE} 467 */ 468 @RequiresPermission(MANAGE_SAFETY_CENTER) 469 @RequiresApi(UPSIDE_DOWN_CAKE) refreshSafetySources( @efreshReason int refreshReason, @NonNull List<String> safetySourceIds)470 public void refreshSafetySources( 471 @RefreshReason int refreshReason, @NonNull List<String> safetySourceIds) { 472 if (!SdkLevel.isAtLeastU()) { 473 throw new UnsupportedOperationException( 474 "Method not supported on versions lower than UPSIDE_DOWN_CAKE"); 475 } 476 477 requireNonNull(safetySourceIds, "safetySourceIds cannot be null"); 478 479 try { 480 mService.refreshSpecificSafetySources( 481 refreshReason, mContext.getUser().getIdentifier(), safetySourceIds); 482 } catch (RemoteException e) { 483 throw e.rethrowFromSystemServer(); 484 } 485 } 486 487 /** Returns the current {@link SafetyCenterConfig}, if available. */ 488 @RequiresPermission(MANAGE_SAFETY_CENTER) 489 @Nullable getSafetyCenterConfig()490 public SafetyCenterConfig getSafetyCenterConfig() { 491 try { 492 return mService.getSafetyCenterConfig(); 493 } catch (RemoteException e) { 494 throw e.rethrowFromSystemServer(); 495 } 496 } 497 498 /** 499 * Returns the current {@link SafetyCenterData}, assembled from {@link SafetySourceData} from 500 * all sources. 501 */ 502 @RequiresPermission(MANAGE_SAFETY_CENTER) 503 @NonNull getSafetyCenterData()504 public SafetyCenterData getSafetyCenterData() { 505 try { 506 return mService.getSafetyCenterData( 507 mContext.getPackageName(), mContext.getUser().getIdentifier()); 508 } catch (RemoteException e) { 509 throw e.rethrowFromSystemServer(); 510 } 511 } 512 513 /** 514 * Adds a listener for changes to {@link SafetyCenterData}. 515 * 516 * @see #removeOnSafetyCenterDataChangedListener(OnSafetyCenterDataChangedListener) 517 */ 518 @RequiresPermission(MANAGE_SAFETY_CENTER) addOnSafetyCenterDataChangedListener( @onNull @allbackExecutor Executor executor, @NonNull OnSafetyCenterDataChangedListener listener)519 public void addOnSafetyCenterDataChangedListener( 520 @NonNull @CallbackExecutor Executor executor, 521 @NonNull OnSafetyCenterDataChangedListener listener) { 522 requireNonNull(executor, "executor cannot be null"); 523 requireNonNull(listener, "listener cannot be null"); 524 525 synchronized (mListenersLock) { 526 if (mListenersToDelegates.containsKey(listener)) return; 527 528 ListenerDelegate delegate = new ListenerDelegate(executor, listener); 529 try { 530 mService.addOnSafetyCenterDataChangedListener( 531 delegate, mContext.getPackageName(), mContext.getUser().getIdentifier()); 532 } catch (RemoteException e) { 533 throw e.rethrowFromSystemServer(); 534 } 535 mListenersToDelegates.put(listener, delegate); 536 } 537 } 538 539 /** 540 * Removes a listener for changes to {@link SafetyCenterData}. 541 * 542 * @see #addOnSafetyCenterDataChangedListener(Executor, OnSafetyCenterDataChangedListener) 543 */ 544 @RequiresPermission(MANAGE_SAFETY_CENTER) removeOnSafetyCenterDataChangedListener( @onNull OnSafetyCenterDataChangedListener listener)545 public void removeOnSafetyCenterDataChangedListener( 546 @NonNull OnSafetyCenterDataChangedListener listener) { 547 requireNonNull(listener, "listener cannot be null"); 548 549 synchronized (mListenersLock) { 550 ListenerDelegate delegate = mListenersToDelegates.get(listener); 551 if (delegate == null) return; 552 553 try { 554 mService.removeOnSafetyCenterDataChangedListener( 555 delegate, mContext.getUser().getIdentifier()); 556 } catch (RemoteException e) { 557 throw e.rethrowFromSystemServer(); 558 } 559 delegate.markAsRemoved(); 560 mListenersToDelegates.remove(listener); 561 } 562 } 563 564 /** 565 * Dismiss a Safety Center issue and prevent it from affecting the overall safety status. 566 * 567 * @param safetyCenterIssueId the target issue ID returned by {@link SafetyCenterIssue#getId()} 568 */ 569 @RequiresPermission(MANAGE_SAFETY_CENTER) dismissSafetyCenterIssue(@onNull String safetyCenterIssueId)570 public void dismissSafetyCenterIssue(@NonNull String safetyCenterIssueId) { 571 requireNonNull(safetyCenterIssueId, "safetyCenterIssueId cannot be null"); 572 573 try { 574 mService.dismissSafetyCenterIssue( 575 safetyCenterIssueId, mContext.getUser().getIdentifier()); 576 } catch (RemoteException e) { 577 throw e.rethrowFromSystemServer(); 578 } 579 } 580 581 /** 582 * Executes the specified Safety Center issue action on the specified Safety Center issue. 583 * 584 * @param safetyCenterIssueId the target issue ID returned by {@link SafetyCenterIssue#getId()} 585 * @param safetyCenterIssueActionId the target action ID returned by {@link 586 * SafetyCenterIssue.Action#getId()} 587 */ 588 @RequiresPermission(MANAGE_SAFETY_CENTER) executeSafetyCenterIssueAction( @onNull String safetyCenterIssueId, @NonNull String safetyCenterIssueActionId)589 public void executeSafetyCenterIssueAction( 590 @NonNull String safetyCenterIssueId, @NonNull String safetyCenterIssueActionId) { 591 requireNonNull(safetyCenterIssueId, "safetyCenterIssueId cannot be null"); 592 requireNonNull(safetyCenterIssueActionId, "safetyCenterIssueActionId cannot be null"); 593 594 try { 595 mService.executeSafetyCenterIssueAction( 596 safetyCenterIssueId, 597 safetyCenterIssueActionId, 598 mContext.getUser().getIdentifier()); 599 } catch (RemoteException e) { 600 throw e.rethrowFromSystemServer(); 601 } 602 } 603 604 /** 605 * Clears all {@link SafetySourceData} (set by safety sources using {@link 606 * #setSafetySourceData}) for testing. 607 * 608 * <p>Note: This API serves to facilitate CTS testing and should not be used for other purposes. 609 */ 610 @RequiresPermission(MANAGE_SAFETY_CENTER) clearAllSafetySourceDataForTests()611 public void clearAllSafetySourceDataForTests() { 612 try { 613 mService.clearAllSafetySourceDataForTests(); 614 } catch (RemoteException e) { 615 throw e.rethrowFromSystemServer(); 616 } 617 } 618 619 /** 620 * Overrides the {@link SafetyCenterConfig} for testing. 621 * 622 * <p>When set, the overridden {@link SafetyCenterConfig} will be used instead of the {@link 623 * SafetyCenterConfig} parsed from the XML file to read configured safety sources. 624 * 625 * <p>Note: This API serves to facilitate CTS testing and should not be used to configure safety 626 * sources dynamically for production. Once used for testing, the override should be cleared. 627 * 628 * @see #clearSafetyCenterConfigForTests() 629 */ 630 @RequiresPermission(MANAGE_SAFETY_CENTER) setSafetyCenterConfigForTests(@onNull SafetyCenterConfig safetyCenterConfig)631 public void setSafetyCenterConfigForTests(@NonNull SafetyCenterConfig safetyCenterConfig) { 632 requireNonNull(safetyCenterConfig, "safetyCenterConfig cannot be null"); 633 634 try { 635 mService.setSafetyCenterConfigForTests(safetyCenterConfig); 636 } catch (RemoteException e) { 637 throw e.rethrowFromSystemServer(); 638 } 639 } 640 641 /** 642 * Clears the override of the {@link SafetyCenterConfig} set for testing. 643 * 644 * <p>Once cleared, the {@link SafetyCenterConfig} parsed from the XML file will be used to read 645 * configured safety sources. 646 * 647 * <p>Note: This API serves to facilitate CTS testing and should not be used for other purposes. 648 * 649 * @see #setSafetyCenterConfigForTests(SafetyCenterConfig) 650 */ 651 @RequiresPermission(MANAGE_SAFETY_CENTER) clearSafetyCenterConfigForTests()652 public void clearSafetyCenterConfigForTests() { 653 try { 654 mService.clearSafetyCenterConfigForTests(); 655 } catch (RemoteException e) { 656 throw e.rethrowFromSystemServer(); 657 } 658 } 659 660 private static final class ListenerDelegate extends IOnSafetyCenterDataChangedListener.Stub { 661 @NonNull private final Executor mExecutor; 662 @NonNull private final OnSafetyCenterDataChangedListener mOriginalListener; 663 664 private volatile boolean mRemoved = false; 665 ListenerDelegate( @onNull Executor executor, @NonNull OnSafetyCenterDataChangedListener originalListener)666 private ListenerDelegate( 667 @NonNull Executor executor, 668 @NonNull OnSafetyCenterDataChangedListener originalListener) { 669 mExecutor = executor; 670 mOriginalListener = originalListener; 671 } 672 673 @Override onSafetyCenterDataChanged(@onNull SafetyCenterData safetyCenterData)674 public void onSafetyCenterDataChanged(@NonNull SafetyCenterData safetyCenterData) { 675 requireNonNull(safetyCenterData, "safetyCenterData cannot be null"); 676 677 final long identity = Binder.clearCallingIdentity(); 678 try { 679 mExecutor.execute( 680 () -> { 681 if (mRemoved) { 682 return; 683 } 684 // The remove call could still complete at this point, but we're ok 685 // with this raciness on separate threads. If the listener is removed on 686 // the same thread as `mExecutor`; the above check should ensure that 687 // the listener won't be called after the remove call. 688 mOriginalListener.onSafetyCenterDataChanged(safetyCenterData); 689 }); 690 } finally { 691 Binder.restoreCallingIdentity(identity); 692 } 693 } 694 695 @Override onError(@onNull SafetyCenterErrorDetails safetyCenterErrorDetails)696 public void onError(@NonNull SafetyCenterErrorDetails safetyCenterErrorDetails) { 697 requireNonNull(safetyCenterErrorDetails, "safetyCenterErrorDetails cannot be null"); 698 699 final long identity = Binder.clearCallingIdentity(); 700 try { 701 mExecutor.execute( 702 () -> { 703 if (mRemoved) { 704 return; 705 } 706 // The remove call could still complete at this point, but we're ok 707 // with this raciness on separate threads. If the listener is removed on 708 // the same thread as `mExecutor`; the above check should ensure that 709 // the listener won't be called after the remove call. 710 mOriginalListener.onError(safetyCenterErrorDetails); 711 }); 712 } finally { 713 Binder.restoreCallingIdentity(identity); 714 } 715 } 716 markAsRemoved()717 public void markAsRemoved() { 718 mRemoved = true; 719 } 720 } 721 } 722