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