package com.android.systemui.qs; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; 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 PagedTileLayout extends ViewPager implements QSTileLayout { private static final boolean DEBUG = false; private static final String TAG = "PagedTileLayout"; private final ArrayList<TileRecord> mTiles = new ArrayList<TileRecord>(); private final ArrayList<TilePage> mPages = new ArrayList<TilePage>(); private PageIndicator mPageIndicator; private int mNumPages; private View mDecorGroup; private PageListener mPageListener; private int mPosition; private boolean mOffPage; private boolean mListening; public PagedTileLayout(Context context, AttributeSet attrs) { super(context, attrs); setAdapter(mAdapter); setOnPageChangeListener(new OnPageChangeListener() { @Override public void onPageSelected(int position) { if (mPageIndicator == null) return; if (mPageListener != null) { mPageListener.onPageChanged(isLayoutRtl() ? position == mPages.size() - 1 : position == 0); } } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { if (mPageIndicator == null) return; setCurrentPage(position, positionOffset != 0); mPageIndicator.setLocation(position + positionOffset); if (mPageListener != null) { mPageListener.onPageChanged(positionOffsetPixels == 0 && (isLayoutRtl() ? position == mPages.size() - 1 : position == 0)); } } @Override public void onPageScrollStateChanged(int state) { } }); setCurrentItem(0); } @Override public void onRtlPropertiesChanged(int layoutDirection) { super.onRtlPropertiesChanged(layoutDirection); setAdapter(mAdapter); setCurrentItem(0, false); } @Override public void setCurrentItem(int item, boolean smoothScroll) { if (isLayoutRtl()) { item = mPages.size() - 1 - item; } super.setCurrentItem(item, smoothScroll); } @Override public void setListening(boolean listening) { if (mListening == listening) return; mListening = listening; if (mListening) { setPageListening(mPosition, true); if (mOffPage) { setPageListening(mPosition + 1, true); } } else { // Make sure no pages are listening. for (int i = 0; i < mPages.size(); i++) { mPages.get(i).setListening(false); } } } /** * Sets individual pages to listening or not. If offPage it will set * the next page after position to listening as well since we are in between * pages. */ private void setCurrentPage(int position, boolean offPage) { if (mPosition == position && mOffPage == offPage) return; if (mListening) { if (mPosition != position) { // Clear out the last pages from listening. setPageListening(mPosition, false); if (mOffPage) { setPageListening(mPosition + 1, false); } // Set the new pages to listening setPageListening(position, true); if (offPage) { setPageListening(position + 1, true); } } else if (mOffPage != offPage) { // Whether we are showing position + 1 has changed. setPageListening(mPosition + 1, offPage); } } // Save the current state. mPosition = position; mOffPage = offPage; } private void setPageListening(int position, boolean listening) { if (position >= mPages.size()) return; if (isLayoutRtl()) { position = mPages.size() - 1 - position; } mPages.get(position).setListening(listening); } @Override public boolean hasOverlappingRendering() { return false; } @Override protected void onFinishInflate() { super.onFinishInflate(); mPageIndicator = (PageIndicator) findViewById(R.id.page_indicator); mDecorGroup = findViewById(R.id.page_decor); ((LayoutParams) mDecorGroup.getLayoutParams()).isDecor = true; mPages.add((TilePage) LayoutInflater.from(mContext) .inflate(R.layout.qs_paged_page, this, false)); } @Override public int getOffsetTop(TileRecord tile) { final ViewGroup parent = (ViewGroup) tile.tileView.getParent(); if (parent == null) return 0; return parent.getTop() + getTop(); } @Override public void addTile(TileRecord tile) { mTiles.add(tile); postDistributeTiles(); } @Override public void removeTile(TileRecord tile) { if (mTiles.remove(tile)) { postDistributeTiles(); } } public void setPageListener(PageListener listener) { mPageListener = listener; } private void postDistributeTiles() { removeCallbacks(mDistribute); post(mDistribute); } private void distributeTiles() { if (DEBUG) Log.d(TAG, "Distributing tiles"); final int NP = mPages.size(); for (int i = 0; i < NP; i++) { mPages.get(i).removeAllViews(); } int index = 0; final int NT = mTiles.size(); for (int i = 0; i < NT; i++) { TileRecord tile = mTiles.get(i); if (mPages.get(index).isFull()) { if (++index == mPages.size()) { if (DEBUG) Log.d(TAG, "Adding page for " + tile.tile.getClass().getSimpleName()); mPages.add((TilePage) LayoutInflater.from(mContext) .inflate(R.layout.qs_paged_page, this, false)); } } if (DEBUG) Log.d(TAG, "Adding " + tile.tile.getClass().getSimpleName() + " to " + index); mPages.get(index).addTile(tile); } if (mNumPages != index + 1) { mNumPages = index + 1; while (mPages.size() > mNumPages) { mPages.remove(mPages.size() - 1); } if (DEBUG) Log.d(TAG, "Size: " + mNumPages); mPageIndicator.setNumPages(mNumPages); mDecorGroup.setVisibility(mNumPages > 1 ? View.VISIBLE : View.GONE); setAdapter(mAdapter); mAdapter.notifyDataSetChanged(); setCurrentItem(0, false); } } @Override public boolean updateResources() { boolean changed = false; for (int i = 0; i < mPages.size(); i++) { changed |= mPages.get(i).updateResources(); } if (changed) { distributeTiles(); } return changed; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); // The ViewPager likes to eat all of the space, instead force it to wrap to the max height // of the pages. int maxHeight = 0; final int N = getChildCount(); for (int i = 0; i < N; i++) { int height = getChildAt(i).getMeasuredHeight(); if (height > maxHeight) { maxHeight = height; } } setMeasuredDimension(getMeasuredWidth(), maxHeight + (mDecorGroup.getVisibility() != View.GONE ? mDecorGroup.getMeasuredHeight() : 0)); } private final Runnable mDistribute = new Runnable() { @Override public void run() { distributeTiles(); } }; public int getColumnCount() { if (mPages.size() == 0) return 0; return mPages.get(0).mColumns; } public static class TilePage extends TileLayout { private int mMaxRows = 3; public TilePage(Context context, AttributeSet attrs) { super(context, attrs); updateResources(); setContentDescription(mContext.getString(R.string.accessibility_desc_quick_settings)); } @Override public boolean updateResources() { final int rows = getRows(); boolean changed = rows != mMaxRows; if (changed) { mMaxRows = rows; requestLayout(); } return super.updateResources() || changed; } private int getRows() { final Resources res = getContext().getResources(); if (res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { // Always have 3 rows in portrait. return 3; } return Math.max(1, res.getInteger(R.integer.quick_settings_num_rows)); } public void setMaxRows(int maxRows) { mMaxRows = maxRows; } public boolean isFull() { return mRecords.size() >= mColumns * mMaxRows; } } private final PagerAdapter mAdapter = new PagerAdapter() { public void destroyItem(ViewGroup container, int position, Object object) { if (DEBUG) Log.d(TAG, "Destantiating " + position); container.removeView((View) object); } public Object instantiateItem(ViewGroup container, int position) { if (DEBUG) Log.d(TAG, "Instantiating " + position); if (isLayoutRtl()) { position = mPages.size() - 1 - position; } ViewGroup view = mPages.get(position); container.addView(view); return view; } @Override public int getCount() { return mNumPages; } @Override public boolean isViewFromObject(View view, Object object) { return view == object; } }; public interface PageListener { void onPageChanged(boolean isFirst); } }