blob: 5d735a46c8eebf460fbea4a0beaac0c9c218242a [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;
37import 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
73 private CallStateMonitor mCallStateMonitor;
Santos Cordona3d05142013-07-29 11:25:17 -070074 private CallManager mCallManager;
Santos Cordon995c8162013-07-29 09:22:22 -070075 private HashMap<Connection, Call> mCallMap = Maps.newHashMap();
Santos Cordon63a84242013-07-23 13:32:52 -070076 private List<Listener> mListeners = Lists.newArrayList();
77 private AtomicInteger mNextCallId = new AtomicInteger(CALL_ID_START_VALUE);
78
Santos Cordona3d05142013-07-29 11:25:17 -070079 public CallModeler(CallStateMonitor callStateMonitor, CallManager callManager) {
Santos Cordon63a84242013-07-23 13:32:52 -070080 mCallStateMonitor = callStateMonitor;
Santos Cordona3d05142013-07-29 11:25:17 -070081 mCallManager = callManager;
Santos Cordon63a84242013-07-23 13:32:52 -070082
83 mCallStateMonitor.addListener(this);
84 }
85
86 @Override
87 public void handleMessage(Message msg) {
88 switch(msg.what) {
89 case CallStateMonitor.PHONE_NEW_RINGING_CONNECTION:
90 onNewRingingConnection((AsyncResult) msg.obj);
91 break;
92 case CallStateMonitor.PHONE_DISCONNECT:
93 onDisconnect((AsyncResult) msg.obj);
Santos Cordon995c8162013-07-29 09:22:22 -070094 break;
95 case CallStateMonitor.PHONE_STATE_CHANGED:
96 onPhoneStateChanged((AsyncResult) msg.obj);
97 break;
Santos Cordon63a84242013-07-23 13:32:52 -070098 default:
99 break;
100 }
101 }
102
103 public void addListener(Listener listener) {
104 Preconditions.checkNotNull(listener);
105
106 mListeners.add(listener);
107 }
108
109 private void onNewRingingConnection(AsyncResult r) {
110 final Connection conn = (Connection) r.result;
Santos Cordon995c8162013-07-29 09:22:22 -0700111 final Call call = getCallFromConnection(conn, true);
Santos Cordona3d05142013-07-29 11:25:17 -0700112 call.setState(Call.State.INCOMING);
Santos Cordon63a84242013-07-23 13:32:52 -0700113
Santos Cordon995c8162013-07-29 09:22:22 -0700114 if (call != null) {
115 for (Listener l : mListeners) {
116 l.onNewCall(call);
117 }
Santos Cordon63a84242013-07-23 13:32:52 -0700118 }
119 }
120
121 private void onDisconnect(AsyncResult r) {
122 final Connection conn = (Connection) r.result;
Santos Cordon995c8162013-07-29 09:22:22 -0700123 final Call call = getCallFromConnection(conn, false);
Santos Cordon63a84242013-07-23 13:32:52 -0700124
Santos Cordon995c8162013-07-29 09:22:22 -0700125 if (call != null) {
126 mCallMap.remove(conn);
Santos Cordon63a84242013-07-23 13:32:52 -0700127
128 for (Listener l : mListeners) {
Santos Cordon995c8162013-07-29 09:22:22 -0700129 l.onDisconnect(call);
Santos Cordon63a84242013-07-23 13:32:52 -0700130 }
131 }
132 }
133
Santos Cordona3d05142013-07-29 11:25:17 -0700134 /**
135 * Called when the phone state changes.
136 * The telephony layer maintains a certain amount of preallocated telephony::Call instances
137 * that change throughout the lifetime of the phone. When we get an alert that the state
138 * changes, we need to go through the telephony::Calls and determine what changed.
139 */
Santos Cordon995c8162013-07-29 09:22:22 -0700140 private void onPhoneStateChanged(AsyncResult r) {
Santos Cordona3d05142013-07-29 11:25:17 -0700141 final List<com.android.internal.telephony.Call> telephonyCalls = Lists.newArrayList();
142 telephonyCalls.addAll(mCallManager.getRingingCalls());
143 telephonyCalls.addAll(mCallManager.getForegroundCalls());
144 telephonyCalls.addAll(mCallManager.getBackgroundCalls());
145
146 final List<Call> updatedCalls = Lists.newArrayList();
147
148 // Cycle through all the Connections on all the Calls. Update our Call objects
149 // to reflect any new state and send the updated Call objects to the handler service.
150 for (com.android.internal.telephony.Call telephonyCall : telephonyCalls) {
151 final int state = translateStateFromTelephony(telephonyCall.getState());
152
153 for (Connection connection : telephonyCall.getConnections()) {
154 final Call call = getCallFromConnection(connection, true);
155
156 if (call.getState() != state) {
157 call.setState(state);
158 updatedCalls.add(call);
159 }
160 }
161 }
162
163 for (Listener l : mListeners) {
164 l.onUpdate(updatedCalls);
165 }
166 }
167
168 private int translateStateFromTelephony(com.android.internal.telephony.Call.State teleState) {
169 int retval = State.IDLE;
170 switch (teleState) {
171 case ACTIVE:
172 retval = State.ACTIVE;
173 break;
174 case INCOMING:
175 retval = State.INCOMING;
176 break;
177 case DIALING:
178 case ALERTING:
179 retval = State.DIALING;
180 break;
181 case WAITING:
182 retval = State.CALL_WAITING;
183 break;
184 case HOLDING:
185 retval = State.ONHOLD;
186 break;
187 default:
188 }
189
190 return retval;
Santos Cordon995c8162013-07-29 09:22:22 -0700191 }
192
Santos Cordon63a84242013-07-23 13:32:52 -0700193 /**
194 * Gets an existing callId for a connection, or creates one
195 * if none exists.
196 */
Santos Cordon995c8162013-07-29 09:22:22 -0700197 private Call getCallFromConnection(Connection conn, boolean createIfMissing) {
198 Call call = null;
Santos Cordon63a84242013-07-23 13:32:52 -0700199
200 // Find the call id or create if missing and requested.
201 if (conn != null) {
Santos Cordon995c8162013-07-29 09:22:22 -0700202 if (mCallMap.containsKey(conn)) {
203 call = mCallMap.get(conn);
Santos Cordon63a84242013-07-23 13:32:52 -0700204 } else if (createIfMissing) {
Santos Cordon995c8162013-07-29 09:22:22 -0700205 int callId;
Santos Cordon63a84242013-07-23 13:32:52 -0700206 int newNextCallId;
207 do {
208 callId = mNextCallId.get();
209
210 // protect against overflow
211 newNextCallId = (callId == Integer.MAX_VALUE ?
212 CALL_ID_START_VALUE : callId + 1);
213
214 // Keep looping if the change was not atomic OR the value is already taken.
215 // The call to containsValue() is linear, however, most devices support a
216 // maximum of 7 connections so it's not expensive.
217 } while (!mNextCallId.compareAndSet(callId, newNextCallId) ||
Santos Cordon995c8162013-07-29 09:22:22 -0700218 mCallMap.containsValue(callId));
Santos Cordon63a84242013-07-23 13:32:52 -0700219
Santos Cordon995c8162013-07-29 09:22:22 -0700220 call = new Call(callId);
221 mCallMap.put(conn, call);
Santos Cordon63a84242013-07-23 13:32:52 -0700222 }
223 }
Santos Cordon995c8162013-07-29 09:22:22 -0700224 return call;
Santos Cordon63a84242013-07-23 13:32:52 -0700225 }
226
227 /**
228 * Listener interface for changes to Calls.
229 */
230 public interface Listener {
Santos Cordon995c8162013-07-29 09:22:22 -0700231 void onNewCall(Call call);
232 void onDisconnect(Call call);
Santos Cordona3d05142013-07-29 11:25:17 -0700233 void onUpdate(List<Call> calls);
Santos Cordon63a84242013-07-23 13:32:52 -0700234 }
235}