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