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