1 /*
2  * Copyright (C) 2018 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.quickstep.views;
18 
19 import static com.android.launcher3.Flags.enableGridOnlyOverview;
20 import static com.android.quickstep.util.BorderAnimator.DEFAULT_BORDER_COLOR;
21 
22 import android.content.Context;
23 import android.content.res.Resources;
24 import android.content.res.TypedArray;
25 import android.graphics.Canvas;
26 import android.graphics.Rect;
27 import android.util.AttributeSet;
28 import android.util.FloatProperty;
29 import android.widget.Button;
30 
31 import androidx.annotation.NonNull;
32 import androidx.annotation.Nullable;
33 
34 import com.android.launcher3.DeviceProfile;
35 import com.android.launcher3.Flags;
36 import com.android.launcher3.R;
37 import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
38 import com.android.quickstep.util.BorderAnimator;
39 
40 import kotlin.Unit;
41 
42 public class ClearAllButton extends Button {
43 
44     public static final FloatProperty<ClearAllButton> VISIBILITY_ALPHA =
45             new FloatProperty<ClearAllButton>("visibilityAlpha") {
46                 @Override
47                 public Float get(ClearAllButton view) {
48                     return view.mVisibilityAlpha;
49                 }
50 
51                 @Override
52                 public void setValue(ClearAllButton view, float v) {
53                     view.setVisibilityAlpha(v);
54                 }
55             };
56 
57     public static final FloatProperty<ClearAllButton> DISMISS_ALPHA =
58             new FloatProperty<ClearAllButton>("dismissAlpha") {
59                 @Override
60                 public Float get(ClearAllButton view) {
61                     return view.mDismissAlpha;
62                 }
63 
64                 @Override
65                 public void setValue(ClearAllButton view, float v) {
66                     view.setDismissAlpha(v);
67                 }
68             };
69 
70     private final RecentsViewContainer mContainer;
71     private float mScrollAlpha = 1;
72     private float mContentAlpha = 1;
73     private float mVisibilityAlpha = 1;
74     private float mDismissAlpha = 1;
75     private float mFullscreenProgress = 1;
76     private float mGridProgress = 1;
77 
78     private boolean mIsRtl;
79     private float mNormalTranslationPrimary;
80     private float mFullscreenTranslationPrimary;
81     private float mGridTranslationPrimary;
82     private float mGridScrollOffset;
83     private float mScrollOffsetPrimary;
84 
85     private int mSidePadding;
86     private int mOutlinePadding;
87     private boolean mBorderEnabled;
88     @Nullable
89     private final BorderAnimator mFocusBorderAnimator;
90 
ClearAllButton(Context context, AttributeSet attrs)91     public ClearAllButton(Context context, AttributeSet attrs) {
92         super(context, attrs);
93         mIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
94         mContainer = RecentsViewContainer.containerFromContext(context);
95 
96         if (Flags.enableFocusOutline()) {
97             TypedArray styledAttrs = context.obtainStyledAttributes(attrs,
98                     R.styleable.ClearAllButton);
99             Resources resources = getResources();
100             mOutlinePadding = resources.getDimensionPixelSize(
101                     R.dimen.recents_clear_all_outline_padding);
102             mFocusBorderAnimator =
103                     BorderAnimator.createSimpleBorderAnimator(
104                             /* borderRadiusPx= */ resources.getDimensionPixelSize(
105                                     R.dimen.recents_clear_all_outline_radius),
106                             /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
107                                     R.dimen.keyboard_quick_switch_border_width),
108                             /* boundsBuilder= */ this::updateBorderBounds,
109                             /* targetView= */ this,
110                             /* borderColor= */ styledAttrs.getColor(
111                                     R.styleable.ClearAllButton_focusBorderColor,
112                                     DEFAULT_BORDER_COLOR));
113             styledAttrs.recycle();
114         } else {
115             mFocusBorderAnimator = null;
116         }
117     }
118 
updateBorderBounds(@onNull Rect bounds)119     private Unit updateBorderBounds(@NonNull Rect bounds) {
120         bounds.set(0, 0, getWidth(), getHeight());
121         // Make the value negative to form a padding between button and outline
122         bounds.inset(-mOutlinePadding, -mOutlinePadding);
123         return Unit.INSTANCE;
124     }
125 
126     @Override
onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect)127     public void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
128         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
129         if (mFocusBorderAnimator != null && mBorderEnabled) {
130             mFocusBorderAnimator.setBorderVisibility(gainFocus, /* animated= */ true);
131         }
132     }
133 
134     /**
135      * Enable or disable showing border on focus change
136      */
setBorderEnabled(boolean enabled)137     public void setBorderEnabled(boolean enabled) {
138         if (mBorderEnabled == enabled) {
139             return;
140         }
141 
142         mBorderEnabled = enabled;
143         if (mFocusBorderAnimator != null) {
144             mFocusBorderAnimator.setBorderVisibility(/* visible= */
145                     enabled && isFocused(), /* animated= */true);
146         }
147     }
148 
149     @Override
draw(Canvas canvas)150     public void draw(Canvas canvas) {
151         if (mFocusBorderAnimator != null) {
152             mFocusBorderAnimator.drawBorder(canvas);
153         }
154         super.draw(canvas);
155     }
156 
157     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)158     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
159         super.onLayout(changed, left, top, right, bottom);
160         RecentsPagedOrientationHandler orientationHandler =
161                 getRecentsView().getPagedOrientationHandler();
162         mSidePadding = orientationHandler.getClearAllSidePadding(getRecentsView(), mIsRtl);
163     }
164 
getRecentsView()165     private RecentsView getRecentsView() {
166         return (RecentsView) getParent();
167     }
168 
169     @Override
onRtlPropertiesChanged(int layoutDirection)170     public void onRtlPropertiesChanged(int layoutDirection) {
171         super.onRtlPropertiesChanged(layoutDirection);
172         mIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
173     }
174 
175     @Override
hasOverlappingRendering()176     public boolean hasOverlappingRendering() {
177         return false;
178     }
179 
getScrollAlpha()180     public float getScrollAlpha() {
181         return mScrollAlpha;
182     }
183 
setContentAlpha(float alpha)184     public void setContentAlpha(float alpha) {
185         if (mContentAlpha != alpha) {
186             mContentAlpha = alpha;
187             updateAlpha();
188         }
189     }
190 
setVisibilityAlpha(float alpha)191     public void setVisibilityAlpha(float alpha) {
192         if (mVisibilityAlpha != alpha) {
193             mVisibilityAlpha = alpha;
194             updateAlpha();
195         }
196     }
197 
setDismissAlpha(float alpha)198     public void setDismissAlpha(float alpha) {
199         if (mDismissAlpha != alpha) {
200             mDismissAlpha = alpha;
201             updateAlpha();
202         }
203     }
204 
onRecentsViewScroll(int scroll, boolean gridEnabled)205     public void onRecentsViewScroll(int scroll, boolean gridEnabled) {
206         RecentsView recentsView = getRecentsView();
207         if (recentsView == null) {
208             return;
209         }
210 
211         RecentsPagedOrientationHandler orientationHandler =
212                 recentsView.getPagedOrientationHandler();
213         float orientationSize = orientationHandler.getPrimaryValue(getWidth(), getHeight());
214         if (orientationSize == 0) {
215             return;
216         }
217 
218         int clearAllScroll = recentsView.getClearAllScroll();
219         int adjustedScrollFromEdge = Math.abs(scroll - clearAllScroll);
220         float shift = Math.min(adjustedScrollFromEdge, orientationSize);
221         mNormalTranslationPrimary = mIsRtl ? -shift : shift;
222         if (!gridEnabled) {
223             mNormalTranslationPrimary += mSidePadding;
224         }
225         applyPrimaryTranslation();
226         applySecondaryTranslation();
227         float clearAllSpacing =
228                 recentsView.getPageSpacing() + recentsView.getClearAllExtraPageSpacing();
229         clearAllSpacing = mIsRtl ? -clearAllSpacing : clearAllSpacing;
230         mScrollAlpha = Math.max((clearAllScroll + clearAllSpacing - scroll) / clearAllSpacing, 0);
231         updateAlpha();
232     }
233 
updateAlpha()234     private void updateAlpha() {
235         final float alpha = mScrollAlpha * mContentAlpha * mVisibilityAlpha * mDismissAlpha;
236         setAlpha(alpha);
237         setClickable(Math.min(alpha, 1) == 1);
238     }
239 
setFullscreenTranslationPrimary(float fullscreenTranslationPrimary)240     public void setFullscreenTranslationPrimary(float fullscreenTranslationPrimary) {
241         mFullscreenTranslationPrimary = fullscreenTranslationPrimary;
242         applyPrimaryTranslation();
243     }
244 
setGridTranslationPrimary(float gridTranslationPrimary)245     public void setGridTranslationPrimary(float gridTranslationPrimary) {
246         mGridTranslationPrimary = gridTranslationPrimary;
247         applyPrimaryTranslation();
248     }
249 
setGridScrollOffset(float gridScrollOffset)250     public void setGridScrollOffset(float gridScrollOffset) {
251         mGridScrollOffset = gridScrollOffset;
252     }
253 
setScrollOffsetPrimary(float scrollOffsetPrimary)254     public void setScrollOffsetPrimary(float scrollOffsetPrimary) {
255         mScrollOffsetPrimary = scrollOffsetPrimary;
256     }
257 
getScrollAdjustment(boolean fullscreenEnabled, boolean gridEnabled)258     public float getScrollAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
259         float scrollAdjustment = 0;
260         if (fullscreenEnabled) {
261             scrollAdjustment += mFullscreenTranslationPrimary;
262         }
263         if (gridEnabled) {
264             scrollAdjustment += mGridTranslationPrimary + mGridScrollOffset;
265         }
266         scrollAdjustment += mScrollOffsetPrimary;
267         return scrollAdjustment;
268     }
269 
getOffsetAdjustment(boolean fullscreenEnabled, boolean gridEnabled)270     public float getOffsetAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
271         return getScrollAdjustment(fullscreenEnabled, gridEnabled);
272     }
273 
274     /**
275      * Adjust translation when this TaskView is about to be shown fullscreen.
276      *
277      * @param progress: 0 = no translation; 1 = translate according to TaskVIew translations.
278      */
setFullscreenProgress(float progress)279     public void setFullscreenProgress(float progress) {
280         mFullscreenProgress = progress;
281         applyPrimaryTranslation();
282     }
283 
284     /**
285      * Moves ClearAllButton between carousel and 2 row grid.
286      *
287      * @param gridProgress 0 = carousel; 1 = 2 row grid.
288      */
setGridProgress(float gridProgress)289     public void setGridProgress(float gridProgress) {
290         mGridProgress = gridProgress;
291         applyPrimaryTranslation();
292     }
293 
applyPrimaryTranslation()294     private void applyPrimaryTranslation() {
295         RecentsView recentsView = getRecentsView();
296         if (recentsView == null) {
297             return;
298         }
299 
300         RecentsPagedOrientationHandler orientationHandler =
301                 recentsView.getPagedOrientationHandler();
302         orientationHandler.getPrimaryViewTranslate().set(this,
303                 orientationHandler.getPrimaryValue(0f, getOriginalTranslationY())
304                         + mNormalTranslationPrimary + getFullscreenTrans(
305                         mFullscreenTranslationPrimary) + getGridTrans(mGridTranslationPrimary));
306     }
307 
applySecondaryTranslation()308     private void applySecondaryTranslation() {
309         RecentsView recentsView = getRecentsView();
310         if (recentsView == null) {
311             return;
312         }
313 
314         RecentsPagedOrientationHandler orientationHandler =
315                 recentsView.getPagedOrientationHandler();
316         orientationHandler.getSecondaryViewTranslate().set(this,
317                 orientationHandler.getSecondaryValue(0f, getOriginalTranslationY()));
318     }
319 
getFullscreenTrans(float endTranslation)320     private float getFullscreenTrans(float endTranslation) {
321         return mFullscreenProgress > 0 ? endTranslation : 0;
322     }
323 
getGridTrans(float endTranslation)324     private float getGridTrans(float endTranslation) {
325         return mGridProgress > 0 ? endTranslation : 0;
326     }
327 
328     /**
329      * Get the Y translation that is set in the original layout position, before scrolling.
330      */
getOriginalTranslationY()331     private float getOriginalTranslationY() {
332         DeviceProfile deviceProfile = mContainer.getDeviceProfile();
333         if (deviceProfile.isTablet) {
334             if (enableGridOnlyOverview()) {
335                 return (getRecentsView().getLastComputedTaskSize().height()
336                         + deviceProfile.overviewTaskThumbnailTopMarginPx) / 2.0f
337                         + deviceProfile.overviewRowSpacing;
338             } else {
339                 return deviceProfile.overviewRowSpacing;
340             }
341         }
342         return deviceProfile.overviewTaskThumbnailTopMarginPx / 2.0f;
343     }
344 }
345