• 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.content.res.Configuration;
23 import android.graphics.Canvas;
24 import android.graphics.Path;
25 import android.graphics.Point;
26 import android.graphics.PointF;
27 import android.util.AttributeSet;
28 import android.view.View;
29 import android.view.WindowInsets;
30 import android.widget.FrameLayout;
31 
32 import com.android.systemui.Dumpable;
33 import com.android.systemui.R;
34 import com.android.systemui.qs.customize.QSCustomizer;
35 
36 import java.io.FileDescriptor;
37 import java.io.PrintWriter;
38 
39 /**
40  * Wrapper view with background which contains {@link QSPanel} and {@link QuickStatusBarHeader}
41  */
42 public class QSContainerImpl extends FrameLayout implements Dumpable {
43 
44     private final Point mSizePoint = new Point();
45     private int mFancyClippingTop;
46     private int mFancyClippingBottom;
47     private final float[] mFancyClippingRadii = new float[] {0, 0, 0, 0, 0, 0, 0, 0};
48     private  final Path mFancyClippingPath = new Path();
49     private int mHeightOverride = -1;
50     private View mQSDetail;
51     private QuickStatusBarHeader mHeader;
52     private float mQsExpansion;
53     private QSCustomizer mQSCustomizer;
54     private NonInterceptingScrollView mQSPanelContainer;
55 
56     private int mSideMargins;
57     private boolean mQsDisabled;
58     private int mContentPadding = -1;
59     private int mNavBarInset = 0;
60     private boolean mClippingEnabled;
61 
QSContainerImpl(Context context, AttributeSet attrs)62     public QSContainerImpl(Context context, AttributeSet attrs) {
63         super(context, attrs);
64     }
65 
66     @Override
onFinishInflate()67     protected void onFinishInflate() {
68         super.onFinishInflate();
69         mQSPanelContainer = findViewById(R.id.expanded_qs_scroll_view);
70         mQSDetail = findViewById(R.id.qs_detail);
71         mHeader = findViewById(R.id.header);
72         mQSCustomizer = findViewById(R.id.qs_customize);
73         setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
74     }
75 
76     @Override
hasOverlappingRendering()77     public boolean hasOverlappingRendering() {
78         return false;
79     }
80 
81     @Override
onConfigurationChanged(Configuration newConfig)82     protected void onConfigurationChanged(Configuration newConfig) {
83         super.onConfigurationChanged(newConfig);
84         mSizePoint.set(0, 0); // Will be retrieved on next measure pass.
85     }
86 
87     @Override
performClick()88     public boolean performClick() {
89         // Want to receive clicks so missing QQS tiles doesn't cause collapse, but
90         // don't want to do anything with them.
91         return true;
92     }
93 
94     @Override
onApplyWindowInsets(WindowInsets insets)95     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
96         mNavBarInset = insets.getInsets(WindowInsets.Type.navigationBars()).bottom;
97         mQSPanelContainer.setPaddingRelative(
98                 mQSPanelContainer.getPaddingStart(),
99                 mQSPanelContainer.getPaddingTop(),
100                 mQSPanelContainer.getPaddingEnd(),
101                 mNavBarInset
102         );
103         return super.onApplyWindowInsets(insets);
104     }
105 
106     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)107     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
108         // QSPanel will show as many rows as it can (up to TileLayout.MAX_ROWS) such that the
109         // bottom and footer are inside the screen.
110         MarginLayoutParams layoutParams = (MarginLayoutParams) mQSPanelContainer.getLayoutParams();
111 
112         int maxQs = getDisplayHeight() - layoutParams.topMargin - layoutParams.bottomMargin
113                 - getPaddingBottom();
114         int padding = mPaddingLeft + mPaddingRight + layoutParams.leftMargin
115                 + layoutParams.rightMargin;
116         final int qsPanelWidthSpec = getChildMeasureSpec(widthMeasureSpec, padding,
117                 layoutParams.width);
118         mQSPanelContainer.measure(qsPanelWidthSpec,
119                 MeasureSpec.makeMeasureSpec(maxQs, MeasureSpec.AT_MOST));
120         int width = mQSPanelContainer.getMeasuredWidth() + padding;
121         super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
122                 MeasureSpec.makeMeasureSpec(getDisplayHeight(), MeasureSpec.EXACTLY));
123         // QSCustomizer will always be the height of the screen, but do this after
124         // other measuring to avoid changing the height of the QS.
125         mQSCustomizer.measure(widthMeasureSpec,
126                 MeasureSpec.makeMeasureSpec(getDisplayHeight(), MeasureSpec.EXACTLY));
127     }
128 
129     @Override
dispatchDraw(Canvas canvas)130     public void dispatchDraw(Canvas canvas) {
131         if (!mFancyClippingPath.isEmpty()) {
132             canvas.translate(0, -getTranslationY());
133             canvas.clipOutPath(mFancyClippingPath);
134             canvas.translate(0, getTranslationY());
135         }
136         super.dispatchDraw(canvas);
137     }
138 
139     @Override
measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed)140     protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
141             int parentHeightMeasureSpec, int heightUsed) {
142         // Do not measure QSPanel again when doing super.onMeasure.
143         // This prevents the pages in PagedTileLayout to be remeasured with a different (incorrect)
144         // size to the one used for determining the number of rows and then the number of pages.
145         if (child != mQSPanelContainer) {
146             super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed,
147                     parentHeightMeasureSpec, heightUsed);
148         }
149     }
150 
151     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)152     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
153         super.onLayout(changed, left, top, right, bottom);
154         updateExpansion();
155         updateClippingPath();
156     }
157 
disable(int state1, int state2, boolean animate)158     public void disable(int state1, int state2, boolean animate) {
159         final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0;
160         if (disabled == mQsDisabled) return;
161         mQsDisabled = disabled;
162     }
163 
updateResources(QSPanelController qsPanelController, QuickStatusBarHeaderController quickStatusBarHeaderController)164     void updateResources(QSPanelController qsPanelController,
165             QuickStatusBarHeaderController quickStatusBarHeaderController) {
166         mQSPanelContainer.setPaddingRelative(
167                 getPaddingStart(),
168                 mContext.getResources().getDimensionPixelSize(
169                 com.android.internal.R.dimen.quick_qs_offset_height),
170                 getPaddingEnd(),
171                 getPaddingBottom()
172         );
173 
174         int sideMargins = getResources().getDimensionPixelSize(R.dimen.notification_side_paddings);
175         int padding = getResources().getDimensionPixelSize(
176                 R.dimen.notification_shade_content_margin_horizontal);
177         boolean marginsChanged = padding != mContentPadding || sideMargins != mSideMargins;
178         mContentPadding = padding;
179         mSideMargins = sideMargins;
180         if (marginsChanged) {
181             updatePaddingsAndMargins(qsPanelController, quickStatusBarHeaderController);
182         }
183     }
184 
185     /**
186      * Overrides the height of this view (post-layout), so that the content is clipped to that
187      * height and the background is set to that height.
188      *
189      * @param heightOverride the overridden height
190      */
setHeightOverride(int heightOverride)191     public void setHeightOverride(int heightOverride) {
192         mHeightOverride = heightOverride;
193         updateExpansion();
194     }
195 
updateExpansion()196     public void updateExpansion() {
197         int height = calculateContainerHeight();
198         int scrollBottom = calculateContainerBottom();
199         setBottom(getTop() + height);
200         mQSDetail.setBottom(getTop() + scrollBottom);
201         int qsDetailBottomMargin = ((MarginLayoutParams) mQSDetail.getLayoutParams()).bottomMargin;
202         mQSDetail.setBottom(getTop() + scrollBottom - qsDetailBottomMargin);
203     }
204 
calculateContainerHeight()205     protected int calculateContainerHeight() {
206         int heightOverride = mHeightOverride != -1 ? mHeightOverride : getMeasuredHeight();
207         return mQSCustomizer.isCustomizing() ? mQSCustomizer.getHeight()
208                 : Math.round(mQsExpansion * (heightOverride - mHeader.getHeight()))
209                 + mHeader.getHeight();
210     }
211 
calculateContainerBottom()212     int calculateContainerBottom() {
213         int heightOverride = mHeightOverride != -1 ? mHeightOverride : getMeasuredHeight();
214         return mQSCustomizer.isCustomizing() ? mQSCustomizer.getHeight()
215                 : Math.round(mQsExpansion
216                         * (heightOverride + mQSPanelContainer.getScrollRange()
217                                 - mQSPanelContainer.getScrollY() - mHeader.getHeight()))
218                         + mHeader.getHeight();
219     }
220 
setExpansion(float expansion)221     public void setExpansion(float expansion) {
222         mQsExpansion = expansion;
223         mQSPanelContainer.setScrollingEnabled(expansion > 0f);
224         updateExpansion();
225     }
226 
updatePaddingsAndMargins(QSPanelController qsPanelController, QuickStatusBarHeaderController quickStatusBarHeaderController)227     private void updatePaddingsAndMargins(QSPanelController qsPanelController,
228             QuickStatusBarHeaderController quickStatusBarHeaderController) {
229         for (int i = 0; i < getChildCount(); i++) {
230             View view = getChildAt(i);
231             if (view == mQSCustomizer) {
232                 // Some views are always full width or have dependent padding
233                 continue;
234             }
235             LayoutParams lp = (LayoutParams) view.getLayoutParams();
236             lp.rightMargin = mSideMargins;
237             lp.leftMargin = mSideMargins;
238             if (view == mQSPanelContainer) {
239                 // QS panel lays out some of its content full width
240                 qsPanelController.setContentMargins(mContentPadding, mContentPadding);
241                 // Set it as double the side margin (to simulate end margin of current page +
242                 // start margin of next page).
243                 qsPanelController.setPageMargin(mSideMargins);
244             } else if (view == mHeader) {
245                 quickStatusBarHeaderController.setContentMargins(mContentPadding, mContentPadding);
246             } else {
247                 view.setPaddingRelative(
248                         mContentPadding,
249                         view.getPaddingTop(),
250                         mContentPadding,
251                         view.getPaddingBottom());
252             }
253         }
254     }
255 
getDisplayHeight()256     private int getDisplayHeight() {
257         if (mSizePoint.y == 0) {
258             getDisplay().getRealSize(mSizePoint);
259         }
260         return mSizePoint.y;
261     }
262 
263     /**
264      * Clip QS bottom using a concave shape.
265      */
setFancyClipping(int top, int bottom, int radius, boolean enabled)266     public void setFancyClipping(int top, int bottom, int radius, boolean enabled) {
267         boolean updatePath = false;
268         if (mFancyClippingRadii[0] != radius) {
269             mFancyClippingRadii[0] = radius;
270             mFancyClippingRadii[1] = radius;
271             mFancyClippingRadii[2] = radius;
272             mFancyClippingRadii[3] = radius;
273             updatePath = true;
274         }
275         if (mFancyClippingTop != top) {
276             mFancyClippingTop = top;
277             updatePath = true;
278         }
279         if (mFancyClippingBottom != bottom) {
280             mFancyClippingBottom = bottom;
281             updatePath = true;
282         }
283         if (mClippingEnabled != enabled) {
284             mClippingEnabled = enabled;
285             updatePath = true;
286         }
287 
288         if (updatePath) {
289             updateClippingPath();
290         }
291     }
292 
293     @Override
isTransformedTouchPointInView(float x, float y, View child, PointF outLocalPoint)294     protected boolean isTransformedTouchPointInView(float x, float y,
295             View child, PointF outLocalPoint) {
296         // Prevent touches outside the clipped area from propagating to a child in that area.
297         if (mClippingEnabled && y + getTranslationY() > mFancyClippingTop) {
298             return false;
299         }
300         return super.isTransformedTouchPointInView(x, y, child, outLocalPoint);
301     }
302 
updateClippingPath()303     private void updateClippingPath() {
304         mFancyClippingPath.reset();
305         if (!mClippingEnabled) {
306             invalidate();
307             return;
308         }
309 
310         mFancyClippingPath.addRoundRect(0, mFancyClippingTop, getWidth(),
311                 mFancyClippingBottom, mFancyClippingRadii, Path.Direction.CW);
312         invalidate();
313     }
314 
315     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)316     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
317         pw.println(getClass().getSimpleName() + " updateClippingPath: top("
318                 + mFancyClippingTop + ") bottom(" + mFancyClippingBottom  + ") mClippingEnabled("
319                 + mClippingEnabled + ")");
320     }
321 }
322