Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d414b99d authored by Tom Natan's avatar Tom Natan Committed by Android (Google) Code Review
Browse files

Merge "[7/n] Letterbox Education: add enter/exit animation to the dialog."

parents cd9b0794 4a463aaa
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -22,6 +22,8 @@

    <!-- The background of the top-level layout acts as the background dim. -->

    <!-- Setting the alpha of the dialog container to 0, since it shouldn't be visible until the
         enter animation starts. -->
    <LinearLayout
        android:id="@+id/letterbox_education_dialog_container"
        android:layout_width="wrap_content"
@@ -30,7 +32,8 @@
        android:gravity="center_horizontal"
        android:orientation="vertical"
        android:background="@drawable/letterbox_education_dialog_background"
        android:padding="24dp">
        android:padding="24dp"
        android:alpha="0">

        <ImageView
            android:id="@+id/letterbox_education_icon"
+197 −0
Original line number Diff line number Diff line
/*
 * 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.letterboxedu;

import static com.android.internal.R.styleable.WindowAnimation_windowEnterAnimation;
import static com.android.internal.R.styleable.WindowAnimation_windowExitAnimation;

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 animations of the letterbox education.
 */
// TODO(b/215316431): Add tests
class LetterboxEduAnimationController {
    private static final String TAG = "LetterboxEduAnimation";

    private final TransitionAnimation mTransitionAnimation;
    private final String mPackageName;
    @AnyRes
    private final int mAnimStyleResId;

    @Nullable
    private Animation mDialogAnimation;
    @Nullable
    private Animator mBackgroundDimAnimator;

    LetterboxEduAnimationController(Context context) {
        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();
    }

    /**
     * Starts both background dim fade-in animation and the dialog enter animation.
     */
    void startEnterAnimation(@NonNull LetterboxEduDialogLayout layout, Runnable endCallback) {
        // Cancel any previous animation if it's still running.
        cancelAnimation();

        final View dialogContainer = layout.getDialogContainer();
        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.getBackgroundDim(),
                /* endAlpha= */ LetterboxEduDialogLayout.BACKGROUND_DIM_ALPHA,
                mDialogAnimation.getDuration());
        mBackgroundDimAnimator.addListener(getDimAnimatorListener());

        dialogContainer.startAnimation(mDialogAnimation);
        mBackgroundDimAnimator.start();
    }

    /**
     * Starts both the background dim fade-out animation and the dialog exit animation.
     */
    void startExitAnimation(@Nullable LetterboxEduDialogLayout layout, Runnable endCallback) {
        // Cancel any previous animation if it's still running.
        cancelAnimation();

        if (layout == null) {
            endCallback.run();
            return;
        }

        final View dialogContainer = layout.getDialogContainer();
        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.getBackgroundDim(), /* endAlpha= */ 0,
                mDialogAnimation.getDuration());
        mBackgroundDimAnimator.addListener(getDimAnimatorListener());

        dialogContainer.startAnimation(mDialogAnimation);
        mBackgroundDimAnimator.start();
    }

    /**
     * Cancels all animations and resets the state of the controller.
     */
    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(TAG, "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();
        }
    };
}
+18 −3
Original line number Diff line number Diff line
@@ -17,7 +17,9 @@
package com.android.wm.shell.compatui.letterboxedu;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;

import com.android.wm.shell.R;
@@ -33,9 +35,11 @@ class LetterboxEduDialogLayout extends FrameLayout {

    // The alpha of a background is a number between 0 (fully transparent) to 255 (fully opaque).
    // 204 is simply 255 * 0.8.
    private static final int BACKGROUND_DIM_ALPHA = 204;
    static final int BACKGROUND_DIM_ALPHA = 204;

    private LetterboxEduWindowManager mWindowManager;
    private View mDialogContainer;
    private Drawable mBackgroundDim;

    public LetterboxEduDialogLayout(Context context) {
        this(context, null);
@@ -58,6 +62,14 @@ class LetterboxEduDialogLayout extends FrameLayout {
        mWindowManager = windowManager;
    }

    View getDialogContainer() {
        return mDialogContainer;
    }

    Drawable getBackgroundDim() {
        return mBackgroundDim;
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
@@ -77,12 +89,15 @@ class LetterboxEduDialogLayout extends FrameLayout {
        setOnClickListener(view -> callback.run());
        // We add a no-op on-click listener to the dialog container so that clicks on it won't
        // propagate to the listener of the layout (which represents the background dim).
        findViewById(R.id.letterbox_education_dialog_container).setOnClickListener(view -> {});
        mDialogContainer.setOnClickListener(view -> {});
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        getBackground().mutate().setAlpha(BACKGROUND_DIM_ALPHA);
        mDialogContainer = findViewById(R.id.letterbox_education_dialog_container);
        mBackgroundDim = getBackground().mutate();
        // Set the alpha of the background dim to 0 for enter animation.
        mBackgroundDim.setAlpha(0);
    }
}
+23 −3
Original line number Diff line number Diff line
@@ -55,6 +55,8 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
     */
    private final SharedPreferences mSharedPreferences;

    private final LetterboxEduAnimationController mAnimationController;

    // Remember the last reported state in case visibility changes due to keyguard or IME updates.
    private boolean mEligibleForLetterboxEducation;

@@ -69,6 +71,7 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
        super(context, taskInfo, syncQueue, taskListener, displayLayout);
        mOnDismissCallback = onDismissCallback;
        mEligibleForLetterboxEducation = taskInfo.topActivityEligibleForLetterboxEducation;
        mAnimationController = new LetterboxEduAnimationController(context);
        mSharedPreferences = mContext.getSharedPreferences(HAS_SEEN_LETTERBOX_EDUCATION_PREF_NAME,
                Context.MODE_PRIVATE);
    }
@@ -101,7 +104,9 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
        setSeenLetterboxEducation();
        mLayout = inflateLayout();
        mLayout.inject(this);
        mLayout.setDismissOnClickListener(this::onDismiss);

        mAnimationController.startEnterAnimation(mLayout, /* endCallback= */
                this::setDismissOnClickListener);

        return mLayout;
    }
@@ -111,9 +116,24 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
                R.layout.letterbox_education_dialog_layout, null);
    }

    private void setDismissOnClickListener() {
        if (mLayout == null) {
            return;
        }
        mLayout.setDismissOnClickListener(this::onDismiss);
    }

    private void onDismiss() {
        mAnimationController.startExitAnimation(mLayout, () -> {
            release();
            mOnDismissCallback.run();
        });
    }

    @Override
    public void release() {
        mAnimationController.cancelAnimation();
        super.release();
    }

    @Override