blob: 1c5f15dfee0ba5fb0221fbd17a376af5f74f0517 [file] [log] [blame]
Vitalii Tomkiv6e5ee612016-03-09 14:57:32 -08001/*
2 * Copyright (C) 2016 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 */
16package com.android.car;
17
18import android.car.CarProjectionManager;
19import android.car.ICarProjection;
20import android.car.ICarProjectionListener;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.Intent;
24import android.content.ServiceConnection;
25import android.os.Binder;
Vitalii Tomkiv6e5ee612016-03-09 14:57:32 -080026import android.os.IBinder;
Vitalii Tomkiv6e5ee612016-03-09 14:57:32 -080027import android.os.RemoteException;
28import android.os.UserHandle;
29import android.util.Log;
30import android.view.KeyEvent;
31
32import java.io.PrintWriter;
33
34/**
35 * Car projection service allows to bound to projected app to boost it prioirity.
36 * It also enables proejcted applications to handle voice action requests.
37 */
38class CarProjectionService extends ICarProjection.Stub implements CarServiceBase,
39 BinderInterfaceContainer.BinderEventHandler<ICarProjectionListener> {
40 private final ListenerHolder mAllListeners;
41 private final CarInputService mCarInputService;
42 private final Context mContext;
43
44 private final CarInputService.KeyEventListener mVoiceAssistantKeyListener =
45 new CarInputService.KeyEventListener() {
46 @Override
Yao Chenc4d442f2016-04-08 11:33:47 -070047 public boolean onKeyEvent(KeyEvent event) {
Vitalii Tomkiv6e5ee612016-03-09 14:57:32 -080048 handleVoiceAssitantRequest(false);
Yao Chenc4d442f2016-04-08 11:33:47 -070049 return true;
Vitalii Tomkiv6e5ee612016-03-09 14:57:32 -080050 }
51 };
52
53 private final CarInputService.KeyEventListener mLongVoiceAssistantKeyListener =
54 new CarInputService.KeyEventListener() {
55 @Override
Yao Chenc4d442f2016-04-08 11:33:47 -070056 public boolean onKeyEvent(KeyEvent event) {
Vitalii Tomkiv6e5ee612016-03-09 14:57:32 -080057 handleVoiceAssitantRequest(true);
Yao Chenc4d442f2016-04-08 11:33:47 -070058 return true;
Vitalii Tomkiv6e5ee612016-03-09 14:57:32 -080059 }
60 };
61
62 private final ServiceConnection mConnection = new ServiceConnection() {
63 @Override
64 public void onServiceConnected(ComponentName className, IBinder service) {
65 synchronized (CarProjectionService.this) {
66 mBound = true;
67 }
68 }
69
70 @Override
71 public void onServiceDisconnected(ComponentName className) {
72 // Service has crashed.
73 Log.w(CarLog.TAG_PROJECTION, "Service disconnected: " + className);
74 synchronized (CarProjectionService.this) {
75 mRegisteredService = null;
76 }
77 unbindServiceIfBound();
78 }
79 };
80
81 private boolean mBound;
82 private Intent mRegisteredService;
83
84 CarProjectionService(Context context, CarInputService carInputService) {
85 mContext = context;
86 mCarInputService = carInputService;
87 mAllListeners = new ListenerHolder(this);
88 }
89
90 @Override
91 public void registerProjectionRunner(Intent serviceIntent) {
92 // We assume one active projection app running in the system at one time.
93 synchronized (this) {
94 if (serviceIntent.filterEquals(mRegisteredService)) {
95 return;
96 }
97 if (mRegisteredService != null) {
98 Log.w(CarLog.TAG_PROJECTION, "Registering new service[" + serviceIntent
99 + "] while old service[" + mRegisteredService + "] is still running");
100 }
101 unbindServiceIfBound();
102 }
103 bindToService(serviceIntent);
104 }
105
106 @Override
107 public void unregisterProjectionRunner(Intent serviceIntent) {
108 synchronized (this) {
109 if (!serviceIntent.filterEquals(mRegisteredService)) {
110 Log.w(CarLog.TAG_PROJECTION, "Request to unbind unregistered service["
111 + serviceIntent + "]. Registered service[" + mRegisteredService + "]");
112 return;
113 }
114 mRegisteredService = null;
115 }
116 unbindServiceIfBound();
117 }
118
119 private void bindToService(Intent serviceIntent) {
120 synchronized (this) {
121 mRegisteredService = serviceIntent;
122 }
123 UserHandle userHandle = UserHandle.getUserHandleForUid(Binder.getCallingUid());
124 mContext.startServiceAsUser(serviceIntent, userHandle);
125 mContext.bindServiceAsUser(serviceIntent, mConnection, Context.BIND_IMPORTANT, userHandle);
126 }
127
128 private void unbindServiceIfBound() {
129 synchronized (this) {
130 if (!mBound) {
131 return;
132 }
133 mBound = false;
134 }
135 mContext.unbindService(mConnection);
136 }
137
138 private synchronized void handleVoiceAssitantRequest(boolean isTriggeredByLongPress) {
139 for (BinderInterfaceContainer.BinderInterface<ICarProjectionListener> listener :
140 mAllListeners.getInterfaces()) {
141 ListenerInfo listenerInfo = (ListenerInfo) listener;
142 if ((listenerInfo.hasFilter(CarProjectionManager.PROJECTION_LONG_PRESS_VOICE_SEARCH)
143 && isTriggeredByLongPress)
144 || (listenerInfo.hasFilter(CarProjectionManager.PROJECTION_VOICE_SEARCH)
145 && !isTriggeredByLongPress)) {
146 dispatchVoiceAssistantRequest(listenerInfo.binderInterface, isTriggeredByLongPress);
147 }
148 }
149 }
150
151 @Override
152 public void regsiterProjectionListener(ICarProjectionListener listener, int filter) {
153 synchronized (this) {
154 ListenerInfo info = (ListenerInfo) mAllListeners.getBinderInterface(listener);
155 if (info == null) {
156 info = new ListenerInfo(mAllListeners, listener, filter);
157 mAllListeners.addBinderInterface(info);
158 } else {
159 info.setFilter(filter);
160 }
161 }
162 updateCarInputServiceListeners();
163 }
164
165 @Override
166 public void unregsiterProjectionListener(ICarProjectionListener listener) {
167 synchronized (this) {
168 mAllListeners.removeBinder(listener);
169 }
170 updateCarInputServiceListeners();
171 }
172
173 private void updateCarInputServiceListeners() {
174 boolean listenShortPress = false;
175 boolean listenLongPress = false;
176 synchronized (this) {
177 for (BinderInterfaceContainer.BinderInterface<ICarProjectionListener> listener :
178 mAllListeners.getInterfaces()) {
179 ListenerInfo listenerInfo = (ListenerInfo) listener;
180 listenShortPress |= listenerInfo.hasFilter(
181 CarProjectionManager.PROJECTION_VOICE_SEARCH);
182 listenLongPress |= listenerInfo.hasFilter(
183 CarProjectionManager.PROJECTION_LONG_PRESS_VOICE_SEARCH);
184 }
185 }
Pavel Maltseve11ad3a2016-03-25 15:40:24 -0700186 mCarInputService.setVoiceAssistantKeyListener(listenShortPress
Vitalii Tomkiv6e5ee612016-03-09 14:57:32 -0800187 ? mVoiceAssistantKeyListener : null);
Pavel Maltseve11ad3a2016-03-25 15:40:24 -0700188 mCarInputService.setLongVoiceAssistantKeyListener(listenLongPress
Vitalii Tomkiv6e5ee612016-03-09 14:57:32 -0800189 ? mLongVoiceAssistantKeyListener : null);
190 }
191
192 @Override
193 public void init() {
194 // nothing to do
195 }
196
197 @Override
198 public void release() {
199 synchronized (this) {
200 mAllListeners.clear();
201 }
202 }
203
204 @Override
205 public void onBinderDeath(
206 BinderInterfaceContainer.BinderInterface<ICarProjectionListener> bInterface) {
207 unregsiterProjectionListener(bInterface.binderInterface);
208 }
209
210 @Override
211 public void dump(PrintWriter writer) {
212 writer.println("**CarProjectionService**");
213 synchronized (this) {
214 for (BinderInterfaceContainer.BinderInterface<ICarProjectionListener> listener :
215 mAllListeners.getInterfaces()) {
216 ListenerInfo listenerInfo = (ListenerInfo) listener;
217 writer.println(listenerInfo.toString());
218 }
219 }
220 }
221
222 private void dispatchVoiceAssistantRequest(ICarProjectionListener listener,
223 boolean fromLongPress) {
224 try {
225 listener.onVoiceAssistantRequest(fromLongPress);
226 } catch (RemoteException e) {
227 }
228 }
229
230 private static class ListenerHolder extends BinderInterfaceContainer<ICarProjectionListener> {
231 private ListenerHolder(CarProjectionService service) {
232 super(service);
233 }
234 }
235
236 private static class ListenerInfo extends
237 BinderInterfaceContainer.BinderInterface<ICarProjectionListener> {
238 private int mFilter;
239
240 private ListenerInfo(ListenerHolder holder, ICarProjectionListener binder, int filter) {
241 super(holder, binder);
242 this.mFilter = filter;
243 }
244
245 private synchronized int getFilter() {
246 return mFilter;
247 }
248
249 private boolean hasFilter(int filter) {
250 return (getFilter() & filter) != 0;
251 }
252
253 private synchronized void setFilter(int filter) {
254 mFilter = filter;
255 }
256
257 @Override
258 public String toString() {
259 synchronized (this) {
260 return "ListenerInfo{filter=" + Integer.toHexString(mFilter) + "}";
261 }
262 }
263 }
264}