• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.accessibility;
18 
19 import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS;
20 
21 import android.accessibilityservice.AccessibilityService;
22 import android.app.PendingIntent;
23 import android.app.RemoteAction;
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.content.res.Configuration;
29 import android.graphics.drawable.Icon;
30 import android.hardware.input.InputManager;
31 import android.os.Handler;
32 import android.os.Looper;
33 import android.os.PowerManager;
34 import android.os.RemoteException;
35 import android.os.SystemClock;
36 import android.util.Log;
37 import android.view.IWindowManager;
38 import android.view.InputDevice;
39 import android.view.KeyCharacterMap;
40 import android.view.KeyEvent;
41 import android.view.WindowManagerGlobal;
42 import android.view.accessibility.AccessibilityManager;
43 import android.view.accessibility.Flags;
44 
45 import com.android.internal.R;
46 import com.android.internal.accessibility.util.AccessibilityUtils;
47 import com.android.internal.annotations.VisibleForTesting;
48 import com.android.internal.util.ScreenshotHelper;
49 import com.android.systemui.CoreStartable;
50 import com.android.systemui.dagger.SysUISingleton;
51 import com.android.systemui.recents.Recents;
52 import com.android.systemui.settings.DisplayTracker;
53 import com.android.systemui.settings.UserTracker;
54 import com.android.systemui.shade.ShadeController;
55 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
56 import com.android.systemui.statusbar.CommandQueue;
57 import com.android.systemui.statusbar.NotificationShadeWindowController;
58 import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
59 import com.android.systemui.statusbar.policy.ConfigurationController;
60 import com.android.systemui.statusbar.policy.KeyguardStateController;
61 import com.android.systemui.util.Assert;
62 
63 import dagger.Lazy;
64 
65 import java.util.Locale;
66 import java.util.Optional;
67 
68 import javax.inject.Inject;
69 
70 /**
71  * Class to register system actions with accessibility framework.
72  */
73 @SysUISingleton
74 public class SystemActions implements CoreStartable, ConfigurationController.ConfigurationListener {
75     private static final String TAG = "SystemActions";
76 
77     /**
78      * Action ID to go back.
79      */
80     private static final int SYSTEM_ACTION_ID_BACK = AccessibilityService.GLOBAL_ACTION_BACK; // = 1
81 
82     /**
83      * Action ID to go home.
84      */
85     private static final int SYSTEM_ACTION_ID_HOME = AccessibilityService.GLOBAL_ACTION_HOME; // = 2
86 
87     /**
88      * Action ID to toggle showing the overview of recent apps. Will fail on platforms that don't
89      * show recent apps.
90      */
91     private static final int SYSTEM_ACTION_ID_RECENTS =
92             AccessibilityService.GLOBAL_ACTION_RECENTS; // = 3
93 
94     /**
95      * Action ID to open the notifications.
96      */
97     private static final int SYSTEM_ACTION_ID_NOTIFICATIONS =
98             AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS; // = 4
99 
100     /**
101      * Action ID to open the quick settings.
102      */
103     private static final int SYSTEM_ACTION_ID_QUICK_SETTINGS =
104             AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS; // = 5
105 
106     /**
107      * Action ID to open the power long-press dialog.
108      */
109     private static final int SYSTEM_ACTION_ID_POWER_DIALOG =
110             AccessibilityService.GLOBAL_ACTION_POWER_DIALOG; // = 6
111 
112     /**
113      * Action ID to lock the screen
114      */
115     private static final int SYSTEM_ACTION_ID_LOCK_SCREEN =
116             AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN; // = 8
117 
118     /**
119      * Action ID to take a screenshot
120      */
121     private static final int SYSTEM_ACTION_ID_TAKE_SCREENSHOT =
122             AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT; // = 9
123 
124     /**
125      * Action ID to send the KEYCODE_HEADSETHOOK KeyEvent, which is used to answer/hang up calls and
126      * play/stop media
127      */
128     private static final int SYSTEM_ACTION_ID_KEYCODE_HEADSETHOOK =
129             AccessibilityService.GLOBAL_ACTION_KEYCODE_HEADSETHOOK; // = 10
130 
131     /**
132      * Action ID to trigger the accessibility button
133      */
134     public static final int SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON =
135             AccessibilityService.GLOBAL_ACTION_ACCESSIBILITY_BUTTON; // 11
136 
137     /**
138      * Action ID to show accessibility button's menu of services
139      */
140     public static final int SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON_CHOOSER =
141             AccessibilityService.GLOBAL_ACTION_ACCESSIBILITY_BUTTON_CHOOSER; // 12
142 
143     public static final int SYSTEM_ACTION_ID_ACCESSIBILITY_SHORTCUT =
144             AccessibilityService.GLOBAL_ACTION_ACCESSIBILITY_SHORTCUT; // 13
145 
146     public static final int SYSTEM_ACTION_ID_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE =
147             AccessibilityService.GLOBAL_ACTION_DISMISS_NOTIFICATION_SHADE; // 15
148 
149     /**
150      * Action ID to trigger the dpad up button
151      */
152     private static final int SYSTEM_ACTION_ID_DPAD_UP =
153             AccessibilityService.GLOBAL_ACTION_DPAD_UP; // 16
154 
155     /**
156      * Action ID to trigger the dpad down button
157      */
158     private static final int SYSTEM_ACTION_ID_DPAD_DOWN =
159             AccessibilityService.GLOBAL_ACTION_DPAD_DOWN; // 17
160 
161     /**
162      * Action ID to trigger the dpad left button
163      */
164     private static final int SYSTEM_ACTION_ID_DPAD_LEFT =
165             AccessibilityService.GLOBAL_ACTION_DPAD_LEFT; // 18
166 
167     /**
168      * Action ID to trigger the dpad right button
169      */
170     private static final int SYSTEM_ACTION_ID_DPAD_RIGHT =
171             AccessibilityService.GLOBAL_ACTION_DPAD_RIGHT; // 19
172 
173     /**
174      * Action ID to trigger dpad center keyevent
175      */
176     private static final int SYSTEM_ACTION_ID_DPAD_CENTER =
177             AccessibilityService.GLOBAL_ACTION_DPAD_CENTER; // 20
178 
179     /**
180      * Action ID to trigger menu key event.
181      */
182     private static final int SYSTEM_ACTION_ID_MENU =
183             AccessibilityService.GLOBAL_ACTION_MENU; // 21
184 
185     /**
186      * Action ID to trigger media play/pause key event.
187      */
188     private static final int SYSTEM_ACTION_ID_MEDIA_PLAY_PAUSE =
189             AccessibilityService.GLOBAL_ACTION_MEDIA_PLAY_PAUSE; // 22
190 
191     private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
192 
193     private final SystemActionsBroadcastReceiver mReceiver;
194     private final Context mContext;
195     private final UserTracker mUserTracker;
196     private final Optional<Recents> mRecentsOptional;
197     private final DisplayTracker mDisplayTracker;
198     private Locale mLocale;
199     private final AccessibilityManager mA11yManager;
200     private final NotificationShadeWindowController mNotificationShadeController;
201     private final KeyguardStateController mKeyguardStateController;
202     private final ShadeController mShadeController;
203     private final Lazy<PanelExpansionInteractor> mPanelExpansionInteractor;
204     private final StatusBarWindowCallback mNotificationShadeCallback;
205     private final ScreenshotHelper mScreenshotHelper;
206     private boolean mDismissNotificationShadeActionRegistered;
207 
208     @Inject
SystemActions(Context context, UserTracker userTracker, NotificationShadeWindowController notificationShadeController, KeyguardStateController keyguardStateController, ShadeController shadeController, Lazy<PanelExpansionInteractor> panelExpansionInteractor, Optional<Recents> recentsOptional, DisplayTracker displayTracker)209     public SystemActions(Context context,
210             UserTracker userTracker,
211             NotificationShadeWindowController notificationShadeController,
212             KeyguardStateController keyguardStateController,
213             ShadeController shadeController,
214             Lazy<PanelExpansionInteractor> panelExpansionInteractor,
215             Optional<Recents> recentsOptional,
216             DisplayTracker displayTracker) {
217         mContext = context;
218         mUserTracker = userTracker;
219         mKeyguardStateController = keyguardStateController;
220         mShadeController = shadeController;
221         mPanelExpansionInteractor = panelExpansionInteractor;
222         mRecentsOptional = recentsOptional;
223         mDisplayTracker = displayTracker;
224         mReceiver = new SystemActionsBroadcastReceiver();
225         mLocale = mContext.getResources().getConfiguration().getLocales().get(0);
226         mA11yManager = (AccessibilityManager) mContext.getSystemService(
227                 Context.ACCESSIBILITY_SERVICE);
228         mNotificationShadeController = notificationShadeController;
229         // Saving in instance variable since to prevent GC since
230         // NotificationShadeWindowController.registerCallback() only keeps weak references.
231         mNotificationShadeCallback =
232                 (keyguardShowing, keyguardOccluded, keyguardGoingAway, bouncerShowing, mDozing,
233                         panelExpanded, isDreaming, communalShowing) ->
234                         registerOrUnregisterDismissNotificationShadeAction();
235         mScreenshotHelper = new ScreenshotHelper(mContext);
236     }
237 
238     @Override
start()239     public void start() {
240         mNotificationShadeController.registerCallback(mNotificationShadeCallback);
241         mContext.registerReceiverForAllUsers(
242                 mReceiver,
243                 mReceiver.createIntentFilter(),
244                 PERMISSION_SELF,
245                 null,
246                 Context.RECEIVER_EXPORTED);
247         registerActions();
248     }
249 
250     @Override
onConfigChanged(Configuration newConfig)251     public void onConfigChanged(Configuration newConfig) {
252         final Locale locale = mContext.getResources().getConfiguration().getLocales().get(0);
253         if (!locale.equals(mLocale)) {
254             mLocale = locale;
255             registerActions();
256         }
257     }
258 
registerActions()259     private void registerActions() {
260         RemoteAction actionBack = createRemoteAction(
261                 R.string.accessibility_system_action_back_label,
262                 SystemActionsBroadcastReceiver.INTENT_ACTION_BACK);
263 
264         RemoteAction actionHome = createRemoteAction(
265                 R.string.accessibility_system_action_home_label,
266                 SystemActionsBroadcastReceiver.INTENT_ACTION_HOME);
267 
268         RemoteAction actionRecents = createRemoteAction(
269                 R.string.accessibility_system_action_recents_label,
270                 SystemActionsBroadcastReceiver.INTENT_ACTION_RECENTS);
271 
272         RemoteAction actionNotifications = createRemoteAction(
273                 R.string.accessibility_system_action_notifications_label,
274                 SystemActionsBroadcastReceiver.INTENT_ACTION_NOTIFICATIONS);
275 
276         RemoteAction actionQuickSettings = createRemoteAction(
277                 R.string.accessibility_system_action_quick_settings_label,
278                 SystemActionsBroadcastReceiver.INTENT_ACTION_QUICK_SETTINGS);
279 
280         RemoteAction actionPowerDialog = createRemoteAction(
281                 R.string.accessibility_system_action_power_dialog_label,
282                 SystemActionsBroadcastReceiver.INTENT_ACTION_POWER_DIALOG);
283 
284         RemoteAction actionLockScreen = createRemoteAction(
285                 R.string.accessibility_system_action_lock_screen_label,
286                 SystemActionsBroadcastReceiver.INTENT_ACTION_LOCK_SCREEN);
287 
288         RemoteAction actionTakeScreenshot = createRemoteAction(
289                 R.string.accessibility_system_action_screenshot_label,
290                 SystemActionsBroadcastReceiver.INTENT_ACTION_TAKE_SCREENSHOT);
291 
292         RemoteAction actionHeadsetHook = createRemoteAction(
293                 R.string.accessibility_system_action_headset_hook_label,
294                 SystemActionsBroadcastReceiver.INTENT_ACTION_HEADSET_HOOK);
295 
296         RemoteAction actionAccessibilityShortcut = createRemoteAction(
297                 R.string.accessibility_system_action_hardware_a11y_shortcut_label,
298                 SystemActionsBroadcastReceiver.INTENT_ACTION_ACCESSIBILITY_SHORTCUT);
299 
300         RemoteAction actionDpadUp = createRemoteAction(
301                 R.string.accessibility_system_action_dpad_up_label,
302                 SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_UP);
303 
304         RemoteAction actionDpadDown = createRemoteAction(
305                 R.string.accessibility_system_action_dpad_down_label,
306                 SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_DOWN);
307 
308         RemoteAction actionDpadLeft = createRemoteAction(
309                 R.string.accessibility_system_action_dpad_left_label,
310                 SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_LEFT);
311 
312         RemoteAction actionDpadRight = createRemoteAction(
313                 R.string.accessibility_system_action_dpad_right_label,
314                 SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_RIGHT);
315 
316         RemoteAction actionDpadCenter = createRemoteAction(
317                 R.string.accessibility_system_action_dpad_center_label,
318                 SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_CENTER);
319 
320         RemoteAction actionMenu = createRemoteAction(
321                 R.string.accessibility_system_action_menu_label,
322                 SystemActionsBroadcastReceiver.INTENT_ACTION_MENU);
323 
324         RemoteAction actionMediaPlayPause = createRemoteAction(
325                 R.string.accessibility_system_action_media_play_pause_label,
326                 SystemActionsBroadcastReceiver.INTENT_ACTION_MEDIA_PLAY_PAUSE);
327 
328         mA11yManager.registerSystemAction(actionBack, SYSTEM_ACTION_ID_BACK);
329         mA11yManager.registerSystemAction(actionHome, SYSTEM_ACTION_ID_HOME);
330         mA11yManager.registerSystemAction(actionRecents, SYSTEM_ACTION_ID_RECENTS);
331         if (mShadeController.isShadeEnabled()) {
332             // These two actions require the shade to be enabled.
333             mA11yManager.registerSystemAction(actionNotifications, SYSTEM_ACTION_ID_NOTIFICATIONS);
334             mA11yManager.registerSystemAction(actionQuickSettings, SYSTEM_ACTION_ID_QUICK_SETTINGS);
335         }
336         mA11yManager.registerSystemAction(actionPowerDialog, SYSTEM_ACTION_ID_POWER_DIALOG);
337         mA11yManager.registerSystemAction(actionLockScreen, SYSTEM_ACTION_ID_LOCK_SCREEN);
338         mA11yManager.registerSystemAction(actionTakeScreenshot, SYSTEM_ACTION_ID_TAKE_SCREENSHOT);
339         mA11yManager.registerSystemAction(actionHeadsetHook, SYSTEM_ACTION_ID_KEYCODE_HEADSETHOOK);
340         mA11yManager.registerSystemAction(
341                 actionAccessibilityShortcut, SYSTEM_ACTION_ID_ACCESSIBILITY_SHORTCUT);
342         mA11yManager.registerSystemAction(actionDpadUp, SYSTEM_ACTION_ID_DPAD_UP);
343         mA11yManager.registerSystemAction(actionDpadDown, SYSTEM_ACTION_ID_DPAD_DOWN);
344         mA11yManager.registerSystemAction(actionDpadLeft, SYSTEM_ACTION_ID_DPAD_LEFT);
345         mA11yManager.registerSystemAction(actionDpadRight, SYSTEM_ACTION_ID_DPAD_RIGHT);
346         mA11yManager.registerSystemAction(actionDpadCenter, SYSTEM_ACTION_ID_DPAD_CENTER);
347         mA11yManager.registerSystemAction(actionMenu, SYSTEM_ACTION_ID_MENU);
348         mA11yManager.registerSystemAction(actionMediaPlayPause, SYSTEM_ACTION_ID_MEDIA_PLAY_PAUSE);
349         registerOrUnregisterDismissNotificationShadeAction();
350     }
351 
registerOrUnregisterDismissNotificationShadeAction()352     private void registerOrUnregisterDismissNotificationShadeAction() {
353         Assert.isMainThread();
354 
355         if (mPanelExpansionInteractor.get().isPanelExpanded()
356                 && !mKeyguardStateController.isShowing()) {
357             if (!mDismissNotificationShadeActionRegistered) {
358                 mA11yManager.registerSystemAction(
359                         createRemoteAction(
360                                 R.string.accessibility_system_action_dismiss_notification_shade,
361                                 SystemActionsBroadcastReceiver
362                                         .INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE),
363                         SYSTEM_ACTION_ID_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE);
364                 mDismissNotificationShadeActionRegistered = true;
365             }
366         } else {
367             if (mDismissNotificationShadeActionRegistered) {
368                 mA11yManager.unregisterSystemAction(
369                         SYSTEM_ACTION_ID_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE);
370                 mDismissNotificationShadeActionRegistered = false;
371             }
372         }
373     }
374 
375     /**
376      * Register a system action.
377      *
378      * @param actionId the action ID to register.
379      */
register(int actionId)380     public void register(int actionId) {
381         int labelId;
382         String intent;
383         switch (actionId) {
384             case SYSTEM_ACTION_ID_BACK:
385                 labelId = R.string.accessibility_system_action_back_label;
386                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_BACK;
387                 break;
388             case SYSTEM_ACTION_ID_HOME:
389                 labelId = R.string.accessibility_system_action_home_label;
390                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_HOME;
391                 break;
392             case SYSTEM_ACTION_ID_RECENTS:
393                 labelId = R.string.accessibility_system_action_recents_label;
394                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_RECENTS;
395                 break;
396             case SYSTEM_ACTION_ID_NOTIFICATIONS:
397                 labelId = R.string.accessibility_system_action_notifications_label;
398                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_NOTIFICATIONS;
399                 break;
400             case SYSTEM_ACTION_ID_QUICK_SETTINGS:
401                 labelId = R.string.accessibility_system_action_quick_settings_label;
402                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_QUICK_SETTINGS;
403                 break;
404             case SYSTEM_ACTION_ID_POWER_DIALOG:
405                 labelId = R.string.accessibility_system_action_power_dialog_label;
406                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_POWER_DIALOG;
407                 break;
408             case SYSTEM_ACTION_ID_LOCK_SCREEN:
409                 labelId = R.string.accessibility_system_action_lock_screen_label;
410                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_LOCK_SCREEN;
411                 break;
412             case SYSTEM_ACTION_ID_TAKE_SCREENSHOT:
413                 labelId = R.string.accessibility_system_action_screenshot_label;
414                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_TAKE_SCREENSHOT;
415                 break;
416             case SYSTEM_ACTION_ID_KEYCODE_HEADSETHOOK:
417                 labelId = R.string.accessibility_system_action_headset_hook_label;
418                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_HEADSET_HOOK;
419                 break;
420             case SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON:
421                 labelId = R.string.accessibility_system_action_on_screen_a11y_shortcut_label;
422                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_ACCESSIBILITY_BUTTON;
423                 break;
424             case SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON_CHOOSER:
425                 labelId =
426                         R.string.accessibility_system_action_on_screen_a11y_shortcut_chooser_label;
427                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER;
428                 break;
429             case SYSTEM_ACTION_ID_ACCESSIBILITY_SHORTCUT:
430                 labelId = R.string.accessibility_system_action_hardware_a11y_shortcut_label;
431                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_ACCESSIBILITY_SHORTCUT;
432                 break;
433             case SYSTEM_ACTION_ID_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE:
434                 labelId = R.string.accessibility_system_action_dismiss_notification_shade;
435                 intent = SystemActionsBroadcastReceiver
436                         .INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE;
437                 break;
438             case SYSTEM_ACTION_ID_DPAD_UP:
439                 labelId = R.string.accessibility_system_action_dpad_up_label;
440                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_UP;
441                 break;
442             case SYSTEM_ACTION_ID_DPAD_DOWN:
443                 labelId = R.string.accessibility_system_action_dpad_down_label;
444                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_DOWN;
445                 break;
446             case SYSTEM_ACTION_ID_DPAD_LEFT:
447                 labelId = R.string.accessibility_system_action_dpad_left_label;
448                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_LEFT;
449                 break;
450             case SYSTEM_ACTION_ID_DPAD_RIGHT:
451                 labelId = R.string.accessibility_system_action_dpad_right_label;
452                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_RIGHT;
453                 break;
454             case SYSTEM_ACTION_ID_DPAD_CENTER:
455                 labelId = R.string.accessibility_system_action_dpad_center_label;
456                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_CENTER;
457                 break;
458             case SYSTEM_ACTION_ID_MENU:
459                 labelId = R.string.accessibility_system_action_menu_label;
460                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_MENU;
461                 break;
462             case SYSTEM_ACTION_ID_MEDIA_PLAY_PAUSE:
463                 labelId = R.string.accessibility_system_action_media_play_pause_label;
464                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_MEDIA_PLAY_PAUSE;
465                 break;
466             default:
467                 return;
468         }
469         mA11yManager.registerSystemAction(createRemoteAction(labelId, intent), actionId);
470     }
471 
createRemoteAction(int labelId, String intent)472     private RemoteAction createRemoteAction(int labelId, String intent) {
473         // TODO(b/148087487): update the icon used below to a valid one
474         return new RemoteAction(
475                 Icon.createWithResource(mContext, R.drawable.ic_info),
476                 mContext.getString(labelId),
477                 mContext.getString(labelId),
478                 mReceiver.createPendingIntent(mContext, intent));
479     }
480 
481     /**
482      * Unregister a system action.
483      *
484      * @param actionId the action ID to unregister.
485      */
unregister(int actionId)486     public void unregister(int actionId) {
487         mA11yManager.unregisterSystemAction(actionId);
488     }
489 
handleBack()490     private void handleBack() {
491         sendDownAndUpKeyEvents(KeyEvent.KEYCODE_BACK);
492     }
493 
handleHome()494     private void handleHome() {
495         sendDownAndUpKeyEvents(KeyEvent.KEYCODE_HOME);
496     }
497 
sendDownAndUpKeyEvents(int keyCode)498     private void sendDownAndUpKeyEvents(int keyCode) {
499         final long downTime = SystemClock.uptimeMillis();
500         sendKeyEventIdentityCleared(keyCode, KeyEvent.ACTION_DOWN, downTime, downTime);
501         sendKeyEventIdentityCleared(
502                 keyCode, KeyEvent.ACTION_UP, downTime, SystemClock.uptimeMillis());
503     }
504 
sendKeyEventIdentityCleared(int keyCode, int action, long downTime, long time)505     private void sendKeyEventIdentityCleared(int keyCode, int action, long downTime, long time) {
506         KeyEvent event = KeyEvent.obtain(downTime, time, action, keyCode, 0, 0,
507                 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FROM_SYSTEM,
508                 InputDevice.SOURCE_KEYBOARD, null);
509         mContext.getSystemService(InputManager.class)
510                 .injectInputEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
511         event.recycle();
512     }
513 
handleRecents()514     private void handleRecents() {
515         mRecentsOptional.ifPresent(Recents::toggleRecentApps);
516     }
517 
handleNotifications()518     private void handleNotifications() {
519         mShadeController.animateExpandShade();
520     }
521 
handleQuickSettings()522     private void handleQuickSettings() {
523         mShadeController.animateExpandQs();
524     }
525 
handlePowerDialog()526     private void handlePowerDialog() {
527         IWindowManager windowManager = WindowManagerGlobal.getWindowManagerService();
528 
529         try {
530             windowManager.showGlobalActions();
531         } catch (RemoteException e) {
532             Log.e(TAG, "failed to display power dialog.");
533         }
534     }
535 
handleLockScreen()536     private void handleLockScreen() {
537         IWindowManager windowManager = WindowManagerGlobal.getWindowManagerService();
538 
539         mContext.getSystemService(PowerManager.class).goToSleep(SystemClock.uptimeMillis(),
540                 PowerManager.GO_TO_SLEEP_REASON_ACCESSIBILITY, 0);
541         try {
542             windowManager.lockNow(null);
543         } catch (RemoteException e) {
544             Log.e(TAG, "failed to lock screen.");
545         }
546     }
547 
handleTakeScreenshot()548     private void handleTakeScreenshot() {
549         mScreenshotHelper.takeScreenshot(
550                 SCREENSHOT_ACCESSIBILITY_ACTIONS, new Handler(Looper.getMainLooper()), null);
551     }
552 
553     @VisibleForTesting
handleHeadsetHook()554     void handleHeadsetHook() {
555         if (!AccessibilityUtils.interceptHeadsetHookForActiveCall(mContext)) {
556             sendDownAndUpKeyEvents(KeyEvent.KEYCODE_HEADSETHOOK);
557         }
558     }
559 
handleAccessibilityButton()560     private void handleAccessibilityButton() {
561         mA11yManager.notifyAccessibilityButtonClicked(
562                 mDisplayTracker.getDefaultDisplayId());
563     }
564 
handleAccessibilityButtonChooser()565     private void handleAccessibilityButtonChooser() {
566         mA11yManager.notifyAccessibilityButtonLongClicked(
567                 mDisplayTracker.getDefaultDisplayId());
568     }
569 
handleAccessibilityShortcut()570     private void handleAccessibilityShortcut() {
571         mA11yManager.performAccessibilityShortcut();
572     }
573 
handleAccessibilityDismissNotificationShade()574     private void handleAccessibilityDismissNotificationShade() {
575         mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
576     }
577 
handleDpadUp()578     private void handleDpadUp() {
579         sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_UP);
580     }
581 
handleDpadDown()582     private void handleDpadDown() {
583         sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_DOWN);
584     }
585 
handleDpadLeft()586     private void handleDpadLeft() {
587         sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_LEFT);
588     }
589 
handleDpadRight()590     private void handleDpadRight() {
591         sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_RIGHT);
592     }
593 
handleDpadCenter()594     private void handleDpadCenter() {
595         sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_CENTER);
596     }
597 
598     @VisibleForTesting
handleMenu()599     void handleMenu() {
600         sendDownAndUpKeyEvents(KeyEvent.KEYCODE_MENU);
601     }
602 
603     @VisibleForTesting
handleMediaPlayPause()604     void handleMediaPlayPause() {
605         sendDownAndUpKeyEvents(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
606     }
607 
608     private class SystemActionsBroadcastReceiver extends BroadcastReceiver {
609         private static final String INTENT_ACTION_BACK = "SYSTEM_ACTION_BACK";
610         private static final String INTENT_ACTION_HOME = "SYSTEM_ACTION_HOME";
611         private static final String INTENT_ACTION_RECENTS = "SYSTEM_ACTION_RECENTS";
612         private static final String INTENT_ACTION_NOTIFICATIONS = "SYSTEM_ACTION_NOTIFICATIONS";
613         private static final String INTENT_ACTION_QUICK_SETTINGS = "SYSTEM_ACTION_QUICK_SETTINGS";
614         private static final String INTENT_ACTION_POWER_DIALOG = "SYSTEM_ACTION_POWER_DIALOG";
615         private static final String INTENT_ACTION_LOCK_SCREEN = "SYSTEM_ACTION_LOCK_SCREEN";
616         private static final String INTENT_ACTION_TAKE_SCREENSHOT = "SYSTEM_ACTION_TAKE_SCREENSHOT";
617         private static final String INTENT_ACTION_HEADSET_HOOK = "SYSTEM_ACTION_HEADSET_HOOK";
618         private static final String INTENT_ACTION_ACCESSIBILITY_BUTTON =
619                 "SYSTEM_ACTION_ACCESSIBILITY_BUTTON";
620         private static final String INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER =
621                 "SYSTEM_ACTION_ACCESSIBILITY_BUTTON_MENU";
622         private static final String INTENT_ACTION_ACCESSIBILITY_SHORTCUT =
623                 "SYSTEM_ACTION_ACCESSIBILITY_SHORTCUT";
624         private static final String INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE =
625                 "SYSTEM_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE";
626         private static final String INTENT_ACTION_DPAD_UP = "SYSTEM_ACTION_DPAD_UP";
627         private static final String INTENT_ACTION_DPAD_DOWN = "SYSTEM_ACTION_DPAD_DOWN";
628         private static final String INTENT_ACTION_DPAD_LEFT = "SYSTEM_ACTION_DPAD_LEFT";
629         private static final String INTENT_ACTION_DPAD_RIGHT = "SYSTEM_ACTION_DPAD_RIGHT";
630         private static final String INTENT_ACTION_DPAD_CENTER = "SYSTEM_ACTION_DPAD_CENTER";
631         private static final String INTENT_ACTION_MENU = "SYSTEM_ACTION_MENU";
632         private static final String INTENT_ACTION_MEDIA_PLAY_PAUSE =
633                 "SYSTEM_ACTION_MEDIA_PLAY_PAUSE";
634 
createPendingIntent(Context context, String intentAction)635         private PendingIntent createPendingIntent(Context context, String intentAction) {
636             switch (intentAction) {
637                 case INTENT_ACTION_BACK:
638                 case INTENT_ACTION_HOME:
639                 case INTENT_ACTION_RECENTS:
640                 case INTENT_ACTION_NOTIFICATIONS:
641                 case INTENT_ACTION_QUICK_SETTINGS:
642                 case INTENT_ACTION_POWER_DIALOG:
643                 case INTENT_ACTION_LOCK_SCREEN:
644                 case INTENT_ACTION_TAKE_SCREENSHOT:
645                 case INTENT_ACTION_HEADSET_HOOK:
646                 case INTENT_ACTION_ACCESSIBILITY_BUTTON:
647                 case INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER:
648                 case INTENT_ACTION_ACCESSIBILITY_SHORTCUT:
649                 case INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE:
650                 case INTENT_ACTION_DPAD_UP:
651                 case INTENT_ACTION_DPAD_DOWN:
652                 case INTENT_ACTION_DPAD_LEFT:
653                 case INTENT_ACTION_DPAD_RIGHT:
654                 case INTENT_ACTION_DPAD_CENTER:
655                 case INTENT_ACTION_MENU:
656                 case INTENT_ACTION_MEDIA_PLAY_PAUSE: {
657                     Intent intent = new Intent(intentAction);
658                     intent.setPackage(context.getPackageName());
659                     intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
660                     return PendingIntent.getBroadcast(context, 0, intent,
661                             PendingIntent.FLAG_IMMUTABLE);
662                 }
663                 default:
664                     break;
665             }
666             return null;
667         }
668 
createIntentFilter()669         private IntentFilter createIntentFilter() {
670             IntentFilter intentFilter = new IntentFilter();
671             intentFilter.addAction(INTENT_ACTION_BACK);
672             intentFilter.addAction(INTENT_ACTION_HOME);
673             intentFilter.addAction(INTENT_ACTION_RECENTS);
674             intentFilter.addAction(INTENT_ACTION_NOTIFICATIONS);
675             intentFilter.addAction(INTENT_ACTION_QUICK_SETTINGS);
676             intentFilter.addAction(INTENT_ACTION_POWER_DIALOG);
677             intentFilter.addAction(INTENT_ACTION_LOCK_SCREEN);
678             intentFilter.addAction(INTENT_ACTION_TAKE_SCREENSHOT);
679             intentFilter.addAction(INTENT_ACTION_HEADSET_HOOK);
680             intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_BUTTON);
681             intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER);
682             intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_SHORTCUT);
683             intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE);
684             intentFilter.addAction(INTENT_ACTION_DPAD_UP);
685             intentFilter.addAction(INTENT_ACTION_DPAD_DOWN);
686             intentFilter.addAction(INTENT_ACTION_DPAD_LEFT);
687             intentFilter.addAction(INTENT_ACTION_DPAD_RIGHT);
688             intentFilter.addAction(INTENT_ACTION_DPAD_CENTER);
689             intentFilter.addAction(INTENT_ACTION_MENU);
690             intentFilter.addAction(INTENT_ACTION_MEDIA_PLAY_PAUSE);
691             return intentFilter;
692         }
693 
694         @Override
onReceive(Context context, Intent intent)695         public void onReceive(Context context, Intent intent) {
696             String intentAction = intent.getAction();
697             switch (intentAction) {
698                 case INTENT_ACTION_BACK: {
699                     handleBack();
700                     break;
701                 }
702                 case INTENT_ACTION_HOME: {
703                     handleHome();
704                     break;
705                 }
706                 case INTENT_ACTION_RECENTS: {
707                     handleRecents();
708                     break;
709                 }
710                 case INTENT_ACTION_NOTIFICATIONS: {
711                     handleNotifications();
712                     break;
713                 }
714                 case INTENT_ACTION_QUICK_SETTINGS: {
715                     handleQuickSettings();
716                     break;
717                 }
718                 case INTENT_ACTION_POWER_DIALOG: {
719                     handlePowerDialog();
720                     break;
721                 }
722                 case INTENT_ACTION_LOCK_SCREEN: {
723                     handleLockScreen();
724                     break;
725                 }
726                 case INTENT_ACTION_TAKE_SCREENSHOT: {
727                     handleTakeScreenshot();
728                     break;
729                 }
730                 case INTENT_ACTION_HEADSET_HOOK: {
731                     handleHeadsetHook();
732                     break;
733                 }
734                 case INTENT_ACTION_ACCESSIBILITY_BUTTON: {
735                     handleAccessibilityButton();
736                     break;
737                 }
738                 case INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER: {
739                     handleAccessibilityButtonChooser();
740                     break;
741                 }
742                 case INTENT_ACTION_ACCESSIBILITY_SHORTCUT: {
743                     handleAccessibilityShortcut();
744                     break;
745                 }
746                 case INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE: {
747                     handleAccessibilityDismissNotificationShade();
748                     break;
749                 }
750                 case INTENT_ACTION_DPAD_UP: {
751                     handleDpadUp();
752                     break;
753                 }
754                 case INTENT_ACTION_DPAD_DOWN: {
755                     handleDpadDown();
756                     break;
757                 }
758                 case INTENT_ACTION_DPAD_LEFT: {
759                     handleDpadLeft();
760                     break;
761                 }
762                 case INTENT_ACTION_DPAD_RIGHT: {
763                     handleDpadRight();
764                     break;
765                 }
766                 case INTENT_ACTION_DPAD_CENTER: {
767                     handleDpadCenter();
768                     break;
769                 }
770                 case INTENT_ACTION_MENU: {
771                     if (Flags.globalActionMenu()) {
772                         handleMenu();
773                     }
774                     break;
775                 }
776                 case INTENT_ACTION_MEDIA_PLAY_PAUSE: {
777                     if (Flags.globalActionMediaPlayPause()) {
778                         handleMediaPlayPause();
779                     }
780                     break;
781                 }
782                 default:
783                     break;
784             }
785         }
786     }
787 }
788