1 package com.android.systemui.qs; 2 3 import android.content.Context; 4 import android.content.res.Configuration; 5 import android.content.res.Resources; 6 import android.support.v4.view.PagerAdapter; 7 import android.support.v4.view.ViewPager; 8 import android.util.AttributeSet; 9 import android.util.Log; 10 import android.view.LayoutInflater; 11 import android.view.View; 12 import android.view.ViewGroup; 13 import com.android.systemui.R; 14 import com.android.systemui.qs.QSPanel.QSTileLayout; 15 import com.android.systemui.qs.QSPanel.TileRecord; 16 17 import java.util.ArrayList; 18 19 public class PagedTileLayout extends ViewPager implements QSTileLayout { 20 21 private static final boolean DEBUG = false; 22 23 private static final String TAG = "PagedTileLayout"; 24 25 private final ArrayList<TileRecord> mTiles = new ArrayList<TileRecord>(); 26 private final ArrayList<TilePage> mPages = new ArrayList<TilePage>(); 27 28 private PageIndicator mPageIndicator; 29 30 private int mNumPages; 31 private View mDecorGroup; 32 private PageListener mPageListener; 33 34 private int mPosition; 35 private boolean mOffPage; 36 private boolean mListening; 37 PagedTileLayout(Context context, AttributeSet attrs)38 public PagedTileLayout(Context context, AttributeSet attrs) { 39 super(context, attrs); 40 setAdapter(mAdapter); 41 setOnPageChangeListener(new OnPageChangeListener() { 42 @Override 43 public void onPageSelected(int position) { 44 if (mPageIndicator == null) return; 45 if (mPageListener != null) { 46 mPageListener.onPageChanged(isLayoutRtl() ? position == mPages.size() - 1 47 : position == 0); 48 } 49 } 50 51 @Override 52 public void onPageScrolled(int position, float positionOffset, 53 int positionOffsetPixels) { 54 if (mPageIndicator == null) return; 55 setCurrentPage(position, positionOffset != 0); 56 mPageIndicator.setLocation(position + positionOffset); 57 if (mPageListener != null) { 58 mPageListener.onPageChanged(positionOffsetPixels == 0 && 59 (isLayoutRtl() ? position == mPages.size() - 1 : position == 0)); 60 } 61 } 62 63 @Override 64 public void onPageScrollStateChanged(int state) { 65 } 66 }); 67 setCurrentItem(0); 68 } 69 70 @Override onRtlPropertiesChanged(int layoutDirection)71 public void onRtlPropertiesChanged(int layoutDirection) { 72 super.onRtlPropertiesChanged(layoutDirection); 73 setAdapter(mAdapter); 74 setCurrentItem(0, false); 75 } 76 77 @Override setCurrentItem(int item, boolean smoothScroll)78 public void setCurrentItem(int item, boolean smoothScroll) { 79 if (isLayoutRtl()) { 80 item = mPages.size() - 1 - item; 81 } 82 super.setCurrentItem(item, smoothScroll); 83 } 84 85 @Override setListening(boolean listening)86 public void setListening(boolean listening) { 87 if (mListening == listening) return; 88 mListening = listening; 89 if (mListening) { 90 mPages.get(mPosition).setListening(listening); 91 if (mOffPage) { 92 mPages.get(mPosition + 1).setListening(listening); 93 } 94 } else { 95 // Make sure no pages are listening. 96 for (int i = 0; i < mPages.size(); i++) { 97 mPages.get(i).setListening(false); 98 } 99 } 100 } 101 102 /** 103 * Sets individual pages to listening or not. If offPage it will set 104 * the next page after position to listening as well since we are in between 105 * pages. 106 */ setCurrentPage(int position, boolean offPage)107 private void setCurrentPage(int position, boolean offPage) { 108 if (mPosition == position && mOffPage == offPage) return; 109 if (mListening) { 110 if (mPosition != position) { 111 // Clear out the last pages from listening. 112 setPageListening(mPosition, false); 113 if (mOffPage) { 114 setPageListening(mPosition + 1, false); 115 } 116 // Set the new pages to listening 117 setPageListening(position, true); 118 if (offPage) { 119 setPageListening(position + 1, true); 120 } 121 } else if (mOffPage != offPage) { 122 // Whether we are showing position + 1 has changed. 123 setPageListening(mPosition + 1, offPage); 124 } 125 } 126 // Save the current state. 127 mPosition = position; 128 mOffPage = offPage; 129 } 130 setPageListening(int position, boolean listening)131 private void setPageListening(int position, boolean listening) { 132 if (position >= mPages.size()) return; 133 mPages.get(position).setListening(listening); 134 } 135 136 @Override hasOverlappingRendering()137 public boolean hasOverlappingRendering() { 138 return false; 139 } 140 141 @Override onFinishInflate()142 protected void onFinishInflate() { 143 super.onFinishInflate(); 144 mPageIndicator = (PageIndicator) findViewById(R.id.page_indicator); 145 mDecorGroup = findViewById(R.id.page_decor); 146 ((LayoutParams) mDecorGroup.getLayoutParams()).isDecor = true; 147 148 mPages.add((TilePage) LayoutInflater.from(mContext) 149 .inflate(R.layout.qs_paged_page, this, false)); 150 } 151 152 @Override getOffsetTop(TileRecord tile)153 public int getOffsetTop(TileRecord tile) { 154 final ViewGroup parent = (ViewGroup) tile.tileView.getParent(); 155 if (parent == null) return 0; 156 return parent.getTop() + getTop(); 157 } 158 159 @Override addTile(TileRecord tile)160 public void addTile(TileRecord tile) { 161 mTiles.add(tile); 162 postDistributeTiles(); 163 } 164 165 @Override removeTile(TileRecord tile)166 public void removeTile(TileRecord tile) { 167 if (mTiles.remove(tile)) { 168 postDistributeTiles(); 169 } 170 } 171 setPageListener(PageListener listener)172 public void setPageListener(PageListener listener) { 173 mPageListener = listener; 174 } 175 postDistributeTiles()176 private void postDistributeTiles() { 177 removeCallbacks(mDistribute); 178 post(mDistribute); 179 } 180 distributeTiles()181 private void distributeTiles() { 182 if (DEBUG) Log.d(TAG, "Distributing tiles"); 183 final int NP = mPages.size(); 184 for (int i = 0; i < NP; i++) { 185 mPages.get(i).removeAllViews(); 186 } 187 int index = 0; 188 final int NT = mTiles.size(); 189 for (int i = 0; i < NT; i++) { 190 TileRecord tile = mTiles.get(i); 191 if (mPages.get(index).isFull()) { 192 if (++index == mPages.size()) { 193 if (DEBUG) Log.d(TAG, "Adding page for " 194 + tile.tile.getClass().getSimpleName()); 195 mPages.add((TilePage) LayoutInflater.from(mContext) 196 .inflate(R.layout.qs_paged_page, this, false)); 197 } 198 } 199 if (DEBUG) Log.d(TAG, "Adding " + tile.tile.getClass().getSimpleName() + " to " 200 + index); 201 mPages.get(index).addTile(tile); 202 } 203 if (mNumPages != index + 1) { 204 mNumPages = index + 1; 205 while (mPages.size() > mNumPages) { 206 mPages.remove(mPages.size() - 1); 207 } 208 if (DEBUG) Log.d(TAG, "Size: " + mNumPages); 209 mPageIndicator.setNumPages(mNumPages); 210 setAdapter(mAdapter); 211 mAdapter.notifyDataSetChanged(); 212 setCurrentItem(0, false); 213 } 214 } 215 216 @Override updateResources()217 public boolean updateResources() { 218 boolean changed = false; 219 for (int i = 0; i < mPages.size(); i++) { 220 changed |= mPages.get(i).updateResources(); 221 } 222 if (changed) { 223 distributeTiles(); 224 } 225 return changed; 226 } 227 228 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)229 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 230 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 231 // The ViewPager likes to eat all of the space, instead force it to wrap to the max height 232 // of the pages. 233 int maxHeight = 0; 234 final int N = getChildCount(); 235 for (int i = 0; i < N; i++) { 236 int height = getChildAt(i).getMeasuredHeight(); 237 if (height > maxHeight) { 238 maxHeight = height; 239 } 240 } 241 setMeasuredDimension(getMeasuredWidth(), maxHeight + mDecorGroup.getMeasuredHeight()); 242 } 243 244 private final Runnable mDistribute = new Runnable() { 245 @Override 246 public void run() { 247 distributeTiles(); 248 } 249 }; 250 getColumnCount()251 public int getColumnCount() { 252 if (mPages.size() == 0) return 0; 253 return mPages.get(0).mColumns; 254 } 255 256 public static class TilePage extends TileLayout { 257 private int mMaxRows = 3; 258 TilePage(Context context, AttributeSet attrs)259 public TilePage(Context context, AttributeSet attrs) { 260 super(context, attrs); 261 updateResources(); 262 setContentDescription(mContext.getString(R.string.accessibility_desc_quick_settings)); 263 } 264 265 @Override updateResources()266 public boolean updateResources() { 267 final int rows = getRows(); 268 boolean changed = rows != mMaxRows; 269 if (changed) { 270 mMaxRows = rows; 271 requestLayout(); 272 } 273 return super.updateResources() || changed; 274 } 275 getRows()276 private int getRows() { 277 final Resources res = getContext().getResources(); 278 if (res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { 279 // Always have 3 rows in portrait. 280 return 3; 281 } 282 return Math.max(1, res.getInteger(R.integer.quick_settings_num_rows)); 283 } 284 setMaxRows(int maxRows)285 public void setMaxRows(int maxRows) { 286 mMaxRows = maxRows; 287 } 288 isFull()289 public boolean isFull() { 290 return mRecords.size() >= mColumns * mMaxRows; 291 } 292 } 293 294 private final PagerAdapter mAdapter = new PagerAdapter() { 295 public void destroyItem(ViewGroup container, int position, Object object) { 296 if (DEBUG) Log.d(TAG, "Destantiating " + position); 297 container.removeView((View) object); 298 } 299 300 public Object instantiateItem(ViewGroup container, int position) { 301 if (DEBUG) Log.d(TAG, "Instantiating " + position); 302 if (isLayoutRtl()) { 303 position = mPages.size() - 1 - position; 304 } 305 ViewGroup view = mPages.get(position); 306 container.addView(view); 307 return view; 308 } 309 310 @Override 311 public int getCount() { 312 return mNumPages; 313 } 314 315 @Override 316 public boolean isViewFromObject(View view, Object object) { 317 return view == object; 318 } 319 }; 320 321 public interface PageListener { onPageChanged(boolean isFirst)322 void onPageChanged(boolean isFirst); 323 } 324 } 325