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