blob: 6fdf80a1e50f502c1a97ad82d2b296ad5b3de8af [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 Chengd38eebc2013-08-28 14:38:14 -070056
Chiao Cheng6c6b2722013-08-22 18:35:54 -070057 private ICallHandlerService mCallHandlerServiceGuarded; // Guarded by mServiceAndQueueLock
Chiao Chengd38eebc2013-08-28 14:38:14 -070058 // Single queue to guarantee ordering
59 private List<QueueParams> mQueue; // Guarded by mServiceAndQueueLock
60
Chiao Cheng6c6b2722013-08-22 18:35:54 -070061 private final Object mServiceAndQueueLock = new Object();
62 private int mBindRetryCount = 0;
63
64 @Override
65 public void handleMessage(Message msg) {
66 super.handleMessage(msg);
67
68 switch (msg.what) {
69 case BIND_RETRY_MSG:
70 setupServiceConnection();
71 break;
72 }
73 }
Santos Cordon89647a62013-07-16 13:38:09 -070074
Santos Cordon63a84242013-07-23 13:32:52 -070075 public CallHandlerServiceProxy(Context context, CallModeler callModeler,
Santos Cordon593ab382013-08-06 21:58:23 -070076 CallCommandService callCommandService, AudioRouter audioRouter) {
Chiao Cheng6c6b2722013-08-22 18:35:54 -070077 if (DBG) {
78 Log.d(TAG, "init CallHandlerServiceProxy");
79 }
Santos Cordon89647a62013-07-16 13:38:09 -070080 mContext = context;
Santos Cordoncba1b442013-07-18 12:43:58 -070081 mCallCommandService = callCommandService;
Santos Cordon63a84242013-07-23 13:32:52 -070082 mCallModeler = callModeler;
Santos Cordon593ab382013-08-06 21:58:23 -070083 mAudioRouter = audioRouter;
Santos Cordon89647a62013-07-16 13:38:09 -070084
Santos Cordon593ab382013-08-06 21:58:23 -070085 mAudioRouter.addAudioModeListener(this);
Christine Chendaf7bf62013-08-05 19:12:31 -070086 mCallModeler.addListener(this);
Chiao Cheng6c6b2722013-08-22 18:35:54 -070087
Yorke Lee576472d2013-08-29 11:16:35 -070088 if (PhoneGlobals.sVoiceCapable) {
89 setupServiceConnection();
90 }
Santos Cordon63a84242013-07-23 13:32:52 -070091 }
Santos Cordon89647a62013-07-16 13:38:09 -070092
Santos Cordon63a84242013-07-23 13:32:52 -070093 @Override
Santos Cordon995c8162013-07-29 09:22:22 -070094 public void onDisconnect(Call call) {
Chiao Cheng6c6b2722013-08-22 18:35:54 -070095 try {
96 synchronized (mServiceAndQueueLock) {
97 if (mCallHandlerServiceGuarded == null) {
98 if (DBG) {
99 Log.d(TAG, "CallHandlerService not connected. Enqueue disconnect");
100 }
101 enqueueDisconnect(call);
102 return;
103 }
Santos Cordon63a84242013-07-23 13:32:52 -0700104 }
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700105 if (DBG) {
106 Log.d(TAG, "onDisconnect: " + call);
107 }
108 mCallHandlerServiceGuarded.onDisconnect(call);
109 } catch (Exception e) {
110 Log.e(TAG, "Remote exception handling onDisconnect ", e);
Santos Cordon89647a62013-07-16 13:38:09 -0700111 }
112 }
113
Santos Cordona3d05142013-07-29 11:25:17 -0700114 @Override
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700115 public void onIncoming(Call call) {
116 try {
117 synchronized (mServiceAndQueueLock) {
118 if (mCallHandlerServiceGuarded == null) {
119 if (DBG) {
120 Log.d(TAG, "CallHandlerService not connected. Enqueue incoming.");
121 }
122 enqueueIncoming(call);
123 return;
124 }
Christine Chenee09a492013-08-06 16:02:29 -0700125 }
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700126 if (DBG) {
127 Log.d(TAG, "onIncoming: " + call);
128 }
129 // TODO(klp): check RespondViaSmsManager.allowRespondViaSmsForCall()
130 // must refactor call method to accept proper call object.
131 mCallHandlerServiceGuarded.onIncoming(call,
132 RejectWithTextMessageManager.loadCannedResponses());
133 } catch (Exception e) {
134 Log.e(TAG, "Remote exception handling onUpdate", e);
Christine Chenee09a492013-08-06 16:02:29 -0700135 }
136 }
137
138 @Override
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700139 public void onUpdate(List<Call> calls) {
140 try {
141 synchronized (mServiceAndQueueLock) {
142 if (mCallHandlerServiceGuarded == null) {
143 if (DBG) {
144 Log.d(TAG, "CallHandlerService not connected. Enqueue update.");
145 }
146 enqueueUpdate(calls);
147 return;
148 }
Santos Cordona3d05142013-07-29 11:25:17 -0700149 }
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700150
151 if (DBG) {
152 Log.d(TAG, "onUpdate: " + calls.toString());
153 }
154 mCallHandlerServiceGuarded.onUpdate(calls);
155 } catch (Exception e) {
156 Log.e(TAG, "Remote exception handling onUpdate", e);
Santos Cordona3d05142013-07-29 11:25:17 -0700157 }
158 }
159
Santos Cordon9b7bac72013-08-06 08:04:52 -0700160 @Override
Santos Cordoncd95f622013-08-29 03:38:52 -0700161 public void onAudioModeChange(int newMode, boolean muted) {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700162 try {
163 synchronized (mServiceAndQueueLock) {
164 // TODO(klp): does this need to be enqueued?
165 if (mCallHandlerServiceGuarded == null) {
166 if (DBG) {
167 Log.d(TAG, "CallHandlerService not conneccted. Skipping "
168 + "onAudioModeChange().");
169 }
170 return;
171 }
Santos Cordon9b7bac72013-08-06 08:04:52 -0700172 }
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700173
174 // Just do a simple log for now.
175 Log.i(TAG, "Updating with new audio mode: " + AudioMode.toString(newMode) +
Santos Cordoncd95f622013-08-29 03:38:52 -0700176 " with mute " + muted);
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700177
178 if (DBG) {
179 Log.d(TAG, "onSupportAudioModeChange");
180 }
181
Santos Cordoncd95f622013-08-29 03:38:52 -0700182 mCallHandlerServiceGuarded.onAudioModeChange(newMode, muted);
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700183 } catch (Exception e) {
184 Log.e(TAG, "Remote exception handling onAudioModeChange", e);
Santos Cordon9b7bac72013-08-06 08:04:52 -0700185 }
186 }
187
Santos Cordon593ab382013-08-06 21:58:23 -0700188 @Override
189 public void onSupportedAudioModeChange(int modeMask) {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700190 try {
191 synchronized (mServiceAndQueueLock) {
192 // TODO(klp): does this need to be enqueued?
193 if (mCallHandlerServiceGuarded == null) {
194 if (DBG) {
195 Log.d(TAG, "CallHandlerService not conneccted. Skipping"
196 + "onSupportedAudioModeChange().");
197 }
198 return;
199 }
200 }
Santos Cordon593ab382013-08-06 21:58:23 -0700201
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700202 if (DBG) {
203 Log.d(TAG, "onSupportAudioModeChange: " + AudioMode.toString(modeMask));
204 }
205
206 mCallHandlerServiceGuarded.onSupportedAudioModeChange(modeMask);
207 } catch (Exception e) {
208 Log.e(TAG, "Remote exception handling onAudioModeChange", e);
209 }
210
211 }
212
213 private ServiceConnection mConnection = new ServiceConnection() {
214 @Override public void onServiceConnected (ComponentName className, IBinder service){
215 if (DBG) {
216 Log.d(TAG, "Service Connected");
217 }
218 onCallHandlerServiceConnected(ICallHandlerService.Stub.asInterface(service));
219 }
220
221 @Override public void onServiceDisconnected (ComponentName className){
222 Log.i(TAG, "Disconnected from UI service.");
223 synchronized (mServiceAndQueueLock) {
224 mCallHandlerServiceGuarded = null;
225
226 // Technically, unbindService is un-necessary since the framework will schedule and
227 // restart the crashed service. But there is a exponential backoff for the restart.
228 // Unbind explicitly and setup again to avoid the backoff since it's important to
229 // always have an in call ui.
230 mContext.unbindService(mConnection);
231 setupServiceConnection();
Santos Cordon593ab382013-08-06 21:58:23 -0700232 }
233 }
Santos Cordon19d814b2013-08-28 14:58:17 -0700234 };
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700235
Santos Cordon406c0342013-08-28 00:07:47 -0700236 public void bringToForeground() {
237 // only support this call if the service is already connected.
Santos Cordon19d814b2013-08-28 14:58:17 -0700238 synchronized (mServiceAndQueueLock) {
239 if (mCallHandlerServiceGuarded != null && mCallModeler.hasLiveCall()) {
240 try {
241 if (DBG) Log.d(TAG, "bringToForeground");
242 mCallHandlerServiceGuarded.bringToForeground();
243 } catch (RemoteException e) {
244 Log.e(TAG, "Exception handling bringToForeground", e);
245 }
Santos Cordon406c0342013-08-28 00:07:47 -0700246 }
247 }
248 }
249
Santos Cordon89647a62013-07-16 13:38:09 -0700250 /**
Santos Cordon345350e2013-07-19 17:16:14 -0700251 * Sets up the connection with ICallHandlerService
Santos Cordon89647a62013-07-16 13:38:09 -0700252 */
253 private void setupServiceConnection() {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700254 synchronized (mServiceAndQueueLock) {
255 if (mCallHandlerServiceGuarded == null) {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700256 final Intent serviceIntent = new Intent(ICallHandlerService.class.getName());
257 final ComponentName component = new ComponentName(mContext.getResources().getString(
258 R.string.incall_ui_default_package), mContext.getResources().getString(
259 R.string.incall_ui_default_class));
260 serviceIntent.setComponent(component);
261
Santos Cordon89647a62013-07-16 13:38:09 -0700262 if (DBG) {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700263 Log.d(TAG, "binding to service " + serviceIntent);
Santos Cordon89647a62013-07-16 13:38:09 -0700264 }
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700265 if (!mContext.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE)) {
266 // This happens when the in-call package is in the middle of being installed.
267 // Delay the retry.
268 mBindRetryCount++;
269 if (mBindRetryCount < MAX_RETRY_COUNT) {
270 Log.e(TAG, "bindService failed on " + serviceIntent + ". Retrying in " +
271 RETRY_DELAY_MILLIS + " ms.");
272 sendMessageDelayed(Message.obtain(this, BIND_RETRY_MSG),
273 RETRY_DELAY_MILLIS);
274 } else {
275 Log.wtf(TAG, "Tried to bind to in-call UI " + MAX_RETRY_COUNT + " times."
276 + " Giving up.");
277 }
278 }
Santos Cordonaf763a12013-08-19 20:04:58 -0700279 }
280 }
281 }
282
283 /**
Santos Cordoncba1b442013-07-18 12:43:58 -0700284 * Called when the in-call UI service is connected. Send command interface to in-call.
285 */
Santos Cordon63a84242013-07-23 13:32:52 -0700286 private void onCallHandlerServiceConnected(ICallHandlerService callHandlerService) {
Chiao Chengd38eebc2013-08-28 14:38:14 -0700287
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700288 synchronized (mServiceAndQueueLock) {
289 mCallHandlerServiceGuarded = callHandlerService;
290
Santos Cordonad078192013-08-28 15:14:54 -0700291 // Before we send any updates, we need to set up the initial service calls.
292 makeInitialServiceCalls();
293
Chiao Chengd38eebc2013-08-28 14:38:14 -0700294 processQueue();
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700295 }
Santos Cordonad078192013-08-28 15:14:54 -0700296 }
Santos Cordoncba1b442013-07-18 12:43:58 -0700297
Santos Cordonad078192013-08-28 15:14:54 -0700298 /**
299 * Makes initial service calls to set up callcommandservice and audio modes.
300 */
301 private void makeInitialServiceCalls() {
Santos Cordoncba1b442013-07-18 12:43:58 -0700302 try {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700303 mCallHandlerServiceGuarded.setCallCommandService(mCallCommandService);
Santos Cordonad078192013-08-28 15:14:54 -0700304
305 onSupportedAudioModeChange(mAudioRouter.getSupportedAudioModes());
Santos Cordon8fd0ec72013-08-29 16:44:43 -0700306 onAudioModeChange(mAudioRouter.getAudioMode(), mAudioRouter.getMute());
Santos Cordoncba1b442013-07-18 12:43:58 -0700307 } catch (RemoteException e) {
Santos Cordon63a84242013-07-23 13:32:52 -0700308 Log.e(TAG, "Remote exception calling CallHandlerService::setCallCommandService", e);
Santos Cordon89647a62013-07-16 13:38:09 -0700309 }
310 }
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700311
Chiao Chengd38eebc2013-08-28 14:38:14 -0700312 private List<QueueParams> getQueue() {
313 if (mQueue == null) {
314 mQueue = Lists.newArrayList();
315 }
316 return mQueue;
317 }
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700318
319 private void enqueueDisconnect(Call call) {
Chiao Chengd38eebc2013-08-28 14:38:14 -0700320 getQueue().add(new QueueParams(QueueParams.METHOD_DISCONNECT, new Call(call)));
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700321 }
322
323 private void enqueueIncoming(Call call) {
Chiao Chengd38eebc2013-08-28 14:38:14 -0700324 getQueue().add(new QueueParams(QueueParams.METHOD_INCOMING, new Call(call)));
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700325 }
326
327 private void enqueueUpdate(List<Call> calls) {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700328 final List<Call> copy = Lists.newArrayList();
329 for (Call call : calls) {
330 copy.add(new Call(call));
331 }
Chiao Chengd38eebc2013-08-28 14:38:14 -0700332 getQueue().add(new QueueParams(QueueParams.METHOD_INCOMING, copy));
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700333 }
334
Chiao Chengd38eebc2013-08-28 14:38:14 -0700335 private void processQueue() {
336 List<QueueParams> queue = getQueue();
337 for (QueueParams params : queue) {
338 switch (params.mMethod) {
339 case QueueParams.METHOD_INCOMING:
340 onIncoming((Call) params.mArg);
341 break;
342 case QueueParams.METHOD_UPDATE:
343 onUpdate((List<Call>) params.mArg);
344 break;
345 case QueueParams.METHOD_DISCONNECT:
346 onDisconnect((Call) params.mArg);
347 break;
348 default:
349 throw new IllegalArgumentException("Method type " + params.mMethod +
350 " not recognized.");
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700351 }
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700352 }
Chiao Chengd38eebc2013-08-28 14:38:14 -0700353 mQueue.clear();
354 mQueue = null;
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700355 }
356
Chiao Chengd38eebc2013-08-28 14:38:14 -0700357 /**
358 * Holds method parameters.
359 */
360 private static class QueueParams {
361 private static final int METHOD_INCOMING = 1;
362 private static final int METHOD_UPDATE = 2;
363 private static final int METHOD_DISCONNECT = 3;
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700364
Chiao Chengd38eebc2013-08-28 14:38:14 -0700365 private final int mMethod;
366 private final Object mArg;
367
368 private QueueParams(int method, Object arg) {
369 mMethod = method;
370 this.mArg = arg;
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700371 }
372 }
Santos Cordon89647a62013-07-16 13:38:09 -0700373}