Fix component query/binding for multiuser
This change apples to non-UI incall services and carmode-UI service.
When a managed profile user make a calls, we query non-UI incall
services for the managed user AND for the parent user.
We also store the parent handle for the later phase of actual binding.
Similar change is made for carmode-UI. If we dont find a carmode-UI
component for a manager user, we also check using the parent user.
Bug:285988874
Test: atest InCallControllerTests and manual whatsapp workprofile call
Test: workprofile regression tests and manual call tests
Change-Id: I6308db89045d73857900b25eaa422afdc108c7ee
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index a952fba..c44227d 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -47,6 +47,7 @@
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
+import android.permission.PermissionManager;
import android.telecom.CallAudioState;
import android.telecom.CallEndpoint;
import android.telecom.ConnectionService;
@@ -146,16 +147,20 @@
private long mBindingStartTime;
private long mDisconnectTime;
+ private boolean mHasCrossUserOrProfilePerm;
+
public InCallServiceInfo(ComponentName componentName,
boolean isExternalCallsSupported,
boolean isSelfManageCallsSupported,
- int type) {
+ int type, boolean hasCrossUserOrProfilePerm) {
mComponentName = componentName;
mIsExternalCallsSupported = isExternalCallsSupported;
mIsSelfManagedCallsSupported = isSelfManageCallsSupported;
mType = type;
+ mHasCrossUserOrProfilePerm = hasCrossUserOrProfilePerm;
}
+ public boolean hasCrossUserOrProfilePermission() { return mHasCrossUserOrProfilePerm; }
public ComponentName getComponentName() {
return mComponentName;
}
@@ -292,8 +297,19 @@
private boolean mIsNullBinding = false;
private NotificationManager mNotificationManager;
+ //this is really used for cases where the userhandle for a call
+ //does not match what we want to use for bindAsUser
+ private final UserHandle mUserHandleToUseForBinding;
+
public InCallServiceBindingConnection(InCallServiceInfo info) {
mInCallServiceInfo = info;
+ mUserHandleToUseForBinding = null;
+ }
+
+ public InCallServiceBindingConnection(InCallServiceInfo info,
+ UserHandle userHandleToUseForBinding) {
+ mInCallServiceInfo = info;
+ mUserHandleToUseForBinding = userHandleToUseForBinding;
}
@Override
@@ -335,14 +351,31 @@
Log.i(this, "Attempting to bind to InCall %s, with %s", mInCallServiceInfo, intent);
mIsConnected = true;
mInCallServiceInfo.setBindingStartTime(mClockProxy.elapsedRealtime());
- UserHandle userToBind = getUserFromCall(call);
- boolean isManagedProfile = UserUtil.isManagedProfile(mContext, userToBind);
+ boolean isManagedProfile = UserUtil.isManagedProfile(mContext, userFromCall);
// Note that UserHandle.CURRENT fails to capture the work profile, so we need to handle
// it separately to ensure that the ICS is bound to the appropriate user. If ECBM is
// active, we know that a work sim was previously used to place a MO emergency call. We
// need to ensure that we bind to the CURRENT_USER in this case, as the work user would
// not be running (handled in getUserFromCall).
- userToBind = isManagedProfile ? userToBind : UserHandle.CURRENT;
+ UserHandle userToBind = isManagedProfile ? userFromCall : UserHandle.CURRENT;
+ if ((mInCallServiceInfo.mType == IN_CALL_SERVICE_TYPE_NON_UI
+ || mInCallServiceInfo.mType == IN_CALL_SERVICE_TYPE_CAR_MODE_UI) && (
+ mUserHandleToUseForBinding != null)) {
+ //guarding change for non-UI/carmode-UI services which may not be present for
+ // work profile.
+ //In this case, we use the parent user handle. (This also seems to be more
+ // accurate that USER_CURRENT since we queried/discovered the packages using the
+ // parent handle)
+ if (mInCallServiceInfo.hasCrossUserOrProfilePermission()) {
+ userToBind = mUserHandleToUseForBinding;
+ } else {
+ Log.i(this,
+ "service does not have INTERACT_ACROSS_PROFILES or "
+ + "INTERACT_ACROSS_USERS permission");
+ }
+ }
+ Log.i(this, "using user id: %s for binding. User from Call is: %s", userToBind,
+ userFromCall);
if (!mContext.bindServiceAsUser(intent, mServiceConnection,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
| Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS
@@ -668,7 +701,7 @@
}
mCarModeConnection = mCurrentConnection =
- new InCallServiceBindingConnection(carModeConnectionInfo);
+ new InCallServiceBindingConnection(carModeConnectionInfo, userHandle);
mIsCarMode = true;
int result = mCurrentConnection.connect(null);
@@ -960,34 +993,137 @@
}
};
+ private UserHandle findChildManagedProfileUser(UserHandle parent, UserManager um) {
+ UserHandle childManagedProfileUser = null;
+
+ //find child managed profile user (if any)
+ List<UserHandle> allUsers = um.getAllProfiles();
+ for (UserHandle u : allUsers) {
+ if ((um.getProfileParent(u) != null) && (um.getProfileParent(u).equals(parent))
+ && um.isManagedProfile(u.getIdentifier())) {
+ //found managed profile child
+ Log.i(this,
+ "Child managed profile user found: " + u.getIdentifier());
+ childManagedProfileUser = u;
+ break;
+ }
+ }
+ return childManagedProfileUser;
+ }
private BroadcastReceiver mPackageChangedReceiver = new BroadcastReceiver() {
+ private List<InCallController.InCallServiceInfo> getNonUiInCallServiceInfoList(
+ Intent intent, UserHandle userHandle) {
+ String changedPackage = intent.getData().getSchemeSpecificPart();
+ List<InCallController.InCallServiceInfo> inCallServiceInfoList =
+ Arrays.stream(intent.getStringArrayExtra(
+ Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST))
+ .map((className) ->
+ ComponentName.createRelative(changedPackage,
+ className))
+ .filter(mKnownNonUiInCallServices::contains)
+ .flatMap(componentName -> getInCallServiceComponents(
+ userHandle, componentName,
+ IN_CALL_SERVICE_TYPE_NON_UI).stream())
+ .collect(Collectors.toList());
+ return ((inCallServiceInfoList != null) ? inCallServiceInfoList : new ArrayList<>());
+ }
+
+ //Here we query components using the userHandle. We then also query components using the
+ //parent userHandle (if any) while removing duplicates. For non-dup components found using
+ //parent userHandle, we use the overloaded InCallServiceBindingConnection constructor.
+ @SuppressWarnings("ReturnValueIgnored")
+ private List<InCallServiceBindingConnection> getNonUiInCallServiceBindingConnectionList(
+ Intent intent, @NonNull UserHandle userHandle, UserHandle parentUserHandle) {
+ List<InCallServiceBindingConnection> result = new ArrayList<>();
+ List<InCallController.InCallServiceInfo> serviceInfoListForParent = new ArrayList<>();
+
+ //query and add components for the child
+ List<InCallController.InCallServiceInfo> serviceInfoListForUser =
+ getNonUiInCallServiceInfoList(intent, userHandle);
+
+ //if user has a parent, get components for parents
+ if (parentUserHandle != null) {
+ serviceInfoListForParent = getNonUiInCallServiceInfoList(intent, parentUserHandle);
+ }
+
+ serviceInfoListForUser
+ .stream()
+ .map(InCallServiceBindingConnection::new)
+ .collect(Collectors.toCollection(() -> result));
+
+ serviceInfoListForParent
+ .stream()
+ .filter((e) -> !(serviceInfoListForUser.contains(e)))
+ .map((serviceinfo) -> new InCallServiceBindingConnection(serviceinfo,
+ parentUserHandle))
+ .collect(Collectors.toCollection(() -> result));
+
+ return result;
+ }
+
@Override
public void onReceive(Context context, Intent intent) {
Log.startSession("ICC.pCR");
+ UserManager um = mContext.getSystemService(UserManager.class);
try {
if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction())) {
synchronized (mLock) {
- String changedPackage = intent.getData().getSchemeSpecificPart();
int uid = intent.getIntExtra(Intent.EXTRA_UID, 0);
UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
- List<InCallServiceBindingConnection> componentsToBind =
- Arrays.stream(intent.getStringArrayExtra(
- Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST))
- .map((className) ->
- ComponentName.createRelative(changedPackage,
- className))
- .filter(mKnownNonUiInCallServices::contains)
- .flatMap(componentName -> getInCallServiceComponents(
- userHandle, componentName,
- IN_CALL_SERVICE_TYPE_NON_UI).stream())
- .map(InCallServiceBindingConnection::new)
- .collect(Collectors.toList());
+ boolean isManagedProfile = um.isManagedProfile(userHandle.getIdentifier());
- if (mNonUIInCallServiceConnections.containsKey(userHandle)) {
- mNonUIInCallServiceConnections.get(userHandle).
- addConnections(componentsToBind);
+ /*
+ There are two possibilities here:
+ 1) We get a work-profile/managed userHandle. In this case we need to check
+ if there are any ongoing calls for that user. If yes, then process further
+ by querying component using this user handle (also bindAsUser using this
+ handle). Else safely ignore it.
+ OR
+ 2) We get the primary/non-managed userHandle. In this case, we have two
+ sub-cases to handle:
+ a) If there are ongoing calls for this user, query components
+ using this user and addConnections
+ b) If there are ongoing calls for the child of this user, we
+ also addConnections to that child (but invoke bindAsUser later
+ with the parent handle).
+
+ */
+
+ UserHandle childManagedProfileUser = findChildManagedProfileUser(
+ userHandle, um);
+ boolean isUserKeyPresent = mNonUIInCallServiceConnections.containsKey(
+ userHandle);
+ boolean isChildUserKeyPresent = (childManagedProfileUser == null) ? false
+ : mNonUIInCallServiceConnections.containsKey(
+ childManagedProfileUser);
+ List<InCallServiceBindingConnection> componentsToBindForUser = null;
+ List<InCallServiceBindingConnection> componentsToBindForChild = null;
+
+ if(isUserKeyPresent) {
+ componentsToBindForUser =
+ getNonUiInCallServiceBindingConnectionList(intent,
+ userHandle, null);
+ }
+ if (isChildUserKeyPresent) {
+ componentsToBindForChild =
+ getNonUiInCallServiceBindingConnectionList(intent,
+ childManagedProfileUser, userHandle);
}
+ Log.i(this,
+ "isUserKeyPresent:%b isChildKeyPresent:%b isManagedProfile:%b "
+ + "user:%d",
+ isUserKeyPresent, isChildUserKeyPresent, isManagedProfile,
+ userHandle.getIdentifier());
+
+ if (isUserKeyPresent && componentsToBindForUser != null) {
+ mNonUIInCallServiceConnections.get(userHandle).
+ addConnections(componentsToBindForUser);
+ }
+ if (isChildUserKeyPresent && componentsToBindForChild != null) {
+ mNonUIInCallServiceConnections.get(childManagedProfileUser).
+ addConnections(componentsToBindForChild);
+ }
// If the current car mode app become enabled from disabled, update
// the connection to binding
updateCarModeForConnections();
@@ -1691,6 +1827,14 @@
@VisibleForTesting
public void bindToServices(Call call) {
UserHandle userFromCall = getUserFromCall(call);
+ UserHandle parentUser = null;
+ UserManager um = mContext.getSystemService(UserManager.class);
+
+ if (um.isManagedProfile(userFromCall.getIdentifier())) {
+ parentUser = um.getProfileParent(userFromCall);
+ Log.i(this, "child:%s parent:%s", userFromCall, parentUser);
+ }
+
if (!mInCallServiceConnections.containsKey(userFromCall)) {
InCallServiceConnection dialerInCall = null;
InCallServiceInfo defaultDialerComponentInfo = getDefaultDialerComponent(userFromCall);
@@ -1710,10 +1854,24 @@
InCallServiceConnection carModeInCall = null;
InCallServiceInfo carModeComponentInfo = getCurrentCarModeComponent(userFromCall);
+ InCallServiceInfo carModeComponentInfoForParentUser = null;
+ if(parentUser != null) {
+ //query using parent user too
+ carModeComponentInfoForParentUser = getCurrentCarModeComponent(
+ parentUser);
+ }
+
if (carModeComponentInfo != null &&
!carModeComponentInfo.getComponentName().equals(
mDefaultDialerCache.getSystemDialerComponent())) {
carModeInCall = new InCallServiceBindingConnection(carModeComponentInfo);
+ } else if (carModeComponentInfo == null &&
+ carModeComponentInfoForParentUser != null &&
+ !carModeComponentInfoForParentUser.getComponentName().equals(
+ mDefaultDialerCache.getSystemDialerComponent())) {
+ carModeInCall = new InCallServiceBindingConnection(
+ carModeComponentInfoForParentUser, parentUser);
+ Log.i(this, "Using car mode component queried using parent handle");
}
mInCallServiceConnections.put(userFromCall,
@@ -1747,12 +1905,43 @@
private void updateNonUiInCallServices(Call call) {
UserHandle userFromCall = getUserFromCall(call);
+ UserHandle parentUser = null;
+
+ UserManager um = mContext.getSystemService(UserManager.class);
+ if(um.isManagedProfile(userFromCall.getIdentifier()))
+ {
+ parentUser = um.getProfileParent(userFromCall);
+ }
+
List<InCallServiceInfo> nonUIInCallComponents =
getInCallServiceComponents(userFromCall, IN_CALL_SERVICE_TYPE_NON_UI);
+ List<InCallServiceInfo> nonUIInCallComponentsForParent = new ArrayList<>();
+ if(parentUser != null)
+ {
+ //also get Non-UI services using parent handle.
+ nonUIInCallComponentsForParent =
+ getInCallServiceComponents(parentUser, IN_CALL_SERVICE_TYPE_NON_UI);
+
+ }
List<InCallServiceBindingConnection> nonUIInCalls = new LinkedList<>();
for (InCallServiceInfo serviceInfo : nonUIInCallComponents) {
nonUIInCalls.add(new InCallServiceBindingConnection(serviceInfo));
}
+
+ //add nonUI InCall services queried using parent user (if any)
+ for (InCallServiceInfo serviceInfo : nonUIInCallComponentsForParent) {
+ if (nonUIInCallComponents.contains(serviceInfo)) {
+ //skip dups
+ Log.i(this, "skipped duplicate component found using parent user: "
+ + serviceInfo.getComponentName());
+ } else {
+ nonUIInCalls.add(new InCallServiceBindingConnection(serviceInfo, parentUser));
+ Log.i(this,
+ "added component queried using parent user: "
+ + serviceInfo.getComponentName());
+ }
+ }
+
List<String> callCompanionApps = mCallsManager
.getRoleManagerAdapter().getCallCompanionApps();
if (callCompanionApps != null && !callCompanionApps.isEmpty()) {
@@ -1819,7 +2008,7 @@
// Last Resort: Try to bind to the ComponentName given directly.
Log.e(this, new Exception(), "Package Manager could not find ComponentName: "
+ componentName + ". Trying to bind anyway.");
- return new InCallServiceInfo(componentName, false, false, type);
+ return new InCallServiceInfo(componentName, false, false, type, false);
}
}
@@ -1854,6 +2043,34 @@
return getInCallServiceComponents(userHandle, packageName,
componentName, requestedType, true /* ignoreDisabled */);
}
+ private boolean canInteractAcrossUsersOrProfiles(ServiceInfo serviceInfo,
+ PackageManager packageManager) {
+ String op = AppOpsManager.permissionToOp("android.permission.INTERACT_ACROSS_PROFILES");
+ String[] uidPackages = packageManager.getPackagesForUid(serviceInfo.applicationInfo.uid);
+
+ boolean hasInteractAcrossProfiles = Arrays.stream(uidPackages).anyMatch(
+ p -> ((packageManager.checkPermission(
+ Manifest.permission.INTERACT_ACROSS_PROFILES,
+ p) == PackageManager.PERMISSION_GRANTED)
+ ));
+ boolean hasInteractAcrossUsers = Arrays.stream(uidPackages).anyMatch(
+ p -> ((packageManager.checkPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS,
+ p) == PackageManager.PERMISSION_GRANTED)
+ ));
+ boolean hasInteractAcrossProfilesAppOp = Arrays.stream(uidPackages).anyMatch(
+ p -> (AppOpsManager.MODE_ALLOWED == mAppOpsManager.checkOpNoThrow(
+ op, serviceInfo.applicationInfo.uid, p))
+ );
+ Log.i(this,
+ "packageName:%s INTERACT_ACROSS_USERS:%b INTERACT_ACROSS_PROFILES:%b "
+ + "INTERACT_ACROSS_PROFILES_APPOP:%b",
+ uidPackages[0], hasInteractAcrossUsers, hasInteractAcrossProfiles,
+ hasInteractAcrossProfilesAppOp);
+
+ return (hasInteractAcrossUsers || hasInteractAcrossProfiles
+ || hasInteractAcrossProfilesAppOp);
+ }
private List<InCallServiceInfo> getInCallServiceComponents(UserHandle userHandle,
String packageName, ComponentName componentName,
@@ -1867,11 +2084,16 @@
if (componentName != null) {
serviceIntent.setComponent(componentName);
}
+ Log.i(this,
+ "getComponents, pkgname: " + packageName + " comp: " + componentName + " userid: "
+ + userHandle.getIdentifier() + " requestedType: " + requestedType);
PackageManager packageManager = mContext.getPackageManager();
Context userContext = mContext.createContextAsUser(userHandle,
0 /* flags */);
PackageManager userPackageManager = userContext != null ?
userContext.getPackageManager() : packageManager;
+
+
for (ResolveInfo entry : packageManager.queryIntentServicesAsUser(
serviceIntent,
PackageManager.GET_META_DATA | PackageManager.MATCH_DISABLED_COMPONENTS,
@@ -1888,6 +2110,10 @@
int currentType = getInCallServiceType(userHandle,
entry.serviceInfo, packageManager, packageName);
+
+ boolean hasInteractAcrossUserOrProfilePerm = canInteractAcrossUsersOrProfiles(
+ entry.serviceInfo, packageManager);
+
ComponentName foundComponentName =
new ComponentName(serviceInfo.packageName, serviceInfo.name);
if (requestedType == IN_CALL_SERVICE_TYPE_NON_UI) {
@@ -1903,9 +2129,16 @@
isRequestedType = requestedType == currentType;
}
+ Log.i(this,
+ "found:%s isRequestedtype:%b isEnabled:%b ignoreDisabled:%b "
+ + "hasCrossProfilePerm:%b",
+ foundComponentName, isRequestedType, isEnabled, ignoreDisabled,
+ hasInteractAcrossUserOrProfilePerm);
+
if ((!ignoreDisabled || isEnabled) && isRequestedType) {
retval.add(new InCallServiceInfo(foundComponentName, isExternalCallsSupported,
- isSelfManageCallsSupported, requestedType));
+ isSelfManageCallsSupported, requestedType,
+ hasInteractAcrossUserOrProfilePerm));
}
}
}
@@ -2439,19 +2672,48 @@
Log.i(this, "updateCarModeForConnections: car mode apps: %s",
mCarModeTracker.getCarModeApps().stream().collect(Collectors.joining(", ")));
- if (mInCallServiceConnections.containsKey(mCallsManager.getCurrentUserHandle())) {
- CarSwappingInCallServiceConnection inCallServiceConnection = mInCallServiceConnections.
- get(mCallsManager.getCurrentUserHandle());
- if (shouldUseCarModeUI()) {
- Log.i(this, "updateCarModeForConnections: potentially update car mode app.");
- inCallServiceConnection.changeCarModeApp(mCarModeTracker.getCurrentCarModePackage(),
- mCallsManager.getCurrentUserHandle());
- } else {
- if (inCallServiceConnection.isCarMode()) {
- Log.i(this, "updateCarModeForConnections: car mode no longer "
- + "applicable; disabling");
- inCallServiceConnection.disableCarMode();
- }
+ UserManager um = mContext.getSystemService(UserManager.class);
+ UserHandle currentUser = mCallsManager.getCurrentUserHandle();
+ UserHandle childUser = findChildManagedProfileUser(currentUser, um);
+
+ CarSwappingInCallServiceConnection inCallServiceConnectionForCurrentUser = null;
+ CarSwappingInCallServiceConnection inCallServiceConnectionForChildUser = null;
+
+ Log.i(this, "update carmode current:%s parent:%s", currentUser, childUser);
+ if (mInCallServiceConnections.containsKey(currentUser)) {
+ inCallServiceConnectionForCurrentUser = mInCallServiceConnections.
+ get(currentUser);
+ }
+ if (childUser != null && mInCallServiceConnections.containsKey(childUser)) {
+ inCallServiceConnectionForChildUser = mInCallServiceConnections.
+ get(childUser);
+ }
+
+ if (shouldUseCarModeUI()) {
+ Log.i(this, "updateCarModeForConnections: potentially update car mode app.");
+ //always pass current user to changeCarMode. That will ultimately be used for bindAsUser
+ if (inCallServiceConnectionForCurrentUser != null) {
+ inCallServiceConnectionForCurrentUser.changeCarModeApp(
+ mCarModeTracker.getCurrentCarModePackage(),
+ currentUser);
+ }
+ if (inCallServiceConnectionForChildUser != null) {
+ inCallServiceConnectionForChildUser.changeCarModeApp(
+ mCarModeTracker.getCurrentCarModePackage(),
+ currentUser);
+ }
+ } else {
+ if (inCallServiceConnectionForCurrentUser != null
+ && inCallServiceConnectionForCurrentUser.isCarMode()) {
+ Log.i(this, "updateCarModeForConnections: car mode no longer "
+ + "applicable for current user; disabling");
+ inCallServiceConnectionForCurrentUser.disableCarMode();
+ }
+ if (inCallServiceConnectionForChildUser != null
+ && inCallServiceConnectionForChildUser.isCarMode()) {
+ Log.i(this, "updateCarModeForConnections: car mode no longer "
+ + "applicable for child user; disabling");
+ inCallServiceConnectionForChildUser.disableCarMode();
}
}
}
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index 4c8d8e2..d87ce7b 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -27,6 +27,7 @@
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyObject;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.matches;
import static org.mockito.ArgumentMatchers.nullable;
@@ -154,6 +155,7 @@
@Mock private AnomalyReporterAdapter mAnomalyReporterAdapter;
@Mock UserManager mMockUserManager;
@Mock UserInfo mMockUserInfo;
+ @Mock UserInfo mMockChildUserInfo; //work profile
@Rule
public TestRule compatChangeRule = new PlatformCompatChangeRule();
@@ -198,6 +200,10 @@
| Context.BIND_FOREGROUND_SERVICE | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS
| Context.BIND_SCHEDULE_LIKE_TOP_APP;
+ private UserHandle mChildUserHandle = UserHandle.of(10);
+ private @Mock Call mMockChildUserCall;
+ private UserHandle mParentUserHandle = UserHandle.of(1);
+
@Override
@Before
public void setUp() throws Exception {
@@ -296,6 +302,8 @@
when(mMockCallsManager.getAudioState()).thenReturn(new CallAudioState(false, 0, 0));
when(mMockContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mMockUserManager);
+ when(mMockContext.getSystemService(eq(UserManager.class)))
+ .thenReturn(mMockUserManager);
// Mock user info to allow binding on user stored in the phone account (mUserHandle).
when(mMockUserManager.getUserInfo(anyInt())).thenReturn(mMockUserInfo);
when(mMockUserInfo.isManagedProfile()).thenReturn(true);
@@ -1736,6 +1744,69 @@
eq(UserHandle.CURRENT));
}
+ private void setupMocksForWorkProfileTest() {
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
+ when(mMockChildUserCall.isIncoming()).thenReturn(false);
+ when(mMockChildUserCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
+ when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID)).thenReturn(DEF_PKG);
+ when(mMockContext.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class),
+ anyInt(), any())).thenReturn(true);
+ when(mMockChildUserCall.isExternalCall()).thenReturn(false);
+ when(mMockChildUserCall.isSelfManaged()).thenReturn(true);
+ when(mMockChildUserCall.visibleToInCallService()).thenReturn(true);
+
+ //Setup up parent and child/work profile relation
+ when(mMockUserInfo.getUserHandle()).thenReturn(mParentUserHandle);
+ when(mMockChildUserInfo.getUserHandle()).thenReturn(mChildUserHandle);
+ when(mMockUserInfo.isManagedProfile()).thenReturn(false);
+ when(mMockChildUserInfo.isManagedProfile()).thenReturn(true);
+ when(mMockChildUserCall.getUserHandleFromTargetPhoneAccount()).thenReturn(mChildUserHandle);
+ when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mChildUserHandle);
+ when(mMockUserManager.getProfileParent(mChildUserHandle.getIdentifier())).thenReturn(
+ mMockUserInfo);
+ when(mMockUserManager.getProfileParent(mChildUserHandle)).thenReturn(mParentUserHandle);
+ when(mMockUserManager.getUserInfo(eq(mParentUserHandle.getIdentifier()))).thenReturn(
+ mMockUserInfo);
+ when(mMockUserManager.getUserInfo(eq(mChildUserHandle.getIdentifier()))).thenReturn(
+ mMockChildUserInfo);
+ when(mMockUserManager.isManagedProfile(mChildUserHandle.getIdentifier())).thenReturn(true);
+ when(mMockUserManager.isManagedProfile(mParentUserHandle.getIdentifier())).thenReturn(
+ false);
+ }
+
+ @Test
+ public void testManagedProfileCallQueriesIcsUsingParentUserToo() throws Exception {
+ setupMocksForWorkProfileTest();
+ setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
+ setupMockPackageManager(true /* default */,
+ true /*useNonUiInCalls*/, true /*useAppOpNonUiInCalls*/,
+ true /*useSystemDialer*/, false /*includeExternalCalls*/,
+ true /*includeSelfManagedCallsInDefaultDialer*/,
+ true /*includeSelfManagedCallsInCarModeDialer*/,
+ true /*includeSelfManagedCallsInNonUi*/);
+
+ //pass in call by child/work-profileuser
+ mInCallController.bindToServices(mMockChildUserCall);
+
+ // Verify that queryIntentServicesAsUser is also called with parent handle
+ // Query for the different InCallServices
+ ArgumentCaptor<Integer> userIdCaptor = ArgumentCaptor.forClass(Integer.class);
+ ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+ ArgumentCaptor<Integer> flagCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMockPackageManager, times(6)).queryIntentServicesAsUser(
+ queryIntentCaptor.capture(), flagCaptor.capture(), userIdCaptor.capture());
+ List<Integer> userIds = userIdCaptor.getAllValues();
+
+ //check if queryIntentServices was called with child user handle
+ assertTrue("no query parent user handle",
+ userIds.contains(mChildUserHandle.getIdentifier()));
+ //check if queryIntentServices was also called with parent user handle
+ assertTrue("no query parent user handle",
+ userIds.contains(mParentUserHandle.getIdentifier()));
+
+ }
+
private void setupMocks(boolean isExternalCall) {
setupMocks(isExternalCall, false /* isSelfManagedCall */);
}