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.content.ClipDescription; 20import android.content.Intent; 21import android.graphics.Point; 22import android.graphics.Rect; 23import android.os.Handler; 24import android.os.Looper; 25import android.os.Parcel; 26import android.os.SystemClock; 27import android.util.Log; 28import android.view.DragEvent; 29import android.view.View; 30 31import com.android.launcher3.DeleteDropTarget; 32import com.android.launcher3.DragSource; 33import com.android.launcher3.DropTarget; 34import com.android.launcher3.Launcher; 35import com.android.launcher3.R; 36import com.android.launcher3.folder.Folder; 37import com.android.launcher3.widget.PendingItemDragHelper; 38 39import java.util.UUID; 40 41/** 42 * {@link DragSource} for handling drop from a different window. 43 */ 44public abstract class BaseItemDragListener implements 45 View.OnDragListener, DragSource, DragOptions.PreDragCondition { 46 47 private static final String TAG = "BaseItemDragListener"; 48 49 private static final String MIME_TYPE_PREFIX = "com.android.launcher3.drag_and_drop/"; 50 public static final String EXTRA_PIN_ITEM_DRAG_LISTENER = "pin_item_drag_listener"; 51 52 // Position of preview relative to the touch location 53 private final Rect mPreviewRect; 54 55 private final int mPreviewBitmapWidth; 56 private final int mPreviewViewWidth; 57 58 // Randomly generated id used to verify the drag event. 59 private final String mId; 60 61 protected Launcher mLauncher; 62 private DragController mDragController; 63 private long mDragStartTime; 64 65 public BaseItemDragListener(Rect previewRect, int previewBitmapWidth, int previewViewWidth) { 66 mPreviewRect = previewRect; 67 mPreviewBitmapWidth = previewBitmapWidth; 68 mPreviewViewWidth = previewViewWidth; 69 mId = UUID.randomUUID().toString(); 70 } 71 72 protected BaseItemDragListener(Parcel parcel) { 73 mPreviewRect = Rect.CREATOR.createFromParcel(parcel); 74 mPreviewBitmapWidth = parcel.readInt(); 75 mPreviewViewWidth = parcel.readInt(); 76 mId = parcel.readString(); 77 } 78 79 protected void writeToParcel(Parcel parcel, int i) { 80 mPreviewRect.writeToParcel(parcel, i); 81 parcel.writeInt(mPreviewBitmapWidth); 82 parcel.writeInt(mPreviewViewWidth); 83 parcel.writeString(mId); 84 } 85 86 public String getMimeType() { 87 return MIME_TYPE_PREFIX + mId; 88 } 89 90 public void setLauncher(Launcher launcher) { 91 mLauncher = launcher; 92 mDragController = launcher.getDragController(); 93 } 94 95 @Override 96 public boolean onDrag(View view, DragEvent event) { 97 if (mLauncher == null || mDragController == null) { 98 postCleanup(); 99 return false; 100 } 101 if (event.getAction() == DragEvent.ACTION_DRAG_STARTED) { 102 if (onDragStart(event)) { 103 return true; 104 } else { 105 postCleanup(); 106 return false; 107 } 108 } 109 return mDragController.onDragEvent(mDragStartTime, event); 110 } 111 112 protected boolean onDragStart(DragEvent event) { 113 ClipDescription desc = event.getClipDescription(); 114 if (desc == null || !desc.hasMimeType(getMimeType())) { 115 Log.e(TAG, "Someone started a dragAndDrop before us."); 116 return false; 117 } 118 119 Point downPos = new Point((int) event.getX(), (int) event.getY()); 120 DragOptions options = new DragOptions(); 121 options.systemDndStartPoint = downPos; 122 options.preDragCondition = this; 123 124 // We use drag event position as the screenPos for the preview image. Since mPreviewRect 125 // already includes the view position relative to the drag event on the source window, 126 // and the absolute position (position relative to the screen) of drag event is same 127 // across windows, using drag position here give a good estimate for relative position 128 // to source window. 129 createDragHelper().startDrag(new Rect(mPreviewRect), 130 mPreviewBitmapWidth, mPreviewViewWidth, downPos, this, options); 131 mDragStartTime = SystemClock.uptimeMillis(); 132 return true; 133 } 134 135 protected abstract PendingItemDragHelper createDragHelper(); 136 137 @Override 138 public boolean shouldStartDrag(double distanceDragged) { 139 // Stay in pre-drag mode, if workspace is locked. 140 return !mLauncher.isWorkspaceLocked(); 141 } 142 143 @Override 144 public void onPreDragStart(DropTarget.DragObject dragObject) { 145 // The predrag starts when the workspace is not yet loaded. In some cases we set 146 // the dragLayer alpha to 0 to have a nice fade-in animation. But that will prevent the 147 // dragView from being visible. Instead just skip the fade-in animation here. 148 mLauncher.getDragLayer().setAlpha(1); 149 150 dragObject.dragView.setColor( 151 mLauncher.getResources().getColor(R.color.delete_target_hover_tint)); 152 } 153 154 @Override 155 public void onPreDragEnd(DropTarget.DragObject dragObject, boolean dragStarted) { 156 if (dragStarted) { 157 dragObject.dragView.setColor(0); 158 } 159 } 160 161 @Override 162 public boolean supportsAppInfoDropTarget() { 163 return false; 164 } 165 166 @Override 167 public boolean supportsDeleteDropTarget() { 168 return false; 169 } 170 171 @Override 172 public float getIntrinsicIconScaleFactor() { 173 return 1f; 174 } 175 176 @Override 177 public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete, 178 boolean success) { 179 if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() && 180 !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) { 181 // Exit spring loaded mode if we have not successfully dropped or have not handled the 182 // drop in Workspace 183 mLauncher.exitSpringLoadedDragModeDelayed(true, 184 Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null); 185 } 186 187 if (!success) { 188 d.deferDragViewCleanupPostAnimation = false; 189 } 190 postCleanup(); 191 } 192 193 private void postCleanup() { 194 if (mLauncher != null) { 195 // Remove any drag params from the launcher intent since the drag operation is complete. 196 Intent newIntent = new Intent(mLauncher.getIntent()); 197 newIntent.removeExtra(EXTRA_PIN_ITEM_DRAG_LISTENER); 198 mLauncher.setIntent(newIntent); 199 } 200 201 new Handler(Looper.getMainLooper()).post(new Runnable() { 202 @Override 203 public void run() { 204 removeListener(); 205 } 206 }); 207 } 208 209 public void removeListener() { 210 if (mLauncher != null) { 211 mLauncher.getDragLayer().setOnDragListener(null); 212 } 213 } 214} 215