Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9b963d3c authored by Adrian Roos's avatar Adrian Roos
Browse files

Add CompositionSamplingListener

Adds a facility to sample the median luma in a region
of the SurfaceComposer's result.

Test: atest CompositionSamplingListenerTest
Bug: 124305231
Change-Id: I78eececa9aef420f488a860f4e6891d4af84d27f
parent 06363e34
Loading
Loading
Loading
Loading
+90 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.
 */

package android.view;

import android.graphics.Rect;
import android.os.IBinder;

import com.android.internal.util.Preconditions;

import java.util.concurrent.Executor;

/**
 * Listener for sampling the result of the screen composition.
 * {@hide}
 */
public abstract class CompositionSamplingListener {

    private final long mNativeListener;
    private final Executor mExecutor;

    public CompositionSamplingListener(Executor executor) {
        mExecutor = executor;
        mNativeListener = nativeCreate(this);
    }

    @Override
    protected void finalize() throws Throwable {
        try {
            if (mNativeListener != 0) {
                unregister(this);
                nativeDestroy(mNativeListener);
            }
        } finally {
            super.finalize();
        }
    }

    /**
     * Reports a luma sample from the registered region.
     */
    public abstract void onSampleCollected(float medianLuma);

    /**
     * Registers a sampling listener.
     */
    public static void register(CompositionSamplingListener listener,
            int displayId, IBinder stopLayer, Rect samplingArea) {
        Preconditions.checkArgument(displayId == Display.DEFAULT_DISPLAY,
                "default display only for now");
        nativeRegister(listener.mNativeListener, stopLayer, samplingArea.left, samplingArea.top,
                samplingArea.right, samplingArea.bottom);
    }

    /**
     * Unregisters a sampling listener.
     */
    public static void unregister(CompositionSamplingListener listener) {
        nativeUnregister(listener.mNativeListener);
    }

    /**
     * Dispatch the collected sample.
     *
     * Called from native code on a binder thread.
     */
    private static void dispatchOnSampleCollected(CompositionSamplingListener listener,
            float medianLuma) {
        listener.mExecutor.execute(() -> listener.onSampleCollected(medianLuma));
    }

    private static native long nativeCreate(CompositionSamplingListener thiz);
    private static native void nativeDestroy(long ptr);
    private static native void nativeRegister(long ptr, IBinder stopLayer,
            int samplingAreaLeft, int top, int right, int bottom);
    private static native void nativeUnregister(long ptr);
}
+1 −0
Original line number Diff line number Diff line
@@ -62,6 +62,7 @@ cc_library_shared {
        "android_graphics_drawable_AnimatedVectorDrawable.cpp",
        "android_graphics_drawable_VectorDrawable.cpp",
        "android_graphics_Picture.cpp",
        "android_view_CompositionSamplingListener.cpp",
        "android_view_DisplayEventReceiver.cpp",
        "android_view_DisplayListCanvas.cpp",
        "android_view_TextureLayer.cpp",
+2 −0
Original line number Diff line number Diff line
@@ -162,6 +162,7 @@ extern int register_android_view_RenderNodeAnimator(JNIEnv* env);
extern int register_android_view_Surface(JNIEnv* env);
extern int register_android_view_SurfaceControl(JNIEnv* env);
extern int register_android_view_SurfaceSession(JNIEnv* env);
extern int register_android_view_CompositionSamplingListener(JNIEnv* env);
extern int register_android_view_TextureView(JNIEnv* env);
extern int register_android_view_ThreadedRenderer(JNIEnv* env);
extern int register_com_android_internal_view_animation_NativeInterpolatorFactoryHelper(JNIEnv *env);
@@ -1398,6 +1399,7 @@ static const RegJNIRec gRegJNI[] = {
    REG_JNI(register_android_view_Surface),
    REG_JNI(register_android_view_SurfaceControl),
    REG_JNI(register_android_view_SurfaceSession),
    REG_JNI(register_android_view_CompositionSamplingListener),
    REG_JNI(register_android_view_TextureView),
    REG_JNI(register_com_android_internal_view_animation_NativeInterpolatorFactoryHelper),
    REG_JNI(register_com_google_android_gles_jni_EGLImpl),
+135 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2012 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 "CompositionSamplingListener"

#include "android_util_Binder.h"

#include <nativehelper/JNIHelp.h>

#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/Log.h>
#include <utils/Log.h>
#include <utils/RefBase.h>
#include <binder/IServiceManager.h>

#include <gui/IRegionSamplingListener.h>
#include <gui/ISurfaceComposer.h>
#include <ui/Rect.h>

namespace android {

namespace {

struct {
    jclass mClass;
    jmethodID mDispatchOnSampleCollected;
} gListenerClassInfo;

struct CompositionSamplingListener : public BnRegionSamplingListener {
    CompositionSamplingListener(JNIEnv* env, jobject listener)
            : mListener(env->NewGlobalRef(listener)) {}

    void onSampleCollected(float medianLuma) override {
        JNIEnv* env = AndroidRuntime::getJNIEnv();
        LOG_ALWAYS_FATAL_IF(env == nullptr, "Unable to retrieve JNIEnv in onSampleCollected.");

        env->CallStaticVoidMethod(gListenerClassInfo.mClass,
                gListenerClassInfo.mDispatchOnSampleCollected, mListener,
                static_cast<jfloat>(medianLuma));
        if (env->ExceptionCheck()) {
            ALOGE("CompositionSamplingListener.onSampleCollected() failed.");
            LOGE_EX(env);
            env->ExceptionClear();
        }
    }

protected:
    virtual ~CompositionSamplingListener() {
        JNIEnv* env = AndroidRuntime::getJNIEnv();
        env->DeleteGlobalRef(mListener);
    }

private:
    jobject mListener;
};

jlong nativeCreate(JNIEnv* env, jclass clazz, jobject obj) {
    CompositionSamplingListener* listener = new CompositionSamplingListener(env, obj);
    listener->incStrong((void*)nativeCreate);
    return reinterpret_cast<jlong>(listener);
}

void nativeDestroy(JNIEnv* env, jclass clazz, jlong ptr) {
    CompositionSamplingListener* listener = reinterpret_cast<CompositionSamplingListener*>(ptr);
    listener->decStrong((void*)nativeCreate);
}

void nativeRegister(JNIEnv* env, jclass clazz, jlong ptr, jobject stopLayerTokenObj,
        jint left, jint top, jint right, jint bottom) {
    sp<CompositionSamplingListener> listener = reinterpret_cast<CompositionSamplingListener*>(ptr);
    sp<IBinder> stopLayerHandle = ibinderForJavaObject(env, stopLayerTokenObj);

    // TODO: Use SurfaceComposerClient once it has addRegionSamplingListener.
    sp<ISurfaceComposer> composer;
    if (getService(String16("SurfaceFlinger"), &composer) != NO_ERROR) {
        jniThrowRuntimeException(env, "Couldn't retrieve SurfaceFlinger");
        return;
    }

    composer->addRegionSamplingListener(
            Rect(left, top, right, bottom), stopLayerHandle, listener);
}

void nativeUnregister(JNIEnv* env, jclass clazz, jlong ptr) {
    sp<CompositionSamplingListener> listener = reinterpret_cast<CompositionSamplingListener*>(ptr);

    // TODO: Use SurfaceComposerClient once it has addRegionSamplingListener.
    sp<ISurfaceComposer> composer;
    if (getService(String16("SurfaceFlinger"), &composer) != NO_ERROR) {
        jniThrowRuntimeException(env, "Couldn't retrieve SurfaceFlinger");
        return;
    }

    composer->removeRegionSamplingListener(listener);
}

const JNINativeMethod gMethods[] = {
    /* name, signature, funcPtr */
    { "nativeCreate", "(Landroid/view/CompositionSamplingListener;)J",
            (void*)nativeCreate },
    { "nativeDestroy", "(J)V",
            (void*)nativeDestroy },
    { "nativeRegister", "(JLandroid/os/IBinder;IIII)V",
            (void*)nativeRegister },
    { "nativeUnregister", "(J)V",
            (void*)nativeUnregister }
};

} // namespace

int register_android_view_CompositionSamplingListener(JNIEnv* env) {
    int res = jniRegisterNativeMethods(env, "android/view/CompositionSamplingListener",
            gMethods, NELEM(gMethods));
    LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");

    jclass clazz = env->FindClass("android/view/CompositionSamplingListener");
    gListenerClassInfo.mDispatchOnSampleCollected = env->GetStaticMethodID(
            clazz, "dispatchOnSampleCollected", "(Landroid/view/CompositionSamplingListener;F)V");
    return 0;
}

} // namespace android
+49 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.
 */

package android.view;

import static android.view.Display.DEFAULT_DISPLAY;

import android.graphics.Rect;
import android.os.Binder;
import android.platform.test.annotations.Presubmit;

import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(AndroidJUnit4.class)
@SmallTest
@Presubmit
public class CompositionSamplingListenerTest {

    @Test
    public void testRegisterUnregister() {
        CompositionSamplingListener.register(mListener, DEFAULT_DISPLAY, new Binder(),
                new Rect(1, 1, 10, 10));
        CompositionSamplingListener.unregister(mListener);
    }

    private CompositionSamplingListener mListener = new CompositionSamplingListener(Runnable::run) {
        @Override
        public void onSampleCollected(float medianLuma) {
            // Ignore
        }
    };
}