• 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.content.Context;
24 import android.content.res.Resources;
25 import android.graphics.Rect;
26 import android.graphics.drawable.Drawable;
27 import android.text.TextUtils;
28 import android.util.AttributeSet;
29 import android.view.LayoutInflater;
30 import android.view.View;
31 import android.view.View.OnClickListener;
32 import android.view.accessibility.AccessibilityEvent;
33 import android.widget.PopupWindow;
34 import android.widget.TextView;
35 
36 import com.android.launcher3.anim.Interpolators;
37 import com.android.launcher3.dragndrop.DragController;
38 import com.android.launcher3.dragndrop.DragLayer;
39 import com.android.launcher3.dragndrop.DragOptions;
40 import com.android.launcher3.dragndrop.DragView;
41 import com.android.launcher3.model.data.ItemInfo;
42 
43 /**
44  * Implements a DropTarget.
45  */
46 public abstract class ButtonDropTarget extends TextView
47         implements DropTarget, DragController.DragListener, OnClickListener {
48 
49     private static final int[] sTempCords = new int[2];
50     private static final int DRAG_VIEW_DROP_DURATION = 285;
51     private static final float DRAG_VIEW_HOVER_OVER_OPACITY = 0.65f;
52 
53     public static final int TOOLTIP_DEFAULT = 0;
54     public static final int TOOLTIP_LEFT = 1;
55     public static final int TOOLTIP_RIGHT = 2;
56 
57     protected final Launcher mLauncher;
58 
59     protected DropTargetBar mDropTargetBar;
60 
61     /** Whether this drop target is active for the current drag */
62     protected boolean mActive;
63     /** Whether an accessible drag is in progress */
64     private boolean mAccessibleDrag;
65     /** An item must be dragged at least this many pixels before this drop target is enabled. */
66     private final int mDragDistanceThreshold;
67     /** The size of the drawable shown in the drop target. */
68     private final int mDrawableSize;
69 
70     protected CharSequence mText;
71     protected Drawable mDrawable;
72     private boolean mTextVisible = true;
73 
74     private PopupWindow mToolTip;
75     private int mToolTipLocation;
76 
ButtonDropTarget(Context context, AttributeSet attrs)77     public ButtonDropTarget(Context context, AttributeSet attrs) {
78         this(context, attrs, 0);
79     }
80 
ButtonDropTarget(Context context, AttributeSet attrs, int defStyle)81     public ButtonDropTarget(Context context, AttributeSet attrs, int defStyle) {
82         super(context, attrs, defStyle);
83         mLauncher = Launcher.getLauncher(context);
84 
85         Resources resources = getResources();
86         mDragDistanceThreshold = resources.getDimensionPixelSize(R.dimen.drag_distanceThreshold);
87         mDrawableSize = resources.getDimensionPixelSize(R.dimen.drop_target_text_size);
88     }
89 
90     @Override
onFinishInflate()91     protected void onFinishInflate() {
92         super.onFinishInflate();
93         mText = getText();
94         setContentDescription(mText);
95     }
96 
updateText(int resId)97     protected void updateText(int resId) {
98         setText(resId);
99         mText = getText();
100         setContentDescription(mText);
101     }
102 
setDrawable(int resId)103     protected void setDrawable(int resId) {
104         // We do not set the drawable in the xml as that inflates two drawables corresponding to
105         // drawableLeft and drawableStart.
106         mDrawable = getContext().getDrawable(resId).mutate();
107         mDrawable.setBounds(0, 0, mDrawableSize, mDrawableSize);
108         mDrawable.setTintList(getTextColors());
109         setCompoundDrawablesRelative(mDrawable, null, null, null);
110     }
111 
setDropTargetBar(DropTargetBar dropTargetBar)112     public void setDropTargetBar(DropTargetBar dropTargetBar) {
113         mDropTargetBar = dropTargetBar;
114     }
115 
hideTooltip()116     private void hideTooltip() {
117         if (mToolTip != null) {
118             mToolTip.dismiss();
119             mToolTip = null;
120         }
121     }
122 
123     @Override
onDragEnter(DragObject d)124     public final void onDragEnter(DragObject d) {
125         if (!mAccessibleDrag && !mTextVisible) {
126             // Show tooltip
127             hideTooltip();
128 
129             TextView message = (TextView) LayoutInflater.from(getContext()).inflate(
130                     R.layout.drop_target_tool_tip, null);
131             message.setText(mText);
132 
133             mToolTip = new PopupWindow(message, WRAP_CONTENT, WRAP_CONTENT);
134             int x = 0, y = 0;
135             if (mToolTipLocation != TOOLTIP_DEFAULT) {
136                 y = -getMeasuredHeight();
137                 message.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
138                 if (mToolTipLocation == TOOLTIP_LEFT) {
139                     x = - getMeasuredWidth() - message.getMeasuredWidth() / 2;
140                 } else {
141                     x = getMeasuredWidth() / 2 + message.getMeasuredWidth() / 2;
142                 }
143             }
144             mToolTip.showAsDropDown(this, x, y);
145         }
146 
147         d.dragView.setAlpha(DRAG_VIEW_HOVER_OVER_OPACITY);
148         setSelected(true);
149         if (d.stateAnnouncer != null) {
150             d.stateAnnouncer.cancel();
151         }
152         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
153     }
154 
155     @Override
onDragOver(DragObject d)156     public void onDragOver(DragObject d) {
157         // Do nothing
158     }
159 
160     @Override
onDragExit(DragObject d)161     public final void onDragExit(DragObject d) {
162         hideTooltip();
163 
164         if (!d.dragComplete) {
165             d.dragView.setAlpha(1f);
166             setSelected(false);
167         } else {
168             d.dragView.setAlpha(DRAG_VIEW_HOVER_OVER_OPACITY);
169         }
170     }
171 
172     @Override
onDragStart(DropTarget.DragObject dragObject, DragOptions options)173     public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
174         mActive = !options.isKeyboardDrag && supportsDrop(dragObject.dragInfo);
175         setVisibility(mActive ? View.VISIBLE : View.GONE);
176 
177         mAccessibleDrag = options.isAccessibleDrag;
178         setOnClickListener(mAccessibleDrag ? this : null);
179     }
180 
181     @Override
acceptDrop(DragObject dragObject)182     public final boolean acceptDrop(DragObject dragObject) {
183         return supportsDrop(dragObject.dragInfo);
184     }
185 
supportsDrop(ItemInfo info)186     protected abstract boolean supportsDrop(ItemInfo info);
187 
supportsAccessibilityDrop(ItemInfo info, View view)188     public abstract boolean supportsAccessibilityDrop(ItemInfo info, View view);
189 
190     @Override
isDropEnabled()191     public boolean isDropEnabled() {
192         return mActive && (mAccessibleDrag ||
193                 mLauncher.getDragController().getDistanceDragged() >= mDragDistanceThreshold);
194     }
195 
196     @Override
onDragEnd()197     public void onDragEnd() {
198         mActive = false;
199         setOnClickListener(null);
200         setSelected(false);
201     }
202 
203     /**
204      * On drop animate the dropView to the icon.
205      */
206     @Override
onDrop(final DragObject d, final DragOptions options)207     public void onDrop(final DragObject d, final DragOptions options) {
208         if (options.isFlingToDelete) {
209             // FlingAnimation handles the animation and then calls completeDrop().
210             return;
211         }
212         final DragLayer dragLayer = mLauncher.getDragLayer();
213         final DragView dragView = d.dragView;
214         final Rect from = new Rect();
215         dragLayer.getViewRectRelativeToSelf(d.dragView, from);
216 
217         final Rect to = getIconRect(d);
218         final float scale = (float) to.width() / from.width();
219         dragView.disableColorExtraction();
220         dragView.detachContentView(/* reattachToPreviousParent= */ true);
221         mDropTargetBar.deferOnDragEnd();
222 
223         Runnable onAnimationEndRunnable = () -> {
224             completeDrop(d);
225             mDropTargetBar.onDragEnd();
226             mLauncher.getStateManager().goToState(NORMAL);
227             // Only re-enable updates once the workspace is back to normal, which will be after the
228             // current frame.
229             post(dragView::resumeColorExtraction);
230         };
231 
232         dragLayer.animateView(d.dragView, from, to, scale, 1f, 1f, 0.1f, 0.1f,
233                 DRAG_VIEW_DROP_DURATION,
234                 Interpolators.DEACCEL_2, Interpolators.LINEAR, onAnimationEndRunnable,
235                 DragLayer.ANIMATION_END_DISAPPEAR, null);
236     }
237 
getAccessibilityAction()238     public abstract int getAccessibilityAction();
239 
240     @Override
prepareAccessibilityDrop()241     public void prepareAccessibilityDrop() { }
242 
onAccessibilityDrop(View view, ItemInfo item)243     public abstract void onAccessibilityDrop(View view, ItemInfo item);
244 
completeDrop(DragObject d)245     public abstract void completeDrop(DragObject d);
246 
247     @Override
getHitRectRelativeToDragLayer(android.graphics.Rect outRect)248     public void getHitRectRelativeToDragLayer(android.graphics.Rect outRect) {
249         super.getHitRect(outRect);
250         outRect.bottom += mLauncher.getDeviceProfile().dropTargetDragPaddingPx;
251 
252         sTempCords[0] = sTempCords[1] = 0;
253         mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, sTempCords);
254         outRect.offsetTo(sTempCords[0], sTempCords[1]);
255     }
256 
getIconRect(DragObject dragObject)257     public Rect getIconRect(DragObject dragObject) {
258         int viewWidth = dragObject.dragView.getMeasuredWidth();
259         int viewHeight = dragObject.dragView.getMeasuredHeight();
260         int drawableWidth = mDrawable.getIntrinsicWidth();
261         int drawableHeight = mDrawable.getIntrinsicHeight();
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 = -(viewWidth - width) / 2;
289         final int yOffset = -(viewHeight - height) / 2;
290         to.offset(xOffset, yOffset);
291 
292         return to;
293     }
294 
295     @Override
onClick(View v)296     public void onClick(View v) {
297         mLauncher.getAccessibilityDelegate().handleAccessibleDrop(this, null, null);
298     }
299 
setTextVisible(boolean isVisible)300     public void setTextVisible(boolean isVisible) {
301         CharSequence newText = isVisible ? mText : "";
302         if (mTextVisible != isVisible || !TextUtils.equals(newText, getText())) {
303             mTextVisible = isVisible;
304             setText(newText);
305             setCompoundDrawablesRelative(mDrawable, null, null, null);
306         }
307     }
308 
setToolTipLocation(int location)309     public void setToolTipLocation(int location) {
310         mToolTipLocation = location;
311         hideTooltip();
312     }
313 
isTextTruncated(int availableWidth)314     public boolean isTextTruncated(int availableWidth) {
315         availableWidth -= (getPaddingLeft() + getPaddingRight() + mDrawable.getIntrinsicWidth()
316                 + getCompoundDrawablePadding());
317         CharSequence displayedText = TextUtils.ellipsize(mText, getPaint(), availableWidth,
318                 TextUtils.TruncateAt.END);
319         return !mText.equals(displayedText);
320     }
321 }
322