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