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