diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml index 31c0f5f2ab..3a4bb1086f 100644 --- a/quickstep/res/values/config.xml +++ b/quickstep/res/values/config.xml @@ -39,4 +39,8 @@ 23 + + + + diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml index 151b8e4604..70a4a7d5f7 100644 --- a/quickstep/res/values/strings.xml +++ b/quickstep/res/values/strings.xml @@ -233,4 +233,9 @@ Close Done + + + Move to top/left + + Move to bottom/right diff --git a/quickstep/src/com/android/launcher3/QuickstepAccessibilityDelegate.java b/quickstep/src/com/android/launcher3/QuickstepAccessibilityDelegate.java index 96559cbb8a..962fd91c2e 100644 --- a/quickstep/src/com/android/launcher3/QuickstepAccessibilityDelegate.java +++ b/quickstep/src/com/android/launcher3/QuickstepAccessibilityDelegate.java @@ -44,7 +44,7 @@ public class QuickstepAccessibilityDelegate extends LauncherAccessibilityDelegat @Override protected boolean performAction(View host, ItemInfo item, int action, boolean fromKeyboard) { - QuickstepLauncher launcher = (QuickstepLauncher) mLauncher; + QuickstepLauncher launcher = (QuickstepLauncher) mContext; if (action == PIN_PREDICTION) { if (launcher.getHotseatPredictionController() == null) { return false; diff --git a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java index 90c035fed0..f1e67479f5 100644 --- a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java @@ -60,6 +60,7 @@ public class FallbackTaskbarUIController extends TaskbarUIController { @Override protected void onDestroy() { + super.onDestroy(); mRecentsActivity.setTaskbarUIController(null); mRecentsActivity.getStateManager().removeStateListener(mStateListener); } diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java index 7d234392c4..2622700871 100644 --- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java @@ -99,6 +99,7 @@ public class LauncherTaskbarUIController extends TaskbarUIController { @Override protected void onDestroy() { + super.onDestroy(); onLauncherResumedOrPaused(false); mTaskbarLauncherStateController.onDestroy(); diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java index 0ab775694a..ce1e8b6b6e 100644 --- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java @@ -31,6 +31,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_I import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; import android.animation.ArgbEvaluator; import android.animation.ObjectAnimator; @@ -86,6 +87,7 @@ public class NavbarButtonsViewController { private static final int FLAG_DISABLE_RECENTS = 1 << 8; private static final int FLAG_DISABLE_BACK = 1 << 9; private static final int FLAG_NOTIFICATION_SHADE_EXPANDED = 1 << 10; + private static final int FLAG_SCREEN_PINNING_ACTIVE = 1 << 10; private static final int MASK_IME_SWITCHER_VISIBLE = FLAG_SWITCHER_SUPPORTED | FLAG_IME_VISIBLE; @@ -152,7 +154,9 @@ public class NavbarButtonsViewController { mPropertyHolders.add(new StatePropertyHolder( mControllers.taskbarViewController.getTaskbarIconAlpha() .getProperty(ALPHA_INDEX_KEYGUARD), - flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0, MultiValueAlpha.VALUE, 1, 0)); + flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 + && (flags & FLAG_SCREEN_PINNING_ACTIVE) == 0, + MultiValueAlpha.VALUE, 1, 0)); mPropertyHolders.add(new StatePropertyHolder(mControllers.taskbarDragLayerController .getKeyguardBgTaskbar(), @@ -286,6 +290,7 @@ public class NavbarButtonsViewController { int shadeExpandedFlags = SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED | SYSUI_STATE_QUICK_SETTINGS_EXPANDED; boolean isNotificationShadeExpanded = (sysUiStateFlags & shadeExpandedFlags) != 0; + boolean isScreenPinningActive = (sysUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0; // TODO(b/202218289) we're getting IME as not visible on lockscreen from system updateStateForFlag(FLAG_IME_VISIBLE, isImeVisible); @@ -295,6 +300,7 @@ public class NavbarButtonsViewController { updateStateForFlag(FLAG_DISABLE_RECENTS, isRecentsDisabled); updateStateForFlag(FLAG_DISABLE_BACK, isBackDisabled); updateStateForFlag(FLAG_NOTIFICATION_SHADE_EXPANDED, isNotificationShadeExpanded); + updateStateForFlag(FLAG_SCREEN_PINNING_ACTIVE, isScreenPinningActive); if (mA11yButton != null) { // Only used in 3 button diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index 8fd6d35228..883515e4b1 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -53,6 +53,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.launcher3.AbstractFloatingView; +import com.android.launcher3.BubbleTextView; import com.android.launcher3.DeviceProfile; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.R; @@ -113,6 +114,8 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ // The flag to know if the window is excluded from magnification region computation. private boolean mIsExcludeFromMagnificationRegion = false; + private final TaskbarShortcutMenuAccessibilityDelegate mAccessibilityDelegate; + public TaskbarActivityContext(Context windowContext, DeviceProfile dp, TaskbarNavButtonController buttonController, ScopedUnfoldTransitionProgressProvider unfoldTransitionProgressProvider) { @@ -148,6 +151,8 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ mLeftCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT); mRightCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT); + mAccessibilityDelegate = new TaskbarShortcutMenuAccessibilityDelegate(this); + // Construct controllers. mControllers = new TaskbarControllers(this, new TaskbarDragController(this), @@ -333,6 +338,11 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ return mControllers.taskbarPopupController.getPopupDataProvider(); } + @Override + public View.AccessibilityDelegate getAccessibilityDelegate() { + return mAccessibilityDelegate; + } + /** * Sets a new data-source for this taskbar instance */ @@ -375,6 +385,7 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ mControllers.taskbarStashController.updateStateForSysuiFlags(systemUiStateFlags, fromInit); mControllers.taskbarScrimViewController.updateStateForSysuiFlags(systemUiStateFlags, fromInit); + mControllers.navButtonController.updateSysuiFlags(systemUiStateFlags); } /** @@ -563,4 +574,9 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ } mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams); } + + public void showPopupMenuForIcon(BubbleTextView btv) { + setTaskbarWindowFullscreen(true); + btv.post(() -> mControllers.taskbarPopupController.showForIcon(btv)); + } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java index b3a9f8db7c..3315f8cad2 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java @@ -51,6 +51,8 @@ import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.popup.PopupContainerWithArrow; +import com.android.launcher3.shortcuts.DeepShortcutView; +import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.system.ClipDescriptionCompat; import com.android.systemui.shared.system.LauncherAppsCompat; @@ -88,6 +90,21 @@ public class TaskbarDragController extends DragController { - startInternalDrag(btv); + DragView dragView = startInternalDrag(btv, dragPreviewProvider); + if (iconShift != null) { + dragView.animateShift(-iconShift.x, -iconShift.y); + } btv.getIcon().setIsDisabled(true); mControllers.taskbarAutohideSuspendController.updateFlag( TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING, true); @@ -104,7 +124,8 @@ public class TaskbarDragController extends DragController recreateTaskbar(); mComponentCallbacks = new ComponentCallbacks() { private Configuration mOldConfig = mContext.getResources().getConfiguration(); diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java index ae23eda2a4..d23336505a 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java @@ -19,8 +19,10 @@ package com.android.launcher3.taskbar; import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS; import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_KEY; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; import android.os.Bundle; +import android.os.Handler; import androidx.annotation.IntDef; @@ -40,6 +42,13 @@ import java.lang.annotation.RetentionPolicy; */ public class TaskbarNavButtonController { + /** Allow some time in between the long press for back and recents. */ + static final int SCREEN_PIN_LONG_PRESS_THRESHOLD = 200; + static final int SCREEN_PIN_LONG_PRESS_RESET = SCREEN_PIN_LONG_PRESS_THRESHOLD + 100; + + private long mLastScreenPinLongPress; + private boolean mScreenPinned; + @Retention(RetentionPolicy.SOURCE) @IntDef(value = { BUTTON_BACK, @@ -57,10 +66,20 @@ public class TaskbarNavButtonController { static final int BUTTON_IME_SWITCH = BUTTON_RECENTS << 1; static final int BUTTON_A11Y = BUTTON_IME_SWITCH << 1; - private final TouchInteractionService mService; + private static final int SCREEN_UNPIN_COMBO = BUTTON_BACK | BUTTON_RECENTS; + private int mLongPressedButtons = 0; - public TaskbarNavButtonController(TouchInteractionService service) { + private final TouchInteractionService mService; + private final SystemUiProxy mSystemUiProxy; + private final Handler mHandler; + + private final Runnable mResetLongPress = this::resetScreenUnpin; + + public TaskbarNavButtonController(TouchInteractionService service, + SystemUiProxy systemUiProxy, Handler handler) { mService = service; + mSystemUiProxy = systemUiProxy; + mHandler = handler; } public void onButtonClick(@TaskbarButton int buttonType) { @@ -72,13 +91,13 @@ public class TaskbarNavButtonController { navigateHome(); break; case BUTTON_RECENTS: - navigateToOverview();; + navigateToOverview(); break; case BUTTON_IME_SWITCH: showIMESwitcher(); break; case BUTTON_A11Y: - notifyImeClick(false /* longClick */); + notifyA11yClick(false /* longClick */); break; } } @@ -89,46 +108,98 @@ public class TaskbarNavButtonController { startAssistant(); return true; case BUTTON_A11Y: - notifyImeClick(true /* longClick */); + notifyA11yClick(true /* longClick */); return true; case BUTTON_BACK: - case BUTTON_IME_SWITCH: case BUTTON_RECENTS: + mLongPressedButtons |= buttonType; + return determineScreenUnpin(); + case BUTTON_IME_SWITCH: default: return false; } } + /** + * Checks if the user has long pressed back and recents buttons + * "together" (within {@link #SCREEN_PIN_LONG_PRESS_THRESHOLD})ms + * If so, then requests the system to turn off screen pinning. + * + * @return true if the long press is a valid user action in attempting to unpin an app + * Will always return {@code false} when screen pinning is not active. + * NOTE: Returning true does not mean that screen pinning has stopped + */ + private boolean determineScreenUnpin() { + long timeNow = System.currentTimeMillis(); + if (!mScreenPinned) { + return false; + } + + if (mLastScreenPinLongPress == 0) { + // First button long press registered, just mark time and wait for second button press + mLastScreenPinLongPress = System.currentTimeMillis(); + mHandler.postDelayed(mResetLongPress, SCREEN_PIN_LONG_PRESS_RESET); + return true; + } + + if ((timeNow - mLastScreenPinLongPress) > SCREEN_PIN_LONG_PRESS_THRESHOLD) { + // Too long in-between presses, reset the clock + resetScreenUnpin(); + return false; + } + + if ((mLongPressedButtons & SCREEN_UNPIN_COMBO) == SCREEN_UNPIN_COMBO) { + // Hooray! They did it (finally...) + mSystemUiProxy.stopScreenPinning(); + mHandler.removeCallbacks(mResetLongPress); + resetScreenUnpin(); + } + return true; + } + + private void resetScreenUnpin() { + mLongPressedButtons = 0; + mLastScreenPinLongPress = 0; + } + + public void updateSysuiFlags(int sysuiFlags) { + mScreenPinned = (sysuiFlags & SYSUI_STATE_SCREEN_PINNING) != 0; + } + private void navigateHome() { mService.getOverviewCommandHelper().addCommand(OverviewCommandHelper.TYPE_HOME); } private void navigateToOverview() { + if (mScreenPinned) { + return; + } TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle"); mService.getOverviewCommandHelper().addCommand(OverviewCommandHelper.TYPE_TOGGLE); } private void executeBack() { - SystemUiProxy.INSTANCE.getNoCreate().onBackPressed(); + mSystemUiProxy.onBackPressed(); } private void showIMESwitcher() { - SystemUiProxy.INSTANCE.getNoCreate().onImeSwitcherPressed(); + mSystemUiProxy.onImeSwitcherPressed(); } - private void notifyImeClick(boolean longClick) { - SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.getNoCreate(); + private void notifyA11yClick(boolean longClick) { if (longClick) { - systemUiProxy.notifyAccessibilityButtonLongClicked(); + mSystemUiProxy.notifyAccessibilityButtonLongClicked(); } else { - systemUiProxy.notifyAccessibilityButtonClicked(mService.getDisplayId()); + mSystemUiProxy.notifyAccessibilityButtonClicked(mService.getDisplayId()); } } private void startAssistant() { + if (mScreenPinned) { + return; + } Bundle args = new Bundle(); args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS); - SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.getNoCreate(); - systemUiProxy.startAssistant(args); + mSystemUiProxy.startAssistant(args); } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java index 06c75fc68d..2dee50688d 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java @@ -15,6 +15,10 @@ */ package com.android.launcher3.taskbar; +import android.graphics.Point; +import android.view.MotionEvent; +import android.view.View; + import androidx.annotation.NonNull; import com.android.launcher3.BubbleTextView; @@ -30,6 +34,7 @@ import com.android.launcher3.popup.PopupContainerWithArrow; import com.android.launcher3.popup.PopupDataProvider; import com.android.launcher3.popup.PopupLiveUpdateHandler; import com.android.launcher3.popup.SystemShortcut; +import com.android.launcher3.shortcuts.DeepShortcutView; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.LauncherBindableItemsContainer; import com.android.launcher3.util.PackageUserKey; @@ -131,8 +136,14 @@ public class TaskbarPopupController { (PopupContainerWithArrow) context.getLayoutInflater().inflate( R.layout.popup_container, context.getDragLayer(), false); container.addOnAttachStateChangeListener( - new PopupLiveUpdateHandler<>(mContext, container)); + new PopupLiveUpdateHandler(mContext, container) { + @Override + protected void showPopupContainerForIcon(BubbleTextView originalIcon) { + showForIcon(originalIcon); + } + }); // TODO (b/198438631): configure for taskbar/context + container.setPopupItemDragHandler(new TaskbarPopupItemDragHandler()); container.populateAndShow(icon, mPopupDataProvider.getShortcutCountForItem(item), @@ -145,4 +156,43 @@ public class TaskbarPopupController { container.requestFocus(); return container; } + + private class TaskbarPopupItemDragHandler implements + PopupContainerWithArrow.PopupItemDragHandler { + + protected final Point mIconLastTouchPos = new Point(); + + TaskbarPopupItemDragHandler() {} + + @Override + public boolean onTouch(View view, MotionEvent ev) { + // Touched a shortcut, update where it was touched so we can drag from there on + // long click. + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_MOVE: + mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY()); + break; + } + return false; + } + + @Override + public boolean onLongClick(View v) { + // Return early if not the correct view + if (!(v.getParent() instanceof DeepShortcutView)) return false; + + DeepShortcutView sv = (DeepShortcutView) v.getParent(); + sv.setWillDrawIcon(false); + + // Move the icon to align with the center-top of the touch point + Point iconShift = new Point(); + iconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x; + iconShift.y = mIconLastTouchPos.y - mContext.getDeviceProfile().iconSizePx; + + mControllers.taskbarDragController.startDragOnLongClick(sv, iconShift); + + return false; + } + } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java new file mode 100644 index 0000000000..158958217f --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java @@ -0,0 +1,117 @@ +/* + * 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.taskbar; + +import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.DEEP_SHORTCUTS; +import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.SHORTCUTS_AND_NOTIFICATIONS; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; + +import android.content.Intent; +import android.content.pm.LauncherApps; +import android.view.KeyEvent; +import android.view.View; + +import com.android.launcher3.BubbleTextView; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.R; +import com.android.launcher3.accessibility.BaseAccessibilityDelegate; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.launcher3.notification.NotificationListener; +import com.android.launcher3.util.ShortcutUtil; +import com.android.quickstep.SystemUiProxy; +import com.android.systemui.shared.system.LauncherAppsCompat; + +import java.util.List; + +/** + * Accessibility delegate for the Taskbar. This provides an accessible interface for taskbar + * features. + */ +public class TaskbarShortcutMenuAccessibilityDelegate + extends BaseAccessibilityDelegate { + + public static final int MOVE_TO_TOP_OR_LEFT = R.id.action_move_to_top_or_left; + public static final int MOVE_TO_BOTTOM_OR_RIGHT = R.id.action_move_to_bottom_or_right; + + private final LauncherApps mLauncherApps; + + public TaskbarShortcutMenuAccessibilityDelegate(TaskbarActivityContext context) { + super(context); + mLauncherApps = context.getSystemService(LauncherApps.class); + + 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)); + mActions.put(MOVE_TO_TOP_OR_LEFT, new LauncherAction( + MOVE_TO_TOP_OR_LEFT, R.string.move_drop_target_top_or_left, KeyEvent.KEYCODE_L)); + mActions.put(MOVE_TO_BOTTOM_OR_RIGHT, new LauncherAction( + MOVE_TO_BOTTOM_OR_RIGHT, + R.string.move_drop_target_bottom_or_right, + KeyEvent.KEYCODE_R)); + } + + @Override + protected void getSupportedActions(View host, ItemInfo item, List out) { + if (ShortcutUtil.supportsShortcuts(item) && FeatureFlags.ENABLE_TASKBAR_POPUP_MENU.get()) { + out.add(mActions.get(NotificationListener.getInstanceIfConnected() != null + ? SHORTCUTS_AND_NOTIFICATIONS : DEEP_SHORTCUTS)); + } + out.add(mActions.get(MOVE_TO_TOP_OR_LEFT)); + out.add(mActions.get(MOVE_TO_BOTTOM_OR_RIGHT)); + } + + @Override + protected boolean performAction(View host, ItemInfo item, int action, boolean fromKeyboard) { + if (item instanceof WorkspaceItemInfo + && (action == MOVE_TO_TOP_OR_LEFT || action == MOVE_TO_BOTTOM_OR_RIGHT)) { + WorkspaceItemInfo info = (WorkspaceItemInfo) item; + int side = action == MOVE_TO_TOP_OR_LEFT + ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT; + + if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { + SystemUiProxy.INSTANCE.get(mContext).startShortcut( + info.getIntent().getPackage(), + info.getDeepShortcutId(), + side, + /* bundleOpts= */ null, + info.user); + } else { + SystemUiProxy.INSTANCE.get(mContext).startIntent( + LauncherAppsCompat.getMainActivityLaunchIntent( + mLauncherApps, + item.getIntent().getComponent(), + /* startActivityOptions= */null, + item.user), + new Intent(), side, null); + } + return true; + } else if (action == DEEP_SHORTCUTS || action == SHORTCUTS_AND_NOTIFICATIONS) { + mContext.showPopupMenuForIcon((BubbleTextView) host); + + return true; + } + return false; + } + + @Override + protected boolean beginAccessibleDrag(View item, ItemInfo info, boolean fromKeyboard) { + return false; + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java index f713dcabcb..f6bc785a14 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java @@ -18,6 +18,8 @@ package com.android.launcher3.taskbar; import android.graphics.Rect; import android.view.View; +import androidx.annotation.CallSuper; + import com.android.launcher3.model.data.ItemInfoWithIcon; import com.android.launcher3.model.data.WorkspaceItemInfo; @@ -33,11 +35,15 @@ public class TaskbarUIController { // Initialized in init. protected TaskbarControllers mControllers; + @CallSuper protected void init(TaskbarControllers taskbarControllers) { mControllers = taskbarControllers; } - protected void onDestroy() { } + @CallSuper + protected void onDestroy() { + mControllers = null; + } protected boolean isTaskbarTouchable() { return true; diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java index b21d677106..19897a1bf8 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java @@ -66,6 +66,12 @@ public final class RecentsViewStateController extends // In Overview, we may be layering app surfaces behind Launcher, so we need to notify // DepthController to prevent optimizations which might occlude the layers behind mLauncher.getDepthController().setHasContentBehindLauncher(state.overviewUi); + + if (isSplitSelectionState(state)) { + mRecentsView.applySplitPrimaryScrollOffset(); + } else { + mRecentsView.resetSplitPrimaryScrollOffset(); + } } @Override @@ -90,9 +96,10 @@ public final class RecentsViewStateController extends LauncherState currentState = mLauncher.getStateManager().getState(); if (isSplitSelectionState(toState) && !isSplitSelectionState(currentState)) { builder.add(mRecentsView.createSplitSelectInitAnimation().buildAnim()); + } + if (isSplitSelectionState(toState)) { mRecentsView.applySplitPrimaryScrollOffset(); - } else if (!isSplitSelectionState(toState) && isSplitSelectionState(currentState)) { - builder.add(mRecentsView.cancelSplitSelect(true).buildAnim()); + } else { mRecentsView.resetSplitPrimaryScrollOffset(); } diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java index 106375a765..4f5f27a2c0 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java +++ b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java @@ -30,11 +30,6 @@ public class SplitScreenSelectState extends OverviewState { super(id); } - @Override - public void onBackPressed(Launcher launcher) { - launcher.getStateManager().goToState(OVERVIEW); - } - @Override public int getVisibleElements(Launcher launcher) { return SPLIT_PLACHOLDER_VIEW; diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java index a9fef41c3f..6c623bcc75 100644 --- a/quickstep/src/com/android/quickstep/SystemUiProxy.java +++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java @@ -599,11 +599,11 @@ public class SystemUiProxy implements ISystemUiProxy, } } - public void startShortcut(String packageName, String shortcutId, int stage, int position, + public void startShortcut(String packageName, String shortcutId, int position, Bundle options, UserHandle user) { if (mSplitScreen != null) { try { - mSplitScreen.startShortcut(packageName, shortcutId, stage, position, options, + mSplitScreen.startShortcut(packageName, shortcutId, position, options, user); } catch (RemoteException e) { Log.w(TAG, "Failed call startShortcut"); @@ -611,11 +611,11 @@ public class SystemUiProxy implements ISystemUiProxy, } } - public void startIntent(PendingIntent intent, Intent fillInIntent, int stage, int position, + public void startIntent(PendingIntent intent, Intent fillInIntent, int position, Bundle options) { if (mSplitScreen != null) { try { - mSplitScreen.startIntent(intent, fillInIntent, stage, position, options); + mSplitScreen.startIntent(intent, fillInIntent, position, options); } catch (RemoteException e) { Log.w(TAG, "Failed call startIntent"); } diff --git a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java index 325ec044fe..a343e0a593 100644 --- a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java +++ b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java @@ -129,9 +129,7 @@ public class FloatingTaskView extends FrameLayout { public void update(RectF position, float progress, float windowRadius) { MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams(); - float dX = mIsRtl - ? position.left - (lp.getMarginStart() - lp.width) - : position.left - lp.getMarginStart(); + float dX = position.left - lp.getMarginStart(); float dY = position.top - lp.topMargin; setTranslationX(dX); @@ -157,16 +155,10 @@ public class FloatingTaskView extends FrameLayout { lp.ignoreInsets = true; // Position the floating view exactly on top of the original lp.topMargin = Math.round(pos.top); - if (mIsRtl) { - lp.setMarginStart(Math.round(mLauncher.getDeviceProfile().widthPx - pos.right)); - } else { - lp.setMarginStart(Math.round(pos.left)); - } + lp.setMarginStart(Math.round(pos.left)); // Set the properties here already to make sure they are available when running the first // animation frame. - int left = mIsRtl - ? mLauncher.getDeviceProfile().widthPx - lp.getMarginStart() - lp.width - : lp.leftMargin; + int left = lp.leftMargin; layout(left, lp.topMargin, left + lp.width, lp.topMargin + lp.height); } diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java index 00f541d4c6..9311261624 100644 --- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java +++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java @@ -157,10 +157,20 @@ public class GroupedTaskView extends TaskView { @Nullable @Override public RunnableList launchTaskAnimated() { - getRecentsView().getSplitPlaceholder().launchTasks(this /*groupedTaskView*/, - null /*callback*/, + if (mTask == null || mSecondaryTask == null) { + return null; + } + + RunnableList endCallback = new RunnableList(); + RecentsView recentsView = getRecentsView(); + // Callbacks run from remote animation when recents animation not currently running + recentsView.getSplitPlaceholder().launchTasks(this /*groupedTaskView*/, + success -> endCallback.executeAllAndDestroy(), false /* freezeTaskList */); - return null; + + // Callbacks get run from recentsView for case when recents animation already running + recentsView.addSideTaskLaunchCallback(endCallback); + return endCallback; } @Override @@ -248,4 +258,10 @@ public class GroupedTaskView extends TaskView { super.updateSnapshotRadius(); mSnapshotView2.setFullscreenParams(mCurrentFullscreenParams); } + + @Override + protected void setIconAndDimTransitionProgress(float progress, boolean invert) { + super.setIconAndDimTransitionProgress(progress, invert); + mIconView2.setAlpha(mIconView.getAlpha()); + } } diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 9cf76b378a..3020dd905d 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -1123,9 +1123,6 @@ public abstract class RecentsView view.getVisibility() != GONE && view != mSplitHiddenTaskView); - - int[] newScroll = new int[getChildCount()]; - getPageScrolls(newScroll, false, SIMPLE_SCROLL_LOGIC); - - boolean needsCurveUpdates = false; - for (int i = mSplitHiddenTaskViewIndex; i >= 0; i--) { - View child = getChildAt(i); - if (child == mSplitHiddenTaskView) { - TaskView taskView = (TaskView) child; - - int dir = mOrientationHandler.getSplitTaskViewDismissDirection(stagePosition, - mActivity.getDeviceProfile()); - FloatProperty dismissingTaskViewTranslate; - Rect hiddenBounds = new Rect(taskView.getLeft(), taskView.getTop(), - taskView.getRight(), taskView.getBottom()); - int distanceDelta = 0; - if (dir == PagedOrientationHandler.SPLIT_TRANSLATE_SECONDARY_NEGATIVE) { - dismissingTaskViewTranslate = taskView - .getSecondaryDissmissTranslationProperty(); - distanceDelta = initialBounds.top - hiddenBounds.top; - taskView.layout(initialBounds.left, hiddenBounds.top, initialBounds.right, - hiddenBounds.bottom); - } else { - dismissingTaskViewTranslate = taskView - .getPrimaryDismissTranslationProperty(); - distanceDelta = initialBounds.left - hiddenBounds.left; - taskView.layout(hiddenBounds.left, initialBounds.top, hiddenBounds.right, - initialBounds.bottom); - if (dir == PagedOrientationHandler.SPLIT_TRANSLATE_PRIMARY_POSITIVE) { - distanceDelta *= -1; - } - } - pendingAnim.add(ObjectAnimator.ofFloat(mSplitHiddenTaskView, - dismissingTaskViewTranslate, - distanceDelta)); - pendingAnim.add(ObjectAnimator.ofFloat(mSplitHiddenTaskView, ALPHA, 1)); - } else { - // If insertion is on last index (furthest from clear all), we directly add the view - // else we translate all views to the right of insertion index further right, - // ignore views to left - if (showAsGrid()) { - // TODO(b/186800707) handle more elegantly for grid - continue; - } - int scrollDiff = newScroll[i] - oldScroll[i]; - if (scrollDiff != 0) { - FloatProperty translationProperty = child instanceof TaskView - ? ((TaskView) child).getPrimaryDismissTranslationProperty() - : mOrientationHandler.getPrimaryViewTranslate(); - - ResourceProvider rp = DynamicResource.provider(mActivity); - SpringProperty sp = new SpringProperty(SpringProperty.FLAG_CAN_SPRING_ON_END) - .setDampingRatio( - rp.getFloat(R.dimen.dismiss_task_trans_x_damping_ratio)) - .setStiffness(rp.getFloat(R.dimen.dismiss_task_trans_x_stiffness)); - pendingAnim.add(ObjectAnimator.ofFloat(child, translationProperty, scrollDiff) - .setDuration(duration), ACCEL, sp); - needsCurveUpdates = true; - } - } - } - - if (needsCurveUpdates) { - pendingAnim.addOnFrameCallback(this::updateCurveProperties); - } - - pendingAnim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - // TODO(b/186800707) Figure out how to undo for grid view - // Need to handle cases where dismissed task is - // * Top Row - // * Bottom Row - // * Focused Task - updateGridProperties(); - resetFromSplitSelectionState(); - updateScrollSynchronously(); - } - }); - - return pendingAnim; - } - /** TODO(b/181707736) More gracefully handle exiting split selection state */ private void resetFromSplitSelectionState() { if (!mActivity.getDeviceProfile().overviewShowAsGrid) { diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java index f7f5efcc13..467d225150 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/src/com/android/quickstep/views/TaskView.java @@ -912,7 +912,7 @@ public class TaskView extends FrameLayout implements Reusable { return deviceProfile.overviewShowAsGrid && !isFocusedTask(); } - private void setIconAndDimTransitionProgress(float progress, boolean invert) { + protected void setIconAndDimTransitionProgress(float progress, boolean invert) { if (invert) { progress = 1 - progress; } diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java new file mode 100644 index 0000000000..ba1a60dd38 --- /dev/null +++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java @@ -0,0 +1,159 @@ +package com.android.launcher3.taskbar; + +import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y; +import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK; +import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME; +import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH; +import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS; +import static com.android.launcher3.taskbar.TaskbarNavButtonController.SCREEN_PIN_LONG_PRESS_THRESHOLD; +import static com.android.quickstep.OverviewCommandHelper.TYPE_HOME; +import static com.android.quickstep.OverviewCommandHelper.TYPE_TOGGLE; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.os.Handler; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.quickstep.OverviewCommandHelper; +import com.android.quickstep.SystemUiProxy; +import com.android.quickstep.TouchInteractionService; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public class TaskbarNavButtonControllerTest { + + private final static int DISPLAY_ID = 2; + + @Mock + SystemUiProxy mockSystemUiProxy; + @Mock + TouchInteractionService mockService; + @Mock + OverviewCommandHelper mockCommandHelper; + @Mock + Handler mockHandler; + + private TaskbarNavButtonController mNavButtonController; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + when(mockService.getDisplayId()).thenReturn(DISPLAY_ID); + when(mockService.getOverviewCommandHelper()).thenReturn(mockCommandHelper); + mNavButtonController = new TaskbarNavButtonController(mockService, + mockSystemUiProxy, mockHandler); + } + + @Test + public void testPressBack() { + mNavButtonController.onButtonClick(BUTTON_BACK); + verify(mockSystemUiProxy, times(1)).onBackPressed(); + } + + @Test + public void testPressImeSwitcher() { + mNavButtonController.onButtonClick(BUTTON_IME_SWITCH); + verify(mockSystemUiProxy, times(1)).onImeSwitcherPressed(); + } + + @Test + public void testPressA11yShortClick() { + mNavButtonController.onButtonClick(BUTTON_A11Y); + verify(mockSystemUiProxy, times(1)) + .notifyAccessibilityButtonClicked(DISPLAY_ID); + } + + @Test + public void testPressA11yLongClick() { + mNavButtonController.onButtonLongClick(BUTTON_A11Y); + verify(mockSystemUiProxy, times(1)).notifyAccessibilityButtonLongClicked(); + } + + @Test + public void testLongPressHome() { + mNavButtonController.onButtonLongClick(BUTTON_HOME); + verify(mockSystemUiProxy, times(1)).startAssistant(any()); + } + + @Test + public void testPressHome() { + mNavButtonController.onButtonClick(BUTTON_HOME); + verify(mockCommandHelper, times(1)).addCommand(TYPE_HOME); + } + + @Test + public void testPressRecents() { + mNavButtonController.onButtonClick(BUTTON_RECENTS); + verify(mockCommandHelper, times(1)).addCommand(TYPE_TOGGLE); + } + + @Test + public void testPressRecentsWithScreenPinned() { + mNavButtonController.updateSysuiFlags(SYSUI_STATE_SCREEN_PINNING); + mNavButtonController.onButtonClick(BUTTON_RECENTS); + verify(mockCommandHelper, times(0)).addCommand(TYPE_TOGGLE); + } + + @Test + public void testLongPressBackRecentsNotPinned() { + mNavButtonController.onButtonLongClick(BUTTON_RECENTS); + mNavButtonController.onButtonLongClick(BUTTON_BACK); + verify(mockSystemUiProxy, times(0)).stopScreenPinning(); + } + + @Test + public void testLongPressBackRecentsPinned() { + mNavButtonController.updateSysuiFlags(SYSUI_STATE_SCREEN_PINNING); + mNavButtonController.onButtonLongClick(BUTTON_RECENTS); + mNavButtonController.onButtonLongClick(BUTTON_BACK); + verify(mockSystemUiProxy, times(1)).stopScreenPinning(); + } + + @Test + public void testLongPressBackRecentsTooLongPinned() { + mNavButtonController.updateSysuiFlags(SYSUI_STATE_SCREEN_PINNING); + mNavButtonController.onButtonLongClick(BUTTON_RECENTS); + try { + Thread.sleep(SCREEN_PIN_LONG_PRESS_THRESHOLD + 5); + } catch (InterruptedException e) { + e.printStackTrace(); + } + mNavButtonController.onButtonLongClick(BUTTON_BACK); + verify(mockSystemUiProxy, times(0)).stopScreenPinning(); + } + + @Test + public void testLongPressBackRecentsMultipleAttemptPinned() { + mNavButtonController.updateSysuiFlags(SYSUI_STATE_SCREEN_PINNING); + mNavButtonController.onButtonLongClick(BUTTON_RECENTS); + try { + Thread.sleep(SCREEN_PIN_LONG_PRESS_THRESHOLD + 5); + } catch (InterruptedException e) { + e.printStackTrace(); + } + mNavButtonController.onButtonLongClick(BUTTON_BACK); + verify(mockSystemUiProxy, times(0)).stopScreenPinning(); + + // Try again w/in threshold + mNavButtonController.onButtonLongClick(BUTTON_RECENTS); + mNavButtonController.onButtonLongClick(BUTTON_BACK); + verify(mockSystemUiProxy, times(1)).stopScreenPinning(); + } + + @Test + public void testLongPressHomeScreenPinned() { + mNavButtonController.updateSysuiFlags(SYSUI_STATE_SCREEN_PINNING); + mNavButtonController.onButtonLongClick(BUTTON_HOME); + verify(mockSystemUiProxy, times(0)).startAssistant(any()); + } +} diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 5a6365adf9..f01696517b 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -48,7 +48,7 @@ import android.widget.TextView; import androidx.annotation.Nullable; import androidx.annotation.UiThread; -import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; +import com.android.launcher3.accessibility.BaseAccessibilityDelegate; import com.android.launcher3.dot.DotInfo; import com.android.launcher3.dragndrop.DraggableView; import com.android.launcher3.folder.FolderIcon; @@ -296,7 +296,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, @Override public void setAccessibilityDelegate(AccessibilityDelegate delegate) { - if (delegate instanceof LauncherAccessibilityDelegate) { + if (delegate instanceof BaseAccessibilityDelegate) { super.setAccessibilityDelegate(delegate); } else { // NO-OP diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 0656125c90..ab9b980aa3 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -115,8 +115,8 @@ import androidx.annotation.StringRes; import androidx.annotation.VisibleForTesting; import com.android.launcher3.DropTarget.DragObject; +import com.android.launcher3.accessibility.BaseAccessibilityDelegate.LauncherAction; 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; diff --git a/src/com/android/launcher3/accessibility/BaseAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/BaseAccessibilityDelegate.java new file mode 100644 index 0000000000..14b2431662 --- /dev/null +++ b/src/com/android/launcher3/accessibility/BaseAccessibilityDelegate.java @@ -0,0 +1,192 @@ +/* + * 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.accessibility; + +import android.content.Context; +import android.graphics.Rect; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.SparseArray; +import android.view.View; +import android.view.accessibility.AccessibilityNodeInfo; + +import com.android.launcher3.DropTarget; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.dragndrop.DragController; +import com.android.launcher3.dragndrop.DragOptions; +import com.android.launcher3.model.data.FolderInfo; +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.popup.PopupContainerWithArrow; +import com.android.launcher3.util.Thunk; +import com.android.launcher3.views.ActivityContext; + +import java.util.ArrayList; +import java.util.List; + +public abstract class BaseAccessibilityDelegate + extends View.AccessibilityDelegate implements DragController.DragListener { + + public enum DragType { + ICON, + FOLDER, + WIDGET + } + + public static class DragInfo { + public DragType dragType; + public ItemInfo info; + public View item; + } + + protected final SparseArray mActions = new SparseArray<>(); + protected final T mContext; + + protected DragInfo mDragInfo = null; + + protected BaseAccessibilityDelegate(T context) { + mContext = context; + } + + @Override + public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(host, info); + 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(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK); + } + } + } + + /** + * Adds all the accessibility actions that can be handled. + */ + protected abstract void getSupportedActions(View host, ItemInfo item, List out); + + private boolean itemSupportsLongClick(View host, ItemInfo info) { + return PopupContainerWithArrow.canShow(host, info); + } + + protected boolean itemSupportsAccessibleDrag(ItemInfo item) { + if (item instanceof WorkspaceItemInfo) { + // Support the action unless the item is in a context menu. + return item.screenId >= 0 + && item.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION; + } + return (item instanceof LauncherAppWidgetInfo) + || (item instanceof FolderInfo); + } + + @Override + public boolean performAccessibilityAction(View host, int action, Bundle args) { + if ((host.getTag() instanceof ItemInfo) + && performAction(host, (ItemInfo) host.getTag(), action, false)) { + return true; + } + return super.performAccessibilityAction(host, action, args); + } + + protected abstract boolean performAction( + View host, ItemInfo item, int action, boolean fromKeyboard); + + @Thunk + protected void announceConfirmation(String confirmation) { + mContext.getDragLayer().announceForAccessibility(confirmation); + + } + + public boolean isInAccessibleDrag() { + return mDragInfo != null; + } + + public DragInfo getDragInfo() { + return mDragInfo; + } + + /** + * @param clickedTarget the actual view that was clicked + * @param dropLocation relative to {@param clickedTarget}. If provided, its center is used + * as the actual drop location otherwise the views center is used. + */ + public void handleAccessibleDrop(View clickedTarget, Rect dropLocation, + String confirmation) { + if (!isInAccessibleDrag()) return; + + int[] loc = new int[2]; + if (dropLocation == null) { + loc[0] = clickedTarget.getWidth() / 2; + loc[1] = clickedTarget.getHeight() / 2; + } else { + loc[0] = dropLocation.centerX(); + loc[1] = dropLocation.centerY(); + } + + mContext.getDragLayer().getDescendantCoordRelativeToSelf(clickedTarget, loc); + mContext.getDragController().completeAccessibleDrag(loc); + + if (!TextUtils.isEmpty(confirmation)) { + announceConfirmation(confirmation); + } + } + + protected abstract boolean beginAccessibleDrag(View item, ItemInfo info, boolean fromKeyboard); + + + @Override + public void onDragEnd() { + mContext.getDragController().removeDragListener(this); + mDragInfo = null; + } + + @Override + public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { + // No-op + } + + public class LauncherAction { + public final int keyCode; + public final AccessibilityNodeInfo.AccessibilityAction accessibilityAction; + + private final BaseAccessibilityDelegate mDelegate; + + public LauncherAction(int id, int labelRes, int keyCode) { + this.keyCode = keyCode; + accessibilityAction = new AccessibilityNodeInfo.AccessibilityAction( + id, mContext.getString(labelRes)); + mDelegate = BaseAccessibilityDelegate.this; + } + + /** + * Invokes the action for the provided host + */ + 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/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java index 157df5d070..18c05ebd43 100644 --- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java @@ -10,27 +10,19 @@ import android.appwidget.AppWidgetProviderInfo; 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; -import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import com.android.launcher3.BubbleTextView; 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.Favorites; import com.android.launcher3.PendingAddItemInfo; import com.android.launcher3.R; import com.android.launcher3.Workspace; -import com.android.launcher3.dragndrop.DragController.DragListener; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.dragndrop.DragView; import com.android.launcher3.folder.Folder; @@ -57,7 +49,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -public class LauncherAccessibilityDelegate extends AccessibilityDelegate implements DragListener { +public class LauncherAccessibilityDelegate extends BaseAccessibilityDelegate { private static final String TAG = "LauncherAccessibilityDelegate"; @@ -73,25 +65,8 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme public static final int DEEP_SHORTCUTS = R.id.action_deep_shortcuts; public static final int SHORTCUTS_AND_NOTIFICATIONS = R.id.action_shortcuts_and_notifications; - public enum DragType { - ICON, - FOLDER, - WIDGET - } - - public static class DragInfo { - public DragType dragType; - public ItemInfo info; - public View item; - } - - protected final SparseArray mActions = new SparseArray<>(); - protected final Launcher mLauncher; - - private DragInfo mDragInfo = null; - public LauncherAccessibilityDelegate(Launcher launcher) { - mLauncher = launcher; + super(launcher); mActions.put(REMOVE, new LauncherAction( REMOVE, R.string.remove_drop_target_label, KeyEvent.KEYCODE_X)); @@ -116,25 +91,6 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme } @Override - public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(host, info); - 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); - } - } - } - - /** - * 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 @@ -143,7 +99,7 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme ? SHORTCUTS_AND_NOTIFICATIONS : DEEP_SHORTCUTS)); } - for (ButtonDropTarget target : mLauncher.getDropTargetBar().getDropTargets()) { + for (ButtonDropTarget target : mContext.getDropTargetBar().getDropTargets()) { if (target.supportsAccessibilityDrop(item, host)) { out.add(mActions.get(target.getAccessibilityAction())); } @@ -183,31 +139,7 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme return result; } - private boolean itemSupportsLongClick(View host, ItemInfo info) { - return PopupContainerWithArrow.canShow(host, info); - } - - private boolean itemSupportsAccessibleDrag(ItemInfo item) { - if (item instanceof WorkspaceItemInfo) { - // Support the action unless the item is in a context menu. - return item.screenId >= 0 && item.container != Favorites.CONTAINER_HOTSEAT_PREDICTION; - } - return (item instanceof LauncherAppWidgetInfo) - || (item instanceof FolderInfo); - } - @Override - public boolean performAccessibilityAction(View host, int action, Bundle args) { - if ((host.getTag() instanceof ItemInfo) - && performAction(host, (ItemInfo) host.getTag(), action, false)) { - return true; - } - return super.performAccessibilityAction(host, action, args); - } - - /** - * Performs the provided action on the host - */ protected boolean performAction(final View host, final ItemInfo item, int action, boolean fromKeyboard) { if (action == ACTION_LONG_CLICK) { @@ -226,36 +158,36 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme if (screenId == -1) { return false; } - mLauncher.getStateManager().goToState(NORMAL, true, forSuccessCallback(() -> { + mContext.getStateManager().goToState(NORMAL, true, forSuccessCallback(() -> { if (item instanceof AppInfo) { WorkspaceItemInfo info = ((AppInfo) item).makeWorkspaceItem(); - mLauncher.getModelWriter().addItemToDatabase(info, + mContext.getModelWriter().addItemToDatabase(info, Favorites.CONTAINER_DESKTOP, screenId, coordinates[0], coordinates[1]); - mLauncher.bindItems( + mContext.bindItems( Collections.singletonList(info), /* forceAnimateIcons= */ true, /* focusFirstItemForAccessibility= */ true); announceConfirmation(R.string.item_added_to_workspace); } else if (item instanceof PendingAddItemInfo) { PendingAddItemInfo info = (PendingAddItemInfo) item; - Workspace workspace = mLauncher.getWorkspace(); + Workspace workspace = mContext.getWorkspace(); workspace.snapToPage(workspace.getPageIndexForScreenId(screenId)); - mLauncher.addPendingItem(info, Favorites.CONTAINER_DESKTOP, + mContext.addPendingItem(info, Favorites.CONTAINER_DESKTOP, screenId, coordinates, info.spanX, info.spanY); } else if (item instanceof WorkspaceItemInfo) { WorkspaceItemInfo info = ((WorkspaceItemInfo) item).clone(); - mLauncher.getModelWriter().addItemToDatabase(info, + mContext.getModelWriter().addItemToDatabase(info, Favorites.CONTAINER_DESKTOP, screenId, coordinates[0], coordinates[1]); - mLauncher.bindItems(Collections.singletonList(info), true, true); + mContext.bindItems(Collections.singletonList(info), true, true); } })); return true; } else if (action == MOVE_TO_WORKSPACE) { - Folder folder = Folder.getOpen(mLauncher); + Folder folder = Folder.getOpen(mContext); folder.close(true); WorkspaceItemInfo info = (WorkspaceItemInfo) item; folder.getInfo().remove(info, false); @@ -265,14 +197,14 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme if (screenId == -1) { return false; } - mLauncher.getModelWriter().moveItemInDatabase(info, + mContext.getModelWriter().moveItemInDatabase(info, 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(() -> { - mLauncher.bindItems(Collections.singletonList(item), true); + mContext.bindItems(Collections.singletonList(item), true); announceConfirmation(R.string.item_moved); }); return true; @@ -280,15 +212,15 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) item; List actions = getSupportedResizeActions(host, info); Rect pos = new Rect(); - mLauncher.getDragLayer().getDescendantRectRelativeToSelf(host, pos); - ArrowPopup popup = OptionsPopupView.show(mLauncher, new RectF(pos), actions, false); + mContext.getDragLayer().getDescendantRectRelativeToSelf(host, pos); + ArrowPopup popup = OptionsPopupView.show(mContext, new RectF(pos), actions, false); popup.requestFocus(); popup.setOnCloseCallback(host::requestFocus); return true; } else if (action == DEEP_SHORTCUTS || action == SHORTCUTS_AND_NOTIFICATIONS) { return PopupContainerWithArrow.showForIcon((BubbleTextView) host) != null; } else { - for (ButtonDropTarget dropTarget : mLauncher.getDropTargetBar().getDropTargets()) { + for (ButtonDropTarget dropTarget : mContext.getDropTargetBar().getDropTargets()) { if (dropTarget.supportsAccessibilityDrop(item, host) && action == dropTarget.getAccessibilityAction()) { dropTarget.onAccessibilityDrop(host, item); @@ -315,7 +247,7 @@ 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(new OptionItem(mLauncher, + actions.add(new OptionItem(mContext, R.string.action_increase_width, R.drawable.ic_widget_width_increase, IGNORE, @@ -323,7 +255,7 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme } if (info.spanX > info.minSpanX && info.spanX > 1) { - actions.add(new OptionItem(mLauncher, + actions.add(new OptionItem(mContext, R.string.action_decrease_width, R.drawable.ic_widget_width_decrease, IGNORE, @@ -334,7 +266,7 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme 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(new OptionItem(mLauncher, + actions.add(new OptionItem(mContext, R.string.action_increase_height, R.drawable.ic_widget_height_increase, IGNORE, @@ -342,7 +274,7 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme } if (info.spanY > info.minSpanY && info.spanY > 1) { - actions.add(new OptionItem(mLauncher, + actions.add(new OptionItem(mContext, R.string.action_decrease_height, R.drawable.ic_widget_height_decrease, IGNORE, @@ -382,58 +314,20 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme } layout.markCellsAsOccupiedForView(host); - WidgetSizes.updateWidgetSizeRanges(((LauncherAppWidgetHostView) host), mLauncher, + WidgetSizes.updateWidgetSizeRanges(((LauncherAppWidgetHostView) host), mContext, info.spanX, info.spanY); host.requestLayout(); - mLauncher.getModelWriter().updateItemInDatabase(info); - announceConfirmation(mLauncher.getString(R.string.widget_resized, info.spanX, info.spanY)); + mContext.getModelWriter().updateItemInDatabase(info); + announceConfirmation(mContext.getString(R.string.widget_resized, info.spanX, info.spanY)); return true; } @Thunk void announceConfirmation(int resId) { - announceConfirmation(mLauncher.getResources().getString(resId)); + announceConfirmation(mContext.getResources().getString(resId)); } - @Thunk void announceConfirmation(String confirmation) { - mLauncher.getDragLayer().announceForAccessibility(confirmation); - - } - - public boolean isInAccessibleDrag() { - return mDragInfo != null; - } - - public DragInfo getDragInfo() { - return mDragInfo; - } - - /** - * @param clickedTarget the actual view that was clicked - * @param dropLocation relative to {@param clickedTarget}. If provided, its center is used - * as the actual drop location otherwise the views center is used. - */ - public void handleAccessibleDrop(View clickedTarget, Rect dropLocation, - String confirmation) { - if (!isInAccessibleDrag()) return; - - int[] loc = new int[2]; - if (dropLocation == null) { - loc[0] = clickedTarget.getWidth() / 2; - loc[1] = clickedTarget.getHeight() / 2; - } else { - loc[0] = dropLocation.centerX(); - loc[1] = dropLocation.centerY(); - } - - mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(clickedTarget, loc); - mLauncher.getDragController().completeAccessibleDrag(loc); - - if (!TextUtils.isEmpty(confirmation)) { - announceConfirmation(confirmation); - } - } - - private boolean beginAccessibleDrag(View item, ItemInfo info, boolean fromKeyboard) { + @Override + protected boolean beginAccessibleDrag(View item, ItemInfo info, boolean fromKeyboard) { if (!itemSupportsAccessibleDrag(info)) { return false; } @@ -449,8 +343,8 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme } Rect pos = new Rect(); - mLauncher.getDragLayer().getDescendantRectRelativeToSelf(item, pos); - mLauncher.getDragController().addDragListener(this); + mContext.getDragLayer().getDescendantRectRelativeToSelf(item, pos); + mContext.getDragController().addDragListener(this); DragOptions options = new DragOptions(); options.isAccessibleDrag = true; @@ -458,31 +352,20 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme options.simulatedDndStartPoint = new Point(pos.centerX(), pos.centerY()); if (fromKeyboard) { - KeyboardDragAndDropView popup = (KeyboardDragAndDropView) mLauncher.getLayoutInflater() - .inflate(R.layout.keyboard_drag_and_drop, mLauncher.getDragLayer(), false); + KeyboardDragAndDropView popup = (KeyboardDragAndDropView) mContext.getLayoutInflater() + .inflate(R.layout.keyboard_drag_and_drop, mContext.getDragLayer(), false); popup.showForIcon(item, info, options); } else { - ItemLongClickListener.beginDrag(item, mLauncher, info, options); + ItemLongClickListener.beginDrag(item, mContext, info, options); } return true; } - @Override - public void onDragStart(DragObject dragObject, DragOptions options) { - // No-op - } - - @Override - public void onDragEnd() { - mLauncher.getDragController().removeDragListener(this); - mDragInfo = null; - } - /** * Find empty space on the workspace and returns the screenId. */ protected int findSpaceOnWorkspace(ItemInfo info, int[] outCoordinates) { - Workspace workspace = mLauncher.getWorkspace(); + Workspace workspace = mContext.getWorkspace(); IntArray workspaceScreens = workspace.getScreenOrder(); int screenId; @@ -520,29 +403,4 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme } return screenId; } - - 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; - } - - /** - * Invokes the action for the provided host - */ - 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 bf5a24b65b..fb847ec9ae 100644 --- a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java @@ -71,12 +71,12 @@ public class ShortcutMenuAccessibilityDelegate extends LauncherAccessibilityDele if (screenId == -1) { return false; } - mLauncher.getStateManager().goToState(NORMAL, true, forSuccessCallback(() -> { - mLauncher.getModelWriter().addItemToDatabase(info, + mContext.getStateManager().goToState(NORMAL, true, forSuccessCallback(() -> { + mContext.getModelWriter().addItemToDatabase(info, LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId, coordinates[0], coordinates[1]); - mLauncher.bindItems(Collections.singletonList(info), true); - AbstractFloatingView.closeAllOpenViews(mLauncher); + mContext.bindItems(Collections.singletonList(info), true); + AbstractFloatingView.closeAllOpenViews(mContext); announceConfirmation(R.string.item_added_to_workspace); })); return true; diff --git a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java index a331924f22..a8624dd17d 100644 --- a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java +++ b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java @@ -22,7 +22,7 @@ import android.view.View; import com.android.launcher3.CellLayout; import com.android.launcher3.R; -import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.DragType; +import com.android.launcher3.accessibility.BaseAccessibilityDelegate.DragType; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.ItemInfo; diff --git a/src/com/android/launcher3/popup/LauncherPopupLiveUpdateHandler.java b/src/com/android/launcher3/popup/LauncherPopupLiveUpdateHandler.java index 731f439d6c..3e3f633567 100644 --- a/src/com/android/launcher3/popup/LauncherPopupLiveUpdateHandler.java +++ b/src/com/android/launcher3/popup/LauncherPopupLiveUpdateHandler.java @@ -18,6 +18,7 @@ package com.android.launcher3.popup; import android.view.View; import android.view.ViewGroup; +import com.android.launcher3.BubbleTextView; import com.android.launcher3.Launcher; import com.android.launcher3.R; import com.android.launcher3.model.data.ItemInfo; @@ -86,4 +87,9 @@ public class LauncherPopupLiveUpdateHandler extends PopupLiveUpdateHandler }; } + public void setPopupItemDragHandler(PopupItemDragHandler popupItemDragHandler) { + mPopupItemDragHandler = popupItemDragHandler; + } + public PopupItemDragHandler getItemDragHandler() { return mPopupItemDragHandler; } diff --git a/src/com/android/launcher3/popup/PopupLiveUpdateHandler.java b/src/com/android/launcher3/popup/PopupLiveUpdateHandler.java index 194c22faf0..c5d545225c 100644 --- a/src/com/android/launcher3/popup/PopupLiveUpdateHandler.java +++ b/src/com/android/launcher3/popup/PopupLiveUpdateHandler.java @@ -20,6 +20,7 @@ import static android.view.View.GONE; import android.content.Context; import android.view.View; +import com.android.launcher3.BubbleTextView; import com.android.launcher3.dot.DotInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.notification.NotificationContainer; @@ -36,7 +37,7 @@ import java.util.function.Predicate; * * @param The activity on which the popup shows */ -public class PopupLiveUpdateHandler implements +public abstract class PopupLiveUpdateHandler implements PopupDataProvider.PopupDataChangeListener, View.OnAttachStateChangeListener { protected final T mContext; @@ -103,6 +104,8 @@ public class PopupLiveUpdateHandler impleme @Override public void onSystemShortcutsUpdated() { mPopupContainerWithArrow.close(true); - PopupContainerWithArrow.showForIcon(mPopupContainerWithArrow.getOriginalIcon()); + showPopupContainerForIcon(mPopupContainerWithArrow.getOriginalIcon()); } + + protected abstract void showPopupContainerForIcon(BubbleTextView originalIcon); } diff --git a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java index cecbb0db13..c166bfc83a 100644 --- a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java +++ b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java @@ -23,12 +23,12 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.view.View; -import com.android.launcher3.Launcher; import com.android.launcher3.Utilities; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.graphics.DragPreviewProvider; import com.android.launcher3.icons.BitmapRenderer; import com.android.launcher3.icons.FastBitmapDrawable; +import com.android.launcher3.views.ActivityContext; /** * Extension of {@link DragPreviewProvider} which generates bitmaps scaled to the default icon size. @@ -45,7 +45,8 @@ public class ShortcutDragPreviewProvider extends DragPreviewProvider { @Override public Drawable createDrawable() { if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) { - int size = Launcher.getLauncher(mView.getContext()).getDeviceProfile().iconSizePx; + int size = ActivityContext.lookupContext(mView.getContext()) + .getDeviceProfile().iconSizePx; return new FastBitmapDrawable( BitmapRenderer.createHardwareBitmap( size + blurSizeOutline, @@ -59,7 +60,7 @@ public class ShortcutDragPreviewProvider extends DragPreviewProvider { private Bitmap createDragBitmapLegacy() { Drawable d = mView.getBackground(); Rect bounds = getDrawableBounds(d); - int size = Launcher.getLauncher(mView.getContext()).getDeviceProfile().iconSizePx; + int size = ActivityContext.lookupContext(mView.getContext()).getDeviceProfile().iconSizePx; final Bitmap b = Bitmap.createBitmap( size + blurSizeOutline, size + blurSizeOutline, @@ -84,9 +85,9 @@ public class ShortcutDragPreviewProvider extends DragPreviewProvider { @Override public float getScaleAndPosition(Drawable preview, int[] outPos) { - Launcher launcher = Launcher.getLauncher(mView.getContext()); + ActivityContext context = ActivityContext.lookupContext(mView.getContext()); int iconSize = getDrawableBounds(mView.getBackground()).width(); - float scale = launcher.getDragLayer().getLocationInDragLayer(mView, outPos); + float scale = context.getDragLayer().getLocationInDragLayer(mView, outPos); int iconLeft = mView.getPaddingStart(); if (Utilities.isRtl(mView.getResources())) { @@ -98,7 +99,7 @@ public class ShortcutDragPreviewProvider extends DragPreviewProvider { + mPositionShift.x); outPos[1] += Math.round((scale * mView.getHeight() - preview.getIntrinsicHeight()) / 2 + mPositionShift.y); - float size = launcher.getDeviceProfile().iconSizePx; + float size = context.getDeviceProfile().iconSizePx; return scale * iconSize / size; } } diff --git a/src/com/android/launcher3/views/Snackbar.java b/src/com/android/launcher3/views/Snackbar.java index 49fcd2ebde..f945819c8d 100644 --- a/src/com/android/launcher3/views/Snackbar.java +++ b/src/com/android/launcher3/views/Snackbar.java @@ -28,8 +28,9 @@ import android.view.Gravity; import android.view.MotionEvent; import android.widget.TextView; +import androidx.annotation.Nullable; + import com.android.launcher3.AbstractFloatingView; -import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.R; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.compat.AccessibilityManagerCompat; @@ -44,7 +45,7 @@ public class Snackbar extends AbstractFloatingView { private static final long HIDE_DURATION_MS = 180; private static final int TIMEOUT_DURATION_MS = 4000; - private final BaseDraggingActivity mActivity; + private final ActivityContext mActivity; private Runnable mOnDismissed; public Snackbar(Context context, AttributeSet attrs) { @@ -53,12 +54,19 @@ public class Snackbar extends AbstractFloatingView { public Snackbar(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - mActivity = BaseDraggingActivity.fromContext(context); + mActivity = ActivityContext.lookupContext(context); inflate(context, R.layout.snackbar, this); } - public static void show(BaseDraggingActivity activity, int labelStringResId, - int actionStringResId, Runnable onDismissed, Runnable onActionClicked) { + /** Show a snackbar with just a label. */ + public static void show(T activity, int labelStringRedId, + Runnable onDismissed) { + show(activity, labelStringRedId, NO_ID, onDismissed, null); + } + + /** Show a snackbar with a label and action. */ + public static void show(T activity, int labelStringResId, + int actionStringResId, Runnable onDismissed, @Nullable Runnable onActionClicked) { closeOpenViews(activity, true, TYPE_SNACKBAR); Snackbar snackbar = new Snackbar(activity, null); // Set some properties here since inflated xml only contains the children. @@ -87,13 +95,30 @@ public class Snackbar extends AbstractFloatingView { params.setMargins(0, 0, 0, marginBottom + insets.bottom); TextView labelView = snackbar.findViewById(R.id.label); - TextView actionView = snackbar.findViewById(R.id.action); String labelText = res.getString(labelStringResId); - String actionText = res.getString(actionStringResId); - int totalContentWidth = (int) (labelView.getPaint().measureText(labelText) - + actionView.getPaint().measureText(actionText)) + labelView.setText(labelText); + + TextView actionView = snackbar.findViewById(R.id.action); + float actionWidth; + if (actionStringResId != NO_ID) { + String actionText = res.getString(actionStringResId); + actionWidth = actionView.getPaint().measureText(actionText) + + actionView.getPaddingRight() + actionView.getPaddingLeft(); + actionView.setText(actionText); + actionView.setOnClickListener(v -> { + if (onActionClicked != null) { + onActionClicked.run(); + } + snackbar.mOnDismissed = null; + snackbar.close(true); + }); + } else { + actionWidth = 0; + actionView.setVisibility(GONE); + } + + int totalContentWidth = (int) (labelView.getPaint().measureText(labelText) + actionWidth) + labelView.getPaddingRight() + labelView.getPaddingLeft() - + actionView.getPaddingRight() + actionView.getPaddingLeft() + padding * 2; if (totalContentWidth > params.width) { // The text doesn't fit in our standard width so update width to accommodate. @@ -113,17 +138,8 @@ public class Snackbar extends AbstractFloatingView { params.width = maxWidth; } } - labelView.setText(labelText); - actionView.setText(actionText); - actionView.setOnClickListener(v -> { - if (onActionClicked != null) { - onActionClicked.run(); - } - snackbar.mOnDismissed = null; - snackbar.close(true); - }); - snackbar.mOnDismissed = onDismissed; + snackbar.mOnDismissed = onDismissed; snackbar.setAlpha(0); snackbar.setScaleX(0.8f); snackbar.setScaleY(0.8f); diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java index 6e7264ac0b..0bac2ca258 100644 --- a/tests/tapl/com/android/launcher3/tapl/Widgets.java +++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java @@ -77,7 +77,8 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer { mLauncher.scroll( widgetsContainer, Direction.UP, - new Rect(0, 0, mLauncher.getVisibleBounds(widgetsContainer).width(), 0), + new Rect(0, 0, mLauncher.getRightGestureMarginInContainer(widgetsContainer) + 1, + 0), FLING_STEPS, false); try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("flung back")) { verifyActiveContainer();