• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
QSAnimator(QSContainer container, QuickQSPanel quickPanel, QSPanel panel)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 
onRtlChanged()86     public void onRtlChanged() {
87         updateAnimators();
88     }
89 
setOnKeyguard(boolean onKeyguard)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 
setHost(QSTileHost qsh)98     public void setHost(QSTileHost qsh) {
99         mHost = qsh;
100         qsh.addCallback(this);
101         updateAnimators();
102     }
103 
104     @Override
onViewAttachedToWindow(View v)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
onViewDetachedFromWindow(View v)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
onTuningChanged(String key, String newValue)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
onPageChanged(boolean isFirst)135     public void onPageChanged(boolean isFirst) {
136         if (mOnFirstPage == isFirst) return;
137         if (!isFirst) {
138             clearAnimationState();
139         }
140         mOnFirstPage = isFirst;
141     }
142 
updateAnimators()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 
isIconInAnimatedRow(int count)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 
getRelativePosition(int[] loc1, View view, View parent)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 
getRelativePositionInt(int[] loc1, View view, View parent)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 
setPosition(float position)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
onAnimationAtStart()289     public void onAnimationAtStart() {
290         mQuickQsPanel.setVisibility(View.VISIBLE);
291     }
292 
293     @Override
onAnimationAtEnd()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
onAnimationStarted()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 
clearAnimationState()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
onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)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
onTilesChanged()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