Vitalii Tomkiv | 6e5ee61 | 2016-03-09 14:57:32 -0800 | [diff] [blame] | 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; |
Vitalii Tomkiv | 6e5ee61 | 2016-03-09 14:57:32 -0800 | [diff] [blame] | 26 | import android.os.IBinder; |
Vitalii Tomkiv | 6e5ee61 | 2016-03-09 14:57:32 -0800 | [diff] [blame] | 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<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 Chen | c4d442f | 2016-04-08 11:33:47 -0700 | [diff] [blame] | 47 | public boolean onKeyEvent(KeyEvent event) { |
Vitalii Tomkiv | 6e5ee61 | 2016-03-09 14:57:32 -0800 | [diff] [blame] | 48 | handleVoiceAssitantRequest(false); |
Yao Chen | c4d442f | 2016-04-08 11:33:47 -0700 | [diff] [blame] | 49 | return true; |
Vitalii Tomkiv | 6e5ee61 | 2016-03-09 14:57:32 -0800 | [diff] [blame] | 50 | } |
| 51 | }; |
| 52 | |
| 53 | private final CarInputService.KeyEventListener mLongVoiceAssistantKeyListener = |
| 54 | new CarInputService.KeyEventListener() { |
| 55 | @Override |
Yao Chen | c4d442f | 2016-04-08 11:33:47 -0700 | [diff] [blame] | 56 | public boolean onKeyEvent(KeyEvent event) { |
Vitalii Tomkiv | 6e5ee61 | 2016-03-09 14:57:32 -0800 | [diff] [blame] | 57 | handleVoiceAssitantRequest(true); |
Yao Chen | c4d442f | 2016-04-08 11:33:47 -0700 | [diff] [blame] | 58 | return true; |
Vitalii Tomkiv | 6e5ee61 | 2016-03-09 14:57:32 -0800 | [diff] [blame] | 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.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 Maltsev | e11ad3a | 2016-03-25 15:40:24 -0700 | [diff] [blame] | 186 | mCarInputService.setVoiceAssistantKeyListener(listenShortPress |
Vitalii Tomkiv | 6e5ee61 | 2016-03-09 14:57:32 -0800 | [diff] [blame] | 187 | ? mVoiceAssistantKeyListener : null); |
Pavel Maltsev | e11ad3a | 2016-03-25 15:40:24 -0700 | [diff] [blame] | 188 | mCarInputService.setLongVoiceAssistantKeyListener(listenLongPress |
Vitalii Tomkiv | 6e5ee61 | 2016-03-09 14:57:32 -0800 | [diff] [blame] | 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<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 | } |