1/* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.launcher3.dragndrop; 18 19import android.appwidget.AppWidgetManager; 20import android.content.ClipDescription; 21import android.content.Intent; 22import android.graphics.Point; 23import android.graphics.Rect; 24import android.os.Bundle; 25import android.os.Handler; 26import android.os.Looper; 27import android.os.Parcel; 28import android.os.Parcelable; 29import android.os.SystemClock; 30import android.util.Log; 31import android.view.DragEvent; 32import android.view.View; 33import android.widget.RemoteViews; 34 35import com.android.launcher3.DeleteDropTarget; 36import com.android.launcher3.DragSource; 37import com.android.launcher3.DropTarget; 38import com.android.launcher3.ItemInfo; 39import com.android.launcher3.Launcher; 40import com.android.launcher3.LauncherAppWidgetProviderInfo; 41import com.android.launcher3.PendingAddItemInfo; 42import com.android.launcher3.R; 43import com.android.launcher3.compat.PinItemRequestCompat; 44import com.android.launcher3.folder.Folder; 45import com.android.launcher3.userevent.nano.LauncherLogProto; 46import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; 47import com.android.launcher3.widget.PendingAddShortcutInfo; 48import com.android.launcher3.widget.PendingAddWidgetInfo; 49import com.android.launcher3.widget.PendingItemDragHelper; 50import com.android.launcher3.widget.WidgetAddFlowHandler; 51 52import java.util.UUID; 53 54/** 55 * {@link DragSource} for handling drop from a different window. This object is initialized 56 * in the source window and is passed on to the Launcher activity as an Intent extra. 57 */ 58public class PinItemDragListener 59 implements Parcelable, View.OnDragListener, DragSource, DragOptions.PreDragCondition { 60 61 private static final String TAG = "PinItemDragListener"; 62 63 private static final String MIME_TYPE_PREFIX = "com.android.launcher3.drag_and_drop/"; 64 public static final String EXTRA_PIN_ITEM_DRAG_LISTENER = "pin_item_drag_listener"; 65 66 private final PinItemRequestCompat mRequest; 67 68 // Position of preview relative to the touch location 69 private final Rect mPreviewRect; 70 71 private final int mPreviewBitmapWidth; 72 private final int mPreviewViewWidth; 73 74 // Randomly generated id used to verify the drag event. 75 private final String mId; 76 77 private Launcher mLauncher; 78 private DragController mDragController; 79 private long mDragStartTime; 80 81 public PinItemDragListener(PinItemRequestCompat request, Rect previewRect, 82 int previewBitmapWidth, int previewViewWidth) { 83 mRequest = request; 84 mPreviewRect = previewRect; 85 mPreviewBitmapWidth = previewBitmapWidth; 86 mPreviewViewWidth = previewViewWidth; 87 mId = UUID.randomUUID().toString(); 88 } 89 90 private PinItemDragListener(Parcel parcel) { 91 mRequest = PinItemRequestCompat.CREATOR.createFromParcel(parcel); 92 mPreviewRect = Rect.CREATOR.createFromParcel(parcel); 93 mPreviewBitmapWidth = parcel.readInt(); 94 mPreviewViewWidth = parcel.readInt(); 95 mId = parcel.readString(); 96 } 97 98 public String getMimeType() { 99 return MIME_TYPE_PREFIX + mId; 100 } 101 102 @Override 103 public int describeContents() { 104 return 0; 105 } 106 107 @Override 108 public void writeToParcel(Parcel parcel, int i) { 109 mRequest.writeToParcel(parcel, i); 110 mPreviewRect.writeToParcel(parcel, i); 111 parcel.writeInt(mPreviewBitmapWidth); 112 parcel.writeInt(mPreviewViewWidth); 113 parcel.writeString(mId); 114 } 115 116 public void setLauncher(Launcher launcher) { 117 mLauncher = launcher; 118 mDragController = launcher.getDragController(); 119 } 120 121 @Override 122 public boolean onDrag(View view, DragEvent event) { 123 if (mLauncher == null || mDragController == null) { 124 postCleanup(); 125 return false; 126 } 127 if (event.getAction() == DragEvent.ACTION_DRAG_STARTED) { 128 if (onDragStart(event)) { 129 return true; 130 } else { 131 postCleanup(); 132 return false; 133 } 134 } 135 return mDragController.onDragEvent(mDragStartTime, event); 136 } 137 138 private boolean onDragStart(DragEvent event) { 139 if (!mRequest.isValid()) { 140 return false; 141 } 142 ClipDescription desc = event.getClipDescription(); 143 if (desc == null || !desc.hasMimeType(getMimeType())) { 144 Log.e(TAG, "Someone started a dragAndDrop before us."); 145 return false; 146 } 147 148 final PendingAddItemInfo item; 149 if (mRequest.getRequestType() == PinItemRequestCompat.REQUEST_TYPE_SHORTCUT) { 150 item = new PendingAddShortcutInfo( 151 new PinShortcutRequestActivityInfo(mRequest, mLauncher)); 152 } else { 153 // mRequest.getRequestType() == PinItemRequestCompat.REQUEST_TYPE_APPWIDGET 154 LauncherAppWidgetProviderInfo providerInfo = 155 LauncherAppWidgetProviderInfo.fromProviderInfo( 156 mLauncher, mRequest.getAppWidgetProviderInfo(mLauncher)); 157 final PinWidgetFlowHandler flowHandler = 158 new PinWidgetFlowHandler(providerInfo, mRequest); 159 item = new PendingAddWidgetInfo(providerInfo) { 160 @Override 161 public WidgetAddFlowHandler getHandler() { 162 return flowHandler; 163 } 164 }; 165 } 166 View view = new View(mLauncher); 167 view.setTag(item); 168 169 Point downPos = new Point((int) event.getX(), (int) event.getY()); 170 DragOptions options = new DragOptions(); 171 options.systemDndStartPoint = downPos; 172 options.preDragCondition = this; 173 174 // We use drag event position as the screenPos for the preview image. Since mPreviewRect 175 // already includes the view position relative to the drag event on the source window, 176 // and the absolute position (position relative to the screen) of drag event is same 177 // across windows, using drag position here give a good estimate for relative position 178 // to source window. 179 PendingItemDragHelper dragHelper = new PendingItemDragHelper(view); 180 if (mRequest.getRequestType() == PinItemRequestCompat.REQUEST_TYPE_APPWIDGET) { 181 dragHelper.setPreview(getPreview(mRequest)); 182 } 183 184 dragHelper.startDrag(new Rect(mPreviewRect), 185 mPreviewBitmapWidth, mPreviewViewWidth, downPos, this, options); 186 mDragStartTime = SystemClock.uptimeMillis(); 187 return true; 188 } 189 190 @Override 191 public boolean shouldStartDrag(double distanceDragged) { 192 // Stay in pre-drag mode, if workspace is locked. 193 return !mLauncher.isWorkspaceLocked(); 194 } 195 196 @Override 197 public void onPreDragStart(DropTarget.DragObject dragObject) { 198 // The predrag starts when the workspace is not yet loaded. In some cases we set 199 // the dragLayer alpha to 0 to have a nice fade-in animation. But that will prevent the 200 // dragView from being visible. Instead just skip the fade-in animation here. 201 mLauncher.getDragLayer().setAlpha(1); 202 203 dragObject.dragView.setColor( 204 mLauncher.getResources().getColor(R.color.delete_target_hover_tint)); 205 } 206 207 @Override 208 public void onPreDragEnd(DropTarget.DragObject dragObject, boolean dragStarted) { 209 if (dragStarted) { 210 dragObject.dragView.setColor(0); 211 } 212 } 213 214 @Override 215 public boolean supportsAppInfoDropTarget() { 216 return false; 217 } 218 219 @Override 220 public boolean supportsDeleteDropTarget() { 221 return false; 222 } 223 224 @Override 225 public float getIntrinsicIconScaleFactor() { 226 return 1f; 227 } 228 229 @Override 230 public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete, 231 boolean success) { 232 if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() && 233 !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) { 234 // Exit spring loaded mode if we have not successfully dropped or have not handled the 235 // drop in Workspace 236 mLauncher.exitSpringLoadedDragModeDelayed(true, 237 Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null); 238 } 239 240 if (!success) { 241 d.deferDragViewCleanupPostAnimation = false; 242 } 243 postCleanup(); 244 } 245 246 @Override 247 public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target, 248 LauncherLogProto.Target targetParent) { 249 targetParent.containerType = ContainerType.PINITEM; 250 } 251 252 private void postCleanup() { 253 if (mLauncher != null) { 254 // Remove any drag params from the launcher intent since the drag operation is complete. 255 Intent newIntent = new Intent(mLauncher.getIntent()); 256 newIntent.removeExtra(EXTRA_PIN_ITEM_DRAG_LISTENER); 257 mLauncher.setIntent(newIntent); 258 } 259 260 new Handler(Looper.getMainLooper()).post(new Runnable() { 261 @Override 262 public void run() { 263 removeListener(); 264 } 265 }); 266 } 267 268 public void removeListener() { 269 if (mLauncher != null) { 270 mLauncher.getDragLayer().setOnDragListener(null); 271 } 272 } 273 274 public static RemoteViews getPreview(PinItemRequestCompat request) { 275 Bundle extras = request.getExtras(); 276 if (extras != null && 277 extras.get(AppWidgetManager.EXTRA_APPWIDGET_PREVIEW) instanceof RemoteViews) { 278 return (RemoteViews) extras.get(AppWidgetManager.EXTRA_APPWIDGET_PREVIEW); 279 } 280 return null; 281 } 282 283 public static boolean handleDragRequest(Launcher launcher, Intent intent) { 284 if (intent == null || !Intent.ACTION_MAIN.equals(intent.getAction())) { 285 return false; 286 } 287 Parcelable dragExtra = intent.getParcelableExtra(EXTRA_PIN_ITEM_DRAG_LISTENER); 288 if (dragExtra instanceof PinItemDragListener) { 289 PinItemDragListener dragListener = (PinItemDragListener) dragExtra; 290 dragListener.setLauncher(launcher); 291 292 launcher.getDragLayer().setOnDragListener(dragListener); 293 return true; 294 } 295 return false; 296 } 297 298 public static final Parcelable.Creator<PinItemDragListener> CREATOR = 299 new Parcelable.Creator<PinItemDragListener>() { 300 public PinItemDragListener createFromParcel(Parcel source) { 301 return new PinItemDragListener(source); 302 } 303 304 public PinItemDragListener[] newArray(int size) { 305 return new PinItemDragListener[size]; 306 } 307 }; 308} 309