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