• 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             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(tileView.getIcon());
193                 mAllViews.add(tileView.getIcon());
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 
isIconInAnimatedRow(int count)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 
getRelativePosition(int[] loc1, View view, View parent)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 
getRelativePositionInt(int[] loc1, View view, View parent)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 
setPosition(float position)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
onAnimationAtStart()306     public void onAnimationAtStart() {
307         mQuickQsPanel.setVisibility(View.VISIBLE);
308     }
309 
310     @Override
onAnimationAtEnd()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
onAnimationStarted()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 
clearAnimationState()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
onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)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
onTilesChanged()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