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