1/*
2 * Copyright (C) 2010 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;
18
19import android.animation.AnimatorSet;
20import android.animation.FloatArrayEvaluator;
21import android.animation.ObjectAnimator;
22import android.animation.ValueAnimator;
23import android.animation.ValueAnimator.AnimatorUpdateListener;
24import android.annotation.TargetApi;
25import android.content.Context;
26import android.content.res.ColorStateList;
27import android.graphics.ColorMatrix;
28import android.graphics.ColorMatrixColorFilter;
29import android.graphics.PointF;
30import android.graphics.Rect;
31import android.graphics.drawable.Drawable;
32import android.os.Build;
33import android.util.AttributeSet;
34import android.view.View;
35import android.view.View.OnClickListener;
36import android.view.ViewGroup;
37import android.view.animation.DecelerateInterpolator;
38import android.view.animation.LinearInterpolator;
39import android.widget.TextView;
40
41import com.android.launcher3.util.Thunk;
42
43/**
44 * Implements a DropTarget.
45 */
46public abstract class ButtonDropTarget extends TextView
47        implements DropTarget, DragController.DragListener, OnClickListener {
48
49    private static int DRAG_VIEW_DROP_DURATION = 285;
50
51    protected Launcher mLauncher;
52    private int mBottomDragPadding;
53    protected SearchDropTargetBar mSearchDropTargetBar;
54
55    /** Whether this drop target is active for the current drag */
56    protected boolean mActive;
57
58    /** The paint applied to the drag view on hover */
59    protected int mHoverColor = 0;
60
61    protected ColorStateList mOriginalTextColor;
62    protected Drawable mDrawable;
63
64    private AnimatorSet mCurrentColorAnim;
65    @Thunk ColorMatrix mSrcFilter, mDstFilter, mCurrentFilter;
66
67
68    public ButtonDropTarget(Context context, AttributeSet attrs) {
69        this(context, attrs, 0);
70    }
71
72    public ButtonDropTarget(Context context, AttributeSet attrs, int defStyle) {
73        super(context, attrs, defStyle);
74        mBottomDragPadding = getResources().getDimensionPixelSize(R.dimen.drop_target_drag_padding);
75    }
76
77    @Override
78    protected void onFinishInflate() {
79        super.onFinishInflate();
80        mOriginalTextColor = getTextColors();
81
82        // Remove the text in the Phone UI in landscape
83        DeviceProfile grid = ((Launcher) getContext()).getDeviceProfile();
84        if (grid.isVerticalBarLayout()) {
85            setText("");
86        }
87    }
88
89    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
90    protected void setDrawable(int resId) {
91        // We do not set the drawable in the xml as that inflates two drawables corresponding to
92        // drawableLeft and drawableStart.
93        mDrawable = getResources().getDrawable(resId);
94
95        if (Utilities.ATLEAST_JB_MR1) {
96            setCompoundDrawablesRelativeWithIntrinsicBounds(mDrawable, null, null, null);
97        } else {
98            setCompoundDrawablesWithIntrinsicBounds(mDrawable, null, null, null);
99        }
100    }
101
102    public void setLauncher(Launcher launcher) {
103        mLauncher = launcher;
104    }
105
106    public void setSearchDropTargetBar(SearchDropTargetBar searchDropTargetBar) {
107        mSearchDropTargetBar = searchDropTargetBar;
108    }
109
110    @Override
111    public void onFlingToDelete(DragObject d, PointF vec) { }
112
113    @Override
114    public final void onDragEnter(DragObject d) {
115        d.dragView.setColor(mHoverColor);
116        if (Utilities.ATLEAST_LOLLIPOP) {
117            animateTextColor(mHoverColor);
118        } else {
119            if (mCurrentFilter == null) {
120                mCurrentFilter = new ColorMatrix();
121            }
122            DragView.setColorScale(mHoverColor, mCurrentFilter);
123            mDrawable.setColorFilter(new ColorMatrixColorFilter(mCurrentFilter));
124            setTextColor(mHoverColor);
125        }
126    }
127
128    @Override
129    public void onDragOver(DragObject d) {
130        // Do nothing
131    }
132
133    protected void resetHoverColor() {
134        if (Utilities.ATLEAST_LOLLIPOP) {
135            animateTextColor(mOriginalTextColor.getDefaultColor());
136        } else {
137            mDrawable.setColorFilter(null);
138            setTextColor(mOriginalTextColor);
139        }
140    }
141
142    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
143    private void animateTextColor(int targetColor) {
144        if (mCurrentColorAnim != null) {
145            mCurrentColorAnim.cancel();
146        }
147
148        mCurrentColorAnim = new AnimatorSet();
149        mCurrentColorAnim.setDuration(DragView.COLOR_CHANGE_DURATION);
150
151        if (mSrcFilter == null) {
152            mSrcFilter = new ColorMatrix();
153            mDstFilter = new ColorMatrix();
154            mCurrentFilter = new ColorMatrix();
155        }
156
157        DragView.setColorScale(getTextColor(), mSrcFilter);
158        DragView.setColorScale(targetColor, mDstFilter);
159        ValueAnimator anim1 = ValueAnimator.ofObject(
160                new FloatArrayEvaluator(mCurrentFilter.getArray()),
161                mSrcFilter.getArray(), mDstFilter.getArray());
162        anim1.addUpdateListener(new AnimatorUpdateListener() {
163
164            @Override
165            public void onAnimationUpdate(ValueAnimator animation) {
166                mDrawable.setColorFilter(new ColorMatrixColorFilter(mCurrentFilter));
167                invalidate();
168            }
169        });
170
171        mCurrentColorAnim.play(anim1);
172        mCurrentColorAnim.play(ObjectAnimator.ofArgb(this, "textColor", targetColor));
173        mCurrentColorAnim.start();
174    }
175
176    @Override
177    public final void onDragExit(DragObject d) {
178        if (!d.dragComplete) {
179            d.dragView.setColor(0);
180            resetHoverColor();
181        } else {
182            // Restore the hover color
183            d.dragView.setColor(mHoverColor);
184        }
185    }
186
187	@Override
188    public final void onDragStart(DragSource source, Object info, int dragAction) {
189        mActive = supportsDrop(source, info);
190        mDrawable.setColorFilter(null);
191        if (mCurrentColorAnim != null) {
192            mCurrentColorAnim.cancel();
193            mCurrentColorAnim = null;
194        }
195        setTextColor(mOriginalTextColor);
196        ((ViewGroup) getParent()).setVisibility(mActive ? View.VISIBLE : View.GONE);
197    }
198
199    @Override
200    public final boolean acceptDrop(DragObject dragObject) {
201        return supportsDrop(dragObject.dragSource, dragObject.dragInfo);
202    }
203
204    protected abstract boolean supportsDrop(DragSource source, Object info);
205
206    @Override
207    public boolean isDropEnabled() {
208        return mActive;
209    }
210
211    @Override
212    public void onDragEnd() {
213        mActive = false;
214    }
215
216    /**
217     * On drop animate the dropView to the icon.
218     */
219    @Override
220    public void onDrop(final DragObject d) {
221        final DragLayer dragLayer = mLauncher.getDragLayer();
222        final Rect from = new Rect();
223        dragLayer.getViewRectRelativeToSelf(d.dragView, from);
224
225        int width = mDrawable.getIntrinsicWidth();
226        int height = mDrawable.getIntrinsicHeight();
227        final Rect to = getIconRect(d.dragView.getMeasuredWidth(), d.dragView.getMeasuredHeight(),
228                width, height);
229        final float scale = (float) to.width() / from.width();
230        mSearchDropTargetBar.deferOnDragEnd();
231
232        Runnable onAnimationEndRunnable = new Runnable() {
233            @Override
234            public void run() {
235                completeDrop(d);
236                mSearchDropTargetBar.onDragEnd();
237                mLauncher.exitSpringLoadedDragModeDelayed(true, 0, null);
238            }
239        };
240        dragLayer.animateView(d.dragView, from, to, scale, 1f, 1f, 0.1f, 0.1f,
241                DRAG_VIEW_DROP_DURATION, new DecelerateInterpolator(2),
242                new LinearInterpolator(), onAnimationEndRunnable,
243                DragLayer.ANIMATION_END_DISAPPEAR, null);
244    }
245
246    @Override
247    public void prepareAccessibilityDrop() { }
248
249    @Thunk abstract void completeDrop(DragObject d);
250
251    @Override
252    public void getHitRectRelativeToDragLayer(android.graphics.Rect outRect) {
253        super.getHitRect(outRect);
254        outRect.bottom += mBottomDragPadding;
255
256        int[] coords = new int[2];
257        mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, coords);
258        outRect.offsetTo(coords[0], coords[1]);
259    }
260
261    protected Rect getIconRect(int viewWidth, int viewHeight, int drawableWidth, int drawableHeight) {
262        DragLayer dragLayer = mLauncher.getDragLayer();
263
264        // Find the rect to animate to (the view is center aligned)
265        Rect to = new Rect();
266        dragLayer.getViewRectRelativeToSelf(this, to);
267
268        final int width = drawableWidth;
269        final int height = drawableHeight;
270
271        final int left;
272        final int right;
273
274        if (Utilities.isRtl(getResources())) {
275            right = to.right - getPaddingRight();
276            left = right - width;
277        } else {
278            left = to.left + getPaddingLeft();
279            right = left + width;
280        }
281
282        final int top = to.top + (getMeasuredHeight() - height) / 2;
283        final int bottom = top +  height;
284
285        to.set(left, top, right, bottom);
286
287        // Center the destination rect about the trash icon
288        final int xOffset = (int) -(viewWidth - width) / 2;
289        final int yOffset = (int) -(viewHeight - height) / 2;
290        to.offset(xOffset, yOffset);
291
292        return to;
293    }
294
295    @Override
296    public void getLocationInDragLayer(int[] loc) {
297        mLauncher.getDragLayer().getLocationInDragLayer(this, loc);
298    }
299
300    public void enableAccessibleDrag(boolean enable) {
301        setOnClickListener(enable ? this : null);
302    }
303
304    protected String getAccessibilityDropConfirmation() {
305        return null;
306    }
307
308    @Override
309    public void onClick(View v) {
310        LauncherAppState.getInstance().getAccessibilityDelegate()
311            .handleAccessibleDrop(this, null, getAccessibilityDropConfirmation());
312    }
313
314    public int getTextColor() {
315        return getTextColors().getDefaultColor();
316    }
317}
318