CallList.java revision a12f2589f424e26c63c8ee5b6fad89e30b69d7fb
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.os.Handler; 27import android.os.Message; 28 29import com.android.services.telephony.common.Call; 30 31import java.util.AbstractMap; 32import java.util.ArrayList; 33import java.util.HashMap; 34import java.util.List; 35import java.util.Map; 36import java.util.Set; 37 38/** 39 * Maintains the list of active calls received from CallHandlerService and notifies interested 40 * classes of changes to the call list as they are received from the telephony stack. 41 * Primary lister of changes to this class is InCallPresenter. 42 */ 43public class CallList { 44 45 private static final int DISCONNECTED_CALL_TIMEOUT_MS = 3000; 46 47 private static final int EVENT_DISCONNECTED_TIMEOUT = 1; 48 49 private static CallList sInstance; 50 51 private final HashMap<Integer, Call> mCallMap = Maps.newHashMap(); 52 private final HashMap<Integer, ArrayList<String>> mCallTextReponsesMap = 53 Maps.newHashMap(); 54 private final Set<Listener> mListeners = Sets.newArraySet(); 55 56 /** 57 * Static singleton accessor method. 58 */ 59 public static synchronized CallList getInstance() { 60 if (sInstance == null) { 61 sInstance = new CallList(); 62 } 63 return sInstance; 64 } 65 66 /** 67 * Private constructor. Instance should only be acquired through getInstance(). 68 */ 69 private CallList() { 70 } 71 72 /** 73 * Called when a single call has changed. 74 */ 75 public void onUpdate(Call call) { 76 Logger.d(this, "onUpdate - ", call); 77 78 updateCallInMap(call); 79 notifyListenersOfChange(); 80 } 81 82 /** 83 * Called when a single call disconnects. 84 */ 85 public void onDisconnect(Call call) { 86 Logger.d(this, "onDisconnect: ", call); 87 88 updateCallInMap(call); 89 90 notifyListenersOfChange(); 91 } 92 93 /** 94 * Called when a single call has changed. 95 */ 96 public void onUpdate(AbstractMap.SimpleEntry<Call, List<String> > incomingCall) { 97 Logger.d(this, "onUpdate - " + incomingCall.getKey()); 98 99 updateCallInMap(incomingCall.getKey()); 100 updateCallTextMap(incomingCall.getKey(), incomingCall.getValue()); 101 102 notifyListenersOfChange(); 103 } 104 105 /** 106 * Called when multiple calls have changed. 107 */ 108 public void onUpdate(List<Call> callsToUpdate) { 109 Logger.d(this, "onUpdate(...)"); 110 111 Preconditions.checkNotNull(callsToUpdate); 112 for (Call call : callsToUpdate) { 113 Logger.d(this, "\t" + call); 114 115 updateCallInMap(call); 116 updateCallTextMap(call, null); 117 } 118 119 notifyListenersOfChange(); 120 } 121 122 public void addListener(Listener listener) { 123 Preconditions.checkNotNull(listener); 124 125 mListeners.add(listener); 126 127 // Let the listener know about the active calls immediately. 128 listener.onCallListChange(this); 129 } 130 131 public void removeListener(Listener listener) { 132 Preconditions.checkNotNull(listener); 133 mListeners.remove(listener); 134 } 135 136 /** 137 * TODO: Change so that this function is not needed. Instead of assuming there is an active 138 * call, the code should rely on the status of a specific Call and allow the presenters to 139 * update the Call object when the active call changes. 140 */ 141 public Call getIncomingOrActive() { 142 Call retval = getIncomingCall(); 143 if (retval == null) { 144 retval = getActiveCall(); 145 } 146 return retval; 147 } 148 149 public Call getOutgoingCall() { 150 return getFirstCallWithState(Call.State.DIALING); 151 } 152 153 public Call getActiveCall() { 154 return getFirstCallWithState(Call.State.ACTIVE); 155 } 156 157 public Call getBackgroundCall() { 158 return getFirstCallWithState(Call.State.ONHOLD); 159 } 160 161 public Call getDisconnectedCall() { 162 return getFirstCallWithState(Call.State.DISCONNECTED); 163 } 164 165 public Call getSecondBackgroundCall() { 166 return getCallWithState(Call.State.ONHOLD, 1); 167 } 168 169 public Call getActiveOrBackgroundCall() { 170 Call call = getActiveCall(); 171 if (call == null) { 172 call = getBackgroundCall(); 173 } 174 return call; 175 } 176 177 public Call getIncomingCall() { 178 Call call = getFirstCallWithState(Call.State.INCOMING); 179 if (call == null) { 180 call = getFirstCallWithState(Call.State.CALL_WAITING); 181 } 182 183 return call; 184 } 185 186 public boolean existsLiveCall() { 187 for (Call call : mCallMap.values()) { 188 if (!isCallDead(call)) { 189 return true; 190 } 191 } 192 return false; 193 } 194 195 public ArrayList<String> getTextResponses(Call call) { 196 return mCallTextReponsesMap.get(call.getCallId()); 197 } 198 199 /** 200 * Returns first call found in the call map with the specified state. 201 */ 202 public Call getFirstCallWithState(int state) { 203 return getCallWithState(state, 0); 204 } 205 206 /** 207 * Returns the [position]th call found in the call map with the specified state. 208 * TODO(klp): Improve this logic to sort by call time. 209 */ 210 public Call getCallWithState(int state, int positionToFind) { 211 Call retval = null; 212 int position = 0; 213 for (Call call : mCallMap.values()) { 214 if (call.getState() == state) { 215 if (position >= positionToFind) { 216 retval = call; 217 break; 218 } else { 219 position++; 220 } 221 } 222 } 223 224 Logger.v(this, "Found call: ", retval); 225 return retval; 226 } 227 228 /** 229 * Sends a generic notification to all listeners that something has changed. 230 * It is up to the listeners to call back to determine what changed. 231 */ 232 private void notifyListenersOfChange() { 233 for (Listener listener : mListeners) { 234 listener.onCallListChange(this); 235 } 236 } 237 238 private void updateCallInMap(Call call) { 239 Preconditions.checkNotNull(call); 240 241 final Integer id = new Integer(call.getCallId()); 242 243 if (call.getState() == Call.State.DISCONNECTED) { 244 // For disconnected calls, we want to keep them alive for a few seconds so that the UI 245 // has a chance to display anything it needs when a call is disconnected. 246 247 // Set up a timer to destroy the call after X seconds. 248 Message msg = mHandler.obtainMessage(EVENT_DISCONNECTED_TIMEOUT, call); 249 boolean sent = mHandler.sendMessageDelayed(msg, DISCONNECTED_CALL_TIMEOUT_MS); 250 251 Logger.d(this, "Retval from sendMessageDelayed: ", Boolean.toString(sent)); 252 253 // Don't add disconnected calls that do not already exist in the map 254 if (mCallMap.containsKey(id)) { 255 mCallMap.put(id, call); 256 } 257 } else if (!isCallDead(call)) { 258 mCallMap.put(id, call); 259 } else if (mCallMap.containsKey(id)) { 260 mCallMap.remove(id); 261 } 262 } 263 264 private void updateCallTextMap(Call call, List<String> textResponses) { 265 Preconditions.checkNotNull(call); 266 267 final Integer id = new Integer(call.getCallId()); 268 269 if (!isCallDead(call)) { 270 if (textResponses != null) { 271 mCallTextReponsesMap.put(id, (ArrayList<String>) textResponses); 272 } 273 } else if (mCallMap.containsKey(id)) { 274 mCallTextReponsesMap.remove(id); 275 } 276 } 277 278 private boolean isCallDead(Call call) { 279 final int state = call.getState(); 280 return Call.State.IDLE == state || Call.State.INVALID == state; 281 } 282 283 /** 284 * Sets up a call for deletion and notifies listeners of change. 285 */ 286 private void finishDisconnectedCall(Call call) { 287 call.setState(Call.State.IDLE); 288 updateCallInMap(call); 289 notifyListenersOfChange(); 290 } 291 292 /** 293 * Handles the timeout for destroying disconnected calls. 294 */ 295 private Handler mHandler = new Handler() { 296 @Override 297 public void handleMessage(Message msg) { 298 switch (msg.what) { 299 case EVENT_DISCONNECTED_TIMEOUT: 300 Logger.d(this, "EVENT_DISCONNECTED_TIMEOUT ", msg.obj); 301 finishDisconnectedCall((Call) msg.obj); 302 break; 303 default: 304 Logger.wtf(this, "Message not expected: " + msg.what); 305 break; 306 } 307 } 308 }; 309 310 /** 311 * Listener interface for any class that wants to be notified of changes 312 * to the call list. 313 */ 314 public interface Listener { 315 public void onCallListChange(CallList callList); 316 } 317} 318