mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-19 18:58:19 +00:00
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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user