blob: 7475feac5b12d601f2ea3179a5dc8e1dcc534f7c [file] [log] [blame]
/*
* Copyright (C) 2022 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.compatui;
import static com.android.internal.R.styleable.WindowAnimation_windowEnterAnimation;
import static com.android.internal.R.styleable.WindowAnimation_windowExitAnimation;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.annotation.AnyRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.IntProperty;
import android.util.Log;
import android.util.Property;
import android.view.ContextThemeWrapper;
import android.view.View;
import android.view.animation.Animation;
import com.android.internal.policy.TransitionAnimation;
/**
* Controls the enter/exit a dialog.
*
* @param <T> The {@link DialogContainerSupplier} to use
*/
public class DialogAnimationController<T extends DialogContainerSupplier> {
// The alpha of a background is a number between 0 (fully transparent) to 255 (fully opaque).
// 204 is simply 255 * 0.8.
static final int BACKGROUND_DIM_ALPHA = 204;
// If shell transitions are enabled, startEnterAnimation will be called after all transitions
// have finished, and therefore the start delay should be shorter.
private static final int ENTER_ANIM_START_DELAY_MILLIS = ENABLE_SHELL_TRANSITIONS ? 300 : 500;
private final TransitionAnimation mTransitionAnimation;
private final String mPackageName;
private final String mTag;
@AnyRes
private final int mAnimStyleResId;
@Nullable
private Animation mDialogAnimation;
@Nullable
private Animator mBackgroundDimAnimator;
public DialogAnimationController(Context context, String tag) {
mTransitionAnimation = new TransitionAnimation(context, /* debug= */ false, tag);
mAnimStyleResId = (new ContextThemeWrapper(context,
android.R.style.ThemeOverlay_Material_Dialog).getTheme()).obtainStyledAttributes(
com.android.internal.R.styleable.Window).getResourceId(
com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
mPackageName = context.getPackageName();
mTag = tag;
}
/**
* Starts both background dim fade-in animation and the dialog enter animation.
*/
public void startEnterAnimation(@NonNull T layout, Runnable endCallback) {
// Cancel any previous animation if it's still running.
cancelAnimation();
final View dialogContainer = layout.getDialogContainerView();
mDialogAnimation = loadAnimation(WindowAnimation_windowEnterAnimation);
if (mDialogAnimation == null) {
endCallback.run();
return;
}
mDialogAnimation.setAnimationListener(getAnimationListener(
/* startCallback= */ () -> dialogContainer.setAlpha(1),
/* endCallback= */ () -> {
mDialogAnimation = null;
endCallback.run();
}));
mBackgroundDimAnimator = getAlphaAnimator(layout.getBackgroundDimDrawable(),
/* endAlpha= */ BACKGROUND_DIM_ALPHA,
mDialogAnimation.getDuration());
mBackgroundDimAnimator.addListener(getDimAnimatorListener());
mDialogAnimation.setStartOffset(ENTER_ANIM_START_DELAY_MILLIS);
mBackgroundDimAnimator.setStartDelay(ENTER_ANIM_START_DELAY_MILLIS);
dialogContainer.startAnimation(mDialogAnimation);
mBackgroundDimAnimator.start();
}
/**
* Starts both the background dim fade-out animation and the dialog exit animation.
*/
public void startExitAnimation(@NonNull T layout, Runnable endCallback) {
// Cancel any previous animation if it's still running.
cancelAnimation();
final View dialogContainer = layout.getDialogContainerView();
mDialogAnimation = loadAnimation(WindowAnimation_windowExitAnimation);
if (mDialogAnimation == null) {
endCallback.run();
return;
}
mDialogAnimation.setAnimationListener(getAnimationListener(
/* startCallback= */ () -> {},
/* endCallback= */ () -> {
dialogContainer.setAlpha(0);
mDialogAnimation = null;
endCallback.run();
}));
mBackgroundDimAnimator = getAlphaAnimator(layout.getBackgroundDimDrawable(),
/* endAlpha= */ 0, mDialogAnimation.getDuration());
mBackgroundDimAnimator.addListener(getDimAnimatorListener());
dialogContainer.startAnimation(mDialogAnimation);
mBackgroundDimAnimator.start();
}
/**
* Cancels all animations and resets the state of the controller.
*/
public void cancelAnimation() {
if (mDialogAnimation != null) {
mDialogAnimation.cancel();
mDialogAnimation = null;
}
if (mBackgroundDimAnimator != null) {
mBackgroundDimAnimator.cancel();
mBackgroundDimAnimator = null;
}
}
private Animation loadAnimation(int animAttr) {
Animation animation = mTransitionAnimation.loadAnimationAttr(mPackageName, mAnimStyleResId,
animAttr, /* translucent= */ false);
if (animation == null) {
Log.e(mTag, "Failed to load animation " + animAttr);
}
return animation;
}
private Animation.AnimationListener getAnimationListener(Runnable startCallback,
Runnable endCallback) {
return new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
startCallback.run();
}
@Override
public void onAnimationEnd(Animation animation) {
endCallback.run();
}
@Override
public void onAnimationRepeat(Animation animation) {}
};
}
private AnimatorListenerAdapter getDimAnimatorListener() {
return new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mBackgroundDimAnimator = null;
}
};
}
private static Animator getAlphaAnimator(
Drawable drawable, int endAlpha, long duration) {
Animator animator = ObjectAnimator.ofInt(drawable, DRAWABLE_ALPHA, endAlpha);
animator.setDuration(duration);
return animator;
}
private static final Property<Drawable, Integer> DRAWABLE_ALPHA = new IntProperty<Drawable>(
"alpha") {
@Override
public void setValue(Drawable object, int value) {
object.setAlpha(value);
}
@Override
public Integer get(Drawable object) {
return object.getAlpha();
}
};
}