blob: 624a086f566c46bc8e46f25e44fecf30a01b27d9 [file] [log] [blame]
Santos Cordon89647a62013-07-16 13:38:09 -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 android.content.ComponentName;
20import android.content.Context;
21import android.content.Intent;
22import android.content.ServiceConnection;
Santos Cordon89647a62013-07-16 13:38:09 -070023import android.os.Handler;
24import android.os.IBinder;
25import android.os.Message;
26import android.os.RemoteException;
27import android.os.SystemProperties;
28import android.util.Log;
29
Santos Cordon9b7bac72013-08-06 08:04:52 -070030import com.android.phone.AudioRouter.AudioModeListener;
31import com.android.services.telephony.common.AudioMode;
Santos Cordonf4046882013-07-25 18:49:27 -070032import com.android.services.telephony.common.Call;
Santos Cordon345350e2013-07-19 17:16:14 -070033import com.android.services.telephony.common.ICallHandlerService;
Chiao Cheng6c6b2722013-08-22 18:35:54 -070034import com.google.common.collect.Lists;
Santos Cordon89647a62013-07-16 13:38:09 -070035
Santos Cordona3d05142013-07-29 11:25:17 -070036import java.util.List;
37
Santos Cordon89647a62013-07-16 13:38:09 -070038/**
Santos Cordon345350e2013-07-19 17:16:14 -070039 * This class is responsible for passing through call state changes to the CallHandlerService.
Santos Cordon89647a62013-07-16 13:38:09 -070040 */
Chiao Cheng6c6b2722013-08-22 18:35:54 -070041public class CallHandlerServiceProxy extends Handler
42 implements CallModeler.Listener, AudioModeListener {
Santos Cordon89647a62013-07-16 13:38:09 -070043
Santos Cordon345350e2013-07-19 17:16:14 -070044 private static final String TAG = CallHandlerServiceProxy.class.getSimpleName();
Chiao Cheng6c6b2722013-08-22 18:35:54 -070045 private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt(
46 "ro.debuggable", 0) == 1);
Chiao Chenge41661c2013-07-23 13:28:26 -070047
Chiao Cheng6c6b2722013-08-22 18:35:54 -070048 public static final int RETRY_DELAY_MILLIS = 2000;
49 private static final int BIND_RETRY_MSG = 1;
50 private static final int MAX_RETRY_COUNT = 5;
Santos Cordon89647a62013-07-16 13:38:09 -070051
Santos Cordon593ab382013-08-06 21:58:23 -070052 private AudioRouter mAudioRouter;
Santos Cordoncba1b442013-07-18 12:43:58 -070053 private CallCommandService mCallCommandService;
Santos Cordon593ab382013-08-06 21:58:23 -070054 private CallModeler mCallModeler;
55 private Context mContext;
Chiao Cheng6c6b2722013-08-22 18:35:54 -070056 private ICallHandlerService mCallHandlerServiceGuarded; // Guarded by mServiceAndQueueLock
57 private List<Call> mIncomingCallQueueGuarded; // Guarded by mServiceAndQueueLock
58 private List<List<Call>> mUpdateCallQueueGuarded; // Guarded by mServiceAndQueueLock
59 private List<Call> mDisconnectCallQueueGuarded; // Guarded by mServiceAndQueueLock
60 private final Object mServiceAndQueueLock = new Object();
61 private int mBindRetryCount = 0;
62
63 @Override
64 public void handleMessage(Message msg) {
65 super.handleMessage(msg);
66
67 switch (msg.what) {
68 case BIND_RETRY_MSG:
69 setupServiceConnection();
70 break;
71 }
72 }
Santos Cordon89647a62013-07-16 13:38:09 -070073
Santos Cordon63a84242013-07-23 13:32:52 -070074 public CallHandlerServiceProxy(Context context, CallModeler callModeler,
Santos Cordon593ab382013-08-06 21:58:23 -070075 CallCommandService callCommandService, AudioRouter audioRouter) {
Chiao Cheng6c6b2722013-08-22 18:35:54 -070076 if (DBG) {
77 Log.d(TAG, "init CallHandlerServiceProxy");
78 }
Santos Cordon89647a62013-07-16 13:38:09 -070079 mContext = context;
Santos Cordoncba1b442013-07-18 12:43:58 -070080 mCallCommandService = callCommandService;
Santos Cordon63a84242013-07-23 13:32:52 -070081 mCallModeler = callModeler;
Santos Cordon593ab382013-08-06 21:58:23 -070082 mAudioRouter = audioRouter;
Santos Cordon89647a62013-07-16 13:38:09 -070083
Santos Cordon593ab382013-08-06 21:58:23 -070084 mAudioRouter.addAudioModeListener(this);
Christine Chendaf7bf62013-08-05 19:12:31 -070085 mCallModeler.addListener(this);
Chiao Cheng6c6b2722013-08-22 18:35:54 -070086
Yorke Lee576472d2013-08-29 11:16:35 -070087 if (PhoneGlobals.sVoiceCapable) {
88 setupServiceConnection();
89 }
Santos Cordon63a84242013-07-23 13:32:52 -070090 }
Santos Cordon89647a62013-07-16 13:38:09 -070091
Santos Cordon63a84242013-07-23 13:32:52 -070092 @Override
Santos Cordon995c8162013-07-29 09:22:22 -070093 public void onDisconnect(Call call) {
Chiao Cheng6c6b2722013-08-22 18:35:54 -070094 try {
95 synchronized (mServiceAndQueueLock) {
96 if (mCallHandlerServiceGuarded == null) {
97 if (DBG) {
98 Log.d(TAG, "CallHandlerService not connected. Enqueue disconnect");
99 }
100 enqueueDisconnect(call);
101 return;
102 }
Santos Cordon63a84242013-07-23 13:32:52 -0700103 }
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700104 if (DBG) {
105 Log.d(TAG, "onDisconnect: " + call);
106 }
107 mCallHandlerServiceGuarded.onDisconnect(call);
108 } catch (Exception e) {
109 Log.e(TAG, "Remote exception handling onDisconnect ", e);
Santos Cordon89647a62013-07-16 13:38:09 -0700110 }
111 }
112
Santos Cordona3d05142013-07-29 11:25:17 -0700113 @Override
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700114 public void onIncoming(Call call) {
115 try {
116 synchronized (mServiceAndQueueLock) {
117 if (mCallHandlerServiceGuarded == null) {
118 if (DBG) {
119 Log.d(TAG, "CallHandlerService not connected. Enqueue incoming.");
120 }
121 enqueueIncoming(call);
122 return;
123 }
Christine Chenee09a492013-08-06 16:02:29 -0700124 }
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700125 if (DBG) {
126 Log.d(TAG, "onIncoming: " + call);
127 }
128 // TODO(klp): check RespondViaSmsManager.allowRespondViaSmsForCall()
129 // must refactor call method to accept proper call object.
130 mCallHandlerServiceGuarded.onIncoming(call,
131 RejectWithTextMessageManager.loadCannedResponses());
132 } catch (Exception e) {
133 Log.e(TAG, "Remote exception handling onUpdate", e);
Christine Chenee09a492013-08-06 16:02:29 -0700134 }
135 }
136
137 @Override
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700138 public void onUpdate(List<Call> calls) {
139 try {
140 synchronized (mServiceAndQueueLock) {
141 if (mCallHandlerServiceGuarded == null) {
142 if (DBG) {
143 Log.d(TAG, "CallHandlerService not connected. Enqueue update.");
144 }
145 enqueueUpdate(calls);
146 return;
147 }
Santos Cordona3d05142013-07-29 11:25:17 -0700148 }
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700149
150 if (DBG) {
151 Log.d(TAG, "onUpdate: " + calls.toString());
152 }
153 mCallHandlerServiceGuarded.onUpdate(calls);
154 } catch (Exception e) {
155 Log.e(TAG, "Remote exception handling onUpdate", e);
Santos Cordona3d05142013-07-29 11:25:17 -0700156 }
157 }
158
Santos Cordon9b7bac72013-08-06 08:04:52 -0700159 @Override
160 public void onAudioModeChange(int previousMode, int newMode) {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700161 try {
162 synchronized (mServiceAndQueueLock) {
163 // TODO(klp): does this need to be enqueued?
164 if (mCallHandlerServiceGuarded == null) {
165 if (DBG) {
166 Log.d(TAG, "CallHandlerService not conneccted. Skipping "
167 + "onAudioModeChange().");
168 }
169 return;
170 }
Santos Cordon9b7bac72013-08-06 08:04:52 -0700171 }
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700172
173 // Just do a simple log for now.
174 Log.i(TAG, "Updating with new audio mode: " + AudioMode.toString(newMode) +
175 " from " + AudioMode.toString(previousMode));
176
177 if (DBG) {
178 Log.d(TAG, "onSupportAudioModeChange");
179 }
180
181 mCallHandlerServiceGuarded.onAudioModeChange(newMode);
182 } catch (Exception e) {
183 Log.e(TAG, "Remote exception handling onAudioModeChange", e);
Santos Cordon9b7bac72013-08-06 08:04:52 -0700184 }
185 }
186
Santos Cordon593ab382013-08-06 21:58:23 -0700187 @Override
188 public void onSupportedAudioModeChange(int modeMask) {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700189 try {
190 synchronized (mServiceAndQueueLock) {
191 // TODO(klp): does this need to be enqueued?
192 if (mCallHandlerServiceGuarded == null) {
193 if (DBG) {
194 Log.d(TAG, "CallHandlerService not conneccted. Skipping"
195 + "onSupportedAudioModeChange().");
196 }
197 return;
198 }
199 }
Santos Cordon593ab382013-08-06 21:58:23 -0700200
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700201 if (DBG) {
202 Log.d(TAG, "onSupportAudioModeChange: " + AudioMode.toString(modeMask));
203 }
204
205 mCallHandlerServiceGuarded.onSupportedAudioModeChange(modeMask);
206 } catch (Exception e) {
207 Log.e(TAG, "Remote exception handling onAudioModeChange", e);
208 }
209
210 }
211
212 private ServiceConnection mConnection = new ServiceConnection() {
213 @Override public void onServiceConnected (ComponentName className, IBinder service){
214 if (DBG) {
215 Log.d(TAG, "Service Connected");
216 }
217 onCallHandlerServiceConnected(ICallHandlerService.Stub.asInterface(service));
218 }
219
220 @Override public void onServiceDisconnected (ComponentName className){
221 Log.i(TAG, "Disconnected from UI service.");
222 synchronized (mServiceAndQueueLock) {
223 mCallHandlerServiceGuarded = null;
224
225 // Technically, unbindService is un-necessary since the framework will schedule and
226 // restart the crashed service. But there is a exponential backoff for the restart.
227 // Unbind explicitly and setup again to avoid the backoff since it's important to
228 // always have an in call ui.
229 mContext.unbindService(mConnection);
230 setupServiceConnection();
Santos Cordon593ab382013-08-06 21:58:23 -0700231 }
232 }
Santos Cordon19d814b2013-08-28 14:58:17 -0700233 };
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700234
Santos Cordon406c0342013-08-28 00:07:47 -0700235 public void bringToForeground() {
236 // only support this call if the service is already connected.
Santos Cordon19d814b2013-08-28 14:58:17 -0700237 synchronized (mServiceAndQueueLock) {
238 if (mCallHandlerServiceGuarded != null && mCallModeler.hasLiveCall()) {
239 try {
240 if (DBG) Log.d(TAG, "bringToForeground");
241 mCallHandlerServiceGuarded.bringToForeground();
242 } catch (RemoteException e) {
243 Log.e(TAG, "Exception handling bringToForeground", e);
244 }
Santos Cordon406c0342013-08-28 00:07:47 -0700245 }
246 }
247 }
248
Santos Cordon89647a62013-07-16 13:38:09 -0700249 /**
Santos Cordon345350e2013-07-19 17:16:14 -0700250 * Sets up the connection with ICallHandlerService
Santos Cordon89647a62013-07-16 13:38:09 -0700251 */
252 private void setupServiceConnection() {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700253 synchronized (mServiceAndQueueLock) {
254 if (mCallHandlerServiceGuarded == null) {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700255 final Intent serviceIntent = new Intent(ICallHandlerService.class.getName());
256 final ComponentName component = new ComponentName(mContext.getResources().getString(
257 R.string.incall_ui_default_package), mContext.getResources().getString(
258 R.string.incall_ui_default_class));
259 serviceIntent.setComponent(component);
260
Santos Cordon89647a62013-07-16 13:38:09 -0700261 if (DBG) {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700262 Log.d(TAG, "binding to service " + serviceIntent);
Santos Cordon89647a62013-07-16 13:38:09 -0700263 }
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700264 if (!mContext.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE)) {
265 // This happens when the in-call package is in the middle of being installed.
266 // Delay the retry.
267 mBindRetryCount++;
268 if (mBindRetryCount < MAX_RETRY_COUNT) {
269 Log.e(TAG, "bindService failed on " + serviceIntent + ". Retrying in " +
270 RETRY_DELAY_MILLIS + " ms.");
271 sendMessageDelayed(Message.obtain(this, BIND_RETRY_MSG),
272 RETRY_DELAY_MILLIS);
273 } else {
274 Log.wtf(TAG, "Tried to bind to in-call UI " + MAX_RETRY_COUNT + " times."
275 + " Giving up.");
276 }
277 }
Santos Cordonaf763a12013-08-19 20:04:58 -0700278 }
279 }
280 }
281
282 /**
Santos Cordoncba1b442013-07-18 12:43:58 -0700283 * Called when the in-call UI service is connected. Send command interface to in-call.
284 */
Santos Cordon63a84242013-07-23 13:32:52 -0700285 private void onCallHandlerServiceConnected(ICallHandlerService callHandlerService) {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700286 synchronized (mServiceAndQueueLock) {
287 mCallHandlerServiceGuarded = callHandlerService;
288
Santos Cordonad078192013-08-28 15:14:54 -0700289 // Before we send any updates, we need to set up the initial service calls.
290 makeInitialServiceCalls();
291
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700292 // TODO(klp): combine queues into a single ordered queue.
293 processIncomingCallQueue();
294 processUpdateCallQueue();
295 processDisconnectQueue();
296 }
Santos Cordonad078192013-08-28 15:14:54 -0700297 }
Santos Cordoncba1b442013-07-18 12:43:58 -0700298
Santos Cordonad078192013-08-28 15:14:54 -0700299 /**
300 * Makes initial service calls to set up callcommandservice and audio modes.
301 */
302 private void makeInitialServiceCalls() {
Santos Cordoncba1b442013-07-18 12:43:58 -0700303 try {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700304 mCallHandlerServiceGuarded.setCallCommandService(mCallCommandService);
Santos Cordonad078192013-08-28 15:14:54 -0700305
306 onSupportedAudioModeChange(mAudioRouter.getSupportedAudioModes());
307 final int mode = mAudioRouter.getAudioMode();
308 onAudioModeChange(mode, mode);
Santos Cordoncba1b442013-07-18 12:43:58 -0700309 } catch (RemoteException e) {
Santos Cordon63a84242013-07-23 13:32:52 -0700310 Log.e(TAG, "Remote exception calling CallHandlerService::setCallCommandService", e);
Santos Cordon89647a62013-07-16 13:38:09 -0700311 }
312 }
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700313
314
315 private void enqueueDisconnect(Call call) {
316 if (mDisconnectCallQueueGuarded == null) {
317 mDisconnectCallQueueGuarded = Lists.newArrayList();
318 }
319 mDisconnectCallQueueGuarded.add(new Call(call));
320 }
321
322 private void enqueueIncoming(Call call) {
323 if (mIncomingCallQueueGuarded == null) {
324 mIncomingCallQueueGuarded = Lists.newArrayList();
325 }
326 mIncomingCallQueueGuarded.add(new Call(call));
327 }
328
329 private void enqueueUpdate(List<Call> calls) {
330 if (mUpdateCallQueueGuarded == null) {
331 mUpdateCallQueueGuarded = Lists.newArrayList();
332 }
333 final List<Call> copy = Lists.newArrayList();
334 for (Call call : calls) {
335 copy.add(new Call(call));
336 }
337 mUpdateCallQueueGuarded.add(copy);
338 }
339
340 private void processDisconnectQueue() {
341 if (mDisconnectCallQueueGuarded != null) {
342 for (Call call : mDisconnectCallQueueGuarded) {
343 onDisconnect(call);
344 }
345 mDisconnectCallQueueGuarded.clear();
346 mDisconnectCallQueueGuarded = null;
347 }
348 }
349
350 private void processIncomingCallQueue() {
351 if (mIncomingCallQueueGuarded != null) {
352 for (Call call : mIncomingCallQueueGuarded) {
353 onIncoming(call);
354 }
355 mIncomingCallQueueGuarded.clear();
356 mIncomingCallQueueGuarded = null;
357 }
358 }
359
360 private void processUpdateCallQueue() {
361 if (mUpdateCallQueueGuarded != null) {
362 for (List<Call> calls : mUpdateCallQueueGuarded) {
363 onUpdate(calls);
364 }
365 mUpdateCallQueueGuarded.clear();
366 mUpdateCallQueueGuarded = null;
367 }
368 }
Santos Cordon89647a62013-07-16 13:38:09 -0700369}