/* * Copyright (C) 2020 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.wm.shell.onehanded; import android.animation.Animator; import android.animation.ValueAnimator; import android.annotation.IntDef; import android.annotation.Nullable; import android.content.Context; import android.graphics.Rect; import android.view.SurfaceControl; import android.view.animation.BaseInterpolator; import android.window.WindowContainerToken; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.HashMap; import java.util.List; /** * Controller class of OneHanded animations (both from and to OneHanded mode). */ public class OneHandedAnimationController { private static final String TAG = "OneHandedAnimationController"; private static final float FRACTION_START = 0f; private static final float FRACTION_END = 1f; public static final int TRANSITION_DIRECTION_NONE = 0; public static final int TRANSITION_DIRECTION_TRIGGER = 1; public static final int TRANSITION_DIRECTION_EXIT = 2; @IntDef(prefix = {"TRANSITION_DIRECTION_"}, value = { TRANSITION_DIRECTION_NONE, TRANSITION_DIRECTION_TRIGGER, TRANSITION_DIRECTION_EXIT, }) @Retention(RetentionPolicy.SOURCE) public @interface TransitionDirection { } private final OneHandedInterpolator mInterpolator; private final OneHandedSurfaceTransactionHelper mSurfaceTransactionHelper; private final HashMap mAnimatorMap = new HashMap<>(); /** * Constructor of OneHandedAnimationController */ public OneHandedAnimationController(Context context) { mSurfaceTransactionHelper = new OneHandedSurfaceTransactionHelper(context); mInterpolator = new OneHandedInterpolator(); } @SuppressWarnings("unchecked") OneHandedTransitionAnimator getAnimator(WindowContainerToken token, SurfaceControl leash, float startPos, float endPos, Rect displayBounds) { final OneHandedTransitionAnimator animator = mAnimatorMap.get(token); if (animator == null) { mAnimatorMap.put(token, setupOneHandedTransitionAnimator( OneHandedTransitionAnimator.ofYOffset(token, leash, startPos, endPos, displayBounds))); } else if (animator.isRunning()) { animator.updateEndValue(endPos); } else { animator.cancel(); mAnimatorMap.put(token, setupOneHandedTransitionAnimator( OneHandedTransitionAnimator.ofYOffset(token, leash, startPos, endPos, displayBounds))); } return mAnimatorMap.get(token); } HashMap getAnimatorMap() { return mAnimatorMap; } boolean isAnimatorsConsumed() { return mAnimatorMap.isEmpty(); } void removeAnimator(WindowContainerToken token) { final OneHandedTransitionAnimator animator = mAnimatorMap.remove(token); if (animator != null && animator.isRunning()) { animator.cancel(); } } OneHandedTransitionAnimator setupOneHandedTransitionAnimator( OneHandedTransitionAnimator animator) { animator.setSurfaceTransactionHelper(mSurfaceTransactionHelper); animator.setInterpolator(mInterpolator); animator.setFloatValues(FRACTION_START, FRACTION_END); return animator; } /** * Animator for OneHanded transition animation which supports both alpha and bounds animation. */ // TODO: Refactoring to use SpringAnimation and DynamicAnimation instead of using ValueAnimator // to implement One-Handed transition animation. (b/185129031) public abstract static class OneHandedTransitionAnimator extends ValueAnimator implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener { private final SurfaceControl mLeash; private final WindowContainerToken mToken; private float mStartValue; private float mEndValue; private float mCurrentValue; private final List mOneHandedAnimationCallbacks = new ArrayList<>(); private OneHandedSurfaceTransactionHelper mSurfaceTransactionHelper; private OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory mSurfaceControlTransactionFactory; private @TransitionDirection int mTransitionDirection; private OneHandedTransitionAnimator(WindowContainerToken token, SurfaceControl leash, float startValue, float endValue) { mLeash = leash; mToken = token; mStartValue = startValue; mEndValue = endValue; addListener(this); addUpdateListener(this); mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new; mTransitionDirection = TRANSITION_DIRECTION_NONE; } @Override public void onAnimationStart(Animator animation) { mCurrentValue = mStartValue; mOneHandedAnimationCallbacks.forEach( (callback) -> callback.onOneHandedAnimationStart(this) ); } @Override public void onAnimationEnd(Animator animation) { mCurrentValue = mEndValue; final SurfaceControl.Transaction tx = newSurfaceControlTransaction(); onEndTransaction(mLeash, tx); mOneHandedAnimationCallbacks.forEach( (callback) -> callback.onOneHandedAnimationEnd(tx, this) ); mOneHandedAnimationCallbacks.clear(); } @Override public void onAnimationCancel(Animator animation) { mCurrentValue = mEndValue; mOneHandedAnimationCallbacks.forEach( (callback) -> callback.onOneHandedAnimationCancel(this) ); mOneHandedAnimationCallbacks.clear(); } @Override public void onAnimationRepeat(Animator animation) { } @Override public void onAnimationUpdate(ValueAnimator animation) { final SurfaceControl.Transaction tx = newSurfaceControlTransaction(); mOneHandedAnimationCallbacks.forEach( (callback) -> callback.onAnimationUpdate(tx, 0f, mCurrentValue) ); applySurfaceControlTransaction(mLeash, tx, animation.getAnimatedFraction()); } void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) { } void onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) { } abstract void applySurfaceControlTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, float fraction); OneHandedSurfaceTransactionHelper getSurfaceTransactionHelper() { return mSurfaceTransactionHelper; } void setSurfaceTransactionHelper(OneHandedSurfaceTransactionHelper helper) { mSurfaceTransactionHelper = helper; } OneHandedTransitionAnimator addOneHandedAnimationCallback( @Nullable OneHandedAnimationCallback callback) { if (callback != null) { mOneHandedAnimationCallbacks.add(callback); } return this; } WindowContainerToken getToken() { return mToken; } float getDestinationOffset() { return (mEndValue - mStartValue); } @TransitionDirection int getTransitionDirection() { return mTransitionDirection; } OneHandedTransitionAnimator setTransitionDirection(int direction) { mTransitionDirection = direction; return this; } float getStartValue() { return mStartValue; } float getEndValue() { return mEndValue; } void setCurrentValue(float value) { mCurrentValue = value; } /** * Updates the {@link #mEndValue}. */ void updateEndValue(float endValue) { mEndValue = endValue; } SurfaceControl.Transaction newSurfaceControlTransaction() { return mSurfaceControlTransactionFactory.getTransaction(); } @VisibleForTesting static OneHandedTransitionAnimator ofYOffset(WindowContainerToken token, SurfaceControl leash, float startValue, float endValue, Rect displayBounds) { return new OneHandedTransitionAnimator(token, leash, startValue, endValue) { private final Rect mTmpRect = new Rect(displayBounds); private float getCastedFractionValue(float start, float end, float fraction) { return (start * (1 - fraction) + end * fraction + .5f); } @Override void applySurfaceControlTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, float fraction) { final float start = getStartValue(); final float end = getEndValue(); final float currentValue = getCastedFractionValue(start, end, fraction); mTmpRect.set( mTmpRect.left, mTmpRect.top + Math.round(currentValue), mTmpRect.right, mTmpRect.bottom + Math.round(currentValue)); setCurrentValue(currentValue); getSurfaceTransactionHelper() .crop(tx, leash, mTmpRect) .round(tx, leash) .translate(tx, leash, currentValue); tx.apply(); } @Override void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) { getSurfaceTransactionHelper() .crop(tx, leash, mTmpRect) .round(tx, leash) .translate(tx, leash, getStartValue()); tx.apply(); } }; } } /** * An Interpolator for One-Handed transition animation. */ public class OneHandedInterpolator extends BaseInterpolator { @Override public float getInterpolation(float input) { return (float) (Math.pow(2, -10 * input) * Math.sin(((input - 4.0f) / 4.0f) * (2.0f * Math.PI) / 4.0f) + 1.0f); } } void dump(@NonNull PrintWriter pw) { final String innerPrefix = " "; pw.println(TAG + "states: "); pw.print(innerPrefix + "mAnimatorMap="); pw.println(mAnimatorMap); if (mSurfaceTransactionHelper != null) { mSurfaceTransactionHelper.dump(pw); } } }