Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8781f501 authored by Hunter Knepshield's avatar Hunter Knepshield
Browse files

Telecom plumbing for SIM call manager voice status

When PhoneAccountRegistrar sees a PhoneAccount with
CAPABILITY_VOICE_CALLING_AVAILABLE registered, it will inform telephony
on the corresponding subId(s) for propagation to ServiceState.

Bug: 205737545
Test: com.android.server.telecom.tests.PhoneAccountRegistrarTest
Change-Id: Ia5894e6ac060975b6e1bd9b78e839e05361e3687
parent bedc5211
Loading
Loading
Loading
Loading
+97 −0
Original line number Diff line number Diff line
@@ -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
@@ -847,6 +872,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) {
@@ -856,6 +884,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);
            }
        }
    }
@@ -998,6 +1029,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.
+50 −0
Original line number Diff line number Diff line
@@ -532,6 +532,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)) {
@@ -2338,6 +2347,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(
@@ -2547,6 +2574,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);

+4 −0
Original line number Diff line number Diff line
@@ -739,6 +739,10 @@ public class ComponentContextFixture implements TestFixture<Context> {
        return mTelephonyManager;
    }

    public CarrierConfigManager getCarrierConfigManager() {
        return mCarrierConfigManager;
    }

    public NotificationManager getNotificationManager() {
        return mNotificationManager;
    }
+213 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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;
@@ -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",
@@ -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))
@@ -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;