1 /* 2 * Copyright (C) 2016 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 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING; 21 22 import static com.android.car.BuiltinPackageDependency.CAR_ACCESSIBILITY_SERVICE_CLASS; 23 import static com.android.car.CarServiceUtils.getCommonHandlerThread; 24 import static com.android.car.CarServiceUtils.getContentResolverForUser; 25 import static com.android.car.CarServiceUtils.isEventOfType; 26 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 27 import static com.android.internal.util.Preconditions.checkArgument; 28 29 import static java.util.Objects.requireNonNull; 30 31 import android.annotation.Nullable; 32 import android.annotation.UserIdInt; 33 import android.car.CarOccupantZoneManager; 34 import android.car.CarOccupantZoneManager.OccupantZoneInfo; 35 import android.car.CarProjectionManager; 36 import android.car.VehicleAreaSeat; 37 import android.car.builtin.input.InputManagerHelper; 38 import android.car.builtin.util.AssistUtilsHelper; 39 import android.car.builtin.util.AssistUtilsHelper.VoiceInteractionSessionShowCallbackHelper; 40 import android.car.builtin.util.Slogf; 41 import android.car.builtin.view.InputEventHelper; 42 import android.car.builtin.view.KeyEventHelper; 43 import android.car.input.CarInputManager; 44 import android.car.input.CustomInputEvent; 45 import android.car.input.ICarInput; 46 import android.car.input.ICarInputCallback; 47 import android.car.input.RotaryEvent; 48 import android.car.user.CarUserManager.UserLifecycleListener; 49 import android.car.user.UserLifecycleEventFilter; 50 import android.content.ContentResolver; 51 import android.content.Context; 52 import android.content.Intent; 53 import android.content.pm.PackageManager; 54 import android.content.res.Resources; 55 import android.hardware.input.InputManager; 56 import android.net.Uri; 57 import android.os.Binder; 58 import android.os.Handler; 59 import android.os.UserHandle; 60 import android.provider.CallLog.Calls; 61 import android.provider.Settings; 62 import android.telecom.TelecomManager; 63 import android.text.TextUtils; 64 import android.util.Log; 65 import android.util.SparseArray; 66 import android.util.SparseBooleanArray; 67 import android.util.proto.ProtoOutputStream; 68 import android.view.Display; 69 import android.view.InputDevice; 70 import android.view.InputEvent; 71 import android.view.KeyEvent; 72 import android.view.MotionEvent; 73 import android.view.ViewConfiguration; 74 75 import com.android.car.bluetooth.CarBluetoothService; 76 import com.android.car.hal.InputHalService; 77 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 78 import com.android.car.internal.common.UserHelperLite; 79 import com.android.car.internal.util.IndentingPrintWriter; 80 import com.android.car.power.CarPowerManagementService; 81 import com.android.car.systeminterface.SystemInterface; 82 import com.android.car.user.CarUserService; 83 import com.android.internal.annotations.GuardedBy; 84 import com.android.internal.annotations.VisibleForTesting; 85 86 import java.util.ArrayList; 87 import java.util.Arrays; 88 import java.util.BitSet; 89 import java.util.Collections; 90 import java.util.List; 91 import java.util.function.BooleanSupplier; 92 import java.util.function.IntSupplier; 93 import java.util.function.Supplier; 94 95 /** 96 * CarInputService monitors and handles input event through vehicle HAL. 97 */ 98 public class CarInputService extends ICarInput.Stub 99 implements CarServiceBase, InputHalService.InputListener { 100 public static final String ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR = ":"; 101 102 private static final int MAX_RETRIES_FOR_ENABLING_ACCESSIBILITY_SERVICES = 5; 103 104 @VisibleForTesting 105 static final String TAG = CarLog.TAG_INPUT; 106 107 private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG); 108 109 @VisibleForTesting 110 static final String LONG_PRESS_TIMEOUT = "long_press_timeout"; 111 112 /** An interface to receive {@link KeyEvent}s as they occur. */ 113 public interface KeyEventListener { 114 /** Called when a key event occurs. */ 115 // TODO(b/247170915): This method is no needed anymore, please remove and use 116 // onKeyEvent(KeyEvent event, intDisplayType, int seat) onKeyEvent(KeyEvent event)117 default void onKeyEvent(KeyEvent event) { 118 } 119 120 /** 121 * Called when a key event occurs with seat. 122 * 123 * @param event the key event that occurred 124 * @param displayType target display the event is associated with should be one of 125 * {@link CarOccupantZoneManager#DISPLAY_TYPE_MAIN}, 126 * {@link CarOccupantZoneManager#DISPLAY_TYPE_INSTRUMENT_CLUSTER}, 127 * {@link CarOccupantZoneManager#DISPLAY_TYPE_HUD}, 128 * {@link CarOccupantZoneManager#DISPLAY_TYPE_INPUT}, 129 * {@link CarOccupantZoneManager#DISPLAY_TYPE_AUXILIARY}, 130 * @param seat the area id this event is occurring from 131 */ onKeyEvent(KeyEvent event, @DisplayTypeEnum int displayType, @VehicleAreaSeat.Enum int seat)132 default void onKeyEvent(KeyEvent event, @DisplayTypeEnum int displayType, 133 @VehicleAreaSeat.Enum int seat) { 134 // No op 135 } 136 } 137 138 /** An interface to receive {@link MotionEvent}s as they occur. */ 139 public interface MotionEventListener { 140 /** Called when a motion event occurs. */ onMotionEvent(MotionEvent event)141 void onMotionEvent(MotionEvent event); 142 } 143 144 private final class KeyPressTimer { 145 private final Runnable mLongPressRunnable; 146 private final Runnable mCallback = this::onTimerExpired; 147 private final IntSupplier mLongPressDelaySupplier; 148 149 @GuardedBy("CarInputService.this.mLock") 150 private final Handler mHandler; 151 @GuardedBy("CarInputService.this.mLock") 152 private boolean mDown; 153 @GuardedBy("CarInputService.this.mLock") 154 private boolean mLongPress = false; 155 KeyPressTimer( Handler handler, IntSupplier longPressDelaySupplier, Runnable longPressRunnable)156 KeyPressTimer( 157 Handler handler, IntSupplier longPressDelaySupplier, Runnable longPressRunnable) { 158 mHandler = handler; 159 mLongPressRunnable = longPressRunnable; 160 mLongPressDelaySupplier = longPressDelaySupplier; 161 } 162 163 /** Marks that a key was pressed, and starts the long-press timer. */ keyDown()164 void keyDown() { 165 synchronized (mLock) { 166 mDown = true; 167 mLongPress = false; 168 mHandler.removeCallbacks(mCallback); 169 mHandler.postDelayed(mCallback, mLongPressDelaySupplier.getAsInt()); 170 } 171 } 172 173 /** 174 * Marks that a key was released, and stops the long-press timer. 175 * <p> 176 * Returns true if the press was a long-press. 177 */ keyUp()178 boolean keyUp() { 179 synchronized (mLock) { 180 mHandler.removeCallbacks(mCallback); 181 mDown = false; 182 return mLongPress; 183 } 184 } 185 onTimerExpired()186 private void onTimerExpired() { 187 synchronized (mLock) { 188 // If the timer expires after key-up, don't retroactively make the press long. 189 if (!mDown) { 190 return; 191 } 192 mLongPress = true; 193 } 194 mLongPressRunnable.run(); 195 } 196 } 197 198 private final VoiceInteractionSessionShowCallbackHelper mShowCallback; 199 static final VoiceInteractionSessionShowCallbackHelper sDefaultShowCallback = 200 new VoiceInteractionSessionShowCallbackHelper() { 201 @Override 202 public void onFailed() { 203 Slogf.w(TAG, "Failed to show VoiceInteractionSession"); 204 } 205 206 @Override 207 public void onShown() { 208 Slogf.d(TAG, "VoiceInteractionSessionShowCallbackHelper onShown()"); 209 } 210 }; 211 212 private final Context mContext; 213 private final InputHalService mInputHalService; 214 private final CarUserService mUserService; 215 private final CarOccupantZoneService mCarOccupantZoneService; 216 private final CarBluetoothService mCarBluetoothService; 217 private final CarPowerManagementService mCarPowerService; 218 private final TelecomManager mTelecomManager; 219 private final SystemInterface mSystemInterface; 220 221 // The default handler for main-display key events. By default, injects the events into 222 // the input queue via InputManager, but can be overridden for testing. 223 private final KeyEventListener mDefaultKeyHandler; 224 // The default handler for main-display motion events. By default, injects the events into 225 // the input queue via InputManager, but can be overridden for testing. 226 private final MotionEventListener mDefaultMotionHandler; 227 // The supplier for the last-called number. By default, gets the number from the call log. 228 // May be overridden for testing. 229 private final Supplier<String> mLastCalledNumberSupplier; 230 // The supplier for the system long-press delay, in milliseconds. By default, gets the value 231 // from Settings.Secure for the current user, falling back to the system-wide default 232 // long-press delay defined in ViewConfiguration. May be overridden for testing. 233 private final IntSupplier mLongPressDelaySupplier; 234 // ComponentName of the RotaryService. 235 private final String mRotaryServiceComponentName; 236 237 private final BooleanSupplier mShouldCallButtonEndOngoingCallSupplier; 238 239 private final Object mLock = new Object(); 240 241 @GuardedBy("mLock") 242 private CarProjectionManager.ProjectionKeyEventHandler mProjectionKeyEventHandler; 243 244 @GuardedBy("mLock") 245 private final BitSet mProjectionKeyEventsSubscribed = new BitSet(); 246 247 private final KeyPressTimer mVoiceKeyTimer; 248 private final KeyPressTimer mCallKeyTimer; 249 250 @GuardedBy("mLock") 251 private KeyEventListener mInstrumentClusterKeyListener; 252 253 @GuardedBy("mLock") 254 private final SparseArray<KeyEventListener> mListeners = new SparseArray<>(); 255 256 private final InputCaptureClientController mCaptureController; 257 258 private int mDriverSeat = VehicleAreaSeat.SEAT_UNKNOWN; 259 260 private boolean mHasDriver; 261 262 // key: seat, value: power key handled by ACTION_DOWN. 263 // {@code true} if the screen was turned on with the power key ACTION_DOWN. In this case, 264 // we need to block the power key's ACTION_UP to prevent the device from going back to sleep. 265 // When ACTION_UP, it is released with {@code false}. 266 private SparseBooleanArray mPowerKeyHandled = new SparseBooleanArray(); 267 268 // The default handler for special keys. The behavior of the keys is implemented in this 269 // service. It can be overridden by {@link #registerKeyEventListener}. 270 private final KeyEventListener mDefaultSpecialKeyHandler = new KeyEventListener() { 271 @Override 272 public void onKeyEvent(KeyEvent event, @DisplayTypeEnum int displayType, 273 @VehicleAreaSeat.Enum int seat) { 274 switch (event.getKeyCode()) { 275 case KeyEvent.KEYCODE_HOME: 276 handleHomeKey(event, displayType, seat); 277 break; 278 case KeyEvent.KEYCODE_POWER: 279 handlePowerKey(event, displayType, seat); 280 break; 281 default: 282 Slogf.e(TAG, "Key event %s is not supported by special key handler", 283 KeyEvent.keyCodeToString(event.getKeyCode())); 284 break; 285 } 286 } 287 }; 288 289 private final UserLifecycleListener mUserLifecycleListener = event -> { 290 if (!isEventOfType(TAG, event, USER_LIFECYCLE_EVENT_TYPE_SWITCHING)) { 291 return; 292 } 293 Slogf.d(TAG, "CarInputService.onEvent(%s)", event); 294 295 updateCarAccessibilityServicesSettings(event.getUserId()); 296 }; 297 getViewLongPressDelay(Context context)298 private static int getViewLongPressDelay(Context context) { 299 return Settings.Secure.getInt(getContentResolverForUser(context, 300 UserHandle.CURRENT.getIdentifier()), LONG_PRESS_TIMEOUT, 301 ViewConfiguration.getLongPressTimeout()); 302 } 303 CarInputService(Context context, InputHalService inputHalService, CarUserService userService, CarOccupantZoneService occupantZoneService, CarBluetoothService bluetoothService, CarPowerManagementService carPowerService, SystemInterface systemInterface)304 public CarInputService(Context context, InputHalService inputHalService, 305 CarUserService userService, CarOccupantZoneService occupantZoneService, 306 CarBluetoothService bluetoothService, CarPowerManagementService carPowerService, 307 SystemInterface systemInterface) { 308 this(context, inputHalService, userService, occupantZoneService, bluetoothService, 309 carPowerService, systemInterface, 310 new Handler(getCommonHandlerThread().getLooper()), 311 context.getSystemService(TelecomManager.class), 312 new KeyEventListener() { 313 @Override 314 public void onKeyEvent(KeyEvent event, @DisplayTypeEnum int displayType, 315 @VehicleAreaSeat.Enum int seat) { 316 InputManagerHelper.injectInputEvent( 317 context.getSystemService(InputManager.class), event); 318 } 319 }, 320 /* defaultMotionHandler= */ event -> InputManagerHelper.injectInputEvent( 321 context.getSystemService(InputManager.class), event), 322 /* lastCalledNumberSupplier= */ () -> Calls.getLastOutgoingCall(context), 323 /* longPressDelaySupplier= */ () -> getViewLongPressDelay(context), 324 /* shouldCallButtonEndOngoingCallSupplier= */ () -> context.getResources() 325 .getBoolean(R.bool.config_callButtonEndsOngoingCall), 326 new InputCaptureClientController(context), sDefaultShowCallback); 327 } 328 329 @VisibleForTesting CarInputService(Context context, InputHalService inputHalService, CarUserService userService, CarOccupantZoneService occupantZoneService, CarBluetoothService bluetoothService, CarPowerManagementService carPowerService, SystemInterface systemInterface, Handler handler, TelecomManager telecomManager, KeyEventListener defaultKeyHandler, MotionEventListener defaultMotionHandler, Supplier<String> lastCalledNumberSupplier, IntSupplier longPressDelaySupplier, BooleanSupplier shouldCallButtonEndOngoingCallSupplier, InputCaptureClientController captureController, VoiceInteractionSessionShowCallbackHelper showCallback)330 CarInputService(Context context, InputHalService inputHalService, CarUserService userService, 331 CarOccupantZoneService occupantZoneService, CarBluetoothService bluetoothService, 332 CarPowerManagementService carPowerService, SystemInterface systemInterface, 333 Handler handler, TelecomManager telecomManager, 334 KeyEventListener defaultKeyHandler, MotionEventListener defaultMotionHandler, 335 Supplier<String> lastCalledNumberSupplier, IntSupplier longPressDelaySupplier, 336 BooleanSupplier shouldCallButtonEndOngoingCallSupplier, 337 InputCaptureClientController captureController, 338 VoiceInteractionSessionShowCallbackHelper showCallback) { 339 super(); 340 mContext = context; 341 mCaptureController = captureController; 342 mInputHalService = inputHalService; 343 mUserService = userService; 344 mCarOccupantZoneService = occupantZoneService; 345 mCarBluetoothService = bluetoothService; 346 mCarPowerService = carPowerService; 347 mSystemInterface = systemInterface; 348 mTelecomManager = telecomManager; 349 mDefaultKeyHandler = defaultKeyHandler; 350 mDefaultMotionHandler = defaultMotionHandler; 351 mLastCalledNumberSupplier = lastCalledNumberSupplier; 352 mLongPressDelaySupplier = longPressDelaySupplier; 353 mShowCallback = showCallback; 354 355 mVoiceKeyTimer = new KeyPressTimer( 356 handler, longPressDelaySupplier, this::handleVoiceAssistLongPress); 357 mCallKeyTimer = new KeyPressTimer(handler, longPressDelaySupplier, 358 this::handleCallLongPress); 359 360 mRotaryServiceComponentName = mContext.getString(R.string.rotaryService); 361 mShouldCallButtonEndOngoingCallSupplier = shouldCallButtonEndOngoingCallSupplier; 362 363 registerKeyEventListener(mDefaultSpecialKeyHandler, 364 Arrays.asList(KeyEvent.KEYCODE_HOME, KeyEvent.KEYCODE_POWER)); 365 } 366 367 /** 368 * Set projection key event listener. If null, unregister listener. 369 */ setProjectionKeyEventHandler( @ullable CarProjectionManager.ProjectionKeyEventHandler listener, @Nullable BitSet events)370 public void setProjectionKeyEventHandler( 371 @Nullable CarProjectionManager.ProjectionKeyEventHandler listener, 372 @Nullable BitSet events) { 373 synchronized (mLock) { 374 mProjectionKeyEventHandler = listener; 375 mProjectionKeyEventsSubscribed.clear(); 376 if (events != null) { 377 mProjectionKeyEventsSubscribed.or(events); 378 } 379 } 380 } 381 382 /** 383 * This method registers a keyEventListener to listen on key events that it is interested in. 384 * 385 * @param listener the listener to be registered 386 * @param keyCodesOfInterest the events of interest that the listener is interested in 387 * @throws IllegalArgumentException when an event is already registered to another listener 388 */ registerKeyEventListener(KeyEventListener listener, List<Integer> keyCodesOfInterest)389 public void registerKeyEventListener(KeyEventListener listener, 390 List<Integer> keyCodesOfInterest) { 391 requireNonNull(listener, "Key event listener can not be null"); 392 requireNonNull(keyCodesOfInterest, "Key events of interest can not be null"); 393 checkArgument(!keyCodesOfInterest.isEmpty(), 394 "Key events of interest can not be empty"); 395 synchronized (mLock) { 396 // Check for invalid key codes 397 for (int i = 0; i < keyCodesOfInterest.size(); i++) { 398 if (mListeners.contains(keyCodesOfInterest.get(i)) 399 && mListeners.get(keyCodesOfInterest.get(i)) != mDefaultSpecialKeyHandler) { 400 throw new IllegalArgumentException("Event " 401 + KeyEvent.keyCodeToString(keyCodesOfInterest.get(i)) 402 + " already registered to another listener"); 403 } 404 } 405 for (int i = 0; i < keyCodesOfInterest.size(); i++) { 406 mListeners.put(keyCodesOfInterest.get(i), listener); 407 } 408 } 409 } 410 411 /** 412 * Unregisters the key event listener for all the keys it currently listen to 413 * 414 * @param listener the listener to be unregistered 415 */ unregisterKeyEventListener(KeyEventListener listener)416 public boolean unregisterKeyEventListener(KeyEventListener listener) { 417 requireNonNull(listener, "Key event listener can not be null"); 418 synchronized (mLock) { 419 var keysToRemove = new ArrayList<Integer>(); 420 for (int c = 0; c < mListeners.size(); c++) { 421 if (!mListeners.valueAt(c).equals(listener)) { 422 continue; 423 } 424 keysToRemove.add(mListeners.keyAt(c)); 425 } 426 if (keysToRemove.isEmpty()) { 427 Slogf.w(TAG, "Failed to unregister listener ({%s} was not registered)", 428 listener); 429 return false; 430 } 431 for (int c = 0; c < keysToRemove.size(); c++) { 432 mListeners.delete(keysToRemove.get(c)); 433 } 434 } 435 return true; 436 } 437 438 /** 439 * Sets the instrument cluster key event listener. 440 */ setInstrumentClusterKeyListener(KeyEventListener listener)441 public void setInstrumentClusterKeyListener(KeyEventListener listener) { 442 synchronized (mLock) { 443 mInstrumentClusterKeyListener = listener; 444 } 445 } 446 447 @Override init()448 public void init() { 449 if (!mInputHalService.isKeyInputSupported()) { 450 Slogf.w(TAG, "Hal does not support key input."); 451 return; 452 } 453 Slogf.d(TAG, "Hal supports key input."); 454 mInputHalService.setInputListener(this); 455 UserLifecycleEventFilter userSwitchingEventFilter = new UserLifecycleEventFilter.Builder() 456 .addEventType(USER_LIFECYCLE_EVENT_TYPE_SWITCHING).build(); 457 mUserService.addUserLifecycleListener(userSwitchingEventFilter, mUserLifecycleListener); 458 mDriverSeat = mCarOccupantZoneService.getDriverSeat(); 459 mHasDriver = (mDriverSeat != VehicleAreaSeat.SEAT_UNKNOWN); 460 } 461 462 @Override release()463 public void release() { 464 synchronized (mLock) { 465 mProjectionKeyEventHandler = null; 466 mProjectionKeyEventsSubscribed.clear(); 467 mInstrumentClusterKeyListener = null; 468 mListeners.clear(); 469 } 470 mUserService.removeUserLifecycleListener(mUserLifecycleListener); 471 } 472 473 @Override onKeyEvent(KeyEvent event, @DisplayTypeEnum int targetDisplayType)474 public void onKeyEvent(KeyEvent event, @DisplayTypeEnum int targetDisplayType) { 475 onKeyEvent(event, targetDisplayType, mDriverSeat); 476 } 477 478 /** 479 * Called for key event 480 * 481 * @throws IllegalArgumentException if the passed seat is an unknown seat and the driver seat is 482 * not an unknown seat 483 */ 484 @Override onKeyEvent(KeyEvent event, @DisplayTypeEnum int targetDisplayType, @VehicleAreaSeat.Enum int seat)485 public void onKeyEvent(KeyEvent event, @DisplayTypeEnum int targetDisplayType, 486 @VehicleAreaSeat.Enum int seat) { 487 if (mHasDriver && seat == VehicleAreaSeat.SEAT_UNKNOWN) { 488 // To support {@link #onKeyEvent(KeyEvent, int)}, we need to check whether the driver 489 // exists or not. 490 // For example, for a passenger-only system, the driver seat might be SEAT_UNKNOWN. 491 // In this case, no exception should be occurred. 492 throw new IllegalArgumentException("Unknown seat"); 493 } 494 495 // Update user activity information to car power management service. 496 notifyUserActivity(event, targetDisplayType, seat); 497 498 // Driver key events are handled the same as HW_KEY_INPUT. 499 if (seat == mDriverSeat) { 500 dispatchKeyEventForDriver(event, targetDisplayType); 501 return; 502 } 503 504 // Notifies the listeners of the key event. 505 notifyKeyEventListener(event, targetDisplayType, seat); 506 } 507 dispatchKeyEventForDriver(KeyEvent event, @DisplayTypeEnum int targetDisplayType)508 private void dispatchKeyEventForDriver(KeyEvent event, @DisplayTypeEnum int targetDisplayType) { 509 // Special case key code that have special "long press" handling for automotive 510 switch (event.getKeyCode()) { 511 case KeyEvent.KEYCODE_VOICE_ASSIST: 512 // TODO: b/288107028 - Pass target display type to handleVoiceAssistKey() 513 // when passenger displays support voice assist keys 514 handleVoiceAssistKey(event, targetDisplayType); 515 return; 516 case KeyEvent.KEYCODE_CALL: 517 handleCallKey(event); 518 return; 519 default: 520 break; 521 } 522 523 assignDisplayId(event, targetDisplayType); 524 525 // Allow specifically targeted keys to be routed to the cluster 526 if (targetDisplayType == CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER 527 && handleInstrumentClusterKey(event)) { 528 return; 529 } 530 if (mCaptureController.onKeyEvent(targetDisplayType, event)) { 531 return; 532 } 533 mDefaultKeyHandler.onKeyEvent(event, targetDisplayType, mDriverSeat); 534 } 535 536 /** 537 * Called for motion event 538 */ 539 @Override onMotionEvent(MotionEvent event, @DisplayTypeEnum int targetDisplayType, @VehicleAreaSeat.Enum int seat)540 public void onMotionEvent(MotionEvent event, @DisplayTypeEnum int targetDisplayType, 541 @VehicleAreaSeat.Enum int seat) { 542 if (seat == VehicleAreaSeat.SEAT_UNKNOWN) { 543 throw new IllegalArgumentException("Unknown seat"); 544 } 545 546 notifyUserActivity(event, targetDisplayType, seat); 547 assignDisplayIdForSeat(event, targetDisplayType, seat); 548 mDefaultMotionHandler.onMotionEvent(event); 549 } 550 notifyKeyEventListener(KeyEvent event, int targetDisplay, int seat)551 private void notifyKeyEventListener(KeyEvent event, int targetDisplay, int seat) { 552 KeyEventListener keyEventListener; 553 synchronized (mLock) { 554 keyEventListener = mListeners.get(event.getKeyCode()); 555 } 556 if (keyEventListener == null) { 557 if (DBG) { 558 Slogf.d(TAG, "Key event listener not found for event %s", 559 KeyEvent.keyCodeToString(event.getKeyCode())); 560 } 561 // If there is no listener for the key event, it is injected into the core system. 562 keyEventListener = mDefaultKeyHandler; 563 } 564 assignDisplayIdForSeat(event, targetDisplay, seat); 565 keyEventListener.onKeyEvent(event, targetDisplay, seat); 566 } 567 assignDisplayId(KeyEvent event, @DisplayTypeEnum int targetDisplayType)568 private void assignDisplayId(KeyEvent event, @DisplayTypeEnum int targetDisplayType) { 569 // Setting display id for driver user id (currently MAIN and CLUSTER display types are 570 // linked to driver user only) 571 int newDisplayId = mCarOccupantZoneService.getDisplayIdForDriver(targetDisplayType); 572 573 // Display id is overridden even if already set. 574 KeyEventHelper.setDisplayId(event, newDisplayId); 575 } 576 assignDisplayIdForSeat(InputEvent event, @DisplayTypeEnum int targetDisplayType, @VehicleAreaSeat.Enum int seat)577 private void assignDisplayIdForSeat(InputEvent event, @DisplayTypeEnum int targetDisplayType, 578 @VehicleAreaSeat.Enum int seat) { 579 int newDisplayId = getDisplayIdForSeat(targetDisplayType, seat); 580 581 InputEventHelper.setDisplayId(event, newDisplayId); 582 } 583 getDisplayIdForSeat(@isplayTypeEnum int targetDisplayType, @VehicleAreaSeat.Enum int seat)584 private int getDisplayIdForSeat(@DisplayTypeEnum int targetDisplayType, 585 @VehicleAreaSeat.Enum int seat) { 586 int zoneId = mCarOccupantZoneService.getOccupantZoneIdForSeat(seat); 587 return mCarOccupantZoneService.getDisplayForOccupant(zoneId, targetDisplayType); 588 } 589 590 /** 591 * Notifies the car power manager that user activity happened. 592 */ notifyUserActivity(InputEvent event, @DisplayTypeEnum int targetDisplayType, @VehicleAreaSeat.Enum int seat)593 private void notifyUserActivity(InputEvent event, @DisplayTypeEnum int targetDisplayType, 594 @VehicleAreaSeat.Enum int seat) { 595 int displayId = getDisplayIdForSeat(targetDisplayType, seat); 596 if (displayId == Display.INVALID_DISPLAY) { 597 return; 598 } 599 mCarPowerService.notifyUserActivity(displayId, event.getEventTime()); 600 } 601 602 @Override onRotaryEvent(RotaryEvent event, @DisplayTypeEnum int targetDisplay)603 public void onRotaryEvent(RotaryEvent event, @DisplayTypeEnum int targetDisplay) { 604 if (!mCaptureController.onRotaryEvent(targetDisplay, event)) { 605 List<KeyEvent> keyEvents = rotaryEventToKeyEvents(event); 606 for (KeyEvent keyEvent : keyEvents) { 607 onKeyEvent(keyEvent, targetDisplay); 608 } 609 } 610 } 611 612 @Override onCustomInputEvent(CustomInputEvent event)613 public void onCustomInputEvent(CustomInputEvent event) { 614 if (!mCaptureController.onCustomInputEvent(event)) { 615 Slogf.w(TAG, "Failed to propagate (%s)", event); 616 return; 617 } 618 Slogf.d(TAG, "Succeed injecting (%s)", event); 619 } 620 rotaryEventToKeyEvents(RotaryEvent event)621 private static List<KeyEvent> rotaryEventToKeyEvents(RotaryEvent event) { 622 int numClicks = event.getNumberOfClicks(); 623 int numEvents = numClicks * 2; // up / down per each click 624 boolean clockwise = event.isClockwise(); 625 int keyCode; 626 switch (event.getInputType()) { 627 case CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION: 628 keyCode = clockwise 629 ? KeyEvent.KEYCODE_NAVIGATE_NEXT 630 : KeyEvent.KEYCODE_NAVIGATE_PREVIOUS; 631 break; 632 case CarInputManager.INPUT_TYPE_ROTARY_VOLUME: 633 keyCode = clockwise 634 ? KeyEvent.KEYCODE_VOLUME_UP 635 : KeyEvent.KEYCODE_VOLUME_DOWN; 636 break; 637 default: 638 Slogf.e(TAG, "Unknown rotary input type: %d", event.getInputType()); 639 return Collections.EMPTY_LIST; 640 } 641 ArrayList<KeyEvent> keyEvents = new ArrayList<>(numEvents); 642 for (int i = 0; i < numClicks; i++) { 643 long uptime = event.getUptimeMillisForClick(i); 644 KeyEvent downEvent = createKeyEvent(/* down= */ true, uptime, uptime, keyCode); 645 KeyEvent upEvent = createKeyEvent(/* down= */ false, uptime, uptime, keyCode); 646 keyEvents.add(downEvent); 647 keyEvents.add(upEvent); 648 } 649 return keyEvents; 650 } 651 createKeyEvent(boolean down, long downTime, long eventTime, int keyCode)652 private static KeyEvent createKeyEvent(boolean down, long downTime, long eventTime, 653 int keyCode) { 654 return new KeyEvent( 655 downTime, 656 eventTime, 657 /* action= */ down ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP, 658 keyCode, 659 /* repeat= */ 0, 660 /* metaState= */ 0, 661 /* deviceId= */ 0, 662 /* scancode= */ 0, 663 /* flags= */ 0, 664 InputDevice.SOURCE_CLASS_BUTTON); 665 } 666 667 @Override requestInputEventCapture(ICarInputCallback callback, @DisplayTypeEnum int targetDisplayType, int[] inputTypes, int requestFlags)668 public int requestInputEventCapture(ICarInputCallback callback, 669 @DisplayTypeEnum int targetDisplayType, 670 int[] inputTypes, int requestFlags) { 671 return mCaptureController.requestInputEventCapture(callback, targetDisplayType, inputTypes, 672 requestFlags); 673 } 674 675 @Override releaseInputEventCapture(ICarInputCallback callback, @DisplayTypeEnum int targetDisplayType)676 public void releaseInputEventCapture(ICarInputCallback callback, 677 @DisplayTypeEnum int targetDisplayType) { 678 mCaptureController.releaseInputEventCapture(callback, targetDisplayType); 679 } 680 681 /** 682 * Injects the {@link KeyEvent} passed as parameter against Car Input API. 683 * <p> 684 * The event's display id will be overwritten accordingly to the display type (it will be 685 * retrieved from {@link CarOccupantZoneService}). 686 * 687 * @param event the event to inject 688 * @param targetDisplayType the display type associated with the event 689 * @throws SecurityException when caller doesn't have INJECT_EVENTS permission granted 690 */ 691 @Override injectKeyEvent(KeyEvent event, @DisplayTypeEnum int targetDisplayType)692 public void injectKeyEvent(KeyEvent event, @DisplayTypeEnum int targetDisplayType) { 693 // Permission check 694 if (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission( 695 android.Manifest.permission.INJECT_EVENTS)) { 696 throw new SecurityException("Injecting KeyEvent requires INJECT_EVENTS permission"); 697 } 698 699 long token = Binder.clearCallingIdentity(); 700 try { 701 // Redirect event to onKeyEvent 702 onKeyEvent(event, targetDisplayType); 703 } finally { 704 Binder.restoreCallingIdentity(token); 705 } 706 } 707 708 /** 709 * Injects the {@link KeyEvent} passed as parameter against Car Input API. 710 * <p> 711 * The event's display id will be overwritten accordingly to the display type (it will be 712 * retrieved from {@link CarOccupantZoneService}). 713 * 714 * @param event the event to inject 715 * @param targetDisplayType the display type associated with the event 716 * @param seat the seat associated with the event 717 * @throws SecurityException when caller doesn't have INJECT_EVENTS permission granted 718 */ injectKeyEventForSeat(KeyEvent event, @DisplayTypeEnum int targetDisplayType, @VehicleAreaSeat.Enum int seat)719 public void injectKeyEventForSeat(KeyEvent event, @DisplayTypeEnum int targetDisplayType, 720 @VehicleAreaSeat.Enum int seat) { 721 // Permission check 722 if (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission( 723 android.Manifest.permission.INJECT_EVENTS)) { 724 throw new SecurityException("Injecting KeyEvent requires INJECT_EVENTS permission"); 725 } 726 727 long token = Binder.clearCallingIdentity(); 728 try { 729 // Redirect event to onKeyEvent 730 onKeyEvent(event, targetDisplayType, seat); 731 } finally { 732 Binder.restoreCallingIdentity(token); 733 } 734 } 735 736 /** 737 * Injects the {@link MotionEvent} passed as parameter against Car Input API. 738 * <p> 739 * The event's display id will be overwritten accordingly to the display type (it will be 740 * retrieved from {@link CarOccupantZoneService}). 741 * 742 * @param event the event to inject 743 * @param targetDisplayType the display type associated with the event 744 * @param seat the seat associated with the event 745 * @throws SecurityException when caller doesn't have INJECT_EVENTS permission granted 746 */ injectMotionEventForSeat(MotionEvent event, @DisplayTypeEnum int targetDisplayType, @VehicleAreaSeat.Enum int seat)747 public void injectMotionEventForSeat(MotionEvent event, @DisplayTypeEnum int targetDisplayType, 748 @VehicleAreaSeat.Enum int seat) { 749 // Permission check 750 if (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission( 751 android.Manifest.permission.INJECT_EVENTS)) { 752 throw new SecurityException("Injecting MotionEvent requires INJECT_EVENTS permission"); 753 } 754 755 long token = Binder.clearCallingIdentity(); 756 try { 757 // Redirect event to onMotionEvent 758 onMotionEvent(event, targetDisplayType, seat); 759 } finally { 760 Binder.restoreCallingIdentity(token); 761 } 762 } 763 handleVoiceAssistKey(KeyEvent event, @DisplayTypeEnum int targetDisplayType)764 private void handleVoiceAssistKey(KeyEvent event, @DisplayTypeEnum int targetDisplayType) { 765 int action = event.getAction(); 766 if (action == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { 767 mVoiceKeyTimer.keyDown(); 768 dispatchProjectionKeyEvent(CarProjectionManager.KEY_EVENT_VOICE_SEARCH_KEY_DOWN); 769 } else if (action == KeyEvent.ACTION_UP) { 770 if (mVoiceKeyTimer.keyUp()) { 771 // Long press already handled by handleVoiceAssistLongPress(), nothing more to do. 772 // Hand it off to projection, if it's interested, otherwise we're done. 773 dispatchProjectionKeyEvent( 774 CarProjectionManager.KEY_EVENT_VOICE_SEARCH_LONG_PRESS_KEY_UP); 775 return; 776 } 777 778 if (dispatchProjectionKeyEvent( 779 CarProjectionManager.KEY_EVENT_VOICE_SEARCH_SHORT_PRESS_KEY_UP)) { 780 return; 781 } 782 783 // TODO: b/288107028 - Pass the actual target display type to onKeyEvent 784 // when passenger displays support voice assist keys 785 if (mCaptureController.onKeyEvent(targetDisplayType, event)) { 786 return; 787 } 788 789 launchDefaultVoiceAssistantHandler(); 790 } 791 } 792 handleVoiceAssistLongPress()793 private void handleVoiceAssistLongPress() { 794 // If projection wants this event, let it take it. 795 if (dispatchProjectionKeyEvent( 796 CarProjectionManager.KEY_EVENT_VOICE_SEARCH_LONG_PRESS_KEY_DOWN)) { 797 return; 798 } 799 // Otherwise, try to launch voice recognition on a BT device. 800 if (launchBluetoothVoiceRecognition()) { 801 return; 802 } 803 // Finally, fallback to the default voice assist handling. 804 launchDefaultVoiceAssistantHandler(); 805 } 806 handleCallKey(KeyEvent event)807 private void handleCallKey(KeyEvent event) { 808 int action = event.getAction(); 809 if (action == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { 810 mCallKeyTimer.keyDown(); 811 dispatchProjectionKeyEvent(CarProjectionManager.KEY_EVENT_CALL_KEY_DOWN); 812 } else if (action == KeyEvent.ACTION_UP) { 813 if (mCallKeyTimer.keyUp()) { 814 // Long press already handled by handleCallLongPress(), nothing more to do. 815 // Hand it off to projection, if it's interested, otherwise we're done. 816 dispatchProjectionKeyEvent(CarProjectionManager.KEY_EVENT_CALL_LONG_PRESS_KEY_UP); 817 return; 818 } 819 820 if (acceptCallIfRinging()) { 821 // Ringing call answered, nothing more to do. 822 return; 823 } 824 825 if (mShouldCallButtonEndOngoingCallSupplier.getAsBoolean() && endCall()) { 826 // On-going call ended, nothing more to do. 827 return; 828 } 829 830 if (dispatchProjectionKeyEvent( 831 CarProjectionManager.KEY_EVENT_CALL_SHORT_PRESS_KEY_UP)) { 832 return; 833 } 834 835 launchDialerHandler(); 836 } 837 } 838 handleCallLongPress()839 private void handleCallLongPress() { 840 // Long-press answers call if ringing, same as short-press. 841 if (acceptCallIfRinging()) { 842 return; 843 } 844 845 if (mShouldCallButtonEndOngoingCallSupplier.getAsBoolean() && endCall()) { 846 return; 847 } 848 849 if (dispatchProjectionKeyEvent(CarProjectionManager.KEY_EVENT_CALL_LONG_PRESS_KEY_DOWN)) { 850 return; 851 } 852 853 dialLastCallHandler(); 854 } 855 handlePowerKey(KeyEvent event, @DisplayTypeEnum int targetDisplayType, @VehicleAreaSeat.Enum int seat)856 private void handlePowerKey(KeyEvent event, @DisplayTypeEnum int targetDisplayType, 857 @VehicleAreaSeat.Enum int seat) { 858 if (DBG) { 859 Slogf.d(TAG, "called handlePowerKey: DisplayType=%d, VehicleAreaSeat=%d", 860 targetDisplayType, seat); 861 } 862 863 int displayId = getDisplayIdForSeat(targetDisplayType, seat); 864 if (displayId == Display.INVALID_DISPLAY) { 865 Slogf.e(TAG, "Failed to set display power state : Invalid display type=%d, seat=%d", 866 targetDisplayType, seat); 867 return; 868 } 869 870 boolean isOn = mSystemInterface.isDisplayEnabled(displayId); 871 872 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { 873 if (!isOn) { 874 mCarPowerService.setDisplayPowerState(displayId, /* enable= */ true); 875 setPowerKeyHandled(seat, /* handled= */ true); 876 } 877 } else if (event.getAction() == KeyEvent.ACTION_UP) { 878 if (isOn && !isPowerKeyHandled(seat)) { 879 mCarPowerService.setDisplayPowerState(displayId, /* enable= */ false); 880 } 881 setPowerKeyHandled(seat, /* handled= */ false); 882 } 883 } 884 isPowerKeyHandled(@ehicleAreaSeat.Enum int seat)885 private boolean isPowerKeyHandled(@VehicleAreaSeat.Enum int seat) { 886 return mPowerKeyHandled.get(seat); 887 } 888 setPowerKeyHandled(@ehicleAreaSeat.Enum int seat, boolean handled)889 private void setPowerKeyHandled(@VehicleAreaSeat.Enum int seat, boolean handled) { 890 mPowerKeyHandled.put(seat, handled); 891 } 892 handleHomeKey(KeyEvent event, @DisplayTypeEnum int targetDisplayType, @VehicleAreaSeat.Enum int seat)893 private void handleHomeKey(KeyEvent event, @DisplayTypeEnum int targetDisplayType, 894 @VehicleAreaSeat.Enum int seat) { 895 if (DBG) { 896 Slogf.d(TAG, "called handleHomeKey: DisplayType=%d, VehicleAreaSeat=%d", 897 targetDisplayType, seat); 898 } 899 if (event.getAction() == KeyEvent.ACTION_UP) { 900 int zoneId = mCarOccupantZoneService.getOccupantZoneIdForSeat(seat); 901 if (zoneId == OccupantZoneInfo.INVALID_ZONE_ID) { 902 Slogf.w(TAG, "Failed to get occupant zone id : Invalid seat=%d", seat); 903 return; 904 } 905 906 int userId = mCarOccupantZoneService.getUserForOccupant(zoneId); 907 int displayId = mCarOccupantZoneService.getDisplayForOccupant(zoneId, 908 targetDisplayType); 909 CarServiceUtils.startHomeForUserAndDisplay(mContext, userId, displayId); 910 } 911 } 912 dispatchProjectionKeyEvent(@arProjectionManager.KeyEventNum int event)913 private boolean dispatchProjectionKeyEvent(@CarProjectionManager.KeyEventNum int event) { 914 CarProjectionManager.ProjectionKeyEventHandler projectionKeyEventHandler; 915 synchronized (mLock) { 916 projectionKeyEventHandler = mProjectionKeyEventHandler; 917 if (projectionKeyEventHandler == null || !mProjectionKeyEventsSubscribed.get(event)) { 918 // No event handler, or event handler doesn't want this event - we're done. 919 return false; 920 } 921 } 922 923 projectionKeyEventHandler.onKeyEvent(event); 924 return true; 925 } 926 launchDialerHandler()927 private void launchDialerHandler() { 928 Slogf.i(TAG, "call key, launch dialer intent"); 929 Intent dialerIntent = new Intent(Intent.ACTION_DIAL); 930 mContext.startActivityAsUser(dialerIntent, UserHandle.CURRENT); 931 } 932 dialLastCallHandler()933 private void dialLastCallHandler() { 934 Slogf.i(TAG, "call key, dialing last call"); 935 936 String lastNumber = mLastCalledNumberSupplier.get(); 937 if (!TextUtils.isEmpty(lastNumber)) { 938 Intent callLastNumberIntent = new Intent(Intent.ACTION_CALL) 939 .setData(Uri.fromParts("tel", lastNumber, /* fragment= */ null)) 940 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 941 mContext.startActivityAsUser(callLastNumberIntent, UserHandle.CURRENT); 942 } 943 } 944 acceptCallIfRinging()945 private boolean acceptCallIfRinging() { 946 if (mTelecomManager != null && mTelecomManager.isRinging()) { 947 Slogf.i(TAG, "call key while ringing. Answer the call!"); 948 mTelecomManager.acceptRingingCall(); 949 return true; 950 } 951 return false; 952 } 953 endCall()954 private boolean endCall() { 955 if (mTelecomManager != null && mTelecomManager.isInCall()) { 956 Slogf.i(TAG, "End the call!"); 957 mTelecomManager.endCall(); 958 return true; 959 } 960 return false; 961 } 962 isBluetoothVoiceRecognitionEnabled()963 private boolean isBluetoothVoiceRecognitionEnabled() { 964 Resources res = mContext.getResources(); 965 return res.getBoolean(R.bool.enableLongPressBluetoothVoiceRecognition); 966 } 967 launchBluetoothVoiceRecognition()968 private boolean launchBluetoothVoiceRecognition() { 969 if (isBluetoothVoiceRecognitionEnabled()) { 970 Slogf.d(TAG, "Attempting to start Bluetooth Voice Recognition."); 971 return mCarBluetoothService.startBluetoothVoiceRecognition(); 972 } 973 Slogf.d(TAG, "Unable to start Bluetooth Voice Recognition, it is not enabled."); 974 return false; 975 } 976 launchDefaultVoiceAssistantHandler()977 private void launchDefaultVoiceAssistantHandler() { 978 Slogf.d(TAG, "voice key, invoke AssistUtilsHelper"); 979 980 if (!AssistUtilsHelper.showPushToTalkSessionForActiveService(mContext, mShowCallback)) { 981 Slogf.w(TAG, "Unable to retrieve assist component for current user"); 982 } 983 } 984 985 /** 986 * @return false if the KeyEvent isn't consumed because there is no 987 * InstrumentClusterKeyListener. 988 */ handleInstrumentClusterKey(KeyEvent event)989 private boolean handleInstrumentClusterKey(KeyEvent event) { 990 KeyEventListener listener; 991 synchronized (mLock) { 992 listener = mInstrumentClusterKeyListener; 993 } 994 if (listener == null) { 995 return false; 996 } 997 listener.onKeyEvent(event); 998 return true; 999 } 1000 getAccessibilityServicesToBeEnabled()1001 private List<String> getAccessibilityServicesToBeEnabled() { 1002 String carSafetyAccessibilityServiceComponentName = BuiltinPackageDependency 1003 .getComponentName(CAR_ACCESSIBILITY_SERVICE_CLASS); 1004 ArrayList<String> accessibilityServicesToBeEnabled = new ArrayList<>(); 1005 accessibilityServicesToBeEnabled.add(carSafetyAccessibilityServiceComponentName); 1006 if (!TextUtils.isEmpty(mRotaryServiceComponentName)) { 1007 accessibilityServicesToBeEnabled.add(mRotaryServiceComponentName); 1008 } 1009 return accessibilityServicesToBeEnabled; 1010 } 1011 createServiceListFromSettingsString( String accessibilityServicesString)1012 private static List<String> createServiceListFromSettingsString( 1013 String accessibilityServicesString) { 1014 return TextUtils.isEmpty(accessibilityServicesString) 1015 ? new ArrayList<>() 1016 : Arrays.asList(accessibilityServicesString.split( 1017 ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR)); 1018 } 1019 1020 @Override 1021 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)1022 public void dump(IndentingPrintWriter writer) { 1023 writer.println("*Input Service*"); 1024 writer.println("Long-press delay: " + mLongPressDelaySupplier.getAsInt() + "ms"); 1025 writer.println("Call button ends ongoing call: " 1026 + mShouldCallButtonEndOngoingCallSupplier.getAsBoolean()); 1027 mCaptureController.dump(writer); 1028 } 1029 1030 @Override 1031 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dumpProto(ProtoOutputStream proto)1032 public void dumpProto(ProtoOutputStream proto) { 1033 // No op 1034 } 1035 updateCarAccessibilityServicesSettings(@serIdInt int userId)1036 private void updateCarAccessibilityServicesSettings(@UserIdInt int userId) { 1037 if (UserHelperLite.isHeadlessSystemUser(userId)) { 1038 return; 1039 } 1040 List<String> accessibilityServicesToBeEnabled = getAccessibilityServicesToBeEnabled(); 1041 ContentResolver contentResolverForUser = getContentResolverForUser(mContext, userId); 1042 List<String> alreadyEnabledServices = createServiceListFromSettingsString( 1043 Settings.Secure.getString(contentResolverForUser, 1044 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES)); 1045 1046 int retry = 0; 1047 while (!alreadyEnabledServices.containsAll(accessibilityServicesToBeEnabled) 1048 && retry <= MAX_RETRIES_FOR_ENABLING_ACCESSIBILITY_SERVICES) { 1049 ArrayList<String> enabledServicesList = new ArrayList<>(alreadyEnabledServices); 1050 int numAccessibilityServicesToBeEnabled = accessibilityServicesToBeEnabled.size(); 1051 for (int i = 0; i < numAccessibilityServicesToBeEnabled; i++) { 1052 String serviceToBeEnabled = accessibilityServicesToBeEnabled.get(i); 1053 if (!enabledServicesList.contains(serviceToBeEnabled)) { 1054 enabledServicesList.add(serviceToBeEnabled); 1055 } 1056 } 1057 Settings.Secure.putString(contentResolverForUser, 1058 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, 1059 String.join(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR, enabledServicesList)); 1060 // Read again to account for any race condition with other parts of the code that might 1061 // be enabling other accessibility services. 1062 alreadyEnabledServices = createServiceListFromSettingsString( 1063 Settings.Secure.getString(contentResolverForUser, 1064 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES)); 1065 retry++; 1066 } 1067 if (!alreadyEnabledServices.containsAll(accessibilityServicesToBeEnabled)) { 1068 Slogf.e(TAG, "Failed to enable accessibility services"); 1069 } 1070 1071 Settings.Secure.putString(contentResolverForUser, Settings.Secure.ACCESSIBILITY_ENABLED, 1072 "1"); 1073 } 1074 } 1075