blob: a4f7abae5471b3200eede806df63b4b1f4d39195 [file] [log] [blame]
// Copyright 2017 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.feedback;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.task.PostTask;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.tab.SadTab;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.content_public.browser.UiThreadTaskTraits;
import org.chromium.ui.UiUtils;
import org.chromium.ui.base.WindowAndroid;
/**
* A utility class to take a feedback-formatted screenshot of an {@link Activity}.
*/
@JNINamespace("chrome::android")
final class ScreenshotTask implements ScreenshotSource {
/**
* Maximum dimension for the screenshot to be sent to the feedback handler. This size
* ensures the size of bitmap < 1MB, which is a requirement of the handler.
*/
private static final int MAX_FEEDBACK_SCREENSHOT_DIMENSION = 600;
private final Activity mActivity;
private boolean mDone;
private Bitmap mBitmap;
private Runnable mCallback;
/**
* Creates a {@link ScreenshotTask} instance that, will grab a screenshot of {@code activity}.
* @param activity The {@link Activity} to grab a screenshot of.
*/
public ScreenshotTask(Activity activity) {
mActivity = activity;
}
// ScreenshotSource implementation.
@Override
public void capture(@Nullable Runnable callback) {
mCallback = callback;
if (takeCompositorScreenshot(mActivity)) return;
if (takeAndroidViewScreenshot(mActivity)) return;
// If neither the compositor nor the Android view screenshot tasks were kicked off, admit
// defeat and return a {@code null} screenshot.
PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
@Override
public void run() {
onBitmapReceived(null);
}
});
}
@Override
public boolean isReady() {
return mDone;
}
@Override
public Bitmap getScreenshot() {
return mBitmap;
}
// This will be called on the UI thread in response to nativeGrabWindowSnapshotAsync.
@CalledByNative
private void onBytesReceived(byte[] pngBytes) {
Bitmap bitmap = null;
if (pngBytes != null) bitmap = BitmapFactory.decodeByteArray(pngBytes, 0, pngBytes.length);
onBitmapReceived(bitmap);
}
private void onBitmapReceived(@Nullable Bitmap bitmap) {
mDone = true;
mBitmap = bitmap;
if (mCallback != null) mCallback.run();
mCallback = null;
}
private boolean takeCompositorScreenshot(@Nullable Activity activity) {
if (!shouldTakeCompositorScreenshot((activity))) return false;
Rect rect = new Rect();
activity.getWindow().getDecorView().getRootView().getWindowVisibleDisplayFrame(rect);
nativeGrabWindowSnapshotAsync(
this, ((ChromeActivity) activity).getWindowAndroid(), rect.width(), rect.height());
return true;
}
private boolean takeAndroidViewScreenshot(@Nullable final Activity activity) {
if (activity == null) return false;
PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
@Override
public void run() {
Bitmap bitmap = UiUtils.generateScaledScreenshot(
activity.getWindow().getDecorView().getRootView(),
MAX_FEEDBACK_SCREENSHOT_DIMENSION, Bitmap.Config.ARGB_8888);
onBitmapReceived(bitmap);
}
});
return true;
}
private boolean shouldTakeCompositorScreenshot(Activity activity) {
// If Activity isn't a ChromeActivity, we aren't using the Compositor to render.
if (!(activity instanceof ChromeActivity)) return false;
ChromeActivity chromeActivity = (ChromeActivity) activity;
Tab currentTab = chromeActivity.getActivityTab();
// If the bottom sheet is currently open, then do not use the Compositor based screenshot
// so that the Android View for the bottom sheet will be captured.
// TODO(https://crbug.com/835862): When the sheet is partially opened both the compositor
// and Android views should be captured in the screenshot.
if (chromeActivity.getBottomSheet() != null
&& chromeActivity.getBottomSheet().isSheetOpen()) {
return false;
}
// If the tab is null, assume in the tab switcher so a Compositor snapshot is good.
if (currentTab == null) return true;
// If the tab is not interactable, also assume in the tab switcher.
if (!currentTab.isUserInteractable()) return true;
// If the tab focused and not showing Android widget based content, then use the Compositor
// based screenshot.
if (currentTab.getNativePage() == null && !SadTab.isShowing(currentTab)) return true;
// Assume the UI is drawn primarily by Android widgets, so do not use the Compositor
// screenshot.
return false;
}
private static native void nativeGrabWindowSnapshotAsync(
ScreenshotTask callback, WindowAndroid window, int width, int height);
}