• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.android.systemui.qs;
2 
3 import static com.android.systemui.util.Utils.useQsMediaPlayer;
4 
5 import android.content.Context;
6 import android.content.res.Resources;
7 import android.provider.Settings;
8 import android.util.AttributeSet;
9 import android.view.View;
10 import android.view.ViewGroup;
11 import android.view.accessibility.AccessibilityNodeInfo;
12 
13 import androidx.annotation.Nullable;
14 
15 import com.android.internal.logging.UiEventLogger;
16 import com.android.systemui.R;
17 import com.android.systemui.qs.QSPanel.QSTileLayout;
18 import com.android.systemui.qs.QSPanelControllerBase.TileRecord;
19 import com.android.systemui.qs.tileimpl.HeightOverrideable;
20 import com.android.systemui.qs.tileimpl.QSTileViewImplKt;
21 
22 import java.util.ArrayList;
23 
24 public class TileLayout extends ViewGroup implements QSTileLayout {
25 
26     public static final int NO_MAX_COLUMNS = 100;
27 
28     private static final String TAG = "TileLayout";
29 
30     protected int mColumns;
31     protected int mCellWidth;
32     protected int mCellHeightResId = R.dimen.qs_tile_height;
33     protected int mCellHeight;
34     protected int mMaxCellHeight;
35     protected int mCellMarginHorizontal;
36     protected int mCellMarginVertical;
37     protected int mSidePadding;
38     protected int mRows = 1;
39 
40     protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
41     protected boolean mListening;
42     protected int mMaxAllowedRows = 3;
43 
44     // Prototyping with less rows
45     private final boolean mLessRows;
46     private int mMinRows = 1;
47     private int mMaxColumns = NO_MAX_COLUMNS;
48     protected int mResourceColumns;
49     private float mSquishinessFraction = 1f;
50     protected int mLastTileBottom;
51 
TileLayout(Context context)52     public TileLayout(Context context) {
53         this(context, null);
54     }
55 
TileLayout(Context context, @Nullable AttributeSet attrs)56     public TileLayout(Context context, @Nullable AttributeSet attrs) {
57         super(context, attrs);
58         setFocusableInTouchMode(true);
59         mLessRows = ((Settings.System.getInt(context.getContentResolver(), "qs_less_rows", 0) != 0)
60                 || useQsMediaPlayer(context));
61         updateResources();
62     }
63 
64     @Override
getOffsetTop(TileRecord tile)65     public int getOffsetTop(TileRecord tile) {
66         return getTop();
67     }
68 
setListening(boolean listening)69     public void setListening(boolean listening) {
70         setListening(listening, null);
71     }
72 
73     @Override
setListening(boolean listening, @Nullable UiEventLogger uiEventLogger)74     public void setListening(boolean listening, @Nullable UiEventLogger uiEventLogger) {
75         if (mListening == listening) return;
76         mListening = listening;
77         for (TileRecord record : mRecords) {
78             record.tile.setListening(this, mListening);
79         }
80     }
81 
82     @Override
setMinRows(int minRows)83     public boolean setMinRows(int minRows) {
84         if (mMinRows != minRows) {
85             mMinRows = minRows;
86             updateResources();
87             return true;
88         }
89         return false;
90     }
91 
92     @Override
setMaxColumns(int maxColumns)93     public boolean setMaxColumns(int maxColumns) {
94         mMaxColumns = maxColumns;
95         return updateColumns();
96     }
97 
addTile(TileRecord tile)98     public void addTile(TileRecord tile) {
99         mRecords.add(tile);
100         tile.tile.setListening(this, mListening);
101         addTileView(tile);
102     }
103 
addTileView(TileRecord tile)104     protected void addTileView(TileRecord tile) {
105         addView(tile.tileView);
106     }
107 
108     @Override
removeTile(TileRecord tile)109     public void removeTile(TileRecord tile) {
110         mRecords.remove(tile);
111         tile.tile.setListening(this, false);
112         removeView(tile.tileView);
113     }
114 
removeAllViews()115     public void removeAllViews() {
116         for (TileRecord record : mRecords) {
117             record.tile.setListening(this, false);
118         }
119         mRecords.clear();
120         super.removeAllViews();
121     }
122 
updateResources()123     public boolean updateResources() {
124         final Resources res = mContext.getResources();
125         mResourceColumns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns));
126         mMaxCellHeight = mContext.getResources().getDimensionPixelSize(mCellHeightResId);
127         mCellMarginHorizontal = res.getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal);
128         mSidePadding = useSidePadding() ? mCellMarginHorizontal / 2 : 0;
129         mCellMarginVertical= res.getDimensionPixelSize(R.dimen.qs_tile_margin_vertical);
130         mMaxAllowedRows = Math.max(1, getResources().getInteger(R.integer.quick_settings_max_rows));
131         if (mLessRows) mMaxAllowedRows = Math.max(mMinRows, mMaxAllowedRows - 1);
132         if (updateColumns()) {
133             requestLayout();
134             return true;
135         }
136         return false;
137     }
138 
useSidePadding()139     protected boolean useSidePadding() {
140         return true;
141     }
142 
updateColumns()143     private boolean updateColumns() {
144         int oldColumns = mColumns;
145         mColumns = Math.min(mResourceColumns, mMaxColumns);
146         return oldColumns != mColumns;
147     }
148 
149     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)150     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
151         // If called with AT_MOST, it will limit the number of rows. If called with UNSPECIFIED
152         // it will show all its tiles. In this case, the tiles have to be entered before the
153         // container is measured. Any change in the tiles, should trigger a remeasure.
154         final int numTiles = mRecords.size();
155         final int width = MeasureSpec.getSize(widthMeasureSpec);
156         final int availableWidth = width - getPaddingStart() - getPaddingEnd();
157         final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
158         if (heightMode == MeasureSpec.UNSPECIFIED) {
159             mRows = (numTiles + mColumns - 1) / mColumns;
160         }
161         final int gaps = mColumns - 1;
162         mCellWidth =
163                 (availableWidth - (mCellMarginHorizontal * gaps) - mSidePadding * 2) / mColumns;
164 
165         // Measure each QS tile.
166         View previousView = this;
167         int verticalMeasure = exactly(getCellHeight());
168         for (TileRecord record : mRecords) {
169             if (record.tileView.getVisibility() == GONE) continue;
170             record.tileView.measure(exactly(mCellWidth), verticalMeasure);
171             previousView = record.tileView.updateAccessibilityOrder(previousView);
172             mCellHeight = record.tileView.getMeasuredHeight();
173         }
174 
175         int height = (mCellHeight + mCellMarginVertical) * mRows;
176         height -= mCellMarginVertical;
177 
178         if (height < 0) height = 0;
179 
180         setMeasuredDimension(width, height);
181     }
182 
183     /**
184      * Determines the maximum number of rows that can be shown based on height. Clips at a minimum
185      * of 1 and a maximum of mMaxAllowedRows.
186      *
187      * @param allowedHeight The height this view has visually available
188      * @param tilesCount Upper limit on the number of tiles to show. to prevent empty rows.
189      */
updateMaxRows(int allowedHeight, int tilesCount)190     public boolean updateMaxRows(int allowedHeight, int tilesCount) {
191         // Add the cell margin in order to divide easily by the height + the margin below
192         final int availableHeight =  allowedHeight + mCellMarginVertical;
193         final int previousRows = mRows;
194         mRows = availableHeight / (getCellHeight() + mCellMarginVertical);
195         if (mRows < mMinRows) {
196             mRows = mMinRows;
197         } else if (mRows >= mMaxAllowedRows) {
198             mRows = mMaxAllowedRows;
199         }
200         if (mRows > (tilesCount + mColumns - 1) / mColumns) {
201             mRows = (tilesCount + mColumns - 1) / mColumns;
202         }
203         return previousRows != mRows;
204     }
205 
206     @Override
hasOverlappingRendering()207     public boolean hasOverlappingRendering() {
208         return false;
209     }
210 
exactly(int size)211     protected static int exactly(int size) {
212         return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
213     }
214 
getCellHeight()215     protected int getCellHeight() {
216         return mMaxCellHeight;
217     }
218 
layoutTileRecords(int numRecords, boolean forLayout)219     private void layoutTileRecords(int numRecords, boolean forLayout) {
220         final boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
221         int row = 0;
222         int column = 0;
223         mLastTileBottom = 0;
224 
225         // Layout each QS tile.
226         final int tilesToLayout = Math.min(numRecords, mRows * mColumns);
227         for (int i = 0; i < tilesToLayout; i++, column++) {
228             // If we reached the last column available to layout a tile, wrap back to the next row.
229             if (column == mColumns) {
230                 column = 0;
231                 row++;
232             }
233 
234             final TileRecord record = mRecords.get(i);
235             final int top = getRowTop(row);
236             final int left = getColumnStart(isRtl ? mColumns - column - 1 : column);
237             final int right = left + mCellWidth;
238             final int bottom = top + record.tileView.getMeasuredHeight();
239             if (forLayout) {
240                 record.tileView.layout(left, top, right, bottom);
241             } else {
242                 record.tileView.setLeftTopRightBottom(left, top, right, bottom);
243             }
244             record.tileView.setPosition(i);
245 
246             // Set the bottom to the unoverriden squished bottom. This is to avoid fake bottoms that
247             // are only used for QQS -> QS expansion animations
248             float scale = QSTileViewImplKt.constrainSquishiness(mSquishinessFraction);
249             mLastTileBottom = top + (int) (record.tileView.getMeasuredHeight() * scale);
250         }
251     }
252 
253     @Override
onLayout(boolean changed, int l, int t, int r, int b)254     protected void onLayout(boolean changed, int l, int t, int r, int b) {
255         layoutTileRecords(mRecords.size(), true /* forLayout */);
256     }
257 
getRowTop(int row)258     protected int getRowTop(int row) {
259         float scale = QSTileViewImplKt.constrainSquishiness(mSquishinessFraction);
260         return (int) (row * (mCellHeight * scale + mCellMarginVertical));
261     }
262 
getColumnStart(int column)263     protected int getColumnStart(int column) {
264         return getPaddingStart() + mSidePadding
265                 + column *  (mCellWidth + mCellMarginHorizontal);
266     }
267 
268     @Override
getNumVisibleTiles()269     public int getNumVisibleTiles() {
270         return mRecords.size();
271     }
272 
isFull()273     public boolean isFull() {
274         return false;
275     }
276 
277     /**
278      * @return The maximum number of tiles this layout can hold
279      */
maxTiles()280     public int maxTiles() {
281         // Each layout should be able to hold at least one tile. If there's not enough room to
282         // show even 1 or there are no tiles, it probably means we are in the middle of setting
283         // up.
284         return Math.max(mColumns * mRows, 1);
285     }
286 
287     @Override
getTilesHeight()288     public int getTilesHeight() {
289         return mLastTileBottom + getPaddingBottom();
290     }
291 
292     @Override
setSquishinessFraction(float squishinessFraction)293     public void setSquishinessFraction(float squishinessFraction) {
294         if (Float.compare(mSquishinessFraction, squishinessFraction) == 0) {
295             return;
296         }
297         mSquishinessFraction = squishinessFraction;
298         layoutTileRecords(mRecords.size(), false /* forLayout */);
299 
300         for (TileRecord record : mRecords) {
301             if (record.tileView instanceof HeightOverrideable) {
302                 ((HeightOverrideable) record.tileView).setSquishinessFraction(mSquishinessFraction);
303             }
304         }
305     }
306 
307     @Override
onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)308     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
309         super.onInitializeAccessibilityNodeInfoInternal(info);
310         info.setCollectionInfo(
311                 new AccessibilityNodeInfo.CollectionInfo(mRecords.size(), 1, false));
312     }
313 }
314