blob: c70da9a8faabca3ea5cd8d888436728f65ea9146 [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;
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 Cordona3d05142013-07-29 11:25:17 -070029import com.android.internal.telephony.PhoneConstants;
Santos Cordon995c8162013-07-29 09:22:22 -070030import com.android.services.telephony.common.Call;
Santos Cordona3d05142013-07-29 11:25:17 -070031import com.android.services.telephony.common.Call.State;
Santos Cordon63a84242013-07-23 13:32:52 -070032
33import java.util.ArrayList;
34import java.util.HashMap;
35import java.util.List;
Santos Cordon249efd02013-08-05 03:33:56 -070036import java.util.Map.Entry;
Santos Cordon63a84242013-07-23 13:32:52 -070037import java.util.concurrent.atomic.AtomicInteger;
38
39/**
40 * Creates a Call model from Call state and data received from the telephony
41 * layer. The telephony layer maintains 3 conceptual objects: Phone, Call,
42 * Connection.
43 *
44 * Phone represents the radio and there is an implementation per technology
45 * type such as GSMPhone, SipPhone, CDMAPhone, etc. Generally, we will only ever
46 * deal with one instance of this object for the lifetime of this class.
47 *
48 * There are 3 Call instances that exist for the lifetime of this class which
49 * are created by CallTracker. The three are RingingCall, ForegroundCall, and
50 * BackgroundCall.
51 *
52 * A Connection most closely resembles what the layperson would consider a call.
53 * A Connection is created when a user dials and it is "owned" by one of the
54 * three Call instances. Which of the three Calls owns the Connection changes
55 * as the Connection goes between ACTIVE, HOLD, RINGING, and other states.
56 *
57 * This class models a new Call class from Connection objects received from
58 * the telephony layer. We use Connection references as identifiers for a call;
59 * new reference = new call.
60 *
61 * TODO(klp): Create a new Call class to replace the simple call Id ints
62 * being used currently.
63 *
64 * The new Call models are parcellable for transfer via the CallHandlerService
65 * API.
66 */
67public class CallModeler extends Handler {
68
69 private static final String TAG = CallModeler.class.getSimpleName();
70
71 private static final int CALL_ID_START_VALUE = 1;
Santos Cordon63a84242013-07-23 13:32:52 -070072
Santos Cordon998f42b2013-08-02 16:13:12 -070073 private final CallStateMonitor mCallStateMonitor;
74 private final CallManager mCallManager;
75 private final HashMap<Connection, Call> mCallMap = Maps.newHashMap();
76 private final AtomicInteger mNextCallId = new AtomicInteger(CALL_ID_START_VALUE);
Christine Chendaf7bf62013-08-05 19:12:31 -070077 private final ArrayList<Listener> mListeners = new ArrayList<Listener>();
Christine Chenee09a492013-08-06 16:02:29 -070078 private RejectWithTextMessageManager mRejectWithTextMessageManager;
Santos Cordon63a84242013-07-23 13:32:52 -070079
Christine Chenee09a492013-08-06 16:02:29 -070080 public CallModeler(CallStateMonitor callStateMonitor, CallManager callManager,
81 RejectWithTextMessageManager rejectWithTextMessageManager) {
Santos Cordon63a84242013-07-23 13:32:52 -070082 mCallStateMonitor = callStateMonitor;
Santos Cordona3d05142013-07-29 11:25:17 -070083 mCallManager = callManager;
Christine Chenee09a492013-08-06 16:02:29 -070084 mRejectWithTextMessageManager = rejectWithTextMessageManager;
Santos Cordon63a84242013-07-23 13:32:52 -070085
86 mCallStateMonitor.addListener(this);
87 }
88
89 @Override
90 public void handleMessage(Message msg) {
91 switch(msg.what) {
92 case CallStateMonitor.PHONE_NEW_RINGING_CONNECTION:
93 onNewRingingConnection((AsyncResult) msg.obj);
94 break;
95 case CallStateMonitor.PHONE_DISCONNECT:
96 onDisconnect((AsyncResult) msg.obj);
Santos Cordon995c8162013-07-29 09:22:22 -070097 break;
98 case CallStateMonitor.PHONE_STATE_CHANGED:
99 onPhoneStateChanged((AsyncResult) msg.obj);
100 break;
Santos Cordon63a84242013-07-23 13:32:52 -0700101 default:
102 break;
103 }
104 }
105
Christine Chendaf7bf62013-08-05 19:12:31 -0700106 public void addListener(Listener listener) {
Santos Cordon63a84242013-07-23 13:32:52 -0700107 Preconditions.checkNotNull(listener);
Christine Chendaf7bf62013-08-05 19:12:31 -0700108 Preconditions.checkNotNull(mListeners);
Christine Chen4748abd2013-08-07 15:44:15 -0700109 if (!mListeners.contains(listener)) {
110 mListeners.add(listener);
111 }
Santos Cordon998f42b2013-08-02 16:13:12 -0700112 }
113
114 public List<Call> getFullList() {
115 final List<Call> retval = Lists.newArrayList();
116 doUpdate(true, retval);
117 return retval;
Santos Cordon63a84242013-07-23 13:32:52 -0700118 }
119
Santos Cordon249efd02013-08-05 03:33:56 -0700120 public CallResult getCallWithId(int callId) {
121 // max 8 connections, so this should be fast even through we are traversing the entire map.
122 for (Entry<Connection, Call> entry : mCallMap.entrySet()) {
123 if (entry.getValue().getCallId() == callId) {
124 return new CallResult(entry.getValue(), entry.getKey());
125 }
126 }
127 return null;
128 }
129
Santos Cordon2eaff902013-08-05 04:37:55 -0700130 public boolean hasOutstandingActiveCall() {
131 for (Call call : mCallMap.values()) {
132 int state = call.getState();
133 if (Call.State.INVALID != state &&
134 Call.State.IDLE != state &&
135 Call.State.INCOMING != state) {
136 return true;
137 }
138 }
139
140 return false;
141 }
142
Santos Cordon63a84242013-07-23 13:32:52 -0700143 private void onNewRingingConnection(AsyncResult r) {
144 final Connection conn = (Connection) r.result;
Santos Cordon995c8162013-07-29 09:22:22 -0700145 final Call call = getCallFromConnection(conn, true);
Santos Cordona3d05142013-07-29 11:25:17 -0700146 call.setState(Call.State.INCOMING);
Santos Cordon63a84242013-07-23 13:32:52 -0700147
Christine Chendaf7bf62013-08-05 19:12:31 -0700148 for (int i = 0; i < mListeners.size(); ++i) {
Christine Chenee09a492013-08-06 16:02:29 -0700149 if (call != null) {
150 mListeners.get(i).onIncoming(call,
151 mRejectWithTextMessageManager.loadCannedResponses());
152 }
Santos Cordon63a84242013-07-23 13:32:52 -0700153 }
154 }
155
156 private void onDisconnect(AsyncResult r) {
157 final Connection conn = (Connection) r.result;
Santos Cordon995c8162013-07-29 09:22:22 -0700158 final Call call = getCallFromConnection(conn, false);
Santos Cordon998f42b2013-08-02 16:13:12 -0700159 call.setState(Call.State.IDLE);
Santos Cordon63a84242013-07-23 13:32:52 -0700160
Santos Cordon995c8162013-07-29 09:22:22 -0700161 if (call != null) {
162 mCallMap.remove(conn);
Santos Cordon63a84242013-07-23 13:32:52 -0700163
Christine Chendaf7bf62013-08-05 19:12:31 -0700164 for (int i = 0; i < mListeners.size(); ++i) {
165 mListeners.get(i).onDisconnect(call);
Santos Cordon63a84242013-07-23 13:32:52 -0700166 }
167 }
168 }
169
Santos Cordona3d05142013-07-29 11:25:17 -0700170 /**
171 * Called when the phone state changes.
Santos Cordona3d05142013-07-29 11:25:17 -0700172 */
Santos Cordon995c8162013-07-29 09:22:22 -0700173 private void onPhoneStateChanged(AsyncResult r) {
Santos Cordon998f42b2013-08-02 16:13:12 -0700174 final List<Call> updatedCalls = Lists.newArrayList();
175 doUpdate(false, updatedCalls);
176
Christine Chendaf7bf62013-08-05 19:12:31 -0700177 for (int i = 0; i < mListeners.size(); ++i) {
178 mListeners.get(i).onUpdate(updatedCalls, false);
Santos Cordon998f42b2013-08-02 16:13:12 -0700179 }
180 }
181
182
183 /**
184 * Go through the Calls from CallManager and return the list of calls that were updated.
185 * Or, the full list if requested.
186 */
187 private void doUpdate(boolean fullUpdate, List<Call> out) {
Santos Cordona3d05142013-07-29 11:25:17 -0700188 final List<com.android.internal.telephony.Call> telephonyCalls = Lists.newArrayList();
189 telephonyCalls.addAll(mCallManager.getRingingCalls());
190 telephonyCalls.addAll(mCallManager.getForegroundCalls());
191 telephonyCalls.addAll(mCallManager.getBackgroundCalls());
192
Santos Cordona3d05142013-07-29 11:25:17 -0700193 // Cycle through all the Connections on all the Calls. Update our Call objects
194 // to reflect any new state and send the updated Call objects to the handler service.
195 for (com.android.internal.telephony.Call telephonyCall : telephonyCalls) {
196 final int state = translateStateFromTelephony(telephonyCall.getState());
197
198 for (Connection connection : telephonyCall.getConnections()) {
Santos Cordon998f42b2013-08-02 16:13:12 -0700199 // new connections return a Call with INVALID state, which does not translate to
200 // a state in the Connection object. This ensures that staleness check below
201 // fails and we always add the item to the update list if it is new.
Santos Cordona3d05142013-07-29 11:25:17 -0700202 final Call call = getCallFromConnection(connection, true);
203
Santos Cordon998f42b2013-08-02 16:13:12 -0700204 if (fullUpdate || call.getState() != state) {
Santos Cordona3d05142013-07-29 11:25:17 -0700205 call.setState(state);
Santos Cordon998f42b2013-08-02 16:13:12 -0700206 out.add(call);
Santos Cordona3d05142013-07-29 11:25:17 -0700207 }
208 }
209 }
Santos Cordona3d05142013-07-29 11:25:17 -0700210 }
211
212 private int translateStateFromTelephony(com.android.internal.telephony.Call.State teleState) {
213 int retval = State.IDLE;
214 switch (teleState) {
215 case ACTIVE:
216 retval = State.ACTIVE;
217 break;
218 case INCOMING:
219 retval = State.INCOMING;
220 break;
221 case DIALING:
222 case ALERTING:
223 retval = State.DIALING;
224 break;
225 case WAITING:
226 retval = State.CALL_WAITING;
227 break;
228 case HOLDING:
229 retval = State.ONHOLD;
230 break;
231 default:
232 }
233
234 return retval;
Santos Cordon995c8162013-07-29 09:22:22 -0700235 }
236
Santos Cordon63a84242013-07-23 13:32:52 -0700237 /**
238 * Gets an existing callId for a connection, or creates one
239 * if none exists.
240 */
Santos Cordon995c8162013-07-29 09:22:22 -0700241 private Call getCallFromConnection(Connection conn, boolean createIfMissing) {
242 Call call = null;
Santos Cordon63a84242013-07-23 13:32:52 -0700243
244 // Find the call id or create if missing and requested.
245 if (conn != null) {
Santos Cordon995c8162013-07-29 09:22:22 -0700246 if (mCallMap.containsKey(conn)) {
247 call = mCallMap.get(conn);
Santos Cordon63a84242013-07-23 13:32:52 -0700248 } else if (createIfMissing) {
Santos Cordon995c8162013-07-29 09:22:22 -0700249 int callId;
Santos Cordon63a84242013-07-23 13:32:52 -0700250 int newNextCallId;
251 do {
252 callId = mNextCallId.get();
253
254 // protect against overflow
255 newNextCallId = (callId == Integer.MAX_VALUE ?
256 CALL_ID_START_VALUE : callId + 1);
257
258 // Keep looping if the change was not atomic OR the value is already taken.
259 // The call to containsValue() is linear, however, most devices support a
260 // maximum of 7 connections so it's not expensive.
261 } while (!mNextCallId.compareAndSet(callId, newNextCallId) ||
Santos Cordon995c8162013-07-29 09:22:22 -0700262 mCallMap.containsValue(callId));
Santos Cordon63a84242013-07-23 13:32:52 -0700263
Santos Cordon995c8162013-07-29 09:22:22 -0700264 call = new Call(callId);
Santos Cordon179907f2013-07-31 09:40:55 -0700265 call.setNumber(conn.getAddress());
Santos Cordon995c8162013-07-29 09:22:22 -0700266 mCallMap.put(conn, call);
Santos Cordon63a84242013-07-23 13:32:52 -0700267 }
268 }
Santos Cordon995c8162013-07-29 09:22:22 -0700269 return call;
Santos Cordon63a84242013-07-23 13:32:52 -0700270 }
271
272 /**
273 * Listener interface for changes to Calls.
274 */
275 public interface Listener {
Santos Cordon995c8162013-07-29 09:22:22 -0700276 void onDisconnect(Call call);
Christine Chenee09a492013-08-06 16:02:29 -0700277 void onIncoming(Call call, ArrayList<String> textReponses);
Santos Cordon998f42b2013-08-02 16:13:12 -0700278 void onUpdate(List<Call> calls, boolean fullUpdate);
Santos Cordon63a84242013-07-23 13:32:52 -0700279 }
Santos Cordon249efd02013-08-05 03:33:56 -0700280
281 /**
282 * Result class for accessing a call by connection.
283 */
284 public static class CallResult {
285 public Call mCall;
286 public Connection mConnection;
287
288 private CallResult(Call call, Connection connection) {
289 mCall = call;
290 mConnection = connection;
291 }
292
293 public Call getCall() {
294 return mCall;
295 }
296
297 public Connection getConnection() {
298 return mConnection;
299 }
300 }
Santos Cordon63a84242013-07-23 13:32:52 -0700301}