blob: f5b6116e32e72d2bfbf5e7849fee2905e2e94665 [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
Yorke Lee79840002013-09-16 14:30:56 -070019import android.Manifest;
Santos Cordon89647a62013-07-16 13:38:09 -070020import android.content.ComponentName;
21import android.content.Context;
22import android.content.Intent;
23import android.content.ServiceConnection;
Chiao Cheng11a4b652013-09-02 01:08:19 -070024import android.content.pm.PackageManager;
25import android.content.pm.ResolveInfo;
Yorke Lee79840002013-09-16 14:30:56 -070026import android.content.pm.ServiceInfo;
Santos Cordon89647a62013-07-16 13:38:09 -070027import android.os.Handler;
28import android.os.IBinder;
29import android.os.Message;
Chiao Cheng26c6e922013-09-14 16:45:38 -070030import android.os.PowerManager;
Santos Cordon89647a62013-07-16 13:38:09 -070031import android.os.RemoteException;
Chiao Cheng26c6e922013-09-14 16:45:38 -070032import android.os.SystemClock;
Santos Cordon89647a62013-07-16 13:38:09 -070033import android.os.SystemProperties;
Yorke Leede41f672013-09-19 13:46:55 -070034import android.text.TextUtils;
Santos Cordon89647a62013-07-16 13:38:09 -070035import android.util.Log;
36
Yorke Leede41f672013-09-19 13:46:55 -070037import com.android.internal.telephony.Connection;
38import com.android.internal.telephony.Connection.PostDialState;
Santos Cordon9b7bac72013-08-06 08:04:52 -070039import com.android.phone.AudioRouter.AudioModeListener;
Yorke Lee362cec22013-09-18 15:20:26 -070040import com.android.phone.NotificationMgr.StatusBarHelper;
Santos Cordon9b7bac72013-08-06 08:04:52 -070041import com.android.services.telephony.common.AudioMode;
Santos Cordonf4046882013-07-25 18:49:27 -070042import com.android.services.telephony.common.Call;
Santos Cordon345350e2013-07-19 17:16:14 -070043import com.android.services.telephony.common.ICallHandlerService;
Chiao Cheng6c6b2722013-08-22 18:35:54 -070044import com.google.common.collect.Lists;
Santos Cordon89647a62013-07-16 13:38:09 -070045
Santos Cordona3d05142013-07-29 11:25:17 -070046import java.util.List;
47
Santos Cordon89647a62013-07-16 13:38:09 -070048/**
Santos Cordon345350e2013-07-19 17:16:14 -070049 * This class is responsible for passing through call state changes to the CallHandlerService.
Santos Cordon89647a62013-07-16 13:38:09 -070050 */
Chiao Cheng6c6b2722013-08-22 18:35:54 -070051public class CallHandlerServiceProxy extends Handler
52 implements CallModeler.Listener, AudioModeListener {
Santos Cordon89647a62013-07-16 13:38:09 -070053
Santos Cordon345350e2013-07-19 17:16:14 -070054 private static final String TAG = CallHandlerServiceProxy.class.getSimpleName();
Santos Cordon71d5c6e2013-09-05 21:34:33 -070055 private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt(
Chiao Cheng6c6b2722013-08-22 18:35:54 -070056 "ro.debuggable", 0) == 1);
Chiao Chenge41661c2013-07-23 13:28:26 -070057
Chiao Cheng6c6b2722013-08-22 18:35:54 -070058 public static final int RETRY_DELAY_MILLIS = 2000;
Santos Cordon70d38662013-10-21 17:12:23 -070059 public static final int RETRY_DELAY_LONG_MILLIS = 30 * 1000; // 30 seconds
Chiao Cheng6c6b2722013-08-22 18:35:54 -070060 private static final int BIND_RETRY_MSG = 1;
nfjb73439a24d2013-12-13 11:41:25 -060061 private static final int BIND_TIME_OUT = 2;
Santos Cordon70d38662013-10-21 17:12:23 -070062 private static final int MAX_SHORT_DELAY_RETRY_COUNT = 5;
Santos Cordon89647a62013-07-16 13:38:09 -070063
Santos Cordon593ab382013-08-06 21:58:23 -070064 private AudioRouter mAudioRouter;
Santos Cordoncba1b442013-07-18 12:43:58 -070065 private CallCommandService mCallCommandService;
Santos Cordon593ab382013-08-06 21:58:23 -070066 private CallModeler mCallModeler;
67 private Context mContext;
Santos Cordonf9ad0642013-09-26 10:48:07 -070068 private boolean mFullUpdateOnConnect;
Chiao Chengd38eebc2013-08-28 14:38:14 -070069
Chiao Cheng6c6b2722013-08-22 18:35:54 -070070 private ICallHandlerService mCallHandlerServiceGuarded; // Guarded by mServiceAndQueueLock
Chiao Chengd38eebc2013-08-28 14:38:14 -070071 // Single queue to guarantee ordering
72 private List<QueueParams> mQueue; // Guarded by mServiceAndQueueLock
73
Chiao Cheng6c6b2722013-08-22 18:35:54 -070074 private final Object mServiceAndQueueLock = new Object();
75 private int mBindRetryCount = 0;
76
77 @Override
78 public void handleMessage(Message msg) {
79 super.handleMessage(msg);
80
81 switch (msg.what) {
82 case BIND_RETRY_MSG:
nfjb73439a24d2013-12-13 11:41:25 -060083 // Remove any pending messages since we're already performing the action.
84 // If the call to setupServiceConnection() fails, it will queue up another retry.
85 removeMessages(BIND_RETRY_MSG);
Santos Cordon70d38662013-10-21 17:12:23 -070086 handleConnectRetry();
Chiao Cheng6c6b2722013-08-22 18:35:54 -070087 break;
nfjb73439a24d2013-12-13 11:41:25 -060088 case BIND_TIME_OUT:
89 // Remove any pending messages since we're already performing the action.
90 // If the call to setupServiceConnection() fails, it will queue up another retry.
91 removeMessages(BIND_TIME_OUT);
92 synchronized (mServiceAndQueueLock) {
93 if(mCallHandlerServiceGuarded == null) {
94 Log.w(TAG, "Binding time out. InCallUI did not respond in time.");
95 try {
96 mContext.unbindService(mConnection);
97 } catch(Exception e) {
98 Log.w(TAG, "unbindservice exception", e);
99 }
100 mConnection = null;
101 handleConnectRetry();
102 }
103 }
104 break;
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700105 }
106 }
Santos Cordon89647a62013-07-16 13:38:09 -0700107
Santos Cordon63a84242013-07-23 13:32:52 -0700108 public CallHandlerServiceProxy(Context context, CallModeler callModeler,
Santos Cordon593ab382013-08-06 21:58:23 -0700109 CallCommandService callCommandService, AudioRouter audioRouter) {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700110 if (DBG) {
111 Log.d(TAG, "init CallHandlerServiceProxy");
112 }
Santos Cordon89647a62013-07-16 13:38:09 -0700113 mContext = context;
Santos Cordoncba1b442013-07-18 12:43:58 -0700114 mCallCommandService = callCommandService;
Santos Cordon63a84242013-07-23 13:32:52 -0700115 mCallModeler = callModeler;
Santos Cordon593ab382013-08-06 21:58:23 -0700116 mAudioRouter = audioRouter;
Santos Cordon89647a62013-07-16 13:38:09 -0700117
Santos Cordon593ab382013-08-06 21:58:23 -0700118 mAudioRouter.addAudioModeListener(this);
Christine Chendaf7bf62013-08-05 19:12:31 -0700119 mCallModeler.addListener(this);
Santos Cordon63a84242013-07-23 13:32:52 -0700120 }
Santos Cordon89647a62013-07-16 13:38:09 -0700121
Santos Cordon63a84242013-07-23 13:32:52 -0700122 @Override
Santos Cordon995c8162013-07-29 09:22:22 -0700123 public void onDisconnect(Call call) {
Chiao Cheng26c6e922013-09-14 16:45:38 -0700124 // Wake up in case the screen was off.
125 wakeUpScreen();
Chiao Cheng3e6486e2013-09-03 19:27:46 -0700126 synchronized (mServiceAndQueueLock) {
127 if (mCallHandlerServiceGuarded == null) {
128 if (DBG) {
129 Log.d(TAG, "CallHandlerService not connected. Enqueue disconnect");
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700130 }
Chiao Cheng3e6486e2013-09-03 19:27:46 -0700131 enqueueDisconnect(call);
132 setupServiceConnection();
133 return;
Santos Cordon63a84242013-07-23 13:32:52 -0700134 }
Chiao Cheng3e6486e2013-09-03 19:27:46 -0700135 }
136 processDisconnect(call);
137 }
138
Chiao Cheng26c6e922013-09-14 16:45:38 -0700139 private void wakeUpScreen() {
140 Log.d(TAG, "wakeUpScreen()");
141 final PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
142 pm.wakeUp(SystemClock.uptimeMillis());
143 }
144
Chiao Cheng3e6486e2013-09-03 19:27:46 -0700145 private void processDisconnect(Call call) {
146 try {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700147 if (DBG) {
148 Log.d(TAG, "onDisconnect: " + call);
149 }
Chiao Cheng3e6486e2013-09-03 19:27:46 -0700150 synchronized (mServiceAndQueueLock) {
151 if (mCallHandlerServiceGuarded != null) {
152 mCallHandlerServiceGuarded.onDisconnect(call);
153 }
154 }
155 if (!mCallModeler.hasLiveCall()) {
156 unbind();
157 }
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700158 } catch (Exception e) {
159 Log.e(TAG, "Remote exception handling onDisconnect ", e);
Santos Cordon89647a62013-07-16 13:38:09 -0700160 }
161 }
162
Santos Cordona3d05142013-07-29 11:25:17 -0700163 @Override
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700164 public void onIncoming(Call call) {
Santos Cordon70d38662013-10-21 17:12:23 -0700165 // for new incoming calls, reset the retry count.
166 resetConnectRetryCount();
167
Chiao Cheng3e6486e2013-09-03 19:27:46 -0700168 synchronized (mServiceAndQueueLock) {
169 if (mCallHandlerServiceGuarded == null) {
170 if (DBG) {
171 Log.d(TAG, "CallHandlerService not connected. Enqueue incoming.");
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700172 }
Chiao Cheng3e6486e2013-09-03 19:27:46 -0700173 enqueueIncoming(call);
174 setupServiceConnection();
175 return;
Christine Chenee09a492013-08-06 16:02:29 -0700176 }
Chiao Cheng3e6486e2013-09-03 19:27:46 -0700177 }
178 processIncoming(call);
179 }
180
181 private void processIncoming(Call call) {
182 if (DBG) {
183 Log.d(TAG, "onIncoming: " + call);
184 }
185 try {
Christine Chen3e0f0412013-09-18 20:33:49 -0700186 // TODO: check RespondViaSmsManager.allowRespondViaSmsForCall()
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700187 // must refactor call method to accept proper call object.
Chiao Cheng3e6486e2013-09-03 19:27:46 -0700188 synchronized (mServiceAndQueueLock) {
189 if (mCallHandlerServiceGuarded != null) {
190 mCallHandlerServiceGuarded.onIncoming(call,
191 RejectWithTextMessageManager.loadCannedResponses());
192 }
193 }
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700194 } catch (Exception e) {
195 Log.e(TAG, "Remote exception handling onUpdate", e);
Christine Chenee09a492013-08-06 16:02:29 -0700196 }
197 }
198
199 @Override
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700200 public void onUpdate(List<Call> calls) {
Chiao Cheng3e6486e2013-09-03 19:27:46 -0700201 synchronized (mServiceAndQueueLock) {
202 if (mCallHandlerServiceGuarded == null) {
203 if (DBG) {
204 Log.d(TAG, "CallHandlerService not connected. Enqueue update.");
205 }
206 enqueueUpdate(calls);
207 setupServiceConnection();
208 return;
209 }
210 }
211 processUpdate(calls);
212 }
213
214 private void processUpdate(List<Call> calls) {
215 if (DBG) {
216 Log.d(TAG, "onUpdate: " + calls.toString());
217 }
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700218 try {
219 synchronized (mServiceAndQueueLock) {
Chiao Cheng3e6486e2013-09-03 19:27:46 -0700220 if (mCallHandlerServiceGuarded != null) {
221 mCallHandlerServiceGuarded.onUpdate(calls);
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700222 }
Santos Cordona3d05142013-07-29 11:25:17 -0700223 }
Chiao Cheng3e6486e2013-09-03 19:27:46 -0700224 if (!mCallModeler.hasLiveCall()) {
225 // TODO: unbinding happens in both onUpdate and onDisconnect because the ordering
226 // is not deterministic. Unbinding in both ensures that the service is unbound.
227 // But it also makes this in-efficient because we are unbinding twice, which leads
228 // to the CallHandlerService performing onCreate() and onDestroy() twice for each
229 // disconnect.
230 unbind();
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700231 }
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700232 } catch (Exception e) {
233 Log.e(TAG, "Remote exception handling onUpdate", e);
Santos Cordona3d05142013-07-29 11:25:17 -0700234 }
235 }
236
Chiao Cheng3f015c92013-09-06 15:56:27 -0700237
238 @Override
Yorke Leede41f672013-09-19 13:46:55 -0700239 public void onPostDialAction(Connection.PostDialState state, int callId, String remainingChars,
240 char currentChar) {
241 if (state != PostDialState.WAIT) return;
Chiao Cheng3f015c92013-09-06 15:56:27 -0700242 try {
243 synchronized (mServiceAndQueueLock) {
244 if (mCallHandlerServiceGuarded == null) {
245 if (DBG) {
246 Log.d(TAG, "CallHandlerService not conneccted. Skipping "
247 + "onPostDialWait().");
248 }
249 return;
250 }
251 }
252
253 mCallHandlerServiceGuarded.onPostDialWait(callId, remainingChars);
254 } catch (Exception e) {
255 Log.e(TAG, "Remote exception handling onUpdate", e);
256 }
257 }
258
Santos Cordon9b7bac72013-08-06 08:04:52 -0700259 @Override
Santos Cordoncd95f622013-08-29 03:38:52 -0700260 public void onAudioModeChange(int newMode, boolean muted) {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700261 try {
262 synchronized (mServiceAndQueueLock) {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700263 if (mCallHandlerServiceGuarded == null) {
264 if (DBG) {
265 Log.d(TAG, "CallHandlerService not conneccted. Skipping "
266 + "onAudioModeChange().");
267 }
268 return;
269 }
Santos Cordon9b7bac72013-08-06 08:04:52 -0700270 }
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700271
272 // Just do a simple log for now.
273 Log.i(TAG, "Updating with new audio mode: " + AudioMode.toString(newMode) +
Santos Cordoncd95f622013-08-29 03:38:52 -0700274 " with mute " + muted);
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700275
Santos Cordoncd95f622013-08-29 03:38:52 -0700276 mCallHandlerServiceGuarded.onAudioModeChange(newMode, muted);
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700277 } catch (Exception e) {
278 Log.e(TAG, "Remote exception handling onAudioModeChange", e);
Santos Cordon9b7bac72013-08-06 08:04:52 -0700279 }
280 }
281
Santos Cordon593ab382013-08-06 21:58:23 -0700282 @Override
283 public void onSupportedAudioModeChange(int modeMask) {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700284 try {
285 synchronized (mServiceAndQueueLock) {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700286 if (mCallHandlerServiceGuarded == null) {
287 if (DBG) {
288 Log.d(TAG, "CallHandlerService not conneccted. Skipping"
289 + "onSupportedAudioModeChange().");
290 }
291 return;
292 }
293 }
Santos Cordon593ab382013-08-06 21:58:23 -0700294
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700295 if (DBG) {
296 Log.d(TAG, "onSupportAudioModeChange: " + AudioMode.toString(modeMask));
297 }
298
299 mCallHandlerServiceGuarded.onSupportedAudioModeChange(modeMask);
300 } catch (Exception e) {
301 Log.e(TAG, "Remote exception handling onAudioModeChange", e);
302 }
303
304 }
305
Chiao Cheng3e6486e2013-09-03 19:27:46 -0700306 private ServiceConnection mConnection = null;
307
308 private class InCallServiceConnection implements ServiceConnection {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700309 @Override public void onServiceConnected (ComponentName className, IBinder service){
310 if (DBG) {
311 Log.d(TAG, "Service Connected");
312 }
313 onCallHandlerServiceConnected(ICallHandlerService.Stub.asInterface(service));
nfjb73439a24d2013-12-13 11:41:25 -0600314 removeMessages(BIND_TIME_OUT);
315 if (DBG) {
316 Log.d(TAG, "Service Connected. Cancel timer");
317 }
Santos Cordon70d38662013-10-21 17:12:23 -0700318 resetConnectRetryCount();
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700319 }
320
321 @Override public void onServiceDisconnected (ComponentName className){
322 Log.i(TAG, "Disconnected from UI service.");
323 synchronized (mServiceAndQueueLock) {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700324 // Technically, unbindService is un-necessary since the framework will schedule and
325 // restart the crashed service. But there is a exponential backoff for the restart.
326 // Unbind explicitly and setup again to avoid the backoff since it's important to
327 // always have an in call ui.
Chiao Cheng3e6486e2013-09-03 19:27:46 -0700328 unbind();
329
Santos Cordonf9ad0642013-09-26 10:48:07 -0700330 reconnectOnRemainingCalls();
Santos Cordon593ab382013-08-06 21:58:23 -0700331 }
332 }
Chiao Cheng3e6486e2013-09-03 19:27:46 -0700333 }
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700334
Makoto Onukibcf20992013-09-12 17:59:30 -0700335 public void bringToForeground(boolean showDialpad) {
Santos Cordon406c0342013-08-28 00:07:47 -0700336 // only support this call if the service is already connected.
Santos Cordon19d814b2013-08-28 14:58:17 -0700337 synchronized (mServiceAndQueueLock) {
338 if (mCallHandlerServiceGuarded != null && mCallModeler.hasLiveCall()) {
339 try {
Makoto Onukibcf20992013-09-12 17:59:30 -0700340 if (DBG) Log.d(TAG, "bringToForeground: " + showDialpad);
341 mCallHandlerServiceGuarded.bringToForeground(showDialpad);
Santos Cordon19d814b2013-08-28 14:58:17 -0700342 } catch (RemoteException e) {
343 Log.e(TAG, "Exception handling bringToForeground", e);
344 }
Santos Cordon406c0342013-08-28 00:07:47 -0700345 }
346 }
347 }
348
Chiao Cheng3e6486e2013-09-03 19:27:46 -0700349 private static Intent getInCallServiceIntent(Context context) {
350 final Intent serviceIntent = new Intent(ICallHandlerService.class.getName());
351 final ComponentName component = new ComponentName(context.getResources().getString(
Yorke Leed3105fe2013-09-25 12:44:45 -0700352 R.string.ui_default_package), context.getResources().getString(
353 R.string.incall_default_class));
Chiao Cheng3e6486e2013-09-03 19:27:46 -0700354 serviceIntent.setComponent(component);
355 return serviceIntent;
356 }
357
Santos Cordon89647a62013-07-16 13:38:09 -0700358 /**
Santos Cordon345350e2013-07-19 17:16:14 -0700359 * Sets up the connection with ICallHandlerService
Santos Cordon89647a62013-07-16 13:38:09 -0700360 */
361 private void setupServiceConnection() {
Chiao Cheng3e6486e2013-09-03 19:27:46 -0700362 if (!PhoneGlobals.sVoiceCapable) {
363 return;
364 }
Chiao Cheng11a4b652013-09-02 01:08:19 -0700365
Chiao Cheng3e6486e2013-09-03 19:27:46 -0700366 final Intent serviceIntent = getInCallServiceIntent(mContext);
Chiao Cheng11a4b652013-09-02 01:08:19 -0700367 if (DBG) {
368 Log.d(TAG, "binding to service " + serviceIntent);
369 }
370
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700371 synchronized (mServiceAndQueueLock) {
Chiao Cheng3e6486e2013-09-03 19:27:46 -0700372 if (mConnection == null) {
373 mConnection = new InCallServiceConnection();
374
Santos Cordon70d38662013-10-21 17:12:23 -0700375 boolean failedConnection = false;
Chiao Cheng3e6486e2013-09-03 19:27:46 -0700376 final PackageManager packageManger = mContext.getPackageManager();
377 final List<ResolveInfo> services = packageManger.queryIntentServices(serviceIntent,
378 0);
Yorke Lee79840002013-09-16 14:30:56 -0700379
380 ServiceInfo serviceInfo = null;
381
382 for (int i = 0; i < services.size(); i++) {
383 final ResolveInfo info = services.get(i);
384 if (info.serviceInfo != null) {
385 if (Manifest.permission.BIND_CALL_SERVICE.equals(
386 info.serviceInfo.permission)) {
387 serviceInfo = info.serviceInfo;
388 break;
389 }
390 }
391 }
392
393 if (serviceInfo == null) {
Chiao Cheng3e6486e2013-09-03 19:27:46 -0700394 // Service not found, retry again after some delay
395 // This can happen if the service is being installed by the package manager.
396 // Between deletes and installs, bindService could get a silent service not
397 // found error.
Santos Cordon70d38662013-10-21 17:12:23 -0700398 Log.w(TAG, "Default call handler service not found.");
399 failedConnection = true;
400 } else {
401
402 serviceIntent.setComponent(new ComponentName(serviceInfo.packageName,
403 serviceInfo.name));
404 if (DBG) {
405 Log.d(TAG, "binding to service " + serviceIntent);
Chiao Cheng3e6486e2013-09-03 19:27:46 -0700406 }
Santos Cordon70d38662013-10-21 17:12:23 -0700407 if (!mContext.bindService(serviceIntent, mConnection,
408 Context.BIND_AUTO_CREATE)) {
409 // This happens when the in-call package is in the middle of being installed
410 Log.w(TAG, "Could not bind to default call handler service: " +
411 serviceIntent.getComponent());
412 failedConnection = true;
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700413 }
414 }
Chiao Cheng3e6486e2013-09-03 19:27:46 -0700415
Santos Cordon70d38662013-10-21 17:12:23 -0700416 if (failedConnection) {
417 mConnection = null;
nfjb73439a24d2013-12-13 11:41:25 -0600418 enqueueConnectRetry(BIND_RETRY_MSG);
419 } else {
420 enqueueConnectRetry(BIND_TIME_OUT);
Santos Cordon70d38662013-10-21 17:12:23 -0700421 }
Chiao Cheng3e6486e2013-09-03 19:27:46 -0700422 } else {
423 Log.d(TAG, "Service connection to in call service already started.");
Santos Cordonaf763a12013-08-19 20:04:58 -0700424 }
425 }
426 }
427
Santos Cordon70d38662013-10-21 17:12:23 -0700428 private void resetConnectRetryCount() {
429 mBindRetryCount = 0;
430 }
431
432 private void incrementRetryCount() {
433 // Reset to the short delay retry count to avoid overflow
434 if (Integer.MAX_VALUE == mBindRetryCount) {
435 mBindRetryCount = MAX_SHORT_DELAY_RETRY_COUNT;
436 }
437
438 mBindRetryCount++;
439 }
440
441 private void handleConnectRetry() {
Santos Cordon70d38662013-10-21 17:12:23 -0700442 // Something else triggered the connection, cancel.
443 if (mConnection != null) {
444 Log.i(TAG, "Retry: already connected.");
445 return;
446 }
447
448 if (mCallModeler.hasLiveCall()) {
449 // Update the count when we are actually trying the retry instead of when the
450 // retry is queued up.
451 incrementRetryCount();
452
453 Log.i(TAG, "Retrying connection: " + mBindRetryCount);
454 setupServiceConnection();
455 } else {
456 Log.i(TAG, "Canceling connection retry since there are no calls.");
457 // We are not currently connected and there is no call so lets not bother
458 // with the retry. Also, empty the queue of pending messages to send
459 // to the UI.
460 synchronized (mServiceAndQueueLock) {
461 if (mQueue != null) {
462 mQueue.clear();
463 }
464 }
465
466 // Since we have no calls, reset retry count.
467 resetConnectRetryCount();
468 }
469 }
470
471 /**
472 * Called after the connection failed and a retry is needed.
473 * Queues up a retry to happen with a delay.
474 */
nfjb73439a24d2013-12-13 11:41:25 -0600475 private void enqueueConnectRetry(int msg) {
Santos Cordon70d38662013-10-21 17:12:23 -0700476 final boolean isLongDelay = (mBindRetryCount > MAX_SHORT_DELAY_RETRY_COUNT);
477 final int delay = isLongDelay ? RETRY_DELAY_LONG_MILLIS : RETRY_DELAY_MILLIS;
478
479 Log.w(TAG, "InCallUI Connection failed. Enqueuing delayed retry for " + delay + " ms." +
480 " retries(" + mBindRetryCount + ")");
481
nfjb73439a24d2013-12-13 11:41:25 -0600482 sendEmptyMessageDelayed(msg, delay);
Santos Cordon70d38662013-10-21 17:12:23 -0700483 }
484
Chiao Cheng3e6486e2013-09-03 19:27:46 -0700485 private void unbind() {
486 synchronized (mServiceAndQueueLock) {
Yorke Lee362cec22013-09-18 15:20:26 -0700487 // On unbind, reenable the notification shade and navigation bar just in case the
488 // in-call UI crashed on an incoming call.
489 final StatusBarHelper statusBarHelper = PhoneGlobals.getInstance().notificationMgr.
490 statusBarHelper;
491 statusBarHelper.enableSystemBarNavigation(true);
492 statusBarHelper.enableExpandedView(true);
Chiao Cheng3e6486e2013-09-03 19:27:46 -0700493 if (mCallHandlerServiceGuarded != null) {
494 Log.d(TAG, "Unbinding service.");
495 mCallHandlerServiceGuarded = null;
496 mContext.unbindService(mConnection);
497 }
498 mConnection = null;
499 }
500 }
501
Santos Cordonaf763a12013-08-19 20:04:58 -0700502 /**
Santos Cordoncba1b442013-07-18 12:43:58 -0700503 * Called when the in-call UI service is connected. Send command interface to in-call.
504 */
Santos Cordon63a84242013-07-23 13:32:52 -0700505 private void onCallHandlerServiceConnected(ICallHandlerService callHandlerService) {
Chiao Chengd38eebc2013-08-28 14:38:14 -0700506
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700507 synchronized (mServiceAndQueueLock) {
508 mCallHandlerServiceGuarded = callHandlerService;
509
Santos Cordonad078192013-08-28 15:14:54 -0700510 // Before we send any updates, we need to set up the initial service calls.
511 makeInitialServiceCalls();
512
Chiao Chengd38eebc2013-08-28 14:38:14 -0700513 processQueue();
Santos Cordonf9ad0642013-09-26 10:48:07 -0700514
515 if (mFullUpdateOnConnect) {
516 mFullUpdateOnConnect = false;
517 onUpdate(mCallModeler.getFullList());
518 }
519 }
520 }
521
522 /**
523 * Checks to see if there are any live calls left, and if so, try reconnecting the UI.
524 */
525 private void reconnectOnRemainingCalls() {
526 if (mCallModeler.hasLiveCall()) {
527 mFullUpdateOnConnect = true;
528 setupServiceConnection();
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700529 }
Santos Cordonad078192013-08-28 15:14:54 -0700530 }
Santos Cordoncba1b442013-07-18 12:43:58 -0700531
Santos Cordonad078192013-08-28 15:14:54 -0700532 /**
533 * Makes initial service calls to set up callcommandservice and audio modes.
534 */
535 private void makeInitialServiceCalls() {
Santos Cordoncba1b442013-07-18 12:43:58 -0700536 try {
Santos Cordon12a03aa2013-09-12 23:34:05 -0700537 mCallHandlerServiceGuarded.startCallService(mCallCommandService);
Santos Cordonad078192013-08-28 15:14:54 -0700538
539 onSupportedAudioModeChange(mAudioRouter.getSupportedAudioModes());
Santos Cordon8fd0ec72013-08-29 16:44:43 -0700540 onAudioModeChange(mAudioRouter.getAudioMode(), mAudioRouter.getMute());
Santos Cordoncba1b442013-07-18 12:43:58 -0700541 } catch (RemoteException e) {
Santos Cordon63a84242013-07-23 13:32:52 -0700542 Log.e(TAG, "Remote exception calling CallHandlerService::setCallCommandService", e);
Santos Cordon89647a62013-07-16 13:38:09 -0700543 }
544 }
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700545
Chiao Chengd38eebc2013-08-28 14:38:14 -0700546 private List<QueueParams> getQueue() {
547 if (mQueue == null) {
548 mQueue = Lists.newArrayList();
549 }
550 return mQueue;
551 }
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700552
553 private void enqueueDisconnect(Call call) {
Chiao Chengd38eebc2013-08-28 14:38:14 -0700554 getQueue().add(new QueueParams(QueueParams.METHOD_DISCONNECT, new Call(call)));
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700555 }
556
557 private void enqueueIncoming(Call call) {
Chiao Chengd38eebc2013-08-28 14:38:14 -0700558 getQueue().add(new QueueParams(QueueParams.METHOD_INCOMING, new Call(call)));
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700559 }
560
561 private void enqueueUpdate(List<Call> calls) {
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700562 final List<Call> copy = Lists.newArrayList();
563 for (Call call : calls) {
564 copy.add(new Call(call));
565 }
Chiao Chengc340ba92013-08-30 13:04:46 -0700566 getQueue().add(new QueueParams(QueueParams.METHOD_UPDATE, copy));
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700567 }
568
Chiao Chengd38eebc2013-08-28 14:38:14 -0700569 private void processQueue() {
Chiao Cheng3e6486e2013-09-03 19:27:46 -0700570 synchronized (mServiceAndQueueLock) {
571 if (mQueue != null) {
572 for (QueueParams params : mQueue) {
573 switch (params.mMethod) {
574 case QueueParams.METHOD_INCOMING:
575 processIncoming((Call) params.mArg);
576 break;
577 case QueueParams.METHOD_UPDATE:
578 processUpdate((List<Call>) params.mArg);
579 break;
580 case QueueParams.METHOD_DISCONNECT:
581 processDisconnect((Call) params.mArg);
582 break;
583 default:
584 throw new IllegalArgumentException("Method type " + params.mMethod +
585 " not recognized.");
586 }
587 }
588 mQueue.clear();
589 mQueue = null;
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700590 }
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700591 }
592 }
593
Chiao Chengd38eebc2013-08-28 14:38:14 -0700594 /**
595 * Holds method parameters.
596 */
597 private static class QueueParams {
598 private static final int METHOD_INCOMING = 1;
599 private static final int METHOD_UPDATE = 2;
600 private static final int METHOD_DISCONNECT = 3;
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700601
Chiao Chengd38eebc2013-08-28 14:38:14 -0700602 private final int mMethod;
603 private final Object mArg;
604
605 private QueueParams(int method, Object arg) {
606 mMethod = method;
607 this.mArg = arg;
Chiao Cheng6c6b2722013-08-22 18:35:54 -0700608 }
609 }
Santos Cordon89647a62013-07-16 13:38:09 -0700610}