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