blob: 10ea4431b7626cde6c688886aa4e0306388ffafa [file] [log] [blame]
Santos Cordone3d76ab2014-01-28 17:25:20 -08001/*
2 * Copyright (C) 2014 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.telecomm;
18
Sailesh Nepal9d58de52014-07-18 14:53:19 -070019import android.app.PendingIntent;
Santos Cordone3d76ab2014-01-28 17:25:20 -080020import android.content.ComponentName;
21import android.content.Context;
22import android.content.Intent;
23import android.content.ServiceConnection;
Santos Cordonfdfcafa2014-06-26 14:49:05 -070024
25import android.content.res.Resources;
Sailesh Nepal84fa5f82014-04-02 11:01:11 -070026import android.net.Uri;
Santos Cordone3d76ab2014-01-28 17:25:20 -080027import android.os.IBinder;
28import android.os.RemoteException;
Amith Yamasani60e75842014-05-23 10:09:14 -070029import android.os.UserHandle;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070030import android.telecomm.CallAudioState;
Sailesh Nepal8c85dee2014-04-07 22:21:40 -070031import android.telecomm.CallCapabilities;
Sailesh Nepale8ecb982014-07-11 17:19:42 -070032import android.telecomm.CallPropertyPresentation;
Sailesh Nepal8c85dee2014-04-07 22:21:40 -070033import android.telecomm.CallState;
Santos Cordon2583b672014-07-19 13:07:33 -070034import android.telecomm.ParcelableCall;
Sailesh Nepala439e1b2014-03-11 18:19:58 -070035
36import com.android.internal.telecomm.IInCallService;
Sailesh Nepal810735e2014-03-18 18:15:46 -070037import com.google.common.collect.ImmutableCollection;
Santos Cordone3d76ab2014-01-28 17:25:20 -080038
Santos Cordona1610702014-06-04 20:22:56 -070039import java.util.ArrayList;
40import java.util.List;
Santos Cordona1610702014-06-04 20:22:56 -070041
Santos Cordone3d76ab2014-01-28 17:25:20 -080042/**
43 * Binds to {@link IInCallService} and provides the service to {@link CallsManager} through which it
44 * can send updates to the in-call app. This class is created and owned by CallsManager and retains
Sailesh Nepale59bb192014-04-01 18:33:59 -070045 * a binding to the {@link IInCallService} (implemented by the in-call app).
Santos Cordone3d76ab2014-01-28 17:25:20 -080046 */
Sailesh Nepal810735e2014-03-18 18:15:46 -070047public final class InCallController extends CallsManagerListenerBase {
Santos Cordone3d76ab2014-01-28 17:25:20 -080048 /**
49 * Used to bind to the in-call app and triggers the start of communication between
Sailesh Nepale59bb192014-04-01 18:33:59 -070050 * this class and in-call app.
Santos Cordone3d76ab2014-01-28 17:25:20 -080051 */
52 private class InCallServiceConnection implements ServiceConnection {
53 /** {@inheritDoc} */
54 @Override public void onServiceConnected(ComponentName name, IBinder service) {
55 onConnected(service);
56 }
57
58 /** {@inheritDoc} */
59 @Override public void onServiceDisconnected(ComponentName name) {
60 onDisconnected();
61 }
62 }
63
Sailesh Nepale8ecb982014-07-11 17:19:42 -070064 private final Call.Listener mCallListener = new Call.ListenerBase() {
65 @Override
66 public void onCallCapabilitiesChanged(Call call) {
67 updateCall(call);
68 }
69
70 @Override
71 public void onCannedSmsResponsesLoaded(Call call) {
72 updateCall(call);
73 }
74
75 @Override
Andrew Lee3bcf9352014-07-23 12:36:05 -070076 public void onVideoCallProviderChanged(Call call) {
Sailesh Nepale8ecb982014-07-11 17:19:42 -070077 updateCall(call);
78 }
79
80 @Override
81 public void onStatusHintsChanged(Call call) {
82 updateCall(call);
83 }
84
85 @Override
86 public void onHandleChanged(Call call) {
87 updateCall(call);
88 }
89
90 @Override
91 public void onCallerDisplayNameChanged(Call call) {
92 updateCall(call);
93 }
Andrew Lee4a796602014-07-11 17:23:03 -070094
95 @Override
96 public void onVideoStateChanged(Call call) {
97 updateCall(call);
98 }
Sailesh Nepal9d58de52014-07-18 14:53:19 -070099
100 @Override
101 public void onStartActivityFromInCall(Call call, PendingIntent intent) {
102 if (mInCallService != null) {
103 Log.i(this, "Calling startActivity, intent: %s", intent);
104 try {
105 mInCallService.startActivity(mCallIdMapper.getCallId(call), intent);
106 } catch (RemoteException ignored) {
107 }
108 }
109 }
Ihab Awad69eb0f52014-07-18 11:20:37 -0700110
111 @Override
112 public void onPhoneAccountChanged(Call call) {
113 updateCall(call);
114 }
Sailesh Nepale8ecb982014-07-11 17:19:42 -0700115 };
116
Santos Cordone3d76ab2014-01-28 17:25:20 -0800117 /** Maintains a binding connection to the in-call app. */
118 private final InCallServiceConnection mConnection = new InCallServiceConnection();
119
Santos Cordone3d76ab2014-01-28 17:25:20 -0800120 /** The in-call app implementation, see {@link IInCallService}. */
121 private IInCallService mInCallService;
122
Sailesh Nepale59bb192014-04-01 18:33:59 -0700123 private final CallIdMapper mCallIdMapper = new CallIdMapper("InCall");
124
Santos Cordone3d76ab2014-01-28 17:25:20 -0800125 IInCallService getService() {
126 return mInCallService;
127 }
128
Sailesh Nepal810735e2014-03-18 18:15:46 -0700129 @Override
130 public void onCallAdded(Call call) {
131 if (mInCallService == null) {
132 bind();
133 } else {
134 Log.i(this, "Adding call: %s", call);
Ihab Awadff7493a2014-06-10 13:47:44 -0700135 if (mCallIdMapper.getCallId(call) == null) {
136 mCallIdMapper.addCall(call);
Sailesh Nepale8ecb982014-07-11 17:19:42 -0700137 call.addListener(mCallListener);
Ihab Awadff7493a2014-06-10 13:47:44 -0700138 try {
Santos Cordon2583b672014-07-19 13:07:33 -0700139 mInCallService.addCall(toParcelableCall(call));
Ihab Awadff7493a2014-06-10 13:47:44 -0700140 } catch (RemoteException ignored) {
141 }
Santos Cordone3d76ab2014-01-28 17:25:20 -0800142 }
Santos Cordon049b7b62014-01-30 05:34:26 -0800143 }
144 }
145
Sailesh Nepal810735e2014-03-18 18:15:46 -0700146 @Override
147 public void onCallRemoved(Call call) {
148 if (CallsManager.getInstance().getCalls().isEmpty()) {
149 // TODO(sail): Wait for all messages to be delivered to the service before unbinding.
150 unbind();
Santos Cordon049b7b62014-01-30 05:34:26 -0800151 }
Sailesh Nepale8ecb982014-07-11 17:19:42 -0700152 call.removeListener(mCallListener);
Sailesh Nepale59bb192014-04-01 18:33:59 -0700153 mCallIdMapper.removeCall(call);
Santos Cordon049b7b62014-01-30 05:34:26 -0800154 }
155
Sailesh Nepal810735e2014-03-18 18:15:46 -0700156 @Override
157 public void onCallStateChanged(Call call, CallState oldState, CallState newState) {
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700158 updateCall(call);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700159 }
160
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700161 @Override
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700162 public void onConnectionServiceChanged(
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700163 Call call,
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700164 ConnectionServiceWrapper oldService,
165 ConnectionServiceWrapper newService) {
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700166 updateCall(call);
167 }
168
169 @Override
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700170 public void onAudioStateChanged(CallAudioState oldAudioState, CallAudioState newAudioState) {
171 if (mInCallService != null) {
172 Log.i(this, "Calling onAudioStateChanged, audioState: %s -> %s", oldAudioState,
173 newAudioState);
174 try {
175 mInCallService.onAudioStateChanged(newAudioState);
Santos Cordonf3671a62014-05-29 21:51:53 -0700176 } catch (RemoteException ignored) {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700177 }
178 }
179 }
180
Evan Charlton352105c2014-06-03 14:10:54 -0700181 void onPostDialWait(Call call, String remaining) {
182 if (mInCallService != null) {
183 Log.i(this, "Calling onPostDialWait, remaining = %s", remaining);
184 try {
185 mInCallService.setPostDialWait(mCallIdMapper.getCallId(call), remaining);
186 } catch (RemoteException ignored) {
187 }
188 }
189 }
190
Santos Cordona1610702014-06-04 20:22:56 -0700191 @Override
Santos Cordona1610702014-06-04 20:22:56 -0700192 public void onIsConferencedChanged(Call call) {
193 Log.v(this, "onIsConferencedChanged %s", call);
194 updateCall(call);
195 }
196
Santos Cordonf3671a62014-05-29 21:51:53 -0700197 void bringToForeground(boolean showDialpad) {
198 if (mInCallService != null) {
199 try {
200 mInCallService.bringToForeground(showDialpad);
201 } catch (RemoteException ignored) {
202 }
203 } else {
204 Log.w(this, "Asking to bring unbound in-call UI to foreground.");
205 }
206 }
207
Santos Cordone3d76ab2014-01-28 17:25:20 -0800208 /**
209 * Unbinds an existing bound connection to the in-call app.
Santos Cordone3d76ab2014-01-28 17:25:20 -0800210 */
Sailesh Nepal810735e2014-03-18 18:15:46 -0700211 private void unbind() {
Santos Cordone3d76ab2014-01-28 17:25:20 -0800212 ThreadUtil.checkOnMainThread();
213 if (mInCallService != null) {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800214 Log.i(this, "Unbinding from InCallService");
Santos Cordon049b7b62014-01-30 05:34:26 -0800215 TelecommApp.getInstance().unbindService(mConnection);
Santos Cordone3d76ab2014-01-28 17:25:20 -0800216 mInCallService = null;
217 }
218 }
219
220 /**
Santos Cordon049b7b62014-01-30 05:34:26 -0800221 * Binds to the in-call app if not already connected by binding directly to the saved
222 * component name of the {@link IInCallService} implementation.
223 */
224 private void bind() {
225 ThreadUtil.checkOnMainThread();
226 if (mInCallService == null) {
Santos Cordonfdfcafa2014-06-26 14:49:05 -0700227 Context context = TelecommApp.getInstance();
228 Resources resources = context.getResources();
229 ComponentName component = new ComponentName(
230 resources.getString(R.string.ui_default_package),
231 resources.getString(R.string.incall_default_class));
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800232 Log.i(this, "Attempting to bind to InCallService: %s", component);
Santos Cordon049b7b62014-01-30 05:34:26 -0800233
234 Intent serviceIntent = new Intent(IInCallService.class.getName());
235 serviceIntent.setComponent(component);
236
Amith Yamasani60e75842014-05-23 10:09:14 -0700237 if (!context.bindServiceAsUser(serviceIntent, mConnection, Context.BIND_AUTO_CREATE,
238 UserHandle.CURRENT)) {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800239 Log.w(this, "Could not connect to the in-call app (%s)", component);
Santos Cordon049b7b62014-01-30 05:34:26 -0800240
241 // TODO(santoscordon): Implement retry or fall-back-to-default logic.
242 }
243 }
244 }
245
246 /**
Santos Cordone3d76ab2014-01-28 17:25:20 -0800247 * Persists the {@link IInCallService} instance and starts the communication between
Sailesh Nepale59bb192014-04-01 18:33:59 -0700248 * this class and in-call app by sending the first update to in-call app. This method is
Santos Cordone3d76ab2014-01-28 17:25:20 -0800249 * called after a successful binding connection is established.
250 *
251 * @param service The {@link IInCallService} implementation.
252 */
253 private void onConnected(IBinder service) {
254 ThreadUtil.checkOnMainThread();
255 mInCallService = IInCallService.Stub.asInterface(service);
256
257 try {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700258 mInCallService.setInCallAdapter(new InCallAdapter(CallsManager.getInstance(),
259 mCallIdMapper));
Santos Cordone3d76ab2014-01-28 17:25:20 -0800260 } catch (RemoteException e) {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800261 Log.e(this, e, "Failed to set the in-call adapter.");
Santos Cordone3d76ab2014-01-28 17:25:20 -0800262 mInCallService = null;
Sailesh Nepal810735e2014-03-18 18:15:46 -0700263 return;
Santos Cordone3d76ab2014-01-28 17:25:20 -0800264 }
265
Santos Cordon049b7b62014-01-30 05:34:26 -0800266 // Upon successful connection, send the state of the world to the in-call app.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700267 ImmutableCollection<Call> calls = CallsManager.getInstance().getCalls();
268 if (!calls.isEmpty()) {
269 for (Call call : calls) {
270 onCallAdded(call);
271 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700272 onAudioStateChanged(null, CallsManager.getInstance().getAudioState());
Sailesh Nepal810735e2014-03-18 18:15:46 -0700273 } else {
274 unbind();
Santos Cordon049b7b62014-01-30 05:34:26 -0800275 }
Santos Cordone3d76ab2014-01-28 17:25:20 -0800276 }
277
278 /**
279 * Cleans up the instance of in-call app after the service has been unbound.
280 */
281 private void onDisconnected() {
282 ThreadUtil.checkOnMainThread();
283 mInCallService = null;
284 }
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700285
286 private void updateCall(Call call) {
287 if (mInCallService != null) {
288 try {
Santos Cordon2583b672014-07-19 13:07:33 -0700289 ParcelableCall parcelableCall = toParcelableCall(call);
290 Log.v(this, "updateCall %s ==> %s", call, parcelableCall);
291 mInCallService.updateCall(parcelableCall);
Santos Cordonf3671a62014-05-29 21:51:53 -0700292 } catch (RemoteException ignored) {
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700293 }
294 }
295 }
296
Santos Cordon2583b672014-07-19 13:07:33 -0700297 private ParcelableCall toParcelableCall(Call call) {
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700298 String callId = mCallIdMapper.getCallId(call);
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700299
Sailesh Nepale20bc972014-07-09 21:22:36 -0700300 int capabilities = call.getCallCapabilities();
Santos Cordon4b034a62014-07-18 13:42:05 -0700301 if (CallsManager.getInstance().isAddCallCapable(call)) {
302 capabilities |= CallCapabilities.ADD_CALL;
Santos Cordon10838c22014-06-11 17:36:04 -0700303 }
Santos Cordon4b034a62014-07-18 13:42:05 -0700304 if (!call.isEmergencyCall()) {
305 capabilities |= CallCapabilities.MUTE;
Santos Cordona1610702014-06-04 20:22:56 -0700306 }
Sailesh Nepale20bc972014-07-09 21:22:36 -0700307
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700308 CallState state = call.getState();
309 if (state == CallState.ABORTED) {
310 state = CallState.DISCONNECTED;
311 }
Santos Cordona1610702014-06-04 20:22:56 -0700312
313 String parentCallId = null;
314 Call parentCall = call.getParentCall();
315 if (parentCall != null) {
316 parentCallId = mCallIdMapper.getCallId(parentCall);
317 }
318
319 long connectTimeMillis = call.getConnectTimeMillis();
320 List<Call> childCalls = call.getChildCalls();
321 List<String> childCallIds = new ArrayList<>();
322 if (!childCalls.isEmpty()) {
323 connectTimeMillis = Long.MAX_VALUE;
324 for (Call child : childCalls) {
325 connectTimeMillis = Math.min(child.getConnectTimeMillis(), connectTimeMillis);
326 childCallIds.add(mCallIdMapper.getCallId(child));
327 }
328 }
329
Ihab Awadff7493a2014-06-10 13:47:44 -0700330 if (call.isRespondViaSmsCapable()) {
331 capabilities |= CallCapabilities.RESPOND_VIA_TEXT;
332 }
333
Sailesh Nepale8ecb982014-07-11 17:19:42 -0700334 Uri handle = call.getHandlePresentation() == CallPropertyPresentation.ALLOWED ?
335 call.getHandle() : null;
336 String callerDisplayName = call.getCallerDisplayNamePresentation() ==
337 CallPropertyPresentation.ALLOWED ? call.getCallerDisplayName() : null;
338
Santos Cordon2583b672014-07-19 13:07:33 -0700339 return new ParcelableCall(
340 callId,
341 state,
342 call.getDisconnectCause(),
343 call.getDisconnectMessage(),
344 call.getCannedSmsResponses(),
345 capabilities,
346 connectTimeMillis,
347 handle,
348 call.getHandlePresentation(),
349 callerDisplayName,
350 call.getCallerDisplayNamePresentation(),
351 call.getGatewayInfo(),
352 call.getPhoneAccount(),
Andrew Lee3bcf9352014-07-23 12:36:05 -0700353 call.getVideoCallProvider(),
Santos Cordon2583b672014-07-19 13:07:33 -0700354 parentCallId,
355 childCallIds,
356 call.getStatusHints(),
357 call.getVideoState());
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700358 }
Santos Cordone3d76ab2014-01-28 17:25:20 -0800359}