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