blob: 89dcb21e92abcb920c3c8146e743adaf37c0bf41 [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;
Santos Cordon4ad64cd2013-08-15 00:36:14 -070021import com.google.android.collect.Sets;
Santos Cordon63a84242013-07-23 13:32:52 -070022import com.google.common.base.Preconditions;
Santos Cordone38b1ff2013-08-07 12:12:16 -070023import com.google.common.collect.ImmutableMap;
Santos Cordon4ad64cd2013-08-15 00:36:14 -070024import com.google.common.collect.ImmutableSortedSet;
Santos Cordon63a84242013-07-23 13:32:52 -070025
26import android.os.AsyncResult;
27import android.os.Handler;
28import android.os.Message;
Santos Cordon4ad64cd2013-08-15 00:36:14 -070029import android.os.SystemProperties;
Santos Cordone38b1ff2013-08-07 12:12:16 -070030import android.text.TextUtils;
31import android.util.Log;
Santos Cordon63a84242013-07-23 13:32:52 -070032
Santos Cordona3d05142013-07-29 11:25:17 -070033import com.android.internal.telephony.CallManager;
Santos Cordon63a84242013-07-23 13:32:52 -070034import com.android.internal.telephony.Connection;
Santos Cordoneead6ec2013-08-07 22:16:33 -070035import com.android.internal.telephony.Phone;
Santos Cordona3d05142013-07-29 11:25:17 -070036import com.android.internal.telephony.PhoneConstants;
Santos Cordon26e7b242013-08-07 21:15:45 -070037import com.android.internal.telephony.TelephonyCapabilities;
Santos Cordon995c8162013-07-29 09:22:22 -070038import com.android.services.telephony.common.Call;
Santos Cordon26e7b242013-08-07 21:15:45 -070039import com.android.services.telephony.common.Call.Capabilities;
Santos Cordona3d05142013-07-29 11:25:17 -070040import com.android.services.telephony.common.Call.State;
Santos Cordon63a84242013-07-23 13:32:52 -070041
42import java.util.ArrayList;
43import java.util.HashMap;
44import java.util.List;
Santos Cordon249efd02013-08-05 03:33:56 -070045import java.util.Map.Entry;
Santos Cordon4ad64cd2013-08-15 00:36:14 -070046import java.util.SortedSet;
Santos Cordon63a84242013-07-23 13:32:52 -070047import java.util.concurrent.atomic.AtomicInteger;
48
49/**
50 * Creates a Call model from Call state and data received from the telephony
51 * layer. The telephony layer maintains 3 conceptual objects: Phone, Call,
52 * Connection.
53 *
54 * Phone represents the radio and there is an implementation per technology
55 * type such as GSMPhone, SipPhone, CDMAPhone, etc. Generally, we will only ever
56 * deal with one instance of this object for the lifetime of this class.
57 *
58 * There are 3 Call instances that exist for the lifetime of this class which
59 * are created by CallTracker. The three are RingingCall, ForegroundCall, and
60 * BackgroundCall.
61 *
62 * A Connection most closely resembles what the layperson would consider a call.
63 * A Connection is created when a user dials and it is "owned" by one of the
64 * three Call instances. Which of the three Calls owns the Connection changes
65 * as the Connection goes between ACTIVE, HOLD, RINGING, and other states.
66 *
67 * This class models a new Call class from Connection objects received from
68 * the telephony layer. We use Connection references as identifiers for a call;
69 * new reference = new call.
70 *
71 * TODO(klp): Create a new Call class to replace the simple call Id ints
72 * being used currently.
73 *
74 * The new Call models are parcellable for transfer via the CallHandlerService
75 * API.
76 */
77public class CallModeler extends Handler {
78
79 private static final String TAG = CallModeler.class.getSimpleName();
Santos Cordon4ad64cd2013-08-15 00:36:14 -070080 private static final boolean DBG =
81 (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
Santos Cordon63a84242013-07-23 13:32:52 -070082
83 private static final int CALL_ID_START_VALUE = 1;
Santos Cordon63a84242013-07-23 13:32:52 -070084
Santos Cordon998f42b2013-08-02 16:13:12 -070085 private final CallStateMonitor mCallStateMonitor;
86 private final CallManager mCallManager;
87 private final HashMap<Connection, Call> mCallMap = Maps.newHashMap();
Santos Cordon4ad64cd2013-08-15 00:36:14 -070088 private final HashMap<Connection, Call> mConfCallMap = Maps.newHashMap();
Santos Cordon998f42b2013-08-02 16:13:12 -070089 private final AtomicInteger mNextCallId = new AtomicInteger(CALL_ID_START_VALUE);
Christine Chendaf7bf62013-08-05 19:12:31 -070090 private final ArrayList<Listener> mListeners = new ArrayList<Listener>();
Christine Chenee09a492013-08-06 16:02:29 -070091 private RejectWithTextMessageManager mRejectWithTextMessageManager;
Santos Cordon63a84242013-07-23 13:32:52 -070092
Christine Chenee09a492013-08-06 16:02:29 -070093 public CallModeler(CallStateMonitor callStateMonitor, CallManager callManager,
94 RejectWithTextMessageManager rejectWithTextMessageManager) {
Santos Cordon63a84242013-07-23 13:32:52 -070095 mCallStateMonitor = callStateMonitor;
Santos Cordona3d05142013-07-29 11:25:17 -070096 mCallManager = callManager;
Christine Chenee09a492013-08-06 16:02:29 -070097 mRejectWithTextMessageManager = rejectWithTextMessageManager;
Santos Cordon63a84242013-07-23 13:32:52 -070098
99 mCallStateMonitor.addListener(this);
100 }
101
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700102 @Override
Santos Cordon63a84242013-07-23 13:32:52 -0700103 public void handleMessage(Message msg) {
104 switch(msg.what) {
105 case CallStateMonitor.PHONE_NEW_RINGING_CONNECTION:
106 onNewRingingConnection((AsyncResult) msg.obj);
107 break;
108 case CallStateMonitor.PHONE_DISCONNECT:
109 onDisconnect((AsyncResult) msg.obj);
Santos Cordon995c8162013-07-29 09:22:22 -0700110 break;
111 case CallStateMonitor.PHONE_STATE_CHANGED:
112 onPhoneStateChanged((AsyncResult) msg.obj);
113 break;
Santos Cordon63a84242013-07-23 13:32:52 -0700114 default:
115 break;
116 }
117 }
118
Christine Chendaf7bf62013-08-05 19:12:31 -0700119 public void addListener(Listener listener) {
Santos Cordon63a84242013-07-23 13:32:52 -0700120 Preconditions.checkNotNull(listener);
Christine Chendaf7bf62013-08-05 19:12:31 -0700121 Preconditions.checkNotNull(mListeners);
Christine Chen4748abd2013-08-07 15:44:15 -0700122 if (!mListeners.contains(listener)) {
123 mListeners.add(listener);
124 }
Santos Cordon998f42b2013-08-02 16:13:12 -0700125 }
126
127 public List<Call> getFullList() {
128 final List<Call> retval = Lists.newArrayList();
129 doUpdate(true, retval);
130 return retval;
Santos Cordon63a84242013-07-23 13:32:52 -0700131 }
132
Santos Cordon249efd02013-08-05 03:33:56 -0700133 public CallResult getCallWithId(int callId) {
134 // max 8 connections, so this should be fast even through we are traversing the entire map.
135 for (Entry<Connection, Call> entry : mCallMap.entrySet()) {
136 if (entry.getValue().getCallId() == callId) {
137 return new CallResult(entry.getValue(), entry.getKey());
138 }
139 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700140
141 for (Entry<Connection, Call> entry : mConfCallMap.entrySet()) {
142 if (entry.getValue().getCallId() == callId) {
143 if (entry.getValue().getChildCallIds().size() == 0) {
144 return null;
145 }
146 final CallResult child = getCallWithId(entry.getValue().getChildCallIds().first());
147 return new CallResult(entry.getValue(), child.getActionableCall(),
148 child.getConnection());
149 }
150 }
Santos Cordon249efd02013-08-05 03:33:56 -0700151 return null;
152 }
153
Santos Cordonaf763a12013-08-19 20:04:58 -0700154 public boolean hasLiveCall() {
155 return hasLiveCallInternal(mCallMap) ||
156 hasLiveCallInternal(mConfCallMap);
157 }
158
159 private boolean hasLiveCallInternal(HashMap<Connection, Call> map) {
160 for (Call call : map.values()) {
161 final int state = call.getState();
162 if (state == Call.State.ACTIVE ||
163 state == Call.State.CALL_WAITING ||
164 state == Call.State.CONFERENCED ||
165 state == Call.State.DIALING ||
166 state == Call.State.INCOMING ||
167 state == Call.State.ONHOLD) {
168 return true;
169 }
170 }
171 return false;
172 }
173
Santos Cordon2eaff902013-08-05 04:37:55 -0700174 public boolean hasOutstandingActiveCall() {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700175 return hasOutstandingActiveCallInternal(mCallMap) ||
176 hasOutstandingActiveCallInternal(mConfCallMap);
177 }
178
179 private static boolean hasOutstandingActiveCallInternal(HashMap<Connection, Call> map) {
180 for (Call call : map.values()) {
181 final int state = call.getState();
182 if (Call.State.ACTIVE == state) {
Santos Cordon2eaff902013-08-05 04:37:55 -0700183 return true;
184 }
185 }
186
187 return false;
188 }
189
Santos Cordon63a84242013-07-23 13:32:52 -0700190 private void onNewRingingConnection(AsyncResult r) {
191 final Connection conn = (Connection) r.result;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700192 final Call call = getCallFromMap(mCallMap, conn, true);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700193
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700194 updateCallFromConnection(call, conn, false);
Santos Cordona3d05142013-07-29 11:25:17 -0700195 call.setState(Call.State.INCOMING);
Santos Cordon63a84242013-07-23 13:32:52 -0700196
Christine Chendaf7bf62013-08-05 19:12:31 -0700197 for (int i = 0; i < mListeners.size(); ++i) {
Christine Chenee09a492013-08-06 16:02:29 -0700198 if (call != null) {
199 mListeners.get(i).onIncoming(call,
200 mRejectWithTextMessageManager.loadCannedResponses());
201 }
Santos Cordon63a84242013-07-23 13:32:52 -0700202 }
203 }
204
205 private void onDisconnect(AsyncResult r) {
206 final Connection conn = (Connection) r.result;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700207 final Call call = getCallFromMap(mCallMap, conn, false);
Santos Cordon63a84242013-07-23 13:32:52 -0700208
Santos Cordon995c8162013-07-29 09:22:22 -0700209 if (call != null) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700210 final boolean wasConferenced = call.getState() == State.CONFERENCED;
211
212 updateCallFromConnection(call, conn, false);
213 call.setState(Call.State.DISCONNECTED);
Santos Cordon63a84242013-07-23 13:32:52 -0700214
Christine Chendaf7bf62013-08-05 19:12:31 -0700215 for (int i = 0; i < mListeners.size(); ++i) {
216 mListeners.get(i).onDisconnect(call);
Santos Cordon63a84242013-07-23 13:32:52 -0700217 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700218
219 // If it was a conferenced call, we need to run the entire update
220 // to make the proper changes to parent conference calls.
221 if (wasConferenced) {
222 onPhoneStateChanged(null);
223 }
224
225 mCallMap.remove(conn);
Santos Cordon63a84242013-07-23 13:32:52 -0700226 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700227
228 // TODO(klp): Do a final check to see if there are any active calls.
229 // If there are not, totally cancel all calls
Santos Cordon63a84242013-07-23 13:32:52 -0700230 }
231
Santos Cordona3d05142013-07-29 11:25:17 -0700232 /**
233 * Called when the phone state changes.
Santos Cordona3d05142013-07-29 11:25:17 -0700234 */
Santos Cordon995c8162013-07-29 09:22:22 -0700235 private void onPhoneStateChanged(AsyncResult r) {
Santos Cordon998f42b2013-08-02 16:13:12 -0700236 final List<Call> updatedCalls = Lists.newArrayList();
237 doUpdate(false, updatedCalls);
238
Christine Chendaf7bf62013-08-05 19:12:31 -0700239 for (int i = 0; i < mListeners.size(); ++i) {
240 mListeners.get(i).onUpdate(updatedCalls, false);
Santos Cordon998f42b2013-08-02 16:13:12 -0700241 }
242 }
243
244
245 /**
246 * Go through the Calls from CallManager and return the list of calls that were updated.
247 * Or, the full list if requested.
248 */
249 private void doUpdate(boolean fullUpdate, List<Call> out) {
Santos Cordona3d05142013-07-29 11:25:17 -0700250 final List<com.android.internal.telephony.Call> telephonyCalls = Lists.newArrayList();
251 telephonyCalls.addAll(mCallManager.getRingingCalls());
252 telephonyCalls.addAll(mCallManager.getForegroundCalls());
253 telephonyCalls.addAll(mCallManager.getBackgroundCalls());
254
Santos Cordona3d05142013-07-29 11:25:17 -0700255 // Cycle through all the Connections on all the Calls. Update our Call objects
256 // to reflect any new state and send the updated Call objects to the handler service.
257 for (com.android.internal.telephony.Call telephonyCall : telephonyCalls) {
Santos Cordona3d05142013-07-29 11:25:17 -0700258
259 for (Connection connection : telephonyCall.getConnections()) {
Santos Cordon998f42b2013-08-02 16:13:12 -0700260 // new connections return a Call with INVALID state, which does not translate to
Santos Cordone38b1ff2013-08-07 12:12:16 -0700261 // a state in the internal.telephony.Call object. This ensures that staleness
262 // check below fails and we always add the item to the update list if it is new.
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700263 final Call call = getCallFromMap(mCallMap, connection, true);
Santos Cordona3d05142013-07-29 11:25:17 -0700264
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700265 boolean changed = updateCallFromConnection(call, connection, false);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700266 if (fullUpdate || changed) {
Santos Cordon998f42b2013-08-02 16:13:12 -0700267 out.add(call);
Santos Cordona3d05142013-07-29 11:25:17 -0700268 }
269 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700270
271 // We do a second loop to address conference call scenarios. We do this as a separate
272 // loop to ensure all child calls are up to date before we start updating the parent
273 // conference calls.
274 for (Connection connection : telephonyCall.getConnections()) {
275 updateForConferenceCalls(connection, out);
276 }
277
Santos Cordona3d05142013-07-29 11:25:17 -0700278 }
Santos Cordona3d05142013-07-29 11:25:17 -0700279 }
280
Santos Cordone38b1ff2013-08-07 12:12:16 -0700281 /**
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700282 * Checks to see if the connection is the first connection in a conference call.
283 * If it is a conference call, we will create a new Conference Call object or
284 * update the existing conference call object for that connection.
285 * If it is not a conference call but a previous associated conference call still exists,
286 * we mark it as idle and remove it from the map.
287 * In both cases above, we add the Calls to be updated to the UI.
288 * @param connection The connection object to check.
289 * @param updatedCalls List of 'updated' calls that will be sent to the UI.
290 */
291 private boolean updateForConferenceCalls(Connection connection, List<Call> updatedCalls) {
292 // We consider this connection a conference connection if the call it
293 // belongs to is a multiparty call AND it is the first connection.
294 final boolean isConferenceCallConnection = isPartOfLiveConferenceCall(connection) &&
295 connection.getCall().getEarliestConnection() == connection;
296
297 boolean changed = false;
298
299 // If this connection is the main connection for the conference call, then create or update
300 // a Call object for that conference call.
301 if (isConferenceCallConnection) {
302 final Call confCall = getCallFromMap(mConfCallMap, connection, true);
303 changed = updateCallFromConnection(confCall, connection, true);
304
305 if (changed) {
306 updatedCalls.add(confCall);
307 }
308
309 if (DBG) Log.d(TAG, "Updating a conference call: " + confCall);
310
311 // It is possible that through a conference call split, there may be lingering conference
312 // calls where this connection was the main connection. We clean those up here.
313 } else {
314 final Call oldConfCall = getCallFromMap(mConfCallMap, connection, false);
315
316 // We found a conference call for this connection, which is no longer a conference call.
317 // Kill it!
318 if (oldConfCall != null) {
319 if (DBG) Log.d(TAG, "Cleaning up an old conference call: " + oldConfCall);
320 mConfCallMap.remove(connection);
321 oldConfCall.setState(State.IDLE);
322 changed = true;
323
324 // add to the list of calls to update
325 updatedCalls.add(oldConfCall);
326 }
327 }
328
329 return changed;
330 }
331
332 /**
Santos Cordone38b1ff2013-08-07 12:12:16 -0700333 * Updates the Call properties to match the state of the connection object
334 * that it represents.
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700335 * @param call The call object to update.
336 * @param connection The connection object from which to update call.
337 * @param isForConference There are slight differences in how we populate data for conference
338 * calls. This boolean tells us which method to use.
Santos Cordone38b1ff2013-08-07 12:12:16 -0700339 */
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700340 private boolean updateCallFromConnection(Call call, Connection connection,
341 boolean isForConference) {
Santos Cordone38b1ff2013-08-07 12:12:16 -0700342 boolean changed = false;
343
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700344 final int newState = translateStateFromTelephony(connection, isForConference);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700345
346 if (call.getState() != newState) {
347 call.setState(newState);
348 changed = true;
349 }
350
Santos Cordone38b1ff2013-08-07 12:12:16 -0700351 final Call.DisconnectCause newDisconnectCause =
352 translateDisconnectCauseFromTelephony(connection.getDisconnectCause());
353 if (call.getDisconnectCause() != newDisconnectCause) {
354 call.setDisconnectCause(newDisconnectCause);
355 changed = true;
356 }
357
Santos Cordonbbe8ecf2013-08-13 15:26:18 -0700358 final long oldConnectTime = call.getConnectTime();
359 if (oldConnectTime != connection.getConnectTime()) {
360 call.setConnectTime(connection.getConnectTime());
361 changed = true;
362 }
363
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700364 if (!isForConference) {
365 final String oldNumber = call.getNumber();
366 if (TextUtils.isEmpty(oldNumber) || !oldNumber.equals(connection.getAddress())) {
367 call.setNumber(connection.getAddress());
368 changed = true;
369 }
370
371 final int newNumberPresentation = connection.getNumberPresentation();
372 if (call.getNumberPresentation() != newNumberPresentation) {
373 call.setNumberPresentation(newNumberPresentation);
374 changed = true;
375 }
376
377 final int newCnapNamePresentation = connection.getCnapNamePresentation();
378 if (call.getCnapNamePresentation() != newCnapNamePresentation) {
379 call.setCnapNamePresentation(newCnapNamePresentation);
380 changed = true;
381 }
382
383 final String oldCnapName = call.getCnapName();
384 if (TextUtils.isEmpty(oldCnapName) || !oldCnapName.equals(connection.getCnapName())) {
385 call.setCnapName(connection.getCnapName());
386 changed = true;
387 }
388 } else {
389
390 // update the list of children by:
391 // 1) Saving the old set
392 // 2) Removing all children
393 // 3) Adding the correct children into the Call
394 // 4) Comparing the new children set with the old children set
395 ImmutableSortedSet<Integer> oldSet = call.getChildCallIds();
396 call.removeAllChildren();
397
398 if (connection.getCall() != null) {
399 for (Connection childConn : connection.getCall().getConnections()) {
400 final Call childCall = getCallFromMap(mCallMap, childConn, false);
401 if (childCall != null && childConn.isAlive()) {
402 call.addChildId(childCall.getCallId());
403 }
404 }
405 }
406 changed |= oldSet.equals(call.getChildCallIds());
407 }
408
Santos Cordoneead6ec2013-08-07 22:16:33 -0700409 /**
410 * !!! Uses values from connection and call collected above so this part must be last !!!
411 */
412 final int newCapabilities = getCapabilitiesFor(connection, call);
Santos Cordon26e7b242013-08-07 21:15:45 -0700413 if (call.getCapabilities() != newCapabilities) {
414 call.setCapabilities(newCapabilities);
415 changed = true;
416 }
417
Santos Cordone38b1ff2013-08-07 12:12:16 -0700418 return changed;
419 }
420
Santos Cordon26e7b242013-08-07 21:15:45 -0700421 /**
422 * Returns a mask of capabilities for the connection such as merge, hold, etc.
423 */
Santos Cordoneead6ec2013-08-07 22:16:33 -0700424 private int getCapabilitiesFor(Connection connection, Call call) {
425 final boolean callIsActive = (call.getState() == Call.State.ACTIVE);
426 final Phone phone = connection.getCall().getPhone();
427
428 final boolean canHold = TelephonyCapabilities.supportsAnswerAndHold(phone);
429 boolean canAddCall = false;
430 boolean canMergeCall = false;
431 boolean canSwapCall = false;
432
433 // only applies to active calls
434 if (callIsActive) {
435 canAddCall = PhoneUtils.okToAddCall(mCallManager);
436 canMergeCall = PhoneUtils.okToMergeCalls(mCallManager);
437 canSwapCall = PhoneUtils.okToSwapCalls(mCallManager);
438 }
439
440 // special rules section!
441 // CDMA always has Add
442 if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
443 canAddCall = true;
444 } else {
445 // if neither merge nor add is on...then allow add
446 canAddCall |= !(canAddCall || canMergeCall);
447 }
448
Santos Cordon26e7b242013-08-07 21:15:45 -0700449 int retval = 0x0;
Santos Cordoneead6ec2013-08-07 22:16:33 -0700450 if (canHold) {
Santos Cordon26e7b242013-08-07 21:15:45 -0700451 retval |= Capabilities.HOLD;
452 }
Santos Cordoneead6ec2013-08-07 22:16:33 -0700453 if (canAddCall) {
454 retval |= Capabilities.ADD_CALL;
455 }
456 if (canMergeCall) {
457 retval |= Capabilities.MERGE_CALLS;
458 }
459 if (canSwapCall) {
460 retval |= Capabilities.SWAP_CALLS;
461 }
Santos Cordon26e7b242013-08-07 21:15:45 -0700462
463 return retval;
464 }
465
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700466 /**
467 * Returns true if the Connection is part of a multiparty call.
468 * We do this by checking the isMultiparty() method of the telephony.Call object and also
469 * checking to see if more than one of it's children is alive.
470 */
471 private boolean isPartOfLiveConferenceCall(Connection connection) {
472 if (connection.getCall() != null && connection.getCall().isMultiparty()) {
473 int count = 0;
474 for (Connection currConn : connection.getCall().getConnections()) {
475 if (currConn.isAlive()) {
476 count++;
477 if (count >= 2) {
478 return true;
479 }
480 }
481 }
482 }
483 return false;
484 }
485
486 private int translateStateFromTelephony(Connection connection, boolean isForConference) {
487
Santos Cordona3d05142013-07-29 11:25:17 -0700488 int retval = State.IDLE;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700489 switch (connection.getState()) {
Santos Cordona3d05142013-07-29 11:25:17 -0700490 case ACTIVE:
491 retval = State.ACTIVE;
492 break;
493 case INCOMING:
494 retval = State.INCOMING;
495 break;
496 case DIALING:
497 case ALERTING:
498 retval = State.DIALING;
499 break;
500 case WAITING:
501 retval = State.CALL_WAITING;
502 break;
503 case HOLDING:
504 retval = State.ONHOLD;
505 break;
Santos Cordone38b1ff2013-08-07 12:12:16 -0700506 case DISCONNECTED:
507 case DISCONNECTING:
508 retval = State.DISCONNECTED;
Santos Cordona3d05142013-07-29 11:25:17 -0700509 default:
510 }
511
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700512 // If we are dealing with a potential child call (not the parent conference call),
513 // the check to see if we have to set the state to CONFERENCED.
514 if (!isForConference) {
515
516 // if the connection is part of a multiparty call, and it is live,
517 // annotate it with CONFERENCED state instead.
518 if (isPartOfLiveConferenceCall(connection) && connection.isAlive()) {
519 return State.CONFERENCED;
520 }
521 }
522
Santos Cordona3d05142013-07-29 11:25:17 -0700523 return retval;
Santos Cordon995c8162013-07-29 09:22:22 -0700524 }
525
Santos Cordone38b1ff2013-08-07 12:12:16 -0700526 private final ImmutableMap<Connection.DisconnectCause, Call.DisconnectCause> CAUSE_MAP =
527 ImmutableMap.<Connection.DisconnectCause, Call.DisconnectCause>builder()
528 .put(Connection.DisconnectCause.BUSY, Call.DisconnectCause.BUSY)
529 .put(Connection.DisconnectCause.CALL_BARRED, Call.DisconnectCause.CALL_BARRED)
530 .put(Connection.DisconnectCause.CDMA_ACCESS_BLOCKED,
531 Call.DisconnectCause.CDMA_ACCESS_BLOCKED)
532 .put(Connection.DisconnectCause.CDMA_ACCESS_FAILURE,
533 Call.DisconnectCause.CDMA_ACCESS_FAILURE)
534 .put(Connection.DisconnectCause.CDMA_DROP, Call.DisconnectCause.CDMA_DROP)
535 .put(Connection.DisconnectCause.CDMA_INTERCEPT, Call.DisconnectCause.CDMA_INTERCEPT)
536 .put(Connection.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE,
537 Call.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE)
538 .put(Connection.DisconnectCause.CDMA_NOT_EMERGENCY,
539 Call.DisconnectCause.CDMA_NOT_EMERGENCY)
540 .put(Connection.DisconnectCause.CDMA_PREEMPTED, Call.DisconnectCause.CDMA_PREEMPTED)
541 .put(Connection.DisconnectCause.CDMA_REORDER, Call.DisconnectCause.CDMA_REORDER)
542 .put(Connection.DisconnectCause.CDMA_RETRY_ORDER,
543 Call.DisconnectCause.CDMA_RETRY_ORDER)
544 .put(Connection.DisconnectCause.CDMA_SO_REJECT, Call.DisconnectCause.CDMA_SO_REJECT)
545 .put(Connection.DisconnectCause.CONGESTION, Call.DisconnectCause.CONGESTION)
546 .put(Connection.DisconnectCause.CS_RESTRICTED, Call.DisconnectCause.CS_RESTRICTED)
547 .put(Connection.DisconnectCause.CS_RESTRICTED_EMERGENCY,
548 Call.DisconnectCause.CS_RESTRICTED_EMERGENCY)
549 .put(Connection.DisconnectCause.CS_RESTRICTED_NORMAL,
550 Call.DisconnectCause.CS_RESTRICTED_NORMAL)
551 .put(Connection.DisconnectCause.ERROR_UNSPECIFIED,
552 Call.DisconnectCause.ERROR_UNSPECIFIED)
553 .put(Connection.DisconnectCause.FDN_BLOCKED, Call.DisconnectCause.FDN_BLOCKED)
554 .put(Connection.DisconnectCause.ICC_ERROR, Call.DisconnectCause.ICC_ERROR)
555 .put(Connection.DisconnectCause.INCOMING_MISSED,
556 Call.DisconnectCause.INCOMING_MISSED)
557 .put(Connection.DisconnectCause.INCOMING_REJECTED,
558 Call.DisconnectCause.INCOMING_REJECTED)
559 .put(Connection.DisconnectCause.INVALID_CREDENTIALS,
560 Call.DisconnectCause.INVALID_CREDENTIALS)
561 .put(Connection.DisconnectCause.INVALID_NUMBER,
562 Call.DisconnectCause.INVALID_NUMBER)
563 .put(Connection.DisconnectCause.LIMIT_EXCEEDED, Call.DisconnectCause.LIMIT_EXCEEDED)
564 .put(Connection.DisconnectCause.LOCAL, Call.DisconnectCause.LOCAL)
565 .put(Connection.DisconnectCause.LOST_SIGNAL, Call.DisconnectCause.LOST_SIGNAL)
566 .put(Connection.DisconnectCause.MMI, Call.DisconnectCause.MMI)
567 .put(Connection.DisconnectCause.NORMAL, Call.DisconnectCause.NORMAL)
568 .put(Connection.DisconnectCause.NOT_DISCONNECTED,
569 Call.DisconnectCause.NOT_DISCONNECTED)
570 .put(Connection.DisconnectCause.NUMBER_UNREACHABLE,
571 Call.DisconnectCause.NUMBER_UNREACHABLE)
572 .put(Connection.DisconnectCause.OUT_OF_NETWORK, Call.DisconnectCause.OUT_OF_NETWORK)
573 .put(Connection.DisconnectCause.OUT_OF_SERVICE, Call.DisconnectCause.OUT_OF_SERVICE)
574 .put(Connection.DisconnectCause.POWER_OFF, Call.DisconnectCause.POWER_OFF)
575 .put(Connection.DisconnectCause.SERVER_ERROR, Call.DisconnectCause.SERVER_ERROR)
576 .put(Connection.DisconnectCause.SERVER_UNREACHABLE,
577 Call.DisconnectCause.SERVER_UNREACHABLE)
578 .put(Connection.DisconnectCause.TIMED_OUT, Call.DisconnectCause.TIMED_OUT)
579 .put(Connection.DisconnectCause.UNOBTAINABLE_NUMBER,
580 Call.DisconnectCause.UNOBTAINABLE_NUMBER)
581 .build();
582
583 private Call.DisconnectCause translateDisconnectCauseFromTelephony(
584 Connection.DisconnectCause causeSource) {
585
586 if (CAUSE_MAP.containsKey(causeSource)) {
587 return CAUSE_MAP.get(causeSource);
588 }
589
590 return Call.DisconnectCause.UNKNOWN;
591 }
592
Santos Cordon63a84242013-07-23 13:32:52 -0700593 /**
Santos Cordone38b1ff2013-08-07 12:12:16 -0700594 * Gets an existing callId for a connection, or creates one if none exists.
595 * This function does NOT set any of the Connection data onto the Call class.
596 * A separate call to updateCallFromConnection must be made for that purpose.
Santos Cordon63a84242013-07-23 13:32:52 -0700597 */
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700598 private Call getCallFromMap(HashMap<Connection, Call> map, Connection conn,
599 boolean createIfMissing) {
Santos Cordon995c8162013-07-29 09:22:22 -0700600 Call call = null;
Santos Cordon63a84242013-07-23 13:32:52 -0700601
602 // Find the call id or create if missing and requested.
603 if (conn != null) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700604 if (map.containsKey(conn)) {
605 call = map.get(conn);
Santos Cordon63a84242013-07-23 13:32:52 -0700606 } else if (createIfMissing) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700607 call = createNewCall();
608 map.put(conn, call);
Santos Cordon63a84242013-07-23 13:32:52 -0700609 }
610 }
Santos Cordon995c8162013-07-29 09:22:22 -0700611 return call;
Santos Cordon63a84242013-07-23 13:32:52 -0700612 }
613
614 /**
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700615 * Creates a brand new connection for the call.
616 */
617 private Call createNewCall() {
618 int callId;
619 int newNextCallId;
620 do {
621 callId = mNextCallId.get();
622
623 // protect against overflow
624 newNextCallId = (callId == Integer.MAX_VALUE ?
625 CALL_ID_START_VALUE : callId + 1);
626
627 // Keep looping if the change was not atomic OR the value is already taken.
628 // The call to containsValue() is linear, however, most devices support a
629 // maximum of 7 connections so it's not expensive.
630 } while (!mNextCallId.compareAndSet(callId, newNextCallId));
631
632 return new Call(callId);
633 }
634
635 /**
Santos Cordon63a84242013-07-23 13:32:52 -0700636 * Listener interface for changes to Calls.
637 */
638 public interface Listener {
Santos Cordon995c8162013-07-29 09:22:22 -0700639 void onDisconnect(Call call);
Christine Chenee09a492013-08-06 16:02:29 -0700640 void onIncoming(Call call, ArrayList<String> textReponses);
Santos Cordon998f42b2013-08-02 16:13:12 -0700641 void onUpdate(List<Call> calls, boolean fullUpdate);
Santos Cordon63a84242013-07-23 13:32:52 -0700642 }
Santos Cordon249efd02013-08-05 03:33:56 -0700643
644 /**
645 * Result class for accessing a call by connection.
646 */
647 public static class CallResult {
648 public Call mCall;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700649 public Call mActionableCall;
Santos Cordon249efd02013-08-05 03:33:56 -0700650 public Connection mConnection;
651
652 private CallResult(Call call, Connection connection) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700653 this(call, call, connection);
654 }
655
656 private CallResult(Call call, Call actionableCall, Connection connection) {
Santos Cordon249efd02013-08-05 03:33:56 -0700657 mCall = call;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700658 mActionableCall = actionableCall;
Santos Cordon249efd02013-08-05 03:33:56 -0700659 mConnection = connection;
660 }
661
662 public Call getCall() {
663 return mCall;
664 }
665
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700666 // The call that should be used for call actions like hanging up.
667 public Call getActionableCall() {
668 return mActionableCall;
669 }
670
Santos Cordon249efd02013-08-05 03:33:56 -0700671 public Connection getConnection() {
672 return mConnection;
673 }
674 }
Santos Cordon63a84242013-07-23 13:32:52 -0700675}