blob: 38373d7703c271441969c8222d2544062e52c99d [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;
Chiao Cheng11a4b652013-09-02 01:08:19 -070023import android.content.pm.PackageManager;
24import android.content.pm.ResolveInfo;
Santos Cordon89647a62013-07-16 13:38:09 -070025import android.os.Handler;
26import android.os.IBinder;
27import android.os.Message;
28import android.os.RemoteException;
29import android.os.SystemProperties;
30import android.util.Log;
31
Santos Cordon9b7bac72013-08-06 08:04:52 -070032import com.android.phone.AudioRouter.AudioModeListener;
33import com.android.services.telephony.common.AudioMode;
Santos Cordonf4046882013-07-25 18:49:27 -070034import com.android.services.telephony.common.Call;
Santos Cordon345350e2013-07-19 17:16:14 -070035import com.android.services.telephony.common.ICallHandlerService;
Chiao Cheng6c6b2722013-08-22 18:35:54 -070036import com.google.common.collect.Lists;
Santos Cordon89647a62013-07-16 13:38:09 -070037
Santos Cordona3d05142013-07-29 11:25:17 -070038import java.util.List;
39
Santos Cordon89647a62013-07-16 13:38:09 -070040/**
Santos Cordon345350e2013-07-19 17:16:14 -070041 * This class is responsible for passing through call state changes to the CallHandlerService.
Santos Cordon89647a62013-07-16 13:38:09 -070042 */
Chiao Cheng6c6b2722013-08-22 18:35:54 -070043public class CallHandlerServiceProxy extends Handler
44 implements CallModeler.Listener, AudioModeListener {
Santos Cordon89647a62013-07-16 13:38:09 -070045
Santos Cordon345350e2013-07-19 17:16:14 -070046 private static final String TAG = CallHandlerServiceProxy.class.getSimpleName();
Chiao Cheng6c6b2722013-08-22 18:35:54 -070047 private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt(
48 "ro.debuggable", 0) == 1);
Chiao Chenge41661c2013-07-23 13:28:26 -070049
Chiao Cheng6c6b2722013-08-22 18:35:54 -070050 public static final int RETRY_DELAY_MILLIS = 2000;
51 private static final int BIND_RETRY_MSG = 1;
52 private static final int MAX_RETRY_COUNT = 5;
Santos Cordon89647a62013-07-16 13:38:09 -070053
Santos Cordon593ab382013-08-06 21:58:23 -070054 private AudioRouter mAudioRouter;
Santos Cordoncba1b442013-07-18 12:43:58 -070055 private CallCommandService mCallCommandService;
Santos Cordon593ab382013-08-06 21:58:23 -070056 private CallModeler mCallModeler;
57 private Context mContext;
Chiao Chengd38eebc2013-08-28 14:38:14 -070058
Chiao Cheng6c6b2722013-08-22 18:35:54 -070059 private ICallHandlerService mCallHandlerServiceGuarded; // Guarded by mServiceAndQueueLock
Chiao Chengd38eebc2013-08-28 14:38:14 -070060 // Single queue to guarantee ordering
61 private List<QueueParams> mQueue; // Guarded by mServiceAndQueueLock
62
Chiao Cheng6c6b2722013-08-22 18:35:54 -070063 private final Object mServiceAndQueueLock = new Object();
64 private int mBindRetryCount = 0;
65
66 @Override
67 public void handleMessage(Message msg) {
68 super.handleMessage(msg);
69
70 switch (msg.what) {
71 case BIND_RETRY_MSG:
72 setupServiceConnection();
73 break;
74 }
75 }
Santos Cordon89647a62013-07-16 13:38:09 -070076
Santos Cordon63a84242013-07-23 13:32:52 -070077 public CallHandlerServiceProxy(Context context, CallModeler callModeler,
Santos Cordon593ab382013-08-06 21:58:23 -070078 CallCommandService callCommandService, AudioRouter audioRouter) {
Chiao Cheng6c6b2722013-08-22 18:35:54 -070079 if (DBG) {
80 Log.d(TAG, "init CallHandlerServiceProxy");
81 }
Santos Cordon89647a62013-07-16 13:38:09 -070082 mContext = context;
Santos Cordoncba1b442013-07-18 12:43:58 -070083 mCallCommandService = callCommandService;
Santos Cordon63a84242013-07-23 13:32:52 -070084 mCallModeler = callModeler;
Santos Cordon593ab382013-08-06 21:58:23 -070085 mAudioRouter = audioRouter;
Santos Cordon89647a62013-07-16 13:38:09 -070086
Santos Cordon593ab382013-08-06 21:58:23 -070087 mAudioRouter.addAudioModeListener(this);
Christine Chendaf7bf62013-08-05 19:12:31 -070088 mCallModeler.addListener(this);
Chiao Cheng6c6b2722013-08-22 18:35:54 -070089
Yorke Lee576472d2013-08-29 11:16:35 -070090 if (PhoneGlobals.sVoiceCapable) {
91 setupServiceConnection();
92 }
Santos Cordon63a84242013-07-23 13:32:52 -070093 }
Santos Cordon89647a62013-07-16 13:38:09 -070094
Santos Cordon63a84242013-07-23 13:32:52 -070095 @Override
Santos Cordon995c8162013-07-29 09:22:22 -070096 public void onDisconnect(Call call) {
Chiao Cheng6c6b2722013-08-22 18:35:54 -070097 try {
98 synchronized (mServiceAndQueueLock) {
99 if (mCallHandlerServiceGuarded == null) {
100 if (DBG) {
101 Log.d(TAG, "CallHandlerService not connected. Enqueue disconnect");
102 }
103 enqueueDisconnect(call);
104 return;
105 }
Santos Cordon63a84242013-07-23 13:32:52 -0700106 }
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700107 if (DBG) {
108 Log.d(TAG, "onDisconnect: " + call);
109 }
110 mCallHandlerServiceGuarded.onDisconnect(call);
111 } catch (Exception e) {
112 Log.e(TAG, "Remote exception handling onDisconnect ", e);
Santos Cordon89647a62013-07-16 13:38:09 -0700113 }
114 }
115
Santos Cordona3d05142013-07-29 11:25:17 -0700116 @Override
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700117 public void onIncoming(Call call) {
118 try {
119 synchronized (mServiceAndQueueLock) {
120 if (mCallHandlerServiceGuarded == null) {
121 if (DBG) {
122 Log.d(TAG, "CallHandlerService not connected. Enqueue incoming.");
123 }
124 enqueueIncoming(call);
125 return;
126 }
Christine Chenee09a492013-08-06 16:02:29 -0700127 }
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700128 if (DBG) {
129 Log.d(TAG, "onIncoming: " + call);
130 }
131 // TODO(klp): check RespondViaSmsManager.allowRespondViaSmsForCall()
132 // must refactor call method to accept proper call object.
133 mCallHandlerServiceGuarded.onIncoming(call,
134 RejectWithTextMessageManager.loadCannedResponses());
135 } catch (Exception e) {
136 Log.e(TAG, "Remote exception handling onUpdate", e);
Christine Chenee09a492013-08-06 16:02:29 -0700137 }
138 }
139
140 @Override
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700141 public void onUpdate(List<Call> calls) {
142 try {
143 synchronized (mServiceAndQueueLock) {
144 if (mCallHandlerServiceGuarded == null) {
145 if (DBG) {
146 Log.d(TAG, "CallHandlerService not connected. Enqueue update.");
147 }
148 enqueueUpdate(calls);
149 return;
150 }
Santos Cordona3d05142013-07-29 11:25:17 -0700151 }
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700152
153 if (DBG) {
154 Log.d(TAG, "onUpdate: " + calls.toString());
155 }
156 mCallHandlerServiceGuarded.onUpdate(calls);
157 } catch (Exception e) {
158 Log.e(TAG, "Remote exception handling onUpdate", e);
Santos Cordona3d05142013-07-29 11:25:17 -0700159 }
160 }
161
Santos Cordon9b7bac72013-08-06 08:04:52 -0700162 @Override
Santos Cordoncd95f622013-08-29 03:38:52 -0700163 public void onAudioModeChange(int newMode, boolean muted) {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700164 try {
165 synchronized (mServiceAndQueueLock) {
166 // TODO(klp): does this need to be enqueued?
167 if (mCallHandlerServiceGuarded == null) {
168 if (DBG) {
169 Log.d(TAG, "CallHandlerService not conneccted. Skipping "
170 + "onAudioModeChange().");
171 }
172 return;
173 }
Santos Cordon9b7bac72013-08-06 08:04:52 -0700174 }
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700175
176 // Just do a simple log for now.
177 Log.i(TAG, "Updating with new audio mode: " + AudioMode.toString(newMode) +
Santos Cordoncd95f622013-08-29 03:38:52 -0700178 " with mute " + muted);
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700179
180 if (DBG) {
181 Log.d(TAG, "onSupportAudioModeChange");
182 }
183
Santos Cordoncd95f622013-08-29 03:38:52 -0700184 mCallHandlerServiceGuarded.onAudioModeChange(newMode, muted);
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700185 } catch (Exception e) {
186 Log.e(TAG, "Remote exception handling onAudioModeChange", e);
Santos Cordon9b7bac72013-08-06 08:04:52 -0700187 }
188 }
189
Santos Cordon593ab382013-08-06 21:58:23 -0700190 @Override
191 public void onSupportedAudioModeChange(int modeMask) {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700192 try {
193 synchronized (mServiceAndQueueLock) {
194 // TODO(klp): does this need to be enqueued?
195 if (mCallHandlerServiceGuarded == null) {
196 if (DBG) {
197 Log.d(TAG, "CallHandlerService not conneccted. Skipping"
198 + "onSupportedAudioModeChange().");
199 }
200 return;
201 }
202 }
Santos Cordon593ab382013-08-06 21:58:23 -0700203
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700204 if (DBG) {
205 Log.d(TAG, "onSupportAudioModeChange: " + AudioMode.toString(modeMask));
206 }
207
208 mCallHandlerServiceGuarded.onSupportedAudioModeChange(modeMask);
209 } catch (Exception e) {
210 Log.e(TAG, "Remote exception handling onAudioModeChange", e);
211 }
212
213 }
214
215 private ServiceConnection mConnection = new ServiceConnection() {
216 @Override public void onServiceConnected (ComponentName className, IBinder service){
217 if (DBG) {
218 Log.d(TAG, "Service Connected");
219 }
220 onCallHandlerServiceConnected(ICallHandlerService.Stub.asInterface(service));
Chiao Cheng11a4b652013-09-02 01:08:19 -0700221 mBindRetryCount = 0;
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700222 }
223
224 @Override public void onServiceDisconnected (ComponentName className){
225 Log.i(TAG, "Disconnected from UI service.");
226 synchronized (mServiceAndQueueLock) {
227 mCallHandlerServiceGuarded = null;
228
229 // Technically, unbindService is un-necessary since the framework will schedule and
230 // restart the crashed service. But there is a exponential backoff for the restart.
231 // Unbind explicitly and setup again to avoid the backoff since it's important to
232 // always have an in call ui.
233 mContext.unbindService(mConnection);
234 setupServiceConnection();
Santos Cordon593ab382013-08-06 21:58:23 -0700235 }
236 }
Santos Cordon19d814b2013-08-28 14:58:17 -0700237 };
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700238
Santos Cordon406c0342013-08-28 00:07:47 -0700239 public void bringToForeground() {
240 // only support this call if the service is already connected.
Santos Cordon19d814b2013-08-28 14:58:17 -0700241 synchronized (mServiceAndQueueLock) {
242 if (mCallHandlerServiceGuarded != null && mCallModeler.hasLiveCall()) {
243 try {
244 if (DBG) Log.d(TAG, "bringToForeground");
245 mCallHandlerServiceGuarded.bringToForeground();
246 } catch (RemoteException e) {
247 Log.e(TAG, "Exception handling bringToForeground", e);
248 }
Santos Cordon406c0342013-08-28 00:07:47 -0700249 }
250 }
251 }
252
Santos Cordon89647a62013-07-16 13:38:09 -0700253 /**
Santos Cordon345350e2013-07-19 17:16:14 -0700254 * Sets up the connection with ICallHandlerService
Santos Cordon89647a62013-07-16 13:38:09 -0700255 */
256 private void setupServiceConnection() {
Chiao Cheng11a4b652013-09-02 01:08:19 -0700257 final Intent serviceIntent = new Intent(ICallHandlerService.class.getName());
258 final ComponentName component = new ComponentName(mContext.getResources().getString(
259 R.string.incall_ui_default_package), mContext.getResources().getString(
260 R.string.incall_ui_default_class));
261 serviceIntent.setComponent(component);
262
263 if (DBG) {
264 Log.d(TAG, "binding to service " + serviceIntent);
265 }
266
267 final PackageManager packageManger = mContext.getPackageManager();
268 final List<ResolveInfo> services = packageManger.queryIntentServices(serviceIntent, 0);
269 if (services.size() == 0) {
270 // Service not found, retry again after some delay
271 // This can happen if the service is being installed by the package manager. Between
272 // deletes and installs, bindService could get a silent service not found error.
273 mBindRetryCount++;
274 if (mBindRetryCount < MAX_RETRY_COUNT) {
275 Log.w(TAG, "InCallUI service not found. " + serviceIntent + ". This happens if " +
276 "the service is being installed and should be transient. Retrying" +
277 RETRY_DELAY_MILLIS + " ms.");
278 sendMessageDelayed(Message.obtain(this, BIND_RETRY_MSG), RETRY_DELAY_MILLIS);
279 } else {
280 Log.e(TAG, "Tried to bind to in-call UI " + MAX_RETRY_COUNT + " times."
281 + " Giving up.");
282 }
283 return;
284 }
285
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700286 synchronized (mServiceAndQueueLock) {
287 if (mCallHandlerServiceGuarded == null) {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700288 if (!mContext.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE)) {
289 // This happens when the in-call package is in the middle of being installed.
290 // Delay the retry.
291 mBindRetryCount++;
292 if (mBindRetryCount < MAX_RETRY_COUNT) {
293 Log.e(TAG, "bindService failed on " + serviceIntent + ". Retrying in " +
294 RETRY_DELAY_MILLIS + " ms.");
295 sendMessageDelayed(Message.obtain(this, BIND_RETRY_MSG),
296 RETRY_DELAY_MILLIS);
297 } else {
298 Log.wtf(TAG, "Tried to bind to in-call UI " + MAX_RETRY_COUNT + " times."
299 + " Giving up.");
300 }
301 }
Santos Cordonaf763a12013-08-19 20:04:58 -0700302 }
303 }
304 }
305
306 /**
Santos Cordoncba1b442013-07-18 12:43:58 -0700307 * Called when the in-call UI service is connected. Send command interface to in-call.
308 */
Santos Cordon63a84242013-07-23 13:32:52 -0700309 private void onCallHandlerServiceConnected(ICallHandlerService callHandlerService) {
Chiao Chengd38eebc2013-08-28 14:38:14 -0700310
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700311 synchronized (mServiceAndQueueLock) {
312 mCallHandlerServiceGuarded = callHandlerService;
313
Santos Cordonad078192013-08-28 15:14:54 -0700314 // Before we send any updates, we need to set up the initial service calls.
315 makeInitialServiceCalls();
316
Chiao Chengd38eebc2013-08-28 14:38:14 -0700317 processQueue();
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700318 }
Santos Cordonad078192013-08-28 15:14:54 -0700319 }
Santos Cordoncba1b442013-07-18 12:43:58 -0700320
Santos Cordonad078192013-08-28 15:14:54 -0700321 /**
322 * Makes initial service calls to set up callcommandservice and audio modes.
323 */
324 private void makeInitialServiceCalls() {
Santos Cordoncba1b442013-07-18 12:43:58 -0700325 try {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700326 mCallHandlerServiceGuarded.setCallCommandService(mCallCommandService);
Santos Cordonad078192013-08-28 15:14:54 -0700327
328 onSupportedAudioModeChange(mAudioRouter.getSupportedAudioModes());
Santos Cordon8fd0ec72013-08-29 16:44:43 -0700329 onAudioModeChange(mAudioRouter.getAudioMode(), mAudioRouter.getMute());
Santos Cordoncba1b442013-07-18 12:43:58 -0700330 } catch (RemoteException e) {
Santos Cordon63a84242013-07-23 13:32:52 -0700331 Log.e(TAG, "Remote exception calling CallHandlerService::setCallCommandService", e);
Santos Cordon89647a62013-07-16 13:38:09 -0700332 }
333 }
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700334
Chiao Chengd38eebc2013-08-28 14:38:14 -0700335 private List<QueueParams> getQueue() {
336 if (mQueue == null) {
337 mQueue = Lists.newArrayList();
338 }
339 return mQueue;
340 }
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700341
342 private void enqueueDisconnect(Call call) {
Chiao Chengd38eebc2013-08-28 14:38:14 -0700343 getQueue().add(new QueueParams(QueueParams.METHOD_DISCONNECT, new Call(call)));
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700344 }
345
346 private void enqueueIncoming(Call call) {
Chiao Chengd38eebc2013-08-28 14:38:14 -0700347 getQueue().add(new QueueParams(QueueParams.METHOD_INCOMING, new Call(call)));
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700348 }
349
350 private void enqueueUpdate(List<Call> calls) {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700351 final List<Call> copy = Lists.newArrayList();
352 for (Call call : calls) {
353 copy.add(new Call(call));
354 }
Chiao Chengc340ba92013-08-30 13:04:46 -0700355 getQueue().add(new QueueParams(QueueParams.METHOD_UPDATE, copy));
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700356 }
357
Chiao Chengd38eebc2013-08-28 14:38:14 -0700358 private void processQueue() {
359 List<QueueParams> queue = getQueue();
360 for (QueueParams params : queue) {
361 switch (params.mMethod) {
362 case QueueParams.METHOD_INCOMING:
363 onIncoming((Call) params.mArg);
364 break;
365 case QueueParams.METHOD_UPDATE:
366 onUpdate((List<Call>) params.mArg);
367 break;
368 case QueueParams.METHOD_DISCONNECT:
369 onDisconnect((Call) params.mArg);
370 break;
371 default:
372 throw new IllegalArgumentException("Method type " + params.mMethod +
373 " not recognized.");
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700374 }
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700375 }
Chiao Chengd38eebc2013-08-28 14:38:14 -0700376 mQueue.clear();
377 mQueue = null;
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700378 }
379
Chiao Chengd38eebc2013-08-28 14:38:14 -0700380 /**
381 * Holds method parameters.
382 */
383 private static class QueueParams {
384 private static final int METHOD_INCOMING = 1;
385 private static final int METHOD_UPDATE = 2;
386 private static final int METHOD_DISCONNECT = 3;
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700387
Chiao Chengd38eebc2013-08-28 14:38:14 -0700388 private final int mMethod;
389 private final Object mArg;
390
391 private QueueParams(int method, Object arg) {
392 mMethod = method;
393 this.mArg = arg;
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700394 }
395 }
Santos Cordon89647a62013-07-16 13:38:09 -0700396}