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 17 package com.android.internal.telephony.cdma; 18 19 import android.os.AsyncResult; 20 import android.os.Handler; 21 import android.os.Message; 22 import android.os.Registrant; 23 import android.os.RegistrantList; 24 import android.telephony.PhoneNumberUtils; 25 import android.telephony.ServiceState; 26 import android.telephony.Rlog; 27 import android.os.SystemProperties; 28 29 import com.android.internal.telephony.CallStateException; 30 import com.android.internal.telephony.CallTracker; 31 import com.android.internal.telephony.CommandsInterface; 32 import com.android.internal.telephony.Connection; 33 import com.android.internal.telephony.DriverCall; 34 import com.android.internal.telephony.Phone; 35 import com.android.internal.telephony.PhoneConstants; 36 import com.android.internal.telephony.TelephonyProperties; 37 38 import java.io.FileDescriptor; 39 import java.io.PrintWriter; 40 import java.util.ArrayList; 41 import java.util.List; 42 43 44 /** 45 * {@hide} 46 */ 47 public final class CdmaCallTracker extends CallTracker { 48 static final String LOG_TAG = "CdmaCallTracker"; 49 50 private static final boolean REPEAT_POLLING = false; 51 52 private static final boolean DBG_POLL = false; 53 54 //***** Constants 55 56 static final int MAX_CONNECTIONS = 1; // only 1 connection allowed in CDMA 57 static final int MAX_CONNECTIONS_PER_CALL = 1; // only 1 connection allowed per call 58 59 //***** Instance Variables 60 61 CdmaConnection mConnections[] = new CdmaConnection[MAX_CONNECTIONS]; 62 RegistrantList mVoiceCallEndedRegistrants = new RegistrantList(); 63 RegistrantList mVoiceCallStartedRegistrants = new RegistrantList(); 64 RegistrantList mCallWaitingRegistrants = new RegistrantList(); 65 66 67 // connections dropped during last poll 68 ArrayList<CdmaConnection> mDroppedDuringPoll 69 = new ArrayList<CdmaConnection>(MAX_CONNECTIONS); 70 71 CdmaCall mRingingCall = new CdmaCall(this); 72 // A call that is ringing or (call) waiting 73 CdmaCall mForegroundCall = new CdmaCall(this); 74 CdmaCall mBackgroundCall = new CdmaCall(this); 75 76 CdmaConnection mPendingMO; 77 boolean mHangupPendingMO; 78 boolean mPendingCallInEcm=false; 79 boolean mIsInEmergencyCall = false; 80 CDMAPhone mPhone; 81 82 boolean mDesiredMute = false; // false = mute off 83 84 int mPendingCallClirMode; 85 PhoneConstants.State mState = PhoneConstants.State.IDLE; 86 87 private boolean mIsEcmTimerCanceled = false; 88 89 // boolean needsPoll; 90 91 92 93 //***** Events 94 95 //***** Constructors 96 CdmaCallTracker(CDMAPhone phone) { 97 mPhone = phone; 98 mCi = phone.mCi; 99 mCi.registerForCallStateChanged(this, EVENT_CALL_STATE_CHANGE, null); 100 mCi.registerForOn(this, EVENT_RADIO_AVAILABLE, null); 101 mCi.registerForNotAvailable(this, EVENT_RADIO_NOT_AVAILABLE, null); 102 mCi.registerForCallWaitingInfo(this, EVENT_CALL_WAITING_INFO_CDMA, null); 103 mForegroundCall.setGeneric(false); 104 } 105 106 public void dispose() { 107 mCi.unregisterForCallStateChanged(this); 108 mCi.unregisterForOn(this); 109 mCi.unregisterForNotAvailable(this); 110 mCi.unregisterForCallWaitingInfo(this); 111 for(CdmaConnection c : mConnections) { 112 try { 113 if(c != null) hangup(c); 114 } catch (CallStateException ex) { 115 Rlog.e(LOG_TAG, "unexpected error on hangup during dispose"); 116 } 117 } 118 119 try { 120 if(mPendingMO != null) hangup(mPendingMO); 121 } catch (CallStateException ex) { 122 Rlog.e(LOG_TAG, "unexpected error on hangup during dispose"); 123 } 124 125 clearDisconnected(); 126 127 } 128 129 @Override 130 protected void finalize() { 131 Rlog.d(LOG_TAG, "CdmaCallTracker finalized"); 132 } 133 134 //***** Instance Methods 135 136 //***** Public Methods 137 @Override 138 public void registerForVoiceCallStarted(Handler h, int what, Object obj) { 139 Registrant r = new Registrant(h, what, obj); 140 mVoiceCallStartedRegistrants.add(r); 141 // Notify if in call when registering 142 if (mState != PhoneConstants.State.IDLE) { 143 r.notifyRegistrant(new AsyncResult(null, null, null)); 144 } 145 } 146 @Override 147 public void unregisterForVoiceCallStarted(Handler h) { 148 mVoiceCallStartedRegistrants.remove(h); 149 } 150 151 @Override 152 public void registerForVoiceCallEnded(Handler h, int what, Object obj) { 153 Registrant r = new Registrant(h, what, obj); 154 mVoiceCallEndedRegistrants.add(r); 155 } 156 157 @Override 158 public void unregisterForVoiceCallEnded(Handler h) { 159 mVoiceCallEndedRegistrants.remove(h); 160 } 161 162 public void registerForCallWaiting(Handler h, int what, Object obj) { 163 Registrant r = new Registrant (h, what, obj); 164 mCallWaitingRegistrants.add(r); 165 } 166 167 public void unregisterForCallWaiting(Handler h) { 168 mCallWaitingRegistrants.remove(h); 169 } 170 171 /** 172 * clirMode is one of the CLIR_ constants 173 */ 174 Connection 175 dial (String dialString, int clirMode) throws CallStateException { 176 // note that this triggers call state changed notif 177 clearDisconnected(); 178 179 if (!canDial()) { 180 throw new CallStateException("cannot dial in current state"); 181 } 182 183 String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false"); 184 boolean isPhoneInEcmMode = inEcm.equals("true"); 185 boolean isEmergencyCall = 186 PhoneNumberUtils.isLocalEmergencyNumber(dialString, mPhone.getContext()); 187 188 // Cancel Ecm timer if a second emergency call is originating in Ecm mode 189 if (isPhoneInEcmMode && isEmergencyCall) { 190 handleEcmTimer(CDMAPhone.CANCEL_ECM_TIMER); 191 } 192 193 // We are initiating a call therefore even if we previously 194 // didn't know the state (i.e. Generic was true) we now know 195 // and therefore can set Generic to false. 196 mForegroundCall.setGeneric(false); 197 198 // The new call must be assigned to the foreground call. 199 // That call must be idle, so place anything that's 200 // there on hold 201 if (mForegroundCall.getState() == CdmaCall.State.ACTIVE) { 202 return dialThreeWay(dialString); 203 } 204 205 mPendingMO = new CdmaConnection(mPhone.getContext(), checkForTestEmergencyNumber(dialString), 206 this, mForegroundCall); 207 mHangupPendingMO = false; 208 209 if (mPendingMO.mAddress == null || mPendingMO.mAddress.length() == 0 210 || mPendingMO.mAddress.indexOf(PhoneNumberUtils.WILD) >= 0) { 211 // Phone number is invalid 212 mPendingMO.mCause = Connection.DisconnectCause.INVALID_NUMBER; 213 214 // handlePollCalls() will notice this call not present 215 // and will mark it as dropped. 216 pollCallsWhenSafe(); 217 } else { 218 // Always unmute when initiating a new call 219 setMute(false); 220 221 // Check data call 222 disableDataCallInEmergencyCall(dialString); 223 224 // In Ecm mode, if another emergency call is dialed, Ecm mode will not exit. 225 if(!isPhoneInEcmMode || (isPhoneInEcmMode && isEmergencyCall)) { 226 mCi.dial(mPendingMO.mAddress, clirMode, obtainCompleteMessage()); 227 } else { 228 mPhone.exitEmergencyCallbackMode(); 229 mPhone.setOnEcbModeExitResponse(this,EVENT_EXIT_ECM_RESPONSE_CDMA, null); 230 mPendingCallClirMode=clirMode; 231 mPendingCallInEcm=true; 232 } 233 } 234 235 updatePhoneState(); 236 mPhone.notifyPreciseCallStateChanged(); 237 238 return mPendingMO; 239 } 240 241 242 Connection 243 dial (String dialString) throws CallStateException { 244 return dial(dialString, CommandsInterface.CLIR_DEFAULT); 245 } 246 247 private Connection 248 dialThreeWay (String dialString) { 249 if (!mForegroundCall.isIdle()) { 250 // Check data call 251 disableDataCallInEmergencyCall(dialString); 252 253 // Attach the new connection to foregroundCall 254 mPendingMO = new CdmaConnection(mPhone.getContext(), 255 checkForTestEmergencyNumber(dialString), this, mForegroundCall); 256 mCi.sendCDMAFeatureCode(mPendingMO.mAddress, 257 obtainMessage(EVENT_THREE_WAY_DIAL_L2_RESULT_CDMA)); 258 return mPendingMO; 259 } 260 return null; 261 } 262 263 void 264 acceptCall() throws CallStateException { 265 if (mRingingCall.getState() == CdmaCall.State.INCOMING) { 266 Rlog.i("phone", "acceptCall: incoming..."); 267 // Always unmute when answering a new call 268 setMute(false); 269 mCi.acceptCall(obtainCompleteMessage()); 270 } else if (mRingingCall.getState() == CdmaCall.State.WAITING) { 271 CdmaConnection cwConn = (CdmaConnection)(mRingingCall.getLatestConnection()); 272 273 // Since there is no network response for supplimentary 274 // service for CDMA, we assume call waiting is answered. 275 // ringing Call state change to idle is in CdmaCall.detach 276 // triggered by updateParent. 277 cwConn.updateParent(mRingingCall, mForegroundCall); 278 cwConn.onConnectedInOrOut(); 279 updatePhoneState(); 280 switchWaitingOrHoldingAndActive(); 281 } else { 282 throw new CallStateException("phone not ringing"); 283 } 284 } 285 286 void 287 rejectCall () throws CallStateException { 288 // AT+CHLD=0 means "release held or UDUB" 289 // so if the phone isn't ringing, this could hang up held 290 if (mRingingCall.getState().isRinging()) { 291 mCi.rejectCall(obtainCompleteMessage()); 292 } else { 293 throw new CallStateException("phone not ringing"); 294 } 295 } 296 297 void 298 switchWaitingOrHoldingAndActive() throws CallStateException { 299 // Should we bother with this check? 300 if (mRingingCall.getState() == CdmaCall.State.INCOMING) { 301 throw new CallStateException("cannot be in the incoming state"); 302 } else if (mForegroundCall.getConnections().size() > 1) { 303 flashAndSetGenericTrue(); 304 } else { 305 // Send a flash command to CDMA network for putting the other party on hold. 306 // For CDMA networks which do not support this the user would just hear a beep 307 // from the network. For CDMA networks which do support it will put the other 308 // party on hold. 309 mCi.sendCDMAFeatureCode("", obtainMessage(EVENT_SWITCH_RESULT)); 310 } 311 } 312 313 void 314 conference() { 315 // Should we be checking state? 316 flashAndSetGenericTrue(); 317 } 318 319 void 320 explicitCallTransfer() { 321 mCi.explicitCallTransfer(obtainCompleteMessage(EVENT_ECT_RESULT)); 322 } 323 324 void 325 clearDisconnected() { 326 internalClearDisconnected(); 327 328 updatePhoneState(); 329 mPhone.notifyPreciseCallStateChanged(); 330 } 331 332 boolean 333 canConference() { 334 return mForegroundCall.getState() == CdmaCall.State.ACTIVE 335 && mBackgroundCall.getState() == CdmaCall.State.HOLDING 336 && !mBackgroundCall.isFull() 337 && !mForegroundCall.isFull(); 338 } 339 340 boolean 341 canDial() { 342 boolean ret; 343 int serviceState = mPhone.getServiceState().getState(); 344 String disableCall = SystemProperties.get( 345 TelephonyProperties.PROPERTY_DISABLE_CALL, "false"); 346 347 ret = (serviceState != ServiceState.STATE_POWER_OFF) 348 && mPendingMO == null 349 && !mRingingCall.isRinging() 350 && !disableCall.equals("true") 351 && (!mForegroundCall.getState().isAlive() 352 || (mForegroundCall.getState() == CdmaCall.State.ACTIVE) 353 || !mBackgroundCall.getState().isAlive()); 354 355 if (!ret) { 356 log(String.format("canDial is false\n" + 357 "((serviceState=%d) != ServiceState.STATE_POWER_OFF)::=%s\n" + 358 "&& pendingMO == null::=%s\n" + 359 "&& !ringingCall.isRinging()::=%s\n" + 360 "&& !disableCall.equals(\"true\")::=%s\n" + 361 "&& (!foregroundCall.getState().isAlive()::=%s\n" + 362 " || foregroundCall.getState() == CdmaCall.State.ACTIVE::=%s\n" + 363 " ||!backgroundCall.getState().isAlive())::=%s)", 364 serviceState, 365 serviceState != ServiceState.STATE_POWER_OFF, 366 mPendingMO == null, 367 !mRingingCall.isRinging(), 368 !disableCall.equals("true"), 369 !mForegroundCall.getState().isAlive(), 370 mForegroundCall.getState() == CdmaCall.State.ACTIVE, 371 !mBackgroundCall.getState().isAlive())); 372 } 373 return ret; 374 } 375 376 boolean 377 canTransfer() { 378 Rlog.e(LOG_TAG, "canTransfer: not possible in CDMA"); 379 return false; 380 } 381 382 //***** Private Instance Methods 383 384 private void 385 internalClearDisconnected() { 386 mRingingCall.clearDisconnected(); 387 mForegroundCall.clearDisconnected(); 388 mBackgroundCall.clearDisconnected(); 389 } 390 391 /** 392 * Obtain a message to use for signalling "invoke getCurrentCalls() when 393 * this operation and all other pending operations are complete 394 */ 395 private Message 396 obtainCompleteMessage() { 397 return obtainCompleteMessage(EVENT_OPERATION_COMPLETE); 398 } 399 400 /** 401 * Obtain a message to use for signalling "invoke getCurrentCalls() when 402 * this operation and all other pending operations are complete 403 */ 404 private Message 405 obtainCompleteMessage(int what) { 406 mPendingOperations++; 407 mLastRelevantPoll = null; 408 mNeedsPoll = true; 409 410 if (DBG_POLL) log("obtainCompleteMessage: pendingOperations=" + 411 mPendingOperations + ", needsPoll=" + mNeedsPoll); 412 413 return obtainMessage(what); 414 } 415 416 private void 417 operationComplete() { 418 mPendingOperations--; 419 420 if (DBG_POLL) log("operationComplete: pendingOperations=" + 421 mPendingOperations + ", needsPoll=" + mNeedsPoll); 422 423 if (mPendingOperations == 0 && mNeedsPoll) { 424 mLastRelevantPoll = obtainMessage(EVENT_POLL_CALLS_RESULT); 425 mCi.getCurrentCalls(mLastRelevantPoll); 426 } else if (mPendingOperations < 0) { 427 // this should never happen 428 Rlog.e(LOG_TAG,"CdmaCallTracker.pendingOperations < 0"); 429 mPendingOperations = 0; 430 } 431 } 432 433 434 435 private void 436 updatePhoneState() { 437 PhoneConstants.State oldState = mState; 438 439 if (mRingingCall.isRinging()) { 440 mState = PhoneConstants.State.RINGING; 441 } else if (mPendingMO != null || 442 !(mForegroundCall.isIdle() && mBackgroundCall.isIdle())) { 443 mState = PhoneConstants.State.OFFHOOK; 444 } else { 445 mState = PhoneConstants.State.IDLE; 446 } 447 448 if (mState == PhoneConstants.State.IDLE && oldState != mState) { 449 mVoiceCallEndedRegistrants.notifyRegistrants( 450 new AsyncResult(null, null, null)); 451 } else if (oldState == PhoneConstants.State.IDLE && oldState != mState) { 452 mVoiceCallStartedRegistrants.notifyRegistrants ( 453 new AsyncResult(null, null, null)); 454 } 455 if (Phone.DEBUG_PHONE) { 456 log("update phone state, old=" + oldState + " new="+ mState); 457 } 458 if (mState != oldState) { 459 mPhone.notifyPhoneStateChanged(); 460 } 461 } 462 463 // ***** Overwritten from CallTracker 464 465 @Override 466 protected void 467 handlePollCalls(AsyncResult ar) { 468 List polledCalls; 469 470 if (ar.exception == null) { 471 polledCalls = (List)ar.result; 472 } else if (isCommandExceptionRadioNotAvailable(ar.exception)) { 473 // just a dummy empty ArrayList to cause the loop 474 // to hang up all the calls 475 polledCalls = new ArrayList(); 476 } else { 477 // Radio probably wasn't ready--try again in a bit 478 // But don't keep polling if the channel is closed 479 pollCallsAfterDelay(); 480 return; 481 } 482 483 Connection newRinging = null; //or waiting 484 boolean hasNonHangupStateChanged = false; // Any change besides 485 // a dropped connection 486 boolean needsPollDelay = false; 487 boolean unknownConnectionAppeared = false; 488 489 for (int i = 0, curDC = 0, dcSize = polledCalls.size() 490 ; i < mConnections.length; i++) { 491 CdmaConnection conn = mConnections[i]; 492 DriverCall dc = null; 493 494 // polledCall list is sparse 495 if (curDC < dcSize) { 496 dc = (DriverCall) polledCalls.get(curDC); 497 498 if (dc.index == i+1) { 499 curDC++; 500 } else { 501 dc = null; 502 } 503 } 504 505 if (DBG_POLL) log("poll: conn[i=" + i + "]=" + 506 conn+", dc=" + dc); 507 508 if (conn == null && dc != null) { 509 // Connection appeared in CLCC response that we don't know about 510 if (mPendingMO != null && mPendingMO.compareTo(dc)) { 511 512 if (DBG_POLL) log("poll: pendingMO=" + mPendingMO); 513 514 // It's our pending mobile originating call 515 mConnections[i] = mPendingMO; 516 mPendingMO.mIndex = i; 517 mPendingMO.update(dc); 518 mPendingMO = null; 519 520 // Someone has already asked to hangup this call 521 if (mHangupPendingMO) { 522 mHangupPendingMO = false; 523 // Re-start Ecm timer when an uncompleted emergency call ends 524 if (mIsEcmTimerCanceled) { 525 handleEcmTimer(CDMAPhone.RESTART_ECM_TIMER); 526 } 527 528 try { 529 if (Phone.DEBUG_PHONE) log( 530 "poll: hangupPendingMO, hangup conn " + i); 531 hangup(mConnections[i]); 532 } catch (CallStateException ex) { 533 Rlog.e(LOG_TAG, "unexpected error on hangup"); 534 } 535 536 // Do not continue processing this poll 537 // Wait for hangup and repoll 538 return; 539 } 540 } else { 541 if (Phone.DEBUG_PHONE) { 542 log("pendingMo=" + mPendingMO + ", dc=" + dc); 543 } 544 // find if the MT call is a new ring or unknown connection 545 newRinging = checkMtFindNewRinging(dc,i); 546 if (newRinging == null) { 547 unknownConnectionAppeared = true; 548 } 549 checkAndEnableDataCallAfterEmergencyCallDropped(); 550 } 551 hasNonHangupStateChanged = true; 552 } else if (conn != null && dc == null) { 553 // This case means the RIL has no more active call anymore and 554 // we need to clean up the foregroundCall and ringingCall. 555 // Loop through foreground call connections as 556 // it contains the known logical connections. 557 int count = mForegroundCall.mConnections.size(); 558 for (int n = 0; n < count; n++) { 559 if (Phone.DEBUG_PHONE) log("adding fgCall cn " + n + " to droppedDuringPoll"); 560 CdmaConnection cn = (CdmaConnection)mForegroundCall.mConnections.get(n); 561 mDroppedDuringPoll.add(cn); 562 } 563 count = mRingingCall.mConnections.size(); 564 // Loop through ringing call connections as 565 // it may contain the known logical connections. 566 for (int n = 0; n < count; n++) { 567 if (Phone.DEBUG_PHONE) log("adding rgCall cn " + n + " to droppedDuringPoll"); 568 CdmaConnection cn = (CdmaConnection)mRingingCall.mConnections.get(n); 569 mDroppedDuringPoll.add(cn); 570 } 571 mForegroundCall.setGeneric(false); 572 mRingingCall.setGeneric(false); 573 574 // Re-start Ecm timer when the connected emergency call ends 575 if (mIsEcmTimerCanceled) { 576 handleEcmTimer(CDMAPhone.RESTART_ECM_TIMER); 577 } 578 // If emergency call is not going through while dialing 579 checkAndEnableDataCallAfterEmergencyCallDropped(); 580 581 // Dropped connections are removed from the CallTracker 582 // list but kept in the Call list 583 mConnections[i] = null; 584 } else if (conn != null && dc != null) { /* implicit conn.compareTo(dc) */ 585 // Call collision case 586 if (conn.mIsIncoming != dc.isMT) { 587 if (dc.isMT == true){ 588 // Mt call takes precedence than Mo,drops Mo 589 mDroppedDuringPoll.add(conn); 590 // find if the MT call is a new ring or unknown connection 591 newRinging = checkMtFindNewRinging(dc,i); 592 if (newRinging == null) { 593 unknownConnectionAppeared = true; 594 } 595 checkAndEnableDataCallAfterEmergencyCallDropped(); 596 } else { 597 // Call info stored in conn is not consistent with the call info from dc. 598 // We should follow the rule of MT calls taking precedence over MO calls 599 // when there is conflict, so here we drop the call info from dc and 600 // continue to use the call info from conn, and only take a log. 601 Rlog.e(LOG_TAG,"Error in RIL, Phantom call appeared " + dc); 602 } 603 } else { 604 boolean changed; 605 changed = conn.update(dc); 606 hasNonHangupStateChanged = hasNonHangupStateChanged || changed; 607 } 608 } 609 610 if (REPEAT_POLLING) { 611 if (dc != null) { 612 // FIXME with RIL, we should not need this anymore 613 if ((dc.state == DriverCall.State.DIALING 614 /*&& cm.getOption(cm.OPTION_POLL_DIALING)*/) 615 || (dc.state == DriverCall.State.ALERTING 616 /*&& cm.getOption(cm.OPTION_POLL_ALERTING)*/) 617 || (dc.state == DriverCall.State.INCOMING 618 /*&& cm.getOption(cm.OPTION_POLL_INCOMING)*/) 619 || (dc.state == DriverCall.State.WAITING 620 /*&& cm.getOption(cm.OPTION_POLL_WAITING)*/) 621 ) { 622 // Sometimes there's no unsolicited notification 623 // for state transitions 624 needsPollDelay = true; 625 } 626 } 627 } 628 } 629 630 // This is the first poll after an ATD. 631 // We expect the pending call to appear in the list 632 // If it does not, we land here 633 if (mPendingMO != null) { 634 Rlog.d(LOG_TAG,"Pending MO dropped before poll fg state:" 635 + mForegroundCall.getState()); 636 637 mDroppedDuringPoll.add(mPendingMO); 638 mPendingMO = null; 639 mHangupPendingMO = false; 640 if( mPendingCallInEcm) { 641 mPendingCallInEcm = false; 642 } 643 } 644 645 if (newRinging != null) { 646 mPhone.notifyNewRingingConnection(newRinging); 647 } 648 649 // clear the "local hangup" and "missed/rejected call" 650 // cases from the "dropped during poll" list 651 // These cases need no "last call fail" reason 652 for (int i = mDroppedDuringPoll.size() - 1; i >= 0 ; i--) { 653 CdmaConnection conn = mDroppedDuringPoll.get(i); 654 655 if (conn.isIncoming() && conn.getConnectTime() == 0) { 656 // Missed or rejected call 657 Connection.DisconnectCause cause; 658 if (conn.mCause == Connection.DisconnectCause.LOCAL) { 659 cause = Connection.DisconnectCause.INCOMING_REJECTED; 660 } else { 661 cause = Connection.DisconnectCause.INCOMING_MISSED; 662 } 663 664 if (Phone.DEBUG_PHONE) { 665 log("missed/rejected call, conn.cause=" + conn.mCause); 666 log("setting cause to " + cause); 667 } 668 mDroppedDuringPoll.remove(i); 669 conn.onDisconnect(cause); 670 } else if (conn.mCause == Connection.DisconnectCause.LOCAL) { 671 // Local hangup 672 mDroppedDuringPoll.remove(i); 673 conn.onDisconnect(Connection.DisconnectCause.LOCAL); 674 } else if (conn.mCause == Connection.DisconnectCause.INVALID_NUMBER) { 675 mDroppedDuringPoll.remove(i); 676 conn.onDisconnect(Connection.DisconnectCause.INVALID_NUMBER); 677 } 678 } 679 680 // Any non-local disconnects: determine cause 681 if (mDroppedDuringPoll.size() > 0) { 682 mCi.getLastCallFailCause( 683 obtainNoPollCompleteMessage(EVENT_GET_LAST_CALL_FAIL_CAUSE)); 684 } 685 686 if (needsPollDelay) { 687 pollCallsAfterDelay(); 688 } 689 690 // Cases when we can no longer keep disconnected Connection's 691 // with their previous calls 692 // 1) the phone has started to ring 693 // 2) A Call/Connection object has changed state... 694 // we may have switched or held or answered (but not hung up) 695 if (newRinging != null || hasNonHangupStateChanged) { 696 internalClearDisconnected(); 697 } 698 699 updatePhoneState(); 700 701 if (unknownConnectionAppeared) { 702 mPhone.notifyUnknownConnection(); 703 } 704 705 if (hasNonHangupStateChanged || newRinging != null) { 706 mPhone.notifyPreciseCallStateChanged(); 707 } 708 709 //dumpState(); 710 } 711 712 //***** Called from CdmaConnection 713 /*package*/ void 714 hangup (CdmaConnection conn) throws CallStateException { 715 if (conn.mOwner != this) { 716 throw new CallStateException ("CdmaConnection " + conn 717 + "does not belong to CdmaCallTracker " + this); 718 } 719 720 if (conn == mPendingMO) { 721 // We're hanging up an outgoing call that doesn't have it's 722 // GSM index assigned yet 723 724 if (Phone.DEBUG_PHONE) log("hangup: set hangupPendingMO to true"); 725 mHangupPendingMO = true; 726 } else if ((conn.getCall() == mRingingCall) 727 && (mRingingCall.getState() == CdmaCall.State.WAITING)) { 728 // Handle call waiting hang up case. 729 // 730 // The ringingCall state will change to IDLE in CdmaCall.detach 731 // if the ringing call connection size is 0. We don't specifically 732 // set the ringing call state to IDLE here to avoid a race condition 733 // where a new call waiting could get a hang up from an old call 734 // waiting ringingCall. 735 // 736 // PhoneApp does the call log itself since only PhoneApp knows 737 // the hangup reason is user ignoring or timing out. So conn.onDisconnect() 738 // is not called here. Instead, conn.onLocalDisconnect() is called. 739 conn.onLocalDisconnect(); 740 updatePhoneState(); 741 mPhone.notifyPreciseCallStateChanged(); 742 return; 743 } else { 744 try { 745 mCi.hangupConnection (conn.getCDMAIndex(), obtainCompleteMessage()); 746 } catch (CallStateException ex) { 747 // Ignore "connection not found" 748 // Call may have hung up already 749 Rlog.w(LOG_TAG,"CdmaCallTracker WARN: hangup() on absent connection " 750 + conn); 751 } 752 } 753 754 conn.onHangupLocal(); 755 } 756 757 /*package*/ void 758 separate (CdmaConnection conn) throws CallStateException { 759 if (conn.mOwner != this) { 760 throw new CallStateException ("CdmaConnection " + conn 761 + "does not belong to CdmaCallTracker " + this); 762 } 763 try { 764 mCi.separateConnection (conn.getCDMAIndex(), 765 obtainCompleteMessage(EVENT_SEPARATE_RESULT)); 766 } catch (CallStateException ex) { 767 // Ignore "connection not found" 768 // Call may have hung up already 769 Rlog.w(LOG_TAG,"CdmaCallTracker WARN: separate() on absent connection " 770 + conn); 771 } 772 } 773 774 //***** Called from CDMAPhone 775 776 /*package*/ void 777 setMute(boolean mute) { 778 mDesiredMute = mute; 779 mCi.setMute(mDesiredMute, null); 780 } 781 782 /*package*/ boolean 783 getMute() { 784 return mDesiredMute; 785 } 786 787 788 //***** Called from CdmaCall 789 790 /* package */ void 791 hangup (CdmaCall call) throws CallStateException { 792 if (call.getConnections().size() == 0) { 793 throw new CallStateException("no connections in call"); 794 } 795 796 if (call == mRingingCall) { 797 if (Phone.DEBUG_PHONE) log("(ringing) hangup waiting or background"); 798 mCi.hangupWaitingOrBackground(obtainCompleteMessage()); 799 } else if (call == mForegroundCall) { 800 if (call.isDialingOrAlerting()) { 801 if (Phone.DEBUG_PHONE) { 802 log("(foregnd) hangup dialing or alerting..."); 803 } 804 hangup((CdmaConnection)(call.getConnections().get(0))); 805 } else { 806 hangupForegroundResumeBackground(); 807 } 808 } else if (call == mBackgroundCall) { 809 if (mRingingCall.isRinging()) { 810 if (Phone.DEBUG_PHONE) { 811 log("hangup all conns in background call"); 812 } 813 hangupAllConnections(call); 814 } else { 815 hangupWaitingOrBackground(); 816 } 817 } else { 818 throw new RuntimeException ("CdmaCall " + call + 819 "does not belong to CdmaCallTracker " + this); 820 } 821 822 call.onHangupLocal(); 823 mPhone.notifyPreciseCallStateChanged(); 824 } 825 826 /* package */ 827 void hangupWaitingOrBackground() { 828 if (Phone.DEBUG_PHONE) log("hangupWaitingOrBackground"); 829 mCi.hangupWaitingOrBackground(obtainCompleteMessage()); 830 } 831 832 /* package */ 833 void hangupForegroundResumeBackground() { 834 if (Phone.DEBUG_PHONE) log("hangupForegroundResumeBackground"); 835 mCi.hangupForegroundResumeBackground(obtainCompleteMessage()); 836 } 837 838 void hangupConnectionByIndex(CdmaCall call, int index) 839 throws CallStateException { 840 int count = call.mConnections.size(); 841 for (int i = 0; i < count; i++) { 842 CdmaConnection cn = (CdmaConnection)call.mConnections.get(i); 843 if (cn.getCDMAIndex() == index) { 844 mCi.hangupConnection(index, obtainCompleteMessage()); 845 return; 846 } 847 } 848 849 throw new CallStateException("no gsm index found"); 850 } 851 852 void hangupAllConnections(CdmaCall call) { 853 try { 854 int count = call.mConnections.size(); 855 for (int i = 0; i < count; i++) { 856 CdmaConnection cn = (CdmaConnection)call.mConnections.get(i); 857 mCi.hangupConnection(cn.getCDMAIndex(), obtainCompleteMessage()); 858 } 859 } catch (CallStateException ex) { 860 Rlog.e(LOG_TAG, "hangupConnectionByIndex caught " + ex); 861 } 862 } 863 864 /* package */ 865 CdmaConnection getConnectionByIndex(CdmaCall call, int index) 866 throws CallStateException { 867 int count = call.mConnections.size(); 868 for (int i = 0; i < count; i++) { 869 CdmaConnection cn = (CdmaConnection)call.mConnections.get(i); 870 if (cn.getCDMAIndex() == index) { 871 return cn; 872 } 873 } 874 875 return null; 876 } 877 878 private void flashAndSetGenericTrue() { 879 mCi.sendCDMAFeatureCode("", obtainMessage(EVENT_SWITCH_RESULT)); 880 881 // Set generic to true because in CDMA it is not known what 882 // the status of the call is after a call waiting is answered, 883 // 3 way call merged or a switch between calls. 884 mForegroundCall.setGeneric(true); 885 mPhone.notifyPreciseCallStateChanged(); 886 } 887 888 private void handleRadioNotAvailable() { 889 // handlePollCalls will clear out its 890 // call list when it gets the CommandException 891 // error result from this 892 pollCallsWhenSafe(); 893 } 894 895 private void notifyCallWaitingInfo(CdmaCallWaitingNotification obj) { 896 if (mCallWaitingRegistrants != null) { 897 mCallWaitingRegistrants.notifyRegistrants(new AsyncResult(null, obj, null)); 898 } 899 } 900 901 private void handleCallWaitingInfo (CdmaCallWaitingNotification cw) { 902 // Check how many connections in foregroundCall. 903 // If the connection in foregroundCall is more 904 // than one, then the connection information is 905 // not reliable anymore since it means either 906 // call waiting is connected or 3 way call is 907 // dialed before, so set generic. 908 if (mForegroundCall.mConnections.size() > 1 ) { 909 mForegroundCall.setGeneric(true); 910 } 911 912 // Create a new CdmaConnection which attaches itself to ringingCall. 913 mRingingCall.setGeneric(false); 914 new CdmaConnection(mPhone.getContext(), cw, this, mRingingCall); 915 updatePhoneState(); 916 917 // Finally notify application 918 notifyCallWaitingInfo(cw); 919 } 920 //****** Overridden from Handler 921 922 @Override 923 public void 924 handleMessage (Message msg) { 925 AsyncResult ar; 926 927 switch (msg.what) { 928 case EVENT_POLL_CALLS_RESULT:{ 929 Rlog.d(LOG_TAG, "Event EVENT_POLL_CALLS_RESULT Received"); 930 ar = (AsyncResult)msg.obj; 931 932 if(msg == mLastRelevantPoll) { 933 if(DBG_POLL) log( 934 "handle EVENT_POLL_CALL_RESULT: set needsPoll=F"); 935 mNeedsPoll = false; 936 mLastRelevantPoll = null; 937 handlePollCalls((AsyncResult)msg.obj); 938 } 939 } 940 break; 941 942 case EVENT_OPERATION_COMPLETE: 943 operationComplete(); 944 break; 945 946 case EVENT_SWITCH_RESULT: 947 // In GSM call operationComplete() here which gets the 948 // current call list. But in CDMA there is no list so 949 // there is nothing to do. 950 break; 951 952 case EVENT_GET_LAST_CALL_FAIL_CAUSE: 953 int causeCode; 954 ar = (AsyncResult)msg.obj; 955 956 operationComplete(); 957 958 if (ar.exception != null) { 959 // An exception occurred...just treat the disconnect 960 // cause as "normal" 961 causeCode = CallFailCause.NORMAL_CLEARING; 962 Rlog.i(LOG_TAG, 963 "Exception during getLastCallFailCause, assuming normal disconnect"); 964 } else { 965 causeCode = ((int[])ar.result)[0]; 966 } 967 968 for (int i = 0, s = mDroppedDuringPoll.size() 969 ; i < s ; i++ 970 ) { 971 CdmaConnection conn = mDroppedDuringPoll.get(i); 972 973 conn.onRemoteDisconnect(causeCode); 974 } 975 976 updatePhoneState(); 977 978 mPhone.notifyPreciseCallStateChanged(); 979 mDroppedDuringPoll.clear(); 980 break; 981 982 case EVENT_REPOLL_AFTER_DELAY: 983 case EVENT_CALL_STATE_CHANGE: 984 pollCallsWhenSafe(); 985 break; 986 987 case EVENT_RADIO_AVAILABLE: 988 handleRadioAvailable(); 989 break; 990 991 case EVENT_RADIO_NOT_AVAILABLE: 992 handleRadioNotAvailable(); 993 break; 994 995 case EVENT_EXIT_ECM_RESPONSE_CDMA: 996 //no matter the result, we still do the same here 997 if (mPendingCallInEcm) { 998 mCi.dial(mPendingMO.mAddress, mPendingCallClirMode, obtainCompleteMessage()); 999 mPendingCallInEcm = false; 1000 } 1001 mPhone.unsetOnEcbModeExitResponse(this); 1002 break; 1003 1004 case EVENT_CALL_WAITING_INFO_CDMA: 1005 ar = (AsyncResult)msg.obj; 1006 if (ar.exception == null) { 1007 handleCallWaitingInfo((CdmaCallWaitingNotification)ar.result); 1008 Rlog.d(LOG_TAG, "Event EVENT_CALL_WAITING_INFO_CDMA Received"); 1009 } 1010 break; 1011 1012 case EVENT_THREE_WAY_DIAL_L2_RESULT_CDMA: 1013 ar = (AsyncResult)msg.obj; 1014 if (ar.exception == null) { 1015 // Assume 3 way call is connected 1016 mPendingMO.onConnectedInOrOut(); 1017 mPendingMO = null; 1018 } 1019 break; 1020 1021 default:{ 1022 throw new RuntimeException("unexpected event not handled"); 1023 } 1024 } 1025 } 1026 1027 /** 1028 * Handle Ecm timer to be canceled or re-started 1029 */ 1030 private void handleEcmTimer(int action) { 1031 mPhone.handleTimerInEmergencyCallbackMode(action); 1032 switch(action) { 1033 case CDMAPhone.CANCEL_ECM_TIMER: mIsEcmTimerCanceled = true; break; 1034 case CDMAPhone.RESTART_ECM_TIMER: mIsEcmTimerCanceled = false; break; 1035 default: 1036 Rlog.e(LOG_TAG, "handleEcmTimer, unsupported action " + action); 1037 } 1038 } 1039 1040 /** 1041 * Disable data call when emergency call is connected 1042 */ 1043 private void disableDataCallInEmergencyCall(String dialString) { 1044 if (PhoneNumberUtils.isLocalEmergencyNumber(dialString, mPhone.getContext())) { 1045 if (Phone.DEBUG_PHONE) log("disableDataCallInEmergencyCall"); 1046 mIsInEmergencyCall = true; 1047 mPhone.mDcTracker.setInternalDataEnabled(false); 1048 } 1049 } 1050 1051 /** 1052 * Check and enable data call after an emergency call is dropped if it's 1053 * not in ECM 1054 */ 1055 private void checkAndEnableDataCallAfterEmergencyCallDropped() { 1056 if (mIsInEmergencyCall) { 1057 mIsInEmergencyCall = false; 1058 String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false"); 1059 if (Phone.DEBUG_PHONE) { 1060 log("checkAndEnableDataCallAfterEmergencyCallDropped,inEcm=" + inEcm); 1061 } 1062 if (inEcm.compareTo("false") == 0) { 1063 // Re-initiate data connection 1064 mPhone.mDcTracker.setInternalDataEnabled(true); 1065 } 1066 } 1067 } 1068 1069 /** 1070 * Check the MT call to see if it's a new ring or 1071 * a unknown connection. 1072 */ 1073 private Connection checkMtFindNewRinging(DriverCall dc, int i) { 1074 1075 Connection newRinging = null; 1076 1077 mConnections[i] = new CdmaConnection(mPhone.getContext(), dc, this, i); 1078 // it's a ringing call 1079 if (mConnections[i].getCall() == mRingingCall) { 1080 newRinging = mConnections[i]; 1081 if (Phone.DEBUG_PHONE) log("Notify new ring " + dc); 1082 } else { 1083 // Something strange happened: a call which is neither 1084 // a ringing call nor the one we created. It could be the 1085 // call collision result from RIL 1086 Rlog.e(LOG_TAG,"Phantom call appeared " + dc); 1087 // If it's a connected call, set the connect time so that 1088 // it's non-zero. It may not be accurate, but at least 1089 // it won't appear as a Missed Call. 1090 if (dc.state != DriverCall.State.ALERTING 1091 && dc.state != DriverCall.State.DIALING) { 1092 mConnections[i].onConnectedInOrOut(); 1093 if (dc.state == DriverCall.State.HOLDING) { 1094 // We've transitioned into HOLDING 1095 mConnections[i].onStartedHolding(); 1096 } 1097 } 1098 } 1099 return newRinging; 1100 } 1101 1102 /** 1103 * Check if current call is in emergency call 1104 * 1105 * @return true if it is in emergency call 1106 * false if it is not in emergency call 1107 */ 1108 boolean isInEmergencyCall() { 1109 return mIsInEmergencyCall; 1110 } 1111 1112 @Override 1113 protected void log(String msg) { 1114 Rlog.d(LOG_TAG, "[CdmaCallTracker] " + msg); 1115 } 1116 1117 @Override 1118 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1119 pw.println("GsmCallTracker extends:"); 1120 super.dump(fd, pw, args); 1121 pw.println("droppedDuringPoll: length=" + mConnections.length); 1122 for(int i=0; i < mConnections.length; i++) { 1123 pw.printf(" mConnections[%d]=%s\n", i, mConnections[i]); 1124 } 1125 pw.println(" mVoiceCallEndedRegistrants=" + mVoiceCallEndedRegistrants); 1126 pw.println(" mVoiceCallStartedRegistrants=" + mVoiceCallStartedRegistrants); 1127 pw.println(" mCallWaitingRegistrants=" + mCallWaitingRegistrants); 1128 pw.println("droppedDuringPoll: size=" + mDroppedDuringPoll.size()); 1129 for(int i = 0; i < mDroppedDuringPoll.size(); i++) { 1130 pw.printf( " mDroppedDuringPoll[%d]=%s\n", i, mDroppedDuringPoll.get(i)); 1131 } 1132 pw.println(" mRingingCall=" + mRingingCall); 1133 pw.println(" mForegroundCall=" + mForegroundCall); 1134 pw.println(" mBackgroundCall=" + mBackgroundCall); 1135 pw.println(" mPendingMO=" + mPendingMO); 1136 pw.println(" mHangupPendingMO=" + mHangupPendingMO); 1137 pw.println(" mPendingCallInEcm=" + mPendingCallInEcm); 1138 pw.println(" mIsInEmergencyCall=" + mIsInEmergencyCall); 1139 pw.println(" mPhone=" + mPhone); 1140 pw.println(" mDesiredMute=" + mDesiredMute); 1141 pw.println(" mPendingCallClirMode=" + mPendingCallClirMode); 1142 pw.println(" mState=" + mState); 1143 pw.println(" mIsEcmTimerCanceled=" + mIsEcmTimerCanceled); 1144 } 1145 } 1146