diff options
author | 2024-02-08 12:55:40 +0000 | |
---|---|---|
committer | 2024-02-08 12:55:40 +0000 | |
commit | fcba235c4c03c0bf0b58e8b1661be45eec7d57ca (patch) | |
tree | f0a2d001ad75140572f9a5d7bf43e0547f362cb6 | |
parent | a78527583c737cd4c04535533a9dae6df9f6951b (diff) | |
parent | 1103d313bf297370cd948cf4a78d4a3f748ba87e (diff) |
Merge changes from topics "ps_safety_center", "sc_biometrics", "sc_private_profile_api" into main
* changes:
Add the titleForPrivateProfile API
Generalise the code for various profile types - 6
Generalise the code for various profile types - 5
Generalise the code for various profile types - 4
Don't add primary profile in default group summary
Generalise the code for various profile types - 3
Generalise the code for various profile types - 2
Generalise the code for various profile types - 1
45 files changed, 1951 insertions, 356 deletions
diff --git a/SafetyCenter/Config/Android.bp b/SafetyCenter/Config/Android.bp index d6423288a..48c8eab46 100644 --- a/SafetyCenter/Config/Android.bp +++ b/SafetyCenter/Config/Android.bp @@ -40,6 +40,7 @@ java_library { ], static_libs: [ "modules-utils-build", + "permissions-aconfig-flags-lib", ], apex_available: [ "com.android.permission", diff --git a/SafetyCenter/Config/java/com/android/safetycenter/config/SafetyCenterConfigParser.java b/SafetyCenter/Config/java/com/android/safetycenter/config/SafetyCenterConfigParser.java index b6730f36f..38eee9e51 100644 --- a/SafetyCenter/Config/java/com/android/safetycenter/config/SafetyCenterConfigParser.java +++ b/SafetyCenter/Config/java/com/android/safetycenter/config/SafetyCenterConfigParser.java @@ -33,10 +33,12 @@ import android.content.res.Resources; import android.safetycenter.config.SafetyCenterConfig; import android.safetycenter.config.SafetySource; import android.safetycenter.config.SafetySourcesGroup; +import android.util.Log; import androidx.annotation.RequiresApi; import com.android.modules.utils.build.SdkLevel; +import com.android.permission.flags.Flags; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -49,6 +51,7 @@ import java.io.InputStream; @RequiresApi(TIRAMISU) public final class SafetyCenterConfigParser { + private static final String TAG = "SafetyCenterConfigParser"; private static final String TAG_SAFETY_CENTER_CONFIG = "safety-center-config"; private static final String TAG_SAFETY_SOURCES_CONFIG = "safety-sources-config"; private static final String TAG_SAFETY_SOURCES_GROUP = "safety-sources-group"; @@ -64,6 +67,8 @@ public final class SafetyCenterConfigParser { private static final String ATTR_SAFETY_SOURCE_PACKAGE_NAME = "packageName"; private static final String ATTR_SAFETY_SOURCE_TITLE = "title"; private static final String ATTR_SAFETY_SOURCE_TITLE_FOR_WORK = "titleForWork"; + private static final String ATTR_SAFETY_SOURCE_TITLE_FOR_PRIVATE_PROFILE = + "titleForPrivateProfile"; private static final String ATTR_SAFETY_SOURCE_SUMMARY = "summary"; private static final String ATTR_SAFETY_SOURCE_INTENT_ACTION = "intentAction"; private static final String ATTR_SAFETY_SOURCE_PROFILE = "profile"; @@ -270,6 +275,26 @@ public final class SafetyCenterConfigParser { parser.getAttributeName(i), resources)); break; + case ATTR_SAFETY_SOURCE_TITLE_FOR_PRIVATE_PROFILE: + if (SdkLevel.isAtLeastV()) { + if (Flags.privateProfileTitleApi()) { + builder.setTitleForPrivateProfileResId( + parseStringResourceName( + parser.getAttributeValue(i), + name, + parser.getAttributeName(i), + resources)); + } else { + Log.i( + TAG, + String.format( + "Ignoring attribute %s.%s", + name, ATTR_SAFETY_SOURCE_TITLE_FOR_PRIVATE_PROFILE)); + } + break; + } else { + throw attributeUnexpected(name, parser.getAttributeName(i)); + } case ATTR_SAFETY_SOURCE_SUMMARY: builder.setSummaryResId( parseStringResourceName( diff --git a/SafetyCenter/Config/tests/Android.bp b/SafetyCenter/Config/tests/Android.bp index 3adedfc25..20a536237 100644 --- a/SafetyCenter/Config/tests/Android.bp +++ b/SafetyCenter/Config/tests/Android.bp @@ -33,6 +33,7 @@ android_test { "compatibility-device-util-axt", "safety-center-config", "safety-center-test-util-lib", + "permissions-aconfig-flags-lib", ], test_suites: [ "general-tests", diff --git a/SafetyCenter/Config/tests/java/com/android/safetycenter/config/ParserConfigInvalidTest.kt b/SafetyCenter/Config/tests/java/com/android/safetycenter/config/ParserConfigInvalidTest.kt index 64775d7fe..98b899ebf 100644 --- a/SafetyCenter/Config/tests/java/com/android/safetycenter/config/ParserConfigInvalidTest.kt +++ b/SafetyCenter/Config/tests/java/com/android/safetycenter/config/ParserConfigInvalidTest.kt @@ -19,6 +19,7 @@ package com.android.safetycenter.config import android.content.Context import androidx.test.core.app.ApplicationProvider.getApplicationContext import com.android.modules.utils.build.SdkLevel +import com.android.permission.flags.Flags import com.android.safetycenter.config.tests.R import com.google.common.truth.Truth.assertThat import org.junit.Assert.assertThrows @@ -63,18 +64,39 @@ class ParserConfigInvalidTest { fun parameters() = arrayOf( Params( + "ConfigDynamicSafetySourceAllDisabledNoPrivate", + R.raw.config_dynamic_safety_source_all_disabled_no_private, + "Element dynamic-safety-source invalid", + "Required attribute titleForPrivateProfile missing", + SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi() + ), + Params( "ConfigDynamicSafetySourceAllDisabledNoWork", R.raw.config_dynamic_safety_source_all_disabled_no_work, "Element dynamic-safety-source invalid", "Required attribute titleForWork missing" ), Params( + "ConfigDynamicSafetySourceAllHiddenWithSearchNoPrivate", + R.raw.config_dynamic_safety_source_all_hidden_with_search_no_private, + "Element dynamic-safety-source invalid", + "Required attribute titleForPrivateProfile missing", + SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi() + ), + Params( "ConfigDynamicSafetySourceAllHiddenWithSearchNoWork", R.raw.config_dynamic_safety_source_all_hidden_with_search_no_work, "Element dynamic-safety-source invalid", "Required attribute titleForWork missing" ), Params( + "ConfigDynamicSafetySourceAllNoPrivate", + R.raw.config_dynamic_safety_source_all_no_private, + "Element dynamic-safety-source invalid", + "Required attribute titleForPrivateProfile missing", + SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi() + ), + Params( "ConfigDynamicSafetySourceAllNoWork", R.raw.config_dynamic_safety_source_all_no_work, "Element dynamic-safety-source invalid", @@ -160,12 +182,26 @@ class ParserConfigInvalidTest { "Required attribute title missing" ), Params( + "ConfigDynamicSafetySourcePrimaryHiddenWithPrivate", + R.raw.config_dynamic_safety_source_primary_hidden_with_private, + "Element dynamic-safety-source invalid", + "Prohibited attribute titleForPrivateProfile present", + SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi() + ), + Params( "ConfigDynamicSafetySourcePrimaryHiddenWithWork", R.raw.config_dynamic_safety_source_primary_hidden_with_work, "Element dynamic-safety-source invalid", "Prohibited attribute titleForWork present" ), Params( + "ConfigDynamicSafetySourcePrimaryWithPrivate", + R.raw.config_dynamic_safety_source_primary_with_private, + "Element dynamic-safety-source invalid", + "Prohibited attribute titleForPrivateProfile present", + SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi() + ), + Params( "ConfigDynamicSafetySourcePrimaryWithWork", R.raw.config_dynamic_safety_source_primary_with_work, "Element dynamic-safety-source invalid", @@ -226,6 +262,13 @@ class ParserConfigInvalidTest { "Prohibited attribute intentAction present" ), Params( + "ConfigIssueOnlySafetySourceWithPrivate", + R.raw.config_issue_only_safety_source_with_private, + "Element issue-only-safety-source invalid", + "Prohibited attribute titleForPrivateProfile present", + SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi() + ), + Params( "ConfigIssueOnlySafetySourceWithSearch", R.raw.config_issue_only_safety_source_with_search, "Element issue-only-safety-source invalid", @@ -425,12 +468,26 @@ class ParserConfigInvalidTest { SdkLevel.isAtLeastU() ), Params( + "ConfigStaticSafetySourceWithPrimaryAndPrivate", + R.raw.config_static_safety_source_with_primary_and_private, + "Element static-safety-source invalid", + "Prohibited attribute titleForPrivateProfile present", + SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi() + ), + Params( "ConfigStaticSafetySourceWithPrimaryAndWork", R.raw.config_static_safety_source_with_primary_and_work, "Element static-safety-source invalid", "Prohibited attribute titleForWork present" ), Params( + "ConfigStaticSafetySourceWithPrivatePreV", + R.raw.config_static_safety_source_with_private_profile, + "Element static-safety-source invalid", + "Unexpected attribute static-safety-source.titleForPrivateProfile", + !SdkLevel.isAtLeastV() + ), + Params( "ConfigStaticSafetySourceWithRefresh", R.raw.config_static_safety_source_with_refresh, "Element static-safety-source invalid", diff --git a/SafetyCenter/Config/tests/java/com/android/safetycenter/config/ParserConfigValidTest.kt b/SafetyCenter/Config/tests/java/com/android/safetycenter/config/ParserConfigValidTest.kt index b63ccead7..5e8d04597 100644 --- a/SafetyCenter/Config/tests/java/com/android/safetycenter/config/ParserConfigValidTest.kt +++ b/SafetyCenter/Config/tests/java/com/android/safetycenter/config/ParserConfigValidTest.kt @@ -77,6 +77,9 @@ class ParserConfigValidTest { addPackageCertificateHash("feed1") addPackageCertificateHash("feed2") } + if (SdkLevel.isAtLeastV()) { + setTitleForPrivateProfileResId(R.string.reference) + } } .build() ) @@ -101,6 +104,9 @@ class ParserConfigValidTest { addPackageCertificateHash("feed1") addPackageCertificateHash("feed2") } + if (SdkLevel.isAtLeastV()) { + setTitleForPrivateProfileResId(R.string.reference) + } } .build() ) @@ -133,6 +139,11 @@ class ParserConfigValidTest { .setProfile(SafetySource.PROFILE_ALL) .setInitialDisplayState(SafetySource.INITIAL_DISPLAY_STATE_HIDDEN) .setSearchTermsResId(R.string.reference) + .apply { + if (SdkLevel.isAtLeastV()) { + setTitleForPrivateProfileResId(R.string.reference) + } + } .build() ) .build() @@ -159,6 +170,11 @@ class ParserConfigValidTest { .setIntentAction("intent") .setProfile(SafetySource.PROFILE_ALL) .setSearchTermsResId(R.string.reference) + .apply { + if (SdkLevel.isAtLeastV()) { + setTitleForPrivateProfileResId(R.string.reference) + } + } .build() ) .build() diff --git a/SafetyCenter/Config/tests/res/raw-v35/config_dynamic_safety_source_all_disabled_no_private.xml b/SafetyCenter/Config/tests/res/raw-v35/config_dynamic_safety_source_all_disabled_no_private.xml new file mode 100644 index 000000000..dae6ca754 --- /dev/null +++ b/SafetyCenter/Config/tests/res/raw-v35/config_dynamic_safety_source_all_disabled_no_private.xml @@ -0,0 +1,18 @@ +<safety-center-config> + <safety-sources-config> + <safety-sources-group + id="id" + title="@com.android.safetycenter.config.tests:string/reference" + summary="@com.android.safetycenter.config.tests:string/reference"> + <dynamic-safety-source + id="id" + packageName="package" + title="@com.android.safetycenter.config.tests:string/reference" + titleForWork="@com.android.safetycenter.config.tests:string/reference" + summary="@com.android.safetycenter.config.tests:string/reference" + intentAction="intent" + profile="all_profiles" + initialDisplayState="disabled"/> + </safety-sources-group> + </safety-sources-config> +</safety-center-config> diff --git a/SafetyCenter/Config/tests/res/raw-v35/config_dynamic_safety_source_all_hidden_with_search_no_private.xml b/SafetyCenter/Config/tests/res/raw-v35/config_dynamic_safety_source_all_hidden_with_search_no_private.xml new file mode 100644 index 000000000..e1852b6ec --- /dev/null +++ b/SafetyCenter/Config/tests/res/raw-v35/config_dynamic_safety_source_all_hidden_with_search_no_private.xml @@ -0,0 +1,17 @@ +<safety-center-config> + <safety-sources-config> + <safety-sources-group + id="id" + title="@com.android.safetycenter.config.tests:string/reference" + summary="@com.android.safetycenter.config.tests:string/reference"> + <dynamic-safety-source + id="id" + packageName="package" + title="@com.android.safetycenter.config.tests:string/reference" + titleForWork="@com.android.safetycenter.config.tests:string/reference" + profile="all_profiles" + initialDisplayState="hidden" + searchTerms="@com.android.safetycenter.config.tests:string/reference"/> + </safety-sources-group> + </safety-sources-config> +</safety-center-config> diff --git a/SafetyCenter/Config/tests/res/raw-v35/config_dynamic_safety_source_all_no_private.xml b/SafetyCenter/Config/tests/res/raw-v35/config_dynamic_safety_source_all_no_private.xml new file mode 100644 index 000000000..8446b71cc --- /dev/null +++ b/SafetyCenter/Config/tests/res/raw-v35/config_dynamic_safety_source_all_no_private.xml @@ -0,0 +1,17 @@ +<safety-center-config> + <safety-sources-config> + <safety-sources-group + id="id" + title="@com.android.safetycenter.config.tests:string/reference" + summary="@com.android.safetycenter.config.tests:string/reference"> + <dynamic-safety-source + id="id" + packageName="package" + title="@com.android.safetycenter.config.tests:string/reference" + titleForWork="@com.android.safetycenter.config.tests:string/reference" + summary="@com.android.safetycenter.config.tests:string/reference" + intentAction="intent" + profile="all_profiles"/> + </safety-sources-group> + </safety-sources-config> +</safety-center-config> diff --git a/SafetyCenter/Config/tests/res/raw-v35/config_dynamic_safety_source_primary_hidden_with_private.xml b/SafetyCenter/Config/tests/res/raw-v35/config_dynamic_safety_source_primary_hidden_with_private.xml new file mode 100644 index 000000000..3d5840b03 --- /dev/null +++ b/SafetyCenter/Config/tests/res/raw-v35/config_dynamic_safety_source_primary_hidden_with_private.xml @@ -0,0 +1,15 @@ +<safety-center-config> + <safety-sources-config> + <safety-sources-group + id="id" + title="@com.android.safetycenter.config.tests:string/reference" + summary="@com.android.safetycenter.config.tests:string/reference"> + <dynamic-safety-source + id="id" + packageName="package" + titleForPrivateProfile="@com.android.safetycenter.config.tests:string/reference" + profile="primary_profile_only" + initialDisplayState="hidden"/> + </safety-sources-group> + </safety-sources-config> +</safety-center-config> diff --git a/SafetyCenter/Config/tests/res/raw-v35/config_dynamic_safety_source_primary_with_private.xml b/SafetyCenter/Config/tests/res/raw-v35/config_dynamic_safety_source_primary_with_private.xml new file mode 100644 index 000000000..b95a3ed6e --- /dev/null +++ b/SafetyCenter/Config/tests/res/raw-v35/config_dynamic_safety_source_primary_with_private.xml @@ -0,0 +1,17 @@ +<safety-center-config> + <safety-sources-config> + <safety-sources-group + id="id" + title="@com.android.safetycenter.config.tests:string/reference" + summary="@com.android.safetycenter.config.tests:string/reference"> + <dynamic-safety-source + id="id" + packageName="package" + title="@com.android.safetycenter.config.tests:string/reference" + titleForPrivateProfile="@com.android.safetycenter.config.tests:string/reference" + summary="@com.android.safetycenter.config.tests:string/reference" + intentAction="intent" + profile="primary_profile_only"/> + </safety-sources-group> + </safety-sources-config> +</safety-center-config> diff --git a/SafetyCenter/Config/tests/res/raw-v35/config_issue_only_safety_source_with_private.xml b/SafetyCenter/Config/tests/res/raw-v35/config_issue_only_safety_source_with_private.xml new file mode 100644 index 000000000..7b2484a41 --- /dev/null +++ b/SafetyCenter/Config/tests/res/raw-v35/config_issue_only_safety_source_with_private.xml @@ -0,0 +1,12 @@ +<safety-center-config> + <safety-sources-config> + <safety-sources-group + id="id"> + <issue-only-safety-source + id="id" + packageName="package" + profile="all_profiles" + titleForPrivateProfile="@com.android.safetycenter.config.tests:string/reference"/> + </safety-sources-group> + </safety-sources-config> +</safety-center-config> diff --git a/SafetyCenter/Config/tests/res/raw-v35/config_static_safety_source_with_primary_and_private.xml b/SafetyCenter/Config/tests/res/raw-v35/config_static_safety_source_with_primary_and_private.xml new file mode 100644 index 000000000..0ab3d885b --- /dev/null +++ b/SafetyCenter/Config/tests/res/raw-v35/config_static_safety_source_with_primary_and_private.xml @@ -0,0 +1,16 @@ +<safety-center-config> + <safety-sources-config> + <safety-sources-group + id="id" + title="@com.android.safetycenter.config.tests:string/reference" + summary="@com.android.safetycenter.config.tests:string/reference"> + <static-safety-source + id="id" + title="@com.android.safetycenter.config.tests:string/reference" + titleForPrivateProfile="@com.android.safetycenter.config.tests:string/reference" + summary="@com.android.safetycenter.config.tests:string/reference" + intentAction="intent" + profile="primary_profile_only"/> + </safety-sources-group> + </safety-sources-config> +</safety-center-config> diff --git a/SafetyCenter/Config/tests/res/raw-v35/config_valid.xml b/SafetyCenter/Config/tests/res/raw-v35/config_valid.xml new file mode 100644 index 000000000..e49a6cdc4 --- /dev/null +++ b/SafetyCenter/Config/tests/res/raw-v35/config_valid.xml @@ -0,0 +1,197 @@ +<safety-center-config> + <safety-sources-config> + <safety-sources-group + id="dynamic" + title="@com.android.safetycenter.config.tests:string/reference" + summary="@com.android.safetycenter.config.tests:string/reference" + statelessIconType="privacy"> + <dynamic-safety-source + id="dynamic_barebone" + packageName="package" + title="@com.android.safetycenter.config.tests:string/reference" + summary="@com.android.safetycenter.config.tests:string/reference" + intentAction="intent" + profile="primary_profile_only"/> + <dynamic-safety-source + id="dynamic_all_optional" + packageName="package" + title="@com.android.safetycenter.config.tests:string/reference" + titleForWork="@com.android.safetycenter.config.tests:string/reference" + titleForPrivateProfile="@com.android.safetycenter.config.tests:string/reference" + summary="@com.android.safetycenter.config.tests:string/reference" + intentAction="intent" + profile="all_profiles" + initialDisplayState="disabled" + maxSeverityLevel="300" + searchTerms="@com.android.safetycenter.config.tests:string/reference" + loggingAllowed="false" + refreshOnPageOpenAllowed="true" + notificationsAllowed="true" + deduplicationGroup="group" + packageCertificateHashes="feed1,feed2"/> + <dynamic-safety-source + id="@com.android.safetycenter.config.tests:string/dynamic_all_references_id" + packageName="@com.android.safetycenter.config.tests:string/dynamic_all_references_package_name" + title="@com.android.safetycenter.config.tests:string/reference" + titleForWork="@com.android.safetycenter.config.tests:string/reference" + titleForPrivateProfile="@com.android.safetycenter.config.tests:string/reference" + summary="@com.android.safetycenter.config.tests:string/reference" + intentAction="@com.android.safetycenter.config.tests:string/dynamic_all_references_intent_action" + profile="@com.android.safetycenter.config.tests:string/dynamic_all_references_profile" + initialDisplayState="@com.android.safetycenter.config.tests:string/dynamic_all_references_initial_display_state" + maxSeverityLevel="@com.android.safetycenter.config.tests:string/dynamic_all_references_max_severity_level" + searchTerms="@com.android.safetycenter.config.tests:string/reference" + loggingAllowed="@com.android.safetycenter.config.tests:string/dynamic_all_references_logging_allowed" + refreshOnPageOpenAllowed="@com.android.safetycenter.config.tests:string/dynamic_all_references_refresh_on_page_open_allowed" + notificationsAllowed="@com.android.safetycenter.config.tests:string/dynamic_all_references_notifications_allowed" + deduplicationGroup="@com.android.safetycenter.config.tests:string/dynamic_all_references_deduplication_group" + packageCertificateHashes="@com.android.safetycenter.config.tests:string/dynamic_all_references_package_cert_hashes"/> + <dynamic-safety-source + id="dynamic_disabled" + packageName="package" + title="@com.android.safetycenter.config.tests:string/reference" + summary="@com.android.safetycenter.config.tests:string/reference" + profile="primary_profile_only" + initialDisplayState="disabled"/> + <dynamic-safety-source + id="dynamic_hidden" + packageName="package" + profile="all_profiles" + initialDisplayState="hidden"/> + <dynamic-safety-source + id="dynamic_hidden_with_search" + packageName="package" + title="@com.android.safetycenter.config.tests:string/reference" + titleForWork="@com.android.safetycenter.config.tests:string/reference" + titleForPrivateProfile="@com.android.safetycenter.config.tests:string/reference" + summary="@com.android.safetycenter.config.tests:string/reference" + intentAction="intent" + profile="all_profiles" + initialDisplayState="hidden" + searchTerms="@com.android.safetycenter.config.tests:string/reference"/> + </safety-sources-group> + <safety-sources-group + id="static" + title="@com.android.safetycenter.config.tests:string/reference"> + <static-safety-source + id="static_barebone" + title="@com.android.safetycenter.config.tests:string/reference" + intentAction="intent" + profile="primary_profile_only"/> + <static-safety-source + id="static_all_optional" + packageName="package" + title="@com.android.safetycenter.config.tests:string/reference" + titleForWork="@com.android.safetycenter.config.tests:string/reference" + titleForPrivateProfile="@com.android.safetycenter.config.tests:string/reference" + summary="@com.android.safetycenter.config.tests:string/reference" + intentAction="intent" + profile="all_profiles" + searchTerms="@com.android.safetycenter.config.tests:string/reference"/> + </safety-sources-group> + <safety-sources-group + id="issue_only"> + <issue-only-safety-source + id="issue_only_barebone" + packageName="package" + profile="primary_profile_only"/> + <issue-only-safety-source + id="issue_only_all_optional" + packageName="package" + profile="all_profiles" + maxSeverityLevel="300" + loggingAllowed="false" + refreshOnPageOpenAllowed="true" + notificationsAllowed="true" + deduplicationGroup="group" + packageCertificateHashes="feed1,feed2"/> + <issue-only-safety-source + id="id_test_abcxyz_ABCXYZ_012789" + packageName="package" + profile="primary_profile_only"/> + </safety-sources-group> + <safety-sources-group + id="mixed" + title="@com.android.safetycenter.config.tests:string/reference"> + <dynamic-safety-source + id="mixed_dynamic_barebone" + packageName="package" + title="@com.android.safetycenter.config.tests:string/reference" + summary="@com.android.safetycenter.config.tests:string/reference" + intentAction="intent" + profile="primary_profile_only"/> + <issue-only-safety-source + id="mixed_issue_only_barebone" + packageName="package" + profile="primary_profile_only"/> + <static-safety-source + id="mixed_static_barebone" + title="@com.android.safetycenter.config.tests:string/reference" + intentAction="intent" + profile="primary_profile_only"/> + </safety-sources-group> + <safety-sources-group + type="stateful" + id="stateful_barebone" + title="@com.android.safetycenter.config.tests:string/reference"> + <static-safety-source + id="stateful_barebone_source" + title="@com.android.safetycenter.config.tests:string/reference" + intentAction="intent" + profile="primary_profile_only"/> + </safety-sources-group> + <safety-sources-group + type="stateful" + id="stateful_all_optional" + title="@com.android.safetycenter.config.tests:string/reference" + summary="@com.android.safetycenter.config.tests:string/reference" + statelessIconType="privacy"> + <static-safety-source + id="stateful_all_optional_source" + title="@com.android.safetycenter.config.tests:string/reference" + intentAction="intent" + profile="primary_profile_only"/> + </safety-sources-group> + <safety-sources-group + type="stateless" + id="stateless_barebone" + title="@com.android.safetycenter.config.tests:string/reference"> + <static-safety-source + id="stateless_barebone_source" + title="@com.android.safetycenter.config.tests:string/reference" + intentAction="intent" + profile="primary_profile_only"/> + </safety-sources-group> + <safety-sources-group + type="stateless" + id="stateless_all_optional" + title="@com.android.safetycenter.config.tests:string/reference" + summary="@com.android.safetycenter.config.tests:string/reference" + statelessIconType="privacy"> + <static-safety-source + id="stateless_all_optional_source" + title="@com.android.safetycenter.config.tests:string/reference" + intentAction="intent" + profile="primary_profile_only"/> + </safety-sources-group> + <safety-sources-group + type="hidden" + id="hidden_barebone"> + <issue-only-safety-source + id="hidden_barebone_source" + packageName="package" + profile="primary_profile_only"/> + </safety-sources-group> + <safety-sources-group + type="hidden" + id="hidden_all_optional" + title="@com.android.safetycenter.config.tests:string/reference" + summary="@com.android.safetycenter.config.tests:string/reference" + statelessIconType="privacy"> + <issue-only-safety-source + id="hidden_all_optional_source" + packageName="package" + profile="primary_profile_only"/> + </safety-sources-group> + </safety-sources-config> +</safety-center-config> diff --git a/SafetyCenter/Config/tests/res/raw/config_static_safety_source_with_private_profile.xml b/SafetyCenter/Config/tests/res/raw/config_static_safety_source_with_private_profile.xml new file mode 100644 index 000000000..f790baec9 --- /dev/null +++ b/SafetyCenter/Config/tests/res/raw/config_static_safety_source_with_private_profile.xml @@ -0,0 +1,17 @@ +<safety-center-config> + <safety-sources-config> + <safety-sources-group + id="id" + title="@com.android.safetycenter.config.tests:string/reference" + summary="@com.android.safetycenter.config.tests:string/reference"> + <static-safety-source + id="id" + title="@com.android.safetycenter.config.tests:string/reference" + titleForWork="@com.android.safetycenter.config.tests:string/reference" + titleForPrivateProfile="@com.android.safetycenter.config.tests:string/reference" + summary="@com.android.safetycenter.config.tests:string/reference" + intentAction="intent" + profile="all_profiles"/> + </safety-sources-group> + </safety-sources-config> +</safety-center-config> diff --git a/SafetyCenter/ConfigLintChecker/Android.bp b/SafetyCenter/ConfigLintChecker/Android.bp index fad0165df..7c615d2f4 100644 --- a/SafetyCenter/ConfigLintChecker/Android.bp +++ b/SafetyCenter/ConfigLintChecker/Android.bp @@ -12,64 +12,65 @@ // See the License for the specific language governing permissions and // limitations under the License. -package { - default_team: "trendy_team_android_permissions", - default_applicable_licenses: ["Android-Apache-2.0"], -} +// TODO(b/322944911): Reconsider enabling linter checker +//package { +// default_team: "trendy_team_android_permissions", +// default_applicable_licenses: ["Android-Apache-2.0"], +//} -java_library_host { - name: "ConfigLintChecker", - srcs: [ - "java/**/*.java", - "java/**/*.kt", - ":safetycenter-annotations-sources", - ":safetycenter-config-api-sources", - ":safetycenter-config-parser-sources", - ], - plugins: ["auto_service_plugin"], - libs: [ - "androidx.annotation_annotation", // For androidx.annotation.RequiresApi - "auto_service_annotations", - "core-xml-for-host", // For org.xmlpull.v1.* - "framework-annotations-lib", // For com.android.annotation.* - "layoutlib_api-prebuilt", // For com.android.resources.ResourceFolderType - "lint_api", - ], - java_resources: [":safetycenter-config-schemas"], - jarjar_rules: "jarjar-rules.txt", - kotlincflags: ["-Xjvm-default=all"], - visibility: [ - "//packages/modules/Permission:__subpackages__", - "//vendor:__subpackages__", - ], -} +//java_library_host { +// name: "ConfigLintChecker", +// srcs: [ +// "java/**/*.java", +// "java/**/*.kt", +// ":safetycenter-annotations-sources", +// ":safetycenter-config-api-sources", +// ":safetycenter-config-parser-sources", +// ], +// plugins: ["auto_service_plugin"], +// libs: [ +// "androidx.annotation_annotation", // For androidx.annotation.RequiresApi +// "auto_service_annotations", +// "core-xml-for-host", // For org.xmlpull.v1.* +// "framework-annotations-lib", // For com.android.annotation.* +// "layoutlib_api-prebuilt", // For com.android.resources.ResourceFolderType +// "lint_api", +// ], +// java_resources: [":safetycenter-config-schemas"], +// jarjar_rules: "jarjar-rules.txt", +// kotlincflags: ["-Xjvm-default=all"], +// visibility: [ +// "//packages/modules/Permission:__subpackages__", +// "//vendor:__subpackages__", +// ], +//} -java_test_host { - name: "ConfigLintCheckerTest", - srcs: [ - "tests/java/**/*.kt", - ], - static_libs: [ - "ConfigLintChecker", - "junit", - "lint", - "lint_tests", - ], - test_options: { - unit_test: true, - tradefed_options: [ - { - // lint bundles in some classes that were built with older versions - // of libraries, and no longer load. Since tradefed tries to load - // all classes in the jar to look for tests, it crashes loading them. - // Exclude these classes from tradefed's search. - name: "exclude-paths", - value: "org/apache", - }, - { - name: "exclude-paths", - value: "META-INF", - }, - ], - }, -} +//java_test_host { +// name: "ConfigLintCheckerTest", +// srcs: [ +// "tests/java/**/*.kt", +// ], +// static_libs: [ +// "ConfigLintChecker", +// "junit", +// "lint", +// "lint_tests", +// ], +// test_options: { +// unit_test: true, +// tradefed_options: [ +// { +// // lint bundles in some classes that were built with older versions +// // of libraries, and no longer load. Since tradefed tries to load +// // all classes in the jar to look for tests, it crashes loading them. +// // Exclude these classes from tradefed's search. +// name: "exclude-paths", +// value: "org/apache", +// }, +// { +// name: "exclude-paths", +// value: "META-INF", +// }, +// ], +// }, +//} diff --git a/SafetyCenter/Resources/Android.bp b/SafetyCenter/Resources/Android.bp index 6f635c885..a10ea7f1a 100644 --- a/SafetyCenter/Resources/Android.bp +++ b/SafetyCenter/Resources/Android.bp @@ -42,9 +42,10 @@ android_app { min_sdk_version: "30", apex_available: ["com.android.permission"], certificate: ":com.android.safetycenter.resources.certificate", - lint: { - extra_check_modules: ["ConfigLintChecker"], - }, + // TODO(b/322944911): Reconsider enabling linter checker + //lint: { + // extra_check_modules: ["ConfigLintChecker"], + //}, static_libs: [ "SafetyCenterResourcesShared", ], diff --git a/SafetyCenter/Resources/res/raw-v35/safety_center_config.xml b/SafetyCenter/Resources/res/raw-v35/safety_center_config.xml index 5d626757c..cb49e9d36 100644 --- a/SafetyCenter/Resources/res/raw-v35/safety_center_config.xml +++ b/SafetyCenter/Resources/res/raw-v35/safety_center_config.xml @@ -35,6 +35,7 @@ profile="all_profiles" title="@com.android.safetycenter.resources:string/biometrics_title" titleForWork="@com.android.safetycenter.resources:string/biometrics_title_for_work" + titleForPrivateProfile="@com.android.safetycenter.resources:string/biometrics_title_for_private_profile" searchTerms="@com.android.safetycenter.resources:string/biometrics_search_terms" initialDisplayState="hidden"/> </safety-sources-group> diff --git a/SafetyCenter/Resources/res/values-v35/strings.xml b/SafetyCenter/Resources/res/values-v35/strings.xml index af43f11f2..bdbc4b648 100644 --- a/SafetyCenter/Resources/res/values-v35/strings.xml +++ b/SafetyCenter/Resources/res/values-v35/strings.xml @@ -20,6 +20,9 @@ <string name="cellular_network_security_title" description="The title of the group of safety settings relating to cellular network security">Cellular network security</string> <string name="cellular_network_security_summary" description="The summary of the group of safety settings relating to cellular network security, which describes the group contents">Network type, encryption, notification controls</string> + <!-- Device unlock --> + <string name="biometrics_title_for_private_profile" description="The default title of the setting for managing biometric options on the device for private space"><!-- Empty placeholder--></string> + <!-- More settings --> <string name="private_space_title" description="The title of the entry for Private Space">Private Space</string> <string name="private_space_summary" description="The summary of the entry for Private Space settings, which describes the page contents">Setup Private Space, and more</string> diff --git a/flags/flags.aconfig b/flags/flags.aconfig index 00f7c2ada..8bf3a63ab 100644 --- a/flags/flags.aconfig +++ b/flags/flags.aconfig @@ -7,6 +7,22 @@ flag { bug: "292252664" } +flag { + name: "private_profile_supported" + namespace: "permissions" + description: "This flag is used to support private profile in safety center" + bug: "286539356" + is_fixed_read_only: true +} + +flag { + name: "private_profile_title_api" + namespace: "permissions" + description: "This flag is used to guard the private profile title api in safety center" + bug: "286539356" + is_fixed_read_only: true +} + # TODO: Remove when wear_privacy_dashboard_enabled_read_only reaches to the next stage. flag { name: "wear_privacy_dashboard_enabled" diff --git a/framework-s/api/system-current.txt b/framework-s/api/system-current.txt index 527407bf3..61af606e9 100644 --- a/framework-s/api/system-current.txt +++ b/framework-s/api/system-current.txt @@ -571,6 +571,7 @@ package android.safetycenter.config { method public int getProfile(); method @StringRes public int getSearchTermsResId(); method @StringRes public int getSummaryResId(); + method @FlaggedApi("com.android.permission.flags.private_profile_title_api") @StringRes public int getTitleForPrivateProfileResId(); method @StringRes public int getTitleForWorkResId(); method @StringRes public int getTitleResId(); method public int getType(); @@ -606,6 +607,7 @@ package android.safetycenter.config { method @NonNull public android.safetycenter.config.SafetySource.Builder setRefreshOnPageOpenAllowed(boolean); method @NonNull public android.safetycenter.config.SafetySource.Builder setSearchTermsResId(@StringRes int); method @NonNull public android.safetycenter.config.SafetySource.Builder setSummaryResId(@StringRes int); + method @FlaggedApi("com.android.permission.flags.private_profile_title_api") @NonNull public android.safetycenter.config.SafetySource.Builder setTitleForPrivateProfileResId(@StringRes int); method @NonNull public android.safetycenter.config.SafetySource.Builder setTitleForWorkResId(@StringRes int); method @NonNull public android.safetycenter.config.SafetySource.Builder setTitleResId(@StringRes int); } diff --git a/framework-s/java/android/safetycenter/config/SafetySource.java b/framework-s/java/android/safetycenter/config/SafetySource.java index 8aa897850..5502ca950 100644 --- a/framework-s/java/android/safetycenter/config/SafetySource.java +++ b/framework-s/java/android/safetycenter/config/SafetySource.java @@ -18,9 +18,11 @@ package android.safetycenter.config; import static android.os.Build.VERSION_CODES.TIRAMISU; import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; +import static android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM; import static java.util.Objects.requireNonNull; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -34,6 +36,7 @@ import android.util.ArraySet; import androidx.annotation.RequiresApi; import com.android.modules.utils.build.SdkLevel; +import com.android.permission.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -182,6 +185,9 @@ public final class SafetySource implements Parcelable { builder.addPackageCertificateHash(certs.get(i)); } } + if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) { + builder.setTitleForPrivateProfileResId(in.readInt()); + } return builder.build(); } @@ -207,6 +213,7 @@ public final class SafetySource implements Parcelable { private final boolean mNotificationsAllowed; @Nullable final String mDeduplicationGroup; @NonNull private final Set<String> mPackageCertificateHashes; + @StringRes private final int mTitleForPrivateProfileResId; private SafetySource( @SafetySourceType int type, @@ -224,7 +231,8 @@ public final class SafetySource implements Parcelable { boolean refreshOnPageOpenAllowed, boolean notificationsAllowed, @Nullable String deduplicationGroup, - @NonNull Set<String> packageCertificateHashes) { + @NonNull Set<String> packageCertificateHashes, + @StringRes int titleForPrivateProfileResId) { mType = type; mId = id; mPackageName = packageName; @@ -241,6 +249,7 @@ public final class SafetySource implements Parcelable { mNotificationsAllowed = notificationsAllowed; mDeduplicationGroup = deduplicationGroup; mPackageCertificateHashes = Set.copyOf(packageCertificateHashes); + mTitleForPrivateProfileResId = titleForPrivateProfileResId; } /** Returns the type of this safety source. */ @@ -347,6 +356,37 @@ public final class SafetySource implements Parcelable { } /** + * Returns the resource id of the title for private profile of this safety source. + * + * <p>The id refers to a string resource that is either accessible from any resource context or + * that is accessible from the same resource context that was used to load the Safety Center + * configuration. The id is {@link Resources#ID_NULL} when a title for private profile is not + * provided. + * + * @throws UnsupportedOperationException if the source is of type {@link + * SafetySource#SAFETY_SOURCE_TYPE_ISSUE_ONLY} or if the profile property of the source is + * set to {@link SafetySource#PROFILE_PRIMARY} + */ + @FlaggedApi(Flags.FLAG_PRIVATE_PROFILE_TITLE_API) + @RequiresApi(VANILLA_ICE_CREAM) + @StringRes + public int getTitleForPrivateProfileResId() { + if (!SdkLevel.isAtLeastV()) { + throw new UnsupportedOperationException( + "getTitleForPrivateProfileResId unsupported for SDKs lower than V"); + } + if (mType == SAFETY_SOURCE_TYPE_ISSUE_ONLY) { + throw new UnsupportedOperationException( + "getTitleForPrivateProfileResId unsupported for issue-only safety source"); + } + if (mProfile == PROFILE_PRIMARY) { + throw new UnsupportedOperationException( + "getTitleForPrivateProfileResId unsupported for primary profile safety source"); + } + return mTitleForPrivateProfileResId; + } + + /** * Returns the resource id of the summary of this safety source. * * <p>The id refers to a string resource that is either accessible from any resource context or @@ -554,7 +594,8 @@ public final class SafetySource implements Parcelable { && mRefreshOnPageOpenAllowed == that.mRefreshOnPageOpenAllowed && mNotificationsAllowed == that.mNotificationsAllowed && Objects.equals(mDeduplicationGroup, that.mDeduplicationGroup) - && Objects.equals(mPackageCertificateHashes, that.mPackageCertificateHashes); + && Objects.equals(mPackageCertificateHashes, that.mPackageCertificateHashes) + && mTitleForPrivateProfileResId == that.mTitleForPrivateProfileResId; } @Override @@ -575,7 +616,8 @@ public final class SafetySource implements Parcelable { mRefreshOnPageOpenAllowed, mNotificationsAllowed, mDeduplicationGroup, - mPackageCertificateHashes); + mPackageCertificateHashes, + mTitleForPrivateProfileResId); } @Override @@ -613,6 +655,8 @@ public final class SafetySource implements Parcelable { + mDeduplicationGroup + ", mPackageCertificateHashes=" + mPackageCertificateHashes + + ", mTitleForPrivateProfileResId=" + + mTitleForPrivateProfileResId + '}'; } @@ -641,6 +685,9 @@ public final class SafetySource implements Parcelable { dest.writeString(mDeduplicationGroup); dest.writeStringList(List.copyOf(mPackageCertificateHashes)); } + if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) { + dest.writeInt(mTitleForPrivateProfileResId); + } } /** Builder class for {@link SafetySource}. */ @@ -662,6 +709,7 @@ public final class SafetySource implements Parcelable { @Nullable private Boolean mNotificationsAllowed; @Nullable private String mDeduplicationGroup; @NonNull private final ArraySet<String> mPackageCertificateHashes = new ArraySet<>(); + @Nullable @StringRes private Integer mTitleForPrivateProfileResId; /** Creates a {@link Builder} for a {@link SafetySource}. */ public Builder(@SafetySourceType int type) { @@ -692,6 +740,7 @@ public final class SafetySource implements Parcelable { mNotificationsAllowed = safetySource.mNotificationsAllowed; mDeduplicationGroup = safetySource.mDeduplicationGroup; mPackageCertificateHashes.addAll(safetySource.mPackageCertificateHashes); + mTitleForPrivateProfileResId = safetySource.mTitleForPrivateProfileResId; } /** @@ -759,6 +808,32 @@ public final class SafetySource implements Parcelable { } /** + * Sets the resource id of the title for work of this safety source. + * + * <p>The id must refer to a string resource that is either accessible from any resource + * context or that is accessible from the same resource context that was used to load the + * Safety Center configuration. The id defaults to {@link Resources#ID_NULL} when a title + * for work is not provided. + * + * <p>The title for work is required if the profile property of the source is set to {@link + * SafetySource#PROFILE_ALL} and either the source is of type static or the source is a + * source of type dynamic that is not hidden and that does not provide search terms. The + * title for work is prohibited for sources of type issue-only and if the profile property + * of the source is not set to {@link SafetySource#PROFILE_ALL}. + */ + @FlaggedApi(Flags.FLAG_PRIVATE_PROFILE_TITLE_API) + @RequiresApi(VANILLA_ICE_CREAM) + @NonNull + public Builder setTitleForPrivateProfileResId(@StringRes int titleForPrivateProfileResId) { + if (!SdkLevel.isAtLeastV()) { + throw new UnsupportedOperationException( + "setTitleForPrivateProfileResId unsupported for SDKs lower than V"); + } + mTitleForPrivateProfileResId = titleForPrivateProfileResId; + return this; + } + + /** * Sets the resource id of the summary of this safety source. * * <p>The id must refer to a string resource that is either accessible from any resource @@ -984,7 +1059,7 @@ public final class SafetySource implements Parcelable { PROFILE_NONE, PROFILE_PRIMARY, PROFILE_ALL); - boolean hasWork = profile == PROFILE_ALL; + boolean hasAllProfiles = profile == PROFILE_ALL; int searchTermsResId = BuilderUtils.validateResId( @@ -1000,8 +1075,8 @@ public final class SafetySource implements Parcelable { BuilderUtils.validateResId( mTitleForWorkResId, "titleForWork", - hasWork && titleRequired, - !hasWork || isIssueOnly); + hasAllProfiles && titleRequired, + !hasAllProfiles || isIssueOnly); int summaryResId = BuilderUtils.validateResId( @@ -1052,6 +1127,16 @@ public final class SafetySource implements Parcelable { packageCertificateHashes, "packageCertificateHashes", false, isStatic); } + int titleForPrivateProfileResId = Resources.ID_NULL; + if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) { + titleForPrivateProfileResId = + BuilderUtils.validateResId( + mTitleForPrivateProfileResId, + "titleForPrivateProfile", + hasAllProfiles && titleRequired, + !hasAllProfiles || isIssueOnly); + } + return new SafetySource( type, id, @@ -1068,7 +1153,8 @@ public final class SafetySource implements Parcelable { refreshOnPageOpenAllowed, notificationsAllowed, deduplicationGroup, - packageCertificateHashes); + packageCertificateHashes, + titleForPrivateProfileResId); } } } diff --git a/framework-s/java/android/safetycenter/config/safety_center_config-v35.xsd b/framework-s/java/android/safetycenter/config/safety_center_config-v35.xsd new file mode 100644 index 000000000..20b1e7655 --- /dev/null +++ b/framework-s/java/android/safetycenter/config/safety_center_config-v35.xsd @@ -0,0 +1,223 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<!-- This file contains comments that define constraints that cannot be covered by the XSD language --> +<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" + version="1.0"> + + <xsd:element name="safety-center-config" type="safety-center-config"/> + + <xsd:complexType name="safety-center-config"> + <xsd:sequence> + <xsd:element name="safety-sources-config" type="safety-sources-config"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="safety-sources-config"> + <xsd:sequence> + <xsd:element + name="safety-sources-group" type="safety-sources-group" + minOccurs="1" maxOccurs="unbounded"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="safety-sources-group"> + <xsd:choice minOccurs="1" maxOccurs="unbounded"> + <xsd:element name="dynamic-safety-source" type="dynamic-safety-source"/> + <xsd:element name="static-safety-source" type="static-safety-source"/> + <xsd:element name="issue-only-safety-source" type="issue-only-safety-source"/> + </xsd:choice> + <!-- id must be unique among safety sources groups --> + <xsd:attribute name="id" type="idOrStringResourceName" use="required"/> + <!-- title is required unless the group contains issue only and/or internal sources --> + <xsd:attribute name="title" type="runtimeStringResourceName"/> + <xsd:attribute name="summary" type="runtimeStringResourceName"/> + <xsd:attribute name="statelessIconType" type="statelessIconTypeOrStringResourceName" + default="none"/> + <!-- type is inferred from other attributes and the group content if omitted --> + <xsd:attribute name="type" type="groupTypeOrStringResourceName"/> + </xsd:complexType> + + <xsd:complexType name="dynamic-safety-source"> + <!-- id must be unique among safety sources --> + <xsd:attribute name="id" type="idOrStringResourceName" use="required"/> + <xsd:attribute name="packageName" type="stringOrStringResourceName" use="required"/> + <!-- optional comma-separated set of certificate hashes, if provided will be used for validation. --> + <xsd:attribute name="packageCertificateHashes" type="stringOrStringResourceName"/> + <!-- title is required if initialDisplayState is not set to hidden or if searchTerms are provided --> + <xsd:attribute name="title" type="runtimeStringResourceName"/> + <!-- titleForWork is required if profile is set to all_profiles, and initialDisplayState is not set to hidden or if searchTerms are provided --> + <!-- titleForWork is prohibited if profile is set to primary_profile_only --> + <xsd:attribute name="titleForWork" type="runtimeStringResourceName"/> + <!-- titleForPrivateProfile is required if profile is set to all_profiles, and initialDisplayState is not set to hidden or if searchTerms are provided --> + <!-- titleForPrivateProfile is prohibited if profile is set to primary_profile_only --> + <xsd:attribute name="titleForPrivateProfile" type="runtimeStringResourceName"/> + <!-- summary is required if initialDisplayState is not set to hidden --> + <xsd:attribute name="summary" type="runtimeStringResourceName"/> + <!-- intentAction is required if initialDisplayState is set to enabled --> + <xsd:attribute name="intentAction" type="stringOrStringResourceName"/> + <xsd:attribute name="profile" type="profile" use="required"/> + <xsd:attribute name="initialDisplayState" type="initialDisplayStateOrStringResourceName" + default="enabled"/> + <xsd:attribute name="maxSeverityLevel" type="intOrStringResourceName" default="2147483647"/> + <xsd:attribute name="searchTerms" type="runtimeStringResourceName"/> + <xsd:attribute name="loggingAllowed" type="booleanOrStringResourceName" default="true"/> + <xsd:attribute name="refreshOnPageOpenAllowed" type="booleanOrStringResourceName" + default="false"/> + <xsd:attribute name="notificationsAllowed" type="booleanOrStringResourceName" + default="false"/> + <xsd:attribute name="deduplicationGroup" type="stringOrStringResourceName"/> + </xsd:complexType> + + <xsd:complexType name="issue-only-safety-source"> + <!-- id must be unique among safety sources --> + <xsd:attribute name="id" type="idOrStringResourceName" use="required"/> + <xsd:attribute name="packageName" type="stringOrStringResourceName" use="required"/> + <!-- optional comma-separated set of certificate hashes, if provided will be used for validation. --> + <xsd:attribute name="packageCertificateHashes" type="stringOrStringResourceName"/> + <xsd:attribute name="profile" type="profileOrStringResourceName" use="required"/> + <xsd:attribute name="maxSeverityLevel" type="intOrStringResourceName" default="2147483647"/> + <xsd:attribute name="loggingAllowed" type="booleanOrStringResourceName" default="true"/> + <xsd:attribute name="refreshOnPageOpenAllowed" type="booleanOrStringResourceName" + default="false"/> + <xsd:attribute name="notificationsAllowed" type="booleanOrStringResourceName" + default="false"/> + <xsd:attribute name="deduplicationGroup" type="stringOrStringResourceName"/> + </xsd:complexType> + + <xsd:complexType name="static-safety-source"> + <!-- id must be unique among safety sources --> + <xsd:attribute name="id" type="idOrStringResourceName" use="required"/> + <xsd:attribute name="packageName" type="stringOrStringResourceName"/> + <xsd:attribute name="title" type="runtimeStringResourceName" use="required"/> + <!-- titleForWork is required if profile is set to all_profiles --> + <!-- titleForWork is prohibited if profile is set to primary_profile_only --> + <xsd:attribute name="titleForWork" type="runtimeStringResourceName"/> + <!-- titleForPrivateProfile is required if profile is set to all_profiles --> + <!-- titleForPrivateProfile is prohibited if profile is set to primary_profile_only --> + <xsd:attribute name="titleForPrivateProfile" type="runtimeStringResourceName"/> + <xsd:attribute name="summary" type="runtimeStringResourceName"/> + <xsd:attribute name="intentAction" type="stringOrStringResourceName" use="required"/> + <xsd:attribute name="profile" type="profileOrStringResourceName" use="required"/> + <xsd:attribute name="searchTerms" type="runtimeStringResourceName"/> + </xsd:complexType> + + <xsd:simpleType name="intOrStringResourceName"> + <!-- String resource names will be resolved only once at parse time. --> + <!-- Locale changes and device config changes will be ignored. --> + <!-- The value of the string resource must be of type xsd:int. --> + <xsd:union memberTypes="stringResourceName xsd:int"/> + </xsd:simpleType> + + <xsd:simpleType name="booleanOrStringResourceName"> + <!-- String resource names will be resolved only once at parse time. --> + <!-- Locale changes and device config changes will be ignored. --> + <!-- The value of the string resource must be of type xsd:boolean. --> + <xsd:union memberTypes="stringResourceName xsd:boolean"/> + </xsd:simpleType> + + <xsd:simpleType name="stringOrStringResourceName"> + <!-- String resource names will be resolved only once at parse time. --> + <!-- Locale changes and device config changes will be ignored. --> + <!-- The value of the string resource must be of type xsd:string. --> + <xsd:union memberTypes="stringResourceName xsd:string"/> + </xsd:simpleType> + + <xsd:simpleType name="idOrStringResourceName"> + <!-- String resource names will be resolved only once at parse time. --> + <!-- Locale changes and device config changes will be ignored. --> + <!-- The value of the string resource must be of type xsd:string. --> + <xsd:union memberTypes="stringResourceName id"/> + </xsd:simpleType> + + <xsd:simpleType name="id"> + <xsd:restriction base="xsd:string"> + <xsd:pattern value="[0-9a-zA-Z_-]+"/> + </xsd:restriction> + </xsd:simpleType> + + <xsd:simpleType name="statelessIconTypeOrStringResourceName"> + <!-- String resource names will be resolved only once at parse time. --> + <!-- Locale changes and device config changes will be ignored. --> + <!-- The value of the string resource must be of type statelessIconType. --> + <xsd:union memberTypes="stringResourceName statelessIconType"/> + </xsd:simpleType> + + <xsd:simpleType name="statelessIconType"> + <xsd:restriction base="xsd:string"> + <xsd:enumeration value="none"/> + <xsd:enumeration value="privacy"/> + </xsd:restriction> + </xsd:simpleType> + + <xsd:simpleType name="profileOrStringResourceName"> + <!-- String resource names will be resolved only once at parse time. --> + <!-- Locale changes and device config changes will be ignored. --> + <!-- The value of the string resource must be of type profile. --> + <xsd:union memberTypes="stringResourceName profile"/> + </xsd:simpleType> + + <xsd:simpleType name="profile"> + <xsd:restriction base="xsd:string"> + <xsd:enumeration value="primary_profile_only"/> + <xsd:enumeration value="all_profiles"/> + </xsd:restriction> + </xsd:simpleType> + + <xsd:simpleType name="initialDisplayStateOrStringResourceName"> + <!-- String resource names will be resolved only once at parse time. --> + <!-- Locale changes and device config changes will be ignored. --> + <!-- The value of the string resource must be of type initialDisplayState. --> + <xsd:union memberTypes="stringResourceName initialDisplayState"/> + </xsd:simpleType> + + <xsd:simpleType name="initialDisplayState"> + <xsd:restriction base="xsd:string"> + <xsd:enumeration value="enabled"/> + <xsd:enumeration value="disabled"/> + <xsd:enumeration value="hidden"/> + </xsd:restriction> + </xsd:simpleType> + + <xsd:simpleType name="groupTypeOrStringResourceName"> + <!-- String resource names will be resolved only once at parse time. --> + <!-- Locale changes and device config changes will be ignored. --> + <!-- The value of the string resource must be of type groupType. --> + <xsd:union memberTypes="stringResourceName groupType"/> + </xsd:simpleType> + + <xsd:simpleType name="groupType"> + <xsd:restriction base="xsd:string"> + <xsd:enumeration value="stateless"/> + <xsd:enumeration value="stateful"/> + <xsd:enumeration value="hidden"/> + </xsd:restriction> + </xsd:simpleType> + + <xsd:simpleType name="runtimeStringResourceName"> + <!-- String resource names will be resolved at runtime whenever the string value is used. --> + <xsd:union memberTypes="stringResourceName"/> + </xsd:simpleType> + + <!-- String resource names will be ignored for any attribute not directly or indirectly marked as stringResourceName. --> + <!-- A stringResourceName is a fully qualified resource name of the form "@package:string/entry". Package is required. --> + <xsd:simpleType name="stringResourceName"> + <xsd:restriction base="xsd:string"> + <xsd:pattern value="@([a-z]+\.)*[a-z]+:string/.+"/> + </xsd:restriction> + </xsd:simpleType> + +</xsd:schema> diff --git a/service/java/com/android/permission/util/UserUtils.java b/service/java/com/android/permission/util/UserUtils.java index 8205be239..33389a88f 100644 --- a/service/java/com/android/permission/util/UserUtils.java +++ b/service/java/com/android/permission/util/UserUtils.java @@ -25,7 +25,9 @@ import android.os.UserHandle; import android.os.UserManager; import com.android.internal.util.Preconditions; +import com.android.modules.utils.build.SdkLevel; import com.android.permission.compat.UserHandleCompat; +import com.android.permission.flags.Flags; import java.util.List; @@ -92,6 +94,39 @@ public final class UserUtils { } /** + * Returns whether the given {@code userId} is a private profile. Note that private profiles are + * allowed from Android V+ only, so this method will return false on Sdk levels below that. + */ + public static boolean isPrivateProfile(@UserIdInt int userId, @NonNull Context context) { + if (!isPrivateProfileSupported()) { + return false; + } + // It's needed to clear the calling identity because we are going to query the UserManager + // for isPrivateProfile() and Context for createContextAsUser, which requires one of the + // following permissions: + // MANAGE_USERS, QUERY_USERS, or INTERACT_ACROSS_USERS. + final long identity = Binder.clearCallingIdentity(); + try { + Context userContext = context + .createContextAsUser(UserHandle.of(userId), /* flags= */ 0); + UserManager userManager = userContext.getSystemService(UserManager.class); + return userManager != null && userManager.isPrivateProfile(); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + + /** + * Returns whether private profile's allowed to exist. This can be true iff the SdkLevel is at + * least V AND the permission module's private profile feature flag is enabled. + */ + public static boolean isPrivateProfileSupported() { + //TODO(b/286539356) add the os feature flag protection when available. + return SdkLevel.isAtLeastV() && Flags.privateProfileSupported(); + } + + /** * Returns whether a given {@code userId} corresponds to a running managed profile, i.e. the * user is running and the quiet mode is not enabled. */ diff --git a/service/java/com/android/safetycenter/SafetyCenterBroadcastDispatcher.java b/service/java/com/android/safetycenter/SafetyCenterBroadcastDispatcher.java index a36beb2d3..5c9dfa664 100644 --- a/service/java/com/android/safetycenter/SafetyCenterBroadcastDispatcher.java +++ b/service/java/com/android/safetycenter/SafetyCenterBroadcastDispatcher.java @@ -49,6 +49,7 @@ import androidx.annotation.Nullable; import com.android.permission.util.PackageUtils; import com.android.safetycenter.SafetyCenterConfigReader.Broadcast; +import com.android.safetycenter.UserProfileGroup.ProfileType; import com.android.safetycenter.data.SafetyCenterDataManager; import java.time.Duration; @@ -334,37 +335,35 @@ final class SafetyCenterBroadcastDispatcher { * lists of source IDs. * * <p>The set of user IDs (keys) is the profile parent user ID of {@code userProfileGroup} plus - * the (possibly empty) set of running managed profile user IDs in that group. - * + * all the other types of running profiles: + * <ol> + * <li>The (possibly empty) set of running managed profile user IDs in that group. + * <li>The (possibly empty) set of running private profile user ID in that group. + * </ol> * <p>Every value present is a non-empty list, but the overall result may be empty. */ private SparseArray<List<String>> getUserIdsToSourceIds( Broadcast broadcast, UserProfileGroup userProfileGroup, @RefreshReason int refreshReason) { - int[] managedProfileIds = userProfileGroup.getManagedRunningProfilesUserIds(); - SparseArray<List<String>> result = new SparseArray<>(managedProfileIds.length + 1); - List<String> profileParentSources = - getSourceIdsForRefreshReason( - refreshReason, - broadcast.getSourceIdsForProfileParent(), - broadcast.getSourceIdsForProfileParentOnPageOpen(), - userProfileGroup.getProfileParentUserId()); - - if (!profileParentSources.isEmpty()) { - result.put(userProfileGroup.getProfileParentUserId(), profileParentSources); - } - - for (int i = 0; i < managedProfileIds.length; i++) { - List<String> managedProfileSources = - getSourceIdsForRefreshReason( - refreshReason, - broadcast.getSourceIdsForManagedProfiles(), - broadcast.getSourceIdsForManagedProfilesOnPageOpen(), - managedProfileIds[i]); - - if (!managedProfileSources.isEmpty()) { - result.put(managedProfileIds[i], managedProfileSources); + SparseArray<List<String>> result = + new SparseArray<>(userProfileGroup.getNumRunningProfiles()); + for (int profilTypeIdx = 0; + profilTypeIdx < ProfileType.ALL_PROFILE_TYPES.length; + ++profilTypeIdx) { + @ProfileType int profileType = ProfileType.ALL_PROFILE_TYPES[profilTypeIdx]; + int[] runningProfiles = userProfileGroup.getRunningProfilesOfType(profileType); + for (int profileIdx = 0; profileIdx < runningProfiles.length; ++profileIdx) { + List<String> profileSources = + getSourceIdsForRefreshReason( + refreshReason, + broadcast.getSourceIdsForProfileType(profileType), + broadcast.getSourceIdsOnPageOpenForProfileType(profileType), + runningProfiles[profileIdx]); + + if (!profileSources.isEmpty()) { + result.put(runningProfiles[profileIdx], profileSources); + } } } diff --git a/service/java/com/android/safetycenter/SafetyCenterConfigReader.java b/service/java/com/android/safetycenter/SafetyCenterConfigReader.java index c473ad916..641c242f1 100644 --- a/service/java/com/android/safetycenter/SafetyCenterConfigReader.java +++ b/service/java/com/android/safetycenter/SafetyCenterConfigReader.java @@ -16,6 +16,11 @@ package com.android.safetycenter; +import static com.android.safetycenter.UserProfileGroup.PROFILE_TYPE_MANAGED; +import static com.android.safetycenter.UserProfileGroup.PROFILE_TYPE_PRIMARY; +import static com.android.safetycenter.UserProfileGroup.PROFILE_TYPE_PRIVATE; + +import static java.util.Collections.emptyList; import static java.util.Collections.unmodifiableList; import static java.util.Objects.requireNonNull; @@ -28,6 +33,7 @@ import android.util.Log; import androidx.annotation.Nullable; +import com.android.safetycenter.UserProfileGroup.ProfileType; import com.android.safetycenter.config.ParseException; import com.android.safetycenter.config.SafetyCenterConfigParser; import com.android.safetycenter.resources.SafetyCenterResourcesApk; @@ -410,7 +416,7 @@ public final class SafetyCenterConfigReader { broadcast.mSourceIdsForProfileParentOnPageOpen.add(safetySource.getId()); } boolean needsManagedProfilesBroadcast = - SafetySources.supportsManagedProfiles(safetySource); + SafetySources.supportsProfileType(safetySource, PROFILE_TYPE_MANAGED); if (needsManagedProfilesBroadcast) { broadcast.mSourceIdsForManagedProfiles.add(safetySource.getId()); if (safetySource.isRefreshOnPageOpenAllowed()) { @@ -418,6 +424,19 @@ public final class SafetyCenterConfigReader { safetySource.getId()); } } + + // TODO(b/317378205): think about generalising these fields in Broadcast so that + // we are not duplicating the code - it can be a source of confusion and errors + // in future. + boolean needsPrivateProfileBroadcast = + SafetySources.supportsProfileType(safetySource, PROFILE_TYPE_PRIVATE); + if (needsPrivateProfileBroadcast) { + broadcast.mSourceIdsForPrivateProfile.add(safetySource.getId()); + if (safetySource.isRefreshOnPageOpenAllowed()) { + broadcast.mSourceIdsForPrivateProfileOnPageOpen.add( + safetySource.getId()); + } + } } } @@ -486,6 +505,8 @@ public final class SafetyCenterConfigReader { private final List<String> mSourceIdsForProfileParentOnPageOpen = new ArrayList<>(); private final List<String> mSourceIdsForManagedProfiles = new ArrayList<>(); private final List<String> mSourceIdsForManagedProfilesOnPageOpen = new ArrayList<>(); + private final List<String> mSourceIdsForPrivateProfile = new ArrayList<>(); + private final List<String> mSourceIdsForPrivateProfileOnPageOpen = new ArrayList<>(); private Broadcast(String packageName) { mPackageName = packageName; @@ -497,41 +518,42 @@ public final class SafetyCenterConfigReader { } /** - * Returns the safety source ids associated with this broadcast in the profile owner. - * - * <p>If this list is empty, there are no sources to dispatch to in the profile owner. - */ - List<String> getSourceIdsForProfileParent() { - return unmodifiableList(mSourceIdsForProfileParent); - } - - /** - * Returns the safety source ids associated with this broadcast in the profile owner that - * have refreshOnPageOpenAllowed set to true in the XML config. + * Returns the safety source ids associated with this broadcast in the given profile type. * - * <p>If this list is empty, there are no sources to dispatch to in the profile owner. + * <p>If this list is empty, there are no sources to dispatch to in the given profile type. */ - List<String> getSourceIdsForProfileParentOnPageOpen() { - return unmodifiableList(mSourceIdsForProfileParentOnPageOpen); - } - - /** - * Returns the safety source ids associated with this broadcast in the managed profile(s). - * - * <p>If this list is empty, there are no sources to dispatch to in the managed profile(s). - */ - List<String> getSourceIdsForManagedProfiles() { - return unmodifiableList(mSourceIdsForManagedProfiles); + List<String> getSourceIdsForProfileType(@ProfileType int profileType) { + switch (profileType) { + case PROFILE_TYPE_PRIMARY: + return unmodifiableList(mSourceIdsForProfileParent); + case PROFILE_TYPE_MANAGED: + return unmodifiableList(mSourceIdsForManagedProfiles); + case PROFILE_TYPE_PRIVATE: + return unmodifiableList(mSourceIdsForPrivateProfile); + default: + Log.w(TAG, "source ids asked for unexpected profile " + profileType); + return emptyList(); + } } /** - * Returns the safety source ids associated with this broadcast in the managed profile(s) + * Returns the safety source ids associated with this broadcast in the given profile type * that have refreshOnPageOpenAllowed set to true in the XML config. * - * <p>If this list is empty, there are no sources to dispatch to in the managed profile(s). + * <p>If this list is empty, there are no sources to dispatch to in the given profile type. */ - List<String> getSourceIdsForManagedProfilesOnPageOpen() { - return unmodifiableList(mSourceIdsForManagedProfilesOnPageOpen); + List<String> getSourceIdsOnPageOpenForProfileType(@ProfileType int profileType) { + switch (profileType) { + case PROFILE_TYPE_PRIMARY: + return unmodifiableList(mSourceIdsForProfileParentOnPageOpen); + case PROFILE_TYPE_MANAGED: + return unmodifiableList(mSourceIdsForManagedProfilesOnPageOpen); + case PROFILE_TYPE_PRIVATE: + return unmodifiableList(mSourceIdsForPrivateProfileOnPageOpen); + default: + Log.w(TAG, "source ids asked for unexpected profile " + profileType); + return emptyList(); + } } @Override @@ -545,7 +567,10 @@ public final class SafetyCenterConfigReader { that.mSourceIdsForProfileParentOnPageOpen) && mSourceIdsForManagedProfiles.equals(that.mSourceIdsForManagedProfiles) && mSourceIdsForManagedProfilesOnPageOpen.equals( - that.mSourceIdsForManagedProfilesOnPageOpen); + that.mSourceIdsForManagedProfilesOnPageOpen) + && mSourceIdsForPrivateProfile.equals(that.mSourceIdsForPrivateProfile) + && mSourceIdsForPrivateProfileOnPageOpen.equals( + that.mSourceIdsForPrivateProfileOnPageOpen); } @Override @@ -555,7 +580,9 @@ public final class SafetyCenterConfigReader { mSourceIdsForProfileParent, mSourceIdsForProfileParentOnPageOpen, mSourceIdsForManagedProfiles, - mSourceIdsForManagedProfilesOnPageOpen); + mSourceIdsForManagedProfilesOnPageOpen, + mSourceIdsForPrivateProfile, + mSourceIdsForPrivateProfileOnPageOpen); } @Override @@ -571,6 +598,10 @@ public final class SafetyCenterConfigReader { + mSourceIdsForManagedProfiles + ", mSourceIdsForManagedProfilesOnPageOpen=" + mSourceIdsForManagedProfilesOnPageOpen + + ", mSourceIdsForPrivateProfile=" + + mSourceIdsForPrivateProfile + + ", mSourceIdsForPrivateProfileOnPageOpen=" + + mSourceIdsForPrivateProfileOnPageOpen + '}'; } } diff --git a/service/java/com/android/safetycenter/SafetyCenterDataFactory.java b/service/java/com/android/safetycenter/SafetyCenterDataFactory.java index d74d160f4..7c7ade3f1 100644 --- a/service/java/com/android/safetycenter/SafetyCenterDataFactory.java +++ b/service/java/com/android/safetycenter/SafetyCenterDataFactory.java @@ -17,7 +17,11 @@ package com.android.safetycenter; import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; +import static android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM; +import static com.android.safetycenter.UserProfileGroup.PROFILE_TYPE_MANAGED; +import static com.android.safetycenter.UserProfileGroup.PROFILE_TYPE_PRIMARY; +import static com.android.safetycenter.UserProfileGroup.PROFILE_TYPE_PRIVATE; import static com.android.safetycenter.internaldata.SafetyCenterBundles.ISSUES_TO_GROUPS_BUNDLE_KEY; import static com.android.safetycenter.internaldata.SafetyCenterBundles.STATIC_ENTRIES_TO_IDS_BUNDLE_KEY; @@ -53,7 +57,8 @@ import android.util.Log; import androidx.annotation.Nullable; import com.android.modules.utils.build.SdkLevel; -import com.android.permission.util.UserUtils; +import com.android.permission.flags.Flags; +import com.android.safetycenter.UserProfileGroup.ProfileType; import com.android.safetycenter.data.SafetyCenterDataManager; import com.android.safetycenter.internaldata.SafetyCenterBundles; import com.android.safetycenter.internaldata.SafetyCenterEntryId; @@ -387,42 +392,36 @@ public final class SafetyCenterDataFactory { List<SafetySource> safetySources = safetySourcesGroup.getSafetySources(); List<SafetyCenterEntry> entries = new ArrayList<>(safetySources.size()); - for (int i = 0; i < safetySources.size(); i++) { - SafetySource safetySource = safetySources.get(i); - - groupSafetyCenterEntryLevel = - mergeSafetyCenterEntrySeverityLevels( - groupSafetyCenterEntryLevel, - addSafetyCenterEntry( - safetyCenterOverallState, - entries, - safetySource, - defaultPackageName, - userProfileGroup.getProfileParentUserId(), - /* isUserManaged= */ false, - /* isManagedUserRunning= */ false)); - - if (!SafetySources.supportsManagedProfiles(safetySource)) { - continue; - } - - int[] managedProfilesUserIds = userProfileGroup.getManagedProfilesUserIds(); - for (int j = 0; j < managedProfilesUserIds.length; j++) { - int managedProfileUserId = managedProfilesUserIds[j]; - boolean isManagedUserRunning = - userProfileGroup.isManagedUserRunning(managedProfileUserId); + for (int safetySourceIdx = 0; safetySourceIdx < safetySources.size(); ++safetySourceIdx) { + SafetySource safetySource = safetySources.get(safetySourceIdx); + for (int profileTypeIdx = 0; + profileTypeIdx < ProfileType.ALL_PROFILE_TYPES.length; + ++profileTypeIdx) { + @ProfileType int profileType = ProfileType.ALL_PROFILE_TYPES[profileTypeIdx]; + if (!SafetySources.supportsProfileType(safetySource, profileType)) { + continue; + } - groupSafetyCenterEntryLevel = - mergeSafetyCenterEntrySeverityLevels( - groupSafetyCenterEntryLevel, - addSafetyCenterEntry( - safetyCenterOverallState, - entries, - safetySource, - defaultPackageName, - managedProfileUserId, - /* isUserManaged= */ true, - isManagedUserRunning)); + int[] profileIds = userProfileGroup.getProfilesOfType(profileType); + for (int profileIdx = 0; profileIdx < profileIds.length; profileIdx++) { + int profileId = profileIds[profileIdx]; + boolean isUserRunning = + userProfileGroup.containsRunningUserId(profileId, profileType); + if (profileType == PROFILE_TYPE_PRIVATE && !isUserRunning) { + continue; + } + groupSafetyCenterEntryLevel = + mergeSafetyCenterEntrySeverityLevels( + groupSafetyCenterEntryLevel, + addSafetyCenterEntry( + safetyCenterOverallState, + entries, + safetySource, + defaultPackageName, + profileId, + profileType, + isUserRunning)); + } } } @@ -534,7 +533,8 @@ public final class SafetyCenterDataFactory { SafetyCenterEntry entry = entries.get(i); SafetyCenterEntryId entryId = SafetyCenterIds.entryIdFromString(entry.getId()); - if (UserUtils.isManagedProfile(entryId.getUserId(), mContext)) { + if (UserProfileGroup.getProfileTypeOfUser(entryId.getUserId(), mContext) + != PROFILE_TYPE_PRIMARY) { continue; } @@ -558,15 +558,15 @@ public final class SafetyCenterDataFactory { SafetySource safetySource, String defaultPackageName, @UserIdInt int userId, - boolean isUserManaged, - boolean isManagedUserRunning) { + @ProfileType int profileType, + boolean isUserRunning) { SafetyCenterEntry safetyCenterEntry = toSafetyCenterEntry( safetySource, defaultPackageName, userId, - isUserManaged, - isManagedUserRunning); + profileType, + isUserRunning); if (safetyCenterEntry == null) { return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED; } @@ -584,8 +584,8 @@ public final class SafetyCenterDataFactory { SafetySource safetySource, String defaultPackageName, @UserIdInt int userId, - boolean isUserManaged, - boolean isManagedUserRunning) { + @ProfileType int profileType, + boolean isUserRunning) { switch (safetySource.getType()) { case SafetySource.SAFETY_SOURCE_TYPE_ISSUE_ONLY: return null; @@ -594,7 +594,7 @@ public final class SafetyCenterDataFactory { SafetySourceStatus safetySourceStatus = getSafetySourceStatus( mSafetyCenterDataManager.getSafetySourceDataInternal(key)); - boolean inQuietMode = isUserManaged && !isManagedUserRunning; + boolean inQuietMode = (PROFILE_TYPE_MANAGED == profileType) && !isUserRunning; if (safetySourceStatus == null) { int severityLevel = inQuietMode @@ -606,8 +606,8 @@ public final class SafetyCenterDataFactory { severityLevel, SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION, userId, - isUserManaged, - isManagedUserRunning); + profileType, + isUserRunning); } PendingIntent sourceProvidedPendingIntent = inQuietMode ? null : safetySourceStatus.getPendingIntent(); @@ -665,8 +665,8 @@ public final class SafetyCenterDataFactory { SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED, SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_NO_ICON, userId, - isUserManaged, - isManagedUserRunning); + profileType, + isUserRunning); } Log.w( TAG, @@ -681,8 +681,8 @@ public final class SafetyCenterDataFactory { @SafetyCenterEntry.EntrySeverityLevel int entrySeverityLevel, @SafetyCenterEntry.SeverityUnspecifiedIconType int severityUnspecifiedIconType, @UserIdInt int userId, - boolean isUserManaged, - boolean isManagedUserRunning) { + @ProfileType int profileType, + boolean isUserRunning) { if (SafetySources.isDefaultEntryHidden(safetySource)) { return null; } @@ -692,7 +692,7 @@ public final class SafetyCenterDataFactory { .setSafetySourceId(safetySource.getId()) .setUserId(userId) .build(); - boolean isQuietModeEnabled = isUserManaged && !isManagedUserRunning; + boolean isQuietModeEnabled = (PROFILE_TYPE_MANAGED == profileType) && !isUserRunning; PendingIntent pendingIntent = mPendingIntentFactory.getPendingIntent( safetySource.getId(), @@ -702,13 +702,7 @@ public final class SafetyCenterDataFactory { isQuietModeEnabled); boolean enabled = pendingIntent != null && !SafetySources.isDefaultEntryDisabled(safetySource); - CharSequence title = - isUserManaged - ? DevicePolicyResources.getSafetySourceWorkString( - mSafetyCenterResourcesApk, - safetySource.getId(), - safetySource.getTitleForWorkResId()) - : mSafetyCenterResourcesApk.getString(safetySource.getTitleResId()); + CharSequence title = getTitleForProfileType(profileType, safetySource); CharSequence summary = mSafetyCenterDataManager.sourceHasError( SafetySourceKey.of(safetySource.getId(), userId)) @@ -738,38 +732,33 @@ public final class SafetyCenterDataFactory { UserProfileGroup userProfileGroup) { List<SafetySource> safetySources = safetySourcesGroup.getSafetySources(); List<SafetyCenterStaticEntry> staticEntries = new ArrayList<>(safetySources.size()); - for (int i = 0; i < safetySources.size(); i++) { - SafetySource safetySource = safetySources.get(i); - - addSafetyCenterStaticEntry( - staticEntriesToIds, - safetyCenterOverallState, - staticEntries, - safetySource, - defaultPackageName, - userProfileGroup.getProfileParentUserId(), - /* isUserManaged= */ false, - /* isManagedUserRunning= */ false); - - if (!SafetySources.supportsManagedProfiles(safetySource)) { - continue; - } - - int[] managedProfilesUserIds = userProfileGroup.getManagedProfilesUserIds(); - for (int j = 0; j < managedProfilesUserIds.length; j++) { - int managedProfileUserId = managedProfilesUserIds[j]; - boolean isManagedUserRunning = - userProfileGroup.isManagedUserRunning(managedProfileUserId); - - addSafetyCenterStaticEntry( - staticEntriesToIds, - safetyCenterOverallState, - staticEntries, - safetySource, - defaultPackageName, - managedProfileUserId, - /* isUserManaged= */ true, - isManagedUserRunning); + for (int safetySourceIdx = 0; safetySourceIdx < safetySources.size(); safetySourceIdx++) { + SafetySource safetySource = safetySources.get(safetySourceIdx); + for (int profileTypeIdx = 0; + profileTypeIdx < ProfileType.ALL_PROFILE_TYPES.length; + ++profileTypeIdx) { + @ProfileType int profileType = ProfileType.ALL_PROFILE_TYPES[profileTypeIdx]; + if (!SafetySources.supportsProfileType(safetySource, profileType)) { + continue; + } + int[] profileIds = userProfileGroup.getProfilesOfType(profileType); + for (int profileIdx = 0; profileIdx < profileIds.length; ++profileIdx) { + int profileId = profileIds[profileIdx]; + boolean isUserRunning = + userProfileGroup.containsRunningUserId(profileId, profileType); + if (profileType == PROFILE_TYPE_PRIVATE && !isUserRunning) { + continue; + } + addSafetyCenterStaticEntry( + staticEntriesToIds, + safetyCenterOverallState, + staticEntries, + safetySource, + defaultPackageName, + profileId, + profileType, + isUserRunning); + } } } @@ -790,15 +779,15 @@ public final class SafetyCenterDataFactory { SafetySource safetySource, String defaultPackageName, @UserIdInt int userId, - boolean isUserManaged, - boolean isManagedUserRunning) { + @ProfileType int profileType, + boolean isUserRunning) { SafetyCenterStaticEntry staticEntry = toSafetyCenterStaticEntry( safetySource, defaultPackageName, userId, - isUserManaged, - isManagedUserRunning); + profileType, + isUserRunning); if (staticEntry == null) { return; } @@ -826,8 +815,8 @@ public final class SafetyCenterDataFactory { SafetySource safetySource, String defaultPackageName, @UserIdInt int userId, - boolean isUserManaged, - boolean isManagedUserRunning) { + @ProfileType int profileType, + boolean isUserRunning) { switch (safetySource.getType()) { case SafetySource.SAFETY_SOURCE_TYPE_ISSUE_ONLY: return null; @@ -836,7 +825,7 @@ public final class SafetyCenterDataFactory { SafetySourceStatus safetySourceStatus = getSafetySourceStatus( mSafetyCenterDataManager.getSafetySourceDataInternal(key)); - boolean inQuietMode = isUserManaged && !isManagedUserRunning; + boolean inQuietMode = (profileType == PROFILE_TYPE_MANAGED) && !isUserRunning; if (safetySourceStatus != null) { PendingIntent sourceProvidedPendingIntent = inQuietMode ? null : safetySourceStatus.getPendingIntent(); @@ -867,15 +856,15 @@ public final class SafetyCenterDataFactory { safetySource, safetySource.getPackageName(), userId, - isUserManaged, - isManagedUserRunning); + profileType, + isUserRunning); case SafetySource.SAFETY_SOURCE_TYPE_STATIC: return toDefaultSafetyCenterStaticEntry( safetySource, getStaticSourcePackageNameOrDefault(safetySource, defaultPackageName), userId, - isUserManaged, - isManagedUserRunning); + profileType, + isUserRunning); } Log.w(TAG, "Unknown safety source type found in rigid group: " + safetySource.getType()); return null; @@ -886,12 +875,12 @@ public final class SafetyCenterDataFactory { SafetySource safetySource, String packageName, @UserIdInt int userId, - boolean isUserManaged, - boolean isManagedUserRunning) { + @ProfileType int profileType, + boolean isUserRunning) { if (SafetySources.isDefaultEntryHidden(safetySource)) { return null; } - boolean isQuietModeEnabled = isUserManaged && !isManagedUserRunning; + boolean isQuietModeEnabled = (profileType == PROFILE_TYPE_MANAGED) && !isUserRunning; PendingIntent pendingIntent = mPendingIntentFactory.getPendingIntent( safetySource.getId(), @@ -905,13 +894,7 @@ public final class SafetyCenterDataFactory { return null; } - CharSequence title = - isUserManaged - ? DevicePolicyResources.getSafetySourceWorkString( - mSafetyCenterResourcesApk, - safetySource.getId(), - safetySource.getTitleForWorkResId()) - : mSafetyCenterResourcesApk.getString(safetySource.getTitleResId()); + CharSequence title = getTitleForProfileType(profileType, safetySource); CharSequence summary = mSafetyCenterDataManager.sourceHasError( SafetySourceKey.of(safetySource.getId(), userId)) @@ -1242,6 +1225,29 @@ public final class SafetyCenterDataFactory { return null; } + private CharSequence getTitleForProfileType( + @ProfileType int profileType, SafetySource safetySource) { + switch (profileType) { + case PROFILE_TYPE_PRIMARY: + return mSafetyCenterResourcesApk.getString(safetySource.getTitleResId()); + case PROFILE_TYPE_MANAGED: + return DevicePolicyResources.getSafetySourceWorkString( + mSafetyCenterResourcesApk, + safetySource.getId(), + safetySource.getTitleForWorkResId()); + case PROFILE_TYPE_PRIVATE: + if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) { + return mSafetyCenterResourcesApk.getString( + safetySource.getTitleForPrivateProfileResId()); + } + Log.w(TAG, "unsupported private profile type encountered"); + return mSafetyCenterResourcesApk.getString(safetySource.getTitleResId()); + default: + Log.w(TAG, "unexpected value for the profile type " + profileType); + return mSafetyCenterResourcesApk.getString(safetySource.getTitleResId()); + } + } + private static SafetySourceKey toSafetySourceKey(String safetyCenterEntryIdString) { SafetyCenterEntryId id = SafetyCenterIds.entryIdFromString(safetyCenterEntryIdString); return SafetySourceKey.of(id.getSafetySourceId(), id.getUserId()); diff --git a/service/java/com/android/safetycenter/SafetyCenterListeners.java b/service/java/com/android/safetycenter/SafetyCenterListeners.java index 5f89f46ff..091daa0a6 100644 --- a/service/java/com/android/safetycenter/SafetyCenterListeners.java +++ b/service/java/com/android/safetycenter/SafetyCenterListeners.java @@ -87,7 +87,7 @@ final class SafetyCenterListeners { */ void deliverDataForUserProfileGroup(UserProfileGroup userProfileGroup) { ArrayMap<String, SafetyCenterData> safetyCenterDataCache = new ArrayMap<>(); - int[] relevantUserIds = userProfileGroup.getProfileParentAndManagedRunningProfilesUserIds(); + int[] relevantUserIds = userProfileGroup.getAllRunningProfilesUserIds(); for (int i = 0; i < relevantUserIds.length; i++) { deliverUpdateForUser( relevantUserIds[i], @@ -105,7 +105,7 @@ final class SafetyCenterListeners { void deliverErrorForUserProfileGroup( UserProfileGroup userProfileGroup, SafetyCenterErrorDetails safetyCenterErrorDetails) { ArrayMap<String, SafetyCenterData> safetyCenterDataCache = new ArrayMap<>(); - int[] relevantUserIds = userProfileGroup.getProfileParentAndManagedRunningProfilesUserIds(); + int[] relevantUserIds = userProfileGroup.getAllRunningProfilesUserIds(); for (int i = 0; i < relevantUserIds.length; i++) { deliverUpdateForUser( relevantUserIds[i], diff --git a/service/java/com/android/safetycenter/SafetyCenterRefreshTracker.java b/service/java/com/android/safetycenter/SafetyCenterRefreshTracker.java index d98127300..cc23b4e02 100644 --- a/service/java/com/android/safetycenter/SafetyCenterRefreshTracker.java +++ b/service/java/com/android/safetycenter/SafetyCenterRefreshTracker.java @@ -34,7 +34,6 @@ import android.util.Log; import androidx.annotation.Nullable; -import com.android.permission.util.UserUtils; import com.android.safetycenter.logging.SafetyCenterStatsdLogger; import java.io.PrintWriter; @@ -175,7 +174,7 @@ public final class SafetyCenterRefreshTracker { SafetyCenterStatsdLogger.writeSourceRefreshSystemEvent( requestType, safetySourceKey.getSourceId(), - UserUtils.isManagedProfile(safetySourceKey.getUserId(), mContext), + UserProfileGroup.getProfileTypeOfUser(safetySourceKey.getUserId(), mContext), duration, sourceResult, refreshReason, @@ -284,7 +283,7 @@ public final class SafetyCenterRefreshTracker { SafetyCenterStatsdLogger.writeSourceRefreshSystemEvent( requestType, sourceKey.getSourceId(), - UserUtils.isManagedProfile(sourceKey.getUserId(), mContext), + UserProfileGroup.getProfileTypeOfUser(sourceKey.getUserId(), mContext), duration, SAFETY_CENTER_SYSTEM_EVENT_REPORTED__RESULT__TIMEOUT, refreshReason, diff --git a/service/java/com/android/safetycenter/SafetySources.java b/service/java/com/android/safetycenter/SafetySources.java index 02d83d27b..7be0ef00e 100644 --- a/service/java/com/android/safetycenter/SafetySources.java +++ b/service/java/com/android/safetycenter/SafetySources.java @@ -20,6 +20,8 @@ import android.safetycenter.SafetySourceData; import android.safetycenter.config.SafetySource; import android.util.Log; +import com.android.safetycenter.UserProfileGroup.ProfileType; + /** * A helper class to facilitate working with {@link SafetySource} objects. * @@ -48,6 +50,23 @@ public final class SafetySources { /** Returns whether a {@link SafetySource} supports managed profiles. */ public static boolean supportsManagedProfiles(SafetySource safetySource) { + return supportsAllProfiles(safetySource); + } + + /** + * Returns whether a {@link SafetySource} supports the profile of the given type + * {@code profileType}. + */ + public static boolean supportsProfileType( + SafetySource safetySource, @ProfileType int profileType) { + if (UserProfileGroup.PROFILE_TYPE_PRIMARY == profileType) { + return true; + } + return supportsAllProfiles(safetySource); + } + + /** Returns whether a {@link SafetySource} supports all profiles. */ + private static boolean supportsAllProfiles(SafetySource safetySource) { int safetySourceProfile = safetySource.getProfile(); switch (safetySourceProfile) { case SafetySource.PROFILE_PRIMARY: diff --git a/service/java/com/android/safetycenter/UserProfileGroup.java b/service/java/com/android/safetycenter/UserProfileGroup.java index 74b9b136f..2f4ab17e4 100644 --- a/service/java/com/android/safetycenter/UserProfileGroup.java +++ b/service/java/com/android/safetycenter/UserProfileGroup.java @@ -18,6 +18,7 @@ package com.android.safetycenter; import static java.util.Objects.requireNonNull; +import android.annotation.IntDef; import android.annotation.UserIdInt; import android.content.Context; import android.content.pm.PackageManager; @@ -31,6 +32,8 @@ import androidx.annotation.Nullable; import com.android.permission.util.UserUtils; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -42,21 +45,47 @@ import java.util.Objects; * * @hide */ +//TODO(b/286539356) Do not expose the private profile when it's not running. public final class UserProfileGroup { private static final String TAG = "UserProfileGroup"; + // UserHandle#USER_NULL is a @TestApi so it cannot be accessed from the mainline module. + public static final @UserIdInt int USER_NULL = -10000; @UserIdInt private final int mProfileParentUserId; private final int[] mManagedProfilesUserIds; private final int[] mManagedRunningProfilesUserIds; + @UserIdInt private final int mPrivateProfileUserId; + private final boolean mPrivateProfileRunning; + + /** Respresents the profile type of the primary user. */ + public static final int PROFILE_TYPE_PRIMARY = 0; + /** Respresents the profile type of the managed profile. */ + public static final int PROFILE_TYPE_MANAGED = 1; + /** Respresents the profile type of the private profile. */ + public static final int PROFILE_TYPE_PRIVATE = 2; + + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = {PROFILE_TYPE_PRIMARY, PROFILE_TYPE_MANAGED, PROFILE_TYPE_PRIVATE}) + public @interface ProfileType { + // This array needs to cover all profile types. So whenever a new entry is added above then + // please remember to include it in this array as well. + int[] ALL_PROFILE_TYPES = + {PROFILE_TYPE_PRIMARY, PROFILE_TYPE_MANAGED, PROFILE_TYPE_PRIVATE}; + } + private UserProfileGroup( @UserIdInt int profileParentUserId, int[] managedProfilesUserIds, - int[] managedRunningProfilesUserIds) { + int[] managedRunningProfilesUserIds, + @UserIdInt int privateProfileUserId, + boolean privateProfileRunning) { mProfileParentUserId = profileParentUserId; mManagedProfilesUserIds = managedProfilesUserIds; mManagedRunningProfilesUserIds = managedRunningProfilesUserIds; + mPrivateProfileUserId = privateProfileUserId; + mPrivateProfileRunning = privateProfileRunning; } /** Returns all the alive {@link UserProfileGroup}s. */ @@ -117,6 +146,10 @@ public final class UserProfileGroup { int[] managedRunningProfilesUserIds = new int[userProfiles.size()]; int managedProfilesUserIdsLen = 0; int managedRunningProfilesUserIdsLen = 0; + + int privateProfileUserId = USER_NULL; + boolean privateProfileRunning = false; + for (int i = 0; i < userProfiles.size(); i++) { UserHandle userProfileHandle = userProfiles.get(i); int userProfileId = userProfileHandle.getIdentifier(); @@ -127,15 +160,19 @@ public final class UserProfileGroup { managedRunningProfilesUserIds[managedRunningProfilesUserIdsLen++] = userProfileId; } + } else if (UserUtils.isPrivateProfile(userProfileId, context)) { + privateProfileUserId = userProfileId; + privateProfileRunning = UserUtils.isProfileRunning(userProfileId, context); } } - UserProfileGroup userProfileGroup = - new UserProfileGroup( - profileParentUserId, - Arrays.copyOf(managedProfilesUserIds, managedProfilesUserIdsLen), - Arrays.copyOf( - managedRunningProfilesUserIds, managedRunningProfilesUserIdsLen)); + UserProfileGroup userProfileGroup = new UserProfileGroup( + profileParentUserId, + Arrays.copyOf(managedProfilesUserIds, managedProfilesUserIdsLen), + Arrays.copyOf(managedRunningProfilesUserIds, managedRunningProfilesUserIdsLen), + privateProfileUserId, + privateProfileRunning + ); if (!userProfileGroup.contains(userId)) { Log.i( TAG, @@ -150,7 +187,8 @@ public final class UserProfileGroup { if (!isProfile(userId, context)) { return true; } - return UserUtils.isManagedProfile(userId, context); + return UserUtils.isManagedProfile(userId, context) + || UserUtils.isPrivateProfile(userId, context); } private static UserManager getUserManagerForUser(@UserIdInt int userId, Context context) { @@ -208,32 +246,141 @@ public final class UserProfileGroup { return mProfileParentUserId; } - /** Returns the managed profile user ids of the {@link UserProfileGroup}. */ - public int[] getManagedProfilesUserIds() { - return mManagedProfilesUserIds; - } + /** + * A convenience method to get all the profile ids of all the users of all profile types. So, in + * essence, this is equivalent to iterating through all the profile types using + * {@link ProfileType#ALL_PROFILE_TYPES} and getting all the users for each of the profile type + * using {@link #getProfilesOfType(int profileType)} + */ + public int[] getAllProfilesUserIds() { + int[] allProfileIds = new int[getNumProfiles()]; + allProfileIds[0] = mProfileParentUserId; + System.arraycopy( + mManagedProfilesUserIds, + /* srcPos= */ 0, + allProfileIds, + /* destPos= */ 1, + mManagedProfilesUserIds.length); - /** Returns the running managed profile user ids of the {@link UserProfileGroup}. */ - public int[] getManagedRunningProfilesUserIds() { - return mManagedRunningProfilesUserIds; + if (mPrivateProfileUserId != USER_NULL) { + allProfileIds[allProfileIds.length - 1] = mPrivateProfileUserId; + } + + return allProfileIds; } /** - * Convenience method that combines the results of {@link - * UserProfileGroup#getProfileParentUserId()} and {@link - * UserProfileGroup#getManagedRunningProfilesUserIds()}. + * A convenience method to get all the profile ids of all the users (that are currently running) + * of all profile types. So, in essence, this is equivalent to iterating through all the profile + * {types using {@link ProfileType#ALL_PROFILE_TYPES} and getting all the users for each of the + * profile type using {@link #getProfilesOfType(int profileType)} only if they are running. */ - public int[] getProfileParentAndManagedRunningProfilesUserIds() { - int[] profileParentAndManagedRunningProfilesUserIds = - new int[mManagedRunningProfilesUserIds.length + 1]; - profileParentAndManagedRunningProfilesUserIds[0] = mProfileParentUserId; + public int[] getAllRunningProfilesUserIds() { + int[] allRunningProfileIds = new int[getNumRunningProfiles()]; + allRunningProfileIds[0] = mProfileParentUserId; System.arraycopy( mManagedRunningProfilesUserIds, /* srcPos= */ 0, - profileParentAndManagedRunningProfilesUserIds, + allRunningProfileIds, /* destPos= */ 1, mManagedRunningProfilesUserIds.length); - return profileParentAndManagedRunningProfilesUserIds; + + if (mPrivateProfileRunning) { + allRunningProfileIds[allRunningProfileIds.length - 1] = mPrivateProfileUserId; + } + + return allRunningProfileIds; + } + + /** + * Returns the profiles of the specified type. Returns an empty array if no profile of the + * specified type exists. + */ + public int[] getProfilesOfType(@ProfileType int profileType) { + switch (profileType) { + case PROFILE_TYPE_PRIMARY: + return new int[] {mProfileParentUserId}; + case PROFILE_TYPE_MANAGED: + return mManagedProfilesUserIds; + case PROFILE_TYPE_PRIVATE: + return mPrivateProfileUserId != USER_NULL + ? new int[]{mPrivateProfileUserId} : new int[]{}; + default: + Log.w(TAG, "profiles requested for unexpected profile type " + profileType); + return new int[] {}; + } + } + + /** + * Returns the running profiles of the specified type. Returns an empty array if no profile of + * the specified type exists. + */ + public int[] getRunningProfilesOfType(@ProfileType int profileType) { + switch (profileType) { + case PROFILE_TYPE_PRIMARY: + return new int[] {mProfileParentUserId}; + case PROFILE_TYPE_MANAGED: + return mManagedRunningProfilesUserIds; + case PROFILE_TYPE_PRIVATE: + //TODO(b/286539356) add the new feature flag protection when available. + return mPrivateProfileRunning + ? new int[] {} : new int[] {mPrivateProfileUserId}; + default: + Log.w(TAG, "Unexpected profile type " + profileType); + return new int[] {}; + } + } + + /** Returns the total number of running profiles in this user profile group */ + public int getNumRunningProfiles() { + return 1 + + mManagedRunningProfilesUserIds.length + + (mPrivateProfileRunning ? 1 : 0); + } + + /** Returns the total number of profiles in this user profile group */ + private int getNumProfiles() { + return 1 + + mManagedProfilesUserIds.length + + (mPrivateProfileUserId == USER_NULL ? 0 : 1); + } + + /** + * Returns the {@link ProfileType} for the provided {@code userId}. Note that the provided + * {@code userId} must be supported by the {@link UserProfileGroup} i.e. + * {@link #isSupported(int, Context)} should return true for {@code userId}. + */ + public static @ProfileType int getProfileTypeOfUser(@UserIdInt int userId, Context context) { + if (UserUtils.isManagedProfile(userId, context)) { + return PROFILE_TYPE_MANAGED; + } + if (UserUtils.isPrivateProfile(userId, context)) { + return PROFILE_TYPE_PRIVATE; + } + return PROFILE_TYPE_PRIMARY; + } + + /** + * Returns true iff the given userId is contained in this {@link UserProfileGroup} and it's + * running. + */ + boolean containsRunningUserId(@UserIdInt int userId, @ProfileType int profileType) { + switch (profileType) { + case PROFILE_TYPE_PRIMARY: + return true; + case PROFILE_TYPE_MANAGED: + for (int i = 0; i < mManagedRunningProfilesUserIds.length; i++) { + if (mManagedRunningProfilesUserIds[i] == userId) { + return true; + } + } + return false; + case PROFILE_TYPE_PRIVATE: + return mPrivateProfileRunning; + default: + Log.w(TAG, "Unexpected profile type " + profileType); + return false; + } } /** Returns whether the {@link UserProfileGroup} contains the given {@code userId}. */ @@ -248,17 +395,7 @@ public final class UserProfileGroup { } } - return false; - } - - /** Returns whether the given {@code userId} is associated with a running managed profile. */ - boolean isManagedUserRunning(@UserIdInt int userId) { - for (int i = 0; i < mManagedRunningProfilesUserIds.length; i++) { - if (userId == mManagedRunningProfilesUserIds[i]) { - return true; - } - } - return false; + return USER_NULL != mPrivateProfileUserId && userId == mPrivateProfileUserId; } @Override @@ -269,7 +406,9 @@ public final class UserProfileGroup { return mProfileParentUserId == that.mProfileParentUserId && Arrays.equals(mManagedProfilesUserIds, that.mManagedProfilesUserIds) && Arrays.equals( - mManagedRunningProfilesUserIds, that.mManagedRunningProfilesUserIds); + mManagedRunningProfilesUserIds, that.mManagedRunningProfilesUserIds) + && mPrivateProfileUserId == that.mPrivateProfileUserId + && mPrivateProfileRunning == that.mPrivateProfileRunning; } @Override @@ -277,7 +416,9 @@ public final class UserProfileGroup { return Objects.hash( mProfileParentUserId, Arrays.hashCode(mManagedProfilesUserIds), - Arrays.hashCode(mManagedRunningProfilesUserIds)); + Arrays.hashCode(mManagedRunningProfilesUserIds), + mPrivateProfileUserId, + mPrivateProfileRunning); } @Override @@ -289,6 +430,10 @@ public final class UserProfileGroup { + Arrays.toString(mManagedProfilesUserIds) + ", mManagedRunningProfilesUserIds=" + Arrays.toString(mManagedRunningProfilesUserIds) + + ", mPrivateProfileUserId" + + mPrivateProfileUserId + + ", mPrivateProfileRunning" + + mPrivateProfileRunning + '}'; } } diff --git a/service/java/com/android/safetycenter/data/SafetyCenterDataManager.java b/service/java/com/android/safetycenter/data/SafetyCenterDataManager.java index dff7c4339..7385a2c3b 100644 --- a/service/java/com/android/safetycenter/data/SafetyCenterDataManager.java +++ b/service/java/com/android/safetycenter/data/SafetyCenterDataManager.java @@ -41,6 +41,7 @@ import com.android.safetycenter.SafetyCenterRefreshTracker; import com.android.safetycenter.SafetySourceIssueInfo; import com.android.safetycenter.SafetySourceKey; import com.android.safetycenter.UserProfileGroup; +import com.android.safetycenter.UserProfileGroup.ProfileType; import com.android.safetycenter.internaldata.SafetyCenterIssueActionId; import com.android.safetycenter.internaldata.SafetyCenterIssueKey; import com.android.safetycenter.logging.SafetyCenterStatsdLogger; @@ -567,7 +568,7 @@ public final class SafetyCenterDataManager { * Writes a SafetySourceStateCollected atom for the given source in response to a stats pull. */ public void logSafetySourceStateCollectedAutomatic( - SafetySourceKey sourceKey, boolean isManagedProfile) { - mSafetySourceStateCollectedLogger.writeAutomaticAtom(sourceKey, isManagedProfile); + SafetySourceKey sourceKey, @ProfileType int profileType) { + mSafetySourceStateCollectedLogger.writeAutomaticAtom(sourceKey, profileType); } } diff --git a/service/java/com/android/safetycenter/data/SafetyCenterInFlightIssueActionRepository.java b/service/java/com/android/safetycenter/data/SafetyCenterInFlightIssueActionRepository.java index 82eb3a6c7..39809aa6f 100644 --- a/service/java/com/android/safetycenter/data/SafetyCenterInFlightIssueActionRepository.java +++ b/service/java/com/android/safetycenter/data/SafetyCenterInFlightIssueActionRepository.java @@ -28,8 +28,8 @@ import android.util.Log; import androidx.annotation.Nullable; -import com.android.permission.util.UserUtils; import com.android.safetycenter.SafetySourceIssues; +import com.android.safetycenter.UserProfileGroup; import com.android.safetycenter.internaldata.SafetyCenterIssueActionId; import com.android.safetycenter.internaldata.SafetyCenterIssueKey; import com.android.safetycenter.logging.SafetyCenterStatsdLogger; @@ -87,7 +87,7 @@ final class SafetyCenterInFlightIssueActionRepository { SafetyCenterStatsdLogger.writeInlineActionSystemEvent( issueKey.getSafetySourceId(), - UserUtils.isManagedProfile(issueKey.getUserId(), mContext), + UserProfileGroup.getProfileTypeOfUser(issueKey.getUserId(), mContext), issueTypeId, duration, result); diff --git a/service/java/com/android/safetycenter/data/SafetyCenterIssueRepository.java b/service/java/com/android/safetycenter/data/SafetyCenterIssueRepository.java index 2e6f707a3..3806584a8 100644 --- a/service/java/com/android/safetycenter/data/SafetyCenterIssueRepository.java +++ b/service/java/com/android/safetycenter/data/SafetyCenterIssueRepository.java @@ -31,12 +31,12 @@ import android.safetycenter.config.SafetySourcesGroup; import android.util.SparseArray; import com.android.modules.utils.build.SdkLevel; -import com.android.permission.util.UserUtils; import com.android.safetycenter.SafetyCenterConfigReader; import com.android.safetycenter.SafetySourceIssueInfo; import com.android.safetycenter.SafetySourceKey; import com.android.safetycenter.SafetySources; import com.android.safetycenter.UserProfileGroup; +import com.android.safetycenter.UserProfileGroup.ProfileType; import com.android.safetycenter.internaldata.SafetyCenterIssueKey; import java.io.PrintWriter; @@ -88,12 +88,12 @@ final class SafetyCenterIssueRepository { * that can affect issues. */ void updateIssues(@UserIdInt int userId) { - updateIssues(userId, UserUtils.isManagedProfile(userId, mContext)); + updateIssues(userId, UserProfileGroup.getProfileTypeOfUser(userId, mContext)); } - private void updateIssues(@UserIdInt int userId, boolean isManagedProfile) { + private void updateIssues(@UserIdInt int userId, @ProfileType int profileType) { List<SafetySourceIssueInfo> issues = - getAllStoredIssuesFromRawSourceData(userId, isManagedProfile); + getAllStoredIssuesFromRawSourceData(userId, profileType); issues.sort(SAFETY_SOURCE_ISSUES_INFO_BY_SEVERITY_DESCENDING); @@ -183,14 +183,14 @@ final class SafetyCenterIssueRepository { } private List<SafetySourceIssueInfo> getAllStoredIssuesFromRawSourceData( - @UserIdInt int userId, boolean isManagedProfile) { + @UserIdInt int userId, @ProfileType int profileType) { List<SafetySourceIssueInfo> allIssuesInfo = new ArrayList<>(); List<SafetySourcesGroup> safetySourcesGroups = mSafetyCenterConfigReader.getSafetySourcesGroups(); for (int j = 0; j < safetySourcesGroups.size(); j++) { addSafetySourceIssuesInfo( - allIssuesInfo, safetySourcesGroups.get(j), userId, isManagedProfile); + allIssuesInfo, safetySourcesGroups.get(j), userId, profileType); } return allIssuesInfo; @@ -200,7 +200,7 @@ final class SafetyCenterIssueRepository { List<SafetySourceIssueInfo> issuesInfo, SafetySourcesGroup safetySourcesGroup, @UserIdInt int userId, - boolean isManagedProfile) { + @ProfileType int profileType) { List<SafetySource> safetySources = safetySourcesGroup.getSafetySources(); for (int i = 0; i < safetySources.size(); i++) { SafetySource safetySource = safetySources.get(i); @@ -208,7 +208,7 @@ final class SafetyCenterIssueRepository { if (!SafetySources.isExternal(safetySource)) { continue; } - if (isManagedProfile && !SafetySources.supportsManagedProfiles(safetySource)) { + if (!SafetySources.supportsProfileType(safetySource, profileType)) { continue; } @@ -244,12 +244,11 @@ final class SafetyCenterIssueRepository { * UserProfileGroup}. */ private List<SafetySourceIssueInfo> getIssuesFor(UserProfileGroup userProfileGroup) { - List<SafetySourceIssueInfo> issues = - new ArrayList<>(getIssuesForUser(userProfileGroup.getProfileParentUserId())); + List<SafetySourceIssueInfo> issues = new ArrayList<>(); - int[] managedRunningProfileUserIds = userProfileGroup.getManagedRunningProfilesUserIds(); - for (int i = 0; i < managedRunningProfileUserIds.length; i++) { - issues.addAll(getIssuesForUser(managedRunningProfileUserIds[i])); + int[] allRunningProfileUserIds = userProfileGroup.getAllRunningProfilesUserIds(); + for (int i = 0; i < allRunningProfileUserIds.length; i++) { + issues.addAll(getIssuesForUser(allRunningProfileUserIds[i])); } return issues; diff --git a/service/java/com/android/safetycenter/data/SafetySourceDataValidator.java b/service/java/com/android/safetycenter/data/SafetySourceDataValidator.java index 942e4ce18..4b74b0440 100644 --- a/service/java/com/android/safetycenter/data/SafetySourceDataValidator.java +++ b/service/java/com/android/safetycenter/data/SafetySourceDataValidator.java @@ -29,10 +29,10 @@ import android.util.Log; import androidx.annotation.Nullable; import com.android.modules.utils.build.SdkLevel; -import com.android.permission.util.UserUtils; import com.android.safetycenter.SafetyCenterConfigReader; import com.android.safetycenter.SafetyCenterFlags; import com.android.safetycenter.SafetySources; +import com.android.safetycenter.UserProfileGroup; import java.util.List; import java.util.Set; @@ -90,10 +90,14 @@ final class SafetySourceDataValidator { validateCallingPackage(safetySource, packageName, safetySourceId); } - if (UserUtils.isManagedProfile(userId, mContext) - && !SafetySources.supportsManagedProfiles(safetySource)) { + @UserProfileGroup.ProfileType int profileType = + UserProfileGroup.getProfileTypeOfUser(userId, mContext); + if (!SafetySources.supportsProfileType(safetySource, profileType)) { throw new IllegalArgumentException( - "Unexpected managed profile request for safety source: " + safetySourceId); + "Unexpected profile type: " + + profileType + + " for safety source: " + + safetySourceId); } boolean retrievingOrClearingData = safetySourceData == null; diff --git a/service/java/com/android/safetycenter/data/SafetySourceStateCollectedLogger.java b/service/java/com/android/safetycenter/data/SafetySourceStateCollectedLogger.java index e73459598..1bf8685bf 100644 --- a/service/java/com/android/safetycenter/data/SafetySourceStateCollectedLogger.java +++ b/service/java/com/android/safetycenter/data/SafetySourceStateCollectedLogger.java @@ -26,9 +26,10 @@ import android.safetycenter.SafetySourceStatus; import androidx.annotation.Nullable; -import com.android.permission.util.UserUtils; import com.android.safetycenter.SafetySourceIssueInfo; import com.android.safetycenter.SafetySourceKey; +import com.android.safetycenter.UserProfileGroup; +import com.android.safetycenter.UserProfileGroup.ProfileType; import com.android.safetycenter.internaldata.SafetyCenterIssueKey; import com.android.safetycenter.logging.SafetyCenterStatsdLogger; @@ -63,13 +64,13 @@ final class SafetySourceStateCollectedLogger { /** * Writes a SafetySourceStateCollected atom for the given source in response to a stats pull. */ - void writeAutomaticAtom(SafetySourceKey sourceKey, boolean isManagedProfile) { + void writeAutomaticAtom(SafetySourceKey sourceKey, @ProfileType int profileType) { logSafetySourceStateCollected( sourceKey, mSourceDataRepository.getSafetySourceData(sourceKey), /* refreshReason= */ null, /* sourceDataDiffers= */ false, - isManagedProfile, + profileType, /* safetyEvent= */ null, mSourceDataRepository.getSafetySourceLastUpdated(sourceKey)); } @@ -90,7 +91,7 @@ final class SafetySourceStateCollectedLogger { safetySourceData, refreshReason, sourceDataDiffers, - UserUtils.isManagedProfile(userId, mContext), + UserProfileGroup.getProfileTypeOfUser(userId, mContext), safetyEvent, /* lastUpdatedElapsedTimeMillis= */ null); } @@ -100,7 +101,7 @@ final class SafetySourceStateCollectedLogger { @Nullable SafetySourceData sourceData, @Nullable @SafetyCenterManager.RefreshReason Integer refreshReason, boolean sourceDataDiffers, - boolean isManagedProfile, + @ProfileType int profileType, @Nullable SafetyEvent safetyEvent, @Nullable @ElapsedRealtimeLong Long lastUpdatedElapsedTimeMillis) { SafetySourceStatus sourceStatus = sourceData == null ? null : sourceData.getStatus(); @@ -131,7 +132,7 @@ final class SafetySourceStateCollectedLogger { Integer severityLevel = maxSeverityLevel > Integer.MIN_VALUE ? maxSeverityLevel : null; SafetyCenterStatsdLogger.writeSafetySourceStateCollected( sourceKey.getSourceId(), - isManagedProfile, + profileType, severityLevel, openIssuesCount, dismissedIssuesCount, diff --git a/service/java/com/android/safetycenter/logging/SafetyCenterPullAtomCallback.java b/service/java/com/android/safetycenter/logging/SafetyCenterPullAtomCallback.java index 168d73a0f..a8dc7568e 100644 --- a/service/java/com/android/safetycenter/logging/SafetyCenterPullAtomCallback.java +++ b/service/java/com/android/safetycenter/logging/SafetyCenterPullAtomCallback.java @@ -38,6 +38,7 @@ import com.android.safetycenter.SafetyCenterFlags; import com.android.safetycenter.SafetySourceKey; import com.android.safetycenter.SafetySources; import com.android.safetycenter.UserProfileGroup; +import com.android.safetycenter.UserProfileGroup.ProfileType; import com.android.safetycenter.data.SafetyCenterDataManager; import java.util.List; @@ -149,19 +150,19 @@ public final class SafetyCenterPullAtomCallback implements StatsPullAtomCallback continue; } - writeSafetySourceStateCollectedAtomLocked( - loggableSource, - userProfileGroup.getProfileParentUserId(), - /* isUserManaged= */ false); - - if (!SafetySources.supportsManagedProfiles(loggableSource)) { - continue; - } - - int[] managedIds = userProfileGroup.getManagedRunningProfilesUserIds(); - for (int k = 0; k < managedIds.length; k++) { - writeSafetySourceStateCollectedAtomLocked( - loggableSource, managedIds[k], /* isUserManaged= */ true); + for (int profileTypeIdx = 0; + profileTypeIdx < ProfileType.ALL_PROFILE_TYPES.length; + ++profileTypeIdx) { + @ProfileType int profileType = ProfileType.ALL_PROFILE_TYPES[profileTypeIdx]; + if (!SafetySources.supportsProfileType(loggableSource, profileType)) { + continue; + } + + int[] profileIds = userProfileGroup.getProfilesOfType(profileType); + for (int profileIdx = 0; profileIdx < profileIds.length; profileIdx++) { + writeSafetySourceStateCollectedAtomLocked( + loggableSource, profileIds[profileIdx], profileType); + } } } } @@ -169,8 +170,8 @@ public final class SafetyCenterPullAtomCallback implements StatsPullAtomCallback @GuardedBy("mApiLock") private void writeSafetySourceStateCollectedAtomLocked( - SafetySource safetySource, @UserIdInt int userId, boolean isUserManaged) { + SafetySource safetySource, @UserIdInt int userId, @ProfileType int profileType) { SafetySourceKey sourceKey = SafetySourceKey.of(safetySource.getId(), userId); - mDataManager.logSafetySourceStateCollectedAutomatic(sourceKey, isUserManaged); + mDataManager.logSafetySourceStateCollectedAutomatic(sourceKey, profileType); } } diff --git a/service/java/com/android/safetycenter/logging/SafetyCenterStatsdLogger.java b/service/java/com/android/safetycenter/logging/SafetyCenterStatsdLogger.java index 710c3f7ac..3311d0c1f 100644 --- a/service/java/com/android/safetycenter/logging/SafetyCenterStatsdLogger.java +++ b/service/java/com/android/safetycenter/logging/SafetyCenterStatsdLogger.java @@ -25,6 +25,8 @@ import static com.android.permission.PermissionStatsLog.SAFETY_CENTER_INTERACTIO import static com.android.permission.PermissionStatsLog.SAFETY_CENTER_INTERACTION_REPORTED__NAVIGATION_SOURCE__SOURCE_UNKNOWN; import static com.android.permission.PermissionStatsLog.SAFETY_CENTER_INTERACTION_REPORTED__SAFETY_SOURCE_PROFILE_TYPE__PROFILE_TYPE_MANAGED; import static com.android.permission.PermissionStatsLog.SAFETY_CENTER_INTERACTION_REPORTED__SAFETY_SOURCE_PROFILE_TYPE__PROFILE_TYPE_PERSONAL; +import static com.android.permission.PermissionStatsLog.SAFETY_CENTER_INTERACTION_REPORTED__SAFETY_SOURCE_PROFILE_TYPE__PROFILE_TYPE_PRIVATE; +import static com.android.permission.PermissionStatsLog.SAFETY_CENTER_INTERACTION_REPORTED__SAFETY_SOURCE_PROFILE_TYPE__PROFILE_TYPE_UNKNOWN; import static com.android.permission.PermissionStatsLog.SAFETY_CENTER_INTERACTION_REPORTED__SENSOR__SENSOR_UNKNOWN; import static com.android.permission.PermissionStatsLog.SAFETY_CENTER_INTERACTION_REPORTED__SEVERITY_LEVEL__SAFETY_SEVERITY_CRITICAL_WARNING; import static com.android.permission.PermissionStatsLog.SAFETY_CENTER_INTERACTION_REPORTED__SEVERITY_LEVEL__SAFETY_SEVERITY_OK; @@ -43,12 +45,15 @@ import static com.android.permission.PermissionStatsLog.SAFETY_CENTER_SYSTEM_EVE import static com.android.permission.PermissionStatsLog.SAFETY_CENTER_SYSTEM_EVENT_REPORTED__RESULT__TIMEOUT; import static com.android.permission.PermissionStatsLog.SAFETY_CENTER_SYSTEM_EVENT_REPORTED__SAFETY_SOURCE_PROFILE_TYPE__PROFILE_TYPE_MANAGED; import static com.android.permission.PermissionStatsLog.SAFETY_CENTER_SYSTEM_EVENT_REPORTED__SAFETY_SOURCE_PROFILE_TYPE__PROFILE_TYPE_PERSONAL; +import static com.android.permission.PermissionStatsLog.SAFETY_CENTER_SYSTEM_EVENT_REPORTED__SAFETY_SOURCE_PROFILE_TYPE__PROFILE_TYPE_PRIVATE; import static com.android.permission.PermissionStatsLog.SAFETY_CENTER_SYSTEM_EVENT_REPORTED__SAFETY_SOURCE_PROFILE_TYPE__PROFILE_TYPE_UNKNOWN; import static com.android.permission.PermissionStatsLog.SAFETY_SOURCE_STATE_COLLECTED; import static com.android.permission.PermissionStatsLog.SAFETY_SOURCE_STATE_COLLECTED__COLLECTION_TYPE__AUTOMATIC; import static com.android.permission.PermissionStatsLog.SAFETY_SOURCE_STATE_COLLECTED__COLLECTION_TYPE__SOURCE_UPDATED; import static com.android.permission.PermissionStatsLog.SAFETY_SOURCE_STATE_COLLECTED__SAFETY_SOURCE_PROFILE_TYPE__PROFILE_TYPE_MANAGED; import static com.android.permission.PermissionStatsLog.SAFETY_SOURCE_STATE_COLLECTED__SAFETY_SOURCE_PROFILE_TYPE__PROFILE_TYPE_PERSONAL; +import static com.android.permission.PermissionStatsLog.SAFETY_SOURCE_STATE_COLLECTED__SAFETY_SOURCE_PROFILE_TYPE__PROFILE_TYPE_PRIVATE; +import static com.android.permission.PermissionStatsLog.SAFETY_SOURCE_STATE_COLLECTED__SAFETY_SOURCE_PROFILE_TYPE__PROFILE_TYPE_UNKNOWN; import static com.android.permission.PermissionStatsLog.SAFETY_SOURCE_STATE_COLLECTED__SEVERITY_LEVEL__SAFETY_SEVERITY_CRITICAL_WARNING; import static com.android.permission.PermissionStatsLog.SAFETY_SOURCE_STATE_COLLECTED__SEVERITY_LEVEL__SAFETY_SEVERITY_LEVEL_UNKNOWN; import static com.android.permission.PermissionStatsLog.SAFETY_SOURCE_STATE_COLLECTED__SEVERITY_LEVEL__SAFETY_SEVERITY_OK; @@ -69,6 +74,9 @@ import static com.android.permission.PermissionStatsLog.SAFETY_STATE__OVERALL_SE import static com.android.permission.PermissionStatsLog.SAFETY_STATE__OVERALL_SEVERITY_LEVEL__SAFETY_SEVERITY_LEVEL_UNKNOWN; import static com.android.permission.PermissionStatsLog.SAFETY_STATE__OVERALL_SEVERITY_LEVEL__SAFETY_SEVERITY_OK; import static com.android.permission.PermissionStatsLog.SAFETY_STATE__OVERALL_SEVERITY_LEVEL__SAFETY_SEVERITY_RECOMMENDATION; +import static com.android.safetycenter.UserProfileGroup.PROFILE_TYPE_MANAGED; +import static com.android.safetycenter.UserProfileGroup.PROFILE_TYPE_PRIMARY; +import static com.android.safetycenter.UserProfileGroup.PROFILE_TYPE_PRIVATE; import android.annotation.ElapsedRealtimeLong; import android.annotation.IntDef; @@ -84,6 +92,7 @@ import androidx.annotation.Nullable; import com.android.permission.PermissionStatsLog; import com.android.safetycenter.SafetyCenterFlags; +import com.android.safetycenter.UserProfileGroup.ProfileType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -161,7 +170,7 @@ public final class SafetyCenterStatsdLogger { /** Writes a {@link PermissionStatsLog#SAFETY_SOURCE_STATE_COLLECTED} atom. */ public static void writeSafetySourceStateCollected( String sourceId, - boolean isManagedProfile, + @ProfileType int profileType, @Nullable @SafetySourceData.SeverityLevel Integer sourceSeverityLevel, long openIssuesCount, long dismissedIssuesCount, @@ -181,7 +190,7 @@ public final class SafetyCenterStatsdLogger { PermissionStatsLog.write( SAFETY_SOURCE_STATE_COLLECTED, idStringToLong(sourceId), - toSourceStateCollectedProfileType(isManagedProfile), + toSourceStateCollectedProfileType(profileType), toSafetySourceStateCollectedSeverityLevel(sourceSeverityLevel), openIssuesCount, dismissedIssuesCount, @@ -203,7 +212,7 @@ public final class SafetyCenterStatsdLogger { public static void writeSourceRefreshSystemEvent( @RefreshRequestType int refreshType, String sourceId, - boolean isManagedProfile, + @ProfileType int profileType, Duration duration, @SystemEventResult int result, long refreshReason, @@ -215,7 +224,7 @@ public final class SafetyCenterStatsdLogger { SAFETY_CENTER_SYSTEM_EVENT_REPORTED, toSourceRefreshEventType(refreshType), idStringToLong(sourceId), - toSystemEventProfileType(isManagedProfile), + toSystemEventProfileType(profileType), UNSET_ISSUE_TYPE_ID, duration.toMillis(), result, @@ -254,7 +263,7 @@ public final class SafetyCenterStatsdLogger { */ public static void writeInlineActionSystemEvent( String sourceId, - boolean isManagedProfile, + @ProfileType int profileType, @Nullable String issueTypeId, Duration duration, @SystemEventResult int result) { @@ -265,7 +274,7 @@ public final class SafetyCenterStatsdLogger { SAFETY_CENTER_SYSTEM_EVENT_REPORTED, SAFETY_CENTER_SYSTEM_EVENT_REPORTED__EVENT_TYPE__INLINE_ACTION, idStringToLong(sourceId), - toSystemEventProfileType(isManagedProfile), + toSystemEventProfileType(profileType), issueTypeId == null ? UNSET_ISSUE_TYPE_ID : idStringToLong(issueTypeId), duration.toMillis(), result, @@ -279,13 +288,13 @@ public final class SafetyCenterStatsdLogger { */ public static void writeNotificationPostedEvent( String sourceId, - boolean isManagedProfile, + @ProfileType int profileType, String issueTypeId, @SafetySourceData.SeverityLevel int sourceSeverityLevel) { writeNotificationInteractionReportedEvent( SAFETY_CENTER_INTERACTION_REPORTED__ACTION__NOTIFICATION_POSTED, sourceId, - isManagedProfile, + profileType, issueTypeId, sourceSeverityLevel); } @@ -296,13 +305,13 @@ public final class SafetyCenterStatsdLogger { */ public static void writeNotificationDismissedEvent( String sourceId, - boolean isManagedProfile, + @ProfileType int profileType, String issueTypeId, @SafetySourceData.SeverityLevel int sourceSeverityLevel) { writeNotificationInteractionReportedEvent( SAFETY_CENTER_INTERACTION_REPORTED__ACTION__NOTIFICATION_DISMISSED, sourceId, - isManagedProfile, + profileType, issueTypeId, sourceSeverityLevel); } @@ -313,7 +322,7 @@ public final class SafetyCenterStatsdLogger { */ public static void writeNotificationActionClickedEvent( String sourceId, - boolean isManagedProfile, + @ProfileType int profileType, String issueTypeId, @SafetySourceData.SeverityLevel int sourceSeverityLevel, boolean isPrimaryAction) { @@ -322,13 +331,13 @@ public final class SafetyCenterStatsdLogger { ? SAFETY_CENTER_INTERACTION_REPORTED__ACTION__ISSUE_PRIMARY_ACTION_CLICKED : SAFETY_CENTER_INTERACTION_REPORTED__ACTION__ISSUE_SECONDARY_ACTION_CLICKED; writeNotificationInteractionReportedEvent( - action, sourceId, isManagedProfile, issueTypeId, sourceSeverityLevel); + action, sourceId, profileType, issueTypeId, sourceSeverityLevel); } private static void writeNotificationInteractionReportedEvent( int interactionReportedAction, String sourceId, - boolean isManagedProfile, + @ProfileType int profileType, String issueTypeId, @SafetySourceData.SeverityLevel int sourceSeverityLevel) { if (!SafetyCenterFlags.getAllowStatsdLogging()) { @@ -342,7 +351,7 @@ public final class SafetyCenterStatsdLogger { SAFETY_CENTER_INTERACTION_REPORTED__NAVIGATION_SOURCE__SOURCE_UNKNOWN, toInteractionReportedSeverityLevel(sourceSeverityLevel), idStringToLong(sourceId), - toInteractionReportedProfileType(isManagedProfile), + toInteractionReportedProfileType(profileType), idStringToLong(issueTypeId), SAFETY_CENTER_INTERACTION_REPORTED__SENSOR__SENSOR_UNKNOWN, UNSET_SOURCE_GROUP_ID, @@ -382,22 +391,43 @@ public final class SafetyCenterStatsdLogger { return SAFETY_CENTER_SYSTEM_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_UNKNOWN; } - private static int toSourceStateCollectedProfileType(boolean isManagedProfile) { - return isManagedProfile - ? SAFETY_SOURCE_STATE_COLLECTED__SAFETY_SOURCE_PROFILE_TYPE__PROFILE_TYPE_MANAGED - : SAFETY_SOURCE_STATE_COLLECTED__SAFETY_SOURCE_PROFILE_TYPE__PROFILE_TYPE_PERSONAL; + private static int toSourceStateCollectedProfileType(@ProfileType int profileType) { + switch (profileType) { + case PROFILE_TYPE_PRIMARY: + return SAFETY_SOURCE_STATE_COLLECTED__SAFETY_SOURCE_PROFILE_TYPE__PROFILE_TYPE_PERSONAL; + case PROFILE_TYPE_MANAGED: + return SAFETY_SOURCE_STATE_COLLECTED__SAFETY_SOURCE_PROFILE_TYPE__PROFILE_TYPE_MANAGED; + case PROFILE_TYPE_PRIVATE: + return SAFETY_SOURCE_STATE_COLLECTED__SAFETY_SOURCE_PROFILE_TYPE__PROFILE_TYPE_PRIVATE; + } + Log.w(TAG, "state collect arg requested for unknown profile type " + profileType); + return SAFETY_SOURCE_STATE_COLLECTED__SAFETY_SOURCE_PROFILE_TYPE__PROFILE_TYPE_UNKNOWN; } - private static int toSystemEventProfileType(boolean isManagedProfile) { - return isManagedProfile - ? SAFETY_CENTER_SYSTEM_EVENT_REPORTED__SAFETY_SOURCE_PROFILE_TYPE__PROFILE_TYPE_MANAGED - : SAFETY_CENTER_SYSTEM_EVENT_REPORTED__SAFETY_SOURCE_PROFILE_TYPE__PROFILE_TYPE_PERSONAL; + private static int toSystemEventProfileType(@ProfileType int profileType) { + switch (profileType) { + case PROFILE_TYPE_PRIMARY: + return SAFETY_CENTER_SYSTEM_EVENT_REPORTED__SAFETY_SOURCE_PROFILE_TYPE__PROFILE_TYPE_PERSONAL; + case PROFILE_TYPE_MANAGED: + return SAFETY_CENTER_SYSTEM_EVENT_REPORTED__SAFETY_SOURCE_PROFILE_TYPE__PROFILE_TYPE_MANAGED; + case PROFILE_TYPE_PRIVATE: + return SAFETY_CENTER_SYSTEM_EVENT_REPORTED__SAFETY_SOURCE_PROFILE_TYPE__PROFILE_TYPE_PRIVATE; + } + Log.w(TAG, "system event arg requested for unknown profile type " + profileType); + return SAFETY_CENTER_SYSTEM_EVENT_REPORTED__SAFETY_SOURCE_PROFILE_TYPE__PROFILE_TYPE_UNKNOWN; } - private static int toInteractionReportedProfileType(boolean isManagedProfile) { - return isManagedProfile - ? SAFETY_CENTER_INTERACTION_REPORTED__SAFETY_SOURCE_PROFILE_TYPE__PROFILE_TYPE_MANAGED - : SAFETY_CENTER_INTERACTION_REPORTED__SAFETY_SOURCE_PROFILE_TYPE__PROFILE_TYPE_PERSONAL; + private static int toInteractionReportedProfileType(@ProfileType int profileType) { + switch (profileType) { + case PROFILE_TYPE_PRIMARY: + return SAFETY_CENTER_INTERACTION_REPORTED__SAFETY_SOURCE_PROFILE_TYPE__PROFILE_TYPE_PERSONAL; + case PROFILE_TYPE_MANAGED: + return SAFETY_CENTER_INTERACTION_REPORTED__SAFETY_SOURCE_PROFILE_TYPE__PROFILE_TYPE_MANAGED; + case PROFILE_TYPE_PRIVATE: + return SAFETY_CENTER_INTERACTION_REPORTED__SAFETY_SOURCE_PROFILE_TYPE__PROFILE_TYPE_PRIVATE; + } + Log.w(TAG, "interaction enum requested for unknown profile type " + profileType); + return SAFETY_CENTER_INTERACTION_REPORTED__SAFETY_SOURCE_PROFILE_TYPE__PROFILE_TYPE_UNKNOWN; } /** diff --git a/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationReceiver.java b/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationReceiver.java index ed0e95177..6cfa39580 100644 --- a/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationReceiver.java +++ b/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationReceiver.java @@ -27,7 +27,6 @@ import android.util.Log; import androidx.annotation.Nullable; import com.android.internal.annotations.GuardedBy; -import com.android.permission.util.UserUtils; import com.android.safetycenter.ApiLock; import com.android.safetycenter.PendingIntentFactory; import com.android.safetycenter.SafetyCenterDataChangeNotifier; @@ -214,7 +213,7 @@ public final class SafetyCenterNotificationReceiver extends BroadcastReceiver { if (dismissedIssue != null) { SafetyCenterStatsdLogger.writeNotificationDismissedEvent( issueKey.getSafetySourceId(), - UserUtils.isManagedProfile(userId, context), + UserProfileGroup.getProfileTypeOfUser(userId, context), dismissedIssue.getIssueTypeId(), dismissedIssue.getSeverityLevel()); } @@ -240,7 +239,7 @@ public final class SafetyCenterNotificationReceiver extends BroadcastReceiver { if (issue != null) { SafetyCenterStatsdLogger.writeNotificationActionClickedEvent( issueKey.getSafetySourceId(), - UserUtils.isManagedProfile(issueKey.getUserId(), context), + UserProfileGroup.getProfileTypeOfUser(issueKey.getUserId(), context), issue.getIssueTypeId(), issue.getSeverityLevel(), SafetySourceIssues.isPrimaryAction( diff --git a/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationSender.java b/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationSender.java index d17090c34..2e298fa90 100644 --- a/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationSender.java +++ b/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationSender.java @@ -37,7 +37,6 @@ import android.util.Log; import androidx.annotation.Nullable; import com.android.modules.utils.build.SdkLevel; -import com.android.permission.util.UserUtils; import com.android.safetycenter.SafetyCenterFlags; import com.android.safetycenter.SafetySourceIssueInfo; import com.android.safetycenter.SafetySourceIssues; @@ -210,11 +209,9 @@ public final class SafetyCenterNotificationSender { /** Updates Safety Center notifications for the given {@link UserProfileGroup}. */ public void updateNotifications(UserProfileGroup userProfileGroup) { - updateNotifications(userProfileGroup.getProfileParentUserId()); - - int[] managedProfileUserIds = userProfileGroup.getManagedProfilesUserIds(); - for (int i = 0; i < managedProfileUserIds.length; i++) { - updateNotifications(managedProfileUserIds[i]); + int[] allProfilesUserIds = userProfileGroup.getAllProfilesUserIds(); + for (int i = 0; i < allProfilesUserIds.length; i++) { + updateNotifications(allProfilesUserIds[i]); } } @@ -387,7 +384,7 @@ public final class SafetyCenterNotificationSender { mNotifiedIssues.put(key, issue); SafetyCenterStatsdLogger.writeNotificationPostedEvent( key.getSafetySourceId(), - UserUtils.isManagedProfile(key.getUserId(), mContext), + UserProfileGroup.getProfileTypeOfUser(key.getUserId(), mContext), issue.getIssueTypeId(), issue.getSeverityLevel()); } diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourceTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourceTest.kt index 59cc6547a..c5c5c218d 100644 --- a/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourceTest.kt +++ b/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourceTest.kt @@ -18,11 +18,13 @@ package android.safetycenter.cts.config import android.content.res.Resources import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE +import android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM import android.safetycenter.config.SafetySource import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.truth.os.ParcelableSubject.assertThat import androidx.test.filters.SdkSuppress import com.android.modules.utils.build.SdkLevel +import com.android.permission.flags.Flags import com.android.safetycenter.testing.EqualsHashCodeToStringTester import com.google.common.truth.Truth.assertThat import org.junit.Assert.assertThrows @@ -126,6 +128,31 @@ class SafetySourceTest { } } + @SdkSuppress(minSdkVersion = VANILLA_ICE_CREAM) + @Test + fun getTitleForPrivateProfileResId_returnsTitleForPrivateProfileResIdOrThrows() { + assertThrows(UnsupportedOperationException::class.java) { + DYNAMIC_BAREBONE.titleForPrivateProfileResId + } + assertThat(dynamicAllOptional().titleForPrivateProfileResId).isEqualTo(REFERENCE_RES_ID) + assertThrows(UnsupportedOperationException::class.java) { + DYNAMIC_DISABLED.titleForPrivateProfileResId + } + assertThat(DYNAMIC_HIDDEN.titleForPrivateProfileResId).isEqualTo(Resources.ID_NULL) + assertThat(DYNAMIC_HIDDEN_WITH_SEARCH.titleForPrivateProfileResId) + .isEqualTo(REFERENCE_RES_ID) + assertThrows(UnsupportedOperationException::class.java) { + STATIC_BAREBONE.titleForPrivateProfileResId + } + assertThat(STATIC_ALL_OPTIONAL.titleForPrivateProfileResId).isEqualTo(REFERENCE_RES_ID) + assertThrows(UnsupportedOperationException::class.java) { + ISSUE_ONLY_BAREBONE.titleForPrivateProfileResId + } + assertThrows(UnsupportedOperationException::class.java) { + issueOnlyAllOptional().titleForPrivateProfileResId + } + } + @Test fun getSummaryResId_returnsSummaryResIdOrThrows() { assertThat(DYNAMIC_BAREBONE.summaryResId).isEqualTo(REFERENCE_RES_ID) @@ -360,6 +387,9 @@ class SafetySourceTest { setDeduplicationGroup(DEDUPLICATION_GROUP) addPackageCertificateHash(HASH1) } + if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) { + setTitleForPrivateProfileResId(REFERENCE_RES_ID) + } } .build() ) @@ -390,6 +420,9 @@ class SafetySourceTest { setDeduplicationGroup(DEDUPLICATION_GROUP) addPackageCertificateHash(HASH1) } + if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) { + setTitleForPrivateProfileResId(REFERENCE_RES_ID) + } } .build() ) @@ -413,6 +446,9 @@ class SafetySourceTest { setDeduplicationGroup(DEDUPLICATION_GROUP) addPackageCertificateHash(HASH1) } + if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) { + setTitleForPrivateProfileResId(REFERENCE_RES_ID) + } } .build() ) @@ -436,6 +472,9 @@ class SafetySourceTest { setDeduplicationGroup(DEDUPLICATION_GROUP) addPackageCertificateHash(HASH1) } + if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) { + setTitleForPrivateProfileResId(REFERENCE_RES_ID) + } } .build() ) @@ -459,6 +498,9 @@ class SafetySourceTest { setDeduplicationGroup(DEDUPLICATION_GROUP) addPackageCertificateHash(HASH1) } + if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) { + setTitleForPrivateProfileResId(REFERENCE_RES_ID) + } } .build() ) @@ -482,6 +524,9 @@ class SafetySourceTest { setDeduplicationGroup(DEDUPLICATION_GROUP) addPackageCertificateHash(HASH1) } + if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) { + setTitleForPrivateProfileResId(REFERENCE_RES_ID) + } } .build() ) @@ -505,6 +550,9 @@ class SafetySourceTest { setDeduplicationGroup(DEDUPLICATION_GROUP) addPackageCertificateHash(HASH1) } + if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) { + setTitleForPrivateProfileResId(REFERENCE_RES_ID) + } } .build() ) @@ -536,6 +584,9 @@ class SafetySourceTest { setDeduplicationGroup(DEDUPLICATION_GROUP) addPackageCertificateHash(HASH1) } + if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) { + setTitleForPrivateProfileResId(REFERENCE_RES_ID) + } } .build() ) @@ -559,6 +610,9 @@ class SafetySourceTest { setDeduplicationGroup(DEDUPLICATION_GROUP) addPackageCertificateHash(HASH1) } + if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) { + setTitleForPrivateProfileResId(REFERENCE_RES_ID) + } } .build() ) @@ -582,6 +636,9 @@ class SafetySourceTest { setDeduplicationGroup(DEDUPLICATION_GROUP) addPackageCertificateHash(HASH1) } + if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) { + setTitleForPrivateProfileResId(REFERENCE_RES_ID) + } } .build() ) @@ -605,6 +662,9 @@ class SafetySourceTest { setDeduplicationGroup(DEDUPLICATION_GROUP) addPackageCertificateHash(HASH1) } + if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) { + setTitleForPrivateProfileResId(REFERENCE_RES_ID) + } } .build() ) @@ -628,6 +688,9 @@ class SafetySourceTest { setDeduplicationGroup(DEDUPLICATION_GROUP) addPackageCertificateHash(HASH1) } + if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) { + setTitleForPrivateProfileResId(REFERENCE_RES_ID) + } } .build() ) @@ -650,6 +713,11 @@ class SafetySourceTest { .setNotificationsAllowed(false) .setDeduplicationGroup(DEDUPLICATION_GROUP) .addPackageCertificateHash(HASH1) + .apply { + if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) { + setTitleForPrivateProfileResId(REFERENCE_RES_ID) + } + } .build() ) addEqualityGroup( @@ -669,6 +737,11 @@ class SafetySourceTest { .setNotificationsAllowed(true) .setDeduplicationGroup("other_deduplication_group") .addPackageCertificateHash(HASH1) + .apply { + if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) { + setTitleForPrivateProfileResId(REFERENCE_RES_ID) + } + } .build() ) // With no package cert hashes provided @@ -688,6 +761,11 @@ class SafetySourceTest { .setRefreshOnPageOpenAllowed(true) .setNotificationsAllowed(true) .setDeduplicationGroup(DEDUPLICATION_GROUP) + .apply { + if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) { + setTitleForPrivateProfileResId(REFERENCE_RES_ID) + } + } .build() ) // With longer package cert hash list @@ -709,6 +787,11 @@ class SafetySourceTest { .setDeduplicationGroup(DEDUPLICATION_GROUP) .addPackageCertificateHash(HASH1) .addPackageCertificateHash(HASH2) + .apply { + if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) { + setTitleForPrivateProfileResId(REFERENCE_RES_ID) + } + } .build() ) // With package cert hash list with different value @@ -729,6 +812,11 @@ class SafetySourceTest { .setNotificationsAllowed(true) .setDeduplicationGroup(DEDUPLICATION_GROUP) .addPackageCertificateHash(HASH2) + .apply { + if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) { + setTitleForPrivateProfileResId(REFERENCE_RES_ID) + } + } .build() ) } @@ -785,6 +873,9 @@ class SafetySourceTest { setDeduplicationGroup(DEDUPLICATION_GROUP) addPackageCertificateHash(HASH1) } + if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) { + setTitleForPrivateProfileResId(REFERENCE_RES_ID) + } } .build() @@ -817,6 +908,11 @@ class SafetySourceTest { .setProfile(SafetySource.PROFILE_ALL) .setInitialDisplayState(SafetySource.INITIAL_DISPLAY_STATE_HIDDEN) .setSearchTermsResId(REFERENCE_RES_ID) + .apply { + if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) { + setTitleForPrivateProfileResId(REFERENCE_RES_ID) + } + } .build() internal val STATIC_BAREBONE = @@ -837,6 +933,11 @@ class SafetySourceTest { .setIntentAction(INTENT_ACTION) .setProfile(SafetySource.PROFILE_ALL) .setSearchTermsResId(REFERENCE_RES_ID) + .apply { + if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) { + setTitleForPrivateProfileResId(REFERENCE_RES_ID) + } + } .build() internal val ISSUE_ONLY_BAREBONE = diff --git a/tests/functional/safetycenter/multiusers/Android.bp b/tests/functional/safetycenter/multiusers/Android.bp index b5caf48e6..2f1cda9ed 100644 --- a/tests/functional/safetycenter/multiusers/Android.bp +++ b/tests/functional/safetycenter/multiusers/Android.bp @@ -36,6 +36,7 @@ android_test { "Harrier", "Nene", "TestApp", + "permissions-aconfig-flags-lib", ], test_suites: [ "general-tests", diff --git a/tests/functional/safetycenter/multiusers/src/android/safetycenter/functional/multiusers/SafetyCenterMultiUsersTest.kt b/tests/functional/safetycenter/multiusers/src/android/safetycenter/functional/multiusers/SafetyCenterMultiUsersTest.kt index acbc5cfc0..2eb1a6c48 100644 --- a/tests/functional/safetycenter/multiusers/src/android/safetycenter/functional/multiusers/SafetyCenterMultiUsersTest.kt +++ b/tests/functional/safetycenter/multiusers/src/android/safetycenter/functional/multiusers/SafetyCenterMultiUsersTest.kt @@ -21,6 +21,8 @@ import android.Manifest.permission.INTERACT_ACROSS_USERS_FULL import android.app.PendingIntent import android.content.Context import android.os.UserHandle +import android.platform.test.annotations.RequiresFlagsDisabled +import android.platform.test.annotations.RequiresFlagsEnabled import android.safetycenter.SafetyCenterData import android.safetycenter.SafetyCenterEntry import android.safetycenter.SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_CRITICAL_WARNING @@ -43,6 +45,7 @@ import com.android.bedstead.harrier.DeviceState import com.android.bedstead.harrier.annotations.EnsureHasAdditionalUser import com.android.bedstead.harrier.annotations.EnsureHasCloneProfile import com.android.bedstead.harrier.annotations.EnsureHasNoWorkProfile +import com.android.bedstead.harrier.annotations.EnsureHasPrivateProfile import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile import com.android.bedstead.harrier.annotations.enterprise.EnsureHasDeviceOwner import com.android.bedstead.harrier.annotations.enterprise.EnsureHasNoDeviceOwner @@ -227,6 +230,13 @@ class SafetyCenterMultiUsersTest { .setEnabled(false) .build() + private val dynamicDisabledForPrivateUpdated: SafetyCenterEntry + get() = + safetyCenterEntryOkForPrivate(DYNAMIC_DISABLED_ID, deviceState.privateProfile().id()) + + private val dynamicHiddenForPrivateUpdated: SafetyCenterEntry + get() = safetyCenterEntryOkForPrivate(DYNAMIC_HIDDEN_ID, deviceState.privateProfile().id()) + private val staticGroupBuilder = SafetyCenterEntryGroup.Builder(STATIC_GROUP_ID, "OK") .setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNSPECIFIED) @@ -267,6 +277,24 @@ class SafetyCenterMultiUsersTest { .setEnabled(false) .build() + private val staticAllOptionalForPrivateBuilder + get() = + safetyCenterTestData + .safetyCenterEntryDefaultStaticBuilder( + STATIC_ALL_OPTIONAL_ID, + userId = deviceState.privateProfile().id(), + title = "Unknown" + ) + .setPendingIntent( + createTestActivityRedirectPendingIntentForUser( + deviceState.privateProfile().userHandle(), + explicit = false + ) + ) + + private val staticAllOptionalForPrivate + get() = staticAllOptionalForPrivateBuilder.build() + private fun createStaticEntry(explicit: Boolean = true): SafetyCenterStaticEntry = SafetyCenterStaticEntry.Builder("OK") .setSummary("OK") @@ -292,9 +320,25 @@ class SafetyCenterMultiUsersTest { ) ) + private fun staticEntryForPrivateBuilder( + title: CharSequence = "Unknown", + explicit: Boolean = true + ) = + SafetyCenterStaticEntry.Builder(title) + .setSummary("OK") + .setPendingIntent( + createTestActivityRedirectPendingIntentForUser( + deviceState.privateProfile().userHandle(), + explicit + ) + ) + private fun createStaticEntryForWork(explicit: Boolean = true): SafetyCenterStaticEntry = staticEntryForWorkBuilder(explicit = explicit).build() + private fun createStaticEntryForPrivate(explicit: Boolean = true): SafetyCenterStaticEntry = + staticEntryForPrivateBuilder(explicit = explicit).build() + private fun createStaticEntryForWorkPaused(): SafetyCenterStaticEntry = staticEntryForWorkBuilder(explicit = false) .setSummary(safetyCenterResourcesApk.getStringByName("work_profile_paused")) @@ -313,6 +357,13 @@ class SafetyCenterMultiUsersTest { .setPendingIntent(safetySourceTestData.createTestActivityRedirectPendingIntent()) .build() + private val staticEntryForPrivateUpdated: SafetyCenterStaticEntry + get() = + SafetyCenterStaticEntry.Builder("Unspecified title for Private") + .setSummary("Unspecified summary") + .setPendingIntent(safetySourceTestData.createTestActivityRedirectPendingIntent()) + .build() + private val safetyCenterDataForAdditionalUser get() = SafetyCenterData( @@ -657,10 +708,122 @@ class SafetyCenterMultiUsersTest { @Test @EnsureHasWorkProfile(installInstrumentedApp = TRUE) - fun getSafetyCenterData_withComplexConfigWithAllDataProvided_returnsAllDataProvided() { + fun getSafetyCenterData_withComplexConfigWithExtraWorkOnlyWithAllDataProvided_returnsAllDataProvided() { + safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.complexAllProfileConfig) + updatePrimaryProfileSources() + updateWorkProfileSources() + + val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission() + + val managedUserId = deviceState.workProfile().id() + val safetyCenterDataFromComplexConfig = + SafetyCenterData( + safetyCenterTestData.safetyCenterStatusCritical(11), + listOf( + safetyCenterTestData.safetyCenterIssueCritical( + DYNAMIC_BAREBONE_ID, + groupId = DYNAMIC_GROUP_ID + ), + safetyCenterTestData.safetyCenterIssueCritical( + ISSUE_ONLY_BAREBONE_ID, + attributionTitle = null, + groupId = ISSUE_ONLY_GROUP_ID + ), + safetyCenterTestData.safetyCenterIssueRecommendation( + DYNAMIC_DISABLED_ID, + groupId = DYNAMIC_GROUP_ID + ), + safetyCenterTestData.safetyCenterIssueRecommendation( + ISSUE_ONLY_ALL_OPTIONAL_ID, + attributionTitle = null, + groupId = ISSUE_ONLY_GROUP_ID + ), + safetyCenterTestData.safetyCenterIssueInformation( + DYNAMIC_IN_STATELESS_ID, + groupId = MIXED_STATELESS_GROUP_ID + ), + safetyCenterTestData.safetyCenterIssueInformation( + ISSUE_ONLY_IN_STATELESS_ID, + groupId = MIXED_STATELESS_GROUP_ID + ), + safetyCenterTestData.safetyCenterIssueInformation( + DYNAMIC_DISABLED_ID, + managedUserId, + groupId = DYNAMIC_GROUP_ID + ), + safetyCenterTestData.safetyCenterIssueInformation( + DYNAMIC_HIDDEN_ID, + managedUserId, + groupId = DYNAMIC_GROUP_ID + ), + safetyCenterTestData.safetyCenterIssueInformation( + ISSUE_ONLY_ALL_OPTIONAL_ID, + managedUserId, + attributionTitle = null, + groupId = ISSUE_ONLY_GROUP_ID + ), + safetyCenterTestData.safetyCenterIssueInformation( + DYNAMIC_IN_STATELESS_ID, + managedUserId, + groupId = MIXED_STATELESS_GROUP_ID + ), + safetyCenterTestData.safetyCenterIssueInformation( + ISSUE_ONLY_IN_STATELESS_ID, + managedUserId, + groupId = MIXED_STATELESS_GROUP_ID + ) + ), + listOf( + SafetyCenterEntryOrGroup( + SafetyCenterEntryGroup.Builder(DYNAMIC_GROUP_ID, "OK") + .setSeverityLevel(ENTRY_SEVERITY_LEVEL_CRITICAL_WARNING) + .setSummary("Critical summary") + .setEntries( + listOf( + dynamicBareboneUpdated, + dynamicDisabledUpdated, + dynamicDisabledForWorkUpdated, + dynamicHiddenUpdated, + dynamicHiddenForWorkUpdated + ) + ) + .setSeverityUnspecifiedIconType( + SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION + ) + .build() + ), + SafetyCenterEntryOrGroup( + staticGroupBuilder + .setEntries( + listOf(staticBarebone, staticAllOptional, staticAllOptionalForWork) + ) + .build() + ) + ), + listOf( + SafetyCenterStaticEntryGroup( + "OK", + listOf( + staticEntryUpdated, + staticEntryForWorkUpdated, + createStaticEntry(explicit = false), + createStaticEntryForWork(explicit = false) + ) + ) + ) + ) + assertThat(apiSafetyCenterData.withoutExtras()).isEqualTo(safetyCenterDataFromComplexConfig) + } + + @Test + @RequiresFlagsDisabled(com.android.permission.flags.Flags.FLAG_PRIVATE_PROFILE_SUPPORTED) + @EnsureHasWorkProfile(installInstrumentedApp = TRUE) + @EnsureHasPrivateProfile(installInstrumentedApp = TRUE) + fun getSafetyCenterData_withComplexConfigWithPrivateProfileDisallowedWithAllDataProvided_returnsAllDataProvided() { safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.complexAllProfileConfig) updatePrimaryProfileSources() updateWorkProfileSources() + updatePrivateProfileSources() val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission() @@ -764,6 +927,155 @@ class SafetyCenterMultiUsersTest { assertThat(apiSafetyCenterData.withoutExtras()).isEqualTo(safetyCenterDataFromComplexConfig) } + // TODO(b/286539356) add the os feature flag requirement when available. + @Test + @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_PRIVATE_PROFILE_SUPPORTED) + @EnsureHasWorkProfile(installInstrumentedApp = TRUE) + @EnsureHasPrivateProfile(installInstrumentedApp = TRUE) + fun getSafetyCenterData_withComplexConfigWithAllDataProvided_returnsAllDataProvided() { + safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.complexAllProfileConfig) + updatePrimaryProfileSources() + updateWorkProfileSources() + updatePrivateProfileSources() + + val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission() + + val managedUserId = deviceState.workProfile().id() + val privateProfileId = deviceState.privateProfile().id() + val safetyCenterDataFromComplexConfig = + SafetyCenterData( + safetyCenterTestData.safetyCenterStatusCritical(11), + listOf( + safetyCenterTestData.safetyCenterIssueCritical( + DYNAMIC_BAREBONE_ID, + groupId = DYNAMIC_GROUP_ID + ), + safetyCenterTestData.safetyCenterIssueCritical( + ISSUE_ONLY_BAREBONE_ID, + attributionTitle = null, + groupId = ISSUE_ONLY_GROUP_ID + ), + safetyCenterTestData.safetyCenterIssueRecommendation( + DYNAMIC_DISABLED_ID, + groupId = DYNAMIC_GROUP_ID + ), + safetyCenterTestData.safetyCenterIssueRecommendation( + ISSUE_ONLY_ALL_OPTIONAL_ID, + attributionTitle = null, + groupId = ISSUE_ONLY_GROUP_ID + ), + safetyCenterTestData.safetyCenterIssueInformation( + DYNAMIC_IN_STATELESS_ID, + groupId = MIXED_STATELESS_GROUP_ID + ), + safetyCenterTestData.safetyCenterIssueInformation( + ISSUE_ONLY_IN_STATELESS_ID, + groupId = MIXED_STATELESS_GROUP_ID + ), + safetyCenterTestData.safetyCenterIssueInformation( + DYNAMIC_DISABLED_ID, + managedUserId, + groupId = DYNAMIC_GROUP_ID + ), + safetyCenterTestData.safetyCenterIssueInformation( + DYNAMIC_HIDDEN_ID, + managedUserId, + groupId = DYNAMIC_GROUP_ID + ), + safetyCenterTestData.safetyCenterIssueInformation( + ISSUE_ONLY_ALL_OPTIONAL_ID, + managedUserId, + attributionTitle = null, + groupId = ISSUE_ONLY_GROUP_ID + ), + safetyCenterTestData.safetyCenterIssueInformation( + DYNAMIC_IN_STATELESS_ID, + managedUserId, + groupId = MIXED_STATELESS_GROUP_ID + ), + safetyCenterTestData.safetyCenterIssueInformation( + ISSUE_ONLY_IN_STATELESS_ID, + managedUserId, + groupId = MIXED_STATELESS_GROUP_ID + ), + safetyCenterTestData.safetyCenterIssueInformation( + DYNAMIC_DISABLED_ID, + privateProfileId, + groupId = DYNAMIC_GROUP_ID + ), + safetyCenterTestData.safetyCenterIssueInformation( + DYNAMIC_HIDDEN_ID, + privateProfileId, + groupId = DYNAMIC_GROUP_ID + ), + safetyCenterTestData.safetyCenterIssueInformation( + ISSUE_ONLY_ALL_OPTIONAL_ID, + privateProfileId, + attributionTitle = null, + groupId = ISSUE_ONLY_GROUP_ID + ), + safetyCenterTestData.safetyCenterIssueInformation( + DYNAMIC_IN_STATELESS_ID, + privateProfileId, + groupId = MIXED_STATELESS_GROUP_ID + ), + safetyCenterTestData.safetyCenterIssueInformation( + ISSUE_ONLY_IN_STATELESS_ID, + privateProfileId, + groupId = MIXED_STATELESS_GROUP_ID + ) + ), + listOf( + SafetyCenterEntryOrGroup( + SafetyCenterEntryGroup.Builder(DYNAMIC_GROUP_ID, "OK") + .setSeverityLevel(ENTRY_SEVERITY_LEVEL_CRITICAL_WARNING) + .setSummary("Critical summary") + .setEntries( + listOf( + dynamicBareboneUpdated, + dynamicDisabledUpdated, + dynamicDisabledForWorkUpdated, + dynamicDisabledForPrivateUpdated, + dynamicHiddenUpdated, + dynamicHiddenForWorkUpdated, + dynamicHiddenForPrivateUpdated + ) + ) + .setSeverityUnspecifiedIconType( + SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION + ) + .build() + ), + SafetyCenterEntryOrGroup( + staticGroupBuilder + .setEntries( + listOf( + staticBarebone, + staticAllOptional, + staticAllOptionalForWork, + staticAllOptionalForPrivate + ) + ) + .build() + ) + ), + listOf( + SafetyCenterStaticEntryGroup( + "OK", + listOf( + staticEntryUpdated, + staticEntryForWorkUpdated, + staticEntryForPrivateUpdated, + createStaticEntry(explicit = false), + createStaticEntryForWork(explicit = false), + createStaticEntryForPrivate(explicit = false) + ) + ) + ) + ) + assertThat(apiSafetyCenterData.withoutExtras()).isEqualTo(safetyCenterDataFromComplexConfig) + } + @Test @EnsureHasWorkProfile(installInstrumentedApp = TRUE) fun getSafetyCenterData_withQuietMode_shouldHaveWorkProfilePausedSummaryAndNoWorkIssues() { @@ -920,6 +1232,79 @@ class SafetyCenterMultiUsersTest { } @Test + @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_PRIVATE_PROFILE_SUPPORTED) + @EnsureHasPrivateProfile(installInstrumentedApp = TRUE) + fun getSafetyCenterData_afterPrivateProfileRemoved_returnsDefaultData() { + safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceAllProfileConfig) + val privateSafetyCenterManager = + getSafetyCenterManagerForUser(deviceState.privateProfile().userHandle()) + val safetyCenterDataWithPrivateProfile = + SafetyCenterData( + safetyCenterTestData.safetyCenterStatusUnknown, + emptyList(), + listOf( + SafetyCenterEntryOrGroup( + SafetyCenterEntryGroup.Builder(SINGLE_SOURCE_GROUP_ID, "OK") + .setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNKNOWN) + .setSummary( + safetyCenterResourcesApk.getStringByName("group_unknown_summary") + ) + .setEntries( + listOf( + safetyCenterTestData.safetyCenterEntryDefault( + SINGLE_SOURCE_ALL_PROFILE_ID + ), + safetyCenterTestData.safetyCenterEntryDefault( + SINGLE_SOURCE_ALL_PROFILE_ID, + deviceState.privateProfile().id(), + title = "Unknown", + pendingIntent = + createTestActivityRedirectPendingIntentForUser( + deviceState.privateProfile().userHandle() + ) + ) + ) + ) + .setSeverityUnspecifiedIconType( + SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION + ) + .build() + ) + ), + emptyList() + ) + + checkState( + safetyCenterManager.getSafetyCenterDataWithPermission() == + safetyCenterDataWithPrivateProfile + ) + checkState( + privateSafetyCenterManager.getSafetyCenterDataWithInteractAcrossUsersPermission() == + safetyCenterDataWithPrivateProfile + ) + + deviceState.privateProfile().remove() + + val safetyCenterDataForPrimaryUser = + SafetyCenterData( + safetyCenterTestData.safetyCenterStatusUnknown, + emptyList(), + listOf( + SafetyCenterEntryOrGroup( + safetyCenterTestData.safetyCenterEntryDefault(SINGLE_SOURCE_ALL_PROFILE_ID) + ) + ), + emptyList() + ) + assertThat(safetyCenterManager.getSafetyCenterDataWithPermission()) + .isEqualTo(safetyCenterDataForPrimaryUser) + assertThat( + privateSafetyCenterManager.getSafetyCenterDataWithInteractAcrossUsersPermission() + ) + .isEqualTo(SafetyCenterTestData.DEFAULT) + } + + @Test @EnsureHasAdditionalUser(installInstrumentedApp = TRUE) fun getSafetyCenterData_afterAdditionalUserRemoved_returnsDefaultData() { safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceAllProfileConfig) @@ -1290,6 +1675,11 @@ class SafetyCenterMultiUsersTest { .safetyCenterEntryOkBuilder(sourceId, managedUserId, title = "Ok title for Work") .build() + private fun safetyCenterEntryOkForPrivate(sourceId: String, managedUserId: Int) = + safetyCenterTestData + .safetyCenterEntryOkBuilder(sourceId, managedUserId, title = "Ok title for Private") + .build() + private fun updatePrimaryProfileSources() { safetyCenterTestHelper.setData( DYNAMIC_BAREBONE_ID, @@ -1342,4 +1732,29 @@ class SafetyCenterMultiUsersTest { SafetySourceTestData.issuesOnly(safetySourceTestData.informationIssue) ) } + + private fun updatePrivateProfileSources() { + val privateSafetyCenterManager = + getSafetyCenterManagerForUser(deviceState.privateProfile().userHandle()) + privateSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission( + DYNAMIC_DISABLED_ID, + safetySourceTestData.informationWithIssueForPrivate + ) + privateSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission( + DYNAMIC_HIDDEN_ID, + safetySourceTestData.informationWithIssueForPrivate + ) + privateSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission( + ISSUE_ONLY_ALL_OPTIONAL_ID, + SafetySourceTestData.issuesOnly(safetySourceTestData.informationIssue) + ) + privateSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission( + DYNAMIC_IN_STATELESS_ID, + safetySourceTestData.unspecifiedWithIssueForPrivate + ) + privateSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission( + ISSUE_ONLY_IN_STATELESS_ID, + SafetySourceTestData.issuesOnly(safetySourceTestData.informationIssue) + ) + } } diff --git a/tests/utils/safetycenter/Android.bp b/tests/utils/safetycenter/Android.bp index 6accefae9..8514b0662 100644 --- a/tests/utils/safetycenter/Android.bp +++ b/tests/utils/safetycenter/Android.bp @@ -36,6 +36,7 @@ android_library { "kotlinx-coroutines-android", "safety-center-internal-data", "safety-center-resources-lib", + "permissions-aconfig-flags-lib", ], apex_available: [ "com.android.permission", diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestConfigs.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestConfigs.kt index fd3749094..a4600e88b 100644 --- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestConfigs.kt +++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestConfigs.kt @@ -31,6 +31,7 @@ import android.safetycenter.config.SafetySource.SAFETY_SOURCE_TYPE_STATIC import android.safetycenter.config.SafetySourcesGroup import androidx.annotation.RequiresApi import com.android.modules.utils.build.SdkLevel +import com.android.permission.flags.Flags import com.android.safetycenter.testing.SettingsPackage.getSettingsPackageName import java.security.MessageDigest @@ -686,6 +687,11 @@ class SafetyCenterTestConfigs(private val context: Context) { .setSummaryResId(Resources.ID_NULL) .setIntentAction(null) .setInitialDisplayState(SafetySource.INITIAL_DISPLAY_STATE_HIDDEN) + .apply { + if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) { + setTitleForPrivateProfileResId(Resources.ID_NULL) + } + } .build() ) .build() @@ -788,6 +794,11 @@ class SafetyCenterTestConfigs(private val context: Context) { dynamicSafetySourceBuilder(id) .setProfile(SafetySource.PROFILE_ALL) .setTitleForWorkResId(android.R.string.paste) + .apply { + if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) { + setTitleForPrivateProfileResId(android.R.string.paste) + } + } private fun staticSafetySource(id: String) = staticSafetySourceBuilder(id).build() @@ -803,6 +814,11 @@ class SafetyCenterTestConfigs(private val context: Context) { staticSafetySourceBuilder(id) .setProfile(SafetySource.PROFILE_ALL) .setTitleForWorkResId(android.R.string.paste) + .apply { + if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) { + setTitleForPrivateProfileResId(android.R.string.unknownName) + } + } @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) private fun issueOnlySafetySourceWithDuplicationInfo(id: String, deduplicationGroup: String) = diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt index 66be55fa8..559215c0c 100644 --- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt +++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt @@ -175,6 +175,24 @@ class SafetySourceTestData(private val context: Context) { .addIssue(informationIssue) .build() + /** + * A [SafetySourceData] with a [SEVERITY_LEVEL_INFORMATION] redirecting [SafetySourceIssue] and + * a [SEVERITY_LEVEL_UNSPECIFIED] [SafetySourceStatus], to be used for a private profile entry. + */ + val unspecifiedWithIssueForPrivate = + SafetySourceData.Builder() + .setStatus( + SafetySourceStatus.Builder( + "Unspecified title for Private", + "Unspecified summary", + SEVERITY_LEVEL_UNSPECIFIED + ) + .setPendingIntent(createTestActivityRedirectPendingIntent()) + .build() + ) + .addIssue(informationIssue) + .build() + /** A [SafetySourceData] with a [SEVERITY_LEVEL_INFORMATION] [SafetySourceStatus]. */ val information = SafetySourceData.Builder() @@ -284,6 +302,24 @@ class SafetySourceTestData(private val context: Context) { /** * A [SafetySourceData] with a [SEVERITY_LEVEL_INFORMATION] redirecting [SafetySourceIssue] and + * [SafetySourceStatus], to be used for a private profile entry. + */ + val informationWithIssueForPrivate = + SafetySourceData.Builder() + .setStatus( + SafetySourceStatus.Builder( + "Ok title for Private", + "Ok summary", + SEVERITY_LEVEL_INFORMATION + ) + .setPendingIntent(createTestActivityRedirectPendingIntent()) + .build() + ) + .addIssue(informationIssue) + .build() + + /** + * A [SafetySourceData] with a [SEVERITY_LEVEL_INFORMATION] redirecting [SafetySourceIssue] and * [SafetySourceStatus]. */ val informationWithSubtitleIssue = |