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