blob: 5258f8804cab0ca060ecb29ace6effa5052665b3 [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;
Santos Cordon25008582013-11-12 13:39:40 -080039import com.google.android.collect.Sets;
Yorke Lee814da302013-08-30 16:01:07 -070040import com.google.common.base.Preconditions;
41import com.google.common.collect.ImmutableMap;
42import com.google.common.collect.ImmutableSortedSet;
Santos Cordonad1ed6d2013-09-16 03:04:23 -070043import com.google.common.collect.Lists;
Yorke Lee814da302013-08-30 16:01:07 -070044
Santos Cordon63a84242013-07-23 13:32:52 -070045import java.util.ArrayList;
Santos Cordonad1ed6d2013-09-16 03:04:23 -070046import java.util.Collections;
Santos Cordon63a84242013-07-23 13:32:52 -070047import java.util.HashMap;
48import java.util.List;
Santos Cordon249efd02013-08-05 03:33:56 -070049import java.util.Map.Entry;
Santos Cordon25008582013-11-12 13:39:40 -080050import java.util.Set;
Santos Cordon63a84242013-07-23 13:32:52 -070051import java.util.concurrent.atomic.AtomicInteger;
52
53/**
54 * Creates a Call model from Call state and data received from the telephony
55 * layer. The telephony layer maintains 3 conceptual objects: Phone, Call,
56 * Connection.
57 *
58 * Phone represents the radio and there is an implementation per technology
59 * type such as GSMPhone, SipPhone, CDMAPhone, etc. Generally, we will only ever
60 * deal with one instance of this object for the lifetime of this class.
61 *
62 * There are 3 Call instances that exist for the lifetime of this class which
63 * are created by CallTracker. The three are RingingCall, ForegroundCall, and
64 * BackgroundCall.
65 *
66 * A Connection most closely resembles what the layperson would consider a call.
67 * A Connection is created when a user dials and it is "owned" by one of the
68 * three Call instances. Which of the three Calls owns the Connection changes
69 * as the Connection goes between ACTIVE, HOLD, RINGING, and other states.
70 *
71 * This class models a new Call class from Connection objects received from
72 * the telephony layer. We use Connection references as identifiers for a call;
73 * new reference = new call.
74 *
Christine Chen91db67d2013-09-18 12:01:11 -070075 * TODO: Create a new Call class to replace the simple call Id ints
Santos Cordon63a84242013-07-23 13:32:52 -070076 * being used currently.
77 *
78 * The new Call models are parcellable for transfer via the CallHandlerService
79 * API.
80 */
81public class CallModeler extends Handler {
82
83 private static final String TAG = CallModeler.class.getSimpleName();
Santos Cordon4ad64cd2013-08-15 00:36:14 -070084 private static final boolean DBG =
85 (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
Santos Cordon63a84242013-07-23 13:32:52 -070086
87 private static final int CALL_ID_START_VALUE = 1;
Santos Cordon63a84242013-07-23 13:32:52 -070088
Santos Cordon998f42b2013-08-02 16:13:12 -070089 private final CallStateMonitor mCallStateMonitor;
90 private final CallManager mCallManager;
Santos Cordon69a69192013-08-22 14:25:42 -070091 private final CallGatewayManager mCallGatewayManager;
Santos Cordon998f42b2013-08-02 16:13:12 -070092 private final HashMap<Connection, Call> mCallMap = Maps.newHashMap();
Santos Cordon4ad64cd2013-08-15 00:36:14 -070093 private final HashMap<Connection, Call> mConfCallMap = Maps.newHashMap();
Santos Cordon998f42b2013-08-02 16:13:12 -070094 private final AtomicInteger mNextCallId = new AtomicInteger(CALL_ID_START_VALUE);
Christine Chendaf7bf62013-08-05 19:12:31 -070095 private final ArrayList<Listener> mListeners = new ArrayList<Listener>();
Santos Cordona5d5db82013-09-15 13:00:34 -070096 private Connection mCdmaIncomingConnection;
Santos Cordonad1ed6d2013-09-16 03:04:23 -070097 private Connection mCdmaOutgoingConnection;
Sailesh Nepal1eaf22b2014-02-22 17:00:49 -080098 // TODO(sail): Temporary flag to disable sending telephony updates when Telecomm is being used.
99 private boolean mShouldDisableUpdates;
Santos Cordon63a84242013-07-23 13:32:52 -0700100
Christine Chenee09a492013-08-06 16:02:29 -0700101 public CallModeler(CallStateMonitor callStateMonitor, CallManager callManager,
Santos Cordon69a69192013-08-22 14:25:42 -0700102 CallGatewayManager callGatewayManager) {
Santos Cordon63a84242013-07-23 13:32:52 -0700103 mCallStateMonitor = callStateMonitor;
Santos Cordona3d05142013-07-29 11:25:17 -0700104 mCallManager = callManager;
Santos Cordon69a69192013-08-22 14:25:42 -0700105 mCallGatewayManager = callGatewayManager;
Santos Cordon63a84242013-07-23 13:32:52 -0700106
107 mCallStateMonitor.addListener(this);
108 }
109
Sailesh Nepal1eaf22b2014-02-22 17:00:49 -0800110 public void setShouldDisableUpdates(boolean shouldDisableUpdates) {
111 Log.i(TAG, "setShouldDisableUpdates " + mShouldDisableUpdates + " -> " +
112 shouldDisableUpdates);
113 mShouldDisableUpdates = shouldDisableUpdates;
114 }
115
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700116 @Override
Santos Cordon63a84242013-07-23 13:32:52 -0700117 public void handleMessage(Message msg) {
118 switch(msg.what) {
119 case CallStateMonitor.PHONE_NEW_RINGING_CONNECTION:
Christine Chenfb0cc2b2013-09-16 14:21:29 -0700120 // We let the CallNotifier handle the new ringing connection first. When the custom
121 // ringtone and send_to_voicemail settings are retrieved, CallNotifier will directly
122 // call CallModeler's onNewRingingConnection.
Santos Cordon63a84242013-07-23 13:32:52 -0700123 break;
124 case CallStateMonitor.PHONE_DISCONNECT:
Santos Cordona5d5db82013-09-15 13:00:34 -0700125 onDisconnect((Connection) ((AsyncResult) msg.obj).result);
Santos Cordon995c8162013-07-29 09:22:22 -0700126 break;
Santos Cordon54fdb592013-09-19 05:16:18 -0700127 case CallStateMonitor.PHONE_UNKNOWN_CONNECTION_APPEARED:
128 // fall through
Santos Cordon995c8162013-07-29 09:22:22 -0700129 case CallStateMonitor.PHONE_STATE_CHANGED:
130 onPhoneStateChanged((AsyncResult) msg.obj);
131 break;
Chiao Cheng3f015c92013-09-06 15:56:27 -0700132 case CallStateMonitor.PHONE_ON_DIAL_CHARS:
133 onPostDialChars((AsyncResult) msg.obj, (char) msg.arg1);
134 break;
Santos Cordon63a84242013-07-23 13:32:52 -0700135 default:
136 break;
137 }
138 }
139
Christine Chendaf7bf62013-08-05 19:12:31 -0700140 public void addListener(Listener listener) {
Santos Cordon63a84242013-07-23 13:32:52 -0700141 Preconditions.checkNotNull(listener);
Christine Chendaf7bf62013-08-05 19:12:31 -0700142 Preconditions.checkNotNull(mListeners);
Christine Chen4748abd2013-08-07 15:44:15 -0700143 if (!mListeners.contains(listener)) {
144 mListeners.add(listener);
145 }
Santos Cordon998f42b2013-08-02 16:13:12 -0700146 }
147
148 public List<Call> getFullList() {
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700149 final List<Call> calls =
150 Lists.newArrayListWithCapacity(mCallMap.size() + mConfCallMap.size());
151 calls.addAll(mCallMap.values());
152 calls.addAll(mConfCallMap.values());
153 return calls;
Santos Cordon63a84242013-07-23 13:32:52 -0700154 }
155
Santos Cordon249efd02013-08-05 03:33:56 -0700156 public CallResult getCallWithId(int callId) {
157 // max 8 connections, so this should be fast even through we are traversing the entire map.
158 for (Entry<Connection, Call> entry : mCallMap.entrySet()) {
159 if (entry.getValue().getCallId() == callId) {
160 return new CallResult(entry.getValue(), entry.getKey());
161 }
162 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700163
164 for (Entry<Connection, Call> entry : mConfCallMap.entrySet()) {
165 if (entry.getValue().getCallId() == callId) {
Christine Chen69050202013-09-14 15:36:35 -0700166 return new CallResult(entry.getValue(), entry.getKey());
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700167 }
168 }
Santos Cordon249efd02013-08-05 03:33:56 -0700169 return null;
170 }
171
Santos Cordonaf763a12013-08-19 20:04:58 -0700172 public boolean hasLiveCall() {
173 return hasLiveCallInternal(mCallMap) ||
174 hasLiveCallInternal(mConfCallMap);
175 }
176
Santos Cordona5d5db82013-09-15 13:00:34 -0700177 public void onCdmaCallWaiting(CdmaCallWaitingNotification callWaitingInfo) {
178 // We dont get the traditional onIncomingCall notification for cdma call waiting,
179 // but the Connection does actually exist. We need to find it in the set of ringing calls
180 // and pass it through our normal incoming logic.
181 final com.android.internal.telephony.Call teleCall =
182 mCallManager.getFirstActiveRingingCall();
183
184 if (teleCall.getState() == com.android.internal.telephony.Call.State.WAITING) {
185 Connection connection = teleCall.getLatestConnection();
186
187 if (connection != null) {
188 String number = connection.getAddress();
189 if (number != null && number.equals(callWaitingInfo.number)) {
190 Call call = onNewRingingConnection(connection);
191 mCdmaIncomingConnection = connection;
192 return;
193 }
194 }
195 }
196
197 Log.e(TAG, "CDMA Call waiting notification without a matching connection.");
198 }
199
200 public void onCdmaCallWaitingReject() {
201 // Cdma call was rejected...
202 if (mCdmaIncomingConnection != null) {
203 onDisconnect(mCdmaIncomingConnection);
204 mCdmaIncomingConnection = null;
205 } else {
206 Log.e(TAG, "CDMA Call waiting rejection without an incoming call.");
207 }
208 }
209
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700210 /**
211 * CDMA Calls have no sense of "dialing" state. For outgoing calls 3way calls we want to
212 * mimick this state so that the the UI can notify the user that there is a "dialing"
213 * call.
214 */
215 public void setCdmaOutgoing3WayCall(Connection connection) {
216 boolean wasSet = mCdmaOutgoingConnection != null;
217
218 mCdmaOutgoingConnection = connection;
219
220 // If we reset the connection, that mean we can now tell the user that the call is actually
221 // part of the conference call and move it out of the dialing state. To do this, issue a
222 // new update completely.
223 if (wasSet && mCdmaOutgoingConnection == null) {
224 onPhoneStateChanged(null);
225 }
226 }
227
Santos Cordonaf763a12013-08-19 20:04:58 -0700228 private boolean hasLiveCallInternal(HashMap<Connection, Call> map) {
229 for (Call call : map.values()) {
230 final int state = call.getState();
231 if (state == Call.State.ACTIVE ||
232 state == Call.State.CALL_WAITING ||
233 state == Call.State.CONFERENCED ||
234 state == Call.State.DIALING ||
Santos Cordonce02f3a2013-09-19 01:58:42 -0700235 state == Call.State.REDIALING ||
Santos Cordonaf763a12013-08-19 20:04:58 -0700236 state == Call.State.INCOMING ||
Christine Chen3e0f0412013-09-18 20:33:49 -0700237 state == Call.State.ONHOLD ||
238 state == Call.State.DISCONNECTING) {
Santos Cordonaf763a12013-08-19 20:04:58 -0700239 return true;
240 }
241 }
242 return false;
243 }
244
Santos Cordon2b73bd62013-08-27 14:53:43 -0700245 public boolean hasOutstandingActiveOrDialingCall() {
246 return hasOutstandingActiveOrDialingCallInternal(mCallMap) ||
247 hasOutstandingActiveOrDialingCallInternal(mConfCallMap);
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700248 }
249
Santos Cordon2b73bd62013-08-27 14:53:43 -0700250 private static boolean hasOutstandingActiveOrDialingCallInternal(
251 HashMap<Connection, Call> map) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700252 for (Call call : map.values()) {
253 final int state = call.getState();
Santos Cordonce02f3a2013-09-19 01:58:42 -0700254 if (state == Call.State.ACTIVE || Call.State.isDialing(state)) {
Santos Cordon2eaff902013-08-05 04:37:55 -0700255 return true;
256 }
257 }
258
259 return false;
260 }
261
Chiao Cheng3f015c92013-09-06 15:56:27 -0700262
263 /**
264 * Handles the POST_ON_DIAL_CHARS message from the Phone (see our call to
265 * mPhone.setOnPostDialCharacter() above.)
266 *
267 * TODO: NEED TO TEST THIS SEQUENCE now that we no longer handle "dialable" key events here in
268 * the InCallScreen: we do directly to the Dialer UI instead. Similarly, we may now need to go
269 * directly to the Dialer to handle POST_ON_DIAL_CHARS too.
270 */
271 private void onPostDialChars(AsyncResult r, char ch) {
272 final Connection c = (Connection) r.result;
273
274 if (c != null) {
275 final Connection.PostDialState state = (Connection.PostDialState) r.userObj;
276
277 switch (state) {
Chiao Cheng3f015c92013-09-06 15:56:27 -0700278 case WAIT:
279 final Call call = getCallFromMap(mCallMap, c, false);
280 if (call == null) {
281 Log.i(TAG, "Call no longer exists. Skipping onPostDialWait().");
Sailesh Nepal1eaf22b2014-02-22 17:00:49 -0800282 } else if (!mShouldDisableUpdates) {
Chiao Cheng3f015c92013-09-06 15:56:27 -0700283 for (Listener mListener : mListeners) {
Yorke Leede41f672013-09-19 13:46:55 -0700284 mListener.onPostDialAction(state, call.getCallId(),
285 c.getRemainingPostDialString(), ch);
Chiao Cheng3f015c92013-09-06 15:56:27 -0700286 }
287 }
288 break;
Chiao Cheng3f015c92013-09-06 15:56:27 -0700289 default:
Yorke Leede41f672013-09-19 13:46:55 -0700290 // This is primarily to cause the DTMFTonePlayer to play local tones.
291 // Other listeners simply perform no-ops.
Sailesh Nepal1eaf22b2014-02-22 17:00:49 -0800292 if (!mShouldDisableUpdates) {
293 for (Listener mListener : mListeners) {
294 mListener.onPostDialAction(state, 0, "", ch);
295 }
Yorke Leede41f672013-09-19 13:46:55 -0700296 }
Chiao Cheng3f015c92013-09-06 15:56:27 -0700297 break;
298 }
299 }
300 }
301
Christine Chenfb0cc2b2013-09-16 14:21:29 -0700302 /* package */ Call onNewRingingConnection(Connection conn) {
Santos Cordon2b73bd62013-08-27 14:53:43 -0700303 Log.i(TAG, "onNewRingingConnection");
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700304 final Call call = getCallFromMap(mCallMap, conn, true);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700305
Christine Chenfb0cc2b2013-09-16 14:21:29 -0700306 if (call != null) {
307 updateCallFromConnection(call, conn, false);
Santos Cordon63a84242013-07-23 13:32:52 -0700308
Sailesh Nepal1eaf22b2014-02-22 17:00:49 -0800309 if (!mShouldDisableUpdates) {
310 for (int i = 0; i < mListeners.size(); ++i) {
311 mListeners.get(i).onIncoming(call);
312 }
Christine Chenee09a492013-08-06 16:02:29 -0700313 }
Santos Cordon63a84242013-07-23 13:32:52 -0700314 }
Santos Cordona5d5db82013-09-15 13:00:34 -0700315
Santos Cordon24a92b32013-09-26 16:48:14 -0700316 PhoneGlobals.getInstance().updateWakeState();
Santos Cordona5d5db82013-09-15 13:00:34 -0700317 return call;
Santos Cordon63a84242013-07-23 13:32:52 -0700318 }
319
Santos Cordona5d5db82013-09-15 13:00:34 -0700320 private void onDisconnect(Connection conn) {
Santos Cordon2b73bd62013-08-27 14:53:43 -0700321 Log.i(TAG, "onDisconnect");
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700322 final Call call = getCallFromMap(mCallMap, conn, false);
Santos Cordon63a84242013-07-23 13:32:52 -0700323
Santos Cordon995c8162013-07-29 09:22:22 -0700324 if (call != null) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700325 final boolean wasConferenced = call.getState() == State.CONFERENCED;
326
327 updateCallFromConnection(call, conn, false);
Santos Cordon63a84242013-07-23 13:32:52 -0700328
Sailesh Nepal1eaf22b2014-02-22 17:00:49 -0800329 if (!mShouldDisableUpdates) {
330 for (int i = 0; i < mListeners.size(); ++i) {
331 mListeners.get(i).onDisconnect(call);
332 }
Santos Cordon63a84242013-07-23 13:32:52 -0700333 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700334
335 // If it was a conferenced call, we need to run the entire update
336 // to make the proper changes to parent conference calls.
337 if (wasConferenced) {
338 onPhoneStateChanged(null);
339 }
340
341 mCallMap.remove(conn);
Santos Cordon63a84242013-07-23 13:32:52 -0700342 }
Santos Cordon24a92b32013-09-26 16:48:14 -0700343
Christine Chen6af50e62013-09-26 15:05:57 -0700344 mCallManager.clearDisconnected();
Santos Cordon24a92b32013-09-26 16:48:14 -0700345 PhoneGlobals.getInstance().updateWakeState();
Santos Cordon63a84242013-07-23 13:32:52 -0700346 }
347
Santos Cordona3d05142013-07-29 11:25:17 -0700348 /**
349 * Called when the phone state changes.
Santos Cordona3d05142013-07-29 11:25:17 -0700350 */
Santos Cordon995c8162013-07-29 09:22:22 -0700351 private void onPhoneStateChanged(AsyncResult r) {
Santos Cordon2b73bd62013-08-27 14:53:43 -0700352 Log.i(TAG, "onPhoneStateChanged: ");
Santos Cordon998f42b2013-08-02 16:13:12 -0700353 final List<Call> updatedCalls = Lists.newArrayList();
354 doUpdate(false, updatedCalls);
355
Sailesh Nepal1eaf22b2014-02-22 17:00:49 -0800356 if (updatedCalls.size() > 0 && !mShouldDisableUpdates) {
Santos Cordon71d5c6e2013-09-05 21:34:33 -0700357 for (int i = 0; i < mListeners.size(); ++i) {
358 mListeners.get(i).onUpdate(updatedCalls);
359 }
Santos Cordon998f42b2013-08-02 16:13:12 -0700360 }
Santos Cordon24a92b32013-09-26 16:48:14 -0700361
362 PhoneGlobals.getInstance().updateWakeState();
Santos Cordon998f42b2013-08-02 16:13:12 -0700363 }
364
Santos Cordon998f42b2013-08-02 16:13:12 -0700365 /**
366 * Go through the Calls from CallManager and return the list of calls that were updated.
Santos Cordon25008582013-11-12 13:39:40 -0800367 * Method also finds any orphaned Calls (Connection objects no longer returned by telephony as
368 * either ringing, foreground, or background). For each orphaned call, it sets the call state
369 * to IDLE and adds it to the list of calls to update.
370 *
371 * @param fullUpdate Add all calls to out parameter including those that have no updates.
372 * @param out List to populate with Calls that have been updated.
Santos Cordon998f42b2013-08-02 16:13:12 -0700373 */
374 private void doUpdate(boolean fullUpdate, List<Call> out) {
Santos Cordona3d05142013-07-29 11:25:17 -0700375 final List<com.android.internal.telephony.Call> telephonyCalls = Lists.newArrayList();
376 telephonyCalls.addAll(mCallManager.getRingingCalls());
377 telephonyCalls.addAll(mCallManager.getForegroundCalls());
378 telephonyCalls.addAll(mCallManager.getBackgroundCalls());
379
Santos Cordon25008582013-11-12 13:39:40 -0800380 // orphanedConnections starts out including all connections we know about.
381 // As we iterate through the connections we get from the telephony layer we
382 // prune this Set down to only the connections we have but telephony no longer
383 // recognizes.
384 final Set<Connection> orphanedConnections = Sets.newHashSet();
385 orphanedConnections.addAll(mCallMap.keySet());
386 orphanedConnections.addAll(mConfCallMap.keySet());
387
Santos Cordona3d05142013-07-29 11:25:17 -0700388 // Cycle through all the Connections on all the Calls. Update our Call objects
389 // to reflect any new state and send the updated Call objects to the handler service.
390 for (com.android.internal.telephony.Call telephonyCall : telephonyCalls) {
Santos Cordona3d05142013-07-29 11:25:17 -0700391
392 for (Connection connection : telephonyCall.getConnections()) {
Christine Chen3e0f0412013-09-18 20:33:49 -0700393 if (DBG) Log.d(TAG, "connection: " + connection + connection.getState());
Santos Cordona5d5db82013-09-15 13:00:34 -0700394
Santos Cordon25008582013-11-12 13:39:40 -0800395 if (orphanedConnections.contains(connection)) {
396 orphanedConnections.remove(connection);
397 }
398
Santos Cordon12a03aa2013-09-12 23:34:05 -0700399 // We only send updates for live calls which are not incoming (ringing).
400 // Disconnected and incoming calls are handled by onDisconnect and
401 // onNewRingingConnection.
Christine Chendf19e452013-09-25 17:21:23 -0700402 final boolean shouldUpdate =
Christine Chen3e0f0412013-09-18 20:33:49 -0700403 connection.getState() !=
404 com.android.internal.telephony.Call.State.DISCONNECTED &&
405 connection.getState() !=
406 com.android.internal.telephony.Call.State.IDLE &&
Santos Cordon71d5c6e2013-09-05 21:34:33 -0700407 !connection.getState().isRinging();
408
Christine Chendf19e452013-09-25 17:21:23 -0700409 final boolean isDisconnecting = connection.getState() ==
410 com.android.internal.telephony.Call.State.DISCONNECTING;
411
412 // For disconnecting calls, we still need to send the update to the UI but we do
413 // not create a new call if the call did not exist.
414 final boolean shouldCreate = shouldUpdate && !isDisconnecting;
415
Santos Cordon71d5c6e2013-09-05 21:34:33 -0700416 // New connections return a Call with INVALID state, which does not translate to
Santos Cordone38b1ff2013-08-07 12:12:16 -0700417 // a state in the internal.telephony.Call object. This ensures that staleness
418 // check below fails and we always add the item to the update list if it is new.
Christine Chendf19e452013-09-25 17:21:23 -0700419 final Call call = getCallFromMap(mCallMap, connection, shouldCreate /* create */);
Santos Cordon71d5c6e2013-09-05 21:34:33 -0700420
Santos Cordon12a03aa2013-09-12 23:34:05 -0700421 if (call == null || !shouldUpdate) {
Santos Cordona5d5db82013-09-15 13:00:34 -0700422 if (DBG) Log.d(TAG, "update skipped");
Santos Cordon71d5c6e2013-09-05 21:34:33 -0700423 continue;
424 }
Santos Cordona3d05142013-07-29 11:25:17 -0700425
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700426 boolean changed = updateCallFromConnection(call, connection, false);
Santos Cordon2b73bd62013-08-27 14:53:43 -0700427
Santos Cordone38b1ff2013-08-07 12:12:16 -0700428 if (fullUpdate || changed) {
Santos Cordon998f42b2013-08-02 16:13:12 -0700429 out.add(call);
Santos Cordona3d05142013-07-29 11:25:17 -0700430 }
431 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700432
433 // We do a second loop to address conference call scenarios. We do this as a separate
434 // loop to ensure all child calls are up to date before we start updating the parent
435 // conference calls.
436 for (Connection connection : telephonyCall.getConnections()) {
437 updateForConferenceCalls(connection, out);
438 }
Santos Cordon25008582013-11-12 13:39:40 -0800439 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700440
Santos Cordon25008582013-11-12 13:39:40 -0800441 // Iterate through orphaned connections, set them to idle, and remove
442 // them from our internal structures.
443 for (Connection orphanedConnection : orphanedConnections) {
444 if (mCallMap.containsKey(orphanedConnection)) {
445 final Call call = mCallMap.get(orphanedConnection);
446 call.setState(Call.State.IDLE);
447 out.add(call);
448
449 mCallMap.remove(orphanedConnection);
450 }
451
452 if (mConfCallMap.containsKey(orphanedConnection)) {
453 final Call call = mCallMap.get(orphanedConnection);
454 call.setState(Call.State.IDLE);
455 out.add(call);
456
457 mConfCallMap.remove(orphanedConnection);
458 }
Santos Cordona3d05142013-07-29 11:25:17 -0700459 }
Santos Cordona3d05142013-07-29 11:25:17 -0700460 }
461
Santos Cordone38b1ff2013-08-07 12:12:16 -0700462 /**
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700463 * Checks to see if the connection is the first connection in a conference call.
464 * If it is a conference call, we will create a new Conference Call object or
465 * update the existing conference call object for that connection.
466 * If it is not a conference call but a previous associated conference call still exists,
467 * we mark it as idle and remove it from the map.
468 * In both cases above, we add the Calls to be updated to the UI.
469 * @param connection The connection object to check.
470 * @param updatedCalls List of 'updated' calls that will be sent to the UI.
471 */
472 private boolean updateForConferenceCalls(Connection connection, List<Call> updatedCalls) {
473 // We consider this connection a conference connection if the call it
Yorke Leecd3f9692013-09-14 13:51:27 -0700474 // belongs to is a multiparty call AND it is the first live connection.
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700475 final boolean isConferenceCallConnection = isPartOfLiveConferenceCall(connection) &&
Yorke Leecd3f9692013-09-14 13:51:27 -0700476 getEarliestLiveConnection(connection.getCall()) == connection;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700477
478 boolean changed = false;
479
480 // If this connection is the main connection for the conference call, then create or update
481 // a Call object for that conference call.
482 if (isConferenceCallConnection) {
483 final Call confCall = getCallFromMap(mConfCallMap, connection, true);
484 changed = updateCallFromConnection(confCall, connection, true);
485
486 if (changed) {
487 updatedCalls.add(confCall);
488 }
489
490 if (DBG) Log.d(TAG, "Updating a conference call: " + confCall);
491
492 // It is possible that through a conference call split, there may be lingering conference
493 // calls where this connection was the main connection. We clean those up here.
494 } else {
495 final Call oldConfCall = getCallFromMap(mConfCallMap, connection, false);
496
497 // We found a conference call for this connection, which is no longer a conference call.
498 // Kill it!
499 if (oldConfCall != null) {
500 if (DBG) Log.d(TAG, "Cleaning up an old conference call: " + oldConfCall);
501 mConfCallMap.remove(connection);
502 oldConfCall.setState(State.IDLE);
503 changed = true;
504
505 // add to the list of calls to update
506 updatedCalls.add(oldConfCall);
507 }
508 }
509
510 return changed;
511 }
512
Yorke Leecd3f9692013-09-14 13:51:27 -0700513 private Connection getEarliestLiveConnection(com.android.internal.telephony.Call call) {
514 final List<Connection> connections = call.getConnections();
515 final int size = connections.size();
516 Connection earliestConn = null;
517 long earliestTime = Long.MAX_VALUE;
518 for (int i = 0; i < size; i++) {
519 final Connection connection = connections.get(i);
520 if (!connection.isAlive()) continue;
521 final long time = connection.getCreateTime();
522 if (time < earliestTime) {
523 earliestTime = time;
524 earliestConn = connection;
525 }
526 }
527 return earliestConn;
528 }
529
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700530 /**
Santos Cordon69a69192013-08-22 14:25:42 -0700531 * Sets the new call state onto the call and performs some additional logic
532 * associated with setting the state.
533 */
534 private void setNewState(Call call, int newState, Connection connection) {
535 Preconditions.checkState(call.getState() != newState);
536
537 // When starting an outgoing call, we need to grab gateway information
538 // for the call, if available, and set it.
539 final RawGatewayInfo info = mCallGatewayManager.getGatewayInfo(connection);
540
Santos Cordonce02f3a2013-09-19 01:58:42 -0700541 if (Call.State.isDialing(newState)) {
Santos Cordon69a69192013-08-22 14:25:42 -0700542 if (!info.isEmpty()) {
543 call.setGatewayNumber(info.getFormattedGatewayNumber());
544 call.setGatewayPackage(info.packageName);
545 }
546 } else if (!Call.State.isConnected(newState)) {
547 mCallGatewayManager.clearGatewayData(connection);
548 }
549
550 call.setState(newState);
551 }
552
553 /**
Santos Cordone38b1ff2013-08-07 12:12:16 -0700554 * Updates the Call properties to match the state of the connection object
555 * that it represents.
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700556 * @param call The call object to update.
557 * @param connection The connection object from which to update call.
558 * @param isForConference There are slight differences in how we populate data for conference
559 * calls. This boolean tells us which method to use.
Santos Cordone38b1ff2013-08-07 12:12:16 -0700560 */
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700561 private boolean updateCallFromConnection(Call call, Connection connection,
562 boolean isForConference) {
Santos Cordone38b1ff2013-08-07 12:12:16 -0700563 boolean changed = false;
564
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700565 final int newState = translateStateFromTelephony(connection, isForConference);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700566
567 if (call.getState() != newState) {
Santos Cordon69a69192013-08-22 14:25:42 -0700568 setNewState(call, newState, connection);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700569 changed = true;
570 }
571
Sailesh Nepald1e68152013-12-12 19:08:02 -0800572 final boolean isWifiCall = connection.getCall().getPhone().getPhoneType() ==
573 PhoneConstants.PHONE_TYPE_THIRD_PARTY;
574 if (call.isWifiCall() != isWifiCall) {
575 call.setIsWifiCall(isWifiCall);
576 changed = true;
577 }
578
Santos Cordone38b1ff2013-08-07 12:12:16 -0700579 final Call.DisconnectCause newDisconnectCause =
580 translateDisconnectCauseFromTelephony(connection.getDisconnectCause());
581 if (call.getDisconnectCause() != newDisconnectCause) {
582 call.setDisconnectCause(newDisconnectCause);
583 changed = true;
584 }
585
Santos Cordonbbe8ecf2013-08-13 15:26:18 -0700586 final long oldConnectTime = call.getConnectTime();
587 if (oldConnectTime != connection.getConnectTime()) {
588 call.setConnectTime(connection.getConnectTime());
589 changed = true;
590 }
591
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700592 if (!isForConference) {
Santos Cordon69a69192013-08-22 14:25:42 -0700593 // Number
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700594 final String oldNumber = call.getNumber();
Santos Cordon69a69192013-08-22 14:25:42 -0700595 String newNumber = connection.getAddress();
596 RawGatewayInfo info = mCallGatewayManager.getGatewayInfo(connection);
597 if (!info.isEmpty()) {
598 newNumber = info.trueNumber;
599 }
600 if (TextUtils.isEmpty(oldNumber) || !oldNumber.equals(newNumber)) {
601 call.setNumber(newNumber);
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700602 changed = true;
603 }
604
Santos Cordon69a69192013-08-22 14:25:42 -0700605 // Number presentation
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700606 final int newNumberPresentation = connection.getNumberPresentation();
607 if (call.getNumberPresentation() != newNumberPresentation) {
608 call.setNumberPresentation(newNumberPresentation);
609 changed = true;
610 }
611
Santos Cordon69a69192013-08-22 14:25:42 -0700612 // Name
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700613 final String oldCnapName = call.getCnapName();
614 if (TextUtils.isEmpty(oldCnapName) || !oldCnapName.equals(connection.getCnapName())) {
615 call.setCnapName(connection.getCnapName());
616 changed = true;
617 }
Santos Cordon69a69192013-08-22 14:25:42 -0700618
619 // Name Presentation
620 final int newCnapNamePresentation = connection.getCnapNamePresentation();
621 if (call.getCnapNamePresentation() != newCnapNamePresentation) {
622 call.setCnapNamePresentation(newCnapNamePresentation);
623 changed = true;
624 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700625 } else {
626
627 // update the list of children by:
628 // 1) Saving the old set
629 // 2) Removing all children
630 // 3) Adding the correct children into the Call
631 // 4) Comparing the new children set with the old children set
632 ImmutableSortedSet<Integer> oldSet = call.getChildCallIds();
633 call.removeAllChildren();
634
635 if (connection.getCall() != null) {
636 for (Connection childConn : connection.getCall().getConnections()) {
637 final Call childCall = getCallFromMap(mCallMap, childConn, false);
638 if (childCall != null && childConn.isAlive()) {
639 call.addChildId(childCall.getCallId());
640 }
641 }
642 }
Christine Chen45277022013-09-05 10:55:37 -0700643 changed |= !oldSet.equals(call.getChildCallIds());
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700644 }
645
Santos Cordoneead6ec2013-08-07 22:16:33 -0700646 /**
647 * !!! Uses values from connection and call collected above so this part must be last !!!
648 */
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700649 final int newCapabilities = getCapabilitiesFor(connection, call, isForConference);
Santos Cordon26e7b242013-08-07 21:15:45 -0700650 if (call.getCapabilities() != newCapabilities) {
651 call.setCapabilities(newCapabilities);
652 changed = true;
653 }
654
Santos Cordone38b1ff2013-08-07 12:12:16 -0700655 return changed;
656 }
657
Santos Cordon26e7b242013-08-07 21:15:45 -0700658 /**
659 * Returns a mask of capabilities for the connection such as merge, hold, etc.
660 */
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700661 private int getCapabilitiesFor(Connection connection, Call call, boolean isForConference) {
Santos Cordoneead6ec2013-08-07 22:16:33 -0700662 final boolean callIsActive = (call.getState() == Call.State.ACTIVE);
663 final Phone phone = connection.getCall().getPhone();
664
Santos Cordoneead6ec2013-08-07 22:16:33 -0700665 boolean canAddCall = false;
666 boolean canMergeCall = false;
667 boolean canSwapCall = false;
Yorke Lee814da302013-08-30 16:01:07 -0700668 boolean canRespondViaText = false;
Christine Chenaf2fd0a2013-09-13 16:27:40 -0700669 boolean canMute = false;
670
671 final boolean supportHold = PhoneUtils.okToSupportHold(mCallManager);
Christine Chen94853bc2013-09-17 16:53:33 -0700672 final boolean canHold = (supportHold ? PhoneUtils.okToHoldCall(mCallManager) : false);
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700673 final boolean genericConf = isForConference &&
Sailesh Nepald1e68152013-12-12 19:08:02 -0800674 (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA);
Santos Cordoneead6ec2013-08-07 22:16:33 -0700675
676 // only applies to active calls
677 if (callIsActive) {
Santos Cordoneead6ec2013-08-07 22:16:33 -0700678 canMergeCall = PhoneUtils.okToMergeCalls(mCallManager);
679 canSwapCall = PhoneUtils.okToSwapCalls(mCallManager);
680 }
681
Christine Chenaf2fd0a2013-09-13 16:27:40 -0700682 canAddCall = PhoneUtils.okToAddCall(mCallManager);
683
684 // "Mute": only enabled when the foreground call is ACTIVE.
685 // (It's meaningless while on hold, or while DIALING/ALERTING.)
686 // It's also explicitly disabled during emergency calls or if
687 // emergency callback mode (ECM) is active.
688 boolean isEmergencyCall = false;
689 if (connection != null) {
690 isEmergencyCall = PhoneNumberUtils.isLocalEmergencyNumber(connection.getAddress(),
691 phone.getContext());
692 }
693 boolean isECM = PhoneUtils.isPhoneInEcm(phone);
694 if (isEmergencyCall || isECM) { // disable "Mute" item
695 canMute = false;
696 } else {
697 canMute = callIsActive;
698 }
699
Yorke Lee814da302013-08-30 16:01:07 -0700700 canRespondViaText = RejectWithTextMessageManager.allowRespondViaSmsForCall(call,
701 connection);
702
Santos Cordoneead6ec2013-08-07 22:16:33 -0700703 // special rules section!
704 // CDMA always has Add
705 if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
706 canAddCall = true;
Santos Cordoneead6ec2013-08-07 22:16:33 -0700707 }
708
Santos Cordon26e7b242013-08-07 21:15:45 -0700709 int retval = 0x0;
Santos Cordoneead6ec2013-08-07 22:16:33 -0700710 if (canHold) {
Santos Cordon26e7b242013-08-07 21:15:45 -0700711 retval |= Capabilities.HOLD;
712 }
Christine Chenaf2fd0a2013-09-13 16:27:40 -0700713 if (supportHold) {
714 retval |= Capabilities.SUPPORT_HOLD;
715 }
Santos Cordoneead6ec2013-08-07 22:16:33 -0700716 if (canAddCall) {
717 retval |= Capabilities.ADD_CALL;
718 }
719 if (canMergeCall) {
720 retval |= Capabilities.MERGE_CALLS;
721 }
722 if (canSwapCall) {
723 retval |= Capabilities.SWAP_CALLS;
724 }
Yorke Lee814da302013-08-30 16:01:07 -0700725 if (canRespondViaText) {
726 retval |= Capabilities.RESPOND_VIA_TEXT;
727 }
Christine Chenaf2fd0a2013-09-13 16:27:40 -0700728 if (canMute) {
729 retval |= Capabilities.MUTE;
730 }
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700731 if (genericConf) {
732 retval |= Capabilities.GENERIC_CONFERENCE;
733 }
Yorke Lee814da302013-08-30 16:01:07 -0700734
Santos Cordon26e7b242013-08-07 21:15:45 -0700735 return retval;
736 }
737
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700738 /**
739 * Returns true if the Connection is part of a multiparty call.
740 * We do this by checking the isMultiparty() method of the telephony.Call object and also
741 * checking to see if more than one of it's children is alive.
742 */
743 private boolean isPartOfLiveConferenceCall(Connection connection) {
744 if (connection.getCall() != null && connection.getCall().isMultiparty()) {
745 int count = 0;
746 for (Connection currConn : connection.getCall().getConnections()) {
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700747
748 // Only count connections which are alive and never cound the special
749 // "dialing" 3way call for CDMA calls.
750 if (currConn.isAlive() && currConn != mCdmaOutgoingConnection) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700751 count++;
752 if (count >= 2) {
753 return true;
754 }
755 }
756 }
757 }
758 return false;
759 }
760
761 private int translateStateFromTelephony(Connection connection, boolean isForConference) {
762
Santos Cordonce02f3a2013-09-19 01:58:42 -0700763 com.android.internal.telephony.Call.State connState = connection.getState();
764
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700765 // For the "fake" outgoing CDMA call, we need to always treat it as an outgoing call.
766 if (mCdmaOutgoingConnection == connection) {
Santos Cordonce02f3a2013-09-19 01:58:42 -0700767 connState = com.android.internal.telephony.Call.State.DIALING;
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700768 }
769
Santos Cordona3d05142013-07-29 11:25:17 -0700770 int retval = State.IDLE;
Santos Cordonce02f3a2013-09-19 01:58:42 -0700771 switch (connState) {
Santos Cordona3d05142013-07-29 11:25:17 -0700772 case ACTIVE:
773 retval = State.ACTIVE;
774 break;
775 case INCOMING:
776 retval = State.INCOMING;
777 break;
778 case DIALING:
779 case ALERTING:
Santos Cordonce02f3a2013-09-19 01:58:42 -0700780 if (PhoneGlobals.getInstance().notifier.getIsCdmaRedialCall()) {
781 retval = State.REDIALING;
782 } else {
783 retval = State.DIALING;
784 }
Santos Cordona3d05142013-07-29 11:25:17 -0700785 break;
786 case WAITING:
787 retval = State.CALL_WAITING;
788 break;
789 case HOLDING:
790 retval = State.ONHOLD;
791 break;
Santos Cordone38b1ff2013-08-07 12:12:16 -0700792 case DISCONNECTING:
Christine Chen3e0f0412013-09-18 20:33:49 -0700793 retval = State.DISCONNECTING;
794 break;
795 case DISCONNECTED:
Santos Cordone38b1ff2013-08-07 12:12:16 -0700796 retval = State.DISCONNECTED;
Santos Cordona3d05142013-07-29 11:25:17 -0700797 default:
798 }
799
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700800 // If we are dealing with a potential child call (not the parent conference call),
801 // the check to see if we have to set the state to CONFERENCED.
802 if (!isForConference) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700803 // if the connection is part of a multiparty call, and it is live,
804 // annotate it with CONFERENCED state instead.
805 if (isPartOfLiveConferenceCall(connection) && connection.isAlive()) {
806 return State.CONFERENCED;
807 }
808 }
809
Santos Cordona3d05142013-07-29 11:25:17 -0700810 return retval;
Santos Cordon995c8162013-07-29 09:22:22 -0700811 }
812
Santos Cordone38b1ff2013-08-07 12:12:16 -0700813 private final ImmutableMap<Connection.DisconnectCause, Call.DisconnectCause> CAUSE_MAP =
814 ImmutableMap.<Connection.DisconnectCause, Call.DisconnectCause>builder()
815 .put(Connection.DisconnectCause.BUSY, Call.DisconnectCause.BUSY)
816 .put(Connection.DisconnectCause.CALL_BARRED, Call.DisconnectCause.CALL_BARRED)
817 .put(Connection.DisconnectCause.CDMA_ACCESS_BLOCKED,
818 Call.DisconnectCause.CDMA_ACCESS_BLOCKED)
819 .put(Connection.DisconnectCause.CDMA_ACCESS_FAILURE,
820 Call.DisconnectCause.CDMA_ACCESS_FAILURE)
821 .put(Connection.DisconnectCause.CDMA_DROP, Call.DisconnectCause.CDMA_DROP)
822 .put(Connection.DisconnectCause.CDMA_INTERCEPT, Call.DisconnectCause.CDMA_INTERCEPT)
823 .put(Connection.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE,
824 Call.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE)
825 .put(Connection.DisconnectCause.CDMA_NOT_EMERGENCY,
826 Call.DisconnectCause.CDMA_NOT_EMERGENCY)
827 .put(Connection.DisconnectCause.CDMA_PREEMPTED, Call.DisconnectCause.CDMA_PREEMPTED)
828 .put(Connection.DisconnectCause.CDMA_REORDER, Call.DisconnectCause.CDMA_REORDER)
829 .put(Connection.DisconnectCause.CDMA_RETRY_ORDER,
830 Call.DisconnectCause.CDMA_RETRY_ORDER)
831 .put(Connection.DisconnectCause.CDMA_SO_REJECT, Call.DisconnectCause.CDMA_SO_REJECT)
832 .put(Connection.DisconnectCause.CONGESTION, Call.DisconnectCause.CONGESTION)
833 .put(Connection.DisconnectCause.CS_RESTRICTED, Call.DisconnectCause.CS_RESTRICTED)
834 .put(Connection.DisconnectCause.CS_RESTRICTED_EMERGENCY,
835 Call.DisconnectCause.CS_RESTRICTED_EMERGENCY)
836 .put(Connection.DisconnectCause.CS_RESTRICTED_NORMAL,
837 Call.DisconnectCause.CS_RESTRICTED_NORMAL)
838 .put(Connection.DisconnectCause.ERROR_UNSPECIFIED,
839 Call.DisconnectCause.ERROR_UNSPECIFIED)
840 .put(Connection.DisconnectCause.FDN_BLOCKED, Call.DisconnectCause.FDN_BLOCKED)
841 .put(Connection.DisconnectCause.ICC_ERROR, Call.DisconnectCause.ICC_ERROR)
842 .put(Connection.DisconnectCause.INCOMING_MISSED,
843 Call.DisconnectCause.INCOMING_MISSED)
844 .put(Connection.DisconnectCause.INCOMING_REJECTED,
845 Call.DisconnectCause.INCOMING_REJECTED)
846 .put(Connection.DisconnectCause.INVALID_CREDENTIALS,
847 Call.DisconnectCause.INVALID_CREDENTIALS)
848 .put(Connection.DisconnectCause.INVALID_NUMBER,
849 Call.DisconnectCause.INVALID_NUMBER)
850 .put(Connection.DisconnectCause.LIMIT_EXCEEDED, Call.DisconnectCause.LIMIT_EXCEEDED)
851 .put(Connection.DisconnectCause.LOCAL, Call.DisconnectCause.LOCAL)
852 .put(Connection.DisconnectCause.LOST_SIGNAL, Call.DisconnectCause.LOST_SIGNAL)
853 .put(Connection.DisconnectCause.MMI, Call.DisconnectCause.MMI)
854 .put(Connection.DisconnectCause.NORMAL, Call.DisconnectCause.NORMAL)
855 .put(Connection.DisconnectCause.NOT_DISCONNECTED,
856 Call.DisconnectCause.NOT_DISCONNECTED)
857 .put(Connection.DisconnectCause.NUMBER_UNREACHABLE,
858 Call.DisconnectCause.NUMBER_UNREACHABLE)
859 .put(Connection.DisconnectCause.OUT_OF_NETWORK, Call.DisconnectCause.OUT_OF_NETWORK)
860 .put(Connection.DisconnectCause.OUT_OF_SERVICE, Call.DisconnectCause.OUT_OF_SERVICE)
861 .put(Connection.DisconnectCause.POWER_OFF, Call.DisconnectCause.POWER_OFF)
862 .put(Connection.DisconnectCause.SERVER_ERROR, Call.DisconnectCause.SERVER_ERROR)
863 .put(Connection.DisconnectCause.SERVER_UNREACHABLE,
864 Call.DisconnectCause.SERVER_UNREACHABLE)
865 .put(Connection.DisconnectCause.TIMED_OUT, Call.DisconnectCause.TIMED_OUT)
866 .put(Connection.DisconnectCause.UNOBTAINABLE_NUMBER,
867 Call.DisconnectCause.UNOBTAINABLE_NUMBER)
868 .build();
869
870 private Call.DisconnectCause translateDisconnectCauseFromTelephony(
871 Connection.DisconnectCause causeSource) {
872
873 if (CAUSE_MAP.containsKey(causeSource)) {
874 return CAUSE_MAP.get(causeSource);
875 }
876
877 return Call.DisconnectCause.UNKNOWN;
878 }
879
Santos Cordon63a84242013-07-23 13:32:52 -0700880 /**
Santos Cordone38b1ff2013-08-07 12:12:16 -0700881 * Gets an existing callId for a connection, or creates one if none exists.
882 * This function does NOT set any of the Connection data onto the Call class.
883 * A separate call to updateCallFromConnection must be made for that purpose.
Santos Cordon63a84242013-07-23 13:32:52 -0700884 */
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700885 private Call getCallFromMap(HashMap<Connection, Call> map, Connection conn,
886 boolean createIfMissing) {
Santos Cordon995c8162013-07-29 09:22:22 -0700887 Call call = null;
Santos Cordon63a84242013-07-23 13:32:52 -0700888
889 // Find the call id or create if missing and requested.
890 if (conn != null) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700891 if (map.containsKey(conn)) {
892 call = map.get(conn);
Santos Cordon63a84242013-07-23 13:32:52 -0700893 } else if (createIfMissing) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700894 call = createNewCall();
895 map.put(conn, call);
Santos Cordon63a84242013-07-23 13:32:52 -0700896 }
897 }
Santos Cordon995c8162013-07-29 09:22:22 -0700898 return call;
Santos Cordon63a84242013-07-23 13:32:52 -0700899 }
900
901 /**
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700902 * Creates a brand new connection for the call.
903 */
904 private Call createNewCall() {
905 int callId;
906 int newNextCallId;
907 do {
908 callId = mNextCallId.get();
909
910 // protect against overflow
911 newNextCallId = (callId == Integer.MAX_VALUE ?
912 CALL_ID_START_VALUE : callId + 1);
913
914 // Keep looping if the change was not atomic OR the value is already taken.
915 // The call to containsValue() is linear, however, most devices support a
916 // maximum of 7 connections so it's not expensive.
917 } while (!mNextCallId.compareAndSet(callId, newNextCallId));
918
919 return new Call(callId);
920 }
921
922 /**
Santos Cordon63a84242013-07-23 13:32:52 -0700923 * Listener interface for changes to Calls.
924 */
925 public interface Listener {
Santos Cordon995c8162013-07-29 09:22:22 -0700926 void onDisconnect(Call call);
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700927 void onIncoming(Call call);
928 void onUpdate(List<Call> calls);
Yorke Leede41f672013-09-19 13:46:55 -0700929 void onPostDialAction(Connection.PostDialState state, int callId, String remainingChars,
930 char c);
Santos Cordon63a84242013-07-23 13:32:52 -0700931 }
Santos Cordon249efd02013-08-05 03:33:56 -0700932
933 /**
934 * Result class for accessing a call by connection.
935 */
936 public static class CallResult {
937 public Call mCall;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700938 public Call mActionableCall;
Santos Cordon249efd02013-08-05 03:33:56 -0700939 public Connection mConnection;
940
941 private CallResult(Call call, Connection connection) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700942 this(call, call, connection);
943 }
944
945 private CallResult(Call call, Call actionableCall, Connection connection) {
Santos Cordon249efd02013-08-05 03:33:56 -0700946 mCall = call;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700947 mActionableCall = actionableCall;
Santos Cordon249efd02013-08-05 03:33:56 -0700948 mConnection = connection;
949 }
950
951 public Call getCall() {
952 return mCall;
953 }
954
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700955 // The call that should be used for call actions like hanging up.
956 public Call getActionableCall() {
957 return mActionableCall;
958 }
959
Santos Cordon249efd02013-08-05 03:33:56 -0700960 public Connection getConnection() {
961 return mConnection;
962 }
963 }
Santos Cordon63a84242013-07-23 13:32:52 -0700964}