ImsPhoneConnection.java revision 541accbd8e08fac77faa1c60d14533b1a5a36347
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.content.Context; 20import android.net.Uri; 21import android.os.AsyncResult; 22import android.os.Handler; 23import android.os.Looper; 24import android.os.Message; 25import android.os.PowerManager; 26import android.os.Registrant; 27import android.os.SystemClock; 28import android.telecom.Log; 29import android.telephony.DisconnectCause; 30import android.telephony.PhoneNumberUtils; 31import android.telephony.Rlog; 32 33import com.android.ims.ImsException; 34import com.android.ims.ImsStreamMediaProfile; 35import com.android.internal.telephony.CallStateException; 36import com.android.internal.telephony.Connection; 37import com.android.internal.telephony.Phone; 38import com.android.internal.telephony.PhoneConstants; 39import com.android.internal.telephony.UUSInfo; 40 41import com.android.ims.ImsCall; 42import com.android.ims.ImsCallProfile; 43 44/** 45 * {@hide} 46 */ 47public class ImsPhoneConnection extends Connection { 48 private static final String LOG_TAG = "ImsPhoneConnection"; 49 private static final boolean DBG = true; 50 51 //***** Instance Variables 52 53 private ImsPhoneCallTracker mOwner; 54 private ImsPhoneCall mParent; 55 private ImsCall mImsCall; 56 57 private String mPostDialString; // outgoing calls only 58 private boolean mDisconnected; 59 60 /* 61 int mIndex; // index in ImsPhoneCallTracker.connections[], -1 if unassigned 62 // The GSM index is 1 + this 63 */ 64 65 /* 66 * These time/timespan values are based on System.currentTimeMillis(), 67 * i.e., "wall clock" time. 68 */ 69 private long mDisconnectTime; 70 71 private int mNextPostDialChar; // index into postDialString 72 73 private int mCause = DisconnectCause.NOT_DISCONNECTED; 74 private PostDialState mPostDialState = PostDialState.NOT_STARTED; 75 private UUSInfo mUusInfo; 76 private Handler mHandler; 77 78 private PowerManager.WakeLock mPartialWakeLock; 79 80 //***** Event Constants 81 private static final int EVENT_DTMF_DONE = 1; 82 private static final int EVENT_PAUSE_DONE = 2; 83 private static final int EVENT_NEXT_POST_DIAL = 3; 84 private static final int EVENT_WAKE_LOCK_TIMEOUT = 4; 85 86 //***** Constants 87 private static final int PAUSE_DELAY_MILLIS = 3 * 1000; 88 private static final int WAKE_LOCK_TIMEOUT_MILLIS = 60*1000; 89 90 //***** Inner Classes 91 92 class MyHandler extends Handler { 93 MyHandler(Looper l) {super(l);} 94 95 @Override 96 public void 97 handleMessage(Message msg) { 98 99 switch (msg.what) { 100 case EVENT_NEXT_POST_DIAL: 101 case EVENT_DTMF_DONE: 102 case EVENT_PAUSE_DONE: 103 processNextPostDialChar(); 104 break; 105 case EVENT_WAKE_LOCK_TIMEOUT: 106 releaseWakeLock(); 107 break; 108 } 109 } 110 } 111 112 //***** Constructors 113 114 /** This is probably an MT call */ 115 /*package*/ 116 ImsPhoneConnection(Context context, ImsCall imsCall, ImsPhoneCallTracker ct, ImsPhoneCall parent) { 117 createWakeLock(context); 118 acquireWakeLock(); 119 120 mOwner = ct; 121 mHandler = new MyHandler(mOwner.getLooper()); 122 mImsCall = imsCall; 123 124 if ((imsCall != null) && (imsCall.getCallProfile() != null)) { 125 mAddress = imsCall.getCallProfile().getCallExtra(ImsCallProfile.EXTRA_OI); 126 mCnapName = imsCall.getCallProfile().getCallExtra(ImsCallProfile.EXTRA_CNA); 127 mNumberPresentation = ImsCallProfile.OIRToPresentation( 128 imsCall.getCallProfile().getCallExtraInt(ImsCallProfile.EXTRA_OIR)); 129 mCnapNamePresentation = ImsCallProfile.OIRToPresentation( 130 imsCall.getCallProfile().getCallExtraInt(ImsCallProfile.EXTRA_CNAP)); 131 132 ImsCallProfile imsCallProfile = imsCall.getCallProfile(); 133 if (imsCallProfile != null) { 134 int callType = imsCall.getCallProfile().mCallType; 135 setVideoState(ImsCallProfile.getVideoStateFromCallType(callType)); 136 137 ImsStreamMediaProfile mediaProfile = imsCallProfile.mMediaProfile; 138 if (mediaProfile != null) { 139 setAudioQuality(getAudioQualityFromMediaProfile(mediaProfile)); 140 } 141 } 142 143 // Determine if the current call have video capabilities. 144 try { 145 ImsCallProfile localCallProfile = imsCall.getLocalCallProfile(); 146 if (localCallProfile != null) { 147 int localCallTypeCapability = localCallProfile.mCallType; 148 boolean isLocalVideoCapable = localCallTypeCapability 149 == ImsCallProfile.CALL_TYPE_VT; 150 151 setLocalVideoCapable(isLocalVideoCapable); 152 } 153 } catch (ImsException e) { 154 // No session, so cannot get local capabilities. 155 } 156 } else { 157 mNumberPresentation = PhoneConstants.PRESENTATION_UNKNOWN; 158 mCnapNamePresentation = PhoneConstants.PRESENTATION_UNKNOWN; 159 } 160 161 mIsIncoming = true; 162 mCreateTime = System.currentTimeMillis(); 163 mUusInfo = null; 164 165 //mIndex = index; 166 167 mParent = parent; 168 mParent.attach(this, ImsPhoneCall.State.INCOMING); 169 } 170 171 /** This is an MO call, created when dialing */ 172 /*package*/ 173 ImsPhoneConnection(Context context, String dialString, ImsPhoneCallTracker ct, ImsPhoneCall parent) { 174 createWakeLock(context); 175 acquireWakeLock(); 176 177 mOwner = ct; 178 mHandler = new MyHandler(mOwner.getLooper()); 179 180 mDialString = dialString; 181 182 mAddress = PhoneNumberUtils.extractNetworkPortionAlt(dialString); 183 mPostDialString = PhoneNumberUtils.extractPostDialPortion(dialString); 184 185 //mIndex = -1; 186 187 mIsIncoming = false; 188 mCnapName = null; 189 mCnapNamePresentation = PhoneConstants.PRESENTATION_ALLOWED; 190 mNumberPresentation = PhoneConstants.PRESENTATION_ALLOWED; 191 mCreateTime = System.currentTimeMillis(); 192 193 mParent = parent; 194 parent.attachFake(this, ImsPhoneCall.State.DIALING); 195 } 196 197 public void dispose() { 198 } 199 200 static boolean 201 equalsHandlesNulls (Object a, Object b) { 202 return (a == null) ? (b == null) : a.equals (b); 203 } 204 205 /** 206 * Determines the {@link ImsPhoneConnection} audio quality based on an 207 * {@link ImsStreamMediaProfile}. 208 * 209 * @param mediaProfile The media profile. 210 * @return The audio quality. 211 */ 212 private int getAudioQualityFromMediaProfile(ImsStreamMediaProfile mediaProfile) { 213 int audioQuality; 214 215 // The Adaptive Multi-Rate Wideband codec is used for high definition audio calls. 216 if (mediaProfile.mAudioQuality == ImsStreamMediaProfile.AUDIO_QUALITY_AMR_WB) { 217 audioQuality = AUDIO_QUALITY_HIGH_DEFINITION; 218 } else { 219 audioQuality = AUDIO_QUALITY_STANDARD; 220 } 221 222 return audioQuality; 223 } 224 225 226 @Override 227 public String getOrigDialString(){ 228 return mDialString; 229 } 230 231 @Override 232 public ImsPhoneCall getCall() { 233 return mParent; 234 } 235 236 @Override 237 public long getDisconnectTime() { 238 return mDisconnectTime; 239 } 240 241 @Override 242 public long getHoldingStartTime() { 243 return mHoldingStartTime; 244 } 245 246 @Override 247 public long getHoldDurationMillis() { 248 if (getState() != ImsPhoneCall.State.HOLDING) { 249 // If not holding, return 0 250 return 0; 251 } else { 252 return SystemClock.elapsedRealtime() - mHoldingStartTime; 253 } 254 } 255 256 @Override 257 public int getDisconnectCause() { 258 return mCause; 259 } 260 261 public void setDisconnectCause(int cause) { 262 mCause = cause; 263 } 264 265 public ImsPhoneCallTracker getOwner () { 266 return mOwner; 267 } 268 269 @Override 270 public ImsPhoneCall.State getState() { 271 if (mDisconnected) { 272 return ImsPhoneCall.State.DISCONNECTED; 273 } else { 274 return super.getState(); 275 } 276 } 277 278 @Override 279 public void hangup() throws CallStateException { 280 if (!mDisconnected) { 281 mOwner.hangup(this); 282 } else { 283 throw new CallStateException ("disconnected"); 284 } 285 } 286 287 @Override 288 public void separate() throws CallStateException { 289 throw new CallStateException ("not supported"); 290 } 291 292 @Override 293 public PostDialState getPostDialState() { 294 return mPostDialState; 295 } 296 297 @Override 298 public void proceedAfterWaitChar() { 299 if (mPostDialState != PostDialState.WAIT) { 300 Rlog.w(LOG_TAG, "ImsPhoneConnection.proceedAfterWaitChar(): Expected " 301 + "getPostDialState() to be WAIT but was " + mPostDialState); 302 return; 303 } 304 305 setPostDialState(PostDialState.STARTED); 306 307 processNextPostDialChar(); 308 } 309 310 @Override 311 public void proceedAfterWildChar(String str) { 312 if (mPostDialState != PostDialState.WILD) { 313 Rlog.w(LOG_TAG, "ImsPhoneConnection.proceedAfterWaitChar(): Expected " 314 + "getPostDialState() to be WILD but was " + mPostDialState); 315 return; 316 } 317 318 setPostDialState(PostDialState.STARTED); 319 320 // make a new postDialString, with the wild char replacement string 321 // at the beginning, followed by the remaining postDialString. 322 323 StringBuilder buf = new StringBuilder(str); 324 buf.append(mPostDialString.substring(mNextPostDialChar)); 325 mPostDialString = buf.toString(); 326 mNextPostDialChar = 0; 327 if (Phone.DEBUG_PHONE) { 328 Rlog.d(LOG_TAG, "proceedAfterWildChar: new postDialString is " + 329 mPostDialString); 330 } 331 332 processNextPostDialChar(); 333 } 334 335 @Override 336 public void cancelPostDial() { 337 setPostDialState(PostDialState.CANCELLED); 338 } 339 340 /** 341 * Called when this Connection is being hung up locally (eg, user pressed "end") 342 */ 343 void 344 onHangupLocal() { 345 mCause = DisconnectCause.LOCAL; 346 } 347 348 /** Called when the connection has been disconnected */ 349 /*package*/ boolean 350 onDisconnect(int cause) { 351 Rlog.d(LOG_TAG, "onDisconnect: cause=" + cause); 352 if (mCause != DisconnectCause.LOCAL) mCause = cause; 353 return onDisconnect(); 354 } 355 356 /*package*/ boolean 357 onDisconnect() { 358 boolean changed = false; 359 360 if (!mDisconnected) { 361 //mIndex = -1; 362 363 mDisconnectTime = System.currentTimeMillis(); 364 mDuration = SystemClock.elapsedRealtime() - mConnectTimeReal; 365 mDisconnected = true; 366 367 mOwner.mPhone.notifyDisconnect(this); 368 369 if (mParent != null) { 370 changed = mParent.connectionDisconnected(this); 371 } else { 372 Rlog.d(LOG_TAG, "onDisconnect: no parent"); 373 } 374 if (mImsCall != null) mImsCall.close(); 375 mImsCall = null; 376 } 377 releaseWakeLock(); 378 return changed; 379 } 380 381 /** 382 * An incoming or outgoing call has connected 383 */ 384 void 385 onConnectedInOrOut() { 386 mConnectTime = System.currentTimeMillis(); 387 mConnectTimeReal = SystemClock.elapsedRealtime(); 388 mDuration = 0; 389 390 if (Phone.DEBUG_PHONE) { 391 Rlog.d(LOG_TAG, "onConnectedInOrOut: connectTime=" + mConnectTime); 392 } 393 394 if (!mIsIncoming) { 395 // outgoing calls only 396 processNextPostDialChar(); 397 } 398 releaseWakeLock(); 399 } 400 401 /*package*/ void 402 onStartedHolding() { 403 mHoldingStartTime = SystemClock.elapsedRealtime(); 404 } 405 /** 406 * Performs the appropriate action for a post-dial char, but does not 407 * notify application. returns false if the character is invalid and 408 * should be ignored 409 */ 410 private boolean 411 processPostDialChar(char c) { 412 if (PhoneNumberUtils.is12Key(c)) { 413 mOwner.mCi.sendDtmf(c, mHandler.obtainMessage(EVENT_DTMF_DONE)); 414 } else if (c == PhoneNumberUtils.PAUSE) { 415 // From TS 22.101: 416 // It continues... 417 // Upon the called party answering the UE shall send the DTMF digits 418 // automatically to the network after a delay of 3 seconds( 20 ). 419 // The digits shall be sent according to the procedures and timing 420 // specified in 3GPP TS 24.008 [13]. The first occurrence of the 421 // "DTMF Control Digits Separator" shall be used by the ME to 422 // distinguish between the addressing digits (i.e. the phone number) 423 // and the DTMF digits. Upon subsequent occurrences of the 424 // separator, 425 // the UE shall pause again for 3 seconds ( 20 ) before sending 426 // any further DTMF digits. 427 mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_PAUSE_DONE), 428 PAUSE_DELAY_MILLIS); 429 } else if (c == PhoneNumberUtils.WAIT) { 430 setPostDialState(PostDialState.WAIT); 431 } else if (c == PhoneNumberUtils.WILD) { 432 setPostDialState(PostDialState.WILD); 433 } else { 434 return false; 435 } 436 437 return true; 438 } 439 440 @Override 441 public String 442 getRemainingPostDialString() { 443 if (mPostDialState == PostDialState.CANCELLED 444 || mPostDialState == PostDialState.COMPLETE 445 || mPostDialString == null 446 || mPostDialString.length() <= mNextPostDialChar 447 ) { 448 return ""; 449 } 450 451 return mPostDialString.substring(mNextPostDialChar); 452 } 453 454 @Override 455 protected void finalize() 456 { 457 releaseWakeLock(); 458 } 459 460 private void 461 processNextPostDialChar() { 462 char c = 0; 463 Registrant postDialHandler; 464 465 if (mPostDialState == PostDialState.CANCELLED) { 466 //Rlog.d(LOG_TAG, "##### processNextPostDialChar: postDialState == CANCELLED, bail"); 467 return; 468 } 469 470 if (mPostDialString == null || 471 mPostDialString.length() <= mNextPostDialChar) { 472 setPostDialState(PostDialState.COMPLETE); 473 474 // notifyMessage.arg1 is 0 on complete 475 c = 0; 476 } else { 477 boolean isValid; 478 479 setPostDialState(PostDialState.STARTED); 480 481 c = mPostDialString.charAt(mNextPostDialChar++); 482 483 isValid = processPostDialChar(c); 484 485 if (!isValid) { 486 // Will call processNextPostDialChar 487 mHandler.obtainMessage(EVENT_NEXT_POST_DIAL).sendToTarget(); 488 // Don't notify application 489 Rlog.e(LOG_TAG, "processNextPostDialChar: c=" + c + " isn't valid!"); 490 return; 491 } 492 } 493 494 postDialHandler = mOwner.mPhone.mPostDialHandler; 495 496 Message notifyMessage; 497 498 if (postDialHandler != null 499 && (notifyMessage = postDialHandler.messageForRegistrant()) != null) { 500 // The AsyncResult.result is the Connection object 501 PostDialState state = mPostDialState; 502 AsyncResult ar = AsyncResult.forMessage(notifyMessage); 503 ar.result = this; 504 ar.userObj = state; 505 506 // arg1 is the character that was/is being processed 507 notifyMessage.arg1 = c; 508 509 //Rlog.v(LOG_TAG, "##### processNextPostDialChar: send msg to postDialHandler, arg1=" + c); 510 notifyMessage.sendToTarget(); 511 } 512 } 513 514 /** 515 * Set post dial state and acquire wake lock while switching to "started" 516 * state, the wake lock will be released if state switches out of "started" 517 * state or after WAKE_LOCK_TIMEOUT_MILLIS. 518 * @param s new PostDialState 519 */ 520 private void setPostDialState(PostDialState s) { 521 if (mPostDialState != PostDialState.STARTED 522 && s == PostDialState.STARTED) { 523 acquireWakeLock(); 524 Message msg = mHandler.obtainMessage(EVENT_WAKE_LOCK_TIMEOUT); 525 mHandler.sendMessageDelayed(msg, WAKE_LOCK_TIMEOUT_MILLIS); 526 } else if (mPostDialState == PostDialState.STARTED 527 && s != PostDialState.STARTED) { 528 mHandler.removeMessages(EVENT_WAKE_LOCK_TIMEOUT); 529 releaseWakeLock(); 530 } 531 mPostDialState = s; 532 } 533 534 private void 535 createWakeLock(Context context) { 536 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 537 mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG); 538 } 539 540 private void 541 acquireWakeLock() { 542 Rlog.d(LOG_TAG, "acquireWakeLock"); 543 mPartialWakeLock.acquire(); 544 } 545 546 void 547 releaseWakeLock() { 548 synchronized(mPartialWakeLock) { 549 if (mPartialWakeLock.isHeld()) { 550 Rlog.d(LOG_TAG, "releaseWakeLock"); 551 mPartialWakeLock.release(); 552 } 553 } 554 } 555 556 @Override 557 public int getNumberPresentation() { 558 return mNumberPresentation; 559 } 560 561 @Override 562 public UUSInfo getUUSInfo() { 563 return mUusInfo; 564 } 565 566 @Override 567 public Connection getOrigConnection() { 568 return null; 569 } 570 571 @Override 572 public boolean isMultiparty() { 573 return mImsCall != null && mImsCall.isMultiparty(); 574 } 575 576 /*package*/ ImsCall getImsCall() { 577 return mImsCall; 578 } 579 580 /*package*/ void setImsCall(ImsCall imsCall) { 581 mImsCall = imsCall; 582 } 583 584 /*package*/ void changeParent(ImsPhoneCall parent) { 585 mParent = parent; 586 } 587 588 /*package*/ boolean 589 update(ImsCall imsCall, ImsPhoneCall.State state) { 590 boolean changed = false; 591 592 if (state == ImsPhoneCall.State.ACTIVE) { 593 if (mParent.getState().isRinging() 594 || mParent.getState().isDialing()) { 595 onConnectedInOrOut(); 596 } 597 598 if (mParent.getState().isRinging() 599 || mParent == mOwner.mBackgroundCall) { 600 //mForegroundCall should be IDLE 601 //when accepting WAITING call 602 //before accept WAITING call, 603 //the ACTIVE call should be held ahead 604 mParent.detach(this); 605 mParent = mOwner.mForegroundCall; 606 mParent.attach(this); 607 } 608 } else if (state == ImsPhoneCall.State.HOLDING) { 609 onStartedHolding(); 610 } 611 612 changed = mParent.update(this, imsCall, state); 613 614 if (imsCall != null) { 615 // Check for a change in the video capabilities for the call and update the 616 // {@link ImsPhoneConnection} with this information. 617 try { 618 // Get the current local VT capabilities (i.e. even if currentCallType above is 619 // audio-only, the local capability could support bi-directional video). 620 ImsCallProfile localCallProfile = imsCall.getLocalCallProfile(); 621 if (localCallProfile != null) { 622 int localCallTypeCapability = localCallProfile.mCallType; 623 boolean newLocalVideoCapable = localCallTypeCapability 624 == ImsCallProfile.CALL_TYPE_VT; 625 626 if (isLocalVideoCapable() != newLocalVideoCapable) { 627 setLocalVideoCapable(newLocalVideoCapable); 628 changed = true; 629 } 630 } 631 } catch (ImsException e) { 632 // No session in place -- no change 633 } 634 635 // Check for a change in the call type / video state, or audio quality of the 636 // {@link ImsCall} and update the {@link ImsPhoneConnection} with this information. 637 ImsCallProfile callProfile = imsCall.getCallProfile(); 638 if (callProfile != null) { 639 int oldVideoState = getVideoState(); 640 int newVideoState = ImsCallProfile.getVideoStateFromCallType(callProfile.mCallType); 641 642 if (oldVideoState != newVideoState) { 643 setVideoState(newVideoState); 644 changed = true; 645 } 646 647 ImsStreamMediaProfile mediaProfile = callProfile.mMediaProfile; 648 if (mediaProfile != null) { 649 int oldAudioQuality = getAudioQuality(); 650 int newAudioQuality = getAudioQualityFromMediaProfile(mediaProfile); 651 652 if (oldAudioQuality != newAudioQuality) { 653 setAudioQuality(newAudioQuality); 654 changed = true; 655 } 656 } 657 } 658 } 659 return changed; 660 } 661 662 @Override 663 public int getPreciseDisconnectCause() { 664 return 0; 665 } 666 667 /** 668 * Notifies this Connection of a request to disconnect a participant of the conference managed 669 * by the connection. 670 * 671 * @param endpoint the {@link android.net.Uri} of the participant to disconnect. 672 */ 673 @Override 674 public void onDisconnectConferenceParticipant(Uri endpoint) { 675 ImsCall imsCall = getImsCall(); 676 if (imsCall == null) { 677 return; 678 } 679 try { 680 imsCall.removeParticipants(new String[]{endpoint.toString()}); 681 } catch (ImsException e) { 682 // No session in place -- no change 683 Rlog.e(LOG_TAG, "onDisconnectConferenceParticipant: no session in place. "+ 684 "Failed to disconnect endpoint = " + endpoint); 685 } 686 } 687 688 /** 689 * Provides a string representation of the {@link ImsPhoneConnection}. Primarily intended for 690 * use in log statements. 691 * 692 * @return String representation of call. 693 */ 694 @Override 695 public String toString() { 696 StringBuilder sb = new StringBuilder(); 697 sb.append("[ImsPhoneConnection objId: "); 698 sb.append(System.identityHashCode(this)); 699 sb.append(" address:"); 700 sb.append(Log.pii(getAddress())); 701 sb.append(" ImsCall:"); 702 if (mImsCall == null) { 703 sb.append("null"); 704 } else { 705 sb.append(mImsCall); 706 } 707 sb.append("]"); 708 return sb.toString(); 709 } 710} 711 712