• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
20 
21 import static com.android.launcher3.LauncherState.NORMAL;
22 
23 import android.animation.AnimatorSet;
24 import android.animation.FloatArrayEvaluator;
25 import android.animation.ObjectAnimator;
26 import android.animation.ValueAnimator;
27 import android.content.Context;
28 import android.content.res.ColorStateList;
29 import android.content.res.Resources;
30 import android.graphics.ColorMatrix;
31 import android.graphics.ColorMatrixColorFilter;
32 import android.graphics.Rect;
33 import android.graphics.drawable.Drawable;
34 import android.text.TextUtils;
35 import android.util.AttributeSet;
36 import android.view.LayoutInflater;
37 import android.view.View;
38 import android.view.View.OnClickListener;
39 import android.view.accessibility.AccessibilityEvent;
40 import android.widget.PopupWindow;
41 import android.widget.TextView;
42 
43 import com.android.launcher3.anim.Interpolators;
44 import com.android.launcher3.dragndrop.DragController;
45 import com.android.launcher3.dragndrop.DragLayer;
46 import com.android.launcher3.dragndrop.DragOptions;
47 import com.android.launcher3.dragndrop.DragView;
48 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
49 import com.android.launcher3.util.Themes;
50 import com.android.launcher3.util.Thunk;
51 
52 /**
53  * Implements a DropTarget.
54  */
55 public abstract class ButtonDropTarget extends TextView
56         implements DropTarget, DragController.DragListener, OnClickListener {
57 
58     private static final int[] sTempCords = new int[2];
59     private static final int DRAG_VIEW_DROP_DURATION = 285;
60 
61     public static final int TOOLTIP_DEFAULT = 0;
62     public static final int TOOLTIP_LEFT = 1;
63     public static final int TOOLTIP_RIGHT = 2;
64 
65     protected final Launcher mLauncher;
66 
67     private int mBottomDragPadding;
68     protected DropTargetBar mDropTargetBar;
69 
70     /** Whether this drop target is active for the current drag */
71     protected boolean mActive;
72     /** Whether an accessible drag is in progress */
73     private boolean mAccessibleDrag;
74     /** An item must be dragged at least this many pixels before this drop target is enabled. */
75     private final int mDragDistanceThreshold;
76 
77     /** The paint applied to the drag view on hover */
78     protected int mHoverColor = 0;
79 
80     protected CharSequence mText;
81     protected ColorStateList mOriginalTextColor;
82     protected Drawable mDrawable;
83     private boolean mTextVisible = true;
84 
85     private PopupWindow mToolTip;
86     private int mToolTipLocation;
87 
88     private AnimatorSet mCurrentColorAnim;
89     @Thunk ColorMatrix mSrcFilter, mDstFilter, mCurrentFilter;
90 
ButtonDropTarget(Context context, AttributeSet attrs)91     public ButtonDropTarget(Context context, AttributeSet attrs) {
92         this(context, attrs, 0);
93     }
94 
ButtonDropTarget(Context context, AttributeSet attrs, int defStyle)95     public ButtonDropTarget(Context context, AttributeSet attrs, int defStyle) {
96         super(context, attrs, defStyle);
97         mLauncher = Launcher.getLauncher(context);
98 
99         Resources resources = getResources();
100         mBottomDragPadding = resources.getDimensionPixelSize(R.dimen.drop_target_drag_padding);
101         mDragDistanceThreshold = resources.getDimensionPixelSize(R.dimen.drag_distanceThreshold);
102     }
103 
104     @Override
onFinishInflate()105     protected void onFinishInflate() {
106         super.onFinishInflate();
107         mText = getText();
108         mOriginalTextColor = getTextColors();
109         setContentDescription(mText);
110     }
111 
updateText(int resId)112     protected void updateText(int resId) {
113         setText(resId);
114         mText = getText();
115         setContentDescription(mText);
116     }
117 
setDrawable(int resId)118     protected void setDrawable(int resId) {
119         // We do not set the drawable in the xml as that inflates two drawables corresponding to
120         // drawableLeft and drawableStart.
121         if (mTextVisible) {
122             setCompoundDrawablesRelativeWithIntrinsicBounds(resId, 0, 0, 0);
123             mDrawable = getCompoundDrawablesRelative()[0];
124         } else {
125             setCompoundDrawablesRelativeWithIntrinsicBounds(0, resId, 0, 0);
126             mDrawable = getCompoundDrawablesRelative()[1];
127         }
128     }
129 
setDropTargetBar(DropTargetBar dropTargetBar)130     public void setDropTargetBar(DropTargetBar dropTargetBar) {
131         mDropTargetBar = dropTargetBar;
132     }
133 
hideTooltip()134     private void hideTooltip() {
135         if (mToolTip != null) {
136             mToolTip.dismiss();
137             mToolTip = null;
138         }
139     }
140 
141     @Override
onDragEnter(DragObject d)142     public final void onDragEnter(DragObject d) {
143         if (!d.accessibleDrag && !mTextVisible) {
144             // Show tooltip
145             hideTooltip();
146 
147             TextView message = (TextView) LayoutInflater.from(getContext()).inflate(
148                     R.layout.drop_target_tool_tip, null);
149             message.setText(mText);
150 
151             mToolTip = new PopupWindow(message, WRAP_CONTENT, WRAP_CONTENT);
152             int x = 0, y = 0;
153             if (mToolTipLocation != TOOLTIP_DEFAULT) {
154                 y = -getMeasuredHeight();
155                 message.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
156                 if (mToolTipLocation == TOOLTIP_LEFT) {
157                     x = - getMeasuredWidth() - message.getMeasuredWidth() / 2;
158                 } else {
159                     x = getMeasuredWidth() / 2 + message.getMeasuredWidth() / 2;
160                 }
161             }
162             mToolTip.showAsDropDown(this, x, y);
163         }
164 
165         d.dragView.setColor(mHoverColor);
166         animateTextColor(mHoverColor);
167         if (d.stateAnnouncer != null) {
168             d.stateAnnouncer.cancel();
169         }
170         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
171     }
172 
173     @Override
onDragOver(DragObject d)174     public void onDragOver(DragObject d) {
175         // Do nothing
176     }
177 
resetHoverColor()178     protected void resetHoverColor() {
179         animateTextColor(mOriginalTextColor.getDefaultColor());
180     }
181 
animateTextColor(int targetColor)182     private void animateTextColor(int targetColor) {
183         if (mCurrentColorAnim != null) {
184             mCurrentColorAnim.cancel();
185         }
186 
187         mCurrentColorAnim = new AnimatorSet();
188         mCurrentColorAnim.setDuration(DragView.COLOR_CHANGE_DURATION);
189 
190         if (mSrcFilter == null) {
191             mSrcFilter = new ColorMatrix();
192             mDstFilter = new ColorMatrix();
193             mCurrentFilter = new ColorMatrix();
194         }
195 
196         int defaultTextColor = mOriginalTextColor.getDefaultColor();
197         Themes.setColorChangeOnMatrix(defaultTextColor, getTextColor(), mSrcFilter);
198         Themes.setColorChangeOnMatrix(defaultTextColor, targetColor, mDstFilter);
199 
200         ValueAnimator anim1 = ValueAnimator.ofObject(
201                 new FloatArrayEvaluator(mCurrentFilter.getArray()),
202                 mSrcFilter.getArray(), mDstFilter.getArray());
203         anim1.addUpdateListener((anim) -> {
204             mDrawable.setColorFilter(new ColorMatrixColorFilter(mCurrentFilter));
205             invalidate();
206         });
207 
208         mCurrentColorAnim.play(anim1);
209         mCurrentColorAnim.play(ObjectAnimator.ofArgb(this, "textColor", targetColor));
210         mCurrentColorAnim.start();
211     }
212 
213     @Override
onDragExit(DragObject d)214     public final void onDragExit(DragObject d) {
215         hideTooltip();
216 
217         if (!d.dragComplete) {
218             d.dragView.setColor(0);
219             resetHoverColor();
220         } else {
221             // Restore the hover color
222             d.dragView.setColor(mHoverColor);
223         }
224     }
225 
226     @Override
onDragStart(DropTarget.DragObject dragObject, DragOptions options)227     public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
228         mActive = supportsDrop(dragObject.dragInfo);
229         mDrawable.setColorFilter(null);
230         if (mCurrentColorAnim != null) {
231             mCurrentColorAnim.cancel();
232             mCurrentColorAnim = null;
233         }
234         setTextColor(mOriginalTextColor);
235         setVisibility(mActive ? View.VISIBLE : View.GONE);
236 
237         mAccessibleDrag = options.isAccessibleDrag;
238         setOnClickListener(mAccessibleDrag ? this : null);
239     }
240 
241     @Override
acceptDrop(DragObject dragObject)242     public final boolean acceptDrop(DragObject dragObject) {
243         return supportsDrop(dragObject.dragInfo);
244     }
245 
supportsDrop(ItemInfo info)246     protected abstract boolean supportsDrop(ItemInfo info);
247 
supportsAccessibilityDrop(ItemInfo info, View view)248     public abstract boolean supportsAccessibilityDrop(ItemInfo info, View view);
249 
250     @Override
isDropEnabled()251     public boolean isDropEnabled() {
252         return mActive && (mAccessibleDrag ||
253                 mLauncher.getDragController().getDistanceDragged() >= mDragDistanceThreshold);
254     }
255 
256     @Override
onDragEnd()257     public void onDragEnd() {
258         mActive = false;
259         setOnClickListener(null);
260     }
261 
262     /**
263      * On drop animate the dropView to the icon.
264      */
265     @Override
onDrop(final DragObject d, final DragOptions options)266     public void onDrop(final DragObject d, final DragOptions options) {
267         final DragLayer dragLayer = mLauncher.getDragLayer();
268         final Rect from = new Rect();
269         dragLayer.getViewRectRelativeToSelf(d.dragView, from);
270 
271         final Rect to = getIconRect(d);
272         final float scale = (float) to.width() / from.width();
273         mDropTargetBar.deferOnDragEnd();
274 
275         Runnable onAnimationEndRunnable = () -> {
276             completeDrop(d);
277             mDropTargetBar.onDragEnd();
278             mLauncher.getStateManager().goToState(NORMAL);
279         };
280 
281         dragLayer.animateView(d.dragView, from, to, scale, 1f, 1f, 0.1f, 0.1f,
282                 DRAG_VIEW_DROP_DURATION,
283                 Interpolators.DEACCEL_2, Interpolators.LINEAR, onAnimationEndRunnable,
284                 DragLayer.ANIMATION_END_DISAPPEAR, null);
285     }
286 
getAccessibilityAction()287     public abstract int getAccessibilityAction();
288 
289     @Override
prepareAccessibilityDrop()290     public void prepareAccessibilityDrop() { }
291 
onAccessibilityDrop(View view, ItemInfo item)292     public abstract void onAccessibilityDrop(View view, ItemInfo item);
293 
completeDrop(DragObject d)294     public abstract void completeDrop(DragObject d);
295 
296     @Override
getHitRectRelativeToDragLayer(android.graphics.Rect outRect)297     public void getHitRectRelativeToDragLayer(android.graphics.Rect outRect) {
298         super.getHitRect(outRect);
299         outRect.bottom += mBottomDragPadding;
300 
301         sTempCords[0] = sTempCords[1] = 0;
302         mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, sTempCords);
303         outRect.offsetTo(sTempCords[0], sTempCords[1]);
304     }
305 
getIconRect(DragObject dragObject)306     public Rect getIconRect(DragObject dragObject) {
307         int viewWidth = dragObject.dragView.getMeasuredWidth();
308         int viewHeight = dragObject.dragView.getMeasuredHeight();
309         int drawableWidth = mDrawable.getIntrinsicWidth();
310         int drawableHeight = mDrawable.getIntrinsicHeight();
311         DragLayer dragLayer = mLauncher.getDragLayer();
312 
313         // Find the rect to animate to (the view is center aligned)
314         Rect to = new Rect();
315         dragLayer.getViewRectRelativeToSelf(this, to);
316 
317         final int width = drawableWidth;
318         final int height = drawableHeight;
319 
320         final int left;
321         final int right;
322 
323         if (Utilities.isRtl(getResources())) {
324             right = to.right - getPaddingRight();
325             left = right - width;
326         } else {
327             left = to.left + getPaddingLeft();
328             right = left + width;
329         }
330 
331         final int top = to.top + (getMeasuredHeight() - height) / 2;
332         final int bottom = top +  height;
333 
334         to.set(left, top, right, bottom);
335 
336         // Center the destination rect about the trash icon
337         final int xOffset = -(viewWidth - width) / 2;
338         final int yOffset = -(viewHeight - height) / 2;
339         to.offset(xOffset, yOffset);
340 
341         return to;
342     }
343 
344     @Override
onClick(View v)345     public void onClick(View v) {
346         mLauncher.getAccessibilityDelegate().handleAccessibleDrop(this, null, null);
347     }
348 
getTextColor()349     public int getTextColor() {
350         return getTextColors().getDefaultColor();
351     }
352 
setTextVisible(boolean isVisible)353     public void setTextVisible(boolean isVisible) {
354         CharSequence newText = isVisible ? mText : "";
355         if (mTextVisible != isVisible || !TextUtils.equals(newText, getText())) {
356             mTextVisible = isVisible;
357             setText(newText);
358             if (mTextVisible) {
359                 setCompoundDrawablesRelativeWithIntrinsicBounds(mDrawable, null, null, null);
360             } else {
361                 setCompoundDrawablesRelativeWithIntrinsicBounds(null, mDrawable, null, null);
362             }
363         }
364     }
365 
setToolTipLocation(int location)366     public void setToolTipLocation(int location) {
367         mToolTipLocation = location;
368         hideTooltip();
369     }
370 
isTextTruncated(int availableWidth)371     public boolean isTextTruncated(int availableWidth) {
372         availableWidth -= (getPaddingLeft() + getPaddingRight() + mDrawable.getIntrinsicWidth()
373                 + getCompoundDrawablePadding());
374         CharSequence displayedText = TextUtils.ellipsize(mText, getPaint(), availableWidth,
375                 TextUtils.TruncateAt.END);
376         return !mText.equals(displayedText);
377     }
378 
getDropTargetForLogging()379     public abstract Target getDropTargetForLogging();
380 }
381