package com.android.systemui.qs; import android.content.Context; import android.content.res.Resources; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import com.android.systemui.R; import com.android.systemui.qs.QSPanel.QSTileLayout; import com.android.systemui.qs.QSPanel.TileRecord; import java.util.ArrayList; public class TileLayout extends ViewGroup implements QSTileLayout { private static final float TILE_ASPECT = 1.2f; private static final String TAG = "TileLayout"; protected int mColumns; protected int mCellWidth; protected int mCellHeight; protected int mCellMarginHorizontal; protected int mCellMarginVertical; protected int mSidePadding; protected int mRows = 1; protected final ArrayList mRecords = new ArrayList<>(); private int mCellMarginTop; private boolean mListening; protected int mMaxAllowedRows = 3; public TileLayout(Context context) { this(context, null); } public TileLayout(Context context, AttributeSet attrs) { super(context, attrs); setFocusableInTouchMode(true); updateResources(); } @Override public int getOffsetTop(TileRecord tile) { return getTop(); } @Override public void setListening(boolean listening) { if (mListening == listening) return; mListening = listening; for (TileRecord record : mRecords) { record.tile.setListening(this, mListening); } } public void addTile(TileRecord tile) { mRecords.add(tile); tile.tile.setListening(this, mListening); addTileView(tile); } protected void addTileView(TileRecord tile) { addView(tile.tileView); } @Override public void removeTile(TileRecord tile) { mRecords.remove(tile); tile.tile.setListening(this, false); removeView(tile.tileView); } public void removeAllViews() { for (TileRecord record : mRecords) { record.tile.setListening(this, false); } mRecords.clear(); super.removeAllViews(); } public boolean updateResources() { final Resources res = mContext.getResources(); final int columns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns)); mCellHeight = mContext.getResources().getDimensionPixelSize(R.dimen.qs_tile_height); mCellMarginHorizontal = res.getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal); mCellMarginVertical= res.getDimensionPixelSize(R.dimen.qs_tile_margin_vertical); mCellMarginTop = res.getDimensionPixelSize(R.dimen.qs_tile_margin_top); mSidePadding = res.getDimensionPixelOffset(R.dimen.qs_tile_layout_margin_side); mMaxAllowedRows = Math.max(1, getResources().getInteger(R.integer.quick_settings_max_rows)); if (mColumns != columns) { mColumns = columns; requestLayout(); return true; } return false; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // If called with AT_MOST, it will limit the number of rows. If called with UNSPECIFIED // it will show all its tiles. In this case, the tiles have to be entered before the // container is measured. Any change in the tiles, should trigger a remeasure. final int numTiles = mRecords.size(); final int width = MeasureSpec.getSize(widthMeasureSpec); final int availableWidth = width - getPaddingStart() - getPaddingEnd(); final int heightMode = MeasureSpec.getMode(heightMeasureSpec); if (heightMode == MeasureSpec.UNSPECIFIED) { mRows = (numTiles + mColumns - 1) / mColumns; } mCellWidth = (availableWidth - mSidePadding * 2 - (mCellMarginHorizontal * mColumns)) / mColumns; // Measure each QS tile. View previousView = this; for (TileRecord record : mRecords) { if (record.tileView.getVisibility() == GONE) continue; record.tileView.measure(exactly(mCellWidth), exactly(mCellHeight)); previousView = record.tileView.updateAccessibilityOrder(previousView); } // Only include the top margin in our measurement if we have more than 1 row to show. // Otherwise, don't add the extra margin buffer at top. int height = (mCellHeight + mCellMarginVertical) * mRows + (mRows != 0 ? (mCellMarginTop - mCellMarginVertical) : 0); if (height < 0) height = 0; setMeasuredDimension(width, height); } /** * Determines the maximum number of rows that can be shown based on height. Clips at a minimum * of 1 and a maximum of mMaxAllowedRows. * * @param heightMeasureSpec Available height. * @param tilesCount Upper limit on the number of tiles to show. to prevent empty rows. */ public boolean updateMaxRows(int heightMeasureSpec, int tilesCount) { final int availableHeight = MeasureSpec.getSize(heightMeasureSpec) - mCellMarginTop + mCellMarginVertical; final int previousRows = mRows; mRows = availableHeight / (mCellHeight + mCellMarginVertical); if (mRows >= mMaxAllowedRows) { mRows = mMaxAllowedRows; } else if (mRows <= 1) { mRows = 1; } if (mRows > (tilesCount + mColumns - 1) / mColumns) { mRows = (tilesCount + mColumns - 1) / mColumns; } return previousRows != mRows; } @Override public boolean hasOverlappingRendering() { return false; } protected static int exactly(int size) { return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY); } protected void layoutTileRecords(int numRecords) { final boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL; int row = 0; int column = 0; // Layout each QS tile. final int tilesToLayout = Math.min(numRecords, mRows * mColumns); for (int i = 0; i < tilesToLayout; i++, column++) { // If we reached the last column available to layout a tile, wrap back to the next row. if (column == mColumns) { column = 0; row++; } final TileRecord record = mRecords.get(i); final int top = getRowTop(row); final int left = getColumnStart(isRtl ? mColumns - column - 1 : column); final int right = left + mCellWidth; record.tileView.layout(left, top, right, top + record.tileView.getMeasuredHeight()); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { layoutTileRecords(mRecords.size()); } private int getRowTop(int row) { return row * (mCellHeight + mCellMarginVertical) + mCellMarginTop; } protected int getColumnStart(int column) { return getPaddingStart() + mSidePadding + mCellMarginHorizontal / 2 + column * (mCellWidth + mCellMarginHorizontal); } @Override public int getNumVisibleTiles() { return mRecords.size(); } }