Adding support for drag and drop for requestPinItem.

On long pressing, the confirmation activity starts a system
drag-n-drop and focuses the launcher activity. We then drive
the launcher drag controller using the system drag event

Caveats:
> We use a transparent preview for system drag and drop and use
  a view inside launcher for actual preview. This gives us better
  control over various animations.
> The parameters for drag operation are passed to the Launcher
  activity using the intent. Since onNewIntent and onDragEvent
  come at different times and are not associated, a random uuid
  is used as mime-type to match the drag event with intent params
> If the workspace is locked (eg, loader is running) the drag
  operation is simply dropped. Will be imporved in follow up cls

Bug: 33584624
Change-Id: I0bb5b25b690f86b6af31a14e11beb669fcb3a281
This commit is contained in:
Sunny Goyal 2017-01-20 19:32:31 -08:00
parent 658058b960
commit b38fab7573
15 changed files with 413 additions and 342 deletions

View File

@ -81,6 +81,8 @@
<activity android:name="com.android.launcher3.dragndrop.AddItemActivity"
android:theme="@android:style/Theme.DeviceDefault.Light.Dialog.Alert"
android:excludeFromRecents="true"
android:autoRemoveFromRecents="true"
android:label="@string/action_add_to_workspace" >
<intent-filter>
<action android:name="android.content.pm.action.CONFIRM_PIN_ITEM" />

View File

@ -1,61 +0,0 @@
/*
* Copyright (C) 2015 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;
import android.content.Context;
import android.graphics.PointF;
import android.graphics.Rect;
/**
* Drop target used when another window (i.e. another process) has accepted a global system drag.
* If the accepted item was a shortcut, we delete it from Launcher.
*/
public class AnotherWindowDropTarget implements DropTarget {
final Launcher mLauncher;
public AnotherWindowDropTarget (Context context) { mLauncher = Launcher.getLauncher(context); }
@Override
public boolean isDropEnabled() { return true; }
@Override
public void onDrop(DragObject dragObject) {
dragObject.deferDragViewCleanupPostAnimation = false;
LauncherModel.deleteItemFromDatabase(mLauncher, (ShortcutInfo) dragObject.dragInfo);
}
@Override
public void onDragEnter(DragObject dragObject) {}
@Override
public void onDragOver(DragObject dragObject) {}
@Override
public void onDragExit(DragObject dragObject) {}
@Override
public boolean acceptDrop(DragObject dragObject) {
return dragObject.dragInfo instanceof ShortcutInfo;
}
@Override
public void prepareAccessibilityDrop() {}
// These methods are implemented in Views
@Override
public void getHitRectRelativeToDragLayer(Rect outRect) {}
}

View File

@ -49,6 +49,7 @@ import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcelable;
import android.os.Process;
import android.os.StrictMode;
import android.os.SystemClock;
@ -94,6 +95,7 @@ import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.dragndrop.PinItemDragListener;
import com.android.launcher3.dynamicui.ExtractedColors;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
@ -826,7 +828,7 @@ public class Launcher extends BaseActivity
}
@Override
protected void onActivityResult(
public void onActivityResult(
final int requestCode, final int resultCode, final Intent data) {
handleActivityResult(requestCode, resultCode, data);
if (mLauncherCallbacks != null) {
@ -1752,6 +1754,14 @@ public class Launcher extends BaseActivity
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onHomeIntent();
}
Parcelable dragExtra = intent
.getParcelableExtra(PinItemDragListener.EXTRA_PIN_ITEM_DRAG_LISTENER);
if (dragExtra instanceof PinItemDragListener) {
PinItemDragListener dragListener = (PinItemDragListener) dragExtra;
dragListener.setLauncher(this);
mDragLayer.setOnDragListener(dragListener);
}
}
if (mLauncherCallbacks != null) {

View File

@ -21,13 +21,14 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.ShortcutInfo;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
/**
* A wrapper around platform implementation of PinItemRequestCompat until the
* updated SDK is available.
*/
public class PinItemRequestCompat {
public class PinItemRequestCompat implements Parcelable {
public static final String EXTRA_PIN_ITEM_REQUEST = "android.content.pm.extra.PIN_ITEM_REQUEST";
@ -83,6 +84,32 @@ public class PinItemRequestCompat {
}
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeParcelable(mObject, i);
}
public Intent toIntent() {
return new Intent().putExtra(EXTRA_PIN_ITEM_REQUEST, mObject);
}
public static final Parcelable.Creator<PinItemRequestCompat> CREATOR =
new Parcelable.Creator<PinItemRequestCompat>() {
public PinItemRequestCompat createFromParcel(Parcel source) {
Parcelable object = source.readParcelable(null);
return new PinItemRequestCompat(object);
}
public PinItemRequestCompat[] newArray(int size) {
return new PinItemRequestCompat[size];
}
};
public static PinItemRequestCompat getPinItemRequest(Intent intent) {
Parcelable extra = intent.getParcelableExtra(EXTRA_PIN_ITEM_REQUEST);
return extra == null ? null : new PinItemRequestCompat(extra);

View File

@ -35,6 +35,7 @@ import android.util.Log;
import android.widget.Toast;
import com.android.launcher3.IconCache;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@ -67,7 +68,7 @@ public abstract class ShortcutConfigActivityInfo {
public abstract Drawable getFullResIcon(IconCache cache);
public boolean startConfigActivity(Activity activity, int requestCode) {
public boolean startConfigActivity(Launcher activity, int requestCode) {
Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT)
.setComponent(getComponent());
try {
@ -136,7 +137,7 @@ public abstract class ShortcutConfigActivityInfo {
}
@Override
public boolean startConfigActivity(Activity activity, int requestCode) {
public boolean startConfigActivity(Launcher activity, int requestCode) {
if (getUser().equals(Process.myUserHandle())) {
return super.startConfigActivity(activity, requestCode);
}

View File

@ -17,12 +17,23 @@
package com.android.launcher3.dragndrop;
import android.annotation.TargetApi;
import android.app.ActivityOptions;
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetManager;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.*;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.InstallShortcutReceiver;
@ -38,13 +49,18 @@ import com.android.launcher3.shortcuts.ShortcutInfoCompat;
import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.WidgetCell;
import com.android.launcher3.widget.WidgetHostViewLoader;
import com.android.launcher3.widget.WidgetImageView;
@TargetApi(Build.VERSION_CODES.N_MR1)
public class AddItemActivity extends BaseActivity {
public class AddItemActivity extends BaseActivity implements OnLongClickListener, OnTouchListener {
private static final int SHADOW_SIZE = 10;
private static final int REQUEST_BIND_APPWIDGET = 1;
private static final String STATE_EXTRA_WIDGET_ID = "state.widget.id";
private final PointF mLastTouchPos = new PointF();
private PinItemRequestCompat mRequest;
private LauncherAppState mApp;
private InvariantDeviceProfile mIdp;
@ -86,11 +102,54 @@ public class AddItemActivity extends BaseActivity {
finish();
}
}
mWidgetCell.setOnTouchListener(this);
mWidgetCell.setOnLongClickListener(this);
}
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
mLastTouchPos.set(motionEvent.getX(), motionEvent.getY());
return false;
}
@Override
public boolean onLongClick(View view) {
// Find the position of the preview relative to the touch location.
WidgetImageView img = mWidgetCell.getWidgetView();
Rect bounds = img.getBitmapBounds();
bounds.offset(img.getLeft() - (int) mLastTouchPos.x, img.getTop() - (int) mLastTouchPos.y);
// Start home and pass the draw request params
PinItemDragListener listener = new PinItemDragListener(mRequest, bounds);
Intent homeIntent = new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_HOME)
.setPackage(getPackageName())
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(PinItemDragListener.EXTRA_PIN_ITEM_DRAG_LISTENER, listener);
startActivity(homeIntent,
ActivityOptions.makeCustomAnimation(this, 0, android.R.anim.fade_out).toBundle());
// Start a system drag and drop. We use a transparent bitmap as preview for system drag
// as the preview is handled internally by launcher.
ClipDescription description = new ClipDescription("", new String[]{listener.getMimeType()});
ClipData data = new ClipData(description, new ClipData.Item(""));
view.startDragAndDrop(data, new DragShadowBuilder(view) {
@Override
public void onDrawShadow(Canvas canvas) { }
@Override
public void onProvideShadowMetrics(Point outShadowSize, Point outShadowTouchPoint) {
outShadowSize.set(SHADOW_SIZE, SHADOW_SIZE);
outShadowTouchPoint.set(SHADOW_SIZE / 2, SHADOW_SIZE / 2);
}
}, null, View.DRAG_FLAG_GLOBAL);
return false;
}
private void setupShortcut() {
WidgetItem item = new WidgetItem(new PinShortcutRequestActivityInfo(
mRequest.getShortcutInfo(), this));
WidgetItem item = new WidgetItem(new PinShortcutRequestActivityInfo(mRequest, this));
mWidgetCell.applyFromCellItem(item, mApp.getWidgetCache());
mWidgetCell.ensurePreview();
}

View File

@ -1,67 +0,0 @@
/*
* 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 supportsAppInfoDropTarget() {
return false;
}
@Override
public boolean supportsDeleteDropTarget() {
return false;
}
@Override
public float getIntrinsicIconScaleFactor() {
return 1;
}
@Override
public void onDropCompleted(View target, DragObject d,
boolean isFlingToDelete, boolean success) {
if (!success) {
Launcher.getLauncher(mContext).exitSpringLoadedDragModeDelayed(false, 0, null);
}
}
@Override
public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
// TODO: Probably log something
}
}

View File

@ -36,7 +36,6 @@ import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.accessibility.DragViewStateAnnouncer;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.TouchController;
@ -447,7 +446,8 @@ public class DragController implements DragDriver.EventListener, TouchController
/**
* Call this from a drag source view.
*/
public boolean onDragEvent(DragEvent event) {
public boolean onDragEvent(long dragStartTime, DragEvent event) {
mFlingToDeleteHelper.recordDragEvent(dragStartTime, event);
return mDragDriver != null && mDragDriver.onDragEvent(event);
}

View File

@ -16,20 +16,13 @@
package com.android.launcher3.dragndrop;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Context;
import android.content.Intent;
import android.view.DragEvent;
import android.view.MotionEvent;
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.InstallShortcutReceiver;
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.Utilities;
import java.util.ArrayList;
/**
* Base class for driving a drag/drop operation.
*/
@ -107,7 +100,6 @@ class SystemDragDriver extends DragDriver {
private final DragObject mDragObject;
private final Context mContext;
boolean mReceivedDropEvent = false;
float mLastX = 0;
float mLastY = 0;
@ -149,65 +141,21 @@ class SystemDragDriver extends DragDriver {
case DragEvent.ACTION_DROP:
mLastX = event.getX();
mLastY = event.getY();
mReceivedDropEvent =
updateInfoFromClipData(event.getClipData(), event.getClipDescription());
return mReceivedDropEvent;
mEventListener.onDriverDragMove(event.getX(), event.getY());
mEventListener.onDriverDragEnd(mLastX, mLastY);
return true;
case DragEvent.ACTION_DRAG_EXITED:
mEventListener.onDriverDragExitWindow();
return true;
case DragEvent.ACTION_DRAG_ENDED:
if (mReceivedDropEvent) {
mEventListener.onDriverDragEnd(mLastX, mLastY);
} else {
mEventListener.onDriverDragCancel();
}
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;
}
}
/**

View File

@ -21,21 +21,13 @@ 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.os.Build;
import android.util.AttributeSet;
import android.view.DragEvent;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
@ -59,9 +51,7 @@ 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;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.Folder;
@ -373,49 +363,6 @@ 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.ATLEAST_NOUGAT) {
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);
}
/**
* Determine the rect of the descendant in this DragLayer's coordinates
*

View File

@ -1,79 +0,0 @@
/*
* 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.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.graphics.DragPreviewProvider;
import com.android.launcher3.graphics.HolographicOutlineHelper;
/**
* 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, launcher);
mLauncher = launcher;
mAddInfo = addInfo;
mOutlineSize = mLauncher.getWorkspace().estimateItemSize(mAddInfo, false, false);
}
public Rect getPreviewBounds() {
Rect rect = new Rect();
DeviceProfile dp = mLauncher.getDeviceProfile();
rect.left = blurSizeOutline / 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(blurSizeOutline / 2 + radius,
blurSizeOutline / 2 + radius, radius * 0.9f, paint);
HolographicOutlineHelper.getInstance(mLauncher).applyExpensiveOutlineWithBlur(b, canvas);
canvas.setBitmap(null);
return b;
}
}

View File

@ -17,6 +17,8 @@
package com.android.launcher3.dragndrop;
import android.graphics.PointF;
import android.os.SystemClock;
import android.view.DragEvent;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
@ -53,6 +55,31 @@ public class FlingToDeleteHelper {
mVelocityTracker.addMovement(ev);
}
/**
* Same as {@link #recordMotionEvent}. It creates a temporary {@link MotionEvent} object
* using {@param event} for tracking velocity.
*/
public void recordDragEvent(long dragStartTime, DragEvent event) {
final int motionAction;
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
motionAction = MotionEvent.ACTION_DOWN;
break;
case DragEvent.ACTION_DRAG_LOCATION:
motionAction = MotionEvent.ACTION_MOVE;
break;
case DragEvent.ACTION_DRAG_ENDED:
motionAction = MotionEvent.ACTION_UP;
break;
default:
return;
}
MotionEvent emulatedEvent = MotionEvent.obtain(dragStartTime, SystemClock.uptimeMillis(),
motionAction, event.getX(), event.getY(), 0);
recordMotionEvent(emulatedEvent);
emulatedEvent.recycle();
}
public void releaseVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();

View File

@ -0,0 +1,259 @@
/*
* Copyright (C) 2017 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.ClipDescription;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
import android.util.Log;
import android.view.DragEvent;
import android.view.View;
import com.android.launcher3.DeleteDropTarget;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.PendingAddItemInfo;
import com.android.launcher3.compat.PinItemRequestCompat;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.graphics.LauncherIcons;
import com.android.launcher3.shortcuts.ShortcutInfoCompat;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.widget.PendingAddShortcutInfo;
import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.PendingItemPreviewProvider;
import java.util.UUID;
/**
* {@link DragSource} for handling drop from from a different window. This object is initialized
* in the source window and is passed on to the Launcher activity as an Intent extra.
*/
public class PinItemDragListener implements Parcelable, View.OnDragListener, DragSource {
private static final String TAG = "PinItemDragListener";
private static final String MIME_TYPE_PREFIX = "com.android.launcher3.drag_and_drop/";
public static final String EXTRA_PIN_ITEM_DRAG_LISTENER = "pin_item_drag_listener";
private final PinItemRequestCompat mRequest;
// Position of preview relative to the touch location
private final Rect mPreviewRect;
// Randomly generated id used to verify the drag event.
private final String mId;
private Launcher mLauncher;
private DragController mDragController;
private long mDragStartTime;
public PinItemDragListener(PinItemRequestCompat request, Rect previewRect) {
mRequest = request;
mPreviewRect = previewRect;
mId = UUID.randomUUID().toString();
}
private PinItemDragListener(Parcel parcel) {
mRequest = PinItemRequestCompat.CREATOR.createFromParcel(parcel);
mPreviewRect = Rect.CREATOR.createFromParcel(parcel);
mId = parcel.readString();
}
public String getMimeType() {
return MIME_TYPE_PREFIX + mId;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
mRequest.writeToParcel(parcel, i);
mPreviewRect.writeToParcel(parcel, i);
parcel.writeString(mId);
}
public void setLauncher(Launcher launcher) {
mLauncher = launcher;
mDragController = launcher.getDragController();
}
@Override
public boolean onDrag(View view, DragEvent event) {
if (mLauncher == null || mDragController == null) {
postCleanup();
return false;
}
if (event.getAction() == DragEvent.ACTION_DRAG_STARTED) {
if (onDragStart(event)) {
return true;
} else {
postCleanup();
return false;
}
}
return mDragController.onDragEvent(mDragStartTime, event);
}
private boolean onDragStart(DragEvent event) {
if (!mRequest.isValid()) {
return false;
}
ClipDescription desc = event.getClipDescription();
if (desc == null || !desc.hasMimeType(getMimeType())) {
Log.e(TAG, "Someone started a dragAndDrop before us.");
return false;
}
if (mLauncher.isWorkspaceLocked()) {
// TODO: implement wait
return false;
}
final PendingAddItemInfo item;
final Bitmap preview;
Point dragShift = new Point(mPreviewRect.left, mPreviewRect.top);
if (mRequest.getRequestType() == PinItemRequestCompat.REQUEST_TYPE_SHORTCUT) {
item = new PendingAddShortcutInfo(
new PinShortcutRequestActivityInfo(mRequest, mLauncher));
ShortcutInfoCompat compat = new ShortcutInfoCompat(mRequest.getShortcutInfo());
Bitmap icon = LauncherIcons.createShortcutIcon(compat, mLauncher, false /* badged */);
// Create a preview same as the workspace cell size and draw the icon at the
// appropriate position.
int[] size = mLauncher.getWorkspace().estimateItemSize(item, true, false);
preview = Bitmap.createBitmap(size[0], size[1], Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(preview);
DeviceProfile dp = mLauncher.getDeviceProfile();
c.drawBitmap(icon, (size[0] - icon.getWidth()) / 2,
(size[1] - icon.getHeight() - dp.iconTextSizePx - dp.iconDrawablePaddingPx) / 2,
new Paint(Paint.FILTER_BITMAP_FLAG));
} else {
PendingAddWidgetInfo info = new PendingAddWidgetInfo(
LauncherAppWidgetProviderInfo.fromProviderInfo(
mLauncher, mRequest.getAppWidgetProviderInfo(mLauncher)));
int[] size = mLauncher.getWorkspace().estimateItemSize(info, true, false);
float minScale = 1.25f;
int maxWidth = Math.min((int) (mPreviewRect.width() * minScale), size[0]);
int[] previewSizeBeforeScale = new int[1];
preview = LauncherAppState.getInstance(mLauncher).getWidgetCache()
.generateWidgetPreview(mLauncher, info.info, maxWidth, null,
previewSizeBeforeScale);
dragShift.offset(
(mPreviewRect.width() - preview.getWidth()) / 2,
(mPreviewRect.height() - preview.getHeight()) / 2);
item = info;
}
PendingItemPreviewProvider previewProvider =
new PendingItemPreviewProvider(new View(mLauncher), item, preview);
// Since we are not going through the workspace for starting the drag, set drag related
// information on the workspace before starting the drag.
mLauncher.getWorkspace().prepareDragWithProvider(previewProvider);
Point downPos = new Point((int) event.getX(), (int) event.getY());
DragOptions options = new DragOptions();
options.systemDndStartPoint = downPos;
int x = downPos.x + dragShift.x;
int y = downPos.y + dragShift.y;
mDragController.startDrag(
preview, x, y, this, item, null, null, 1f, options);
mDragStartTime = SystemClock.uptimeMillis();
return true;
}
@Override
public boolean supportsAppInfoDropTarget() {
return false;
}
@Override
public boolean supportsDeleteDropTarget() {
return false;
}
@Override
public float getIntrinsicIconScaleFactor() {
return 1f;
}
@Override
public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete,
boolean success) {
if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
!(target instanceof DeleteDropTarget) && !(target instanceof Folder))) {
// Exit spring loaded mode if we have not successfully dropped or have not handled the
// drop in Workspace
mLauncher.exitSpringLoadedDragModeDelayed(true,
Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
}
postCleanup();
}
@Override
public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
LauncherLogProto.Target targetParent) {
// TODO: We should probably log something
}
private void postCleanup() {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
removeListener();
}
});
}
public void removeListener() {
if (mLauncher != null) {
mLauncher.getDragLayer().setOnDragListener(null);
}
}
public static final Parcelable.Creator<PinItemDragListener> CREATOR =
new Parcelable.Creator<PinItemDragListener>() {
public PinItemDragListener createFromParcel(Parcel source) {
return new PinItemDragListener(source);
}
public PinItemDragListener[] newArray(int size) {
return new PinItemDragListener[size];
}
};
}

View File

@ -26,7 +26,9 @@ import android.graphics.drawable.Drawable;
import android.os.Build;
import com.android.launcher3.IconCache;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.compat.PinItemRequestCompat;
import com.android.launcher3.compat.ShortcutConfigActivityInfo;
/**
@ -40,12 +42,15 @@ class PinShortcutRequestActivityInfo extends ShortcutConfigActivityInfo {
// actual existing class.
private static final String DUMMY_COMPONENT_CLASS = "pinned-shortcut";
private final PinItemRequestCompat mRequest;
private final ShortcutInfo mInfo;
private final Context mContext;
public PinShortcutRequestActivityInfo(ShortcutInfo info, Context context) {
super(new ComponentName(info.getPackage(), DUMMY_COMPONENT_CLASS), info.getUserHandle());
mInfo = info;
public PinShortcutRequestActivityInfo(PinItemRequestCompat request, Context context) {
super(new ComponentName(request.getShortcutInfo().getPackage(), DUMMY_COMPONENT_CLASS),
request.getShortcutInfo().getUserHandle());
mRequest = request;
mInfo = request.getShortcutInfo();
mContext = context;
}
@ -61,8 +66,9 @@ class PinShortcutRequestActivityInfo extends ShortcutConfigActivityInfo {
}
@Override
public boolean startConfigActivity(Activity activity, int requestCode) {
throw new RuntimeException("Not supported");
public boolean startConfigActivity(Launcher activity, int requestCode) {
activity.onActivityResult(requestCode, Activity.RESULT_OK, mRequest.toIntent());
return true;
}
@Override

View File

@ -144,12 +144,8 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
}
}
public int[] getPreviewSize() {
int[] maxSize = new int[2];
maxSize[0] = mPresetPreviewSize;
maxSize[1] = mPresetPreviewSize;
return maxSize;
public WidgetImageView getWidgetView() {
return mWidgetImage;
}
public void applyPreview(Bitmap bitmap) {
@ -166,12 +162,8 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
if (mActiveRequest != null) {
return;
}
int[] size = getPreviewSize();
if (DEBUG) {
Log.d(TAG, String.format("[tag=%s] ensurePreview (%d, %d):",
getTagToString(), size[0], size[1]));
}
mActiveRequest = mWidgetPreviewLoader.getPreview(mItem, size[0], size[1], this);
mActiveRequest = mWidgetPreviewLoader.getPreview(
mItem, mPresetPreviewSize, mPresetPreviewSize, this);
}
@Override