blob: 3dd0539c050c6775cc3f2cd4162339bd479b02c2 [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
19import android.content.ComponentName;
20import android.content.Context;
21import android.content.Intent;
22import android.content.ServiceConnection;
Sailesh Nepal84fa5f82014-04-02 11:01:11 -070023import android.net.Uri;
Santos Cordone3d76ab2014-01-28 17:25:20 -080024import android.os.IBinder;
25import android.os.RemoteException;
Amith Yamasani60e75842014-05-23 10:09:14 -070026import android.os.UserHandle;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070027import android.telecomm.CallAudioState;
Sailesh Nepal8c85dee2014-04-07 22:21:40 -070028import android.telecomm.CallCapabilities;
29import android.telecomm.CallServiceDescriptor;
30import android.telecomm.CallState;
31import android.telecomm.InCallCall;
Sailesh Nepala439e1b2014-03-11 18:19:58 -070032
33import com.android.internal.telecomm.IInCallService;
Sailesh Nepal810735e2014-03-18 18:15:46 -070034import com.google.common.collect.ImmutableCollection;
Santos Cordone3d76ab2014-01-28 17:25:20 -080035
Santos Cordona1610702014-06-04 20:22:56 -070036import java.util.ArrayList;
Ihab Awadff7493a2014-06-10 13:47:44 -070037import java.util.Collections;
Santos Cordona1610702014-06-04 20:22:56 -070038import java.util.List;
Santos Cordona1610702014-06-04 20:22:56 -070039
Santos Cordone3d76ab2014-01-28 17:25:20 -080040/**
41 * Binds to {@link IInCallService} and provides the service to {@link CallsManager} through which it
42 * 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 -070043 * a binding to the {@link IInCallService} (implemented by the in-call app).
Santos Cordone3d76ab2014-01-28 17:25:20 -080044 */
Sailesh Nepal810735e2014-03-18 18:15:46 -070045public final class InCallController extends CallsManagerListenerBase {
Santos Cordone3d76ab2014-01-28 17:25:20 -080046 /**
47 * Used to bind to the in-call app and triggers the start of communication between
Sailesh Nepale59bb192014-04-01 18:33:59 -070048 * this class and in-call app.
Santos Cordone3d76ab2014-01-28 17:25:20 -080049 */
50 private class InCallServiceConnection implements ServiceConnection {
51 /** {@inheritDoc} */
52 @Override public void onServiceConnected(ComponentName name, IBinder service) {
53 onConnected(service);
54 }
55
56 /** {@inheritDoc} */
57 @Override public void onServiceDisconnected(ComponentName name) {
58 onDisconnected();
59 }
60 }
61
Santos Cordone3d76ab2014-01-28 17:25:20 -080062 /**
63 * Package name of the in-call app. Although in-call code in kept in its own namespace, it is
Ben Gilad13329fd2014-02-11 17:20:29 -080064 * ultimately compiled into the dialer APK, hence the difference in namespaces between this and
65 * {@link #IN_CALL_SERVICE_CLASS_NAME}.
Santos Cordone3d76ab2014-01-28 17:25:20 -080066 * TODO(santoscordon): Change this into config.xml resource entry.
67 */
68 private static final String IN_CALL_PACKAGE_NAME = "com.google.android.dialer";
69
70 /**
71 * Class name of the component within in-call app which implements {@link IInCallService}.
72 */
Sailesh Nepala439e1b2014-03-11 18:19:58 -070073 private static final String IN_CALL_SERVICE_CLASS_NAME =
74 "com.android.incallui.InCallServiceImpl";
Santos Cordone3d76ab2014-01-28 17:25:20 -080075
76 /** Maintains a binding connection to the in-call app. */
77 private final InCallServiceConnection mConnection = new InCallServiceConnection();
78
Santos Cordone3d76ab2014-01-28 17:25:20 -080079 /** The in-call app implementation, see {@link IInCallService}. */
80 private IInCallService mInCallService;
81
Sailesh Nepale59bb192014-04-01 18:33:59 -070082 private final CallIdMapper mCallIdMapper = new CallIdMapper("InCall");
83
Santos Cordone3d76ab2014-01-28 17:25:20 -080084 IInCallService getService() {
85 return mInCallService;
86 }
87
Sailesh Nepal810735e2014-03-18 18:15:46 -070088 @Override
89 public void onCallAdded(Call call) {
90 if (mInCallService == null) {
91 bind();
92 } else {
93 Log.i(this, "Adding call: %s", call);
Ihab Awadff7493a2014-06-10 13:47:44 -070094 if (mCallIdMapper.getCallId(call) == null) {
95 mCallIdMapper.addCall(call);
96 try {
97 mInCallService.addCall(toInCallCall(call));
98 } catch (RemoteException ignored) {
99 }
Santos Cordone3d76ab2014-01-28 17:25:20 -0800100 }
Santos Cordon049b7b62014-01-30 05:34:26 -0800101 }
102 }
103
Sailesh Nepal810735e2014-03-18 18:15:46 -0700104 @Override
105 public void onCallRemoved(Call call) {
106 if (CallsManager.getInstance().getCalls().isEmpty()) {
107 // TODO(sail): Wait for all messages to be delivered to the service before unbinding.
108 unbind();
Santos Cordon049b7b62014-01-30 05:34:26 -0800109 }
Sailesh Nepale59bb192014-04-01 18:33:59 -0700110 mCallIdMapper.removeCall(call);
Santos Cordon049b7b62014-01-30 05:34:26 -0800111 }
112
Sailesh Nepal810735e2014-03-18 18:15:46 -0700113 @Override
114 public void onCallStateChanged(Call call, CallState oldState, CallState newState) {
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700115 updateCall(call);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700116 }
117
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700118 @Override
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700119 public void onCallHandoffHandleChanged(Call call, Uri oldHandle, Uri newHandle) {
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700120 updateCall(call);
121 }
122
123 @Override
124 public void onCallServiceChanged(
125 Call call,
126 CallServiceWrapper oldCallServiceWrapper,
127 CallServiceWrapper newCallService) {
128 updateCall(call);
129 }
130
131 @Override
132 public void onCallHandoffCallServiceDescriptorChanged(
133 Call call,
134 CallServiceDescriptor oldDescriptor,
135 CallServiceDescriptor newDescriptor) {
136 updateCall(call);
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700137 }
138
139 @Override
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700140 public void onAudioStateChanged(CallAudioState oldAudioState, CallAudioState newAudioState) {
141 if (mInCallService != null) {
142 Log.i(this, "Calling onAudioStateChanged, audioState: %s -> %s", oldAudioState,
143 newAudioState);
144 try {
145 mInCallService.onAudioStateChanged(newAudioState);
Santos Cordonf3671a62014-05-29 21:51:53 -0700146 } catch (RemoteException ignored) {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700147 }
148 }
149 }
150
Evan Charlton352105c2014-06-03 14:10:54 -0700151 void onPostDialWait(Call call, String remaining) {
152 if (mInCallService != null) {
153 Log.i(this, "Calling onPostDialWait, remaining = %s", remaining);
154 try {
155 mInCallService.setPostDialWait(mCallIdMapper.getCallId(call), remaining);
156 } catch (RemoteException ignored) {
157 }
158 }
159 }
160
Santos Cordona1610702014-06-04 20:22:56 -0700161 @Override
162 public void onIsConferenceCapableChanged(Call call, boolean isConferenceCapable) {
163 updateCall(call);
164 }
165
166 @Override
167 public void onIsConferencedChanged(Call call) {
168 Log.v(this, "onIsConferencedChanged %s", call);
169 updateCall(call);
170 }
171
Ihab Awadff7493a2014-06-10 13:47:44 -0700172 @Override
173 public void onCannedSmsResponsesLoaded(Call call) {
174 updateCall(call);
175 }
176
Santos Cordonf3671a62014-05-29 21:51:53 -0700177 void bringToForeground(boolean showDialpad) {
178 if (mInCallService != null) {
179 try {
180 mInCallService.bringToForeground(showDialpad);
181 } catch (RemoteException ignored) {
182 }
183 } else {
184 Log.w(this, "Asking to bring unbound in-call UI to foreground.");
185 }
186 }
187
Santos Cordone3d76ab2014-01-28 17:25:20 -0800188 /**
189 * Unbinds an existing bound connection to the in-call app.
Santos Cordone3d76ab2014-01-28 17:25:20 -0800190 */
Sailesh Nepal810735e2014-03-18 18:15:46 -0700191 private void unbind() {
Santos Cordone3d76ab2014-01-28 17:25:20 -0800192 ThreadUtil.checkOnMainThread();
193 if (mInCallService != null) {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800194 Log.i(this, "Unbinding from InCallService");
Santos Cordon049b7b62014-01-30 05:34:26 -0800195 TelecommApp.getInstance().unbindService(mConnection);
Santos Cordone3d76ab2014-01-28 17:25:20 -0800196 mInCallService = null;
197 }
198 }
199
200 /**
Santos Cordon049b7b62014-01-30 05:34:26 -0800201 * Binds to the in-call app if not already connected by binding directly to the saved
202 * component name of the {@link IInCallService} implementation.
203 */
204 private void bind() {
205 ThreadUtil.checkOnMainThread();
206 if (mInCallService == null) {
207 ComponentName component =
208 new ComponentName(IN_CALL_PACKAGE_NAME, IN_CALL_SERVICE_CLASS_NAME);
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800209 Log.i(this, "Attempting to bind to InCallService: %s", component);
Santos Cordon049b7b62014-01-30 05:34:26 -0800210
211 Intent serviceIntent = new Intent(IInCallService.class.getName());
212 serviceIntent.setComponent(component);
213
214 Context context = TelecommApp.getInstance();
Amith Yamasani60e75842014-05-23 10:09:14 -0700215 if (!context.bindServiceAsUser(serviceIntent, mConnection, Context.BIND_AUTO_CREATE,
216 UserHandle.CURRENT)) {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800217 Log.w(this, "Could not connect to the in-call app (%s)", component);
Santos Cordon049b7b62014-01-30 05:34:26 -0800218
219 // TODO(santoscordon): Implement retry or fall-back-to-default logic.
220 }
221 }
222 }
223
224 /**
Santos Cordone3d76ab2014-01-28 17:25:20 -0800225 * Persists the {@link IInCallService} instance and starts the communication between
Sailesh Nepale59bb192014-04-01 18:33:59 -0700226 * 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 -0800227 * called after a successful binding connection is established.
228 *
229 * @param service The {@link IInCallService} implementation.
230 */
231 private void onConnected(IBinder service) {
232 ThreadUtil.checkOnMainThread();
233 mInCallService = IInCallService.Stub.asInterface(service);
234
235 try {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700236 mInCallService.setInCallAdapter(new InCallAdapter(CallsManager.getInstance(),
237 mCallIdMapper));
Santos Cordone3d76ab2014-01-28 17:25:20 -0800238 } catch (RemoteException e) {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800239 Log.e(this, e, "Failed to set the in-call adapter.");
Santos Cordone3d76ab2014-01-28 17:25:20 -0800240 mInCallService = null;
Sailesh Nepal810735e2014-03-18 18:15:46 -0700241 return;
Santos Cordone3d76ab2014-01-28 17:25:20 -0800242 }
243
Santos Cordon049b7b62014-01-30 05:34:26 -0800244 // Upon successful connection, send the state of the world to the in-call app.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700245 ImmutableCollection<Call> calls = CallsManager.getInstance().getCalls();
246 if (!calls.isEmpty()) {
247 for (Call call : calls) {
248 onCallAdded(call);
249 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700250 onAudioStateChanged(null, CallsManager.getInstance().getAudioState());
Sailesh Nepal810735e2014-03-18 18:15:46 -0700251 } else {
252 unbind();
Santos Cordon049b7b62014-01-30 05:34:26 -0800253 }
Santos Cordone3d76ab2014-01-28 17:25:20 -0800254 }
255
256 /**
257 * Cleans up the instance of in-call app after the service has been unbound.
258 */
259 private void onDisconnected() {
260 ThreadUtil.checkOnMainThread();
261 mInCallService = null;
262 }
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700263
264 private void updateCall(Call call) {
265 if (mInCallService != null) {
266 try {
Santos Cordona1610702014-06-04 20:22:56 -0700267 InCallCall inCallCall = toInCallCall(call);
268 Log.v(this, "updateCall %s ==> %s", call, inCallCall);
269 mInCallService.updateCall(inCallCall);
Santos Cordonf3671a62014-05-29 21:51:53 -0700270 } catch (RemoteException ignored) {
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700271 }
272 }
273 }
274
275 private InCallCall toInCallCall(Call call) {
276 String callId = mCallIdMapper.getCallId(call);
277 CallServiceDescriptor descriptor =
278 call.getCallService() != null ? call.getCallService().getDescriptor() : null;
279
280 boolean isHandoffCapable = call.getHandoffHandle() != null;
281 int capabilities = CallCapabilities.HOLD | CallCapabilities.MUTE;
282 if (call.getHandoffHandle() != null) {
283 capabilities |= CallCapabilities.CONNECTION_HANDOFF;
284 }
Santos Cordon10838c22014-06-11 17:36:04 -0700285 if (CallsManager.getInstance().isAddCallCapable(call)) {
286 capabilities |= CallCapabilities.ADD_CALL;
287 }
Santos Cordona1610702014-06-04 20:22:56 -0700288 if (call.isConferenceCapable()) {
289 capabilities |= CallCapabilities.MERGE_CALLS;
290 }
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700291 CallState state = call.getState();
292 if (state == CallState.ABORTED) {
293 state = CallState.DISCONNECTED;
294 }
295 // TODO(sail): Remove this and replace with final reconnecting code.
296 if (state == CallState.DISCONNECTED && call.getHandoffCallServiceDescriptor() != null) {
297 state = CallState.ACTIVE;
298 }
Santos Cordona1610702014-06-04 20:22:56 -0700299
300 String parentCallId = null;
301 Call parentCall = call.getParentCall();
302 if (parentCall != null) {
303 parentCallId = mCallIdMapper.getCallId(parentCall);
304 }
305
306 long connectTimeMillis = call.getConnectTimeMillis();
307 List<Call> childCalls = call.getChildCalls();
308 List<String> childCallIds = new ArrayList<>();
309 if (!childCalls.isEmpty()) {
310 connectTimeMillis = Long.MAX_VALUE;
311 for (Call child : childCalls) {
312 connectTimeMillis = Math.min(child.getConnectTimeMillis(), connectTimeMillis);
313 childCallIds.add(mCallIdMapper.getCallId(child));
314 }
315 }
316
Ihab Awadff7493a2014-06-10 13:47:44 -0700317 if (call.isRespondViaSmsCapable()) {
318 capabilities |= CallCapabilities.RESPOND_VIA_TEXT;
319 }
320
Ihab Awad0fea3f22014-06-03 18:45:05 -0700321 return new InCallCall(callId, state, call.getDisconnectCause(), call.getDisconnectMessage(),
Ihab Awadff7493a2014-06-10 13:47:44 -0700322 call.getCannedSmsResponses(), capabilities, connectTimeMillis, call.getHandle(),
323 call.getGatewayInfo(), descriptor, call.getHandoffCallServiceDescriptor(),
324 parentCallId, childCallIds);
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700325 }
Santos Cordon10838c22014-06-11 17:36:04 -0700326
Santos Cordone3d76ab2014-01-28 17:25:20 -0800327}