Merge "Binds to 3rd-party InCallService with MANAGE_ONGOING_CALL permission" into rvc-qpr-dev-plus-aosp
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 9a1c876..954aa44 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -28,6 +28,7 @@
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;
@@ -1576,9 +1577,17 @@
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;
}
@@ -1593,7 +1602,8 @@
// 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;
}
@@ -2010,6 +2020,12 @@
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;
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index af062d7..a4302b6 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -42,6 +42,7 @@
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 @@
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 @@
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.
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index 693859b..6a6b9f3 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -52,6 +52,7 @@
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 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}};
}
+ 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 @@
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 @@
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;