chromium/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/frame/PlayerFrameBitmapStateController.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.paintpreview.player.frame;

import android.util.Size;

import org.chromium.base.UnguessableToken;
import org.chromium.components.paintpreview.player.PlayerCompositorDelegate;

/** Class for managing which bitmap state is shown. */
public class PlayerFrameBitmapStateController {
    private PlayerFrameBitmapState mLoadingBitmapState;
    private PlayerFrameBitmapState mVisibleBitmapState;

    private final UnguessableToken mGuid;
    private final PlayerFrameViewport mViewport;
    private final Size mContentSize;
    private final PlayerCompositorDelegate mCompositorDelegate;
    private final PlayerFrameMediatorDelegate mMediatorDelegate;

    PlayerFrameBitmapStateController(
            UnguessableToken guid,
            PlayerFrameViewport viewport,
            Size contentSize,
            PlayerCompositorDelegate compositorDelegate,
            PlayerFrameMediatorDelegate mediatorDelegate) {
        mGuid = guid;
        mViewport = viewport;
        mContentSize = contentSize;
        mCompositorDelegate = compositorDelegate;
        if (mCompositorDelegate != null) {
            mCompositorDelegate.addMemoryPressureListener(this::onMemoryPressure);
        }
        mMediatorDelegate = mediatorDelegate;
    }

    void deleteAll() {
        if (mLoadingBitmapState != null) {
            mLoadingBitmapState.destroy();
            mLoadingBitmapState = null;
        }
        if (mVisibleBitmapState != null) {
            mVisibleBitmapState.destroy();
            mVisibleBitmapState = null;
        }
    }

    void destroy() {
        deleteAll();
    }

    void swapForTest() {
        swap(mLoadingBitmapState);
    }

    void onMemoryPressure() {
        if (mVisibleBitmapState == null) return;

        mVisibleBitmapState.releaseNotVisibleTiles();
        stateUpdated(mVisibleBitmapState);
    }

    /**
     * Gets the bitmap state for loading.
     * @param scaleUpdated Whether the scale was updated.
     * @return The bitmap state to load new bitmaps to.
     */
    PlayerFrameBitmapState getBitmapState(boolean scaleUpdated) {
        // Prefer mLoadingBitmapState if one exist. Otherwise use mVisibleBitmapState.
        PlayerFrameBitmapState activeLoadingState =
                (mLoadingBitmapState == null) ? mVisibleBitmapState : mLoadingBitmapState;
        if (scaleUpdated || activeLoadingState == null) {
            invalidateLoadingBitmaps();
            Size tileSize = mViewport.getBitmapTileSize();
            mLoadingBitmapState =
                    new PlayerFrameBitmapState(
                            mGuid,
                            tileSize.getWidth(),
                            tileSize.getHeight(),
                            mViewport.getScale(),
                            mContentSize,
                            mCompositorDelegate,
                            this);
            if (mVisibleBitmapState == null) {
                mLoadingBitmapState.skipWaitingForVisibleBitmaps();
                swap(mLoadingBitmapState);
                activeLoadingState = mVisibleBitmapState;
            } else {
                activeLoadingState = mLoadingBitmapState;
            }
        }
        return activeLoadingState;
    }

    /**
     * Swaps the state to be new state.
     * @param newState The new visible bitmap state.
     */
    void swap(PlayerFrameBitmapState newState) {
        assert mLoadingBitmapState == newState;
        PlayerFrameBitmapState oldState = mVisibleBitmapState;
        mVisibleBitmapState = newState;
        mLoadingBitmapState = null;
        mMediatorDelegate.onSwapState();
        // Clear the state to stop potential stragling updates. Destroy afterwards in case drawing
        // is happening concurrently somehow.
        if (oldState != null) {
            oldState.destroy();
        }
    }

    /**
     * Signals the bitmap state was updated.
     * @param bitmapState The bitmap state that was updated.
     */
    void stateUpdated(PlayerFrameBitmapState bitmapState) {
        if (isVisible(bitmapState)) {
            mMediatorDelegate.updateBitmapMatrix(bitmapState.getMatrix());
            return;
        }

        if (!bitmapState.isReadyToShow()) return;

        swap(bitmapState);
    }

    /** Whether the bitmap state is visible. */
    boolean isVisible(PlayerFrameBitmapState state) {
        return state == mVisibleBitmapState;
    }

    void onStartScaling() {
        if (mVisibleBitmapState == null) return;
        invalidateLoadingBitmaps();

        if (mVisibleBitmapState == null) return;

        mVisibleBitmapState.lock();
    }

    /** Invalidates loading bitmaps. */
    void invalidateLoadingBitmaps() {
        if (mLoadingBitmapState == null) return;

        // Invalidate an in-progress load if there is one. We only want one new scale factor fetched
        // at a time. NOTE: we clear then null as the bitmap callbacks still hold a reference to the
        // state so it won't be GC'd right away.
        mLoadingBitmapState.destroy();
        mLoadingBitmapState = null;
    }
}