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; 72 public NetworkSelectListPreference(Context context, AttributeSet attrs) { 73 super(context, attrs); 74 } 75 76 public NetworkSelectListPreference(Context context, AttributeSet attrs, int defStyleAttr, 77 int defStyleRes) { 78 super(context, attrs, defStyleAttr, defStyleRes); 79 } 80 81 @Override 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 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 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 */ 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. 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. 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 204 protected void onPrepareForRemoval() { 205 destroy(); 206 super.onPrepareForRemoval(); 207 } 208 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 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 234 private void displayNetworkSelectionInProgress() { 235 showProgressBar(DIALOG_NETWORK_SELECTION); 236 } 237 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 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 */ 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 */ 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 357 private void clearList() { 358 if (mOperatorInfoList != null) { 359 mOperatorInfoList.clear(); 360 } 361 } 362 363 private void dismissProgressBar() { 364 if (mProgressDialog != null && mProgressDialog.isShowing()) { 365 mProgressDialog.dismiss(); 366 } 367 } 368 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 */ 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 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 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 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 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 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 508 private void logd(String msg) { 509 Log.d(LOG_TAG, "[NetworksList] " + msg); 510 } 511 512 private void loge(String msg) { 513 Log.e(LOG_TAG, "[NetworksList] " + msg); 514 } 515 } 516