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