ImsPhoneCallTracker.java revision 64e62340aae85179a6468ccac4a401900eb4dc2f
1/* 2 * Copyright (C) 2013 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 17package com.android.internal.telephony.imsphone; 18 19import android.app.PendingIntent; 20import android.content.BroadcastReceiver; 21import android.content.Context; 22import android.content.Intent; 23import android.content.IntentFilter; 24import android.content.SharedPreferences; 25import android.os.AsyncResult; 26import android.os.Handler; 27import android.os.Message; 28import android.os.Registrant; 29import android.os.RegistrantList; 30import android.os.SystemProperties; 31import android.preference.PreferenceManager; 32import android.telecomm.VideoCallProfile; 33import android.telephony.DisconnectCause; 34import android.telephony.PhoneNumberUtils; 35import android.telephony.ServiceState; 36import android.telephony.Rlog; 37 38import com.android.internal.telephony.Call; 39import com.android.internal.telephony.CallStateException; 40import com.android.internal.telephony.CallTracker; 41import com.android.internal.telephony.CommandException; 42import com.android.internal.telephony.CommandsInterface; 43import com.android.internal.telephony.Connection; 44import com.android.internal.telephony.EventLogTags; 45import com.android.internal.telephony.Phone; 46import com.android.internal.telephony.PhoneBase; 47import com.android.internal.telephony.PhoneConstants; 48import com.android.internal.telephony.TelephonyProperties; 49 50import com.android.ims.ImsCall; 51import com.android.ims.ImsCallProfile; 52import com.android.ims.ImsConnectionStateListener; 53import com.android.ims.ImsException; 54import com.android.ims.ImsManager; 55import com.android.ims.ImsReasonInfo; 56import com.android.ims.ImsServiceClass; 57import com.android.ims.ImsUtInterface; 58 59 60import java.io.FileDescriptor; 61import java.io.PrintWriter; 62import java.util.List; 63import java.util.ArrayList; 64 65/** 66 * {@hide} 67 */ 68public final class ImsPhoneCallTracker extends CallTracker { 69 static final String LOG_TAG = "ImsPhoneCallTracker"; 70 71 private static final boolean DBG = true; 72 73 private BroadcastReceiver mReceiver = new BroadcastReceiver() { 74 @Override 75 public void onReceive(Context context, Intent intent) { 76 if (intent.getAction().equals(ImsManager.ACTION_IMS_INCOMING_CALL)) { 77 if (DBG) log("onReceive : incoming call intent"); 78 79 if (mImsManager == null) return; 80 81 if (mServiceId < 0) return; 82 83 try { 84 // Network initiated USSD will be treated by mImsUssdListener 85 boolean isUssd = intent.getBooleanExtra(ImsManager.EXTRA_USSD, false); 86 if (isUssd) { 87 if (DBG) log("onReceive : USSD"); 88 mUssdSession = mImsManager.takeCall(mServiceId, intent, mImsUssdListener); 89 if (mUssdSession != null) { 90 mUssdSession.accept(); 91 } 92 return; 93 } 94 95 // Normal MT call 96 ImsCall imsCall = mImsManager.takeCall(mServiceId, intent, mImsCallListener); 97 98 ImsPhoneConnection conn = new ImsPhoneConnection(mPhone.getContext(), imsCall, 99 ImsPhoneCallTracker.this, mRingingCall); 100 addConnection(conn); 101 102 if ((mForegroundCall.getState() != ImsPhoneCall.State.IDLE) || 103 (mBackgroundCall.getState() != ImsPhoneCall.State.IDLE)) { 104 conn.update(imsCall, ImsPhoneCall.State.WAITING); 105 } 106 107 mPhone.notifyNewRingingConnection(conn); 108 mPhone.notifyIncomingRing(); 109 110 updatePhoneState(); 111 mPhone.notifyPreciseCallStateChanged(); 112 } catch (ImsException e) { 113 loge("onReceive : exception " + e); 114 } 115 } 116 } 117 }; 118 119 //***** Constants 120 121 static final int MAX_CONNECTIONS = 7; 122 static final int MAX_CONNECTIONS_PER_CALL = 5; 123 124 private static final int EVENT_HANGUP_PENDINGMO = 18; 125 private static final int EVENT_RESUME_BACKGROUND = 19; 126 private static final int EVENT_DIAL_PENDINGMO = 20; 127 128 private static final int TIMEOUT_HANGUP_PENDINGMO = 500; 129 130 //***** Instance Variables 131 private ArrayList<ImsPhoneConnection> mConnections = new ArrayList<ImsPhoneConnection>(); 132 private RegistrantList mVoiceCallEndedRegistrants = new RegistrantList(); 133 private RegistrantList mVoiceCallStartedRegistrants = new RegistrantList(); 134 135 ImsPhoneCall mRingingCall = new ImsPhoneCall(this); 136 ImsPhoneCall mForegroundCall = new ImsPhoneCall(this); 137 ImsPhoneCall mBackgroundCall = new ImsPhoneCall(this); 138 ImsPhoneCall mHandoverCall = new ImsPhoneCall(this); 139 140 private ImsPhoneConnection mPendingMO; 141 private int mClirMode = CommandsInterface.CLIR_DEFAULT; 142 private Object mSyncHold = new Object(); 143 144 private ImsCall mUssdSession = null; 145 private Message mPendingUssd = null; 146 147 ImsPhone mPhone; 148 149 private boolean mDesiredMute = false; // false = mute off 150 private boolean mOnHoldToneStarted = false; 151 152 PhoneConstants.State mState = PhoneConstants.State.IDLE; 153 154 private ImsManager mImsManager; 155 private int mServiceId = -1; 156 157 private Call.SrvccState mSrvccState = Call.SrvccState.NONE; 158 159 //***** Events 160 161 162 //***** Constructors 163 164 ImsPhoneCallTracker(ImsPhone phone) { 165 this.mPhone = phone; 166 167 IntentFilter intentfilter = new IntentFilter(); 168 intentfilter.addAction(ImsManager.ACTION_IMS_INCOMING_CALL); 169 mPhone.getContext().registerReceiver(mReceiver, intentfilter); 170 171 Thread t = new Thread() { 172 public void run() { 173 getImsService(); 174 } 175 }; 176 t.start(); 177 } 178 179 private PendingIntent createIncomingCallPendingIntent() { 180 Intent intent = new Intent(ImsManager.ACTION_IMS_INCOMING_CALL); 181 return PendingIntent.getBroadcast(mPhone.getContext(), 0, intent, 182 PendingIntent.FLAG_UPDATE_CURRENT); 183 } 184 185 private void getImsService() { 186 if (DBG) log("getImsService"); 187 mImsManager = ImsManager.getInstance(mPhone.getContext(), mPhone.getSubId()); 188 try { 189 mServiceId = mImsManager.open(ImsServiceClass.MMTEL, 190 createIncomingCallPendingIntent(), 191 mImsConnectionStateListener); 192 } catch (ImsException e) { 193 loge("getImsService: " + e); 194 //Leave mImsManager as null, then CallStateException will be thrown when dialing 195 mImsManager = null; 196 } 197 } 198 199 public void dispose() { 200 if (DBG) log("dispose"); 201 mRingingCall.dispose(); 202 mBackgroundCall.dispose(); 203 mForegroundCall.dispose(); 204 mHandoverCall.dispose(); 205 206 clearDisconnected(); 207 mPhone.getContext().unregisterReceiver(mReceiver); 208 } 209 210 @Override 211 protected void finalize() { 212 log("ImsPhoneCallTracker finalized"); 213 } 214 215 //***** Instance Methods 216 217 //***** Public Methods 218 @Override 219 public void registerForVoiceCallStarted(Handler h, int what, Object obj) { 220 Registrant r = new Registrant(h, what, obj); 221 mVoiceCallStartedRegistrants.add(r); 222 } 223 224 @Override 225 public void unregisterForVoiceCallStarted(Handler h) { 226 mVoiceCallStartedRegistrants.remove(h); 227 } 228 229 @Override 230 public void registerForVoiceCallEnded(Handler h, int what, Object obj) { 231 Registrant r = new Registrant(h, what, obj); 232 mVoiceCallEndedRegistrants.add(r); 233 } 234 235 @Override 236 public void unregisterForVoiceCallEnded(Handler h) { 237 mVoiceCallEndedRegistrants.remove(h); 238 } 239 240 Connection 241 dial(String dialString, int videoState) throws CallStateException { 242 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mPhone.getContext()); 243 int oirMode = sp.getInt(PhoneBase.CLIR_KEY, CommandsInterface.CLIR_DEFAULT); 244 return dial(dialString, oirMode, videoState); 245 } 246 247 /** 248 * oirMode is one of the CLIR_ constants 249 */ 250 synchronized Connection 251 dial(String dialString, int clirMode, int videoState) throws CallStateException { 252 if (DBG) log("dial clirMode=" + clirMode); 253 254 // note that this triggers call state changed notif 255 clearDisconnected(); 256 257 if (mImsManager == null) { 258 throw new CallStateException("service not available"); 259 } 260 261 if (!canDial()) { 262 throw new CallStateException("cannot dial in current state"); 263 } 264 265 boolean holdBeforeDial = false; 266 267 // The new call must be assigned to the foreground call. 268 // That call must be idle, so place anything that's 269 // there on hold 270 if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) { 271 if (mBackgroundCall.getState() != ImsPhoneCall.State.IDLE) { 272 //we should have failed in !canDial() above before we get here 273 throw new CallStateException("cannot dial in current state"); 274 } 275 // foreground call is empty for the newly dialed connection 276 holdBeforeDial = true; 277 switchWaitingOrHoldingAndActive(); 278 } 279 280 ImsPhoneCall.State fgState = ImsPhoneCall.State.IDLE; 281 ImsPhoneCall.State bgState = ImsPhoneCall.State.IDLE; 282 283 mClirMode = clirMode; 284 285 synchronized (mSyncHold) { 286 if (holdBeforeDial) { 287 fgState = mForegroundCall.getState(); 288 bgState = mBackgroundCall.getState(); 289 290 //holding foreground call failed 291 if (fgState == ImsPhoneCall.State.ACTIVE) { 292 throw new CallStateException("cannot dial in current state"); 293 } 294 295 //holding foreground call succeeded 296 if (bgState == ImsPhoneCall.State.HOLDING) { 297 holdBeforeDial = false; 298 } 299 } 300 301 mPendingMO = new ImsPhoneConnection(mPhone.getContext(), 302 checkForTestEmergencyNumber(dialString), this, mForegroundCall); 303 } 304 addConnection(mPendingMO); 305 306 if (!holdBeforeDial) { 307 dialInternal(mPendingMO, clirMode, videoState); 308 } 309 310 updatePhoneState(); 311 mPhone.notifyPreciseCallStateChanged(); 312 313 return mPendingMO; 314 } 315 316 private void dialInternal(ImsPhoneConnection conn, int clirMode, int videoState) { 317 if (conn == null) { 318 return; 319 } 320 321 if (conn.getAddress()== null || conn.getAddress().length() == 0 322 || conn.getAddress().indexOf(PhoneNumberUtils.WILD) >= 0) { 323 // Phone number is invalid 324 conn.setDisconnectCause(DisconnectCause.INVALID_NUMBER); 325 sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO); 326 return; 327 } 328 329 // Always unmute when initiating a new call 330 setMute(false); 331 int serviceType = PhoneNumberUtils.isEmergencyNumber(conn.getAddress()) ? 332 ImsCallProfile.SERVICE_TYPE_EMERGENCY : ImsCallProfile.SERVICE_TYPE_NORMAL; 333 int callType = ImsCallProfile.getCallTypeFromVideoState(videoState); 334 //TODO(vt): Is this sufficient? At what point do we know the video state of the call? 335 conn.setVideoState(videoState); 336 337 try { 338 String[] callees = new String[] { conn.getAddress() }; 339 ImsCallProfile profile = mImsManager.createCallProfile(mServiceId, 340 serviceType, callType); 341 profile.setCallExtraInt(ImsCallProfile.EXTRA_OIR, clirMode); 342 343 ImsCall imsCall = mImsManager.makeCall(mServiceId, profile, 344 callees, mImsCallListener); 345 conn.setImsCall(imsCall); 346 } catch (ImsException e) { 347 loge("dialInternal : " + e); 348 conn.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED); 349 sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO); 350 } 351 } 352 353 void 354 acceptCall () throws CallStateException { 355 if (DBG) log("acceptCall"); 356 357 if (mForegroundCall.getState().isAlive() 358 && mBackgroundCall.getState().isAlive()) { 359 throw new CallStateException("cannot accept call"); 360 } 361 362 if ((mRingingCall.getState() == ImsPhoneCall.State.WAITING) 363 && mForegroundCall.getState().isAlive()) { 364 setMute(false); 365 switchWaitingOrHoldingAndActive(); 366 } else if (mRingingCall.getState().isRinging()) { 367 if (DBG) log("acceptCall: incoming..."); 368 // Always unmute when answering a new call 369 setMute(false); 370 try { 371 ImsCall imsCall = mRingingCall.getImsCall(); 372 if (imsCall != null) { 373 imsCall.accept(); 374 } else { 375 throw new CallStateException("no valid ims call"); 376 } 377 } catch (ImsException e) { 378 throw new CallStateException("cannot accept call"); 379 } 380 } else { 381 throw new CallStateException("phone not ringing"); 382 } 383 } 384 385 void 386 rejectCall () throws CallStateException { 387 if (DBG) log("rejectCall"); 388 389 if (mRingingCall.getState().isRinging()) { 390 hangup(mRingingCall); 391 } else { 392 throw new CallStateException("phone not ringing"); 393 } 394 } 395 396 void 397 switchWaitingOrHoldingAndActive() throws CallStateException { 398 if (DBG) log("switchWaitingOrHoldingAndActive"); 399 400 if (mRingingCall.getState() == ImsPhoneCall.State.INCOMING) { 401 throw new CallStateException("cannot be in the incoming state"); 402 } 403 404 if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) { 405 ImsCall imsCall = mForegroundCall.getImsCall(); 406 if (imsCall == null) { 407 throw new CallStateException("no ims call"); 408 } 409 410 mForegroundCall.switchWith(mBackgroundCall); 411 412 try { 413 imsCall.hold(); 414 } catch (ImsException e) { 415 mForegroundCall.switchWith(mBackgroundCall); 416 throw new CallStateException(e.getMessage()); 417 } 418 } else if (mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING) { 419 resumeWaitingOrHolding(); 420 } 421 } 422 423 void 424 conference() { 425 if (DBG) log("conference"); 426 427 ImsCall fgImsCall = mForegroundCall.getImsCall(); 428 if (fgImsCall == null) { 429 log("conference no foreground ims call"); 430 return; 431 } 432 433 ImsCall bgImsCall = mBackgroundCall.getImsCall(); 434 if (bgImsCall == null) { 435 log("conference no background ims call"); 436 return; 437 } 438 439 try { 440 fgImsCall.merge(bgImsCall); 441 } catch (ImsException e) { 442 log("conference " + e.getMessage()); 443 } 444 } 445 446 void 447 explicitCallTransfer() { 448 //TODO : implement 449 } 450 451 void 452 clearDisconnected() { 453 if (DBG) log("clearDisconnected"); 454 455 internalClearDisconnected(); 456 457 updatePhoneState(); 458 mPhone.notifyPreciseCallStateChanged(); 459 } 460 461 boolean 462 canConference() { 463 return mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE 464 && mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING 465 && !mBackgroundCall.isFull() 466 && !mForegroundCall.isFull(); 467 } 468 469 boolean 470 canDial() { 471 boolean ret; 472 int serviceState = mPhone.getServiceState().getState(); 473 String disableCall = SystemProperties.get( 474 TelephonyProperties.PROPERTY_DISABLE_CALL, "false"); 475 476 ret = (serviceState != ServiceState.STATE_POWER_OFF) 477 && mPendingMO == null 478 && !mRingingCall.isRinging() 479 && !disableCall.equals("true") 480 && (!mForegroundCall.getState().isAlive() 481 || !mBackgroundCall.getState().isAlive()); 482 483 return ret; 484 } 485 486 boolean 487 canTransfer() { 488 return mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE 489 && mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING; 490 } 491 492 //***** Private Instance Methods 493 494 private void 495 internalClearDisconnected() { 496 mRingingCall.clearDisconnected(); 497 mForegroundCall.clearDisconnected(); 498 mBackgroundCall.clearDisconnected(); 499 mHandoverCall.clearDisconnected(); 500 } 501 502 private void 503 updatePhoneState() { 504 PhoneConstants.State oldState = mState; 505 506 if (mRingingCall.isRinging()) { 507 mState = PhoneConstants.State.RINGING; 508 } else if (mPendingMO != null || 509 !(mForegroundCall.isIdle() && mBackgroundCall.isIdle())) { 510 mState = PhoneConstants.State.OFFHOOK; 511 } else { 512 mState = PhoneConstants.State.IDLE; 513 } 514 515 if (mState == PhoneConstants.State.IDLE && oldState != mState) { 516 mVoiceCallEndedRegistrants.notifyRegistrants( 517 new AsyncResult(null, null, null)); 518 } else if (oldState == PhoneConstants.State.IDLE && oldState != mState) { 519 mVoiceCallStartedRegistrants.notifyRegistrants ( 520 new AsyncResult(null, null, null)); 521 } 522 523 if (DBG) log("updatePhoneState oldState=" + oldState + ", newState=" + mState); 524 525 if (mState != oldState) { 526 mPhone.notifyPhoneStateChanged(); 527 } 528 } 529 530 private void 531 handleRadioNotAvailable() { 532 // handlePollCalls will clear out its 533 // call list when it gets the CommandException 534 // error result from this 535 pollCallsWhenSafe(); 536 } 537 538 private void 539 dumpState() { 540 List l; 541 542 log("Phone State:" + mState); 543 544 log("Ringing call: " + mRingingCall.toString()); 545 546 l = mRingingCall.getConnections(); 547 for (int i = 0, s = l.size(); i < s; i++) { 548 log(l.get(i).toString()); 549 } 550 551 log("Foreground call: " + mForegroundCall.toString()); 552 553 l = mForegroundCall.getConnections(); 554 for (int i = 0, s = l.size(); i < s; i++) { 555 log(l.get(i).toString()); 556 } 557 558 log("Background call: " + mBackgroundCall.toString()); 559 560 l = mBackgroundCall.getConnections(); 561 for (int i = 0, s = l.size(); i < s; i++) { 562 log(l.get(i).toString()); 563 } 564 565 } 566 567 //***** Called from ImsPhone 568 569 /*package*/ void 570 setMute(boolean mute) { 571 mDesiredMute = mute; 572 mForegroundCall.setMute(mute); 573 } 574 575 /*package*/ boolean 576 getMute() { 577 return mDesiredMute; 578 } 579 580 /*package*/ void 581 sendDtmf(char c) { 582 if (DBG) log("sendDtmf"); 583 584 ImsCall imscall = mForegroundCall.getImsCall(); 585 if (imscall != null) { 586 imscall.sendDtmf(convertDtmf(c)); 587 } 588 } 589 590 private int convertDtmf(char c) { 591 int code = c - '0'; 592 if ((code < 0) || (code > 9)) { 593 switch (c) { 594 case '*': return 10; 595 case '#': return 11; 596 case 'A': return 12; 597 case 'B': return 13; 598 case 'C': return 14; 599 case 'D': return 15; 600 default: 601 throw new IllegalArgumentException( 602 "invalid DTMF char: " + (int) c); 603 } 604 } 605 return code; 606 } 607 608 //***** Called from ImsPhoneConnection 609 610 /*package*/ void 611 hangup (ImsPhoneConnection conn) throws CallStateException { 612 if (DBG) log("hangup connection"); 613 614 if (conn.getOwner() != this) { 615 throw new CallStateException ("ImsPhoneConnection " + conn 616 + "does not belong to ImsPhoneCallTracker " + this); 617 } 618 619 hangup(conn.getCall()); 620 } 621 622 //***** Called from ImsPhoneCall 623 624 /* package */ void 625 hangup (ImsPhoneCall call) throws CallStateException { 626 if (DBG) log("hangup call"); 627 628 if (call.getConnections().size() == 0) { 629 throw new CallStateException("no connections"); 630 } 631 632 ImsCall imsCall = call.getImsCall(); 633 boolean rejectCall = false; 634 635 if (call == mRingingCall) { 636 if (Phone.DEBUG_PHONE) log("(ringing) hangup incoming"); 637 rejectCall = true; 638 } else if (call == mForegroundCall) { 639 if (call.isDialingOrAlerting()) { 640 if (Phone.DEBUG_PHONE) { 641 log("(foregnd) hangup dialing or alerting..."); 642 } 643 } else { 644 if (Phone.DEBUG_PHONE) { 645 log("(foregnd) hangup foreground"); 646 } 647 //held call will be resumed by onCallTerminated 648 } 649 } else if (call == mBackgroundCall) { 650 if (Phone.DEBUG_PHONE) { 651 log("(backgnd) hangup waiting or background"); 652 } 653 } else { 654 throw new RuntimeException ("ImsPhoneCall " + call + 655 "does not belong to ImsPhoneCallTracker " + this); 656 } 657 658 call.onHangupLocal(); 659 660 try { 661 if (imsCall != null) { 662 if (rejectCall) imsCall.reject(ImsReasonInfo.CODE_USER_DECLINE); 663 else imsCall.terminate(ImsReasonInfo.CODE_USER_TERMINATED); 664 } else if (mPendingMO != null && call == mForegroundCall) { 665 // is holding a foreground call 666 mPendingMO.update(null, ImsPhoneCall.State.DISCONNECTED); 667 mPendingMO.onDisconnect(); 668 removeConnection(mPendingMO); 669 mPendingMO = null; 670 updatePhoneState(); 671 removeMessages(EVENT_DIAL_PENDINGMO); 672 } 673 } catch (ImsException e) { 674 throw new CallStateException(e.getMessage()); 675 } 676 677 mPhone.notifyPreciseCallStateChanged(); 678 } 679 680 /* package */ 681 void resumeWaitingOrHolding() throws CallStateException { 682 if (DBG) log("resumeWaitingOrHolding"); 683 684 try { 685 if (mForegroundCall.getState().isAlive()) { 686 //resume foreground call after holding background call 687 //they were switched before holding 688 ImsCall imsCall = mForegroundCall.getImsCall(); 689 if (imsCall != null) imsCall.resume(); 690 } else if (mRingingCall.getState() == ImsPhoneCall.State.WAITING) { 691 //accept waiting call after holding background call 692 ImsCall imsCall = mRingingCall.getImsCall(); 693 if (imsCall != null) imsCall.accept(); 694 } else { 695 //Just resume background call. 696 //To distinguish resuming call with swapping calls 697 //we do not switch calls.here 698 //ImsPhoneConnection.update will chnage the parent when completed 699 ImsCall imsCall = mBackgroundCall.getImsCall(); 700 if (imsCall != null) imsCall.resume(); 701 } 702 } catch (ImsException e) { 703 throw new CallStateException(e.getMessage()); 704 } 705 } 706 707 /* package */ 708 void sendUSSD (String ussdString, Message response) { 709 if (DBG) log("sendUSSD"); 710 711 try { 712 if (mUssdSession != null) { 713 mUssdSession.sendUssd(ussdString); 714 AsyncResult.forMessage(response, null, null); 715 response.sendToTarget(); 716 return; 717 } 718 719 String[] callees = new String[] { ussdString }; 720 ImsCallProfile profile = mImsManager.createCallProfile(mServiceId, 721 ImsCallProfile.SERVICE_TYPE_NORMAL, ImsCallProfile.CALL_TYPE_VOICE); 722 profile.setCallExtraInt(ImsCallProfile.EXTRA_DIALSTRING, 723 ImsCallProfile.DIALSTRING_USSD); 724 725 mUssdSession = mImsManager.makeCall(mServiceId, profile, 726 callees, mImsUssdListener); 727 } catch (ImsException e) { 728 loge("sendUSSD : " + e); 729 mPhone.sendErrorResponse(response, e); 730 } 731 } 732 733 /* package */ 734 void cancelUSSD() { 735 if (mUssdSession == null) return; 736 737 try { 738 mUssdSession.terminate(ImsReasonInfo.CODE_USER_TERMINATED); 739 } catch (ImsException e) { 740 } 741 742 } 743 744 private synchronized ImsPhoneConnection findConnection(ImsCall imsCall) { 745 for (ImsPhoneConnection conn : mConnections) { 746 if (conn.getImsCall() == imsCall) { 747 return conn; 748 } 749 } 750 return null; 751 } 752 753 private synchronized void removeConnection(ImsPhoneConnection conn) { 754 mConnections.remove(conn); 755 } 756 757 private synchronized void addConnection(ImsPhoneConnection conn) { 758 mConnections.add(conn); 759 } 760 761 private void processCallStateChange(ImsCall imsCall, ImsPhoneCall.State state, int cause) { 762 if (DBG) log("processCallStateChange state=" + state + " cause=" + cause); 763 764 if (imsCall == null) return; 765 766 boolean changed = false; 767 ImsPhoneConnection conn = findConnection(imsCall); 768 769 if (conn == null) { 770 // TODO : what should be done? 771 return; 772 } 773 774 changed = conn.update(imsCall, state); 775 776 if (state == ImsPhoneCall.State.DISCONNECTED) { 777 changed = conn.onDisconnect(cause) || changed; 778 removeConnection(conn); 779 } 780 781 if (changed) { 782 if (conn.getCall() == mHandoverCall) return; 783 updatePhoneState(); 784 mPhone.notifyPreciseCallStateChanged(); 785 } 786 } 787 788 private int getDisconnectCauseFromReasonInfo(ImsReasonInfo reasonInfo) { 789 int cause = DisconnectCause.ERROR_UNSPECIFIED; 790 791 //int type = reasonInfo.getReasonType(); 792 int code = reasonInfo.getCode(); 793 switch (code) { 794 case ImsReasonInfo.CODE_SIP_BAD_ADDRESS: 795 case ImsReasonInfo.CODE_SIP_NOT_REACHABLE: 796 return DisconnectCause.NUMBER_UNREACHABLE; 797 798 case ImsReasonInfo.CODE_SIP_BUSY: 799 return DisconnectCause.BUSY; 800 801 case ImsReasonInfo.CODE_USER_TERMINATED: 802 return DisconnectCause.LOCAL; 803 804 case ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE: 805 return DisconnectCause.NORMAL; 806 807 case ImsReasonInfo.CODE_SIP_REDIRECTED: 808 case ImsReasonInfo.CODE_SIP_BAD_REQUEST: 809 case ImsReasonInfo.CODE_SIP_FORBIDDEN: 810 case ImsReasonInfo.CODE_SIP_NOT_ACCEPTABLE: 811 case ImsReasonInfo.CODE_SIP_USER_REJECTED: 812 case ImsReasonInfo.CODE_SIP_GLOBAL_ERROR: 813 return DisconnectCause.SERVER_ERROR; 814 815 case ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE: 816 case ImsReasonInfo.CODE_SIP_NOT_FOUND: 817 case ImsReasonInfo.CODE_SIP_SERVER_ERROR: 818 return DisconnectCause.SERVER_UNREACHABLE; 819 820 case ImsReasonInfo.CODE_LOCAL_NETWORK_ROAMING: 821 case ImsReasonInfo.CODE_LOCAL_NETWORK_IP_CHANGED: 822 case ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN: 823 case ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE: 824 case ImsReasonInfo.CODE_LOCAL_NOT_REGISTERED: 825 case ImsReasonInfo.CODE_LOCAL_NETWORK_NO_LTE_COVERAGE: 826 case ImsReasonInfo.CODE_LOCAL_NETWORK_NO_SERVICE: 827 case ImsReasonInfo.CODE_LOCAL_CALL_VCC_ON_PROGRESSING: 828 return DisconnectCause.OUT_OF_SERVICE; 829 830 case ImsReasonInfo.CODE_SIP_REQUEST_TIMEOUT: 831 case ImsReasonInfo.CODE_TIMEOUT_1XX_WAITING: 832 case ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER: 833 case ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER_CALL_UPDATE: 834 return DisconnectCause.TIMED_OUT; 835 836 case ImsReasonInfo.CODE_LOCAL_LOW_BATTERY: 837 case ImsReasonInfo.CODE_LOCAL_POWER_OFF: 838 return DisconnectCause.POWER_OFF; 839 840 default: 841 } 842 843 return cause; 844 } 845 846 /** 847 * Listen to the IMS call state change 848 */ 849 private ImsCall.Listener mImsCallListener = new ImsCall.Listener() { 850 @Override 851 public void onCallProgressing(ImsCall imsCall) { 852 if (DBG) log("onCallProgressing"); 853 854 mPendingMO = null; 855 processCallStateChange(imsCall, ImsPhoneCall.State.ALERTING, 856 DisconnectCause.NOT_DISCONNECTED); 857 } 858 859 @Override 860 public void onCallStarted(ImsCall imsCall) { 861 if (DBG) log("onCallStarted"); 862 863 mPendingMO = null; 864 processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE, 865 DisconnectCause.NOT_DISCONNECTED); 866 } 867 868 /** 869 * onCallStartFailed will be invoked when: 870 * case 1) Dialing fails 871 * case 2) Ringing call is disconnected by local or remote user 872 */ 873 @Override 874 public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) { 875 if (DBG) log("onCallStartFailed reasonCode=" + reasonInfo.getCode()); 876 877 if (mPendingMO != null) { 878 // To initiate dialing circuit-switched call 879 if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED 880 && mBackgroundCall.getState() == ImsPhoneCall.State.IDLE 881 && mRingingCall.getState() == ImsPhoneCall.State.IDLE) { 882 mForegroundCall.detach(mPendingMO); 883 removeConnection(mPendingMO); 884 mPendingMO.finalize(); 885 mPendingMO = null; 886 mPhone.initiateSilentRedial(); 887 return; 888 } 889 mPendingMO = null; 890 } 891 onCallTerminated(imsCall, reasonInfo); 892 } 893 894 @Override 895 public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) { 896 if (DBG) log("onCallTerminated reasonCode=" + reasonInfo.getCode()); 897 898 ImsPhoneCall.State oldState = mForegroundCall.getState(); 899 900 processCallStateChange(imsCall, ImsPhoneCall.State.DISCONNECTED, 901 getDisconnectCauseFromReasonInfo(reasonInfo)); 902 903 if (reasonInfo.getCode() == ImsReasonInfo.CODE_USER_TERMINATED) { 904 if ((oldState == ImsPhoneCall.State.DISCONNECTING) 905 && (mForegroundCall.getState() == ImsPhoneCall.State.DISCONNECTED) 906 && (mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING)) { 907 sendEmptyMessage(EVENT_RESUME_BACKGROUND); 908 } 909 } 910 } 911 912 @Override 913 public void onCallHeld(ImsCall imsCall) { 914 if (DBG) log("onCallHeld"); 915 916 synchronized (mSyncHold) { 917 ImsPhoneCall.State oldState = mBackgroundCall.getState(); 918 processCallStateChange(imsCall, ImsPhoneCall.State.HOLDING, 919 DisconnectCause.NOT_DISCONNECTED); 920 921 if (oldState == ImsPhoneCall.State.ACTIVE) { 922 if ((mForegroundCall.getState() == ImsPhoneCall.State.HOLDING) 923 || (mRingingCall.getState() == ImsPhoneCall.State.WAITING)) { 924 sendEmptyMessage(EVENT_RESUME_BACKGROUND); 925 } else { 926 //when multiple connections belong to background call, 927 //only the first callback reaches here 928 //otherwise the oldState is already HOLDING 929 if (mPendingMO != null) { 930 sendEmptyMessage(EVENT_DIAL_PENDINGMO); 931 } 932 } 933 } 934 } 935 } 936 937 @Override 938 public void onCallHoldFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) { 939 if (DBG) log("onCallHoldFailed reasonCode=" + reasonInfo.getCode()); 940 941 synchronized (mSyncHold) { 942 ImsPhoneCall.State bgState = mBackgroundCall.getState(); 943 if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED) { 944 // disconnected while processing hold 945 if (mPendingMO != null) { 946 sendEmptyMessage(EVENT_DIAL_PENDINGMO); 947 } 948 } else if (bgState == ImsPhoneCall.State.ACTIVE) { 949 mForegroundCall.switchWith(mBackgroundCall); 950 951 if (mPendingMO != null) { 952 mPendingMO.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED); 953 sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO); 954 } 955 } 956 } 957 } 958 959 @Override 960 public void onCallResumed(ImsCall imsCall) { 961 if (DBG) log("onCallResumed"); 962 963 processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE, 964 DisconnectCause.NOT_DISCONNECTED); 965 } 966 967 @Override 968 public void onCallResumeFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) { 969 // TODO : What should be done? 970 } 971 972 @Override 973 public void onCallResumeReceived(ImsCall imsCall) { 974 if (DBG) log("onCallResumeReceived"); 975 976 if (mOnHoldToneStarted) { 977 mPhone.stopOnHoldTone(); 978 mOnHoldToneStarted = false; 979 } 980 } 981 982 @Override 983 public void onCallHoldReceived(ImsCall imsCall) { 984 if (DBG) log("onCallHoldReceived"); 985 986 ImsPhoneConnection conn = findConnection(imsCall); 987 if (conn != null && conn.getState() == ImsPhoneCall.State.ACTIVE) { 988 if (!mOnHoldToneStarted && ImsPhoneCall.isLocalTone(imsCall)) { 989 mPhone.startOnHoldTone(); 990 mOnHoldToneStarted = true; 991 } 992 } 993 } 994 995 @Override 996 public void onCallMerged(ImsCall call, ImsCall newCall) { 997 if (DBG) log("onCallMerged"); 998 999 mForegroundCall.merge(mBackgroundCall, mForegroundCall.getState()); 1000 updatePhoneState(); 1001 mPhone.notifyPreciseCallStateChanged(); 1002 } 1003 1004 @Override 1005 public void onCallMergeFailed(ImsCall call, ImsReasonInfo reasonInfo) { 1006 if (DBG) log("onCallMergeFailed reasonCode=" + reasonInfo.getCode()); 1007 mPhone.notifySuppServiceFailed(Phone.SuppService.CONFERENCE); 1008 } 1009 }; 1010 1011 /** 1012 * Listen to the IMS call state change 1013 */ 1014 private ImsCall.Listener mImsUssdListener = new ImsCall.Listener() { 1015 @Override 1016 public void onCallStarted(ImsCall imsCall) { 1017 if (DBG) log("mImsUssdListener onCallStarted"); 1018 1019 if (imsCall == mUssdSession) { 1020 if (mPendingUssd != null) { 1021 AsyncResult.forMessage(mPendingUssd); 1022 mPendingUssd.sendToTarget(); 1023 mPendingUssd = null; 1024 } 1025 } 1026 } 1027 1028 @Override 1029 public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) { 1030 if (DBG) log("mImsUssdListener onCallStartFailed reasonCode=" + reasonInfo.getCode()); 1031 1032 onCallTerminated(imsCall, reasonInfo); 1033 } 1034 1035 @Override 1036 public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) { 1037 if (DBG) log("mImsUssdListener onCallTerminated reasonCode=" + reasonInfo.getCode()); 1038 1039 if (imsCall == mUssdSession) { 1040 mUssdSession = null; 1041 if (mPendingUssd != null) { 1042 CommandException ex = 1043 new CommandException(CommandException.Error.GENERIC_FAILURE); 1044 AsyncResult.forMessage(mPendingUssd, null, ex); 1045 mPendingUssd.sendToTarget(); 1046 mPendingUssd = null; 1047 } 1048 } 1049 imsCall.close(); 1050 } 1051 1052 @Override 1053 public void onCallUssdMessageReceived(ImsCall call, 1054 int mode, String ussdMessage) { 1055 if (DBG) log("mImsUssdListener onCallUssdMessageReceived mode=" + mode); 1056 1057 int ussdMode = -1; 1058 1059 switch(mode) { 1060 case ImsCall.USSD_MODE_REQUEST: 1061 ussdMode = CommandsInterface.USSD_MODE_REQUEST; 1062 break; 1063 1064 case ImsCall.USSD_MODE_NOTIFY: 1065 ussdMode = CommandsInterface.USSD_MODE_NOTIFY; 1066 break; 1067 } 1068 1069 mPhone.onIncomingUSSD(ussdMode, ussdMessage); 1070 } 1071 }; 1072 1073 /** 1074 * Listen to the IMS service state change 1075 * 1076 */ 1077 private ImsConnectionStateListener mImsConnectionStateListener = 1078 new ImsConnectionStateListener() { 1079 @Override 1080 public void onImsConnected() { 1081 if (DBG) log("onImsConnected"); 1082 mPhone.setServiceState(ServiceState.STATE_IN_SERVICE); 1083 } 1084 1085 @Override 1086 public void onImsDisconnected() { 1087 if (DBG) log("onImsDisconnected"); 1088 mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE); 1089 } 1090 1091 @Override 1092 public void onImsResumed() { 1093 if (DBG) log("onImsResumed"); 1094 mPhone.setServiceState(ServiceState.STATE_IN_SERVICE); 1095 } 1096 1097 @Override 1098 public void onImsSuspended() { 1099 if (DBG) log("onImsSuspended"); 1100 mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE); 1101 } 1102 }; 1103 1104 /* package */ 1105 ImsUtInterface getUtInterface() throws ImsException { 1106 if (mImsManager == null) { 1107 throw new ImsException("no ims manager", ImsReasonInfo.CODE_UNSPECIFIED); 1108 } 1109 1110 ImsUtInterface ut = mImsManager.getSupplementaryServiceConfiguration(mServiceId); 1111 return ut; 1112 } 1113 1114 /* package */ 1115 void notifySrvccState(Call.SrvccState state) { 1116 if (DBG) log("notifySrvccState state=" + state); 1117 1118 mSrvccState = state; 1119 1120 if (mSrvccState == Call.SrvccState.COMPLETED) { 1121 if (mForegroundCall.getConnections().size() > 0) { 1122 mHandoverCall.switchWith(mForegroundCall); 1123 } else if (mBackgroundCall.getConnections().size() > 0) { 1124 mHandoverCall.switchWith(mBackgroundCall); 1125 } 1126 } 1127 } 1128 1129 //****** Overridden from Handler 1130 1131 @Override 1132 public void 1133 handleMessage (Message msg) { 1134 AsyncResult ar; 1135 if (DBG) log("handleMessage what=" + msg.what); 1136 1137 switch (msg.what) { 1138 case EVENT_HANGUP_PENDINGMO: 1139 if (mPendingMO != null) { 1140 mPendingMO.onDisconnect(); 1141 removeConnection(mPendingMO); 1142 mPendingMO = null; 1143 } 1144 1145 updatePhoneState(); 1146 mPhone.notifyPreciseCallStateChanged(); 1147 break; 1148 case EVENT_RESUME_BACKGROUND: 1149 try { 1150 resumeWaitingOrHolding(); 1151 } catch (CallStateException e) { 1152 if (Phone.DEBUG_PHONE) { 1153 loge("handleMessage EVENT_RESUME_BACKGROUND exception=" + e); 1154 } 1155 } 1156 break; 1157 case EVENT_DIAL_PENDINGMO: 1158 dialInternal(mPendingMO, mClirMode, VideoCallProfile.VIDEO_STATE_AUDIO_ONLY); 1159 break; 1160 } 1161 } 1162 1163 @Override 1164 protected void log(String msg) { 1165 Rlog.d(LOG_TAG, "[ImsPhoneCallTracker] " + msg); 1166 } 1167 1168 protected void loge(String msg) { 1169 Rlog.e(LOG_TAG, "[ImsPhoneCallTracker] " + msg); 1170 } 1171 1172 @Override 1173 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1174 pw.println("ImsPhoneCallTracker extends:"); 1175 super.dump(fd, pw, args); 1176 pw.println(" mVoiceCallEndedRegistrants=" + mVoiceCallEndedRegistrants); 1177 pw.println(" mVoiceCallStartedRegistrants=" + mVoiceCallStartedRegistrants); 1178 pw.println(" mRingingCall=" + mRingingCall); 1179 pw.println(" mForegroundCall=" + mForegroundCall); 1180 pw.println(" mBackgroundCall=" + mBackgroundCall); 1181 pw.println(" mHandoverCall=" + mHandoverCall); 1182 pw.println(" mPendingMO=" + mPendingMO); 1183 //pw.println(" mHangupPendingMO=" + mHangupPendingMO); 1184 pw.println(" mPhone=" + mPhone); 1185 pw.println(" mDesiredMute=" + mDesiredMute); 1186 pw.println(" mState=" + mState); 1187 } 1188 1189 @Override 1190 protected void handlePollCalls(AsyncResult ar) { 1191 } 1192} 1193