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