blob: 6dac2cee677763400502d4926375b0c7167cf6e0 [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;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070026import android.telecomm.CallAudioState;
Sailesh Nepal8c85dee2014-04-07 22:21:40 -070027import android.telecomm.CallCapabilities;
28import android.telecomm.CallServiceDescriptor;
29import android.telecomm.CallState;
30import android.telecomm.InCallCall;
Sailesh Nepal810735e2014-03-18 18:15:46 -070031import android.telecomm.CallState;
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 Cordon6a405e02014-06-04 20:22:56 -070036import java.util.ArrayList;
37import java.util.List;
38import java.util.Set;
39
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);
Sailesh Nepale59bb192014-04-01 18:33:59 -070094 mCallIdMapper.addCall(call);
Sailesh Nepal810735e2014-03-18 18:15:46 -070095 try {
Sailesh Nepal8c85dee2014-04-07 22:21:40 -070096 mInCallService.addCall(toInCallCall(call));
Santos Cordonf3671a62014-05-29 21:51:53 -070097 } catch (RemoteException ignored) {
Santos Cordone3d76ab2014-01-28 17:25:20 -080098 }
Santos Cordon049b7b62014-01-30 05:34:26 -080099 }
100 }
101
Sailesh Nepal810735e2014-03-18 18:15:46 -0700102 @Override
103 public void onCallRemoved(Call call) {
104 if (CallsManager.getInstance().getCalls().isEmpty()) {
105 // TODO(sail): Wait for all messages to be delivered to the service before unbinding.
106 unbind();
Santos Cordon049b7b62014-01-30 05:34:26 -0800107 }
Sailesh Nepale59bb192014-04-01 18:33:59 -0700108 mCallIdMapper.removeCall(call);
Santos Cordon049b7b62014-01-30 05:34:26 -0800109 }
110
Sailesh Nepal810735e2014-03-18 18:15:46 -0700111 @Override
112 public void onCallStateChanged(Call call, CallState oldState, CallState newState) {
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700113 updateCall(call);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700114 }
115
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700116 @Override
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700117 public void onCallHandoffHandleChanged(Call call, Uri oldHandle, Uri newHandle) {
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700118 updateCall(call);
119 }
120
121 @Override
122 public void onCallServiceChanged(
123 Call call,
124 CallServiceWrapper oldCallServiceWrapper,
125 CallServiceWrapper newCallService) {
126 updateCall(call);
127 }
128
129 @Override
130 public void onCallHandoffCallServiceDescriptorChanged(
131 Call call,
132 CallServiceDescriptor oldDescriptor,
133 CallServiceDescriptor newDescriptor) {
134 updateCall(call);
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700135 }
136
137 @Override
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700138 public void onAudioStateChanged(CallAudioState oldAudioState, CallAudioState newAudioState) {
139 if (mInCallService != null) {
140 Log.i(this, "Calling onAudioStateChanged, audioState: %s -> %s", oldAudioState,
141 newAudioState);
142 try {
143 mInCallService.onAudioStateChanged(newAudioState);
Santos Cordonf3671a62014-05-29 21:51:53 -0700144 } catch (RemoteException ignored) {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700145 }
146 }
147 }
148
Evan Charlton352105c2014-06-03 14:10:54 -0700149 void onPostDialWait(Call call, String remaining) {
150 if (mInCallService != null) {
151 Log.i(this, "Calling onPostDialWait, remaining = %s", remaining);
152 try {
153 mInCallService.setPostDialWait(mCallIdMapper.getCallId(call), remaining);
154 } catch (RemoteException ignored) {
155 }
156 }
157 }
158
Santos Cordon6a405e02014-06-04 20:22:56 -0700159 @Override
160 public void onIsConferenceCapableChanged(Call call, boolean isConferenceCapable) {
161 updateCall(call);
162 }
163
164 @Override
165 public void onIsConferencedChanged(Call call) {
166 Log.v(this, "onIsConferencedChanged %s", call);
167 updateCall(call);
168 }
169
Santos Cordonf3671a62014-05-29 21:51:53 -0700170 void bringToForeground(boolean showDialpad) {
171 if (mInCallService != null) {
172 try {
173 mInCallService.bringToForeground(showDialpad);
174 } catch (RemoteException ignored) {
175 }
176 } else {
177 Log.w(this, "Asking to bring unbound in-call UI to foreground.");
178 }
179 }
180
Santos Cordone3d76ab2014-01-28 17:25:20 -0800181 /**
182 * Unbinds an existing bound connection to the in-call app.
Santos Cordone3d76ab2014-01-28 17:25:20 -0800183 */
Sailesh Nepal810735e2014-03-18 18:15:46 -0700184 private void unbind() {
Santos Cordone3d76ab2014-01-28 17:25:20 -0800185 ThreadUtil.checkOnMainThread();
186 if (mInCallService != null) {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800187 Log.i(this, "Unbinding from InCallService");
Santos Cordon049b7b62014-01-30 05:34:26 -0800188 TelecommApp.getInstance().unbindService(mConnection);
Santos Cordone3d76ab2014-01-28 17:25:20 -0800189 mInCallService = null;
190 }
191 }
192
193 /**
Santos Cordon049b7b62014-01-30 05:34:26 -0800194 * Binds to the in-call app if not already connected by binding directly to the saved
195 * component name of the {@link IInCallService} implementation.
196 */
197 private void bind() {
198 ThreadUtil.checkOnMainThread();
199 if (mInCallService == null) {
200 ComponentName component =
201 new ComponentName(IN_CALL_PACKAGE_NAME, IN_CALL_SERVICE_CLASS_NAME);
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800202 Log.i(this, "Attempting to bind to InCallService: %s", component);
Santos Cordon049b7b62014-01-30 05:34:26 -0800203
204 Intent serviceIntent = new Intent(IInCallService.class.getName());
205 serviceIntent.setComponent(component);
206
207 Context context = TelecommApp.getInstance();
208 if (!context.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE)) {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800209 Log.w(this, "Could not connect to the in-call app (%s)", component);
Santos Cordon049b7b62014-01-30 05:34:26 -0800210
211 // TODO(santoscordon): Implement retry or fall-back-to-default logic.
212 }
213 }
214 }
215
216 /**
Santos Cordone3d76ab2014-01-28 17:25:20 -0800217 * Persists the {@link IInCallService} instance and starts the communication between
Sailesh Nepale59bb192014-04-01 18:33:59 -0700218 * 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 -0800219 * called after a successful binding connection is established.
220 *
221 * @param service The {@link IInCallService} implementation.
222 */
223 private void onConnected(IBinder service) {
224 ThreadUtil.checkOnMainThread();
225 mInCallService = IInCallService.Stub.asInterface(service);
226
227 try {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700228 mInCallService.setInCallAdapter(new InCallAdapter(CallsManager.getInstance(),
229 mCallIdMapper));
Santos Cordone3d76ab2014-01-28 17:25:20 -0800230 } catch (RemoteException e) {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800231 Log.e(this, e, "Failed to set the in-call adapter.");
Santos Cordone3d76ab2014-01-28 17:25:20 -0800232 mInCallService = null;
Sailesh Nepal810735e2014-03-18 18:15:46 -0700233 return;
Santos Cordone3d76ab2014-01-28 17:25:20 -0800234 }
235
Santos Cordon049b7b62014-01-30 05:34:26 -0800236 // Upon successful connection, send the state of the world to the in-call app.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700237 ImmutableCollection<Call> calls = CallsManager.getInstance().getCalls();
238 if (!calls.isEmpty()) {
239 for (Call call : calls) {
240 onCallAdded(call);
241 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700242 onAudioStateChanged(null, CallsManager.getInstance().getAudioState());
Sailesh Nepal810735e2014-03-18 18:15:46 -0700243 } else {
244 unbind();
Santos Cordon049b7b62014-01-30 05:34:26 -0800245 }
Santos Cordone3d76ab2014-01-28 17:25:20 -0800246 }
247
248 /**
249 * Cleans up the instance of in-call app after the service has been unbound.
250 */
251 private void onDisconnected() {
252 ThreadUtil.checkOnMainThread();
253 mInCallService = null;
254 }
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700255
256 private void updateCall(Call call) {
257 if (mInCallService != null) {
258 try {
Santos Cordon6a405e02014-06-04 20:22:56 -0700259 InCallCall inCallCall = toInCallCall(call);
260 Log.v(this, "updateCall %s ==> %s", call, inCallCall);
261 mInCallService.updateCall(inCallCall);
Santos Cordonf3671a62014-05-29 21:51:53 -0700262 } catch (RemoteException ignored) {
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700263 }
264 }
265 }
266
267 private InCallCall toInCallCall(Call call) {
268 String callId = mCallIdMapper.getCallId(call);
269 CallServiceDescriptor descriptor =
270 call.getCallService() != null ? call.getCallService().getDescriptor() : null;
271
272 boolean isHandoffCapable = call.getHandoffHandle() != null;
273 int capabilities = CallCapabilities.HOLD | CallCapabilities.MUTE;
274 if (call.getHandoffHandle() != null) {
275 capabilities |= CallCapabilities.CONNECTION_HANDOFF;
276 }
Santos Cordon6a405e02014-06-04 20:22:56 -0700277 if (call.isConferenceCapable()) {
278 capabilities |= CallCapabilities.MERGE_CALLS;
279 }
Santos Cordon10838c22014-06-11 17:36:04 -0700280 if (CallsManager.getInstance().isAddCallCapable(call)) {
281 capabilities |= CallCapabilities.ADD_CALL;
282 }
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700283 CallState state = call.getState();
284 if (state == CallState.ABORTED) {
285 state = CallState.DISCONNECTED;
286 }
287 // TODO(sail): Remove this and replace with final reconnecting code.
288 if (state == CallState.DISCONNECTED && call.getHandoffCallServiceDescriptor() != null) {
289 state = CallState.ACTIVE;
290 }
Santos Cordon6a405e02014-06-04 20:22:56 -0700291
292 String parentCallId = null;
293 Call parentCall = call.getParentCall();
294 if (parentCall != null) {
295 parentCallId = mCallIdMapper.getCallId(parentCall);
296 }
297
298 long connectTimeMillis = call.getConnectTimeMillis();
299 List<Call> childCalls = call.getChildCalls();
300 List<String> childCallIds = new ArrayList<>();
301 if (!childCalls.isEmpty()) {
302 connectTimeMillis = Long.MAX_VALUE;
303 for (Call child : childCalls) {
304 connectTimeMillis = Math.min(child.getConnectTimeMillis(), connectTimeMillis);
305 childCallIds.add(mCallIdMapper.getCallId(child));
306 }
307 }
308
Ihab Awada3cb9e32014-06-03 18:45:05 -0700309 return new InCallCall(callId, state, call.getDisconnectCause(), call.getDisconnectMessage(),
Santos Cordon6a405e02014-06-04 20:22:56 -0700310 capabilities, connectTimeMillis, call.getHandle(), call.getGatewayInfo(),
311 descriptor, call.getHandoffCallServiceDescriptor(), parentCallId, childCallIds);
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700312 }
Santos Cordon10838c22014-06-11 17:36:04 -0700313
Santos Cordone3d76ab2014-01-28 17:25:20 -0800314}