Loading src/com/android/server/telecom/InCallController.java +33 −8 Original line number Original line Diff line number Diff line Loading @@ -34,7 +34,6 @@ import android.os.Trace; import android.os.UserHandle; import android.os.UserHandle; import android.telecom.CallAudioState; import android.telecom.CallAudioState; import android.telecom.ConnectionService; import android.telecom.ConnectionService; import android.telecom.DefaultDialerManager; import android.telecom.InCallService; import android.telecom.InCallService; import android.telecom.Log; import android.telecom.Log; import android.telecom.Logging.Runnable; import android.telecom.Logging.Runnable; Loading Loading @@ -717,6 +716,7 @@ public class InCallController extends CallsManagerListenerBase { private static final int IN_CALL_SERVICE_TYPE_SYSTEM_UI = 2; private static final int IN_CALL_SERVICE_TYPE_SYSTEM_UI = 2; private static final int IN_CALL_SERVICE_TYPE_CAR_MODE_UI = 3; private static final int IN_CALL_SERVICE_TYPE_CAR_MODE_UI = 3; private static final int IN_CALL_SERVICE_TYPE_NON_UI = 4; private static final int IN_CALL_SERVICE_TYPE_NON_UI = 4; private static final int IN_CALL_SERVICE_TYPE_COMPANION = 5; /** The in-call app implementations, see {@link IInCallService}. */ /** The in-call app implementations, see {@link IInCallService}. */ private final Map<InCallServiceInfo, IInCallService> mInCallServices = new ArrayMap<>(); private final Map<InCallServiceInfo, IInCallService> mInCallServices = new ArrayMap<>(); Loading Loading @@ -1146,6 +1146,17 @@ public class InCallController extends CallsManagerListenerBase { for (InCallServiceInfo serviceInfo : nonUIInCallComponents) { for (InCallServiceInfo serviceInfo : nonUIInCallComponents) { nonUIInCalls.add(new InCallServiceBindingConnection(serviceInfo)); nonUIInCalls.add(new InCallServiceBindingConnection(serviceInfo)); } } List<String> callCompanionApps = mCallsManager .getRoleManagerAdapter().getCallCompanionApps(); if (callCompanionApps != null && !callCompanionApps.isEmpty()) { for(String pkg : callCompanionApps) { InCallServiceInfo info = getInCallServiceComponent(pkg, IN_CALL_SERVICE_TYPE_COMPANION); if (info != null) { nonUIInCalls.add(new InCallServiceBindingConnection(info)); } } } mNonUIInCallServiceConnections = new NonUIInCallServiceConnectionCollection(nonUIInCalls); mNonUIInCallServiceConnections = new NonUIInCallServiceConnectionCollection(nonUIInCalls); mNonUIInCallServiceConnections.connect(call); mNonUIInCallServiceConnections.connect(call); } } Loading @@ -1159,9 +1170,10 @@ public class InCallController extends CallsManagerListenerBase { } } private InCallServiceInfo getCarModeComponent() { private InCallServiceInfo getCarModeComponent() { // Seems strange to cast a String to null, but the signatures of getInCallServiceComponent // The signatures of getInCallServiceComponent differ in the types of the first parameter, // differ in the types of the first parameter, and passing in null is inherently ambiguous. // and passing in null is inherently ambiguous. (If no car mode component found) return getInCallServiceComponent((String) null, IN_CALL_SERVICE_TYPE_CAR_MODE_UI); String defaultCarMode = mCallsManager.getRoleManagerAdapter().getCarModeDialerApp(); return getInCallServiceComponent(defaultCarMode, IN_CALL_SERVICE_TYPE_CAR_MODE_UI); } } private InCallServiceInfo getInCallServiceComponent(ComponentName componentName, int type) { private InCallServiceInfo getInCallServiceComponent(ComponentName componentName, int type) { Loading Loading @@ -1216,7 +1228,6 @@ public class InCallController extends CallsManagerListenerBase { PackageManager.GET_META_DATA, PackageManager.GET_META_DATA, mCallsManager.getCurrentUserHandle().getIdentifier())) { mCallsManager.getCurrentUserHandle().getIdentifier())) { ServiceInfo serviceInfo = entry.serviceInfo; ServiceInfo serviceInfo = entry.serviceInfo; if (serviceInfo != null) { if (serviceInfo != null) { boolean isExternalCallsSupported = serviceInfo.metaData != null && boolean isExternalCallsSupported = serviceInfo.metaData != null && serviceInfo.metaData.getBoolean( serviceInfo.metaData.getBoolean( Loading Loading @@ -1266,6 +1277,14 @@ public class InCallController extends CallsManagerListenerBase { return IN_CALL_SERVICE_TYPE_SYSTEM_UI; return IN_CALL_SERVICE_TYPE_SYSTEM_UI; } } // Check to see if the service holds permissions or metadata for third party apps. boolean hasInCallServiceUIMetadata = serviceInfo.metaData != null && serviceInfo.metaData.containsKey(TelecomManager.METADATA_IN_CALL_SERVICE_UI); boolean isThirdPartyCompanionApp = packageManager.checkPermission( Manifest.permission.CALL_COMPANION_APP, serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED && !hasInCallServiceUIMetadata; // Check to see if the service is a car-mode UI type by checking that it has the // Check to see if the service is a car-mode UI type by checking that it has the // CONTROL_INCALL_EXPERIENCE (to verify it is a system app) and that it has the // CONTROL_INCALL_EXPERIENCE (to verify it is a system app) and that it has the // car-mode UI metadata. // car-mode UI metadata. Loading @@ -1275,7 +1294,7 @@ public class InCallController extends CallsManagerListenerBase { boolean isCarModeUIService = serviceInfo.metaData != null && boolean isCarModeUIService = serviceInfo.metaData != null && serviceInfo.metaData.getBoolean( serviceInfo.metaData.getBoolean( TelecomManager.METADATA_IN_CALL_SERVICE_CAR_MODE_UI, false) && TelecomManager.METADATA_IN_CALL_SERVICE_CAR_MODE_UI, false) && hasControlInCallPermission; (hasControlInCallPermission || isThirdPartyCompanionApp); if (isCarModeUIService) { if (isCarModeUIService) { return IN_CALL_SERVICE_TYPE_CAR_MODE_UI; return IN_CALL_SERVICE_TYPE_CAR_MODE_UI; } } Loading @@ -1293,9 +1312,15 @@ public class InCallController extends CallsManagerListenerBase { // Also allow any in-call service that has the control-experience permission (to ensure // 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. // that it is a system app) and doesn't claim to show any UI. if (hasControlInCallPermission && !isUIService) { if (!isUIService) { if (hasControlInCallPermission && !isThirdPartyCompanionApp) { return IN_CALL_SERVICE_TYPE_NON_UI; return IN_CALL_SERVICE_TYPE_NON_UI; } } // Third party companion alls without CONTROL_INCALL_EXPERIENCE permission. if (!hasControlInCallPermission && isThirdPartyCompanionApp) { return IN_CALL_SERVICE_TYPE_COMPANION; } } // Anything else that remains, we will not bind to. // Anything else that remains, we will not bind to. Log.i(this, "Skipping binding to %s:%s, control: %b, car-mode: %b, ui: %b", Log.i(this, "Skipping binding to %s:%s, control: %b, car-mode: %b, ui: %b", Loading src/com/android/server/telecom/RoleManagerAdapterImpl.java +3 −1 Original line number Original line Diff line number Diff line Loading @@ -73,7 +73,9 @@ public class RoleManagerAdapterImpl implements RoleManagerAdapter { @Override @Override public List<String> getCallCompanionApps() { public List<String> getCallCompanionApps() { List<String> callCompanionApps = getRoleManagerCallCompanionApps(); List<String> callCompanionApps = new ArrayList<>(); // List from RoleManager is not resizable. AbstractList.add action is not supported. callCompanionApps.addAll(getRoleManagerCallCompanionApps()); callCompanionApps.addAll(mOverrideCallCompanionApps); callCompanionApps.addAll(mOverrideCallCompanionApps); return callCompanionApps; return callCompanionApps; } } Loading tests/src/com/android/server/telecom/tests/ComponentContextFixture.java +12 −0 Original line number Original line Diff line number Diff line Loading @@ -26,9 +26,11 @@ import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer; import android.Manifest; import android.app.AppOpsManager; import android.app.AppOpsManager; import android.app.NotificationManager; import android.app.NotificationManager; import android.app.StatusBarManager; import android.app.StatusBarManager; // import android.app.role.RoleManager; import android.content.BroadcastReceiver; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ComponentName; import android.content.ContentResolver; import android.content.ContentResolver; Loading Loading @@ -71,6 +73,7 @@ import java.util.List; import java.util.Locale; import java.util.Locale; import java.util.Map; import java.util.Map; import static org.mockito.ArgumentMatchers.matches; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.any; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyInt; Loading Loading @@ -181,6 +184,9 @@ public class ComponentContextFixture implements TestFixture<Context> { return mCarrierConfigManager; return mCarrierConfigManager; case Context.COUNTRY_DETECTOR: case Context.COUNTRY_DETECTOR: return mCountryDetector; return mCountryDetector; // TODO: RoleManager not ready yet, uncomment when it's merged into aosp. // case Context.ROLE_SERVICE: // return mRoleManager; default: default: return null; return null; } } Loading Loading @@ -432,6 +438,7 @@ public class ComponentContextFixture implements TestFixture<Context> { private final Map<String, IContentProvider> mIContentProviderByUri = new HashMap<>(); private final Map<String, IContentProvider> mIContentProviderByUri = new HashMap<>(); private final Configuration mResourceConfiguration = new Configuration(); private final Configuration mResourceConfiguration = new Configuration(); private final ApplicationInfo mTestApplicationInfo = new ApplicationInfo(); private final ApplicationInfo mTestApplicationInfo = new ApplicationInfo(); // private final RoleManager mRoleManager = mock(RoleManager.class); private TelecomManager mTelecomManager = null; private TelecomManager mTelecomManager = null; Loading Loading @@ -463,6 +470,11 @@ public class ComponentContextFixture implements TestFixture<Context> { } } }).when(mPackageManager).queryIntentServicesAsUser((Intent) any(), anyInt(), anyInt()); }).when(mPackageManager).queryIntentServicesAsUser((Intent) any(), anyInt(), anyInt()); // By default, tests use non-ui apps instead of 3rd party companion apps. when(mPackageManager.checkPermission( matches(Manifest.permission.CALL_COMPANION_APP), anyString())) .thenReturn(PackageManager.PERMISSION_DENIED); when(mTelephonyManager.getSubIdForPhoneAccount((PhoneAccount) any())).thenReturn(1); when(mTelephonyManager.getSubIdForPhoneAccount((PhoneAccount) any())).thenReturn(1); when(mTelephonyManager.getNetworkOperatorName()).thenReturn("label1"); when(mTelephonyManager.getNetworkOperatorName()).thenReturn("label1"); Loading tests/src/com/android/server/telecom/tests/InCallControllerTests.java +150 −4 Original line number Original line Diff line number Diff line Loading @@ -50,6 +50,7 @@ import com.android.server.telecom.EmergencyCallHelper; import com.android.server.telecom.InCallController; import com.android.server.telecom.InCallController; import com.android.server.telecom.PhoneAccountRegistrar; import com.android.server.telecom.PhoneAccountRegistrar; import com.android.server.telecom.R; import com.android.server.telecom.R; import com.android.server.telecom.RoleManagerAdapter; import com.android.server.telecom.SystemStateHelper; import com.android.server.telecom.SystemStateHelper; import com.android.server.telecom.TelecomSystem; import com.android.server.telecom.TelecomSystem; import com.android.server.telecom.Timeouts; import com.android.server.telecom.Timeouts; Loading @@ -65,11 +66,15 @@ import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer; import java.util.ArrayList; import java.util.Collections; import java.util.Collections; import java.util.LinkedList; import java.util.LinkedList; import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.matches; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Matchers.any; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyInt; Loading @@ -95,12 +100,17 @@ public class InCallControllerTests extends TelecomTestCase { @Mock MockContext mMockContext; @Mock MockContext mMockContext; @Mock Timeouts.Adapter mTimeoutsAdapter; @Mock Timeouts.Adapter mTimeoutsAdapter; @Mock DefaultDialerCache mDefaultDialerCache; @Mock DefaultDialerCache mDefaultDialerCache; @Mock RoleManagerAdapter mMockRoleManagerAdapter; private static final int CURRENT_USER_ID = 900973; private static final int CURRENT_USER_ID = 900973; private static final String DEF_PKG = "defpkg"; private static final String DEF_PKG = "defpkg"; private static final String DEF_CLASS = "defcls"; private static final String DEF_CLASS = "defcls"; private static final String SYS_PKG = "syspkg"; private static final String SYS_PKG = "syspkg"; private static final String SYS_CLASS = "syscls"; private static final String SYS_CLASS = "syscls"; private static final String COMPANION_PKG = "cpnpkg"; private static final String COMPANION_CLASS = "cpncls"; private static final String CAR_PKG = "carpkg"; private static final String CAR_CLASS = "carcls"; private static final PhoneAccountHandle PA_HANDLE = private static final PhoneAccountHandle PA_HANDLE = new PhoneAccountHandle(new ComponentName("pa_pkg", "pa_cls"), "pa_id"); new PhoneAccountHandle(new ComponentName("pa_pkg", "pa_cls"), "pa_id"); Loading @@ -121,9 +131,17 @@ public class InCallControllerTests extends TelecomTestCase { doReturn(true).when(mMockResources).getBoolean(R.bool.grant_location_permission_enabled); doReturn(true).when(mMockResources).getBoolean(R.bool.grant_location_permission_enabled); mEmergencyCallHelper = new EmergencyCallHelper(mMockContext, SYS_PKG, mEmergencyCallHelper = new EmergencyCallHelper(mMockContext, SYS_PKG, mTimeoutsAdapter); mTimeoutsAdapter); when(mMockCallsManager.getRoleManagerAdapter()).thenReturn(mMockRoleManagerAdapter); mInCallController = new InCallController(mMockContext, mLock, mMockCallsManager, mInCallController = new InCallController(mMockContext, mLock, mMockCallsManager, mMockSystemStateHelper, mDefaultDialerCache, mTimeoutsAdapter, mMockSystemStateHelper, mDefaultDialerCache, mTimeoutsAdapter, mEmergencyCallHelper); mEmergencyCallHelper); // Companion Apps don't have CONTROL_INCALL_EXPERIENCE permission. when(mMockPackageManager.checkPermission( matches(Manifest.permission.CONTROL_INCALL_EXPERIENCE), matches(COMPANION_PKG))).thenReturn(PackageManager.PERMISSION_DENIED); when(mMockPackageManager.checkPermission( matches(Manifest.permission.CONTROL_INCALL_EXPERIENCE), matches(CAR_PKG))).thenReturn(PackageManager.PERMISSION_DENIED); } } @Override @Override Loading Loading @@ -493,6 +511,103 @@ public class InCallControllerTests extends TelecomTestCase { verify(mockInCallService).addCall(any(ParcelableCall.class)); verify(mockInCallService).addCall(any(ParcelableCall.class)); } } /** * Ensures that the {@link InCallController} will bind to an {@link InCallService} which * supports third party companion calls. */ @MediumTest public void doNotTestBindToService_Companion() throws Exception { setupMocks(true /* isExternalCall */); setupMockPackageManager(true /* default */, true /* system */, true /* external calls */); List<String> companionAppsList = new ArrayList<>(); companionAppsList.add(COMPANION_PKG); companionAppsList.add(COMPANION_PKG); when(mMockRoleManagerAdapter.getCallCompanionApps()).thenReturn(companionAppsList); mInCallController.bindToServices(mMockCall); // Query for the different InCallServices ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mMockPackageManager, times(6)).queryIntentServicesAsUser( queryIntentCaptor.capture(), eq(PackageManager.GET_META_DATA), eq(CURRENT_USER_ID)); // Verify call for default dialer InCallService assertEquals(DEF_PKG, queryIntentCaptor.getAllValues().get(0).getPackage()); // Verify call for system dialer InCallService assertEquals(null, queryIntentCaptor.getAllValues().get(1).getPackage()); // Verify call for car mode ui InCallServices assertEquals(null, queryIntentCaptor.getAllValues().get(2).getPackage()); // Verify call for non-UI InCallServices assertEquals(null, queryIntentCaptor.getAllValues().get(3).getPackage()); // Verify call for companion InCallServices assertEquals(COMPANION_PKG, queryIntentCaptor.getAllValues().get(4).getPackage()); assertEquals(COMPANION_PKG, queryIntentCaptor.getAllValues().get(5).getPackage()); // Bind InCallServices ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mMockContext, times(3)).bindServiceAsUser( bindIntentCaptor.capture(), any(ServiceConnection.class), eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE), eq(UserHandle.CURRENT)); // Verify bind dialer Intent bindIntent = bindIntentCaptor.getAllValues().get(0); assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction()); assertEquals(DEF_PKG, bindIntent.getComponent().getPackageName()); assertEquals(DEF_CLASS, bindIntent.getComponent().getClassName()); // Verify bind companion apps bindIntent = bindIntentCaptor.getAllValues().get(1); assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction()); assertEquals(COMPANION_PKG, bindIntent.getComponent().getPackageName()); assertEquals(COMPANION_CLASS, bindIntent.getComponent().getClassName()); bindIntent = bindIntentCaptor.getAllValues().get(2); assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction()); assertEquals(COMPANION_PKG, bindIntent.getComponent().getPackageName()); assertEquals(COMPANION_CLASS, bindIntent.getComponent().getClassName()); } /** * Ensures that the {@link InCallController} will bind to an {@link InCallService} which * supports third party car mode ui calls */ @MediumTest public void doNotTestBindToService_CarModeUI() throws Exception { setupMocks(true /* isExternalCall */); setupMockPackageManager(true /* default */, true /* system */, true /* external calls */); when(mMockRoleManagerAdapter.getCarModeDialerApp()).thenReturn(CAR_PKG); // Enable car mode when(mMockSystemStateHelper.isCarMode()).thenReturn(true); mInCallController.bindToServices(mMockCall); // Query for the different InCallServices ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mMockPackageManager, times(4)).queryIntentServicesAsUser( queryIntentCaptor.capture(), eq(PackageManager.GET_META_DATA), eq(CURRENT_USER_ID)); // Verify call for default dialer InCallService assertEquals(DEF_PKG, queryIntentCaptor.getAllValues().get(0).getPackage()); // Verify call for system dialer InCallService assertEquals(null, queryIntentCaptor.getAllValues().get(1).getPackage()); // Verify call for car mode ui InCallServices assertEquals(CAR_PKG, queryIntentCaptor.getAllValues().get(2).getPackage()); // Verify call for non-UI InCallServices assertEquals(null, queryIntentCaptor.getAllValues().get(3).getPackage()); // Bind InCallServices ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mMockContext, times(1)).bindServiceAsUser( bindIntentCaptor.capture(), any(ServiceConnection.class), eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE), eq(UserHandle.CURRENT)); // Verify bind car mode ui Intent bindIntent = bindIntentCaptor.getAllValues().get(0); assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction()); assertEquals(CAR_PKG, bindIntent.getComponent().getPackageName()); assertEquals(CAR_CLASS, bindIntent.getComponent().getClassName()); } private void setupMocks(boolean isExternalCall) { private void setupMocks(boolean isExternalCall) { when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle); when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle); when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); Loading Loading @@ -521,6 +636,22 @@ public class InCallControllerTests extends TelecomTestCase { }}; }}; } } private ResolveInfo getCarModeResolveinfo(final boolean includeExternalCalls) { return new ResolveInfo() {{ serviceInfo = new ServiceInfo(); serviceInfo.packageName = CAR_PKG; serviceInfo.name = CAR_CLASS; serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE; serviceInfo.metaData = new Bundle(); serviceInfo.metaData.putBoolean( TelecomManager.METADATA_IN_CALL_SERVICE_CAR_MODE_UI, true); if (includeExternalCalls) { serviceInfo.metaData.putBoolean( TelecomManager.METADATA_INCLUDE_EXTERNAL_CALLS, true); } }}; } private ResolveInfo getSysResolveinfo() { private ResolveInfo getSysResolveinfo() { return new ResolveInfo() {{ return new ResolveInfo() {{ serviceInfo = new ServiceInfo(); serviceInfo = new ServiceInfo(); Loading @@ -530,6 +661,15 @@ public class InCallControllerTests extends TelecomTestCase { }}; }}; } } private ResolveInfo getCompanionResolveinfo() { return new ResolveInfo() {{ serviceInfo = new ServiceInfo(); serviceInfo.packageName = COMPANION_PKG; serviceInfo.name = COMPANION_CLASS; serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE; }}; } private void setupMockPackageManager(final boolean useDefaultDialer, private void setupMockPackageManager(final boolean useDefaultDialer, final boolean useSystemDialer, final boolean includeExternalCalls) { final boolean useSystemDialer, final boolean includeExternalCalls) { Loading @@ -545,15 +685,21 @@ public class InCallControllerTests extends TelecomTestCase { } } LinkedList<ResolveInfo> resolveInfo = new LinkedList<ResolveInfo>(); LinkedList<ResolveInfo> resolveInfo = new LinkedList<ResolveInfo>(); if (!TextUtils.isEmpty(packageName)) { if (!TextUtils.isEmpty(packageName)) { if ((TextUtils.isEmpty(packageName) || packageName.equals(DEF_PKG)) && if (packageName.equals(DEF_PKG) && useDefaultDialer) { useDefaultDialer) { resolveInfo.add(getDefResolveInfo(includeExternalCalls)); resolveInfo.add(getDefResolveInfo(includeExternalCalls)); } } if ((TextUtils.isEmpty(packageName) || packageName.equals(SYS_PKG)) && if (packageName.equals(SYS_PKG) && useSystemDialer) { useSystemDialer) { resolveInfo.add(getSysResolveinfo()); resolveInfo.add(getSysResolveinfo()); } } if (packageName.equals(COMPANION_PKG)) { resolveInfo.add(getCompanionResolveinfo()); } if (packageName.equals(CAR_PKG)) { resolveInfo.add(getCarModeResolveinfo(includeExternalCalls)); } } } return resolveInfo; return resolveInfo; } } Loading Loading
src/com/android/server/telecom/InCallController.java +33 −8 Original line number Original line Diff line number Diff line Loading @@ -34,7 +34,6 @@ import android.os.Trace; import android.os.UserHandle; import android.os.UserHandle; import android.telecom.CallAudioState; import android.telecom.CallAudioState; import android.telecom.ConnectionService; import android.telecom.ConnectionService; import android.telecom.DefaultDialerManager; import android.telecom.InCallService; import android.telecom.InCallService; import android.telecom.Log; import android.telecom.Log; import android.telecom.Logging.Runnable; import android.telecom.Logging.Runnable; Loading Loading @@ -717,6 +716,7 @@ public class InCallController extends CallsManagerListenerBase { private static final int IN_CALL_SERVICE_TYPE_SYSTEM_UI = 2; private static final int IN_CALL_SERVICE_TYPE_SYSTEM_UI = 2; private static final int IN_CALL_SERVICE_TYPE_CAR_MODE_UI = 3; private static final int IN_CALL_SERVICE_TYPE_CAR_MODE_UI = 3; private static final int IN_CALL_SERVICE_TYPE_NON_UI = 4; private static final int IN_CALL_SERVICE_TYPE_NON_UI = 4; private static final int IN_CALL_SERVICE_TYPE_COMPANION = 5; /** The in-call app implementations, see {@link IInCallService}. */ /** The in-call app implementations, see {@link IInCallService}. */ private final Map<InCallServiceInfo, IInCallService> mInCallServices = new ArrayMap<>(); private final Map<InCallServiceInfo, IInCallService> mInCallServices = new ArrayMap<>(); Loading Loading @@ -1146,6 +1146,17 @@ public class InCallController extends CallsManagerListenerBase { for (InCallServiceInfo serviceInfo : nonUIInCallComponents) { for (InCallServiceInfo serviceInfo : nonUIInCallComponents) { nonUIInCalls.add(new InCallServiceBindingConnection(serviceInfo)); nonUIInCalls.add(new InCallServiceBindingConnection(serviceInfo)); } } List<String> callCompanionApps = mCallsManager .getRoleManagerAdapter().getCallCompanionApps(); if (callCompanionApps != null && !callCompanionApps.isEmpty()) { for(String pkg : callCompanionApps) { InCallServiceInfo info = getInCallServiceComponent(pkg, IN_CALL_SERVICE_TYPE_COMPANION); if (info != null) { nonUIInCalls.add(new InCallServiceBindingConnection(info)); } } } mNonUIInCallServiceConnections = new NonUIInCallServiceConnectionCollection(nonUIInCalls); mNonUIInCallServiceConnections = new NonUIInCallServiceConnectionCollection(nonUIInCalls); mNonUIInCallServiceConnections.connect(call); mNonUIInCallServiceConnections.connect(call); } } Loading @@ -1159,9 +1170,10 @@ public class InCallController extends CallsManagerListenerBase { } } private InCallServiceInfo getCarModeComponent() { private InCallServiceInfo getCarModeComponent() { // Seems strange to cast a String to null, but the signatures of getInCallServiceComponent // The signatures of getInCallServiceComponent differ in the types of the first parameter, // differ in the types of the first parameter, and passing in null is inherently ambiguous. // and passing in null is inherently ambiguous. (If no car mode component found) return getInCallServiceComponent((String) null, IN_CALL_SERVICE_TYPE_CAR_MODE_UI); String defaultCarMode = mCallsManager.getRoleManagerAdapter().getCarModeDialerApp(); return getInCallServiceComponent(defaultCarMode, IN_CALL_SERVICE_TYPE_CAR_MODE_UI); } } private InCallServiceInfo getInCallServiceComponent(ComponentName componentName, int type) { private InCallServiceInfo getInCallServiceComponent(ComponentName componentName, int type) { Loading Loading @@ -1216,7 +1228,6 @@ public class InCallController extends CallsManagerListenerBase { PackageManager.GET_META_DATA, PackageManager.GET_META_DATA, mCallsManager.getCurrentUserHandle().getIdentifier())) { mCallsManager.getCurrentUserHandle().getIdentifier())) { ServiceInfo serviceInfo = entry.serviceInfo; ServiceInfo serviceInfo = entry.serviceInfo; if (serviceInfo != null) { if (serviceInfo != null) { boolean isExternalCallsSupported = serviceInfo.metaData != null && boolean isExternalCallsSupported = serviceInfo.metaData != null && serviceInfo.metaData.getBoolean( serviceInfo.metaData.getBoolean( Loading Loading @@ -1266,6 +1277,14 @@ public class InCallController extends CallsManagerListenerBase { return IN_CALL_SERVICE_TYPE_SYSTEM_UI; return IN_CALL_SERVICE_TYPE_SYSTEM_UI; } } // Check to see if the service holds permissions or metadata for third party apps. boolean hasInCallServiceUIMetadata = serviceInfo.metaData != null && serviceInfo.metaData.containsKey(TelecomManager.METADATA_IN_CALL_SERVICE_UI); boolean isThirdPartyCompanionApp = packageManager.checkPermission( Manifest.permission.CALL_COMPANION_APP, serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED && !hasInCallServiceUIMetadata; // Check to see if the service is a car-mode UI type by checking that it has the // Check to see if the service is a car-mode UI type by checking that it has the // CONTROL_INCALL_EXPERIENCE (to verify it is a system app) and that it has the // CONTROL_INCALL_EXPERIENCE (to verify it is a system app) and that it has the // car-mode UI metadata. // car-mode UI metadata. Loading @@ -1275,7 +1294,7 @@ public class InCallController extends CallsManagerListenerBase { boolean isCarModeUIService = serviceInfo.metaData != null && boolean isCarModeUIService = serviceInfo.metaData != null && serviceInfo.metaData.getBoolean( serviceInfo.metaData.getBoolean( TelecomManager.METADATA_IN_CALL_SERVICE_CAR_MODE_UI, false) && TelecomManager.METADATA_IN_CALL_SERVICE_CAR_MODE_UI, false) && hasControlInCallPermission; (hasControlInCallPermission || isThirdPartyCompanionApp); if (isCarModeUIService) { if (isCarModeUIService) { return IN_CALL_SERVICE_TYPE_CAR_MODE_UI; return IN_CALL_SERVICE_TYPE_CAR_MODE_UI; } } Loading @@ -1293,9 +1312,15 @@ public class InCallController extends CallsManagerListenerBase { // Also allow any in-call service that has the control-experience permission (to ensure // 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. // that it is a system app) and doesn't claim to show any UI. if (hasControlInCallPermission && !isUIService) { if (!isUIService) { if (hasControlInCallPermission && !isThirdPartyCompanionApp) { return IN_CALL_SERVICE_TYPE_NON_UI; return IN_CALL_SERVICE_TYPE_NON_UI; } } // Third party companion alls without CONTROL_INCALL_EXPERIENCE permission. if (!hasControlInCallPermission && isThirdPartyCompanionApp) { return IN_CALL_SERVICE_TYPE_COMPANION; } } // Anything else that remains, we will not bind to. // Anything else that remains, we will not bind to. Log.i(this, "Skipping binding to %s:%s, control: %b, car-mode: %b, ui: %b", Log.i(this, "Skipping binding to %s:%s, control: %b, car-mode: %b, ui: %b", Loading
src/com/android/server/telecom/RoleManagerAdapterImpl.java +3 −1 Original line number Original line Diff line number Diff line Loading @@ -73,7 +73,9 @@ public class RoleManagerAdapterImpl implements RoleManagerAdapter { @Override @Override public List<String> getCallCompanionApps() { public List<String> getCallCompanionApps() { List<String> callCompanionApps = getRoleManagerCallCompanionApps(); List<String> callCompanionApps = new ArrayList<>(); // List from RoleManager is not resizable. AbstractList.add action is not supported. callCompanionApps.addAll(getRoleManagerCallCompanionApps()); callCompanionApps.addAll(mOverrideCallCompanionApps); callCompanionApps.addAll(mOverrideCallCompanionApps); return callCompanionApps; return callCompanionApps; } } Loading
tests/src/com/android/server/telecom/tests/ComponentContextFixture.java +12 −0 Original line number Original line Diff line number Diff line Loading @@ -26,9 +26,11 @@ import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer; import android.Manifest; import android.app.AppOpsManager; import android.app.AppOpsManager; import android.app.NotificationManager; import android.app.NotificationManager; import android.app.StatusBarManager; import android.app.StatusBarManager; // import android.app.role.RoleManager; import android.content.BroadcastReceiver; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ComponentName; import android.content.ContentResolver; import android.content.ContentResolver; Loading Loading @@ -71,6 +73,7 @@ import java.util.List; import java.util.Locale; import java.util.Locale; import java.util.Map; import java.util.Map; import static org.mockito.ArgumentMatchers.matches; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.any; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyInt; Loading Loading @@ -181,6 +184,9 @@ public class ComponentContextFixture implements TestFixture<Context> { return mCarrierConfigManager; return mCarrierConfigManager; case Context.COUNTRY_DETECTOR: case Context.COUNTRY_DETECTOR: return mCountryDetector; return mCountryDetector; // TODO: RoleManager not ready yet, uncomment when it's merged into aosp. // case Context.ROLE_SERVICE: // return mRoleManager; default: default: return null; return null; } } Loading Loading @@ -432,6 +438,7 @@ public class ComponentContextFixture implements TestFixture<Context> { private final Map<String, IContentProvider> mIContentProviderByUri = new HashMap<>(); private final Map<String, IContentProvider> mIContentProviderByUri = new HashMap<>(); private final Configuration mResourceConfiguration = new Configuration(); private final Configuration mResourceConfiguration = new Configuration(); private final ApplicationInfo mTestApplicationInfo = new ApplicationInfo(); private final ApplicationInfo mTestApplicationInfo = new ApplicationInfo(); // private final RoleManager mRoleManager = mock(RoleManager.class); private TelecomManager mTelecomManager = null; private TelecomManager mTelecomManager = null; Loading Loading @@ -463,6 +470,11 @@ public class ComponentContextFixture implements TestFixture<Context> { } } }).when(mPackageManager).queryIntentServicesAsUser((Intent) any(), anyInt(), anyInt()); }).when(mPackageManager).queryIntentServicesAsUser((Intent) any(), anyInt(), anyInt()); // By default, tests use non-ui apps instead of 3rd party companion apps. when(mPackageManager.checkPermission( matches(Manifest.permission.CALL_COMPANION_APP), anyString())) .thenReturn(PackageManager.PERMISSION_DENIED); when(mTelephonyManager.getSubIdForPhoneAccount((PhoneAccount) any())).thenReturn(1); when(mTelephonyManager.getSubIdForPhoneAccount((PhoneAccount) any())).thenReturn(1); when(mTelephonyManager.getNetworkOperatorName()).thenReturn("label1"); when(mTelephonyManager.getNetworkOperatorName()).thenReturn("label1"); Loading
tests/src/com/android/server/telecom/tests/InCallControllerTests.java +150 −4 Original line number Original line Diff line number Diff line Loading @@ -50,6 +50,7 @@ import com.android.server.telecom.EmergencyCallHelper; import com.android.server.telecom.InCallController; import com.android.server.telecom.InCallController; import com.android.server.telecom.PhoneAccountRegistrar; import com.android.server.telecom.PhoneAccountRegistrar; import com.android.server.telecom.R; import com.android.server.telecom.R; import com.android.server.telecom.RoleManagerAdapter; import com.android.server.telecom.SystemStateHelper; import com.android.server.telecom.SystemStateHelper; import com.android.server.telecom.TelecomSystem; import com.android.server.telecom.TelecomSystem; import com.android.server.telecom.Timeouts; import com.android.server.telecom.Timeouts; Loading @@ -65,11 +66,15 @@ import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer; import java.util.ArrayList; import java.util.Collections; import java.util.Collections; import java.util.LinkedList; import java.util.LinkedList; import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.matches; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Matchers.any; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyInt; Loading @@ -95,12 +100,17 @@ public class InCallControllerTests extends TelecomTestCase { @Mock MockContext mMockContext; @Mock MockContext mMockContext; @Mock Timeouts.Adapter mTimeoutsAdapter; @Mock Timeouts.Adapter mTimeoutsAdapter; @Mock DefaultDialerCache mDefaultDialerCache; @Mock DefaultDialerCache mDefaultDialerCache; @Mock RoleManagerAdapter mMockRoleManagerAdapter; private static final int CURRENT_USER_ID = 900973; private static final int CURRENT_USER_ID = 900973; private static final String DEF_PKG = "defpkg"; private static final String DEF_PKG = "defpkg"; private static final String DEF_CLASS = "defcls"; private static final String DEF_CLASS = "defcls"; private static final String SYS_PKG = "syspkg"; private static final String SYS_PKG = "syspkg"; private static final String SYS_CLASS = "syscls"; private static final String SYS_CLASS = "syscls"; private static final String COMPANION_PKG = "cpnpkg"; private static final String COMPANION_CLASS = "cpncls"; private static final String CAR_PKG = "carpkg"; private static final String CAR_CLASS = "carcls"; private static final PhoneAccountHandle PA_HANDLE = private static final PhoneAccountHandle PA_HANDLE = new PhoneAccountHandle(new ComponentName("pa_pkg", "pa_cls"), "pa_id"); new PhoneAccountHandle(new ComponentName("pa_pkg", "pa_cls"), "pa_id"); Loading @@ -121,9 +131,17 @@ public class InCallControllerTests extends TelecomTestCase { doReturn(true).when(mMockResources).getBoolean(R.bool.grant_location_permission_enabled); doReturn(true).when(mMockResources).getBoolean(R.bool.grant_location_permission_enabled); mEmergencyCallHelper = new EmergencyCallHelper(mMockContext, SYS_PKG, mEmergencyCallHelper = new EmergencyCallHelper(mMockContext, SYS_PKG, mTimeoutsAdapter); mTimeoutsAdapter); when(mMockCallsManager.getRoleManagerAdapter()).thenReturn(mMockRoleManagerAdapter); mInCallController = new InCallController(mMockContext, mLock, mMockCallsManager, mInCallController = new InCallController(mMockContext, mLock, mMockCallsManager, mMockSystemStateHelper, mDefaultDialerCache, mTimeoutsAdapter, mMockSystemStateHelper, mDefaultDialerCache, mTimeoutsAdapter, mEmergencyCallHelper); mEmergencyCallHelper); // Companion Apps don't have CONTROL_INCALL_EXPERIENCE permission. when(mMockPackageManager.checkPermission( matches(Manifest.permission.CONTROL_INCALL_EXPERIENCE), matches(COMPANION_PKG))).thenReturn(PackageManager.PERMISSION_DENIED); when(mMockPackageManager.checkPermission( matches(Manifest.permission.CONTROL_INCALL_EXPERIENCE), matches(CAR_PKG))).thenReturn(PackageManager.PERMISSION_DENIED); } } @Override @Override Loading Loading @@ -493,6 +511,103 @@ public class InCallControllerTests extends TelecomTestCase { verify(mockInCallService).addCall(any(ParcelableCall.class)); verify(mockInCallService).addCall(any(ParcelableCall.class)); } } /** * Ensures that the {@link InCallController} will bind to an {@link InCallService} which * supports third party companion calls. */ @MediumTest public void doNotTestBindToService_Companion() throws Exception { setupMocks(true /* isExternalCall */); setupMockPackageManager(true /* default */, true /* system */, true /* external calls */); List<String> companionAppsList = new ArrayList<>(); companionAppsList.add(COMPANION_PKG); companionAppsList.add(COMPANION_PKG); when(mMockRoleManagerAdapter.getCallCompanionApps()).thenReturn(companionAppsList); mInCallController.bindToServices(mMockCall); // Query for the different InCallServices ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mMockPackageManager, times(6)).queryIntentServicesAsUser( queryIntentCaptor.capture(), eq(PackageManager.GET_META_DATA), eq(CURRENT_USER_ID)); // Verify call for default dialer InCallService assertEquals(DEF_PKG, queryIntentCaptor.getAllValues().get(0).getPackage()); // Verify call for system dialer InCallService assertEquals(null, queryIntentCaptor.getAllValues().get(1).getPackage()); // Verify call for car mode ui InCallServices assertEquals(null, queryIntentCaptor.getAllValues().get(2).getPackage()); // Verify call for non-UI InCallServices assertEquals(null, queryIntentCaptor.getAllValues().get(3).getPackage()); // Verify call for companion InCallServices assertEquals(COMPANION_PKG, queryIntentCaptor.getAllValues().get(4).getPackage()); assertEquals(COMPANION_PKG, queryIntentCaptor.getAllValues().get(5).getPackage()); // Bind InCallServices ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mMockContext, times(3)).bindServiceAsUser( bindIntentCaptor.capture(), any(ServiceConnection.class), eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE), eq(UserHandle.CURRENT)); // Verify bind dialer Intent bindIntent = bindIntentCaptor.getAllValues().get(0); assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction()); assertEquals(DEF_PKG, bindIntent.getComponent().getPackageName()); assertEquals(DEF_CLASS, bindIntent.getComponent().getClassName()); // Verify bind companion apps bindIntent = bindIntentCaptor.getAllValues().get(1); assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction()); assertEquals(COMPANION_PKG, bindIntent.getComponent().getPackageName()); assertEquals(COMPANION_CLASS, bindIntent.getComponent().getClassName()); bindIntent = bindIntentCaptor.getAllValues().get(2); assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction()); assertEquals(COMPANION_PKG, bindIntent.getComponent().getPackageName()); assertEquals(COMPANION_CLASS, bindIntent.getComponent().getClassName()); } /** * Ensures that the {@link InCallController} will bind to an {@link InCallService} which * supports third party car mode ui calls */ @MediumTest public void doNotTestBindToService_CarModeUI() throws Exception { setupMocks(true /* isExternalCall */); setupMockPackageManager(true /* default */, true /* system */, true /* external calls */); when(mMockRoleManagerAdapter.getCarModeDialerApp()).thenReturn(CAR_PKG); // Enable car mode when(mMockSystemStateHelper.isCarMode()).thenReturn(true); mInCallController.bindToServices(mMockCall); // Query for the different InCallServices ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mMockPackageManager, times(4)).queryIntentServicesAsUser( queryIntentCaptor.capture(), eq(PackageManager.GET_META_DATA), eq(CURRENT_USER_ID)); // Verify call for default dialer InCallService assertEquals(DEF_PKG, queryIntentCaptor.getAllValues().get(0).getPackage()); // Verify call for system dialer InCallService assertEquals(null, queryIntentCaptor.getAllValues().get(1).getPackage()); // Verify call for car mode ui InCallServices assertEquals(CAR_PKG, queryIntentCaptor.getAllValues().get(2).getPackage()); // Verify call for non-UI InCallServices assertEquals(null, queryIntentCaptor.getAllValues().get(3).getPackage()); // Bind InCallServices ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mMockContext, times(1)).bindServiceAsUser( bindIntentCaptor.capture(), any(ServiceConnection.class), eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE), eq(UserHandle.CURRENT)); // Verify bind car mode ui Intent bindIntent = bindIntentCaptor.getAllValues().get(0); assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction()); assertEquals(CAR_PKG, bindIntent.getComponent().getPackageName()); assertEquals(CAR_CLASS, bindIntent.getComponent().getClassName()); } private void setupMocks(boolean isExternalCall) { private void setupMocks(boolean isExternalCall) { when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle); when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle); when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); Loading Loading @@ -521,6 +636,22 @@ public class InCallControllerTests extends TelecomTestCase { }}; }}; } } private ResolveInfo getCarModeResolveinfo(final boolean includeExternalCalls) { return new ResolveInfo() {{ serviceInfo = new ServiceInfo(); serviceInfo.packageName = CAR_PKG; serviceInfo.name = CAR_CLASS; serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE; serviceInfo.metaData = new Bundle(); serviceInfo.metaData.putBoolean( TelecomManager.METADATA_IN_CALL_SERVICE_CAR_MODE_UI, true); if (includeExternalCalls) { serviceInfo.metaData.putBoolean( TelecomManager.METADATA_INCLUDE_EXTERNAL_CALLS, true); } }}; } private ResolveInfo getSysResolveinfo() { private ResolveInfo getSysResolveinfo() { return new ResolveInfo() {{ return new ResolveInfo() {{ serviceInfo = new ServiceInfo(); serviceInfo = new ServiceInfo(); Loading @@ -530,6 +661,15 @@ public class InCallControllerTests extends TelecomTestCase { }}; }}; } } private ResolveInfo getCompanionResolveinfo() { return new ResolveInfo() {{ serviceInfo = new ServiceInfo(); serviceInfo.packageName = COMPANION_PKG; serviceInfo.name = COMPANION_CLASS; serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE; }}; } private void setupMockPackageManager(final boolean useDefaultDialer, private void setupMockPackageManager(final boolean useDefaultDialer, final boolean useSystemDialer, final boolean includeExternalCalls) { final boolean useSystemDialer, final boolean includeExternalCalls) { Loading @@ -545,15 +685,21 @@ public class InCallControllerTests extends TelecomTestCase { } } LinkedList<ResolveInfo> resolveInfo = new LinkedList<ResolveInfo>(); LinkedList<ResolveInfo> resolveInfo = new LinkedList<ResolveInfo>(); if (!TextUtils.isEmpty(packageName)) { if (!TextUtils.isEmpty(packageName)) { if ((TextUtils.isEmpty(packageName) || packageName.equals(DEF_PKG)) && if (packageName.equals(DEF_PKG) && useDefaultDialer) { useDefaultDialer) { resolveInfo.add(getDefResolveInfo(includeExternalCalls)); resolveInfo.add(getDefResolveInfo(includeExternalCalls)); } } if ((TextUtils.isEmpty(packageName) || packageName.equals(SYS_PKG)) && if (packageName.equals(SYS_PKG) && useSystemDialer) { useSystemDialer) { resolveInfo.add(getSysResolveinfo()); resolveInfo.add(getSysResolveinfo()); } } if (packageName.equals(COMPANION_PKG)) { resolveInfo.add(getCompanionResolveinfo()); } if (packageName.equals(CAR_PKG)) { resolveInfo.add(getCarModeResolveinfo(includeExternalCalls)); } } } return resolveInfo; return resolveInfo; } } Loading