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