diff --git a/res/drawable/signal_strength_1x.xml b/res/drawable/signal_strength_1x.xml
new file mode 100644
index 00000000000..d69b0dc07af
--- /dev/null
+++ b/res/drawable/signal_strength_1x.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable/signal_strength_3g.xml b/res/drawable/signal_strength_3g.xml
new file mode 100644
index 00000000000..9b5e4a65717
--- /dev/null
+++ b/res/drawable/signal_strength_3g.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable/signal_strength_g.xml b/res/drawable/signal_strength_g.xml
new file mode 100644
index 00000000000..6f6e21b2524
--- /dev/null
+++ b/res/drawable/signal_strength_g.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable/signal_strength_lte.xml b/res/drawable/signal_strength_lte.xml
new file mode 100644
index 00000000000..998999f4782
--- /dev/null
+++ b/res/drawable/signal_strength_lte.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 37b3c4b812b..e28bc98bc21 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -351,4 +351,7 @@
6dp
6dp
+
+ 24dp
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 0c3caf6f21f..04852e0b64c 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -10074,4 +10074,20 @@
CBRS Data
+
+ Choose network
+
+ Disconnected
+
+ Connected
+
+ Connecting\u2026
+
+ Couldn\u2019t connect
+
+ No networks found.
+
+ Couldn\u2019t find networks. Try again.
+
+ (forbidden)
diff --git a/res/xml/choose_network.xml b/res/xml/choose_network.xml
new file mode 100644
index 00000000000..97ea9be2fd5
--- /dev/null
+++ b/res/xml/choose_network.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/com/android/settings/mobilenetwork/CellInfoUtil.java b/src/com/android/settings/mobilenetwork/CellInfoUtil.java
new file mode 100644
index 00000000000..3d6cb28a5bb
--- /dev/null
+++ b/src/com/android/settings/mobilenetwork/CellInfoUtil.java
@@ -0,0 +1,135 @@
+/*
+ * 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.telephony.CellIdentity;
+import android.telephony.CellIdentityCdma;
+import android.telephony.CellIdentityGsm;
+import android.telephony.CellIdentityLte;
+import android.telephony.CellIdentityWcdma;
+import android.telephony.CellInfo;
+import android.telephony.CellInfoCdma;
+import android.telephony.CellInfoGsm;
+import android.telephony.CellInfoLte;
+import android.telephony.CellInfoWcdma;
+import android.text.BidiFormatter;
+import android.text.TextDirectionHeuristics;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.telephony.OperatorInfo;
+
+import java.util.List;
+
+/**
+ * Add static Utility functions to get information from the CellInfo object.
+ * TODO: Modify {@link CellInfo} for simplify those functions
+ */
+public final class CellInfoUtil {
+ private static final String TAG = "NetworkSelectSetting";
+
+ private CellInfoUtil() {
+ }
+
+ /**
+ * Wrap a CellIdentity into a CellInfo.
+ */
+ public static CellInfo wrapCellInfoWithCellIdentity(CellIdentity cellIdentity) {
+ if (cellIdentity instanceof CellIdentityLte) {
+ CellInfoLte cellInfo = new CellInfoLte();
+ cellInfo.setCellIdentity((CellIdentityLte) cellIdentity);
+ return cellInfo;
+ } else if (cellIdentity instanceof CellIdentityCdma) {
+ CellInfoCdma cellInfo = new CellInfoCdma();
+ cellInfo.setCellIdentity((CellIdentityCdma) cellIdentity);
+ return cellInfo;
+ } else if (cellIdentity instanceof CellIdentityWcdma) {
+ CellInfoWcdma cellInfo = new CellInfoWcdma();
+ cellInfo.setCellIdentity((CellIdentityWcdma) cellIdentity);
+ return cellInfo;
+ } else if (cellIdentity instanceof CellIdentityGsm) {
+ CellInfoGsm cellInfo = new CellInfoGsm();
+ cellInfo.setCellIdentity((CellIdentityGsm) cellIdentity);
+ return cellInfo;
+ } else {
+ Log.e(TAG, "Invalid CellInfo type");
+ return null;
+ }
+ }
+
+ /**
+ * 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.
+ */
+ public static String getNetworkTitle(CellInfo cellInfo) {
+ OperatorInfo oi = getOperatorInfoFromCellInfo(cellInfo);
+
+ if (!TextUtils.isEmpty(oi.getOperatorAlphaLong())) {
+ return oi.getOperatorAlphaLong();
+ } else if (!TextUtils.isEmpty(oi.getOperatorAlphaShort())) {
+ return oi.getOperatorAlphaShort();
+ } else {
+ BidiFormatter bidiFormatter = BidiFormatter.getInstance();
+ return bidiFormatter.unicodeWrap(oi.getOperatorNumeric(), TextDirectionHeuristics.LTR);
+ }
+ }
+
+ /**
+ * Wrap a cell info into an operator info.
+ */
+ public static 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 {
+ Log.e(TAG, "Invalid CellInfo type");
+ oi = new OperatorInfo("", "", "");
+ }
+ return oi;
+ }
+
+ /** Checks whether the network operator is forbidden. */
+ public static boolean isForbidden(CellInfo cellInfo, List forbiddenPlmns) {
+ String plmn = CellInfoUtil.getOperatorInfoFromCellInfo(cellInfo).getOperatorNumeric();
+ return forbiddenPlmns != null && forbiddenPlmns.contains(plmn);
+ }
+}
diff --git a/src/com/android/settings/mobilenetwork/NetworkOperatorPreference.java b/src/com/android/settings/mobilenetwork/NetworkOperatorPreference.java
new file mode 100644
index 00000000000..b11d52c02c6
--- /dev/null
+++ b/src/com/android/settings/mobilenetwork/NetworkOperatorPreference.java
@@ -0,0 +1,135 @@
+/*
+ * 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.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.telephony.CellInfo;
+import android.telephony.SignalStrength;
+import android.util.Log;
+import android.view.Gravity;
+
+import com.android.settings.R;
+import com.android.settingslib.graph.SignalDrawable;
+
+import java.util.List;
+
+import androidx.preference.Preference;
+
+/**
+ * A Preference represents a network operator in the NetworkSelectSetting fragment.
+ */
+public class NetworkOperatorPreference extends Preference {
+
+ private static final String TAG = "NetworkOperatorPref";
+ private static final boolean DBG = false;
+ // number of signal strength level
+ public static final int NUMBER_OF_LEVELS = SignalStrength.NUM_SIGNAL_STRENGTH_BINS;
+ private CellInfo mCellInfo;
+ private List mForbiddenPlmns;
+ private int mLevel = -1;
+
+ // The following constants are used to draw signal icon.
+ private static final Drawable EMPTY_DRAWABLE = new ColorDrawable(Color.TRANSPARENT);
+ private static final int NO_CELL_DATA_CONNECTED_ICON = 0;
+
+ public NetworkOperatorPreference(
+ CellInfo cellinfo, Context context, List forbiddenPlmns) {
+ super(context);
+ mCellInfo = cellinfo;
+ mForbiddenPlmns = forbiddenPlmns;
+ refresh();
+ }
+
+ public CellInfo getCellInfo() {
+ return mCellInfo;
+ }
+
+ /**
+ * Refresh the NetworkOperatorPreference by updating the title and the icon.
+ */
+ public void refresh() {
+ if (DBG) Log.d(TAG, "refresh the network: " + CellInfoUtil.getNetworkTitle(mCellInfo));
+ String networkTitle = CellInfoUtil.getNetworkTitle(mCellInfo);
+ if (CellInfoUtil.isForbidden(mCellInfo, mForbiddenPlmns)) {
+ networkTitle += " " + getContext().getResources().getString(R.string.forbidden_network);
+ }
+ setTitle(networkTitle);
+ int level = mCellInfo.getCellSignalStrength().getLevel();
+ if (DBG) Log.d(TAG, "refresh level: " + String.valueOf(level));
+ if (mLevel != level) {
+ mLevel = level;
+ updateIcon(mLevel);
+ }
+ }
+
+ /**
+ * Update the icon according to the input signal strength level.
+ */
+ public void setIcon(int level) {
+ updateIcon(level);
+ }
+
+ private static int getIconIdForCell(CellInfo ci) {
+ final int type = ci.getCellIdentity().getType();
+ switch (type) {
+ case CellInfo.TYPE_GSM: return R.drawable.signal_strength_g;
+ case CellInfo.TYPE_WCDMA: // fall through
+ case CellInfo.TYPE_TDSCDMA: return R.drawable.signal_strength_3g;
+ case CellInfo.TYPE_LTE: return R.drawable.signal_strength_lte;
+ case CellInfo.TYPE_CDMA: return R.drawable.signal_strength_1x;
+ default: return 0;
+ }
+ }
+
+ private void updateIcon(int level) {
+ if (level < 0 || level >= NUMBER_OF_LEVELS) return;
+ Context context = getContext();
+ // Make the signal strength drawable
+ int iconId = 0;
+ if (DBG) Log.d(TAG, "updateIcon level: " + String.valueOf(level));
+ iconId = SignalDrawable.getState(level, NUMBER_OF_LEVELS, false /* cutOut */);
+
+ SignalDrawable signalDrawable = new SignalDrawable(getContext());
+ signalDrawable.setLevel(iconId);
+ signalDrawable.setDarkIntensity(0);
+
+ // Make the network type drawable
+ int iconType = getIconIdForCell(mCellInfo);
+ Drawable networkDrawable =
+ iconType == NO_CELL_DATA_CONNECTED_ICON
+ ? EMPTY_DRAWABLE
+ : getContext()
+ .getResources().getDrawable(iconType, getContext().getTheme());
+
+ // Overlay the two drawables
+ Drawable[] layers = {networkDrawable, signalDrawable};
+ final int iconSize =
+ context.getResources().getDimensionPixelSize(R.dimen.signal_strength_icon_size);
+
+ LayerDrawable icons = new LayerDrawable(layers);
+ // Set the network type icon at the top left
+ icons.setLayerGravity(0 /* index of networkDrawable */, Gravity.TOP | Gravity.LEFT);
+ // Set the signal strength icon at the bottom right
+ icons.setLayerGravity(1 /* index of SignalDrawable */, Gravity.BOTTOM | Gravity.RIGHT);
+ icons.setLayerSize(1 /* index of SignalDrawable */, iconSize, iconSize);
+ setIcon(icons);
+ }
+}
diff --git a/src/com/android/settings/mobilenetwork/NetworkSelectSettings.java b/src/com/android/settings/mobilenetwork/NetworkSelectSettings.java
new file mode 100644
index 00000000000..f04eae70d91
--- /dev/null
+++ b/src/com/android/settings/mobilenetwork/NetworkSelectSettings.java
@@ -0,0 +1,557 @@
+/*
+ * 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.content.Context;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.SearchIndexableResource;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.CellIdentity;
+import android.telephony.CellInfo;
+import android.telephony.NetworkRegistrationState;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.view.View;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.telephony.OperatorInfo;
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.search.Indexable;
+import com.android.settingslib.search.SearchIndexable;
+import com.android.settingslib.utils.ThreadUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceScreen;
+
+/**
+ * "Choose network" settings UI for the Phone app.
+ */
+//TODO(b/115429509): Add test for this file once b/115429509 is not blocked anymore
+@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
+public class NetworkSelectSettings extends DashboardFragment {
+
+ private static final String TAG = "NetworkSelectSetting";
+ private static final boolean DBG = true;
+
+ public static final String KEY_SUBSCRIPTION_ID = "subscription_id";
+
+ 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_CONNECTED_NETWORK_OPERATOR =
+ "connected_network_operator_preference";
+ private static final String PREF_KEY_NETWORK_OPERATORS = "network_operators_preference";
+
+ // used to add/remove NetworkOperatorsPreference.
+ private PreferenceCategory mNetworkOperatorsPreferences;
+ // used to add/remove connected NetworkOperatorPreference.
+ private PreferenceCategory mConnectedNetworkOperatorsPreference;
+ // manage the progress bar on the top of the page.
+ private View mProgressHeader;
+ private Preference mStatusMessagePreference;
+ private List mCellInfoList;
+ private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ private NetworkOperatorPreference mSelectedNetworkOperatorPreference;
+ private TelephonyManager mTelephonyManager;
+ private List mForbiddenPlmns;
+ //Flag indicating whether we have called bind on the service.
+ private boolean mShouldUnbind;
+
+ private final Runnable mUpdateNetworkOperatorsRunnable = () -> {
+ updateNetworkOperatorsPreferenceCategory();
+ };
+
+ /**
+ * Create a new instance of this fragment.
+ */
+ public static NetworkSelectSettings newInstance(int subId) {
+ Bundle args = new Bundle();
+ args.putInt(KEY_SUBSCRIPTION_ID, subId);
+ NetworkSelectSettings
+ fragment = new NetworkSelectSettings();
+ fragment.setArguments(args);
+
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ if (DBG) logd("onCreate");
+ super.onCreate(icicle);
+
+ mSubId = getArguments().getInt(KEY_SUBSCRIPTION_ID);
+
+ mConnectedNetworkOperatorsPreference =
+ (PreferenceCategory) findPreference(PREF_KEY_CONNECTED_NETWORK_OPERATOR);
+ mNetworkOperatorsPreferences =
+ (PreferenceCategory) findPreference(PREF_KEY_NETWORK_OPERATORS);
+ mStatusMessagePreference = new Preference(getContext());
+ mSelectedNetworkOperatorPreference = null;
+ mTelephonyManager = TelephonyManager.from(getContext()).createForSubscriptionId(mSubId);
+ setRetainInstance(true);
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ mProgressHeader = setPinnedHeaderView(R.layout.wifi_progress_header)
+ .findViewById(R.id.progress_bar_animation);
+ setProgressBarVisible(false);
+ forceConfigConnectedNetworkOperatorsPreferenceCategory();
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ new AsyncTask>() {
+ @Override
+ protected List doInBackground(Void... voids) {
+ String[] forbiddenPlmns = mTelephonyManager.getForbiddenPlmns();
+ return forbiddenPlmns != null ? Arrays.asList(forbiddenPlmns) : null;
+ }
+
+ @Override
+ protected void onPostExecute(List result) {
+ mForbiddenPlmns = result;
+ bindNetworkQueryService();
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+
+ /**
+ * Invoked on each preference click in this hierarchy, overrides
+ * PreferenceActivity's implementation. Used to make sure we track the
+ * preference click events.
+ * Since the connected network operator is either faked (when no data connection) or already
+ * connected, we do not allow user to click the connected network operator.
+ */
+ @Override
+ public boolean onPreferenceTreeClick(Preference preference) {
+ if (DBG) logd("User clicked the screen");
+ stopNetworkQuery();
+ setProgressBarVisible(false);
+ if (preference instanceof NetworkOperatorPreference) {
+ // Refresh the last selected item in case users reselect network.
+ if (mSelectedNetworkOperatorPreference != null) {
+ mSelectedNetworkOperatorPreference.setSummary("");
+ }
+
+ mSelectedNetworkOperatorPreference = (NetworkOperatorPreference) preference;
+ CellInfo cellInfo = mSelectedNetworkOperatorPreference.getCellInfo();
+ if (DBG) logd("User click a NetworkOperatorPreference: " + cellInfo.toString());
+
+ // Send metrics event
+ MetricsLogger.action(getContext(),
+ MetricsEvent.ACTION_MOBILE_NETWORK_MANUAL_SELECT_NETWORK);
+
+ // Connect to the network
+ if (SubscriptionManager.isValidSubscriptionId(mSubId)) {
+ if (DBG) {
+ logd("Connect to the network: " + CellInfoUtil.getNetworkTitle(cellInfo));
+ }
+ // Set summary as "Connecting" to the selected network.
+ mSelectedNetworkOperatorPreference.setSummary(R.string.network_connecting);
+
+ // Set summary as "Disconnected" to the previously connected network
+ if (mConnectedNetworkOperatorsPreference.getPreferenceCount() > 0) {
+ NetworkOperatorPreference connectedNetworkOperator = (NetworkOperatorPreference)
+ (mConnectedNetworkOperatorsPreference.getPreference(0));
+ if (!CellInfoUtil.getNetworkTitle(cellInfo).equals(
+ CellInfoUtil.getNetworkTitle(connectedNetworkOperator.getCellInfo()))) {
+ connectedNetworkOperator.setSummary(R.string.network_disconnected);
+ }
+ }
+
+ OperatorInfo operatorInfo = CellInfoUtil.getOperatorInfoFromCellInfo(cellInfo);
+ if (DBG) logd("manually selected network operator: " + operatorInfo.toString());
+
+ ThreadUtils.postOnBackgroundThread(() -> {
+ Message msg = mHandler.obtainMessage(EVENT_SET_NETWORK_SELECTION_MANUALLY_DONE);
+ msg.obj = mTelephonyManager.setNetworkSelectionModeManual(
+ operatorInfo.getOperatorNumeric(), true /* persistSelection */);
+ msg.sendToTarget();
+ });
+
+ setProgressBarVisible(true);
+ return true;
+ } else {
+ loge("Error selecting network. Subscription Id is invalid.");
+ mSelectedNetworkOperatorPreference = null;
+ return false;
+ }
+
+ } else {
+ getPreferenceScreen().setEnabled(false);
+ return false;
+ }
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ if (DBG) logd("onStop");
+ getView().removeCallbacks(mUpdateNetworkOperatorsRunnable);
+ stopNetworkQuery();
+ // Unbind the NetworkQueryService
+ unbindNetworkQueryService();
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.choose_network;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ //TODO(b/114749736): add metrics id for this page
+ return 0;
+ }
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_SET_NETWORK_SELECTION_MANUALLY_DONE:
+ if (DBG) logd("network selection done: hide the progress header");
+ setProgressBarVisible(false);
+
+ boolean isSuccessed = (boolean) msg.obj;
+ if (isSuccessed) {
+ if (DBG) logd("manual network selection: succeeded! ");
+ // Set summary as "Connected" to the selected network.
+ mSelectedNetworkOperatorPreference.setSummary(R.string.network_connected);
+ } else {
+ if (DBG) logd("manual network selection: failed! ");
+ updateNetworkSelection();
+ // Set summary as "Couldn't connect" to the selected network.
+ mSelectedNetworkOperatorPreference.setSummary(
+ R.string.network_could_not_connect);
+ }
+ break;
+
+ case EVENT_NETWORK_SCAN_RESULTS:
+ List results = aggregateCellInfoList((List) msg.obj);
+ mCellInfoList = new ArrayList<>(results);
+ if (DBG) logd("after aggregate: " + mCellInfoList.toString());
+ if (mCellInfoList != null && mCellInfoList.size() != 0) {
+ updateNetworkOperators();
+ } else {
+ addMessagePreference(R.string.empty_networks_list);
+ }
+
+ break;
+
+ case EVENT_NETWORK_SCAN_ERROR:
+ int error = msg.arg1;
+ if (DBG) logd("error while querying available networks " + error);
+ stopNetworkQuery();
+ addMessagePreference(R.string.network_query_error);
+ break;
+
+ case EVENT_NETWORK_SCAN_COMPLETED:
+ stopNetworkQuery();
+ if (DBG) logd("scan complete");
+ if (mCellInfoList == null) {
+ // In case the scan timeout before getting any results
+ addMessagePreference(R.string.empty_networks_list);
+ }
+ break;
+ }
+ return;
+ }
+ };
+
+ private void updateNetworkOperators() {
+ if (DBG) logd("updateNetworkOperators");
+ if (getActivity() != null) {
+ final View view = getView();
+ final Handler handler = view.getHandler();
+ if (handler != null && handler.hasCallbacks(mUpdateNetworkOperatorsRunnable)) {
+ return;
+ }
+ view.post(mUpdateNetworkOperatorsRunnable);
+ }
+ }
+
+ /**
+ * Update the currently available network operators list, which only contains the unregistered
+ * network operators. So if the device has no data and the network operator in the connected
+ * network operator category shows "Disconnected", it will also exist in the available network
+ * operator category for user to select. On the other hand, if the device has data and the
+ * network operator in the connected network operator category shows "Connected", it will not
+ * exist in the available network category.
+ */
+ private void updateNetworkOperatorsPreferenceCategory() {
+ mNetworkOperatorsPreferences.removeAll();
+
+ configConnectedNetworkOperatorsPreferenceCategory();
+ for (int index = 0; index < mCellInfoList.size(); index++) {
+ if (!mCellInfoList.get(index).isRegistered()) {
+ NetworkOperatorPreference pref = new NetworkOperatorPreference(
+ mCellInfoList.get(index), getContext(), mForbiddenPlmns);
+ pref.setKey(CellInfoUtil.getNetworkTitle(mCellInfoList.get(index)));
+ pref.setOrder(index);
+ mNetworkOperatorsPreferences.addPreference(pref);
+ }
+ }
+ }
+
+ /**
+ * Config the connected network operator preference when the page was created. When user get
+ * into this page, the device might or might not have data connection.
+ * - If the device has data:
+ * 1. use {@code ServiceState#getNetworkRegistrationStates()} to get the currently
+ * registered cellIdentity, wrap it into a CellInfo;
+ * 2. set the signal strength level as strong;
+ * 3. use {@link TelephonyManager#getNetworkOperatorName()} to get the title of the
+ * previously connected network operator, since the CellIdentity got from step 1 only has
+ * PLMN.
+ * - If the device has no data, we will remove the connected network operators list from the
+ * screen.
+ */
+ private void forceConfigConnectedNetworkOperatorsPreferenceCategory() {
+ if (DBG) logd("Force config ConnectedNetworkOperatorsPreferenceCategory");
+ if (mTelephonyManager.getDataState() == mTelephonyManager.DATA_CONNECTED) {
+ // Try to get the network registration states
+ ServiceState ss = mTelephonyManager.getServiceState();
+ List networkList =
+ ss.getNetworkRegistrationStates(AccessNetworkConstants.TransportType.WWAN);
+ if (networkList == null || networkList.size() == 0) {
+ loge("getNetworkRegistrationStates return null");
+ // Remove the connected network operators category
+ removeConnectedNetworkOperatorPreference();
+ return;
+ }
+ CellIdentity cellIdentity = networkList.get(0).getCellIdentity();
+ CellInfo cellInfo = CellInfoUtil.wrapCellInfoWithCellIdentity(cellIdentity);
+ if (cellInfo != null) {
+ if (DBG) logd("Currently registered cell: " + cellInfo.toString());
+ NetworkOperatorPreference pref =
+ new NetworkOperatorPreference(cellInfo, getContext(), mForbiddenPlmns);
+ pref.setTitle(mTelephonyManager.getNetworkOperatorName());
+ pref.setSummary(R.string.network_connected);
+ // Update the signal strength icon, since the default signalStrength value would be
+ // zero (it would be quite confusing why the connected network has no signal)
+ pref.setIcon(NetworkOperatorPreference.NUMBER_OF_LEVELS - 1);
+
+ mConnectedNetworkOperatorsPreference.addPreference(pref);
+ } else {
+ loge("Invalid CellIfno: " + cellInfo.toString());
+ // Remove the connected network operators category
+ removeConnectedNetworkOperatorPreference();
+ }
+ } else {
+ if (DBG) logd("No currently registered cell");
+ // Remove the connected network operators category
+ removeConnectedNetworkOperatorPreference();
+ }
+ }
+
+ /**
+ * Configure the ConnectedNetworkOperatorsPreferenceCategory. The category only need to be
+ * configured if the category is currently empty or the operator network title of the previous
+ * connected network is different from the new one.
+ */
+ private void configConnectedNetworkOperatorsPreferenceCategory() {
+ if (DBG) logd("config ConnectedNetworkOperatorsPreferenceCategory");
+ // Remove the category if the CellInfo list is empty or does not have registered cell.
+ if (mCellInfoList.size() == 0) {
+ if (DBG) logd("empty cellinfo list");
+ removeConnectedNetworkOperatorPreference();
+ }
+ CellInfo connectedNetworkOperator = null;
+ for (CellInfo cellInfo : mCellInfoList) {
+ if (cellInfo.isRegistered()) {
+ connectedNetworkOperator = cellInfo;
+ break;
+ }
+ }
+ if (connectedNetworkOperator == null) {
+ if (DBG) logd("no registered network");
+ removeConnectedNetworkOperatorPreference();
+ return;
+ }
+
+ // config the category if it is empty.
+ if (mConnectedNetworkOperatorsPreference.getPreferenceCount() == 0) {
+ if (DBG) logd("ConnectedNetworkSelectList is empty, add one");
+ addConnectedNetworkOperatorPreference(connectedNetworkOperator);
+ return;
+ }
+ NetworkOperatorPreference previousConnectedNetworkOperator = (NetworkOperatorPreference)
+ (mConnectedNetworkOperatorsPreference.getPreference(0));
+
+ // config the category if the network title of the previous connected network is different
+ // from the new one.
+ String cTitle = CellInfoUtil.getNetworkTitle(connectedNetworkOperator);
+ String pTitle = CellInfoUtil.getNetworkTitle(
+ previousConnectedNetworkOperator.getCellInfo());
+ if (!cTitle.equals(pTitle)) {
+ if (DBG) logd("reconfig the category: connected network changed");
+ addConnectedNetworkOperatorPreference(connectedNetworkOperator);
+ return;
+ }
+ if (DBG) logd("same network operator is connected, only refresh the connected network");
+ // Otherwise same network operator is connected, only refresh the connected network
+ // operator preference (first and the only one in this category).
+ ((NetworkOperatorPreference) mConnectedNetworkOperatorsPreference.getPreference(0))
+ .refresh();
+ return;
+ }
+
+ /**
+ * Creates a Preference for the given {@link CellInfo} and adds it to the
+ * {@link #mConnectedNetworkOperatorsPreference}.
+ */
+ private void addConnectedNetworkOperatorPreference(CellInfo cellInfo) {
+ if (DBG) logd("addConnectedNetworkOperatorPreference");
+ // Remove the current ConnectedNetworkOperatorsPreference
+ removeConnectedNetworkOperatorPreference();
+ final NetworkOperatorPreference pref =
+ new NetworkOperatorPreference(cellInfo, getContext(), mForbiddenPlmns);
+ pref.setSummary(R.string.network_connected);
+ mConnectedNetworkOperatorsPreference.addPreference(pref);
+ PreferenceScreen preferenceScreen = getPreferenceScreen();
+ preferenceScreen.addPreference(mConnectedNetworkOperatorsPreference);
+ }
+
+ /** Removes all preferences and hide the {@link #mConnectedNetworkOperatorsPreference}. */
+ private void removeConnectedNetworkOperatorPreference() {
+ mConnectedNetworkOperatorsPreference.removeAll();
+ PreferenceScreen preferenceScreen = getPreferenceScreen();
+ preferenceScreen.removePreference(mConnectedNetworkOperatorsPreference);
+ }
+
+ protected void setProgressBarVisible(boolean visible) {
+ if (mProgressHeader != null) {
+ mProgressHeader.setVisibility(visible ? View.VISIBLE : View.GONE);
+ }
+ }
+
+ private void addMessagePreference(int messageId) {
+ if (DBG) logd("remove callback");
+ getView().removeCallbacks(mUpdateNetworkOperatorsRunnable);
+ setProgressBarVisible(false);
+ if (DBG) logd("addMessagePreference");
+ mStatusMessagePreference.setTitle(messageId);
+ removeConnectedNetworkOperatorPreference();
+ mNetworkOperatorsPreferences.removeAll();
+ mNetworkOperatorsPreferences.addPreference(mStatusMessagePreference);
+ }
+
+ /**
+ * The Scan results may contains several cell infos with different radio technologies and signal
+ * strength for one network operator. Aggregate the CellInfoList by retaining only the cell info
+ * with the strongest signal strength.
+ */
+ private List aggregateCellInfoList(List cellInfoList) {
+ if (DBG) logd("before aggregate: " + cellInfoList.toString());
+ Map map = new HashMap<>();
+ for (CellInfo cellInfo : cellInfoList) {
+ String plmn = CellInfoUtil.getOperatorInfoFromCellInfo(cellInfo).getOperatorNumeric();
+ if (cellInfo.isRegistered() || !map.containsKey(plmn)) {
+ map.put(plmn, cellInfo);
+ } else {
+ if (map.get(plmn).isRegistered()
+ || map.get(plmn).getCellSignalStrength().getLevel()
+ > cellInfo.getCellSignalStrength().getLevel()) {
+ // Skip if the stored cellInfo is registered or has higher signal strength level
+ continue;
+ }
+ // Otherwise replace it with the new CellInfo
+ map.put(plmn, cellInfo);
+ }
+ }
+ return new ArrayList<>(map.values());
+ }
+
+ private void loadNetworksList() {
+ if (DBG) logd("load networks list...");
+ setProgressBarVisible(true);
+ //TODO(b/114749736): load network list once b/115401728 is done
+ }
+
+ private void bindNetworkQueryService() {
+ if (DBG) logd("bindNetworkQueryService");
+ //TODO(b/114749736): bind service/manager once b/115401728 is done
+ mShouldUnbind = true;
+ }
+
+ private void unbindNetworkQueryService() {
+ if (DBG) logd("unbindNetworkQueryService");
+ if (mShouldUnbind) {
+ if (DBG) logd("mShouldUnbind is true");
+ // unbind the service.
+ //TODO(b/114749736): unbind service/manager once b/115401728 is done
+ mShouldUnbind = false;
+ }
+ }
+
+ private void updateNetworkSelection() {
+ if (DBG) logd("Update notification about no service of user selected operator");
+ //TODO(b/114749736): update network selection once b/115429509 is done
+ }
+
+ private void stopNetworkQuery() {
+ // Stop the network query process
+ //TODO(b/114749736): stop service/manager query once b/115401728 is done
+ }
+
+ private void logd(String msg) {
+ Log.d(TAG, msg);
+ }
+
+ private void loge(String msg) {
+ Log.e(TAG, msg);
+ }
+
+ public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider() {
+ @Override
+ public List getXmlResourcesToIndex(Context context,
+ boolean enabled) {
+ final ArrayList result = new ArrayList<>();
+
+ final SearchIndexableResource sir = new SearchIndexableResource(context);
+ sir.xmlResId = R.xml.choose_network;
+ result.add(sir);
+ return result;
+ }
+ };
+}