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 // 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 onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference)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 Phone phone = PhoneFactory.getPhone(mPhoneId); 208 if (phone != null) { 209 phone.selectNetworkManually(mNetworkMap.get(selectedCarrier), true, msg); 210 displayNetworkSeletionInProgress(networkStr); 211 handled = true; 212 } else { 213 log("Error selecting network. phone is null."); 214 } 215 216 217 } 218 219 return handled; 220 } 221 222 //implemented for DialogInterface.OnCancelListener onCancel(DialogInterface dialog)223 public void onCancel(DialogInterface dialog) { 224 // request that the service stop the query with this callback object. 225 try { 226 mNetworkQueryService.stopNetworkQuery(mCallback); 227 } catch (RemoteException e) { 228 log("onCancel: exception from stopNetworkQuery " + e); 229 } 230 finish(); 231 } 232 getNormalizedCarrierName(OperatorInfo ni)233 public String getNormalizedCarrierName(OperatorInfo ni) { 234 if (ni != null) { 235 return ni.getOperatorAlphaLong() + " (" + ni.getOperatorNumeric() + ")"; 236 } 237 return null; 238 } 239 240 @Override onCreate(Bundle icicle)241 protected void onCreate(Bundle icicle) { 242 super.onCreate(icicle); 243 244 mUm = (UserManager) getSystemService(Context.USER_SERVICE); 245 246 if (mUm.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)) { 247 setContentView(R.layout.telephony_disallowed_preference_screen); 248 mUnavailable = true; 249 return; 250 } 251 252 addPreferencesFromResource(R.xml.carrier_select); 253 254 int subId; 255 Intent intent = getIntent(); 256 if (intent != null && intent.getExtras() != null) { 257 subId = intent.getExtras().getInt(GsmUmtsOptions.EXTRA_SUB_ID); 258 if (SubscriptionManager.isValidSubscriptionId(subId)) { 259 mPhoneId = SubscriptionManager.getPhoneId(subId); 260 } 261 } 262 263 mNetworkList = (PreferenceGroup) getPreferenceScreen().findPreference(LIST_NETWORKS_KEY); 264 mNetworkMap = new HashMap<Preference, OperatorInfo>(); 265 266 mSearchButton = getPreferenceScreen().findPreference(BUTTON_SRCH_NETWRKS_KEY); 267 mAutoSelect = getPreferenceScreen().findPreference(BUTTON_AUTO_SELECT_KEY); 268 269 // Start the Network Query service, and bind it. 270 // The OS knows to start he service only once and keep the instance around (so 271 // long as startService is called) until a stopservice request is made. Since 272 // we want this service to just stay in the background until it is killed, we 273 // don't bother stopping it from our end. 274 startService (new Intent(this, NetworkQueryService.class)); 275 bindService (new Intent(this, NetworkQueryService.class).setAction( 276 NetworkQueryService.ACTION_LOCAL_BINDER), 277 mNetworkQueryServiceConnection, Context.BIND_AUTO_CREATE); 278 } 279 280 @Override onResume()281 public void onResume() { 282 super.onResume(); 283 mIsForeground = true; 284 } 285 286 @Override onPause()287 public void onPause() { 288 super.onPause(); 289 mIsForeground = false; 290 } 291 292 /** 293 * Override onDestroy() to unbind the query service, avoiding service 294 * leak exceptions. 295 */ 296 @Override onDestroy()297 protected void onDestroy() { 298 try { 299 // used to un-register callback 300 mNetworkQueryService.unregisterCallback(mCallback); 301 } catch (RemoteException e) { 302 log("onDestroy: exception from unregisterCallback " + e); 303 } 304 305 if (!mUnavailable) { 306 // unbind the service. 307 unbindService(mNetworkQueryServiceConnection); 308 } 309 super.onDestroy(); 310 } 311 312 @Override onCreateDialog(int id)313 protected Dialog onCreateDialog(int id) { 314 315 if ((id == DIALOG_NETWORK_SELECTION) || (id == DIALOG_NETWORK_LIST_LOAD) || 316 (id == DIALOG_NETWORK_AUTO_SELECT)) { 317 ProgressDialog dialog = new ProgressDialog(this); 318 switch (id) { 319 case DIALOG_NETWORK_SELECTION: 320 // It would be more efficient to reuse this dialog by moving 321 // this setMessage() into onPreparedDialog() and NOT use 322 // removeDialog(). However, this is not possible since the 323 // message is rendered only 2 times in the ProgressDialog - 324 // after show() and before onCreate. 325 dialog.setMessage(mNetworkSelectMsg); 326 dialog.setCancelable(false); 327 dialog.setIndeterminate(true); 328 break; 329 case DIALOG_NETWORK_AUTO_SELECT: 330 dialog.setMessage(getResources().getString(R.string.register_automatically)); 331 dialog.setCancelable(false); 332 dialog.setIndeterminate(true); 333 break; 334 case DIALOG_NETWORK_LIST_LOAD: 335 default: 336 // reinstate the cancelablity of the dialog. 337 dialog.setMessage(getResources().getString(R.string.load_networks_progress)); 338 dialog.setCanceledOnTouchOutside(false); 339 dialog.setOnCancelListener(this); 340 break; 341 } 342 return dialog; 343 } 344 return null; 345 } 346 347 @Override onPrepareDialog(int id, Dialog dialog)348 protected void onPrepareDialog(int id, Dialog dialog) { 349 if ((id == DIALOG_NETWORK_SELECTION) || (id == DIALOG_NETWORK_LIST_LOAD) || 350 (id == DIALOG_NETWORK_AUTO_SELECT)) { 351 // when the dialogs come up, we'll need to indicate that 352 // we're in a busy state to dissallow further input. 353 getPreferenceScreen().setEnabled(false); 354 } 355 } 356 displayEmptyNetworkList(boolean flag)357 private void displayEmptyNetworkList(boolean flag) { 358 mNetworkList.setTitle(flag ? R.string.empty_networks_list : R.string.label_available); 359 } 360 displayNetworkSeletionInProgress(String networkStr)361 private void displayNetworkSeletionInProgress(String networkStr) { 362 // TODO: use notification manager? 363 mNetworkSelectMsg = getResources().getString(R.string.register_on_network, networkStr); 364 365 if (mIsForeground) { 366 showDialog(DIALOG_NETWORK_SELECTION); 367 } 368 } 369 displayNetworkQueryFailed(int error)370 private void displayNetworkQueryFailed(int error) { 371 String status = getResources().getString(R.string.network_query_error); 372 373 final PhoneGlobals app = PhoneGlobals.getInstance(); 374 app.notificationMgr.postTransientNotification( 375 NotificationMgr.NETWORK_SELECTION_NOTIFICATION, status); 376 } 377 displayNetworkSelectionFailed(Throwable ex)378 private void displayNetworkSelectionFailed(Throwable ex) { 379 String status; 380 381 if ((ex != null && ex instanceof CommandException) && 382 ((CommandException)ex).getCommandError() 383 == CommandException.Error.ILLEGAL_SIM_OR_ME) 384 { 385 status = getResources().getString(R.string.not_allowed); 386 } else { 387 status = getResources().getString(R.string.connect_later); 388 } 389 390 final PhoneGlobals app = PhoneGlobals.getInstance(); 391 app.notificationMgr.postTransientNotification( 392 NotificationMgr.NETWORK_SELECTION_NOTIFICATION, status); 393 394 TelephonyManager tm = (TelephonyManager) app.getSystemService(Context.TELEPHONY_SERVICE); 395 Phone phone = PhoneFactory.getPhone(mPhoneId); 396 if (phone != null) { 397 ServiceState ss = tm.getServiceStateForSubscriber(phone.getSubId()); 398 if (ss != null) { 399 app.notificationMgr.updateNetworkSelection(ss.getState()); 400 } 401 } 402 } 403 displayNetworkSelectionSucceeded()404 private void displayNetworkSelectionSucceeded() { 405 String status = getResources().getString(R.string.registration_done); 406 407 final PhoneGlobals app = PhoneGlobals.getInstance(); 408 app.notificationMgr.postTransientNotification( 409 NotificationMgr.NETWORK_SELECTION_NOTIFICATION, status); 410 411 mHandler.postDelayed(new Runnable() { 412 public void run() { 413 finish(); 414 } 415 }, 3000); 416 } 417 loadNetworksList()418 private void loadNetworksList() { 419 if (DBG) log("load networks list..."); 420 421 if (mIsForeground) { 422 showDialog(DIALOG_NETWORK_LIST_LOAD); 423 } 424 425 // delegate query request to the service. 426 try { 427 mNetworkQueryService.startNetworkQuery(mCallback, mPhoneId); 428 } catch (RemoteException e) { 429 log("loadNetworksList: exception from startNetworkQuery " + e); 430 if (mIsForeground) { 431 try { 432 dismissDialog(DIALOG_NETWORK_LIST_LOAD); 433 } catch (IllegalArgumentException e1) { 434 // do nothing 435 } 436 } 437 } 438 439 displayEmptyNetworkList(false); 440 } 441 442 /** 443 * networksListLoaded has been rewritten to take an array of 444 * OperatorInfo objects and a status field, instead of an 445 * AsyncResult. Otherwise, the functionality which takes the 446 * OperatorInfo array and creates a list of preferences from it, 447 * remains unchanged. 448 */ networksListLoaded(List<OperatorInfo> result, int status)449 private void networksListLoaded(List<OperatorInfo> result, int status) { 450 if (DBG) log("networks list loaded"); 451 452 // used to un-register callback 453 try { 454 mNetworkQueryService.unregisterCallback(mCallback); 455 } catch (RemoteException e) { 456 log("networksListLoaded: exception from unregisterCallback " + e); 457 } 458 459 // update the state of the preferences. 460 if (DBG) log("hideProgressPanel"); 461 462 // Always try to dismiss the dialog because activity may 463 // be moved to background after dialog is shown. 464 try { 465 dismissDialog(DIALOG_NETWORK_LIST_LOAD); 466 } catch (IllegalArgumentException e) { 467 // It's not a error in following scenario, we just ignore it. 468 // "Load list" dialog will not show, if NetworkQueryService is 469 // connected after this activity is moved to background. 470 if (DBG) log("Fail to dismiss network load list dialog " + e); 471 } 472 473 getPreferenceScreen().setEnabled(true); 474 clearList(); 475 476 if (status != NetworkQueryService.QUERY_OK) { 477 if (DBG) log("error while querying available networks"); 478 displayNetworkQueryFailed(status); 479 displayEmptyNetworkList(true); 480 } else { 481 if (result != null){ 482 displayEmptyNetworkList(false); 483 484 // create a preference for each item in the list. 485 // just use the operator name instead of the mildly 486 // confusing mcc/mnc. 487 for (OperatorInfo ni : result) { 488 Preference carrier = new Preference(this, null); 489 carrier.setTitle(getNetworkTitle(ni)); 490 carrier.setPersistent(false); 491 mNetworkList.addPreference(carrier); 492 mNetworkMap.put(carrier, ni); 493 494 if (DBG) log(" " + ni); 495 } 496 } else { 497 displayEmptyNetworkList(true); 498 } 499 } 500 } 501 502 /** 503 * Returns the title of the network obtained in the manual search. 504 * 505 * @param OperatorInfo contains the information of the network. 506 * 507 * @return Long Name if not null/empty, otherwise Short Name if not null/empty, 508 * else MCCMNC string. 509 */ 510 getNetworkTitle(OperatorInfo ni)511 private String getNetworkTitle(OperatorInfo ni) { 512 if (!TextUtils.isEmpty(ni.getOperatorAlphaLong())) { 513 return ni.getOperatorAlphaLong(); 514 } else if (!TextUtils.isEmpty(ni.getOperatorAlphaShort())) { 515 return ni.getOperatorAlphaShort(); 516 } else { 517 BidiFormatter bidiFormatter = BidiFormatter.getInstance(); 518 return bidiFormatter.unicodeWrap(ni.getOperatorNumeric(), TextDirectionHeuristics.LTR); 519 } 520 } 521 clearList()522 private void clearList() { 523 for (Preference p : mNetworkMap.keySet()) { 524 mNetworkList.removePreference(p); 525 } 526 mNetworkMap.clear(); 527 } 528 selectNetworkAutomatic()529 private void selectNetworkAutomatic() { 530 if (DBG) log("select network automatically..."); 531 if (mIsForeground) { 532 showDialog(DIALOG_NETWORK_AUTO_SELECT); 533 } 534 535 Message msg = mHandler.obtainMessage(EVENT_AUTO_SELECT_DONE); 536 Phone phone = PhoneFactory.getPhone(mPhoneId); 537 if (phone != null) { 538 phone.setNetworkSelectionModeAutomatic(msg); 539 } 540 } 541 log(String msg)542 private void log(String msg) { 543 Log.d(LOG_TAG, "[NetworksList] " + msg); 544 } 545 } 546