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