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