blob: 5fbf1f81f50e86096369a0743a4b6d78d1fa9ad6 [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;
23import android.os.IBinder;
24import android.os.RemoteException;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070025import android.telecomm.CallAudioState;
Santos Cordon049b7b62014-01-30 05:34:26 -080026import android.telecomm.CallInfo;
Sailesh Nepal810735e2014-03-18 18:15:46 -070027import android.telecomm.CallState;
Sailesh Nepala439e1b2014-03-11 18:19:58 -070028
29import com.android.internal.telecomm.IInCallService;
Sailesh Nepal810735e2014-03-18 18:15:46 -070030import com.google.common.collect.ImmutableCollection;
Santos Cordone3d76ab2014-01-28 17:25:20 -080031
32/**
33 * Binds to {@link IInCallService} and provides the service to {@link CallsManager} through which it
34 * can send updates to the in-call app. This class is created and owned by CallsManager and retains
35 * a binding to the {@link IInCallService} (implemented by the in-call app) until CallsManager
36 * explicitly disconnects it. CallsManager starts the connection by calling {@link #connect} and
37 * retains the connection as long as it has calls which need UI. When all calls are disconnected,
38 * CallsManager will invoke {@link #disconnect} to sever the binding until the in-call UI is needed
39 * again.
40 */
Sailesh Nepal810735e2014-03-18 18:15:46 -070041public final class InCallController extends CallsManagerListenerBase {
Santos Cordone3d76ab2014-01-28 17:25:20 -080042 /**
43 * Used to bind to the in-call app and triggers the start of communication between
44 * CallsManager and in-call app.
45 */
46 private class InCallServiceConnection implements ServiceConnection {
47 /** {@inheritDoc} */
48 @Override public void onServiceConnected(ComponentName name, IBinder service) {
49 onConnected(service);
50 }
51
52 /** {@inheritDoc} */
53 @Override public void onServiceDisconnected(ComponentName name) {
54 onDisconnected();
55 }
56 }
57
Santos Cordone3d76ab2014-01-28 17:25:20 -080058 /**
59 * 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 -080060 * ultimately compiled into the dialer APK, hence the difference in namespaces between this and
61 * {@link #IN_CALL_SERVICE_CLASS_NAME}.
Santos Cordone3d76ab2014-01-28 17:25:20 -080062 * TODO(santoscordon): Change this into config.xml resource entry.
63 */
64 private static final String IN_CALL_PACKAGE_NAME = "com.google.android.dialer";
65
66 /**
67 * Class name of the component within in-call app which implements {@link IInCallService}.
68 */
Sailesh Nepala439e1b2014-03-11 18:19:58 -070069 private static final String IN_CALL_SERVICE_CLASS_NAME =
70 "com.android.incallui.InCallServiceImpl";
Santos Cordone3d76ab2014-01-28 17:25:20 -080071
72 /** Maintains a binding connection to the in-call app. */
73 private final InCallServiceConnection mConnection = new InCallServiceConnection();
74
Santos Cordone3d76ab2014-01-28 17:25:20 -080075 /** The in-call app implementation, see {@link IInCallService}. */
76 private IInCallService mInCallService;
77
Santos Cordone3d76ab2014-01-28 17:25:20 -080078 // TODO(santoscordon): May be better to expose the IInCallService methods directly from this
79 // class as its own method to make the CallsManager code easier to read.
80 IInCallService getService() {
81 return mInCallService;
82 }
83
Sailesh Nepal810735e2014-03-18 18:15:46 -070084 @Override
85 public void onCallAdded(Call call) {
86 if (mInCallService == null) {
87 bind();
88 } else {
89 Log.i(this, "Adding call: %s", call);
90 try {
91 mInCallService.addCall(call.toCallInfo());
92 } catch (RemoteException e) {
93 Log.e(this, e, "Exception attempting to addCall.");
Santos Cordone3d76ab2014-01-28 17:25:20 -080094 }
Santos Cordon049b7b62014-01-30 05:34:26 -080095 }
96 }
97
Sailesh Nepal810735e2014-03-18 18:15:46 -070098 @Override
99 public void onCallRemoved(Call call) {
100 if (CallsManager.getInstance().getCalls().isEmpty()) {
101 // TODO(sail): Wait for all messages to be delivered to the service before unbinding.
102 unbind();
Santos Cordon049b7b62014-01-30 05:34:26 -0800103 }
104 }
105
Sailesh Nepal810735e2014-03-18 18:15:46 -0700106 @Override
107 public void onCallStateChanged(Call call, CallState oldState, CallState newState) {
108 if (mInCallService == null) {
109 return;
Santos Cordone3d76ab2014-01-28 17:25:20 -0800110 }
Santos Cordone3d76ab2014-01-28 17:25:20 -0800111
Sailesh Nepal810735e2014-03-18 18:15:46 -0700112 switch (newState) {
113 case ACTIVE:
114 Log.i(this, "Mark call as ACTIVE: %s", call.getId());
115 try {
116 mInCallService.setActive(call.getId());
117 } catch (RemoteException e) {
118 Log.e(this, e, "Exception attempting to call setActive.");
119 }
120 break;
121 case ON_HOLD:
122 Log.i(this, "Mark call as HOLD: %s", call.getId());
123 try {
124 mInCallService.setOnHold(call.getId());
125 } catch (RemoteException e) {
126 Log.e(this, e, "Exception attempting to call setOnHold.");
127 }
128 break;
129 case DISCONNECTED:
130 Log.i(this, "Mark call as DISCONNECTED: %s", call.getId());
131 try {
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700132 mInCallService.setDisconnected(call.getId(), call.getDisconnectCause());
Sailesh Nepal810735e2014-03-18 18:15:46 -0700133 } catch (RemoteException e) {
134 Log.e(this, e, "Exception attempting to call setDisconnected.");
135 }
136 break;
137 default:
138 break;
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700139 }
140 }
141
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700142 @Override
143 public void onAudioStateChanged(CallAudioState oldAudioState, CallAudioState newAudioState) {
144 if (mInCallService != null) {
145 Log.i(this, "Calling onAudioStateChanged, audioState: %s -> %s", oldAudioState,
146 newAudioState);
147 try {
148 mInCallService.onAudioStateChanged(newAudioState);
149 } catch (RemoteException e) {
150 Log.e(this, e, "Exception attempting to update audio state.");
151 }
152 }
153 }
154
Santos Cordone3d76ab2014-01-28 17:25:20 -0800155 /**
156 * Unbinds an existing bound connection to the in-call app.
Santos Cordone3d76ab2014-01-28 17:25:20 -0800157 */
Sailesh Nepal810735e2014-03-18 18:15:46 -0700158 private void unbind() {
Santos Cordone3d76ab2014-01-28 17:25:20 -0800159 ThreadUtil.checkOnMainThread();
160 if (mInCallService != null) {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800161 Log.i(this, "Unbinding from InCallService");
Santos Cordon049b7b62014-01-30 05:34:26 -0800162 TelecommApp.getInstance().unbindService(mConnection);
Santos Cordone3d76ab2014-01-28 17:25:20 -0800163 mInCallService = null;
164 }
165 }
166
167 /**
Santos Cordon049b7b62014-01-30 05:34:26 -0800168 * Binds to the in-call app if not already connected by binding directly to the saved
169 * component name of the {@link IInCallService} implementation.
170 */
171 private void bind() {
172 ThreadUtil.checkOnMainThread();
173 if (mInCallService == null) {
174 ComponentName component =
175 new ComponentName(IN_CALL_PACKAGE_NAME, IN_CALL_SERVICE_CLASS_NAME);
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800176 Log.i(this, "Attempting to bind to InCallService: %s", component);
Santos Cordon049b7b62014-01-30 05:34:26 -0800177
178 Intent serviceIntent = new Intent(IInCallService.class.getName());
179 serviceIntent.setComponent(component);
180
181 Context context = TelecommApp.getInstance();
182 if (!context.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE)) {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800183 Log.w(this, "Could not connect to the in-call app (%s)", component);
Santos Cordon049b7b62014-01-30 05:34:26 -0800184
185 // TODO(santoscordon): Implement retry or fall-back-to-default logic.
186 }
187 }
188 }
189
190 /**
Santos Cordone3d76ab2014-01-28 17:25:20 -0800191 * Persists the {@link IInCallService} instance and starts the communication between
192 * CallsManager and in-call app by sending the first update to in-call app. This method is
193 * called after a successful binding connection is established.
194 *
195 * @param service The {@link IInCallService} implementation.
196 */
197 private void onConnected(IBinder service) {
198 ThreadUtil.checkOnMainThread();
199 mInCallService = IInCallService.Stub.asInterface(service);
200
201 try {
Sailesh Nepal810735e2014-03-18 18:15:46 -0700202 mInCallService.setInCallAdapter(new InCallAdapter(CallsManager.getInstance()));
Santos Cordone3d76ab2014-01-28 17:25:20 -0800203 } catch (RemoteException e) {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800204 Log.e(this, e, "Failed to set the in-call adapter.");
Santos Cordone3d76ab2014-01-28 17:25:20 -0800205 mInCallService = null;
Sailesh Nepal810735e2014-03-18 18:15:46 -0700206 return;
Santos Cordone3d76ab2014-01-28 17:25:20 -0800207 }
208
Santos Cordon049b7b62014-01-30 05:34:26 -0800209 // Upon successful connection, send the state of the world to the in-call app.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700210 ImmutableCollection<Call> calls = CallsManager.getInstance().getCalls();
211 if (!calls.isEmpty()) {
212 for (Call call : calls) {
213 onCallAdded(call);
214 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700215 onAudioStateChanged(null, CallsManager.getInstance().getAudioState());
Sailesh Nepal810735e2014-03-18 18:15:46 -0700216 } else {
217 unbind();
Santos Cordon049b7b62014-01-30 05:34:26 -0800218 }
Santos Cordone3d76ab2014-01-28 17:25:20 -0800219 }
220
221 /**
222 * Cleans up the instance of in-call app after the service has been unbound.
223 */
224 private void onDisconnected() {
225 ThreadUtil.checkOnMainThread();
226 mInCallService = null;
227 }
Santos Cordone3d76ab2014-01-28 17:25:20 -0800228}