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 @JvmStatic
fun cellInfoListToString(cellInfos: List<CellInfo>): String = 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. * 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; package com.android.settings.network.telephony;
import android.app.Activity;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@@ -39,6 +38,7 @@ import android.util.Log;
import android.view.View; import android.view.View;
import androidx.annotation.Keep; import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference; import androidx.preference.Preference;
@@ -49,14 +49,21 @@ import com.android.internal.telephony.OperatorInfo;
import com.android.internal.telephony.flags.Flags; import com.android.internal.telephony.flags.Flags;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment; 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.settings.overlay.FeatureFactory;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.utils.ThreadUtils; import com.android.settingslib.utils.ThreadUtils;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@@ -71,12 +78,8 @@ public class NetworkSelectSettings extends DashboardFragment {
private static final String TAG = "NetworkSelectSettings"; private static final String TAG = "NetworkSelectSettings";
private static final int EVENT_SET_NETWORK_SELECTION_MANUALLY_DONE = 1; 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 String PREF_KEY_NETWORK_OPERATORS = "network_operators_preference";
private static final int MIN_NUMBER_OF_SCAN_REQUIRED = 2;
private PreferenceCategory mPreferenceCategory; private PreferenceCategory mPreferenceCategory;
@VisibleForTesting @VisibleForTesting
@@ -91,18 +94,14 @@ public class NetworkSelectSettings extends DashboardFragment {
private CarrierConfigManager mCarrierConfigManager; private CarrierConfigManager mCarrierConfigManager;
private List<String> mForbiddenPlmns; private List<String> mForbiddenPlmns;
private boolean mShow4GForLTE = false; private boolean mShow4GForLTE = false;
private NetworkScanHelper mNetworkScanHelper;
private final ExecutorService mNetworkScanExecutor = Executors.newFixedThreadPool(1); private final ExecutorService mNetworkScanExecutor = Executors.newFixedThreadPool(1);
private MetricsFeatureProvider mMetricsFeatureProvider; private MetricsFeatureProvider mMetricsFeatureProvider;
private boolean mUseNewApi;
private long mRequestIdManualNetworkSelect;
private long mRequestIdManualNetworkScan;
private long mWaitingForNumberOfScanResults;
@VisibleForTesting
boolean mIsAggregationEnabled = false;
private CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener; private CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener;
private AtomicBoolean mShouldFilterOutSatellitePlmn = new AtomicBoolean(); private AtomicBoolean mShouldFilterOutSatellitePlmn = new AtomicBoolean();
private NetworkScanRepository mNetworkScanRepository;
private boolean mUpdateScanResult = false;
@Override @Override
public void onCreate(Bundle icicle) { public void onCreate(Bundle icicle) {
super.onCreate(icicle); super.onCreate(icicle);
@@ -114,7 +113,6 @@ public class NetworkSelectSettings extends DashboardFragment {
@Initializer @Initializer
protected void onCreateInitialization() { protected void onCreateInitialization() {
Context context = getContext(); Context context = getContext();
mUseNewApi = enableNewAutoSelectNetworkUI(context);
mSubId = getSubId(); mSubId = getSubId();
mPreferenceCategory = getPreferenceCategory(PREF_KEY_NETWORK_OPERATORS); mPreferenceCategory = getPreferenceCategory(PREF_KEY_NETWORK_OPERATORS);
@@ -124,8 +122,6 @@ public class NetworkSelectSettings extends DashboardFragment {
mTelephonyManager = getTelephonyManager(context, mSubId); mTelephonyManager = getTelephonyManager(context, mSubId);
mSatelliteManager = getSatelliteManager(context); mSatelliteManager = getSatelliteManager(context);
mCarrierConfigManager = getCarrierConfigManager(context); mCarrierConfigManager = getCarrierConfigManager(context);
mNetworkScanHelper = new NetworkScanHelper(
mTelephonyManager, mCallback, mNetworkScanExecutor);
PersistableBundle bundle = mCarrierConfigManager.getConfigForSubId(mSubId, PersistableBundle bundle = mCarrierConfigManager.getConfigForSubId(mSubId,
CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL, CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL,
CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL); CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL);
@@ -136,30 +132,13 @@ public class NetworkSelectSettings extends DashboardFragment {
true)); true));
mMetricsFeatureProvider = getMetricsFeatureProvider(context); mMetricsFeatureProvider = getMetricsFeatureProvider(context);
mIsAggregationEnabled = enableAggregation(context);
Log.d(TAG, "init: mUseNewApi:" + mUseNewApi
+ " ,mIsAggregationEnabled:" + mIsAggregationEnabled + " ,mSubId:" + mSubId);
mCarrierConfigChangeListener = mCarrierConfigChangeListener =
(slotIndex, subId, carrierId, specificCarrierId) -> handleCarrierConfigChanged( (slotIndex, subId, carrierId, specificCarrierId) -> handleCarrierConfigChanged(
subId); subId);
mCarrierConfigManager.registerCarrierConfigChangeListener(mNetworkScanExecutor, mCarrierConfigManager.registerCarrierConfigChangeListener(mNetworkScanExecutor,
mCarrierConfigChangeListener); mCarrierConfigChangeListener);
mNetworkScanRepository = new NetworkScanRepository(context, mSubId);
}
@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);
} }
@Keep @Keep
@@ -218,17 +197,42 @@ public class NetworkSelectSettings extends DashboardFragment {
} }
@Override @Override
public void onViewCreated(View view, Bundle savedInstanceState) { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
final Activity activity = getActivity(); mProgressHeader = setPinnedHeaderView(
if (activity != null) { com.android.settingslib.widget.progressbar.R.layout.progress_header
mProgressHeader = setPinnedHeaderView( ).findViewById(com.android.settingslib.widget.progressbar.R.id.progress_bar_animation);
com.android.settingslib.widget.progressbar.R.layout.progress_header)
.findViewById(com.android.settingslib.widget.progressbar.R.id.progress_bar_animation);
setProgressBarVisible(false);
}
forceUpdateConnectedPreferenceCategory(); 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 @Override
@@ -236,12 +240,8 @@ public class NetworkSelectSettings extends DashboardFragment {
super.onStart(); super.onStart();
updateForbiddenPlmns(); updateForbiddenPlmns();
if (isProgressBarVisible()) { setProgressBarVisible(true);
return; mUpdateScanResult = true;
}
if (mWaitingForNumberOfScanResults <= 0) {
startNetworkQuery();
}
} }
/** /**
@@ -256,14 +256,6 @@ public class NetworkSelectSettings extends DashboardFragment {
: new ArrayList<>(); : new ArrayList<>();
} }
@Override
public void onStop() {
if (mWaitingForNumberOfScanResults <= 0) {
stopNetworkQuery();
}
super.onStop();
}
@Override @Override
public boolean onPreferenceTreeClick(Preference preference) { public boolean onPreferenceTreeClick(Preference preference) {
if (preference == mSelectedPreference) { if (preference == mSelectedPreference) {
@@ -275,7 +267,7 @@ public class NetworkSelectSettings extends DashboardFragment {
return false; return false;
} }
stopNetworkQuery(); mUpdateScanResult = false;
// Refresh the last selected item in case users reselect network. // Refresh the last selected item in case users reselect network.
clearPreferenceSummary(); clearPreferenceSummary();
@@ -294,8 +286,6 @@ public class NetworkSelectSettings extends DashboardFragment {
// Disable the screen until network is manually set // Disable the screen until network is manually set
enablePreferenceScreen(false); enablePreferenceScreen(false);
mRequestIdManualNetworkSelect = getNewRequestId();
mWaitingForNumberOfScanResults = MIN_NUMBER_OF_SCAN_REQUIRED;
final OperatorInfo operator = mSelectedPreference.getOperatorInfo(); final OperatorInfo operator = mSelectedPreference.getOperatorInfo();
ThreadUtils.postOnBackgroundThread(() -> { ThreadUtils.postOnBackgroundThread(() -> {
final Message msg = mHandler.obtainMessage( final Message msg = mHandler.obtainMessage(
@@ -329,7 +319,6 @@ public class NetworkSelectSettings extends DashboardFragment {
switch (msg.what) { switch (msg.what) {
case EVENT_SET_NETWORK_SELECTION_MANUALLY_DONE: case EVENT_SET_NETWORK_SELECTION_MANUALLY_DONE:
final boolean isSucceed = (boolean) msg.obj; final boolean isSucceed = (boolean) msg.obj;
stopNetworkQuery();
setProgressBarVisible(false); setProgressBarVisible(false);
enablePreferenceScreen(true); enablePreferenceScreen(true);
@@ -341,86 +330,15 @@ public class NetworkSelectSettings extends DashboardFragment {
Log.e(TAG, "No preference to update!"); Log.e(TAG, "No preference to update!");
} }
break; 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 /* 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 cellular network. Therefore, it is needed to filter out satellite plmns from current cell
info list */ info list */
private List<CellInfo> filterOutSatellitePlmn(List<CellInfo> cellInfoList) { @VisibleForTesting
List<CellInfo> filterOutSatellitePlmn(List<CellInfo> cellInfoList) {
List<String> aggregatedSatellitePlmn = getSatellitePlmnsForCarrierWrapper(); List<String> aggregatedSatellitePlmn = getSatellitePlmnsForCarrierWrapper();
if (!mShouldFilterOutSatellitePlmn.get() || aggregatedSatellitePlmn.isEmpty()) { if (!mShouldFilterOutSatellitePlmn.get() || aggregatedSatellitePlmn.isEmpty()) {
return cellInfoList; 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 @Keep
@VisibleForTesting @VisibleForTesting
protected void scanResultHandler(List<CellInfo> results) { protected void scanResultHandler(List<CellInfo> results) {
if (mRequestIdManualNetworkScan < mRequestIdManualNetworkSelect) { mCellInfoList = filterOutSatellitePlmn(results);
Log.d(TAG, "CellInfoList (drop): "
+ CellInfoUtil.cellInfoListToString(new ArrayList<>(results)));
return;
}
mWaitingForNumberOfScanResults--;
if ((mWaitingForNumberOfScanResults <= 0) && (!isResumed())) {
stopNetworkQuery();
}
mCellInfoList = doAggregation(results);
Log.d(TAG, "CellInfoList: " + CellInfoUtil.cellInfoListToString(mCellInfoList)); Log.d(TAG, "CellInfoList: " + CellInfoUtil.cellInfoListToString(mCellInfoList));
if (mCellInfoList != null && mCellInfoList.size() != 0) { if (mCellInfoList != null && mCellInfoList.size() != 0) {
final NetworkOperatorPreference connectedPref = updateAllPreferenceCategory(); 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() { private boolean isProgressBarVisible() {
if (mProgressHeader == null) { if (mProgressHeader == null) {
return false; return false;
@@ -671,29 +555,8 @@ public class NetworkSelectSettings extends DashboardFragment {
mPreferenceCategory.addPreference(mStatusMessagePreference); 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 @Override
public void onDestroy() { public void onDestroy() {
stopNetworkQuery();
mNetworkScanExecutor.shutdown(); mNetworkScanExecutor.shutdown();
super.onDestroy(); 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 Context mContext;
public PreferenceCategory mPreferenceCategory; public PreferenceCategory mPreferenceCategory;
public boolean mIsAggregationEnabled = true;
private TargetClass mNetworkSelectSettings; private TargetClass mNetworkSelectSettings;
@@ -104,12 +103,13 @@ public class NetworkSelectSettingsTest {
doReturn(mCellId2).when(mCellInfo2).getCellIdentity(); doReturn(mCellId2).when(mCellInfo2).getCellIdentity();
doReturn(mock(CellSignalStrength.class)).when(mCellInfo2).getCellSignalStrength(); doReturn(mock(CellSignalStrength.class)).when(mCellInfo2).getCellSignalStrength();
doReturn(CARRIER_NAME2).when(mCellId2).getOperatorAlphaLong(); doReturn(CARRIER_NAME2).when(mCellId2).getOperatorAlphaLong();
mIsAggregationEnabled = true;
mNetworkSelectSettings = spy(new TargetClass(this)); mNetworkSelectSettings = spy(new TargetClass(this));
PersistableBundle config = new PersistableBundle(); PersistableBundle config = new PersistableBundle();
config.putBoolean(CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL, true); 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(); doReturn(TelephonyManager.DATA_CONNECTED).when(mTelephonyManager).getDataState();
} }
@@ -174,11 +174,6 @@ public class NetworkSelectSettingsTest {
return pref; return pref;
} }
@Override
protected boolean enableAggregation(Context context) {
return mTestEnv.mIsAggregationEnabled;
}
@Override @Override
protected int getSubId() { protected int getSubId() {
return SUB_ID; return SUB_ID;
@@ -210,77 +205,7 @@ public class NetworkSelectSettingsTest {
} }
@Test @Test
public void doAggregation_hasDuplicateItemsDiffCellIdCase1_removeSamePlmnRatItem() { public void filterOutSatellitePlmn_filterOutSatellitePlmn_whenKeyIsTrue() {
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() {
PersistableBundle config = new PersistableBundle(); PersistableBundle config = new PersistableBundle();
config.putBoolean( config.putBoolean(
CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL, true); CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL, true);
@@ -304,11 +229,11 @@ public class NetworkSelectSettingsTest {
List<CellInfo> expected = Arrays.asList( List<CellInfo> expected = Arrays.asList(
createGsmCellInfo(false, 123, "123", "233", "CarrierB"), createGsmCellInfo(false, 123, "123", "233", "CarrierB"),
createLteCellInfo(false, 1234, "123", "234", "CarrierC")); createLteCellInfo(false, 1234, "123", "234", "CarrierC"));
assertThat(mNetworkSelectSettings.doAggregation(testList)).isEqualTo(expected); assertThat(mNetworkSelectSettings.filterOutSatellitePlmn(testList)).isEqualTo(expected);
} }
@Test @Test
public void doAggregation_filterOutSatellitePlmn_whenNoSatellitePlmnIsAvailable() { public void filterOutSatellitePlmn_filterOutSatellitePlmn_whenNoSatellitePlmnIsAvailable() {
PersistableBundle config = new PersistableBundle(); PersistableBundle config = new PersistableBundle();
config.putBoolean( config.putBoolean(
CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL, true); CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL, true);
@@ -336,17 +261,17 @@ public class NetworkSelectSettingsTest {
createGsmCellInfo(false, 123, "123", "233", "CarrierB"), createGsmCellInfo(false, 123, "123", "233", "CarrierB"),
createLteCellInfo(false, 1234, "123", "234", "CarrierC"), createLteCellInfo(false, 1234, "123", "234", "CarrierC"),
createGsmCellInfo(false, 12345, "123", "235", "CarrierD")); 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. // Expect no filter out when KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL is false.
config.putBoolean( config.putBoolean(
CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL, false); CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL, false);
mNetworkSelectSettings.onCreateInitialization(); mNetworkSelectSettings.onCreateInitialization();
assertThat(mNetworkSelectSettings.doAggregation(testList)).isEqualTo(expected); assertThat(mNetworkSelectSettings.filterOutSatellitePlmn(testList)).isEqualTo(expected);
} }
@Test @Test
public void doAggregation_filterOutSatellitePlmn_whenKeyIsFalse() { public void filterOutSatellitePlmn_filterOutSatellitePlmn_whenKeyIsFalse() {
PersistableBundle config = new PersistableBundle(); PersistableBundle config = new PersistableBundle();
config.putBoolean( config.putBoolean(
CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL, true); CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL, true);
@@ -372,7 +297,7 @@ public class NetworkSelectSettingsTest {
createGsmCellInfo(false, 123, "123", "233", "CarrierB"), createGsmCellInfo(false, 123, "123", "233", "CarrierB"),
createLteCellInfo(false, 1234, "123", "234", "CarrierC"), createLteCellInfo(false, 1234, "123", "234", "CarrierC"),
createGsmCellInfo(false, 12345, "123", "235", "CarrierD")); 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, private CellInfoLte createLteCellInfo(boolean registered, int cellId, String mcc, String mnc,