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