blob: 6b7a137e72d188729e6180be53ecfe23b9b3dac6 [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 Cordon69a69192013-08-22 14:25:42 -070038import com.android.phone.CallGatewayManager.RawGatewayInfo;
Santos Cordon995c8162013-07-29 09:22:22 -070039import com.android.services.telephony.common.Call;
Santos Cordon26e7b242013-08-07 21:15:45 -070040import com.android.services.telephony.common.Call.Capabilities;
Santos Cordona3d05142013-07-29 11:25:17 -070041import com.android.services.telephony.common.Call.State;
Santos Cordon63a84242013-07-23 13:32:52 -070042
43import java.util.ArrayList;
44import java.util.HashMap;
45import java.util.List;
Santos Cordon249efd02013-08-05 03:33:56 -070046import java.util.Map.Entry;
Santos Cordon4ad64cd2013-08-15 00:36:14 -070047import java.util.SortedSet;
Santos Cordon63a84242013-07-23 13:32:52 -070048import java.util.concurrent.atomic.AtomicInteger;
49
50/**
51 * Creates a Call model from Call state and data received from the telephony
52 * layer. The telephony layer maintains 3 conceptual objects: Phone, Call,
53 * Connection.
54 *
55 * Phone represents the radio and there is an implementation per technology
56 * type such as GSMPhone, SipPhone, CDMAPhone, etc. Generally, we will only ever
57 * deal with one instance of this object for the lifetime of this class.
58 *
59 * There are 3 Call instances that exist for the lifetime of this class which
60 * are created by CallTracker. The three are RingingCall, ForegroundCall, and
61 * BackgroundCall.
62 *
63 * A Connection most closely resembles what the layperson would consider a call.
64 * A Connection is created when a user dials and it is "owned" by one of the
65 * three Call instances. Which of the three Calls owns the Connection changes
66 * as the Connection goes between ACTIVE, HOLD, RINGING, and other states.
67 *
68 * This class models a new Call class from Connection objects received from
69 * the telephony layer. We use Connection references as identifiers for a call;
70 * new reference = new call.
71 *
72 * TODO(klp): Create a new Call class to replace the simple call Id ints
73 * being used currently.
74 *
75 * The new Call models are parcellable for transfer via the CallHandlerService
76 * API.
77 */
78public class CallModeler extends Handler {
79
80 private static final String TAG = CallModeler.class.getSimpleName();
Santos Cordon4ad64cd2013-08-15 00:36:14 -070081 private static final boolean DBG =
82 (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
Santos Cordon63a84242013-07-23 13:32:52 -070083
84 private static final int CALL_ID_START_VALUE = 1;
Santos Cordon63a84242013-07-23 13:32:52 -070085
Santos Cordon998f42b2013-08-02 16:13:12 -070086 private final CallStateMonitor mCallStateMonitor;
87 private final CallManager mCallManager;
Santos Cordon69a69192013-08-22 14:25:42 -070088 private final CallGatewayManager mCallGatewayManager;
Santos Cordon998f42b2013-08-02 16:13:12 -070089 private final HashMap<Connection, Call> mCallMap = Maps.newHashMap();
Santos Cordon4ad64cd2013-08-15 00:36:14 -070090 private final HashMap<Connection, Call> mConfCallMap = Maps.newHashMap();
Santos Cordon998f42b2013-08-02 16:13:12 -070091 private final AtomicInteger mNextCallId = new AtomicInteger(CALL_ID_START_VALUE);
Christine Chendaf7bf62013-08-05 19:12:31 -070092 private final ArrayList<Listener> mListeners = new ArrayList<Listener>();
Christine Chenee09a492013-08-06 16:02:29 -070093 private RejectWithTextMessageManager mRejectWithTextMessageManager;
Santos Cordon63a84242013-07-23 13:32:52 -070094
Christine Chenee09a492013-08-06 16:02:29 -070095 public CallModeler(CallStateMonitor callStateMonitor, CallManager callManager,
Santos Cordon69a69192013-08-22 14:25:42 -070096 RejectWithTextMessageManager rejectWithTextMessageManager,
97 CallGatewayManager callGatewayManager) {
Santos Cordon63a84242013-07-23 13:32:52 -070098 mCallStateMonitor = callStateMonitor;
Santos Cordona3d05142013-07-29 11:25:17 -070099 mCallManager = callManager;
Christine Chenee09a492013-08-06 16:02:29 -0700100 mRejectWithTextMessageManager = rejectWithTextMessageManager;
Santos Cordon69a69192013-08-22 14:25:42 -0700101 mCallGatewayManager = callGatewayManager;
Santos Cordon63a84242013-07-23 13:32:52 -0700102
103 mCallStateMonitor.addListener(this);
104 }
105
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700106 @Override
Santos Cordon63a84242013-07-23 13:32:52 -0700107 public void handleMessage(Message msg) {
108 switch(msg.what) {
109 case CallStateMonitor.PHONE_NEW_RINGING_CONNECTION:
110 onNewRingingConnection((AsyncResult) msg.obj);
111 break;
112 case CallStateMonitor.PHONE_DISCONNECT:
113 onDisconnect((AsyncResult) msg.obj);
Santos Cordon995c8162013-07-29 09:22:22 -0700114 break;
115 case CallStateMonitor.PHONE_STATE_CHANGED:
116 onPhoneStateChanged((AsyncResult) msg.obj);
117 break;
Santos Cordon63a84242013-07-23 13:32:52 -0700118 default:
119 break;
120 }
121 }
122
Christine Chendaf7bf62013-08-05 19:12:31 -0700123 public void addListener(Listener listener) {
Santos Cordon63a84242013-07-23 13:32:52 -0700124 Preconditions.checkNotNull(listener);
Christine Chendaf7bf62013-08-05 19:12:31 -0700125 Preconditions.checkNotNull(mListeners);
Christine Chen4748abd2013-08-07 15:44:15 -0700126 if (!mListeners.contains(listener)) {
127 mListeners.add(listener);
128 }
Santos Cordon998f42b2013-08-02 16:13:12 -0700129 }
130
131 public List<Call> getFullList() {
132 final List<Call> retval = Lists.newArrayList();
133 doUpdate(true, retval);
134 return retval;
Santos Cordon63a84242013-07-23 13:32:52 -0700135 }
136
Santos Cordon249efd02013-08-05 03:33:56 -0700137 public CallResult getCallWithId(int callId) {
138 // max 8 connections, so this should be fast even through we are traversing the entire map.
139 for (Entry<Connection, Call> entry : mCallMap.entrySet()) {
140 if (entry.getValue().getCallId() == callId) {
141 return new CallResult(entry.getValue(), entry.getKey());
142 }
143 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700144
145 for (Entry<Connection, Call> entry : mConfCallMap.entrySet()) {
146 if (entry.getValue().getCallId() == callId) {
147 if (entry.getValue().getChildCallIds().size() == 0) {
148 return null;
149 }
150 final CallResult child = getCallWithId(entry.getValue().getChildCallIds().first());
151 return new CallResult(entry.getValue(), child.getActionableCall(),
152 child.getConnection());
153 }
154 }
Santos Cordon249efd02013-08-05 03:33:56 -0700155 return null;
156 }
157
Santos Cordonaf763a12013-08-19 20:04:58 -0700158 public boolean hasLiveCall() {
159 return hasLiveCallInternal(mCallMap) ||
160 hasLiveCallInternal(mConfCallMap);
161 }
162
163 private boolean hasLiveCallInternal(HashMap<Connection, Call> map) {
164 for (Call call : map.values()) {
165 final int state = call.getState();
166 if (state == Call.State.ACTIVE ||
167 state == Call.State.CALL_WAITING ||
168 state == Call.State.CONFERENCED ||
169 state == Call.State.DIALING ||
170 state == Call.State.INCOMING ||
171 state == Call.State.ONHOLD) {
172 return true;
173 }
174 }
175 return false;
176 }
177
Santos Cordon2b73bd62013-08-27 14:53:43 -0700178 public boolean hasOutstandingActiveOrDialingCall() {
179 return hasOutstandingActiveOrDialingCallInternal(mCallMap) ||
180 hasOutstandingActiveOrDialingCallInternal(mConfCallMap);
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700181 }
182
Santos Cordon2b73bd62013-08-27 14:53:43 -0700183 private static boolean hasOutstandingActiveOrDialingCallInternal(
184 HashMap<Connection, Call> map) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700185 for (Call call : map.values()) {
186 final int state = call.getState();
Santos Cordon2b73bd62013-08-27 14:53:43 -0700187 if (state == Call.State.ACTIVE ||
188 state == Call.State.DIALING) {
Santos Cordon2eaff902013-08-05 04:37:55 -0700189 return true;
190 }
191 }
192
193 return false;
194 }
195
Santos Cordon63a84242013-07-23 13:32:52 -0700196 private void onNewRingingConnection(AsyncResult r) {
Santos Cordon2b73bd62013-08-27 14:53:43 -0700197 Log.i(TAG, "onNewRingingConnection");
Santos Cordon63a84242013-07-23 13:32:52 -0700198 final Connection conn = (Connection) r.result;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700199 final Call call = getCallFromMap(mCallMap, conn, true);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700200
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700201 updateCallFromConnection(call, conn, false);
Santos Cordona3d05142013-07-29 11:25:17 -0700202 call.setState(Call.State.INCOMING);
Santos Cordon63a84242013-07-23 13:32:52 -0700203
Christine Chendaf7bf62013-08-05 19:12:31 -0700204 for (int i = 0; i < mListeners.size(); ++i) {
Christine Chenee09a492013-08-06 16:02:29 -0700205 if (call != null) {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700206 mListeners.get(i).onIncoming(call);
Christine Chenee09a492013-08-06 16:02:29 -0700207 }
Santos Cordon63a84242013-07-23 13:32:52 -0700208 }
209 }
210
211 private void onDisconnect(AsyncResult r) {
Santos Cordon2b73bd62013-08-27 14:53:43 -0700212 Log.i(TAG, "onDisconnect");
Santos Cordon63a84242013-07-23 13:32:52 -0700213 final Connection conn = (Connection) r.result;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700214 final Call call = getCallFromMap(mCallMap, conn, false);
Santos Cordon63a84242013-07-23 13:32:52 -0700215
Santos Cordon995c8162013-07-29 09:22:22 -0700216 if (call != null) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700217 final boolean wasConferenced = call.getState() == State.CONFERENCED;
218
219 updateCallFromConnection(call, conn, false);
220 call.setState(Call.State.DISCONNECTED);
Santos Cordon63a84242013-07-23 13:32:52 -0700221
Christine Chendaf7bf62013-08-05 19:12:31 -0700222 for (int i = 0; i < mListeners.size(); ++i) {
223 mListeners.get(i).onDisconnect(call);
Santos Cordon63a84242013-07-23 13:32:52 -0700224 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700225
226 // If it was a conferenced call, we need to run the entire update
227 // to make the proper changes to parent conference calls.
228 if (wasConferenced) {
229 onPhoneStateChanged(null);
230 }
231
232 mCallMap.remove(conn);
Santos Cordon63a84242013-07-23 13:32:52 -0700233 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700234
235 // TODO(klp): Do a final check to see if there are any active calls.
236 // If there are not, totally cancel all calls
Santos Cordon63a84242013-07-23 13:32:52 -0700237 }
238
Santos Cordona3d05142013-07-29 11:25:17 -0700239 /**
240 * Called when the phone state changes.
Santos Cordona3d05142013-07-29 11:25:17 -0700241 */
Santos Cordon995c8162013-07-29 09:22:22 -0700242 private void onPhoneStateChanged(AsyncResult r) {
Santos Cordon2b73bd62013-08-27 14:53:43 -0700243 Log.i(TAG, "onPhoneStateChanged: ");
Santos Cordon998f42b2013-08-02 16:13:12 -0700244 final List<Call> updatedCalls = Lists.newArrayList();
245 doUpdate(false, updatedCalls);
246
Christine Chendaf7bf62013-08-05 19:12:31 -0700247 for (int i = 0; i < mListeners.size(); ++i) {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700248 mListeners.get(i).onUpdate(updatedCalls);
Santos Cordon998f42b2013-08-02 16:13:12 -0700249 }
250 }
251
252
253 /**
254 * Go through the Calls from CallManager and return the list of calls that were updated.
255 * Or, the full list if requested.
256 */
257 private void doUpdate(boolean fullUpdate, List<Call> out) {
Santos Cordona3d05142013-07-29 11:25:17 -0700258 final List<com.android.internal.telephony.Call> telephonyCalls = Lists.newArrayList();
259 telephonyCalls.addAll(mCallManager.getRingingCalls());
260 telephonyCalls.addAll(mCallManager.getForegroundCalls());
261 telephonyCalls.addAll(mCallManager.getBackgroundCalls());
262
Santos Cordona3d05142013-07-29 11:25:17 -0700263 // Cycle through all the Connections on all the Calls. Update our Call objects
264 // to reflect any new state and send the updated Call objects to the handler service.
265 for (com.android.internal.telephony.Call telephonyCall : telephonyCalls) {
Santos Cordona3d05142013-07-29 11:25:17 -0700266
267 for (Connection connection : telephonyCall.getConnections()) {
Santos Cordon998f42b2013-08-02 16:13:12 -0700268 // new connections return a Call with INVALID state, which does not translate to
Santos Cordone38b1ff2013-08-07 12:12:16 -0700269 // a state in the internal.telephony.Call object. This ensures that staleness
270 // check below fails and we always add the item to the update list if it is new.
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700271 final Call call = getCallFromMap(mCallMap, connection, true);
Santos Cordona3d05142013-07-29 11:25:17 -0700272
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700273 boolean changed = updateCallFromConnection(call, connection, false);
Santos Cordon2b73bd62013-08-27 14:53:43 -0700274
275 Log.i(TAG, "doUpdate: " + call);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700276 if (fullUpdate || changed) {
Santos Cordon998f42b2013-08-02 16:13:12 -0700277 out.add(call);
Santos Cordona3d05142013-07-29 11:25:17 -0700278 }
279 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700280
281 // We do a second loop to address conference call scenarios. We do this as a separate
282 // loop to ensure all child calls are up to date before we start updating the parent
283 // conference calls.
284 for (Connection connection : telephonyCall.getConnections()) {
285 updateForConferenceCalls(connection, out);
286 }
287
Santos Cordona3d05142013-07-29 11:25:17 -0700288 }
Santos Cordona3d05142013-07-29 11:25:17 -0700289 }
290
Santos Cordone38b1ff2013-08-07 12:12:16 -0700291 /**
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700292 * Checks to see if the connection is the first connection in a conference call.
293 * If it is a conference call, we will create a new Conference Call object or
294 * update the existing conference call object for that connection.
295 * If it is not a conference call but a previous associated conference call still exists,
296 * we mark it as idle and remove it from the map.
297 * In both cases above, we add the Calls to be updated to the UI.
298 * @param connection The connection object to check.
299 * @param updatedCalls List of 'updated' calls that will be sent to the UI.
300 */
301 private boolean updateForConferenceCalls(Connection connection, List<Call> updatedCalls) {
302 // We consider this connection a conference connection if the call it
303 // belongs to is a multiparty call AND it is the first connection.
304 final boolean isConferenceCallConnection = isPartOfLiveConferenceCall(connection) &&
305 connection.getCall().getEarliestConnection() == connection;
306
307 boolean changed = false;
308
309 // If this connection is the main connection for the conference call, then create or update
310 // a Call object for that conference call.
311 if (isConferenceCallConnection) {
312 final Call confCall = getCallFromMap(mConfCallMap, connection, true);
313 changed = updateCallFromConnection(confCall, connection, true);
314
315 if (changed) {
316 updatedCalls.add(confCall);
317 }
318
319 if (DBG) Log.d(TAG, "Updating a conference call: " + confCall);
320
321 // It is possible that through a conference call split, there may be lingering conference
322 // calls where this connection was the main connection. We clean those up here.
323 } else {
324 final Call oldConfCall = getCallFromMap(mConfCallMap, connection, false);
325
326 // We found a conference call for this connection, which is no longer a conference call.
327 // Kill it!
328 if (oldConfCall != null) {
329 if (DBG) Log.d(TAG, "Cleaning up an old conference call: " + oldConfCall);
330 mConfCallMap.remove(connection);
331 oldConfCall.setState(State.IDLE);
332 changed = true;
333
334 // add to the list of calls to update
335 updatedCalls.add(oldConfCall);
336 }
337 }
338
339 return changed;
340 }
341
342 /**
Santos Cordon69a69192013-08-22 14:25:42 -0700343 * Sets the new call state onto the call and performs some additional logic
344 * associated with setting the state.
345 */
346 private void setNewState(Call call, int newState, Connection connection) {
347 Preconditions.checkState(call.getState() != newState);
348
349 // When starting an outgoing call, we need to grab gateway information
350 // for the call, if available, and set it.
351 final RawGatewayInfo info = mCallGatewayManager.getGatewayInfo(connection);
352
353 if (newState == Call.State.DIALING) {
354 if (!info.isEmpty()) {
355 call.setGatewayNumber(info.getFormattedGatewayNumber());
356 call.setGatewayPackage(info.packageName);
357 }
358 } else if (!Call.State.isConnected(newState)) {
359 mCallGatewayManager.clearGatewayData(connection);
360 }
361
362 call.setState(newState);
363 }
364
365 /**
Santos Cordone38b1ff2013-08-07 12:12:16 -0700366 * Updates the Call properties to match the state of the connection object
367 * that it represents.
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700368 * @param call The call object to update.
369 * @param connection The connection object from which to update call.
370 * @param isForConference There are slight differences in how we populate data for conference
371 * calls. This boolean tells us which method to use.
Santos Cordone38b1ff2013-08-07 12:12:16 -0700372 */
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700373 private boolean updateCallFromConnection(Call call, Connection connection,
374 boolean isForConference) {
Santos Cordone38b1ff2013-08-07 12:12:16 -0700375 boolean changed = false;
376
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700377 final int newState = translateStateFromTelephony(connection, isForConference);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700378
379 if (call.getState() != newState) {
Santos Cordon69a69192013-08-22 14:25:42 -0700380 setNewState(call, newState, connection);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700381 changed = true;
382 }
383
Santos Cordone38b1ff2013-08-07 12:12:16 -0700384 final Call.DisconnectCause newDisconnectCause =
385 translateDisconnectCauseFromTelephony(connection.getDisconnectCause());
386 if (call.getDisconnectCause() != newDisconnectCause) {
387 call.setDisconnectCause(newDisconnectCause);
388 changed = true;
389 }
390
Santos Cordonbbe8ecf2013-08-13 15:26:18 -0700391 final long oldConnectTime = call.getConnectTime();
392 if (oldConnectTime != connection.getConnectTime()) {
393 call.setConnectTime(connection.getConnectTime());
394 changed = true;
395 }
396
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700397 if (!isForConference) {
Santos Cordon69a69192013-08-22 14:25:42 -0700398 // Number
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700399 final String oldNumber = call.getNumber();
Santos Cordon69a69192013-08-22 14:25:42 -0700400 String newNumber = connection.getAddress();
401 RawGatewayInfo info = mCallGatewayManager.getGatewayInfo(connection);
402 if (!info.isEmpty()) {
403 newNumber = info.trueNumber;
404 }
405 if (TextUtils.isEmpty(oldNumber) || !oldNumber.equals(newNumber)) {
406 call.setNumber(newNumber);
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700407 changed = true;
408 }
409
Santos Cordon69a69192013-08-22 14:25:42 -0700410 // Number presentation
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700411 final int newNumberPresentation = connection.getNumberPresentation();
412 if (call.getNumberPresentation() != newNumberPresentation) {
413 call.setNumberPresentation(newNumberPresentation);
414 changed = true;
415 }
416
Santos Cordon69a69192013-08-22 14:25:42 -0700417 // Name
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700418 final String oldCnapName = call.getCnapName();
419 if (TextUtils.isEmpty(oldCnapName) || !oldCnapName.equals(connection.getCnapName())) {
420 call.setCnapName(connection.getCnapName());
421 changed = true;
422 }
Santos Cordon69a69192013-08-22 14:25:42 -0700423
424 // Name Presentation
425 final int newCnapNamePresentation = connection.getCnapNamePresentation();
426 if (call.getCnapNamePresentation() != newCnapNamePresentation) {
427 call.setCnapNamePresentation(newCnapNamePresentation);
428 changed = true;
429 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700430 } else {
431
432 // update the list of children by:
433 // 1) Saving the old set
434 // 2) Removing all children
435 // 3) Adding the correct children into the Call
436 // 4) Comparing the new children set with the old children set
437 ImmutableSortedSet<Integer> oldSet = call.getChildCallIds();
438 call.removeAllChildren();
439
440 if (connection.getCall() != null) {
441 for (Connection childConn : connection.getCall().getConnections()) {
442 final Call childCall = getCallFromMap(mCallMap, childConn, false);
443 if (childCall != null && childConn.isAlive()) {
444 call.addChildId(childCall.getCallId());
445 }
446 }
447 }
448 changed |= oldSet.equals(call.getChildCallIds());
449 }
450
Santos Cordoneead6ec2013-08-07 22:16:33 -0700451 /**
452 * !!! Uses values from connection and call collected above so this part must be last !!!
453 */
454 final int newCapabilities = getCapabilitiesFor(connection, call);
Santos Cordon26e7b242013-08-07 21:15:45 -0700455 if (call.getCapabilities() != newCapabilities) {
456 call.setCapabilities(newCapabilities);
457 changed = true;
458 }
459
Santos Cordone38b1ff2013-08-07 12:12:16 -0700460 return changed;
461 }
462
Santos Cordon26e7b242013-08-07 21:15:45 -0700463 /**
464 * Returns a mask of capabilities for the connection such as merge, hold, etc.
465 */
Santos Cordoneead6ec2013-08-07 22:16:33 -0700466 private int getCapabilitiesFor(Connection connection, Call call) {
467 final boolean callIsActive = (call.getState() == Call.State.ACTIVE);
468 final Phone phone = connection.getCall().getPhone();
469
470 final boolean canHold = TelephonyCapabilities.supportsAnswerAndHold(phone);
471 boolean canAddCall = false;
472 boolean canMergeCall = false;
473 boolean canSwapCall = false;
474
475 // only applies to active calls
476 if (callIsActive) {
477 canAddCall = PhoneUtils.okToAddCall(mCallManager);
478 canMergeCall = PhoneUtils.okToMergeCalls(mCallManager);
479 canSwapCall = PhoneUtils.okToSwapCalls(mCallManager);
480 }
481
482 // special rules section!
483 // CDMA always has Add
484 if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
485 canAddCall = true;
486 } else {
487 // if neither merge nor add is on...then allow add
488 canAddCall |= !(canAddCall || canMergeCall);
489 }
490
Santos Cordon26e7b242013-08-07 21:15:45 -0700491 int retval = 0x0;
Santos Cordoneead6ec2013-08-07 22:16:33 -0700492 if (canHold) {
Santos Cordon26e7b242013-08-07 21:15:45 -0700493 retval |= Capabilities.HOLD;
494 }
Santos Cordoneead6ec2013-08-07 22:16:33 -0700495 if (canAddCall) {
496 retval |= Capabilities.ADD_CALL;
497 }
498 if (canMergeCall) {
499 retval |= Capabilities.MERGE_CALLS;
500 }
501 if (canSwapCall) {
502 retval |= Capabilities.SWAP_CALLS;
503 }
Santos Cordon26e7b242013-08-07 21:15:45 -0700504
505 return retval;
506 }
507
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700508 /**
509 * Returns true if the Connection is part of a multiparty call.
510 * We do this by checking the isMultiparty() method of the telephony.Call object and also
511 * checking to see if more than one of it's children is alive.
512 */
513 private boolean isPartOfLiveConferenceCall(Connection connection) {
514 if (connection.getCall() != null && connection.getCall().isMultiparty()) {
515 int count = 0;
516 for (Connection currConn : connection.getCall().getConnections()) {
517 if (currConn.isAlive()) {
518 count++;
519 if (count >= 2) {
520 return true;
521 }
522 }
523 }
524 }
525 return false;
526 }
527
528 private int translateStateFromTelephony(Connection connection, boolean isForConference) {
529
Santos Cordona3d05142013-07-29 11:25:17 -0700530 int retval = State.IDLE;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700531 switch (connection.getState()) {
Santos Cordona3d05142013-07-29 11:25:17 -0700532 case ACTIVE:
533 retval = State.ACTIVE;
534 break;
535 case INCOMING:
536 retval = State.INCOMING;
537 break;
538 case DIALING:
539 case ALERTING:
540 retval = State.DIALING;
541 break;
542 case WAITING:
543 retval = State.CALL_WAITING;
544 break;
545 case HOLDING:
546 retval = State.ONHOLD;
547 break;
Santos Cordone38b1ff2013-08-07 12:12:16 -0700548 case DISCONNECTED:
549 case DISCONNECTING:
550 retval = State.DISCONNECTED;
Santos Cordona3d05142013-07-29 11:25:17 -0700551 default:
552 }
553
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700554 // If we are dealing with a potential child call (not the parent conference call),
555 // the check to see if we have to set the state to CONFERENCED.
556 if (!isForConference) {
557
558 // if the connection is part of a multiparty call, and it is live,
559 // annotate it with CONFERENCED state instead.
560 if (isPartOfLiveConferenceCall(connection) && connection.isAlive()) {
561 return State.CONFERENCED;
562 }
563 }
564
Santos Cordona3d05142013-07-29 11:25:17 -0700565 return retval;
Santos Cordon995c8162013-07-29 09:22:22 -0700566 }
567
Santos Cordone38b1ff2013-08-07 12:12:16 -0700568 private final ImmutableMap<Connection.DisconnectCause, Call.DisconnectCause> CAUSE_MAP =
569 ImmutableMap.<Connection.DisconnectCause, Call.DisconnectCause>builder()
570 .put(Connection.DisconnectCause.BUSY, Call.DisconnectCause.BUSY)
571 .put(Connection.DisconnectCause.CALL_BARRED, Call.DisconnectCause.CALL_BARRED)
572 .put(Connection.DisconnectCause.CDMA_ACCESS_BLOCKED,
573 Call.DisconnectCause.CDMA_ACCESS_BLOCKED)
574 .put(Connection.DisconnectCause.CDMA_ACCESS_FAILURE,
575 Call.DisconnectCause.CDMA_ACCESS_FAILURE)
576 .put(Connection.DisconnectCause.CDMA_DROP, Call.DisconnectCause.CDMA_DROP)
577 .put(Connection.DisconnectCause.CDMA_INTERCEPT, Call.DisconnectCause.CDMA_INTERCEPT)
578 .put(Connection.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE,
579 Call.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE)
580 .put(Connection.DisconnectCause.CDMA_NOT_EMERGENCY,
581 Call.DisconnectCause.CDMA_NOT_EMERGENCY)
582 .put(Connection.DisconnectCause.CDMA_PREEMPTED, Call.DisconnectCause.CDMA_PREEMPTED)
583 .put(Connection.DisconnectCause.CDMA_REORDER, Call.DisconnectCause.CDMA_REORDER)
584 .put(Connection.DisconnectCause.CDMA_RETRY_ORDER,
585 Call.DisconnectCause.CDMA_RETRY_ORDER)
586 .put(Connection.DisconnectCause.CDMA_SO_REJECT, Call.DisconnectCause.CDMA_SO_REJECT)
587 .put(Connection.DisconnectCause.CONGESTION, Call.DisconnectCause.CONGESTION)
588 .put(Connection.DisconnectCause.CS_RESTRICTED, Call.DisconnectCause.CS_RESTRICTED)
589 .put(Connection.DisconnectCause.CS_RESTRICTED_EMERGENCY,
590 Call.DisconnectCause.CS_RESTRICTED_EMERGENCY)
591 .put(Connection.DisconnectCause.CS_RESTRICTED_NORMAL,
592 Call.DisconnectCause.CS_RESTRICTED_NORMAL)
593 .put(Connection.DisconnectCause.ERROR_UNSPECIFIED,
594 Call.DisconnectCause.ERROR_UNSPECIFIED)
595 .put(Connection.DisconnectCause.FDN_BLOCKED, Call.DisconnectCause.FDN_BLOCKED)
596 .put(Connection.DisconnectCause.ICC_ERROR, Call.DisconnectCause.ICC_ERROR)
597 .put(Connection.DisconnectCause.INCOMING_MISSED,
598 Call.DisconnectCause.INCOMING_MISSED)
599 .put(Connection.DisconnectCause.INCOMING_REJECTED,
600 Call.DisconnectCause.INCOMING_REJECTED)
601 .put(Connection.DisconnectCause.INVALID_CREDENTIALS,
602 Call.DisconnectCause.INVALID_CREDENTIALS)
603 .put(Connection.DisconnectCause.INVALID_NUMBER,
604 Call.DisconnectCause.INVALID_NUMBER)
605 .put(Connection.DisconnectCause.LIMIT_EXCEEDED, Call.DisconnectCause.LIMIT_EXCEEDED)
606 .put(Connection.DisconnectCause.LOCAL, Call.DisconnectCause.LOCAL)
607 .put(Connection.DisconnectCause.LOST_SIGNAL, Call.DisconnectCause.LOST_SIGNAL)
608 .put(Connection.DisconnectCause.MMI, Call.DisconnectCause.MMI)
609 .put(Connection.DisconnectCause.NORMAL, Call.DisconnectCause.NORMAL)
610 .put(Connection.DisconnectCause.NOT_DISCONNECTED,
611 Call.DisconnectCause.NOT_DISCONNECTED)
612 .put(Connection.DisconnectCause.NUMBER_UNREACHABLE,
613 Call.DisconnectCause.NUMBER_UNREACHABLE)
614 .put(Connection.DisconnectCause.OUT_OF_NETWORK, Call.DisconnectCause.OUT_OF_NETWORK)
615 .put(Connection.DisconnectCause.OUT_OF_SERVICE, Call.DisconnectCause.OUT_OF_SERVICE)
616 .put(Connection.DisconnectCause.POWER_OFF, Call.DisconnectCause.POWER_OFF)
617 .put(Connection.DisconnectCause.SERVER_ERROR, Call.DisconnectCause.SERVER_ERROR)
618 .put(Connection.DisconnectCause.SERVER_UNREACHABLE,
619 Call.DisconnectCause.SERVER_UNREACHABLE)
620 .put(Connection.DisconnectCause.TIMED_OUT, Call.DisconnectCause.TIMED_OUT)
621 .put(Connection.DisconnectCause.UNOBTAINABLE_NUMBER,
622 Call.DisconnectCause.UNOBTAINABLE_NUMBER)
623 .build();
624
625 private Call.DisconnectCause translateDisconnectCauseFromTelephony(
626 Connection.DisconnectCause causeSource) {
627
628 if (CAUSE_MAP.containsKey(causeSource)) {
629 return CAUSE_MAP.get(causeSource);
630 }
631
632 return Call.DisconnectCause.UNKNOWN;
633 }
634
Santos Cordon63a84242013-07-23 13:32:52 -0700635 /**
Santos Cordone38b1ff2013-08-07 12:12:16 -0700636 * Gets an existing callId for a connection, or creates one if none exists.
637 * This function does NOT set any of the Connection data onto the Call class.
638 * A separate call to updateCallFromConnection must be made for that purpose.
Santos Cordon63a84242013-07-23 13:32:52 -0700639 */
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700640 private Call getCallFromMap(HashMap<Connection, Call> map, Connection conn,
641 boolean createIfMissing) {
Santos Cordon995c8162013-07-29 09:22:22 -0700642 Call call = null;
Santos Cordon63a84242013-07-23 13:32:52 -0700643
644 // Find the call id or create if missing and requested.
645 if (conn != null) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700646 if (map.containsKey(conn)) {
647 call = map.get(conn);
Santos Cordon63a84242013-07-23 13:32:52 -0700648 } else if (createIfMissing) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700649 call = createNewCall();
650 map.put(conn, call);
Santos Cordon63a84242013-07-23 13:32:52 -0700651 }
652 }
Santos Cordon995c8162013-07-29 09:22:22 -0700653 return call;
Santos Cordon63a84242013-07-23 13:32:52 -0700654 }
655
656 /**
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700657 * Creates a brand new connection for the call.
658 */
659 private Call createNewCall() {
660 int callId;
661 int newNextCallId;
662 do {
663 callId = mNextCallId.get();
664
665 // protect against overflow
666 newNextCallId = (callId == Integer.MAX_VALUE ?
667 CALL_ID_START_VALUE : callId + 1);
668
669 // Keep looping if the change was not atomic OR the value is already taken.
670 // The call to containsValue() is linear, however, most devices support a
671 // maximum of 7 connections so it's not expensive.
672 } while (!mNextCallId.compareAndSet(callId, newNextCallId));
673
674 return new Call(callId);
675 }
676
677 /**
Santos Cordon63a84242013-07-23 13:32:52 -0700678 * Listener interface for changes to Calls.
679 */
680 public interface Listener {
Santos Cordon995c8162013-07-29 09:22:22 -0700681 void onDisconnect(Call call);
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700682 void onIncoming(Call call);
683 void onUpdate(List<Call> calls);
Santos Cordon63a84242013-07-23 13:32:52 -0700684 }
Santos Cordon249efd02013-08-05 03:33:56 -0700685
686 /**
687 * Result class for accessing a call by connection.
688 */
689 public static class CallResult {
690 public Call mCall;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700691 public Call mActionableCall;
Santos Cordon249efd02013-08-05 03:33:56 -0700692 public Connection mConnection;
693
694 private CallResult(Call call, Connection connection) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700695 this(call, call, connection);
696 }
697
698 private CallResult(Call call, Call actionableCall, Connection connection) {
Santos Cordon249efd02013-08-05 03:33:56 -0700699 mCall = call;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700700 mActionableCall = actionableCall;
Santos Cordon249efd02013-08-05 03:33:56 -0700701 mConnection = connection;
702 }
703
704 public Call getCall() {
705 return mCall;
706 }
707
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700708 // The call that should be used for call actions like hanging up.
709 public Call getActionableCall() {
710 return mActionableCall;
711 }
712
Santos Cordon249efd02013-08-05 03:33:56 -0700713 public Connection getConnection() {
714 return mConnection;
715 }
716 }
Santos Cordon63a84242013-07-23 13:32:52 -0700717}