322 lines
12 KiB
Java
322 lines
12 KiB
Java
/*
|
|
* 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);
|
|
}
|
|
}
|
|
}
|