Taskbar drag starts internal pre-drag before system drag

- TaskbarDragController now extends DragController.
- Currently there is no pre-drag condition, so we immediately get onDragStart(), which starts the system global drag (which cancels the original internal drag).
- Make the original view invisible during the drag and drop operation, across both internal and system drag events.
- No longer handle onDragEvent() in TaskbarView, as TaskbarDragController handles all of it now.

Test: Drag and drop from taskbar still works (bonus: starts from the correct registration point that you touched down on). Locally added a PreDragCondition and verified a seamless handoff to system drag and drop when the pre drag end condition was met.
Bug: 182981908
Change-Id: I6bf48141a5eedfc6db6f461258e880ef8146e733
This commit is contained in:
Tony Wickham
2021-05-24 15:47:38 -07:00
parent c7cbf254f3
commit 8ac277ebd8
10 changed files with 381 additions and 118 deletions

View File

@@ -20,19 +20,35 @@ import static android.view.View.VISIBLE;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.view.DragEvent;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.Nullable;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.accessibility.DragViewStateAnnouncer;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragDriver;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.graphics.DragPreviewProvider;
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ClipDescriptionCompat;
@@ -41,14 +57,20 @@ import com.android.systemui.shared.system.LauncherAppsCompat;
/**
* Handles long click on Taskbar items to start a system drag and drop operation.
*/
public class TaskbarDragController {
public class TaskbarDragController extends DragController<TaskbarActivityContext> {
private final Context mContext;
private final int mDragIconSize;
private final int[] mTempXY = new int[2];
public TaskbarDragController(Context context) {
mContext = context;
Resources resources = mContext.getResources();
// Where the initial touch was relative to the dragged icon.
private int mRegistrationX;
private int mRegistrationY;
private boolean mIsSystemDragInProgress;
public TaskbarDragController(TaskbarActivityContext activity) {
super(activity);
Resources resources = mActivity.getResources();
mDragIconSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_drag_icon_size);
}
@@ -57,18 +79,146 @@ public class TaskbarDragController {
* generate the ClipDescription and Intent.
* @return Whether {@link View#startDragAndDrop} started successfully.
*/
protected boolean startSystemDragOnLongClick(View view) {
protected boolean startDragOnLongClick(View view) {
if (!(view instanceof BubbleTextView)) {
return false;
}
BubbleTextView btv = (BubbleTextView) view;
View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view) {
mActivity.setTaskbarWindowFullscreen(true);
view.post(() -> {
startInternalDrag(btv);
btv.setVisibility(INVISIBLE);
});
return true;
}
private void startInternalDrag(BubbleTextView btv) {
float iconScale = 1f;
Drawable icon = btv.getIcon();
if (icon instanceof FastBitmapDrawable) {
iconScale = ((FastBitmapDrawable) icon).getAnimatedScale();
}
// Clear the pressed state if necessary
btv.clearFocus();
btv.setPressed(false);
btv.clearPressedBackground();
final DragPreviewProvider previewProvider = new DragPreviewProvider(btv);
final Drawable drawable = previewProvider.createDrawable();
final float scale = previewProvider.getScaleAndPosition(drawable, mTempXY);
int dragLayerX = mTempXY[0];
int dragLayerY = mTempXY[1];
Rect dragRect = new Rect();
btv.getSourceVisualDragBounds(dragRect);
dragLayerY += dragRect.top;
DragOptions dragOptions = new DragOptions();
// TODO: open popup/pre-drag
// PopupContainerWithArrow popupContainer = PopupContainerWithArrow.showForIcon(view);
// if (popupContainer != null) {
// dragOptions.preDragCondition = popupContainer.createPreDragCondition();
// }
startDrag(
drawable,
/* view = */ null,
/* originalView = */ btv,
dragLayerX,
dragLayerY,
(View target, DropTarget.DragObject d, boolean success) -> {} /* DragSource */,
(WorkspaceItemInfo) btv.getTag(),
/* dragVisualizeOffset = */ null,
dragRect,
scale * iconScale,
scale,
dragOptions);
}
@Override
protected DragView startDrag(@Nullable Drawable drawable, @Nullable View view,
DraggableView originalView, int dragLayerX, int dragLayerY, DragSource source,
ItemInfo dragInfo, Point dragOffset, Rect dragRegion, float initialDragViewScale,
float dragViewScaleOnDrop, DragOptions options) {
mOptions = options;
mRegistrationX = mMotionDown.x - dragLayerX;
mRegistrationY = mMotionDown.y - dragLayerY;
final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left;
final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;
mLastDropTarget = null;
mDragObject = new DropTarget.DragObject(mActivity.getApplicationContext());
mDragObject.originalView = originalView;
mIsInPreDrag = mOptions.preDragCondition != null
&& !mOptions.preDragCondition.shouldStartDrag(0);
float scalePx = mDragIconSize - dragRegion.width();
final DragView dragView = mDragObject.dragView = new TaskbarDragView(
mActivity,
drawable,
mRegistrationX,
mRegistrationY,
initialDragViewScale,
dragViewScaleOnDrop,
scalePx);
dragView.setItemInfo(dragInfo);
mDragObject.dragComplete = false;
mDragObject.xOffset = mMotionDown.x - (dragLayerX + dragRegionLeft);
mDragObject.yOffset = mMotionDown.y - (dragLayerY + dragRegionTop);
mDragDriver = DragDriver.create(this, mOptions, /* secondaryEventConsumer = */ ev -> {});
if (!mOptions.isAccessibleDrag) {
mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView);
}
mDragObject.dragSource = source;
mDragObject.dragInfo = dragInfo;
mDragObject.originalDragInfo = mDragObject.dragInfo.makeShallowCopy();
if (dragRegion != null) {
dragView.setDragRegion(new Rect(dragRegion));
}
dragView.show(mLastTouch.x, mLastTouch.y);
mDistanceSinceScroll = 0;
if (!mIsInPreDrag) {
callOnDragStart();
} else if (mOptions.preDragCondition != null) {
mOptions.preDragCondition.onPreDragStart(mDragObject);
}
handleMoveEvent(mLastTouch.x, mLastTouch.y);
return dragView;
}
@Override
protected void callOnDragStart() {
super.callOnDragStart();
// Pre-drag has ended, start the global system drag.
AbstractFloatingView.closeAllOpenViews(mActivity);
startSystemDrag((BubbleTextView) mDragObject.originalView);
}
private void startSystemDrag(BubbleTextView btv) {
View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(btv) {
@Override
public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
shadowSize.set(mDragIconSize, mDragIconSize);
// TODO: should be based on last touch point on the icon.
shadowTouchPoint.set(shadowSize.x / 2, shadowSize.y / 2);
// 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 offset = (mDragIconSize - btv.getIconSize()) / 2;
shadowTouchPoint.set(mRegistrationX + offset, mRegistrationY + offset);
}
@Override
@@ -81,12 +231,12 @@ public class TaskbarDragController {
}
};
Object tag = view.getTag();
Object tag = btv.getTag();
ClipDescription clipDescription = null;
Intent intent = null;
if (tag instanceof WorkspaceItemInfo) {
WorkspaceItemInfo item = (WorkspaceItemInfo) tag;
LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
LauncherApps launcherApps = mActivity.getSystemService(LauncherApps.class);
clipDescription = new ClipDescription(item.title,
new String[] {
item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
@@ -116,28 +266,89 @@ public class TaskbarDragController {
if (clipDescription != null && intent != null) {
ClipData clipData = new ClipData(clipDescription, new ClipData.Item(intent));
view.setOnDragListener(getDraggedViewDragListener());
return view.startDragAndDrop(clipData, shadowBuilder, null /* localState */,
View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_OPAQUE);
if (btv.startDragAndDrop(clipData, shadowBuilder, null /* localState */,
View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_OPAQUE)) {
onSystemDragStarted();
}
}
return false;
}
/**
* Hide the original Taskbar item while it is being dragged.
*/
private View.OnDragListener getDraggedViewDragListener() {
return (view, dragEvent) -> {
private void onSystemDragStarted() {
mIsSystemDragInProgress = true;
mActivity.getDragLayer().setOnDragListener((view, dragEvent) -> {
switch (dragEvent.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
view.setVisibility(INVISIBLE);
// Return true to tell system we are interested in events, so we get DRAG_ENDED.
return true;
case DragEvent.ACTION_DRAG_ENDED:
view.setVisibility(VISIBLE);
view.setOnDragListener(null);
mIsSystemDragInProgress = false;
maybeOnDragEnd();
return true;
}
return false;
};
});
}
@Override
public boolean isDragging() {
return super.isDragging() || mIsSystemDragInProgress;
}
/**
* Whether we started dragging the given view and the drag is still in progress.
*/
public boolean isDraggingView(View child) {
return isDragging() && mDragObject != null && mDragObject.originalView == child;
}
private void maybeOnDragEnd() {
if (!isDragging()) {
((View) mDragObject.originalView).setVisibility(VISIBLE);
}
}
@Override
protected void callOnDragEnd() {
super.callOnDragEnd();
maybeOnDragEnd();
}
@Override
protected float getX(MotionEvent ev) {
// We will resize to fill the screen while dragging, so use screen coordinates. This ensures
// we start at the correct position even though touch down is on the smaller DragLayer size.
return ev.getRawX();
}
@Override
protected float getY(MotionEvent ev) {
// We will resize to fill the screen while dragging, so use screen coordinates. This ensures
// we start at the correct position even though touch down is on the smaller DragLayer size.
return ev.getRawY();
}
@Override
protected Point getClampedDragLayerPos(float x, float y) {
// No need to clamp, as we will take up the entire screen.
mTmpPoint.set(Math.round(x), Math.round(y));
return mTmpPoint;
}
@Override
protected void exitDrag() {
if (mDragObject != null) {
mActivity.getDragLayer().removeView(mDragObject.dragView);
}
}
@Override
public void addDropTarget(DropTarget target) {
// No-op as Taskbar currently doesn't support any drop targets internally.
// Note: if we do add internal DropTargets, we'll still need to ignore Folder.
}
@Override
protected DropTarget getDefaultDropTarget(int[] dropCoordinates) {
return null;
}
}