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