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