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 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 72 public void onRtlPropertiesChanged(int layoutDirection) { 73 super.onRtlPropertiesChanged(layoutDirection); 74 setAdapter(mAdapter); 75 setCurrentItem(0, false); 76 } 77 78 @Override 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 87 public void setListening(boolean listening) { 88 if (mListening == listening) return; 89 mListening = listening; 90 if (mListening) { 91 setPageListening(mPosition, true); 92 if (mOffPage) { 93 setPageListening(mPosition + 1, true); 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 */ 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 132 private void setPageListening(int position, boolean listening) { 133 if (position >= mPages.size()) return; 134 if (isLayoutRtl()) { 135 position = mPages.size() - 1 - position; 136 } 137 mPages.get(position).setListening(listening); 138 } 139 140 @Override 141 public boolean hasOverlappingRendering() { 142 return false; 143 } 144 145 @Override 146 protected void onFinishInflate() { 147 super.onFinishInflate(); 148 mPageIndicator = (PageIndicator) findViewById(R.id.page_indicator); 149 mDecorGroup = findViewById(R.id.page_decor); 150 ((LayoutParams) mDecorGroup.getLayoutParams()).isDecor = true; 151 152 mPages.add((TilePage) LayoutInflater.from(mContext) 153 .inflate(R.layout.qs_paged_page, this, false)); 154 } 155 156 @Override 157 public int getOffsetTop(TileRecord tile) { 158 final ViewGroup parent = (ViewGroup) tile.tileView.getParent(); 159 if (parent == null) return 0; 160 return parent.getTop() + getTop(); 161 } 162 163 @Override 164 public void addTile(TileRecord tile) { 165 mTiles.add(tile); 166 postDistributeTiles(); 167 } 168 169 @Override 170 public void removeTile(TileRecord tile) { 171 if (mTiles.remove(tile)) { 172 postDistributeTiles(); 173 } 174 } 175 176 public void setPageListener(PageListener listener) { 177 mPageListener = listener; 178 } 179 180 private void postDistributeTiles() { 181 removeCallbacks(mDistribute); 182 post(mDistribute); 183 } 184 185 private void distributeTiles() { 186 if (DEBUG) Log.d(TAG, "Distributing tiles"); 187 final int NP = mPages.size(); 188 for (int i = 0; i < NP; i++) { 189 mPages.get(i).removeAllViews(); 190 } 191 int index = 0; 192 final int NT = mTiles.size(); 193 for (int i = 0; i < NT; i++) { 194 TileRecord tile = mTiles.get(i); 195 if (mPages.get(index).isFull()) { 196 if (++index == mPages.size()) { 197 if (DEBUG) Log.d(TAG, "Adding page for " 198 + tile.tile.getClass().getSimpleName()); 199 mPages.add((TilePage) LayoutInflater.from(mContext) 200 .inflate(R.layout.qs_paged_page, this, false)); 201 } 202 } 203 if (DEBUG) Log.d(TAG, "Adding " + tile.tile.getClass().getSimpleName() + " to " 204 + index); 205 mPages.get(index).addTile(tile); 206 } 207 if (mNumPages != index + 1) { 208 mNumPages = index + 1; 209 while (mPages.size() > mNumPages) { 210 mPages.remove(mPages.size() - 1); 211 } 212 if (DEBUG) Log.d(TAG, "Size: " + mNumPages); 213 mPageIndicator.setNumPages(mNumPages); 214 mDecorGroup.setVisibility(mNumPages > 1 ? View.VISIBLE : View.GONE); 215 setAdapter(mAdapter); 216 mAdapter.notifyDataSetChanged(); 217 setCurrentItem(0, false); 218 } 219 } 220 221 @Override 222 public boolean updateResources() { 223 boolean changed = false; 224 for (int i = 0; i < mPages.size(); i++) { 225 changed |= mPages.get(i).updateResources(); 226 } 227 if (changed) { 228 distributeTiles(); 229 } 230 return changed; 231 } 232 233 @Override 234 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 235 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 236 // The ViewPager likes to eat all of the space, instead force it to wrap to the max height 237 // of the pages. 238 int maxHeight = 0; 239 final int N = getChildCount(); 240 for (int i = 0; i < N; i++) { 241 int height = getChildAt(i).getMeasuredHeight(); 242 if (height > maxHeight) { 243 maxHeight = height; 244 } 245 } 246 setMeasuredDimension(getMeasuredWidth(), maxHeight 247 + (mDecorGroup.getVisibility() != View.GONE ? mDecorGroup.getMeasuredHeight() : 0)); 248 } 249 250 private final Runnable mDistribute = new Runnable() { 251 @Override 252 public void run() { 253 distributeTiles(); 254 } 255 }; 256 257 public int getColumnCount() { 258 if (mPages.size() == 0) return 0; 259 return mPages.get(0).mColumns; 260 } 261 262 public static class TilePage extends TileLayout { 263 private int mMaxRows = 3; 264 265 public TilePage(Context context, AttributeSet attrs) { 266 super(context, attrs); 267 updateResources(); 268 setContentDescription(mContext.getString(R.string.accessibility_desc_quick_settings)); 269 } 270 271 @Override 272 public boolean updateResources() { 273 final int rows = getRows(); 274 boolean changed = rows != mMaxRows; 275 if (changed) { 276 mMaxRows = rows; 277 requestLayout(); 278 } 279 return super.updateResources() || changed; 280 } 281 282 private int getRows() { 283 final Resources res = getContext().getResources(); 284 if (res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { 285 // Always have 3 rows in portrait. 286 return 3; 287 } 288 return Math.max(1, res.getInteger(R.integer.quick_settings_num_rows)); 289 } 290 291 public void setMaxRows(int maxRows) { 292 mMaxRows = maxRows; 293 } 294 295 public boolean isFull() { 296 return mRecords.size() >= mColumns * mMaxRows; 297 } 298 } 299 300 private final PagerAdapter mAdapter = new PagerAdapter() { 301 public void destroyItem(ViewGroup container, int position, Object object) { 302 if (DEBUG) Log.d(TAG, "Destantiating " + position); 303 container.removeView((View) object); 304 } 305 306 public Object instantiateItem(ViewGroup container, int position) { 307 if (DEBUG) Log.d(TAG, "Instantiating " + position); 308 if (isLayoutRtl()) { 309 position = mPages.size() - 1 - position; 310 } 311 ViewGroup view = mPages.get(position); 312 container.addView(view); 313 return view; 314 } 315 316 @Override 317 public int getCount() { 318 return mNumPages; 319 } 320 321 @Override 322 public boolean isViewFromObject(View view, Object object) { 323 return view == object; 324 } 325 }; 326 327 public interface PageListener { 328 void onPageChanged(boolean isFirst); 329 } 330 } 331