• 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 = .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 
QSAnimator(QSContainer container, QuickQSPanel quickPanel, QSPanel panel)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 
onRtlChanged()85     public void onRtlChanged() {
86         updateAnimators();
87     }
88 
setOnKeyguard(boolean onKeyguard)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 
setHost(QSTileHost qsh)97     public void setHost(QSTileHost qsh) {
98         mHost = qsh;
99         qsh.addCallback(this);
100         updateAnimators();
101     }
102 
103     @Override
onViewAttachedToWindow(View v)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
onViewDetachedFromWindow(View v)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
onTuningChanged(String key, String newValue)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
onPageChanged(boolean isFirst)134     public void onPageChanged(boolean isFirst) {
135         if (mOnFirstPage == isFirst) return;
136         if (!isFirst) {
137             clearAnimationState();
138         }
139         mOnFirstPage = isFirst;
140     }
141 
updateAnimators()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             final TextView label = ((QSTileView) tileView).getLabel();
164             final View tileIcon = tileView.getIcon().getIconView();
165             if (count < mNumQuickTiles && mAllowFancy) {
166                 // Quick tiles.
167                 QSTileBaseView quickTileView = mQuickQsPanel.getTileView(tile);
168 
169                 lastX = loc1[0];
170                 getRelativePosition(loc1, quickTileView.getIcon(), mQsContainer);
171                 getRelativePosition(loc2, tileIcon, mQsContainer);
172                 final int xDiff = loc2[0] - loc1[0];
173                 final int yDiff = loc2[1] - loc1[1];
174                 lastXDiff = loc1[0] - lastX;
175                 // Move the quick tile right from its location to the new one.
176                 translationXBuilder.addFloat(quickTileView, "translationX", 0, xDiff);
177                 translationYBuilder.addFloat(quickTileView, "translationY", 0, yDiff);
178 
179                 // Counteract the parent translation on the tile. So we have a static base to
180                 // animate the label position off from.
181                 firstPageBuilder.addFloat(tileView, "translationY", mQsPanel.getHeight(), 0);
182 
183                 // Move the real tile's label from the quick tile position to its final
184                 // location.
185                 translationXBuilder.addFloat(label, "translationX", -xDiff, 0);
186                 translationYBuilder.addFloat(label, "translationY", -yDiff, 0);
187 
188                 mTopFiveQs.add(tileIcon);
189                 mAllViews.add(tileIcon);
190                 mAllViews.add(quickTileView);
191             } else if (mFullRows && isIconInAnimatedRow(count)) {
192                 // TODO: Refactor some of this, it shares a lot with the above block.
193                 // Move the last tile position over by the last difference between quick tiles.
194                 // This makes the extra icons seems as if they are coming from positions in the
195                 // quick panel.
196                 loc1[0] += lastXDiff;
197                 getRelativePosition(loc2, tileIcon, mQsContainer);
198                 final int xDiff = loc2[0] - loc1[0];
199                 final int yDiff = loc2[1] - loc1[1];
200 
201                 firstPageBuilder.addFloat(tileView, "translationY", mQsPanel.getHeight(), 0);
202                 translationXBuilder.addFloat(tileView, "translationX", -xDiff, 0);
203                 translationYBuilder.addFloat(label, "translationY", -yDiff, 0);
204                 translationYBuilder.addFloat(tileIcon, "translationY", -yDiff, 0);
205 
206                 mAllViews.add(tileIcon);
207             } else {
208                 firstPageBuilder.addFloat(tileView, "alpha", 0, 1);
209             }
210             mAllViews.add(tileView);
211             mAllViews.add(label);
212             count++;
213         }
214         if (mAllowFancy) {
215             // Make brightness appear static position and alpha in through second half.
216             View brightness = mQsPanel.getBrightnessView();
217             if (brightness != null) {
218                 firstPageBuilder.addFloat(brightness, "translationY", mQsPanel.getHeight(), 0);
219                 mBrightnessAnimator = new TouchAnimator.Builder()
220                         .addFloat(brightness, "alpha", 0, 1)
221                         .setStartDelay(.5f)
222                         .build();
223                 mAllViews.add(brightness);
224             } else {
225                 mBrightnessAnimator = null;
226             }
227             mFirstPageAnimator = firstPageBuilder
228                     .setListener(this)
229                     .build();
230             // Fade in the tiles/labels as we reach the final position.
231             mFirstPageDelayedAnimator = new TouchAnimator.Builder()
232                     .setStartDelay(EXPANDED_TILE_DELAY)
233                     .addFloat(mQsPanel.getTileLayout(), "alpha", 0, 1)
234                     .addFloat(mQsPanel.getFooter().getView(), "alpha", 0, 1).build();
235             mAllViews.add(mQsPanel.getFooter().getView());
236             float px = 0;
237             float py = 1;
238             if (tiles.size() <= 3) {
239                 px = 1;
240             } else if (tiles.size() <= 6) {
241                 px = .4f;
242             }
243             PathInterpolatorBuilder interpolatorBuilder = new PathInterpolatorBuilder(0, 0, px, py);
244             translationXBuilder.setInterpolator(interpolatorBuilder.getXInterpolator());
245             translationYBuilder.setInterpolator(interpolatorBuilder.getYInterpolator());
246             mTranslationXAnimator = translationXBuilder.build();
247             mTranslationYAnimator = translationYBuilder.build();
248         }
249         mNonfirstPageAnimator = new TouchAnimator.Builder()
250                 .addFloat(mQuickQsPanel, "alpha", 1, 0)
251                 .setListener(mNonFirstPageListener)
252                 .setEndDelay(.5f)
253                 .build();
254     }
255 
isIconInAnimatedRow(int count)256     private boolean isIconInAnimatedRow(int count) {
257         if (mPagedLayout == null) {
258             return false;
259         }
260         final int columnCount = mPagedLayout.getColumnCount();
261         return count < ((mNumQuickTiles + columnCount - 1) / columnCount) * columnCount;
262     }
263 
getRelativePosition(int[] loc1, View view, View parent)264     private void getRelativePosition(int[] loc1, View view, View parent) {
265         loc1[0] = 0 + view.getWidth() / 2;
266         loc1[1] = 0;
267         getRelativePositionInt(loc1, view, parent);
268     }
269 
getRelativePositionInt(int[] loc1, View view, View parent)270     private void getRelativePositionInt(int[] loc1, View view, View parent) {
271         if(view == parent || view == null) return;
272         // Ignore tile pages as they can have some offset we don't want to take into account in
273         // RTL.
274         if (!(view instanceof PagedTileLayout.TilePage)) {
275             loc1[0] += view.getLeft();
276             loc1[1] += view.getTop();
277         }
278         getRelativePositionInt(loc1, (View) view.getParent(), parent);
279     }
280 
setPosition(float position)281     public void setPosition(float position) {
282         if (mFirstPageAnimator == null) return;
283         if (mOnKeyguard) {
284             return;
285         }
286         mLastPosition = position;
287         if (mOnFirstPage && mAllowFancy) {
288             mQuickQsPanel.setAlpha(1);
289             mFirstPageAnimator.setPosition(position);
290             mFirstPageDelayedAnimator.setPosition(position);
291             mTranslationXAnimator.setPosition(position);
292             mTranslationYAnimator.setPosition(position);
293             if (mBrightnessAnimator != null) {
294                 mBrightnessAnimator.setPosition(position);
295             }
296         } else {
297             mNonfirstPageAnimator.setPosition(position);
298         }
299     }
300 
301     @Override
onAnimationAtStart()302     public void onAnimationAtStart() {
303         mQuickQsPanel.setVisibility(View.VISIBLE);
304     }
305 
306     @Override
onAnimationAtEnd()307     public void onAnimationAtEnd() {
308         mQuickQsPanel.setVisibility(View.INVISIBLE);
309         final int N = mTopFiveQs.size();
310         for (int i = 0; i < N; i++) {
311             mTopFiveQs.get(i).setVisibility(View.VISIBLE);
312         }
313     }
314 
315     @Override
onAnimationStarted()316     public void onAnimationStarted() {
317         mQuickQsPanel.setVisibility(mOnKeyguard ? View.INVISIBLE : View.VISIBLE);
318         if (mOnFirstPage) {
319             final int N = mTopFiveQs.size();
320             for (int i = 0; i < N; i++) {
321                 mTopFiveQs.get(i).setVisibility(View.INVISIBLE);
322             }
323         }
324     }
325 
clearAnimationState()326     private void clearAnimationState() {
327         final int N = mAllViews.size();
328         mQuickQsPanel.setAlpha(0);
329         for (int i = 0; i < N; i++) {
330             View v = mAllViews.get(i);
331             v.setAlpha(1);
332             v.setTranslationX(0);
333             v.setTranslationY(0);
334         }
335         final int N2 = mTopFiveQs.size();
336         for (int i = 0; i < N2; i++) {
337             mTopFiveQs.get(i).setVisibility(View.VISIBLE);
338         }
339     }
340 
341     @Override
onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)342     public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
343             int oldTop, int oldRight, int oldBottom) {
344         mQsPanel.post(mUpdateAnimators);
345     }
346 
347     @Override
onTilesChanged()348     public void onTilesChanged() {
349         // Give the QS panels a moment to generate their new tiles, then create all new animators
350         // hooked up to the new views.
351         mQsPanel.post(mUpdateAnimators);
352     }
353 
354     private final TouchAnimator.Listener mNonFirstPageListener =
355             new TouchAnimator.ListenerAdapter() {
356                 @Override
357                 public void onAnimationStarted() {
358                     mQuickQsPanel.setVisibility(View.VISIBLE);
359                 }
360             };
361 
362     private Runnable mUpdateAnimators = new Runnable() {
363         @Override
364         public void run() {
365             updateAnimators();
366             setPosition(mLastPosition);
367         }
368     };
369 }
370