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.
+ *
+ * There is no more result returned from {@link TelephonyManager} if an error occurred.
+ *
+ *
{@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.
+ *
+ *
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)}.
+ *
+ *
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. */
+ private static final boolean INCREMENTAL_RESULTS = true;
+ private static final int SEARCH_PERIODICITY_SEC = 5;
+ private static final int MAX_SEARCH_TIME_SEC = 300;
+ private static final int INCREMENTAL_RESULTS_PERIODICITY_SEC = 3;
+
+ private static final NetworkScanRequest NETWORK_SCAN_REQUEST =
+ new NetworkScanRequest(
+ NetworkScanRequest.SCAN_TYPE_ONE_SHOT,
+ new RadioAccessSpecifier[]{
+ // GSM
+ new RadioAccessSpecifier(
+ AccessNetworkType.GERAN,
+ null /* bands */,
+ null /* channels */),
+ // LTE
+ new RadioAccessSpecifier(
+ AccessNetworkType.EUTRAN,
+ null /* bands */,
+ null /* channels */),
+ // WCDMA
+ new RadioAccessSpecifier(
+ AccessNetworkType.UTRAN,
+ null /* bands */,
+ null /* channels */)
+ },
+ SEARCH_PERIODICITY_SEC,
+ MAX_SEARCH_TIME_SEC,
+ INCREMENTAL_RESULTS,
+ INCREMENTAL_RESULTS_PERIODICITY_SEC,
+ null /* List of PLMN ids (MCC-MNC) */);
+
+ private final NetworkScanCallback mNetworkScanCallback;
+ private final TelephonyManager mTelephonyManager;
+ private final TelephonyScanManager.NetworkScanCallback mInternalNetworkScanCallback;
+ private final Executor mExecutor;
+
+ private NetworkScan mNetworkScanRequester;
+
+ /** Callbacks for sync network scan */
+ private ListenableFuture> mNetworkScanFuture;
+
+ public NetworkScanHelper(TelephonyManager tm, NetworkScanCallback callback, Executor executor) {
+ mTelephonyManager = tm;
+ mNetworkScanCallback = callback;
+ mInternalNetworkScanCallback = new NetworkScanCallbackImpl();
+ mExecutor = executor;
+ }
+
+ /**
+ * 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>() {
+ @Override
+ public void onSuccess(List result) {
+ onResults(result);
+ onComplete();
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ int errCode = Integer.parseInt(t.getMessage());
+ onError(errCode);
+ }
+ });
+ mExecutor.execute(new NetworkScanSyncTask(
+ mTelephonyManager, (SettableFuture) mNetworkScanFuture));
+ } else if (type == NETWORK_SCAN_TYPE_INCREMENTAL_RESULTS) {
+ if (DBG) Log.d(TAG, "start network scan async");
+ mNetworkScanRequester = mTelephonyManager.requestNetworkScan(
+ NETWORK_SCAN_REQUEST,
+ mExecutor,
+ mInternalNetworkScanCallback);
+ }
+ }
+
+ /**
+ * 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();
+ mNetworkScanFuture = null;
+ }
+
+ if (mNetworkScanFuture != null) {
+ mNetworkScanFuture.cancel(true /* mayInterruptIfRunning */);
+ mNetworkScanFuture = null;
+ }
+ }
+
+ private void onResults(List cellInfos) {
+ mNetworkScanCallback.onResults(cellInfos);
+ }
+
+ private void onComplete() {
+ mNetworkScanCallback.onComplete();
+ }
+
+ private void onError(int errCode) {
+ mNetworkScanCallback.onError(errCode);
+ }
+
+ /**
+ * 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 results) {
+ if (DBG) Log.d(TAG, "async scan onResults() results = " + results);
+ NetworkScanHelper.this.onResults(results);
+ }
+
+ public void onComplete() {
+ if (DBG) Log.d(TAG, "async scan onComplete()");
+ NetworkScanHelper.this.onComplete();
+ }
+
+ public void onError(@NetworkScan.ScanErrorCode int errCode) {
+ if (DBG) Log.d(TAG, "async scan onError() errorCode = " + errCode);
+ NetworkScanHelper.this.onError(errCode);
+ }
+ }
+
+ private static final class NetworkScanSyncTask implements Runnable {
+ private final SettableFuture> mCallback;
+ private final TelephonyManager mTelephonyManager;
+
+ NetworkScanSyncTask(
+ TelephonyManager telephonyManager, SettableFuture> callback) {
+ mTelephonyManager = telephonyManager;
+ mCallback = callback;
+ }
+
+ @Override
+ public void run() {
+ if (DBG) Log.d(TAG, "sync scan start");
+ CellNetworkScanResult result = mTelephonyManager.getAvailableNetworks();
+ if (result.getStatus() == CellNetworkScanResult.STATUS_SUCCESS) {
+ List cellInfos = result.getOperators()
+ .stream()
+ .map(operatorInfo
+ -> CellInfoUtil.convertOperatorInfoToCellInfo(operatorInfo))
+ .collect(Collectors.toList());
+ if (DBG) Log.d(TAG, "sync scan complete");
+ mCallback.set(cellInfos);
+ } else {
+ mCallback.setException(new Throwable(
+ Integer.toString(convertToScanErrorCode(result.getStatus()))));
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/mobilenetwork/NetworkSelectListPreference.java b/src/com/android/settings/mobilenetwork/NetworkSelectListPreference.java
new file mode 100644
index 00000000000..95283dcc74b
--- /dev/null
+++ b/src/com/android/settings/mobilenetwork/NetworkSelectListPreference.java
@@ -0,0 +1,505 @@
+/*
+ * Copyright (C) 2006 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.phone;
+
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.telephony.CellInfo;
+import android.telephony.CellInfoCdma;
+import android.telephony.CellInfoGsm;
+import android.telephony.CellInfoLte;
+import android.telephony.CellInfoWcdma;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.text.BidiFormatter;
+import android.text.TextDirectionHeuristics;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.telephony.OperatorInfo;
+import com.android.phone.NetworkScanHelper.NetworkScanCallback;
+import com.android.settingslib.utils.ThreadUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+
+/**
+ * "Networks" preference in "Mobile network" settings UI for the Phone app.
+ * It's used to manually search and choose mobile network. Enabled only when
+ * autoSelect preference is turned off.
+ */
+public class NetworkSelectListPreference extends ListPreference
+ implements DialogInterface.OnCancelListener,
+ Preference.OnPreferenceChangeListener{
+
+ private static final String LOG_TAG = "networkSelect";
+ private static final boolean DBG = true;
+
+ private static final int EVENT_MANUALLY_NETWORK_SELECTION_DONE = 1;
+ private static final int EVENT_NETWORK_SCAN_RESULTS = 2;
+ private static final int EVENT_NETWORK_SCAN_COMPLETED = 3;
+ private static final int EVENT_NETWORK_SCAN_ERROR = 4;
+
+ //dialog ids
+ private static final int DIALOG_NETWORK_SELECTION = 100;
+ private static final int DIALOG_NETWORK_LIST_LOAD = 200;
+
+ private final ExecutorService mNetworkScanExecutor = Executors.newFixedThreadPool(1);
+
+ private List mCellInfoList;
+ private CellInfo mCellInfo;
+
+ private int mSubId;
+ private TelephonyManager mTelephonyManager;
+ private NetworkScanHelper mNetworkScanHelper;
+ private NetworkOperators mNetworkOperators;
+ private List mForbiddenPlmns;
+
+ private ProgressDialog mProgressDialog;
+ public NetworkSelectListPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public NetworkSelectListPreference(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onClick() {
+ showProgressDialog(DIALOG_NETWORK_LIST_LOAD);
+ TelephonyManager telephonyManager = (TelephonyManager)
+ getContext().getSystemService(Context.TELEPHONY_SERVICE);
+ new AsyncTask>() {
+ @Override
+ protected List doInBackground(Void... voids) {
+ String[] forbiddenPlmns = telephonyManager.getForbiddenPlmns();
+ return forbiddenPlmns != null ? Arrays.asList(forbiddenPlmns) : null;
+ }
+
+ @Override
+ protected void onPostExecute(List result) {
+ mForbiddenPlmns = result;
+ loadNetworksList();
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_MANUALLY_NETWORK_SELECTION_DONE:
+ if (DBG) logd("hideProgressPanel");
+ dismissProgressDialog();
+
+ boolean isSuccessed = (boolean) msg.obj;
+ if (isSuccessed) {
+ if (DBG) {
+ logd("manual network selection: succeeded! "
+ + getNetworkTitle(mCellInfo));
+ }
+ mNetworkOperators.displayNetworkSelectionSucceeded();
+ } else {
+ if (DBG) logd("manual network selection: failed!");
+ mNetworkOperators.displayNetworkSelectionFailed();
+ }
+ mNetworkOperators.getNetworkSelectionMode();
+ break;
+
+ case EVENT_NETWORK_SCAN_RESULTS:
+ List results = (List) msg.obj;
+ results.removeIf(cellInfo -> cellInfo == null);
+ mCellInfoList = new ArrayList<>(results);
+ if (DBG) logd("CALLBACK_SCAN_RESULTS" + mCellInfoList.toString());
+ break;
+
+ case EVENT_NETWORK_SCAN_COMPLETED:
+ if (DBG) logd("scan complete, load the cellInfosList");
+ dismissProgressDialog();
+ networksListLoaded();
+ break;
+ case EVENT_NETWORK_SCAN_ERROR:
+ dismissProgressDialog();
+ displayNetworkQueryFailed();
+ mNetworkOperators.getNetworkSelectionMode();
+ break;
+ }
+ return;
+ }
+ };
+
+ private final NetworkScanHelper.NetworkScanCallback mCallback = new NetworkScanCallback() {
+ public void onResults(List results) {
+ if (DBG) logd("get scan results: " + results.toString());
+ Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_RESULTS, results);
+ msg.sendToTarget();
+ }
+
+ public void onComplete() {
+ if (DBG) logd("network scan completed.");
+ Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_COMPLETED);
+ msg.sendToTarget();
+ }
+
+ public void onError(int error) {
+ if (DBG) logd("network scan error.");
+ Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_ERROR);
+ msg.sendToTarget();
+ }
+ };
+
+ @Override
+ //implemented for DialogInterface.OnCancelListener
+ public void onCancel(DialogInterface dialog) {
+ if (DBG) logd("user manually close the dialog");
+ mNetworkScanHelper.stopNetworkQuery();
+
+ // If cancelled, we query NetworkSelectMode and update states of AutoSelect button.
+ mNetworkOperators.getNetworkSelectionMode();
+ }
+
+ @Override
+ protected void onDialogClosed(boolean positiveResult) {
+ super.onDialogClosed(positiveResult);
+ // If dismissed, we query NetworkSelectMode and update states of AutoSelect button.
+ if (!positiveResult) {
+ mNetworkOperators.getNetworkSelectionMode();
+ }
+ }
+
+ // This initialize method needs to be called for this preference to work properly.
+ protected void initialize(int subId, NetworkOperators networkOperators,
+ ProgressDialog progressDialog) {
+ mSubId = subId;
+ mNetworkOperators = networkOperators;
+ // This preference should share the same progressDialog with networkOperators category.
+ mProgressDialog = progressDialog;
+
+ mTelephonyManager = TelephonyManager.from(getContext()).createForSubscriptionId(mSubId);
+ mNetworkScanHelper = new NetworkScanHelper(
+ mTelephonyManager, mCallback, mNetworkScanExecutor);
+
+ setSummary(mTelephonyManager.getNetworkOperatorName());
+
+ setOnPreferenceChangeListener(this);
+ }
+
+ @Override
+ protected void onPrepareForRemoval() {
+ destroy();
+ super.onPrepareForRemoval();
+ }
+
+ private void destroy() {
+ dismissProgressDialog();
+
+ if (mNetworkScanHelper != null) {
+ mNetworkScanHelper.stopNetworkQuery();
+ }
+
+ mNetworkScanExecutor.shutdown();
+ }
+
+ private void displayEmptyNetworkList() {
+ Toast.makeText(getContext(), R.string.empty_networks_list, Toast.LENGTH_LONG).show();
+ }
+
+ private void displayNetworkQueryFailed() {
+ Toast.makeText(getContext(), R.string.network_query_error, Toast.LENGTH_LONG).show();
+ }
+
+ private void loadNetworksList() {
+ if (DBG) logd("load networks list...");
+ mNetworkScanHelper.startNetworkScan(
+ NetworkScanHelper.NETWORK_SCAN_TYPE_WAIT_FOR_ALL_RESULTS);
+ }
+
+ private void networksListLoaded() {
+ if (DBG) logd("networks list loaded");
+
+ mNetworkOperators.getNetworkSelectionMode();
+ if (mCellInfoList != null) {
+ // create a preference for each item in the list.
+ // just use the operator name instead of the mildly
+ // confusing mcc/mnc.
+ List networkEntriesList = new ArrayList<>();
+ List networkEntryValuesList = new ArrayList<>();
+ for (CellInfo cellInfo: mCellInfoList) {
+ // Display each operator name only once.
+ String networkTitle = getNetworkTitle(cellInfo);
+ if (CellInfoUtil.isForbidden(cellInfo, mForbiddenPlmns)) {
+ networkTitle += " "
+ + getContext().getResources().getString(R.string.forbidden_network);
+ }
+ networkEntriesList.add(networkTitle);
+ networkEntryValuesList.add(getOperatorNumeric(cellInfo));
+ }
+ setEntries(networkEntriesList.toArray(new CharSequence[networkEntriesList.size()]));
+ setEntryValues(networkEntryValuesList.toArray(
+ new CharSequence[networkEntryValuesList.size()]));
+
+ super.onClick();
+ } else {
+ displayEmptyNetworkList();
+ }
+ }
+
+ private void dismissProgressDialog() {
+ if (mProgressDialog != null && mProgressDialog.isShowing()) {
+ try {
+ mProgressDialog.dismiss();
+ } catch (IllegalArgumentException ex) {
+ loge("Can't close the progress dialog " + ex);
+ }
+ }
+ }
+
+ private void showProgressDialog(int id) {
+ if (mProgressDialog == null) {
+ mProgressDialog = new ProgressDialog(getContext());
+ } else {
+ // Dismiss progress bar if it's showing now.
+ dismissProgressDialog();
+ }
+
+ switch (id) {
+ case DIALOG_NETWORK_SELECTION:
+ final String networkSelectMsg = getContext().getResources()
+ .getString(R.string.register_on_network,
+ getNetworkTitle(mCellInfo));
+ mProgressDialog.setMessage(networkSelectMsg);
+ mProgressDialog.setCanceledOnTouchOutside(false);
+ mProgressDialog.setCancelable(false);
+ mProgressDialog.setIndeterminate(true);
+ break;
+ case DIALOG_NETWORK_LIST_LOAD:
+ mProgressDialog.setMessage(
+ getContext().getResources().getString(R.string.load_networks_progress));
+ mProgressDialog.setCanceledOnTouchOutside(false);
+ mProgressDialog.setCancelable(true);
+ mProgressDialog.setIndeterminate(false);
+ mProgressDialog.setOnCancelListener(this);
+ break;
+ default:
+ }
+ mProgressDialog.show();
+ }
+
+ /**
+ * Implemented to support onPreferenceChangeListener to look for preference
+ * changes specifically on this button.
+ *
+ * @param preference is the preference to be changed, should be network select button.
+ * @param newValue should be the value of the selection as index of operators.
+ */
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ int operatorIndex = findIndexOfValue((String) newValue);
+ mCellInfo = mCellInfoList.get(operatorIndex);
+ if (DBG) logd("selected network: " + mCellInfo.toString());
+
+ MetricsLogger.action(getContext(),
+ MetricsEvent.ACTION_MOBILE_NETWORK_MANUAL_SELECT_NETWORK);
+
+ if (SubscriptionManager.isValidSubscriptionId(mSubId)) {
+ ThreadUtils.postOnBackgroundThread(() -> {
+ final OperatorInfo operatorInfo = getOperatorInfoFromCellInfo(mCellInfo);
+ if (DBG) logd("manually selected network: " + operatorInfo.toString());
+ boolean isSuccessed = mTelephonyManager.setNetworkSelectionModeManual(
+ operatorInfo, true /* persistSelection */);
+ Message msg = mHandler.obtainMessage(EVENT_MANUALLY_NETWORK_SELECTION_DONE);
+ msg.obj = isSuccessed;
+ msg.sendToTarget();
+ });
+ } else {
+ loge("Error selecting network, subscription Id is invalid " + mSubId);
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the title of the network obtained in the manual search.
+ *
+ * @param cellInfo contains the information of the network.
+ * @return Long Name if not null/empty, otherwise Short Name if not null/empty,
+ * else MCCMNC string.
+ */
+ private String getNetworkTitle(CellInfo cellInfo) {
+ OperatorInfo ni = getOperatorInfoFromCellInfo(cellInfo);
+
+ if (!TextUtils.isEmpty(ni.getOperatorAlphaLong())) {
+ return ni.getOperatorAlphaLong();
+ } else if (!TextUtils.isEmpty(ni.getOperatorAlphaShort())) {
+ return ni.getOperatorAlphaShort();
+ } else {
+ BidiFormatter bidiFormatter = BidiFormatter.getInstance();
+ return bidiFormatter.unicodeWrap(ni.getOperatorNumeric(), TextDirectionHeuristics.LTR);
+ }
+ }
+
+ /**
+ * Returns the operator numeric (MCCMNC) obtained in the manual search.
+ *
+ * @param cellInfo contains the information of the network.
+ * @return MCCMNC string.
+ */
+ private String getOperatorNumeric(CellInfo cellInfo) {
+ return getOperatorInfoFromCellInfo(cellInfo).getOperatorNumeric();
+ }
+
+ /**
+ * Wrap a cell info into an operator info.
+ */
+ private OperatorInfo getOperatorInfoFromCellInfo(CellInfo cellInfo) {
+ OperatorInfo oi;
+ if (cellInfo instanceof CellInfoLte) {
+ CellInfoLte lte = (CellInfoLte) cellInfo;
+ oi = new OperatorInfo(
+ (String) lte.getCellIdentity().getOperatorAlphaLong(),
+ (String) lte.getCellIdentity().getOperatorAlphaShort(),
+ lte.getCellIdentity().getMobileNetworkOperator());
+ } else if (cellInfo instanceof CellInfoWcdma) {
+ CellInfoWcdma wcdma = (CellInfoWcdma) cellInfo;
+ oi = new OperatorInfo(
+ (String) wcdma.getCellIdentity().getOperatorAlphaLong(),
+ (String) wcdma.getCellIdentity().getOperatorAlphaShort(),
+ wcdma.getCellIdentity().getMobileNetworkOperator());
+ } else if (cellInfo instanceof CellInfoGsm) {
+ CellInfoGsm gsm = (CellInfoGsm) cellInfo;
+ oi = new OperatorInfo(
+ (String) gsm.getCellIdentity().getOperatorAlphaLong(),
+ (String) gsm.getCellIdentity().getOperatorAlphaShort(),
+ gsm.getCellIdentity().getMobileNetworkOperator());
+ } else if (cellInfo instanceof CellInfoCdma) {
+ CellInfoCdma cdma = (CellInfoCdma) cellInfo;
+ oi = new OperatorInfo(
+ (String) cdma.getCellIdentity().getOperatorAlphaLong(),
+ (String) cdma.getCellIdentity().getOperatorAlphaShort(),
+ "" /* operator numeric */);
+ } else {
+ oi = new OperatorInfo("", "", "");
+ }
+ return oi;
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ final Parcelable superState = super.onSaveInstanceState();
+ if (isPersistent()) {
+ // No need to save instance state since it's persistent
+ return superState;
+ }
+
+ final SavedState myState = new SavedState(superState);
+ myState.mDialogListEntries = getEntries();
+ myState.mDialogListEntryValues = getEntryValues();
+ myState.mCellInfoList = mCellInfoList;
+ return myState;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ if (state == null || !state.getClass().equals(SavedState.class)) {
+ // Didn't save state for us in onSaveInstanceState
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
+ SavedState myState = (SavedState) state;
+
+ if (getEntries() == null && myState.mDialogListEntries != null) {
+ setEntries(myState.mDialogListEntries);
+ }
+ if (getEntryValues() == null && myState.mDialogListEntryValues != null) {
+ setEntryValues(myState.mDialogListEntryValues);
+ }
+ if (mCellInfoList == null && myState.mCellInfoList != null) {
+ mCellInfoList = myState.mCellInfoList;
+ }
+
+ super.onRestoreInstanceState(myState.getSuperState());
+ }
+
+ /**
+ * We save entries, entryValues and operatorInfoList into bundle.
+ * At onCreate of fragment, dialog will be restored if it was open. In this case,
+ * we need to restore entries, entryValues and operatorInfoList. Without those information,
+ * onPreferenceChange will fail if user select network from the dialog.
+ */
+ private static class SavedState extends BaseSavedState {
+ CharSequence[] mDialogListEntries;
+ CharSequence[] mDialogListEntryValues;
+ List mCellInfoList;
+
+ SavedState(Parcel source) {
+ super(source);
+ final ClassLoader boot = Object.class.getClassLoader();
+ mDialogListEntries = source.readCharSequenceArray();
+ mDialogListEntryValues = source.readCharSequenceArray();
+ mCellInfoList = source.readParcelableList(mCellInfoList, boot);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeCharSequenceArray(mDialogListEntries);
+ dest.writeCharSequenceArray(mDialogListEntryValues);
+ dest.writeParcelableList(mCellInfoList, flags);
+ }
+
+ SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ public static final Parcelable.Creator CREATOR =
+ new Parcelable.Creator() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+ private void logd(String msg) {
+ Log.d(LOG_TAG, "[NetworksList] " + msg);
+ }
+
+ private void loge(String msg) {
+ Log.e(LOG_TAG, "[NetworksList] " + msg);
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/mobilenetwork/RoamingDialogFragment.java b/src/com/android/settings/mobilenetwork/RoamingDialogFragment.java
new file mode 100644
index 00000000000..1b1091bcc8b
--- /dev/null
+++ b/src/com/android/settings/mobilenetwork/RoamingDialogFragment.java
@@ -0,0 +1,93 @@
+/*
+ * 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.mobilenetwork;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+
+/**
+ * A dialog fragment that asks the user if they are sure they want to turn on data roaming
+ * to avoid accidental charges.
+ */
+public class RoamingDialogFragment extends DialogFragment implements OnClickListener {
+
+ public static final String SUB_ID_KEY = "sub_id_key";
+
+ private CarrierConfigManager mCarrierConfigManager;
+ private int mSubId;
+
+ /**
+ * The interface we expect a host activity to implement.
+ */
+ public interface RoamingDialogListener {
+ void onPositiveButtonClick(DialogFragment dialog);
+ }
+
+ // the host activity which implements the listening interface
+ private RoamingDialogListener mListener;
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ Bundle args = getArguments();
+ mSubId = args.getInt(SUB_ID_KEY);
+ mCarrierConfigManager = new CarrierConfigManager(context);
+
+ // Verify host activity implemented callback interface
+ FragmentManager fragmentManager = getFragmentManager();
+ Fragment fragment = fragmentManager.findFragmentById(R.id.network_setting_content);
+ try {
+ mListener = (RoamingDialogListener) fragment;
+ } catch (ClassCastException e) {
+ throw new ClassCastException(fragment.toString() +
+ "must implement RoamingDialogListener");
+ }
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ int title = R.string.roaming_alert_title;
+ PersistableBundle carrierConfig = mCarrierConfigManager.getConfigForSubId(mSubId);
+ if (carrierConfig != null && carrierConfig.getBoolean(
+ CarrierConfigManager.KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL)) {
+ title = R.string.roaming_check_price_warning;
+ }
+ builder.setMessage(getResources().getString(R.string.roaming_warning))
+ .setTitle(title)
+ .setIconAttribute(android.R.attr.alertDialogIcon)
+ .setPositiveButton(android.R.string.yes, this)
+ .setNegativeButton(android.R.string.no, this);
+ return builder.create();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // let the host know that the positive button has been clicked
+ if (which == dialog.BUTTON_POSITIVE) {
+ mListener.onPositiveButtonClick(this);
+ }
+ }
+}