blob: 52a7ef8cc5cae697ab90ebf6fd196cc5fbddaf17 [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
Santos Cordon63a84242013-07-23 13:32:52 -070019import android.os.AsyncResult;
20import android.os.Handler;
21import android.os.Message;
Santos Cordon4ad64cd2013-08-15 00:36:14 -070022import android.os.SystemProperties;
Christine Chenaf2fd0a2013-09-13 16:27:40 -070023import android.telephony.PhoneNumberUtils;
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 Cordon69a69192013-08-22 14:25:42 -070031import com.android.phone.CallGatewayManager.RawGatewayInfo;
Santos Cordon995c8162013-07-29 09:22:22 -070032import com.android.services.telephony.common.Call;
Santos Cordon26e7b242013-08-07 21:15:45 -070033import com.android.services.telephony.common.Call.Capabilities;
Santos Cordona3d05142013-07-29 11:25:17 -070034import com.android.services.telephony.common.Call.State;
Santos Cordon63a84242013-07-23 13:32:52 -070035
Yorke Lee814da302013-08-30 16:01:07 -070036import com.google.android.collect.Lists;
37import com.google.android.collect.Maps;
38import com.google.common.base.Preconditions;
39import com.google.common.collect.ImmutableMap;
40import com.google.common.collect.ImmutableSortedSet;
41
Santos Cordon63a84242013-07-23 13:32:52 -070042import java.util.ArrayList;
43import java.util.HashMap;
44import java.util.List;
Santos Cordon249efd02013-08-05 03:33:56 -070045import java.util.Map.Entry;
Santos Cordon63a84242013-07-23 13:32:52 -070046import java.util.concurrent.atomic.AtomicInteger;
47
48/**
49 * Creates a Call model from Call state and data received from the telephony
50 * layer. The telephony layer maintains 3 conceptual objects: Phone, Call,
51 * Connection.
52 *
53 * Phone represents the radio and there is an implementation per technology
54 * type such as GSMPhone, SipPhone, CDMAPhone, etc. Generally, we will only ever
55 * deal with one instance of this object for the lifetime of this class.
56 *
57 * There are 3 Call instances that exist for the lifetime of this class which
58 * are created by CallTracker. The three are RingingCall, ForegroundCall, and
59 * BackgroundCall.
60 *
61 * A Connection most closely resembles what the layperson would consider a call.
62 * A Connection is created when a user dials and it is "owned" by one of the
63 * three Call instances. Which of the three Calls owns the Connection changes
64 * as the Connection goes between ACTIVE, HOLD, RINGING, and other states.
65 *
66 * This class models a new Call class from Connection objects received from
67 * the telephony layer. We use Connection references as identifiers for a call;
68 * new reference = new call.
69 *
70 * TODO(klp): Create a new Call class to replace the simple call Id ints
71 * being used currently.
72 *
73 * The new Call models are parcellable for transfer via the CallHandlerService
74 * API.
75 */
76public class CallModeler extends Handler {
77
78 private static final String TAG = CallModeler.class.getSimpleName();
Santos Cordon4ad64cd2013-08-15 00:36:14 -070079 private static final boolean DBG =
80 (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
Santos Cordon63a84242013-07-23 13:32:52 -070081
82 private static final int CALL_ID_START_VALUE = 1;
Santos Cordon63a84242013-07-23 13:32:52 -070083
Santos Cordon998f42b2013-08-02 16:13:12 -070084 private final CallStateMonitor mCallStateMonitor;
85 private final CallManager mCallManager;
Santos Cordon69a69192013-08-22 14:25:42 -070086 private final CallGatewayManager mCallGatewayManager;
Santos Cordon998f42b2013-08-02 16:13:12 -070087 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,
Santos Cordon69a69192013-08-22 14:25:42 -070094 RejectWithTextMessageManager rejectWithTextMessageManager,
95 CallGatewayManager callGatewayManager) {
Santos Cordon63a84242013-07-23 13:32:52 -070096 mCallStateMonitor = callStateMonitor;
Santos Cordona3d05142013-07-29 11:25:17 -070097 mCallManager = callManager;
Christine Chenee09a492013-08-06 16:02:29 -070098 mRejectWithTextMessageManager = rejectWithTextMessageManager;
Santos Cordon69a69192013-08-22 14:25:42 -070099 mCallGatewayManager = callGatewayManager;
Santos Cordon63a84242013-07-23 13:32:52 -0700100
101 mCallStateMonitor.addListener(this);
102 }
103
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700104 @Override
Santos Cordon63a84242013-07-23 13:32:52 -0700105 public void handleMessage(Message msg) {
106 switch(msg.what) {
107 case CallStateMonitor.PHONE_NEW_RINGING_CONNECTION:
108 onNewRingingConnection((AsyncResult) msg.obj);
109 break;
110 case CallStateMonitor.PHONE_DISCONNECT:
111 onDisconnect((AsyncResult) msg.obj);
Santos Cordon995c8162013-07-29 09:22:22 -0700112 break;
113 case CallStateMonitor.PHONE_STATE_CHANGED:
114 onPhoneStateChanged((AsyncResult) msg.obj);
115 break;
Chiao Cheng3f015c92013-09-06 15:56:27 -0700116 case CallStateMonitor.PHONE_ON_DIAL_CHARS:
117 onPostDialChars((AsyncResult) msg.obj, (char) msg.arg1);
118 break;
Santos Cordon63a84242013-07-23 13:32:52 -0700119 default:
120 break;
121 }
122 }
123
Christine Chendaf7bf62013-08-05 19:12:31 -0700124 public void addListener(Listener listener) {
Santos Cordon63a84242013-07-23 13:32:52 -0700125 Preconditions.checkNotNull(listener);
Christine Chendaf7bf62013-08-05 19:12:31 -0700126 Preconditions.checkNotNull(mListeners);
Christine Chen4748abd2013-08-07 15:44:15 -0700127 if (!mListeners.contains(listener)) {
128 mListeners.add(listener);
129 }
Santos Cordon998f42b2013-08-02 16:13:12 -0700130 }
131
132 public List<Call> getFullList() {
133 final List<Call> retval = Lists.newArrayList();
134 doUpdate(true, retval);
135 return retval;
Santos Cordon63a84242013-07-23 13:32:52 -0700136 }
137
Santos Cordon249efd02013-08-05 03:33:56 -0700138 public CallResult getCallWithId(int callId) {
139 // max 8 connections, so this should be fast even through we are traversing the entire map.
140 for (Entry<Connection, Call> entry : mCallMap.entrySet()) {
141 if (entry.getValue().getCallId() == callId) {
142 return new CallResult(entry.getValue(), entry.getKey());
143 }
144 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700145
146 for (Entry<Connection, Call> entry : mConfCallMap.entrySet()) {
147 if (entry.getValue().getCallId() == callId) {
148 if (entry.getValue().getChildCallIds().size() == 0) {
149 return null;
150 }
151 final CallResult child = getCallWithId(entry.getValue().getChildCallIds().first());
152 return new CallResult(entry.getValue(), child.getActionableCall(),
153 child.getConnection());
154 }
155 }
Santos Cordon249efd02013-08-05 03:33:56 -0700156 return null;
157 }
158
Santos Cordonaf763a12013-08-19 20:04:58 -0700159 public boolean hasLiveCall() {
160 return hasLiveCallInternal(mCallMap) ||
161 hasLiveCallInternal(mConfCallMap);
162 }
163
164 private boolean hasLiveCallInternal(HashMap<Connection, Call> map) {
165 for (Call call : map.values()) {
166 final int state = call.getState();
167 if (state == Call.State.ACTIVE ||
168 state == Call.State.CALL_WAITING ||
169 state == Call.State.CONFERENCED ||
170 state == Call.State.DIALING ||
171 state == Call.State.INCOMING ||
172 state == Call.State.ONHOLD) {
173 return true;
174 }
175 }
176 return false;
177 }
178
Santos Cordon2b73bd62013-08-27 14:53:43 -0700179 public boolean hasOutstandingActiveOrDialingCall() {
180 return hasOutstandingActiveOrDialingCallInternal(mCallMap) ||
181 hasOutstandingActiveOrDialingCallInternal(mConfCallMap);
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700182 }
183
Santos Cordon2b73bd62013-08-27 14:53:43 -0700184 private static boolean hasOutstandingActiveOrDialingCallInternal(
185 HashMap<Connection, Call> map) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700186 for (Call call : map.values()) {
187 final int state = call.getState();
Santos Cordon2b73bd62013-08-27 14:53:43 -0700188 if (state == Call.State.ACTIVE ||
189 state == Call.State.DIALING) {
Santos Cordon2eaff902013-08-05 04:37:55 -0700190 return true;
191 }
192 }
193
194 return false;
195 }
196
Chiao Cheng3f015c92013-09-06 15:56:27 -0700197
198 /**
199 * Handles the POST_ON_DIAL_CHARS message from the Phone (see our call to
200 * mPhone.setOnPostDialCharacter() above.)
201 *
202 * TODO: NEED TO TEST THIS SEQUENCE now that we no longer handle "dialable" key events here in
203 * the InCallScreen: we do directly to the Dialer UI instead. Similarly, we may now need to go
204 * directly to the Dialer to handle POST_ON_DIAL_CHARS too.
205 */
206 private void onPostDialChars(AsyncResult r, char ch) {
207 final Connection c = (Connection) r.result;
208
209 if (c != null) {
210 final Connection.PostDialState state = (Connection.PostDialState) r.userObj;
211
212 switch (state) {
213 // TODO(klp): add other post dial related functions
214 case WAIT:
215 final Call call = getCallFromMap(mCallMap, c, false);
216 if (call == null) {
217 Log.i(TAG, "Call no longer exists. Skipping onPostDialWait().");
218 } else {
219 for (Listener mListener : mListeners) {
220 mListener.onPostDialWait(call.getCallId(),
221 c.getRemainingPostDialString());
222 }
223 }
224 break;
225
226 default:
227 break;
228 }
229 }
230 }
231
Santos Cordon63a84242013-07-23 13:32:52 -0700232 private void onNewRingingConnection(AsyncResult r) {
Santos Cordon2b73bd62013-08-27 14:53:43 -0700233 Log.i(TAG, "onNewRingingConnection");
Santos Cordon63a84242013-07-23 13:32:52 -0700234 final Connection conn = (Connection) r.result;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700235 final Call call = getCallFromMap(mCallMap, conn, true);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700236
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700237 updateCallFromConnection(call, conn, false);
Santos Cordon63a84242013-07-23 13:32:52 -0700238
Christine Chendaf7bf62013-08-05 19:12:31 -0700239 for (int i = 0; i < mListeners.size(); ++i) {
Christine Chenee09a492013-08-06 16:02:29 -0700240 if (call != null) {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700241 mListeners.get(i).onIncoming(call);
Christine Chenee09a492013-08-06 16:02:29 -0700242 }
Santos Cordon63a84242013-07-23 13:32:52 -0700243 }
244 }
245
246 private void onDisconnect(AsyncResult r) {
Santos Cordon2b73bd62013-08-27 14:53:43 -0700247 Log.i(TAG, "onDisconnect");
Santos Cordon63a84242013-07-23 13:32:52 -0700248 final Connection conn = (Connection) r.result;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700249 final Call call = getCallFromMap(mCallMap, conn, false);
Santos Cordon63a84242013-07-23 13:32:52 -0700250
Santos Cordon995c8162013-07-29 09:22:22 -0700251 if (call != null) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700252 final boolean wasConferenced = call.getState() == State.CONFERENCED;
253
254 updateCallFromConnection(call, conn, false);
Santos Cordon63a84242013-07-23 13:32:52 -0700255
Christine Chendaf7bf62013-08-05 19:12:31 -0700256 for (int i = 0; i < mListeners.size(); ++i) {
257 mListeners.get(i).onDisconnect(call);
Santos Cordon63a84242013-07-23 13:32:52 -0700258 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700259
260 // If it was a conferenced call, we need to run the entire update
261 // to make the proper changes to parent conference calls.
262 if (wasConferenced) {
263 onPhoneStateChanged(null);
264 }
265
266 mCallMap.remove(conn);
Santos Cordon63a84242013-07-23 13:32:52 -0700267 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700268
269 // TODO(klp): Do a final check to see if there are any active calls.
270 // If there are not, totally cancel all calls
Santos Cordon63a84242013-07-23 13:32:52 -0700271 }
272
Santos Cordona3d05142013-07-29 11:25:17 -0700273 /**
274 * Called when the phone state changes.
Santos Cordona3d05142013-07-29 11:25:17 -0700275 */
Santos Cordon995c8162013-07-29 09:22:22 -0700276 private void onPhoneStateChanged(AsyncResult r) {
Santos Cordon2b73bd62013-08-27 14:53:43 -0700277 Log.i(TAG, "onPhoneStateChanged: ");
Santos Cordon998f42b2013-08-02 16:13:12 -0700278 final List<Call> updatedCalls = Lists.newArrayList();
279 doUpdate(false, updatedCalls);
280
Santos Cordon71d5c6e2013-09-05 21:34:33 -0700281 if (updatedCalls.size() > 0) {
282 for (int i = 0; i < mListeners.size(); ++i) {
283 mListeners.get(i).onUpdate(updatedCalls);
284 }
Santos Cordon998f42b2013-08-02 16:13:12 -0700285 }
286 }
287
288
289 /**
290 * Go through the Calls from CallManager and return the list of calls that were updated.
291 * Or, the full list if requested.
292 */
293 private void doUpdate(boolean fullUpdate, List<Call> out) {
Santos Cordona3d05142013-07-29 11:25:17 -0700294 final List<com.android.internal.telephony.Call> telephonyCalls = Lists.newArrayList();
295 telephonyCalls.addAll(mCallManager.getRingingCalls());
296 telephonyCalls.addAll(mCallManager.getForegroundCalls());
297 telephonyCalls.addAll(mCallManager.getBackgroundCalls());
298
Santos Cordona3d05142013-07-29 11:25:17 -0700299 // Cycle through all the Connections on all the Calls. Update our Call objects
300 // to reflect any new state and send the updated Call objects to the handler service.
301 for (com.android.internal.telephony.Call telephonyCall : telephonyCalls) {
Santos Cordona3d05142013-07-29 11:25:17 -0700302
303 for (Connection connection : telephonyCall.getConnections()) {
Santos Cordon12a03aa2013-09-12 23:34:05 -0700304 // We only send updates for live calls which are not incoming (ringing).
305 // Disconnected and incoming calls are handled by onDisconnect and
306 // onNewRingingConnection.
307 boolean shouldUpdate = connection.getState().isAlive() &&
Santos Cordon71d5c6e2013-09-05 21:34:33 -0700308 !connection.getState().isRinging();
309
310 // New connections return a Call with INVALID state, which does not translate to
Santos Cordone38b1ff2013-08-07 12:12:16 -0700311 // a state in the internal.telephony.Call object. This ensures that staleness
312 // check below fails and we always add the item to the update list if it is new.
Santos Cordon12a03aa2013-09-12 23:34:05 -0700313 final Call call = getCallFromMap(mCallMap, connection, shouldUpdate /* create */);
Santos Cordon71d5c6e2013-09-05 21:34:33 -0700314
Santos Cordon12a03aa2013-09-12 23:34:05 -0700315 if (call == null || !shouldUpdate) {
Santos Cordon71d5c6e2013-09-05 21:34:33 -0700316 continue;
317 }
Santos Cordona3d05142013-07-29 11:25:17 -0700318
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700319 boolean changed = updateCallFromConnection(call, connection, false);
Santos Cordon2b73bd62013-08-27 14:53:43 -0700320
Santos Cordone38b1ff2013-08-07 12:12:16 -0700321 if (fullUpdate || changed) {
Santos Cordon998f42b2013-08-02 16:13:12 -0700322 out.add(call);
Santos Cordona3d05142013-07-29 11:25:17 -0700323 }
324 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700325
326 // We do a second loop to address conference call scenarios. We do this as a separate
327 // loop to ensure all child calls are up to date before we start updating the parent
328 // conference calls.
329 for (Connection connection : telephonyCall.getConnections()) {
330 updateForConferenceCalls(connection, out);
331 }
332
Santos Cordona3d05142013-07-29 11:25:17 -0700333 }
Santos Cordona3d05142013-07-29 11:25:17 -0700334 }
335
Santos Cordone38b1ff2013-08-07 12:12:16 -0700336 /**
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700337 * Checks to see if the connection is the first connection in a conference call.
338 * If it is a conference call, we will create a new Conference Call object or
339 * update the existing conference call object for that connection.
340 * If it is not a conference call but a previous associated conference call still exists,
341 * we mark it as idle and remove it from the map.
342 * In both cases above, we add the Calls to be updated to the UI.
343 * @param connection The connection object to check.
344 * @param updatedCalls List of 'updated' calls that will be sent to the UI.
345 */
346 private boolean updateForConferenceCalls(Connection connection, List<Call> updatedCalls) {
347 // We consider this connection a conference connection if the call it
Yorke Leecd3f9692013-09-14 13:51:27 -0700348 // belongs to is a multiparty call AND it is the first live connection.
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700349 final boolean isConferenceCallConnection = isPartOfLiveConferenceCall(connection) &&
Yorke Leecd3f9692013-09-14 13:51:27 -0700350 getEarliestLiveConnection(connection.getCall()) == connection;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700351
352 boolean changed = false;
353
354 // If this connection is the main connection for the conference call, then create or update
355 // a Call object for that conference call.
356 if (isConferenceCallConnection) {
357 final Call confCall = getCallFromMap(mConfCallMap, connection, true);
358 changed = updateCallFromConnection(confCall, connection, true);
359
360 if (changed) {
361 updatedCalls.add(confCall);
362 }
363
364 if (DBG) Log.d(TAG, "Updating a conference call: " + confCall);
365
366 // It is possible that through a conference call split, there may be lingering conference
367 // calls where this connection was the main connection. We clean those up here.
368 } else {
369 final Call oldConfCall = getCallFromMap(mConfCallMap, connection, false);
370
371 // We found a conference call for this connection, which is no longer a conference call.
372 // Kill it!
373 if (oldConfCall != null) {
374 if (DBG) Log.d(TAG, "Cleaning up an old conference call: " + oldConfCall);
375 mConfCallMap.remove(connection);
376 oldConfCall.setState(State.IDLE);
377 changed = true;
378
379 // add to the list of calls to update
380 updatedCalls.add(oldConfCall);
381 }
382 }
383
384 return changed;
385 }
386
Yorke Leecd3f9692013-09-14 13:51:27 -0700387 private Connection getEarliestLiveConnection(com.android.internal.telephony.Call call) {
388 final List<Connection> connections = call.getConnections();
389 final int size = connections.size();
390 Connection earliestConn = null;
391 long earliestTime = Long.MAX_VALUE;
392 for (int i = 0; i < size; i++) {
393 final Connection connection = connections.get(i);
394 if (!connection.isAlive()) continue;
395 final long time = connection.getCreateTime();
396 if (time < earliestTime) {
397 earliestTime = time;
398 earliestConn = connection;
399 }
400 }
401 return earliestConn;
402 }
403
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700404 /**
Santos Cordon69a69192013-08-22 14:25:42 -0700405 * Sets the new call state onto the call and performs some additional logic
406 * associated with setting the state.
407 */
408 private void setNewState(Call call, int newState, Connection connection) {
409 Preconditions.checkState(call.getState() != newState);
410
411 // When starting an outgoing call, we need to grab gateway information
412 // for the call, if available, and set it.
413 final RawGatewayInfo info = mCallGatewayManager.getGatewayInfo(connection);
414
415 if (newState == Call.State.DIALING) {
416 if (!info.isEmpty()) {
417 call.setGatewayNumber(info.getFormattedGatewayNumber());
418 call.setGatewayPackage(info.packageName);
419 }
420 } else if (!Call.State.isConnected(newState)) {
421 mCallGatewayManager.clearGatewayData(connection);
422 }
423
424 call.setState(newState);
425 }
426
427 /**
Santos Cordone38b1ff2013-08-07 12:12:16 -0700428 * Updates the Call properties to match the state of the connection object
429 * that it represents.
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700430 * @param call The call object to update.
431 * @param connection The connection object from which to update call.
432 * @param isForConference There are slight differences in how we populate data for conference
433 * calls. This boolean tells us which method to use.
Santos Cordone38b1ff2013-08-07 12:12:16 -0700434 */
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700435 private boolean updateCallFromConnection(Call call, Connection connection,
436 boolean isForConference) {
Santos Cordone38b1ff2013-08-07 12:12:16 -0700437 boolean changed = false;
438
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700439 final int newState = translateStateFromTelephony(connection, isForConference);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700440
441 if (call.getState() != newState) {
Santos Cordon69a69192013-08-22 14:25:42 -0700442 setNewState(call, newState, connection);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700443 changed = true;
444 }
445
Santos Cordone38b1ff2013-08-07 12:12:16 -0700446 final Call.DisconnectCause newDisconnectCause =
447 translateDisconnectCauseFromTelephony(connection.getDisconnectCause());
448 if (call.getDisconnectCause() != newDisconnectCause) {
449 call.setDisconnectCause(newDisconnectCause);
450 changed = true;
451 }
452
Santos Cordonbbe8ecf2013-08-13 15:26:18 -0700453 final long oldConnectTime = call.getConnectTime();
454 if (oldConnectTime != connection.getConnectTime()) {
455 call.setConnectTime(connection.getConnectTime());
456 changed = true;
457 }
458
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700459 if (!isForConference) {
Santos Cordon69a69192013-08-22 14:25:42 -0700460 // Number
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700461 final String oldNumber = call.getNumber();
Santos Cordon69a69192013-08-22 14:25:42 -0700462 String newNumber = connection.getAddress();
463 RawGatewayInfo info = mCallGatewayManager.getGatewayInfo(connection);
464 if (!info.isEmpty()) {
465 newNumber = info.trueNumber;
466 }
467 if (TextUtils.isEmpty(oldNumber) || !oldNumber.equals(newNumber)) {
468 call.setNumber(newNumber);
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700469 changed = true;
470 }
471
Santos Cordon69a69192013-08-22 14:25:42 -0700472 // Number presentation
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700473 final int newNumberPresentation = connection.getNumberPresentation();
474 if (call.getNumberPresentation() != newNumberPresentation) {
475 call.setNumberPresentation(newNumberPresentation);
476 changed = true;
477 }
478
Santos Cordon69a69192013-08-22 14:25:42 -0700479 // Name
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700480 final String oldCnapName = call.getCnapName();
481 if (TextUtils.isEmpty(oldCnapName) || !oldCnapName.equals(connection.getCnapName())) {
482 call.setCnapName(connection.getCnapName());
483 changed = true;
484 }
Santos Cordon69a69192013-08-22 14:25:42 -0700485
486 // Name Presentation
487 final int newCnapNamePresentation = connection.getCnapNamePresentation();
488 if (call.getCnapNamePresentation() != newCnapNamePresentation) {
489 call.setCnapNamePresentation(newCnapNamePresentation);
490 changed = true;
491 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700492 } else {
493
494 // update the list of children by:
495 // 1) Saving the old set
496 // 2) Removing all children
497 // 3) Adding the correct children into the Call
498 // 4) Comparing the new children set with the old children set
499 ImmutableSortedSet<Integer> oldSet = call.getChildCallIds();
500 call.removeAllChildren();
501
502 if (connection.getCall() != null) {
503 for (Connection childConn : connection.getCall().getConnections()) {
504 final Call childCall = getCallFromMap(mCallMap, childConn, false);
505 if (childCall != null && childConn.isAlive()) {
506 call.addChildId(childCall.getCallId());
507 }
508 }
509 }
Christine Chen45277022013-09-05 10:55:37 -0700510 changed |= !oldSet.equals(call.getChildCallIds());
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700511 }
512
Santos Cordoneead6ec2013-08-07 22:16:33 -0700513 /**
514 * !!! Uses values from connection and call collected above so this part must be last !!!
515 */
516 final int newCapabilities = getCapabilitiesFor(connection, call);
Santos Cordon26e7b242013-08-07 21:15:45 -0700517 if (call.getCapabilities() != newCapabilities) {
518 call.setCapabilities(newCapabilities);
519 changed = true;
520 }
521
Santos Cordone38b1ff2013-08-07 12:12:16 -0700522 return changed;
523 }
524
Santos Cordon26e7b242013-08-07 21:15:45 -0700525 /**
526 * Returns a mask of capabilities for the connection such as merge, hold, etc.
527 */
Santos Cordoneead6ec2013-08-07 22:16:33 -0700528 private int getCapabilitiesFor(Connection connection, Call call) {
529 final boolean callIsActive = (call.getState() == Call.State.ACTIVE);
530 final Phone phone = connection.getCall().getPhone();
531
Santos Cordoneead6ec2013-08-07 22:16:33 -0700532 boolean canAddCall = false;
533 boolean canMergeCall = false;
534 boolean canSwapCall = false;
Yorke Lee814da302013-08-30 16:01:07 -0700535 boolean canRespondViaText = false;
Christine Chenaf2fd0a2013-09-13 16:27:40 -0700536 boolean canMute = false;
537
538 final boolean supportHold = PhoneUtils.okToSupportHold(mCallManager);
539 final boolean canHold = PhoneUtils.okToHoldCall(mCallManager);
Santos Cordoneead6ec2013-08-07 22:16:33 -0700540
541 // only applies to active calls
542 if (callIsActive) {
Santos Cordoneead6ec2013-08-07 22:16:33 -0700543 canMergeCall = PhoneUtils.okToMergeCalls(mCallManager);
544 canSwapCall = PhoneUtils.okToSwapCalls(mCallManager);
545 }
546
Christine Chenaf2fd0a2013-09-13 16:27:40 -0700547 canAddCall = PhoneUtils.okToAddCall(mCallManager);
548
549 // "Mute": only enabled when the foreground call is ACTIVE.
550 // (It's meaningless while on hold, or while DIALING/ALERTING.)
551 // It's also explicitly disabled during emergency calls or if
552 // emergency callback mode (ECM) is active.
553 boolean isEmergencyCall = false;
554 if (connection != null) {
555 isEmergencyCall = PhoneNumberUtils.isLocalEmergencyNumber(connection.getAddress(),
556 phone.getContext());
557 }
558 boolean isECM = PhoneUtils.isPhoneInEcm(phone);
559 if (isEmergencyCall || isECM) { // disable "Mute" item
560 canMute = false;
561 } else {
562 canMute = callIsActive;
563 }
564
Yorke Lee814da302013-08-30 16:01:07 -0700565 canRespondViaText = RejectWithTextMessageManager.allowRespondViaSmsForCall(call,
566 connection);
567
Santos Cordoneead6ec2013-08-07 22:16:33 -0700568 // special rules section!
569 // CDMA always has Add
570 if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
571 canAddCall = true;
Santos Cordoneead6ec2013-08-07 22:16:33 -0700572 }
573
Santos Cordon26e7b242013-08-07 21:15:45 -0700574 int retval = 0x0;
Santos Cordoneead6ec2013-08-07 22:16:33 -0700575 if (canHold) {
Santos Cordon26e7b242013-08-07 21:15:45 -0700576 retval |= Capabilities.HOLD;
577 }
Christine Chenaf2fd0a2013-09-13 16:27:40 -0700578 if (supportHold) {
579 retval |= Capabilities.SUPPORT_HOLD;
580 }
Santos Cordoneead6ec2013-08-07 22:16:33 -0700581 if (canAddCall) {
582 retval |= Capabilities.ADD_CALL;
583 }
584 if (canMergeCall) {
585 retval |= Capabilities.MERGE_CALLS;
586 }
587 if (canSwapCall) {
588 retval |= Capabilities.SWAP_CALLS;
589 }
Yorke Lee814da302013-08-30 16:01:07 -0700590 if (canRespondViaText) {
591 retval |= Capabilities.RESPOND_VIA_TEXT;
592 }
Christine Chenaf2fd0a2013-09-13 16:27:40 -0700593 if (canMute) {
594 retval |= Capabilities.MUTE;
595 }
Yorke Lee814da302013-08-30 16:01:07 -0700596
Santos Cordon26e7b242013-08-07 21:15:45 -0700597 return retval;
598 }
599
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700600 /**
601 * Returns true if the Connection is part of a multiparty call.
602 * We do this by checking the isMultiparty() method of the telephony.Call object and also
603 * checking to see if more than one of it's children is alive.
604 */
605 private boolean isPartOfLiveConferenceCall(Connection connection) {
606 if (connection.getCall() != null && connection.getCall().isMultiparty()) {
607 int count = 0;
608 for (Connection currConn : connection.getCall().getConnections()) {
609 if (currConn.isAlive()) {
610 count++;
611 if (count >= 2) {
612 return true;
613 }
614 }
615 }
616 }
617 return false;
618 }
619
620 private int translateStateFromTelephony(Connection connection, boolean isForConference) {
621
Santos Cordona3d05142013-07-29 11:25:17 -0700622 int retval = State.IDLE;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700623 switch (connection.getState()) {
Santos Cordona3d05142013-07-29 11:25:17 -0700624 case ACTIVE:
625 retval = State.ACTIVE;
626 break;
627 case INCOMING:
628 retval = State.INCOMING;
629 break;
630 case DIALING:
631 case ALERTING:
632 retval = State.DIALING;
633 break;
634 case WAITING:
635 retval = State.CALL_WAITING;
636 break;
637 case HOLDING:
638 retval = State.ONHOLD;
639 break;
Santos Cordone38b1ff2013-08-07 12:12:16 -0700640 case DISCONNECTED:
641 case DISCONNECTING:
642 retval = State.DISCONNECTED;
Santos Cordona3d05142013-07-29 11:25:17 -0700643 default:
644 }
645
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700646 // If we are dealing with a potential child call (not the parent conference call),
647 // the check to see if we have to set the state to CONFERENCED.
648 if (!isForConference) {
649
650 // if the connection is part of a multiparty call, and it is live,
651 // annotate it with CONFERENCED state instead.
652 if (isPartOfLiveConferenceCall(connection) && connection.isAlive()) {
653 return State.CONFERENCED;
654 }
655 }
656
Santos Cordona3d05142013-07-29 11:25:17 -0700657 return retval;
Santos Cordon995c8162013-07-29 09:22:22 -0700658 }
659
Santos Cordone38b1ff2013-08-07 12:12:16 -0700660 private final ImmutableMap<Connection.DisconnectCause, Call.DisconnectCause> CAUSE_MAP =
661 ImmutableMap.<Connection.DisconnectCause, Call.DisconnectCause>builder()
662 .put(Connection.DisconnectCause.BUSY, Call.DisconnectCause.BUSY)
663 .put(Connection.DisconnectCause.CALL_BARRED, Call.DisconnectCause.CALL_BARRED)
664 .put(Connection.DisconnectCause.CDMA_ACCESS_BLOCKED,
665 Call.DisconnectCause.CDMA_ACCESS_BLOCKED)
666 .put(Connection.DisconnectCause.CDMA_ACCESS_FAILURE,
667 Call.DisconnectCause.CDMA_ACCESS_FAILURE)
668 .put(Connection.DisconnectCause.CDMA_DROP, Call.DisconnectCause.CDMA_DROP)
669 .put(Connection.DisconnectCause.CDMA_INTERCEPT, Call.DisconnectCause.CDMA_INTERCEPT)
670 .put(Connection.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE,
671 Call.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE)
672 .put(Connection.DisconnectCause.CDMA_NOT_EMERGENCY,
673 Call.DisconnectCause.CDMA_NOT_EMERGENCY)
674 .put(Connection.DisconnectCause.CDMA_PREEMPTED, Call.DisconnectCause.CDMA_PREEMPTED)
675 .put(Connection.DisconnectCause.CDMA_REORDER, Call.DisconnectCause.CDMA_REORDER)
676 .put(Connection.DisconnectCause.CDMA_RETRY_ORDER,
677 Call.DisconnectCause.CDMA_RETRY_ORDER)
678 .put(Connection.DisconnectCause.CDMA_SO_REJECT, Call.DisconnectCause.CDMA_SO_REJECT)
679 .put(Connection.DisconnectCause.CONGESTION, Call.DisconnectCause.CONGESTION)
680 .put(Connection.DisconnectCause.CS_RESTRICTED, Call.DisconnectCause.CS_RESTRICTED)
681 .put(Connection.DisconnectCause.CS_RESTRICTED_EMERGENCY,
682 Call.DisconnectCause.CS_RESTRICTED_EMERGENCY)
683 .put(Connection.DisconnectCause.CS_RESTRICTED_NORMAL,
684 Call.DisconnectCause.CS_RESTRICTED_NORMAL)
685 .put(Connection.DisconnectCause.ERROR_UNSPECIFIED,
686 Call.DisconnectCause.ERROR_UNSPECIFIED)
687 .put(Connection.DisconnectCause.FDN_BLOCKED, Call.DisconnectCause.FDN_BLOCKED)
688 .put(Connection.DisconnectCause.ICC_ERROR, Call.DisconnectCause.ICC_ERROR)
689 .put(Connection.DisconnectCause.INCOMING_MISSED,
690 Call.DisconnectCause.INCOMING_MISSED)
691 .put(Connection.DisconnectCause.INCOMING_REJECTED,
692 Call.DisconnectCause.INCOMING_REJECTED)
693 .put(Connection.DisconnectCause.INVALID_CREDENTIALS,
694 Call.DisconnectCause.INVALID_CREDENTIALS)
695 .put(Connection.DisconnectCause.INVALID_NUMBER,
696 Call.DisconnectCause.INVALID_NUMBER)
697 .put(Connection.DisconnectCause.LIMIT_EXCEEDED, Call.DisconnectCause.LIMIT_EXCEEDED)
698 .put(Connection.DisconnectCause.LOCAL, Call.DisconnectCause.LOCAL)
699 .put(Connection.DisconnectCause.LOST_SIGNAL, Call.DisconnectCause.LOST_SIGNAL)
700 .put(Connection.DisconnectCause.MMI, Call.DisconnectCause.MMI)
701 .put(Connection.DisconnectCause.NORMAL, Call.DisconnectCause.NORMAL)
702 .put(Connection.DisconnectCause.NOT_DISCONNECTED,
703 Call.DisconnectCause.NOT_DISCONNECTED)
704 .put(Connection.DisconnectCause.NUMBER_UNREACHABLE,
705 Call.DisconnectCause.NUMBER_UNREACHABLE)
706 .put(Connection.DisconnectCause.OUT_OF_NETWORK, Call.DisconnectCause.OUT_OF_NETWORK)
707 .put(Connection.DisconnectCause.OUT_OF_SERVICE, Call.DisconnectCause.OUT_OF_SERVICE)
708 .put(Connection.DisconnectCause.POWER_OFF, Call.DisconnectCause.POWER_OFF)
709 .put(Connection.DisconnectCause.SERVER_ERROR, Call.DisconnectCause.SERVER_ERROR)
710 .put(Connection.DisconnectCause.SERVER_UNREACHABLE,
711 Call.DisconnectCause.SERVER_UNREACHABLE)
712 .put(Connection.DisconnectCause.TIMED_OUT, Call.DisconnectCause.TIMED_OUT)
713 .put(Connection.DisconnectCause.UNOBTAINABLE_NUMBER,
714 Call.DisconnectCause.UNOBTAINABLE_NUMBER)
715 .build();
716
717 private Call.DisconnectCause translateDisconnectCauseFromTelephony(
718 Connection.DisconnectCause causeSource) {
719
720 if (CAUSE_MAP.containsKey(causeSource)) {
721 return CAUSE_MAP.get(causeSource);
722 }
723
724 return Call.DisconnectCause.UNKNOWN;
725 }
726
Santos Cordon63a84242013-07-23 13:32:52 -0700727 /**
Santos Cordone38b1ff2013-08-07 12:12:16 -0700728 * Gets an existing callId for a connection, or creates one if none exists.
729 * This function does NOT set any of the Connection data onto the Call class.
730 * A separate call to updateCallFromConnection must be made for that purpose.
Santos Cordon63a84242013-07-23 13:32:52 -0700731 */
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700732 private Call getCallFromMap(HashMap<Connection, Call> map, Connection conn,
733 boolean createIfMissing) {
Santos Cordon995c8162013-07-29 09:22:22 -0700734 Call call = null;
Santos Cordon63a84242013-07-23 13:32:52 -0700735
736 // Find the call id or create if missing and requested.
737 if (conn != null) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700738 if (map.containsKey(conn)) {
739 call = map.get(conn);
Santos Cordon63a84242013-07-23 13:32:52 -0700740 } else if (createIfMissing) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700741 call = createNewCall();
742 map.put(conn, call);
Santos Cordon63a84242013-07-23 13:32:52 -0700743 }
744 }
Santos Cordon995c8162013-07-29 09:22:22 -0700745 return call;
Santos Cordon63a84242013-07-23 13:32:52 -0700746 }
747
748 /**
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700749 * Creates a brand new connection for the call.
750 */
751 private Call createNewCall() {
752 int callId;
753 int newNextCallId;
754 do {
755 callId = mNextCallId.get();
756
757 // protect against overflow
758 newNextCallId = (callId == Integer.MAX_VALUE ?
759 CALL_ID_START_VALUE : callId + 1);
760
761 // Keep looping if the change was not atomic OR the value is already taken.
762 // The call to containsValue() is linear, however, most devices support a
763 // maximum of 7 connections so it's not expensive.
764 } while (!mNextCallId.compareAndSet(callId, newNextCallId));
765
766 return new Call(callId);
767 }
768
769 /**
Santos Cordon63a84242013-07-23 13:32:52 -0700770 * Listener interface for changes to Calls.
771 */
772 public interface Listener {
Santos Cordon995c8162013-07-29 09:22:22 -0700773 void onDisconnect(Call call);
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700774 void onIncoming(Call call);
775 void onUpdate(List<Call> calls);
Chiao Cheng3f015c92013-09-06 15:56:27 -0700776 void onPostDialWait(int callId, String remainingChars);
Santos Cordon63a84242013-07-23 13:32:52 -0700777 }
Santos Cordon249efd02013-08-05 03:33:56 -0700778
779 /**
780 * Result class for accessing a call by connection.
781 */
782 public static class CallResult {
783 public Call mCall;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700784 public Call mActionableCall;
Santos Cordon249efd02013-08-05 03:33:56 -0700785 public Connection mConnection;
786
787 private CallResult(Call call, Connection connection) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700788 this(call, call, connection);
789 }
790
791 private CallResult(Call call, Call actionableCall, Connection connection) {
Santos Cordon249efd02013-08-05 03:33:56 -0700792 mCall = call;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700793 mActionableCall = actionableCall;
Santos Cordon249efd02013-08-05 03:33:56 -0700794 mConnection = connection;
795 }
796
797 public Call getCall() {
798 return mCall;
799 }
800
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700801 // The call that should be used for call actions like hanging up.
802 public Call getActionableCall() {
803 return mActionableCall;
804 }
805
Santos Cordon249efd02013-08-05 03:33:56 -0700806 public Connection getConnection() {
807 return mConnection;
808 }
809 }
Santos Cordon63a84242013-07-23 13:32:52 -0700810}