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.os.AsyncResult;
20import android.os.Handler;
21import android.os.Message;
22import android.os.SystemProperties;
23import android.telephony.PhoneNumberUtils;
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.internal.telephony.cdma.CdmaCallWaitingNotification;
33import com.android.phone.CallGatewayManager.RawGatewayInfo;
34import com.android.services.telephony.common.Call;
35import com.android.services.telephony.common.Call.Capabilities;
36import com.android.services.telephony.common.Call.State;
37
38import com.google.android.collect.Maps;
39import com.google.android.collect.Sets;
40import com.google.common.base.Preconditions;
41import com.google.common.collect.ImmutableMap;
42import com.google.common.collect.ImmutableSortedSet;
43import com.google.common.collect.Lists;
44
45import java.util.ArrayList;
46import java.util.Collections;
47import java.util.HashMap;
48import java.util.List;
49import java.util.Map.Entry;
50import java.util.Set;
51import 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 */
81public 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