1 /*
2  * Copyright (C) 2021 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 package com.android.launcher3.taskbar;
17 
18 import static android.view.KeyEvent.ACTION_UP;
19 import static android.view.KeyEvent.KEYCODE_BACK;
20 
21 import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
22 import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
23 
24 import android.content.Context;
25 import android.graphics.Canvas;
26 import android.graphics.RectF;
27 import android.media.permission.SafeCloseable;
28 import android.util.AttributeSet;
29 import android.util.FloatProperty;
30 import android.view.KeyEvent;
31 import android.view.MotionEvent;
32 import android.view.View;
33 import android.view.ViewTreeObserver;
34 import android.view.WindowInsets;
35 
36 import androidx.annotation.NonNull;
37 import androidx.annotation.Nullable;
38 import androidx.core.graphics.Insets;
39 import androidx.core.view.WindowInsetsCompat;
40 
41 import com.android.app.viewcapture.ViewCaptureFactory;
42 import com.android.launcher3.AbstractFloatingView;
43 import com.android.launcher3.testing.TestLogging;
44 import com.android.launcher3.testing.shared.TestProtocol;
45 import com.android.launcher3.util.DisplayController;
46 import com.android.launcher3.util.MultiPropertyFactory;
47 import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
48 import com.android.launcher3.views.BaseDragLayer;
49 
50 /**
51  * Top-level ViewGroup that hosts the TaskbarView as well as Views created by it such as Folder.
52  */
53 public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
54 
55     private static final int INDEX_ALL_OTHER_STATES = 0;
56     private static final int INDEX_STASH_ANIM = 1;
57     private static final int INDEX_COUNT = 2;
58 
59     private static final FloatProperty<TaskbarDragLayer> BG_ALPHA =
60             new FloatProperty<>("taskbarBgAlpha") {
61                 @Override
62                 public void setValue(TaskbarDragLayer dragLayer, float alpha) {
63                     dragLayer.mBackgroundRenderer.getPaint().setAlpha((int) (alpha * 255));
64                     dragLayer.invalidate();
65                 }
66 
67                 @Override
68                 public Float get(TaskbarDragLayer dragLayer) {
69                     return dragLayer.mBackgroundRenderer.getPaint().getAlpha() / 255f;
70                 }
71             };
72 
73 
74     private final TaskbarBackgroundRenderer mBackgroundRenderer;
75     private final ViewTreeObserver.OnComputeInternalInsetsListener mTaskbarInsetsComputer =
76             this::onComputeTaskbarInsets;
77 
78     // Initialized in init.
79     private TaskbarDragLayerController.TaskbarDragLayerCallbacks mControllerCallbacks;
80     private SafeCloseable mViewCaptureCloseable;
81 
82     private float mTaskbarBackgroundOffset;
83     private float mTaskbarBackgroundProgress;
84     private boolean mIsAnimatingTaskbarPinning = false;
85 
86     private final MultiPropertyFactory<TaskbarDragLayer> mTaskbarBackgroundAlpha;
87 
TaskbarDragLayer(@onNull Context context)88     public TaskbarDragLayer(@NonNull Context context) {
89         this(context, null);
90     }
91 
TaskbarDragLayer(@onNull Context context, @Nullable AttributeSet attrs)92     public TaskbarDragLayer(@NonNull Context context, @Nullable AttributeSet attrs) {
93         this(context, attrs, 0);
94     }
95 
TaskbarDragLayer(@onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr)96     public TaskbarDragLayer(@NonNull Context context, @Nullable AttributeSet attrs,
97             int defStyleAttr) {
98         this(context, attrs, defStyleAttr, 0);
99     }
100 
TaskbarDragLayer(@onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)101     public TaskbarDragLayer(@NonNull Context context, @Nullable AttributeSet attrs,
102             int defStyleAttr, int defStyleRes) {
103         super(context, attrs, 1 /* alphaChannelCount */);
104         mBackgroundRenderer = new TaskbarBackgroundRenderer(mActivity);
105 
106         mTaskbarBackgroundAlpha = new MultiPropertyFactory<>(this, BG_ALPHA, INDEX_COUNT,
107                 (a, b) -> a * b, 1f);
108         mTaskbarBackgroundAlpha.get(INDEX_ALL_OTHER_STATES).setValue(0);
109         mTaskbarBackgroundAlpha.get(INDEX_STASH_ANIM).setValue(
110                 enableScalingRevealHomeAnimation() && DisplayController.isTransientTaskbar(context)
111                         ? 0
112                         : 1);
113     }
114 
init(TaskbarDragLayerController.TaskbarDragLayerCallbacks callbacks)115     public void init(TaskbarDragLayerController.TaskbarDragLayerCallbacks callbacks) {
116         mControllerCallbacks = callbacks;
117         mBackgroundRenderer.updateStashedHandleWidth(mActivity, getResources());
118         recreateControllers();
119     }
120 
121     @Override
onApplyWindowInsets(WindowInsets insets)122     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
123         if (insets != null) {
124             WindowInsetsCompat insetsCompat = WindowInsetsCompat.toWindowInsetsCompat(insets, this);
125             Insets imeInsets = insetsCompat.getInsets(WindowInsetsCompat.Type.ime());
126             if (imeInsets != null) {
127                 mControllerCallbacks.onImeInsetChanged();
128             }
129         }
130         return insets;
131     }
132 
133     @Override
recreateControllers()134     public void recreateControllers() {
135         mControllers = mControllerCallbacks.getTouchControllers();
136     }
137 
onComputeTaskbarInsets(ViewTreeObserver.InternalInsetsInfo insetsInfo)138     private void onComputeTaskbarInsets(ViewTreeObserver.InternalInsetsInfo insetsInfo) {
139         if (mControllerCallbacks != null) {
140             mControllerCallbacks.updateInsetsTouchability(insetsInfo);
141         }
142     }
143 
onDestroy(boolean forceDestroy)144     protected void onDestroy(boolean forceDestroy) {
145         if (forceDestroy) {
146             getViewTreeObserver().removeOnComputeInternalInsetsListener(mTaskbarInsetsComputer);
147         }
148     }
149 
onDestroy()150     protected void onDestroy() {
151         onDestroy(!ENABLE_TASKBAR_NAVBAR_UNIFICATION);
152     }
153 
154     @Override
onAttachedToWindow()155     protected void onAttachedToWindow() {
156         super.onAttachedToWindow();
157         getViewTreeObserver().addOnComputeInternalInsetsListener(mTaskbarInsetsComputer);
158         mViewCaptureCloseable = ViewCaptureFactory.getInstance(getContext())
159                 .startCapture(getRootView(), ".Taskbar");
160     }
161 
162     @Override
onDetachedFromWindow()163     protected void onDetachedFromWindow() {
164         super.onDetachedFromWindow();
165         mViewCaptureCloseable.close();
166         onDestroy(true);
167     }
168 
169     @Override
isEventWithinSystemGestureRegion(MotionEvent ev)170     protected boolean isEventWithinSystemGestureRegion(MotionEvent ev) {
171         final float x = ev.getX();
172         final float y = ev.getY();
173 
174         return x >= mSystemGestureRegion.left && x < getWidth() - mSystemGestureRegion.right
175                 && y >= mSystemGestureRegion.top;
176     }
177 
178     @Override
canFindActiveController()179     protected boolean canFindActiveController() {
180         // Unlike super class, we want to be able to find controllers when touches occur in the
181         // gesture area. For example, this allows Folder to close itself when touching the Taskbar.
182         return true;
183     }
184 
185     @Override
onViewRemoved(View child)186     public void onViewRemoved(View child) {
187         super.onViewRemoved(child);
188         if (mControllerCallbacks != null) {
189             mControllerCallbacks.onDragLayerViewRemoved();
190         }
191     }
192 
193     @Override
dispatchDraw(Canvas canvas)194     protected void dispatchDraw(Canvas canvas) {
195         float backgroundHeight = mControllerCallbacks.getTaskbarBackgroundHeight()
196                 * (1f - mTaskbarBackgroundOffset);
197         mBackgroundRenderer.setBackgroundHeight(backgroundHeight);
198         mBackgroundRenderer.setBackgroundProgress(mTaskbarBackgroundProgress);
199         mBackgroundRenderer.draw(canvas);
200         super.dispatchDraw(canvas);
201         mControllerCallbacks.drawDebugUi(canvas);
202     }
203 
204     /**
205      * Sets animation boolean when taskbar pinning animation starts or stops.
206      */
setAnimatingTaskbarPinning(boolean animatingTaskbarPinning)207     public void setAnimatingTaskbarPinning(boolean animatingTaskbarPinning) {
208         mIsAnimatingTaskbarPinning = animatingTaskbarPinning;
209         mBackgroundRenderer.setAnimatingPinning(mIsAnimatingTaskbarPinning);
210     }
211 
getBackgroundRendererAlpha()212     protected MultiProperty getBackgroundRendererAlpha() {
213         return mTaskbarBackgroundAlpha.get(INDEX_ALL_OTHER_STATES);
214     }
215 
getBackgroundRendererAlphaForStash()216     protected MultiProperty getBackgroundRendererAlphaForStash() {
217         return mTaskbarBackgroundAlpha.get(INDEX_STASH_ANIM);
218     }
219 
220     /**
221      * Sets the value for taskbar background switching between persistent and transient backgrounds.
222      * @param progress 0 is transient background, 1 is persistent background.
223      */
setTaskbarBackgroundProgress(float progress)224     protected void setTaskbarBackgroundProgress(float progress) {
225         mTaskbarBackgroundProgress = progress;
226         invalidate();
227     }
228 
229     /**
230      * Sets the translation of the background color behind all the Taskbar contents.
231      * @param offset 0 is fully onscreen, 1 is fully offscreen.
232      */
setTaskbarBackgroundOffset(float offset)233     protected void setTaskbarBackgroundOffset(float offset) {
234         mTaskbarBackgroundOffset = offset;
235         invalidate();
236     }
237 
238     /**
239      * Sets the roundness of the round corner above Taskbar.
240      * @param cornerRoundness 0 has no round corner, 1 has complete round corner.
241      */
setCornerRoundness(float cornerRoundness)242     protected void setCornerRoundness(float cornerRoundness) {
243         mBackgroundRenderer.setCornerRoundness(cornerRoundness);
244         invalidate();
245     }
246 
247     /*
248      * Sets the translation of the background during the swipe up gesture.
249      */
setBackgroundTranslationYForSwipe(float translationY)250     protected void setBackgroundTranslationYForSwipe(float translationY) {
251         mBackgroundRenderer.setTranslationYForSwipe(translationY);
252         invalidate();
253     }
254 
255     /*
256      * Sets the translation of the background during the spring on stash animation.
257      */
setBackgroundTranslationYForStash(float translationY)258     protected void setBackgroundTranslationYForStash(float translationY) {
259         mBackgroundRenderer.setTranslationYForStash(translationY);
260         invalidate();
261     }
262 
263     /** Returns the bounds in DragLayer coordinates of where the transient background was drawn. */
getLastDrawnTransientRect()264     protected RectF getLastDrawnTransientRect() {
265         return mBackgroundRenderer.getLastDrawnTransientRect();
266     }
267 
268     @Override
dispatchTouchEvent(MotionEvent ev)269     public boolean dispatchTouchEvent(MotionEvent ev) {
270         TestLogging.recordMotionEvent(TestProtocol.SEQUENCE_MAIN, "Touch event", ev);
271         return super.dispatchTouchEvent(ev);
272     }
273 
274     /** Called while Taskbar window is focusable, e.g. when pressing back while a folder is open */
275     @Override
dispatchKeyEvent(KeyEvent event)276     public boolean dispatchKeyEvent(KeyEvent event) {
277         if (event.getAction() == ACTION_UP && event.getKeyCode() == KEYCODE_BACK) {
278             AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
279             if (topView != null && topView.canHandleBack()) {
280                 topView.onBackInvoked();
281                 // Handled by the floating view.
282                 return true;
283             }
284         }
285         return super.dispatchKeyEvent(event);
286     }
287 
288     /**
289      * Sets the width percentage to inset the transient taskbar's background from the left and from
290      * the right.
291      */
setBackgroundHorizontalInsets(float insetPercentage)292     public void setBackgroundHorizontalInsets(float insetPercentage) {
293         mBackgroundRenderer.setBackgroundHorizontalInsets(insetPercentage);
294         invalidate();
295     }
296 }
297