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