blob: d8c11d654579c0a9b1e7f401e0904567750a6904 [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
Yorke Lee814da302013-08-30 16:01:07 -070019import android.content.Context;
Santos Cordon63a84242013-07-23 13:32:52 -070020import android.os.AsyncResult;
21import android.os.Handler;
22import android.os.Message;
Santos Cordon4ad64cd2013-08-15 00:36:14 -070023import android.os.SystemProperties;
Santos Cordone38b1ff2013-08-07 12:12:16 -070024import android.text.TextUtils;
25import android.util.Log;
Santos Cordon63a84242013-07-23 13:32:52 -070026
Santos Cordona3d05142013-07-29 11:25:17 -070027import com.android.internal.telephony.CallManager;
Santos Cordon63a84242013-07-23 13:32:52 -070028import com.android.internal.telephony.Connection;
Santos Cordoneead6ec2013-08-07 22:16:33 -070029import com.android.internal.telephony.Phone;
Santos Cordona3d05142013-07-29 11:25:17 -070030import com.android.internal.telephony.PhoneConstants;
Santos Cordon26e7b242013-08-07 21:15:45 -070031import com.android.internal.telephony.TelephonyCapabilities;
Santos Cordon69a69192013-08-22 14:25:42 -070032import com.android.phone.CallGatewayManager.RawGatewayInfo;
Santos Cordon995c8162013-07-29 09:22:22 -070033import com.android.services.telephony.common.Call;
Santos Cordon26e7b242013-08-07 21:15:45 -070034import com.android.services.telephony.common.Call.Capabilities;
Santos Cordona3d05142013-07-29 11:25:17 -070035import com.android.services.telephony.common.Call.State;
Santos Cordon63a84242013-07-23 13:32:52 -070036
Yorke Lee814da302013-08-30 16:01:07 -070037import com.google.android.collect.Lists;
38import com.google.android.collect.Maps;
39import com.google.common.base.Preconditions;
40import com.google.common.collect.ImmutableMap;
41import com.google.common.collect.ImmutableSortedSet;
42
Santos Cordon63a84242013-07-23 13:32:52 -070043import java.util.ArrayList;
44import java.util.HashMap;
45import java.util.List;
Santos Cordon249efd02013-08-05 03:33:56 -070046import java.util.Map.Entry;
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;
Santos Cordon69a69192013-08-22 14:25:42 -070087 private final CallGatewayManager mCallGatewayManager;
Santos Cordon998f42b2013-08-02 16:13:12 -070088 private final HashMap<Connection, Call> mCallMap = Maps.newHashMap();
Santos Cordon4ad64cd2013-08-15 00:36:14 -070089 private final HashMap<Connection, Call> mConfCallMap = Maps.newHashMap();
Santos Cordon998f42b2013-08-02 16:13:12 -070090 private final AtomicInteger mNextCallId = new AtomicInteger(CALL_ID_START_VALUE);
Christine Chendaf7bf62013-08-05 19:12:31 -070091 private final ArrayList<Listener> mListeners = new ArrayList<Listener>();
Christine Chenee09a492013-08-06 16:02:29 -070092 private RejectWithTextMessageManager mRejectWithTextMessageManager;
Santos Cordon63a84242013-07-23 13:32:52 -070093
Christine Chenee09a492013-08-06 16:02:29 -070094 public CallModeler(CallStateMonitor callStateMonitor, CallManager callManager,
Santos Cordon69a69192013-08-22 14:25:42 -070095 RejectWithTextMessageManager rejectWithTextMessageManager,
96 CallGatewayManager callGatewayManager) {
Santos Cordon63a84242013-07-23 13:32:52 -070097 mCallStateMonitor = callStateMonitor;
Santos Cordona3d05142013-07-29 11:25:17 -070098 mCallManager = callManager;
Christine Chenee09a492013-08-06 16:02:29 -070099 mRejectWithTextMessageManager = rejectWithTextMessageManager;
Santos Cordon69a69192013-08-22 14:25:42 -0700100 mCallGatewayManager = callGatewayManager;
Santos Cordon63a84242013-07-23 13:32:52 -0700101
102 mCallStateMonitor.addListener(this);
103 }
104
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700105 @Override
Santos Cordon63a84242013-07-23 13:32:52 -0700106 public void handleMessage(Message msg) {
107 switch(msg.what) {
108 case CallStateMonitor.PHONE_NEW_RINGING_CONNECTION:
109 onNewRingingConnection((AsyncResult) msg.obj);
110 break;
111 case CallStateMonitor.PHONE_DISCONNECT:
112 onDisconnect((AsyncResult) msg.obj);
Santos Cordon995c8162013-07-29 09:22:22 -0700113 break;
114 case CallStateMonitor.PHONE_STATE_CHANGED:
115 onPhoneStateChanged((AsyncResult) msg.obj);
116 break;
Santos Cordon63a84242013-07-23 13:32:52 -0700117 default:
118 break;
119 }
120 }
121
Christine Chendaf7bf62013-08-05 19:12:31 -0700122 public void addListener(Listener listener) {
Santos Cordon63a84242013-07-23 13:32:52 -0700123 Preconditions.checkNotNull(listener);
Christine Chendaf7bf62013-08-05 19:12:31 -0700124 Preconditions.checkNotNull(mListeners);
Christine Chen4748abd2013-08-07 15:44:15 -0700125 if (!mListeners.contains(listener)) {
126 mListeners.add(listener);
127 }
Santos Cordon998f42b2013-08-02 16:13:12 -0700128 }
129
130 public List<Call> getFullList() {
131 final List<Call> retval = Lists.newArrayList();
132 doUpdate(true, retval);
133 return retval;
Santos Cordon63a84242013-07-23 13:32:52 -0700134 }
135
Santos Cordon249efd02013-08-05 03:33:56 -0700136 public CallResult getCallWithId(int callId) {
137 // max 8 connections, so this should be fast even through we are traversing the entire map.
138 for (Entry<Connection, Call> entry : mCallMap.entrySet()) {
139 if (entry.getValue().getCallId() == callId) {
140 return new CallResult(entry.getValue(), entry.getKey());
141 }
142 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700143
144 for (Entry<Connection, Call> entry : mConfCallMap.entrySet()) {
145 if (entry.getValue().getCallId() == callId) {
146 if (entry.getValue().getChildCallIds().size() == 0) {
147 return null;
148 }
149 final CallResult child = getCallWithId(entry.getValue().getChildCallIds().first());
150 return new CallResult(entry.getValue(), child.getActionableCall(),
151 child.getConnection());
152 }
153 }
Santos Cordon249efd02013-08-05 03:33:56 -0700154 return null;
155 }
156
Santos Cordonaf763a12013-08-19 20:04:58 -0700157 public boolean hasLiveCall() {
158 return hasLiveCallInternal(mCallMap) ||
159 hasLiveCallInternal(mConfCallMap);
160 }
161
162 private boolean hasLiveCallInternal(HashMap<Connection, Call> map) {
163 for (Call call : map.values()) {
164 final int state = call.getState();
165 if (state == Call.State.ACTIVE ||
166 state == Call.State.CALL_WAITING ||
167 state == Call.State.CONFERENCED ||
168 state == Call.State.DIALING ||
169 state == Call.State.INCOMING ||
170 state == Call.State.ONHOLD) {
171 return true;
172 }
173 }
174 return false;
175 }
176
Santos Cordon2b73bd62013-08-27 14:53:43 -0700177 public boolean hasOutstandingActiveOrDialingCall() {
178 return hasOutstandingActiveOrDialingCallInternal(mCallMap) ||
179 hasOutstandingActiveOrDialingCallInternal(mConfCallMap);
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700180 }
181
Santos Cordon2b73bd62013-08-27 14:53:43 -0700182 private static boolean hasOutstandingActiveOrDialingCallInternal(
183 HashMap<Connection, Call> map) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700184 for (Call call : map.values()) {
185 final int state = call.getState();
Santos Cordon2b73bd62013-08-27 14:53:43 -0700186 if (state == Call.State.ACTIVE ||
187 state == Call.State.DIALING) {
Santos Cordon2eaff902013-08-05 04:37:55 -0700188 return true;
189 }
190 }
191
192 return false;
193 }
194
Santos Cordon63a84242013-07-23 13:32:52 -0700195 private void onNewRingingConnection(AsyncResult r) {
Santos Cordon2b73bd62013-08-27 14:53:43 -0700196 Log.i(TAG, "onNewRingingConnection");
Santos Cordon63a84242013-07-23 13:32:52 -0700197 final Connection conn = (Connection) r.result;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700198 final Call call = getCallFromMap(mCallMap, conn, true);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700199
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700200 updateCallFromConnection(call, conn, false);
Santos Cordon63a84242013-07-23 13:32:52 -0700201
Christine Chendaf7bf62013-08-05 19:12:31 -0700202 for (int i = 0; i < mListeners.size(); ++i) {
Christine Chenee09a492013-08-06 16:02:29 -0700203 if (call != null) {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700204 mListeners.get(i).onIncoming(call);
Christine Chenee09a492013-08-06 16:02:29 -0700205 }
Santos Cordon63a84242013-07-23 13:32:52 -0700206 }
207 }
208
209 private void onDisconnect(AsyncResult r) {
Santos Cordon2b73bd62013-08-27 14:53:43 -0700210 Log.i(TAG, "onDisconnect");
Santos Cordon63a84242013-07-23 13:32:52 -0700211 final Connection conn = (Connection) r.result;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700212 final Call call = getCallFromMap(mCallMap, conn, false);
Santos Cordon63a84242013-07-23 13:32:52 -0700213
Santos Cordon995c8162013-07-29 09:22:22 -0700214 if (call != null) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700215 final boolean wasConferenced = call.getState() == State.CONFERENCED;
216
217 updateCallFromConnection(call, conn, false);
Santos Cordon63a84242013-07-23 13:32:52 -0700218
Christine Chendaf7bf62013-08-05 19:12:31 -0700219 for (int i = 0; i < mListeners.size(); ++i) {
220 mListeners.get(i).onDisconnect(call);
Santos Cordon63a84242013-07-23 13:32:52 -0700221 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700222
223 // If it was a conferenced call, we need to run the entire update
224 // to make the proper changes to parent conference calls.
225 if (wasConferenced) {
226 onPhoneStateChanged(null);
227 }
228
229 mCallMap.remove(conn);
Santos Cordon63a84242013-07-23 13:32:52 -0700230 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700231
232 // TODO(klp): Do a final check to see if there are any active calls.
233 // If there are not, totally cancel all calls
Santos Cordon63a84242013-07-23 13:32:52 -0700234 }
235
Santos Cordona3d05142013-07-29 11:25:17 -0700236 /**
237 * Called when the phone state changes.
Santos Cordona3d05142013-07-29 11:25:17 -0700238 */
Santos Cordon995c8162013-07-29 09:22:22 -0700239 private void onPhoneStateChanged(AsyncResult r) {
Santos Cordon2b73bd62013-08-27 14:53:43 -0700240 Log.i(TAG, "onPhoneStateChanged: ");
Santos Cordon998f42b2013-08-02 16:13:12 -0700241 final List<Call> updatedCalls = Lists.newArrayList();
242 doUpdate(false, updatedCalls);
243
Santos Cordon71d5c6e2013-09-05 21:34:33 -0700244 if (updatedCalls.size() > 0) {
245 for (int i = 0; i < mListeners.size(); ++i) {
246 mListeners.get(i).onUpdate(updatedCalls);
247 }
Santos Cordon998f42b2013-08-02 16:13:12 -0700248 }
249 }
250
251
252 /**
253 * Go through the Calls from CallManager and return the list of calls that were updated.
254 * Or, the full list if requested.
255 */
256 private void doUpdate(boolean fullUpdate, List<Call> out) {
Santos Cordona3d05142013-07-29 11:25:17 -0700257 final List<com.android.internal.telephony.Call> telephonyCalls = Lists.newArrayList();
258 telephonyCalls.addAll(mCallManager.getRingingCalls());
259 telephonyCalls.addAll(mCallManager.getForegroundCalls());
260 telephonyCalls.addAll(mCallManager.getBackgroundCalls());
261
Santos Cordona3d05142013-07-29 11:25:17 -0700262 // Cycle through all the Connections on all the Calls. Update our Call objects
263 // to reflect any new state and send the updated Call objects to the handler service.
264 for (com.android.internal.telephony.Call telephonyCall : telephonyCalls) {
Santos Cordona3d05142013-07-29 11:25:17 -0700265
266 for (Connection connection : telephonyCall.getConnections()) {
Santos Cordon71d5c6e2013-09-05 21:34:33 -0700267 // We do not create incoming or disconnected calls on update. Those are created
268 // from the associated onNewRingingConnection and onDisconnected which do this
269 // process on their own and slightly differently.
270 boolean create = connection.getState().isAlive() &&
271 !connection.getState().isRinging();
272
273 // New connections return a Call with INVALID state, which does not translate to
Santos Cordone38b1ff2013-08-07 12:12:16 -0700274 // a state in the internal.telephony.Call object. This ensures that staleness
275 // check below fails and we always add the item to the update list if it is new.
Santos Cordon71d5c6e2013-09-05 21:34:33 -0700276 final Call call = getCallFromMap(mCallMap, connection, create);
277
278 if (call == null) {
279 continue;
280 }
Santos Cordona3d05142013-07-29 11:25:17 -0700281
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700282 boolean changed = updateCallFromConnection(call, connection, false);
Santos Cordon2b73bd62013-08-27 14:53:43 -0700283
Santos Cordone38b1ff2013-08-07 12:12:16 -0700284 if (fullUpdate || changed) {
Santos Cordon998f42b2013-08-02 16:13:12 -0700285 out.add(call);
Santos Cordona3d05142013-07-29 11:25:17 -0700286 }
287 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700288
289 // We do a second loop to address conference call scenarios. We do this as a separate
290 // loop to ensure all child calls are up to date before we start updating the parent
291 // conference calls.
292 for (Connection connection : telephonyCall.getConnections()) {
293 updateForConferenceCalls(connection, out);
294 }
295
Santos Cordona3d05142013-07-29 11:25:17 -0700296 }
Santos Cordona3d05142013-07-29 11:25:17 -0700297 }
298
Santos Cordone38b1ff2013-08-07 12:12:16 -0700299 /**
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700300 * Checks to see if the connection is the first connection in a conference call.
301 * If it is a conference call, we will create a new Conference Call object or
302 * update the existing conference call object for that connection.
303 * If it is not a conference call but a previous associated conference call still exists,
304 * we mark it as idle and remove it from the map.
305 * In both cases above, we add the Calls to be updated to the UI.
306 * @param connection The connection object to check.
307 * @param updatedCalls List of 'updated' calls that will be sent to the UI.
308 */
309 private boolean updateForConferenceCalls(Connection connection, List<Call> updatedCalls) {
310 // We consider this connection a conference connection if the call it
311 // belongs to is a multiparty call AND it is the first connection.
312 final boolean isConferenceCallConnection = isPartOfLiveConferenceCall(connection) &&
313 connection.getCall().getEarliestConnection() == connection;
314
315 boolean changed = false;
316
317 // If this connection is the main connection for the conference call, then create or update
318 // a Call object for that conference call.
319 if (isConferenceCallConnection) {
320 final Call confCall = getCallFromMap(mConfCallMap, connection, true);
321 changed = updateCallFromConnection(confCall, connection, true);
322
323 if (changed) {
324 updatedCalls.add(confCall);
325 }
326
327 if (DBG) Log.d(TAG, "Updating a conference call: " + confCall);
328
329 // It is possible that through a conference call split, there may be lingering conference
330 // calls where this connection was the main connection. We clean those up here.
331 } else {
332 final Call oldConfCall = getCallFromMap(mConfCallMap, connection, false);
333
334 // We found a conference call for this connection, which is no longer a conference call.
335 // Kill it!
336 if (oldConfCall != null) {
337 if (DBG) Log.d(TAG, "Cleaning up an old conference call: " + oldConfCall);
338 mConfCallMap.remove(connection);
339 oldConfCall.setState(State.IDLE);
340 changed = true;
341
342 // add to the list of calls to update
343 updatedCalls.add(oldConfCall);
344 }
345 }
346
347 return changed;
348 }
349
350 /**
Santos Cordon69a69192013-08-22 14:25:42 -0700351 * Sets the new call state onto the call and performs some additional logic
352 * associated with setting the state.
353 */
354 private void setNewState(Call call, int newState, Connection connection) {
355 Preconditions.checkState(call.getState() != newState);
356
357 // When starting an outgoing call, we need to grab gateway information
358 // for the call, if available, and set it.
359 final RawGatewayInfo info = mCallGatewayManager.getGatewayInfo(connection);
360
361 if (newState == Call.State.DIALING) {
362 if (!info.isEmpty()) {
363 call.setGatewayNumber(info.getFormattedGatewayNumber());
364 call.setGatewayPackage(info.packageName);
365 }
366 } else if (!Call.State.isConnected(newState)) {
367 mCallGatewayManager.clearGatewayData(connection);
368 }
369
370 call.setState(newState);
371 }
372
373 /**
Santos Cordone38b1ff2013-08-07 12:12:16 -0700374 * Updates the Call properties to match the state of the connection object
375 * that it represents.
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700376 * @param call The call object to update.
377 * @param connection The connection object from which to update call.
378 * @param isForConference There are slight differences in how we populate data for conference
379 * calls. This boolean tells us which method to use.
Santos Cordone38b1ff2013-08-07 12:12:16 -0700380 */
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700381 private boolean updateCallFromConnection(Call call, Connection connection,
382 boolean isForConference) {
Santos Cordone38b1ff2013-08-07 12:12:16 -0700383 boolean changed = false;
384
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700385 final int newState = translateStateFromTelephony(connection, isForConference);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700386
387 if (call.getState() != newState) {
Santos Cordon69a69192013-08-22 14:25:42 -0700388 setNewState(call, newState, connection);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700389 changed = true;
390 }
391
Santos Cordone38b1ff2013-08-07 12:12:16 -0700392 final Call.DisconnectCause newDisconnectCause =
393 translateDisconnectCauseFromTelephony(connection.getDisconnectCause());
394 if (call.getDisconnectCause() != newDisconnectCause) {
395 call.setDisconnectCause(newDisconnectCause);
396 changed = true;
397 }
398
Santos Cordonbbe8ecf2013-08-13 15:26:18 -0700399 final long oldConnectTime = call.getConnectTime();
400 if (oldConnectTime != connection.getConnectTime()) {
401 call.setConnectTime(connection.getConnectTime());
402 changed = true;
403 }
404
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700405 if (!isForConference) {
Santos Cordon69a69192013-08-22 14:25:42 -0700406 // Number
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700407 final String oldNumber = call.getNumber();
Santos Cordon69a69192013-08-22 14:25:42 -0700408 String newNumber = connection.getAddress();
409 RawGatewayInfo info = mCallGatewayManager.getGatewayInfo(connection);
410 if (!info.isEmpty()) {
411 newNumber = info.trueNumber;
412 }
413 if (TextUtils.isEmpty(oldNumber) || !oldNumber.equals(newNumber)) {
414 call.setNumber(newNumber);
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700415 changed = true;
416 }
417
Santos Cordon69a69192013-08-22 14:25:42 -0700418 // Number presentation
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700419 final int newNumberPresentation = connection.getNumberPresentation();
420 if (call.getNumberPresentation() != newNumberPresentation) {
421 call.setNumberPresentation(newNumberPresentation);
422 changed = true;
423 }
424
Santos Cordon69a69192013-08-22 14:25:42 -0700425 // Name
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700426 final String oldCnapName = call.getCnapName();
427 if (TextUtils.isEmpty(oldCnapName) || !oldCnapName.equals(connection.getCnapName())) {
428 call.setCnapName(connection.getCnapName());
429 changed = true;
430 }
Santos Cordon69a69192013-08-22 14:25:42 -0700431
432 // Name Presentation
433 final int newCnapNamePresentation = connection.getCnapNamePresentation();
434 if (call.getCnapNamePresentation() != newCnapNamePresentation) {
435 call.setCnapNamePresentation(newCnapNamePresentation);
436 changed = true;
437 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700438 } else {
439
440 // update the list of children by:
441 // 1) Saving the old set
442 // 2) Removing all children
443 // 3) Adding the correct children into the Call
444 // 4) Comparing the new children set with the old children set
445 ImmutableSortedSet<Integer> oldSet = call.getChildCallIds();
446 call.removeAllChildren();
447
448 if (connection.getCall() != null) {
449 for (Connection childConn : connection.getCall().getConnections()) {
450 final Call childCall = getCallFromMap(mCallMap, childConn, false);
451 if (childCall != null && childConn.isAlive()) {
452 call.addChildId(childCall.getCallId());
453 }
454 }
455 }
456 changed |= oldSet.equals(call.getChildCallIds());
457 }
458
Santos Cordoneead6ec2013-08-07 22:16:33 -0700459 /**
460 * !!! Uses values from connection and call collected above so this part must be last !!!
461 */
462 final int newCapabilities = getCapabilitiesFor(connection, call);
Santos Cordon26e7b242013-08-07 21:15:45 -0700463 if (call.getCapabilities() != newCapabilities) {
464 call.setCapabilities(newCapabilities);
465 changed = true;
466 }
467
Santos Cordone38b1ff2013-08-07 12:12:16 -0700468 return changed;
469 }
470
Santos Cordon26e7b242013-08-07 21:15:45 -0700471 /**
472 * Returns a mask of capabilities for the connection such as merge, hold, etc.
473 */
Santos Cordoneead6ec2013-08-07 22:16:33 -0700474 private int getCapabilitiesFor(Connection connection, Call call) {
475 final boolean callIsActive = (call.getState() == Call.State.ACTIVE);
476 final Phone phone = connection.getCall().getPhone();
477
478 final boolean canHold = TelephonyCapabilities.supportsAnswerAndHold(phone);
479 boolean canAddCall = false;
480 boolean canMergeCall = false;
481 boolean canSwapCall = false;
Yorke Lee814da302013-08-30 16:01:07 -0700482 boolean canRespondViaText = false;
Santos Cordoneead6ec2013-08-07 22:16:33 -0700483
484 // only applies to active calls
485 if (callIsActive) {
486 canAddCall = PhoneUtils.okToAddCall(mCallManager);
487 canMergeCall = PhoneUtils.okToMergeCalls(mCallManager);
488 canSwapCall = PhoneUtils.okToSwapCalls(mCallManager);
489 }
490
Yorke Lee814da302013-08-30 16:01:07 -0700491 canRespondViaText = RejectWithTextMessageManager.allowRespondViaSmsForCall(call,
492 connection);
493
Santos Cordoneead6ec2013-08-07 22:16:33 -0700494 // special rules section!
495 // CDMA always has Add
496 if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
497 canAddCall = true;
498 } else {
499 // if neither merge nor add is on...then allow add
500 canAddCall |= !(canAddCall || canMergeCall);
501 }
502
Santos Cordon26e7b242013-08-07 21:15:45 -0700503 int retval = 0x0;
Santos Cordoneead6ec2013-08-07 22:16:33 -0700504 if (canHold) {
Santos Cordon26e7b242013-08-07 21:15:45 -0700505 retval |= Capabilities.HOLD;
506 }
Santos Cordoneead6ec2013-08-07 22:16:33 -0700507 if (canAddCall) {
508 retval |= Capabilities.ADD_CALL;
509 }
510 if (canMergeCall) {
511 retval |= Capabilities.MERGE_CALLS;
512 }
513 if (canSwapCall) {
514 retval |= Capabilities.SWAP_CALLS;
515 }
Santos Cordon26e7b242013-08-07 21:15:45 -0700516
Yorke Lee814da302013-08-30 16:01:07 -0700517 if (canRespondViaText) {
518 retval |= Capabilities.RESPOND_VIA_TEXT;
519 }
520
Santos Cordon26e7b242013-08-07 21:15:45 -0700521 return retval;
522 }
523
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700524 /**
525 * Returns true if the Connection is part of a multiparty call.
526 * We do this by checking the isMultiparty() method of the telephony.Call object and also
527 * checking to see if more than one of it's children is alive.
528 */
529 private boolean isPartOfLiveConferenceCall(Connection connection) {
530 if (connection.getCall() != null && connection.getCall().isMultiparty()) {
531 int count = 0;
532 for (Connection currConn : connection.getCall().getConnections()) {
533 if (currConn.isAlive()) {
534 count++;
535 if (count >= 2) {
536 return true;
537 }
538 }
539 }
540 }
541 return false;
542 }
543
544 private int translateStateFromTelephony(Connection connection, boolean isForConference) {
545
Santos Cordona3d05142013-07-29 11:25:17 -0700546 int retval = State.IDLE;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700547 switch (connection.getState()) {
Santos Cordona3d05142013-07-29 11:25:17 -0700548 case ACTIVE:
549 retval = State.ACTIVE;
550 break;
551 case INCOMING:
552 retval = State.INCOMING;
553 break;
554 case DIALING:
555 case ALERTING:
556 retval = State.DIALING;
557 break;
558 case WAITING:
559 retval = State.CALL_WAITING;
560 break;
561 case HOLDING:
562 retval = State.ONHOLD;
563 break;
Santos Cordone38b1ff2013-08-07 12:12:16 -0700564 case DISCONNECTED:
565 case DISCONNECTING:
566 retval = State.DISCONNECTED;
Santos Cordona3d05142013-07-29 11:25:17 -0700567 default:
568 }
569
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700570 // If we are dealing with a potential child call (not the parent conference call),
571 // the check to see if we have to set the state to CONFERENCED.
572 if (!isForConference) {
573
574 // if the connection is part of a multiparty call, and it is live,
575 // annotate it with CONFERENCED state instead.
576 if (isPartOfLiveConferenceCall(connection) && connection.isAlive()) {
577 return State.CONFERENCED;
578 }
579 }
580
Santos Cordona3d05142013-07-29 11:25:17 -0700581 return retval;
Santos Cordon995c8162013-07-29 09:22:22 -0700582 }
583
Santos Cordone38b1ff2013-08-07 12:12:16 -0700584 private final ImmutableMap<Connection.DisconnectCause, Call.DisconnectCause> CAUSE_MAP =
585 ImmutableMap.<Connection.DisconnectCause, Call.DisconnectCause>builder()
586 .put(Connection.DisconnectCause.BUSY, Call.DisconnectCause.BUSY)
587 .put(Connection.DisconnectCause.CALL_BARRED, Call.DisconnectCause.CALL_BARRED)
588 .put(Connection.DisconnectCause.CDMA_ACCESS_BLOCKED,
589 Call.DisconnectCause.CDMA_ACCESS_BLOCKED)
590 .put(Connection.DisconnectCause.CDMA_ACCESS_FAILURE,
591 Call.DisconnectCause.CDMA_ACCESS_FAILURE)
592 .put(Connection.DisconnectCause.CDMA_DROP, Call.DisconnectCause.CDMA_DROP)
593 .put(Connection.DisconnectCause.CDMA_INTERCEPT, Call.DisconnectCause.CDMA_INTERCEPT)
594 .put(Connection.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE,
595 Call.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE)
596 .put(Connection.DisconnectCause.CDMA_NOT_EMERGENCY,
597 Call.DisconnectCause.CDMA_NOT_EMERGENCY)
598 .put(Connection.DisconnectCause.CDMA_PREEMPTED, Call.DisconnectCause.CDMA_PREEMPTED)
599 .put(Connection.DisconnectCause.CDMA_REORDER, Call.DisconnectCause.CDMA_REORDER)
600 .put(Connection.DisconnectCause.CDMA_RETRY_ORDER,
601 Call.DisconnectCause.CDMA_RETRY_ORDER)
602 .put(Connection.DisconnectCause.CDMA_SO_REJECT, Call.DisconnectCause.CDMA_SO_REJECT)
603 .put(Connection.DisconnectCause.CONGESTION, Call.DisconnectCause.CONGESTION)
604 .put(Connection.DisconnectCause.CS_RESTRICTED, Call.DisconnectCause.CS_RESTRICTED)
605 .put(Connection.DisconnectCause.CS_RESTRICTED_EMERGENCY,
606 Call.DisconnectCause.CS_RESTRICTED_EMERGENCY)
607 .put(Connection.DisconnectCause.CS_RESTRICTED_NORMAL,
608 Call.DisconnectCause.CS_RESTRICTED_NORMAL)
609 .put(Connection.DisconnectCause.ERROR_UNSPECIFIED,
610 Call.DisconnectCause.ERROR_UNSPECIFIED)
611 .put(Connection.DisconnectCause.FDN_BLOCKED, Call.DisconnectCause.FDN_BLOCKED)
612 .put(Connection.DisconnectCause.ICC_ERROR, Call.DisconnectCause.ICC_ERROR)
613 .put(Connection.DisconnectCause.INCOMING_MISSED,
614 Call.DisconnectCause.INCOMING_MISSED)
615 .put(Connection.DisconnectCause.INCOMING_REJECTED,
616 Call.DisconnectCause.INCOMING_REJECTED)
617 .put(Connection.DisconnectCause.INVALID_CREDENTIALS,
618 Call.DisconnectCause.INVALID_CREDENTIALS)
619 .put(Connection.DisconnectCause.INVALID_NUMBER,
620 Call.DisconnectCause.INVALID_NUMBER)
621 .put(Connection.DisconnectCause.LIMIT_EXCEEDED, Call.DisconnectCause.LIMIT_EXCEEDED)
622 .put(Connection.DisconnectCause.LOCAL, Call.DisconnectCause.LOCAL)
623 .put(Connection.DisconnectCause.LOST_SIGNAL, Call.DisconnectCause.LOST_SIGNAL)
624 .put(Connection.DisconnectCause.MMI, Call.DisconnectCause.MMI)
625 .put(Connection.DisconnectCause.NORMAL, Call.DisconnectCause.NORMAL)
626 .put(Connection.DisconnectCause.NOT_DISCONNECTED,
627 Call.DisconnectCause.NOT_DISCONNECTED)
628 .put(Connection.DisconnectCause.NUMBER_UNREACHABLE,
629 Call.DisconnectCause.NUMBER_UNREACHABLE)
630 .put(Connection.DisconnectCause.OUT_OF_NETWORK, Call.DisconnectCause.OUT_OF_NETWORK)
631 .put(Connection.DisconnectCause.OUT_OF_SERVICE, Call.DisconnectCause.OUT_OF_SERVICE)
632 .put(Connection.DisconnectCause.POWER_OFF, Call.DisconnectCause.POWER_OFF)
633 .put(Connection.DisconnectCause.SERVER_ERROR, Call.DisconnectCause.SERVER_ERROR)
634 .put(Connection.DisconnectCause.SERVER_UNREACHABLE,
635 Call.DisconnectCause.SERVER_UNREACHABLE)
636 .put(Connection.DisconnectCause.TIMED_OUT, Call.DisconnectCause.TIMED_OUT)
637 .put(Connection.DisconnectCause.UNOBTAINABLE_NUMBER,
638 Call.DisconnectCause.UNOBTAINABLE_NUMBER)
639 .build();
640
641 private Call.DisconnectCause translateDisconnectCauseFromTelephony(
642 Connection.DisconnectCause causeSource) {
643
644 if (CAUSE_MAP.containsKey(causeSource)) {
645 return CAUSE_MAP.get(causeSource);
646 }
647
648 return Call.DisconnectCause.UNKNOWN;
649 }
650
Santos Cordon63a84242013-07-23 13:32:52 -0700651 /**
Santos Cordone38b1ff2013-08-07 12:12:16 -0700652 * Gets an existing callId for a connection, or creates one if none exists.
653 * This function does NOT set any of the Connection data onto the Call class.
654 * A separate call to updateCallFromConnection must be made for that purpose.
Santos Cordon63a84242013-07-23 13:32:52 -0700655 */
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700656 private Call getCallFromMap(HashMap<Connection, Call> map, Connection conn,
657 boolean createIfMissing) {
Santos Cordon995c8162013-07-29 09:22:22 -0700658 Call call = null;
Santos Cordon63a84242013-07-23 13:32:52 -0700659
660 // Find the call id or create if missing and requested.
661 if (conn != null) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700662 if (map.containsKey(conn)) {
663 call = map.get(conn);
Santos Cordon63a84242013-07-23 13:32:52 -0700664 } else if (createIfMissing) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700665 call = createNewCall();
666 map.put(conn, call);
Santos Cordon63a84242013-07-23 13:32:52 -0700667 }
668 }
Santos Cordon995c8162013-07-29 09:22:22 -0700669 return call;
Santos Cordon63a84242013-07-23 13:32:52 -0700670 }
671
672 /**
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700673 * Creates a brand new connection for the call.
674 */
675 private Call createNewCall() {
676 int callId;
677 int newNextCallId;
678 do {
679 callId = mNextCallId.get();
680
681 // protect against overflow
682 newNextCallId = (callId == Integer.MAX_VALUE ?
683 CALL_ID_START_VALUE : callId + 1);
684
685 // Keep looping if the change was not atomic OR the value is already taken.
686 // The call to containsValue() is linear, however, most devices support a
687 // maximum of 7 connections so it's not expensive.
688 } while (!mNextCallId.compareAndSet(callId, newNextCallId));
689
690 return new Call(callId);
691 }
692
693 /**
Santos Cordon63a84242013-07-23 13:32:52 -0700694 * Listener interface for changes to Calls.
695 */
696 public interface Listener {
Santos Cordon995c8162013-07-29 09:22:22 -0700697 void onDisconnect(Call call);
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700698 void onIncoming(Call call);
699 void onUpdate(List<Call> calls);
Santos Cordon63a84242013-07-23 13:32:52 -0700700 }
Santos Cordon249efd02013-08-05 03:33:56 -0700701
702 /**
703 * Result class for accessing a call by connection.
704 */
705 public static class CallResult {
706 public Call mCall;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700707 public Call mActionableCall;
Santos Cordon249efd02013-08-05 03:33:56 -0700708 public Connection mConnection;
709
710 private CallResult(Call call, Connection connection) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700711 this(call, call, connection);
712 }
713
714 private CallResult(Call call, Call actionableCall, Connection connection) {
Santos Cordon249efd02013-08-05 03:33:56 -0700715 mCall = call;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700716 mActionableCall = actionableCall;
Santos Cordon249efd02013-08-05 03:33:56 -0700717 mConnection = connection;
718 }
719
720 public Call getCall() {
721 return mCall;
722 }
723
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700724 // The call that should be used for call actions like hanging up.
725 public Call getActionableCall() {
726 return mActionableCall;
727 }
728
Santos Cordon249efd02013-08-05 03:33:56 -0700729 public Connection getConnection() {
730 return mConnection;
731 }
732 }
Santos Cordon63a84242013-07-23 13:32:52 -0700733}