blob: 0bfe30d407cd6216cd03242e8b26f799a5fd57d7 [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;
Santos Cordon049b7b62014-01-30 05:34:26 -080025import android.telecomm.CallInfo;
Sailesh Nepal810735e2014-03-18 18:15:46 -070026import android.telecomm.CallState;
Sailesh Nepala439e1b2014-03-11 18:19:58 -070027
28import com.android.internal.telecomm.IInCallService;
Sailesh Nepal810735e2014-03-18 18:15:46 -070029import com.google.common.collect.ImmutableCollection;
Santos Cordone3d76ab2014-01-28 17:25:20 -080030
31/**
32 * Binds to {@link IInCallService} and provides the service to {@link CallsManager} through which it
33 * can send updates to the in-call app. This class is created and owned by CallsManager and retains
34 * a binding to the {@link IInCallService} (implemented by the in-call app) until CallsManager
35 * explicitly disconnects it. CallsManager starts the connection by calling {@link #connect} and
36 * retains the connection as long as it has calls which need UI. When all calls are disconnected,
37 * CallsManager will invoke {@link #disconnect} to sever the binding until the in-call UI is needed
38 * again.
39 */
Sailesh Nepal810735e2014-03-18 18:15:46 -070040public final class InCallController extends CallsManagerListenerBase {
Santos Cordone3d76ab2014-01-28 17:25:20 -080041 /**
42 * Used to bind to the in-call app and triggers the start of communication between
43 * CallsManager and in-call app.
44 */
45 private class InCallServiceConnection implements ServiceConnection {
46 /** {@inheritDoc} */
47 @Override public void onServiceConnected(ComponentName name, IBinder service) {
48 onConnected(service);
49 }
50
51 /** {@inheritDoc} */
52 @Override public void onServiceDisconnected(ComponentName name) {
53 onDisconnected();
54 }
55 }
56
Santos Cordone3d76ab2014-01-28 17:25:20 -080057 /**
58 * 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 -080059 * ultimately compiled into the dialer APK, hence the difference in namespaces between this and
60 * {@link #IN_CALL_SERVICE_CLASS_NAME}.
Santos Cordone3d76ab2014-01-28 17:25:20 -080061 * TODO(santoscordon): Change this into config.xml resource entry.
62 */
63 private static final String IN_CALL_PACKAGE_NAME = "com.google.android.dialer";
64
65 /**
66 * Class name of the component within in-call app which implements {@link IInCallService}.
67 */
Sailesh Nepala439e1b2014-03-11 18:19:58 -070068 private static final String IN_CALL_SERVICE_CLASS_NAME =
69 "com.android.incallui.InCallServiceImpl";
Santos Cordone3d76ab2014-01-28 17:25:20 -080070
71 /** Maintains a binding connection to the in-call app. */
72 private final InCallServiceConnection mConnection = new InCallServiceConnection();
73
Santos Cordone3d76ab2014-01-28 17:25:20 -080074 /** The in-call app implementation, see {@link IInCallService}. */
75 private IInCallService mInCallService;
76
Santos Cordone3d76ab2014-01-28 17:25:20 -080077 // TODO(santoscordon): May be better to expose the IInCallService methods directly from this
78 // class as its own method to make the CallsManager code easier to read.
79 IInCallService getService() {
80 return mInCallService;
81 }
82
Sailesh Nepal810735e2014-03-18 18:15:46 -070083 @Override
84 public void onCallAdded(Call call) {
85 if (mInCallService == null) {
86 bind();
87 } else {
88 Log.i(this, "Adding call: %s", call);
89 try {
90 mInCallService.addCall(call.toCallInfo());
91 } catch (RemoteException e) {
92 Log.e(this, e, "Exception attempting to addCall.");
Santos Cordone3d76ab2014-01-28 17:25:20 -080093 }
Santos Cordon049b7b62014-01-30 05:34:26 -080094 }
95 }
96
Sailesh Nepal810735e2014-03-18 18:15:46 -070097 @Override
98 public void onCallRemoved(Call call) {
99 if (CallsManager.getInstance().getCalls().isEmpty()) {
100 // TODO(sail): Wait for all messages to be delivered to the service before unbinding.
101 unbind();
Santos Cordon049b7b62014-01-30 05:34:26 -0800102 }
103 }
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 Nepal810735e2014-03-18 18:15:46 -0700111 switch (newState) {
112 case ACTIVE:
113 Log.i(this, "Mark call as ACTIVE: %s", call.getId());
114 try {
115 mInCallService.setActive(call.getId());
116 } catch (RemoteException e) {
117 Log.e(this, e, "Exception attempting to call setActive.");
118 }
119 break;
120 case ON_HOLD:
121 Log.i(this, "Mark call as HOLD: %s", call.getId());
122 try {
123 mInCallService.setOnHold(call.getId());
124 } catch (RemoteException e) {
125 Log.e(this, e, "Exception attempting to call setOnHold.");
126 }
127 break;
128 case DISCONNECTED:
129 Log.i(this, "Mark call as DISCONNECTED: %s", call.getId());
130 try {
131 mInCallService.setDisconnected(call.getId());
132 } catch (RemoteException e) {
133 Log.e(this, e, "Exception attempting to call setDisconnected.");
134 }
135 break;
136 default:
137 break;
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700138 }
139 }
140
Santos Cordone3d76ab2014-01-28 17:25:20 -0800141 /**
142 * Unbinds an existing bound connection to the in-call app.
Santos Cordone3d76ab2014-01-28 17:25:20 -0800143 */
Sailesh Nepal810735e2014-03-18 18:15:46 -0700144 private void unbind() {
Santos Cordone3d76ab2014-01-28 17:25:20 -0800145 ThreadUtil.checkOnMainThread();
146 if (mInCallService != null) {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800147 Log.i(this, "Unbinding from InCallService");
Santos Cordon049b7b62014-01-30 05:34:26 -0800148 TelecommApp.getInstance().unbindService(mConnection);
Santos Cordone3d76ab2014-01-28 17:25:20 -0800149 mInCallService = null;
150 }
151 }
152
153 /**
Santos Cordon049b7b62014-01-30 05:34:26 -0800154 * Binds to the in-call app if not already connected by binding directly to the saved
155 * component name of the {@link IInCallService} implementation.
156 */
157 private void bind() {
158 ThreadUtil.checkOnMainThread();
159 if (mInCallService == null) {
160 ComponentName component =
161 new ComponentName(IN_CALL_PACKAGE_NAME, IN_CALL_SERVICE_CLASS_NAME);
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800162 Log.i(this, "Attempting to bind to InCallService: %s", component);
Santos Cordon049b7b62014-01-30 05:34:26 -0800163
164 Intent serviceIntent = new Intent(IInCallService.class.getName());
165 serviceIntent.setComponent(component);
166
167 Context context = TelecommApp.getInstance();
168 if (!context.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE)) {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800169 Log.w(this, "Could not connect to the in-call app (%s)", component);
Santos Cordon049b7b62014-01-30 05:34:26 -0800170
171 // TODO(santoscordon): Implement retry or fall-back-to-default logic.
172 }
173 }
174 }
175
176 /**
Santos Cordone3d76ab2014-01-28 17:25:20 -0800177 * Persists the {@link IInCallService} instance and starts the communication between
178 * CallsManager and in-call app by sending the first update to in-call app. This method is
179 * called after a successful binding connection is established.
180 *
181 * @param service The {@link IInCallService} implementation.
182 */
183 private void onConnected(IBinder service) {
184 ThreadUtil.checkOnMainThread();
185 mInCallService = IInCallService.Stub.asInterface(service);
186
187 try {
Sailesh Nepal810735e2014-03-18 18:15:46 -0700188 mInCallService.setInCallAdapter(new InCallAdapter(CallsManager.getInstance()));
Santos Cordone3d76ab2014-01-28 17:25:20 -0800189 } catch (RemoteException e) {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800190 Log.e(this, e, "Failed to set the in-call adapter.");
Santos Cordone3d76ab2014-01-28 17:25:20 -0800191 mInCallService = null;
Sailesh Nepal810735e2014-03-18 18:15:46 -0700192 return;
Santos Cordone3d76ab2014-01-28 17:25:20 -0800193 }
194
Santos Cordon049b7b62014-01-30 05:34:26 -0800195 // Upon successful connection, send the state of the world to the in-call app.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700196 ImmutableCollection<Call> calls = CallsManager.getInstance().getCalls();
197 if (!calls.isEmpty()) {
198 for (Call call : calls) {
199 onCallAdded(call);
200 }
201 } else {
202 unbind();
Santos Cordon049b7b62014-01-30 05:34:26 -0800203 }
Santos Cordone3d76ab2014-01-28 17:25:20 -0800204 }
205
206 /**
207 * Cleans up the instance of in-call app after the service has been unbound.
208 */
209 private void onDisconnected() {
210 ThreadUtil.checkOnMainThread();
211 mInCallService = null;
212 }
Santos Cordone3d76ab2014-01-28 17:25:20 -0800213}