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

Commit 62eb37c4 authored by Xiangyu/Malcolm Chen's avatar Xiangyu/Malcolm Chen Committed by Gerrit Code Review
Browse files

Merge "Adding validate before switch feature."

parents 59102af4 494a1b20
Loading
Loading
Loading
Loading
+225 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.internal.telephony;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.os.Handler;
import android.telephony.SubscriptionManager;
import android.util.Log;

/**
 * This class will validate whether cellular network verified by Connectivity's
 * validation process. It listens request on a specific subId, sends a network request
 * to Connectivity and listens to its callback or timeout.
 */
public class CellularNetworkValidator extends ConnectivityManager.NetworkCallback  {
    private static final String LOG_TAG = "NetworkValidator";

    // States of validator. Only one validation can happen at once.
    // IDLE: no validation going on.
    private static final int STATE_IDLE                = 0;
    // VALIDATING: validation going on.
    private static final int STATE_VALIDATING          = 1;
    // VALIDATED: validation is done and successful.
    // Waiting for stopValidation() to release
    // validationg NetworkRequest.
    private static final int STATE_VALIDATED           = 2;

    // Singleton instance.
    private static CellularNetworkValidator sInstance;

    private int mState = STATE_IDLE;
    private int mSubId;
    private int mTimeoutInMs;
    private boolean mReleaseAfterValidation;

    private NetworkRequest mNetworkRequest;
    private ValidationCallback mValidationCallback;
    private Context mContext;
    private ConnectivityManager mConnectivityManager;
    private Handler mHandler = new Handler();

    /**
     * Callback to pass in when starting validation.
     */
    public interface ValidationCallback {
        /**
         * Validation failed, passed or timed out.
         */
        void onValidationResult(boolean validated, int subId);
    }

    /**
     * Create instance.
     */
    public static CellularNetworkValidator make(Context context) {
        if (sInstance != null) {
            logd("createCellularNetworkValidator failed. Instance already exists.");
        } else {
            sInstance = new CellularNetworkValidator(context);
        }

        return sInstance;
    }

    /**
     * Get instance.
     */
    public static CellularNetworkValidator getInstance() {
        return sInstance;
    }

    /**
     * Check whether this feature is supported or not.
     */
    public static boolean isValidationFeatureSupported() {
        return PhoneConfigurationManager.getInstance().getCurrentPhoneCapability()
                .validationBeforeSwitchSupported;
    }

    private CellularNetworkValidator(Context context) {
        mContext = context;
        mConnectivityManager = (ConnectivityManager)
                mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
    }

    /**
     * API to start a validation
     */
    public synchronized void validate(int subId, int timeoutInMs,
            boolean releaseAfterValidation, ValidationCallback callback) {
        // If it's already validating the same subscription, do nothing.
        if (subId == mSubId) return;

        if (isValidating()) {
            logd("Failed to start validation. Already validating sub " + mSubId);
            callback.onValidationResult(false, subId);
            return;
        }

        Phone phone = PhoneFactory.getPhone(SubscriptionManager.getPhoneId(subId));
        if (phone == null) {
            logd("Failed to start validation. Inactive subId " + subId);
            callback.onValidationResult(false, subId);
            return;
        }

        mState = STATE_VALIDATING;
        mSubId = subId;
        mTimeoutInMs = timeoutInMs;
        mValidationCallback = callback;
        mReleaseAfterValidation = releaseAfterValidation;
        mNetworkRequest = createNetworkRequest();

        logd("Start validating subId " + mSubId + " mTimeoutInMs " + mTimeoutInMs
                + " mReleaseAfterValidation " + mReleaseAfterValidation);

        mConnectivityManager.requestNetwork(mNetworkRequest, this, mHandler, mTimeoutInMs);
    }

    /**
     * API to stop the current validation.
     */
    public synchronized void stopValidation() {
        if (!isValidating()) {
            logd("No need to stop validation.");
        } else {
            mConnectivityManager.unregisterNetworkCallback(this);
            mState = STATE_IDLE;
        }
        mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
    }

    /**
     * Return which subscription is under validating.
     */
    public synchronized int getSubIdInValidation() {
        return mSubId;
    }

    /**
     * Return whether there's an ongoing validation.
     */
    public synchronized boolean isValidating() {
        return mState != STATE_IDLE;
    }

    private NetworkRequest createNetworkRequest() {
        return new NetworkRequest.Builder()
                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
                .setNetworkSpecifier(String.valueOf(mSubId))
                .build();
    }

    private synchronized void reportValidationResult(boolean passed) {
        // Deal with the result only when state is still VALIDATING. This is to avoid
        // receiving multiple callbacks in queue.
        if (mState == STATE_VALIDATING) {
            mValidationCallback.onValidationResult(passed, mSubId);
            if (!mReleaseAfterValidation && passed) {
                mState = STATE_VALIDATED;
            } else {
                mConnectivityManager.unregisterNetworkCallback(this);
                mState = STATE_IDLE;
            }
        }
    }

    /**
     * ConnectivityManager.NetworkCallback implementation
     */
    @Override
    public void onAvailable(Network network) {
        logd("network onAvailable " + network);
    }

    @Override
    public void onLosing(Network network, int maxMsToLive) {
        logd("network onLosing " + network + " maxMsToLive " + maxMsToLive);
        reportValidationResult(false);
    }

    @Override
    public void onLost(Network network) {
        logd("network onLost " + network);
        reportValidationResult(false);
    }

    @Override
    public void onUnavailable() {
        logd("onUnavailable");
        reportValidationResult(false);
    }

    @Override
    public void onCapabilitiesChanged(Network network,
            NetworkCapabilities networkCapabilities) {
        if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
            logd("onValidated");
            reportValidationResult(true);
        }
    }

    private static void logd(String log) {
        Log.d(LOG_TAG, log);
    }
}
+3 −0
Original line number Original line Diff line number Diff line
@@ -88,6 +88,7 @@ public class PhoneFactory {
    static private TelephonyNetworkFactory[] sTelephonyNetworkFactories;
    static private TelephonyNetworkFactory[] sTelephonyNetworkFactories;
    static private ImsResolver sImsResolver;
    static private ImsResolver sImsResolver;
    static private NotificationChannelController sNotificationChannelController;
    static private NotificationChannelController sNotificationChannelController;
    static private CellularNetworkValidator sCellularNetworkValidator;


    static private final HashMap<String, LocalLog>sLocalLogs = new HashMap<String, LocalLog>();
    static private final HashMap<String, LocalLog>sLocalLogs = new HashMap<String, LocalLog>();


@@ -243,6 +244,8 @@ public class PhoneFactory {


                sPhoneConfigurationManager = PhoneConfigurationManager.init(sContext);
                sPhoneConfigurationManager = PhoneConfigurationManager.init(sContext);


                sCellularNetworkValidator = CellularNetworkValidator.make(sContext);

                int maxActivePhones = sPhoneConfigurationManager
                int maxActivePhones = sPhoneConfigurationManager
                        .getNumberOfModemsWithSimultaneousDataConnections();
                        .getNumberOfModemsWithSimultaneousDataConnections();


+63 −6
Original line number Original line Diff line number Diff line
@@ -80,7 +80,10 @@ public class PhoneSwitcher extends Handler {
    private final LocalLog mLocalLog;
    private final LocalLog mLocalLog;
    @VisibleForTesting
    @VisibleForTesting
    public final PhoneStateListener mPhoneStateListener;
    public final PhoneStateListener mPhoneStateListener;

    private final CellularNetworkValidator mValidator;
    private final CellularNetworkValidator.ValidationCallback mValidationCallback =
            (validated, subId) -> Message.obtain(PhoneSwitcher.this,
                    EVENT_NETWORK_VALIDATION_DONE, subId, validated ? 1 : 0).sendToTarget();
    private int mMaxActivePhones;
    private int mMaxActivePhones;
    private static PhoneSwitcher sPhoneSwitcher = null;
    private static PhoneSwitcher sPhoneSwitcher = null;


@@ -95,7 +98,7 @@ public class PhoneSwitcher extends Handler {
    private int mPreferredDataSubId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
    private int mPreferredDataSubId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;


    @VisibleForTesting
    @VisibleForTesting
    // Corresponding phoneId after considerting mPreferredDataSubId and mDefaultDataSubId above.
    // Corresponding phoneId after considering mPreferredDataSubId and mDefaultDataSubId above.
    protected int mPreferredDataPhoneId = SubscriptionManager.INVALID_PHONE_INDEX;
    protected int mPreferredDataPhoneId = SubscriptionManager.INVALID_PHONE_INDEX;


    private int mPhoneIdInCall = SubscriptionManager.INVALID_PHONE_INDEX;
    private int mPhoneIdInCall = SubscriptionManager.INVALID_PHONE_INDEX;
@@ -109,6 +112,7 @@ public class PhoneSwitcher extends Handler {
    private static final int EVENT_PREFERRED_SUBSCRIPTION_CHANGED = 107;
    private static final int EVENT_PREFERRED_SUBSCRIPTION_CHANGED = 107;
    private static final int EVENT_RADIO_AVAILABLE                = 108;
    private static final int EVENT_RADIO_AVAILABLE                = 108;
    private static final int EVENT_PHONE_IN_CALL_CHANGED          = 109;
    private static final int EVENT_PHONE_IN_CALL_CHANGED          = 109;
    private static final int EVENT_NETWORK_VALIDATION_DONE        = 110;


    // Depending on version of IRadioConfig, we need to send either RIL_REQUEST_ALLOW_DATA if it's
    // Depending on version of IRadioConfig, we need to send either RIL_REQUEST_ALLOW_DATA if it's
    // 1.0, or RIL_REQUEST_SET_PREFERRED_DATA if it's 1.1 or later. So internally mHalCommandToUse
    // 1.0, or RIL_REQUEST_SET_PREFERRED_DATA if it's 1.1 or later. So internally mHalCommandToUse
@@ -122,6 +126,9 @@ public class PhoneSwitcher extends Handler {


    private final static int MAX_LOCAL_LOG_LINES = 30;
    private final static int MAX_LOCAL_LOG_LINES = 30;


    // Default timeout value of network validation in millisecond.
    private final static int DEFAULT_VALIDATION_EXPIRATION_TIME = 2000;

    /**
    /**
     * Method to get singleton instance.
     * Method to get singleton instance.
     */
     */
@@ -162,6 +169,7 @@ public class PhoneSwitcher extends Handler {
                onPhoneCapabilityChangedInternal(capability);
                onPhoneCapabilityChangedInternal(capability);
            }
            }
        };
        };
        mValidator = CellularNetworkValidator.getInstance();
    }
    }


    @VisibleForTesting
    @VisibleForTesting
@@ -208,6 +216,8 @@ public class PhoneSwitcher extends Handler {
            }
            }
        };
        };


        mValidator = CellularNetworkValidator.getInstance();

        TelephonyManager telephonyManager =
        TelephonyManager telephonyManager =
                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
        telephonyManager.listen(mPhoneStateListener, LISTEN_PHONE_CAPABILITY_CHANGE
        telephonyManager.listen(mPhoneStateListener, LISTEN_PHONE_CAPABILITY_CHANGE
@@ -316,6 +326,12 @@ public class PhoneSwitcher extends Handler {
                onEvaluate(REQUESTS_UNCHANGED, "EVENT_PHONE_IN_CALL_CHANGED");
                onEvaluate(REQUESTS_UNCHANGED, "EVENT_PHONE_IN_CALL_CHANGED");
                break;
                break;
            }
            }
            case EVENT_NETWORK_VALIDATION_DONE: {
                int subId = msg.arg1;
                boolean passed = (msg.arg2 == 1);
                onValidationDone(subId, passed);
                break;
            }
        }
        }
    }
    }


@@ -555,7 +571,8 @@ public class PhoneSwitcher extends Handler {
        // Internet connection on the preferred phone. So we only accept Internet request with
        // Internet connection on the preferred phone. So we only accept Internet request with
        // preferred data subscription or no specified subscription.
        // preferred data subscription or no specified subscription.
        if (netRequest.networkCapabilities.hasCapability(
        if (netRequest.networkCapabilities.hasCapability(
                NetworkCapabilities.NET_CAPABILITY_INTERNET) && subId != preferredDataSubId) {
                NetworkCapabilities.NET_CAPABILITY_INTERNET)
                && subId != preferredDataSubId && subId != mValidator.getSubIdInValidation()) {
            // Returning INVALID_PHONE_INDEX will result in netRequest not being handled.
            // Returning INVALID_PHONE_INDEX will result in netRequest not being handled.
            return INVALID_PHONE_INDEX;
            return INVALID_PHONE_INDEX;
        }
        }
@@ -667,10 +684,50 @@ public class PhoneSwitcher extends Handler {
    }
    }


    /**
    /**
     * Set a subscription as preferred data subscription.
     * Set opportunistic data subscription. It's an indication to switch Internet data to this
     * See {@link SubscriptionManager#setPreferredDataSubscriptionId(int)} for more details.
     * subscription. It has to be an active subscription, and PhoneSwitcher will try to validate
     * it first if needed.
     */
    public void setOpportunisticDataSubscription(int subId) {
        if (!mSubscriptionController.isActiveSubId(subId)) {
            log("Can't switch data to inactive subId " + subId);
            return;
        }

        // If validation feature is not supported, set it directly. Otherwise,
        // start validation on the subscription first.
        if (!CellularNetworkValidator.isValidationFeatureSupported()) {
            setPreferredDataSubscriptionId(subId);
        } else {
            mValidator.validate(subId, DEFAULT_VALIDATION_EXPIRATION_TIME,
                    false, mValidationCallback);
        }
    }

    /**
     * Unset opportunistic data subscription. It's an indication to switch Internet data back
     * from opportunistic subscription to primary subscription.
     */
     */
    public void setPreferredDataSubscriptionId(int subId) {
    public void unsetOpportunisticDataSubscription() {
        if (CellularNetworkValidator.isValidationFeatureSupported()
                && mValidator.isValidating()) {
            mValidator.stopValidation();
        }

        // Set mPreferredDataSubId back to DEFAULT_SUBSCRIPTION_ID. This will trigger
        // data switch to mDefaultDataSubId.
        setPreferredDataSubscriptionId(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
    }

    private void onValidationDone(int subId, boolean passed) {
        log("Network validation " + (passed ? "passed" : "failed")
                + " on subId " + subId);
        mValidator.stopValidation();
        if (passed) setPreferredDataSubscriptionId(subId);
    }

    // TODO b/123598154: rename preferredDataSub to opportunisticSubId.
    private void setPreferredDataSubscriptionId(int subId) {
        if (mPreferredDataSubId != subId) {
        if (mPreferredDataSubId != subId) {
            log("setPreferredDataSubscriptionId subId changed to " + subId);
            log("setPreferredDataSubscriptionId subId changed to " + subId);
            mPreferredDataSubId = subId;
            mPreferredDataSubId = subId;
+5 −7
Original line number Original line Diff line number Diff line
@@ -2672,18 +2672,16 @@ public class SubscriptionController extends ISub.Stub {
    }
    }


    @Override
    @Override
    public int setPreferredDataSubscriptionId(int subId) {
    public void setPreferredDataSubscriptionId(int subId) {
        enforceModifyPhoneState("setPreferredDataSubscriptionId");
        enforceModifyPhoneState("setPreferredDataSubscriptionId");
        final long token = Binder.clearCallingIdentity();
        final long token = Binder.clearCallingIdentity();


        try {
        try {
            if (mPreferredDataSubId != subId) {
            if (SubscriptionManager.isUsableSubscriptionId(subId)) {
                mPreferredDataSubId = subId;
                PhoneSwitcher.getInstance().setOpportunisticDataSubscription(subId);
                PhoneSwitcher.getInstance().setPreferredDataSubscriptionId(subId);
            } else {
                notifyPreferredDataSubIdChanged();
                PhoneSwitcher.getInstance().unsetOpportunisticDataSubscription();
            }
            }

            return 0;
        } finally {
        } finally {
            Binder.restoreCallingIdentity(token);
            Binder.restoreCallingIdentity(token);
        }
        }
+8 −4
Original line number Original line Diff line number Diff line
@@ -40,6 +40,7 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.HandlerThread;
import android.os.Message;
import android.os.Message;
import android.os.Messenger;
import android.os.Messenger;
import android.telephony.PhoneCapability;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager;
import android.test.suitebuilder.annotation.SmallTest;
import android.test.suitebuilder.annotation.SmallTest;


@@ -88,6 +89,9 @@ public class PhoneSwitcherTest extends TelephonyTest {
    @Before
    @Before
    public void setUp() throws Exception {
    public void setUp() throws Exception {
        super.setUp(getClass().getSimpleName());
        super.setUp(getClass().getSimpleName());

        PhoneCapability phoneCapability = new PhoneCapability(1, 1, 0, null, false);
        doReturn(phoneCapability).when(mPhoneConfigurationManager).getCurrentPhoneCapability();
    }
    }


    @After
    @After
@@ -382,13 +386,13 @@ public class PhoneSwitcherTest extends TelephonyTest {
        assertTrue(mDataAllowed[0]);
        assertTrue(mDataAllowed[0]);


        // Set sub 2 as preferred sub should make phone 1 activated and phone 0 deactivated.
        // Set sub 2 as preferred sub should make phone 1 activated and phone 0 deactivated.
        mPhoneSwitcher.setPreferredDataSubscriptionId(2);
        mPhoneSwitcher.setOpportunisticDataSubscription(2);
        waitABit();
        waitABit();
        assertFalse(mDataAllowed[0]);
        assertFalse(mDataAllowed[0]);
        assertTrue(mDataAllowed[1]);
        assertTrue(mDataAllowed[1]);


        // Unset preferred sub should make default data sub (phone 0 / sub 1) activated again.
        // Unset preferred sub should make default data sub (phone 0 / sub 1) activated again.
        mPhoneSwitcher.setPreferredDataSubscriptionId(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
        mPhoneSwitcher.unsetOpportunisticDataSubscription();
        waitABit();
        waitABit();
        assertTrue(mDataAllowed[0]);
        assertTrue(mDataAllowed[0]);
        assertFalse(mDataAllowed[1]);
        assertFalse(mDataAllowed[1]);
@@ -437,7 +441,7 @@ public class PhoneSwitcherTest extends TelephonyTest {
        assertTrue(mPhoneSwitcher.shouldApplyNetworkRequest(mmsRequest, 1));
        assertTrue(mPhoneSwitcher.shouldApplyNetworkRequest(mmsRequest, 1));


        // Set sub 2 as preferred sub should make phone 1 preferredDataModem
        // Set sub 2 as preferred sub should make phone 1 preferredDataModem
        mPhoneSwitcher.setPreferredDataSubscriptionId(2);
        mPhoneSwitcher.setOpportunisticDataSubscription(2);
        waitABit();
        waitABit();
        verify(mMockRadioConfig).setPreferredDataModem(eq(1), any());
        verify(mMockRadioConfig).setPreferredDataModem(eq(1), any());
        verify(mActivePhoneSwitchHandler, times(2)).sendMessageAtTime(any(), anyLong());
        verify(mActivePhoneSwitchHandler, times(2)).sendMessageAtTime(any(), anyLong());
@@ -450,7 +454,7 @@ public class PhoneSwitcherTest extends TelephonyTest {
        clearInvocations(mActivePhoneSwitchHandler);
        clearInvocations(mActivePhoneSwitchHandler);


        // Unset preferred sub should make phone0 preferredDataModem again.
        // Unset preferred sub should make phone0 preferredDataModem again.
        mPhoneSwitcher.setPreferredDataSubscriptionId(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
        mPhoneSwitcher.unsetOpportunisticDataSubscription();
        waitABit();
        waitABit();
        verify(mMockRadioConfig).setPreferredDataModem(eq(0), any());
        verify(mMockRadioConfig).setPreferredDataModem(eq(0), any());
        verify(mActivePhoneSwitchHandler, times(2)).sendMessageAtTime(any(), anyLong());
        verify(mActivePhoneSwitchHandler, times(2)).sendMessageAtTime(any(), anyLong());
Loading