Create NetworkScanRepository

And migrate network scan to flow for better maintenance and prevent ANR.

Fix: 323105271
Test: manual - Choose network
Test: unit test
Change-Id: I5c49d195fc202143c0131ffd78bc3adc168b119c
Merged-In: I5c49d195fc202143c0131ffd78bc3adc168b119c
This commit is contained in:
Chaohui Wang
2024-03-04 19:14:52 +08:00
parent b9a0722d20
commit 12158eb1fe
7 changed files with 495 additions and 881 deletions

View File

@@ -82,7 +82,7 @@ object CellInfoUtil {
*/
@JvmStatic
fun cellInfoListToString(cellInfos: List<CellInfo>): String =
cellInfos.joinToString { cellInfo -> cellInfo.readableString() }
cellInfos.joinToString(System.lineSeparator()) { cellInfo -> cellInfo.readableString() }
/**
* Convert [CellInfo] to a readable string without sensitive info.

View File

@@ -1,346 +0,0 @@
/*
* Copyright (C) 2018 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.settings.network.telephony;
import android.annotation.IntDef;
import android.content.Context;
import android.telephony.AccessNetworkConstants.AccessNetworkType;
import android.telephony.CellInfo;
import android.telephony.NetworkScan;
import android.telephony.NetworkScanRequest;
import android.telephony.PhoneCapability;
import android.telephony.RadioAccessSpecifier;
import android.telephony.TelephonyManager;
import android.telephony.TelephonyScanManager;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import com.android.internal.telephony.CellNetworkScanResult;
import com.android.settings.R;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;
/**
* A helper class that builds the common interface and performs the network scan for two different
* network scan APIs.
*/
public class NetworkScanHelper {
public static final String TAG = "NetworkScanHelper";
/**
* Callbacks interface to inform the network scan results.
*/
public interface NetworkScanCallback {
/**
* Called when the results is returned from {@link TelephonyManager}. This method will be
* called at least one time if there is no error occurred during the network scan.
*
* <p> This method can be called multiple times in one network scan, until
* {@link #onComplete()} or {@link #onError(int)} is called.
*
* @param results
*/
void onResults(List<CellInfo> results);
/**
* Called when the current network scan process is finished. No more
* {@link #onResults(List)} will be called for the current network scan after this method is
* called.
*/
void onComplete();
/**
* Called when an error occurred during the network scan process.
*
* <p> There is no more result returned from {@link TelephonyManager} if an error occurred.
*
* <p> {@link #onComplete()} will not be called if an error occurred.
*
* @see {@link NetworkScan.ScanErrorCode}
*/
void onError(int errorCode);
}
@Retention(RetentionPolicy.SOURCE)
@IntDef({NETWORK_SCAN_TYPE_WAIT_FOR_ALL_RESULTS, NETWORK_SCAN_TYPE_INCREMENTAL_RESULTS})
public @interface NetworkQueryType {}
/**
* Performs the network scan using {@link TelephonyManager#getAvailableNetworks()}. The network
* scan results won't be returned to the caller until the network scan is completed.
*
* <p> This is typically used when the modem doesn't support the new network scan api
* {@link TelephonyManager#requestNetworkScan(
* NetworkScanRequest, Executor, TelephonyScanManager.NetworkScanCallback)}.
*/
public static final int NETWORK_SCAN_TYPE_WAIT_FOR_ALL_RESULTS = 1;
/**
* Performs the network scan using {@link TelephonyManager#requestNetworkScan(
* NetworkScanRequest, Executor, TelephonyScanManager.NetworkScanCallback)} The network scan
* results will be returned to the caller periodically in a small time window until the network
* scan is completed. The complete results should be returned in the last called of
* {@link NetworkScanCallback#onResults(List)}.
*
* <p> This is recommended to be used if modem supports the new network scan api
* {@link TelephonyManager#requestNetworkScan(
* NetworkScanRequest, Executor, TelephonyScanManager.NetworkScanCallback)}
*/
public static final int NETWORK_SCAN_TYPE_INCREMENTAL_RESULTS = 2;
/** The constants below are used in the async network scan. */
@VisibleForTesting
static final boolean INCREMENTAL_RESULTS = true;
@VisibleForTesting
static final int SEARCH_PERIODICITY_SEC = 5;
@VisibleForTesting
static final int MAX_SEARCH_TIME_SEC = 300;
@VisibleForTesting
static final int INCREMENTAL_RESULTS_PERIODICITY_SEC = 3;
private final NetworkScanCallback mNetworkScanCallback;
private final TelephonyManager mTelephonyManager;
private final TelephonyScanManager.NetworkScanCallback mInternalNetworkScanCallback;
private final Executor mExecutor;
private int mMaxSearchTimeSec = MAX_SEARCH_TIME_SEC;
private NetworkScan mNetworkScanRequester;
/** Callbacks for sync network scan */
private ListenableFuture<List<CellInfo>> mNetworkScanFuture;
public NetworkScanHelper(TelephonyManager tm, NetworkScanCallback callback, Executor executor) {
mTelephonyManager = tm;
mNetworkScanCallback = callback;
mInternalNetworkScanCallback = new NetworkScanCallbackImpl();
mExecutor = executor;
}
public NetworkScanHelper(Context context, TelephonyManager tm, NetworkScanCallback callback,
Executor executor) {
this(tm, callback, executor);
mMaxSearchTimeSec = context.getResources().getInteger(
R.integer.config_network_scan_helper_max_search_time_sec);
}
@VisibleForTesting
NetworkScanRequest createNetworkScanForPreferredAccessNetworks() {
long networkTypeBitmap3gpp = mTelephonyManager.getPreferredNetworkTypeBitmask()
& TelephonyManager.NETWORK_STANDARDS_FAMILY_BITMASK_3GPP;
List<RadioAccessSpecifier> radioAccessSpecifiers = new ArrayList<>();
// If the allowed network types are unknown or if they are of the right class, scan for
// them; otherwise, skip them to save scan time and prevent users from being shown networks
// that they can't connect to.
if (networkTypeBitmap3gpp == 0
|| (networkTypeBitmap3gpp & TelephonyManager.NETWORK_CLASS_BITMASK_2G) != 0) {
radioAccessSpecifiers.add(
new RadioAccessSpecifier(AccessNetworkType.GERAN, null, null));
}
if (networkTypeBitmap3gpp == 0
|| (networkTypeBitmap3gpp & TelephonyManager.NETWORK_CLASS_BITMASK_3G) != 0) {
radioAccessSpecifiers.add(
new RadioAccessSpecifier(AccessNetworkType.UTRAN, null, null));
}
if (networkTypeBitmap3gpp == 0
|| (networkTypeBitmap3gpp & TelephonyManager.NETWORK_CLASS_BITMASK_4G) != 0) {
radioAccessSpecifiers.add(
new RadioAccessSpecifier(AccessNetworkType.EUTRAN, null, null));
}
// If a device supports 5G stand-alone then the code below should be re-enabled; however
// a device supporting only non-standalone mode cannot perform PLMN selection and camp on
// a 5G network, which means that it shouldn't scan for 5G at the expense of battery as
// part of the manual network selection process.
//
if (networkTypeBitmap3gpp == 0
|| (hasNrSaCapability()
&& (networkTypeBitmap3gpp & TelephonyManager.NETWORK_CLASS_BITMASK_5G) != 0)) {
radioAccessSpecifiers.add(
new RadioAccessSpecifier(AccessNetworkType.NGRAN, null, null));
Log.d(TAG, "radioAccessSpecifiers add NGRAN.");
}
return new NetworkScanRequest(
NetworkScanRequest.SCAN_TYPE_ONE_SHOT,
radioAccessSpecifiers.toArray(
new RadioAccessSpecifier[radioAccessSpecifiers.size()]),
SEARCH_PERIODICITY_SEC,
mMaxSearchTimeSec,
INCREMENTAL_RESULTS,
INCREMENTAL_RESULTS_PERIODICITY_SEC,
null /* List of PLMN ids (MCC-MNC) */);
}
/**
* Performs a network scan for the given type {@code type}.
* {@link #NETWORK_SCAN_TYPE_INCREMENTAL_RESULTS} is recommended if modem supports
* {@link TelephonyManager#requestNetworkScan(
* NetworkScanRequest, Executor, TelephonyScanManager.NetworkScanCallback)}.
*
* @param type used to tell which network scan API should be used.
*/
public void startNetworkScan(@NetworkQueryType int type) {
if (type == NETWORK_SCAN_TYPE_WAIT_FOR_ALL_RESULTS) {
mNetworkScanFuture = SettableFuture.create();
Futures.addCallback(mNetworkScanFuture, new FutureCallback<List<CellInfo>>() {
@Override
public void onSuccess(List<CellInfo> result) {
onResults(result);
onComplete();
}
@Override
public void onFailure(Throwable t) {
if (t instanceof CancellationException) {
return;
}
int errCode = Integer.parseInt(t.getMessage());
onError(errCode);
}
}, MoreExecutors.directExecutor());
mExecutor.execute(new NetworkScanSyncTask(
mTelephonyManager, (SettableFuture) mNetworkScanFuture));
} else if (type == NETWORK_SCAN_TYPE_INCREMENTAL_RESULTS) {
if (mNetworkScanRequester != null) {
return;
}
mNetworkScanRequester = mTelephonyManager.requestNetworkScan(
createNetworkScanForPreferredAccessNetworks(),
mExecutor,
mInternalNetworkScanCallback);
if (mNetworkScanRequester == null) {
onError(NetworkScan.ERROR_RADIO_INTERFACE_ERROR);
}
}
}
/**
* The network scan of type {@link #NETWORK_SCAN_TYPE_WAIT_FOR_ALL_RESULTS} can't be stopped,
* however, the result of the current network scan won't be returned to the callback after
* calling this method.
*/
public void stopNetworkQuery() {
if (mNetworkScanRequester != null) {
mNetworkScanRequester.stopScan();
mNetworkScanRequester = null;
}
if (mNetworkScanFuture != null) {
mNetworkScanFuture.cancel(true /* mayInterruptIfRunning */);
mNetworkScanFuture = null;
}
}
private void onResults(List<CellInfo> cellInfos) {
mNetworkScanCallback.onResults(cellInfos);
}
private void onComplete() {
mNetworkScanCallback.onComplete();
}
private void onError(int errCode) {
mNetworkScanCallback.onError(errCode);
}
private boolean hasNrSaCapability() {
return Arrays.stream(
mTelephonyManager.getPhoneCapability().getDeviceNrCapabilities())
.anyMatch(i -> i == PhoneCapability.DEVICE_NR_CAPABILITY_SA);
}
/**
* Converts the status code of {@link CellNetworkScanResult} to one of the
* {@link NetworkScan.ScanErrorCode}.
* @param errCode status code from {@link CellNetworkScanResult}.
*
* @return one of the scan error code from {@link NetworkScan.ScanErrorCode}.
*/
private static int convertToScanErrorCode(int errCode) {
switch (errCode) {
case CellNetworkScanResult.STATUS_RADIO_NOT_AVAILABLE:
return NetworkScan.ERROR_RADIO_INTERFACE_ERROR;
case CellNetworkScanResult.STATUS_RADIO_GENERIC_FAILURE:
default:
return NetworkScan.ERROR_MODEM_ERROR;
}
}
private final class NetworkScanCallbackImpl extends TelephonyScanManager.NetworkScanCallback {
public void onResults(List<CellInfo> results) {
Log.d(TAG, "Async scan onResults() results = "
+ CellInfoUtil.cellInfoListToString(results));
NetworkScanHelper.this.onResults(results);
}
public void onComplete() {
Log.d(TAG, "async scan onComplete()");
NetworkScanHelper.this.onComplete();
}
public void onError(@NetworkScan.ScanErrorCode int errCode) {
Log.d(TAG, "async scan onError() errorCode = " + errCode);
NetworkScanHelper.this.onError(errCode);
}
}
private static final class NetworkScanSyncTask implements Runnable {
private final SettableFuture<List<CellInfo>> mCallback;
private final TelephonyManager mTelephonyManager;
NetworkScanSyncTask(
TelephonyManager telephonyManager, SettableFuture<List<CellInfo>> callback) {
mTelephonyManager = telephonyManager;
mCallback = callback;
}
@Override
public void run() {
final CellNetworkScanResult result = mTelephonyManager.getAvailableNetworks();
if (result.getStatus() == CellNetworkScanResult.STATUS_SUCCESS) {
final List<CellInfo> cellInfos = result.getOperators()
.stream()
.map(operatorInfo
-> CellInfoUtil.convertOperatorInfoToCellInfo(operatorInfo))
.collect(Collectors.toList());
Log.d(TAG, "Sync network scan completed, cellInfos = "
+ CellInfoUtil.cellInfoListToString(cellInfos));
mCallback.set(cellInfos);
} else {
final Throwable error = new Throwable(
Integer.toString(convertToScanErrorCode(result.getStatus())));
mCallback.setException(error);
Log.d(TAG, "Sync network scan error, ex = " + error);
}
}
}
}

View File

@@ -16,7 +16,6 @@
package com.android.settings.network.telephony;
import android.app.Activity;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
@@ -39,6 +38,7 @@ import android.util.Log;
import android.view.View;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
@@ -49,14 +49,21 @@ import com.android.internal.telephony.OperatorInfo;
import com.android.internal.telephony.flags.Flags;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.network.telephony.scan.NetworkScanRepository;
import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanCellInfos;
import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanComplete;
import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanError;
import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanResult;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.utils.ThreadUtils;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -71,12 +78,8 @@ public class NetworkSelectSettings extends DashboardFragment {
private static final String TAG = "NetworkSelectSettings";
private static final int EVENT_SET_NETWORK_SELECTION_MANUALLY_DONE = 1;
private static final int EVENT_NETWORK_SCAN_RESULTS = 2;
private static final int EVENT_NETWORK_SCAN_ERROR = 3;
private static final int EVENT_NETWORK_SCAN_COMPLETED = 4;
private static final String PREF_KEY_NETWORK_OPERATORS = "network_operators_preference";
private static final int MIN_NUMBER_OF_SCAN_REQUIRED = 2;
private PreferenceCategory mPreferenceCategory;
@VisibleForTesting
@@ -91,18 +94,14 @@ public class NetworkSelectSettings extends DashboardFragment {
private CarrierConfigManager mCarrierConfigManager;
private List<String> mForbiddenPlmns;
private boolean mShow4GForLTE = false;
private NetworkScanHelper mNetworkScanHelper;
private final ExecutorService mNetworkScanExecutor = Executors.newFixedThreadPool(1);
private MetricsFeatureProvider mMetricsFeatureProvider;
private boolean mUseNewApi;
private long mRequestIdManualNetworkSelect;
private long mRequestIdManualNetworkScan;
private long mWaitingForNumberOfScanResults;
@VisibleForTesting
boolean mIsAggregationEnabled = false;
private CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener;
private AtomicBoolean mShouldFilterOutSatellitePlmn = new AtomicBoolean();
private NetworkScanRepository mNetworkScanRepository;
private boolean mUpdateScanResult = false;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
@@ -114,7 +113,6 @@ public class NetworkSelectSettings extends DashboardFragment {
@Initializer
protected void onCreateInitialization() {
Context context = getContext();
mUseNewApi = enableNewAutoSelectNetworkUI(context);
mSubId = getSubId();
mPreferenceCategory = getPreferenceCategory(PREF_KEY_NETWORK_OPERATORS);
@@ -124,8 +122,6 @@ public class NetworkSelectSettings extends DashboardFragment {
mTelephonyManager = getTelephonyManager(context, mSubId);
mSatelliteManager = getSatelliteManager(context);
mCarrierConfigManager = getCarrierConfigManager(context);
mNetworkScanHelper = new NetworkScanHelper(
mTelephonyManager, mCallback, mNetworkScanExecutor);
PersistableBundle bundle = mCarrierConfigManager.getConfigForSubId(mSubId,
CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL,
CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL);
@@ -136,30 +132,13 @@ public class NetworkSelectSettings extends DashboardFragment {
true));
mMetricsFeatureProvider = getMetricsFeatureProvider(context);
mIsAggregationEnabled = enableAggregation(context);
Log.d(TAG, "init: mUseNewApi:" + mUseNewApi
+ " ,mIsAggregationEnabled:" + mIsAggregationEnabled + " ,mSubId:" + mSubId);
mCarrierConfigChangeListener =
(slotIndex, subId, carrierId, specificCarrierId) -> handleCarrierConfigChanged(
subId);
mCarrierConfigManager.registerCarrierConfigChangeListener(mNetworkScanExecutor,
mCarrierConfigChangeListener);
}
@Keep
@VisibleForTesting
protected boolean enableNewAutoSelectNetworkUI(Context context) {
return context.getResources().getBoolean(
com.android.internal.R.bool.config_enableNewAutoSelectNetworkUI);
}
@Keep
@VisibleForTesting
protected boolean enableAggregation(Context context) {
return context.getResources().getBoolean(
R.bool.config_network_selection_list_aggregation_enabled);
mNetworkScanRepository = new NetworkScanRepository(context, mSubId);
}
@Keep
@@ -218,17 +197,42 @@ public class NetworkSelectSettings extends DashboardFragment {
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
final Activity activity = getActivity();
if (activity != null) {
mProgressHeader = setPinnedHeaderView(
com.android.settingslib.widget.progressbar.R.layout.progress_header)
.findViewById(com.android.settingslib.widget.progressbar.R.id.progress_bar_animation);
setProgressBarVisible(false);
}
com.android.settingslib.widget.progressbar.R.layout.progress_header
).findViewById(com.android.settingslib.widget.progressbar.R.id.progress_bar_animation);
forceUpdateConnectedPreferenceCategory();
launchNetworkScan();
}
private void launchNetworkScan() {
mNetworkScanRepository.launchNetworkScan(getViewLifecycleOwner(), new Function1<>() {
@Override
public Unit invoke(@NonNull NetworkScanResult networkScanResult) {
if (!mUpdateScanResult) {
// Not update UI if not in scan mode.
return Unit.INSTANCE;
}
if (networkScanResult instanceof NetworkScanCellInfos networkScanCellInfos) {
scanResultHandler(networkScanCellInfos.getCellInfos());
return Unit.INSTANCE;
}
if (!isPreferenceScreenEnabled()) {
clearPreferenceSummary();
enablePreferenceScreen(true);
} else if (networkScanResult instanceof NetworkScanComplete
&& mCellInfoList == null) {
// In case the scan timeout before getting any results
addMessagePreference(R.string.empty_networks_list);
} else if (networkScanResult instanceof NetworkScanError) {
addMessagePreference(R.string.network_query_error);
}
return Unit.INSTANCE;
}
});
}
@Override
@@ -236,12 +240,8 @@ public class NetworkSelectSettings extends DashboardFragment {
super.onStart();
updateForbiddenPlmns();
if (isProgressBarVisible()) {
return;
}
if (mWaitingForNumberOfScanResults <= 0) {
startNetworkQuery();
}
setProgressBarVisible(true);
mUpdateScanResult = true;
}
/**
@@ -256,14 +256,6 @@ public class NetworkSelectSettings extends DashboardFragment {
: new ArrayList<>();
}
@Override
public void onStop() {
if (mWaitingForNumberOfScanResults <= 0) {
stopNetworkQuery();
}
super.onStop();
}
@Override
public boolean onPreferenceTreeClick(Preference preference) {
if (preference == mSelectedPreference) {
@@ -275,7 +267,7 @@ public class NetworkSelectSettings extends DashboardFragment {
return false;
}
stopNetworkQuery();
mUpdateScanResult = false;
// Refresh the last selected item in case users reselect network.
clearPreferenceSummary();
@@ -294,8 +286,6 @@ public class NetworkSelectSettings extends DashboardFragment {
// Disable the screen until network is manually set
enablePreferenceScreen(false);
mRequestIdManualNetworkSelect = getNewRequestId();
mWaitingForNumberOfScanResults = MIN_NUMBER_OF_SCAN_REQUIRED;
final OperatorInfo operator = mSelectedPreference.getOperatorInfo();
ThreadUtils.postOnBackgroundThread(() -> {
final Message msg = mHandler.obtainMessage(
@@ -329,7 +319,6 @@ public class NetworkSelectSettings extends DashboardFragment {
switch (msg.what) {
case EVENT_SET_NETWORK_SELECTION_MANUALLY_DONE:
final boolean isSucceed = (boolean) msg.obj;
stopNetworkQuery();
setProgressBarVisible(false);
enablePreferenceScreen(true);
@@ -341,86 +330,15 @@ public class NetworkSelectSettings extends DashboardFragment {
Log.e(TAG, "No preference to update!");
}
break;
case EVENT_NETWORK_SCAN_RESULTS:
scanResultHandler((List<CellInfo>) msg.obj);
break;
case EVENT_NETWORK_SCAN_ERROR:
stopNetworkQuery();
Log.i(TAG, "Network scan failure " + msg.arg1 + ":"
+ " scan request 0x" + Long.toHexString(mRequestIdManualNetworkScan)
+ ", waiting for scan results = " + mWaitingForNumberOfScanResults
+ ", select request 0x"
+ Long.toHexString(mRequestIdManualNetworkSelect));
if (mRequestIdManualNetworkScan < mRequestIdManualNetworkSelect) {
break;
}
if (!isPreferenceScreenEnabled()) {
clearPreferenceSummary();
enablePreferenceScreen(true);
} else {
addMessagePreference(R.string.network_query_error);
}
break;
case EVENT_NETWORK_SCAN_COMPLETED:
stopNetworkQuery();
Log.d(TAG, "Network scan complete:"
+ " scan request 0x" + Long.toHexString(mRequestIdManualNetworkScan)
+ ", waiting for scan results = " + mWaitingForNumberOfScanResults
+ ", select request 0x"
+ Long.toHexString(mRequestIdManualNetworkSelect));
if (mRequestIdManualNetworkScan < mRequestIdManualNetworkSelect) {
break;
}
if (!isPreferenceScreenEnabled()) {
clearPreferenceSummary();
enablePreferenceScreen(true);
} else if (mCellInfoList == null) {
// In case the scan timeout before getting any results
addMessagePreference(R.string.empty_networks_list);
}
break;
}
return;
}
};
@VisibleForTesting
List<CellInfo> doAggregation(List<CellInfo> cellInfoListInput) {
if (!mIsAggregationEnabled) {
Log.d(TAG, "no aggregation");
return new ArrayList<>(cellInfoListInput);
}
ArrayList<CellInfo> aggregatedList = new ArrayList<>();
for (CellInfo cellInfo : cellInfoListInput) {
String plmn = CellInfoUtil.getNetworkTitle(cellInfo.getCellIdentity());
Class className = cellInfo.getClass();
Optional<CellInfo> itemInTheList = aggregatedList.stream().filter(
item -> {
String itemPlmn = CellInfoUtil.getNetworkTitle(item.getCellIdentity());
return itemPlmn.equals(plmn) && item.getClass().equals(className);
})
.findFirst();
if (itemInTheList.isPresent()) {
if (cellInfo.isRegistered() && !itemInTheList.get().isRegistered()) {
// Adding the registered cellinfo item into list. If there are two registered
// cellinfo items, then select first one from source list.
aggregatedList.set(aggregatedList.indexOf(itemInTheList.get()), cellInfo);
}
continue;
}
aggregatedList.add(cellInfo);
}
return filterOutSatellitePlmn(aggregatedList);
}
/* We do not want to expose carrier satellite plmns to the user when manually scan the
cellular network. Therefore, it is needed to filter out satellite plmns from current cell
info list */
private List<CellInfo> filterOutSatellitePlmn(List<CellInfo> cellInfoList) {
@VisibleForTesting
List<CellInfo> filterOutSatellitePlmn(List<CellInfo> cellInfoList) {
List<String> aggregatedSatellitePlmn = getSatellitePlmnsForCarrierWrapper();
if (!mShouldFilterOutSatellitePlmn.get() || aggregatedSatellitePlmn.isEmpty()) {
return cellInfoList;
@@ -455,39 +373,10 @@ public class NetworkSelectSettings extends DashboardFragment {
}
}
private final NetworkScanHelper.NetworkScanCallback mCallback =
new NetworkScanHelper.NetworkScanCallback() {
public void onResults(List<CellInfo> results) {
final Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_RESULTS, results);
msg.sendToTarget();
}
public void onComplete() {
final Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_COMPLETED);
msg.sendToTarget();
}
public void onError(int error) {
final Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_ERROR, error,
0 /* arg2 */);
msg.sendToTarget();
}
};
@Keep
@VisibleForTesting
protected void scanResultHandler(List<CellInfo> results) {
if (mRequestIdManualNetworkScan < mRequestIdManualNetworkSelect) {
Log.d(TAG, "CellInfoList (drop): "
+ CellInfoUtil.cellInfoListToString(new ArrayList<>(results)));
return;
}
mWaitingForNumberOfScanResults--;
if ((mWaitingForNumberOfScanResults <= 0) && (!isResumed())) {
stopNetworkQuery();
}
mCellInfoList = doAggregation(results);
mCellInfoList = filterOutSatellitePlmn(results);
Log.d(TAG, "CellInfoList: " + CellInfoUtil.cellInfoListToString(mCellInfoList));
if (mCellInfoList != null && mCellInfoList.size() != 0) {
final NetworkOperatorPreference connectedPref = updateAllPreferenceCategory();
@@ -646,11 +535,6 @@ public class NetworkSelectSettings extends DashboardFragment {
}
}
private long getNewRequestId() {
return Math.max(mRequestIdManualNetworkSelect,
mRequestIdManualNetworkScan) + 1;
}
private boolean isProgressBarVisible() {
if (mProgressHeader == null) {
return false;
@@ -671,29 +555,8 @@ public class NetworkSelectSettings extends DashboardFragment {
mPreferenceCategory.addPreference(mStatusMessagePreference);
}
private void startNetworkQuery() {
setProgressBarVisible(true);
if (mNetworkScanHelper != null) {
mRequestIdManualNetworkScan = getNewRequestId();
mWaitingForNumberOfScanResults = MIN_NUMBER_OF_SCAN_REQUIRED;
mNetworkScanHelper.startNetworkScan(
mUseNewApi
? NetworkScanHelper.NETWORK_SCAN_TYPE_INCREMENTAL_RESULTS
: NetworkScanHelper.NETWORK_SCAN_TYPE_WAIT_FOR_ALL_RESULTS);
}
}
private void stopNetworkQuery() {
setProgressBarVisible(false);
if (mNetworkScanHelper != null) {
mWaitingForNumberOfScanResults = 0;
mNetworkScanHelper.stopNetworkQuery();
}
}
@Override
public void onDestroy() {
stopNetworkQuery();
mNetworkScanExecutor.shutdown();
super.onDestroy();
}

View File

@@ -0,0 +1,167 @@
/*
* 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.settings.network.telephony.scan
import android.content.Context
import android.telephony.AccessNetworkConstants.AccessNetworkType
import android.telephony.CellInfo
import android.telephony.NetworkScanRequest
import android.telephony.PhoneCapability
import android.telephony.RadioAccessSpecifier
import android.telephony.TelephonyManager
import android.telephony.TelephonyScanManager
import android.util.Log
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LifecycleOwner
import com.android.settings.network.telephony.CellInfoUtil
import com.android.settings.network.telephony.CellInfoUtil.getNetworkTitle
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flowOn
class NetworkScanRepository(context: Context, subId: Int) {
sealed interface NetworkScanResult
data class NetworkScanCellInfos(val cellInfos: List<CellInfo>) : NetworkScanResult
data object NetworkScanComplete : NetworkScanResult
data class NetworkScanError(val error: Int) : NetworkScanResult
private val telephonyManager =
context.getSystemService(TelephonyManager::class.java)!!.createForSubscriptionId(subId)
/** TODO: Move this to UI layer, when UI layer migrated to Kotlin. */
fun launchNetworkScan(lifecycleOwner: LifecycleOwner, onResult: (NetworkScanResult) -> Unit) {
networkScanFlow().collectLatestWithLifecycle(lifecycleOwner, action = onResult)
}
data class CellInfoScanKey(
val title: String?,
val className: String,
val isRegistered: Boolean,
) {
constructor(cellInfo: CellInfo) : this(
title = cellInfo.cellIdentity.getNetworkTitle(),
className = cellInfo.javaClass.name,
isRegistered = cellInfo.isRegistered,
)
}
fun networkScanFlow(): Flow<NetworkScanResult> = callbackFlow {
val callback = object : TelephonyScanManager.NetworkScanCallback() {
override fun onResults(results: List<CellInfo>) {
val cellInfos = results.distinctBy { CellInfoScanKey(it) }
trySend(NetworkScanCellInfos(cellInfos))
Log.d(TAG, "CellInfoList: ${CellInfoUtil.cellInfoListToString(cellInfos)}")
}
override fun onComplete() {
trySend(NetworkScanComplete)
close()
Log.d(TAG, "onComplete")
}
override fun onError(error: Int) {
trySend(NetworkScanError(error))
close()
Log.d(TAG, "onError: $error")
}
}
val networkScan = telephonyManager.requestNetworkScan(
createNetworkScan(),
Dispatchers.Default.asExecutor(),
callback,
)
awaitClose { networkScan.stopScan() }
}.flowOn(Dispatchers.Default)
/** Create network scan for allowed network types. */
private fun createNetworkScan(): NetworkScanRequest {
val allowedNetworkTypes = getAllowedNetworkTypes()
Log.d(TAG, "createNetworkScan: allowedNetworkTypes = $allowedNetworkTypes")
val radioAccessSpecifiers = allowedNetworkTypes
.map { RadioAccessSpecifier(it, null, null) }
.toTypedArray()
return NetworkScanRequest(
NetworkScanRequest.SCAN_TYPE_ONE_SHOT,
radioAccessSpecifiers,
NetworkScanRequest.MIN_SEARCH_PERIODICITY_SEC, // one shot, not used
MAX_SEARCH_TIME_SEC,
true,
INCREMENTAL_RESULTS_PERIODICITY_SEC,
null,
)
}
private fun getAllowedNetworkTypes(): List<Int> {
val networkTypeBitmap3gpp: Long =
telephonyManager.getAllowedNetworkTypesBitmask() and
TelephonyManager.NETWORK_STANDARDS_FAMILY_BITMASK_3GPP
return buildList {
// If the allowed network types are unknown or if they are of the right class, scan for
// them; otherwise, skip them to save scan time and prevent users from being shown
// networks that they can't connect to.
if (networkTypeBitmap3gpp == 0L
|| networkTypeBitmap3gpp and TelephonyManager.NETWORK_CLASS_BITMASK_2G != 0L
) {
add(AccessNetworkType.GERAN)
}
if (networkTypeBitmap3gpp == 0L
|| networkTypeBitmap3gpp and TelephonyManager.NETWORK_CLASS_BITMASK_3G != 0L
) {
add(AccessNetworkType.UTRAN)
}
if (networkTypeBitmap3gpp == 0L
|| networkTypeBitmap3gpp and TelephonyManager.NETWORK_CLASS_BITMASK_4G != 0L
) {
add(AccessNetworkType.EUTRAN)
}
// If a device supports 5G stand-alone then the code below should be re-enabled; however
// a device supporting only non-standalone mode cannot perform PLMN selection and camp
// on a 5G network, which means that it shouldn't scan for 5G at the expense of battery
// as part of the manual network selection process.
//
if (networkTypeBitmap3gpp == 0L
|| (networkTypeBitmap3gpp and TelephonyManager.NETWORK_CLASS_BITMASK_5G != 0L &&
hasNrSaCapability())
) {
add(AccessNetworkType.NGRAN)
Log.d(TAG, "radioAccessSpecifiers add NGRAN.")
}
}
}
private fun hasNrSaCapability(): Boolean {
val phoneCapability = telephonyManager.getPhoneCapability()
return PhoneCapability.DEVICE_NR_CAPABILITY_SA in phoneCapability.deviceNrCapabilities
}
companion object {
private const val TAG = "NetworkScanRepository"
@VisibleForTesting
val MAX_SEARCH_TIME_SEC = 300
@VisibleForTesting
val INCREMENTAL_RESULTS_PERIODICITY_SEC = 3
}
}

View File

@@ -0,0 +1,265 @@
/*
* 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.settings.network.telephony.scan
import android.content.Context
import android.telephony.AccessNetworkConstants.AccessNetworkType
import android.telephony.CellIdentityCdma
import android.telephony.CellIdentityGsm
import android.telephony.CellIdentityLte
import android.telephony.CellInfoCdma
import android.telephony.CellInfoGsm
import android.telephony.CellInfoLte
import android.telephony.NetworkScan
import android.telephony.NetworkScanRequest
import android.telephony.PhoneCapability
import android.telephony.TelephonyManager
import android.telephony.TelephonyManager.NETWORK_CLASS_BITMASK_5G
import android.telephony.TelephonyScanManager
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanCellInfos
import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanComplete
import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanError
import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
import com.android.settingslib.spa.testutils.toListWithTimeout
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.argThat
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
import org.mockito.kotlin.stub
import org.mockito.kotlin.verify
@RunWith(AndroidJUnit4::class)
class NetworkScanRepositoryTest {
private var callback: TelephonyScanManager.NetworkScanCallback? = null
private val mockTelephonyManager = mock<TelephonyManager> {
on { createForSubscriptionId(SUB_ID) } doReturn mock
on { requestNetworkScan(any(), any(), any()) } doAnswer {
callback = it.arguments[2] as TelephonyScanManager.NetworkScanCallback
mock<NetworkScan>()
}
}
private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager
}
private val repository = NetworkScanRepository(context, SUB_ID)
@Test
fun networkScanFlow_initial() = runBlocking {
val result = repository.networkScanFlow().firstWithTimeoutOrNull()
assertThat(result).isNull()
}
@Test
fun networkScanFlow_onResults(): Unit = runBlocking {
val cellInfos = listOf(CellInfoCdma().apply { cellIdentity = CELL_IDENTITY_CDMA })
val listDeferred = async {
repository.networkScanFlow().toListWithTimeout()
}
delay(100)
callback?.onResults(cellInfos)
assertThat(listDeferred.await()).containsExactly(NetworkScanCellInfos(cellInfos))
}
@Test
fun networkScanFlow_onComplete(): Unit = runBlocking {
val listDeferred = async {
repository.networkScanFlow().toListWithTimeout()
}
delay(100)
callback?.onComplete()
assertThat(listDeferred.await()).containsExactly(NetworkScanComplete)
}
@Test
fun networkScanFlow_onError(): Unit = runBlocking {
val listDeferred = async {
repository.networkScanFlow().toListWithTimeout()
}
delay(100)
callback?.onError(1)
assertThat(listDeferred.await()).containsExactly(NetworkScanError(1))
}
@Test
fun networkScanFlow_hasDuplicateItems(): Unit = runBlocking {
val cellInfos = listOf(
createCellInfoLte("123", false),
createCellInfoLte("123", false),
createCellInfoLte("124", true),
createCellInfoLte("124", true),
createCellInfoGsm("123", false),
createCellInfoGsm("123", false),
)
val listDeferred = async {
repository.networkScanFlow().toListWithTimeout()
}
delay(100)
callback?.onResults(cellInfos)
assertThat(listDeferred.await()).containsExactly(
NetworkScanCellInfos(
listOf(
createCellInfoLte("123", false),
createCellInfoLte("124", true),
createCellInfoGsm("123", false),
)
)
)
}
@Test
fun networkScanFlow_noDuplicateItems(): Unit = runBlocking {
val cellInfos = listOf(
createCellInfoLte("123", false),
createCellInfoLte("123", true),
createCellInfoLte("124", false),
createCellInfoLte("124", true),
createCellInfoGsm("456", false),
createCellInfoGsm("456", true),
)
val listDeferred = async {
repository.networkScanFlow().toListWithTimeout()
}
delay(100)
callback?.onResults(cellInfos)
assertThat(listDeferred.await()).containsExactly(
NetworkScanCellInfos(
listOf(
createCellInfoLte("123", false),
createCellInfoLte("123", true),
createCellInfoLte("124", false),
createCellInfoLte("124", true),
createCellInfoGsm("456", false),
createCellInfoGsm("456", true),
)
)
)
}
@Test
fun createNetworkScan_deviceHasNrSa_requestNgran(): Unit = runBlocking {
mockTelephonyManager.stub {
on { getAllowedNetworkTypesBitmask() } doReturn NETWORK_CLASS_BITMASK_5G
on { getPhoneCapability() } doReturn
createPhoneCapability(intArrayOf(PhoneCapability.DEVICE_NR_CAPABILITY_SA))
}
repository.networkScanFlow().firstWithTimeoutOrNull()
verify(mockTelephonyManager).requestNetworkScan(argThat<NetworkScanRequest> {
specifiers.any { it.radioAccessNetwork == AccessNetworkType.NGRAN }
}, any(), any())
}
@Test
fun createNetworkScan_deviceNoNrSa_noNgran(): Unit = runBlocking {
mockTelephonyManager.stub {
on { getAllowedNetworkTypesBitmask() } doReturn NETWORK_CLASS_BITMASK_5G
on { getPhoneCapability() } doReturn
createPhoneCapability(intArrayOf(PhoneCapability.DEVICE_NR_CAPABILITY_NSA))
}
repository.networkScanFlow().firstWithTimeoutOrNull()
verify(mockTelephonyManager).requestNetworkScan(argThat<NetworkScanRequest> {
specifiers.none { it.radioAccessNetwork == AccessNetworkType.NGRAN }
}, any(), any())
}
private companion object {
const val SUB_ID = 1
const val LONG = "Long"
const val SHORT = "Short"
val CELL_IDENTITY_CDMA = CellIdentityCdma(
/* nid = */ 1,
/* sid = */ 2,
/* bid = */ 3,
/* lon = */ 4,
/* lat = */ 5,
/* alphal = */ LONG,
/* alphas = */ SHORT,
)
private fun createCellInfoLte(alphaLong: String, registered: Boolean): CellInfoLte {
val cellIdentityLte = CellIdentityLte(
/* ci = */ 1,
/* pci = */ 2,
/* tac = */ 3,
/* earfcn = */ 4,
/* bands = */ intArrayOf(1, 2),
/* bandwidth = */ 10000,
/* mccStr = */ null,
/* mncStr = */ null,
/* alphal = */ alphaLong,
/* alphas = */ null,
/* additionalPlmns = */ emptyList(),
/* csgInfo = */ null,
)
return CellInfoLte().apply {
cellIdentity = cellIdentityLte
isRegistered = registered
}
}
private fun createCellInfoGsm(alphaLong: String, registered: Boolean): CellInfoGsm {
val cellIdentityGsm = CellIdentityGsm(
/* lac = */ 1,
/* cid = */ 2,
/* arfcn = */ 3,
/* bsic = */ 4,
/* mccStr = */ "123",
/* mncStr = */ "01",
/* alphal = */ alphaLong,
/* alphas = */ null,
/* additionalPlmns = */ emptyList(),
)
return CellInfoGsm().apply {
cellIdentity = cellIdentityGsm
isRegistered = registered
}
}
private fun createPhoneCapability(deviceNrCapabilities: IntArray) =
PhoneCapability.Builder().setDeviceNrCapabilities(deviceNrCapabilities).build()
}
}

View File

@@ -1,260 +0,0 @@
/*
* Copyright (C) 2021 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.settings.network.telephony;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.telephony.AccessNetworkConstants;
import android.telephony.CellInfo;
import android.telephony.ModemInfo;
import android.telephony.NetworkScan;
import android.telephony.NetworkScanRequest;
import android.telephony.PhoneCapability;
import android.telephony.RadioAccessSpecifier;
import android.telephony.TelephonyManager;
import android.telephony.TelephonyScanManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@RunWith(AndroidJUnit4.class)
public class NetworkScanHelperTest {
@Mock
private TelephonyManager mTelephonyManager;
@Mock
private List<CellInfo> mCellInfos;
@Mock
private NetworkScanHelper.NetworkScanCallback mNetworkScanCallback;
private static final long THREAD_EXECUTION_TIMEOUT_MS = 3000L;
private ExecutorService mNetworkScanExecutor;
private NetworkScanHelper mNetworkScanHelper;
private static final int SCAN_ID = 1234;
private static final int SUB_ID = 1;
private NetworkScan mNetworkScan;
public class NetworkScanMock extends NetworkScan {
NetworkScanMock(int scanId, int subId) {
super(scanId, subId);
}
@Override
public void stopScan() {
return;
}
}
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mNetworkScanExecutor = Executors.newFixedThreadPool(1);
mNetworkScanHelper = new NetworkScanHelper(mTelephonyManager,
mNetworkScanCallback, mNetworkScanExecutor);
mNetworkScan = spy(new NetworkScanMock(SCAN_ID, SUB_ID));
}
@Test
public void startNetworkScan_incrementalAndSuccess_completionWithResult() {
when(mCellInfos.size()).thenReturn(1);
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
TelephonyScanManager.NetworkScanCallback callback =
(TelephonyScanManager.NetworkScanCallback)
(invocation.getArguments()[2]);
callback.onResults(mCellInfos);
callback.onComplete();
return mNetworkScan;
}
}).when(mTelephonyManager).requestNetworkScan(
any(NetworkScanRequest.class), any(Executor.class),
any(TelephonyScanManager.NetworkScanCallback.class));
ArgumentCaptor<List<CellInfo>> argument = ArgumentCaptor.forClass(List.class);
startNetworkScan_incremental(true);
verify(mNetworkScanCallback, times(1)).onResults(argument.capture());
List<CellInfo> actualResult = argument.getValue();
assertThat(actualResult.size()).isEqualTo(mCellInfos.size());
verify(mNetworkScanCallback, times(1)).onComplete();
}
@Test
public void startNetworkScan_incrementalAndImmediateFailure_failureWithErrorCode() {
doReturn(null).when(mTelephonyManager).requestNetworkScan(
any(NetworkScanRequest.class), any(Executor.class),
any(TelephonyScanManager.NetworkScanCallback.class));
startNetworkScan_incremental(true);
verify(mNetworkScanCallback, times(1)).onError(anyInt());
}
@Test
public void startNetworkScan_incrementalAndFailure_failureWithErrorCode() {
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
TelephonyScanManager.NetworkScanCallback callback =
(TelephonyScanManager.NetworkScanCallback)
(invocation.getArguments()[2]);
callback.onError(NetworkScan.ERROR_MODEM_ERROR);
return mNetworkScan;
}
}).when(mTelephonyManager).requestNetworkScan(
any(NetworkScanRequest.class), any(Executor.class),
any(TelephonyScanManager.NetworkScanCallback.class));
startNetworkScan_incremental(true);
verify(mNetworkScanCallback, times(1)).onError(anyInt());
}
@Test
public void startNetworkScan_incrementalAndAbort_doStop() {
doReturn(mNetworkScan).when(mTelephonyManager).requestNetworkScan(
any(NetworkScanRequest.class), any(Executor.class),
any(TelephonyScanManager.NetworkScanCallback.class));
startNetworkScan_incremental(false);
verify(mNetworkScan, times(1)).stopScan();
}
@Test
public void createNetworkScanForPreferredAccessNetworks_deviceNoNrSa_noNgran() {
int[] deviceNrCapabilities = new int[]{PhoneCapability.DEVICE_NR_CAPABILITY_NSA};
PhoneCapability phoneCapability = createPhoneCapability(deviceNrCapabilities);
doReturn(TelephonyManager.NETWORK_CLASS_BITMASK_2G
| TelephonyManager.NETWORK_CLASS_BITMASK_3G
| TelephonyManager.NETWORK_CLASS_BITMASK_4G
| TelephonyManager.NETWORK_CLASS_BITMASK_5G).when(
mTelephonyManager).getPreferredNetworkTypeBitmask();
doReturn(phoneCapability).when(mTelephonyManager).getPhoneCapability();
List<RadioAccessSpecifier> radioAccessSpecifiers = new ArrayList<>();
radioAccessSpecifiers.add(
new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.GERAN, null,
null));
radioAccessSpecifiers.add(
new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.UTRAN, null,
null));
radioAccessSpecifiers.add(
new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.EUTRAN, null,
null));
NetworkScanRequest expectedNetworkScanRequest = createNetworkScanRequest(
radioAccessSpecifiers);
assertEquals(expectedNetworkScanRequest,
mNetworkScanHelper.createNetworkScanForPreferredAccessNetworks());
}
@Test
public void createNetworkScanForPreferredAccessNetworks_deviceHasNrSa_hasNgran() {
int[] deviceNrCapabilities = new int[]{PhoneCapability.DEVICE_NR_CAPABILITY_NSA,
PhoneCapability.DEVICE_NR_CAPABILITY_SA};
PhoneCapability phoneCapability = createPhoneCapability(deviceNrCapabilities);
doReturn(TelephonyManager.NETWORK_CLASS_BITMASK_2G
| TelephonyManager.NETWORK_CLASS_BITMASK_3G
| TelephonyManager.NETWORK_CLASS_BITMASK_4G
| TelephonyManager.NETWORK_CLASS_BITMASK_5G).when(
mTelephonyManager).getPreferredNetworkTypeBitmask();
doReturn(phoneCapability).when(mTelephonyManager).getPhoneCapability();
List<RadioAccessSpecifier> radioAccessSpecifiers = new ArrayList<>();
radioAccessSpecifiers.add(
new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.GERAN, null,
null));
radioAccessSpecifiers.add(
new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.UTRAN, null,
null));
radioAccessSpecifiers.add(
new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.EUTRAN, null,
null));
radioAccessSpecifiers.add(
new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.NGRAN, null,
null));
NetworkScanRequest expectedNetworkScanRequest = createNetworkScanRequest(
radioAccessSpecifiers);
assertEquals(expectedNetworkScanRequest,
mNetworkScanHelper.createNetworkScanForPreferredAccessNetworks());
}
private PhoneCapability createPhoneCapability(int[] deviceNrCapabilities) {
int maxActiveVoiceCalls = 1;
int maxActiveData = 2;
ModemInfo modemInfo = new ModemInfo(1, 2, true, false);
List<ModemInfo> logicalModemList = new ArrayList<>();
logicalModemList.add(modemInfo);
return new PhoneCapability(maxActiveVoiceCalls, maxActiveData,
logicalModemList, false, deviceNrCapabilities);
}
private NetworkScanRequest createNetworkScanRequest(
List<RadioAccessSpecifier> radioAccessSpecifiers) {
return new NetworkScanRequest(
NetworkScanRequest.SCAN_TYPE_ONE_SHOT,
radioAccessSpecifiers.toArray(
new RadioAccessSpecifier[radioAccessSpecifiers.size()]),
mNetworkScanHelper.SEARCH_PERIODICITY_SEC,
mNetworkScanHelper.MAX_SEARCH_TIME_SEC,
mNetworkScanHelper.INCREMENTAL_RESULTS,
mNetworkScanHelper.INCREMENTAL_RESULTS_PERIODICITY_SEC,
null /* List of PLMN ids (MCC-MNC) */);
}
private void startNetworkScan_incremental(boolean waitForCompletion) {
mNetworkScanHelper.startNetworkScan(
NetworkScanHelper.NETWORK_SCAN_TYPE_INCREMENTAL_RESULTS);
if (!waitForCompletion) {
mNetworkScanHelper.stopNetworkQuery();
}
}
}

View File

@@ -83,7 +83,6 @@ public class NetworkSelectSettingsTest {
public Context mContext;
public PreferenceCategory mPreferenceCategory;
public boolean mIsAggregationEnabled = true;
private TargetClass mNetworkSelectSettings;
@@ -104,12 +103,13 @@ public class NetworkSelectSettingsTest {
doReturn(mCellId2).when(mCellInfo2).getCellIdentity();
doReturn(mock(CellSignalStrength.class)).when(mCellInfo2).getCellSignalStrength();
doReturn(CARRIER_NAME2).when(mCellId2).getOperatorAlphaLong();
mIsAggregationEnabled = true;
mNetworkSelectSettings = spy(new TargetClass(this));
PersistableBundle config = new PersistableBundle();
config.putBoolean(CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL, true);
doReturn(config).when(mCarrierConfigManager).getConfigForSubId(SUB_ID);
doReturn(config).when(mCarrierConfigManager).getConfigForSubId(SUB_ID,
CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL,
CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL);
doReturn(TelephonyManager.DATA_CONNECTED).when(mTelephonyManager).getDataState();
}
@@ -174,11 +174,6 @@ public class NetworkSelectSettingsTest {
return pref;
}
@Override
protected boolean enableAggregation(Context context) {
return mTestEnv.mIsAggregationEnabled;
}
@Override
protected int getSubId() {
return SUB_ID;
@@ -210,77 +205,7 @@ public class NetworkSelectSettingsTest {
}
@Test
public void doAggregation_hasDuplicateItemsDiffCellIdCase1_removeSamePlmnRatItem() {
mNetworkSelectSettings.onCreateInitialization();
List<CellInfo> testList = Arrays.asList(
createLteCellInfo(true, 123, "123", "232", "CarrierA"),
createLteCellInfo(true, 1234, "123", "232", "CarrierA"),
createGsmCellInfo(false, 123, "123", "232", "CarrierB"));
List<CellInfo> expected = Arrays.asList(
createLteCellInfo(true, 123, "123", "232", "CarrierA"),
createGsmCellInfo(false, 123, "123", "232", "CarrierB"));
assertThat(mNetworkSelectSettings.doAggregation(testList)).isEqualTo(expected);
}
@Test
public void doAggregation_hasDuplicateItemsDiffCellIdCase2_removeSamePlmnRatItem() {
mNetworkSelectSettings.onCreateInitialization();
List<CellInfo> testList = Arrays.asList(
createLteCellInfo(true, 123, "123", "232", "CarrierA"),
createGsmCellInfo(false, 123, "123", "232", "CarrierB"),
createLteCellInfo(false, 1234, "123", "232", "CarrierB"),
createGsmCellInfo(false, 1234, "123", "232", "CarrierB"));
List<CellInfo> expected = Arrays.asList(
createLteCellInfo(true, 123, "123", "232", "CarrierA"),
createGsmCellInfo(false, 123, "123", "232", "CarrierB"),
createLteCellInfo(false, 1234, "123", "232", "CarrierB"));
assertThat(mNetworkSelectSettings.doAggregation(testList)).isEqualTo(expected);
}
@Test
public void doAggregation_hasDuplicateItemsDiffMccMncCase1_removeSamePlmnRatItem() {
mNetworkSelectSettings.onCreateInitialization();
List<CellInfo> testList = Arrays.asList(
createLteCellInfo(true, 123, "123", "232", "CarrierA"),
createLteCellInfo(true, 123, "456", "232", "CarrierA"),
createGsmCellInfo(false, 123, "123", "232", "CarrierB"));
List<CellInfo> expected = Arrays.asList(
createLteCellInfo(true, 123, "123", "232", "CarrierA"),
createGsmCellInfo(false, 123, "123", "232", "CarrierB"));
assertThat(mNetworkSelectSettings.doAggregation(testList)).isEqualTo(expected);
}
@Test
public void doAggregation_hasDuplicateItemsDiffMccMncCase2_removeSamePlmnRatItem() {
mNetworkSelectSettings.onCreateInitialization();
List<CellInfo> testList = Arrays.asList(
createLteCellInfo(true, 123, "123", "232", "CarrierA"),
createGsmCellInfo(false, 123, "123", "232", "CarrierB"),
createLteCellInfo(false, 1234, "123", "232", "CarrierB"),
createGsmCellInfo(false, 123, "456", "232", "CarrierB"));
List<CellInfo> expected = Arrays.asList(
createLteCellInfo(true, 123, "123", "232", "CarrierA"),
createGsmCellInfo(false, 123, "123", "232", "CarrierB"),
createLteCellInfo(false, 1234, "123", "232", "CarrierB"));
assertThat(mNetworkSelectSettings.doAggregation(testList)).isEqualTo(expected);
}
@Test
public void doAggregation_hasDuplicateItemsDiffMccMncCase3_removeSamePlmnRatItem() {
mNetworkSelectSettings.onCreateInitialization();
List<CellInfo> testList = Arrays.asList(
createLteCellInfo(false, 123, "123", "232", "CarrierA"),
createLteCellInfo(false, 124, "123", "233", "CarrierA"),
createLteCellInfo(true, 125, "123", "234", "CarrierA"),
createGsmCellInfo(false, 126, "456", "232", "CarrierA"));
List<CellInfo> expected = Arrays.asList(
createLteCellInfo(true, 125, "123", "234", "CarrierA"),
createGsmCellInfo(false, 126, "456", "232", "CarrierA"));
assertThat(mNetworkSelectSettings.doAggregation(testList)).isEqualTo(expected);
}
@Test
public void doAggregation_filterOutSatellitePlmn_whenKeyIsTrue() {
public void filterOutSatellitePlmn_filterOutSatellitePlmn_whenKeyIsTrue() {
PersistableBundle config = new PersistableBundle();
config.putBoolean(
CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL, true);
@@ -304,11 +229,11 @@ public class NetworkSelectSettingsTest {
List<CellInfo> expected = Arrays.asList(
createGsmCellInfo(false, 123, "123", "233", "CarrierB"),
createLteCellInfo(false, 1234, "123", "234", "CarrierC"));
assertThat(mNetworkSelectSettings.doAggregation(testList)).isEqualTo(expected);
assertThat(mNetworkSelectSettings.filterOutSatellitePlmn(testList)).isEqualTo(expected);
}
@Test
public void doAggregation_filterOutSatellitePlmn_whenNoSatellitePlmnIsAvailable() {
public void filterOutSatellitePlmn_filterOutSatellitePlmn_whenNoSatellitePlmnIsAvailable() {
PersistableBundle config = new PersistableBundle();
config.putBoolean(
CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL, true);
@@ -336,17 +261,17 @@ public class NetworkSelectSettingsTest {
createGsmCellInfo(false, 123, "123", "233", "CarrierB"),
createLteCellInfo(false, 1234, "123", "234", "CarrierC"),
createGsmCellInfo(false, 12345, "123", "235", "CarrierD"));
assertThat(mNetworkSelectSettings.doAggregation(testList)).isEqualTo(expected);
assertThat(mNetworkSelectSettings.filterOutSatellitePlmn(testList)).isEqualTo(expected);
// Expect no filter out when KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL is false.
config.putBoolean(
CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL, false);
mNetworkSelectSettings.onCreateInitialization();
assertThat(mNetworkSelectSettings.doAggregation(testList)).isEqualTo(expected);
assertThat(mNetworkSelectSettings.filterOutSatellitePlmn(testList)).isEqualTo(expected);
}
@Test
public void doAggregation_filterOutSatellitePlmn_whenKeyIsFalse() {
public void filterOutSatellitePlmn_filterOutSatellitePlmn_whenKeyIsFalse() {
PersistableBundle config = new PersistableBundle();
config.putBoolean(
CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL, true);
@@ -372,7 +297,7 @@ public class NetworkSelectSettingsTest {
createGsmCellInfo(false, 123, "123", "233", "CarrierB"),
createLteCellInfo(false, 1234, "123", "234", "CarrierC"),
createGsmCellInfo(false, 12345, "123", "235", "CarrierD"));
assertThat(mNetworkSelectSettings.doAggregation(testList)).isEqualTo(expected);
assertThat(mNetworkSelectSettings.filterOutSatellitePlmn(testList)).isEqualTo(expected);
}
private CellInfoLte createLteCellInfo(boolean registered, int cellId, String mcc, String mnc,