1 /* 2 * Copyright (C) 2013 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.phone; 18 19 import android.os.AsyncResult; 20 import android.os.Handler; 21 import android.os.Message; 22 import android.os.SystemProperties; 23 import android.telephony.PhoneNumberUtils; 24 import android.text.TextUtils; 25 import android.util.Log; 26 27 import com.android.internal.telephony.CallManager; 28 import com.android.internal.telephony.Connection; 29 import com.android.internal.telephony.Phone; 30 import com.android.internal.telephony.PhoneConstants; 31 import com.android.internal.telephony.TelephonyCapabilities; 32 import com.android.internal.telephony.cdma.CdmaCallWaitingNotification; 33 import com.android.phone.CallGatewayManager.RawGatewayInfo; 34 import com.android.services.telephony.common.Call; 35 import com.android.services.telephony.common.Call.Capabilities; 36 import com.android.services.telephony.common.Call.State; 37 38 import com.google.android.collect.Maps; 39 import com.google.android.collect.Sets; 40 import com.google.common.base.Preconditions; 41 import com.google.common.collect.ImmutableMap; 42 import com.google.common.collect.ImmutableSortedSet; 43 import com.google.common.collect.Lists; 44 45 import java.util.ArrayList; 46 import java.util.Collections; 47 import java.util.HashMap; 48 import java.util.List; 49 import java.util.Map.Entry; 50 import java.util.Set; 51 import java.util.concurrent.atomic.AtomicInteger; 52 53 /** 54 * Creates a Call model from Call state and data received from the telephony 55 * layer. The telephony layer maintains 3 conceptual objects: Phone, Call, 56 * Connection. 57 * 58 * Phone represents the radio and there is an implementation per technology 59 * type such as GSMPhone, SipPhone, CDMAPhone, etc. Generally, we will only ever 60 * deal with one instance of this object for the lifetime of this class. 61 * 62 * There are 3 Call instances that exist for the lifetime of this class which 63 * are created by CallTracker. The three are RingingCall, ForegroundCall, and 64 * BackgroundCall. 65 * 66 * A Connection most closely resembles what the layperson would consider a call. 67 * A Connection is created when a user dials and it is "owned" by one of the 68 * three Call instances. Which of the three Calls owns the Connection changes 69 * as the Connection goes between ACTIVE, HOLD, RINGING, and other states. 70 * 71 * This class models a new Call class from Connection objects received from 72 * the telephony layer. We use Connection references as identifiers for a call; 73 * new reference = new call. 74 * 75 * TODO: Create a new Call class to replace the simple call Id ints 76 * being used currently. 77 * 78 * The new Call models are parcellable for transfer via the CallHandlerService 79 * API. 80 */ 81 public class CallModeler extends Handler { 82 83 private static final String TAG = CallModeler.class.getSimpleName(); 84 private static final boolean DBG = 85 (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1); 86 87 private static final int CALL_ID_START_VALUE = 1; 88 89 private final CallStateMonitor mCallStateMonitor; 90 private final CallManager mCallManager; 91 private final CallGatewayManager mCallGatewayManager; 92 private final HashMap<Connection, Call> mCallMap = Maps.newHashMap(); 93 private final HashMap<Connection, Call> mConfCallMap = Maps.newHashMap(); 94 private final AtomicInteger mNextCallId = new AtomicInteger(CALL_ID_START_VALUE); 95 private final ArrayList<Listener> mListeners = new ArrayList<Listener>(); 96 private Connection mCdmaIncomingConnection; 97 private Connection mCdmaOutgoingConnection; 98 99 public CallModeler(CallStateMonitor callStateMonitor, CallManager callManager, 100 CallGatewayManager callGatewayManager) { 101 mCallStateMonitor = callStateMonitor; 102 mCallManager = callManager; 103 mCallGatewayManager = callGatewayManager; 104 105 mCallStateMonitor.addListener(this); 106 } 107 108 @Override 109 public void handleMessage(Message msg) { 110 switch(msg.what) { 111 case CallStateMonitor.PHONE_NEW_RINGING_CONNECTION: 112 // We let the CallNotifier handle the new ringing connection first. When the custom 113 // ringtone and send_to_voicemail settings are retrieved, CallNotifier will directly 114 // call CallModeler's onNewRingingConnection. 115 break; 116 case CallStateMonitor.PHONE_DISCONNECT: 117 onDisconnect((Connection) ((AsyncResult) msg.obj).result); 118 break; 119 case CallStateMonitor.PHONE_UNKNOWN_CONNECTION_APPEARED: 120 // fall through 121 case CallStateMonitor.PHONE_STATE_CHANGED: 122 onPhoneStateChanged((AsyncResult) msg.obj); 123 break; 124 case CallStateMonitor.PHONE_ON_DIAL_CHARS: 125 onPostDialChars((AsyncResult) msg.obj, (char) msg.arg1); 126 break; 127 default: 128 break; 129 } 130 } 131 132 public void addListener(Listener listener) { 133 Preconditions.checkNotNull(listener); 134 Preconditions.checkNotNull(mListeners); 135 if (!mListeners.contains(listener)) { 136 mListeners.add(listener); 137 } 138 } 139 140 public List<Call> getFullList() { 141 final List<Call> calls = 142 Lists.newArrayListWithCapacity(mCallMap.size() + mConfCallMap.size()); 143 calls.addAll(mCallMap.values()); 144 calls.addAll(mConfCallMap.values()); 145 return calls; 146 } 147 148 public CallResult getCallWithId(int callId) { 149 // max 8 connections, so this should be fast even through we are traversing the entire map. 150 for (Entry<Connection, Call> entry : mCallMap.entrySet()) { 151 if (entry.getValue().getCallId() == callId) { 152 return new CallResult(entry.getValue(), entry.getKey()); 153 } 154 } 155 156 for (Entry<Connection, Call> entry : mConfCallMap.entrySet()) { 157 if (entry.getValue().getCallId() == callId) { 158 return new CallResult(entry.getValue(), entry.getKey()); 159 } 160 } 161 return null; 162 } 163 164 public boolean hasLiveCall() { 165 return hasLiveCallInternal(mCallMap) || 166 hasLiveCallInternal(mConfCallMap); 167 } 168 169 public void onCdmaCallWaiting(CdmaCallWaitingNotification callWaitingInfo) { 170 // We dont get the traditional onIncomingCall notification for cdma call waiting, 171 // but the Connection does actually exist. We need to find it in the set of ringing calls 172 // and pass it through our normal incoming logic. 173 final com.android.internal.telephony.Call teleCall = 174 mCallManager.getFirstActiveRingingCall(); 175 176 if (teleCall.getState() == com.android.internal.telephony.Call.State.WAITING) { 177 Connection connection = teleCall.getLatestConnection(); 178 179 if (connection != null) { 180 String number = connection.getAddress(); 181 if (number != null && number.equals(callWaitingInfo.number)) { 182 Call call = onNewRingingConnection(connection); 183 mCdmaIncomingConnection = connection; 184 return; 185 } 186 } 187 } 188 189 Log.e(TAG, "CDMA Call waiting notification without a matching connection."); 190 } 191 192 public void onCdmaCallWaitingReject() { 193 // Cdma call was rejected... 194 if (mCdmaIncomingConnection != null) { 195 onDisconnect(mCdmaIncomingConnection); 196 mCdmaIncomingConnection = null; 197 } else { 198 Log.e(TAG, "CDMA Call waiting rejection without an incoming call."); 199 } 200 } 201 202 /** 203 * CDMA Calls have no sense of "dialing" state. For outgoing calls 3way calls we want to 204 * mimick this state so that the the UI can notify the user that there is a "dialing" 205 * call. 206 */ 207 public void setCdmaOutgoing3WayCall(Connection connection) { 208 boolean wasSet = mCdmaOutgoingConnection != null; 209 210 mCdmaOutgoingConnection = connection; 211 212 // If we reset the connection, that mean we can now tell the user that the call is actually 213 // part of the conference call and move it out of the dialing state. To do this, issue a 214 // new update completely. 215 if (wasSet && mCdmaOutgoingConnection == null) { 216 onPhoneStateChanged(null); 217 } 218 } 219 220 private boolean hasLiveCallInternal(HashMap<Connection, Call> map) { 221 for (Call call : map.values()) { 222 final int state = call.getState(); 223 if (state == Call.State.ACTIVE || 224 state == Call.State.CALL_WAITING || 225 state == Call.State.CONFERENCED || 226 state == Call.State.DIALING || 227 state == Call.State.REDIALING || 228 state == Call.State.INCOMING || 229 state == Call.State.ONHOLD || 230 state == Call.State.DISCONNECTING) { 231 return true; 232 } 233 } 234 return false; 235 } 236 237 public boolean hasOutstandingActiveOrDialingCall() { 238 return hasOutstandingActiveOrDialingCallInternal(mCallMap) || 239 hasOutstandingActiveOrDialingCallInternal(mConfCallMap); 240 } 241 242 private static boolean hasOutstandingActiveOrDialingCallInternal( 243 HashMap<Connection, Call> map) { 244 for (Call call : map.values()) { 245 final int state = call.getState(); 246 if (state == Call.State.ACTIVE || Call.State.isDialing(state)) { 247 return true; 248 } 249 } 250 251 return false; 252 } 253 254 255 /** 256 * Handles the POST_ON_DIAL_CHARS message from the Phone (see our call to 257 * mPhone.setOnPostDialCharacter() above.) 258 * 259 * TODO: NEED TO TEST THIS SEQUENCE now that we no longer handle "dialable" key events here in 260 * the InCallScreen: we do directly to the Dialer UI instead. Similarly, we may now need to go 261 * directly to the Dialer to handle POST_ON_DIAL_CHARS too. 262 */ 263 private void onPostDialChars(AsyncResult r, char ch) { 264 final Connection c = (Connection) r.result; 265 266 if (c != null) { 267 final Connection.PostDialState state = (Connection.PostDialState) r.userObj; 268 269 switch (state) { 270 case WAIT: 271 final Call call = getCallFromMap(mCallMap, c, false); 272 if (call == null) { 273 Log.i(TAG, "Call no longer exists. Skipping onPostDialWait()."); 274 } else { 275 for (Listener mListener : mListeners) { 276 mListener.onPostDialAction(state, call.getCallId(), 277 c.getRemainingPostDialString(), ch); 278 } 279 } 280 break; 281 default: 282 // This is primarily to cause the DTMFTonePlayer to play local tones. 283 // Other listeners simply perform no-ops. 284 for (Listener mListener : mListeners) { 285 mListener.onPostDialAction(state, 0, "", ch); 286 } 287 break; 288 } 289 } 290 } 291 292 /* package */ Call onNewRingingConnection(Connection conn) { 293 Log.i(TAG, "onNewRingingConnection"); 294 final Call call = getCallFromMap(mCallMap, conn, true); 295 296 if (call != null) { 297 updateCallFromConnection(call, conn, false); 298 299 for (int i = 0; i < mListeners.size(); ++i) { 300 mListeners.get(i).onIncoming(call); 301 } 302 } 303 304 PhoneGlobals.getInstance().updateWakeState(); 305 return call; 306 } 307 308 private void onDisconnect(Connection conn) { 309 Log.i(TAG, "onDisconnect"); 310 final Call call = getCallFromMap(mCallMap, conn, false); 311 312 if (call != null) { 313 final boolean wasConferenced = call.getState() == State.CONFERENCED; 314 315 updateCallFromConnection(call, conn, false); 316 317 for (int i = 0; i < mListeners.size(); ++i) { 318 mListeners.get(i).onDisconnect(call); 319 } 320 321 // If it was a conferenced call, we need to run the entire update 322 // to make the proper changes to parent conference calls. 323 if (wasConferenced) { 324 onPhoneStateChanged(null); 325 } 326 327 mCallMap.remove(conn); 328 } 329 330 mCallManager.clearDisconnected(); 331 PhoneGlobals.getInstance().updateWakeState(); 332 } 333 334 /** 335 * Called when the phone state changes. 336 */ 337 private void onPhoneStateChanged(AsyncResult r) { 338 Log.i(TAG, "onPhoneStateChanged: "); 339 final List<Call> updatedCalls = Lists.newArrayList(); 340 doUpdate(false, updatedCalls); 341 342 if (updatedCalls.size() > 0) { 343 for (int i = 0; i < mListeners.size(); ++i) { 344 mListeners.get(i).onUpdate(updatedCalls); 345 } 346 } 347 348 PhoneGlobals.getInstance().updateWakeState(); 349 } 350 351 /** 352 * Go through the Calls from CallManager and return the list of calls that were updated. 353 * Method also finds any orphaned Calls (Connection objects no longer returned by telephony as 354 * either ringing, foreground, or background). For each orphaned call, it sets the call state 355 * to IDLE and adds it to the list of calls to update. 356 * 357 * @param fullUpdate Add all calls to out parameter including those that have no updates. 358 * @param out List to populate with Calls that have been updated. 359 */ 360 private void doUpdate(boolean fullUpdate, List<Call> out) { 361 final List<com.android.internal.telephony.Call> telephonyCalls = Lists.newArrayList(); 362 telephonyCalls.addAll(mCallManager.getRingingCalls()); 363 telephonyCalls.addAll(mCallManager.getForegroundCalls()); 364 telephonyCalls.addAll(mCallManager.getBackgroundCalls()); 365 366 // orphanedConnections starts out including all connections we know about. 367 // As we iterate through the connections we get from the telephony layer we 368 // prune this Set down to only the connections we have but telephony no longer 369 // recognizes. 370 final Set<Connection> orphanedConnections = Sets.newHashSet(); 371 orphanedConnections.addAll(mCallMap.keySet()); 372 orphanedConnections.addAll(mConfCallMap.keySet()); 373 374 // Cycle through all the Connections on all the Calls. Update our Call objects 375 // to reflect any new state and send the updated Call objects to the handler service. 376 for (com.android.internal.telephony.Call telephonyCall : telephonyCalls) { 377 378 for (Connection connection : telephonyCall.getConnections()) { 379 if (DBG) Log.d(TAG, "connection: " + connection + connection.getState()); 380 381 if (orphanedConnections.contains(connection)) { 382 orphanedConnections.remove(connection); 383 } 384 385 // We only send updates for live calls which are not incoming (ringing). 386 // Disconnected and incoming calls are handled by onDisconnect and 387 // onNewRingingConnection. 388 final boolean shouldUpdate = 389 connection.getState() != 390 com.android.internal.telephony.Call.State.DISCONNECTED && 391 connection.getState() != 392 com.android.internal.telephony.Call.State.IDLE && 393 !connection.getState().isRinging(); 394 395 final boolean isDisconnecting = connection.getState() == 396 com.android.internal.telephony.Call.State.DISCONNECTING; 397 398 // For disconnecting calls, we still need to send the update to the UI but we do 399 // not create a new call if the call did not exist. 400 final boolean shouldCreate = shouldUpdate && !isDisconnecting; 401 402 // New connections return a Call with INVALID state, which does not translate to 403 // a state in the internal.telephony.Call object. This ensures that staleness 404 // check below fails and we always add the item to the update list if it is new. 405 final Call call = getCallFromMap(mCallMap, connection, shouldCreate /* create */); 406 407 if (call == null || !shouldUpdate) { 408 if (DBG) Log.d(TAG, "update skipped"); 409 continue; 410 } 411 412 boolean changed = updateCallFromConnection(call, connection, false); 413 414 if (fullUpdate || changed) { 415 out.add(call); 416 } 417 } 418 419 // We do a second loop to address conference call scenarios. We do this as a separate 420 // loop to ensure all child calls are up to date before we start updating the parent 421 // conference calls. 422 for (Connection connection : telephonyCall.getConnections()) { 423 updateForConferenceCalls(connection, out); 424 } 425 } 426 427 // Iterate through orphaned connections, set them to idle, and remove 428 // them from our internal structures. 429 for (Connection orphanedConnection : orphanedConnections) { 430 if (mCallMap.containsKey(orphanedConnection)) { 431 final Call call = mCallMap.get(orphanedConnection); 432 call.setState(Call.State.IDLE); 433 out.add(call); 434 435 mCallMap.remove(orphanedConnection); 436 } 437 438 if (mConfCallMap.containsKey(orphanedConnection)) { 439 final Call call = mCallMap.get(orphanedConnection); 440 call.setState(Call.State.IDLE); 441 out.add(call); 442 443 mConfCallMap.remove(orphanedConnection); 444 } 445 } 446 } 447 448 /** 449 * Checks to see if the connection is the first connection in a conference call. 450 * If it is a conference call, we will create a new Conference Call object or 451 * update the existing conference call object for that connection. 452 * If it is not a conference call but a previous associated conference call still exists, 453 * we mark it as idle and remove it from the map. 454 * In both cases above, we add the Calls to be updated to the UI. 455 * @param connection The connection object to check. 456 * @param updatedCalls List of 'updated' calls that will be sent to the UI. 457 */ 458 private boolean updateForConferenceCalls(Connection connection, List<Call> updatedCalls) { 459 // We consider this connection a conference connection if the call it 460 // belongs to is a multiparty call AND it is the first live connection. 461 final boolean isConferenceCallConnection = isPartOfLiveConferenceCall(connection) && 462 getEarliestLiveConnection(connection.getCall()) == connection; 463 464 boolean changed = false; 465 466 // If this connection is the main connection for the conference call, then create or update 467 // a Call object for that conference call. 468 if (isConferenceCallConnection) { 469 final Call confCall = getCallFromMap(mConfCallMap, connection, true); 470 changed = updateCallFromConnection(confCall, connection, true); 471 472 if (changed) { 473 updatedCalls.add(confCall); 474 } 475 476 if (DBG) Log.d(TAG, "Updating a conference call: " + confCall); 477 478 // It is possible that through a conference call split, there may be lingering conference 479 // calls where this connection was the main connection. We clean those up here. 480 } else { 481 final Call oldConfCall = getCallFromMap(mConfCallMap, connection, false); 482 483 // We found a conference call for this connection, which is no longer a conference call. 484 // Kill it! 485 if (oldConfCall != null) { 486 if (DBG) Log.d(TAG, "Cleaning up an old conference call: " + oldConfCall); 487 mConfCallMap.remove(connection); 488 oldConfCall.setState(State.IDLE); 489 changed = true; 490 491 // add to the list of calls to update 492 updatedCalls.add(oldConfCall); 493 } 494 } 495 496 return changed; 497 } 498 499 private Connection getEarliestLiveConnection(com.android.internal.telephony.Call call) { 500 final List<Connection> connections = call.getConnections(); 501 final int size = connections.size(); 502 Connection earliestConn = null; 503 long earliestTime = Long.MAX_VALUE; 504 for (int i = 0; i < size; i++) { 505 final Connection connection = connections.get(i); 506 if (!connection.isAlive()) continue; 507 final long time = connection.getCreateTime(); 508 if (time < earliestTime) { 509 earliestTime = time; 510 earliestConn = connection; 511 } 512 } 513 return earliestConn; 514 } 515 516 /** 517 * Sets the new call state onto the call and performs some additional logic 518 * associated with setting the state. 519 */ 520 private void setNewState(Call call, int newState, Connection connection) { 521 Preconditions.checkState(call.getState() != newState); 522 523 // When starting an outgoing call, we need to grab gateway information 524 // for the call, if available, and set it. 525 final RawGatewayInfo info = mCallGatewayManager.getGatewayInfo(connection); 526 527 if (Call.State.isDialing(newState)) { 528 if (!info.isEmpty()) { 529 call.setGatewayNumber(info.getFormattedGatewayNumber()); 530 call.setGatewayPackage(info.packageName); 531 } 532 } else if (!Call.State.isConnected(newState)) { 533 mCallGatewayManager.clearGatewayData(connection); 534 } 535 536 call.setState(newState); 537 } 538 539 /** 540 * Updates the Call properties to match the state of the connection object 541 * that it represents. 542 * @param call The call object to update. 543 * @param connection The connection object from which to update call. 544 * @param isForConference There are slight differences in how we populate data for conference 545 * calls. This boolean tells us which method to use. 546 */ 547 private boolean updateCallFromConnection(Call call, Connection connection, 548 boolean isForConference) { 549 boolean changed = false; 550 551 final int newState = translateStateFromTelephony(connection, isForConference); 552 553 if (call.getState() != newState) { 554 setNewState(call, newState, connection); 555 changed = true; 556 } 557 558 final Call.DisconnectCause newDisconnectCause = 559 translateDisconnectCauseFromTelephony(connection.getDisconnectCause()); 560 if (call.getDisconnectCause() != newDisconnectCause) { 561 call.setDisconnectCause(newDisconnectCause); 562 changed = true; 563 } 564 565 final long oldConnectTime = call.getConnectTime(); 566 if (oldConnectTime != connection.getConnectTime()) { 567 call.setConnectTime(connection.getConnectTime()); 568 changed = true; 569 } 570 571 if (!isForConference) { 572 // Number 573 final String oldNumber = call.getNumber(); 574 String newNumber = connection.getAddress(); 575 RawGatewayInfo info = mCallGatewayManager.getGatewayInfo(connection); 576 if (!info.isEmpty()) { 577 newNumber = info.trueNumber; 578 } 579 if (TextUtils.isEmpty(oldNumber) || !oldNumber.equals(newNumber)) { 580 call.setNumber(newNumber); 581 changed = true; 582 } 583 584 // Number presentation 585 final int newNumberPresentation = connection.getNumberPresentation(); 586 if (call.getNumberPresentation() != newNumberPresentation) { 587 call.setNumberPresentation(newNumberPresentation); 588 changed = true; 589 } 590 591 // Name 592 final String oldCnapName = call.getCnapName(); 593 if (TextUtils.isEmpty(oldCnapName) || !oldCnapName.equals(connection.getCnapName())) { 594 call.setCnapName(connection.getCnapName()); 595 changed = true; 596 } 597 598 // Name Presentation 599 final int newCnapNamePresentation = connection.getCnapNamePresentation(); 600 if (call.getCnapNamePresentation() != newCnapNamePresentation) { 601 call.setCnapNamePresentation(newCnapNamePresentation); 602 changed = true; 603 } 604 } else { 605 606 // update the list of children by: 607 // 1) Saving the old set 608 // 2) Removing all children 609 // 3) Adding the correct children into the Call 610 // 4) Comparing the new children set with the old children set 611 ImmutableSortedSet<Integer> oldSet = call.getChildCallIds(); 612 call.removeAllChildren(); 613 614 if (connection.getCall() != null) { 615 for (Connection childConn : connection.getCall().getConnections()) { 616 final Call childCall = getCallFromMap(mCallMap, childConn, false); 617 if (childCall != null && childConn.isAlive()) { 618 call.addChildId(childCall.getCallId()); 619 } 620 } 621 } 622 changed |= !oldSet.equals(call.getChildCallIds()); 623 } 624 625 /** 626 * !!! Uses values from connection and call collected above so this part must be last !!! 627 */ 628 final int newCapabilities = getCapabilitiesFor(connection, call, isForConference); 629 if (call.getCapabilities() != newCapabilities) { 630 call.setCapabilities(newCapabilities); 631 changed = true; 632 } 633 634 return changed; 635 } 636 637 /** 638 * Returns a mask of capabilities for the connection such as merge, hold, etc. 639 */ 640 private int getCapabilitiesFor(Connection connection, Call call, boolean isForConference) { 641 final boolean callIsActive = (call.getState() == Call.State.ACTIVE); 642 final Phone phone = connection.getCall().getPhone(); 643 644 boolean canAddCall = false; 645 boolean canMergeCall = false; 646 boolean canSwapCall = false; 647 boolean canRespondViaText = false; 648 boolean canMute = false; 649 650 final boolean supportHold = PhoneUtils.okToSupportHold(mCallManager); 651 final boolean canHold = (supportHold ? PhoneUtils.okToHoldCall(mCallManager) : false); 652 final boolean genericConf = isForConference && 653 (connection.getCall().getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA); 654 655 // only applies to active calls 656 if (callIsActive) { 657 canMergeCall = PhoneUtils.okToMergeCalls(mCallManager); 658 canSwapCall = PhoneUtils.okToSwapCalls(mCallManager); 659 } 660 661 canAddCall = PhoneUtils.okToAddCall(mCallManager); 662 663 // "Mute": only enabled when the foreground call is ACTIVE. 664 // (It's meaningless while on hold, or while DIALING/ALERTING.) 665 // It's also explicitly disabled during emergency calls or if 666 // emergency callback mode (ECM) is active. 667 boolean isEmergencyCall = false; 668 if (connection != null) { 669 isEmergencyCall = PhoneNumberUtils.isLocalEmergencyNumber(connection.getAddress(), 670 phone.getContext()); 671 } 672 boolean isECM = PhoneUtils.isPhoneInEcm(phone); 673 if (isEmergencyCall || isECM) { // disable "Mute" item 674 canMute = false; 675 } else { 676 canMute = callIsActive; 677 } 678 679 canRespondViaText = RejectWithTextMessageManager.allowRespondViaSmsForCall(call, 680 connection); 681 682 // special rules section! 683 // CDMA always has Add 684 if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) { 685 canAddCall = true; 686 } 687 688 int retval = 0x0; 689 if (canHold) { 690 retval |= Capabilities.HOLD; 691 } 692 if (supportHold) { 693 retval |= Capabilities.SUPPORT_HOLD; 694 } 695 if (canAddCall) { 696 retval |= Capabilities.ADD_CALL; 697 } 698 if (canMergeCall) { 699 retval |= Capabilities.MERGE_CALLS; 700 } 701 if (canSwapCall) { 702 retval |= Capabilities.SWAP_CALLS; 703 } 704 if (canRespondViaText) { 705 retval |= Capabilities.RESPOND_VIA_TEXT; 706 } 707 if (canMute) { 708 retval |= Capabilities.MUTE; 709 } 710 if (genericConf) { 711 retval |= Capabilities.GENERIC_CONFERENCE; 712 } 713 714 return retval; 715 } 716 717 /** 718 * Returns true if the Connection is part of a multiparty call. 719 * We do this by checking the isMultiparty() method of the telephony.Call object and also 720 * checking to see if more than one of it's children is alive. 721 */ 722 private boolean isPartOfLiveConferenceCall(Connection connection) { 723 if (connection.getCall() != null && connection.getCall().isMultiparty()) { 724 int count = 0; 725 for (Connection currConn : connection.getCall().getConnections()) { 726 727 // Only count connections which are alive and never cound the special 728 // "dialing" 3way call for CDMA calls. 729 if (currConn.isAlive() && currConn != mCdmaOutgoingConnection) { 730 count++; 731 if (count >= 2) { 732 return true; 733 } 734 } 735 } 736 } 737 return false; 738 } 739 740 private int translateStateFromTelephony(Connection connection, boolean isForConference) { 741 742 com.android.internal.telephony.Call.State connState = connection.getState(); 743 744 // For the "fake" outgoing CDMA call, we need to always treat it as an outgoing call. 745 if (mCdmaOutgoingConnection == connection) { 746 connState = com.android.internal.telephony.Call.State.DIALING; 747 } 748 749 int retval = State.IDLE; 750 switch (connState) { 751 case ACTIVE: 752 retval = State.ACTIVE; 753 break; 754 case INCOMING: 755 retval = State.INCOMING; 756 break; 757 case DIALING: 758 case ALERTING: 759 if (PhoneGlobals.getInstance().notifier.getIsCdmaRedialCall()) { 760 retval = State.REDIALING; 761 } else { 762 retval = State.DIALING; 763 } 764 break; 765 case WAITING: 766 retval = State.CALL_WAITING; 767 break; 768 case HOLDING: 769 retval = State.ONHOLD; 770 break; 771 case DISCONNECTING: 772 retval = State.DISCONNECTING; 773 break; 774 case DISCONNECTED: 775 retval = State.DISCONNECTED; 776 default: 777 } 778 779 // If we are dealing with a potential child call (not the parent conference call), 780 // the check to see if we have to set the state to CONFERENCED. 781 if (!isForConference) { 782 // if the connection is part of a multiparty call, and it is live, 783 // annotate it with CONFERENCED state instead. 784 if (isPartOfLiveConferenceCall(connection) && connection.isAlive()) { 785 return State.CONFERENCED; 786 } 787 } 788 789 return retval; 790 } 791 792 private final ImmutableMap<Connection.DisconnectCause, Call.DisconnectCause> CAUSE_MAP = 793 ImmutableMap.<Connection.DisconnectCause, Call.DisconnectCause>builder() 794 .put(Connection.DisconnectCause.BUSY, Call.DisconnectCause.BUSY) 795 .put(Connection.DisconnectCause.CALL_BARRED, Call.DisconnectCause.CALL_BARRED) 796 .put(Connection.DisconnectCause.CDMA_ACCESS_BLOCKED, 797 Call.DisconnectCause.CDMA_ACCESS_BLOCKED) 798 .put(Connection.DisconnectCause.CDMA_ACCESS_FAILURE, 799 Call.DisconnectCause.CDMA_ACCESS_FAILURE) 800 .put(Connection.DisconnectCause.CDMA_DROP, Call.DisconnectCause.CDMA_DROP) 801 .put(Connection.DisconnectCause.CDMA_INTERCEPT, Call.DisconnectCause.CDMA_INTERCEPT) 802 .put(Connection.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE, 803 Call.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE) 804 .put(Connection.DisconnectCause.CDMA_NOT_EMERGENCY, 805 Call.DisconnectCause.CDMA_NOT_EMERGENCY) 806 .put(Connection.DisconnectCause.CDMA_PREEMPTED, Call.DisconnectCause.CDMA_PREEMPTED) 807 .put(Connection.DisconnectCause.CDMA_REORDER, Call.DisconnectCause.CDMA_REORDER) 808 .put(Connection.DisconnectCause.CDMA_RETRY_ORDER, 809 Call.DisconnectCause.CDMA_RETRY_ORDER) 810 .put(Connection.DisconnectCause.CDMA_SO_REJECT, Call.DisconnectCause.CDMA_SO_REJECT) 811 .put(Connection.DisconnectCause.CONGESTION, Call.DisconnectCause.CONGESTION) 812 .put(Connection.DisconnectCause.CS_RESTRICTED, Call.DisconnectCause.CS_RESTRICTED) 813 .put(Connection.DisconnectCause.CS_RESTRICTED_EMERGENCY, 814 Call.DisconnectCause.CS_RESTRICTED_EMERGENCY) 815 .put(Connection.DisconnectCause.CS_RESTRICTED_NORMAL, 816 Call.DisconnectCause.CS_RESTRICTED_NORMAL) 817 .put(Connection.DisconnectCause.ERROR_UNSPECIFIED, 818 Call.DisconnectCause.ERROR_UNSPECIFIED) 819 .put(Connection.DisconnectCause.FDN_BLOCKED, Call.DisconnectCause.FDN_BLOCKED) 820 .put(Connection.DisconnectCause.ICC_ERROR, Call.DisconnectCause.ICC_ERROR) 821 .put(Connection.DisconnectCause.INCOMING_MISSED, 822 Call.DisconnectCause.INCOMING_MISSED) 823 .put(Connection.DisconnectCause.INCOMING_REJECTED, 824 Call.DisconnectCause.INCOMING_REJECTED) 825 .put(Connection.DisconnectCause.INVALID_CREDENTIALS, 826 Call.DisconnectCause.INVALID_CREDENTIALS) 827 .put(Connection.DisconnectCause.INVALID_NUMBER, 828 Call.DisconnectCause.INVALID_NUMBER) 829 .put(Connection.DisconnectCause.LIMIT_EXCEEDED, Call.DisconnectCause.LIMIT_EXCEEDED) 830 .put(Connection.DisconnectCause.LOCAL, Call.DisconnectCause.LOCAL) 831 .put(Connection.DisconnectCause.LOST_SIGNAL, Call.DisconnectCause.LOST_SIGNAL) 832 .put(Connection.DisconnectCause.MMI, Call.DisconnectCause.MMI) 833 .put(Connection.DisconnectCause.NORMAL, Call.DisconnectCause.NORMAL) 834 .put(Connection.DisconnectCause.NOT_DISCONNECTED, 835 Call.DisconnectCause.NOT_DISCONNECTED) 836 .put(Connection.DisconnectCause.NUMBER_UNREACHABLE, 837 Call.DisconnectCause.NUMBER_UNREACHABLE) 838 .put(Connection.DisconnectCause.OUT_OF_NETWORK, Call.DisconnectCause.OUT_OF_NETWORK) 839 .put(Connection.DisconnectCause.OUT_OF_SERVICE, Call.DisconnectCause.OUT_OF_SERVICE) 840 .put(Connection.DisconnectCause.POWER_OFF, Call.DisconnectCause.POWER_OFF) 841 .put(Connection.DisconnectCause.SERVER_ERROR, Call.DisconnectCause.SERVER_ERROR) 842 .put(Connection.DisconnectCause.SERVER_UNREACHABLE, 843 Call.DisconnectCause.SERVER_UNREACHABLE) 844 .put(Connection.DisconnectCause.TIMED_OUT, Call.DisconnectCause.TIMED_OUT) 845 .put(Connection.DisconnectCause.UNOBTAINABLE_NUMBER, 846 Call.DisconnectCause.UNOBTAINABLE_NUMBER) 847 .build(); 848 849 private Call.DisconnectCause translateDisconnectCauseFromTelephony( 850 Connection.DisconnectCause causeSource) { 851 852 if (CAUSE_MAP.containsKey(causeSource)) { 853 return CAUSE_MAP.get(causeSource); 854 } 855 856 return Call.DisconnectCause.UNKNOWN; 857 } 858 859 /** 860 * Gets an existing callId for a connection, or creates one if none exists. 861 * This function does NOT set any of the Connection data onto the Call class. 862 * A separate call to updateCallFromConnection must be made for that purpose. 863 */ 864 private Call getCallFromMap(HashMap<Connection, Call> map, Connection conn, 865 boolean createIfMissing) { 866 Call call = null; 867 868 // Find the call id or create if missing and requested. 869 if (conn != null) { 870 if (map.containsKey(conn)) { 871 call = map.get(conn); 872 } else if (createIfMissing) { 873 call = createNewCall(); 874 map.put(conn, call); 875 } 876 } 877 return call; 878 } 879 880 /** 881 * Creates a brand new connection for the call. 882 */ 883 private Call createNewCall() { 884 int callId; 885 int newNextCallId; 886 do { 887 callId = mNextCallId.get(); 888 889 // protect against overflow 890 newNextCallId = (callId == Integer.MAX_VALUE ? 891 CALL_ID_START_VALUE : callId + 1); 892 893 // Keep looping if the change was not atomic OR the value is already taken. 894 // The call to containsValue() is linear, however, most devices support a 895 // maximum of 7 connections so it's not expensive. 896 } while (!mNextCallId.compareAndSet(callId, newNextCallId)); 897 898 return new Call(callId); 899 } 900 901 /** 902 * Listener interface for changes to Calls. 903 */ 904 public interface Listener { 905 void onDisconnect(Call call); 906 void onIncoming(Call call); 907 void onUpdate(List<Call> calls); 908 void onPostDialAction(Connection.PostDialState state, int callId, String remainingChars, 909 char c); 910 } 911 912 /** 913 * Result class for accessing a call by connection. 914 */ 915 public static class CallResult { 916 public Call mCall; 917 public Call mActionableCall; 918 public Connection mConnection; 919 920 private CallResult(Call call, Connection connection) { 921 this(call, call, connection); 922 } 923 924 private CallResult(Call call, Call actionableCall, Connection connection) { 925 mCall = call; 926 mActionableCall = actionableCall; 927 mConnection = connection; 928 } 929 930 public Call getCall() { 931 return mCall; 932 } 933 934 // The call that should be used for call actions like hanging up. 935 public Call getActionableCall() { 936 return mActionableCall; 937 } 938 939 public Connection getConnection() { 940 return mConnection; 941 } 942 } 943 } 944