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

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

Merge changes from topic "140070796"

* changes:
  Use carrier config to define network validation cache ttl.
  For validation off case, do NOT switch data until new conn is up.
  Skip network validation if validated recently upon switching
  Do not set a sub as default data sub unless it's active.
parents 3c8b42a5 7870ec7f
Loading
Loading
Loading
Loading
+169 −8
Original line number Diff line number Diff line
@@ -16,6 +16,10 @@

package com.android.internal.telephony;

import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
import static android.telephony.CarrierConfigManager.KEY_DATA_SWITCH_VALIDATION_MIN_GAP_LONG;
import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.Network;
@@ -23,6 +27,12 @@ import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.TelephonyNetworkSpecifier;
import android.os.Handler;
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
import android.telephony.CellIdentity;
import android.telephony.CellIdentityLte;
import android.telephony.CellInfo;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.SubscriptionManager;
import android.util.Log;

@@ -30,6 +40,12 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.metrics.TelephonyMetrics;
import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent;

import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.concurrent.TimeUnit;

/**
 * This class will validate whether cellular network verified by Connectivity's
 * validation process. It listens request on a specific subId, sends a network request
@@ -37,6 +53,9 @@ import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent;
 */
public class CellularNetworkValidator {
    private static final String LOG_TAG = "NetworkValidator";
    // If true, upon validated network cache hit, we report validationDone only when
    // network becomes available. Otherwise, we report validationDone immediately.
    private static boolean sWaitForNetworkAvailableWhenCacheHit = true;

    // States of validator. Only one validation can happen at once.
    // IDLE: no validation going on.
@@ -50,6 +69,8 @@ public class CellularNetworkValidator {

    // Singleton instance.
    private static CellularNetworkValidator sInstance;
    @VisibleForTesting
    public static final long MAX_VALIDATION_CACHE_TTL = TimeUnit.DAYS.toMillis(1);

    private int mState = STATE_IDLE;
    private int mSubId;
@@ -66,6 +87,116 @@ public class CellularNetworkValidator {
    public ConnectivityNetworkCallback mNetworkCallback;
    @VisibleForTesting
    public Runnable mTimeoutCallback;
    private final ValidatedNetworkCache mValidatedNetworkCache = new ValidatedNetworkCache();

    private class ValidatedNetworkCache {
        // A cache with fixed size. It remembers 10 most recently successfully validated networks.
        private static final int VALIDATED_NETWORK_CACHE_SIZE = 10;
        private final PriorityQueue<ValidatedNetwork> mValidatedNetworkPQ =
                new PriorityQueue((Comparator<ValidatedNetwork>) (n1, n2) -> {
                    if (n1.mValidationTimeStamp < n2.mValidationTimeStamp) {
                        return -1;
                    } else if (n1.mValidationTimeStamp > n2.mValidationTimeStamp) {
                        return 1;
                    } else {
                        return 0;
                    }
                });
        private final Map<String, ValidatedNetwork> mValidatedNetworkMap = new HashMap();

        private final class ValidatedNetwork {
            ValidatedNetwork(String identity, long timeStamp) {
                mValidationIdentity = identity;
                mValidationTimeStamp = timeStamp;
            }
            void update(long timeStamp) {
                mValidationTimeStamp = timeStamp;
            }
            final String mValidationIdentity;
            long mValidationTimeStamp;
        }

        boolean isRecentlyValidated(int subId) {
            long cacheTtl = getValidationCacheTtl(subId);
            String networkIdentity = getValidationNetworkIdentity(subId);
            if (networkIdentity == null || !mValidatedNetworkMap.containsKey(networkIdentity)) {
                return false;
            }
            long validatedTime = mValidatedNetworkMap.get(networkIdentity).mValidationTimeStamp;
            boolean recentlyValidated = System.currentTimeMillis() - validatedTime < cacheTtl;
            logd("isRecentlyValidated on subId " + subId + " ? " + recentlyValidated);
            return recentlyValidated;
        }

        void storeLastValidationResult(int subId, boolean validated) {
            String networkIdentity = getValidationNetworkIdentity(subId);
            logd("storeLastValidationResult for subId " + subId
                    + (validated ? " validated." : " not validated."));
            if (networkIdentity == null) return;

            if (!validated) {
                // If validation failed, clear it from the cache.
                mValidatedNetworkPQ.remove(mValidatedNetworkMap.get(networkIdentity));
                mValidatedNetworkMap.remove(networkIdentity);
                return;
            }
            long time =  System.currentTimeMillis();
            ValidatedNetwork network = mValidatedNetworkMap.get(networkIdentity);
            if (network != null) {
                // Already existed in cache, update.
                network.update(time);
                // Re-add to re-sort.
                mValidatedNetworkPQ.remove(network);
                mValidatedNetworkPQ.add(network);
            } else {
                network = new ValidatedNetwork(networkIdentity, time);
                mValidatedNetworkMap.put(networkIdentity, network);
                mValidatedNetworkPQ.add(network);
            }
            // If exceeded max size, remove the one with smallest validation timestamp.
            if (mValidatedNetworkPQ.size() > VALIDATED_NETWORK_CACHE_SIZE) {
                ValidatedNetwork networkToRemove = mValidatedNetworkPQ.poll();
                mValidatedNetworkMap.remove(networkToRemove.mValidationIdentity);
            }
        }

        private String getValidationNetworkIdentity(int subId) {
            if (!SubscriptionManager.isUsableSubscriptionId(subId)) return null;
            SubscriptionController subController = SubscriptionController.getInstance();
            if (subController == null) return null;
            Phone phone = PhoneFactory.getPhone(subController.getPhoneId(subId));
            if (phone == null || phone.getServiceState() == null) return null;

            NetworkRegistrationInfo regInfo = phone.getServiceState().getNetworkRegistrationInfo(
                    DOMAIN_PS, TRANSPORT_TYPE_WWAN);
            if (regInfo == null || regInfo.getCellIdentity() == null) return null;

            CellIdentity cellIdentity = regInfo.getCellIdentity();
            // TODO: add support for other technologies.
            if (cellIdentity.getType() != CellInfo.TYPE_LTE
                    || cellIdentity.getMccString() == null || cellIdentity.getMncString() == null
                    || ((CellIdentityLte) cellIdentity).getTac() == CellInfo.UNAVAILABLE) {
                return null;
            }

            return cellIdentity.getMccString() + cellIdentity.getMncString() + "_"
                    + ((CellIdentityLte) cellIdentity).getTac() + "_" + subId;
        }

        private long getValidationCacheTtl(int subId) {
            long ttl = 0;
            CarrierConfigManager configManager = (CarrierConfigManager)
                    mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
            if (configManager != null) {
                PersistableBundle b = configManager.getConfigForSubId(subId);
                if (b != null) {
                    ttl = b.getLong(KEY_DATA_SWITCH_VALIDATION_MIN_GAP_LONG);
                }
            }
            // Ttl can't be bigger than one day for now.
            return Math.min(ttl, MAX_VALIDATION_CACHE_TTL);
        }
    }

    /**
     * Callback to pass in when starting validation.
@@ -74,7 +205,11 @@ public class CellularNetworkValidator {
        /**
         * Validation failed, passed or timed out.
         */
        void onValidationResult(boolean validated, int subId);
        void onValidationDone(boolean validated, int subId);
        /**
         * Called when a corresponding network becomes available.
         */
        void onNetworkAvailable(Network network, int subId);
    }

    /**
@@ -120,10 +255,9 @@ public class CellularNetworkValidator {
        // If it's already validating the same subscription, do nothing.
        if (subId == mSubId) return;

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

@@ -131,6 +265,12 @@ public class CellularNetworkValidator {
            stopValidation();
        }

        if (!sWaitForNetworkAvailableWhenCacheHit && mValidatedNetworkCache
                .isRecentlyValidated(subId)) {
            callback.onValidationDone(true, subId);
            return;
        }

        mState = STATE_VALIDATING;
        mSubId = subId;
        mTimeoutInMs = timeoutInMs;
@@ -147,6 +287,8 @@ public class CellularNetworkValidator {

        mTimeoutCallback = () -> {
            logd("timeout on subId " + subId + " validation.");
            // Remember latest validated network.
            mValidatedNetworkCache.storeLastValidationResult(subId, false);
            reportValidationResult(false, subId);
        };

@@ -208,7 +350,7 @@ public class CellularNetworkValidator {
        // 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);
            mValidationCallback.onValidationDone(passed, mSubId);
            if (!mReleaseAfterValidation && passed) {
                mState = STATE_VALIDATED;
            } else {
@@ -224,6 +366,12 @@ public class CellularNetworkValidator {
        mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
    }

    private synchronized void reportNetworkAvailable(Network network, int subId) {
        // If the validation result is not for current subId, do nothing.
        if (mSubId != subId) return;
        mValidationCallback.onNetworkAvailable(network, subId);
    }

    @VisibleForTesting
    public class ConnectivityNetworkCallback extends ConnectivityManager.NetworkCallback {
        private final int mSubId;
@@ -237,27 +385,38 @@ public class CellularNetworkValidator {
        @Override
        public void onAvailable(Network network) {
            logd("network onAvailable " + network);
            if (ConnectivityNetworkCallback.this.mSubId == CellularNetworkValidator.this.mSubId) {
            TelephonyMetrics.getInstance().writeNetworkValidate(
                    TelephonyEvent.NetworkValidationState.NETWORK_VALIDATION_STATE_AVAILABLE);
            // If it hits validation cache, we report as validation passed; otherwise we report
            // network is available.
            if (mValidatedNetworkCache.isRecentlyValidated(mSubId)) {
                reportValidationResult(true, ConnectivityNetworkCallback.this.mSubId);
            } else {
                reportNetworkAvailable(network, ConnectivityNetworkCallback.this.mSubId);
            }
        }

        @Override
        public void onLosing(Network network, int maxMsToLive) {
            logd("network onLosing " + network + " maxMsToLive " + maxMsToLive);
            mValidatedNetworkCache.storeLastValidationResult(
                    ConnectivityNetworkCallback.this.mSubId, false);
            reportValidationResult(false, ConnectivityNetworkCallback.this.mSubId);
        }

        @Override
        public void onLost(Network network) {
            logd("network onLost " + network);
            mValidatedNetworkCache.storeLastValidationResult(
                    ConnectivityNetworkCallback.this.mSubId, false);
            reportValidationResult(false, ConnectivityNetworkCallback.this.mSubId);
        }

        @Override
        public void onUnavailable() {
            logd("onUnavailable");
            mValidatedNetworkCache.storeLastValidationResult(
                    ConnectivityNetworkCallback.this.mSubId, false);
            reportValidationResult(false, ConnectivityNetworkCallback.this.mSubId);
        }

@@ -266,6 +425,8 @@ public class CellularNetworkValidator {
                NetworkCapabilities networkCapabilities) {
            if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
                logd("onValidated");
                mValidatedNetworkCache.storeLastValidationResult(
                        ConnectivityNetworkCallback.this.mSubId, true);
                reportValidationResult(true, ConnectivityNetworkCallback.this.mSubId);
            }
        }
+1 −1
Original line number Diff line number Diff line
@@ -292,7 +292,7 @@ public class MultiSimSettingController extends Handler {

        // If user is enabling a non-default non-opportunistic subscription, make it default.
        if (mSubController.getDefaultDataSubId() != subId && !mSubController.isOpportunistic(subId)
                && enable) {
                && enable && mSubController.isActiveSubId(subId)) {
            mSubController.setDefaultDataSubId(subId);
        }
    }
+81 −21
Original line number Diff line number Diff line
@@ -164,10 +164,25 @@ public class PhoneSwitcher extends Handler {
    @VisibleForTesting
    public final PhoneStateListener mPhoneStateListener;
    private final CellularNetworkValidator mValidator;
    private int mPendingSwitchSubId = INVALID_SUBSCRIPTION_ID;
    private boolean mPendingSwitchNeedValidation;

    @VisibleForTesting
    public final CellularNetworkValidator.ValidationCallback mValidationCallback =
            (validated, subId) -> Message.obtain(PhoneSwitcher.this,
            new CellularNetworkValidator.ValidationCallback() {
                @Override
                public void onValidationDone(boolean validated, int subId) {
                    Message.obtain(PhoneSwitcher.this,
                            EVENT_NETWORK_VALIDATION_DONE, subId, validated ? 1 : 0).sendToTarget();
                }

                @Override
                public void onNetworkAvailable(Network network, int subId) {
                    Message.obtain(PhoneSwitcher.this,
                            EVENT_NETWORK_AVAILABLE, subId, 0, network).sendToTarget();

                }
            };

    @UnsupportedAppUsage
    // How many phones (correspondingly logical modems) are allowed for PS attach. This is used
@@ -244,6 +259,7 @@ public class PhoneSwitcher extends Handler {
    // If it exists, remove the current mEmergencyOverride DDS override.
    @VisibleForTesting
    public static final int EVENT_MULTI_SIM_CONFIG_CHANGED        = 117;
    private static final int EVENT_NETWORK_AVAILABLE              = 118;

    // 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
@@ -522,6 +538,12 @@ public class PhoneSwitcher extends Handler {
                onValidationDone(subId, passed);
                break;
            }
            case EVENT_NETWORK_AVAILABLE: {
                int subId = msg.arg1;
                Network network = (Network) msg.obj;
                onNetworkAvailable(subId, network);
                break;
            }
            case EVENT_REMOVE_DEFAULT_NETWORK_CHANGE_CALLBACK: {
                removeDefaultNetworkChangeCallback();
                break;
@@ -1143,10 +1165,13 @@ public class PhoneSwitcher extends Handler {
        // Remove EVENT_NETWORK_VALIDATION_DONE. Don't handle validation result of previously subId
        // if queued.
        removeMessages(EVENT_NETWORK_VALIDATION_DONE);
        removeMessages(EVENT_NETWORK_AVAILABLE);

        int subIdToValidate = (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)
                ? mPrimaryDataSubId : subId;

        mPendingSwitchSubId = INVALID_SUBSCRIPTION_ID;

        if (mValidator.isValidating()) {
            mValidator.stopValidation();
            sendSetOpptCallbackHelper(mSetOpptSubCallback, SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED);
@@ -1167,22 +1192,37 @@ public class PhoneSwitcher extends Handler {

        // If validation feature is not supported, set it directly. Otherwise,
        // start validation on the subscription first.
        if (mValidator.isValidationFeatureSupported() && needValidation) {
        if (!mValidator.isValidationFeatureSupported()) {
            setOpportunisticSubscriptionInternal(subId);
            sendSetOpptCallbackHelper(callback, SET_OPPORTUNISTIC_SUB_SUCCESS);
            return;
        }

        // Even if needValidation is false, we still send request to validator. The reason is we
        // want to delay data switch until network is available on the target sub, to have a
        // smoothest transition possible.
        // In this case, even if data connection eventually failed in 2 seconds, we still
        // confirm the switch, to maximally respect the request.
        mPendingSwitchSubId = subIdToValidate;
        mPendingSwitchNeedValidation = needValidation;
        mSetOpptSubCallback = callback;
        long validationTimeout = getValidationTimeout(subIdToValidate, needValidation);
        mValidator.validate(subIdToValidate, validationTimeout, false, mValidationCallback);
    }

    private long getValidationTimeout(int subId, boolean needValidation) {
        if (!needValidation) return DEFAULT_VALIDATION_EXPIRATION_TIME;

        long validationTimeout = DEFAULT_VALIDATION_EXPIRATION_TIME;
        CarrierConfigManager configManager = (CarrierConfigManager)
                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
        if (configManager != null) {
                PersistableBundle b = configManager.getConfigForSubId(subIdToValidate);
            PersistableBundle b = configManager.getConfigForSubId(subId);
            if (b != null) {
                validationTimeout = b.getLong(KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG);
            }
        }
            mValidator.validate(subIdToValidate, validationTimeout, false, mValidationCallback);
        } else {
            setOpportunisticSubscriptionInternal(subId);
            sendSetOpptCallbackHelper(callback, SET_OPPORTUNISTIC_SUB_SUCCESS);
        }
        return validationTimeout;
    }

    private void sendSetOpptCallbackHelper(ISetOpportunisticDataCallback callback, int result) {
@@ -1204,15 +1244,13 @@ public class PhoneSwitcher extends Handler {
        }
    }

    private void onValidationDone(int subId, boolean passed) {
        log("onValidationDone: " + (passed ? "passed" : "failed")
                + " on subId " + subId);
    private void confirmSwitch(int subId, boolean confirm) {
        log("confirmSwitch: subId " + subId + (confirm ? " confirmed." : " cancelled."));
        int resultForCallBack;

        if (!mSubscriptionController.isActiveSubId(subId)) {
            log("onValidationDone: subId " + subId + " is no longer active");
            log("confirmSwitch: subId " + subId + " is no longer active");
            resultForCallBack = SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION;
        } else if (!passed) {
        } else if (!confirm) {
            resultForCallBack = SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED;
        } else {
            if (mSubscriptionController.isOpportunistic(subId)) {
@@ -1227,6 +1265,28 @@ public class PhoneSwitcher extends Handler {
        // Trigger callback if needed
        sendSetOpptCallbackHelper(mSetOpptSubCallback, resultForCallBack);
        mSetOpptSubCallback = null;
        mPendingSwitchSubId = INVALID_SUBSCRIPTION_ID;
    }

    private void onNetworkAvailable(int subId, Network network) {
        log("onNetworkAvailable: on subId " + subId);
        // Do nothing unless pending switch matches target subId and it doesn't require
        // validation pass.
        if (mPendingSwitchSubId == INVALID_SUBSCRIPTION_ID || mPendingSwitchSubId != subId
                || mPendingSwitchNeedValidation) {
            return;
        }
        confirmSwitch(subId, true);
        mValidator.stopValidation();
    }

    private void onValidationDone(int subId, boolean passed) {
        log("onValidationDone: " + (passed ? "passed" : "failed") + " on subId " + subId);
        if (mPendingSwitchSubId == INVALID_SUBSCRIPTION_ID || mPendingSwitchSubId != subId) return;

        // If validation failed and mPendingSwitch.mNeedValidation is false, we still confirm
        // the switch.
        confirmSwitch(subId, passed || !mPendingSwitchNeedValidation);
    }

    /**
+272 −35

File changed.

Preview size limit exceeded, changes collapsed.

+39 −0
Original line number Diff line number Diff line
@@ -582,4 +582,43 @@ public class MultiSimSettingControllerTest extends TelephonyTest {
        assertEquals(EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_DATA,
                intent.getIntExtra(EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE, -1));
    }

    @Test
    @SmallTest
    // b/146446143
    public void testGroupChangeOnInactiveSub_shouldNotMarkAsDefaultDataSub() throws Exception {
        // Make sub1 and sub3 as active sub.
        doReturn(false).when(mSubControllerMock).isActiveSubId(2);
        doReturn(true).when(mSubControllerMock).isActiveSubId(3);
        doReturn(SubscriptionManager.INVALID_PHONE_INDEX).when(mSubControllerMock).getPhoneId(2);
        doReturn(1).when(mSubControllerMock).getPhoneId(3);
        doReturn(3).when(mPhoneMock2).getSubId();
        List<SubscriptionInfo> infoList = Arrays.asList(mSubInfo1, mSubInfo3);
        doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString(),
                nullable(String.class));
        doReturn(new int[]{1, 3}).when(mSubControllerMock).getActiveSubIdList(anyBoolean());
        doReturn(Arrays.asList(mSubInfo2, mSubInfo3, mSubInfo4)).when(mSubControllerMock)
                .getSubscriptionsInGroup(any(), anyString(), nullable(String.class));

        // Sub 3 and sub 2's mobile data are enabled, and sub 3 is the default data sub.
        doReturn(3).when(mSubControllerMock).getDefaultDataSubId();
        GlobalSettingsHelper.setBoolean(mContext, Settings.Global.MOBILE_DATA, 1, false);
        GlobalSettingsHelper.setBoolean(mContext, Settings.Global.MOBILE_DATA, 2, true);
        GlobalSettingsHelper.setBoolean(mContext, Settings.Global.MOBILE_DATA, 3, true);
        doReturn(false).when(mPhoneMock1).isUserDataEnabled();
        doReturn(true).when(mPhoneMock2).isUserDataEnabled();
        // Sub 2 should have mobile data off, but it shouldn't happen until carrier configs are
        // loaded on both subscriptions.
        mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 1);
        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1, 3);
        processAllMessages();

        // Mark sub3 as oppt and notify grouping
        doReturn(true).when(mSubControllerMock).isOpportunistic(3);
        mMultiSimSettingControllerUT.notifySubscriptionGroupChanged(mGroupUuid1);
        processAllMessages();
        // Shouldn't mark sub 2 as default data, as sub 2 is in active.
        verify(mSubControllerMock, never()).setDefaultDataSubId(2);
    }
}
Loading