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.text.TextUtils; 31 import android.util.Log; 32 33 import com.android.internal.telephony.DefaultPhoneNotifier; 34 import com.android.internal.telephony.IccCard; 35 import com.android.internal.telephony.ITelephony; 36 import com.android.internal.telephony.Phone; 37 38 import static com.android.internal.telephony.RILConstants.GSM_PHONE; 39 import static com.android.internal.telephony.RILConstants.CDMA_PHONE; 40 41 import java.util.List; 42 import java.util.ArrayList; 43 44 /** 45 * Implementation of the ITelephony interface. 46 */ 47 public class PhoneInterfaceManager extends ITelephony.Stub { 48 private static final String LOG_TAG = "PhoneInterfaceManager"; 49 private static final boolean DBG = (PhoneApp.DBG_LEVEL >= 2); 50 51 // Message codes used with mMainThreadHandler 52 private static final int CMD_HANDLE_PIN_MMI = 1; 53 private static final int CMD_HANDLE_NEIGHBORING_CELL = 2; 54 private static final int EVENT_NEIGHBORING_CELL_DONE = 3; 55 private static final int CMD_ANSWER_RINGING_CALL = 4; 56 private static final int CMD_END_CALL = 5; // not used yet 57 private static final int CMD_SILENCE_RINGER = 6; 58 59 PhoneApp mApp; 60 Phone mPhone; 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 MainThreadRequest(Object argument)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 handleMessage(Message msg)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 default: 139 Log.w(LOG_TAG, "MainThreadHandler: unexpected message code: " + msg.what); 140 break; 141 } 142 } 143 } 144 145 /** 146 * Posts the specified command to be executed on the main thread, 147 * waits for the request to complete, and returns the result. 148 * @see sendRequestAsync 149 */ sendRequest(int command, Object argument)150 private Object sendRequest(int command, Object argument) { 151 if (Looper.myLooper() == mMainThreadHandler.getLooper()) { 152 throw new RuntimeException("This method will deadlock if called from the main thread."); 153 } 154 155 MainThreadRequest request = new MainThreadRequest(argument); 156 Message msg = mMainThreadHandler.obtainMessage(command, request); 157 msg.sendToTarget(); 158 159 // Wait for the request to complete 160 synchronized (request) { 161 while (request.result == null) { 162 try { 163 request.wait(); 164 } catch (InterruptedException e) { 165 // Do nothing, go back and wait until the request is complete 166 } 167 } 168 } 169 return request.result; 170 } 171 172 /** 173 * Asynchronous ("fire and forget") version of sendRequest(): 174 * Posts the specified command to be executed on the main thread, and 175 * returns immediately. 176 * @see sendRequest 177 */ sendRequestAsync(int command)178 private void sendRequestAsync(int command) { 179 mMainThreadHandler.sendEmptyMessage(command); 180 } 181 PhoneInterfaceManager(PhoneApp app, Phone phone)182 public PhoneInterfaceManager(PhoneApp app, Phone phone) { 183 mApp = app; 184 mPhone = phone; 185 mMainThreadHandler = new MainThreadHandler(); 186 publish(); 187 } 188 publish()189 private void publish() { 190 if (DBG) log("publish: " + this); 191 192 ServiceManager.addService("phone", this); 193 } 194 195 // 196 // Implementation of the ITelephony interface. 197 // 198 dial(String number)199 public void dial(String number) { 200 if (DBG) log("dial: " + number); 201 // No permission check needed here: This is just a wrapper around the 202 // ACTION_DIAL intent, which is available to any app since it puts up 203 // the UI before it does anything. 204 205 String url = createTelUrl(number); 206 if (url == null) { 207 return; 208 } 209 210 // PENDING: should we just silently fail if phone is offhook or ringing? 211 Phone.State state = mPhone.getState(); 212 if (state != Phone.State.OFFHOOK && state != Phone.State.RINGING) { 213 Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(url)); 214 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 215 mApp.startActivity(intent); 216 } 217 } 218 call(String number)219 public void call(String number) { 220 if (DBG) log("call: " + number); 221 222 // This is just a wrapper around the ACTION_CALL intent, but we still 223 // need to do a permission check since we're calling startActivity() 224 // from the context of the phone app. 225 enforceCallPermission(); 226 227 String url = createTelUrl(number); 228 if (url == null) { 229 return; 230 } 231 232 Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse(url)); 233 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 234 intent.setClassName(mApp, PhoneApp.getCallScreenClassName()); 235 mApp.startActivity(intent); 236 } 237 showCallScreenInternal(boolean specifyInitialDialpadState, boolean initialDialpadState)238 private boolean showCallScreenInternal(boolean specifyInitialDialpadState, 239 boolean initialDialpadState) { 240 if (isIdle()) { 241 return false; 242 } 243 // If the phone isn't idle then go to the in-call screen 244 long callingId = Binder.clearCallingIdentity(); 245 try { 246 Intent intent; 247 if (specifyInitialDialpadState) { 248 intent = PhoneApp.createInCallIntent(initialDialpadState); 249 } else { 250 intent = PhoneApp.createInCallIntent(); 251 } 252 mApp.startActivity(intent); 253 } finally { 254 Binder.restoreCallingIdentity(callingId); 255 } 256 return true; 257 } 258 259 // Show the in-call screen without specifying the initial dialpad state. showCallScreen()260 public boolean showCallScreen() { 261 return showCallScreenInternal(false, false); 262 } 263 264 // The variation of showCallScreen() that specifies the initial dialpad state. 265 // (Ideally this would be called showCallScreen() too, just with a different 266 // signature, but AIDL doesn't allow that.) showCallScreenWithDialpad(boolean showDialpad)267 public boolean showCallScreenWithDialpad(boolean showDialpad) { 268 return showCallScreenInternal(true, showDialpad); 269 } 270 271 // TODO: Watch out: it's dangerous to call into the telephony code 272 // directly from here (we're running in a random binder thread, but 273 // the telephony code expects to always be called from the phone app 274 // main thread.) We should instead wrap this in a sendRequest() call 275 // (just like with answerRingingCall() / answerRingingCallInternal()). endCall()276 public boolean endCall() { 277 enforceCallPermission(); 278 boolean hungUp = PhoneUtils.hangup(mPhone); 279 if (DBG) log("endCall: " + (hungUp ? "hung up!" : "no call to hang up")); 280 return hungUp; 281 } 282 answerRingingCall()283 public void answerRingingCall() { 284 if (DBG) log("answerRingingCall..."); 285 // TODO: there should eventually be a separate "ANSWER_PHONE" permission, 286 // but that can probably wait till the big TelephonyManager API overhaul. 287 // For now, protect this call with the MODIFY_PHONE_STATE permission. 288 enforceModifyPermission(); 289 sendRequestAsync(CMD_ANSWER_RINGING_CALL); 290 } 291 292 /** 293 * Make the actual telephony calls to implement answerRingingCall(). 294 * This should only be called from the main thread of the Phone app. 295 * @see answerRingingCall 296 * 297 * TODO: it would be nice to return true if we answered the call, or 298 * false if there wasn't actually a ringing incoming call, or some 299 * other error occurred. (In other words, pass back the return value 300 * from PhoneUtils.answerCall() or PhoneUtils.answerAndEndActive().) 301 * But that would require calling this method via sendRequest() rather 302 * than sendRequestAsync(), and right now we don't actually *need* that 303 * return value, so let's just return void for now. 304 */ answerRingingCallInternal()305 private void answerRingingCallInternal() { 306 final boolean hasRingingCall = !mPhone.getRingingCall().isIdle(); 307 if (hasRingingCall) { 308 final boolean hasActiveCall = !mPhone.getForegroundCall().isIdle(); 309 final boolean hasHoldingCall = !mPhone.getBackgroundCall().isIdle(); 310 if (hasActiveCall && hasHoldingCall) { 311 // Both lines are in use! 312 // TODO: provide a flag to let the caller specify what 313 // policy to use if both lines are in use. (The current 314 // behavior is hardwired to "answer incoming, end ongoing", 315 // which is how the CALL button is specced to behave.) 316 PhoneUtils.answerAndEndActive(mPhone); 317 return; 318 } else { 319 // answerCall() will automatically hold the current active 320 // call, if there is one. 321 PhoneUtils.answerCall(mPhone); 322 return; 323 } 324 } else { 325 // No call was ringing. 326 return; 327 } 328 } 329 silenceRinger()330 public void silenceRinger() { 331 if (DBG) log("silenceRinger..."); 332 // TODO: find a more appropriate permission to check here. 333 // (That can probably wait till the big TelephonyManager API overhaul. 334 // For now, protect this call with the MODIFY_PHONE_STATE permission.) 335 enforceModifyPermission(); 336 sendRequestAsync(CMD_SILENCE_RINGER); 337 } 338 339 /** 340 * Internal implemenation of silenceRinger(). 341 * This should only be called from the main thread of the Phone app. 342 * @see silenceRinger 343 */ silenceRingerInternal()344 private void silenceRingerInternal() { 345 if ((mPhone.getState() == Phone.State.RINGING) 346 && mApp.notifier.isRinging()) { 347 // Ringer is actually playing, so silence it. 348 if (DBG) log("silenceRingerInternal: silencing..."); 349 PhoneUtils.setAudioControlState(PhoneUtils.AUDIO_IDLE); 350 mApp.notifier.silenceRinger(); 351 } 352 } 353 isOffhook()354 public boolean isOffhook() { 355 return (mPhone.getState() == Phone.State.OFFHOOK); 356 } 357 isRinging()358 public boolean isRinging() { 359 return (mPhone.getState() == Phone.State.RINGING); 360 } 361 isIdle()362 public boolean isIdle() { 363 return (mPhone.getState() == Phone.State.IDLE); 364 } 365 isSimPinEnabled()366 public boolean isSimPinEnabled() { 367 enforceReadPermission(); 368 return (PhoneApp.getInstance().isSimPinEnabled()); 369 } 370 supplyPin(String pin)371 public boolean supplyPin(String pin) { 372 enforceModifyPermission(); 373 final CheckSimPin checkSimPin = new CheckSimPin(mPhone.getIccCard()); 374 checkSimPin.start(); 375 return checkSimPin.checkPin(pin); 376 } 377 378 /** 379 * Helper thread to turn async call to {@link SimCard#supplyPin} into 380 * a synchronous one. 381 */ 382 private static class CheckSimPin extends Thread { 383 384 private final IccCard mSimCard; 385 386 private boolean mDone = false; 387 private boolean mResult = false; 388 389 // For replies from SimCard interface 390 private Handler mHandler; 391 392 // For async handler to identify request type 393 private static final int SUPPLY_PIN_COMPLETE = 100; 394 CheckSimPin(IccCard simCard)395 public CheckSimPin(IccCard simCard) { 396 mSimCard = simCard; 397 } 398 399 @Override run()400 public void run() { 401 Looper.prepare(); 402 synchronized (CheckSimPin.this) { 403 mHandler = new Handler() { 404 @Override 405 public void handleMessage(Message msg) { 406 AsyncResult ar = (AsyncResult) msg.obj; 407 switch (msg.what) { 408 case SUPPLY_PIN_COMPLETE: 409 Log.d(LOG_TAG, "SUPPLY_PIN_COMPLETE"); 410 synchronized (CheckSimPin.this) { 411 mResult = (ar.exception == null); 412 mDone = true; 413 CheckSimPin.this.notifyAll(); 414 } 415 break; 416 } 417 } 418 }; 419 CheckSimPin.this.notifyAll(); 420 } 421 Looper.loop(); 422 } 423 checkPin(String pin)424 synchronized boolean checkPin(String pin) { 425 426 while (mHandler == null) { 427 try { 428 wait(); 429 } catch (InterruptedException e) { 430 Thread.currentThread().interrupt(); 431 } 432 } 433 Message callback = Message.obtain(mHandler, SUPPLY_PIN_COMPLETE); 434 435 mSimCard.supplyPin(pin, callback); 436 437 while (!mDone) { 438 try { 439 Log.d(LOG_TAG, "wait for done"); 440 wait(); 441 } catch (InterruptedException e) { 442 // Restore the interrupted status 443 Thread.currentThread().interrupt(); 444 } 445 } 446 Log.d(LOG_TAG, "done"); 447 return mResult; 448 } 449 } 450 updateServiceLocation()451 public void updateServiceLocation() { 452 // No permission check needed here: this call is harmless, and it's 453 // needed for the ServiceState.requestStateUpdate() call (which is 454 // already intentionally exposed to 3rd parties.) 455 mPhone.updateServiceLocation(null); 456 } 457 isRadioOn()458 public boolean isRadioOn() { 459 return mPhone.getServiceState().getState() != ServiceState.STATE_POWER_OFF; 460 } 461 toggleRadioOnOff()462 public void toggleRadioOnOff() { 463 enforceModifyPermission(); 464 mPhone.setRadioPower(!isRadioOn()); 465 } setRadio(boolean turnOn)466 public boolean setRadio(boolean turnOn) { 467 enforceModifyPermission(); 468 if ((mPhone.getServiceState().getState() != ServiceState.STATE_POWER_OFF) != turnOn) { 469 toggleRadioOnOff(); 470 } 471 return true; 472 } 473 enableDataConnectivity()474 public boolean enableDataConnectivity() { 475 enforceModifyPermission(); 476 return mPhone.enableDataConnectivity(); 477 } 478 enableApnType(String type)479 public int enableApnType(String type) { 480 enforceModifyPermission(); 481 return mPhone.enableApnType(type); 482 } 483 disableApnType(String type)484 public int disableApnType(String type) { 485 enforceModifyPermission(); 486 return mPhone.disableApnType(type); 487 } 488 disableDataConnectivity()489 public boolean disableDataConnectivity() { 490 enforceModifyPermission(); 491 return mPhone.disableDataConnectivity(); 492 } 493 isDataConnectivityPossible()494 public boolean isDataConnectivityPossible() { 495 return mPhone.isDataConnectivityPossible(); 496 } 497 handlePinMmi(String dialString)498 public boolean handlePinMmi(String dialString) { 499 enforceModifyPermission(); 500 return (Boolean) sendRequest(CMD_HANDLE_PIN_MMI, dialString); 501 } 502 cancelMissedCallsNotification()503 public void cancelMissedCallsNotification() { 504 enforceModifyPermission(); 505 NotificationMgr.getDefault().cancelMissedCallNotification(); 506 } 507 getCallState()508 public int getCallState() { 509 return DefaultPhoneNotifier.convertCallState(mPhone.getState()); 510 } 511 getDataState()512 public int getDataState() { 513 return DefaultPhoneNotifier.convertDataState(mPhone.getDataConnectionState()); 514 } 515 getDataActivity()516 public int getDataActivity() { 517 return DefaultPhoneNotifier.convertDataActivityState(mPhone.getDataActivityState()); 518 } 519 getCellLocation()520 public Bundle getCellLocation() { 521 try { 522 mApp.enforceCallingOrSelfPermission( 523 android.Manifest.permission.ACCESS_FINE_LOCATION, null); 524 } catch (SecurityException e) { 525 // If we have ACCESS_FINE_LOCATION permission, skip the check for ACCESS_COARSE_LOCATION 526 // A failure should throw the SecurityException from ACCESS_COARSE_LOCATION since this 527 // is the weaker precondition 528 mApp.enforceCallingOrSelfPermission( 529 android.Manifest.permission.ACCESS_COARSE_LOCATION, null); 530 } 531 Bundle data = new Bundle(); 532 mPhone.getCellLocation().fillInNotifierBundle(data); 533 return data; 534 } 535 enableLocationUpdates()536 public void enableLocationUpdates() { 537 mApp.enforceCallingOrSelfPermission( 538 android.Manifest.permission.CONTROL_LOCATION_UPDATES, null); 539 mPhone.enableLocationUpdates(); 540 } 541 disableLocationUpdates()542 public void disableLocationUpdates() { 543 mApp.enforceCallingOrSelfPermission( 544 android.Manifest.permission.CONTROL_LOCATION_UPDATES, null); 545 mPhone.disableLocationUpdates(); 546 } 547 548 @SuppressWarnings("unchecked") getNeighboringCellInfo()549 public List<NeighboringCellInfo> getNeighboringCellInfo() { 550 try { 551 mApp.enforceCallingOrSelfPermission( 552 android.Manifest.permission.ACCESS_FINE_LOCATION, null); 553 } catch (SecurityException e) { 554 // If we have ACCESS_FINE_LOCATION permission, skip the check 555 // for ACCESS_COARSE_LOCATION 556 // A failure should throw the SecurityException from 557 // ACCESS_COARSE_LOCATION since this is the weaker precondition 558 mApp.enforceCallingOrSelfPermission( 559 android.Manifest.permission.ACCESS_COARSE_LOCATION, null); 560 } 561 562 ArrayList<NeighboringCellInfo> cells = null; 563 564 try { 565 cells = (ArrayList<NeighboringCellInfo>) sendRequest( 566 CMD_HANDLE_NEIGHBORING_CELL, null); 567 } catch (RuntimeException e) { 568 } 569 570 return (List <NeighboringCellInfo>) cells; 571 } 572 573 574 // 575 // Internal helper methods. 576 // 577 578 /** 579 * Make sure the caller has the READ_PHONE_STATE permission. 580 * 581 * @throws SecurityException if the caller does not have the required permission 582 */ enforceReadPermission()583 private void enforceReadPermission() { 584 mApp.enforceCallingOrSelfPermission(android.Manifest.permission.READ_PHONE_STATE, null); 585 } 586 587 /** 588 * Make sure the caller has the MODIFY_PHONE_STATE permission. 589 * 590 * @throws SecurityException if the caller does not have the required permission 591 */ enforceModifyPermission()592 private void enforceModifyPermission() { 593 mApp.enforceCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE, null); 594 } 595 596 /** 597 * Make sure the caller has the CALL_PHONE permission. 598 * 599 * @throws SecurityException if the caller does not have the required permission 600 */ enforceCallPermission()601 private void enforceCallPermission() { 602 mApp.enforceCallingOrSelfPermission(android.Manifest.permission.CALL_PHONE, null); 603 } 604 605 createTelUrl(String number)606 private String createTelUrl(String number) { 607 if (TextUtils.isEmpty(number)) { 608 return null; 609 } 610 611 StringBuilder buf = new StringBuilder("tel:"); 612 buf.append(number); 613 return buf.toString(); 614 } 615 log(String msg)616 private void log(String msg) { 617 Log.d(LOG_TAG, "[PhoneIntfMgr] " + msg); 618 } 619 getActivePhoneType()620 public int getActivePhoneType() { 621 if(mPhone.getPhoneName().equals("CDMA")) { 622 return CDMA_PHONE; 623 } else { 624 return GSM_PHONE; 625 } 626 } 627 628 /** 629 * Returns the CDMA ERI icon index to display 630 */ getCdmaEriIconIndex()631 public int getCdmaEriIconIndex() { 632 return mPhone.getCdmaEriIconIndex(); 633 } 634 635 /** 636 * Returns the CDMA ERI icon mode, 637 * 0 - ON 638 * 1 - FLASHING 639 */ getCdmaEriIconMode()640 public int getCdmaEriIconMode() { 641 return mPhone.getCdmaEriIconMode(); 642 } 643 644 /** 645 * Returns the CDMA ERI text, 646 */ getCdmaEriText()647 public String getCdmaEriText() { 648 return mPhone.getCdmaEriText(); 649 } 650 651 /** 652 * Returns the unread count of voicemails 653 */ getVoiceMessageCount()654 public int getVoiceMessageCount() { 655 return mPhone.getVoiceMessageCount(); 656 } 657 658 } 659