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