CallList.java revision e7be13fb0556e62b07bc271b130412d82d7f7521
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.incallui;
18
19import com.google.android.collect.Lists;
20import com.google.android.collect.Maps;
21import com.google.android.collect.Sets;
22import com.google.common.base.Preconditions;
23import com.google.common.collect.ImmutableList;
24import com.google.common.collect.ImmutableMap;
25
26import com.android.services.telephony.common.Call;
27
28import java.util.AbstractMap;
29import java.util.ArrayList;
30import java.util.HashMap;
31import java.util.List;
32import java.util.Map;
33import java.util.Set;
34
35/**
36 * Maintains the list of active calls received from CallHandlerService and notifies interested
37 * classes of changes to the call list as they are received from the telephony stack.
38 * Primary lister of changes to this class is InCallPresenter.
39 */
40public class CallList {
41
42    private static CallList sInstance;
43
44    private final HashMap<Integer, Call> mCallMap = Maps.newHashMap();
45    private final HashMap<Integer, ArrayList<String>> mCallTextReponsesMap =
46            Maps.newHashMap();
47    private final Set<Listener> mListeners = Sets.newArraySet();
48
49    /**
50     * Static singleton accessor method.
51     */
52    public static synchronized CallList getInstance() {
53        if (sInstance == null) {
54            sInstance = new CallList();
55        }
56        return sInstance;
57    }
58
59    /**
60     * Private constructor.  Instance should only be acquired through getInstance().
61     */
62    private CallList() {
63    }
64
65    /**
66     * Called when a single call has changed.
67     */
68    public void onUpdate(Call call) {
69        Logger.d(this, "onUpdate - " + call);
70
71        updateCallInMap(call);
72
73        notifyListenersOfChange();
74    }
75
76    /**
77     * Called when a single call has changed.
78     */
79    public void onUpdate(AbstractMap.SimpleEntry<Call, List<String> > incomingCall) {
80        Logger.d(this, "onUpdate - " + incomingCall.getKey());
81
82        updateCallInMap(incomingCall.getKey());
83        updateCallTextMap(incomingCall.getKey(), incomingCall.getValue());
84
85        notifyListenersOfChange();
86    }
87
88    /**
89     * Called when multiple calls have changed.
90     */
91    public void onUpdate(List<Call> callsToUpdate) {
92        Logger.d(this, "onUpdate(...)");
93
94        Preconditions.checkNotNull(callsToUpdate);
95        for (Call call : callsToUpdate) {
96            Logger.d(this, "\t" + call);
97
98            updateCallInMap(call);
99            updateCallTextMap(call, null);
100        }
101
102        notifyListenersOfChange();
103    }
104
105    public void addListener(Listener listener) {
106        Preconditions.checkNotNull(listener);
107
108        mListeners.add(listener);
109
110        // Let the listener know about the active calls immediately.
111        listener.onCallListChange(this);
112    }
113
114    public void removeListener(Listener listener) {
115        Preconditions.checkNotNull(listener);
116        mListeners.remove(listener);
117    }
118
119    /**
120     * TODO: Change so that this function is not needed. Instead of assuming there is an active
121     * call, the code should rely on the status of a specific Call and allow the presenters to
122     * update the Call object when the active call changes.
123     */
124    public Call getIncomingOrActive() {
125        Call retval = getIncomingCall();
126        if (retval == null) {
127            retval = getActiveCall();
128        }
129        return retval;
130    }
131
132    public Call getOutgoingCall() {
133        return getFirstCallWithState(Call.State.DIALING);
134    }
135
136    public Call getActiveCall() {
137        return getFirstCallWithState(Call.State.ACTIVE);
138    }
139
140    public Call getBackgroundCall() {
141        return getFirstCallWithState(Call.State.ONHOLD);
142    }
143
144    public Call getSecondBackgroundCall() {
145        return getCallWithState(Call.State.ONHOLD, 1);
146    }
147
148    public Call getActiveOrBackgroundCall() {
149        Call call = getActiveCall();
150        if (call == null) {
151            call = getBackgroundCall();
152        }
153        return call;
154    }
155
156    public Call getIncomingCall() {
157        Call call = getFirstCallWithState(Call.State.INCOMING);
158        if (call == null) {
159            call = getFirstCallWithState(Call.State.CALL_WAITING);
160        }
161
162        return call;
163    }
164
165    public boolean existsLiveCall() {
166        for (Call call : mCallMap.values()) {
167            if (!isCallDead(call)) {
168                return true;
169            }
170        }
171        return false;
172    }
173
174    public ArrayList<String> getTextResponses(Call call) {
175        return mCallTextReponsesMap.get(call.getCallId());
176    }
177
178    /**
179     * Returns first call found in the call map with the specified state.
180     */
181    public Call getFirstCallWithState(int state) {
182        return getCallWithState(state, 0);
183    }
184
185    /**
186     * Returns the [position]th call found in the call map with the specified state.
187     * TODO(klp): Improve this logic to sort by call time.
188     */
189    public Call getCallWithState(int state, int positionToFind) {
190        Call retval = null;
191        int position = 0;
192        for (Call call : mCallMap.values()) {
193            if (call.getState() == state) {
194                if (position >= positionToFind) {
195                    retval = call;
196                    break;
197                } else {
198                    position++;
199                }
200            }
201        }
202
203        Logger.d(this, "Found call: " + retval);
204        return retval;
205    }
206
207    /**
208     * Sends a generic notification to all listeners that something has changed.
209     * It is up to the listeners to call back to determine what changed.
210     */
211    private void notifyListenersOfChange() {
212        for (Listener listener : mListeners) {
213            listener.onCallListChange(this);
214        }
215    }
216
217    private void updateCallInMap(Call call) {
218        Preconditions.checkNotNull(call);
219
220        final Integer id = new Integer(call.getCallId());
221
222        if (!isCallDead(call)) {
223            mCallMap.put(id, call);
224        } else if (mCallMap.containsKey(id)) {
225            mCallMap.remove(id);
226        }
227    }
228
229    private void updateCallTextMap(Call call, List<String> textResponses) {
230        Preconditions.checkNotNull(call);
231
232        final Integer id = new Integer(call.getCallId());
233
234        if (!isCallDead(call)) {
235            if (textResponses != null) {
236                mCallTextReponsesMap.put(id, (ArrayList<String>) textResponses);
237            }
238        } else if (mCallMap.containsKey(id)) {
239            mCallTextReponsesMap.remove(id);
240        }
241    }
242
243    private boolean isCallDead(Call call) {
244        final int state = call.getState();
245        return Call.State.IDLE == state || Call.State.INVALID == state;
246    }
247
248    /**
249     * Listener interface for any class that wants to be notified of changes
250     * to the call list.
251     */
252    public interface Listener {
253        public void onCallListChange(CallList callList);
254    }
255}
256