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