From 4d0edf369f515abff7d2b9ab3f9794d9ecfc7c76 Mon Sep 17 00:00:00 2001 From: kholoud mohamed Date: Wed, 18 Mar 2020 17:00:50 +0000 Subject: [PATCH] Show all apps that requested cross profile permission show all apps that requested the permission, show the admin restricted dialog if the app is not whitelisted by the admin, and show a link to install the app if the app is missing in one of the profiles. Bug: 149742043 Test: make RunSettingsRoboTests ROBOTEST_FILTER=InteractAcrossProfilesPreferenceControllerTest Test: make RunSettingsRoboTests ROBOTEST_FILTER=InteractAcrossProfilesControllerTest Test: make RunSettingsRoboTests ROBOTEST_FILTER=InteractAcrossProfilesSettingsTest Test: make RunSettingsRoboTests ROBOTEST_FILTER=InteractAcrossProfilesDetailsTest Change-Id: I2bc86b9966a2e14a12ee8f4b7f1b75f866ed98e3 --- res/drawable/ic_download_for_offline.xml | 21 ++ res/values/strings.xml | 25 +- ...ct_across_profiles_permissions_details.xml | 32 +- .../InteractAcrossProfilesDetails.java | 288 ++++++++++++++---- ...ssProfilesDetailsPreferenceController.java | 9 +- .../InteractAcrossProfilesSettings.java | 63 ++-- .../InteractAcrossProfilesDetailsTest.java | 47 ++- ...crossProfilesPreferenceControllerTest.java | 24 +- .../InteractAcrossProfilesSettingsTest.java | 67 ++-- 9 files changed, 428 insertions(+), 148 deletions(-) create mode 100644 res/drawable/ic_download_for_offline.xml diff --git a/res/drawable/ic_download_for_offline.xml b/res/drawable/ic_download_for_offline.xml new file mode 100644 index 00000000000..f9ea3050f55 --- /dev/null +++ b/res/drawable/ic_download_for_offline.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 46d75e82680..b340e0351b4 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -8521,6 +8521,12 @@ When a user connects select work and personal apps, they can access work and personal data together. [CHAR LIMIT=50] --> Connected work & personal apps + + Connected + + + Not connected + No connected apps @@ -8587,27 +8593,18 @@ - Install %1$s in your work profile - - - To connect these apps, install the %1$s app in your work profile + Install work %1$s to connect these apps - Install %1$s in your personal profile + The placeholder would be the app name (e.g. Calendar). [CHAR LIMIT=NONE]--> + Install personal %1$s to connect these apps - To connect these apps, install the %1$s app in your personal profile - - - Get the app + Tap to get the app Do Not Disturb access diff --git a/res/xml/interact_across_profiles_permissions_details.xml b/res/xml/interact_across_profiles_permissions_details.xml index 8b1e043eee8..1baccf227e9 100644 --- a/res/xml/interact_across_profiles_permissions_details.xml +++ b/res/xml/interact_across_profiles_permissions_details.xml @@ -15,22 +15,28 @@ ~ limitations under the License. --> - - + - + - + - + + + diff --git a/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesDetails.java b/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesDetails.java index ff616268063..474a5b6ec79 100644 --- a/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesDetails.java +++ b/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesDetails.java @@ -15,15 +15,25 @@ */ package com.android.settings.applications.specialaccess.interactacrossprofiles; +import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; +import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; + import android.Manifest; +import android.annotation.UserIdInt; +import android.app.ActionBar; import android.app.AppOpsManager; +import android.app.admin.DevicePolicyManager; import android.app.settings.SettingsEnums; import android.content.Context; import android.content.DialogInterface; +import android.content.Intent; import android.content.PermissionChecker; import android.content.pm.CrossProfileApps; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.pm.UserInfo; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; @@ -32,13 +42,15 @@ import android.view.View; import android.widget.ImageView; import android.widget.TextView; -import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.preference.Preference; -import androidx.preference.SwitchPreference; import com.android.settings.R; import com.android.settings.applications.AppInfoBase; +import com.android.settings.applications.AppStoreUtil; +import com.android.settings.widget.CardPreference; +import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.RestrictedSwitchPreference; import com.android.settingslib.widget.LayoutPreference; public class InteractAcrossProfilesDetails extends AppInfoBase @@ -47,13 +59,21 @@ public class InteractAcrossProfilesDetails extends AppInfoBase private static final String INTERACT_ACROSS_PROFILES_SETTINGS_SWITCH = "interact_across_profiles_settings_switch"; private static final String INTERACT_ACROSS_PROFILES_HEADER = "interact_across_profiles_header"; + public static final String INSTALL_APP_BANNER_KEY = "install_app_banner"; private Context mContext; private CrossProfileApps mCrossProfileApps; private UserManager mUserManager; - private SwitchPreference mSwitchPref; + private RestrictedSwitchPreference mSwitchPref; private LayoutPreference mHeader; + private CardPreference mInstallBanner; private PackageManager mPackageManager; + private UserHandle mPersonalProfile; + private UserHandle mWorkProfile; + private boolean mInstalledInPersonal; + private boolean mInstalledInWork; + private String mAppLabel; + private Intent mInstallAppIntent; @Override public void onCreate(Bundle savedInstanceState) { @@ -64,19 +84,30 @@ public class InteractAcrossProfilesDetails extends AppInfoBase mUserManager = mContext.getSystemService(UserManager.class); mPackageManager = mContext.getPackageManager(); + mWorkProfile = InteractAcrossProfilesSettings.getWorkProfile(mUserManager); + mPersonalProfile = mUserManager.getProfileParent(mWorkProfile); + mInstalledInWork = isPackageInstalled(mPackageName, mWorkProfile.getIdentifier()); + mInstalledInPersonal = isPackageInstalled(mPackageName, mPersonalProfile.getIdentifier()); + + mAppLabel = mPackageInfo.applicationInfo.loadLabel(mPackageManager).toString(); + mInstallAppIntent = AppStoreUtil.getAppStoreLink(mContext, mPackageName); + addPreferencesFromResource(R.xml.interact_across_profiles_permissions_details); mSwitchPref = findPreference(INTERACT_ACROSS_PROFILES_SETTINGS_SWITCH); mSwitchPref.setOnPreferenceClickListener(this); + mHeader = findPreference(INTERACT_ACROSS_PROFILES_HEADER); + mInstallBanner = findPreference(INSTALL_APP_BANNER_KEY); + mInstallBanner.setOnPreferenceClickListener(this); + // refreshUi checks that the user can still configure the appOp, return to the // previous page if it can't. if (!refreshUi()) { setIntentAndFinish(true/* appChanged */); } - final UserHandle workProfile = getWorkProfile(); - final UserHandle personalProfile = mUserManager.getProfileParent(workProfile); - addAppTitleAndIcons(personalProfile, workProfile); + addAppTitleAndIcons(mPersonalProfile, mWorkProfile); + styleActionBar(); } private void addAppTitleAndIcons(UserHandle personalProfile, UserHandle workProfile) { @@ -89,70 +120,101 @@ public class InteractAcrossProfilesDetails extends AppInfoBase final ImageView personalIconView = mHeader.findViewById(R.id.entity_header_icon_personal); if (personalIconView != null) { - personalIconView.setImageDrawable(IconDrawableFactory.newInstance(mContext) - .getBadgedIcon(mPackageInfo.applicationInfo, personalProfile.getIdentifier())); + Drawable icon = IconDrawableFactory.newInstance(mContext) + .getBadgedIcon(mPackageInfo.applicationInfo, personalProfile.getIdentifier()) + .mutate(); + if (!mInstalledInPersonal) { + icon.setColorFilter(createSuspendedColorMatrix()); + } + personalIconView.setImageDrawable(icon); } - final ImageView workIconView2 = mHeader.findViewById(R.id.entity_header_icon_work); - if (workIconView2 != null) { - workIconView2.setImageDrawable(IconDrawableFactory.newInstance(mContext) - .getBadgedIcon(mPackageInfo.applicationInfo, workProfile.getIdentifier())); + + final ImageView workIconView = mHeader.findViewById(R.id.entity_header_icon_work); + if (workIconView != null) { + Drawable icon = IconDrawableFactory.newInstance(mContext) + .getBadgedIcon(mPackageInfo.applicationInfo, workProfile.getIdentifier()) + .mutate(); + if (!mInstalledInWork) { + icon.setColorFilter(createSuspendedColorMatrix()); + } + workIconView.setImageDrawable(icon); } } - @Nullable - private UserHandle getWorkProfile() { - for (UserInfo user : mUserManager.getProfiles(UserHandle.myUserId())) { - if (mUserManager.isManagedProfile(user.id)) { - return user.getUserHandle(); - } + private void styleActionBar() { + final ActionBar actionBar = getActivity().getActionBar(); + if (actionBar != null) { + actionBar.setElevation(0); } - return null; + } + + private ColorMatrixColorFilter createSuspendedColorMatrix() { + int grayValue = 127; + float scale = 0.5f; // half bright + + ColorMatrix tempBrightnessMatrix = new ColorMatrix(); + float[] mat = tempBrightnessMatrix.getArray(); + mat[0] = scale; + mat[6] = scale; + mat[12] = scale; + mat[4] = grayValue; + mat[9] = grayValue; + mat[14] = grayValue; + + ColorMatrix matrix = new ColorMatrix(); + matrix.setSaturation(0.0f); + matrix.preConcat(tempBrightnessMatrix); + return new ColorMatrixColorFilter(matrix); } @Override public boolean onPreferenceClick(Preference preference) { - if (preference != mSwitchPref) { - return false; - } // refreshUi checks that the user can still configure the appOp, return to the // previous page if it can't. if (!refreshUi()) { setIntentAndFinish(true/* appChanged */); } + if (preference == mSwitchPref) { + handleSwitchPreferenceClick(); + return true; + } + if (preference == mInstallBanner) { + handleInstallBannerClick(); + return true; + } + return false; + } + + private void handleSwitchPreferenceClick() { if (isInteractAcrossProfilesEnabled()) { enableInteractAcrossProfiles(false); refreshUi(); - return true; - } - if (!isInteractAcrossProfilesEnabled()) { + } else { showConsentDialog(); } - return true; } private void showConsentDialog() { - final String appLabel = mPackageInfo.applicationInfo.loadLabel(mPackageManager).toString(); - final View dialogView = getLayoutInflater().inflate( R.layout.interact_across_profiles_consent_dialog, null); final TextView dialogTitle = dialogView.findViewById( R.id.interact_across_profiles_consent_dialog_title); dialogTitle.setText( - getString(R.string.interact_across_profiles_consent_dialog_title, appLabel)); + getString(R.string.interact_across_profiles_consent_dialog_title, mAppLabel)); final TextView dialogSummary = dialogView.findViewById( R.id.interact_across_profiles_consent_dialog_summary); dialogSummary.setText( - getString(R.string.interact_across_profiles_consent_dialog_summary, appLabel)); + getString(R.string.interact_across_profiles_consent_dialog_summary, mAppLabel)); final TextView appDataSummary = dialogView.findViewById(R.id.app_data_summary); appDataSummary.setText(getString( - R.string.interact_across_profiles_consent_dialog_app_data_summary, appLabel)); + R.string.interact_across_profiles_consent_dialog_app_data_summary, mAppLabel)); final TextView permissionsSummary = dialogView.findViewById(R.id.permissions_summary); permissionsSummary.setText(getString( - R.string.interact_across_profiles_consent_dialog_permissions_summary, appLabel)); + R.string.interact_across_profiles_consent_dialog_permissions_summary, mAppLabel)); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setView(dialogView) @@ -171,18 +233,38 @@ public class InteractAcrossProfilesDetails extends AppInfoBase } private boolean isInteractAcrossProfilesEnabled() { - return isInteractAcrossProfilesEnabled( - mContext, mPackageName, mPackageInfo.applicationInfo.uid); + return isInteractAcrossProfilesEnabled(mContext, mPackageName); } - static boolean isInteractAcrossProfilesEnabled(Context context, String packageName, int uid) { + static boolean isInteractAcrossProfilesEnabled( + Context context, String packageName) { + UserManager userManager = context.getSystemService(UserManager.class); + UserHandle workProfile = InteractAcrossProfilesSettings.getWorkProfile(userManager); + UserHandle personalProfile = userManager.getProfileParent(workProfile); + return context.getSystemService( + CrossProfileApps.class).canConfigureInteractAcrossProfiles(packageName) + && isInteractAcrossProfilesEnabledInProfile(context, packageName, personalProfile) + && isInteractAcrossProfilesEnabledInProfile(context, packageName, workProfile); + + } + + private static boolean isInteractAcrossProfilesEnabledInProfile( + Context context, String packageName, UserHandle userHandle) { + final PackageManager packageManager = context.getPackageManager(); + final int uid; + try { + uid = packageManager.getApplicationInfoAsUser( + packageName, /* flags= */0, userHandle).uid; + } catch (PackageManager.NameNotFoundException e) { + return false; + } return PermissionChecker.PERMISSION_GRANTED == PermissionChecker.checkPermissionForPreflight( - context, - Manifest.permission.INTERACT_ACROSS_PROFILES, - PermissionChecker.PID_UNKNOWN, - uid, - packageName); + context, + Manifest.permission.INTERACT_ACROSS_PROFILES, + PermissionChecker.PID_UNKNOWN, + uid, + packageName); } private void enableInteractAcrossProfiles(boolean newState) { @@ -190,14 +272,28 @@ public class InteractAcrossProfilesDetails extends AppInfoBase mPackageName, newState ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED); } + private void handleInstallBannerClick() { + if (mInstallAppIntent == null) { + return; + } + if (!mInstalledInWork) { + mContext.startActivityAsUser(mInstallAppIntent, mWorkProfile); + return; + } + if (!mInstalledInPersonal) { + mContext.startActivityAsUser(mInstallAppIntent, mPersonalProfile); + } + } + /** * @return the summary for the current state of whether the app associated with the given * {@code packageName} is allowed to interact across profiles. */ - public static CharSequence getPreferenceSummary(Context context, String packageName, int uid) { - return context.getString(isInteractAcrossProfilesEnabled(context, packageName, uid) - ? R.string.app_permission_summary_allowed - : R.string.app_permission_summary_not_allowed); + public static CharSequence getPreferenceSummary( + Context context, String packageName) { + return context.getString(isInteractAcrossProfilesEnabled(context, packageName) + ? R.string.interact_across_profiles_summary_allowed + : R.string.interact_across_profiles_summary_not_allowed); } @Override @@ -205,31 +301,99 @@ public class InteractAcrossProfilesDetails extends AppInfoBase if (mPackageInfo == null || mPackageInfo.applicationInfo == null) { return false; } - if (!mCrossProfileApps.canConfigureInteractAcrossProfiles(mPackageName)) { + if (!mCrossProfileApps.canUserAttemptToConfigureInteractAcrossProfiles(mPackageName)) { // Invalid app entry. Should not allow changing permission mSwitchPref.setEnabled(false); return false; } - - final ImageView horizontalArrowIcon = mHeader.findViewById(R.id.entity_header_swap_horiz); - if (isInteractAcrossProfilesEnabled()) { - mSwitchPref.setChecked(true); - mSwitchPref.setTitle(R.string.interact_across_profiles_switch_enabled); - if (horizontalArrowIcon != null) { - horizontalArrowIcon.setImageDrawable( - mContext.getDrawable(R.drawable.ic_swap_horiz_blue)); - } - } else { - mSwitchPref.setChecked(false); - mSwitchPref.setTitle(R.string.interact_across_profiles_switch_disabled); - if (horizontalArrowIcon != null) { - horizontalArrowIcon.setImageDrawable( - mContext.getDrawable(R.drawable.ic_swap_horiz_grey)); - } + if (!mCrossProfileApps.canConfigureInteractAcrossProfiles(mPackageName)) { + return refreshUiForNonConfigurableApps(); } + refreshUiForConfigurableApps(); return true; } + private boolean refreshUiForNonConfigurableApps() { + mSwitchPref.setChecked(false); + mSwitchPref.setTitle(R.string.interact_across_profiles_switch_disabled); + if (!isCrossProfilePackageWhitelisted(mPackageName)) { + mInstallBanner.setVisible(false); + mSwitchPref.setDisabledByAdmin(RestrictedLockUtils.getProfileOrDeviceOwner( + mContext, mWorkProfile)); + return true; + } + mSwitchPref.setEnabled(false); + if (!mInstalledInPersonal && !mInstalledInWork) { + return false; + } + if (!mInstalledInPersonal) { + mInstallBanner.setTitle(getString( + R.string.interact_across_profiles_install_personal_app_title, + mAppLabel)); + mInstallBanner.setSummary( + R.string.interact_across_profiles_install_app_summary); + mInstallBanner.setVisible(true); + return true; + } + if (!mInstalledInWork) { + mInstallBanner.setTitle(getString( + R.string.interact_across_profiles_install_work_app_title, + mAppLabel)); + mInstallBanner.setSummary( + R.string.interact_across_profiles_install_app_summary); + mInstallBanner.setVisible(true); + return true; + } + return false; + } + + private boolean isCrossProfilePackageWhitelisted(String packageName) { + return mContext.getSystemService(DevicePolicyManager.class) + .getAllCrossProfilePackages().contains(packageName); + } + + private boolean isPackageInstalled(String packageName, @UserIdInt int userId) { + final PackageInfo info; + try { + info = mContext.createContextAsUser(UserHandle.of(userId), /* flags= */0) + .getPackageManager().getPackageInfo(packageName, + MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE); + } catch (PackageManager.NameNotFoundException e) { + return false; + } + return info != null; + } + + private void refreshUiForConfigurableApps() { + mInstallBanner.setVisible(false); + mSwitchPref.setEnabled(true); + if (isInteractAcrossProfilesEnabled()) { + enableSwitchPref(); + } else { + disableSwitchPref(); + } + } + + private void enableSwitchPref() { + mSwitchPref.setChecked(true); + mSwitchPref.setTitle(R.string.interact_across_profiles_switch_enabled); + final ImageView horizontalArrowIcon = mHeader.findViewById(R.id.entity_header_swap_horiz); + if (horizontalArrowIcon != null) { + horizontalArrowIcon.setImageDrawable( + mContext.getDrawable(R.drawable.ic_swap_horiz_blue)); + } + } + + private void disableSwitchPref() { + mSwitchPref.setChecked(false); + mSwitchPref.setTitle(R.string.interact_across_profiles_switch_disabled); + final ImageView horizontalArrowIcon = mHeader.findViewById(R.id.entity_header_swap_horiz); + if (horizontalArrowIcon != null) { + horizontalArrowIcon.setImageDrawable( + mContext.getDrawable(R.drawable.ic_swap_horiz_grey)); + } + } + @Override protected AlertDialog createDialog(int id, int errorCode) { return null; diff --git a/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesDetailsPreferenceController.java b/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesDetailsPreferenceController.java index 41e25a7d1d0..f57a6ed7c25 100644 --- a/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesDetailsPreferenceController.java +++ b/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesDetailsPreferenceController.java @@ -35,7 +35,7 @@ public class InteractAcrossProfilesDetailsPreferenceController @Override public int getAvailabilityStatus() { - return canConfigureInteractAcrossProfiles() ? AVAILABLE : DISABLED_FOR_USER; + return canUserAttemptToConfigureInteractAcrossProfiles() ? AVAILABLE : DISABLED_FOR_USER; } @Override @@ -49,13 +49,12 @@ public class InteractAcrossProfilesDetailsPreferenceController } private CharSequence getPreferenceSummary() { - return InteractAcrossProfilesDetails.getPreferenceSummary(mContext, mPackageName, - mParent.getPackageInfo().applicationInfo.uid); + return InteractAcrossProfilesDetails.getPreferenceSummary(mContext, mPackageName); } - private boolean canConfigureInteractAcrossProfiles() { + private boolean canUserAttemptToConfigureInteractAcrossProfiles() { return mContext.getSystemService(CrossProfileApps.class) - .canConfigureInteractAcrossProfiles(mPackageName); + .canUserAttemptToConfigureInteractAcrossProfiles(mPackageName); } public void setPackageName(String packageName) { diff --git a/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesSettings.java b/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesSettings.java index d686978fde4..c5d848af7a3 100644 --- a/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesSettings.java +++ b/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesSettings.java @@ -85,8 +85,8 @@ public class InteractAcrossProfilesSettings extends EmptyTextSettings { final Preference pref = new AppPreference(prefContext); pref.setIcon(mIconDrawableFactory.getBadgedIcon(appInfo, user.getIdentifier())); pref.setTitle(mPackageManager.getUserBadgedLabel(label, user)); - pref.setSummary(InteractAcrossProfilesDetails.getPreferenceSummary(prefContext, - packageName, appInfo.uid)); + pref.setSummary(InteractAcrossProfilesDetails.getPreferenceSummary( + prefContext, packageName)); pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { @Override public boolean onPreferenceClick(Preference preference) { @@ -127,49 +127,78 @@ public class InteractAcrossProfilesSettings extends EmptyTextSettings { static ArrayList> collectConfigurableApps( PackageManager packageManager, UserManager userManager, CrossProfileApps crossProfileApps) { - final UserHandle personalProfile = getPersonalProfileForCallingUser(userManager); + final UserHandle workProfile = getWorkProfile(userManager); + if (workProfile == null) { + return new ArrayList<>(); + } + final UserHandle personalProfile = userManager.getProfileParent(workProfile); if (personalProfile == null) { return new ArrayList<>(); } final ArrayList> apps = new ArrayList<>(); - final List installedPackages = packageManager.getInstalledPackagesAsUser( - GET_ACTIVITIES, personalProfile.getIdentifier()); - for (PackageInfo packageInfo : installedPackages) { - if (crossProfileApps.canConfigureInteractAcrossProfiles(packageInfo.packageName)) { + for (PackageInfo packageInfo : getAllInstalledPackages( + packageManager, personalProfile, workProfile)) { + if (crossProfileApps.canUserAttemptToConfigureInteractAcrossProfiles( + packageInfo.packageName)) { apps.add(new Pair<>(packageInfo.applicationInfo, personalProfile)); } } return apps; } + private static List getAllInstalledPackages( + PackageManager packageManager, UserHandle personalProfile, UserHandle workProfile) { + List personalPackages = packageManager.getInstalledPackagesAsUser( + GET_ACTIVITIES, personalProfile.getIdentifier()); + List workPackages = packageManager.getInstalledPackagesAsUser( + GET_ACTIVITIES, workProfile.getIdentifier()); + List allPackages = new ArrayList<>(personalPackages); + for (PackageInfo workPackage : workPackages) { + if (allPackages.stream().noneMatch( + p -> workPackage.packageName.equals(p.packageName))) { + allPackages.add(workPackage); + } + } + return allPackages; + } + /** * @return the number of applications that can interact across profiles. */ static int getNumberOfEnabledApps( Context context, PackageManager packageManager, UserManager userManager, CrossProfileApps crossProfileApps) { + UserHandle workProfile = getWorkProfile(userManager); + if (workProfile == null) { + return 0; + } + UserHandle personalProfile = userManager.getProfileParent(workProfile); + if (personalProfile == null) { + return 0; + } final ArrayList> apps = collectConfigurableApps(packageManager, userManager, crossProfileApps); apps.removeIf( app -> !InteractAcrossProfilesDetails.isInteractAcrossProfilesEnabled( - context, app.first.packageName, app.first.uid)); + context, app.first.packageName) + || !crossProfileApps.canConfigureInteractAcrossProfiles( + app.first.packageName)); return apps.size(); } /** - * Returns the personal profile in the profile group of the calling user. - * Returns null if user is not in a profile group. + * Returns the work profile in the profile group of the calling user. + * Returns null if not found. */ @Nullable - private static UserHandle getPersonalProfileForCallingUser(UserManager userManager) { - final int callingUser = UserHandle.myUserId(); - if (userManager.getProfiles(callingUser).isEmpty()) { - return null; + static UserHandle getWorkProfile(UserManager userManager) { + for (UserInfo user : userManager.getProfiles(UserHandle.myUserId())) { + if (userManager.isManagedProfile(user.id)) { + return user.getUserHandle(); + } } - final UserInfo parentProfile = userManager.getProfileParent(callingUser); - return parentProfile == null - ? UserHandle.of(callingUser) : parentProfile.getUserHandle(); + return null; } public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = diff --git a/tests/robotests/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesDetailsTest.java b/tests/robotests/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesDetailsTest.java index 9e48b9ea3bb..bae9a12670b 100644 --- a/tests/robotests/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesDetailsTest.java +++ b/tests/robotests/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesDetailsTest.java @@ -22,14 +22,18 @@ import static org.robolectric.Shadows.shadowOf; import android.app.AppOpsManager; import android.content.Context; +import android.content.pm.CrossProfileApps; import android.content.pm.PackageManager; import android.content.pm.PermissionInfo; -import android.os.Process; +import android.content.pm.UserInfo; +import android.os.UserManager; import androidx.test.core.app.ApplicationProvider; import com.android.settings.R; +import com.google.common.collect.ImmutableList; + import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @@ -37,6 +41,9 @@ import org.robolectric.RobolectricTestRunner; @RunWith(RobolectricTestRunner.class) public class InteractAcrossProfilesDetailsTest { + private static final int PERSONAL_PROFILE_ID = 0; + private static final int WORK_PROFILE_ID = 10; + private static final int PACKAGE_UID = 0; private static final String CROSS_PROFILE_PACKAGE_NAME = "crossProfilePackage"; public static final String INTERACT_ACROSS_PROFILES_PERMISSION = "android.permission.INTERACT_ACROSS_PROFILES"; @@ -44,29 +51,53 @@ public class InteractAcrossProfilesDetailsTest { private final Context mContext = ApplicationProvider.getApplicationContext(); private final AppOpsManager mAppOpsManager = mContext.getSystemService(AppOpsManager.class); private final PackageManager mPackageManager = mContext.getPackageManager(); - private final InteractAcrossProfilesDetails mFragment = new InteractAcrossProfilesDetails(); + private final UserManager mUserManager = mContext.getSystemService(UserManager.class); + private final CrossProfileApps mCrossProfileApps = mContext.getSystemService( + CrossProfileApps.class); @Test public void getPreferenceSummary_appOpAllowed_returnsAllowed() { + shadowOf(mUserManager).addUser( + PERSONAL_PROFILE_ID, "personal-profile"/* name */, 0/* flags */); + shadowOf(mUserManager).addProfile( + PERSONAL_PROFILE_ID, WORK_PROFILE_ID, + "work-profile"/* profileName */, UserInfo.FLAG_MANAGED_PROFILE); + shadowOf(mPackageManager).setInstalledPackagesForUserId( + PERSONAL_PROFILE_ID, ImmutableList.of(CROSS_PROFILE_PACKAGE_NAME)); + shadowOf(mPackageManager).setInstalledPackagesForUserId( + WORK_PROFILE_ID, ImmutableList.of(CROSS_PROFILE_PACKAGE_NAME)); + shadowOf(mCrossProfileApps).addCrossProfilePackage( + CROSS_PROFILE_PACKAGE_NAME); String appOp = AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION); shadowOf(mAppOpsManager).setMode( - appOp, Process.myUid(), CROSS_PROFILE_PACKAGE_NAME, AppOpsManager.MODE_ALLOWED); + appOp, PACKAGE_UID, CROSS_PROFILE_PACKAGE_NAME, AppOpsManager.MODE_ALLOWED); shadowOf(mPackageManager).addPermissionInfo(createCrossProfilesPermissionInfo()); - assertThat(mFragment.getPreferenceSummary( - mContext, CROSS_PROFILE_PACKAGE_NAME, Process.myUid())) + assertThat(InteractAcrossProfilesDetails.getPreferenceSummary( + mContext, CROSS_PROFILE_PACKAGE_NAME)) .isEqualTo(mContext.getString(R.string.app_permission_summary_allowed)); } @Test public void getPreferenceSummary_appOpNotAllowed_returnsNotAllowed() { + shadowOf(mUserManager).addUser( + PERSONAL_PROFILE_ID, "personal-profile"/* name */, 0/* flags */); + shadowOf(mUserManager).addProfile( + PERSONAL_PROFILE_ID, WORK_PROFILE_ID, + "work-profile"/* profileName */, UserInfo.FLAG_MANAGED_PROFILE); + shadowOf(mPackageManager).setInstalledPackagesForUserId( + PERSONAL_PROFILE_ID, ImmutableList.of(CROSS_PROFILE_PACKAGE_NAME)); + shadowOf(mPackageManager).setInstalledPackagesForUserId( + WORK_PROFILE_ID, ImmutableList.of(CROSS_PROFILE_PACKAGE_NAME)); + shadowOf(mCrossProfileApps).addCrossProfilePackage( + CROSS_PROFILE_PACKAGE_NAME); String appOp = AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION); shadowOf(mAppOpsManager).setMode( - appOp, Process.myUid(), CROSS_PROFILE_PACKAGE_NAME, AppOpsManager.MODE_IGNORED); + appOp, PACKAGE_UID, CROSS_PROFILE_PACKAGE_NAME, AppOpsManager.MODE_IGNORED); shadowOf(mPackageManager).addPermissionInfo(createCrossProfilesPermissionInfo()); - assertThat(mFragment.getPreferenceSummary( - mContext, CROSS_PROFILE_PACKAGE_NAME, Process.myUid())) + assertThat(InteractAcrossProfilesDetails.getPreferenceSummary( + mContext, CROSS_PROFILE_PACKAGE_NAME)) .isEqualTo(mContext.getString(R.string.app_permission_summary_not_allowed)); } diff --git a/tests/robotests/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesPreferenceControllerTest.java index bac7437d81d..8479035804b 100644 --- a/tests/robotests/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesPreferenceControllerTest.java @@ -21,12 +21,15 @@ import static com.google.common.truth.Truth.assertThat; import static org.robolectric.Shadows.shadowOf; import android.content.Context; -import android.content.pm.CrossProfileApps; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; import androidx.test.core.app.ApplicationProvider; import com.android.settings.core.BasePreferenceController; +import com.google.common.collect.ImmutableList; + import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @@ -36,25 +39,34 @@ public class InteractAcrossProfilesPreferenceControllerTest { private static final String CROSS_PROFILE_PACKAGE_NAME = "crossProfilePackage"; private static final String NOT_CROSS_PROFILE_PACKAGE_NAME = "notCrossProfilePackage"; + public static final String INTERACT_ACROSS_PROFILES_PERMISSION = + "android.permission.INTERACT_ACROSS_PROFILES"; + private static final int PROFILE_ID = 0; private final Context mContext = ApplicationProvider.getApplicationContext(); - private final CrossProfileApps mCrossProfileApps = - mContext.getSystemService(CrossProfileApps.class); + private final PackageManager mPackageManager = mContext.getPackageManager(); private final InteractAcrossProfilesDetailsPreferenceController mController = new InteractAcrossProfilesDetailsPreferenceController(mContext, "test_key"); @Test - public void getAvailabilityStatus_crossProfilePackage_returnsAvailable() { + public void getAvailabilityStatus_requestedCrossProfilePermission_returnsAvailable() { mController.setPackageName(CROSS_PROFILE_PACKAGE_NAME); - shadowOf(mCrossProfileApps).addCrossProfilePackage(CROSS_PROFILE_PACKAGE_NAME); + shadowOf(mPackageManager).setInstalledPackagesForUserId( + PROFILE_ID, ImmutableList.of(CROSS_PROFILE_PACKAGE_NAME)); + PackageInfo packageInfo = shadowOf(mPackageManager).getInternalMutablePackageInfo( + CROSS_PROFILE_PACKAGE_NAME); + packageInfo.requestedPermissions = new String[]{ + INTERACT_ACROSS_PROFILES_PERMISSION}; assertThat(mController.getAvailabilityStatus()) .isEqualTo(BasePreferenceController.AVAILABLE); } @Test - public void getAvailabilityStatus_notCrossProfilePackage_returnsDisabled() { + public void getAvailabilityStatus_notRequestedCrossProfilePermission_returnsDisabled() { mController.setPackageName(NOT_CROSS_PROFILE_PACKAGE_NAME); + shadowOf(mPackageManager).setInstalledPackagesForUserId( + PROFILE_ID, ImmutableList.of(NOT_CROSS_PROFILE_PACKAGE_NAME)); assertThat(mController.getAvailabilityStatus()) .isEqualTo(BasePreferenceController.DISABLED_FOR_USER); diff --git a/tests/robotests/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesSettingsTest.java b/tests/robotests/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesSettingsTest.java index 19ea50b6572..dac3e22913f 100644 --- a/tests/robotests/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesSettingsTest.java +++ b/tests/robotests/src/com/android/settings/applications/specialaccess/interactacrossprofiles/InteractAcrossProfilesSettingsTest.java @@ -18,14 +18,17 @@ package com.android.settings.applications.specialaccess.interactacrossprofiles; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertTrue; import static org.robolectric.Shadows.shadowOf; import android.app.AppOpsManager; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.CrossProfileApps; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PermissionInfo; +import android.content.pm.UserInfo; import android.os.UserHandle; import android.os.UserManager; import android.util.Pair; @@ -68,49 +71,56 @@ public class InteractAcrossProfilesSettingsTest { private final CrossProfileApps mCrossProfileApps = mContext.getSystemService(CrossProfileApps.class); private final AppOpsManager mAppOpsManager = mContext.getSystemService(AppOpsManager.class); - private final InteractAcrossProfilesSettings mFragment = new InteractAcrossProfilesSettings(); @Test - public void collectConfigurableApps_fromPersonal_returnsPersonalPackages() { + public void collectConfigurableApps_fromPersonal_returnsCombinedPackages() { shadowOf(mUserManager).addUser( PERSONAL_PROFILE_ID, "personal-profile"/* name */, 0/* flags */); shadowOf(mUserManager).addProfile( PERSONAL_PROFILE_ID, WORK_PROFILE_ID, - "work-profile"/* profileName */, 0/* profileFlags */); + "work-profile"/* profileName */, UserInfo.FLAG_MANAGED_PROFILE); shadowOf(mPackageManager).setInstalledPackagesForUserId( PERSONAL_PROFILE_ID, PERSONAL_PROFILE_INSTALLED_PACKAGES); shadowOf(mPackageManager).setInstalledPackagesForUserId( WORK_PROFILE_ID, WORK_PROFILE_INSTALLED_PACKAGES); - shadowOf(mCrossProfileApps).addCrossProfilePackage(PERSONAL_CROSS_PROFILE_PACKAGE); - shadowOf(mCrossProfileApps).addCrossProfilePackage(WORK_CROSS_PROFILE_PACKAGE); + installCrossProfilePackage(PERSONAL_PROFILE_ID, PERSONAL_CROSS_PROFILE_PACKAGE); + installCrossProfilePackage(WORK_PROFILE_ID, WORK_CROSS_PROFILE_PACKAGE); - List> apps = mFragment.collectConfigurableApps( - mPackageManager, mUserManager, mCrossProfileApps); + List> apps = + InteractAcrossProfilesSettings.collectConfigurableApps( + mPackageManager, mUserManager, mCrossProfileApps); - assertThat(apps.size()).isEqualTo(1); - assertThat(apps.get(0).first.packageName).isEqualTo(PERSONAL_CROSS_PROFILE_PACKAGE); + assertThat(apps.size()).isEqualTo(2); + assertTrue(apps.stream().anyMatch( + app -> app.first.packageName.equals(PERSONAL_CROSS_PROFILE_PACKAGE))); + assertTrue(apps.stream().anyMatch( + app -> app.first.packageName.equals(WORK_CROSS_PROFILE_PACKAGE))); } @Test - public void collectConfigurableApps_fromWork_returnsPersonalPackages() { + public void collectConfigurableApps_fromWork_returnsCombinedPackages() { shadowOf(mUserManager).addUser( PERSONAL_PROFILE_ID, "personal-profile"/* name */, 0/* flags */); shadowOf(mUserManager).addProfile( PERSONAL_PROFILE_ID, WORK_PROFILE_ID, - "work-profile"/* profileName */, 0/* profileFlags */); + "work-profile"/* profileName */, UserInfo.FLAG_MANAGED_PROFILE); ShadowProcess.setUid(WORK_UID); shadowOf(mPackageManager).setInstalledPackagesForUserId( PERSONAL_PROFILE_ID, PERSONAL_PROFILE_INSTALLED_PACKAGES); shadowOf(mPackageManager).setInstalledPackagesForUserId( WORK_PROFILE_ID, WORK_PROFILE_INSTALLED_PACKAGES); - shadowOf(mCrossProfileApps).addCrossProfilePackage(PERSONAL_CROSS_PROFILE_PACKAGE); - shadowOf(mCrossProfileApps).addCrossProfilePackage(WORK_CROSS_PROFILE_PACKAGE); + installCrossProfilePackage(PERSONAL_PROFILE_ID, PERSONAL_CROSS_PROFILE_PACKAGE); + installCrossProfilePackage(WORK_PROFILE_ID, WORK_CROSS_PROFILE_PACKAGE); - List> apps = mFragment.collectConfigurableApps( - mPackageManager, mUserManager, mCrossProfileApps); + List> apps = + InteractAcrossProfilesSettings.collectConfigurableApps( + mPackageManager, mUserManager, mCrossProfileApps); - assertThat(apps.size()).isEqualTo(1); - assertThat(apps.get(0).first.packageName).isEqualTo(PERSONAL_CROSS_PROFILE_PACKAGE); + assertThat(apps.size()).isEqualTo(2); + assertTrue(apps.stream().anyMatch( + app -> app.first.packageName.equals(PERSONAL_CROSS_PROFILE_PACKAGE))); + assertTrue(apps.stream().anyMatch( + app -> app.first.packageName.equals(WORK_CROSS_PROFILE_PACKAGE))); } @Test @@ -119,10 +129,11 @@ public class InteractAcrossProfilesSettingsTest { PERSONAL_PROFILE_ID, "personal-profile"/* name */, 0/* flags */); shadowOf(mPackageManager).setInstalledPackagesForUserId( PERSONAL_PROFILE_ID, PERSONAL_PROFILE_INSTALLED_PACKAGES); - shadowOf(mCrossProfileApps).addCrossProfilePackage(PERSONAL_CROSS_PROFILE_PACKAGE); + installCrossProfilePackage(PERSONAL_PROFILE_ID, PERSONAL_CROSS_PROFILE_PACKAGE); - List> apps = mFragment.collectConfigurableApps( - mPackageManager, mUserManager, mCrossProfileApps); + List> apps = + InteractAcrossProfilesSettings.collectConfigurableApps( + mPackageManager, mUserManager, mCrossProfileApps); assertThat(apps).isEmpty(); } @@ -133,11 +144,14 @@ public class InteractAcrossProfilesSettingsTest { PERSONAL_PROFILE_ID, "personal-profile"/* name */, 0/* flags */); shadowOf(mUserManager).addProfile( PERSONAL_PROFILE_ID, WORK_PROFILE_ID, - "work-profile"/* profileName */, 0/* profileFlags */); + "work-profile"/* profileName */, UserInfo.FLAG_MANAGED_PROFILE); shadowOf(mPackageManager).setInstalledPackagesForUserId( PERSONAL_PROFILE_ID, PERSONAL_PROFILE_INSTALLED_PACKAGES); + shadowOf(mPackageManager).setInstalledPackagesForUserId( + WORK_PROFILE_ID, WORK_PROFILE_INSTALLED_PACKAGES); + installCrossProfilePackage(PERSONAL_PROFILE_ID, PERSONAL_CROSS_PROFILE_PACKAGE); + installCrossProfilePackage(WORK_PROFILE_ID, WORK_CROSS_PROFILE_PACKAGE); shadowOf(mCrossProfileApps).addCrossProfilePackage(PERSONAL_CROSS_PROFILE_PACKAGE); - shadowOf(mCrossProfileApps).addCrossProfilePackage(PERSONAL_NON_CROSS_PROFILE_PACKAGE); String appOp = AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION); shadowOf(mAppOpsManager).setMode( appOp, PACKAGE_UID, PERSONAL_CROSS_PROFILE_PACKAGE, AppOpsManager.MODE_ALLOWED); @@ -145,12 +159,19 @@ public class InteractAcrossProfilesSettingsTest { appOp, PACKAGE_UID, PERSONAL_NON_CROSS_PROFILE_PACKAGE, AppOpsManager.MODE_IGNORED); shadowOf(mPackageManager).addPermissionInfo(createCrossProfilesPermissionInfo()); - int numOfApps = mFragment.getNumberOfEnabledApps( + int numOfApps = InteractAcrossProfilesSettings.getNumberOfEnabledApps( mContext, mPackageManager, mUserManager, mCrossProfileApps); assertThat(numOfApps).isEqualTo(1); } + private void installCrossProfilePackage(int profileId, String packageName) { + PackageInfo personalPackageInfo = shadowOf(mPackageManager).getInternalMutablePackageInfo( + packageName); + personalPackageInfo.requestedPermissions = new String[]{ + INTERACT_ACROSS_PROFILES_PERMISSION}; + } + private PermissionInfo createCrossProfilesPermissionInfo() { PermissionInfo permissionInfo = new PermissionInfo(); permissionInfo.name = INTERACT_ACROSS_PROFILES_PERMISSION;