diff options
23 files changed, 411 insertions, 165 deletions
diff --git a/Android.bp b/Android.bp index f9eee9ddb..4a436da6c 100644 --- a/Android.bp +++ b/Android.bp @@ -54,11 +54,6 @@ android_app_certificate { certificate: "com.android.permission", } -filegroup { - name: "permission-jarjar-rules", - srcs: ["jarjar-rules.txt"], -} - sdk { name: "permission-module-sdk", apexes: [ @@ -110,9 +105,9 @@ bootclasspath_fragment { // result in a build failure due to inconsistent flags. package_prefixes: [ "android.app.role", + "android.permission.jarjar", "android.safetycenter", "android.safetylabel", - "com.android.permission", ], }, } diff --git a/PermissionController/res/layout/app_permission.xml b/PermissionController/res/layout/app_permission.xml index 81b6eea5e..6cdec331a 100644 --- a/PermissionController/res/layout/app_permission.xml +++ b/PermissionController/res/layout/app_permission.xml @@ -45,6 +45,7 @@ style="@style/AppPermissionRationaleContainer"> <TextView android:id="@+id/app_permission_rationale_message" + android:text="@string/app_permission_rationale_message" style="@style/AppPermissionMessage" /> <LinearLayout @@ -66,10 +67,12 @@ <TextView android:duplicateParentState="true" android:id="@+id/app_permission_rationale_title" + android:text="@string/app_location_permission_rationale_title" style="@style/AppPermissionRationaleTitle" /> <TextView android:duplicateParentState="true" android:id="@+id/app_permission_rationale_subtitle" + android:text="@string/app_location_permission_rationale_subtitle" style="@style/AppPermissionRationaleSubtitle" /> </LinearLayout> diff --git a/PermissionController/res/values-v34/strings.xml b/PermissionController/res/values-v34/strings.xml index e14e9bc91..26074240b 100644 --- a/PermissionController/res/values-v34/strings.xml +++ b/PermissionController/res/values-v34/strings.xml @@ -16,131 +16,6 @@ --> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - - <!-- Link for Safety Label Data Sharing help center page. [CHAR LIMIT=NONE]--> - <string name="data_sharing_help_center_link" translatable="false">https://support.google.com/android?p=data_sharing</string> - - <!-- Permission Rationale - Start --> - - <!-- Title message shown for Permission Rationale dialog for Location permission [CHAR LIMIT=50] --> - <string name="permission_rationale_location_title">Data sharing and location</string> - - <!-- Title shown for Permission Rationale "where data sharing come from" section [CHAR LIMIT=70] --> - <string name="permission_rationale_data_sharing_source_title">Where data sharing info comes from</string> - - <!-- Message shown to the user letting them know where data usage information came from which - will map to app store labels/names." [CHAR LIMIT=200] --> - <string name="permission_rationale_data_sharing_source_message">The developer provided info to <annotation id="link"><annotation id="install_source" example="App Store">%1$s</annotation></annotation> about how this app shares data. It may update this info over time.</string> - - <!-- Title shown for Permission Rationale data sharing purposes section [CHAR LIMIT=50] --> - <string name="permission_rationale_location_purpose_title">The app may share location data for:</string> - - <!-- Message shown to the user letting them know that data will be shared and for which - purposes. Purposes will be in bullet list form and are one or many of the following: "app - functionality", "analytics", "developer communications", "advertising or marketing", - "fraud prevention, security, and compliance", "personalization", "account management". - [CHAR LIMIT=NONE] --> - <string name="permission_rationale_purpose_message"><annotation id="purpose_list" example="purpose 1, purpose 2, purpose 3">%1$s</annotation></string> - - <!-- Title shown for Permission Rationale "Data sharing varies" section [CHAR LIMIT=50] --> - <string name="permission_rationale_permission_data_sharing_varies_title">Data sharing varies</string> - - <!-- Message shown for Permission Rationale "Data sharing varies" section [CHAR LIMIT=200] --> - <string name="permission_rationale_data_sharing_varies_message">Data practices may vary based on your app version, use, region, and age. <annotation id="link">More about data sharing</annotation></string> - - <!-- Title shown for Permission Rationale location permission settings section [CHAR LIMIT=40] --> - <string name="permission_rationale_location_settings_title">Your location data</string> - - <!-- Message shown to the user letting them where to change permission settings in the future [CHAR LIMIT=100] --> - <string name="permission_rationale_permission_settings_message">Change this app’s access in <annotation id="link">privacy settings</annotation></string> - - <!-- Permission usage purpose shown for Permission Rationale. This will be used with the - permission_rationale_purpose_message string in for of bullet list. [CHAR LIMIT=30] --> - <string name="permission_rationale_purpose_app_functionality">App functionality</string> - - <!-- Permission usage purpose shown for Permission Rationale. This will be used with the - permission_rationale_purpose_message string in for of bullet list. [CHAR LIMIT=30] --> - <string name="permission_rationale_purpose_analytics">Analytics</string> - - <!-- Permission usage purpose shown for Permission Rationale. This will be used with the - permission_rationale_purpose_message string in for of bullet list. [CHAR LIMIT=50] --> - <string name="permission_rationale_purpose_developer_communications">Developer communications</string> - - <!-- Permission usage purpose shown for Permission Rationale. This will be used with the - permission_rationale_purpose_message string in for of bullet list. [CHAR LIMIT=50] --> - <string name="permission_rationale_purpose_advertising">Advertising or marketing</string> - - <!-- Permission usage purpose shown for Permission Rationale. This will be used with the - permission_rationale_purpose_message string in for of bullet list. [CHAR LIMIT=75] --> - <string name="permission_rationale_purpose_fraud_prevention_security">Fraud prevention, security, and compliance</string> - - <!-- Permission usage purpose shown for Permission Rationale. This will be used with the - permission_rationale_purpose_message string in for of bullet list. [CHAR LIMIT=50] --> - <string name="permission_rationale_purpose_personalization">Personalization</string> - - <!-- Permission usage purpose shown for Permission Rationale. This will be used with the - permission_rationale_purpose_message string in for of bullet list. [CHAR LIMIT=50] --> - <string name="permission_rationale_purpose_account_management">Account management</string> - - <!-- Text for an app's permission rationale header [CHAR LIMIT=60]--> - <string name="app_permission_rationale_message">Data safety</string> - - <!-- Text for linking to an app's location permission rationale dialog. [CHAR LIMIT=60] --> - <string name="app_location_permission_rationale_title">Location data may be shared</string> - - <!-- Description for the app's location permission rationale dialog content. [CHAR LIMIT=130] --> - <string name="app_location_permission_rationale_subtitle">This app stated it may share your location data with third parties</string> - - <!-- Permission Rationale - End --> - - <!-- Safety Label Change Notifications Start --> - - <!-- Title for Data Sharing updates page, which displays updates from apps on their data sharing - practices. Also used as the title for Data Sharing updates page entry point from Safety Center. - [CHAR LIMIT=70] --> - <string name="data_sharing_updates_title">Data sharing updates for location</string> - <!-- Summary for Data Sharing updates page entry point in Safety Center. Clicking on the entry - will navigate to Data Sharing updates page, which displays updates from apps on their data - sharing practices. [CHAR LIMIT=130] --> - <string name="data_sharing_updates_summary">Review apps that changed the way they may share your location data</string> - <!-- Subtitle for Data Sharing updates page, which displays updates from apps on their data - sharing practices. [CHAR LIMIT=320] --> - <string name="data_sharing_updates_subtitle">These apps have changed the way they may share your location data. They may not have shared it before, or may now share it for advertising or marketing purposes.</string> - <!-- Footer message for Data Sharing updates page, which displays updates from apps on their - data sharing practices. [CHAR LIMIT=420] --> - <string name="data_sharing_updates_footer_message">The developers of these apps provided info about their data sharing practices to an app store. They may update it over time.\n\nData sharing practices may vary based on your app version, use, region, and age.</string> - <!-- Link for Data Sharing Help Center page, which will open a web page detailing what app data - sharing policies mean. [CHAR LIMIT=60] --> - <string name="learn_about_data_sharing">Learn about data sharing</string> - <!-- Message indicating that an app now shares location data with third parties for the purpose - of advertising, when it earlier did not share location data for the purpose of advertising, but - did for other purposes. Third parties are other companies or organizations. - [CHAR LIMIT=100] --> - <string name="shares_location_with_third_parties">Your location data is now shared with third parties</string> - <!-- Message indicating that an app now shares location data with third parties for the purpose - of advertising, when it earlier did not. Third parties are other companies or organizations. - [CHAR LIMIT=160] --> - <string name="shares_location_with_third_parties_for_advertising">Your location data is now shared with third parties for advertising or marketing</string> - <!-- Header for the number of updates in the last days. [CHAR LIMIT=40] --> - <!-- TODO(b/261914980): Update with final strings for 0 and 1 case. --> - <string name="updated_in_last_days">{count, plural, - =0 {Updated within the last day} - =1 {Updated within the last day} - other {Updated within # days} - }</string> - <!-- Message indicating that no apps have provided recent updates. [CHAR LIMIT=100] --> - <string name="no_updates_at_this_time">No updates at this time</string> - - <!-- Notification title for safety label changes notification [CHAR LIMIT=70] --> - <string name="safety_label_changes_notification_title">Data sharing updates</string> - <!-- Notification description for safety label changes notification [CHAR LIMIT=120] --> - <string name="safety_label_changes_notification_desc">Some apps changed the way they may share your location data</string> - - <!-- Content description for the Gear icon --> - <string name="safety_label_changes_gear_description">Settings</string> - - <!-- Safety Label Change Notifications End --> - <!-- String representing the security and privacy brand name [CHAR LIMIT=NONE] --> <string name="security_privacy_brand_name">Security & privacy</string> <!-- Header for the controls category on the privacy subpage of Safety Center [CHAR LIMIT=NONE] --> diff --git a/PermissionController/res/values/strings.xml b/PermissionController/res/values/strings.xml index 8bf0f7373..213d03801 100644 --- a/PermissionController/res/values/strings.xml +++ b/PermissionController/res/values/strings.xml @@ -1734,6 +1734,11 @@ Allow <xliff:g id="app_name" example="Gmail">%4$s</xliff:g> to upload a bug repo <!-- Summary for toggle controlling whether to show the first letter while typing passwords. [CHAR LIMIT=NONE] --> <string name="show_password_summary">Display characters briefly as you type</string> + <!-- Link for Safety Label Data Sharing help center page. [CHAR LIMIT=NONE]--> + <string name="data_sharing_help_center_link" translatable="false">https://support.google.com/android?p=data_sharing</string> + + <!-- Permission Rationale - Start --> + <!-- Permission rationale message when an app requests a location permission. Third parties are other organizations outside of the app developer. These could be companies or even governmental organizations. But because we aren't able to be inclusive of all possibilities, phrasing should @@ -1741,4 +1746,122 @@ Allow <xliff:g id="app_name" example="Gmail">%4$s</xliff:g> to upload a bug repo with the developer company. [CHAR LIMIT=130] --> <string name="permission_rationale_message_location">This app stated it may share your location data with third parties</string> + <!-- Title message shown for Permission Rationale dialog for Location permission [CHAR LIMIT=50] --> + <string name="permission_rationale_location_title">Data sharing and location</string> + + <!-- Title shown for Permission Rationale "where data sharing come from" section [CHAR LIMIT=70] --> + <string name="permission_rationale_data_sharing_source_title">Where data sharing info comes from</string> + + <!-- Message shown to the user letting them know where data usage information came from which + will map to app store labels/names." [CHAR LIMIT=200] --> + <string name="permission_rationale_data_sharing_source_message">The developer provided info to <annotation id="link"><annotation id="install_source" example="App Store">%1$s</annotation></annotation> about how this app shares data. It may update this info over time.</string> + + <!-- Title shown for Permission Rationale data sharing purposes section [CHAR LIMIT=50] --> + <string name="permission_rationale_location_purpose_title">The app may share location data for:</string> + + <!-- Message shown to the user letting them know that data will be shared and for which + purposes. Purposes will be in bullet list form and are one or many of the following: "app + functionality", "analytics", "developer communications", "advertising or marketing", + "fraud prevention, security, and compliance", "personalization", "account management". + [CHAR LIMIT=NONE] --> + <string name="permission_rationale_purpose_message"><annotation id="purpose_list" example="purpose 1, purpose 2, purpose 3">%1$s</annotation></string> + + <!-- Title shown for Permission Rationale "Data sharing varies" section [CHAR LIMIT=50] --> + <string name="permission_rationale_permission_data_sharing_varies_title">Data sharing varies</string> + + <!-- Message shown for Permission Rationale "Data sharing varies" section [CHAR LIMIT=200] --> + <string name="permission_rationale_data_sharing_varies_message">Data practices may vary based on your app version, use, region, and age. <annotation id="link">More about data sharing</annotation></string> + + <!-- Title shown for Permission Rationale location permission settings section [CHAR LIMIT=40] --> + <string name="permission_rationale_location_settings_title">Your location data</string> + + <!-- Message shown to the user letting them where to change permission settings in the future [CHAR LIMIT=100] --> + <string name="permission_rationale_permission_settings_message">Change this app’s access in <annotation id="link">privacy settings</annotation></string> + + <!-- Permission usage purpose shown for Permission Rationale. This will be used with the + permission_rationale_purpose_message string in for of bullet list. [CHAR LIMIT=30] --> + <string name="permission_rationale_purpose_app_functionality">App functionality</string> + + <!-- Permission usage purpose shown for Permission Rationale. This will be used with the + permission_rationale_purpose_message string in for of bullet list. [CHAR LIMIT=30] --> + <string name="permission_rationale_purpose_analytics">Analytics</string> + + <!-- Permission usage purpose shown for Permission Rationale. This will be used with the + permission_rationale_purpose_message string in for of bullet list. [CHAR LIMIT=50] --> + <string name="permission_rationale_purpose_developer_communications">Developer communications</string> + + <!-- Permission usage purpose shown for Permission Rationale. This will be used with the + permission_rationale_purpose_message string in for of bullet list. [CHAR LIMIT=50] --> + <string name="permission_rationale_purpose_advertising">Advertising or marketing</string> + + <!-- Permission usage purpose shown for Permission Rationale. This will be used with the + permission_rationale_purpose_message string in for of bullet list. [CHAR LIMIT=75] --> + <string name="permission_rationale_purpose_fraud_prevention_security">Fraud prevention, security, and compliance</string> + + <!-- Permission usage purpose shown for Permission Rationale. This will be used with the + permission_rationale_purpose_message string in for of bullet list. [CHAR LIMIT=50] --> + <string name="permission_rationale_purpose_personalization">Personalization</string> + + <!-- Permission usage purpose shown for Permission Rationale. This will be used with the + permission_rationale_purpose_message string in for of bullet list. [CHAR LIMIT=50] --> + <string name="permission_rationale_purpose_account_management">Account management</string> + + <!-- Text for an app's permission rationale header [CHAR LIMIT=60]--> + <string name="app_permission_rationale_message">Data safety</string> + + <!-- Text for linking to an app's location permission rationale dialog. [CHAR LIMIT=60] --> + <string name="app_location_permission_rationale_title">Location data may be shared</string> + + <!-- Description for the app's location permission rationale dialog content. [CHAR LIMIT=130] --> + <string name="app_location_permission_rationale_subtitle">This app stated it may share your location data with third parties</string> + + <!-- Permission Rationale - End --> + + <!-- Safety Label Change Notifications Start --> + + <!-- Title for Data Sharing updates page, which displays updates from apps on their data sharing + practices. Also used as the title for Data Sharing updates page entry point from Safety Center. + [CHAR LIMIT=70] --> + <string name="data_sharing_updates_title">Data sharing updates for location</string> + <!-- Summary for Data Sharing updates page entry point in Safety Center. Clicking on the entry + will navigate to Data Sharing updates page, which displays updates from apps on their data + sharing practices. [CHAR LIMIT=130] --> + <string name="data_sharing_updates_summary">Review apps that changed the way they may share your location data</string> + <!-- Subtitle for Data Sharing updates page, which displays updates from apps on their data + sharing practices. [CHAR LIMIT=320] --> + <string name="data_sharing_updates_subtitle">These apps have changed the way they may share your location data. They may not have shared it before, or may now share it for advertising or marketing purposes.</string> + <!-- Footer message for Data Sharing updates page, which displays updates from apps on their + data sharing practices. [CHAR LIMIT=420] --> + <string name="data_sharing_updates_footer_message">The developers of these apps provided info about their data sharing practices to an app store. They may update it over time.\n\nData sharing practices may vary based on your app version, use, region, and age.</string> + <!-- Link for Data Sharing Help Center page, which will open a web page detailing what app data + sharing policies mean. [CHAR LIMIT=60] --> + <string name="learn_about_data_sharing">Learn about data sharing</string> + <!-- Message indicating that an app now shares location data with third parties for the purpose + of advertising, when it earlier did not share location data for the purpose of advertising, but + did for other purposes. Third parties are other companies or organizations. + [CHAR LIMIT=100] --> + <string name="shares_location_with_third_parties">Your location data is now shared with third parties</string> + <!-- Message indicating that an app now shares location data with third parties for the purpose + of advertising, when it earlier did not. Third parties are other companies or organizations. + [CHAR LIMIT=160] --> + <string name="shares_location_with_third_parties_for_advertising">Your location data is now shared with third parties for advertising or marketing</string> + <!-- Header for the number of updates in the last days. [CHAR LIMIT=40] --> + <!-- TODO(b/261914980): Update with final strings for 0 and 1 case. --> + <string name="updated_in_last_days">{count, plural, + =0 {Updated within the last day} + =1 {Updated within the last day} + other {Updated within # days} + }</string> + <!-- Message indicating that no apps have provided recent updates. [CHAR LIMIT=100] --> + <string name="no_updates_at_this_time">No updates at this time</string> + + <!-- Notification title for safety label changes notification [CHAR LIMIT=70] --> + <string name="safety_label_changes_notification_title">Data sharing updates</string> + <!-- Notification description for safety label changes notification [CHAR LIMIT=120] --> + <string name="safety_label_changes_notification_desc">Some apps changed the way they may share your location data</string> + + <!-- Content description for the Gear icon --> + <string name="safety_label_changes_gear_description">Settings</string> + + <!-- Safety Label Change Notifications End --> </resources> diff --git a/SafetyCenter/Config/Android.bp b/SafetyCenter/Config/Android.bp index bbf0a8008..895b242b0 100644 --- a/SafetyCenter/Config/Android.bp +++ b/SafetyCenter/Config/Android.bp @@ -35,9 +35,11 @@ java_library { "androidx.annotation_annotation", "framework-annotations-lib", "framework-permission-s", - "modules-utils-build", "safety-center-annotations", ], + static_libs: [ + "modules-utils-build", + ], apex_available: [ "com.android.permission", "test_com.android.permission", diff --git a/SafetyCenter/ConfigLintChecker/java/android/util/ArraySet.java b/SafetyCenter/ConfigLintChecker/java/android/util/ArraySet.java index cfc864c65..2b5c5669a 100644 --- a/SafetyCenter/ConfigLintChecker/java/android/util/ArraySet.java +++ b/SafetyCenter/ConfigLintChecker/java/android/util/ArraySet.java @@ -16,6 +16,8 @@ package android.util; +import java.util.Arrays; +import java.util.Collection; import java.util.HashSet; /** @@ -25,4 +27,16 @@ import java.util.HashSet; * * @param <E> the type of elements maintained by this set */ -public final class ArraySet<E> extends HashSet<E> {} +public final class ArraySet<E> extends HashSet<E> { + public ArraySet() { + super(); + } + + public ArraySet(Collection<? extends E> c) { + super(c); + } + + public ArraySet(E[] array) { + super(Arrays.stream(array).toList()); + } +} diff --git a/SafetyCenter/PendingIntents/Android.bp b/SafetyCenter/PendingIntents/Android.bp index f01a7fc5e..05bec7988 100644 --- a/SafetyCenter/PendingIntents/Android.bp +++ b/SafetyCenter/PendingIntents/Android.bp @@ -24,9 +24,11 @@ java_library { ], libs: [ "androidx.annotation_annotation", - "modules-utils-build_system", "safety-center-annotations", ], + static_libs: [ + "modules-utils-build_system", + ], apex_available: [ "com.android.permission", "test_com.android.permission", diff --git a/SafetyCenter/Resources/res/values/config.xml b/SafetyCenter/Resources/res/values/config.xml index 8ffc89c5a..82a3cdcc8 100644 --- a/SafetyCenter/Resources/res/values/config.xml +++ b/SafetyCenter/Resources/res/values/config.xml @@ -17,7 +17,7 @@ <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <!-- Comma separated list of safety source IDs to show in the same task as the safety center --> - <string name="config_same_task_safety_source_ids" translatable="false">AndroidAccessibility,AndroidAdvancedPrivacy,AndroidAdvancedSecurity,AndroidBackgroundLocation,AndroidBiometrics,AndroidLockScreen,AndroidNotificationListener,AndroidPermissionAutoRevoke,AndroidPermissionManager,AndroidPermissionUsage,AndroidPrivacyControls,AndroidWorkPolicyInfo</string> + <string name="config_same_task_safety_source_ids" translatable="false">AndroidAccessibility,AndroidAdsPrivacy,AndroidAdvancedPrivacy,AndroidAdvancedSecurity,AndroidBackgroundLocation,AndroidBiometrics,AndroidLockScreen,AndroidNotificationListener,AndroidPermissionAutoRevoke,AndroidPermissionManager,AndroidPermissionUsage,AndroidPrivacyAppDataSharingUpdates,AndroidPrivacyControls,AndroidWorkPolicyInfo</string> <!-- Comma separated list of packages that are pregranted to enable a notification listener service. For T- platforms.--> <string name="config_NotificationListenerServicePregrants" translatable="false"></string> <!-- Comma separated list of safety source IDs to add an Intent Extra confirming they should be displayed as if opened by a settings UI page. --> diff --git a/framework-s/Android.bp b/framework-s/Android.bp index 2199553e8..90baed8f0 100644 --- a/framework-s/Android.bp +++ b/framework-s/Android.bp @@ -47,9 +47,6 @@ java_library { "framework-annotations-lib", "unsupportedappusage", ], - static_libs: [ - "modules-utils-build", - ], apex_available: [ "com.android.permission", "test_com.android.permission", @@ -71,6 +68,7 @@ java_sdk_library { ], static_libs: [ "framework-permission-s-shared", + "modules-utils-build", ], apex_available: [ "com.android.permission", @@ -83,7 +81,7 @@ java_sdk_library { "//packages/modules/Permission:__subpackages__", ], installable: true, - jarjar_rules: ":permission-jarjar-rules", + jarjar_rules: "jarjar-rules.txt", lint: { strict_updatability_linting: true, }, @@ -93,8 +91,6 @@ java_sdk_library { "android.app.role", "android.safetycenter", "android.safetylabel", - // For com.android.permission.jarjar. - "com.android.permission", ], } diff --git a/framework-s/jarjar-rules.txt b/framework-s/jarjar-rules.txt new file mode 100644 index 000000000..3b888fe99 --- /dev/null +++ b/framework-s/jarjar-rules.txt @@ -0,0 +1,4 @@ +rule android.os.HandlerExecutor android.permission.jarjar.@0 +rule android.util.IndentingPrintWriter android.permission.jarjar.@0 +rule com.android.internal.** android.permission.jarjar.@0 +rule com.android.modules.** android.permission.jarjar.@0 diff --git a/service/Android.bp b/service/Android.bp index c80044fac..11ff2c148 100644 --- a/service/Android.bp +++ b/service/Android.bp @@ -51,6 +51,8 @@ java_library { srcs: [":service-permission-shared-srcs"], libs: [ "framework-annotations-lib", + ], + static_libs: [ "framework-permission-s-shared", ], apex_available: [ @@ -97,6 +99,7 @@ java_sdk_library { "kotlin-stdlib", "modules-utils-backgroundthread", "modules-utils-binary-xml", + "modules-utils-build", "modules-utils-os", "safety-center-config", "safety-center-internal-data", @@ -111,7 +114,7 @@ java_sdk_library { javacflags: ["-Xep:GuardedBy:ERROR"], }, exclude_kotlinc_generated_files: true, - jarjar_rules: ":permission-jarjar-rules", + jarjar_rules: "jarjar-rules.txt", kotlincflags: [ "-Xjvm-default=all", "-Xno-call-assertions", diff --git a/jarjar-rules.txt b/service/jarjar-rules.txt index 2b8765cf5..2b8765cf5 100644 --- a/jarjar-rules.txt +++ b/service/jarjar-rules.txt diff --git a/service/java/com/android/safetycenter/SafetyCenterFlags.java b/service/java/com/android/safetycenter/SafetyCenterFlags.java index bd1380fa9..de0593b56 100644 --- a/service/java/com/android/safetycenter/SafetyCenterFlags.java +++ b/service/java/com/android/safetycenter/SafetyCenterFlags.java @@ -99,18 +99,36 @@ public final class SafetyCenterFlags { private static final String PROPERTY_ADDITIONAL_ALLOW_PACKAGE_CERTS = "safety_center_additional_allow_package_certs"; - private static final Duration REFRESH_SOURCES_TIMEOUT_DEFAULT_DURATION = Duration.ofSeconds(15); + private static final Duration FGS_ALLOWLIST_DEFAULT_DURATION = Duration.ofSeconds(20); + + private static final String PROPERTY_TEMP_HIDDEN_ISSUE_RESURFACE_DELAY_MILLIS = + "safety_center_temp_hidden_issue_resurface_delay_millis"; private static final Duration RESOLVING_ACTION_TIMEOUT_DEFAULT_DURATION = Duration.ofSeconds(10); - private static final Duration FGS_ALLOWLIST_DEFAULT_DURATION = Duration.ofSeconds(20); + private static final Duration NOTIFICATIONS_MIN_DELAY_DEFAULT_DURATION = Duration.ofDays(180); + + private static final String ISSUE_CATEGORY_ALLOWLIST_DEFAULT = ""; - private static final long RESURFACE_ISSUE_DEFAULT_MAX_COUNT = 0; + private static final String REFRESH_SOURCES_TIMEOUT_DEFAULT = + "100:15000,200:60000,300:30000,400:30000,500:30000,600:3600000"; + private static final Duration REFRESH_SOURCES_TIMEOUT_DEFAULT_DURATION = Duration.ofSeconds(15); - private static final Duration RESURFACE_ISSUE_DEFAULT_DELAY = Duration.ofDays(180); + private static final String RESURFACE_ISSUE_MAX_COUNT_DEFAULT = "200:0,300:1,400:1"; + private static final long RESURFACE_ISSUE_MAX_COUNT_DEFAULT_COUNT = 0; - private static final Duration NOTIFICATIONS_MIN_DELAY_DEFAULT_DURATION = Duration.ofDays(180); + private static final String RESURFACE_ISSUE_DELAYS_DEFAULT = ""; + private static final Duration RESURFACE_ISSUE_DELAYS_DEFAULT_DURATION = Duration.ofDays(180); + + private static final String UNTRACKED_SOURCES_DEFAULT = + "AndroidAccessibility,AndroidBackgroundLocation," + + "AndroidNotificationListener,AndroidPermissionAutoRevoke"; + + private static final String BACKGROUND_REFRESH_DENY_DEFAULT = ""; + + private static final Duration TEMP_HIDDEN_ISSUE_RESURFACE_DELAY_DEFAULT_DURATION = + Duration.ofDays(2); /** Dumps state for debugging purposes. */ static void dump(PrintWriter fout) { @@ -247,7 +265,7 @@ public final class SafetyCenterFlags { * mid-rollout. Broadcasts are still sent to these sources. */ static ArraySet<String> getUntrackedSourceIds() { - return getCommaSeparatedStrings(PROPERTY_UNTRACKED_SOURCES); + return getCommaSeparatedStrings(PROPERTY_UNTRACKED_SOURCES, UNTRACKED_SOURCES_DEFAULT); } /** @@ -255,7 +273,8 @@ public final class SafetyCenterFlags { * will refresh these sources only on page open and when the scan button is clicked. */ static ArraySet<String> getBackgroundRefreshDeniedSourceIds() { - return getCommaSeparatedStrings(PROPERTY_BACKGROUND_REFRESH_DENIED_SOURCES); + return getCommaSeparatedStrings( + PROPERTY_BACKGROUND_REFRESH_DENIED_SOURCES, BACKGROUND_REFRESH_DENY_DEFAULT); } /** @@ -278,7 +297,7 @@ public final class SafetyCenterFlags { * a refresh. */ private static String getRefreshSourcesTimeoutsMillis() { - return getString(PROPERTY_REFRESH_SOURCES_TIMEOUTS_MILLIS, ""); + return getString(PROPERTY_REFRESH_SOURCES_TIMEOUTS_MILLIS, REFRESH_SOURCES_TIMEOUT_DEFAULT); } /** @@ -292,7 +311,7 @@ public final class SafetyCenterFlags { if (maxCount != null) { return maxCount; } - return RESURFACE_ISSUE_DEFAULT_MAX_COUNT; + return RESURFACE_ISSUE_MAX_COUNT_DEFAULT_COUNT; } /** @@ -301,7 +320,7 @@ public final class SafetyCenterFlags { * this {@link SafetySourceData.SeverityLevel} should be resurfaced. */ private static String getResurfaceIssueMaxCounts() { - return getString(PROPERTY_RESURFACE_ISSUE_MAX_COUNTS, ""); + return getString(PROPERTY_RESURFACE_ISSUE_MAX_COUNTS, RESURFACE_ISSUE_MAX_COUNT_DEFAULT); } /** @@ -317,7 +336,7 @@ public final class SafetyCenterFlags { if (delayMillis != null) { return Duration.ofMillis(delayMillis); } - return RESURFACE_ISSUE_DEFAULT_DELAY; + return RESURFACE_ISSUE_DELAYS_DEFAULT_DURATION; } /** @@ -328,7 +347,14 @@ public final class SafetyCenterFlags { * should be resurfaced. */ private static String getResurfaceIssueDelaysMillis() { - return getString(PROPERTY_RESURFACE_ISSUE_DELAYS_MILLIS, ""); + return getString(PROPERTY_RESURFACE_ISSUE_DELAYS_MILLIS, RESURFACE_ISSUE_DELAYS_DEFAULT); + } + + /** Returns a duration after which a temporarily hidden issue will resurface. */ + public static Duration getTemporarilyHiddenIssueResurfaceDelay() { + return getDuration( + PROPERTY_TEMP_HIDDEN_ISSUE_RESURFACE_DELAY_MILLIS, + TEMP_HIDDEN_ISSUE_RESURFACE_DELAY_DEFAULT_DURATION); } /** @@ -368,7 +394,7 @@ public final class SafetyCenterFlags { * of IDs of safety sources that are allowed to send issues with this category. */ private static String getIssueCategoryAllowlists() { - return getString(PROPERTY_ISSUE_CATEGORY_ALLOWLISTS, ""); + return getString(PROPERTY_ISSUE_CATEGORY_ALLOWLISTS, ISSUE_CATEGORY_ALLOWLIST_DEFAULT); } private static String getAdditionalAllowedPackageCertsString() { @@ -422,7 +448,11 @@ public final class SafetyCenterFlags { } private static ArraySet<String> getCommaSeparatedStrings(String property) { - return new ArraySet<>(getString(property, "").split(",")); + return getCommaSeparatedStrings(property, ""); + } + + private static ArraySet<String> getCommaSeparatedStrings(String property, String defaultValue) { + return new ArraySet<>(getString(property, defaultValue).split(",")); } private static String getString(String property, String defaultValue) { diff --git a/service/java/com/android/safetycenter/data/SafetyCenterDataManager.java b/service/java/com/android/safetycenter/data/SafetyCenterDataManager.java index 9f6ffc257..b40caf540 100644 --- a/service/java/com/android/safetycenter/data/SafetyCenterDataManager.java +++ b/service/java/com/android/safetycenter/data/SafetyCenterDataManager.java @@ -92,6 +92,7 @@ public final class SafetyCenterDataManager { context, mSafetySourceDataRepository, safetyCenterConfigReader, + mSafetyCenterIssueDismissalRepository, SdkLevel.isAtLeastU() ? new SafetyCenterIssueDeduplicator( mSafetyCenterIssueDismissalRepository) diff --git a/service/java/com/android/safetycenter/data/SafetyCenterIssueDeduplicator.java b/service/java/com/android/safetycenter/data/SafetyCenterIssueDeduplicator.java index 07aefdf16..30dd1bce8 100644 --- a/service/java/com/android/safetycenter/data/SafetyCenterIssueDeduplicator.java +++ b/service/java/com/android/safetycenter/data/SafetyCenterIssueDeduplicator.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Log; import androidx.annotation.RequiresApi; @@ -40,6 +41,8 @@ import javax.annotation.concurrent.NotThreadSafe; @NotThreadSafe final class SafetyCenterIssueDeduplicator { + private static final String TAG = "SafetyCenterDedup"; + private final SafetyCenterIssueDismissalRepository mSafetyCenterIssueDismissalRepository; SafetyCenterIssueDeduplicator( @@ -74,14 +77,37 @@ final class SafetyCenterIssueDeduplicator { ArraySet<SafetyCenterIssueKey> duplicatesToFilterOut = getDuplicatesToFilterOut(dedupBuckets); + resurfaceHiddenIssuesIfNeeded(dedupBuckets); + if (duplicatesToFilterOut.isEmpty()) { return; } Iterator<SafetySourceIssueInfo> it = sortedIssues.iterator(); while (it.hasNext()) { - if (duplicatesToFilterOut.contains(it.next().getSafetyCenterIssueKey())) { + SafetyCenterIssueKey issueKey = it.next().getSafetyCenterIssueKey(); + if (duplicatesToFilterOut.contains(issueKey)) { it.remove(); + // mark as temporarily hidden, which will delay showing these issues if the top + // issue gets resolved. + mSafetyCenterIssueDismissalRepository.hideIssue(issueKey); + } + } + } + + private void resurfaceHiddenIssuesIfNeeded( + ArrayMap<DeduplicationKey, List<SafetySourceIssueInfo>> dedupBuckets) { + for (int i = 0; i < dedupBuckets.size(); i++) { + List<SafetySourceIssueInfo> duplicates = dedupBuckets.valueAt(i); + if (duplicates.isEmpty()) { + Log.w(TAG, "List of duplicates in a dedupBucket is empty"); + continue; + } + + // top issue in the bucket, if hidden, should resurface after certain period + SafetyCenterIssueKey topIssueKey = duplicates.get(0).getSafetyCenterIssueKey(); + if (mSafetyCenterIssueDismissalRepository.isIssueHidden(topIssueKey)) { + mSafetyCenterIssueDismissalRepository.resurfaceHiddenIssueAfterPeriod(topIssueKey); } } } @@ -196,15 +222,17 @@ final class SafetyCenterIssueDeduplicator { } /** Returns a set of duplicate issues that need to be filtered out. */ - private static ArraySet<SafetyCenterIssueKey> getDuplicatesToFilterOut( + private ArraySet<SafetyCenterIssueKey> getDuplicatesToFilterOut( ArrayMap<DeduplicationKey, List<SafetySourceIssueInfo>> dedupBuckets) { ArraySet<SafetyCenterIssueKey> duplicatesToFilterOut = new ArraySet<>(); for (int i = 0; i < dedupBuckets.size(); i++) { List<SafetySourceIssueInfo> duplicates = dedupBuckets.valueAt(i); + // all but the top one in the bucket for (int j = 1; j < duplicates.size(); j++) { - duplicatesToFilterOut.add(duplicates.get(j).getSafetyCenterIssueKey()); + SafetyCenterIssueKey issueKey = duplicates.get(j).getSafetyCenterIssueKey(); + duplicatesToFilterOut.add(issueKey); } } diff --git a/service/java/com/android/safetycenter/data/SafetyCenterIssueDismissalRepository.java b/service/java/com/android/safetycenter/data/SafetyCenterIssueDismissalRepository.java index 8ec91da73..0d6e99d38 100644 --- a/service/java/com/android/safetycenter/data/SafetyCenterIssueDismissalRepository.java +++ b/service/java/com/android/safetycenter/data/SafetyCenterIssueDismissalRepository.java @@ -273,6 +273,62 @@ final class SafetyCenterIssueDismissalRepository { } } + /** Returns whether the issue is currently hidden. */ + boolean isIssueHidden(SafetyCenterIssueKey safetyCenterIssueKey) { + IssueData issueData = getOrWarn(safetyCenterIssueKey, "checking if issue hidden"); + if (issueData == null || !issueData.isHidden()) { + return false; + } + + Instant timerStart = issueData.getResurfaceTimerStartTime(); + if (timerStart == null) { + return true; + } + + Duration delay = SafetyCenterFlags.getTemporarilyHiddenIssueResurfaceDelay(); + Duration timeSinceTimerStarted = Duration.between(timerStart, Instant.now()); + boolean isTimeToResurface = timeSinceTimerStarted.compareTo(delay) >= 0; + + if (isTimeToResurface) { + issueData.setHidden(false); + issueData.setResurfaceTimerStartTime(null); + return false; + } + return true; + } + + /** Hides the issue with the given {@link SafetyCenterIssueKey}. */ + void hideIssue(SafetyCenterIssueKey safetyCenterIssueKey) { + IssueData issueData = getOrWarn(safetyCenterIssueKey, "hiding issue"); + if (issueData != null) { + issueData.setHidden(true); + // to abide by the method was called last: hideIssue or resurfaceHiddenIssueAfterPeriod + issueData.setResurfaceTimerStartTime(null); + } + } + + /** + * The issue with the given {@link SafetyCenterIssueKey} will be resurfaced (marked as not + * hidden) after a period of time defined by {@link + * SafetyCenterFlags#getTemporarilyHiddenIssueResurfaceDelay()}, such that {@link + * SafetyCenterIssueDismissalRepository#isIssueHidden} will start returning {@code false} for + * the given issue. + * + * <p>If this method is called multiple times in a row, the period will be set by the first call + * and all following calls won't have any effect. + */ + void resurfaceHiddenIssueAfterPeriod(SafetyCenterIssueKey safetyCenterIssueKey) { + IssueData issueData = getOrWarn(safetyCenterIssueKey, "resurfaceIssueAfterPeriod"); + if (issueData == null) { + return; + } + + // if timer already started, we don't want to restart + if (issueData.getResurfaceTimerStartTime() == null) { + issueData.setResurfaceTimerStartTime(Instant.now()); + } + } + /** Takes a snapshot of the contents of the repository to be written to persistent storage. */ private List<PersistedSafetyCenterIssue> snapshot() { List<PersistedSafetyCenterIssue> persistedIssues = new ArrayList<>(); @@ -446,6 +502,11 @@ final class SafetyCenterIssueDismissalRepository { @Nullable private Instant mNotificationDismissedAt; + // TODO(b/270015734): maybe persist those as well + private boolean mHidden = false; + // Moment when a theoretical timer starts - when it ends the issue gets unmarked as hidden. + @Nullable private Instant mResurfaceTimerStartTime; + private IssueData(Instant firstSeenAt) { mFirstSeenAt = firstSeenAt; } @@ -480,6 +541,23 @@ final class SafetyCenterIssueDismissalRepository { mNotificationDismissedAt = notificationDismissedAt; } + private boolean isHidden() { + return mHidden; + } + + private void setHidden(boolean hidden) { + mHidden = hidden; + } + + @Nullable + private Instant getResurfaceTimerStartTime() { + return mResurfaceTimerStartTime; + } + + private void setResurfaceTimerStartTime(@Nullable Instant resurfaceTimerStartTime) { + this.mResurfaceTimerStartTime = resurfaceTimerStartTime; + } + private PersistedSafetyCenterIssue.Builder toPersistedIssueBuilder() { return new PersistedSafetyCenterIssue.Builder() .setFirstSeenAt(mFirstSeenAt) diff --git a/service/java/com/android/safetycenter/data/SafetyCenterIssueRepository.java b/service/java/com/android/safetycenter/data/SafetyCenterIssueRepository.java index aa2feebf9..a0746271f 100644 --- a/service/java/com/android/safetycenter/data/SafetyCenterIssueRepository.java +++ b/service/java/com/android/safetycenter/data/SafetyCenterIssueRepository.java @@ -18,9 +18,6 @@ package com.android.safetycenter.data; import static android.os.Build.VERSION_CODES.TIRAMISU; -import static java.util.Collections.emptyList; -import static java.util.Collections.unmodifiableList; - import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.Context; @@ -63,11 +60,13 @@ final class SafetyCenterIssueRepository { private final Context mContext; private final SafetySourceDataRepository mSafetySourceDataRepository; private final SafetyCenterConfigReader mSafetyCenterConfigReader; + private final SafetyCenterIssueDismissalRepository mSafetyCenterIssueDismissalRepository; // Only available on Android U+. @Nullable private final SafetyCenterIssueDeduplicator mSafetyCenterIssueDeduplicator; // userId -> sorted and deduplicated list of issues + // can contain temporarily hidden issues private final SparseArray<List<SafetySourceIssueInfo>> mUserIdToIssuesInfo = new SparseArray<>(); @@ -75,10 +74,12 @@ final class SafetyCenterIssueRepository { Context context, SafetySourceDataRepository safetySourceDataRepository, SafetyCenterConfigReader safetyCenterConfigReader, + SafetyCenterIssueDismissalRepository safetyCenterIssueDismissalRepository, @Nullable SafetyCenterIssueDeduplicator safetyCenterIssueDeduplicator) { mContext = context; mSafetySourceDataRepository = safetySourceDataRepository; mSafetyCenterConfigReader = safetyCenterConfigReader; + mSafetyCenterIssueDismissalRepository = safetyCenterIssueDismissalRepository; mSafetyCenterIssueDeduplicator = safetyCenterIssueDeduplicator; } @@ -107,7 +108,7 @@ final class SafetyCenterIssueRepository { List<SafetySourceIssueInfo> issues = getAllStoredIssuesFromRawSourceData(userId, isManagedProfile); processIssues(issues); - mUserIdToIssuesInfo.put(userId, unmodifiableList(issues)); + mUserIdToIssuesInfo.put(userId, issues); } /** @@ -144,9 +145,21 @@ final class SafetyCenterIssueRepository { return issueCount; } - /** Gets an unmodifiable list of all issues for the given {@code userId}. */ + /** Gets a list of all issues for the given {@code userId}. */ List<SafetySourceIssueInfo> getIssuesForUser(@UserIdInt int userId) { - return mUserIdToIssuesInfo.get(userId, emptyList()); + return filterOutHiddenIssues(mUserIdToIssuesInfo.get(userId, new ArrayList<>())); + } + + private List<SafetySourceIssueInfo> filterOutHiddenIssues(List<SafetySourceIssueInfo> issues) { + List<SafetySourceIssueInfo> result = new ArrayList<>(); + for (int i = 0; i < issues.size(); i++) { + SafetySourceIssueInfo issueInfo = issues.get(i); + if (!mSafetyCenterIssueDismissalRepository.isIssueHidden( + issueInfo.getSafetyCenterIssueKey())) { + result.add(issueInfo); + } + } + return result; } private void processIssues(List<SafetySourceIssueInfo> issuesInfo) { diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt index 14bfaae73..8d44f2736 100644 --- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt +++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt @@ -250,6 +250,7 @@ class SafetyCenterIssueTest { fun getGroupId_withVersionLessThanU_throwsUnsupportedOperationException() { // TODO(b/258228790): Remove after U is no longer in pre-release assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake") + assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream") val issue = SafetyCenterIssue.Builder("issue_id", "Everything's good", "Please acknowledge this") .build() @@ -274,6 +275,7 @@ class SafetyCenterIssueTest { fun setGroupId_withVersionLessThanU_throwsUnsupportedOperationException() { // TODO(b/258228790): Remove after U is no longer in pre-release assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake") + assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream") val exception = assertFailsWith(UnsupportedOperationException::class) { @@ -374,6 +376,7 @@ class SafetyCenterIssueTest { fun action_getConfirmationDialogDetails_withVersionLessThanU_throwsUnsupportedOperation() { // TODO(b/258228790): Remove after U is no longer in pre-release assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake") + assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream") assertFailsWith(UnsupportedOperationException::class) { action1.confirmationDialogDetails } } @@ -383,6 +386,7 @@ class SafetyCenterIssueTest { fun action_setConfirmationDialogDetails_withVersionLessThanU_throwsUnsupportedOperation() { // TODO(b/258228790): Remove after U is no longer in pre-release assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake") + assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream") assertFailsWith(UnsupportedOperationException::class) { SafetyCenterIssue.Action.Builder("action_id", "Action label", pendingIntent1) .setConfirmationDialogDetails( diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt index 5a33b3377..8c771770b 100644 --- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt +++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt @@ -1604,6 +1604,7 @@ class SafetyCenterManagerTest { fun refreshSafetySources_versionLessThanU_throwsUnsupportedOperationException() { // TODO(b/258228790): Remove after U is no longer in pre-release assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake") + assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream") safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig) val exception = diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceIssueTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceIssueTest.kt index cf9f37245..4749c3616 100644 --- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceIssueTest.kt +++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceIssueTest.kt @@ -122,6 +122,7 @@ class SafetySourceIssueTest { fun action_getConfirmationDialogDetails_withVersionLessThanU_throwsUnsupportedOperation() { // TODO(b/258228790): Remove after U is no longer in pre-release assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake") + assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream") val action = Action.Builder("action_id", "Action label", pendingIntent1).build() assertFailsWith(UnsupportedOperationException::class) { action.confirmationDialogDetails } @@ -132,6 +133,7 @@ class SafetySourceIssueTest { fun action_setConfirmationDialogDetails_withVersionLessThanU_throwsUnsupportedOperation() { // TODO(b/258228790): Remove after U is no longer in pre-release assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake") + assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream") assertFailsWith(UnsupportedOperationException::class) { Action.Builder("action_id", "Action label", pendingIntent1) .setConfirmationDialogDetails( @@ -673,6 +675,7 @@ class SafetySourceIssueTest { fun getAttributionTitle_withVersionLessThanU_throwsUnsupportedOperationException() { // TODO(b/258228790): Remove after U is no longer in pre-release assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake") + assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream") val safetySourceIssue = SafetySourceIssue.Builder( "Issue id", @@ -692,6 +695,7 @@ class SafetySourceIssueTest { fun setAttributionTitle_withVersionLessThanU_throwsUnsupportedOperationException() { // TODO(b/258228790): Remove after U is no longer in pre-release assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake") + assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream") val safetySourceIssueBuilder = SafetySourceIssue.Builder( "Issue id", @@ -919,6 +923,7 @@ class SafetySourceIssueTest { fun getDeduplicationId_withVersionLessThanU_throwsUnsupportedOperationException() { // TODO(b/258228790): Remove after U is no longer in pre-release assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake") + assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream") val safetySourceIssue = SafetySourceIssue.Builder( "Issue id", @@ -938,6 +943,7 @@ class SafetySourceIssueTest { fun setDeduplicationId_withVersionLessThanU_throwsUnsupportedOperationException() { // TODO(b/258228790): Remove after U is no longer in pre-release assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake") + assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream") val safetySourceIssueBuilder = SafetySourceIssue.Builder( "Issue id", @@ -1017,6 +1023,7 @@ class SafetySourceIssueTest { fun getCustomNotification_withVersionLessThanU_throwsUnsupportedOperationException() { // TODO(b/258228790): Remove after U is no longer in pre-release assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake") + assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream") val safetySourceIssue = SafetySourceIssue.Builder( "Issue id", @@ -1038,6 +1045,7 @@ class SafetySourceIssueTest { fun setCustomNotification_withVersionLessThanU_throwsUnsupportedOperationException() { // TODO(b/258228790): Remove after U is no longer in pre-release assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake") + assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream") val safetySourceIssueBuilder = SafetySourceIssue.Builder( "Issue id", @@ -1094,6 +1102,7 @@ class SafetySourceIssueTest { fun getNotificationBehavior_withVersionLessThanU_throwsUnsupportedOperationException() { // TODO(b/258228790): Remove after U is no longer in pre-release assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake") + assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream") val safetySourceIssue = SafetySourceIssue.Builder( "Issue id", @@ -1135,6 +1144,7 @@ class SafetySourceIssueTest { fun setNotificationBehavior_withVersionLessThanU_throwsUnsupportedOperationException() { // TODO(b/258228790): Remove after U is no longer in pre-release assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake") + assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream") val safetySourceIssueBuilder = SafetySourceIssue.Builder( "Issue id", @@ -1191,6 +1201,7 @@ class SafetySourceIssueTest { fun getIssueActionability_withVersionLessThanU_throwsUnsupportedOperationException() { // TODO(b/258228790): Remove after U is no longer in pre-release assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake") + assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream") val safetySourceIssue = SafetySourceIssue.Builder( "Issue id", @@ -1232,6 +1243,7 @@ class SafetySourceIssueTest { fun setIssueActionability_withVersionLessThanU_throwsUnsupportedOperationException() { // TODO(b/258228790): Remove after U is no longer in pre-release assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake") + assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream") val safetySourceIssueBuilder = SafetySourceIssue.Builder( "Issue id", @@ -1356,6 +1368,7 @@ class SafetySourceIssueTest { fun build_withUIssueCategoryValueOnT_throwsIllegalArgumentException() { // TODO(b/258228790): Remove after U is no longer in pre-release assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake") + assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream") val builder = SafetySourceIssue.Builder( "Issue id", diff --git a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt index 1f5cb72c6..900acf592 100644 --- a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt +++ b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt @@ -949,6 +949,7 @@ class SafetyCenterManagerTest { fun getSafetyCenterData_attributionNotSetBySourceOnTiramisu_returnsNullAttributionTitle() { // TODO(b/258228790): Remove after U is no longer in pre-release assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake") + assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream") safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig) safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, safetySourceTestData.informationWithIssue) @@ -1886,6 +1887,7 @@ class SafetyCenterManagerTest { @Test @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake") fun getSafetyCenterData_dupIssuesTopOneDismissedThenDisappears_bottomOneReemergesTimely() { + SafetyCenterFlags.tempHiddenIssueResurfaceDelay = Duration.ZERO SafetyCenterFlags.resurfaceIssueMaxCounts = mapOf(SEVERITY_LEVEL_CRITICAL_WARNING to 99L) SafetyCenterFlags.resurfaceIssueDelays = mapOf(SEVERITY_LEVEL_CRITICAL_WARNING to RESURFACE_DELAY) @@ -1939,6 +1941,7 @@ class SafetyCenterManagerTest { @Test @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake") fun getSafetyCenterData_dupsOfDiffSeveritiesTopOneDismissedThenGone_bottomOneReemergesTimely() { + SafetyCenterFlags.tempHiddenIssueResurfaceDelay = Duration.ZERO SafetyCenterFlags.resurfaceIssueMaxCounts = mapOf( SEVERITY_LEVEL_INFORMATION to 0L, @@ -2123,6 +2126,50 @@ class SafetyCenterManagerTest { } @Test + @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake") + fun getSafetyCenterData_dupIssuesTopOneResolved_bottomOneReemergesAfterTemporaryHiddenPeriod() { + SafetyCenterFlags.tempHiddenIssueResurfaceDelay = RESURFACE_DELAY + safetyCenterTestHelper.setConfig( + safetyCenterTestConfigs.multipleSourcesWithDeduplicationInfoConfig + ) + // Belongs to DEDUPLICATION_GROUP_1 + safetyCenterTestHelper.setData( + SOURCE_ID_1, + SafetySourceTestData.issuesOnly( + safetySourceTestData.criticalIssueWithDeduplicationId("same") + ) + ) + // Belongs to DEDUPLICATION_GROUP_1 + safetyCenterTestHelper.setData( + SOURCE_ID_5, + SafetySourceTestData.issuesOnly( + safetySourceTestData.criticalIssueWithDeduplicationId("same") + ) + ) + + val listener = safetyCenterTestHelper.addListener() + safetyCenterTestHelper.setData(SOURCE_ID_1, SafetySourceTestData.issuesOnly()) + + val apiSafetyCenterIssues = listener.receiveSafetyCenterData().issues + + assertThat(apiSafetyCenterIssues).isEmpty() + + waitForWithTimeout(timeout = RESURFACE_TIMEOUT, checkPeriod = RESURFACE_CHECK) { + val hasResurfaced = + safetyCenterManager + .getSafetyCenterDataWithPermission() + .issues + .contains( + safetyCenterTestData.safetyCenterIssueCritical( + SOURCE_ID_5, + groupId = MULTIPLE_SOURCES_GROUP_ID_2 + ) + ) + hasResurfaced + } + } + + @Test fun getSafetyCenterData_criticalDeviceIssues_returnsOverallStatusBasedOnAddIssueCallOrder() { safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig) safetyCenterTestHelper.setData( diff --git a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt index 75b74d36b..3e476f5c4 100644 --- a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt +++ b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt @@ -927,6 +927,7 @@ class SafetyCenterActivityTest { fun issueCard_attributionNotSetBySourceOnTiramisu_doesNotDisplayAttributionTitle() { // TODO(b/258228790): Remove after U is no longer in pre-release assumeFalse(CODENAME == "UpsideDownCake") + assumeFalse(CODENAME == "VanillaIceCream") safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig) val data = safetySourceTestData.recommendationWithGeneralIssue @@ -1412,6 +1413,7 @@ class SafetyCenterActivityTest { fun launchSafetyCenter_enableSubpagesFlagOnT_stillShowsExpandAndCollapseEntries() { // TODO(b/258228790): Remove after U is no longer in pre-release assumeFalse(CODENAME == "UpsideDownCake") + assumeFalse(CODENAME == "VanillaIceCream") SafetyCenterFlags.showSubpages = true val sourceTestData = safetySourceTestData.information diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt index 091f99d80..d2fff0776 100644 --- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt +++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt @@ -120,6 +120,14 @@ object SafetyCenterFlags { DurationParser() ) + /** Flag that determines a duration after which a temporarily hidden issue will resurface. */ + private val tempHiddenIssueResurfaceDelayFlag = + Flag( + "safety_center_temp_hidden_issue_resurface_delay_millis", + defaultValue = Duration.ofDays(2), + DurationParser() + ) + /** * Flag that determines the time for which Safety Center will wait before starting dismissal of * resolved issue UI @@ -283,6 +291,7 @@ object SafetyCenterFlags { replaceLockScreenIconActionFlag, refreshSourceTimeoutsFlag, resolveActionTimeoutFlag, + tempHiddenIssueResurfaceDelayFlag, hideResolveUiTransitionDelayFlag, untrackedSourcesFlag, resurfaceIssueMaxCountsFlag, @@ -332,6 +341,9 @@ object SafetyCenterFlags { /** A property that allows getting and setting the [resolveActionTimeoutFlag]. */ var resolveActionTimeout: Duration by resolveActionTimeoutFlag + /** A property that allows getting and setting the [tempHiddenIssueResurfaceDelayFlag]. */ + var tempHiddenIssueResurfaceDelay: Duration by tempHiddenIssueResurfaceDelayFlag + /** A property that allows getting and setting the [hideResolveUiTransitionDelayFlag]. */ var hideResolvedIssueUiTransitionDelay: Duration by hideResolveUiTransitionDelayFlag |