1 package com.android.systemui.qs;
2 
3 import android.content.Context;
4 import android.content.res.Resources;
5 import android.util.AttributeSet;
6 import android.view.View;
7 import android.view.ViewGroup;
8 
9 import com.android.systemui.R;
10 import com.android.systemui.qs.QSPanel.QSTileLayout;
11 import com.android.systemui.qs.QSPanel.TileRecord;
12 
13 import java.util.ArrayList;
14 
15 public class TileLayout extends ViewGroup implements QSTileLayout {
16 
17     private static final float TILE_ASPECT = 1.2f;
18 
19     private static final String TAG = "TileLayout";
20 
21     protected int mColumns;
22     protected int mCellWidth;
23     protected int mCellHeight;
24     protected int mCellMarginHorizontal;
25     protected int mCellMarginVertical;
26     protected int mSidePadding;
27     protected int mRows = 1;
28 
29     protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
30     private int mCellMarginTop;
31     private boolean mListening;
32     protected int mMaxAllowedRows = 3;
33 
TileLayout(Context context)34     public TileLayout(Context context) {
35         this(context, null);
36     }
37 
TileLayout(Context context, AttributeSet attrs)38     public TileLayout(Context context, AttributeSet attrs) {
39         super(context, attrs);
40         setFocusableInTouchMode(true);
41         updateResources();
42     }
43 
44     @Override
getOffsetTop(TileRecord tile)45     public int getOffsetTop(TileRecord tile) {
46         return getTop();
47     }
48 
49     @Override
setListening(boolean listening)50     public void setListening(boolean listening) {
51         if (mListening == listening) return;
52         mListening = listening;
53         for (TileRecord record : mRecords) {
54             record.tile.setListening(this, mListening);
55         }
56     }
57 
addTile(TileRecord tile)58     public void addTile(TileRecord tile) {
59         mRecords.add(tile);
60         tile.tile.setListening(this, mListening);
61         addTileView(tile);
62     }
63 
addTileView(TileRecord tile)64     protected void addTileView(TileRecord tile) {
65         addView(tile.tileView);
66     }
67 
68     @Override
removeTile(TileRecord tile)69     public void removeTile(TileRecord tile) {
70         mRecords.remove(tile);
71         tile.tile.setListening(this, false);
72         removeView(tile.tileView);
73     }
74 
removeAllViews()75     public void removeAllViews() {
76         for (TileRecord record : mRecords) {
77             record.tile.setListening(this, false);
78         }
79         mRecords.clear();
80         super.removeAllViews();
81     }
82 
updateResources()83     public boolean updateResources() {
84         final Resources res = mContext.getResources();
85         final int columns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns));
86         mCellHeight = mContext.getResources().getDimensionPixelSize(R.dimen.qs_tile_height);
87         mCellMarginHorizontal = res.getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal);
88         mCellMarginVertical= res.getDimensionPixelSize(R.dimen.qs_tile_margin_vertical);
89         mCellMarginTop = res.getDimensionPixelSize(R.dimen.qs_tile_margin_top);
90         mSidePadding = res.getDimensionPixelOffset(R.dimen.qs_tile_layout_margin_side);
91         mMaxAllowedRows = Math.max(1, getResources().getInteger(R.integer.quick_settings_max_rows));
92         if (mColumns != columns) {
93             mColumns = columns;
94             requestLayout();
95             return true;
96         }
97         return false;
98     }
99 
100     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)101     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
102         // If called with AT_MOST, it will limit the number of rows. If called with UNSPECIFIED
103         // it will show all its tiles. In this case, the tiles have to be entered before the
104         // container is measured. Any change in the tiles, should trigger a remeasure.
105         final int numTiles = mRecords.size();
106         final int width = MeasureSpec.getSize(widthMeasureSpec);
107         final int availableWidth = width - getPaddingStart() - getPaddingEnd();
108         final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
109         if (heightMode == MeasureSpec.UNSPECIFIED) {
110             mRows = (numTiles + mColumns - 1) / mColumns;
111         }
112         mCellWidth =
113                 (availableWidth - mSidePadding * 2 - (mCellMarginHorizontal * mColumns)) / mColumns;
114 
115         // Measure each QS tile.
116         View previousView = this;
117         for (TileRecord record : mRecords) {
118             if (record.tileView.getVisibility() == GONE) continue;
119             record.tileView.measure(exactly(mCellWidth), exactly(mCellHeight));
120             previousView = record.tileView.updateAccessibilityOrder(previousView);
121         }
122 
123         // Only include the top margin in our measurement if we have more than 1 row to show.
124         // Otherwise, don't add the extra margin buffer at top.
125         int height = (mCellHeight + mCellMarginVertical) * mRows +
126                 (mRows != 0 ? (mCellMarginTop - mCellMarginVertical) : 0);
127         if (height < 0) height = 0;
128 
129         setMeasuredDimension(width, height);
130     }
131 
132     /**
133      * Determines the maximum number of rows that can be shown based on height. Clips at a minimum
134      * of 1 and a maximum of mMaxAllowedRows.
135      *
136      * @param heightMeasureSpec Available height.
137      * @param tilesCount Upper limit on the number of tiles to show. to prevent empty rows.
138      */
updateMaxRows(int heightMeasureSpec, int tilesCount)139     public boolean updateMaxRows(int heightMeasureSpec, int tilesCount) {
140         final int availableHeight = MeasureSpec.getSize(heightMeasureSpec) - mCellMarginTop
141                 + mCellMarginVertical;
142         final int previousRows = mRows;
143         mRows = availableHeight / (mCellHeight + mCellMarginVertical);
144         if (mRows >= mMaxAllowedRows) {
145             mRows = mMaxAllowedRows;
146         } else if (mRows <= 1) {
147             mRows = 1;
148         }
149         if (mRows > (tilesCount + mColumns - 1) / mColumns) {
150             mRows = (tilesCount + mColumns - 1) / mColumns;
151         }
152         return previousRows != mRows;
153     }
154 
155     @Override
hasOverlappingRendering()156     public boolean hasOverlappingRendering() {
157         return false;
158     }
159 
exactly(int size)160     protected static int exactly(int size) {
161         return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
162     }
163 
164 
layoutTileRecords(int numRecords)165     protected void layoutTileRecords(int numRecords) {
166         final boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
167         int row = 0;
168         int column = 0;
169 
170         // Layout each QS tile.
171         final int tilesToLayout = Math.min(numRecords, mRows * mColumns);
172         for (int i = 0; i < tilesToLayout; i++, column++) {
173             // If we reached the last column available to layout a tile, wrap back to the next row.
174             if (column == mColumns) {
175                 column = 0;
176                 row++;
177             }
178 
179             final TileRecord record = mRecords.get(i);
180             final int top = getRowTop(row);
181             final int left = getColumnStart(isRtl ? mColumns - column - 1 : column);
182             final int right = left + mCellWidth;
183             record.tileView.layout(left, top, right, top + record.tileView.getMeasuredHeight());
184         }
185     }
186 
187     @Override
onLayout(boolean changed, int l, int t, int r, int b)188     protected void onLayout(boolean changed, int l, int t, int r, int b) {
189         layoutTileRecords(mRecords.size());
190     }
191 
getRowTop(int row)192     private int getRowTop(int row) {
193         return row * (mCellHeight + mCellMarginVertical) + mCellMarginTop;
194     }
195 
getColumnStart(int column)196     protected int getColumnStart(int column) {
197         return getPaddingStart() + mSidePadding + mCellMarginHorizontal / 2 +
198                 column *  (mCellWidth + mCellMarginHorizontal);
199     }
200 
201     @Override
getNumVisibleTiles()202     public int getNumVisibleTiles() {
203         return mRecords.size();
204     }
205 }
206