blob: a362aa8ca172a2631ff33e27ecabaec9f046fac1 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// 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;
import android.content.Context;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.FrameLayout;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.TraceEvent;
import org.chromium.base.UnguessableToken;
import org.chromium.components.paintpreview.browser.NativePaintPreviewServiceProvider;
import org.chromium.components.paintpreview.player.frame.PlayerFrameCoordinator;
import org.chromium.url.GURL;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* This is the only public class in this package and is hence the access point of this component for
* the outer world. Users should call {@link #destroy()} to ensure the native part is destroyed.
*/
public class PlayerManager {
private Context mContext;
private PlayerCompositorDelegateImpl mDelegate;
private PlayerFrameCoordinator mRootFrameCoordinator;
private FrameLayout mHostView;
private Runnable mViewReadyCallback;
private Runnable mUserInteractionCallback;
private static final String sInitEvent = "paint_preview PlayerManager init";
private PlayerSwipeRefreshHandler mPlayerSwipeRefreshHandler;
private boolean mIgnoreInitialScrollOffset;
/**
* Creates a new {@link PlayerManager}.
* @param url The url for the stored content that should be shown.
* @param nativePaintPreviewServiceProvider The native paint preview service.
* @param directoryKey The key for the directory storing the data.
* @param linkClickHandler Called with a url to trigger a navigation.
* @param refreshCallback Called when the paint preview should be refreshed.
* @param viewReadyCallback Called when the view is ready. Will not be called if compositorError
* is called prior to the view being ready.
* @param userInteractionCallback Called when the use interacts with the paint preview.
* @param backgroundColor The color used for the background.
* @param compositorErrorCallback Called when the compositor has had an error (either during
* initialization or due to a disconnect).
* @param ignoreInitialScrollOffset If true the initial scroll state that is recorded at capture
* time is ignored.
*/
public PlayerManager(GURL url, Context context,
NativePaintPreviewServiceProvider nativePaintPreviewServiceProvider,
String directoryKey, @Nonnull LinkClickHandler linkClickHandler,
@Nullable Runnable refreshCallback, Runnable viewReadyCallback,
Runnable userInteractionCallback, int backgroundColor, Runnable compositorErrorCallback,
boolean ignoreInitialScrollOffset) {
TraceEvent.startAsync(sInitEvent, hashCode());
mContext = context;
mDelegate = new PlayerCompositorDelegateImpl(nativePaintPreviewServiceProvider, url,
directoryKey, this::onCompositorReady, linkClickHandler, compositorErrorCallback);
mHostView = new FrameLayout(mContext);
if (refreshCallback != null) {
mPlayerSwipeRefreshHandler = new PlayerSwipeRefreshHandler(mContext, refreshCallback);
}
mHostView.setLayoutParams(
new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
mHostView.setBackgroundColor(backgroundColor);
mViewReadyCallback = viewReadyCallback;
mUserInteractionCallback = userInteractionCallback;
mIgnoreInitialScrollOffset = ignoreInitialScrollOffset;
}
/**
* Called by {@link PlayerCompositorDelegateImpl} when the compositor is initialized. This
* method initializes a sub-component for each frame and adds the view for the root frame to
* {@link #mHostView}.
*/
private void onCompositorReady(UnguessableToken rootFrameGuid, UnguessableToken[] frameGuids,
int[] frameContentSize, int[] scrollOffsets, int[] subFramesCount,
UnguessableToken[] subFrameGuids, int[] subFrameClipRects) {
PaintPreviewFrame rootFrame = buildFrameTreeHierarchy(rootFrameGuid, frameGuids,
frameContentSize, scrollOffsets, subFramesCount, subFrameGuids, subFrameClipRects,
mIgnoreInitialScrollOffset);
mRootFrameCoordinator = new PlayerFrameCoordinator(mContext, mDelegate, rootFrame.getGuid(),
rootFrame.getContentWidth(), rootFrame.getContentHeight(),
rootFrame.getInitialScrollX(), rootFrame.getInitialScrollY(), true,
mPlayerSwipeRefreshHandler, mUserInteractionCallback);
buildSubFrameCoordinators(mRootFrameCoordinator, rootFrame);
mHostView.addView(mRootFrameCoordinator.getView(),
new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
if (mPlayerSwipeRefreshHandler != null) {
mHostView.addView(mPlayerSwipeRefreshHandler.getView());
}
TraceEvent.finishAsync(sInitEvent, hashCode());
mViewReadyCallback.run();
}
/**
* This method builds a hierarchy of {@link PaintPreviewFrame}s from primitive variables that
* originate from native. Detailed explanation of the parameters can be found in {@link
* PlayerCompositorDelegateImpl#onCompositorReady}.
*
* @return The root {@link PaintPreviewFrame}
*/
@VisibleForTesting
static PaintPreviewFrame buildFrameTreeHierarchy(UnguessableToken rootFrameGuid,
UnguessableToken[] frameGuids, int[] frameContentSize, int[] scrollOffsets,
int[] subFramesCount, UnguessableToken[] subFrameGuids, int[] subFrameClipRects,
boolean ignoreInitialScrollOffset) {
Map<UnguessableToken, PaintPreviewFrame> framesMap = new HashMap<>();
for (int i = 0; i < frameGuids.length; i++) {
int initalScrollX = ignoreInitialScrollOffset ? 0 : scrollOffsets[i * 2];
int initalScrollY = ignoreInitialScrollOffset ? 0 : scrollOffsets[(i * 2) + 1];
framesMap.put(frameGuids[i],
new PaintPreviewFrame(frameGuids[i], frameContentSize[i * 2],
frameContentSize[(i * 2) + 1], initalScrollX, initalScrollY));
}
int subFrameIdIndex = 0;
for (int i = 0; i < frameGuids.length; i++) {
PaintPreviewFrame currentFrame = framesMap.get(frameGuids[i]);
int currentFrameSubFrameCount = subFramesCount[i];
PaintPreviewFrame[] subFrames = new PaintPreviewFrame[currentFrameSubFrameCount];
Rect[] subFrameClips = new Rect[currentFrameSubFrameCount];
for (int subFrameIndex = 0; subFrameIndex < currentFrameSubFrameCount;
subFrameIndex++, subFrameIdIndex++) {
subFrames[subFrameIndex] = framesMap.get(subFrameGuids[subFrameIdIndex]);
int x = subFrameClipRects[subFrameIdIndex * 4];
int y = subFrameClipRects[subFrameIdIndex * 4 + 1];
int width = subFrameClipRects[subFrameIdIndex * 4 + 2];
int height = subFrameClipRects[subFrameIdIndex * 4 + 3];
subFrameClips[subFrameIndex] = new Rect(x, y, x + width, y + height);
}
currentFrame.setSubFrames(subFrames);
currentFrame.setSubFrameClips(subFrameClips);
}
return framesMap.get(rootFrameGuid);
}
/**
* Recursively builds {@link PlayerFrameCoordinator}s for the sub-frames of the given frame and
* adds them to the given frameCoordinator.
*/
private void buildSubFrameCoordinators(
PlayerFrameCoordinator frameCoordinator, PaintPreviewFrame frame) {
if (frame.getSubFrames() == null || frame.getSubFrames().length == 0) {
return;
}
for (int i = 0; i < frame.getSubFrames().length; i++) {
PaintPreviewFrame childFrame = frame.getSubFrames()[i];
PlayerFrameCoordinator childCoordinator = new PlayerFrameCoordinator(mContext,
mDelegate, childFrame.getGuid(), childFrame.getContentWidth(),
childFrame.getContentHeight(), childFrame.getInitialScrollX(),
childFrame.getInitialScrollY(), false, null, mUserInteractionCallback);
buildSubFrameCoordinators(childCoordinator, childFrame);
frameCoordinator.addSubFrame(childCoordinator, frame.getSubFrameClips()[i]);
}
}
public void destroy() {
if (mDelegate != null) {
mDelegate.destroy();
mDelegate = null;
}
}
public View getView() {
return mHostView;
}
}