blob: 9f4bb764b5cf5a045568e8ed976f3be68f1e6253 [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 *
Christine Chen91db67d2013-09-18 12:01:11 -070073 * TODO: Create a new Call class to replace the simple call Id ints
Santos Cordon63a84242013-07-23 13:32:52 -070074 * 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>();
Santos Cordona5d5db82013-09-15 13:00:34 -070094 private Connection mCdmaIncomingConnection;
Santos Cordonad1ed6d2013-09-16 03:04:23 -070095 private Connection mCdmaOutgoingConnection;
Santos Cordon63a84242013-07-23 13:32:52 -070096
Christine Chenee09a492013-08-06 16:02:29 -070097 public CallModeler(CallStateMonitor callStateMonitor, CallManager callManager,
Santos Cordon69a69192013-08-22 14:25:42 -070098 CallGatewayManager callGatewayManager) {
Santos Cordon63a84242013-07-23 13:32:52 -070099 mCallStateMonitor = callStateMonitor;
Santos Cordona3d05142013-07-29 11:25:17 -0700100 mCallManager = callManager;
Santos Cordon69a69192013-08-22 14:25:42 -0700101 mCallGatewayManager = callGatewayManager;
Santos Cordon63a84242013-07-23 13:32:52 -0700102
103 mCallStateMonitor.addListener(this);
104 }
105
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700106 @Override
Santos Cordon63a84242013-07-23 13:32:52 -0700107 public void handleMessage(Message msg) {
108 switch(msg.what) {
109 case CallStateMonitor.PHONE_NEW_RINGING_CONNECTION:
Christine Chenfb0cc2b2013-09-16 14:21:29 -0700110 // We let the CallNotifier handle the new ringing connection first. When the custom
111 // ringtone and send_to_voicemail settings are retrieved, CallNotifier will directly
112 // call CallModeler's onNewRingingConnection.
Santos Cordon63a84242013-07-23 13:32:52 -0700113 break;
114 case CallStateMonitor.PHONE_DISCONNECT:
Santos Cordona5d5db82013-09-15 13:00:34 -0700115 onDisconnect((Connection) ((AsyncResult) msg.obj).result);
Santos Cordon995c8162013-07-29 09:22:22 -0700116 break;
Santos Cordon54fdb592013-09-19 05:16:18 -0700117 case CallStateMonitor.PHONE_UNKNOWN_CONNECTION_APPEARED:
118 // fall through
Santos Cordon995c8162013-07-29 09:22:22 -0700119 case CallStateMonitor.PHONE_STATE_CHANGED:
120 onPhoneStateChanged((AsyncResult) msg.obj);
121 break;
Chiao Cheng3f015c92013-09-06 15:56:27 -0700122 case CallStateMonitor.PHONE_ON_DIAL_CHARS:
123 onPostDialChars((AsyncResult) msg.obj, (char) msg.arg1);
124 break;
Santos Cordon63a84242013-07-23 13:32:52 -0700125 default:
126 break;
127 }
128 }
129
Christine Chendaf7bf62013-08-05 19:12:31 -0700130 public void addListener(Listener listener) {
Santos Cordon63a84242013-07-23 13:32:52 -0700131 Preconditions.checkNotNull(listener);
Christine Chendaf7bf62013-08-05 19:12:31 -0700132 Preconditions.checkNotNull(mListeners);
Christine Chen4748abd2013-08-07 15:44:15 -0700133 if (!mListeners.contains(listener)) {
134 mListeners.add(listener);
135 }
Santos Cordon998f42b2013-08-02 16:13:12 -0700136 }
137
138 public List<Call> getFullList() {
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700139 final List<Call> calls =
140 Lists.newArrayListWithCapacity(mCallMap.size() + mConfCallMap.size());
141 calls.addAll(mCallMap.values());
142 calls.addAll(mConfCallMap.values());
143 return calls;
Santos Cordon63a84242013-07-23 13:32:52 -0700144 }
145
Santos Cordon249efd02013-08-05 03:33:56 -0700146 public CallResult getCallWithId(int callId) {
147 // max 8 connections, so this should be fast even through we are traversing the entire map.
148 for (Entry<Connection, Call> entry : mCallMap.entrySet()) {
149 if (entry.getValue().getCallId() == callId) {
150 return new CallResult(entry.getValue(), entry.getKey());
151 }
152 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700153
154 for (Entry<Connection, Call> entry : mConfCallMap.entrySet()) {
155 if (entry.getValue().getCallId() == callId) {
Christine Chen69050202013-09-14 15:36:35 -0700156 return new CallResult(entry.getValue(), entry.getKey());
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700157 }
158 }
Santos Cordon249efd02013-08-05 03:33:56 -0700159 return null;
160 }
161
Santos Cordonaf763a12013-08-19 20:04:58 -0700162 public boolean hasLiveCall() {
163 return hasLiveCallInternal(mCallMap) ||
164 hasLiveCallInternal(mConfCallMap);
165 }
166
Santos Cordona5d5db82013-09-15 13:00:34 -0700167 public void onCdmaCallWaiting(CdmaCallWaitingNotification callWaitingInfo) {
168 // We dont get the traditional onIncomingCall notification for cdma call waiting,
169 // but the Connection does actually exist. We need to find it in the set of ringing calls
170 // and pass it through our normal incoming logic.
171 final com.android.internal.telephony.Call teleCall =
172 mCallManager.getFirstActiveRingingCall();
173
174 if (teleCall.getState() == com.android.internal.telephony.Call.State.WAITING) {
175 Connection connection = teleCall.getLatestConnection();
176
177 if (connection != null) {
178 String number = connection.getAddress();
179 if (number != null && number.equals(callWaitingInfo.number)) {
180 Call call = onNewRingingConnection(connection);
181 mCdmaIncomingConnection = connection;
182 return;
183 }
184 }
185 }
186
187 Log.e(TAG, "CDMA Call waiting notification without a matching connection.");
188 }
189
190 public void onCdmaCallWaitingReject() {
191 // Cdma call was rejected...
192 if (mCdmaIncomingConnection != null) {
193 onDisconnect(mCdmaIncomingConnection);
194 mCdmaIncomingConnection = null;
195 } else {
196 Log.e(TAG, "CDMA Call waiting rejection without an incoming call.");
197 }
198 }
199
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700200 /**
201 * CDMA Calls have no sense of "dialing" state. For outgoing calls 3way calls we want to
202 * mimick this state so that the the UI can notify the user that there is a "dialing"
203 * call.
204 */
205 public void setCdmaOutgoing3WayCall(Connection connection) {
206 boolean wasSet = mCdmaOutgoingConnection != null;
207
208 mCdmaOutgoingConnection = connection;
209
210 // If we reset the connection, that mean we can now tell the user that the call is actually
211 // part of the conference call and move it out of the dialing state. To do this, issue a
212 // new update completely.
213 if (wasSet && mCdmaOutgoingConnection == null) {
214 onPhoneStateChanged(null);
215 }
216 }
217
Santos Cordonaf763a12013-08-19 20:04:58 -0700218 private boolean hasLiveCallInternal(HashMap<Connection, Call> map) {
219 for (Call call : map.values()) {
220 final int state = call.getState();
221 if (state == Call.State.ACTIVE ||
222 state == Call.State.CALL_WAITING ||
223 state == Call.State.CONFERENCED ||
224 state == Call.State.DIALING ||
Santos Cordonce02f3a2013-09-19 01:58:42 -0700225 state == Call.State.REDIALING ||
Santos Cordonaf763a12013-08-19 20:04:58 -0700226 state == Call.State.INCOMING ||
Christine Chen3e0f0412013-09-18 20:33:49 -0700227 state == Call.State.ONHOLD ||
228 state == Call.State.DISCONNECTING) {
Santos Cordonaf763a12013-08-19 20:04:58 -0700229 return true;
230 }
231 }
232 return false;
233 }
234
Santos Cordon2b73bd62013-08-27 14:53:43 -0700235 public boolean hasOutstandingActiveOrDialingCall() {
236 return hasOutstandingActiveOrDialingCallInternal(mCallMap) ||
237 hasOutstandingActiveOrDialingCallInternal(mConfCallMap);
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700238 }
239
Santos Cordon2b73bd62013-08-27 14:53:43 -0700240 private static boolean hasOutstandingActiveOrDialingCallInternal(
241 HashMap<Connection, Call> map) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700242 for (Call call : map.values()) {
243 final int state = call.getState();
Santos Cordonce02f3a2013-09-19 01:58:42 -0700244 if (state == Call.State.ACTIVE || Call.State.isDialing(state)) {
Santos Cordon2eaff902013-08-05 04:37:55 -0700245 return true;
246 }
247 }
248
249 return false;
250 }
251
Chiao Cheng3f015c92013-09-06 15:56:27 -0700252
253 /**
254 * Handles the POST_ON_DIAL_CHARS message from the Phone (see our call to
255 * mPhone.setOnPostDialCharacter() above.)
256 *
257 * TODO: NEED TO TEST THIS SEQUENCE now that we no longer handle "dialable" key events here in
258 * the InCallScreen: we do directly to the Dialer UI instead. Similarly, we may now need to go
259 * directly to the Dialer to handle POST_ON_DIAL_CHARS too.
260 */
261 private void onPostDialChars(AsyncResult r, char ch) {
262 final Connection c = (Connection) r.result;
263
264 if (c != null) {
265 final Connection.PostDialState state = (Connection.PostDialState) r.userObj;
266
267 switch (state) {
Chiao Cheng3f015c92013-09-06 15:56:27 -0700268 case WAIT:
269 final Call call = getCallFromMap(mCallMap, c, false);
270 if (call == null) {
271 Log.i(TAG, "Call no longer exists. Skipping onPostDialWait().");
272 } else {
273 for (Listener mListener : mListeners) {
Yorke Leede41f672013-09-19 13:46:55 -0700274 mListener.onPostDialAction(state, call.getCallId(),
275 c.getRemainingPostDialString(), ch);
Chiao Cheng3f015c92013-09-06 15:56:27 -0700276 }
277 }
278 break;
Chiao Cheng3f015c92013-09-06 15:56:27 -0700279 default:
Yorke Leede41f672013-09-19 13:46:55 -0700280 // This is primarily to cause the DTMFTonePlayer to play local tones.
281 // Other listeners simply perform no-ops.
282 for (Listener mListener : mListeners) {
283 mListener.onPostDialAction(state, 0, "", ch);
284 }
Chiao Cheng3f015c92013-09-06 15:56:27 -0700285 break;
286 }
287 }
288 }
289
Christine Chenfb0cc2b2013-09-16 14:21:29 -0700290 /* package */ Call onNewRingingConnection(Connection conn) {
Santos Cordon2b73bd62013-08-27 14:53:43 -0700291 Log.i(TAG, "onNewRingingConnection");
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700292 final Call call = getCallFromMap(mCallMap, conn, true);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700293
Christine Chenfb0cc2b2013-09-16 14:21:29 -0700294 if (call != null) {
295 updateCallFromConnection(call, conn, false);
Santos Cordon63a84242013-07-23 13:32:52 -0700296
Christine Chenfb0cc2b2013-09-16 14:21:29 -0700297 for (int i = 0; i < mListeners.size(); ++i) {
298 mListeners.get(i).onIncoming(call);
Christine Chenee09a492013-08-06 16:02:29 -0700299 }
Santos Cordon63a84242013-07-23 13:32:52 -0700300 }
Santos Cordona5d5db82013-09-15 13:00:34 -0700301
Santos Cordon24a92b32013-09-26 16:48:14 -0700302 PhoneGlobals.getInstance().updateWakeState();
Santos Cordona5d5db82013-09-15 13:00:34 -0700303 return call;
Santos Cordon63a84242013-07-23 13:32:52 -0700304 }
305
Santos Cordona5d5db82013-09-15 13:00:34 -0700306 private void onDisconnect(Connection conn) {
Santos Cordon2b73bd62013-08-27 14:53:43 -0700307 Log.i(TAG, "onDisconnect");
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700308 final Call call = getCallFromMap(mCallMap, conn, false);
Santos Cordon63a84242013-07-23 13:32:52 -0700309
Santos Cordon995c8162013-07-29 09:22:22 -0700310 if (call != null) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700311 final boolean wasConferenced = call.getState() == State.CONFERENCED;
312
313 updateCallFromConnection(call, conn, false);
Santos Cordon63a84242013-07-23 13:32:52 -0700314
Christine Chendaf7bf62013-08-05 19:12:31 -0700315 for (int i = 0; i < mListeners.size(); ++i) {
316 mListeners.get(i).onDisconnect(call);
Santos Cordon63a84242013-07-23 13:32:52 -0700317 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700318
319 // If it was a conferenced call, we need to run the entire update
320 // to make the proper changes to parent conference calls.
321 if (wasConferenced) {
322 onPhoneStateChanged(null);
323 }
324
325 mCallMap.remove(conn);
Santos Cordon63a84242013-07-23 13:32:52 -0700326 }
Santos Cordon24a92b32013-09-26 16:48:14 -0700327
Christine Chen6af50e62013-09-26 15:05:57 -0700328 mCallManager.clearDisconnected();
Santos Cordon24a92b32013-09-26 16:48:14 -0700329 PhoneGlobals.getInstance().updateWakeState();
Santos Cordon63a84242013-07-23 13:32:52 -0700330 }
331
Santos Cordona3d05142013-07-29 11:25:17 -0700332 /**
333 * Called when the phone state changes.
Santos Cordona3d05142013-07-29 11:25:17 -0700334 */
Santos Cordon995c8162013-07-29 09:22:22 -0700335 private void onPhoneStateChanged(AsyncResult r) {
Santos Cordon2b73bd62013-08-27 14:53:43 -0700336 Log.i(TAG, "onPhoneStateChanged: ");
Santos Cordon998f42b2013-08-02 16:13:12 -0700337 final List<Call> updatedCalls = Lists.newArrayList();
338 doUpdate(false, updatedCalls);
339
Santos Cordon71d5c6e2013-09-05 21:34:33 -0700340 if (updatedCalls.size() > 0) {
341 for (int i = 0; i < mListeners.size(); ++i) {
342 mListeners.get(i).onUpdate(updatedCalls);
343 }
Santos Cordon998f42b2013-08-02 16:13:12 -0700344 }
Santos Cordon24a92b32013-09-26 16:48:14 -0700345
346 PhoneGlobals.getInstance().updateWakeState();
Santos Cordon998f42b2013-08-02 16:13:12 -0700347 }
348
349
350 /**
351 * Go through the Calls from CallManager and return the list of calls that were updated.
352 * Or, the full list if requested.
353 */
354 private void doUpdate(boolean fullUpdate, List<Call> out) {
Santos Cordona3d05142013-07-29 11:25:17 -0700355 final List<com.android.internal.telephony.Call> telephonyCalls = Lists.newArrayList();
356 telephonyCalls.addAll(mCallManager.getRingingCalls());
357 telephonyCalls.addAll(mCallManager.getForegroundCalls());
358 telephonyCalls.addAll(mCallManager.getBackgroundCalls());
359
Santos Cordona3d05142013-07-29 11:25:17 -0700360 // Cycle through all the Connections on all the Calls. Update our Call objects
361 // to reflect any new state and send the updated Call objects to the handler service.
362 for (com.android.internal.telephony.Call telephonyCall : telephonyCalls) {
Santos Cordona3d05142013-07-29 11:25:17 -0700363
364 for (Connection connection : telephonyCall.getConnections()) {
Christine Chen3e0f0412013-09-18 20:33:49 -0700365 if (DBG) Log.d(TAG, "connection: " + connection + connection.getState());
Santos Cordona5d5db82013-09-15 13:00:34 -0700366
Santos Cordon12a03aa2013-09-12 23:34:05 -0700367 // We only send updates for live calls which are not incoming (ringing).
368 // Disconnected and incoming calls are handled by onDisconnect and
369 // onNewRingingConnection.
Christine Chendf19e452013-09-25 17:21:23 -0700370 final boolean shouldUpdate =
Christine Chen3e0f0412013-09-18 20:33:49 -0700371 connection.getState() !=
372 com.android.internal.telephony.Call.State.DISCONNECTED &&
373 connection.getState() !=
374 com.android.internal.telephony.Call.State.IDLE &&
Santos Cordon71d5c6e2013-09-05 21:34:33 -0700375 !connection.getState().isRinging();
376
Christine Chendf19e452013-09-25 17:21:23 -0700377 final boolean isDisconnecting = connection.getState() ==
378 com.android.internal.telephony.Call.State.DISCONNECTING;
379
380 // For disconnecting calls, we still need to send the update to the UI but we do
381 // not create a new call if the call did not exist.
382 final boolean shouldCreate = shouldUpdate && !isDisconnecting;
383
Santos Cordon71d5c6e2013-09-05 21:34:33 -0700384 // New connections return a Call with INVALID state, which does not translate to
Santos Cordone38b1ff2013-08-07 12:12:16 -0700385 // a state in the internal.telephony.Call object. This ensures that staleness
386 // check below fails and we always add the item to the update list if it is new.
Christine Chendf19e452013-09-25 17:21:23 -0700387 final Call call = getCallFromMap(mCallMap, connection, shouldCreate /* create */);
Santos Cordon71d5c6e2013-09-05 21:34:33 -0700388
Santos Cordon12a03aa2013-09-12 23:34:05 -0700389 if (call == null || !shouldUpdate) {
Santos Cordona5d5db82013-09-15 13:00:34 -0700390 if (DBG) Log.d(TAG, "update skipped");
Santos Cordon71d5c6e2013-09-05 21:34:33 -0700391 continue;
392 }
Santos Cordona3d05142013-07-29 11:25:17 -0700393
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700394 boolean changed = updateCallFromConnection(call, connection, false);
Santos Cordon2b73bd62013-08-27 14:53:43 -0700395
Santos Cordone38b1ff2013-08-07 12:12:16 -0700396 if (fullUpdate || changed) {
Santos Cordon998f42b2013-08-02 16:13:12 -0700397 out.add(call);
Santos Cordona3d05142013-07-29 11:25:17 -0700398 }
399 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700400
401 // We do a second loop to address conference call scenarios. We do this as a separate
402 // loop to ensure all child calls are up to date before we start updating the parent
403 // conference calls.
404 for (Connection connection : telephonyCall.getConnections()) {
405 updateForConferenceCalls(connection, out);
406 }
407
Santos Cordona3d05142013-07-29 11:25:17 -0700408 }
Santos Cordona3d05142013-07-29 11:25:17 -0700409 }
410
Santos Cordone38b1ff2013-08-07 12:12:16 -0700411 /**
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700412 * Checks to see if the connection is the first connection in a conference call.
413 * If it is a conference call, we will create a new Conference Call object or
414 * update the existing conference call object for that connection.
415 * If it is not a conference call but a previous associated conference call still exists,
416 * we mark it as idle and remove it from the map.
417 * In both cases above, we add the Calls to be updated to the UI.
418 * @param connection The connection object to check.
419 * @param updatedCalls List of 'updated' calls that will be sent to the UI.
420 */
421 private boolean updateForConferenceCalls(Connection connection, List<Call> updatedCalls) {
422 // We consider this connection a conference connection if the call it
Yorke Leecd3f9692013-09-14 13:51:27 -0700423 // belongs to is a multiparty call AND it is the first live connection.
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700424 final boolean isConferenceCallConnection = isPartOfLiveConferenceCall(connection) &&
Yorke Leecd3f9692013-09-14 13:51:27 -0700425 getEarliestLiveConnection(connection.getCall()) == connection;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700426
427 boolean changed = false;
428
429 // If this connection is the main connection for the conference call, then create or update
430 // a Call object for that conference call.
431 if (isConferenceCallConnection) {
432 final Call confCall = getCallFromMap(mConfCallMap, connection, true);
433 changed = updateCallFromConnection(confCall, connection, true);
434
435 if (changed) {
436 updatedCalls.add(confCall);
437 }
438
439 if (DBG) Log.d(TAG, "Updating a conference call: " + confCall);
440
441 // It is possible that through a conference call split, there may be lingering conference
442 // calls where this connection was the main connection. We clean those up here.
443 } else {
444 final Call oldConfCall = getCallFromMap(mConfCallMap, connection, false);
445
446 // We found a conference call for this connection, which is no longer a conference call.
447 // Kill it!
448 if (oldConfCall != null) {
449 if (DBG) Log.d(TAG, "Cleaning up an old conference call: " + oldConfCall);
450 mConfCallMap.remove(connection);
451 oldConfCall.setState(State.IDLE);
452 changed = true;
453
454 // add to the list of calls to update
455 updatedCalls.add(oldConfCall);
456 }
457 }
458
459 return changed;
460 }
461
Yorke Leecd3f9692013-09-14 13:51:27 -0700462 private Connection getEarliestLiveConnection(com.android.internal.telephony.Call call) {
463 final List<Connection> connections = call.getConnections();
464 final int size = connections.size();
465 Connection earliestConn = null;
466 long earliestTime = Long.MAX_VALUE;
467 for (int i = 0; i < size; i++) {
468 final Connection connection = connections.get(i);
469 if (!connection.isAlive()) continue;
470 final long time = connection.getCreateTime();
471 if (time < earliestTime) {
472 earliestTime = time;
473 earliestConn = connection;
474 }
475 }
476 return earliestConn;
477 }
478
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700479 /**
Santos Cordon69a69192013-08-22 14:25:42 -0700480 * Sets the new call state onto the call and performs some additional logic
481 * associated with setting the state.
482 */
483 private void setNewState(Call call, int newState, Connection connection) {
484 Preconditions.checkState(call.getState() != newState);
485
486 // When starting an outgoing call, we need to grab gateway information
487 // for the call, if available, and set it.
488 final RawGatewayInfo info = mCallGatewayManager.getGatewayInfo(connection);
489
Santos Cordonce02f3a2013-09-19 01:58:42 -0700490 if (Call.State.isDialing(newState)) {
Santos Cordon69a69192013-08-22 14:25:42 -0700491 if (!info.isEmpty()) {
492 call.setGatewayNumber(info.getFormattedGatewayNumber());
493 call.setGatewayPackage(info.packageName);
494 }
495 } else if (!Call.State.isConnected(newState)) {
496 mCallGatewayManager.clearGatewayData(connection);
497 }
498
499 call.setState(newState);
500 }
501
502 /**
Santos Cordone38b1ff2013-08-07 12:12:16 -0700503 * Updates the Call properties to match the state of the connection object
504 * that it represents.
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700505 * @param call The call object to update.
506 * @param connection The connection object from which to update call.
507 * @param isForConference There are slight differences in how we populate data for conference
508 * calls. This boolean tells us which method to use.
Santos Cordone38b1ff2013-08-07 12:12:16 -0700509 */
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700510 private boolean updateCallFromConnection(Call call, Connection connection,
511 boolean isForConference) {
Santos Cordone38b1ff2013-08-07 12:12:16 -0700512 boolean changed = false;
513
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700514 final int newState = translateStateFromTelephony(connection, isForConference);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700515
516 if (call.getState() != newState) {
Santos Cordon69a69192013-08-22 14:25:42 -0700517 setNewState(call, newState, connection);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700518 changed = true;
519 }
520
Santos Cordone38b1ff2013-08-07 12:12:16 -0700521 final Call.DisconnectCause newDisconnectCause =
522 translateDisconnectCauseFromTelephony(connection.getDisconnectCause());
523 if (call.getDisconnectCause() != newDisconnectCause) {
524 call.setDisconnectCause(newDisconnectCause);
525 changed = true;
526 }
527
Santos Cordonbbe8ecf2013-08-13 15:26:18 -0700528 final long oldConnectTime = call.getConnectTime();
529 if (oldConnectTime != connection.getConnectTime()) {
530 call.setConnectTime(connection.getConnectTime());
531 changed = true;
532 }
533
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700534 if (!isForConference) {
Santos Cordon69a69192013-08-22 14:25:42 -0700535 // Number
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700536 final String oldNumber = call.getNumber();
Santos Cordon69a69192013-08-22 14:25:42 -0700537 String newNumber = connection.getAddress();
538 RawGatewayInfo info = mCallGatewayManager.getGatewayInfo(connection);
539 if (!info.isEmpty()) {
540 newNumber = info.trueNumber;
541 }
542 if (TextUtils.isEmpty(oldNumber) || !oldNumber.equals(newNumber)) {
543 call.setNumber(newNumber);
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700544 changed = true;
545 }
546
Santos Cordon69a69192013-08-22 14:25:42 -0700547 // Number presentation
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700548 final int newNumberPresentation = connection.getNumberPresentation();
549 if (call.getNumberPresentation() != newNumberPresentation) {
550 call.setNumberPresentation(newNumberPresentation);
551 changed = true;
552 }
553
Santos Cordon69a69192013-08-22 14:25:42 -0700554 // Name
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700555 final String oldCnapName = call.getCnapName();
556 if (TextUtils.isEmpty(oldCnapName) || !oldCnapName.equals(connection.getCnapName())) {
557 call.setCnapName(connection.getCnapName());
558 changed = true;
559 }
Santos Cordon69a69192013-08-22 14:25:42 -0700560
561 // Name Presentation
562 final int newCnapNamePresentation = connection.getCnapNamePresentation();
563 if (call.getCnapNamePresentation() != newCnapNamePresentation) {
564 call.setCnapNamePresentation(newCnapNamePresentation);
565 changed = true;
566 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700567 } else {
568
569 // update the list of children by:
570 // 1) Saving the old set
571 // 2) Removing all children
572 // 3) Adding the correct children into the Call
573 // 4) Comparing the new children set with the old children set
574 ImmutableSortedSet<Integer> oldSet = call.getChildCallIds();
575 call.removeAllChildren();
576
577 if (connection.getCall() != null) {
578 for (Connection childConn : connection.getCall().getConnections()) {
579 final Call childCall = getCallFromMap(mCallMap, childConn, false);
580 if (childCall != null && childConn.isAlive()) {
581 call.addChildId(childCall.getCallId());
582 }
583 }
584 }
Christine Chen45277022013-09-05 10:55:37 -0700585 changed |= !oldSet.equals(call.getChildCallIds());
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700586 }
587
Santos Cordoneead6ec2013-08-07 22:16:33 -0700588 /**
589 * !!! Uses values from connection and call collected above so this part must be last !!!
590 */
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700591 final int newCapabilities = getCapabilitiesFor(connection, call, isForConference);
Santos Cordon26e7b242013-08-07 21:15:45 -0700592 if (call.getCapabilities() != newCapabilities) {
593 call.setCapabilities(newCapabilities);
594 changed = true;
595 }
596
Santos Cordone38b1ff2013-08-07 12:12:16 -0700597 return changed;
598 }
599
Santos Cordon26e7b242013-08-07 21:15:45 -0700600 /**
601 * Returns a mask of capabilities for the connection such as merge, hold, etc.
602 */
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700603 private int getCapabilitiesFor(Connection connection, Call call, boolean isForConference) {
Santos Cordoneead6ec2013-08-07 22:16:33 -0700604 final boolean callIsActive = (call.getState() == Call.State.ACTIVE);
605 final Phone phone = connection.getCall().getPhone();
606
Santos Cordoneead6ec2013-08-07 22:16:33 -0700607 boolean canAddCall = false;
608 boolean canMergeCall = false;
609 boolean canSwapCall = false;
Yorke Lee814da302013-08-30 16:01:07 -0700610 boolean canRespondViaText = false;
Christine Chenaf2fd0a2013-09-13 16:27:40 -0700611 boolean canMute = false;
612
613 final boolean supportHold = PhoneUtils.okToSupportHold(mCallManager);
Christine Chen94853bc2013-09-17 16:53:33 -0700614 final boolean canHold = (supportHold ? PhoneUtils.okToHoldCall(mCallManager) : false);
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700615 final boolean genericConf = isForConference &&
616 (connection.getCall().getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA);
Santos Cordoneead6ec2013-08-07 22:16:33 -0700617
618 // only applies to active calls
619 if (callIsActive) {
Santos Cordoneead6ec2013-08-07 22:16:33 -0700620 canMergeCall = PhoneUtils.okToMergeCalls(mCallManager);
621 canSwapCall = PhoneUtils.okToSwapCalls(mCallManager);
622 }
623
Christine Chenaf2fd0a2013-09-13 16:27:40 -0700624 canAddCall = PhoneUtils.okToAddCall(mCallManager);
625
626 // "Mute": only enabled when the foreground call is ACTIVE.
627 // (It's meaningless while on hold, or while DIALING/ALERTING.)
628 // It's also explicitly disabled during emergency calls or if
629 // emergency callback mode (ECM) is active.
630 boolean isEmergencyCall = false;
631 if (connection != null) {
632 isEmergencyCall = PhoneNumberUtils.isLocalEmergencyNumber(connection.getAddress(),
633 phone.getContext());
634 }
635 boolean isECM = PhoneUtils.isPhoneInEcm(phone);
636 if (isEmergencyCall || isECM) { // disable "Mute" item
637 canMute = false;
638 } else {
639 canMute = callIsActive;
640 }
641
Yorke Lee814da302013-08-30 16:01:07 -0700642 canRespondViaText = RejectWithTextMessageManager.allowRespondViaSmsForCall(call,
643 connection);
644
Santos Cordoneead6ec2013-08-07 22:16:33 -0700645 // special rules section!
646 // CDMA always has Add
647 if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
648 canAddCall = true;
Santos Cordoneead6ec2013-08-07 22:16:33 -0700649 }
650
Santos Cordon26e7b242013-08-07 21:15:45 -0700651 int retval = 0x0;
Santos Cordoneead6ec2013-08-07 22:16:33 -0700652 if (canHold) {
Santos Cordon26e7b242013-08-07 21:15:45 -0700653 retval |= Capabilities.HOLD;
654 }
Christine Chenaf2fd0a2013-09-13 16:27:40 -0700655 if (supportHold) {
656 retval |= Capabilities.SUPPORT_HOLD;
657 }
Santos Cordoneead6ec2013-08-07 22:16:33 -0700658 if (canAddCall) {
659 retval |= Capabilities.ADD_CALL;
660 }
661 if (canMergeCall) {
662 retval |= Capabilities.MERGE_CALLS;
663 }
664 if (canSwapCall) {
665 retval |= Capabilities.SWAP_CALLS;
666 }
Yorke Lee814da302013-08-30 16:01:07 -0700667 if (canRespondViaText) {
668 retval |= Capabilities.RESPOND_VIA_TEXT;
669 }
Christine Chenaf2fd0a2013-09-13 16:27:40 -0700670 if (canMute) {
671 retval |= Capabilities.MUTE;
672 }
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700673 if (genericConf) {
674 retval |= Capabilities.GENERIC_CONFERENCE;
675 }
Yorke Lee814da302013-08-30 16:01:07 -0700676
Santos Cordon26e7b242013-08-07 21:15:45 -0700677 return retval;
678 }
679
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700680 /**
681 * Returns true if the Connection is part of a multiparty call.
682 * We do this by checking the isMultiparty() method of the telephony.Call object and also
683 * checking to see if more than one of it's children is alive.
684 */
685 private boolean isPartOfLiveConferenceCall(Connection connection) {
686 if (connection.getCall() != null && connection.getCall().isMultiparty()) {
687 int count = 0;
688 for (Connection currConn : connection.getCall().getConnections()) {
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700689
690 // Only count connections which are alive and never cound the special
691 // "dialing" 3way call for CDMA calls.
692 if (currConn.isAlive() && currConn != mCdmaOutgoingConnection) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700693 count++;
694 if (count >= 2) {
695 return true;
696 }
697 }
698 }
699 }
700 return false;
701 }
702
703 private int translateStateFromTelephony(Connection connection, boolean isForConference) {
704
Santos Cordonce02f3a2013-09-19 01:58:42 -0700705 com.android.internal.telephony.Call.State connState = connection.getState();
706
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700707 // For the "fake" outgoing CDMA call, we need to always treat it as an outgoing call.
708 if (mCdmaOutgoingConnection == connection) {
Santos Cordonce02f3a2013-09-19 01:58:42 -0700709 connState = com.android.internal.telephony.Call.State.DIALING;
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700710 }
711
Santos Cordona3d05142013-07-29 11:25:17 -0700712 int retval = State.IDLE;
Santos Cordonce02f3a2013-09-19 01:58:42 -0700713 switch (connState) {
Santos Cordona3d05142013-07-29 11:25:17 -0700714 case ACTIVE:
715 retval = State.ACTIVE;
716 break;
717 case INCOMING:
718 retval = State.INCOMING;
719 break;
720 case DIALING:
721 case ALERTING:
Santos Cordonce02f3a2013-09-19 01:58:42 -0700722 if (PhoneGlobals.getInstance().notifier.getIsCdmaRedialCall()) {
723 retval = State.REDIALING;
724 } else {
725 retval = State.DIALING;
726 }
Santos Cordona3d05142013-07-29 11:25:17 -0700727 break;
728 case WAITING:
729 retval = State.CALL_WAITING;
730 break;
731 case HOLDING:
732 retval = State.ONHOLD;
733 break;
Santos Cordone38b1ff2013-08-07 12:12:16 -0700734 case DISCONNECTING:
Christine Chen3e0f0412013-09-18 20:33:49 -0700735 retval = State.DISCONNECTING;
736 break;
737 case DISCONNECTED:
Santos Cordone38b1ff2013-08-07 12:12:16 -0700738 retval = State.DISCONNECTED;
Santos Cordona3d05142013-07-29 11:25:17 -0700739 default:
740 }
741
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700742 // If we are dealing with a potential child call (not the parent conference call),
743 // the check to see if we have to set the state to CONFERENCED.
744 if (!isForConference) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700745 // if the connection is part of a multiparty call, and it is live,
746 // annotate it with CONFERENCED state instead.
747 if (isPartOfLiveConferenceCall(connection) && connection.isAlive()) {
748 return State.CONFERENCED;
749 }
750 }
751
Santos Cordona3d05142013-07-29 11:25:17 -0700752 return retval;
Santos Cordon995c8162013-07-29 09:22:22 -0700753 }
754
Santos Cordone38b1ff2013-08-07 12:12:16 -0700755 private final ImmutableMap<Connection.DisconnectCause, Call.DisconnectCause> CAUSE_MAP =
756 ImmutableMap.<Connection.DisconnectCause, Call.DisconnectCause>builder()
757 .put(Connection.DisconnectCause.BUSY, Call.DisconnectCause.BUSY)
758 .put(Connection.DisconnectCause.CALL_BARRED, Call.DisconnectCause.CALL_BARRED)
759 .put(Connection.DisconnectCause.CDMA_ACCESS_BLOCKED,
760 Call.DisconnectCause.CDMA_ACCESS_BLOCKED)
761 .put(Connection.DisconnectCause.CDMA_ACCESS_FAILURE,
762 Call.DisconnectCause.CDMA_ACCESS_FAILURE)
763 .put(Connection.DisconnectCause.CDMA_DROP, Call.DisconnectCause.CDMA_DROP)
764 .put(Connection.DisconnectCause.CDMA_INTERCEPT, Call.DisconnectCause.CDMA_INTERCEPT)
765 .put(Connection.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE,
766 Call.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE)
767 .put(Connection.DisconnectCause.CDMA_NOT_EMERGENCY,
768 Call.DisconnectCause.CDMA_NOT_EMERGENCY)
769 .put(Connection.DisconnectCause.CDMA_PREEMPTED, Call.DisconnectCause.CDMA_PREEMPTED)
770 .put(Connection.DisconnectCause.CDMA_REORDER, Call.DisconnectCause.CDMA_REORDER)
771 .put(Connection.DisconnectCause.CDMA_RETRY_ORDER,
772 Call.DisconnectCause.CDMA_RETRY_ORDER)
773 .put(Connection.DisconnectCause.CDMA_SO_REJECT, Call.DisconnectCause.CDMA_SO_REJECT)
774 .put(Connection.DisconnectCause.CONGESTION, Call.DisconnectCause.CONGESTION)
775 .put(Connection.DisconnectCause.CS_RESTRICTED, Call.DisconnectCause.CS_RESTRICTED)
776 .put(Connection.DisconnectCause.CS_RESTRICTED_EMERGENCY,
777 Call.DisconnectCause.CS_RESTRICTED_EMERGENCY)
778 .put(Connection.DisconnectCause.CS_RESTRICTED_NORMAL,
779 Call.DisconnectCause.CS_RESTRICTED_NORMAL)
780 .put(Connection.DisconnectCause.ERROR_UNSPECIFIED,
781 Call.DisconnectCause.ERROR_UNSPECIFIED)
782 .put(Connection.DisconnectCause.FDN_BLOCKED, Call.DisconnectCause.FDN_BLOCKED)
783 .put(Connection.DisconnectCause.ICC_ERROR, Call.DisconnectCause.ICC_ERROR)
784 .put(Connection.DisconnectCause.INCOMING_MISSED,
785 Call.DisconnectCause.INCOMING_MISSED)
786 .put(Connection.DisconnectCause.INCOMING_REJECTED,
787 Call.DisconnectCause.INCOMING_REJECTED)
788 .put(Connection.DisconnectCause.INVALID_CREDENTIALS,
789 Call.DisconnectCause.INVALID_CREDENTIALS)
790 .put(Connection.DisconnectCause.INVALID_NUMBER,
791 Call.DisconnectCause.INVALID_NUMBER)
792 .put(Connection.DisconnectCause.LIMIT_EXCEEDED, Call.DisconnectCause.LIMIT_EXCEEDED)
793 .put(Connection.DisconnectCause.LOCAL, Call.DisconnectCause.LOCAL)
794 .put(Connection.DisconnectCause.LOST_SIGNAL, Call.DisconnectCause.LOST_SIGNAL)
795 .put(Connection.DisconnectCause.MMI, Call.DisconnectCause.MMI)
796 .put(Connection.DisconnectCause.NORMAL, Call.DisconnectCause.NORMAL)
797 .put(Connection.DisconnectCause.NOT_DISCONNECTED,
798 Call.DisconnectCause.NOT_DISCONNECTED)
799 .put(Connection.DisconnectCause.NUMBER_UNREACHABLE,
800 Call.DisconnectCause.NUMBER_UNREACHABLE)
801 .put(Connection.DisconnectCause.OUT_OF_NETWORK, Call.DisconnectCause.OUT_OF_NETWORK)
802 .put(Connection.DisconnectCause.OUT_OF_SERVICE, Call.DisconnectCause.OUT_OF_SERVICE)
803 .put(Connection.DisconnectCause.POWER_OFF, Call.DisconnectCause.POWER_OFF)
804 .put(Connection.DisconnectCause.SERVER_ERROR, Call.DisconnectCause.SERVER_ERROR)
805 .put(Connection.DisconnectCause.SERVER_UNREACHABLE,
806 Call.DisconnectCause.SERVER_UNREACHABLE)
807 .put(Connection.DisconnectCause.TIMED_OUT, Call.DisconnectCause.TIMED_OUT)
808 .put(Connection.DisconnectCause.UNOBTAINABLE_NUMBER,
809 Call.DisconnectCause.UNOBTAINABLE_NUMBER)
810 .build();
811
812 private Call.DisconnectCause translateDisconnectCauseFromTelephony(
813 Connection.DisconnectCause causeSource) {
814
815 if (CAUSE_MAP.containsKey(causeSource)) {
816 return CAUSE_MAP.get(causeSource);
817 }
818
819 return Call.DisconnectCause.UNKNOWN;
820 }
821
Santos Cordon63a84242013-07-23 13:32:52 -0700822 /**
Santos Cordone38b1ff2013-08-07 12:12:16 -0700823 * Gets an existing callId for a connection, or creates one if none exists.
824 * This function does NOT set any of the Connection data onto the Call class.
825 * A separate call to updateCallFromConnection must be made for that purpose.
Santos Cordon63a84242013-07-23 13:32:52 -0700826 */
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700827 private Call getCallFromMap(HashMap<Connection, Call> map, Connection conn,
828 boolean createIfMissing) {
Santos Cordon995c8162013-07-29 09:22:22 -0700829 Call call = null;
Santos Cordon63a84242013-07-23 13:32:52 -0700830
831 // Find the call id or create if missing and requested.
832 if (conn != null) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700833 if (map.containsKey(conn)) {
834 call = map.get(conn);
Santos Cordon63a84242013-07-23 13:32:52 -0700835 } else if (createIfMissing) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700836 call = createNewCall();
837 map.put(conn, call);
Santos Cordon63a84242013-07-23 13:32:52 -0700838 }
839 }
Santos Cordon995c8162013-07-29 09:22:22 -0700840 return call;
Santos Cordon63a84242013-07-23 13:32:52 -0700841 }
842
843 /**
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700844 * Creates a brand new connection for the call.
845 */
846 private Call createNewCall() {
847 int callId;
848 int newNextCallId;
849 do {
850 callId = mNextCallId.get();
851
852 // protect against overflow
853 newNextCallId = (callId == Integer.MAX_VALUE ?
854 CALL_ID_START_VALUE : callId + 1);
855
856 // Keep looping if the change was not atomic OR the value is already taken.
857 // The call to containsValue() is linear, however, most devices support a
858 // maximum of 7 connections so it's not expensive.
859 } while (!mNextCallId.compareAndSet(callId, newNextCallId));
860
861 return new Call(callId);
862 }
863
864 /**
Santos Cordon63a84242013-07-23 13:32:52 -0700865 * Listener interface for changes to Calls.
866 */
867 public interface Listener {
Santos Cordon995c8162013-07-29 09:22:22 -0700868 void onDisconnect(Call call);
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700869 void onIncoming(Call call);
870 void onUpdate(List<Call> calls);
Yorke Leede41f672013-09-19 13:46:55 -0700871 void onPostDialAction(Connection.PostDialState state, int callId, String remainingChars,
872 char c);
Santos Cordon63a84242013-07-23 13:32:52 -0700873 }
Santos Cordon249efd02013-08-05 03:33:56 -0700874
875 /**
876 * Result class for accessing a call by connection.
877 */
878 public static class CallResult {
879 public Call mCall;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700880 public Call mActionableCall;
Santos Cordon249efd02013-08-05 03:33:56 -0700881 public Connection mConnection;
882
883 private CallResult(Call call, Connection connection) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700884 this(call, call, connection);
885 }
886
887 private CallResult(Call call, Call actionableCall, Connection connection) {
Santos Cordon249efd02013-08-05 03:33:56 -0700888 mCall = call;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700889 mActionableCall = actionableCall;
Santos Cordon249efd02013-08-05 03:33:56 -0700890 mConnection = connection;
891 }
892
893 public Call getCall() {
894 return mCall;
895 }
896
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700897 // The call that should be used for call actions like hanging up.
898 public Call getActionableCall() {
899 return mActionableCall;
900 }
901
Santos Cordon249efd02013-08-05 03:33:56 -0700902 public Connection getConnection() {
903 return mConnection;
904 }
905 }
Santos Cordon63a84242013-07-23 13:32:52 -0700906}