Commit 56f6805d authored by Tanya Gupta's avatar Tanya Gupta Committed by Chromium LUCI CQ

[LongScreenshots] Created LongScreenshotsEntry

LongScreenshotsEntry allows for management of multiple FDT requests
and generated bitmaps. EntryManager is responsible for managing all
of the entries.

Bug: 1153969
Change-Id: I11053f9b0ba0a47282f0e1e6153f37675de3ab1a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2605600
Commit-Queue: Tanya Gupta <tgupta@chromium.org>
Reviewed-by: default avatarDavid Trainor <dtrainor@chromium.org>
Reviewed-by: default avatarKyle Milka <kmilka@chromium.org>
Cr-Commit-Position: refs/heads/master@{#841145}
parent 1355fedd
......@@ -15,7 +15,7 @@ public class PaintPreviewCompositorUtils {
/**
* Warms up the compositor process.
*/
static void warmupCompositor() {
public static void warmupCompositor() {
PaintPreviewCompositorUtilsJni.get().warmupCompositor();
}
......
// Copyright 2020 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.chrome.browser.share.long_screenshots;
import android.content.Context;
import android.graphics.Bitmap;
import org.chromium.base.Callback;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.content_public.browser.RenderCoordinates;
import org.chromium.ui.display.DisplayAndroid;
import java.util.ArrayList;
import java.util.List;
/**
* Entry manager responsible for managing all the of the {@LongScreenshotEntry}. This should be used
* to generate and retreive the needed bitmaps. The first bitmap can be generated by calling
* {@link generateInitialBitmapBlocking}.
*/
public class EntryManager {
private Context mContext;
private Tab mTab;
private List<LongScreenshotsEntry> mEntries;
private int mDisplayHeightPx;
/**
* @param context An instance of current Android {@link Context}.
* @param tab Tab to generate the bitmap for.
*/
public EntryManager(Context context, Tab tab) {
mContext = context;
mTab = tab;
mEntries = new ArrayList<LongScreenshotsEntry>();
calculateClipHeight();
}
/**
* Generates the first bitmap of the page that is the height of the phone display. Calls the
* provided callback with the generated bitmap.
*
* @param onGenerated Callback to pass the generated bitmap.
*/
public void generateInitialBitmap(Callback<Bitmap> onGenerated) {
LongScreenshotsEntry entry =
new LongScreenshotsEntry(mContext, mTab, new LongScreenshotsEntry.Listener() {
@Override
public void onCompositorError(int status) {
// TODO(tgupta): Auto-generated method stub
}
@Override
public void onCaptureError() {
// TODO(tgupta): Auto-generated method stub
}
@Override
public void onBitmapGenerated(LongScreenshotsEntry entry) {
mEntries.add(entry);
onGenerated.onResult(entry.getBitmap());
}
});
RenderCoordinates coords = RenderCoordinates.fromWebContents(mTab.getWebContents());
int startY = coords.getScrollYPixInt() / coords.getPageScaleFactorInt();
entry.setClipBounds(startY, mDisplayHeightPx);
entry.captureScreenshot();
}
/**
* Calculates the height of the phone used to determine the height of the bitmaps.
*/
private void calculateClipHeight() {
DisplayAndroid displayAndroid = DisplayAndroid.getNonMultiDisplay(mContext);
mDisplayHeightPx = displayAndroid.getDisplayHeight();
}
}
......@@ -10,49 +10,44 @@ import android.graphics.Rect;
import org.chromium.base.Callback;
import org.chromium.base.UnguessableToken;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.components.paint_preview.common.proto.PaintPreview.PaintPreviewProto;
import org.chromium.components.paintpreview.browser.NativePaintPreviewServiceProvider;
import org.chromium.components.paintpreview.player.CompositorStatus;
import org.chromium.components.paintpreview.player.PlayerCompositorDelegate;
import org.chromium.components.paintpreview.player.PlayerCompositorDelegateImpl;
import org.chromium.content_public.browser.RenderCoordinates;
import org.chromium.url.GURL;
/**
* Compositor for LongScreenshots. Responsible for calling into Freeze-dried tabs to composite
* the captured webpage.
* Compositor for LongScreenshots. Responsible for calling into Freeze-dried tabs to composite the
* captured webpage.
*/
public class LongScreenshotsCompositor {
private Context mContext;
private PlayerCompositorDelegate mDelegate;
private Callback<Bitmap> mBitmapCallback;
private Tab mTab;
private static final int CLIP_HEIGHT = 1000;
private Rect mRect;
/**
* Creates a new {@link LongScreenshotsCompositor}.
*
* @param tab The tab for which the content should be captured for.
* @param url The URL for which the content should be composited for.
* @param context An instance of current Android {@link Context}.
* @param nativePaintPreviewServiceProvider The native paint preview service.
* @param directoryKey The key for the directory storing the data.
* @param rect The area of the captured webpage that should be composited.
* @param response The proto with the address of the captured bitmap.
* @param bitmapCallback Callback to process the composited bitmap.
*/
public LongScreenshotsCompositor(Tab tab, Context context,
public LongScreenshotsCompositor(GURL url, Context context,
NativePaintPreviewServiceProvider nativePaintPreviewServiceProvider,
String directoryKey, PaintPreviewProto response, Callback<Bitmap> bitmapCallback) {
mTab = tab;
String directoryKey, PaintPreviewProto response, Rect rect,
Callback<Bitmap> bitmapCallback) {
mContext = context;
mBitmapCallback = bitmapCallback;
mRect = rect;
// TODO(tgupta): Look into warmupCompositor
// TODO(tgupta): Investigate why the PlayerCompositorDelegateFactory is the preferred
// way to construct a PlayerCompositorDelegate.
mDelegate = new PlayerCompositorDelegateImpl(nativePaintPreviewServiceProvider, response,
mTab.getUrl(), directoryKey, true, this::onCompositorReady,
this::onCompositorError);
url, directoryKey, true, this::onCompositorReady, this::onCompositorError);
}
/**
......@@ -71,13 +66,7 @@ public class LongScreenshotsCompositor {
private void onCompositorReady(UnguessableToken rootFrameGuid, UnguessableToken[] frameGuids,
int[] frameContentSize, int[] scrollOffsets, int[] subFramesCount,
UnguessableToken[] subFrameGuids, int[] subFrameClipRects) {
RenderCoordinates coords = RenderCoordinates.fromWebContents(mTab.getWebContents());
int startY = coords.getScrollYPixInt() / coords.getPageScaleFactorInt();
int endX = coords.getContentWidthPixInt() / coords.getPageScaleFactorInt();
int endY = startY + (CLIP_HEIGHT * coords.getPageScaleFactorInt());
Rect rect = new Rect(0, startY, endX, endY);
mDelegate.requestBitmap(rect, 1, mBitmapCallback, this::onError);
mDelegate.requestBitmap(mRect, 1, mBitmapCallback, this::onError);
}
/**
......
......@@ -6,20 +6,19 @@ package org.chromium.chrome.browser.share.long_screenshots;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Rect;
import org.chromium.base.Callback;
import org.chromium.chrome.browser.paint_preview.PaintPreviewCompositorUtils;
import org.chromium.chrome.browser.share.screenshot.ScreenshotCoordinator;
import org.chromium.chrome.browser.share.share_sheet.ChromeOptionShareCallback;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.modules.image_editor.ImageEditorModuleProvider;
import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
import org.chromium.components.paint_preview.common.proto.PaintPreview.PaintPreviewProto;
/**
* Handles the long screenshot action in the Sharing Hub and launches the screenshot editor.
*/
public class LongScreenshotsCoordinator
extends ScreenshotCoordinator implements LongScreenshotsTabService.CaptureProcessor {
public class LongScreenshotsCoordinator extends ScreenshotCoordinator {
private LongScreenshotsTabService mLongScreenshotsTabService;
private static final String DIR_NAME = "long_screenshots_dir";
......@@ -39,8 +38,7 @@ public class LongScreenshotsCoordinator
ImageEditorModuleProvider imageEditorModuleProvider) {
super(activity, tab, chromeOptionShareCallback, sheetController, imageEditorModuleProvider);
mLongScreenshotsTabService = LongScreenshotsTabServiceFactory.getServiceInstance();
mLongScreenshotsTabService.setCaptureProcessor(this);
PaintPreviewCompositorUtils.warmupCompositor();
}
/**
......@@ -49,31 +47,14 @@ public class LongScreenshotsCoordinator
*/
@Override
public void captureScreenshot() {
// TODO(tgupta): Provide the correct bounds.
mLongScreenshotsTabService.captureTab(mTab, new Rect(0, 0, 1000, 1000));
}
/**
* Called after the bitmap has been composited and can be shown to the user.
* @param result Bitmap to display in the dialog.
*/
public void onBitmapResult(Bitmap result) {
mScreenshot = result;
launchSharesheet();
}
/**
* Pass the proto response from the LongScreenshotsTabService to the compositor. Called from
* the tabService.
*
* @response PaintPreview response with the address of the stored screenshot
* @status Status of the capturing
*/
@Override
public void processCapturedTab(PaintPreviewProto response, int status) {
// TODO(tgupta): Process a non success status
new LongScreenshotsCompositor(mTab, mActivity,
LongScreenshotsTabServiceFactory.getServiceInstance(), DIR_NAME, response,
this::onBitmapResult);
EntryManager entryManager = new EntryManager(mActivity, mTab);
entryManager.generateInitialBitmap(new Callback<Bitmap>() {
@Override
public void onResult(Bitmap result) {
mScreenshot = result;
launchSharesheet();
}
});
}
}
// Copyright 2020 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.chrome.browser.share.long_screenshots;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Rect;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.components.paint_preview.common.proto.PaintPreview.PaintPreviewProto;
import org.chromium.components.paintpreview.player.CompositorStatus;
import org.chromium.content_public.browser.RenderCoordinates;
import org.chromium.url.GURL;
/**
* Manages the lifecycle of generating one bitmap in the long screenshot. Takes in the bounds of the
* desired screenshot. 1. Requests a capture of the webpage for the bounds specified through
* {@link LongScreenshotsTabService}. 2. Composites the captured webpage through
* {@LongScreenshotsCompositor} 3. Returns the generated bitmap via provided
* {@link LongScreenshotsEntry.Listener}
*/
public class LongScreenshotsEntry implements LongScreenshotsTabService.CaptureProcessor {
private Context mContext;
private int mStartXAxis;
private int mStartYAxis;
private int mEndXAxis;
private int mEndYAxis;
// Response with a pointer to the skia image
private PaintPreviewProto mProtoResponse;
// Compositor delegate responsible for compositing the skia image.
private LongScreenshotsCompositor mCompositor;
private LongScreenshotsTabService mLongScreenshotsTabService;
private Tab mTab;
private static final String DIR_NAME = "long_screenshots_dir";
// Generated bitmap
private Bitmap mGeneratedBitmap;
private Listener mEventListener;
/**
* Users of the {@link LongScreenshotsEntry} class have to implement and pass this interface in
* the constructor.
*/
public interface Listener {
/**
* Called when the compositor cannot be successfully initialized.
*/
void onCompositorError(@CompositorStatus int status);
/**
* Called when the bitmap has been succesfully generated.
*/
void onBitmapGenerated(LongScreenshotsEntry entry);
/**
* Called when the capture failed.
*/
void onCaptureError();
}
/**
* @param context An instance of current Android {@link Context}.
* @param tab The tab to capture the results for.
* @param listener The listener to be notified with the generated bitmap.
*/
public LongScreenshotsEntry(Context context, Tab tab, Listener listener) {
mContext = context;
mTab = tab;
mEventListener = listener;
mLongScreenshotsTabService = LongScreenshotsTabServiceFactory.getServiceInstance();
mLongScreenshotsTabService.setCaptureProcessor(this);
}
/**
* Defines the bounds of the capture and compositing. Only the starting height and the height of
* the clip is needed. The entire width is always captured.
*
* @param startYAxis Where on the scrolled page the capture and compisiting should start.
* @param clipHeight The length of the webpage that should be captured.
*/
public void setClipBounds(int startYAxis, int clipHeight) {
RenderCoordinates coords = RenderCoordinates.fromWebContents(mTab.getWebContents());
mStartYAxis = startYAxis;
mEndYAxis = (int) Math.floor(mStartYAxis + (clipHeight * coords.getPageScaleFactor()));
mStartXAxis = 0;
mEndXAxis = (int) Math.floor(coords.getContentWidthPixInt() / coords.getPageScaleFactor());
}
/**
* Starts the capture of the screenshot.
*/
public void captureScreenshot() {
// TODO(tgupta): Consider throwing an error here if the clip bounds have not been
// provided.
Rect rect = new Rect(mStartXAxis, mStartYAxis, mEndXAxis, mEndYAxis);
mLongScreenshotsTabService.captureTab(mTab, rect);
}
/**
* Called from native after the tab has been captured.
*
* @param response Response generated by the capturer with the filepath to the skia image.
* @param status Status with the state of the capture.
*/
@Override
public void processCapturedTab(PaintPreviewProto response, int status) {
// TODO(tgupta): Process a non success status
mCompositor = new LongScreenshotsCompositor(new GURL(response.getMetadata().getUrl()),
mContext, mLongScreenshotsTabService, DIR_NAME, response,
new Rect(mStartXAxis, mStartYAxis, mEndXAxis, mEndYAxis), this::onBitmapResult);
}
/**
* Called after the bitmap has been composited and can be shown to the user.
*
* @param result Bitmap to display in the dialog.
*/
private void onBitmapResult(Bitmap result) {
mGeneratedBitmap = result;
mEventListener.onBitmapGenerated(this);
}
/**
* @return the generated bitmap or null in the case of error or incomplete generation. Callers
* should only call this function after listening for onBitmapGenerated.
*/
public Bitmap getBitmap() {
return mGeneratedBitmap;
}
public void destroy() {
if (mCompositor != null) {
mCompositor.destroy();
mCompositor = null;
}
mGeneratedBitmap = null;
}
}
......@@ -9,8 +9,10 @@ share_java_sources = [
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/clipboard/ClipboardImageFileProvider.java",
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/link_to_text/LinkToTextCoordinator.java",
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/link_to_text/LinkToTextMetricsBridge.java",
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/long_screenshots/EntryManager.java",
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/long_screenshots/LongScreenshotsCompositor.java",
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/long_screenshots/LongScreenshotsCoordinator.java",
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/long_screenshots/LongScreenshotsEntry.java",
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/long_screenshots/LongScreenshotsTabService.java",
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/long_screenshots/LongScreenshotsTabServiceFactory.java",
"//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/qrcode/QRCodeGenerationRequest.java",
......
// Copyright 2020 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.chrome.browser.share.long_screenshots;
import androidx.test.filters.MediumTest;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Criteria;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.Matchers;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.paint_preview.PaintPreviewCompositorUtils;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.net.test.EmbeddedTestServer;
/** Tests for the LongScreenshotsEntryTest. */
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class LongScreenshotsEntryTest {
@Rule
public final ChromeTabbedActivityTestRule mActivityTestRule =
new ChromeTabbedActivityTestRule();
@Rule
public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
private Tab mTab;
private LongScreenshotsEntry mEntry;
private TestListener mTestListener;
class TestListener implements LongScreenshotsEntry.Listener {
boolean mOnBitmapGeneratedCalled;
@Override
public void onCompositorError(int status) {}
@Override
public void onBitmapGenerated(LongScreenshotsEntry entry) {
mOnBitmapGeneratedCalled = true;
}
@Override
public void onCaptureError() {}
boolean getOnBitmapGeneratedCalled() {
return mOnBitmapGeneratedCalled;
}
}
@Before
public void setUp() throws Exception {
mActivityTestRule.startMainActivityOnBlankPage();
mTab = mActivityTestRule.getActivity().getActivityTab();
mTestListener = new TestListener();
TestThreadUtils.runOnUiThreadBlocking(() -> {
mEntry = new LongScreenshotsEntry(mActivityTestRule.getActivity(), mTab, mTestListener);
PaintPreviewCompositorUtils.warmupCompositor();
});
}
@After
public void tearDown() throws Exception {
TestThreadUtils.runOnUiThreadBlocking(() -> { mEntry.destroy(); });
}
/**
* Verifies that a Tab's contents are captured.
*/
@Test
@MediumTest
@Feature({"LongScreenshots"})
public void testCaptured() throws Exception {
EmbeddedTestServer testServer = mActivityTestRule.getTestServer();
final String url = testServer.getURL("/chrome/test/data/android/about.html");
TestThreadUtils.runOnUiThreadBlocking(() -> {
mTab.loadUrl(new LoadUrlParams(url));
mEntry.setClipBounds(0, 100);
mEntry.captureScreenshot();
});
CriteriaHelper.pollUiThread(() -> {
Criteria.checkThat("Callback was not called",
mTestListener.getOnBitmapGeneratedCalled(), Matchers.is(true));
}, 10000L, 50L);
Assert.assertNotNull(mEntry.getBitmap());
}
}
......@@ -5,6 +5,7 @@
# TODO(crbug.com/1022172): This should be a separate build target when circular dependencies are removed.
share_test_java_sources = [
"//chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/clipboard/ClipboardImageFileProviderTest.java",
"//chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/long_screenshots/LongScreenshotsEntryTest.java",
"//chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/long_screenshots/LongScreenshotsTabServiceTest.java",
"//chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/screenshot/ScreenshotShareSheetSaveDelegateTest.java",
"//chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/screenshot/ScreenshotShareSheetViewTest.java",
......
......@@ -173,6 +173,7 @@ public class RenderCoordinatesImpl implements RenderCoordinates {
/**
* @return Current page scale factor (maps CSS pixels to DIP pixels).
*/
@Override
public float getPageScaleFactor() {
return mPageScaleFactor;
}
......
......@@ -65,5 +65,10 @@ public interface RenderCoordinates {
/**
* @return Current page scale factor (approx, integer).
*/
public int getPageScaleFactorInt();
int getPageScaleFactorInt();
/**
* @return Current page scale factor
*/
float getPageScaleFactor();
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment