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