Adding support for keyboard based drag and drop
For keyboard DnD, we use Accessible DnD implementation. A placeholder FloatingView draws the focus indicator for the virtual views Test: Visible Bug: 178781566 Change-Id: I632fc7377dffa1e05e3f0a9c3ad18641deb5a1a4
This commit is contained in:
parent
d462965698
commit
a4647b681f
|
@ -0,0 +1,34 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- 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.
|
||||||
|
-->
|
||||||
|
<com.android.launcher3.keyboard.KeyboardDragAndDropView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:focusable="true"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:elevation="6dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/label"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:background="?attr/folderFillColor"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:textColor="?attr/folderTextColor"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</com.android.launcher3.keyboard.KeyboardDragAndDropView>
|
|
@ -139,6 +139,7 @@
|
||||||
<dimen name="drag_flingToDeleteMinVelocity">-1500dp</dimen>
|
<dimen name="drag_flingToDeleteMinVelocity">-1500dp</dimen>
|
||||||
|
|
||||||
<dimen name="spring_loaded_panel_border">1dp</dimen>
|
<dimen name="spring_loaded_panel_border">1dp</dimen>
|
||||||
|
<dimen name="keyboard_drag_stroke_width">4dp</dimen>
|
||||||
|
|
||||||
<!-- Folders -->
|
<!-- Folders -->
|
||||||
<dimen name="page_indicator_dot_size">8dp</dimen>
|
<dimen name="page_indicator_dot_size">8dp</dimen>
|
||||||
|
|
|
@ -76,17 +76,18 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch
|
||||||
public static final int TYPE_SNACKBAR = 1 << 7;
|
public static final int TYPE_SNACKBAR = 1 << 7;
|
||||||
public static final int TYPE_LISTENER = 1 << 8;
|
public static final int TYPE_LISTENER = 1 << 8;
|
||||||
public static final int TYPE_ALL_APPS_EDU = 1 << 9;
|
public static final int TYPE_ALL_APPS_EDU = 1 << 9;
|
||||||
|
public static final int TYPE_DRAG_DROP_POPUP = 1 << 10;
|
||||||
|
|
||||||
// Popups related to quickstep UI
|
// Popups related to quickstep UI
|
||||||
public static final int TYPE_TASK_MENU = 1 << 10;
|
public static final int TYPE_TASK_MENU = 1 << 11;
|
||||||
public static final int TYPE_OPTIONS_POPUP = 1 << 11;
|
public static final int TYPE_OPTIONS_POPUP = 1 << 12;
|
||||||
public static final int TYPE_ICON_SURFACE = 1 << 12;
|
public static final int TYPE_ICON_SURFACE = 1 << 13;
|
||||||
|
|
||||||
public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP
|
public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP
|
||||||
| TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET
|
| TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET
|
||||||
| TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE | TYPE_TASK_MENU
|
| TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE | TYPE_TASK_MENU
|
||||||
| TYPE_OPTIONS_POPUP | TYPE_SNACKBAR | TYPE_LISTENER | TYPE_ALL_APPS_EDU
|
| TYPE_OPTIONS_POPUP | TYPE_SNACKBAR | TYPE_LISTENER | TYPE_ALL_APPS_EDU
|
||||||
| TYPE_ICON_SURFACE;
|
| TYPE_ICON_SURFACE | TYPE_DRAG_DROP_POPUP;
|
||||||
|
|
||||||
// Type of popups which should be kept open during launcher rebind
|
// Type of popups which should be kept open during launcher rebind
|
||||||
public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET
|
public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET
|
||||||
|
@ -103,7 +104,7 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch
|
||||||
// These view all have particular operation associated with swipe down interaction.
|
// These view all have particular operation associated with swipe down interaction.
|
||||||
public static final int TYPE_STATUS_BAR_SWIPE_DOWN_DISALLOW = TYPE_WIDGETS_BOTTOM_SHEET |
|
public static final int TYPE_STATUS_BAR_SWIPE_DOWN_DISALLOW = TYPE_WIDGETS_BOTTOM_SHEET |
|
||||||
TYPE_WIDGETS_FULL_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_ON_BOARD_POPUP |
|
TYPE_WIDGETS_FULL_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_ON_BOARD_POPUP |
|
||||||
TYPE_DISCOVERY_BOUNCE | TYPE_TASK_MENU ;
|
TYPE_DISCOVERY_BOUNCE | TYPE_TASK_MENU | TYPE_DRAG_DROP_POPUP;
|
||||||
|
|
||||||
protected boolean mIsOpen;
|
protected boolean mIsOpen;
|
||||||
|
|
||||||
|
|
|
@ -240,7 +240,7 @@ public abstract class ButtonDropTarget extends TextView
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
|
public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
|
||||||
mActive = supportsDrop(dragObject.dragInfo);
|
mActive = !options.isKeyboardDrag && supportsDrop(dragObject.dragInfo);
|
||||||
mDrawable.setColorFilter(null);
|
mDrawable.setColorFilter(null);
|
||||||
if (mCurrentColorAnim != null) {
|
if (mCurrentColorAnim != null) {
|
||||||
mCurrentColorAnim.cancel();
|
mCurrentColorAnim.cancel();
|
||||||
|
|
|
@ -312,6 +312,13 @@ public class CellLayout extends ViewGroup {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the currently set accessibility delegate
|
||||||
|
*/
|
||||||
|
public DragAndDropAccessibilityDelegate getDragAndDropAccessibilityDelegate() {
|
||||||
|
return mTouchHelper;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean dispatchHoverEvent(MotionEvent event) {
|
public boolean dispatchHoverEvent(MotionEvent event) {
|
||||||
// Always attempt to dispatch hover events to accessibility first.
|
// Always attempt to dispatch hover events to accessibility first.
|
||||||
|
|
|
@ -131,7 +131,10 @@ public class DropTargetBar extends FrameLayout
|
||||||
int width = MeasureSpec.getSize(widthMeasureSpec);
|
int width = MeasureSpec.getSize(widthMeasureSpec);
|
||||||
int height = MeasureSpec.getSize(heightMeasureSpec);
|
int height = MeasureSpec.getSize(heightMeasureSpec);
|
||||||
|
|
||||||
if (mIsVertical) {
|
int visibleCount = getVisibleButtonsCount();
|
||||||
|
if (visibleCount == 0) {
|
||||||
|
// do nothing
|
||||||
|
} else if (mIsVertical) {
|
||||||
int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
|
int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
|
||||||
int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
|
int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
|
||||||
|
|
||||||
|
@ -142,7 +145,6 @@ public class DropTargetBar extends FrameLayout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
int visibleCount = getVisibleButtonsCount();
|
|
||||||
int availableWidth = width / visibleCount;
|
int availableWidth = width / visibleCount;
|
||||||
boolean textVisible = true;
|
boolean textVisible = true;
|
||||||
for (ButtonDropTarget buttons : mDropTargets) {
|
for (ButtonDropTarget buttons : mDropTargets) {
|
||||||
|
@ -165,7 +167,10 @@ public class DropTargetBar extends FrameLayout
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||||
if (mIsVertical) {
|
int visibleCount = getVisibleButtonsCount();
|
||||||
|
if (visibleCount == 0) {
|
||||||
|
// do nothing
|
||||||
|
} else if (mIsVertical) {
|
||||||
int gap = getResources().getDimensionPixelSize(R.dimen.drop_target_vertical_gap);
|
int gap = getResources().getDimensionPixelSize(R.dimen.drop_target_vertical_gap);
|
||||||
int start = gap;
|
int start = gap;
|
||||||
int end;
|
int end;
|
||||||
|
@ -178,7 +183,6 @@ public class DropTargetBar extends FrameLayout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
int visibleCount = getVisibleButtonsCount();
|
|
||||||
int frameSize = (right - left) / visibleCount;
|
int frameSize = (right - left) / visibleCount;
|
||||||
|
|
||||||
int start = frameSize / 2;
|
int start = frameSize / 2;
|
||||||
|
|
|
@ -2681,7 +2681,8 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
|
||||||
&& focusedView.getTag() instanceof ItemInfo
|
&& focusedView.getTag() instanceof ItemInfo
|
||||||
&& mAccessibilityDelegate.performAction(focusedView,
|
&& mAccessibilityDelegate.performAction(focusedView,
|
||||||
(ItemInfo) focusedView.getTag(),
|
(ItemInfo) focusedView.getTag(),
|
||||||
LauncherAccessibilityDelegate.DEEP_SHORTCUTS)) {
|
LauncherAccessibilityDelegate.DEEP_SHORTCUTS,
|
||||||
|
true)) {
|
||||||
PopupContainerWithArrow.getOpen(this).requestFocus();
|
PopupContainerWithArrow.getOpen(this).requestFocus();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ import androidx.customview.widget.ExploreByTouchHelper;
|
||||||
import com.android.launcher3.CellLayout;
|
import com.android.launcher3.CellLayout;
|
||||||
import com.android.launcher3.Launcher;
|
import com.android.launcher3.Launcher;
|
||||||
import com.android.launcher3.R;
|
import com.android.launcher3.R;
|
||||||
|
import com.android.launcher3.dragndrop.DragLayer;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -41,30 +42,32 @@ public abstract class DragAndDropAccessibilityDelegate extends ExploreByTouchHel
|
||||||
implements OnClickListener, OnHoverListener {
|
implements OnClickListener, OnHoverListener {
|
||||||
protected static final int INVALID_POSITION = -1;
|
protected static final int INVALID_POSITION = -1;
|
||||||
|
|
||||||
private static final int[] sTempArray = new int[2];
|
protected final Rect mTempRect = new Rect();
|
||||||
|
protected final int[] mTempCords = new int[2];
|
||||||
|
|
||||||
protected final CellLayout mView;
|
protected final CellLayout mView;
|
||||||
protected final Context mContext;
|
protected final Context mContext;
|
||||||
protected final LauncherAccessibilityDelegate mDelegate;
|
protected final LauncherAccessibilityDelegate mDelegate;
|
||||||
|
protected final DragLayer mDragLayer;
|
||||||
private final Rect mTempRect = new Rect();
|
|
||||||
|
|
||||||
public DragAndDropAccessibilityDelegate(CellLayout forView) {
|
public DragAndDropAccessibilityDelegate(CellLayout forView) {
|
||||||
super(forView);
|
super(forView);
|
||||||
mView = forView;
|
mView = forView;
|
||||||
mContext = mView.getContext();
|
mContext = mView.getContext();
|
||||||
mDelegate = Launcher.getLauncher(mContext).getAccessibilityDelegate();
|
Launcher launcher = Launcher.getLauncher(mContext);
|
||||||
|
mDelegate = launcher.getAccessibilityDelegate();
|
||||||
|
mDragLayer = launcher.getDragLayer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int getVirtualViewAt(float x, float y) {
|
public int getVirtualViewAt(float x, float y) {
|
||||||
if (x < 0 || y < 0 || x > mView.getMeasuredWidth() || y > mView.getMeasuredHeight()) {
|
if (x < 0 || y < 0 || x > mView.getMeasuredWidth() || y > mView.getMeasuredHeight()) {
|
||||||
return INVALID_ID;
|
return INVALID_ID;
|
||||||
}
|
}
|
||||||
mView.pointToCellExact((int) x, (int) y, sTempArray);
|
mView.pointToCellExact((int) x, (int) y, mTempCords);
|
||||||
|
|
||||||
// Map cell to id
|
// Map cell to id
|
||||||
int id = sTempArray[0] + sTempArray[1] * mView.getCountX();
|
int id = mTempCords[0] + mTempCords[1] * mView.getCountX();
|
||||||
return intersectsValidDropTarget(id);
|
return intersectsValidDropTarget(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +78,7 @@ public abstract class DragAndDropAccessibilityDelegate extends ExploreByTouchHel
|
||||||
protected abstract int intersectsValidDropTarget(int id);
|
protected abstract int intersectsValidDropTarget(int id);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void getVisibleVirtualViews(List<Integer> virtualViews) {
|
public void getVisibleVirtualViews(List<Integer> virtualViews) {
|
||||||
// We create a virtual view for each cell of the grid
|
// We create a virtual view for each cell of the grid
|
||||||
// The cell ids correspond to cells in reading order.
|
// The cell ids correspond to cells in reading order.
|
||||||
int nCells = mView.getCountX() * mView.getCountY();
|
int nCells = mView.getCountX() * mView.getCountY();
|
||||||
|
@ -88,7 +91,7 @@ public abstract class DragAndDropAccessibilityDelegate extends ExploreByTouchHel
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean onPerformActionForVirtualView(int viewId, int action, Bundle args) {
|
public boolean onPerformActionForVirtualView(int viewId, int action, Bundle args) {
|
||||||
if (action == AccessibilityNodeInfoCompat.ACTION_CLICK && viewId != INVALID_ID) {
|
if (action == AccessibilityNodeInfoCompat.ACTION_CLICK && viewId != INVALID_ID) {
|
||||||
String confirmation = getConfirmationForIconDrop(viewId);
|
String confirmation = getConfirmationForIconDrop(viewId);
|
||||||
mDelegate.handleAccessibleDrop(mView, getItemBounds(viewId), confirmation);
|
mDelegate.handleAccessibleDrop(mView, getItemBounds(viewId), confirmation);
|
||||||
|
@ -112,13 +115,25 @@ public abstract class DragAndDropAccessibilityDelegate extends ExploreByTouchHel
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPopulateNodeForVirtualView(int id, AccessibilityNodeInfoCompat node) {
|
public void onPopulateNodeForVirtualView(int id, AccessibilityNodeInfoCompat node) {
|
||||||
if (id == INVALID_ID) {
|
if (id == INVALID_ID) {
|
||||||
throw new IllegalArgumentException("Invalid virtual view id");
|
throw new IllegalArgumentException("Invalid virtual view id");
|
||||||
}
|
}
|
||||||
|
|
||||||
node.setContentDescription(getLocationDescriptionForIconDrop(id));
|
node.setContentDescription(getLocationDescriptionForIconDrop(id));
|
||||||
node.setBoundsInParent(getItemBounds(id));
|
|
||||||
|
Rect itemBounds = getItemBounds(id);
|
||||||
|
node.setBoundsInParent(itemBounds);
|
||||||
|
|
||||||
|
// ExploreByTouchHelper does not currently handle view scale.
|
||||||
|
// Update BoundsInScreen to appropriate value.
|
||||||
|
mTempCords[0] = mTempCords[1] = 0;
|
||||||
|
float scale = mDragLayer.getDescendantCoordRelativeToSelf(mView, mTempCords);
|
||||||
|
mTempRect.left = mTempCords[0] + (int) (itemBounds.left * scale);
|
||||||
|
mTempRect.right = mTempCords[0] + (int) (itemBounds.right * scale);
|
||||||
|
mTempRect.top = mTempCords[1] + (int) (itemBounds.top * scale);
|
||||||
|
mTempRect.bottom = mTempCords[1] + (int) (itemBounds.bottom * scale);
|
||||||
|
node.setBoundsInScreen(mTempRect);
|
||||||
|
|
||||||
node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
|
node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
|
||||||
node.setClickable(true);
|
node.setClickable(true);
|
||||||
|
@ -130,6 +145,13 @@ public abstract class DragAndDropAccessibilityDelegate extends ExploreByTouchHel
|
||||||
return dispatchHoverEvent(motionEvent);
|
return dispatchHoverEvent(motionEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the target host container
|
||||||
|
*/
|
||||||
|
public View getHost() {
|
||||||
|
return mView;
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract String getLocationDescriptionForIconDrop(int id);
|
protected abstract String getLocationDescriptionForIconDrop(int id);
|
||||||
|
|
||||||
protected abstract String getConfirmationForIconDrop(int id);
|
protected abstract String getConfirmationForIconDrop(int id);
|
||||||
|
|
|
@ -34,6 +34,7 @@ import com.android.launcher3.dragndrop.DragController.DragListener;
|
||||||
import com.android.launcher3.dragndrop.DragOptions;
|
import com.android.launcher3.dragndrop.DragOptions;
|
||||||
import com.android.launcher3.folder.Folder;
|
import com.android.launcher3.folder.Folder;
|
||||||
import com.android.launcher3.keyboard.CustomActionsPopup;
|
import com.android.launcher3.keyboard.CustomActionsPopup;
|
||||||
|
import com.android.launcher3.keyboard.KeyboardDragAndDropView;
|
||||||
import com.android.launcher3.model.data.AppInfo;
|
import com.android.launcher3.model.data.AppInfo;
|
||||||
import com.android.launcher3.model.data.FolderInfo;
|
import com.android.launcher3.model.data.FolderInfo;
|
||||||
import com.android.launcher3.model.data.ItemInfo;
|
import com.android.launcher3.model.data.ItemInfo;
|
||||||
|
@ -107,10 +108,6 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme
|
||||||
launcher.getText(R.string.shortcuts_menu_with_notifications_description)));
|
launcher.getText(R.string.shortcuts_menu_with_notifications_description)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addAccessibilityAction(int action, int actionLabel) {
|
|
||||||
mActions.put(action, new AccessibilityAction(action, mLauncher.getText(actionLabel)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
|
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
|
||||||
super.onInitializeAccessibilityNodeInfo(host, info);
|
super.onInitializeAccessibilityNodeInfo(host, info);
|
||||||
|
@ -139,7 +136,7 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not add move actions for keyboard request as this uses virtual nodes.
|
// Do not add move actions for keyboard request as this uses virtual nodes.
|
||||||
if (!fromKeyboard && itemSupportsAccessibleDrag(item)) {
|
if (itemSupportsAccessibleDrag(item)) {
|
||||||
info.addAction(mActions.get(MOVE));
|
info.addAction(mActions.get(MOVE));
|
||||||
|
|
||||||
if (item.container >= 0) {
|
if (item.container >= 0) {
|
||||||
|
@ -178,13 +175,17 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme
|
||||||
@Override
|
@Override
|
||||||
public boolean performAccessibilityAction(View host, int action, Bundle args) {
|
public boolean performAccessibilityAction(View host, int action, Bundle args) {
|
||||||
if ((host.getTag() instanceof ItemInfo)
|
if ((host.getTag() instanceof ItemInfo)
|
||||||
&& performAction(host, (ItemInfo) host.getTag(), action)) {
|
&& performAction(host, (ItemInfo) host.getTag(), action, false)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return super.performAccessibilityAction(host, action, args);
|
return super.performAccessibilityAction(host, action, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean performAction(final View host, final ItemInfo item, int action) {
|
/**
|
||||||
|
* Performs the provided action on the host
|
||||||
|
*/
|
||||||
|
public boolean performAction(final View host, final ItemInfo item, int action,
|
||||||
|
boolean fromKeyboard) {
|
||||||
if (action == ACTION_LONG_CLICK) {
|
if (action == ACTION_LONG_CLICK) {
|
||||||
if (PopupContainerWithArrow.canShow(host, item)) {
|
if (PopupContainerWithArrow.canShow(host, item)) {
|
||||||
// Long press should be consumed for workspace items, and it should invoke the
|
// Long press should be consumed for workspace items, and it should invoke the
|
||||||
|
@ -205,7 +206,7 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (action == MOVE) {
|
if (action == MOVE) {
|
||||||
beginAccessibleDrag(host, item);
|
return beginAccessibleDrag(host, item, fromKeyboard);
|
||||||
} else if (action == ADD_TO_WORKSPACE) {
|
} else if (action == ADD_TO_WORKSPACE) {
|
||||||
final int[] coordinates = new int[2];
|
final int[] coordinates = new int[2];
|
||||||
final int screenId = findSpaceOnWorkspace(item, coordinates);
|
final int screenId = findSpaceOnWorkspace(item, coordinates);
|
||||||
|
@ -406,7 +407,11 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void beginAccessibleDrag(View item, ItemInfo info) {
|
private boolean beginAccessibleDrag(View item, ItemInfo info, boolean fromKeyboard) {
|
||||||
|
if (!itemSupportsAccessibleDrag(info)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
mDragInfo = new DragInfo();
|
mDragInfo = new DragInfo();
|
||||||
mDragInfo.info = info;
|
mDragInfo.info = info;
|
||||||
mDragInfo.item = item;
|
mDragInfo.item = item;
|
||||||
|
@ -423,8 +428,17 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme
|
||||||
|
|
||||||
DragOptions options = new DragOptions();
|
DragOptions options = new DragOptions();
|
||||||
options.isAccessibleDrag = true;
|
options.isAccessibleDrag = true;
|
||||||
|
options.isKeyboardDrag = fromKeyboard;
|
||||||
options.simulatedDndStartPoint = new Point(pos.centerX(), pos.centerY());
|
options.simulatedDndStartPoint = new Point(pos.centerX(), pos.centerY());
|
||||||
ItemLongClickListener.beginDrag(item, mLauncher, info, options);
|
|
||||||
|
if (fromKeyboard) {
|
||||||
|
KeyboardDragAndDropView popup = (KeyboardDragAndDropView) mLauncher.getLayoutInflater()
|
||||||
|
.inflate(R.layout.keyboard_drag_and_drop, mLauncher.getDragLayer(), false);
|
||||||
|
popup.showForIcon(item, info, options);
|
||||||
|
} else {
|
||||||
|
ItemLongClickListener.beginDrag(item, mLauncher, info, options);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -59,7 +59,7 @@ public class ShortcutMenuAccessibilityDelegate extends LauncherAccessibilityDele
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean performAction(View host, ItemInfo item, int action) {
|
public boolean performAction(View host, ItemInfo item, int action, boolean fromKeyboard) {
|
||||||
if (action == ADD_TO_WORKSPACE) {
|
if (action == ADD_TO_WORKSPACE) {
|
||||||
if (!(host.getParent() instanceof DeepShortcutView)) {
|
if (!(host.getParent() instanceof DeepShortcutView)) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -17,17 +17,12 @@
|
||||||
package com.android.launcher3.accessibility;
|
package com.android.launcher3.accessibility;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
|
|
||||||
|
|
||||||
import com.android.launcher3.CellLayout;
|
import com.android.launcher3.CellLayout;
|
||||||
import com.android.launcher3.Launcher;
|
|
||||||
import com.android.launcher3.R;
|
import com.android.launcher3.R;
|
||||||
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.DragType;
|
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.DragType;
|
||||||
import com.android.launcher3.dragndrop.DragLayer;
|
|
||||||
import com.android.launcher3.model.data.AppInfo;
|
import com.android.launcher3.model.data.AppInfo;
|
||||||
import com.android.launcher3.model.data.FolderInfo;
|
import com.android.launcher3.model.data.FolderInfo;
|
||||||
import com.android.launcher3.model.data.ItemInfo;
|
import com.android.launcher3.model.data.ItemInfo;
|
||||||
|
@ -38,9 +33,6 @@ import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||||
*/
|
*/
|
||||||
public class WorkspaceAccessibilityHelper extends DragAndDropAccessibilityDelegate {
|
public class WorkspaceAccessibilityHelper extends DragAndDropAccessibilityDelegate {
|
||||||
|
|
||||||
private final Rect mTempRect = new Rect();
|
|
||||||
private final int[] mTempCords = new int[2];
|
|
||||||
|
|
||||||
public WorkspaceAccessibilityHelper(CellLayout layout) {
|
public WorkspaceAccessibilityHelper(CellLayout layout) {
|
||||||
super(layout);
|
super(layout);
|
||||||
}
|
}
|
||||||
|
@ -134,26 +126,6 @@ public class WorkspaceAccessibilityHelper extends DragAndDropAccessibilityDelega
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPopulateNodeForVirtualView(int id, AccessibilityNodeInfoCompat node) {
|
|
||||||
super.onPopulateNodeForVirtualView(id, node);
|
|
||||||
|
|
||||||
|
|
||||||
// ExploreByTouchHelper does not currently handle view scale.
|
|
||||||
// Update BoundsInScreen to appropriate value.
|
|
||||||
DragLayer dragLayer = Launcher.getLauncher(mView.getContext()).getDragLayer();
|
|
||||||
mTempCords[0] = mTempCords[1] = 0;
|
|
||||||
float scale = dragLayer.getDescendantCoordRelativeToSelf(mView, mTempCords);
|
|
||||||
|
|
||||||
node.getBoundsInParent(mTempRect);
|
|
||||||
mTempRect.left = mTempCords[0] + (int) (mTempRect.left * scale);
|
|
||||||
mTempRect.right = mTempCords[0] + (int) (mTempRect.right * scale);
|
|
||||||
mTempRect.top = mTempCords[1] + (int) (mTempRect.top * scale);
|
|
||||||
mTempRect.bottom = mTempCords[1] + (int) (mTempRect.bottom * scale);
|
|
||||||
node.setBoundsInScreen(mTempRect);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getLocationDescriptionForIconDrop(int id) {
|
protected String getLocationDescriptionForIconDrop(int id) {
|
||||||
int x = id % mView.getCountX();
|
int x = id % mView.getCountX();
|
||||||
|
|
|
@ -85,9 +85,6 @@ public class DragLayer extends BaseDragLayer<Launcher> {
|
||||||
private final WorkspaceAndHotseatScrim mWorkspaceScrim;
|
private final WorkspaceAndHotseatScrim mWorkspaceScrim;
|
||||||
private final OverviewScrim mOverviewScrim;
|
private final OverviewScrim mOverviewScrim;
|
||||||
|
|
||||||
// View that should handle move events
|
|
||||||
private View mMoveTarget;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to create a new DragLayer from XML.
|
* Used to create a new DragLayer from XML.
|
||||||
*
|
*
|
||||||
|
@ -109,7 +106,6 @@ public class DragLayer extends BaseDragLayer<Launcher> {
|
||||||
public void setup(DragController dragController, Workspace workspace) {
|
public void setup(DragController dragController, Workspace workspace) {
|
||||||
mDragController = dragController;
|
mDragController = dragController;
|
||||||
mWorkspaceScrim.setWorkspace(workspace);
|
mWorkspaceScrim.setWorkspace(workspace);
|
||||||
mMoveTarget = workspace;
|
|
||||||
recreateControllers();
|
recreateControllers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,12 +210,6 @@ public class DragLayer extends BaseDragLayer<Launcher> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean dispatchUnhandledMove(View focused, int direction) {
|
|
||||||
return super.dispatchUnhandledMove(focused, direction)
|
|
||||||
|| mMoveTarget.dispatchUnhandledMove(focused, direction);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean dispatchTouchEvent(MotionEvent ev) {
|
public boolean dispatchTouchEvent(MotionEvent ev) {
|
||||||
ev.offsetLocation(getTranslationX(), 0);
|
ev.offsetLocation(getTranslationX(), 0);
|
||||||
|
|
|
@ -28,6 +28,9 @@ public class DragOptions {
|
||||||
/** Whether or not an accessible drag operation is in progress. */
|
/** Whether or not an accessible drag operation is in progress. */
|
||||||
public boolean isAccessibleDrag = false;
|
public boolean isAccessibleDrag = false;
|
||||||
|
|
||||||
|
/** Whether or not the drag operation is controlled by keyboard. */
|
||||||
|
public boolean isKeyboardDrag = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies the start location for a simulated DnD (like system drag or accessibility drag),
|
* Specifies the start location for a simulated DnD (like system drag or accessibility drag),
|
||||||
* null when using internal DnD
|
* null when using internal DnD
|
||||||
|
|
|
@ -88,6 +88,7 @@ public class CustomActionsPopup implements OnMenuItemClickListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onMenuItemClick(MenuItem menuItem) {
|
public boolean onMenuItemClick(MenuItem menuItem) {
|
||||||
return mDelegate.performAction(mIcon, (ItemInfo) mIcon.getTag(), menuItem.getItemId());
|
return mDelegate.performAction(mIcon, (ItemInfo) mIcon.getTag(), menuItem.getItemId(),
|
||||||
|
true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,233 +16,30 @@
|
||||||
|
|
||||||
package com.android.launcher3.keyboard;
|
package com.android.launcher3.keyboard;
|
||||||
|
|
||||||
import android.animation.Animator;
|
|
||||||
import android.animation.AnimatorListenerAdapter;
|
|
||||||
import android.animation.ObjectAnimator;
|
|
||||||
import android.animation.PropertyValuesHolder;
|
|
||||||
import android.animation.RectEvaluator;
|
|
||||||
import android.animation.ValueAnimator;
|
|
||||||
import android.animation.ValueAnimator.AnimatorUpdateListener;
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Color;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.util.Property;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnFocusChangeListener;
|
import android.view.View.OnFocusChangeListener;
|
||||||
|
|
||||||
import com.android.launcher3.R;
|
import com.android.launcher3.R;
|
||||||
import com.android.launcher3.config.FeatureFlags;
|
|
||||||
import com.android.launcher3.util.Themes;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A helper class to draw background of a focused view.
|
* A helper class to draw background of a focused view.
|
||||||
*/
|
*/
|
||||||
public abstract class FocusIndicatorHelper implements
|
public abstract class FocusIndicatorHelper extends ItemFocusIndicatorHelper<View>
|
||||||
OnFocusChangeListener, AnimatorUpdateListener {
|
implements OnFocusChangeListener {
|
||||||
|
|
||||||
private static final float MIN_VISIBLE_ALPHA = 0.2f;
|
|
||||||
private static final long ANIM_DURATION = 150;
|
|
||||||
|
|
||||||
public static final Property<FocusIndicatorHelper, Float> ALPHA =
|
|
||||||
new Property<FocusIndicatorHelper, Float>(Float.TYPE, "alpha") {
|
|
||||||
@Override
|
|
||||||
public void set(FocusIndicatorHelper object, Float value) {
|
|
||||||
object.setAlpha(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Float get(FocusIndicatorHelper object) {
|
|
||||||
return object.mAlpha;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public static final Property<FocusIndicatorHelper, Float> SHIFT =
|
|
||||||
new Property<FocusIndicatorHelper, Float>(
|
|
||||||
Float.TYPE, "shift") {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void set(FocusIndicatorHelper object, Float value) {
|
|
||||||
object.mShift = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Float get(FocusIndicatorHelper object) {
|
|
||||||
return object.mShift;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private static final RectEvaluator RECT_EVALUATOR = new RectEvaluator(new Rect());
|
|
||||||
private static final Rect sTempRect1 = new Rect();
|
|
||||||
private static final Rect sTempRect2 = new Rect();
|
|
||||||
|
|
||||||
private final View mContainer;
|
|
||||||
private final Paint mPaint;
|
|
||||||
private final int mMaxAlpha;
|
|
||||||
|
|
||||||
private final Rect mDirtyRect = new Rect();
|
|
||||||
private boolean mIsDirty = false;
|
|
||||||
|
|
||||||
private View mLastFocusedView;
|
|
||||||
|
|
||||||
private View mCurrentView;
|
|
||||||
private View mTargetView;
|
|
||||||
/**
|
|
||||||
* The fraction indicating the position of the focusRect between {@link #mCurrentView}
|
|
||||||
* & {@link #mTargetView}
|
|
||||||
*/
|
|
||||||
private float mShift;
|
|
||||||
|
|
||||||
private ObjectAnimator mCurrentAnimation;
|
|
||||||
private float mAlpha;
|
|
||||||
private float mRadius;
|
|
||||||
|
|
||||||
public FocusIndicatorHelper(View container) {
|
public FocusIndicatorHelper(View container) {
|
||||||
mContainer = container;
|
super(container, container.getResources().getColor(R.color.focused_background));
|
||||||
|
|
||||||
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
|
||||||
int color = container.getResources().getColor(R.color.focused_background);
|
|
||||||
mMaxAlpha = Color.alpha(color);
|
|
||||||
mPaint.setColor(0xFF000000 | color);
|
|
||||||
|
|
||||||
setAlpha(0);
|
|
||||||
mShift = 0;
|
|
||||||
if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
|
|
||||||
mRadius = Themes.getDialogCornerRadius(container.getContext());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void setAlpha(float alpha) {
|
|
||||||
mAlpha = alpha;
|
|
||||||
mPaint.setAlpha((int) (mAlpha * mMaxAlpha));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAnimationUpdate(ValueAnimator animation) {
|
|
||||||
invalidateDirty();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void invalidateDirty() {
|
|
||||||
if (mIsDirty) {
|
|
||||||
mContainer.invalidate(mDirtyRect);
|
|
||||||
mIsDirty = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Rect newRect = getDrawRect();
|
|
||||||
if (newRect != null) {
|
|
||||||
mContainer.invalidate(newRect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void draw(Canvas c) {
|
|
||||||
if (mAlpha <= 0) return;
|
|
||||||
|
|
||||||
Rect newRect = getDrawRect();
|
|
||||||
if (newRect != null) {
|
|
||||||
mDirtyRect.set(newRect);
|
|
||||||
c.drawRoundRect((float) mDirtyRect.left, (float) mDirtyRect.top,
|
|
||||||
(float) mDirtyRect.right, (float) mDirtyRect.bottom,
|
|
||||||
mRadius, mRadius, mPaint);
|
|
||||||
mIsDirty = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Rect getDrawRect() {
|
|
||||||
if (mCurrentView != null && mCurrentView.isAttachedToWindow()) {
|
|
||||||
viewToRect(mCurrentView, sTempRect1);
|
|
||||||
|
|
||||||
if (mShift > 0 && mTargetView != null) {
|
|
||||||
viewToRect(mTargetView, sTempRect2);
|
|
||||||
return RECT_EVALUATOR.evaluate(mShift, sTempRect1, sTempRect2);
|
|
||||||
} else {
|
|
||||||
return sTempRect1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFocusChange(View v, boolean hasFocus) {
|
public void onFocusChange(View v, boolean hasFocus) {
|
||||||
if (hasFocus) {
|
changeFocus(v, hasFocus);
|
||||||
endCurrentAnimation();
|
|
||||||
|
|
||||||
if (mAlpha > MIN_VISIBLE_ALPHA) {
|
|
||||||
mTargetView = v;
|
|
||||||
|
|
||||||
mCurrentAnimation = ObjectAnimator.ofPropertyValuesHolder(this,
|
|
||||||
PropertyValuesHolder.ofFloat(ALPHA, 1),
|
|
||||||
PropertyValuesHolder.ofFloat(SHIFT, 1));
|
|
||||||
mCurrentAnimation.addListener(new ViewSetListener(v, true));
|
|
||||||
} else {
|
|
||||||
setCurrentView(v);
|
|
||||||
|
|
||||||
mCurrentAnimation = ObjectAnimator.ofPropertyValuesHolder(this,
|
|
||||||
PropertyValuesHolder.ofFloat(ALPHA, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
mLastFocusedView = v;
|
|
||||||
} else {
|
|
||||||
if (mLastFocusedView == v) {
|
|
||||||
mLastFocusedView = null;
|
|
||||||
endCurrentAnimation();
|
|
||||||
mCurrentAnimation = ObjectAnimator.ofPropertyValuesHolder(this,
|
|
||||||
PropertyValuesHolder.ofFloat(ALPHA, 0));
|
|
||||||
mCurrentAnimation.addListener(new ViewSetListener(null, false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// invalidate once
|
|
||||||
invalidateDirty();
|
|
||||||
|
|
||||||
mLastFocusedView = hasFocus ? v : null;
|
|
||||||
if (mCurrentAnimation != null) {
|
|
||||||
mCurrentAnimation.addUpdateListener(this);
|
|
||||||
mCurrentAnimation.setDuration(ANIM_DURATION).start();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void endCurrentAnimation() {
|
@Override
|
||||||
if (mCurrentAnimation != null) {
|
protected boolean shouldDraw(View item) {
|
||||||
mCurrentAnimation.cancel();
|
return item.isAttachedToWindow();
|
||||||
mCurrentAnimation = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void setCurrentView(View v) {
|
|
||||||
mCurrentView = v;
|
|
||||||
mShift = 0;
|
|
||||||
mTargetView = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the position of {@param v} relative to {@link #mContainer}.
|
|
||||||
*/
|
|
||||||
public abstract void viewToRect(View v, Rect outRect);
|
|
||||||
|
|
||||||
private class ViewSetListener extends AnimatorListenerAdapter {
|
|
||||||
private final View mViewToSet;
|
|
||||||
private final boolean mCallOnCancel;
|
|
||||||
private boolean mCalled = false;
|
|
||||||
|
|
||||||
public ViewSetListener(View v, boolean callOnCancel) {
|
|
||||||
mViewToSet = v;
|
|
||||||
mCallOnCancel = callOnCancel;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAnimationCancel(Animator animation) {
|
|
||||||
if (!mCallOnCancel) {
|
|
||||||
mCalled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAnimationEnd(Animator animation) {
|
|
||||||
if (!mCalled) {
|
|
||||||
setCurrentView(mViewToSet);
|
|
||||||
mCalled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,252 @@
|
||||||
|
/*
|
||||||
|
* 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.keyboard;
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.animation.AnimatorListenerAdapter;
|
||||||
|
import android.animation.ObjectAnimator;
|
||||||
|
import android.animation.PropertyValuesHolder;
|
||||||
|
import android.animation.RectEvaluator;
|
||||||
|
import android.animation.ValueAnimator;
|
||||||
|
import android.animation.ValueAnimator.AnimatorUpdateListener;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.util.FloatProperty;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.android.launcher3.config.FeatureFlags;
|
||||||
|
import com.android.launcher3.util.Themes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper class to draw background of a focused item.
|
||||||
|
* @param <T> Item type
|
||||||
|
*/
|
||||||
|
public abstract class ItemFocusIndicatorHelper<T> implements AnimatorUpdateListener {
|
||||||
|
|
||||||
|
private static final float MIN_VISIBLE_ALPHA = 0.2f;
|
||||||
|
private static final long ANIM_DURATION = 150;
|
||||||
|
|
||||||
|
public static final FloatProperty<ItemFocusIndicatorHelper> ALPHA =
|
||||||
|
new FloatProperty<ItemFocusIndicatorHelper>("alpha") {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValue(ItemFocusIndicatorHelper object, float value) {
|
||||||
|
object.setAlpha(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Float get(ItemFocusIndicatorHelper object) {
|
||||||
|
return object.mAlpha;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static final FloatProperty<ItemFocusIndicatorHelper> SHIFT =
|
||||||
|
new FloatProperty<ItemFocusIndicatorHelper>("shift") {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValue(ItemFocusIndicatorHelper object, float value) {
|
||||||
|
object.mShift = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Float get(ItemFocusIndicatorHelper object) {
|
||||||
|
return object.mShift;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final RectEvaluator RECT_EVALUATOR = new RectEvaluator(new Rect());
|
||||||
|
private static final Rect sTempRect1 = new Rect();
|
||||||
|
private static final Rect sTempRect2 = new Rect();
|
||||||
|
|
||||||
|
private final View mContainer;
|
||||||
|
protected final Paint mPaint;
|
||||||
|
private final int mMaxAlpha;
|
||||||
|
|
||||||
|
private final Rect mDirtyRect = new Rect();
|
||||||
|
private boolean mIsDirty = false;
|
||||||
|
|
||||||
|
private T mLastFocusedItem;
|
||||||
|
|
||||||
|
private T mCurrentItem;
|
||||||
|
private T mTargetItem;
|
||||||
|
/**
|
||||||
|
* The fraction indicating the position of the focusRect between {@link #mCurrentItem}
|
||||||
|
* & {@link #mTargetItem}
|
||||||
|
*/
|
||||||
|
private float mShift;
|
||||||
|
|
||||||
|
private ObjectAnimator mCurrentAnimation;
|
||||||
|
private float mAlpha;
|
||||||
|
private float mRadius;
|
||||||
|
|
||||||
|
public ItemFocusIndicatorHelper(View container, int color) {
|
||||||
|
mContainer = container;
|
||||||
|
|
||||||
|
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
mMaxAlpha = Color.alpha(color);
|
||||||
|
mPaint.setColor(0xFF000000 | color);
|
||||||
|
|
||||||
|
setAlpha(0);
|
||||||
|
mShift = 0;
|
||||||
|
if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
|
||||||
|
mRadius = Themes.getDialogCornerRadius(container.getContext());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setAlpha(float alpha) {
|
||||||
|
mAlpha = alpha;
|
||||||
|
mPaint.setAlpha((int) (mAlpha * mMaxAlpha));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationUpdate(ValueAnimator animation) {
|
||||||
|
invalidateDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void invalidateDirty() {
|
||||||
|
if (mIsDirty) {
|
||||||
|
mContainer.invalidate(mDirtyRect);
|
||||||
|
mIsDirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect newRect = getDrawRect();
|
||||||
|
if (newRect != null) {
|
||||||
|
mContainer.invalidate(newRect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws the indicator on the canvas
|
||||||
|
*/
|
||||||
|
public void draw(Canvas c) {
|
||||||
|
if (mAlpha <= 0) return;
|
||||||
|
|
||||||
|
Rect newRect = getDrawRect();
|
||||||
|
if (newRect != null) {
|
||||||
|
mDirtyRect.set(newRect);
|
||||||
|
c.drawRoundRect((float) mDirtyRect.left, (float) mDirtyRect.top,
|
||||||
|
(float) mDirtyRect.right, (float) mDirtyRect.bottom,
|
||||||
|
mRadius, mRadius, mPaint);
|
||||||
|
mIsDirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Rect getDrawRect() {
|
||||||
|
if (mCurrentItem != null && shouldDraw(mCurrentItem)) {
|
||||||
|
viewToRect(mCurrentItem, sTempRect1);
|
||||||
|
|
||||||
|
if (mShift > 0 && mTargetItem != null) {
|
||||||
|
viewToRect(mTargetItem, sTempRect2);
|
||||||
|
return RECT_EVALUATOR.evaluate(mShift, sTempRect1, sTempRect2);
|
||||||
|
} else {
|
||||||
|
return sTempRect1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the provided item is valid
|
||||||
|
*/
|
||||||
|
protected boolean shouldDraw(T item) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void changeFocus(T item, boolean hasFocus) {
|
||||||
|
if (hasFocus) {
|
||||||
|
endCurrentAnimation();
|
||||||
|
|
||||||
|
if (mAlpha > MIN_VISIBLE_ALPHA) {
|
||||||
|
mTargetItem = item;
|
||||||
|
|
||||||
|
mCurrentAnimation = ObjectAnimator.ofPropertyValuesHolder(this,
|
||||||
|
PropertyValuesHolder.ofFloat(ALPHA, 1),
|
||||||
|
PropertyValuesHolder.ofFloat(SHIFT, 1));
|
||||||
|
mCurrentAnimation.addListener(new ViewSetListener(item, true));
|
||||||
|
} else {
|
||||||
|
setCurrentItem(item);
|
||||||
|
|
||||||
|
mCurrentAnimation = ObjectAnimator.ofPropertyValuesHolder(this,
|
||||||
|
PropertyValuesHolder.ofFloat(ALPHA, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
mLastFocusedItem = item;
|
||||||
|
} else {
|
||||||
|
if (mLastFocusedItem == item) {
|
||||||
|
mLastFocusedItem = null;
|
||||||
|
endCurrentAnimation();
|
||||||
|
mCurrentAnimation = ObjectAnimator.ofPropertyValuesHolder(this,
|
||||||
|
PropertyValuesHolder.ofFloat(ALPHA, 0));
|
||||||
|
mCurrentAnimation.addListener(new ViewSetListener(null, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// invalidate once
|
||||||
|
invalidateDirty();
|
||||||
|
|
||||||
|
mLastFocusedItem = hasFocus ? item : null;
|
||||||
|
if (mCurrentAnimation != null) {
|
||||||
|
mCurrentAnimation.addUpdateListener(this);
|
||||||
|
mCurrentAnimation.setDuration(ANIM_DURATION).start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void endCurrentAnimation() {
|
||||||
|
if (mCurrentAnimation != null) {
|
||||||
|
mCurrentAnimation.cancel();
|
||||||
|
mCurrentAnimation = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setCurrentItem(T item) {
|
||||||
|
mCurrentItem = item;
|
||||||
|
mShift = 0;
|
||||||
|
mTargetItem = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the position of the item relative to {@link #mContainer}.
|
||||||
|
*/
|
||||||
|
public abstract void viewToRect(T item, Rect outRect);
|
||||||
|
|
||||||
|
private class ViewSetListener extends AnimatorListenerAdapter {
|
||||||
|
private final T mItemToSet;
|
||||||
|
private final boolean mCallOnCancel;
|
||||||
|
private boolean mCalled = false;
|
||||||
|
|
||||||
|
ViewSetListener(T item, boolean callOnCancel) {
|
||||||
|
mItemToSet = item;
|
||||||
|
mCallOnCancel = callOnCancel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationCancel(Animator animation) {
|
||||||
|
if (!mCallOnCancel) {
|
||||||
|
mCalled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
if (!mCalled) {
|
||||||
|
setCurrentItem(mItemToSet);
|
||||||
|
mCalled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,342 @@
|
||||||
|
/*
|
||||||
|
* 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.keyboard;
|
||||||
|
|
||||||
|
import static android.app.Activity.DEFAULT_KEYS_SEARCH_LOCAL;
|
||||||
|
|
||||||
|
import static com.android.launcher3.LauncherState.SPRING_LOADED;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint.Style;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewParent;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
|
||||||
|
|
||||||
|
import com.android.launcher3.AbstractFloatingView;
|
||||||
|
import com.android.launcher3.CellLayout;
|
||||||
|
import com.android.launcher3.Insettable;
|
||||||
|
import com.android.launcher3.Launcher;
|
||||||
|
import com.android.launcher3.LauncherState;
|
||||||
|
import com.android.launcher3.PagedView;
|
||||||
|
import com.android.launcher3.R;
|
||||||
|
import com.android.launcher3.Utilities;
|
||||||
|
import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
|
||||||
|
import com.android.launcher3.dragndrop.DragOptions;
|
||||||
|
import com.android.launcher3.folder.Folder;
|
||||||
|
import com.android.launcher3.model.data.ItemInfo;
|
||||||
|
import com.android.launcher3.statemanager.StateManager.StateListener;
|
||||||
|
import com.android.launcher3.touch.ItemLongClickListener;
|
||||||
|
import com.android.launcher3.util.Themes;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.ToIntBiFunction;
|
||||||
|
import java.util.function.ToIntFunction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A floating view to allow keyboard navigation across virtual nodes
|
||||||
|
*/
|
||||||
|
public class KeyboardDragAndDropView extends AbstractFloatingView
|
||||||
|
implements Insettable, StateListener<LauncherState> {
|
||||||
|
|
||||||
|
private static final long MINOR_AXIS_WEIGHT = 13;
|
||||||
|
|
||||||
|
private final ArrayList<Integer> mIntList = new ArrayList<>();
|
||||||
|
private final ArrayList<DragAndDropAccessibilityDelegate> mDelegates = new ArrayList<>();
|
||||||
|
private final ArrayList<VirtualNodeInfo> mNodes = new ArrayList<>();
|
||||||
|
|
||||||
|
private final Rect mTempRect = new Rect();
|
||||||
|
private final Rect mTempRect2 = new Rect();
|
||||||
|
private final AccessibilityNodeInfoCompat mTempNodeInfo = AccessibilityNodeInfoCompat.obtain();
|
||||||
|
|
||||||
|
private final RectFocusIndicator mFocusIndicator;
|
||||||
|
|
||||||
|
private final Launcher mLauncher;
|
||||||
|
private VirtualNodeInfo mCurrentSelection;
|
||||||
|
|
||||||
|
|
||||||
|
public KeyboardDragAndDropView(Context context, AttributeSet attrs) {
|
||||||
|
this(context, attrs, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyboardDragAndDropView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
mLauncher = Launcher.getLauncher(context);
|
||||||
|
mFocusIndicator = new RectFocusIndicator(this);
|
||||||
|
setWillNotDraw(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void handleClose(boolean animate) {
|
||||||
|
mLauncher.getDragLayer().removeView(this);
|
||||||
|
mLauncher.getStateManager().removeStateListener(this);
|
||||||
|
mLauncher.setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
|
||||||
|
mIsOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isOfType(int type) {
|
||||||
|
return (type & TYPE_DRAG_DROP_POPUP) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
|
||||||
|
// Consume all touch
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setInsets(Rect insets) {
|
||||||
|
setPadding(insets.left, insets.top, insets.right, insets.bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStateTransitionStart(LauncherState toState) {
|
||||||
|
if (toState != SPRING_LOADED) {
|
||||||
|
close(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStateTransitionComplete(LauncherState finalState) {
|
||||||
|
if (mCurrentSelection != null) {
|
||||||
|
setCurrentSelection(mCurrentSelection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setCurrentSelection(VirtualNodeInfo nodeInfo) {
|
||||||
|
mCurrentSelection = nodeInfo;
|
||||||
|
((TextView) findViewById(R.id.label))
|
||||||
|
.setText(nodeInfo.populate(mTempNodeInfo).getContentDescription());
|
||||||
|
|
||||||
|
Rect bounds = new Rect();
|
||||||
|
mTempNodeInfo.getBoundsInParent(bounds);
|
||||||
|
View host = nodeInfo.delegate.getHost();
|
||||||
|
ViewParent parent = host.getParent();
|
||||||
|
if (parent instanceof PagedView) {
|
||||||
|
PagedView pv = (PagedView) parent;
|
||||||
|
int pageIndex = pv.indexOfChild(host);
|
||||||
|
|
||||||
|
pv.setCurrentPage(pageIndex);
|
||||||
|
bounds.offset(pv.getScrollX() - pv.getScrollForPage(pageIndex), 0);
|
||||||
|
}
|
||||||
|
float[] pos = new float[] {bounds.left, bounds.top, bounds.right, bounds.bottom};
|
||||||
|
Utilities.getDescendantCoordRelativeToAncestor(host, mLauncher.getDragLayer(), pos, true);
|
||||||
|
|
||||||
|
new RectF(pos[0], pos[1], pos[2], pos[3]).roundOut(bounds);
|
||||||
|
mFocusIndicator.changeFocus(bounds, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDraw(Canvas canvas) {
|
||||||
|
mFocusIndicator.draw(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean dispatchUnhandledMove(View focused, int direction) {
|
||||||
|
VirtualNodeInfo nodeInfo = getNextSelection(direction);
|
||||||
|
if (nodeInfo == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
setCurrentSelection(nodeInfo);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Focus finding logic:
|
||||||
|
* Collect all virtual nodes in reading order (used for forward and backwards).
|
||||||
|
* Then find the closest view by comparing the distances spatially. Since it is a move
|
||||||
|
* operation. consider all cell sizes to be approximately of the same size.
|
||||||
|
*/
|
||||||
|
private VirtualNodeInfo getNextSelection(int direction) {
|
||||||
|
// Collect all virtual nodes
|
||||||
|
mDelegates.clear();
|
||||||
|
mNodes.clear();
|
||||||
|
|
||||||
|
Folder openFolder = Folder.getOpen(mLauncher);
|
||||||
|
PagedView pv = openFolder == null ? mLauncher.getWorkspace() : openFolder.getContent();
|
||||||
|
int count = pv.getPageCount();
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
mDelegates.add(((CellLayout) pv.getChildAt(i)).getDragAndDropAccessibilityDelegate());
|
||||||
|
}
|
||||||
|
if (openFolder == null) {
|
||||||
|
mDelegates.add(pv.getNextPage() + 1,
|
||||||
|
mLauncher.getHotseat().getDragAndDropAccessibilityDelegate());
|
||||||
|
}
|
||||||
|
mDelegates.forEach(delegate -> {
|
||||||
|
mIntList.clear();
|
||||||
|
delegate.getVisibleVirtualViews(mIntList);
|
||||||
|
mIntList.forEach(id -> mNodes.add(new VirtualNodeInfo(delegate, id)));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (mNodes.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int index = mNodes.indexOf(mCurrentSelection);
|
||||||
|
if (mCurrentSelection == null || index < 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int totalNodes = mNodes.size();
|
||||||
|
|
||||||
|
final ToIntBiFunction<Rect, Rect> majorAxis;
|
||||||
|
final ToIntFunction<Rect> minorAxis;
|
||||||
|
|
||||||
|
switch (direction) {
|
||||||
|
case View.FOCUS_RIGHT:
|
||||||
|
majorAxis = (source, dest) -> dest.left - source.left;
|
||||||
|
minorAxis = Rect::centerY;
|
||||||
|
break;
|
||||||
|
case View.FOCUS_LEFT:
|
||||||
|
majorAxis = (source, dest) -> source.left - dest.left;
|
||||||
|
minorAxis = Rect::centerY;
|
||||||
|
break;
|
||||||
|
case View.FOCUS_UP:
|
||||||
|
majorAxis = (source, dest) -> source.top - dest.top;
|
||||||
|
minorAxis = Rect::centerX;
|
||||||
|
break;
|
||||||
|
case View.FOCUS_DOWN:
|
||||||
|
majorAxis = (source, dest) -> dest.top - source.top;
|
||||||
|
minorAxis = Rect::centerX;
|
||||||
|
break;
|
||||||
|
case View.FOCUS_FORWARD:
|
||||||
|
return mNodes.get((index + 1) % totalNodes);
|
||||||
|
case View.FOCUS_BACKWARD:
|
||||||
|
return mNodes.get((index + totalNodes - 1) % totalNodes);
|
||||||
|
default:
|
||||||
|
// Unknown direction
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
mCurrentSelection.populate(mTempNodeInfo).getBoundsInScreen(mTempRect);
|
||||||
|
|
||||||
|
float minWeight = Float.MAX_VALUE;
|
||||||
|
VirtualNodeInfo match = null;
|
||||||
|
for (int i = 0; i < totalNodes; i++) {
|
||||||
|
VirtualNodeInfo node = mNodes.get(i);
|
||||||
|
node.populate(mTempNodeInfo).getBoundsInScreen(mTempRect2);
|
||||||
|
|
||||||
|
int majorAxisWeight = majorAxis.applyAsInt(mTempRect, mTempRect2);
|
||||||
|
if (majorAxisWeight <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int minorAxisWeight = minorAxis.applyAsInt(mTempRect2)
|
||||||
|
- minorAxis.applyAsInt(mTempRect);
|
||||||
|
|
||||||
|
float weight = majorAxisWeight * majorAxisWeight
|
||||||
|
+ minorAxisWeight * minorAxisWeight * MINOR_AXIS_WEIGHT;
|
||||||
|
if (weight < minWeight) {
|
||||||
|
minWeight = weight;
|
||||||
|
match = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||||
|
if (keyCode == KeyEvent.KEYCODE_ENTER && mCurrentSelection != null) {
|
||||||
|
mCurrentSelection.delegate.onPerformActionForVirtualView(
|
||||||
|
mCurrentSelection.id, AccessibilityNodeInfoCompat.ACTION_CLICK, null);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.onKeyUp(keyCode, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the keyboard drag popup for the provided view
|
||||||
|
*/
|
||||||
|
public void showForIcon(View icon, ItemInfo item, DragOptions dragOptions) {
|
||||||
|
mIsOpen = true;
|
||||||
|
mLauncher.getDragLayer().addView(this);
|
||||||
|
mLauncher.getStateManager().addStateListener(this);
|
||||||
|
|
||||||
|
// Find current selection
|
||||||
|
CellLayout currentParent = (CellLayout) icon.getParent().getParent();
|
||||||
|
float[] iconPos = new float[] {currentParent.getCellWidth() / 2,
|
||||||
|
currentParent.getCellHeight() / 2};
|
||||||
|
Utilities.getDescendantCoordRelativeToAncestor(icon, currentParent, iconPos, false);
|
||||||
|
|
||||||
|
ItemLongClickListener.beginDrag(icon, mLauncher, item, dragOptions);
|
||||||
|
|
||||||
|
DragAndDropAccessibilityDelegate dndDelegate =
|
||||||
|
currentParent.getDragAndDropAccessibilityDelegate();
|
||||||
|
setCurrentSelection(new VirtualNodeInfo(
|
||||||
|
dndDelegate, dndDelegate.getVirtualViewAt(iconPos[0], iconPos[1])));
|
||||||
|
|
||||||
|
mLauncher.setDefaultKeyMode(Activity.DEFAULT_KEYS_DISABLE);
|
||||||
|
requestFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class VirtualNodeInfo {
|
||||||
|
public final DragAndDropAccessibilityDelegate delegate;
|
||||||
|
public final int id;
|
||||||
|
|
||||||
|
VirtualNodeInfo(DragAndDropAccessibilityDelegate delegate, int id) {
|
||||||
|
this.id = id;
|
||||||
|
this.delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof VirtualNodeInfo)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
VirtualNodeInfo that = (VirtualNodeInfo) o;
|
||||||
|
return id == that.id && delegate.equals(that.delegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccessibilityNodeInfoCompat populate(AccessibilityNodeInfoCompat nodeInfo) {
|
||||||
|
delegate.onPopulateNodeForVirtualView(id, nodeInfo);
|
||||||
|
return nodeInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getBounds(AccessibilityNodeInfoCompat nodeInfo, Rect out) {
|
||||||
|
delegate.onPopulateNodeForVirtualView(id, nodeInfo);
|
||||||
|
nodeInfo.getBoundsInScreen(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(id, delegate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class RectFocusIndicator extends ItemFocusIndicatorHelper<Rect> {
|
||||||
|
|
||||||
|
RectFocusIndicator(View container) {
|
||||||
|
super(container, Themes.getColorAccent(container.getContext()));
|
||||||
|
mPaint.setStrokeWidth(container.getResources()
|
||||||
|
.getDimension(R.dimen.keyboard_drag_stroke_width));
|
||||||
|
mPaint.setStyle(Style.STROKE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void viewToRect(Rect item, Rect outRect) {
|
||||||
|
outRect.set(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -454,12 +454,6 @@ public abstract class BaseDragLayer<T extends Context & ActivityContext>
|
||||||
r.set(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight());
|
r.set(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean dispatchUnhandledMove(View focused, int direction) {
|
|
||||||
// Consume the unhandled move if a container is open, to avoid switching pages underneath.
|
|
||||||
return AbstractFloatingView.getTopOpenView(mActivity) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
|
protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
|
||||||
View topView = AbstractFloatingView.getTopOpenView(mActivity);
|
View topView = AbstractFloatingView.getTopOpenView(mActivity);
|
||||||
|
|
Loading…
Reference in New Issue