blob: 18651e267a8f1c6035ccaba3ac7655535ec6472b [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;
Santos Cordone3d76ab2014-01-28 17:25:20 -080026import android.telecomm.IInCallService;
Santos Cordone3d76ab2014-01-28 17:25:20 -080027
28/**
29 * Binds to {@link IInCallService} and provides the service to {@link CallsManager} through which it
30 * can send updates to the in-call app. This class is created and owned by CallsManager and retains
31 * a binding to the {@link IInCallService} (implemented by the in-call app) until CallsManager
32 * explicitly disconnects it. CallsManager starts the connection by calling {@link #connect} and
33 * retains the connection as long as it has calls which need UI. When all calls are disconnected,
34 * CallsManager will invoke {@link #disconnect} to sever the binding until the in-call UI is needed
35 * again.
36 */
37public final class InCallController {
38 /**
39 * Used to bind to the in-call app and triggers the start of communication between
40 * CallsManager and in-call app.
41 */
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 */
Santos Cordon049b7b62014-01-30 05:34:26 -080065 private static final String IN_CALL_SERVICE_CLASS_NAME = "com.android.incallui.InCallService";
Santos Cordone3d76ab2014-01-28 17:25:20 -080066
67 /** Maintains a binding connection to the in-call app. */
68 private final InCallServiceConnection mConnection = new InCallServiceConnection();
69
70 private final CallsManager mCallsManager;
71
72 /** The in-call app implementation, see {@link IInCallService}. */
73 private IInCallService mInCallService;
74
75 /**
76 * Persists the specified parameters.
Santos Cordone3d76ab2014-01-28 17:25:20 -080077 */
Santos Cordon6242b132014-02-07 16:24:42 -080078 InCallController(CallsManager callsManager) {
79 mCallsManager = callsManager;
Santos Cordone3d76ab2014-01-28 17:25:20 -080080 }
81
82 // TODO(santoscordon): May be better to expose the IInCallService methods directly from this
83 // class as its own method to make the CallsManager code easier to read.
84 IInCallService getService() {
85 return mInCallService;
86 }
87
88 /**
Santos Cordon049b7b62014-01-30 05:34:26 -080089 * Indicates to the in-call app that a new call has been created and an appropriate
90 * user-interface should be built and shown to notify the user. Information about the call
91 * including its current state is passed in through the callInfo object.
Santos Cordone3d76ab2014-01-28 17:25:20 -080092 *
Santos Cordon049b7b62014-01-30 05:34:26 -080093 * @param callInfo Details about the new call.
Santos Cordone3d76ab2014-01-28 17:25:20 -080094 */
Santos Cordon049b7b62014-01-30 05:34:26 -080095 void addCall(CallInfo callInfo) {
96 try {
97 if (mInCallService == null) {
98 bind();
99 } else {
100 // TODO(santoscordon): Protect against logging phone number.
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800101 Log.i(this, "Adding call: %s", callInfo);
Santos Cordon049b7b62014-01-30 05:34:26 -0800102 mInCallService.addCall(callInfo);
Santos Cordone3d76ab2014-01-28 17:25:20 -0800103 }
Santos Cordon049b7b62014-01-30 05:34:26 -0800104 } catch (RemoteException e) {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800105 Log.e(this, e, "Exception attempting to addCall.");
Santos Cordon049b7b62014-01-30 05:34:26 -0800106 }
107 }
108
109 /**
110 * Indicates to the in-call app that a call has moved to the active state.
111 *
112 * @param callId The identifier of the call that became active.
113 */
114 void markCallAsActive(String callId) {
115 try {
116 if (mInCallService != null) {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800117 Log.i(this, "Mark call as ACTIVE: %s", callId);
Santos Cordon049b7b62014-01-30 05:34:26 -0800118 mInCallService.setActive(callId);
119 }
120 } catch (RemoteException e) {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800121 Log.e(this, e, "Exception attempting to markCallAsActive.");
Santos Cordon049b7b62014-01-30 05:34:26 -0800122 }
123 }
124
125 /**
126 * Indicates to the in-call app that a call has been disconnected and the user should be
127 * notified.
128 *
129 * @param callId The identifier of the call that was disconnected.
130 */
131 void markCallAsDisconnected(String callId) {
132 try {
133 if (mInCallService != null) {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800134 Log.i(this, "Mark call as DISCONNECTED: %s", callId);
Santos Cordon049b7b62014-01-30 05:34:26 -0800135 mInCallService.setDisconnected(callId);
136 }
137 } catch (RemoteException e) {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800138 Log.e(this, e, "Exception attempting to markCallAsDisconnected.");
Santos Cordone3d76ab2014-01-28 17:25:20 -0800139 }
140 }
141
142 /**
143 * Unbinds an existing bound connection to the in-call app.
Santos Cordone3d76ab2014-01-28 17:25:20 -0800144 */
Santos Cordon049b7b62014-01-30 05:34:26 -0800145 void unbind() {
Santos Cordone3d76ab2014-01-28 17:25:20 -0800146 ThreadUtil.checkOnMainThread();
147 if (mInCallService != null) {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800148 Log.i(this, "Unbinding from InCallService");
Santos Cordon049b7b62014-01-30 05:34:26 -0800149 TelecommApp.getInstance().unbindService(mConnection);
Santos Cordone3d76ab2014-01-28 17:25:20 -0800150 mInCallService = null;
151 }
152 }
153
154 /**
Santos Cordon049b7b62014-01-30 05:34:26 -0800155 * Binds to the in-call app if not already connected by binding directly to the saved
156 * component name of the {@link IInCallService} implementation.
157 */
158 private void bind() {
159 ThreadUtil.checkOnMainThread();
160 if (mInCallService == null) {
161 ComponentName component =
162 new ComponentName(IN_CALL_PACKAGE_NAME, IN_CALL_SERVICE_CLASS_NAME);
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800163 Log.i(this, "Attempting to bind to InCallService: %s", component);
Santos Cordon049b7b62014-01-30 05:34:26 -0800164
165 Intent serviceIntent = new Intent(IInCallService.class.getName());
166 serviceIntent.setComponent(component);
167
168 Context context = TelecommApp.getInstance();
169 if (!context.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE)) {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800170 Log.w(this, "Could not connect to the in-call app (%s)", component);
Santos Cordon049b7b62014-01-30 05:34:26 -0800171
172 // TODO(santoscordon): Implement retry or fall-back-to-default logic.
173 }
174 }
175 }
176
177 /**
Santos Cordone3d76ab2014-01-28 17:25:20 -0800178 * Persists the {@link IInCallService} instance and starts the communication between
179 * CallsManager and in-call app by sending the first update to in-call app. This method is
180 * called after a successful binding connection is established.
181 *
182 * @param service The {@link IInCallService} implementation.
183 */
184 private void onConnected(IBinder service) {
185 ThreadUtil.checkOnMainThread();
186 mInCallService = IInCallService.Stub.asInterface(service);
187
188 try {
189 mInCallService.setInCallAdapter(new InCallAdapter(mCallsManager));
190 } catch (RemoteException e) {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800191 Log.e(this, e, "Failed to set the in-call adapter.");
Santos Cordone3d76ab2014-01-28 17:25:20 -0800192 mInCallService = null;
193 }
194
Santos Cordon049b7b62014-01-30 05:34:26 -0800195 // Upon successful connection, send the state of the world to the in-call app.
196 if (mInCallService != null) {
197 mCallsManager.updateInCall();
198 }
199
Santos Cordone3d76ab2014-01-28 17:25:20 -0800200 }
201
202 /**
203 * Cleans up the instance of in-call app after the service has been unbound.
204 */
205 private void onDisconnected() {
206 ThreadUtil.checkOnMainThread();
207 mInCallService = null;
208 }
Santos Cordone3d76ab2014-01-28 17:25:20 -0800209}