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.content.Intent; 20 import android.net.Uri; 21 import android.os.AsyncResult; 22 import android.os.Binder; 23 import android.os.Bundle; 24 import android.os.Handler; 25 import android.os.Looper; 26 import android.os.Message; 27 import android.os.ServiceManager; 28 import android.telephony.NeighboringCellInfo; 29 import android.telephony.ServiceState; 30 import android.telephony.TelephonyManager; 31 import android.text.TextUtils; 32 import android.util.Log; 33 34 import com.android.internal.telephony.DefaultPhoneNotifier; 35 import com.android.internal.telephony.IccCard; 36 import com.android.internal.telephony.ITelephony; 37 import com.android.internal.telephony.Phone; 38 39 import java.util.List; 40 import java.util.ArrayList; 41 42 /** 43 * Implementation of the ITelephony interface. 44 */ 45 public class PhoneInterfaceManager extends ITelephony.Stub { 46 private static final String LOG_TAG = "PhoneInterfaceManager"; 47 private static final boolean DBG = (PhoneApp.DBG_LEVEL >= 2); 48 49 // Message codes used with mMainThreadHandler 50 private static final int CMD_HANDLE_PIN_MMI = 1; 51 private static final int CMD_HANDLE_NEIGHBORING_CELL = 2; 52 private static final int EVENT_NEIGHBORING_CELL_DONE = 3; 53 private static final int CMD_ANSWER_RINGING_CALL = 4; 54 private static final int CMD_END_CALL = 5; // not used yet 55 private static final int CMD_SILENCE_RINGER = 6; 56 57 PhoneApp mApp; 58 Phone mPhone; 59 MainThreadHandler mMainThreadHandler; 60 61 /** 62 * A request object for use with {@link MainThreadHandler}. Requesters should wait() on the 63 * request after sending. The main thread will notify the request when it is complete. 64 */ 65 private static final class MainThreadRequest { 66 /** The argument to use for the request */ 67 public Object argument; 68 /** The result of the request that is run on the main thread */ 69 public Object result; 70 71 public MainThreadRequest(Object argument) { 72 this.argument = argument; 73 } 74 } 75 76 /** 77 * A handler that processes messages on the main thread in the phone process. Since many 78 * of the Phone calls are not thread safe this is needed to shuttle the requests from the 79 * inbound binder threads to the main thread in the phone process. The Binder thread 80 * may provide a {@link MainThreadRequest} object in the msg.obj field that they are waiting 81 * on, which will be notified when the operation completes and will contain the result of the 82 * request. 83 * 84 * <p>If a MainThreadRequest object is provided in the msg.obj field, 85 * note that request.result must be set to something non-null for the calling thread to 86 * unblock. 87 */ 88 private final class MainThreadHandler extends Handler { 89 @Override 90 public void handleMessage(Message msg) { 91 MainThreadRequest request; 92 Message onCompleted; 93 AsyncResult ar; 94 95 switch (msg.what) { 96 case CMD_HANDLE_PIN_MMI: 97 request = (MainThreadRequest) msg.obj; 98 request.result = Boolean.valueOf( 99 mPhone.handlePinMmi((String) request.argument)); 100 // Wake up the requesting thread 101 synchronized (request) { 102 request.notifyAll(); 103 } 104 break; 105 106 case CMD_HANDLE_NEIGHBORING_CELL: 107 request = (MainThreadRequest) msg.obj; 108 onCompleted = obtainMessage(EVENT_NEIGHBORING_CELL_DONE, 109 request); 110 mPhone.getNeighboringCids(onCompleted); 111 break; 112 113 case EVENT_NEIGHBORING_CELL_DONE: 114 ar = (AsyncResult) msg.obj; 115 request = (MainThreadRequest) ar.userObj; 116 if (ar.exception == null && ar.result != null) { 117 request.result = ar.result; 118 } else { 119 // create an empty list to notify the waiting thread 120 request.result = new ArrayList<NeighboringCellInfo>(); 121 } 122 // Wake up the requesting thread 123 synchronized (request) { 124 request.notifyAll(); 125 } 126 break; 127 128 case CMD_ANSWER_RINGING_CALL: 129 answerRingingCallInternal(); 130 break; 131 132 case CMD_SILENCE_RINGER: 133 silenceRingerInternal(); 134 break; 135 136 case CMD_END_CALL: 137 request = (MainThreadRequest) msg.obj; 138 boolean hungUp = false; 139 int phoneType = mPhone.getPhoneType(); 140 if (phoneType == Phone.PHONE_TYPE_CDMA) { 141 // CDMA: If the user presses the Power button we treat it as 142 // ending the complete call session 143 hungUp = PhoneUtils.hangupRingingAndActive(mPhone); 144 } else if (phoneType == Phone.PHONE_TYPE_GSM) { 145 // GSM: End the call as per the Phone state 146 hungUp = PhoneUtils.hangup(mPhone); 147 } else { 148 throw new IllegalStateException("Unexpected phone type: " + phoneType); 149 } 150 if (DBG) log("CMD_END_CALL: " + (hungUp ? "hung up!" : "no call to hang up")); 151 request.result = hungUp; 152 // Wake up the requesting thread 153 synchronized (request) { 154 request.notifyAll(); 155 } 156 break; 157 158 default: 159 Log.w(LOG_TAG, "MainThreadHandler: unexpected message code: " + msg.what); 160 break; 161 } 162 } 163 } 164 165 /** 166 * Posts the specified command to be executed on the main thread, 167 * waits for the request to complete, and returns the result. 168 * @see sendRequestAsync 169 */ 170 private Object sendRequest(int command, Object argument) { 171 if (Looper.myLooper() == mMainThreadHandler.getLooper()) { 172 throw new RuntimeException("This method will deadlock if called from the main thread."); 173 } 174 175 MainThreadRequest request = new MainThreadRequest(argument); 176 Message msg = mMainThreadHandler.obtainMessage(command, request); 177 msg.sendToTarget(); 178 179 // Wait for the request to complete 180 synchronized (request) { 181 while (request.result == null) { 182 try { 183 request.wait(); 184 } catch (InterruptedException e) { 185 // Do nothing, go back and wait until the request is complete 186 } 187 } 188 } 189 return request.result; 190 } 191 192 /** 193 * Asynchronous ("fire and forget") version of sendRequest(): 194 * Posts the specified command to be executed on the main thread, and 195 * returns immediately. 196 * @see sendRequest 197 */ 198 private void sendRequestAsync(int command) { 199 mMainThreadHandler.sendEmptyMessage(command); 200 } 201 202 public PhoneInterfaceManager(PhoneApp app, Phone phone) { 203 mApp = app; 204 mPhone = phone; 205 mMainThreadHandler = new MainThreadHandler(); 206 publish(); 207 } 208 209 private void publish() { 210 if (DBG) log("publish: " + this); 211 212 ServiceManager.addService("phone", this); 213 } 214 215 // 216 // Implementation of the ITelephony interface. 217 // 218 219 public void dial(String number) { 220 if (DBG) log("dial: " + number); 221 // No permission check needed here: This is just a wrapper around the 222 // ACTION_DIAL intent, which is available to any app since it puts up 223 // the UI before it does anything. 224 225 String url = createTelUrl(number); 226 if (url == null) { 227 return; 228 } 229 230 // PENDING: should we just silently fail if phone is offhook or ringing? 231 Phone.State state = mPhone.getState(); 232 if (state != Phone.State.OFFHOOK && state != Phone.State.RINGING) { 233 Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(url)); 234 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 235 mApp.startActivity(intent); 236 } 237 } 238 239 public void call(String number) { 240 if (DBG) log("call: " + number); 241 242 // This is just a wrapper around the ACTION_CALL intent, but we still 243 // need to do a permission check since we're calling startActivity() 244 // from the context of the phone app. 245 enforceCallPermission(); 246 247 String url = createTelUrl(number); 248 if (url == null) { 249 return; 250 } 251 252 Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse(url)); 253 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 254 intent.setClassName(mApp, PhoneApp.getCallScreenClassName()); 255 mApp.startActivity(intent); 256 } 257 258 private boolean showCallScreenInternal(boolean specifyInitialDialpadState, 259 boolean initialDialpadState) { 260 if (isIdle()) { 261 return false; 262 } 263 // If the phone isn't idle then go to the in-call screen 264 long callingId = Binder.clearCallingIdentity(); 265 try { 266 Intent intent; 267 if (specifyInitialDialpadState) { 268 intent = PhoneApp.createInCallIntent(initialDialpadState); 269 } else { 270 intent = PhoneApp.createInCallIntent(); 271 } 272 mApp.startActivity(intent); 273 } finally { 274 Binder.restoreCallingIdentity(callingId); 275 } 276 return true; 277 } 278 279 // Show the in-call screen without specifying the initial dialpad state. 280 public boolean showCallScreen() { 281 return showCallScreenInternal(false, false); 282 } 283 284 // The variation of showCallScreen() that specifies the initial dialpad state. 285 // (Ideally this would be called showCallScreen() too, just with a different 286 // signature, but AIDL doesn't allow that.) 287 public boolean showCallScreenWithDialpad(boolean showDialpad) { 288 return showCallScreenInternal(true, showDialpad); 289 } 290 291 /** 292 * End a call based on call state 293 * @return true is a call was ended 294 */ 295 public boolean endCall() { 296 enforceCallPermission(); 297 return (Boolean) sendRequest(CMD_END_CALL, null); 298 } 299 300 public void answerRingingCall() { 301 if (DBG) log("answerRingingCall..."); 302 // TODO: there should eventually be a separate "ANSWER_PHONE" permission, 303 // but that can probably wait till the big TelephonyManager API overhaul. 304 // For now, protect this call with the MODIFY_PHONE_STATE permission. 305 enforceModifyPermission(); 306 sendRequestAsync(CMD_ANSWER_RINGING_CALL); 307 } 308 309 /** 310 * Make the actual telephony calls to implement answerRingingCall(). 311 * This should only be called from the main thread of the Phone app. 312 * @see answerRingingCall 313 * 314 * TODO: it would be nice to return true if we answered the call, or 315 * false if there wasn't actually a ringing incoming call, or some 316 * other error occurred. (In other words, pass back the return value 317 * from PhoneUtils.answerCall() or PhoneUtils.answerAndEndActive().) 318 * But that would require calling this method via sendRequest() rather 319 * than sendRequestAsync(), and right now we don't actually *need* that 320 * return value, so let's just return void for now. 321 */ 322 private void answerRingingCallInternal() { 323 final boolean hasRingingCall = !mPhone.getRingingCall().isIdle(); 324 if (hasRingingCall) { 325 final boolean hasActiveCall = !mPhone.getForegroundCall().isIdle(); 326 final boolean hasHoldingCall = !mPhone.getBackgroundCall().isIdle(); 327 if (hasActiveCall && hasHoldingCall) { 328 // Both lines are in use! 329 // TODO: provide a flag to let the caller specify what 330 // policy to use if both lines are in use. (The current 331 // behavior is hardwired to "answer incoming, end ongoing", 332 // which is how the CALL button is specced to behave.) 333 PhoneUtils.answerAndEndActive(mPhone); 334 return; 335 } else { 336 // answerCall() will automatically hold the current active 337 // call, if there is one. 338 PhoneUtils.answerCall(mPhone); 339 return; 340 } 341 } else { 342 // No call was ringing. 343 return; 344 } 345 } 346 347 public void silenceRinger() { 348 if (DBG) log("silenceRinger..."); 349 // TODO: find a more appropriate permission to check here. 350 // (That can probably wait till the big TelephonyManager API overhaul. 351 // For now, protect this call with the MODIFY_PHONE_STATE permission.) 352 enforceModifyPermission(); 353 sendRequestAsync(CMD_SILENCE_RINGER); 354 } 355 356 /** 357 * Internal implemenation of silenceRinger(). 358 * This should only be called from the main thread of the Phone app. 359 * @see silenceRinger 360 */ 361 private void silenceRingerInternal() { 362 if ((mPhone.getState() == Phone.State.RINGING) 363 && mApp.notifier.isRinging()) { 364 // Ringer is actually playing, so silence it. 365 if (DBG) log("silenceRingerInternal: silencing..."); 366 PhoneUtils.setAudioControlState(PhoneUtils.AUDIO_IDLE); 367 mApp.notifier.silenceRinger(); 368 } 369 } 370 371 public boolean isOffhook() { 372 return (mPhone.getState() == Phone.State.OFFHOOK); 373 } 374 375 public boolean isRinging() { 376 return (mPhone.getState() == Phone.State.RINGING); 377 } 378 379 public boolean isIdle() { 380 return (mPhone.getState() == Phone.State.IDLE); 381 } 382 383 public boolean isSimPinEnabled() { 384 enforceReadPermission(); 385 return (PhoneApp.getInstance().isSimPinEnabled()); 386 } 387 388 public boolean supplyPin(String pin) { 389 enforceModifyPermission(); 390 final CheckSimPin checkSimPin = new CheckSimPin(mPhone.getIccCard()); 391 checkSimPin.start(); 392 return checkSimPin.checkPin(pin); 393 } 394 395 /** 396 * Helper thread to turn async call to {@link SimCard#supplyPin} into 397 * a synchronous one. 398 */ 399 private static class CheckSimPin extends Thread { 400 401 private final IccCard mSimCard; 402 403 private boolean mDone = false; 404 private boolean mResult = false; 405 406 // For replies from SimCard interface 407 private Handler mHandler; 408 409 // For async handler to identify request type 410 private static final int SUPPLY_PIN_COMPLETE = 100; 411 412 public CheckSimPin(IccCard simCard) { 413 mSimCard = simCard; 414 } 415 416 @Override 417 public void run() { 418 Looper.prepare(); 419 synchronized (CheckSimPin.this) { 420 mHandler = new Handler() { 421 @Override 422 public void handleMessage(Message msg) { 423 AsyncResult ar = (AsyncResult) msg.obj; 424 switch (msg.what) { 425 case SUPPLY_PIN_COMPLETE: 426 Log.d(LOG_TAG, "SUPPLY_PIN_COMPLETE"); 427 synchronized (CheckSimPin.this) { 428 mResult = (ar.exception == null); 429 mDone = true; 430 CheckSimPin.this.notifyAll(); 431 } 432 break; 433 } 434 } 435 }; 436 CheckSimPin.this.notifyAll(); 437 } 438 Looper.loop(); 439 } 440 441 synchronized boolean checkPin(String pin) { 442 443 while (mHandler == null) { 444 try { 445 wait(); 446 } catch (InterruptedException e) { 447 Thread.currentThread().interrupt(); 448 } 449 } 450 Message callback = Message.obtain(mHandler, SUPPLY_PIN_COMPLETE); 451 452 mSimCard.supplyPin(pin, callback); 453 454 while (!mDone) { 455 try { 456 Log.d(LOG_TAG, "wait for done"); 457 wait(); 458 } catch (InterruptedException e) { 459 // Restore the interrupted status 460 Thread.currentThread().interrupt(); 461 } 462 } 463 Log.d(LOG_TAG, "done"); 464 return mResult; 465 } 466 } 467 468 public void updateServiceLocation() { 469 // No permission check needed here: this call is harmless, and it's 470 // needed for the ServiceState.requestStateUpdate() call (which is 471 // already intentionally exposed to 3rd parties.) 472 mPhone.updateServiceLocation(); 473 } 474 475 public boolean isRadioOn() { 476 return mPhone.getServiceState().getState() != ServiceState.STATE_POWER_OFF; 477 } 478 479 public void toggleRadioOnOff() { 480 enforceModifyPermission(); 481 mPhone.setRadioPower(!isRadioOn()); 482 } 483 public boolean setRadio(boolean turnOn) { 484 enforceModifyPermission(); 485 if ((mPhone.getServiceState().getState() != ServiceState.STATE_POWER_OFF) != turnOn) { 486 toggleRadioOnOff(); 487 } 488 return true; 489 } 490 491 public boolean enableDataConnectivity() { 492 enforceModifyPermission(); 493 return mPhone.enableDataConnectivity(); 494 } 495 496 public int enableApnType(String type) { 497 enforceModifyPermission(); 498 return mPhone.enableApnType(type); 499 } 500 501 public int disableApnType(String type) { 502 enforceModifyPermission(); 503 return mPhone.disableApnType(type); 504 } 505 506 public boolean disableDataConnectivity() { 507 enforceModifyPermission(); 508 return mPhone.disableDataConnectivity(); 509 } 510 511 public boolean isDataConnectivityPossible() { 512 return mPhone.isDataConnectivityPossible(); 513 } 514 515 public boolean handlePinMmi(String dialString) { 516 enforceModifyPermission(); 517 return (Boolean) sendRequest(CMD_HANDLE_PIN_MMI, dialString); 518 } 519 520 public void cancelMissedCallsNotification() { 521 enforceModifyPermission(); 522 NotificationMgr.getDefault().cancelMissedCallNotification(); 523 } 524 525 public int getCallState() { 526 return DefaultPhoneNotifier.convertCallState(mPhone.getState()); 527 } 528 529 public int getDataState() { 530 return DefaultPhoneNotifier.convertDataState(mPhone.getDataConnectionState()); 531 } 532 533 public int getDataActivity() { 534 return DefaultPhoneNotifier.convertDataActivityState(mPhone.getDataActivityState()); 535 } 536 537 public Bundle getCellLocation() { 538 try { 539 mApp.enforceCallingOrSelfPermission( 540 android.Manifest.permission.ACCESS_FINE_LOCATION, null); 541 } catch (SecurityException e) { 542 // If we have ACCESS_FINE_LOCATION permission, skip the check for ACCESS_COARSE_LOCATION 543 // A failure should throw the SecurityException from ACCESS_COARSE_LOCATION since this 544 // is the weaker precondition 545 mApp.enforceCallingOrSelfPermission( 546 android.Manifest.permission.ACCESS_COARSE_LOCATION, null); 547 } 548 Bundle data = new Bundle(); 549 mPhone.getCellLocation().fillInNotifierBundle(data); 550 return data; 551 } 552 553 public void enableLocationUpdates() { 554 mApp.enforceCallingOrSelfPermission( 555 android.Manifest.permission.CONTROL_LOCATION_UPDATES, null); 556 mPhone.enableLocationUpdates(); 557 } 558 559 public void disableLocationUpdates() { 560 mApp.enforceCallingOrSelfPermission( 561 android.Manifest.permission.CONTROL_LOCATION_UPDATES, null); 562 mPhone.disableLocationUpdates(); 563 } 564 565 @SuppressWarnings("unchecked") 566 public List<NeighboringCellInfo> getNeighboringCellInfo() { 567 try { 568 mApp.enforceCallingOrSelfPermission( 569 android.Manifest.permission.ACCESS_FINE_LOCATION, null); 570 } catch (SecurityException e) { 571 // If we have ACCESS_FINE_LOCATION permission, skip the check 572 // for ACCESS_COARSE_LOCATION 573 // A failure should throw the SecurityException from 574 // ACCESS_COARSE_LOCATION since this is the weaker precondition 575 mApp.enforceCallingOrSelfPermission( 576 android.Manifest.permission.ACCESS_COARSE_LOCATION, null); 577 } 578 579 ArrayList<NeighboringCellInfo> cells = null; 580 581 try { 582 cells = (ArrayList<NeighboringCellInfo>) sendRequest( 583 CMD_HANDLE_NEIGHBORING_CELL, null); 584 } catch (RuntimeException e) { 585 Log.e(LOG_TAG, "getNeighboringCellInfo " + e); 586 } 587 588 return (List <NeighboringCellInfo>) cells; 589 } 590 591 592 // 593 // Internal helper methods. 594 // 595 596 /** 597 * Make sure the caller has the READ_PHONE_STATE permission. 598 * 599 * @throws SecurityException if the caller does not have the required permission 600 */ 601 private void enforceReadPermission() { 602 mApp.enforceCallingOrSelfPermission(android.Manifest.permission.READ_PHONE_STATE, null); 603 } 604 605 /** 606 * Make sure the caller has the MODIFY_PHONE_STATE permission. 607 * 608 * @throws SecurityException if the caller does not have the required permission 609 */ 610 private void enforceModifyPermission() { 611 mApp.enforceCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE, null); 612 } 613 614 /** 615 * Make sure the caller has the CALL_PHONE permission. 616 * 617 * @throws SecurityException if the caller does not have the required permission 618 */ 619 private void enforceCallPermission() { 620 mApp.enforceCallingOrSelfPermission(android.Manifest.permission.CALL_PHONE, null); 621 } 622 623 624 private String createTelUrl(String number) { 625 if (TextUtils.isEmpty(number)) { 626 return null; 627 } 628 629 StringBuilder buf = new StringBuilder("tel:"); 630 buf.append(number); 631 return buf.toString(); 632 } 633 634 private void log(String msg) { 635 Log.d(LOG_TAG, "[PhoneIntfMgr] " + msg); 636 } 637 638 public int getActivePhoneType() { 639 return mPhone.getPhoneType(); 640 } 641 642 /** 643 * Returns the CDMA ERI icon index to display 644 */ 645 public int getCdmaEriIconIndex() { 646 return mPhone.getCdmaEriIconIndex(); 647 } 648 649 /** 650 * Returns the CDMA ERI icon mode, 651 * 0 - ON 652 * 1 - FLASHING 653 */ 654 public int getCdmaEriIconMode() { 655 return mPhone.getCdmaEriIconMode(); 656 } 657 658 /** 659 * Returns the CDMA ERI text, 660 */ 661 public String getCdmaEriText() { 662 return mPhone.getCdmaEriText(); 663 } 664 665 /** 666 * Returns true if CDMA provisioning needs to run. 667 */ 668 public boolean getCdmaNeedsProvisioning() { 669 if (getActivePhoneType() == Phone.PHONE_TYPE_GSM) { 670 return false; 671 } 672 673 boolean needsProvisioning = false; 674 String cdmaMin = mPhone.getCdmaMin(); 675 try { 676 needsProvisioning = OtaUtils.needsActivation(cdmaMin); 677 } catch (IllegalArgumentException e) { 678 // shouldn't get here unless hardware is misconfigured 679 Log.e(LOG_TAG, "CDMA MIN string " + ((cdmaMin == null) ? "was null" : "was too short")); 680 } 681 return needsProvisioning; 682 } 683 684 /** 685 * Returns the unread count of voicemails 686 */ 687 public int getVoiceMessageCount() { 688 return mPhone.getVoiceMessageCount(); 689 } 690 691 /** 692 * Returns the network type 693 */ 694 public int getNetworkType() { 695 int radiotech = mPhone.getServiceState().getRadioTechnology(); 696 switch(radiotech) { 697 case ServiceState.RADIO_TECHNOLOGY_GPRS: 698 return TelephonyManager.NETWORK_TYPE_GPRS; 699 case ServiceState.RADIO_TECHNOLOGY_EDGE: 700 return TelephonyManager.NETWORK_TYPE_EDGE; 701 case ServiceState.RADIO_TECHNOLOGY_UMTS: 702 return TelephonyManager.NETWORK_TYPE_UMTS; 703 case ServiceState.RADIO_TECHNOLOGY_HSDPA: 704 return TelephonyManager.NETWORK_TYPE_HSDPA; 705 case ServiceState.RADIO_TECHNOLOGY_HSUPA: 706 return TelephonyManager.NETWORK_TYPE_HSUPA; 707 case ServiceState.RADIO_TECHNOLOGY_HSPA: 708 return TelephonyManager.NETWORK_TYPE_HSPA; 709 case ServiceState.RADIO_TECHNOLOGY_IS95A: 710 case ServiceState.RADIO_TECHNOLOGY_IS95B: 711 return TelephonyManager.NETWORK_TYPE_CDMA; 712 case ServiceState.RADIO_TECHNOLOGY_1xRTT: 713 return TelephonyManager.NETWORK_TYPE_1xRTT; 714 case ServiceState.RADIO_TECHNOLOGY_EVDO_0: 715 return TelephonyManager.NETWORK_TYPE_EVDO_0; 716 case ServiceState.RADIO_TECHNOLOGY_EVDO_A: 717 return TelephonyManager.NETWORK_TYPE_EVDO_A; 718 default: 719 return TelephonyManager.NETWORK_TYPE_UNKNOWN; 720 } 721 } 722 723 /** 724 * @return true if a ICC card is present 725 */ 726 public boolean hasIccCard() { 727 return mPhone.getIccCard().hasIccCard(); 728 } 729 } 730