// Copyright 2015 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 org.chromium.base.ThreadUtils;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.ui.UiUtils;
import org.chromium.ui.base.WindowAndroid;
import javax.annotation.Nullable;
/**
* A utility class to take a feedback-formatted screenshot of an {@link Activity}.
*/
@JNINamespace("chrome::android")
public final class ScreenshotTask {
/**
* 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;
/**
* A callback passed to {@link #create} which will get a bitmap version of the screenshot.
*/
public interface ScreenshotTaskCallback {
/**
* Called when collection of the bitmap has completed.
* @param bitmap the bitmap or null.
*/
void onGotBitmap(@Nullable Bitmap bitmap);
}
/**
* Prepares screenshot (possibly asynchronously) and invokes the callback when the screenshot
* is available, or collection has failed. The asynchronous path is only taken when the activity
* that is passed in is a {@link ChromeActivity}.
* The callback is always invoked asynchronously.
*/
public static void create(Activity activity, final ScreenshotTaskCallback callback) {
if (activity instanceof ChromeActivity) {
Rect rect = new Rect();
activity.getWindow().getDecorView().getRootView().getWindowVisibleDisplayFrame(rect);
createCompositorScreenshot(((ChromeActivity) activity).getWindowAndroid(), rect,
callback);
return;
}
final Bitmap bitmap = prepareScreenshot(activity, null);
ThreadUtils.postOnUiThread(new Runnable() {
@Override
public void run() {
callback.onGotBitmap(bitmap);
}
});
}
/**
* A callback passed to the native snapshot API which returns the result in PNG format.
*/
private interface SnapshotResultCallback {
/**
* Called when collection of the bitmap has completed.
* @param pngBytes PNG-formatted bitmap in byte array if successful; otherwise null
*/
void onCompleted(@Nullable byte[] pngBytes);
}
private static void createCompositorScreenshot(WindowAndroid windowAndroid,
Rect windowRect, final ScreenshotTaskCallback callback) {
SnapshotResultCallback resultCallback = new SnapshotResultCallback() {
@Override
public void onCompleted(byte[] pngBytes) {
callback.onGotBitmap(pngBytes != null
? BitmapFactory.decodeByteArray(pngBytes, 0, pngBytes.length) : null);
}
};
nativeGrabWindowSnapshotAsync(resultCallback, windowAndroid.getNativePointer(),
windowRect.width(), windowRect.height());
}
/**
* Prepares a given screenshot for sending with a feedback.
* If no screenshot is given it creates one from the activity View if an activity is provided.
* @param activity An activity or null
* @param bitmap A screenshot or null
* @return A feedback-ready screenshot or null
*/
private static Bitmap prepareScreenshot(@Nullable Activity activity, @Nullable Bitmap bitmap) {
if (bitmap == null) {
if (activity == null) return null;
return UiUtils.generateScaledScreenshot(
activity.getWindow().getDecorView().getRootView(),
MAX_FEEDBACK_SCREENSHOT_DIMENSION, Bitmap.Config.ARGB_8888);
}
int screenshotMaxDimension = Math.max(bitmap.getWidth(), bitmap.getHeight());
if (screenshotMaxDimension <= MAX_FEEDBACK_SCREENSHOT_DIMENSION) return bitmap;
float screenshotScale = (float) MAX_FEEDBACK_SCREENSHOT_DIMENSION / screenshotMaxDimension;
int destWidth = (int) (bitmap.getWidth() * screenshotScale);
int destHeight = (int) (bitmap.getHeight() * screenshotScale);
return Bitmap.createScaledBitmap(bitmap, destWidth, destHeight, true);
}
@CalledByNative
private static void notifySnapshotFinished(Object callback, byte[] pngBytes) {
((SnapshotResultCallback) callback).onCompleted(pngBytes);
}
// This is a utility class, so it should never be created.
private ScreenshotTask() {}
private static native void nativeGrabWindowSnapshotAsync(SnapshotResultCallback callback,
long nativeWindowAndroid, int width, int height);
}