From e0ab6b1aa19a952c1df4b95d167284bc6a542e1d Mon Sep 17 00:00:00 2001 From: Hongming Jin Date: Thu, 16 Jan 2020 14:52:19 -0800 Subject: [PATCH] Add a boot time service in sysUI to register system actions with accessibility registration API. Bug:136286274 Test: manual test with talkback Change-Id: I0fcccc1f2e6377dec97efdf7b226e6f2d18f7eb9 --- core/java/android/view/IWindowManager.aidl | 5 + core/res/res/values/strings.xml | 3 +- core/res/res/values/symbols.xml | 1 + packages/SystemUI/res/values/config.xml | 1 + .../systemui/accessibility/SystemActions.java | 393 ++++++++++++++++++ .../systemui/dagger/SystemUIBinder.java | 7 + .../server/wm/WindowManagerService.java | 9 +- 7 files changed, 417 insertions(+), 2 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index d9c502e14e684..14390f1c209b0 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -748,4 +748,9 @@ interface IWindowManager void getWindowInsets(in WindowManager.LayoutParams attrs, int displayId, out Rect outContentInsets, out Rect outStableInsets, out DisplayCutout.ParcelableWrapper displayCutout); + + /** + * Called to show global actions. + */ + void showGlobalActions(); } diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index be2b678565d36..179d8c3b8095d 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -5318,7 +5318,8 @@ Lock Screen Screenshot - + + Accessibility Menu Caption bar of %1$s. diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index ea8ca9aa1c493..eadb4352858bb 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3803,6 +3803,7 @@ + diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index e52010600ab31..edcd8012c82cf 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -296,6 +296,7 @@ com.android.systemui.statusbar.notification.InstantAppNotifier com.android.systemui.theme.ThemeOverlayController com.android.systemui.accessibility.WindowMagnification + com.android.systemui.accessibility.SystemActions diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java new file mode 100644 index 0000000000000..7262f8caac89f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java @@ -0,0 +1,393 @@ +/* + * 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 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.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.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.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 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"; + // TODO(b/147916452): add implementation on launcher side to register this action. + + /** + * 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 toggle docking the current app's window + */ + private static final int SYSTEM_ACTION_ID_TOGGLE_SPLIT_SCREEN = + AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN; // = 7 + + /** + * 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 show accessibility menu + */ + private static final int SYSTEM_ACTION_ID_ACCESSIBILITY_MENU = 10; + + private Recents mRecents; + private StatusBar mStatusBar; + private SystemActionsBroadcastReceiver mReceiver; + + @Inject + public SystemActions(Context context) { + super(context); + mRecents = Dependency.get(Recents.class); + mStatusBar = Dependency.get(StatusBar.class); + mReceiver = new SystemActionsBroadcastReceiver(); + } + + @Override + public void start() { + mContext.registerReceiverForAllUsers(mReceiver, mReceiver.createIntentFilter(), null, null); + + // TODO(b/148087487): update the icon used below to a valid one + RemoteAction actionBack = new RemoteAction( + Icon.createWithResource(mContext, R.drawable.ic_info), + mContext.getString(R.string.accessibility_system_action_back_label), + mContext.getString(R.string.accessibility_system_action_back_label), + mReceiver.createPendingIntent( + mContext, SystemActionsBroadcastReceiver.INTENT_ACTION_BACK)); + RemoteAction actionHome = new RemoteAction( + Icon.createWithResource(mContext, R.drawable.ic_info), + mContext.getString(R.string.accessibility_system_action_home_label), + mContext.getString(R.string.accessibility_system_action_home_label), + mReceiver.createPendingIntent( + mContext, SystemActionsBroadcastReceiver.INTENT_ACTION_HOME)); + + RemoteAction actionRecents = new RemoteAction( + Icon.createWithResource(mContext, R.drawable.ic_info), + mContext.getString(R.string.accessibility_system_action_recents_label), + mContext.getString(R.string.accessibility_system_action_recents_label), + mReceiver.createPendingIntent( + mContext, SystemActionsBroadcastReceiver.INTENT_ACTION_RECENTS)); + + RemoteAction actionNotifications = new RemoteAction( + Icon.createWithResource(mContext, R.drawable.ic_info), + mContext.getString(R.string.accessibility_system_action_notifications_label), + mContext.getString(R.string.accessibility_system_action_notifications_label), + mReceiver.createPendingIntent( + mContext, SystemActionsBroadcastReceiver.INTENT_ACTION_NOTIFICATIONS)); + + RemoteAction actionQuickSettings = new RemoteAction( + Icon.createWithResource(mContext, R.drawable.ic_info), + mContext.getString(R.string.accessibility_system_action_quick_settings_label), + mContext.getString(R.string.accessibility_system_action_quick_settings_label), + mReceiver.createPendingIntent( + mContext, SystemActionsBroadcastReceiver.INTENT_ACTION_QUICK_SETTINGS)); + + RemoteAction actionPowerDialog = new RemoteAction( + Icon.createWithResource(mContext, R.drawable.ic_info), + mContext.getString(R.string.accessibility_system_action_power_dialog_label), + mContext.getString(R.string.accessibility_system_action_power_dialog_label), + mReceiver.createPendingIntent( + mContext, SystemActionsBroadcastReceiver.INTENT_ACTION_POWER_DIALOG)); + + RemoteAction actionToggleSplitScreen = new RemoteAction( + Icon.createWithResource(mContext, R.drawable.ic_info), + mContext.getString(R.string.accessibility_system_action_toggle_split_screen_label), + mContext.getString(R.string.accessibility_system_action_toggle_split_screen_label), + mReceiver.createPendingIntent( + mContext, + SystemActionsBroadcastReceiver.INTENT_ACTION_TOGGLE_SPLIT_SCREEN)); + + RemoteAction actionLockScreen = new RemoteAction( + Icon.createWithResource(mContext, R.drawable.ic_info), + mContext.getString(R.string.accessibility_system_action_lock_screen_label), + mContext.getString(R.string.accessibility_system_action_lock_screen_label), + mReceiver.createPendingIntent( + mContext, SystemActionsBroadcastReceiver.INTENT_ACTION_LOCK_SCREEN)); + + RemoteAction actionTakeScreenshot = new RemoteAction( + Icon.createWithResource(mContext, R.drawable.ic_info), + mContext.getString(R.string.accessibility_system_action_screenshot_label), + mContext.getString(R.string.accessibility_system_action_screenshot_label), + mReceiver.createPendingIntent( + mContext, SystemActionsBroadcastReceiver.INTENT_ACTION_TAKE_SCREENSHOT)); + + RemoteAction actionAccessibilityMenu = new RemoteAction( + Icon.createWithResource(mContext, R.drawable.ic_info), + mContext.getString(R.string.accessibility_system_action_accessibility_menu_label), + mContext.getString(R.string.accessibility_system_action_accessibility_menu_label), + mReceiver.createPendingIntent( + mContext, SystemActionsBroadcastReceiver.INTENT_ACTION_ACCESSIBILITY_MENU)); + + AccessibilityManager am = (AccessibilityManager) mContext.getSystemService( + Context.ACCESSIBILITY_SERVICE); + + am.registerSystemAction(actionBack, SYSTEM_ACTION_ID_BACK); + am.registerSystemAction(actionHome, SYSTEM_ACTION_ID_HOME); + am.registerSystemAction(actionRecents, SYSTEM_ACTION_ID_RECENTS); + am.registerSystemAction(actionNotifications, SYSTEM_ACTION_ID_NOTIFICATIONS); + am.registerSystemAction(actionQuickSettings, SYSTEM_ACTION_ID_QUICK_SETTINGS); + am.registerSystemAction(actionPowerDialog, SYSTEM_ACTION_ID_POWER_DIALOG); + am.registerSystemAction(actionToggleSplitScreen, SYSTEM_ACTION_ID_TOGGLE_SPLIT_SCREEN); + am.registerSystemAction(actionLockScreen, SYSTEM_ACTION_ID_LOCK_SCREEN); + am.registerSystemAction(actionTakeScreenshot, SYSTEM_ACTION_ID_TAKE_SCREENSHOT); + am.registerSystemAction(actionAccessibilityMenu, SYSTEM_ACTION_ID_ACCESSIBILITY_MENU); + } + + 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 handleToggleSplitScreen() { + mStatusBar.toggleSplitScreen(); + } + + 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, new Handler(Looper.getMainLooper()), null); + } + + private void handleAccessibilityMenu() { + AccessibilityManager.getInstance(mContext).notifyAccessibilityButtonClicked( + Display.DEFAULT_DISPLAY); + } + + 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_TOGGLE_SPLIT_SCREEN = + "SYSTEM_ACTION_TOGGLE_SPLIT_SCREEN"; + 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_MENU = + "SYSTEM_ACTION_ACCESSIBILITY_MENU"; + + 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_TOGGLE_SPLIT_SCREEN: + case INTENT_ACTION_LOCK_SCREEN: + case INTENT_ACTION_TAKE_SCREENSHOT: + case INTENT_ACTION_ACCESSIBILITY_MENU: { + Intent intent = new Intent(intentAction); + 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_TOGGLE_SPLIT_SCREEN); + intentFilter.addAction(INTENT_ACTION_LOCK_SCREEN); + intentFilter.addAction(INTENT_ACTION_TAKE_SCREENSHOT); + intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_MENU); + 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_TOGGLE_SPLIT_SCREEN: { + handleToggleSplitScreen(); + break; + } + case INTENT_ACTION_LOCK_SCREEN: { + handleLockScreen(); + break; + } + case INTENT_ACTION_TAKE_SCREENSHOT: { + handleTakeScreenshot(); + break; + } + case INTENT_ACTION_ACCESSIBILITY_MENU: { + handleAccessibilityMenu(); + break; + } + default: + break; + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java index 99dd5e2356d66..d4e47f699345e 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java @@ -21,6 +21,7 @@ import com.android.systemui.ScreenDecorations; import com.android.systemui.SizeCompatModeActivityController; import com.android.systemui.SliceBroadcastRelayHandler; import com.android.systemui.SystemUI; +import com.android.systemui.accessibility.SystemActions; import com.android.systemui.accessibility.WindowMagnification; import com.android.systemui.biometrics.AuthController; import com.android.systemui.globalactions.GlobalActionsComponent; @@ -140,6 +141,12 @@ public abstract class SystemUIBinder { @ClassKey(StatusBar.class) public abstract SystemUI bindsStatusBar(StatusBar sysui); + /** Inject into SystemActions. */ + @Binds + @IntoMap + @ClassKey(SystemActions.class) + public abstract SystemUI bindSystemActions(SystemActions sysui); + /** Inject into ThemeOverlayController. */ @Binds @IntoMap diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 8d4ad28972e9e..27de95a84ab6d 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -122,6 +122,7 @@ import android.animation.ValueAnimator; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.app.ActivityManager; import android.app.ActivityManager.TaskSnapshot; import android.app.ActivityManagerInternal; @@ -2996,7 +2997,13 @@ public class WindowManagerService extends IWindowManager.Stub } } - void showGlobalActions() { + @RequiresPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW) + @Override + public void showGlobalActions() { + if (!checkCallingPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW, + "showGlobalActions()")) { + throw new SecurityException("Requires INTERNAL_SYSTEM_WINDOW permission"); + } mPolicy.showGlobalActions(); }