diff options
16 files changed, 548 insertions, 48 deletions
diff --git a/PermissionController/res/xml/roles.xml b/PermissionController/res/xml/roles.xml index 3d858a9c5..0ccd1ce1a 100644 --- a/PermissionController/res/xml/roles.xml +++ b/PermissionController/res/xml/roles.xml @@ -1886,6 +1886,7 @@ requestTitle="@string/role_for_testing_profile_group_exclusivity_request_title" shortLabel="@string/role_for_testing_profile_group_exclusivity_short_label" showNone="true" + uiBehavior="ReservedForTestingProfileGroupExclusivityRoleUiBehavior" visible="true"/> <!--- diff --git a/PermissionController/role-controller/java/com/android/role/controller/behavior/ReservedForTestingProfileGroupExclusivityRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/ReservedForTestingProfileGroupExclusivityRoleBehavior.java index 91b4e1531..f02b4d90c 100644 --- a/PermissionController/role-controller/java/com/android/role/controller/behavior/ReservedForTestingProfileGroupExclusivityRoleBehavior.java +++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/ReservedForTestingProfileGroupExclusivityRoleBehavior.java @@ -18,6 +18,7 @@ package com.android.role.controller.behavior; import android.app.role.RoleManager; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.os.UserHandle; import androidx.annotation.NonNull; @@ -25,11 +26,14 @@ import androidx.annotation.Nullable; import com.android.role.controller.model.Role; import com.android.role.controller.model.RoleBehavior; +import com.android.role.controller.util.PackageUtils; import com.android.role.controller.util.RoleFlags; import com.android.role.controller.util.UserUtils; +import java.util.ArrayList; import java.util.List; +// TODO(b/383538899): make minSdk36 public class ReservedForTestingProfileGroupExclusivityRoleBehavior implements RoleBehavior { @Nullable @Override @@ -55,4 +59,31 @@ public class ReservedForTestingProfileGroupExclusivityRoleBehavior implements Ro return false; } } + + @Nullable + @Override + public List<String> getQualifyingPackagesAsUser(@NonNull Role role, @NonNull UserHandle user, + @NonNull Context context) { + if (RoleFlags.isProfileGroupExclusivityAvailable()) { + Context userContext = UserUtils.getUserContext(context, user); + RoleManager userRoleManager = userContext.getSystemService(RoleManager.class); + List<String> qualifyingPackageNames = + userRoleManager.getDefaultHoldersForTest(role.getName()); + + // When getQualifyingPackagesAsUser returns a package that isn't installed, Default App + // Settings fails to load. Only return available packages. + List<String> availableQualifyingPackageNames = new ArrayList<>(); + for (int i = 0; i < qualifyingPackageNames.size(); i++) { + String qualifyingPackage = qualifyingPackageNames.get(i); + ApplicationInfo applicationInfo = + PackageUtils.getApplicationInfoAsUser(qualifyingPackage, user, context); + if (applicationInfo != null) { + availableQualifyingPackageNames.add(qualifyingPackage); + } + } + return availableQualifyingPackageNames; + } else { + return null; + } + } } diff --git a/PermissionController/src/com/android/permissioncontroller/role/TEST_MAPPING b/PermissionController/src/com/android/permissioncontroller/role/TEST_MAPPING index 46b148e68..93ad3d31b 100644 --- a/PermissionController/src/com/android/permissioncontroller/role/TEST_MAPPING +++ b/PermissionController/src/com/android/permissioncontroller/role/TEST_MAPPING @@ -46,6 +46,9 @@ "postsubmit": [ { "name": "CtsRoleTestCases" + }, + { + "name": "CtsRoleMultiUserTestCases" } ], "mainline-postsubmit": [ @@ -60,6 +63,9 @@ "exclude-filter": "android.app.role.cts.RoleManagerTest#removeSmsRoleHolderThenPermissionIsRevoked" } ] + }, + { + "name": "CtsRoleMultiUserTestCases[com.google.android.permission.apex]" } ] } diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppChildFragment.java b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppChildFragment.java index 2f515f02c..400e1f162 100644 --- a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppChildFragment.java +++ b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppChildFragment.java @@ -69,6 +69,10 @@ public class DefaultAppChildFragment<PF extends PreferenceFragmentCompat + ".preference.DESCRIPTION"; private static final String PREFERENCE_KEY_OTHER_NFC_SERVICES = DefaultAppChildFragment.class.getName() + ".preference.OTHER_NFC_SERVICES"; + private static final String PREFERENCE_EXTRA_PACKAGE_NAME = + DefaultAppChildFragment.class.getName() + ".extra.PACKAGE_NAME"; + private static final String PREFERENCE_EXTRA_USER = DefaultAppChildFragment.class.getName() + + ".extra.USER"; @NonNull private String mRoleName; @@ -133,7 +137,6 @@ public class DefaultAppChildFragment<PF extends PreferenceFragmentCompat Context context = preferenceManager.getContext(); PreferenceScreen preferenceScreen = preferenceFragment.getPreferenceScreen(); - Preference oldDescriptionPreference = null; ArrayMap<String, Preference> oldPreferences = new ArrayMap<>(); if (preferenceScreen == null) { preferenceScreen = preferenceManager.createPreferenceScreen(context); @@ -162,7 +165,9 @@ public class DefaultAppChildFragment<PF extends PreferenceFragmentCompat ApplicationInfo qualifyingApplicationInfo = qualifyingApplication.first; boolean isHolderApplication = qualifyingApplication.second; - String key = qualifyingApplicationInfo.packageName; + int userId = + UserHandle.getUserHandleForUid(qualifyingApplicationInfo.uid).getIdentifier(); + String key = qualifyingApplicationInfo.packageName + "@" + userId; Drawable icon = Utils.getBadgedIcon(context, qualifyingApplicationInfo); String title = Utils.getFullAppLabel(qualifyingApplicationInfo, context); addPreference(key, icon, title, isHolderApplication, qualifyingApplicationInfo, @@ -205,6 +210,16 @@ public class DefaultAppChildFragment<PF extends PreferenceFragmentCompat preference.setPersistent(false); preference.setOnPreferenceChangeListener((preference2, newValue) -> false); preference.setOnPreferenceClickListener(this); + // In the cases we need this (see #onPreferenceClick()), this should never be null. + // This method (addPreference) is used for both legitimate apps and the `NONE` item, + // the `NONE` item passes a null applicationinfo object. NFC uses a different preference + // method for adding, and a different onclick method + if (applicationInfo != null) { + Bundle extras = preference.getExtras(); + extras.putString(PREFERENCE_EXTRA_PACKAGE_NAME, applicationInfo.packageName); + extras.putParcelable(PREFERENCE_EXTRA_USER, + UserHandle.getUserHandleForUid(applicationInfo.uid)); + } } else { preference = roleApplicationPreference.asTwoStatePreference(); } @@ -243,22 +258,26 @@ public class DefaultAppChildFragment<PF extends PreferenceFragmentCompat if (Objects.equals(key, PREFERENCE_KEY_NONE)) { mViewModel.setNoneDefaultApp(); } else { - String packageName = key; + String packageName = + preference.getExtras().getString(PREFERENCE_EXTRA_PACKAGE_NAME); + UserHandle user = + preference.getExtras().getParcelable(PREFERENCE_EXTRA_USER); CharSequence confirmationMessage = RoleUiBehaviorUtils.getConfirmationMessage(mRole, packageName, requireContext()); if (confirmationMessage != null) { - DefaultAppConfirmationDialogFragment.show(packageName, confirmationMessage, this); + DefaultAppConfirmationDialogFragment.show(packageName, user, confirmationMessage, + this); } else { - setDefaultApp(packageName); + setDefaultApp(packageName, user); } } return true; } @Override - public void setDefaultApp(@NonNull String packageName) { - mViewModel.setDefaultApp(packageName); + public void setDefaultApp(@NonNull String packageName, @NonNull UserHandle user) { + mViewModel.setDefaultApp(packageName, user); } private void addNonPaymentNfcServicesPreference(@NonNull PreferenceScreen preferenceScreen, diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppConfirmationDialogFragment.java b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppConfirmationDialogFragment.java index 843854bf4..5f399a0b8 100644 --- a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppConfirmationDialogFragment.java +++ b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppConfirmationDialogFragment.java @@ -20,9 +20,11 @@ import android.app.AlertDialog; import android.app.Dialog; import android.content.Intent; import android.os.Bundle; +import android.os.UserHandle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.os.BundleCompat; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; @@ -32,24 +34,27 @@ import androidx.fragment.app.Fragment; public class DefaultAppConfirmationDialogFragment extends DialogFragment { private String mPackageName; + private UserHandle mUser; private CharSequence mMessage; /** * Create a new instance of this fragment. * * @param packageName the package name of the application + * @param user the user the specified package is running in * @param message the confirmation message * * @return a new instance of this fragment * - * @see #show(String, CharSequence, Fragment) + * @see #show(String, UserHandle, CharSequence, Fragment) */ @NonNull public static DefaultAppConfirmationDialogFragment newInstance(@NonNull String packageName, - @NonNull CharSequence message) { + @NonNull UserHandle user, @NonNull CharSequence message) { DefaultAppConfirmationDialogFragment fragment = new DefaultAppConfirmationDialogFragment(); Bundle arguments = new Bundle(); arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName); + arguments.putParcelable(Intent.EXTRA_USER, user); arguments.putCharSequence(Intent.EXTRA_TEXT, message); fragment.setArguments(arguments); return fragment; @@ -59,14 +64,15 @@ public class DefaultAppConfirmationDialogFragment extends DialogFragment { * Show a new instance of this fragment. * * @param packageName the package name of the application + * @param user the user the specified package is running in * @param message the confirmation message * @param fragment the parent fragment * - * @see #newInstance(String, CharSequence) + * @see #newInstance(String, UserHandle, CharSequence) */ - public static void show(@NonNull String packageName, @NonNull CharSequence message, - @NonNull Fragment fragment) { - newInstance(packageName, message).show(fragment.getChildFragmentManager(), null); + public static void show(@NonNull String packageName, @NonNull UserHandle user, + @NonNull CharSequence message, @NonNull Fragment fragment) { + newInstance(packageName, user, message).show(fragment.getChildFragmentManager(), null); } @Override @@ -75,6 +81,7 @@ public class DefaultAppConfirmationDialogFragment extends DialogFragment { Bundle arguments = getArguments(); mPackageName = arguments.getString(Intent.EXTRA_PACKAGE_NAME); + mUser = BundleCompat.getParcelable(arguments, Intent.EXTRA_USER, UserHandle.class); mMessage = arguments.getCharSequence(Intent.EXTRA_TEXT); } @@ -90,7 +97,7 @@ public class DefaultAppConfirmationDialogFragment extends DialogFragment { private void onOk() { Listener listener = (Listener) getParentFragment(); - listener.setDefaultApp(mPackageName); + listener.setDefaultApp(mPackageName, mUser); } /** @@ -103,6 +110,6 @@ public class DefaultAppConfirmationDialogFragment extends DialogFragment { * * @param packageName the package name of the application */ - void setDefaultApp(@NonNull String packageName); + void setDefaultApp(@NonNull String packageName, @NonNull UserHandle user); } } diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListViewModel.java b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListViewModel.java index 5bc25df54..82253ed00 100644 --- a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListViewModel.java +++ b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListViewModel.java @@ -30,8 +30,11 @@ import androidx.lifecycle.ViewModel; import com.android.permissioncontroller.permission.utils.Utils; import com.android.permissioncontroller.role.utils.UserUtils; +import com.android.role.controller.model.Role; +import com.android.role.controller.util.RoleFlags; import java.util.List; +import java.util.function.Predicate; /** * {@link ViewModel} for the list of default apps. @@ -55,12 +58,34 @@ public class DefaultAppListViewModel extends AndroidViewModel { super(application); mUser = Process.myUserHandle(); + RoleListLiveData liveData = new RoleListLiveData(true, mUser, application); RoleListSortFunction sortFunction = new RoleListSortFunction(application); - mLiveData = Transformations.map(new RoleListLiveData(true, mUser, application), - sortFunction); mWorkProfile = UserUtils.getWorkProfile(application); - mWorkLiveData = mWorkProfile != null ? Transformations.map(new RoleListLiveData(true, - mWorkProfile, application), sortFunction) : null; + if (RoleFlags.isProfileGroupExclusivityAvailable()) { + if (mWorkProfile != null) { + // Show profile group exclusive roles from work profile in primary group. + RoleListLiveData workLiveData = + new RoleListLiveData(true, mWorkProfile, application); + Predicate<RoleItem> exclusivityPredicate = roleItem -> + roleItem.getRole().getExclusivity() == Role.EXCLUSIVITY_PROFILE_GROUP; + mLiveData = Transformations.map( + new MergeRoleListLiveData(liveData, + Transformations.map(workLiveData, + new RoleListFilterFunction(exclusivityPredicate))), + sortFunction); + mWorkLiveData = Transformations.map( + Transformations.map(workLiveData, + new RoleListFilterFunction(exclusivityPredicate.negate())), + sortFunction); + } else { + mLiveData = Transformations.map(liveData, sortFunction); + mWorkLiveData = null; + } + } else { + mLiveData = Transformations.map(liveData, sortFunction); + mWorkLiveData = mWorkProfile != null ? Transformations.map( + new RoleListLiveData(true, mWorkProfile, application), sortFunction) : null; + } UserHandle privateProfile = UserUtils.getPrivateProfile(application); if (privateProfile != null && Utils.shouldShowInSettings( diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppViewModel.java b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppViewModel.java index c89e1f71e..c982f44c6 100644 --- a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppViewModel.java +++ b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppViewModel.java @@ -30,6 +30,7 @@ import androidx.lifecycle.Transformations; import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModelProvider; +import com.android.permissioncontroller.role.utils.UserUtils; import com.android.role.controller.model.Role; import java.util.List; @@ -58,10 +59,23 @@ public class DefaultAppViewModel extends AndroidViewModel { super(application); mRole = role; - mUser = user; - - mRoleLiveData = Transformations.map(new RoleLiveData(mRole, mUser, application), - new RoleSortFunction(application)); + mUser = mRole.getExclusivity() == Role.EXCLUSIVITY_PROFILE_GROUP + ? UserUtils.getProfileParentOrSelf(user, application) + : user; + RoleLiveData liveData = new RoleLiveData(mRole, mUser, application); + RoleSortFunction sortFunction = new RoleSortFunction(application); + if (mRole.getExclusivity() == Role.EXCLUSIVITY_PROFILE_GROUP) { + UserHandle workProfile = UserUtils.getWorkProfile(application); + if (workProfile != null) { + RoleLiveData workLiveData = new RoleLiveData(role, workProfile, application); + mRoleLiveData = Transformations.map(new MergeRoleLiveData(liveData, workLiveData), + sortFunction); + } else { + mRoleLiveData = Transformations.map(liveData, sortFunction); + } + } else { + mRoleLiveData = Transformations.map(liveData, sortFunction); + } } @NonNull @@ -79,13 +93,13 @@ public class DefaultAppViewModel extends AndroidViewModel { * * @param packageName the package name of the application */ - public void setDefaultApp(@NonNull String packageName) { + public void setDefaultApp(@NonNull String packageName, @NonNull UserHandle user) { if (mManageRoleHolderStateLiveData.getValue() != ManageRoleHolderStateLiveData.STATE_IDLE) { Log.i(LOG_TAG, "Trying to set default app while another request is on-going"); return; } mManageRoleHolderStateLiveData.setRoleHolderAsUser(mRole.getName(), packageName, true, 0, - mUser, getApplication()); + user, getApplication()); } /** diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/MergeRoleListLiveData.java b/PermissionController/src/com/android/permissioncontroller/role/ui/MergeRoleListLiveData.java index 0318800f4..0c2d96b2c 100644 --- a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/MergeRoleListLiveData.java +++ b/PermissionController/src/com/android/permissioncontroller/role/ui/MergeRoleListLiveData.java @@ -14,16 +14,14 @@ * limitations under the License. */ -package com.android.permissioncontroller.role.ui.specialappaccess; +package com.android.permissioncontroller.role.ui; import android.util.ArrayMap; import androidx.annotation.NonNull; +import androidx.lifecycle.LiveData; import androidx.lifecycle.MediatorLiveData; -import com.android.permissioncontroller.role.ui.RoleItem; -import com.android.permissioncontroller.role.ui.RoleListLiveData; - import java.util.ArrayList; import java.util.List; @@ -33,14 +31,14 @@ import java.util.List; public class MergeRoleListLiveData extends MediatorLiveData<List<RoleItem>> { @NonNull - private final RoleListLiveData[] mLiveDatas; + private final LiveData<List<RoleItem>>[] mLiveDatas; - public MergeRoleListLiveData(@NonNull RoleListLiveData... liveDatas) { + public MergeRoleListLiveData(@NonNull LiveData<List<RoleItem>>... liveDatas) { mLiveDatas = liveDatas; int liveDatasLength = mLiveDatas.length; for (int i = 0; i < liveDatasLength; i++) { - RoleListLiveData liveData = mLiveDatas[i]; + LiveData<List<RoleItem>> liveData = mLiveDatas[i]; addSource(liveData, roleItems -> onRoleListChanged()); } @@ -50,7 +48,7 @@ public class MergeRoleListLiveData extends MediatorLiveData<List<RoleItem>> { ArrayMap<String, RoleItem> mergedRoleItemMap = new ArrayMap<>(); int liveDatasLength = mLiveDatas.length; for (int liveDatasIndex = 0; liveDatasIndex < liveDatasLength; liveDatasIndex++) { - RoleListLiveData liveData = mLiveDatas[liveDatasIndex]; + LiveData<List<RoleItem>> liveData = mLiveDatas[liveDatasIndex]; List<RoleItem> roleItems = liveData.getValue(); if (roleItems == null) { diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/MergeRoleLiveData.java b/PermissionController/src/com/android/permissioncontroller/role/ui/MergeRoleLiveData.java index dab59a1ab..31a02729a 100644 --- a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/MergeRoleLiveData.java +++ b/PermissionController/src/com/android/permissioncontroller/role/ui/MergeRoleLiveData.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.permissioncontroller.role.ui.specialappaccess; +package com.android.permissioncontroller.role.ui; import android.content.pm.ApplicationInfo; import android.util.Pair; @@ -22,8 +22,6 @@ import android.util.Pair; import androidx.annotation.NonNull; import androidx.lifecycle.MediatorLiveData; -import com.android.permissioncontroller.role.ui.RoleLiveData; - import java.util.ArrayList; import java.util.List; diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/RoleListFilterFunction.java b/PermissionController/src/com/android/permissioncontroller/role/ui/RoleListFilterFunction.java new file mode 100644 index 000000000..b84fa80b9 --- /dev/null +++ b/PermissionController/src/com/android/permissioncontroller/role/ui/RoleListFilterFunction.java @@ -0,0 +1,53 @@ +/* + * 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 com.android.permissioncontroller.role.ui; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import kotlin.jvm.functions.Function1; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +/** + * A function for {@link androidx.lifecycle#map(androidx.lifecycle.LiveData, Function1)} + * that filters a live data for role list. + */ +public class RoleListFilterFunction implements Function1<List<RoleItem>, List<RoleItem>> { + private final Predicate<RoleItem> mPredicate; + + public RoleListFilterFunction(@NonNull Predicate<RoleItem> predicate) { + mPredicate = predicate; + } + + @NonNull + @Override + public List<RoleItem> invoke(@Nullable List<RoleItem> roleItems) { + List<RoleItem> filteredRoleItems = new ArrayList<>(); + if (roleItems != null) { + int roleItemsSize = roleItems.size(); + for (int i = 0; i < roleItemsSize; i++) { + RoleItem roleItem = roleItems.get(i); + if (mPredicate.test(roleItem)) { + filteredRoleItems.add(roleItem); + } + } + } + return filteredRoleItems; + } +} diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/ReservedForTestingProfileGroupExclusivityRoleUiBehavior.java b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/ReservedForTestingProfileGroupExclusivityRoleUiBehavior.java new file mode 100644 index 000000000..abc89edab --- /dev/null +++ b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/ReservedForTestingProfileGroupExclusivityRoleUiBehavior.java @@ -0,0 +1,63 @@ +/* + * 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 com.android.permissioncontroller.role.ui.behavior; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.os.UserHandle; + +import androidx.annotation.NonNull; +import androidx.preference.Preference; + +import com.android.permissioncontroller.permission.utils.Utils; +import com.android.permissioncontroller.role.ui.TwoTargetPreference; +import com.android.role.controller.model.Role; +import com.android.role.controller.util.UserUtils; + +import java.util.List; + +public class ReservedForTestingProfileGroupExclusivityRoleUiBehavior implements RoleUiBehavior { + @Override + public void preparePreferenceAsUser(@NonNull Role role, @NonNull TwoTargetPreference preference, + @NonNull List<ApplicationInfo> applicationInfos, @NonNull UserHandle user, + @NonNull Context context) { + Context userContext = UserUtils.getUserContext(context, user); + if (!applicationInfos.isEmpty()) { + preparePreferenceInternal(preference.asPreference(), applicationInfos.get(0), + false, userContext); + } + } + + @Override + public void prepareApplicationPreferenceAsUser(@NonNull Role role, + @NonNull Preference preference, @NonNull ApplicationInfo applicationInfo, + @NonNull UserHandle user, @NonNull Context context) { + Context userContext = UserUtils.getUserContext(context, user); + preparePreferenceInternal(preference, applicationInfo, true, userContext); + } + + private void preparePreferenceInternal(@NonNull Preference preference, + @NonNull ApplicationInfo applicationInfo, boolean setTitle, @NonNull Context context) { + String title = Utils.getFullAppLabel(applicationInfo, context) + "@" + + UserHandle.getUserHandleForUid(applicationInfo.uid).getIdentifier(); + if (setTitle) { + preference.setTitle(title); + } else { + preference.setSummary(title); + } + } +} diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessListViewModel.java b/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessListViewModel.java index ee5a0dbbd..e96fb0943 100644 --- a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessListViewModel.java +++ b/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessListViewModel.java @@ -26,6 +26,7 @@ import androidx.lifecycle.LiveData; import androidx.lifecycle.Transformations; import androidx.lifecycle.ViewModel; +import com.android.permissioncontroller.role.ui.MergeRoleListLiveData; import com.android.permissioncontroller.role.ui.RoleItem; import com.android.permissioncontroller.role.ui.RoleListLiveData; import com.android.permissioncontroller.role.ui.RoleListSortFunction; diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessViewModel.java b/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessViewModel.java index 0cc00abc1..c12265d43 100644 --- a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessViewModel.java +++ b/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessViewModel.java @@ -33,6 +33,7 @@ import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModelProvider; import com.android.permissioncontroller.role.ui.ManageRoleHolderStateLiveData; +import com.android.permissioncontroller.role.ui.MergeRoleLiveData; import com.android.permissioncontroller.role.ui.RoleLiveData; import com.android.permissioncontroller.role.ui.RoleSortFunction; import com.android.permissioncontroller.role.utils.UserUtils; diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppHelper.kt b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppHelper.kt index a47719cf7..610745674 100644 --- a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppHelper.kt +++ b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppHelper.kt @@ -34,7 +34,7 @@ class WearDefaultAppHelper( val user: UserHandle, val role: Role, val viewModel: DefaultAppViewModel, - val confirmDialogViewModel: DefaultAppConfirmDialogViewModel + val confirmDialogViewModel: DefaultAppConfirmDialogViewModel, ) { fun getTitle() = context.getString(role.labelResource) @@ -46,7 +46,7 @@ class WearDefaultAppHelper( context = context, defaultLabel = context.getString(R.string.default_app_none), checked = !hasHolderApplication(qualifyingApplications), - onDefaultCheckChanged = { _ -> viewModel.setNoneDefaultApp() } + onDefaultCheckChanged = { _ -> viewModel.setNoneDefaultApp() }, ) .apply { icon = context.getDrawable(R.drawable.ic_remove_circle) } } else { @@ -67,19 +67,24 @@ class WearDefaultAppHelper( onDefaultCheckChanged = { _ -> run { val packageName = appInfo.packageName + val user = UserHandle.getUserHandleForUid(appInfo.uid) val confirmationMessage = RoleUiBehaviorUtils.getConfirmationMessage( role, packageName, - context + context, ) if (confirmationMessage != null) { - showConfirmDialog(packageName, confirmationMessage.toString()) + showConfirmDialog( + packageName, + user, + confirmationMessage.toString(), + ) } else { - setDefaultApp(packageName) + setDefaultApp(packageName, user) } } - } + }, ) .apply { icon = appInfo.loadIcon(context.packageManager) @@ -91,22 +96,22 @@ class WearDefaultAppHelper( this, appInfo, user, - context + context, ) } } .toList() } - private fun showConfirmDialog(packageName: String, message: String) { + private fun showConfirmDialog(packageName: String, userHandle: UserHandle, message: String) { confirmDialogViewModel.confirmDialogArgs = ConfirmDialogArgs( message = message, onOkButtonClick = { - setDefaultApp(packageName) + setDefaultApp(packageName, userHandle) dismissConfirmDialog() }, - onCancelButtonClick = { dismissConfirmDialog() } + onCancelButtonClick = { dismissConfirmDialog() }, ) confirmDialogViewModel.showConfirmDialogLiveData.value = true } @@ -116,8 +121,8 @@ class WearDefaultAppHelper( confirmDialogViewModel.showConfirmDialogLiveData.value = false } - private fun setDefaultApp(packageName: String) { - viewModel.setDefaultApp(packageName) + private fun setDefaultApp(packageName: String, user: UserHandle) { + viewModel.setDefaultApp(packageName, user) } fun getDescription() = context.getString(role.descriptionResource) diff --git a/PermissionController/src/com/android/permissioncontroller/role/utils/UserUtils.java b/PermissionController/src/com/android/permissioncontroller/role/utils/UserUtils.java index 339b2a12a..f3ae21578 100644 --- a/PermissionController/src/com/android/permissioncontroller/role/utils/UserUtils.java +++ b/PermissionController/src/com/android/permissioncontroller/role/utils/UserUtils.java @@ -37,6 +37,16 @@ public class UserUtils { private UserUtils() {} /** + * Returns the parent of a given user, or user if it has no parent (e.g. it is the primary + * user) + */ + @NonNull + public static UserHandle getProfileParentOrSelf(@NonNull UserHandle user, + @NonNull Context context) { + return com.android.role.controller.util.UserUtils.getProfileParentOrSelf(user, context); + } + + /** * Get the work profile of current user, if any. * * @param context the {@code Context} to retrieve system services diff --git a/tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt b/tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt index e33e95706..a70bc070a 100644 --- a/tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt +++ b/tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt @@ -17,10 +17,14 @@ package android.app.rolemultiuser.cts import android.app.role.RoleManager import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager import android.os.Build import android.os.Process import android.os.UserHandle +import android.provider.Settings import androidx.test.filters.SdkSuppress +import androidx.test.uiautomator.By import com.android.bedstead.enterprise.annotations.EnsureHasWorkProfile import com.android.bedstead.enterprise.annotations.RequireRunOnWorkProfile import com.android.bedstead.enterprise.workProfile @@ -43,8 +47,12 @@ import com.android.bedstead.permissions.CommonPermissions.MANAGE_DEFAULT_APPLICA 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.android.compatibility.common.util.DisableAnimationRule +import com.android.compatibility.common.util.FreezeRotationRule import com.android.compatibility.common.util.SystemUtil import com.android.compatibility.common.util.SystemUtil.eventually +import com.android.compatibility.common.util.UiAutomatorUtils2.getUiDevice +import com.android.compatibility.common.util.UiAutomatorUtils2.waitFindObject import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import java.util.Objects @@ -555,6 +563,234 @@ class RoleManagerMultiUserTest { assertExpectedProfileHasRoleUsingGetDefaultApplication(targetActiveUser) } + @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED) + @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS) + @EnsureHasWorkProfile + @RequireRunOnPrimaryUser + @Test + @Throws(java.lang.Exception::class) + fun openDefaultAppListAndSetDefaultAppThenIsDefaultApp() { + try { + // Set test default role holder. Ensures fallbacks to a default holder + setDefaultHoldersForTestForAllUsers() + setRoleVisibleForTestForAllUsers() + + context.startActivity( + Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS) + .addCategory(Intent.CATEGORY_DEFAULT) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + ) + getUiDevice().waitForIdle() + waitFindObject(By.text(PROFILE_GROUP_EXCLUSIVITY_ROLE_SHORT_LABEL)).click() + getUiDevice().waitForIdle() + + val targetActiveUser = users().current().userHandle() + val targetAppLabel = "$APP_LABEL@${targetActiveUser.identifier}" + if (isWatch) { + waitFindObject(By.clickable(true).hasDescendant(By.text(targetAppLabel))).click() + } else { + waitFindObject( + By.clickable(true) + .hasDescendant(By.checkable(true)) + .hasDescendant(By.text(targetAppLabel)) + ) + .click() + } + + if (isWatch) { + waitFindObject( + By.clickable(true).checked(true).hasDescendant(By.text(targetAppLabel)) + ) + } else { + waitFindObject( + By.clickable(true) + .hasDescendant(By.checkable(true).checked(true)) + .hasDescendant(By.text(targetAppLabel)) + ) + } + + assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)) + .isEqualTo(targetActiveUser) + assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(targetActiveUser) + + pressBack() + pressBack() + } finally { + clearDefaultHoldersForTestForAllUsers() + clearRoleVisibleForTestForAllUsers() + } + } + + @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED) + @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS) + @EnsureHasWorkProfile + @RequireRunOnPrimaryUser + @Test + @Throws(java.lang.Exception::class) + fun openDefaultAppListAndSetWorkDefaultAppThenIsDefaultApp() { + try { + // Set test default role holder. Ensures fallbacks to a default holder + setDefaultHoldersForTestForAllUsers() + setRoleVisibleForTestForAllUsers() + + context.startActivity( + Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS) + .addCategory(Intent.CATEGORY_DEFAULT) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + ) + getUiDevice().waitForIdle() + waitFindObject(By.text(PROFILE_GROUP_EXCLUSIVITY_ROLE_SHORT_LABEL)).click() + getUiDevice().waitForIdle() + + val targetActiveUser = deviceState.workProfile().userHandle() + val targetAppLabel = "$APP_LABEL@${targetActiveUser.identifier}" + if (isWatch) { + waitFindObject(By.clickable(true).hasDescendant(By.text(targetAppLabel))).click() + } else { + waitFindObject( + By.clickable(true) + .hasDescendant(By.checkable(true)) + .hasDescendant(By.text(targetAppLabel)) + ) + .click() + } + + if (isWatch) { + waitFindObject( + By.clickable(true).checked(true).hasDescendant(By.text(targetAppLabel)) + ) + } else { + waitFindObject( + By.clickable(true) + .hasDescendant(By.checkable(true).checked(true)) + .hasDescendant(By.text(targetAppLabel)) + ) + } + + assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)) + .isEqualTo(targetActiveUser) + assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(targetActiveUser) + + pressBack() + pressBack() + } finally { + clearDefaultHoldersForTestForAllUsers() + clearRoleVisibleForTestForAllUsers() + } + } + + @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED) + @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS) + @EnsureHasWorkProfile + @RequireRunOnPrimaryUser + @Test + @Throws(java.lang.Exception::class) + fun openDefaultAppListAndSetDefaultAppThenIsDefaultAppInList() { + try { + // Set test default role holder. Ensures fallbacks to a default holder + setDefaultHoldersForTestForAllUsers() + setRoleVisibleForTestForAllUsers() + + context.startActivity( + Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS) + .addCategory(Intent.CATEGORY_DEFAULT) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + ) + getUiDevice().waitForIdle() + waitFindObject(By.text(PROFILE_GROUP_EXCLUSIVITY_ROLE_SHORT_LABEL)).click() + getUiDevice().waitForIdle() + + val targetActiveUser = users().current().userHandle() + val targetAppLabel = "$APP_LABEL@${targetActiveUser.identifier}" + if (isWatch) { + waitFindObject(By.clickable(true).hasDescendant(By.text(targetAppLabel))).click() + waitFindObject( + By.clickable(true).checked(true).hasDescendant(By.text(targetAppLabel)) + ) + } else { + waitFindObject( + By.clickable(true) + .hasDescendant(By.checkable(true)) + .hasDescendant(By.text(targetAppLabel)) + ) + .click() + waitFindObject( + By.clickable(true) + .hasDescendant(By.checkable(true).checked(true)) + .hasDescendant(By.text(targetAppLabel)) + ) + } + pressBack() + + waitFindObject(By.text(targetAppLabel)) + + assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)) + .isEqualTo(targetActiveUser) + assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(targetActiveUser) + + pressBack() + } finally { + clearDefaultHoldersForTestForAllUsers() + clearRoleVisibleForTestForAllUsers() + } + } + + @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED) + @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS) + @EnsureHasWorkProfile + @RequireRunOnPrimaryUser + @Test + @Throws(java.lang.Exception::class) + fun openDefaultAppListAndSetWorkDefaultAppThenIsDefaultAppInList() { + try { + // Set test default role holder. Ensures fallbacks to a default holder + setDefaultHoldersForTestForAllUsers() + setRoleVisibleForTestForAllUsers() + + context.startActivity( + Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS) + .addCategory(Intent.CATEGORY_DEFAULT) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + ) + getUiDevice().waitForIdle() + waitFindObject(By.text(PROFILE_GROUP_EXCLUSIVITY_ROLE_SHORT_LABEL)).click() + getUiDevice().waitForIdle() + + val targetActiveUser = deviceState.workProfile().userHandle() + val targetAppLabel = "$APP_LABEL@${targetActiveUser.identifier}" + if (isWatch) { + waitFindObject(By.clickable(true).hasDescendant(By.text(targetAppLabel))).click() + waitFindObject( + By.clickable(true).checked(true).hasDescendant(By.text(targetAppLabel)) + ) + } else { + waitFindObject( + By.clickable(true) + .hasDescendant(By.checkable(true)) + .hasDescendant(By.text(targetAppLabel)) + ) + .click() + waitFindObject( + By.clickable(true) + .hasDescendant(By.checkable(true).checked(true)) + .hasDescendant(By.text(targetAppLabel)) + ) + } + pressBack() + + waitFindObject(By.text(targetAppLabel)) + + assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)) + .isEqualTo(targetActiveUser) + assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(targetActiveUser) + + pressBack() + } finally { + clearDefaultHoldersForTestForAllUsers() + clearRoleVisibleForTestForAllUsers() + } + } + @Throws(java.lang.Exception::class) private fun installAppForAllUsers() { SystemUtil.runShellCommandOrThrow("pm install -r --user all $APP_APK_PATH") @@ -564,6 +800,11 @@ class RoleManagerMultiUserTest { SystemUtil.runShellCommand("pm uninstall $APP_PACKAGE_NAME") } + private fun pressBack() { + getUiDevice().pressBack() + getUiDevice().waitForIdle() + } + private fun assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser( expectedActiveUser: UserHandle ) { @@ -641,6 +882,20 @@ class RoleManagerMultiUserTest { } } + private fun setRoleVisibleForTestForAllUsers() { + // Set test default role holder. Ensures fallbacks to a default holder + for (userRoleManager in users().all().map { getRoleManagerForUser(it.userHandle()) }) { + userRoleManager.setRoleVisibleForTest(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, true) + } + } + + private fun clearRoleVisibleForTestForAllUsers() { + // Set test default role holder. Ensures fallbacks to a default holder + for (userRoleManager in users().all().map { getRoleManagerForUser(it.userHandle()) }) { + userRoleManager.setRoleVisibleForTest(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, false) + } + } + private fun getRoleManagerForUser(user: UserHandle): RoleManager { val userContext = context.createContextAsUser(user, 0) return userContext.getSystemService(RoleManager::class.java) @@ -656,13 +911,26 @@ class RoleManagerMultiUserTest { private const val TIMEOUT_MILLIS: Long = (15 * 1000).toLong() private const val PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME = RoleManager.ROLE_RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY + private const val PROFILE_GROUP_EXCLUSIVITY_ROLE_SHORT_LABEL = + "Test profile group exclusive role app" private const val PRIVATE_PROFILE_TYPE_NAME = "android.os.usertype.profile.PRIVATE" private const val APP_APK_PATH: String = "/data/local/tmp/cts-role/CtsRoleMultiUserTestApp.apk" private const val APP_PACKAGE_NAME: String = "android.app.rolemultiuser.cts.app" + private const val APP_LABEL: String = "CtsRoleMultiUserTestApp" + private val context: Context = context().instrumentedContext() private val roleManager: RoleManager = context.getSystemService(RoleManager::class.java) + private val packageManager: PackageManager = context.packageManager + private val isWatch = packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH) @JvmField @ClassRule @Rule val deviceState = DeviceState() + + @JvmField + @ClassRule + @Rule + var disableAnimationRule: DisableAnimationRule = DisableAnimationRule() + + @JvmField @ClassRule @Rule var freezeRotationRule: FreezeRotationRule = FreezeRotationRule() } } |