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