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 android.content.Context;
19 import android.graphics.Canvas;
20 import android.graphics.Paint;
21 import android.graphics.Path;
22 import android.util.AttributeSet;
23 import android.view.MotionEvent;
24 import android.view.View;
25 
26 import androidx.annotation.NonNull;
27 import androidx.annotation.Nullable;
28 
29 import com.android.launcher3.R;
30 import com.android.launcher3.testing.TestLogging;
31 import com.android.launcher3.testing.TestProtocol;
32 import com.android.launcher3.util.TouchController;
33 import com.android.launcher3.views.BaseDragLayer;
34 import com.android.systemui.shared.system.ViewTreeObserverWrapper;
35 import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo;
36 import com.android.systemui.shared.system.ViewTreeObserverWrapper.OnComputeInsetsListener;
37 
38 /**
39  * Top-level ViewGroup that hosts the TaskbarView as well as Views created by it such as Folder.
40  */
41 public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
42 
43     private final Paint mTaskbarBackgroundPaint;
44     private final Path mInvertedLeftCornerPath, mInvertedRightCornerPath;
45     private final OnComputeInsetsListener mTaskbarInsetsComputer = this::onComputeTaskbarInsets;
46 
47     // Initialized in init.
48     private TaskbarDragLayerController.TaskbarDragLayerCallbacks mControllerCallbacks;
49     private float mLeftCornerRadius, mRightCornerRadius;
50 
51     private float mTaskbarBackgroundOffset;
52 
TaskbarDragLayer(@onNull Context context)53     public TaskbarDragLayer(@NonNull Context context) {
54         this(context, null);
55     }
56 
TaskbarDragLayer(@onNull Context context, @Nullable AttributeSet attrs)57     public TaskbarDragLayer(@NonNull Context context, @Nullable AttributeSet attrs) {
58         this(context, attrs, 0);
59     }
60 
TaskbarDragLayer(@onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr)61     public TaskbarDragLayer(@NonNull Context context, @Nullable AttributeSet attrs,
62             int defStyleAttr) {
63         this(context, attrs, defStyleAttr, 0);
64     }
65 
TaskbarDragLayer(@onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)66     public TaskbarDragLayer(@NonNull Context context, @Nullable AttributeSet attrs,
67             int defStyleAttr, int defStyleRes) {
68         super(context, attrs, 1 /* alphaChannelCount */);
69         mTaskbarBackgroundPaint = new Paint();
70         mTaskbarBackgroundPaint.setColor(getResources().getColor(R.color.taskbar_background));
71         mTaskbarBackgroundPaint.setAlpha(0);
72         mTaskbarBackgroundPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
73         mTaskbarBackgroundPaint.setStyle(Paint.Style.FILL);
74 
75         // Will be set in init(), but this ensures they are always non-null.
76         mInvertedLeftCornerPath = new Path();
77         mInvertedRightCornerPath = new Path();
78     }
79 
init(TaskbarDragLayerController.TaskbarDragLayerCallbacks callbacks)80     public void init(TaskbarDragLayerController.TaskbarDragLayerCallbacks callbacks) {
81         mControllerCallbacks = callbacks;
82 
83         // Create the paths for the inverted rounded corners above the taskbar. Start with a filled
84         // square, and then subtracting out a circle from the appropriate corner.
85         mLeftCornerRadius = mActivity.getLeftCornerRadius();
86         mRightCornerRadius = mActivity.getRightCornerRadius();
87         Path square = new Path();
88         square.addRect(0, 0, mLeftCornerRadius, mLeftCornerRadius, Path.Direction.CW);
89         Path circle = new Path();
90         circle.addCircle(mLeftCornerRadius, 0, mLeftCornerRadius, Path.Direction.CW);
91         mInvertedLeftCornerPath.op(square, circle, Path.Op.DIFFERENCE);
92         square.reset();
93         square.addRect(0, 0, mRightCornerRadius, mRightCornerRadius, Path.Direction.CW);
94         circle.reset();
95         circle.addCircle(0, 0, mRightCornerRadius, Path.Direction.CW);
96         mInvertedRightCornerPath.op(square, circle, Path.Op.DIFFERENCE);
97 
98         recreateControllers();
99     }
100 
101     @Override
recreateControllers()102     public void recreateControllers() {
103         mControllers = new TouchController[] {mActivity.getDragController()};
104     }
105 
onComputeTaskbarInsets(InsetsInfo insetsInfo)106     private void onComputeTaskbarInsets(InsetsInfo insetsInfo) {
107         if (mControllerCallbacks != null) {
108             mControllerCallbacks.updateInsetsTouchability(insetsInfo);
109             mControllerCallbacks.updateContentInsets(insetsInfo.contentInsets);
110         }
111     }
112 
onDestroy()113     protected void onDestroy() {
114         ViewTreeObserverWrapper.removeOnComputeInsetsListener(mTaskbarInsetsComputer);
115     }
116 
117     @Override
onAttachedToWindow()118     protected void onAttachedToWindow() {
119         super.onAttachedToWindow();
120         ViewTreeObserverWrapper.addOnComputeInsetsListener(getViewTreeObserver(),
121                 mTaskbarInsetsComputer);
122     }
123 
124     @Override
onDetachedFromWindow()125     protected void onDetachedFromWindow() {
126         super.onDetachedFromWindow();
127 
128         onDestroy();
129     }
130 
131     @Override
canFindActiveController()132     protected boolean canFindActiveController() {
133         // Unlike super class, we want to be able to find controllers when touches occur in the
134         // gesture area. For example, this allows Folder to close itself when touching the Taskbar.
135         return true;
136     }
137 
138     @Override
onViewRemoved(View child)139     public void onViewRemoved(View child) {
140         super.onViewRemoved(child);
141         if (mControllerCallbacks != null) {
142             mControllerCallbacks.onDragLayerViewRemoved();
143         }
144     }
145 
146     @Override
dispatchDraw(Canvas canvas)147     protected void dispatchDraw(Canvas canvas) {
148         float backgroundHeight = mControllerCallbacks.getTaskbarBackgroundHeight()
149                 * (1f - mTaskbarBackgroundOffset);
150         canvas.save();
151         canvas.translate(0, canvas.getHeight() - backgroundHeight);
152 
153         // Draw the background behind taskbar content.
154         canvas.drawRect(0, 0, canvas.getWidth(), backgroundHeight, mTaskbarBackgroundPaint);
155 
156         // Draw the inverted rounded corners above the taskbar.
157         canvas.translate(0, -mLeftCornerRadius);
158         canvas.drawPath(mInvertedLeftCornerPath, mTaskbarBackgroundPaint);
159         canvas.translate(0, mLeftCornerRadius);
160         canvas.translate(canvas.getWidth() - mRightCornerRadius, -mRightCornerRadius);
161         canvas.drawPath(mInvertedRightCornerPath, mTaskbarBackgroundPaint);
162 
163         canvas.restore();
164         super.dispatchDraw(canvas);
165     }
166 
167     /**
168      * Sets the alpha of the background color behind all the Taskbar contents.
169      * @param alpha 0 is fully transparent, 1 is fully opaque.
170      */
setTaskbarBackgroundAlpha(float alpha)171     protected void setTaskbarBackgroundAlpha(float alpha) {
172         mTaskbarBackgroundPaint.setAlpha((int) (alpha * 255));
173         invalidate();
174     }
175 
176     /**
177      * Sets the translation of the background color behind all the Taskbar contents.
178      * @param offset 0 is fully onscreen, 1 is fully offscreen.
179      */
setTaskbarBackgroundOffset(float offset)180     protected void setTaskbarBackgroundOffset(float offset) {
181         mTaskbarBackgroundOffset = offset;
182         invalidate();
183     }
184 
185     @Override
dispatchTouchEvent(MotionEvent ev)186     public boolean dispatchTouchEvent(MotionEvent ev) {
187         TestLogging.recordMotionEvent(TestProtocol.SEQUENCE_MAIN, "Touch event", ev);
188         return super.dispatchTouchEvent(ev);
189     }
190 }
191