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