Merge "Import launcher style reveal animations to QS."
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index ee573fb..396d317 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -24,6 +24,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Map;
+import java.util.Set;
public final class Prefs {
private Prefs() {} // no instantation
@@ -87,6 +88,7 @@
String NUM_APPS_LAUNCHED = "NumAppsLaunched";
String HAS_SEEN_RECENTS_ONBOARDING = "HasSeenRecentsOnboarding";
String SEEN_RINGER_GUIDANCE_COUNT = "RingerGuidanceCount";
+ String QS_TILE_SPECS_REVEALED = "QsTileSpecsRevealed";
}
public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) {
@@ -121,6 +123,15 @@
get(context).edit().putString(key, value).apply();
}
+ public static void putStringSet(Context context, @Key String key, Set<String> value) {
+ get(context).edit().putStringSet(key, value).apply();
+ }
+
+ public static Set<String> getStringSet(
+ Context context, @Key String key, Set<String> defaultValue) {
+ return get(context).getStringSet(key, defaultValue);
+ }
+
public static Map<String, ?> getAll(Context context) {
return get(context).getAll();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index f3417dc..ea3a60b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -1,5 +1,10 @@
package com.android.systemui.qs;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -8,20 +13,34 @@
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.animation.Interpolator;
+import android.view.animation.OvershootInterpolator;
+import android.widget.Scroller;
import com.android.systemui.R;
import com.android.systemui.qs.QSPanel.QSTileLayout;
import com.android.systemui.qs.QSPanel.TileRecord;
import java.util.ArrayList;
+import java.util.Set;
public class PagedTileLayout extends ViewPager implements QSTileLayout {
private static final boolean DEBUG = false;
private static final String TAG = "PagedTileLayout";
+ private static final int REVEAL_SCROLL_DURATION_MILLIS = 750;
+ private static final float BOUNCE_ANIMATION_TENSION = 1.3f;
+ private static final long BOUNCE_ANIMATION_DURATION = 450L;
+ private static final int TILE_ANIMATION_STAGGER_DELAY = 85;
+ private static final Interpolator SCROLL_CUBIC = (t) -> {
+ t -= 1.0f;
+ return t * t * t + 1.0f;
+ };
+
private final ArrayList<TileRecord> mTiles = new ArrayList<TileRecord>();
private final ArrayList<TilePage> mPages = new ArrayList<TilePage>();
@@ -34,37 +53,17 @@
private int mPosition;
private boolean mOffPage;
private boolean mListening;
+ private Scroller mScroller;
+
+ private AnimatorSet mBounceAnimatorSet;
+ private int mAnimatingToPage = -1;
public PagedTileLayout(Context context, AttributeSet attrs) {
super(context, attrs);
+ mScroller = new Scroller(context, SCROLL_CUBIC);
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);
+ setOnPageChangeListener(mOnPageChangeListener);
+ setCurrentItem(0, false);
}
@Override
@@ -99,6 +98,45 @@
}
}
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ // Suppress all touch event during reveal animation.
+ if (mAnimatingToPage != -1) {
+ return true;
+ }
+ return super.onInterceptTouchEvent(ev);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ // Suppress all touch event during reveal animation.
+ if (mAnimatingToPage != -1) {
+ return true;
+ }
+ return super.onTouchEvent(ev);
+ }
+
+ @Override
+ public void computeScroll() {
+ if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
+ scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
+ float pageFraction = (float) getScrollX() / getWidth();
+ int position = (int) pageFraction;
+ float positionOffset = pageFraction - position;
+ mOnPageChangeListener.onPageScrolled(position, positionOffset, getScrollX());
+ // Keep on drawing until the animation has finished.
+ postInvalidateOnAnimation();
+ return;
+ }
+ if (mAnimatingToPage != -1) {
+ setCurrentItem(mAnimatingToPage, true);
+ mBounceAnimatorSet.start();
+ setOffscreenPageLimit(1);
+ mAnimatingToPage = -1;
+ }
+ super.computeScroll();
+ }
+
/**
* 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
@@ -257,9 +295,84 @@
return mPages.get(0).mColumns;
}
+ public void startTileReveal(Set<String> tileSpecs, final Runnable postAnimation) {
+ if (tileSpecs.isEmpty() || mPages.size() < 2 || getScrollX() != 0) {
+ // Do not start the reveal animation unless there are tiles to animate, multiple
+ // TilePages available and the user has not already started dragging.
+ return;
+ }
+
+ final int lastPageNumber = mPages.size() - 1;
+ final TilePage lastPage = mPages.get(lastPageNumber);
+ final ArrayList<Animator> bounceAnims = new ArrayList<>();
+ for (TileRecord tr : lastPage.mRecords) {
+ if (tileSpecs.contains(tr.tile.getTileSpec())) {
+ bounceAnims.add(setupBounceAnimator(tr.tileView, bounceAnims.size()));
+ }
+ }
+
+ if (bounceAnims.isEmpty()) {
+ // All tileSpecs are on the first page. Nothing to do.
+ // TODO: potentially show a bounce animation for first page QS tiles
+ return;
+ }
+
+ mBounceAnimatorSet = new AnimatorSet();
+ mBounceAnimatorSet.playTogether(bounceAnims);
+ mBounceAnimatorSet.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mBounceAnimatorSet = null;
+ postAnimation.run();
+ }
+ });
+ mAnimatingToPage = lastPageNumber;
+ setOffscreenPageLimit(mAnimatingToPage); // Ensure the page to reveal has been inflated.
+ mScroller.startScroll(getScrollX(), getScrollY(), getWidth() * mAnimatingToPage, 0,
+ REVEAL_SCROLL_DURATION_MILLIS);
+ postInvalidateOnAnimation();
+ }
+
+ private static Animator setupBounceAnimator(View view, int ordinal) {
+ view.setAlpha(0f);
+ view.setScaleX(0f);
+ view.setScaleY(0f);
+ ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view,
+ PropertyValuesHolder.ofFloat(View.ALPHA, 1),
+ PropertyValuesHolder.ofFloat(View.SCALE_X, 1),
+ PropertyValuesHolder.ofFloat(View.SCALE_Y, 1));
+ animator.setDuration(BOUNCE_ANIMATION_DURATION);
+ animator.setStartDelay(ordinal * TILE_ANIMATION_STAGGER_DELAY);
+ animator.setInterpolator(new OvershootInterpolator(BOUNCE_ANIMATION_TENSION));
+ return animator;
+ }
+
+ private final ViewPager.OnPageChangeListener mOnPageChangeListener =
+ new ViewPager.SimpleOnPageChangeListener() {
+ @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));
+ }
+ }
+ };
+
public static class TilePage extends TileLayout {
private int mMaxRows = 3;
-
public TilePage(Context context, AttributeSet attrs) {
super(context, attrs);
updateResources();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 5758762..29f3c43 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -290,6 +290,7 @@
// Let the views animate their contents correctly by giving them the necessary context.
mHeader.setExpansion(mKeyguardShowing, expansion, panelTranslationY);
mFooter.setExpansion(mKeyguardShowing ? 1 : expansion);
+ mQSPanel.getQsTileRevealController().setExpansion(expansion);
mQSPanel.setTranslationY(translationScaleY * heightDiff);
mQSDetail.setFullyExpanded(fullyExpanded);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 143ad21..61e3065 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -60,11 +60,12 @@
public static final String QS_SHOW_HEADER = "qs_show_header";
protected final Context mContext;
- protected final ArrayList<TileRecord> mRecords = new ArrayList<TileRecord>();
+ protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
protected final View mBrightnessView;
private final H mHandler = new H();
private final View mPageIndicator;
private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
+ private final QSTileRevealController mQsTileRevealController;
protected boolean mExpanded;
protected boolean mListening;
@@ -108,6 +109,8 @@
addView(mPageIndicator);
((PagedTileLayout) mTileLayout).setPageIndicator((PageIndicator) mPageIndicator);
+ mQsTileRevealController = new QSTileRevealController(mContext, this,
+ ((PagedTileLayout) mTileLayout));
addDivider();
@@ -136,6 +139,10 @@
return mPageIndicator;
}
+ public QSTileRevealController getQsTileRevealController() {
+ return mQsTileRevealController;
+ }
+
public boolean isShowingCustomize() {
return mCustomizePanel != null && mCustomizePanel.isCustomizing();
}
@@ -352,6 +359,9 @@
}
public void setTiles(Collection<QSTile> tiles, boolean collapsedView) {
+ if (!collapsedView) {
+ mQsTileRevealController.updateRevealedTiles(tiles);
+ }
for (TileRecord record : mRecords) {
mTileLayout.removeTile(record);
record.tile.removeCallback(record.callback);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java
new file mode 100644
index 0000000..2f012e6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java
@@ -0,0 +1,76 @@
+package com.android.systemui.qs;
+
+import static com.android.systemui.Prefs.Key.QS_TILE_SPECS_REVEALED;
+
+import android.content.Context;
+import android.os.Handler;
+import android.util.ArraySet;
+
+import com.android.systemui.Prefs;
+import com.android.systemui.plugins.qs.QSTile;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+public class QSTileRevealController {
+ private static final long QS_REVEAL_TILES_DELAY = 500L;
+
+ private final Context mContext;
+ private final QSPanel mQSPanel;
+ private final PagedTileLayout mPagedTileLayout;
+ private final ArraySet<String> mTilesToReveal = new ArraySet<>();
+ private final Handler mHandler = new Handler();
+
+ private final Runnable mRevealQsTiles = new Runnable() {
+ @Override
+ public void run() {
+ mPagedTileLayout.startTileReveal(mTilesToReveal, () -> {
+ if (mQSPanel.isExpanded()) {
+ addTileSpecsToRevealed(mTilesToReveal);
+ mTilesToReveal.clear();
+ }
+ });
+ }
+ };
+
+ QSTileRevealController(Context context, QSPanel qsPanel, PagedTileLayout pagedTileLayout) {
+ mContext = context;
+ mQSPanel = qsPanel;
+ mPagedTileLayout = pagedTileLayout;
+ }
+
+ public void setExpansion(float expansion) {
+ if (expansion == 1f) {
+ mHandler.postDelayed(mRevealQsTiles, QS_REVEAL_TILES_DELAY);
+ } else {
+ mHandler.removeCallbacks(mRevealQsTiles);
+ }
+ }
+
+ public void updateRevealedTiles(Collection<QSTile> tiles) {
+ ArraySet<String> tileSpecs = new ArraySet<>();
+ for (QSTile tile : tiles) {
+ tileSpecs.add(tile.getTileSpec());
+ }
+
+ final Set<String> revealedTiles = Prefs.getStringSet(
+ mContext, QS_TILE_SPECS_REVEALED, Collections.EMPTY_SET);
+ if (revealedTiles.isEmpty() || mQSPanel.isShowingCustomize()) {
+ // Do not reveal QS tiles the user has upon first load or those that they directly
+ // added through customization.
+ addTileSpecsToRevealed(tileSpecs);
+ } else {
+ // Animate all tiles that the user has not directly added themselves.
+ tileSpecs.removeAll(revealedTiles);
+ mTilesToReveal.addAll(tileSpecs);
+ }
+ }
+
+ private void addTileSpecsToRevealed(ArraySet<String> specs) {
+ final ArraySet<String> revealedTiles = new ArraySet<>(
+ Prefs.getStringSet(mContext, QS_TILE_SPECS_REVEALED, Collections.EMPTY_SET));
+ revealedTiles.addAll(specs);
+ Prefs.putStringSet(mContext, QS_TILE_SPECS_REVEALED, revealedTiles);
+ }
+}