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