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