diff options
Diffstat (limited to 'tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java')
-rw-r--r-- | tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java | 985 |
1 files changed, 926 insertions, 59 deletions
diff --git a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java index f6f2ae283..8bc1f2a4d 100644 --- a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java +++ b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java @@ -18,11 +18,13 @@ package com.android.server.telecom.tests; import static android.Manifest.permission.CALL_PHONE; import static android.Manifest.permission.CALL_PRIVILEGED; +import static android.Manifest.permission.MANAGE_OWN_CALLS; import static android.Manifest.permission.MODIFY_PHONE_STATE; import static android.Manifest.permission.READ_PHONE_NUMBERS; import static android.Manifest.permission.READ_PHONE_STATE; import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE; +import android.Manifest; import android.app.ActivityManager; import android.app.AppOpsManager; import android.content.ComponentName; @@ -31,13 +33,16 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.Bundle; +import android.os.OutcomeReceiver; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; +import android.telecom.CallAttributes; import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; @@ -45,7 +50,9 @@ import android.telecom.VideoProfile; import android.telephony.TelephonyManager; import android.test.suitebuilder.annotation.SmallTest; +import com.android.internal.telecom.ICallEventCallback; import com.android.internal.telecom.ITelecomService; +import com.android.server.telecom.AnomalyReporterAdapter; import com.android.server.telecom.Call; import com.android.server.telecom.CallIntentProcessor; import com.android.server.telecom.CallState; @@ -56,6 +63,9 @@ import com.android.server.telecom.TelecomServiceImpl; import com.android.server.telecom.TelecomSystem; import com.android.server.telecom.components.UserCallIntentProcessor; import com.android.server.telecom.components.UserCallIntentProcessorFactory; +import com.android.server.telecom.voip.IncomingCallTransaction; +import com.android.server.telecom.voip.OutgoingCallTransaction; +import com.android.server.telecom.voip.TransactionManager; import org.junit.After; import org.junit.Before; @@ -66,7 +76,6 @@ import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatcher; import org.mockito.Mock; -import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.Executor; @@ -78,6 +87,7 @@ import static android.Manifest.permission.WRITE_SECURE_SETTINGS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.nullable; @@ -94,13 +104,18 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.isA; import static org.mockito.Mockito.when; @RunWith(JUnit4.class) public class TelecomServiceImplTest extends TelecomTestCase { + private static final String CALLING_PACKAGE = TelecomServiceImplTest.class.getPackageName(); + private static final String TEST_NAME = "Alan Turing"; + private static final Uri TEST_URI = Uri.fromParts("tel", "abc", "123"); public static final String TEST_PACKAGE = "com.test"; public static final String PACKAGE_NAME = "test"; @@ -174,6 +189,9 @@ public class TelecomServiceImplTest extends TelecomTestCase { @Mock private UserCallIntentProcessor mUserCallIntentProcessor; private PackageManager mPackageManager; @Mock private ApplicationInfo mApplicationInfo; + @Mock private ICallEventCallback mICallEventCallback; + @Mock private TransactionManager mTransactionManager; + @Mock private AnomalyReporterAdapter mAnomalyReporterAdapter; private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { }; @@ -203,6 +221,8 @@ public class TelecomServiceImplTest extends TelecomTestCase { doReturn(mContext).when(mContext).createContextAsUser(any(UserHandle.class), anyInt()); doNothing().when(mContext).sendBroadcastAsUser(any(Intent.class), any(UserHandle.class), anyString()); + when(mContext.checkCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS)) + .thenReturn(PackageManager.PERMISSION_GRANTED); doAnswer(invocation -> { mDefaultDialerObserver = invocation.getArgument(1); return null; @@ -223,6 +243,8 @@ public class TelecomServiceImplTest extends TelecomTestCase { mSubscriptionManagerAdapter, mSettingsSecureAdapter, mLock); + telecomServiceImpl.setTransactionManager(mTransactionManager); + telecomServiceImpl.setAnomalyReporterAdapter(mAnomalyReporterAdapter); mTSIBinder = telecomServiceImpl.getBinder(); mComponentContextFixture.setTelecomManager(mTelecomManager); when(mTelecomManager.getDefaultDialerPackage()).thenReturn(DEFAULT_DIALER_PACKAGE); @@ -271,6 +293,51 @@ public class TelecomServiceImplTest extends TelecomTestCase { assertEquals(SIP_PA_HANDLE_17, returnedHandleSip); } + /** + * Clear the groupId from the PhoneAccount if a package does NOT have MODIFY_PHONE_STATE + */ + @SmallTest + @Test + public void testGroupIdIsClearedWhenPermissionIsMissing() throws RemoteException { + // GIVEN + PhoneAccount phoneAccount = makePhoneAccount(TEL_PA_HANDLE_CURRENT) + .setGroupId("testId") + .build(); + // WHEN + doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount( + eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class), anyBoolean()); + doNothing().when(mAppOpsManager).checkPackage(anyInt(), anyString()); + doReturn(PackageManager.PERMISSION_DENIED) + .when(mContext).checkCallingPermission(MODIFY_PHONE_STATE); + // THEN + PhoneAccount account = + mTSIBinder.getPhoneAccount(TEL_PA_HANDLE_CURRENT, PACKAGE_NAME); + assertEquals("***", account.getGroupId()); + } + + /** + * Ensure groupId is not cleared if a package has MODIFY_PHONE_STATE + */ + @SmallTest + @Test + public void testGroupIdIsNotCleared() throws RemoteException { + // GIVEN + final String groupId = "testId"; + PhoneAccount phoneAccount = makePhoneAccount(TEL_PA_HANDLE_CURRENT) + .setGroupId(groupId) + .build(); + // WHEN + doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount( + eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class), anyBoolean()); + doNothing().when(mAppOpsManager).checkPackage(anyInt(), anyString()); + doReturn(PackageManager.PERMISSION_GRANTED) + .when(mContext).checkCallingPermission(MODIFY_PHONE_STATE); + // THEN + PhoneAccount account = + mTSIBinder.getPhoneAccount(TEL_PA_HANDLE_CURRENT, DEFAULT_DIALER_PACKAGE); + assertEquals(groupId, account.getGroupId()); + } + @SmallTest @Test public void testGetDefaultOutgoingPhoneAccountSucceedsIfCallerIsSimCallManager() @@ -354,19 +421,90 @@ public class TelecomServiceImplTest extends TelecomTestCase { .setUserSelectedOutgoingPhoneAccount(eq(TEL_PA_HANDLE_16), any(UserHandle.class)); } + @Test + public void testAddCallWithOutgoingCall() throws RemoteException { + // GIVEN + CallAttributes mOutgoingCallAttributes = new CallAttributes.Builder(TEL_PA_HANDLE_CURRENT, + CallAttributes.DIRECTION_OUTGOING, TEST_NAME, TEST_URI) + .setCallType(CallAttributes.AUDIO_CALL) + .setCallCapabilities(CallAttributes.SUPPORTS_SET_INACTIVE) + .build(); + PhoneAccount phoneAccount = makeMultiUserPhoneAccount(TEL_PA_HANDLE_CURRENT).build(); + phoneAccount.setIsEnabled(true); + + // WHEN + when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn( + phoneAccount); + + doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount( + eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class)); + + mTSIBinder.addCall(mOutgoingCallAttributes, mICallEventCallback, "1", CALLING_PACKAGE); + + // THEN + verify(mTransactionManager, times(1)) + .addTransaction(isA(OutgoingCallTransaction.class), isA(OutcomeReceiver.class)); + } + + @Test + public void testAddCallWithIncomingCall() throws RemoteException { + // GIVEN + CallAttributes mIncomingCallAttributes = new CallAttributes.Builder(TEL_PA_HANDLE_CURRENT, + CallAttributes.DIRECTION_INCOMING, TEST_NAME, TEST_URI) + .setCallType(CallAttributes.AUDIO_CALL) + .setCallCapabilities(CallAttributes.SUPPORTS_SET_INACTIVE) + .build(); + PhoneAccount phoneAccount = makeMultiUserPhoneAccount(TEL_PA_HANDLE_CURRENT).build(); + phoneAccount.setIsEnabled(true); + + // WHEN + when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn( + phoneAccount); + + doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount( + eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class)); + + mTSIBinder.addCall(mIncomingCallAttributes, mICallEventCallback, "1", CALLING_PACKAGE); + + // THEN + verify(mTransactionManager, times(1)) + .addTransaction(isA(IncomingCallTransaction.class), isA(OutcomeReceiver.class)); + } + + @Test + public void testAddCallWithManagedPhoneAccount() throws RemoteException { + // GIVEN + CallAttributes attributes = new CallAttributes.Builder(TEL_PA_HANDLE_CURRENT, + CallAttributes.DIRECTION_OUTGOING, TEST_NAME, TEST_URI).build(); + PhoneAccount phoneAccount = makeMultiUserPhoneAccount(TEL_PA_HANDLE_CURRENT) + .setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION) + .build(); + phoneAccount.setIsEnabled(true); + + // WHEN + when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn( + phoneAccount); + + doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount( + eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class)); + + // THEN + try { + mTSIBinder.addCall(attributes, mICallEventCallback, "1", CALLING_PACKAGE); + fail("should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // pass + } + } + @SmallTest @Test - public void testSetUserSelectedOutgoingPhoneAccountFailure() throws RemoteException { + public void testSetUserSelectedOutgoingPhoneAccountWithoutPermission() throws RemoteException { doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission( anyString(), nullable(String.class)); - try { - mTSIBinder.setUserSelectedOutgoingPhoneAccount(TEL_PA_HANDLE_16); - } catch (SecurityException e) { - // desired result - } - verify(mFakePhoneAccountRegistrar, never()) - .setUserSelectedOutgoingPhoneAccount( - any(PhoneAccountHandle.class), any(UserHandle.class)); + + assertThrows(SecurityException.class, + () -> mTSIBinder.setUserSelectedOutgoingPhoneAccount(TEL_PA_HANDLE_16)); } @SmallTest @@ -378,11 +516,11 @@ public class TelecomServiceImplTest extends TelecomTestCase { // Returns all phone accounts when getCallCapablePhoneAccounts is called. when(mFakePhoneAccountRegistrar .getCallCapablePhoneAccounts(nullable(String.class), eq(true), - nullable(UserHandle.class))).thenReturn(fullPHList); + nullable(UserHandle.class), eq(true))).thenReturn(fullPHList); // Returns only enabled phone accounts when getCallCapablePhoneAccounts is called. when(mFakePhoneAccountRegistrar .getCallCapablePhoneAccounts(nullable(String.class), eq(false), - nullable(UserHandle.class))).thenReturn(smallPHList); + nullable(UserHandle.class), eq(true))).thenReturn(smallPHList); makeAccountsVisibleToAllUsers(TEL_PA_HANDLE_16, SIP_PA_HANDLE_17); assertEquals(fullPHList, @@ -395,21 +533,63 @@ public class TelecomServiceImplTest extends TelecomTestCase { @SmallTest @Test - public void testGetCallCapablePhoneAccountsFailure() throws RemoteException { + public void testGetCallCapablePhoneAccountsWithoutPermission() throws RemoteException { List<String> enforcedPermissions = List.of(READ_PHONE_STATE, READ_PRIVILEGED_PHONE_STATE); doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission( argThat(new AnyStringIn(enforcedPermissions)), anyString()); - List<PhoneAccountHandle> result = null; - try { - result = mTSIBinder.getCallCapablePhoneAccounts(true, "", null).getList(); - } catch (SecurityException e) { - // intended behavior - } - assertNull(result); - verify(mFakePhoneAccountRegistrar, never()) - .getCallCapablePhoneAccounts(anyString(), anyBoolean(), any(UserHandle.class)); + assertThrows(SecurityException.class, + () -> mTSIBinder.getCallCapablePhoneAccounts(true, "", null)); + } + + @SmallTest + @Test + public void testGetSelfManagedPhoneAccounts() throws RemoteException { + List<PhoneAccountHandle> accounts = List.of(TEL_PA_HANDLE_16); + + when(mFakePhoneAccountRegistrar.getSelfManagedPhoneAccounts(nullable(UserHandle.class))) + .thenReturn(accounts); + makeAccountsVisibleToAllUsers(TEL_PA_HANDLE_16); + + assertEquals(accounts, + mTSIBinder.getSelfManagedPhoneAccounts(DEFAULT_DIALER_PACKAGE, null).getList()); + } + + @SmallTest + @Test + public void testGetSelfManagedPhoneAccountsWithoutPermission() throws RemoteException { + List<String> enforcedPermissions = List.of(READ_PHONE_STATE, READ_PRIVILEGED_PHONE_STATE); + doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission( + argThat(new AnyStringIn(enforcedPermissions)), anyString()); + + assertThrows(SecurityException.class, + () -> mTSIBinder.getSelfManagedPhoneAccounts("", null)); + } + + @SmallTest + @Test + public void testGetOwnSelfManagedPhoneAccounts() throws RemoteException { + List<PhoneAccountHandle> accounts = List.of(TEL_PA_HANDLE_16); + + when(mFakePhoneAccountRegistrar.getSelfManagedPhoneAccountsForPackage( + eq(DEFAULT_DIALER_PACKAGE), nullable(UserHandle.class))) + .thenReturn(accounts); + makeAccountsVisibleToAllUsers(TEL_PA_HANDLE_16); + + assertEquals(accounts, + mTSIBinder.getOwnSelfManagedPhoneAccounts(DEFAULT_DIALER_PACKAGE, null).getList()); + } + + @SmallTest + @Test + public void testGetOwnSelfManagedPhoneAccountsWithoutPermission() throws RemoteException { + List<String> enforcedPermissions = List.of(MANAGE_OWN_CALLS); + doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission( + argThat(new AnyStringIn(enforcedPermissions)), anyString()); + + assertThrows(SecurityException.class, + () -> mTSIBinder.getOwnSelfManagedPhoneAccounts("", null)); } @SmallTest @@ -419,10 +599,12 @@ public class TelecomServiceImplTest extends TelecomTestCase { List<PhoneAccountHandle> telPHList = List.of(TEL_PA_HANDLE_16); when(mFakePhoneAccountRegistrar - .getCallCapablePhoneAccounts(eq("tel"), anyBoolean(), any(UserHandle.class))) + .getCallCapablePhoneAccounts(eq("tel"), anyBoolean(), + any(UserHandle.class), anyBoolean())) .thenReturn(telPHList); when(mFakePhoneAccountRegistrar - .getCallCapablePhoneAccounts(eq("sip"), anyBoolean(), any(UserHandle.class))) + .getCallCapablePhoneAccounts(eq("sip"), anyBoolean(), + any(UserHandle.class), anyBoolean())) .thenReturn(sipPHList); makeAccountsVisibleToAllUsers(TEL_PA_HANDLE_16, SIP_PA_HANDLE_17); @@ -436,11 +618,21 @@ public class TelecomServiceImplTest extends TelecomTestCase { @SmallTest @Test + public void testGetPhoneAccountsSupportingSchemeWithoutPermission() throws RemoteException { + List<String> enforcedPermissions = List.of(MODIFY_PHONE_STATE); + doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission( + argThat(new AnyStringIn(enforcedPermissions)), anyString()); + + assertTrue(mTSIBinder.getPhoneAccountsSupportingScheme("any", "").getList().isEmpty()); + } + + @SmallTest + @Test public void testGetPhoneAccountsForPackage() throws RemoteException { List<PhoneAccountHandle> phoneAccountHandleList = List.of( TEL_PA_HANDLE_16, SIP_PA_HANDLE_17); when(mFakePhoneAccountRegistrar - .getPhoneAccountsForPackage(anyString(), any(UserHandle.class))) + .getAllPhoneAccountHandlesForPackage(any(UserHandle.class), anyString())) .thenReturn(phoneAccountHandleList); makeAccountsVisibleToAllUsers(TEL_PA_HANDLE_16, SIP_PA_HANDLE_17); assertEquals(phoneAccountHandleList, @@ -450,7 +642,20 @@ public class TelecomServiceImplTest extends TelecomTestCase { @SmallTest @Test + public void testGetPhoneAccountsForPackageWithoutPermission() throws RemoteException { + List<String> enforcedPermissions = List.of(READ_PRIVILEGED_PHONE_STATE); + doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission( + argThat(new AnyStringIn(enforcedPermissions)), any()); + + assertThrows(SecurityException.class, + () -> mTSIBinder.getPhoneAccountsForPackage("")); + } + + @SmallTest + @Test public void testGetPhoneAccount() throws Exception { + doReturn(PackageManager.PERMISSION_GRANTED) + .when(mContext).checkCallingPermission(MODIFY_PHONE_STATE); makeAccountsVisibleToAllUsers(TEL_PA_HANDLE_16, SIP_PA_HANDLE_17); assertEquals(TEL_PA_HANDLE_16, mTSIBinder.getPhoneAccount(TEL_PA_HANDLE_16, mContext.getPackageName()).getAccountHandle()); @@ -466,15 +671,96 @@ public class TelecomServiceImplTest extends TelecomTestCase { @SmallTest @Test + public void testGetAllPhoneAccountsCount() throws RemoteException { + List<PhoneAccount> phoneAccountList = List.of( + makePhoneAccount(TEL_PA_HANDLE_16).build(), + makePhoneAccount(SIP_PA_HANDLE_17).build()); + + when(mFakePhoneAccountRegistrar.getAllPhoneAccounts(any(UserHandle.class), anyBoolean())) + .thenReturn(phoneAccountList); + + assertEquals(phoneAccountList.size(), mTSIBinder.getAllPhoneAccountsCount()); + } + + @SmallTest + @Test + public void testGetAllPhoneAccountsCountWithoutPermission() throws RemoteException { + List<String> enforcedPermissions = List.of(MODIFY_PHONE_STATE); + doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission( + argThat(new AnyStringIn(enforcedPermissions)), any()); + + assertThrows(SecurityException.class, + () -> mTSIBinder.getAllPhoneAccountsCount()); + } + + @SmallTest + @Test public void testGetAllPhoneAccounts() throws RemoteException { List<PhoneAccount> phoneAccountList = List.of( makePhoneAccount(TEL_PA_HANDLE_16).build(), makePhoneAccount(SIP_PA_HANDLE_17).build()); - when(mFakePhoneAccountRegistrar.getAllPhoneAccounts(any(UserHandle.class))) + when(mFakePhoneAccountRegistrar.getAllPhoneAccounts(any(UserHandle.class), anyBoolean())) .thenReturn(phoneAccountList); - assertEquals(2, mTSIBinder.getAllPhoneAccounts().getList().size()); + assertEquals(phoneAccountList.size(), mTSIBinder.getAllPhoneAccounts().getList().size()); + } + + @SmallTest + @Test + public void testGetAllPhoneAccountsWithoutPermission() throws RemoteException { + List<String> enforcedPermissions = List.of(MODIFY_PHONE_STATE); + doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission( + argThat(new AnyStringIn(enforcedPermissions)), any()); + + assertThrows(SecurityException.class, + () -> mTSIBinder.getAllPhoneAccounts()); + } + + @SmallTest + @Test + public void testGetAllPhoneAccountHandles() throws RemoteException { + List<PhoneAccountHandle> handles = List.of(TEL_PA_HANDLE_16, SIP_PA_HANDLE_17); + when(mFakePhoneAccountRegistrar.getAllPhoneAccountHandles( + any(UserHandle.class), anyBoolean())).thenReturn(handles); + + assertEquals(handles, mTSIBinder.getAllPhoneAccountHandles().getList()); + } + + @SmallTest + @Test + public void testGetAllPhoneAccountHandlesWithoutPermission() throws RemoteException { + List<String> enforcedPermissions = List.of(MODIFY_PHONE_STATE); + doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission( + argThat(new AnyStringIn(enforcedPermissions)), any()); + + assertThrows(SecurityException.class, + () -> mTSIBinder.getAllPhoneAccountHandles()); + } + + @SmallTest + @Test + public void testGetSimCallManager() throws RemoteException { + final PhoneAccountHandle handle = TEL_PA_HANDLE_16; + final int subId = 1; + when(mFakePhoneAccountRegistrar.getSimCallManager(eq(subId), any(UserHandle.class))) + .thenReturn(handle); + + assertEquals(handle, mTSIBinder.getSimCallManager(subId, "any")); + } + + @SmallTest + @Test + public void testGetSimCallManagerForUser() throws RemoteException { + final PhoneAccountHandle handle = TEL_PA_HANDLE_16; + final int user = 1; + when(mFakePhoneAccountRegistrar.getSimCallManager( + argThat(userHandle -> { + return userHandle.getIdentifier() == user; + }))) + .thenReturn(handle); + + assertEquals(handle, mTSIBinder.getSimCallManagerForUser(user, "any")); } @SmallTest @@ -492,6 +778,65 @@ public class TelecomServiceImplTest extends TelecomTestCase { @SmallTest @Test + public void testRegisterPhoneAccountWithoutPermissionAnomalyReported() throws RemoteException { + PhoneAccountHandle handle = new PhoneAccountHandle( + new ComponentName("package", "cs"), "test", Binder.getCallingUserHandle()); + PhoneAccount account = makeSelfManagedPhoneAccount(handle).build(); + + List<String> enforcedPermissions = List.of(MANAGE_OWN_CALLS); + doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission( + argThat(new AnyStringIn(enforcedPermissions)), any()); + + registerPhoneAccountTestHelper(account, false); + verify(mAnomalyReporterAdapter).reportAnomaly( + TelecomServiceImpl.REGISTER_PHONE_ACCOUNT_ERROR_UUID, + TelecomServiceImpl.REGISTER_PHONE_ACCOUNT_ERROR_MSG); + } + + @SmallTest + @Test + public void testRegisterPhoneAccountSelfManagedWithoutPermission() throws RemoteException { + PhoneAccountHandle handle = new PhoneAccountHandle( + new ComponentName("package", "cs"), "test", Binder.getCallingUserHandle()); + PhoneAccount account = makeSelfManagedPhoneAccount(handle).build(); + + List<String> enforcedPermissions = List.of(MANAGE_OWN_CALLS); + doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission( + argThat(new AnyStringIn(enforcedPermissions)), any()); + + registerPhoneAccountTestHelper(account, false); + } + + @SmallTest + @Test + public void testRegisterPhoneAccountSelfManagedInvalidCapabilities() throws RemoteException { + PhoneAccountHandle handle = new PhoneAccountHandle( + new ComponentName("package", "cs"), "test", Binder.getCallingUserHandle()); + + PhoneAccount selfManagedCallProviderAccount = makePhoneAccount(handle) + .setCapabilities( + PhoneAccount.CAPABILITY_SELF_MANAGED | + PhoneAccount.CAPABILITY_CALL_PROVIDER) + .build(); + registerPhoneAccountTestHelper(selfManagedCallProviderAccount, false); + + PhoneAccount selfManagedConnectionManagerAccount = makePhoneAccount(handle) + .setCapabilities( + PhoneAccount.CAPABILITY_SELF_MANAGED | + PhoneAccount.CAPABILITY_CONNECTION_MANAGER) + .build(); + registerPhoneAccountTestHelper(selfManagedConnectionManagerAccount, false); + + PhoneAccount selfManagedSimSubscriptionAccount = makePhoneAccount(handle) + .setCapabilities( + PhoneAccount.CAPABILITY_SELF_MANAGED | + PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION) + .build(); + registerPhoneAccountTestHelper(selfManagedSimSubscriptionAccount, false); + } + + @SmallTest + @Test public void testRegisterPhoneAccountWithoutModifyPermission() throws RemoteException { // tests the case where the package does not have MODIFY_PHONE_STATE but is // registering its own phone account as a third-party connection service @@ -589,7 +934,7 @@ public class TelecomServiceImplTest extends TelecomTestCase { ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); boolean didExceptionOccur = false; try { - mTSIBinder.registerPhoneAccount(testPhoneAccount); + mTSIBinder.registerPhoneAccount(testPhoneAccount, CALLING_PACKAGE); } catch (Exception e) { didExceptionOccur = true; } @@ -606,6 +951,26 @@ public class TelecomServiceImplTest extends TelecomTestCase { @SmallTest @Test + public void testRegisterPhoneAccountImageIconCrossUser() throws RemoteException { + String packageNameToUse = "com.android.officialpackage"; + PhoneAccountHandle phHandle = new PhoneAccountHandle(new ComponentName( + packageNameToUse, "cs"), "test", Binder.getCallingUserHandle()); + Icon icon = Icon.createWithContentUri("content://10@media/external/images/media/"); + PhoneAccount phoneAccount = makePhoneAccount(phHandle).setIcon(icon).build(); + doReturn(PackageManager.PERMISSION_GRANTED) + .when(mContext).checkCallingOrSelfPermission(MODIFY_PHONE_STATE); + + // This should fail; security exception will be thrown. + registerPhoneAccountTestHelper(phoneAccount, false); + + icon = Icon.createWithContentUri("content://0@media/external/images/media/"); + phoneAccount = makePhoneAccount(phHandle).setIcon(icon).build(); + // This should succeed. + registerPhoneAccountTestHelper(phoneAccount, true); + } + + @SmallTest + @Test public void testUnregisterPhoneAccount() throws RemoteException { String packageNameToUse = "com.android.officialpackage"; PhoneAccountHandle phHandle = new PhoneAccountHandle(new ComponentName( @@ -615,7 +980,7 @@ public class TelecomServiceImplTest extends TelecomTestCase { doReturn(PackageManager.PERMISSION_GRANTED) .when(mContext).checkCallingOrSelfPermission(MODIFY_PHONE_STATE); - mTSIBinder.unregisterPhoneAccount(phHandle); + mTSIBinder.unregisterPhoneAccount(phHandle, CALLING_PACKAGE); verify(mFakePhoneAccountRegistrar).unregisterPhoneAccount(phHandle); } @@ -632,7 +997,7 @@ public class TelecomServiceImplTest extends TelecomTestCase { when(pm.hasSystemFeature(PackageManager.FEATURE_TELECOM)).thenReturn(false); try { - mTSIBinder.unregisterPhoneAccount(phHandle); + mTSIBinder.unregisterPhoneAccount(phHandle, CALLING_PACKAGE); } catch (UnsupportedOperationException e) { // expected behavior } @@ -644,25 +1009,47 @@ public class TelecomServiceImplTest extends TelecomTestCase { @SmallTest @Test + public void testClearAccounts() throws RemoteException { + mTSIBinder.clearAccounts(CALLING_PACKAGE); + + verify(mFakePhoneAccountRegistrar) + .clearAccounts(CALLING_PACKAGE, mTSIBinder.getCallingUserHandle()); + } + + @SmallTest + @Test + public void testClearAccountsWithoutPermission() throws RemoteException { + doReturn(PackageManager.PERMISSION_DENIED) + .when(mContext).checkCallingOrSelfPermission(MODIFY_PHONE_STATE); + + assertThrows(UnsupportedOperationException.class, + () -> mTSIBinder.clearAccounts(CALLING_PACKAGE)); + } + + @SmallTest + @Test public void testAddNewIncomingCall() throws Exception { - PhoneAccount phoneAccount = makePhoneAccount(TEL_PA_HANDLE_CURRENT).build(); + PhoneAccount phoneAccount = makePhoneAccount(TEL_PA_HANDLE_16).build(); phoneAccount.setIsEnabled(true); doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount( - eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class)); + eq(TEL_PA_HANDLE_16), any(UserHandle.class)); doNothing().when(mAppOpsManager).checkPackage(anyInt(), anyString()); Bundle extras = createSampleExtras(); - mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_CURRENT, extras); + mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_16, extras, CALLING_PACKAGE); + verify(mFakePhoneAccountRegistrar).getPhoneAccount( + TEL_PA_HANDLE_16, TEL_PA_HANDLE_16.getUserHandle()); addCallTestHelper(TelecomManager.ACTION_INCOMING_CALL, - CallIntentProcessor.KEY_IS_INCOMING_CALL, extras, false); + CallIntentProcessor.KEY_IS_INCOMING_CALL, extras, + TEL_PA_HANDLE_16, false); } @SmallTest @Test public void testAddNewIncomingCallFailure() throws Exception { try { - mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_16, null); + mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_16, null, CALLING_PACKAGE); } catch (SecurityException e) { // expected } @@ -670,7 +1057,7 @@ public class TelecomServiceImplTest extends TelecomTestCase { doThrow(new SecurityException()).when(mAppOpsManager).checkPackage(anyInt(), anyString()); try { - mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_CURRENT, null); + mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_CURRENT, null, CALLING_PACKAGE); } catch (SecurityException e) { // expected } @@ -693,7 +1080,7 @@ public class TelecomServiceImplTest extends TelecomTestCase { mTSIBinder.addNewUnknownCall(TEL_PA_HANDLE_CURRENT, extras); addCallTestHelper(TelecomManager.ACTION_NEW_UNKNOWN_CALL, - CallIntentProcessor.KEY_IS_UNKNOWN_CALL, extras, true); + CallIntentProcessor.KEY_IS_UNKNOWN_CALL, extras, TEL_PA_HANDLE_CURRENT, true); } @SmallTest @@ -719,7 +1106,8 @@ public class TelecomServiceImplTest extends TelecomTestCase { } private void addCallTestHelper(String expectedAction, String extraCallKey, - Bundle expectedExtras, boolean isUnknown) { + Bundle expectedExtras, PhoneAccountHandle expectedPhoneAccountHandle, + boolean isUnknown) { ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); if (isUnknown) { verify(mCallIntentProcessorAdapter).processUnknownCallIntent(any(CallsManager.class), @@ -731,7 +1119,7 @@ public class TelecomServiceImplTest extends TelecomTestCase { Intent capturedIntent = intentCaptor.getValue(); assertEquals(expectedAction, capturedIntent.getAction()); Bundle intentExtras = capturedIntent.getExtras(); - assertEquals(TEL_PA_HANDLE_CURRENT, + assertEquals(expectedPhoneAccountHandle, intentExtras.get(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE)); assertTrue(intentExtras.getBoolean(extraCallKey)); @@ -747,88 +1135,517 @@ public class TelecomServiceImplTest extends TelecomTestCase { } } + /** + * Place a managed call with no PhoneAccount specified and ensure no security exception is + * thrown. + */ @SmallTest @Test public void testPlaceCallWithNonEmergencyPermission() throws Exception { Uri handle = Uri.parse("tel:6505551234"); Bundle extras = createSampleExtras(); + // We have passed in the DEFAULT_DIALER_PACKAGE for this test, so canCallPhone is always + // true. when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(), nullable(String.class), nullable(String.class))) .thenReturn(AppOpsManager.MODE_ALLOWED); doReturn(PackageManager.PERMISSION_GRANTED) - .when(mContext).checkCallingPermission(CALL_PHONE); + .when(mContext).checkCallingOrSelfPermission(CALL_PHONE); doReturn(PackageManager.PERMISSION_DENIED) - .when(mContext).checkCallingPermission(CALL_PRIVILEGED); + .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED); mTSIBinder.placeCall(handle, extras, DEFAULT_DIALER_PACKAGE, null); - placeCallTestHelper(handle, extras, true); + placeCallTestHelper(handle, extras, /*isSelfManagedExpected*/ false, + /*shouldNonEmergencyBeAllowed*/ true); } + /** + * Ensure that we get a SecurityException if the UID of the caller doesn't match the UID of the + * UID of the package name passed in. + */ + @SmallTest + @Test + public void testPlaceCall_enforceCallingPackageFailure() throws Exception { + Uri handle = Uri.parse("tel:6505551234"); + Bundle extras = createSampleExtras(); + // callingPackage matches the PhoneAccountHandle, so this is an app with a self-managed + // ConnectionService. + extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT); + + // Return a non-matching UID for testing purposes. + when(mPackageManager.getPackageUid(anyString(), eq(0))).thenReturn(-1); + try { + mTSIBinder.placeCall(handle, extras, PACKAGE_NAME, null); + fail("Expected SecurityException because calling package doesn't match"); + } catch(SecurityException e) { + // expected + } + } + + /** + * In the case that there is a self-managed call request and MANAGE_OWN_CALLS is granted, ensure + * that placeCall does not generate a SecurityException. + */ + @SmallTest + @Test + public void testPlaceCall_selfManaged_permissionGranted() throws Exception { + doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer( + eq(DEFAULT_DIALER_PACKAGE), anyInt()); + when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn( + makeSelfManagedPhoneAccount(TEL_PA_HANDLE_CURRENT).build()); + Uri handle = Uri.parse("tel:6505551234"); + Bundle extras = createSampleExtras(); + // callingPackage matches the PhoneAccountHandle, so this is an app with a self-managed + // ConnectionService. + extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT); + + // pass MANAGE_OWN_CALLS check, but do not have CALL_PHONE + doNothing().when(mContext).enforceCallingOrSelfPermission( + eq(Manifest.permission.MANAGE_OWN_CALLS), anyString()); + doReturn(PackageManager.PERMISSION_DENIED) + .when(mContext).checkCallingOrSelfPermission(CALL_PHONE); + doReturn(PackageManager.PERMISSION_DENIED) + .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED); + + try { + mTSIBinder.placeCall(handle, extras, PACKAGE_NAME, null); + placeCallTestHelper(handle, extras, /*isSelfManagedExpected*/ true, + /*shouldNonEmergencyBeAllowed*/ false); + } catch(SecurityException e) { + fail("Unexpected SecurityException - MANAGE_OWN_CALLS is set"); + } + } + + /** + * In the case that the placeCall API is being used place a self-managed call + * (phone account is marked self-managed and the calling application owns that PhoneAccount), + * ensure that the call gets placed as not self-managed as to not disclose PA info. + */ + @SmallTest + @Test + public void testPlaceCall_selfManaged_noPermission() throws Exception { + doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer( + eq(DEFAULT_DIALER_PACKAGE), anyInt()); + when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn( + makeSelfManagedPhoneAccount(TEL_PA_HANDLE_CURRENT).build()); + Uri handle = Uri.parse("tel:6505551234"); + Bundle extras = createSampleExtras(); + // callingPackage matches the PhoneAccountHandle, so this is an app with a self-managed + // ConnectionService. + extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT); + + doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission( + eq(Manifest.permission.MANAGE_OWN_CALLS), anyString()); + + try { + mTSIBinder.placeCall(handle, extras, PACKAGE_NAME, null); + fail("Expected SecurityException because MANAGE_OWN_CALLS is not set"); + } catch(SecurityException e) { + // expected + } + } + + /** + * In the case that there is a self-managed call request and the app doesn't own that + * PhoneAccount, we will need to check CALL_PHONE. If they do not have CALL_PHONE permission, + * we need to throw a security exception. + */ + @SmallTest + @Test + public void testPlaceCall_selfManaged_permissionFail() throws Exception { + doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer( + eq(DEFAULT_DIALER_PACKAGE), anyInt()); + when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn( + makeSelfManagedPhoneAccount(TEL_PA_HANDLE_CURRENT).build()); + Uri handle = Uri.parse("tel:6505551234"); + Bundle extras = createSampleExtras(); + // callingPackage doesn't match the PhoneAccountHandle package + extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT); + + // pass MANAGE_OWN_CALLS check, but do not have CALL PHONE + doNothing().when(mContext).enforceCallingOrSelfPermission( + eq(Manifest.permission.MANAGE_OWN_CALLS), anyString()); + doReturn(PackageManager.PERMISSION_DENIED) + .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED); + doThrow(new SecurityException()) + .when(mContext).enforceCallingOrSelfPermission(eq(CALL_PHONE), anyString()); + + try { + // Calling package is received and is not the same as PACKAGE_NAME + mTSIBinder.placeCall(handle, extras, PACKAGE_NAME + "2", null); + fail("Expected a SecurityException - CALL_PHONE was not granted"); + } catch(SecurityException e) { + // expected + } + } + + /** + * In the case that there is a self-managed call request and the app doesn't own that + * PhoneAccount, we will need to check CALL_PHONE. If they have the CALL_PHONE permission, but + * the app op has been denied, this should throw a security exception. + */ + @SmallTest + @Test + public void testPlaceCall_selfManaged_appOpPermissionFail() throws Exception { + doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer( + eq(DEFAULT_DIALER_PACKAGE), anyInt()); + when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn( + makeSelfManagedPhoneAccount(TEL_PA_HANDLE_CURRENT).build()); + Uri handle = Uri.parse("tel:6505551234"); + Bundle extras = createSampleExtras(); + // callingPackage doesn't match the PhoneAccountHandle package. + extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT); + + // pass MANAGE_OWN_CALLS check, but do not have CALL PHONE + doNothing().when(mContext).enforceCallingOrSelfPermission( + eq(Manifest.permission.MANAGE_OWN_CALLS), anyString()); + doNothing().when(mContext).enforceCallingOrSelfPermission(eq(CALL_PHONE), anyString()); + doReturn(PackageManager.PERMISSION_DENIED) + .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED); + when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(), + nullable(String.class), nullable(String.class))) + .thenReturn(AppOpsManager.MODE_ERRORED); + try { + mTSIBinder.placeCall(handle, extras, PACKAGE_NAME + "2", null); + fail("Expected a SecurityException - CALL_PHONE app op is denied"); + } catch(SecurityException e) { + // expected + } + } + + /** + * In the case that there is a self-managed call request and the app doesn't own that + * PhoneAccount, we will need to check CALL_PHONE. If they have the correct permissions, the + * call will go through, however we will have removed the self-managed PhoneAccountHandle. The + * call will go through as a normal managed call request with no PhoneAccountHandle. + */ + @SmallTest + @Test + public void testPlaceCall_selfManaged_differentCallingPackage() throws Exception { + doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer( + eq(DEFAULT_DIALER_PACKAGE), anyInt()); + when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn( + makeSelfManagedPhoneAccount(TEL_PA_HANDLE_CURRENT).build()); + Uri handle = Uri.parse("tel:6505551234"); + Bundle extras = createSampleExtras(); + // callingPackage doesn't match the PhoneAccountHandle package + extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT); + + // simulate default dialer so CALL_PHONE is granted. + when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(), + nullable(String.class), nullable(String.class))) + .thenReturn(AppOpsManager.MODE_ALLOWED); + doReturn(PackageManager.PERMISSION_GRANTED) + .when(mContext).checkCallingOrSelfPermission(CALL_PHONE); + doReturn(PackageManager.PERMISSION_DENIED) + .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED); + + // We expect the call to go through with no PhoneAccount specified, since the request + // contained a self-managed PhoneAccountHandle that didn't belong to this app. + Bundle expectedExtras = extras.deepCopy(); + expectedExtras.remove(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE); + try { + mTSIBinder.placeCall(handle, extras, DEFAULT_DIALER_PACKAGE, null); + } catch (SecurityException e) { + fail("Unexpected SecurityException - CTS is default dialer and MANAGE_OWN_CALLS is not" + + " required. Exception: " + e); + } + placeCallTestHelper(handle, extras, /*isSelfManagedExpected*/ false, + /*shouldNonEmergencyBeAllowed*/ true); + } + + /** + * In the case that there is a managed call request and the app owns that + * PhoneAccount (but is not a self-managed), we will still need to check CALL_PHONE. + */ + @SmallTest + @Test + public void testPlaceCall_samePackage_managedPhoneAccount_permissionFail() throws Exception { + doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer( + eq(DEFAULT_DIALER_PACKAGE), anyInt()); + when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn( + makePhoneAccount(TEL_PA_HANDLE_CURRENT).build()); + Uri handle = Uri.parse("tel:6505551234"); + Bundle extras = createSampleExtras(); + // callingPackage doesn't match the PhoneAccountHandle package + extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT); + + // CALL_PHONE is not granted to the device. + doThrow(new SecurityException()) + .when(mContext).enforceCallingOrSelfPermission( + eq(Manifest.permission.MANAGE_OWN_CALLS), anyString()); + doReturn(PackageManager.PERMISSION_DENIED) + .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED); + doThrow(new SecurityException()) + .when(mContext).enforceCallingOrSelfPermission(eq(CALL_PHONE), anyString()); + + try { + mTSIBinder.placeCall(handle, extras, PACKAGE_NAME + "2", null); + fail("Expected a SecurityException - CALL_PHONE is not granted"); + } catch(SecurityException e) { + // expected + } + } + + /** + * In the case that there is a managed call request and the app owns that + * PhoneAccount (but is not a self-managed), we will still need to check CALL_PHONE. + */ + @SmallTest + @Test + public void testPlaceCall_samePackage_managedPhoneAccount_AppOpFail() throws Exception { + doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer( + eq(DEFAULT_DIALER_PACKAGE), anyInt()); + when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn( + makePhoneAccount(TEL_PA_HANDLE_CURRENT).build()); + Uri handle = Uri.parse("tel:6505551234"); + Bundle extras = createSampleExtras(); + // callingPackage matches the PhoneAccountHandle, but this is not a self managed phone + // account. + extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT); + + // CALL_PHONE is granted, but the app op is not + doThrow(new SecurityException()) + .when(mContext).enforceCallingOrSelfPermission( + eq(Manifest.permission.MANAGE_OWN_CALLS), anyString()); + doReturn(PackageManager.PERMISSION_DENIED) + .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED); + doNothing().when(mContext).enforceCallingOrSelfPermission(eq(CALL_PHONE), anyString()); + when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(), + nullable(String.class), nullable(String.class))) + .thenReturn(AppOpsManager.MODE_ERRORED); + + try { + mTSIBinder.placeCall(handle, extras, PACKAGE_NAME + "2", null); + fail("Expected a SecurityException - CALL_PHONE app op is denied"); + } catch(SecurityException e) { + // expected + } + } + + /** + * Since this is a self-managed call being requested, so ensure we report the call as + * self-managed and without non-emergency permissions. + */ + @SmallTest + @Test + public void testPlaceCall_selfManaged_nonEmergencyPermission() throws Exception { + doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer( + eq(DEFAULT_DIALER_PACKAGE), anyInt()); + when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn( + makeSelfManagedPhoneAccount(TEL_PA_HANDLE_CURRENT).build()); + Uri handle = Uri.parse("tel:6505551234"); + Bundle extras = createSampleExtras(); + // callingPackage matches the PhoneAccountHandle, so this is an app with a self-managed + // ConnectionService. + extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT); + + // enforceCallingOrSelfPermission is implicitly granted for MANAGE_OWN_CALLS here and + // CALL_PHONE is not required. + when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(), + nullable(String.class), nullable(String.class))) + .thenReturn(AppOpsManager.MODE_IGNORED); + doReturn(PackageManager.PERMISSION_DENIED) + .when(mContext).checkCallingOrSelfPermission(CALL_PHONE); + doReturn(PackageManager.PERMISSION_DENIED) + .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED); + + mTSIBinder.placeCall(handle, extras, PACKAGE_NAME, null); + placeCallTestHelper(handle, extras, /*isSelfManagedExpected*/ true, + /*shouldNonEmergencyBeAllowed*/ false); + } + + /** + * Default dialer is calling placeCall and has CALL_PHONE granted, so non-emergency calls + * are allowed. + */ + @SmallTest + @Test + public void testPlaceCall_managed_nonEmergencyGranted() throws Exception { + doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer( + eq(DEFAULT_DIALER_PACKAGE), anyInt()); + Uri handle = Uri.parse("tel:6505551234"); + Bundle extras = createSampleExtras(); + // callingPackage doesn't match the PhoneAccountHandle, so this app does not have a + // self-managed ConnectionService + extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT); + + // CALL_PHONE granted + when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(), + nullable(String.class), nullable(String.class))) + .thenReturn(AppOpsManager.MODE_ALLOWED); + doReturn(PackageManager.PERMISSION_GRANTED) + .when(mContext).checkCallingOrSelfPermission(CALL_PHONE); + doReturn(PackageManager.PERMISSION_DENIED) + .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED); + + mTSIBinder.placeCall(handle, extras, DEFAULT_DIALER_PACKAGE, null); + placeCallTestHelper(handle, extras, /*isSelfManagedExpected*/ false, + /*shouldNonEmergencyBeAllowed*/ true); + } + + /** + * In the case that there is a managed normal call request and the app has CALL_PRIVILEGED + * permission, place call should complete successfully. + */ + @SmallTest + @Test + public void testPlaceCallPrivileged() throws Exception { + doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer( + eq(DEFAULT_DIALER_PACKAGE), anyInt()); + Uri handle = Uri.parse("tel:6505551234"); + + // CALL_PHONE is not granted, but CALL_PRIVILEGED is + doThrow(new SecurityException()) + .when(mContext).enforceCallingOrSelfPermission( + eq(Manifest.permission.MANAGE_OWN_CALLS), anyString()); + doReturn(PackageManager.PERMISSION_GRANTED) + .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED); + doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission( + eq(CALL_PHONE), anyString()); + when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(), + nullable(String.class), nullable(String.class))) + .thenReturn(AppOpsManager.MODE_ERRORED); + + try { + mTSIBinder.placeCall(handle, null, PACKAGE_NAME + "2", null); + } catch(SecurityException e) { + fail("Expected no SecurityException - CALL_PRIVILEGED is granted"); + } + ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(mUserCallIntentProcessor).processIntent(intentCaptor.capture(), anyString(), + eq(false), eq(true), eq(true)); + Intent capturedIntent = intentCaptor.getValue(); + assertEquals(Intent.ACTION_CALL_PRIVILEGED, capturedIntent.getAction()); + assertEquals(handle, capturedIntent.getData()); + } + + /** + * The default dialer is requesting to place a call and CALL_PHONE is granted, however + * OP_CALL_PHONE app op is denied to that app, so non-emergency calls will be denied. + */ @SmallTest @Test public void testPlaceCallWithAppOpsOff() throws Exception { Uri handle = Uri.parse("tel:6505551234"); Bundle extras = createSampleExtras(); + // We have passed in the DEFAULT_DIALER_PACKAGE for this test, so canCallPhone is always + // true. when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(), nullable(String.class), nullable(String.class))) .thenReturn(AppOpsManager.MODE_IGNORED); doReturn(PackageManager.PERMISSION_GRANTED) - .when(mContext).checkCallingPermission(CALL_PHONE); + .when(mContext).checkCallingOrSelfPermission(CALL_PHONE); doReturn(PackageManager.PERMISSION_DENIED) - .when(mContext).checkCallingPermission(CALL_PRIVILEGED); + .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED); mTSIBinder.placeCall(handle, extras, DEFAULT_DIALER_PACKAGE, null); - placeCallTestHelper(handle, extras, false); + placeCallTestHelper(handle, extras, /*isSelfManagedExpected*/ false, + /*shouldNonEmergencyBeAllowed*/ false); } + /** + * The default dialer is requesting to place a call, however CALL_PHONE is denied to that app, + * so non-emergency calls will be denied. + */ @SmallTest @Test public void testPlaceCallWithNoCallingPermission() throws Exception { Uri handle = Uri.parse("tel:6505551234"); Bundle extras = createSampleExtras(); + // We are assumed to be default dialer in this test, so canCallPhone is always true. when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(), nullable(String.class), nullable(String.class))) .thenReturn(AppOpsManager.MODE_ALLOWED); doReturn(PackageManager.PERMISSION_DENIED) - .when(mContext).checkCallingPermission(CALL_PHONE); + .when(mContext).checkCallingOrSelfPermission(CALL_PHONE); doReturn(PackageManager.PERMISSION_DENIED) - .when(mContext).checkCallingPermission(CALL_PRIVILEGED); + .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED); mTSIBinder.placeCall(handle, extras, DEFAULT_DIALER_PACKAGE, null); - placeCallTestHelper(handle, extras, false); + placeCallTestHelper(handle, extras, /*isSelfManagedExpected*/ false, + /*shouldNonEmergencyBeAllowed*/ false); } + /** + * Ensure the expected handle, extras, and non-emergency call permission checks have been + * correctly included in the ACTION_CALL intent as part of the + * {@link UserCallIntentProcessor#processIntent} method called during the placeCall procedure. + * @param expectedHandle Expected outgoing number handle + * @param expectedExtras Expected extras in the ACTION_CALL intent. + * @param shouldNonEmergencyBeAllowed true if non-emergency calls should be allowed, false if + * permission checks failed for non-emergency. + */ private void placeCallTestHelper(Uri expectedHandle, Bundle expectedExtras, - boolean shouldNonEmergencyBeAllowed) { + boolean isSelfManagedExpected, boolean shouldNonEmergencyBeAllowed) { ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mUserCallIntentProcessor).processIntent(intentCaptor.capture(), anyString(), - eq(shouldNonEmergencyBeAllowed), eq(true)); + eq(isSelfManagedExpected), eq(shouldNonEmergencyBeAllowed), eq(true)); Intent capturedIntent = intentCaptor.getValue(); assertEquals(Intent.ACTION_CALL, capturedIntent.getAction()); assertEquals(expectedHandle, capturedIntent.getData()); assertTrue(areBundlesEqual(expectedExtras, capturedIntent.getExtras())); } + /** + * Ensure that if the caller was never granted CALL_PHONE (and is not the default dialer), a + * SecurityException is thrown. + */ @SmallTest @Test public void testPlaceCallFailure() throws Exception { Uri handle = Uri.parse("tel:6505551234"); Bundle extras = createSampleExtras(); + // The app is not considered a privileged dialer and does not have the CALL_PHONE + // permission. doThrow(new SecurityException()) .when(mContext).enforceCallingOrSelfPermission(eq(CALL_PHONE), anyString()); + doReturn(PackageManager.PERMISSION_DENIED) + .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED); + + try { + mTSIBinder.placeCall(handle, extras, "arbitrary_package_name", null); + fail("Expected SecurityException because CALL_PHONE was not granted to caller"); + } catch (SecurityException e) { + // expected + } + + verify(mUserCallIntentProcessor, never()) + .processIntent(any(Intent.class), anyString(), eq(false), anyBoolean(), eq(true)); + } + + /** + * Ensure that if the caller was granted CALL_PHONE, but did not get the OP_CALL_PHONE app op + * (and is not the default dialer), a SecurityException is thrown. + */ + @SmallTest + @Test + public void testPlaceCallAppOpFailure() throws Exception { + Uri handle = Uri.parse("tel:6505551234"); + Bundle extras = createSampleExtras(); + + // The app is not considered a privileged dialer and does not have the OP_CALL_PHONE + // app op. + doNothing().when(mContext).enforceCallingOrSelfPermission(eq(CALL_PHONE), anyString()); + doReturn(PackageManager.PERMISSION_DENIED) + .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED); + when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(), + nullable(String.class), nullable(String.class))) + .thenReturn(AppOpsManager.MODE_IGNORED); try { mTSIBinder.placeCall(handle, extras, "arbitrary_package_name", null); + fail("Expected SecurityException because CALL_PHONE was not granted to caller"); } catch (SecurityException e) { // expected } verify(mUserCallIntentProcessor, never()) - .processIntent(any(Intent.class), anyString(), anyBoolean(), eq(true)); + .processIntent(any(Intent.class), anyString(), eq(false), anyBoolean(), eq(true)); } @SmallTest @@ -1090,6 +1907,29 @@ public class TelecomServiceImplTest extends TelecomTestCase { @SmallTest @Test + public void testGetDefaultDialerPackageForUser() throws Exception { + final int userId = 1; + final String packageName = "some.package"; + + when(mDefaultDialerCache.getDefaultDialerApplication(userId)) + .thenReturn(packageName); + + assertEquals(packageName, mTSIBinder.getDefaultDialerPackageForUser(userId)); + } + + @SmallTest + @Test + public void testGetSystemDialerPackage() throws Exception { + final String packageName = "some.package"; + + when(mDefaultDialerCache.getSystemDialerApplication()) + .thenReturn(packageName); + + assertEquals(packageName, mTSIBinder.getSystemDialerPackage(CALLING_PACKAGE)); + } + + @SmallTest + @Test public void testEndCallWithRingingForegroundCall() throws Exception { Call call = mock(Call.class); when(call.getState()).thenReturn(CallState.RINGING); @@ -1123,8 +1963,7 @@ public class TelecomServiceImplTest extends TelecomTestCase { public void testEndCallWithNoForegroundCall() throws Exception { Call call = mock(Call.class); when(call.getState()).thenReturn(CallState.ACTIVE); - when(mFakeCallsManager.getFirstCallWithState(any())) - .thenReturn(call); + when(mFakeCallsManager.getFirstCallWithState(any())).thenReturn(call); assertTrue(mTSIBinder.endCall(TEST_PACKAGE)); verify(mFakeCallsManager).disconnectCall(eq(call)); } @@ -1165,14 +2004,16 @@ public class TelecomServiceImplTest extends TelecomTestCase { @SmallTest @Test public void testIsInCall() throws Exception { - when(mFakeCallsManager.hasOngoingCalls()).thenReturn(true); + when(mFakeCallsManager.hasOngoingCalls(any(UserHandle.class), anyBoolean())) + .thenReturn(true); assertTrue(mTSIBinder.isInCall(DEFAULT_DIALER_PACKAGE, null)); } @SmallTest @Test public void testNotIsInCall() throws Exception { - when(mFakeCallsManager.hasOngoingCalls()).thenReturn(false); + when(mFakeCallsManager.hasOngoingCalls(any(UserHandle.class), anyBoolean())) + .thenReturn(false); assertFalse(mTSIBinder.isInCall(DEFAULT_DIALER_PACKAGE, null)); } @@ -1187,20 +2028,22 @@ public class TelecomServiceImplTest extends TelecomTestCase { } catch (SecurityException e) { // desired result } - verify(mFakeCallsManager, never()).hasOngoingCalls(); + verify(mFakeCallsManager, never()).hasOngoingCalls(any(UserHandle.class), anyBoolean()); } @SmallTest @Test public void testIsInManagedCall() throws Exception { - when(mFakeCallsManager.hasOngoingManagedCalls()).thenReturn(true); + when(mFakeCallsManager.hasOngoingManagedCalls(any(UserHandle.class), anyBoolean())) + .thenReturn(true); assertTrue(mTSIBinder.isInManagedCall(DEFAULT_DIALER_PACKAGE, null)); } @SmallTest @Test public void testNotIsInManagedCall() throws Exception { - when(mFakeCallsManager.hasOngoingManagedCalls()).thenReturn(false); + when(mFakeCallsManager.hasOngoingManagedCalls(any(UserHandle.class), anyBoolean())) + .thenReturn(false); assertFalse(mTSIBinder.isInManagedCall(DEFAULT_DIALER_PACKAGE, null)); } @@ -1215,7 +2058,7 @@ public class TelecomServiceImplTest extends TelecomTestCase { } catch (SecurityException e) { // desired result } - verify(mFakeCallsManager, never()).hasOngoingCalls(); + verify(mFakeCallsManager, never()).hasOngoingCalls(any(UserHandle.class), anyBoolean()); } /** @@ -1251,6 +2094,22 @@ public class TelecomServiceImplTest extends TelecomTestCase { verify(mFakeCallsManager, never()).answerCall(eq(call), anyInt()); } + @SmallTest + @Test + public void testGetAdnUriForPhoneAccount() throws Exception { + final int subId = 1; + final Uri adnUri = Uri.parse("content://icc/adn/subId/" + subId); + PhoneAccount phoneAccount = makePhoneAccount(TEL_PA_HANDLE_CURRENT).build(); + when(mFakePhoneAccountRegistrar.getPhoneAccount( + eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class))) + .thenReturn(phoneAccount); + when(mFakePhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(TEL_PA_HANDLE_CURRENT)) + .thenReturn(subId); + + assertEquals(adnUri, + mTSIBinder.getAdnUriForPhoneAccount(TEL_PA_HANDLE_CURRENT, DEFAULT_DIALER_PACKAGE)); + } + /** * Register phone accounts for the supplied PhoneAccountHandles to make them * visible to all users (via the isVisibleToCaller method in TelecomServiceImpl. @@ -1275,6 +2134,12 @@ public class TelecomServiceImplTest extends TelecomTestCase { return paBuilder; } + private PhoneAccount.Builder makeSelfManagedPhoneAccount(PhoneAccountHandle paHandle) { + PhoneAccount.Builder paBuilder = makePhoneAccount(paHandle); + paBuilder.setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED); + return paBuilder; + } + private PhoneAccount.Builder makePhoneAccount(PhoneAccountHandle paHandle) { return new PhoneAccount.Builder(paHandle, "testLabel"); } @@ -1286,6 +2151,8 @@ public class TelecomServiceImplTest extends TelecomTestCase { } private static boolean areBundlesEqual(Bundle b1, Bundle b2) { + if (b1.keySet().size() != b2.keySet().size()) return false; + for (String key1 : b1.keySet()) { if (!b1.get(key1).equals(b2.get(key1))) { return false; |