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