diff options
5 files changed, 240 insertions, 29 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java index ee573fbeaf33..396d317e8954 100644 --- a/packages/SystemUI/src/com/android/systemui/Prefs.java +++ b/packages/SystemUI/src/com/android/systemui/Prefs.java @@ -24,6 +24,7 @@ import android.content.SharedPreferences.OnSharedPreferenceChangeListener; 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 @@ public final class Prefs { 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 @@ public final class Prefs { 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 f3417dc078c2..ea3a60b93246 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.support.v4.view.ViewPager; 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 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { 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 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { } } + @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 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { 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 5758762b06a0..29f3c43a1fa4 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 @@ public class QSFragment extends Fragment implements QS { // 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 143ad21c998c..61e3065fd4a3 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 class QSPanel extends LinearLayout implements Tunable, Callback, Brightne 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 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne addView(mPageIndicator); ((PagedTileLayout) mTileLayout).setPageIndicator((PageIndicator) mPageIndicator); + mQsTileRevealController = new QSTileRevealController(mContext, this, + ((PagedTileLayout) mTileLayout)); addDivider(); @@ -136,6 +139,10 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne return mPageIndicator; } + public QSTileRevealController getQsTileRevealController() { + return mQsTileRevealController; + } + public boolean isShowingCustomize() { return mCustomizePanel != null && mCustomizePanel.isCustomizing(); } @@ -352,6 +359,9 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne } 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 000000000000..2f012e6e608e --- /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); + } +} |