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