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