Adding support for multiwindow drag and drop

Change-Id: I95b46e3c3f1238307d3ef5a6c81a8e530ba0987a
This commit is contained in:
Sunny Goyal
2016-09-12 14:36:30 -07:00
parent 1e35fbbac7
commit d139b0aa7d
9 changed files with 301 additions and 106 deletions
@@ -259,7 +259,8 @@ public abstract class ButtonDropTarget extends TextView
}
};
dragLayer.animateView(d.dragView, from, to, scale, 1f, 1f, 0.1f, 0.1f,
DRAG_VIEW_DROP_DURATION, new DecelerateInterpolator(2),
mLauncher.getDragController().isExternalDrag() ? 1 : DRAG_VIEW_DROP_DURATION,
new DecelerateInterpolator(2),
new LinearInterpolator(), onAnimationEndRunnable,
DragLayer.ANIMATION_END_DISAPPEAR, null);
}
+1 -1
View File
@@ -158,7 +158,7 @@ public class ShortcutInfo extends ItemInfo {
*/
Intent promisedIntent;
ShortcutInfo() {
public ShortcutInfo() {
itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT;
}
@@ -0,0 +1,76 @@
/*
* Copyright (C) 2016 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.dragndrop;
import android.content.Context;
import android.view.View;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
/**
* DragSource used when the drag started at another window.
*/
public class AnotherWindowDragSource implements DragSource {
private final Context mContext;
AnotherWindowDragSource(Context context) {
mContext = context;
}
@Override
public boolean supportsFlingToDelete() {
return false;
}
@Override
public boolean supportsAppInfoDropTarget() {
return false;
}
@Override
public boolean supportsDeleteDropTarget() {
return false;
}
@Override
public float getIntrinsicIconScaleFactor() {
return 1;
}
@Override
public void onFlingToDeleteCompleted() {
}
@Override
public void onDropCompleted(View target, DragObject d,
boolean isFlingToDelete, boolean success) {
if (!success) {
Launcher.getLauncher(mContext).exitSpringLoadedDragModeDelayed(false, 0, null);
}
}
@Override
public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
// TODO: Probably log something
}
}
@@ -25,7 +25,6 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
import android.view.DragEvent;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
@@ -213,6 +212,12 @@ public class DragController implements DragDriver.EventListener, TouchController
}
mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0);
mOptions = options;
if (mOptions.systemDndStartPoint != null) {
mMotionDownX = mOptions.systemDndStartPoint.x;
mMotionDownY = mOptions.systemDndStartPoint.y;
}
final int registrationX = mMotionDownX - dragLayerX;
final int registrationY = mMotionDownY - dragLayerY;
@@ -221,7 +226,6 @@ public class DragController implements DragDriver.EventListener, TouchController
mLastDropTarget = null;
mOptions = options;
mDragObject = new DropTarget.DragObject();
final Resources res = mLauncher.getResources();
@@ -241,7 +245,7 @@ public class DragController implements DragDriver.EventListener, TouchController
mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop);
mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView);
mDragDriver = DragDriver.create(this, dragInfo, dragView);
mDragDriver = DragDriver.create(mLauncher, this, mDragObject, mOptions);
}
mDragObject.dragSource = source;
@@ -293,6 +297,10 @@ public class DragController implements DragDriver.EventListener, TouchController
return mDragDriver != null || (mOptions != null && mOptions.isAccessibleDrag);
}
public boolean isExternalDrag() {
return (mOptions != null && mOptions.systemDndStartPoint != null);
}
/**
* Stop dragging without dropping.
*/
@@ -17,18 +17,19 @@
package com.android.launcher3.dragndrop;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Context;
import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Point;
import android.view.DragEvent;
import android.view.MotionEvent;
import android.view.View;
import com.android.launcher3.AnotherWindowDropTarget;
import com.android.launcher3.DropTarget;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.InstallShortcutReceiver;
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import java.util.ArrayList;
/**
* Base class for driving a drag/drop operation.
@@ -50,7 +51,7 @@ public abstract class DragDriver {
/**
* Handles ending of the DragView animation.
*/
public abstract void onDragViewAnimationEnd();
public void onDragViewAnimationEnd() { }
public boolean onTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
@@ -89,100 +90,46 @@ public abstract class DragDriver {
return true;
}
public static DragDriver create(
DragController dragController, ItemInfo dragInfo, DragView dragView) {
if (FeatureFlags.LAUNCHER3_USE_SYSTEM_DRAG_DRIVER && Utilities.isNycOrAbove()) {
return new SystemDragDriver(dragController, dragInfo.getIntent(), dragView);
public static DragDriver create(Context context, DragController dragController,
DragObject dragObject, DragOptions options) {
if (Utilities.isNycOrAbove() && options.systemDndStartPoint != null) {
return new SystemDragDriver(dragController, context, dragObject);
} else {
return new InternalDragDriver(dragController);
}
}
};
}
/**
* Class for driving a system (i.e. framework) drag/drop operation.
*/
class SystemDragDriver extends DragDriver {
/** Intent associated with the drag operation, or null is there no associated intent. */
private final Intent mDragIntent;
private final DragView mDragView;
boolean mIsFrameworkDragActive = false;
private final DragObject mDragObject;
private final Context mContext;
boolean mReceivedDropEvent = false;
float mLastX = 0;
float mLastY = 0;
public SystemDragDriver(DragController dragController, Intent dragIntent, DragView dragView) {
public SystemDragDriver(DragController dragController, Context context, DragObject dragObject) {
super(dragController);
mDragIntent = dragIntent;
mDragView = dragView;
}
private static class ShadowBuilder extends View.DragShadowBuilder {
final DragView mDragView;
public ShadowBuilder(DragView dragView) {
mDragView = dragView;
}
@Override
public void onProvideShadowMetrics (Point size, Point touch) {
mDragView.provideDragShadowMetrics(size, touch);
}
@Override
public void onDrawShadow(Canvas canvas) {
mDragView.drawDragShadow(canvas);
}
};
@Override
public void onDragViewAnimationEnd() {
// Clip data for the drag operation. If there is an intent, create an intent-based ClipData,
// which will be passed to a global DND.
// If there is no intent, craft a fake ClipData and start a local DND operation; this
// ClipData will be ignored.
final ClipData dragData = mDragIntent != null ?
ClipData.newIntent("", mDragIntent) :
ClipData.newPlainText("", "");
View.DragShadowBuilder shadowBuilder = new ShadowBuilder(mDragView);
// TODO: DND flags are in flux, once settled, use the appropriate constant.
final int flagGlobal = 1 << 0;
final int flagOpaque = 1 << 9;
final int flags = (mDragIntent != null ? flagGlobal : 0) | flagOpaque;
mIsFrameworkDragActive = true;
if (!mDragView.startDrag(dragData, shadowBuilder, null, flags)) {
mIsFrameworkDragActive = false;
mEventListener.onDriverDragCancel();
return;
}
// Starting from this point, the driver takes over showing the drag shadow, so hiding the
// drag view.
mDragView.setVisibility(View.INVISIBLE);
mDragObject = dragObject;
mContext = context;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
return !mIsFrameworkDragActive && super.onTouchEvent(ev);
return false;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return !mIsFrameworkDragActive && super.onInterceptTouchEvent(ev);
return false;
}
@Override
public boolean onDragEvent (DragEvent event) {
if (!mIsFrameworkDragActive) {
// We are interested only in drag events started by this driver.
return false;
}
final int action = event.getAction();
switch (action) {
@@ -192,8 +139,6 @@ class SystemDragDriver extends DragDriver {
return true;
case DragEvent.ACTION_DRAG_ENTERED:
mLastX = event.getX();
mLastY = event.getY();
return true;
case DragEvent.ACTION_DRAG_LOCATION:
@@ -205,35 +150,66 @@ class SystemDragDriver extends DragDriver {
case DragEvent.ACTION_DROP:
mLastX = event.getX();
mLastY = event.getY();
mReceivedDropEvent = true;
return true;
mReceivedDropEvent =
updateInfoFromClipData(event.getClipData(), event.getClipDescription());
return mReceivedDropEvent;
case DragEvent.ACTION_DRAG_EXITED:
mLastX = event.getX();
mLastY = event.getY();
mEventListener.onDriverDragExitWindow();
return true;
case DragEvent.ACTION_DRAG_ENDED:
final boolean dragAccepted = event.getResult();
final boolean acceptedByAnotherWindow = dragAccepted && !mReceivedDropEvent;
// When the system drag ends, its drag shadow disappears. Resume showing the drag
// view for the possible final animation.
mDragView.setVisibility(View.VISIBLE);
final DropTarget dropTargetOverride = acceptedByAnotherWindow ?
new AnotherWindowDropTarget(mDragView.getContext()) : null;
mEventListener.onDriverDragEnd(mLastX, mLastY, dropTargetOverride);
mIsFrameworkDragActive = false;
if (mReceivedDropEvent) {
mEventListener.onDriverDragEnd(mLastX, mLastY, null);
} else {
mEventListener.onDriverDragCancel();
}
return true;
default:
return false;
}
}
};
private boolean updateInfoFromClipData(ClipData data, ClipDescription desc) {
if (data == null) {
return false;
}
ArrayList<Intent> intents = new ArrayList<>();
int itemCount = data.getItemCount();
for (int i = 0; i < itemCount; i++) {
Intent intent = data.getItemAt(i).getIntent();
if (intent == null) {
continue;
}
// Give preference to shortcut intents.
if (!Intent.ACTION_CREATE_SHORTCUT.equals(intent.getAction())) {
intents.add(intent);
continue;
}
ShortcutInfo info = InstallShortcutReceiver.fromShortcutIntent(mContext, intent);
if (info != null) {
mDragObject.dragInfo = info;
return true;
}
return true;
}
// Try creating shortcuts just using the intent and label
Intent fullIntent = new Intent().putExtra(Intent.EXTRA_SHORTCUT_NAME, desc.getLabel());
for (Intent intent : intents) {
fullIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, intent);
ShortcutInfo info = InstallShortcutReceiver.fromShortcutIntent(mContext, fullIntent);
if (info != null) {
mDragObject.dragInfo = info;
return true;
}
}
return false;
}
}
/**
* Class for driving an internal (i.e. not using framework) drag/drop operation.
@@ -243,9 +219,6 @@ class InternalDragDriver extends DragDriver {
super(dragController);
}
@Override
public void onDragViewAnimationEnd() {}
@Override
public boolean onDragEvent (DragEvent event) { return false; }
};
@@ -21,14 +21,22 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.TargetApi;
import android.content.ClipDescription;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.DragEvent;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -45,12 +53,14 @@ import com.android.launcher3.AppWidgetResizeFrame;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DropTargetBar;
import com.android.launcher3.InsettableFrameLayout;
import com.android.launcher3.InstallShortcutReceiver;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppWidgetHostView;
import com.android.launcher3.PinchToOverviewListener;
import com.android.launcher3.R;
import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.allapps.AllAppsTransitionController;
@@ -62,6 +72,7 @@ import com.android.launcher3.shortcuts.DeepShortcutsContainer;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.TouchController;
import java.net.URISyntaxException;
import java.util.ArrayList;
/**
@@ -431,8 +442,46 @@ public class DragLayer extends InsettableFrameLayout {
return false;
}
@TargetApi(Build.VERSION_CODES.N)
private void handleSystemDragStart(DragEvent event) {
if (!FeatureFlags.LAUNCHER3_USE_SYSTEM_DRAG_DRIVER || !Utilities.isNycOrAbove()) {
return;
}
if (mLauncher.isWorkspaceLocked()) {
return;
}
ClipDescription description = event.getClipDescription();
if (!description.hasMimeType(ClipDescription.MIMETYPE_TEXT_INTENT)) {
return;
}
ShortcutInfo info = new ShortcutInfo();
// Set a dummy intent until we get the final value
info.intent = new Intent();
// Since we are not going through the workspace for starting the drag, set drag related
// information on the workspace before starting the drag.
ExternalDragPreviewProvider previewProvider =
new ExternalDragPreviewProvider(mLauncher, info);
mLauncher.getWorkspace().prepareDragWithProvider(previewProvider);
DragOptions options = new DragOptions();
options.systemDndStartPoint = new Point((int) event.getX(), (int) event.getY());
int halfPadding = previewProvider.previewPadding / 2;
mDragController.startDrag(
Bitmap.createBitmap(1, 1, Config.ARGB_8888),
0, 0,
new AnotherWindowDragSource(mLauncher), info,
new Point(- halfPadding, halfPadding),
previewProvider.getPreviewBounds(), 1f, options);
}
@Override
public boolean onDragEvent (DragEvent event) {
if (event.getAction() == DragEvent.ACTION_DRAG_STARTED) {
handleSystemDragStart(event);
}
return mDragController.onDragEvent(event);
}
@@ -758,11 +807,15 @@ public class DragLayer extends InsettableFrameLayout {
// If duration < 0, this is a cue to compute the duration based on the distance
if (duration < 0) {
duration = res.getInteger(R.integer.config_dropAnimMaxDuration);
if (dist < maxDist) {
duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist);
if (mDragController != null && mDragController.isExternalDrag()) {
duration = 1;
} else {
duration = res.getInteger(R.integer.config_dropAnimMaxDuration);
if (dist < maxDist) {
duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist);
}
duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration));
}
duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration));
}
// Fall back to cubic ease out interpolator for the animation if none is specified
@@ -16,6 +16,8 @@
package com.android.launcher3.dragndrop;
import android.graphics.Point;
/**
* Set of options to control the drag and drop behavior.
*/
@@ -23,4 +25,7 @@ public class DragOptions {
/** Whether or not an accessible drag operation is in progress. */
public boolean isAccessibleDrag = false;
/** Specifies the start location for the system DnD, null when using internal DnD */
public Point systemDndStartPoint = null;
}
@@ -0,0 +1,79 @@
/*
* Copyright (C) 2016 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.dragndrop;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.HolographicOutlineHelper;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.graphics.DragPreviewProvider;
/**
* Extension of {@link DragPreviewProvider} which provides a dummy outline when drag starts from
* a different window.
* It just draws an empty circle to a placeholder outline.
*/
public class ExternalDragPreviewProvider extends DragPreviewProvider {
private final Launcher mLauncher;
private final ItemInfo mAddInfo;
private final int[] mOutlineSize;
public ExternalDragPreviewProvider(Launcher launcher, ItemInfo addInfo) {
super(null);
mLauncher = launcher;
mAddInfo = addInfo;
mOutlineSize = mLauncher.getWorkspace().estimateItemSize(mAddInfo, false);
}
public Rect getPreviewBounds() {
Rect rect = new Rect();
DeviceProfile dp = mLauncher.getDeviceProfile();
rect.left = DRAG_BITMAP_PADDING / 2;
rect.top = (mOutlineSize[1] - dp.cellHeightPx) / 2;
rect.right = rect.left + dp.iconSizePx;
rect.bottom = rect.top + dp.iconSizePx;
return rect;
}
@Override
public Bitmap createDragOutline(Canvas canvas) {
final Bitmap b = Bitmap.createBitmap(mOutlineSize[0], mOutlineSize[1], Bitmap.Config.ALPHA_8);
canvas.setBitmap(b);
Paint paint = new Paint();
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.FILL);
// Use 0.9f times the radius for the actual circle to account for icon normalization.
float radius = getPreviewBounds().width() * 0.5f;
canvas.drawCircle(DRAG_BITMAP_PADDING / 2 + radius,
DRAG_BITMAP_PADDING / 2 + radius, radius * 0.9f, paint);
HolographicOutlineHelper.obtain(mLauncher).applyExpensiveOutlineWithBlur(b, canvas);
canvas.setBitmap(null);
return b;
}
}
@@ -27,7 +27,7 @@ public final class FeatureFlags {
// As opposed to the new spring-loaded workspace.
public static boolean LAUNCHER3_LEGACY_WORKSPACE_DND = false;
public static boolean LAUNCHER3_LEGACY_FOLDER_ICON = false;
public static boolean LAUNCHER3_USE_SYSTEM_DRAG_DRIVER = false;
public static boolean LAUNCHER3_USE_SYSTEM_DRAG_DRIVER = true;
public static boolean LAUNCHER3_DISABLE_PINCH_TO_OVERVIEW = false;
public static boolean LAUNCHER3_ALL_APPS_PULL_UP = true;