blob: aaa4e51ae832198d09a72ac9e19c79b2b7982b03 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.android.systemui.qs;
import android.graphics.Path;
import android.util.Log;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.View.OnLayoutChangeListener;
import android.widget.TextView;
import com.android.systemui.qs.PagedTileLayout.PageListener;
import com.android.systemui.qs.QSPanel.QSTileLayout;
import com.android.systemui.qs.QSTile.Host.Callback;
import com.android.systemui.qs.TouchAnimator.Builder;
import com.android.systemui.qs.TouchAnimator.Listener;
import com.android.systemui.statusbar.phone.QSTileHost;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
import java.util.ArrayList;
import java.util.Collection;
public class QSAnimator implements Callback, PageListener, Listener, OnLayoutChangeListener,
OnAttachStateChangeListener, Tunable {
private static final String TAG = "QSAnimator";
private static final String ALLOW_FANCY_ANIMATION = "sysui_qs_fancy_anim";
private static final String MOVE_FULL_ROWS = "sysui_qs_move_whole_rows";
public static final float EXPANDED_TILE_DELAY = .86f;
private final ArrayList<View> mAllViews = new ArrayList<>();
private final ArrayList<View> mTopFiveQs = new ArrayList<>();
private final QuickQSPanel mQuickQsPanel;
private final QSPanel mQsPanel;
private final QSContainer mQsContainer;
private PagedTileLayout mPagedLayout;
private boolean mOnFirstPage = true;
private TouchAnimator mFirstPageAnimator;
private TouchAnimator mFirstPageDelayedAnimator;
private TouchAnimator mTranslationXAnimator;
private TouchAnimator mTranslationYAnimator;
private TouchAnimator mNonfirstPageAnimator;
private TouchAnimator mBrightnessAnimator;
private boolean mOnKeyguard;
private boolean mAllowFancy;
private boolean mFullRows;
private int mNumQuickTiles;
private float mLastPosition;
private QSTileHost mHost;
public QSAnimator(QSContainer container, QuickQSPanel quickPanel, QSPanel panel) {
mQsContainer = container;
mQuickQsPanel = quickPanel;
mQsPanel = panel;
mQsPanel.addOnAttachStateChangeListener(this);
container.addOnLayoutChangeListener(this);
QSTileLayout tileLayout = mQsPanel.getTileLayout();
if (tileLayout instanceof PagedTileLayout) {
mPagedLayout = ((PagedTileLayout) tileLayout);
mPagedLayout.setPageListener(this);
} else {
Log.w(TAG, "QS Not using page layout");
}
}
public void onRtlChanged() {
updateAnimators();
}
public void setOnKeyguard(boolean onKeyguard) {
mOnKeyguard = onKeyguard;
mQuickQsPanel.setVisibility(mOnKeyguard ? View.INVISIBLE : View.VISIBLE);
if (mOnKeyguard) {
clearAnimationState();
}
}
public void setHost(QSTileHost qsh) {
mHost = qsh;
qsh.addCallback(this);
updateAnimators();
}
@Override
public void onViewAttachedToWindow(View v) {
TunerService.get(mQsContainer.getContext()).addTunable(this, ALLOW_FANCY_ANIMATION,
MOVE_FULL_ROWS, QuickQSPanel.NUM_QUICK_TILES);
}
@Override
public void onViewDetachedFromWindow(View v) {
if (mHost != null) {
mHost.removeCallback(this);
}
TunerService.get(mQsContainer.getContext()).removeTunable(this);
}
@Override
public void onTuningChanged(String key, String newValue) {
if (ALLOW_FANCY_ANIMATION.equals(key)) {
mAllowFancy = newValue == null || Integer.parseInt(newValue) != 0;
if (!mAllowFancy) {
clearAnimationState();
}
} else if (MOVE_FULL_ROWS.equals(key)) {
mFullRows = newValue == null || Integer.parseInt(newValue) != 0;
} else if (QuickQSPanel.NUM_QUICK_TILES.equals(key)) {
mNumQuickTiles = mQuickQsPanel.getNumQuickTiles(mQsContainer.getContext());
clearAnimationState();
}
updateAnimators();
}
@Override
public void onPageChanged(boolean isFirst) {
if (mOnFirstPage == isFirst) return;
if (!isFirst) {
clearAnimationState();
}
mOnFirstPage = isFirst;
}
private void updateAnimators() {
TouchAnimator.Builder firstPageBuilder = new Builder();
TouchAnimator.Builder translationXBuilder = new Builder();
TouchAnimator.Builder translationYBuilder = new Builder();
if (mQsPanel.getHost() == null) return;
Collection<QSTile<?>> tiles = mQsPanel.getHost().getTiles();
int count = 0;
int[] loc1 = new int[2];
int[] loc2 = new int[2];
int lastXDiff = 0;
int lastX = 0;
clearAnimationState();
mAllViews.clear();
mTopFiveQs.clear();
mAllViews.add((View) mQsPanel.getTileLayout());
for (QSTile<?> tile : tiles) {
QSTileBaseView tileView = mQsPanel.getTileView(tile);
final TextView label = ((QSTileView) tileView).getLabel();
final View tileIcon = tileView.getIcon().getIconView();
if (count < mNumQuickTiles && mAllowFancy) {
// Quick tiles.
QSTileBaseView quickTileView = mQuickQsPanel.getTileView(tile);
lastX = loc1[0];
getRelativePosition(loc1, quickTileView.getIcon(), mQsContainer);
getRelativePosition(loc2, tileIcon, mQsContainer);
final int xDiff = loc2[0] - loc1[0];
final int yDiff = loc2[1] - loc1[1];
lastXDiff = loc1[0] - lastX;
// Move the quick tile right from its location to the new one.
translationXBuilder.addFloat(quickTileView, "translationX", 0, xDiff);
translationYBuilder.addFloat(quickTileView, "translationY", 0, yDiff);
// Counteract the parent translation on the tile. So we have a static base to
// animate the label position off from.
firstPageBuilder.addFloat(tileView, "translationY", mQsPanel.getHeight(), 0);
// Move the real tile's label from the quick tile position to its final
// location.
translationXBuilder.addFloat(label, "translationX", -xDiff, 0);
translationYBuilder.addFloat(label, "translationY", -yDiff, 0);
mTopFiveQs.add(tileIcon);
mAllViews.add(tileIcon);
mAllViews.add(quickTileView);
} else if (mFullRows && isIconInAnimatedRow(count)) {
// TODO: Refactor some of this, it shares a lot with the above block.
// Move the last tile position over by the last difference between quick tiles.
// This makes the extra icons seems as if they are coming from positions in the
// quick panel.
loc1[0] += lastXDiff;
getRelativePosition(loc2, tileIcon, mQsContainer);
final int xDiff = loc2[0] - loc1[0];
final int yDiff = loc2[1] - loc1[1];
firstPageBuilder.addFloat(tileView, "translationY", mQsPanel.getHeight(), 0);
translationXBuilder.addFloat(tileView, "translationX", -xDiff, 0);
translationYBuilder.addFloat(label, "translationY", -yDiff, 0);
translationYBuilder.addFloat(tileIcon, "translationY", -yDiff, 0);
mAllViews.add(tileIcon);
} else {
firstPageBuilder.addFloat(tileView, "alpha", 0, 1);
}
mAllViews.add(tileView);
mAllViews.add(label);
count++;
}
if (mAllowFancy) {
// Make brightness appear static position and alpha in through second half.
View brightness = mQsPanel.getBrightnessView();
if (brightness != null) {
firstPageBuilder.addFloat(brightness, "translationY", mQsPanel.getHeight(), 0);
mBrightnessAnimator = new TouchAnimator.Builder()
.addFloat(brightness, "alpha", 0, 1)
.setStartDelay(.5f)
.build();
mAllViews.add(brightness);
} else {
mBrightnessAnimator = null;
}
mFirstPageAnimator = firstPageBuilder
.setListener(this)
.build();
// Fade in the tiles/labels as we reach the final position.
mFirstPageDelayedAnimator = new TouchAnimator.Builder()
.setStartDelay(EXPANDED_TILE_DELAY)
.addFloat(mQsPanel.getTileLayout(), "alpha", 0, 1).build();
Path path = new Path();
path.moveTo(0, 0);
path.cubicTo(0, 0, 0, 1, 1, 1);
PathInterpolatorBuilder interpolatorBuilder = new PathInterpolatorBuilder(0, 0, 0, 1);
translationXBuilder.setInterpolator(interpolatorBuilder.getXInterpolator());
translationYBuilder.setInterpolator(interpolatorBuilder.getYInterpolator());
mTranslationXAnimator = translationXBuilder.build();
mTranslationYAnimator = translationYBuilder.build();
}
mNonfirstPageAnimator = new TouchAnimator.Builder()
.addFloat(mQuickQsPanel, "alpha", 1, 0)
.setListener(mNonFirstPageListener)
.setEndDelay(.5f)
.build();
}
private boolean isIconInAnimatedRow(int count) {
if (mPagedLayout == null) {
return false;
}
final int columnCount = mPagedLayout.getColumnCount();
return count < ((mNumQuickTiles + columnCount - 1) / columnCount) * columnCount;
}
private void getRelativePosition(int[] loc1, View view, View parent) {
loc1[0] = 0 + view.getWidth() / 2;
loc1[1] = 0;
getRelativePositionInt(loc1, view, parent);
}
private void getRelativePositionInt(int[] loc1, View view, View parent) {
if(view == parent || view == null) return;
// Ignore tile pages as they can have some offset we don't want to take into account in
// RTL.
if (!(view instanceof PagedTileLayout.TilePage)) {
loc1[0] += view.getLeft();
loc1[1] += view.getTop();
}
getRelativePositionInt(loc1, (View) view.getParent(), parent);
}
public void setPosition(float position) {
if (mFirstPageAnimator == null) return;
if (mOnKeyguard) {
return;
}
mLastPosition = position;
if (mOnFirstPage && mAllowFancy) {
mQuickQsPanel.setAlpha(1);
mFirstPageAnimator.setPosition(position);
mFirstPageDelayedAnimator.setPosition(position);
mTranslationXAnimator.setPosition(position);
mTranslationYAnimator.setPosition(position);
if (mBrightnessAnimator != null) {
mBrightnessAnimator.setPosition(position);
}
} else {
mNonfirstPageAnimator.setPosition(position);
}
}
@Override
public void onAnimationAtStart() {
mQuickQsPanel.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationAtEnd() {
mQuickQsPanel.setVisibility(View.INVISIBLE);
final int N = mTopFiveQs.size();
for (int i = 0; i < N; i++) {
mTopFiveQs.get(i).setVisibility(View.VISIBLE);
}
}
@Override
public void onAnimationStarted() {
mQuickQsPanel.setVisibility(mOnKeyguard ? View.INVISIBLE : View.VISIBLE);
if (mOnFirstPage) {
final int N = mTopFiveQs.size();
for (int i = 0; i < N; i++) {
mTopFiveQs.get(i).setVisibility(View.INVISIBLE);
}
}
}
private void clearAnimationState() {
final int N = mAllViews.size();
mQuickQsPanel.setAlpha(0);
for (int i = 0; i < N; i++) {
View v = mAllViews.get(i);
v.setAlpha(1);
v.setTranslationX(0);
v.setTranslationY(0);
}
final int N2 = mTopFiveQs.size();
for (int i = 0; i < N2; i++) {
mTopFiveQs.get(i).setVisibility(View.VISIBLE);
}
}
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
int oldTop, int oldRight, int oldBottom) {
mQsPanel.post(mUpdateAnimators);
}
@Override
public void onTilesChanged() {
// Give the QS panels a moment to generate their new tiles, then create all new animators
// hooked up to the new views.
mQsPanel.post(mUpdateAnimators);
}
private final TouchAnimator.Listener mNonFirstPageListener =
new TouchAnimator.ListenerAdapter() {
@Override
public void onAnimationStarted() {
mQuickQsPanel.setVisibility(View.VISIBLE);
}
};
private Runnable mUpdateAnimators = new Runnable() {
@Override
public void run() {
updateAnimators();
setPosition(mLastPosition);
}
};
}