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