Loading src/com/android/server/telecom/PhoneAccountRegistrar.java +97 −0 Original line number Diff line number Diff line Loading @@ -452,6 +452,31 @@ public class PhoneAccountRegistrar { return retval; } /** * Loops through all SIM accounts ({@link #getSimPhoneAccounts}) and returns those with SIM call * manager components specified in carrier config that match {@code simCallManagerHandle}. * * <p>Note that this will return handles even when {@code simCallManagerHandle} has not yet been * registered or was recently unregistered. * * <p>If the given {@code simCallManagerHandle} is not the SIM call manager for any active SIMs, * returns an empty list. */ public @NonNull List<PhoneAccountHandle> getSimPhoneAccountsFromSimCallManager( @NonNull PhoneAccountHandle simCallManagerHandle) { List<PhoneAccountHandle> matchingSimHandles = new ArrayList<>(); for (PhoneAccountHandle simHandle : getSimPhoneAccounts(simCallManagerHandle.getUserHandle())) { ComponentName simCallManager = getSystemSimCallManagerComponent(getSubscriptionIdForPhoneAccount(simHandle)); if (simCallManager == null) continue; if (simCallManager.equals(simCallManagerHandle.getComponentName())) { matchingSimHandles.add(simHandle); } } return matchingSimHandles; } /** * Sets a filter for which {@link PhoneAccount}s will be returned from * {@link #filterRestrictedPhoneAccounts(List)}. If non-null, only {@link PhoneAccount}s Loading Loading @@ -866,6 +891,9 @@ public class PhoneAccountRegistrar { } else { fireAccountChanged(account); } // If this is the SIM call manager, tell telephony when the voice ServiceState override // needs to be updated. maybeNotifyTelephonyForVoiceServiceState(account, /* registered= */ true); } public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) { Loading @@ -875,6 +903,9 @@ public class PhoneAccountRegistrar { write(); fireAccountsChanged(); fireAccountUnRegistered(accountHandle); // If this is the SIM call manager, tell telephony when the voice ServiceState // override needs to be updated. maybeNotifyTelephonyForVoiceServiceState(account, /* registered= */ false); } } } Loading Loading @@ -1017,6 +1048,72 @@ public class PhoneAccountRegistrar { } } private void maybeNotifyTelephonyForVoiceServiceState( @NonNull PhoneAccount account, boolean registered) { // TODO(b/215419665) what about SIM_SUBSCRIPTION accounts? They could theoretically also use // these capabilities, but don't today. If they do start using them, then there will need to // be a kind of "or" logic between SIM_SUBSCRIPTION and CONNECTION_MANAGER accounts to get // the correct value of hasService for a given SIM. boolean hasService = false; List<PhoneAccountHandle> simHandlesToNotify; if (account.hasCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER)) { // When we unregister the SIM call manager account, we always set hasService back to // false since it is no longer providing OTT calling capability once unregistered. if (registered) { // Note: we do *not* early return when the SUPPORTS capability is not present // because it's possible the SIM call manager could remove either capability at // runtime and re-register. However, it is an error to use the AVAILABLE capability // without also setting SUPPORTS. hasService = account.hasCapabilities( PhoneAccount.CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS | PhoneAccount.CAPABILITY_VOICE_CALLING_AVAILABLE); } // Notify for all SIMs that named this component as their SIM call manager in carrier // config, since there may be more than one impacted SIM here. simHandlesToNotify = getSimPhoneAccountsFromSimCallManager(account.getAccountHandle()); } else if (account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) { // When new SIMs get registered, we notify them of their current voice status override. // If there is no SIM call manager for this SIM, we treat that as hasService = false and // still notify to ensure consistency. if (!registered) { // We don't do anything when SIMs are unregistered because we won't have an active // subId to map back to phoneId and tell telephony about; that case is handled by // telephony internally. return; } PhoneAccountHandle simCallManagerHandle = getSimCallManagerFromHandle( account.getAccountHandle(), account.getAccountHandle().getUserHandle()); if (simCallManagerHandle != null) { PhoneAccount simCallManager = getPhoneAccountUnchecked(simCallManagerHandle); hasService = simCallManager != null && simCallManager.hasCapabilities( PhoneAccount.CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS | PhoneAccount.CAPABILITY_VOICE_CALLING_AVAILABLE); } simHandlesToNotify = Collections.singletonList(account.getAccountHandle()); } else { // Not a relevant account - we only care about CONNECTION_MANAGER and SIM_SUBSCRIPTION. return; } if (simHandlesToNotify.isEmpty()) return; Log.i( this, "Notifying telephony of voice service override change for %d SIMs, hasService = %b", simHandlesToNotify.size(), hasService); TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); for (PhoneAccountHandle simHandle : simHandlesToNotify) { // This may be null if there are no active SIMs but the device is still camped for // emergency calls and registered a SIM_SUBSCRIPTION for that purpose. TelephonyManager simTm = tm.createForPhoneAccountHandle(simHandle); if (simTm == null) continue; simTm.setVoiceServiceStateOverride(hasService); } } /** * Determines if the connection service specified by a {@link PhoneAccountHandle} requires the * {@link Manifest.permission#BIND_TELECOM_CONNECTION_SERVICE} permission. Loading src/com/android/server/telecom/TelecomServiceImpl.java +50 −0 Original line number Diff line number Diff line Loading @@ -573,6 +573,15 @@ public class TelecomServiceImpl { if (account.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) { enforceRegisterMultiUser(); } // These capabilities are for SIM-based accounts only, so only the platform // and carrier-designated SIM call manager can register accounts with these // capabilities. if (account.hasCapabilities( PhoneAccount.CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS) || account.hasCapabilities( PhoneAccount.CAPABILITY_VOICE_CALLING_AVAILABLE)) { enforceRegisterVoiceCallingIndicationCapabilities(account); } Bundle extras = account.getExtras(); if (extras != null && extras.getBoolean(PhoneAccount.EXTRA_SKIP_CALL_FILTERING)) { Loading Loading @@ -2447,6 +2456,24 @@ public class TelecomServiceImpl { } } private void enforceRegisterVoiceCallingIndicationCapabilities(PhoneAccount account) { // Caller must be able to register a SIM PhoneAccount or be the SIM call manager (as named // in carrier config) to declare the two voice indication capabilities. boolean prerequisiteCapabilitiesOk = account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION) || account.hasCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER); boolean permissionsOk = isCallerSimCallManagerForAnySim(account.getAccountHandle()) || mContext.checkCallingOrSelfPermission(REGISTER_SIM_SUBSCRIPTION) == PackageManager.PERMISSION_GRANTED; if (!prerequisiteCapabilitiesOk || !permissionsOk) { throw new SecurityException( "Only SIM subscriptions and connection managers are allowed to declare " + "CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS and " + "CAPABILITY_VOICE_CALLING_AVAILABLE"); } } private void enforceRegisterSkipCallFiltering() { if (!isCallerSystemApp()) { throw new SecurityException( Loading Loading @@ -2656,6 +2683,29 @@ public class TelecomServiceImpl { return false; } /** * Similar to {@link #isCallerSimCallManager}, but works for all SIMs and does not require * {@code accountHandle} to be registered yet. */ private boolean isCallerSimCallManagerForAnySim(PhoneAccountHandle accountHandle) { if (isCallerSimCallManager(accountHandle)) { // The caller has already registered a CONNECTION_MANAGER PhoneAccount, so let them pass // (this allows the SIM call manager through in case of SIM switches, where carrier // config may be in a transient state) return true; } // If the caller isn't already registered, then we have to look at the active PSTN // PhoneAccounts and check their carrier configs to see if any point to this one's component final long token = Binder.clearCallingIdentity(); try { return !mPhoneAccountRegistrar .getSimPhoneAccountsFromSimCallManager(accountHandle) .isEmpty(); } finally { Binder.restoreCallingIdentity(token); } } private boolean isPrivilegedDialerCalling(String callingPackage) { mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage); Loading tests/src/com/android/server/telecom/tests/ComponentContextFixture.java +4 −0 Original line number Diff line number Diff line Loading @@ -739,6 +739,10 @@ public class ComponentContextFixture implements TestFixture<Context> { return mTelephonyManager; } public CarrierConfigManager getCarrierConfigManager() { return mCarrierConfigManager; } public NotificationManager getNotificationManager() { return mNotificationManager; } Loading tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java +213 −0 Original line number Diff line number Diff line Loading @@ -22,10 +22,16 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageInfo; Loading @@ -36,6 +42,7 @@ import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Bundle; import android.os.Parcel; import android.os.PersistableBundle; import android.os.Process; import android.os.UserHandle; import android.os.UserManager; Loading @@ -43,6 +50,7 @@ import android.telecom.Log; import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; import android.telephony.CarrierConfigManager; import android.test.suitebuilder.annotation.SmallTest; import android.test.suitebuilder.annotation.MediumTest; import android.util.Xml; Loading Loading @@ -1066,6 +1074,156 @@ public class PhoneAccountRegistrarTest extends TelecomTestCase { assertEquals(1, deletedAccounts); } @Test public void testGetSimPhoneAccountsFromSimCallManager() throws Exception { // Register the SIM PhoneAccounts mComponentContextFixture.addConnectionService( makeQuickConnectionServiceComponentName(), Mockito.mock(IConnectionService.class)); PhoneAccount sim1Account = makeQuickSimAccount(1); PhoneAccountHandle sim1Handle = sim1Account.getAccountHandle(); registerAndEnableAccount(sim1Account); PhoneAccount sim2Account = makeQuickSimAccount(2); PhoneAccountHandle sim2Handle = sim2Account.getAccountHandle(); registerAndEnableAccount(sim2Account); assertEquals( List.of(sim1Handle, sim2Handle), mRegistrar.getSimPhoneAccountsOfCurrentUser()); // Set up the SIM call manager app + carrier configs ComponentName simCallManagerComponent = new ComponentName("com.carrier.app", "CarrierConnectionService"); PhoneAccountHandle simCallManagerHandle = makeQuickAccountHandle(simCallManagerComponent, "sim-call-manager"); setSimCallManagerCarrierConfig( 1, new ComponentName("com.other.carrier", "OtherConnectionService")); setSimCallManagerCarrierConfig(2, simCallManagerComponent); // Since SIM 1 names another app, so we only get the handle for SIM 2 assertEquals( List.of(sim2Handle), mRegistrar.getSimPhoneAccountsFromSimCallManager(simCallManagerHandle)); // We do exact component matching, not just package name matching assertEquals( List.of(), mRegistrar.getSimPhoneAccountsFromSimCallManager( makeQuickAccountHandle( new ComponentName("com.carrier.app", "SomeOtherUnrelatedService"), "same-pkg-but-diff-svc"))); // Results are identical after we register the PhoneAccount mComponentContextFixture.addConnectionService( simCallManagerComponent, Mockito.mock(IConnectionService.class)); PhoneAccount simCallManagerAccount = new PhoneAccount.Builder(simCallManagerHandle, "SIM call manager") .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER) .build(); mRegistrar.registerPhoneAccount(simCallManagerAccount); assertEquals( List.of(sim2Handle), mRegistrar.getSimPhoneAccountsFromSimCallManager(simCallManagerHandle)); } @Test public void testMaybeNotifyTelephonyForVoiceServiceState() throws Exception { // Register the SIM PhoneAccounts mComponentContextFixture.addConnectionService( makeQuickConnectionServiceComponentName(), Mockito.mock(IConnectionService.class)); PhoneAccount sim1Account = makeQuickSimAccount(1); registerAndEnableAccount(sim1Account); PhoneAccount sim2Account = makeQuickSimAccount(2); registerAndEnableAccount(sim2Account); // Telephony is notified by default when new SIM accounts are registered verify(mComponentContextFixture.getTelephonyManager(), times(2)) .setVoiceServiceStateOverride(false); clearInvocations(mComponentContextFixture.getTelephonyManager()); // Set up the SIM call manager app + carrier configs ComponentName simCallManagerComponent = new ComponentName("com.carrier.app", "CarrierConnectionService"); PhoneAccountHandle simCallManagerHandle = makeQuickAccountHandle(simCallManagerComponent, "sim-call-manager"); mComponentContextFixture.addConnectionService( simCallManagerComponent, Mockito.mock(IConnectionService.class)); setSimCallManagerCarrierConfig(1, simCallManagerComponent); setSimCallManagerCarrierConfig(2, simCallManagerComponent); // When the SIM call manager is registered without the SUPPORTS capability, telephony is // still notified for consistency (e.g. runtime capability removal + re-registration). PhoneAccount simCallManagerAccount = new PhoneAccount.Builder(simCallManagerHandle, "SIM call manager") .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER) .build(); mRegistrar.registerPhoneAccount(simCallManagerAccount); verify(mComponentContextFixture.getTelephonyManager(), times(2)) .setVoiceServiceStateOverride(false); clearInvocations(mComponentContextFixture.getTelephonyManager()); // Adding the SUPPORTS capability causes the SIMs to get notified with false again for // consistency purposes simCallManagerAccount = copyPhoneAccountAndAddCapabilities( simCallManagerAccount, PhoneAccount.CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS); mRegistrar.registerPhoneAccount(simCallManagerAccount); verify(mComponentContextFixture.getTelephonyManager(), times(2)) .setVoiceServiceStateOverride(false); clearInvocations(mComponentContextFixture.getTelephonyManager()); // Adding the AVAILABLE capability updates the SIMs again, this time with hasService = true simCallManagerAccount = copyPhoneAccountAndAddCapabilities( simCallManagerAccount, PhoneAccount.CAPABILITY_VOICE_CALLING_AVAILABLE); mRegistrar.registerPhoneAccount(simCallManagerAccount); verify(mComponentContextFixture.getTelephonyManager(), times(2)) .setVoiceServiceStateOverride(true); clearInvocations(mComponentContextFixture.getTelephonyManager()); // Removing a SIM account does nothing, regardless of SIM call manager capabilities mRegistrar.unregisterPhoneAccount(sim1Account.getAccountHandle()); verify(mComponentContextFixture.getTelephonyManager(), never()) .setVoiceServiceStateOverride(anyBoolean()); clearInvocations(mComponentContextFixture.getTelephonyManager()); // Adding a SIM account while a SIM call manager with both capabilities is registered causes // a call to telephony with hasService = true mRegistrar.registerPhoneAccount(sim1Account); verify(mComponentContextFixture.getTelephonyManager(), times(1)) .setVoiceServiceStateOverride(true); clearInvocations(mComponentContextFixture.getTelephonyManager()); // Removing the SIM call manager while it has both capabilities causes a call to telephony // with hasService = false mRegistrar.unregisterPhoneAccount(simCallManagerHandle); verify(mComponentContextFixture.getTelephonyManager(), times(2)) .setVoiceServiceStateOverride(false); clearInvocations(mComponentContextFixture.getTelephonyManager()); // Removing the SIM call manager while it has the SUPPORTS capability but not AVAILABLE // still causes a call to telephony with hasService = false for consistency simCallManagerAccount = copyPhoneAccountAndRemoveCapabilities( simCallManagerAccount, PhoneAccount.CAPABILITY_VOICE_CALLING_AVAILABLE); mRegistrar.registerPhoneAccount(simCallManagerAccount); clearInvocations(mComponentContextFixture.getTelephonyManager()); // from re-registration mRegistrar.unregisterPhoneAccount(simCallManagerHandle); verify(mComponentContextFixture.getTelephonyManager(), times(2)) .setVoiceServiceStateOverride(false); clearInvocations(mComponentContextFixture.getTelephonyManager()); // Finally, removing the SIM call manager while it has neither capability still causes a // call to telephony with hasService = false for consistency simCallManagerAccount = copyPhoneAccountAndRemoveCapabilities( simCallManagerAccount, PhoneAccount.CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS); mRegistrar.registerPhoneAccount(simCallManagerAccount); clearInvocations(mComponentContextFixture.getTelephonyManager()); // from re-registration mRegistrar.unregisterPhoneAccount(simCallManagerHandle); verify(mComponentContextFixture.getTelephonyManager(), times(2)) .setVoiceServiceStateOverride(false); clearInvocations(mComponentContextFixture.getTelephonyManager()); } private static ComponentName makeQuickConnectionServiceComponentName() { return new ComponentName( "com.android.server.telecom.tests", Loading @@ -1086,6 +1244,23 @@ public class PhoneAccountRegistrarTest extends TelecomTestCase { "label" + idx); } private static PhoneAccount copyPhoneAccountAndOverrideCapabilities( PhoneAccount base, int newCapabilities) { return base.toBuilder().setCapabilities(newCapabilities).build(); } private static PhoneAccount copyPhoneAccountAndAddCapabilities( PhoneAccount base, int capabilitiesToAdd) { return copyPhoneAccountAndOverrideCapabilities( base, base.getCapabilities() | capabilitiesToAdd); } private static PhoneAccount copyPhoneAccountAndRemoveCapabilities( PhoneAccount base, int capabilitiesToRemove) { return copyPhoneAccountAndOverrideCapabilities( base, base.getCapabilities() & ~capabilitiesToRemove); } private PhoneAccount makeQuickAccount(String id, int idx) { return makeQuickAccountBuilder(id, idx) .setAddress(Uri.parse("http://foo.com/" + idx)) Loading @@ -1098,6 +1273,44 @@ public class PhoneAccountRegistrarTest extends TelecomTestCase { .build(); } /** * Similar to {@link #makeQuickAccount}, but also hooks up {@code TelephonyManager} so that it * returns {@code simId} as the account's subscriptionId. */ private PhoneAccount makeQuickSimAccount(int simId) { PhoneAccount simAccount = makeQuickAccountBuilder("sim" + simId, simId) .setCapabilities( PhoneAccount.CAPABILITY_CALL_PROVIDER | PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION) .setIsEnabled(true) .build(); when(mComponentContextFixture .getTelephonyManager() .getSubscriptionId(simAccount.getAccountHandle())) .thenReturn(simId); // mComponentContextFixture already sets up the createForSubscriptionId self-reference when(mComponentContextFixture .getTelephonyManager() .createForPhoneAccountHandle(simAccount.getAccountHandle())) .thenReturn(mComponentContextFixture.getTelephonyManager()); return simAccount; } /** * Hooks up carrier config to point to {@code simCallManagerComponent} for the given {@code * subscriptionId}. */ private void setSimCallManagerCarrierConfig( int subscriptionId, @Nullable ComponentName simCallManagerComponent) { PersistableBundle config = new PersistableBundle(); config.putString( CarrierConfigManager.KEY_DEFAULT_SIM_CALL_MANAGER_STRING, simCallManagerComponent != null ? simCallManagerComponent.flattenToString() : null); when(mComponentContextFixture.getCarrierConfigManager().getConfigForSubId(subscriptionId)) .thenReturn(config); } private static void roundTripPhoneAccount(PhoneAccount original) throws Exception { PhoneAccount copy = null; Loading Loading
src/com/android/server/telecom/PhoneAccountRegistrar.java +97 −0 Original line number Diff line number Diff line Loading @@ -452,6 +452,31 @@ public class PhoneAccountRegistrar { return retval; } /** * Loops through all SIM accounts ({@link #getSimPhoneAccounts}) and returns those with SIM call * manager components specified in carrier config that match {@code simCallManagerHandle}. * * <p>Note that this will return handles even when {@code simCallManagerHandle} has not yet been * registered or was recently unregistered. * * <p>If the given {@code simCallManagerHandle} is not the SIM call manager for any active SIMs, * returns an empty list. */ public @NonNull List<PhoneAccountHandle> getSimPhoneAccountsFromSimCallManager( @NonNull PhoneAccountHandle simCallManagerHandle) { List<PhoneAccountHandle> matchingSimHandles = new ArrayList<>(); for (PhoneAccountHandle simHandle : getSimPhoneAccounts(simCallManagerHandle.getUserHandle())) { ComponentName simCallManager = getSystemSimCallManagerComponent(getSubscriptionIdForPhoneAccount(simHandle)); if (simCallManager == null) continue; if (simCallManager.equals(simCallManagerHandle.getComponentName())) { matchingSimHandles.add(simHandle); } } return matchingSimHandles; } /** * Sets a filter for which {@link PhoneAccount}s will be returned from * {@link #filterRestrictedPhoneAccounts(List)}. If non-null, only {@link PhoneAccount}s Loading Loading @@ -866,6 +891,9 @@ public class PhoneAccountRegistrar { } else { fireAccountChanged(account); } // If this is the SIM call manager, tell telephony when the voice ServiceState override // needs to be updated. maybeNotifyTelephonyForVoiceServiceState(account, /* registered= */ true); } public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) { Loading @@ -875,6 +903,9 @@ public class PhoneAccountRegistrar { write(); fireAccountsChanged(); fireAccountUnRegistered(accountHandle); // If this is the SIM call manager, tell telephony when the voice ServiceState // override needs to be updated. maybeNotifyTelephonyForVoiceServiceState(account, /* registered= */ false); } } } Loading Loading @@ -1017,6 +1048,72 @@ public class PhoneAccountRegistrar { } } private void maybeNotifyTelephonyForVoiceServiceState( @NonNull PhoneAccount account, boolean registered) { // TODO(b/215419665) what about SIM_SUBSCRIPTION accounts? They could theoretically also use // these capabilities, but don't today. If they do start using them, then there will need to // be a kind of "or" logic between SIM_SUBSCRIPTION and CONNECTION_MANAGER accounts to get // the correct value of hasService for a given SIM. boolean hasService = false; List<PhoneAccountHandle> simHandlesToNotify; if (account.hasCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER)) { // When we unregister the SIM call manager account, we always set hasService back to // false since it is no longer providing OTT calling capability once unregistered. if (registered) { // Note: we do *not* early return when the SUPPORTS capability is not present // because it's possible the SIM call manager could remove either capability at // runtime and re-register. However, it is an error to use the AVAILABLE capability // without also setting SUPPORTS. hasService = account.hasCapabilities( PhoneAccount.CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS | PhoneAccount.CAPABILITY_VOICE_CALLING_AVAILABLE); } // Notify for all SIMs that named this component as their SIM call manager in carrier // config, since there may be more than one impacted SIM here. simHandlesToNotify = getSimPhoneAccountsFromSimCallManager(account.getAccountHandle()); } else if (account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) { // When new SIMs get registered, we notify them of their current voice status override. // If there is no SIM call manager for this SIM, we treat that as hasService = false and // still notify to ensure consistency. if (!registered) { // We don't do anything when SIMs are unregistered because we won't have an active // subId to map back to phoneId and tell telephony about; that case is handled by // telephony internally. return; } PhoneAccountHandle simCallManagerHandle = getSimCallManagerFromHandle( account.getAccountHandle(), account.getAccountHandle().getUserHandle()); if (simCallManagerHandle != null) { PhoneAccount simCallManager = getPhoneAccountUnchecked(simCallManagerHandle); hasService = simCallManager != null && simCallManager.hasCapabilities( PhoneAccount.CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS | PhoneAccount.CAPABILITY_VOICE_CALLING_AVAILABLE); } simHandlesToNotify = Collections.singletonList(account.getAccountHandle()); } else { // Not a relevant account - we only care about CONNECTION_MANAGER and SIM_SUBSCRIPTION. return; } if (simHandlesToNotify.isEmpty()) return; Log.i( this, "Notifying telephony of voice service override change for %d SIMs, hasService = %b", simHandlesToNotify.size(), hasService); TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); for (PhoneAccountHandle simHandle : simHandlesToNotify) { // This may be null if there are no active SIMs but the device is still camped for // emergency calls and registered a SIM_SUBSCRIPTION for that purpose. TelephonyManager simTm = tm.createForPhoneAccountHandle(simHandle); if (simTm == null) continue; simTm.setVoiceServiceStateOverride(hasService); } } /** * Determines if the connection service specified by a {@link PhoneAccountHandle} requires the * {@link Manifest.permission#BIND_TELECOM_CONNECTION_SERVICE} permission. Loading
src/com/android/server/telecom/TelecomServiceImpl.java +50 −0 Original line number Diff line number Diff line Loading @@ -573,6 +573,15 @@ public class TelecomServiceImpl { if (account.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) { enforceRegisterMultiUser(); } // These capabilities are for SIM-based accounts only, so only the platform // and carrier-designated SIM call manager can register accounts with these // capabilities. if (account.hasCapabilities( PhoneAccount.CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS) || account.hasCapabilities( PhoneAccount.CAPABILITY_VOICE_CALLING_AVAILABLE)) { enforceRegisterVoiceCallingIndicationCapabilities(account); } Bundle extras = account.getExtras(); if (extras != null && extras.getBoolean(PhoneAccount.EXTRA_SKIP_CALL_FILTERING)) { Loading Loading @@ -2447,6 +2456,24 @@ public class TelecomServiceImpl { } } private void enforceRegisterVoiceCallingIndicationCapabilities(PhoneAccount account) { // Caller must be able to register a SIM PhoneAccount or be the SIM call manager (as named // in carrier config) to declare the two voice indication capabilities. boolean prerequisiteCapabilitiesOk = account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION) || account.hasCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER); boolean permissionsOk = isCallerSimCallManagerForAnySim(account.getAccountHandle()) || mContext.checkCallingOrSelfPermission(REGISTER_SIM_SUBSCRIPTION) == PackageManager.PERMISSION_GRANTED; if (!prerequisiteCapabilitiesOk || !permissionsOk) { throw new SecurityException( "Only SIM subscriptions and connection managers are allowed to declare " + "CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS and " + "CAPABILITY_VOICE_CALLING_AVAILABLE"); } } private void enforceRegisterSkipCallFiltering() { if (!isCallerSystemApp()) { throw new SecurityException( Loading Loading @@ -2656,6 +2683,29 @@ public class TelecomServiceImpl { return false; } /** * Similar to {@link #isCallerSimCallManager}, but works for all SIMs and does not require * {@code accountHandle} to be registered yet. */ private boolean isCallerSimCallManagerForAnySim(PhoneAccountHandle accountHandle) { if (isCallerSimCallManager(accountHandle)) { // The caller has already registered a CONNECTION_MANAGER PhoneAccount, so let them pass // (this allows the SIM call manager through in case of SIM switches, where carrier // config may be in a transient state) return true; } // If the caller isn't already registered, then we have to look at the active PSTN // PhoneAccounts and check their carrier configs to see if any point to this one's component final long token = Binder.clearCallingIdentity(); try { return !mPhoneAccountRegistrar .getSimPhoneAccountsFromSimCallManager(accountHandle) .isEmpty(); } finally { Binder.restoreCallingIdentity(token); } } private boolean isPrivilegedDialerCalling(String callingPackage) { mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage); Loading
tests/src/com/android/server/telecom/tests/ComponentContextFixture.java +4 −0 Original line number Diff line number Diff line Loading @@ -739,6 +739,10 @@ public class ComponentContextFixture implements TestFixture<Context> { return mTelephonyManager; } public CarrierConfigManager getCarrierConfigManager() { return mCarrierConfigManager; } public NotificationManager getNotificationManager() { return mNotificationManager; } Loading
tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java +213 −0 Original line number Diff line number Diff line Loading @@ -22,10 +22,16 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageInfo; Loading @@ -36,6 +42,7 @@ import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Bundle; import android.os.Parcel; import android.os.PersistableBundle; import android.os.Process; import android.os.UserHandle; import android.os.UserManager; Loading @@ -43,6 +50,7 @@ import android.telecom.Log; import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; import android.telephony.CarrierConfigManager; import android.test.suitebuilder.annotation.SmallTest; import android.test.suitebuilder.annotation.MediumTest; import android.util.Xml; Loading Loading @@ -1066,6 +1074,156 @@ public class PhoneAccountRegistrarTest extends TelecomTestCase { assertEquals(1, deletedAccounts); } @Test public void testGetSimPhoneAccountsFromSimCallManager() throws Exception { // Register the SIM PhoneAccounts mComponentContextFixture.addConnectionService( makeQuickConnectionServiceComponentName(), Mockito.mock(IConnectionService.class)); PhoneAccount sim1Account = makeQuickSimAccount(1); PhoneAccountHandle sim1Handle = sim1Account.getAccountHandle(); registerAndEnableAccount(sim1Account); PhoneAccount sim2Account = makeQuickSimAccount(2); PhoneAccountHandle sim2Handle = sim2Account.getAccountHandle(); registerAndEnableAccount(sim2Account); assertEquals( List.of(sim1Handle, sim2Handle), mRegistrar.getSimPhoneAccountsOfCurrentUser()); // Set up the SIM call manager app + carrier configs ComponentName simCallManagerComponent = new ComponentName("com.carrier.app", "CarrierConnectionService"); PhoneAccountHandle simCallManagerHandle = makeQuickAccountHandle(simCallManagerComponent, "sim-call-manager"); setSimCallManagerCarrierConfig( 1, new ComponentName("com.other.carrier", "OtherConnectionService")); setSimCallManagerCarrierConfig(2, simCallManagerComponent); // Since SIM 1 names another app, so we only get the handle for SIM 2 assertEquals( List.of(sim2Handle), mRegistrar.getSimPhoneAccountsFromSimCallManager(simCallManagerHandle)); // We do exact component matching, not just package name matching assertEquals( List.of(), mRegistrar.getSimPhoneAccountsFromSimCallManager( makeQuickAccountHandle( new ComponentName("com.carrier.app", "SomeOtherUnrelatedService"), "same-pkg-but-diff-svc"))); // Results are identical after we register the PhoneAccount mComponentContextFixture.addConnectionService( simCallManagerComponent, Mockito.mock(IConnectionService.class)); PhoneAccount simCallManagerAccount = new PhoneAccount.Builder(simCallManagerHandle, "SIM call manager") .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER) .build(); mRegistrar.registerPhoneAccount(simCallManagerAccount); assertEquals( List.of(sim2Handle), mRegistrar.getSimPhoneAccountsFromSimCallManager(simCallManagerHandle)); } @Test public void testMaybeNotifyTelephonyForVoiceServiceState() throws Exception { // Register the SIM PhoneAccounts mComponentContextFixture.addConnectionService( makeQuickConnectionServiceComponentName(), Mockito.mock(IConnectionService.class)); PhoneAccount sim1Account = makeQuickSimAccount(1); registerAndEnableAccount(sim1Account); PhoneAccount sim2Account = makeQuickSimAccount(2); registerAndEnableAccount(sim2Account); // Telephony is notified by default when new SIM accounts are registered verify(mComponentContextFixture.getTelephonyManager(), times(2)) .setVoiceServiceStateOverride(false); clearInvocations(mComponentContextFixture.getTelephonyManager()); // Set up the SIM call manager app + carrier configs ComponentName simCallManagerComponent = new ComponentName("com.carrier.app", "CarrierConnectionService"); PhoneAccountHandle simCallManagerHandle = makeQuickAccountHandle(simCallManagerComponent, "sim-call-manager"); mComponentContextFixture.addConnectionService( simCallManagerComponent, Mockito.mock(IConnectionService.class)); setSimCallManagerCarrierConfig(1, simCallManagerComponent); setSimCallManagerCarrierConfig(2, simCallManagerComponent); // When the SIM call manager is registered without the SUPPORTS capability, telephony is // still notified for consistency (e.g. runtime capability removal + re-registration). PhoneAccount simCallManagerAccount = new PhoneAccount.Builder(simCallManagerHandle, "SIM call manager") .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER) .build(); mRegistrar.registerPhoneAccount(simCallManagerAccount); verify(mComponentContextFixture.getTelephonyManager(), times(2)) .setVoiceServiceStateOverride(false); clearInvocations(mComponentContextFixture.getTelephonyManager()); // Adding the SUPPORTS capability causes the SIMs to get notified with false again for // consistency purposes simCallManagerAccount = copyPhoneAccountAndAddCapabilities( simCallManagerAccount, PhoneAccount.CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS); mRegistrar.registerPhoneAccount(simCallManagerAccount); verify(mComponentContextFixture.getTelephonyManager(), times(2)) .setVoiceServiceStateOverride(false); clearInvocations(mComponentContextFixture.getTelephonyManager()); // Adding the AVAILABLE capability updates the SIMs again, this time with hasService = true simCallManagerAccount = copyPhoneAccountAndAddCapabilities( simCallManagerAccount, PhoneAccount.CAPABILITY_VOICE_CALLING_AVAILABLE); mRegistrar.registerPhoneAccount(simCallManagerAccount); verify(mComponentContextFixture.getTelephonyManager(), times(2)) .setVoiceServiceStateOverride(true); clearInvocations(mComponentContextFixture.getTelephonyManager()); // Removing a SIM account does nothing, regardless of SIM call manager capabilities mRegistrar.unregisterPhoneAccount(sim1Account.getAccountHandle()); verify(mComponentContextFixture.getTelephonyManager(), never()) .setVoiceServiceStateOverride(anyBoolean()); clearInvocations(mComponentContextFixture.getTelephonyManager()); // Adding a SIM account while a SIM call manager with both capabilities is registered causes // a call to telephony with hasService = true mRegistrar.registerPhoneAccount(sim1Account); verify(mComponentContextFixture.getTelephonyManager(), times(1)) .setVoiceServiceStateOverride(true); clearInvocations(mComponentContextFixture.getTelephonyManager()); // Removing the SIM call manager while it has both capabilities causes a call to telephony // with hasService = false mRegistrar.unregisterPhoneAccount(simCallManagerHandle); verify(mComponentContextFixture.getTelephonyManager(), times(2)) .setVoiceServiceStateOverride(false); clearInvocations(mComponentContextFixture.getTelephonyManager()); // Removing the SIM call manager while it has the SUPPORTS capability but not AVAILABLE // still causes a call to telephony with hasService = false for consistency simCallManagerAccount = copyPhoneAccountAndRemoveCapabilities( simCallManagerAccount, PhoneAccount.CAPABILITY_VOICE_CALLING_AVAILABLE); mRegistrar.registerPhoneAccount(simCallManagerAccount); clearInvocations(mComponentContextFixture.getTelephonyManager()); // from re-registration mRegistrar.unregisterPhoneAccount(simCallManagerHandle); verify(mComponentContextFixture.getTelephonyManager(), times(2)) .setVoiceServiceStateOverride(false); clearInvocations(mComponentContextFixture.getTelephonyManager()); // Finally, removing the SIM call manager while it has neither capability still causes a // call to telephony with hasService = false for consistency simCallManagerAccount = copyPhoneAccountAndRemoveCapabilities( simCallManagerAccount, PhoneAccount.CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS); mRegistrar.registerPhoneAccount(simCallManagerAccount); clearInvocations(mComponentContextFixture.getTelephonyManager()); // from re-registration mRegistrar.unregisterPhoneAccount(simCallManagerHandle); verify(mComponentContextFixture.getTelephonyManager(), times(2)) .setVoiceServiceStateOverride(false); clearInvocations(mComponentContextFixture.getTelephonyManager()); } private static ComponentName makeQuickConnectionServiceComponentName() { return new ComponentName( "com.android.server.telecom.tests", Loading @@ -1086,6 +1244,23 @@ public class PhoneAccountRegistrarTest extends TelecomTestCase { "label" + idx); } private static PhoneAccount copyPhoneAccountAndOverrideCapabilities( PhoneAccount base, int newCapabilities) { return base.toBuilder().setCapabilities(newCapabilities).build(); } private static PhoneAccount copyPhoneAccountAndAddCapabilities( PhoneAccount base, int capabilitiesToAdd) { return copyPhoneAccountAndOverrideCapabilities( base, base.getCapabilities() | capabilitiesToAdd); } private static PhoneAccount copyPhoneAccountAndRemoveCapabilities( PhoneAccount base, int capabilitiesToRemove) { return copyPhoneAccountAndOverrideCapabilities( base, base.getCapabilities() & ~capabilitiesToRemove); } private PhoneAccount makeQuickAccount(String id, int idx) { return makeQuickAccountBuilder(id, idx) .setAddress(Uri.parse("http://foo.com/" + idx)) Loading @@ -1098,6 +1273,44 @@ public class PhoneAccountRegistrarTest extends TelecomTestCase { .build(); } /** * Similar to {@link #makeQuickAccount}, but also hooks up {@code TelephonyManager} so that it * returns {@code simId} as the account's subscriptionId. */ private PhoneAccount makeQuickSimAccount(int simId) { PhoneAccount simAccount = makeQuickAccountBuilder("sim" + simId, simId) .setCapabilities( PhoneAccount.CAPABILITY_CALL_PROVIDER | PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION) .setIsEnabled(true) .build(); when(mComponentContextFixture .getTelephonyManager() .getSubscriptionId(simAccount.getAccountHandle())) .thenReturn(simId); // mComponentContextFixture already sets up the createForSubscriptionId self-reference when(mComponentContextFixture .getTelephonyManager() .createForPhoneAccountHandle(simAccount.getAccountHandle())) .thenReturn(mComponentContextFixture.getTelephonyManager()); return simAccount; } /** * Hooks up carrier config to point to {@code simCallManagerComponent} for the given {@code * subscriptionId}. */ private void setSimCallManagerCarrierConfig( int subscriptionId, @Nullable ComponentName simCallManagerComponent) { PersistableBundle config = new PersistableBundle(); config.putString( CarrierConfigManager.KEY_DEFAULT_SIM_CALL_MANAGER_STRING, simCallManagerComponent != null ? simCallManagerComponent.flattenToString() : null); when(mComponentContextFixture.getCarrierConfigManager().getConfigForSubId(subscriptionId)) .thenReturn(config); } private static void roundTripPhoneAccount(PhoneAccount original) throws Exception { PhoneAccount copy = null; Loading