chromium/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/frame/PlayerFrameView.java

// Copyright 2019 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.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.util.Size;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewStructure;
import android.view.accessibility.AccessibilityNodeProvider;
import android.widget.FrameLayout;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import org.chromium.content_public.browser.WebContentsAccessibility;
import org.chromium.ui.accessibility.AccessibilityState;

import java.util.List;

/**
 * Responsible for detecting touch gestures, displaying the content of a frame and its sub-frames.
 * {@link PlayerFrameBitmapPainter} is used for drawing the contents. Sub-frames are represented
 * with individual {@link View}s. {@link #mSubFrames} contains the list of all sub-frames and their
 * relative positions.
 */
public class PlayerFrameView extends FrameLayout {
    private PlayerFrameBitmapPainter mBitmapPainter;
    private PlayerFrameGestureDetector mGestureDetector;
    private PlayerFrameViewDelegate mDelegate;
    private List<View> mSubFrameViews;
    private List<Rect> mSubFrameRects;
    private Matrix mScaleMatrix;
    private Matrix mOffset = new Matrix();
    protected WebContentsAccessibility mWebContentsAccessibility;

    /**
     * @param context                 Used for initialization.
     * @param canDetectZoom           Whether this {@link View} should detect zoom (scale)
     *                                gestures.
     * @param playerFrameViewDelegate The interface used for forwarding events.
     */
    public PlayerFrameView(
            @NonNull Context context,
            boolean canDetectZoom,
            PlayerFrameViewDelegate playerFrameViewDelegate,
            PlayerFrameGestureDetectorDelegate gestureDetectorDelegate,
            @Nullable Runnable firstPaintListener) {
        super(context);
        setWillNotDraw(false);
        mDelegate = playerFrameViewDelegate;
        mBitmapPainter = new PlayerFrameBitmapPainter(this::postInvalidate, firstPaintListener);
        mGestureDetector =
                new PlayerFrameGestureDetector(context, canDetectZoom, gestureDetectorDelegate);
    }

    /** Sets the {@link WebContentsAccessibility} for this View. */
    public void setWebContentsAccessibility(WebContentsAccessibility webContentsAccessibility) {
        mWebContentsAccessibility = webContentsAccessibility;
    }

    PlayerFrameGestureDetector getGestureDetector() {
        return mGestureDetector;
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        mDelegate.setLayoutDimensions(getWidth(), getHeight());
    }

    /**
     * Updates the sub-frame views that this {@link PlayerFrameView} should display.
     *
     * @param subFrameViews List of all sub-frame views.
     */
    void updateSubFrameViews(List<View> subFrameViews) {
        mSubFrameViews = subFrameViews;
    }

    /**
     * Updates clip rects for sub-frames that this {@link PlayerFrameView} should display.
     *
     * @param subFrameRects List of all sub-frames clip rects.
     */
    void updateSubFrameRects(List<Rect> subFrameRects) {
        mSubFrameRects = subFrameRects;
    }

    void updateOffset(int left, int top) {
        mOffset.setTranslate(left, top);
    }

    void updateViewPort(int left, int top, int right, int bottom) {
        mBitmapPainter.updateViewPort(left, top, right, bottom);
        layoutSubFrames();
    }

    void updateBitmapMatrix(Bitmap[][] bitmapMatrix) {
        mBitmapPainter.updateBitmapMatrix(bitmapMatrix);
    }

    void updateTileDimensions(Size tileDimensions) {
        mBitmapPainter.updateTileDimensions(tileDimensions);
    }

    void updateScaleMatrix(Matrix matrix) {
        mScaleMatrix = matrix;
        if (mScaleMatrix.isIdentity()) return;

        postInvalidate();
        layoutSubFrames();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.save();
        canvas.concat(mOffset);
        canvas.concat(mScaleMatrix);
        mBitmapPainter.onDraw(canvas);
        canvas.restore();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        return mGestureDetector.onTouchEvent(event);
    }

    @Override
    public boolean onHoverEvent(MotionEvent event) {
        if (mWebContentsAccessibility != null && AccessibilityState.isTouchExplorationEnabled()) {
            return mWebContentsAccessibility.onHoverEventNoRenderer(event);
        }
        return super.onHoverEvent(event);
    }

    private void layoutSubFrames() {
        // Remove all views if there are no sub-frames.
        if (mSubFrameViews == null || mSubFrameRects == null) {
            removeAllViews();
            return;
        }

        // Layout the sub-frames.
        for (int i = 0; i < mSubFrameViews.size(); i++) {
            View subFrameView = mSubFrameViews.get(i);
            if (subFrameView.getVisibility() != View.VISIBLE) {
                removeView(subFrameView);
                continue;
            }

            if (subFrameView.getParent() == null) {
                addView(mSubFrameViews.get(i));
            } else if (subFrameView.getParent() != this) {
                throw new IllegalStateException("Sub-frame view already has a parent.");
            }
            Rect layoutRect = mSubFrameRects.get(i);
            subFrameView.layout(
                    layoutRect.left, layoutRect.top, layoutRect.right, layoutRect.bottom);
        }
    }

    @Override
    public AccessibilityNodeProvider getAccessibilityNodeProvider() {
        AccessibilityNodeProvider provider =
                (mWebContentsAccessibility != null)
                        ? mWebContentsAccessibility.getAccessibilityNodeProvider()
                        : null;
        return (provider != null) ? provider : super.getAccessibilityNodeProvider();
    }

    @Override
    public void onProvideVirtualStructure(final ViewStructure structure) {
        if (mWebContentsAccessibility != null) {
            mWebContentsAccessibility.onProvideVirtualStructure(structure, false);
        }
    }

    void destroy() {
        mBitmapPainter.destroy();
    }
}