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