diff options
28 files changed, 609 insertions, 143 deletions
diff --git a/PermissionController/res/layout-v33/preference_issue_card.xml b/PermissionController/res/layout-v33/preference_issue_card.xml index e6d749142..107c778a1 100644 --- a/PermissionController/res/layout-v33/preference_issue_card.xml +++ b/PermissionController/res/layout-v33/preference_issue_card.xml @@ -13,81 +13,84 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - -<androidx.constraintlayout.widget.ConstraintLayout +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:id="@+id/issue_card" - android:clickable="false" - android:screenReaderFocusable="true" - style="@style/SafetyCenterCard.Issue"> + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <androidx.constraintlayout.widget.ConstraintLayout + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/issue_card" + android:clickable="false" + android:screenReaderFocusable="true" + style="@style/SafetyCenterCard.Issue"> - <ImageButton - android:id="@+id/issue_card_dismiss_btn" - android:src="@drawable/ic_safety_issue_dismiss" - android:contentDescription="@string/safety_center_issue_card_dismiss_button" - style="@style/SafetyCenterIssueDismiss" /> + <ImageButton + android:id="@+id/issue_card_dismiss_btn" + android:src="@drawable/ic_safety_issue_dismiss" + android:contentDescription="@string/safety_center_issue_card_dismiss_button" + style="@style/SafetyCenterIssueDismiss" /> - <TextView - android:id="@+id/issue_card_attribution_title" - android:text="@string/summary_placeholder" - android:screenReaderFocusable="false" - style="@style/SafetyCenterIssueAttributionTitle" /> + <TextView + android:id="@+id/issue_card_attribution_title" + android:text="@string/summary_placeholder" + android:screenReaderFocusable="false" + style="@style/SafetyCenterIssueAttributionTitle" /> - <TextView - android:id="@+id/issue_card_title" - android:text="@string/summary_placeholder" - android:screenReaderFocusable="false" - style="@style/SafetyCenterIssueTitle" /> + <TextView + android:id="@+id/issue_card_title" + android:text="@string/summary_placeholder" + android:screenReaderFocusable="false" + style="@style/SafetyCenterIssueTitle" /> - <TextView - android:id="@+id/issue_card_subtitle" - android:text="@string/summary_placeholder" - android:screenReaderFocusable="false" - style="@style/SafetyCenterIssueSubtitle" /> + <TextView + android:id="@+id/issue_card_subtitle" + android:text="@string/summary_placeholder" + android:screenReaderFocusable="false" + style="@style/SafetyCenterIssueSubtitle" /> - <TextView - android:id="@+id/issue_card_summary" - android:text="@string/summary_placeholder" - android:screenReaderFocusable="false" - style="@style/SafetyCenterIssueSummary" /> + <TextView + android:id="@+id/issue_card_summary" + android:text="@string/summary_placeholder" + android:screenReaderFocusable="false" + style="@style/SafetyCenterIssueSummary" /> - <include - android:id="@+id/issue_card_action_button_list" - layout="?attr/scActionButtonListLayout"/> + <include + android:id="@+id/issue_card_action_button_list" + layout="?attr/scActionButtonListLayout"/> - <com.android.permissioncontroller.permission.ui.v33.widget.SafetyProtectionSectionView - android:id="@+id/issue_card_protected_by_android" - android:importantForAccessibility="no" - style="@style/SafetyCenterIssueSafetyProtectionSection" /> + <com.android.permissioncontroller.permission.ui.v33.widget.SafetyProtectionSectionView + android:id="@+id/issue_card_protected_by_android" + android:importantForAccessibility="no" + style="@style/SafetyCenterIssueSafetyProtectionSection" /> - <ImageView - android:id="@+id/resolved_issue_image" - android:src="@drawable/safety_center_issue_resolved_avd" - android:importantForAccessibility="no" - style="@style/SafetyCenterIssueCardResolvedImage" /> + <ImageView + android:id="@+id/resolved_issue_image" + android:src="@drawable/safety_center_issue_resolved_avd" + android:importantForAccessibility="no" + style="@style/SafetyCenterIssueCardResolvedImage" /> - <TextView - android:id="@+id/resolved_issue_text" - android:text="@string/safety_center_resolved_issue_fallback" - style="@style/SafetyCenterIssueCardResolvedTitle" /> + <TextView + android:id="@+id/resolved_issue_text" + android:text="@string/safety_center_resolved_issue_fallback" + style="@style/SafetyCenterIssueCardResolvedTitle" /> - <!-- This group doesn't contain issue_card_attribution_title, issue_card_dismiss_btn, - issue_card_subtitle or issue_card_protected_by_android since the version of - ConstraintLayout we're using doesn't allow us to override the group's visibility on - individual group members. See b/242705351 for context. --> - <androidx.constraintlayout.widget.Group - android:id="@+id/default_issue_content" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:visibility="visible" - app:constraint_referenced_ids="issue_card_title,issue_card_summary,issue_card_action_button_list" /> + <!-- This group doesn't contain issue_card_attribution_title, issue_card_dismiss_btn, + issue_card_subtitle or issue_card_protected_by_android since the version of + ConstraintLayout we're using doesn't allow us to override the group's visibility on + individual group members. See b/242705351 for context. --> + <androidx.constraintlayout.widget.Group + android:id="@+id/default_issue_content" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="visible" + app:constraint_referenced_ids="issue_card_title,issue_card_summary,issue_card_action_button_list" /> - <androidx.constraintlayout.widget.Group - android:id="@+id/resolved_issue_content" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:visibility="gone" - app:constraint_referenced_ids="resolved_issue_image,resolved_issue_text" /> + <androidx.constraintlayout.widget.Group + android:id="@+id/resolved_issue_content" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + app:constraint_referenced_ids="resolved_issue_image,resolved_issue_text" /> -</androidx.constraintlayout.widget.ConstraintLayout> + </androidx.constraintlayout.widget.ConstraintLayout> +</FrameLayout>
\ No newline at end of file diff --git a/PermissionController/res/layout-v33/preference_more_issues_card.xml b/PermissionController/res/layout-v33/preference_more_issues_card.xml index c93125762..d0ac6b1f5 100644 --- a/PermissionController/res/layout-v33/preference_more_issues_card.xml +++ b/PermissionController/res/layout-v33/preference_more_issues_card.xml @@ -13,8 +13,11 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - -<com.android.permissioncontroller.safetycenter.ui.view.MoreIssuesHeaderView +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/issue_card" - style="@style/SafetyCenterMoreIssuesCollapsed"/> + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <com.android.permissioncontroller.safetycenter.ui.view.MoreIssuesHeaderView + android:id="@+id/more_issues_card" + style="@style/SafetyCenterMoreIssuesCollapsed"/> +</FrameLayout>
\ No newline at end of file diff --git a/PermissionController/res/layout-v33/preference_safety_status.xml b/PermissionController/res/layout-v33/preference_safety_status.xml index 42bf1c22d..17adc035f 100644 --- a/PermissionController/res/layout-v33/preference_safety_status.xml +++ b/PermissionController/res/layout-v33/preference_safety_status.xml @@ -14,7 +14,12 @@ ~ limitations under the License. --> -<com.android.permissioncontroller.safetycenter.ui.view.StatusCardView +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:clickable="false" - style="@style/SafetyCenterCard.Status" />
\ No newline at end of file + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <com.android.permissioncontroller.safetycenter.ui.view.StatusCardView + android:id="@+id/status_card" + android:clickable="false" + style="@style/SafetyCenterCard.Status" /> +</FrameLayout>
\ No newline at end of file diff --git a/PermissionController/res/values-night-v33/themes.xml b/PermissionController/res/values-night-v33/themes.xml index 9b6f638a6..31940ede5 100644 --- a/PermissionController/res/values-night-v33/themes.xml +++ b/PermissionController/res/values-night-v33/themes.xml @@ -52,6 +52,8 @@ @style/SecondarySafetyCenterActionButton.Responsive </item> + <item name="scCardSideMargin">@dimen/sc_spacing_large</item> + <item name="textColorScActionButton">@color/sc_primary_action_button_text</item> <item name="textColorScSecondaryActionButton">?android:attr/textColorPrimary</item> diff --git a/PermissionController/res/values-v33/attrs.xml b/PermissionController/res/values-v33/attrs.xml index 4a417076d..b136ab718 100644 --- a/PermissionController/res/values-v33/attrs.xml +++ b/PermissionController/res/values-v33/attrs.xml @@ -39,8 +39,9 @@ <attr name="scStatusButtonStyle" format="reference" /> <attr name="scActionButtonListLayout" format="reference" /> <attr name="scActionButtonTheme" format="reference" /> - <attr name="scActionButtonStyle" format="reference"/> - <attr name="scSecondaryActionButtonStyle" format="reference"/> + <attr name="scActionButtonStyle" format="reference" /> + <attr name="scSecondaryActionButtonStyle" format="reference" /> + <attr name="scCardSideMargin" format="dimension" /> <attr name="colorScShieldAccent" format="color" /> </resources>
\ No newline at end of file diff --git a/PermissionController/res/values-v33/styles.xml b/PermissionController/res/values-v33/styles.xml index 94344615b..7e959c2f4 100644 --- a/PermissionController/res/values-v33/styles.xml +++ b/PermissionController/res/values-v33/styles.xml @@ -317,8 +317,8 @@ <item name="android:paddingEnd">@dimen/sc_spacing_xxxlarge</item> <item name="android:paddingTop">@dimen/sc_spacing_xxxlarge</item> <item name="android:paddingBottom">@dimen/sc_card_margin_bottom</item> - <item name="android:layout_marginStart">@dimen/sc_spacing_large</item> - <item name="android:layout_marginEnd">@dimen/sc_spacing_large</item> + <item name="android:layout_marginStart">?attr/scCardSideMargin</item> + <item name="android:layout_marginEnd">?attr/scCardSideMargin</item> <item name="android:background">@drawable/safety_center_card_background</item> </style> diff --git a/PermissionController/res/values-v33/themes.xml b/PermissionController/res/values-v33/themes.xml index 82a8ef5b6..a3d1b16c3 100644 --- a/PermissionController/res/values-v33/themes.xml +++ b/PermissionController/res/values-v33/themes.xml @@ -57,6 +57,8 @@ @style/SecondarySafetyCenterActionButton.Fixed </item> + <item name="scCardSideMargin">@dimen/sc_spacing_large</item> + <item name="textColorScActionButton">@color/sc_primary_action_button_text</item> <item name="textColorScSecondaryActionButton">?android:attr/textColorPrimary</item> @@ -69,6 +71,10 @@ <style name="Theme.SafetyCenterQs" parent="Theme.SafetyCenterQsBase" /> + <style name="Theme.SafetyCenterQsExpressive" parent="Theme.SafetyCenterQs"> + <item name="scCardSideMargin">0dp</item> + </style> + <style name="Theme.SafetyCenterBase" parent="Theme.PermissionController.Settings.FilterTouches"> <item name="colorSurface">@color/sc_surface_light</item> <item name="colorSurfaceVariant">@color/sc_surface_variant_light</item> @@ -104,6 +110,8 @@ @style/SecondarySafetyCenterActionButton.Responsive </item> + <item name="scCardSideMargin">@dimen/sc_spacing_large</item> + <item name="textColorScActionButton">@color/sc_primary_action_button_text</item> <item name="textColorScSecondaryActionButton">?android:attr/textColorPrimary</item> @@ -115,4 +123,8 @@ </style> <style name="Theme.SafetyCenter" parent="Theme.SafetyCenterBase" /> + + <style name="Theme.SafetyCenterExpressive" parent="Theme.SafetyCenter"> + <item name="scCardSideMargin">0dp</item> + </style> </resources>
\ No newline at end of file diff --git a/PermissionController/res/xml/roles.xml b/PermissionController/res/xml/roles.xml index 2989d8f96..7b1f84a20 100644 --- a/PermissionController/res/xml/roles.xml +++ b/PermissionController/res/xml/roles.xml @@ -1238,7 +1238,9 @@ <permission-set name="virtual_device" /> <!-- For capturing audio from the app on the device. --> <permission name="android.permission.RECORD_AUDIO" /> - + <permission + name="android.permission.ADD_MIRROR_DISPLAY" + featureFlag="android.companion.virtualdevice.flags.Flags.enableLimitedVdmRole" /> <!--TODO(b/201605314) For calling Telecom framework API for audio streaming--> <!--<permission name="android.permission.PROVIDE_CALL_ENDPOINTS" />--> </permissions> diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/ClickableDisabledSwitchPreference.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/ClickableDisabledSwitchPreference.java index 3ab4faa66..54cb86ff3 100644 --- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/ClickableDisabledSwitchPreference.java +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/ClickableDisabledSwitchPreference.java @@ -26,7 +26,7 @@ import android.view.ViewGroup; import androidx.annotation.RequiresApi; import androidx.fragment.app.Fragment; import androidx.preference.PreferenceViewHolder; -import androidx.preference.SwitchPreference; +import androidx.preference.SwitchPreferenceCompat; import com.android.permissioncontroller.R; import com.android.permissioncontroller.safetycenter.ui.model.PrivacyControlsViewModel; @@ -38,7 +38,7 @@ import com.android.permissioncontroller.safetycenter.ui.model.PrivacyControlsVie * method will not register any changes while it appears disabled. */ @RequiresApi(TIRAMISU) -public class ClickableDisabledSwitchPreference extends SwitchPreference { +public class ClickableDisabledSwitchPreference extends SwitchPreferenceCompat { private boolean mAppearDisabled; diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardPreference.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardPreference.java index 7622270b9..88759797e 100644 --- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardPreference.java +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardPreference.java @@ -52,6 +52,7 @@ import androidx.preference.PreferenceViewHolder; import com.android.modules.utils.build.SdkLevel; import com.android.permissioncontroller.R; import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterViewModel; +import com.android.settingslib.widget.GroupSectionDividerMixin; import com.google.android.material.button.MaterialButton; import com.google.android.material.shape.AbsoluteCornerSize; @@ -62,7 +63,8 @@ import java.util.Objects; /** A preference that displays a card representing a {@link SafetyCenterIssue}. */ @RequiresApi(TIRAMISU) -public class IssueCardPreference extends Preference implements ComparablePreference { +public class IssueCardPreference extends Preference + implements ComparablePreference, GroupSectionDividerMixin { public static final String TAG = IssueCardPreference.class.getSimpleName(); @@ -101,12 +103,13 @@ public class IssueCardPreference extends Preference implements ComparablePrefere public void onBindViewHolder(PreferenceViewHolder holder) { super.onBindViewHolder(holder); - holder.itemView.setBackgroundResource(mPositionInCardList.getBackgroundDrawableResId()); + View issueCardView = holder.itemView.requireViewById(R.id.issue_card); + issueCardView.setBackgroundResource(mPositionInCardList.getBackgroundDrawableResId()); int topMargin = getTopMargin(mPositionInCardList, getContext()); - MarginLayoutParams layoutParams = (MarginLayoutParams) holder.itemView.getLayoutParams(); + MarginLayoutParams layoutParams = (MarginLayoutParams) issueCardView.getLayoutParams(); if (layoutParams.topMargin != topMargin) { layoutParams.topMargin = topMargin; - holder.itemView.setLayoutParams(layoutParams); + issueCardView.setLayoutParams(layoutParams); } // Set default group visibility in case view is being reused @@ -202,19 +205,20 @@ public class IssueCardPreference extends Preference implements ComparablePrefere private void configureSafetyProtectionView(PreferenceViewHolder holder) { View safetyProtectionSectionView = holder.findViewById(R.id.issue_card_protected_by_android); + View issueCard = holder.findViewById(R.id.issue_card); if (safetyProtectionSectionView.getVisibility() == View.GONE) { - holder.itemView.setPaddingRelative( - holder.itemView.getPaddingStart(), - holder.itemView.getPaddingTop(), - holder.itemView.getPaddingEnd(), + issueCard.setPaddingRelative( + issueCard.getPaddingStart(), + issueCard.getPaddingTop(), + issueCard.getPaddingEnd(), /* bottom= */ getContext() .getResources() .getDimensionPixelSize(R.dimen.sc_card_margin_bottom)); } else { - holder.itemView.setPaddingRelative( - holder.itemView.getPaddingStart(), - holder.itemView.getPaddingTop(), - holder.itemView.getPaddingEnd(), + issueCard.setPaddingRelative( + issueCard.getPaddingStart(), + issueCard.getPaddingTop(), + issueCard.getPaddingEnd(), /* bottom= */ 0); } } @@ -427,9 +431,7 @@ public class IssueCardPreference extends Preference implements ComparablePrefere TypedValue buttonThemeValue = new TypedValue(); mContext.getTheme() .resolveAttribute( - R.attr.scActionButtonTheme, - buttonThemeValue, - /* resolveRefs= */ false); + R.attr.scActionButtonTheme, buttonThemeValue, /* resolveRefs= */ false); mContextThemeWrapper = new ContextThemeWrapper(context, buttonThemeValue.data); } diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/MoreIssuesCardPreference.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/MoreIssuesCardPreference.kt index a63e19984..5c86b4515 100644 --- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/MoreIssuesCardPreference.kt +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/MoreIssuesCardPreference.kt @@ -24,6 +24,7 @@ import androidx.preference.Preference import androidx.preference.PreferenceViewHolder import com.android.permissioncontroller.R import com.android.permissioncontroller.safetycenter.ui.view.MoreIssuesHeaderView +import com.android.settingslib.widget.GroupSectionDividerMixin /** A preference that displays a card linking to a list of more {@link SafetyCenterIssue}. */ @RequiresApi(Build.VERSION_CODES.TIRAMISU) @@ -34,8 +35,8 @@ internal class MoreIssuesCardPreference( private var newMoreIssuesCardData: MoreIssuesCardData, private val dismissedOnly: Boolean, val isStaticHeader: Boolean, - private val onClickListener: () -> Unit -) : Preference(context), ComparablePreference { + private val onClickListener: () -> Unit, +) : Preference(context), ComparablePreference, GroupSectionDividerMixin { init { layoutResource = R.layout.preference_more_issues_card @@ -44,11 +45,12 @@ internal class MoreIssuesCardPreference( override fun onBindViewHolder(holder: PreferenceViewHolder) { super.onBindViewHolder(holder) - val issueHeaderView = holder.itemView as MoreIssuesHeaderView + val issueHeaderView = + holder.itemView.requireViewById<MoreIssuesHeaderView>(R.id.more_issues_card) if (isStaticHeader) { issueHeaderView.showStaticHeader( context.getString(R.string.safety_center_dismissed_issues_card_title), - newMoreIssuesCardData.severityLevel + newMoreIssuesCardData.severityLevel, ) } else { issueHeaderView.showExpandableHeader( @@ -62,7 +64,7 @@ internal class MoreIssuesCardPreference( } ), overrideChevronIconResId, - onClickListener + onClickListener, ) } } @@ -94,5 +96,5 @@ internal class MoreIssuesCardPreference( internal data class MoreIssuesCardData( val severityLevel: Int, val hiddenIssueCount: Int, - val isExpanded: Boolean + val isExpanded: Boolean, ) diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyBrandChipPreference.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyBrandChipPreference.kt index 57e4175ca..c5287af53 100644 --- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyBrandChipPreference.kt +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyBrandChipPreference.kt @@ -28,11 +28,12 @@ import androidx.preference.PreferenceViewHolder import com.android.permissioncontroller.Constants.EXTRA_SESSION_ID import com.android.permissioncontroller.R import com.android.permissioncontroller.safetycenter.SafetyCenterConstants +import com.android.settingslib.widget.GroupSectionDividerMixin /** A preference that displays the Security and Privacy brand name on a Safety Center subpage. */ @RequiresApi(UPSIDE_DOWN_CAKE) internal class SafetyBrandChipPreference(context: Context, attrs: AttributeSet) : - Preference(context, attrs) { + Preference(context, attrs), GroupSectionDividerMixin { init { setLayoutResource(R.layout.preference_brand_chip) @@ -67,7 +68,7 @@ internal class SafetyBrandChipPreference(context: Context, attrs: AttributeSet) fun closeSubpage( fragmentActivity: FragmentActivity, fragmentContext: Context, - sessionId: Long + sessionId: Long, ) { val openedFromHomepage = fragmentActivity diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java index c6f2d146f..04206479f 100644 --- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java @@ -29,6 +29,8 @@ import static com.android.permissioncontroller.safetycenter.SafetyCenterConstant import static com.android.permissioncontroller.safetycenter.SafetyCenterConstants.PRIVATE_PROFILE_SUFFIX; import static com.android.permissioncontroller.safetycenter.SafetyCenterConstants.WORK_PROFILE_SUFFIX; +import static java.util.Objects.requireNonNull; + import android.app.ActionBar; import android.content.Intent; import android.content.res.Configuration; @@ -60,6 +62,7 @@ import com.android.permissioncontroller.permission.utils.Utils; import com.android.permissioncontroller.safetycenter.ui.model.PrivacyControlsViewModel.Pref; import com.android.settingslib.activityembedding.ActivityEmbeddingUtils; import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity; +import com.android.settingslib.widget.SettingsThemeHelper; import java.util.List; import java.util.Objects; @@ -78,10 +81,10 @@ public final class SafetyCenterActivity extends CollapsingToolbarBaseActivity { private static final String EXTRA_PREVENT_TRAMPOLINE_TO_SETTINGS = "com.android.permissioncontroller.safetycenter.extra.PREVENT_TRAMPOLINE_TO_SETTINGS"; - private SafetyCenterManager mSafetyCenterManager; + @Nullable private SafetyCenterManager mSafetyCenterManager; @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); mSafetyCenterManager = getSystemService(SafetyCenterManager.class); @@ -93,18 +96,25 @@ public final class SafetyCenterActivity extends CollapsingToolbarBaseActivity { return; } + if (SettingsThemeHelper.isExpressiveTheme(this)) { + // Setting a theme programmatically causes standard preferences to display weirdly. + // See b/377519324. + setTheme(R.style.Theme_SafetyCenterExpressive); + } + Fragment frag; + Intent intent = getIntent(); final boolean maybeOpenSubpage = SafetyCenterUiFlags.getShowSubpages() - && getIntent().getAction().equals(ACTION_SAFETY_CENTER); - if (maybeOpenSubpage && getIntent().hasExtra(EXTRA_SAFETY_SOURCES_GROUP_ID)) { - String groupId = getIntent().getStringExtra(EXTRA_SAFETY_SOURCES_GROUP_ID); + && Objects.equals(intent.getAction(), ACTION_SAFETY_CENTER); + if (maybeOpenSubpage && intent.hasExtra(EXTRA_SAFETY_SOURCES_GROUP_ID)) { + String groupId = intent.getStringExtra(EXTRA_SAFETY_SOURCES_GROUP_ID); frag = openRelevantSubpage(groupId); - } else if (maybeOpenSubpage && getIntent().hasExtra(EXTRA_SETTINGS_FRAGMENT_ARGS_KEY)) { - String preferenceKey = getIntent().getStringExtra(EXTRA_SETTINGS_FRAGMENT_ARGS_KEY); + } else if (maybeOpenSubpage && intent.hasExtra(EXTRA_SETTINGS_FRAGMENT_ARGS_KEY)) { + String preferenceKey = intent.getStringExtra(EXTRA_SETTINGS_FRAGMENT_ARGS_KEY); String groupId = getParentGroupId(preferenceKey); frag = openRelevantSubpage(groupId); - } else if (getIntent().getAction().equals(PRIVACY_CONTROLS_ACTION)) { + } else if (Objects.equals(intent.getAction(), PRIVACY_CONTROLS_ACTION)) { setTitle(R.string.privacy_controls_title); frag = PrivacyControlsFragment.newInstance(); } else { @@ -306,7 +316,8 @@ public final class SafetyCenterActivity extends CollapsingToolbarBaseActivity { return PRIVACY_SOURCES_GROUP_ID; } - SafetyCenterConfig safetyCenterConfig = mSafetyCenterManager.getSafetyCenterConfig(); + SafetyCenterConfig safetyCenterConfig = + requireNonNull(mSafetyCenterManager).getSafetyCenterConfig(); String[] splitKey; if (preferenceKey.endsWith(PERSONAL_PROFILE_SUFFIX)) { splitKey = preferenceKey.split("_" + PERSONAL_PROFILE_SUFFIX); diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java index efbd57080..ed6bc382c 100644 --- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java @@ -56,6 +56,7 @@ import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterUiData import com.android.permissioncontroller.safetycenter.ui.model.StatusUiData; import com.android.safetycenter.internaldata.SafetyCenterBundles; import com.android.safetycenter.resources.SafetyCenterResourcesApk; +import com.android.settingslib.widget.SettingsThemeHelper; import kotlin.Unit; @@ -121,12 +122,16 @@ public final class SafetyCenterDashboardFragment extends SafetyCenterFragment { mEntriesGroup = getPreferenceScreen().findPreference(ENTRIES_GROUP_KEY); mStaticEntriesGroup = getPreferenceScreen().findPreference(STATIC_ENTRIES_GROUP_KEY); + Preference spacerPreference = getPreferenceScreen().findPreference(SPACER_KEY); + if (SettingsThemeHelper.isExpressiveTheme(requireContext())) { + getPreferenceScreen().removePreference(spacerPreference); + } + if (mIsQuickSettingsFragment) { getPreferenceScreen().removePreference(mEntriesGroup); mEntriesGroup = null; getPreferenceScreen().removePreference(mStaticEntriesGroup); mStaticEntriesGroup = null; - Preference spacerPreference = getPreferenceScreen().findPreference(SPACER_KEY); getPreferenceScreen().removePreference(spacerPreference); } getSafetyCenterViewModel().getStatusUiLiveData().observe(this, this::updateStatus); diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterFragment.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterFragment.kt index 9feecf5d4..04503de5e 100644 --- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterFragment.kt +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterFragment.kt @@ -22,7 +22,6 @@ import android.safetycenter.SafetyCenterErrorDetails import android.widget.Toast import androidx.annotation.RequiresApi import androidx.lifecycle.ViewModelProvider -import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceScreen import androidx.recyclerview.widget.RecyclerView import com.android.permissioncontroller.Constants.EXTRA_SESSION_ID @@ -33,10 +32,12 @@ import com.android.permissioncontroller.safetycenter.ui.model.LiveSafetyCenterVi import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterUiData import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterViewModel import com.android.safetycenter.resources.SafetyCenterResourcesApk +import com.android.settingslib.widget.SettingsBasePreferenceFragment +import com.android.settingslib.widget.SettingsThemeHelper /** A base fragment that represents a page in Safety Center. */ @RequiresApi(TIRAMISU) -abstract class SafetyCenterFragment : PreferenceFragmentCompat() { +abstract class SafetyCenterFragment : SettingsBasePreferenceFragment() { lateinit var safetyCenterViewModel: SafetyCenterViewModel lateinit var sameTaskSourceIds: List<String> @@ -51,12 +52,17 @@ abstract class SafetyCenterFragment : PreferenceFragmentCompat() { override fun onCreateAdapter( preferenceScreen: PreferenceScreen - ): RecyclerView.Adapter<RecyclerView.ViewHolder> { + ): RecyclerView.Adapter<out RecyclerView.ViewHolder> { /* The scroll-to-result functionality for settings search is currently implemented only for * subpages i.e. non expand-and-collapse type entries. Hence, we check that the flag is * enabled before using an adapter that does the highlighting and scrolling. */ - val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder> = - if (SafetyCenterUiFlags.getShowSubpages()) { + val adapter: RecyclerView.Adapter<out RecyclerView.ViewHolder> = + if ( + SafetyCenterUiFlags.getShowSubpages() && + !SettingsThemeHelper.isExpressiveTheme(requireContext()) + ) { + // TODO: b/378433878 - Create highlight adapter for settings expressive theme, which + // has a different base class. highlightManager.createAdapter(preferenceScreen) } else { super.onCreateAdapter(preferenceScreen) @@ -80,7 +86,7 @@ abstract class SafetyCenterFragment : PreferenceFragmentCompat() { safetyCenterViewModel = ViewModelProvider( requireActivity(), - LiveSafetyCenterViewModelFactory(requireActivity().getApplication()) + LiveSafetyCenterViewModelFactory(requireActivity().getApplication()), ) .get(SafetyCenterViewModel::class.java) safetyCenterViewModel.safetyCenterUiLiveData.observe(this) { uiData: SafetyCenterUiData? -> @@ -177,7 +183,7 @@ abstract class SafetyCenterFragment : PreferenceFragmentCompat() { safetyCenterViewModel.interactionLogger.recordForIssue( Action.SAFETY_CENTER_VIEWED, maybeIssue, - isDismissed = false + isDismissed = false, ) } } diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterQsActivity.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterQsActivity.java index 2ad282449..d9f45cc08 100644 --- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterQsActivity.java +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterQsActivity.java @@ -24,7 +24,9 @@ import android.permission.PermissionManager; import androidx.fragment.app.FragmentActivity; import com.android.modules.utils.build.SdkLevel; +import com.android.permissioncontroller.R; import com.android.permissioncontroller.permission.utils.Utils; +import com.android.settingslib.widget.SettingsThemeHelper; /** Activity for the Safety Center Quick Settings Activity */ public class SafetyCenterQsActivity extends FragmentActivity { @@ -39,6 +41,12 @@ public class SafetyCenterQsActivity extends FragmentActivity { return; } + if (SettingsThemeHelper.isExpressiveTheme(this)) { + // Safe to set expressive theme here since QS doesn't display vanilla preferences. + // See b/377519324. + setTheme(R.style.Theme_SafetyCenterQsExpressive); + } + configureFragment(); } diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterSubpageFragment.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterSubpageFragment.kt index fdade2189..2e5ba49ae 100644 --- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterSubpageFragment.kt +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterSubpageFragment.kt @@ -28,6 +28,7 @@ import com.android.permissioncontroller.safetycenter.ui.SafetyBrandChipPreferenc import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterUiData import com.android.safetycenter.resources.SafetyCenterResourcesApk import com.android.settingslib.widget.FooterPreference +import com.android.settingslib.widget.SettingsThemeHelper /** A fragment that represents a generic subpage in Safety Center. */ @RequiresApi(UPSIDE_DOWN_CAKE) @@ -45,15 +46,16 @@ class SafetyCenterSubpageFragment : SafetyCenterFragment() { setPreferencesFromResource(R.xml.safety_center_subpage, rootKey) sourceGroupId = requireArguments().getString(SOURCE_GROUP_ID_KEY)!! - subpageBrandChip = getPreferenceScreen().findPreference(BRAND_CHIP_KEY)!! - subpageIllustration = getPreferenceScreen().findPreference(ILLUSTRATION_KEY)!! - subpageIssueGroup = getPreferenceScreen().findPreference(ISSUE_GROUP_KEY)!! - subpageEntryGroup = getPreferenceScreen().findPreference(ENTRY_GROUP_KEY)!! - subpageFooter = getPreferenceScreen().findPreference(FOOTER_KEY)!! + subpageBrandChip = preferenceScreen.findPreference(BRAND_CHIP_KEY)!! + subpageIllustration = preferenceScreen.findPreference(ILLUSTRATION_KEY)!! + subpageIssueGroup = preferenceScreen.findPreference(ISSUE_GROUP_KEY)!! + subpageEntryGroup = preferenceScreen.findPreference(ENTRY_GROUP_KEY)!! + subpageFooter = preferenceScreen.findPreference(FOOTER_KEY)!! subpageBrandChip.setupListener(requireActivity(), safetyCenterSessionId) setupIllustration() setupFooter() + maybeRemoveSpacer() prerenderCurrentSafetyCenterData() } @@ -80,7 +82,7 @@ class SafetyCenterSubpageFragment : SafetyCenterFragment() { return } - requireActivity().setTitle(entryGroup.title) + requireActivity().title = entryGroup.title updateSafetyCenterIssues(uiData) updateSafetyCenterEntries(entryGroup) } @@ -91,7 +93,7 @@ class SafetyCenterSubpageFragment : SafetyCenterFragment() { val drawable = SafetyCenterResourcesApk(context).getDrawableByName(resName, context.theme) if (drawable == null) { Log.w(TAG, "$sourceGroupId doesn't have any matching illustration") - subpageIllustration.setVisible(false) + subpageIllustration.isVisible = false } subpageIllustration.illustrationDrawable = drawable @@ -102,12 +104,19 @@ class SafetyCenterSubpageFragment : SafetyCenterFragment() { val footerText = SafetyCenterResourcesApk(requireContext()).getStringByName(resName) if (footerText.isEmpty()) { Log.w(TAG, "$sourceGroupId doesn't have any matching footer") - subpageFooter.setVisible(false) + subpageFooter.isVisible = false } // footer is ordered last by default // in order to keep a spacer after the footer, footer needs to be the second from last - subpageFooter.setOrder(Int.MAX_VALUE - 2) - subpageFooter.setSummary(footerText) + subpageFooter.order = Int.MAX_VALUE - 2 + subpageFooter.summary = footerText + } + + private fun maybeRemoveSpacer() { + if (SettingsThemeHelper.isExpressiveTheme(requireContext())) { + val spacerPreference = preferenceScreen.findPreference<SpacerPreference>(SPACER_KEY)!! + preferenceScreen.removePreference(spacerPreference) + } } private fun updateSafetyCenterIssues(uiData: SafetyCenterUiData?) { @@ -131,7 +140,7 @@ class SafetyCenterSubpageFragment : SafetyCenterFragment() { subpageIssues, subpageDismissedIssues, uiData.resolvedIssues, - requireActivity().getTaskId() + requireActivity().taskId, ) } @@ -145,23 +154,24 @@ class SafetyCenterSubpageFragment : SafetyCenterFragment() { PendingIntentSender.getTaskIdForEntry( entry.id, sameTaskSourceIds, - requireActivity() + requireActivity(), ), entry, - safetyCenterViewModel + safetyCenterViewModel, ) ) } } companion object { - private val TAG: String = SafetyCenterSubpageFragment::class.java.simpleName - private const val BRAND_CHIP_KEY: String = "subpage_brand_chip" - private const val ILLUSTRATION_KEY: String = "subpage_illustration" - private const val ISSUE_GROUP_KEY: String = "subpage_issue_group" - private const val ENTRY_GROUP_KEY: String = "subpage_entry_group" - private const val FOOTER_KEY: String = "subpage_footer" - private const val SOURCE_GROUP_ID_KEY: String = "source_group_id" + private val TAG = SafetyCenterSubpageFragment::class.java.simpleName + private const val BRAND_CHIP_KEY = "subpage_brand_chip" + private const val ILLUSTRATION_KEY = "subpage_illustration" + private const val ISSUE_GROUP_KEY = "subpage_issue_group" + private const val ENTRY_GROUP_KEY = "subpage_entry_group" + private const val FOOTER_KEY = "subpage_footer" + private const val SPACER_KEY = "subpage_spacer" + private const val SOURCE_GROUP_ID_KEY = "source_group_id" /** Creates an instance of SafetyCenterSubpageFragment with the arguments set */ @JvmStatic diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyIllustrationPreference.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyIllustrationPreference.kt index 5acb27131..3976fa7e3 100644 --- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyIllustrationPreference.kt +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyIllustrationPreference.kt @@ -25,11 +25,12 @@ import androidx.annotation.RequiresApi import androidx.preference.Preference import androidx.preference.PreferenceViewHolder import com.android.permissioncontroller.R +import com.android.settingslib.widget.GroupSectionDividerMixin /** A preference that displays the illustration on a Safety Center subpage. */ @RequiresApi(UPSIDE_DOWN_CAKE) internal class SafetyIllustrationPreference(context: Context, attrs: AttributeSet) : - Preference(context, attrs) { + Preference(context, attrs), GroupSectionDividerMixin { init { layoutResource = R.layout.preference_illustration diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java index 811841845..abf159955 100644 --- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java @@ -40,6 +40,7 @@ import com.android.permissioncontroller.R; import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterViewModel; import com.android.permissioncontroller.safetycenter.ui.model.StatusUiData; import com.android.permissioncontroller.safetycenter.ui.view.StatusCardView; +import com.android.settingslib.widget.GroupSectionDividerMixin; import kotlin.Pair; @@ -48,7 +49,8 @@ import java.util.Objects; /** Preference which displays a visual representation of {@link SafetyCenterStatus}. */ @RequiresApi(TIRAMISU) -public class SafetyStatusPreference extends Preference implements ComparablePreference { +public class SafetyStatusPreference extends Preference + implements ComparablePreference, GroupSectionDividerMixin { private static final String TAG = "SafetyStatusPreference"; @@ -82,7 +84,8 @@ public class SafetyStatusPreference extends Preference implements ComparablePref } Context context = getContext(); - StatusCardView statusCardView = (StatusCardView) holder.itemView; + StatusCardView statusCardView = holder.itemView.requireViewById(R.id.status_card); + configureButtons(context, statusCardView); statusCardView .getTitleAndSummaryContainerView() diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SpacerPreference.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SpacerPreference.kt index 030b67be9..7f619d1ca 100644 --- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SpacerPreference.kt +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SpacerPreference.kt @@ -55,6 +55,7 @@ internal class SpacerPreference(context: Context, attrs: AttributeSet) : } private var maxKnownToolbarHeight = 0 + override fun onBindViewHolder(holder: PreferenceViewHolder) { super.onBindViewHolder(holder) val spacer = holder.itemView @@ -74,7 +75,7 @@ internal class SpacerPreference(context: Context, attrs: AttributeSet) : oldLeft: Int, oldTop: Int, oldRight: Int, - oldBottom: Int + oldBottom: Int, ) { adjustHeight(spacer) } diff --git a/tests/cts/permissionpolicy/res/raw/android_manifest.xml b/tests/cts/permissionpolicy/res/raw/android_manifest.xml index e1608f878..0cd91b50a 100644 --- a/tests/cts/permissionpolicy/res/raw/android_manifest.xml +++ b/tests/cts/permissionpolicy/res/raw/android_manifest.xml @@ -7873,6 +7873,15 @@ <permission android:name="android.permission.ADD_ALWAYS_UNLOCKED_DISPLAY" android:protectionLevel="signature|role"/> + <!-- Allows an application to create displays that mirror other displays' content. + <p>Not for use by third-party applications. + <p>Protection level: internal|role + @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ENABLE_LIMITED_VDM_ROLE) + @hide @SystemApi --> + <permission android:name="android.permission.ADD_MIRROR_DISPLAY" + android:protectionLevel="internal|role" + android:featureFlag="android.companion.virtualdevice.flags.enable_limited_vdm_role" /> + <!-- @hide @SystemApi Allows an application to access locusId events in the usage stats. --> <permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS" android:protectionLevel="signature|role" /> diff --git a/tests/cts/permissionui/src/android/permissionui/cts/PermissionSplitTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/PermissionSplitTest.kt index d509add3a..da70fc186 100644 --- a/tests/cts/permissionui/src/android/permissionui/cts/PermissionSplitTest.kt +++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionSplitTest.kt @@ -17,15 +17,22 @@ package android.permissionui.cts import android.os.Build +import android.permission.flags.Flags +import android.platform.test.annotations.RequiresFlagsDisabled +import android.platform.test.flag.junit.DeviceFlagsValueProvider import androidx.test.filters.FlakyTest import androidx.test.filters.SdkSuppress import org.junit.Assume.assumeFalse import org.junit.Before +import org.junit.Rule import org.junit.Test /** Runtime permission behavior tests for permission splits. */ @FlakyTest class PermissionSplitTest : BaseUsePermissionTest() { + + @Rule @JvmField val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() + @Before fun assumeNotTv() { assumeFalse(isTv) @@ -56,6 +63,7 @@ class PermissionSplitTest : BaseUsePermissionTest() { } @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu") + @RequiresFlagsDisabled(Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED) @Test fun testBodySensorSplit() { installPackage(APP_APK_PATH_31) @@ -63,6 +71,7 @@ class PermissionSplitTest : BaseUsePermissionTest() { } @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu") + @RequiresFlagsDisabled(Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED) @Test fun testBodySensorSplit32() { installPackage(APP_APK_PATH_32) @@ -70,6 +79,7 @@ class PermissionSplitTest : BaseUsePermissionTest() { } @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu") + @RequiresFlagsDisabled(Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED) @Test fun testBodySensorNonSplit() { installPackage(APP_APK_PATH_LATEST) diff --git a/tests/cts/rolemultiuser/Android.bp b/tests/cts/rolemultiuser/Android.bp new file mode 100644 index 000000000..13fbf28ff --- /dev/null +++ b/tests/cts/rolemultiuser/Android.bp @@ -0,0 +1,47 @@ +// Copyright (C) 2024 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. + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_test { + name: "CtsRoleMultiUserTestCases", + defaults: ["mts-target-sdk-version-current"], + sdk_version: "test_current", + min_sdk_version: "30", + + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], + + static_libs: [ + "bedstead-flags", + "bedstead-multiuser", + "com.android.permission.flags-aconfig-java-export", + "ctstestrunner-axt", + "Harrier", + "flag-junit", + "Nene", + "truth", + ], + + test_suites: [ + "cts", + "general-tests", + "mts-permission", + "mcts-permission", + ], +} diff --git a/tests/cts/rolemultiuser/AndroidManifest.xml b/tests/cts/rolemultiuser/AndroidManifest.xml new file mode 100644 index 000000000..1524c5703 --- /dev/null +++ b/tests/cts/rolemultiuser/AndroidManifest.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + * Copyright (C) 2024 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. + --> + +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="android.app.rolemultiuser.cts"> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.app.rolemultiuser.cts" + android:label="CTS multi-user tests of android.app.role"> + </instrumentation> +</manifest> diff --git a/tests/cts/rolemultiuser/AndroidTest.xml b/tests/cts/rolemultiuser/AndroidTest.xml new file mode 100644 index 000000000..00f4c0993 --- /dev/null +++ b/tests/cts/rolemultiuser/AndroidTest.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + ~ Copyright (C) 2024 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. + --> + +<configuration description="Config for CTS role multi-user test cases"> + + <option name="test-suite-tag" value="cts" /> + <option name="config-descriptor:metadata" key="component" value="permissions" /> + <option name="config-descriptor:metadata" key="parameter" value="all_foldable_states" /> + <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" /> + <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" /> + <option name="config-descriptor:metadata" key="parameter" value="multiuser" /> + <option name="config-descriptor:metadata" key="parameter" value="secondary_user" /> + <option name="config-descriptor:metadata" key="parameter" value="run_on_sdk_sandbox" /> + <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.permission.apex" /> + <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" /> + + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="CtsRoleMultiUserTestCases.apk" /> + </target_preparer> + + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" value="mkdir -p /data/local/tmp/cts-role" /> + <option name="teardown-command" value="rm -rf /data/local/tmp/cts-role"/> + </target_preparer> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="android.app.rolemultiuser.cts" /> + <option name="exclude-annotation" value="com.android.bedstead.enterprise.annotations.RequireRunOnWorkProfile" /> + <option name="exclude-annotation" value="com.android.bedstead.multiuser.annotations.RequireRunOnSecondaryUser" /> + <option name="runtime-hint" value="5m" /> + </test> +</configuration> diff --git a/tests/cts/rolemultiuser/OWNERS b/tests/cts/rolemultiuser/OWNERS new file mode 100644 index 000000000..fb6099cf7 --- /dev/null +++ b/tests/cts/rolemultiuser/OWNERS @@ -0,0 +1,3 @@ +# Bug component: 137825 + +include platform/frameworks/base:/core/java/android/permission/OWNERS diff --git a/tests/cts/rolemultiuser/TEST_MAPPING b/tests/cts/rolemultiuser/TEST_MAPPING new file mode 100644 index 000000000..323e3094c --- /dev/null +++ b/tests/cts/rolemultiuser/TEST_MAPPING @@ -0,0 +1,12 @@ +{ + "postsubmit": [ + { + "name": "CtsRoleMultiUserTestCases" + } + ], + "mainline-postsubmit": [ + { + "name": "CtsRoleMultiUserTestCases[com.google.android.permission.apex]" + } + ] +} diff --git a/tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt b/tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt new file mode 100644 index 000000000..d1bf5284c --- /dev/null +++ b/tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2024 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. + */ +package android.app.rolemultiuser.cts + +import android.app.role.RoleManager +import android.content.Context +import android.os.Build +import android.os.Process +import androidx.test.filters.SdkSuppress +import com.android.bedstead.enterprise.annotations.EnsureHasWorkProfile +import com.android.bedstead.enterprise.workProfile +import com.android.bedstead.flags.annotations.RequireFlagsEnabled +import com.android.bedstead.harrier.BedsteadJUnit4 +import com.android.bedstead.harrier.DeviceState +import com.android.bedstead.multiuser.annotations.EnsureHasAdditionalUser +import com.android.bedstead.multiuser.annotations.EnsureHasPrivateProfile +import com.android.bedstead.multiuser.annotations.EnsureHasSecondaryUser +import com.android.bedstead.multiuser.annotations.RequireRunNotOnSecondaryUser +import com.android.bedstead.multiuser.privateProfile +import com.android.bedstead.multiuser.secondaryUser +import com.android.bedstead.nene.TestApis.context +import com.android.bedstead.nene.TestApis.users +import com.android.bedstead.nene.types.OptionalBoolean +import com.android.bedstead.permissions.CommonPermissions.INTERACT_ACROSS_USERS_FULL +import com.android.bedstead.permissions.CommonPermissions.MANAGE_DEFAULT_APPLICATIONS +import com.android.bedstead.permissions.CommonPermissions.MANAGE_ROLE_HOLDERS +import com.android.bedstead.permissions.annotations.EnsureDoesNotHavePermission +import com.android.bedstead.permissions.annotations.EnsureHasPermission +import com.google.common.truth.Truth.assertThat +import org.junit.Assert.assertThrows +import org.junit.Assume.assumeFalse +import org.junit.ClassRule +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) +@RunWith(BedsteadJUnit4::class) +class RoleManagerMultiUserTest { + @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED) + @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS) + @Test + @Throws(Exception::class) + fun cannotGetActiveUserForNonCrossUserRole() { + assertThrows(IllegalArgumentException::class.java) { + roleManager.getActiveUserForRole(RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER) + } + } + + @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED) + @EnsureHasPermission(MANAGE_ROLE_HOLDERS) + @EnsureDoesNotHavePermission(INTERACT_ACROSS_USERS_FULL) + @Test + @Throws(Exception::class) + fun cannotGetActiveUserForRoleWithoutInteractAcrossUserPermission() { + assertThrows(SecurityException::class.java) { + roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME) + } + } + + @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED) + @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL) + @EnsureDoesNotHavePermission(MANAGE_ROLE_HOLDERS, MANAGE_DEFAULT_APPLICATIONS) + @Test + @Throws(Exception::class) + fun cannotGetActiveUserForRoleWithoutManageRoleAndManageDefaultApplicationsPermission() { + assertThrows(SecurityException::class.java) { + roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME) + } + } + + @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED) + @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS) + @Test + @Throws(Exception::class) + fun cannotSetActiveUserForNonCrossUserRole() { + assertThrows(IllegalArgumentException::class.java) { + roleManager.setActiveUserForRole( + RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER, + Process.myUserHandle(), + 0, + ) + } + } + + @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED) + @EnsureHasPermission(MANAGE_ROLE_HOLDERS) + @EnsureDoesNotHavePermission(INTERACT_ACROSS_USERS_FULL) + @Test + @Throws(Exception::class) + fun cannotSetActiveUserForRoleWithoutInteractAcrossUserPermission() { + assertThrows(SecurityException::class.java) { + roleManager.setActiveUserForRole( + PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, + Process.myUserHandle(), + 0, + ) + } + } + + @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED) + @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL) + @EnsureDoesNotHavePermission(MANAGE_ROLE_HOLDERS, MANAGE_DEFAULT_APPLICATIONS) + @Test + @Throws(Exception::class) + fun cannotSetActiveUserForRoleWithoutManageRoleAndManageDefaultApplicationsPermission() { + assertThrows(SecurityException::class.java) { + roleManager.setActiveUserForRole( + PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, + Process.myUserHandle(), + 0, + ) + } + } + + @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED) + @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS) + @Test + @Throws(Exception::class) + fun cannotSetActiveUserForRoleToNonExistentUser() { + val targetActiveUser = users().nonExisting().userHandle() + roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, targetActiveUser, 0) + + assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)) + .isNotEqualTo(targetActiveUser) + } + + @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED) + @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS) + @EnsureHasPrivateProfile(installInstrumentedApp = OptionalBoolean.TRUE) + @Test + @Throws(Exception::class) + fun cannotSetActiveUserForRoleToPrivateProfileUser() { + val targetActiveUser = deviceState.privateProfile().userHandle() + roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, targetActiveUser, 0) + + assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)) + .isNotEqualTo(targetActiveUser) + } + + @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED) + @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS) + @EnsureHasAdditionalUser(installInstrumentedApp = OptionalBoolean.TRUE) + @EnsureHasSecondaryUser + @RequireRunNotOnSecondaryUser + @Test + @Throws(Exception::class) + fun cannotSetActiveUserForRoleToUserNotInProfileGroup() { + val targetActiveUser = deviceState.secondaryUser().userHandle() + roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, targetActiveUser, 0) + + assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)) + .isNotEqualTo(targetActiveUser) + } + + @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED) + @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS) + @EnsureDoesNotHavePermission(MANAGE_DEFAULT_APPLICATIONS) + @EnsureHasWorkProfile(installInstrumentedApp = OptionalBoolean.TRUE) + @Test + @Throws(Exception::class) + fun setAndGetActiveUserForRoleSetCurrentUserWithManageRoleHoldersPermission() { + assumeFalse( + "setActiveUser not supported for private profile", + users().current().type().name() == PRIVATE_PROFILE_TYPE_NAME, + ) + + val targetActiveUser = users().current().userHandle() + roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, targetActiveUser, 0) + assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)) + .isEqualTo(targetActiveUser) + } + + @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED) + @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_DEFAULT_APPLICATIONS) + @EnsureDoesNotHavePermission(MANAGE_ROLE_HOLDERS) + @EnsureHasWorkProfile(installInstrumentedApp = OptionalBoolean.TRUE) + @Test + @Throws(Exception::class) + fun setAndGetActiveUserForRoleSetCurrentUserWithManageDefaultApplicationPermission() { + assumeFalse( + "setActiveUser not supported for private profile", + users().current().type().name() == PRIVATE_PROFILE_TYPE_NAME, + ) + + val targetActiveUser = users().current().userHandle() + roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, targetActiveUser, 0) + assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)) + .isEqualTo(targetActiveUser) + } + + @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED) + @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS) + @EnsureHasWorkProfile(installInstrumentedApp = OptionalBoolean.TRUE) + @Test + @Throws(Exception::class) + fun setAndGetActiveUserForRoleSetWorkProfile() { + val targetActiveUser = deviceState.workProfile().userHandle() + roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, targetActiveUser, 0) + + assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)) + .isEqualTo(targetActiveUser) + } + + companion object { + private const val PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME = + RoleManager.ROLE_RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY + private const val PRIVATE_PROFILE_TYPE_NAME = "android.os.usertype.profile.PRIVATE" + private val context: Context = context().instrumentedContext() + private val roleManager: RoleManager = context.getSystemService(RoleManager::class.java) + + @JvmField @ClassRule @Rule val deviceState = DeviceState() + } +} |