Snap for 7961093 from eba2a6aa8a to tm-release

Change-Id: I2e90cfb4725790808fbd3f645ada61a41a622ac4
This commit is contained in:
Android Build Coastguard Worker
2021-12-02 02:16:48 +00:00
33 changed files with 819 additions and 368 deletions
+4
View File
@@ -39,4 +39,8 @@
<string name="wellbeing_provider_pkg" translatable="false"/>
<integer name="max_depth_blur_radius">23</integer>
<!-- Accessibility actions -->
<item type="id" name="action_move_to_top_or_left" />
<item type="id" name="action_move_to_bottom_or_right" />
</resources>
+5
View File
@@ -233,4 +233,9 @@
<string name="taskbar_edu_close">Close</string>
<!-- Text on button to finish a tutorial [CHAR_LIMIT=16] -->
<string name="taskbar_edu_done">Done</string>
<!-- Label for moving drop target to the top or left side of the screen, depending on orientation (from the taskbar only). -->
<string name="move_drop_target_top_or_left">Move to top&#47;left</string>
<!-- Label for moving drop target to the bottom or right side of the screen, depending on orientation (from the taskbar only). -->
<string name="move_drop_target_bottom_or_right">Move to bottom&#47;right</string>
</resources>
@@ -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;
@@ -60,6 +60,7 @@ public class FallbackTaskbarUIController extends TaskbarUIController {
@Override
protected void onDestroy() {
super.onDestroy();
mRecentsActivity.setTaskbarUIController(null);
mRecentsActivity.getStateManager().removeStateListener(mStateListener);
}
@@ -99,6 +99,7 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
@Override
protected void onDestroy() {
super.onDestroy();
onLauncherResumedOrPaused(false);
mTaskbarLauncherStateController.onDestroy();
@@ -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
@@ -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));
}
}
@@ -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<TaskbarActivityContext
* @return Whether {@link View#startDragAndDrop} started successfully.
*/
protected boolean startDragOnLongClick(View view) {
return startDragOnLongClick(view, null, null);
}
protected boolean startDragOnLongClick(
DeepShortcutView shortcutView, Point iconShift) {
return startDragOnLongClick(
shortcutView.getBubbleText(),
new ShortcutDragPreviewProvider(shortcutView.getIconView(), iconShift),
iconShift);
}
private boolean startDragOnLongClick(
View view,
@Nullable DragPreviewProvider dragPreviewProvider,
@Nullable Point iconShift) {
if (!(view instanceof BubbleTextView)) {
return false;
}
@@ -96,7 +113,10 @@ public class TaskbarDragController extends DragController<TaskbarActivityContext
mActivity.setTaskbarWindowFullscreen(true);
btv.post(() -> {
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<TaskbarActivityContext
return true;
}
private void startInternalDrag(BubbleTextView btv) {
private DragView startInternalDrag(
BubbleTextView btv, @Nullable DragPreviewProvider dragPreviewProvider) {
float iconScale = btv.getIcon().getAnimatedScale();
// Clear the pressed state if necessary
@@ -112,7 +133,8 @@ public class TaskbarDragController extends DragController<TaskbarActivityContext
btv.setPressed(false);
btv.clearPressedBackground();
final DragPreviewProvider previewProvider = new DragPreviewProvider(btv);
final DragPreviewProvider previewProvider = dragPreviewProvider == null
? new DragPreviewProvider(btv) : dragPreviewProvider;
final Drawable drawable = previewProvider.createDrawable();
final float scale = previewProvider.getScaleAndPosition(drawable, mTempXY);
int dragLayerX = mTempXY[0];
@@ -149,7 +171,7 @@ public class TaskbarDragController extends DragController<TaskbarActivityContext
}
}
startDrag(
return startDrag(
drawable,
/* view = */ null,
/* originalView = */ btv,
@@ -241,7 +263,8 @@ public class TaskbarDragController extends DragController<TaskbarActivityContext
@Override
public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
shadowSize.set(mDragIconSize, mDragIconSize);
int iconSize = Math.max(mDragIconSize, btv.getWidth());
shadowSize.set(iconSize, iconSize);
// The registration point was taken before the icon scaled to mDragIconSize, so
// offset the registration to where the touch is on the new size.
int offsetX = (mDragIconSize - mDragObject.dragView.getDragRegionWidth()) / 2;
@@ -273,6 +296,12 @@ public class TaskbarDragController extends DragController<TaskbarActivityContext
});
intent = new Intent();
if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
intent.putExtra(ClipDescriptionCompat.EXTRA_PENDING_INTENT,
launcherApps.getShortcutIntent(
item.getIntent().getPackage(),
item.getDeepShortcutId(),
null,
item.user));
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, item.getIntent().getPackage());
intent.putExtra(Intent.EXTRA_SHORTCUT_ID, item.getDeepShortcutId());
} else {
@@ -30,8 +30,8 @@ import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.hardware.display.DisplayManager;
import android.net.Uri;
import android.os.Handler;
import android.provider.Settings;
import android.util.Log;
import android.view.Display;
import androidx.annotation.NonNull;
@@ -94,7 +94,8 @@ public class TaskbarManager implements DisplayController.DisplayInfoChangeListen
Display display =
service.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
mContext = service.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null);
mNavButtonController = new TaskbarNavButtonController(service);
mNavButtonController = new TaskbarNavButtonController(service,
SystemUiProxy.INSTANCE.get(mContext), new Handler());
mUserSetupCompleteListener = isUserSetupComplete -> recreateTaskbar();
mComponentCallbacks = new ComponentCallbacks() {
private Configuration mOldConfig = mContext.getResources().getConfiguration();
@@ -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);
}
}
@@ -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<TaskbarActivityContext>(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;
}
}
}
@@ -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<TaskbarActivityContext> {
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<LauncherAction> 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;
}
}
@@ -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;
@@ -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();
}
@@ -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;
@@ -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");
}
@@ -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);
}
@@ -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());
}
}
@@ -1123,9 +1123,6 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
// Reset the running task when leaving overview since it can still have a reference to
// its thumbnail
mTmpRunningTasks = null;
if (mSplitSelectStateController.isSplitSelectActive()) {
cancelSplitSelect(false);
}
// Remove grouped tasks and recycle once we exit overview
int taskCount = getTaskViewCount();
for (int i = 0; i < taskCount; i++) {
@@ -3940,109 +3937,6 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
pendingAnimation.buildAnim().start();
}
public PendingAnimation cancelSplitSelect(boolean animate) {
SplitSelectStateController splitController = mSplitSelectStateController;
@StagePosition int stagePosition = splitController.getActiveSplitStagePosition();
Rect initialBounds = splitController.getInitialBounds();
splitController.resetState();
int duration = mActivity.getStateManager().getState().getTransitionDuration(getContext());
PendingAnimation pendingAnim = new PendingAnimation(duration);
mSplitToast.cancel();
mSplitUnsupportedToast.cancel();
if (!animate) {
resetFromSplitSelectionState();
return pendingAnim;
}
addViewInLayout(mSplitHiddenTaskView, mSplitHiddenTaskViewIndex,
mSplitHiddenTaskView.getLayoutParams());
mSplitHiddenTaskView.setAlpha(0);
int[] oldScroll = new int[getChildCount()];
getPageScrolls(oldScroll, false,
view -> 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<TaskView> 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) {
@@ -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;
}
@@ -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());
}
}
@@ -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
+1 -1
View File
@@ -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;
@@ -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<T extends Context & ActivityContext>
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<LauncherAction> 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<LauncherAction> 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<LauncherAction> 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<T> 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;
}
}
}
}
@@ -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<Launcher> {
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<LauncherAction> 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<LauncherAction> 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<LauncherAction> 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<OptionItem> 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;
}
}
}
}
@@ -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;
@@ -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;
@@ -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<Launc
}
}
}
@Override
protected void showPopupContainerForIcon(BubbleTextView originalIcon) {
PopupContainerWithArrow.showForIcon(originalIcon);
}
}
@@ -152,6 +152,10 @@ public class PopupContainerWithArrow<T extends Context & ActivityContext>
};
}
public void setPopupItemDragHandler(PopupItemDragHandler popupItemDragHandler) {
mPopupItemDragHandler = popupItemDragHandler;
}
public PopupItemDragHandler getItemDragHandler() {
return mPopupItemDragHandler;
}
@@ -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 <T> The activity on which the popup shows
*/
public class PopupLiveUpdateHandler<T extends Context & ActivityContext> implements
public abstract class PopupLiveUpdateHandler<T extends Context & ActivityContext> implements
PopupDataProvider.PopupDataChangeListener, View.OnAttachStateChangeListener {
protected final T mContext;
@@ -103,6 +104,8 @@ public class PopupLiveUpdateHandler<T extends Context & ActivityContext> impleme
@Override
public void onSystemShortcutsUpdated() {
mPopupContainerWithArrow.close(true);
PopupContainerWithArrow.showForIcon(mPopupContainerWithArrow.getOriginalIcon());
showPopupContainerForIcon(mPopupContainerWithArrow.getOriginalIcon());
}
protected abstract void showPopupContainerForIcon(BubbleTextView originalIcon);
}
@@ -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;
}
}
+36 -20
View File
@@ -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 <T extends Context & ActivityContext> 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 <T extends Context & ActivityContext> 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);
@@ -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();