blob: f3e0d36ef49f14aac7ac80380be452127fa8c29c [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 Cordona5d5db82013-09-15 13:00:34 -070031import com.android.internal.telephony.TelephonyCapabilities;
32import com.android.internal.telephony.cdma.CdmaCallWaitingNotification;
Santos Cordon69a69192013-08-22 14:25:42 -070033import com.android.phone.CallGatewayManager.RawGatewayInfo;
Santos Cordon995c8162013-07-29 09:22:22 -070034import com.android.services.telephony.common.Call;
Santos Cordon26e7b242013-08-07 21:15:45 -070035import com.android.services.telephony.common.Call.Capabilities;
Santos Cordona3d05142013-07-29 11:25:17 -070036import com.android.services.telephony.common.Call.State;
Santos Cordon63a84242013-07-23 13:32:52 -070037
Yorke Lee814da302013-08-30 16:01:07 -070038import com.google.android.collect.Lists;
39import com.google.android.collect.Maps;
40import com.google.common.base.Preconditions;
41import com.google.common.collect.ImmutableMap;
42import com.google.common.collect.ImmutableSortedSet;
43
Santos Cordon63a84242013-07-23 13:32:52 -070044import java.util.ArrayList;
45import java.util.HashMap;
46import java.util.List;
Santos Cordon249efd02013-08-05 03:33:56 -070047import java.util.Map.Entry;
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 Cordona5d5db82013-09-15 13:00:34 -070094 private Connection mCdmaIncomingConnection;
Santos Cordon63a84242013-07-23 13:32:52 -070095
Christine Chenee09a492013-08-06 16:02:29 -070096 public CallModeler(CallStateMonitor callStateMonitor, CallManager callManager,
Santos Cordon69a69192013-08-22 14:25:42 -070097 RejectWithTextMessageManager rejectWithTextMessageManager,
98 CallGatewayManager callGatewayManager) {
Santos Cordon63a84242013-07-23 13:32:52 -070099 mCallStateMonitor = callStateMonitor;
Santos Cordona3d05142013-07-29 11:25:17 -0700100 mCallManager = callManager;
Christine Chenee09a492013-08-06 16:02:29 -0700101 mRejectWithTextMessageManager = rejectWithTextMessageManager;
Santos Cordon69a69192013-08-22 14:25:42 -0700102 mCallGatewayManager = callGatewayManager;
Santos Cordon63a84242013-07-23 13:32:52 -0700103
104 mCallStateMonitor.addListener(this);
105 }
106
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700107 @Override
Santos Cordon63a84242013-07-23 13:32:52 -0700108 public void handleMessage(Message msg) {
109 switch(msg.what) {
110 case CallStateMonitor.PHONE_NEW_RINGING_CONNECTION:
Santos Cordona5d5db82013-09-15 13:00:34 -0700111 onNewRingingConnection((Connection) ((AsyncResult) msg.obj).result);
Santos Cordon63a84242013-07-23 13:32:52 -0700112 break;
113 case CallStateMonitor.PHONE_DISCONNECT:
Santos Cordona5d5db82013-09-15 13:00:34 -0700114 onDisconnect((Connection) ((AsyncResult) msg.obj).result);
Santos Cordon995c8162013-07-29 09:22:22 -0700115 break;
116 case CallStateMonitor.PHONE_STATE_CHANGED:
117 onPhoneStateChanged((AsyncResult) msg.obj);
118 break;
Chiao Cheng3f015c92013-09-06 15:56:27 -0700119 case CallStateMonitor.PHONE_ON_DIAL_CHARS:
120 onPostDialChars((AsyncResult) msg.obj, (char) msg.arg1);
121 break;
Santos Cordon63a84242013-07-23 13:32:52 -0700122 default:
123 break;
124 }
125 }
126
Christine Chendaf7bf62013-08-05 19:12:31 -0700127 public void addListener(Listener listener) {
Santos Cordon63a84242013-07-23 13:32:52 -0700128 Preconditions.checkNotNull(listener);
Christine Chendaf7bf62013-08-05 19:12:31 -0700129 Preconditions.checkNotNull(mListeners);
Christine Chen4748abd2013-08-07 15:44:15 -0700130 if (!mListeners.contains(listener)) {
131 mListeners.add(listener);
132 }
Santos Cordon998f42b2013-08-02 16:13:12 -0700133 }
134
135 public List<Call> getFullList() {
136 final List<Call> retval = Lists.newArrayList();
137 doUpdate(true, retval);
138 return retval;
Santos Cordon63a84242013-07-23 13:32:52 -0700139 }
140
Santos Cordon249efd02013-08-05 03:33:56 -0700141 public CallResult getCallWithId(int callId) {
142 // max 8 connections, so this should be fast even through we are traversing the entire map.
143 for (Entry<Connection, Call> entry : mCallMap.entrySet()) {
144 if (entry.getValue().getCallId() == callId) {
145 return new CallResult(entry.getValue(), entry.getKey());
146 }
147 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700148
149 for (Entry<Connection, Call> entry : mConfCallMap.entrySet()) {
150 if (entry.getValue().getCallId() == callId) {
Christine Chen69050202013-09-14 15:36:35 -0700151 return new CallResult(entry.getValue(), entry.getKey());
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700152 }
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
Santos Cordona5d5db82013-09-15 13:00:34 -0700162 public void onCdmaCallWaiting(CdmaCallWaitingNotification callWaitingInfo) {
163 // We dont get the traditional onIncomingCall notification for cdma call waiting,
164 // but the Connection does actually exist. We need to find it in the set of ringing calls
165 // and pass it through our normal incoming logic.
166 final com.android.internal.telephony.Call teleCall =
167 mCallManager.getFirstActiveRingingCall();
168
169 if (teleCall.getState() == com.android.internal.telephony.Call.State.WAITING) {
170 Connection connection = teleCall.getLatestConnection();
171
172 if (connection != null) {
173 String number = connection.getAddress();
174 if (number != null && number.equals(callWaitingInfo.number)) {
175 Call call = onNewRingingConnection(connection);
176 mCdmaIncomingConnection = connection;
177 return;
178 }
179 }
180 }
181
182 Log.e(TAG, "CDMA Call waiting notification without a matching connection.");
183 }
184
185 public void onCdmaCallWaitingReject() {
186 // Cdma call was rejected...
187 if (mCdmaIncomingConnection != null) {
188 onDisconnect(mCdmaIncomingConnection);
189 mCdmaIncomingConnection = null;
190 } else {
191 Log.e(TAG, "CDMA Call waiting rejection without an incoming call.");
192 }
193 }
194
Santos Cordonaf763a12013-08-19 20:04:58 -0700195 private boolean hasLiveCallInternal(HashMap<Connection, Call> map) {
196 for (Call call : map.values()) {
197 final int state = call.getState();
198 if (state == Call.State.ACTIVE ||
199 state == Call.State.CALL_WAITING ||
200 state == Call.State.CONFERENCED ||
201 state == Call.State.DIALING ||
202 state == Call.State.INCOMING ||
203 state == Call.State.ONHOLD) {
204 return true;
205 }
206 }
207 return false;
208 }
209
Santos Cordon2b73bd62013-08-27 14:53:43 -0700210 public boolean hasOutstandingActiveOrDialingCall() {
211 return hasOutstandingActiveOrDialingCallInternal(mCallMap) ||
212 hasOutstandingActiveOrDialingCallInternal(mConfCallMap);
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700213 }
214
Santos Cordon2b73bd62013-08-27 14:53:43 -0700215 private static boolean hasOutstandingActiveOrDialingCallInternal(
216 HashMap<Connection, Call> map) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700217 for (Call call : map.values()) {
218 final int state = call.getState();
Santos Cordon2b73bd62013-08-27 14:53:43 -0700219 if (state == Call.State.ACTIVE ||
220 state == Call.State.DIALING) {
Santos Cordon2eaff902013-08-05 04:37:55 -0700221 return true;
222 }
223 }
224
225 return false;
226 }
227
Chiao Cheng3f015c92013-09-06 15:56:27 -0700228
229 /**
230 * Handles the POST_ON_DIAL_CHARS message from the Phone (see our call to
231 * mPhone.setOnPostDialCharacter() above.)
232 *
233 * TODO: NEED TO TEST THIS SEQUENCE now that we no longer handle "dialable" key events here in
234 * the InCallScreen: we do directly to the Dialer UI instead. Similarly, we may now need to go
235 * directly to the Dialer to handle POST_ON_DIAL_CHARS too.
236 */
237 private void onPostDialChars(AsyncResult r, char ch) {
238 final Connection c = (Connection) r.result;
239
240 if (c != null) {
241 final Connection.PostDialState state = (Connection.PostDialState) r.userObj;
242
243 switch (state) {
244 // TODO(klp): add other post dial related functions
245 case WAIT:
246 final Call call = getCallFromMap(mCallMap, c, false);
247 if (call == null) {
248 Log.i(TAG, "Call no longer exists. Skipping onPostDialWait().");
249 } else {
250 for (Listener mListener : mListeners) {
251 mListener.onPostDialWait(call.getCallId(),
252 c.getRemainingPostDialString());
253 }
254 }
255 break;
256
257 default:
258 break;
259 }
260 }
261 }
262
Santos Cordona5d5db82013-09-15 13:00:34 -0700263 private Call onNewRingingConnection(Connection conn) {
Santos Cordon2b73bd62013-08-27 14:53:43 -0700264 Log.i(TAG, "onNewRingingConnection");
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700265 final Call call = getCallFromMap(mCallMap, conn, true);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700266
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700267 updateCallFromConnection(call, conn, false);
Santos Cordon63a84242013-07-23 13:32:52 -0700268
Christine Chendaf7bf62013-08-05 19:12:31 -0700269 for (int i = 0; i < mListeners.size(); ++i) {
Christine Chenee09a492013-08-06 16:02:29 -0700270 if (call != null) {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700271 mListeners.get(i).onIncoming(call);
Christine Chenee09a492013-08-06 16:02:29 -0700272 }
Santos Cordon63a84242013-07-23 13:32:52 -0700273 }
Santos Cordona5d5db82013-09-15 13:00:34 -0700274
275 return call;
Santos Cordon63a84242013-07-23 13:32:52 -0700276 }
277
Santos Cordona5d5db82013-09-15 13:00:34 -0700278 private void onDisconnect(Connection conn) {
Santos Cordon2b73bd62013-08-27 14:53:43 -0700279 Log.i(TAG, "onDisconnect");
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700280 final Call call = getCallFromMap(mCallMap, conn, false);
Santos Cordon63a84242013-07-23 13:32:52 -0700281
Santos Cordon995c8162013-07-29 09:22:22 -0700282 if (call != null) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700283 final boolean wasConferenced = call.getState() == State.CONFERENCED;
284
285 updateCallFromConnection(call, conn, false);
Santos Cordon63a84242013-07-23 13:32:52 -0700286
Christine Chendaf7bf62013-08-05 19:12:31 -0700287 for (int i = 0; i < mListeners.size(); ++i) {
288 mListeners.get(i).onDisconnect(call);
Santos Cordon63a84242013-07-23 13:32:52 -0700289 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700290
291 // If it was a conferenced call, we need to run the entire update
292 // to make the proper changes to parent conference calls.
293 if (wasConferenced) {
294 onPhoneStateChanged(null);
295 }
296
297 mCallMap.remove(conn);
Santos Cordon63a84242013-07-23 13:32:52 -0700298 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700299
300 // TODO(klp): Do a final check to see if there are any active calls.
301 // If there are not, totally cancel all calls
Santos Cordon63a84242013-07-23 13:32:52 -0700302 }
303
Santos Cordona3d05142013-07-29 11:25:17 -0700304 /**
305 * Called when the phone state changes.
Santos Cordona3d05142013-07-29 11:25:17 -0700306 */
Santos Cordon995c8162013-07-29 09:22:22 -0700307 private void onPhoneStateChanged(AsyncResult r) {
Santos Cordon2b73bd62013-08-27 14:53:43 -0700308 Log.i(TAG, "onPhoneStateChanged: ");
Santos Cordon998f42b2013-08-02 16:13:12 -0700309 final List<Call> updatedCalls = Lists.newArrayList();
310 doUpdate(false, updatedCalls);
311
Santos Cordon71d5c6e2013-09-05 21:34:33 -0700312 if (updatedCalls.size() > 0) {
313 for (int i = 0; i < mListeners.size(); ++i) {
314 mListeners.get(i).onUpdate(updatedCalls);
315 }
Santos Cordon998f42b2013-08-02 16:13:12 -0700316 }
317 }
318
319
320 /**
321 * Go through the Calls from CallManager and return the list of calls that were updated.
322 * Or, the full list if requested.
323 */
324 private void doUpdate(boolean fullUpdate, List<Call> out) {
Santos Cordona3d05142013-07-29 11:25:17 -0700325 final List<com.android.internal.telephony.Call> telephonyCalls = Lists.newArrayList();
326 telephonyCalls.addAll(mCallManager.getRingingCalls());
327 telephonyCalls.addAll(mCallManager.getForegroundCalls());
328 telephonyCalls.addAll(mCallManager.getBackgroundCalls());
329
Santos Cordona3d05142013-07-29 11:25:17 -0700330 // Cycle through all the Connections on all the Calls. Update our Call objects
331 // to reflect any new state and send the updated Call objects to the handler service.
332 for (com.android.internal.telephony.Call telephonyCall : telephonyCalls) {
Santos Cordona3d05142013-07-29 11:25:17 -0700333
334 for (Connection connection : telephonyCall.getConnections()) {
Santos Cordona5d5db82013-09-15 13:00:34 -0700335 if (DBG) Log.d(TAG, "connection: " + connection);
336
Santos Cordon12a03aa2013-09-12 23:34:05 -0700337 // We only send updates for live calls which are not incoming (ringing).
338 // Disconnected and incoming calls are handled by onDisconnect and
339 // onNewRingingConnection.
340 boolean shouldUpdate = connection.getState().isAlive() &&
Santos Cordon71d5c6e2013-09-05 21:34:33 -0700341 !connection.getState().isRinging();
342
343 // New connections return a Call with INVALID state, which does not translate to
Santos Cordone38b1ff2013-08-07 12:12:16 -0700344 // a state in the internal.telephony.Call object. This ensures that staleness
345 // check below fails and we always add the item to the update list if it is new.
Santos Cordon12a03aa2013-09-12 23:34:05 -0700346 final Call call = getCallFromMap(mCallMap, connection, shouldUpdate /* create */);
Santos Cordon71d5c6e2013-09-05 21:34:33 -0700347
Santos Cordon12a03aa2013-09-12 23:34:05 -0700348 if (call == null || !shouldUpdate) {
Santos Cordona5d5db82013-09-15 13:00:34 -0700349 if (DBG) Log.d(TAG, "update skipped");
Santos Cordon71d5c6e2013-09-05 21:34:33 -0700350 continue;
351 }
Santos Cordona3d05142013-07-29 11:25:17 -0700352
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700353 boolean changed = updateCallFromConnection(call, connection, false);
Santos Cordon2b73bd62013-08-27 14:53:43 -0700354
Santos Cordone38b1ff2013-08-07 12:12:16 -0700355 if (fullUpdate || changed) {
Santos Cordon998f42b2013-08-02 16:13:12 -0700356 out.add(call);
Santos Cordona3d05142013-07-29 11:25:17 -0700357 }
358 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700359
360 // We do a second loop to address conference call scenarios. We do this as a separate
361 // loop to ensure all child calls are up to date before we start updating the parent
362 // conference calls.
363 for (Connection connection : telephonyCall.getConnections()) {
364 updateForConferenceCalls(connection, out);
365 }
366
Santos Cordona3d05142013-07-29 11:25:17 -0700367 }
Santos Cordona3d05142013-07-29 11:25:17 -0700368 }
369
Santos Cordone38b1ff2013-08-07 12:12:16 -0700370 /**
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700371 * Checks to see if the connection is the first connection in a conference call.
372 * If it is a conference call, we will create a new Conference Call object or
373 * update the existing conference call object for that connection.
374 * If it is not a conference call but a previous associated conference call still exists,
375 * we mark it as idle and remove it from the map.
376 * In both cases above, we add the Calls to be updated to the UI.
377 * @param connection The connection object to check.
378 * @param updatedCalls List of 'updated' calls that will be sent to the UI.
379 */
380 private boolean updateForConferenceCalls(Connection connection, List<Call> updatedCalls) {
381 // We consider this connection a conference connection if the call it
Yorke Leecd3f9692013-09-14 13:51:27 -0700382 // belongs to is a multiparty call AND it is the first live connection.
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700383 final boolean isConferenceCallConnection = isPartOfLiveConferenceCall(connection) &&
Yorke Leecd3f9692013-09-14 13:51:27 -0700384 getEarliestLiveConnection(connection.getCall()) == connection;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700385
386 boolean changed = false;
387
388 // If this connection is the main connection for the conference call, then create or update
389 // a Call object for that conference call.
390 if (isConferenceCallConnection) {
391 final Call confCall = getCallFromMap(mConfCallMap, connection, true);
392 changed = updateCallFromConnection(confCall, connection, true);
393
394 if (changed) {
395 updatedCalls.add(confCall);
396 }
397
398 if (DBG) Log.d(TAG, "Updating a conference call: " + confCall);
399
400 // It is possible that through a conference call split, there may be lingering conference
401 // calls where this connection was the main connection. We clean those up here.
402 } else {
403 final Call oldConfCall = getCallFromMap(mConfCallMap, connection, false);
404
405 // We found a conference call for this connection, which is no longer a conference call.
406 // Kill it!
407 if (oldConfCall != null) {
408 if (DBG) Log.d(TAG, "Cleaning up an old conference call: " + oldConfCall);
409 mConfCallMap.remove(connection);
410 oldConfCall.setState(State.IDLE);
411 changed = true;
412
413 // add to the list of calls to update
414 updatedCalls.add(oldConfCall);
415 }
416 }
417
418 return changed;
419 }
420
Yorke Leecd3f9692013-09-14 13:51:27 -0700421 private Connection getEarliestLiveConnection(com.android.internal.telephony.Call call) {
422 final List<Connection> connections = call.getConnections();
423 final int size = connections.size();
424 Connection earliestConn = null;
425 long earliestTime = Long.MAX_VALUE;
426 for (int i = 0; i < size; i++) {
427 final Connection connection = connections.get(i);
428 if (!connection.isAlive()) continue;
429 final long time = connection.getCreateTime();
430 if (time < earliestTime) {
431 earliestTime = time;
432 earliestConn = connection;
433 }
434 }
435 return earliestConn;
436 }
437
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700438 /**
Santos Cordon69a69192013-08-22 14:25:42 -0700439 * Sets the new call state onto the call and performs some additional logic
440 * associated with setting the state.
441 */
442 private void setNewState(Call call, int newState, Connection connection) {
443 Preconditions.checkState(call.getState() != newState);
444
445 // When starting an outgoing call, we need to grab gateway information
446 // for the call, if available, and set it.
447 final RawGatewayInfo info = mCallGatewayManager.getGatewayInfo(connection);
448
449 if (newState == Call.State.DIALING) {
450 if (!info.isEmpty()) {
451 call.setGatewayNumber(info.getFormattedGatewayNumber());
452 call.setGatewayPackage(info.packageName);
453 }
454 } else if (!Call.State.isConnected(newState)) {
455 mCallGatewayManager.clearGatewayData(connection);
456 }
457
458 call.setState(newState);
459 }
460
461 /**
Santos Cordone38b1ff2013-08-07 12:12:16 -0700462 * Updates the Call properties to match the state of the connection object
463 * that it represents.
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700464 * @param call The call object to update.
465 * @param connection The connection object from which to update call.
466 * @param isForConference There are slight differences in how we populate data for conference
467 * calls. This boolean tells us which method to use.
Santos Cordone38b1ff2013-08-07 12:12:16 -0700468 */
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700469 private boolean updateCallFromConnection(Call call, Connection connection,
470 boolean isForConference) {
Santos Cordone38b1ff2013-08-07 12:12:16 -0700471 boolean changed = false;
472
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700473 final int newState = translateStateFromTelephony(connection, isForConference);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700474
475 if (call.getState() != newState) {
Santos Cordon69a69192013-08-22 14:25:42 -0700476 setNewState(call, newState, connection);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700477 changed = true;
478 }
479
Santos Cordone38b1ff2013-08-07 12:12:16 -0700480 final Call.DisconnectCause newDisconnectCause =
481 translateDisconnectCauseFromTelephony(connection.getDisconnectCause());
482 if (call.getDisconnectCause() != newDisconnectCause) {
483 call.setDisconnectCause(newDisconnectCause);
484 changed = true;
485 }
486
Santos Cordonbbe8ecf2013-08-13 15:26:18 -0700487 final long oldConnectTime = call.getConnectTime();
488 if (oldConnectTime != connection.getConnectTime()) {
489 call.setConnectTime(connection.getConnectTime());
490 changed = true;
491 }
492
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700493 if (!isForConference) {
Santos Cordon69a69192013-08-22 14:25:42 -0700494 // Number
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700495 final String oldNumber = call.getNumber();
Santos Cordon69a69192013-08-22 14:25:42 -0700496 String newNumber = connection.getAddress();
497 RawGatewayInfo info = mCallGatewayManager.getGatewayInfo(connection);
498 if (!info.isEmpty()) {
499 newNumber = info.trueNumber;
500 }
501 if (TextUtils.isEmpty(oldNumber) || !oldNumber.equals(newNumber)) {
502 call.setNumber(newNumber);
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700503 changed = true;
504 }
505
Santos Cordon69a69192013-08-22 14:25:42 -0700506 // Number presentation
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700507 final int newNumberPresentation = connection.getNumberPresentation();
508 if (call.getNumberPresentation() != newNumberPresentation) {
509 call.setNumberPresentation(newNumberPresentation);
510 changed = true;
511 }
512
Santos Cordon69a69192013-08-22 14:25:42 -0700513 // Name
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700514 final String oldCnapName = call.getCnapName();
515 if (TextUtils.isEmpty(oldCnapName) || !oldCnapName.equals(connection.getCnapName())) {
516 call.setCnapName(connection.getCnapName());
517 changed = true;
518 }
Santos Cordon69a69192013-08-22 14:25:42 -0700519
520 // Name Presentation
521 final int newCnapNamePresentation = connection.getCnapNamePresentation();
522 if (call.getCnapNamePresentation() != newCnapNamePresentation) {
523 call.setCnapNamePresentation(newCnapNamePresentation);
524 changed = true;
525 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700526 } else {
527
528 // update the list of children by:
529 // 1) Saving the old set
530 // 2) Removing all children
531 // 3) Adding the correct children into the Call
532 // 4) Comparing the new children set with the old children set
533 ImmutableSortedSet<Integer> oldSet = call.getChildCallIds();
534 call.removeAllChildren();
535
536 if (connection.getCall() != null) {
537 for (Connection childConn : connection.getCall().getConnections()) {
538 final Call childCall = getCallFromMap(mCallMap, childConn, false);
539 if (childCall != null && childConn.isAlive()) {
540 call.addChildId(childCall.getCallId());
541 }
542 }
543 }
Christine Chen45277022013-09-05 10:55:37 -0700544 changed |= !oldSet.equals(call.getChildCallIds());
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700545 }
546
Santos Cordoneead6ec2013-08-07 22:16:33 -0700547 /**
548 * !!! Uses values from connection and call collected above so this part must be last !!!
549 */
550 final int newCapabilities = getCapabilitiesFor(connection, call);
Santos Cordon26e7b242013-08-07 21:15:45 -0700551 if (call.getCapabilities() != newCapabilities) {
552 call.setCapabilities(newCapabilities);
553 changed = true;
554 }
555
Santos Cordone38b1ff2013-08-07 12:12:16 -0700556 return changed;
557 }
558
Santos Cordon26e7b242013-08-07 21:15:45 -0700559 /**
560 * Returns a mask of capabilities for the connection such as merge, hold, etc.
561 */
Santos Cordoneead6ec2013-08-07 22:16:33 -0700562 private int getCapabilitiesFor(Connection connection, Call call) {
563 final boolean callIsActive = (call.getState() == Call.State.ACTIVE);
564 final Phone phone = connection.getCall().getPhone();
565
Santos Cordoneead6ec2013-08-07 22:16:33 -0700566 boolean canAddCall = false;
567 boolean canMergeCall = false;
568 boolean canSwapCall = false;
Yorke Lee814da302013-08-30 16:01:07 -0700569 boolean canRespondViaText = false;
Christine Chenaf2fd0a2013-09-13 16:27:40 -0700570 boolean canMute = false;
571
572 final boolean supportHold = PhoneUtils.okToSupportHold(mCallManager);
573 final boolean canHold = PhoneUtils.okToHoldCall(mCallManager);
Santos Cordoneead6ec2013-08-07 22:16:33 -0700574
575 // only applies to active calls
576 if (callIsActive) {
Santos Cordoneead6ec2013-08-07 22:16:33 -0700577 canMergeCall = PhoneUtils.okToMergeCalls(mCallManager);
578 canSwapCall = PhoneUtils.okToSwapCalls(mCallManager);
579 }
580
Christine Chenaf2fd0a2013-09-13 16:27:40 -0700581 canAddCall = PhoneUtils.okToAddCall(mCallManager);
582
583 // "Mute": only enabled when the foreground call is ACTIVE.
584 // (It's meaningless while on hold, or while DIALING/ALERTING.)
585 // It's also explicitly disabled during emergency calls or if
586 // emergency callback mode (ECM) is active.
587 boolean isEmergencyCall = false;
588 if (connection != null) {
589 isEmergencyCall = PhoneNumberUtils.isLocalEmergencyNumber(connection.getAddress(),
590 phone.getContext());
591 }
592 boolean isECM = PhoneUtils.isPhoneInEcm(phone);
593 if (isEmergencyCall || isECM) { // disable "Mute" item
594 canMute = false;
595 } else {
596 canMute = callIsActive;
597 }
598
Yorke Lee814da302013-08-30 16:01:07 -0700599 canRespondViaText = RejectWithTextMessageManager.allowRespondViaSmsForCall(call,
600 connection);
601
Santos Cordoneead6ec2013-08-07 22:16:33 -0700602 // special rules section!
603 // CDMA always has Add
604 if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
605 canAddCall = true;
Santos Cordoneead6ec2013-08-07 22:16:33 -0700606 }
607
Santos Cordon26e7b242013-08-07 21:15:45 -0700608 int retval = 0x0;
Santos Cordoneead6ec2013-08-07 22:16:33 -0700609 if (canHold) {
Santos Cordon26e7b242013-08-07 21:15:45 -0700610 retval |= Capabilities.HOLD;
611 }
Christine Chenaf2fd0a2013-09-13 16:27:40 -0700612 if (supportHold) {
613 retval |= Capabilities.SUPPORT_HOLD;
614 }
Santos Cordoneead6ec2013-08-07 22:16:33 -0700615 if (canAddCall) {
616 retval |= Capabilities.ADD_CALL;
617 }
618 if (canMergeCall) {
619 retval |= Capabilities.MERGE_CALLS;
620 }
621 if (canSwapCall) {
622 retval |= Capabilities.SWAP_CALLS;
623 }
Yorke Lee814da302013-08-30 16:01:07 -0700624 if (canRespondViaText) {
625 retval |= Capabilities.RESPOND_VIA_TEXT;
626 }
Christine Chenaf2fd0a2013-09-13 16:27:40 -0700627 if (canMute) {
628 retval |= Capabilities.MUTE;
629 }
Yorke Lee814da302013-08-30 16:01:07 -0700630
Santos Cordon26e7b242013-08-07 21:15:45 -0700631 return retval;
632 }
633
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700634 /**
635 * Returns true if the Connection is part of a multiparty call.
636 * We do this by checking the isMultiparty() method of the telephony.Call object and also
637 * checking to see if more than one of it's children is alive.
638 */
639 private boolean isPartOfLiveConferenceCall(Connection connection) {
640 if (connection.getCall() != null && connection.getCall().isMultiparty()) {
641 int count = 0;
642 for (Connection currConn : connection.getCall().getConnections()) {
643 if (currConn.isAlive()) {
644 count++;
645 if (count >= 2) {
646 return true;
647 }
648 }
649 }
650 }
651 return false;
652 }
653
654 private int translateStateFromTelephony(Connection connection, boolean isForConference) {
655
Santos Cordona3d05142013-07-29 11:25:17 -0700656 int retval = State.IDLE;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700657 switch (connection.getState()) {
Santos Cordona3d05142013-07-29 11:25:17 -0700658 case ACTIVE:
659 retval = State.ACTIVE;
660 break;
661 case INCOMING:
662 retval = State.INCOMING;
663 break;
664 case DIALING:
665 case ALERTING:
666 retval = State.DIALING;
667 break;
668 case WAITING:
669 retval = State.CALL_WAITING;
670 break;
671 case HOLDING:
672 retval = State.ONHOLD;
673 break;
Santos Cordone38b1ff2013-08-07 12:12:16 -0700674 case DISCONNECTED:
675 case DISCONNECTING:
676 retval = State.DISCONNECTED;
Santos Cordona3d05142013-07-29 11:25:17 -0700677 default:
678 }
679
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700680 // If we are dealing with a potential child call (not the parent conference call),
681 // the check to see if we have to set the state to CONFERENCED.
682 if (!isForConference) {
683
684 // if the connection is part of a multiparty call, and it is live,
685 // annotate it with CONFERENCED state instead.
686 if (isPartOfLiveConferenceCall(connection) && connection.isAlive()) {
687 return State.CONFERENCED;
688 }
689 }
690
Santos Cordona3d05142013-07-29 11:25:17 -0700691 return retval;
Santos Cordon995c8162013-07-29 09:22:22 -0700692 }
693
Santos Cordone38b1ff2013-08-07 12:12:16 -0700694 private final ImmutableMap<Connection.DisconnectCause, Call.DisconnectCause> CAUSE_MAP =
695 ImmutableMap.<Connection.DisconnectCause, Call.DisconnectCause>builder()
696 .put(Connection.DisconnectCause.BUSY, Call.DisconnectCause.BUSY)
697 .put(Connection.DisconnectCause.CALL_BARRED, Call.DisconnectCause.CALL_BARRED)
698 .put(Connection.DisconnectCause.CDMA_ACCESS_BLOCKED,
699 Call.DisconnectCause.CDMA_ACCESS_BLOCKED)
700 .put(Connection.DisconnectCause.CDMA_ACCESS_FAILURE,
701 Call.DisconnectCause.CDMA_ACCESS_FAILURE)
702 .put(Connection.DisconnectCause.CDMA_DROP, Call.DisconnectCause.CDMA_DROP)
703 .put(Connection.DisconnectCause.CDMA_INTERCEPT, Call.DisconnectCause.CDMA_INTERCEPT)
704 .put(Connection.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE,
705 Call.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE)
706 .put(Connection.DisconnectCause.CDMA_NOT_EMERGENCY,
707 Call.DisconnectCause.CDMA_NOT_EMERGENCY)
708 .put(Connection.DisconnectCause.CDMA_PREEMPTED, Call.DisconnectCause.CDMA_PREEMPTED)
709 .put(Connection.DisconnectCause.CDMA_REORDER, Call.DisconnectCause.CDMA_REORDER)
710 .put(Connection.DisconnectCause.CDMA_RETRY_ORDER,
711 Call.DisconnectCause.CDMA_RETRY_ORDER)
712 .put(Connection.DisconnectCause.CDMA_SO_REJECT, Call.DisconnectCause.CDMA_SO_REJECT)
713 .put(Connection.DisconnectCause.CONGESTION, Call.DisconnectCause.CONGESTION)
714 .put(Connection.DisconnectCause.CS_RESTRICTED, Call.DisconnectCause.CS_RESTRICTED)
715 .put(Connection.DisconnectCause.CS_RESTRICTED_EMERGENCY,
716 Call.DisconnectCause.CS_RESTRICTED_EMERGENCY)
717 .put(Connection.DisconnectCause.CS_RESTRICTED_NORMAL,
718 Call.DisconnectCause.CS_RESTRICTED_NORMAL)
719 .put(Connection.DisconnectCause.ERROR_UNSPECIFIED,
720 Call.DisconnectCause.ERROR_UNSPECIFIED)
721 .put(Connection.DisconnectCause.FDN_BLOCKED, Call.DisconnectCause.FDN_BLOCKED)
722 .put(Connection.DisconnectCause.ICC_ERROR, Call.DisconnectCause.ICC_ERROR)
723 .put(Connection.DisconnectCause.INCOMING_MISSED,
724 Call.DisconnectCause.INCOMING_MISSED)
725 .put(Connection.DisconnectCause.INCOMING_REJECTED,
726 Call.DisconnectCause.INCOMING_REJECTED)
727 .put(Connection.DisconnectCause.INVALID_CREDENTIALS,
728 Call.DisconnectCause.INVALID_CREDENTIALS)
729 .put(Connection.DisconnectCause.INVALID_NUMBER,
730 Call.DisconnectCause.INVALID_NUMBER)
731 .put(Connection.DisconnectCause.LIMIT_EXCEEDED, Call.DisconnectCause.LIMIT_EXCEEDED)
732 .put(Connection.DisconnectCause.LOCAL, Call.DisconnectCause.LOCAL)
733 .put(Connection.DisconnectCause.LOST_SIGNAL, Call.DisconnectCause.LOST_SIGNAL)
734 .put(Connection.DisconnectCause.MMI, Call.DisconnectCause.MMI)
735 .put(Connection.DisconnectCause.NORMAL, Call.DisconnectCause.NORMAL)
736 .put(Connection.DisconnectCause.NOT_DISCONNECTED,
737 Call.DisconnectCause.NOT_DISCONNECTED)
738 .put(Connection.DisconnectCause.NUMBER_UNREACHABLE,
739 Call.DisconnectCause.NUMBER_UNREACHABLE)
740 .put(Connection.DisconnectCause.OUT_OF_NETWORK, Call.DisconnectCause.OUT_OF_NETWORK)
741 .put(Connection.DisconnectCause.OUT_OF_SERVICE, Call.DisconnectCause.OUT_OF_SERVICE)
742 .put(Connection.DisconnectCause.POWER_OFF, Call.DisconnectCause.POWER_OFF)
743 .put(Connection.DisconnectCause.SERVER_ERROR, Call.DisconnectCause.SERVER_ERROR)
744 .put(Connection.DisconnectCause.SERVER_UNREACHABLE,
745 Call.DisconnectCause.SERVER_UNREACHABLE)
746 .put(Connection.DisconnectCause.TIMED_OUT, Call.DisconnectCause.TIMED_OUT)
747 .put(Connection.DisconnectCause.UNOBTAINABLE_NUMBER,
748 Call.DisconnectCause.UNOBTAINABLE_NUMBER)
749 .build();
750
751 private Call.DisconnectCause translateDisconnectCauseFromTelephony(
752 Connection.DisconnectCause causeSource) {
753
754 if (CAUSE_MAP.containsKey(causeSource)) {
755 return CAUSE_MAP.get(causeSource);
756 }
757
758 return Call.DisconnectCause.UNKNOWN;
759 }
760
Santos Cordon63a84242013-07-23 13:32:52 -0700761 /**
Santos Cordone38b1ff2013-08-07 12:12:16 -0700762 * Gets an existing callId for a connection, or creates one if none exists.
763 * This function does NOT set any of the Connection data onto the Call class.
764 * A separate call to updateCallFromConnection must be made for that purpose.
Santos Cordon63a84242013-07-23 13:32:52 -0700765 */
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700766 private Call getCallFromMap(HashMap<Connection, Call> map, Connection conn,
767 boolean createIfMissing) {
Santos Cordon995c8162013-07-29 09:22:22 -0700768 Call call = null;
Santos Cordon63a84242013-07-23 13:32:52 -0700769
770 // Find the call id or create if missing and requested.
771 if (conn != null) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700772 if (map.containsKey(conn)) {
773 call = map.get(conn);
Santos Cordon63a84242013-07-23 13:32:52 -0700774 } else if (createIfMissing) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700775 call = createNewCall();
776 map.put(conn, call);
Santos Cordon63a84242013-07-23 13:32:52 -0700777 }
778 }
Santos Cordon995c8162013-07-29 09:22:22 -0700779 return call;
Santos Cordon63a84242013-07-23 13:32:52 -0700780 }
781
782 /**
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700783 * Creates a brand new connection for the call.
784 */
785 private Call createNewCall() {
786 int callId;
787 int newNextCallId;
788 do {
789 callId = mNextCallId.get();
790
791 // protect against overflow
792 newNextCallId = (callId == Integer.MAX_VALUE ?
793 CALL_ID_START_VALUE : callId + 1);
794
795 // Keep looping if the change was not atomic OR the value is already taken.
796 // The call to containsValue() is linear, however, most devices support a
797 // maximum of 7 connections so it's not expensive.
798 } while (!mNextCallId.compareAndSet(callId, newNextCallId));
799
800 return new Call(callId);
801 }
802
803 /**
Santos Cordon63a84242013-07-23 13:32:52 -0700804 * Listener interface for changes to Calls.
805 */
806 public interface Listener {
Santos Cordon995c8162013-07-29 09:22:22 -0700807 void onDisconnect(Call call);
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700808 void onIncoming(Call call);
809 void onUpdate(List<Call> calls);
Chiao Cheng3f015c92013-09-06 15:56:27 -0700810 void onPostDialWait(int callId, String remainingChars);
Santos Cordon63a84242013-07-23 13:32:52 -0700811 }
Santos Cordon249efd02013-08-05 03:33:56 -0700812
813 /**
814 * Result class for accessing a call by connection.
815 */
816 public static class CallResult {
817 public Call mCall;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700818 public Call mActionableCall;
Santos Cordon249efd02013-08-05 03:33:56 -0700819 public Connection mConnection;
820
821 private CallResult(Call call, Connection connection) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700822 this(call, call, connection);
823 }
824
825 private CallResult(Call call, Call actionableCall, Connection connection) {
Santos Cordon249efd02013-08-05 03:33:56 -0700826 mCall = call;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700827 mActionableCall = actionableCall;
Santos Cordon249efd02013-08-05 03:33:56 -0700828 mConnection = connection;
829 }
830
831 public Call getCall() {
832 return mCall;
833 }
834
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700835 // The call that should be used for call actions like hanging up.
836 public Call getActionableCall() {
837 return mActionableCall;
838 }
839
Santos Cordon249efd02013-08-05 03:33:56 -0700840 public Connection getConnection() {
841 return mConnection;
842 }
843 }
Santos Cordon63a84242013-07-23 13:32:52 -0700844}