Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f4a36820 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Import launcher style reveal animations to QS."

parents 8f79aca4 a07a17bc
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -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();
    }
+141 −28
Original line number Diff line number Diff line
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();
+1 −0
Original line number Diff line number Diff line
@@ -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);

+11 −1
Original line number Diff line number Diff line
@@ -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);
+76 −0
Original line number Diff line number Diff line
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);
    }
}