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