/*
|
* Copyright (C) 2017 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.server.wm;
|
|
import static com.android.server.wm.AnimationAdapter.STATUS_BAR_TRANSITION_DURATION;
|
import static com.android.server.wm.AnimationSpecProto.WINDOW;
|
import static com.android.server.wm.WindowAnimationSpecProto.ANIMATION;
|
import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
|
import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE;
|
|
import android.graphics.Point;
|
import android.graphics.Rect;
|
import android.os.SystemClock;
|
import android.util.proto.ProtoOutputStream;
|
import android.view.SurfaceControl;
|
import android.view.SurfaceControl.Transaction;
|
import android.view.animation.Animation;
|
import android.view.animation.AnimationSet;
|
import android.view.animation.Interpolator;
|
import android.view.animation.Transformation;
|
import android.view.animation.TranslateAnimation;
|
|
import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
|
|
import java.io.PrintWriter;
|
|
/**
|
* Animation spec for regular window animations.
|
*/
|
public class WindowAnimationSpec implements AnimationSpec {
|
|
private Animation mAnimation;
|
private final Point mPosition = new Point();
|
private final ThreadLocal<TmpValues> mThreadLocalTmps = ThreadLocal.withInitial(TmpValues::new);
|
private final boolean mCanSkipFirstFrame;
|
private final boolean mIsAppAnimation;
|
private final Rect mStackBounds = new Rect();
|
private int mStackClipMode;
|
private final Rect mTmpRect = new Rect();
|
private final float mWindowCornerRadius;
|
|
public WindowAnimationSpec(Animation animation, Point position, boolean canSkipFirstFrame,
|
float windowCornerRadius) {
|
this(animation, position, null /* stackBounds */, canSkipFirstFrame, STACK_CLIP_NONE,
|
false /* isAppAnimation */, windowCornerRadius);
|
}
|
|
public WindowAnimationSpec(Animation animation, Point position, Rect stackBounds,
|
boolean canSkipFirstFrame, int stackClipMode, boolean isAppAnimation,
|
float windowCornerRadius) {
|
mAnimation = animation;
|
if (position != null) {
|
mPosition.set(position.x, position.y);
|
}
|
mWindowCornerRadius = windowCornerRadius;
|
mCanSkipFirstFrame = canSkipFirstFrame;
|
mIsAppAnimation = isAppAnimation;
|
mStackClipMode = stackClipMode;
|
if (stackBounds != null) {
|
mStackBounds.set(stackBounds);
|
}
|
}
|
|
@Override
|
public boolean getShowWallpaper() {
|
return mAnimation.getShowWallpaper();
|
}
|
|
@Override
|
public int getBackgroundColor() {
|
return mAnimation.getBackgroundColor();
|
}
|
|
@Override
|
public long getDuration() {
|
return mAnimation.computeDurationHint();
|
}
|
|
@Override
|
public void apply(Transaction t, SurfaceControl leash, long currentPlayTime) {
|
final TmpValues tmp = mThreadLocalTmps.get();
|
tmp.transformation.clear();
|
mAnimation.getTransformation(currentPlayTime, tmp.transformation);
|
tmp.transformation.getMatrix().postTranslate(mPosition.x, mPosition.y);
|
t.setMatrix(leash, tmp.transformation.getMatrix(), tmp.floats);
|
t.setAlpha(leash, tmp.transformation.getAlpha());
|
|
boolean cropSet = false;
|
if (mStackClipMode == STACK_CLIP_NONE) {
|
if (tmp.transformation.hasClipRect()) {
|
t.setWindowCrop(leash, tmp.transformation.getClipRect());
|
cropSet = true;
|
}
|
} else {
|
mTmpRect.set(mStackBounds);
|
if (tmp.transformation.hasClipRect()) {
|
mTmpRect.intersect(tmp.transformation.getClipRect());
|
}
|
t.setWindowCrop(leash, mTmpRect);
|
cropSet = true;
|
}
|
|
// We can only apply rounded corner if a crop is set, as otherwise the value is meaningless,
|
// since it doesn't have anything it's relative to.
|
if (cropSet && mAnimation.hasRoundedCorners() && mWindowCornerRadius > 0) {
|
t.setCornerRadius(leash, mWindowCornerRadius);
|
}
|
}
|
|
@Override
|
public long calculateStatusBarTransitionStartTime() {
|
TranslateAnimation openTranslateAnimation = findTranslateAnimation(mAnimation);
|
if (openTranslateAnimation != null) {
|
|
// Some interpolators are extremely quickly mostly finished, but not completely. For
|
// our purposes, we need to find the fraction for which ther interpolator is mostly
|
// there, and use that value for the calculation.
|
float t = findAlmostThereFraction(openTranslateAnimation.getInterpolator());
|
return SystemClock.uptimeMillis()
|
+ openTranslateAnimation.getStartOffset()
|
+ (long)(openTranslateAnimation.getDuration() * t)
|
- STATUS_BAR_TRANSITION_DURATION;
|
} else {
|
return SystemClock.uptimeMillis();
|
}
|
}
|
|
@Override
|
public boolean canSkipFirstFrame() {
|
return mCanSkipFirstFrame;
|
}
|
|
@Override
|
public boolean needsEarlyWakeup() {
|
return mIsAppAnimation;
|
}
|
|
@Override
|
public void dump(PrintWriter pw, String prefix) {
|
pw.print(prefix); pw.println(mAnimation);
|
}
|
|
@Override
|
public void writeToProtoInner(ProtoOutputStream proto) {
|
final long token = proto.start(WINDOW);
|
proto.write(ANIMATION, mAnimation.toString());
|
proto.end(token);
|
}
|
|
/**
|
* Tries to find a {@link TranslateAnimation} inside the {@code animation}.
|
*
|
* @return the found animation, {@code null} otherwise
|
*/
|
private static TranslateAnimation findTranslateAnimation(Animation animation) {
|
if (animation instanceof TranslateAnimation) {
|
return (TranslateAnimation) animation;
|
} else if (animation instanceof AnimationSet) {
|
AnimationSet set = (AnimationSet) animation;
|
for (int i = 0; i < set.getAnimations().size(); i++) {
|
Animation a = set.getAnimations().get(i);
|
if (a instanceof TranslateAnimation) {
|
return (TranslateAnimation) a;
|
}
|
}
|
}
|
return null;
|
}
|
|
/**
|
* Binary searches for a {@code t} such that there exists a {@code -0.01 < eps < 0.01} for which
|
* {@code interpolator(t + eps) > 0.99}.
|
*/
|
private static float findAlmostThereFraction(Interpolator interpolator) {
|
float val = 0.5f;
|
float adj = 0.25f;
|
while (adj >= 0.01f) {
|
if (interpolator.getInterpolation(val) < 0.99f) {
|
val += adj;
|
} else {
|
val -= adj;
|
}
|
adj /= 2;
|
}
|
return val;
|
}
|
|
private static class TmpValues {
|
final Transformation transformation = new Transformation();
|
final float[] floats = new float[9];
|
}
|
}
|