blob: beac0d579a3956eb8a0e79903c889d4459015b30 [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;
Santos Cordon049b7b62014-01-30 05:34:26 -080027import android.telecomm.CallInfo;
Sailesh Nepal810735e2014-03-18 18:15:46 -070028import android.telecomm.CallState;
Sailesh Nepala439e1b2014-03-11 18:19:58 -070029
30import com.android.internal.telecomm.IInCallService;
Sailesh Nepal810735e2014-03-18 18:15:46 -070031import com.google.common.collect.ImmutableCollection;
Santos Cordone3d76ab2014-01-28 17:25:20 -080032
33/**
34 * Binds to {@link IInCallService} and provides the service to {@link CallsManager} through which it
35 * 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 -070036 * a binding to the {@link IInCallService} (implemented by the in-call app).
Santos Cordone3d76ab2014-01-28 17:25:20 -080037 */
Sailesh Nepal810735e2014-03-18 18:15:46 -070038public final class InCallController extends CallsManagerListenerBase {
Santos Cordone3d76ab2014-01-28 17:25:20 -080039 /**
40 * Used to bind to the in-call app and triggers the start of communication between
Sailesh Nepale59bb192014-04-01 18:33:59 -070041 * this class and in-call app.
Santos Cordone3d76ab2014-01-28 17:25:20 -080042 */
43 private class InCallServiceConnection implements ServiceConnection {
44 /** {@inheritDoc} */
45 @Override public void onServiceConnected(ComponentName name, IBinder service) {
46 onConnected(service);
47 }
48
49 /** {@inheritDoc} */
50 @Override public void onServiceDisconnected(ComponentName name) {
51 onDisconnected();
52 }
53 }
54
Santos Cordone3d76ab2014-01-28 17:25:20 -080055 /**
56 * 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 -080057 * ultimately compiled into the dialer APK, hence the difference in namespaces between this and
58 * {@link #IN_CALL_SERVICE_CLASS_NAME}.
Santos Cordone3d76ab2014-01-28 17:25:20 -080059 * TODO(santoscordon): Change this into config.xml resource entry.
60 */
61 private static final String IN_CALL_PACKAGE_NAME = "com.google.android.dialer";
62
63 /**
64 * Class name of the component within in-call app which implements {@link IInCallService}.
65 */
Sailesh Nepala439e1b2014-03-11 18:19:58 -070066 private static final String IN_CALL_SERVICE_CLASS_NAME =
67 "com.android.incallui.InCallServiceImpl";
Santos Cordone3d76ab2014-01-28 17:25:20 -080068
69 /** Maintains a binding connection to the in-call app. */
70 private final InCallServiceConnection mConnection = new InCallServiceConnection();
71
Santos Cordone3d76ab2014-01-28 17:25:20 -080072 /** The in-call app implementation, see {@link IInCallService}. */
73 private IInCallService mInCallService;
74
Sailesh Nepale59bb192014-04-01 18:33:59 -070075 private final CallIdMapper mCallIdMapper = new CallIdMapper("InCall");
76
Santos Cordone3d76ab2014-01-28 17:25:20 -080077 IInCallService getService() {
78 return mInCallService;
79 }
80
Sailesh Nepal810735e2014-03-18 18:15:46 -070081 @Override
82 public void onCallAdded(Call call) {
83 if (mInCallService == null) {
84 bind();
85 } else {
86 Log.i(this, "Adding call: %s", call);
Sailesh Nepale59bb192014-04-01 18:33:59 -070087 mCallIdMapper.addCall(call);
88 CallInfo callInfo = call.toCallInfo(mCallIdMapper.getCallId(call));
Sailesh Nepal810735e2014-03-18 18:15:46 -070089 try {
Sailesh Nepale59bb192014-04-01 18:33:59 -070090 mInCallService.addCall(callInfo);
Sailesh Nepal810735e2014-03-18 18:15:46 -070091 } catch (RemoteException e) {
Santos Cordone3d76ab2014-01-28 17:25:20 -080092 }
Santos Cordon049b7b62014-01-30 05:34:26 -080093 }
94 }
95
Sailesh Nepal810735e2014-03-18 18:15:46 -070096 @Override
97 public void onCallRemoved(Call call) {
98 if (CallsManager.getInstance().getCalls().isEmpty()) {
99 // TODO(sail): Wait for all messages to be delivered to the service before unbinding.
100 unbind();
Santos Cordon049b7b62014-01-30 05:34:26 -0800101 }
Sailesh Nepale59bb192014-04-01 18:33:59 -0700102 mCallIdMapper.removeCall(call);
Santos Cordon049b7b62014-01-30 05:34:26 -0800103 }
104
Sailesh Nepal810735e2014-03-18 18:15:46 -0700105 @Override
106 public void onCallStateChanged(Call call, CallState oldState, CallState newState) {
107 if (mInCallService == null) {
108 return;
Santos Cordone3d76ab2014-01-28 17:25:20 -0800109 }
Santos Cordone3d76ab2014-01-28 17:25:20 -0800110
Sailesh Nepale59bb192014-04-01 18:33:59 -0700111 String callId = mCallIdMapper.getCallId(call);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700112 switch (newState) {
113 case ACTIVE:
Sailesh Nepale59bb192014-04-01 18:33:59 -0700114 Log.i(this, "Mark call as ACTIVE: %s", callId);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700115 try {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700116 mInCallService.setActive(callId);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700117 } catch (RemoteException e) {
Sailesh Nepal810735e2014-03-18 18:15:46 -0700118 }
119 break;
120 case ON_HOLD:
Sailesh Nepale59bb192014-04-01 18:33:59 -0700121 Log.i(this, "Mark call as HOLD: %s", callId);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700122 try {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700123 mInCallService.setOnHold(callId);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700124 } catch (RemoteException e) {
Sailesh Nepal810735e2014-03-18 18:15:46 -0700125 }
126 break;
127 case DISCONNECTED:
Sailesh Nepale59bb192014-04-01 18:33:59 -0700128 Log.i(this, "Mark call as DISCONNECTED: %s", callId);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700129 try {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700130 mInCallService.setDisconnected(callId, call.getDisconnectCause());
Sailesh Nepal810735e2014-03-18 18:15:46 -0700131 } catch (RemoteException e) {
Sailesh Nepal810735e2014-03-18 18:15:46 -0700132 }
133 break;
134 default:
135 break;
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700136 }
137 }
138
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700139 @Override
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700140 public void onCallHandoffHandleChanged(Call call, Uri oldHandle, Uri newHandle) {
141 if (mInCallService != null) {
142 try {
143 mInCallService.setHandoffEnabled(mCallIdMapper.getCallId(call), newHandle != null);
144 } catch (RemoteException e) {
145 Log.e(this, e, "Exception attempting to call setHandoffEnabled.");
146 }
147 }
148 }
149
150 @Override
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700151 public void onAudioStateChanged(CallAudioState oldAudioState, CallAudioState newAudioState) {
152 if (mInCallService != null) {
153 Log.i(this, "Calling onAudioStateChanged, audioState: %s -> %s", oldAudioState,
154 newAudioState);
155 try {
156 mInCallService.onAudioStateChanged(newAudioState);
157 } catch (RemoteException e) {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700158 }
159 }
160 }
161
Santos Cordone3d76ab2014-01-28 17:25:20 -0800162 /**
163 * Unbinds an existing bound connection to the in-call app.
Santos Cordone3d76ab2014-01-28 17:25:20 -0800164 */
Sailesh Nepal810735e2014-03-18 18:15:46 -0700165 private void unbind() {
Santos Cordone3d76ab2014-01-28 17:25:20 -0800166 ThreadUtil.checkOnMainThread();
167 if (mInCallService != null) {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800168 Log.i(this, "Unbinding from InCallService");
Santos Cordon049b7b62014-01-30 05:34:26 -0800169 TelecommApp.getInstance().unbindService(mConnection);
Santos Cordone3d76ab2014-01-28 17:25:20 -0800170 mInCallService = null;
171 }
172 }
173
174 /**
Santos Cordon049b7b62014-01-30 05:34:26 -0800175 * Binds to the in-call app if not already connected by binding directly to the saved
176 * component name of the {@link IInCallService} implementation.
177 */
178 private void bind() {
179 ThreadUtil.checkOnMainThread();
180 if (mInCallService == null) {
181 ComponentName component =
182 new ComponentName(IN_CALL_PACKAGE_NAME, IN_CALL_SERVICE_CLASS_NAME);
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800183 Log.i(this, "Attempting to bind to InCallService: %s", component);
Santos Cordon049b7b62014-01-30 05:34:26 -0800184
185 Intent serviceIntent = new Intent(IInCallService.class.getName());
186 serviceIntent.setComponent(component);
187
188 Context context = TelecommApp.getInstance();
189 if (!context.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE)) {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800190 Log.w(this, "Could not connect to the in-call app (%s)", component);
Santos Cordon049b7b62014-01-30 05:34:26 -0800191
192 // TODO(santoscordon): Implement retry or fall-back-to-default logic.
193 }
194 }
195 }
196
197 /**
Santos Cordone3d76ab2014-01-28 17:25:20 -0800198 * Persists the {@link IInCallService} instance and starts the communication between
Sailesh Nepale59bb192014-04-01 18:33:59 -0700199 * 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 -0800200 * called after a successful binding connection is established.
201 *
202 * @param service The {@link IInCallService} implementation.
203 */
204 private void onConnected(IBinder service) {
205 ThreadUtil.checkOnMainThread();
206 mInCallService = IInCallService.Stub.asInterface(service);
207
208 try {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700209 mInCallService.setInCallAdapter(new InCallAdapter(CallsManager.getInstance(),
210 mCallIdMapper));
Santos Cordone3d76ab2014-01-28 17:25:20 -0800211 } catch (RemoteException e) {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800212 Log.e(this, e, "Failed to set the in-call adapter.");
Santos Cordone3d76ab2014-01-28 17:25:20 -0800213 mInCallService = null;
Sailesh Nepal810735e2014-03-18 18:15:46 -0700214 return;
Santos Cordone3d76ab2014-01-28 17:25:20 -0800215 }
216
Santos Cordon049b7b62014-01-30 05:34:26 -0800217 // Upon successful connection, send the state of the world to the in-call app.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700218 ImmutableCollection<Call> calls = CallsManager.getInstance().getCalls();
219 if (!calls.isEmpty()) {
220 for (Call call : calls) {
221 onCallAdded(call);
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700222 onCallHandoffHandleChanged(call, null, call.getHandoffHandle());
Sailesh Nepal810735e2014-03-18 18:15:46 -0700223 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700224 onAudioStateChanged(null, CallsManager.getInstance().getAudioState());
Sailesh Nepal810735e2014-03-18 18:15:46 -0700225 } else {
226 unbind();
Santos Cordon049b7b62014-01-30 05:34:26 -0800227 }
Santos Cordone3d76ab2014-01-28 17:25:20 -0800228 }
229
230 /**
231 * Cleans up the instance of in-call app after the service has been unbound.
232 */
233 private void onDisconnected() {
234 ThreadUtil.checkOnMainThread();
235 mInCallService = null;
236 }
Santos Cordone3d76ab2014-01-28 17:25:20 -0800237}