/* * 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 The {@link DialogContainerSupplier} to use */ public class DialogAnimationController { // 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_ALPHA = new IntProperty( "alpha") { @Override public void setValue(Drawable object, int value) { object.setAlpha(value); } @Override public Integer get(Drawable object) { return object.getAlpha(); } }; }