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