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