CallModeler.java revision 71d5c6e4d0036e30eaa6a23faf0c246934ef8e6b
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 17package com.android.phone; 18 19import android.content.Context; 20import android.os.AsyncResult; 21import android.os.Handler; 22import android.os.Message; 23import android.os.SystemProperties; 24import android.text.TextUtils; 25import android.util.Log; 26 27import com.android.internal.telephony.CallManager; 28import com.android.internal.telephony.Connection; 29import com.android.internal.telephony.Phone; 30import com.android.internal.telephony.PhoneConstants; 31import com.android.internal.telephony.TelephonyCapabilities; 32import com.android.phone.CallGatewayManager.RawGatewayInfo; 33import com.android.services.telephony.common.Call; 34import com.android.services.telephony.common.Call.Capabilities; 35import com.android.services.telephony.common.Call.State; 36 37import com.google.android.collect.Lists; 38import com.google.android.collect.Maps; 39import com.google.common.base.Preconditions; 40import com.google.common.collect.ImmutableMap; 41import com.google.common.collect.ImmutableSortedSet; 42 43import java.util.ArrayList; 44import java.util.HashMap; 45import java.util.List; 46import java.util.Map.Entry; 47import java.util.concurrent.atomic.AtomicInteger; 48 49/** 50 * Creates a Call model from Call state and data received from the telephony 51 * layer. The telephony layer maintains 3 conceptual objects: Phone, Call, 52 * Connection. 53 * 54 * Phone represents the radio and there is an implementation per technology 55 * type such as GSMPhone, SipPhone, CDMAPhone, etc. Generally, we will only ever 56 * deal with one instance of this object for the lifetime of this class. 57 * 58 * There are 3 Call instances that exist for the lifetime of this class which 59 * are created by CallTracker. The three are RingingCall, ForegroundCall, and 60 * BackgroundCall. 61 * 62 * A Connection most closely resembles what the layperson would consider a call. 63 * A Connection is created when a user dials and it is "owned" by one of the 64 * three Call instances. Which of the three Calls owns the Connection changes 65 * as the Connection goes between ACTIVE, HOLD, RINGING, and other states. 66 * 67 * This class models a new Call class from Connection objects received from 68 * the telephony layer. We use Connection references as identifiers for a call; 69 * new reference = new call. 70 * 71 * TODO(klp): Create a new Call class to replace the simple call Id ints 72 * being used currently. 73 * 74 * The new Call models are parcellable for transfer via the CallHandlerService 75 * API. 76 */ 77public class CallModeler extends Handler { 78 79 private static final String TAG = CallModeler.class.getSimpleName(); 80 private static final boolean DBG = 81 (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1); 82 83 private static final int CALL_ID_START_VALUE = 1; 84 85 private final CallStateMonitor mCallStateMonitor; 86 private final CallManager mCallManager; 87 private final CallGatewayManager mCallGatewayManager; 88 private final HashMap<Connection, Call> mCallMap = Maps.newHashMap(); 89 private final HashMap<Connection, Call> mConfCallMap = Maps.newHashMap(); 90 private final AtomicInteger mNextCallId = new AtomicInteger(CALL_ID_START_VALUE); 91 private final ArrayList<Listener> mListeners = new ArrayList<Listener>(); 92 private RejectWithTextMessageManager mRejectWithTextMessageManager; 93 94 public CallModeler(CallStateMonitor callStateMonitor, CallManager callManager, 95 RejectWithTextMessageManager rejectWithTextMessageManager, 96 CallGatewayManager callGatewayManager) { 97 mCallStateMonitor = callStateMonitor; 98 mCallManager = callManager; 99 mRejectWithTextMessageManager = rejectWithTextMessageManager; 100 mCallGatewayManager = callGatewayManager; 101 102 mCallStateMonitor.addListener(this); 103 } 104 105 @Override 106 public void handleMessage(Message msg) { 107 switch(msg.what) { 108 case CallStateMonitor.PHONE_NEW_RINGING_CONNECTION: 109 onNewRingingConnection((AsyncResult) msg.obj); 110 break; 111 case CallStateMonitor.PHONE_DISCONNECT: 112 onDisconnect((AsyncResult) msg.obj); 113 break; 114 case CallStateMonitor.PHONE_STATE_CHANGED: 115 onPhoneStateChanged((AsyncResult) msg.obj); 116 break; 117 default: 118 break; 119 } 120 } 121 122 public void addListener(Listener listener) { 123 Preconditions.checkNotNull(listener); 124 Preconditions.checkNotNull(mListeners); 125 if (!mListeners.contains(listener)) { 126 mListeners.add(listener); 127 } 128 } 129 130 public List<Call> getFullList() { 131 final List<Call> retval = Lists.newArrayList(); 132 doUpdate(true, retval); 133 return retval; 134 } 135 136 public CallResult getCallWithId(int callId) { 137 // max 8 connections, so this should be fast even through we are traversing the entire map. 138 for (Entry<Connection, Call> entry : mCallMap.entrySet()) { 139 if (entry.getValue().getCallId() == callId) { 140 return new CallResult(entry.getValue(), entry.getKey()); 141 } 142 } 143 144 for (Entry<Connection, Call> entry : mConfCallMap.entrySet()) { 145 if (entry.getValue().getCallId() == callId) { 146 if (entry.getValue().getChildCallIds().size() == 0) { 147 return null; 148 } 149 final CallResult child = getCallWithId(entry.getValue().getChildCallIds().first()); 150 return new CallResult(entry.getValue(), child.getActionableCall(), 151 child.getConnection()); 152 } 153 } 154 return null; 155 } 156 157 public boolean hasLiveCall() { 158 return hasLiveCallInternal(mCallMap) || 159 hasLiveCallInternal(mConfCallMap); 160 } 161 162 private boolean hasLiveCallInternal(HashMap<Connection, Call> map) { 163 for (Call call : map.values()) { 164 final int state = call.getState(); 165 if (state == Call.State.ACTIVE || 166 state == Call.State.CALL_WAITING || 167 state == Call.State.CONFERENCED || 168 state == Call.State.DIALING || 169 state == Call.State.INCOMING || 170 state == Call.State.ONHOLD) { 171 return true; 172 } 173 } 174 return false; 175 } 176 177 public boolean hasOutstandingActiveOrDialingCall() { 178 return hasOutstandingActiveOrDialingCallInternal(mCallMap) || 179 hasOutstandingActiveOrDialingCallInternal(mConfCallMap); 180 } 181 182 private static boolean hasOutstandingActiveOrDialingCallInternal( 183 HashMap<Connection, Call> map) { 184 for (Call call : map.values()) { 185 final int state = call.getState(); 186 if (state == Call.State.ACTIVE || 187 state == Call.State.DIALING) { 188 return true; 189 } 190 } 191 192 return false; 193 } 194 195 private void onNewRingingConnection(AsyncResult r) { 196 Log.i(TAG, "onNewRingingConnection"); 197 final Connection conn = (Connection) r.result; 198 final Call call = getCallFromMap(mCallMap, conn, true); 199 200 updateCallFromConnection(call, conn, false); 201 202 for (int i = 0; i < mListeners.size(); ++i) { 203 if (call != null) { 204 mListeners.get(i).onIncoming(call); 205 } 206 } 207 } 208 209 private void onDisconnect(AsyncResult r) { 210 Log.i(TAG, "onDisconnect"); 211 final Connection conn = (Connection) r.result; 212 final Call call = getCallFromMap(mCallMap, conn, false); 213 214 if (call != null) { 215 final boolean wasConferenced = call.getState() == State.CONFERENCED; 216 217 updateCallFromConnection(call, conn, false); 218 219 for (int i = 0; i < mListeners.size(); ++i) { 220 mListeners.get(i).onDisconnect(call); 221 } 222 223 // If it was a conferenced call, we need to run the entire update 224 // to make the proper changes to parent conference calls. 225 if (wasConferenced) { 226 onPhoneStateChanged(null); 227 } 228 229 mCallMap.remove(conn); 230 } 231 232 // TODO(klp): Do a final check to see if there are any active calls. 233 // If there are not, totally cancel all calls 234 } 235 236 /** 237 * Called when the phone state changes. 238 */ 239 private void onPhoneStateChanged(AsyncResult r) { 240 Log.i(TAG, "onPhoneStateChanged: "); 241 final List<Call> updatedCalls = Lists.newArrayList(); 242 doUpdate(false, updatedCalls); 243 244 if (updatedCalls.size() > 0) { 245 for (int i = 0; i < mListeners.size(); ++i) { 246 mListeners.get(i).onUpdate(updatedCalls); 247 } 248 } 249 } 250 251 252 /** 253 * Go through the Calls from CallManager and return the list of calls that were updated. 254 * Or, the full list if requested. 255 */ 256 private void doUpdate(boolean fullUpdate, List<Call> out) { 257 final List<com.android.internal.telephony.Call> telephonyCalls = Lists.newArrayList(); 258 telephonyCalls.addAll(mCallManager.getRingingCalls()); 259 telephonyCalls.addAll(mCallManager.getForegroundCalls()); 260 telephonyCalls.addAll(mCallManager.getBackgroundCalls()); 261 262 // Cycle through all the Connections on all the Calls. Update our Call objects 263 // to reflect any new state and send the updated Call objects to the handler service. 264 for (com.android.internal.telephony.Call telephonyCall : telephonyCalls) { 265 266 for (Connection connection : telephonyCall.getConnections()) { 267 // We do not create incoming or disconnected calls on update. Those are created 268 // from the associated onNewRingingConnection and onDisconnected which do this 269 // process on their own and slightly differently. 270 boolean create = connection.getState().isAlive() && 271 !connection.getState().isRinging(); 272 273 // New connections return a Call with INVALID state, which does not translate to 274 // a state in the internal.telephony.Call object. This ensures that staleness 275 // check below fails and we always add the item to the update list if it is new. 276 final Call call = getCallFromMap(mCallMap, connection, create); 277 278 if (call == null) { 279 continue; 280 } 281 282 boolean changed = updateCallFromConnection(call, connection, false); 283 284 if (fullUpdate || changed) { 285 out.add(call); 286 } 287 } 288 289 // We do a second loop to address conference call scenarios. We do this as a separate 290 // loop to ensure all child calls are up to date before we start updating the parent 291 // conference calls. 292 for (Connection connection : telephonyCall.getConnections()) { 293 updateForConferenceCalls(connection, out); 294 } 295 296 } 297 } 298 299 /** 300 * Checks to see if the connection is the first connection in a conference call. 301 * If it is a conference call, we will create a new Conference Call object or 302 * update the existing conference call object for that connection. 303 * If it is not a conference call but a previous associated conference call still exists, 304 * we mark it as idle and remove it from the map. 305 * In both cases above, we add the Calls to be updated to the UI. 306 * @param connection The connection object to check. 307 * @param updatedCalls List of 'updated' calls that will be sent to the UI. 308 */ 309 private boolean updateForConferenceCalls(Connection connection, List<Call> updatedCalls) { 310 // We consider this connection a conference connection if the call it 311 // belongs to is a multiparty call AND it is the first connection. 312 final boolean isConferenceCallConnection = isPartOfLiveConferenceCall(connection) && 313 connection.getCall().getEarliestConnection() == connection; 314 315 boolean changed = false; 316 317 // If this connection is the main connection for the conference call, then create or update 318 // a Call object for that conference call. 319 if (isConferenceCallConnection) { 320 final Call confCall = getCallFromMap(mConfCallMap, connection, true); 321 changed = updateCallFromConnection(confCall, connection, true); 322 323 if (changed) { 324 updatedCalls.add(confCall); 325 } 326 327 if (DBG) Log.d(TAG, "Updating a conference call: " + confCall); 328 329 // It is possible that through a conference call split, there may be lingering conference 330 // calls where this connection was the main connection. We clean those up here. 331 } else { 332 final Call oldConfCall = getCallFromMap(mConfCallMap, connection, false); 333 334 // We found a conference call for this connection, which is no longer a conference call. 335 // Kill it! 336 if (oldConfCall != null) { 337 if (DBG) Log.d(TAG, "Cleaning up an old conference call: " + oldConfCall); 338 mConfCallMap.remove(connection); 339 oldConfCall.setState(State.IDLE); 340 changed = true; 341 342 // add to the list of calls to update 343 updatedCalls.add(oldConfCall); 344 } 345 } 346 347 return changed; 348 } 349 350 /** 351 * Sets the new call state onto the call and performs some additional logic 352 * associated with setting the state. 353 */ 354 private void setNewState(Call call, int newState, Connection connection) { 355 Preconditions.checkState(call.getState() != newState); 356 357 // When starting an outgoing call, we need to grab gateway information 358 // for the call, if available, and set it. 359 final RawGatewayInfo info = mCallGatewayManager.getGatewayInfo(connection); 360 361 if (newState == Call.State.DIALING) { 362 if (!info.isEmpty()) { 363 call.setGatewayNumber(info.getFormattedGatewayNumber()); 364 call.setGatewayPackage(info.packageName); 365 } 366 } else if (!Call.State.isConnected(newState)) { 367 mCallGatewayManager.clearGatewayData(connection); 368 } 369 370 call.setState(newState); 371 } 372 373 /** 374 * Updates the Call properties to match the state of the connection object 375 * that it represents. 376 * @param call The call object to update. 377 * @param connection The connection object from which to update call. 378 * @param isForConference There are slight differences in how we populate data for conference 379 * calls. This boolean tells us which method to use. 380 */ 381 private boolean updateCallFromConnection(Call call, Connection connection, 382 boolean isForConference) { 383 boolean changed = false; 384 385 final int newState = translateStateFromTelephony(connection, isForConference); 386 387 if (call.getState() != newState) { 388 setNewState(call, newState, connection); 389 changed = true; 390 } 391 392 final Call.DisconnectCause newDisconnectCause = 393 translateDisconnectCauseFromTelephony(connection.getDisconnectCause()); 394 if (call.getDisconnectCause() != newDisconnectCause) { 395 call.setDisconnectCause(newDisconnectCause); 396 changed = true; 397 } 398 399 final long oldConnectTime = call.getConnectTime(); 400 if (oldConnectTime != connection.getConnectTime()) { 401 call.setConnectTime(connection.getConnectTime()); 402 changed = true; 403 } 404 405 if (!isForConference) { 406 // Number 407 final String oldNumber = call.getNumber(); 408 String newNumber = connection.getAddress(); 409 RawGatewayInfo info = mCallGatewayManager.getGatewayInfo(connection); 410 if (!info.isEmpty()) { 411 newNumber = info.trueNumber; 412 } 413 if (TextUtils.isEmpty(oldNumber) || !oldNumber.equals(newNumber)) { 414 call.setNumber(newNumber); 415 changed = true; 416 } 417 418 // Number presentation 419 final int newNumberPresentation = connection.getNumberPresentation(); 420 if (call.getNumberPresentation() != newNumberPresentation) { 421 call.setNumberPresentation(newNumberPresentation); 422 changed = true; 423 } 424 425 // Name 426 final String oldCnapName = call.getCnapName(); 427 if (TextUtils.isEmpty(oldCnapName) || !oldCnapName.equals(connection.getCnapName())) { 428 call.setCnapName(connection.getCnapName()); 429 changed = true; 430 } 431 432 // Name Presentation 433 final int newCnapNamePresentation = connection.getCnapNamePresentation(); 434 if (call.getCnapNamePresentation() != newCnapNamePresentation) { 435 call.setCnapNamePresentation(newCnapNamePresentation); 436 changed = true; 437 } 438 } else { 439 440 // update the list of children by: 441 // 1) Saving the old set 442 // 2) Removing all children 443 // 3) Adding the correct children into the Call 444 // 4) Comparing the new children set with the old children set 445 ImmutableSortedSet<Integer> oldSet = call.getChildCallIds(); 446 call.removeAllChildren(); 447 448 if (connection.getCall() != null) { 449 for (Connection childConn : connection.getCall().getConnections()) { 450 final Call childCall = getCallFromMap(mCallMap, childConn, false); 451 if (childCall != null && childConn.isAlive()) { 452 call.addChildId(childCall.getCallId()); 453 } 454 } 455 } 456 changed |= oldSet.equals(call.getChildCallIds()); 457 } 458 459 /** 460 * !!! Uses values from connection and call collected above so this part must be last !!! 461 */ 462 final int newCapabilities = getCapabilitiesFor(connection, call); 463 if (call.getCapabilities() != newCapabilities) { 464 call.setCapabilities(newCapabilities); 465 changed = true; 466 } 467 468 return changed; 469 } 470 471 /** 472 * Returns a mask of capabilities for the connection such as merge, hold, etc. 473 */ 474 private int getCapabilitiesFor(Connection connection, Call call) { 475 final boolean callIsActive = (call.getState() == Call.State.ACTIVE); 476 final Phone phone = connection.getCall().getPhone(); 477 478 final boolean canHold = TelephonyCapabilities.supportsAnswerAndHold(phone); 479 boolean canAddCall = false; 480 boolean canMergeCall = false; 481 boolean canSwapCall = false; 482 boolean canRespondViaText = false; 483 484 // only applies to active calls 485 if (callIsActive) { 486 canAddCall = PhoneUtils.okToAddCall(mCallManager); 487 canMergeCall = PhoneUtils.okToMergeCalls(mCallManager); 488 canSwapCall = PhoneUtils.okToSwapCalls(mCallManager); 489 } 490 491 canRespondViaText = RejectWithTextMessageManager.allowRespondViaSmsForCall(call, 492 connection); 493 494 // special rules section! 495 // CDMA always has Add 496 if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) { 497 canAddCall = true; 498 } else { 499 // if neither merge nor add is on...then allow add 500 canAddCall |= !(canAddCall || canMergeCall); 501 } 502 503 int retval = 0x0; 504 if (canHold) { 505 retval |= Capabilities.HOLD; 506 } 507 if (canAddCall) { 508 retval |= Capabilities.ADD_CALL; 509 } 510 if (canMergeCall) { 511 retval |= Capabilities.MERGE_CALLS; 512 } 513 if (canSwapCall) { 514 retval |= Capabilities.SWAP_CALLS; 515 } 516 517 if (canRespondViaText) { 518 retval |= Capabilities.RESPOND_VIA_TEXT; 519 } 520 521 return retval; 522 } 523 524 /** 525 * Returns true if the Connection is part of a multiparty call. 526 * We do this by checking the isMultiparty() method of the telephony.Call object and also 527 * checking to see if more than one of it's children is alive. 528 */ 529 private boolean isPartOfLiveConferenceCall(Connection connection) { 530 if (connection.getCall() != null && connection.getCall().isMultiparty()) { 531 int count = 0; 532 for (Connection currConn : connection.getCall().getConnections()) { 533 if (currConn.isAlive()) { 534 count++; 535 if (count >= 2) { 536 return true; 537 } 538 } 539 } 540 } 541 return false; 542 } 543 544 private int translateStateFromTelephony(Connection connection, boolean isForConference) { 545 546 int retval = State.IDLE; 547 switch (connection.getState()) { 548 case ACTIVE: 549 retval = State.ACTIVE; 550 break; 551 case INCOMING: 552 retval = State.INCOMING; 553 break; 554 case DIALING: 555 case ALERTING: 556 retval = State.DIALING; 557 break; 558 case WAITING: 559 retval = State.CALL_WAITING; 560 break; 561 case HOLDING: 562 retval = State.ONHOLD; 563 break; 564 case DISCONNECTED: 565 case DISCONNECTING: 566 retval = State.DISCONNECTED; 567 default: 568 } 569 570 // If we are dealing with a potential child call (not the parent conference call), 571 // the check to see if we have to set the state to CONFERENCED. 572 if (!isForConference) { 573 574 // if the connection is part of a multiparty call, and it is live, 575 // annotate it with CONFERENCED state instead. 576 if (isPartOfLiveConferenceCall(connection) && connection.isAlive()) { 577 return State.CONFERENCED; 578 } 579 } 580 581 return retval; 582 } 583 584 private final ImmutableMap<Connection.DisconnectCause, Call.DisconnectCause> CAUSE_MAP = 585 ImmutableMap.<Connection.DisconnectCause, Call.DisconnectCause>builder() 586 .put(Connection.DisconnectCause.BUSY, Call.DisconnectCause.BUSY) 587 .put(Connection.DisconnectCause.CALL_BARRED, Call.DisconnectCause.CALL_BARRED) 588 .put(Connection.DisconnectCause.CDMA_ACCESS_BLOCKED, 589 Call.DisconnectCause.CDMA_ACCESS_BLOCKED) 590 .put(Connection.DisconnectCause.CDMA_ACCESS_FAILURE, 591 Call.DisconnectCause.CDMA_ACCESS_FAILURE) 592 .put(Connection.DisconnectCause.CDMA_DROP, Call.DisconnectCause.CDMA_DROP) 593 .put(Connection.DisconnectCause.CDMA_INTERCEPT, Call.DisconnectCause.CDMA_INTERCEPT) 594 .put(Connection.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE, 595 Call.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE) 596 .put(Connection.DisconnectCause.CDMA_NOT_EMERGENCY, 597 Call.DisconnectCause.CDMA_NOT_EMERGENCY) 598 .put(Connection.DisconnectCause.CDMA_PREEMPTED, Call.DisconnectCause.CDMA_PREEMPTED) 599 .put(Connection.DisconnectCause.CDMA_REORDER, Call.DisconnectCause.CDMA_REORDER) 600 .put(Connection.DisconnectCause.CDMA_RETRY_ORDER, 601 Call.DisconnectCause.CDMA_RETRY_ORDER) 602 .put(Connection.DisconnectCause.CDMA_SO_REJECT, Call.DisconnectCause.CDMA_SO_REJECT) 603 .put(Connection.DisconnectCause.CONGESTION, Call.DisconnectCause.CONGESTION) 604 .put(Connection.DisconnectCause.CS_RESTRICTED, Call.DisconnectCause.CS_RESTRICTED) 605 .put(Connection.DisconnectCause.CS_RESTRICTED_EMERGENCY, 606 Call.DisconnectCause.CS_RESTRICTED_EMERGENCY) 607 .put(Connection.DisconnectCause.CS_RESTRICTED_NORMAL, 608 Call.DisconnectCause.CS_RESTRICTED_NORMAL) 609 .put(Connection.DisconnectCause.ERROR_UNSPECIFIED, 610 Call.DisconnectCause.ERROR_UNSPECIFIED) 611 .put(Connection.DisconnectCause.FDN_BLOCKED, Call.DisconnectCause.FDN_BLOCKED) 612 .put(Connection.DisconnectCause.ICC_ERROR, Call.DisconnectCause.ICC_ERROR) 613 .put(Connection.DisconnectCause.INCOMING_MISSED, 614 Call.DisconnectCause.INCOMING_MISSED) 615 .put(Connection.DisconnectCause.INCOMING_REJECTED, 616 Call.DisconnectCause.INCOMING_REJECTED) 617 .put(Connection.DisconnectCause.INVALID_CREDENTIALS, 618 Call.DisconnectCause.INVALID_CREDENTIALS) 619 .put(Connection.DisconnectCause.INVALID_NUMBER, 620 Call.DisconnectCause.INVALID_NUMBER) 621 .put(Connection.DisconnectCause.LIMIT_EXCEEDED, Call.DisconnectCause.LIMIT_EXCEEDED) 622 .put(Connection.DisconnectCause.LOCAL, Call.DisconnectCause.LOCAL) 623 .put(Connection.DisconnectCause.LOST_SIGNAL, Call.DisconnectCause.LOST_SIGNAL) 624 .put(Connection.DisconnectCause.MMI, Call.DisconnectCause.MMI) 625 .put(Connection.DisconnectCause.NORMAL, Call.DisconnectCause.NORMAL) 626 .put(Connection.DisconnectCause.NOT_DISCONNECTED, 627 Call.DisconnectCause.NOT_DISCONNECTED) 628 .put(Connection.DisconnectCause.NUMBER_UNREACHABLE, 629 Call.DisconnectCause.NUMBER_UNREACHABLE) 630 .put(Connection.DisconnectCause.OUT_OF_NETWORK, Call.DisconnectCause.OUT_OF_NETWORK) 631 .put(Connection.DisconnectCause.OUT_OF_SERVICE, Call.DisconnectCause.OUT_OF_SERVICE) 632 .put(Connection.DisconnectCause.POWER_OFF, Call.DisconnectCause.POWER_OFF) 633 .put(Connection.DisconnectCause.SERVER_ERROR, Call.DisconnectCause.SERVER_ERROR) 634 .put(Connection.DisconnectCause.SERVER_UNREACHABLE, 635 Call.DisconnectCause.SERVER_UNREACHABLE) 636 .put(Connection.DisconnectCause.TIMED_OUT, Call.DisconnectCause.TIMED_OUT) 637 .put(Connection.DisconnectCause.UNOBTAINABLE_NUMBER, 638 Call.DisconnectCause.UNOBTAINABLE_NUMBER) 639 .build(); 640 641 private Call.DisconnectCause translateDisconnectCauseFromTelephony( 642 Connection.DisconnectCause causeSource) { 643 644 if (CAUSE_MAP.containsKey(causeSource)) { 645 return CAUSE_MAP.get(causeSource); 646 } 647 648 return Call.DisconnectCause.UNKNOWN; 649 } 650 651 /** 652 * Gets an existing callId for a connection, or creates one if none exists. 653 * This function does NOT set any of the Connection data onto the Call class. 654 * A separate call to updateCallFromConnection must be made for that purpose. 655 */ 656 private Call getCallFromMap(HashMap<Connection, Call> map, Connection conn, 657 boolean createIfMissing) { 658 Call call = null; 659 660 // Find the call id or create if missing and requested. 661 if (conn != null) { 662 if (map.containsKey(conn)) { 663 call = map.get(conn); 664 } else if (createIfMissing) { 665 call = createNewCall(); 666 map.put(conn, call); 667 } 668 } 669 return call; 670 } 671 672 /** 673 * Creates a brand new connection for the call. 674 */ 675 private Call createNewCall() { 676 int callId; 677 int newNextCallId; 678 do { 679 callId = mNextCallId.get(); 680 681 // protect against overflow 682 newNextCallId = (callId == Integer.MAX_VALUE ? 683 CALL_ID_START_VALUE : callId + 1); 684 685 // Keep looping if the change was not atomic OR the value is already taken. 686 // The call to containsValue() is linear, however, most devices support a 687 // maximum of 7 connections so it's not expensive. 688 } while (!mNextCallId.compareAndSet(callId, newNextCallId)); 689 690 return new Call(callId); 691 } 692 693 /** 694 * Listener interface for changes to Calls. 695 */ 696 public interface Listener { 697 void onDisconnect(Call call); 698 void onIncoming(Call call); 699 void onUpdate(List<Call> calls); 700 } 701 702 /** 703 * Result class for accessing a call by connection. 704 */ 705 public static class CallResult { 706 public Call mCall; 707 public Call mActionableCall; 708 public Connection mConnection; 709 710 private CallResult(Call call, Connection connection) { 711 this(call, call, connection); 712 } 713 714 private CallResult(Call call, Call actionableCall, Connection connection) { 715 mCall = call; 716 mActionableCall = actionableCall; 717 mConnection = connection; 718 } 719 720 public Call getCall() { 721 return mCall; 722 } 723 724 // The call that should be used for call actions like hanging up. 725 public Call getActionableCall() { 726 return mActionableCall; 727 } 728 729 public Connection getConnection() { 730 return mConnection; 731 } 732 } 733} 734