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

Commit 02761d24 authored by Tyler Gunn's avatar Tyler Gunn
Browse files

Add support for notification of midcall video call radio handovers.

Adding support for video calls to:
- Notification of successful LTE to WIFI handover
- Notification of failure to handover from LTE to WIFI.

If there is a handover from WIFI to LTE (or the initial handover from
lte to WIFI fails at the start of a call), we enable a
connectivity listener to track when a new WIFI network becomes available.
If a new wifi network becomes available and there is no handover to WIFI
before a 1 min time expires, we warn the user (existing connection event)
that we couldn't handover to wifi.
If the handover to WIFI is successful, we send a connection event which
dialer will use to show a toast for the handover.

Test: Manual, added unit tests.
Bug: 65490850
Change-Id: I7fa16003e7309d40df0654c2992c823ed4d12e28
parent 26cb20ac
Loading
Loading
Loading
Loading
+175 −15
Original line number Diff line number Diff line
@@ -24,7 +24,10 @@ import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkRequest;
import android.net.NetworkStats;
import android.net.Uri;
import android.os.AsyncResult;
@@ -227,6 +230,24 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
        }
    };

    /**
     * Tracks whether we are currently monitoring network connectivity for the purpose of warning
     * the user of an inability to handover from LTE to WIFI for video calls.
     */
    private boolean mIsMonitoringConnectivity = false;

    /**
     * Network callback used to schedule the handover check when a wireless network connects.
     */
    private ConnectivityManager.NetworkCallback mNetworkCallback =
            new ConnectivityManager.NetworkCallback() {
                @Override
                public void onAvailable(Network network) {
                    Rlog.i(LOG_TAG, "Network available: " + network);
                    scheduleHandoverCheck();
                }
            };

    //***** Constants

    static final int MAX_CONNECTIONS = 7;
@@ -579,6 +600,22 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
     */
    private boolean mNotifyHandoverVideoFromWifiToLTE = false;

    /**
     * Carrier configuration option which determines whether the carrier wants to inform the user
     * when a video call is handed over from LTE to WIFI.
     * See {@link CarrierConfigManager#KEY_NOTIFY_HANDOVER_VIDEO_FROM_LTE_TO_WIFI_BOOL} for more
     * information.
     */
    private boolean mNotifyHandoverVideoFromLTEToWifi = false;

    /**
     * When {@code} false, indicates that no handover from LTE to WIFI has occurred during the start
     * of the call.
     * When {@code true}, indicates that the start of call handover from LTE to WIFI has been
     * attempted (it may have suceeded or failed).
     */
    private boolean mHasPerformedStartOfCallHandover = false;

    /**
     * Carrier configuration option which determines whether the carrier supports the
     * {@link VideoProfile#STATE_PAUSED} signalling.
@@ -986,6 +1023,16 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
            return;
        }

        updateCarrierConfigCache(carrierConfig);
    }

    /**
     * Updates the local carrier config cache from a bundle obtained from the carrier config
     * manager.  Also supports unit testing by injecting configuration at test time.
     * @param carrierConfig The config bundle.
     */
    @VisibleForTesting
    public void updateCarrierConfigCache(PersistableBundle carrierConfig) {
        mAllowEmergencyVideoCalls =
                carrierConfig.getBoolean(CarrierConfigManager.KEY_ALLOW_EMERGENCY_VIDEO_CALLS_BOOL);
        mTreatDowngradedVideoCallsAsVideoCalls =
@@ -1003,6 +1050,8 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
                CarrierConfigManager.KEY_SUPPORT_DOWNGRADE_VT_TO_AUDIO_BOOL);
        mNotifyHandoverVideoFromWifiToLTE = carrierConfig.getBoolean(
                CarrierConfigManager.KEY_NOTIFY_HANDOVER_VIDEO_FROM_WIFI_TO_LTE_BOOL);
        mNotifyHandoverVideoFromLTEToWifi = carrierConfig.getBoolean(
                CarrierConfigManager.KEY_NOTIFY_HANDOVER_VIDEO_FROM_LTE_TO_WIFI_BOOL);
        mIgnoreDataEnabledChangedForVideoCalls = carrierConfig.getBoolean(
                CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS);
        mIsViLteDataMetered = carrierConfig.getBoolean(
@@ -2016,13 +2065,18 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
            processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE,
                    DisconnectCause.NOT_DISCONNECTED);

            if (mNotifyVtHandoverToWifiFail &&
                    !imsCall.isWifiCall() && imsCall.isVideoCall() && isWifiConnected()) {
            if (mNotifyVtHandoverToWifiFail && imsCall.isVideoCall() && !imsCall.isWifiCall()) {
                if (isWifiConnected()) {
                    // Schedule check to see if handover succeeded.
                    sendMessageDelayed(obtainMessage(EVENT_CHECK_FOR_WIFI_HANDOVER, imsCall),
                            HANDOVER_TO_WIFI_TIMEOUT_MS);
                } else {
                    // No wifi connectivity, so keep track of network availability for potential
                    // handover.
                    registerForConnectivityChanges();
                }

            }
            mHasPerformedStartOfCallHandover = false;
            mMetrics.writeOnImsCallStarted(mPhone.getPhoneId(), imsCall.getCallSession());
        }

@@ -2538,18 +2592,35 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
            boolean isHandoverToWifi = srcAccessTech != ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN
                    && srcAccessTech != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
                    && targetAccessTech == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN;
            if (isHandoverToWifi) {
                // If we handed over to wifi successfully, don't check for failure in the future.
                removeMessages(EVENT_CHECK_FOR_WIFI_HANDOVER);
            }

            ImsPhoneConnection conn = findConnection(imsCall);
            if (conn != null) {
            // Only consider it a handover from WIFI if the source and target radio tech is known.
            boolean isHandoverFromWifi =
                    srcAccessTech == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
                            && targetAccessTech != ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN
                            && targetAccessTech != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN;

            ImsPhoneConnection conn = findConnection(imsCall);
            if (conn != null) {
                if (conn.getDisconnectCause() == DisconnectCause.NOT_DISCONNECTED) {
                    if (isHandoverToWifi) {
                        removeMessages(EVENT_CHECK_FOR_WIFI_HANDOVER);

                        if (mNotifyHandoverVideoFromLTEToWifi && mHasPerformedStartOfCallHandover) {
                            // This is a handover which happened mid-call (ie not the start of call
                            // handover from LTE to WIFI), so we'll notify the InCall UI.
                            conn.onConnectionEvent(
                                    TelephonyManager.EVENT_HANDOVER_VIDEO_FROM_LTE_TO_WIFI, null);
                        }

                        // We are on WIFI now so no need to get notified of network availability.
                        unregisterForConnectivityChanges();
                    } else if (isHandoverFromWifi && imsCall.isVideoCall()) {
                        // A video call just dropped from WIFI to LTE; we want to be informed if a
                        // new WIFI
                        // network comes into range.
                        registerForConnectivityChanges();
                    }
                }

                if (isHandoverFromWifi && imsCall.isVideoCall()) {
                    if (mNotifyHandoverVideoFromWifiToLTE && mIsDataEnabled) {
                        if (conn.getDisconnectCause() == DisconnectCause.NOT_DISCONNECTED) {
@@ -2575,6 +2646,9 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
                loge("onCallHandover :: connection null.");
            }

            if (!mHasPerformedStartOfCallHandover) {
                mHasPerformedStartOfCallHandover = true;
            }
            mMetrics.writeOnImsCallHandoverEvent(mPhone.getPhoneId(),
                    TelephonyCallSession.Event.Type.IMS_CALL_HANDOVER, imsCall.getCallSession(),
                    srcAccessTech, targetAccessTech, reasonInfo);
@@ -2600,11 +2674,20 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
                // If we know we failed to handover, don't check for failure in the future.
                removeMessages(EVENT_CHECK_FOR_WIFI_HANDOVER);

                if (imsCall.isVideoCall()
                        && conn.getDisconnectCause() == DisconnectCause.NOT_DISCONNECTED) {
                    // Start listening for a WIFI network to come into range for potential handover.
                    registerForConnectivityChanges();
                }

                if (mNotifyVtHandoverToWifiFail) {
                    // Only notify others if carrier config indicates to do so.
                    conn.onHandoverToWifiFailed();
                }
            }
            if (!mHasPerformedStartOfCallHandover) {
                mHasPerformedStartOfCallHandover = true;
            }
        }

        @Override
@@ -2682,6 +2765,8 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
        public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) {
            if (DBG) log("mImsUssdListener onCallTerminated reasonCode=" + reasonInfo.getCode());
            removeMessages(EVENT_CHECK_FOR_WIFI_HANDOVER);
            mHasPerformedStartOfCallHandover = false;
            unregisterForConnectivityChanges();

            if (imsCall == mUssdSession) {
                mUssdSession = null;
@@ -2950,12 +3035,24 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
            case EVENT_CHECK_FOR_WIFI_HANDOVER:
                if (msg.obj instanceof ImsCall) {
                    ImsCall imsCall = (ImsCall) msg.obj;
                    if (imsCall != mForegroundCall.getImsCall()) {
                        Rlog.i(LOG_TAG, "handoverCheck: no longer FG; check skipped.");
                        unregisterForConnectivityChanges();
                        // Handover check and its not the foreground call any more.
                        return;
                    }
                    if (!imsCall.isWifiCall()) {
                        // Call did not handover to wifi, notify of handover failure.
                        ImsPhoneConnection conn = findConnection(imsCall);
                        if (conn != null) {
                            Rlog.i(LOG_TAG, "handoverCheck: handover failed.");
                            conn.onHandoverToWifiFailed();
                        }

                        if (imsCall.isVideoCall()
                                && conn.getDisconnectCause() == DisconnectCause.NOT_DISCONNECTED) {
                            registerForConnectivityChanges();
                        }
                    }
                }
                break;
@@ -3566,6 +3663,64 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
        return false;
    }

    /**
     * Registers for changes to network connectivity.  Specifically requests the availability of new
     * WIFI networks which an IMS video call could potentially hand over to.
     */
    private void registerForConnectivityChanges() {
        if (mIsMonitoringConnectivity || !mNotifyVtHandoverToWifiFail) {
            return;
        }
        ConnectivityManager cm = (ConnectivityManager) mPhone.getContext()
                .getSystemService(Context.CONNECTIVITY_SERVICE);
        if (cm != null) {
            Rlog.i(LOG_TAG, "registerForConnectivityChanges");
            NetworkCapabilities capabilities = new NetworkCapabilities();
            capabilities.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
            NetworkRequest.Builder builder = new NetworkRequest.Builder();
            builder.setCapabilities(capabilities);
            cm.registerNetworkCallback(builder.build(), mNetworkCallback);
            mIsMonitoringConnectivity = true;
        }
    }

    /**
     * Unregister for connectivity changes.  Will be called when a call disconnects or if the call
     * ends up handing over to WIFI.
     */
    private void unregisterForConnectivityChanges() {
        if (!mIsMonitoringConnectivity || !mNotifyVtHandoverToWifiFail) {
            return;
        }
        ConnectivityManager cm = (ConnectivityManager) mPhone.getContext()
                .getSystemService(Context.CONNECTIVITY_SERVICE);
        if (cm != null) {
            Rlog.i(LOG_TAG, "unregisterForConnectivityChanges");
            cm.unregisterNetworkCallback(mNetworkCallback);
            mIsMonitoringConnectivity = false;
        }
    }

    /**
     * If the foreground call is a video call, schedule a handover check if one is not already
     * scheduled.  This method is intended ONLY for use when scheduling to watch for mid-call
     * handovers.
     */
    private void scheduleHandoverCheck() {
        ImsCall fgCall = mForegroundCall.getImsCall();
        ImsPhoneConnection conn = mForegroundCall.getFirstConnection();
        if (!mNotifyVtHandoverToWifiFail || fgCall == null || !fgCall.isVideoCall() || conn == null
                || conn.getDisconnectCause() != DisconnectCause.NOT_DISCONNECTED) {
            return;
        }

        if (!hasMessages(EVENT_CHECK_FOR_WIFI_HANDOVER)) {
            Rlog.i(LOG_TAG, "scheduleHandoverCheck: schedule");
            sendMessageDelayed(obtainMessage(EVENT_CHECK_FOR_WIFI_HANDOVER, fgCall),
                    HANDOVER_TO_WIFI_TIMEOUT_MS);
        }
    }

    /**
     * @return {@code true} if downgrading of a video call to audio is supported.
     */
@@ -3573,6 +3728,11 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
        return mSupportDowngradeVtToAudio;
    }

    @VisibleForTesting
    public void setDataEnabled(boolean isDataEnabled) {
        mIsDataEnabled = isDataEnabled;
    }

    private void handleFeatureCapabilityChanged(int serviceClass,
            int[] enabledFeatures, int[] disabledFeatures) {
        if (serviceClass == ImsServiceClass.MMTEL) {
+64 −1
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -42,10 +43,14 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.PersistableBundle;
import android.support.test.filters.FlakyTest;
import android.telecom.VideoProfile;
import android.telephony.CarrierConfigManager;
import android.telephony.DisconnectCause;
import android.telephony.PhoneNumberUtils;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
import android.telephony.ims.feature.ImsFeature;
import android.test.suitebuilder.annotation.SmallTest;

@@ -88,6 +93,8 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest {
    private ImsCallSession mImsCallSession;
    @Mock
    private SharedPreferences mSharedPreferences;
    @Mock
    private ImsPhoneConnection.Listener mImsPhoneConnectionListener;
    private Handler mCTHander;

    private class ImsCTHandlerThread extends HandlerThread {
@@ -103,6 +110,7 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest {
                    ImsReasonInfo.CODE_ANSWERED_ELSEWHERE);
            mCTUT.addReasonCodeRemapping(510, "Call answered elsewhere.",
                    ImsReasonInfo.CODE_ANSWERED_ELSEWHERE);
            mCTUT.setDataEnabled(true);
            mCTHander = new Handler(mCTUT.getLooper());
            setReady(true);
        }
@@ -156,7 +164,7 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest {
            }
        }).when(mImsCall).hold();

        doReturn(mImsCallSession).when(mImsCall).getCallSession();
        mImsCall.attachSession(mImsCallSession);
    }

    @Before
@@ -167,6 +175,7 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest {
        mImsManagerInstances.put(mImsPhone.getPhoneId(), mImsManager);
        mImsCall = spy(new ImsCall(mContext, mImsCallProfile));
        mSecondImsCall = spy(new ImsCall(mContext, mImsCallProfile));
        mImsPhoneConnectionListener = mock(ImsPhoneConnection.Listener.class);
        imsCallMocking(mImsCall);
        imsCallMocking(mSecondImsCall);
        doReturn(ImsFeature.STATE_READY).when(mImsManager).getImsServiceStatus();
@@ -189,6 +198,7 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest {
            public ImsCall answer(InvocationOnMock invocation) throws Throwable {
                mImsCallListener =
                        (ImsCall.Listener) invocation.getArguments()[2];
                mImsCall.setListener(mImsCallListener);
                return mImsCall;
            }
        }).when(mImsManager).takeCall(eq(mServiceId), (Intent) any(), (ImsCall.Listener) any());
@@ -198,6 +208,7 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest {
            public ImsCall answer(InvocationOnMock invocation) throws Throwable {
                mImsCallListener =
                        (ImsCall.Listener) invocation.getArguments()[3];
                mSecondImsCall.setListener(mImsCallListener);
                return mSecondImsCall;
            }
        }).when(mImsManager).makeCall(eq(mServiceId), eq(mImsCallProfile), (String []) any(),
@@ -262,6 +273,9 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest {
        assertEquals(PhoneConstants.State.RINGING, mCTUT.getState());
        assertTrue(mCTUT.mRingingCall.isRinging());
        assertEquals(1, mCTUT.mRingingCall.getConnections().size());
        ImsPhoneConnection connection =
                (ImsPhoneConnection) mCTUT.mRingingCall.getConnections().get(0);
        connection.addListener(mImsPhoneConnectionListener);
    }

    @Test
@@ -570,6 +584,55 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest {
                nullable(ImsConnectionStateListener.class));
    }

    /**
     * Test notification of handover from LTE to WIFI and WIFI to LTE and ensure that the expected
     * connection events are sent.
     */
    @Test
    @SmallTest
    public void testNotifyHandovers() {
        setupCarrierConfig();

        //establish a MT call
        testImsMTCallAccept();
        ImsPhoneConnection connection =
                (ImsPhoneConnection) mCTUT.mForegroundCall.getConnections().get(0);
        ImsCall call = connection.getImsCall();
        // Needs to be a video call to see this signalling.
        doReturn(true).when(mImsCallProfile).isVideoCall();

        // First handover from LTE to WIFI; this takes us into a mid-call state.
        call.getImsCallSessionListenerProxy().callSessionHandover(call.getCallSession(),
                ServiceState.RIL_RADIO_TECHNOLOGY_LTE, ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN,
                new ImsReasonInfo());
        // Handover back to LTE.
        call.getImsCallSessionListenerProxy().callSessionHandover(call.getCallSession(),
                ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN, ServiceState.RIL_RADIO_TECHNOLOGY_LTE,
                new ImsReasonInfo());
        verify(mImsPhoneConnectionListener).onConnectionEvent(eq(
                TelephonyManager.EVENT_HANDOVER_VIDEO_FROM_WIFI_TO_LTE), isNull());

        // Finally hand back to WIFI
        call.getImsCallSessionListenerProxy().callSessionHandover(call.getCallSession(),
                ServiceState.RIL_RADIO_TECHNOLOGY_LTE, ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN,
                new ImsReasonInfo());
        verify(mImsPhoneConnectionListener).onConnectionEvent(eq(
                TelephonyManager.EVENT_HANDOVER_VIDEO_FROM_LTE_TO_WIFI), isNull());
    }

    /**
     * Configure carrier config options relevant to the unit test.
     */
    public void setupCarrierConfig() {
        PersistableBundle bundle = new PersistableBundle();
        bundle.putBoolean(CarrierConfigManager.KEY_NOTIFY_HANDOVER_VIDEO_FROM_LTE_TO_WIFI_BOOL,
                true);
        bundle.putBoolean(CarrierConfigManager.KEY_NOTIFY_HANDOVER_VIDEO_FROM_WIFI_TO_LTE_BOOL,
                true);
        bundle.putBoolean(CarrierConfigManager.KEY_NOTIFY_VT_HANDOVER_TO_WIFI_FAILURE_BOOL, true);
        mCTUT.updateCarrierConfigCache(bundle);
    }

    @Test
    @SmallTest
    public void testLowBatteryDisconnectMidCall() {