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 = .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 QSContainer mQsContainer;
     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(QSContainer container, QuickQSPanel quickPanel, QSPanel panel) {
     71         mQsContainer = container;
     72         mQuickQsPanel = quickPanel;
     73         mQsPanel = panel;
     74         mQsPanel.addOnAttachStateChangeListener(this);
     75         container.addOnLayoutChangeListener(this);
     76         QSTileLayout tileLayout = mQsPanel.getTileLayout();
     77         if (tileLayout instanceof PagedTileLayout) {
     78             mPagedLayout = ((PagedTileLayout) tileLayout);
     79             mPagedLayout.setPageListener(this);
     80         } else {
     81             Log.w(TAG, "QS Not using page layout");
     82         }
     83     }
     84 
     85     public void onRtlChanged() {
     86         updateAnimators();
     87     }
     88 
     89     public void setOnKeyguard(boolean onKeyguard) {
     90         mOnKeyguard = onKeyguard;
     91         mQuickQsPanel.setVisibility(mOnKeyguard ? View.INVISIBLE : View.VISIBLE);
     92         if (mOnKeyguard) {
     93             clearAnimationState();
     94         }
     95     }
     96 
     97     public void setHost(QSTileHost qsh) {
     98         mHost = qsh;
     99         qsh.addCallback(this);
    100         updateAnimators();
    101     }
    102 
    103     @Override
    104     public void onViewAttachedToWindow(View v) {
    105         TunerService.get(mQsContainer.getContext()).addTunable(this, ALLOW_FANCY_ANIMATION,
    106                 MOVE_FULL_ROWS, QuickQSPanel.NUM_QUICK_TILES);
    107     }
    108 
    109     @Override
    110     public void onViewDetachedFromWindow(View v) {
    111         if (mHost != null) {
    112             mHost.removeCallback(this);
    113         }
    114         TunerService.get(mQsContainer.getContext()).removeTunable(this);
    115     }
    116 
    117     @Override
    118     public void onTuningChanged(String key, String newValue) {
    119         if (ALLOW_FANCY_ANIMATION.equals(key)) {
    120             mAllowFancy = newValue == null || Integer.parseInt(newValue) != 0;
    121             if (!mAllowFancy) {
    122                 clearAnimationState();
    123             }
    124         } else if (MOVE_FULL_ROWS.equals(key)) {
    125             mFullRows = newValue == null || Integer.parseInt(newValue) != 0;
    126         } else if (QuickQSPanel.NUM_QUICK_TILES.equals(key)) {
    127             mNumQuickTiles = mQuickQsPanel.getNumQuickTiles(mQsContainer.getContext());
    128             clearAnimationState();
    129         }
    130         updateAnimators();
    131     }
    132 
    133     @Override
    134     public void onPageChanged(boolean isFirst) {
    135         if (mOnFirstPage == isFirst) return;
    136         if (!isFirst) {
    137             clearAnimationState();
    138         }
    139         mOnFirstPage = isFirst;
    140     }
    141 
    142     private void updateAnimators() {
    143         TouchAnimator.Builder firstPageBuilder = new Builder();
    144         TouchAnimator.Builder translationXBuilder = new Builder();
    145         TouchAnimator.Builder translationYBuilder = new Builder();
    146 
    147         if (mQsPanel.getHost() == null) return;
    148         Collection<QSTile<?>> tiles = mQsPanel.getHost().getTiles();
    149         int count = 0;
    150         int[] loc1 = new int[2];
    151         int[] loc2 = new int[2];
    152         int lastXDiff = 0;
    153         int lastX = 0;
    154 
    155         clearAnimationState();
    156         mAllViews.clear();
    157         mTopFiveQs.clear();
    158 
    159         mAllViews.add((View) mQsPanel.getTileLayout());
    160 
    161         for (QSTile<?> tile : tiles) {
    162             QSTileBaseView tileView = mQsPanel.getTileView(tile);
    163             if (tileView == null) {
    164                 Log.e(TAG, "tileView is null " + tile.getTileSpec());
    165                 continue;
    166             }
    167             final TextView label = ((QSTileView) tileView).getLabel();
    168             final View tileIcon = tileView.getIcon().getIconView();
    169             if (count < mNumQuickTiles && mAllowFancy) {
    170                 // Quick tiles.
    171                 QSTileBaseView quickTileView = mQuickQsPanel.getTileView(tile);
    172 
    173                 lastX = loc1[0];
    174                 getRelativePosition(loc1, quickTileView.getIcon(), mQsContainer);
    175                 getRelativePosition(loc2, tileIcon, mQsContainer);
    176                 final int xDiff = loc2[0] - loc1[0];
    177                 final int yDiff = loc2[1] - loc1[1];
    178                 lastXDiff = loc1[0] - lastX;
    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                 firstPageBuilder.addFloat(tileView, "alpha", 0, 1);
    213             }
    214             mAllViews.add(tileView);
    215             mAllViews.add(label);
    216             count++;
    217         }
    218         if (mAllowFancy) {
    219             // Make brightness appear static position and alpha in through second half.
    220             View brightness = mQsPanel.getBrightnessView();
    221             if (brightness != null) {
    222                 firstPageBuilder.addFloat(brightness, "translationY", mQsPanel.getHeight(), 0);
    223                 mBrightnessAnimator = new TouchAnimator.Builder()
    224                         .addFloat(brightness, "alpha", 0, 1)
    225                         .setStartDelay(.5f)
    226                         .build();
    227                 mAllViews.add(brightness);
    228             } else {
    229                 mBrightnessAnimator = null;
    230             }
    231             mFirstPageAnimator = firstPageBuilder
    232                     .setListener(this)
    233                     .build();
    234             // Fade in the tiles/labels as we reach the final position.
    235             mFirstPageDelayedAnimator = new TouchAnimator.Builder()
    236                     .setStartDelay(EXPANDED_TILE_DELAY)
    237                     .addFloat(mQsPanel.getTileLayout(), "alpha", 0, 1)
    238                     .addFloat(mQsPanel.getFooter().getView(), "alpha", 0, 1).build();
    239             mAllViews.add(mQsPanel.getFooter().getView());
    240             float px = 0;
    241             float py = 1;
    242             if (tiles.size() <= 3) {
    243                 px = 1;
    244             } else if (tiles.size() <= 6) {
    245                 px = .4f;
    246             }
    247             PathInterpolatorBuilder interpolatorBuilder = new PathInterpolatorBuilder(0, 0, px, py);
    248             translationXBuilder.setInterpolator(interpolatorBuilder.getXInterpolator());
    249             translationYBuilder.setInterpolator(interpolatorBuilder.getYInterpolator());
    250             mTranslationXAnimator = translationXBuilder.build();
    251             mTranslationYAnimator = translationYBuilder.build();
    252         }
    253         mNonfirstPageAnimator = new TouchAnimator.Builder()
    254                 .addFloat(mQuickQsPanel, "alpha", 1, 0)
    255                 .setListener(mNonFirstPageListener)
    256                 .setEndDelay(.5f)
    257                 .build();
    258     }
    259 
    260     private boolean isIconInAnimatedRow(int count) {
    261         if (mPagedLayout == null) {
    262             return false;
    263         }
    264         final int columnCount = mPagedLayout.getColumnCount();
    265         return count < ((mNumQuickTiles + columnCount - 1) / columnCount) * columnCount;
    266     }
    267 
    268     private void getRelativePosition(int[] loc1, View view, View parent) {
    269         loc1[0] = 0 + view.getWidth() / 2;
    270         loc1[1] = 0;
    271         getRelativePositionInt(loc1, view, parent);
    272     }
    273 
    274     private void getRelativePositionInt(int[] loc1, View view, View parent) {
    275         if(view == parent || view == null) return;
    276         // Ignore tile pages as they can have some offset we don't want to take into account in
    277         // RTL.
    278         if (!(view instanceof PagedTileLayout.TilePage)) {
    279             loc1[0] += view.getLeft();
    280             loc1[1] += view.getTop();
    281         }
    282         getRelativePositionInt(loc1, (View) view.getParent(), parent);
    283     }
    284 
    285     public void setPosition(float position) {
    286         if (mFirstPageAnimator == null) return;
    287         if (mOnKeyguard) {
    288             return;
    289         }
    290         mLastPosition = position;
    291         if (mOnFirstPage && mAllowFancy) {
    292             mQuickQsPanel.setAlpha(1);
    293             mFirstPageAnimator.setPosition(position);
    294             mFirstPageDelayedAnimator.setPosition(position);
    295             mTranslationXAnimator.setPosition(position);
    296             mTranslationYAnimator.setPosition(position);
    297             if (mBrightnessAnimator != null) {
    298                 mBrightnessAnimator.setPosition(position);
    299             }
    300         } else {
    301             mNonfirstPageAnimator.setPosition(position);
    302         }
    303     }
    304 
    305     @Override
    306     public void onAnimationAtStart() {
    307         mQuickQsPanel.setVisibility(View.VISIBLE);
    308     }
    309 
    310     @Override
    311     public void onAnimationAtEnd() {
    312         mQuickQsPanel.setVisibility(View.INVISIBLE);
    313         final int N = mTopFiveQs.size();
    314         for (int i = 0; i < N; i++) {
    315             mTopFiveQs.get(i).setVisibility(View.VISIBLE);
    316         }
    317     }
    318 
    319     @Override
    320     public void onAnimationStarted() {
    321         mQuickQsPanel.setVisibility(mOnKeyguard ? View.INVISIBLE : View.VISIBLE);
    322         if (mOnFirstPage) {
    323             final int N = mTopFiveQs.size();
    324             for (int i = 0; i < N; i++) {
    325                 mTopFiveQs.get(i).setVisibility(View.INVISIBLE);
    326             }
    327         }
    328     }
    329 
    330     private void clearAnimationState() {
    331         final int N = mAllViews.size();
    332         mQuickQsPanel.setAlpha(0);
    333         for (int i = 0; i < N; i++) {
    334             View v = mAllViews.get(i);
    335             v.setAlpha(1);
    336             v.setTranslationX(0);
    337             v.setTranslationY(0);
    338         }
    339         final int N2 = mTopFiveQs.size();
    340         for (int i = 0; i < N2; i++) {
    341             mTopFiveQs.get(i).setVisibility(View.VISIBLE);
    342         }
    343     }
    344 
    345     @Override
    346     public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
    347             int oldTop, int oldRight, int oldBottom) {
    348         mQsPanel.post(mUpdateAnimators);
    349     }
    350 
    351     @Override
    352     public void onTilesChanged() {
    353         // Give the QS panels a moment to generate their new tiles, then create all new animators
    354         // hooked up to the new views.
    355         mQsPanel.post(mUpdateAnimators);
    356     }
    357 
    358     private final TouchAnimator.Listener mNonFirstPageListener =
    359             new TouchAnimator.ListenerAdapter() {
    360                 @Override
    361                 public void onAnimationStarted() {
    362                     mQuickQsPanel.setVisibility(View.VISIBLE);
    363                 }
    364             };
    365 
    366     private Runnable mUpdateAnimators = new Runnable() {
    367         @Override
    368         public void run() {
    369             updateAnimators();
    370             setPosition(mLastPosition);
    371         }
    372     };
    373 }
    374