1/*
2 * Copyright (C) 2015 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.ClipData;
20import android.content.ClipDescription;
21import android.content.Context;
22import android.content.Intent;
23import android.view.DragEvent;
24import android.view.MotionEvent;
25
26import com.android.launcher3.DropTarget;
27import com.android.launcher3.DropTarget.DragObject;
28import com.android.launcher3.InstallShortcutReceiver;
29import com.android.launcher3.ShortcutInfo;
30import com.android.launcher3.Utilities;
31
32import java.util.ArrayList;
33
34/**
35 * Base class for driving a drag/drop operation.
36 */
37public abstract class DragDriver {
38    protected final EventListener mEventListener;
39
40    public interface EventListener {
41        void onDriverDragMove(float x, float y);
42        void onDriverDragExitWindow();
43        void onDriverDragEnd(float x, float y, DropTarget dropTargetOverride);
44        void onDriverDragCancel();
45    }
46
47    public DragDriver(EventListener eventListener) {
48        mEventListener = eventListener;
49    }
50
51    /**
52     * Handles ending of the DragView animation.
53     */
54    public void onDragViewAnimationEnd() { }
55
56    public boolean onTouchEvent(MotionEvent ev) {
57        final int action = ev.getAction();
58
59        switch (action) {
60            case MotionEvent.ACTION_MOVE:
61                mEventListener.onDriverDragMove(ev.getX(), ev.getY());
62                break;
63            case MotionEvent.ACTION_UP:
64                mEventListener.onDriverDragMove(ev.getX(), ev.getY());
65                mEventListener.onDriverDragEnd(ev.getX(), ev.getY(), null);
66                break;
67            case MotionEvent.ACTION_CANCEL:
68                mEventListener.onDriverDragCancel();
69                break;
70        }
71
72        return true;
73    }
74
75    public abstract boolean onDragEvent (DragEvent event);
76
77
78    public boolean onInterceptTouchEvent(MotionEvent ev) {
79        final int action = ev.getAction();
80
81        switch (action) {
82            case MotionEvent.ACTION_UP:
83                mEventListener.onDriverDragEnd(ev.getX(), ev.getY(), null);
84                break;
85            case MotionEvent.ACTION_CANCEL:
86                mEventListener.onDriverDragCancel();
87                break;
88        }
89
90        return true;
91    }
92
93    public static DragDriver create(Context context, DragController dragController,
94            DragObject dragObject, DragOptions options) {
95        if (Utilities.isNycOrAbove() && options.systemDndStartPoint != null) {
96            return new SystemDragDriver(dragController, context, dragObject);
97        } else {
98            return new InternalDragDriver(dragController);
99        }
100    }
101}
102
103/**
104 * Class for driving a system (i.e. framework) drag/drop operation.
105 */
106class SystemDragDriver extends DragDriver {
107
108    private final DragObject mDragObject;
109    private final Context mContext;
110
111    boolean mReceivedDropEvent = false;
112    float mLastX = 0;
113    float mLastY = 0;
114
115    public SystemDragDriver(DragController dragController, Context context, DragObject dragObject) {
116        super(dragController);
117        mDragObject = dragObject;
118        mContext = context;
119    }
120
121    @Override
122    public boolean onTouchEvent(MotionEvent ev) {
123        return false;
124    }
125
126    @Override
127    public boolean onInterceptTouchEvent(MotionEvent ev) {
128        return false;
129    }
130
131    @Override
132    public boolean onDragEvent (DragEvent event) {
133        final int action = event.getAction();
134
135        switch (action) {
136            case DragEvent.ACTION_DRAG_STARTED:
137                mLastX = event.getX();
138                mLastY = event.getY();
139                return true;
140
141            case DragEvent.ACTION_DRAG_ENTERED:
142                return true;
143
144            case DragEvent.ACTION_DRAG_LOCATION:
145                mLastX = event.getX();
146                mLastY = event.getY();
147                mEventListener.onDriverDragMove(event.getX(), event.getY());
148                return true;
149
150            case DragEvent.ACTION_DROP:
151                mLastX = event.getX();
152                mLastY = event.getY();
153                mReceivedDropEvent =
154                        updateInfoFromClipData(event.getClipData(), event.getClipDescription());
155                return mReceivedDropEvent;
156
157            case DragEvent.ACTION_DRAG_EXITED:
158                mEventListener.onDriverDragExitWindow();
159                return true;
160
161            case DragEvent.ACTION_DRAG_ENDED:
162                if (mReceivedDropEvent) {
163                    mEventListener.onDriverDragEnd(mLastX, mLastY, null);
164                } else {
165                    mEventListener.onDriverDragCancel();
166                }
167                return true;
168
169            default:
170                return false;
171        }
172    }
173
174    private boolean updateInfoFromClipData(ClipData data, ClipDescription desc) {
175        if (data == null) {
176            return false;
177        }
178        ArrayList<Intent> intents = new ArrayList<>();
179        int itemCount = data.getItemCount();
180        for (int i = 0; i < itemCount; i++) {
181            Intent intent = data.getItemAt(i).getIntent();
182            if (intent == null) {
183                continue;
184            }
185
186            // Give preference to shortcut intents.
187            if (!Intent.ACTION_CREATE_SHORTCUT.equals(intent.getAction())) {
188                intents.add(intent);
189                continue;
190            }
191            ShortcutInfo info = InstallShortcutReceiver.fromShortcutIntent(mContext, intent);
192            if (info != null) {
193                mDragObject.dragInfo = info;
194                return true;
195            }
196            return true;
197        }
198
199        // Try creating shortcuts just using the intent and label
200        Intent fullIntent = new Intent().putExtra(Intent.EXTRA_SHORTCUT_NAME, desc.getLabel());
201        for (Intent intent : intents) {
202            fullIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, intent);
203            ShortcutInfo info = InstallShortcutReceiver.fromShortcutIntent(mContext, fullIntent);
204            if (info != null) {
205                mDragObject.dragInfo = info;
206                return true;
207            }
208        }
209
210        return false;
211    }
212}
213
214/**
215 * Class for driving an internal (i.e. not using framework) drag/drop operation.
216 */
217class InternalDragDriver extends DragDriver {
218    public InternalDragDriver(DragController dragController) {
219        super(dragController);
220    }
221
222    @Override
223    public boolean onDragEvent (DragEvent event) { return false; }
224};
225