From 384b578ab2ba408d4cc20ca5b0c04f76ec905858 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Tue, 9 Feb 2021 22:50:02 -0800 Subject: [PATCH] Removing menu and dialog for custom actions hanlding. These do not work well with gesture-nav and can potentially block the Launcher UI. Instead exposing on custom actions with keyboard accelerator and using thee internal arrowPopup for resize options Fixing SecondoryDropTarget not sending appropriate stats log Bug: 179854703 Test: Verified on device Change-Id: I268690f8a937896e4350496128a38959003f8939 --- .../QuickstepAccessibilityDelegate.java | 57 +++++ .../uioverrides/PredictedAppIcon.java | 34 +-- .../uioverrides/QuickstepLauncher.java | 7 + res/drawable/ic_widget_height_decrease.xml | 25 ++ res/drawable/ic_widget_height_increase.xml | 25 ++ res/drawable/ic_widget_width_decrease.xml | 22 ++ res/drawable/ic_widget_width_increase.xml | 22 ++ res/values/strings.xml | 2 - .../launcher3/AbstractFloatingView.java | 2 +- src/com/android/launcher3/CellLayout.java | 2 + src/com/android/launcher3/Launcher.java | 47 +--- .../launcher3/SecondaryDropTarget.java | 13 +- .../LauncherAccessibilityDelegate.java | 240 +++++++++--------- .../ShortcutMenuAccessibilityDelegate.java | 22 +- .../keyboard/CustomActionsPopup.java | 94 ------- .../android/launcher3/popup/ArrowPopup.java | 12 + .../popup/PopupContainerWithArrow.java | 1 + .../launcher3/views/OptionsPopupView.java | 4 +- 18 files changed, 342 insertions(+), 289 deletions(-) create mode 100644 quickstep/src/com/android/launcher3/QuickstepAccessibilityDelegate.java create mode 100644 res/drawable/ic_widget_height_decrease.xml create mode 100644 res/drawable/ic_widget_height_increase.xml create mode 100644 res/drawable/ic_widget_width_decrease.xml create mode 100644 res/drawable/ic_widget_width_increase.xml delete mode 100644 src/com/android/launcher3/keyboard/CustomActionsPopup.java diff --git a/quickstep/src/com/android/launcher3/QuickstepAccessibilityDelegate.java b/quickstep/src/com/android/launcher3/QuickstepAccessibilityDelegate.java new file mode 100644 index 0000000000..96559cbb8a --- /dev/null +++ b/quickstep/src/com/android/launcher3/QuickstepAccessibilityDelegate.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2021 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.launcher3; + +import android.view.KeyEvent; +import android.view.View; + +import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; +import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.uioverrides.PredictedAppIcon; +import com.android.launcher3.uioverrides.QuickstepLauncher; + +import java.util.List; + +public class QuickstepAccessibilityDelegate extends LauncherAccessibilityDelegate { + + public QuickstepAccessibilityDelegate(QuickstepLauncher launcher) { + super(launcher); + mActions.put(PIN_PREDICTION, new LauncherAction( + PIN_PREDICTION, R.string.pin_prediction, KeyEvent.KEYCODE_P)); + } + + @Override + protected void getSupportedActions(View host, ItemInfo item, List out) { + if (host instanceof PredictedAppIcon && !((PredictedAppIcon) host).isPinned()) { + out.add(new LauncherAction(PIN_PREDICTION, R.string.pin_prediction, + KeyEvent.KEYCODE_P)); + } + super.getSupportedActions(host, item, out); + } + + @Override + protected boolean performAction(View host, ItemInfo item, int action, boolean fromKeyboard) { + QuickstepLauncher launcher = (QuickstepLauncher) mLauncher; + if (action == PIN_PREDICTION) { + if (launcher.getHotseatPredictionController() == null) { + return false; + } + launcher.getHotseatPredictionController().pinPrediction(item); + return true; + } + return super.performAction(host, item, action, fromKeyboard); + } +} diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java index 2d501257c3..98551fb17a 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java +++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java @@ -15,7 +15,6 @@ */ package com.android.launcher3.uioverrides; -import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.PIN_PREDICTION; import static com.android.launcher3.graphics.IconShape.getShape; import android.content.Context; @@ -29,7 +28,6 @@ import android.os.Process; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.ViewGroup; -import android.view.accessibility.AccessibilityNodeInfo; import androidx.core.graphics.ColorUtils; @@ -37,9 +35,7 @@ import com.android.launcher3.CellLayout; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.R; -import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; import com.android.launcher3.graphics.IconPalette; -import com.android.launcher3.hybridhotseat.HotseatPredictionController; import com.android.launcher3.icons.IconNormalizer; import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.model.data.ItemInfo; @@ -53,8 +49,7 @@ import com.android.launcher3.views.DoubleShadowBubbleTextView; /** * A BubbleTextView with a ring around it's drawable */ -public class PredictedAppIcon extends DoubleShadowBubbleTextView implements - LauncherAccessibilityDelegate.AccessibilityActionHandler { +public class PredictedAppIcon extends DoubleShadowBubbleTextView { private static final int RING_SHADOW_COLOR = 0x99000000; private static final float RING_EFFECT_RATIO = 0.095f; @@ -147,29 +142,6 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView implements verifyHighRes(); } - @Override - public void addSupportedAccessibilityActions(AccessibilityNodeInfo accessibilityNodeInfo) { - if (!mIsPinned) { - accessibilityNodeInfo.addAction( - new AccessibilityNodeInfo.AccessibilityAction(PIN_PREDICTION, - getContext().getText(R.string.pin_prediction))); - } - } - - @Override - public boolean performAccessibilityAction(int action, ItemInfo info) { - QuickstepLauncher launcher = Launcher.cast(Launcher.getLauncher(getContext())); - if (action == PIN_PREDICTION) { - if (launcher == null) { - return false; - } - HotseatPredictionController controller = launcher.getHotseatPredictionController(); - controller.pinPrediction(info); - return true; - } - return false; - } - @Override public void getIconBounds(Rect outBounds) { super.getIconBounds(outBounds); @@ -179,6 +151,10 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView implements } } + public boolean isPinned() { + return mIsPinned; + } + private int getOutlineOffsetX() { return (getMeasuredWidth() / 2) - mNormalizedIconRadius; } diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index 2e018f357f..0461e96871 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -44,7 +44,9 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.LauncherState; +import com.android.launcher3.QuickstepAccessibilityDelegate; import com.android.launcher3.Workspace; +import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.appprediction.PredictionRowView; import com.android.launcher3.hybridhotseat.HotseatPredictionController; @@ -138,6 +140,11 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { mHotseatPredictionController.logLaunchedAppRankingInfo(info, instanceId); } + @Override + protected LauncherAccessibilityDelegate createAccessibilityDelegate() { + return new QuickstepAccessibilityDelegate(this); + } + /** * Returns Prediction controller for hybrid hotseat */ diff --git a/res/drawable/ic_widget_height_decrease.xml b/res/drawable/ic_widget_height_decrease.xml new file mode 100644 index 0000000000..df704bacbe --- /dev/null +++ b/res/drawable/ic_widget_height_decrease.xml @@ -0,0 +1,25 @@ + + + + diff --git a/res/drawable/ic_widget_height_increase.xml b/res/drawable/ic_widget_height_increase.xml new file mode 100644 index 0000000000..c263a4b43c --- /dev/null +++ b/res/drawable/ic_widget_height_increase.xml @@ -0,0 +1,25 @@ + + + + diff --git a/res/drawable/ic_widget_width_decrease.xml b/res/drawable/ic_widget_width_decrease.xml new file mode 100644 index 0000000000..2a2fad777c --- /dev/null +++ b/res/drawable/ic_widget_width_decrease.xml @@ -0,0 +1,22 @@ + + diff --git a/res/drawable/ic_widget_width_increase.xml b/res/drawable/ic_widget_width_increase.xml new file mode 100644 index 0000000000..89b9f40673 --- /dev/null +++ b/res/drawable/ic_widget_width_increase.xml @@ -0,0 +1,22 @@ + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 447c9ac568..dd1cc7cbf9 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -37,8 +37,6 @@ Shortcut isn\'t available Home - - Custom actions diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java index 6037c96f82..95cdbdd147 100644 --- a/src/com/android/launcher3/AbstractFloatingView.java +++ b/src/com/android/launcher3/AbstractFloatingView.java @@ -59,7 +59,7 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch TYPE_SNACKBAR, TYPE_LISTENER, TYPE_ALL_APPS_EDU, - + TYPE_DRAG_DROP_POPUP, TYPE_TASK_MENU, TYPE_OPTIONS_POPUP, TYPE_ICON_SURFACE diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index 452207db3b..d750c6ccc1 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -305,6 +305,8 @@ public class CellLayout extends ViewGroup { setImportantForAccessibility(accessibilityFlag); getShortcutsAndWidgets().setImportantForAccessibility(accessibilityFlag); + // ExploreByTouchHelper sets focusability. Clear it when the delegate is cleared. + setFocusable(delegate != null); // Invalidate the accessibility hierarchy if (getParent() != null) { getParent().notifySubtreeAccessibilityStateChanged( diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 2df9cbed54..30dac64fc9 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -37,6 +37,7 @@ import static com.android.launcher3.LauncherState.NO_SCALE; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.LauncherState.SPRING_LOADED; import static com.android.launcher3.Utilities.postAsyncCallback; +import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.getSupportedActions; import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_LAUNCHER_LOAD; import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND; import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME; @@ -104,6 +105,7 @@ import androidx.annotation.VisibleForTesting; import com.android.launcher3.DropTarget.DragObject; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; +import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.LauncherAction; import com.android.launcher3.allapps.AllAppsContainerView; import com.android.launcher3.allapps.AllAppsStore; import com.android.launcher3.allapps.AllAppsTransitionController; @@ -120,7 +122,6 @@ import com.android.launcher3.folder.FolderGridOrganizer; import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.icons.BitmapRenderer; import com.android.launcher3.icons.IconCache; -import com.android.launcher3.keyboard.CustomActionsPopup; import com.android.launcher3.keyboard.ViewGroupFocusHelper; import com.android.launcher3.logger.LauncherAtom; import com.android.launcher3.logging.FileLog; @@ -163,7 +164,6 @@ import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.PendingRequestArgs; import com.android.launcher3.util.SafeCloseable; -import com.android.launcher3.util.ShortcutUtil; import com.android.launcher3.util.SystemUiController; import com.android.launcher3.util.Themes; import com.android.launcher3.util.Thunk; @@ -383,7 +383,7 @@ public class Launcher extends StatefulActivity implements Launche idp.addOnChangeListener(this); mSharedPrefs = Utilities.getPrefs(this); mIconCache = app.getIconCache(); - mAccessibilityDelegate = new LauncherAccessibilityDelegate(this); + mAccessibilityDelegate = createAccessibilityDelegate(); mDragController = new DragController(this); mAllAppsController = new AllAppsTransitionController(this); @@ -2633,19 +2633,9 @@ public class Launcher extends StatefulActivity implements Launche shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.widget_button_text), KeyEvent.KEYCODE_W, KeyEvent.META_CTRL_ON)); } - final View currentFocus = getCurrentFocus(); - if (currentFocus != null) { - if (new CustomActionsPopup(this, currentFocus).canShow()) { - shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.custom_actions), - KeyEvent.KEYCODE_O, KeyEvent.META_CTRL_ON)); - } - if (currentFocus.getTag() instanceof ItemInfo - && ShortcutUtil.supportsShortcuts((ItemInfo) currentFocus.getTag())) { + getSupportedActions(this, getCurrentFocus()).forEach(la -> shortcutInfos.add(new KeyboardShortcutInfo( - getString(R.string.shortcuts_menu_with_notifications_description), - KeyEvent.KEYCODE_S, KeyEvent.META_CTRL_ON)); - } - } + la.accessibilityAction.getLabel(), la.keyCode, KeyEvent.META_CTRL_ON))); if (!shortcutInfos.isEmpty()) { data.add(new KeyboardShortcutGroup(getString(R.string.home_screen), shortcutInfos)); } @@ -2663,30 +2653,18 @@ public class Launcher extends StatefulActivity implements Launche return true; } break; - case KeyEvent.KEYCODE_S: { - View focusedView = getCurrentFocus(); - if (focusedView instanceof BubbleTextView - && focusedView.getTag() instanceof ItemInfo - && mAccessibilityDelegate.performAction(focusedView, - (ItemInfo) focusedView.getTag(), - LauncherAccessibilityDelegate.DEEP_SHORTCUTS, - true)) { - PopupContainerWithArrow.getOpen(this).requestFocus(); - return true; - } - break; - } - case KeyEvent.KEYCODE_O: - if (new CustomActionsPopup(this, getCurrentFocus()).show()) { - return true; - } - break; case KeyEvent.KEYCODE_W: if (isInState(NORMAL)) { OptionsPopupView.openWidgets(this); return true; } break; + default: + for (LauncherAction la : getSupportedActions(this, getCurrentFocus())) { + if (la.keyCode == keyCode) { + return la.invokeFromKeyboard(getCurrentFocus()); + } + } } } return super.onKeyShortcut(keyCode, event); @@ -2744,6 +2722,9 @@ public class Launcher extends StatefulActivity implements Launche return Stream.of(APP_INFO, WIDGETS, INSTALL); } + protected LauncherAccessibilityDelegate createAccessibilityDelegate() { + return new LauncherAccessibilityDelegate(this); + } /** * @see LauncherState#getOverviewScaleAndOffset(Launcher) diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java index 92b88e6186..7276887f76 100644 --- a/src/com/android/launcher3/SecondaryDropTarget.java +++ b/src/com/android/launcher3/SecondaryDropTarget.java @@ -37,6 +37,8 @@ import com.android.launcher3.Launcher.OnResumeCallback; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.logging.FileLog; +import com.android.launcher3.logging.InstanceId; +import com.android.launcher3.logging.InstanceIdSequence; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.logging.StatsLogManager.StatsLogger; import com.android.launcher3.model.data.ItemInfo; @@ -203,9 +205,13 @@ public class SecondaryDropTarget extends ButtonDropTarget implements OnAlarmList d.dragSource = new DeferredOnComplete(d.dragSource, getContext()); super.onDrop(d, options); - StatsLogger logger = mStatsLogManager.logger().withInstanceId(d.logInstanceId); - if (d.originalDragInfo != null) { - logger.withItemInfo(d.originalDragInfo); + doLog(d.logInstanceId, d.originalDragInfo); + } + + private void doLog(InstanceId logInstanceId, ItemInfo itemInfo) { + StatsLogger logger = mStatsLogManager.logger().withInstanceId(logInstanceId); + if (itemInfo != null) { + logger.withItemInfo(itemInfo); } if (mCurrentAccessibilityAction == UNINSTALL) { logger.log(LAUNCHER_ITEM_DROPPED_ON_UNINSTALL); @@ -296,6 +302,7 @@ public class SecondaryDropTarget extends ButtonDropTarget implements OnAlarmList @Override public void onAccessibilityDrop(View view, ItemInfo item) { + doLog(new InstanceIdSequence().newInstanceId(), item); performDropAction(view, item); } diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java index 6fac79aa7e..cd4616a967 100644 --- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java @@ -3,17 +3,18 @@ package com.android.launcher3.accessibility; import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK; import static com.android.launcher3.LauncherState.NORMAL; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE; -import android.app.AlertDialog; import android.appwidget.AppWidgetProviderInfo; -import android.content.DialogInterface; import android.graphics.Point; import android.graphics.Rect; +import android.graphics.RectF; import android.os.Bundle; import android.os.Handler; import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; +import android.view.KeyEvent; import android.view.View; import android.view.View.AccessibilityDelegate; import android.view.accessibility.AccessibilityNodeInfo; @@ -25,7 +26,6 @@ import com.android.launcher3.ButtonDropTarget; import com.android.launcher3.CellLayout; import com.android.launcher3.DropTarget.DragObject; import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.PendingAddItemInfo; import com.android.launcher3.R; @@ -33,7 +33,6 @@ import com.android.launcher3.Workspace; import com.android.launcher3.dragndrop.DragController.DragListener; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.folder.Folder; -import com.android.launcher3.keyboard.CustomActionsPopup; import com.android.launcher3.keyboard.KeyboardDragAndDropView; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.FolderInfo; @@ -41,14 +40,19 @@ import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.LauncherAppWidgetInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.notification.NotificationListener; +import com.android.launcher3.popup.ArrowPopup; import com.android.launcher3.popup.PopupContainerWithArrow; import com.android.launcher3.touch.ItemLongClickListener; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.ShortcutUtil; import com.android.launcher3.util.Thunk; +import com.android.launcher3.views.OptionsPopupView; +import com.android.launcher3.views.OptionsPopupView.OptionItem; import com.android.launcher3.widget.LauncherAppWidgetHostView; import java.util.ArrayList; +import java.util.Collections; +import java.util.List; public class LauncherAccessibilityDelegate extends AccessibilityDelegate implements DragListener { @@ -78,89 +82,105 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme public View item; } - protected final SparseArray mActions = new SparseArray<>(); - @Thunk final Launcher mLauncher; + protected final SparseArray mActions = new SparseArray<>(); + protected final Launcher mLauncher; private DragInfo mDragInfo = null; public LauncherAccessibilityDelegate(Launcher launcher) { mLauncher = launcher; - mActions.put(REMOVE, new AccessibilityAction(REMOVE, - launcher.getText(R.string.remove_drop_target_label))); - mActions.put(UNINSTALL, new AccessibilityAction(UNINSTALL, - launcher.getText(R.string.uninstall_drop_target_label))); - mActions.put(DISMISS_PREDICTION, new AccessibilityAction(DISMISS_PREDICTION, - launcher.getText(R.string.dismiss_prediction_label))); - mActions.put(RECONFIGURE, new AccessibilityAction(RECONFIGURE, - launcher.getText(R.string.gadget_setup_text))); - mActions.put(ADD_TO_WORKSPACE, new AccessibilityAction(ADD_TO_WORKSPACE, - launcher.getText(R.string.action_add_to_workspace))); - mActions.put(MOVE, new AccessibilityAction(MOVE, - launcher.getText(R.string.action_move))); - mActions.put(MOVE_TO_WORKSPACE, new AccessibilityAction(MOVE_TO_WORKSPACE, - launcher.getText(R.string.action_move_to_workspace))); - mActions.put(RESIZE, new AccessibilityAction(RESIZE, - launcher.getText(R.string.action_resize))); - mActions.put(DEEP_SHORTCUTS, new AccessibilityAction(DEEP_SHORTCUTS, - launcher.getText(R.string.action_deep_shortcut))); - mActions.put(SHORTCUTS_AND_NOTIFICATIONS, new AccessibilityAction(DEEP_SHORTCUTS, - launcher.getText(R.string.shortcuts_menu_with_notifications_description))); + mActions.put(REMOVE, new LauncherAction( + REMOVE, R.string.remove_drop_target_label, KeyEvent.KEYCODE_X)); + mActions.put(UNINSTALL, new LauncherAction( + UNINSTALL, R.string.uninstall_drop_target_label, KeyEvent.KEYCODE_U)); + mActions.put(DISMISS_PREDICTION, new LauncherAction(DISMISS_PREDICTION, + R.string.dismiss_prediction_label, KeyEvent.KEYCODE_X)); + mActions.put(RECONFIGURE, new LauncherAction( + RECONFIGURE, R.string.gadget_setup_text, KeyEvent.KEYCODE_E)); + mActions.put(ADD_TO_WORKSPACE, new LauncherAction( + ADD_TO_WORKSPACE, R.string.action_add_to_workspace, KeyEvent.KEYCODE_P)); + mActions.put(MOVE, new LauncherAction( + MOVE, R.string.action_move, KeyEvent.KEYCODE_M)); + mActions.put(MOVE_TO_WORKSPACE, new LauncherAction(MOVE_TO_WORKSPACE, + R.string.action_move_to_workspace, KeyEvent.KEYCODE_P)); + mActions.put(RESIZE, new LauncherAction( + RESIZE, R.string.action_resize, KeyEvent.KEYCODE_R)); + mActions.put(DEEP_SHORTCUTS, new LauncherAction(DEEP_SHORTCUTS, + R.string.action_deep_shortcut, KeyEvent.KEYCODE_S)); + mActions.put(SHORTCUTS_AND_NOTIFICATIONS, new LauncherAction(DEEP_SHORTCUTS, + R.string.shortcuts_menu_with_notifications_description, KeyEvent.KEYCODE_S)); } @Override public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(host, info); - addSupportedActions(host, info, false); + if (host.getTag() instanceof ItemInfo) { + ItemInfo item = (ItemInfo) host.getTag(); + + List actions = new ArrayList<>(); + getSupportedActions(host, item, actions); + actions.forEach(la -> info.addAction(la.accessibilityAction)); + + if (!itemSupportsLongClick(host, item)) { + info.setLongClickable(false); + info.removeAction(AccessibilityAction.ACTION_LONG_CLICK); + } + } } - public void addSupportedActions(View host, AccessibilityNodeInfo info, boolean fromKeyboard) { - if (!(host.getTag() instanceof ItemInfo)) return; - ItemInfo item = (ItemInfo) host.getTag(); - - if (host instanceof AccessibilityActionHandler) { - ((AccessibilityActionHandler) host).addSupportedAccessibilityActions(info); - } - + /** + * Adds all the accessibility actions that can be handled. + */ + protected void getSupportedActions(View host, ItemInfo item, List out) { // If the request came from keyboard, do not add custom shortcuts as that is already // exposed as a direct shortcut - if (!fromKeyboard && ShortcutUtil.supportsShortcuts(item)) { - info.addAction(mActions.get(NotificationListener.getInstanceIfConnected() != null + if (ShortcutUtil.supportsShortcuts(item)) { + out.add(mActions.get(NotificationListener.getInstanceIfConnected() != null ? SHORTCUTS_AND_NOTIFICATIONS : DEEP_SHORTCUTS)); } for (ButtonDropTarget target : mLauncher.getDropTargetBar().getDropTargets()) { if (target.supportsAccessibilityDrop(item, host)) { - info.addAction(mActions.get(target.getAccessibilityAction())); + out.add(mActions.get(target.getAccessibilityAction())); } } // Do not add move actions for keyboard request as this uses virtual nodes. if (itemSupportsAccessibleDrag(item)) { - info.addAction(mActions.get(MOVE)); + out.add(mActions.get(MOVE)); if (item.container >= 0) { - info.addAction(mActions.get(MOVE_TO_WORKSPACE)); + out.add(mActions.get(MOVE_TO_WORKSPACE)); } else if (item instanceof LauncherAppWidgetInfo) { if (!getSupportedResizeActions(host, (LauncherAppWidgetInfo) item).isEmpty()) { - info.addAction(mActions.get(RESIZE)); + out.add(mActions.get(RESIZE)); } } } - if (!fromKeyboard && !itemSupportsLongClick(host, item)) { - info.setLongClickable(false); - info.removeAction(AccessibilityAction.ACTION_LONG_CLICK); - } - if ((item instanceof AppInfo) || (item instanceof PendingAddItemInfo)) { - info.addAction(mActions.get(ADD_TO_WORKSPACE)); + out.add(mActions.get(ADD_TO_WORKSPACE)); } } + /** + * Returns all the accessibility actions that can be handled by the host. + */ + public static List getSupportedActions(Launcher launcher, View host) { + if (host == null || !(host.getTag() instanceof ItemInfo)) { + return Collections.emptyList(); + } + PopupContainerWithArrow container = PopupContainerWithArrow.getOpen(launcher); + LauncherAccessibilityDelegate delegate = container != null + ? container.getAccessibilityDelegate() : launcher.getAccessibilityDelegate(); + List result = new ArrayList<>(); + delegate.getSupportedActions(host, (ItemInfo) host.getTag(), result); + return result; + } + private boolean itemSupportsLongClick(View host, ItemInfo info) { - return PopupContainerWithArrow.canShow(host, info) - || new CustomActionsPopup(mLauncher, host).canShow(); + return PopupContainerWithArrow.canShow(host, info); } private boolean itemSupportsAccessibleDrag(ItemInfo item) { @@ -184,7 +204,7 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme /** * Performs the provided action on the host */ - public boolean performAction(final View host, final ItemInfo item, int action, + protected boolean performAction(final View host, final ItemInfo item, int action, boolean fromKeyboard) { if (action == ACTION_LONG_CLICK) { if (PopupContainerWithArrow.canShow(host, item)) { @@ -193,19 +213,8 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme // standard long press path does. PopupContainerWithArrow.showForIcon((BubbleTextView) host); return true; - } else { - CustomActionsPopup popup = new CustomActionsPopup(mLauncher, host); - if (popup.canShow()) { - popup.show(); - return true; - } } - } - if (host instanceof AccessibilityActionHandler - && ((AccessibilityActionHandler) host).performAccessibilityAction(action, item)) { - return true; - } - if (action == MOVE) { + } else if (action == MOVE) { return beginAccessibleDrag(host, item, fromKeyboard); } else if (action == ADD_TO_WORKSPACE) { final int[] coordinates = new int[2]; @@ -220,9 +229,7 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme Favorites.CONTAINER_DESKTOP, screenId, coordinates[0], coordinates[1]); - ArrayList itemList = new ArrayList<>(); - itemList.add(info); - mLauncher.bindItems(itemList, true); + mLauncher.bindItems(Collections.singletonList(info), true); announceConfirmation(R.string.item_added_to_workspace); } else if (item instanceof PendingAddItemInfo) { PendingAddItemInfo info = (PendingAddItemInfo) item; @@ -243,47 +250,31 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme final int[] coordinates = new int[2]; final int screenId = findSpaceOnWorkspace(item, coordinates); mLauncher.getModelWriter().moveItemInDatabase(info, - LauncherSettings.Favorites.CONTAINER_DESKTOP, + Favorites.CONTAINER_DESKTOP, screenId, coordinates[0], coordinates[1]); // Bind the item in next frame so that if a new workspace page was created, // it will get laid out. - new Handler().post(new Runnable() { - - @Override - public void run() { - ArrayList itemList = new ArrayList<>(); - itemList.add(item); - mLauncher.bindItems(itemList, true); - announceConfirmation(R.string.item_moved); - } + new Handler().post(() -> { + mLauncher.bindItems(Collections.singletonList(item), true); + announceConfirmation(R.string.item_moved); }); + return true; } else if (action == RESIZE) { final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) item; - final IntArray actions = getSupportedResizeActions(host, info); - CharSequence[] labels = new CharSequence[actions.size()]; - for (int i = 0; i < actions.size(); i++) { - labels[i] = mLauncher.getText(actions.get(i)); - } - - new AlertDialog.Builder(mLauncher) - .setTitle(R.string.action_resize) - .setItems(labels, new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - performResizeAction(actions.get(which), host, info); - dialog.dismiss(); - } - }) - .show(); + List actions = getSupportedResizeActions(host, info); + Rect pos = new Rect(); + mLauncher.getDragLayer().getDescendantRectRelativeToSelf(host, pos); + ArrowPopup popup = OptionsPopupView.show(mLauncher, new RectF(pos), actions); + popup.requestFocus(); + popup.setOnCloseCallback(host::requestFocus); return true; - } else if (action == DEEP_SHORTCUTS) { + } else if (action == DEEP_SHORTCUTS || action == SHORTCUTS_AND_NOTIFICATIONS) { return PopupContainerWithArrow.showForIcon((BubbleTextView) host) != null; } else { for (ButtonDropTarget dropTarget : mLauncher.getDropTargetBar().getDropTargets()) { - if (dropTarget.supportsAccessibilityDrop(item, host) && - action == dropTarget.getAccessibilityAction()) { + if (dropTarget.supportsAccessibilityDrop(item, host) + && action == dropTarget.getAccessibilityAction()) { dropTarget.onAccessibilityDrop(host, item); return true; } @@ -292,9 +283,8 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme return false; } - private IntArray getSupportedResizeActions(View host, LauncherAppWidgetInfo info) { - IntArray actions = new IntArray(); - + private List getSupportedResizeActions(View host, LauncherAppWidgetInfo info) { + List actions = new ArrayList<>(); AppWidgetProviderInfo providerInfo = ((LauncherAppWidgetHostView) host).getAppWidgetInfo(); if (providerInfo == null) { return actions; @@ -304,28 +294,40 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme if ((providerInfo.resizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0) { if (layout.isRegionVacant(info.cellX + info.spanX, info.cellY, 1, info.spanY) || layout.isRegionVacant(info.cellX - 1, info.cellY, 1, info.spanY)) { - actions.add(R.string.action_increase_width); + actions.add(new OptionItem( + R.string.action_increase_width, R.drawable.ic_widget_width_increase, + IGNORE, + v -> performResizeAction(R.string.action_increase_width, host, info))); } if (info.spanX > info.minSpanX && info.spanX > 1) { - actions.add(R.string.action_decrease_width); + actions.add(new OptionItem( + R.string.action_decrease_width, R.drawable.ic_widget_width_decrease, + IGNORE, + v -> performResizeAction(R.string.action_decrease_width, host, info))); } } if ((providerInfo.resizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0) { if (layout.isRegionVacant(info.cellX, info.cellY + info.spanY, info.spanX, 1) || layout.isRegionVacant(info.cellX, info.cellY - 1, info.spanX, 1)) { - actions.add(R.string.action_increase_height); + actions.add(new OptionItem( + R.string.action_increase_height, R.drawable.ic_widget_height_increase, + IGNORE, + v -> performResizeAction(R.string.action_increase_height, host, info))); } if (info.spanY > info.minSpanY && info.spanY > 1) { - actions.add(R.string.action_decrease_height); + actions.add(new OptionItem( + R.string.action_decrease_height, R.drawable.ic_widget_height_decrease, + IGNORE, + v -> performResizeAction(R.string.action_decrease_height, host, info))); } } return actions; } - @Thunk void performResizeAction(int action, View host, LauncherAppWidgetInfo info) { + private boolean performResizeAction(int action, View host, LauncherAppWidgetInfo info) { CellLayout.LayoutParams lp = (CellLayout.LayoutParams) host.getLayoutParams(); CellLayout layout = (CellLayout) host.getParent().getParent(); layout.markCellsAsUnoccupiedForView(host); @@ -362,6 +364,7 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme host.requestLayout(); mLauncher.getModelWriter().updateItemInDatabase(info); announceConfirmation(mLauncher.getString(R.string.widget_resized, info.spanX, info.spanY)); + return true; } @Thunk void announceConfirmation(int resId) { @@ -489,19 +492,28 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme return screenId; } - /** - * An interface allowing views to handle their own action. - */ - public interface AccessibilityActionHandler { + public class LauncherAction { + public final int keyCode; + public final AccessibilityAction accessibilityAction; + + private final LauncherAccessibilityDelegate mDelegate; + + public LauncherAction(int id, int labelRes, int keyCode) { + this.keyCode = keyCode; + accessibilityAction = new AccessibilityAction(id, mLauncher.getString(labelRes)); + mDelegate = LauncherAccessibilityDelegate.this; + } /** - * performs accessibility action and returns true on success + * Invokes the action for the provided host */ - boolean performAccessibilityAction(int action, ItemInfo itemInfo); - - /** - * adds all the accessibility actions that can be handled. - */ - void addSupportedAccessibilityActions(AccessibilityNodeInfo accessibilityNodeInfo); + public boolean invokeFromKeyboard(View host) { + if (host != null && host.getTag() instanceof ItemInfo) { + return mDelegate.performAction( + host, (ItemInfo) host.getTag(), accessibilityAction.getId(), true); + } else { + return false; + } + } } } diff --git a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java index aaaff98e18..1733e5dd41 100644 --- a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java @@ -18,9 +18,8 @@ package com.android.launcher3.accessibility; import static com.android.launcher3.LauncherState.NORMAL; +import android.view.KeyEvent; import android.view.View; -import android.view.accessibility.AccessibilityNodeInfo; -import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.Launcher; @@ -31,7 +30,8 @@ import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.notification.NotificationMainView; import com.android.launcher3.shortcuts.DeepShortcutView; -import java.util.ArrayList; +import java.util.Collections; +import java.util.List; /** * Extension of {@link LauncherAccessibilityDelegate} with actions specific to shortcuts in @@ -43,23 +43,23 @@ public class ShortcutMenuAccessibilityDelegate extends LauncherAccessibilityDele public ShortcutMenuAccessibilityDelegate(Launcher launcher) { super(launcher); - mActions.put(DISMISS_NOTIFICATION, new AccessibilityAction(DISMISS_NOTIFICATION, - launcher.getText(R.string.action_dismiss_notification))); + mActions.put(DISMISS_NOTIFICATION, new LauncherAction(DISMISS_NOTIFICATION, + R.string.action_dismiss_notification, KeyEvent.KEYCODE_X)); } @Override - public void addSupportedActions(View host, AccessibilityNodeInfo info, boolean fromKeyboard) { + protected void getSupportedActions(View host, ItemInfo item, List out) { if ((host.getParent() instanceof DeepShortcutView)) { - info.addAction(mActions.get(ADD_TO_WORKSPACE)); + out.add(mActions.get(ADD_TO_WORKSPACE)); } else if (host instanceof NotificationMainView) { if (((NotificationMainView) host).canChildBeDismissed()) { - info.addAction(mActions.get(DISMISS_NOTIFICATION)); + out.add(mActions.get(DISMISS_NOTIFICATION)); } } } @Override - public boolean performAction(View host, ItemInfo item, int action, boolean fromKeyboard) { + protected boolean performAction(View host, ItemInfo item, int action, boolean fromKeyboard) { if (action == ADD_TO_WORKSPACE) { if (!(host.getParent() instanceof DeepShortcutView)) { return false; @@ -73,9 +73,7 @@ public class ShortcutMenuAccessibilityDelegate extends LauncherAccessibilityDele mLauncher.getModelWriter().addItemToDatabase(info, LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId, coordinates[0], coordinates[1]); - ArrayList itemList = new ArrayList<>(); - itemList.add(info); - mLauncher.bindItems(itemList, true); + mLauncher.bindItems(Collections.singletonList(info), true); AbstractFloatingView.closeAllOpenViews(mLauncher); announceConfirmation(R.string.item_added_to_workspace); } diff --git a/src/com/android/launcher3/keyboard/CustomActionsPopup.java b/src/com/android/launcher3/keyboard/CustomActionsPopup.java deleted file mode 100644 index 77ce4a8c88..0000000000 --- a/src/com/android/launcher3/keyboard/CustomActionsPopup.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2016 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.launcher3.keyboard; - -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.accessibility.AccessibilityNodeInfo; -import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; -import android.widget.PopupMenu; -import android.widget.PopupMenu.OnMenuItemClickListener; - -import com.android.launcher3.Launcher; -import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; -import com.android.launcher3.model.data.ItemInfo; -import com.android.launcher3.popup.PopupContainerWithArrow; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Handles showing a popup menu with available custom actions for a launcher icon. - * This allows exposing various custom actions using keyboard shortcuts. - */ -public class CustomActionsPopup implements OnMenuItemClickListener { - - private final Launcher mLauncher; - private final LauncherAccessibilityDelegate mDelegate; - private final View mIcon; - - public CustomActionsPopup(Launcher launcher, View icon) { - mLauncher = launcher; - mIcon = icon; - PopupContainerWithArrow container = PopupContainerWithArrow.getOpen(launcher); - if (container != null) { - mDelegate = container.getAccessibilityDelegate(); - } else { - mDelegate = launcher.getAccessibilityDelegate(); - } - } - - private List getActionList() { - if (mIcon == null || !(mIcon.getTag() instanceof ItemInfo)) { - return Collections.EMPTY_LIST; - } - - AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(); - mDelegate.addSupportedActions(mIcon, info, true); - List result = new ArrayList<>(info.getActionList()); - info.recycle(); - return result; - } - - public boolean canShow() { - return !getActionList().isEmpty(); - } - - public boolean show() { - List actions = getActionList(); - if (actions.isEmpty()) { - return false; - } - - PopupMenu popup = new PopupMenu(mLauncher, mIcon); - popup.setOnMenuItemClickListener(this); - Menu menu = popup.getMenu(); - for (AccessibilityAction action : actions) { - menu.add(Menu.NONE, action.getId(), Menu.NONE, action.getLabel()); - } - popup.show(); - return true; - } - - @Override - public boolean onMenuItemClick(MenuItem menuItem) { - return mDelegate.performAction(mIcon, (ItemInfo) mIcon.getTag(), menuItem.getItemId(), - true); - } -} diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java index 90285c470b..56438d0815 100644 --- a/src/com/android/launcher3/popup/ArrowPopup.java +++ b/src/com/android/launcher3/popup/ArrowPopup.java @@ -40,6 +40,8 @@ import android.view.ViewGroup; import android.view.ViewOutlineProvider; import android.widget.FrameLayout; +import androidx.annotation.NonNull; + import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.InsettableFrameLayout; @@ -82,6 +84,8 @@ public abstract class ArrowPopup extends Abstrac private final Rect mStartRect = new Rect(); private final Rect mEndRect = new Rect(); + private Runnable mOnCloseCallback = () -> { }; + public ArrowPopup(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mInflater = LayoutInflater.from(context); @@ -555,6 +559,14 @@ public abstract class ArrowPopup extends Abstrac mDeferContainerRemoval = false; getPopupContainer().removeView(this); getPopupContainer().removeView(mArrow); + mOnCloseCallback.run(); + } + + /** + * Callback to be called when the popup is closed + */ + public void setOnCloseCallback(@NonNull Runnable callback) { + mOnCloseCallback = callback; } protected BaseDragLayer getPopupContainer() { diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java index 59930ff850..a1ba74703c 100644 --- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java +++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java @@ -206,6 +206,7 @@ public class PopupContainerWithArrow extends Arr .filter(Objects::nonNull) .collect(Collectors.toList())); launcher.refreshAndBindWidgetsForPackageUser(PackageUserKey.fromItemInfo(item)); + container.requestFocus(); return container; } diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java index addaf9cb82..0cb8c1ec77 100644 --- a/src/com/android/launcher3/views/OptionsPopupView.java +++ b/src/com/android/launcher3/views/OptionsPopupView.java @@ -118,7 +118,8 @@ public class OptionsPopupView extends ArrowPopup mTargetRect.roundOut(outPos); } - public static void show(Launcher launcher, RectF targetRect, List items) { + public static OptionsPopupView show( + Launcher launcher, RectF targetRect, List items) { OptionsPopupView popup = (OptionsPopupView) launcher.getLayoutInflater() .inflate(R.layout.longpress_options_menu, launcher.getDragLayer(), false); popup.mTargetRect = targetRect; @@ -134,6 +135,7 @@ public class OptionsPopupView extends ArrowPopup popup.mItemMap.put(view, item); } popup.show(); + return popup; } @VisibleForTesting