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.AsyncResult; 23 import android.os.Handler; 24 import android.os.Message; 25 import android.os.Parcel; 26 import android.os.Parcelable; 27 import android.os.RemoteException; 28 import android.preference.ListPreference; 29 import android.preference.Preference; 30 import android.telephony.SubscriptionManager; 31 import android.telephony.TelephonyManager; 32 import android.text.BidiFormatter; 33 import android.text.TextDirectionHeuristics; 34 import android.text.TextUtils; 35 import android.util.AttributeSet; 36 import android.util.Log; 37 38 import com.android.internal.telephony.OperatorInfo; 39 import com.android.internal.telephony.Phone; 40 import com.android.internal.telephony.PhoneFactory; 41 42 import java.util.List; 43 44 45 /** 46 * "Networks" preference in "Mobile network" settings UI for the Phone app. 47 * It's used to manually search and choose mobile network. Enabled only when 48 * autoSelect preference is turned off. 49 */ 50 public class NetworkSelectListPreference extends ListPreference 51 implements DialogInterface.OnCancelListener, 52 Preference.OnPreferenceChangeListener{ 53 54 private static final String LOG_TAG = "networkSelect"; 55 private static final boolean DBG = true; 56 57 private static final int EVENT_NETWORK_SCAN_COMPLETED = 100; 58 private static final int EVENT_NETWORK_SELECTION_DONE = 200; 59 60 //dialog ids 61 private static final int DIALOG_NETWORK_SELECTION = 100; 62 private static final int DIALOG_NETWORK_LIST_LOAD = 200; 63 64 private int mPhoneId = SubscriptionManager.INVALID_PHONE_INDEX; 65 private List<OperatorInfo> mOperatorInfoList; 66 private OperatorInfo mOperatorInfo; 67 68 private int mSubId; 69 private NetworkOperators mNetworkOperators; 70 71 private ProgressDialog mProgressDialog; NetworkSelectListPreference(Context context, AttributeSet attrs)72 public NetworkSelectListPreference(Context context, AttributeSet attrs) { 73 super(context, attrs); 74 } 75 NetworkSelectListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)76 public NetworkSelectListPreference(Context context, AttributeSet attrs, int defStyleAttr, 77 int defStyleRes) { 78 super(context, attrs, defStyleAttr, defStyleRes); 79 } 80 81 @Override onClick()82 protected void onClick() { 83 loadNetworksList(); 84 } 85 86 private final Handler mHandler = new Handler() { 87 @Override 88 public void handleMessage(Message msg) { 89 AsyncResult ar; 90 switch (msg.what) { 91 case EVENT_NETWORK_SCAN_COMPLETED: 92 networksListLoaded((List<OperatorInfo>) msg.obj, msg.arg1); 93 break; 94 95 case EVENT_NETWORK_SELECTION_DONE: 96 if (DBG) logd("hideProgressPanel"); 97 try { 98 dismissProgressBar(); 99 } catch (IllegalArgumentException e) { 100 } 101 setEnabled(true); 102 103 ar = (AsyncResult) msg.obj; 104 if (ar.exception != null) { 105 if (DBG) logd("manual network selection: failed!"); 106 mNetworkOperators.displayNetworkSelectionFailed(ar.exception); 107 } else { 108 if (DBG) { 109 logd("manual network selection: succeeded!" 110 + getNetworkTitle(mOperatorInfo)); 111 } 112 mNetworkOperators.displayNetworkSelectionSucceeded(); 113 } 114 mNetworkOperators.getNetworkSelectionMode(); 115 break; 116 } 117 118 return; 119 } 120 }; 121 122 INetworkQueryService mNetworkQueryService = null; 123 /** 124 * This implementation of INetworkQueryServiceCallback is used to receive 125 * callback notifications from the network query service. 126 */ 127 private final INetworkQueryServiceCallback mCallback = new INetworkQueryServiceCallback.Stub() { 128 129 /** place the message on the looper queue upon query completion. */ 130 public void onQueryComplete(List<OperatorInfo> networkInfoArray, int status) { 131 if (DBG) logd("notifying message loop of query completion."); 132 Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_COMPLETED, 133 status, 0, networkInfoArray); 134 msg.sendToTarget(); 135 } 136 }; 137 138 @Override 139 //implemented for DialogInterface.OnCancelListener onCancel(DialogInterface dialog)140 public void onCancel(DialogInterface dialog) { 141 // request that the service stop the query with this callback object. 142 try { 143 if (mNetworkQueryService != null) { 144 mNetworkQueryService.stopNetworkQuery(mCallback); 145 } 146 // If cancelled, we query NetworkSelectMode and update states of AutoSelect button. 147 mNetworkOperators.getNetworkSelectionMode(); 148 } catch (RemoteException e) { 149 loge("onCancel: exception from stopNetworkQuery " + e); 150 } 151 } 152 153 @Override onDialogClosed(boolean positiveResult)154 protected void onDialogClosed(boolean positiveResult) { 155 super.onDialogClosed(positiveResult); 156 157 // If dismissed, we query NetworkSelectMode and update states of AutoSelect button. 158 if (!positiveResult) { 159 mNetworkOperators.getNetworkSelectionMode(); 160 } 161 } 162 163 /** 164 * Return normalized carrier name given network info. 165 * 166 * @param ni is network information in OperatorInfo type. 167 */ getNormalizedCarrierName(OperatorInfo ni)168 public String getNormalizedCarrierName(OperatorInfo ni) { 169 if (ni != null) { 170 return ni.getOperatorAlphaLong() + " (" + ni.getOperatorNumeric() + ")"; 171 } 172 return null; 173 } 174 175 // This method is provided besides initialize() because bind to network query service 176 // may be binded after initialize(). In that case this method needs to be called explicitly 177 // to set mNetworkQueryService. Otherwise mNetworkQueryService will remain null. setNetworkQueryService(INetworkQueryService queryService)178 public void setNetworkQueryService(INetworkQueryService queryService) { 179 mNetworkQueryService = queryService; 180 } 181 182 // This initialize method needs to be called for this preference to work properly. initialize(int subId, INetworkQueryService queryService, NetworkOperators networkOperators, ProgressDialog progressDialog)183 protected void initialize(int subId, INetworkQueryService queryService, 184 NetworkOperators networkOperators, ProgressDialog progressDialog) { 185 mSubId = subId; 186 mNetworkQueryService = queryService; 187 mNetworkOperators = networkOperators; 188 // This preference should share the same progressDialog with networkOperators category. 189 mProgressDialog = progressDialog; 190 191 if (SubscriptionManager.isValidSubscriptionId(mSubId)) { 192 mPhoneId = SubscriptionManager.getPhoneId(mSubId); 193 } 194 195 TelephonyManager telephonyManager = (TelephonyManager) 196 getContext().getSystemService(Context.TELEPHONY_SERVICE); 197 198 setSummary(telephonyManager.getNetworkOperatorName()); 199 200 setOnPreferenceChangeListener(this); 201 } 202 203 @Override onPrepareForRemoval()204 protected void onPrepareForRemoval() { 205 destroy(); 206 super.onPrepareForRemoval(); 207 } 208 destroy()209 private void destroy() { 210 try { 211 dismissProgressBar(); 212 } catch (IllegalArgumentException e) { 213 loge("onDestroy: exception from dismissProgressBar " + e); 214 } 215 216 try { 217 if (mNetworkQueryService != null) { 218 // used to un-register callback 219 mNetworkQueryService.unregisterCallback(mCallback); 220 } 221 } catch (RemoteException e) { 222 loge("onDestroy: exception from unregisterCallback " + e); 223 } 224 } 225 displayEmptyNetworkList()226 private void displayEmptyNetworkList() { 227 String status = getContext().getResources().getString(R.string.empty_networks_list); 228 229 final PhoneGlobals app = PhoneGlobals.getInstance(); 230 app.notificationMgr.postTransientNotification( 231 NotificationMgr.NETWORK_SELECTION_NOTIFICATION, status); 232 } 233 displayNetworkSelectionInProgress()234 private void displayNetworkSelectionInProgress() { 235 showProgressBar(DIALOG_NETWORK_SELECTION); 236 } 237 displayNetworkQueryFailed(int error)238 private void displayNetworkQueryFailed(int error) { 239 String status = getContext().getResources().getString(R.string.network_query_error); 240 241 try { 242 dismissProgressBar(); 243 } catch (IllegalArgumentException e1) { 244 // do nothing 245 } 246 247 final PhoneGlobals app = PhoneGlobals.getInstance(); 248 app.notificationMgr.postTransientNotification( 249 NotificationMgr.NETWORK_SELECTION_NOTIFICATION, status); 250 } 251 loadNetworksList()252 private void loadNetworksList() { 253 if (DBG) logd("load networks list..."); 254 255 showProgressBar(DIALOG_NETWORK_LIST_LOAD); 256 257 // delegate query request to the service. 258 try { 259 if (mNetworkQueryService != null) { 260 mNetworkQueryService.startNetworkQuery(mCallback, mPhoneId); 261 } else { 262 displayNetworkQueryFailed(NetworkQueryService.QUERY_EXCEPTION); 263 } 264 } catch (RemoteException e) { 265 loge("loadNetworksList: exception from startNetworkQuery " + e); 266 displayNetworkQueryFailed(NetworkQueryService.QUERY_EXCEPTION); 267 } 268 } 269 270 /** 271 * networksListLoaded has been rewritten to take an array of 272 * OperatorInfo objects and a status field, instead of an 273 * AsyncResult. Otherwise, the functionality which takes the 274 * OperatorInfo array and creates a list of preferences from it, 275 * remains unchanged. 276 */ networksListLoaded(List<OperatorInfo> result, int status)277 private void networksListLoaded(List<OperatorInfo> result, int status) { 278 if (DBG) logd("networks list loaded"); 279 280 // used to un-register callback 281 try { 282 if (mNetworkQueryService != null) { 283 mNetworkQueryService.unregisterCallback(mCallback); 284 } 285 } catch (RemoteException e) { 286 loge("networksListLoaded: exception from unregisterCallback " + e); 287 } 288 289 // update the state of the preferences. 290 if (DBG) logd("hideProgressPanel"); 291 292 // Always try to dismiss the dialog because activity may 293 // be moved to background after dialog is shown. 294 try { 295 dismissProgressBar(); 296 } catch (IllegalArgumentException e) { 297 // It's not a error in following scenario, we just ignore it. 298 // "Load list" dialog will not show, if NetworkQueryService is 299 // connected after this activity is moved to background. 300 loge("Fail to dismiss network load list dialog " + e); 301 } 302 303 setEnabled(true); 304 clearList(); 305 306 if (status != NetworkQueryService.QUERY_OK) { 307 if (DBG) logd("error while querying available networks"); 308 displayNetworkQueryFailed(status); 309 } else { 310 if (result != null) { 311 // create a preference for each item in the list. 312 // just use the operator name instead of the mildly 313 // confusing mcc/mnc. 314 mOperatorInfoList = result; 315 CharSequence[] networkEntries = new CharSequence[result.size()]; 316 CharSequence[] networkEntryValues = new CharSequence[result.size()]; 317 for (int i = 0; i < mOperatorInfoList.size(); i++) { 318 if (mOperatorInfoList.get(i).getState() == OperatorInfo.State.FORBIDDEN) { 319 networkEntries[i] = getNetworkTitle(mOperatorInfoList.get(i)) 320 + " " 321 + getContext().getResources().getString(R.string.forbidden_network); 322 } else { 323 networkEntries[i] = getNetworkTitle(mOperatorInfoList.get(i)); 324 } 325 networkEntryValues[i] = Integer.toString(i + 2); 326 } 327 328 setEntries(networkEntries); 329 setEntryValues(networkEntryValues); 330 331 super.onClick(); 332 } else { 333 displayEmptyNetworkList(); 334 } 335 } 336 } 337 338 /** 339 * Returns the title of the network obtained in the manual search. 340 * 341 * @param ni contains the information of the network. 342 * 343 * @return Long Name if not null/empty, otherwise Short Name if not null/empty, 344 * else MCCMNC string. 345 */ getNetworkTitle(OperatorInfo ni)346 private String getNetworkTitle(OperatorInfo ni) { 347 if (!TextUtils.isEmpty(ni.getOperatorAlphaLong())) { 348 return ni.getOperatorAlphaLong(); 349 } else if (!TextUtils.isEmpty(ni.getOperatorAlphaShort())) { 350 return ni.getOperatorAlphaShort(); 351 } else { 352 BidiFormatter bidiFormatter = BidiFormatter.getInstance(); 353 return bidiFormatter.unicodeWrap(ni.getOperatorNumeric(), TextDirectionHeuristics.LTR); 354 } 355 } 356 clearList()357 private void clearList() { 358 if (mOperatorInfoList != null) { 359 mOperatorInfoList.clear(); 360 } 361 } 362 dismissProgressBar()363 private void dismissProgressBar() { 364 if (mProgressDialog != null && mProgressDialog.isShowing()) { 365 mProgressDialog.dismiss(); 366 } 367 } 368 showProgressBar(int id)369 private void showProgressBar(int id) { 370 if (mProgressDialog == null) { 371 mProgressDialog = new ProgressDialog(getContext()); 372 } else { 373 // Dismiss progress bar if it's showing now. 374 dismissProgressBar(); 375 } 376 377 if ((id == DIALOG_NETWORK_SELECTION) || (id == DIALOG_NETWORK_LIST_LOAD)) { 378 switch (id) { 379 case DIALOG_NETWORK_SELECTION: 380 final String networkSelectMsg = getContext().getResources() 381 .getString(R.string.register_on_network, 382 getNetworkTitle(mOperatorInfo)); 383 mProgressDialog.setMessage(networkSelectMsg); 384 mProgressDialog.setCanceledOnTouchOutside(false); 385 mProgressDialog.setCancelable(false); 386 mProgressDialog.setIndeterminate(true); 387 break; 388 case DIALOG_NETWORK_LIST_LOAD: 389 mProgressDialog.setMessage( 390 getContext().getResources().getString(R.string.load_networks_progress)); 391 mProgressDialog.setCanceledOnTouchOutside(false); 392 mProgressDialog.setCancelable(true); 393 mProgressDialog.setIndeterminate(false); 394 mProgressDialog.setOnCancelListener(this); 395 break; 396 default: 397 } 398 mProgressDialog.show(); 399 } 400 } 401 402 /** 403 * Implemented to support onPreferenceChangeListener to look for preference 404 * changes specifically on this button. 405 * 406 * @param preference is the preference to be changed, should be network select button. 407 * @param newValue should be the value of the selection as index of operators. 408 */ onPreferenceChange(Preference preference, Object newValue)409 public boolean onPreferenceChange(Preference preference, Object newValue) { 410 int operatorIndex = findIndexOfValue((String) newValue); 411 mOperatorInfo = mOperatorInfoList.get(operatorIndex); 412 413 if (DBG) logd("selected network: " + getNetworkTitle(mOperatorInfo)); 414 415 Message msg = mHandler.obtainMessage(EVENT_NETWORK_SELECTION_DONE); 416 Phone phone = PhoneFactory.getPhone(mPhoneId); 417 if (phone != null) { 418 phone.selectNetworkManually(mOperatorInfo, true, msg); 419 displayNetworkSelectionInProgress(); 420 } else { 421 loge("Error selecting network. phone is null."); 422 } 423 424 return true; 425 } 426 427 @Override onSaveInstanceState()428 protected Parcelable onSaveInstanceState() { 429 final Parcelable superState = super.onSaveInstanceState(); 430 if (isPersistent()) { 431 // No need to save instance state since it's persistent 432 return superState; 433 } 434 435 final SavedState myState = new SavedState(superState); 436 myState.mDialogListEntries = getEntries(); 437 myState.mDialogListEntryValues = getEntryValues(); 438 myState.mOperatorInfoList = mOperatorInfoList; 439 return myState; 440 } 441 442 @Override onRestoreInstanceState(Parcelable state)443 protected void onRestoreInstanceState(Parcelable state) { 444 if (state == null || !state.getClass().equals(SavedState.class)) { 445 // Didn't save state for us in onSaveInstanceState 446 super.onRestoreInstanceState(state); 447 return; 448 } 449 450 SavedState myState = (SavedState) state; 451 452 if (getEntries() == null && myState.mDialogListEntries != null) { 453 setEntries(myState.mDialogListEntries); 454 } 455 if (getEntryValues() == null && myState.mDialogListEntryValues != null) { 456 setEntryValues(myState.mDialogListEntryValues); 457 } 458 if (mOperatorInfoList == null && myState.mOperatorInfoList != null) { 459 mOperatorInfoList = myState.mOperatorInfoList; 460 } 461 462 super.onRestoreInstanceState(myState.getSuperState()); 463 } 464 465 /** 466 * We save entries, entryValues and operatorInfoList into bundle. 467 * At onCreate of fragment, dialog will be restored if it was open. In this case, 468 * we need to restore entries, entryValues and operatorInfoList. Without those information, 469 * onPreferenceChange will fail if user select network from the dialog. 470 */ 471 private static class SavedState extends BaseSavedState { 472 CharSequence[] mDialogListEntries; 473 CharSequence[] mDialogListEntryValues; 474 List<OperatorInfo> mOperatorInfoList; 475 SavedState(Parcel source)476 SavedState(Parcel source) { 477 super(source); 478 final ClassLoader boot = Object.class.getClassLoader(); 479 mDialogListEntries = source.readCharSequenceArray(); 480 mDialogListEntryValues = source.readCharSequenceArray(); 481 mOperatorInfoList = source.readParcelableList(mOperatorInfoList, boot); 482 } 483 484 @Override writeToParcel(Parcel dest, int flags)485 public void writeToParcel(Parcel dest, int flags) { 486 super.writeToParcel(dest, flags); 487 dest.writeCharSequenceArray(mDialogListEntries); 488 dest.writeCharSequenceArray(mDialogListEntryValues); 489 dest.writeParcelableList(mOperatorInfoList, flags); 490 } 491 SavedState(Parcelable superState)492 SavedState(Parcelable superState) { 493 super(superState); 494 } 495 496 public static final Parcelable.Creator<SavedState> CREATOR = 497 new Parcelable.Creator<SavedState>() { 498 public SavedState createFromParcel(Parcel in) { 499 return new SavedState(in); 500 } 501 502 public SavedState[] newArray(int size) { 503 return new SavedState[size]; 504 } 505 }; 506 } 507 logd(String msg)508 private void logd(String msg) { 509 Log.d(LOG_TAG, "[NetworksList] " + msg); 510 } 511 loge(String msg)512 private void loge(String msg) { 513 Log.e(LOG_TAG, "[NetworksList] " + msg); 514 } 515 } 516