GsmConnection.java revision 2d265969ca301ba3720458ea9da2ec8d6a18bb8d
1/* 2 * Copyright (C) 2006 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.gsm; 18import android.content.Context; 19import android.os.AsyncResult; 20import android.os.Handler; 21import android.os.Looper; 22import android.os.Message; 23import android.os.PowerManager; 24import android.os.Registrant; 25import android.os.SystemClock; 26import android.telephony.DisconnectCause; 27import android.telephony.Rlog; 28import android.telephony.PhoneNumberUtils; 29import android.telephony.ServiceState; 30import android.text.TextUtils; 31 32import com.android.internal.telephony.*; 33import com.android.internal.telephony.uicc.UiccCardApplication; 34import com.android.internal.telephony.uicc.UiccController; 35import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState; 36 37/** 38 * {@hide} 39 */ 40public class GsmConnection extends Connection { 41 private static final String LOG_TAG = "GsmConnection"; 42 private static final boolean DBG = true; 43 44 //***** Instance Variables 45 46 GsmCallTracker mOwner; 47 GsmCall mParent; 48 49 String mPostDialString; // outgoing calls only 50 boolean mDisconnected; 51 52 int mIndex; // index in GsmCallTracker.connections[], -1 if unassigned 53 // The GSM index is 1 + this 54 55 /* 56 * These time/timespan values are based on System.currentTimeMillis(), 57 * i.e., "wall clock" time. 58 */ 59 long mDisconnectTime; 60 61 int mNextPostDialChar; // index into postDialString 62 63 int mCause = DisconnectCause.NOT_DISCONNECTED; 64 PostDialState mPostDialState = PostDialState.NOT_STARTED; 65 UUSInfo mUusInfo; 66 int mPreciseCause = 0; 67 68 Connection mOrigConnection; 69 70 Handler mHandler; 71 72 private PowerManager.WakeLock mPartialWakeLock; 73 74 //***** Event Constants 75 static final int EVENT_DTMF_DONE = 1; 76 static final int EVENT_PAUSE_DONE = 2; 77 static final int EVENT_NEXT_POST_DIAL = 3; 78 static final int EVENT_WAKE_LOCK_TIMEOUT = 4; 79 80 //***** Constants 81 static final int PAUSE_DELAY_MILLIS = 3 * 1000; 82 static final int WAKE_LOCK_TIMEOUT_MILLIS = 60*1000; 83 84 //***** Inner Classes 85 86 class MyHandler extends Handler { 87 MyHandler(Looper l) {super(l);} 88 89 @Override 90 public void 91 handleMessage(Message msg) { 92 93 switch (msg.what) { 94 case EVENT_NEXT_POST_DIAL: 95 case EVENT_DTMF_DONE: 96 case EVENT_PAUSE_DONE: 97 processNextPostDialChar(); 98 break; 99 case EVENT_WAKE_LOCK_TIMEOUT: 100 releaseWakeLock(); 101 break; 102 } 103 } 104 } 105 106 //***** Constructors 107 108 /** This is probably an MT call that we first saw in a CLCC response */ 109 /*package*/ 110 GsmConnection (Context context, DriverCall dc, GsmCallTracker ct, int index) { 111 createWakeLock(context); 112 acquireWakeLock(); 113 114 mOwner = ct; 115 mHandler = new MyHandler(mOwner.getLooper()); 116 117 mAddress = dc.number; 118 119 mIsIncoming = dc.isMT; 120 mCreateTime = System.currentTimeMillis(); 121 mCnapName = dc.name; 122 mCnapNamePresentation = dc.namePresentation; 123 mNumberPresentation = dc.numberPresentation; 124 mUusInfo = dc.uusInfo; 125 126 mIndex = index; 127 128 mParent = parentFromDCState (dc.state); 129 mParent.attach(this, dc); 130 } 131 132 /** This is an MO call, created when dialing */ 133 /*package*/ 134 GsmConnection (Context context, String dialString, GsmCallTracker ct, GsmCall parent) { 135 createWakeLock(context); 136 acquireWakeLock(); 137 138 mOwner = ct; 139 mHandler = new MyHandler(mOwner.getLooper()); 140 141 mDialString = dialString; 142 143 mAddress = PhoneNumberUtils.extractNetworkPortionAlt(dialString); 144 mPostDialString = PhoneNumberUtils.extractPostDialPortion(dialString); 145 146 mIndex = -1; 147 148 mIsIncoming = false; 149 mCnapName = null; 150 mCnapNamePresentation = PhoneConstants.PRESENTATION_ALLOWED; 151 mNumberPresentation = PhoneConstants.PRESENTATION_ALLOWED; 152 mCreateTime = System.currentTimeMillis(); 153 154 mParent = parent; 155 parent.attachFake(this, GsmCall.State.DIALING); 156 } 157 158 public void dispose() { 159 } 160 161 static boolean 162 equalsHandlesNulls (Object a, Object b) { 163 return (a == null) ? (b == null) : a.equals (b); 164 } 165 166 /*package*/ boolean 167 compareTo(DriverCall c) { 168 // On mobile originated (MO) calls, the phone number may have changed 169 // due to a SIM Toolkit call control modification. 170 // 171 // We assume we know when MO calls are created (since we created them) 172 // and therefore don't need to compare the phone number anyway. 173 if (! (mIsIncoming || c.isMT)) return true; 174 175 // A new call appearing by SRVCC may have invalid number 176 // if IMS service is not tightly coupled with cellular modem stack. 177 // Thus we prefer the preexisting handover connection instance. 178 if (mOrigConnection != null) return true; 179 180 // ... but we can compare phone numbers on MT calls, and we have 181 // no control over when they begin, so we might as well 182 183 String cAddress = PhoneNumberUtils.stringFromStringAndTOA(c.number, c.TOA); 184 return mIsIncoming == c.isMT && equalsHandlesNulls(mAddress, cAddress); 185 } 186 187 @Override 188 public GsmCall getCall() { 189 return mParent; 190 } 191 192 @Override 193 public long getDisconnectTime() { 194 return mDisconnectTime; 195 } 196 197 @Override 198 public long getHoldDurationMillis() { 199 if (getState() != GsmCall.State.HOLDING) { 200 // If not holding, return 0 201 return 0; 202 } else { 203 return SystemClock.elapsedRealtime() - mHoldingStartTime; 204 } 205 } 206 207 @Override 208 public int getDisconnectCause() { 209 return mCause; 210 } 211 212 @Override 213 public GsmCall.State getState() { 214 if (mDisconnected) { 215 return GsmCall.State.DISCONNECTED; 216 } else { 217 return super.getState(); 218 } 219 } 220 221 @Override 222 public void hangup() throws CallStateException { 223 if (!mDisconnected) { 224 mOwner.hangup(this); 225 } else { 226 throw new CallStateException ("disconnected"); 227 } 228 } 229 230 @Override 231 public void separate() throws CallStateException { 232 if (!mDisconnected) { 233 mOwner.separate(this); 234 } else { 235 throw new CallStateException ("disconnected"); 236 } 237 } 238 239 @Override 240 public PostDialState getPostDialState() { 241 return mPostDialState; 242 } 243 244 @Override 245 public void proceedAfterWaitChar() { 246 if (mPostDialState != PostDialState.WAIT) { 247 Rlog.w(LOG_TAG, "GsmConnection.proceedAfterWaitChar(): Expected " 248 + "getPostDialState() to be WAIT but was " + mPostDialState); 249 return; 250 } 251 252 setPostDialState(PostDialState.STARTED); 253 254 processNextPostDialChar(); 255 } 256 257 @Override 258 public void proceedAfterWildChar(String str) { 259 if (mPostDialState != PostDialState.WILD) { 260 Rlog.w(LOG_TAG, "GsmConnection.proceedAfterWaitChar(): Expected " 261 + "getPostDialState() to be WILD but was " + mPostDialState); 262 return; 263 } 264 265 setPostDialState(PostDialState.STARTED); 266 267 // make a new postDialString, with the wild char replacement string 268 // at the beginning, followed by the remaining postDialString. 269 270 StringBuilder buf = new StringBuilder(str); 271 buf.append(mPostDialString.substring(mNextPostDialChar)); 272 mPostDialString = buf.toString(); 273 mNextPostDialChar = 0; 274 if (Phone.DEBUG_PHONE) { 275 log("proceedAfterWildChar: new postDialString is " + 276 mPostDialString); 277 } 278 279 processNextPostDialChar(); 280 } 281 282 @Override 283 public void cancelPostDial() { 284 setPostDialState(PostDialState.CANCELLED); 285 } 286 287 /** 288 * Called when this Connection is being hung up locally (eg, user pressed "end") 289 * Note that at this point, the hangup request has been dispatched to the radio 290 * but no response has yet been received so update() has not yet been called 291 */ 292 void 293 onHangupLocal() { 294 mCause = DisconnectCause.LOCAL; 295 mPreciseCause = 0; 296 } 297 298 /** 299 * Maps RIL call disconnect code to {@link DisconnectCause}. 300 * @param causeCode RIL disconnect code 301 * @return the corresponding value from {@link DisconnectCause} 302 */ 303 int disconnectCauseFromCode(int causeCode) { 304 /** 305 * See 22.001 Annex F.4 for mapping of cause codes 306 * to local tones 307 */ 308 309 switch (causeCode) { 310 case CallFailCause.USER_BUSY: 311 return DisconnectCause.BUSY; 312 313 case CallFailCause.NO_CIRCUIT_AVAIL: 314 case CallFailCause.TEMPORARY_FAILURE: 315 case CallFailCause.SWITCHING_CONGESTION: 316 case CallFailCause.CHANNEL_NOT_AVAIL: 317 case CallFailCause.QOS_NOT_AVAIL: 318 case CallFailCause.BEARER_NOT_AVAIL: 319 return DisconnectCause.CONGESTION; 320 321 case CallFailCause.ACM_LIMIT_EXCEEDED: 322 return DisconnectCause.LIMIT_EXCEEDED; 323 324 case CallFailCause.CALL_BARRED: 325 return DisconnectCause.CALL_BARRED; 326 327 case CallFailCause.FDN_BLOCKED: 328 return DisconnectCause.FDN_BLOCKED; 329 330 case CallFailCause.UNOBTAINABLE_NUMBER: 331 return DisconnectCause.UNOBTAINABLE_NUMBER; 332 333 case CallFailCause.ERROR_UNSPECIFIED: 334 case CallFailCause.NORMAL_CLEARING: 335 default: 336 GSMPhone phone = mOwner.mPhone; 337 int serviceState = phone.getServiceState().getState(); 338 UiccCardApplication cardApp = phone.getUiccCardApplication(); 339 AppState uiccAppState = (cardApp != null) ? cardApp.getState() : 340 AppState.APPSTATE_UNKNOWN; 341 if (serviceState == ServiceState.STATE_POWER_OFF) { 342 return DisconnectCause.POWER_OFF; 343 } else if (serviceState == ServiceState.STATE_OUT_OF_SERVICE 344 || serviceState == ServiceState.STATE_EMERGENCY_ONLY ) { 345 return DisconnectCause.OUT_OF_SERVICE; 346 } else if (uiccAppState != AppState.APPSTATE_READY) { 347 return DisconnectCause.ICC_ERROR; 348 } else if (causeCode == CallFailCause.ERROR_UNSPECIFIED) { 349 if (phone.mSST.mRestrictedState.isCsRestricted()) { 350 return DisconnectCause.CS_RESTRICTED; 351 } else if (phone.mSST.mRestrictedState.isCsEmergencyRestricted()) { 352 return DisconnectCause.CS_RESTRICTED_EMERGENCY; 353 } else if (phone.mSST.mRestrictedState.isCsNormalRestricted()) { 354 return DisconnectCause.CS_RESTRICTED_NORMAL; 355 } else { 356 return DisconnectCause.ERROR_UNSPECIFIED; 357 } 358 } else if (causeCode == CallFailCause.NORMAL_CLEARING) { 359 return DisconnectCause.NORMAL; 360 } else { 361 // If nothing else matches, report unknown call drop reason 362 // to app, not NORMAL call end. 363 return DisconnectCause.ERROR_UNSPECIFIED; 364 } 365 } 366 } 367 368 /*package*/ void 369 onRemoteDisconnect(int causeCode) { 370 this.mPreciseCause = causeCode; 371 onDisconnect(disconnectCauseFromCode(causeCode)); 372 } 373 374 /** 375 * Called when the radio indicates the connection has been disconnected. 376 * @param cause call disconnect cause; values are defined in {@link DisconnectCause} 377 */ 378 /*package*/ boolean onDisconnect(int cause) { 379 boolean changed = false; 380 381 mCause = cause; 382 383 if (!mDisconnected) { 384 mIndex = -1; 385 386 mDisconnectTime = System.currentTimeMillis(); 387 mDuration = SystemClock.elapsedRealtime() - mConnectTimeReal; 388 mDisconnected = true; 389 390 if (DBG) Rlog.d(LOG_TAG, "onDisconnect: cause=" + cause); 391 392 mOwner.mPhone.notifyDisconnect(this); 393 394 if (mParent != null) { 395 changed = mParent.connectionDisconnected(this); 396 } 397 398 mOrigConnection = null; 399 } 400 clearPostDialListeners(); 401 releaseWakeLock(); 402 return changed; 403 } 404 405 // Returns true if state has changed, false if nothing changed 406 /*package*/ boolean 407 update (DriverCall dc) { 408 GsmCall newParent; 409 boolean changed = false; 410 boolean wasConnectingInOrOut = isConnectingInOrOut(); 411 boolean wasHolding = (getState() == GsmCall.State.HOLDING); 412 413 newParent = parentFromDCState(dc.state); 414 415 //Ignore dc.number and dc.name in case of a handover connection 416 if (mOrigConnection != null) { 417 if (Phone.DEBUG_PHONE) log("update: mOrigConnection is not null"); 418 } else { 419 log(" mNumberConverted " + mNumberConverted); 420 if (!equalsHandlesNulls(mAddress, dc.number) && (!mNumberConverted 421 || !equalsHandlesNulls(mConvertedNumber, dc.number))) { 422 if (Phone.DEBUG_PHONE) log("update: phone # changed!"); 423 mAddress = dc.number; 424 changed = true; 425 } 426 } 427 428 // A null cnapName should be the same as "" 429 if (TextUtils.isEmpty(dc.name)) { 430 if (!TextUtils.isEmpty(mCnapName)) { 431 changed = true; 432 mCnapName = ""; 433 } 434 } else if (!dc.name.equals(mCnapName)) { 435 changed = true; 436 mCnapName = dc.name; 437 } 438 439 if (Phone.DEBUG_PHONE) log("--dssds----"+mCnapName); 440 mCnapNamePresentation = dc.namePresentation; 441 mNumberPresentation = dc.numberPresentation; 442 443 if (newParent != mParent) { 444 if (mParent != null) { 445 mParent.detach(this); 446 } 447 newParent.attach(this, dc); 448 mParent = newParent; 449 changed = true; 450 } else { 451 boolean parentStateChange; 452 parentStateChange = mParent.update (this, dc); 453 changed = changed || parentStateChange; 454 } 455 456 /** Some state-transition events */ 457 458 if (Phone.DEBUG_PHONE) log( 459 "update: parent=" + mParent + 460 ", hasNewParent=" + (newParent != mParent) + 461 ", wasConnectingInOrOut=" + wasConnectingInOrOut + 462 ", wasHolding=" + wasHolding + 463 ", isConnectingInOrOut=" + isConnectingInOrOut() + 464 ", changed=" + changed); 465 466 467 if (wasConnectingInOrOut && !isConnectingInOrOut()) { 468 onConnectedInOrOut(); 469 } 470 471 if (changed && !wasHolding && (getState() == GsmCall.State.HOLDING)) { 472 // We've transitioned into HOLDING 473 onStartedHolding(); 474 } 475 476 return changed; 477 } 478 479 /** 480 * Called when this Connection is in the foregroundCall 481 * when a dial is initiated. 482 * We know we're ACTIVE, and we know we're going to end up 483 * HOLDING in the backgroundCall 484 */ 485 void 486 fakeHoldBeforeDial() { 487 if (mParent != null) { 488 mParent.detach(this); 489 } 490 491 mParent = mOwner.mBackgroundCall; 492 mParent.attachFake(this, GsmCall.State.HOLDING); 493 494 onStartedHolding(); 495 } 496 497 /*package*/ int 498 getGSMIndex() throws CallStateException { 499 if (mIndex >= 0) { 500 return mIndex + 1; 501 } else { 502 throw new CallStateException ("GSM index not yet assigned"); 503 } 504 } 505 506 /** 507 * An incoming or outgoing call has connected 508 */ 509 void 510 onConnectedInOrOut() { 511 mConnectTime = System.currentTimeMillis(); 512 mConnectTimeReal = SystemClock.elapsedRealtime(); 513 mDuration = 0; 514 515 // bug #678474: incoming call interpreted as missed call, even though 516 // it sounds like the user has picked up the call. 517 if (Phone.DEBUG_PHONE) { 518 log("onConnectedInOrOut: connectTime=" + mConnectTime); 519 } 520 521 if (!mIsIncoming) { 522 // outgoing calls only 523 processNextPostDialChar(); 524 } 525 releaseWakeLock(); 526 } 527 528 /*package*/ void 529 onStartedHolding() { 530 mHoldingStartTime = SystemClock.elapsedRealtime(); 531 } 532 /** 533 * Performs the appropriate action for a post-dial char, but does not 534 * notify application. returns false if the character is invalid and 535 * should be ignored 536 */ 537 private boolean 538 processPostDialChar(char c) { 539 if (PhoneNumberUtils.is12Key(c)) { 540 mOwner.mCi.sendDtmf(c, mHandler.obtainMessage(EVENT_DTMF_DONE)); 541 } else if (c == PhoneNumberUtils.PAUSE) { 542 // From TS 22.101: 543 // It continues... 544 // Upon the called party answering the UE shall send the DTMF digits 545 // automatically to the network after a delay of 3 seconds( 20 ). 546 // The digits shall be sent according to the procedures and timing 547 // specified in 3GPP TS 24.008 [13]. The first occurrence of the 548 // "DTMF Control Digits Separator" shall be used by the ME to 549 // distinguish between the addressing digits (i.e. the phone number) 550 // and the DTMF digits. Upon subsequent occurrences of the 551 // separator, 552 // the UE shall pause again for 3 seconds ( 20 ) before sending 553 // any further DTMF digits. 554 mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_PAUSE_DONE), 555 PAUSE_DELAY_MILLIS); 556 } else if (c == PhoneNumberUtils.WAIT) { 557 setPostDialState(PostDialState.WAIT); 558 } else if (c == PhoneNumberUtils.WILD) { 559 setPostDialState(PostDialState.WILD); 560 } else { 561 return false; 562 } 563 564 return true; 565 } 566 567 @Override 568 public String 569 getRemainingPostDialString() { 570 if (mPostDialState == PostDialState.CANCELLED 571 || mPostDialState == PostDialState.COMPLETE 572 || mPostDialString == null 573 || mPostDialString.length() <= mNextPostDialChar 574 ) { 575 return ""; 576 } 577 578 return mPostDialString.substring(mNextPostDialChar); 579 } 580 581 @Override 582 protected void finalize() 583 { 584 /** 585 * It is understood that This finializer is not guaranteed 586 * to be called and the release lock call is here just in 587 * case there is some path that doesn't call onDisconnect 588 * and or onConnectedInOrOut. 589 */ 590 if (mPartialWakeLock.isHeld()) { 591 Rlog.e(LOG_TAG, "[GSMConn] UNEXPECTED; mPartialWakeLock is held when finalizing."); 592 } 593 clearPostDialListeners(); 594 releaseWakeLock(); 595 } 596 597 private void 598 processNextPostDialChar() { 599 char c = 0; 600 Registrant postDialHandler; 601 602 if (mPostDialState == PostDialState.CANCELLED) { 603 //Rlog.v("GSM", "##### processNextPostDialChar: postDialState == CANCELLED, bail"); 604 return; 605 } 606 607 if (mPostDialString == null || 608 mPostDialString.length() <= mNextPostDialChar) { 609 setPostDialState(PostDialState.COMPLETE); 610 611 // notifyMessage.arg1 is 0 on complete 612 c = 0; 613 } else { 614 boolean isValid; 615 616 setPostDialState(PostDialState.STARTED); 617 618 c = mPostDialString.charAt(mNextPostDialChar++); 619 620 isValid = processPostDialChar(c); 621 622 if (!isValid) { 623 // Will call processNextPostDialChar 624 mHandler.obtainMessage(EVENT_NEXT_POST_DIAL).sendToTarget(); 625 // Don't notify application 626 Rlog.e("GSM", "processNextPostDialChar: c=" + c + " isn't valid!"); 627 return; 628 } 629 } 630 631 postDialHandler = mOwner.mPhone.mPostDialHandler; 632 633 Message notifyMessage; 634 635 if (postDialHandler != null 636 && (notifyMessage = postDialHandler.messageForRegistrant()) != null) { 637 // The AsyncResult.result is the Connection object 638 PostDialState state = mPostDialState; 639 AsyncResult ar = AsyncResult.forMessage(notifyMessage); 640 ar.result = this; 641 ar.userObj = state; 642 643 // arg1 is the character that was/is being processed 644 notifyMessage.arg1 = c; 645 646 //Rlog.v("GSM", "##### processNextPostDialChar: send msg to postDialHandler, arg1=" + c); 647 notifyMessage.sendToTarget(); 648 } 649 } 650 651 652 /** "connecting" means "has never been ACTIVE" for both incoming 653 * and outgoing calls 654 */ 655 private boolean 656 isConnectingInOrOut() { 657 return mParent == null || mParent == mOwner.mRingingCall 658 || mParent.mState == GsmCall.State.DIALING 659 || mParent.mState == GsmCall.State.ALERTING; 660 } 661 662 private GsmCall 663 parentFromDCState (DriverCall.State state) { 664 switch (state) { 665 case ACTIVE: 666 case DIALING: 667 case ALERTING: 668 return mOwner.mForegroundCall; 669 //break; 670 671 case HOLDING: 672 return mOwner.mBackgroundCall; 673 //break; 674 675 case INCOMING: 676 case WAITING: 677 return mOwner.mRingingCall; 678 //break; 679 680 default: 681 throw new RuntimeException("illegal call state: " + state); 682 } 683 } 684 685 /** 686 * Set post dial state and acquire wake lock while switching to "started" 687 * state, the wake lock will be released if state switches out of "started" 688 * state or after WAKE_LOCK_TIMEOUT_MILLIS. 689 * @param s new PostDialState 690 */ 691 private void setPostDialState(PostDialState s) { 692 if (mPostDialState != PostDialState.STARTED 693 && s == PostDialState.STARTED) { 694 acquireWakeLock(); 695 Message msg = mHandler.obtainMessage(EVENT_WAKE_LOCK_TIMEOUT); 696 mHandler.sendMessageDelayed(msg, WAKE_LOCK_TIMEOUT_MILLIS); 697 } else if (mPostDialState == PostDialState.STARTED 698 && s != PostDialState.STARTED) { 699 mHandler.removeMessages(EVENT_WAKE_LOCK_TIMEOUT); 700 releaseWakeLock(); 701 } 702 mPostDialState = s; 703 notifyPostDialListeners(); 704 } 705 706 private void 707 createWakeLock(Context context) { 708 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 709 mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG); 710 } 711 712 private void 713 acquireWakeLock() { 714 log("acquireWakeLock"); 715 mPartialWakeLock.acquire(); 716 } 717 718 private void 719 releaseWakeLock() { 720 synchronized(mPartialWakeLock) { 721 if (mPartialWakeLock.isHeld()) { 722 log("releaseWakeLock"); 723 mPartialWakeLock.release(); 724 } 725 } 726 } 727 728 private void log(String msg) { 729 Rlog.d(LOG_TAG, "[GSMConn] " + msg); 730 } 731 732 @Override 733 public int getNumberPresentation() { 734 return mNumberPresentation; 735 } 736 737 @Override 738 public UUSInfo getUUSInfo() { 739 return mUusInfo; 740 } 741 742 public int getPreciseDisconnectCause() { 743 return mPreciseCause; 744 } 745 746 @Override 747 public void migrateFrom(Connection c) { 748 if (c == null) return; 749 750 super.migrateFrom(c); 751 752 this.mUusInfo = c.getUUSInfo(); 753 754 this.setUserData(c.getUserData()); 755 } 756 757 @Override 758 public Connection getOrigConnection() { 759 return mOrigConnection; 760 } 761 762 @Override 763 public boolean isMultiparty() { 764 if (mOrigConnection != null) { 765 return mOrigConnection.isMultiparty(); 766 } 767 768 return false; 769 } 770} 771