summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt1
-rw-r--r--core/api/system-current.txt12
-rw-r--r--core/java/android/view/SurfaceControl.java100
-rw-r--r--core/java/android/view/SurfaceControlActivePicture.java58
-rw-r--r--core/java/android/view/SurfaceControlActivePictureListener.java69
-rw-r--r--core/jni/Android.bp1
-rw-r--r--core/jni/AndroidRuntime.cpp2
-rw-r--r--core/jni/android_view_SurfaceControl.cpp34
-rw-r--r--core/jni/android_view_SurfaceControlActivePictureListener.cpp191
-rw-r--r--media/java/android/media/quality/PictureProfileHandle.java38
-rw-r--r--tests/CtsSurfaceControlTestsStaging/AndroidManifest.xml8
-rw-r--r--tests/CtsSurfaceControlTestsStaging/AndroidTest.xml3
-rw-r--r--tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlPictureProfileTest.java260
-rw-r--r--tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlPictureProfileTestActivity.java43
-rw-r--r--tests/CtsSurfaceControlTestsStaging/src/main/res/layout/picture_profile_test_layout.xml32
15 files changed, 838 insertions, 14 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index 77d39badeaaf..73d0d5baa6f7 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -53413,6 +53413,7 @@ package android.view {
method @NonNull public android.view.SurfaceControl.Transaction setBuffer(@NonNull android.view.SurfaceControl, @Nullable android.hardware.HardwareBuffer, @Nullable android.hardware.SyncFence, @Nullable java.util.function.Consumer<android.hardware.SyncFence>);
method @NonNull public android.view.SurfaceControl.Transaction setBufferSize(@NonNull android.view.SurfaceControl, @IntRange(from=0) int, @IntRange(from=0) int);
method @NonNull public android.view.SurfaceControl.Transaction setBufferTransform(@NonNull android.view.SurfaceControl, int);
+ method @FlaggedApi("android.media.tv.flags.apply_picture_profiles") @NonNull public android.view.SurfaceControl.Transaction setContentPriority(@NonNull android.view.SurfaceControl, int);
method @NonNull public android.view.SurfaceControl.Transaction setCrop(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect);
method @NonNull public android.view.SurfaceControl.Transaction setDamageRegion(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Region);
method @NonNull public android.view.SurfaceControl.Transaction setDataSpace(@NonNull android.view.SurfaceControl, int);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index c184ba54bdf7..a0d10f5f9751 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -8171,6 +8171,14 @@ package android.media.quality {
method @NonNull public android.media.quality.PictureProfile.Builder setProfileType(int);
}
+ @FlaggedApi("android.media.tv.flags.apply_picture_profiles") public final class PictureProfileHandle implements android.os.Parcelable {
+ method @FlaggedApi("android.media.tv.flags.apply_picture_profiles") public int describeContents();
+ method @FlaggedApi("android.media.tv.flags.apply_picture_profiles") public long getId();
+ method @FlaggedApi("android.media.tv.flags.apply_picture_profiles") public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @FlaggedApi("android.media.tv.flags.apply_picture_profiles") @NonNull public static final android.os.Parcelable.Creator<android.media.quality.PictureProfileHandle> CREATOR;
+ field @NonNull public static final android.media.quality.PictureProfileHandle NONE;
+ }
+
}
package android.media.session {
@@ -18999,6 +19007,10 @@ package android.util {
package android.view {
+ public static class SurfaceControl.Transaction implements java.io.Closeable android.os.Parcelable {
+ method @FlaggedApi("android.media.tv.flags.apply_picture_profiles") @NonNull public android.view.SurfaceControl.Transaction setPictureProfileHandle(@NonNull android.view.SurfaceControl, @NonNull android.media.quality.PictureProfileHandle);
+ }
+
@UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback {
method @NonNull public final java.util.List<android.graphics.Rect> getUnrestrictedPreferKeepClearRects();
method @RequiresPermission(android.Manifest.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS) public final void setUnrestrictedPreferKeepClearRects(@NonNull java.util.List<android.graphics.Rect>);
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 446d23e17903..68674dd402b4 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -37,6 +37,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.Size;
+import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.ColorSpace;
@@ -58,6 +59,7 @@ import android.hardware.display.DeviceProductInfo;
import android.hardware.display.DisplayedContentSample;
import android.hardware.display.DisplayedContentSamplingAttributes;
import android.hardware.graphics.common.DisplayDecorationSupport;
+import android.media.quality.PictureProfileHandle;
import android.opengl.EGLDisplay;
import android.opengl.EGLSync;
import android.os.Build;
@@ -234,7 +236,6 @@ public final class SurfaceControl implements Parcelable {
long nativeObject, float currentBufferRatio, float desiredRatio);
private static native void nativeSetDesiredHdrHeadroom(long transactionObj,
long nativeObject, float desiredRatio);
-
private static native void nativeSetCachingHint(long transactionObj,
long nativeObject, int cachingHint);
private static native void nativeSetDamageRegion(long transactionObj, long nativeObject,
@@ -314,6 +315,11 @@ public final class SurfaceControl implements Parcelable {
private static native void nativeSetLuts(long transactionObj, long nativeObject,
float[] buffers, int[] slots, int[] dimensions, int[] sizes, int[] samplingKeys);
private static native void nativeEnableDebugLogCallPoints(long transactionObj);
+ private static native int nativeGetMaxPictureProfiles();
+ private static native void nativeSetPictureProfileId(long transactionObj,
+ long nativeObject, long pictureProfileId);
+ private static native void nativeSetContentPriority(long transactionObj, long nativeObject,
+ int priority);
/**
* Transforms that can be applied to buffers as they are displayed to a window.
@@ -2836,6 +2842,33 @@ public final class SurfaceControl implements Parcelable {
}
/**
+ * Retrieve the maximum number of concurrent picture profiles allowed across all displays.
+ *
+ * A picture profile is assigned to a layer via:
+ * <ul>
+ * <li>Picture processing via {@link MediaCodec.KEY_PICTURE_PROFILE}</li>
+ * <li>Picture processing via {@link SurfaceControl.Transaction#setPictureProfileHandle}
+ * </li>
+ * </ul>
+ *
+ * If the maximum number is exceeded, some layers will not receive profiles based on:
+ * <ul>
+ * <li>The content priority assigned by the app</li>
+ * <li>The system-determined priority of the app owning the layer</li>
+ * </ul>
+ *
+ * @see MediaCodec.KEY_PICTURE_PROFILE
+ * @see SurfaceControl.Transaction#setPictureProfileHandle
+ * @see SurfaceControl.Transaction#setContentPriority
+ *
+ * @hide
+ */
+ @IntRange(from = 0)
+ public static int getMaxPictureProfiles() {
+ return nativeGetMaxPictureProfiles();
+ }
+
+ /**
* Interface to handle request to
* {@link SurfaceControl.Transaction#addTransactionCommittedListener(Executor, TransactionCommittedListener)}
*/
@@ -4598,6 +4631,71 @@ public final class SurfaceControl implements Parcelable {
}
/**
+ * Sets the desired picture profile handle for a layer.
+ * <p>
+ * A handle, retrieved from {@link MediaQualityManager#getProfileHandles}, which
+ * refers a set of parameters that are used to configure picture processing that is applied
+ * to all subsequent buffers to enhance the quality of their contents (e.g. gamma, color
+ * temperature, hue, saturation, etc.).
+ * <p>
+ * Setting a handle does not guarantee access to limited picture processing. The content
+ * priority for the as well as the prominance of app to the current user experience plays a
+ * role in which layer(s) get access to the limited picture processing resources. A maximum
+ * number of {@link MediaQualityManager.getMaxPictureProfiles} can be applied at any given
+ * point in time.
+ *
+ * @param sc The SurfaceControl of the layer that should be updated
+ * @param handle The picture profile handle which refers to the set of desired parameters
+ *
+ * @see MediaQualityManager#getMaxPictureProfiles
+ * @see MediaQualityManager#getProfileHandles
+ * @see MediaCodec.KEY_PICTURE_PROFILE
+ * @see SurfaceControl.Transaction#setContentPriority
+ *
+ * @hide
+ */
+ @FlaggedApi(android.media.tv.flags.Flags.FLAG_APPLY_PICTURE_PROFILES)
+ @SystemApi
+ public @NonNull Transaction setPictureProfileHandle(@NonNull SurfaceControl sc,
+ @NonNull PictureProfileHandle handle) {
+ checkPreconditions(sc);
+
+ nativeSetPictureProfileId(mNativeObject, sc.mNativeObject, handle.getId());
+ return this;
+ }
+
+ /**
+ * Sets the importance the layer's contents has to the app's user experience.
+ * <p>
+ * When a two layers within the same app are competing for a limited rendering resource,
+ * the priority will determine which layer gets access to the resource. The lower the
+ * priority, the more likely the layer will get access to the resource.
+ * <p>
+ * Resources managed by this priority:
+ * <ul>
+ * <li>Picture processing via {@link MediaCodec.KEY_PICTURE_PROFILE}</li>
+ * <li>Picture processing via {@link SurfaceControl.Transaction#setPictureProfileHandle}
+ * </li>
+ * </ul>
+ *
+ * @param sc The SurfaceControl of the layer that should be updated
+ * @param priority The priority this layer should have with respect to other layers in the
+ * app. The default priority is zero.
+ *
+ * @see MediaQualityManager#getMaxPictureProfiles
+ * @see MediaCodec.KEY_PICTURE_PROFILE
+ * @see SurfaceControl.Transaction#setPictureProfileHandle
+ */
+ @FlaggedApi(android.media.tv.flags.Flags.FLAG_APPLY_PICTURE_PROFILES)
+ public @NonNull Transaction setContentPriority(@NonNull SurfaceControl sc,
+ int priority) {
+ checkPreconditions(sc);
+
+ nativeSetContentPriority(mNativeObject, sc.mNativeObject, priority);
+ return this;
+ }
+
+ /**
* Sets the caching hint for the layer. By default, the caching hint is
* {@link CACHING_ENABLED}.
*
diff --git a/core/java/android/view/SurfaceControlActivePicture.java b/core/java/android/view/SurfaceControlActivePicture.java
new file mode 100644
index 000000000000..569159d73d2d
--- /dev/null
+++ b/core/java/android/view/SurfaceControlActivePicture.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2024 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.annotation.NonNull;
+import android.media.quality.PictureProfileHandle;
+
+/**
+ * A record of a visible layer that is using picture processing.
+ * @hide
+ */
+public class SurfaceControlActivePicture {
+ private final int mLayerId;
+ private final int mOwnerUid;
+ private final @NonNull PictureProfileHandle mPictureProfileHandle;
+
+ /**
+ * Create a record of a visible layer that is using picture processing.
+ *
+ * @param layerId the layer that is using picture processing
+ * @param ownerUid the UID of the package that owns the layer
+ * @param handle the handle for the picture profile that configured the processing
+ */
+ private SurfaceControlActivePicture(int layerId, int ownerUid, PictureProfileHandle handle) {
+ mLayerId = layerId;
+ mOwnerUid = ownerUid;
+ mPictureProfileHandle = handle;
+ }
+
+ /** The layer that is using picture processing. */
+ public int getLayerId() {
+ return mLayerId;
+ }
+
+ /** The UID of the package that owns the layer using picture processing. */
+ public int getOwnerUid() {
+ return mOwnerUid;
+ }
+
+ /** A handle that indicates which picture profile has configured the picture processing. */
+ public @NonNull PictureProfileHandle getPictureProfileHandle() {
+ return mPictureProfileHandle;
+ }
+}
diff --git a/core/java/android/view/SurfaceControlActivePictureListener.java b/core/java/android/view/SurfaceControlActivePictureListener.java
new file mode 100644
index 000000000000..d7c53748784f
--- /dev/null
+++ b/core/java/android/view/SurfaceControlActivePictureListener.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2024 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.annotation.RequiresPermission;
+
+import libcore.util.NativeAllocationRegistry;
+
+/**
+ * Allows for the monitoring of visible layers that are using picture processing.
+ * @hide
+ */
+public abstract class SurfaceControlActivePictureListener {
+ private static final NativeAllocationRegistry sRegistry =
+ NativeAllocationRegistry.createMalloced(
+ SurfaceControlActivePictureListener.class.getClassLoader(),
+ nativeGetDestructor());
+
+ /**
+ * Callback when there are changes in the visible layers that are using picture processing.
+ *
+ * @param activePictures The visible layers that are using picture processing.
+ */
+ public abstract void onActivePicturesChanged(SurfaceControlActivePicture[] activePictures);
+
+ /**
+ * Start listening to changes in active pictures.
+ */
+ @RequiresPermission(android.Manifest.permission.OBSERVE_PICTURE_PROFILES)
+ public void startListening() {
+ synchronized (this) {
+ long nativePtr = nativeMakeAndStartListening();
+ mDestructor = sRegistry.registerNativeAllocation(this, nativePtr);
+ }
+ }
+
+ /**
+ * Stop listening to changes in active pictures.
+ */
+ @RequiresPermission(android.Manifest.permission.OBSERVE_PICTURE_PROFILES)
+ public void stopListening() {
+ final Runnable destructor;
+ synchronized (this) {
+ destructor = mDestructor;
+ }
+ if (destructor != null) {
+ destructor.run();
+ }
+ }
+
+ private native long nativeMakeAndStartListening();
+ private static native long nativeGetDestructor();
+
+ private Runnable mDestructor;
+}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index a21bf9abdd7b..5c03c5cca66d 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -161,6 +161,7 @@ cc_library_shared_for_libandroid_runtime {
"android_view_MotionPredictor.cpp",
"android_view_PointerIcon.cpp",
"android_view_SurfaceControl.cpp",
+ "android_view_SurfaceControlActivePictureListener.cpp",
"android_view_SurfaceControlHdrLayerInfoListener.cpp",
"android_view_WindowManagerGlobal.cpp",
"android_graphics_BLASTBufferQueue.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 821861efd59b..00a62977de43 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -128,6 +128,7 @@ extern int register_android_view_InputApplicationHandle(JNIEnv* env);
extern int register_android_view_InputWindowHandle(JNIEnv* env);
extern int register_android_view_Surface(JNIEnv* env);
extern int register_android_view_SurfaceControl(JNIEnv* env);
+extern int register_android_view_SurfaceControlActivePictureListener(JNIEnv* env);
extern int register_android_view_SurfaceControlHdrLayerInfoListener(JNIEnv* env);
extern int register_android_view_SurfaceSession(JNIEnv* env);
extern int register_android_view_CompositionSamplingListener(JNIEnv* env);
@@ -1563,6 +1564,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_view_DisplayEventReceiver),
REG_JNI(register_android_view_Surface),
REG_JNI(register_android_view_SurfaceControl),
+ REG_JNI(register_android_view_SurfaceControlActivePictureListener),
REG_JNI(register_android_view_SurfaceControlHdrLayerInfoListener),
REG_JNI(register_android_view_SurfaceSession),
REG_JNI(register_android_view_InputApplicationHandle),
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 593b982d4cf2..68e642086636 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -55,6 +55,7 @@
#include <ui/FrameStats.h>
#include <ui/GraphicTypes.h>
#include <ui/HdrCapabilities.h>
+#include <ui/PictureProfileHandle.h>
#include <ui/Rect.h>
#include <ui/Region.h>
#include <ui/StaticDisplayInfo.h>
@@ -820,6 +821,21 @@ static void nativeSetLuts(JNIEnv* env, jclass clazz, jlong transactionObj, jlong
transaction->setLuts(ctrl, base::unique_fd(fd), offsets, dimensions, sizes, samplingKeys);
}
+static void nativeSetPictureProfileId(JNIEnv* env, jclass clazz, jlong transactionObj,
+ jlong surfaceControlObj, jlong pictureProfileId) {
+ auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+ SurfaceControl* const surfaceControl = reinterpret_cast<SurfaceControl*>(surfaceControlObj);
+ PictureProfileHandle handle(pictureProfileId);
+ transaction->setPictureProfileHandle(surfaceControl, handle);
+}
+
+static void nativeSetContentPriority(JNIEnv* env, jclass clazz, jlong transactionObj,
+ jlong surfaceControlObj, jint priority) {
+ auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+ SurfaceControl* const surfaceControl = reinterpret_cast<SurfaceControl*>(surfaceControlObj);
+ transaction->setContentPriority(surfaceControl, priority);
+}
+
static void nativeSetCachingHint(JNIEnv* env, jclass clazz, jlong transactionObj,
jlong nativeObject, jint cachingHint) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -2351,6 +2367,20 @@ static jboolean nativeBootFinished(JNIEnv* env, jclass clazz) {
return error == OK ? JNI_TRUE : JNI_FALSE;
}
+static jint nativeGetMaxPictureProfiles(JNIEnv* env, jclass clazz) {
+ const auto displayIds = SurfaceComposerClient::SurfaceComposerClient::getPhysicalDisplayIds();
+ int largestMaxProfiles = 0;
+ for (auto displayId : displayIds) {
+ sp<IBinder> token = SurfaceComposerClient::getPhysicalDisplayToken(displayId);
+ int32_t maxProfiles = 0;
+ SurfaceComposerClient::getMaxLayerPictureProfiles(token, &maxProfiles);
+ if (maxProfiles > largestMaxProfiles) {
+ largestMaxProfiles = maxProfiles;
+ }
+ }
+ return largestMaxProfiles;
+}
+
jlong nativeCreateTpc(JNIEnv* env, jclass clazz, jobject trustedPresentationCallback) {
return reinterpret_cast<jlong>(
new TrustedPresentationCallbackWrapper(env, trustedPresentationCallback));
@@ -2672,6 +2702,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = {
(void*)nativeGetDefaultApplyToken },
{"nativeBootFinished", "()Z",
(void*)nativeBootFinished },
+ {"nativeGetMaxPictureProfiles", "()I",
+ (void*)nativeGetMaxPictureProfiles },
{"nativeCreateTpc", "(Landroid/view/SurfaceControl$TrustedPresentationCallback;)J",
(void*)nativeCreateTpc},
{"getNativeTrustedPresentationCallbackFinalizer", "()J", (void*)getNativeTrustedPresentationCallbackFinalizer },
@@ -2683,6 +2715,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = {
(void*)nativeNotifyShutdown },
{"nativeSetLuts", "(JJ[F[I[I[I[I)V", (void*)nativeSetLuts },
{"nativeEnableDebugLogCallPoints", "(J)V", (void*)nativeEnableDebugLogCallPoints },
+ {"nativeSetPictureProfileId", "(JJJ)V", (void*)nativeSetPictureProfileId },
+ {"nativeSetContentPriority", "(JJI)V", (void*)nativeSetContentPriority },
// clang-format on
};
diff --git a/core/jni/android_view_SurfaceControlActivePictureListener.cpp b/core/jni/android_view_SurfaceControlActivePictureListener.cpp
new file mode 100644
index 000000000000..91849c1514cc
--- /dev/null
+++ b/core/jni/android_view_SurfaceControlActivePictureListener.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2021 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 "SurfaceControlActivePictureListener"
+
+#include <android/gui/BnActivePictureListener.h>
+#include <android_runtime/Log.h>
+#include <gui/ISurfaceComposer.h>
+#include <gui/SurfaceComposerClient.h>
+#include <log/log.h>
+#include <nativehelper/JNIHelp.h>
+#include <utils/RefBase.h>
+
+#include "android_util_Binder.h"
+#include "core_jni_helpers.h"
+
+namespace android {
+
+namespace {
+
+struct {
+ jclass clazz;
+ jmethodID onActivePicturesChanged;
+} gListenerClassInfo;
+
+static struct {
+ jclass clazz;
+ jmethodID constructor;
+} gActivePictureClassInfo;
+
+static struct {
+ jclass clazz;
+ jmethodID constructor;
+ jfieldID id;
+} gPictureProfileHandleClassInfo;
+
+struct SurfaceControlActivePictureListener : public gui::BnActivePictureListener {
+ SurfaceControlActivePictureListener(JNIEnv* env, jobject listener)
+ : mListener(env->NewGlobalRef(listener)) {
+ LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&mVm) != JNI_OK, "Failed to GetJavaVm");
+ }
+
+ binder::Status onActivePicturesChanged(
+ const std::vector<gui::ActivePicture>& activePictures) override {
+ JNIEnv* env = requireEnv();
+
+ ScopedLocalRef<jobjectArray> activePictureArrayObj(env);
+ activePictureArrayObj.reset(
+ env->NewObjectArray(activePictures.size(), gActivePictureClassInfo.clazz, NULL));
+ if (env->ExceptionCheck() || !activePictureArrayObj.get()) {
+ LOGE_EX(env);
+ LOG_ALWAYS_FATAL("Failed to create an active picture array.");
+ }
+
+ {
+ std::vector<ScopedLocalRef<jobject>> pictureProfileHandleObjs;
+ std::vector<ScopedLocalRef<jobject>> activePictureObjs;
+
+ for (size_t i = 0; i < activePictures.size(); ++i) {
+ pictureProfileHandleObjs.push_back(ScopedLocalRef<jobject>(env));
+ pictureProfileHandleObjs[i].reset(
+ env->NewObject(gPictureProfileHandleClassInfo.clazz,
+ gPictureProfileHandleClassInfo.constructor,
+ activePictures[i].pictureProfileId));
+ if (env->ExceptionCheck() || !pictureProfileHandleObjs[i].get()) {
+ LOGE_EX(env);
+ LOG_ALWAYS_FATAL("Failed to create a picture profile handle.");
+ }
+ activePictureObjs.push_back(ScopedLocalRef<jobject>(env));
+ activePictureObjs[i].reset(env->NewObject(gActivePictureClassInfo.clazz,
+ gActivePictureClassInfo.constructor,
+ activePictures[i].layerId,
+ activePictures[i].ownerUid,
+ pictureProfileHandleObjs[i].get()));
+ if (env->ExceptionCheck() || !activePictureObjs[i].get()) {
+ LOGE_EX(env);
+ LOG_ALWAYS_FATAL("Failed to create an active picture.");
+ }
+ env->SetObjectArrayElement(activePictureArrayObj.get(), i,
+ activePictureObjs[i].get());
+ }
+
+ env->CallVoidMethod(mListener, gListenerClassInfo.onActivePicturesChanged,
+ activePictureArrayObj.get());
+ }
+
+ if (env->ExceptionCheck()) {
+ ALOGE("SurfaceControlActivePictureListener.onActivePicturesChanged failed");
+ LOGE_EX(env);
+ env->ExceptionClear();
+ }
+ return binder::Status::ok();
+ }
+
+ status_t startListening() {
+ // TODO(b/337330263): Make SF multiple-listener capable
+ return SurfaceComposerClient::setActivePictureListener(this);
+ }
+
+ status_t stopListening() {
+ return SurfaceComposerClient::setActivePictureListener(nullptr);
+ }
+
+protected:
+ virtual ~SurfaceControlActivePictureListener() {
+ JNIEnv* env = requireEnv();
+ env->DeleteGlobalRef(mListener);
+ }
+
+ JNIEnv* requireEnv() {
+ JNIEnv* env = nullptr;
+ 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;
+ }
+
+private:
+ jobject mListener;
+ JavaVM* mVm;
+};
+
+jlong nativeMakeAndStartListening(JNIEnv* env, jobject jthis) {
+ auto listener = sp<SurfaceControlActivePictureListener>::make(env, jthis);
+ status_t err = listener->startListening();
+ if (err != OK) {
+ auto errStr = statusToString(err);
+ jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+ "Failed to start listening, err = %d (%s)", err, errStr.c_str());
+ return 0;
+ }
+ SurfaceControlActivePictureListener* listenerRawPtr = listener.get();
+ listenerRawPtr->incStrong(0);
+ return static_cast<jlong>(reinterpret_cast<intptr_t>(listenerRawPtr));
+}
+
+static void destroy(SurfaceControlActivePictureListener* listener) {
+ listener->stopListening();
+ listener->decStrong(0);
+}
+
+static jlong nativeGetDestructor(JNIEnv* env, jobject clazz) {
+ return static_cast<jlong>(reinterpret_cast<intptr_t>(&destroy));
+}
+
+const JNINativeMethod gMethods[] = {
+ /* name, signature, funcPtr */
+ {"nativeGetDestructor", "()J", (void*)nativeGetDestructor},
+ {"nativeMakeAndStartListening", "()J", (void*)nativeMakeAndStartListening}};
+} // namespace
+
+int register_android_view_SurfaceControlActivePictureListener(JNIEnv* env) {
+ int res = jniRegisterNativeMethods(env, "android/view/SurfaceControlActivePictureListener",
+ gMethods, NELEM(gMethods));
+ LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
+
+ jclass listenerClazz = env->FindClass("android/view/SurfaceControlActivePictureListener");
+ gListenerClassInfo.clazz = MakeGlobalRefOrDie(env, listenerClazz);
+ gListenerClassInfo.onActivePicturesChanged =
+ env->GetMethodID(listenerClazz, "onActivePicturesChanged",
+ "([Landroid/view/SurfaceControlActivePicture;)V");
+
+ gActivePictureClassInfo.clazz = static_cast<jclass>(
+ env->NewGlobalRef(env->FindClass("android/view/SurfaceControlActivePicture")));
+ gActivePictureClassInfo.constructor =
+ env->GetMethodID(gActivePictureClassInfo.clazz, "<init>",
+ "(IILandroid/media/quality/PictureProfileHandle;)V");
+
+ gPictureProfileHandleClassInfo.clazz = static_cast<jclass>(
+ env->NewGlobalRef(env->FindClass("android/media/quality/PictureProfileHandle")));
+ gPictureProfileHandleClassInfo.constructor =
+ env->GetMethodID(gPictureProfileHandleClassInfo.clazz, "<init>", "(J)V");
+ return 0;
+}
+
+} // namespace android
diff --git a/media/java/android/media/quality/PictureProfileHandle.java b/media/java/android/media/quality/PictureProfileHandle.java
index 2b1cda4eb742..714fd36d664a 100644
--- a/media/java/android/media/quality/PictureProfileHandle.java
+++ b/media/java/android/media/quality/PictureProfileHandle.java
@@ -17,46 +17,58 @@
package android.media.quality;
import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
-import androidx.annotation.NonNull;
-
-// TODO(b/337330263): Expose as public API after API review
/**
- * A type-safe handle to a picture profile, which represents a collection of parameters used to
- * configure picture processing hardware to enhance the quality of graphic buffers.
+ * A type-safe handle to a picture profile used to apply picture processing to a SurfaceControl.
+ *
+ * A picture profile represents a collection of parameters used to configure picture processing
+ * to enhance the quality of graphic buffers.
+ *
* @hide
*/
-@FlaggedApi(android.media.tv.flags.Flags.FLAG_MEDIA_QUALITY_FW)
+@SystemApi
+@FlaggedApi(android.media.tv.flags.Flags.FLAG_APPLY_PICTURE_PROFILES)
public final class PictureProfileHandle implements Parcelable {
+ public static final @NonNull PictureProfileHandle NONE = new PictureProfileHandle(0);
+
private final long mId;
- @FlaggedApi(android.media.tv.flags.Flags.FLAG_MEDIA_QUALITY_FW)
+ /** @hide */
public PictureProfileHandle(long id) {
mId = id;
}
- @FlaggedApi(android.media.tv.flags.Flags.FLAG_MEDIA_QUALITY_FW)
+ /** @hide */
+ @SystemApi
+ @FlaggedApi(android.media.tv.flags.Flags.FLAG_APPLY_PICTURE_PROFILES)
public long getId() {
return mId;
}
- @FlaggedApi(android.media.tv.flags.Flags.FLAG_MEDIA_QUALITY_FW)
+ /** @hide */
+ @SystemApi
@Override
+ @FlaggedApi(android.media.tv.flags.Flags.FLAG_APPLY_PICTURE_PROFILES)
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeLong(mId);
}
- @FlaggedApi(android.media.tv.flags.Flags.FLAG_MEDIA_QUALITY_FW)
+ /** @hide */
+ @SystemApi
@Override
+ @FlaggedApi(android.media.tv.flags.Flags.FLAG_APPLY_PICTURE_PROFILES)
public int describeContents() {
return 0;
}
- @FlaggedApi(android.media.tv.flags.Flags.FLAG_MEDIA_QUALITY_FW)
- @NonNull
- public static final Creator<PictureProfileHandle> CREATOR =
+ /** @hide */
+ @SystemApi
+ @FlaggedApi(android.media.tv.flags.Flags.FLAG_APPLY_PICTURE_PROFILES)
+ public static final @NonNull Creator<PictureProfileHandle> CREATOR =
new Creator<PictureProfileHandle>() {
@Override
public PictureProfileHandle createFromParcel(Parcel in) {
diff --git a/tests/CtsSurfaceControlTestsStaging/AndroidManifest.xml b/tests/CtsSurfaceControlTestsStaging/AndroidManifest.xml
index d8eb9ff37e78..da510fcd4d63 100644
--- a/tests/CtsSurfaceControlTestsStaging/AndroidManifest.xml
+++ b/tests/CtsSurfaceControlTestsStaging/AndroidManifest.xml
@@ -18,12 +18,20 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.view.surfacecontroltests">
+ <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER"/>
+ <uses-permission android:name="android.permission.OBSERVE_PICTURE_PROFILES"/>
+
<application android:debuggable="true" android:testOnly="true">
<uses-library android:name="android.test.runner"/>
<activity
android:name=".GraphicsActivity"
android:exported="false">
</activity>
+ <activity android:name=".SurfaceControlPictureProfileTestActivity"
+ android:exported="true"
+ android:turnScreenOn="true"
+ android:showWhenLocked="true"
+ android:theme="@android:style/Theme.NoTitleBar.Fullscreen" />
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/CtsSurfaceControlTestsStaging/AndroidTest.xml b/tests/CtsSurfaceControlTestsStaging/AndroidTest.xml
index 5c0163fcfa7e..025bf378d82f 100644
--- a/tests/CtsSurfaceControlTestsStaging/AndroidTest.xml
+++ b/tests/CtsSurfaceControlTestsStaging/AndroidTest.xml
@@ -21,6 +21,9 @@
<option name="install-arg" value="-t" />
<option name="test-file-name" value="CtsSurfaceControlTestsStaging.apk" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.FeatureFlagTargetPreparer" >
+ <option name="flag-value" value="media_tv/android.media.tv.flags.apply_picture_profiles=true" />
+ </target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.view.surfacecontroltests" />
<option name="hidden-api-checks" value="false" />
diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlPictureProfileTest.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlPictureProfileTest.java
new file mode 100644
index 000000000000..135f7102b8a9
--- /dev/null
+++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlPictureProfileTest.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2024 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.surfacecontroltests;
+
+import static android.Manifest.permission.OBSERVE_PICTURE_PROFILES;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
+import static java.util.Arrays.stream;
+
+import android.hardware.HardwareBuffer;
+import android.media.quality.PictureProfileHandle;
+import android.os.Process;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlActivePicture;
+import android.view.SurfaceControlActivePictureListener;
+import android.view.SurfaceView;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.LongStream;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SurfaceControlPictureProfileTest {
+ private static final String TAG = SurfaceControlPictureProfileTest.class.getSimpleName();
+
+ private SurfaceControl[] mSurfaceControls;
+ private SurfaceControl mSurfaceControl;
+
+ @Rule
+ public ActivityTestRule<SurfaceControlPictureProfileTestActivity> mActivityRule =
+ new ActivityTestRule<>(SurfaceControlPictureProfileTestActivity.class);
+
+ @Before
+ public void setup() {
+ SurfaceView[] surfaceViews = mActivityRule.getActivity().getSurfaceViews();
+ mSurfaceControls = new SurfaceControl[surfaceViews.length];
+ // Create a child surface control so we can set a buffer, priority and profile handle all
+ // on one single surface control
+ for (int i = 0; i < mSurfaceControls.length; ++i) {
+ mSurfaceControls[i] = new SurfaceControl.Builder().setName("test").setHidden(false)
+ .setParent(surfaceViews[i].getSurfaceControl()).build();
+ }
+ mSurfaceControl = mSurfaceControls[0];
+ }
+
+ @Test
+ public void whenPictureProfileApplied_noExecptionsThrown() {
+ assumeTrue("Skipping test because feature flag is disabled",
+ com.android.graphics.libgui.flags.Flags.applyPictureProfiles());
+ // TODO(b/337330263): Call MediaQualityManager.getMaxPictureProfiles instead
+ assumeTrue("Skipping test because no picture profile support",
+ SurfaceControl.getMaxPictureProfiles() > 0);
+
+ // TODO(b/337330263): Load the handle from MediaQualityManager instead
+ PictureProfileHandle handle = new PictureProfileHandle(1);
+ HardwareBuffer buffer = getSolidBuffer(100, 100);
+ new SurfaceControl.Transaction()
+ .setBuffer(mSurfaceControl, buffer)
+ .setPictureProfileHandle(mSurfaceControl, handle)
+ .apply();
+ }
+
+ @Test
+ public void whenStartsListening_callsListener() {
+ assumeTrue("Skipping test because feature flag is disabled",
+ com.android.graphics.libgui.flags.Flags.applyPictureProfiles());
+ // TODO(b/337330263): Call MediaQualityManager.getMaxPictureProfiles instead
+ assumeTrue("Skipping test because no picture profile support",
+ SurfaceControl.getMaxPictureProfiles() > 0);
+
+ BlockingQueue<SurfaceControlActivePicture[]> picturesQueue = new LinkedBlockingQueue<>();
+ SurfaceControlActivePicture[] pictures;
+ SurfaceControlActivePictureListener listener = new SurfaceControlActivePictureListener() {
+ @Override
+ public void onActivePicturesChanged(SurfaceControlActivePicture[] pictures) {
+ picturesQueue.add(pictures);
+ }
+ };
+ // TODO(b/337330263): Call MediaQualityManager.addActivePictureListener instead
+ adoptShellPermissionIdentity(OBSERVE_PICTURE_PROFILES);
+ listener.startListening();
+ {
+ HardwareBuffer buffer = getSolidBuffer(100, 100);
+ new SurfaceControl.Transaction().setBuffer(mSurfaceControl, buffer).apply();
+ }
+
+ pictures = pollMs(picturesQueue, 200);
+ assertThat(pictures).isNotNull();
+ assertThat(pictures).isEmpty();
+ }
+
+ @Test
+ public void whenPictureProfileApplied_callsListenerWithUidAndProfileId() {
+ assumeTrue("Skipping test because feature flag is disabled",
+ com.android.graphics.libgui.flags.Flags.applyPictureProfiles());
+ // TODO(b/337330263): Call MediaQualityManager.getMaxPictureProfiles instead
+ assumeTrue("Skipping test because no picture profile support",
+ SurfaceControl.getMaxPictureProfiles() > 0);
+
+ BlockingQueue<SurfaceControlActivePicture[]> picturesQueue = new LinkedBlockingQueue<>();
+ SurfaceControlActivePicture[] pictures;
+ SurfaceControlActivePictureListener listener = new SurfaceControlActivePictureListener() {
+ @Override
+ public void onActivePicturesChanged(SurfaceControlActivePicture[] pictures) {
+ picturesQueue.add(pictures);
+ }
+ };
+ // TODO(b/337330263): Call MediaQualityManager.addActivePictureListener instead
+ adoptShellPermissionIdentity(OBSERVE_PICTURE_PROFILES);
+ listener.startListening();
+ {
+ HardwareBuffer buffer = getSolidBuffer(100, 100);
+ new SurfaceControl.Transaction().setBuffer(mSurfaceControl, buffer).apply();
+ }
+
+ pictures = pollMs(picturesQueue, 200);
+ assertThat(pictures).isNotNull();
+ assertThat(pictures).isEmpty();
+
+ // TODO(b/337330263): Load the handle from MediaQualityManager instead
+ PictureProfileHandle handle = new PictureProfileHandle(1);
+ HardwareBuffer buffer = getSolidBuffer(100, 100);
+ new SurfaceControl.Transaction()
+ .setBuffer(mSurfaceControl, buffer)
+ .setPictureProfileHandle(mSurfaceControl, handle)
+ .apply();
+
+ pictures = pollMs(picturesQueue, 200);
+ assertThat(pictures).isNotNull();
+ assertThat(stream(pictures).map(picture -> picture.getPictureProfileHandle().getId()))
+ .containsExactly(handle.getId());
+ assertThat(stream(pictures).map(picture -> picture.getOwnerUid()))
+ .containsExactly(Process.myUid());
+ }
+
+ @Test
+ public void whenPriorityChanges_callsListenerOnlyForLowerPriorityLayers() {
+ assumeTrue("Skipping test because feature flag is disabled",
+ com.android.graphics.libgui.flags.Flags.applyPictureProfiles());
+ // TODO(b/337330263): Call MediaQualityManager.getMaxPictureProfiles instead
+ int maxPictureProfiles = SurfaceControl.getMaxPictureProfiles();
+ assumeTrue("Skipping test because no picture profile support", maxPictureProfiles > 0);
+
+ BlockingQueue<SurfaceControlActivePicture[]> picturesQueue = new LinkedBlockingQueue<>();
+ SurfaceControlActivePicture[] pictures;
+ SurfaceControlActivePictureListener listener = new SurfaceControlActivePictureListener() {
+ @Override
+ public void onActivePicturesChanged(SurfaceControlActivePicture[] pictures) {
+ picturesQueue.add(pictures);
+ }
+ };
+ // TODO(b/337330263): Call MediaQualityManager.addActivePictureListener instead
+ adoptShellPermissionIdentity(OBSERVE_PICTURE_PROFILES);
+ listener.startListening();
+ {
+ HardwareBuffer buffer = getSolidBuffer(100, 100);
+ new SurfaceControl.Transaction().setBuffer(mSurfaceControl, buffer).apply();
+ }
+
+ pictures = pollMs(picturesQueue, 200);
+ assertThat(pictures).isNotNull();
+ assertThat(pictures).isEmpty();
+
+ SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+ // Use one more picture profile than allowed
+ for (int i = 0; i <= maxPictureProfiles; ++i) {
+ // Increase the number of surface views as necessary to support device configuration.
+ assertThat(i).isLessThan(mSurfaceControls.length);
+
+ // TODO(b/337330263): Load the handle from MediaQualityManager instead
+ PictureProfileHandle handle = new PictureProfileHandle(i + 1);
+ HardwareBuffer buffer = getSolidBuffer(100, 100);
+ transaction
+ .setBuffer(mSurfaceControls[i], buffer)
+ .setPictureProfileHandle(mSurfaceControls[i], handle)
+ .setContentPriority(mSurfaceControls[i], 0);
+ }
+ // Make the first layer low priority (high value)
+ transaction.setContentPriority(mSurfaceControls[0], 2);
+ // Make the last layer higher priority (lower value)
+ transaction.setContentPriority(mSurfaceControls[maxPictureProfiles], 1);
+ transaction.apply();
+
+ pictures = pollMs(picturesQueue, 200);
+ assertThat(pictures).isNotNull();
+ assertThat(stream(pictures).map(picture -> picture.getLayerId()))
+ .containsNoDuplicates();
+ // Expect all but the first layer to be listed as an active picture
+ assertThat(stream(pictures).map(picture -> picture.getPictureProfileHandle().getId()))
+ .containsExactlyElementsIn(toIterableRange(2, maxPictureProfiles + 1));
+
+ // Change priority and ensure that the first layer gets access
+ new SurfaceControl.Transaction().setContentPriority(mSurfaceControls[0], 0).apply();
+ pictures = pollMs(picturesQueue, 200);
+ assertThat(pictures).isNotNull();
+ // Expect all but the last layer to be listed as an active picture
+ assertThat(stream(pictures).map(picture -> picture.getPictureProfileHandle().getId()))
+ .containsExactlyElementsIn(toIterableRange(1, maxPictureProfiles));
+ }
+
+ private static SurfaceControlActivePicture[] pollMs(
+ BlockingQueue<SurfaceControlActivePicture[]> picturesQueue, int waitMs) {
+ SurfaceControlActivePicture[] pictures = null;
+ long nowMs = System.currentTimeMillis();
+ long endTimeMs = nowMs + waitMs;
+ while (nowMs < endTimeMs && pictures == null) {
+ try {
+ pictures = picturesQueue.poll(endTimeMs - nowMs, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ // continue polling until timeout when interrupted
+ }
+ nowMs = System.currentTimeMillis();
+ }
+ return pictures;
+ }
+
+ Iterable<Long> toIterableRange(int start, int stop) {
+ return () -> LongStream.rangeClosed(start, stop).iterator();
+ }
+
+ private void adoptShellPermissionIdentity(String permission) {
+ getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(permission);
+ }
+
+ private HardwareBuffer getSolidBuffer(int width, int height) {
+ // We can assume that RGBA_8888 format is supported for every platform.
+ return HardwareBuffer.create(
+ width, height, HardwareBuffer.RGBA_8888, 1, HardwareBuffer.USAGE_CPU_WRITE_OFTEN);
+ }
+}
diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlPictureProfileTestActivity.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlPictureProfileTestActivity.java
new file mode 100644
index 000000000000..42fcb261fa9d
--- /dev/null
+++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlPictureProfileTestActivity.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 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.surfacecontroltests;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.SurfaceView;
+
+public class SurfaceControlPictureProfileTestActivity extends Activity {
+ private SurfaceView[] mSurfaceViews;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.picture_profile_test_layout);
+ mSurfaceViews = new SurfaceView[3];
+ mSurfaceViews[0] = (SurfaceView) findViewById(R.id.surfaceview1);
+ mSurfaceViews[1] = (SurfaceView) findViewById(R.id.surfaceview2);
+ mSurfaceViews[2] = (SurfaceView) findViewById(R.id.surfaceview3);
+ }
+
+ public SurfaceView getSurfaceView() {
+ return mSurfaceViews[0];
+ }
+
+ public SurfaceView[] getSurfaceViews() {
+ return mSurfaceViews;
+ }
+}
diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/res/layout/picture_profile_test_layout.xml b/tests/CtsSurfaceControlTestsStaging/src/main/res/layout/picture_profile_test_layout.xml
new file mode 100644
index 000000000000..9aa25785d9f2
--- /dev/null
+++ b/tests/CtsSurfaceControlTestsStaging/src/main/res/layout/picture_profile_test_layout.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ * Copyright (C) 2024 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.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <SurfaceView android:id="@+id/surfaceview1"
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:layout_weight="1"/>
+ <SurfaceView android:id="@+id/surfaceview2"
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:layout_weight="1"/>
+ <SurfaceView android:id="@+id/surfaceview3"
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:layout_weight="1"/>
+</LinearLayout>