blob: d975b0bc3e4e0f9cc8b8e93479ce39798700f296 [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
Sailesh Nepale59bb192014-04-01 18:33:59 -070035 * a binding to the {@link IInCallService} (implemented by the in-call app).
Santos Cordone3d76ab2014-01-28 17:25:20 -080036 */
Sailesh Nepal810735e2014-03-18 18:15:46 -070037public final class InCallController extends CallsManagerListenerBase {
Santos Cordone3d76ab2014-01-28 17:25:20 -080038 /**
39 * Used to bind to the in-call app and triggers the start of communication between
Sailesh Nepale59bb192014-04-01 18:33:59 -070040 * this class and in-call app.
Santos Cordone3d76ab2014-01-28 17:25:20 -080041 */
42 private class InCallServiceConnection implements ServiceConnection {
43 /** {@inheritDoc} */
44 @Override public void onServiceConnected(ComponentName name, IBinder service) {
45 onConnected(service);
46 }
47
48 /** {@inheritDoc} */
49 @Override public void onServiceDisconnected(ComponentName name) {
50 onDisconnected();
51 }
52 }
53
Santos Cordone3d76ab2014-01-28 17:25:20 -080054 /**
55 * 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 -080056 * ultimately compiled into the dialer APK, hence the difference in namespaces between this and
57 * {@link #IN_CALL_SERVICE_CLASS_NAME}.
Santos Cordone3d76ab2014-01-28 17:25:20 -080058 * TODO(santoscordon): Change this into config.xml resource entry.
59 */
60 private static final String IN_CALL_PACKAGE_NAME = "com.google.android.dialer";
61
62 /**
63 * Class name of the component within in-call app which implements {@link IInCallService}.
64 */
Sailesh Nepala439e1b2014-03-11 18:19:58 -070065 private static final String IN_CALL_SERVICE_CLASS_NAME =
66 "com.android.incallui.InCallServiceImpl";
Santos Cordone3d76ab2014-01-28 17:25:20 -080067
68 /** Maintains a binding connection to the in-call app. */
69 private final InCallServiceConnection mConnection = new InCallServiceConnection();
70
Santos Cordone3d76ab2014-01-28 17:25:20 -080071 /** The in-call app implementation, see {@link IInCallService}. */
72 private IInCallService mInCallService;
73
Sailesh Nepale59bb192014-04-01 18:33:59 -070074 private final CallIdMapper mCallIdMapper = new CallIdMapper("InCall");
75
Santos Cordone3d76ab2014-01-28 17:25:20 -080076 IInCallService getService() {
77 return mInCallService;
78 }
79
Sailesh Nepal810735e2014-03-18 18:15:46 -070080 @Override
81 public void onCallAdded(Call call) {
82 if (mInCallService == null) {
83 bind();
84 } else {
85 Log.i(this, "Adding call: %s", call);
Sailesh Nepale59bb192014-04-01 18:33:59 -070086 mCallIdMapper.addCall(call);
87 CallInfo callInfo = call.toCallInfo(mCallIdMapper.getCallId(call));
Sailesh Nepal810735e2014-03-18 18:15:46 -070088 try {
Sailesh Nepale59bb192014-04-01 18:33:59 -070089 mInCallService.addCall(callInfo);
Sailesh Nepal810735e2014-03-18 18:15:46 -070090 } catch (RemoteException e) {
Santos Cordone3d76ab2014-01-28 17:25:20 -080091 }
Santos Cordon049b7b62014-01-30 05:34:26 -080092 }
93 }
94
Sailesh Nepal810735e2014-03-18 18:15:46 -070095 @Override
96 public void onCallRemoved(Call call) {
97 if (CallsManager.getInstance().getCalls().isEmpty()) {
98 // TODO(sail): Wait for all messages to be delivered to the service before unbinding.
99 unbind();
Santos Cordon049b7b62014-01-30 05:34:26 -0800100 }
Sailesh Nepale59bb192014-04-01 18:33:59 -0700101 mCallIdMapper.removeCall(call);
Santos Cordon049b7b62014-01-30 05:34:26 -0800102 }
103
Sailesh Nepal810735e2014-03-18 18:15:46 -0700104 @Override
105 public void onCallStateChanged(Call call, CallState oldState, CallState newState) {
106 if (mInCallService == null) {
107 return;
Santos Cordone3d76ab2014-01-28 17:25:20 -0800108 }
Santos Cordone3d76ab2014-01-28 17:25:20 -0800109
Sailesh Nepale59bb192014-04-01 18:33:59 -0700110 String callId = mCallIdMapper.getCallId(call);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700111 switch (newState) {
112 case ACTIVE:
Sailesh Nepale59bb192014-04-01 18:33:59 -0700113 Log.i(this, "Mark call as ACTIVE: %s", callId);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700114 try {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700115 mInCallService.setActive(callId);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700116 } catch (RemoteException e) {
Sailesh Nepal810735e2014-03-18 18:15:46 -0700117 }
118 break;
119 case ON_HOLD:
Sailesh Nepale59bb192014-04-01 18:33:59 -0700120 Log.i(this, "Mark call as HOLD: %s", callId);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700121 try {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700122 mInCallService.setOnHold(callId);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700123 } catch (RemoteException e) {
Sailesh Nepal810735e2014-03-18 18:15:46 -0700124 }
125 break;
126 case DISCONNECTED:
Sailesh Nepale59bb192014-04-01 18:33:59 -0700127 Log.i(this, "Mark call as DISCONNECTED: %s", callId);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700128 try {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700129 mInCallService.setDisconnected(callId, call.getDisconnectCause());
Sailesh Nepal810735e2014-03-18 18:15:46 -0700130 } catch (RemoteException e) {
Sailesh Nepal810735e2014-03-18 18:15:46 -0700131 }
132 break;
133 default:
134 break;
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700135 }
136 }
137
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700138 @Override
139 public void onAudioStateChanged(CallAudioState oldAudioState, CallAudioState newAudioState) {
140 if (mInCallService != null) {
141 Log.i(this, "Calling onAudioStateChanged, audioState: %s -> %s", oldAudioState,
142 newAudioState);
143 try {
144 mInCallService.onAudioStateChanged(newAudioState);
145 } catch (RemoteException e) {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700146 }
147 }
148 }
149
Santos Cordone3d76ab2014-01-28 17:25:20 -0800150 /**
151 * Unbinds an existing bound connection to the in-call app.
Santos Cordone3d76ab2014-01-28 17:25:20 -0800152 */
Sailesh Nepal810735e2014-03-18 18:15:46 -0700153 private void unbind() {
Santos Cordone3d76ab2014-01-28 17:25:20 -0800154 ThreadUtil.checkOnMainThread();
155 if (mInCallService != null) {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800156 Log.i(this, "Unbinding from InCallService");
Santos Cordon049b7b62014-01-30 05:34:26 -0800157 TelecommApp.getInstance().unbindService(mConnection);
Santos Cordone3d76ab2014-01-28 17:25:20 -0800158 mInCallService = null;
159 }
160 }
161
162 /**
Santos Cordon049b7b62014-01-30 05:34:26 -0800163 * Binds to the in-call app if not already connected by binding directly to the saved
164 * component name of the {@link IInCallService} implementation.
165 */
166 private void bind() {
167 ThreadUtil.checkOnMainThread();
168 if (mInCallService == null) {
169 ComponentName component =
170 new ComponentName(IN_CALL_PACKAGE_NAME, IN_CALL_SERVICE_CLASS_NAME);
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800171 Log.i(this, "Attempting to bind to InCallService: %s", component);
Santos Cordon049b7b62014-01-30 05:34:26 -0800172
173 Intent serviceIntent = new Intent(IInCallService.class.getName());
174 serviceIntent.setComponent(component);
175
176 Context context = TelecommApp.getInstance();
177 if (!context.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE)) {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800178 Log.w(this, "Could not connect to the in-call app (%s)", component);
Santos Cordon049b7b62014-01-30 05:34:26 -0800179
180 // TODO(santoscordon): Implement retry or fall-back-to-default logic.
181 }
182 }
183 }
184
185 /**
Santos Cordone3d76ab2014-01-28 17:25:20 -0800186 * Persists the {@link IInCallService} instance and starts the communication between
Sailesh Nepale59bb192014-04-01 18:33:59 -0700187 * 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 -0800188 * called after a successful binding connection is established.
189 *
190 * @param service The {@link IInCallService} implementation.
191 */
192 private void onConnected(IBinder service) {
193 ThreadUtil.checkOnMainThread();
194 mInCallService = IInCallService.Stub.asInterface(service);
195
196 try {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700197 mInCallService.setInCallAdapter(new InCallAdapter(CallsManager.getInstance(),
198 mCallIdMapper));
Santos Cordone3d76ab2014-01-28 17:25:20 -0800199 } catch (RemoteException e) {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800200 Log.e(this, e, "Failed to set the in-call adapter.");
Santos Cordone3d76ab2014-01-28 17:25:20 -0800201 mInCallService = null;
Sailesh Nepal810735e2014-03-18 18:15:46 -0700202 return;
Santos Cordone3d76ab2014-01-28 17:25:20 -0800203 }
204
Santos Cordon049b7b62014-01-30 05:34:26 -0800205 // Upon successful connection, send the state of the world to the in-call app.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700206 ImmutableCollection<Call> calls = CallsManager.getInstance().getCalls();
207 if (!calls.isEmpty()) {
208 for (Call call : calls) {
209 onCallAdded(call);
210 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700211 onAudioStateChanged(null, CallsManager.getInstance().getAudioState());
Sailesh Nepal810735e2014-03-18 18:15:46 -0700212 } else {
213 unbind();
Santos Cordon049b7b62014-01-30 05:34:26 -0800214 }
Santos Cordone3d76ab2014-01-28 17:25:20 -0800215 }
216
217 /**
218 * Cleans up the instance of in-call app after the service has been unbound.
219 */
220 private void onDisconnected() {
221 ThreadUtil.checkOnMainThread();
222 mInCallService = null;
223 }
Santos Cordone3d76ab2014-01-28 17:25:20 -0800224}