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_GLOBAL_ACTIONS;
20 
21 import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
22 
23 import android.accessibilityservice.AccessibilityService;
24 import android.app.PendingIntent;
25 import android.app.RemoteAction;
26 import android.content.BroadcastReceiver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.content.res.Configuration;
31 import android.graphics.drawable.Icon;
32 import android.hardware.input.InputManager;
33 import android.os.Handler;
34 import android.os.Looper;
35 import android.os.PowerManager;
36 import android.os.RemoteException;
37 import android.os.SystemClock;
38 import android.os.UserHandle;
39 import android.util.Log;
40 import android.view.Display;
41 import android.view.IWindowManager;
42 import android.view.InputDevice;
43 import android.view.KeyCharacterMap;
44 import android.view.KeyEvent;
45 import android.view.WindowManager;
46 import android.view.WindowManagerGlobal;
47 import android.view.accessibility.AccessibilityManager;
48 
49 import com.android.internal.R;
50 import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity;
51 import com.android.internal.util.ScreenshotHelper;
52 import com.android.systemui.Dependency;
53 import com.android.systemui.SystemUI;
54 import com.android.systemui.recents.Recents;
55 import com.android.systemui.statusbar.phone.StatusBar;
56 
57 import java.util.Locale;
58 
59 import javax.inject.Inject;
60 import javax.inject.Singleton;
61 
62 /**
63  * Class to register system actions with accessibility framework.
64  */
65 @Singleton
66 public class SystemActions extends SystemUI {
67     private static final String TAG = "SystemActions";
68 
69     /**
70      * Action ID to go back.
71      */
72     private static final int SYSTEM_ACTION_ID_BACK = AccessibilityService.GLOBAL_ACTION_BACK; // = 1
73 
74     /**
75      * Action ID to go home.
76      */
77     private static final int SYSTEM_ACTION_ID_HOME = AccessibilityService.GLOBAL_ACTION_HOME; // = 2
78 
79     /**
80      * Action ID to toggle showing the overview of recent apps. Will fail on platforms that don't
81      * show recent apps.
82      */
83     private static final int SYSTEM_ACTION_ID_RECENTS =
84             AccessibilityService.GLOBAL_ACTION_RECENTS; // = 3
85 
86     /**
87      * Action ID to open the notifications.
88      */
89     private static final int SYSTEM_ACTION_ID_NOTIFICATIONS =
90             AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS; // = 4
91 
92     /**
93      * Action ID to open the quick settings.
94      */
95     private static final int SYSTEM_ACTION_ID_QUICK_SETTINGS =
96             AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS; // = 5
97 
98     /**
99      * Action ID to open the power long-press dialog.
100      */
101     private static final int SYSTEM_ACTION_ID_POWER_DIALOG =
102             AccessibilityService.GLOBAL_ACTION_POWER_DIALOG; // = 6
103 
104     /**
105      * Action ID to lock the screen
106      */
107     private static final int SYSTEM_ACTION_ID_LOCK_SCREEN =
108             AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN; // = 8
109 
110     /**
111      * Action ID to take a screenshot
112      */
113     private static final int SYSTEM_ACTION_ID_TAKE_SCREENSHOT =
114             AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT; // = 9
115 
116     /**
117      * Action ID to trigger the accessibility button
118      */
119     public static final int SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON =
120             AccessibilityService.GLOBAL_ACTION_ACCESSIBILITY_BUTTON; // 11
121 
122     /**
123      * Action ID to show accessibility button's menu of services
124      */
125     public static final int SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON_CHOOSER =
126             AccessibilityService.GLOBAL_ACTION_ACCESSIBILITY_BUTTON_CHOOSER; // 12
127 
128     public static final int SYSTEM_ACTION_ID_ACCESSIBILITY_SHORTCUT =
129             AccessibilityService.GLOBAL_ACTION_ACCESSIBILITY_SHORTCUT; // 13
130 
131     private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
132 
133     private Recents mRecents;
134     private StatusBar mStatusBar;
135     private SystemActionsBroadcastReceiver mReceiver;
136     private Locale mLocale;
137     private AccessibilityManager mA11yManager;
138 
139     @Inject
SystemActions(Context context)140     public SystemActions(Context context) {
141         super(context);
142         mRecents = Dependency.get(Recents.class);
143         mStatusBar = Dependency.get(StatusBar.class);
144         mReceiver = new SystemActionsBroadcastReceiver();
145         mLocale = mContext.getResources().getConfiguration().getLocales().get(0);
146         mA11yManager = (AccessibilityManager) mContext.getSystemService(
147                 Context.ACCESSIBILITY_SERVICE);
148     }
149 
150     @Override
start()151     public void start() {
152         mContext.registerReceiverForAllUsers(
153                 mReceiver,
154                 mReceiver.createIntentFilter(),
155                 PERMISSION_SELF,
156                 null);
157         registerActions();
158     }
159 
160     @Override
onConfigurationChanged(Configuration newConfig)161     public void onConfigurationChanged(Configuration newConfig) {
162         super.onConfigurationChanged(newConfig);
163         final Locale locale = mContext.getResources().getConfiguration().getLocales().get(0);
164         if (!locale.equals(mLocale)) {
165             mLocale = locale;
166             registerActions();
167         }
168     }
169 
registerActions()170     private void registerActions() {
171         RemoteAction actionBack = createRemoteAction(
172                 R.string.accessibility_system_action_back_label,
173                 SystemActionsBroadcastReceiver.INTENT_ACTION_BACK);
174 
175         RemoteAction actionHome = createRemoteAction(
176                 R.string.accessibility_system_action_home_label,
177                 SystemActionsBroadcastReceiver.INTENT_ACTION_HOME);
178 
179         RemoteAction actionRecents = createRemoteAction(
180                 R.string.accessibility_system_action_recents_label,
181                 SystemActionsBroadcastReceiver.INTENT_ACTION_RECENTS);
182 
183         RemoteAction actionNotifications = createRemoteAction(
184                 R.string.accessibility_system_action_notifications_label,
185                 SystemActionsBroadcastReceiver.INTENT_ACTION_NOTIFICATIONS);
186 
187         RemoteAction actionQuickSettings = createRemoteAction(
188                 R.string.accessibility_system_action_quick_settings_label,
189                 SystemActionsBroadcastReceiver.INTENT_ACTION_QUICK_SETTINGS);
190 
191         RemoteAction actionPowerDialog = createRemoteAction(
192                 R.string.accessibility_system_action_power_dialog_label,
193                 SystemActionsBroadcastReceiver.INTENT_ACTION_POWER_DIALOG);
194 
195         RemoteAction actionLockScreen = createRemoteAction(
196                 R.string.accessibility_system_action_lock_screen_label,
197                 SystemActionsBroadcastReceiver.INTENT_ACTION_LOCK_SCREEN);
198 
199         RemoteAction actionTakeScreenshot = createRemoteAction(
200                 R.string.accessibility_system_action_screenshot_label,
201                 SystemActionsBroadcastReceiver.INTENT_ACTION_TAKE_SCREENSHOT);
202 
203         RemoteAction actionAccessibilityShortcut = createRemoteAction(
204                 R.string.accessibility_system_action_hardware_a11y_shortcut_label,
205                 SystemActionsBroadcastReceiver.INTENT_ACTION_ACCESSIBILITY_SHORTCUT);
206 
207         mA11yManager.registerSystemAction(actionBack, SYSTEM_ACTION_ID_BACK);
208         mA11yManager.registerSystemAction(actionHome, SYSTEM_ACTION_ID_HOME);
209         mA11yManager.registerSystemAction(actionRecents, SYSTEM_ACTION_ID_RECENTS);
210         mA11yManager.registerSystemAction(actionNotifications, SYSTEM_ACTION_ID_NOTIFICATIONS);
211         mA11yManager.registerSystemAction(actionQuickSettings, SYSTEM_ACTION_ID_QUICK_SETTINGS);
212         mA11yManager.registerSystemAction(actionPowerDialog, SYSTEM_ACTION_ID_POWER_DIALOG);
213         mA11yManager.registerSystemAction(actionLockScreen, SYSTEM_ACTION_ID_LOCK_SCREEN);
214         mA11yManager.registerSystemAction(actionTakeScreenshot, SYSTEM_ACTION_ID_TAKE_SCREENSHOT);
215         mA11yManager.registerSystemAction(
216                 actionAccessibilityShortcut, SYSTEM_ACTION_ID_ACCESSIBILITY_SHORTCUT);
217     }
218 
219     /**
220      * Register a system action.
221      * @param actionId the action ID to register.
222      */
register(int actionId)223     public void register(int actionId) {
224         int labelId;
225         String intent;
226         switch (actionId) {
227             case SYSTEM_ACTION_ID_BACK:
228                 labelId = R.string.accessibility_system_action_back_label;
229                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_BACK;
230                 break;
231             case SYSTEM_ACTION_ID_HOME:
232                 labelId = R.string.accessibility_system_action_home_label;
233                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_HOME;
234                 break;
235             case SYSTEM_ACTION_ID_RECENTS:
236                 labelId = R.string.accessibility_system_action_recents_label;
237                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_RECENTS;
238                 break;
239             case SYSTEM_ACTION_ID_NOTIFICATIONS:
240                 labelId = R.string.accessibility_system_action_notifications_label;
241                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_NOTIFICATIONS;
242                 break;
243             case SYSTEM_ACTION_ID_QUICK_SETTINGS:
244                 labelId = R.string.accessibility_system_action_quick_settings_label;
245                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_QUICK_SETTINGS;
246                 break;
247             case SYSTEM_ACTION_ID_POWER_DIALOG:
248                 labelId = R.string.accessibility_system_action_power_dialog_label;
249                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_POWER_DIALOG;
250                 break;
251             case SYSTEM_ACTION_ID_LOCK_SCREEN:
252                 labelId = R.string.accessibility_system_action_lock_screen_label;
253                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_LOCK_SCREEN;
254                 break;
255             case SYSTEM_ACTION_ID_TAKE_SCREENSHOT:
256                 labelId = R.string.accessibility_system_action_screenshot_label;
257                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_TAKE_SCREENSHOT;
258                 break;
259             case SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON:
260                 labelId = R.string.accessibility_system_action_on_screen_a11y_shortcut_label;
261                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_ACCESSIBILITY_BUTTON;
262                 break;
263             case SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON_CHOOSER:
264                 labelId =
265                         R.string.accessibility_system_action_on_screen_a11y_shortcut_chooser_label;
266                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER;
267                 break;
268             case  SYSTEM_ACTION_ID_ACCESSIBILITY_SHORTCUT:
269                 labelId = R.string.accessibility_system_action_hardware_a11y_shortcut_label;
270                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_ACCESSIBILITY_SHORTCUT;
271                 break;
272             default:
273                 return;
274         }
275         mA11yManager.registerSystemAction(createRemoteAction(labelId, intent), actionId);
276     }
277 
createRemoteAction(int labelId, String intent)278     private RemoteAction createRemoteAction(int labelId, String intent) {
279         // TODO(b/148087487): update the icon used below to a valid one
280         return new RemoteAction(
281                 Icon.createWithResource(mContext, R.drawable.ic_info),
282                 mContext.getString(labelId),
283                 mContext.getString(labelId),
284                 mReceiver.createPendingIntent(mContext, intent));
285     }
286 
287     /**
288      * Unregister a system action.
289      * @param actionId the action ID to unregister.
290      */
unregister(int actionId)291     public void unregister(int actionId) {
292         mA11yManager.unregisterSystemAction(actionId);
293     }
294 
handleBack()295     private void handleBack() {
296         sendDownAndUpKeyEvents(KeyEvent.KEYCODE_BACK);
297     }
298 
handleHome()299     private void handleHome() {
300         sendDownAndUpKeyEvents(KeyEvent.KEYCODE_HOME);
301     }
302 
sendDownAndUpKeyEvents(int keyCode)303     private void sendDownAndUpKeyEvents(int keyCode) {
304         final long downTime = SystemClock.uptimeMillis();
305         sendKeyEventIdentityCleared(keyCode, KeyEvent.ACTION_DOWN, downTime, downTime);
306         sendKeyEventIdentityCleared(
307                 keyCode, KeyEvent.ACTION_UP, downTime, SystemClock.uptimeMillis());
308     }
309 
sendKeyEventIdentityCleared(int keyCode, int action, long downTime, long time)310     private void sendKeyEventIdentityCleared(int keyCode, int action, long downTime, long time) {
311         KeyEvent event = KeyEvent.obtain(downTime, time, action, keyCode, 0, 0,
312                 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FROM_SYSTEM,
313                 InputDevice.SOURCE_KEYBOARD, null);
314         InputManager.getInstance()
315                 .injectInputEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
316         event.recycle();
317     }
318 
handleRecents()319     private void handleRecents() {
320         mRecents.toggleRecentApps();
321     }
322 
handleNotifications()323     private void handleNotifications() {
324         mStatusBar.animateExpandNotificationsPanel();
325     }
326 
handleQuickSettings()327     private void handleQuickSettings() {
328         mStatusBar.animateExpandSettingsPanel(null);
329     }
330 
handlePowerDialog()331     private void handlePowerDialog() {
332         IWindowManager windowManager = WindowManagerGlobal.getWindowManagerService();
333 
334         try {
335             windowManager.showGlobalActions();
336         } catch (RemoteException e) {
337             Log.e(TAG, "failed to display power dialog.");
338         }
339     }
340 
handleLockScreen()341     private void handleLockScreen() {
342         IWindowManager windowManager = WindowManagerGlobal.getWindowManagerService();
343 
344         mContext.getSystemService(PowerManager.class).goToSleep(SystemClock.uptimeMillis(),
345                 PowerManager.GO_TO_SLEEP_REASON_ACCESSIBILITY, 0);
346         try {
347             windowManager.lockNow(null);
348         } catch (RemoteException e) {
349             Log.e(TAG, "failed to lock screen.");
350         }
351     }
352 
handleTakeScreenshot()353     private void handleTakeScreenshot() {
354         ScreenshotHelper screenshotHelper = new ScreenshotHelper(mContext);
355         screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN, true, true,
356                 SCREENSHOT_GLOBAL_ACTIONS, new Handler(Looper.getMainLooper()), null);
357     }
358 
handleAccessibilityButton()359     private void handleAccessibilityButton() {
360         AccessibilityManager.getInstance(mContext).notifyAccessibilityButtonClicked(
361                 Display.DEFAULT_DISPLAY);
362     }
363 
handleAccessibilityButtonChooser()364     private void handleAccessibilityButtonChooser() {
365         final Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
366         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
367         final String chooserClassName = AccessibilityButtonChooserActivity.class.getName();
368         intent.setClassName(CHOOSER_PACKAGE_NAME, chooserClassName);
369         mContext.startActivityAsUser(intent, UserHandle.CURRENT);
370     }
371 
handleAccessibilityShortcut()372     private void handleAccessibilityShortcut() {
373         mA11yManager.performAccessibilityShortcut();
374     }
375 
376     private class SystemActionsBroadcastReceiver extends BroadcastReceiver {
377         private static final String INTENT_ACTION_BACK = "SYSTEM_ACTION_BACK";
378         private static final String INTENT_ACTION_HOME = "SYSTEM_ACTION_HOME";
379         private static final String INTENT_ACTION_RECENTS = "SYSTEM_ACTION_RECENTS";
380         private static final String INTENT_ACTION_NOTIFICATIONS = "SYSTEM_ACTION_NOTIFICATIONS";
381         private static final String INTENT_ACTION_QUICK_SETTINGS = "SYSTEM_ACTION_QUICK_SETTINGS";
382         private static final String INTENT_ACTION_POWER_DIALOG = "SYSTEM_ACTION_POWER_DIALOG";
383         private static final String INTENT_ACTION_LOCK_SCREEN = "SYSTEM_ACTION_LOCK_SCREEN";
384         private static final String INTENT_ACTION_TAKE_SCREENSHOT = "SYSTEM_ACTION_TAKE_SCREENSHOT";
385         private static final String INTENT_ACTION_ACCESSIBILITY_BUTTON =
386                 "SYSTEM_ACTION_ACCESSIBILITY_BUTTON";
387         private static final String INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER =
388                 "SYSTEM_ACTION_ACCESSIBILITY_BUTTON_MENU";
389         private static final String INTENT_ACTION_ACCESSIBILITY_SHORTCUT =
390                 "SYSTEM_ACTION_ACCESSIBILITY_SHORTCUT";
391 
createPendingIntent(Context context, String intentAction)392         private PendingIntent createPendingIntent(Context context, String intentAction) {
393             switch (intentAction) {
394                 case INTENT_ACTION_BACK:
395                 case INTENT_ACTION_HOME:
396                 case INTENT_ACTION_RECENTS:
397                 case INTENT_ACTION_NOTIFICATIONS:
398                 case INTENT_ACTION_QUICK_SETTINGS:
399                 case INTENT_ACTION_POWER_DIALOG:
400                 case INTENT_ACTION_LOCK_SCREEN:
401                 case INTENT_ACTION_TAKE_SCREENSHOT:
402                 case INTENT_ACTION_ACCESSIBILITY_BUTTON:
403                 case INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER:
404                 case INTENT_ACTION_ACCESSIBILITY_SHORTCUT: {
405                     Intent intent = new Intent(intentAction);
406                     intent.setPackage(context.getPackageName());
407                     return PendingIntent.getBroadcast(context, 0, intent, 0);
408                 }
409                 default:
410                     break;
411             }
412             return null;
413         }
414 
createIntentFilter()415         private IntentFilter createIntentFilter() {
416             IntentFilter intentFilter = new IntentFilter();
417             intentFilter.addAction(INTENT_ACTION_BACK);
418             intentFilter.addAction(INTENT_ACTION_HOME);
419             intentFilter.addAction(INTENT_ACTION_RECENTS);
420             intentFilter.addAction(INTENT_ACTION_NOTIFICATIONS);
421             intentFilter.addAction(INTENT_ACTION_QUICK_SETTINGS);
422             intentFilter.addAction(INTENT_ACTION_POWER_DIALOG);
423             intentFilter.addAction(INTENT_ACTION_LOCK_SCREEN);
424             intentFilter.addAction(INTENT_ACTION_TAKE_SCREENSHOT);
425             intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_BUTTON);
426             intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER);
427             intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_SHORTCUT);
428             return intentFilter;
429         }
430 
431         @Override
onReceive(Context context, Intent intent)432         public void onReceive(Context context, Intent intent) {
433             String intentAction = intent.getAction();
434             switch (intentAction) {
435                 case INTENT_ACTION_BACK: {
436                     handleBack();
437                     break;
438                 }
439                 case INTENT_ACTION_HOME: {
440                     handleHome();
441                     break;
442                 }
443                 case INTENT_ACTION_RECENTS: {
444                     handleRecents();
445                     break;
446                 }
447                 case INTENT_ACTION_NOTIFICATIONS: {
448                     handleNotifications();
449                     break;
450                 }
451                 case INTENT_ACTION_QUICK_SETTINGS: {
452                     handleQuickSettings();
453                     break;
454                 }
455                 case INTENT_ACTION_POWER_DIALOG: {
456                     handlePowerDialog();
457                     break;
458                 }
459                 case INTENT_ACTION_LOCK_SCREEN: {
460                     handleLockScreen();
461                     break;
462                 }
463                 case INTENT_ACTION_TAKE_SCREENSHOT: {
464                     handleTakeScreenshot();
465                     break;
466                 }
467                 case INTENT_ACTION_ACCESSIBILITY_BUTTON: {
468                     handleAccessibilityButton();
469                     break;
470                 }
471                 case INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER: {
472                     handleAccessibilityButtonChooser();
473                     break;
474                 }
475                 case INTENT_ACTION_ACCESSIBILITY_SHORTCUT: {
476                     handleAccessibilityShortcut();
477                     break;
478                 }
479                 default:
480                     break;
481             }
482         }
483     }
484 }
485