blob: 5f9ebfaed67535b6411df2480af42c297db6bba2 [file] [log] [blame]
Keun young Park401479c2020-02-19 14:15:51 -08001/*
2 * Copyright (C) 2020 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
17package com.android.car;
18
kanant49fc8d42020-10-19 16:34:01 -070019import static android.car.CarOccupantZoneManager.DisplayTypeEnum;
20
Keun young Park401479c2020-02-19 14:15:51 -080021import static java.util.Map.entry;
22
23import android.annotation.NonNull;
kanant31039c32020-10-15 10:37:02 -070024import android.car.Car;
kanant49fc8d42020-10-19 16:34:01 -070025import android.car.CarOccupantZoneManager;
Keun young Park401479c2020-02-19 14:15:51 -080026import android.car.input.CarInputManager;
kananted82e292020-07-23 14:10:17 -070027import android.car.input.CustomInputEvent;
Keun young Park401479c2020-02-19 14:15:51 -080028import android.car.input.ICarInputCallback;
29import android.car.input.RotaryEvent;
Yuncheol Heo34ba7dd2021-03-12 20:27:19 -080030import android.content.ComponentName;
Keun young Park401479c2020-02-19 14:15:51 -080031import android.content.Context;
32import android.os.Binder;
33import android.os.IBinder;
34import android.os.RemoteException;
35import android.util.ArrayMap;
Eric Jeongbd5fb562020-12-21 13:49:40 -080036import android.util.Slog;
Keun young Park401479c2020-02-19 14:15:51 -080037import android.util.SparseArray;
38import android.view.KeyEvent;
39
40import com.android.internal.annotations.GuardedBy;
41import com.android.internal.util.ArrayUtils;
Keun young Park5b9fb362020-03-13 14:37:59 -070042import com.android.internal.util.Preconditions;
Keun young Park401479c2020-02-19 14:15:51 -080043
Keun young Park401479c2020-02-19 14:15:51 -080044import java.io.PrintWriter;
45import java.util.ArrayList;
46import java.util.Arrays;
47import java.util.HashMap;
48import java.util.LinkedList;
49import java.util.List;
50import java.util.Map;
Keun young Park5b9fb362020-03-13 14:37:59 -070051import java.util.Objects;
52import java.util.Set;
Keun young Park401479c2020-02-19 14:15:51 -080053
54/**
55 * Manages input capture request from clients
56 */
57public class InputCaptureClientController {
58 private static final boolean DBG_STACK = false;
59 private static final boolean DBG_DISPATCH = false;
60 private static final boolean DBG_CALLS = false;
61
Mayank Garg72c71d22021-02-03 23:54:45 -080062 private static final String TAG = CarLog.tagFor(InputCaptureClientController.class);
Keun young Park401479c2020-02-19 14:15:51 -080063 /**
kananted82e292020-07-23 14:10:17 -070064 * This table decides which input key goes into which input type. Not mapped here means it is
65 * not supported for capturing. Rotary events are treated separately and this is only for
66 * key events.
Keun young Park401479c2020-02-19 14:15:51 -080067 */
68 private static final Map<Integer, Integer> KEY_EVENT_TO_INPUT_TYPE = Map.ofEntries(
69 entry(KeyEvent.KEYCODE_DPAD_CENTER, CarInputManager.INPUT_TYPE_DPAD_KEYS),
70 entry(KeyEvent.KEYCODE_DPAD_DOWN, CarInputManager.INPUT_TYPE_DPAD_KEYS),
71 entry(KeyEvent.KEYCODE_DPAD_UP, CarInputManager.INPUT_TYPE_DPAD_KEYS),
72 entry(KeyEvent.KEYCODE_DPAD_LEFT, CarInputManager.INPUT_TYPE_DPAD_KEYS),
73 entry(KeyEvent.KEYCODE_DPAD_RIGHT, CarInputManager.INPUT_TYPE_DPAD_KEYS),
74 entry(KeyEvent.KEYCODE_DPAD_DOWN_LEFT, CarInputManager.INPUT_TYPE_DPAD_KEYS),
75 entry(KeyEvent.KEYCODE_DPAD_DOWN_RIGHT, CarInputManager.INPUT_TYPE_DPAD_KEYS),
76 entry(KeyEvent.KEYCODE_DPAD_UP_LEFT, CarInputManager.INPUT_TYPE_DPAD_KEYS),
77 entry(KeyEvent.KEYCODE_DPAD_UP_RIGHT, CarInputManager.INPUT_TYPE_DPAD_KEYS),
78 entry(KeyEvent.KEYCODE_NAVIGATE_IN, CarInputManager.INPUT_TYPE_NAVIGATE_KEYS),
79 entry(KeyEvent.KEYCODE_NAVIGATE_OUT, CarInputManager.INPUT_TYPE_NAVIGATE_KEYS),
80 entry(KeyEvent.KEYCODE_NAVIGATE_NEXT, CarInputManager.INPUT_TYPE_NAVIGATE_KEYS),
Keun young Park5b9fb362020-03-13 14:37:59 -070081 entry(KeyEvent.KEYCODE_NAVIGATE_PREVIOUS, CarInputManager.INPUT_TYPE_NAVIGATE_KEYS),
82 entry(KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN,
83 CarInputManager.INPUT_TYPE_SYSTEM_NAVIGATE_KEYS),
84 entry(KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP,
85 CarInputManager.INPUT_TYPE_SYSTEM_NAVIGATE_KEYS),
86 entry(KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT,
87 CarInputManager.INPUT_TYPE_SYSTEM_NAVIGATE_KEYS),
88 entry(KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT,
89 CarInputManager.INPUT_TYPE_SYSTEM_NAVIGATE_KEYS)
Keun young Park401479c2020-02-19 14:15:51 -080090 );
91
Keun young Park5b9fb362020-03-13 14:37:59 -070092 private static final Set<Integer> VALID_INPUT_TYPES = Set.of(
Keun young Park401479c2020-02-19 14:15:51 -080093 CarInputManager.INPUT_TYPE_ALL_INPUTS,
Keun young Park5b9fb362020-03-13 14:37:59 -070094 CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION,
Keun young Park401479c2020-02-19 14:15:51 -080095 CarInputManager.INPUT_TYPE_DPAD_KEYS,
96 CarInputManager.INPUT_TYPE_NAVIGATE_KEYS,
kananted82e292020-07-23 14:10:17 -070097 CarInputManager.INPUT_TYPE_SYSTEM_NAVIGATE_KEYS,
98 CarInputManager.INPUT_TYPE_CUSTOM_INPUT_EVENT
Keun young Park401479c2020-02-19 14:15:51 -080099 );
100
Keun young Park5b9fb362020-03-13 14:37:59 -0700101 private static final Set<Integer> VALID_ROTARY_TYPES = Set.of(
Keun young Park401479c2020-02-19 14:15:51 -0800102 CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION
103 );
104
105 // TODO(b/150818155) Need to migrate cluster code to use this to enable it.
106 private static final List<Integer> SUPPORTED_DISPLAY_TYPES = List.of(
Yuncheol Heo34ba7dd2021-03-12 20:27:19 -0800107 CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
108 CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER
Keun young Park401479c2020-02-19 14:15:51 -0800109 );
110
111 private static final int[] EMPTY_INPUT_TYPES = new int[0];
112
Keun young Park5b9fb362020-03-13 14:37:59 -0700113 private final class ClientInfoForDisplay implements IBinder.DeathRecipient {
Keun young Park401479c2020-02-19 14:15:51 -0800114 private final int mUid;
115 private final int mPid;
116 private final ICarInputCallback mCallback;
117 private final int mTargetDisplayType;
118 private final int[] mInputTypes;
119 private final int mFlags;
120 private final ArrayList<Integer> mGrantedTypes;
121
122 private ClientInfoForDisplay(int uid, int pid, @NonNull ICarInputCallback callback,
123 int targetDisplayType, int[] inputTypes, int flags) {
124 mUid = uid;
125 mPid = pid;
126 mCallback = callback;
127 mTargetDisplayType = targetDisplayType;
128 mInputTypes = inputTypes;
129 mFlags = flags;
130 mGrantedTypes = new ArrayList<>(inputTypes.length);
131 }
132
133 private void linkToDeath() throws RemoteException {
134 mCallback.asBinder().linkToDeath(this, 0);
135 }
136
137 private void unlinkToDeath() {
138 mCallback.asBinder().unlinkToDeath(this, 0);
139 }
140
141 @Override
142 public void binderDied() {
143 onClientDeath(this);
144 }
145
146 @Override
147 public String toString() {
Keun young Park5b9fb362020-03-13 14:37:59 -0700148 return new StringBuilder(128)
149 .append("Client{")
150 .append("uid:")
151 .append(mUid)
152 .append(",pid:")
153 .append(mPid)
154 .append(",callback:")
155 .append(mCallback)
156 .append(",inputTypes:")
157 .append(mInputTypes)
158 .append(",flags:")
159 .append(Integer.toHexString(mFlags))
160 .append(",grantedTypes:")
161 .append(mGrantedTypes)
162 .append("}")
163 .toString();
Keun young Park401479c2020-02-19 14:15:51 -0800164 }
Keun young Park401479c2020-02-19 14:15:51 -0800165 }
166
Keun young Park5b9fb362020-03-13 14:37:59 -0700167 private static final class ClientsToDispatch {
Keun young Park401479c2020-02-19 14:15:51 -0800168 // The same client can be added multiple times. Keeping only the last addition is ok.
169 private final ArrayMap<ICarInputCallback, int[]> mClientsToDispatch =
170 new ArrayMap<>();
171 private final int mDisplayType;
172
173 private ClientsToDispatch(int displayType) {
174 mDisplayType = displayType;
175 }
176
177 private void add(ClientInfoForDisplay client) {
178 int[] inputTypesToDispatch;
179 if (client.mGrantedTypes.isEmpty()) {
180 inputTypesToDispatch = EMPTY_INPUT_TYPES;
181 } else {
182 inputTypesToDispatch = ArrayUtils.convertToIntArray(client.mGrantedTypes);
183 }
184 mClientsToDispatch.put(client.mCallback, inputTypesToDispatch);
185 }
186 }
187
188 private final Context mContext;
189
190 private final Object mLock = new Object();
191
192 /**
193 * key: display type, for quick discovery of client
194 * LinkedList is for implementing stack. First entry is the top.
195 */
196 @GuardedBy("mLock")
197 private final SparseArray<LinkedList<ClientInfoForDisplay>> mFullDisplayEventCapturers =
198 new SparseArray<>(2);
199
200 /**
201 * key: display type -> inputType, for quick discovery of client
202 * LinkedList is for implementing stack. First entry is the top.
203 */
204 @GuardedBy("mLock")
205 private final SparseArray<SparseArray<LinkedList<ClientInfoForDisplay>>>
206 mPerInputTypeCapturers = new SparseArray<>(2);
207
208 @GuardedBy("mLock")
209 /** key: display type -> client binder */
210 private final SparseArray<HashMap<IBinder, ClientInfoForDisplay>> mAllClients =
211 new SparseArray<>(1);
212
213 @GuardedBy("mLock")
214 /** Keeps events to dispatch together. FIFO, last one added to last */
215 private final LinkedList<ClientsToDispatch> mClientDispatchQueue =
216 new LinkedList<>();
217
218 /** Accessed from dispatch thread only */
Keun young Park5b9fb362020-03-13 14:37:59 -0700219 private final ArrayList<KeyEvent> mKeyEventDispatchScratchList = new ArrayList<>(1);
Keun young Park401479c2020-02-19 14:15:51 -0800220
221 /** Accessed from dispatch thread only */
Keun young Park5b9fb362020-03-13 14:37:59 -0700222 private final ArrayList<RotaryEvent> mRotaryEventDispatchScratchList = new ArrayList<>(1);
Keun young Park401479c2020-02-19 14:15:51 -0800223
kananted82e292020-07-23 14:10:17 -0700224 /** Accessed from dispatch thread only */
225 private final ArrayList<CustomInputEvent> mCustomInputEventDispatchScratchList =
226 new ArrayList<>(1);
227
Keun young Park401479c2020-02-19 14:15:51 -0800228 @GuardedBy("mLock")
229 private int mNumKeyEventsDispatched;
230 @GuardedBy("mLock")
231 private int mNumRotaryEventsDispatched;
232
Yuncheol Heo34ba7dd2021-03-12 20:27:19 -0800233 private final String mClusterHomePackage;
234
Keun young Park401479c2020-02-19 14:15:51 -0800235 public InputCaptureClientController(Context context) {
236 mContext = context;
Yuncheol Heo34ba7dd2021-03-12 20:27:19 -0800237 mClusterHomePackage = ComponentName.unflattenFromString(
238 mContext.getString(R.string.config_clusterHomeActivity)).getPackageName();
Keun young Park401479c2020-02-19 14:15:51 -0800239 }
240
241 /**
Keun young Park5b9fb362020-03-13 14:37:59 -0700242 * See
Keun young Park401479c2020-02-19 14:15:51 -0800243 * {@link CarInputManager#requestInputEventCapture(CarInputManager.CarInputCaptureCallback,
244 * int, int[], int)}.
245 */
kanant49fc8d42020-10-19 16:34:01 -0700246 public int requestInputEventCapture(ICarInputCallback callback,
247 @DisplayTypeEnum int targetDisplayType,
Keun young Park401479c2020-02-19 14:15:51 -0800248 int[] inputTypes, int requestFlags) {
Antonio Kantek48e16af2021-08-05 22:50:43 -0700249 ICarImpl.assertAnyPermission(mContext, Car.PERMISSION_CAR_MONITOR_INPUT,
250 android.Manifest.permission.MONITOR_INPUT);
Keun young Park401479c2020-02-19 14:15:51 -0800251
Keun young Park5b9fb362020-03-13 14:37:59 -0700252 Preconditions.checkArgument(SUPPORTED_DISPLAY_TYPES.contains(targetDisplayType),
253 "Display not supported yet:" + targetDisplayType);
254
255 boolean isRequestingAllEvents =
Keun young Park401479c2020-02-19 14:15:51 -0800256 (requestFlags & CarInputManager.CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY) != 0;
257 if (isRequestingAllEvents) {
Yuncheol Heo34ba7dd2021-03-12 20:27:19 -0800258 if (targetDisplayType != CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER) {
259 ICarImpl.assertCallingFromSystemProcessOrSelf();
260 } else { // for DISPLAY_TYPE_INSTRUMENT_CLUSTER
261 if (!ICarImpl.isCallingFromSystemProcessOrSelf()) {
262 CarServiceUtils.assertPackageName(mContext, mClusterHomePackage);
263 }
264 }
Keun young Park401479c2020-02-19 14:15:51 -0800265 if (inputTypes.length != 1 || inputTypes[0] != CarInputManager.INPUT_TYPE_ALL_INPUTS) {
266 throw new IllegalArgumentException("Input type should be INPUT_TYPE_ALL_INPUTS"
267 + " for CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY");
268 }
269 }
kanant49fc8d42020-10-19 16:34:01 -0700270 if (targetDisplayType != CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER
271 && targetDisplayType != CarOccupantZoneManager.DISPLAY_TYPE_MAIN) {
Keun young Park401479c2020-02-19 14:15:51 -0800272 throw new IllegalArgumentException("Unrecognized display type:" + targetDisplayType);
273 }
274 if (inputTypes == null) {
275 throw new IllegalArgumentException("inputTypes cannot be null");
276 }
277 assertInputTypeValid(inputTypes);
Keun young Park401479c2020-02-19 14:15:51 -0800278 Arrays.sort(inputTypes);
279 IBinder clientBinder = callback.asBinder();
Keun young Park5b9fb362020-03-13 14:37:59 -0700280 boolean allowsDelayedGrant =
Keun young Park401479c2020-02-19 14:15:51 -0800281 (requestFlags & CarInputManager.CAPTURE_REQ_FLAGS_ALLOW_DELAYED_GRANT) != 0;
Keun young Park5b9fb362020-03-13 14:37:59 -0700282 int ret = CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED;
Keun young Park401479c2020-02-19 14:15:51 -0800283 if (DBG_CALLS) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800284 Slog.i(TAG,
Keun young Park401479c2020-02-19 14:15:51 -0800285 "requestInputEventCapture callback:" + callback
286 + ", display:" + targetDisplayType
287 + ", inputTypes:" + Arrays.toString(inputTypes)
288 + ", flags:" + requestFlags);
289 }
290 ClientsToDispatch clientsToDispatch = new ClientsToDispatch(targetDisplayType);
291 synchronized (mLock) {
292 HashMap<IBinder, ClientInfoForDisplay> allClientsForDisplay = mAllClients.get(
293 targetDisplayType);
294 if (allClientsForDisplay == null) {
295 allClientsForDisplay = new HashMap<IBinder, ClientInfoForDisplay>();
296 mAllClients.put(targetDisplayType, allClientsForDisplay);
297 }
298 ClientInfoForDisplay oldClientInfo = allClientsForDisplay.remove(clientBinder);
299
300 LinkedList<ClientInfoForDisplay> fullCapturersStack = mFullDisplayEventCapturers.get(
301 targetDisplayType);
302 if (fullCapturersStack == null) {
303 fullCapturersStack = new LinkedList<ClientInfoForDisplay>();
304 mFullDisplayEventCapturers.put(targetDisplayType, fullCapturersStack);
305 }
306
307 if (!isRequestingAllEvents && fullCapturersStack.size() > 0
308 && fullCapturersStack.getFirst() != oldClientInfo && !allowsDelayedGrant) {
309 // full capturing active. return failed if not delayed granting.
Keun young Park5b9fb362020-03-13 14:37:59 -0700310 return CarInputManager.INPUT_CAPTURE_RESPONSE_FAILED;
Keun young Park401479c2020-02-19 14:15:51 -0800311 }
312 // Now we need to register client anyway, so do death monitoring from here.
313 ClientInfoForDisplay newClient = new ClientInfoForDisplay(Binder.getCallingUid(),
314 Binder.getCallingPid(), callback, targetDisplayType,
315 inputTypes, requestFlags);
316 try {
317 newClient.linkToDeath();
318 } catch (RemoteException e) {
319 // client died
Eric Jeongbd5fb562020-12-21 13:49:40 -0800320 Slog.i(TAG, "requestInputEventCapture, cannot linkToDeath to client, pid:"
Keun young Park5b9fb362020-03-13 14:37:59 -0700321 + Binder.getCallingUid());
322 return CarInputManager.INPUT_CAPTURE_RESPONSE_FAILED;
Keun young Park401479c2020-02-19 14:15:51 -0800323 }
324
325 SparseArray<LinkedList<ClientInfoForDisplay>> perInputStacks =
326 mPerInputTypeCapturers.get(targetDisplayType);
327 if (perInputStacks == null) {
328 perInputStacks = new SparseArray<LinkedList<ClientInfoForDisplay>>();
329 mPerInputTypeCapturers.put(targetDisplayType, perInputStacks);
330 }
331
332 if (isRequestingAllEvents) {
Keun young Park5b9fb362020-03-13 14:37:59 -0700333 if (!fullCapturersStack.isEmpty()) {
Keun young Park401479c2020-02-19 14:15:51 -0800334 ClientInfoForDisplay oldCapturer = fullCapturersStack.getFirst();
335 if (oldCapturer != oldClientInfo) {
336 oldCapturer.mGrantedTypes.clear();
337 clientsToDispatch.add(oldCapturer);
338 }
339 fullCapturersStack.remove(oldClientInfo);
340 } else { // All per input type top stack client should be notified.
341 for (int i = 0; i < perInputStacks.size(); i++) {
342 LinkedList<ClientInfoForDisplay> perTypeStack = perInputStacks.valueAt(i);
Keun young Park5b9fb362020-03-13 14:37:59 -0700343 if (!perTypeStack.isEmpty()) {
Keun young Park401479c2020-02-19 14:15:51 -0800344 ClientInfoForDisplay topClient = perTypeStack.getFirst();
345 if (topClient != oldClientInfo) {
346 topClient.mGrantedTypes.clear();
347 clientsToDispatch.add(topClient);
348 }
Keun young Park5b9fb362020-03-13 14:37:59 -0700349 // Even if the client was on top, the one in back does not need
Keun young Park401479c2020-02-19 14:15:51 -0800350 // update.
351 perTypeStack.remove(oldClientInfo);
352 }
353 }
354 }
355 fullCapturersStack.addFirst(newClient);
356
357 } else {
358 boolean hadFullCapture = false;
359 boolean fullCaptureActive = false;
360 if (fullCapturersStack.size() > 0) {
361 if (fullCapturersStack.getFirst() == oldClientInfo) {
362 fullCapturersStack.remove(oldClientInfo);
363 // Now we need to check if there is other client in fullCapturersStack
364 if (fullCapturersStack.size() > 0) {
365 fullCaptureActive = true;
Keun young Park5b9fb362020-03-13 14:37:59 -0700366 ret = CarInputManager.INPUT_CAPTURE_RESPONSE_DELAYED;
Keun young Park401479c2020-02-19 14:15:51 -0800367 ClientInfoForDisplay topClient = fullCapturersStack.getFirst();
368 topClient.mGrantedTypes.clear();
369 topClient.mGrantedTypes.add(CarInputManager.INPUT_TYPE_ALL_INPUTS);
370 clientsToDispatch.add(topClient);
371 } else {
372 hadFullCapture = true;
373 }
374 } else {
375 // other client doing full capturing and it should have DELAYED_GRANT flag.
376 fullCaptureActive = true;
Keun young Park5b9fb362020-03-13 14:37:59 -0700377 ret = CarInputManager.INPUT_CAPTURE_RESPONSE_DELAYED;
Keun young Park401479c2020-02-19 14:15:51 -0800378 }
379 }
380 for (int i = 0; i < perInputStacks.size(); i++) {
381 LinkedList<ClientInfoForDisplay> perInputStack = perInputStacks.valueAt(i);
382 perInputStack.remove(oldClientInfo);
383 }
384 // Now go through per input stack
385 for (int inputType : inputTypes) {
386 LinkedList<ClientInfoForDisplay> perInputStack = perInputStacks.get(
387 inputType);
388 if (perInputStack == null) {
389 perInputStack = new LinkedList<ClientInfoForDisplay>();
390 perInputStacks.put(inputType, perInputStack);
391 }
392 if (perInputStack.size() > 0) {
393 ClientInfoForDisplay oldTopClient = perInputStack.getFirst();
394 if (oldTopClient.mGrantedTypes.remove(Integer.valueOf(inputType))) {
395 clientsToDispatch.add(oldTopClient);
396 }
397 }
398 if (!fullCaptureActive) {
399 newClient.mGrantedTypes.add(inputType);
400 }
401 perInputStack.addFirst(newClient);
402 }
403 if (!fullCaptureActive && hadFullCapture) {
404 for (int i = 0; i < perInputStacks.size(); i++) {
405 int inputType = perInputStacks.keyAt(i);
406 LinkedList<ClientInfoForDisplay> perInputStack = perInputStacks.valueAt(
407 i);
408 if (perInputStack.size() > 0) {
409 ClientInfoForDisplay topStackClient = perInputStack.getFirst();
410 if (topStackClient == newClient) {
411 continue;
412 }
413 if (!topStackClient.mGrantedTypes.contains(inputType)) {
414 topStackClient.mGrantedTypes.add(inputType);
415 clientsToDispatch.add(topStackClient);
416 }
417 }
418 }
419 }
420 }
421 allClientsForDisplay.put(clientBinder, newClient);
422 dispatchClientCallbackLocked(clientsToDispatch);
423 }
424 return ret;
425 }
426
427 /**
Keun young Park5b9fb362020-03-13 14:37:59 -0700428 * See {@link CarInputManager#releaseInputEventCapture(int)}.
Keun young Park401479c2020-02-19 14:15:51 -0800429 */
430 public void releaseInputEventCapture(ICarInputCallback callback, int targetDisplayType) {
Keun young Park5b9fb362020-03-13 14:37:59 -0700431 Objects.requireNonNull(callback);
432 Preconditions.checkArgument(SUPPORTED_DISPLAY_TYPES.contains(targetDisplayType),
433 "Display not supported yet:" + targetDisplayType);
434
Keun young Park401479c2020-02-19 14:15:51 -0800435 if (DBG_CALLS) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800436 Slog.i(TAG, "releaseInputEventCapture callback:" + callback
kananted82e292020-07-23 14:10:17 -0700437 + ", display:" + targetDisplayType);
Keun young Park401479c2020-02-19 14:15:51 -0800438 }
439 ClientsToDispatch clientsToDispatch = new ClientsToDispatch(targetDisplayType);
440 synchronized (mLock) {
441 HashMap<IBinder, ClientInfoForDisplay> allClientsForDisplay = mAllClients.get(
442 targetDisplayType);
443 ClientInfoForDisplay clientInfo = allClientsForDisplay.remove(callback.asBinder());
444 if (clientInfo == null) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800445 Slog.w(TAG, "Cannot find client for releaseInputEventCapture:" + callback);
Keun young Park401479c2020-02-19 14:15:51 -0800446 return;
447 }
448 clientInfo.unlinkToDeath();
449 LinkedList<ClientInfoForDisplay> fullCapturersStack = mFullDisplayEventCapturers.get(
450 targetDisplayType);
451 boolean fullCaptureActive = false;
452 if (fullCapturersStack.size() > 0) {
453 if (fullCapturersStack.getFirst() == clientInfo) {
454 fullCapturersStack.remove(clientInfo);
455 if (fullCapturersStack.size() > 0) {
456 ClientInfoForDisplay newStopStackClient = fullCapturersStack.getFirst();
457 newStopStackClient.mGrantedTypes.clear();
458 newStopStackClient.mGrantedTypes.add(CarInputManager.INPUT_TYPE_ALL_INPUTS);
459 clientsToDispatch.add(newStopStackClient);
460 fullCaptureActive = true;
461 }
462 } else { // no notification as other client is in top of the stack
463 fullCaptureActive = true;
464 }
465 fullCapturersStack.remove(clientInfo);
466 }
467 SparseArray<LinkedList<ClientInfoForDisplay>> perInputStacks =
468 mPerInputTypeCapturers.get(targetDisplayType);
469 if (DBG_STACK) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800470 Slog.i(TAG, "releaseInputEventCapture, fullCaptureActive:"
Keun young Park401479c2020-02-19 14:15:51 -0800471 + fullCaptureActive + ", perInputStacks:" + perInputStacks);
472 }
473 if (perInputStacks != null) {
474 for (int i = 0; i < perInputStacks.size(); i++) {
475 int inputType = perInputStacks.keyAt(i);
476 LinkedList<ClientInfoForDisplay> perInputStack = perInputStacks.valueAt(i);
477 if (perInputStack.size() > 0) {
478 if (perInputStack.getFirst() == clientInfo) {
479 perInputStack.removeFirst();
480 if (perInputStack.size() > 0) {
481 ClientInfoForDisplay newTopClient = perInputStack.getFirst();
482 if (!fullCaptureActive) {
483 newTopClient.mGrantedTypes.add(inputType);
484 clientsToDispatch.add(newTopClient);
485 }
486 }
487 } else { // something else on top.
488 if (!fullCaptureActive) {
489 ClientInfoForDisplay topClient = perInputStack.getFirst();
490 if (!topClient.mGrantedTypes.contains(inputType)) {
491 topClient.mGrantedTypes.add(inputType);
492 clientsToDispatch.add(topClient);
493 }
494 }
495 perInputStack.remove(clientInfo);
496 }
497 }
498 }
499 }
500 dispatchClientCallbackLocked(clientsToDispatch);
501 }
502 }
503
504 /**
Keun young Park5b9fb362020-03-13 14:37:59 -0700505 * Dispatches the given {@code KeyEvent} to a capturing client if there is one.
Keun young Park401479c2020-02-19 14:15:51 -0800506 *
kananted82e292020-07-23 14:10:17 -0700507 * @param displayType the display type defined in {@code CarInputManager} such as
kanant49fc8d42020-10-19 16:34:01 -0700508 * {@link CarOccupantZoneManager#DISPLAY_TYPE_MAIN}
509 * @param event the key event to handle
Keun young Park401479c2020-02-19 14:15:51 -0800510 * @return true if the event was consumed.
511 */
kanant49fc8d42020-10-19 16:34:01 -0700512 public boolean onKeyEvent(@DisplayTypeEnum int displayType, KeyEvent event) {
Keun young Park401479c2020-02-19 14:15:51 -0800513 if (!SUPPORTED_DISPLAY_TYPES.contains(displayType)) {
514 return false;
515 }
516 Integer inputType = KEY_EVENT_TO_INPUT_TYPE.get(event.getKeyCode());
517 if (inputType == null) { // not supported key
Yuncheol Heo34ba7dd2021-03-12 20:27:19 -0800518 inputType = CarInputManager.INPUT_TYPE_ALL_INPUTS;
Keun young Park401479c2020-02-19 14:15:51 -0800519 }
520 ICarInputCallback callback;
521 synchronized (mLock) {
522 callback = getClientForInputTypeLocked(displayType, inputType);
523 if (callback == null) {
524 return false;
525 }
526 mNumKeyEventsDispatched++;
527 }
528
529 dispatchKeyEvent(displayType, event, callback);
530 return true;
531 }
532
533 /**
Keun young Park5b9fb362020-03-13 14:37:59 -0700534 * Dispatches the given {@code RotaryEvent} to a capturing client if there is one.
Keun young Park401479c2020-02-19 14:15:51 -0800535 *
kananted82e292020-07-23 14:10:17 -0700536 * @param displayType the display type defined in {@code CarInputManager} such as
kanant49fc8d42020-10-19 16:34:01 -0700537 * {@link CarOccupantZoneManager#DISPLAY_TYPE_MAIN}
538 * @param event the Rotary event to handle
Keun young Park401479c2020-02-19 14:15:51 -0800539 * @return true if the event was consumed.
540 */
kanant49fc8d42020-10-19 16:34:01 -0700541 public boolean onRotaryEvent(@DisplayTypeEnum int displayType, RotaryEvent event) {
Keun young Park401479c2020-02-19 14:15:51 -0800542 if (!SUPPORTED_DISPLAY_TYPES.contains(displayType)) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800543 Slog.w(TAG, "onRotaryEvent for not supported display:" + displayType);
Keun young Park401479c2020-02-19 14:15:51 -0800544 return false;
545 }
546 int inputType = event.getInputType();
547 if (!VALID_ROTARY_TYPES.contains(inputType)) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800548 Slog.w(TAG, "onRotaryEvent for not supported input type:" + inputType);
Keun young Park401479c2020-02-19 14:15:51 -0800549 return false;
550 }
551
552 ICarInputCallback callback;
553 synchronized (mLock) {
554 callback = getClientForInputTypeLocked(displayType, inputType);
555 if (callback == null) {
556 if (DBG_DISPATCH) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800557 Slog.i(TAG, "onRotaryEvent no client for input type:" + inputType);
Keun young Park401479c2020-02-19 14:15:51 -0800558 }
559 return false;
560 }
561 mNumRotaryEventsDispatched++;
562 }
563
564 dispatchRotaryEvent(displayType, event, callback);
565 return true;
566 }
567
kananted82e292020-07-23 14:10:17 -0700568 /**
569 * Dispatches the given {@link CustomInputEvent} to a capturing client if there is one.
570 * Nothing happens if no callback was registered for the incoming event. In this case this
571 * method will return {@code false}.
572 * <p>
573 * In case of there are more than one client registered for this event, then only the first one
574 * will be notified.
575 *
576 * @param event the {@link CustomInputEvent} to dispatch
577 * @return {@code true} if the event was consumed.
578 */
579 public boolean onCustomInputEvent(CustomInputEvent event) {
580 int displayType = event.getTargetDisplayType();
581 if (!SUPPORTED_DISPLAY_TYPES.contains(displayType)) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800582 Slog.w(TAG, "onCustomInputEvent for not supported display:" + displayType);
kananted82e292020-07-23 14:10:17 -0700583 return false;
584 }
585 ICarInputCallback callback;
586 synchronized (mLock) {
587 callback = getClientForInputTypeLocked(displayType,
588 CarInputManager.INPUT_TYPE_CUSTOM_INPUT_EVENT);
589 if (callback == null) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800590 Slog.w(TAG, "No client for input: " + CarInputManager.INPUT_TYPE_CUSTOM_INPUT_EVENT
kananted82e292020-07-23 14:10:17 -0700591 + " and display: " + displayType);
592 return false;
593 }
594 }
595 dispatchCustomInputEvent(displayType, event, callback);
596 return true;
597 }
598
Keun young Park401479c2020-02-19 14:15:51 -0800599 ICarInputCallback getClientForInputTypeLocked(int targetDisplayType, int inputType) {
600 LinkedList<ClientInfoForDisplay> fullCapturersStack = mFullDisplayEventCapturers.get(
601 targetDisplayType);
602 if (fullCapturersStack != null && fullCapturersStack.size() > 0) {
603 return fullCapturersStack.getFirst().mCallback;
604 }
605
606 SparseArray<LinkedList<ClientInfoForDisplay>> perInputStacks =
607 mPerInputTypeCapturers.get(targetDisplayType);
608 if (perInputStacks == null) {
609 return null;
610 }
611
612 LinkedList<ClientInfoForDisplay> perInputStack = perInputStacks.get(inputType);
613 if (perInputStack != null && perInputStack.size() > 0) {
614 return perInputStack.getFirst().mCallback;
615 }
616
617 return null;
618 }
619
620 private void onClientDeath(ClientInfoForDisplay client) {
621 releaseInputEventCapture(client.mCallback, client.mTargetDisplayType);
622 }
623
624 /** dump for debugging */
625 public void dump(PrintWriter writer) {
626 writer.println("**InputCaptureClientController**");
627 synchronized (mLock) {
kananted82e292020-07-23 14:10:17 -0700628 for (int display : SUPPORTED_DISPLAY_TYPES) {
Keun young Park401479c2020-02-19 14:15:51 -0800629 writer.println("***Display:" + display);
630
631 HashMap<IBinder, ClientInfoForDisplay> allClientsForDisplay = mAllClients.get(
632 display);
633 if (allClientsForDisplay != null) {
634 writer.println("****All clients:");
kananted82e292020-07-23 14:10:17 -0700635 for (ClientInfoForDisplay client : allClientsForDisplay.values()) {
Keun young Park5b9fb362020-03-13 14:37:59 -0700636 writer.println(client);
Keun young Park401479c2020-02-19 14:15:51 -0800637 }
638 }
639
640 LinkedList<ClientInfoForDisplay> fullCapturersStack =
641 mFullDisplayEventCapturers.get(display);
642 if (fullCapturersStack != null) {
643 writer.println("****Full capture stack");
kananted82e292020-07-23 14:10:17 -0700644 for (ClientInfoForDisplay client : fullCapturersStack) {
Keun young Park5b9fb362020-03-13 14:37:59 -0700645 writer.println(client);
Keun young Park401479c2020-02-19 14:15:51 -0800646 }
647 }
648 SparseArray<LinkedList<ClientInfoForDisplay>> perInputStacks =
649 mPerInputTypeCapturers.get(display);
650 if (perInputStacks != null) {
651 for (int i = 0; i < perInputStacks.size(); i++) {
652 int inputType = perInputStacks.keyAt(i);
653 LinkedList<ClientInfoForDisplay> perInputStack = perInputStacks.valueAt(i);
654 if (perInputStack.size() > 0) {
655 writer.println("**** Per Input stack, input type:" + inputType);
kananted82e292020-07-23 14:10:17 -0700656 for (ClientInfoForDisplay client : perInputStack) {
Keun young Park5b9fb362020-03-13 14:37:59 -0700657 writer.println(client);
Keun young Park401479c2020-02-19 14:15:51 -0800658 }
659 }
660 }
661 }
662 }
663 writer.println("mNumKeyEventsDispatched:" + mNumKeyEventsDispatched
664 + ",mNumRotaryEventsDispatched:" + mNumRotaryEventsDispatched);
665 }
666 }
667
668 private void dispatchClientCallbackLocked(ClientsToDispatch clientsToDispatch) {
669 if (clientsToDispatch.mClientsToDispatch.isEmpty()) {
670 return;
671 }
672 if (DBG_DISPATCH) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800673 Slog.i(TAG, "dispatchClientCallbackLocked, number of clients:"
Keun young Park401479c2020-02-19 14:15:51 -0800674 + clientsToDispatch.mClientsToDispatch.size());
675 }
676 mClientDispatchQueue.add(clientsToDispatch);
677 CarServiceUtils.runOnMain(() -> {
678 ClientsToDispatch clients;
679 synchronized (mLock) {
Keun young Park5b9fb362020-03-13 14:37:59 -0700680 if (mClientDispatchQueue.isEmpty()) {
Keun young Park401479c2020-02-19 14:15:51 -0800681 return;
682 }
683 clients = mClientDispatchQueue.pop();
684 }
685
686 if (DBG_DISPATCH) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800687 Slog.i(TAG, "dispatching to clients, num of clients:"
Keun young Park401479c2020-02-19 14:15:51 -0800688 + clients.mClientsToDispatch.size()
689 + ", display:" + clients.mDisplayType);
690 }
691 for (int i = 0; i < clients.mClientsToDispatch.size(); i++) {
692 ICarInputCallback callback = clients.mClientsToDispatch.keyAt(i);
693 int[] inputTypes = clients.mClientsToDispatch.valueAt(i);
694 Arrays.sort(inputTypes);
695 if (DBG_DISPATCH) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800696 Slog.i(TAG, "dispatching to client, callback:"
Keun young Park401479c2020-02-19 14:15:51 -0800697 + callback + ", inputTypes:" + Arrays.toString(inputTypes));
698 }
699 try {
700 callback.onCaptureStateChanged(clients.mDisplayType, inputTypes);
701 } catch (RemoteException e) {
702 // Ignore. Let death handler deal with it.
703 }
704 }
705 });
706 }
707
708 private void dispatchKeyEvent(int targetDisplayType, KeyEvent event,
709 ICarInputCallback callback) {
710 CarServiceUtils.runOnMain(() -> {
Keun young Park5b9fb362020-03-13 14:37:59 -0700711 mKeyEventDispatchScratchList.clear();
712 mKeyEventDispatchScratchList.add(event);
Keun young Park401479c2020-02-19 14:15:51 -0800713 try {
Keun young Park5b9fb362020-03-13 14:37:59 -0700714 callback.onKeyEvents(targetDisplayType, mKeyEventDispatchScratchList);
Keun young Park401479c2020-02-19 14:15:51 -0800715 } catch (RemoteException e) {
kananted82e292020-07-23 14:10:17 -0700716 if (DBG_DISPATCH) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800717 Slog.e(TAG, "Failed to dispatch KeyEvent " + event, e);
kananted82e292020-07-23 14:10:17 -0700718 }
Keun young Park401479c2020-02-19 14:15:51 -0800719 }
720 });
721 }
722
723 private void dispatchRotaryEvent(int targetDisplayType, RotaryEvent event,
724 ICarInputCallback callback) {
725 if (DBG_DISPATCH) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800726 Slog.i(TAG, "dispatchRotaryEvent:" + event);
Keun young Park401479c2020-02-19 14:15:51 -0800727 }
kananted82e292020-07-23 14:10:17 -0700728 // TODO(b/159623196): Use HandlerThread for dispatching rather than relying on the main
729 // thread. Change here and other dispatch methods.
Keun young Park401479c2020-02-19 14:15:51 -0800730 CarServiceUtils.runOnMain(() -> {
Keun young Park5b9fb362020-03-13 14:37:59 -0700731 mRotaryEventDispatchScratchList.clear();
732 mRotaryEventDispatchScratchList.add(event);
Keun young Park401479c2020-02-19 14:15:51 -0800733 try {
Keun young Park5b9fb362020-03-13 14:37:59 -0700734 callback.onRotaryEvents(targetDisplayType, mRotaryEventDispatchScratchList);
Keun young Park401479c2020-02-19 14:15:51 -0800735 } catch (RemoteException e) {
kananted82e292020-07-23 14:10:17 -0700736 if (DBG_DISPATCH) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800737 Slog.e(TAG, "Failed to dispatch RotaryEvent " + event, e);
kananted82e292020-07-23 14:10:17 -0700738 }
739 }
740 });
741 }
742
kanant49fc8d42020-10-19 16:34:01 -0700743 private void dispatchCustomInputEvent(@DisplayTypeEnum int targetDisplayType,
744 CustomInputEvent event,
kananted82e292020-07-23 14:10:17 -0700745 ICarInputCallback callback) {
746 if (DBG_DISPATCH) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800747 Slog.d(TAG, "dispatchCustomInputEvent:" + event);
kananted82e292020-07-23 14:10:17 -0700748 }
749 CarServiceUtils.runOnMain(() -> {
750 mCustomInputEventDispatchScratchList.clear();
751 mCustomInputEventDispatchScratchList.add(event);
752 try {
753 callback.onCustomInputEvents(targetDisplayType,
754 mCustomInputEventDispatchScratchList);
755 } catch (RemoteException e) {
756 if (DBG_DISPATCH) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800757 Slog.e(TAG, "Failed to dispatch CustomInputEvent " + event, e);
kananted82e292020-07-23 14:10:17 -0700758 }
Keun young Park401479c2020-02-19 14:15:51 -0800759 }
760 });
761 }
762
Keun young Park5b9fb362020-03-13 14:37:59 -0700763 private static void assertInputTypeValid(int[] inputTypes) {
Keun young Park401479c2020-02-19 14:15:51 -0800764 for (int inputType : inputTypes) {
765 if (!VALID_INPUT_TYPES.contains(inputType)) {
Keun young Park5b9fb362020-03-13 14:37:59 -0700766 throw new IllegalArgumentException("Invalid input type:" + inputType
767 + ", inputTypes:" + Arrays.toString(inputTypes));
Keun young Park401479c2020-02-19 14:15:51 -0800768 }
769 }
770 }
771}