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