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