1 /*
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 
17 package com.android.car;
18 
19 import static android.car.CarOccupantZoneManager.DisplayTypeEnum;
20 
21 import static java.util.Map.entry;
22 
23 import android.annotation.NonNull;
24 import android.car.Car;
25 import android.car.CarOccupantZoneManager;
26 import android.car.input.CarInputManager;
27 import android.car.input.CustomInputEvent;
28 import android.car.input.ICarInputCallback;
29 import android.car.input.RotaryEvent;
30 import android.content.ComponentName;
31 import android.content.Context;
32 import android.os.Binder;
33 import android.os.IBinder;
34 import android.os.RemoteException;
35 import android.util.ArrayMap;
36 import android.util.Slog;
37 import android.util.SparseArray;
38 import android.view.KeyEvent;
39 
40 import com.android.internal.annotations.GuardedBy;
41 import com.android.internal.util.ArrayUtils;
42 import com.android.internal.util.Preconditions;
43 
44 import java.io.PrintWriter;
45 import java.util.ArrayList;
46 import java.util.Arrays;
47 import java.util.HashMap;
48 import java.util.LinkedList;
49 import java.util.List;
50 import java.util.Map;
51 import java.util.Objects;
52 import java.util.Set;
53 
54 /**
55  * Manages input capture request from clients
56  */
57 public 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 
62     private static final String TAG = CarLog.tagFor(InputCaptureClientController.class);
63     /**
64      * 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.
67      */
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),
81             entry(KeyEvent.KEYCODE_NAVIGATE_PREVIOUS, CarInputManager.INPUT_TYPE_NAVIGATE_KEYS),
82             entry(KeyEvent.KEYCODE_BACK, CarInputManager.INPUT_TYPE_NAVIGATE_KEYS),
83             entry(KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN,
84                     CarInputManager.INPUT_TYPE_SYSTEM_NAVIGATE_KEYS),
85             entry(KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP,
86                     CarInputManager.INPUT_TYPE_SYSTEM_NAVIGATE_KEYS),
87             entry(KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT,
88                     CarInputManager.INPUT_TYPE_SYSTEM_NAVIGATE_KEYS),
89             entry(KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT,
90                     CarInputManager.INPUT_TYPE_SYSTEM_NAVIGATE_KEYS)
91     );
92 
93     private static final Set<Integer> VALID_INPUT_TYPES = Set.of(
94             CarInputManager.INPUT_TYPE_ALL_INPUTS,
95             CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION,
96             CarInputManager.INPUT_TYPE_DPAD_KEYS,
97             CarInputManager.INPUT_TYPE_NAVIGATE_KEYS,
98             CarInputManager.INPUT_TYPE_SYSTEM_NAVIGATE_KEYS,
99             CarInputManager.INPUT_TYPE_CUSTOM_INPUT_EVENT
100     );
101 
102     private static final Set<Integer> VALID_ROTARY_TYPES = Set.of(
103             CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION
104     );
105 
106     // TODO(b/150818155) Need to migrate cluster code to use this to enable it.
107     private static final List<Integer> SUPPORTED_DISPLAY_TYPES = List.of(
108             CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
109             CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER
110     );
111 
112     private static final int[] EMPTY_INPUT_TYPES = new int[0];
113 
114     private final class ClientInfoForDisplay implements IBinder.DeathRecipient {
115         private final int mUid;
116         private final int mPid;
117         private final ICarInputCallback mCallback;
118         private final int mTargetDisplayType;
119         private final int[] mInputTypes;
120         private final int mFlags;
121         private final ArrayList<Integer> mGrantedTypes;
122 
ClientInfoForDisplay(int uid, int pid, @NonNull ICarInputCallback callback, int targetDisplayType, int[] inputTypes, int flags)123         private ClientInfoForDisplay(int uid, int pid, @NonNull ICarInputCallback callback,
124                 int targetDisplayType, int[] inputTypes, int flags) {
125             mUid = uid;
126             mPid = pid;
127             mCallback = callback;
128             mTargetDisplayType = targetDisplayType;
129             mInputTypes = inputTypes;
130             mFlags = flags;
131             mGrantedTypes = new ArrayList<>(inputTypes.length);
132         }
133 
linkToDeath()134         private void linkToDeath() throws RemoteException {
135             mCallback.asBinder().linkToDeath(this, 0);
136         }
137 
unlinkToDeath()138         private void unlinkToDeath() {
139             mCallback.asBinder().unlinkToDeath(this, 0);
140         }
141 
142         @Override
binderDied()143         public void binderDied() {
144             onClientDeath(this);
145         }
146 
147         @Override
toString()148         public String toString() {
149             return new StringBuilder(128)
150                     .append("Client{")
151                     .append("uid:")
152                     .append(mUid)
153                     .append(",pid:")
154                     .append(mPid)
155                     .append(",callback:")
156                     .append(mCallback)
157                     .append(",inputTypes:")
158                     .append(mInputTypes)
159                     .append(",flags:")
160                     .append(Integer.toHexString(mFlags))
161                     .append(",grantedTypes:")
162                     .append(mGrantedTypes)
163                     .append("}")
164                     .toString();
165         }
166     }
167 
168     private static final class ClientsToDispatch {
169         // The same client can be added multiple times. Keeping only the last addition is ok.
170         private final ArrayMap<ICarInputCallback, int[]> mClientsToDispatch =
171                 new ArrayMap<>();
172         private final int mDisplayType;
173 
ClientsToDispatch(int displayType)174         private ClientsToDispatch(int displayType) {
175             mDisplayType = displayType;
176         }
177 
add(ClientInfoForDisplay client)178         private void add(ClientInfoForDisplay client) {
179             int[] inputTypesToDispatch;
180             if (client.mGrantedTypes.isEmpty()) {
181                 inputTypesToDispatch = EMPTY_INPUT_TYPES;
182             } else {
183                 inputTypesToDispatch = ArrayUtils.convertToIntArray(client.mGrantedTypes);
184             }
185             mClientsToDispatch.put(client.mCallback, inputTypesToDispatch);
186         }
187     }
188 
189     private final Context mContext;
190 
191     private final Object mLock = new Object();
192 
193     /**
194      * key: display type, for quick discovery of client
195      * LinkedList is for implementing stack. First entry is the top.
196      */
197     @GuardedBy("mLock")
198     private final SparseArray<LinkedList<ClientInfoForDisplay>> mFullDisplayEventCapturers =
199             new SparseArray<>(2);
200 
201     /**
202      * key: display type -> inputType, for quick discovery of client
203      * LinkedList is for implementing stack. First entry is the top.
204      */
205     @GuardedBy("mLock")
206     private final SparseArray<SparseArray<LinkedList<ClientInfoForDisplay>>>
207             mPerInputTypeCapturers = new SparseArray<>(2);
208 
209     @GuardedBy("mLock")
210     /** key: display type -> client binder */
211     private final SparseArray<HashMap<IBinder, ClientInfoForDisplay>> mAllClients =
212             new SparseArray<>(1);
213 
214     @GuardedBy("mLock")
215     /** Keeps events to dispatch together. FIFO, last one added to last */
216     private final LinkedList<ClientsToDispatch> mClientDispatchQueue =
217             new LinkedList<>();
218 
219     /** Accessed from dispatch thread only */
220     private final ArrayList<KeyEvent> mKeyEventDispatchScratchList = new ArrayList<>(1);
221 
222     /** Accessed from dispatch thread only */
223     private final ArrayList<RotaryEvent> mRotaryEventDispatchScratchList = new ArrayList<>(1);
224 
225     /** Accessed from dispatch thread only */
226     private final ArrayList<CustomInputEvent> mCustomInputEventDispatchScratchList =
227             new ArrayList<>(1);
228 
229     @GuardedBy("mLock")
230     private int mNumKeyEventsDispatched;
231     @GuardedBy("mLock")
232     private int mNumRotaryEventsDispatched;
233 
234     private final String mClusterHomePackage;
235 
InputCaptureClientController(Context context)236     public InputCaptureClientController(Context context) {
237         mContext = context;
238         mClusterHomePackage = ComponentName.unflattenFromString(
239                 mContext.getString(R.string.config_clusterHomeActivity)).getPackageName();
240     }
241 
242     /**
243      * See
244      * {@link CarInputManager#requestInputEventCapture(CarInputManager.CarInputCaptureCallback,
245      * int, int[], int)}.
246      */
requestInputEventCapture(ICarInputCallback callback, @DisplayTypeEnum int targetDisplayType, int[] inputTypes, int requestFlags)247     public int requestInputEventCapture(ICarInputCallback callback,
248             @DisplayTypeEnum int targetDisplayType,
249             int[] inputTypes, int requestFlags) {
250         ICarImpl.assertAnyPermission(mContext, Car.PERMISSION_CAR_MONITOR_INPUT,
251                 android.Manifest.permission.MONITOR_INPUT);
252 
253         Preconditions.checkArgument(SUPPORTED_DISPLAY_TYPES.contains(targetDisplayType),
254                 "Display not supported yet:" + targetDisplayType);
255 
256         boolean isRequestingAllEvents =
257                 (requestFlags & CarInputManager.CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY) != 0;
258         if (isRequestingAllEvents) {
259             if (targetDisplayType != CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER) {
260                 ICarImpl.assertCallingFromSystemProcessOrSelf();
261             } else {  // for DISPLAY_TYPE_INSTRUMENT_CLUSTER
262                 if (!ICarImpl.isCallingFromSystemProcessOrSelf()) {
263                     CarServiceUtils.assertPackageName(mContext, mClusterHomePackage);
264                 }
265             }
266             if (inputTypes.length != 1 || inputTypes[0] != CarInputManager.INPUT_TYPE_ALL_INPUTS) {
267                 throw new IllegalArgumentException("Input type should be INPUT_TYPE_ALL_INPUTS"
268                         + " for CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY");
269             }
270         }
271         if (targetDisplayType != CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER
272                 && targetDisplayType != CarOccupantZoneManager.DISPLAY_TYPE_MAIN) {
273             throw new IllegalArgumentException("Unrecognized display type:" + targetDisplayType);
274         }
275         if (inputTypes == null) {
276             throw new IllegalArgumentException("inputTypes cannot be null");
277         }
278         assertInputTypeValid(inputTypes);
279         Arrays.sort(inputTypes);
280         IBinder clientBinder = callback.asBinder();
281         boolean allowsDelayedGrant =
282                 (requestFlags & CarInputManager.CAPTURE_REQ_FLAGS_ALLOW_DELAYED_GRANT) != 0;
283         int ret = CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED;
284         if (DBG_CALLS) {
285             Slog.i(TAG,
286                     "requestInputEventCapture callback:" + callback
287                             + ", display:" + targetDisplayType
288                             + ", inputTypes:" + Arrays.toString(inputTypes)
289                             + ", flags:" + requestFlags);
290         }
291         ClientsToDispatch clientsToDispatch = new ClientsToDispatch(targetDisplayType);
292         synchronized (mLock) {
293             HashMap<IBinder, ClientInfoForDisplay> allClientsForDisplay = mAllClients.get(
294                     targetDisplayType);
295             if (allClientsForDisplay == null) {
296                 allClientsForDisplay = new HashMap<IBinder, ClientInfoForDisplay>();
297                 mAllClients.put(targetDisplayType, allClientsForDisplay);
298             }
299             ClientInfoForDisplay oldClientInfo = allClientsForDisplay.remove(clientBinder);
300 
301             LinkedList<ClientInfoForDisplay> fullCapturersStack = mFullDisplayEventCapturers.get(
302                     targetDisplayType);
303             if (fullCapturersStack == null) {
304                 fullCapturersStack = new LinkedList<ClientInfoForDisplay>();
305                 mFullDisplayEventCapturers.put(targetDisplayType, fullCapturersStack);
306             }
307 
308             if (!isRequestingAllEvents && fullCapturersStack.size() > 0
309                     && fullCapturersStack.getFirst() != oldClientInfo && !allowsDelayedGrant) {
310                 // full capturing active. return failed if not delayed granting.
311                 return CarInputManager.INPUT_CAPTURE_RESPONSE_FAILED;
312             }
313             // Now we need to register client anyway, so do death monitoring from here.
314             ClientInfoForDisplay newClient = new ClientInfoForDisplay(Binder.getCallingUid(),
315                     Binder.getCallingPid(), callback, targetDisplayType,
316                     inputTypes, requestFlags);
317             try {
318                 newClient.linkToDeath();
319             } catch (RemoteException e) {
320                 // client died
321                 Slog.i(TAG, "requestInputEventCapture, cannot linkToDeath to client, pid:"
322                         + Binder.getCallingUid());
323                 return CarInputManager.INPUT_CAPTURE_RESPONSE_FAILED;
324             }
325 
326             SparseArray<LinkedList<ClientInfoForDisplay>> perInputStacks =
327                     mPerInputTypeCapturers.get(targetDisplayType);
328             if (perInputStacks == null) {
329                 perInputStacks = new SparseArray<LinkedList<ClientInfoForDisplay>>();
330                 mPerInputTypeCapturers.put(targetDisplayType, perInputStacks);
331             }
332 
333             if (isRequestingAllEvents) {
334                 if (!fullCapturersStack.isEmpty()) {
335                     ClientInfoForDisplay oldCapturer = fullCapturersStack.getFirst();
336                     if (oldCapturer != oldClientInfo) {
337                         oldCapturer.mGrantedTypes.clear();
338                         clientsToDispatch.add(oldCapturer);
339                     }
340                     fullCapturersStack.remove(oldClientInfo);
341                 } else { // All per input type top stack client should be notified.
342                     for (int i = 0; i < perInputStacks.size(); i++) {
343                         LinkedList<ClientInfoForDisplay> perTypeStack = perInputStacks.valueAt(i);
344                         if (!perTypeStack.isEmpty()) {
345                             ClientInfoForDisplay topClient = perTypeStack.getFirst();
346                             if (topClient != oldClientInfo) {
347                                 topClient.mGrantedTypes.clear();
348                                 clientsToDispatch.add(topClient);
349                             }
350                             // Even if the client was on top, the one in back does not need
351                             // update.
352                             perTypeStack.remove(oldClientInfo);
353                         }
354                     }
355                 }
356                 fullCapturersStack.addFirst(newClient);
357 
358             } else {
359                 boolean hadFullCapture = false;
360                 boolean fullCaptureActive = false;
361                 if (fullCapturersStack.size() > 0) {
362                     if (fullCapturersStack.getFirst() == oldClientInfo) {
363                         fullCapturersStack.remove(oldClientInfo);
364                         // Now we need to check if there is other client in fullCapturersStack
365                         if (fullCapturersStack.size() > 0) {
366                             fullCaptureActive = true;
367                             ret = CarInputManager.INPUT_CAPTURE_RESPONSE_DELAYED;
368                             ClientInfoForDisplay topClient = fullCapturersStack.getFirst();
369                             topClient.mGrantedTypes.clear();
370                             topClient.mGrantedTypes.add(CarInputManager.INPUT_TYPE_ALL_INPUTS);
371                             clientsToDispatch.add(topClient);
372                         } else {
373                             hadFullCapture = true;
374                         }
375                     } else {
376                         // other client doing full capturing and it should have DELAYED_GRANT flag.
377                         fullCaptureActive = true;
378                         ret = CarInputManager.INPUT_CAPTURE_RESPONSE_DELAYED;
379                     }
380                 }
381                 for (int i = 0; i < perInputStacks.size(); i++) {
382                     LinkedList<ClientInfoForDisplay> perInputStack = perInputStacks.valueAt(i);
383                     perInputStack.remove(oldClientInfo);
384                 }
385                 // Now go through per input stack
386                 for (int inputType : inputTypes) {
387                     LinkedList<ClientInfoForDisplay> perInputStack = perInputStacks.get(
388                             inputType);
389                     if (perInputStack == null) {
390                         perInputStack = new LinkedList<ClientInfoForDisplay>();
391                         perInputStacks.put(inputType, perInputStack);
392                     }
393                     if (perInputStack.size() > 0) {
394                         ClientInfoForDisplay oldTopClient = perInputStack.getFirst();
395                         if (oldTopClient.mGrantedTypes.remove(Integer.valueOf(inputType))) {
396                             clientsToDispatch.add(oldTopClient);
397                         }
398                     }
399                     if (!fullCaptureActive) {
400                         newClient.mGrantedTypes.add(inputType);
401                     }
402                     perInputStack.addFirst(newClient);
403                 }
404                 if (!fullCaptureActive && hadFullCapture) {
405                     for (int i = 0; i < perInputStacks.size(); i++) {
406                         int inputType = perInputStacks.keyAt(i);
407                         LinkedList<ClientInfoForDisplay> perInputStack = perInputStacks.valueAt(
408                                 i);
409                         if (perInputStack.size() > 0) {
410                             ClientInfoForDisplay topStackClient = perInputStack.getFirst();
411                             if (topStackClient == newClient) {
412                                 continue;
413                             }
414                             if (!topStackClient.mGrantedTypes.contains(inputType)) {
415                                 topStackClient.mGrantedTypes.add(inputType);
416                                 clientsToDispatch.add(topStackClient);
417                             }
418                         }
419                     }
420                 }
421             }
422             allClientsForDisplay.put(clientBinder, newClient);
423             dispatchClientCallbackLocked(clientsToDispatch);
424         }
425         return ret;
426     }
427 
428     /**
429      * See {@link CarInputManager#releaseInputEventCapture(int)}.
430      */
releaseInputEventCapture(ICarInputCallback callback, int targetDisplayType)431     public void releaseInputEventCapture(ICarInputCallback callback, int targetDisplayType) {
432         Objects.requireNonNull(callback);
433         Preconditions.checkArgument(SUPPORTED_DISPLAY_TYPES.contains(targetDisplayType),
434                 "Display not supported yet:" + targetDisplayType);
435 
436         if (DBG_CALLS) {
437             Slog.i(TAG, "releaseInputEventCapture callback:" + callback
438                     + ", display:" + targetDisplayType);
439         }
440         ClientsToDispatch clientsToDispatch = new ClientsToDispatch(targetDisplayType);
441         synchronized (mLock) {
442             HashMap<IBinder, ClientInfoForDisplay> allClientsForDisplay = mAllClients.get(
443                     targetDisplayType);
444             ClientInfoForDisplay clientInfo = allClientsForDisplay.remove(callback.asBinder());
445             if (clientInfo == null) {
446                 Slog.w(TAG, "Cannot find client for releaseInputEventCapture:" + callback);
447                 return;
448             }
449             clientInfo.unlinkToDeath();
450             LinkedList<ClientInfoForDisplay> fullCapturersStack = mFullDisplayEventCapturers.get(
451                     targetDisplayType);
452             boolean fullCaptureActive = false;
453             if (fullCapturersStack.size() > 0) {
454                 if (fullCapturersStack.getFirst() == clientInfo) {
455                     fullCapturersStack.remove(clientInfo);
456                     if (fullCapturersStack.size() > 0) {
457                         ClientInfoForDisplay newStopStackClient = fullCapturersStack.getFirst();
458                         newStopStackClient.mGrantedTypes.clear();
459                         newStopStackClient.mGrantedTypes.add(CarInputManager.INPUT_TYPE_ALL_INPUTS);
460                         clientsToDispatch.add(newStopStackClient);
461                         fullCaptureActive = true;
462                     }
463                 } else { // no notification as other client is in top of the stack
464                     fullCaptureActive = true;
465                 }
466                 fullCapturersStack.remove(clientInfo);
467             }
468             SparseArray<LinkedList<ClientInfoForDisplay>> perInputStacks =
469                     mPerInputTypeCapturers.get(targetDisplayType);
470             if (DBG_STACK) {
471                 Slog.i(TAG, "releaseInputEventCapture, fullCaptureActive:"
472                         + fullCaptureActive + ", perInputStacks:" + perInputStacks);
473             }
474             if (perInputStacks != null) {
475                 for (int i = 0; i < perInputStacks.size(); i++) {
476                     int inputType = perInputStacks.keyAt(i);
477                     LinkedList<ClientInfoForDisplay> perInputStack = perInputStacks.valueAt(i);
478                     if (perInputStack.size() > 0) {
479                         if (perInputStack.getFirst() == clientInfo) {
480                             perInputStack.removeFirst();
481                             if (perInputStack.size() > 0) {
482                                 ClientInfoForDisplay newTopClient = perInputStack.getFirst();
483                                 if (!fullCaptureActive) {
484                                     newTopClient.mGrantedTypes.add(inputType);
485                                     clientsToDispatch.add(newTopClient);
486                                 }
487                             }
488                         } else { // something else on top.
489                             if (!fullCaptureActive) {
490                                 ClientInfoForDisplay topClient = perInputStack.getFirst();
491                                 if (!topClient.mGrantedTypes.contains(inputType)) {
492                                     topClient.mGrantedTypes.add(inputType);
493                                     clientsToDispatch.add(topClient);
494                                 }
495                             }
496                             perInputStack.remove(clientInfo);
497                         }
498                     }
499                 }
500             }
501             dispatchClientCallbackLocked(clientsToDispatch);
502         }
503     }
504 
505     /**
506      * Dispatches the given {@code KeyEvent} to a capturing client if there is one.
507      *
508      * @param displayType the display type defined in {@code CarInputManager} such as
509      *                    {@link CarOccupantZoneManager#DISPLAY_TYPE_MAIN}
510      * @param event       the key event to handle
511      * @return true if the event was consumed.
512      */
onKeyEvent(@isplayTypeEnum int displayType, KeyEvent event)513     public boolean onKeyEvent(@DisplayTypeEnum int displayType, KeyEvent event) {
514         if (!SUPPORTED_DISPLAY_TYPES.contains(displayType)) {
515             return false;
516         }
517         Integer inputType = KEY_EVENT_TO_INPUT_TYPE.get(event.getKeyCode());
518         if (inputType == null) { // not supported key
519             inputType = CarInputManager.INPUT_TYPE_ALL_INPUTS;
520         }
521         ICarInputCallback callback;
522         synchronized (mLock) {
523             callback = getClientForInputTypeLocked(displayType, inputType);
524             if (callback == null) {
525                 return false;
526             }
527             mNumKeyEventsDispatched++;
528         }
529 
530         dispatchKeyEvent(displayType, event, callback);
531         return true;
532     }
533 
534     /**
535      * Dispatches the given {@code RotaryEvent} to a capturing client if there is one.
536      *
537      * @param displayType the display type defined in {@code CarInputManager} such as
538      *                    {@link CarOccupantZoneManager#DISPLAY_TYPE_MAIN}
539      * @param event       the Rotary event to handle
540      * @return true if the event was consumed.
541      */
onRotaryEvent(@isplayTypeEnum int displayType, RotaryEvent event)542     public boolean onRotaryEvent(@DisplayTypeEnum int displayType, RotaryEvent event) {
543         if (!SUPPORTED_DISPLAY_TYPES.contains(displayType)) {
544             Slog.w(TAG, "onRotaryEvent for not supported display:" + displayType);
545             return false;
546         }
547         int inputType = event.getInputType();
548         if (!VALID_ROTARY_TYPES.contains(inputType)) {
549             Slog.w(TAG, "onRotaryEvent for not supported input type:" + inputType);
550             return false;
551         }
552 
553         ICarInputCallback callback;
554         synchronized (mLock) {
555             callback = getClientForInputTypeLocked(displayType, inputType);
556             if (callback == null) {
557                 if (DBG_DISPATCH) {
558                     Slog.i(TAG, "onRotaryEvent no client for input type:" + inputType);
559                 }
560                 return false;
561             }
562             mNumRotaryEventsDispatched++;
563         }
564 
565         dispatchRotaryEvent(displayType, event, callback);
566         return true;
567     }
568 
569     /**
570      * Dispatches the given {@link CustomInputEvent} to a capturing client if there is one.
571      * Nothing happens if no callback was registered for the incoming event. In this case this
572      * method will return {@code false}.
573      * <p>
574      * In case of there are more than one client registered for this event, then only the first one
575      * will be notified.
576      *
577      * @param event the {@link CustomInputEvent} to dispatch
578      * @return {@code true} if the event was consumed.
579      */
onCustomInputEvent(CustomInputEvent event)580     public boolean onCustomInputEvent(CustomInputEvent event) {
581         int displayType = event.getTargetDisplayType();
582         if (!SUPPORTED_DISPLAY_TYPES.contains(displayType)) {
583             Slog.w(TAG, "onCustomInputEvent for not supported display:" + displayType);
584             return false;
585         }
586         ICarInputCallback callback;
587         synchronized (mLock) {
588             callback = getClientForInputTypeLocked(displayType,
589                     CarInputManager.INPUT_TYPE_CUSTOM_INPUT_EVENT);
590             if (callback == null) {
591                 Slog.w(TAG, "No client for input: " + CarInputManager.INPUT_TYPE_CUSTOM_INPUT_EVENT
592                         + " and display: " + displayType);
593                 return false;
594             }
595         }
596         dispatchCustomInputEvent(displayType, event, callback);
597         return true;
598     }
599 
getClientForInputTypeLocked(int targetDisplayType, int inputType)600     ICarInputCallback getClientForInputTypeLocked(int targetDisplayType, int inputType) {
601         LinkedList<ClientInfoForDisplay> fullCapturersStack = mFullDisplayEventCapturers.get(
602                 targetDisplayType);
603         if (fullCapturersStack != null && fullCapturersStack.size() > 0) {
604             return fullCapturersStack.getFirst().mCallback;
605         }
606 
607         SparseArray<LinkedList<ClientInfoForDisplay>> perInputStacks =
608                 mPerInputTypeCapturers.get(targetDisplayType);
609         if (perInputStacks == null) {
610             return null;
611         }
612 
613         LinkedList<ClientInfoForDisplay> perInputStack = perInputStacks.get(inputType);
614         if (perInputStack != null && perInputStack.size() > 0) {
615             return perInputStack.getFirst().mCallback;
616         }
617 
618         return null;
619     }
620 
onClientDeath(ClientInfoForDisplay client)621     private void onClientDeath(ClientInfoForDisplay client) {
622         releaseInputEventCapture(client.mCallback, client.mTargetDisplayType);
623     }
624 
625     /** dump for debugging */
dump(PrintWriter writer)626     public void dump(PrintWriter writer) {
627         writer.println("**InputCaptureClientController**");
628         synchronized (mLock) {
629             for (int display : SUPPORTED_DISPLAY_TYPES) {
630                 writer.println("***Display:" + display);
631 
632                 HashMap<IBinder, ClientInfoForDisplay> allClientsForDisplay = mAllClients.get(
633                         display);
634                 if (allClientsForDisplay != null) {
635                     writer.println("****All clients:");
636                     for (ClientInfoForDisplay client : allClientsForDisplay.values()) {
637                         writer.println(client);
638                     }
639                 }
640 
641                 LinkedList<ClientInfoForDisplay> fullCapturersStack =
642                         mFullDisplayEventCapturers.get(display);
643                 if (fullCapturersStack != null) {
644                     writer.println("****Full capture stack");
645                     for (ClientInfoForDisplay client : fullCapturersStack) {
646                         writer.println(client);
647                     }
648                 }
649                 SparseArray<LinkedList<ClientInfoForDisplay>> perInputStacks =
650                         mPerInputTypeCapturers.get(display);
651                 if (perInputStacks != null) {
652                     for (int i = 0; i < perInputStacks.size(); i++) {
653                         int inputType = perInputStacks.keyAt(i);
654                         LinkedList<ClientInfoForDisplay> perInputStack = perInputStacks.valueAt(i);
655                         if (perInputStack.size() > 0) {
656                             writer.println("**** Per Input stack, input type:" + inputType);
657                             for (ClientInfoForDisplay client : perInputStack) {
658                                 writer.println(client);
659                             }
660                         }
661                     }
662                 }
663             }
664             writer.println("mNumKeyEventsDispatched:" + mNumKeyEventsDispatched
665                     + ",mNumRotaryEventsDispatched:" + mNumRotaryEventsDispatched);
666         }
667     }
668 
dispatchClientCallbackLocked(ClientsToDispatch clientsToDispatch)669     private void dispatchClientCallbackLocked(ClientsToDispatch clientsToDispatch) {
670         if (clientsToDispatch.mClientsToDispatch.isEmpty()) {
671             return;
672         }
673         if (DBG_DISPATCH) {
674             Slog.i(TAG, "dispatchClientCallbackLocked, number of clients:"
675                     + clientsToDispatch.mClientsToDispatch.size());
676         }
677         mClientDispatchQueue.add(clientsToDispatch);
678         CarServiceUtils.runOnMain(() -> {
679             ClientsToDispatch clients;
680             synchronized (mLock) {
681                 if (mClientDispatchQueue.isEmpty()) {
682                     return;
683                 }
684                 clients = mClientDispatchQueue.pop();
685             }
686 
687             if (DBG_DISPATCH) {
688                 Slog.i(TAG, "dispatching to clients, num of clients:"
689                         + clients.mClientsToDispatch.size()
690                         + ", display:" + clients.mDisplayType);
691             }
692             for (int i = 0; i < clients.mClientsToDispatch.size(); i++) {
693                 ICarInputCallback callback = clients.mClientsToDispatch.keyAt(i);
694                 int[] inputTypes = clients.mClientsToDispatch.valueAt(i);
695                 Arrays.sort(inputTypes);
696                 if (DBG_DISPATCH) {
697                     Slog.i(TAG, "dispatching to client, callback:"
698                             + callback + ", inputTypes:" + Arrays.toString(inputTypes));
699                 }
700                 try {
701                     callback.onCaptureStateChanged(clients.mDisplayType, inputTypes);
702                 } catch (RemoteException e) {
703                     // Ignore. Let death handler deal with it.
704                 }
705             }
706         });
707     }
708 
dispatchKeyEvent(int targetDisplayType, KeyEvent event, ICarInputCallback callback)709     private void dispatchKeyEvent(int targetDisplayType, KeyEvent event,
710             ICarInputCallback callback) {
711         CarServiceUtils.runOnMain(() -> {
712             mKeyEventDispatchScratchList.clear();
713             mKeyEventDispatchScratchList.add(event);
714             try {
715                 callback.onKeyEvents(targetDisplayType, mKeyEventDispatchScratchList);
716             } catch (RemoteException e) {
717                 if (DBG_DISPATCH) {
718                     Slog.e(TAG, "Failed to dispatch KeyEvent " + event, e);
719                 }
720             }
721         });
722     }
723 
dispatchRotaryEvent(int targetDisplayType, RotaryEvent event, ICarInputCallback callback)724     private void dispatchRotaryEvent(int targetDisplayType, RotaryEvent event,
725             ICarInputCallback callback) {
726         if (DBG_DISPATCH) {
727             Slog.i(TAG, "dispatchRotaryEvent:" + event);
728         }
729         // TODO(b/159623196): Use HandlerThread for dispatching rather than relying on the main
730         //     thread. Change here and other dispatch methods.
731         CarServiceUtils.runOnMain(() -> {
732             mRotaryEventDispatchScratchList.clear();
733             mRotaryEventDispatchScratchList.add(event);
734             try {
735                 callback.onRotaryEvents(targetDisplayType, mRotaryEventDispatchScratchList);
736             } catch (RemoteException e) {
737                 if (DBG_DISPATCH) {
738                     Slog.e(TAG, "Failed to dispatch RotaryEvent " + event, e);
739                 }
740             }
741         });
742     }
743 
dispatchCustomInputEvent(@isplayTypeEnum int targetDisplayType, CustomInputEvent event, ICarInputCallback callback)744     private void dispatchCustomInputEvent(@DisplayTypeEnum int targetDisplayType,
745             CustomInputEvent event,
746             ICarInputCallback callback) {
747         if (DBG_DISPATCH) {
748             Slog.d(TAG, "dispatchCustomInputEvent:" + event);
749         }
750         CarServiceUtils.runOnMain(() -> {
751             mCustomInputEventDispatchScratchList.clear();
752             mCustomInputEventDispatchScratchList.add(event);
753             try {
754                 callback.onCustomInputEvents(targetDisplayType,
755                         mCustomInputEventDispatchScratchList);
756             } catch (RemoteException e) {
757                 if (DBG_DISPATCH) {
758                     Slog.e(TAG, "Failed to dispatch CustomInputEvent " + event, e);
759                 }
760             }
761         });
762     }
763 
assertInputTypeValid(int[] inputTypes)764     private static void assertInputTypeValid(int[] inputTypes) {
765         for (int inputType : inputTypes) {
766             if (!VALID_INPUT_TYPES.contains(inputType)) {
767                 throw new IllegalArgumentException("Invalid input type:" + inputType
768                         + ", inputTypes:" + Arrays.toString(inputTypes));
769             }
770         }
771     }
772 }
773