Home | History | Annotate | Download | only in qs
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
      5  * except in compliance with the License. You may obtain a copy of the License at
      6  *
      7  *      http://www.apache.org/licenses/LICENSE-2.0
      8  *
      9  * Unless required by applicable law or agreed to in writing, software distributed under the
     10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     11  * KIND, either express or implied. See the License for the specific language governing
     12  * permissions and limitations under the License.
     13  */
     14 
     15 package com.android.systemui.qs;
     16 
     17 import android.util.Log;
     18 import android.view.View;
     19 import android.view.View.OnAttachStateChangeListener;
     20 import android.view.View.OnLayoutChangeListener;
     21 
     22 import com.android.systemui.Dependency;
     23 import com.android.systemui.plugins.qs.*;
     24 import com.android.systemui.plugins.qs.QSTileView;
     25 import com.android.systemui.qs.PagedTileLayout.PageListener;
     26 import com.android.systemui.qs.QSPanel.QSTileLayout;
     27 import com.android.systemui.qs.QSHost.Callback;
     28 import com.android.systemui.qs.TouchAnimator.Builder;
     29 import com.android.systemui.qs.TouchAnimator.Listener;
     30 import com.android.systemui.tuner.TunerService;
     31 import com.android.systemui.tuner.TunerService.Tunable;
     32 
     33 import java.util.ArrayList;
     34 import java.util.Collection;
     35 
     36 public class QSAnimator implements Callback, PageListener, Listener, OnLayoutChangeListener,
     37         OnAttachStateChangeListener, Tunable {
     38 
     39     private static final String TAG = "QSAnimator";
     40 
     41     private static final String ALLOW_FANCY_ANIMATION = "sysui_qs_fancy_anim";
     42     private static final String MOVE_FULL_ROWS = "sysui_qs_move_whole_rows";
     43 
     44     public static final float EXPANDED_TILE_DELAY = .86f;
     45 
     46     private final ArrayList<View> mAllViews = new ArrayList<>();
     47     private final ArrayList<View> mTopFiveQs = new ArrayList<>();
     48     private final QuickQSPanel mQuickQsPanel;
     49     private final QSPanel mQsPanel;
     50     private final QS mQs;
     51 
     52     private PagedTileLayout mPagedLayout;
     53 
     54     private boolean mOnFirstPage = true;
     55     private TouchAnimator mFirstPageAnimator;
     56     private TouchAnimator mFirstPageDelayedAnimator;
     57     private TouchAnimator mTranslationXAnimator;
     58     private TouchAnimator mTranslationYAnimator;
     59     private TouchAnimator mNonfirstPageAnimator;
     60     private TouchAnimator mBrightnessAnimator;
     61 
     62     private boolean mOnKeyguard;
     63 
     64     private boolean mAllowFancy;
     65     private boolean mFullRows;
     66     private int mNumQuickTiles;
     67     private float mLastPosition;
     68     private QSTileHost mHost;
     69 
     70     public QSAnimator(QS qs, QuickQSPanel quickPanel, QSPanel panel) {
     71         mQs = qs;
     72         mQuickQsPanel = quickPanel;
     73         mQsPanel = panel;
     74         mQsPanel.addOnAttachStateChangeListener(this);
     75         qs.getView().addOnLayoutChangeListener(this);
     76         if (mQsPanel.isAttachedToWindow()) {
     77             onViewAttachedToWindow(null);
     78         }
     79         QSTileLayout tileLayout = mQsPanel.getTileLayout();
     80         if (tileLayout instanceof PagedTileLayout) {
     81             mPagedLayout = ((PagedTileLayout) tileLayout);
     82             mPagedLayout.setPageListener(this);
     83         } else {
     84             Log.w(TAG, "QS Not using page layout");
     85         }
     86     }
     87 
     88     public void onRtlChanged() {
     89         updateAnimators();
     90     }
     91 
     92     public void setOnKeyguard(boolean onKeyguard) {
     93         mOnKeyguard = onKeyguard;
     94         mQuickQsPanel.setVisibility(mOnKeyguard ? View.INVISIBLE : View.VISIBLE);
     95         if (mOnKeyguard) {
     96             clearAnimationState();
     97         }
     98     }
     99 
    100     public void setHost(QSTileHost qsh) {
    101         mHost = qsh;
    102         qsh.addCallback(this);
    103         updateAnimators();
    104     }
    105 
    106     @Override
    107     public void onViewAttachedToWindow(View v) {
    108         Dependency.get(TunerService.class).addTunable(this, ALLOW_FANCY_ANIMATION,
    109                 MOVE_FULL_ROWS, QuickQSPanel.NUM_QUICK_TILES);
    110     }
    111 
    112     @Override
    113     public void onViewDetachedFromWindow(View v) {
    114         if (mHost != null) {
    115             mHost.removeCallback(this);
    116         }
    117         Dependency.get(TunerService.class).removeTunable(this);
    118     }
    119 
    120     @Override
    121     public void onTuningChanged(String key, String newValue) {
    122         if (ALLOW_FANCY_ANIMATION.equals(key)) {
    123             mAllowFancy = newValue == null || Integer.parseInt(newValue) != 0;
    124             if (!mAllowFancy) {
    125                 clearAnimationState();
    126             }
    127         } else if (MOVE_FULL_ROWS.equals(key)) {
    128             mFullRows = newValue == null || Integer.parseInt(newValue) != 0;
    129         } else if (QuickQSPanel.NUM_QUICK_TILES.equals(key)) {
    130             mNumQuickTiles = mQuickQsPanel.getNumQuickTiles(mQs.getContext());
    131             clearAnimationState();
    132         }
    133         updateAnimators();
    134     }
    135 
    136     @Override
    137     public void onPageChanged(boolean isFirst) {
    138         if (mOnFirstPage == isFirst) return;
    139         if (!isFirst) {
    140             clearAnimationState();
    141         }
    142         mOnFirstPage = isFirst;
    143     }
    144 
    145     private void updateAnimators() {
    146         TouchAnimator.Builder firstPageBuilder = new Builder();
    147         TouchAnimator.Builder translationXBuilder = new Builder();
    148         TouchAnimator.Builder translationYBuilder = new Builder();
    149 
    150         if (mQsPanel.getHost() == null) return;
    151         Collection<QSTile> tiles = mQsPanel.getHost().getTiles();
    152         int count = 0;
    153         int[] loc1 = new int[2];
    154         int[] loc2 = new int[2];
    155         int lastXDiff = 0;
    156         int lastX = 0;
    157 
    158         clearAnimationState();
    159         mAllViews.clear();
    160         mTopFiveQs.clear();
    161 
    162         QSTileLayout tileLayout = mQsPanel.getTileLayout();
    163         mAllViews.add((View) tileLayout);
    164         int height = mQs.getView() != null ? mQs.getView().getMeasuredHeight() : 0;
    165         int heightDiff = height - mQs.getHeader().getBottom()
    166                 + mQs.getHeader().getPaddingBottom();
    167         firstPageBuilder.addFloat(tileLayout, "translationY", heightDiff, 0);
    168 
    169         for (QSTile tile : tiles) {
    170             QSTileView tileView = mQsPanel.getTileView(tile);
    171             if (tileView == null) {
    172                 Log.e(TAG, "tileView is null " + tile.getTileSpec());
    173                 continue;
    174             }
    175             final View tileIcon = tileView.getIcon().getIconView();
    176             View view = mQs.getView();
    177             if (count < mNumQuickTiles && mAllowFancy) {
    178                 // Quick tiles.
    179                 QSTileView quickTileView = mQuickQsPanel.getTileView(tile);
    180                 if (quickTileView == null) continue;
    181 
    182                 lastX = loc1[0];
    183                 getRelativePosition(loc1, quickTileView.getIcon().getIconView(), view);
    184                 getRelativePosition(loc2, tileIcon, view);
    185                 final int xDiff = loc2[0] - loc1[0];
    186                 final int yDiff = loc2[1] - loc1[1];
    187                 lastXDiff = loc1[0] - lastX;
    188                 // Move the quick tile right from its location to the new one.
    189                 translationXBuilder.addFloat(quickTileView, "translationX", 0, xDiff);
    190                 translationYBuilder.addFloat(quickTileView, "translationY", 0, yDiff);
    191 
    192                 // Counteract the parent translation on the tile. So we have a static base to
    193                 // animate the label position off from.
    194                 //firstPageBuilder.addFloat(tileView, "translationY", mQsPanel.getHeight(), 0);
    195 
    196                 // Move the real tile from the quick tile position to its final
    197                 // location.
    198                 translationXBuilder.addFloat(tileView, "translationX", -xDiff, 0);
    199                 translationYBuilder.addFloat(tileView, "translationY", -yDiff, 0);
    200 
    201                 mTopFiveQs.add(tileView.getIcon());
    202                 mAllViews.add(tileView.getIcon());
    203                 mAllViews.add(quickTileView);
    204             } else if (mFullRows && isIconInAnimatedRow(count)) {
    205                 // TODO: Refactor some of this, it shares a lot with the above block.
    206                 // Move the last tile position over by the last difference between quick tiles.
    207                 // This makes the extra icons seems as if they are coming from positions in the
    208                 // quick panel.
    209                 loc1[0] += lastXDiff;
    210                 getRelativePosition(loc2, tileIcon, view);
    211                 final int xDiff = loc2[0] - loc1[0];
    212                 final int yDiff = loc2[1] - loc1[1];
    213 
    214                 firstPageBuilder.addFloat(tileView, "translationY", heightDiff, 0);
    215                 translationXBuilder.addFloat(tileView, "translationX", -xDiff, 0);
    216                 translationYBuilder.addFloat(tileView, "translationY", -yDiff, 0);
    217                 translationYBuilder.addFloat(tileIcon, "translationY", -yDiff, 0);
    218 
    219                 mAllViews.add(tileIcon);
    220             } else {
    221                 firstPageBuilder.addFloat(tileView, "alpha", 0, 1);
    222                 firstPageBuilder.addFloat(tileView, "translationY", -heightDiff, 0);
    223             }
    224             mAllViews.add(tileView);
    225             count++;
    226         }
    227         if (mAllowFancy) {
    228             // Make brightness appear static position and alpha in through second half.
    229             View brightness = mQsPanel.getBrightnessView();
    230             if (brightness != null) {
    231                 firstPageBuilder.addFloat(brightness, "translationY", heightDiff, 0);
    232                 mBrightnessAnimator = new TouchAnimator.Builder()
    233                         .addFloat(brightness, "alpha", 0, 1)
    234                         .setStartDelay(.5f)
    235                         .build();
    236                 mAllViews.add(brightness);
    237             } else {
    238                 mBrightnessAnimator = null;
    239             }
    240             mFirstPageAnimator = firstPageBuilder
    241                     .setListener(this)
    242                     .build();
    243             // Fade in the tiles/labels as we reach the final position.
    244             mFirstPageDelayedAnimator = new TouchAnimator.Builder()
    245                     .setStartDelay(EXPANDED_TILE_DELAY)
    246                     .addFloat(tileLayout, "alpha", 0, 1)
    247                     .addFloat(mQsPanel.getPageIndicator(), "alpha", 0, 1)
    248                     .addFloat(mQsPanel.getDivider(), "alpha", 0, 1)
    249                     .addFloat(mQsPanel.getFooter().getView(), "alpha", 0, 1).build();
    250             mAllViews.add(mQsPanel.getPageIndicator());
    251             mAllViews.add(mQsPanel.getDivider());
    252             mAllViews.add(mQsPanel.getFooter().getView());
    253             float px = 0;
    254             float py = 1;
    255             if (tiles.size() <= 3) {
    256                 px = 1;
    257             } else if (tiles.size() <= 6) {
    258                 px = .4f;
    259             }
    260             PathInterpolatorBuilder interpolatorBuilder = new PathInterpolatorBuilder(0, 0, px, py);
    261             translationXBuilder.setInterpolator(interpolatorBuilder.getXInterpolator());
    262             translationYBuilder.setInterpolator(interpolatorBuilder.getYInterpolator());
    263             mTranslationXAnimator = translationXBuilder.build();
    264             mTranslationYAnimator = translationYBuilder.build();
    265         }
    266         mNonfirstPageAnimator = new TouchAnimator.Builder()
    267                 .addFloat(mQuickQsPanel, "alpha", 1, 0)
    268                 .addFloat(mQsPanel.getPageIndicator(), "alpha", 0, 1)
    269                 .addFloat(mQsPanel.getDivider(), "alpha", 0, 1)
    270                 .setListener(mNonFirstPageListener)
    271                 .setEndDelay(.5f)
    272                 .build();
    273     }
    274 
    275     private boolean isIconInAnimatedRow(int count) {
    276         if (mPagedLayout == null) {
    277             return false;
    278         }
    279         final int columnCount = mPagedLayout.getColumnCount();
    280         return count < ((mNumQuickTiles + columnCount - 1) / columnCount) * columnCount;
    281     }
    282 
    283     private void getRelativePosition(int[] loc1, View view, View parent) {
    284         loc1[0] = 0 + view.getWidth() / 2;
    285         loc1[1] = 0;
    286         getRelativePositionInt(loc1, view, parent);
    287     }
    288 
    289     private void getRelativePositionInt(int[] loc1, View view, View parent) {
    290         if(view == parent || view == null) return;
    291         // Ignore tile pages as they can have some offset we don't want to take into account in
    292         // RTL.
    293         if (!(view instanceof PagedTileLayout.TilePage)) {
    294             loc1[0] += view.getLeft();
    295             loc1[1] += view.getTop();
    296         }
    297         getRelativePositionInt(loc1, (View) view.getParent(), parent);
    298     }
    299 
    300     public void setPosition(float position) {
    301         if (mFirstPageAnimator == null) return;
    302         if (mOnKeyguard) {
    303             return;
    304         }
    305         mLastPosition = position;
    306         if (mOnFirstPage && mAllowFancy) {
    307             mQuickQsPanel.setAlpha(1);
    308             mFirstPageAnimator.setPosition(position);
    309             mFirstPageDelayedAnimator.setPosition(position);
    310             mTranslationXAnimator.setPosition(position);
    311             mTranslationYAnimator.setPosition(position);
    312             if (mBrightnessAnimator != null) {
    313                 mBrightnessAnimator.setPosition(position);
    314             }
    315         } else {
    316             mNonfirstPageAnimator.setPosition(position);
    317         }
    318     }
    319 
    320     @Override
    321     public void onAnimationAtStart() {
    322         mQuickQsPanel.setVisibility(View.VISIBLE);
    323     }
    324 
    325     @Override
    326     public void onAnimationAtEnd() {
    327         mQuickQsPanel.setVisibility(View.INVISIBLE);
    328         final int N = mTopFiveQs.size();
    329         for (int i = 0; i < N; i++) {
    330             mTopFiveQs.get(i).setVisibility(View.VISIBLE);
    331         }
    332     }
    333 
    334     @Override
    335     public void onAnimationStarted() {
    336         mQuickQsPanel.setVisibility(mOnKeyguard ? View.INVISIBLE : View.VISIBLE);
    337         if (mOnFirstPage) {
    338             final int N = mTopFiveQs.size();
    339             for (int i = 0; i < N; i++) {
    340                 mTopFiveQs.get(i).setVisibility(View.INVISIBLE);
    341             }
    342         }
    343     }
    344 
    345     private void clearAnimationState() {
    346         final int N = mAllViews.size();
    347         mQuickQsPanel.setAlpha(0);
    348         for (int i = 0; i < N; i++) {
    349             View v = mAllViews.get(i);
    350             v.setAlpha(1);
    351             v.setTranslationX(0);
    352             v.setTranslationY(0);
    353         }
    354         final int N2 = mTopFiveQs.size();
    355         for (int i = 0; i < N2; i++) {
    356             mTopFiveQs.get(i).setVisibility(View.VISIBLE);
    357         }
    358     }
    359 
    360     @Override
    361     public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
    362             int oldTop, int oldRight, int oldBottom) {
    363         mQsPanel.post(mUpdateAnimators);
    364     }
    365 
    366     @Override
    367     public void onTilesChanged() {
    368         // Give the QS panels a moment to generate their new tiles, then create all new animators
    369         // hooked up to the new views.
    370         mQsPanel.post(mUpdateAnimators);
    371     }
    372 
    373     private final TouchAnimator.Listener mNonFirstPageListener =
    374             new TouchAnimator.ListenerAdapter() {
    375                 @Override
    376                 public void onAnimationAtEnd() {
    377                     mQuickQsPanel.setVisibility(View.INVISIBLE);
    378                 }
    379 
    380                 @Override
    381                 public void onAnimationStarted() {
    382                     mQuickQsPanel.setVisibility(View.VISIBLE);
    383                 }
    384             };
    385 
    386     private Runnable mUpdateAnimators = new Runnable() {
    387         @Override
    388         public void run() {
    389             updateAnimators();
    390             setPosition(mLastPosition);
    391         }
    392     };
    393 }
    394