• 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.ICarProjectionCallback;
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.IBinder;
27 import android.os.RemoteException;
28 import android.os.UserHandle;
29 import android.util.Log;
30 import android.view.KeyEvent;
31 
32 import 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  */
38 class CarProjectionService extends ICarProjection.Stub implements CarServiceBase,
39         BinderInterfaceContainer.BinderEventHandler<ICarProjectionCallback> {
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
47                 public boolean onKeyEvent(KeyEvent event) {
48                     handleVoiceAssitantRequest(false);
49                     return true;
50                 }
51             };
52 
53     private final CarInputService.KeyEventListener mLongVoiceAssistantKeyListener =
54             new CarInputService.KeyEventListener() {
55                 @Override
56                 public boolean onKeyEvent(KeyEvent event) {
57                     handleVoiceAssitantRequest(true);
58                     return true;
59                 }
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 
CarProjectionService(Context context, CarInputService carInputService)84     CarProjectionService(Context context, CarInputService carInputService) {
85         mContext = context;
86         mCarInputService = carInputService;
87         mAllListeners = new ListenerHolder(this);
88     }
89 
90     @Override
registerProjectionRunner(Intent serviceIntent)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
unregisterProjectionRunner(Intent serviceIntent)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 
bindToService(Intent serviceIntent)119     private void bindToService(Intent serviceIntent) {
120         synchronized (this) {
121             mRegisteredService = serviceIntent;
122         }
123         UserHandle userHandle = UserHandle.getUserHandleForUid(Binder.getCallingUid());
124         mContext.bindServiceAsUser(serviceIntent, mConnection, Context.BIND_AUTO_CREATE,
125                 userHandle);
126     }
127 
unbindServiceIfBound()128     private void unbindServiceIfBound() {
129         synchronized (this) {
130             if (!mBound) {
131                 return;
132             }
133             mBound = false;
134         }
135         mContext.unbindService(mConnection);
136     }
137 
handleVoiceAssitantRequest(boolean isTriggeredByLongPress)138     private synchronized void handleVoiceAssitantRequest(boolean isTriggeredByLongPress) {
139         for (BinderInterfaceContainer.BinderInterface<ICarProjectionCallback> 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
regsiterProjectionListener(ICarProjectionCallback listener, int filter)152     public void regsiterProjectionListener(ICarProjectionCallback 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
unregsiterProjectionListener(ICarProjectionCallback listener)166     public void unregsiterProjectionListener(ICarProjectionCallback listener) {
167         synchronized (this) {
168             mAllListeners.removeBinder(listener);
169         }
170         updateCarInputServiceListeners();
171     }
172 
updateCarInputServiceListeners()173     private void updateCarInputServiceListeners() {
174         boolean listenShortPress = false;
175         boolean listenLongPress = false;
176         synchronized (this) {
177             for (BinderInterfaceContainer.BinderInterface<ICarProjectionCallback> 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         }
186         mCarInputService.setVoiceAssistantKeyListener(listenShortPress
187                 ? mVoiceAssistantKeyListener : null);
188         mCarInputService.setLongVoiceAssistantKeyListener(listenLongPress
189                 ? mLongVoiceAssistantKeyListener : null);
190     }
191 
192     @Override
init()193     public void init() {
194         // nothing to do
195     }
196 
197     @Override
release()198     public void release() {
199         synchronized (this) {
200             mAllListeners.clear();
201         }
202     }
203 
204     @Override
onBinderDeath( BinderInterfaceContainer.BinderInterface<ICarProjectionCallback> bInterface)205     public void onBinderDeath(
206             BinderInterfaceContainer.BinderInterface<ICarProjectionCallback> bInterface) {
207         unregsiterProjectionListener(bInterface.binderInterface);
208     }
209 
210     @Override
dump(PrintWriter writer)211     public void dump(PrintWriter writer) {
212         writer.println("**CarProjectionService**");
213         synchronized (this) {
214             for (BinderInterfaceContainer.BinderInterface<ICarProjectionCallback> listener :
215                          mAllListeners.getInterfaces()) {
216                 ListenerInfo listenerInfo = (ListenerInfo) listener;
217                 writer.println(listenerInfo.toString());
218             }
219         }
220     }
221 
dispatchVoiceAssistantRequest(ICarProjectionCallback listener, boolean fromLongPress)222     private void dispatchVoiceAssistantRequest(ICarProjectionCallback listener,
223             boolean fromLongPress) {
224         try {
225             listener.onVoiceAssistantRequest(fromLongPress);
226         } catch (RemoteException e) {
227         }
228     }
229 
230     private static class ListenerHolder extends BinderInterfaceContainer<ICarProjectionCallback> {
ListenerHolder(CarProjectionService service)231         private ListenerHolder(CarProjectionService service) {
232             super(service);
233         }
234     }
235 
236     private static class ListenerInfo extends
237             BinderInterfaceContainer.BinderInterface<ICarProjectionCallback> {
238         private int mFilter;
239 
ListenerInfo(ListenerHolder holder, ICarProjectionCallback binder, int filter)240         private ListenerInfo(ListenerHolder holder, ICarProjectionCallback binder, int filter) {
241             super(holder, binder);
242             this.mFilter = filter;
243         }
244 
getFilter()245         private synchronized int getFilter() {
246             return mFilter;
247         }
248 
hasFilter(int filter)249         private boolean hasFilter(int filter) {
250             return (getFilter() & filter) != 0;
251         }
252 
setFilter(int filter)253         private synchronized void setFilter(int filter) {
254             mFilter = filter;
255         }
256 
257         @Override
toString()258         public String toString() {
259             synchronized (this) {
260                 return "ListenerInfo{filter=" + Integer.toHexString(mFilter) + "}";
261             }
262         }
263     }
264 }
265