chromium/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/scrim/ScrimCoordinator.java

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.components.browser_ui.widget.scrim;

import android.content.Context;
import android.view.MotionEvent;
import android.view.ViewGroup;

import androidx.annotation.ColorInt;
import androidx.annotation.VisibleForTesting;

import org.chromium.base.supplier.Supplier;
import org.chromium.ui.UiUtils;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;

/**
 * The coordinator for the scrim widget used to bring focus to certain elements on screen.
 *
 * To use the scrim, {@link #showScrim(PropertyModel)} must be called to set the params for
 * how the scrim will behave:
 *
 * PropertyModel model = new PropertyModel.Builder(ScrimProperties.ALL_KEYS)...
 *
 * After that, users can either allow the default animation to run or change the view's alpha
 * manually using {@link #setAlpha(float)}. Once the scrim is done being used,
 * {@link #hideScrim(boolean)} should be called.
 */
public class ScrimCoordinator {
    /** The duration for the scrim animation. */
    private static final int ANIM_DURATION_MS = 300;

    /** A delegate to expose functionality that changes the scrim over the system UI. */
    public interface SystemUiScrimDelegate {
        /**
         * Pass the current scrim color to the relevant system UI elements.
         * @param scrimColor The current base color of the scrim.
         */
        default void setScrimColor(@ColorInt int scrimColor) {
            // Default no-op, since we fallback to R.color.default_scrim_color if this isn't called.
        }

        /**
         * Set the amount of scrim over the status bar. The implementor may choose to not respect
         * the value provided to this method.
         * @param scrimFraction The scrim fraction over the status bar. 0 is completely hidden, 1 is
         *                      completely shown.
         */
        void setStatusBarScrimFraction(float scrimFraction);

        /**
         * Set the amount of scrim over the navigation bar. The implementor may choose to not
         * respect the value provided to this method.
         * @param scrimFraction The scrim fraction over the status bar. 0 is completely hidden, 1 is
         *                      completely shown.
         */
        void setNavigationBarScrimFraction(float scrimFraction);
    }

    /** A mechanism for delegating motion events out to the mediator. */
    interface TouchEventDelegate {
        /**
         * @param event The event that occurred.
         * @return Whether the event was consumed.
         */
        boolean onTouchEvent(MotionEvent event);
    }

    /** A supplier of new {@link ScrimView}s to use when {@link #showScrim(PropertyModel)} is called. */
    private final Supplier<ScrimView> mScrimViewBuilder;

    /** The component's mediator for handling animation and model management. */
    private final ScrimMediator mMediator;

    /**
     * A handle to the view to bind models to. This should otherwise remain untouched. Each time
     * {@link #showScrim(PropertyModel)} is called, this view is recreated, so all old state is
     * discarded.
     */
    private ScrimView mView;

    /** A handle to the object pushing updates from the model to the view. */
    private PropertyModelChangeProcessor mChangeProcessor;

    /**
     * @param context An Android {@link Context} for creating the view.
     * @param systemUiScrimDelegate A means of changing the scrim over the system UI.
     * @param parent The {@link ViewGroup} the scrim should exist in.
     * @param defaultColor The default color of the scrim.
     */
    public ScrimCoordinator(
            Context context,
            SystemUiScrimDelegate systemUiScrimDelegate,
            ViewGroup parent,
            @ColorInt int defaultColor) {
        mMediator =
                new ScrimMediator(
                        () -> {
                            if (mChangeProcessor != null) mChangeProcessor.destroy();
                            if (mView != null) UiUtils.removeViewFromParent(mView);
                            mView = null;
                            mChangeProcessor = null;
                        },
                        systemUiScrimDelegate);
        mScrimViewBuilder =
                () -> {
                    ScrimView view = new ScrimView(context, parent, defaultColor, mMediator);
                    return view;
                };
    }

    /**
     * Show the scrim.
     * @param model The property model of {@link ScrimProperties} that define the scrim behavior.
     */
    public void showScrim(PropertyModel model) {
        assert model != null : "Showing the scrim requires a model.";

        // Ensure the previous scrim is hidden before showing the new one. This logic should be in
        // the mediator, but it depends on the old view and binder being available which are
        // replaced prior to mediator#showScrim being called.
        if (mMediator.isActive()) mMediator.hideScrim(false, ANIM_DURATION_MS);

        if (mChangeProcessor != null) mChangeProcessor.destroy();

        mView = mScrimViewBuilder.get();
        mChangeProcessor = PropertyModelChangeProcessor.create(model, mView, ScrimViewBinder::bind);
        mMediator.showScrim(model, ANIM_DURATION_MS);
    }

    /**
     * Hide the scrim.
     * @param animate Whether the scrim should animate and fade out.
     */
    public void hideScrim(boolean animate) {
        hideScrim(animate, ANIM_DURATION_MS);
    }

    /**
     * Hide the scrim.
     * @param animate Whether the scrim should animate and fade out.
     * @param duration Duration for animation.
     */
    public void hideScrim(boolean animate, int duration) {
        mMediator.hideScrim(animate, duration);
    }

    /** @return Whether the scrim is being shown. */
    public boolean isShowingScrim() {
        return mMediator.isActive();
    }

    /** Forces the current scrim fade animation to complete if one is running. */
    public void forceAnimationToFinish() {
        mMediator.forceAnimationToFinish();
    }

    /**
     * Manually set the alpha for the scrim.
     * @param alpha The alpha in range [0, 1].
     */
    public void setAlpha(float alpha) {
        mMediator.setAlpha(alpha);
    }

    /** Clean up this coordinator. */
    public void destroy() {
        mMediator.destroy();
    }

    public void disableAnimationForTesting(boolean disable) {
        mMediator.disableAnimationForTesting(disable);
    }

    public ScrimView getViewForTesting() {
        return mView;
    }

    ScrimMediator getMediatorForTesting() {
        return mMediator;
    }

    @VisibleForTesting
    boolean areAnimationsRunning() {
        return mMediator.areAnimationsRunning();
    }
}