blob: a1a5cabc468704c89fcebca04f24b2fdb5caaf6f [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.Maps;
39import com.google.common.base.Preconditions;
40import com.google.common.collect.ImmutableMap;
41import com.google.common.collect.ImmutableSortedSet;
Santos Cordonad1ed6d2013-09-16 03:04:23 -070042import com.google.common.collect.Lists;
Yorke Lee814da302013-08-30 16:01:07 -070043
Santos Cordon63a84242013-07-23 13:32:52 -070044import java.util.ArrayList;
Santos Cordonad1ed6d2013-09-16 03:04:23 -070045import java.util.Collections;
Santos Cordon63a84242013-07-23 13:32:52 -070046import java.util.HashMap;
47import java.util.List;
Santos Cordon249efd02013-08-05 03:33:56 -070048import java.util.Map.Entry;
Santos Cordon63a84242013-07-23 13:32:52 -070049import java.util.concurrent.atomic.AtomicInteger;
50
51/**
52 * Creates a Call model from Call state and data received from the telephony
53 * layer. The telephony layer maintains 3 conceptual objects: Phone, Call,
54 * Connection.
55 *
56 * Phone represents the radio and there is an implementation per technology
57 * type such as GSMPhone, SipPhone, CDMAPhone, etc. Generally, we will only ever
58 * deal with one instance of this object for the lifetime of this class.
59 *
60 * There are 3 Call instances that exist for the lifetime of this class which
61 * are created by CallTracker. The three are RingingCall, ForegroundCall, and
62 * BackgroundCall.
63 *
64 * A Connection most closely resembles what the layperson would consider a call.
65 * A Connection is created when a user dials and it is "owned" by one of the
66 * three Call instances. Which of the three Calls owns the Connection changes
67 * as the Connection goes between ACTIVE, HOLD, RINGING, and other states.
68 *
69 * This class models a new Call class from Connection objects received from
70 * the telephony layer. We use Connection references as identifiers for a call;
71 * new reference = new call.
72 *
73 * TODO(klp): Create a new Call class to replace the simple call Id ints
74 * being used currently.
75 *
76 * The new Call models are parcellable for transfer via the CallHandlerService
77 * API.
78 */
79public class CallModeler extends Handler {
80
81 private static final String TAG = CallModeler.class.getSimpleName();
Santos Cordon4ad64cd2013-08-15 00:36:14 -070082 private static final boolean DBG =
83 (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
Santos Cordon63a84242013-07-23 13:32:52 -070084
85 private static final int CALL_ID_START_VALUE = 1;
Santos Cordon63a84242013-07-23 13:32:52 -070086
Santos Cordon998f42b2013-08-02 16:13:12 -070087 private final CallStateMonitor mCallStateMonitor;
88 private final CallManager mCallManager;
Santos Cordon69a69192013-08-22 14:25:42 -070089 private final CallGatewayManager mCallGatewayManager;
Santos Cordon998f42b2013-08-02 16:13:12 -070090 private final HashMap<Connection, Call> mCallMap = Maps.newHashMap();
Santos Cordon4ad64cd2013-08-15 00:36:14 -070091 private final HashMap<Connection, Call> mConfCallMap = Maps.newHashMap();
Santos Cordon998f42b2013-08-02 16:13:12 -070092 private final AtomicInteger mNextCallId = new AtomicInteger(CALL_ID_START_VALUE);
Christine Chendaf7bf62013-08-05 19:12:31 -070093 private final ArrayList<Listener> mListeners = new ArrayList<Listener>();
Christine Chenee09a492013-08-06 16:02:29 -070094 private RejectWithTextMessageManager mRejectWithTextMessageManager;
Santos Cordona5d5db82013-09-15 13:00:34 -070095 private Connection mCdmaIncomingConnection;
Santos Cordonad1ed6d2013-09-16 03:04:23 -070096 private Connection mCdmaOutgoingConnection;
Santos Cordon63a84242013-07-23 13:32:52 -070097
Christine Chenee09a492013-08-06 16:02:29 -070098 public CallModeler(CallStateMonitor callStateMonitor, CallManager callManager,
Santos Cordon69a69192013-08-22 14:25:42 -070099 RejectWithTextMessageManager rejectWithTextMessageManager,
100 CallGatewayManager callGatewayManager) {
Santos Cordon63a84242013-07-23 13:32:52 -0700101 mCallStateMonitor = callStateMonitor;
Santos Cordona3d05142013-07-29 11:25:17 -0700102 mCallManager = callManager;
Christine Chenee09a492013-08-06 16:02:29 -0700103 mRejectWithTextMessageManager = rejectWithTextMessageManager;
Santos Cordon69a69192013-08-22 14:25:42 -0700104 mCallGatewayManager = callGatewayManager;
Santos Cordon63a84242013-07-23 13:32:52 -0700105
106 mCallStateMonitor.addListener(this);
107 }
108
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700109 @Override
Santos Cordon63a84242013-07-23 13:32:52 -0700110 public void handleMessage(Message msg) {
111 switch(msg.what) {
112 case CallStateMonitor.PHONE_NEW_RINGING_CONNECTION:
Santos Cordona5d5db82013-09-15 13:00:34 -0700113 onNewRingingConnection((Connection) ((AsyncResult) msg.obj).result);
Santos Cordon63a84242013-07-23 13:32:52 -0700114 break;
115 case CallStateMonitor.PHONE_DISCONNECT:
Santos Cordona5d5db82013-09-15 13:00:34 -0700116 onDisconnect((Connection) ((AsyncResult) msg.obj).result);
Santos Cordon995c8162013-07-29 09:22:22 -0700117 break;
118 case CallStateMonitor.PHONE_STATE_CHANGED:
119 onPhoneStateChanged((AsyncResult) msg.obj);
120 break;
Chiao Cheng3f015c92013-09-06 15:56:27 -0700121 case CallStateMonitor.PHONE_ON_DIAL_CHARS:
122 onPostDialChars((AsyncResult) msg.obj, (char) msg.arg1);
123 break;
Santos Cordon63a84242013-07-23 13:32:52 -0700124 default:
125 break;
126 }
127 }
128
Christine Chendaf7bf62013-08-05 19:12:31 -0700129 public void addListener(Listener listener) {
Santos Cordon63a84242013-07-23 13:32:52 -0700130 Preconditions.checkNotNull(listener);
Christine Chendaf7bf62013-08-05 19:12:31 -0700131 Preconditions.checkNotNull(mListeners);
Christine Chen4748abd2013-08-07 15:44:15 -0700132 if (!mListeners.contains(listener)) {
133 mListeners.add(listener);
134 }
Santos Cordon998f42b2013-08-02 16:13:12 -0700135 }
136
137 public List<Call> getFullList() {
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700138 final List<Call> calls =
139 Lists.newArrayListWithCapacity(mCallMap.size() + mConfCallMap.size());
140 calls.addAll(mCallMap.values());
141 calls.addAll(mConfCallMap.values());
142 return calls;
Santos Cordon63a84242013-07-23 13:32:52 -0700143 }
144
Santos Cordon249efd02013-08-05 03:33:56 -0700145 public CallResult getCallWithId(int callId) {
146 // max 8 connections, so this should be fast even through we are traversing the entire map.
147 for (Entry<Connection, Call> entry : mCallMap.entrySet()) {
148 if (entry.getValue().getCallId() == callId) {
149 return new CallResult(entry.getValue(), entry.getKey());
150 }
151 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700152
153 for (Entry<Connection, Call> entry : mConfCallMap.entrySet()) {
154 if (entry.getValue().getCallId() == callId) {
Christine Chen69050202013-09-14 15:36:35 -0700155 return new CallResult(entry.getValue(), entry.getKey());
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700156 }
157 }
Santos Cordon249efd02013-08-05 03:33:56 -0700158 return null;
159 }
160
Santos Cordonaf763a12013-08-19 20:04:58 -0700161 public boolean hasLiveCall() {
162 return hasLiveCallInternal(mCallMap) ||
163 hasLiveCallInternal(mConfCallMap);
164 }
165
Santos Cordona5d5db82013-09-15 13:00:34 -0700166 public void onCdmaCallWaiting(CdmaCallWaitingNotification callWaitingInfo) {
167 // We dont get the traditional onIncomingCall notification for cdma call waiting,
168 // but the Connection does actually exist. We need to find it in the set of ringing calls
169 // and pass it through our normal incoming logic.
170 final com.android.internal.telephony.Call teleCall =
171 mCallManager.getFirstActiveRingingCall();
172
173 if (teleCall.getState() == com.android.internal.telephony.Call.State.WAITING) {
174 Connection connection = teleCall.getLatestConnection();
175
176 if (connection != null) {
177 String number = connection.getAddress();
178 if (number != null && number.equals(callWaitingInfo.number)) {
179 Call call = onNewRingingConnection(connection);
180 mCdmaIncomingConnection = connection;
181 return;
182 }
183 }
184 }
185
186 Log.e(TAG, "CDMA Call waiting notification without a matching connection.");
187 }
188
189 public void onCdmaCallWaitingReject() {
190 // Cdma call was rejected...
191 if (mCdmaIncomingConnection != null) {
192 onDisconnect(mCdmaIncomingConnection);
193 mCdmaIncomingConnection = null;
194 } else {
195 Log.e(TAG, "CDMA Call waiting rejection without an incoming call.");
196 }
197 }
198
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700199 /**
200 * CDMA Calls have no sense of "dialing" state. For outgoing calls 3way calls we want to
201 * mimick this state so that the the UI can notify the user that there is a "dialing"
202 * call.
203 */
204 public void setCdmaOutgoing3WayCall(Connection connection) {
205 boolean wasSet = mCdmaOutgoingConnection != null;
206
207 mCdmaOutgoingConnection = connection;
208
209 // If we reset the connection, that mean we can now tell the user that the call is actually
210 // part of the conference call and move it out of the dialing state. To do this, issue a
211 // new update completely.
212 if (wasSet && mCdmaOutgoingConnection == null) {
213 onPhoneStateChanged(null);
214 }
215 }
216
Santos Cordonaf763a12013-08-19 20:04:58 -0700217 private boolean hasLiveCallInternal(HashMap<Connection, Call> map) {
218 for (Call call : map.values()) {
219 final int state = call.getState();
220 if (state == Call.State.ACTIVE ||
221 state == Call.State.CALL_WAITING ||
222 state == Call.State.CONFERENCED ||
223 state == Call.State.DIALING ||
224 state == Call.State.INCOMING ||
225 state == Call.State.ONHOLD) {
226 return true;
227 }
228 }
229 return false;
230 }
231
Santos Cordon2b73bd62013-08-27 14:53:43 -0700232 public boolean hasOutstandingActiveOrDialingCall() {
233 return hasOutstandingActiveOrDialingCallInternal(mCallMap) ||
234 hasOutstandingActiveOrDialingCallInternal(mConfCallMap);
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700235 }
236
Santos Cordon2b73bd62013-08-27 14:53:43 -0700237 private static boolean hasOutstandingActiveOrDialingCallInternal(
238 HashMap<Connection, Call> map) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700239 for (Call call : map.values()) {
240 final int state = call.getState();
Santos Cordon2b73bd62013-08-27 14:53:43 -0700241 if (state == Call.State.ACTIVE ||
242 state == Call.State.DIALING) {
Santos Cordon2eaff902013-08-05 04:37:55 -0700243 return true;
244 }
245 }
246
247 return false;
248 }
249
Chiao Cheng3f015c92013-09-06 15:56:27 -0700250
251 /**
252 * Handles the POST_ON_DIAL_CHARS message from the Phone (see our call to
253 * mPhone.setOnPostDialCharacter() above.)
254 *
255 * TODO: NEED TO TEST THIS SEQUENCE now that we no longer handle "dialable" key events here in
256 * the InCallScreen: we do directly to the Dialer UI instead. Similarly, we may now need to go
257 * directly to the Dialer to handle POST_ON_DIAL_CHARS too.
258 */
259 private void onPostDialChars(AsyncResult r, char ch) {
260 final Connection c = (Connection) r.result;
261
262 if (c != null) {
263 final Connection.PostDialState state = (Connection.PostDialState) r.userObj;
264
265 switch (state) {
266 // TODO(klp): add other post dial related functions
267 case WAIT:
268 final Call call = getCallFromMap(mCallMap, c, false);
269 if (call == null) {
270 Log.i(TAG, "Call no longer exists. Skipping onPostDialWait().");
271 } else {
272 for (Listener mListener : mListeners) {
273 mListener.onPostDialWait(call.getCallId(),
274 c.getRemainingPostDialString());
275 }
276 }
277 break;
278
279 default:
280 break;
281 }
282 }
283 }
284
Santos Cordona5d5db82013-09-15 13:00:34 -0700285 private Call onNewRingingConnection(Connection conn) {
Santos Cordon2b73bd62013-08-27 14:53:43 -0700286 Log.i(TAG, "onNewRingingConnection");
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700287 final Call call = getCallFromMap(mCallMap, conn, true);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700288
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700289 updateCallFromConnection(call, conn, false);
Santos Cordon63a84242013-07-23 13:32:52 -0700290
Christine Chendaf7bf62013-08-05 19:12:31 -0700291 for (int i = 0; i < mListeners.size(); ++i) {
Christine Chenee09a492013-08-06 16:02:29 -0700292 if (call != null) {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700293 mListeners.get(i).onIncoming(call);
Christine Chenee09a492013-08-06 16:02:29 -0700294 }
Santos Cordon63a84242013-07-23 13:32:52 -0700295 }
Santos Cordona5d5db82013-09-15 13:00:34 -0700296
297 return call;
Santos Cordon63a84242013-07-23 13:32:52 -0700298 }
299
Santos Cordona5d5db82013-09-15 13:00:34 -0700300 private void onDisconnect(Connection conn) {
Santos Cordon2b73bd62013-08-27 14:53:43 -0700301 Log.i(TAG, "onDisconnect");
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700302 final Call call = getCallFromMap(mCallMap, conn, false);
Santos Cordon63a84242013-07-23 13:32:52 -0700303
Santos Cordon995c8162013-07-29 09:22:22 -0700304 if (call != null) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700305 final boolean wasConferenced = call.getState() == State.CONFERENCED;
306
307 updateCallFromConnection(call, conn, false);
Santos Cordon63a84242013-07-23 13:32:52 -0700308
Christine Chendaf7bf62013-08-05 19:12:31 -0700309 for (int i = 0; i < mListeners.size(); ++i) {
310 mListeners.get(i).onDisconnect(call);
Santos Cordon63a84242013-07-23 13:32:52 -0700311 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700312
313 // If it was a conferenced call, we need to run the entire update
314 // to make the proper changes to parent conference calls.
315 if (wasConferenced) {
316 onPhoneStateChanged(null);
317 }
318
319 mCallMap.remove(conn);
Santos Cordon63a84242013-07-23 13:32:52 -0700320 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700321
322 // TODO(klp): Do a final check to see if there are any active calls.
323 // If there are not, totally cancel all calls
Santos Cordon63a84242013-07-23 13:32:52 -0700324 }
325
Santos Cordona3d05142013-07-29 11:25:17 -0700326 /**
327 * Called when the phone state changes.
Santos Cordona3d05142013-07-29 11:25:17 -0700328 */
Santos Cordon995c8162013-07-29 09:22:22 -0700329 private void onPhoneStateChanged(AsyncResult r) {
Santos Cordon2b73bd62013-08-27 14:53:43 -0700330 Log.i(TAG, "onPhoneStateChanged: ");
Santos Cordon998f42b2013-08-02 16:13:12 -0700331 final List<Call> updatedCalls = Lists.newArrayList();
332 doUpdate(false, updatedCalls);
333
Santos Cordon71d5c6e2013-09-05 21:34:33 -0700334 if (updatedCalls.size() > 0) {
335 for (int i = 0; i < mListeners.size(); ++i) {
336 mListeners.get(i).onUpdate(updatedCalls);
337 }
Santos Cordon998f42b2013-08-02 16:13:12 -0700338 }
339 }
340
341
342 /**
343 * Go through the Calls from CallManager and return the list of calls that were updated.
344 * Or, the full list if requested.
345 */
346 private void doUpdate(boolean fullUpdate, List<Call> out) {
Santos Cordona3d05142013-07-29 11:25:17 -0700347 final List<com.android.internal.telephony.Call> telephonyCalls = Lists.newArrayList();
348 telephonyCalls.addAll(mCallManager.getRingingCalls());
349 telephonyCalls.addAll(mCallManager.getForegroundCalls());
350 telephonyCalls.addAll(mCallManager.getBackgroundCalls());
351
Santos Cordona3d05142013-07-29 11:25:17 -0700352 // Cycle through all the Connections on all the Calls. Update our Call objects
353 // to reflect any new state and send the updated Call objects to the handler service.
354 for (com.android.internal.telephony.Call telephonyCall : telephonyCalls) {
Santos Cordona3d05142013-07-29 11:25:17 -0700355
356 for (Connection connection : telephonyCall.getConnections()) {
Santos Cordona5d5db82013-09-15 13:00:34 -0700357 if (DBG) Log.d(TAG, "connection: " + connection);
358
Santos Cordon12a03aa2013-09-12 23:34:05 -0700359 // We only send updates for live calls which are not incoming (ringing).
360 // Disconnected and incoming calls are handled by onDisconnect and
361 // onNewRingingConnection.
362 boolean shouldUpdate = connection.getState().isAlive() &&
Santos Cordon71d5c6e2013-09-05 21:34:33 -0700363 !connection.getState().isRinging();
364
365 // New connections return a Call with INVALID state, which does not translate to
Santos Cordone38b1ff2013-08-07 12:12:16 -0700366 // a state in the internal.telephony.Call object. This ensures that staleness
367 // check below fails and we always add the item to the update list if it is new.
Santos Cordon12a03aa2013-09-12 23:34:05 -0700368 final Call call = getCallFromMap(mCallMap, connection, shouldUpdate /* create */);
Santos Cordon71d5c6e2013-09-05 21:34:33 -0700369
Santos Cordon12a03aa2013-09-12 23:34:05 -0700370 if (call == null || !shouldUpdate) {
Santos Cordona5d5db82013-09-15 13:00:34 -0700371 if (DBG) Log.d(TAG, "update skipped");
Santos Cordon71d5c6e2013-09-05 21:34:33 -0700372 continue;
373 }
Santos Cordona3d05142013-07-29 11:25:17 -0700374
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700375 boolean changed = updateCallFromConnection(call, connection, false);
Santos Cordon2b73bd62013-08-27 14:53:43 -0700376
Santos Cordone38b1ff2013-08-07 12:12:16 -0700377 if (fullUpdate || changed) {
Santos Cordon998f42b2013-08-02 16:13:12 -0700378 out.add(call);
Santos Cordona3d05142013-07-29 11:25:17 -0700379 }
380 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700381
382 // We do a second loop to address conference call scenarios. We do this as a separate
383 // loop to ensure all child calls are up to date before we start updating the parent
384 // conference calls.
385 for (Connection connection : telephonyCall.getConnections()) {
386 updateForConferenceCalls(connection, out);
387 }
388
Santos Cordona3d05142013-07-29 11:25:17 -0700389 }
Santos Cordona3d05142013-07-29 11:25:17 -0700390 }
391
Santos Cordone38b1ff2013-08-07 12:12:16 -0700392 /**
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700393 * Checks to see if the connection is the first connection in a conference call.
394 * If it is a conference call, we will create a new Conference Call object or
395 * update the existing conference call object for that connection.
396 * If it is not a conference call but a previous associated conference call still exists,
397 * we mark it as idle and remove it from the map.
398 * In both cases above, we add the Calls to be updated to the UI.
399 * @param connection The connection object to check.
400 * @param updatedCalls List of 'updated' calls that will be sent to the UI.
401 */
402 private boolean updateForConferenceCalls(Connection connection, List<Call> updatedCalls) {
403 // We consider this connection a conference connection if the call it
Yorke Leecd3f9692013-09-14 13:51:27 -0700404 // belongs to is a multiparty call AND it is the first live connection.
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700405 final boolean isConferenceCallConnection = isPartOfLiveConferenceCall(connection) &&
Yorke Leecd3f9692013-09-14 13:51:27 -0700406 getEarliestLiveConnection(connection.getCall()) == connection;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700407
408 boolean changed = false;
409
410 // If this connection is the main connection for the conference call, then create or update
411 // a Call object for that conference call.
412 if (isConferenceCallConnection) {
413 final Call confCall = getCallFromMap(mConfCallMap, connection, true);
414 changed = updateCallFromConnection(confCall, connection, true);
415
416 if (changed) {
417 updatedCalls.add(confCall);
418 }
419
420 if (DBG) Log.d(TAG, "Updating a conference call: " + confCall);
421
422 // It is possible that through a conference call split, there may be lingering conference
423 // calls where this connection was the main connection. We clean those up here.
424 } else {
425 final Call oldConfCall = getCallFromMap(mConfCallMap, connection, false);
426
427 // We found a conference call for this connection, which is no longer a conference call.
428 // Kill it!
429 if (oldConfCall != null) {
430 if (DBG) Log.d(TAG, "Cleaning up an old conference call: " + oldConfCall);
431 mConfCallMap.remove(connection);
432 oldConfCall.setState(State.IDLE);
433 changed = true;
434
435 // add to the list of calls to update
436 updatedCalls.add(oldConfCall);
437 }
438 }
439
440 return changed;
441 }
442
Yorke Leecd3f9692013-09-14 13:51:27 -0700443 private Connection getEarliestLiveConnection(com.android.internal.telephony.Call call) {
444 final List<Connection> connections = call.getConnections();
445 final int size = connections.size();
446 Connection earliestConn = null;
447 long earliestTime = Long.MAX_VALUE;
448 for (int i = 0; i < size; i++) {
449 final Connection connection = connections.get(i);
450 if (!connection.isAlive()) continue;
451 final long time = connection.getCreateTime();
452 if (time < earliestTime) {
453 earliestTime = time;
454 earliestConn = connection;
455 }
456 }
457 return earliestConn;
458 }
459
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700460 /**
Santos Cordon69a69192013-08-22 14:25:42 -0700461 * Sets the new call state onto the call and performs some additional logic
462 * associated with setting the state.
463 */
464 private void setNewState(Call call, int newState, Connection connection) {
465 Preconditions.checkState(call.getState() != newState);
466
467 // When starting an outgoing call, we need to grab gateway information
468 // for the call, if available, and set it.
469 final RawGatewayInfo info = mCallGatewayManager.getGatewayInfo(connection);
470
471 if (newState == Call.State.DIALING) {
472 if (!info.isEmpty()) {
473 call.setGatewayNumber(info.getFormattedGatewayNumber());
474 call.setGatewayPackage(info.packageName);
475 }
476 } else if (!Call.State.isConnected(newState)) {
477 mCallGatewayManager.clearGatewayData(connection);
478 }
479
480 call.setState(newState);
481 }
482
483 /**
Santos Cordone38b1ff2013-08-07 12:12:16 -0700484 * Updates the Call properties to match the state of the connection object
485 * that it represents.
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700486 * @param call The call object to update.
487 * @param connection The connection object from which to update call.
488 * @param isForConference There are slight differences in how we populate data for conference
489 * calls. This boolean tells us which method to use.
Santos Cordone38b1ff2013-08-07 12:12:16 -0700490 */
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700491 private boolean updateCallFromConnection(Call call, Connection connection,
492 boolean isForConference) {
Santos Cordone38b1ff2013-08-07 12:12:16 -0700493 boolean changed = false;
494
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700495 final int newState = translateStateFromTelephony(connection, isForConference);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700496
497 if (call.getState() != newState) {
Santos Cordon69a69192013-08-22 14:25:42 -0700498 setNewState(call, newState, connection);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700499 changed = true;
500 }
501
Santos Cordone38b1ff2013-08-07 12:12:16 -0700502 final Call.DisconnectCause newDisconnectCause =
503 translateDisconnectCauseFromTelephony(connection.getDisconnectCause());
504 if (call.getDisconnectCause() != newDisconnectCause) {
505 call.setDisconnectCause(newDisconnectCause);
506 changed = true;
507 }
508
Santos Cordonbbe8ecf2013-08-13 15:26:18 -0700509 final long oldConnectTime = call.getConnectTime();
510 if (oldConnectTime != connection.getConnectTime()) {
511 call.setConnectTime(connection.getConnectTime());
512 changed = true;
513 }
514
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700515 if (!isForConference) {
Santos Cordon69a69192013-08-22 14:25:42 -0700516 // Number
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700517 final String oldNumber = call.getNumber();
Santos Cordon69a69192013-08-22 14:25:42 -0700518 String newNumber = connection.getAddress();
519 RawGatewayInfo info = mCallGatewayManager.getGatewayInfo(connection);
520 if (!info.isEmpty()) {
521 newNumber = info.trueNumber;
522 }
523 if (TextUtils.isEmpty(oldNumber) || !oldNumber.equals(newNumber)) {
524 call.setNumber(newNumber);
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700525 changed = true;
526 }
527
Santos Cordon69a69192013-08-22 14:25:42 -0700528 // Number presentation
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700529 final int newNumberPresentation = connection.getNumberPresentation();
530 if (call.getNumberPresentation() != newNumberPresentation) {
531 call.setNumberPresentation(newNumberPresentation);
532 changed = true;
533 }
534
Santos Cordon69a69192013-08-22 14:25:42 -0700535 // Name
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700536 final String oldCnapName = call.getCnapName();
537 if (TextUtils.isEmpty(oldCnapName) || !oldCnapName.equals(connection.getCnapName())) {
538 call.setCnapName(connection.getCnapName());
539 changed = true;
540 }
Santos Cordon69a69192013-08-22 14:25:42 -0700541
542 // Name Presentation
543 final int newCnapNamePresentation = connection.getCnapNamePresentation();
544 if (call.getCnapNamePresentation() != newCnapNamePresentation) {
545 call.setCnapNamePresentation(newCnapNamePresentation);
546 changed = true;
547 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700548 } else {
549
550 // update the list of children by:
551 // 1) Saving the old set
552 // 2) Removing all children
553 // 3) Adding the correct children into the Call
554 // 4) Comparing the new children set with the old children set
555 ImmutableSortedSet<Integer> oldSet = call.getChildCallIds();
556 call.removeAllChildren();
557
558 if (connection.getCall() != null) {
559 for (Connection childConn : connection.getCall().getConnections()) {
560 final Call childCall = getCallFromMap(mCallMap, childConn, false);
561 if (childCall != null && childConn.isAlive()) {
562 call.addChildId(childCall.getCallId());
563 }
564 }
565 }
Christine Chen45277022013-09-05 10:55:37 -0700566 changed |= !oldSet.equals(call.getChildCallIds());
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700567 }
568
Santos Cordoneead6ec2013-08-07 22:16:33 -0700569 /**
570 * !!! Uses values from connection and call collected above so this part must be last !!!
571 */
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700572 final int newCapabilities = getCapabilitiesFor(connection, call, isForConference);
Santos Cordon26e7b242013-08-07 21:15:45 -0700573 if (call.getCapabilities() != newCapabilities) {
574 call.setCapabilities(newCapabilities);
575 changed = true;
576 }
577
Santos Cordone38b1ff2013-08-07 12:12:16 -0700578 return changed;
579 }
580
Santos Cordon26e7b242013-08-07 21:15:45 -0700581 /**
582 * Returns a mask of capabilities for the connection such as merge, hold, etc.
583 */
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700584 private int getCapabilitiesFor(Connection connection, Call call, boolean isForConference) {
Santos Cordoneead6ec2013-08-07 22:16:33 -0700585 final boolean callIsActive = (call.getState() == Call.State.ACTIVE);
586 final Phone phone = connection.getCall().getPhone();
587
Santos Cordoneead6ec2013-08-07 22:16:33 -0700588 boolean canAddCall = false;
589 boolean canMergeCall = false;
590 boolean canSwapCall = false;
Yorke Lee814da302013-08-30 16:01:07 -0700591 boolean canRespondViaText = false;
Christine Chenaf2fd0a2013-09-13 16:27:40 -0700592 boolean canMute = false;
593
594 final boolean supportHold = PhoneUtils.okToSupportHold(mCallManager);
Christine Chen94853bc2013-09-17 16:53:33 -0700595 final boolean canHold = (supportHold ? PhoneUtils.okToHoldCall(mCallManager) : false);
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700596 final boolean genericConf = isForConference &&
597 (connection.getCall().getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA);
Santos Cordoneead6ec2013-08-07 22:16:33 -0700598
599 // only applies to active calls
600 if (callIsActive) {
Santos Cordoneead6ec2013-08-07 22:16:33 -0700601 canMergeCall = PhoneUtils.okToMergeCalls(mCallManager);
602 canSwapCall = PhoneUtils.okToSwapCalls(mCallManager);
603 }
604
Christine Chenaf2fd0a2013-09-13 16:27:40 -0700605 canAddCall = PhoneUtils.okToAddCall(mCallManager);
606
607 // "Mute": only enabled when the foreground call is ACTIVE.
608 // (It's meaningless while on hold, or while DIALING/ALERTING.)
609 // It's also explicitly disabled during emergency calls or if
610 // emergency callback mode (ECM) is active.
611 boolean isEmergencyCall = false;
612 if (connection != null) {
613 isEmergencyCall = PhoneNumberUtils.isLocalEmergencyNumber(connection.getAddress(),
614 phone.getContext());
615 }
616 boolean isECM = PhoneUtils.isPhoneInEcm(phone);
617 if (isEmergencyCall || isECM) { // disable "Mute" item
618 canMute = false;
619 } else {
620 canMute = callIsActive;
621 }
622
Yorke Lee814da302013-08-30 16:01:07 -0700623 canRespondViaText = RejectWithTextMessageManager.allowRespondViaSmsForCall(call,
624 connection);
625
Santos Cordoneead6ec2013-08-07 22:16:33 -0700626 // special rules section!
627 // CDMA always has Add
628 if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
629 canAddCall = true;
Santos Cordoneead6ec2013-08-07 22:16:33 -0700630 }
631
Santos Cordon26e7b242013-08-07 21:15:45 -0700632 int retval = 0x0;
Santos Cordoneead6ec2013-08-07 22:16:33 -0700633 if (canHold) {
Santos Cordon26e7b242013-08-07 21:15:45 -0700634 retval |= Capabilities.HOLD;
635 }
Christine Chenaf2fd0a2013-09-13 16:27:40 -0700636 if (supportHold) {
637 retval |= Capabilities.SUPPORT_HOLD;
638 }
Santos Cordoneead6ec2013-08-07 22:16:33 -0700639 if (canAddCall) {
640 retval |= Capabilities.ADD_CALL;
641 }
642 if (canMergeCall) {
643 retval |= Capabilities.MERGE_CALLS;
644 }
645 if (canSwapCall) {
646 retval |= Capabilities.SWAP_CALLS;
647 }
Yorke Lee814da302013-08-30 16:01:07 -0700648 if (canRespondViaText) {
649 retval |= Capabilities.RESPOND_VIA_TEXT;
650 }
Christine Chenaf2fd0a2013-09-13 16:27:40 -0700651 if (canMute) {
652 retval |= Capabilities.MUTE;
653 }
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700654 if (genericConf) {
655 retval |= Capabilities.GENERIC_CONFERENCE;
656 }
Yorke Lee814da302013-08-30 16:01:07 -0700657
Santos Cordon26e7b242013-08-07 21:15:45 -0700658 return retval;
659 }
660
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700661 /**
662 * Returns true if the Connection is part of a multiparty call.
663 * We do this by checking the isMultiparty() method of the telephony.Call object and also
664 * checking to see if more than one of it's children is alive.
665 */
666 private boolean isPartOfLiveConferenceCall(Connection connection) {
667 if (connection.getCall() != null && connection.getCall().isMultiparty()) {
668 int count = 0;
669 for (Connection currConn : connection.getCall().getConnections()) {
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700670
671 // Only count connections which are alive and never cound the special
672 // "dialing" 3way call for CDMA calls.
673 if (currConn.isAlive() && currConn != mCdmaOutgoingConnection) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700674 count++;
675 if (count >= 2) {
676 return true;
677 }
678 }
679 }
680 }
681 return false;
682 }
683
684 private int translateStateFromTelephony(Connection connection, boolean isForConference) {
685
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700686 // For the "fake" outgoing CDMA call, we need to always treat it as an outgoing call.
687 if (mCdmaOutgoingConnection == connection) {
688 return State.DIALING;
689 }
690
Santos Cordona3d05142013-07-29 11:25:17 -0700691 int retval = State.IDLE;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700692 switch (connection.getState()) {
Santos Cordona3d05142013-07-29 11:25:17 -0700693 case ACTIVE:
694 retval = State.ACTIVE;
695 break;
696 case INCOMING:
697 retval = State.INCOMING;
698 break;
699 case DIALING:
700 case ALERTING:
701 retval = State.DIALING;
702 break;
703 case WAITING:
704 retval = State.CALL_WAITING;
705 break;
706 case HOLDING:
707 retval = State.ONHOLD;
708 break;
Santos Cordone38b1ff2013-08-07 12:12:16 -0700709 case DISCONNECTED:
710 case DISCONNECTING:
711 retval = State.DISCONNECTED;
Santos Cordona3d05142013-07-29 11:25:17 -0700712 default:
713 }
714
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700715 // If we are dealing with a potential child call (not the parent conference call),
716 // the check to see if we have to set the state to CONFERENCED.
717 if (!isForConference) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700718 // if the connection is part of a multiparty call, and it is live,
719 // annotate it with CONFERENCED state instead.
720 if (isPartOfLiveConferenceCall(connection) && connection.isAlive()) {
721 return State.CONFERENCED;
722 }
723 }
724
Santos Cordona3d05142013-07-29 11:25:17 -0700725 return retval;
Santos Cordon995c8162013-07-29 09:22:22 -0700726 }
727
Santos Cordone38b1ff2013-08-07 12:12:16 -0700728 private final ImmutableMap<Connection.DisconnectCause, Call.DisconnectCause> CAUSE_MAP =
729 ImmutableMap.<Connection.DisconnectCause, Call.DisconnectCause>builder()
730 .put(Connection.DisconnectCause.BUSY, Call.DisconnectCause.BUSY)
731 .put(Connection.DisconnectCause.CALL_BARRED, Call.DisconnectCause.CALL_BARRED)
732 .put(Connection.DisconnectCause.CDMA_ACCESS_BLOCKED,
733 Call.DisconnectCause.CDMA_ACCESS_BLOCKED)
734 .put(Connection.DisconnectCause.CDMA_ACCESS_FAILURE,
735 Call.DisconnectCause.CDMA_ACCESS_FAILURE)
736 .put(Connection.DisconnectCause.CDMA_DROP, Call.DisconnectCause.CDMA_DROP)
737 .put(Connection.DisconnectCause.CDMA_INTERCEPT, Call.DisconnectCause.CDMA_INTERCEPT)
738 .put(Connection.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE,
739 Call.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE)
740 .put(Connection.DisconnectCause.CDMA_NOT_EMERGENCY,
741 Call.DisconnectCause.CDMA_NOT_EMERGENCY)
742 .put(Connection.DisconnectCause.CDMA_PREEMPTED, Call.DisconnectCause.CDMA_PREEMPTED)
743 .put(Connection.DisconnectCause.CDMA_REORDER, Call.DisconnectCause.CDMA_REORDER)
744 .put(Connection.DisconnectCause.CDMA_RETRY_ORDER,
745 Call.DisconnectCause.CDMA_RETRY_ORDER)
746 .put(Connection.DisconnectCause.CDMA_SO_REJECT, Call.DisconnectCause.CDMA_SO_REJECT)
747 .put(Connection.DisconnectCause.CONGESTION, Call.DisconnectCause.CONGESTION)
748 .put(Connection.DisconnectCause.CS_RESTRICTED, Call.DisconnectCause.CS_RESTRICTED)
749 .put(Connection.DisconnectCause.CS_RESTRICTED_EMERGENCY,
750 Call.DisconnectCause.CS_RESTRICTED_EMERGENCY)
751 .put(Connection.DisconnectCause.CS_RESTRICTED_NORMAL,
752 Call.DisconnectCause.CS_RESTRICTED_NORMAL)
753 .put(Connection.DisconnectCause.ERROR_UNSPECIFIED,
754 Call.DisconnectCause.ERROR_UNSPECIFIED)
755 .put(Connection.DisconnectCause.FDN_BLOCKED, Call.DisconnectCause.FDN_BLOCKED)
756 .put(Connection.DisconnectCause.ICC_ERROR, Call.DisconnectCause.ICC_ERROR)
757 .put(Connection.DisconnectCause.INCOMING_MISSED,
758 Call.DisconnectCause.INCOMING_MISSED)
759 .put(Connection.DisconnectCause.INCOMING_REJECTED,
760 Call.DisconnectCause.INCOMING_REJECTED)
761 .put(Connection.DisconnectCause.INVALID_CREDENTIALS,
762 Call.DisconnectCause.INVALID_CREDENTIALS)
763 .put(Connection.DisconnectCause.INVALID_NUMBER,
764 Call.DisconnectCause.INVALID_NUMBER)
765 .put(Connection.DisconnectCause.LIMIT_EXCEEDED, Call.DisconnectCause.LIMIT_EXCEEDED)
766 .put(Connection.DisconnectCause.LOCAL, Call.DisconnectCause.LOCAL)
767 .put(Connection.DisconnectCause.LOST_SIGNAL, Call.DisconnectCause.LOST_SIGNAL)
768 .put(Connection.DisconnectCause.MMI, Call.DisconnectCause.MMI)
769 .put(Connection.DisconnectCause.NORMAL, Call.DisconnectCause.NORMAL)
770 .put(Connection.DisconnectCause.NOT_DISCONNECTED,
771 Call.DisconnectCause.NOT_DISCONNECTED)
772 .put(Connection.DisconnectCause.NUMBER_UNREACHABLE,
773 Call.DisconnectCause.NUMBER_UNREACHABLE)
774 .put(Connection.DisconnectCause.OUT_OF_NETWORK, Call.DisconnectCause.OUT_OF_NETWORK)
775 .put(Connection.DisconnectCause.OUT_OF_SERVICE, Call.DisconnectCause.OUT_OF_SERVICE)
776 .put(Connection.DisconnectCause.POWER_OFF, Call.DisconnectCause.POWER_OFF)
777 .put(Connection.DisconnectCause.SERVER_ERROR, Call.DisconnectCause.SERVER_ERROR)
778 .put(Connection.DisconnectCause.SERVER_UNREACHABLE,
779 Call.DisconnectCause.SERVER_UNREACHABLE)
780 .put(Connection.DisconnectCause.TIMED_OUT, Call.DisconnectCause.TIMED_OUT)
781 .put(Connection.DisconnectCause.UNOBTAINABLE_NUMBER,
782 Call.DisconnectCause.UNOBTAINABLE_NUMBER)
783 .build();
784
785 private Call.DisconnectCause translateDisconnectCauseFromTelephony(
786 Connection.DisconnectCause causeSource) {
787
788 if (CAUSE_MAP.containsKey(causeSource)) {
789 return CAUSE_MAP.get(causeSource);
790 }
791
792 return Call.DisconnectCause.UNKNOWN;
793 }
794
Santos Cordon63a84242013-07-23 13:32:52 -0700795 /**
Santos Cordone38b1ff2013-08-07 12:12:16 -0700796 * Gets an existing callId for a connection, or creates one if none exists.
797 * This function does NOT set any of the Connection data onto the Call class.
798 * A separate call to updateCallFromConnection must be made for that purpose.
Santos Cordon63a84242013-07-23 13:32:52 -0700799 */
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700800 private Call getCallFromMap(HashMap<Connection, Call> map, Connection conn,
801 boolean createIfMissing) {
Santos Cordon995c8162013-07-29 09:22:22 -0700802 Call call = null;
Santos Cordon63a84242013-07-23 13:32:52 -0700803
804 // Find the call id or create if missing and requested.
805 if (conn != null) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700806 if (map.containsKey(conn)) {
807 call = map.get(conn);
Santos Cordon63a84242013-07-23 13:32:52 -0700808 } else if (createIfMissing) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700809 call = createNewCall();
810 map.put(conn, call);
Santos Cordon63a84242013-07-23 13:32:52 -0700811 }
812 }
Santos Cordon995c8162013-07-29 09:22:22 -0700813 return call;
Santos Cordon63a84242013-07-23 13:32:52 -0700814 }
815
816 /**
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700817 * Creates a brand new connection for the call.
818 */
819 private Call createNewCall() {
820 int callId;
821 int newNextCallId;
822 do {
823 callId = mNextCallId.get();
824
825 // protect against overflow
826 newNextCallId = (callId == Integer.MAX_VALUE ?
827 CALL_ID_START_VALUE : callId + 1);
828
829 // Keep looping if the change was not atomic OR the value is already taken.
830 // The call to containsValue() is linear, however, most devices support a
831 // maximum of 7 connections so it's not expensive.
832 } while (!mNextCallId.compareAndSet(callId, newNextCallId));
833
834 return new Call(callId);
835 }
836
837 /**
Santos Cordon63a84242013-07-23 13:32:52 -0700838 * Listener interface for changes to Calls.
839 */
840 public interface Listener {
Santos Cordon995c8162013-07-29 09:22:22 -0700841 void onDisconnect(Call call);
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700842 void onIncoming(Call call);
843 void onUpdate(List<Call> calls);
Chiao Cheng3f015c92013-09-06 15:56:27 -0700844 void onPostDialWait(int callId, String remainingChars);
Santos Cordon63a84242013-07-23 13:32:52 -0700845 }
Santos Cordon249efd02013-08-05 03:33:56 -0700846
847 /**
848 * Result class for accessing a call by connection.
849 */
850 public static class CallResult {
851 public Call mCall;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700852 public Call mActionableCall;
Santos Cordon249efd02013-08-05 03:33:56 -0700853 public Connection mConnection;
854
855 private CallResult(Call call, Connection connection) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700856 this(call, call, connection);
857 }
858
859 private CallResult(Call call, Call actionableCall, Connection connection) {
Santos Cordon249efd02013-08-05 03:33:56 -0700860 mCall = call;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700861 mActionableCall = actionableCall;
Santos Cordon249efd02013-08-05 03:33:56 -0700862 mConnection = connection;
863 }
864
865 public Call getCall() {
866 return mCall;
867 }
868
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700869 // The call that should be used for call actions like hanging up.
870 public Call getActionableCall() {
871 return mActionableCall;
872 }
873
Santos Cordon249efd02013-08-05 03:33:56 -0700874 public Connection getConnection() {
875 return mConnection;
876 }
877 }
Santos Cordon63a84242013-07-23 13:32:52 -0700878}