• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.phone;
18 
19 import android.app.ProgressDialog;
20 import android.content.Context;
21 import android.content.DialogInterface;
22 import android.os.AsyncTask;
23 import android.os.Handler;
24 import android.os.Message;
25 import android.os.Parcel;
26 import android.os.Parcelable;
27 import android.preference.ListPreference;
28 import android.preference.Preference;
29 import android.telephony.CellInfo;
30 import android.telephony.CellInfoCdma;
31 import android.telephony.CellInfoGsm;
32 import android.telephony.CellInfoLte;
33 import android.telephony.CellInfoWcdma;
34 import android.telephony.SubscriptionManager;
35 import android.telephony.TelephonyManager;
36 import android.text.BidiFormatter;
37 import android.text.TextDirectionHeuristics;
38 import android.text.TextUtils;
39 import android.util.AttributeSet;
40 import android.util.Log;
41 import android.widget.Toast;
42 
43 import com.android.internal.logging.MetricsLogger;
44 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
45 import com.android.internal.telephony.OperatorInfo;
46 import com.android.phone.NetworkScanHelper.NetworkScanCallback;
47 import com.android.settingslib.utils.ThreadUtils;
48 
49 import java.util.ArrayList;
50 import java.util.Arrays;
51 import java.util.List;
52 import java.util.concurrent.ExecutorService;
53 import java.util.concurrent.Executors;
54 
55 
56 /**
57  * "Networks" preference in "Mobile network" settings UI for the Phone app.
58  * It's used to manually search and choose mobile network. Enabled only when
59  * autoSelect preference is turned off.
60  */
61 public class NetworkSelectListPreference extends ListPreference
62         implements DialogInterface.OnCancelListener,
63         Preference.OnPreferenceChangeListener{
64 
65     private static final String LOG_TAG = "networkSelect";
66     private static final boolean DBG = true;
67 
68     private static final int EVENT_MANUALLY_NETWORK_SELECTION_DONE = 1;
69     private static final int EVENT_NETWORK_SCAN_RESULTS = 2;
70     private static final int EVENT_NETWORK_SCAN_COMPLETED = 3;
71     private static final int EVENT_NETWORK_SCAN_ERROR = 4;
72 
73     //dialog ids
74     private static final int DIALOG_NETWORK_SELECTION = 100;
75     private static final int DIALOG_NETWORK_LIST_LOAD = 200;
76 
77     private final ExecutorService mNetworkScanExecutor = Executors.newFixedThreadPool(1);
78 
79     private List<CellInfo> mCellInfoList;
80     private CellInfo mCellInfo;
81 
82     private int mSubId;
83     private TelephonyManager mTelephonyManager;
84     private NetworkScanHelper mNetworkScanHelper;
85     private NetworkOperators mNetworkOperators;
86     private List<String> mForbiddenPlmns;
87 
88     private ProgressDialog mProgressDialog;
NetworkSelectListPreference(Context context, AttributeSet attrs)89     public NetworkSelectListPreference(Context context, AttributeSet attrs) {
90         super(context, attrs);
91     }
92 
NetworkSelectListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)93     public NetworkSelectListPreference(Context context, AttributeSet attrs, int defStyleAttr,
94                                        int defStyleRes) {
95         super(context, attrs, defStyleAttr, defStyleRes);
96     }
97 
98     @Override
onClick()99     protected void onClick() {
100         showProgressDialog(DIALOG_NETWORK_LIST_LOAD);
101         TelephonyManager telephonyManager = (TelephonyManager)
102                 getContext().getSystemService(Context.TELEPHONY_SERVICE);
103         new AsyncTask<Void, Void, List<String>>() {
104             @Override
105             protected List<String> doInBackground(Void... voids) {
106                 String[] forbiddenPlmns = telephonyManager.getForbiddenPlmns();
107                 return forbiddenPlmns != null ? Arrays.asList(forbiddenPlmns) : null;
108             }
109 
110             @Override
111             protected void onPostExecute(List<String> result) {
112                 mForbiddenPlmns = result;
113                 loadNetworksList();
114             }
115         }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
116     }
117 
118     private final Handler mHandler = new Handler() {
119         @Override
120         public void handleMessage(Message msg) {
121             switch (msg.what) {
122                 case EVENT_MANUALLY_NETWORK_SELECTION_DONE:
123                     if (DBG) logd("hideProgressPanel");
124                     dismissProgressDialog();
125 
126                     boolean isSuccessed = (boolean) msg.obj;
127                     if (isSuccessed) {
128                         if (DBG) {
129                             logd("manual network selection: succeeded! "
130                                     + getNetworkTitle(mCellInfo));
131                         }
132                         mNetworkOperators.displayNetworkSelectionSucceeded();
133                     } else {
134                         if (DBG) logd("manual network selection: failed!");
135                         mNetworkOperators.displayNetworkSelectionFailed();
136                     }
137                     mNetworkOperators.getNetworkSelectionMode();
138                     break;
139 
140                 case EVENT_NETWORK_SCAN_RESULTS:
141                     List<CellInfo> results = (List<CellInfo>) msg.obj;
142                     results.removeIf(cellInfo -> cellInfo == null);
143                     mCellInfoList = new ArrayList<>(results);
144                     if (DBG) logd("CALLBACK_SCAN_RESULTS" + mCellInfoList.toString());
145                     break;
146 
147                 case EVENT_NETWORK_SCAN_COMPLETED:
148                     if (DBG) logd("scan complete, load the cellInfosList");
149                     dismissProgressDialog();
150                     networksListLoaded();
151                     break;
152                 case EVENT_NETWORK_SCAN_ERROR:
153                     dismissProgressDialog();
154                     displayNetworkQueryFailed();
155                     mNetworkOperators.getNetworkSelectionMode();
156                     break;
157             }
158             return;
159         }
160     };
161 
162     private final NetworkScanHelper.NetworkScanCallback mCallback = new NetworkScanCallback() {
163         public void onResults(List<CellInfo> results) {
164             if (DBG) logd("get scan results: " + results.toString());
165             Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_RESULTS, results);
166             msg.sendToTarget();
167         }
168 
169         public void onComplete() {
170             if (DBG) logd("network scan completed.");
171             Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_COMPLETED);
172             msg.sendToTarget();
173         }
174 
175         public void onError(int error) {
176             if (DBG) logd("network scan error.");
177             Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_ERROR);
178             msg.sendToTarget();
179         }
180     };
181 
182     @Override
183     //implemented for DialogInterface.OnCancelListener
onCancel(DialogInterface dialog)184     public void onCancel(DialogInterface dialog) {
185         if (DBG) logd("user manually close the dialog");
186         mNetworkScanHelper.stopNetworkQuery();
187 
188         // If cancelled, we query NetworkSelectMode and update states of AutoSelect button.
189         mNetworkOperators.getNetworkSelectionMode();
190     }
191 
192     @Override
onDialogClosed(boolean positiveResult)193     protected void onDialogClosed(boolean positiveResult) {
194         super.onDialogClosed(positiveResult);
195         // If dismissed, we query NetworkSelectMode and update states of AutoSelect button.
196         if (!positiveResult) {
197             mNetworkOperators.getNetworkSelectionMode();
198         }
199     }
200 
201     // This initialize method needs to be called for this preference to work properly.
initialize(int subId, NetworkOperators networkOperators, ProgressDialog progressDialog)202     protected void initialize(int subId, NetworkOperators networkOperators,
203             ProgressDialog progressDialog) {
204         mSubId = subId;
205         mNetworkOperators = networkOperators;
206         // This preference should share the same progressDialog with networkOperators category.
207         mProgressDialog = progressDialog;
208 
209         mTelephonyManager = TelephonyManager.from(getContext()).createForSubscriptionId(mSubId);
210         mNetworkScanHelper = new NetworkScanHelper(
211                 mTelephonyManager, mCallback, mNetworkScanExecutor);
212 
213         setSummary(mTelephonyManager.getNetworkOperatorName());
214 
215         setOnPreferenceChangeListener(this);
216     }
217 
218     @Override
onPrepareForRemoval()219     protected void onPrepareForRemoval() {
220         destroy();
221         super.onPrepareForRemoval();
222     }
223 
destroy()224     private void destroy() {
225         dismissProgressDialog();
226 
227         if (mNetworkScanHelper != null) {
228             mNetworkScanHelper.stopNetworkQuery();
229         }
230 
231         mNetworkScanExecutor.shutdown();
232     }
233 
displayEmptyNetworkList()234     private void displayEmptyNetworkList() {
235         Toast.makeText(getContext(), R.string.empty_networks_list, Toast.LENGTH_LONG).show();
236     }
237 
displayNetworkQueryFailed()238     private void displayNetworkQueryFailed() {
239         Toast.makeText(getContext(), R.string.network_query_error, Toast.LENGTH_LONG).show();
240     }
241 
loadNetworksList()242     private void loadNetworksList() {
243         if (DBG) logd("load networks list...");
244         mNetworkScanHelper.startNetworkScan(
245                 NetworkScanHelper.NETWORK_SCAN_TYPE_WAIT_FOR_ALL_RESULTS);
246     }
247 
networksListLoaded()248     private void networksListLoaded() {
249         if (DBG) logd("networks list loaded");
250 
251         mNetworkOperators.getNetworkSelectionMode();
252         if (mCellInfoList != null) {
253             // create a preference for each item in the list.
254             // just use the operator name instead of the mildly
255             // confusing mcc/mnc.
256             List<CharSequence> networkEntriesList = new ArrayList<>();
257             List<CharSequence> networkEntryValuesList = new ArrayList<>();
258             for (CellInfo cellInfo: mCellInfoList) {
259                 // Display each operator name only once.
260                 String networkTitle = getNetworkTitle(cellInfo);
261                 if (CellInfoUtil.isForbidden(cellInfo, mForbiddenPlmns)) {
262                     networkTitle += " "
263                             + getContext().getResources().getString(R.string.forbidden_network);
264                 }
265                 networkEntriesList.add(networkTitle);
266                 networkEntryValuesList.add(getOperatorNumeric(cellInfo));
267             }
268             setEntries(networkEntriesList.toArray(new CharSequence[networkEntriesList.size()]));
269             setEntryValues(networkEntryValuesList.toArray(
270                     new CharSequence[networkEntryValuesList.size()]));
271 
272             super.onClick();
273         } else {
274             displayEmptyNetworkList();
275         }
276     }
277 
dismissProgressDialog()278     private void dismissProgressDialog() {
279         if (mProgressDialog != null && mProgressDialog.isShowing()) {
280             try {
281                 mProgressDialog.dismiss();
282             } catch (IllegalArgumentException ex) {
283                 loge("Can't close the progress dialog " + ex);
284             }
285         }
286     }
287 
showProgressDialog(int id)288     private void showProgressDialog(int id) {
289         if (mProgressDialog == null) {
290             mProgressDialog = new ProgressDialog(getContext());
291         } else {
292             // Dismiss progress bar if it's showing now.
293             dismissProgressDialog();
294         }
295 
296         switch (id) {
297             case DIALOG_NETWORK_SELECTION:
298                 final String networkSelectMsg = getContext().getResources()
299                         .getString(R.string.register_on_network,
300                                 getNetworkTitle(mCellInfo));
301                 mProgressDialog.setMessage(networkSelectMsg);
302                 mProgressDialog.setCanceledOnTouchOutside(false);
303                 mProgressDialog.setCancelable(false);
304                 mProgressDialog.setIndeterminate(true);
305                 break;
306             case DIALOG_NETWORK_LIST_LOAD:
307                 mProgressDialog.setMessage(
308                         getContext().getResources().getString(R.string.load_networks_progress));
309                 mProgressDialog.setCanceledOnTouchOutside(false);
310                 mProgressDialog.setCancelable(true);
311                 mProgressDialog.setIndeterminate(false);
312                 mProgressDialog.setOnCancelListener(this);
313                 break;
314             default:
315         }
316         mProgressDialog.show();
317     }
318 
319     /**
320      * Implemented to support onPreferenceChangeListener to look for preference
321      * changes specifically on this button.
322      *
323      * @param preference is the preference to be changed, should be network select button.
324      * @param newValue should be the value of the selection as index of operators.
325      */
326     @Override
onPreferenceChange(Preference preference, Object newValue)327     public boolean onPreferenceChange(Preference preference, Object newValue) {
328         int operatorIndex = findIndexOfValue((String) newValue);
329         mCellInfo = mCellInfoList.get(operatorIndex);
330         if (DBG) logd("selected network: " + mCellInfo.toString());
331 
332         MetricsLogger.action(getContext(),
333                 MetricsEvent.ACTION_MOBILE_NETWORK_MANUAL_SELECT_NETWORK);
334 
335         if (SubscriptionManager.isValidSubscriptionId(mSubId)) {
336             ThreadUtils.postOnBackgroundThread(() -> {
337                 final OperatorInfo operatorInfo = getOperatorInfoFromCellInfo(mCellInfo);
338                 if (DBG) logd("manually selected network: " + operatorInfo.toString());
339                 boolean isSuccessed = mTelephonyManager.setNetworkSelectionModeManual(
340                         operatorInfo, true /* persistSelection */);
341                 Message msg = mHandler.obtainMessage(EVENT_MANUALLY_NETWORK_SELECTION_DONE);
342                 msg.obj = isSuccessed;
343                 msg.sendToTarget();
344             });
345         } else {
346             loge("Error selecting network, subscription Id is invalid " + mSubId);
347         }
348 
349         return true;
350     }
351 
352     /**
353      * Returns the title of the network obtained in the manual search.
354      *
355      * @param cellInfo contains the information of the network.
356      * @return Long Name if not null/empty, otherwise Short Name if not null/empty,
357      * else MCCMNC string.
358      */
getNetworkTitle(CellInfo cellInfo)359     private String getNetworkTitle(CellInfo cellInfo) {
360         OperatorInfo ni = getOperatorInfoFromCellInfo(cellInfo);
361 
362         if (!TextUtils.isEmpty(ni.getOperatorAlphaLong())) {
363             return ni.getOperatorAlphaLong();
364         } else if (!TextUtils.isEmpty(ni.getOperatorAlphaShort())) {
365             return ni.getOperatorAlphaShort();
366         } else {
367             BidiFormatter bidiFormatter = BidiFormatter.getInstance();
368             return bidiFormatter.unicodeWrap(ni.getOperatorNumeric(), TextDirectionHeuristics.LTR);
369         }
370     }
371 
372     /**
373      * Returns the operator numeric (MCCMNC) obtained in the manual search.
374      *
375      * @param cellInfo contains the information of the network.
376      * @return MCCMNC string.
377      */
getOperatorNumeric(CellInfo cellInfo)378     private String getOperatorNumeric(CellInfo cellInfo) {
379         return getOperatorInfoFromCellInfo(cellInfo).getOperatorNumeric();
380     }
381 
382     /**
383      * Wrap a cell info into an operator info.
384      */
getOperatorInfoFromCellInfo(CellInfo cellInfo)385     private OperatorInfo getOperatorInfoFromCellInfo(CellInfo cellInfo) {
386         OperatorInfo oi;
387         if (cellInfo instanceof CellInfoLte) {
388             CellInfoLte lte = (CellInfoLte) cellInfo;
389             oi = new OperatorInfo(
390                     (String) lte.getCellIdentity().getOperatorAlphaLong(),
391                     (String) lte.getCellIdentity().getOperatorAlphaShort(),
392                     lte.getCellIdentity().getMobileNetworkOperator());
393         } else if (cellInfo instanceof CellInfoWcdma) {
394             CellInfoWcdma wcdma = (CellInfoWcdma) cellInfo;
395             oi = new OperatorInfo(
396                     (String) wcdma.getCellIdentity().getOperatorAlphaLong(),
397                     (String) wcdma.getCellIdentity().getOperatorAlphaShort(),
398                     wcdma.getCellIdentity().getMobileNetworkOperator());
399         } else if (cellInfo instanceof CellInfoGsm) {
400             CellInfoGsm gsm = (CellInfoGsm) cellInfo;
401             oi = new OperatorInfo(
402                     (String) gsm.getCellIdentity().getOperatorAlphaLong(),
403                     (String) gsm.getCellIdentity().getOperatorAlphaShort(),
404                     gsm.getCellIdentity().getMobileNetworkOperator());
405         } else if (cellInfo instanceof CellInfoCdma) {
406             CellInfoCdma cdma = (CellInfoCdma) cellInfo;
407             oi = new OperatorInfo(
408                     (String) cdma.getCellIdentity().getOperatorAlphaLong(),
409                     (String) cdma.getCellIdentity().getOperatorAlphaShort(),
410                     "" /* operator numeric */);
411         } else {
412             oi = new OperatorInfo("", "", "");
413         }
414         return oi;
415     }
416 
417     @Override
onSaveInstanceState()418     protected Parcelable onSaveInstanceState() {
419         final Parcelable superState = super.onSaveInstanceState();
420         if (isPersistent()) {
421             // No need to save instance state since it's persistent
422             return superState;
423         }
424 
425         final SavedState myState = new SavedState(superState);
426         myState.mDialogListEntries = getEntries();
427         myState.mDialogListEntryValues = getEntryValues();
428         myState.mCellInfoList = mCellInfoList;
429         return myState;
430     }
431 
432     @Override
onRestoreInstanceState(Parcelable state)433     protected void onRestoreInstanceState(Parcelable state) {
434         if (state == null || !state.getClass().equals(SavedState.class)) {
435             // Didn't save state for us in onSaveInstanceState
436             super.onRestoreInstanceState(state);
437             return;
438         }
439 
440         SavedState myState = (SavedState) state;
441 
442         if (getEntries() == null && myState.mDialogListEntries != null) {
443             setEntries(myState.mDialogListEntries);
444         }
445         if (getEntryValues() == null && myState.mDialogListEntryValues != null) {
446             setEntryValues(myState.mDialogListEntryValues);
447         }
448         if (mCellInfoList == null && myState.mCellInfoList != null) {
449             mCellInfoList = myState.mCellInfoList;
450         }
451 
452         super.onRestoreInstanceState(myState.getSuperState());
453     }
454 
455     /**
456      *  We save entries, entryValues and operatorInfoList into bundle.
457      *  At onCreate of fragment, dialog will be restored if it was open. In this case,
458      *  we need to restore entries, entryValues and operatorInfoList. Without those information,
459      *  onPreferenceChange will fail if user select network from the dialog.
460      */
461     private static class SavedState extends BaseSavedState {
462         CharSequence[] mDialogListEntries;
463         CharSequence[] mDialogListEntryValues;
464         List<CellInfo> mCellInfoList;
465 
SavedState(Parcel source)466         SavedState(Parcel source) {
467             super(source);
468             final ClassLoader boot = Object.class.getClassLoader();
469             mDialogListEntries = source.readCharSequenceArray();
470             mDialogListEntryValues = source.readCharSequenceArray();
471             mCellInfoList = source.readParcelableList(mCellInfoList, boot);
472         }
473 
474         @Override
writeToParcel(Parcel dest, int flags)475         public void writeToParcel(Parcel dest, int flags) {
476             super.writeToParcel(dest, flags);
477             dest.writeCharSequenceArray(mDialogListEntries);
478             dest.writeCharSequenceArray(mDialogListEntryValues);
479             dest.writeParcelableList(mCellInfoList, flags);
480         }
481 
SavedState(Parcelable superState)482         SavedState(Parcelable superState) {
483             super(superState);
484         }
485 
486         public static final Parcelable.Creator<SavedState> CREATOR =
487                 new Parcelable.Creator<SavedState>() {
488                     public SavedState createFromParcel(Parcel in) {
489                         return new SavedState(in);
490                     }
491 
492                     public SavedState[] newArray(int size) {
493                         return new SavedState[size];
494                     }
495                 };
496     }
497 
logd(String msg)498     private void logd(String msg) {
499         Log.d(LOG_TAG, "[NetworksList] " + msg);
500     }
501 
loge(String msg)502     private void loge(String msg) {
503         Log.e(LOG_TAG, "[NetworksList] " + msg);
504     }
505 }
506