Replaced TelephonyNetworkFactory with TelephonyNetworkProvider
Adapted the new network provider model. The new TelephonyNetworkProvider
is a singleton responsible for providing all telephony related networks
including networks on cellular and IWLAN across all active SIMs.
Flag: com.android.internal.telephony.flags.support_network_provider
Fix: 343370895
Test: Comprehensive telephony tests including mobile data and voice call
tests.
Test: atest TelephonyNetworkProviderTest
Change-Id: I99b236131606b83c2733a313715c313486b9a953
diff --git a/flags/data.aconfig b/flags/data.aconfig
index 80d6f61..bd9b21e 100644
--- a/flags/data.aconfig
+++ b/flags/data.aconfig
@@ -181,4 +181,14 @@
bug:"353723350"
}
+# OWNER=jackyu TARGET=25Q1
+flag {
+ name: "support_network_provider"
+ namespace: "telephony"
+ description: "Deprecate network factory and adapt the new network provider model from connectivity service"
+ bug: "343370895"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/src/java/com/android/internal/telephony/PhoneFactory.java b/src/java/com/android/internal/telephony/PhoneFactory.java
index 2cd6021..f7ce388 100644
--- a/src/java/com/android/internal/telephony/PhoneFactory.java
+++ b/src/java/com/android/internal/telephony/PhoneFactory.java
@@ -46,6 +46,7 @@
import com.android.internal.telephony.data.CellularNetworkValidator;
import com.android.internal.telephony.data.PhoneSwitcher;
import com.android.internal.telephony.data.TelephonyNetworkFactory;
+import com.android.internal.telephony.data.TelephonyNetworkProvider;
import com.android.internal.telephony.euicc.EuiccCardController;
import com.android.internal.telephony.euicc.EuiccController;
import com.android.internal.telephony.flags.FeatureFlags;
@@ -101,6 +102,7 @@
static private SimultaneousCallingTracker sSimultaneousCallingTracker;
static private PhoneSwitcher sPhoneSwitcher;
static private TelephonyNetworkFactory[] sTelephonyNetworkFactories;
+ private static TelephonyNetworkProvider sTelephonyNetworkProvider;
static private NotificationChannelController sNotificationChannelController;
static private CellularNetworkValidator sCellularNetworkValidator;
@@ -285,9 +287,15 @@
sNotificationChannelController = new NotificationChannelController(context);
- for (int i = 0; i < numPhones; i++) {
- sTelephonyNetworkFactories[i] = new TelephonyNetworkFactory(
- Looper.myLooper(), sPhones[i], featureFlags);
+ if (featureFlags.supportNetworkProvider()) {
+ // Create the TelephonyNetworkProvider instance, which is a singleton.
+ sTelephonyNetworkProvider = new TelephonyNetworkProvider(Looper.myLooper(),
+ context, featureFlags);
+ } else {
+ for (int i = 0; i < numPhones; i++) {
+ sTelephonyNetworkFactories[i] = new TelephonyNetworkFactory(
+ Looper.myLooper(), sPhones[i], featureFlags);
+ }
}
}
}
@@ -312,7 +320,10 @@
sPhones = copyOf(sPhones, activeModemCount);
sCommandsInterfaces = copyOf(sCommandsInterfaces, activeModemCount);
- sTelephonyNetworkFactories = copyOf(sTelephonyNetworkFactories, activeModemCount);
+
+ if (!sFeatureFlags.supportNetworkProvider()) {
+ sTelephonyNetworkFactories = copyOf(sTelephonyNetworkFactories, activeModemCount);
+ }
int cdmaSubscription = CdmaSubscriptionSourceManager.getDefault(context);
for (int i = prevActiveModemCount; i < activeModemCount; i++) {
@@ -324,8 +335,11 @@
PackageManager.FEATURE_TELEPHONY_IMS)) {
sPhones[i].createImsPhone();
}
- sTelephonyNetworkFactories[i] = new TelephonyNetworkFactory(
- Looper.myLooper(), sPhones[i], sFeatureFlags);
+
+ if (!sFeatureFlags.supportNetworkProvider()) {
+ sTelephonyNetworkFactories[i] = new TelephonyNetworkFactory(
+ Looper.myLooper(), sPhones[i], sFeatureFlags);
+ }
}
}
}
@@ -393,6 +407,10 @@
}
}
+ public static TelephonyNetworkProvider getNetworkProvider() {
+ return sTelephonyNetworkProvider;
+ }
+
/**
* Get the network factory associated with a given phone ID.
* @param phoneId the phone id
@@ -579,13 +597,22 @@
pw.flush();
pw.println("++++++++++++++++++++++++++++++++");
- sTelephonyNetworkFactories[i].dump(fd, pw, args);
+ if (!sFeatureFlags.supportNetworkProvider()) {
+ sTelephonyNetworkFactories[i].dump(fd, pw, args);
+ }
pw.flush();
pw.decreaseIndent();
pw.println("++++++++++++++++++++++++++++++++");
}
+ pw.increaseIndent();
+ if (sFeatureFlags.supportNetworkProvider()) {
+ sTelephonyNetworkProvider.dump(fd, pw, args);
+ }
+ pw.decreaseIndent();
+ pw.println("++++++++++++++++++++++++++++++++");
+
pw.println("UiccController:");
pw.increaseIndent();
try {
diff --git a/src/java/com/android/internal/telephony/RadioConfig.java b/src/java/com/android/internal/telephony/RadioConfig.java
index da20639..b4db14c 100644
--- a/src/java/com/android/internal/telephony/RadioConfig.java
+++ b/src/java/com/android/internal/telephony/RadioConfig.java
@@ -466,7 +466,7 @@
RILRequest rr = obtainRequest(RIL_REQUEST_SET_PREFERRED_DATA_MODEM,
result, mDefaultWorkSource);
if (DBG) {
- logd(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+ logd(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest) + " " + modemId);
}
try {
proxy.setPreferredDataModem(rr.mSerial, modemId);
diff --git a/src/java/com/android/internal/telephony/data/DataNetwork.java b/src/java/com/android/internal/telephony/data/DataNetwork.java
index 2f0c0ff..e66ea69 100644
--- a/src/java/com/android/internal/telephony/data/DataNetwork.java
+++ b/src/java/com/android/internal/telephony/data/DataNetwork.java
@@ -100,6 +100,7 @@
import com.android.internal.telephony.data.DataRetryManager.DataRetryEntry;
import com.android.internal.telephony.data.DataSettingsManager.DataSettingsManagerCallback;
import com.android.internal.telephony.data.LinkBandwidthEstimator.LinkBandwidthEstimatorCallback;
+import com.android.internal.telephony.data.PhoneSwitcher.PhoneSwitcherCallback;
import com.android.internal.telephony.data.TelephonyNetworkAgent.TelephonyNetworkAgentCallback;
import com.android.internal.telephony.flags.FeatureFlags;
import com.android.internal.telephony.metrics.DataCallSessionStats;
@@ -271,6 +272,9 @@
/** Event for response to data network validation request. */
private static final int EVENT_DATA_NETWORK_VALIDATION_RESPONSE = 29;
+ /** Event for preferred data subscription changed. */
+ private static final int EVENT_PREFERRED_DATA_SUBSCRIPTION_CHANGED = 30;
+
/** Invalid context id. */
private static final int INVALID_CID = -1;
@@ -591,6 +595,10 @@
@NonNull
private final DataNetworkController mDataNetworkController;
+ /** Phone switcher which is responsible to determine which phone to route network request. */
+ @NonNull
+ private final PhoneSwitcher mPhoneSwitcher;
+
/** Data network controller callback. */
@NonNull
private final DataNetworkController.DataNetworkControllerCallback
@@ -676,6 +684,13 @@
/** Whether the current data network is congested. */
private boolean mCongested = false;
+ /**
+ * Whether the current data network is on preferred data modem.
+ *
+ * @see PhoneSwitcher#getPreferredDataPhoneId()
+ */
+ private boolean mOnPreferredDataPhone;
+
/** The network requests associated with this data network */
@NonNull
private final NetworkRequestList mAttachedNetworkRequestList =
@@ -815,6 +830,12 @@
private PreciseDataConnectionState mPreciseDataConnectionState;
/**
+ * Callback to listen event from {@link PhoneSwitcher}.
+ */
+ @NonNull
+ private PhoneSwitcherCallback mPhoneSwitcherCallback;
+
+ /**
* The network bandwidth.
*/
public static class NetworkBandwidth {
@@ -1027,6 +1048,8 @@
mAccessNetworksManager = phone.getAccessNetworksManager();
mVcnManager = mPhone.getContext().getSystemService(VcnManager.class);
mDataNetworkController = phone.getDataNetworkController();
+ mPhoneSwitcher = PhoneSwitcher.getInstance();
+ mOnPreferredDataPhone = phone.getPhoneId() == mPhoneSwitcher.getPreferredDataPhoneId();
mDataNetworkControllerCallback = new DataNetworkController.DataNetworkControllerCallback(
getHandler()::post) {
@Override
@@ -1156,14 +1179,25 @@
configBuilder.setNat64DetectionEnabled(false);
}
- final NetworkFactory factory = PhoneFactory.getNetworkFactory(
- mPhone.getPhoneId());
- final NetworkProvider provider = (null == factory) ? null : factory.getProvider();
+ NetworkProvider provider;
+ if (mFlags.supportNetworkProvider()) {
+ provider = PhoneFactory.getNetworkProvider();
+ } else {
+ final NetworkFactory factory = PhoneFactory.getNetworkFactory(
+ mPhone.getPhoneId());
+ provider = (null == factory) ? null : factory.getProvider();
+ }
- mNetworkScore = new NetworkScore.Builder()
- .setKeepConnectedReason(isHandoverInProgress()
+ NetworkScore.Builder builder = new NetworkScore.Builder()
+ .setKeepConnectedReason(isHandoverInProgress()
? NetworkScore.KEEP_CONNECTED_FOR_HANDOVER
- : NetworkScore.KEEP_CONNECTED_NONE).build();
+ : NetworkScore.KEEP_CONNECTED_NONE);
+ if (mFlags.supportNetworkProvider()) {
+ builder.setTransportPrimary(mOnPreferredDataPhone);
+ }
+ mNetworkScore = builder.build();
+ logl("mNetworkScore: isPrimary=" + mNetworkScore.isTransportPrimary()
+ + ", keepConnectedReason=" + mNetworkScore.getKeepConnectedReason());
return new TelephonyNetworkAgent(mPhone, getHandler().getLooper(), this,
mNetworkScore, configBuilder.build(), provider,
@@ -1225,6 +1259,16 @@
mDataNetworkController.getDataSettingsManager()
.registerCallback(mDataSettingsManagerCallback);
+ if (mFlags.supportNetworkProvider()) {
+ mPhoneSwitcherCallback = new PhoneSwitcherCallback(Runnable::run) {
+ @Override
+ public void onPreferredDataPhoneIdChanged(int phoneId) {
+ sendMessage(EVENT_PREFERRED_DATA_SUBSCRIPTION_CHANGED, phoneId, 0);
+ }
+ };
+ mPhoneSwitcher.registerCallback(mPhoneSwitcherCallback);
+ }
+
mPhone.getDisplayInfoController().registerForTelephonyDisplayInfoChanged(
getHandler(), EVENT_DISPLAY_INFO_CHANGED, null);
mPhone.getServiceStateTracker().registerForServiceStateChanged(getHandler(),
@@ -1318,6 +1362,9 @@
mPhone.getServiceStateTracker().unregisterForServiceStateChanged(getHandler());
mPhone.getDisplayInfoController().unregisterForTelephonyDisplayInfoChanged(
getHandler());
+ if (mFlags.supportNetworkProvider()) {
+ mPhoneSwitcher.unregisterCallback(mPhoneSwitcherCallback);
+ }
mDataNetworkController.getDataSettingsManager()
.unregisterCallback(mDataSettingsManagerCallback);
mRil.unregisterForPcoData(getHandler());
@@ -1351,13 +1398,13 @@
}
case EVENT_ATTACH_NETWORK_REQUEST: {
onAttachNetworkRequests((NetworkRequestList) msg.obj);
- updateNetworkScore(isHandoverInProgress());
+ updateNetworkScore();
break;
}
case EVENT_DETACH_NETWORK_REQUEST: {
onDetachNetworkRequest((TelephonyNetworkRequest) msg.obj,
msg.arg1 != 0 /* shouldRetry */);
- updateNetworkScore(isHandoverInProgress());
+ updateNetworkScore();
break;
}
case EVENT_DETACH_ALL_NETWORK_REQUESTS: {
@@ -1428,6 +1475,12 @@
// handle the resultCode in response for the request.
handleDataNetworkValidationRequestResultCode(msg.arg1 /* resultCode */);
break;
+ case EVENT_PREFERRED_DATA_SUBSCRIPTION_CHANGED:
+ mOnPreferredDataPhone = mPhone.getPhoneId() == msg.arg1;
+ logl("Preferred data phone id changed to " + msg.arg1
+ + ", mOnPreferredDataPhone=" + mOnPreferredDataPhone);
+ updateNetworkScore();
+ break;
default:
loge("Unhandled event " + eventToString(msg.what));
break;
@@ -3314,6 +3367,12 @@
return mLinkStatus;
}
+ /**
+ * Update the network score and report to connectivity service if necessary.
+ */
+ private void updateNetworkScore() {
+ updateNetworkScore(isHandoverInProgress());
+ }
/**
* Update the network score and report to connectivity service if necessary.
@@ -3323,10 +3382,18 @@
private void updateNetworkScore(boolean keepConnectedForHandover) {
int connectedReason = keepConnectedForHandover
? NetworkScore.KEEP_CONNECTED_FOR_HANDOVER : NetworkScore.KEEP_CONNECTED_NONE;
- if (mNetworkScore.getKeepConnectedReason() != connectedReason) {
- mNetworkScore = new NetworkScore.Builder()
- .setKeepConnectedReason(connectedReason).build();
+ if (mNetworkScore.getKeepConnectedReason() != connectedReason
+ || (mFlags.supportNetworkProvider()
+ && mNetworkScore.isTransportPrimary() != mOnPreferredDataPhone)) {
+ NetworkScore.Builder builder = new NetworkScore.Builder()
+ .setKeepConnectedReason(connectedReason);
+ if (mFlags.supportNetworkProvider()) {
+ builder.setTransportPrimary(mOnPreferredDataPhone);
+ }
+ mNetworkScore = builder.build();
mNetworkAgent.sendNetworkScore(mNetworkScore);
+ logl("updateNetworkScore: isPrimary=" + mNetworkScore.isTransportPrimary()
+ + ", keepConnectedForHandover=" + keepConnectedForHandover);
}
}
@@ -4055,12 +4122,14 @@
pw.println("Tag: " + name());
pw.increaseIndent();
pw.println("mSubId=" + mSubId);
+ pw.println("mOnPreferredDataPhone=" + mOnPreferredDataPhone);
pw.println("mTransport=" + AccessNetworkConstants.transportTypeToString(mTransport));
pw.println("mLastKnownDataNetworkType=" + TelephonyManager
.getNetworkTypeName(mLastKnownDataNetworkType));
pw.println("WWAN cid=" + mCid.get(AccessNetworkConstants.TRANSPORT_TYPE_WWAN));
pw.println("WLAN cid=" + mCid.get(AccessNetworkConstants.TRANSPORT_TYPE_WLAN));
pw.println("mNetworkScore=" + mNetworkScore);
+ pw.println("keepConnectedReason=" + mNetworkScore.getKeepConnectedReason());
pw.println("mDataAllowedReason=" + mDataAllowedReason);
pw.println("mPduSessionId=" + mPduSessionId);
pw.println("mDataProfile=" + mDataProfile);
diff --git a/src/java/com/android/internal/telephony/data/DataNetworkController.java b/src/java/com/android/internal/telephony/data/DataNetworkController.java
index 2572e2f..1b0af47 100644
--- a/src/java/com/android/internal/telephony/data/DataNetworkController.java
+++ b/src/java/com/android/internal/telephony/data/DataNetworkController.java
@@ -2516,6 +2516,30 @@
}
private void onRemoveNetworkRequest(@NonNull TelephonyNetworkRequest request) {
+ if (mFeatureFlags.supportNetworkProvider()) {
+ if (!mAllNetworkRequestList.remove(request)) {
+ loge("onRemoveNetworkRequest: Network request does not exist. " + request);
+ return;
+ }
+
+ if (request.hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)) {
+ mImsThrottleCounter.addOccurrence();
+ mLastReleasedImsRequestCapabilities = request.getCapabilities();
+ mLastImsOperationIsRelease = true;
+ }
+
+ if (request.getAttachedNetwork() != null) {
+ request.getAttachedNetwork().detachNetworkRequest(
+ request, false /* shouldRetry */);
+ }
+
+ request.setState(TelephonyNetworkRequest.REQUEST_STATE_UNSATISFIED);
+ request.setEvaluation(null);
+
+ log("onRemoveNetworkRequest: Removed " + request);
+ return;
+ }
+
// The request generated from telephony network factory does not contain the information
// the original request has, for example, attached data network. We need to find the
// original one.
@@ -2536,7 +2560,7 @@
if (networkRequest.getAttachedNetwork() != null) {
networkRequest.getAttachedNetwork().detachNetworkRequest(
- networkRequest, false /* shouldRetry */);
+ networkRequest, false /* shouldRetry */);
}
log("onRemoveNetworkRequest: Removed " + networkRequest);
}
diff --git a/src/java/com/android/internal/telephony/data/PhoneSwitcher.java b/src/java/com/android/internal/telephony/data/PhoneSwitcher.java
index f1f7b49..1005bb7 100644
--- a/src/java/com/android/internal/telephony/data/PhoneSwitcher.java
+++ b/src/java/com/android/internal/telephony/data/PhoneSwitcher.java
@@ -30,6 +30,7 @@
import static java.util.Arrays.copyOf;
+import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
@@ -63,6 +64,7 @@
import android.telephony.ims.RegistrationManager;
import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.LocalLog;
import android.util.Log;
import android.util.SparseIntArray;
@@ -183,6 +185,27 @@
}
}
+ /**
+ * Callback from PhoneSwitcher
+ */
+ public static class PhoneSwitcherCallback extends DataCallback {
+ /**
+ * Constructor
+ *
+ * @param executor The executor of the callback.
+ */
+ public PhoneSwitcherCallback(@NonNull @CallbackExecutor Executor executor) {
+ super(executor);
+ }
+
+ /**
+ * Called when preferred data phone id changed.
+ *
+ * @param phoneId The phone id of the preferred data.
+ */
+ public void onPreferredDataPhoneIdChanged(int phoneId) {}
+ }
+
@NonNull
private final NetworkRequestList mNetworkRequestList = new NetworkRequestList();
protected final RegistrantList mActivePhoneRegistrants;
@@ -261,6 +284,10 @@
private ISetOpportunisticDataCallback mSetOpptSubCallback;
+ /** Phone switcher callbacks. */
+ @NonNull
+ private final Set<PhoneSwitcherCallback> mPhoneSwitcherCallbacks = new ArraySet<>();
+
private static final int EVENT_PRIMARY_DATA_SUB_CHANGED = 101;
protected static final int EVENT_SUBSCRIPTION_CHANGED = 102;
private static final int EVENT_REQUEST_NETWORK = 103;
@@ -468,6 +495,24 @@
}
}
+ /**
+ * Register the callback for receiving information from {@link PhoneSwitcher}.
+ *
+ * @param callback The callback.
+ */
+ public void registerCallback(@NonNull PhoneSwitcherCallback callback) {
+ mPhoneSwitcherCallbacks.add(callback);
+ }
+
+ /**
+ * Unregister the callback for receiving information from {@link PhoneSwitcher}.
+ *
+ * @param callback The callback.
+ */
+ public void unregisterCallback(@NonNull PhoneSwitcherCallback callback) {
+ mPhoneSwitcherCallbacks.remove(callback);
+ }
+
private void evaluateIfImmediateDataSwitchIsNeeded(String evaluationReason, int switchReason) {
if (onEvaluate(REQUESTS_UNCHANGED, evaluationReason)) {
logDataSwitchEvent(mPreferredDataSubId.get(),
@@ -607,33 +652,35 @@
mConnectivityManager.registerDefaultNetworkCallback(mDefaultNetworkCallback, this);
- final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder()
- .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
- .addCapability(NetworkCapabilities.NET_CAPABILITY_IA)
- .addCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL)
- .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
- .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_1)
- .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_2)
- .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_3)
- .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_4)
- .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_5)
- .setNetworkSpecifier(new MatchAllNetworkSpecifier());
- TelephonyNetworkRequest.getAllSupportedNetworkCapabilities()
- .forEach(builder::addCapability);
+ if (!mFlags.supportNetworkProvider()) {
+ final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_IA)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+ .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_1)
+ .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_2)
+ .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_3)
+ .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_4)
+ .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_5)
+ .setNetworkSpecifier(new MatchAllNetworkSpecifier());
+ TelephonyNetworkRequest.getAllSupportedNetworkCapabilities()
+ .forEach(builder::addCapability);
- if (mFlags.satelliteInternet()) {
- // TODO: b/328622096 remove the try/catch
- try {
- builder.addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE);
- } catch (IllegalArgumentException exception) {
- loge("TRANSPORT_SATELLITE is not supported.");
+ if (mFlags.satelliteInternet()) {
+ // TODO: b/328622096 remove the try/catch
+ try {
+ builder.addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE);
+ } catch (IllegalArgumentException exception) {
+ loge("TRANSPORT_SATELLITE is not supported.");
+ }
}
- }
- NetworkFactory networkFactory = new PhoneSwitcherNetworkRequestListener(looper, context,
- builder.build(), this);
- // we want to see all requests
- networkFactory.registerIgnoringScore();
+ NetworkFactory networkFactory = new PhoneSwitcherNetworkRequestListener(looper, context,
+ builder.build(), this);
+ // we want to see all requests
+ networkFactory.registerIgnoringScore();
+ }
updateHalCommandToUse();
@@ -1053,11 +1100,12 @@
return false;
}
+ // TODO: Remove after removing TelephonyNetworkFactory
private static class PhoneSwitcherNetworkRequestListener extends NetworkFactory {
private final PhoneSwitcher mPhoneSwitcher;
public PhoneSwitcherNetworkRequestListener (Looper l, Context c,
NetworkCapabilities nc, PhoneSwitcher ps) {
- super(l, c, "PhoneSwitcherNetworkRequstListener", nc);
+ super(l, c, "PhoneSwitcherNetworkRequestListener", nc);
mPhoneSwitcher = ps;
}
@@ -1078,7 +1126,13 @@
}
}
- private void onRequestNetwork(NetworkRequest networkRequest) {
+ /**
+ * Called when receiving a network request.
+ *
+ * @param networkRequest The network request.
+ */
+ // TODO: Transform to TelephonyNetworkRequest after removing TelephonyNetworkFactory
+ public void onRequestNetwork(@NonNull NetworkRequest networkRequest) {
TelephonyNetworkRequest telephonyNetworkRequest = new TelephonyNetworkRequest(
networkRequest, PhoneFactory.getDefaultPhone(), mFlags);
if (!mNetworkRequestList.contains(telephonyNetworkRequest)) {
@@ -1087,7 +1141,13 @@
}
}
- private void onReleaseNetwork(NetworkRequest networkRequest) {
+ /**
+ * Called when releasing a network request.
+ *
+ * @param networkRequest The network request to release.
+ */
+ // TODO: Transform to TelephonyNetworkRequest after removing TelephonyNetworkFactory
+ public void onReleaseNetwork(@NonNull NetworkRequest networkRequest) {
TelephonyNetworkRequest telephonyNetworkRequest = new TelephonyNetworkRequest(
networkRequest, PhoneFactory.getDefaultPhone(), mFlags);
if (mNetworkRequestList.remove(telephonyNetworkRequest)) {
@@ -1101,18 +1161,6 @@
mDefaultNetworkCallback.mSwitchReason = reason;
}
- private void collectRequestNetworkMetrics(NetworkRequest networkRequest) {
- // Request network for MMS will temporary disable the network on default data subscription,
- // this only happen on multi-sim device.
- if (mActiveModemCount > 1 && networkRequest.hasCapability(
- NetworkCapabilities.NET_CAPABILITY_MMS)) {
- OnDemandDataSwitch onDemandDataSwitch = new OnDemandDataSwitch();
- onDemandDataSwitch.apn = TelephonyEvent.ApnType.APN_TYPE_MMS;
- onDemandDataSwitch.state = TelephonyEvent.EventState.EVENT_STATE_START;
- TelephonyMetrics.getInstance().writeOnDemandDataSwitch(onDemandDataSwitch);
- }
- }
-
private void collectReleaseNetworkMetrics(NetworkRequest networkRequest) {
// Release network for MMS will recover the network on default data subscription, this only
// happen on multi-sim device.
@@ -1561,6 +1609,7 @@
* If preferred phone changes, or phone activation status changes, registrants
* will be notified.
*/
+ // TODO: Remove after removing TelephonyNetworkFactory
public void registerForActivePhoneSwitch(Handler h, int what, Object o) {
Registrant r = new Registrant(h, what, o);
mActivePhoneRegistrants.add(r);
@@ -1982,6 +2031,10 @@
// Notify all registrants
mActivePhoneRegistrants.notifyRegistrants();
notifyPreferredDataSubIdChanged();
+ if (mFlags.supportNetworkProvider()) {
+ mPhoneSwitcherCallbacks.forEach(callback -> callback.invokeFromExecutor(
+ () -> callback.onPreferredDataPhoneIdChanged(phoneId)));
+ }
}
private boolean isPhoneIdValidForRetry(int phoneId) {
diff --git a/src/java/com/android/internal/telephony/data/TelephonyNetworkProvider.java b/src/java/com/android/internal/telephony/data/TelephonyNetworkProvider.java
new file mode 100644
index 0000000..63edefa
--- /dev/null
+++ b/src/java/com/android/internal/telephony/data/TelephonyNetworkProvider.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright 2024 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.data;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.MatchAllNetworkSpecifier;
+import android.net.NetworkAgent;
+import android.net.NetworkCapabilities;
+import android.net.NetworkProvider;
+import android.net.NetworkProvider.NetworkOfferCallback;
+import android.net.NetworkRequest;
+import android.net.NetworkScore;
+import android.os.Handler;
+import android.os.Looper;
+import android.telephony.SubscriptionManager;
+import android.util.ArrayMap;
+import android.util.LocalLog;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.data.PhoneSwitcher.PhoneSwitcherCallback;
+import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.telephony.Rlog;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Map;
+
+/**
+ * TelephonyNetworkProvider is a singleton network provider responsible for providing all
+ * telephony related networks including networks on cellular and IWLAN across all active SIMs.
+ */
+public class TelephonyNetworkProvider extends NetworkProvider implements NetworkOfferCallback {
+
+ public final String LOG_TAG = "TNP";
+
+ /** Android feature flags */
+ @NonNull
+ private final FeatureFlags mFlags;
+
+ /** The event handler */
+ @NonNull
+ private final Handler mHandler;
+
+ /** Phone switcher responsible to determine request routing on dual-SIM device */
+ @NonNull
+ private final PhoneSwitcher mPhoneSwitcher;
+
+ /** Network requests map. Key is the network request, value is the phone id it applies to. */
+ private final Map<TelephonyNetworkRequest, Integer> mNetworkRequests = new ArrayMap<>();
+
+ /** Persisted log */
+ @NonNull
+ private final LocalLog mLocalLog = new LocalLog(256);
+
+ /**
+ * Constructor
+ *
+ * @param looper The looper for event handling
+ * @param context The context
+ * @param featureFlags Android feature flags
+ */
+ public TelephonyNetworkProvider(@NonNull Looper looper, @NonNull Context context,
+ @NonNull FeatureFlags featureFlags) {
+ super(context, looper, TelephonyNetworkProvider.class.getSimpleName());
+
+ mFlags = featureFlags;
+ mHandler = new Handler(looper);
+ mPhoneSwitcher = PhoneSwitcher.getInstance();
+
+ // Register for subscription changed event.
+ context.getSystemService(SubscriptionManager.class)
+ .addOnSubscriptionsChangedListener(mHandler::post,
+ new SubscriptionManager.OnSubscriptionsChangedListener() {
+ @Override
+ public void onSubscriptionsChanged() {
+ logl("Subscription changed.");
+ reevaluateNetworkRequests("subscription changed");
+ }});
+
+ // Register for preferred data changed event
+ mPhoneSwitcher.registerCallback(new PhoneSwitcherCallback(mHandler::post) {
+ @Override
+ public void onPreferredDataPhoneIdChanged(int phoneId) {
+ logl("Preferred data sub phone id changed to " + phoneId);
+ reevaluateNetworkRequests("Preferred data subscription changed");
+ }
+ });
+
+ // Register the provider and tell connectivity service what network offer telephony can
+ // provide
+ ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
+ if (cm != null) {
+ cm.registerNetworkProvider(this);
+ NetworkCapabilities caps = makeNetworkFilter();
+ registerNetworkOffer(new NetworkScore.Builder().build(), caps, mHandler::post, this);
+ logl("registerNetworkOffer: " + caps);
+ }
+ }
+
+ /**
+ * Get the phone id for the network request.
+ *
+ * @param request The network request
+ * @return The id of the phone where the network request should route to. If the network request
+ * can't be applied to any phone, {@link SubscriptionManager#INVALID_PHONE_INDEX} will be
+ * returned.
+ */
+ private int getPhoneIdForNetworkRequest(@NonNull TelephonyNetworkRequest request) {
+ for (Phone phone : PhoneFactory.getPhones()) {
+ int phoneId = phone.getPhoneId();
+ if (mPhoneSwitcher.shouldApplyNetworkRequest(request, phoneId)) {
+ // Return here because by design the network request can be only applied to *one*
+ // phone. It's not possible to have two DataNetworkController to attempt to setup
+ // data call for the same network request.
+ return phoneId;
+ }
+ }
+
+ return SubscriptionManager.INVALID_PHONE_INDEX;
+ }
+
+ /**
+ * Called when receiving a network request from connectivity service. This is the entry point
+ * that a network request arrives telephony.
+ *
+ * @param request The network request
+ */
+ @Override
+ public void onNetworkNeeded(@NonNull NetworkRequest request) {
+ TelephonyNetworkRequest networkRequest = new TelephonyNetworkRequest(request, mFlags);
+ if (mNetworkRequests.containsKey(networkRequest)) {
+ loge("Duplicate network request " + networkRequest);
+ return;
+ }
+
+ mPhoneSwitcher.onRequestNetwork(request);
+
+ // Check with PhoneSwitcher to see where to route the request.
+ int phoneId = getPhoneIdForNetworkRequest(networkRequest);
+ if (phoneId != SubscriptionManager.INVALID_PHONE_INDEX) {
+ logl("onNetworkNeeded: phoneId=" + phoneId + ", " + networkRequest);
+ PhoneFactory.getPhone(phoneId).getDataNetworkController()
+ .addNetworkRequest(networkRequest);
+ } else {
+ logl("onNetworkNeeded: Not applied. " + networkRequest);
+ }
+
+ mNetworkRequests.put(networkRequest, phoneId);
+ }
+
+ /**
+ * Called when connectivity service remove the network request. Note this will not result in
+ * network tear down. Even there is no network request attached to the network, telephony still
+ * relies on {@link NetworkAgent#onNetworkUnwanted()} to tear down the network.
+ *
+ * @param request The released network request
+ *
+ * @see TelephonyNetworkAgent#onNetworkUnwanted()
+ */
+ @Override
+ public void onNetworkUnneeded(@NonNull NetworkRequest request) {
+ TelephonyNetworkRequest networkRequest = mNetworkRequests.keySet().stream()
+ .filter(r -> r.getNativeNetworkRequest().equals(request))
+ .findFirst()
+ .orElse(null);
+ if (networkRequest == null) {
+ loge("onNetworkUnneeded: Cannot find " + request);
+ return;
+ }
+
+ mPhoneSwitcher.onReleaseNetwork(request);
+ int phoneId = mNetworkRequests.remove(networkRequest);
+ Phone phone = PhoneFactory.getPhone(phoneId);
+ if (phone != null) {
+ logl("onNetworkUnneeded: phoneId=" + phoneId + ", " + networkRequest);
+ // Remove the network request from network controller. Note this will not result
+ // in disconnecting the data network.
+ phone.getDataNetworkController().removeNetworkRequest(networkRequest);
+ } else {
+ loge("onNetworkUnneeded: Unable to get phone. phoneId=" + phoneId);
+ }
+ }
+
+ /**
+ * Re-evaluate the existing networks and re-apply to the applicable phone.
+ *
+ * @param reason The reason for re-evaluating network request. Note this can be only used for
+ * debugging message purposes.
+ */
+ private void reevaluateNetworkRequests(@NonNull String reason) {
+ logl("reevaluateNetworkRequests: " + reason + ".");
+ mNetworkRequests.forEach((request, oldPhoneId) -> {
+ int newPhoneId = getPhoneIdForNetworkRequest(request);
+ if (newPhoneId != oldPhoneId) {
+ // We need to move the request from old phone to the new phone. This can happen
+ // when the user changes the default data subscription.
+
+ if (oldPhoneId != SubscriptionManager.INVALID_PHONE_INDEX) {
+ PhoneFactory.getPhone(oldPhoneId).getDataNetworkController()
+ .removeNetworkRequest(request);
+ }
+
+ if (newPhoneId != SubscriptionManager.INVALID_PHONE_INDEX) {
+ PhoneFactory.getPhone(newPhoneId).getDataNetworkController()
+ .addNetworkRequest(request);
+ }
+
+ logl("Request moved. phoneId " + oldPhoneId + " -> " + newPhoneId + " " + request);
+ mNetworkRequests.put(request, newPhoneId);
+ }
+ });
+ }
+
+ /**
+ * @return The maximal network capabilities that telephony can support.
+ */
+ @NonNull
+ private NetworkCapabilities makeNetworkFilter() {
+ final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_IA)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
+ .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_1)
+ .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_2)
+ .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_3)
+ .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_4)
+ .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_5)
+ // Ideally TelephonyNetworkProvider should only accept TelephonyNetworkSpecifier,
+ // but this network provider is a singleton across all SIMs, and
+ // TelephonyNetworkSpecifier can't accept more than one subscription id, so we let
+ // the provider accepts all different kinds NetworkSpecifier.
+ .setNetworkSpecifier(new MatchAllNetworkSpecifier());
+ TelephonyNetworkRequest.getAllSupportedNetworkCapabilities()
+ .forEach(builder::addCapability);
+
+ // TODO: b/328622096 remove the try/catch
+ try {
+ builder.addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE);
+ } catch (IllegalArgumentException exception) {
+ log("TRANSPORT_SATELLITE is not supported.");
+ }
+
+ return builder.build();
+ }
+
+ /**
+ * Log debug message to logcat.
+ *
+ * @param s The debug message to log
+ */
+ private void log(@NonNull String s) {
+ Rlog.d(LOG_TAG, s);
+ }
+
+ /**
+ * Log error debug messages to logcat.
+ * @param s The error debug messages
+ */
+ private void loge(@NonNull String s) {
+ Rlog.e(LOG_TAG, s);
+ }
+
+ /**
+ * Log to logcat and persisted local log.
+ *
+ * @param s The debug message to log
+ */
+ private void logl(@NonNull String s) {
+ log(s);
+ mLocalLog.log(s);
+ }
+
+ /**
+ * Dump the state of telephony network provider.
+ *
+ * @param fd File descriptor
+ * @param writer Print writer
+ * @param args Arguments
+ */
+ public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
+ pw.println("TelephonyNetworkProvider:");
+ pw.increaseIndent();
+
+ pw.println("mPreferredDataPhoneId=" + mPhoneSwitcher.getPreferredDataPhoneId());
+ int defaultDataSubId = SubscriptionManagerService.getInstance().getDefaultDataSubId();
+ pw.println("DefaultDataSubId=" + defaultDataSubId);
+ pw.println("DefaultDataPhoneId=" + SubscriptionManagerService.getInstance()
+ .getPhoneId(defaultDataSubId));
+
+ pw.println("Registered capabilities: " + makeNetworkFilter());
+ pw.println("Network requests:");
+ pw.increaseIndent();
+ for (Phone phone : PhoneFactory.getPhones()) {
+ pw.println("Phone " + phone.getPhoneId() + ":");
+ pw.increaseIndent();
+ mNetworkRequests.forEach((request, phoneId) -> {
+ if (phoneId == phone.getPhoneId()) {
+ pw.println(request);
+ }
+ });
+ pw.decreaseIndent();
+ }
+ pw.println("Not applied requests:");
+ pw.increaseIndent();
+ mNetworkRequests.forEach((request, phoneId) -> {
+ if (phoneId == SubscriptionManager.INVALID_PHONE_INDEX) {
+ pw.println(request);
+ }
+ });
+ pw.decreaseIndent();
+ pw.decreaseIndent();
+ pw.println();
+ pw.println("Local logs:");
+ pw.increaseIndent();
+ mLocalLog.dump(fd, pw, args);
+ pw.decreaseIndent();
+ pw.decreaseIndent();
+ }
+}
diff --git a/src/java/com/android/internal/telephony/data/TelephonyNetworkRequest.java b/src/java/com/android/internal/telephony/data/TelephonyNetworkRequest.java
index a43367e..ca1ca21 100644
--- a/src/java/com/android/internal/telephony/data/TelephonyNetworkRequest.java
+++ b/src/java/com/android/internal/telephony/data/TelephonyNetworkRequest.java
@@ -488,7 +488,7 @@
*
* @param evaluation The data evaluation result.
*/
- public void setEvaluation(@NonNull DataEvaluation evaluation) {
+ public void setEvaluation(@Nullable DataEvaluation evaluation) {
mEvaluation = evaluation;
}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
index 38b4f77..36ac992 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
@@ -580,6 +580,7 @@
mNullCipherNotifier = Mockito.mock(NullCipherNotifier.class);
doReturn(true).when(mFeatureFlags).minimalTelephonyCdmCheck();
+ doReturn(true).when(mFeatureFlags).supportNetworkProvider();
TelephonyManager.disableServiceHandleCaching();
PropertyInvalidatedCache.disableForTestMode();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
index 499c1f5..0c2faa1 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
@@ -159,7 +159,6 @@
private static final int EVENT_SUBSCRIPTION_OVERRIDE = 23;
// Mocked classes
- private PhoneSwitcher mMockedPhoneSwitcher;
protected ISub mMockedIsub;
private DataNetworkControllerCallback mMockedDataNetworkControllerCallback;
private DataRetryManagerCallback mMockedDataRetryManagerCallback;
@@ -855,7 +854,6 @@
public void setUp() throws Exception {
logd("DataNetworkControllerTest +Setup!");
super.setUp(getClass().getSimpleName());
- mMockedPhoneSwitcher = Mockito.mock(PhoneSwitcher.class);
mMockedIsub = Mockito.mock(ISub.class);
mMockedImsManager = mContext.getSystemService(ImsManager.class);
mMockedImsMmTelManager = Mockito.mock(ImsMmTelManager.class);
@@ -878,7 +876,6 @@
mMockedDataServiceManagers.put(AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
mMockedWlanDataServiceManager);
- replaceInstance(PhoneSwitcher.class, "sPhoneSwitcher", null, mMockedPhoneSwitcher);
doReturn(1).when(mMockedIsub).getDefaultDataSubId();
doReturn(mMockedIsub).when(mIBinder).queryLocalInterface(anyString());
doReturn(mPhone).when(mPhone).getImsPhone();
@@ -4908,14 +4905,10 @@
NetworkRequest nativeNetworkRequest = new NetworkRequest(netCaps,
ConnectivityManager.TYPE_MOBILE, 0, NetworkRequest.Type.REQUEST);
-
- mDataNetworkControllerUT.addNetworkRequest(new TelephonyNetworkRequest(
- nativeNetworkRequest, mPhone, mFeatureFlags));
- processAllMessages();
-
- // Intentionally create a new telephony request with the original native network request.
TelephonyNetworkRequest request = new TelephonyNetworkRequest(
nativeNetworkRequest, mPhone, mFeatureFlags);
+ mDataNetworkControllerUT.addNetworkRequest(request);
+ processAllMessages();
mDataNetworkControllerUT.removeNetworkRequest(request);
processAllFutureMessages();
@@ -4936,7 +4929,7 @@
processAllMessages();
// this slot is 0, modem preferred on slot 1
- doReturn(1).when(mMockedPhoneSwitcher).getPreferredDataPhoneId();
+ doReturn(1).when(mPhoneSwitcher).getPreferredDataPhoneId();
// Simulate telephony network factory remove request due to switch.
mDataNetworkControllerUT.removeNetworkRequest(request);
@@ -4949,7 +4942,7 @@
@Test
public void testSetupDataOnNonDds() throws Exception {
// this slot is 0, modem preferred on slot 1
- doReturn(1).when(mMockedPhoneSwitcher).getPreferredDataPhoneId();
+ doReturn(1).when(mPhoneSwitcher).getPreferredDataPhoneId();
TelephonyNetworkRequest request = createNetworkRequest(
NetworkCapabilities.NET_CAPABILITY_MMS);
@@ -5475,4 +5468,18 @@
assertThat(mDataNetworkControllerUT.getInternetEvaluation(true/*ignoreExistingNetworks*/)
.containsDisallowedReasons()).isTrue();
}
+
+ @Test
+ public void testRemoveNetworkRequestClearState() throws Exception {
+ TelephonyNetworkRequest request = createNetworkRequest(
+ NetworkCapabilities.NET_CAPABILITY_INTERNET);
+ mDataNetworkControllerUT.addNetworkRequest(request);
+ processAllMessages();
+ verifyConnectedNetworkHasCapabilities(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+ assertThat(request.getState()).isEqualTo(TelephonyNetworkRequest.REQUEST_STATE_SATISFIED);
+
+ mDataNetworkControllerUT.removeNetworkRequest(request);
+ processAllMessages();
+ assertThat(request.getState()).isEqualTo(TelephonyNetworkRequest.REQUEST_STATE_UNSATISFIED);
+ }
}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java
index 8be0f8b..6539ca9 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java
@@ -25,6 +25,7 @@
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -86,6 +87,7 @@
import com.android.internal.telephony.data.DataNetworkController.NetworkRequestList;
import com.android.internal.telephony.data.DataSettingsManager.DataSettingsManagerCallback;
import com.android.internal.telephony.data.LinkBandwidthEstimator.LinkBandwidthEstimatorCallback;
+import com.android.internal.telephony.data.PhoneSwitcher.PhoneSwitcherCallback;
import com.android.internal.telephony.metrics.DataCallSessionStats;
import com.android.internal.telephony.test.SimulatedCommands;
@@ -238,14 +240,14 @@
// Mocked classes
private DataNetworkCallback mDataNetworkCallback;
private DataCallSessionStats mDataCallSessionStats;
- private PhoneSwitcher mMockedPhoneSwitcher;
-
private final NetworkRegistrationInfo mIwlanNetworkRegistrationInfo =
new NetworkRegistrationInfo.Builder()
.setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_IWLAN)
.setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
.build();
+ private PhoneSwitcherCallback mPhoneSwitcherCallback;
+
private void setSuccessfulSetupDataResponse(DataServiceManager dsm, int cid) {
setSuccessfulSetupDataResponse(dsm, cid, Collections.emptyList(), null);
}
@@ -379,14 +381,13 @@
@Before
public void setUp() throws Exception {
super.setUp(getClass().getSimpleName());
+ doReturn(1).when(mPhone).getSubId();
doReturn(mImsPhone).when(mPhone).getImsPhone();
doReturn(mImsCT).when(mImsPhone).getCallTracker();
doReturn(PhoneConstants.State.IDLE).when(mImsCT).getState();
mDataNetworkCallback = Mockito.mock(DataNetworkCallback.class);
mDataCallSessionStats = Mockito.mock(DataCallSessionStats.class);
- mMockedPhoneSwitcher = Mockito.mock(PhoneSwitcher.class);
- replaceInstance(PhoneSwitcher.class, "sPhoneSwitcher", null, mMockedPhoneSwitcher);
doAnswer(invocation -> {
((Runnable) invocation.getArguments()[0]).run();
return null;
@@ -2662,4 +2663,35 @@
assertThat(mDataNetworkUT.getNetworkCapabilities()
.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)).isTrue();
}
+
+ @Test
+ public void testPrimaryTransport() throws Exception {
+ doReturn(0).when(mPhoneSwitcher).getPreferredDataPhoneId();
+ setupDataNetwork();
+ TelephonyNetworkAgent mockNetworkAgent = Mockito.mock(TelephonyNetworkAgent.class);
+ replaceInstance(DataNetwork.class, "mNetworkAgent",
+ mDataNetworkUT, mockNetworkAgent);
+
+ ArgumentCaptor<PhoneSwitcherCallback> callbackCaptor =
+ ArgumentCaptor.forClass(PhoneSwitcherCallback.class);
+ verify(mPhoneSwitcher).registerCallback(callbackCaptor.capture());
+ mPhoneSwitcherCallback = callbackCaptor.getValue();
+
+ // Switch the preferred data subscription to another.
+ mPhoneSwitcherCallback.onPreferredDataPhoneIdChanged(1);
+ processAllMessages();
+
+ ArgumentCaptor<NetworkScore> networkScoreCaptor =
+ ArgumentCaptor.forClass(NetworkScore.class);
+ verify(mockNetworkAgent).sendNetworkScore(networkScoreCaptor.capture());
+ assertThat(networkScoreCaptor.getValue().isTransportPrimary()).isFalse();
+ clearInvocations(mockNetworkAgent);
+
+ // Switch back
+ mPhoneSwitcherCallback.onPreferredDataPhoneIdChanged(0);
+ processAllMessages();
+
+ verify(mockNetworkAgent).sendNetworkScore(networkScoreCaptor.capture());
+ assertThat(networkScoreCaptor.getValue().isTransportPrimary()).isTrue();
+ }
}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/PhoneSwitcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/PhoneSwitcherTest.java
index a820ec7..f7990b9 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/PhoneSwitcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/PhoneSwitcherTest.java
@@ -52,18 +52,15 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
-import android.net.NetworkProvider;
import android.net.NetworkRequest;
import android.net.TelephonyNetworkSpecifier;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.os.Messenger;
import android.telephony.AccessNetworkConstants;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.PhoneCapability;
@@ -75,8 +72,6 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
-import androidx.test.filters.SmallTest;
-
import com.android.ims.ImsException;
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CommandException;
@@ -140,9 +135,6 @@
private PhoneSwitcher mPhoneSwitcherUT;
private SubscriptionManager.OnSubscriptionsChangedListener mSubChangedListener;
- private ConnectivityManager mConnectivityManager;
- // The messenger of PhoneSwitcher used to receive network requests.
- private Messenger mNetworkProviderMessenger = null;
private Map<Integer, DataSettingsManager.DataSettingsManagerCallback>
mDataSettingsManagerCallbacks;
private int mDefaultDataSub = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -211,8 +203,6 @@
public void tearDown() throws Exception {
mPhoneSwitcherUT = null;
mSubChangedListener = null;
- mConnectivityManager = null;
- mNetworkProviderMessenger = null;
mTelephonyDisplayInfo = null;
super.tearDown();
}
@@ -221,7 +211,6 @@
* Test that a single phone case results in our phone being active and the RIL called
*/
@Test
- @SmallTest
public void testRegister() throws Exception {
initialize();
@@ -468,7 +457,6 @@
}
@Test
- @SmallTest
public void testAutoDataSwitch_exemptPingTest() throws Exception {
initialize();
@@ -506,7 +494,6 @@
* - don't switch phones when in emergency mode
*/
@Test
- @SmallTest
public void testPrioritization() throws Exception {
initialize();
@@ -541,7 +528,6 @@
* wins (ie, switch to wifi).
*/
@Test
- @SmallTest
public void testHigherPriorityDefault() throws Exception {
initialize();
@@ -574,7 +560,6 @@
* active one.
*/
@Test
- @SmallTest
public void testSetPreferredData() throws Exception {
initialize();
@@ -620,7 +605,6 @@
* 3. CBRS requests OR Auto switch requests - only one case applies at a time
*/
@Test
- @SmallTest
public void testSetPreferredDataCasePriority_CbrsWaitsForVoiceCall() throws Exception {
initialize();
setAllPhonesInactive();
@@ -674,7 +658,6 @@
}
@Test
- @SmallTest
public void testSetPreferredData_NoAutoSwitchWhenCbrs() throws Exception {
initialize();
setAllPhonesInactive();
@@ -728,7 +711,6 @@
}
@Test
- @SmallTest
public void testSetPreferredDataModemCommand() throws Exception {
doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
initialize();
@@ -827,7 +809,6 @@
}
@Test
- @SmallTest
public void testSetPreferredDataWithValidation() throws Exception {
doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
initialize();
@@ -888,7 +869,6 @@
}
@Test
- @SmallTest
public void testNonDefaultDataPhoneInCall_ImsCallOnLte_shouldSwitchDds() throws Exception {
initialize();
setAllPhonesInactive();
@@ -916,7 +896,6 @@
}
@Test
- @SmallTest
public void testNonDefaultDataPhoneInCall_ImsCallDialingOnLte_shouldSwitchDds()
throws Exception {
initialize();
@@ -950,7 +929,6 @@
assertEquals(1, mPhoneSwitcherUT.getPreferredDataPhoneId());
}
@Test
- @SmallTest
public void testNonDefaultDataPhoneInCall_ImsCallIncomingOnLte_shouldSwitchDds()
throws Exception {
initialize();
@@ -979,7 +957,6 @@
}
@Test
- @SmallTest
public void testNonDefaultDataPhoneInCall_ImsCallOnWlan_shouldNotSwitchDds() throws Exception {
initialize();
setAllPhonesInactive();
@@ -1006,7 +983,6 @@
}
@Test
- @SmallTest
public void testNonDefaultDataPhoneInCall_ImsCallOnCrossSIM_HandoverToLTE() throws Exception {
initialize();
setAllPhonesInactive();
@@ -1169,7 +1145,6 @@
}
@Test
- @SmallTest
public void testNetworkRequestOnNonDefaultData() throws Exception {
doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
initialize();
@@ -1194,7 +1169,6 @@
}
@Test
- @SmallTest
public void testEmergencyOverrideSuccessBeforeCallStarts() throws Exception {
doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
initialize();
@@ -1215,7 +1189,6 @@
}
@Test
- @SmallTest
public void testEmergencyOverrideNoDdsChange() throws Exception {
doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
initialize();
@@ -1235,7 +1208,6 @@
}
@Test
- @SmallTest
public void testEmergencyOverrideEndSuccess() throws Exception {
PhoneSwitcher.ECBM_DEFAULT_DATA_SWITCH_BASE_TIME_MS = 500;
doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
@@ -1273,7 +1245,6 @@
}
@Test
- @SmallTest
public void testEmergencyOverrideEcbmStartEnd() throws Exception {
PhoneSwitcher.ECBM_DEFAULT_DATA_SWITCH_BASE_TIME_MS = 500;
doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
@@ -1323,7 +1294,6 @@
}
@Test
- @SmallTest
public void testEmergencyOverrideNoCallStart() throws Exception {
PhoneSwitcher.DEFAULT_DATA_OVERRIDE_TIMEOUT_MS = 500;
doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
@@ -1355,7 +1325,6 @@
}
@Test
- @SmallTest
public void testEmergencyOverrideMultipleOverrideRequests() throws Exception {
PhoneSwitcher.ECBM_DEFAULT_DATA_SWITCH_BASE_TIME_MS = 500;
doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
@@ -1406,7 +1375,6 @@
}
@Test
- @SmallTest
public void testSetPreferredDataCallback() throws Exception {
doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
initialize();
@@ -1581,7 +1549,6 @@
}
@Test
- @SmallTest
public void testMultiSimConfigChange() throws Exception {
doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
mActiveModemCount = 1;
@@ -1609,7 +1576,6 @@
}
@Test
- @SmallTest
public void testValidationOffSwitch_shouldSwitchOnNetworkAvailable() throws Exception {
doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
initialize();
@@ -1650,7 +1616,6 @@
}
@Test
- @SmallTest
public void testValidationOffSwitch_shouldSwitchOnTimeOut() throws Exception {
doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
initialize();
@@ -1773,7 +1738,6 @@
}
@Test
- @SmallTest
public void testRegisterForImsRegistrationCallback() throws Exception {
initialize();
setAllPhonesInactive();
@@ -1801,7 +1765,6 @@
}
@Test
- @SmallTest
public void testReceivingImsRegistrationTech() throws Exception {
doReturn(true).when(mFeatureFlags).changeMethodOfObtainingImsRegistrationRadioTech();
@@ -1882,9 +1845,6 @@
doReturn(true).when(mPhone2).isDataAllowed();
doReturn(true).when(mDataSettingsManager2).isDataEnabled();
- // 3.1 No default network
- doReturn(null).when(mConnectivityManager).getNetworkCapabilities(any());
-
mPhoneSwitcherUT.sendEmptyMessage(EVENT_EVALUATE_AUTO_SWITCH);
}
@@ -2040,7 +2000,6 @@
initializeSubControllerMock();
initializeCommandInterfacesMock();
initializeTelRegistryMock();
- initializeConnManagerMock();
initializeConfigMock();
mPhoneSwitcherUT = new PhoneSwitcher(mMaxDataAttachModemCount, mContext, Looper.myLooper(),
@@ -2154,21 +2113,6 @@
* Capture mNetworkProviderMessenger so that testing can request or release
* network requests on PhoneSwitcher.
*/
- private void initializeConnManagerMock() {
- mConnectivityManager = (ConnectivityManager)
- mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
-
- doAnswer(invocation -> {
- mNetworkProviderMessenger =
- ((NetworkProvider) invocation.getArgument(0)).getMessenger();
- return null;
- }).when(mConnectivityManager).registerNetworkProvider(any());
- }
-
- /**
- * Capture mNetworkProviderMessenger so that testing can request or release
- * network requests on PhoneSwitcher.
- */
private void initializeSubControllerMock() throws Exception {
doReturn(mDefaultDataSub).when(mSubscriptionManagerService).getDefaultDataSubId();
doReturn(mDefaultDataSub).when(mMockedIsub).getDefaultDataSubId();
@@ -2265,13 +2209,7 @@
NetworkRequest networkRequest = new NetworkRequest(netCap, ConnectivityManager.TYPE_NONE,
0, NetworkRequest.Type.REQUEST);
- Message message = Message.obtain();
- message.what = android.net.NetworkProvider.CMD_REQUEST_NETWORK;
- message.arg1 = score;
- message.obj = networkRequest;
- mNetworkProviderMessenger.send(message);
- processAllMessages();
-
+ mPhoneSwitcherUT.onRequestNetwork(networkRequest);
return networkRequest;
}
@@ -2289,14 +2227,7 @@
}
NetworkRequest networkRequest = new NetworkRequest(netCap, ConnectivityManager.TYPE_NONE,
1, NetworkRequest.Type.REQUEST);
-
- Message message = Message.obtain();
- message.what = android.net.NetworkProvider.CMD_REQUEST_NETWORK;
- message.arg1 = 50; // Score
- message.obj = networkRequest;
- mNetworkProviderMessenger.send(message);
- processAllMessages();
-
+ mPhoneSwitcherUT.onRequestNetwork(networkRequest);
return networkRequest;
}
@@ -2304,10 +2235,6 @@
* Tell PhoneSwitcher to release a network request.
*/
private void releaseNetworkRequest(NetworkRequest networkRequest) throws Exception {
- Message message = Message.obtain();
- message.what = android.net.NetworkProvider.CMD_CANCEL_REQUEST;
- message.obj = networkRequest;
- mNetworkProviderMessenger.send(message);
- processAllMessages();
+ mPhoneSwitcherUT.onReleaseNetwork(networkRequest);
}
}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/TelephonyNetworkProviderTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/TelephonyNetworkProviderTest.java
new file mode 100644
index 0000000..2fdf9e6
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/TelephonyNetworkProviderTest.java
@@ -0,0 +1,394 @@
+/*
+ * Copyright (C) 2024 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.data;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.annotation.NonNull;
+import android.net.MatchAllNetworkSpecifier;
+import android.net.NetworkCapabilities;
+import android.net.NetworkProvider;
+import android.net.NetworkRequest;
+import android.net.NetworkScore;
+import android.net.TelephonyNetworkSpecifier;
+import android.net.connectivity.android.net.INetworkOfferCallback;
+import android.os.Looper;
+import android.telephony.Annotation.NetCapability;
+import android.telephony.SubscriptionManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.data.PhoneSwitcher.PhoneSwitcherCallback;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.util.Arrays;
+import java.util.Set;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class TelephonyNetworkProviderTest extends TelephonyTest {
+
+ private TelephonyNetworkProvider mTelephonyNetworkProvider;
+
+ private PhoneSwitcherCallback mPhoneSwitcherCallback;
+
+ // Mocked classes
+ private DataNetworkController mDataNetworkController2;
+
+
+ /**
+ * Set the preferred data phone, which is supposed to take the network request.
+ *
+ * @param phoneId The phone id
+ */
+ private void setPreferredDataPhone(int phoneId) {
+ doAnswer(invocation -> {
+ TelephonyNetworkRequest request = (TelephonyNetworkRequest)
+ invocation.getArguments()[0];
+ int id = (int) invocation.getArguments()[1];
+
+ logd("shouldApplyNetworkRequest: request phone id=" + id
+ + ", preferred data phone id=" + phoneId);
+
+ TelephonyNetworkSpecifier specifier = (TelephonyNetworkSpecifier)
+ request.getNetworkSpecifier();
+ if (specifier != null) {
+ int subId = specifier.getSubscriptionId();
+ logd("shouldApplyNetworkRequest: requested on sub " + subId);
+ if (subId == 1 && mPhone.getPhoneId() == id) {
+ logd("shouldApplyNetworkRequest: matched phone 0");
+ return true;
+ }
+ if (subId == 2 && mPhone2.getPhoneId() == id) {
+ logd("shouldApplyNetworkRequest: matched phone 1");
+ return true;
+ }
+ return false;
+ }
+
+ if (request.hasCapability(NetworkCapabilities.NET_CAPABILITY_EIMS)) return true;
+ return id == phoneId;
+ }).when(mPhoneSwitcher).shouldApplyNetworkRequest(any(TelephonyNetworkRequest.class),
+ anyInt());
+ }
+
+ /**
+ * Create a simple network request with internet capability.
+ *
+ * @return The network request
+ */
+ @NonNull
+ private NetworkRequest createNetworkRequest() {
+ return createNetworkRequestForSub(SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+ NetworkCapabilities.NET_CAPABILITY_INTERNET);
+ }
+
+ /**
+ * Create a network request with specified network capabilities.
+ *
+ * @param caps Network capabilities
+ *
+ * @return The network request
+ */
+ @NonNull
+ private NetworkRequest createNetworkRequest(@NetCapability int... caps) {
+ return createNetworkRequestForSub(SubscriptionManager.INVALID_SUBSCRIPTION_ID, caps);
+ }
+
+ /**
+ * Create a network request with subscription id specified.
+ *
+ * @param subId The subscription in for the network request
+ *
+ * @return The network request
+ */
+ @NonNull
+ private NetworkRequest createNetworkRequestForSub(int subId) {
+ return createNetworkRequestForSub(subId, NetworkCapabilities.NET_CAPABILITY_INTERNET);
+ }
+
+ /**
+ * Create the network request.
+ *
+ * @param subId The subscription id. {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} if no
+ * @param caps Network capabilities in the network request need to specify.
+ *
+ * @return The network request
+ */
+ @NonNull
+ private NetworkRequest createNetworkRequestForSub(int subId, @NetCapability int... caps) {
+ NetworkRequest.Builder builder = new NetworkRequest.Builder();
+ Arrays.stream(caps).boxed().toList().forEach(builder::addCapability);
+ if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ builder.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+ builder.setNetworkSpecifier(new TelephonyNetworkSpecifier(subId));
+ builder.setSubscriptionIds(Set.of(subId));
+ }
+
+ return builder.build();
+ }
+
+ /** Clear all invocations from all DataNetworkControllers. */
+ private void resetInvocations() {
+ clearInvocations(mDataNetworkController);
+ clearInvocations(mDataNetworkController2);
+ }
+
+ /**
+ * Verify the request was sent to the correct phone's DataNetworkController.
+ *
+ * @param phoneId The id of the phone that the request is supposed to send
+ * @param request The network request
+ */
+ private void verifyRequestSentOnPhone(int phoneId, @NonNull NetworkRequest request) {
+ ArgumentCaptor<TelephonyNetworkRequest> requestCaptor =
+ ArgumentCaptor.forClass(TelephonyNetworkRequest.class);
+
+ for (Phone phone : PhoneFactory.getPhones()) {
+ if (phone.getPhoneId() == phoneId) {
+ verify(phone.getDataNetworkController(), times(1)
+ .description("Did not request on phone " + phoneId))
+ .addNetworkRequest(requestCaptor.capture());
+ assertThat(requestCaptor.getValue().getNativeNetworkRequest()).isEqualTo(request);
+ } else {
+ verifyNoRequestSentOnPhone(phone.getPhoneId());
+ }
+ }
+ }
+
+ /**
+ * Verify the request was released on the specified phone's DataNetworkController.
+ *
+ * @param phoneId The id of the phone that the request is supposed to send
+ * @param request The network request
+ */
+ private void verifyRequestReleasedOnPhone(int phoneId, @NonNull NetworkRequest request) {
+ ArgumentCaptor<TelephonyNetworkRequest> requestCaptor =
+ ArgumentCaptor.forClass(TelephonyNetworkRequest.class);
+
+ for (Phone phone : PhoneFactory.getPhones()) {
+ if (phone.getPhoneId() == phoneId) {
+ verify(phone.getDataNetworkController(), times(1)
+ .description("Did not remove on phone " + phoneId))
+ .removeNetworkRequest(requestCaptor.capture());
+ assertThat(requestCaptor.getValue().getNativeNetworkRequest()).isEqualTo(request);
+ } else {
+ verifyNoRequestReleasedOnPhone(phone.getPhoneId());
+ }
+ }
+ }
+
+ /**
+ * Verify there is no request sent on specified phone.
+ *
+ * @param phoneId The phone id
+ */
+ private void verifyNoRequestSentOnPhone(int phoneId) {
+ verify(PhoneFactory.getPhone(phoneId).getDataNetworkController(), never()
+ .description("Should not request on phone " + phoneId))
+ .addNetworkRequest(any(TelephonyNetworkRequest.class));
+ }
+
+ /**
+ * Verify there is no request released on specified phone.
+ *
+ * @param phoneId The phone id
+ */
+ private void verifyNoRequestReleasedOnPhone(int phoneId) {
+ verify(PhoneFactory.getPhone(phoneId).getDataNetworkController(), never()
+ .description("Should not release on phone " + phoneId))
+ .removeNetworkRequest(any(TelephonyNetworkRequest.class));
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ logd("TelephonyNetworkProviderTest +Setup!");
+ super.setUp(getClass().getSimpleName());
+ replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone, mPhone2});
+
+ mDataNetworkController2 = mock(DataNetworkController.class);
+
+ doReturn(0).when(mPhone).getPhoneId();
+ doReturn(1).when(mPhone).getSubId();
+ doReturn(1).when(mPhone2).getPhoneId();
+ doReturn(2).when(mPhone2).getSubId();
+
+ doReturn(mDataNetworkController2).when(mPhone2).getDataNetworkController();
+
+ setPreferredDataPhone(0);
+
+ doAnswer(invocation -> {
+ NetworkProvider provider = (NetworkProvider) invocation.getArguments()[0];
+ provider.setProviderId(1);
+ return 1;
+ }).when(mConnectivityManager).registerNetworkProvider(any(NetworkProvider.class));
+
+ mTelephonyNetworkProvider = new TelephonyNetworkProvider(Looper.myLooper(),
+ mContext, mFeatureFlags);
+
+ ArgumentCaptor<PhoneSwitcherCallback> callbackCaptor =
+ ArgumentCaptor.forClass(PhoneSwitcherCallback.class);
+ verify(mPhoneSwitcher).registerCallback(callbackCaptor.capture());
+ mPhoneSwitcherCallback = callbackCaptor.getValue();
+
+ logd("TelephonyNetworkProviderTest -Setup!");
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ logd("tearDown");
+ super.tearDown();
+ }
+
+ @Test
+ public void testRegisterProvider() {
+ verify(mConnectivityManager).registerNetworkProvider(any(TelephonyNetworkProvider.class));
+
+ ArgumentCaptor<NetworkCapabilities> capsCaptor =
+ ArgumentCaptor.forClass(NetworkCapabilities.class);
+ verify(mConnectivityManager).offerNetwork(anyInt(), any(NetworkScore.class),
+ capsCaptor.capture(), any(INetworkOfferCallback.class));
+
+ NetworkCapabilities caps = capsCaptor.getValue();
+
+ TelephonyNetworkRequest.getAllSupportedNetworkCapabilities().forEach(
+ (cap) -> assertThat(caps.hasCapability(cap)));
+ assertThat(caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_IA)).isTrue();
+ assertThat(caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL)).isTrue();
+ assertThat(caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)).isTrue();
+ assertThat(caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)).isTrue();
+ assertThat(caps.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)).isTrue();
+ assertThat(caps.hasTransport(NetworkCapabilities.TRANSPORT_SATELLITE)).isTrue();
+
+ assertThat(caps.getNetworkSpecifier()).isInstanceOf(MatchAllNetworkSpecifier.class);
+ }
+
+ @Test
+ public void testRequestNetwork() {
+ NetworkRequest request = createNetworkRequest();
+ mTelephonyNetworkProvider.onNetworkNeeded(request);
+ // Should request on phone 0
+ verifyRequestSentOnPhone(0, request);
+ }
+
+ @Test
+ public void testReleaseNetwork() {
+ NetworkRequest request = createNetworkRequest();
+ mTelephonyNetworkProvider.onNetworkNeeded(request);
+ // Should request on phone 0
+ verifyRequestSentOnPhone(0, request);
+ resetInvocations();
+
+ // Now release the network request.
+ mTelephonyNetworkProvider.onNetworkUnneeded(request);
+ // Should release on phone 0
+ verifyRequestReleasedOnPhone(0, request);
+ resetInvocations();
+
+ // Release the same request again should not result in another remove
+ mTelephonyNetworkProvider.onNetworkUnneeded(request);
+ verifyNoRequestReleasedOnPhone(0);
+ verifyNoRequestReleasedOnPhone(1);
+ }
+
+ @Test
+ public void testRequestNetworkDuplicate() {
+ NetworkRequest request = createNetworkRequest();
+ mTelephonyNetworkProvider.onNetworkNeeded(request);
+ // Should request on phone 0
+ verifyRequestSentOnPhone(0, request);
+
+ resetInvocations();
+ // send the same request again should be blocked.
+ mTelephonyNetworkProvider.onNetworkNeeded(request);
+ verifyNoRequestSentOnPhone(0);
+ verifyNoRequestSentOnPhone(1);
+ }
+
+ @Test
+ public void testRequestNetworkPreferredPhone1() {
+ setPreferredDataPhone(1);
+
+ NetworkRequest request = createNetworkRequest();
+ mTelephonyNetworkProvider.onNetworkNeeded(request);
+ // Should request on phone 1
+ verifyRequestSentOnPhone(1, request);
+ }
+
+ @Test
+ public void testRequestEmergencyNetwork() {
+ setPreferredDataPhone(1);
+
+ NetworkRequest request = createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_EIMS);
+ mTelephonyNetworkProvider.onNetworkNeeded(request);
+ // Should request on phone 0
+ verifyRequestSentOnPhone(0, request);
+ }
+
+ @Test
+ public void testRequestNetworkOnSpecifiedSub() {
+ NetworkRequest request = createNetworkRequestForSub(1);
+ mTelephonyNetworkProvider.onNetworkNeeded(request);
+ verifyRequestSentOnPhone(0, request);
+
+ resetInvocations();
+ request = createNetworkRequestForSub(2);
+ mTelephonyNetworkProvider.onNetworkNeeded(request);
+ // Should request on phone 1
+ verifyRequestSentOnPhone(1, request);
+ }
+
+ @Test
+ public void testPreferredDataSwitch() {
+ NetworkRequest request = createNetworkRequest();
+ mTelephonyNetworkProvider.onNetworkNeeded(request);
+ // Should request on phone 0
+ verifyRequestSentOnPhone(0, request);
+ resetInvocations();
+
+ // Now switch from phone 0 to phone 1
+ setPreferredDataPhone(1);
+ mPhoneSwitcherCallback.onPreferredDataPhoneIdChanged(1);
+ verifyRequestReleasedOnPhone(0, request);
+ verifyRequestSentOnPhone(1, request);
+ resetInvocations();
+
+ // Now switch back to phone 0
+ setPreferredDataPhone(0);
+ mPhoneSwitcherCallback.onPreferredDataPhoneIdChanged(0);
+ verifyRequestReleasedOnPhone(1, request);
+ verifyRequestSentOnPhone(0, request);
+ }
+}