| /* |
| * 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<WindowContainerToken, OneHandedTransitionAnimator> 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<WindowContainerToken, OneHandedTransitionAnimator> 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<OneHandedAnimationCallback> 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); |
| } |
| } |
| } |