blob: 7f0c5c50f776e72e482f53371973847bfb98f5c4 [file] [log] [blame]
Santos Cordon63a84242013-07-23 13:32:52 -07001/*
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.phone;
18
19import com.google.android.collect.Lists;
20import com.google.android.collect.Maps;
21import com.google.common.base.Preconditions;
Santos Cordone38b1ff2013-08-07 12:12:16 -070022import com.google.common.collect.ImmutableMap;
Santos Cordon63a84242013-07-23 13:32:52 -070023
24import android.os.AsyncResult;
25import android.os.Handler;
26import android.os.Message;
Santos Cordone38b1ff2013-08-07 12:12:16 -070027import android.text.TextUtils;
28import android.util.Log;
Santos Cordon63a84242013-07-23 13:32:52 -070029
Santos Cordona3d05142013-07-29 11:25:17 -070030import com.android.internal.telephony.CallManager;
Santos Cordon63a84242013-07-23 13:32:52 -070031import com.android.internal.telephony.Connection;
Santos Cordona3d05142013-07-29 11:25:17 -070032import com.android.internal.telephony.PhoneConstants;
Santos Cordon995c8162013-07-29 09:22:22 -070033import com.android.services.telephony.common.Call;
Santos Cordona3d05142013-07-29 11:25:17 -070034import com.android.services.telephony.common.Call.State;
Santos Cordon63a84242013-07-23 13:32:52 -070035
36import java.util.ArrayList;
37import java.util.HashMap;
38import java.util.List;
Santos Cordon249efd02013-08-05 03:33:56 -070039import java.util.Map.Entry;
Santos Cordon63a84242013-07-23 13:32:52 -070040import java.util.concurrent.atomic.AtomicInteger;
41
42/**
43 * Creates a Call model from Call state and data received from the telephony
44 * layer. The telephony layer maintains 3 conceptual objects: Phone, Call,
45 * Connection.
46 *
47 * Phone represents the radio and there is an implementation per technology
48 * type such as GSMPhone, SipPhone, CDMAPhone, etc. Generally, we will only ever
49 * deal with one instance of this object for the lifetime of this class.
50 *
51 * There are 3 Call instances that exist for the lifetime of this class which
52 * are created by CallTracker. The three are RingingCall, ForegroundCall, and
53 * BackgroundCall.
54 *
55 * A Connection most closely resembles what the layperson would consider a call.
56 * A Connection is created when a user dials and it is "owned" by one of the
57 * three Call instances. Which of the three Calls owns the Connection changes
58 * as the Connection goes between ACTIVE, HOLD, RINGING, and other states.
59 *
60 * This class models a new Call class from Connection objects received from
61 * the telephony layer. We use Connection references as identifiers for a call;
62 * new reference = new call.
63 *
64 * TODO(klp): Create a new Call class to replace the simple call Id ints
65 * being used currently.
66 *
67 * The new Call models are parcellable for transfer via the CallHandlerService
68 * API.
69 */
70public class CallModeler extends Handler {
71
72 private static final String TAG = CallModeler.class.getSimpleName();
73
74 private static final int CALL_ID_START_VALUE = 1;
Santos Cordon63a84242013-07-23 13:32:52 -070075
Santos Cordon998f42b2013-08-02 16:13:12 -070076 private final CallStateMonitor mCallStateMonitor;
77 private final CallManager mCallManager;
78 private final HashMap<Connection, Call> mCallMap = Maps.newHashMap();
79 private final AtomicInteger mNextCallId = new AtomicInteger(CALL_ID_START_VALUE);
Christine Chendaf7bf62013-08-05 19:12:31 -070080 private final ArrayList<Listener> mListeners = new ArrayList<Listener>();
Christine Chenee09a492013-08-06 16:02:29 -070081 private RejectWithTextMessageManager mRejectWithTextMessageManager;
Santos Cordon63a84242013-07-23 13:32:52 -070082
Christine Chenee09a492013-08-06 16:02:29 -070083 public CallModeler(CallStateMonitor callStateMonitor, CallManager callManager,
84 RejectWithTextMessageManager rejectWithTextMessageManager) {
Santos Cordon63a84242013-07-23 13:32:52 -070085 mCallStateMonitor = callStateMonitor;
Santos Cordona3d05142013-07-29 11:25:17 -070086 mCallManager = callManager;
Christine Chenee09a492013-08-06 16:02:29 -070087 mRejectWithTextMessageManager = rejectWithTextMessageManager;
Santos Cordon63a84242013-07-23 13:32:52 -070088
89 mCallStateMonitor.addListener(this);
90 }
91
Santos Cordone38b1ff2013-08-07 12:12:16 -070092 //@Override
Santos Cordon63a84242013-07-23 13:32:52 -070093 public void handleMessage(Message msg) {
94 switch(msg.what) {
95 case CallStateMonitor.PHONE_NEW_RINGING_CONNECTION:
96 onNewRingingConnection((AsyncResult) msg.obj);
97 break;
98 case CallStateMonitor.PHONE_DISCONNECT:
99 onDisconnect((AsyncResult) msg.obj);
Santos Cordon995c8162013-07-29 09:22:22 -0700100 break;
101 case CallStateMonitor.PHONE_STATE_CHANGED:
102 onPhoneStateChanged((AsyncResult) msg.obj);
103 break;
Santos Cordon63a84242013-07-23 13:32:52 -0700104 default:
105 break;
106 }
107 }
108
Christine Chendaf7bf62013-08-05 19:12:31 -0700109 public void addListener(Listener listener) {
Santos Cordon63a84242013-07-23 13:32:52 -0700110 Preconditions.checkNotNull(listener);
Christine Chendaf7bf62013-08-05 19:12:31 -0700111 Preconditions.checkNotNull(mListeners);
Christine Chen4748abd2013-08-07 15:44:15 -0700112 if (!mListeners.contains(listener)) {
113 mListeners.add(listener);
114 }
Santos Cordon998f42b2013-08-02 16:13:12 -0700115 }
116
117 public List<Call> getFullList() {
118 final List<Call> retval = Lists.newArrayList();
119 doUpdate(true, retval);
120 return retval;
Santos Cordon63a84242013-07-23 13:32:52 -0700121 }
122
Santos Cordon249efd02013-08-05 03:33:56 -0700123 public CallResult getCallWithId(int callId) {
124 // max 8 connections, so this should be fast even through we are traversing the entire map.
125 for (Entry<Connection, Call> entry : mCallMap.entrySet()) {
126 if (entry.getValue().getCallId() == callId) {
127 return new CallResult(entry.getValue(), entry.getKey());
128 }
129 }
130 return null;
131 }
132
Santos Cordon2eaff902013-08-05 04:37:55 -0700133 public boolean hasOutstandingActiveCall() {
134 for (Call call : mCallMap.values()) {
135 int state = call.getState();
136 if (Call.State.INVALID != state &&
137 Call.State.IDLE != state &&
138 Call.State.INCOMING != state) {
139 return true;
140 }
141 }
142
143 return false;
144 }
145
Santos Cordon63a84242013-07-23 13:32:52 -0700146 private void onNewRingingConnection(AsyncResult r) {
147 final Connection conn = (Connection) r.result;
Santos Cordon995c8162013-07-29 09:22:22 -0700148 final Call call = getCallFromConnection(conn, true);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700149
150 updateCallFromConnection(call, conn);
Santos Cordona3d05142013-07-29 11:25:17 -0700151 call.setState(Call.State.INCOMING);
Santos Cordon63a84242013-07-23 13:32:52 -0700152
Christine Chendaf7bf62013-08-05 19:12:31 -0700153 for (int i = 0; i < mListeners.size(); ++i) {
Christine Chenee09a492013-08-06 16:02:29 -0700154 if (call != null) {
155 mListeners.get(i).onIncoming(call,
156 mRejectWithTextMessageManager.loadCannedResponses());
157 }
Santos Cordon63a84242013-07-23 13:32:52 -0700158 }
159 }
160
161 private void onDisconnect(AsyncResult r) {
162 final Connection conn = (Connection) r.result;
Santos Cordon995c8162013-07-29 09:22:22 -0700163 final Call call = getCallFromConnection(conn, false);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700164
165 updateCallFromConnection(call, conn);
166 call.setState(Call.State.DISCONNECTED);
Santos Cordon63a84242013-07-23 13:32:52 -0700167
Santos Cordon995c8162013-07-29 09:22:22 -0700168 if (call != null) {
169 mCallMap.remove(conn);
Santos Cordon63a84242013-07-23 13:32:52 -0700170
Christine Chendaf7bf62013-08-05 19:12:31 -0700171 for (int i = 0; i < mListeners.size(); ++i) {
172 mListeners.get(i).onDisconnect(call);
Santos Cordon63a84242013-07-23 13:32:52 -0700173 }
174 }
175 }
176
Santos Cordona3d05142013-07-29 11:25:17 -0700177 /**
178 * Called when the phone state changes.
Santos Cordona3d05142013-07-29 11:25:17 -0700179 */
Santos Cordon995c8162013-07-29 09:22:22 -0700180 private void onPhoneStateChanged(AsyncResult r) {
Santos Cordon998f42b2013-08-02 16:13:12 -0700181 final List<Call> updatedCalls = Lists.newArrayList();
182 doUpdate(false, updatedCalls);
183
Christine Chendaf7bf62013-08-05 19:12:31 -0700184 for (int i = 0; i < mListeners.size(); ++i) {
185 mListeners.get(i).onUpdate(updatedCalls, false);
Santos Cordon998f42b2013-08-02 16:13:12 -0700186 }
187 }
188
189
190 /**
191 * Go through the Calls from CallManager and return the list of calls that were updated.
192 * Or, the full list if requested.
193 */
194 private void doUpdate(boolean fullUpdate, List<Call> out) {
Santos Cordona3d05142013-07-29 11:25:17 -0700195 final List<com.android.internal.telephony.Call> telephonyCalls = Lists.newArrayList();
196 telephonyCalls.addAll(mCallManager.getRingingCalls());
197 telephonyCalls.addAll(mCallManager.getForegroundCalls());
198 telephonyCalls.addAll(mCallManager.getBackgroundCalls());
199
Santos Cordona3d05142013-07-29 11:25:17 -0700200 // Cycle through all the Connections on all the Calls. Update our Call objects
201 // to reflect any new state and send the updated Call objects to the handler service.
202 for (com.android.internal.telephony.Call telephonyCall : telephonyCalls) {
Santos Cordona3d05142013-07-29 11:25:17 -0700203
204 for (Connection connection : telephonyCall.getConnections()) {
Santos Cordon998f42b2013-08-02 16:13:12 -0700205 // new connections return a Call with INVALID state, which does not translate to
Santos Cordone38b1ff2013-08-07 12:12:16 -0700206 // a state in the internal.telephony.Call object. This ensures that staleness
207 // check below fails and we always add the item to the update list if it is new.
Santos Cordona3d05142013-07-29 11:25:17 -0700208 final Call call = getCallFromConnection(connection, true);
209
Santos Cordone38b1ff2013-08-07 12:12:16 -0700210 boolean changed = updateCallFromConnection(call, connection);
211
212 if (fullUpdate || changed) {
Santos Cordon998f42b2013-08-02 16:13:12 -0700213 out.add(call);
Santos Cordona3d05142013-07-29 11:25:17 -0700214 }
215 }
216 }
Santos Cordona3d05142013-07-29 11:25:17 -0700217 }
218
Santos Cordone38b1ff2013-08-07 12:12:16 -0700219 /**
220 * Updates the Call properties to match the state of the connection object
221 * that it represents.
222 */
223 private boolean updateCallFromConnection(Call call, Connection connection) {
224 boolean changed = false;
225
226 com.android.internal.telephony.Call telephonyCall = connection.getCall();
227 final int newState = translateStateFromTelephony(telephonyCall.getState());
228
229 if (call.getState() != newState) {
230 call.setState(newState);
231 changed = true;
232 }
233
234 final String oldNumber = call.getNumber();
235 if (TextUtils.isEmpty(oldNumber) || !oldNumber.equals(connection.getAddress())) {
236 call.setNumber(connection.getAddress());
237 changed = true;
238 }
239
240 final Call.DisconnectCause newDisconnectCause =
241 translateDisconnectCauseFromTelephony(connection.getDisconnectCause());
242 if (call.getDisconnectCause() != newDisconnectCause) {
243 call.setDisconnectCause(newDisconnectCause);
244 changed = true;
245 }
246
247 final int newNumberPresentation = connection.getNumberPresentation();
248 if (call.getNumberPresentation() != newNumberPresentation) {
249 call.setNumberPresentation(newNumberPresentation);
250 changed = true;
251 }
252
253 final int newCnapNamePresentation = connection.getCnapNamePresentation();
254 if (call.getCnapNamePresentation() != newCnapNamePresentation) {
255 call.setCnapNamePresentation(newCnapNamePresentation);
256 changed = true;
257 }
258
259 final String oldCnapName = call.getCnapName();
260 if (TextUtils.isEmpty(oldCnapName) || !oldCnapName.equals(connection.getCnapName())) {
261 call.setCnapName(connection.getCnapName());
262 changed = true;
263 }
264
265 return changed;
266 }
267
Santos Cordona3d05142013-07-29 11:25:17 -0700268 private int translateStateFromTelephony(com.android.internal.telephony.Call.State teleState) {
269 int retval = State.IDLE;
270 switch (teleState) {
271 case ACTIVE:
272 retval = State.ACTIVE;
273 break;
274 case INCOMING:
275 retval = State.INCOMING;
276 break;
277 case DIALING:
278 case ALERTING:
279 retval = State.DIALING;
280 break;
281 case WAITING:
282 retval = State.CALL_WAITING;
283 break;
284 case HOLDING:
285 retval = State.ONHOLD;
286 break;
Santos Cordone38b1ff2013-08-07 12:12:16 -0700287 case DISCONNECTED:
288 case DISCONNECTING:
289 retval = State.DISCONNECTED;
Santos Cordona3d05142013-07-29 11:25:17 -0700290 default:
291 }
292
293 return retval;
Santos Cordon995c8162013-07-29 09:22:22 -0700294 }
295
Santos Cordone38b1ff2013-08-07 12:12:16 -0700296 private final ImmutableMap<Connection.DisconnectCause, Call.DisconnectCause> CAUSE_MAP =
297 ImmutableMap.<Connection.DisconnectCause, Call.DisconnectCause>builder()
298 .put(Connection.DisconnectCause.BUSY, Call.DisconnectCause.BUSY)
299 .put(Connection.DisconnectCause.CALL_BARRED, Call.DisconnectCause.CALL_BARRED)
300 .put(Connection.DisconnectCause.CDMA_ACCESS_BLOCKED,
301 Call.DisconnectCause.CDMA_ACCESS_BLOCKED)
302 .put(Connection.DisconnectCause.CDMA_ACCESS_FAILURE,
303 Call.DisconnectCause.CDMA_ACCESS_FAILURE)
304 .put(Connection.DisconnectCause.CDMA_DROP, Call.DisconnectCause.CDMA_DROP)
305 .put(Connection.DisconnectCause.CDMA_INTERCEPT, Call.DisconnectCause.CDMA_INTERCEPT)
306 .put(Connection.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE,
307 Call.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE)
308 .put(Connection.DisconnectCause.CDMA_NOT_EMERGENCY,
309 Call.DisconnectCause.CDMA_NOT_EMERGENCY)
310 .put(Connection.DisconnectCause.CDMA_PREEMPTED, Call.DisconnectCause.CDMA_PREEMPTED)
311 .put(Connection.DisconnectCause.CDMA_REORDER, Call.DisconnectCause.CDMA_REORDER)
312 .put(Connection.DisconnectCause.CDMA_RETRY_ORDER,
313 Call.DisconnectCause.CDMA_RETRY_ORDER)
314 .put(Connection.DisconnectCause.CDMA_SO_REJECT, Call.DisconnectCause.CDMA_SO_REJECT)
315 .put(Connection.DisconnectCause.CONGESTION, Call.DisconnectCause.CONGESTION)
316 .put(Connection.DisconnectCause.CS_RESTRICTED, Call.DisconnectCause.CS_RESTRICTED)
317 .put(Connection.DisconnectCause.CS_RESTRICTED_EMERGENCY,
318 Call.DisconnectCause.CS_RESTRICTED_EMERGENCY)
319 .put(Connection.DisconnectCause.CS_RESTRICTED_NORMAL,
320 Call.DisconnectCause.CS_RESTRICTED_NORMAL)
321 .put(Connection.DisconnectCause.ERROR_UNSPECIFIED,
322 Call.DisconnectCause.ERROR_UNSPECIFIED)
323 .put(Connection.DisconnectCause.FDN_BLOCKED, Call.DisconnectCause.FDN_BLOCKED)
324 .put(Connection.DisconnectCause.ICC_ERROR, Call.DisconnectCause.ICC_ERROR)
325 .put(Connection.DisconnectCause.INCOMING_MISSED,
326 Call.DisconnectCause.INCOMING_MISSED)
327 .put(Connection.DisconnectCause.INCOMING_REJECTED,
328 Call.DisconnectCause.INCOMING_REJECTED)
329 .put(Connection.DisconnectCause.INVALID_CREDENTIALS,
330 Call.DisconnectCause.INVALID_CREDENTIALS)
331 .put(Connection.DisconnectCause.INVALID_NUMBER,
332 Call.DisconnectCause.INVALID_NUMBER)
333 .put(Connection.DisconnectCause.LIMIT_EXCEEDED, Call.DisconnectCause.LIMIT_EXCEEDED)
334 .put(Connection.DisconnectCause.LOCAL, Call.DisconnectCause.LOCAL)
335 .put(Connection.DisconnectCause.LOST_SIGNAL, Call.DisconnectCause.LOST_SIGNAL)
336 .put(Connection.DisconnectCause.MMI, Call.DisconnectCause.MMI)
337 .put(Connection.DisconnectCause.NORMAL, Call.DisconnectCause.NORMAL)
338 .put(Connection.DisconnectCause.NOT_DISCONNECTED,
339 Call.DisconnectCause.NOT_DISCONNECTED)
340 .put(Connection.DisconnectCause.NUMBER_UNREACHABLE,
341 Call.DisconnectCause.NUMBER_UNREACHABLE)
342 .put(Connection.DisconnectCause.OUT_OF_NETWORK, Call.DisconnectCause.OUT_OF_NETWORK)
343 .put(Connection.DisconnectCause.OUT_OF_SERVICE, Call.DisconnectCause.OUT_OF_SERVICE)
344 .put(Connection.DisconnectCause.POWER_OFF, Call.DisconnectCause.POWER_OFF)
345 .put(Connection.DisconnectCause.SERVER_ERROR, Call.DisconnectCause.SERVER_ERROR)
346 .put(Connection.DisconnectCause.SERVER_UNREACHABLE,
347 Call.DisconnectCause.SERVER_UNREACHABLE)
348 .put(Connection.DisconnectCause.TIMED_OUT, Call.DisconnectCause.TIMED_OUT)
349 .put(Connection.DisconnectCause.UNOBTAINABLE_NUMBER,
350 Call.DisconnectCause.UNOBTAINABLE_NUMBER)
351 .build();
352
353 private Call.DisconnectCause translateDisconnectCauseFromTelephony(
354 Connection.DisconnectCause causeSource) {
355
356 if (CAUSE_MAP.containsKey(causeSource)) {
357 return CAUSE_MAP.get(causeSource);
358 }
359
360 return Call.DisconnectCause.UNKNOWN;
361 }
362
Santos Cordon63a84242013-07-23 13:32:52 -0700363 /**
Santos Cordone38b1ff2013-08-07 12:12:16 -0700364 * Gets an existing callId for a connection, or creates one if none exists.
365 * This function does NOT set any of the Connection data onto the Call class.
366 * A separate call to updateCallFromConnection must be made for that purpose.
Santos Cordon63a84242013-07-23 13:32:52 -0700367 */
Santos Cordon995c8162013-07-29 09:22:22 -0700368 private Call getCallFromConnection(Connection conn, boolean createIfMissing) {
369 Call call = null;
Santos Cordon63a84242013-07-23 13:32:52 -0700370
371 // Find the call id or create if missing and requested.
372 if (conn != null) {
Santos Cordon995c8162013-07-29 09:22:22 -0700373 if (mCallMap.containsKey(conn)) {
374 call = mCallMap.get(conn);
Santos Cordon63a84242013-07-23 13:32:52 -0700375 } else if (createIfMissing) {
Santos Cordon995c8162013-07-29 09:22:22 -0700376 int callId;
Santos Cordon63a84242013-07-23 13:32:52 -0700377 int newNextCallId;
378 do {
379 callId = mNextCallId.get();
380
381 // protect against overflow
382 newNextCallId = (callId == Integer.MAX_VALUE ?
383 CALL_ID_START_VALUE : callId + 1);
384
385 // Keep looping if the change was not atomic OR the value is already taken.
386 // The call to containsValue() is linear, however, most devices support a
387 // maximum of 7 connections so it's not expensive.
388 } while (!mNextCallId.compareAndSet(callId, newNextCallId) ||
Santos Cordon995c8162013-07-29 09:22:22 -0700389 mCallMap.containsValue(callId));
Santos Cordon63a84242013-07-23 13:32:52 -0700390
Santos Cordon995c8162013-07-29 09:22:22 -0700391 call = new Call(callId);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700392
Santos Cordon995c8162013-07-29 09:22:22 -0700393 mCallMap.put(conn, call);
Santos Cordon63a84242013-07-23 13:32:52 -0700394 }
395 }
Santos Cordon995c8162013-07-29 09:22:22 -0700396 return call;
Santos Cordon63a84242013-07-23 13:32:52 -0700397 }
398
399 /**
400 * Listener interface for changes to Calls.
401 */
402 public interface Listener {
Santos Cordon995c8162013-07-29 09:22:22 -0700403 void onDisconnect(Call call);
Christine Chenee09a492013-08-06 16:02:29 -0700404 void onIncoming(Call call, ArrayList<String> textReponses);
Santos Cordon998f42b2013-08-02 16:13:12 -0700405 void onUpdate(List<Call> calls, boolean fullUpdate);
Santos Cordon63a84242013-07-23 13:32:52 -0700406 }
Santos Cordon249efd02013-08-05 03:33:56 -0700407
408 /**
409 * Result class for accessing a call by connection.
410 */
411 public static class CallResult {
412 public Call mCall;
413 public Connection mConnection;
414
415 private CallResult(Call call, Connection connection) {
416 mCall = call;
417 mConnection = connection;
418 }
419
420 public Call getCall() {
421 return mCall;
422 }
423
424 public Connection getConnection() {
425 return mConnection;
426 }
427 }
Santos Cordon63a84242013-07-23 13:32:52 -0700428}