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