• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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 com.android.launcher3.ButtonDropTarget.TOOLTIP_DEFAULT;
20 import static com.android.launcher3.anim.AlphaUpdateListener.updateVisibility;
21 import static com.android.launcher3.config.FeatureFlags.HOME_GARDENING_WORKSPACE_BUTTONS;
22 
23 import android.animation.TimeInterpolator;
24 import android.content.Context;
25 import android.graphics.Rect;
26 import android.util.AttributeSet;
27 import android.util.Log;
28 import android.util.TypedValue;
29 import android.view.Gravity;
30 import android.view.View;
31 import android.view.ViewDebug;
32 import android.view.ViewPropertyAnimator;
33 import android.widget.FrameLayout;
34 
35 import androidx.annotation.NonNull;
36 
37 import com.android.launcher3.anim.Interpolators;
38 import com.android.launcher3.dragndrop.DragController;
39 import com.android.launcher3.dragndrop.DragController.DragListener;
40 import com.android.launcher3.dragndrop.DragOptions;
41 import com.android.launcher3.testing.shared.TestProtocol;
42 
43 /*
44  * The top bar containing various drop targets: Delete/App Info/Uninstall.
45  */
46 public class DropTargetBar extends FrameLayout
47         implements DragListener, Insettable {
48 
49     protected static final int DEFAULT_DRAG_FADE_DURATION = 175;
50     protected static final TimeInterpolator DEFAULT_INTERPOLATOR = Interpolators.ACCEL;
51 
52     private final Runnable mFadeAnimationEndRunnable =
53             () -> updateVisibility(DropTargetBar.this);
54 
55     private final Launcher mLauncher;
56 
57     @ViewDebug.ExportedProperty(category = "launcher")
58     protected boolean mDeferOnDragEnd;
59 
60     @ViewDebug.ExportedProperty(category = "launcher")
61     protected boolean mVisible = false;
62 
63     private ButtonDropTarget[] mDropTargets;
64     private ButtonDropTarget[] mTempTargets;
65     private ViewPropertyAnimator mCurrentAnimation;
66 
67     private boolean mIsVertical = true;
68 
DropTargetBar(Context context, AttributeSet attrs)69     public DropTargetBar(Context context, AttributeSet attrs) {
70         super(context, attrs);
71         mLauncher = Launcher.getLauncher(context);
72     }
73 
DropTargetBar(Context context, AttributeSet attrs, int defStyle)74     public DropTargetBar(Context context, AttributeSet attrs, int defStyle) {
75         super(context, attrs, defStyle);
76         mLauncher = Launcher.getLauncher(context);
77     }
78 
79     @Override
onFinishInflate()80     protected void onFinishInflate() {
81         super.onFinishInflate();
82         mDropTargets = new ButtonDropTarget[getChildCount()];
83         for (int i = 0; i < mDropTargets.length; i++) {
84             mDropTargets[i] = (ButtonDropTarget) getChildAt(i);
85             mDropTargets[i].setDropTargetBar(this);
86         }
87         mTempTargets = new ButtonDropTarget[getChildCount()];
88     }
89 
90     @Override
setInsets(Rect insets)91     public void setInsets(Rect insets) {
92         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
93         DeviceProfile grid = mLauncher.getDeviceProfile();
94         mIsVertical = grid.isVerticalBarLayout();
95 
96         lp.leftMargin = insets.left;
97         lp.topMargin = insets.top;
98         lp.bottomMargin = insets.bottom;
99         lp.rightMargin = insets.right;
100         int tooltipLocation = TOOLTIP_DEFAULT;
101 
102         int horizontalMargin;
103         if (grid.isTablet) {
104             // XXX: If the icon size changes across orientations, we will have to take
105             //      that into account here too.
106             horizontalMargin = ((grid.widthPx - 2 * grid.edgeMarginPx
107                     - (grid.inv.numColumns * grid.cellWidthPx))
108                     / (2 * (grid.inv.numColumns + 1)))
109                     + grid.edgeMarginPx;
110         } else {
111             horizontalMargin = getContext().getResources()
112                     .getDimensionPixelSize(R.dimen.drop_target_bar_margin_horizontal);
113         }
114         lp.topMargin += grid.dropTargetBarTopMarginPx;
115         lp.bottomMargin += grid.dropTargetBarBottomMarginPx;
116         lp.width = grid.availableWidthPx - 2 * horizontalMargin;
117         if (mIsVertical) {
118             lp.leftMargin = (grid.widthPx - lp.width) / 2;
119             lp.rightMargin = (grid.widthPx - lp.width) / 2;
120         }
121         lp.height = grid.dropTargetBarSizePx;
122         // TODO: Add tablet support for DropTargetBar when HOME_GARDENING_WORKSPACE_BUTTONS flag
123         //  is on
124         if (HOME_GARDENING_WORKSPACE_BUTTONS.get()) {
125             lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
126         } else {
127             lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
128         }
129 
130         DeviceProfile dp = mLauncher.getDeviceProfile();
131         int horizontalPadding = dp.dropTargetHorizontalPaddingPx;
132         int verticalPadding = dp.dropTargetVerticalPaddingPx;
133         setLayoutParams(lp);
134         for (ButtonDropTarget button : mDropTargets) {
135             button.setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.dropTargetTextSizePx);
136             button.setToolTipLocation(tooltipLocation);
137             button.setPadding(horizontalPadding, verticalPadding, horizontalPadding,
138                     verticalPadding);
139         }
140     }
141 
setup(DragController dragController)142     public void setup(DragController dragController) {
143         dragController.addDragListener(this);
144         for (int i = 0; i < mDropTargets.length; i++) {
145             dragController.addDragListener(mDropTargets[i]);
146             dragController.addDropTarget(mDropTargets[i]);
147         }
148     }
149 
150     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)151     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
152         int width = MeasureSpec.getSize(widthMeasureSpec);
153         int height = MeasureSpec.getSize(heightMeasureSpec);
154         int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
155 
156         int visibleCount = getVisibleButtons(mTempTargets);
157         if (visibleCount == 1) {
158             int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST);
159 
160             ButtonDropTarget firstButton = mTempTargets[0];
161             firstButton.setTextSize(TypedValue.COMPLEX_UNIT_PX,
162                     mLauncher.getDeviceProfile().dropTargetTextSizePx);
163             firstButton.setTextVisible(true);
164             firstButton.setIconVisible(true);
165             firstButton.measure(widthSpec, heightSpec);
166         } else if (visibleCount == 2) {
167             DeviceProfile dp = mLauncher.getDeviceProfile();
168             int verticalPadding = dp.dropTargetVerticalPaddingPx;
169             int horizontalPadding = dp.dropTargetHorizontalPaddingPx;
170 
171             ButtonDropTarget firstButton = mTempTargets[0];
172             firstButton.setTextSize(TypedValue.COMPLEX_UNIT_PX, dp.dropTargetTextSizePx);
173             firstButton.setTextVisible(true);
174             firstButton.setIconVisible(true);
175             firstButton.setTextMultiLine(false);
176             // Reset first button padding in case it was previously changed to multi-line text.
177             firstButton.setPadding(horizontalPadding, verticalPadding, horizontalPadding,
178                     verticalPadding);
179 
180             ButtonDropTarget secondButton = mTempTargets[1];
181             secondButton.setTextSize(TypedValue.COMPLEX_UNIT_PX, dp.dropTargetTextSizePx);
182             secondButton.setTextVisible(true);
183             secondButton.setIconVisible(true);
184             secondButton.setTextMultiLine(false);
185             // Reset second button padding in case it was previously changed to multi-line text.
186             secondButton.setPadding(horizontalPadding, verticalPadding, horizontalPadding,
187                     verticalPadding);
188 
189             int availableWidth;
190             if (dp.isTwoPanels) {
191                 // Each button for two panel fits to half the width of the screen excluding the
192                 // center gap between the buttons.
193                 availableWidth = (dp.availableWidthPx - dp.dropTargetGapPx) / 2;
194             } else {
195                 // Both buttons plus the button gap do not display past the edge of the screen.
196                 availableWidth = dp.availableWidthPx - dp.dropTargetGapPx;
197             }
198 
199             int widthSpec = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST);
200             firstButton.measure(widthSpec, heightSpec);
201             if (!mIsVertical) {
202                 // Remove both icons and put the button's text on two lines if text is truncated.
203                 if (firstButton.isTextTruncated(availableWidth)) {
204                     firstButton.setIconVisible(false);
205                     secondButton.setIconVisible(false);
206                     firstButton.setTextMultiLine(true);
207                     firstButton.setPadding(horizontalPadding, verticalPadding / 2,
208                             horizontalPadding, verticalPadding / 2);
209                 }
210             }
211 
212             if (!dp.isTwoPanels) {
213                 availableWidth -= firstButton.getMeasuredWidth() + dp.dropTargetGapPx;
214                 widthSpec = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST);
215             }
216             secondButton.measure(widthSpec, heightSpec);
217             if (!mIsVertical) {
218                 // Remove both icons and put the button's text on two lines if text is truncated.
219                 if (secondButton.isTextTruncated(availableWidth)) {
220                     secondButton.setIconVisible(false);
221                     firstButton.setIconVisible(false);
222                     secondButton.setTextMultiLine(true);
223                     secondButton.setPadding(horizontalPadding, verticalPadding / 2,
224                             horizontalPadding, verticalPadding / 2);
225                 }
226             }
227 
228             // If text is still truncated, shrink to fit in measured width and resize both targets.
229             float minTextSize =
230                     Math.min(firstButton.resizeTextToFit(), secondButton.resizeTextToFit());
231             if (firstButton.getTextSize() != minTextSize
232                     || secondButton.getTextSize() != minTextSize) {
233                 firstButton.setTextSize(minTextSize);
234                 secondButton.setTextSize(minTextSize);
235             }
236         }
237         setMeasuredDimension(width, height);
238     }
239 
240     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)241     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
242         int visibleCount = getVisibleButtons(mTempTargets);
243         if (visibleCount == 0) {
244             return;
245         }
246 
247         DeviceProfile dp = mLauncher.getDeviceProfile();
248         // Center vertical bar over scaled workspace, accounting for hotseat offset.
249         float scale = dp.getWorkspaceSpringLoadScale(mLauncher);
250         Workspace<?> ws = mLauncher.getWorkspace();
251         int barCenter;
252         if (dp.isTwoPanels) {
253             barCenter = (right - left) / 2;
254         } else {
255             int workspaceCenter = (ws.getLeft() + ws.getRight()) / 2;
256             int cellLayoutCenter = ((dp.getInsets().left + dp.workspacePadding.left) + (dp.widthPx
257                     - dp.getInsets().right - dp.workspacePadding.right)) / 2;
258             int cellLayoutCenterOffset = (int) ((cellLayoutCenter - workspaceCenter) * scale);
259             barCenter = workspaceCenter + cellLayoutCenterOffset - left;
260         }
261 
262         if (visibleCount == 1) {
263             ButtonDropTarget button = mTempTargets[0];
264             button.layout(barCenter - (button.getMeasuredWidth() / 2), 0,
265                     barCenter + (button.getMeasuredWidth() / 2), button.getMeasuredHeight());
266         } else if (visibleCount == 2) {
267             int buttonGap = dp.dropTargetGapPx;
268 
269             ButtonDropTarget leftButton = mTempTargets[0];
270             ButtonDropTarget rightButton = mTempTargets[1];
271             if (dp.isTwoPanels) {
272                 leftButton.layout(barCenter - leftButton.getMeasuredWidth() - (buttonGap / 2), 0,
273                         barCenter - (buttonGap / 2), leftButton.getMeasuredHeight());
274                 rightButton.layout(barCenter + (buttonGap / 2), 0,
275                         barCenter + (buttonGap / 2) + rightButton.getMeasuredWidth(),
276                         rightButton.getMeasuredHeight());
277             } else {
278                 int scaledPanelWidth = (int) (dp.getCellLayoutWidth() * scale);
279 
280                 int leftButtonWidth = leftButton.getMeasuredWidth();
281                 int rightButtonWidth = rightButton.getMeasuredWidth();
282                 int extraSpace = scaledPanelWidth - leftButtonWidth - rightButtonWidth - buttonGap;
283 
284                 int leftButtonStart = barCenter - (scaledPanelWidth / 2) + extraSpace / 2;
285                 int leftButtonEnd = leftButtonStart + leftButtonWidth;
286                 int rightButtonStart = leftButtonEnd + buttonGap;
287                 int rightButtonEnd = rightButtonStart + rightButtonWidth;
288 
289                 leftButton.layout(leftButtonStart, 0, leftButtonEnd,
290                         leftButton.getMeasuredHeight());
291                 rightButton.layout(rightButtonStart, 0, rightButtonEnd,
292                         rightButton.getMeasuredHeight());
293             }
294         }
295     }
296 
getVisibleButtons(ButtonDropTarget[] outVisibleButtons)297     private int getVisibleButtons(ButtonDropTarget[] outVisibleButtons) {
298         int visibleCount = 0;
299         for (ButtonDropTarget button : mDropTargets) {
300             if (button.getVisibility() != GONE) {
301                 outVisibleButtons[visibleCount] = button;
302                 visibleCount++;
303             }
304         }
305         return visibleCount;
306     }
307 
animateToVisibility(boolean isVisible)308     public void animateToVisibility(boolean isVisible) {
309         if (TestProtocol.sDebugTracing) {
310             Log.d(TestProtocol.NO_DROP_TARGET, "8");
311         }
312         if (mVisible != isVisible) {
313             mVisible = isVisible;
314 
315             // Cancel any existing animation
316             if (mCurrentAnimation != null) {
317                 mCurrentAnimation.cancel();
318                 mCurrentAnimation = null;
319             }
320 
321             float finalAlpha = mVisible ? 1 : 0;
322             if (Float.compare(getAlpha(), finalAlpha) != 0) {
323                 setVisibility(View.VISIBLE);
324                 mCurrentAnimation = animate().alpha(finalAlpha)
325                         .setInterpolator(DEFAULT_INTERPOLATOR)
326                         .setDuration(DEFAULT_DRAG_FADE_DURATION)
327                         .withEndAction(mFadeAnimationEndRunnable);
328             }
329 
330         }
331     }
332 
333     /*
334      * DragController.DragListener implementation
335      */
336     @Override
onDragStart(DropTarget.DragObject dragObject, DragOptions options)337     public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
338         if (TestProtocol.sDebugTracing) {
339             Log.d(TestProtocol.NO_DROP_TARGET, "7");
340         }
341         animateToVisibility(true);
342     }
343 
344     /**
345      * This is called to defer hiding the delete drop target until the drop animation has completed,
346      * instead of hiding immediately when the drag has ended.
347      */
deferOnDragEnd()348     protected void deferOnDragEnd() {
349         mDeferOnDragEnd = true;
350     }
351 
352     @Override
onDragEnd()353     public void onDragEnd() {
354         if (!mDeferOnDragEnd) {
355             animateToVisibility(false);
356         } else {
357             mDeferOnDragEnd = false;
358         }
359     }
360 
getDropTargets()361     public ButtonDropTarget[] getDropTargets() {
362         return getVisibility() == View.VISIBLE ? mDropTargets : new ButtonDropTarget[0];
363     }
364 
365     @Override
onVisibilityChanged(@onNull View changedView, int visibility)366     protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
367         super.onVisibilityChanged(changedView, visibility);
368         if (TestProtocol.sDebugTracing) {
369             if (visibility == VISIBLE) {
370                 Log.d(TestProtocol.NO_DROP_TARGET, "9");
371             } else {
372                 Log.d(TestProtocol.NO_DROP_TARGET, "Hiding drop target", new Exception());
373             }
374         }
375     }
376 }
377