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

Commit 9f3a7999 authored by Joonhun Shin's avatar Joonhun Shin Committed by Android (Google) Code Review
Browse files

Merge changes from topic "330120237" into main

* changes:
  Change the method of obtaining IMS registration radio tech and add test case
  Add feature flag to change the method of obtaining IMS registration radio tech
  Update to make PhoneSwitcher testable for ImsRegistrationCallback and add test case
parents 9e02bd79 2c46f309
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -56,3 +56,14 @@ flag {
    description: "This flag supports setting the no reply timer for CFNRy based on carrier config"
    bug:"314732435"
}

# OWNER=joonhunshin TARGET=24Q3
flag {
    name: "change_method_of_obtaining_ims_registration_radio_tech"
    namespace: "telephony"
    description: "This flag changes the method of obtaining IMS registration radio technology"
    bug:"330120237"
    metadata {
        purpose: PURPOSE_BUGFIX
    }
}
+112 −10
Original line number Diff line number Diff line
@@ -68,6 +68,7 @@ import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.util.ArrayMap;
import android.util.LocalLog;
import android.util.Log;
import android.util.SparseIntArray;

import com.android.ims.ImsException;
import com.android.ims.ImsManager;
@@ -84,6 +85,7 @@ import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.data.DataNetworkController.NetworkRequestList;
import com.android.internal.telephony.data.DataSettingsManager.DataSettingsManagerCallback;
import com.android.internal.telephony.flags.FeatureFlags;
import com.android.internal.telephony.imsphone.ImsPhone;
import com.android.internal.telephony.metrics.TelephonyMetrics;
import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent;
import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent.DataSwitch;
@@ -103,6 +105,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;

/**
 * Utility singleton to monitor subscription changes and incoming NetworkRequests
@@ -320,7 +323,8 @@ public class PhoneSwitcher extends Handler {

    private ConnectivityManager mConnectivityManager;
    private int mImsRegistrationTech = REGISTRATION_TECH_NONE;

    @VisibleForTesting
    public final SparseIntArray mImsRegistrationRadioTechMap = new SparseIntArray();
    private List<Set<CommandException.Error>> mCurrentDdsSwitchFailure;

    /** Data settings manager callback. Key is the phone id. */
@@ -389,6 +393,27 @@ public class PhoneSwitcher extends Handler {
    public ImsRegTechProvider mImsRegTechProvider =
            (context, phoneId) -> ImsManager.getInstance(context, phoneId).getRegistrationTech();

    /**
     * Interface to register RegistrationCallback. It's a wrapper of
     * ImsManager#addRegistrationCallback, to make it mock-able in unittests.
     */
    public interface ImsRegisterCallback {
        /** Set RegistrationCallback. */
        void setCallback(Context context, int phoneId, RegistrationManager.RegistrationCallback cb,
                Executor executor) throws ImsException;
    }

    @VisibleForTesting
    public ImsRegisterCallback mImsRegisterCallback =
            (context, phoneId, cb, executor)-> {
                try {
                    ImsManager.getInstance(context, phoneId)
                            .addRegistrationCallback(cb, executor);
                } catch (ImsException e) {
                    throw e;
                }
            };

    /**
     * Method to get singleton instance.
     */
@@ -432,8 +457,7 @@ public class PhoneSwitcher extends Handler {

    private void registerForImsRadioTechChange(Context context, int phoneId) {
        try {
            ImsManager.getInstance(context, phoneId).addRegistrationCallback(
                    mRegistrationCallback, this::post);
            mImsRegisterCallback.setCallback(context, phoneId, mRegistrationCallback, this::post);
            mIsRegisteredForImsRadioTechChange = true;
        } catch (ImsException imsException) {
            mIsRegisteredForImsRadioTechChange = false;
@@ -494,6 +518,14 @@ public class PhoneSwitcher extends Handler {
                if (phone.getImsPhone() != null) {
                    phone.getImsPhone().registerForPreciseCallStateChanged(
                            this, EVENT_PRECISE_CALL_STATE_CHANGED, null);
                    if (mFlags.changeMethodOfObtainingImsRegistrationRadioTech()) {
                        // Initialize IMS registration tech
                        mImsRegistrationRadioTechMap.put(phoneId, REGISTRATION_TECH_NONE);
                        ((ImsPhone) phone.getImsPhone()).registerForImsRegistrationChanges(
                                this, EVENT_IMS_RADIO_TECH_CHANGED, null);

                        log("register handler to receive IMS registration : " + phoneId);
                    }
                }
                mDataSettingsManagerCallbacks.computeIfAbsent(phoneId,
                        v -> new DataSettingsManagerCallback(this::post) {
@@ -511,8 +543,11 @@ public class PhoneSwitcher extends Handler {
                            }});
                phone.getDataSettingsManager().registerCallback(
                        mDataSettingsManagerCallbacks.get(phoneId));

                if (!mFlags.changeMethodOfObtainingImsRegistrationRadioTech()) {
                    registerForImsRadioTechChange(context, phoneId);
                }
            }
            Set<CommandException.Error> ddsFailure = new HashSet<CommandException.Error>();
            mCurrentDdsSwitchFailure.add(ddsFailure);
        }
@@ -721,7 +756,18 @@ public class PhoneSwitcher extends Handler {
            case EVENT_IMS_RADIO_TECH_CHANGED: {
                // register for radio tech change to listen to radio tech handover in case previous
                // attempt was not successful
                if (!mFlags.changeMethodOfObtainingImsRegistrationRadioTech()) {
                    registerForImsRadioTechChange();
                } else {
                    if (msg.obj == null) {
                        log("EVENT_IMS_RADIO_TECH_CHANGED but parameter is not available");
                        break;
                    }
                    if (!onImsRadioTechChanged((AsyncResult) (msg.obj))) {
                        break;
                    }
                }

                // if voice call state changes or in voice call didn't change
                // but RAT changes(e.g. Iwlan -> cross sim), reevaluate for data switch.
                if (updatesIfPhoneInVoiceCallChanged() || isAnyVoiceCallActiveOnDevice()) {
@@ -733,7 +779,9 @@ public class PhoneSwitcher extends Handler {
            case EVENT_PRECISE_CALL_STATE_CHANGED: {
                // register for radio tech change to listen to radio tech handover in case previous
                // attempt was not successful
                if (!mFlags.changeMethodOfObtainingImsRegistrationRadioTech()) {
                    registerForImsRadioTechChange();
                }

                // If the phoneId in voice call didn't change, do nothing.
                if (!updatesIfPhoneInVoiceCallChanged()) {
@@ -885,6 +933,45 @@ public class PhoneSwitcher extends Handler {
        }
    }

    /**
     * Only provide service for the handler of PhoneSwitcher.
     * @return true if the radio tech changed, otherwise false
     */
    private boolean onImsRadioTechChanged(@NonNull AsyncResult asyncResult) {
        ImsPhone.ImsRegistrationRadioTechInfo imsRegistrationRadioTechInfo =
                (ImsPhone.ImsRegistrationRadioTechInfo) asyncResult.result;
        if (imsRegistrationRadioTechInfo == null
                || imsRegistrationRadioTechInfo.phoneId() == INVALID_PHONE_INDEX
                || imsRegistrationRadioTechInfo.imsRegistrationState()
                == RegistrationManager.REGISTRATION_STATE_REGISTERING) {
            // Ignore REGISTERING state, handle only REGISTERED and NOT_REGISTERED
            log("onImsRadioTechChanged : result is not available");
            return false;
        }

        int phoneId = imsRegistrationRadioTechInfo.phoneId();
        int subId = SubscriptionManager.getSubscriptionId(phoneId);
        int tech = imsRegistrationRadioTechInfo.imsRegistrationTech();
        log("onImsRadioTechChanged phoneId : " + phoneId + " subId : " + subId + " old tech : "
                + mImsRegistrationRadioTechMap.get(phoneId, REGISTRATION_TECH_NONE)
                + " new tech : " + tech);

        if (mImsRegistrationRadioTechMap.get(phoneId, REGISTRATION_TECH_NONE) == tech) {
            // Registration tech not changed
            return false;
        }

        mImsRegistrationRadioTechMap.put(phoneId, tech);

        if (subId == INVALID_SUBSCRIPTION_ID) {
            // Need to update the cached IMS registration tech but no need to do any of the
            // following. When the SIM removed, REGISTRATION_STATE_NOT_REGISTERED is notified.
            return false;
        }

        return true;
    }

    private synchronized void onMultiSimConfigChanged(int activeModemCount) {
        // No change.
        if (mActiveModemCount == activeModemCount) return;
@@ -911,6 +998,14 @@ public class PhoneSwitcher extends Handler {
            if (phone.getImsPhone() != null) {
                phone.getImsPhone().registerForPreciseCallStateChanged(
                        this, EVENT_PRECISE_CALL_STATE_CHANGED, null);
                if (mFlags.changeMethodOfObtainingImsRegistrationRadioTech()) {
                    // Initialize IMS registration tech for new phoneId
                    mImsRegistrationRadioTechMap.put(phoneId, REGISTRATION_TECH_NONE);
                    ((ImsPhone) phone.getImsPhone()).registerForImsRegistrationChanges(
                            this, EVENT_IMS_RADIO_TECH_CHANGED, null);

                    log("register handler to receive IMS registration : " + phoneId);
                }
            }

            mDataSettingsManagerCallbacks.computeIfAbsent(phone.getPhoneId(),
@@ -933,8 +1028,11 @@ public class PhoneSwitcher extends Handler {

            Set<CommandException.Error> ddsFailure = new HashSet<CommandException.Error>();
            mCurrentDdsSwitchFailure.add(ddsFailure);

            if (!mFlags.changeMethodOfObtainingImsRegistrationRadioTech()) {
                registerForImsRadioTechChange(mContext, phoneId);
            }
        }

        mAutoDataSwitchController.onMultiSimConfigChanged(activeModemCount);
    }
@@ -1100,10 +1198,14 @@ public class PhoneSwitcher extends Handler {
                    mAutoSelectedDataSubId = DEFAULT_SUBSCRIPTION_ID;
                }
                mPhoneSubscriptions[i] = sub;

                if (!mFlags.changeMethodOfObtainingImsRegistrationRadioTech()) {
                    // Listen to IMS radio tech change for new sub
                    if (SubscriptionManager.isValidSubscriptionId(sub)) {
                        registerForImsRadioTechChange(mContext, i);
                    }
                }

                diffDetected = true;
                mAutoDataSwitchController.notifySubscriptionsMappingChanged();
            }
+36 −3
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static android.telephony.ims.ImsManager.EXTRA_WFC_REGISTRATION_FAILURE_ME
import static android.telephony.ims.ImsManager.EXTRA_WFC_REGISTRATION_FAILURE_TITLE;
import static android.telephony.ims.RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED;
import static android.telephony.ims.RegistrationManager.REGISTRATION_STATE_REGISTERED;
import static android.telephony.ims.RegistrationManager.REGISTRATION_STATE_REGISTERING;
import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_NONE;
import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCKS;
import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK;
@@ -264,6 +265,14 @@ public class ImsPhone extends ImsPhoneBase {
        }
    }

    /**
     * Container to transfer IMS registration radio tech.
     * This will be used as result value of AsyncResult to the handler that called
     * {@link #registerForImsRegistrationChanges(Handler, int, Object)}
     */
    public record ImsRegistrationRadioTechInfo(int phoneId, int imsRegistrationTech,
                                                int imsRegistrationState) {}

    // Instance Variables
    Phone mDefaultPhone;
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -2509,7 +2518,15 @@ public class ImsPhone extends ImsPhoneBase {
            updateImsRegistrationInfo(REGISTRATION_STATE_REGISTERED,
                    attributes.getRegistrationTechnology(), SUGGESTED_ACTION_NONE,
                    imsTransportType);
            AsyncResult ar = new AsyncResult(null, null, null);

            AsyncResult ar;
            if (mFeatureFlags.changeMethodOfObtainingImsRegistrationRadioTech()) {
                ar = new AsyncResult(null, new ImsRegistrationRadioTechInfo(mPhoneId,
                        attributes.getRegistrationTechnology(), REGISTRATION_STATE_REGISTERED),
                        null);
            } else {
                ar = new AsyncResult(null, null, null);
            }
            mImsRegistrationUpdateRegistrants.notifyRegistrants(ar);
        }

@@ -2526,7 +2543,15 @@ public class ImsPhone extends ImsPhoneBase {
            mMetrics.writeOnImsConnectionState(mPhoneId, ImsConnectionState.State.PROGRESSING,
                    null);
            mImsStats.onImsRegistering(imsRadioTech);
            AsyncResult ar = new AsyncResult(null, null, null);

            AsyncResult ar;
            if (mFeatureFlags.changeMethodOfObtainingImsRegistrationRadioTech()) {
                ar = new AsyncResult(null, new ImsRegistrationRadioTechInfo(mPhoneId,
                        imsRadioTech, REGISTRATION_STATE_REGISTERING),
                        null);
            } else {
                ar = new AsyncResult(null, null, null);
            }
            mImsRegistrationUpdateRegistrants.notifyRegistrants(ar);
        }

@@ -2569,7 +2594,15 @@ public class ImsPhone extends ImsPhoneBase {
                setCurrentSubscriberUris(null);
                clearPhoneNumberForSourceIms();
            }
            AsyncResult ar = new AsyncResult(null, null, null);

            AsyncResult ar;
            if (mFeatureFlags.changeMethodOfObtainingImsRegistrationRadioTech()) {
                ar = new AsyncResult(null, new ImsRegistrationRadioTechInfo(mPhoneId,
                        REGISTRATION_TECH_NONE, REGISTRATION_STATE_NOT_REGISTERED),
                        null);
            } else {
                ar = new AsyncResult(null, null, null);
            }
            mImsRegistrationUpdateRegistrants.notifyRegistrants(ar);
        }

+115 −0
Original line number Diff line number Diff line
@@ -23,9 +23,12 @@ import static android.telephony.TelephonyManager.SET_OPPORTUNISTIC_SUB_INACTIVE_
import static android.telephony.TelephonyManager.SET_OPPORTUNISTIC_SUB_SUCCESS;
import static android.telephony.TelephonyManager.SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED;
import static android.telephony.TelephonyManager.SIM_STATE_LOADED;
import static android.telephony.ims.RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED;
import static android.telephony.ims.RegistrationManager.REGISTRATION_STATE_REGISTERED;
import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM;
import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN;
import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_LTE;
import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_NONE;

import static com.android.internal.telephony.data.AutoDataSwitchController.EVALUATION_REASON_VOICE_CALL_END;
import static com.android.internal.telephony.data.PhoneSwitcher.ECBM_DEFAULT_DATA_SWITCH_BASE_TIME_MS;
@@ -42,6 +45,7 @@ import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -73,6 +77,7 @@ import android.testing.TestableLooper;

import androidx.test.filters.SmallTest;

import com.android.ims.ImsException;
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CommandException;
import com.android.internal.telephony.CommandsInterface;
@@ -84,6 +89,8 @@ import com.android.internal.telephony.PhoneFactory;
import com.android.internal.telephony.ServiceStateTracker;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.TelephonyTest;
import com.android.internal.telephony.imsphone.ImsPhone;
import com.android.internal.telephony.imsphone.ImsPhoneCall;
import com.android.internal.telephony.subscription.SubscriptionInfoInternal;

import org.junit.After;
@@ -126,6 +133,7 @@ public class PhoneSwitcherTest extends TelephonyTest {
    private ISetOpportunisticDataCallback mSetOpptDataCallback1;
    private ISetOpportunisticDataCallback mSetOpptDataCallback2;
    PhoneSwitcher.ImsRegTechProvider mMockImsRegTechProvider;
    PhoneSwitcher.ImsRegisterCallback mMockImsRegisterCallback;
    private SubscriptionInfo mSubscriptionInfo;
    private ISub mMockedIsub;
    private AutoDataSwitchController mAutoDataSwitchController;
@@ -167,6 +175,7 @@ public class PhoneSwitcherTest extends TelephonyTest {
        mSetOpptDataCallback1 = mock(ISetOpportunisticDataCallback.class);
        mSetOpptDataCallback2 = mock(ISetOpportunisticDataCallback.class);
        mMockImsRegTechProvider = mock(PhoneSwitcher.ImsRegTechProvider.class);
        mMockImsRegisterCallback = mock(PhoneSwitcher.ImsRegisterCallback.class);
        mSubscriptionInfo = mock(SubscriptionInfo.class);
        mMockedIsub = mock(ISub.class);
        mAutoDataSwitchController = mock(AutoDataSwitchController.class);
@@ -871,6 +880,11 @@ public class PhoneSwitcherTest extends TelephonyTest {
        mPhoneSwitcherUT.mImsRegTechProvider = mMockImsRegTechProvider;
    }

    private void mockImsRegisterCallback(int phoneId) throws ImsException {
        doNothing().when(mMockImsRegisterCallback).setCallback(any(), eq(phoneId), any(), any());
        mPhoneSwitcherUT.mImsRegisterCallback = mMockImsRegisterCallback;
    }

    @Test
    @SmallTest
    public void testNonDefaultDataPhoneInCall_ImsCallOnLte_shouldSwitchDds() throws Exception {
@@ -1756,6 +1770,101 @@ public class PhoneSwitcherTest extends TelephonyTest {
        verify(mCommandsInterface1, never()).setDataAllowed(anyBoolean(), any());
    }

    @Test
    @SmallTest
    public void testRegisterForImsRegistrationCallback() throws Exception {
        initialize();
        setAllPhonesInactive();

        // Phone 0 has sub 1, phone 1 has sub 2.
        // Sub 1 is default data sub.
        // Both are active subscriptions are active sub, as they are in both active slots.
        setSlotIndexToSubId(0, 1);
        setSlotIndexToSubId(1, 2);
        setDefaultDataSubId(1);
        processAllMessages();

        // Phone 0 should be the default data phoneId.
        assertEquals(0, mPhoneSwitcherUT.getPreferredDataPhoneId());

        doReturn(true).when(mPhone).isUserDataEnabled();
        mockImsRegTech(0, REGISTRATION_TECH_LTE);
        mockImsRegisterCallback(0);
        mockImsRegisterCallback(1);

        notifyImsRegistrationTechChange(mPhone);

        // Verify that the callback is re-registered when the IMS registration callback is called.
        verify(mMockImsRegisterCallback, times(2)).setCallback(any(), anyInt(), any(), any());
    }

    @Test
    @SmallTest
    public void testReceivingImsRegistrationTech() throws Exception {
        doReturn(true).when(mFeatureFlags).changeMethodOfObtainingImsRegistrationRadioTech();

        // Set up input and output for testing
        ImsPhone testImsPhone = mock(ImsPhone.class);
        doReturn(testImsPhone).when(mPhone).getImsPhone();
        doReturn(testImsPhone).when(mPhone2).getImsPhone();
        ImsPhoneCall testImsPhoneCall = mock(ImsPhoneCall.class);
        doReturn(Call.State.IDLE).when(testImsPhoneCall).getState();
        doReturn(true).when(testImsPhoneCall).isIdle();
        doReturn(testImsPhoneCall).when(testImsPhone).getForegroundCall();
        doReturn(testImsPhoneCall).when(testImsPhone).getBackgroundCall();
        doReturn(testImsPhoneCall).when(testImsPhone).getRingingCall();

        doNothing().when(testImsPhone).registerForImsRegistrationChanges(any(), anyInt(), any());

        initialize();
        setAllPhonesInactive();

        // Phone 0 has sub 1, phone 1 has sub 2.
        // Sub 1 is default data sub.
        // Both are active subscriptions are active sub, as they are in both active slots.
        setSlotIndexToSubId(0, 1);
        setSlotIndexToSubId(1, 2);
        setDefaultDataSubId(1);
        processAllMessages();

        // Phone 0 should be the default data phoneId.
        assertEquals(0, mPhoneSwitcherUT.getPreferredDataPhoneId());

        doReturn(true).when(mPhone).isUserDataEnabled();
        mockImsRegTech(0, REGISTRATION_TECH_NONE);
        mockImsRegisterCallback(0);
        mockImsRegisterCallback(1);

        AsyncResult ar = new AsyncResult(null, new ImsPhone.ImsRegistrationRadioTechInfo(
                0, REGISTRATION_TECH_LTE, REGISTRATION_STATE_REGISTERED), null);
        notifyImsRegistrationTechChangeWithAsyncResult(ar);

        // Verify cached IMS registration tech is LTE
        assertTrue(REGISTRATION_TECH_LTE == mPhoneSwitcherUT.mImsRegistrationRadioTechMap.get(0));

        ar = new AsyncResult(null, new ImsPhone.ImsRegistrationRadioTechInfo(
                0, REGISTRATION_TECH_IWLAN, REGISTRATION_STATE_REGISTERED), null);
        notifyImsRegistrationTechChangeWithAsyncResult(ar);

        // Verify cached IMS registration tech is WiFi
        assertTrue(REGISTRATION_TECH_IWLAN
                == mPhoneSwitcherUT.mImsRegistrationRadioTechMap.get(0));

        ar = new AsyncResult(null, new ImsPhone.ImsRegistrationRadioTechInfo(
                0, REGISTRATION_TECH_NONE, REGISTRATION_STATE_NOT_REGISTERED), null);
        notifyImsRegistrationTechChangeWithAsyncResult(ar);

        // Verify cached IMS registration tech is NONE
        assertTrue(REGISTRATION_TECH_NONE == mPhoneSwitcherUT.mImsRegistrationRadioTechMap.get(0));

        // Verify there is no crash
        notifyImsRegistrationTechChangeWithAsyncResult(null);

        // Verify that the callback is not re-registered
        // when the IMS registration callback is called.
        verify(mMockImsRegisterCallback, never()).setCallback(any(), anyInt(), any(), any());
    }

    /* Private utility methods start here */

    private void prepareIdealAutoSwitchCondition() {
@@ -1862,6 +1971,12 @@ public class PhoneSwitcherTest extends TelephonyTest {
        processAllMessages();
    }

    private void notifyImsRegistrationTechChangeWithAsyncResult(AsyncResult ar) {
        mPhoneSwitcherUT.sendMessage(
                mPhoneSwitcherUT.obtainMessage(EVENT_IMS_RADIO_TECH_CHANGED, ar));
        processAllMessages();
    }

    private Message getEcbmRegistration(Phone phone) {
        ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
        ArgumentCaptor<Integer> intCaptor = ArgumentCaptor.forClass(Integer.class);