blob: 0403a0505b003366e07c6037cea831b13a7ab448 [file] [log] [blame]
Jason Monkcaf37622015-08-18 12:33:50 -04001package com.android.systemui.qs;
2
Amin Shaikha07a17b2018-02-23 16:02:52 -05003import android.animation.Animator;
4import android.animation.AnimatorListenerAdapter;
5import android.animation.AnimatorSet;
6import android.animation.ObjectAnimator;
7import android.animation.PropertyValuesHolder;
Jason Monkcaf37622015-08-18 12:33:50 -04008import android.content.Context;
Fabian Kozynski407ddb22018-10-03 15:04:56 -04009import android.content.res.Configuration;
Fabian Kozynski6eaf3ac2018-10-01 15:59:05 -040010import android.content.res.Resources;
Fabian Kozynski52bd8c22018-10-08 12:52:51 -040011import android.graphics.Rect;
Fabian Kozynski407ddb22018-10-03 15:04:56 -040012import android.os.Bundle;
Jason Monkcaf37622015-08-18 12:33:50 -040013import android.util.AttributeSet;
14import android.util.Log;
15import android.view.LayoutInflater;
16import android.view.View;
17import android.view.ViewGroup;
Amin Shaikha07a17b2018-02-23 16:02:52 -050018import android.view.animation.Interpolator;
19import android.view.animation.OvershootInterpolator;
20import android.widget.Scroller;
Jason Monkdf36aed2016-07-25 11:21:56 -040021
Fabian Kozynski6eaf3ac2018-10-01 15:59:05 -040022import androidx.viewpager.widget.PagerAdapter;
23import androidx.viewpager.widget.ViewPager;
24
Jason Monkcaf37622015-08-18 12:33:50 -040025import com.android.systemui.R;
26import com.android.systemui.qs.QSPanel.QSTileLayout;
27import com.android.systemui.qs.QSPanel.TileRecord;
28
29import java.util.ArrayList;
Amin Shaikha07a17b2018-02-23 16:02:52 -050030import java.util.Set;
Jason Monkcaf37622015-08-18 12:33:50 -040031
32public class PagedTileLayout extends ViewPager implements QSTileLayout {
33
34 private static final boolean DEBUG = false;
Fabian Kozynski407ddb22018-10-03 15:04:56 -040035 private static final String CURRENT_PAGE = "current_page";
Jason Monkcaf37622015-08-18 12:33:50 -040036
37 private static final String TAG = "PagedTileLayout";
Amin Shaikha07a17b2018-02-23 16:02:52 -050038 private static final int REVEAL_SCROLL_DURATION_MILLIS = 750;
39 private static final float BOUNCE_ANIMATION_TENSION = 1.3f;
40 private static final long BOUNCE_ANIMATION_DURATION = 450L;
41 private static final int TILE_ANIMATION_STAGGER_DELAY = 85;
42 private static final Interpolator SCROLL_CUBIC = (t) -> {
43 t -= 1.0f;
44 return t * t * t + 1.0f;
45 };
46
Amin Shaikh9978c552018-03-22 07:24:41 -040047 private final ArrayList<TileRecord> mTiles = new ArrayList<>();
48 private final ArrayList<TilePage> mPages = new ArrayList<>();
Jason Monkcaf37622015-08-18 12:33:50 -040049
Jason Monkcaf37622015-08-18 12:33:50 -040050 private PageIndicator mPageIndicator;
Rohan Shah3090e792018-04-12 00:01:00 -040051 private float mPageIndicatorPosition;
Jason Monkcaf37622015-08-18 12:33:50 -040052
Jason Monk162011e2016-02-19 08:11:55 -050053 private PageListener mPageListener;
Jason Monkcaf37622015-08-18 12:33:50 -040054
Jason Monke5107a32016-05-31 15:40:58 -040055 private boolean mListening;
Amin Shaikha07a17b2018-02-23 16:02:52 -050056 private Scroller mScroller;
57
58 private AnimatorSet mBounceAnimatorSet;
Amin Shaikh4c9048c2018-04-20 11:27:46 -040059 private float mLastExpansion;
Fabian Kozynski712ae392018-09-12 09:11:16 -040060 private boolean mDistributeTiles = false;
Fabian Kozynski407ddb22018-10-03 15:04:56 -040061 private int mPageToRestore = -1;
62 private int mLayoutOrientation;
63 private int mLayoutDirection;
Fabian Kozynski52bd8c22018-10-08 12:52:51 -040064 private int mHorizontalClipBound;
Fabian Kozynski87bdf0d2019-04-01 09:52:57 -040065 private final Rect mClippingRect;
Fabian Kozynski72cd84a92019-04-15 14:09:43 -040066 private int mLastMaxHeight = -1;
Jason Monke5107a32016-05-31 15:40:58 -040067
Jason Monkcaf37622015-08-18 12:33:50 -040068 public PagedTileLayout(Context context, AttributeSet attrs) {
69 super(context, attrs);
Amin Shaikha07a17b2018-02-23 16:02:52 -050070 mScroller = new Scroller(context, SCROLL_CUBIC);
Jason Monkcaf37622015-08-18 12:33:50 -040071 setAdapter(mAdapter);
Amin Shaikha07a17b2018-02-23 16:02:52 -050072 setOnPageChangeListener(mOnPageChangeListener);
73 setCurrentItem(0, false);
Fabian Kozynski407ddb22018-10-03 15:04:56 -040074 mLayoutOrientation = getResources().getConfiguration().orientation;
75 mLayoutDirection = getLayoutDirection();
Fabian Kozynski87bdf0d2019-04-01 09:52:57 -040076 mClippingRect = new Rect();
Fabian Kozynski407ddb22018-10-03 15:04:56 -040077 }
78
79 public void saveInstanceState(Bundle outState) {
80 outState.putInt(CURRENT_PAGE, getCurrentItem());
81 }
82
83 public void restoreInstanceState(Bundle savedInstanceState) {
84 // There's only 1 page at this point. We want to restore the correct page once the
85 // pages have been inflated
86 mPageToRestore = savedInstanceState.getInt(CURRENT_PAGE, -1);
87 }
88
89 @Override
90 protected void onConfigurationChanged(Configuration newConfig) {
91 super.onConfigurationChanged(newConfig);
92 if (mLayoutOrientation != newConfig.orientation) {
93 mLayoutOrientation = newConfig.orientation;
94 setCurrentItem(0, false);
Fabian Kozynskid9e8aa32019-04-12 10:09:48 -040095 mPageToRestore = 0;
Fabian Kozynski407ddb22018-10-03 15:04:56 -040096 }
Jason Monkcaf37622015-08-18 12:33:50 -040097 }
98
99 @Override
Jason Monk51fb85a2016-03-28 14:06:04 -0400100 public void onRtlPropertiesChanged(int layoutDirection) {
101 super.onRtlPropertiesChanged(layoutDirection);
Fabian Kozynski407ddb22018-10-03 15:04:56 -0400102 if (mLayoutDirection != layoutDirection) {
103 mLayoutDirection = layoutDirection;
104 setAdapter(mAdapter);
105 setCurrentItem(0, false);
Fabian Kozynskid9e8aa32019-04-12 10:09:48 -0400106 mPageToRestore = 0;
Fabian Kozynski407ddb22018-10-03 15:04:56 -0400107 }
Jason Monk51fb85a2016-03-28 14:06:04 -0400108 }
109
110 @Override
111 public void setCurrentItem(int item, boolean smoothScroll) {
112 if (isLayoutRtl()) {
113 item = mPages.size() - 1 - item;
114 }
115 super.setCurrentItem(item, smoothScroll);
116 }
117
Fabian Kozynskid9e8aa32019-04-12 10:09:48 -0400118 /**
119 * Obtains the current page number respecting RTL
120 */
121 private int getCurrentPageNumber() {
122 int page = getCurrentItem();
123 if (mLayoutDirection == LAYOUT_DIRECTION_RTL) {
124 page = mPages.size() - 1 - page;
125 }
126 return page;
127 }
128
Jason Monk51fb85a2016-03-28 14:06:04 -0400129 @Override
Jason Monke5107a32016-05-31 15:40:58 -0400130 public void setListening(boolean listening) {
131 if (mListening == listening) return;
132 mListening = listening;
Amin Shaikh9978c552018-03-22 07:24:41 -0400133 updateListening();
134 }
135
136 private void updateListening() {
137 for (TilePage tilePage : mPages) {
138 tilePage.setListening(tilePage.getParent() == null ? false : mListening);
Jason Monke5107a32016-05-31 15:40:58 -0400139 }
140 }
141
Amin Shaikha07a17b2018-02-23 16:02:52 -0500142 @Override
Amin Shaikha07a17b2018-02-23 16:02:52 -0500143 public void computeScroll() {
144 if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
Amin Shaikh5484d0f2018-07-31 10:44:11 -0400145 fakeDragBy(getScrollX() - mScroller.getCurrX());
Amin Shaikha07a17b2018-02-23 16:02:52 -0500146 // Keep on drawing until the animation has finished.
147 postInvalidateOnAnimation();
148 return;
Amin Shaikh5484d0f2018-07-31 10:44:11 -0400149 } else if (isFakeDragging()) {
150 endFakeDrag();
Amin Shaikha07a17b2018-02-23 16:02:52 -0500151 mBounceAnimatorSet.start();
152 setOffscreenPageLimit(1);
Amin Shaikha07a17b2018-02-23 16:02:52 -0500153 }
154 super.computeScroll();
155 }
156
Jason Monke5107a32016-05-31 15:40:58 -0400157 @Override
Jason Monkc5bdafb2016-02-25 16:24:21 -0500158 public boolean hasOverlappingRendering() {
159 return false;
160 }
161
162 @Override
Jason Monkcaf37622015-08-18 12:33:50 -0400163 protected void onFinishInflate() {
164 super.onFinishInflate();
Jason Monk32508852017-01-18 09:17:13 -0500165 mPages.add((TilePage) LayoutInflater.from(getContext())
Jason Monke4e69302016-01-20 11:27:15 -0500166 .inflate(R.layout.qs_paged_page, this, false));
Fabian Kozynskifae42a82018-10-04 11:18:42 -0400167 mAdapter.notifyDataSetChanged();
Jason Monkcaf37622015-08-18 12:33:50 -0400168 }
169
Jason Monk32508852017-01-18 09:17:13 -0500170 public void setPageIndicator(PageIndicator indicator) {
171 mPageIndicator = indicator;
Fabian Kozynski712ae392018-09-12 09:11:16 -0400172 mPageIndicator.setNumPages(mPages.size());
Rohan Shah3090e792018-04-12 00:01:00 -0400173 mPageIndicator.setLocation(mPageIndicatorPosition);
Jason Monk32508852017-01-18 09:17:13 -0500174 }
175
Jason Monkcaf37622015-08-18 12:33:50 -0400176 @Override
177 public int getOffsetTop(TileRecord tile) {
Jason Monkae5bd032016-03-02 14:38:31 -0500178 final ViewGroup parent = (ViewGroup) tile.tileView.getParent();
179 if (parent == null) return 0;
180 return parent.getTop() + getTop();
Jason Monkcaf37622015-08-18 12:33:50 -0400181 }
182
183 @Override
Jason Monkcaf37622015-08-18 12:33:50 -0400184 public void addTile(TileRecord tile) {
185 mTiles.add(tile);
Fabian Kozynski712ae392018-09-12 09:11:16 -0400186 mDistributeTiles = true;
187 requestLayout();
Jason Monkcaf37622015-08-18 12:33:50 -0400188 }
189
190 @Override
191 public void removeTile(TileRecord tile) {
192 if (mTiles.remove(tile)) {
Fabian Kozynski712ae392018-09-12 09:11:16 -0400193 mDistributeTiles = true;
194 requestLayout();
Jason Monkcaf37622015-08-18 12:33:50 -0400195 }
196 }
197
Amin Shaikh0f8ea5432018-03-27 11:09:27 -0400198 @Override
199 public void setExpansion(float expansion) {
Amin Shaikh4c9048c2018-04-20 11:27:46 -0400200 mLastExpansion = expansion;
201 updateSelected();
202 }
203
204 private void updateSelected() {
205 // Start the marquee when fully expanded and stop when fully collapsed. Leave as is for
206 // other expansion ratios since there is no way way to pause the marquee.
207 if (mLastExpansion > 0f && mLastExpansion < 1f) {
208 return;
209 }
210 boolean selected = mLastExpansion == 1f;
Rohan Shah2dbcb572018-05-25 10:51:22 -0700211
212 // Disable accessibility temporarily while we update selected state purely for the
213 // marquee. This will ensure that accessibility doesn't announce the TYPE_VIEW_SELECTED
214 // event on any of the children.
215 setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
Fabian Kozynskid9e8aa32019-04-12 10:09:48 -0400216 int currentItem = getCurrentPageNumber();
Amin Shaikh4c9048c2018-04-20 11:27:46 -0400217 for (int i = 0; i < mPages.size(); i++) {
Amin Shaikh6f570742018-06-01 17:18:19 -0400218 mPages.get(i).setSelected(i == currentItem ? selected : false);
Amin Shaikh0f8ea5432018-03-27 11:09:27 -0400219 }
Rohan Shah2dbcb572018-05-25 10:51:22 -0700220 setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
Amin Shaikh0f8ea5432018-03-27 11:09:27 -0400221 }
222
Jason Monk162011e2016-02-19 08:11:55 -0500223 public void setPageListener(PageListener listener) {
224 mPageListener = listener;
225 }
226
Jason Monkcaf37622015-08-18 12:33:50 -0400227 private void distributeTiles() {
Fabian Kozynski712ae392018-09-12 09:11:16 -0400228 emptyAndInflateOrRemovePages();
229
230 final int tileCount = mPages.get(0).maxTiles();
Jason Monkcaf37622015-08-18 12:33:50 -0400231 if (DEBUG) Log.d(TAG, "Distributing tiles");
Jason Monkcaf37622015-08-18 12:33:50 -0400232 int index = 0;
233 final int NT = mTiles.size();
234 for (int i = 0; i < NT; i++) {
235 TileRecord tile = mTiles.get(i);
Fabian Kozynski712ae392018-09-12 09:11:16 -0400236 if (mPages.get(index).mRecords.size() == tileCount) index++;
237 if (DEBUG) {
238 Log.d(TAG, "Adding " + tile.tile.getClass().getSimpleName() + " to "
239 + index);
Jason Monkcaf37622015-08-18 12:33:50 -0400240 }
Jason Monkcaf37622015-08-18 12:33:50 -0400241 mPages.get(index).addTile(tile);
242 }
Fabian Kozynski712ae392018-09-12 09:11:16 -0400243 }
244
245 private void emptyAndInflateOrRemovePages() {
246 final int nTiles = mTiles.size();
Fabian Kozynski6ff5c182019-06-20 14:57:47 -0400247 // We should always have at least one page, even if it's empty.
248 int numPages = Math.max(nTiles / mPages.get(0).maxTiles(), 1);
249
Fabian Kozynski712ae392018-09-12 09:11:16 -0400250 // Add one more not full page if needed
Fabian Kozynski492be7c2019-07-31 14:41:18 -0400251 if (nTiles > numPages * mPages.get(0).maxTiles()) {
252 numPages++;
253 }
Fabian Kozynski712ae392018-09-12 09:11:16 -0400254
255 final int NP = mPages.size();
256 for (int i = 0; i < NP; i++) {
257 mPages.get(i).removeAllViews();
Jason Monkcaf37622015-08-18 12:33:50 -0400258 }
Fabian Kozynski712ae392018-09-12 09:11:16 -0400259 if (NP == numPages) {
260 return;
261 }
262 while (mPages.size() < numPages) {
263 if (DEBUG) Log.d(TAG, "Adding page");
264 mPages.add((TilePage) LayoutInflater.from(getContext())
265 .inflate(R.layout.qs_paged_page, this, false));
266 }
267 while (mPages.size() > numPages) {
268 if (DEBUG) Log.d(TAG, "Removing page");
269 mPages.remove(mPages.size() - 1);
270 }
271 mPageIndicator.setNumPages(mPages.size());
272 setAdapter(mAdapter);
273 mAdapter.notifyDataSetChanged();
Fabian Kozynski407ddb22018-10-03 15:04:56 -0400274 if (mPageToRestore != -1) {
275 setCurrentItem(mPageToRestore, false);
276 mPageToRestore = -1;
277 }
Jason Monkcaf37622015-08-18 12:33:50 -0400278 }
279
280 @Override
Jason Monk9d02a432016-01-20 16:33:46 -0500281 public boolean updateResources() {
Rohan Shah3090e792018-04-12 00:01:00 -0400282 // Update bottom padding, useful for removing extra space once the panel page indicator is
283 // hidden.
Fabian Kozynski6eaf3ac2018-10-01 15:59:05 -0400284 Resources res = getContext().getResources();
Fabian Kozynski52bd8c22018-10-08 12:52:51 -0400285 mHorizontalClipBound = res.getDimensionPixelSize(R.dimen.notification_side_paddings);
286 setPadding(0, 0, 0,
Rohan Shah3090e792018-04-12 00:01:00 -0400287 getContext().getResources().getDimensionPixelSize(
288 R.dimen.qs_paged_tile_layout_padding_bottom));
Jason Monk9d02a432016-01-20 16:33:46 -0500289 boolean changed = false;
Jason Monkcaf37622015-08-18 12:33:50 -0400290 for (int i = 0; i < mPages.size(); i++) {
Jason Monk9d02a432016-01-20 16:33:46 -0500291 changed |= mPages.get(i).updateResources();
Jason Monkcaf37622015-08-18 12:33:50 -0400292 }
Jason Monk9d02a432016-01-20 16:33:46 -0500293 if (changed) {
Fabian Kozynski712ae392018-09-12 09:11:16 -0400294 mDistributeTiles = true;
295 requestLayout();
Jason Monk9d02a432016-01-20 16:33:46 -0500296 }
297 return changed;
Jason Monkcaf37622015-08-18 12:33:50 -0400298 }
299
300 @Override
Fabian Kozynski52bd8c22018-10-08 12:52:51 -0400301 protected void onLayout(boolean changed, int l, int t, int r, int b) {
302 super.onLayout(changed, l, t, r, b);
Fabian Kozynski87bdf0d2019-04-01 09:52:57 -0400303 mClippingRect.set(mHorizontalClipBound, 0, (r - l) - mHorizontalClipBound, b - t);
304 setClipBounds(mClippingRect);
Fabian Kozynski52bd8c22018-10-08 12:52:51 -0400305 }
306
307 @Override
Jason Monkcaf37622015-08-18 12:33:50 -0400308 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Fabian Kozynski712ae392018-09-12 09:11:16 -0400309
310 final int nTiles = mTiles.size();
Fabian Kozynski72cd84a92019-04-15 14:09:43 -0400311 // If we have no reason to recalculate the number of rows, skip this step. In particular,
312 // if the height passed by its parent is the same as the last time, we try not to remeasure.
313 if (mDistributeTiles || mLastMaxHeight != MeasureSpec.getSize(heightMeasureSpec)) {
Fabian Kozynski712ae392018-09-12 09:11:16 -0400314
Fabian Kozynski72cd84a92019-04-15 14:09:43 -0400315 mLastMaxHeight = MeasureSpec.getSize(heightMeasureSpec);
Fabian Kozynski712ae392018-09-12 09:11:16 -0400316 // Only change the pages if the number of rows or columns (from updateResources) has
317 // changed or the tiles have changed
318 if (mPages.get(0).updateMaxRows(heightMeasureSpec, nTiles) || mDistributeTiles) {
319 mDistributeTiles = false;
320 distributeTiles();
321 }
322
323 final int nRows = mPages.get(0).mRows;
324 for (int i = 0; i < mPages.size(); i++) {
325 TilePage t = mPages.get(i);
326 t.mRows = nRows;
327 }
328 }
329
Jason Monkcaf37622015-08-18 12:33:50 -0400330 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Fabian Kozynski712ae392018-09-12 09:11:16 -0400331
Jason Monkcaf37622015-08-18 12:33:50 -0400332 // The ViewPager likes to eat all of the space, instead force it to wrap to the max height
333 // of the pages.
334 int maxHeight = 0;
335 final int N = getChildCount();
336 for (int i = 0; i < N; i++) {
337 int height = getChildAt(i).getMeasuredHeight();
338 if (height > maxHeight) {
339 maxHeight = height;
340 }
341 }
Jason Monkf13413e2017-02-15 15:49:32 -0500342 setMeasuredDimension(getMeasuredWidth(), maxHeight + getPaddingBottom());
Jason Monkcaf37622015-08-18 12:33:50 -0400343 }
344
Jason Monk8fb77872016-03-03 16:39:42 -0500345 public int getColumnCount() {
346 if (mPages.size() == 0) return 0;
347 return mPages.get(0).mColumns;
348 }
349
Fabian Kozynski802279f2018-09-07 13:44:54 -0400350 public int getNumVisibleTiles() {
351 if (mPages.size() == 0) return 0;
Fabian Kozynskid9e8aa32019-04-12 10:09:48 -0400352 TilePage currentPage = mPages.get(getCurrentPageNumber());
Fabian Kozynski802279f2018-09-07 13:44:54 -0400353 return currentPage.mRecords.size();
354 }
355
Amin Shaikha07a17b2018-02-23 16:02:52 -0500356 public void startTileReveal(Set<String> tileSpecs, final Runnable postAnimation) {
Amin Shaikh5484d0f2018-07-31 10:44:11 -0400357 if (tileSpecs.isEmpty() || mPages.size() < 2 || getScrollX() != 0 || !beginFakeDrag()) {
Amin Shaikha07a17b2018-02-23 16:02:52 -0500358 // Do not start the reveal animation unless there are tiles to animate, multiple
359 // TilePages available and the user has not already started dragging.
360 return;
361 }
362
363 final int lastPageNumber = mPages.size() - 1;
364 final TilePage lastPage = mPages.get(lastPageNumber);
365 final ArrayList<Animator> bounceAnims = new ArrayList<>();
366 for (TileRecord tr : lastPage.mRecords) {
367 if (tileSpecs.contains(tr.tile.getTileSpec())) {
368 bounceAnims.add(setupBounceAnimator(tr.tileView, bounceAnims.size()));
369 }
370 }
371
372 if (bounceAnims.isEmpty()) {
373 // All tileSpecs are on the first page. Nothing to do.
374 // TODO: potentially show a bounce animation for first page QS tiles
Amin Shaikh5484d0f2018-07-31 10:44:11 -0400375 endFakeDrag();
Amin Shaikha07a17b2018-02-23 16:02:52 -0500376 return;
377 }
378
379 mBounceAnimatorSet = new AnimatorSet();
380 mBounceAnimatorSet.playTogether(bounceAnims);
381 mBounceAnimatorSet.addListener(new AnimatorListenerAdapter() {
382 @Override
383 public void onAnimationEnd(Animator animation) {
384 mBounceAnimatorSet = null;
385 postAnimation.run();
386 }
387 });
Amin Shaikh5484d0f2018-07-31 10:44:11 -0400388 setOffscreenPageLimit(lastPageNumber); // Ensure the page to reveal has been inflated.
389 int dx = getWidth() * lastPageNumber;
390 mScroller.startScroll(getScrollX(), getScrollY(), isLayoutRtl() ? -dx : dx, 0,
391 REVEAL_SCROLL_DURATION_MILLIS);
Amin Shaikha07a17b2018-02-23 16:02:52 -0500392 postInvalidateOnAnimation();
393 }
394
395 private static Animator setupBounceAnimator(View view, int ordinal) {
396 view.setAlpha(0f);
397 view.setScaleX(0f);
398 view.setScaleY(0f);
399 ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view,
400 PropertyValuesHolder.ofFloat(View.ALPHA, 1),
401 PropertyValuesHolder.ofFloat(View.SCALE_X, 1),
402 PropertyValuesHolder.ofFloat(View.SCALE_Y, 1));
403 animator.setDuration(BOUNCE_ANIMATION_DURATION);
404 animator.setStartDelay(ordinal * TILE_ANIMATION_STAGGER_DELAY);
405 animator.setInterpolator(new OvershootInterpolator(BOUNCE_ANIMATION_TENSION));
406 return animator;
407 }
408
409 private final ViewPager.OnPageChangeListener mOnPageChangeListener =
410 new ViewPager.SimpleOnPageChangeListener() {
411 @Override
412 public void onPageSelected(int position) {
Amin Shaikh4c9048c2018-04-20 11:27:46 -0400413 updateSelected();
Amin Shaikha07a17b2018-02-23 16:02:52 -0500414 if (mPageIndicator == null) return;
415 if (mPageListener != null) {
416 mPageListener.onPageChanged(isLayoutRtl() ? position == mPages.size() - 1
417 : position == 0);
418 }
419 }
420
421 @Override
422 public void onPageScrolled(int position, float positionOffset,
423 int positionOffsetPixels) {
424 if (mPageIndicator == null) return;
Rohan Shah3090e792018-04-12 00:01:00 -0400425 mPageIndicatorPosition = position + positionOffset;
426 mPageIndicator.setLocation(mPageIndicatorPosition);
Amin Shaikha07a17b2018-02-23 16:02:52 -0500427 if (mPageListener != null) {
428 mPageListener.onPageChanged(positionOffsetPixels == 0 &&
429 (isLayoutRtl() ? position == mPages.size() - 1 : position == 0));
430 }
431 }
432 };
433
Jason Monkcaf37622015-08-18 12:33:50 -0400434 public static class TilePage extends TileLayout {
Fabian Kozynski712ae392018-09-12 09:11:16 -0400435
Jason Monkcaf37622015-08-18 12:33:50 -0400436 public TilePage(Context context, AttributeSet attrs) {
437 super(context, attrs);
Jason Monkbd6dbb02015-09-03 15:46:25 -0400438 }
439
Jason Monkbd6dbb02015-09-03 15:46:25 -0400440 public boolean isFull() {
Fabian Kozynski6ff5c182019-06-20 14:57:47 -0400441 return mRecords.size() >= maxTiles();
Fabian Kozynski712ae392018-09-12 09:11:16 -0400442 }
443
444 public int maxTiles() {
Fabian Kozynski6ff5c182019-06-20 14:57:47 -0400445 // Each page should be able to hold at least one tile. If there's not enough room to
446 // show even 1 or there are no tiles, it probably means we are in the middle of setting
447 // up.
448 return Math.max(mColumns * mRows, 1);
Jason Monkcaf37622015-08-18 12:33:50 -0400449 }
Fabian Kozynski52bd8c22018-10-08 12:52:51 -0400450
451 @Override
452 public boolean updateResources() {
453 final int sidePadding = getContext().getResources().getDimensionPixelSize(
454 R.dimen.notification_side_paddings);
455 setPadding(sidePadding, 0, sidePadding, 0);
456 return super.updateResources();
457 }
Jason Monkcaf37622015-08-18 12:33:50 -0400458 }
459
460 private final PagerAdapter mAdapter = new PagerAdapter() {
Amin Shaikh9978c552018-03-22 07:24:41 -0400461 @Override
Jason Monkcaf37622015-08-18 12:33:50 -0400462 public void destroyItem(ViewGroup container, int position, Object object) {
463 if (DEBUG) Log.d(TAG, "Destantiating " + position);
Jason Monkcaf37622015-08-18 12:33:50 -0400464 container.removeView((View) object);
Amin Shaikh9978c552018-03-22 07:24:41 -0400465 updateListening();
Jason Monkcaf37622015-08-18 12:33:50 -0400466 }
467
Amin Shaikh9978c552018-03-22 07:24:41 -0400468 @Override
Jason Monkcaf37622015-08-18 12:33:50 -0400469 public Object instantiateItem(ViewGroup container, int position) {
470 if (DEBUG) Log.d(TAG, "Instantiating " + position);
Jason Monk51fb85a2016-03-28 14:06:04 -0400471 if (isLayoutRtl()) {
472 position = mPages.size() - 1 - position;
473 }
Jason Monke4e69302016-01-20 11:27:15 -0500474 ViewGroup view = mPages.get(position);
Amin Shaikh53bc0832018-09-17 15:06:18 -0400475 if (view.getParent() != null) {
476 container.removeView(view);
477 }
Jason Monkcaf37622015-08-18 12:33:50 -0400478 container.addView(view);
Amin Shaikh9978c552018-03-22 07:24:41 -0400479 updateListening();
Jason Monkcaf37622015-08-18 12:33:50 -0400480 return view;
481 }
482
483 @Override
484 public int getCount() {
Fabian Kozynski712ae392018-09-12 09:11:16 -0400485 return mPages.size();
Jason Monkcaf37622015-08-18 12:33:50 -0400486 }
487
488 @Override
489 public boolean isViewFromObject(View view, Object object) {
490 return view == object;
491 }
492 };
Jason Monk162011e2016-02-19 08:11:55 -0500493
494 public interface PageListener {
Jason Monk66eaf312016-02-25 12:29:29 -0500495 void onPageChanged(boolean isFirst);
Jason Monk162011e2016-02-19 08:11:55 -0500496 }
Jason Monkcaf37622015-08-18 12:33:50 -0400497}