• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.os.Build.VERSION_CODES.TIRAMISU;
20 
21 import static com.android.safetycenter.internaldata.SafetyCenterBundles.ISSUES_TO_GROUPS_BUNDLE_KEY;
22 import static com.android.safetycenter.internaldata.SafetyCenterBundles.STATIC_ENTRIES_TO_IDS_BUNDLE_KEY;
23 
24 import static java.util.Collections.emptyList;
25 
26 import android.annotation.Nullable;
27 import android.annotation.UserIdInt;
28 import android.app.PendingIntent;
29 import android.content.Context;
30 import android.icu.text.ListFormatter;
31 import android.icu.text.MessageFormat;
32 import android.icu.util.ULocale;
33 import android.os.Bundle;
34 import android.safetycenter.SafetyCenterData;
35 import android.safetycenter.SafetyCenterEntry;
36 import android.safetycenter.SafetyCenterEntryGroup;
37 import android.safetycenter.SafetyCenterEntryOrGroup;
38 import android.safetycenter.SafetyCenterIssue;
39 import android.safetycenter.SafetyCenterIssue.Action.ConfirmationDialogDetails;
40 import android.safetycenter.SafetyCenterStaticEntry;
41 import android.safetycenter.SafetyCenterStaticEntryGroup;
42 import android.safetycenter.SafetyCenterStatus;
43 import android.safetycenter.SafetySourceData;
44 import android.safetycenter.SafetySourceIssue;
45 import android.safetycenter.SafetySourceStatus;
46 import android.safetycenter.config.SafetyCenterConfig;
47 import android.safetycenter.config.SafetySource;
48 import android.safetycenter.config.SafetySourcesGroup;
49 import android.text.TextUtils;
50 import android.util.ArrayMap;
51 import android.util.Log;
52 
53 import androidx.annotation.RequiresApi;
54 
55 import com.android.modules.utils.build.SdkLevel;
56 import com.android.permission.util.UserUtils;
57 import com.android.safetycenter.data.SafetyCenterDataManager;
58 import com.android.safetycenter.internaldata.SafetyCenterBundles;
59 import com.android.safetycenter.internaldata.SafetyCenterEntryId;
60 import com.android.safetycenter.internaldata.SafetyCenterIds;
61 import com.android.safetycenter.internaldata.SafetyCenterIssueActionId;
62 import com.android.safetycenter.internaldata.SafetyCenterIssueId;
63 import com.android.safetycenter.internaldata.SafetyCenterIssueKey;
64 import com.android.safetycenter.resources.SafetyCenterResourcesContext;
65 
66 import java.util.ArrayList;
67 import java.util.List;
68 import java.util.Locale;
69 import java.util.Set;
70 
71 import javax.annotation.concurrent.NotThreadSafe;
72 
73 /**
74  * Aggregates {@link SafetySourceData} to build {@link SafetyCenterData} instances which are shared
75  * with Safety Center listeners, including PermissionController.
76  *
77  * <p>This class isn't thread safe. Thread safety must be handled by the caller.
78  *
79  * @hide
80  */
81 @RequiresApi(TIRAMISU)
82 @NotThreadSafe
83 public final class SafetyCenterDataFactory {
84 
85     private static final String TAG = "SafetyCenterDataFactory";
86 
87     private static final String ANDROID_LOCK_SCREEN_SOURCES_GROUP_ID = "AndroidLockScreenSources";
88 
89     private final Context mContext;
90     private final SafetyCenterResourcesContext mSafetyCenterResourcesContext;
91     private final SafetyCenterConfigReader mSafetyCenterConfigReader;
92     private final SafetyCenterRefreshTracker mSafetyCenterRefreshTracker;
93     private final PendingIntentFactory mPendingIntentFactory;
94 
95     private final SafetyCenterDataManager mSafetyCenterDataManager;
96 
SafetyCenterDataFactory( Context context, SafetyCenterResourcesContext safetyCenterResourcesContext, SafetyCenterConfigReader safetyCenterConfigReader, SafetyCenterRefreshTracker safetyCenterRefreshTracker, PendingIntentFactory pendingIntentFactory, SafetyCenterDataManager safetyCenterDataManager)97     SafetyCenterDataFactory(
98             Context context,
99             SafetyCenterResourcesContext safetyCenterResourcesContext,
100             SafetyCenterConfigReader safetyCenterConfigReader,
101             SafetyCenterRefreshTracker safetyCenterRefreshTracker,
102             PendingIntentFactory pendingIntentFactory,
103             SafetyCenterDataManager safetyCenterDataManager) {
104         mContext = context;
105         mSafetyCenterResourcesContext = safetyCenterResourcesContext;
106         mSafetyCenterConfigReader = safetyCenterConfigReader;
107         mSafetyCenterRefreshTracker = safetyCenterRefreshTracker;
108         mPendingIntentFactory = pendingIntentFactory;
109         mSafetyCenterDataManager = safetyCenterDataManager;
110     }
111 
112     /**
113      * Returns a default {@link SafetyCenterData} object to be returned when the API is disabled.
114      */
getDefaultSafetyCenterData()115     static SafetyCenterData getDefaultSafetyCenterData() {
116         SafetyCenterStatus defaultSafetyCenterStatus =
117                 new SafetyCenterStatus.Builder("", "")
118                         .setSeverityLevel(SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN)
119                         .build();
120         if (SdkLevel.isAtLeastU()) {
121             return new SafetyCenterData.Builder(defaultSafetyCenterStatus).build();
122         } else {
123             return new SafetyCenterData(
124                     defaultSafetyCenterStatus, emptyList(), emptyList(), emptyList());
125         }
126     }
127 
128     /**
129      * Returns the current {@link SafetyCenterData} for the given {@code packageName} and {@link
130      * UserProfileGroup}, aggregated from all the {@link SafetySourceData} set so far.
131      *
132      * <p>If a {@link SafetySourceData} was not set, the default value from the {@link
133      * SafetyCenterConfig} is used.
134      */
assembleSafetyCenterData( String packageName, UserProfileGroup userProfileGroup)135     SafetyCenterData assembleSafetyCenterData(
136             String packageName, UserProfileGroup userProfileGroup) {
137         return assembleSafetyCenterData(packageName, userProfileGroup, getAllGroups());
138     }
139 
140     /**
141      * Returns the current {@link SafetyCenterData} for the given {@code packageName} and {@link
142      * UserProfileGroup}, aggregated from {@link SafetySourceData} set by the specified {@link
143      * SafetySourcesGroup}s.
144      *
145      * <p>If a {@link SafetySourceData} was not set, the default value from the {@link
146      * SafetyCenterConfig} is used.
147      */
assembleSafetyCenterData( String packageName, UserProfileGroup userProfileGroup, List<SafetySourcesGroup> safetySourcesGroups)148     public SafetyCenterData assembleSafetyCenterData(
149             String packageName,
150             UserProfileGroup userProfileGroup,
151             List<SafetySourcesGroup> safetySourcesGroups) {
152         List<SafetyCenterEntryOrGroup> safetyCenterEntryOrGroups = new ArrayList<>();
153         List<SafetyCenterStaticEntryGroup> safetyCenterStaticEntryGroups = new ArrayList<>();
154         SafetyCenterOverallState safetyCenterOverallState = new SafetyCenterOverallState();
155         Bundle staticEntriesToIds = new Bundle();
156 
157         for (int i = 0; i < safetySourcesGroups.size(); i++) {
158             SafetySourcesGroup safetySourcesGroup = safetySourcesGroups.get(i);
159 
160             int safetySourcesGroupType = safetySourcesGroup.getType();
161             switch (safetySourcesGroupType) {
162                 case SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_STATEFUL:
163                     addSafetyCenterEntryGroup(
164                             safetyCenterOverallState,
165                             safetyCenterEntryOrGroups,
166                             safetySourcesGroup,
167                             packageName,
168                             userProfileGroup);
169                     break;
170                 case SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_STATELESS:
171                     addSafetyCenterStaticEntryGroup(
172                             staticEntriesToIds,
173                             safetyCenterOverallState,
174                             safetyCenterStaticEntryGroups,
175                             safetySourcesGroup,
176                             packageName,
177                             userProfileGroup);
178                     break;
179                 case SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_HIDDEN:
180                     break;
181                 default:
182                     Log.w(TAG, "Unexpected SafetySourceGroupType: " + safetySourcesGroupType);
183                     break;
184             }
185         }
186 
187         List<SafetySourceIssueInfo> issuesInfo =
188                 mSafetyCenterDataManager.getIssuesDedupedSortedDescFor(userProfileGroup);
189 
190         List<SafetyCenterIssue> safetyCenterIssues = new ArrayList<>();
191         List<SafetyCenterIssue> safetyCenterDismissedIssues = new ArrayList<>();
192         SafetySourceIssueInfo topNonDismissedIssueInfo = null;
193         int numTipIssues = 0;
194         int numAutomaticIssues = 0;
195         Bundle issuesToGroups = new Bundle();
196 
197         for (int i = 0; i < issuesInfo.size(); i++) {
198             SafetySourceIssueInfo issueInfo = issuesInfo.get(i);
199             SafetyCenterIssue safetyCenterIssue =
200                     toSafetyCenterIssue(
201                             issueInfo.getSafetySourceIssue(),
202                             issueInfo.getSafetySourcesGroup(),
203                             issueInfo.getSafetyCenterIssueKey());
204 
205             if (mSafetyCenterDataManager.isIssueDismissed(
206                     issueInfo.getSafetyCenterIssueKey(),
207                     issueInfo.getSafetySourceIssue().getSeverityLevel())) {
208                 safetyCenterDismissedIssues.add(safetyCenterIssue);
209             } else {
210                 safetyCenterIssues.add(safetyCenterIssue);
211                 safetyCenterOverallState.addIssueOverallSeverityLevel(
212                         toSafetyCenterStatusOverallSeverityLevel(
213                                 issueInfo.getSafetySourceIssue().getSeverityLevel()));
214                 if (topNonDismissedIssueInfo == null) {
215                     topNonDismissedIssueInfo = issueInfo;
216                 }
217                 if (isTip(issueInfo.getSafetySourceIssue())) {
218                     numTipIssues++;
219                 } else if (isAutomatic(issueInfo.getSafetySourceIssue())) {
220                     numAutomaticIssues++;
221                 }
222             }
223 
224             if (SdkLevel.isAtLeastU()) {
225                 updateIssuesToGroups(
226                         issuesToGroups,
227                         issueInfo.getSafetyCenterIssueKey(),
228                         safetyCenterIssue.getId());
229             }
230         }
231 
232         int refreshStatus = mSafetyCenterRefreshTracker.getRefreshStatus();
233         SafetyCenterStatus safetyCenterStatus =
234                 new SafetyCenterStatus.Builder(
235                                 getSafetyCenterStatusTitle(
236                                         safetyCenterOverallState.getOverallSeverityLevel(),
237                                         topNonDismissedIssueInfo,
238                                         refreshStatus,
239                                         safetyCenterOverallState.hasSettingsToReview()),
240                                 getSafetyCenterStatusSummary(
241                                         safetyCenterOverallState,
242                                         topNonDismissedIssueInfo,
243                                         refreshStatus,
244                                         numTipIssues,
245                                         numAutomaticIssues,
246                                         safetyCenterIssues.size()))
247                         .setSeverityLevel(safetyCenterOverallState.getOverallSeverityLevel())
248                         .setRefreshStatus(refreshStatus)
249                         .build();
250 
251         if (SdkLevel.isAtLeastU()) {
252             SafetyCenterData.Builder builder = new SafetyCenterData.Builder(safetyCenterStatus);
253             for (int i = 0; i < safetyCenterIssues.size(); i++) {
254                 builder.addIssue(safetyCenterIssues.get(i));
255             }
256             for (int i = 0; i < safetyCenterEntryOrGroups.size(); i++) {
257                 builder.addEntryOrGroup(safetyCenterEntryOrGroups.get(i));
258             }
259             for (int i = 0; i < safetyCenterStaticEntryGroups.size(); i++) {
260                 builder.addStaticEntryGroup(safetyCenterStaticEntryGroups.get(i));
261             }
262             for (int i = 0; i < safetyCenterDismissedIssues.size(); i++) {
263                 builder.addDismissedIssue(safetyCenterDismissedIssues.get(i));
264             }
265 
266             Bundle extras = new Bundle();
267             if (!issuesToGroups.isEmpty()) {
268                 extras.putBundle(ISSUES_TO_GROUPS_BUNDLE_KEY, issuesToGroups);
269             }
270             if (!staticEntriesToIds.isEmpty()) {
271                 extras.putBundle(STATIC_ENTRIES_TO_IDS_BUNDLE_KEY, staticEntriesToIds);
272             }
273             if (!issuesToGroups.isEmpty() || !staticEntriesToIds.isEmpty()) {
274                 builder.setExtras(extras);
275             }
276 
277             return builder.build();
278         } else {
279             return new SafetyCenterData(
280                     safetyCenterStatus,
281                     safetyCenterIssues,
282                     safetyCenterEntryOrGroups,
283                     safetyCenterStaticEntryGroups);
284         }
285     }
286 
getAllGroups()287     private List<SafetySourcesGroup> getAllGroups() {
288         return mSafetyCenterConfigReader.getSafetySourcesGroups();
289     }
290 
updateIssuesToGroups( Bundle issuesToGroups, SafetyCenterIssueKey issueKey, String safetyCenterIssueId)291     private void updateIssuesToGroups(
292             Bundle issuesToGroups, SafetyCenterIssueKey issueKey, String safetyCenterIssueId) {
293         Set<String> groups = mSafetyCenterDataManager.getGroupMappingFor(issueKey);
294         if (!groups.isEmpty()) {
295             issuesToGroups.putStringArrayList(safetyCenterIssueId, new ArrayList<>(groups));
296         }
297     }
298 
toSafetyCenterIssue( SafetySourceIssue safetySourceIssue, SafetySourcesGroup safetySourcesGroup, SafetyCenterIssueKey safetyCenterIssueKey)299     private SafetyCenterIssue toSafetyCenterIssue(
300             SafetySourceIssue safetySourceIssue,
301             SafetySourcesGroup safetySourcesGroup,
302             SafetyCenterIssueKey safetyCenterIssueKey) {
303         SafetyCenterIssueId safetyCenterIssueId =
304                 SafetyCenterIssueId.newBuilder()
305                         .setSafetyCenterIssueKey(safetyCenterIssueKey)
306                         .setIssueTypeId(safetySourceIssue.getIssueTypeId())
307                         .build();
308 
309         List<SafetySourceIssue.Action> safetySourceIssueActions = safetySourceIssue.getActions();
310         List<SafetyCenterIssue.Action> safetyCenterIssueActions =
311                 new ArrayList<>(safetySourceIssueActions.size());
312         for (int i = 0; i < safetySourceIssueActions.size(); i++) {
313             SafetySourceIssue.Action safetySourceIssueAction = safetySourceIssueActions.get(i);
314 
315             safetyCenterIssueActions.add(
316                     toSafetyCenterIssueAction(
317                             safetySourceIssueAction,
318                             safetyCenterIssueId.getSafetyCenterIssueKey()));
319         }
320 
321         int safetyCenterIssueSeverityLevel =
322                 toSafetyCenterIssueSeverityLevel(safetySourceIssue.getSeverityLevel());
323         SafetyCenterIssue.Builder safetyCenterIssueBuilder =
324                 new SafetyCenterIssue.Builder(
325                                 SafetyCenterIds.encodeToString(safetyCenterIssueId),
326                                 safetySourceIssue.getTitle(),
327                                 safetySourceIssue.getSummary())
328                         .setSeverityLevel(safetyCenterIssueSeverityLevel)
329                         .setShouldConfirmDismissal(
330                                 safetyCenterIssueSeverityLevel
331                                         > SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_OK)
332                         .setSubtitle(safetySourceIssue.getSubtitle())
333                         .setActions(safetyCenterIssueActions);
334         if (SdkLevel.isAtLeastU()) {
335             CharSequence issueAttributionTitle =
336                     TextUtils.isEmpty(safetySourceIssue.getAttributionTitle())
337                             ? mSafetyCenterResourcesContext.getOptionalString(
338                                     safetySourcesGroup.getTitleResId())
339                             : safetySourceIssue.getAttributionTitle();
340             safetyCenterIssueBuilder.setAttributionTitle(issueAttributionTitle);
341             safetyCenterIssueBuilder.setGroupId(safetySourcesGroup.getId());
342         }
343         return safetyCenterIssueBuilder.build();
344     }
345 
toSafetyCenterIssueAction( SafetySourceIssue.Action safetySourceIssueAction, SafetyCenterIssueKey safetyCenterIssueKey)346     private SafetyCenterIssue.Action toSafetyCenterIssueAction(
347             SafetySourceIssue.Action safetySourceIssueAction,
348             SafetyCenterIssueKey safetyCenterIssueKey) {
349         SafetyCenterIssueActionId safetyCenterIssueActionId =
350                 SafetyCenterIssueActionId.newBuilder()
351                         .setSafetyCenterIssueKey(safetyCenterIssueKey)
352                         .setSafetySourceIssueActionId(safetySourceIssueAction.getId())
353                         .build();
354 
355         SafetyCenterIssue.Action.Builder builder =
356                 new SafetyCenterIssue.Action.Builder(
357                                 SafetyCenterIds.encodeToString(safetyCenterIssueActionId),
358                                 safetySourceIssueAction.getLabel(),
359                                 safetySourceIssueAction.getPendingIntent())
360                         .setSuccessMessage(safetySourceIssueAction.getSuccessMessage())
361                         .setIsInFlight(
362                                 mSafetyCenterDataManager.actionIsInFlight(
363                                         safetyCenterIssueActionId))
364                         .setWillResolve(safetySourceIssueAction.willResolve());
365         if (SdkLevel.isAtLeastU()) {
366             if (safetySourceIssueAction.getConfirmationDialogDetails() != null) {
367                 SafetySourceIssue.Action.ConfirmationDialogDetails detailsFrom =
368                         safetySourceIssueAction.getConfirmationDialogDetails();
369                 ConfirmationDialogDetails detailsTo =
370                         new ConfirmationDialogDetails(
371                                 detailsFrom.getTitle(),
372                                 detailsFrom.getText(),
373                                 detailsFrom.getAcceptButtonText(),
374                                 detailsFrom.getDenyButtonText());
375                 builder.setConfirmationDialogDetails(detailsTo);
376             }
377         }
378         return builder.build();
379     }
380 
addSafetyCenterEntryGroup( SafetyCenterOverallState safetyCenterOverallState, List<SafetyCenterEntryOrGroup> safetyCenterEntryOrGroups, SafetySourcesGroup safetySourcesGroup, String defaultPackageName, UserProfileGroup userProfileGroup)381     private void addSafetyCenterEntryGroup(
382             SafetyCenterOverallState safetyCenterOverallState,
383             List<SafetyCenterEntryOrGroup> safetyCenterEntryOrGroups,
384             SafetySourcesGroup safetySourcesGroup,
385             String defaultPackageName,
386             UserProfileGroup userProfileGroup) {
387         int groupSafetyCenterEntryLevel = SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED;
388 
389         List<SafetySource> safetySources = safetySourcesGroup.getSafetySources();
390         List<SafetyCenterEntry> entries = new ArrayList<>(safetySources.size());
391         for (int i = 0; i < safetySources.size(); i++) {
392             SafetySource safetySource = safetySources.get(i);
393 
394             groupSafetyCenterEntryLevel =
395                     mergeSafetyCenterEntrySeverityLevels(
396                             groupSafetyCenterEntryLevel,
397                             addSafetyCenterEntry(
398                                     safetyCenterOverallState,
399                                     entries,
400                                     safetySource,
401                                     defaultPackageName,
402                                     userProfileGroup.getProfileParentUserId(),
403                                     false,
404                                     false));
405 
406             if (!SafetySources.supportsManagedProfiles(safetySource)) {
407                 continue;
408             }
409 
410             int[] managedProfilesUserIds = userProfileGroup.getManagedProfilesUserIds();
411             for (int j = 0; j < managedProfilesUserIds.length; j++) {
412                 int managedProfileUserId = managedProfilesUserIds[j];
413                 boolean isManagedUserRunning =
414                         userProfileGroup.isManagedUserRunning(managedProfileUserId);
415 
416                 groupSafetyCenterEntryLevel =
417                         mergeSafetyCenterEntrySeverityLevels(
418                                 groupSafetyCenterEntryLevel,
419                                 addSafetyCenterEntry(
420                                         safetyCenterOverallState,
421                                         entries,
422                                         safetySource,
423                                         defaultPackageName,
424                                         managedProfileUserId,
425                                         true,
426                                         isManagedUserRunning));
427             }
428         }
429 
430         if (entries.size() == 0) {
431             return;
432         }
433 
434         if (!SafetyCenterFlags.getShowSubpages() && entries.size() == 1) {
435             safetyCenterEntryOrGroups.add(new SafetyCenterEntryOrGroup(entries.get(0)));
436             return;
437         }
438 
439         CharSequence groupSummary =
440                 getSafetyCenterEntryGroupSummary(
441                         safetySourcesGroup, groupSafetyCenterEntryLevel, entries);
442         safetyCenterEntryOrGroups.add(
443                 new SafetyCenterEntryOrGroup(
444                         new SafetyCenterEntryGroup.Builder(
445                                         safetySourcesGroup.getId(),
446                                         mSafetyCenterResourcesContext.getString(
447                                                 safetySourcesGroup.getTitleResId()))
448                                 .setSeverityLevel(groupSafetyCenterEntryLevel)
449                                 .setSummary(groupSummary)
450                                 .setEntries(entries)
451                                 .setSeverityUnspecifiedIconType(
452                                         toGroupSeverityUnspecifiedIconType(
453                                                 safetySourcesGroup.getStatelessIconType()))
454                                 .build()));
455     }
456 
457     @SafetyCenterEntry.EntrySeverityLevel
mergeSafetyCenterEntrySeverityLevels( @afetyCenterEntry.EntrySeverityLevel int left, @SafetyCenterEntry.EntrySeverityLevel int right)458     private static int mergeSafetyCenterEntrySeverityLevels(
459             @SafetyCenterEntry.EntrySeverityLevel int left,
460             @SafetyCenterEntry.EntrySeverityLevel int right) {
461         if (left > SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK
462                 || right > SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK) {
463             return Math.max(left, right);
464         }
465         if (left == SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN
466                 || right == SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN) {
467             return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN;
468         }
469         return Math.max(left, right);
470     }
471 
472     @Nullable
getSafetyCenterEntryGroupSummary( SafetySourcesGroup safetySourcesGroup, @SafetyCenterEntry.EntrySeverityLevel int groupSafetyCenterEntryLevel, List<SafetyCenterEntry> entries)473     private CharSequence getSafetyCenterEntryGroupSummary(
474             SafetySourcesGroup safetySourcesGroup,
475             @SafetyCenterEntry.EntrySeverityLevel int groupSafetyCenterEntryLevel,
476             List<SafetyCenterEntry> entries) {
477         switch (groupSafetyCenterEntryLevel) {
478             case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_CRITICAL_WARNING:
479             case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION:
480             case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK:
481                 for (int i = 0; i < entries.size(); i++) {
482                     SafetyCenterEntry entry = entries.get(i);
483 
484                     CharSequence entrySummary = entry.getSummary();
485                     if (entry.getSeverityLevel() != groupSafetyCenterEntryLevel
486                             || entrySummary == null) {
487                         continue;
488                     }
489 
490                     if (groupSafetyCenterEntryLevel > SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK) {
491                         return entrySummary;
492                     }
493 
494                     SafetySourceKey key = toSafetySourceKey(entry.getId());
495                     SafetySourceData safetySourceData =
496                             mSafetyCenterDataManager.getSafetySourceDataInternal(key);
497                     boolean hasIssues =
498                             safetySourceData != null && !safetySourceData.getIssues().isEmpty();
499 
500                     if (hasIssues) {
501                         return entrySummary;
502                     }
503                 }
504 
505                 return getDefaultGroupSummary(safetySourcesGroup, entries);
506             case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED:
507                 return getDefaultGroupSummary(safetySourcesGroup, entries);
508             case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN:
509                 for (int i = 0; i < entries.size(); i++) {
510                     SafetySourceKey key = toSafetySourceKey(entries.get(i).getId());
511                     if (mSafetyCenterDataManager.sourceHasError(key)) {
512                         // We always use the singular form of the error string for groups because
513                         // they appear as single entries in the UI and this ensures consistency,
514                         // especially when subpages are enabled.
515                         return getRefreshErrorString(1);
516                     }
517                 }
518                 return mSafetyCenterResourcesContext.getStringByName("group_unknown_summary");
519         }
520 
521         Log.w(
522                 TAG,
523                 "Unexpected SafetyCenterEntry.EntrySeverityLevel for SafetyCenterEntryGroup: "
524                         + groupSafetyCenterEntryLevel);
525         return getDefaultGroupSummary(safetySourcesGroup, entries);
526     }
527 
528     @Nullable
getDefaultGroupSummary( SafetySourcesGroup safetySourcesGroup, List<SafetyCenterEntry> entries)529     private CharSequence getDefaultGroupSummary(
530             SafetySourcesGroup safetySourcesGroup, List<SafetyCenterEntry> entries) {
531         CharSequence groupSummary =
532                 mSafetyCenterResourcesContext.getOptionalString(
533                         safetySourcesGroup.getSummaryResId());
534 
535         if (safetySourcesGroup.getId().equals(ANDROID_LOCK_SCREEN_SOURCES_GROUP_ID)
536                 && TextUtils.isEmpty(groupSummary)) {
537             List<CharSequence> titles = new ArrayList<>();
538             for (int i = 0; i < entries.size(); i++) {
539                 SafetyCenterEntry entry = entries.get(i);
540                 SafetyCenterEntryId entryId = SafetyCenterIds.entryIdFromString(entry.getId());
541 
542                 if (UserUtils.isManagedProfile(entryId.getUserId(), mContext)) {
543                     continue;
544                 }
545 
546                 titles.add(entry.getTitle());
547             }
548             groupSummary =
549                     ListFormatter.getInstance(
550                                     ULocale.getDefault(ULocale.Category.FORMAT),
551                                     ListFormatter.Type.AND,
552                                     ListFormatter.Width.NARROW)
553                             .format(titles);
554         }
555 
556         return groupSummary;
557     }
558 
559     @SafetyCenterEntry.EntrySeverityLevel
addSafetyCenterEntry( SafetyCenterOverallState safetyCenterOverallState, List<SafetyCenterEntry> entries, SafetySource safetySource, String defaultPackageName, @UserIdInt int userId, boolean isUserManaged, boolean isManagedUserRunning)560     private int addSafetyCenterEntry(
561             SafetyCenterOverallState safetyCenterOverallState,
562             List<SafetyCenterEntry> entries,
563             SafetySource safetySource,
564             String defaultPackageName,
565             @UserIdInt int userId,
566             boolean isUserManaged,
567             boolean isManagedUserRunning) {
568         SafetyCenterEntry safetyCenterEntry =
569                 toSafetyCenterEntry(
570                         safetySource,
571                         defaultPackageName,
572                         userId,
573                         isUserManaged,
574                         isManagedUserRunning);
575         if (safetyCenterEntry == null) {
576             return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED;
577         }
578 
579         safetyCenterOverallState.addEntryOverallSeverityLevel(
580                 entryToSafetyCenterStatusOverallSeverityLevel(
581                         safetyCenterEntry.getSeverityLevel()));
582         entries.add(safetyCenterEntry);
583 
584         return safetyCenterEntry.getSeverityLevel();
585     }
586 
587     @Nullable
toSafetyCenterEntry( SafetySource safetySource, String defaultPackageName, @UserIdInt int userId, boolean isUserManaged, boolean isManagedUserRunning)588     private SafetyCenterEntry toSafetyCenterEntry(
589             SafetySource safetySource,
590             String defaultPackageName,
591             @UserIdInt int userId,
592             boolean isUserManaged,
593             boolean isManagedUserRunning) {
594         switch (safetySource.getType()) {
595             case SafetySource.SAFETY_SOURCE_TYPE_ISSUE_ONLY:
596                 return null;
597             case SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC:
598                 SafetySourceKey key = SafetySourceKey.of(safetySource.getId(), userId);
599                 SafetySourceStatus safetySourceStatus =
600                         getSafetySourceStatus(
601                                 mSafetyCenterDataManager.getSafetySourceDataInternal(key));
602                 boolean inQuietMode = isUserManaged && !isManagedUserRunning;
603                 if (safetySourceStatus == null) {
604                     int severityLevel =
605                             inQuietMode
606                                     ? SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED
607                                     : SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN;
608                     return toDefaultSafetyCenterEntry(
609                             safetySource,
610                             safetySource.getPackageName(),
611                             severityLevel,
612                             SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION,
613                             userId,
614                             isUserManaged,
615                             isManagedUserRunning);
616                 }
617                 PendingIntent sourceProvidedPendingIntent =
618                         inQuietMode ? null : safetySourceStatus.getPendingIntent();
619                 PendingIntent entryPendingIntent =
620                         sourceProvidedPendingIntent != null
621                                 ? sourceProvidedPendingIntent
622                                 : mPendingIntentFactory.getPendingIntent(
623                                         safetySource.getId(),
624                                         safetySource.getIntentAction(),
625                                         safetySource.getPackageName(),
626                                         userId,
627                                         inQuietMode);
628                 boolean enabled =
629                         safetySourceStatus.isEnabled()
630                                 && !inQuietMode
631                                 && entryPendingIntent != null;
632                 SafetyCenterEntryId safetyCenterEntryId =
633                         SafetyCenterEntryId.newBuilder()
634                                 .setSafetySourceId(safetySource.getId())
635                                 .setUserId(userId)
636                                 .build();
637                 int severityUnspecifiedIconType =
638                         SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION;
639                 int severityLevel =
640                         enabled
641                                 ? toSafetyCenterEntrySeverityLevel(
642                                         safetySourceStatus.getSeverityLevel())
643                                 : SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED;
644                 SafetyCenterEntry.Builder builder =
645                         new SafetyCenterEntry.Builder(
646                                         SafetyCenterIds.encodeToString(safetyCenterEntryId),
647                                         safetySourceStatus.getTitle())
648                                 .setSeverityLevel(severityLevel)
649                                 .setSummary(
650                                         inQuietMode
651                                                 ? DevicePolicyResources.getWorkProfilePausedString(
652                                                         mSafetyCenterResourcesContext)
653                                                 : safetySourceStatus.getSummary())
654                                 .setEnabled(enabled)
655                                 .setSeverityUnspecifiedIconType(severityUnspecifiedIconType)
656                                 .setPendingIntent(entryPendingIntent);
657                 SafetySourceStatus.IconAction iconAction = safetySourceStatus.getIconAction();
658                 if (iconAction == null) {
659                     return builder.build();
660                 }
661                 return builder.setIconAction(
662                                 new SafetyCenterEntry.IconAction(
663                                         toSafetyCenterEntryIconActionType(iconAction.getIconType()),
664                                         iconAction.getPendingIntent()))
665                         .build();
666             case SafetySource.SAFETY_SOURCE_TYPE_STATIC:
667                 return toDefaultSafetyCenterEntry(
668                         safetySource,
669                         getStaticSourcePackageNameOrDefault(safetySource, defaultPackageName),
670                         SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED,
671                         SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_NO_ICON,
672                         userId,
673                         isUserManaged,
674                         isManagedUserRunning);
675         }
676         Log.w(
677                 TAG,
678                 "Unknown safety source type found in collapsible group: " + safetySource.getType());
679         return null;
680     }
681 
682     @Nullable
toDefaultSafetyCenterEntry( SafetySource safetySource, String packageName, @SafetyCenterEntry.EntrySeverityLevel int entrySeverityLevel, @SafetyCenterEntry.SeverityUnspecifiedIconType int severityUnspecifiedIconType, @UserIdInt int userId, boolean isUserManaged, boolean isManagedUserRunning)683     private SafetyCenterEntry toDefaultSafetyCenterEntry(
684             SafetySource safetySource,
685             String packageName,
686             @SafetyCenterEntry.EntrySeverityLevel int entrySeverityLevel,
687             @SafetyCenterEntry.SeverityUnspecifiedIconType int severityUnspecifiedIconType,
688             @UserIdInt int userId,
689             boolean isUserManaged,
690             boolean isManagedUserRunning) {
691         if (SafetySources.isDefaultEntryHidden(safetySource)) {
692             return null;
693         }
694 
695         SafetyCenterEntryId safetyCenterEntryId =
696                 SafetyCenterEntryId.newBuilder()
697                         .setSafetySourceId(safetySource.getId())
698                         .setUserId(userId)
699                         .build();
700         boolean isQuietModeEnabled = isUserManaged && !isManagedUserRunning;
701         PendingIntent pendingIntent =
702                 mPendingIntentFactory.getPendingIntent(
703                         safetySource.getId(),
704                         safetySource.getIntentAction(),
705                         packageName,
706                         userId,
707                         isQuietModeEnabled);
708         boolean enabled =
709                 pendingIntent != null && !SafetySources.isDefaultEntryDisabled(safetySource);
710         CharSequence title =
711                 isUserManaged
712                         ? DevicePolicyResources.getSafetySourceWorkString(
713                                 mSafetyCenterResourcesContext,
714                                 safetySource.getId(),
715                                 safetySource.getTitleForWorkResId())
716                         : mSafetyCenterResourcesContext.getString(safetySource.getTitleResId());
717         CharSequence summary =
718                 mSafetyCenterDataManager.sourceHasError(
719                                 SafetySourceKey.of(safetySource.getId(), userId))
720                         ? getRefreshErrorString(1)
721                         : mSafetyCenterResourcesContext.getOptionalString(
722                                 safetySource.getSummaryResId());
723         if (isQuietModeEnabled) {
724             enabled = false;
725             summary =
726                     DevicePolicyResources.getWorkProfilePausedString(mSafetyCenterResourcesContext);
727         }
728         return new SafetyCenterEntry.Builder(
729                         SafetyCenterIds.encodeToString(safetyCenterEntryId), title)
730                 .setSeverityLevel(entrySeverityLevel)
731                 .setSummary(summary)
732                 .setEnabled(enabled)
733                 .setPendingIntent(pendingIntent)
734                 .setSeverityUnspecifiedIconType(severityUnspecifiedIconType)
735                 .build();
736     }
737 
addSafetyCenterStaticEntryGroup( Bundle staticEntriesToIds, SafetyCenterOverallState safetyCenterOverallState, List<SafetyCenterStaticEntryGroup> safetyCenterStaticEntryGroups, SafetySourcesGroup safetySourcesGroup, String defaultPackageName, UserProfileGroup userProfileGroup)738     private void addSafetyCenterStaticEntryGroup(
739             Bundle staticEntriesToIds,
740             SafetyCenterOverallState safetyCenterOverallState,
741             List<SafetyCenterStaticEntryGroup> safetyCenterStaticEntryGroups,
742             SafetySourcesGroup safetySourcesGroup,
743             String defaultPackageName,
744             UserProfileGroup userProfileGroup) {
745         List<SafetySource> safetySources = safetySourcesGroup.getSafetySources();
746         List<SafetyCenterStaticEntry> staticEntries = new ArrayList<>(safetySources.size());
747         for (int i = 0; i < safetySources.size(); i++) {
748             SafetySource safetySource = safetySources.get(i);
749 
750             addSafetyCenterStaticEntry(
751                     staticEntriesToIds,
752                     safetyCenterOverallState,
753                     staticEntries,
754                     safetySource,
755                     defaultPackageName,
756                     userProfileGroup.getProfileParentUserId(),
757                     false,
758                     false);
759 
760             if (!SafetySources.supportsManagedProfiles(safetySource)) {
761                 continue;
762             }
763 
764             int[] managedProfilesUserIds = userProfileGroup.getManagedProfilesUserIds();
765             for (int j = 0; j < managedProfilesUserIds.length; j++) {
766                 int managedProfileUserId = managedProfilesUserIds[j];
767                 boolean isManagedUserRunning =
768                         userProfileGroup.isManagedUserRunning(managedProfileUserId);
769 
770                 addSafetyCenterStaticEntry(
771                         staticEntriesToIds,
772                         safetyCenterOverallState,
773                         staticEntries,
774                         safetySource,
775                         defaultPackageName,
776                         managedProfileUserId,
777                         true,
778                         isManagedUserRunning);
779             }
780         }
781 
782         if (staticEntries.isEmpty()) {
783             return;
784         }
785 
786         safetyCenterStaticEntryGroups.add(
787                 new SafetyCenterStaticEntryGroup(
788                         mSafetyCenterResourcesContext.getString(safetySourcesGroup.getTitleResId()),
789                         staticEntries));
790     }
791 
addSafetyCenterStaticEntry( Bundle staticEntriesToIds, SafetyCenterOverallState safetyCenterOverallState, List<SafetyCenterStaticEntry> staticEntries, SafetySource safetySource, String defaultPackageName, @UserIdInt int userId, boolean isUserManaged, boolean isManagedUserRunning)792     private void addSafetyCenterStaticEntry(
793             Bundle staticEntriesToIds,
794             SafetyCenterOverallState safetyCenterOverallState,
795             List<SafetyCenterStaticEntry> staticEntries,
796             SafetySource safetySource,
797             String defaultPackageName,
798             @UserIdInt int userId,
799             boolean isUserManaged,
800             boolean isManagedUserRunning) {
801         SafetyCenterStaticEntry staticEntry =
802                 toSafetyCenterStaticEntry(
803                         safetySource,
804                         defaultPackageName,
805                         userId,
806                         isUserManaged,
807                         isManagedUserRunning);
808         if (staticEntry == null) {
809             return;
810         }
811         if (SdkLevel.isAtLeastU()) {
812             staticEntriesToIds.putString(
813                     SafetyCenterBundles.toBundleKey(staticEntry),
814                     SafetyCenterIds.encodeToString(
815                             SafetyCenterEntryId.newBuilder()
816                                     .setSafetySourceId(safetySource.getId())
817                                     .setUserId(userId)
818                                     .build()));
819         }
820         boolean hasError =
821                 mSafetyCenterDataManager.sourceHasError(
822                         SafetySourceKey.of(safetySource.getId(), userId));
823         if (hasError) {
824             safetyCenterOverallState.addEntryOverallSeverityLevel(
825                     SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN);
826         }
827         staticEntries.add(staticEntry);
828     }
829 
830     @Nullable
toSafetyCenterStaticEntry( SafetySource safetySource, String defaultPackageName, @UserIdInt int userId, boolean isUserManaged, boolean isManagedUserRunning)831     private SafetyCenterStaticEntry toSafetyCenterStaticEntry(
832             SafetySource safetySource,
833             String defaultPackageName,
834             @UserIdInt int userId,
835             boolean isUserManaged,
836             boolean isManagedUserRunning) {
837         switch (safetySource.getType()) {
838             case SafetySource.SAFETY_SOURCE_TYPE_ISSUE_ONLY:
839                 return null;
840             case SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC:
841                 SafetySourceKey key = SafetySourceKey.of(safetySource.getId(), userId);
842                 SafetySourceStatus safetySourceStatus =
843                         getSafetySourceStatus(
844                                 mSafetyCenterDataManager.getSafetySourceDataInternal(key));
845                 boolean inQuietMode = isUserManaged && !isManagedUserRunning;
846                 if (safetySourceStatus != null) {
847                     PendingIntent sourceProvidedPendingIntent =
848                             inQuietMode ? null : safetySourceStatus.getPendingIntent();
849                     PendingIntent entryPendingIntent =
850                             sourceProvidedPendingIntent != null
851                                     ? sourceProvidedPendingIntent
852                                     : mPendingIntentFactory.getPendingIntent(
853                                             safetySource.getId(),
854                                             safetySource.getIntentAction(),
855                                             safetySource.getPackageName(),
856                                             userId,
857                                             inQuietMode);
858                     if (entryPendingIntent == null) {
859                         // TODO(b/222838784): Decide strategy for static entries when the intent is
860                         //  null.
861                         return null;
862                     }
863                     return new SafetyCenterStaticEntry.Builder(safetySourceStatus.getTitle())
864                             .setSummary(
865                                     inQuietMode
866                                             ? DevicePolicyResources.getWorkProfilePausedString(
867                                                     mSafetyCenterResourcesContext)
868                                             : safetySourceStatus.getSummary())
869                             .setPendingIntent(entryPendingIntent)
870                             .build();
871                 }
872                 return toDefaultSafetyCenterStaticEntry(
873                         safetySource,
874                         safetySource.getPackageName(),
875                         userId,
876                         isUserManaged,
877                         isManagedUserRunning);
878             case SafetySource.SAFETY_SOURCE_TYPE_STATIC:
879                 return toDefaultSafetyCenterStaticEntry(
880                         safetySource,
881                         getStaticSourcePackageNameOrDefault(safetySource, defaultPackageName),
882                         userId,
883                         isUserManaged,
884                         isManagedUserRunning);
885         }
886         Log.w(TAG, "Unknown safety source type found in rigid group: " + safetySource.getType());
887         return null;
888     }
889 
890     @Nullable
toDefaultSafetyCenterStaticEntry( SafetySource safetySource, String packageName, @UserIdInt int userId, boolean isUserManaged, boolean isManagedUserRunning)891     private SafetyCenterStaticEntry toDefaultSafetyCenterStaticEntry(
892             SafetySource safetySource,
893             String packageName,
894             @UserIdInt int userId,
895             boolean isUserManaged,
896             boolean isManagedUserRunning) {
897         if (SafetySources.isDefaultEntryHidden(safetySource)) {
898             return null;
899         }
900         boolean isQuietModeEnabled = isUserManaged && !isManagedUserRunning;
901         PendingIntent pendingIntent =
902                 mPendingIntentFactory.getPendingIntent(
903                         safetySource.getId(),
904                         safetySource.getIntentAction(),
905                         packageName,
906                         userId,
907                         isQuietModeEnabled);
908 
909         if (pendingIntent == null) {
910             // TODO(b/222838784): Decide strategy for static entries when the intent is null.
911             return null;
912         }
913 
914         CharSequence title =
915                 isUserManaged
916                         ? DevicePolicyResources.getSafetySourceWorkString(
917                                 mSafetyCenterResourcesContext,
918                                 safetySource.getId(),
919                                 safetySource.getTitleForWorkResId())
920                         : mSafetyCenterResourcesContext.getString(safetySource.getTitleResId());
921         CharSequence summary =
922                 mSafetyCenterDataManager.sourceHasError(
923                                 SafetySourceKey.of(safetySource.getId(), userId))
924                         ? getRefreshErrorString(1)
925                         : mSafetyCenterResourcesContext.getOptionalString(
926                                 safetySource.getSummaryResId());
927         if (isQuietModeEnabled) {
928             summary =
929                     DevicePolicyResources.getWorkProfilePausedString(mSafetyCenterResourcesContext);
930         }
931         return new SafetyCenterStaticEntry.Builder(title)
932                 .setSummary(summary)
933                 .setPendingIntent(pendingIntent)
934                 .build();
935     }
936 
937     @Nullable
getSafetySourceStatus( @ullable SafetySourceData safetySourceData)938     private static SafetySourceStatus getSafetySourceStatus(
939             @Nullable SafetySourceData safetySourceData) {
940         if (safetySourceData == null) {
941             return null;
942         }
943 
944         return safetySourceData.getStatus();
945     }
946 
getStaticSourcePackageNameOrDefault( SafetySource safetySource, String defaultPackageName)947     private static String getStaticSourcePackageNameOrDefault(
948             SafetySource safetySource, String defaultPackageName) {
949         if (!SdkLevel.isAtLeastU()) {
950             return defaultPackageName;
951         }
952         String sourcePackageName = safetySource.getOptionalPackageName();
953         if (sourcePackageName == null) {
954             return defaultPackageName;
955         }
956         return sourcePackageName;
957     }
958 
959     @SafetyCenterStatus.OverallSeverityLevel
toSafetyCenterStatusOverallSeverityLevel( @afetySourceData.SeverityLevel int safetySourceSeverityLevel)960     private static int toSafetyCenterStatusOverallSeverityLevel(
961             @SafetySourceData.SeverityLevel int safetySourceSeverityLevel) {
962         switch (safetySourceSeverityLevel) {
963             case SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED:
964             case SafetySourceData.SEVERITY_LEVEL_INFORMATION:
965                 return SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK;
966             case SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION:
967                 return SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_RECOMMENDATION;
968             case SafetySourceData.SEVERITY_LEVEL_CRITICAL_WARNING:
969                 return SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING;
970         }
971 
972         Log.w(TAG, "Unexpected SafetySourceData.SeverityLevel: " + safetySourceSeverityLevel);
973         return SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN;
974     }
975 
976     @SafetyCenterStatus.OverallSeverityLevel
entryToSafetyCenterStatusOverallSeverityLevel( @afetyCenterEntry.EntrySeverityLevel int safetyCenterEntrySeverityLevel)977     private static int entryToSafetyCenterStatusOverallSeverityLevel(
978             @SafetyCenterEntry.EntrySeverityLevel int safetyCenterEntrySeverityLevel) {
979         switch (safetyCenterEntrySeverityLevel) {
980             case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN:
981                 return SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN;
982             case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED:
983             case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK:
984                 return SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK;
985             case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION:
986                 return SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_RECOMMENDATION;
987             case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_CRITICAL_WARNING:
988                 return SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING;
989         }
990 
991         Log.w(
992                 TAG,
993                 "Unexpected SafetyCenterEntry.EntrySeverityLevel: "
994                         + safetyCenterEntrySeverityLevel);
995         return SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN;
996     }
997 
998     @SafetyCenterEntry.EntrySeverityLevel
toSafetyCenterEntrySeverityLevel( @afetySourceData.SeverityLevel int safetySourceSeverityLevel)999     private static int toSafetyCenterEntrySeverityLevel(
1000             @SafetySourceData.SeverityLevel int safetySourceSeverityLevel) {
1001         switch (safetySourceSeverityLevel) {
1002             case SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED:
1003                 return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED;
1004             case SafetySourceData.SEVERITY_LEVEL_INFORMATION:
1005                 return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK;
1006             case SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION:
1007                 return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION;
1008             case SafetySourceData.SEVERITY_LEVEL_CRITICAL_WARNING:
1009                 return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_CRITICAL_WARNING;
1010         }
1011 
1012         Log.w(
1013                 TAG,
1014                 "Unexpected SafetySourceData.SeverityLevel in SafetySourceStatus: "
1015                         + safetySourceSeverityLevel);
1016         return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN;
1017     }
1018 
1019     @SafetyCenterIssue.IssueSeverityLevel
toSafetyCenterIssueSeverityLevel( @afetySourceData.SeverityLevel int safetySourceIssueSeverityLevel)1020     private static int toSafetyCenterIssueSeverityLevel(
1021             @SafetySourceData.SeverityLevel int safetySourceIssueSeverityLevel) {
1022         switch (safetySourceIssueSeverityLevel) {
1023             case SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED:
1024                 Log.w(
1025                         TAG,
1026                         "Unexpected use of SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED in "
1027                                 + "SafetySourceIssue");
1028                 return SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_OK;
1029             case SafetySourceData.SEVERITY_LEVEL_INFORMATION:
1030                 return SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_OK;
1031             case SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION:
1032                 return SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_RECOMMENDATION;
1033             case SafetySourceData.SEVERITY_LEVEL_CRITICAL_WARNING:
1034                 return SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_CRITICAL_WARNING;
1035         }
1036 
1037         Log.w(
1038                 TAG,
1039                 "Unexpected SafetySourceData.SeverityLevel in SafetySourceIssue: "
1040                         + safetySourceIssueSeverityLevel);
1041         return SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_OK;
1042     }
1043 
1044     @SafetyCenterEntry.SeverityUnspecifiedIconType
toGroupSeverityUnspecifiedIconType( @afetySourcesGroup.StatelessIconType int statelessIconType)1045     private static int toGroupSeverityUnspecifiedIconType(
1046             @SafetySourcesGroup.StatelessIconType int statelessIconType) {
1047         switch (statelessIconType) {
1048             case SafetySourcesGroup.STATELESS_ICON_TYPE_NONE:
1049                 return SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION;
1050             case SafetySourcesGroup.STATELESS_ICON_TYPE_PRIVACY:
1051                 return SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_PRIVACY;
1052         }
1053 
1054         Log.w(TAG, "Unexpected SafetySourcesGroup.StatelessIconType: " + statelessIconType);
1055         return SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_NO_ICON;
1056     }
1057 
1058     @SafetyCenterEntry.IconAction.IconActionType
toSafetyCenterEntryIconActionType( @afetySourceStatus.IconAction.IconType int safetySourceIconActionType)1059     private static int toSafetyCenterEntryIconActionType(
1060             @SafetySourceStatus.IconAction.IconType int safetySourceIconActionType) {
1061         switch (safetySourceIconActionType) {
1062             case SafetySourceStatus.IconAction.ICON_TYPE_GEAR:
1063                 return SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_GEAR;
1064             case SafetySourceStatus.IconAction.ICON_TYPE_INFO:
1065                 return SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_INFO;
1066         }
1067 
1068         Log.w(
1069                 TAG,
1070                 "Unexpected SafetySourceStatus.IconAction.IconActionType: "
1071                         + safetySourceIconActionType);
1072         return SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_INFO;
1073     }
1074 
getSafetyCenterStatusTitle( @afetyCenterStatus.OverallSeverityLevel int overallSeverityLevel, @Nullable SafetySourceIssueInfo topNonDismissedIssueInfo, @SafetyCenterStatus.RefreshStatus int refreshStatus, boolean hasSettingsToReview)1075     private String getSafetyCenterStatusTitle(
1076             @SafetyCenterStatus.OverallSeverityLevel int overallSeverityLevel,
1077             @Nullable SafetySourceIssueInfo topNonDismissedIssueInfo,
1078             @SafetyCenterStatus.RefreshStatus int refreshStatus,
1079             boolean hasSettingsToReview) {
1080         boolean overallSeverityUnknown =
1081                 overallSeverityLevel == SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN;
1082         String refreshStatusTitle =
1083                 getSafetyCenterRefreshStatusTitle(refreshStatus, overallSeverityUnknown);
1084         if (refreshStatusTitle != null) {
1085             return refreshStatusTitle;
1086         }
1087         switch (overallSeverityLevel) {
1088             case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN:
1089             case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK:
1090                 if (hasSettingsToReview) {
1091                     return mSafetyCenterResourcesContext.getStringByName(
1092                             "overall_severity_level_ok_review_title");
1093                 }
1094                 return mSafetyCenterResourcesContext.getStringByName(
1095                         "overall_severity_level_ok_title");
1096             case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_RECOMMENDATION:
1097                 return getStatusTitleFromIssueCategories(
1098                         topNonDismissedIssueInfo,
1099                         "overall_severity_level_device_recommendation_title",
1100                         "overall_severity_level_account_recommendation_title",
1101                         "overall_severity_level_safety_recommendation_title",
1102                         "overall_severity_level_data_recommendation_title",
1103                         "overall_severity_level_passwords_recommendation_title",
1104                         "overall_severity_level_personal_recommendation_title");
1105             case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING:
1106                 return getStatusTitleFromIssueCategories(
1107                         topNonDismissedIssueInfo,
1108                         "overall_severity_level_critical_device_warning_title",
1109                         "overall_severity_level_critical_account_warning_title",
1110                         "overall_severity_level_critical_safety_warning_title",
1111                         "overall_severity_level_critical_data_warning_title",
1112                         "overall_severity_level_critical_passwords_warning_title",
1113                         "overall_severity_level_critical_personal_warning_title");
1114         }
1115 
1116         Log.w(TAG, "Unexpected SafetyCenterStatus.OverallSeverityLevel: " + overallSeverityLevel);
1117         return "";
1118     }
1119 
getStatusTitleFromIssueCategories( @ullable SafetySourceIssueInfo topNonDismissedIssueInfo, String deviceResourceName, String accountResourceName, String generalResourceName, String dataResourceName, String passwordsResourceName, String personalSafetyResourceName)1120     private String getStatusTitleFromIssueCategories(
1121             @Nullable SafetySourceIssueInfo topNonDismissedIssueInfo,
1122             String deviceResourceName,
1123             String accountResourceName,
1124             String generalResourceName,
1125             String dataResourceName,
1126             String passwordsResourceName,
1127             String personalSafetyResourceName) {
1128         String generalString = mSafetyCenterResourcesContext.getStringByName(generalResourceName);
1129         if (topNonDismissedIssueInfo == null) {
1130             Log.w(TAG, "No safety center issues found in a non-green status");
1131             return generalString;
1132         }
1133         int issueCategory = topNonDismissedIssueInfo.getSafetySourceIssue().getIssueCategory();
1134         switch (issueCategory) {
1135             case SafetySourceIssue.ISSUE_CATEGORY_DEVICE:
1136                 return mSafetyCenterResourcesContext.getStringByName(deviceResourceName);
1137             case SafetySourceIssue.ISSUE_CATEGORY_ACCOUNT:
1138                 return mSafetyCenterResourcesContext.getStringByName(accountResourceName);
1139             case SafetySourceIssue.ISSUE_CATEGORY_GENERAL:
1140                 return generalString;
1141         }
1142         if (SdkLevel.isAtLeastU()) {
1143             switch (issueCategory) {
1144                 case SafetySourceIssue.ISSUE_CATEGORY_DATA:
1145                     return mSafetyCenterResourcesContext.getStringByName(dataResourceName);
1146                 case SafetySourceIssue.ISSUE_CATEGORY_PASSWORDS:
1147                     return mSafetyCenterResourcesContext.getStringByName(passwordsResourceName);
1148                 case SafetySourceIssue.ISSUE_CATEGORY_PERSONAL_SAFETY:
1149                     return mSafetyCenterResourcesContext.getStringByName(
1150                             personalSafetyResourceName);
1151             }
1152         }
1153 
1154         Log.w(TAG, "Unexpected SafetySourceIssue.IssueCategory: " + issueCategory);
1155         return generalString;
1156     }
1157 
getSafetyCenterStatusSummary( SafetyCenterOverallState safetyCenterOverallState, @Nullable SafetySourceIssueInfo topNonDismissedIssue, @SafetyCenterStatus.RefreshStatus int refreshStatus, int numTipIssues, int numAutomaticIssues, int numIssues)1158     private String getSafetyCenterStatusSummary(
1159             SafetyCenterOverallState safetyCenterOverallState,
1160             @Nullable SafetySourceIssueInfo topNonDismissedIssue,
1161             @SafetyCenterStatus.RefreshStatus int refreshStatus,
1162             int numTipIssues,
1163             int numAutomaticIssues,
1164             int numIssues) {
1165         String refreshStatusSummary = getSafetyCenterRefreshStatusSummary(refreshStatus);
1166         if (refreshStatusSummary != null) {
1167             return refreshStatusSummary;
1168         }
1169         int overallSeverityLevel = safetyCenterOverallState.getOverallSeverityLevel();
1170         switch (overallSeverityLevel) {
1171             case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN:
1172             case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK:
1173                 if (topNonDismissedIssue == null) {
1174                     if (safetyCenterOverallState.hasSettingsToReview()) {
1175                         return mSafetyCenterResourcesContext.getStringByName(
1176                                 "overall_severity_level_ok_review_summary");
1177                     }
1178                     return mSafetyCenterResourcesContext.getStringByName(
1179                             "overall_severity_level_ok_summary");
1180                 } else if (isTip(topNonDismissedIssue.getSafetySourceIssue())) {
1181                     return mSafetyCenterResourcesContext.getStringByName(
1182                             "overall_severity_level_tip_summary", numTipIssues);
1183 
1184                 } else if (isAutomatic(topNonDismissedIssue.getSafetySourceIssue())) {
1185                     return mSafetyCenterResourcesContext.getStringByName(
1186                             "overall_severity_level_action_taken_summary", numAutomaticIssues);
1187                 }
1188                 // Fall through.
1189             case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_RECOMMENDATION:
1190             case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING:
1191                 return getIcuPluralsString("overall_severity_n_alerts_summary", numIssues);
1192         }
1193 
1194         Log.w(TAG, "Unexpected SafetyCenterStatus.OverallSeverityLevel: " + overallSeverityLevel);
1195         return "";
1196     }
1197 
isTip(SafetySourceIssue safetySourceIssue)1198     private static boolean isTip(SafetySourceIssue safetySourceIssue) {
1199         return SdkLevel.isAtLeastU()
1200                 && safetySourceIssue.getIssueActionability()
1201                         == SafetySourceIssue.ISSUE_ACTIONABILITY_TIP;
1202     }
1203 
isAutomatic(SafetySourceIssue safetySourceIssue)1204     private static boolean isAutomatic(SafetySourceIssue safetySourceIssue) {
1205         return SdkLevel.isAtLeastU()
1206                 && safetySourceIssue.getIssueActionability()
1207                         == SafetySourceIssue.ISSUE_ACTIONABILITY_AUTOMATIC;
1208     }
1209 
getRefreshErrorString(int numberOfErrorEntries)1210     private String getRefreshErrorString(int numberOfErrorEntries) {
1211         return getIcuPluralsString("refresh_error", numberOfErrorEntries);
1212     }
1213 
getIcuPluralsString(String name, int count, Object... formatArgs)1214     private String getIcuPluralsString(String name, int count, Object... formatArgs) {
1215         MessageFormat messageFormat =
1216                 new MessageFormat(
1217                         mSafetyCenterResourcesContext.getStringByName(name, formatArgs),
1218                         Locale.getDefault());
1219         ArrayMap<String, Object> arguments = new ArrayMap<>();
1220         arguments.put("count", count);
1221         return messageFormat.format(arguments);
1222     }
1223 
1224     @Nullable
getSafetyCenterRefreshStatusTitle( @afetyCenterStatus.RefreshStatus int refreshStatus, boolean overallSeverityUnknown)1225     private String getSafetyCenterRefreshStatusTitle(
1226             @SafetyCenterStatus.RefreshStatus int refreshStatus, boolean overallSeverityUnknown) {
1227         switch (refreshStatus) {
1228             case SafetyCenterStatus.REFRESH_STATUS_NONE:
1229                 return null;
1230             case SafetyCenterStatus.REFRESH_STATUS_DATA_FETCH_IN_PROGRESS:
1231                 if (!overallSeverityUnknown) {
1232                     return null;
1233                 }
1234                 // Fall through.
1235             case SafetyCenterStatus.REFRESH_STATUS_FULL_RESCAN_IN_PROGRESS:
1236                 return mSafetyCenterResourcesContext.getStringByName("scanning_title");
1237         }
1238 
1239         Log.w(TAG, "Unexpected SafetyCenterStatus.RefreshStatus: " + refreshStatus);
1240         return null;
1241     }
1242 
1243     @Nullable
getSafetyCenterRefreshStatusSummary( @afetyCenterStatus.RefreshStatus int refreshStatus)1244     private String getSafetyCenterRefreshStatusSummary(
1245             @SafetyCenterStatus.RefreshStatus int refreshStatus) {
1246         switch (refreshStatus) {
1247             case SafetyCenterStatus.REFRESH_STATUS_NONE:
1248                 return null;
1249             case SafetyCenterStatus.REFRESH_STATUS_DATA_FETCH_IN_PROGRESS:
1250             case SafetyCenterStatus.REFRESH_STATUS_FULL_RESCAN_IN_PROGRESS:
1251                 return mSafetyCenterResourcesContext.getStringByName("loading_summary");
1252         }
1253 
1254         Log.w(TAG, "Unexpected SafetyCenterStatus.RefreshStatus: " + refreshStatus);
1255         return null;
1256     }
1257 
toSafetySourceKey(String safetyCenterEntryIdString)1258     private static SafetySourceKey toSafetySourceKey(String safetyCenterEntryIdString) {
1259         SafetyCenterEntryId id = SafetyCenterIds.entryIdFromString(safetyCenterEntryIdString);
1260         return SafetySourceKey.of(id.getSafetySourceId(), id.getUserId());
1261     }
1262 
1263     /**
1264      * An internal mutable class to keep track of the overall {@link SafetyCenterStatus} severity
1265      * level and whether the list of entries provided requires attention.
1266      */
1267     private static final class SafetyCenterOverallState {
1268 
1269         @SafetyCenterStatus.OverallSeverityLevel
1270         private int mIssuesOverallSeverityLevel = SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK;
1271 
1272         @SafetyCenterStatus.OverallSeverityLevel
1273         private int mEntriesOverallSeverityLevel = SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK;
1274 
1275         /**
1276          * Adds a {@link SafetyCenterStatus.OverallSeverityLevel} computed from an issue.
1277          *
1278          * <p>The {@code overallSeverityLevel} provided cannot be {@link
1279          * SafetyCenterStatus#OVERALL_SEVERITY_LEVEL_UNKNOWN}. If the data for an issue is not
1280          * provided yet, this will be reflected when calling {@link
1281          * #addEntryOverallSeverityLevel(int)}. The exception to that are issue-only safety sources
1282          * but since they do not have user-visible entries they do not affect whether the overall
1283          * status is unknown.
1284          */
addIssueOverallSeverityLevel( @afetyCenterStatus.OverallSeverityLevel int issueOverallSeverityLevel)1285         private void addIssueOverallSeverityLevel(
1286                 @SafetyCenterStatus.OverallSeverityLevel int issueOverallSeverityLevel) {
1287             if (issueOverallSeverityLevel == SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN) {
1288                 return;
1289             }
1290             mIssuesOverallSeverityLevel =
1291                     mergeOverallSeverityLevels(
1292                             mIssuesOverallSeverityLevel, issueOverallSeverityLevel);
1293         }
1294 
1295         /**
1296          * Adds a {@link SafetyCenterStatus.OverallSeverityLevel} computed from an entry.
1297          *
1298          * <p>Entries may be unknown (e.g. due to an error or no data provided yet). In this case,
1299          * the overall status will be marked as unknown if there are no recommendations or critical
1300          * issues.
1301          */
addEntryOverallSeverityLevel( @afetyCenterStatus.OverallSeverityLevel int entryOverallSeverityLevel)1302         private void addEntryOverallSeverityLevel(
1303                 @SafetyCenterStatus.OverallSeverityLevel int entryOverallSeverityLevel) {
1304             mEntriesOverallSeverityLevel =
1305                     mergeOverallSeverityLevels(
1306                             mEntriesOverallSeverityLevel, entryOverallSeverityLevel);
1307         }
1308 
1309         /**
1310          * Returns the {@link SafetyCenterStatus.OverallSeverityLevel} computed.
1311          *
1312          * <p>Returns {@link SafetyCenterStatus#OVERALL_SEVERITY_LEVEL_UNKNOWN} if any entry is
1313          * unknown / has errored-out and there are no recommendations or critical issues.
1314          *
1315          * <p>Otherwise, this is computed based on the maximum severity level of issues.
1316          */
1317         @SafetyCenterStatus.OverallSeverityLevel
getOverallSeverityLevel()1318         private int getOverallSeverityLevel() {
1319             if (mEntriesOverallSeverityLevel == SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN
1320                     && mIssuesOverallSeverityLevel
1321                             <= SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK) {
1322                 return SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN;
1323             }
1324             return mIssuesOverallSeverityLevel;
1325         }
1326 
1327         /**
1328          * Returns whether there are settings to review (i.e. at least one entry has a more severe
1329          * status than the overall status, or if any entry is not yet known / has errored-out).
1330          */
hasSettingsToReview()1331         private boolean hasSettingsToReview() {
1332             return mEntriesOverallSeverityLevel == SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN
1333                     || mEntriesOverallSeverityLevel > mIssuesOverallSeverityLevel;
1334         }
1335 
1336         @SafetyCenterStatus.OverallSeverityLevel
mergeOverallSeverityLevels( @afetyCenterStatus.OverallSeverityLevel int left, @SafetyCenterStatus.OverallSeverityLevel int right)1337         private static int mergeOverallSeverityLevels(
1338                 @SafetyCenterStatus.OverallSeverityLevel int left,
1339                 @SafetyCenterStatus.OverallSeverityLevel int right) {
1340             if (left == SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN
1341                     || right == SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN) {
1342                 return SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN;
1343             }
1344             return Math.max(left, right);
1345         }
1346     }
1347 }
1348