CallList.java revision 671b221ccadea34fb9327ef5342b26419eb5ca99
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 android.util.Log;
27
28import com.android.services.telephony.common.Call;
29
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 String TAG = CallList.class.getSimpleName();
42    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
43    private static final Map<Integer, String> STATE_MAP = ImmutableMap.<Integer, String>builder()
44            .put(Call.State.ACTIVE, "ACTIVE")
45            .put(Call.State.CALL_WAITING, "CALL_WAITING")
46            .put(Call.State.DIALING, "DIALING")
47            .put(Call.State.IDLE, "IDLE")
48            .put(Call.State.INCOMING, "INCOMING")
49            .put(Call.State.ONHOLD, "ONHOLD")
50            .put(Call.State.INVALID, "INVALID")
51            .build();
52
53    private static CallList sInstance;
54
55    private final HashMap<Integer, Call> mCallMap = 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        logD("onUpdate - " + safeCallString(call));
79
80        updateCallInMap(call);
81        notifyListenersOfChange();
82    }
83
84    /**
85     * Called when multiple calls have changed.
86     */
87    public void onUpdate(List<Call> callsToUpdate) {
88        logD("onUpdate(...)");
89
90        Preconditions.checkNotNull(callsToUpdate);
91        for (Call call : callsToUpdate) {
92            logD("\t" + safeCallString(call));
93
94            updateCallInMap(call);
95        }
96
97        notifyListenersOfChange();
98    }
99
100    public void addListener(Listener listener) {
101        Preconditions.checkNotNull(listener);
102
103        mListeners.add(listener);
104
105        // Let the listener know about the active calls immediately.
106        listener.onCallListChange(this);
107    }
108
109    public void removeListener(Listener listener) {
110        Preconditions.checkNotNull(listener);
111        mListeners.remove(listener);
112    }
113
114    /**
115     * TODO(klp): Change so that this function is not needed. Instead of assuming there is an active
116     * call, the code should rely on the status of a specific Call and allow the presenters to
117     * update the Call object when the active call changes.
118     */
119    public Call getIncomingOrActive() {
120        Call retval = getIncomingCall();
121        if (retval == null) {
122            retval = getActiveCall();
123        }
124        return retval;
125    }
126
127    public Call getActiveCall() {
128        return getFirstCallWithState(Call.State.ACTIVE);
129    }
130
131    public Call getBackgroundCall() {
132        return getFirstCallWithState(Call.State.ONHOLD);
133    }
134
135    public Call getIncomingCall() {
136        return getFirstCallWithState(Call.State.INCOMING);
137    }
138
139    public boolean existsLiveCall() {
140        for (Call call : mCallMap.values()) {
141            if (!isCallDead(call)) {
142                return true;
143            }
144        }
145        return false;
146    }
147
148    /**
149     * Returns first call found in the call map with the specified state.
150     */
151    public Call getFirstCallWithState(int state) {
152        Call retval = null;
153        for (Call call : mCallMap.values()) {
154            if (call.getState() == state) {
155                retval = call;
156                break;
157            }
158        }
159
160        logD("Found " + (retval == null ? "no " : "") + "call with state: " +
161                STATE_MAP.get(state));
162        return retval;
163    }
164
165    /**
166     * Sends a generic notification to all listeners that something has changed.
167     * It is up to the listeners to call back to determine what changed.
168     */
169    private void notifyListenersOfChange() {
170        for (Listener listener : mListeners) {
171            listener.onCallListChange(this);
172        }
173    }
174
175    private void updateCallInMap(Call call) {
176        Preconditions.checkNotNull(call);
177
178        final Integer id = new Integer(call.getCallId());
179
180        if (!isCallDead(call)) {
181            mCallMap.put(id, call);
182        } else if (mCallMap.containsKey(id)) {
183            mCallMap.remove(id);
184        }
185    }
186
187    private boolean isCallDead(Call call) {
188        final int state = call.getState();
189        return Call.State.IDLE == state || Call.State.INVALID == state;
190    }
191
192    private void logD(String msg) {
193        if (DEBUG) {
194            Log.d(TAG, msg);
195        }
196    }
197
198    /**
199     * Creates a log-safe string for call objects.
200     */
201    private String safeCallString(Call call) {
202        final StringBuffer buffer = new StringBuffer();
203        buffer.append("Call (")
204                .append(call.getCallId())
205                .append("), ")
206                .append(STATE_MAP.get(call.getState()));
207        return buffer.toString();
208    }
209
210    /**
211     * Listener interface for any class that wants to be notified of changes
212     * to the call list.
213     */
214    public interface Listener {
215        public void onCallListChange(CallList callList);
216    }
217}
218