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