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