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