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