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

Commit 3bcef4fe authored by Nathan Harold's avatar Nathan Harold
Browse files

Hook CarrierConfig 5G Capability to setN1ModeEnabled HAL

For devices that support the setN1Mode HAL API, hook it up
to the KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY to allow
carriers to specify whether SA is allowed.

Bug: 302033535
Test: telephony sanity
Test: manually verified
Test: atest GsmCdmaPhoneTest#testNrCapabilityChanged
Change-Id: Ibff0796c3fa3690355677dbd809062cd71db6534
parent efad8a1c
Loading
Loading
Loading
Loading
+115 −0
Original line number Diff line number Diff line
@@ -297,6 +297,16 @@ public class GsmCdmaPhone extends Phone {
    private final SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionsChangedListener;
    private final CallWaitingController mCallWaitingController;

    // Set via Carrier Config
    private boolean mIsN1ModeAllowedByCarrier = true;
    // Set via a call to the method on Phone; the only caller is IMS, and all of this code will
    // need to be updated to a voting mechanism (...enabled for reason...) if additional callers
    // are desired.
    private boolean mIsN1ModeAllowedByIms = true;
    // If this value is null, then the modem value is unknown. If a caller explicitly sets the
    // N1 mode, this value will be initialized before any attempt to set the value in the modem.
    private Boolean mModemN1Mode = null;

    // Constructors

    public GsmCdmaPhone(Context context, CommandsInterface ci, PhoneNotifier notifier, int phoneId,
@@ -2356,6 +2366,75 @@ public class GsmCdmaPhone extends Phone {
        mSsOverCdmaSupported = b.getBoolean(CarrierConfigManager.KEY_SUPPORT_SS_OVER_CDMA_BOOL);
    }

    /**
     * Enables or disables N1 mode (access to 5G core network) in accordance with
     * 3GPP TS 24.501 4.9.
     *
     * <p> To prevent redundant calls down to the modem and to support a mechanism whereby
     * N1 mode is only on if both IMS and carrier config believe that it should be on, this
     * method will first sync the value from the modem prior to possibly setting it. In addition
     * N1 mode will not be set to enabled unless both IMS and Carrier want it, since the use
     * cases require all entities to agree lest it default to disabled.
     *
     * @param enable {@code true} to enable N1 mode, {@code false} to disable N1 mode.
     * @param result Callback message to receive the result or null.
     */
    @Override
    public void setN1ModeEnabled(boolean enable, @Nullable Message result) {
        if (mFeatureFlags.enableCarrierConfigN1Control()) {
            // This might be called by IMS on another thread, so to avoid the requirement to
            // lock, post it through the handler.
            post(() -> {
                mIsN1ModeAllowedByIms = enable;
                if (mModemN1Mode == null) {
                    mCi.isN1ModeEnabled(obtainMessage(EVENT_GET_N1_MODE_ENABLED_DONE, result));
                } else {
                    maybeUpdateModemN1Mode(result);
                }
            });
        } else {
            super.setN1ModeEnabled(enable, result);
        }
    }

    /** Only called on the handler thread. */
    private void maybeUpdateModemN1Mode(@Nullable Message result) {
        final boolean wantN1Enabled = mIsN1ModeAllowedByCarrier && mIsN1ModeAllowedByIms;

        logd("N1 Mode: isModemN1Enabled=" + mModemN1Mode + ", wantN1Enabled=" + wantN1Enabled);

        // mModemN1Mode is never null here
        if (mModemN1Mode != wantN1Enabled) {
            // Assume success pending a response, which avoids multiple concurrent requests
            // going down to the modem. If it fails, that is addressed in the response.
            mModemN1Mode = wantN1Enabled;
            super.setN1ModeEnabled(
                    wantN1Enabled, obtainMessage(EVENT_SET_N1_MODE_ENABLED_DONE, result));
        } else if (result != null) {
            AsyncResult.forMessage(result);
            result.sendToTarget();
        }
    }

    /** Only called on the handler thread. */
    private void updateCarrierN1ModeSupported(@Nullable PersistableBundle b) {
        if (!mFeatureFlags.enableCarrierConfigN1Control()) return;

        if (b == null) return;


        final int[] supportedNrModes = b.getIntArray(
                CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY);

        mIsN1ModeAllowedByCarrier = ArrayUtils.contains(
                supportedNrModes, CarrierConfigManager.CARRIER_NR_AVAILABILITY_SA);
        if (mModemN1Mode == null) {
            mCi.isN1ModeEnabled(obtainMessage(EVENT_GET_N1_MODE_ENABLED_DONE));
        } else {
            maybeUpdateModemN1Mode(null);
        }
    }

    @Override
    public boolean useSsOverIms(Message onComplete) {
        boolean isUtEnabled = isUtEnabled();
@@ -3228,6 +3307,7 @@ public class GsmCdmaPhone extends Phone {
                updateNrSettingsAfterCarrierConfigChanged(b);
                updateVoNrSettings(b);
                updateSsOverCdmaSupported(b);
                updateCarrierN1ModeSupported(b);
                loadAllowedNetworksFromSubscriptionDatabase();
                // Obtain new radio capabilities from the modem, since some are SIM-dependent
                mCi.getRadioCapability(obtainMessage(EVENT_GET_RADIO_CAPABILITY));
@@ -3554,6 +3634,41 @@ public class GsmCdmaPhone extends Phone {
                    }
                }
                break;

            case EVENT_GET_N1_MODE_ENABLED_DONE:
                logd("EVENT_GET_N1_MODE_ENABLED_DONE");
                ar = (AsyncResult) msg.obj;
                if (ar == null || ar.exception != null
                        || ar.result == null || !(ar.result instanceof Boolean)) {
                    Rlog.e(LOG_TAG, "Failed to Retrieve N1 Mode", ar.exception);
                    if (ar != null && ar.userObj instanceof Message) {
                        // original requester's message is stashed in the userObj
                        final Message rsp = (Message) ar.userObj;
                        AsyncResult.forMessage(rsp, null, ar.exception);
                        rsp.sendToTarget();
                    }
                    break;
                }

                mModemN1Mode = (Boolean) ar.result;
                maybeUpdateModemN1Mode((Message) ar.userObj);
                break;

            case EVENT_SET_N1_MODE_ENABLED_DONE:
                logd("EVENT_SET_N1_MODE_ENABLED_DONE");
                ar = (AsyncResult) msg.obj;
                if (ar == null || ar.exception != null) {
                    Rlog.e(LOG_TAG, "Failed to Set N1 Mode", ar.exception);
                    // Set failed, so we have no idea at this point.
                    mModemN1Mode = null;
                }
                if (ar != null && ar.userObj instanceof Message) {
                    // original requester's message is stashed in the userObj
                    final Message rsp = (Message) ar.userObj;
                    AsyncResult.forMessage(rsp, null, ar.exception);
                    rsp.sendToTarget();
                }
                break;
            default:
                super.handleMessage(msg);
        }
+4 −2
Original line number Diff line number Diff line
@@ -252,8 +252,10 @@ public abstract class Phone extends Handler implements PhoneInternalInterface {
    protected static final int EVENT_SET_NULL_CIPHER_AND_INTEGRITY_DONE = 66;
    protected static final int EVENT_GET_DEVICE_IMEI_DONE = 67;
    protected static final int EVENT_TRIGGER_NOTIFY_ANBR = 68;
    protected static final int EVENT_GET_N1_MODE_ENABLED_DONE = 69;
    protected static final int EVENT_SET_N1_MODE_ENABLED_DONE = 70;

    protected static final int EVENT_LAST = EVENT_TRIGGER_NOTIFY_ANBR;
    protected static final int EVENT_LAST = EVENT_SET_N1_MODE_ENABLED_DONE;

    // For shared prefs.
    private static final String GSM_ROAMING_LIST_OVERRIDE_PREFIX = "gsm_roaming_list_";
@@ -868,7 +870,7 @@ public abstract class Phone extends Handler implements PhoneInternalInterface {
                }
                break;
            default:
                throw new RuntimeException("unexpected event not handled");
                throw new RuntimeException("unexpected event not handled, msgId=" + msg.what);
        }
    }

+139 −0
Original line number Diff line number Diff line
@@ -173,6 +173,8 @@ public class GsmCdmaPhoneTest extends TelephonyTest {
        mMockCi = Mockito.mock(CommandsInterface.class);
        adnRecordCache = Mockito.mock(AdnRecordCache.class);
        mDomainSelectionResolver = Mockito.mock(DomainSelectionResolver.class);
        mFeatureFlags = Mockito.mock(FeatureFlags.class);

        doReturn(false).when(mSST).isDeviceShuttingDown();
        doReturn(true).when(mImsManager).isVolteEnabledByPlatform();
        doReturn(false).when(mDomainSelectionResolver).isDomainSelectionSupported();
@@ -1482,6 +1484,143 @@ public class GsmCdmaPhoneTest extends TelephonyTest {
        assertEquals(captor.getValue().what, Phone.EVENT_GET_RADIO_CAPABILITY);
    }

    @Test
    public void testNrCapabilityChanged_firstRequest_noChangeNeeded() {
        when(mFeatureFlags.enableCarrierConfigN1Control()).thenReturn(true);

        mPhoneUT.mCi = mMockCi;
        PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
        bundle.putIntArray(CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY,
                new int[]{
                    CarrierConfigManager.CARRIER_NR_AVAILABILITY_NSA,
                    CarrierConfigManager.CARRIER_NR_AVAILABILITY_SA});

        mPhoneUT.sendMessage(mPhoneUT.obtainMessage(Phone.EVENT_CARRIER_CONFIG_CHANGED));
        processAllMessages();

        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
        verify(mMockCi, times(1)).isN1ModeEnabled(messageCaptor.capture());
        AsyncResult.forMessage(messageCaptor.getValue(), Boolean.TRUE, null);
        messageCaptor.getValue().sendToTarget();
        processAllMessages();

        verify(mMockCi, never()).setN1ModeEnabled(anyBoolean(), any());
    }

    @Test
    public void testNrCapabilityChanged_firstRequest_needsChange() {
        when(mFeatureFlags.enableCarrierConfigN1Control()).thenReturn(true);

        mPhoneUT.mCi = mMockCi;
        PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
        bundle.putIntArray(CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY,
                new int[]{
                    CarrierConfigManager.CARRIER_NR_AVAILABILITY_NSA,
                    CarrierConfigManager.CARRIER_NR_AVAILABILITY_SA});

        mPhoneUT.sendMessage(mPhoneUT.obtainMessage(Phone.EVENT_CARRIER_CONFIG_CHANGED));
        processAllMessages();

        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
        verify(mMockCi, times(1)).isN1ModeEnabled(messageCaptor.capture());
        AsyncResult.forMessage(messageCaptor.getValue(), Boolean.FALSE, null);
        messageCaptor.getValue().sendToTarget();
        processAllMessages();

        verify(mMockCi, times(1)).setN1ModeEnabled(eq(true), messageCaptor.capture());
        AsyncResult.forMessage(messageCaptor.getValue(), Boolean.TRUE, null);
        messageCaptor.getValue().sendToTarget();
        processAllMessages();
    }

    @Test
    public void testNrCapabilityChanged_CarrierConfigChanges() {
        when(mFeatureFlags.enableCarrierConfigN1Control()).thenReturn(true);

        // Initialize the inner cache and set the modem to N1 mode = enabled/true
        testNrCapabilityChanged_firstRequest_needsChange();

        PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
        // Remove SA support and send an additional carrier config change
        bundle.putIntArray(
                CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY,
                new int[]{CarrierConfigManager.CARRIER_NR_AVAILABILITY_NSA});

        mPhoneUT.sendMessage(mPhoneUT.obtainMessage(Phone.EVENT_CARRIER_CONFIG_CHANGED));
        processAllMessages();

        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
        verify(mMockCi, times(1)).isN1ModeEnabled(any()); // not called again
        verify(mMockCi, times(1)).setN1ModeEnabled(eq(false), messageCaptor.capture());
        AsyncResult.forMessage(messageCaptor.getValue(), null, null);
        messageCaptor.getValue().sendToTarget();
        processAllMessages();
    }

    @Test
    public void testNrCapabilityChanged_CarrierConfigChanges_ErrorResponse() {
        when(mFeatureFlags.enableCarrierConfigN1Control()).thenReturn(true);

        mPhoneUT.mCi = mMockCi;
        for (int i = 0; i < 2; i++) {
            PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
            bundle.putIntArray(CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY,
                    new int[]{
                        CarrierConfigManager.CARRIER_NR_AVAILABILITY_NSA,
                        CarrierConfigManager.CARRIER_NR_AVAILABILITY_SA});

            mPhoneUT.sendMessage(mPhoneUT.obtainMessage(Phone.EVENT_CARRIER_CONFIG_CHANGED));
            processAllMessages();

            ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
            verify(mMockCi, times(i + 1)).isN1ModeEnabled(messageCaptor.capture());
            AsyncResult.forMessage(messageCaptor.getValue(), null, new RuntimeException());
            messageCaptor.getValue().sendToTarget();
            processAllMessages();

            verify(mMockCi, never()).setN1ModeEnabled(anyBoolean(), any());
        }
    }

    @Test
    public void testNrCapabilityChanged_firstRequest_ImsChanges() {
        when(mFeatureFlags.enableCarrierConfigN1Control()).thenReturn(true);

        mPhoneUT.mCi = mMockCi;
        Message passthroughMessage = mTestHandler.obtainMessage(0xC0FFEE);

        mPhoneUT.setN1ModeEnabled(false, passthroughMessage);
        processAllMessages();

        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
        verify(mMockCi, times(1)).isN1ModeEnabled(messageCaptor.capture());
        assertEquals(messageCaptor.getValue().obj, passthroughMessage);
        AsyncResult.forMessage(messageCaptor.getValue(), Boolean.TRUE, null);
        messageCaptor.getValue().sendToTarget();
        processAllMessages();

        verify(mMockCi, times(1)).setN1ModeEnabled(eq(false), messageCaptor.capture());
        assertEquals(messageCaptor.getValue().obj, passthroughMessage);
        AsyncResult.forMessage(messageCaptor.getValue(), null, null);
        messageCaptor.getValue().sendToTarget();
        processAllMessages();

        // Verify the return message was received
        ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
        verify(mTestHandler, times(1)).sendMessageAtTime(messageArgumentCaptor.capture(),
                anyLong());
        assertEquals(messageArgumentCaptor.getValue(), passthroughMessage);

        mPhoneUT.setN1ModeEnabled(true, null);
        processAllMessages();

        verify(mMockCi, times(1)).isN1ModeEnabled(any()); // not called again
        verify(mMockCi, times(1)).setN1ModeEnabled(eq(true), messageCaptor.capture());
        AsyncResult.forMessage(messageCaptor.getValue(), null, null);
        messageCaptor.getValue().sendToTarget();
        processAllMessages();
    }

    private void setupForWpsCallTest() throws Exception {
        mSST.mSS = mServiceState;
        doReturn(ServiceState.STATE_IN_SERVICE).when(mServiceState).getState();