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