Loading src/java/com/android/internal/telephony/CellularNetworkValidator.java 0 → 100644 +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); } } src/java/com/android/internal/telephony/PhoneFactory.java +3 −0 Original line number Original line Diff line number Diff line Loading @@ -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>(); Loading Loading @@ -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(); Loading src/java/com/android/internal/telephony/PhoneSwitcher.java +63 −6 Original line number Original line Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 Loading @@ -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. */ */ Loading Loading @@ -162,6 +169,7 @@ public class PhoneSwitcher extends Handler { onPhoneCapabilityChangedInternal(capability); onPhoneCapabilityChangedInternal(capability); } } }; }; mValidator = CellularNetworkValidator.getInstance(); } } @VisibleForTesting @VisibleForTesting Loading Loading @@ -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 Loading Loading @@ -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; } } } } } Loading Loading @@ -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; } } Loading Loading @@ -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; Loading src/java/com/android/internal/telephony/SubscriptionController.java +5 −7 Original line number Original line Diff line number Diff line Loading @@ -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); } } Loading tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java +8 −4 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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]); Loading Loading @@ -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()); Loading @@ -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 Loading
src/java/com/android/internal/telephony/CellularNetworkValidator.java 0 → 100644 +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); } }
src/java/com/android/internal/telephony/PhoneFactory.java +3 −0 Original line number Original line Diff line number Diff line Loading @@ -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>(); Loading Loading @@ -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(); Loading
src/java/com/android/internal/telephony/PhoneSwitcher.java +63 −6 Original line number Original line Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 Loading @@ -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. */ */ Loading Loading @@ -162,6 +169,7 @@ public class PhoneSwitcher extends Handler { onPhoneCapabilityChangedInternal(capability); onPhoneCapabilityChangedInternal(capability); } } }; }; mValidator = CellularNetworkValidator.getInstance(); } } @VisibleForTesting @VisibleForTesting Loading Loading @@ -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 Loading Loading @@ -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; } } } } } Loading Loading @@ -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; } } Loading Loading @@ -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; Loading
src/java/com/android/internal/telephony/SubscriptionController.java +5 −7 Original line number Original line Diff line number Diff line Loading @@ -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); } } Loading
tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java +8 −4 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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]); Loading Loading @@ -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()); Loading @@ -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