blob: dcd11f7e282646d1758342155c988d0da4f728aa [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 Cordon26e7b242013-08-07 21:15:45 -070033import com.android.internal.telephony.TelephonyCapabilities;
Santos Cordon995c8162013-07-29 09:22:22 -070034import com.android.services.telephony.common.Call;
Santos Cordon26e7b242013-08-07 21:15:45 -070035import com.android.services.telephony.common.Call.Capabilities;
Santos Cordona3d05142013-07-29 11:25:17 -070036import com.android.services.telephony.common.Call.State;
Santos Cordon63a84242013-07-23 13:32:52 -070037
38import java.util.ArrayList;
39import java.util.HashMap;
40import java.util.List;
Santos Cordon249efd02013-08-05 03:33:56 -070041import java.util.Map.Entry;
Santos Cordon63a84242013-07-23 13:32:52 -070042import java.util.concurrent.atomic.AtomicInteger;
43
44/**
45 * Creates a Call model from Call state and data received from the telephony
46 * layer. The telephony layer maintains 3 conceptual objects: Phone, Call,
47 * Connection.
48 *
49 * Phone represents the radio and there is an implementation per technology
50 * type such as GSMPhone, SipPhone, CDMAPhone, etc. Generally, we will only ever
51 * deal with one instance of this object for the lifetime of this class.
52 *
53 * There are 3 Call instances that exist for the lifetime of this class which
54 * are created by CallTracker. The three are RingingCall, ForegroundCall, and
55 * BackgroundCall.
56 *
57 * A Connection most closely resembles what the layperson would consider a call.
58 * A Connection is created when a user dials and it is "owned" by one of the
59 * three Call instances. Which of the three Calls owns the Connection changes
60 * as the Connection goes between ACTIVE, HOLD, RINGING, and other states.
61 *
62 * This class models a new Call class from Connection objects received from
63 * the telephony layer. We use Connection references as identifiers for a call;
64 * new reference = new call.
65 *
66 * TODO(klp): Create a new Call class to replace the simple call Id ints
67 * being used currently.
68 *
69 * The new Call models are parcellable for transfer via the CallHandlerService
70 * API.
71 */
72public class CallModeler extends Handler {
73
74 private static final String TAG = CallModeler.class.getSimpleName();
75
76 private static final int CALL_ID_START_VALUE = 1;
Santos Cordon63a84242013-07-23 13:32:52 -070077
Santos Cordon998f42b2013-08-02 16:13:12 -070078 private final CallStateMonitor mCallStateMonitor;
79 private final CallManager mCallManager;
80 private final HashMap<Connection, Call> mCallMap = Maps.newHashMap();
81 private final AtomicInteger mNextCallId = new AtomicInteger(CALL_ID_START_VALUE);
Christine Chendaf7bf62013-08-05 19:12:31 -070082 private final ArrayList<Listener> mListeners = new ArrayList<Listener>();
Christine Chenee09a492013-08-06 16:02:29 -070083 private RejectWithTextMessageManager mRejectWithTextMessageManager;
Santos Cordon63a84242013-07-23 13:32:52 -070084
Christine Chenee09a492013-08-06 16:02:29 -070085 public CallModeler(CallStateMonitor callStateMonitor, CallManager callManager,
86 RejectWithTextMessageManager rejectWithTextMessageManager) {
Santos Cordon63a84242013-07-23 13:32:52 -070087 mCallStateMonitor = callStateMonitor;
Santos Cordona3d05142013-07-29 11:25:17 -070088 mCallManager = callManager;
Christine Chenee09a492013-08-06 16:02:29 -070089 mRejectWithTextMessageManager = rejectWithTextMessageManager;
Santos Cordon63a84242013-07-23 13:32:52 -070090
91 mCallStateMonitor.addListener(this);
92 }
93
Santos Cordone38b1ff2013-08-07 12:12:16 -070094 //@Override
Santos Cordon63a84242013-07-23 13:32:52 -070095 public void handleMessage(Message msg) {
96 switch(msg.what) {
97 case CallStateMonitor.PHONE_NEW_RINGING_CONNECTION:
98 onNewRingingConnection((AsyncResult) msg.obj);
99 break;
100 case CallStateMonitor.PHONE_DISCONNECT:
101 onDisconnect((AsyncResult) msg.obj);
Santos Cordon995c8162013-07-29 09:22:22 -0700102 break;
103 case CallStateMonitor.PHONE_STATE_CHANGED:
104 onPhoneStateChanged((AsyncResult) msg.obj);
105 break;
Santos Cordon63a84242013-07-23 13:32:52 -0700106 default:
107 break;
108 }
109 }
110
Christine Chendaf7bf62013-08-05 19:12:31 -0700111 public void addListener(Listener listener) {
Santos Cordon63a84242013-07-23 13:32:52 -0700112 Preconditions.checkNotNull(listener);
Christine Chendaf7bf62013-08-05 19:12:31 -0700113 Preconditions.checkNotNull(mListeners);
Christine Chen4748abd2013-08-07 15:44:15 -0700114 if (!mListeners.contains(listener)) {
115 mListeners.add(listener);
116 }
Santos Cordon998f42b2013-08-02 16:13:12 -0700117 }
118
119 public List<Call> getFullList() {
120 final List<Call> retval = Lists.newArrayList();
121 doUpdate(true, retval);
122 return retval;
Santos Cordon63a84242013-07-23 13:32:52 -0700123 }
124
Santos Cordon249efd02013-08-05 03:33:56 -0700125 public CallResult getCallWithId(int callId) {
126 // max 8 connections, so this should be fast even through we are traversing the entire map.
127 for (Entry<Connection, Call> entry : mCallMap.entrySet()) {
128 if (entry.getValue().getCallId() == callId) {
129 return new CallResult(entry.getValue(), entry.getKey());
130 }
131 }
132 return null;
133 }
134
Santos Cordon2eaff902013-08-05 04:37:55 -0700135 public boolean hasOutstandingActiveCall() {
136 for (Call call : mCallMap.values()) {
137 int state = call.getState();
138 if (Call.State.INVALID != state &&
139 Call.State.IDLE != state &&
140 Call.State.INCOMING != state) {
141 return true;
142 }
143 }
144
145 return false;
146 }
147
Santos Cordon63a84242013-07-23 13:32:52 -0700148 private void onNewRingingConnection(AsyncResult r) {
149 final Connection conn = (Connection) r.result;
Santos Cordon995c8162013-07-29 09:22:22 -0700150 final Call call = getCallFromConnection(conn, true);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700151
152 updateCallFromConnection(call, conn);
Santos Cordona3d05142013-07-29 11:25:17 -0700153 call.setState(Call.State.INCOMING);
Santos Cordon63a84242013-07-23 13:32:52 -0700154
Christine Chendaf7bf62013-08-05 19:12:31 -0700155 for (int i = 0; i < mListeners.size(); ++i) {
Christine Chenee09a492013-08-06 16:02:29 -0700156 if (call != null) {
157 mListeners.get(i).onIncoming(call,
158 mRejectWithTextMessageManager.loadCannedResponses());
159 }
Santos Cordon63a84242013-07-23 13:32:52 -0700160 }
161 }
162
163 private void onDisconnect(AsyncResult r) {
164 final Connection conn = (Connection) r.result;
Santos Cordon995c8162013-07-29 09:22:22 -0700165 final Call call = getCallFromConnection(conn, false);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700166
167 updateCallFromConnection(call, conn);
168 call.setState(Call.State.DISCONNECTED);
Santos Cordon63a84242013-07-23 13:32:52 -0700169
Santos Cordon995c8162013-07-29 09:22:22 -0700170 if (call != null) {
171 mCallMap.remove(conn);
Santos Cordon63a84242013-07-23 13:32:52 -0700172
Christine Chendaf7bf62013-08-05 19:12:31 -0700173 for (int i = 0; i < mListeners.size(); ++i) {
174 mListeners.get(i).onDisconnect(call);
Santos Cordon63a84242013-07-23 13:32:52 -0700175 }
176 }
177 }
178
Santos Cordona3d05142013-07-29 11:25:17 -0700179 /**
180 * Called when the phone state changes.
Santos Cordona3d05142013-07-29 11:25:17 -0700181 */
Santos Cordon995c8162013-07-29 09:22:22 -0700182 private void onPhoneStateChanged(AsyncResult r) {
Santos Cordon998f42b2013-08-02 16:13:12 -0700183 final List<Call> updatedCalls = Lists.newArrayList();
184 doUpdate(false, updatedCalls);
185
Christine Chendaf7bf62013-08-05 19:12:31 -0700186 for (int i = 0; i < mListeners.size(); ++i) {
187 mListeners.get(i).onUpdate(updatedCalls, false);
Santos Cordon998f42b2013-08-02 16:13:12 -0700188 }
189 }
190
191
192 /**
193 * Go through the Calls from CallManager and return the list of calls that were updated.
194 * Or, the full list if requested.
195 */
196 private void doUpdate(boolean fullUpdate, List<Call> out) {
Santos Cordona3d05142013-07-29 11:25:17 -0700197 final List<com.android.internal.telephony.Call> telephonyCalls = Lists.newArrayList();
198 telephonyCalls.addAll(mCallManager.getRingingCalls());
199 telephonyCalls.addAll(mCallManager.getForegroundCalls());
200 telephonyCalls.addAll(mCallManager.getBackgroundCalls());
201
Santos Cordona3d05142013-07-29 11:25:17 -0700202 // Cycle through all the Connections on all the Calls. Update our Call objects
203 // to reflect any new state and send the updated Call objects to the handler service.
204 for (com.android.internal.telephony.Call telephonyCall : telephonyCalls) {
Santos Cordona3d05142013-07-29 11:25:17 -0700205
206 for (Connection connection : telephonyCall.getConnections()) {
Santos Cordon998f42b2013-08-02 16:13:12 -0700207 // new connections return a Call with INVALID state, which does not translate to
Santos Cordone38b1ff2013-08-07 12:12:16 -0700208 // a state in the internal.telephony.Call object. This ensures that staleness
209 // check below fails and we always add the item to the update list if it is new.
Santos Cordona3d05142013-07-29 11:25:17 -0700210 final Call call = getCallFromConnection(connection, true);
211
Santos Cordone38b1ff2013-08-07 12:12:16 -0700212 boolean changed = updateCallFromConnection(call, connection);
213
214 if (fullUpdate || changed) {
Santos Cordon998f42b2013-08-02 16:13:12 -0700215 out.add(call);
Santos Cordona3d05142013-07-29 11:25:17 -0700216 }
217 }
218 }
Santos Cordona3d05142013-07-29 11:25:17 -0700219 }
220
Santos Cordone38b1ff2013-08-07 12:12:16 -0700221 /**
222 * Updates the Call properties to match the state of the connection object
223 * that it represents.
224 */
225 private boolean updateCallFromConnection(Call call, Connection connection) {
226 boolean changed = false;
227
228 com.android.internal.telephony.Call telephonyCall = connection.getCall();
229 final int newState = translateStateFromTelephony(telephonyCall.getState());
230
231 if (call.getState() != newState) {
232 call.setState(newState);
233 changed = true;
234 }
235
236 final String oldNumber = call.getNumber();
237 if (TextUtils.isEmpty(oldNumber) || !oldNumber.equals(connection.getAddress())) {
238 call.setNumber(connection.getAddress());
239 changed = true;
240 }
241
242 final Call.DisconnectCause newDisconnectCause =
243 translateDisconnectCauseFromTelephony(connection.getDisconnectCause());
244 if (call.getDisconnectCause() != newDisconnectCause) {
245 call.setDisconnectCause(newDisconnectCause);
246 changed = true;
247 }
248
249 final int newNumberPresentation = connection.getNumberPresentation();
250 if (call.getNumberPresentation() != newNumberPresentation) {
251 call.setNumberPresentation(newNumberPresentation);
252 changed = true;
253 }
254
255 final int newCnapNamePresentation = connection.getCnapNamePresentation();
256 if (call.getCnapNamePresentation() != newCnapNamePresentation) {
257 call.setCnapNamePresentation(newCnapNamePresentation);
258 changed = true;
259 }
260
261 final String oldCnapName = call.getCnapName();
262 if (TextUtils.isEmpty(oldCnapName) || !oldCnapName.equals(connection.getCnapName())) {
263 call.setCnapName(connection.getCnapName());
264 changed = true;
265 }
266
Santos Cordon26e7b242013-08-07 21:15:45 -0700267 final int newCapabilities = getCapabilitiesFor(connection);
268 if (call.getCapabilities() != newCapabilities) {
269 call.setCapabilities(newCapabilities);
270 changed = true;
271 }
272
Santos Cordone38b1ff2013-08-07 12:12:16 -0700273 return changed;
274 }
275
Santos Cordon26e7b242013-08-07 21:15:45 -0700276 /**
277 * Returns a mask of capabilities for the connection such as merge, hold, etc.
278 */
279 private int getCapabilitiesFor(Connection connection) {
280 int retval = 0x0;
281
282 final boolean hold = TelephonyCapabilities.supportsAnswerAndHold(connection.getCall().getPhone());
283
284 if (hold) {
285 retval |= Capabilities.HOLD;
286 }
287
288 return retval;
289 }
290
Santos Cordona3d05142013-07-29 11:25:17 -0700291 private int translateStateFromTelephony(com.android.internal.telephony.Call.State teleState) {
292 int retval = State.IDLE;
293 switch (teleState) {
294 case ACTIVE:
295 retval = State.ACTIVE;
296 break;
297 case INCOMING:
298 retval = State.INCOMING;
299 break;
300 case DIALING:
301 case ALERTING:
302 retval = State.DIALING;
303 break;
304 case WAITING:
305 retval = State.CALL_WAITING;
306 break;
307 case HOLDING:
308 retval = State.ONHOLD;
309 break;
Santos Cordone38b1ff2013-08-07 12:12:16 -0700310 case DISCONNECTED:
311 case DISCONNECTING:
312 retval = State.DISCONNECTED;
Santos Cordona3d05142013-07-29 11:25:17 -0700313 default:
314 }
315
316 return retval;
Santos Cordon995c8162013-07-29 09:22:22 -0700317 }
318
Santos Cordone38b1ff2013-08-07 12:12:16 -0700319 private final ImmutableMap<Connection.DisconnectCause, Call.DisconnectCause> CAUSE_MAP =
320 ImmutableMap.<Connection.DisconnectCause, Call.DisconnectCause>builder()
321 .put(Connection.DisconnectCause.BUSY, Call.DisconnectCause.BUSY)
322 .put(Connection.DisconnectCause.CALL_BARRED, Call.DisconnectCause.CALL_BARRED)
323 .put(Connection.DisconnectCause.CDMA_ACCESS_BLOCKED,
324 Call.DisconnectCause.CDMA_ACCESS_BLOCKED)
325 .put(Connection.DisconnectCause.CDMA_ACCESS_FAILURE,
326 Call.DisconnectCause.CDMA_ACCESS_FAILURE)
327 .put(Connection.DisconnectCause.CDMA_DROP, Call.DisconnectCause.CDMA_DROP)
328 .put(Connection.DisconnectCause.CDMA_INTERCEPT, Call.DisconnectCause.CDMA_INTERCEPT)
329 .put(Connection.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE,
330 Call.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE)
331 .put(Connection.DisconnectCause.CDMA_NOT_EMERGENCY,
332 Call.DisconnectCause.CDMA_NOT_EMERGENCY)
333 .put(Connection.DisconnectCause.CDMA_PREEMPTED, Call.DisconnectCause.CDMA_PREEMPTED)
334 .put(Connection.DisconnectCause.CDMA_REORDER, Call.DisconnectCause.CDMA_REORDER)
335 .put(Connection.DisconnectCause.CDMA_RETRY_ORDER,
336 Call.DisconnectCause.CDMA_RETRY_ORDER)
337 .put(Connection.DisconnectCause.CDMA_SO_REJECT, Call.DisconnectCause.CDMA_SO_REJECT)
338 .put(Connection.DisconnectCause.CONGESTION, Call.DisconnectCause.CONGESTION)
339 .put(Connection.DisconnectCause.CS_RESTRICTED, Call.DisconnectCause.CS_RESTRICTED)
340 .put(Connection.DisconnectCause.CS_RESTRICTED_EMERGENCY,
341 Call.DisconnectCause.CS_RESTRICTED_EMERGENCY)
342 .put(Connection.DisconnectCause.CS_RESTRICTED_NORMAL,
343 Call.DisconnectCause.CS_RESTRICTED_NORMAL)
344 .put(Connection.DisconnectCause.ERROR_UNSPECIFIED,
345 Call.DisconnectCause.ERROR_UNSPECIFIED)
346 .put(Connection.DisconnectCause.FDN_BLOCKED, Call.DisconnectCause.FDN_BLOCKED)
347 .put(Connection.DisconnectCause.ICC_ERROR, Call.DisconnectCause.ICC_ERROR)
348 .put(Connection.DisconnectCause.INCOMING_MISSED,
349 Call.DisconnectCause.INCOMING_MISSED)
350 .put(Connection.DisconnectCause.INCOMING_REJECTED,
351 Call.DisconnectCause.INCOMING_REJECTED)
352 .put(Connection.DisconnectCause.INVALID_CREDENTIALS,
353 Call.DisconnectCause.INVALID_CREDENTIALS)
354 .put(Connection.DisconnectCause.INVALID_NUMBER,
355 Call.DisconnectCause.INVALID_NUMBER)
356 .put(Connection.DisconnectCause.LIMIT_EXCEEDED, Call.DisconnectCause.LIMIT_EXCEEDED)
357 .put(Connection.DisconnectCause.LOCAL, Call.DisconnectCause.LOCAL)
358 .put(Connection.DisconnectCause.LOST_SIGNAL, Call.DisconnectCause.LOST_SIGNAL)
359 .put(Connection.DisconnectCause.MMI, Call.DisconnectCause.MMI)
360 .put(Connection.DisconnectCause.NORMAL, Call.DisconnectCause.NORMAL)
361 .put(Connection.DisconnectCause.NOT_DISCONNECTED,
362 Call.DisconnectCause.NOT_DISCONNECTED)
363 .put(Connection.DisconnectCause.NUMBER_UNREACHABLE,
364 Call.DisconnectCause.NUMBER_UNREACHABLE)
365 .put(Connection.DisconnectCause.OUT_OF_NETWORK, Call.DisconnectCause.OUT_OF_NETWORK)
366 .put(Connection.DisconnectCause.OUT_OF_SERVICE, Call.DisconnectCause.OUT_OF_SERVICE)
367 .put(Connection.DisconnectCause.POWER_OFF, Call.DisconnectCause.POWER_OFF)
368 .put(Connection.DisconnectCause.SERVER_ERROR, Call.DisconnectCause.SERVER_ERROR)
369 .put(Connection.DisconnectCause.SERVER_UNREACHABLE,
370 Call.DisconnectCause.SERVER_UNREACHABLE)
371 .put(Connection.DisconnectCause.TIMED_OUT, Call.DisconnectCause.TIMED_OUT)
372 .put(Connection.DisconnectCause.UNOBTAINABLE_NUMBER,
373 Call.DisconnectCause.UNOBTAINABLE_NUMBER)
374 .build();
375
376 private Call.DisconnectCause translateDisconnectCauseFromTelephony(
377 Connection.DisconnectCause causeSource) {
378
379 if (CAUSE_MAP.containsKey(causeSource)) {
380 return CAUSE_MAP.get(causeSource);
381 }
382
383 return Call.DisconnectCause.UNKNOWN;
384 }
385
Santos Cordon63a84242013-07-23 13:32:52 -0700386 /**
Santos Cordone38b1ff2013-08-07 12:12:16 -0700387 * Gets an existing callId for a connection, or creates one if none exists.
388 * This function does NOT set any of the Connection data onto the Call class.
389 * A separate call to updateCallFromConnection must be made for that purpose.
Santos Cordon63a84242013-07-23 13:32:52 -0700390 */
Santos Cordon995c8162013-07-29 09:22:22 -0700391 private Call getCallFromConnection(Connection conn, boolean createIfMissing) {
392 Call call = null;
Santos Cordon63a84242013-07-23 13:32:52 -0700393
394 // Find the call id or create if missing and requested.
395 if (conn != null) {
Santos Cordon995c8162013-07-29 09:22:22 -0700396 if (mCallMap.containsKey(conn)) {
397 call = mCallMap.get(conn);
Santos Cordon63a84242013-07-23 13:32:52 -0700398 } else if (createIfMissing) {
Santos Cordon995c8162013-07-29 09:22:22 -0700399 int callId;
Santos Cordon63a84242013-07-23 13:32:52 -0700400 int newNextCallId;
401 do {
402 callId = mNextCallId.get();
403
404 // protect against overflow
405 newNextCallId = (callId == Integer.MAX_VALUE ?
406 CALL_ID_START_VALUE : callId + 1);
407
408 // Keep looping if the change was not atomic OR the value is already taken.
409 // The call to containsValue() is linear, however, most devices support a
410 // maximum of 7 connections so it's not expensive.
411 } while (!mNextCallId.compareAndSet(callId, newNextCallId) ||
Santos Cordon995c8162013-07-29 09:22:22 -0700412 mCallMap.containsValue(callId));
Santos Cordon63a84242013-07-23 13:32:52 -0700413
Santos Cordon995c8162013-07-29 09:22:22 -0700414 call = new Call(callId);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700415
Santos Cordon995c8162013-07-29 09:22:22 -0700416 mCallMap.put(conn, call);
Santos Cordon63a84242013-07-23 13:32:52 -0700417 }
418 }
Santos Cordon995c8162013-07-29 09:22:22 -0700419 return call;
Santos Cordon63a84242013-07-23 13:32:52 -0700420 }
421
422 /**
423 * Listener interface for changes to Calls.
424 */
425 public interface Listener {
Santos Cordon995c8162013-07-29 09:22:22 -0700426 void onDisconnect(Call call);
Christine Chenee09a492013-08-06 16:02:29 -0700427 void onIncoming(Call call, ArrayList<String> textReponses);
Santos Cordon998f42b2013-08-02 16:13:12 -0700428 void onUpdate(List<Call> calls, boolean fullUpdate);
Santos Cordon63a84242013-07-23 13:32:52 -0700429 }
Santos Cordon249efd02013-08-05 03:33:56 -0700430
431 /**
432 * Result class for accessing a call by connection.
433 */
434 public static class CallResult {
435 public Call mCall;
436 public Connection mConnection;
437
438 private CallResult(Call call, Connection connection) {
439 mCall = call;
440 mConnection = connection;
441 }
442
443 public Call getCall() {
444 return mCall;
445 }
446
447 public Connection getConnection() {
448 return mConnection;
449 }
450 }
Santos Cordon63a84242013-07-23 13:32:52 -0700451}