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 com.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.Manifest.permission.START_TASKS_FROM_RECENTS; 23 import static android.content.pm.PackageManager.PERMISSION_GRANTED; 24 import static android.os.Build.VERSION_CODES.TIRAMISU; 25 import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; 26 import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_OTHER; 27 import static android.safetycenter.SafetyCenterManager.RefreshReason; 28 import static android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED; 29 import static android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED; 30 31 import static com.android.permission.PermissionStatsLog.SAFETY_CENTER_SYSTEM_EVENT_REPORTED__RESULT__TIMEOUT; 32 import static com.android.permission.PermissionStatsLog.SAFETY_STATE; 33 import static com.android.safetycenter.SafetyCenterFlags.PROPERTY_SAFETY_CENTER_ENABLED; 34 import static com.android.safetycenter.internaldata.SafetyCenterIds.toUserFriendlyString; 35 36 import static java.util.Objects.requireNonNull; 37 38 import android.annotation.Nullable; 39 import android.annotation.UserIdInt; 40 import android.app.PendingIntent; 41 import android.app.StatsManager; 42 import android.app.StatsManager.StatsPullAtomCallback; 43 import android.content.BroadcastReceiver; 44 import android.content.Context; 45 import android.content.Intent; 46 import android.content.IntentFilter; 47 import android.content.pm.PackageManager; 48 import android.content.pm.PackageManager.NameNotFoundException; 49 import android.content.pm.PackageManager.PackageInfoFlags; 50 import android.content.res.Resources; 51 import android.os.Binder; 52 import android.os.ParcelFileDescriptor; 53 import android.os.Process; 54 import android.os.UserHandle; 55 import android.provider.DeviceConfig; 56 import android.provider.DeviceConfig.OnPropertiesChangedListener; 57 import android.safetycenter.IOnSafetyCenterDataChangedListener; 58 import android.safetycenter.ISafetyCenterManager; 59 import android.safetycenter.SafetyCenterData; 60 import android.safetycenter.SafetyCenterErrorDetails; 61 import android.safetycenter.SafetyCenterManager; 62 import android.safetycenter.SafetyEvent; 63 import android.safetycenter.SafetySourceData; 64 import android.safetycenter.SafetySourceErrorDetails; 65 import android.safetycenter.SafetySourceIssue; 66 import android.safetycenter.config.SafetyCenterConfig; 67 import android.text.TextUtils; 68 import android.util.ArraySet; 69 import android.util.Log; 70 71 import androidx.annotation.Keep; 72 import androidx.annotation.RequiresApi; 73 74 import com.android.internal.annotations.GuardedBy; 75 import com.android.modules.utils.BackgroundThread; 76 import com.android.permission.util.ForegroundThread; 77 import com.android.permission.util.UserUtils; 78 import com.android.safetycenter.data.SafetyCenterDataManager; 79 import com.android.safetycenter.internaldata.SafetyCenterIds; 80 import com.android.safetycenter.internaldata.SafetyCenterIssueActionId; 81 import com.android.safetycenter.internaldata.SafetyCenterIssueId; 82 import com.android.safetycenter.internaldata.SafetyCenterIssueKey; 83 import com.android.safetycenter.logging.SafetyCenterPullAtomCallback; 84 import com.android.safetycenter.notifications.SafetyCenterNotificationChannels; 85 import com.android.safetycenter.notifications.SafetyCenterNotificationReceiver; 86 import com.android.safetycenter.notifications.SafetyCenterNotificationSender; 87 import com.android.safetycenter.pendingintents.PendingIntentSender; 88 import com.android.safetycenter.resources.SafetyCenterResourcesContext; 89 import com.android.server.SystemService; 90 91 import java.io.FileDescriptor; 92 import java.io.PrintWriter; 93 import java.util.Arrays; 94 import java.util.List; 95 import java.util.concurrent.Executor; 96 97 import javax.annotation.concurrent.NotThreadSafe; 98 99 /** 100 * Service for the safety center. 101 * 102 * @hide 103 */ 104 @Keep 105 @RequiresApi(TIRAMISU) 106 public final class SafetyCenterService extends SystemService { 107 108 private static final String TAG = "SafetyCenterService"; 109 110 private final ApiLock mApiLock = new ApiLock(); 111 112 @GuardedBy("mApiLock") 113 private final SafetyCenterTimeouts mSafetyCenterTimeouts = new SafetyCenterTimeouts(); 114 115 private final SafetyCenterResourcesContext mSafetyCenterResourcesContext; 116 117 private final SafetyCenterNotificationChannels mNotificationChannels; 118 119 @GuardedBy("mApiLock") 120 private final SafetyCenterConfigReader mSafetyCenterConfigReader; 121 122 @GuardedBy("mApiLock") 123 private final SafetyCenterRefreshTracker mSafetyCenterRefreshTracker; 124 125 @GuardedBy("mApiLock") 126 private final SafetyCenterDataManager mSafetyCenterDataManager; 127 128 @GuardedBy("mApiLock") 129 private final SafetyCenterDataFactory mSafetyCenterDataFactory; 130 131 @GuardedBy("mApiLock") 132 private final SafetyCenterListeners mSafetyCenterListeners; 133 134 @GuardedBy("mApiLock") 135 private final SafetyCenterNotificationSender mNotificationSender; 136 137 @GuardedBy("mApiLock") 138 private final SafetyCenterBroadcastDispatcher mSafetyCenterBroadcastDispatcher; 139 140 @GuardedBy("mApiLock") 141 private final SafetyCenterDataChangeNotifier mSafetyCenterDataChangeNotifier; 142 143 private final StatsPullAtomCallback mPullAtomCallback; 144 private final boolean mDeviceSupportsSafetyCenter; 145 146 /** Whether the {@link SafetyCenterConfig} was successfully loaded. */ 147 private volatile boolean mConfigAvailable; 148 SafetyCenterService(Context context)149 public SafetyCenterService(Context context) { 150 super(context); 151 mSafetyCenterResourcesContext = new SafetyCenterResourcesContext(context); 152 mSafetyCenterConfigReader = new SafetyCenterConfigReader(mSafetyCenterResourcesContext); 153 mSafetyCenterRefreshTracker = new SafetyCenterRefreshTracker(context); 154 mSafetyCenterDataManager = 155 new SafetyCenterDataManager( 156 context, mSafetyCenterConfigReader, mSafetyCenterRefreshTracker, mApiLock); 157 mSafetyCenterDataFactory = 158 new SafetyCenterDataFactory( 159 context, 160 mSafetyCenterResourcesContext, 161 mSafetyCenterConfigReader, 162 mSafetyCenterRefreshTracker, 163 new PendingIntentFactory(context, mSafetyCenterResourcesContext), 164 mSafetyCenterDataManager); 165 mSafetyCenterListeners = new SafetyCenterListeners(mSafetyCenterDataFactory); 166 mNotificationChannels = new SafetyCenterNotificationChannels(mSafetyCenterResourcesContext); 167 mNotificationSender = 168 SafetyCenterNotificationSender.newInstance( 169 context, 170 mSafetyCenterResourcesContext, 171 mNotificationChannels, 172 mSafetyCenterDataManager); 173 mSafetyCenterBroadcastDispatcher = 174 new SafetyCenterBroadcastDispatcher( 175 context, 176 mSafetyCenterConfigReader, 177 mSafetyCenterRefreshTracker, 178 mSafetyCenterDataManager); 179 mPullAtomCallback = 180 new SafetyCenterPullAtomCallback( 181 context, 182 mApiLock, 183 mSafetyCenterConfigReader, 184 mSafetyCenterDataFactory, 185 mSafetyCenterDataManager); 186 mSafetyCenterDataChangeNotifier = 187 new SafetyCenterDataChangeNotifier(mNotificationSender, mSafetyCenterListeners); 188 mDeviceSupportsSafetyCenter = 189 context.getResources() 190 .getBoolean( 191 Resources.getSystem() 192 .getIdentifier( 193 "config_enableSafetyCenter", "bool", "android")); 194 if (!mDeviceSupportsSafetyCenter) { 195 Log.i(TAG, "Device does not support safety center, safety center will be disabled."); 196 } 197 } 198 199 @Override onStart()200 public void onStart() { 201 publishBinderService(Context.SAFETY_CENTER_SERVICE, new Stub()); 202 if (mDeviceSupportsSafetyCenter) { 203 synchronized (mApiLock) { 204 mSafetyCenterResourcesContext.init(); 205 SafetyCenterFlags.init(mSafetyCenterResourcesContext); 206 mConfigAvailable = mSafetyCenterConfigReader.loadConfig(); 207 if (mConfigAvailable) { 208 mSafetyCenterDataManager.loadPersistableDataStateFromFile(); 209 new UserBroadcastReceiver().register(getContext()); 210 new SafetyCenterNotificationReceiver( 211 this, 212 mSafetyCenterDataManager, 213 mSafetyCenterDataChangeNotifier, 214 mApiLock) 215 .register(getContext()); 216 new LocaleBroadcastReceiver().register(getContext()); 217 } 218 } 219 } 220 } 221 222 @Override onBootPhase(int phase)223 public void onBootPhase(int phase) { 224 if (phase == SystemService.PHASE_BOOT_COMPLETED && canUseSafetyCenter()) { 225 registerSafetyCenterEnabledListener(); 226 registerSafetyCenterPullAtomCallback(); 227 mNotificationChannels.createAllChannelsForAllUsers(getContext()); 228 } 229 } 230 registerSafetyCenterEnabledListener()231 private void registerSafetyCenterEnabledListener() { 232 Executor foregroundThreadExecutor = ForegroundThread.getExecutor(); 233 SafetyCenterEnabledListener listener = new SafetyCenterEnabledListener(); 234 // Ensure the listener is called first with the current state on the same thread. 235 foregroundThreadExecutor.execute(listener::setInitialState); 236 DeviceConfig.addOnPropertiesChangedListener( 237 DeviceConfig.NAMESPACE_PRIVACY, foregroundThreadExecutor, listener); 238 } 239 registerSafetyCenterPullAtomCallback()240 private void registerSafetyCenterPullAtomCallback() { 241 StatsManager statsManager = 242 requireNonNull(getContext().getSystemService(StatsManager.class)); 243 statsManager.setPullAtomCallback( 244 SAFETY_STATE, null, BackgroundThread.getExecutor(), mPullAtomCallback); 245 } 246 247 /** Service implementation of {@link ISafetyCenterManager.Stub}. */ 248 private final class Stub extends ISafetyCenterManager.Stub { 249 @Override isSafetyCenterEnabled()250 public boolean isSafetyCenterEnabled() { 251 enforceAnyCallingOrSelfPermissions( 252 "isSafetyCenterEnabled", READ_SAFETY_CENTER_STATUS, SEND_SAFETY_CENTER_UPDATE); 253 254 return isApiEnabled(); 255 } 256 257 @Override setSafetySourceData( String safetySourceId, @Nullable SafetySourceData safetySourceData, SafetyEvent safetyEvent, String packageName, @UserIdInt int userId)258 public void setSafetySourceData( 259 String safetySourceId, 260 @Nullable SafetySourceData safetySourceData, 261 SafetyEvent safetyEvent, 262 String packageName, 263 @UserIdInt int userId) { 264 requireNonNull(safetySourceId); 265 requireNonNull(safetyEvent); 266 requireNonNull(packageName); 267 getContext() 268 .enforceCallingOrSelfPermission( 269 SEND_SAFETY_CENTER_UPDATE, "setSafetySourceData"); 270 if (!enforceCrossUserPermission("setSafetySourceData", userId) 271 || !enforcePackage(Binder.getCallingUid(), packageName, userId) 272 || !checkApiEnabled("setSafetySourceData")) { 273 return; 274 } 275 276 UserProfileGroup userProfileGroup = UserProfileGroup.fromUser(getContext(), userId); 277 synchronized (mApiLock) { 278 boolean hasUpdate = 279 mSafetyCenterDataManager.setSafetySourceData( 280 safetySourceData, safetySourceId, safetyEvent, packageName, userId); 281 if (hasUpdate) { 282 // When an action is successfully resolved, call notifyActionSuccess before 283 // updateDataConsumers: Calling the former first will turn any notification for 284 // the resolved issue into a success notification, whereas calling the latter 285 // will simply clear any issue notification and no success message will show. 286 if (safetyEvent.getType() == SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED) { 287 mNotificationSender.notifyActionSuccess( 288 safetySourceId, safetyEvent, userId); 289 } 290 mSafetyCenterDataChangeNotifier.updateDataConsumers(userProfileGroup, userId); 291 } 292 } 293 } 294 295 @Override 296 @Nullable getSafetySourceData( String safetySourceId, String packageName, @UserIdInt int userId)297 public SafetySourceData getSafetySourceData( 298 String safetySourceId, String packageName, @UserIdInt int userId) { 299 requireNonNull(safetySourceId); 300 requireNonNull(packageName); 301 getContext() 302 .enforceCallingOrSelfPermission( 303 SEND_SAFETY_CENTER_UPDATE, "getSafetySourceData"); 304 if (!enforceCrossUserPermission("getSafetySourceData", userId) 305 || !enforcePackage(Binder.getCallingUid(), packageName, userId) 306 || !checkApiEnabled("getSafetySourceData")) { 307 return null; 308 } 309 310 synchronized (mApiLock) { 311 return mSafetyCenterDataManager.getSafetySourceData( 312 safetySourceId, packageName, userId); 313 } 314 } 315 316 @Override reportSafetySourceError( String safetySourceId, SafetySourceErrorDetails errorDetails, String packageName, @UserIdInt int userId)317 public void reportSafetySourceError( 318 String safetySourceId, 319 SafetySourceErrorDetails errorDetails, 320 String packageName, 321 @UserIdInt int userId) { 322 requireNonNull(safetySourceId); 323 requireNonNull(errorDetails); 324 requireNonNull(packageName); 325 getContext() 326 .enforceCallingOrSelfPermission( 327 SEND_SAFETY_CENTER_UPDATE, "reportSafetySourceError"); 328 if (!enforceCrossUserPermission("reportSafetySourceError", userId) 329 || !enforcePackage(Binder.getCallingUid(), packageName, userId) 330 || !checkApiEnabled("reportSafetySourceError")) { 331 return; 332 } 333 334 UserProfileGroup userProfileGroup = UserProfileGroup.fromUser(getContext(), userId); 335 synchronized (mApiLock) { 336 boolean hasUpdate = 337 mSafetyCenterDataManager.reportSafetySourceError( 338 errorDetails, safetySourceId, packageName, userId); 339 SafetyCenterErrorDetails safetyCenterErrorDetails = null; 340 if (hasUpdate 341 && errorDetails.getSafetyEvent().getType() 342 == SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED) { 343 safetyCenterErrorDetails = 344 new SafetyCenterErrorDetails( 345 mSafetyCenterResourcesContext.getStringByName( 346 "resolving_action_error")); 347 } 348 if (hasUpdate) { 349 mSafetyCenterDataChangeNotifier.updateDataConsumers(userProfileGroup, userId); 350 } 351 if (safetyCenterErrorDetails != null) { 352 mSafetyCenterListeners.deliverErrorForUserProfileGroup( 353 userProfileGroup, safetyCenterErrorDetails); 354 } 355 } 356 } 357 358 @Override refreshSafetySources(@efreshReason int refreshReason, @UserIdInt int userId)359 public void refreshSafetySources(@RefreshReason int refreshReason, @UserIdInt int userId) { 360 RefreshReasons.validate(refreshReason); 361 getContext().enforceCallingPermission(MANAGE_SAFETY_CENTER, "refreshSafetySources"); 362 if (!enforceCrossUserPermission("refreshSafetySources", userId) 363 || !checkApiEnabled("refreshSafetySources")) { 364 return; 365 } 366 startRefreshingSafetySources(refreshReason, userId); 367 } 368 369 @Override 370 @RequiresApi(UPSIDE_DOWN_CAKE) refreshSpecificSafetySources( @efreshReason int refreshReason, @UserIdInt int userId, List<String> safetySourceIds)371 public void refreshSpecificSafetySources( 372 @RefreshReason int refreshReason, 373 @UserIdInt int userId, 374 List<String> safetySourceIds) { 375 requireNonNull(safetySourceIds, "safetySourceIds cannot be null"); 376 RefreshReasons.validate(refreshReason); 377 getContext() 378 .enforceCallingPermission(MANAGE_SAFETY_CENTER, "refreshSpecificSafetySources"); 379 if (!enforceCrossUserPermission("refreshSpecificSafetySources", userId) 380 || !checkApiEnabled("refreshSpecificSafetySources")) { 381 return; 382 } 383 startRefreshingSafetySources(refreshReason, userId, safetySourceIds); 384 } 385 386 @Override 387 @Nullable getSafetyCenterConfig()388 public SafetyCenterConfig getSafetyCenterConfig() { 389 getContext() 390 .enforceCallingOrSelfPermission(MANAGE_SAFETY_CENTER, "getSafetyCenterConfig"); 391 // We still return the SafetyCenterConfig object when the API is disabled, as Settings 392 // search works by adding all the entries very rarely (and relies on filtering them out 393 // instead). 394 if (!canUseSafetyCenter()) { 395 Log.w(TAG, "Called getSafetyCenterConfig, but Safety Center is not supported"); 396 return null; 397 } 398 399 synchronized (mApiLock) { 400 return mSafetyCenterConfigReader.getSafetyCenterConfig(); 401 } 402 } 403 404 @Override getSafetyCenterData(String packageName, @UserIdInt int userId)405 public SafetyCenterData getSafetyCenterData(String packageName, @UserIdInt int userId) { 406 requireNonNull(packageName); 407 getContext() 408 .enforceCallingOrSelfPermission(MANAGE_SAFETY_CENTER, "getSafetyCenterData"); 409 if (!enforceCrossUserPermission("getSafetyCenterData", userId) 410 || !enforcePackage(Binder.getCallingUid(), packageName, userId) 411 || !checkApiEnabled("getSafetyCenterData")) { 412 return SafetyCenterDataFactory.getDefaultSafetyCenterData(); 413 } 414 415 UserProfileGroup userProfileGroup = UserProfileGroup.fromUser(getContext(), userId); 416 synchronized (mApiLock) { 417 return mSafetyCenterDataFactory.assembleSafetyCenterData( 418 packageName, userProfileGroup); 419 } 420 } 421 422 @Override addOnSafetyCenterDataChangedListener( IOnSafetyCenterDataChangedListener listener, String packageName, @UserIdInt int userId)423 public void addOnSafetyCenterDataChangedListener( 424 IOnSafetyCenterDataChangedListener listener, 425 String packageName, 426 @UserIdInt int userId) { 427 requireNonNull(listener); 428 requireNonNull(packageName); 429 getContext() 430 .enforceCallingOrSelfPermission( 431 MANAGE_SAFETY_CENTER, "addOnSafetyCenterDataChangedListener"); 432 if (!enforceCrossUserPermission("addOnSafetyCenterDataChangedListener", userId) 433 || !enforcePackage(Binder.getCallingUid(), packageName, userId) 434 || !checkApiEnabled("addOnSafetyCenterDataChangedListener")) { 435 return; 436 } 437 438 UserProfileGroup userProfileGroup = UserProfileGroup.fromUser(getContext(), userId); 439 synchronized (mApiLock) { 440 IOnSafetyCenterDataChangedListener registeredListener = 441 mSafetyCenterListeners.addListener(listener, packageName, userId); 442 if (registeredListener == null) { 443 return; 444 } 445 SafetyCenterListeners.deliverDataForListener( 446 registeredListener, 447 mSafetyCenterDataFactory.assembleSafetyCenterData( 448 packageName, userProfileGroup)); 449 } 450 } 451 452 @Override removeOnSafetyCenterDataChangedListener( IOnSafetyCenterDataChangedListener listener, @UserIdInt int userId)453 public void removeOnSafetyCenterDataChangedListener( 454 IOnSafetyCenterDataChangedListener listener, @UserIdInt int userId) { 455 requireNonNull(listener); 456 getContext() 457 .enforceCallingOrSelfPermission( 458 MANAGE_SAFETY_CENTER, "removeOnSafetyCenterDataChangedListener"); 459 if (!enforceCrossUserPermission("removeOnSafetyCenterDataChangedListener", userId) 460 || !checkApiEnabled("removeOnSafetyCenterDataChangedListener")) { 461 return; 462 } 463 464 synchronized (mApiLock) { 465 mSafetyCenterListeners.removeListener(listener, userId); 466 } 467 } 468 469 @Override dismissSafetyCenterIssue(String issueId, @UserIdInt int userId)470 public void dismissSafetyCenterIssue(String issueId, @UserIdInt int userId) { 471 requireNonNull(issueId); 472 getContext() 473 .enforceCallingOrSelfPermission( 474 MANAGE_SAFETY_CENTER, "dismissSafetyCenterIssue"); 475 if (!enforceCrossUserPermission("dismissSafetyCenterIssue", userId) 476 || !checkApiEnabled("dismissSafetyCenterIssue")) { 477 return; 478 } 479 480 SafetyCenterIssueId safetyCenterIssueId = SafetyCenterIds.issueIdFromString(issueId); 481 SafetyCenterIssueKey safetyCenterIssueKey = 482 safetyCenterIssueId.getSafetyCenterIssueKey(); 483 UserProfileGroup userProfileGroup = UserProfileGroup.fromUser(getContext(), userId); 484 enforceSameUserProfileGroup( 485 "dismissSafetyCenterIssue", userProfileGroup, safetyCenterIssueKey.getUserId()); 486 synchronized (mApiLock) { 487 SafetySourceIssue safetySourceIssue = 488 mSafetyCenterDataManager.getSafetySourceIssue(safetyCenterIssueKey); 489 if (safetySourceIssue == null) { 490 Log.w(TAG, "Attempt to dismiss an issue that is not provided by the source"); 491 // Don't send the error to the UI here, since it could happen when clicking the 492 // button multiple times in a row (e.g. if the source is clearing the issue as a 493 // result of the onDismissPendingIntent). 494 return; 495 } 496 if (mSafetyCenterDataManager.isIssueDismissed( 497 safetyCenterIssueKey, safetySourceIssue.getSeverityLevel())) { 498 Log.w(TAG, "Attempt to dismiss an issue that is already dismissed"); 499 // Don't send the error to the UI here, since it could happen when clicking the 500 // button multiple times in a row. 501 return; 502 } 503 mSafetyCenterDataManager.dismissSafetyCenterIssue(safetyCenterIssueKey); 504 PendingIntent onDismissPendingIntent = 505 safetySourceIssue.getOnDismissPendingIntent(); 506 if (onDismissPendingIntent != null 507 && !dispatchPendingIntent(onDismissPendingIntent, null)) { 508 Log.w( 509 TAG, 510 "Error dispatching dismissal for issue: " 511 + safetyCenterIssueKey.getSafetySourceIssueId() 512 + ", of source: " 513 + safetyCenterIssueKey.getSafetySourceId()); 514 // We still consider the dismissal a success if there is an error dispatching 515 // the dismissal PendingIntent, since SafetyCenter won't surface this warning 516 // anymore. 517 } 518 mSafetyCenterDataChangeNotifier.updateDataConsumers(userProfileGroup, userId); 519 } 520 } 521 522 @Override executeSafetyCenterIssueAction( String issueId, String issueActionId, @UserIdInt int userId)523 public void executeSafetyCenterIssueAction( 524 String issueId, String issueActionId, @UserIdInt int userId) { 525 requireNonNull(issueId); 526 requireNonNull(issueActionId); 527 getContext() 528 .enforceCallingOrSelfPermission( 529 MANAGE_SAFETY_CENTER, "executeSafetyCenterIssueAction"); 530 if (!enforceCrossUserPermission("executeSafetyCenterIssueAction", userId) 531 || !checkApiEnabled("executeSafetyCenterIssueAction")) { 532 return; 533 } 534 535 SafetyCenterIssueId safetyCenterIssueId = SafetyCenterIds.issueIdFromString(issueId); 536 SafetyCenterIssueKey safetyCenterIssueKey = 537 safetyCenterIssueId.getSafetyCenterIssueKey(); 538 SafetyCenterIssueActionId safetyCenterIssueActionId = 539 SafetyCenterIds.issueActionIdFromString(issueActionId); 540 if (!safetyCenterIssueActionId.getSafetyCenterIssueKey().equals(safetyCenterIssueKey)) { 541 throw new IllegalArgumentException( 542 toUserFriendlyString(safetyCenterIssueId) 543 + " and " 544 + toUserFriendlyString(safetyCenterIssueActionId) 545 + " do not match"); 546 } 547 UserProfileGroup userProfileGroup = UserProfileGroup.fromUser(getContext(), userId); 548 enforceSameUserProfileGroup( 549 "executeSafetyCenterIssueAction", 550 userProfileGroup, 551 safetyCenterIssueKey.getUserId()); 552 Integer taskId = 553 safetyCenterIssueId.hasTaskId() ? safetyCenterIssueId.getTaskId() : null; 554 executeIssueActionInternal(safetyCenterIssueActionId, userProfileGroup, taskId); 555 } 556 557 @Override clearAllSafetySourceDataForTests()558 public void clearAllSafetySourceDataForTests() { 559 getContext() 560 .enforceCallingOrSelfPermission( 561 MANAGE_SAFETY_CENTER, "clearAllSafetySourceDataForTests"); 562 if (!checkApiEnabled("clearAllSafetySourceDataForTests")) { 563 return; 564 } 565 566 List<UserProfileGroup> userProfileGroups = 567 UserProfileGroup.getAllUserProfileGroups(getContext()); 568 synchronized (mApiLock) { 569 // TODO(b/236693607): Should tests leave real data untouched? 570 clearDataLocked(); 571 mSafetyCenterDataChangeNotifier.updateDataConsumers(userProfileGroups); 572 } 573 } 574 575 @Override setSafetyCenterConfigForTests(SafetyCenterConfig safetyCenterConfig)576 public void setSafetyCenterConfigForTests(SafetyCenterConfig safetyCenterConfig) { 577 requireNonNull(safetyCenterConfig); 578 getContext() 579 .enforceCallingOrSelfPermission( 580 MANAGE_SAFETY_CENTER, "setSafetyCenterConfigForTests"); 581 if (!checkApiEnabled("setSafetyCenterConfigForTests")) { 582 return; 583 } 584 585 List<UserProfileGroup> userProfileGroups = 586 UserProfileGroup.getAllUserProfileGroups(getContext()); 587 synchronized (mApiLock) { 588 mSafetyCenterConfigReader.setConfigOverrideForTests(safetyCenterConfig); 589 // TODO(b/236693607): Should tests leave real data untouched? 590 clearDataLocked(); 591 mSafetyCenterDataChangeNotifier.updateDataConsumers(userProfileGroups); 592 } 593 } 594 595 @Override clearSafetyCenterConfigForTests()596 public void clearSafetyCenterConfigForTests() { 597 getContext() 598 .enforceCallingOrSelfPermission( 599 MANAGE_SAFETY_CENTER, "clearSafetyCenterConfigForTests"); 600 if (!checkApiEnabled("clearSafetyCenterConfigForTests")) { 601 return; 602 } 603 604 List<UserProfileGroup> userProfileGroups = 605 UserProfileGroup.getAllUserProfileGroups(getContext()); 606 synchronized (mApiLock) { 607 mSafetyCenterConfigReader.clearConfigOverrideForTests(); 608 // TODO(b/236693607): Should tests leave real data untouched? 609 clearDataLocked(); 610 mSafetyCenterDataChangeNotifier.updateDataConsumers(userProfileGroups); 611 } 612 } 613 isApiEnabled()614 private boolean isApiEnabled() { 615 return canUseSafetyCenter() && SafetyCenterFlags.getSafetyCenterEnabled(); 616 } 617 enforceAnyCallingOrSelfPermissions(String message, String... permissions)618 private void enforceAnyCallingOrSelfPermissions(String message, String... permissions) { 619 if (permissions.length == 0) { 620 throw new IllegalArgumentException("Must check at least one permission"); 621 } 622 for (int i = 0; i < permissions.length; i++) { 623 if (getContext().checkCallingOrSelfPermission(permissions[i]) 624 == PERMISSION_GRANTED) { 625 return; 626 } 627 } 628 throw new SecurityException( 629 message 630 + " requires any of: " 631 + Arrays.toString(permissions) 632 + ", but none were granted"); 633 } 634 635 /** Enforces cross user permission and returns whether the user is valid. */ enforceCrossUserPermission(String message, @UserIdInt int userId)636 private boolean enforceCrossUserPermission(String message, @UserIdInt int userId) { 637 UserUtils.enforceCrossUserPermission(userId, false, message, getContext()); 638 if (!UserUtils.isUserExistent(userId, getContext())) { 639 Log.w( 640 TAG, 641 "Called " 642 + message 643 + " with user id " 644 + userId 645 + ", which does not correspond to an existing user"); 646 return false; 647 } 648 if (!UserProfileGroup.isSupported(userId, getContext())) { 649 Log.w( 650 TAG, 651 "Called " 652 + message 653 + " with user id " 654 + userId 655 + ", which is an unsupported user"); 656 return false; 657 } 658 return true; 659 } 660 661 /** 662 * Returns {@code true} if the {@code packageName} exists and it belongs to the {@code 663 * callingUid}. 664 * 665 * <p>Throws a {@link SecurityException} if the {@code packageName} does not belong to the 666 * {@code callingUid}. 667 */ enforcePackage(int callingUid, String packageName, @UserIdInt int userId)668 private boolean enforcePackage(int callingUid, String packageName, @UserIdInt int userId) { 669 if (TextUtils.isEmpty(packageName)) { 670 throw new IllegalArgumentException("packageName may not be empty"); 671 } 672 int actualUid; 673 PackageManager packageManager = getContext().getPackageManager(); 674 try { 675 actualUid = 676 packageManager.getPackageUidAsUser( 677 packageName, PackageInfoFlags.of(0), userId); 678 } catch (NameNotFoundException e) { 679 Log.e(TAG, "packageName=" + packageName + ", not found for userId=" + userId, e); 680 return false; 681 } 682 if (callingUid == Process.ROOT_UID || callingUid == Process.SYSTEM_UID) { 683 return true; 684 } 685 if (UserHandle.getAppId(callingUid) != UserHandle.getAppId(actualUid)) { 686 throw new SecurityException( 687 "packageName=" 688 + packageName 689 + ", does not belong to callingUid=" 690 + callingUid); 691 } 692 return true; 693 } 694 checkApiEnabled(String message)695 private boolean checkApiEnabled(String message) { 696 if (!isApiEnabled()) { 697 Log.w(TAG, "Called " + message + ", but Safety Center is disabled"); 698 return false; 699 } 700 return true; 701 } 702 enforceSameUserProfileGroup( String message, UserProfileGroup userProfileGroup, @UserIdInt int userId)703 private void enforceSameUserProfileGroup( 704 String message, UserProfileGroup userProfileGroup, @UserIdInt int userId) { 705 if (!userProfileGroup.contains(userId)) { 706 throw new SecurityException( 707 message 708 + " requires target user id " 709 + userId 710 + " to be within the same profile group of the caller: " 711 + userProfileGroup); 712 } 713 } 714 715 @Override handleShellCommand( ParcelFileDescriptor in, ParcelFileDescriptor out, ParcelFileDescriptor err, String[] args)716 public int handleShellCommand( 717 ParcelFileDescriptor in, 718 ParcelFileDescriptor out, 719 ParcelFileDescriptor err, 720 String[] args) { 721 return new SafetyCenterShellCommandHandler( 722 getContext(), this, mDeviceSupportsSafetyCenter) 723 .exec( 724 this, 725 in.getFileDescriptor(), 726 out.getFileDescriptor(), 727 err.getFileDescriptor(), 728 args); 729 } 730 731 /** Dumps state for debugging purposes. */ 732 @Override dump(FileDescriptor fd, PrintWriter fout, @Nullable String[] args)733 protected void dump(FileDescriptor fd, PrintWriter fout, @Nullable String[] args) { 734 if (!checkDumpPermission(fout)) { 735 return; 736 } 737 List<String> subjects = Arrays.asList(args); 738 boolean all = subjects.isEmpty(); 739 synchronized (mApiLock) { 740 if (all || subjects.contains("service")) { 741 SafetyCenterService.this.dumpLocked(fout); 742 } 743 if (all || subjects.contains("flags")) { 744 SafetyCenterFlags.dump(fout); 745 } 746 if (all || subjects.contains("config")) { 747 mSafetyCenterConfigReader.dump(fout); 748 } 749 if (all || subjects.contains("data")) { 750 mSafetyCenterDataManager.dump(fd, fout); 751 } 752 if (all || subjects.contains("refresh")) { 753 mSafetyCenterRefreshTracker.dump(fout); 754 } 755 if (all || subjects.contains("timeouts")) { 756 mSafetyCenterTimeouts.dump(fout); 757 } 758 if (all || subjects.contains("listeners")) { 759 mSafetyCenterListeners.dump(fout); 760 } 761 if (all || subjects.contains("notifications")) { 762 mNotificationSender.dump(fout); 763 } 764 } 765 } 766 checkDumpPermission(PrintWriter writer)767 private boolean checkDumpPermission(PrintWriter writer) { 768 if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP) 769 != PERMISSION_GRANTED) { 770 writer.println( 771 "Permission Denial: can't dump " 772 + "safety_center" 773 + " from from pid=" 774 + Binder.getCallingPid() 775 + ", uid=" 776 + Binder.getCallingUid() 777 + " due to missing " 778 + android.Manifest.permission.DUMP 779 + " permission"); 780 return false; 781 } else { 782 return true; 783 } 784 } 785 } 786 787 /** 788 * An {@link OnPropertiesChangedListener} for {@link 789 * SafetyCenterFlags#PROPERTY_SAFETY_CENTER_ENABLED} that sends broadcasts when the SafetyCenter 790 * property is enabled or disabled. 791 * 792 * <p>This listener assumes that the {@link SafetyCenterFlags#PROPERTY_SAFETY_CENTER_ENABLED} 793 * value maps to {@link SafetyCenterManager#isSafetyCenterEnabled()}. It should only be 794 * registered if the device supports SafetyCenter and the {@link SafetyCenterConfig} was loaded 795 * successfully. 796 * 797 * <p>This listener is not thread-safe; it should be called on a single thread. 798 */ 799 @NotThreadSafe 800 private final class SafetyCenterEnabledListener implements OnPropertiesChangedListener { 801 802 private boolean mSafetyCenterEnabled; 803 804 @Override onPropertiesChanged(DeviceConfig.Properties properties)805 public void onPropertiesChanged(DeviceConfig.Properties properties) { 806 if (!properties.getKeyset().contains(PROPERTY_SAFETY_CENTER_ENABLED)) { 807 return; 808 } 809 boolean safetyCenterEnabled = 810 properties.getBoolean(PROPERTY_SAFETY_CENTER_ENABLED, false); 811 if (mSafetyCenterEnabled == safetyCenterEnabled) { 812 return; 813 } 814 onSafetyCenterEnabledChanged(safetyCenterEnabled); 815 } 816 setInitialState()817 private void setInitialState() { 818 mSafetyCenterEnabled = SafetyCenterFlags.getSafetyCenterEnabled(); 819 Log.w(TAG, "SafetyCenter is " + (mSafetyCenterEnabled ? "enabled." : "disabled.")); 820 } 821 onSafetyCenterEnabledChanged(boolean safetyCenterEnabled)822 private void onSafetyCenterEnabledChanged(boolean safetyCenterEnabled) { 823 Log.w(TAG, "SafetyCenter is now " + (safetyCenterEnabled ? "enabled." : "disabled.")); 824 825 if (safetyCenterEnabled) { 826 onApiEnabled(); 827 } else { 828 onApiDisabled(); 829 } 830 mSafetyCenterEnabled = safetyCenterEnabled; 831 } 832 onApiEnabled()833 private void onApiEnabled() { 834 synchronized (mApiLock) { 835 mSafetyCenterBroadcastDispatcher.sendEnabledChanged(); 836 } 837 } 838 onApiDisabled()839 private void onApiDisabled() { 840 synchronized (mApiLock) { 841 clearDataLocked(); 842 mSafetyCenterListeners.clear(); 843 mSafetyCenterBroadcastDispatcher.sendEnabledChanged(); 844 } 845 } 846 } 847 848 /** A {@link Runnable} that is called to signal a refresh timeout. */ 849 private final class RefreshTimeout implements Runnable { 850 851 private final String mRefreshBroadcastId; 852 @RefreshReason private final int mRefreshReason; 853 private final UserProfileGroup mUserProfileGroup; 854 RefreshTimeout( String refreshBroadcastId, @RefreshReason int refreshReason, UserProfileGroup userProfileGroup)855 RefreshTimeout( 856 String refreshBroadcastId, 857 @RefreshReason int refreshReason, 858 UserProfileGroup userProfileGroup) { 859 mRefreshBroadcastId = refreshBroadcastId; 860 mRefreshReason = refreshReason; 861 mUserProfileGroup = userProfileGroup; 862 } 863 864 @Override run()865 public void run() { 866 synchronized (mApiLock) { 867 mSafetyCenterTimeouts.remove(this); 868 ArraySet<SafetySourceKey> stillInFlight = 869 mSafetyCenterRefreshTracker.timeoutRefresh(mRefreshBroadcastId); 870 if (stillInFlight == null) { 871 return; 872 } 873 boolean showErrorEntriesOnTimeout = 874 SafetyCenterFlags.getShowErrorEntriesOnTimeout(); 875 boolean setError = 876 showErrorEntriesOnTimeout 877 && !RefreshReasons.isBackgroundRefresh(mRefreshReason); 878 for (int i = 0; i < stillInFlight.size(); i++) { 879 mSafetyCenterDataManager.markSafetySourceRefreshTimedOut( 880 stillInFlight.valueAt(i), setError); 881 } 882 mSafetyCenterDataChangeNotifier.updateDataConsumers(mUserProfileGroup); 883 if (!showErrorEntriesOnTimeout) { 884 mSafetyCenterListeners.deliverErrorForUserProfileGroup( 885 mUserProfileGroup, 886 new SafetyCenterErrorDetails( 887 mSafetyCenterResourcesContext.getStringByName( 888 "refresh_timeout"))); 889 } 890 } 891 892 Log.v( 893 TAG, 894 "Cleared refresh with broadcastId:" + mRefreshBroadcastId + " after a timeout"); 895 } 896 897 @Override toString()898 public String toString() { 899 return "RefreshTimeout{" 900 + "mRefreshBroadcastId='" 901 + mRefreshBroadcastId 902 + '\'' 903 + ", mUserProfileGroup=" 904 + mUserProfileGroup 905 + '}'; 906 } 907 } 908 909 /** A {@link Runnable} that is called to signal a resolving action timeout. */ 910 private final class ResolvingActionTimeout implements Runnable { 911 912 private final SafetyCenterIssueActionId mSafetyCenterIssueActionId; 913 private final UserProfileGroup mUserProfileGroup; 914 ResolvingActionTimeout( SafetyCenterIssueActionId safetyCenterIssueActionId, UserProfileGroup userProfileGroup)915 ResolvingActionTimeout( 916 SafetyCenterIssueActionId safetyCenterIssueActionId, 917 UserProfileGroup userProfileGroup) { 918 mSafetyCenterIssueActionId = safetyCenterIssueActionId; 919 mUserProfileGroup = userProfileGroup; 920 } 921 922 @Override run()923 public void run() { 924 synchronized (mApiLock) { 925 mSafetyCenterTimeouts.remove(this); 926 SafetySourceIssue safetySourceIssue = 927 mSafetyCenterDataManager.getSafetySourceIssue( 928 mSafetyCenterIssueActionId.getSafetyCenterIssueKey()); 929 boolean safetyCenterDataHasChanged = 930 mSafetyCenterDataManager.unmarkSafetyCenterIssueActionInFlight( 931 mSafetyCenterIssueActionId, 932 safetySourceIssue, 933 SAFETY_CENTER_SYSTEM_EVENT_REPORTED__RESULT__TIMEOUT); 934 if (!safetyCenterDataHasChanged) { 935 return; 936 } 937 mSafetyCenterDataChangeNotifier.updateDataConsumers(mUserProfileGroup); 938 mSafetyCenterListeners.deliverErrorForUserProfileGroup( 939 mUserProfileGroup, 940 new SafetyCenterErrorDetails( 941 mSafetyCenterResourcesContext.getStringByName( 942 "resolving_action_error"))); 943 } 944 } 945 946 @Override toString()947 public String toString() { 948 return "ResolvingActionTimeout{" 949 + "mSafetyCenterIssueActionId=" 950 + toUserFriendlyString(mSafetyCenterIssueActionId) 951 + ", mUserProfileGroup=" 952 + mUserProfileGroup 953 + '}'; 954 } 955 } 956 canUseSafetyCenter()957 private boolean canUseSafetyCenter() { 958 return mDeviceSupportsSafetyCenter && mConfigAvailable; 959 } 960 961 /** {@link BroadcastReceiver} which handles Locale changes. */ 962 private final class LocaleBroadcastReceiver extends BroadcastReceiver { 963 964 private static final String TAG = "LocaleBroadcastReceiver"; 965 register(Context context)966 void register(Context context) { 967 IntentFilter filter = new IntentFilter(); 968 filter.addAction(Intent.ACTION_LOCALE_CHANGED); 969 context.registerReceiverForAllUsers(this, filter, null, null); 970 } 971 972 @Override onReceive(Context context, Intent intent)973 public void onReceive(Context context, Intent intent) { 974 Log.d(TAG, "Locale changed broadcast received"); 975 mNotificationChannels.createAllChannelsForAllUsers(getContext()); 976 } 977 } 978 979 /** 980 * {@link BroadcastReceiver} which handles user and work profile related broadcasts that Safety 981 * Center is interested including quiet mode turning on/off and accounts being added/removed. 982 */ 983 private final class UserBroadcastReceiver extends BroadcastReceiver { 984 985 private static final String TAG = "UserBroadcastReceiver"; 986 register(Context context)987 void register(Context context) { 988 IntentFilter filter = new IntentFilter(); 989 filter.addAction(Intent.ACTION_USER_ADDED); 990 filter.addAction(Intent.ACTION_USER_REMOVED); 991 filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED); 992 filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED); 993 filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE); 994 filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); 995 context.registerReceiverForAllUsers(this, filter, null, null); 996 } 997 998 @Override onReceive(Context context, Intent intent)999 public void onReceive(Context context, Intent intent) { 1000 String action = intent.getAction(); 1001 if (action == null) { 1002 Log.w(TAG, "Received broadcast with null action!"); 1003 return; 1004 } 1005 1006 UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER, UserHandle.class); 1007 if (userHandle == null) { 1008 Log.w(TAG, "Received " + action + " broadcast missing user extra!"); 1009 return; 1010 } 1011 1012 int userId = userHandle.getIdentifier(); 1013 if (!UserProfileGroup.isSupported(userId, context)) { 1014 Log.i( 1015 TAG, 1016 "Received broadcast for user id " 1017 + userId 1018 + ", which is an unsupported user"); 1019 return; 1020 } 1021 Log.d(TAG, "Received " + action + " broadcast for user " + userId); 1022 1023 switch (action) { 1024 case Intent.ACTION_USER_REMOVED: 1025 case Intent.ACTION_MANAGED_PROFILE_REMOVED: 1026 removeUser(userId, true); 1027 break; 1028 case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE: 1029 removeUser(userId, false); 1030 // fall through! 1031 case Intent.ACTION_USER_ADDED: 1032 case Intent.ACTION_MANAGED_PROFILE_ADDED: 1033 case Intent.ACTION_MANAGED_PROFILE_AVAILABLE: 1034 startRefreshingSafetySources(REFRESH_REASON_OTHER, userId); 1035 mNotificationChannels.createAllChannelsForUser(getContext(), userHandle); 1036 break; 1037 } 1038 } 1039 } 1040 removeUser(@serIdInt int userId, boolean clearDataPermanently)1041 private void removeUser(@UserIdInt int userId, boolean clearDataPermanently) { 1042 UserProfileGroup userProfileGroup = UserProfileGroup.fromUser(getContext(), userId); 1043 synchronized (mApiLock) { 1044 mSafetyCenterListeners.clearForUser(userId); 1045 mSafetyCenterRefreshTracker.clearRefreshForUser(userId); 1046 1047 if (clearDataPermanently) { 1048 mSafetyCenterDataManager.clearForUser(userId); 1049 mSafetyCenterDataChangeNotifier.updateDataConsumers(userProfileGroup, userId); 1050 } else { 1051 mSafetyCenterListeners.deliverDataForUserProfileGroup(userProfileGroup); 1052 } 1053 } 1054 } 1055 startRefreshingSafetySources( @efreshReason int refreshReason, @UserIdInt int userId)1056 private void startRefreshingSafetySources( 1057 @RefreshReason int refreshReason, @UserIdInt int userId) { 1058 startRefreshingSafetySources(refreshReason, userId, null); 1059 } 1060 startRefreshingSafetySources( @efreshReason int refreshReason, @UserIdInt int userId, @Nullable List<String> selectedSafetySourceIds)1061 private void startRefreshingSafetySources( 1062 @RefreshReason int refreshReason, 1063 @UserIdInt int userId, 1064 @Nullable List<String> selectedSafetySourceIds) { 1065 UserProfileGroup userProfileGroup = UserProfileGroup.fromUser(getContext(), userId); 1066 synchronized (mApiLock) { 1067 String refreshBroadcastId = 1068 mSafetyCenterBroadcastDispatcher.sendRefreshSafetySources( 1069 refreshReason, userProfileGroup, selectedSafetySourceIds); 1070 if (refreshBroadcastId == null) { 1071 return; 1072 } 1073 1074 RefreshTimeout refreshTimeout = 1075 new RefreshTimeout(refreshBroadcastId, refreshReason, userProfileGroup); 1076 mSafetyCenterTimeouts.add( 1077 refreshTimeout, SafetyCenterFlags.getRefreshSourcesTimeout(refreshReason)); 1078 1079 mSafetyCenterDataChangeNotifier.updateDataConsumers(userProfileGroup); 1080 } 1081 } 1082 1083 /** 1084 * Executes the {@link SafetySourceIssue.Action} specified by the given {@link 1085 * SafetyCenterIssueActionId}. 1086 * 1087 * <p>No validation is performed on the contents of the given ID. 1088 */ executeIssueActionInternal(SafetyCenterIssueActionId safetyCenterIssueActionId)1089 public void executeIssueActionInternal(SafetyCenterIssueActionId safetyCenterIssueActionId) { 1090 SafetyCenterIssueKey safetyCenterIssueKey = 1091 safetyCenterIssueActionId.getSafetyCenterIssueKey(); 1092 UserProfileGroup userProfileGroup = 1093 UserProfileGroup.fromUser(getContext(), safetyCenterIssueKey.getUserId()); 1094 executeIssueActionInternal(safetyCenterIssueActionId, userProfileGroup, null); 1095 } 1096 executeIssueActionInternal( SafetyCenterIssueActionId safetyCenterIssueActionId, UserProfileGroup userProfileGroup, @Nullable Integer taskId)1097 private void executeIssueActionInternal( 1098 SafetyCenterIssueActionId safetyCenterIssueActionId, 1099 UserProfileGroup userProfileGroup, 1100 @Nullable Integer taskId) { 1101 synchronized (mApiLock) { 1102 SafetySourceIssue.Action safetySourceIssueAction = 1103 mSafetyCenterDataManager.getSafetySourceIssueAction(safetyCenterIssueActionId); 1104 1105 if (safetySourceIssueAction == null) { 1106 Log.w( 1107 TAG, 1108 "Attempt to execute an issue action that is not provided by the source," 1109 + " that was dismissed, or is already in flight"); 1110 // Don't send the error to the UI here, since it could happen when clicking the 1111 // button multiple times in a row. 1112 return; 1113 } 1114 PendingIntent issueActionPendingIntent = safetySourceIssueAction.getPendingIntent(); 1115 if (!dispatchPendingIntent(issueActionPendingIntent, taskId)) { 1116 Log.w( 1117 TAG, 1118 "Error dispatching action: " 1119 + toUserFriendlyString(safetyCenterIssueActionId)); 1120 CharSequence errorMessage; 1121 if (safetySourceIssueAction.willResolve()) { 1122 errorMessage = 1123 mSafetyCenterResourcesContext.getStringByName("resolving_action_error"); 1124 } else { 1125 errorMessage = 1126 mSafetyCenterResourcesContext.getStringByName("redirecting_error"); 1127 } 1128 mSafetyCenterListeners.deliverErrorForUserProfileGroup( 1129 userProfileGroup, new SafetyCenterErrorDetails(errorMessage)); 1130 return; 1131 } 1132 if (safetySourceIssueAction.willResolve()) { 1133 mSafetyCenterDataManager.markSafetyCenterIssueActionInFlight( 1134 safetyCenterIssueActionId); 1135 ResolvingActionTimeout resolvingActionTimeout = 1136 new ResolvingActionTimeout(safetyCenterIssueActionId, userProfileGroup); 1137 mSafetyCenterTimeouts.add( 1138 resolvingActionTimeout, SafetyCenterFlags.getResolvingActionTimeout()); 1139 mSafetyCenterDataChangeNotifier.updateDataConsumers(userProfileGroup); 1140 } 1141 } 1142 } 1143 dispatchPendingIntent( PendingIntent pendingIntent, @Nullable Integer launchTaskId)1144 private boolean dispatchPendingIntent( 1145 PendingIntent pendingIntent, @Nullable Integer launchTaskId) { 1146 if (launchTaskId != null 1147 && getContext().checkCallingOrSelfPermission(START_TASKS_FROM_RECENTS) 1148 != PERMISSION_GRANTED) { 1149 launchTaskId = null; 1150 } 1151 return PendingIntentSender.trySend(pendingIntent, launchTaskId); 1152 } 1153 1154 @GuardedBy("mApiLock") clearDataLocked()1155 private void clearDataLocked() { 1156 mSafetyCenterDataManager.clear(); 1157 mSafetyCenterTimeouts.clear(); 1158 mSafetyCenterRefreshTracker.clearRefresh(); 1159 mNotificationSender.cancelAllNotifications(); 1160 } 1161 1162 /** Dumps state for debugging purposes. */ 1163 @GuardedBy("mApiLock") dumpLocked(PrintWriter fout)1164 private void dumpLocked(PrintWriter fout) { 1165 fout.println("SERVICE"); 1166 fout.println( 1167 "\tSafetyCenterService{" 1168 + "mDeviceSupportsSafetyCenter=" 1169 + mDeviceSupportsSafetyCenter 1170 + ", mConfigAvailable=" 1171 + mConfigAvailable 1172 + '}'); 1173 fout.println(); 1174 } 1175 } 1176