Binds to 3rd-party InCallService with MANAGE_ONGOING_CALL permission

Test: atest InCallControllerTests;
      atest ThirdPartyInCallServiceAppOpsPermissionTest;
Bug: 169595473
Change-Id: I28ada2b72d3955dfd500d34317b24dd7385fbc2b
This commit is contained in:
Shuo Qian 2020-10-02 11:39:22 -07:00
parent 8a66b49bd6
commit 4cc5a541d2
3 changed files with 105 additions and 8 deletions

View File

@ -28,6 +28,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.PermissionChecker;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@ -1524,7 +1525,7 @@ public class InCallController extends CallsManagerListenerBase {
if (requestedType == IN_CALL_SERVICE_TYPE_NON_UI) {
mKnownNonUiInCallServices.add(foundComponentName);
}
boolean isEnabled = isServiceEnabled(foundComponentName,
serviceInfo, packageManager);
if (isEnabled && (requestedType == 0 || requestedType == currentType)) {
@ -1594,9 +1595,17 @@ public class InCallController extends CallsManagerListenerBase {
p -> packageManager.checkPermission(
Manifest.permission.CONTROL_INCALL_EXPERIENCE,
p) == PackageManager.PERMISSION_GRANTED);
boolean hasAppOpsPermittedManageOngoingCalls = false;
if (isAppOpsPermittedManageOngoingCalls(serviceInfo.applicationInfo.uid,
serviceInfo.packageName)) {
hasAppOpsPermittedManageOngoingCalls = true;
}
boolean isCarModeUIService = serviceInfo.metaData != null &&
serviceInfo.metaData.getBoolean(
TelecomManager.METADATA_IN_CALL_SERVICE_CAR_MODE_UI, false);
if (isCarModeUIService && hasControlInCallPermission) {
return IN_CALL_SERVICE_TYPE_CAR_MODE_UI;
}
@ -1611,7 +1620,8 @@ public class InCallController extends CallsManagerListenerBase {
// Also allow any in-call service that has the control-experience permission (to ensure
// that it is a system app) and doesn't claim to show any UI.
if (!isUIService && !isCarModeUIService && hasControlInCallPermission) {
if (!isUIService && !isCarModeUIService && (hasControlInCallPermission ||
hasAppOpsPermittedManageOngoingCalls)) {
return IN_CALL_SERVICE_TYPE_NON_UI;
}
@ -2028,6 +2038,12 @@ public class InCallController extends CallsManagerListenerBase {
return mCallsManager.getAudioState().isMuted();
}
private boolean isAppOpsPermittedManageOngoingCalls(int uid, String callingPackage) {
return PermissionChecker.checkPermissionForPreflight(mContext,
Manifest.permission.MANAGE_ONGOING_CALLS, PermissionChecker.PID_UNKNOWN, uid,
callingPackage) == PermissionChecker.PERMISSION_GRANTED;
}
private void sendCrashedInCallServiceNotification(String packageName) {
PackageManager packageManager = mContext.getPackageManager();
CharSequence appName;

View File

@ -42,6 +42,7 @@ import android.content.ServiceConnection;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PermissionInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Configuration;
@ -485,6 +486,7 @@ public class ComponentContextFixture implements TestFixture<Context> {
private final RoleManager mRoleManager = mock(RoleManager.class);
private final TelephonyRegistryManager mTelephonyRegistryManager =
mock(TelephonyRegistryManager.class);
private final PermissionInfo mPermissionInfo = mock(PermissionInfo.class);
private TelecomManager mTelecomManager = mock(TelecomManager.class);
@ -539,6 +541,14 @@ public class ComponentContextFixture implements TestFixture<Context> {
matches(Manifest.permission.CALL_COMPANION_APP), anyString()))
.thenReturn(PackageManager.PERMISSION_DENIED);
try {
when(mPackageManager.getPermissionInfo(anyString(), anyInt())).thenReturn(
mPermissionInfo);
} catch (PackageManager.NameNotFoundException ex) {
}
when(mPermissionInfo.isAppOp()).thenReturn(true);
// Used in CreateConnectionProcessor to rank emergency numbers by viability.
// For the test, make them all equal to INVALID so that the preferred PhoneAccount will be
// chosen.

View File

@ -52,6 +52,7 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PermissionInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
@ -122,6 +123,7 @@ public class InCallControllerTests extends TelecomTestCase {
@Mock ClockProxy mClockProxy;
@Mock Analytics.CallInfoImpl mCallInfo;
@Mock NotificationManager mNotificationManager;
@Mock PermissionInfo mMockPermissionInfo;
private static final int CURRENT_USER_ID = 900973;
private static final String DEF_PKG = "defpkg";
@ -142,6 +144,9 @@ public class InCallControllerTests extends TelecomTestCase {
private static final String NONUI_PKG = "nonui_pkg";
private static final String NONUI_CLASS = "nonui_cls";
private static final int NONUI_UID = 6;
private static final String APPOP_NONUI_PKG = "appop_nonui_pkg";
private static final String APPOP_NONUI_CLASS = "appop_nonui_cls";
private static final int APPOP_NONUI_UID = 7;
private static final PhoneAccountHandle PA_HANDLE =
new PhoneAccountHandle(new ComponentName("pa_pkg", "pa_cls"), "pa_id");
@ -173,6 +178,8 @@ public class InCallControllerTests extends TelecomTestCase {
when(mMockCallsManager.getRoleManagerAdapter()).thenReturn(mMockRoleManagerAdapter);
when(mMockContext.getSystemService(eq(Context.NOTIFICATION_SERVICE)))
.thenReturn(mNotificationManager);
when(mMockPackageManager.getPermissionInfo(anyString(), anyInt())).thenReturn(
mMockPermissionInfo);
mInCallController = new InCallController(mMockContext, mLock, mMockCallsManager,
mMockSystemStateHelper, mDefaultDialerCache, mTimeoutsAdapter,
mEmergencyCallHelper, mCarModeTracker, mClockProxy);
@ -198,6 +205,8 @@ public class InCallControllerTests extends TelecomTestCase {
return new String[] { CAR2_PKG };
case NONUI_UID:
return new String[] { NONUI_PKG };
case APPOP_NONUI_UID:
return new String[] { APPOP_NONUI_PKG };
}
return null;
}).when(mMockPackageManager).getPackagesForUid(anyInt());
@ -213,6 +222,9 @@ public class InCallControllerTests extends TelecomTestCase {
when(mMockPackageManager.checkPermission(
matches(Manifest.permission.CONTROL_INCALL_EXPERIENCE),
matches(NONUI_PKG))).thenReturn(PackageManager.PERMISSION_GRANTED);
when(mMockPackageManager.checkPermission(
matches(Manifest.permission.CONTROL_INCALL_EXPERIENCE),
matches(APPOP_NONUI_PKG))).thenReturn(PackageManager.PERMISSION_DENIED);
when(mMockCallsManager.getAudioState()).thenReturn(new CallAudioState(false, 0, 0));
}
@ -822,6 +834,49 @@ public class InCallControllerTests extends TelecomTestCase {
verifyBinding(bindIntentCaptor, 0, DEF_PKG, DEF_CLASS);
}
/**
* Ensures that the {@link InCallController} will bind to an {@link InCallService} which
* supports third party app
*/
@MediumTest
@Test
public void testBindToService_ThirdPartyApp() throws Exception {
setupMocks(false /* isExternalCall */);
setupMockPackageManager(false /* default */, false /* nonui */, true /* appop_nonui */,
true /* system */, false /* external calls */, false /* self mgd in default */,
false /* self mgd in car*/);
// Enable Third Party Companion App
when(mMockPackageManager.getPermissionInfo(anyString(), anyInt())).thenReturn(
mMockPermissionInfo);
when(mMockPermissionInfo.isAppOp()).thenReturn(true);
when(mMockAppOpsManager.unsafeCheckOpRawNoThrow(matches(
AppOpsManager.OPSTR_MANAGE_ONGOING_CALLS), eq(APPOP_NONUI_UID),
matches(APPOP_NONUI_PKG))).thenReturn(AppOpsManager.MODE_ALLOWED);
// Now bind; we should bind to the system dialer and app op non ui app.
mInCallController.bindToServices(mMockCall);
// Bind InCallServices
ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(mMockContext, times(2)).bindServiceAsUser(
bindIntentCaptor.capture(),
any(ServiceConnection.class),
eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
| Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
eq(UserHandle.CURRENT));
// Verify bind
assertEquals(2, bindIntentCaptor.getAllValues().size());
// Should have first bound to the system dialer.
verifyBinding(bindIntentCaptor, 0, SYS_PKG, SYS_CLASS);
// Should have next bound to the third party app op non ui app.
verifyBinding(bindIntentCaptor, 1, APPOP_NONUI_PKG, APPOP_NONUI_CLASS);
}
@MediumTest
@Test
public void testSanitizeContactName() throws Exception {
@ -934,8 +989,8 @@ public class InCallControllerTests extends TelecomTestCase {
nullable(ContentResolver.class))).thenReturn(500L);
when(mMockCallsManager.getCalls()).thenReturn(Collections.singletonList(mMockCall));
setupMockPackageManager(true /* default */, true /* nonui */, true /* system */,
false /* external calls */,
setupMockPackageManager(true /* default */, true /* nonui */, false /* appop_nonui */ ,
true /* system */, false /* external calls */,
false /* self mgd in default*/, false /* self mgd in car*/);
mInCallController.bindToServices(mMockCall);
@ -1195,9 +1250,21 @@ public class InCallControllerTests extends TelecomTestCase {
}};
}
private ResolveInfo getAppOpNonUiResolveinfo() {
return new ResolveInfo() {{
serviceInfo = new ServiceInfo();
serviceInfo.packageName = APPOP_NONUI_PKG;
serviceInfo.name = APPOP_NONUI_CLASS;
serviceInfo.applicationInfo = new ApplicationInfo();
serviceInfo.applicationInfo.uid = APPOP_NONUI_UID;
serviceInfo.enabled = true;
serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE;
}};
}
private void setupMockPackageManager(final boolean useDefaultDialer,
final boolean useSystemDialer, final boolean includeExternalCalls) {
setupMockPackageManager(useDefaultDialer, false, useSystemDialer, includeExternalCalls,
setupMockPackageManager(useDefaultDialer, false, false, useSystemDialer, includeExternalCalls,
false /* self mgd */, false /* self mgd */);
}
@ -1205,13 +1272,13 @@ public class InCallControllerTests extends TelecomTestCase {
final boolean useSystemDialer, final boolean includeExternalCalls,
final boolean includeSelfManagedCallsInDefaultDialer,
final boolean includeSelfManagedCallsInCarModeDialer) {
setupMockPackageManager(useDefaultDialer, false /* nonui */, useSystemDialer,
includeExternalCalls, includeSelfManagedCallsInDefaultDialer,
setupMockPackageManager(useDefaultDialer, false /* nonui */, false /* appop_nonui */,
useSystemDialer, includeExternalCalls, includeSelfManagedCallsInDefaultDialer,
includeSelfManagedCallsInCarModeDialer);
}
private void setupMockPackageManager(final boolean useDefaultDialer,
final boolean useNonUiInCalls,
final boolean useNonUiInCalls, final boolean useAppOpNonUiInCalls,
final boolean useSystemDialer, final boolean includeExternalCalls,
final boolean includeSelfManagedCallsInDefaultDialer,
final boolean includeSelfManagedCallsInCarModeDialer) {
@ -1254,6 +1321,10 @@ public class InCallControllerTests extends TelecomTestCase {
if (useNonUiInCalls) {
resolveInfo.add(getNonUiResolveinfo());
}
// InCallController uses a blank package name when querying for App Op non-ui incalls
if (useAppOpNonUiInCalls) {
resolveInfo.add(getAppOpNonUiResolveinfo());
}
}
return resolveInfo;