blob: ae8d251c0415dceabdee098efa601fa0f8845be7 [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;
Anders Kristensen0b35f042014-02-27 14:31:07 -080023import android.telephony.DisconnectCause;
Christine Chenaf2fd0a2013-09-13 16:27:40 -070024import android.telephony.PhoneNumberUtils;
Santos Cordone38b1ff2013-08-07 12:12:16 -070025import android.text.TextUtils;
26import android.util.Log;
Santos Cordon63a84242013-07-23 13:32:52 -070027
Santos Cordona3d05142013-07-29 11:25:17 -070028import com.android.internal.telephony.CallManager;
Santos Cordon63a84242013-07-23 13:32:52 -070029import com.android.internal.telephony.Connection;
Santos Cordoneead6ec2013-08-07 22:16:33 -070030import com.android.internal.telephony.Phone;
Santos Cordona3d05142013-07-29 11:25:17 -070031import com.android.internal.telephony.PhoneConstants;
Santos Cordona5d5db82013-09-15 13:00:34 -070032import com.android.internal.telephony.TelephonyCapabilities;
33import com.android.internal.telephony.cdma.CdmaCallWaitingNotification;
Santos Cordon69a69192013-08-22 14:25:42 -070034import com.android.phone.CallGatewayManager.RawGatewayInfo;
Santos Cordon995c8162013-07-29 09:22:22 -070035import com.android.services.telephony.common.Call;
Santos Cordon26e7b242013-08-07 21:15:45 -070036import com.android.services.telephony.common.Call.Capabilities;
Santos Cordona3d05142013-07-29 11:25:17 -070037import com.android.services.telephony.common.Call.State;
Santos Cordon63a84242013-07-23 13:32:52 -070038
Yorke Lee814da302013-08-30 16:01:07 -070039import com.google.android.collect.Maps;
Santos Cordon25008582013-11-12 13:39:40 -080040import com.google.android.collect.Sets;
Yorke Lee814da302013-08-30 16:01:07 -070041import com.google.common.base.Preconditions;
Yorke Lee814da302013-08-30 16:01:07 -070042import 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.
Santos Cordon63a84242013-07-23 13:32:52 -070077 */
78public class CallModeler extends Handler {
79
80 private static final String TAG = CallModeler.class.getSimpleName();
Santos Cordon4ad64cd2013-08-15 00:36:14 -070081 private static final boolean DBG =
82 (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
Santos Cordon63a84242013-07-23 13:32:52 -070083
84 private static final int CALL_ID_START_VALUE = 1;
Santos Cordon63a84242013-07-23 13:32:52 -070085
Santos Cordon998f42b2013-08-02 16:13:12 -070086 private final CallStateMonitor mCallStateMonitor;
87 private final CallManager mCallManager;
Santos Cordon69a69192013-08-22 14:25:42 -070088 private final CallGatewayManager mCallGatewayManager;
Santos Cordon998f42b2013-08-02 16:13:12 -070089 private final HashMap<Connection, Call> mCallMap = Maps.newHashMap();
Santos Cordon4ad64cd2013-08-15 00:36:14 -070090 private final HashMap<Connection, Call> mConfCallMap = Maps.newHashMap();
Santos Cordon998f42b2013-08-02 16:13:12 -070091 private final AtomicInteger mNextCallId = new AtomicInteger(CALL_ID_START_VALUE);
Christine Chendaf7bf62013-08-05 19:12:31 -070092 private final ArrayList<Listener> mListeners = new ArrayList<Listener>();
Santos Cordona5d5db82013-09-15 13:00:34 -070093 private Connection mCdmaIncomingConnection;
Santos Cordonad1ed6d2013-09-16 03:04:23 -070094 private Connection mCdmaOutgoingConnection;
Santos Cordon63a84242013-07-23 13:32:52 -070095
Christine Chenee09a492013-08-06 16:02:29 -070096 public CallModeler(CallStateMonitor callStateMonitor, CallManager callManager,
Santos Cordon69a69192013-08-22 14:25:42 -070097 CallGatewayManager callGatewayManager) {
Santos Cordon63a84242013-07-23 13:32:52 -070098 mCallStateMonitor = callStateMonitor;
Santos Cordona3d05142013-07-29 11:25:17 -070099 mCallManager = callManager;
Santos Cordon69a69192013-08-22 14:25:42 -0700100 mCallGatewayManager = callGatewayManager;
Santos Cordon63a84242013-07-23 13:32:52 -0700101
102 mCallStateMonitor.addListener(this);
103 }
104
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700105 @Override
Santos Cordon63a84242013-07-23 13:32:52 -0700106 public void handleMessage(Message msg) {
107 switch(msg.what) {
108 case CallStateMonitor.PHONE_NEW_RINGING_CONNECTION:
Christine Chenfb0cc2b2013-09-16 14:21:29 -0700109 // We let the CallNotifier handle the new ringing connection first. When the custom
110 // ringtone and send_to_voicemail settings are retrieved, CallNotifier will directly
111 // call CallModeler's onNewRingingConnection.
Santos Cordon63a84242013-07-23 13:32:52 -0700112 break;
113 case CallStateMonitor.PHONE_DISCONNECT:
Santos Cordona5d5db82013-09-15 13:00:34 -0700114 onDisconnect((Connection) ((AsyncResult) msg.obj).result);
Santos Cordon995c8162013-07-29 09:22:22 -0700115 break;
Santos Cordon54fdb592013-09-19 05:16:18 -0700116 case CallStateMonitor.PHONE_UNKNOWN_CONNECTION_APPEARED:
117 // fall through
Santos Cordon995c8162013-07-29 09:22:22 -0700118 case CallStateMonitor.PHONE_STATE_CHANGED:
119 onPhoneStateChanged((AsyncResult) msg.obj);
120 break;
Chiao Cheng3f015c92013-09-06 15:56:27 -0700121 case CallStateMonitor.PHONE_ON_DIAL_CHARS:
122 onPostDialChars((AsyncResult) msg.obj, (char) msg.arg1);
123 break;
Santos Cordon63a84242013-07-23 13:32:52 -0700124 default:
125 break;
126 }
127 }
128
Christine Chendaf7bf62013-08-05 19:12:31 -0700129 public void addListener(Listener listener) {
Santos Cordon63a84242013-07-23 13:32:52 -0700130 Preconditions.checkNotNull(listener);
Christine Chendaf7bf62013-08-05 19:12:31 -0700131 Preconditions.checkNotNull(mListeners);
Christine Chen4748abd2013-08-07 15:44:15 -0700132 if (!mListeners.contains(listener)) {
133 mListeners.add(listener);
134 }
Santos Cordon998f42b2013-08-02 16:13:12 -0700135 }
136
137 public List<Call> getFullList() {
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700138 final List<Call> calls =
139 Lists.newArrayListWithCapacity(mCallMap.size() + mConfCallMap.size());
140 calls.addAll(mCallMap.values());
141 calls.addAll(mConfCallMap.values());
142 return calls;
Santos Cordon63a84242013-07-23 13:32:52 -0700143 }
144
Santos Cordon249efd02013-08-05 03:33:56 -0700145 public CallResult getCallWithId(int callId) {
146 // max 8 connections, so this should be fast even through we are traversing the entire map.
147 for (Entry<Connection, Call> entry : mCallMap.entrySet()) {
148 if (entry.getValue().getCallId() == callId) {
149 return new CallResult(entry.getValue(), entry.getKey());
150 }
151 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700152
153 for (Entry<Connection, Call> entry : mConfCallMap.entrySet()) {
154 if (entry.getValue().getCallId() == callId) {
Christine Chen69050202013-09-14 15:36:35 -0700155 return new CallResult(entry.getValue(), entry.getKey());
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700156 }
157 }
Santos Cordon249efd02013-08-05 03:33:56 -0700158 return null;
159 }
160
Santos Cordonaf763a12013-08-19 20:04:58 -0700161 public boolean hasLiveCall() {
162 return hasLiveCallInternal(mCallMap) ||
163 hasLiveCallInternal(mConfCallMap);
164 }
165
Santos Cordona5d5db82013-09-15 13:00:34 -0700166 public void onCdmaCallWaiting(CdmaCallWaitingNotification callWaitingInfo) {
167 // We dont get the traditional onIncomingCall notification for cdma call waiting,
168 // but the Connection does actually exist. We need to find it in the set of ringing calls
169 // and pass it through our normal incoming logic.
170 final com.android.internal.telephony.Call teleCall =
171 mCallManager.getFirstActiveRingingCall();
172
173 if (teleCall.getState() == com.android.internal.telephony.Call.State.WAITING) {
174 Connection connection = teleCall.getLatestConnection();
175
176 if (connection != null) {
177 String number = connection.getAddress();
178 if (number != null && number.equals(callWaitingInfo.number)) {
179 Call call = onNewRingingConnection(connection);
180 mCdmaIncomingConnection = connection;
181 return;
182 }
183 }
184 }
185
186 Log.e(TAG, "CDMA Call waiting notification without a matching connection.");
187 }
188
189 public void onCdmaCallWaitingReject() {
190 // Cdma call was rejected...
191 if (mCdmaIncomingConnection != null) {
192 onDisconnect(mCdmaIncomingConnection);
193 mCdmaIncomingConnection = null;
194 } else {
195 Log.e(TAG, "CDMA Call waiting rejection without an incoming call.");
196 }
197 }
198
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700199 /**
200 * CDMA Calls have no sense of "dialing" state. For outgoing calls 3way calls we want to
201 * mimick this state so that the the UI can notify the user that there is a "dialing"
202 * call.
203 */
204 public void setCdmaOutgoing3WayCall(Connection connection) {
205 boolean wasSet = mCdmaOutgoingConnection != null;
206
207 mCdmaOutgoingConnection = connection;
208
209 // If we reset the connection, that mean we can now tell the user that the call is actually
210 // part of the conference call and move it out of the dialing state. To do this, issue a
211 // new update completely.
212 if (wasSet && mCdmaOutgoingConnection == null) {
213 onPhoneStateChanged(null);
214 }
215 }
216
Santos Cordonaf763a12013-08-19 20:04:58 -0700217 private boolean hasLiveCallInternal(HashMap<Connection, Call> map) {
218 for (Call call : map.values()) {
219 final int state = call.getState();
220 if (state == Call.State.ACTIVE ||
221 state == Call.State.CALL_WAITING ||
222 state == Call.State.CONFERENCED ||
223 state == Call.State.DIALING ||
Santos Cordonce02f3a2013-09-19 01:58:42 -0700224 state == Call.State.REDIALING ||
Santos Cordonaf763a12013-08-19 20:04:58 -0700225 state == Call.State.INCOMING ||
Christine Chen3e0f0412013-09-18 20:33:49 -0700226 state == Call.State.ONHOLD ||
227 state == Call.State.DISCONNECTING) {
Santos Cordonaf763a12013-08-19 20:04:58 -0700228 return true;
229 }
230 }
231 return false;
232 }
233
Santos Cordon2b73bd62013-08-27 14:53:43 -0700234 public boolean hasOutstandingActiveOrDialingCall() {
235 return hasOutstandingActiveOrDialingCallInternal(mCallMap) ||
236 hasOutstandingActiveOrDialingCallInternal(mConfCallMap);
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700237 }
238
Santos Cordon2b73bd62013-08-27 14:53:43 -0700239 private static boolean hasOutstandingActiveOrDialingCallInternal(
240 HashMap<Connection, Call> map) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700241 for (Call call : map.values()) {
242 final int state = call.getState();
Santos Cordonce02f3a2013-09-19 01:58:42 -0700243 if (state == Call.State.ACTIVE || Call.State.isDialing(state)) {
Santos Cordon2eaff902013-08-05 04:37:55 -0700244 return true;
245 }
246 }
247
248 return false;
249 }
250
Chiao Cheng3f015c92013-09-06 15:56:27 -0700251
252 /**
253 * Handles the POST_ON_DIAL_CHARS message from the Phone (see our call to
254 * mPhone.setOnPostDialCharacter() above.)
255 *
256 * TODO: NEED TO TEST THIS SEQUENCE now that we no longer handle "dialable" key events here in
257 * the InCallScreen: we do directly to the Dialer UI instead. Similarly, we may now need to go
258 * directly to the Dialer to handle POST_ON_DIAL_CHARS too.
259 */
260 private void onPostDialChars(AsyncResult r, char ch) {
261 final Connection c = (Connection) r.result;
262
263 if (c != null) {
264 final Connection.PostDialState state = (Connection.PostDialState) r.userObj;
265
266 switch (state) {
Chiao Cheng3f015c92013-09-06 15:56:27 -0700267 case WAIT:
268 final Call call = getCallFromMap(mCallMap, c, false);
269 if (call == null) {
270 Log.i(TAG, "Call no longer exists. Skipping onPostDialWait().");
Santos Cordoncfe40732014-03-11 14:51:08 -0700271 } else {
Chiao Cheng3f015c92013-09-06 15:56:27 -0700272 for (Listener mListener : mListeners) {
Yorke Leede41f672013-09-19 13:46:55 -0700273 mListener.onPostDialAction(state, call.getCallId(),
274 c.getRemainingPostDialString(), ch);
Chiao Cheng3f015c92013-09-06 15:56:27 -0700275 }
276 }
277 break;
Chiao Cheng3f015c92013-09-06 15:56:27 -0700278 default:
Yorke Leede41f672013-09-19 13:46:55 -0700279 // This is primarily to cause the DTMFTonePlayer to play local tones.
280 // Other listeners simply perform no-ops.
Santos Cordoncfe40732014-03-11 14:51:08 -0700281 for (Listener mListener : mListeners) {
282 mListener.onPostDialAction(state, 0, "", ch);
Yorke Leede41f672013-09-19 13:46:55 -0700283 }
Chiao Cheng3f015c92013-09-06 15:56:27 -0700284 break;
285 }
286 }
287 }
288
Christine Chenfb0cc2b2013-09-16 14:21:29 -0700289 /* package */ Call onNewRingingConnection(Connection conn) {
Santos Cordon2b73bd62013-08-27 14:53:43 -0700290 Log.i(TAG, "onNewRingingConnection");
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700291 final Call call = getCallFromMap(mCallMap, conn, true);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700292
Christine Chenfb0cc2b2013-09-16 14:21:29 -0700293 if (call != null) {
294 updateCallFromConnection(call, conn, false);
Santos Cordon63a84242013-07-23 13:32:52 -0700295
Santos Cordoncfe40732014-03-11 14:51:08 -0700296 for (int i = 0; i < mListeners.size(); ++i) {
297 mListeners.get(i).onIncoming(call);
Christine Chenee09a492013-08-06 16:02:29 -0700298 }
Santos Cordon63a84242013-07-23 13:32:52 -0700299 }
Santos Cordona5d5db82013-09-15 13:00:34 -0700300
Santos Cordon24a92b32013-09-26 16:48:14 -0700301 PhoneGlobals.getInstance().updateWakeState();
Santos Cordona5d5db82013-09-15 13:00:34 -0700302 return call;
Santos Cordon63a84242013-07-23 13:32:52 -0700303 }
304
Santos Cordona5d5db82013-09-15 13:00:34 -0700305 private void onDisconnect(Connection conn) {
Santos Cordon2b73bd62013-08-27 14:53:43 -0700306 Log.i(TAG, "onDisconnect");
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700307 final Call call = getCallFromMap(mCallMap, conn, false);
Santos Cordon63a84242013-07-23 13:32:52 -0700308
Santos Cordon995c8162013-07-29 09:22:22 -0700309 if (call != null) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700310 final boolean wasConferenced = call.getState() == State.CONFERENCED;
311
312 updateCallFromConnection(call, conn, false);
Santos Cordon63a84242013-07-23 13:32:52 -0700313
Santos Cordoncfe40732014-03-11 14:51:08 -0700314 for (int i = 0; i < mListeners.size(); ++i) {
315 mListeners.get(i).onDisconnect(call);
Santos Cordon63a84242013-07-23 13:32:52 -0700316 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700317
318 // If it was a conferenced call, we need to run the entire update
319 // to make the proper changes to parent conference calls.
320 if (wasConferenced) {
321 onPhoneStateChanged(null);
322 }
323
324 mCallMap.remove(conn);
Santos Cordon63a84242013-07-23 13:32:52 -0700325 }
Santos Cordon24a92b32013-09-26 16:48:14 -0700326
Christine Chen6af50e62013-09-26 15:05:57 -0700327 mCallManager.clearDisconnected();
Santos Cordon24a92b32013-09-26 16:48:14 -0700328 PhoneGlobals.getInstance().updateWakeState();
Santos Cordon63a84242013-07-23 13:32:52 -0700329 }
330
Santos Cordona3d05142013-07-29 11:25:17 -0700331 /**
332 * Called when the phone state changes.
Santos Cordona3d05142013-07-29 11:25:17 -0700333 */
Santos Cordon995c8162013-07-29 09:22:22 -0700334 private void onPhoneStateChanged(AsyncResult r) {
Santos Cordon2b73bd62013-08-27 14:53:43 -0700335 Log.i(TAG, "onPhoneStateChanged: ");
Santos Cordon998f42b2013-08-02 16:13:12 -0700336 final List<Call> updatedCalls = Lists.newArrayList();
337 doUpdate(false, updatedCalls);
338
Santos Cordoncfe40732014-03-11 14:51:08 -0700339 if (updatedCalls.size() > 0) {
Santos Cordon71d5c6e2013-09-05 21:34:33 -0700340 for (int i = 0; i < mListeners.size(); ++i) {
341 mListeners.get(i).onUpdate(updatedCalls);
342 }
Santos Cordon998f42b2013-08-02 16:13:12 -0700343 }
Santos Cordon24a92b32013-09-26 16:48:14 -0700344
345 PhoneGlobals.getInstance().updateWakeState();
Santos Cordon998f42b2013-08-02 16:13:12 -0700346 }
347
Santos Cordon998f42b2013-08-02 16:13:12 -0700348 /**
349 * Go through the Calls from CallManager and return the list of calls that were updated.
Santos Cordon25008582013-11-12 13:39:40 -0800350 * Method also finds any orphaned Calls (Connection objects no longer returned by telephony as
351 * either ringing, foreground, or background). For each orphaned call, it sets the call state
352 * to IDLE and adds it to the list of calls to update.
353 *
354 * @param fullUpdate Add all calls to out parameter including those that have no updates.
355 * @param out List to populate with Calls that have been updated.
Santos Cordon998f42b2013-08-02 16:13:12 -0700356 */
357 private void doUpdate(boolean fullUpdate, List<Call> out) {
Santos Cordona3d05142013-07-29 11:25:17 -0700358 final List<com.android.internal.telephony.Call> telephonyCalls = Lists.newArrayList();
359 telephonyCalls.addAll(mCallManager.getRingingCalls());
360 telephonyCalls.addAll(mCallManager.getForegroundCalls());
361 telephonyCalls.addAll(mCallManager.getBackgroundCalls());
362
Santos Cordon25008582013-11-12 13:39:40 -0800363 // orphanedConnections starts out including all connections we know about.
364 // As we iterate through the connections we get from the telephony layer we
365 // prune this Set down to only the connections we have but telephony no longer
366 // recognizes.
367 final Set<Connection> orphanedConnections = Sets.newHashSet();
368 orphanedConnections.addAll(mCallMap.keySet());
369 orphanedConnections.addAll(mConfCallMap.keySet());
370
Santos Cordona3d05142013-07-29 11:25:17 -0700371 // Cycle through all the Connections on all the Calls. Update our Call objects
372 // to reflect any new state and send the updated Call objects to the handler service.
373 for (com.android.internal.telephony.Call telephonyCall : telephonyCalls) {
Santos Cordona3d05142013-07-29 11:25:17 -0700374
375 for (Connection connection : telephonyCall.getConnections()) {
Christine Chen3e0f0412013-09-18 20:33:49 -0700376 if (DBG) Log.d(TAG, "connection: " + connection + connection.getState());
Santos Cordona5d5db82013-09-15 13:00:34 -0700377
Santos Cordon25008582013-11-12 13:39:40 -0800378 if (orphanedConnections.contains(connection)) {
379 orphanedConnections.remove(connection);
380 }
381
Santos Cordon12a03aa2013-09-12 23:34:05 -0700382 // We only send updates for live calls which are not incoming (ringing).
383 // Disconnected and incoming calls are handled by onDisconnect and
384 // onNewRingingConnection.
Christine Chendf19e452013-09-25 17:21:23 -0700385 final boolean shouldUpdate =
Christine Chen3e0f0412013-09-18 20:33:49 -0700386 connection.getState() !=
387 com.android.internal.telephony.Call.State.DISCONNECTED &&
388 connection.getState() !=
389 com.android.internal.telephony.Call.State.IDLE &&
Santos Cordon71d5c6e2013-09-05 21:34:33 -0700390 !connection.getState().isRinging();
391
Christine Chendf19e452013-09-25 17:21:23 -0700392 final boolean isDisconnecting = connection.getState() ==
393 com.android.internal.telephony.Call.State.DISCONNECTING;
394
395 // For disconnecting calls, we still need to send the update to the UI but we do
396 // not create a new call if the call did not exist.
397 final boolean shouldCreate = shouldUpdate && !isDisconnecting;
398
Santos Cordon71d5c6e2013-09-05 21:34:33 -0700399 // New connections return a Call with INVALID state, which does not translate to
Santos Cordone38b1ff2013-08-07 12:12:16 -0700400 // a state in the internal.telephony.Call object. This ensures that staleness
401 // check below fails and we always add the item to the update list if it is new.
Christine Chendf19e452013-09-25 17:21:23 -0700402 final Call call = getCallFromMap(mCallMap, connection, shouldCreate /* create */);
Santos Cordon71d5c6e2013-09-05 21:34:33 -0700403
Santos Cordon12a03aa2013-09-12 23:34:05 -0700404 if (call == null || !shouldUpdate) {
Santos Cordona5d5db82013-09-15 13:00:34 -0700405 if (DBG) Log.d(TAG, "update skipped");
Santos Cordon71d5c6e2013-09-05 21:34:33 -0700406 continue;
407 }
Santos Cordona3d05142013-07-29 11:25:17 -0700408
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700409 boolean changed = updateCallFromConnection(call, connection, false);
Santos Cordon2b73bd62013-08-27 14:53:43 -0700410
Santos Cordone38b1ff2013-08-07 12:12:16 -0700411 if (fullUpdate || changed) {
Santos Cordon998f42b2013-08-02 16:13:12 -0700412 out.add(call);
Santos Cordona3d05142013-07-29 11:25:17 -0700413 }
414 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700415
416 // We do a second loop to address conference call scenarios. We do this as a separate
417 // loop to ensure all child calls are up to date before we start updating the parent
418 // conference calls.
419 for (Connection connection : telephonyCall.getConnections()) {
420 updateForConferenceCalls(connection, out);
421 }
Santos Cordon25008582013-11-12 13:39:40 -0800422 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700423
Santos Cordon25008582013-11-12 13:39:40 -0800424 // Iterate through orphaned connections, set them to idle, and remove
425 // them from our internal structures.
426 for (Connection orphanedConnection : orphanedConnections) {
427 if (mCallMap.containsKey(orphanedConnection)) {
428 final Call call = mCallMap.get(orphanedConnection);
429 call.setState(Call.State.IDLE);
430 out.add(call);
431
432 mCallMap.remove(orphanedConnection);
433 }
434
435 if (mConfCallMap.containsKey(orphanedConnection)) {
436 final Call call = mCallMap.get(orphanedConnection);
437 call.setState(Call.State.IDLE);
438 out.add(call);
439
440 mConfCallMap.remove(orphanedConnection);
441 }
Santos Cordona3d05142013-07-29 11:25:17 -0700442 }
Santos Cordona3d05142013-07-29 11:25:17 -0700443 }
444
Santos Cordone38b1ff2013-08-07 12:12:16 -0700445 /**
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700446 * Checks to see if the connection is the first connection in a conference call.
447 * If it is a conference call, we will create a new Conference Call object or
448 * update the existing conference call object for that connection.
449 * If it is not a conference call but a previous associated conference call still exists,
450 * we mark it as idle and remove it from the map.
451 * In both cases above, we add the Calls to be updated to the UI.
452 * @param connection The connection object to check.
453 * @param updatedCalls List of 'updated' calls that will be sent to the UI.
454 */
455 private boolean updateForConferenceCalls(Connection connection, List<Call> updatedCalls) {
456 // We consider this connection a conference connection if the call it
Yorke Leecd3f9692013-09-14 13:51:27 -0700457 // belongs to is a multiparty call AND it is the first live connection.
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700458 final boolean isConferenceCallConnection = isPartOfLiveConferenceCall(connection) &&
Yorke Leecd3f9692013-09-14 13:51:27 -0700459 getEarliestLiveConnection(connection.getCall()) == connection;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700460
461 boolean changed = false;
462
463 // If this connection is the main connection for the conference call, then create or update
464 // a Call object for that conference call.
465 if (isConferenceCallConnection) {
466 final Call confCall = getCallFromMap(mConfCallMap, connection, true);
467 changed = updateCallFromConnection(confCall, connection, true);
468
469 if (changed) {
470 updatedCalls.add(confCall);
471 }
472
473 if (DBG) Log.d(TAG, "Updating a conference call: " + confCall);
474
475 // It is possible that through a conference call split, there may be lingering conference
476 // calls where this connection was the main connection. We clean those up here.
477 } else {
478 final Call oldConfCall = getCallFromMap(mConfCallMap, connection, false);
479
480 // We found a conference call for this connection, which is no longer a conference call.
481 // Kill it!
482 if (oldConfCall != null) {
483 if (DBG) Log.d(TAG, "Cleaning up an old conference call: " + oldConfCall);
484 mConfCallMap.remove(connection);
485 oldConfCall.setState(State.IDLE);
486 changed = true;
487
488 // add to the list of calls to update
489 updatedCalls.add(oldConfCall);
490 }
491 }
492
493 return changed;
494 }
495
Yorke Leecd3f9692013-09-14 13:51:27 -0700496 private Connection getEarliestLiveConnection(com.android.internal.telephony.Call call) {
497 final List<Connection> connections = call.getConnections();
498 final int size = connections.size();
499 Connection earliestConn = null;
500 long earliestTime = Long.MAX_VALUE;
501 for (int i = 0; i < size; i++) {
502 final Connection connection = connections.get(i);
503 if (!connection.isAlive()) continue;
504 final long time = connection.getCreateTime();
505 if (time < earliestTime) {
506 earliestTime = time;
507 earliestConn = connection;
508 }
509 }
510 return earliestConn;
511 }
512
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700513 /**
Santos Cordon69a69192013-08-22 14:25:42 -0700514 * Sets the new call state onto the call and performs some additional logic
515 * associated with setting the state.
516 */
517 private void setNewState(Call call, int newState, Connection connection) {
518 Preconditions.checkState(call.getState() != newState);
519
520 // When starting an outgoing call, we need to grab gateway information
521 // for the call, if available, and set it.
522 final RawGatewayInfo info = mCallGatewayManager.getGatewayInfo(connection);
523
Santos Cordonce02f3a2013-09-19 01:58:42 -0700524 if (Call.State.isDialing(newState)) {
Santos Cordon69a69192013-08-22 14:25:42 -0700525 if (!info.isEmpty()) {
526 call.setGatewayNumber(info.getFormattedGatewayNumber());
527 call.setGatewayPackage(info.packageName);
528 }
529 } else if (!Call.State.isConnected(newState)) {
530 mCallGatewayManager.clearGatewayData(connection);
531 }
532
533 call.setState(newState);
534 }
535
536 /**
Santos Cordone38b1ff2013-08-07 12:12:16 -0700537 * Updates the Call properties to match the state of the connection object
538 * that it represents.
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700539 * @param call The call object to update.
540 * @param connection The connection object from which to update call.
541 * @param isForConference There are slight differences in how we populate data for conference
542 * calls. This boolean tells us which method to use.
Santos Cordone38b1ff2013-08-07 12:12:16 -0700543 */
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700544 private boolean updateCallFromConnection(Call call, Connection connection,
545 boolean isForConference) {
Santos Cordone38b1ff2013-08-07 12:12:16 -0700546 boolean changed = false;
547
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700548 final int newState = translateStateFromTelephony(connection, isForConference);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700549
550 if (call.getState() != newState) {
Santos Cordon69a69192013-08-22 14:25:42 -0700551 setNewState(call, newState, connection);
Santos Cordone38b1ff2013-08-07 12:12:16 -0700552 changed = true;
553 }
554
Sailesh Nepald1e68152013-12-12 19:08:02 -0800555 final boolean isWifiCall = connection.getCall().getPhone().getPhoneType() ==
556 PhoneConstants.PHONE_TYPE_THIRD_PARTY;
557 if (call.isWifiCall() != isWifiCall) {
558 call.setIsWifiCall(isWifiCall);
559 changed = true;
560 }
561
Anders Kristensen0b35f042014-02-27 14:31:07 -0800562 final int newDisconnectCause = connection.getDisconnectCause();
Santos Cordone38b1ff2013-08-07 12:12:16 -0700563 if (call.getDisconnectCause() != newDisconnectCause) {
564 call.setDisconnectCause(newDisconnectCause);
565 changed = true;
566 }
567
Santos Cordonbbe8ecf2013-08-13 15:26:18 -0700568 final long oldConnectTime = call.getConnectTime();
569 if (oldConnectTime != connection.getConnectTime()) {
570 call.setConnectTime(connection.getConnectTime());
571 changed = true;
572 }
573
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700574 if (!isForConference) {
Santos Cordon69a69192013-08-22 14:25:42 -0700575 // Number
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700576 final String oldNumber = call.getNumber();
Santos Cordon69a69192013-08-22 14:25:42 -0700577 String newNumber = connection.getAddress();
578 RawGatewayInfo info = mCallGatewayManager.getGatewayInfo(connection);
579 if (!info.isEmpty()) {
580 newNumber = info.trueNumber;
581 }
582 if (TextUtils.isEmpty(oldNumber) || !oldNumber.equals(newNumber)) {
583 call.setNumber(newNumber);
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700584 changed = true;
585 }
586
Santos Cordon69a69192013-08-22 14:25:42 -0700587 // Number presentation
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700588 final int newNumberPresentation = connection.getNumberPresentation();
589 if (call.getNumberPresentation() != newNumberPresentation) {
590 call.setNumberPresentation(newNumberPresentation);
591 changed = true;
592 }
593
Santos Cordon69a69192013-08-22 14:25:42 -0700594 // Name
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700595 final String oldCnapName = call.getCnapName();
596 if (TextUtils.isEmpty(oldCnapName) || !oldCnapName.equals(connection.getCnapName())) {
597 call.setCnapName(connection.getCnapName());
598 changed = true;
599 }
Santos Cordon69a69192013-08-22 14:25:42 -0700600
601 // Name Presentation
602 final int newCnapNamePresentation = connection.getCnapNamePresentation();
603 if (call.getCnapNamePresentation() != newCnapNamePresentation) {
604 call.setCnapNamePresentation(newCnapNamePresentation);
605 changed = true;
606 }
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700607 } else {
608
609 // update the list of children by:
610 // 1) Saving the old set
611 // 2) Removing all children
612 // 3) Adding the correct children into the Call
613 // 4) Comparing the new children set with the old children set
614 ImmutableSortedSet<Integer> oldSet = call.getChildCallIds();
615 call.removeAllChildren();
616
617 if (connection.getCall() != null) {
618 for (Connection childConn : connection.getCall().getConnections()) {
619 final Call childCall = getCallFromMap(mCallMap, childConn, false);
620 if (childCall != null && childConn.isAlive()) {
621 call.addChildId(childCall.getCallId());
622 }
623 }
624 }
Christine Chen45277022013-09-05 10:55:37 -0700625 changed |= !oldSet.equals(call.getChildCallIds());
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700626 }
627
Santos Cordoneead6ec2013-08-07 22:16:33 -0700628 /**
629 * !!! Uses values from connection and call collected above so this part must be last !!!
630 */
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700631 final int newCapabilities = getCapabilitiesFor(connection, call, isForConference);
Santos Cordon26e7b242013-08-07 21:15:45 -0700632 if (call.getCapabilities() != newCapabilities) {
633 call.setCapabilities(newCapabilities);
634 changed = true;
635 }
636
Santos Cordone38b1ff2013-08-07 12:12:16 -0700637 return changed;
638 }
639
Santos Cordon26e7b242013-08-07 21:15:45 -0700640 /**
641 * Returns a mask of capabilities for the connection such as merge, hold, etc.
642 */
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700643 private int getCapabilitiesFor(Connection connection, Call call, boolean isForConference) {
Santos Cordoneead6ec2013-08-07 22:16:33 -0700644 final boolean callIsActive = (call.getState() == Call.State.ACTIVE);
645 final Phone phone = connection.getCall().getPhone();
646
Santos Cordoneead6ec2013-08-07 22:16:33 -0700647 boolean canAddCall = false;
648 boolean canMergeCall = false;
649 boolean canSwapCall = false;
Yorke Lee814da302013-08-30 16:01:07 -0700650 boolean canRespondViaText = false;
Christine Chenaf2fd0a2013-09-13 16:27:40 -0700651 boolean canMute = false;
652
653 final boolean supportHold = PhoneUtils.okToSupportHold(mCallManager);
Christine Chen94853bc2013-09-17 16:53:33 -0700654 final boolean canHold = (supportHold ? PhoneUtils.okToHoldCall(mCallManager) : false);
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700655 final boolean genericConf = isForConference &&
Sailesh Nepald1e68152013-12-12 19:08:02 -0800656 (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA);
Santos Cordoneead6ec2013-08-07 22:16:33 -0700657
658 // only applies to active calls
659 if (callIsActive) {
Santos Cordoneead6ec2013-08-07 22:16:33 -0700660 canMergeCall = PhoneUtils.okToMergeCalls(mCallManager);
661 canSwapCall = PhoneUtils.okToSwapCalls(mCallManager);
662 }
663
Christine Chenaf2fd0a2013-09-13 16:27:40 -0700664 canAddCall = PhoneUtils.okToAddCall(mCallManager);
665
666 // "Mute": only enabled when the foreground call is ACTIVE.
667 // (It's meaningless while on hold, or while DIALING/ALERTING.)
668 // It's also explicitly disabled during emergency calls or if
669 // emergency callback mode (ECM) is active.
670 boolean isEmergencyCall = false;
671 if (connection != null) {
Yorke Lee36bb2542014-06-05 08:09:52 -0700672 isEmergencyCall = PhoneNumberUtils.isLocalEmergencyNumber(phone.getContext(),
673 connection.getAddress());
Christine Chenaf2fd0a2013-09-13 16:27:40 -0700674 }
675 boolean isECM = PhoneUtils.isPhoneInEcm(phone);
676 if (isEmergencyCall || isECM) { // disable "Mute" item
677 canMute = false;
678 } else {
679 canMute = callIsActive;
680 }
681
Ihab Awada34070b2014-06-10 14:01:33 -0700682 canRespondViaText = false /* RejectWithTextMessageManager.allowRespondViaSmsForCall(call,
683 connection) */;
Yorke Lee814da302013-08-30 16:01:07 -0700684
Santos Cordoneead6ec2013-08-07 22:16:33 -0700685 // special rules section!
686 // CDMA always has Add
687 if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
688 canAddCall = true;
Santos Cordoneead6ec2013-08-07 22:16:33 -0700689 }
690
Santos Cordon26e7b242013-08-07 21:15:45 -0700691 int retval = 0x0;
Santos Cordoneead6ec2013-08-07 22:16:33 -0700692 if (canHold) {
Santos Cordon26e7b242013-08-07 21:15:45 -0700693 retval |= Capabilities.HOLD;
694 }
Christine Chenaf2fd0a2013-09-13 16:27:40 -0700695 if (supportHold) {
696 retval |= Capabilities.SUPPORT_HOLD;
697 }
Santos Cordoneead6ec2013-08-07 22:16:33 -0700698 if (canAddCall) {
699 retval |= Capabilities.ADD_CALL;
700 }
701 if (canMergeCall) {
702 retval |= Capabilities.MERGE_CALLS;
703 }
704 if (canSwapCall) {
705 retval |= Capabilities.SWAP_CALLS;
706 }
Yorke Lee814da302013-08-30 16:01:07 -0700707 if (canRespondViaText) {
708 retval |= Capabilities.RESPOND_VIA_TEXT;
709 }
Christine Chenaf2fd0a2013-09-13 16:27:40 -0700710 if (canMute) {
711 retval |= Capabilities.MUTE;
712 }
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700713 if (genericConf) {
714 retval |= Capabilities.GENERIC_CONFERENCE;
715 }
Yorke Lee814da302013-08-30 16:01:07 -0700716
Santos Cordon26e7b242013-08-07 21:15:45 -0700717 return retval;
718 }
719
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700720 /**
721 * Returns true if the Connection is part of a multiparty call.
722 * We do this by checking the isMultiparty() method of the telephony.Call object and also
723 * checking to see if more than one of it's children is alive.
724 */
725 private boolean isPartOfLiveConferenceCall(Connection connection) {
726 if (connection.getCall() != null && connection.getCall().isMultiparty()) {
727 int count = 0;
728 for (Connection currConn : connection.getCall().getConnections()) {
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700729
730 // Only count connections which are alive and never cound the special
731 // "dialing" 3way call for CDMA calls.
732 if (currConn.isAlive() && currConn != mCdmaOutgoingConnection) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700733 count++;
734 if (count >= 2) {
735 return true;
736 }
737 }
738 }
739 }
740 return false;
741 }
742
743 private int translateStateFromTelephony(Connection connection, boolean isForConference) {
744
Santos Cordonce02f3a2013-09-19 01:58:42 -0700745 com.android.internal.telephony.Call.State connState = connection.getState();
746
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700747 // For the "fake" outgoing CDMA call, we need to always treat it as an outgoing call.
748 if (mCdmaOutgoingConnection == connection) {
Santos Cordonce02f3a2013-09-19 01:58:42 -0700749 connState = com.android.internal.telephony.Call.State.DIALING;
Santos Cordonad1ed6d2013-09-16 03:04:23 -0700750 }
751
Santos Cordona3d05142013-07-29 11:25:17 -0700752 int retval = State.IDLE;
Santos Cordonce02f3a2013-09-19 01:58:42 -0700753 switch (connState) {
Santos Cordona3d05142013-07-29 11:25:17 -0700754 case ACTIVE:
755 retval = State.ACTIVE;
756 break;
757 case INCOMING:
758 retval = State.INCOMING;
759 break;
760 case DIALING:
761 case ALERTING:
Santos Cordonce02f3a2013-09-19 01:58:42 -0700762 if (PhoneGlobals.getInstance().notifier.getIsCdmaRedialCall()) {
763 retval = State.REDIALING;
764 } else {
765 retval = State.DIALING;
766 }
Santos Cordona3d05142013-07-29 11:25:17 -0700767 break;
768 case WAITING:
769 retval = State.CALL_WAITING;
770 break;
771 case HOLDING:
772 retval = State.ONHOLD;
773 break;
Santos Cordone38b1ff2013-08-07 12:12:16 -0700774 case DISCONNECTING:
Christine Chen3e0f0412013-09-18 20:33:49 -0700775 retval = State.DISCONNECTING;
776 break;
777 case DISCONNECTED:
Santos Cordone38b1ff2013-08-07 12:12:16 -0700778 retval = State.DISCONNECTED;
Santos Cordona3d05142013-07-29 11:25:17 -0700779 default:
780 }
781
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700782 // If we are dealing with a potential child call (not the parent conference call),
783 // the check to see if we have to set the state to CONFERENCED.
784 if (!isForConference) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700785 // if the connection is part of a multiparty call, and it is live,
786 // annotate it with CONFERENCED state instead.
787 if (isPartOfLiveConferenceCall(connection) && connection.isAlive()) {
788 return State.CONFERENCED;
789 }
790 }
791
Santos Cordona3d05142013-07-29 11:25:17 -0700792 return retval;
Santos Cordon995c8162013-07-29 09:22:22 -0700793 }
794
Santos Cordon63a84242013-07-23 13:32:52 -0700795 /**
Santos Cordone38b1ff2013-08-07 12:12:16 -0700796 * Gets an existing callId for a connection, or creates one if none exists.
797 * This function does NOT set any of the Connection data onto the Call class.
798 * A separate call to updateCallFromConnection must be made for that purpose.
Santos Cordon63a84242013-07-23 13:32:52 -0700799 */
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700800 private Call getCallFromMap(HashMap<Connection, Call> map, Connection conn,
801 boolean createIfMissing) {
Santos Cordon995c8162013-07-29 09:22:22 -0700802 Call call = null;
Santos Cordon63a84242013-07-23 13:32:52 -0700803
804 // Find the call id or create if missing and requested.
805 if (conn != null) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700806 if (map.containsKey(conn)) {
807 call = map.get(conn);
Santos Cordon63a84242013-07-23 13:32:52 -0700808 } else if (createIfMissing) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700809 call = createNewCall();
810 map.put(conn, call);
Santos Cordon63a84242013-07-23 13:32:52 -0700811 }
812 }
Santos Cordon995c8162013-07-29 09:22:22 -0700813 return call;
Santos Cordon63a84242013-07-23 13:32:52 -0700814 }
815
816 /**
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700817 * Creates a brand new connection for the call.
818 */
819 private Call createNewCall() {
820 int callId;
821 int newNextCallId;
822 do {
823 callId = mNextCallId.get();
824
825 // protect against overflow
826 newNextCallId = (callId == Integer.MAX_VALUE ?
827 CALL_ID_START_VALUE : callId + 1);
828
829 // Keep looping if the change was not atomic OR the value is already taken.
830 // The call to containsValue() is linear, however, most devices support a
831 // maximum of 7 connections so it's not expensive.
832 } while (!mNextCallId.compareAndSet(callId, newNextCallId));
833
834 return new Call(callId);
835 }
836
837 /**
Santos Cordon63a84242013-07-23 13:32:52 -0700838 * Listener interface for changes to Calls.
839 */
840 public interface Listener {
Santos Cordon995c8162013-07-29 09:22:22 -0700841 void onDisconnect(Call call);
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700842 void onIncoming(Call call);
843 void onUpdate(List<Call> calls);
Yorke Leede41f672013-09-19 13:46:55 -0700844 void onPostDialAction(Connection.PostDialState state, int callId, String remainingChars,
845 char c);
Santos Cordon63a84242013-07-23 13:32:52 -0700846 }
Santos Cordon249efd02013-08-05 03:33:56 -0700847
848 /**
849 * Result class for accessing a call by connection.
850 */
851 public static class CallResult {
852 public Call mCall;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700853 public Call mActionableCall;
Santos Cordon249efd02013-08-05 03:33:56 -0700854 public Connection mConnection;
855
856 private CallResult(Call call, Connection connection) {
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700857 this(call, call, connection);
858 }
859
860 private CallResult(Call call, Call actionableCall, Connection connection) {
Santos Cordon249efd02013-08-05 03:33:56 -0700861 mCall = call;
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700862 mActionableCall = actionableCall;
Santos Cordon249efd02013-08-05 03:33:56 -0700863 mConnection = connection;
864 }
865
866 public Call getCall() {
867 return mCall;
868 }
869
Santos Cordon4ad64cd2013-08-15 00:36:14 -0700870 // The call that should be used for call actions like hanging up.
871 public Call getActionableCall() {
872 return mActionableCall;
873 }
874
Santos Cordon249efd02013-08-05 03:33:56 -0700875 public Connection getConnection() {
876 return mConnection;
877 }
878 }
Santos Cordon63a84242013-07-23 13:32:52 -0700879}