• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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.systemui.qs;
18 
19 import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
20 
21 import android.content.Context;
22 import android.graphics.Canvas;
23 import android.graphics.Path;
24 import android.graphics.PointF;
25 import android.util.AttributeSet;
26 import android.view.MotionEvent;
27 import android.view.View;
28 import android.view.ViewGroup;
29 import android.widget.FrameLayout;
30 
31 import androidx.annotation.Nullable;
32 
33 import com.android.systemui.Dumpable;
34 import com.android.systemui.qs.customize.QSCustomizer;
35 import com.android.systemui.res.R;
36 import com.android.systemui.scene.shared.flag.SceneContainerFlag;
37 import com.android.systemui.shade.LargeScreenHeaderHelper;
38 import com.android.systemui.shade.TouchLogger;
39 import com.android.systemui.util.LargeScreenUtils;
40 
41 import java.io.PrintWriter;
42 
43 /**
44  * Wrapper view with background which contains {@link QSPanel} and {@link QuickStatusBarHeader}
45  */
46 public class QSContainerImpl extends FrameLayout implements Dumpable {
47 
48     private int mFancyClippingLeftInset;
49     private int mFancyClippingTop;
50     private int mFancyClippingRightInset;
51     private int mFancyClippingBottom;
52     private final float[] mFancyClippingRadii = new float[] {0, 0, 0, 0, 0, 0, 0, 0};
53     private  final Path mFancyClippingPath = new Path();
54     private int mHeightOverride = -1;
55     private QuickStatusBarHeader mHeader;
56     private float mQsExpansion;
57     private QSCustomizer mQSCustomizer;
58     private QSPanel mQSPanel;
59     private NonInterceptingScrollView mQSPanelContainer;
60 
61     private int mHorizontalMargins;
62     private int mTilesPageMargin;
63     private boolean mQsDisabled;
64     private int mContentHorizontalPadding = -1;
65     private boolean mClippingEnabled;
66     private boolean mIsFullWidth;
67 
68     private boolean mSceneContainerEnabled;
69 
QSContainerImpl(Context context, AttributeSet attrs)70     public QSContainerImpl(Context context, AttributeSet attrs) {
71         super(context, attrs);
72     }
73 
74     @Override
onFinishInflate()75     protected void onFinishInflate() {
76         super.onFinishInflate();
77         mQSPanelContainer = findViewById(R.id.expanded_qs_scroll_view);
78         mQSPanel = findViewById(R.id.quick_settings_panel);
79         mHeader = findViewById(R.id.header);
80         mQSCustomizer = findViewById(R.id.qs_customize);
81         setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
82     }
83 
setSceneContainerEnabled(boolean enabled)84     void setSceneContainerEnabled(boolean enabled) {
85         mSceneContainerEnabled = enabled;
86         if (enabled) {
87             mQSPanelContainer.removeAllViews();
88             removeView(mQSPanelContainer);
89             LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT,
90                     ViewGroup.LayoutParams.WRAP_CONTENT);
91             addView(mQSPanel, 0, lp);
92         }
93     }
94 
95     @Override
hasOverlappingRendering()96     public boolean hasOverlappingRendering() {
97         return false;
98     }
99 
100     @Override
performClick()101     public boolean performClick() {
102         // Want to receive clicks so missing QQS tiles doesn't cause collapse, but
103         // don't want to do anything with them.
104         return true;
105     }
106 
107     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)108     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
109         // QSPanel will show as many rows as it can (up to TileLayout.MAX_ROWS) such that the
110         // bottom and footer are inside the screen.
111         int availableHeight = View.MeasureSpec.getSize(heightMeasureSpec);
112 
113         if (!mSceneContainerEnabled) {
114             MarginLayoutParams layoutParams =
115                     (MarginLayoutParams) mQSPanelContainer.getLayoutParams();
116             int maxQs = availableHeight - layoutParams.topMargin - layoutParams.bottomMargin
117                     - getPaddingBottom();
118             int padding = mPaddingLeft + mPaddingRight + layoutParams.leftMargin
119                     + layoutParams.rightMargin;
120             final int qsPanelWidthSpec = getChildMeasureSpec(widthMeasureSpec, padding,
121                     layoutParams.width);
122             mQSPanelContainer.measure(qsPanelWidthSpec,
123                     MeasureSpec.makeMeasureSpec(maxQs, MeasureSpec.AT_MOST));
124             int width = mQSPanelContainer.getMeasuredWidth() + padding;
125             super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
126                     MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.EXACTLY));
127         } else {
128             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
129         }
130 
131         // QSCustomizer will always be the height of the screen, but do this after
132         // other measuring to avoid changing the height of the QS.
133         mQSCustomizer.measure(widthMeasureSpec,
134                 MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.EXACTLY));
135     }
136 
137     @Override
dispatchDraw(Canvas canvas)138     public void dispatchDraw(Canvas canvas) {
139         if (!mFancyClippingPath.isEmpty()) {
140             canvas.translate(0, -getTranslationY());
141             canvas.clipOutPath(mFancyClippingPath);
142             canvas.translate(0, getTranslationY());
143         }
144         super.dispatchDraw(canvas);
145     }
146 
147     @Override
measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed)148     protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
149             int parentHeightMeasureSpec, int heightUsed) {
150         if (!mSceneContainerEnabled) {
151             // Do not measure QSPanel again when doing super.onMeasure.
152             // This prevents the pages in PagedTileLayout to be remeasured with a different
153             // (incorrect) size to the one used for determining the number of rows and then the
154             // number of pages.
155             if (child != mQSPanelContainer) {
156                 super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed,
157                         parentHeightMeasureSpec, heightUsed);
158             }
159         } else {
160             // Don't measure the customizer with all the children, it will be measured separately
161             if (child != mQSCustomizer) {
162                 super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed,
163                         parentHeightMeasureSpec, heightUsed);
164             }
165         }
166     }
167 
168     @Override
dispatchTouchEvent(MotionEvent ev)169     public boolean dispatchTouchEvent(MotionEvent ev) {
170         return TouchLogger.logDispatchTouch("QS", ev, super.dispatchTouchEvent(ev));
171     }
172 
173     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)174     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
175         super.onLayout(changed, left, top, right, bottom);
176         updateExpansion();
177         updateClippingPath();
178     }
179 
180     @Nullable
getQSPanelContainer()181     public NonInterceptingScrollView getQSPanelContainer() {
182         return mQSPanelContainer;
183     }
184 
disable(int state1, int state2, boolean animate)185     public void disable(int state1, int state2, boolean animate) {
186         final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0;
187         if (disabled == mQsDisabled) return;
188         mQsDisabled = disabled;
189     }
190 
updateResources(QSPanelController qsPanelController, QuickStatusBarHeaderController quickStatusBarHeaderController)191     void updateResources(QSPanelController qsPanelController,
192             QuickStatusBarHeaderController quickStatusBarHeaderController) {
193         int topPadding = QSUtils.getQsHeaderSystemIconsAreaHeight(mContext);
194         if (!LargeScreenUtils.shouldUseLargeScreenShadeHeader(mContext.getResources())) {
195             topPadding = LargeScreenHeaderHelper.getLargeScreenHeaderHeight(mContext);
196         }
197         if (mQSPanelContainer != null) {
198             mQSPanelContainer.setPaddingRelative(
199                     mQSPanelContainer.getPaddingStart(),
200                     mSceneContainerEnabled ? 0 : topPadding,
201                     mQSPanelContainer.getPaddingEnd(),
202                     mQSPanelContainer.getPaddingBottom());
203         } else {
204             mQSPanel.setPaddingRelative(
205                     mQSPanel.getPaddingStart(),
206                     mSceneContainerEnabled ? 0 : topPadding,
207                     mQSPanel.getPaddingEnd(),
208                     mQSPanel.getPaddingBottom());
209         }
210 
211         int horizontalMargins = getResources().getDimensionPixelSize(R.dimen.qs_horizontal_margin);
212         int horizontalPadding = getResources().getDimensionPixelSize(
213                 R.dimen.qs_content_horizontal_padding);
214         int tilesPageMargin = getResources().getDimensionPixelSize(
215                 R.dimen.qs_tiles_page_horizontal_margin);
216         boolean marginsChanged = horizontalPadding != mContentHorizontalPadding
217                 || horizontalMargins != mHorizontalMargins
218                 || tilesPageMargin != mTilesPageMargin;
219         mContentHorizontalPadding = horizontalPadding;
220         mHorizontalMargins = horizontalMargins;
221         mTilesPageMargin = tilesPageMargin;
222         if (marginsChanged) {
223             updatePaddingsAndMargins(qsPanelController, quickStatusBarHeaderController);
224         }
225     }
226 
227     /**
228      * Overrides the height of this view (post-layout), so that the content is clipped to that
229      * height and the background is set to that height.
230      *
231      * @param heightOverride the overridden height
232      */
setHeightOverride(int heightOverride)233     public void setHeightOverride(int heightOverride) {
234         mHeightOverride = heightOverride;
235         updateExpansion();
236     }
237 
updateExpansion()238     public void updateExpansion() {
239         int height = calculateContainerHeight();
240         setBottom(getTop() + height);
241     }
242 
calculateContainerHeight()243     protected int calculateContainerHeight() {
244         int heightOverride = mHeightOverride != -1 ? mHeightOverride : getMeasuredHeight();
245         // Need to add the dragHandle height so touches will be intercepted by it.
246         return mQSCustomizer.isCustomizing() ? mQSCustomizer.getHeight()
247                 : Math.round(mQsExpansion * (heightOverride - mHeader.getHeight()))
248                 + mHeader.getHeight();
249     }
250 
251     // These next two methods are used with Scene container to determine the size of QQS and QS .
252 
253     /**
254      * Returns the size of the QQS container, regardless of the measured size of this view.
255      * @return size in pixels of QQS
256      */
getQqsHeight()257     public int getQqsHeight() {
258         SceneContainerFlag.unsafeAssertInNewMode();
259         return mHeader.getMeasuredHeight();
260     }
261 
262     /**
263      * @return height with the squishiness fraction applied.
264      */
getSquishedQqsHeight()265     int getSquishedQqsHeight() {
266         return mHeader.getSquishedHeight();
267     }
268 
269     /**
270      * Returns the size of QS (or the QSCustomizer), regardless of the measured size of this view
271      * @return size in pixels of QS (or QSCustomizer)
272      */
getQsHeight()273     public int getQsHeight() {
274         return mQSCustomizer.isCustomizing() ? mQSCustomizer.getMeasuredHeight()
275                 : mQSPanel.getMeasuredHeight();
276     }
277 
278     /**
279      * @return height with the squishiness fraction applied.
280      */
getSquishedQsHeight()281     int getSquishedQsHeight() {
282         return mQSPanel.getSquishedHeight();
283     }
284 
setExpansion(float expansion)285     public void setExpansion(float expansion) {
286         mQsExpansion = expansion;
287         if (mQSPanelContainer != null) {
288             mQSPanelContainer.setScrollingEnabled(expansion > 0f);
289         }
290         updateExpansion();
291     }
292 
updatePaddingsAndMargins(QSPanelController qsPanelController, QuickStatusBarHeaderController quickStatusBarHeaderController)293     private void updatePaddingsAndMargins(QSPanelController qsPanelController,
294             QuickStatusBarHeaderController quickStatusBarHeaderController) {
295         for (int i = 0; i < getChildCount(); i++) {
296             View view = getChildAt(i);
297             if (view == mQSCustomizer) {
298                 // Some views are always full width or have dependent padding
299                 continue;
300             }
301             if (view.getId() != R.id.qs_footer_actions) {
302                 // Only padding for FooterActionsView, no margin. That way, the background goes
303                 // all the way to the edge.
304                 LayoutParams lp = (LayoutParams) view.getLayoutParams();
305                 lp.rightMargin = mHorizontalMargins;
306                 lp.leftMargin = mHorizontalMargins;
307             }
308             if (view == mQSPanelContainer || view == mQSPanel) {
309                 // QS panel lays out some of its content full width
310                 qsPanelController.setContentMargins(mContentHorizontalPadding,
311                         mContentHorizontalPadding);
312                 setPageMargins(qsPanelController);
313             } else if (view == mHeader) {
314                 quickStatusBarHeaderController.setContentMargins(mContentHorizontalPadding,
315                         mContentHorizontalPadding);
316             } else {
317                 // Set the horizontal paddings unless the view is the Compose implementation of the
318                 // footer actions.
319                 if (view.getId() != R.id.qs_footer_actions) {
320                     view.setPaddingRelative(
321                             mContentHorizontalPadding,
322                             view.getPaddingTop(),
323                             mContentHorizontalPadding,
324                             view.getPaddingBottom());
325                 }
326             }
327         }
328     }
329 
setPageMargins(QSPanelController qsPanelController)330     private void setPageMargins(QSPanelController qsPanelController) {
331         if (SceneContainerFlag.isEnabled()) {
332             if (mHorizontalMargins == mTilesPageMargin * 2 + 1) {
333                 qsPanelController.setPageMargin(mTilesPageMargin, mTilesPageMargin + 1);
334             } else {
335                 qsPanelController.setPageMargin(mTilesPageMargin, mTilesPageMargin);
336             }
337         } else {
338             qsPanelController.setPageMargin(mTilesPageMargin, mTilesPageMargin);
339         }
340     }
341 
342     /**
343      * Clip QS bottom using a concave shape.
344      */
setFancyClipping(int leftInset, int top, int rightInset, int bottom, int radius, boolean enabled, boolean fullWidth)345     public void setFancyClipping(int leftInset, int top, int rightInset, int bottom, int radius,
346             boolean enabled, boolean fullWidth) {
347         boolean updatePath = false;
348         if (mFancyClippingRadii[0] != radius) {
349             mFancyClippingRadii[0] = radius;
350             mFancyClippingRadii[1] = radius;
351             mFancyClippingRadii[2] = radius;
352             mFancyClippingRadii[3] = radius;
353             updatePath = true;
354         }
355         if (mFancyClippingLeftInset != leftInset) {
356             mFancyClippingLeftInset = leftInset;
357             updatePath = true;
358         }
359         if (mFancyClippingTop != top) {
360             mFancyClippingTop = top;
361             updatePath = true;
362         }
363         if (mFancyClippingRightInset != rightInset) {
364             mFancyClippingRightInset = rightInset;
365             updatePath = true;
366         }
367         if (mFancyClippingBottom != bottom) {
368             mFancyClippingBottom = bottom;
369             updatePath = true;
370         }
371         if (mClippingEnabled != enabled) {
372             mClippingEnabled = enabled;
373             updatePath = true;
374         }
375         if (mIsFullWidth != fullWidth) {
376             mIsFullWidth = fullWidth;
377             updatePath = true;
378         }
379 
380         if (updatePath) {
381             updateClippingPath();
382         }
383     }
384 
385     @Override
isTransformedTouchPointInView(float x, float y, View child, PointF outLocalPoint)386     protected boolean isTransformedTouchPointInView(float x, float y,
387             View child, PointF outLocalPoint) {
388         // Prevent touches outside the clipped area from propagating to a child in that area.
389         if (mClippingEnabled && y + getTranslationY() > mFancyClippingTop) {
390             return false;
391         }
392         return super.isTransformedTouchPointInView(x, y, child, outLocalPoint);
393     }
394 
updateClippingPath()395     private void updateClippingPath() {
396         mFancyClippingPath.reset();
397         if (!mClippingEnabled) {
398             invalidate();
399             return;
400         }
401 
402         int clippingLeft = mIsFullWidth ? -mFancyClippingLeftInset : 0;
403         int clippingRight = mIsFullWidth ? getWidth() + mFancyClippingRightInset : getWidth();
404         mFancyClippingPath.addRoundRect(clippingLeft, mFancyClippingTop, clippingRight,
405                 mFancyClippingBottom, mFancyClippingRadii, Path.Direction.CW);
406         invalidate();
407     }
408 
409     @Override
dump(PrintWriter pw, String[] args)410     public void dump(PrintWriter pw, String[] args) {
411         pw.println(getClass().getSimpleName() + " updateClippingPath: "
412                 + "leftInset(" + mFancyClippingLeftInset + ") "
413                 + "top(" + mFancyClippingTop + ") "
414                 + "rightInset(" + mFancyClippingRightInset + ") "
415                 + "bottom(" + mFancyClippingBottom  + ") "
416                 + "mClippingEnabled(" + mClippingEnabled + ") "
417                 + "mIsFullWidth(" + mIsFullWidth + ")");
418     }
419 }
420