blob: 6e903b3ab56dbda405d9cc29160349ee646cd580 [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;
jmethodID getNativeExcludeLayers;
jfieldID hintForSeamlessTransition;
} gCaptureArgsClassInfo;
static struct {
jfieldID displayToken;
jfieldID width;
jfieldID height;
} gDisplayCaptureArgsClassInfo;
static struct {
jfieldID layer;
jfieldID childrenOnly;
} gLayerCaptureArgsClassInfo;
static struct {
jmethodID accept;
} gConsumerClassInfo;
static struct {
jclass clazz;
jmethodID builder;
} gScreenshotHardwareBufferClassInfo;
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);
mConsumerWeak = env->NewWeakGlobalRef(jobject);
}
~ScreenCaptureListenerWrapper() {
if (mConsumerWeak) {
getenv()->DeleteWeakGlobalRef(mConsumerWeak);
mConsumerWeak = nullptr;
}
}
binder::Status onScreenCaptureCompleted(
const gui::ScreenCaptureResults& captureResults) override {
JNIEnv* env = getenv();
ScopedLocalRef<jobject> consumer{env, env->NewLocalRef(mConsumerWeak)};
if (consumer == nullptr) {
ALOGE("ScreenCaptureListenerWrapper consumer not alive.");
return binder::Status::ok();
}
if (!captureResults.fenceResult.ok() || captureResults.buffer == nullptr) {
env->CallVoidMethod(consumer.get(), gConsumerClassInfo.accept, nullptr,
fenceStatus(captureResults.fenceResult));
checkAndClearException(env, "accept");
return binder::Status::ok();
}
captureResults.fenceResult.value()->waitForever(LOG_TAG);
jobject jhardwareBuffer = android_hardware_HardwareBuffer_createFromAHardwareBuffer(
env, captureResults.buffer->toAHardwareBuffer());
jobject screenshotHardwareBuffer =
env->CallStaticObjectMethod(gScreenshotHardwareBufferClassInfo.clazz,
gScreenshotHardwareBufferClassInfo.builder,
jhardwareBuffer,
static_cast<jint>(captureResults.capturedDataspace),
captureResults.capturedSecureLayers,
captureResults.capturedHdrLayers);
checkAndClearException(env, "builder");
env->CallVoidMethod(consumer.get(), gConsumerClassInfo.accept, screenshotHardwareBuffer,
fenceStatus(captureResults.fenceResult));
checkAndClearException(env, "accept");
env->DeleteLocalRef(jhardwareBuffer);
env->DeleteLocalRef(screenshotHardwareBuffer);
return binder::Status::ok();
}
private:
jweak mConsumerWeak;
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);
jlongArray excludeObjectArray = reinterpret_cast<jlongArray>(
env->CallObjectMethod(captureArgsObject, gCaptureArgsClassInfo.getNativeExcludeLayers));
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;
}
captureArgs.excludeHandles.emplace(excludeObject->getHandle());
}
}
captureArgs.hintForSeamlessTransition =
env->GetBooleanField(captureArgsObject,
gCaptureArgsClassInfo.hintForSeamlessTransition);
}
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);
return captureArgs;
}
static jint nativeCaptureDisplay(JNIEnv* env, jclass clazz, jobject displayCaptureArgsObject,
jlong screenCaptureListenerObject) {
const DisplayCaptureArgs captureArgs =
displayCaptureArgsFromObject(env, displayCaptureArgsObject);
if (captureArgs.displayToken == nullptr) {
return BAD_VALUE;
}
sp<gui::IScreenCaptureListener> captureListener =
reinterpret_cast<gui::IScreenCaptureListener*>(screenCaptureListenerObject);
return ScreenshotClient::captureDisplay(captureArgs, captureListener);
}
static jint nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerCaptureArgsObject,
jlong 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);
sp<gui::IScreenCaptureListener> captureListener =
reinterpret_cast<gui::IScreenCaptureListener*>(screenCaptureListenerObject);
return ScreenshotClient::captureLayers(captureArgs, captureListener);
}
static jlong nativeCreateScreenCaptureListener(JNIEnv* env, jclass clazz, jobject consumerObj) {
sp<gui::IScreenCaptureListener> listener =
sp<ScreenCaptureListenerWrapper>::make(env, consumerObj);
listener->incStrong((void*)nativeCreateScreenCaptureListener);
return reinterpret_cast<jlong>(listener.get());
}
static void nativeWriteListenerToParcel(JNIEnv* env, jclass clazz, jlong nativeObject,
jobject parcelObj) {
Parcel* parcel = parcelForJavaObject(env, parcelObj);
if (parcel == NULL) {
jniThrowNullPointerException(env, NULL);
return;
}
ScreenCaptureListenerWrapper* const self =
reinterpret_cast<ScreenCaptureListenerWrapper*>(nativeObject);
if (self != nullptr) {
parcel->writeStrongBinder(IInterface::asBinder(self));
}
}
static jlong nativeReadListenerFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj) {
Parcel* parcel = parcelForJavaObject(env, parcelObj);
if (parcel == NULL) {
jniThrowNullPointerException(env, NULL);
return 0;
}
sp<gui::IScreenCaptureListener> listener =
interface_cast<gui::IScreenCaptureListener>(parcel->readStrongBinder());
if (listener == nullptr) {
return 0;
}
listener->incStrong((void*)nativeCreateScreenCaptureListener);
return reinterpret_cast<jlong>(listener.get());
}
void destroyNativeListener(void* ptr) {
ScreenCaptureListenerWrapper* listener = reinterpret_cast<ScreenCaptureListenerWrapper*>(ptr);
listener->decStrong((void*)nativeCreateScreenCaptureListener);
}
static jlong getNativeListenerFinalizer(JNIEnv* env, jclass clazz) {
return static_cast<jlong>(reinterpret_cast<uintptr_t>(&destroyNativeListener));
}
// ----------------------------------------------------------------------------
static const JNINativeMethod sScreenCaptureMethods[] = {
// clang-format off
{"nativeCaptureDisplay", "(Landroid/window/ScreenCapture$DisplayCaptureArgs;J)I",
(void*)nativeCaptureDisplay },
{"nativeCaptureLayers", "(Landroid/window/ScreenCapture$LayerCaptureArgs;J)I",
(void*)nativeCaptureLayers },
{"nativeCreateScreenCaptureListener", "(Ljava/util/function/ObjIntConsumer;)J",
(void*)nativeCreateScreenCaptureListener },
{"nativeWriteListenerToParcel", "(JLandroid/os/Parcel;)V", (void*)nativeWriteListenerToParcel },
{"nativeReadListenerFromParcel", "(Landroid/os/Parcel;)J",
(void*)nativeReadListenerFromParcel },
{"getNativeListenerFinalizer", "()J", (void*)getNativeListenerFinalizer },
// 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");
gCaptureArgsClassInfo.getNativeExcludeLayers =
GetMethodIDOrDie(env, captureArgsClazz, "getNativeExcludeLayers", "()[J");
gCaptureArgsClassInfo.hintForSeamlessTransition =
GetFieldIDOrDie(env, captureArgsClazz, "mHintForSeamlessTransition", "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");
jclass layerCaptureArgsClazz =
FindClassOrDie(env, "android/window/ScreenCapture$LayerCaptureArgs");
gLayerCaptureArgsClassInfo.layer =
GetFieldIDOrDie(env, layerCaptureArgsClazz, "mNativeLayer", "J");
gLayerCaptureArgsClassInfo.childrenOnly =
GetFieldIDOrDie(env, layerCaptureArgsClazz, "mChildrenOnly", "Z");
jclass consumer = FindClassOrDie(env, "java/util/function/ObjIntConsumer");
gConsumerClassInfo.accept = GetMethodIDOrDie(env, consumer, "accept", "(Ljava/lang/Object;I)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