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 85 CarProjectionService(Context context, CarInputService carInputService) { 86 mContext = context; 87 mCarInputService = carInputService; 88 mAllListeners = new ListenerHolder(this); 89 } 90 91 @Override 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 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 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 129 private void unbindServiceIfBound() { 130 synchronized (this) { 131 if (!mBound) { 132 return; 133 } 134 mBound = false; 135 } 136 mContext.unbindService(mConnection); 137 } 138 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 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 167 public void unregsiterProjectionListener(ICarProjectionListener listener) { 168 synchronized (this) { 169 mAllListeners.removeBinder(listener); 170 } 171 updateCarInputServiceListeners(); 172 } 173 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 194 public void init() { 195 // nothing to do 196 } 197 198 @Override 199 public void release() { 200 synchronized (this) { 201 mAllListeners.clear(); 202 } 203 } 204 205 @Override 206 public void onBinderDeath( 207 BinderInterfaceContainer.BinderInterface<ICarProjectionListener> bInterface) { 208 unregsiterProjectionListener(bInterface.binderInterface); 209 } 210 211 @Override 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 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> { 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 241 private ListenerInfo(ListenerHolder holder, ICarProjectionListener binder, int filter) { 242 super(holder, binder); 243 this.mFilter = filter; 244 } 245 246 private synchronized int getFilter() { 247 return mFilter; 248 } 249 250 private boolean hasFilter(int filter) { 251 return (getFilter() & filter) != 0; 252 } 253 254 private synchronized void setFilter(int filter) { 255 mFilter = filter; 256 } 257 258 @Override 259 public String toString() { 260 synchronized (this) { 261 return "ListenerInfo{filter=" + Integer.toHexString(mFilter) + "}"; 262 } 263 } 264 } 265 } 266