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