blob: 13005ee556c355e6a7db4cc38c74fc5acee15ee2 [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
19import com.google.android.collect.Lists;
20import com.google.android.collect.Maps;
21import com.google.common.base.Preconditions;
22
23import android.os.AsyncResult;
24import android.os.Handler;
25import android.os.Message;
26import android.util.Log;
27
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 Cordona3d05142013-07-29 11:25:17 -070030import com.android.internal.telephony.PhoneConstants;
Santos Cordon995c8162013-07-29 09:22:22 -070031import com.android.services.telephony.common.Call;
Santos Cordona3d05142013-07-29 11:25:17 -070032import com.android.services.telephony.common.Call.State;
Santos Cordon63a84242013-07-23 13:32:52 -070033
34import java.util.ArrayList;
35import java.util.HashMap;
36import java.util.List;
Santos Cordon249efd02013-08-05 03:33:56 -070037import java.util.Map.Entry;
Santos Cordon63a84242013-07-23 13:32:52 -070038import java.util.concurrent.atomic.AtomicInteger;
39
40/**
41 * Creates a Call model from Call state and data received from the telephony
42 * layer. The telephony layer maintains 3 conceptual objects: Phone, Call,
43 * Connection.
44 *
45 * Phone represents the radio and there is an implementation per technology
46 * type such as GSMPhone, SipPhone, CDMAPhone, etc. Generally, we will only ever
47 * deal with one instance of this object for the lifetime of this class.
48 *
49 * There are 3 Call instances that exist for the lifetime of this class which
50 * are created by CallTracker. The three are RingingCall, ForegroundCall, and
51 * BackgroundCall.
52 *
53 * A Connection most closely resembles what the layperson would consider a call.
54 * A Connection is created when a user dials and it is "owned" by one of the
55 * three Call instances. Which of the three Calls owns the Connection changes
56 * as the Connection goes between ACTIVE, HOLD, RINGING, and other states.
57 *
58 * This class models a new Call class from Connection objects received from
59 * the telephony layer. We use Connection references as identifiers for a call;
60 * new reference = new call.
61 *
62 * TODO(klp): Create a new Call class to replace the simple call Id ints
63 * being used currently.
64 *
65 * The new Call models are parcellable for transfer via the CallHandlerService
66 * API.
67 */
68public class CallModeler extends Handler {
69
70 private static final String TAG = CallModeler.class.getSimpleName();
71
72 private static final int CALL_ID_START_VALUE = 1;
Santos Cordon63a84242013-07-23 13:32:52 -070073
Santos Cordon998f42b2013-08-02 16:13:12 -070074 private final CallStateMonitor mCallStateMonitor;
75 private final CallManager mCallManager;
76 private final HashMap<Connection, Call> mCallMap = Maps.newHashMap();
77 private final AtomicInteger mNextCallId = new AtomicInteger(CALL_ID_START_VALUE);
Christine Chendaf7bf62013-08-05 19:12:31 -070078 private final ArrayList<Listener> mListeners = new ArrayList<Listener>();
Santos Cordon63a84242013-07-23 13:32:52 -070079
Santos Cordona3d05142013-07-29 11:25:17 -070080 public CallModeler(CallStateMonitor callStateMonitor, CallManager callManager) {
Santos Cordon63a84242013-07-23 13:32:52 -070081 mCallStateMonitor = callStateMonitor;
Santos Cordona3d05142013-07-29 11:25:17 -070082 mCallManager = callManager;
Santos Cordon63a84242013-07-23 13:32:52 -070083
84 mCallStateMonitor.addListener(this);
85 }
86
87 @Override
88 public void handleMessage(Message msg) {
89 switch(msg.what) {
90 case CallStateMonitor.PHONE_NEW_RINGING_CONNECTION:
91 onNewRingingConnection((AsyncResult) msg.obj);
92 break;
93 case CallStateMonitor.PHONE_DISCONNECT:
94 onDisconnect((AsyncResult) msg.obj);
Santos Cordon995c8162013-07-29 09:22:22 -070095 break;
96 case CallStateMonitor.PHONE_STATE_CHANGED:
97 onPhoneStateChanged((AsyncResult) msg.obj);
98 break;
Santos Cordon63a84242013-07-23 13:32:52 -070099 default:
100 break;
101 }
102 }
103
Christine Chendaf7bf62013-08-05 19:12:31 -0700104 public void addListener(Listener listener) {
Santos Cordon63a84242013-07-23 13:32:52 -0700105 Preconditions.checkNotNull(listener);
Christine Chendaf7bf62013-08-05 19:12:31 -0700106 Preconditions.checkNotNull(mListeners);
107 mListeners.add(listener);
Santos Cordon998f42b2013-08-02 16:13:12 -0700108 }
109
110 public List<Call> getFullList() {
111 final List<Call> retval = Lists.newArrayList();
112 doUpdate(true, retval);
113 return retval;
Santos Cordon63a84242013-07-23 13:32:52 -0700114 }
115
Santos Cordon249efd02013-08-05 03:33:56 -0700116 public CallResult getCallWithId(int callId) {
117 // max 8 connections, so this should be fast even through we are traversing the entire map.
118 for (Entry<Connection, Call> entry : mCallMap.entrySet()) {
119 if (entry.getValue().getCallId() == callId) {
120 return new CallResult(entry.getValue(), entry.getKey());
121 }
122 }
123 return null;
124 }
125
Santos Cordon2eaff902013-08-05 04:37:55 -0700126 public boolean hasOutstandingActiveCall() {
127 for (Call call : mCallMap.values()) {
128 int state = call.getState();
129 if (Call.State.INVALID != state &&
130 Call.State.IDLE != state &&
131 Call.State.INCOMING != state) {
132 return true;
133 }
134 }
135
136 return false;
137 }
138
Santos Cordon63a84242013-07-23 13:32:52 -0700139 private void onNewRingingConnection(AsyncResult r) {
140 final Connection conn = (Connection) r.result;
Santos Cordon995c8162013-07-29 09:22:22 -0700141 final Call call = getCallFromConnection(conn, true);
Santos Cordona3d05142013-07-29 11:25:17 -0700142 call.setState(Call.State.INCOMING);
Santos Cordon63a84242013-07-23 13:32:52 -0700143
Christine Chendaf7bf62013-08-05 19:12:31 -0700144 for (int i = 0; i < mListeners.size(); ++i) {
145 mListeners.get(i).onUpdate(Lists.newArrayList(call), false);
Santos Cordon63a84242013-07-23 13:32:52 -0700146 }
147 }
148
149 private void onDisconnect(AsyncResult r) {
150 final Connection conn = (Connection) r.result;
Santos Cordon995c8162013-07-29 09:22:22 -0700151 final Call call = getCallFromConnection(conn, false);
Santos Cordon998f42b2013-08-02 16:13:12 -0700152 call.setState(Call.State.IDLE);
Santos Cordon63a84242013-07-23 13:32:52 -0700153
Santos Cordon995c8162013-07-29 09:22:22 -0700154 if (call != null) {
155 mCallMap.remove(conn);
Santos Cordon63a84242013-07-23 13:32:52 -0700156
Christine Chendaf7bf62013-08-05 19:12:31 -0700157 for (int i = 0; i < mListeners.size(); ++i) {
158 mListeners.get(i).onDisconnect(call);
Santos Cordon63a84242013-07-23 13:32:52 -0700159 }
160 }
161 }
162
Santos Cordona3d05142013-07-29 11:25:17 -0700163 /**
164 * Called when the phone state changes.
Santos Cordona3d05142013-07-29 11:25:17 -0700165 */
Santos Cordon995c8162013-07-29 09:22:22 -0700166 private void onPhoneStateChanged(AsyncResult r) {
Santos Cordon998f42b2013-08-02 16:13:12 -0700167 final List<Call> updatedCalls = Lists.newArrayList();
168 doUpdate(false, updatedCalls);
169
Christine Chendaf7bf62013-08-05 19:12:31 -0700170 for (int i = 0; i < mListeners.size(); ++i) {
171 mListeners.get(i).onUpdate(updatedCalls, false);
Santos Cordon998f42b2013-08-02 16:13:12 -0700172 }
173 }
174
175
176 /**
177 * Go through the Calls from CallManager and return the list of calls that were updated.
178 * Or, the full list if requested.
179 */
180 private void doUpdate(boolean fullUpdate, List<Call> out) {
Santos Cordona3d05142013-07-29 11:25:17 -0700181 final List<com.android.internal.telephony.Call> telephonyCalls = Lists.newArrayList();
182 telephonyCalls.addAll(mCallManager.getRingingCalls());
183 telephonyCalls.addAll(mCallManager.getForegroundCalls());
184 telephonyCalls.addAll(mCallManager.getBackgroundCalls());
185
Santos Cordona3d05142013-07-29 11:25:17 -0700186 // Cycle through all the Connections on all the Calls. Update our Call objects
187 // to reflect any new state and send the updated Call objects to the handler service.
188 for (com.android.internal.telephony.Call telephonyCall : telephonyCalls) {
189 final int state = translateStateFromTelephony(telephonyCall.getState());
190
191 for (Connection connection : telephonyCall.getConnections()) {
Santos Cordon998f42b2013-08-02 16:13:12 -0700192 // new connections return a Call with INVALID state, which does not translate to
193 // a state in the Connection object. This ensures that staleness check below
194 // fails and we always add the item to the update list if it is new.
Santos Cordona3d05142013-07-29 11:25:17 -0700195 final Call call = getCallFromConnection(connection, true);
196
Santos Cordon998f42b2013-08-02 16:13:12 -0700197 if (fullUpdate || call.getState() != state) {
Santos Cordona3d05142013-07-29 11:25:17 -0700198 call.setState(state);
Santos Cordon998f42b2013-08-02 16:13:12 -0700199 out.add(call);
Santos Cordona3d05142013-07-29 11:25:17 -0700200 }
201 }
202 }
Santos Cordona3d05142013-07-29 11:25:17 -0700203 }
204
205 private int translateStateFromTelephony(com.android.internal.telephony.Call.State teleState) {
206 int retval = State.IDLE;
207 switch (teleState) {
208 case ACTIVE:
209 retval = State.ACTIVE;
210 break;
211 case INCOMING:
212 retval = State.INCOMING;
213 break;
214 case DIALING:
215 case ALERTING:
216 retval = State.DIALING;
217 break;
218 case WAITING:
219 retval = State.CALL_WAITING;
220 break;
221 case HOLDING:
222 retval = State.ONHOLD;
223 break;
224 default:
225 }
226
227 return retval;
Santos Cordon995c8162013-07-29 09:22:22 -0700228 }
229
Santos Cordon63a84242013-07-23 13:32:52 -0700230 /**
231 * Gets an existing callId for a connection, or creates one
232 * if none exists.
233 */
Santos Cordon995c8162013-07-29 09:22:22 -0700234 private Call getCallFromConnection(Connection conn, boolean createIfMissing) {
235 Call call = null;
Santos Cordon63a84242013-07-23 13:32:52 -0700236
237 // Find the call id or create if missing and requested.
238 if (conn != null) {
Santos Cordon995c8162013-07-29 09:22:22 -0700239 if (mCallMap.containsKey(conn)) {
240 call = mCallMap.get(conn);
Santos Cordon63a84242013-07-23 13:32:52 -0700241 } else if (createIfMissing) {
Santos Cordon995c8162013-07-29 09:22:22 -0700242 int callId;
Santos Cordon63a84242013-07-23 13:32:52 -0700243 int newNextCallId;
244 do {
245 callId = mNextCallId.get();
246
247 // protect against overflow
248 newNextCallId = (callId == Integer.MAX_VALUE ?
249 CALL_ID_START_VALUE : callId + 1);
250
251 // Keep looping if the change was not atomic OR the value is already taken.
252 // The call to containsValue() is linear, however, most devices support a
253 // maximum of 7 connections so it's not expensive.
254 } while (!mNextCallId.compareAndSet(callId, newNextCallId) ||
Santos Cordon995c8162013-07-29 09:22:22 -0700255 mCallMap.containsValue(callId));
Santos Cordon63a84242013-07-23 13:32:52 -0700256
Santos Cordon995c8162013-07-29 09:22:22 -0700257 call = new Call(callId);
Santos Cordon179907f2013-07-31 09:40:55 -0700258 call.setNumber(conn.getAddress());
Santos Cordon995c8162013-07-29 09:22:22 -0700259 mCallMap.put(conn, call);
Santos Cordon63a84242013-07-23 13:32:52 -0700260 }
261 }
Santos Cordon995c8162013-07-29 09:22:22 -0700262 return call;
Santos Cordon63a84242013-07-23 13:32:52 -0700263 }
264
265 /**
266 * Listener interface for changes to Calls.
267 */
268 public interface Listener {
Santos Cordon995c8162013-07-29 09:22:22 -0700269 void onDisconnect(Call call);
Santos Cordon998f42b2013-08-02 16:13:12 -0700270 void onUpdate(List<Call> calls, boolean fullUpdate);
Santos Cordon63a84242013-07-23 13:32:52 -0700271 }
Santos Cordon249efd02013-08-05 03:33:56 -0700272
273 /**
274 * Result class for accessing a call by connection.
275 */
276 public static class CallResult {
277 public Call mCall;
278 public Connection mConnection;
279
280 private CallResult(Call call, Connection connection) {
281 mCall = call;
282 mConnection = connection;
283 }
284
285 public Call getCall() {
286 return mCall;
287 }
288
289 public Connection getConnection() {
290 return mConnection;
291 }
292 }
Santos Cordon63a84242013-07-23 13:32:52 -0700293}