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 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.bindServiceAsUser(serviceIntent, mConnection, Context.BIND_AUTO_CREATE, 125 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<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 152 public void registerProjectionListener(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 166 public void unregisterProjectionListener(ICarProjectionCallback 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<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 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<ICarProjectionCallback> bInterface) { 207 unregisterProjectionListener(bInterface.binderInterface); 208 } 209 210 @Override 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 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> { 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 240 private ListenerInfo(ListenerHolder holder, ICarProjectionCallback 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 } 265