• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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