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