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