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