blob: 3bada15aa834496dfd1efe28a7f6a8eb4c91a177 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "ScreenCapture"
// #define LOG_NDEBUG 0
#include <android/gui/BnScreenCaptureListener.h>
#include <android_runtime/android_hardware_HardwareBuffer.h>
#include <gui/SurfaceComposerClient.h>
#include <jni.h>
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedPrimitiveArray.h>
#include <ui/GraphicTypes.h>
#include "android_os_Parcel.h"
#include "android_util_Binder.h"
#include "core_jni_helpers.h"
#include "jni_common.h"
// ----------------------------------------------------------------------------
namespace android {
using gui::CaptureArgs;
static struct {
jfieldID pixelFormat;
jfieldID sourceCrop;
jfieldID frameScaleX;
jfieldID frameScaleY;
jfieldID captureSecureLayers;
jfieldID allowProtected;
jfieldID uid;
jfieldID grayscale;
} gCaptureArgsClassInfo;
static struct {
jfieldID displayToken;
jfieldID width;
jfieldID height;
jfieldID useIdentityTransform;
} gDisplayCaptureArgsClassInfo;
static struct {
jfieldID layer;
jfieldID excludeLayers;
jfieldID childrenOnly;
} gLayerCaptureArgsClassInfo;
static struct {
jclass clazz;
jmethodID onScreenCaptureComplete;
} gScreenCaptureListenerClassInfo;
static struct {
jclass clazz;
jmethodID builder;
} gScreenshotHardwareBufferClassInfo;
enum JNamedColorSpace : jint {
// ColorSpace.Named.SRGB.ordinal() = 0;
SRGB = 0,
// ColorSpace.Named.DISPLAY_P3.ordinal() = 7;
DISPLAY_P3 = 7,
};
constexpr jint fromDataspaceToNamedColorSpaceValue(const ui::Dataspace dataspace) {
switch (dataspace) {
case ui::Dataspace::DISPLAY_P3:
return JNamedColorSpace::DISPLAY_P3;
default:
return JNamedColorSpace::SRGB;
}
}
static void checkAndClearException(JNIEnv* env, const char* methodName) {
if (env->ExceptionCheck()) {
ALOGE("An exception was thrown by callback '%s'.", methodName);
env->ExceptionClear();
}
}
class ScreenCaptureListenerWrapper : public gui::BnScreenCaptureListener {
public:
explicit ScreenCaptureListenerWrapper(JNIEnv* env, jobject jobject) {
env->GetJavaVM(&mVm);
mScreenCaptureListenerObject = env->NewGlobalRef(jobject);
LOG_ALWAYS_FATAL_IF(!mScreenCaptureListenerObject, "Failed to make global ref");
}
~ScreenCaptureListenerWrapper() {
if (mScreenCaptureListenerObject) {
getenv()->DeleteGlobalRef(mScreenCaptureListenerObject);
mScreenCaptureListenerObject = nullptr;
}
}
binder::Status onScreenCaptureCompleted(
const gui::ScreenCaptureResults& captureResults) override {
JNIEnv* env = getenv();
if (!captureResults.fenceResult.ok() || captureResults.buffer == nullptr) {
env->CallVoidMethod(mScreenCaptureListenerObject,
gScreenCaptureListenerClassInfo.onScreenCaptureComplete, nullptr);
checkAndClearException(env, "onScreenCaptureComplete");
return binder::Status::ok();
}
captureResults.fenceResult.value()->waitForever(LOG_TAG);
jobject jhardwareBuffer = android_hardware_HardwareBuffer_createFromAHardwareBuffer(
env, captureResults.buffer->toAHardwareBuffer());
const jint namedColorSpace =
fromDataspaceToNamedColorSpaceValue(captureResults.capturedDataspace);
jobject screenshotHardwareBuffer =
env->CallStaticObjectMethod(gScreenshotHardwareBufferClassInfo.clazz,
gScreenshotHardwareBufferClassInfo.builder,
jhardwareBuffer, namedColorSpace,
captureResults.capturedSecureLayers,
captureResults.capturedHdrLayers);
checkAndClearException(env, "builder");
env->CallVoidMethod(mScreenCaptureListenerObject,
gScreenCaptureListenerClassInfo.onScreenCaptureComplete,
screenshotHardwareBuffer);
checkAndClearException(env, "onScreenCaptureComplete");
env->DeleteLocalRef(jhardwareBuffer);
env->DeleteLocalRef(screenshotHardwareBuffer);
return binder::Status::ok();
}
private:
jobject mScreenCaptureListenerObject;
JavaVM* mVm;
JNIEnv* getenv() {
JNIEnv* env;
if (mVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
if (mVm->AttachCurrentThreadAsDaemon(&env, nullptr) != JNI_OK) {
LOG_ALWAYS_FATAL("Failed to AttachCurrentThread!");
}
}
return env;
}
};
static void getCaptureArgs(JNIEnv* env, jobject captureArgsObject, CaptureArgs& captureArgs) {
captureArgs.pixelFormat = static_cast<ui::PixelFormat>(
env->GetIntField(captureArgsObject, gCaptureArgsClassInfo.pixelFormat));
captureArgs.sourceCrop =
JNICommon::rectFromObj(env,
env->GetObjectField(captureArgsObject,
gCaptureArgsClassInfo.sourceCrop));
captureArgs.frameScaleX =
env->GetFloatField(captureArgsObject, gCaptureArgsClassInfo.frameScaleX);
captureArgs.frameScaleY =
env->GetFloatField(captureArgsObject, gCaptureArgsClassInfo.frameScaleY);
captureArgs.captureSecureLayers =
env->GetBooleanField(captureArgsObject, gCaptureArgsClassInfo.captureSecureLayers);
captureArgs.allowProtected =
env->GetBooleanField(captureArgsObject, gCaptureArgsClassInfo.allowProtected);
captureArgs.uid = env->GetLongField(captureArgsObject, gCaptureArgsClassInfo.uid);
captureArgs.grayscale =
env->GetBooleanField(captureArgsObject, gCaptureArgsClassInfo.grayscale);
}
static DisplayCaptureArgs displayCaptureArgsFromObject(JNIEnv* env,
jobject displayCaptureArgsObject) {
DisplayCaptureArgs captureArgs;
getCaptureArgs(env, displayCaptureArgsObject, captureArgs);
captureArgs.displayToken =
ibinderForJavaObject(env,
env->GetObjectField(displayCaptureArgsObject,
gDisplayCaptureArgsClassInfo.displayToken));
captureArgs.width =
env->GetIntField(displayCaptureArgsObject, gDisplayCaptureArgsClassInfo.width);
captureArgs.height =
env->GetIntField(displayCaptureArgsObject, gDisplayCaptureArgsClassInfo.height);
captureArgs.useIdentityTransform =
env->GetBooleanField(displayCaptureArgsObject,
gDisplayCaptureArgsClassInfo.useIdentityTransform);
return captureArgs;
}
static jint nativeCaptureDisplay(JNIEnv* env, jclass clazz, jobject displayCaptureArgsObject,
jobject screenCaptureListenerObject) {
const DisplayCaptureArgs captureArgs =
displayCaptureArgsFromObject(env, displayCaptureArgsObject);
if (captureArgs.displayToken == nullptr) {
return BAD_VALUE;
}
sp<IScreenCaptureListener> captureListener =
sp<ScreenCaptureListenerWrapper>::make(env, screenCaptureListenerObject);
return ScreenshotClient::captureDisplay(captureArgs, captureListener);
}
static jint nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerCaptureArgsObject,
jobject screenCaptureListenerObject) {
LayerCaptureArgs captureArgs;
getCaptureArgs(env, layerCaptureArgsObject, captureArgs);
SurfaceControl* layer = reinterpret_cast<SurfaceControl*>(
env->GetLongField(layerCaptureArgsObject, gLayerCaptureArgsClassInfo.layer));
if (layer == nullptr) {
return BAD_VALUE;
}
captureArgs.layerHandle = layer->getHandle();
captureArgs.childrenOnly =
env->GetBooleanField(layerCaptureArgsObject, gLayerCaptureArgsClassInfo.childrenOnly);
jlongArray excludeObjectArray = reinterpret_cast<jlongArray>(
env->GetObjectField(layerCaptureArgsObject, gLayerCaptureArgsClassInfo.excludeLayers));
if (excludeObjectArray != nullptr) {
ScopedLongArrayRO excludeArray(env, excludeObjectArray);
const jsize len = excludeArray.size();
captureArgs.excludeHandles.reserve(len);
for (jsize i = 0; i < len; i++) {
auto excludeObject = reinterpret_cast<SurfaceControl*>(excludeArray[i]);
if (excludeObject == nullptr) {
jniThrowNullPointerException(env, "Exclude layer is null");
return BAD_VALUE;
}
captureArgs.excludeHandles.emplace(excludeObject->getHandle());
}
}
sp<IScreenCaptureListener> captureListener =
sp<ScreenCaptureListenerWrapper>::make(env, screenCaptureListenerObject);
return ScreenshotClient::captureLayers(captureArgs, captureListener);
}
// ----------------------------------------------------------------------------
static const JNINativeMethod sScreenCaptureMethods[] = {
// clang-format off
{"nativeCaptureDisplay",
"(Landroid/window/ScreenCapture$DisplayCaptureArgs;Landroid/window/ScreenCapture$ScreenCaptureListener;)I",
(void*)nativeCaptureDisplay },
{"nativeCaptureLayers",
"(Landroid/window/ScreenCapture$LayerCaptureArgs;Landroid/window/ScreenCapture$ScreenCaptureListener;)I",
(void*)nativeCaptureLayers },
// clang-format on
};
int register_android_window_ScreenCapture(JNIEnv* env) {
int err = RegisterMethodsOrDie(env, "android/window/ScreenCapture", sScreenCaptureMethods,
NELEM(sScreenCaptureMethods));
jclass captureArgsClazz = FindClassOrDie(env, "android/window/ScreenCapture$CaptureArgs");
gCaptureArgsClassInfo.pixelFormat = GetFieldIDOrDie(env, captureArgsClazz, "mPixelFormat", "I");
gCaptureArgsClassInfo.sourceCrop =
GetFieldIDOrDie(env, captureArgsClazz, "mSourceCrop", "Landroid/graphics/Rect;");
gCaptureArgsClassInfo.frameScaleX = GetFieldIDOrDie(env, captureArgsClazz, "mFrameScaleX", "F");
gCaptureArgsClassInfo.frameScaleY = GetFieldIDOrDie(env, captureArgsClazz, "mFrameScaleY", "F");
gCaptureArgsClassInfo.captureSecureLayers =
GetFieldIDOrDie(env, captureArgsClazz, "mCaptureSecureLayers", "Z");
gCaptureArgsClassInfo.allowProtected =
GetFieldIDOrDie(env, captureArgsClazz, "mAllowProtected", "Z");
gCaptureArgsClassInfo.uid = GetFieldIDOrDie(env, captureArgsClazz, "mUid", "J");
gCaptureArgsClassInfo.grayscale = GetFieldIDOrDie(env, captureArgsClazz, "mGrayscale", "Z");
jclass displayCaptureArgsClazz =
FindClassOrDie(env, "android/window/ScreenCapture$DisplayCaptureArgs");
gDisplayCaptureArgsClassInfo.displayToken =
GetFieldIDOrDie(env, displayCaptureArgsClazz, "mDisplayToken", "Landroid/os/IBinder;");
gDisplayCaptureArgsClassInfo.width =
GetFieldIDOrDie(env, displayCaptureArgsClazz, "mWidth", "I");
gDisplayCaptureArgsClassInfo.height =
GetFieldIDOrDie(env, displayCaptureArgsClazz, "mHeight", "I");
gDisplayCaptureArgsClassInfo.useIdentityTransform =
GetFieldIDOrDie(env, displayCaptureArgsClazz, "mUseIdentityTransform", "Z");
jclass layerCaptureArgsClazz =
FindClassOrDie(env, "android/window/ScreenCapture$LayerCaptureArgs");
gLayerCaptureArgsClassInfo.layer =
GetFieldIDOrDie(env, layerCaptureArgsClazz, "mNativeLayer", "J");
gLayerCaptureArgsClassInfo.excludeLayers =
GetFieldIDOrDie(env, layerCaptureArgsClazz, "mNativeExcludeLayers", "[J");
gLayerCaptureArgsClassInfo.childrenOnly =
GetFieldIDOrDie(env, layerCaptureArgsClazz, "mChildrenOnly", "Z");
jclass screenCaptureListenerClazz =
FindClassOrDie(env, "android/window/ScreenCapture$ScreenCaptureListener");
gScreenCaptureListenerClassInfo.clazz = MakeGlobalRefOrDie(env, screenCaptureListenerClazz);
gScreenCaptureListenerClassInfo.onScreenCaptureComplete =
GetMethodIDOrDie(env, screenCaptureListenerClazz, "onScreenCaptureComplete",
"(Landroid/window/ScreenCapture$ScreenshotHardwareBuffer;)V");
jclass screenshotGraphicsBufferClazz =
FindClassOrDie(env, "android/window/ScreenCapture$ScreenshotHardwareBuffer");
gScreenshotHardwareBufferClassInfo.clazz =
MakeGlobalRefOrDie(env, screenshotGraphicsBufferClazz);
gScreenshotHardwareBufferClassInfo.builder =
GetStaticMethodIDOrDie(env, screenshotGraphicsBufferClazz, "createFromNative",
"(Landroid/hardware/HardwareBuffer;IZZ)Landroid/window/"
"ScreenCapture$ScreenshotHardwareBuffer;");
return err;
}
} // namespace android