/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.accessibility; import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS; import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME; import android.accessibilityservice.AccessibilityService; import android.app.PendingIntent; import android.app.RemoteAction; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Configuration; import android.graphics.drawable.Icon; import android.hardware.input.InputManager; import android.os.Handler; import android.os.Looper; import android.os.PowerManager; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.util.Log; import android.view.Display; import android.view.IWindowManager; import android.view.InputDevice; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityManager; import com.android.internal.R; import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity; import com.android.internal.util.ScreenshotHelper; import com.android.systemui.Dependency; import com.android.systemui.SystemUI; import com.android.systemui.recents.Recents; import com.android.systemui.statusbar.phone.StatusBar; import java.util.Locale; import javax.inject.Inject; import javax.inject.Singleton; /** * Class to register system actions with accessibility framework. */ @Singleton public class SystemActions extends SystemUI { private static final String TAG = "SystemActions"; /** * Action ID to go back. */ private static final int SYSTEM_ACTION_ID_BACK = AccessibilityService.GLOBAL_ACTION_BACK; // = 1 /** * Action ID to go home. */ private static final int SYSTEM_ACTION_ID_HOME = AccessibilityService.GLOBAL_ACTION_HOME; // = 2 /** * Action ID to toggle showing the overview of recent apps. Will fail on platforms that don't * show recent apps. */ private static final int SYSTEM_ACTION_ID_RECENTS = AccessibilityService.GLOBAL_ACTION_RECENTS; // = 3 /** * Action ID to open the notifications. */ private static final int SYSTEM_ACTION_ID_NOTIFICATIONS = AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS; // = 4 /** * Action ID to open the quick settings. */ private static final int SYSTEM_ACTION_ID_QUICK_SETTINGS = AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS; // = 5 /** * Action ID to open the power long-press dialog. */ private static final int SYSTEM_ACTION_ID_POWER_DIALOG = AccessibilityService.GLOBAL_ACTION_POWER_DIALOG; // = 6 /** * Action ID to lock the screen */ private static final int SYSTEM_ACTION_ID_LOCK_SCREEN = AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN; // = 8 /** * Action ID to take a screenshot */ private static final int SYSTEM_ACTION_ID_TAKE_SCREENSHOT = AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT; // = 9 /** * Action ID to trigger the accessibility button */ public static final int SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON = AccessibilityService.GLOBAL_ACTION_ACCESSIBILITY_BUTTON; // 11 /** * Action ID to show accessibility button's menu of services */ public static final int SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON_CHOOSER = AccessibilityService.GLOBAL_ACTION_ACCESSIBILITY_BUTTON_CHOOSER; // 12 public static final int SYSTEM_ACTION_ID_ACCESSIBILITY_SHORTCUT = AccessibilityService.GLOBAL_ACTION_ACCESSIBILITY_SHORTCUT; // 13 private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF"; private Recents mRecents; private StatusBar mStatusBar; private SystemActionsBroadcastReceiver mReceiver; private Locale mLocale; private AccessibilityManager mA11yManager; @Inject public SystemActions(Context context) { super(context); mRecents = Dependency.get(Recents.class); mStatusBar = Dependency.get(StatusBar.class); mReceiver = new SystemActionsBroadcastReceiver(); mLocale = mContext.getResources().getConfiguration().getLocales().get(0); mA11yManager = (AccessibilityManager) mContext.getSystemService( Context.ACCESSIBILITY_SERVICE); } @Override public void start() { mContext.registerReceiverForAllUsers( mReceiver, mReceiver.createIntentFilter(), PERMISSION_SELF, null); registerActions(); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); final Locale locale = mContext.getResources().getConfiguration().getLocales().get(0); if (!locale.equals(mLocale)) { mLocale = locale; registerActions(); } } private void registerActions() { RemoteAction actionBack = createRemoteAction( R.string.accessibility_system_action_back_label, SystemActionsBroadcastReceiver.INTENT_ACTION_BACK); RemoteAction actionHome = createRemoteAction( R.string.accessibility_system_action_home_label, SystemActionsBroadcastReceiver.INTENT_ACTION_HOME); RemoteAction actionRecents = createRemoteAction( R.string.accessibility_system_action_recents_label, SystemActionsBroadcastReceiver.INTENT_ACTION_RECENTS); RemoteAction actionNotifications = createRemoteAction( R.string.accessibility_system_action_notifications_label, SystemActionsBroadcastReceiver.INTENT_ACTION_NOTIFICATIONS); RemoteAction actionQuickSettings = createRemoteAction( R.string.accessibility_system_action_quick_settings_label, SystemActionsBroadcastReceiver.INTENT_ACTION_QUICK_SETTINGS); RemoteAction actionPowerDialog = createRemoteAction( R.string.accessibility_system_action_power_dialog_label, SystemActionsBroadcastReceiver.INTENT_ACTION_POWER_DIALOG); RemoteAction actionLockScreen = createRemoteAction( R.string.accessibility_system_action_lock_screen_label, SystemActionsBroadcastReceiver.INTENT_ACTION_LOCK_SCREEN); RemoteAction actionTakeScreenshot = createRemoteAction( R.string.accessibility_system_action_screenshot_label, SystemActionsBroadcastReceiver.INTENT_ACTION_TAKE_SCREENSHOT); RemoteAction actionAccessibilityShortcut = createRemoteAction( R.string.accessibility_system_action_hardware_a11y_shortcut_label, SystemActionsBroadcastReceiver.INTENT_ACTION_ACCESSIBILITY_SHORTCUT); mA11yManager.registerSystemAction(actionBack, SYSTEM_ACTION_ID_BACK); mA11yManager.registerSystemAction(actionHome, SYSTEM_ACTION_ID_HOME); mA11yManager.registerSystemAction(actionRecents, SYSTEM_ACTION_ID_RECENTS); mA11yManager.registerSystemAction(actionNotifications, SYSTEM_ACTION_ID_NOTIFICATIONS); mA11yManager.registerSystemAction(actionQuickSettings, SYSTEM_ACTION_ID_QUICK_SETTINGS); mA11yManager.registerSystemAction(actionPowerDialog, SYSTEM_ACTION_ID_POWER_DIALOG); mA11yManager.registerSystemAction(actionLockScreen, SYSTEM_ACTION_ID_LOCK_SCREEN); mA11yManager.registerSystemAction(actionTakeScreenshot, SYSTEM_ACTION_ID_TAKE_SCREENSHOT); mA11yManager.registerSystemAction( actionAccessibilityShortcut, SYSTEM_ACTION_ID_ACCESSIBILITY_SHORTCUT); } /** * Register a system action. * @param actionId the action ID to register. */ public void register(int actionId) { int labelId; String intent; switch (actionId) { case SYSTEM_ACTION_ID_BACK: labelId = R.string.accessibility_system_action_back_label; intent = SystemActionsBroadcastReceiver.INTENT_ACTION_BACK; break; case SYSTEM_ACTION_ID_HOME: labelId = R.string.accessibility_system_action_home_label; intent = SystemActionsBroadcastReceiver.INTENT_ACTION_HOME; break; case SYSTEM_ACTION_ID_RECENTS: labelId = R.string.accessibility_system_action_recents_label; intent = SystemActionsBroadcastReceiver.INTENT_ACTION_RECENTS; break; case SYSTEM_ACTION_ID_NOTIFICATIONS: labelId = R.string.accessibility_system_action_notifications_label; intent = SystemActionsBroadcastReceiver.INTENT_ACTION_NOTIFICATIONS; break; case SYSTEM_ACTION_ID_QUICK_SETTINGS: labelId = R.string.accessibility_system_action_quick_settings_label; intent = SystemActionsBroadcastReceiver.INTENT_ACTION_QUICK_SETTINGS; break; case SYSTEM_ACTION_ID_POWER_DIALOG: labelId = R.string.accessibility_system_action_power_dialog_label; intent = SystemActionsBroadcastReceiver.INTENT_ACTION_POWER_DIALOG; break; case SYSTEM_ACTION_ID_LOCK_SCREEN: labelId = R.string.accessibility_system_action_lock_screen_label; intent = SystemActionsBroadcastReceiver.INTENT_ACTION_LOCK_SCREEN; break; case SYSTEM_ACTION_ID_TAKE_SCREENSHOT: labelId = R.string.accessibility_system_action_screenshot_label; intent = SystemActionsBroadcastReceiver.INTENT_ACTION_TAKE_SCREENSHOT; break; case SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON: labelId = R.string.accessibility_system_action_on_screen_a11y_shortcut_label; intent = SystemActionsBroadcastReceiver.INTENT_ACTION_ACCESSIBILITY_BUTTON; break; case SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON_CHOOSER: labelId = R.string.accessibility_system_action_on_screen_a11y_shortcut_chooser_label; intent = SystemActionsBroadcastReceiver.INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER; break; case SYSTEM_ACTION_ID_ACCESSIBILITY_SHORTCUT: labelId = R.string.accessibility_system_action_hardware_a11y_shortcut_label; intent = SystemActionsBroadcastReceiver.INTENT_ACTION_ACCESSIBILITY_SHORTCUT; break; default: return; } mA11yManager.registerSystemAction(createRemoteAction(labelId, intent), actionId); } private RemoteAction createRemoteAction(int labelId, String intent) { // TODO(b/148087487): update the icon used below to a valid one return new RemoteAction( Icon.createWithResource(mContext, R.drawable.ic_info), mContext.getString(labelId), mContext.getString(labelId), mReceiver.createPendingIntent(mContext, intent)); } /** * Unregister a system action. * @param actionId the action ID to unregister. */ public void unregister(int actionId) { mA11yManager.unregisterSystemAction(actionId); } private void handleBack() { sendDownAndUpKeyEvents(KeyEvent.KEYCODE_BACK); } private void handleHome() { sendDownAndUpKeyEvents(KeyEvent.KEYCODE_HOME); } private void sendDownAndUpKeyEvents(int keyCode) { final long downTime = SystemClock.uptimeMillis(); sendKeyEventIdentityCleared(keyCode, KeyEvent.ACTION_DOWN, downTime, downTime); sendKeyEventIdentityCleared( keyCode, KeyEvent.ACTION_UP, downTime, SystemClock.uptimeMillis()); } private void sendKeyEventIdentityCleared(int keyCode, int action, long downTime, long time) { KeyEvent event = KeyEvent.obtain(downTime, time, action, keyCode, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FROM_SYSTEM, InputDevice.SOURCE_KEYBOARD, null); InputManager.getInstance() .injectInputEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); event.recycle(); } private void handleRecents() { mRecents.toggleRecentApps(); } private void handleNotifications() { mStatusBar.animateExpandNotificationsPanel(); } private void handleQuickSettings() { mStatusBar.animateExpandSettingsPanel(null); } private void handlePowerDialog() { IWindowManager windowManager = WindowManagerGlobal.getWindowManagerService(); try { windowManager.showGlobalActions(); } catch (RemoteException e) { Log.e(TAG, "failed to display power dialog."); } } private void handleLockScreen() { IWindowManager windowManager = WindowManagerGlobal.getWindowManagerService(); mContext.getSystemService(PowerManager.class).goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_ACCESSIBILITY, 0); try { windowManager.lockNow(null); } catch (RemoteException e) { Log.e(TAG, "failed to lock screen."); } } private void handleTakeScreenshot() { ScreenshotHelper screenshotHelper = new ScreenshotHelper(mContext); screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN, true, true, SCREENSHOT_GLOBAL_ACTIONS, new Handler(Looper.getMainLooper()), null); } private void handleAccessibilityButton() { AccessibilityManager.getInstance(mContext).notifyAccessibilityButtonClicked( Display.DEFAULT_DISPLAY); } private void handleAccessibilityButtonChooser() { final Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); final String chooserClassName = AccessibilityButtonChooserActivity.class.getName(); intent.setClassName(CHOOSER_PACKAGE_NAME, chooserClassName); mContext.startActivityAsUser(intent, UserHandle.CURRENT); } private void handleAccessibilityShortcut() { mA11yManager.performAccessibilityShortcut(); } private class SystemActionsBroadcastReceiver extends BroadcastReceiver { private static final String INTENT_ACTION_BACK = "SYSTEM_ACTION_BACK"; private static final String INTENT_ACTION_HOME = "SYSTEM_ACTION_HOME"; private static final String INTENT_ACTION_RECENTS = "SYSTEM_ACTION_RECENTS"; private static final String INTENT_ACTION_NOTIFICATIONS = "SYSTEM_ACTION_NOTIFICATIONS"; private static final String INTENT_ACTION_QUICK_SETTINGS = "SYSTEM_ACTION_QUICK_SETTINGS"; private static final String INTENT_ACTION_POWER_DIALOG = "SYSTEM_ACTION_POWER_DIALOG"; private static final String INTENT_ACTION_LOCK_SCREEN = "SYSTEM_ACTION_LOCK_SCREEN"; private static final String INTENT_ACTION_TAKE_SCREENSHOT = "SYSTEM_ACTION_TAKE_SCREENSHOT"; private static final String INTENT_ACTION_ACCESSIBILITY_BUTTON = "SYSTEM_ACTION_ACCESSIBILITY_BUTTON"; private static final String INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER = "SYSTEM_ACTION_ACCESSIBILITY_BUTTON_MENU"; private static final String INTENT_ACTION_ACCESSIBILITY_SHORTCUT = "SYSTEM_ACTION_ACCESSIBILITY_SHORTCUT"; private PendingIntent createPendingIntent(Context context, String intentAction) { switch (intentAction) { case INTENT_ACTION_BACK: case INTENT_ACTION_HOME: case INTENT_ACTION_RECENTS: case INTENT_ACTION_NOTIFICATIONS: case INTENT_ACTION_QUICK_SETTINGS: case INTENT_ACTION_POWER_DIALOG: case INTENT_ACTION_LOCK_SCREEN: case INTENT_ACTION_TAKE_SCREENSHOT: case INTENT_ACTION_ACCESSIBILITY_BUTTON: case INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER: case INTENT_ACTION_ACCESSIBILITY_SHORTCUT: { Intent intent = new Intent(intentAction); intent.setPackage(context.getPackageName()); return PendingIntent.getBroadcast(context, 0, intent, 0); } default: break; } return null; } private IntentFilter createIntentFilter() { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(INTENT_ACTION_BACK); intentFilter.addAction(INTENT_ACTION_HOME); intentFilter.addAction(INTENT_ACTION_RECENTS); intentFilter.addAction(INTENT_ACTION_NOTIFICATIONS); intentFilter.addAction(INTENT_ACTION_QUICK_SETTINGS); intentFilter.addAction(INTENT_ACTION_POWER_DIALOG); intentFilter.addAction(INTENT_ACTION_LOCK_SCREEN); intentFilter.addAction(INTENT_ACTION_TAKE_SCREENSHOT); intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_BUTTON); intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER); intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_SHORTCUT); return intentFilter; } @Override public void onReceive(Context context, Intent intent) { String intentAction = intent.getAction(); switch (intentAction) { case INTENT_ACTION_BACK: { handleBack(); break; } case INTENT_ACTION_HOME: { handleHome(); break; } case INTENT_ACTION_RECENTS: { handleRecents(); break; } case INTENT_ACTION_NOTIFICATIONS: { handleNotifications(); break; } case INTENT_ACTION_QUICK_SETTINGS: { handleQuickSettings(); break; } case INTENT_ACTION_POWER_DIALOG: { handlePowerDialog(); break; } case INTENT_ACTION_LOCK_SCREEN: { handleLockScreen(); break; } case INTENT_ACTION_TAKE_SCREENSHOT: { handleTakeScreenshot(); break; } case INTENT_ACTION_ACCESSIBILITY_BUTTON: { handleAccessibilityButton(); break; } case INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER: { handleAccessibilityButtonChooser(); break; } case INTENT_ACTION_ACCESSIBILITY_SHORTCUT: { handleAccessibilityShortcut(); break; } default: break; } } } }