| /* |
| * Copyright (C) 2020 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.server.pm; |
| |
| import static android.app.AppOpsManager.MODE_ALLOWED; |
| import static android.app.AppOpsManager.MODE_DEFAULT; |
| import static android.app.AppOpsManager.OP_INTERACT_ACROSS_PROFILES; |
| import static android.content.Intent.FLAG_RECEIVER_FOREGROUND; |
| import static android.content.Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND; |
| import static android.content.Intent.FLAG_RECEIVER_REGISTERED_ONLY; |
| import static android.content.pm.CrossProfileApps.ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| |
| import static org.junit.Assert.fail; |
| import static org.mockito.ArgumentMatchers.anyInt; |
| import static org.mockito.ArgumentMatchers.eq; |
| import static org.mockito.Mockito.when; |
| import static org.robolectric.Shadows.shadowOf; |
| |
| import android.Manifest; |
| import android.annotation.UserIdInt; |
| import android.app.ActivityManagerInternal; |
| import android.app.AppOpsManager; |
| import android.app.AppOpsManager.Mode; |
| import android.app.admin.DevicePolicyManagerInternal; |
| import android.content.ComponentName; |
| import android.content.ContextWrapper; |
| import android.content.Intent; |
| import android.content.pm.ActivityInfo; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.IPackageManager; |
| import android.content.pm.PackageInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.PackageManagerInternal; |
| import android.content.pm.PermissionInfo; |
| import android.content.pm.ResolveInfo; |
| import android.os.Process; |
| import android.os.UserHandle; |
| import android.os.UserManager; |
| import android.platform.test.annotations.Presubmit; |
| |
| import androidx.test.core.app.ApplicationProvider; |
| |
| import com.android.internal.util.FunctionalUtils.ThrowingRunnable; |
| import com.android.internal.util.FunctionalUtils.ThrowingSupplier; |
| import com.android.server.LocalServices; |
| import com.android.server.pm.parsing.pkg.AndroidPackage; |
| import com.android.server.pm.parsing.pkg.PackageImpl; |
| import com.android.server.pm.parsing.pkg.ParsedPackage; |
| import com.android.server.testing.shadows.ShadowApplicationPackageManager; |
| import com.android.server.testing.shadows.ShadowUserManager; |
| import com.android.server.wm.ActivityTaskManagerInternal; |
| |
| import com.google.android.collect.Lists; |
| |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.Mock; |
| import org.mockito.MockitoAnnotations; |
| import org.robolectric.RobolectricTestRunner; |
| import org.robolectric.annotation.Config; |
| import org.robolectric.shadow.api.Shadow; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** Unit tests for {@link CrossProfileAppsServiceImpl}. */ |
| @RunWith(RobolectricTestRunner.class) |
| @Presubmit |
| @Config(shadows = {ShadowUserManager.class, ShadowApplicationPackageManager.class}) |
| public class CrossProfileAppsServiceImplRoboTest { |
| private static final int CALLING_UID = 1111; |
| private static final int CALLING_PID = 1000; |
| private static final String CROSS_PROFILE_APP_PACKAGE_NAME = |
| "com.android.server.pm.crossprofileappsserviceimplrobotest.crossprofileapp"; |
| @UserIdInt private static final int PERSONAL_PROFILE_USER_ID = 0; |
| private static final int PERSONAL_PROFILE_UID = 2222; |
| @UserIdInt private static final int WORK_PROFILE_USER_ID = 10; |
| private static final int WORK_PROFILE_UID = 3333; |
| private static final int OTHER_PROFILE_WITHOUT_CROSS_PROFILE_APP_USER_ID = 20; |
| @UserIdInt private static final int OTHER_PROFILE_GROUP_USER_ID = 30; |
| private static final int OTHER_PROFILE_GROUP_UID = 4444; |
| @UserIdInt private static final int OTHER_PROFILE_GROUP_2_USER_ID = 31; |
| private static final int OTHER_PROFILE_GROUP_2_UID = 5555; |
| |
| private final ContextWrapper mContext = ApplicationProvider.getApplicationContext(); |
| private final UserManager mUserManager = mContext.getSystemService(UserManager.class); |
| private final AppOpsManager mAppOpsManager = mContext.getSystemService(AppOpsManager.class); |
| private final PackageManager mPackageManager = mContext.getPackageManager(); |
| private final TestInjector mInjector = new TestInjector(); |
| private final CrossProfileAppsServiceImpl mCrossProfileAppsServiceImpl = |
| new CrossProfileAppsServiceImpl(mContext, mInjector); |
| private final Map<UserHandle, Set<Intent>> mSentUserBroadcasts = new HashMap<>(); |
| private final Map<Integer, List<ApplicationInfo>> installedApplications = new HashMap<>(); |
| private final Set<Integer> mKilledUids = new HashSet<>(); |
| |
| @Mock private PackageManagerInternal mPackageManagerInternal; |
| @Mock private IPackageManager mIPackageManager; |
| @Mock private DevicePolicyManagerInternal mDevicePolicyManagerInternal; |
| |
| @Before |
| public void initializeMocks() throws Exception { |
| MockitoAnnotations.initMocks(this); |
| initializeInstalledApplicationsMock(); |
| mockCrossProfileAppInstalledAndEnabledOnEachProfile(); |
| mockCrossProfileAppRequestsInteractAcrossProfiles(); |
| mockCrossProfileAppRegistersBroadcastReceiver(); |
| mockCrossProfileAppWhitelisted(); |
| } |
| |
| private void initializeInstalledApplicationsMock() { |
| when(mPackageManagerInternal.getInstalledApplications(anyInt(), anyInt(), eq(CALLING_UID))) |
| .thenAnswer(invocation -> installedApplications.get(invocation.getArgument(1))); |
| } |
| |
| private void mockCrossProfileAppInstalledAndEnabledOnEachProfile() { |
| // They are enabled by default, so we simply have to ensure that a package info with an |
| // application info is returned. |
| final PackageInfo packageInfo = buildTestPackageInfo(); |
| mockCrossProfileAppInstalledOnProfile( |
| packageInfo, PERSONAL_PROFILE_USER_ID, PERSONAL_PROFILE_UID); |
| mockCrossProfileAppInstalledOnProfile(packageInfo, WORK_PROFILE_USER_ID, WORK_PROFILE_UID); |
| mockCrossProfileAppInstalledOnProfile( |
| packageInfo, OTHER_PROFILE_GROUP_USER_ID, OTHER_PROFILE_GROUP_UID); |
| mockCrossProfileAppInstalledOnProfile( |
| packageInfo, OTHER_PROFILE_GROUP_2_USER_ID, OTHER_PROFILE_GROUP_2_UID); |
| } |
| |
| private void mockCrossProfileAppInstalledOnProfile( |
| PackageInfo packageInfo, @UserIdInt int userId, int uid) { |
| when(mPackageManagerInternal.getPackageInfo( |
| eq(CROSS_PROFILE_APP_PACKAGE_NAME), |
| /* flags= */ anyInt(), |
| /* filterCallingUid= */ anyInt(), |
| eq(userId))) |
| .thenReturn(packageInfo); |
| when(mPackageManagerInternal.getPackage(uid)) |
| .thenReturn(((ParsedPackage) PackageImpl.forTesting(CROSS_PROFILE_APP_PACKAGE_NAME) |
| .hideAsParsed()).hideAsFinal()); |
| installedApplications.putIfAbsent(userId, new ArrayList<>()); |
| installedApplications.get(userId).add(packageInfo.applicationInfo); |
| } |
| |
| private PackageInfo buildTestPackageInfo() { |
| PackageInfo packageInfo = new PackageInfo(); |
| packageInfo.applicationInfo = new ApplicationInfo(); |
| packageInfo.applicationInfo.packageName = CROSS_PROFILE_APP_PACKAGE_NAME; |
| return packageInfo; |
| } |
| |
| private void mockCrossProfileAppRequestsInteractAcrossProfiles() throws Exception { |
| final String permissionName = Manifest.permission.INTERACT_ACROSS_PROFILES; |
| when(mIPackageManager.getAppOpPermissionPackages(permissionName)) |
| .thenReturn(new String[] {CROSS_PROFILE_APP_PACKAGE_NAME}); |
| } |
| |
| private void mockCrossProfileAppRegistersBroadcastReceiver() { |
| final ShadowApplicationPackageManager shadowApplicationPackageManager = |
| Shadow.extract(mPackageManager); |
| final Intent baseIntent = |
| new Intent(ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED) |
| .setPackage(CROSS_PROFILE_APP_PACKAGE_NAME); |
| final Intent manifestIntent = |
| new Intent(baseIntent) |
| .setFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND |
| | Intent.FLAG_RECEIVER_FOREGROUND); |
| final Intent registeredIntent = |
| new Intent(baseIntent).setFlags(FLAG_RECEIVER_REGISTERED_ONLY); |
| final List<ResolveInfo> resolveInfos = Lists.newArrayList(buildTestResolveInfo()); |
| shadowApplicationPackageManager.setResolveInfosForIntent(manifestIntent, resolveInfos); |
| shadowApplicationPackageManager.setResolveInfosForIntent(registeredIntent, resolveInfos); |
| } |
| |
| private ResolveInfo buildTestResolveInfo() { |
| final ResolveInfo resolveInfo = new ResolveInfo(); |
| resolveInfo.activityInfo = new ActivityInfo(); |
| resolveInfo.activityInfo.packageName = CROSS_PROFILE_APP_PACKAGE_NAME; |
| resolveInfo.activityInfo.name = CROSS_PROFILE_APP_PACKAGE_NAME + ".Receiver"; |
| return resolveInfo; |
| } |
| |
| private void mockCrossProfileAppWhitelisted() { |
| when(mDevicePolicyManagerInternal.getAllCrossProfilePackages()) |
| .thenReturn(Lists.newArrayList(CROSS_PROFILE_APP_PACKAGE_NAME)); |
| } |
| |
| @Before |
| public void setUpCrossProfileAppUidsAndPackageNames() { |
| setUpCrossProfileAppUidAndPackageName( |
| PERSONAL_PROFILE_UID, PERSONAL_PROFILE_USER_ID); |
| setUpCrossProfileAppUidAndPackageName( |
| WORK_PROFILE_UID, WORK_PROFILE_USER_ID); |
| setUpCrossProfileAppUidAndPackageName( |
| OTHER_PROFILE_GROUP_UID, OTHER_PROFILE_GROUP_USER_ID); |
| setUpCrossProfileAppUidAndPackageName( |
| OTHER_PROFILE_GROUP_2_UID, OTHER_PROFILE_GROUP_2_USER_ID); |
| } |
| |
| private void setUpCrossProfileAppUidAndPackageName(int uid, @UserIdInt int userId) { |
| ShadowApplicationPackageManager.setPackageUidAsUser( |
| CROSS_PROFILE_APP_PACKAGE_NAME, uid, userId); |
| when(mPackageManagerInternal |
| .getPackageUid(CROSS_PROFILE_APP_PACKAGE_NAME, /* flags= */ 0, userId)) |
| .thenReturn(uid); |
| } |
| |
| @Before |
| public void grantPermissions() { |
| grantPermissions( |
| Manifest.permission.MANAGE_APP_OPS_MODES, |
| Manifest.permission.UPDATE_APP_OPS_STATS, |
| Manifest.permission.CONFIGURE_INTERACT_ACROSS_PROFILES, |
| Manifest.permission.INTERACT_ACROSS_USERS, |
| Manifest.permission.INTERACT_ACROSS_USERS_FULL); |
| } |
| |
| @Before |
| public void setUpProfiles() { |
| final ShadowUserManager shadowUserManager = Shadow.extract(mUserManager); |
| shadowUserManager.addProfileIds( |
| PERSONAL_PROFILE_USER_ID, |
| WORK_PROFILE_USER_ID, |
| OTHER_PROFILE_WITHOUT_CROSS_PROFILE_APP_USER_ID); |
| shadowUserManager.addProfileIds( |
| OTHER_PROFILE_GROUP_USER_ID, |
| OTHER_PROFILE_GROUP_2_USER_ID); |
| } |
| |
| @Before |
| public void setInteractAcrossProfilesAppOpDefault() { |
| // It seems to be necessary to provide the shadow with the default already specified in |
| // AppOpsManager. |
| final int defaultMode = AppOpsManager.opToDefaultMode(OP_INTERACT_ACROSS_PROFILES); |
| explicitlySetInteractAcrossProfilesAppOp(PERSONAL_PROFILE_UID, defaultMode); |
| explicitlySetInteractAcrossProfilesAppOp(WORK_PROFILE_UID, defaultMode); |
| explicitlySetInteractAcrossProfilesAppOp(OTHER_PROFILE_GROUP_UID, defaultMode); |
| explicitlySetInteractAcrossProfilesAppOp(OTHER_PROFILE_GROUP_2_UID, defaultMode); |
| } |
| |
| @Test |
| public void setInteractAcrossProfilesAppOp_noPermissions_throwsSecurityException() { |
| denyPermissions( |
| Manifest.permission.MANAGE_APP_OPS_MODES, |
| Manifest.permission.UPDATE_APP_OPS_STATS, |
| Manifest.permission.CONFIGURE_INTERACT_ACROSS_PROFILES, |
| Manifest.permission.INTERACT_ACROSS_USERS, |
| Manifest.permission.INTERACT_ACROSS_USERS_FULL); |
| try { |
| mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp( |
| CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED); |
| fail(); |
| } catch (SecurityException expected) {} |
| } |
| |
| @Test |
| public void setInteractAcrossProfilesAppOp_missingInteractAcrossUsersAndFull_throwsSecurityException() { |
| denyPermissions( |
| Manifest.permission.INTERACT_ACROSS_USERS, |
| Manifest.permission.INTERACT_ACROSS_USERS_FULL); |
| grantPermissions(Manifest.permission.CONFIGURE_INTERACT_ACROSS_PROFILES); |
| try { |
| mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp( |
| CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED); |
| fail(); |
| } catch (SecurityException expected) {} |
| } |
| |
| @Test |
| public void setInteractAcrossProfilesAppOp_setsAppOp() { |
| mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp( |
| CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED); |
| assertThat(getCrossProfileAppOp()).isEqualTo(MODE_ALLOWED); |
| } |
| |
| @Test |
| public void setInteractAcrossProfilesAppOp_configureInteractAcrossProfilesPermissionWithoutAppOpsPermissions_setsAppOp() { |
| denyPermissions( |
| Manifest.permission.MANAGE_APP_OPS_MODES, |
| Manifest.permission.UPDATE_APP_OPS_STATS); |
| grantPermissions( |
| Manifest.permission.CONFIGURE_INTERACT_ACROSS_PROFILES, |
| Manifest.permission.INTERACT_ACROSS_USERS); |
| |
| mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp( |
| CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED); |
| |
| assertThat(getCrossProfileAppOp()).isEqualTo(MODE_ALLOWED); |
| } |
| |
| @Test |
| public void setInteractAcrossProfilesAppOp_appOpsPermissionsWithoutConfigureInteractAcrossProfilesPermission_setsAppOp() { |
| denyPermissions(Manifest.permission.CONFIGURE_INTERACT_ACROSS_PROFILES); |
| grantPermissions( |
| Manifest.permission.MANAGE_APP_OPS_MODES, |
| Manifest.permission.UPDATE_APP_OPS_STATS, |
| Manifest.permission.INTERACT_ACROSS_USERS); |
| |
| mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp( |
| CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED); |
| |
| assertThat(getCrossProfileAppOp()).isEqualTo(MODE_ALLOWED); |
| } |
| |
| @Test |
| public void setInteractAcrossProfilesAppOp_setsAppOpWithUsersAndWithoutFull() { |
| denyPermissions(Manifest.permission.INTERACT_ACROSS_USERS_FULL); |
| grantPermissions(Manifest.permission.INTERACT_ACROSS_USERS); |
| mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp( |
| CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED); |
| assertThat(getCrossProfileAppOp()).isEqualTo(MODE_ALLOWED); |
| } |
| |
| @Test |
| public void setInteractAcrossProfilesAppOp_setsAppOpWithFullAndWithoutUsers() { |
| denyPermissions(Manifest.permission.INTERACT_ACROSS_USERS); |
| grantPermissions(Manifest.permission.INTERACT_ACROSS_USERS_FULL); |
| mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp( |
| CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED); |
| assertThat(getCrossProfileAppOp()).isEqualTo(MODE_ALLOWED); |
| } |
| |
| @Test |
| public void setInteractAcrossProfilesAppOp_setsAppOpOnOtherProfile() { |
| mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp( |
| CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED); |
| assertThat(getCrossProfileAppOp(WORK_PROFILE_UID)).isEqualTo(MODE_ALLOWED); |
| } |
| |
| @Test |
| public void setInteractAcrossProfilesAppOp_sendsBroadcast() { |
| mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp( |
| CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED); |
| assertThat(receivedCanInteractAcrossProfilesChangedBroadcast()).isTrue(); |
| } |
| |
| @Test |
| public void setInteractAcrossProfilesAppOp_sendsBroadcastToOtherProfile() { |
| mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp( |
| CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED); |
| assertThat(receivedCanInteractAcrossProfilesChangedBroadcast(WORK_PROFILE_USER_ID)) |
| .isTrue(); |
| } |
| |
| @Test |
| public void setInteractAcrossProfilesAppOp_doesNotSendBroadcastToProfileWithoutPackage() { |
| mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp( |
| CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED); |
| assertThat(receivedCanInteractAcrossProfilesChangedBroadcast( |
| OTHER_PROFILE_WITHOUT_CROSS_PROFILE_APP_USER_ID)) |
| .isFalse(); |
| } |
| |
| @Test |
| public void setInteractAcrossProfilesAppOp_toSameAsCurrent_doesNotSendBroadcast() { |
| explicitlySetInteractAcrossProfilesAppOp(MODE_ALLOWED); |
| mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp( |
| CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED); |
| assertThat(receivedCanInteractAcrossProfilesChangedBroadcast()).isFalse(); |
| } |
| |
| @Test |
| public void setInteractAcrossProfilesAppOp_toAllowed_whenNotAbleToRequest_doesNotSet() { |
| mockCrossProfileAppNotWhitelisted(); |
| mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp( |
| CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED); |
| assertThat(getCrossProfileAppOp()).isNotEqualTo(MODE_ALLOWED); |
| } |
| |
| @Test |
| public void setInteractAcrossProfilesAppOp_toAllowed_whenNotAbleToRequest_doesNotSendBroadcast() { |
| mockCrossProfileAppNotWhitelisted(); |
| mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp( |
| CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED); |
| assertThat(receivedCanInteractAcrossProfilesChangedBroadcast()).isFalse(); |
| } |
| |
| @Test |
| public void setInteractAcrossProfilesAppOp_withoutCrossProfileAttribute_manifestReceiversDoNotGetBroadcast() { |
| declareCrossProfileAttributeOnCrossProfileApp(false); |
| mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp( |
| CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED); |
| assertThat(receivedManifestCanInteractAcrossProfilesChangedBroadcast()).isFalse(); |
| } |
| |
| @Test |
| public void setInteractAcrossProfilesAppOp_withCrossProfileAttribute_manifestReceiversGetBroadcast() { |
| declareCrossProfileAttributeOnCrossProfileApp(true); |
| mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp( |
| CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED); |
| assertThat(receivedManifestCanInteractAcrossProfilesChangedBroadcast()).isTrue(); |
| } |
| |
| @Test |
| public void setInteractAcrossProfilesAppOp_toAllowed_doesNotKillApp() { |
| mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp( |
| CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED); |
| assertThat(mKilledUids).isEmpty(); |
| } |
| |
| @Test |
| public void setInteractAcrossProfilesAppOp_toDisallowed_killsAppsInBothProfiles() { |
| shadowOf(mPackageManager).addPermissionInfo(createCrossProfilesPermissionInfo()); |
| mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp( |
| CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED); |
| |
| mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp( |
| CROSS_PROFILE_APP_PACKAGE_NAME, MODE_DEFAULT); |
| |
| assertThat(mKilledUids).contains(WORK_PROFILE_UID); |
| assertThat(mKilledUids).contains(PERSONAL_PROFILE_UID); |
| } |
| |
| private PermissionInfo createCrossProfilesPermissionInfo() { |
| PermissionInfo permissionInfo = new PermissionInfo(); |
| permissionInfo.name = Manifest.permission.INTERACT_ACROSS_PROFILES; |
| permissionInfo.protectionLevel = PermissionInfo.PROTECTION_FLAG_APPOP; |
| return permissionInfo; |
| } |
| |
| @Test |
| public void setInteractAcrossProfilesAppOp_userToSetInDifferentProfileGroupToCaller_setsAppOp() { |
| mCrossProfileAppsServiceImpl.getLocalService().setInteractAcrossProfilesAppOp( |
| CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED, OTHER_PROFILE_GROUP_USER_ID); |
| assertThat(getCrossProfileAppOp(OTHER_PROFILE_GROUP_UID)).isEqualTo(MODE_ALLOWED); |
| } |
| |
| @Test |
| public void setInteractAcrossProfilesAppOp_userToSetInDifferentProfileGroupToCaller_setsAppOpOnOtherProfile() { |
| mCrossProfileAppsServiceImpl.getLocalService().setInteractAcrossProfilesAppOp( |
| CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED, OTHER_PROFILE_GROUP_USER_ID); |
| assertThat(getCrossProfileAppOp(OTHER_PROFILE_GROUP_2_UID)).isEqualTo(MODE_ALLOWED); |
| } |
| |
| @Test |
| public void setInteractAcrossProfilesAppOp_userToSetInDifferentProfileGroupToCaller_doesNotSetCallerAppOp() { |
| mCrossProfileAppsServiceImpl.getLocalService().setInteractAcrossProfilesAppOp( |
| CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED, OTHER_PROFILE_GROUP_USER_ID); |
| assertThat(getCrossProfileAppOp()).isEqualTo(MODE_DEFAULT); |
| } |
| |
| @Test |
| public void canConfigureInteractAcrossProfiles_packageNotInstalledInProfile_returnsFalse() { |
| mockUninstallCrossProfileAppFromWorkProfile(); |
| assertThat(mCrossProfileAppsServiceImpl |
| .canConfigureInteractAcrossProfiles(CROSS_PROFILE_APP_PACKAGE_NAME)) |
| .isFalse(); |
| } |
| |
| private void mockUninstallCrossProfileAppFromWorkProfile() { |
| when(mPackageManagerInternal.getPackageInfo( |
| eq(CROSS_PROFILE_APP_PACKAGE_NAME), |
| /* flags= */ anyInt(), |
| /* filterCallingUid= */ anyInt(), |
| eq(WORK_PROFILE_USER_ID))) |
| .thenReturn(null); |
| when(mPackageManagerInternal.getPackage(WORK_PROFILE_UID)).thenReturn(null); |
| } |
| |
| @Test |
| public void canConfigureInteractAcrossProfiles_packageDoesNotRequestInteractAcrossProfiles_returnsFalse() |
| throws Exception { |
| mockCrossProfileAppDoesNotRequestInteractAcrossProfiles(); |
| assertThat(mCrossProfileAppsServiceImpl |
| .canConfigureInteractAcrossProfiles(CROSS_PROFILE_APP_PACKAGE_NAME)) |
| .isFalse(); |
| } |
| |
| private void mockCrossProfileAppDoesNotRequestInteractAcrossProfiles() throws Exception { |
| final String permissionName = Manifest.permission.INTERACT_ACROSS_PROFILES; |
| when(mIPackageManager.getAppOpPermissionPackages(permissionName)) |
| .thenReturn(new String[] {}); |
| } |
| |
| @Test |
| public void canConfigureInteractAcrossProfiles_packageNotWhitelisted_returnsFalse() { |
| mockCrossProfileAppNotWhitelisted(); |
| assertThat(mCrossProfileAppsServiceImpl |
| .canConfigureInteractAcrossProfiles(CROSS_PROFILE_APP_PACKAGE_NAME)) |
| .isFalse(); |
| } |
| |
| @Test |
| public void canConfigureInteractAcrossProfiles_returnsTrue() { |
| assertThat(mCrossProfileAppsServiceImpl |
| .canConfigureInteractAcrossProfiles(CROSS_PROFILE_APP_PACKAGE_NAME)) |
| .isTrue(); |
| } |
| |
| @Test |
| public void canUserAttemptToConfigureInteractAcrossProfiles_packageNotInstalledInProfile_returnsTrue() { |
| mockUninstallCrossProfileAppFromWorkProfile(); |
| assertThat(mCrossProfileAppsServiceImpl |
| .canUserAttemptToConfigureInteractAcrossProfiles(CROSS_PROFILE_APP_PACKAGE_NAME)) |
| .isTrue(); |
| } |
| |
| @Test |
| public void canUserAttemptToConfigureInteractAcrossProfiles_packageDoesNotRequestInteractAcrossProfiles_returnsFalse() |
| throws Exception { |
| mockCrossProfileAppDoesNotRequestInteractAcrossProfiles(); |
| assertThat(mCrossProfileAppsServiceImpl |
| .canUserAttemptToConfigureInteractAcrossProfiles(CROSS_PROFILE_APP_PACKAGE_NAME)) |
| .isFalse(); |
| } |
| |
| @Test |
| public void canUserAttemptToConfigureInteractAcrossProfiles_packageNotWhitelisted_returnsTrue() { |
| mockCrossProfileAppNotWhitelisted(); |
| assertThat(mCrossProfileAppsServiceImpl |
| .canUserAttemptToConfigureInteractAcrossProfiles(CROSS_PROFILE_APP_PACKAGE_NAME)) |
| .isTrue(); |
| } |
| |
| @Test |
| public void canUserAttemptToConfigureInteractAcrossProfiles_platformSignedAppWithAutomaticPermission_returnsFalse() { |
| mockCrossProfileAppNotWhitelistedByOem(); |
| shadowOf(mContext).grantPermissions( |
| Process.myPid(), |
| PERSONAL_PROFILE_UID, |
| Manifest.permission.INTERACT_ACROSS_PROFILES); |
| |
| assertThat(mCrossProfileAppsServiceImpl |
| .canUserAttemptToConfigureInteractAcrossProfiles(CROSS_PROFILE_APP_PACKAGE_NAME)) |
| .isFalse(); |
| } |
| |
| @Test |
| public void canUserAttemptToConfigureInteractAcrossProfiles_profileOwnerWorkProfile_returnsFalse() { |
| when(mDevicePolicyManagerInternal.getProfileOwnerAsUser(WORK_PROFILE_USER_ID)) |
| .thenReturn(buildCrossProfileComponentName()); |
| assertThat(mCrossProfileAppsServiceImpl |
| .canUserAttemptToConfigureInteractAcrossProfiles(CROSS_PROFILE_APP_PACKAGE_NAME)) |
| .isFalse(); |
| } |
| |
| @Test |
| public void canUserAttemptToConfigureInteractAcrossProfiles_profileOwnerOtherProfile_returnsFalse() { |
| // Normally, the DPC would not be a profile owner of the personal profile, but for the |
| // purposes of this test, it is just a profile owner of any profile within the profile |
| // group. |
| when(mDevicePolicyManagerInternal.getProfileOwnerAsUser(PERSONAL_PROFILE_USER_ID)) |
| .thenReturn(buildCrossProfileComponentName()); |
| assertThat(mCrossProfileAppsServiceImpl |
| .canUserAttemptToConfigureInteractAcrossProfiles(CROSS_PROFILE_APP_PACKAGE_NAME)) |
| .isFalse(); |
| } |
| |
| @Test |
| public void canUserAttemptToConfigureInteractAcrossProfiles_profileOwnerOutsideProfileGroup_returnsTrue() { |
| when(mDevicePolicyManagerInternal.getProfileOwnerAsUser(OTHER_PROFILE_GROUP_USER_ID)) |
| .thenReturn(buildCrossProfileComponentName()); |
| assertThat(mCrossProfileAppsServiceImpl |
| .canUserAttemptToConfigureInteractAcrossProfiles(CROSS_PROFILE_APP_PACKAGE_NAME)) |
| .isTrue(); |
| } |
| |
| @Test |
| public void canUserAttemptToConfigureInteractAcrossProfiles_returnsTrue() { |
| assertThat(mCrossProfileAppsServiceImpl |
| .canUserAttemptToConfigureInteractAcrossProfiles(CROSS_PROFILE_APP_PACKAGE_NAME)) |
| .isTrue(); |
| } |
| |
| @Test |
| public void clearInteractAcrossProfilesAppOps() { |
| explicitlySetInteractAcrossProfilesAppOp(MODE_ALLOWED); |
| mCrossProfileAppsServiceImpl.clearInteractAcrossProfilesAppOps(); |
| assertThat(getCrossProfileAppOp()).isEqualTo(MODE_DEFAULT); |
| } |
| |
| private void explicitlySetInteractAcrossProfilesAppOp(@Mode int mode) { |
| explicitlySetInteractAcrossProfilesAppOp(PERSONAL_PROFILE_UID, mode); |
| } |
| |
| private void explicitlySetInteractAcrossProfilesAppOp(int uid, @Mode int mode) { |
| shadowOf(mAppOpsManager).setMode( |
| OP_INTERACT_ACROSS_PROFILES, uid, CROSS_PROFILE_APP_PACKAGE_NAME, mode); |
| } |
| |
| private void grantPermissions(String... permissions) { |
| shadowOf(mContext).grantPermissions(Process.myPid(), CALLING_UID, permissions); |
| } |
| |
| private void denyPermissions(String... permissions) { |
| shadowOf(mContext).denyPermissions(Process.myPid(), CALLING_UID, permissions); |
| } |
| |
| private @Mode int getCrossProfileAppOp() { |
| return getCrossProfileAppOp(PERSONAL_PROFILE_UID); |
| } |
| |
| private @Mode int getCrossProfileAppOp(int uid) { |
| return mAppOpsManager.unsafeCheckOpNoThrow( |
| AppOpsManager.permissionToOp(Manifest.permission.INTERACT_ACROSS_PROFILES), |
| uid, |
| CROSS_PROFILE_APP_PACKAGE_NAME); |
| } |
| |
| private boolean receivedCanInteractAcrossProfilesChangedBroadcast() { |
| return receivedCanInteractAcrossProfilesChangedBroadcast(PERSONAL_PROFILE_USER_ID); |
| } |
| |
| private boolean receivedCanInteractAcrossProfilesChangedBroadcast(@UserIdInt int userId) { |
| final UserHandle userHandle = UserHandle.of(userId); |
| if (!mSentUserBroadcasts.containsKey(userHandle)) { |
| return false; |
| } |
| return mSentUserBroadcasts.get(userHandle) |
| .stream() |
| .anyMatch(this::isBroadcastCanInteractAcrossProfilesChanged); |
| } |
| |
| private boolean isBroadcastCanInteractAcrossProfilesChanged(Intent intent) { |
| return intent.getAction().equals(ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED) |
| && CROSS_PROFILE_APP_PACKAGE_NAME.equals(intent.getPackage()); |
| } |
| |
| private void mockCrossProfileAndroidPackage(AndroidPackage androidPackage) { |
| when(mPackageManagerInternal.getPackage(CROSS_PROFILE_APP_PACKAGE_NAME)) |
| .thenReturn(androidPackage); |
| when(mPackageManagerInternal.getPackage(PERSONAL_PROFILE_UID)) |
| .thenReturn(androidPackage); |
| when(mPackageManagerInternal.getPackage(WORK_PROFILE_UID)) |
| .thenReturn(androidPackage); |
| when(mPackageManagerInternal.getPackage(OTHER_PROFILE_GROUP_UID)) |
| .thenReturn(androidPackage); |
| when(mPackageManagerInternal.getPackage(OTHER_PROFILE_GROUP_2_UID)) |
| .thenReturn(androidPackage); |
| } |
| |
| private void mockCrossProfileAppNotWhitelisted() { |
| when(mDevicePolicyManagerInternal.getAllCrossProfilePackages()) |
| .thenReturn(new ArrayList<>()); |
| } |
| |
| private void mockCrossProfileAppNotWhitelistedByOem() { |
| when(mDevicePolicyManagerInternal.getDefaultCrossProfilePackages()) |
| .thenReturn(new ArrayList<>()); |
| } |
| |
| private boolean receivedManifestCanInteractAcrossProfilesChangedBroadcast() { |
| final UserHandle userHandle = UserHandle.of(PERSONAL_PROFILE_USER_ID); |
| if (!mSentUserBroadcasts.containsKey(userHandle)) { |
| return false; |
| } |
| return mSentUserBroadcasts.get(userHandle) |
| .stream() |
| .anyMatch(this::isBroadcastManifestCanInteractAcrossProfilesChanged); |
| } |
| |
| private boolean isBroadcastManifestCanInteractAcrossProfilesChanged(Intent intent) { |
| return isBroadcastCanInteractAcrossProfilesChanged(intent) |
| && (intent.getFlags() & FLAG_RECEIVER_REGISTERED_ONLY) == 0 |
| && (intent.getFlags() & FLAG_RECEIVER_INCLUDE_BACKGROUND) != 0 |
| && (intent.getFlags() & FLAG_RECEIVER_FOREGROUND) != 0 |
| && intent.getComponent() != null |
| && intent.getComponent().getPackageName().equals(CROSS_PROFILE_APP_PACKAGE_NAME); |
| } |
| |
| private void declareCrossProfileAttributeOnCrossProfileApp(boolean value) { |
| mockCrossProfileAndroidPackage( |
| ((ParsedPackage) PackageImpl.forTesting(CROSS_PROFILE_APP_PACKAGE_NAME) |
| .setCrossProfile(value) |
| .hideAsParsed()).hideAsFinal()); |
| } |
| |
| private ComponentName buildCrossProfileComponentName() { |
| return new ComponentName(CROSS_PROFILE_APP_PACKAGE_NAME, "testClassName"); |
| } |
| |
| private class TestInjector implements CrossProfileAppsServiceImpl.Injector { |
| |
| @Override |
| public int getCallingUid() { |
| return CALLING_UID; |
| } |
| |
| @Override |
| public int getCallingPid() { |
| return CALLING_PID; |
| } |
| |
| @Override |
| public @UserIdInt int getCallingUserId() { |
| return PERSONAL_PROFILE_USER_ID; |
| } |
| |
| @Override |
| public UserHandle getCallingUserHandle() { |
| return UserHandle.of(getCallingUserId()); |
| } |
| |
| @Override |
| public long clearCallingIdentity() { |
| return 0; |
| } |
| |
| @Override |
| public void restoreCallingIdentity(long token) {} |
| |
| @Override |
| public void withCleanCallingIdentity(ThrowingRunnable action) { |
| action.run(); |
| } |
| |
| @Override |
| public <T> T withCleanCallingIdentity(ThrowingSupplier<T> action) { |
| return action.get(); |
| } |
| |
| @Override |
| public UserManager getUserManager() { |
| return mUserManager; |
| } |
| |
| @Override |
| public PackageManagerInternal getPackageManagerInternal() { |
| return mPackageManagerInternal; |
| } |
| |
| @Override |
| public PackageManager getPackageManager() { |
| return mPackageManager; |
| } |
| |
| @Override |
| public AppOpsManager getAppOpsManager() { |
| return mAppOpsManager; |
| } |
| |
| @Override |
| public ActivityManagerInternal getActivityManagerInternal() { |
| return LocalServices.getService(ActivityManagerInternal.class); |
| } |
| |
| @Override |
| public ActivityTaskManagerInternal getActivityTaskManagerInternal() { |
| return LocalServices.getService(ActivityTaskManagerInternal.class); |
| } |
| |
| @Override |
| public IPackageManager getIPackageManager() { |
| return mIPackageManager; |
| } |
| |
| @Override |
| public DevicePolicyManagerInternal getDevicePolicyManagerInternal() { |
| return mDevicePolicyManagerInternal; |
| } |
| |
| @Override |
| public void sendBroadcastAsUser(Intent intent, UserHandle user) { |
| // Robolectric's shadows do not currently support sendBroadcastAsUser. |
| final Set<Intent> broadcasts = |
| mSentUserBroadcasts.containsKey(user) |
| ? mSentUserBroadcasts.get(user) |
| : new HashSet<>(); |
| broadcasts.add(intent); |
| mSentUserBroadcasts.put(user, broadcasts); |
| mContext.sendBroadcastAsUser(intent, user); |
| } |
| |
| @Override |
| public int checkComponentPermission( |
| String permission, int uid, int owningUid, boolean exported) { |
| // ActivityManager#checkComponentPermission calls through to |
| // AppGlobals.getPackageManager()#checkUidPermission, which calls through to |
| // ShadowActivityThread with Robolectric. This method is currently not supported there. |
| return mContext.checkPermission(permission, Process.myPid(), uid); |
| } |
| |
| @Override |
| public void killUid(int uid) { |
| mKilledUids.add(uid); |
| } |
| } |
| } |