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.Dialog; 20 import android.app.ProgressDialog; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.DialogInterface; 24 import android.content.Intent; 25 import android.content.ServiceConnection; 26 import android.os.AsyncResult; 27 import android.os.Bundle; 28 import android.os.Handler; 29 import android.os.IBinder; 30 import android.os.Message; 31 import android.os.RemoteException; 32 import android.os.UserManager; 33 import android.preference.Preference; 34 import android.preference.PreferenceActivity; 35 import android.preference.PreferenceGroup; 36 import android.preference.PreferenceScreen; 37 import android.telephony.ServiceState; 38 import android.telephony.TelephonyManager; 39 import android.text.TextUtils; 40 import android.util.Log; 41 import android.telephony.SubscriptionManager; 42 43 import com.android.internal.telephony.CommandException; 44 import com.android.internal.telephony.Phone; 45 import com.android.internal.telephony.PhoneFactory; 46 import com.android.internal.telephony.OperatorInfo; 47 48 import java.util.HashMap; 49 import java.util.List; 50 import android.text.BidiFormatter; 51 import android.text.TextDirectionHeuristics; 52 53 /** 54 * "Networks" settings UI for the Phone app. 55 */ 56 public class NetworkSetting extends PreferenceActivity 57 implements DialogInterface.OnCancelListener { 58 59 private static final String LOG_TAG = "phone"; 60 private static final boolean DBG = true; 61 62 private static final int EVENT_NETWORK_SCAN_COMPLETED = 100; 63 private static final int EVENT_NETWORK_SELECTION_DONE = 200; 64 private static final int EVENT_AUTO_SELECT_DONE = 300; 65 66 //dialog ids 67 private static final int DIALOG_NETWORK_SELECTION = 100; 68 private static final int DIALOG_NETWORK_LIST_LOAD = 200; 69 private static final int DIALOG_NETWORK_AUTO_SELECT = 300; 70 71 //String keys for preference lookup 72 private static final String LIST_NETWORKS_KEY = "list_networks_key"; 73 private static final String BUTTON_SRCH_NETWRKS_KEY = "button_srch_netwrks_key"; 74 private static final String BUTTON_AUTO_SELECT_KEY = "button_auto_select_key"; 75 76 //map of network controls to the network data. 77 private HashMap<Preference, OperatorInfo> mNetworkMap; 78 79 int mPhoneId = SubscriptionManager.INVALID_PHONE_INDEX; 80 protected boolean mIsForeground = false; 81 82 private UserManager mUm; 83 private boolean mUnavailable; 84 85 /** message for network selection */ 86 String mNetworkSelectMsg; 87 88 //preference objects 89 private PreferenceGroup mNetworkList; 90 private Preference mSearchButton; 91 private Preference mAutoSelect; 92 93 private final Handler mHandler = new Handler() { 94 @Override 95 public void handleMessage(Message msg) { 96 AsyncResult ar; 97 switch (msg.what) { 98 case EVENT_NETWORK_SCAN_COMPLETED: 99 networksListLoaded ((List<OperatorInfo>) msg.obj, msg.arg1); 100 break; 101 102 case EVENT_NETWORK_SELECTION_DONE: 103 if (DBG) log("hideProgressPanel"); 104 removeDialog(DIALOG_NETWORK_SELECTION); 105 getPreferenceScreen().setEnabled(true); 106 107 ar = (AsyncResult) msg.obj; 108 if (ar.exception != null) { 109 if (DBG) log("manual network selection: failed!"); 110 displayNetworkSelectionFailed(ar.exception); 111 } else { 112 if (DBG) log("manual network selection: succeeded!"); 113 displayNetworkSelectionSucceeded(); 114 } 115 116 break; 117 case EVENT_AUTO_SELECT_DONE: 118 if (DBG) log("hideProgressPanel"); 119 120 // Always try to dismiss the dialog because activity may 121 // be moved to background after dialog is shown. 122 try { 123 dismissDialog(DIALOG_NETWORK_AUTO_SELECT); 124 } catch (IllegalArgumentException e) { 125 // "auto select" is always trigged in foreground, so "auto select" dialog 126 // should be shown when "auto select" is trigged. Should NOT get 127 // this exception, and Log it. 128 Log.w(LOG_TAG, "[NetworksList] Fail to dismiss auto select dialog ", e); 129 } 130 getPreferenceScreen().setEnabled(true); 131 132 ar = (AsyncResult) msg.obj; 133 if (ar.exception != null) { 134 if (DBG) log("automatic network selection: failed!"); 135 displayNetworkSelectionFailed(ar.exception); 136 } else { 137 if (DBG) log("automatic network selection: succeeded!"); 138 displayNetworkSelectionSucceeded(); 139 } 140 141 break; 142 } 143 144 return; 145 } 146 }; 147 148 /** 149 * Service connection code for the NetworkQueryService. 150 * Handles the work of binding to a local object so that we can make 151 * the appropriate service calls. 152 */ 153 154 /** Local service interface */ 155 private INetworkQueryService mNetworkQueryService = null; 156 157 /** Service connection */ 158 private final ServiceConnection mNetworkQueryServiceConnection = new ServiceConnection() { 159 160 /** Handle the task of binding the local object to the service */ 161 public void onServiceConnected(ComponentName className, IBinder service) { 162 if (DBG) log("connection created, binding local service."); 163 mNetworkQueryService = ((NetworkQueryService.LocalBinder) service).getService(); 164 } 165 166 /** Handle the task of cleaning up the local binding */ 167 public void onServiceDisconnected(ComponentName className) { 168 if (DBG) log("connection disconnected, cleaning local binding."); 169 mNetworkQueryService = null; 170 } 171 }; 172 173 /** 174 * This implementation of INetworkQueryServiceCallback is used to receive 175 * callback notifications from the network query service. 176 */ 177 private final INetworkQueryServiceCallback mCallback = new INetworkQueryServiceCallback.Stub() { 178 179 /** place the message on the looper queue upon query completion. */ 180 public void onQueryComplete(List<OperatorInfo> networkInfoArray, int status) { 181 if (DBG) log("notifying message loop of query completion."); 182 Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_COMPLETED, 183 status, 0, networkInfoArray); 184 msg.sendToTarget(); 185 } 186 }; 187 188 @Override onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference)189 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { 190 boolean handled = false; 191 192 if (preference == mSearchButton) { 193 loadNetworksList(); 194 handled = true; 195 } else if (preference == mAutoSelect) { 196 selectNetworkAutomatic(); 197 handled = true; 198 } else { 199 Preference selectedCarrier = preference; 200 201 String networkStr = selectedCarrier.getTitle().toString(); 202 if (DBG) log("selected network: " + networkStr); 203 204 Message msg = mHandler.obtainMessage(EVENT_NETWORK_SELECTION_DONE); 205 Phone phone = PhoneFactory.getPhone(mPhoneId); 206 if (phone != null) { 207 phone.selectNetworkManually(mNetworkMap.get(selectedCarrier), true, msg); 208 displayNetworkSeletionInProgress(networkStr); 209 handled = true; 210 } else { 211 log("Error selecting network. phone is null."); 212 } 213 214 215 } 216 217 return handled; 218 } 219 220 //implemented for DialogInterface.OnCancelListener onCancel(DialogInterface dialog)221 public void onCancel(DialogInterface dialog) { 222 // request that the service stop the query with this callback object. 223 try { 224 mNetworkQueryService.stopNetworkQuery(mCallback); 225 } catch (RemoteException e) { 226 log("onCancel: exception from stopNetworkQuery " + e); 227 } 228 finish(); 229 } 230 getNormalizedCarrierName(OperatorInfo ni)231 public String getNormalizedCarrierName(OperatorInfo ni) { 232 if (ni != null) { 233 return ni.getOperatorAlphaLong() + " (" + ni.getOperatorNumeric() + ")"; 234 } 235 return null; 236 } 237 238 @Override onCreate(Bundle icicle)239 protected void onCreate(Bundle icicle) { 240 super.onCreate(icicle); 241 242 mUm = (UserManager) getSystemService(Context.USER_SERVICE); 243 244 if (mUm.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)) { 245 setContentView(R.layout.telephony_disallowed_preference_screen); 246 mUnavailable = true; 247 return; 248 } 249 250 addPreferencesFromResource(R.xml.carrier_select); 251 252 int subId; 253 Intent intent = getIntent(); 254 if (intent != null && intent.getExtras() != null) { 255 subId = intent.getExtras().getInt(GsmUmtsOptions.EXTRA_SUB_ID); 256 if (SubscriptionManager.isValidSubscriptionId(subId)) { 257 mPhoneId = SubscriptionManager.getPhoneId(subId); 258 } 259 } 260 261 mNetworkList = (PreferenceGroup) getPreferenceScreen().findPreference(LIST_NETWORKS_KEY); 262 mNetworkMap = new HashMap<Preference, OperatorInfo>(); 263 264 mSearchButton = getPreferenceScreen().findPreference(BUTTON_SRCH_NETWRKS_KEY); 265 mAutoSelect = getPreferenceScreen().findPreference(BUTTON_AUTO_SELECT_KEY); 266 267 // Start the Network Query service, and bind it. 268 // The OS knows to start he service only once and keep the instance around (so 269 // long as startService is called) until a stopservice request is made. Since 270 // we want this service to just stay in the background until it is killed, we 271 // don't bother stopping it from our end. 272 startService (new Intent(this, NetworkQueryService.class)); 273 bindService (new Intent(this, NetworkQueryService.class).setAction( 274 NetworkQueryService.ACTION_LOCAL_BINDER), 275 mNetworkQueryServiceConnection, Context.BIND_AUTO_CREATE); 276 } 277 278 @Override onResume()279 public void onResume() { 280 super.onResume(); 281 mIsForeground = true; 282 } 283 284 @Override onPause()285 public void onPause() { 286 super.onPause(); 287 mIsForeground = false; 288 } 289 290 /** 291 * Override onDestroy() to unbind the query service, avoiding service 292 * leak exceptions. 293 */ 294 @Override onDestroy()295 protected void onDestroy() { 296 try { 297 // used to un-register callback 298 mNetworkQueryService.unregisterCallback(mCallback); 299 } catch (RemoteException e) { 300 log("onDestroy: exception from unregisterCallback " + e); 301 } 302 303 if (!mUnavailable) { 304 // unbind the service. 305 unbindService(mNetworkQueryServiceConnection); 306 } 307 super.onDestroy(); 308 } 309 310 @Override onCreateDialog(int id)311 protected Dialog onCreateDialog(int id) { 312 313 if ((id == DIALOG_NETWORK_SELECTION) || (id == DIALOG_NETWORK_LIST_LOAD) || 314 (id == DIALOG_NETWORK_AUTO_SELECT)) { 315 ProgressDialog dialog = new ProgressDialog(this); 316 switch (id) { 317 case DIALOG_NETWORK_SELECTION: 318 // It would be more efficient to reuse this dialog by moving 319 // this setMessage() into onPreparedDialog() and NOT use 320 // removeDialog(). However, this is not possible since the 321 // message is rendered only 2 times in the ProgressDialog - 322 // after show() and before onCreate. 323 dialog.setMessage(mNetworkSelectMsg); 324 dialog.setCancelable(false); 325 dialog.setIndeterminate(true); 326 break; 327 case DIALOG_NETWORK_AUTO_SELECT: 328 dialog.setMessage(getResources().getString(R.string.register_automatically)); 329 dialog.setCancelable(false); 330 dialog.setIndeterminate(true); 331 break; 332 case DIALOG_NETWORK_LIST_LOAD: 333 default: 334 // reinstate the cancelablity of the dialog. 335 dialog.setMessage(getResources().getString(R.string.load_networks_progress)); 336 dialog.setCanceledOnTouchOutside(false); 337 dialog.setOnCancelListener(this); 338 break; 339 } 340 return dialog; 341 } 342 return null; 343 } 344 345 @Override onPrepareDialog(int id, Dialog dialog)346 protected void onPrepareDialog(int id, Dialog dialog) { 347 if ((id == DIALOG_NETWORK_SELECTION) || (id == DIALOG_NETWORK_LIST_LOAD) || 348 (id == DIALOG_NETWORK_AUTO_SELECT)) { 349 // when the dialogs come up, we'll need to indicate that 350 // we're in a busy state to dissallow further input. 351 getPreferenceScreen().setEnabled(false); 352 } 353 } 354 displayEmptyNetworkList(boolean flag)355 private void displayEmptyNetworkList(boolean flag) { 356 mNetworkList.setTitle(flag ? R.string.empty_networks_list : R.string.label_available); 357 } 358 displayNetworkSeletionInProgress(String networkStr)359 private void displayNetworkSeletionInProgress(String networkStr) { 360 // TODO: use notification manager? 361 mNetworkSelectMsg = getResources().getString(R.string.register_on_network, networkStr); 362 363 if (mIsForeground) { 364 showDialog(DIALOG_NETWORK_SELECTION); 365 } 366 } 367 displayNetworkQueryFailed(int error)368 private void displayNetworkQueryFailed(int error) { 369 String status = getResources().getString(R.string.network_query_error); 370 371 final PhoneGlobals app = PhoneGlobals.getInstance(); 372 app.notificationMgr.postTransientNotification( 373 NotificationMgr.NETWORK_SELECTION_NOTIFICATION, status); 374 } 375 displayNetworkSelectionFailed(Throwable ex)376 private void displayNetworkSelectionFailed(Throwable ex) { 377 String status; 378 379 if ((ex != null && ex instanceof CommandException) && 380 ((CommandException)ex).getCommandError() 381 == CommandException.Error.ILLEGAL_SIM_OR_ME) 382 { 383 status = getResources().getString(R.string.not_allowed); 384 } else { 385 status = getResources().getString(R.string.connect_later); 386 } 387 388 final PhoneGlobals app = PhoneGlobals.getInstance(); 389 app.notificationMgr.postTransientNotification( 390 NotificationMgr.NETWORK_SELECTION_NOTIFICATION, status); 391 392 TelephonyManager tm = (TelephonyManager) app.getSystemService(Context.TELEPHONY_SERVICE); 393 Phone phone = PhoneFactory.getPhone(mPhoneId); 394 if (phone != null) { 395 ServiceState ss = tm.getServiceStateForSubscriber(phone.getSubId()); 396 if (ss != null) { 397 app.notificationMgr.updateNetworkSelection(ss.getState()); 398 } 399 } 400 } 401 displayNetworkSelectionSucceeded()402 private void displayNetworkSelectionSucceeded() { 403 String status = getResources().getString(R.string.registration_done); 404 405 final PhoneGlobals app = PhoneGlobals.getInstance(); 406 app.notificationMgr.postTransientNotification( 407 NotificationMgr.NETWORK_SELECTION_NOTIFICATION, status); 408 409 mHandler.postDelayed(new Runnable() { 410 public void run() { 411 finish(); 412 } 413 }, 3000); 414 } 415 loadNetworksList()416 private void loadNetworksList() { 417 if (DBG) log("load networks list..."); 418 419 if (mIsForeground) { 420 showDialog(DIALOG_NETWORK_LIST_LOAD); 421 } 422 423 // delegate query request to the service. 424 try { 425 mNetworkQueryService.startNetworkQuery(mCallback, mPhoneId); 426 } catch (RemoteException e) { 427 log("loadNetworksList: exception from startNetworkQuery " + e); 428 if (mIsForeground) { 429 try { 430 dismissDialog(DIALOG_NETWORK_LIST_LOAD); 431 } catch (IllegalArgumentException e1) { 432 // do nothing 433 } 434 } 435 } 436 437 displayEmptyNetworkList(false); 438 } 439 440 /** 441 * networksListLoaded has been rewritten to take an array of 442 * OperatorInfo objects and a status field, instead of an 443 * AsyncResult. Otherwise, the functionality which takes the 444 * OperatorInfo array and creates a list of preferences from it, 445 * remains unchanged. 446 */ networksListLoaded(List<OperatorInfo> result, int status)447 private void networksListLoaded(List<OperatorInfo> result, int status) { 448 if (DBG) log("networks list loaded"); 449 450 // used to un-register callback 451 try { 452 mNetworkQueryService.unregisterCallback(mCallback); 453 } catch (RemoteException e) { 454 log("networksListLoaded: exception from unregisterCallback " + e); 455 } 456 457 // update the state of the preferences. 458 if (DBG) log("hideProgressPanel"); 459 460 // Always try to dismiss the dialog because activity may 461 // be moved to background after dialog is shown. 462 try { 463 dismissDialog(DIALOG_NETWORK_LIST_LOAD); 464 } catch (IllegalArgumentException e) { 465 // It's not a error in following scenario, we just ignore it. 466 // "Load list" dialog will not show, if NetworkQueryService is 467 // connected after this activity is moved to background. 468 if (DBG) log("Fail to dismiss network load list dialog " + e); 469 } 470 471 getPreferenceScreen().setEnabled(true); 472 clearList(); 473 474 if (status != NetworkQueryService.QUERY_OK) { 475 if (DBG) log("error while querying available networks"); 476 displayNetworkQueryFailed(status); 477 displayEmptyNetworkList(true); 478 } else { 479 if (result != null){ 480 displayEmptyNetworkList(false); 481 482 // create a preference for each item in the list. 483 // just use the operator name instead of the mildly 484 // confusing mcc/mnc. 485 for (OperatorInfo ni : result) { 486 Preference carrier = new Preference(this, null); 487 carrier.setTitle(getNetworkTitle(ni)); 488 carrier.setPersistent(false); 489 mNetworkList.addPreference(carrier); 490 mNetworkMap.put(carrier, ni); 491 492 if (DBG) log(" " + ni); 493 } 494 } else { 495 displayEmptyNetworkList(true); 496 } 497 } 498 } 499 500 /** 501 * Returns the title of the network obtained in the manual search. 502 * 503 * @param OperatorInfo contains the information of the network. 504 * 505 * @return Long Name if not null/empty, otherwise Short Name if not null/empty, 506 * else MCCMNC string. 507 */ 508 getNetworkTitle(OperatorInfo ni)509 private String getNetworkTitle(OperatorInfo ni) { 510 if (!TextUtils.isEmpty(ni.getOperatorAlphaLong())) { 511 return ni.getOperatorAlphaLong(); 512 } else if (!TextUtils.isEmpty(ni.getOperatorAlphaShort())) { 513 return ni.getOperatorAlphaShort(); 514 } else { 515 BidiFormatter bidiFormatter = BidiFormatter.getInstance(); 516 return bidiFormatter.unicodeWrap(ni.getOperatorNumeric(), TextDirectionHeuristics.LTR); 517 } 518 } 519 clearList()520 private void clearList() { 521 for (Preference p : mNetworkMap.keySet()) { 522 mNetworkList.removePreference(p); 523 } 524 mNetworkMap.clear(); 525 } 526 selectNetworkAutomatic()527 private void selectNetworkAutomatic() { 528 if (DBG) log("select network automatically..."); 529 if (mIsForeground) { 530 showDialog(DIALOG_NETWORK_AUTO_SELECT); 531 } 532 533 Message msg = mHandler.obtainMessage(EVENT_AUTO_SELECT_DONE); 534 Phone phone = PhoneFactory.getPhone(mPhoneId); 535 if (phone != null) { 536 phone.setNetworkSelectionModeAutomatic(msg); 537 } 538 } 539 log(String msg)540 private void log(String msg) { 541 Log.d(LOG_TAG, "[NetworksList] " + msg); 542 } 543 } 544