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

Commit 8d9cccc1 authored by Tyler Gunn's avatar Tyler Gunn Committed by android-build-team Robot
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
(cherry picked from commit 02761d24)
parent e7db19e8
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() {