Merge "TouchpadInputMapper: report usage metrics" into udc-qpr-dev
diff --git a/Android.bp b/Android.bp
index 3992f82..7f1ef67 100644
--- a/Android.bp
+++ b/Android.bp
@@ -36,6 +36,14 @@
],
}
+cc_library_headers {
+ name: "native_headers",
+ host_supported: true,
+ export_include_dirs: [
+ "include/",
+ ],
+}
+
ndk_headers {
name: "libandroid_headers",
from: "include/android",
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 5dbf7ac..8a33756 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -3586,7 +3586,7 @@
// an app; they are irrelevant here because bugreport is triggered via command line.
// Update Last ID before calling Run().
Initialize();
- status = Run(-1 /* calling_uid */, "" /* calling_package */);
+ status = Run(0 /* calling_uid */, "" /* calling_package */);
}
return status;
}
diff --git a/cmds/installd/otapreopt_chroot.cpp b/cmds/installd/otapreopt_chroot.cpp
index 1b7acab..c86993c 100644
--- a/cmds/installd/otapreopt_chroot.cpp
+++ b/cmds/installd/otapreopt_chroot.cpp
@@ -165,7 +165,8 @@
// Bind mount necessary directories.
constexpr const char* kBindMounts[] = {
- "/data", "/dev", "/proc", "/sys"
+ "/data", "/dev", "/proc", "/sys",
+ "/sys/fs/selinux" /* Required for apexd which includes libselinux */
};
for (size_t i = 0; i < arraysize(kBindMounts); ++i) {
std::string trg = StringPrintf("/postinstall%s", kBindMounts[i]);
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index 754e7b2..226cae1 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -329,6 +329,12 @@
}
prebuilt_etc {
+ name: "android.software.opengles.deqp.level-2023-03-01.prebuilt.xml",
+ src: "android.software.opengles.deqp.level-2023-03-01.xml",
+ defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
name: "android.software.sip.voip.prebuilt.xml",
src: "android.software.sip.voip.xml",
defaults: ["frameworks_native_data_etc_defaults"],
@@ -353,6 +359,12 @@
}
prebuilt_etc {
+ name: "android.software.vulkan.deqp.level-2023-03-01.prebuilt.xml",
+ src: "android.software.vulkan.deqp.level-2023-03-01.xml",
+ defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
name: "aosp_excluded_hardware.prebuilt.xml",
src: "aosp_excluded_hardware.xml",
defaults: ["frameworks_native_data_etc_defaults"],
diff --git a/include/input/InputEventLabels.h b/include/input/InputEventLabels.h
index 9dedd2b..909bf08 100644
--- a/include/input/InputEventLabels.h
+++ b/include/input/InputEventLabels.h
@@ -40,7 +40,16 @@
// then you must add it to InputEventLabels.cpp.
class InputEventLookup {
+ /**
+ * This class is not purely static, but uses a singleton pattern in order to delay the
+ * initialization of the maps that it contains. If it were purely static, the maps could be
+ * created early, and would cause sanitizers to report memory leaks.
+ */
public:
+ InputEventLookup(InputEventLookup& other) = delete;
+
+ void operator=(const InputEventLookup&) = delete;
+
static std::optional<int> lookupValueByLabel(const std::unordered_map<std::string, int>& map,
const char* literal);
@@ -61,17 +70,24 @@
static EvdevEventLabel getLinuxEvdevLabel(int32_t type, int32_t code, int32_t value);
private:
- static const std::unordered_map<std::string, int> KEYCODES;
+ InputEventLookup();
- static const std::vector<InputEventLabel> KEY_NAMES;
+ static const InputEventLookup& get() {
+ static InputEventLookup sLookup;
+ return sLookup;
+ }
- static const std::unordered_map<std::string, int> AXES;
+ const std::unordered_map<std::string, int> KEYCODES;
- static const std::vector<InputEventLabel> AXES_NAMES;
+ const std::vector<InputEventLabel> KEY_NAMES;
- static const std::unordered_map<std::string, int> LEDS;
+ const std::unordered_map<std::string, int> AXES;
- static const std::unordered_map<std::string, int> FLAGS;
+ const std::vector<InputEventLabel> AXES_NAMES;
+
+ const std::unordered_map<std::string, int> LEDS;
+
+ const std::unordered_map<std::string, int> FLAGS;
};
} // namespace android
diff --git a/include/input/InputVerifier.h b/include/input/InputVerifier.h
index d4589f5..3715408 100644
--- a/include/input/InputVerifier.h
+++ b/include/input/InputVerifier.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright 2023 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.
@@ -16,13 +16,25 @@
#pragma once
+#include <android-base/result.h>
#include <input/Input.h>
-#include <map>
+#include "rust/cxx.h"
namespace android {
+namespace input {
+namespace verifier {
+struct InputVerifier;
+}
+} // namespace input
+
/*
* Crash if the provided touch stream is inconsistent.
+ * This class is a pass-through to the rust implementation of InputVerifier.
+ * The rust class could also be used directly, but it would be less convenient.
+ * We can't directly invoke the rust methods on a rust object. So, there's no way to do:
+ * mVerifier.process_movement(...).
+ * This C++ class makes it a bit easier to use.
*
* TODO(b/211379801): Add support for hover events:
* - No hover move without enter
@@ -34,16 +46,13 @@
public:
InputVerifier(const std::string& name);
- void processMovement(int32_t deviceId, int32_t action, uint32_t pointerCount,
- const PointerProperties* pointerProperties,
- const PointerCoords* pointerCoords, int32_t flags);
+ android::base::Result<void> processMovement(int32_t deviceId, int32_t action,
+ uint32_t pointerCount,
+ const PointerProperties* pointerProperties,
+ const PointerCoords* pointerCoords, int32_t flags);
private:
- const std::string mName;
- std::map<int32_t /*deviceId*/, std::bitset<MAX_POINTER_ID + 1>> mTouchingPointerIdsByDevice;
- void ensureTouchingPointersMatch(int32_t deviceId, uint32_t pointerCount,
- const PointerProperties* pointerProperties,
- const char* action) const;
+ rust::Box<android::input::verifier::InputVerifier> mVerifier;
};
} // namespace android
diff --git a/libs/graphicsenv/GraphicsEnv.cpp b/libs/graphicsenv/GraphicsEnv.cpp
index 5fbae3c..582437f 100644
--- a/libs/graphicsenv/GraphicsEnv.cpp
+++ b/libs/graphicsenv/GraphicsEnv.cpp
@@ -142,8 +142,8 @@
return appDebuggable || platformDebuggable;
}
-void GraphicsEnv::setDriverPathAndSphalLibraries(const std::string path,
- const std::string sphalLibraries) {
+void GraphicsEnv::setDriverPathAndSphalLibraries(const std::string& path,
+ const std::string& sphalLibraries) {
if (!mDriverPath.empty() || !mSphalLibraries.empty()) {
ALOGV("ignoring attempt to change driver path from '%s' to '%s' or change sphal libraries "
"from '%s' to '%s'",
@@ -392,55 +392,24 @@
return true;
}
-void* GraphicsEnv::loadLibrary(std::string name) {
- const android_dlextinfo dlextinfo = {
- .flags = ANDROID_DLEXT_USE_NAMESPACE,
- .library_namespace = getAngleNamespace(),
- };
-
- std::string libName = std::string("lib") + name + "_angle.so";
-
- void* so = android_dlopen_ext(libName.c_str(), RTLD_LOCAL | RTLD_NOW, &dlextinfo);
-
- if (so) {
- ALOGD("dlopen_ext from APK (%s) success at %p", libName.c_str(), so);
- return so;
- } else {
- ALOGE("dlopen_ext(\"%s\") failed: %s", libName.c_str(), dlerror());
- }
-
- return nullptr;
-}
-
-bool GraphicsEnv::shouldUseAngle(std::string appName) {
- if (appName != mAngleAppName) {
- // Make sure we are checking the app we were init'ed for
- ALOGE("App name does not match: expected '%s', got '%s'", mAngleAppName.c_str(),
- appName.c_str());
- return false;
- }
-
- return shouldUseAngle();
-}
-
bool GraphicsEnv::shouldUseAngle() {
// Make sure we are init'ed
- if (mAngleAppName.empty()) {
- ALOGV("App name is empty. setAngleInfo() has not been called to enable ANGLE.");
+ if (mPackageName.empty()) {
+ ALOGV("Package name is empty. setAngleInfo() has not been called to enable ANGLE.");
return false;
}
- return (mUseAngle == YES) ? true : false;
+ return (mShouldUseAngle == YES) ? true : false;
}
-void GraphicsEnv::updateUseAngle() {
+void GraphicsEnv::updateShouldUseAngle() {
const char* ANGLE_PREFER_ANGLE = "angle";
const char* ANGLE_PREFER_NATIVE = "native";
- mUseAngle = NO;
+ mShouldUseAngle = NO;
if (mAngleDeveloperOptIn == ANGLE_PREFER_ANGLE) {
ALOGV("User set \"Developer Options\" to force the use of ANGLE");
- mUseAngle = YES;
+ mShouldUseAngle = YES;
} else if (mAngleDeveloperOptIn == ANGLE_PREFER_NATIVE) {
ALOGV("User set \"Developer Options\" to force the use of Native");
} else {
@@ -448,13 +417,13 @@
}
}
-void GraphicsEnv::setAngleInfo(const std::string path, const std::string appName,
- const std::string developerOptIn,
+void GraphicsEnv::setAngleInfo(const std::string& path, const std::string& packageName,
+ const std::string& developerOptIn,
const std::vector<std::string> eglFeatures) {
- if (mUseAngle != UNKNOWN) {
+ if (mShouldUseAngle != UNKNOWN) {
// We've already figured out an answer for this app, so just return.
- ALOGV("Already evaluated the rules file for '%s': use ANGLE = %s", appName.c_str(),
- (mUseAngle == YES) ? "true" : "false");
+ ALOGV("Already evaluated the rules file for '%s': use ANGLE = %s", packageName.c_str(),
+ (mShouldUseAngle == YES) ? "true" : "false");
return;
}
@@ -462,16 +431,17 @@
ALOGV("setting ANGLE path to '%s'", path.c_str());
mAnglePath = path;
- ALOGV("setting ANGLE app name to '%s'", appName.c_str());
- mAngleAppName = appName;
+ ALOGV("setting app package name to '%s'", packageName.c_str());
+ mPackageName = packageName;
ALOGV("setting ANGLE application opt-in to '%s'", developerOptIn.c_str());
mAngleDeveloperOptIn = developerOptIn;
// Update the current status of whether we should use ANGLE or not
- updateUseAngle();
+ updateShouldUseAngle();
}
-void GraphicsEnv::setLayerPaths(NativeLoaderNamespace* appNamespace, const std::string layerPaths) {
+void GraphicsEnv::setLayerPaths(NativeLoaderNamespace* appNamespace,
+ const std::string& layerPaths) {
if (mLayerPaths.empty()) {
mLayerPaths = layerPaths;
mAppNamespace = appNamespace;
@@ -485,8 +455,8 @@
return mAppNamespace;
}
-std::string& GraphicsEnv::getAngleAppName() {
- return mAngleAppName;
+std::string& GraphicsEnv::getPackageName() {
+ return mPackageName;
}
const std::vector<std::string>& GraphicsEnv::getAngleEglFeatures() {
@@ -505,11 +475,11 @@
return mDebugLayersGLES;
}
-void GraphicsEnv::setDebugLayers(const std::string layers) {
+void GraphicsEnv::setDebugLayers(const std::string& layers) {
mDebugLayers = layers;
}
-void GraphicsEnv::setDebugLayersGLES(const std::string layers) {
+void GraphicsEnv::setDebugLayersGLES(const std::string& layers) {
mDebugLayersGLES = layers;
}
diff --git a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
index f9b234a..a1b5e50 100644
--- a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
+++ b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
@@ -55,7 +55,7 @@
// Also set additional required sphal libraries to the linker for loading
// graphics drivers. The string is a list of libraries separated by ':',
// which is required by android_link_namespaces.
- void setDriverPathAndSphalLibraries(const std::string path, const std::string sphalLibraries);
+ void setDriverPathAndSphalLibraries(const std::string& path, const std::string& sphalLibraries);
// Get the updatable driver namespace.
android_namespace_t* getDriverNamespace();
std::string getDriverPath() const;
@@ -96,8 +96,6 @@
/*
* Apis for ANGLE
*/
- // Check if the requested app should use ANGLE.
- bool shouldUseAngle(std::string appName);
// Check if this app process should use ANGLE.
bool shouldUseAngle();
// Set a search path for loading ANGLE libraries. The path is a list of
@@ -105,42 +103,39 @@
// (libraries must be stored uncompressed and page aligned); such elements
// in the search path must have a '!' after the zip filename, e.g.
// /system/app/ANGLEPrebuilt/ANGLEPrebuilt.apk!/lib/arm64-v8a
- void setAngleInfo(const std::string path, const std::string appName, std::string devOptIn,
- const std::vector<std::string> eglFeatures);
+ void setAngleInfo(const std::string& path, const std::string& packageName,
+ const std::string& devOptIn, const std::vector<std::string> eglFeatures);
// Get the ANGLE driver namespace.
android_namespace_t* getAngleNamespace();
- // Get the app name for ANGLE debug message.
- std::string& getAngleAppName();
-
+ // Get the app package name.
+ std::string& getPackageName();
const std::vector<std::string>& getAngleEglFeatures();
+ // Set the persist.graphics.egl system property value.
+ void nativeToggleAngleAsSystemDriver(bool enabled);
/*
* Apis for debug layer
*/
// Set additional layer search paths.
- void setLayerPaths(NativeLoaderNamespace* appNamespace, const std::string layerPaths);
+ void setLayerPaths(NativeLoaderNamespace* appNamespace, const std::string& layerPaths);
// Get the app namespace for loading layers.
NativeLoaderNamespace* getAppNamespace();
// Get additional layer search paths.
const std::string& getLayerPaths();
// Set the Vulkan debug layers.
- void setDebugLayers(const std::string layers);
+ void setDebugLayers(const std::string& layers);
// Set the GL debug layers.
- void setDebugLayersGLES(const std::string layers);
+ void setDebugLayersGLES(const std::string& layers);
// Get the debug layers to load.
const std::string& getDebugLayers();
// Get the debug layers to load.
const std::string& getDebugLayersGLES();
- // Set the persist.graphics.egl system property value.
- void nativeToggleAngleAsSystemDriver(bool enabled);
private:
enum UseAngle { UNKNOWN, YES, NO };
- // Load requested ANGLE library.
- void* loadLibrary(std::string name);
// Update whether ANGLE should be used.
- void updateUseAngle();
+ void updateShouldUseAngle();
// Link updatable driver namespace with llndk and vndk-sp libs.
bool linkDriverNamespaceLocked(android_namespace_t* vndkNamespace);
// Check whether this process is ready to send stats.
@@ -149,39 +144,56 @@
void sendGpuStatsLocked(GpuStatsInfo::Api api, bool isDriverLoaded, int64_t driverLoadingTime);
GraphicsEnv() = default;
+
+ // This mutex protects the namespace creation.
+ std::mutex mNamespaceMutex;
+
+ /**
+ * Updatable driver variables.
+ */
// Path to updatable driver libs.
std::string mDriverPath;
// Path to additional sphal libs linked to updatable driver namespace.
std::string mSphalLibraries;
+ // Updatable driver namespace.
+ android_namespace_t* mDriverNamespace = nullptr;
+
+ /**
+ * ANGLE variables.
+ */
+ // Path to ANGLE libs.
+ std::string mAnglePath;
+ // App's package name.
+ std::string mPackageName;
+ // ANGLE developer opt in status.
+ std::string mAngleDeveloperOptIn;
+ // ANGLE EGL features;
+ std::vector<std::string> mAngleEglFeatures;
+ // Use ANGLE flag.
+ UseAngle mShouldUseAngle = UNKNOWN;
+ // ANGLE namespace.
+ android_namespace_t* mAngleNamespace = nullptr;
+
+ /**
+ * GPU metrics.
+ */
// This mutex protects mGpuStats and get gpuservice call.
std::mutex mStatsLock;
// Cache the activity launch info
bool mActivityLaunched = false;
// Information bookkept for GpuStats.
GpuStatsInfo mGpuStats;
- // Path to ANGLE libs.
- std::string mAnglePath;
- // This App's name.
- std::string mAngleAppName;
- // ANGLE developer opt in status.
- std::string mAngleDeveloperOptIn;
- // ANGLE EGL features;
- std::vector<std::string> mAngleEglFeatures;
- // Use ANGLE flag.
- UseAngle mUseAngle = UNKNOWN;
+
+ /**
+ * Debug layers.
+ */
// Vulkan debug layers libs.
std::string mDebugLayers;
// GL debug layers libs.
std::string mDebugLayersGLES;
// Additional debug layers search path.
std::string mLayerPaths;
- // This mutex protects the namespace creation.
- std::mutex mNamespaceMutex;
- // Updatable driver namespace.
- android_namespace_t* mDriverNamespace = nullptr;
- // ANGLE namespace.
- android_namespace_t* mAngleNamespace = nullptr;
- // This App's namespace.
+ // This App's namespace to open native libraries.
NativeLoaderNamespace* mAppNamespace = nullptr;
};
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index ed69100..53a2f64 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -1792,19 +1792,20 @@
int Surface::dispatchSetFrameTimelineInfo(va_list args) {
ATRACE_CALL();
- auto frameNumber = static_cast<uint64_t>(va_arg(args, uint64_t));
- auto frameTimelineVsyncId = static_cast<int64_t>(va_arg(args, int64_t));
- auto inputEventId = static_cast<int32_t>(va_arg(args, int32_t));
- auto startTimeNanos = static_cast<int64_t>(va_arg(args, int64_t));
- auto useForRefreshRateSelection = static_cast<bool>(va_arg(args, int32_t));
-
ALOGV("Surface::%s", __func__);
+
+ const auto nativeWindowFtlInfo = static_cast<ANativeWindowFrameTimelineInfo>(
+ va_arg(args, ANativeWindowFrameTimelineInfo));
+
FrameTimelineInfo ftlInfo;
- ftlInfo.vsyncId = frameTimelineVsyncId;
- ftlInfo.inputEventId = inputEventId;
- ftlInfo.startTimeNanos = startTimeNanos;
- ftlInfo.useForRefreshRateSelection = useForRefreshRateSelection;
- return setFrameTimelineInfo(frameNumber, ftlInfo);
+ ftlInfo.vsyncId = nativeWindowFtlInfo.frameTimelineVsyncId;
+ ftlInfo.inputEventId = nativeWindowFtlInfo.inputEventId;
+ ftlInfo.startTimeNanos = nativeWindowFtlInfo.startTimeNanos;
+ ftlInfo.useForRefreshRateSelection = nativeWindowFtlInfo.useForRefreshRateSelection;
+ ftlInfo.skippedFrameVsyncId = nativeWindowFtlInfo.skippedFrameVsyncId;
+ ftlInfo.skippedFrameStartTimeNanos = nativeWindowFtlInfo.skippedFrameStartTimeNanos;
+
+ return setFrameTimelineInfo(nativeWindowFtlInfo.frameNumber, ftlInfo);
}
bool Surface::transformToDisplayInverse() const {
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 0fda358..5bc05ef 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -1027,7 +1027,7 @@
mEarlyWakeupEnd = false;
mDesiredPresentTime = 0;
mIsAutoTimestamp = true;
- clearFrameTimelineInfo(mFrameTimelineInfo);
+ mFrameTimelineInfo = {};
mApplyToken = nullptr;
mMergedTransactionIds.clear();
}
@@ -2279,27 +2279,13 @@
if (t.vsyncId != FrameTimelineInfo::INVALID_VSYNC_ID &&
other.vsyncId != FrameTimelineInfo::INVALID_VSYNC_ID) {
if (other.vsyncId > t.vsyncId) {
- t.vsyncId = other.vsyncId;
- t.inputEventId = other.inputEventId;
- t.startTimeNanos = other.startTimeNanos;
- t.useForRefreshRateSelection = other.useForRefreshRateSelection;
+ t = other;
}
} else if (t.vsyncId == FrameTimelineInfo::INVALID_VSYNC_ID) {
- t.vsyncId = other.vsyncId;
- t.inputEventId = other.inputEventId;
- t.startTimeNanos = other.startTimeNanos;
- t.useForRefreshRateSelection = other.useForRefreshRateSelection;
+ t = other;
}
}
-// copied from FrameTimelineInfo::clear()
-void SurfaceComposerClient::Transaction::clearFrameTimelineInfo(FrameTimelineInfo& t) {
- t.vsyncId = FrameTimelineInfo::INVALID_VSYNC_ID;
- t.inputEventId = os::IInputConstants::INVALID_INPUT_EVENT_ID;
- t.startTimeNanos = 0;
- t.useForRefreshRateSelection = false;
-}
-
SurfaceComposerClient::Transaction&
SurfaceComposerClient::Transaction::setTrustedPresentationCallback(
const sp<SurfaceControl>& sc, TrustedPresentationCallback cb,
diff --git a/libs/gui/TEST_MAPPING b/libs/gui/TEST_MAPPING
index 9415035..a4d9e77 100644
--- a/libs/gui/TEST_MAPPING
+++ b/libs/gui/TEST_MAPPING
@@ -4,10 +4,58 @@
"path": "frameworks/native/libs/nativewindow"
}
],
- "postsubmit": [
+ "presubmit": [
{
- // TODO(257123981): move this to presubmit after dealing with existing breakages.
- "name": "libgui_test"
+ "name": "libgui_test",
+ "options": [
+ // TODO(b/277604286): Failing on Cuttlefish.
+ {
+ "exclude-filter": "MultiTextureConsumerTest#EGLImageTargetWorks"
+ },
+
+ // TODO(b/285011590): Failing on Cuttlefish.
+ {
+ "exclude-filter": "SurfaceTest#GetHdrSupport"
+ },
+ {
+ "exclude-filter": "SurfaceTest#GetWideColorSupport"
+ },
+
+ // TODO(b/285006554): Failing on Cuttlefish.
+ {
+ "exclude-filter": "SurfaceTextureGLTest#InvalidWidthOrHeightFails"
+ },
+
+ // TODO(b/277347351): Known test data issues, failing across devices.
+ {
+ "exclude-filter": "SurfaceTextureGLTest#TexturingFromCpuFilledYV12BufferNpot"
+ },
+ {
+ "exclude-filter": "SurfaceTextureGLTest#TexturingFromCpuFilledYV12BufferPow2"
+ },
+ {
+ "exclude-filter": "SurfaceTextureGLTest#TexturingFromCpuFilledYV12BufferWithCrop"
+ },
+ {
+ "exclude-filter": "SurfaceTextureGLTest#TexturingFromCpuFilledYV12BuffersRepeatedly"
+ },
+
+ // TODO(b/285041169): Hanging on Cuttlefish.
+ {
+ "exclude-filter": "SurfaceTextureGLThreadToGLTest#UpdateTexImageBeforeFrameFinishedCompletes"
+ },
+ {
+ "exclude-filter": "SurfaceTextureGLThreadToGLTest#RepeatedUpdateTexImageBeforeFrameFinishedCompletes"
+ },
+ {
+ "exclude-filter": "SurfaceTextureGLThreadToGLTest#RepeatedUpdateTexImageAfterFrameFinishedCompletes"
+ },
+
+ // TODO(b/285041070): Failing on Cuttlefish.
+ {
+ "exclude-filter": "SurfaceTextureGLToGLTest#EglDestroySurfaceUnrefsBuffers"
+ }
+ ]
}
]
}
diff --git a/libs/gui/aidl/android/gui/FrameTimelineInfo.aidl b/libs/gui/aidl/android/gui/FrameTimelineInfo.aidl
index 6a86c6a..4b647a4 100644
--- a/libs/gui/aidl/android/gui/FrameTimelineInfo.aidl
+++ b/libs/gui/aidl/android/gui/FrameTimelineInfo.aidl
@@ -37,4 +37,10 @@
// Whether this vsyncId should be used to heuristically select the display refresh rate
// TODO(b/281695725): Clean this up once TextureView use setFrameRate API
boolean useForRefreshRateSelection = false;
+
+ // The VsyncId of a frame that was not drawn and squashed into this frame.
+ long skippedFrameVsyncId = INVALID_VSYNC_ID;
+
+ // The start time of a frame that was not drawn and squashed into this frame.
+ long skippedFrameStartTimeNanos = 0;
}
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index fb57f63..3cf57b1 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -410,7 +410,6 @@
static sp<IBinder> sApplyToken;
void releaseBufferIfOverwriting(const layer_state_t& state);
static void mergeFrameTimelineInfo(FrameTimelineInfo& t, const FrameTimelineInfo& other);
- static void clearFrameTimelineInfo(FrameTimelineInfo& t);
protected:
std::unordered_map<sp<IBinder>, ComposerState, IBinderHash> mComposerStates;
diff --git a/libs/gui/tests/Android.bp b/libs/gui/tests/Android.bp
index cd35d2f..462ce6e 100644
--- a/libs/gui/tests/Android.bp
+++ b/libs/gui/tests/Android.bp
@@ -21,6 +21,7 @@
],
srcs: [
+ "LibGuiMain.cpp", // Custom gtest entrypoint
"BLASTBufferQueue_test.cpp",
"BufferItemConsumer_test.cpp",
"BufferQueue_test.cpp",
diff --git a/libs/gui/tests/AndroidTest.xml b/libs/gui/tests/AndroidTest.xml
index 5e09fff..31b10d7 100644
--- a/libs/gui/tests/AndroidTest.xml
+++ b/libs/gui/tests/AndroidTest.xml
@@ -23,6 +23,7 @@
<option name="screen-always-on" value="on" />
</target_preparer>
<option name="test-suite-tag" value="apct" />
+ <option name="not-shardable" value="true" />
<test class="com.android.tradefed.testtype.GTest" >
<option name="native-test-device-path" value="/data/local/tmp" />
<option name="module-name" value="libgui_test" />
diff --git a/libs/gui/tests/BLASTBufferQueue_test.cpp b/libs/gui/tests/BLASTBufferQueue_test.cpp
index a3ad680..cd90168 100644
--- a/libs/gui/tests/BLASTBufferQueue_test.cpp
+++ b/libs/gui/tests/BLASTBufferQueue_test.cpp
@@ -176,18 +176,6 @@
class BLASTBufferQueueTest : public ::testing::Test {
public:
protected:
- BLASTBufferQueueTest() {
- const ::testing::TestInfo* const testInfo =
- ::testing::UnitTest::GetInstance()->current_test_info();
- ALOGD("Begin test: %s.%s", testInfo->test_case_name(), testInfo->name());
- }
-
- ~BLASTBufferQueueTest() {
- const ::testing::TestInfo* const testInfo =
- ::testing::UnitTest::GetInstance()->current_test_info();
- ALOGD("End test: %s.%s", testInfo->test_case_name(), testInfo->name());
- }
-
void SetUp() {
mComposer = ComposerService::getComposerService();
mClient = new SurfaceComposerClient();
diff --git a/libs/gui/tests/BufferQueue_test.cpp b/libs/gui/tests/BufferQueue_test.cpp
index 2f1fd3e..d585881 100644
--- a/libs/gui/tests/BufferQueue_test.cpp
+++ b/libs/gui/tests/BufferQueue_test.cpp
@@ -17,6 +17,7 @@
#define LOG_TAG "BufferQueue_test"
//#define LOG_NDEBUG 0
+#include "Constants.h"
#include "MockConsumer.h"
#include <gui/BufferItem.h>
@@ -46,20 +47,6 @@
public:
protected:
- BufferQueueTest() {
- const ::testing::TestInfo* const testInfo =
- ::testing::UnitTest::GetInstance()->current_test_info();
- ALOGD("Begin test: %s.%s", testInfo->test_case_name(),
- testInfo->name());
- }
-
- ~BufferQueueTest() {
- const ::testing::TestInfo* const testInfo =
- ::testing::UnitTest::GetInstance()->current_test_info();
- ALOGD("End test: %s.%s", testInfo->test_case_name(),
- testInfo->name());
- }
-
void GetMinUndequeuedBufferCount(int* bufferCount) {
ASSERT_TRUE(bufferCount != nullptr);
ASSERT_EQ(OK, mProducer->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
@@ -535,7 +522,8 @@
int slot;
sp<Fence> fence;
ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
- mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+ nullptr));
sp<GraphicBuffer> buffer;
ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer));
@@ -578,7 +566,8 @@
sp<Fence> fence;
sp<GraphicBuffer> buffer;
ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
- mProducer->dequeueBuffer(&sharedSlot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ mProducer->dequeueBuffer(&sharedSlot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+ nullptr, nullptr));
ASSERT_EQ(OK, mProducer->requestBuffer(sharedSlot, &buffer));
// Queue the buffer
@@ -592,7 +581,9 @@
// always the same one and because async mode gets enabled.
int slot;
for (int i = 0; i < 5; i++) {
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+ nullptr, nullptr));
ASSERT_EQ(sharedSlot, slot);
ASSERT_EQ(OK, mProducer->queueBuffer(sharedSlot, input, &output));
}
@@ -629,7 +620,8 @@
sp<Fence> fence;
sp<GraphicBuffer> buffer;
ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
- mProducer->dequeueBuffer(&sharedSlot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ mProducer->dequeueBuffer(&sharedSlot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+ nullptr, nullptr));
ASSERT_EQ(OK, mProducer->requestBuffer(sharedSlot, &buffer));
// Queue the buffer
@@ -656,7 +648,9 @@
// always return the same one.
int slot;
for (int i = 0; i < 5; i++) {
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+ nullptr, nullptr));
ASSERT_EQ(sharedSlot, slot);
ASSERT_EQ(OK, mProducer->queueBuffer(sharedSlot, input, &output));
}
@@ -695,7 +689,8 @@
sp<Fence> fence;
sp<GraphicBuffer> buffer;
ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
- mProducer->dequeueBuffer(&sharedSlot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ mProducer->dequeueBuffer(&sharedSlot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+ nullptr, nullptr));
ASSERT_EQ(OK, mProducer->requestBuffer(sharedSlot, &buffer));
// Enable shared buffer mode
@@ -712,7 +707,9 @@
// always the same one and because async mode gets enabled.
int slot;
for (int i = 0; i < 5; i++) {
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+ nullptr, nullptr));
ASSERT_EQ(sharedSlot, slot);
ASSERT_EQ(OK, mProducer->queueBuffer(sharedSlot, input, &output));
}
@@ -747,7 +744,8 @@
for (int i = 0; i < 5; ++i) {
int slot = BufferQueue::INVALID_BUFFER_SLOT;
sp<Fence> fence = Fence::NO_FENCE;
- auto result = mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr);
+ auto result = mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+ nullptr, nullptr);
if (i < 2) {
ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
result);
@@ -774,7 +772,9 @@
for (int i = 0; i < 2; ++i) {
int slot = BufferQueue::INVALID_BUFFER_SLOT;
sp<Fence> fence = Fence::NO_FENCE;
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+ nullptr, nullptr));
ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer));
IGraphicBufferProducer::QueueBufferInput input(0ull, true,
HAL_DATASPACE_UNKNOWN, Rect::INVALID_RECT,
@@ -785,7 +785,9 @@
int slot = BufferQueue::INVALID_BUFFER_SLOT;
sp<Fence> fence = Fence::NO_FENCE;
auto startTime = systemTime();
- ASSERT_EQ(TIMED_OUT, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(TIMED_OUT,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+ nullptr));
ASSERT_GE(systemTime() - startTime, TIMEOUT);
// We're technically attaching the same buffer multiple times (since we
@@ -806,7 +808,8 @@
int slot = BufferQueue::INVALID_BUFFER_SLOT;
sp<Fence> sourceFence;
ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
- mProducer->dequeueBuffer(&slot, &sourceFence, 0, 0, 0, 0, nullptr, nullptr));
+ mProducer->dequeueBuffer(&slot, &sourceFence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+ nullptr, nullptr));
sp<GraphicBuffer> buffer;
ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer));
ASSERT_EQ(OK, mProducer->detachBuffer(slot));
@@ -829,7 +832,8 @@
int slot = BufferQueue::INVALID_BUFFER_SLOT;
sp<Fence> fence;
ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
- mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+ nullptr));
sp<GraphicBuffer> firstBuffer;
ASSERT_EQ(OK, mProducer->requestBuffer(slot, &firstBuffer));
@@ -841,7 +845,8 @@
// Dequeue a second buffer
slot = BufferQueue::INVALID_BUFFER_SLOT;
ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
- mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+ nullptr));
sp<GraphicBuffer> secondBuffer;
ASSERT_EQ(OK, mProducer->requestBuffer(slot, &secondBuffer));
@@ -892,8 +897,8 @@
int slots[3] = {};
mProducer->setMaxDequeuedBufferCount(3);
for (size_t i = 0; i < 3; ++i) {
- status_t result =
- mProducer->dequeueBuffer(&slots[i], &fence, 0, 0, 0, 0, nullptr, nullptr);
+ status_t result = mProducer->dequeueBuffer(&slots[i], &fence, 0, 0, 0,
+ TEST_PRODUCER_USAGE_BITS, nullptr, nullptr);
ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, result);
ASSERT_EQ(OK, mProducer->requestBuffer(slots[i], &buffer));
}
@@ -906,7 +911,9 @@
// The first segment is a two-buffer segment, so we only put one buffer into
// the queue at a time
for (size_t i = 0; i < 5; ++i) {
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+ nullptr, nullptr));
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
@@ -921,16 +928,22 @@
// two-buffer segment, but then at the end, we put two buffers in the queue
// at the same time before draining it.
for (size_t i = 0; i < 5; ++i) {
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+ nullptr, nullptr));
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
std::this_thread::sleep_for(16ms);
}
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+ nullptr));
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+ nullptr));
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
@@ -945,10 +958,14 @@
// The third segment is a triple-buffer segment, so the queue is switching
// between one buffer and two buffers deep.
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+ nullptr));
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
for (size_t i = 0; i < 5; ++i) {
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+ nullptr, nullptr));
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
@@ -1047,8 +1064,8 @@
int slots[4] = {};
mProducer->setMaxDequeuedBufferCount(4);
for (size_t i = 0; i < 4; ++i) {
- status_t result =
- mProducer->dequeueBuffer(&slots[i], &fence, 0, 0, 0, 0, nullptr, nullptr);
+ status_t result = mProducer->dequeueBuffer(&slots[i], &fence, 0, 0, 0,
+ TEST_PRODUCER_USAGE_BITS, nullptr, nullptr);
ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, result);
ASSERT_EQ(OK, mProducer->requestBuffer(slots[i], &buffer));
}
@@ -1059,14 +1076,22 @@
// Get buffers in all states: dequeued, filled, acquired, free
// Fill 3 buffers
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+ nullptr));
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+ nullptr));
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+ nullptr));
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
// Dequeue 1 buffer
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+ nullptr));
// Acquire and free 1 buffer
ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
@@ -1132,8 +1157,8 @@
int slots[2] = {};
ASSERT_EQ(OK, mProducer->setMaxDequeuedBufferCount(2));
for (size_t i = 0; i < 2; ++i) {
- status_t result =
- mProducer->dequeueBuffer(&slots[i], &fence, 0, 0, 0, 0, nullptr, nullptr);
+ status_t result = mProducer->dequeueBuffer(&slots[i], &fence, 0, 0, 0,
+ TEST_PRODUCER_USAGE_BITS, nullptr, nullptr);
ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, result);
ASSERT_EQ(OK, mProducer->requestBuffer(slots[i], &buffer));
}
@@ -1143,10 +1168,14 @@
// Fill 2 buffers without consumer consuming them. Verify that all
// queued buffer returns proper bufferReplaced flag
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+ nullptr));
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
ASSERT_EQ(false, output.bufferReplaced);
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+ nullptr));
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
ASSERT_EQ(true, output.bufferReplaced);
}
@@ -1167,7 +1196,8 @@
NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, Fence::NO_FENCE);
// Dequeue, request, and queue one buffer
- status_t result = mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr);
+ status_t result = mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+ nullptr, nullptr);
ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, result);
ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer));
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
@@ -1182,7 +1212,9 @@
EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
// Dequeue and queue the buffer again
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+ nullptr));
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
// Acquire and release the buffer again. Upon acquiring, the buffer handle
@@ -1194,7 +1226,9 @@
EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
// Dequeue and queue the buffer again
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+ nullptr));
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
// Disconnect the producer end. This should clear all of the slots and mark
diff --git a/libs/gui/tests/Constants.h b/libs/gui/tests/Constants.h
new file mode 100644
index 0000000..85c0f9f
--- /dev/null
+++ b/libs/gui/tests/Constants.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include <hardware/gralloc.h>
+
+// Arbitrary non-zero usage flag.
+constexpr uint64_t TEST_PRODUCER_USAGE_BITS = GRALLOC_USAGE_SW_READ_RARELY;
diff --git a/libs/gui/tests/CpuConsumer_test.cpp b/libs/gui/tests/CpuConsumer_test.cpp
index 0a14afa..d80bd9c 100644
--- a/libs/gui/tests/CpuConsumer_test.cpp
+++ b/libs/gui/tests/CpuConsumer_test.cpp
@@ -62,7 +62,7 @@
const ::testing::TestInfo* const test_info =
::testing::UnitTest::GetInstance()->current_test_info();
CpuConsumerTestParams params = GetParam();
- ALOGD("** Starting test %s (%d x %d, %d, 0x%x)",
+ ALOGD("** Starting parameterized test %s (%d x %d, %d, 0x%x)",
test_info->name(),
params.width, params.height,
params.maxLockedBuffers, params.format);
diff --git a/libs/gui/tests/GLTest.cpp b/libs/gui/tests/GLTest.cpp
index 3ae4b6d..afeea42 100644
--- a/libs/gui/tests/GLTest.cpp
+++ b/libs/gui/tests/GLTest.cpp
@@ -29,10 +29,6 @@
}
void GLTest::SetUp() {
- const ::testing::TestInfo* const testInfo =
- ::testing::UnitTest::GetInstance()->current_test_info();
- ALOGD("Begin test: %s.%s", testInfo->test_case_name(), testInfo->name());
-
mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
ASSERT_EQ(EGL_SUCCESS, eglGetError());
ASSERT_NE(EGL_NO_DISPLAY, mEglDisplay);
@@ -132,10 +128,6 @@
eglTerminate(mEglDisplay);
}
ASSERT_EQ(EGL_SUCCESS, eglGetError());
-
- const ::testing::TestInfo* const testInfo =
- ::testing::UnitTest::GetInstance()->current_test_info();
- ALOGD("End test: %s.%s", testInfo->test_case_name(), testInfo->name());
}
EGLint const* GLTest::getConfigAttribs() {
diff --git a/libs/gui/tests/IGraphicBufferProducer_test.cpp b/libs/gui/tests/IGraphicBufferProducer_test.cpp
index e6cb89c..b1f5d08 100644
--- a/libs/gui/tests/IGraphicBufferProducer_test.cpp
+++ b/libs/gui/tests/IGraphicBufferProducer_test.cpp
@@ -17,6 +17,7 @@
#define LOG_TAG "IGraphicBufferProducer_test"
//#define LOG_NDEBUG 0
+#include "Constants.h"
#include "MockConsumer.h"
#include <gtest/gtest.h>
@@ -40,7 +41,6 @@
#define TEST_API NATIVE_WINDOW_API_CPU
#define TEST_API_OTHER NATIVE_WINDOW_API_EGL // valid API that's not TEST_API
#define TEST_CONTROLLED_BY_APP false
-#define TEST_PRODUCER_USAGE_BITS (0)
namespace android {
@@ -82,11 +82,6 @@
IGraphicBufferProducerTest() {}
virtual void SetUp() {
- const ::testing::TestInfo* const testInfo =
- ::testing::UnitTest::GetInstance()->current_test_info();
- ALOGD("Begin test: %s.%s", testInfo->test_case_name(),
- testInfo->name());
-
mMC = new MockConsumer;
switch (GetParam()) {
@@ -111,13 +106,6 @@
ASSERT_OK(mConsumer->consumerConnect(mMC, /*controlledByApp*/ false));
}
- virtual void TearDown() {
- const ::testing::TestInfo* const testInfo =
- ::testing::UnitTest::GetInstance()->current_test_info();
- ALOGD("End test: %s.%s", testInfo->test_case_name(),
- testInfo->name());
- }
-
status_t TryConnectProducer() {
IGraphicBufferProducer::QueueBufferOutput output;
return mProducer->connect(TEST_TOKEN,
diff --git a/libs/gui/tests/LibGuiMain.cpp b/libs/gui/tests/LibGuiMain.cpp
new file mode 100644
index 0000000..10f7207
--- /dev/null
+++ b/libs/gui/tests/LibGuiMain.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2023 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.
+ */
+
+#include "gtest/gtest.h"
+#include "log/log.h"
+
+namespace {
+
+class TestCaseLogger : public ::testing::EmptyTestEventListener {
+ void OnTestStart(const ::testing::TestInfo& testInfo) override {
+ ALOGD("Begin test: %s#%s", testInfo.test_suite_name(), testInfo.name());
+ }
+
+ void OnTestEnd(const testing::TestInfo& testInfo) override {
+ ALOGD("End test: %s#%s", testInfo.test_suite_name(), testInfo.name());
+ }
+};
+
+} // namespace
+
+int main(int argc, char** argv) {
+ testing::InitGoogleTest(&argc, argv);
+ testing::UnitTest::GetInstance()->listeners().Append(new TestCaseLogger());
+ return RUN_ALL_TESTS();
+}
\ No newline at end of file
diff --git a/libs/gui/tests/Malicious.cpp b/libs/gui/tests/Malicious.cpp
index 58d7cc6..376420c 100644
--- a/libs/gui/tests/Malicious.cpp
+++ b/libs/gui/tests/Malicious.cpp
@@ -151,7 +151,6 @@
sp<MaliciousBQP> malicious = getMaliciousBQP();
sp<Surface> surface = new Surface(malicious);
- ASSERT_EQ(NO_ERROR, surface->connect(NATIVE_WINDOW_API_CPU, nullptr, false));
ANativeWindow_Buffer buffer;
ASSERT_EQ(NO_ERROR, surface->lock(&buffer, nullptr));
ASSERT_EQ(NO_ERROR, surface->unlockAndPost());
@@ -165,7 +164,6 @@
sp<MaliciousBQP> malicious = getMaliciousBQP();
sp<Surface> surface = new Surface(malicious);
- ASSERT_EQ(NO_ERROR, surface->connect(NATIVE_WINDOW_API_CPU, nullptr, false));
ANativeWindow_Buffer buffer;
ASSERT_EQ(NO_ERROR, surface->lock(&buffer, nullptr));
ASSERT_EQ(NO_ERROR, surface->unlockAndPost());
@@ -179,7 +177,6 @@
sp<MaliciousBQP> malicious = getMaliciousBQP();
sp<Surface> surface = new Surface(malicious);
- ASSERT_EQ(NO_ERROR, surface->connect(NATIVE_WINDOW_API_CPU, nullptr, false));
ANativeWindow_Buffer buffer;
ASSERT_EQ(NO_ERROR, surface->lock(&buffer, nullptr));
ASSERT_EQ(NO_ERROR, surface->unlockAndPost());
@@ -193,7 +190,6 @@
sp<MaliciousBQP> malicious = getMaliciousBQP();
sp<Surface> surface = new Surface(malicious);
- ASSERT_EQ(NO_ERROR, surface->connect(NATIVE_WINDOW_API_CPU, nullptr, false));
ANativeWindow_Buffer buffer;
ASSERT_EQ(NO_ERROR, surface->lock(&buffer, nullptr));
ASSERT_EQ(NO_ERROR, surface->unlockAndPost());
diff --git a/libs/gui/tests/StreamSplitter_test.cpp b/libs/gui/tests/StreamSplitter_test.cpp
index 2f14924..f34b03e 100644
--- a/libs/gui/tests/StreamSplitter_test.cpp
+++ b/libs/gui/tests/StreamSplitter_test.cpp
@@ -30,23 +30,7 @@
namespace android {
-class StreamSplitterTest : public ::testing::Test {
-
-protected:
- StreamSplitterTest() {
- const ::testing::TestInfo* const testInfo =
- ::testing::UnitTest::GetInstance()->current_test_info();
- ALOGD("Begin test: %s.%s", testInfo->test_case_name(),
- testInfo->name());
- }
-
- ~StreamSplitterTest() {
- const ::testing::TestInfo* const testInfo =
- ::testing::UnitTest::GetInstance()->current_test_info();
- ALOGD("End test: %s.%s", testInfo->test_case_name(),
- testInfo->name());
- }
-};
+class StreamSplitterTest : public ::testing::Test {};
struct FakeListener : public BnConsumerListener {
virtual void onFrameAvailable(const BufferItem& /* item */) {}
diff --git a/libs/gui/tests/SurfaceTextureClient_test.cpp b/libs/gui/tests/SurfaceTextureClient_test.cpp
index 82b6697..b28dca8 100644
--- a/libs/gui/tests/SurfaceTextureClient_test.cpp
+++ b/libs/gui/tests/SurfaceTextureClient_test.cpp
@@ -40,11 +40,6 @@
}
virtual void SetUp() {
- const ::testing::TestInfo* const testInfo =
- ::testing::UnitTest::GetInstance()->current_test_info();
- ALOGD("Begin test: %s.%s", testInfo->test_case_name(),
- testInfo->name());
-
sp<IGraphicBufferProducer> producer;
sp<IGraphicBufferConsumer> consumer;
BufferQueue::createBufferQueue(&producer, &consumer);
@@ -96,11 +91,6 @@
eglDestroyContext(mEglDisplay, mEglContext);
eglDestroySurface(mEglDisplay, mEglSurface);
eglTerminate(mEglDisplay);
-
- const ::testing::TestInfo* const testInfo =
- ::testing::UnitTest::GetInstance()->current_test_info();
- ALOGD("End test: %s.%s", testInfo->test_case_name(),
- testInfo->name());
}
virtual EGLint const* getConfigAttribs() {
diff --git a/libs/gui/tests/SurfaceTextureGL.h b/libs/gui/tests/SurfaceTextureGL.h
index 53eb68c..9d8af5d 100644
--- a/libs/gui/tests/SurfaceTextureGL.h
+++ b/libs/gui/tests/SurfaceTextureGL.h
@@ -17,6 +17,7 @@
#ifndef ANDROID_SURFACE_TEXTURE_GL_H
#define ANDROID_SURFACE_TEXTURE_GL_H
+#include "Constants.h"
#include "GLTest.h"
#include "FrameWaiter.h"
@@ -43,6 +44,7 @@
true, false);
mSTC = new Surface(producer);
mANW = mSTC;
+ ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(), TEST_PRODUCER_USAGE_BITS));
mTextureRenderer = new TextureRenderer(TEX_ID, mST);
ASSERT_NO_FATAL_FAILURE(mTextureRenderer->SetUp());
mFW = new FrameWaiter;
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index 096a43c..90c0a63 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include "Constants.h"
#include "MockConsumer.h"
#include <gtest/gtest.h>
@@ -148,6 +149,7 @@
/*listener*/listener));
const int BUFFER_COUNT = 4 + extraDiscardedBuffers;
ASSERT_EQ(NO_ERROR, native_window_set_buffer_count(window.get(), BUFFER_COUNT));
+ ASSERT_EQ(NO_ERROR, native_window_set_usage(window.get(), TEST_PRODUCER_USAGE_BITS));
ANativeWindowBuffer* buffers[BUFFER_COUNT];
// Dequeue first to allocate a number of buffers
@@ -530,7 +532,8 @@
ASSERT_EQ(NO_ERROR, native_window_api_connect(window.get(),
NATIVE_WINDOW_API_CPU));
- native_window_set_buffer_count(window.get(), 4);
+ ASSERT_EQ(NO_ERROR, native_window_set_buffer_count(window.get(), 4));
+ ASSERT_EQ(NO_ERROR, native_window_set_usage(window.get(), TEST_PRODUCER_USAGE_BITS));
int fence;
ANativeWindowBuffer* buffer;
@@ -560,6 +563,7 @@
/*reportBufferRemoval*/true));
const int BUFFER_COUNT = 4;
ASSERT_EQ(NO_ERROR, native_window_set_buffer_count(window.get(), BUFFER_COUNT));
+ ASSERT_EQ(NO_ERROR, native_window_set_usage(window.get(), TEST_PRODUCER_USAGE_BITS));
sp<GraphicBuffer> detachedBuffer;
sp<Fence> outFence;
@@ -1202,7 +1206,8 @@
ASSERT_EQ(NO_ERROR, native_window_api_connect(mWindow.get(),
NATIVE_WINDOW_API_CPU));
- native_window_set_buffer_count(mWindow.get(), 4);
+ ASSERT_EQ(NO_ERROR, native_window_set_buffer_count(mWindow.get(), 4));
+ ASSERT_EQ(NO_ERROR, native_window_set_usage(mWindow.get(), TEST_PRODUCER_USAGE_BITS));
}
void disableFrameTimestamps() {
@@ -2068,8 +2073,9 @@
sp<Surface> surface = new Surface(producer);
sp<ANativeWindow> window(surface);
- native_window_api_connect(window.get(), NATIVE_WINDOW_API_CPU);
- native_window_set_buffers_dimensions(window.get(), 0, 0);
+ ASSERT_EQ(NO_ERROR, native_window_api_connect(window.get(), NATIVE_WINDOW_API_CPU));
+ ASSERT_EQ(NO_ERROR, native_window_set_buffers_dimensions(window.get(), 0, 0));
+ ASSERT_EQ(NO_ERROR, native_window_set_usage(window.get(), TEST_PRODUCER_USAGE_BITS));
int fence;
ANativeWindowBuffer* buffer;
@@ -2121,6 +2127,7 @@
native_window_api_connect(window.get(), NATIVE_WINDOW_API_CPU);
consumer->setTransformHint(NATIVE_WINDOW_TRANSFORM_ROT_270);
native_window_set_buffers_dimensions(window.get(), 0, 0);
+ native_window_set_usage(window.get(), TEST_PRODUCER_USAGE_BITS);
ASSERT_EQ(NO_ERROR, window->dequeueBuffer(window.get(), &buffer, &fence));
EXPECT_EQ(10, buffer->width);
EXPECT_EQ(20, buffer->height);
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 4be7328..80a8c8d 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -33,6 +33,138 @@
],
}
+aidl_interface {
+ name: "inputconstants",
+ host_supported: true,
+ vendor_available: true,
+ unstable: true,
+ srcs: [
+ ":inputconstants_aidl",
+ ],
+
+ backend: {
+ rust: {
+ enabled: true,
+ },
+ },
+}
+
+rust_bindgen {
+ name: "libinput_bindgen",
+ host_supported: true,
+ crate_name: "input_bindgen",
+ visibility: ["//frameworks/native/services/inputflinger"],
+ wrapper_src: "InputWrapper.hpp",
+
+ include_dirs: [
+ "frameworks/native/include",
+ ],
+
+ source_stem: "bindings",
+
+ bindgen_flags: [
+ "--verbose",
+ "--allowlist-var=AMOTION_EVENT_FLAG_CANCELED",
+ "--allowlist-var=AMOTION_EVENT_ACTION_CANCEL",
+ "--allowlist-var=AMOTION_EVENT_ACTION_UP",
+ "--allowlist-var=AMOTION_EVENT_ACTION_POINTER_DOWN",
+ "--allowlist-var=AMOTION_EVENT_ACTION_DOWN",
+ "--allowlist-var=AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT",
+ "--allowlist-var=MAX_POINTER_ID",
+ ],
+
+ static_libs: [
+ "inputconstants-cpp",
+ "libui-types",
+ ],
+ shared_libs: ["libc++"],
+ header_libs: [
+ "native_headers",
+ "jni_headers",
+ "flatbuffer_headers",
+ ],
+}
+
+// Contains methods to help access C++ code from rust
+cc_library_static {
+ name: "libinput_from_rust_to_cpp",
+ cpp_std: "c++20",
+ host_supported: true,
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ "-Werror",
+ ],
+ srcs: [
+ "FromRustToCpp.cpp",
+ ],
+
+ generated_headers: [
+ "cxx-bridge-header",
+ ],
+ generated_sources: ["libinput_cxx_bridge_code"],
+
+ shared_libs: [
+ "libbase",
+ ],
+}
+
+genrule {
+ name: "libinput_cxx_bridge_code",
+ tools: ["cxxbridge"],
+ cmd: "$(location cxxbridge) $(in) >> $(out)",
+ srcs: ["input_verifier.rs"],
+ out: ["inputverifier_generated.cpp"],
+}
+
+genrule {
+ name: "libinput_cxx_bridge_header",
+ tools: ["cxxbridge"],
+ cmd: "$(location cxxbridge) $(in) --header >> $(out)",
+ srcs: ["input_verifier.rs"],
+ out: ["input_verifier.rs.h"],
+}
+
+rust_defaults {
+ name: "libinput_rust_defaults",
+ srcs: ["input_verifier.rs"],
+ host_supported: true,
+ rustlibs: [
+ "libbitflags",
+ "libcxx",
+ "libinput_bindgen",
+ "liblogger",
+ "liblog_rust",
+ "inputconstants-rust",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "liblog",
+ ],
+}
+
+rust_ffi_static {
+ name: "libinput_rust",
+ crate_name: "input",
+ defaults: ["libinput_rust_defaults"],
+}
+
+rust_test {
+ name: "libinput_rust_test",
+ defaults: ["libinput_rust_defaults"],
+ whole_static_libs: [
+ "libinput_from_rust_to_cpp",
+ ],
+ test_options: {
+ unit_test: true,
+ },
+ test_suites: ["device_tests"],
+ sanitize: {
+ hwaddress: true,
+ },
+}
+
cc_library {
name: "libinput",
cpp_std: "c++20",
@@ -44,6 +176,7 @@
"-Wno-unused-parameter",
],
srcs: [
+ "FromRustToCpp.cpp",
"Input.cpp",
"InputDevice.cpp",
"InputEventLabels.cpp",
@@ -70,9 +203,13 @@
export_header_lib_headers: ["jni_headers"],
generated_headers: [
+ "cxx-bridge-header",
+ "libinput_cxx_bridge_header",
"toolbox_input_labels",
],
+ generated_sources: ["libinput_cxx_bridge_code"],
+
shared_libs: [
"libbase",
"libcutils",
@@ -92,20 +229,29 @@
},
static_libs: [
+ "inputconstants-cpp",
"libui-types",
"libtflite_static",
],
+ whole_static_libs: [
+ "libinput_rust",
+ ],
+
export_static_lib_headers: [
"libui-types",
],
+ export_generated_headers: [
+ "cxx-bridge-header",
+ "libinput_cxx_bridge_header",
+ ],
+
target: {
android: {
srcs: [
"InputTransport.cpp",
"android/os/IInputFlinger.aidl",
- ":inputconstants_aidl",
],
export_shared_lib_headers: ["libbinder"],
@@ -140,9 +286,6 @@
host_linux: {
srcs: [
"InputTransport.cpp",
- "android/os/IInputConstants.aidl",
- "android/os/IInputFlinger.aidl",
- "android/os/InputConfig.aidl",
],
static_libs: [
"libhostgraphics",
diff --git a/libs/input/FromRustToCpp.cpp b/libs/input/FromRustToCpp.cpp
new file mode 100644
index 0000000..e4ce62e
--- /dev/null
+++ b/libs/input/FromRustToCpp.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2023 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.
+ */
+
+#include <android-base/logging.h>
+#include <ffi/FromRustToCpp.h>
+
+namespace android {
+
+bool shouldLog(rust::Str tag) {
+ return android::base::ShouldLog(android::base::LogSeverity::DEBUG, tag.data());
+}
+
+} // namespace android
diff --git a/libs/input/InputEventLabels.cpp b/libs/input/InputEventLabels.cpp
index 1c7cc12..bade686 100644
--- a/libs/input/InputEventLabels.cpp
+++ b/libs/input/InputEventLabels.cpp
@@ -434,17 +434,14 @@
// clang-format on
// --- InputEventLookup ---
-const std::unordered_map<std::string, int> InputEventLookup::KEYCODES = {KEYCODES_SEQUENCE};
-const std::vector<InputEventLabel> InputEventLookup::KEY_NAMES = {KEYCODES_SEQUENCE};
-
-const std::unordered_map<std::string, int> InputEventLookup::AXES = {AXES_SEQUENCE};
-
-const std::vector<InputEventLabel> InputEventLookup::AXES_NAMES = {AXES_SEQUENCE};
-
-const std::unordered_map<std::string, int> InputEventLookup::LEDS = {LEDS_SEQUENCE};
-
-const std::unordered_map<std::string, int> InputEventLookup::FLAGS = {FLAGS_SEQUENCE};
+InputEventLookup::InputEventLookup()
+ : KEYCODES({KEYCODES_SEQUENCE}),
+ KEY_NAMES({KEYCODES_SEQUENCE}),
+ AXES({AXES_SEQUENCE}),
+ AXES_NAMES({AXES_SEQUENCE}),
+ LEDS({LEDS_SEQUENCE}),
+ FLAGS({FLAGS_SEQUENCE}) {}
std::optional<int> InputEventLookup::lookupValueByLabel(
const std::unordered_map<std::string, int>& map, const char* literal) {
@@ -462,30 +459,36 @@
}
std::optional<int> InputEventLookup::getKeyCodeByLabel(const char* label) {
- return lookupValueByLabel(KEYCODES, label);
+ const auto& self = get();
+ return self.lookupValueByLabel(self.KEYCODES, label);
}
const char* InputEventLookup::getLabelByKeyCode(int32_t keyCode) {
- if (keyCode >= 0 && static_cast<size_t>(keyCode) < KEYCODES.size()) {
- return lookupLabelByValue(KEY_NAMES, keyCode);
+ const auto& self = get();
+ if (keyCode >= 0 && static_cast<size_t>(keyCode) < self.KEYCODES.size()) {
+ return get().lookupLabelByValue(self.KEY_NAMES, keyCode);
}
return nullptr;
}
std::optional<int> InputEventLookup::getKeyFlagByLabel(const char* label) {
- return lookupValueByLabel(FLAGS, label);
+ const auto& self = get();
+ return lookupValueByLabel(self.FLAGS, label);
}
std::optional<int> InputEventLookup::getAxisByLabel(const char* label) {
- return lookupValueByLabel(AXES, label);
+ const auto& self = get();
+ return lookupValueByLabel(self.AXES, label);
}
const char* InputEventLookup::getAxisLabel(int32_t axisId) {
- return lookupLabelByValue(AXES_NAMES, axisId);
+ const auto& self = get();
+ return lookupLabelByValue(self.AXES_NAMES, axisId);
}
std::optional<int> InputEventLookup::getLedByLabel(const char* label) {
- return lookupValueByLabel(LEDS, label);
+ const auto& self = get();
+ return lookupValueByLabel(self.LEDS, label);
}
namespace {
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index f6b4648..4d3d8bc 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -4,6 +4,7 @@
// Provides a shared memory transport for input events.
//
#define LOG_TAG "InputTransport"
+#define ATRACE_TAG ATRACE_TAG_INPUT
#include <errno.h>
#include <fcntl.h>
@@ -13,6 +14,7 @@
#include <sys/types.h>
#include <unistd.h>
+#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <binder/Parcel.h>
@@ -80,6 +82,7 @@
} // namespace
+using android::base::Result;
using android::base::StringPrintf;
namespace android {
@@ -449,6 +452,13 @@
ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ sent message of type %s", mName.c_str(),
ftl::enum_string(msg->header.type).c_str());
+
+ if (ATRACE_ENABLED()) {
+ std::string message =
+ StringPrintf("sendMessage(inputChannel=%s, seq=0x%" PRIx32 ", type=0x%" PRIx32 ")",
+ mName.c_str(), msg->header.seq, msg->header.type);
+ ATRACE_NAME(message.c_str());
+ }
return OK;
}
@@ -484,6 +494,13 @@
ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ received message of type %s", mName.c_str(),
ftl::enum_string(msg->header.type).c_str());
+
+ if (ATRACE_ENABLED()) {
+ std::string message = StringPrintf("receiveMessage(inputChannel=%s, seq=0x%" PRIx32
+ ", type=0x%" PRIx32 ")",
+ mName.c_str(), msg->header.seq, msg->header.type);
+ ATRACE_NAME(message.c_str());
+ }
return OK;
}
@@ -606,8 +623,12 @@
ATRACE_NAME(message.c_str());
}
if (verifyEvents()) {
- mInputVerifier.processMovement(deviceId, action, pointerCount, pointerProperties,
- pointerCoords, flags);
+ Result<void> result =
+ mInputVerifier.processMovement(deviceId, action, pointerCount, pointerProperties,
+ pointerCoords, flags);
+ if (!result.ok()) {
+ LOG(FATAL) << "Bad stream: " << result.error();
+ }
}
if (debugTransportPublisher()) {
std::string transformString;
diff --git a/libs/input/InputVerifier.cpp b/libs/input/InputVerifier.cpp
index eb75804..32b4ca0 100644
--- a/libs/input/InputVerifier.cpp
+++ b/libs/input/InputVerifier.cpp
@@ -18,111 +18,35 @@
#include <android-base/logging.h>
#include <input/InputVerifier.h>
+#include "input_verifier.rs.h"
+
+using android::base::Error;
+using android::base::Result;
+using android::input::RustPointerProperties;
namespace android {
-/**
- * Log all of the movements that are sent to this verifier. Helps to identify the streams that lead
- * to inconsistent events.
- * Enable this via "adb shell setprop log.tag.InputVerifierLogEvents DEBUG"
- */
-static bool logEvents() {
- return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "LogEvents", ANDROID_LOG_INFO);
-}
-
// --- InputVerifier ---
-InputVerifier::InputVerifier(const std::string& name) : mName(name){};
+InputVerifier::InputVerifier(const std::string& name)
+ : mVerifier(android::input::verifier::create(name)){};
-void InputVerifier::processMovement(int32_t deviceId, int32_t action, uint32_t pointerCount,
- const PointerProperties* pointerProperties,
- const PointerCoords* pointerCoords, int32_t flags) {
- if (logEvents()) {
- LOG(ERROR) << "Processing " << MotionEvent::actionToString(action) << " for device "
- << deviceId << " (" << pointerCount << " pointer"
- << (pointerCount == 1 ? "" : "s") << ") on " << mName;
+Result<void> InputVerifier::processMovement(int32_t deviceId, int32_t action, uint32_t pointerCount,
+ const PointerProperties* pointerProperties,
+ const PointerCoords* pointerCoords, int32_t flags) {
+ std::vector<RustPointerProperties> rpp;
+ for (size_t i = 0; i < pointerCount; i++) {
+ rpp.emplace_back(RustPointerProperties{.id = pointerProperties[i].id});
}
-
- switch (MotionEvent::getActionMasked(action)) {
- case AMOTION_EVENT_ACTION_DOWN: {
- auto [it, inserted] = mTouchingPointerIdsByDevice.insert({deviceId, {}});
- if (!inserted) {
- LOG(FATAL) << "Got ACTION_DOWN, but already have touching pointers " << it->second
- << " for device " << deviceId << " on " << mName;
- }
- it->second.set(pointerProperties[0].id);
- break;
- }
- case AMOTION_EVENT_ACTION_POINTER_DOWN: {
- auto it = mTouchingPointerIdsByDevice.find(deviceId);
- if (it == mTouchingPointerIdsByDevice.end()) {
- LOG(FATAL) << "Got POINTER_DOWN, but no touching pointers for device " << deviceId
- << " on " << mName;
- }
- it->second.set(pointerProperties[MotionEvent::getActionIndex(action)].id);
- break;
- }
- case AMOTION_EVENT_ACTION_MOVE: {
- ensureTouchingPointersMatch(deviceId, pointerCount, pointerProperties, "MOVE");
- break;
- }
- case AMOTION_EVENT_ACTION_POINTER_UP: {
- auto it = mTouchingPointerIdsByDevice.find(deviceId);
- if (it == mTouchingPointerIdsByDevice.end()) {
- LOG(FATAL) << "Got POINTER_UP, but no touching pointers for device " << deviceId
- << " on " << mName;
- }
- it->second.reset(pointerProperties[MotionEvent::getActionIndex(action)].id);
- break;
- }
- case AMOTION_EVENT_ACTION_UP: {
- auto it = mTouchingPointerIdsByDevice.find(deviceId);
- if (it == mTouchingPointerIdsByDevice.end()) {
- LOG(FATAL) << "Got ACTION_UP, but no record for deviceId " << deviceId << " on "
- << mName;
- }
- const auto& [_, touchingPointerIds] = *it;
- if (touchingPointerIds.count() != 1) {
- LOG(FATAL) << "Got ACTION_UP, but we have pointers: " << touchingPointerIds
- << " for deviceId " << deviceId << " on " << mName;
- }
- const int32_t pointerId = pointerProperties[0].id;
- if (!touchingPointerIds.test(pointerId)) {
- LOG(FATAL) << "Got ACTION_UP, but pointerId " << pointerId
- << " is not touching. Touching pointers: " << touchingPointerIds
- << " for deviceId " << deviceId << " on " << mName;
- }
- mTouchingPointerIdsByDevice.erase(it);
- break;
- }
- case AMOTION_EVENT_ACTION_CANCEL: {
- if ((flags & AMOTION_EVENT_FLAG_CANCELED) != AMOTION_EVENT_FLAG_CANCELED) {
- LOG(FATAL) << "For ACTION_CANCEL, must set FLAG_CANCELED";
- }
- ensureTouchingPointersMatch(deviceId, pointerCount, pointerProperties, "CANCEL");
- mTouchingPointerIdsByDevice.erase(deviceId);
- break;
- }
+ rust::Slice<const RustPointerProperties> properties{rpp.data(), rpp.size()};
+ rust::String errorMessage =
+ android::input::verifier::process_movement(*mVerifier, deviceId, action, properties,
+ flags);
+ if (errorMessage.empty()) {
+ return {};
+ } else {
+ return Error() << errorMessage;
}
}
-void InputVerifier::ensureTouchingPointersMatch(int32_t deviceId, uint32_t pointerCount,
- const PointerProperties* pointerProperties,
- const char* action) const {
- auto it = mTouchingPointerIdsByDevice.find(deviceId);
- if (it == mTouchingPointerIdsByDevice.end()) {
- LOG(FATAL) << "Got " << action << ", but no touching pointers for device " << deviceId
- << " on " << mName;
- }
- const auto& [_, touchingPointerIds] = *it;
- for (size_t i = 0; i < pointerCount; i++) {
- const int32_t pointerId = pointerProperties[i].id;
- if (!touchingPointerIds.test(pointerId)) {
- LOG(FATAL) << "Got " << action << " for pointerId " << pointerId
- << " but the touching pointers are " << touchingPointerIds << " on "
- << mName;
- }
- }
-};
-
} // namespace android
diff --git a/libs/input/InputWrapper.hpp b/libs/input/InputWrapper.hpp
new file mode 100644
index 0000000..a01080d
--- /dev/null
+++ b/libs/input/InputWrapper.hpp
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2023 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.
+ */
+
+#include <android/input.h>
+#include "input/Input.h"
diff --git a/libs/input/ffi/FromRustToCpp.h b/libs/input/ffi/FromRustToCpp.h
new file mode 100644
index 0000000..889945c
--- /dev/null
+++ b/libs/input/ffi/FromRustToCpp.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2023 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.
+ */
+
+#include "rust/cxx.h"
+
+namespace android {
+
+bool shouldLog(rust::Str tag);
+
+} // namespace android
diff --git a/libs/input/input_verifier.rs b/libs/input/input_verifier.rs
new file mode 100644
index 0000000..2e05a63
--- /dev/null
+++ b/libs/input/input_verifier.rs
@@ -0,0 +1,421 @@
+/*
+ * Copyright 2023 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.
+ */
+
+//! Validate the incoming motion stream.
+//! This class is not thread-safe.
+//! State is stored in the "InputVerifier" object
+//! that can be created via the 'create' method.
+//! Usage:
+//! Box<InputVerifier> verifier = create("inputChannel name");
+//! result = process_movement(verifier, ...);
+//! if (result) {
+//! crash(result.error_message());
+//! }
+
+use std::collections::HashMap;
+use std::collections::HashSet;
+
+use bitflags::bitflags;
+use log::info;
+
+#[cxx::bridge(namespace = "android::input")]
+mod ffi {
+ #[namespace = "android"]
+ unsafe extern "C++" {
+ include!("ffi/FromRustToCpp.h");
+ fn shouldLog(tag: &str) -> bool;
+ }
+ #[namespace = "android::input::verifier"]
+ extern "Rust" {
+ type InputVerifier;
+
+ fn create(name: String) -> Box<InputVerifier>;
+ fn process_movement(
+ verifier: &mut InputVerifier,
+ device_id: i32,
+ action: u32,
+ pointer_properties: &[RustPointerProperties],
+ flags: i32,
+ ) -> String;
+ }
+
+ pub struct RustPointerProperties {
+ id: i32,
+ }
+}
+
+use crate::ffi::shouldLog;
+use crate::ffi::RustPointerProperties;
+
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
+struct DeviceId(i32);
+
+fn process_movement(
+ verifier: &mut InputVerifier,
+ device_id: i32,
+ action: u32,
+ pointer_properties: &[RustPointerProperties],
+ flags: i32,
+) -> String {
+ let result = verifier.process_movement(
+ DeviceId(device_id),
+ action,
+ pointer_properties,
+ Flags::from_bits(flags).unwrap(),
+ );
+ match result {
+ Ok(()) => "".to_string(),
+ Err(e) => e,
+ }
+}
+
+fn create(name: String) -> Box<InputVerifier> {
+ Box::new(InputVerifier::new(&name))
+}
+
+#[repr(u32)]
+enum MotionAction {
+ Down = input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ Up = input_bindgen::AMOTION_EVENT_ACTION_UP,
+ Move = input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+ Cancel = input_bindgen::AMOTION_EVENT_ACTION_CANCEL,
+ Outside = input_bindgen::AMOTION_EVENT_ACTION_OUTSIDE,
+ PointerDown { action_index: usize } = input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN,
+ PointerUp { action_index: usize } = input_bindgen::AMOTION_EVENT_ACTION_POINTER_UP,
+ HoverEnter = input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+ HoverMove = input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE,
+ HoverExit = input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT,
+ Scroll = input_bindgen::AMOTION_EVENT_ACTION_SCROLL,
+ ButtonPress = input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+ ButtonRelease = input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE,
+}
+
+fn get_action_index(action: u32) -> usize {
+ let index = (action & input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
+ >> input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+ index.try_into().unwrap()
+}
+
+impl From<u32> for MotionAction {
+ fn from(action: u32) -> Self {
+ let action_masked = action & input_bindgen::AMOTION_EVENT_ACTION_MASK;
+ let action_index = get_action_index(action);
+ match action_masked {
+ input_bindgen::AMOTION_EVENT_ACTION_DOWN => MotionAction::Down,
+ input_bindgen::AMOTION_EVENT_ACTION_UP => MotionAction::Up,
+ input_bindgen::AMOTION_EVENT_ACTION_MOVE => MotionAction::Move,
+ input_bindgen::AMOTION_EVENT_ACTION_CANCEL => MotionAction::Cancel,
+ input_bindgen::AMOTION_EVENT_ACTION_OUTSIDE => MotionAction::Outside,
+ input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN => {
+ MotionAction::PointerDown { action_index }
+ }
+ input_bindgen::AMOTION_EVENT_ACTION_POINTER_UP => {
+ MotionAction::PointerUp { action_index }
+ }
+ input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER => MotionAction::HoverEnter,
+ input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE => MotionAction::HoverMove,
+ input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT => MotionAction::HoverExit,
+ input_bindgen::AMOTION_EVENT_ACTION_SCROLL => MotionAction::Scroll,
+ input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS => MotionAction::ButtonPress,
+ input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE => MotionAction::ButtonRelease,
+ _ => panic!("Unknown action: {}", action),
+ }
+ }
+}
+
+bitflags! {
+ struct Flags: i32 {
+ const CANCELED = input_bindgen::AMOTION_EVENT_FLAG_CANCELED;
+ }
+}
+
+fn motion_action_to_string(action: u32) -> String {
+ match action.into() {
+ MotionAction::Down => "DOWN".to_string(),
+ MotionAction::Up => "UP".to_string(),
+ MotionAction::Move => "MOVE".to_string(),
+ MotionAction::Cancel => "CANCEL".to_string(),
+ MotionAction::Outside => "OUTSIDE".to_string(),
+ MotionAction::PointerDown { action_index } => {
+ format!("POINTER_DOWN({})", action_index)
+ }
+ MotionAction::PointerUp { action_index } => {
+ format!("POINTER_UP({})", action_index)
+ }
+ MotionAction::HoverMove => "HOVER_MOVE".to_string(),
+ MotionAction::Scroll => "SCROLL".to_string(),
+ MotionAction::HoverEnter => "HOVER_ENTER".to_string(),
+ MotionAction::HoverExit => "HOVER_EXIT".to_string(),
+ MotionAction::ButtonPress => "BUTTON_PRESS".to_string(),
+ MotionAction::ButtonRelease => "BUTTON_RELEASE".to_string(),
+ }
+}
+
+/**
+ * Log all of the movements that are sent to this verifier. Helps to identify the streams that lead
+ * to inconsistent events.
+ * Enable this via "adb shell setprop log.tag.InputVerifierLogEvents DEBUG"
+ */
+fn log_events() -> bool {
+ shouldLog("InputVerifierLogEvents")
+}
+
+struct InputVerifier {
+ name: String,
+ touching_pointer_ids_by_device: HashMap<DeviceId, HashSet<i32>>,
+}
+
+impl InputVerifier {
+ fn new(name: &str) -> Self {
+ logger::init(
+ logger::Config::default()
+ .with_tag_on_device("InputVerifier")
+ .with_min_level(log::Level::Trace),
+ );
+ Self { name: name.to_owned(), touching_pointer_ids_by_device: HashMap::new() }
+ }
+
+ fn process_movement(
+ &mut self,
+ device_id: DeviceId,
+ action: u32,
+ pointer_properties: &[RustPointerProperties],
+ flags: Flags,
+ ) -> Result<(), String> {
+ if log_events() {
+ info!(
+ "Processing {} for device {:?} ({} pointer{}) on {}",
+ motion_action_to_string(action),
+ device_id,
+ pointer_properties.len(),
+ if pointer_properties.len() == 1 { "" } else { "s" },
+ self.name
+ );
+ }
+
+ match action.into() {
+ MotionAction::Down => {
+ let it = self
+ .touching_pointer_ids_by_device
+ .entry(device_id)
+ .or_insert_with(HashSet::new);
+ let pointer_id = pointer_properties[0].id;
+ if it.contains(&pointer_id) {
+ return Err(format!(
+ "{}: Invalid DOWN event - pointers already down for device {:?}: {:?}",
+ self.name, device_id, it
+ ));
+ }
+ it.insert(pointer_id);
+ }
+ MotionAction::PointerDown { action_index } => {
+ if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
+ return Err(format!(
+ "{}: Received POINTER_DOWN but no pointers are currently down \
+ for device {:?}",
+ self.name, device_id
+ ));
+ }
+ let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
+ let pointer_id = pointer_properties[action_index].id;
+ if it.contains(&pointer_id) {
+ return Err(format!(
+ "{}: Pointer with id={} not found in the properties",
+ self.name, pointer_id
+ ));
+ }
+ it.insert(pointer_id);
+ }
+ MotionAction::Move => {
+ if !self.ensure_touching_pointers_match(device_id, pointer_properties) {
+ return Err(format!(
+ "{}: ACTION_MOVE touching pointers don't match",
+ self.name
+ ));
+ }
+ }
+ MotionAction::PointerUp { action_index } => {
+ if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
+ return Err(format!(
+ "{}: Received POINTER_UP but no pointers are currently down for device \
+ {:?}",
+ self.name, device_id
+ ));
+ }
+ let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
+ let pointer_id = pointer_properties[action_index].id;
+ it.remove(&pointer_id);
+ }
+ MotionAction::Up => {
+ if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
+ return Err(format!(
+ "{} Received ACTION_UP but no pointers are currently down for device {:?}",
+ self.name, device_id
+ ));
+ }
+ let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
+ if it.len() != 1 {
+ return Err(format!(
+ "{}: Got ACTION_UP, but we have pointers: {:?} for device {:?}",
+ self.name, it, device_id
+ ));
+ }
+ let pointer_id = pointer_properties[0].id;
+ if !it.contains(&pointer_id) {
+ return Err(format!(
+ "{}: Got ACTION_UP, but pointerId {} is not touching. Touching pointers:\
+ {:?} for device {:?}",
+ self.name, pointer_id, it, device_id
+ ));
+ }
+ it.clear();
+ }
+ MotionAction::Cancel => {
+ if flags.contains(Flags::CANCELED) {
+ return Err(format!(
+ "{}: For ACTION_CANCEL, must set FLAG_CANCELED",
+ self.name
+ ));
+ }
+ if !self.ensure_touching_pointers_match(device_id, pointer_properties) {
+ return Err(format!(
+ "{}: Got ACTION_CANCEL, but the pointers don't match. \
+ Existing pointers: {:?}",
+ self.name, self.touching_pointer_ids_by_device
+ ));
+ }
+ self.touching_pointer_ids_by_device.remove(&device_id);
+ }
+ _ => return Ok(()),
+ }
+ Ok(())
+ }
+
+ fn ensure_touching_pointers_match(
+ &self,
+ device_id: DeviceId,
+ pointer_properties: &[RustPointerProperties],
+ ) -> bool {
+ let Some(pointers) = self.touching_pointer_ids_by_device.get(&device_id) else {
+ return false;
+ };
+
+ for pointer_property in pointer_properties.iter() {
+ let pointer_id = pointer_property.id;
+ if !pointers.contains(&pointer_id) {
+ return false;
+ }
+ }
+ true
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::DeviceId;
+ use crate::Flags;
+ use crate::InputVerifier;
+ use crate::RustPointerProperties;
+ #[test]
+ fn single_pointer_stream() {
+ let mut verifier = InputVerifier::new("Test");
+ let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ &pointer_properties,
+ Flags::empty(),
+ )
+ .is_ok());
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+ &pointer_properties,
+ Flags::empty(),
+ )
+ .is_ok());
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ input_bindgen::AMOTION_EVENT_ACTION_UP,
+ &pointer_properties,
+ Flags::empty(),
+ )
+ .is_ok());
+ }
+
+ #[test]
+ fn multi_device_stream() {
+ let mut verifier = InputVerifier::new("Test");
+ let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ &pointer_properties,
+ Flags::empty(),
+ )
+ .is_ok());
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+ &pointer_properties,
+ Flags::empty(),
+ )
+ .is_ok());
+ assert!(verifier
+ .process_movement(
+ DeviceId(2),
+ input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ &pointer_properties,
+ Flags::empty(),
+ )
+ .is_ok());
+ assert!(verifier
+ .process_movement(
+ DeviceId(2),
+ input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+ &pointer_properties,
+ Flags::empty(),
+ )
+ .is_ok());
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ input_bindgen::AMOTION_EVENT_ACTION_UP,
+ &pointer_properties,
+ Flags::empty(),
+ )
+ .is_ok());
+ }
+
+ #[test]
+ fn test_invalid_up() {
+ let mut verifier = InputVerifier::new("Test");
+ let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ input_bindgen::AMOTION_EVENT_ACTION_UP,
+ &pointer_properties,
+ Flags::empty(),
+ )
+ .is_err());
+ }
+}
diff --git a/libs/nativewindow/include/android/hardware_buffer_aidl.h b/libs/nativewindow/include/android/hardware_buffer_aidl.h
index 1659d54..e269f0d 100644
--- a/libs/nativewindow/include/android/hardware_buffer_aidl.h
+++ b/libs/nativewindow/include/android/hardware_buffer_aidl.h
@@ -34,6 +34,10 @@
#include <android/hardware_buffer.h>
#include <sys/cdefs.h>
+#ifdef __cplusplus
+#include <string>
+#endif
+
__BEGIN_DECLS
/**
@@ -142,6 +146,15 @@
return ret;
}
+ inline std::string toString() const {
+ if (!mBuffer) {
+ return "<HardwareBuffer: Invalid>";
+ }
+ uint64_t id = 0;
+ AHardwareBuffer_getId(mBuffer, &id);
+ return "<HardwareBuffer " + std::to_string(id) + ">";
+ }
+
private:
HardwareBuffer(const HardwareBuffer& other) = delete;
HardwareBuffer& operator=(const HardwareBuffer& other) = delete;
diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h
index 0fee3c1..edaa422 100644
--- a/libs/nativewindow/include/system/window.h
+++ b/libs/nativewindow/include/system/window.h
@@ -1066,12 +1066,33 @@
(int)compatibility, (int)changeFrameRateStrategy);
}
+struct ANativeWindowFrameTimelineInfo {
+ // Frame Id received from ANativeWindow_getNextFrameId.
+ uint64_t frameNumber;
+
+ // VsyncId received from the Choreographer callback that started this frame.
+ int64_t frameTimelineVsyncId;
+
+ // Input Event ID received from the input event that started this frame.
+ int32_t inputEventId;
+
+ // The time which this frame rendering started (i.e. when Choreographer callback actually run)
+ int64_t startTimeNanos;
+
+ // Whether or not to use the vsyncId to determine the refresh rate. Used for TextureView only.
+ int32_t useForRefreshRateSelection;
+
+ // The VsyncId of a frame that was not drawn and squashed into this frame.
+ // Used for UI thread updates that were not picked up by RenderThread on time.
+ int64_t skippedFrameVsyncId;
+
+ // The start time of a frame that was not drawn and squashed into this frame.
+ int64_t skippedFrameStartTimeNanos;
+};
+
static inline int native_window_set_frame_timeline_info(
- struct ANativeWindow* window, uint64_t frameNumber, int64_t frameTimelineVsyncId,
- int32_t inputEventId, int64_t startTimeNanos, int32_t useForRefreshRateSelection) {
- return window->perform(window, NATIVE_WINDOW_SET_FRAME_TIMELINE_INFO, frameNumber,
- frameTimelineVsyncId, inputEventId, startTimeNanos,
- useForRefreshRateSelection);
+ struct ANativeWindow* window, struct ANativeWindowFrameTimelineInfo frameTimelineInfo) {
+ return window->perform(window, NATIVE_WINDOW_SET_FRAME_TIMELINE_INFO, frameTimelineInfo);
}
// ------------------------------------------------------------------------------------------------
diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
index 5854135..fda6ea1 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -86,6 +86,7 @@
// Debugging settings
static const bool kPrintLayerSettings = false;
static const bool kFlushAfterEveryLayer = kPrintLayerSettings;
+static constexpr bool kEnableLayerBrightening = true;
} // namespace
@@ -699,7 +700,8 @@
// ...and compute the dimming ratio if dimming is requested
const float displayDimmingRatio = display.targetLuminanceNits > 0.f &&
- maxLayerWhitePoint > 0.f && display.targetLuminanceNits > maxLayerWhitePoint
+ maxLayerWhitePoint > 0.f &&
+ (kEnableLayerBrightening || display.targetLuminanceNits > maxLayerWhitePoint)
? maxLayerWhitePoint / display.targetLuminanceNits
: 1.f;
diff --git a/libs/ultrahdr/fuzzer/Android.bp b/libs/ultrahdr/fuzzer/Android.bp
index 27b38c3..6c0a2f5 100644
--- a/libs/ultrahdr/fuzzer/Android.bp
+++ b/libs/ultrahdr/fuzzer/Android.bp
@@ -24,7 +24,17 @@
cc_defaults {
name: "ultrahdr_fuzzer_defaults",
host_supported: true,
- static_libs: ["liblog"],
+ shared_libs: [
+ "libimage_io",
+ "libjpeg",
+ ],
+ static_libs: [
+ "libjpegdecoder",
+ "libjpegencoder",
+ "libultrahdr",
+ "libutils",
+ "liblog",
+ ],
target: {
darwin: {
enabled: false,
@@ -37,6 +47,8 @@
description: "The fuzzers target the APIs of jpeg hdr",
service_privilege: "constrained",
users: "multi_user",
+ fuzzed_code_usage: "future_version",
+ vector: "local_no_privileges_required",
},
}
@@ -46,20 +58,12 @@
srcs: [
"ultrahdr_enc_fuzzer.cpp",
],
- shared_libs: [
- "libimage_io",
- "libjpeg",
- "liblog",
- ],
- static_libs: [
- "libjpegdecoder",
- "libjpegencoder",
- "libultrahdr",
- "libutils",
- ],
- fuzz_config: {
- fuzzed_code_usage: "future_version",
- vector: "local_no_privileges_required",
- },
}
+cc_fuzz {
+ name: "ultrahdr_dec_fuzzer",
+ defaults: ["ultrahdr_fuzzer_defaults"],
+ srcs: [
+ "ultrahdr_dec_fuzzer.cpp",
+ ],
+}
diff --git a/libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp b/libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp
new file mode 100644
index 0000000..ad1d57a
--- /dev/null
+++ b/libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2023 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.
+ */
+
+// System include files
+#include <fuzzer/FuzzedDataProvider.h>
+#include <iostream>
+#include <vector>
+
+// User include files
+#include "ultrahdr/jpegr.h"
+
+using namespace android::ultrahdr;
+
+// Transfer functions for image data, sync with ultrahdr.h
+const int kOfMin = ULTRAHDR_OUTPUT_UNSPECIFIED + 1;
+const int kOfMax = ULTRAHDR_OUTPUT_MAX;
+
+class UltraHdrDecFuzzer {
+public:
+ UltraHdrDecFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){};
+ void process();
+
+private:
+ FuzzedDataProvider mFdp;
+};
+
+void UltraHdrDecFuzzer::process() {
+ // hdr_of
+ auto of = static_cast<ultrahdr_output_format>(mFdp.ConsumeIntegralInRange<int>(kOfMin, kOfMax));
+ auto buffer = mFdp.ConsumeRemainingBytes<uint8_t>();
+ jpegr_compressed_struct jpegImgR{buffer.data(), (int)buffer.size(), (int)buffer.size(),
+ ULTRAHDR_COLORGAMUT_UNSPECIFIED};
+
+ std::vector<uint8_t> iccData(0);
+ std::vector<uint8_t> exifData(0);
+ jpegr_info_struct info{0, 0, &iccData, &exifData};
+ JpegR jpegHdr;
+ (void)jpegHdr.getJPEGRInfo(&jpegImgR, &info);
+//#define DUMP_PARAM
+#ifdef DUMP_PARAM
+ std::cout << "input buffer size " << jpegImgR.length << std::endl;
+ std::cout << "image dimensions " << info.width << " x " << info.width << std::endl;
+#endif
+ size_t outSize = info.width * info.height * ((of == ULTRAHDR_OUTPUT_SDR) ? 4 : 8);
+ jpegr_uncompressed_struct decodedJpegR;
+ auto decodedRaw = std::make_unique<uint8_t[]>(outSize);
+ decodedJpegR.data = decodedRaw.get();
+ ultrahdr_metadata_struct metadata;
+ jpegr_uncompressed_struct decodedGainMap{};
+ (void)jpegHdr.decodeJPEGR(&jpegImgR, &decodedJpegR,
+ mFdp.ConsumeFloatingPointInRange<float>(1.0, FLT_MAX), nullptr, of,
+ &decodedGainMap, &metadata);
+ if (decodedGainMap.data) free(decodedGainMap.data);
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ UltraHdrDecFuzzer fuzzHandle(data, size);
+ fuzzHandle.process();
+ return 0;
+}
diff --git a/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp b/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp
index 7faa157..bbe58e0 100644
--- a/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp
+++ b/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp
@@ -55,12 +55,9 @@
const int kQfMin = 0;
const int kQfMax = 100;
-// seed
-const unsigned kSeed = 0x7ab7;
-
-class JpegHDRFuzzer {
+class UltraHdrEncFuzzer {
public:
- JpegHDRFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){};
+ UltraHdrEncFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){};
void process();
void fillP010Buffer(uint16_t* data, int width, int height, int stride);
void fill420Buffer(uint8_t* data, int size);
@@ -69,7 +66,7 @@
FuzzedDataProvider mFdp;
};
-void JpegHDRFuzzer::fillP010Buffer(uint16_t* data, int width, int height, int stride) {
+void UltraHdrEncFuzzer::fillP010Buffer(uint16_t* data, int width, int height, int stride) {
uint16_t* tmp = data;
std::vector<uint16_t> buffer(16);
for (int i = 0; i < buffer.size(); i++) {
@@ -78,22 +75,24 @@
for (int j = 0; j < height; j++) {
for (int i = 0; i < width; i += buffer.size()) {
memcpy(data + i, buffer.data(), std::min((int)buffer.size(), (width - i)));
- std::shuffle(buffer.begin(), buffer.end(), std::default_random_engine(kSeed));
+ std::shuffle(buffer.begin(), buffer.end(),
+ std::default_random_engine(std::random_device{}()));
}
tmp += stride;
}
}
-void JpegHDRFuzzer::fill420Buffer(uint8_t* data, int size) {
+void UltraHdrEncFuzzer::fill420Buffer(uint8_t* data, int size) {
std::vector<uint8_t> buffer(16);
mFdp.ConsumeData(buffer.data(), buffer.size());
for (int i = 0; i < size; i += buffer.size()) {
memcpy(data + i, buffer.data(), std::min((int)buffer.size(), (size - i)));
- std::shuffle(buffer.begin(), buffer.end(), std::default_random_engine(kSeed));
+ std::shuffle(buffer.begin(), buffer.end(),
+ std::default_random_engine(std::random_device{}()));
}
}
-void JpegHDRFuzzer::process() {
+void UltraHdrEncFuzzer::process() {
while (mFdp.remaining_bytes()) {
struct jpegr_uncompressed_struct p010Img {};
struct jpegr_uncompressed_struct yuv420Img {};
@@ -250,15 +249,20 @@
jpegGainMap.data = gainMapEncoder.getCompressedImagePtr();
jpegGainMap.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
ultrahdr_metadata_struct metadata;
- metadata.version = "1.3.1";
+ metadata.version = "1.0";
if (tf == ULTRAHDR_TF_HLG) {
metadata.maxContentBoost = kHlgMaxNits / kSdrWhiteNits;
} else if (tf == ULTRAHDR_TF_PQ) {
metadata.maxContentBoost = kPqMaxNits / kSdrWhiteNits;
} else {
- metadata.maxContentBoost = 0;
+ metadata.maxContentBoost = 1.0f;
}
metadata.minContentBoost = 1.0f;
+ metadata.gamma = 1.0f;
+ metadata.offsetSdr = 0.0f;
+ metadata.offsetHdr = 0.0f;
+ metadata.hdrCapacityMin = 1.0f;
+ metadata.hdrCapacityMax = metadata.maxContentBoost;
status = jpegHdr.encodeJPEGR(&jpegImg, &jpegGainMap, &metadata, &jpegImgR);
}
}
@@ -293,7 +297,7 @@
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
- JpegHDRFuzzer fuzzHandle(data, size);
+ UltraHdrEncFuzzer fuzzHandle(data, size);
fuzzHandle.process();
return 0;
}
diff --git a/libs/ultrahdr/gainmapmath.cpp b/libs/ultrahdr/gainmapmath.cpp
index 37c3cf3..ee15363 100644
--- a/libs/ultrahdr/gainmapmath.cpp
+++ b/libs/ultrahdr/gainmapmath.cpp
@@ -119,34 +119,39 @@
return (value < 0.0f) ? 0.0f : (value > kMaxPixelFloat) ? kMaxPixelFloat : value;
}
-// See IEC 61966-2-1, Equation F.7.
+// See IEC 61966-2-1/Amd 1:2003, Equation F.7.
static const float kSrgbR = 0.2126f, kSrgbG = 0.7152f, kSrgbB = 0.0722f;
float srgbLuminance(Color e) {
return kSrgbR * e.r + kSrgbG * e.g + kSrgbB * e.b;
}
-// See ECMA TR/98, Section 7.
-static const float kSrgbRCr = 1.402f, kSrgbGCb = 0.34414f, kSrgbGCr = 0.71414f, kSrgbBCb = 1.772f;
-
-Color srgbYuvToRgb(Color e_gamma) {
- return {{{ clampPixelFloat(e_gamma.y + kSrgbRCr * e_gamma.v),
- clampPixelFloat(e_gamma.y - kSrgbGCb * e_gamma.u - kSrgbGCr * e_gamma.v),
- clampPixelFloat(e_gamma.y + kSrgbBCb * e_gamma.u) }}};
-}
-
-// See ECMA TR/98, Section 7.
-static const float kSrgbYR = 0.299f, kSrgbYG = 0.587f, kSrgbYB = 0.114f;
-static const float kSrgbUR = -0.1687f, kSrgbUG = -0.3313f, kSrgbUB = 0.5f;
-static const float kSrgbVR = 0.5f, kSrgbVG = -0.4187f, kSrgbVB = -0.0813f;
+// See ITU-R BT.709-6, Section 3.
+// Uses the same coefficients for deriving luma signal as
+// IEC 61966-2-1/Amd 1:2003 states for luminance, so we reuse the luminance
+// function above.
+static const float kSrgbCb = 1.8556f, kSrgbCr = 1.5748f;
Color srgbRgbToYuv(Color e_gamma) {
- return {{{ kSrgbYR * e_gamma.r + kSrgbYG * e_gamma.g + kSrgbYB * e_gamma.b,
- kSrgbUR * e_gamma.r + kSrgbUG * e_gamma.g + kSrgbUB * e_gamma.b,
- kSrgbVR * e_gamma.r + kSrgbVG * e_gamma.g + kSrgbVB * e_gamma.b }}};
+ float y_gamma = srgbLuminance(e_gamma);
+ return {{{ y_gamma,
+ (e_gamma.b - y_gamma) / kSrgbCb,
+ (e_gamma.r - y_gamma) / kSrgbCr }}};
}
-// See IEC 61966-2-1, Equations F.5 and F.6.
+// See ITU-R BT.709-6, Section 3.
+// Same derivation to BT.2100's YUV->RGB, below. Similar to srgbRgbToYuv, we
+// can reuse the luminance coefficients since they are the same.
+static const float kSrgbGCb = kSrgbB * kSrgbCb / kSrgbG;
+static const float kSrgbGCr = kSrgbR * kSrgbCr / kSrgbG;
+
+Color srgbYuvToRgb(Color e_gamma) {
+ return {{{ clampPixelFloat(e_gamma.y + kSrgbCr * e_gamma.v),
+ clampPixelFloat(e_gamma.y - kSrgbGCb * e_gamma.u - kSrgbGCr * e_gamma.v),
+ clampPixelFloat(e_gamma.y + kSrgbCb * e_gamma.u) }}};
+}
+
+// See IEC 61966-2-1/Amd 1:2003, Equations F.5 and F.6.
float srgbInvOetf(float e_gamma) {
if (e_gamma <= 0.04045f) {
return e_gamma / 12.92f;
@@ -178,13 +183,38 @@
////////////////////////////////////////////////////////////////////////////////
// Display-P3 transformations
-// See SMPTE EG 432-1, Table 7-2.
+// See SMPTE EG 432-1, Equation 7-8.
static const float kP3R = 0.20949f, kP3G = 0.72160f, kP3B = 0.06891f;
float p3Luminance(Color e) {
return kP3R * e.r + kP3G * e.g + kP3B * e.b;
}
+// See ITU-R BT.601-7, Sections 2.5.1 and 2.5.2.
+// Unfortunately, calculation of luma signal differs from calculation of
+// luminance for Display-P3, so we can't reuse p3Luminance here.
+static const float kP3YR = 0.299f, kP3YG = 0.587f, kP3YB = 0.114f;
+static const float kP3Cb = 1.772f, kP3Cr = 1.402f;
+
+Color p3RgbToYuv(Color e_gamma) {
+ float y_gamma = kP3YR * e_gamma.r + kP3YG * e_gamma.g + kP3YB * e_gamma.b;
+ return {{{ y_gamma,
+ (e_gamma.b - y_gamma) / kP3Cb,
+ (e_gamma.r - y_gamma) / kP3Cr }}};
+}
+
+// See ITU-R BT.601-7, Sections 2.5.1 and 2.5.2.
+// Same derivation to BT.2100's YUV->RGB, below. Similar to p3RgbToYuv, we must
+// use luma signal coefficients rather than the luminance coefficients.
+static const float kP3GCb = kP3YB * kP3Cb / kP3YG;
+static const float kP3GCr = kP3YR * kP3Cr / kP3YG;
+
+Color p3YuvToRgb(Color e_gamma) {
+ return {{{ clampPixelFloat(e_gamma.y + kP3Cr * e_gamma.v),
+ clampPixelFloat(e_gamma.y - kP3GCb * e_gamma.u - kP3GCr * e_gamma.v),
+ clampPixelFloat(e_gamma.y + kP3Cb * e_gamma.u) }}};
+}
+
////////////////////////////////////////////////////////////////////////////////
// BT.2100 transformations - according to ITU-R BT.2100-2
@@ -197,6 +227,8 @@
}
// See ITU-R BT.2100-2, Table 6, Derivation of colour difference signals.
+// BT.2100 uses the same coefficients for calculating luma signal and luminance,
+// so we reuse the luminance function here.
static const float kBt2100Cb = 1.8814f, kBt2100Cr = 1.4746f;
Color bt2100RgbToYuv(Color e_gamma) {
@@ -206,6 +238,10 @@
(e_gamma.r - y_gamma) / kBt2100Cr }}};
}
+// See ITU-R BT.2100-2, Table 6, Derivation of colour difference signals.
+//
+// Similar to bt2100RgbToYuv above, we can reuse the luminance coefficients.
+//
// Derived by inversing bt2100RgbToYuv. The derivation for R and B are pretty
// straight forward; we just invert the formulas for U and V above. But deriving
// the formula for G is a bit more complicated:
@@ -440,6 +476,85 @@
}
}
+// All of these conversions are derived from the respective input YUV->RGB conversion followed by
+// the RGB->YUV for the receiving encoding. They are consistent with the RGB<->YUV functions in this
+// file, given that we uses BT.709 encoding for sRGB and BT.601 encoding for Display-P3, to match
+// DataSpace.
+
+Color yuv709To601(Color e_gamma) {
+ return {{{ 1.0f * e_gamma.y + 0.101579f * e_gamma.u + 0.196076f * e_gamma.v,
+ 0.0f * e_gamma.y + 0.989854f * e_gamma.u + -0.110653f * e_gamma.v,
+ 0.0f * e_gamma.y + -0.072453f * e_gamma.u + 0.983398f * e_gamma.v }}};
+}
+
+Color yuv709To2100(Color e_gamma) {
+ return {{{ 1.0f * e_gamma.y + -0.016969f * e_gamma.u + 0.096312f * e_gamma.v,
+ 0.0f * e_gamma.y + 0.995306f * e_gamma.u + -0.051192f * e_gamma.v,
+ 0.0f * e_gamma.y + 0.011507f * e_gamma.u + 1.002637f * e_gamma.v }}};
+}
+
+Color yuv601To709(Color e_gamma) {
+ return {{{ 1.0f * e_gamma.y + -0.118188f * e_gamma.u + -0.212685f * e_gamma.v,
+ 0.0f * e_gamma.y + 1.018640f * e_gamma.u + 0.114618f * e_gamma.v,
+ 0.0f * e_gamma.y + 0.075049f * e_gamma.u + 1.025327f * e_gamma.v }}};
+}
+
+Color yuv601To2100(Color e_gamma) {
+ return {{{ 1.0f * e_gamma.y + -0.128245f * e_gamma.u + -0.115879f * e_gamma.v,
+ 0.0f * e_gamma.y + 1.010016f * e_gamma.u + 0.061592f * e_gamma.v,
+ 0.0f * e_gamma.y + 0.086969f * e_gamma.u + 1.029350f * e_gamma.v }}};
+}
+
+Color yuv2100To709(Color e_gamma) {
+ return {{{ 1.0f * e_gamma.y + 0.018149f * e_gamma.u + -0.095132f * e_gamma.v,
+ 0.0f * e_gamma.y + 1.004123f * e_gamma.u + 0.051267f * e_gamma.v,
+ 0.0f * e_gamma.y + -0.011524f * e_gamma.u + 0.996782f * e_gamma.v }}};
+}
+
+Color yuv2100To601(Color e_gamma) {
+ return {{{ 1.0f * e_gamma.y + 0.117887f * e_gamma.u + 0.105521f * e_gamma.v,
+ 0.0f * e_gamma.y + 0.995211f * e_gamma.u + -0.059549f * e_gamma.v,
+ 0.0f * e_gamma.y + -0.084085f * e_gamma.u + 0.976518f * e_gamma.v }}};
+}
+
+void transformYuv420(jr_uncompressed_ptr image, size_t x_chroma, size_t y_chroma,
+ ColorTransformFn fn) {
+ Color yuv1 = getYuv420Pixel(image, x_chroma * 2, y_chroma * 2 );
+ Color yuv2 = getYuv420Pixel(image, x_chroma * 2 + 1, y_chroma * 2 );
+ Color yuv3 = getYuv420Pixel(image, x_chroma * 2, y_chroma * 2 + 1);
+ Color yuv4 = getYuv420Pixel(image, x_chroma * 2 + 1, y_chroma * 2 + 1);
+
+ yuv1 = fn(yuv1);
+ yuv2 = fn(yuv2);
+ yuv3 = fn(yuv3);
+ yuv4 = fn(yuv4);
+
+ Color new_uv = (yuv1 + yuv2 + yuv3 + yuv4) / 4.0f;
+
+ size_t pixel_y1_idx = x_chroma * 2 + y_chroma * 2 * image->width;
+ size_t pixel_y2_idx = (x_chroma * 2 + 1) + y_chroma * 2 * image->width;
+ size_t pixel_y3_idx = x_chroma * 2 + (y_chroma * 2 + 1) * image->width;
+ size_t pixel_y4_idx = (x_chroma * 2 + 1) + (y_chroma * 2 + 1) * image->width;
+
+ uint8_t& y1_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y1_idx];
+ uint8_t& y2_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y2_idx];
+ uint8_t& y3_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y3_idx];
+ uint8_t& y4_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y4_idx];
+
+ size_t pixel_count = image->width * image->height;
+ size_t pixel_uv_idx = x_chroma + y_chroma * (image->width / 2);
+
+ uint8_t& u_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_count + pixel_uv_idx];
+ uint8_t& v_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_count * 5 / 4 + pixel_uv_idx];
+
+ y1_uint = static_cast<uint8_t>(floor(yuv1.y * 255.0f + 0.5f));
+ y2_uint = static_cast<uint8_t>(floor(yuv2.y * 255.0f + 0.5f));
+ y3_uint = static_cast<uint8_t>(floor(yuv3.y * 255.0f + 0.5f));
+ y4_uint = static_cast<uint8_t>(floor(yuv4.y * 255.0f + 0.5f));
+
+ u_uint = static_cast<uint8_t>(floor(new_uv.u * 255.0f + 128.0f + 0.5f));
+ v_uint = static_cast<uint8_t>(floor(new_uv.v * 255.0f + 128.0f + 0.5f));
+}
////////////////////////////////////////////////////////////////////////////////
// Gain map calculations
diff --git a/libs/ultrahdr/icc.cpp b/libs/ultrahdr/icc.cpp
index c807705..1ab3c7c 100644
--- a/libs/ultrahdr/icc.cpp
+++ b/libs/ultrahdr/icc.cpp
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+#ifndef USE_BIG_ENDIAN
+#define USE_BIG_ENDIAN true
+#endif
+
#include <ultrahdr/icc.h>
#include <ultrahdr/gainmapmath.h>
#include <vector>
@@ -180,7 +184,7 @@
uint32_t total_length = text_length * 2 + sizeof(header);
total_length = (((total_length + 2) >> 2) << 2); // 4 aligned
- sp<DataStruct> dataStruct = new DataStruct(total_length);
+ sp<DataStruct> dataStruct = sp<DataStruct>::make(total_length);
if (!dataStruct->write(header, sizeof(header))) {
ALOGE("write_text_tag(): error in writing data");
@@ -204,7 +208,7 @@
static_cast<uint32_t>(Endian_SwapBE32(float_round_to_fixed(y))),
static_cast<uint32_t>(Endian_SwapBE32(float_round_to_fixed(z))),
};
- sp<DataStruct> dataStruct = new DataStruct(sizeof(data));
+ sp<DataStruct> dataStruct = sp<DataStruct>::make(sizeof(data));
dataStruct->write(&data, sizeof(data));
return dataStruct;
}
@@ -212,7 +216,7 @@
sp<DataStruct> IccHelper::write_trc_tag(const int table_entries, const void* table_16) {
int total_length = 4 + 4 + 4 + table_entries * 2;
total_length = (((total_length + 2) >> 2) << 2); // 4 aligned
- sp<DataStruct> dataStruct = new DataStruct(total_length);
+ sp<DataStruct> dataStruct = sp<DataStruct>::make(total_length);
dataStruct->write32(Endian_SwapBE32(kTAG_CurveType)); // Type
dataStruct->write32(0); // Reserved
dataStruct->write32(Endian_SwapBE32(table_entries)); // Value count
@@ -225,7 +229,7 @@
sp<DataStruct> IccHelper::write_trc_tag_for_linear() {
int total_length = 16;
- sp<DataStruct> dataStruct = new DataStruct(total_length);
+ sp<DataStruct> dataStruct = sp<DataStruct>::make(total_length);
dataStruct->write32(Endian_SwapBE32(kTAG_ParaCurveType)); // Type
dataStruct->write32(0); // Reserved
dataStruct->write32(Endian_SwapBE16(kExponential_ParaCurveType));
@@ -263,7 +267,7 @@
sp<DataStruct> IccHelper::write_cicp_tag(uint32_t color_primaries,
uint32_t transfer_characteristics) {
int total_length = 12; // 4 + 4 + 1 + 1 + 1 + 1
- sp<DataStruct> dataStruct = new DataStruct(total_length);
+ sp<DataStruct> dataStruct = sp<DataStruct>::make(total_length);
dataStruct->write32(Endian_SwapBE32(kTAG_cicp)); // Type signature
dataStruct->write32(0); // Reserved
dataStruct->write8(color_primaries); // Color primaries
@@ -314,7 +318,7 @@
int total_length = 20 + 2 * value_count;
total_length = (((total_length + 2) >> 2) << 2); // 4 aligned
- sp<DataStruct> dataStruct = new DataStruct(total_length);
+ sp<DataStruct> dataStruct = sp<DataStruct>::make(total_length);
for (size_t i = 0; i < 16; ++i) {
dataStruct->write8(i < kNumChannels ? grid_points[i] : 0); // Grid size
@@ -372,7 +376,7 @@
total_length += a_curves_data[i]->getLength();
}
}
- sp<DataStruct> dataStruct = new DataStruct(total_length);
+ sp<DataStruct> dataStruct = sp<DataStruct>::make(total_length);
dataStruct->write32(Endian_SwapBE32(type)); // Type signature
dataStruct->write32(0); // Reserved
dataStruct->write8(kNumChannels); // Input channels
@@ -421,7 +425,7 @@
break;
default:
// Should not fall here.
- return new DataStruct(0);
+ return nullptr;
}
// Compute primaries.
@@ -540,13 +544,21 @@
size_t tag_table_size = kICCTagTableEntrySize * tags.size();
size_t profile_size = kICCHeaderSize + tag_table_size + tag_data_size;
+ sp<DataStruct> dataStruct = sp<DataStruct>::make(profile_size + kICCIdentifierSize);
+
+ // Write identifier, chunk count, and chunk ID
+ if (!dataStruct->write(kICCIdentifier, sizeof(kICCIdentifier)) ||
+ !dataStruct->write8(1) || !dataStruct->write8(1)) {
+ ALOGE("writeIccProfile(): error in identifier");
+ return dataStruct;
+ }
+
// Write the header.
header.data_color_space = Endian_SwapBE32(Signature_RGB);
header.pcs = Endian_SwapBE32(tf == ULTRAHDR_TF_PQ ? Signature_Lab : Signature_XYZ);
header.size = Endian_SwapBE32(profile_size);
header.tag_count = Endian_SwapBE32(tags.size());
- sp<DataStruct> dataStruct = new DataStruct(profile_size);
if (!dataStruct->write(&header, sizeof(header))) {
ALOGE("writeIccProfile(): error in header");
return dataStruct;
@@ -582,4 +594,84 @@
return dataStruct;
}
-} // namespace android::ultrahdr
\ No newline at end of file
+bool IccHelper::tagsEqualToMatrix(const Matrix3x3& matrix,
+ const uint8_t* red_tag,
+ const uint8_t* green_tag,
+ const uint8_t* blue_tag) {
+ sp<DataStruct> red_tag_test = write_xyz_tag(matrix.vals[0][0], matrix.vals[1][0],
+ matrix.vals[2][0]);
+ sp<DataStruct> green_tag_test = write_xyz_tag(matrix.vals[0][1], matrix.vals[1][1],
+ matrix.vals[2][1]);
+ sp<DataStruct> blue_tag_test = write_xyz_tag(matrix.vals[0][2], matrix.vals[1][2],
+ matrix.vals[2][2]);
+ return memcmp(red_tag, red_tag_test->getData(), kColorantTagSize) == 0 &&
+ memcmp(green_tag, green_tag_test->getData(), kColorantTagSize) == 0 &&
+ memcmp(blue_tag, blue_tag_test->getData(), kColorantTagSize) == 0;
+}
+
+ultrahdr_color_gamut IccHelper::readIccColorGamut(void* icc_data, size_t icc_size) {
+ // Each tag table entry consists of 3 fields of 4 bytes each.
+ static const size_t kTagTableEntrySize = 12;
+
+ if (icc_data == nullptr || icc_size < sizeof(ICCHeader) + kICCIdentifierSize) {
+ return ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+ }
+
+ if (memcmp(icc_data, kICCIdentifier, sizeof(kICCIdentifier)) != 0) {
+ return ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+ }
+
+ uint8_t* icc_bytes = reinterpret_cast<uint8_t*>(icc_data) + kICCIdentifierSize;
+
+ ICCHeader* header = reinterpret_cast<ICCHeader*>(icc_bytes);
+
+ // Use 0 to indicate not found, since offsets are always relative to start
+ // of ICC data and therefore a tag offset of zero would never be valid.
+ size_t red_primary_offset = 0, green_primary_offset = 0, blue_primary_offset = 0;
+ size_t red_primary_size = 0, green_primary_size = 0, blue_primary_size = 0;
+ for (size_t tag_idx = 0; tag_idx < Endian_SwapBE32(header->tag_count); ++tag_idx) {
+ uint32_t* tag_entry_start = reinterpret_cast<uint32_t*>(
+ icc_bytes + sizeof(ICCHeader) + tag_idx * kTagTableEntrySize);
+ // first 4 bytes are the tag signature, next 4 bytes are the tag offset,
+ // last 4 bytes are the tag length in bytes.
+ if (red_primary_offset == 0 && *tag_entry_start == Endian_SwapBE32(kTAG_rXYZ)) {
+ red_primary_offset = Endian_SwapBE32(*(tag_entry_start+1));
+ red_primary_size = Endian_SwapBE32(*(tag_entry_start+2));
+ } else if (green_primary_offset == 0 && *tag_entry_start == Endian_SwapBE32(kTAG_gXYZ)) {
+ green_primary_offset = Endian_SwapBE32(*(tag_entry_start+1));
+ green_primary_size = Endian_SwapBE32(*(tag_entry_start+2));
+ } else if (blue_primary_offset == 0 && *tag_entry_start == Endian_SwapBE32(kTAG_bXYZ)) {
+ blue_primary_offset = Endian_SwapBE32(*(tag_entry_start+1));
+ blue_primary_size = Endian_SwapBE32(*(tag_entry_start+2));
+ }
+ }
+
+ if (red_primary_offset == 0 || red_primary_size != kColorantTagSize ||
+ kICCIdentifierSize + red_primary_offset + red_primary_size > icc_size ||
+ green_primary_offset == 0 || green_primary_size != kColorantTagSize ||
+ kICCIdentifierSize + green_primary_offset + green_primary_size > icc_size ||
+ blue_primary_offset == 0 || blue_primary_size != kColorantTagSize ||
+ kICCIdentifierSize + blue_primary_offset + blue_primary_size > icc_size) {
+ return ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+ }
+
+ uint8_t* red_tag = icc_bytes + red_primary_offset;
+ uint8_t* green_tag = icc_bytes + green_primary_offset;
+ uint8_t* blue_tag = icc_bytes + blue_primary_offset;
+
+ // Serialize tags as we do on encode and compare what we find to that to
+ // determine the gamut (since we don't have a need yet for full deserialize).
+ if (tagsEqualToMatrix(kSRGB, red_tag, green_tag, blue_tag)) {
+ return ULTRAHDR_COLORGAMUT_BT709;
+ } else if (tagsEqualToMatrix(kDisplayP3, red_tag, green_tag, blue_tag)) {
+ return ULTRAHDR_COLORGAMUT_P3;
+ } else if (tagsEqualToMatrix(kRec2020, red_tag, green_tag, blue_tag)) {
+ return ULTRAHDR_COLORGAMUT_BT2100;
+ }
+
+ // Didn't find a match to one of the profiles we write; indicate the gamut
+ // is unspecified since we don't understand it.
+ return ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+}
+
+} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/include/ultrahdr/gainmapmath.h b/libs/ultrahdr/include/ultrahdr/gainmapmath.h
index abc9356..edf152d 100644
--- a/libs/ultrahdr/include/ultrahdr/gainmapmath.h
+++ b/libs/ultrahdr/include/ultrahdr/gainmapmath.h
@@ -218,24 +218,30 @@
// except for those concerning transfer functions.
/*
- * Calculate the luminance of a linear RGB sRGB pixel, according to IEC 61966-2-1.
+ * Calculate the luminance of a linear RGB sRGB pixel, according to
+ * IEC 61966-2-1/Amd 1:2003.
*
* [0.0, 1.0] range in and out.
*/
float srgbLuminance(Color e);
/*
- * Convert from OETF'd srgb YUV to RGB, according to ECMA TR/98.
+ * Convert from OETF'd srgb RGB to YUV, according to ITU-R BT.709-6.
+ *
+ * BT.709 YUV<->RGB matrix is used to match expectations for DataSpace.
+ */
+Color srgbRgbToYuv(Color e_gamma);
+
+
+/*
+ * Convert from OETF'd srgb YUV to RGB, according to ITU-R BT.709-6.
+ *
+ * BT.709 YUV<->RGB matrix is used to match expectations for DataSpace.
*/
Color srgbYuvToRgb(Color e_gamma);
/*
- * Convert from OETF'd srgb RGB to YUV, according to ECMA TR/98.
- */
-Color srgbRgbToYuv(Color e_gamma);
-
-/*
- * Convert from srgb to linear, according to IEC 61966-2-1.
+ * Convert from srgb to linear, according to IEC 61966-2-1/Amd 1:2003.
*
* [0.0, 1.0] range in and out.
*/
@@ -257,6 +263,20 @@
*/
float p3Luminance(Color e);
+/*
+ * Convert from OETF'd P3 RGB to YUV, according to ITU-R BT.601-7.
+ *
+ * BT.601 YUV<->RGB matrix is used to match expectations for DataSpace.
+ */
+Color p3RgbToYuv(Color e_gamma);
+
+/*
+ * Convert from OETF'd P3 YUV to RGB, according to ITU-R BT.601-7.
+ *
+ * BT.601 YUV<->RGB matrix is used to match expectations for DataSpace.
+ */
+Color p3YuvToRgb(Color e_gamma);
+
////////////////////////////////////////////////////////////////////////////////
// BT.2100 transformations - according to ITU-R BT.2100-2
@@ -269,12 +289,16 @@
float bt2100Luminance(Color e);
/*
- * Convert from OETF'd BT.2100 RGB to YUV.
+ * Convert from OETF'd BT.2100 RGB to YUV, according to ITU-R BT.2100-2.
+ *
+ * BT.2100 YUV<->RGB matrix is used to match expectations for DataSpace.
*/
Color bt2100RgbToYuv(Color e_gamma);
/*
- * Convert from OETF'd BT.2100 YUV to RGB.
+ * Convert from OETF'd BT.2100 YUV to RGB, according to ITU-R BT.2100-2.
+ *
+ * BT.2100 YUV<->RGB matrix is used to match expectations for DataSpace.
*/
Color bt2100YuvToRgb(Color e_gamma);
@@ -358,6 +382,31 @@
*/
ColorTransformFn getHdrConversionFn(ultrahdr_color_gamut sdr_gamut, ultrahdr_color_gamut hdr_gamut);
+/*
+ * Convert between YUV encodings, according to ITU-R BT.709-6, ITU-R BT.601-7, and ITU-R BT.2100-2.
+ *
+ * Bt.709 and Bt.2100 have well-defined YUV encodings; Display-P3's is less well defined, but is
+ * treated as Bt.601 by DataSpace, hence we do the same.
+ */
+Color yuv709To601(Color e_gamma);
+Color yuv709To2100(Color e_gamma);
+Color yuv601To709(Color e_gamma);
+Color yuv601To2100(Color e_gamma);
+Color yuv2100To709(Color e_gamma);
+Color yuv2100To601(Color e_gamma);
+
+/*
+ * Performs a transformation at the chroma x and y coordinates provided on a YUV420 image.
+ *
+ * Apply the transformation by determining transformed YUV for each of the 4 Y + 1 UV; each Y gets
+ * this result, and UV gets the averaged result.
+ *
+ * x_chroma and y_chroma should be less than or equal to half the image's width and height
+ * respecitively, since input is 4:2:0 subsampled.
+ */
+void transformYuv420(jr_uncompressed_ptr image, size_t x_chroma, size_t y_chroma,
+ ColorTransformFn fn);
+
////////////////////////////////////////////////////////////////////////////////
// Gain map calculations
@@ -365,6 +414,10 @@
/*
* Calculate the 8-bit unsigned integer gain value for the given SDR and HDR
* luminances in linear space, and the hdr ratio to encode against.
+ *
+ * Note: since this library always uses gamma of 1.0, offsetSdr of 0.0, and
+ * offsetHdr of 0.0, this function doesn't handle different metadata values for
+ * these fields.
*/
uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata);
uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata,
@@ -373,6 +426,10 @@
/*
* Calculates the linear luminance in nits after applying the given gain
* value, with the given hdr ratio, to the given sdr input in the range [0, 1].
+ *
+ * Note: similar to encodeGain(), this function only supports gamma 1.0,
+ * offsetSdr 0.0, offsetHdr 0.0, hdrCapacityMin 1.0, and hdrCapacityMax equal to
+ * gainMapMax, as this library encodes.
*/
Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata);
Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata, float displayBoost);
diff --git a/libs/ultrahdr/include/ultrahdr/icc.h b/libs/ultrahdr/include/ultrahdr/icc.h
index 7f6ab88..7f047f8 100644
--- a/libs/ultrahdr/include/ultrahdr/icc.h
+++ b/libs/ultrahdr/include/ultrahdr/icc.h
@@ -56,12 +56,16 @@
Signature_XYZ = 0x58595A20,
};
-
typedef uint32_t FourByteTag;
static inline constexpr FourByteTag SetFourByteTag(char a, char b, char c, char d) {
return (((uint32_t)a << 24) | ((uint32_t)b << 16) | ((uint32_t)c << 8) | (uint32_t)d);
}
+static constexpr char kICCIdentifier[] = "ICC_PROFILE";
+// 12 for the actual identifier, +2 for the chunk count and chunk index which
+// will always follow.
+static constexpr size_t kICCIdentifierSize = 14;
+
// This is equal to the header size according to the ICC specification (128)
// plus the size of the tag count (4). We include the tag count since we
// always require it to be present anyway.
@@ -70,6 +74,10 @@
// Contains a signature (4), offset (4), and size (4).
static constexpr size_t kICCTagTableEntrySize = 12;
+// size should be 20; 4 bytes for type descriptor, 4 bytes reserved, 12
+// bytes for a single XYZ number type (4 bytes per coordinate).
+static constexpr size_t kColorantTagSize = 20;
+
static constexpr uint32_t kDisplay_Profile = SetFourByteTag('m', 'n', 't', 'r');
static constexpr uint32_t kRGB_ColorSpace = SetFourByteTag('R', 'G', 'B', ' ');
static constexpr uint32_t kXYZ_PCSSpace = SetFourByteTag('X', 'Y', 'Z', ' ');
@@ -225,10 +233,23 @@
static void compute_lut_entry(const Matrix3x3& src_to_XYZD50, float rgb[3]);
static sp<DataStruct> write_clut(const uint8_t* grid_points, const uint8_t* grid_16);
+ // Checks if a set of xyz tags is equivalent to a 3x3 Matrix. Each input
+ // tag buffer assumed to be at least kColorantTagSize in size.
+ static bool tagsEqualToMatrix(const Matrix3x3& matrix,
+ const uint8_t* red_tag,
+ const uint8_t* green_tag,
+ const uint8_t* blue_tag);
+
public:
+ // Output includes JPEG embedding identifier and chunk information, but not
+ // APPx information.
static sp<DataStruct> writeIccProfile(const ultrahdr_transfer_function tf,
const ultrahdr_color_gamut gamut);
+ // NOTE: this function is not robust; it can infer gamuts that IccHelper
+ // writes out but should not be considered a reference implementation for
+ // robust parsing of ICC profiles or their gamuts.
+ static ultrahdr_color_gamut readIccColorGamut(void* icc_data, size_t icc_size);
};
} // namespace android::ultrahdr
-#endif //ANDROID_ULTRAHDR_ICC_H
\ No newline at end of file
+#endif //ANDROID_ULTRAHDR_ICC_H
diff --git a/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h b/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h
index f642bad..8b5499a 100644
--- a/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h
+++ b/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h
@@ -25,6 +25,10 @@
}
#include <utils/Errors.h>
#include <vector>
+
+static const int kMaxWidth = 8192;
+static const int kMaxHeight = 8192;
+
namespace android::ultrahdr {
/*
* Encapsulates a converter from JPEG to raw image (YUV420planer or grey-scale) format.
@@ -79,11 +83,14 @@
*/
size_t getEXIFSize();
/*
- * Returns the position offset of EXIF package
- * (4 bypes offset to FF sign, the byte after FF E1 XX XX <this byte>),
- * or -1 if no EXIF exists.
+ * Returns the ICC data from the image.
*/
- int getEXIFPos() { return mExifPos; }
+ void* getICCPtr();
+ /*
+ * Returns the decompressed ICC buffer size. This method must be called only after
+ * calling decompressImage() or getCompressedImageParameters().
+ */
+ size_t getICCSize();
/*
* Decompresses metadata of the image. All vectors are owned by the caller.
*/
@@ -108,12 +115,12 @@
std::vector<JOCTET> mXMPBuffer;
// The buffer that holds EXIF Data.
std::vector<JOCTET> mEXIFBuffer;
+ // The buffer that holds ICC Data.
+ std::vector<JOCTET> mICCBuffer;
// Resolution of the decompressed image.
size_t mWidth;
size_t mHeight;
- // Position of EXIF package, default value is -1 which means no EXIF package appears.
- size_t mExifPos;
};
} /* namespace android::ultrahdr */
diff --git a/libs/ultrahdr/include/ultrahdr/jpegr.h b/libs/ultrahdr/include/ultrahdr/jpegr.h
index 1f9bd0f..a35fd30 100644
--- a/libs/ultrahdr/include/ultrahdr/jpegr.h
+++ b/libs/ultrahdr/include/ultrahdr/jpegr.h
@@ -125,7 +125,7 @@
*
* Generate gain map from the HDR and SDR inputs, compress SDR YUV to 8-bit JPEG and append
* the gain map to the end of the compressed JPEG. HDR and SDR inputs must be the same
- * resolution.
+ * resolution. SDR input is assumed to use the sRGB transfer function.
* @param uncompressed_p010_image uncompressed HDR image in P010 color format
* @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
* @param hdr_tf transfer function of the HDR image
@@ -152,7 +152,9 @@
* This method requires HAL Hardware JPEG encoder.
*
* Generate gain map from the HDR and SDR inputs, append the gain map to the end of the
- * compressed JPEG. HDR and SDR inputs must be the same resolution and color space.
+ * compressed JPEG. Adds an ICC profile if one isn't present in the input JPEG image. HDR and
+ * SDR inputs must be the same resolution and color space. SDR image is assumed to use the sRGB
+ * transfer function.
* @param uncompressed_p010_image uncompressed HDR image in P010 color format
* @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
* Note: the SDR image must be the decoded version of the JPEG
@@ -178,8 +180,9 @@
* This method requires HAL Hardware JPEG encoder.
*
* Decode the compressed 8-bit JPEG image to YUV SDR, generate gain map from the HDR input
- * and the decoded SDR result, append the gain map to the end of the compressed JPEG. HDR
- * and SDR inputs must be the same resolution.
+ * and the decoded SDR result, append the gain map to the end of the compressed JPEG. Adds an
+ * ICC profile if one isn't present in the input JPEG image. HDR and SDR inputs must be the same
+ * resolution. JPEG image is assumed to use the sRGB transfer function.
* @param uncompressed_p010_image uncompressed HDR image in P010 color format
* @param compressed_jpeg_image compressed 8-bit JPEG image
* @param hdr_tf transfer function of the HDR image
@@ -198,7 +201,8 @@
* Encode API-4
* Assemble JPEGR image from SDR JPEG and gainmap JPEG.
*
- * Assemble the primary JPEG image, the gain map and the metadata to JPEG/R format.
+ * Assemble the primary JPEG image, the gain map and the metadata to JPEG/R format. Adds an ICC
+ * profile if one isn't present in the input JPEG image.
* @param compressed_jpeg_image compressed 8-bit JPEG image
* @param compressed_gainmap compressed 8-bit JPEG single channel image
* @param metadata metadata to be written in XMP of the primary jpeg
@@ -217,6 +221,13 @@
* Decode API
* Decompress JPEGR image.
*
+ * This method assumes that the JPEGR image contains an ICC profile with primaries that match
+ * those of a color gamut that this library is aware of; Bt.709, Display-P3, or Bt.2100. It also
+ * assumes the base image uses the sRGB transfer function.
+ *
+ * This method only supports single gain map metadata values for fields that allow multi-channel
+ * metadata values.
+ *
* @param compressed_jpegr_image compressed JPEGR image.
* @param dest destination of the uncompressed JPEGR image.
* @param max_display_boost (optional) the maximum available boost supported by a display,
@@ -258,6 +269,9 @@
/*
* Gets Info from JPEGR file without decoding it.
*
+ * This method only supports single gain map metadata values for fields that allow multi-channel
+ * metadata values.
+ *
* The output is filled jpegr_info structure
* @param compressed_jpegr_image compressed JPEGR image
* @param jpegr_info pointer to output JPEGR info. Members of jpegr_info
@@ -270,26 +284,30 @@
/*
* This method is called in the encoding pipeline. It will take the uncompressed 8-bit and
* 10-bit yuv images as input, and calculate the uncompressed gain map. The input images
- * must be the same resolution.
+ * must be the same resolution. The SDR input is assumed to use the sRGB transfer function.
*
* @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
* @param uncompressed_p010_image uncompressed HDR image in P010 color format
* @param hdr_tf transfer function of the HDR image
* @param dest gain map; caller responsible for memory of data
* @param metadata max_content_boost is filled in
+ * @param sdr_is_601 if true, then use BT.601 decoding of YUV regardless of SDR image gamut
* @return NO_ERROR if calculation succeeds, error code if error occurs.
*/
status_t generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
jr_uncompressed_ptr uncompressed_p010_image,
ultrahdr_transfer_function hdr_tf,
ultrahdr_metadata_ptr metadata,
- jr_uncompressed_ptr dest);
+ jr_uncompressed_ptr dest,
+ bool sdr_is_601 = false);
/*
* This method is called in the decoding pipeline. It will take the uncompressed (decoded)
* 8-bit yuv image, the uncompressed (decoded) gain map, and extracted JPEG/R metadata as
* input, and calculate the 10-bit recovered image. The recovered output image is the same
* color gamut as the SDR image, with HLG transfer function, and is in RGBA1010102 data format.
+ * The SDR image is assumed to use the sRGB transfer function. The SDR image is also assumed to
+ * be a decoded JPEG for the purpose of YUV interpration.
*
* @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
* @param uncompressed_gain_map uncompressed gain map
@@ -353,6 +371,8 @@
* @param compressed_jpeg_image compressed 8-bit JPEG image
* @param compress_gain_map compressed recover map
* @param (nullable) exif EXIF package
+ * @param (nullable) icc ICC package
+ * @param icc_size length in bytes of ICC package
* @param metadata JPEG/R metadata to encode in XMP of the jpeg
* @param dest compressed JPEGR image
* @return NO_ERROR if calculation succeeds, error code if error occurs.
@@ -360,6 +380,7 @@
status_t appendGainMap(jr_compressed_ptr compressed_jpeg_image,
jr_compressed_ptr compressed_gain_map,
jr_exif_ptr exif,
+ void* icc, size_t icc_size,
ultrahdr_metadata_ptr metadata,
jr_compressed_ptr dest);
@@ -374,6 +395,22 @@
jr_uncompressed_ptr dest);
/*
+ * This method will convert a YUV420 image from one YUV encoding to another in-place (eg.
+ * Bt.709 to Bt.601 YUV encoding).
+ *
+ * src_encoding and dest_encoding indicate the encoding via the YUV conversion defined for that
+ * gamut. P3 indicates Rec.601, since this is how DataSpace encodes Display-P3 YUV data.
+ *
+ * @param image the YUV420 image to convert
+ * @param src_encoding input YUV encoding
+ * @param dest_encoding output YUV encoding
+ * @return NO_ERROR if calculation succeeds, error code if error occurs.
+ */
+ status_t convertYuv(jr_uncompressed_ptr image,
+ ultrahdr_color_gamut src_encoding,
+ ultrahdr_color_gamut dest_encoding);
+
+ /*
* This method will check the validity of the input arguments.
*
* @param uncompressed_p010_image uncompressed HDR image in P010 color format
diff --git a/libs/ultrahdr/include/ultrahdr/jpegrerrorcode.h b/libs/ultrahdr/include/ultrahdr/jpegrerrorcode.h
index 9f59c3e..0641232 100644
--- a/libs/ultrahdr/include/ultrahdr/jpegrerrorcode.h
+++ b/libs/ultrahdr/include/ultrahdr/jpegrerrorcode.h
@@ -42,6 +42,8 @@
ERROR_JPEGR_BUFFER_TOO_SMALL = JPEGR_IO_ERROR_BASE - 4,
ERROR_JPEGR_INVALID_COLORGAMUT = JPEGR_IO_ERROR_BASE - 5,
ERROR_JPEGR_INVALID_TRANS_FUNC = JPEGR_IO_ERROR_BASE - 6,
+ ERROR_JPEGR_INVALID_METADATA = JPEGR_IO_ERROR_BASE - 7,
+ ERROR_JPEGR_UNSUPPORTED_METADATA = JPEGR_IO_ERROR_BASE - 8,
JPEGR_RUNTIME_ERROR_BASE = -20000,
ERROR_JPEGR_ENCODE_ERROR = JPEGR_RUNTIME_ERROR_BASE - 1,
diff --git a/libs/ultrahdr/include/ultrahdr/ultrahdr.h b/libs/ultrahdr/include/ultrahdr/ultrahdr.h
index 21751b4..17cc971 100644
--- a/libs/ultrahdr/include/ultrahdr/ultrahdr.h
+++ b/libs/ultrahdr/include/ultrahdr/ultrahdr.h
@@ -49,14 +49,28 @@
/*
* Holds information for gain map related metadata.
+ *
+ * Not: all values stored in linear. This differs from the metadata encoding in XMP, where
+ * maxContentBoost (aka gainMapMax), minContentBoost (aka gainMapMin), hdrCapacityMin, and
+ * hdrCapacityMax are stored in log2 space.
*/
struct ultrahdr_metadata_struct {
- // Ultra HDR library version
+ // Ultra HDR format version
std::string version;
// Max Content Boost for the map
float maxContentBoost;
// Min Content Boost for the map
float minContentBoost;
+ // Gamma of the map data
+ float gamma;
+ // Offset for SDR data in map calculations
+ float offsetSdr;
+ // Offset for HDR data in map calculations
+ float offsetHdr;
+ // HDR capacity to apply the map at all
+ float hdrCapacityMin;
+ // HDR capacity to apply the map completely
+ float hdrCapacityMax;
};
typedef struct ultrahdr_metadata_struct* ultrahdr_metadata_ptr;
diff --git a/libs/ultrahdr/jpegdecoderhelper.cpp b/libs/ultrahdr/jpegdecoderhelper.cpp
index fac90c5..fef5444 100644
--- a/libs/ultrahdr/jpegdecoderhelper.cpp
+++ b/libs/ultrahdr/jpegdecoderhelper.cpp
@@ -93,7 +93,6 @@
}
JpegDecoderHelper::JpegDecoderHelper() {
- mExifPos = 0;
}
JpegDecoderHelper::~JpegDecoderHelper() {
@@ -138,6 +137,14 @@
return mEXIFBuffer.size();
}
+void* JpegDecoderHelper::getICCPtr() {
+ return mICCBuffer.data();
+}
+
+size_t JpegDecoderHelper::getICCSize() {
+ return mICCBuffer.size();
+}
+
size_t JpegDecoderHelper::getDecompressedImageWidth() {
return mWidth;
}
@@ -150,6 +157,7 @@
jpeg_decompress_struct cinfo;
jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
jpegrerror_mgr myerr;
+ bool status = true;
cinfo.err = jpeg_std_error(&myerr.pub);
myerr.pub.error_exit = jpegrerror_exit;
@@ -167,31 +175,21 @@
cinfo.src = &mgr;
jpeg_read_header(&cinfo, TRUE);
- // Save XMP data and EXIF data.
- // Here we only handle the first XMP / EXIF package.
- // The parameter pos is used for capturing start offset of EXIF, which is hacky, but working...
+ // Save XMP data, EXIF data, and ICC data.
+ // Here we only handle the first XMP / EXIF / ICC package.
// We assume that all packages are starting with two bytes marker (eg FF E1 for EXIF package),
// two bytes of package length which is stored in marker->original_length, and the real data
- // which is stored in marker->data. The pos is adding up all previous package lengths (
- // 4 bytes marker and length, marker->original_length) before EXIF appears. Note that here we
- // we are using marker->original_length instead of marker->data_length because in case the real
- // package length is larger than the limitation, jpeg-turbo will only copy the data within the
- // limitation (represented by data_length) and this may vary from original_length / real offset.
- // A better solution is making jpeg_marker_struct holding the offset, but currently it doesn't.
+ // which is stored in marker->data.
bool exifAppears = false;
bool xmpAppears = false;
- size_t pos = 2; // position after SOI
+ bool iccAppears = false;
for (jpeg_marker_struct* marker = cinfo.marker_list;
- marker && !(exifAppears && xmpAppears);
+ marker && !(exifAppears && xmpAppears && iccAppears);
marker = marker->next) {
- pos += 4;
- pos += marker->original_length;
-
- if (marker->marker != kAPP1Marker) {
+ if (marker->marker != kAPP1Marker && marker->marker != kAPP2Marker) {
continue;
}
-
const unsigned int len = marker->data_length;
if (!xmpAppears &&
len > kXmpNameSpace.size() &&
@@ -209,17 +207,31 @@
mEXIFBuffer.resize(len, 0);
memcpy(static_cast<void*>(mEXIFBuffer.data()), marker->data, len);
exifAppears = true;
- mExifPos = pos - marker->original_length;
+ } else if (!iccAppears &&
+ len > sizeof(kICCSig) &&
+ !memcmp(marker->data, kICCSig, sizeof(kICCSig))) {
+ mICCBuffer.resize(len, 0);
+ memcpy(static_cast<void*>(mICCBuffer.data()), marker->data, len);
+ iccAppears = true;
}
}
+ if (cinfo.image_width > kMaxWidth || cinfo.image_height > kMaxHeight) {
+ // constraint on max width and max height is only due to alloc constraints
+ // tune these values basing on the target device
+ status = false;
+ goto CleanUp;
+ }
+
mWidth = cinfo.image_width;
mHeight = cinfo.image_height;
if (decodeToRGBA) {
if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
// We don't intend to support decoding grayscale to RGBA
- return false;
+ status = false;
+ ALOGE("%s: decoding grayscale to RGBA is unsupported", __func__);
+ goto CleanUp;
}
// 4 bytes per pixel
mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 4);
@@ -232,7 +244,9 @@
cinfo.comp_info[0].v_samp_factor != 2 ||
cinfo.comp_info[1].v_samp_factor != 1 ||
cinfo.comp_info[2].v_samp_factor != 1) {
- return false;
+ status = false;
+ ALOGE("%s: decoding to YUV only supports 4:2:0 subsampling", __func__);
+ goto CleanUp;
}
mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 3 / 2, 0);
} else if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
@@ -248,13 +262,15 @@
if (!decompress(&cinfo, static_cast<const uint8_t*>(mResultBuffer.data()),
cinfo.jpeg_color_space == JCS_GRAYSCALE)) {
- return false;
+ status = false;
+ goto CleanUp;
}
+CleanUp:
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
- return true;
+ return status;
}
bool JpegDecoderHelper::decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest,
@@ -292,8 +308,12 @@
return false;
}
- *pWidth = cinfo.image_width;
- *pHeight = cinfo.image_height;
+ if (pWidth != nullptr) {
+ *pWidth = cinfo.image_width;
+ }
+ if (pHeight != nullptr) {
+ *pHeight = cinfo.image_height;
+ }
if (iccData != nullptr) {
for (jpeg_marker_struct* marker = cinfo.marker_list; marker;
@@ -306,9 +326,7 @@
continue;
}
- const unsigned int len = marker->data_length - kICCMarkerHeaderSize;
- const uint8_t *src = marker->data + kICCMarkerHeaderSize;
- iccData->insert(iccData->end(), src, src+len);
+ iccData->insert(iccData->end(), marker->data, marker->data + marker->data_length);
}
}
@@ -361,7 +379,7 @@
uint8_t* y_plane = const_cast<uint8_t*>(dest);
uint8_t* u_plane = const_cast<uint8_t*>(dest + y_plane_size);
uint8_t* v_plane = const_cast<uint8_t*>(dest + y_plane_size + uv_plane_size);
- std::unique_ptr<uint8_t[]> empty(new uint8_t[cinfo->image_width]);
+ std::unique_ptr<uint8_t[]> empty = std::make_unique<uint8_t[]>(cinfo->image_width);
memset(empty.get(), 0, cinfo->image_width);
const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize);
@@ -435,7 +453,7 @@
JSAMPARRAY planes[1] {y};
uint8_t* y_plane = const_cast<uint8_t*>(dest);
- std::unique_ptr<uint8_t[]> empty(new uint8_t[cinfo->image_width]);
+ std::unique_ptr<uint8_t[]> empty = std::make_unique<uint8_t[]>(cinfo->image_width);
memset(empty.get(), 0, cinfo->image_width);
int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize);
diff --git a/libs/ultrahdr/jpegencoderhelper.cpp b/libs/ultrahdr/jpegencoderhelper.cpp
index ab2f8c7..a03547b 100644
--- a/libs/ultrahdr/jpegencoderhelper.cpp
+++ b/libs/ultrahdr/jpegencoderhelper.cpp
@@ -107,12 +107,11 @@
jpeg_write_marker(&cinfo, JPEG_APP0 + 2, static_cast<const JOCTET*>(iccBuffer), iccSize);
}
- if (!compress(&cinfo, static_cast<const uint8_t*>(image), isSingleChannel)) {
- return false;
- }
+ bool status = compress(&cinfo, static_cast<const uint8_t*>(image), isSingleChannel);
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
- return true;
+
+ return status;
}
void JpegEncoderHelper::setJpegDestination(jpeg_compress_struct* cinfo) {
@@ -174,7 +173,7 @@
uint8_t* y_plane = const_cast<uint8_t*>(yuv);
uint8_t* u_plane = const_cast<uint8_t*>(yuv + y_plane_size);
uint8_t* v_plane = const_cast<uint8_t*>(yuv + y_plane_size + uv_plane_size);
- std::unique_ptr<uint8_t[]> empty(new uint8_t[cinfo->image_width]);
+ std::unique_ptr<uint8_t[]> empty = std::make_unique<uint8_t[]>(cinfo->image_width);
memset(empty.get(), 0, cinfo->image_width);
const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize);
@@ -250,7 +249,7 @@
JSAMPARRAY planes[1] {y};
uint8_t* y_plane = const_cast<uint8_t*>(image);
- std::unique_ptr<uint8_t[]> empty(new uint8_t[cinfo->image_width]);
+ std::unique_ptr<uint8_t[]> empty = std::make_unique<uint8_t[]>(cinfo->image_width);
memset(empty.get(), 0, cinfo->image_width);
const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize);
diff --git a/libs/ultrahdr/jpegr.cpp b/libs/ultrahdr/jpegr.cpp
index b2bde6c..9c57f34 100644
--- a/libs/ultrahdr/jpegr.cpp
+++ b/libs/ultrahdr/jpegr.cpp
@@ -119,6 +119,13 @@
return ERROR_JPEGR_INVALID_INPUT_TYPE;
}
+ if (uncompressed_p010_image->width > kMaxWidth
+ || uncompressed_p010_image->height > kMaxHeight) {
+ ALOGE("Image dimensions cannot be larger than %dx%d, image dimensions %dx%d",
+ kMaxWidth, kMaxHeight, uncompressed_p010_image->width, uncompressed_p010_image->height);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
if (uncompressed_p010_image->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED
|| uncompressed_p010_image->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
ALOGE("Unrecognized p010 color gamut %d", uncompressed_p010_image->colorGamut);
@@ -251,6 +258,10 @@
sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
uncompressed_yuv_420_image.colorGamut);
+ // Convert to Bt601 YUV encoding for JPEG encode
+ JPEGR_CHECK(convertYuv(&uncompressed_yuv_420_image, uncompressed_yuv_420_image.colorGamut,
+ ULTRAHDR_COLORGAMUT_P3));
+
JpegEncoderHelper jpeg_encoder;
if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image.data,
uncompressed_yuv_420_image.width,
@@ -262,7 +273,9 @@
jpeg.data = jpeg_encoder.getCompressedImagePtr();
jpeg.length = jpeg_encoder.getCompressedImageSize();
- JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, &metadata, dest));
+ // No ICC since JPEG encode already did it
+ JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0,
+ &metadata, dest));
return NO_ERROR;
}
@@ -310,10 +323,22 @@
sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
uncompressed_yuv_420_image->colorGamut);
+ // Convert to Bt601 YUV encoding for JPEG encode; make a copy so as to no clobber client data
+ unique_ptr<uint8_t[]> yuv_420_bt601_data = make_unique<uint8_t[]>(
+ uncompressed_yuv_420_image->width * uncompressed_yuv_420_image->height * 3 / 2);
+ memcpy(yuv_420_bt601_data.get(), uncompressed_yuv_420_image->data,
+ uncompressed_yuv_420_image->width * uncompressed_yuv_420_image->height * 3 / 2);
+
+ jpegr_uncompressed_struct yuv_420_bt601_image = {
+ yuv_420_bt601_data.get(), uncompressed_yuv_420_image->width, uncompressed_yuv_420_image->height,
+ uncompressed_yuv_420_image->colorGamut };
+ JPEGR_CHECK(convertYuv(&yuv_420_bt601_image, yuv_420_bt601_image.colorGamut,
+ ULTRAHDR_COLORGAMUT_P3));
+
JpegEncoderHelper jpeg_encoder;
- if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image->data,
- uncompressed_yuv_420_image->width,
- uncompressed_yuv_420_image->height, quality,
+ if (!jpeg_encoder.compressImage(yuv_420_bt601_image.data,
+ yuv_420_bt601_image.width,
+ yuv_420_bt601_image.height, quality,
icc->getData(), icc->getLength())) {
return ERROR_JPEGR_ENCODE_ERROR;
}
@@ -321,7 +346,9 @@
jpeg.data = jpeg_encoder.getCompressedImagePtr();
jpeg.length = jpeg_encoder.getCompressedImageSize();
- JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, &metadata, dest));
+ // No ICC since jpeg encode already did it
+ JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0,
+ &metadata, dest));
return NO_ERROR;
}
@@ -364,7 +391,24 @@
compressed_map.data = jpeg_encoder_gainmap.getCompressedImagePtr();
compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
- JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, nullptr, &metadata, dest));
+ // We just want to check if ICC is present, so don't do a full decode. Note,
+ // this doesn't verify that the ICC is valid.
+ JpegDecoderHelper decoder;
+ std::vector<uint8_t> icc;
+ decoder.getCompressedImageParameters(compressed_jpeg_image->data, compressed_jpeg_image->length,
+ /* pWidth */ nullptr, /* pHeight */ nullptr,
+ &icc, /* exifData */ nullptr);
+
+ // Add ICC if not already present.
+ if (icc.size() > 0) {
+ JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, /* exif */ nullptr,
+ /* icc */ nullptr, /* icc size */ 0, &metadata, dest));
+ } else {
+ sp<DataStruct> newIcc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
+ uncompressed_yuv_420_image->colorGamut);
+ JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, /* exif */ nullptr,
+ newIcc->getData(), newIcc->getLength(), &metadata, dest));
+ }
return NO_ERROR;
}
@@ -385,6 +429,7 @@
return ret;
}
+ // Note: output is Bt.601 YUV encoded regardless of gamut, due to jpeg decode.
JpegDecoderHelper jpeg_decoder;
if (!jpeg_decoder.decompressImage(compressed_jpeg_image->data, compressed_jpeg_image->length)) {
return ERROR_JPEGR_DECODE_ERROR;
@@ -404,8 +449,10 @@
metadata.version = kJpegrVersion;
jpegr_uncompressed_struct map;
+ // Indicate that the SDR image is Bt.601 YUV encoded.
JPEGR_CHECK(generateGainMap(
- &uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
+ &uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map,
+ true /* sdr_is_601 */ ));
std::unique_ptr<uint8_t[]> map_data;
map_data.reset(reinterpret_cast<uint8_t*>(map.data));
@@ -417,7 +464,24 @@
compressed_map.data = jpeg_encoder_gainmap.getCompressedImagePtr();
compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
- JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, nullptr, &metadata, dest));
+ // We just want to check if ICC is present, so don't do a full decode. Note,
+ // this doesn't verify that the ICC is valid.
+ JpegDecoderHelper decoder;
+ std::vector<uint8_t> icc;
+ decoder.getCompressedImageParameters(compressed_jpeg_image->data, compressed_jpeg_image->length,
+ /* pWidth */ nullptr, /* pHeight */ nullptr,
+ &icc, /* exifData */ nullptr);
+
+ // Add ICC if not already present.
+ if (icc.size() > 0) {
+ JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, /* exif */ nullptr,
+ /* icc */ nullptr, /* icc size */ 0, &metadata, dest));
+ } else {
+ sp<DataStruct> newIcc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
+ uncompressed_yuv_420_image.colorGamut);
+ JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, /* exif */ nullptr,
+ newIcc->getData(), newIcc->getLength(), &metadata, dest));
+ }
return NO_ERROR;
}
@@ -442,8 +506,25 @@
return ERROR_JPEGR_INVALID_NULL_PTR;
}
- JPEGR_CHECK(appendGainMap(compressed_jpeg_image, compressed_gainmap, /* exif */ nullptr,
- metadata, dest));
+ // We just want to check if ICC is present, so don't do a full decode. Note,
+ // this doesn't verify that the ICC is valid.
+ JpegDecoderHelper decoder;
+ std::vector<uint8_t> icc;
+ decoder.getCompressedImageParameters(compressed_jpeg_image->data, compressed_jpeg_image->length,
+ /* pWidth */ nullptr, /* pHeight */ nullptr,
+ &icc, /* exifData */ nullptr);
+
+ // Add ICC if not already present.
+ if (icc.size() > 0) {
+ JPEGR_CHECK(appendGainMap(compressed_jpeg_image, compressed_gainmap, /* exif */ nullptr,
+ /* icc */ nullptr, /* icc size */ 0, metadata, dest));
+ } else {
+ sp<DataStruct> newIcc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
+ compressed_jpeg_image->colorGamut);
+ JPEGR_CHECK(appendGainMap(compressed_jpeg_image, compressed_gainmap, /* exif */ nullptr,
+ newIcc->getData(), newIcc->getLength(), metadata, dest));
+ }
+
return NO_ERROR;
}
@@ -563,13 +644,18 @@
ultrahdr_metadata_struct uhdr_metadata;
if (!getMetadataFromXMP(static_cast<uint8_t*>(gain_map_decoder.getXMPPtr()),
gain_map_decoder.getXMPSize(), &uhdr_metadata)) {
- return ERROR_JPEGR_DECODE_ERROR;
+ return ERROR_JPEGR_INVALID_METADATA;
}
if (metadata != nullptr) {
metadata->version = uhdr_metadata.version;
metadata->minContentBoost = uhdr_metadata.minContentBoost;
metadata->maxContentBoost = uhdr_metadata.maxContentBoost;
+ metadata->gamma = uhdr_metadata.gamma;
+ metadata->offsetSdr = uhdr_metadata.offsetSdr;
+ metadata->offsetHdr = uhdr_metadata.offsetHdr;
+ metadata->hdrCapacityMin = uhdr_metadata.hdrCapacityMin;
+ metadata->hdrCapacityMax = uhdr_metadata.hdrCapacityMax;
}
if (output_format == ULTRAHDR_OUTPUT_SDR) {
@@ -606,6 +692,9 @@
uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
+ uncompressed_yuv_420_image.colorGamut = IccHelper::readIccColorGamut(
+ jpeg_decoder.getICCPtr(), jpeg_decoder.getICCSize());
+
JPEGR_CHECK(applyGainMap(&uncompressed_yuv_420_image, &map, &uhdr_metadata, output_format,
max_display_boost, dest));
return NO_ERROR;
@@ -617,6 +706,7 @@
return ERROR_JPEGR_INVALID_NULL_PTR;
}
+ // Don't need to convert YUV to Bt601 since single channel
if (!jpeg_encoder->compressImage(uncompressed_gain_map->data,
uncompressed_gain_map->width,
uncompressed_gain_map->height,
@@ -692,7 +782,8 @@
jr_uncompressed_ptr uncompressed_p010_image,
ultrahdr_transfer_function hdr_tf,
ultrahdr_metadata_ptr metadata,
- jr_uncompressed_ptr dest) {
+ jr_uncompressed_ptr dest,
+ bool sdr_is_601) {
if (uncompressed_yuv_420_image == nullptr
|| uncompressed_p010_image == nullptr
|| metadata == nullptr
@@ -726,7 +817,7 @@
map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
ColorTransformFn hdrInvOetf = nullptr;
- float hdr_white_nits = 0.0f;
+ float hdr_white_nits = kSdrWhiteNits;
switch (hdr_tf) {
case ULTRAHDR_TF_LINEAR:
hdrInvOetf = identityConversion;
@@ -754,6 +845,12 @@
metadata->maxContentBoost = hdr_white_nits / kSdrWhiteNits;
metadata->minContentBoost = 1.0f;
+ metadata->gamma = 1.0f;
+ metadata->offsetSdr = 0.0f;
+ metadata->offsetHdr = 0.0f;
+ metadata->hdrCapacityMin = 1.0f;
+ metadata->hdrCapacityMax = metadata->maxContentBoost;
+
float log2MinBoost = log2(metadata->minContentBoost);
float log2MaxBoost = log2(metadata->maxContentBoost);
@@ -761,15 +858,38 @@
uncompressed_yuv_420_image->colorGamut, uncompressed_p010_image->colorGamut);
ColorCalculationFn luminanceFn = nullptr;
+ ColorTransformFn sdrYuvToRgbFn = nullptr;
switch (uncompressed_yuv_420_image->colorGamut) {
case ULTRAHDR_COLORGAMUT_BT709:
luminanceFn = srgbLuminance;
+ sdrYuvToRgbFn = srgbYuvToRgb;
break;
case ULTRAHDR_COLORGAMUT_P3:
luminanceFn = p3Luminance;
+ sdrYuvToRgbFn = p3YuvToRgb;
break;
case ULTRAHDR_COLORGAMUT_BT2100:
luminanceFn = bt2100Luminance;
+ sdrYuvToRgbFn = bt2100YuvToRgb;
+ break;
+ case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
+ // Should be impossible to hit after input validation.
+ return ERROR_JPEGR_INVALID_COLORGAMUT;
+ }
+ if (sdr_is_601) {
+ sdrYuvToRgbFn = p3YuvToRgb;
+ }
+
+ ColorTransformFn hdrYuvToRgbFn = nullptr;
+ switch (uncompressed_p010_image->colorGamut) {
+ case ULTRAHDR_COLORGAMUT_BT709:
+ hdrYuvToRgbFn = srgbYuvToRgb;
+ break;
+ case ULTRAHDR_COLORGAMUT_P3:
+ hdrYuvToRgbFn = p3YuvToRgb;
+ break;
+ case ULTRAHDR_COLORGAMUT_BT2100:
+ hdrYuvToRgbFn = bt2100YuvToRgb;
break;
case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
// Should be impossible to hit after input validation.
@@ -783,8 +903,8 @@
std::function<void()> generateMap = [uncompressed_yuv_420_image, uncompressed_p010_image,
metadata, dest, hdrInvOetf, hdrGamutConversionFn,
- luminanceFn, hdr_white_nits, log2MinBoost, log2MaxBoost,
- &jobQueue]() -> void {
+ luminanceFn, sdrYuvToRgbFn, hdrYuvToRgbFn, hdr_white_nits,
+ log2MinBoost, log2MaxBoost, &jobQueue]() -> void {
size_t rowStart, rowEnd;
size_t dest_map_width = uncompressed_yuv_420_image->width / kMapDimensionScaleFactor;
size_t dest_map_stride = dest->width;
@@ -793,7 +913,8 @@
for (size_t x = 0; x < dest_map_width; ++x) {
Color sdr_yuv_gamma =
sampleYuv420(uncompressed_yuv_420_image, kMapDimensionScaleFactor, x, y);
- Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma);
+ Color sdr_rgb_gamma = sdrYuvToRgbFn(sdr_yuv_gamma);
+ // We are assuming the SDR input is always sRGB transfer.
#if USE_SRGB_INVOETF_LUT
Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma);
#else
@@ -802,7 +923,7 @@
float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
- Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
+ Color hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma);
Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
hdr_rgb = hdrGamutConversionFn(hdr_rgb);
float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
@@ -848,6 +969,26 @@
return ERROR_JPEGR_INVALID_NULL_PTR;
}
+ if (metadata->version.compare("1.0")) {
+ ALOGE("Unsupported metadata version: %s", metadata->version.c_str());
+ return ERROR_JPEGR_UNSUPPORTED_METADATA;
+ }
+ if (metadata->gamma != 1.0f) {
+ ALOGE("Unsupported metadata gamma: %f", metadata->gamma);
+ return ERROR_JPEGR_UNSUPPORTED_METADATA;
+ }
+ if (metadata->offsetSdr != 0.0f || metadata->offsetHdr != 0.0f) {
+ ALOGE("Unsupported metadata offset sdr, hdr: %f, %f", metadata->offsetSdr,
+ metadata->offsetHdr);
+ return ERROR_JPEGR_UNSUPPORTED_METADATA;
+ }
+ if (metadata->hdrCapacityMin != metadata->minContentBoost
+ || metadata->hdrCapacityMax != metadata->maxContentBoost) {
+ ALOGE("Unsupported metadata hdr capacity min, max: %f, %f", metadata->hdrCapacityMin,
+ metadata->hdrCapacityMax);
+ return ERROR_JPEGR_UNSUPPORTED_METADATA;
+ }
+
// TODO: remove once map scaling factor is computed based on actual map dims
size_t image_width = uncompressed_yuv_420_image->width;
size_t image_height = uncompressed_yuv_420_image->height;
@@ -880,7 +1021,9 @@
for (size_t y = rowStart; y < rowEnd; ++y) {
for (size_t x = 0; x < width; ++x) {
Color yuv_gamma_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
- Color rgb_gamma_sdr = srgbYuvToRgb(yuv_gamma_sdr);
+ // Assuming the sdr image is a decoded JPEG, we should always use Rec.601 YUV coefficients
+ Color rgb_gamma_sdr = p3YuvToRgb(yuv_gamma_sdr);
+ // We are assuming the SDR base image is always sRGB transfer.
#if USE_SRGB_INVOETF_LUT
Color rgb_sdr = srgbInvOetfLUT(rgb_gamma_sdr);
#else
@@ -1058,6 +1201,7 @@
status_t JpegR::appendGainMap(jr_compressed_ptr compressed_jpeg_image,
jr_compressed_ptr compressed_gain_map,
jr_exif_ptr exif,
+ void* icc, size_t icc_size,
ultrahdr_metadata_ptr metadata,
jr_compressed_ptr dest) {
if (compressed_jpeg_image == nullptr
@@ -1067,6 +1211,33 @@
return ERROR_JPEGR_INVALID_NULL_PTR;
}
+ if (metadata->version.compare("1.0")) {
+ ALOGE("received bad value for version: %s", metadata->version.c_str());
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+ if (metadata->maxContentBoost < metadata->minContentBoost) {
+ ALOGE("received bad value for content boost min %f, max %f", metadata->minContentBoost,
+ metadata->maxContentBoost);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
+ if (metadata->hdrCapacityMax < metadata->hdrCapacityMin || metadata->hdrCapacityMin < 1.0f) {
+ ALOGE("received bad value for hdr capacity min %f, max %f", metadata->hdrCapacityMin,
+ metadata->hdrCapacityMax);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
+ if (metadata->offsetSdr < 0.0f || metadata->offsetHdr < 0.0f) {
+ ALOGE("received bad value for offset sdr %f, hdr %f", metadata->offsetSdr,
+ metadata->offsetHdr);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
+ if (metadata->gamma <= 0.0f) {
+ ALOGE("received bad value for gamma %f", metadata->gamma);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
const string nameSpace = "http://ns.adobe.com/xap/1.0/";
const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
@@ -1115,6 +1286,18 @@
JPEGR_CHECK(Write(dest, (void*)xmp_primary.c_str(), xmp_primary.size(), pos));
}
+ // Write ICC
+ if (icc != nullptr && icc_size > 0) {
+ const int length = icc_size + 2;
+ const uint8_t lengthH = ((length >> 8) & 0xff);
+ const uint8_t lengthL = (length & 0xff);
+ JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
+ JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
+ JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
+ JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
+ JPEGR_CHECK(Write(dest, icc, icc_size, pos));
+ }
+
// Prepare and write MPF
{
const int length = 2 + calculateMpfSize();
@@ -1222,4 +1405,82 @@
return NO_ERROR;
}
+status_t JpegR::convertYuv(jr_uncompressed_ptr image,
+ ultrahdr_color_gamut src_encoding,
+ ultrahdr_color_gamut dest_encoding) {
+ if (image == nullptr) {
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+
+ if (src_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED
+ || dest_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
+ return ERROR_JPEGR_INVALID_COLORGAMUT;
+ }
+
+ ColorTransformFn conversionFn = nullptr;
+ switch (src_encoding) {
+ case ULTRAHDR_COLORGAMUT_BT709:
+ switch (dest_encoding) {
+ case ULTRAHDR_COLORGAMUT_BT709:
+ return NO_ERROR;
+ case ULTRAHDR_COLORGAMUT_P3:
+ conversionFn = yuv709To601;
+ break;
+ case ULTRAHDR_COLORGAMUT_BT2100:
+ conversionFn = yuv709To2100;
+ break;
+ default:
+ // Should be impossible to hit after input validation
+ return ERROR_JPEGR_INVALID_COLORGAMUT;
+ }
+ break;
+ case ULTRAHDR_COLORGAMUT_P3:
+ switch (dest_encoding) {
+ case ULTRAHDR_COLORGAMUT_BT709:
+ conversionFn = yuv601To709;
+ break;
+ case ULTRAHDR_COLORGAMUT_P3:
+ return NO_ERROR;
+ case ULTRAHDR_COLORGAMUT_BT2100:
+ conversionFn = yuv601To2100;
+ break;
+ default:
+ // Should be impossible to hit after input validation
+ return ERROR_JPEGR_INVALID_COLORGAMUT;
+ }
+ break;
+ case ULTRAHDR_COLORGAMUT_BT2100:
+ switch (dest_encoding) {
+ case ULTRAHDR_COLORGAMUT_BT709:
+ conversionFn = yuv2100To709;
+ break;
+ case ULTRAHDR_COLORGAMUT_P3:
+ conversionFn = yuv2100To601;
+ break;
+ case ULTRAHDR_COLORGAMUT_BT2100:
+ return NO_ERROR;
+ default:
+ // Should be impossible to hit after input validation
+ return ERROR_JPEGR_INVALID_COLORGAMUT;
+ }
+ break;
+ default:
+ // Should be impossible to hit after input validation
+ return ERROR_JPEGR_INVALID_COLORGAMUT;
+ }
+
+ if (conversionFn == nullptr) {
+ // Should be impossible to hit after input validation
+ return ERROR_JPEGR_INVALID_COLORGAMUT;
+ }
+
+ for (size_t y = 0; y < image->height / 2; ++y) {
+ for (size_t x = 0; x < image->width / 2; ++x) {
+ transformYuv420(image, x, y, conversionFn);
+ }
+ }
+
+ return NO_ERROR;
+}
+
} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/jpegrutils.cpp b/libs/ultrahdr/jpegrutils.cpp
index 6430af1..c434eb6 100644
--- a/libs/ultrahdr/jpegrutils.cpp
+++ b/libs/ultrahdr/jpegrutils.cpp
@@ -113,6 +113,15 @@
XMPXmlHandler() : XmlHandler() {
state = NotStrarted;
+ versionFound = false;
+ minContentBoostFound = false;
+ maxContentBoostFound = false;
+ gammaFound = false;
+ offsetSdrFound = false;
+ offsetHdrFound = false;
+ hdrCapacityMinFound = false;
+ hdrCapacityMaxFound = false;
+ baseRenditionIsHdrFound = false;
}
enum ParseState {
@@ -147,10 +156,24 @@
string val;
if (state == Started) {
if (context.BuildTokenValue(&val)) {
- if (!val.compare(maxContentBoostAttrName)) {
+ if (!val.compare(versionAttrName)) {
+ lastAttributeName = versionAttrName;
+ } else if (!val.compare(maxContentBoostAttrName)) {
lastAttributeName = maxContentBoostAttrName;
} else if (!val.compare(minContentBoostAttrName)) {
lastAttributeName = minContentBoostAttrName;
+ } else if (!val.compare(gammaAttrName)) {
+ lastAttributeName = gammaAttrName;
+ } else if (!val.compare(offsetSdrAttrName)) {
+ lastAttributeName = offsetSdrAttrName;
+ } else if (!val.compare(offsetHdrAttrName)) {
+ lastAttributeName = offsetHdrAttrName;
+ } else if (!val.compare(hdrCapacityMinAttrName)) {
+ lastAttributeName = hdrCapacityMinAttrName;
+ } else if (!val.compare(hdrCapacityMaxAttrName)) {
+ lastAttributeName = hdrCapacityMaxAttrName;
+ } else if (!val.compare(baseRenditionIsHdrAttrName)) {
+ lastAttributeName = baseRenditionIsHdrAttrName;
} else {
lastAttributeName = "";
}
@@ -163,18 +186,52 @@
string val;
if (state == Started) {
if (context.BuildTokenValue(&val, true)) {
- if (!lastAttributeName.compare(maxContentBoostAttrName)) {
+ if (!lastAttributeName.compare(versionAttrName)) {
+ versionStr = val;
+ versionFound = true;
+ } else if (!lastAttributeName.compare(maxContentBoostAttrName)) {
maxContentBoostStr = val;
+ maxContentBoostFound = true;
} else if (!lastAttributeName.compare(minContentBoostAttrName)) {
minContentBoostStr = val;
+ minContentBoostFound = true;
+ } else if (!lastAttributeName.compare(gammaAttrName)) {
+ gammaStr = val;
+ gammaFound = true;
+ } else if (!lastAttributeName.compare(offsetSdrAttrName)) {
+ offsetSdrStr = val;
+ offsetSdrFound = true;
+ } else if (!lastAttributeName.compare(offsetHdrAttrName)) {
+ offsetHdrStr = val;
+ offsetHdrFound = true;
+ } else if (!lastAttributeName.compare(hdrCapacityMinAttrName)) {
+ hdrCapacityMinStr = val;
+ hdrCapacityMinFound = true;
+ } else if (!lastAttributeName.compare(hdrCapacityMaxAttrName)) {
+ hdrCapacityMaxStr = val;
+ hdrCapacityMaxFound = true;
+ } else if (!lastAttributeName.compare(baseRenditionIsHdrAttrName)) {
+ baseRenditionIsHdrStr = val;
+ baseRenditionIsHdrFound = true;
}
}
}
return context.GetResult();
}
- bool getMaxContentBoost(float* max_content_boost) {
+ bool getVersion(string* version, bool* present) {
if (state == Done) {
+ *version = versionStr;
+ *present = versionFound;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ bool getMaxContentBoost(float* max_content_boost, bool* present) {
+ if (state == Done) {
+ *present = maxContentBoostFound;
stringstream ss(maxContentBoostStr);
float val;
if (ss >> val) {
@@ -188,8 +245,9 @@
}
}
- bool getMinContentBoost(float* min_content_boost) {
+ bool getMinContentBoost(float* min_content_boost, bool* present) {
if (state == Done) {
+ *present = minContentBoostFound;
stringstream ss(minContentBoostStr);
float val;
if (ss >> val) {
@@ -203,12 +261,141 @@
}
}
+ bool getGamma(float* gamma, bool* present) {
+ if (state == Done) {
+ *present = gammaFound;
+ stringstream ss(gammaStr);
+ float val;
+ if (ss >> val) {
+ *gamma = val;
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+
+ bool getOffsetSdr(float* offset_sdr, bool* present) {
+ if (state == Done) {
+ *present = offsetSdrFound;
+ stringstream ss(offsetSdrStr);
+ float val;
+ if (ss >> val) {
+ *offset_sdr = val;
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+
+ bool getOffsetHdr(float* offset_hdr, bool* present) {
+ if (state == Done) {
+ *present = offsetHdrFound;
+ stringstream ss(offsetHdrStr);
+ float val;
+ if (ss >> val) {
+ *offset_hdr = val;
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+
+ bool getHdrCapacityMin(float* hdr_capacity_min, bool* present) {
+ if (state == Done) {
+ *present = hdrCapacityMinFound;
+ stringstream ss(hdrCapacityMinStr);
+ float val;
+ if (ss >> val) {
+ *hdr_capacity_min = exp2(val);
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+
+ bool getHdrCapacityMax(float* hdr_capacity_max, bool* present) {
+ if (state == Done) {
+ *present = hdrCapacityMaxFound;
+ stringstream ss(hdrCapacityMaxStr);
+ float val;
+ if (ss >> val) {
+ *hdr_capacity_max = exp2(val);
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+
+ bool getBaseRenditionIsHdr(bool* base_rendition_is_hdr, bool* present) {
+ if (state == Done) {
+ *present = baseRenditionIsHdrFound;
+ if (!baseRenditionIsHdrStr.compare("False")) {
+ *base_rendition_is_hdr = false;
+ return true;
+ } else if (!baseRenditionIsHdrStr.compare("True")) {
+ *base_rendition_is_hdr = true;
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+
+
private:
static const string containerName;
+
+ static const string versionAttrName;
+ string versionStr;
+ bool versionFound;
static const string maxContentBoostAttrName;
string maxContentBoostStr;
+ bool maxContentBoostFound;
static const string minContentBoostAttrName;
string minContentBoostStr;
+ bool minContentBoostFound;
+ static const string gammaAttrName;
+ string gammaStr;
+ bool gammaFound;
+ static const string offsetSdrAttrName;
+ string offsetSdrStr;
+ bool offsetSdrFound;
+ static const string offsetHdrAttrName;
+ string offsetHdrStr;
+ bool offsetHdrFound;
+ static const string hdrCapacityMinAttrName;
+ string hdrCapacityMinStr;
+ bool hdrCapacityMinFound;
+ static const string hdrCapacityMaxAttrName;
+ string hdrCapacityMaxStr;
+ bool hdrCapacityMaxFound;
+ static const string baseRenditionIsHdrAttrName;
+ string baseRenditionIsHdrStr;
+ bool baseRenditionIsHdrFound;
+
string lastAttributeName;
ParseState state;
};
@@ -253,8 +440,15 @@
const string kMapBaseRenditionIsHDR = Name(kGainMapPrefix, "BaseRenditionIsHDR");
// GainMap XMP constants - names for XMP handlers
+const string XMPXmlHandler::versionAttrName = kMapVersion;
const string XMPXmlHandler::minContentBoostAttrName = kMapGainMapMin;
const string XMPXmlHandler::maxContentBoostAttrName = kMapGainMapMax;
+const string XMPXmlHandler::gammaAttrName = kMapGamma;
+const string XMPXmlHandler::offsetSdrAttrName = kMapOffsetSdr;
+const string XMPXmlHandler::offsetHdrAttrName = kMapOffsetHdr;
+const string XMPXmlHandler::hdrCapacityMinAttrName = kMapHDRCapacityMin;
+const string XMPXmlHandler::hdrCapacityMaxAttrName = kMapHDRCapacityMax;
+const string XMPXmlHandler::baseRenditionIsHdrAttrName = kMapBaseRenditionIsHDR;
bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, ultrahdr_metadata_struct* metadata) {
string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
@@ -291,11 +485,48 @@
return false;
}
- if (!handler.getMaxContentBoost(&metadata->maxContentBoost)) {
+ // Apply default values to any not-present fields, except for Version,
+ // maxContentBoost, and hdrCapacityMax, which are required. Return false if
+ // we encounter a present field that couldn't be parsed, since this
+ // indicates it is invalid (eg. string where there should be a float).
+ bool present = false;
+ if (!handler.getVersion(&metadata->version, &present) || !present) {
return false;
}
+ if (!handler.getMaxContentBoost(&metadata->maxContentBoost, &present) || !present) {
+ return false;
+ }
+ if (!handler.getHdrCapacityMax(&metadata->hdrCapacityMax, &present) || !present) {
+ return false;
+ }
+ if (!handler.getMinContentBoost(&metadata->minContentBoost, &present)) {
+ if (present) return false;
+ metadata->minContentBoost = 1.0f;
+ }
+ if (!handler.getGamma(&metadata->gamma, &present)) {
+ if (present) return false;
+ metadata->gamma = 1.0f;
+ }
+ if (!handler.getOffsetSdr(&metadata->offsetSdr, &present)) {
+ if (present) return false;
+ metadata->offsetSdr = 1.0f / 64.0f;
+ }
+ if (!handler.getOffsetHdr(&metadata->offsetHdr, &present)) {
+ if (present) return false;
+ metadata->offsetHdr = 1.0f / 64.0f;
+ }
+ if (!handler.getHdrCapacityMin(&metadata->hdrCapacityMin, &present)) {
+ if (present) return false;
+ metadata->hdrCapacityMin = 1.0f;
+ }
- if (!handler.getMinContentBoost(&metadata->minContentBoost)) {
+ bool base_rendition_is_hdr;
+ if (!handler.getBaseRenditionIsHdr(&base_rendition_is_hdr, &present)) {
+ if (present) return false;
+ base_rendition_is_hdr = false;
+ }
+ if (base_rendition_is_hdr) {
+ ALOGE("Base rendition of HDR is not supported!");
return false;
}
@@ -355,12 +586,11 @@
writer.WriteAttributeNameAndValue(kMapVersion, metadata.version);
writer.WriteAttributeNameAndValue(kMapGainMapMin, log2(metadata.minContentBoost));
writer.WriteAttributeNameAndValue(kMapGainMapMax, log2(metadata.maxContentBoost));
- writer.WriteAttributeNameAndValue(kMapGamma, "1");
- writer.WriteAttributeNameAndValue(kMapOffsetSdr, "0");
- writer.WriteAttributeNameAndValue(kMapOffsetHdr, "0");
- writer.WriteAttributeNameAndValue(
- kMapHDRCapacityMin, std::max(log2(metadata.minContentBoost), 0.0f));
- writer.WriteAttributeNameAndValue(kMapHDRCapacityMax, log2(metadata.maxContentBoost));
+ writer.WriteAttributeNameAndValue(kMapGamma, metadata.gamma);
+ writer.WriteAttributeNameAndValue(kMapOffsetSdr, metadata.offsetSdr);
+ writer.WriteAttributeNameAndValue(kMapOffsetHdr, metadata.offsetHdr);
+ writer.WriteAttributeNameAndValue(kMapHDRCapacityMin, log2(metadata.hdrCapacityMin));
+ writer.WriteAttributeNameAndValue(kMapHDRCapacityMax, log2(metadata.hdrCapacityMax));
writer.WriteAttributeNameAndValue(kMapBaseRenditionIsHDR, "False");
writer.FinishWriting();
diff --git a/libs/ultrahdr/multipictureformat.cpp b/libs/ultrahdr/multipictureformat.cpp
index 7a265c6..f1679ef 100644
--- a/libs/ultrahdr/multipictureformat.cpp
+++ b/libs/ultrahdr/multipictureformat.cpp
@@ -30,7 +30,7 @@
sp<DataStruct> generateMpf(int primary_image_size, int primary_image_offset,
int secondary_image_size, int secondary_image_offset) {
size_t mpf_size = calculateMpfSize();
- sp<DataStruct> dataStruct = new DataStruct(mpf_size);
+ sp<DataStruct> dataStruct = sp<DataStruct>::make(mpf_size);
dataStruct->write(static_cast<const void*>(kMpfSig), sizeof(kMpfSig));
#if USE_BIG_ENDIAN
diff --git a/libs/ultrahdr/tests/Android.bp b/libs/ultrahdr/tests/Android.bp
index 7dd9d04..5944130 100644
--- a/libs/ultrahdr/tests/Android.bp
+++ b/libs/ultrahdr/tests/Android.bp
@@ -25,8 +25,9 @@
name: "libultrahdr_test",
test_suites: ["device-tests"],
srcs: [
- "jpegr_test.cpp",
"gainmapmath_test.cpp",
+ "icchelper_test.cpp",
+ "jpegr_test.cpp",
],
shared_libs: [
"libimage_io",
@@ -72,5 +73,7 @@
static_libs: [
"libgtest",
"libjpegdecoder",
+ "libultrahdr",
+ "libutils",
],
}
diff --git a/libs/ultrahdr/tests/data/minnie-320x240-yuv-icc.jpg b/libs/ultrahdr/tests/data/minnie-320x240-yuv-icc.jpg
new file mode 100644
index 0000000..c7f4538
--- /dev/null
+++ b/libs/ultrahdr/tests/data/minnie-320x240-yuv-icc.jpg
Binary files differ
diff --git a/libs/ultrahdr/tests/gainmapmath_test.cpp b/libs/ultrahdr/tests/gainmapmath_test.cpp
index c456653..af90365 100644
--- a/libs/ultrahdr/tests/gainmapmath_test.cpp
+++ b/libs/ultrahdr/tests/gainmapmath_test.cpp
@@ -28,6 +28,7 @@
float ComparisonEpsilon() { return 1e-4f; }
float LuminanceEpsilon() { return 1e-2f; }
+ float YuvConversionEpsilon() { return 1.0f / (255.0f * 2.0f); }
Color Yuv420(uint8_t y, uint8_t u, uint8_t v) {
return {{{ static_cast<float>(y) / 255.0f,
@@ -63,9 +64,13 @@
Color YuvBlack() { return {{{ 0.0f, 0.0f, 0.0f }}}; }
Color YuvWhite() { return {{{ 1.0f, 0.0f, 0.0f }}}; }
- Color SrgbYuvRed() { return {{{ 0.299f, -0.1687f, 0.5f }}}; }
- Color SrgbYuvGreen() { return {{{ 0.587f, -0.3313f, -0.4187f }}}; }
- Color SrgbYuvBlue() { return {{{ 0.114f, 0.5f, -0.0813f }}}; }
+ Color SrgbYuvRed() { return {{{ 0.2126f, -0.11457f, 0.5f }}}; }
+ Color SrgbYuvGreen() { return {{{ 0.7152f, -0.38543f, -0.45415f }}}; }
+ Color SrgbYuvBlue() { return {{{ 0.0722f, 0.5f, -0.04585f }}}; }
+
+ Color P3YuvRed() { return {{{ 0.299f, -0.16874f, 0.5f }}}; }
+ Color P3YuvGreen() { return {{{ 0.587f, -0.33126f, -0.41869f }}}; }
+ Color P3YuvBlue() { return {{{ 0.114f, 0.5f, -0.08131f }}}; }
Color Bt2100YuvRed() { return {{{ 0.2627f, -0.13963f, 0.5f }}}; }
Color Bt2100YuvGreen() { return {{{ 0.6780f, -0.36037f, -0.45979f }}}; }
@@ -78,6 +83,13 @@
return luminance_scaled * kSdrWhiteNits;
}
+ float P3YuvToLuminance(Color yuv_gamma, ColorCalculationFn luminanceFn) {
+ Color rgb_gamma = p3YuvToRgb(yuv_gamma);
+ Color rgb = srgbInvOetf(rgb_gamma);
+ float luminance_scaled = luminanceFn(rgb);
+ return luminance_scaled * kSdrWhiteNits;
+ }
+
float Bt2100YuvToLuminance(Color yuv_gamma, ColorTransformFn hdrInvOetf,
ColorTransformFn gamutConversionFn, ColorCalculationFn luminanceFn,
float scale_factor) {
@@ -402,6 +414,56 @@
EXPECT_FLOAT_EQ(p3Luminance(RgbBlue()), 0.06891f);
}
+TEST_F(GainMapMathTest, P3YuvToRgb) {
+ Color rgb_black = p3YuvToRgb(YuvBlack());
+ EXPECT_RGB_NEAR(rgb_black, RgbBlack());
+
+ Color rgb_white = p3YuvToRgb(YuvWhite());
+ EXPECT_RGB_NEAR(rgb_white, RgbWhite());
+
+ Color rgb_r = p3YuvToRgb(P3YuvRed());
+ EXPECT_RGB_NEAR(rgb_r, RgbRed());
+
+ Color rgb_g = p3YuvToRgb(P3YuvGreen());
+ EXPECT_RGB_NEAR(rgb_g, RgbGreen());
+
+ Color rgb_b = p3YuvToRgb(P3YuvBlue());
+ EXPECT_RGB_NEAR(rgb_b, RgbBlue());
+}
+
+TEST_F(GainMapMathTest, P3RgbToYuv) {
+ Color yuv_black = p3RgbToYuv(RgbBlack());
+ EXPECT_YUV_NEAR(yuv_black, YuvBlack());
+
+ Color yuv_white = p3RgbToYuv(RgbWhite());
+ EXPECT_YUV_NEAR(yuv_white, YuvWhite());
+
+ Color yuv_r = p3RgbToYuv(RgbRed());
+ EXPECT_YUV_NEAR(yuv_r, P3YuvRed());
+
+ Color yuv_g = p3RgbToYuv(RgbGreen());
+ EXPECT_YUV_NEAR(yuv_g, P3YuvGreen());
+
+ Color yuv_b = p3RgbToYuv(RgbBlue());
+ EXPECT_YUV_NEAR(yuv_b, P3YuvBlue());
+}
+
+TEST_F(GainMapMathTest, P3RgbYuvRoundtrip) {
+ Color rgb_black = p3YuvToRgb(p3RgbToYuv(RgbBlack()));
+ EXPECT_RGB_NEAR(rgb_black, RgbBlack());
+
+ Color rgb_white = p3YuvToRgb(p3RgbToYuv(RgbWhite()));
+ EXPECT_RGB_NEAR(rgb_white, RgbWhite());
+
+ Color rgb_r = p3YuvToRgb(p3RgbToYuv(RgbRed()));
+ EXPECT_RGB_NEAR(rgb_r, RgbRed());
+
+ Color rgb_g = p3YuvToRgb(p3RgbToYuv(RgbGreen()));
+ EXPECT_RGB_NEAR(rgb_g, RgbGreen());
+
+ Color rgb_b = p3YuvToRgb(p3RgbToYuv(RgbBlue()));
+ EXPECT_RGB_NEAR(rgb_b, RgbBlue());
+}
TEST_F(GainMapMathTest, Bt2100Luminance) {
EXPECT_FLOAT_EQ(bt2100Luminance(RgbBlack()), 0.0f);
EXPECT_FLOAT_EQ(bt2100Luminance(RgbWhite()), 1.0f);
@@ -461,6 +523,163 @@
EXPECT_RGB_NEAR(rgb_b, RgbBlue());
}
+TEST_F(GainMapMathTest, Bt709ToBt601YuvConversion) {
+ Color yuv_black = srgbRgbToYuv(RgbBlack());
+ EXPECT_YUV_NEAR(yuv709To601(yuv_black), YuvBlack());
+
+ Color yuv_white = srgbRgbToYuv(RgbWhite());
+ EXPECT_YUV_NEAR(yuv709To601(yuv_white), YuvWhite());
+
+ Color yuv_r = srgbRgbToYuv(RgbRed());
+ EXPECT_YUV_NEAR(yuv709To601(yuv_r), P3YuvRed());
+
+ Color yuv_g = srgbRgbToYuv(RgbGreen());
+ EXPECT_YUV_NEAR(yuv709To601(yuv_g), P3YuvGreen());
+
+ Color yuv_b = srgbRgbToYuv(RgbBlue());
+ EXPECT_YUV_NEAR(yuv709To601(yuv_b), P3YuvBlue());
+}
+
+TEST_F(GainMapMathTest, Bt709ToBt2100YuvConversion) {
+ Color yuv_black = srgbRgbToYuv(RgbBlack());
+ EXPECT_YUV_NEAR(yuv709To2100(yuv_black), YuvBlack());
+
+ Color yuv_white = srgbRgbToYuv(RgbWhite());
+ EXPECT_YUV_NEAR(yuv709To2100(yuv_white), YuvWhite());
+
+ Color yuv_r = srgbRgbToYuv(RgbRed());
+ EXPECT_YUV_NEAR(yuv709To2100(yuv_r), Bt2100YuvRed());
+
+ Color yuv_g = srgbRgbToYuv(RgbGreen());
+ EXPECT_YUV_NEAR(yuv709To2100(yuv_g), Bt2100YuvGreen());
+
+ Color yuv_b = srgbRgbToYuv(RgbBlue());
+ EXPECT_YUV_NEAR(yuv709To2100(yuv_b), Bt2100YuvBlue());
+}
+
+TEST_F(GainMapMathTest, Bt601ToBt709YuvConversion) {
+ Color yuv_black = p3RgbToYuv(RgbBlack());
+ EXPECT_YUV_NEAR(yuv601To709(yuv_black), YuvBlack());
+
+ Color yuv_white = p3RgbToYuv(RgbWhite());
+ EXPECT_YUV_NEAR(yuv601To709(yuv_white), YuvWhite());
+
+ Color yuv_r = p3RgbToYuv(RgbRed());
+ EXPECT_YUV_NEAR(yuv601To709(yuv_r), SrgbYuvRed());
+
+ Color yuv_g = p3RgbToYuv(RgbGreen());
+ EXPECT_YUV_NEAR(yuv601To709(yuv_g), SrgbYuvGreen());
+
+ Color yuv_b = p3RgbToYuv(RgbBlue());
+ EXPECT_YUV_NEAR(yuv601To709(yuv_b), SrgbYuvBlue());
+}
+
+TEST_F(GainMapMathTest, Bt601ToBt2100YuvConversion) {
+ Color yuv_black = p3RgbToYuv(RgbBlack());
+ EXPECT_YUV_NEAR(yuv601To2100(yuv_black), YuvBlack());
+
+ Color yuv_white = p3RgbToYuv(RgbWhite());
+ EXPECT_YUV_NEAR(yuv601To2100(yuv_white), YuvWhite());
+
+ Color yuv_r = p3RgbToYuv(RgbRed());
+ EXPECT_YUV_NEAR(yuv601To2100(yuv_r), Bt2100YuvRed());
+
+ Color yuv_g = p3RgbToYuv(RgbGreen());
+ EXPECT_YUV_NEAR(yuv601To2100(yuv_g), Bt2100YuvGreen());
+
+ Color yuv_b = p3RgbToYuv(RgbBlue());
+ EXPECT_YUV_NEAR(yuv601To2100(yuv_b), Bt2100YuvBlue());
+}
+
+TEST_F(GainMapMathTest, Bt2100ToBt709YuvConversion) {
+ Color yuv_black = bt2100RgbToYuv(RgbBlack());
+ EXPECT_YUV_NEAR(yuv2100To709(yuv_black), YuvBlack());
+
+ Color yuv_white = bt2100RgbToYuv(RgbWhite());
+ EXPECT_YUV_NEAR(yuv2100To709(yuv_white), YuvWhite());
+
+ Color yuv_r = bt2100RgbToYuv(RgbRed());
+ EXPECT_YUV_NEAR(yuv2100To709(yuv_r), SrgbYuvRed());
+
+ Color yuv_g = bt2100RgbToYuv(RgbGreen());
+ EXPECT_YUV_NEAR(yuv2100To709(yuv_g), SrgbYuvGreen());
+
+ Color yuv_b = bt2100RgbToYuv(RgbBlue());
+ EXPECT_YUV_NEAR(yuv2100To709(yuv_b), SrgbYuvBlue());
+}
+
+TEST_F(GainMapMathTest, Bt2100ToBt601YuvConversion) {
+ Color yuv_black = bt2100RgbToYuv(RgbBlack());
+ EXPECT_YUV_NEAR(yuv2100To601(yuv_black), YuvBlack());
+
+ Color yuv_white = bt2100RgbToYuv(RgbWhite());
+ EXPECT_YUV_NEAR(yuv2100To601(yuv_white), YuvWhite());
+
+ Color yuv_r = bt2100RgbToYuv(RgbRed());
+ EXPECT_YUV_NEAR(yuv2100To601(yuv_r), P3YuvRed());
+
+ Color yuv_g = bt2100RgbToYuv(RgbGreen());
+ EXPECT_YUV_NEAR(yuv2100To601(yuv_g), P3YuvGreen());
+
+ Color yuv_b = bt2100RgbToYuv(RgbBlue());
+ EXPECT_YUV_NEAR(yuv2100To601(yuv_b), P3YuvBlue());
+}
+
+TEST_F(GainMapMathTest, TransformYuv420) {
+ ColorTransformFn transforms[] = { yuv709To601, yuv709To2100, yuv601To709, yuv601To2100,
+ yuv2100To709, yuv2100To601 };
+ for (const ColorTransformFn& transform : transforms) {
+ jpegr_uncompressed_struct input = Yuv420Image();
+
+ size_t out_buf_size = input.width * input.height * 3 / 2;
+ std::unique_ptr<uint8_t[]> out_buf = std::make_unique<uint8_t[]>(out_buf_size);
+ memcpy(out_buf.get(), input.data, out_buf_size);
+ jpegr_uncompressed_struct output = Yuv420Image();
+ output.data = out_buf.get();
+
+ transformYuv420(&output, 1, 1, transform);
+
+ for (size_t y = 0; y < 4; ++y) {
+ for (size_t x = 0; x < 4; ++x) {
+ // Skip the last chroma sample, which we modified above
+ if (x >= 2 && y >= 2) {
+ continue;
+ }
+
+ // All other pixels should remain unchanged
+ EXPECT_YUV_EQ(getYuv420Pixel(&input, x, y), getYuv420Pixel(&output, x, y));
+ }
+ }
+
+ // modified pixels should be updated as intended by the transformYuv420 algorithm
+ Color in1 = getYuv420Pixel(&input, 2, 2);
+ Color in2 = getYuv420Pixel(&input, 3, 2);
+ Color in3 = getYuv420Pixel(&input, 2, 3);
+ Color in4 = getYuv420Pixel(&input, 3, 3);
+ Color out1 = getYuv420Pixel(&output, 2, 2);
+ Color out2 = getYuv420Pixel(&output, 3, 2);
+ Color out3 = getYuv420Pixel(&output, 2, 3);
+ Color out4 = getYuv420Pixel(&output, 3, 3);
+
+ EXPECT_NEAR(transform(in1).y, out1.y, YuvConversionEpsilon());
+ EXPECT_NEAR(transform(in2).y, out2.y, YuvConversionEpsilon());
+ EXPECT_NEAR(transform(in3).y, out3.y, YuvConversionEpsilon());
+ EXPECT_NEAR(transform(in4).y, out4.y, YuvConversionEpsilon());
+
+ Color expect_uv = (transform(in1) + transform(in2) + transform(in3) + transform(in4)) / 4.0f;
+
+ EXPECT_NEAR(expect_uv.u, out1.u, YuvConversionEpsilon());
+ EXPECT_NEAR(expect_uv.u, out2.u, YuvConversionEpsilon());
+ EXPECT_NEAR(expect_uv.u, out3.u, YuvConversionEpsilon());
+ EXPECT_NEAR(expect_uv.u, out4.u, YuvConversionEpsilon());
+
+ EXPECT_NEAR(expect_uv.v, out1.v, YuvConversionEpsilon());
+ EXPECT_NEAR(expect_uv.v, out2.v, YuvConversionEpsilon());
+ EXPECT_NEAR(expect_uv.v, out3.v, YuvConversionEpsilon());
+ EXPECT_NEAR(expect_uv.v, out4.v, YuvConversionEpsilon());
+ }
+}
+
TEST_F(GainMapMathTest, HlgOetf) {
EXPECT_FLOAT_EQ(hlgOetf(0.0f), 0.0f);
EXPECT_NEAR(hlgOetf(0.04167f), 0.35357f, ComparisonEpsilon());
@@ -693,7 +912,7 @@
TEST_F(GainMapMathTest, EncodeGain) {
ultrahdr_metadata_struct metadata = { .maxContentBoost = 4.0f,
- .minContentBoost = 1.0f / 4.0f };
+ .minContentBoost = 1.0f / 4.0f };
EXPECT_EQ(encodeGain(0.0f, 0.0f, &metadata), 127);
EXPECT_EQ(encodeGain(0.0f, 1.0f, &metadata), 127);
@@ -751,7 +970,7 @@
TEST_F(GainMapMathTest, ApplyGain) {
ultrahdr_metadata_struct metadata = { .maxContentBoost = 4.0f,
- .minContentBoost = 1.0f / 4.0f };
+ .minContentBoost = 1.0f / 4.0f };
float displayBoost = metadata.maxContentBoost;
EXPECT_RGB_NEAR(applyGain(RgbBlack(), 0.0f, &metadata), RgbBlack());
diff --git a/libs/ultrahdr/tests/icchelper_test.cpp b/libs/ultrahdr/tests/icchelper_test.cpp
new file mode 100644
index 0000000..ff61c08
--- /dev/null
+++ b/libs/ultrahdr/tests/icchelper_test.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright 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.
+ */
+
+#include <gtest/gtest.h>
+#include <ultrahdr/icc.h>
+#include <ultrahdr/ultrahdr.h>
+#include <utils/Log.h>
+
+namespace android::ultrahdr {
+
+class IccHelperTest : public testing::Test {
+public:
+ IccHelperTest();
+ ~IccHelperTest();
+protected:
+ virtual void SetUp();
+ virtual void TearDown();
+};
+
+IccHelperTest::IccHelperTest() {}
+
+IccHelperTest::~IccHelperTest() {}
+
+void IccHelperTest::SetUp() {}
+
+void IccHelperTest::TearDown() {}
+
+TEST_F(IccHelperTest, iccWriteThenRead) {
+ sp<DataStruct> iccBt709 = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
+ ULTRAHDR_COLORGAMUT_BT709);
+ ASSERT_NE(iccBt709->getLength(), 0);
+ ASSERT_NE(iccBt709->getData(), nullptr);
+ EXPECT_EQ(IccHelper::readIccColorGamut(iccBt709->getData(), iccBt709->getLength()),
+ ULTRAHDR_COLORGAMUT_BT709);
+
+ sp<DataStruct> iccP3 = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, ULTRAHDR_COLORGAMUT_P3);
+ ASSERT_NE(iccP3->getLength(), 0);
+ ASSERT_NE(iccP3->getData(), nullptr);
+ EXPECT_EQ(IccHelper::readIccColorGamut(iccP3->getData(), iccP3->getLength()),
+ ULTRAHDR_COLORGAMUT_P3);
+
+ sp<DataStruct> iccBt2100 = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
+ ULTRAHDR_COLORGAMUT_BT2100);
+ ASSERT_NE(iccBt2100->getLength(), 0);
+ ASSERT_NE(iccBt2100->getData(), nullptr);
+ EXPECT_EQ(IccHelper::readIccColorGamut(iccBt2100->getData(), iccBt2100->getLength()),
+ ULTRAHDR_COLORGAMUT_BT2100);
+}
+
+TEST_F(IccHelperTest, iccEndianness) {
+ sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, ULTRAHDR_COLORGAMUT_BT709);
+ size_t profile_size = icc->getLength() - kICCIdentifierSize;
+
+ uint8_t* icc_bytes = reinterpret_cast<uint8_t*>(icc->getData()) + kICCIdentifierSize;
+ uint32_t encoded_size = static_cast<uint32_t>(icc_bytes[0]) << 24 |
+ static_cast<uint32_t>(icc_bytes[1]) << 16 |
+ static_cast<uint32_t>(icc_bytes[2]) << 8 |
+ static_cast<uint32_t>(icc_bytes[3]);
+
+ EXPECT_EQ(static_cast<size_t>(encoded_size), profile_size);
+}
+
+} // namespace android::ultrahdr
+
diff --git a/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp b/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp
index c79dbe3..e2da01c 100644
--- a/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp
+++ b/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp
@@ -15,6 +15,7 @@
*/
#include <ultrahdr/jpegdecoderhelper.h>
+#include <ultrahdr/icc.h>
#include <gtest/gtest.h>
#include <utils/Log.h>
@@ -22,11 +23,19 @@
namespace android::ultrahdr {
+// No ICC or EXIF
#define YUV_IMAGE "/sdcard/Documents/minnie-320x240-yuv.jpg"
#define YUV_IMAGE_SIZE 20193
+// Has ICC and EXIF
+#define YUV_ICC_IMAGE "/sdcard/Documents/minnie-320x240-yuv-icc.jpg"
+#define YUV_ICC_IMAGE_SIZE 34266
+// No ICC or EXIF
#define GREY_IMAGE "/sdcard/Documents/minnie-320x240-y.jpg"
#define GREY_IMAGE_SIZE 20193
+#define IMAGE_WIDTH 320
+#define IMAGE_HEIGHT 240
+
class JpegDecoderHelperTest : public testing::Test {
public:
struct Image {
@@ -39,7 +48,7 @@
virtual void SetUp();
virtual void TearDown();
- Image mYuvImage, mGreyImage;
+ Image mYuvImage, mYuvIccImage, mGreyImage;
};
JpegDecoderHelperTest::JpegDecoderHelperTest() {}
@@ -79,6 +88,10 @@
FAIL() << "Load file " << YUV_IMAGE << " failed";
}
mYuvImage.size = YUV_IMAGE_SIZE;
+ if (!loadFile(YUV_ICC_IMAGE, &mYuvIccImage)) {
+ FAIL() << "Load file " << YUV_ICC_IMAGE << " failed";
+ }
+ mYuvIccImage.size = YUV_ICC_IMAGE_SIZE;
if (!loadFile(GREY_IMAGE, &mGreyImage)) {
FAIL() << "Load file " << GREY_IMAGE << " failed";
}
@@ -91,6 +104,16 @@
JpegDecoderHelper decoder;
EXPECT_TRUE(decoder.decompressImage(mYuvImage.buffer.get(), mYuvImage.size));
ASSERT_GT(decoder.getDecompressedImageSize(), static_cast<uint32_t>(0));
+ EXPECT_EQ(IccHelper::readIccColorGamut(decoder.getICCPtr(), decoder.getICCSize()),
+ ULTRAHDR_COLORGAMUT_UNSPECIFIED);
+}
+
+TEST_F(JpegDecoderHelperTest, decodeYuvIccImage) {
+ JpegDecoderHelper decoder;
+ EXPECT_TRUE(decoder.decompressImage(mYuvIccImage.buffer.get(), mYuvIccImage.size));
+ ASSERT_GT(decoder.getDecompressedImageSize(), static_cast<uint32_t>(0));
+ EXPECT_EQ(IccHelper::readIccColorGamut(decoder.getICCPtr(), decoder.getICCSize()),
+ ULTRAHDR_COLORGAMUT_BT709);
}
TEST_F(JpegDecoderHelperTest, decodeGreyImage) {
@@ -99,4 +122,35 @@
ASSERT_GT(decoder.getDecompressedImageSize(), static_cast<uint32_t>(0));
}
-} // namespace android::ultrahdr
\ No newline at end of file
+TEST_F(JpegDecoderHelperTest, getCompressedImageParameters) {
+ size_t width = 0, height = 0;
+ std::vector<uint8_t> icc, exif;
+
+ JpegDecoderHelper decoder;
+ EXPECT_TRUE(decoder.getCompressedImageParameters(mYuvImage.buffer.get(), mYuvImage.size,
+ &width, &height, &icc, &exif));
+
+ EXPECT_EQ(width, IMAGE_WIDTH);
+ EXPECT_EQ(height, IMAGE_HEIGHT);
+ EXPECT_EQ(icc.size(), 0);
+ EXPECT_EQ(exif.size(), 0);
+}
+
+TEST_F(JpegDecoderHelperTest, getCompressedImageParametersIcc) {
+ size_t width = 0, height = 0;
+ std::vector<uint8_t> icc, exif;
+
+ JpegDecoderHelper decoder;
+ EXPECT_TRUE(decoder.getCompressedImageParameters(mYuvIccImage.buffer.get(), mYuvIccImage.size,
+ &width, &height, &icc, &exif));
+
+ EXPECT_EQ(width, IMAGE_WIDTH);
+ EXPECT_EQ(height, IMAGE_HEIGHT);
+ EXPECT_GT(icc.size(), 0);
+ EXPECT_GT(exif.size(), 0);
+
+ EXPECT_EQ(IccHelper::readIccColorGamut(icc.data(), icc.size()),
+ ULTRAHDR_COLORGAMUT_BT709);
+}
+
+} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/tests/jpegr_test.cpp b/libs/ultrahdr/tests/jpegr_test.cpp
index d482ea1..41d55ec 100644
--- a/libs/ultrahdr/tests/jpegr_test.cpp
+++ b/libs/ultrahdr/tests/jpegr_test.cpp
@@ -819,6 +819,52 @@
EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
&jpegR, nullptr, nullptr, &jpegR)) << "fail, API allows nullptr gainmap image";
+ // test metadata
+ ultrahdr_metadata_struct good_metadata;
+ good_metadata.version = "1.0";
+ good_metadata.minContentBoost = 1.0f;
+ good_metadata.maxContentBoost = 2.0f;
+ good_metadata.gamma = 1.0f;
+ good_metadata.offsetSdr = 0.0f;
+ good_metadata.offsetHdr = 0.0f;
+ good_metadata.hdrCapacityMin = 1.0f;
+ good_metadata.hdrCapacityMax = 2.0f;
+
+ ultrahdr_metadata_struct metadata = good_metadata;
+ metadata.version = "1.1";
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata version";
+
+ metadata = good_metadata;
+ metadata.minContentBoost = 3.0f;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata content boost";
+
+ metadata = good_metadata;
+ metadata.gamma = -0.1f;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata gamma";
+
+ metadata = good_metadata;
+ metadata.offsetSdr = -0.1f;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata offset sdr";
+
+ metadata = good_metadata;
+ metadata.offsetHdr = -0.1f;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata offset hdr";
+
+ metadata = good_metadata;
+ metadata.hdrCapacityMax = 0.5f;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata hdr capacity max";
+
+ metadata = good_metadata;
+ metadata.hdrCapacityMin = 0.5f;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata hdr capacity min";
+
free(jpegR.data);
}
@@ -864,8 +910,13 @@
TEST_F(JpegRTest, writeXmpThenRead) {
ultrahdr_metadata_struct metadata_expected;
metadata_expected.version = "1.0";
- metadata_expected.maxContentBoost = 1.25;
- metadata_expected.minContentBoost = 0.75;
+ metadata_expected.maxContentBoost = 1.25f;
+ metadata_expected.minContentBoost = 0.75f;
+ metadata_expected.gamma = 1.0f;
+ metadata_expected.offsetSdr = 0.0f;
+ metadata_expected.offsetHdr = 0.0f;
+ metadata_expected.hdrCapacityMin = 1.0f;
+ metadata_expected.hdrCapacityMax = metadata_expected.maxContentBoost;
const std::string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
@@ -882,6 +933,11 @@
EXPECT_TRUE(getMetadataFromXMP(xmpData.data(), xmpData.size(), &metadata_read));
EXPECT_FLOAT_EQ(metadata_expected.maxContentBoost, metadata_read.maxContentBoost);
EXPECT_FLOAT_EQ(metadata_expected.minContentBoost, metadata_read.minContentBoost);
+ EXPECT_FLOAT_EQ(metadata_expected.gamma, metadata_read.gamma);
+ EXPECT_FLOAT_EQ(metadata_expected.offsetSdr, metadata_read.offsetSdr);
+ EXPECT_FLOAT_EQ(metadata_expected.offsetHdr, metadata_read.offsetHdr);
+ EXPECT_FLOAT_EQ(metadata_expected.hdrCapacityMin, metadata_read.hdrCapacityMin);
+ EXPECT_FLOAT_EQ(metadata_expected.hdrCapacityMax, metadata_read.hdrCapacityMax);
}
/* Test Encode API-0 */
@@ -1297,9 +1353,7 @@
JpegRBenchmark benchmark;
- ultrahdr_metadata_struct metadata = { .version = "1.0",
- .maxContentBoost = 8.0f,
- .minContentBoost = 1.0f / 8.0f };
+ ultrahdr_metadata_struct metadata = { .version = "1.0" };
jpegr_uncompressed_struct map = { .data = NULL,
.width = 0,
diff --git a/opengl/libs/EGL/Loader.cpp b/opengl/libs/EGL/Loader.cpp
index 2c3ce16..b4fc5f0 100644
--- a/opengl/libs/EGL/Loader.cpp
+++ b/opengl/libs/EGL/Loader.cpp
@@ -137,21 +137,28 @@
#endif
#endif
-static const char* DRIVER_SUFFIX_PROPERTY = "ro.hardware.egl";
+static const char* PERSIST_DRIVER_SUFFIX_PROPERTY = "persist.graphics.egl";
+static const char* RO_DRIVER_SUFFIX_PROPERTY = "ro.hardware.egl";
+static const char* RO_BOARD_PLATFORM_PROPERTY = "ro.board.platform";
static const char* HAL_SUBNAME_KEY_PROPERTIES[3] = {
- "persist.graphics.egl",
- DRIVER_SUFFIX_PROPERTY,
- "ro.board.platform",
+ PERSIST_DRIVER_SUFFIX_PROPERTY,
+ RO_DRIVER_SUFFIX_PROPERTY,
+ RO_BOARD_PLATFORM_PROPERTY,
};
+// Check whether the loaded system drivers should be unloaded in order to
+// load ANGLE or the updatable graphics drivers.
+// If ANGLE namespace is set, it means the application is identified to run on top of ANGLE.
+// If updatable graphics driver namespace is set, it means the application is identified to
+// run on top of updatable graphics drivers.
static bool should_unload_system_driver(egl_connection_t* cnx) {
// Return false if the system driver has been unloaded once.
if (cnx->systemDriverUnloaded) {
return false;
}
- // Return true if Angle namespace is set.
+ // Return true if ANGLE namespace is set.
android_namespace_t* ns = android::GraphicsEnv::getInstance().getAngleNamespace();
if (ns) {
return true;
@@ -245,17 +252,20 @@
continue;
}
hnd = attempt_to_load_system_driver(cnx, prop.c_str(), true);
- if (hnd) {
- break;
- } else if (strcmp(key, DRIVER_SUFFIX_PROPERTY) == 0) {
+ if (!hnd) {
+ ALOGD("Failed to load drivers from property %s with value %s", key, prop.c_str());
failToLoadFromDriverSuffixProperty = true;
}
+
+ // Abort regardless of whether subsequent properties are set, the value must be set
+ // correctly with the first property that has a value.
+ break;
}
}
if (!hnd) {
- // Can't find graphics driver by appending system properties, now search for the exact name
- // without any suffix of the GLES userspace driver in both locations.
+ // Can't find graphics driver by appending the value from system properties, now search for
+ // the exact name without any suffix of the GLES userspace driver in both locations.
// i.e.:
// libGLES.so, or:
// libEGL.so, libGLESv1_CM.so, libGLESv2.so
@@ -274,10 +284,10 @@
false, systemTime() - openTime);
} else {
// init_angle_backend will check if loaded driver is ANGLE or not,
- // will set cnx->useAngle appropriately.
+ // will set cnx->angleLoaded appropriately.
// Do this here so that we use ANGLE path when driver is ANGLE (e.g. loaded as native),
// not just loading ANGLE as option.
- init_angle_backend(hnd->dso[2], cnx);
+ attempt_to_init_angle_backend(hnd->dso[2], cnx);
}
LOG_ALWAYS_FATAL_IF(!hnd,
@@ -319,7 +329,7 @@
delete hnd;
cnx->dso = nullptr;
- cnx->useAngle = false;
+ cnx->angleLoaded = false;
}
void Loader::init_api(void* dso,
@@ -560,14 +570,14 @@
return hnd;
}
-void Loader::init_angle_backend(void* dso, egl_connection_t* cnx) {
+void Loader::attempt_to_init_angle_backend(void* dso, egl_connection_t* cnx) {
void* pANGLEGetDisplayPlatform = dlsym(dso, "ANGLEGetDisplayPlatform");
if (pANGLEGetDisplayPlatform) {
- ALOGV("ANGLE GLES library in use");
- cnx->useAngle = true;
+ ALOGV("ANGLE GLES library loaded");
+ cnx->angleLoaded = true;
} else {
- ALOGV("Native GLES library in use");
- cnx->useAngle = false;
+ ALOGV("Native GLES library loaded");
+ cnx->angleLoaded = false;
}
}
diff --git a/opengl/libs/EGL/Loader.h b/opengl/libs/EGL/Loader.h
index 81742ab..cadbd46 100644
--- a/opengl/libs/EGL/Loader.h
+++ b/opengl/libs/EGL/Loader.h
@@ -57,7 +57,7 @@
driver_t* attempt_to_load_system_driver(egl_connection_t* cnx, const char* suffix, const bool exact);
void unload_system_driver(egl_connection_t* cnx);
void initialize_api(void* dso, egl_connection_t* cnx, uint32_t mask);
- void init_angle_backend(void* dso, egl_connection_t* cnx);
+ void attempt_to_init_angle_backend(void* dso, egl_connection_t* cnx);
static __attribute__((noinline)) void init_api(void* dso, const char* const* api,
const char* const* ref_api,
diff --git a/opengl/libs/EGL/egl_display.cpp b/opengl/libs/EGL/egl_display.cpp
index 525fed1..3317347 100644
--- a/opengl/libs/EGL/egl_display.cpp
+++ b/opengl/libs/EGL/egl_display.cpp
@@ -191,7 +191,7 @@
if (cnx->dso) {
EGLDisplay dpy = EGL_NO_DISPLAY;
- if (cnx->useAngle) {
+ if (cnx->angleLoaded) {
EGLint error;
dpy = getPlatformDisplayAngle(display, cnx, attrib_list, &error);
if (error != EGL_NONE) {
@@ -324,7 +324,7 @@
// b/269060366 Conditionally enabled EGL_ANDROID_get_frame_timestamps extension if the
// device's present timestamps are reliable (which may not be the case on emulators).
- if (cnx->useAngle) {
+ if (cnx->angleLoaded) {
if (android::base::GetBoolProperty("service.sf.present_timestamp", false)) {
mExtensionString.append("EGL_ANDROID_get_frame_timestamps ");
}
@@ -432,7 +432,7 @@
egl_connection_t* const cnx = &gEGLImpl;
if (cnx->dso && disp.state == egl_display_t::INITIALIZED) {
// If we're using ANGLE reset any custom DisplayPlatform
- if (cnx->useAngle) {
+ if (cnx->angleLoaded) {
angle::resetAnglePlatform(disp.dpy);
}
if (cnx->egl.eglTerminate(disp.dpy) == EGL_FALSE) {
diff --git a/opengl/libs/EGL/egl_object.cpp b/opengl/libs/EGL/egl_object.cpp
index efbe613..33a77c4 100644
--- a/opengl/libs/EGL/egl_object.cpp
+++ b/opengl/libs/EGL/egl_object.cpp
@@ -84,7 +84,7 @@
if (win != nullptr && connected) {
// NOTE: When using Vulkan backend, the Vulkan runtime makes all the
// native_window_* calls, so don't do them here.
- if (!cnx->useAngle) {
+ if (!cnx->angleLoaded) {
native_window_set_buffers_format(win, 0);
if (native_window_api_disconnect(win, NATIVE_WINDOW_API_EGL)) {
ALOGW("EGLNativeWindowType %p disconnect failed", win);
diff --git a/opengl/libs/EGL/egl_platform_entries.cpp b/opengl/libs/EGL/egl_platform_entries.cpp
index 48718bb..88001b2 100644
--- a/opengl/libs/EGL/egl_platform_entries.cpp
+++ b/opengl/libs/EGL/egl_platform_entries.cpp
@@ -680,7 +680,7 @@
// NOTE: When using Vulkan backend, the Vulkan runtime makes all the
// native_window_* calls, so don't do them here.
- if (!cnx->useAngle) {
+ if (!cnx->angleLoaded) {
int result = native_window_api_connect(window, NATIVE_WINDOW_API_EGL);
if (result < 0) {
ALOGE("eglCreateWindowSurface: native_window_api_connect (win=%p) "
@@ -699,14 +699,14 @@
std::vector<AttrType> strippedAttribList;
if (!processAttributes<AttrType>(dp, window, attrib_list, &colorSpace, &strippedAttribList)) {
ALOGE("error invalid colorspace: %d", colorSpace);
- if (!cnx->useAngle) {
+ if (!cnx->angleLoaded) {
native_window_api_disconnect(window, NATIVE_WINDOW_API_EGL);
}
return EGL_NO_SURFACE;
}
attrib_list = strippedAttribList.data();
- if (!cnx->useAngle) {
+ if (!cnx->angleLoaded) {
int err = native_window_set_buffers_format(window, static_cast<int>(format));
if (err != 0) {
ALOGE("error setting native window pixel format: %s (%d)", strerror(-err), err);
@@ -738,7 +738,7 @@
}
// EGLSurface creation failed
- if (!cnx->useAngle) {
+ if (!cnx->angleLoaded) {
native_window_set_buffers_format(window, 0);
native_window_api_disconnect(window, NATIVE_WINDOW_API_EGL);
}
@@ -1349,7 +1349,7 @@
}
}
- if (!s->cnx->useAngle) {
+ if (!s->cnx->angleLoaded) {
if (!sendSurfaceMetadata(s)) {
native_window_api_disconnect(s->getNativeWindow(), NATIVE_WINDOW_API_EGL);
return setError(EGL_BAD_NATIVE_WINDOW, (EGLBoolean)EGL_FALSE);
@@ -1374,7 +1374,7 @@
androidRect.bottom = y;
androidRects.push_back(androidRect);
}
- if (!s->cnx->useAngle) {
+ if (!s->cnx->angleLoaded) {
native_window_set_surface_damage(s->getNativeWindow(), androidRects.data(),
androidRects.size());
}
@@ -1465,7 +1465,7 @@
int err = native_window_set_auto_refresh(s->getNativeWindow(), value != 0);
if (err != 0) {
return setError(EGL_BAD_SURFACE, (EGLBoolean)EGL_FALSE);
- } else if (!s->cnx->useAngle) {
+ } else if (!s->cnx->angleLoaded) {
return EGL_TRUE;
} // else if ANGLE, fall through to the call to the driver (i.e. ANGLE) below
}
@@ -1479,7 +1479,7 @@
int err = native_window_enable_frame_timestamps(s->getNativeWindow(), value != 0);
if (err != 0) {
return setError(EGL_BAD_SURFACE, (EGLBoolean)EGL_FALSE);
- } else if (!s->cnx->useAngle) {
+ } else if (!s->cnx->angleLoaded) {
return EGL_TRUE;
} // else if ANGLE, fall through to the call to the driver (i.e. ANGLE) below
}
diff --git a/opengl/libs/EGL/egldefs.h b/opengl/libs/EGL/egldefs.h
index fcc11f1..3bd37cb 100644
--- a/opengl/libs/EGL/egldefs.h
+++ b/opengl/libs/EGL/egldefs.h
@@ -41,7 +41,8 @@
libEgl(nullptr),
libGles1(nullptr),
libGles2(nullptr),
- systemDriverUnloaded(false) {
+ systemDriverUnloaded(false),
+ angleLoaded(false) {
const char* const* entries = platform_names;
EGLFuncPointer* curr = reinterpret_cast<EGLFuncPointer*>(&platform);
while (*entries) {
@@ -73,7 +74,7 @@
void* libGles2;
bool systemDriverUnloaded;
- bool useAngle; // Was ANGLE successfully loaded
+ bool angleLoaded; // Was ANGLE successfully loaded
};
extern gl_hooks_t gHooks[2];
diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp
index ffcc967..dc7c75a 100644
--- a/services/inputflinger/Android.bp
+++ b/services/inputflinger/Android.bp
@@ -106,6 +106,13 @@
"libstatspull",
"libstatssocket",
],
+ include_dirs: [
+ "bionic/libc/kernel/android/uapi/",
+ "bionic/libc/kernel/uapi",
+ ],
+ cflags: [
+ "-D__ANDROID_HOST__",
+ ],
},
},
}
@@ -230,6 +237,9 @@
"inputflinger",
"libinputflingerhost",
+ // rust targets
+ "libinput_rust_test",
+
// native fuzzers
"inputflinger_latencytracker_fuzzer",
"inputflinger_cursor_input_fuzzer",
diff --git a/services/inputflinger/InputDeviceMetricsCollector.cpp b/services/inputflinger/InputDeviceMetricsCollector.cpp
index c21a4f2..2319d51 100644
--- a/services/inputflinger/InputDeviceMetricsCollector.cpp
+++ b/services/inputflinger/InputDeviceMetricsCollector.cpp
@@ -27,10 +27,11 @@
using android::base::StringPrintf;
using std::chrono::nanoseconds;
+using std::chrono_literals::operator""ns;
namespace {
-constexpr nanoseconds DEFAULT_USAGE_SESSION_TIMEOUT = std::chrono::seconds(5);
+constexpr nanoseconds DEFAULT_USAGE_SESSION_TIMEOUT = std::chrono::minutes(2);
/**
* Log debug messages about metrics events logged to statsd.
@@ -75,10 +76,19 @@
ftl::enum_string(src).c_str(), durMillis);
}
+ ALOGD_IF(DEBUG, " Uid breakdown:");
+
+ std::vector<int32_t> uids;
+ std::vector<int32_t> durationsPerUid;
+ for (auto& [uid, dur] : report.uidBreakdown) {
+ uids.push_back(uid);
+ int32_t durMillis = std::chrono::duration_cast<std::chrono::milliseconds>(dur).count();
+ durationsPerUid.push_back(durMillis);
+ ALOGD_IF(DEBUG, " - uid: %d\t duration: %dms", uid, durMillis);
+ }
util::stats_write(util::INPUTDEVICE_USAGE_REPORTED, identifier.vendor, identifier.product,
identifier.version, linuxBusToInputDeviceBusEnum(identifier.bus),
- durationMillis, sources, durationsPerSource, /*uids=*/empty,
- /*usage_durations_per_uid=*/empty);
+ durationMillis, sources, durationsPerSource, uids, durationsPerUid);
}
} sStatsdLogger;
@@ -249,6 +259,15 @@
mNextListener.notify(args);
}
+void InputDeviceMetricsCollector::notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
+ const std::set<int32_t>& uids) {
+ std::set<Uid> typeSafeUids;
+ for (auto uid : uids) {
+ typeSafeUids.emplace(uid);
+ }
+ mInteractionsQueue.push(DeviceId{deviceId}, timestamp, typeSafeUids);
+}
+
void InputDeviceMetricsCollector::dump(std::string& dump) {
dump += "InputDeviceMetricsCollector:\n";
@@ -307,17 +326,33 @@
}
}
-void InputDeviceMetricsCollector::reportCompletedSessions() {
- const auto currentTime = mLogger.getCurrentTime();
+void InputDeviceMetricsCollector::onInputDeviceInteraction(const Interaction& interaction) {
+ auto activeSessionIt = mActiveUsageSessions.find(std::get<DeviceId>(interaction));
+ if (activeSessionIt == mActiveUsageSessions.end()) {
+ return;
+ }
+ activeSessionIt->second.recordInteraction(interaction);
+}
+
+void InputDeviceMetricsCollector::reportCompletedSessions() {
+ // Process all pending interactions.
+ for (auto interaction = mInteractionsQueue.pop(); interaction;
+ interaction = mInteractionsQueue.pop()) {
+ onInputDeviceInteraction(*interaction);
+ }
+
+ const auto currentTime = mLogger.getCurrentTime();
std::vector<DeviceId> completedUsageSessions;
+ // Process usages for all active session to determine if any sessions have expired.
for (auto& [deviceId, activeSession] : mActiveUsageSessions) {
if (activeSession.checkIfCompletedAt(currentTime)) {
completedUsageSessions.emplace_back(deviceId);
}
}
+ // Close out and log all expired usage sessions.
for (DeviceId deviceId : completedUsageSessions) {
const auto infoIt = mLoggedDeviceInfos.find(deviceId);
LOG_ALWAYS_FATAL_IF(infoIt == mLoggedDeviceInfos.end());
@@ -349,6 +384,23 @@
mDeviceSession.end = eventTime;
}
+void InputDeviceMetricsCollector::ActiveSession::recordInteraction(const Interaction& interaction) {
+ const auto sessionExpiryTime = mDeviceSession.end + mUsageSessionTimeout;
+ const auto timestamp = std::get<nanoseconds>(interaction);
+ if (timestamp >= sessionExpiryTime) {
+ // This interaction occurred after the device's current active session is set to expire.
+ // Ignore it.
+ return;
+ }
+
+ for (Uid uid : std::get<std::set<Uid>>(interaction)) {
+ auto [activeUidIt, inserted] = mActiveSessionsByUid.try_emplace(uid, timestamp, timestamp);
+ if (!inserted) {
+ activeUidIt->second.end = timestamp;
+ }
+ }
+}
+
bool InputDeviceMetricsCollector::ActiveSession::checkIfCompletedAt(nanoseconds timestamp) {
const auto sessionExpiryTime = timestamp - mUsageSessionTimeout;
std::vector<InputDeviceUsageSource> completedSourceSessionsForDevice;
@@ -363,6 +415,21 @@
mSourceUsageBreakdown.emplace_back(source, session.end - session.start);
mActiveSessionsBySource.erase(it);
}
+
+ std::vector<Uid> completedUidSessionsForDevice;
+ for (auto& [uid, session] : mActiveSessionsByUid) {
+ if (session.end <= sessionExpiryTime) {
+ completedUidSessionsForDevice.emplace_back(uid);
+ }
+ }
+ for (Uid uid : completedUidSessionsForDevice) {
+ auto it = mActiveSessionsByUid.find(uid);
+ const auto& [_, session] = *it;
+ mUidUsageBreakdown.emplace_back(uid, session.end - session.start);
+ mActiveSessionsByUid.erase(it);
+ }
+
+ // This active session has expired if there are no more active source sessions tracked.
return mActiveSessionsBySource.empty();
}
@@ -375,7 +442,12 @@
}
mActiveSessionsBySource.clear();
- return {deviceUsageDuration, mSourceUsageBreakdown};
+ for (const auto& [uid, uidSession] : mActiveSessionsByUid) {
+ mUidUsageBreakdown.emplace_back(uid, uidSession.end - uidSession.start);
+ }
+ mActiveSessionsByUid.clear();
+
+ return {deviceUsageDuration, mSourceUsageBreakdown, mUidUsageBreakdown};
}
} // namespace android
diff --git a/services/inputflinger/InputDeviceMetricsCollector.h b/services/inputflinger/InputDeviceMetricsCollector.h
index e2e79e4..387786f 100644
--- a/services/inputflinger/InputDeviceMetricsCollector.h
+++ b/services/inputflinger/InputDeviceMetricsCollector.h
@@ -18,6 +18,7 @@
#include "InputListener.h"
#include "NotifyArgs.h"
+#include "SyncQueue.h"
#include <ftl/mixins.h>
#include <input/InputDevice.h>
@@ -33,11 +34,17 @@
/**
* Logs metrics about registered input devices and their usages.
*
- * Not thread safe. Must be called from a single thread.
+ * All methods in the InputListenerInterface must be called from a single thread.
*/
class InputDeviceMetricsCollectorInterface : public InputListenerInterface {
public:
/**
+ * Notify the metrics collector that there was an input device interaction with apps.
+ * Called from the InputDispatcher thread.
+ */
+ virtual void notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
+ const std::set<int32_t>& uids) = 0;
+ /**
* Dump the state of the interaction blocker.
* This method may be called on any thread (usually by the input manager on a binder thread).
*/
@@ -92,9 +99,14 @@
using SourceUsageBreakdown =
std::vector<std::pair<InputDeviceUsageSource, std::chrono::nanoseconds /*duration*/>>;
+ // Describes the breakdown of an input device usage session by the UIDs that it interacted with.
+ using UidUsageBreakdown =
+ std::vector<std::pair<int32_t /*uid*/, std::chrono::nanoseconds /*duration*/>>;
+
struct DeviceUsageReport {
std::chrono::nanoseconds usageDuration;
SourceUsageBreakdown sourceBreakdown;
+ UidUsageBreakdown uidBreakdown;
};
virtual void logInputDeviceUsageReported(const InputDeviceIdentifier&,
@@ -121,6 +133,8 @@
void notifyDeviceReset(const NotifyDeviceResetArgs& args) override;
void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override;
+ void notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
+ const std::set<int32_t>& uids) override;
void dump(std::string& dump) override;
private:
@@ -138,13 +152,25 @@
return std::to_string(ftl::to_underlying(id));
}
+ // Type-safe wrapper for a UID.
+ struct Uid : ftl::Constructible<Uid, std::int32_t>, ftl::Equatable<Uid>, ftl::Orderable<Uid> {
+ using Constructible::Constructible;
+ };
+ static inline std::string toString(const Uid& src) {
+ return std::to_string(ftl::to_underlying(src));
+ }
+
std::map<DeviceId, InputDeviceInfo> mLoggedDeviceInfos;
+ using Interaction = std::tuple<DeviceId, std::chrono::nanoseconds, std::set<Uid>>;
+ SyncQueue<Interaction> mInteractionsQueue;
+
class ActiveSession {
public:
explicit ActiveSession(std::chrono::nanoseconds usageSessionTimeout,
std::chrono::nanoseconds startTime);
void recordUsage(std::chrono::nanoseconds eventTime, InputDeviceUsageSource source);
+ void recordInteraction(const Interaction&);
bool checkIfCompletedAt(std::chrono::nanoseconds timestamp);
InputDeviceMetricsLogger::DeviceUsageReport finishSession();
@@ -159,6 +185,9 @@
std::map<InputDeviceUsageSource, UsageSession> mActiveSessionsBySource{};
InputDeviceMetricsLogger::SourceUsageBreakdown mSourceUsageBreakdown{};
+
+ std::map<Uid, UsageSession> mActiveSessionsByUid{};
+ InputDeviceMetricsLogger::UidUsageBreakdown mUidUsageBreakdown{};
};
// The input devices that currently have active usage sessions.
@@ -169,6 +198,7 @@
using SourceProvider = std::function<std::set<InputDeviceUsageSource>(const InputDeviceInfo&)>;
void onInputDeviceUsage(DeviceId deviceId, std::chrono::nanoseconds eventTime,
const SourceProvider& getSources);
+ void onInputDeviceInteraction(const Interaction&);
void reportCompletedSessions();
};
diff --git a/services/inputflinger/InputManager.cpp b/services/inputflinger/InputManager.cpp
index 863b483..37b3187 100644
--- a/services/inputflinger/InputManager.cpp
+++ b/services/inputflinger/InputManager.cpp
@@ -34,7 +34,7 @@
namespace android {
static const bool ENABLE_INPUT_DEVICE_USAGE_METRICS =
- sysprop::InputProperties::enable_input_device_usage_metrics().value_or(false);
+ sysprop::InputProperties::enable_input_device_usage_metrics().value_or(true);
using gui::FocusRequest;
@@ -127,6 +127,10 @@
return *mProcessor;
}
+InputDeviceMetricsCollectorInterface& InputManager::getMetricsCollector() {
+ return *mCollector;
+}
+
InputDispatcherInterface& InputManager::getDispatcher() {
return *mDispatcher;
}
diff --git a/services/inputflinger/InputManager.h b/services/inputflinger/InputManager.h
index 0f0d8ea..9dc285f 100644
--- a/services/inputflinger/InputManager.h
+++ b/services/inputflinger/InputManager.h
@@ -86,6 +86,9 @@
/* Gets the input processor. */
virtual InputProcessorInterface& getProcessor() = 0;
+ /* Gets the metrics collector. */
+ virtual InputDeviceMetricsCollectorInterface& getMetricsCollector() = 0;
+
/* Gets the input dispatcher. */
virtual InputDispatcherInterface& getDispatcher() = 0;
@@ -109,6 +112,7 @@
InputReaderInterface& getReader() override;
InputProcessorInterface& getProcessor() override;
+ InputDeviceMetricsCollectorInterface& getMetricsCollector() override;
InputDispatcherInterface& getDispatcher() override;
void monitor() override;
void dump(std::string& dump) override;
diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
index fbe8482..06a7352 100644
--- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
+++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
@@ -111,6 +111,9 @@
void notifyDropWindow(const sp<IBinder>&, float x, float y) override {}
+ void notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
+ const std::set<int32_t>& uids) override {}
+
InputDispatcherConfiguration mConfig;
};
diff --git a/services/inputflinger/dispatcher/DebugConfig.cpp b/services/inputflinger/dispatcher/DebugConfig.cpp
index 764194d..12122fd 100644
--- a/services/inputflinger/dispatcher/DebugConfig.cpp
+++ b/services/inputflinger/dispatcher/DebugConfig.cpp
@@ -30,11 +30,10 @@
bool debugInboundEventDetails() {
if (!IS_DEBUGGABLE_BUILD) {
static const bool DEBUG_INBOUND_EVENT_DETAILS =
- __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "InboundEvent",
- ANDROID_LOG_INFO);
+ android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "InboundEvent");
return DEBUG_INBOUND_EVENT_DETAILS;
}
- return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "InboundEvent", ANDROID_LOG_INFO);
+ return android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "InboundEvent");
}
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/DebugConfig.h b/services/inputflinger/dispatcher/DebugConfig.h
index 0e260a7..7a41d68 100644
--- a/services/inputflinger/dispatcher/DebugConfig.h
+++ b/services/inputflinger/dispatcher/DebugConfig.h
@@ -18,8 +18,7 @@
#define LOG_TAG "InputDispatcher"
-#include <log/log.h>
-#include <log/log_event_list.h>
+#include <android-base/logging.h>
namespace android::inputdispatcher {
@@ -42,14 +41,14 @@
* Enable this via "adb shell setprop log.tag.InputDispatcherOutboundEvent DEBUG" (requires restart)
*/
const bool DEBUG_OUTBOUND_EVENT_DETAILS =
- __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "OutboundEvent", ANDROID_LOG_INFO);
+ android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "OutboundEvent");
/**
* Log debug messages about the dispatch cycle.
* Enable this via "adb shell setprop log.tag.InputDispatcherDispatchCycle DEBUG" (requires restart)
*/
const bool DEBUG_DISPATCH_CYCLE =
- __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "DispatchCycle", ANDROID_LOG_INFO);
+ android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "DispatchCycle");
/**
* Log debug messages about channel creation
@@ -57,28 +56,28 @@
* restart)
*/
const bool DEBUG_CHANNEL_CREATION =
- __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "ChannelCreation", ANDROID_LOG_INFO);
+ android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "ChannelCreation");
/**
* Log debug messages about input event injection.
* Enable this via "adb shell setprop log.tag.InputDispatcherInjection DEBUG" (requires restart)
*/
const bool DEBUG_INJECTION =
- __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Injection", ANDROID_LOG_INFO);
+ android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "Injection");
/**
* Log debug messages about input focus tracking.
* Enable this via "adb shell setprop log.tag.InputDispatcherFocus DEBUG" (requires restart)
*/
const bool DEBUG_FOCUS =
- __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Focus", ANDROID_LOG_INFO);
+ android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "Focus");
/**
* Log debug messages about touch mode event
* Enable this via "adb shell setprop log.tag.InputDispatcherTouchMode DEBUG" (requires restart)
*/
const bool DEBUG_TOUCH_MODE =
- __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "TouchMode", ANDROID_LOG_INFO);
+ android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "TouchMode");
/**
* Log debug messages about touch occlusion
@@ -90,13 +89,20 @@
* Enable this via "adb shell setprop log.tag.InputDispatcherAppSwitch DEBUG" (requires restart)
*/
const bool DEBUG_APP_SWITCH =
- __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "AppSwitch", ANDROID_LOG_INFO);
+ android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "AppSwitch");
/**
* Log debug messages about hover events.
* Enable this via "adb shell setprop log.tag.InputDispatcherHover DEBUG" (requires restart)
*/
const bool DEBUG_HOVER =
- __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Hover", ANDROID_LOG_INFO);
+ android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "Hover");
+
+/**
+ * Crash if a bad stream from InputListener is detected.
+ * Enable this via "adb shell setprop log.tag.InputDispatcherVerifyEvents DEBUG" (requires restart)
+ */
+const bool DEBUG_VERIFY_EVENTS =
+ android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "VerifyEvents");
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp
index b625a1b..a670ebe 100644
--- a/services/inputflinger/dispatcher/Entry.cpp
+++ b/services/inputflinger/dispatcher/Entry.cpp
@@ -352,7 +352,7 @@
entry.transform.dump(transform, "transform");
out << ", resolvedFlags=" << entry.resolvedFlags
<< ", targetFlags=" << entry.targetFlags.string() << ", transform=" << transform
- << "} original =" << entry.eventEntry->getDescription();
+ << "} original: " << entry.eventEntry->getDescription();
return out;
}
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index fbbb388..b6a9ac5 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -26,6 +26,7 @@
#include <android/os/IInputConstants.h>
#include <binder/Binder.h>
#include <ftl/enum.h>
+#include <log/log_event_list.h>
#if defined(__ANDROID__)
#include <gui/SurfaceComposerClient.h>
#endif
@@ -1955,7 +1956,7 @@
ALOGD("dispatchEventToCurrentInputTargets");
}
- updateInteractionTokensLocked(*eventEntry, inputTargets);
+ processInteractionsLocked(*eventEntry, inputTargets);
ALOG_ASSERT(eventEntry->dispatchInProgress); // should already have been set to true
@@ -2579,11 +2580,6 @@
if (entry.injectionState != nullptr) {
std::string errs;
for (const TouchedWindow& touchedWindow : tempTouchState.windows) {
- if (touchedWindow.targetFlags.test(InputTarget::Flags::DISPATCH_AS_OUTSIDE)) {
- // Allow ACTION_OUTSIDE events generated by targeted injection to be
- // dispatched to any uid, since the coords will be zeroed out later.
- continue;
- }
const auto err = verifyTargetedInjection(touchedWindow.windowHandle, entry);
if (err) errs += "\n - " + *err;
}
@@ -2636,6 +2632,18 @@
targets);
}
+ // During targeted injection, only allow owned targets to receive events
+ std::erase_if(targets, [&](const InputTarget& target) {
+ LOG_ALWAYS_FATAL_IF(target.windowHandle == nullptr);
+ const auto err = verifyTargetedInjection(target.windowHandle, entry);
+ if (err) {
+ LOG(WARNING) << "Dropping injected event from " << target.windowHandle->getName()
+ << ": " << (*err);
+ return true;
+ }
+ return false;
+ });
+
if (targets.empty()) {
LOG(INFO) << "Dropping event because no targets were found: " << entry.getDescription();
outInjectionResult = InputEventInjectionResult::FAILED;
@@ -2830,6 +2838,7 @@
}
InputTarget inputTarget;
inputTarget.inputChannel = inputChannel;
+ inputTarget.windowHandle = windowHandle;
inputTarget.flags = targetFlags;
inputTarget.globalScaleFactor = windowHandle->getInfo()->globalScaleFactor;
inputTarget.firstDownTimeInTarget = firstDownTimeInTarget;
@@ -2837,7 +2846,7 @@
if (displayInfoIt != mDisplayInfos.end()) {
inputTarget.displayTransform = displayInfoIt->second.transform;
} else {
- // DisplayInfo not found for this window on display windowInfo->displayId.
+ // DisplayInfo not found for this window on display windowHandle->getInfo()->displayId.
// TODO(b/198444055): Make this an error message after 'setInputWindows' API is removed.
}
return inputTarget;
@@ -3292,11 +3301,8 @@
if (!connection->inputState.trackKey(keyEntry, dispatchEntry->resolvedAction,
dispatchEntry->resolvedFlags)) {
- if (DEBUG_DISPATCH_CYCLE) {
- ALOGD("channel '%s' ~ enqueueDispatchEntryLocked: skipping inconsistent key "
- "event",
- connection->getInputChannelName().c_str());
- }
+ LOG(WARNING) << "channel " << connection->getInputChannelName()
+ << "~ dropping inconsistent event: " << *dispatchEntry;
return; // skip the inconsistent event
}
break;
@@ -3349,11 +3355,8 @@
if (!connection->inputState.trackMotion(motionEntry, dispatchEntry->resolvedAction,
dispatchEntry->resolvedFlags)) {
- if (DEBUG_DISPATCH_CYCLE) {
- ALOGD("channel '%s' ~ enqueueDispatchEntryLocked: skipping inconsistent motion "
- "event",
- connection->getInputChannelName().c_str());
- }
+ LOG(WARNING) << "channel " << connection->getInputChannelName()
+ << "~ dropping inconsistent event: " << *dispatchEntry;
return; // skip the inconsistent event
}
@@ -3408,38 +3411,49 @@
}
/**
- * This function is purely for debugging. It helps us understand where the user interaction
- * was taking place. For example, if user is touching launcher, we will see a log that user
- * started interacting with launcher. In that example, the event would go to the wallpaper as well.
- * We will see both launcher and wallpaper in that list.
- * Once the interaction with a particular set of connections starts, no new logs will be printed
- * until the set of interacted connections changes.
+ * This function is for debugging and metrics collection. It has two roles.
*
- * The following items are skipped, to reduce the logspam:
- * ACTION_OUTSIDE: any windows that are receiving ACTION_OUTSIDE are not logged
- * ACTION_UP: any windows that receive ACTION_UP are not logged (for both keys and motions).
- * This includes situations like the soft BACK button key. When the user releases (lifts up the
- * finger) the back button, then navigation bar will inject KEYCODE_BACK with ACTION_UP.
- * Both of those ACTION_UP events would not be logged
+ * The first role is to log input interaction with windows, which helps determine what the user was
+ * interacting with. For example, if user is touching launcher, we will see an input_interaction log
+ * that user started interacting with launcher window, as well as any other window that received
+ * that gesture, such as the wallpaper or other spy windows. A new input_interaction is only logged
+ * when the set of tokens that received the event changes. It is not logged again as long as the
+ * user is interacting with the same windows.
+ *
+ * The second role is to track input device activity for metrics collection. For each input event,
+ * we report the set of UIDs that the input device interacted with to the policy. Unlike for the
+ * input_interaction logs, the device interaction is reported even when the set of interaction
+ * tokens do not change.
+ *
+ * For these purposes, we do not count ACTION_OUTSIDE, ACTION_UP and ACTION_CANCEL actions as
+ * interaction. This includes up and cancel events for both keys and motions.
*/
-void InputDispatcher::updateInteractionTokensLocked(const EventEntry& entry,
- const std::vector<InputTarget>& targets) {
+void InputDispatcher::processInteractionsLocked(const EventEntry& entry,
+ const std::vector<InputTarget>& targets) {
+ int32_t deviceId;
+ nsecs_t eventTime;
// Skip ACTION_UP events, and all events other than keys and motions
if (entry.type == EventEntry::Type::KEY) {
const KeyEntry& keyEntry = static_cast<const KeyEntry&>(entry);
if (keyEntry.action == AKEY_EVENT_ACTION_UP) {
return;
}
+ deviceId = keyEntry.deviceId;
+ eventTime = keyEntry.eventTime;
} else if (entry.type == EventEntry::Type::MOTION) {
const MotionEntry& motionEntry = static_cast<const MotionEntry&>(entry);
if (motionEntry.action == AMOTION_EVENT_ACTION_UP ||
- motionEntry.action == AMOTION_EVENT_ACTION_CANCEL) {
+ motionEntry.action == AMOTION_EVENT_ACTION_CANCEL ||
+ MotionEvent::getActionMasked(motionEntry.action) == AMOTION_EVENT_ACTION_POINTER_UP) {
return;
}
+ deviceId = motionEntry.deviceId;
+ eventTime = motionEntry.eventTime;
} else {
return; // Not a key or a motion
}
+ std::set<int32_t> interactionUids;
std::unordered_set<sp<IBinder>, StrongPointerHash<IBinder>> newConnectionTokens;
std::vector<std::shared_ptr<Connection>> newConnections;
for (const InputTarget& target : targets) {
@@ -3454,7 +3468,18 @@
}
newConnectionTokens.insert(std::move(token));
newConnections.emplace_back(connection);
+ if (target.windowHandle) {
+ interactionUids.emplace(target.windowHandle->getInfo()->ownerUid);
+ }
}
+
+ auto command = [this, deviceId, eventTime, uids = std::move(interactionUids)]()
+ REQUIRES(mLock) {
+ scoped_unlock unlock(mLock);
+ mPolicy.notifyDeviceInteraction(deviceId, eventTime, uids);
+ };
+ postCommandLocked(std::move(command));
+
if (newConnectionTokens == mInteractionConnectionTokens) {
return; // no change
}
@@ -4112,11 +4137,11 @@
}
}
- if (action == AMOTION_EVENT_ACTION_DOWN) {
- LOG_ALWAYS_FATAL_IF(splitDownTime != originalMotionEntry.eventTime,
- "Split motion event has mismatching downTime and eventTime for "
- "ACTION_DOWN, motionEntry=%s, splitDownTime=%" PRId64,
- originalMotionEntry.getDescription().c_str(), splitDownTime);
+ if (action == AMOTION_EVENT_ACTION_DOWN && splitDownTime != originalMotionEntry.eventTime) {
+ logDispatchStateLocked();
+ LOG_ALWAYS_FATAL("Split motion event has mismatching downTime and eventTime for "
+ "ACTION_DOWN, motionEntry=%s, splitDownTime=%" PRId64,
+ originalMotionEntry.getDescription().c_str(), splitDownTime);
}
int32_t newId = mIdGenerator.nextId();
@@ -4324,6 +4349,18 @@
return;
}
+ if (DEBUG_VERIFY_EVENTS) {
+ auto [it, _] =
+ mVerifiersByDisplay.try_emplace(args.displayId,
+ StringPrintf("display %" PRId32, args.displayId));
+ Result<void> result =
+ it->second.processMovement(args.deviceId, args.action, args.pointerCount,
+ args.pointerProperties, args.pointerCoords, args.flags);
+ if (!result.ok()) {
+ LOG(FATAL) << "Bad stream: " << result.error() << " caused by " << args.dump();
+ }
+ }
+
uint32_t policyFlags = args.policyFlags;
policyFlags |= POLICY_FLAG_TRUSTED;
@@ -6668,6 +6705,7 @@
std::erase(mIneligibleDisplaysForPointerCapture, displayId);
// Remove the associated touch mode state.
mTouchModePerDisplay.erase(displayId);
+ mVerifiersByDisplay.erase(displayId);
} // release lock
// Wake up poll loop since it may need to make new input dispatching choices.
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 6b22f2f..6635df7 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -202,7 +202,7 @@
DropReason mLastDropReason GUARDED_BY(mLock);
- const IdGenerator mIdGenerator;
+ const IdGenerator mIdGenerator GUARDED_BY(mLock);
int64_t mWindowInfosVsyncId GUARDED_BY(mLock);
@@ -447,8 +447,8 @@
// when switching touch mode state).
std::unordered_set<sp<IBinder>, StrongPointerHash<IBinder>> mInteractionConnectionTokens
GUARDED_BY(mLock);
- void updateInteractionTokensLocked(const EventEntry& entry,
- const std::vector<InputTarget>& targets) REQUIRES(mLock);
+ void processInteractionsLocked(const EventEntry& entry, const std::vector<InputTarget>& targets)
+ REQUIRES(mLock);
// Dispatch inbound events.
bool dispatchConfigurationChangedLocked(nsecs_t currentTime,
@@ -649,7 +649,7 @@
// splitDownTime refers to the time of first 'down' event on that particular target
std::unique_ptr<MotionEntry> splitMotionEvent(const MotionEntry& originalMotionEntry,
std::bitset<MAX_POINTER_ID + 1> pointerIds,
- nsecs_t splitDownTime);
+ nsecs_t splitDownTime) REQUIRES(mLock);
// Reset and drop everything the dispatcher is doing.
void resetAndDropEverythingLocked(const char* reason) REQUIRES(mLock);
@@ -683,6 +683,7 @@
const std::string& reason) REQUIRES(mLock);
void updateLastAnrStateLocked(const std::string& windowLabel, const std::string& reason)
REQUIRES(mLock);
+ std::map<int32_t /*displayId*/, InputVerifier> mVerifiersByDisplay;
bool afterKeyEventLockedInterruptable(const std::shared_ptr<Connection>& connection,
DispatchEntry* dispatchEntry, KeyEntry& keyEntry,
bool handled) REQUIRES(mLock);
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index 4652c2d..2fcb89a 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -93,11 +93,7 @@
mMotionMementos.erase(mMotionMementos.begin() + index);
return true;
}
- if (DEBUG_OUTBOUND_EVENT_DETAILS) {
- ALOGD("Dropping inconsistent motion up or cancel event: deviceId=%d, source=%08x, "
- "displayId=%" PRId32 ", actionMasked=%d",
- entry.deviceId, entry.source, entry.displayId, actionMasked);
- }
+
return false;
}
@@ -150,11 +146,7 @@
return true;
}
}
- if (DEBUG_OUTBOUND_EVENT_DETAILS) {
- ALOGD("Dropping inconsistent motion pointer up/down or move event: "
- "deviceId=%d, source=%08x, displayId=%" PRId32 ", actionMasked=%d",
- entry.deviceId, entry.source, entry.displayId, actionMasked);
- }
+
return false;
}
@@ -164,11 +156,7 @@
mMotionMementos.erase(mMotionMementos.begin() + index);
return true;
}
- if (DEBUG_OUTBOUND_EVENT_DETAILS) {
- ALOGD("Dropping inconsistent motion hover exit event: deviceId=%d, source=%08x, "
- "displayId=%" PRId32,
- entry.deviceId, entry.source, entry.displayId);
- }
+
return false;
}
diff --git a/services/inputflinger/dispatcher/InputTarget.h b/services/inputflinger/dispatcher/InputTarget.h
index 7b12f81..3bf8b68 100644
--- a/services/inputflinger/dispatcher/InputTarget.h
+++ b/services/inputflinger/dispatcher/InputTarget.h
@@ -17,6 +17,7 @@
#pragma once
#include <ftl/flags.h>
+#include <gui/WindowInfo.h>
#include <gui/constants.h>
#include <input/InputTransport.h>
#include <ui/Transform.h>
@@ -114,6 +115,10 @@
// Transform per pointerId.
ui::Transform pointerTransforms[MAX_POINTERS];
+ // The window that this input target is being dispatched to. It is possible for this to be
+ // null for cases like global monitors.
+ sp<gui::WindowInfoHandle> windowHandle;
+
void addPointers(std::bitset<MAX_POINTER_ID + 1> pointerIds, const ui::Transform& transform);
void setDefaultPointerTransform(const ui::Transform& transform);
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
index 5539915..69caa99 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
@@ -22,6 +22,7 @@
#include <gui/InputApplication.h>
#include <input/Input.h>
#include <utils/RefBase.h>
+#include <set>
namespace android {
@@ -136,6 +137,10 @@
/* Notifies the policy that the drag window has moved over to another window */
virtual void notifyDropWindow(const sp<IBinder>& token, float x, float y) = 0;
+
+ /* Notifies the policy that there was an input device interaction with apps. */
+ virtual void notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
+ const std::set<int32_t>& uids) = 0;
};
} // namespace android
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index 04747cc..4d0e13e 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -864,6 +864,30 @@
strerror(errno));
}
+void EventHub::populateDeviceAbsoluteAxisInfo(Device& device) {
+ for (int axis = 0; axis <= ABS_MAX; axis++) {
+ if (!device.absBitmask.test(axis)) {
+ continue;
+ }
+ struct input_absinfo info {};
+ if (ioctl(device.fd, EVIOCGABS(axis), &info)) {
+ ALOGE("Error reading absolute controller %d for device %s fd %d, errno=%d", axis,
+ device.identifier.name.c_str(), device.fd, errno);
+ continue;
+ }
+ if (info.minimum == info.maximum) {
+ continue;
+ }
+ RawAbsoluteAxisInfo& outAxisInfo = device.rawAbsoluteAxisInfoCache[axis];
+ outAxisInfo.valid = true;
+ outAxisInfo.minValue = info.minimum;
+ outAxisInfo.maxValue = info.maximum;
+ outAxisInfo.flat = info.flat;
+ outAxisInfo.fuzz = info.fuzz;
+ outAxisInfo.resolution = info.resolution;
+ }
+}
+
InputDeviceIdentifier EventHub::getDeviceIdentifier(int32_t deviceId) const {
std::scoped_lock _l(mLock);
Device* device = getDeviceLocked(deviceId);
@@ -894,31 +918,20 @@
status_t EventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis,
RawAbsoluteAxisInfo* outAxisInfo) const {
outAxisInfo->clear();
-
- if (axis >= 0 && axis <= ABS_MAX) {
- std::scoped_lock _l(mLock);
-
- Device* device = getDeviceLocked(deviceId);
- if (device != nullptr && device->hasValidFd() && device->absBitmask.test(axis)) {
- struct input_absinfo info;
- if (ioctl(device->fd, EVIOCGABS(axis), &info)) {
- ALOGW("Error reading absolute controller %d for device %s fd %d, errno=%d", axis,
- device->identifier.name.c_str(), device->fd, errno);
- return -errno;
- }
-
- if (info.minimum != info.maximum) {
- outAxisInfo->valid = true;
- outAxisInfo->minValue = info.minimum;
- outAxisInfo->maxValue = info.maximum;
- outAxisInfo->flat = info.flat;
- outAxisInfo->fuzz = info.fuzz;
- outAxisInfo->resolution = info.resolution;
- }
- return OK;
- }
+ if (axis < 0 || axis > ABS_MAX) {
+ return -1;
}
- return -1;
+ std::scoped_lock _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device == nullptr) {
+ return -1;
+ }
+ auto it = device->rawAbsoluteAxisInfoCache.find(axis);
+ if (it == device->rawAbsoluteAxisInfoCache.end()) {
+ return -1;
+ }
+ *outAxisInfo = it->second;
+ return OK;
}
bool EventHub::hasRelativeAxis(int32_t deviceId, int axis) const {
@@ -2435,6 +2448,9 @@
device->configureFd();
+ // read absolute axis info for all available axes for the device
+ populateDeviceAbsoluteAxisInfo(*device);
+
ALOGI("New device: id=%d, fd=%d, path='%s', name='%s', classes=%s, "
"configuration='%s', keyLayout='%s', keyCharacterMap='%s', builtinKeyboard=%s, ",
deviceId, fd, devicePath.c_str(), device->identifier.name.c_str(),
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index 20612c7..024187f 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -615,6 +615,7 @@
std::unique_ptr<PropertyMap> configuration;
std::unique_ptr<VirtualKeyMap> virtualKeyMap;
KeyMap keyMap;
+ std::unordered_map<int /*axis*/, RawAbsoluteAxisInfo> rawAbsoluteAxisInfoCache;
bool ffEffectPlaying;
int16_t ffEffectId; // initially -1
@@ -717,6 +718,13 @@
void addDeviceInputInotify();
void addDeviceInotify();
+ /**
+ * AbsoluteAxisInfo remains unchanged for the lifetime of the device, hence
+ * we can read and store it with device
+ * @param device target device
+ */
+ static void populateDeviceAbsoluteAxisInfo(Device& device);
+
// Protect all internal state.
mutable std::mutex mLock;
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index f4d50b8..f2b0a4b 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -1889,9 +1889,9 @@
uint32_t id = mCurrentRawState.rawPointerData.touchingIdBits.firstMarkedBit();
const RawPointerData::Pointer& pointer = mCurrentRawState.rawPointerData.pointerForId(id);
// Skip checking whether the pointer is inside the physical frame if the device is in
- // unscaled mode.
+ // unscaled or pointer mode.
if (!isPointInsidePhysicalFrame(pointer.x, pointer.y) &&
- mDeviceMode != DeviceMode::UNSCALED) {
+ mDeviceMode != DeviceMode::UNSCALED && mDeviceMode != DeviceMode::POINTER) {
// If exactly one pointer went down, check for virtual key hit.
// Otherwise, we will drop the entire stroke.
if (mCurrentRawState.rawPointerData.touchingIdBits.count() == 1) {
diff --git a/services/inputflinger/tests/EventBuilders.h b/services/inputflinger/tests/EventBuilders.h
index 606a57d..09438e9 100644
--- a/services/inputflinger/tests/EventBuilders.h
+++ b/services/inputflinger/tests/EventBuilders.h
@@ -242,6 +242,10 @@
mRawYCursorPosition = pointerCoords[0].getY();
}
+ if (mAction == AMOTION_EVENT_ACTION_CANCEL) {
+ addFlag(AMOTION_EVENT_FLAG_CANCELED);
+ }
+
return {InputEvent::nextId(),
mEventTime,
/*readTime=*/mEventTime,
diff --git a/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp b/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp
index e38f88c..c555d95 100644
--- a/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp
+++ b/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp
@@ -341,8 +341,9 @@
TestInputListener mTestListener;
InputDeviceMetricsCollector mMetricsCollector{mTestListener, *this, USAGE_TIMEOUT};
- void assertUsageLogged(const InputDeviceIdentifier& identifier, nanoseconds duration,
- std::optional<SourceUsageBreakdown> sourceBreakdown = {}) {
+ void assertUsageLogged(InputDeviceIdentifier identifier, nanoseconds duration,
+ std::optional<SourceUsageBreakdown> sourceBreakdown = {},
+ std::optional<UidUsageBreakdown> uidBreakdown = {}) {
ASSERT_GE(mLoggedUsageSessions.size(), 1u);
const auto& [loggedIdentifier, report] = *mLoggedUsageSessions.begin();
ASSERT_EQ(identifier, loggedIdentifier);
@@ -350,6 +351,9 @@
if (sourceBreakdown) {
ASSERT_EQ(sourceBreakdown, report.sourceBreakdown);
}
+ if (uidBreakdown) {
+ ASSERT_EQ(uidBreakdown, report.uidBreakdown);
+ }
mLoggedUsageSessions.erase(mLoggedUsageSessions.begin());
}
@@ -357,6 +361,8 @@
void setCurrentTime(nanoseconds time) { mCurrentTime = time; }
+ nsecs_t currentTime() const { return mCurrentTime.count(); }
+
NotifyMotionArgs generateMotionArgs(int32_t deviceId,
uint32_t source = AINPUT_SOURCE_TOUCHSCREEN,
std::vector<ToolType> toolTypes = {ToolType::FINGER}) {
@@ -622,4 +628,146 @@
ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
}
+TEST_F(InputDeviceMetricsCollectorTest, UidsNotTrackedWhenThereIsNoActiveSession) {
+ mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}});
+
+ // Notify interaction with UIDs before the device is used.
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), /*uids=*/{1});
+
+ // Use the device.
+ setCurrentTime(TIME + 100ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ setCurrentTime(TIME + 200ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+
+ // Notify interaction for the wrong device.
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID_2, currentTime(), /*uids=*/{42});
+
+ // Notify interaction after usage session would have expired.
+ // This interaction should not be tracked.
+ setCurrentTime(TIME + 200ns + USAGE_TIMEOUT);
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), /*uids=*/{2, 3});
+
+ // Use the device again, by starting a new usage session.
+ setCurrentTime(TIME + 300ns + USAGE_TIMEOUT);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+
+ // The first usage session is logged.
+ static const UidUsageBreakdown emptyBreakdown;
+ ASSERT_NO_FATAL_FAILURE(assertUsageLogged(getIdentifier(), 100ns, /*sourceBreakdown=*/{},
+ /*uidBreakdown=*/emptyBreakdown));
+
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+}
+
+TEST_F(InputDeviceMetricsCollectorTest, BreakdownUsageByUid) {
+ mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}});
+ UidUsageBreakdown expectedUidBreakdown;
+
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), /*uids=*/{1});
+
+ setCurrentTime(TIME + 100ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), /*uids=*/{1, 2});
+ setCurrentTime(TIME + 200ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), /*uids=*/{1, 2, 3});
+
+ expectedUidBreakdown.emplace_back(1, 200ns);
+ expectedUidBreakdown.emplace_back(2, 100ns);
+ expectedUidBreakdown.emplace_back(3, 0ns);
+
+ // Remove the device to force the usage session to be logged.
+ mMetricsCollector.notifyInputDevicesChanged({});
+ ASSERT_NO_FATAL_FAILURE(assertUsageLogged(getIdentifier(), 200ns, /*sourceBreakdown=*/{},
+ expectedUidBreakdown));
+
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+}
+
+TEST_F(InputDeviceMetricsCollectorTest, BreakdownUsageByUid_TracksMultipleSessionsForUid) {
+ mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}});
+ UidUsageBreakdown expectedUidBreakdown;
+
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), /*uids=*/{1, 2});
+ setCurrentTime(TIME + 100ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), /*uids=*/{1, 2});
+
+ setCurrentTime(TIME + 200ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), /*uids=*/{1});
+
+ setCurrentTime(TIME + 300ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), /*uids=*/{1, 3});
+ setCurrentTime(TIME + 400ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), /*uids=*/{1, 3});
+
+ setCurrentTime(TIME + 200ns + USAGE_TIMEOUT);
+ expectedUidBreakdown.emplace_back(2, 100ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), /*uids=*/{4});
+
+ setCurrentTime(TIME + 300ns + USAGE_TIMEOUT);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), /*uids=*/{1, 4});
+
+ setCurrentTime(TIME + 400ns + USAGE_TIMEOUT);
+ expectedUidBreakdown.emplace_back(3, 100ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), /*uids=*/{2, 3});
+
+ setCurrentTime(TIME + 500ns + USAGE_TIMEOUT);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), /*uids=*/{3});
+
+ // Remove the device to force the usage session to be logged.
+ mMetricsCollector.notifyInputDevicesChanged({});
+ expectedUidBreakdown.emplace_back(1, 300ns + USAGE_TIMEOUT);
+ expectedUidBreakdown.emplace_back(2, 0ns);
+ expectedUidBreakdown.emplace_back(3, 100ns);
+ expectedUidBreakdown.emplace_back(4, 100ns);
+ ASSERT_NO_FATAL_FAILURE(assertUsageLogged(getIdentifier(), 500ns + USAGE_TIMEOUT,
+ /*sourceBreakdown=*/{}, expectedUidBreakdown));
+
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+}
+
+TEST_F(InputDeviceMetricsCollectorTest, BreakdownUsageByUid_TracksUidsByDevice) {
+ mMetricsCollector.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID), generateTestDeviceInfo(DEVICE_ID_2)}});
+ UidUsageBreakdown expectedUidBreakdown1;
+ UidUsageBreakdown expectedUidBreakdown2;
+
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), /*uids=*/{1, 2});
+
+ setCurrentTime(TIME + 100ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID_2));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID_2, currentTime(), /*uids=*/{1, 3});
+
+ setCurrentTime(TIME + 200ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), /*uids=*/{1, 2});
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID_2));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID_2, currentTime(), /*uids=*/{1, 3});
+
+ setCurrentTime(TIME + 200ns + USAGE_TIMEOUT);
+ expectedUidBreakdown1.emplace_back(1, 200ns);
+ expectedUidBreakdown1.emplace_back(2, 200ns);
+ expectedUidBreakdown2.emplace_back(1, 100ns);
+ expectedUidBreakdown2.emplace_back(3, 100ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ ASSERT_NO_FATAL_FAILURE(assertUsageLogged(getIdentifier(DEVICE_ID), 200ns,
+ /*sourceBreakdown=*/{}, expectedUidBreakdown1));
+ ASSERT_NO_FATAL_FAILURE(assertUsageLogged(getIdentifier(DEVICE_ID_2), 100ns,
+ /*sourceBreakdown=*/{}, expectedUidBreakdown2));
+
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+}
+
} // namespace android
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 6eb63ba..cef1791 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -15,6 +15,7 @@
*/
#include "../dispatcher/InputDispatcher.h"
+#include "../BlockingQueue.h"
#include "EventBuilders.h"
#include <android-base/properties.h>
@@ -59,6 +60,9 @@
static constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT;
static constexpr int32_t SECOND_DISPLAY_ID = 1;
+// Ensure common actions are interchangeable between keys and motions for convenience.
+static_assert(AMOTION_EVENT_ACTION_DOWN == AKEY_EVENT_ACTION_DOWN);
+static_assert(AMOTION_EVENT_ACTION_UP == AKEY_EVENT_ACTION_UP);
static constexpr int32_t ACTION_DOWN = AMOTION_EVENT_ACTION_DOWN;
static constexpr int32_t ACTION_MOVE = AMOTION_EVENT_ACTION_MOVE;
static constexpr int32_t ACTION_UP = AMOTION_EVENT_ACTION_UP;
@@ -413,6 +417,14 @@
ASSERT_FALSE(mPokedUserActivity) << "Expected user activity not to have been poked";
}
+ void assertNotifyDeviceInteractionWasCalled(int32_t deviceId, std::set<int32_t> uids) {
+ ASSERT_EQ(std::make_pair(deviceId, uids), mNotifiedInteractions.popWithTimeout(100ms));
+ }
+
+ void assertNotifyDeviceInteractionWasNotCalled() {
+ ASSERT_FALSE(mNotifiedInteractions.popWithTimeout(10ms));
+ }
+
private:
std::mutex mLock;
std::unique_ptr<InputEvent> mFilteredEvent GUARDED_BY(mLock);
@@ -438,6 +450,8 @@
std::chrono::milliseconds mInterceptKeyTimeout = 0ms;
+ BlockingQueue<std::pair<int32_t /*deviceId*/, std::set<int32_t /*uid*/>>> mNotifiedInteractions;
+
// All three ANR-related callbacks behave the same way, so we use this generic function to wait
// for a specific container to become non-empty. When the container is non-empty, return the
// first entry from the container and erase it.
@@ -609,6 +623,11 @@
mDropTargetWindowToken = token;
}
+ void notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
+ const std::set<int32_t>& uids) override {
+ ASSERT_TRUE(mNotifiedInteractions.emplace(deviceId, uids));
+ }
+
void assertFilterInputEventWasCalledInternal(
const std::function<void(const InputEvent&)>& verify) {
std::scoped_lock lock(mLock);
@@ -1145,12 +1164,29 @@
mInfo.inputConfig = WindowInfo::InputConfig::DEFAULT;
}
- sp<FakeWindowHandle> clone(
- const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle,
- const std::unique_ptr<InputDispatcher>& dispatcher, int32_t displayId) {
- sp<FakeWindowHandle> handle =
- sp<FakeWindowHandle>::make(inputApplicationHandle, dispatcher,
- mInfo.name + "(Mirror)", displayId, mInfo.token);
+ sp<FakeWindowHandle> clone(int32_t displayId) {
+ sp<FakeWindowHandle> handle = sp<FakeWindowHandle>::make(mInfo.name + "(Mirror)");
+ handle->mInfo = mInfo;
+ handle->mInfo.displayId = displayId;
+ handle->mInfo.id = sId++;
+ handle->mInputReceiver = mInputReceiver;
+ return handle;
+ }
+
+ /**
+ * This is different from clone, because clone will make a "mirror" window - a window with the
+ * same token, but a different ID. The original window and the clone window are allowed to be
+ * sent to the dispatcher at the same time - they can coexist inside the dispatcher.
+ * This function will create a different object of WindowInfoHandle, but with the same
+ * properties as the original object - including the ID.
+ * You can use either the old or the new object to consume the events.
+ * IMPORTANT: The duplicated object is supposed to replace the original object, and not appear
+ * at the same time inside dispatcher.
+ */
+ sp<FakeWindowHandle> duplicate() {
+ sp<FakeWindowHandle> handle = sp<FakeWindowHandle>::make(mName);
+ handle->mInfo = mInfo;
+ handle->mInputReceiver = mInputReceiver;
return handle;
}
@@ -1419,9 +1455,11 @@
int getChannelFd() { return mInputReceiver->getChannelFd(); }
private:
+ FakeWindowHandle(std::string name) : mName(name){};
const std::string mName;
- std::unique_ptr<FakeInputReceiver> mInputReceiver;
+ std::shared_ptr<FakeInputReceiver> mInputReceiver;
static std::atomic<int32_t> sId; // each window gets a unique id, like in surfaceflinger
+ friend class sp<FakeWindowHandle>;
};
std::atomic<int32_t> FakeWindowHandle::sId{1};
@@ -3606,6 +3644,9 @@
std::chrono::nanoseconds(interceptKeyTimeout).count());
}
+/**
+ * Keys with ACTION_UP are delivered immediately, even if a long 'intercept key timeout' is set.
+ */
TEST_F(InputDispatcherTest, InterceptKeyIfKeyUp) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
@@ -3617,12 +3658,14 @@
window->consumeFocusEvent(true);
- mFakePolicy->setInterceptKeyTimeout(150ms);
mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
- mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT));
-
- // Window should receive key event immediately when same key up.
window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+
+ // Set a value that's significantly larger than the default consumption timeout. If the
+ // implementation is correct, the actual value doesn't matter; it won't slow down the test.
+ mFakePolicy->setInterceptKeyTimeout(600ms);
+ mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT));
+ // Window should receive key event immediately when same key up.
window->consumeKeyUp(ADISPLAY_ID_DEFAULT);
}
@@ -3893,6 +3936,72 @@
}
/**
+ * Send a two-pointer gesture to a single window. The window's orientation changes in response to
+ * the first pointer.
+ * Ensure that the second pointer is not sent to the window.
+ *
+ * The subsequent gesture should be correctly delivered to the window.
+ */
+TEST_F(InputDispatcherTest, MultiplePointersWithRotatingWindow) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 400, 400));
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+
+ const nsecs_t baseTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .downTime(baseTime + 10)
+ .eventTime(baseTime + 10)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+ .build());
+
+ window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+
+ // We need a new window object for the same window, because dispatcher will store objects by
+ // reference. That means that the testing code and the dispatcher will refer to the same shared
+ // object. Calling window->setTransform here would affect dispatcher's comparison
+ // of the old window to the new window, since both the old window and the new window would be
+ // updated to the same value.
+ sp<FakeWindowHandle> windowDup = window->duplicate();
+
+ // Change the transform so that the orientation is now different from original.
+ windowDup->setWindowTransform(0, -1, 1, 0);
+
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowDup}}});
+
+ window->consumeMotionEvent(WithMotionAction(ACTION_CANCEL));
+
+ mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .downTime(baseTime + 10)
+ .eventTime(baseTime + 30)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+ .pointer(PointerBuilder(1, ToolType::FINGER).x(200).y(200))
+ .build());
+
+ // Finish the gesture and start a new one. Ensure the new gesture is sent to the window
+ mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .downTime(baseTime + 10)
+ .eventTime(baseTime + 40)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+ .pointer(PointerBuilder(1, ToolType::FINGER).x(200).y(200))
+ .build());
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .downTime(baseTime + 10)
+ .eventTime(baseTime + 50)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+ .build());
+
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .downTime(baseTime + 60)
+ .eventTime(baseTime + 60)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(40).y(40))
+ .build());
+
+ windowDup->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+}
+
+/**
* Ensure the correct coordinate spaces are used by InputDispatcher.
*
* InputDispatcher works in the display space, so its coordinate system is relative to the display
@@ -4473,16 +4582,13 @@
sp<FakeWindowHandle>::make(application, mDispatcher, "D_1_W2", ADISPLAY_ID_DEFAULT);
secondWindowInPrimary->setFrame(Rect(100, 0, 200, 100));
- sp<FakeWindowHandle> mirrorWindowInPrimary =
- firstWindowInPrimary->clone(application, mDispatcher, ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> mirrorWindowInPrimary = firstWindowInPrimary->clone(ADISPLAY_ID_DEFAULT);
mirrorWindowInPrimary->setFrame(Rect(0, 100, 100, 200));
- sp<FakeWindowHandle> firstWindowInSecondary =
- firstWindowInPrimary->clone(application, mDispatcher, SECOND_DISPLAY_ID);
+ sp<FakeWindowHandle> firstWindowInSecondary = firstWindowInPrimary->clone(SECOND_DISPLAY_ID);
firstWindowInSecondary->setFrame(Rect(0, 0, 100, 100));
- sp<FakeWindowHandle> secondWindowInSecondary =
- secondWindowInPrimary->clone(application, mDispatcher, SECOND_DISPLAY_ID);
+ sp<FakeWindowHandle> secondWindowInSecondary = secondWindowInPrimary->clone(SECOND_DISPLAY_ID);
secondWindowInPrimary->setFrame(Rect(100, 0, 200, 100));
// Update window info, let it find window handle of second display first.
@@ -4532,16 +4638,13 @@
sp<FakeWindowHandle>::make(application, mDispatcher, "D_1_W2", ADISPLAY_ID_DEFAULT);
secondWindowInPrimary->setFrame(Rect(100, 0, 200, 100));
- sp<FakeWindowHandle> mirrorWindowInPrimary =
- firstWindowInPrimary->clone(application, mDispatcher, ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> mirrorWindowInPrimary = firstWindowInPrimary->clone(ADISPLAY_ID_DEFAULT);
mirrorWindowInPrimary->setFrame(Rect(0, 100, 100, 200));
- sp<FakeWindowHandle> firstWindowInSecondary =
- firstWindowInPrimary->clone(application, mDispatcher, SECOND_DISPLAY_ID);
+ sp<FakeWindowHandle> firstWindowInSecondary = firstWindowInPrimary->clone(SECOND_DISPLAY_ID);
firstWindowInSecondary->setFrame(Rect(0, 0, 100, 100));
- sp<FakeWindowHandle> secondWindowInSecondary =
- secondWindowInPrimary->clone(application, mDispatcher, SECOND_DISPLAY_ID);
+ sp<FakeWindowHandle> secondWindowInSecondary = secondWindowInPrimary->clone(SECOND_DISPLAY_ID);
secondWindowInPrimary->setFrame(Rect(100, 0, 200, 100));
// Update window info, let it find window handle of second display first.
@@ -5486,6 +5589,105 @@
rightDropTouchesWindow->assertNoEvents();
}
+TEST_F(InputDispatcherTest, NotifiesDeviceInteractionsWithMotions) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+ sp<FakeWindowHandle> leftWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+ leftWindow->setFrame(Rect(0, 0, 100, 100));
+ leftWindow->setOwnerInfo(1, 101);
+
+ sp<FakeWindowHandle> rightSpy =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Right spy", ADISPLAY_ID_DEFAULT);
+ rightSpy->setFrame(Rect(100, 0, 200, 100));
+ rightSpy->setOwnerInfo(2, 102);
+ rightSpy->setSpy(true);
+ rightSpy->setTrustedOverlay(true);
+
+ sp<FakeWindowHandle> rightWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+ rightWindow->setFrame(Rect(100, 0, 200, 100));
+ rightWindow->setOwnerInfo(3, 103);
+
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {rightSpy, rightWindow, leftWindow}}});
+
+ // Touch in the left window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+ .build());
+ ASSERT_NO_FATAL_FAILURE(leftWindow->consumeMotionDown());
+ mDispatcher->waitForIdle();
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertNotifyDeviceInteractionWasCalled(DEVICE_ID, {101}));
+
+ // Touch another finger over the right windows
+ mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+ .pointer(PointerBuilder(1, ToolType::FINGER).x(150).y(50))
+ .build());
+ ASSERT_NO_FATAL_FAILURE(rightSpy->consumeMotionDown());
+ ASSERT_NO_FATAL_FAILURE(rightWindow->consumeMotionDown());
+ ASSERT_NO_FATAL_FAILURE(leftWindow->consumeMotionMove());
+ mDispatcher->waitForIdle();
+ ASSERT_NO_FATAL_FAILURE(
+ mFakePolicy->assertNotifyDeviceInteractionWasCalled(DEVICE_ID, {101, 102, 103}));
+
+ // Release finger over left window. The UP actions are not treated as device interaction.
+ // The windows that did not receive the UP pointer will receive MOVE events, but since this
+ // is part of the UP action, we do not treat this as device interaction.
+ mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+ .pointer(PointerBuilder(1, ToolType::FINGER).x(150).y(50))
+ .build());
+ ASSERT_NO_FATAL_FAILURE(leftWindow->consumeMotionUp());
+ ASSERT_NO_FATAL_FAILURE(rightSpy->consumeMotionMove());
+ ASSERT_NO_FATAL_FAILURE(rightWindow->consumeMotionMove());
+ mDispatcher->waitForIdle();
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertNotifyDeviceInteractionWasNotCalled());
+
+ // Move remaining finger
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(1, ToolType::FINGER).x(150).y(50))
+ .build());
+ ASSERT_NO_FATAL_FAILURE(rightSpy->consumeMotionMove());
+ ASSERT_NO_FATAL_FAILURE(rightWindow->consumeMotionMove());
+ mDispatcher->waitForIdle();
+ ASSERT_NO_FATAL_FAILURE(
+ mFakePolicy->assertNotifyDeviceInteractionWasCalled(DEVICE_ID, {102, 103}));
+
+ // Release all fingers
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(1, ToolType::FINGER).x(150).y(50))
+ .build());
+ ASSERT_NO_FATAL_FAILURE(rightSpy->consumeMotionUp());
+ ASSERT_NO_FATAL_FAILURE(rightWindow->consumeMotionUp());
+ mDispatcher->waitForIdle();
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertNotifyDeviceInteractionWasNotCalled());
+}
+
+TEST_F(InputDispatcherTest, NotifiesDeviceInteractionsWithKeys) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 100, 100));
+ window->setOwnerInfo(1, 101);
+
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+ setFocusedWindow(window);
+ ASSERT_NO_FATAL_FAILURE(window->consumeFocusEvent(true));
+
+ mDispatcher->notifyKey(KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).build());
+ ASSERT_NO_FATAL_FAILURE(window->consumeKeyDown(ADISPLAY_ID_DEFAULT));
+ mDispatcher->waitForIdle();
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertNotifyDeviceInteractionWasCalled(DEVICE_ID, {101}));
+
+ // The UP actions are not treated as device interaction.
+ mDispatcher->notifyKey(KeyArgsBuilder(ACTION_UP, AINPUT_SOURCE_KEYBOARD).build());
+ ASSERT_NO_FATAL_FAILURE(window->consumeKeyUp(ADISPLAY_ID_DEFAULT));
+ mDispatcher->waitForIdle();
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertNotifyDeviceInteractionWasNotCalled());
+}
+
class InputDispatcherKeyRepeatTest : public InputDispatcherTest {
protected:
static constexpr nsecs_t KEY_REPEAT_TIMEOUT = 40 * 1000000; // 40 ms
@@ -6881,6 +7083,55 @@
mWindow->assertNoEvents();
}
+/**
+ * Send an event to the app and have the app not respond right away.
+ * When ANR is raised, policy will tell the dispatcher to cancel the events for that window.
+ * So InputDispatcher will enqueue ACTION_CANCEL event as well.
+ * At some point, the window becomes responsive again.
+ * Ensure that subsequent events get dropped, and the next gesture is delivered.
+ */
+TEST_F(InputDispatcherSingleWindowAnr, TwoGesturesWithAnr) {
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(10).y(10))
+ .build());
+
+ std::optional<uint32_t> sequenceNum = mWindow->receiveEvent(); // ACTION_DOWN
+ ASSERT_TRUE(sequenceNum);
+ const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
+ mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow);
+
+ mWindow->finishEvent(*sequenceNum);
+ mWindow->consumeMotionEvent(WithMotionAction(ACTION_CANCEL));
+ ASSERT_TRUE(mDispatcher->waitForIdle());
+ mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid());
+
+ // Now that the window is responsive, let's continue the gesture.
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(11).y(11))
+ .build());
+
+ mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(11).y(11))
+ .pointer(PointerBuilder(1, ToolType::FINGER).x(3).y(3))
+ .build());
+
+ mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(11).y(11))
+ .pointer(PointerBuilder(1, ToolType::FINGER).x(3).y(3))
+ .build());
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(11).y(11))
+ .build());
+ // We already canceled this pointer, so the window shouldn't get any new events.
+ mWindow->assertNoEvents();
+
+ // Start another one.
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(15).y(15))
+ .build());
+ mWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+}
+
class InputDispatcherMultiWindowAnr : public InputDispatcherTest {
virtual void SetUp() override {
InputDispatcherTest::SetUp();
@@ -8478,6 +8729,8 @@
mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
+ mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT));
window->assertNoEvents();
// With the flag cleared, the window should get input
@@ -8519,6 +8772,8 @@
mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
+ mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT));
window->assertNoEvents();
// With the flag cleared, the window should get input
@@ -8560,6 +8815,8 @@
mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
+ mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT));
window->assertNoEvents();
// When the window is no longer obscured because it went on top, it should get input
@@ -9531,7 +9788,7 @@
window->assertNoEvents();
}
-TEST_F(InputDispatcherTargetedInjectionTest, CanGenerateActionOutsideToOtherUids) {
+TEST_F(InputDispatcherTargetedInjectionTest, CannotGenerateActionOutsideToOtherUids) {
auto owner = User(mDispatcher, 10, 11);
auto window = owner.createWindow();
@@ -9541,11 +9798,11 @@
randosWindow->setWatchOutsideTouch(true);
mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {randosWindow, window}}});
- // We allow generation of ACTION_OUTSIDE events into windows owned by different uids.
+ // Do not allow generation of ACTION_OUTSIDE events into windows owned by different uids.
EXPECT_EQ(InputEventInjectionResult::SUCCEEDED,
owner.injectTargetedMotion(AMOTION_EVENT_ACTION_DOWN));
window->consumeMotionDown();
- randosWindow->consumeMotionOutside();
+ randosWindow->assertNoEvents();
}
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 327513d..b0d6bd3 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -5903,6 +5903,40 @@
mFakeListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP)));
}
+TEST_F(SingleTouchInputMapperTest, Process_DoesntCheckPhysicalFrameForTouchpads) {
+ std::shared_ptr<FakePointerController> fakePointerController =
+ std::make_shared<FakePointerController>();
+ mFakePolicy->setPointerController(fakePointerController);
+
+ addConfigurationProperty("touch.deviceType", "pointer");
+ prepareAxes(POSITION);
+ prepareDisplay(ui::ROTATION_0);
+ auto& mapper = constructAndAddMapper<SingleTouchInputMapper>();
+
+ // Set a physical frame in the display viewport.
+ auto viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
+ viewport->physicalLeft = 20;
+ viewport->physicalTop = 600;
+ viewport->physicalRight = 30;
+ viewport->physicalBottom = 610;
+ mFakePolicy->updateViewport(*viewport);
+ configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
+
+ // Start the touch.
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_TOUCH, 1);
+ processSync(mapper);
+
+ // Expect all input starting outside the physical frame to result in NotifyMotionArgs being
+ // produced.
+ const std::array<Point, 6> outsidePoints = {
+ {{0, 0}, {19, 605}, {31, 605}, {25, 599}, {25, 611}, {DISPLAY_WIDTH, DISPLAY_HEIGHT}}};
+ for (const auto& p : outsidePoints) {
+ processMove(mapper, toRawX(p.x), toRawY(p.y));
+ processSync(mapper);
+ EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled());
+ }
+}
+
TEST_F(SingleTouchInputMapperTest, Process_AllAxes_DefaultCalibration) {
addConfigurationProperty("touch.deviceType", "touchScreen");
prepareDisplay(ui::ROTATION_0);
diff --git a/services/inputflinger/tests/fuzzers/Android.bp b/services/inputflinger/tests/fuzzers/Android.bp
index 55c2db6..47b0824 100644
--- a/services/inputflinger/tests/fuzzers/Android.bp
+++ b/services/inputflinger/tests/fuzzers/Android.bp
@@ -21,52 +21,35 @@
default_applicable_licenses: ["frameworks_native_license"],
}
-cc_fuzz {
- name: "inputflinger_latencytracker_fuzzer",
- defaults: [
- "inputflinger_defaults",
- ],
- include_dirs: [
- "frameworks/native/services/inputflinger",
- ],
- shared_libs: [
- "libbase",
- "libbinder",
- "liblog",
- "libutils",
- "libinput",
- "libinputflinger",
- ],
- srcs: [
- "LatencyTrackerFuzzer.cpp",
- ],
- fuzz_config: {
- cc: ["android-framework-input@google.com"],
- },
-}
-
cc_defaults {
name: "inputflinger_fuzz_defaults",
defaults: [
"inputflinger_defaults",
+ "libinputflinger_defaults",
],
+ host_supported: true,
include_dirs: [
"frameworks/native/services/inputflinger",
],
shared_libs: [
- "android.hardware.input.classifier@1.0",
- "android.hardware.input.processor-V1-ndk",
- "libbase",
- "libbinder",
- "libcutils",
- "liblog",
- "libutils",
- "libinput",
- "libinputflinger",
"libinputreader",
"libinputflinger_base",
- "libstatslog",
],
+ sanitize: {
+ hwaddress: true,
+ undefined: true,
+ all_undefined: true,
+ diag: {
+ undefined: true,
+ },
+ },
+ target: {
+ host: {
+ sanitize: {
+ address: true,
+ },
+ },
+ },
header_libs: [
"libbatteryservice_headers",
"libinputreader_headers",
@@ -145,3 +128,17 @@
"InputClassifierFuzzer.cpp",
],
}
+
+cc_fuzz {
+ name: "inputflinger_latencytracker_fuzzer",
+ defaults: [
+ "inputflinger_fuzz_defaults",
+ "libinputdispatcher_defaults",
+ ],
+ shared_libs: [
+ "libinputreporter",
+ ],
+ srcs: [
+ "LatencyTrackerFuzzer.cpp",
+ ],
+}
diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp
index 398d602..90d7541 100644
--- a/services/sensorservice/SensorService.cpp
+++ b/services/sensorservice/SensorService.cpp
@@ -1055,7 +1055,12 @@
if (count < 0) {
if(count == DEAD_OBJECT && device.isReconnecting()) {
device.reconnect();
- continue;
+ // There are no "real" events at this point, but do not skip the rest of the loop
+ // if there are pending runtime events.
+ Mutex::Autolock _l(&mLock);
+ if (mRuntimeSensorEventQueue.empty()) {
+ continue;
+ }
} else {
ALOGE("sensor poll failed (%s)", strerror(-count));
break;
diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
index cd9515c..c9eb9c4 100644
--- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
@@ -28,6 +28,14 @@
using namespace ftl::flag_operators;
+namespace {
+// Returns true if the layer is root of a display and can be mirrored by mirroringLayer
+bool canMirrorRootLayer(RequestedLayerState& mirroringLayer, RequestedLayerState& rootLayer) {
+ return rootLayer.isRoot() && rootLayer.layerStack == mirroringLayer.layerStackToMirror &&
+ rootLayer.id != mirroringLayer.id;
+}
+} // namespace
+
void LayerLifecycleManager::addLayers(std::vector<std::unique_ptr<RequestedLayerState>> newLayers) {
if (newLayers.empty()) {
return;
@@ -46,11 +54,15 @@
layer.parentId = linkLayer(layer.parentId, layer.id);
layer.relativeParentId = linkLayer(layer.relativeParentId, layer.id);
if (layer.layerStackToMirror != ui::INVALID_LAYER_STACK) {
+ // Set mirror layer's default layer stack to -1 so it doesn't end up rendered on a
+ // display accidentally.
+ layer.layerStack = ui::INVALID_LAYER_STACK;
+
// if this layer is mirroring a display, then walk though all the existing root layers
// for the layer stack and add them as children to be mirrored.
mDisplayMirroringLayers.emplace_back(layer.id);
for (auto& rootLayer : mLayers) {
- if (rootLayer->isRoot() && rootLayer->layerStack == layer.layerStackToMirror) {
+ if (canMirrorRootLayer(layer, *rootLayer)) {
layer.mirrorIds.emplace_back(rootLayer->id);
linkLayer(rootLayer->id, layer.id);
}
@@ -383,10 +395,9 @@
// and updates its list of layers that its mirroring. This function should be called when a new
// root layer is added, removed or moved to another display.
void LayerLifecycleManager::updateDisplayMirrorLayers(RequestedLayerState& rootLayer) {
- for (uint32_t mirrorLayerId : mDisplayMirroringLayers) {
- RequestedLayerState* mirrorLayer = getLayerFromId(mirrorLayerId);
- bool canBeMirrored =
- rootLayer.isRoot() && rootLayer.layerStack == mirrorLayer->layerStackToMirror;
+ for (uint32_t mirroringLayerId : mDisplayMirroringLayers) {
+ RequestedLayerState* mirrorLayer = getLayerFromId(mirroringLayerId);
+ bool canBeMirrored = canMirrorRootLayer(*mirrorLayer, rootLayer);
bool currentlyMirrored =
std::find(mirrorLayer->mirrorIds.begin(), mirrorLayer->mirrorIds.end(),
rootLayer.id) != mirrorLayer->mirrorIds.end();
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index 96ff70c..7213ffa 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -31,6 +31,7 @@
#include "LayerLog.h"
#include "LayerSnapshotBuilder.h"
#include "TimeStats/TimeStats.h"
+#include "Tracing/TransactionTracing.h"
namespace android::surfaceflinger::frontend {
@@ -435,13 +436,13 @@
// multiple children.
LayerHierarchy::ScopedAddToTraversalPath addChildToPath(root, args.root.getLayer()->id,
LayerHierarchy::Variant::Attached);
- updateSnapshotsInHierarchy(args, args.root, root, mRootSnapshot);
+ updateSnapshotsInHierarchy(args, args.root, root, mRootSnapshot, /*depth=*/0);
} else {
for (auto& [childHierarchy, variant] : args.root.mChildren) {
LayerHierarchy::ScopedAddToTraversalPath addChildToPath(root,
childHierarchy->getLayer()->id,
variant);
- updateSnapshotsInHierarchy(args, *childHierarchy, root, mRootSnapshot);
+ updateSnapshotsInHierarchy(args, *childHierarchy, root, mRootSnapshot, /*depth=*/0);
}
}
@@ -494,7 +495,15 @@
const LayerSnapshot& LayerSnapshotBuilder::updateSnapshotsInHierarchy(
const Args& args, const LayerHierarchy& hierarchy,
- LayerHierarchy::TraversalPath& traversalPath, const LayerSnapshot& parentSnapshot) {
+ LayerHierarchy::TraversalPath& traversalPath, const LayerSnapshot& parentSnapshot,
+ int depth) {
+ if (depth > 50) {
+ TransactionTraceWriter::getInstance().invoke("layer_builder_stack_overflow_",
+ /*overwrite=*/false);
+ LOG_ALWAYS_FATAL("Cycle detected in LayerSnapshotBuilder. See "
+ "builder_stack_overflow_transactions.winscope");
+ }
+
const RequestedLayerState* layer = hierarchy.getLayer();
LayerSnapshot* snapshot = getSnapshot(traversalPath);
const bool newSnapshot = snapshot == nullptr;
@@ -517,7 +526,8 @@
childHierarchy->getLayer()->id,
variant);
const LayerSnapshot& childSnapshot =
- updateSnapshotsInHierarchy(args, *childHierarchy, traversalPath, *snapshot);
+ updateSnapshotsInHierarchy(args, *childHierarchy, traversalPath, *snapshot,
+ depth + 1);
updateChildState(*snapshot, childSnapshot, args);
}
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
index 3f33ab8..2e46dc6 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
@@ -96,7 +96,7 @@
const LayerSnapshot& updateSnapshotsInHierarchy(const Args&, const LayerHierarchy& hierarchy,
LayerHierarchy::TraversalPath& traversalPath,
- const LayerSnapshot& parentSnapshot);
+ const LayerSnapshot& parentSnapshot, int depth);
void updateSnapshot(LayerSnapshot&, const Args&, const RequestedLayerState&,
const LayerSnapshot& parentSnapshot, const LayerHierarchy::TraversalPath&);
static void updateRelativeState(LayerSnapshot& snapshot, const LayerSnapshot& parentSnapshot,
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index 23bb54c..bde2d05 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -131,6 +131,7 @@
const uint32_t oldFlags = flags;
const half oldAlpha = color.a;
const bool hadBuffer = externalTexture != nullptr;
+ uint64_t oldFramenumber = hadBuffer ? bufferData->frameNumber : 0;
const bool hadSideStream = sidebandStream != nullptr;
const layer_state_t& clientState = resolvedComposerState.state;
const bool hadBlur = hasBlur();
@@ -147,12 +148,9 @@
changes |= RequestedLayerState::Changes::Geometry;
}
}
+
if (clientState.what & layer_state_t::eBufferChanged) {
externalTexture = resolvedComposerState.externalTexture;
- barrierProducerId = std::max(bufferData->producerId, barrierProducerId);
- barrierFrameNumber = std::max(bufferData->frameNumber, barrierFrameNumber);
- // TODO(b/277265947) log and flush transaction trace when we detect out of order updates
-
const bool hasBuffer = externalTexture != nullptr;
if (hasBuffer || hasBuffer != hadBuffer) {
changes |= RequestedLayerState::Changes::Buffer;
@@ -163,6 +161,28 @@
RequestedLayerState::Changes::VisibleRegion |
RequestedLayerState::Changes::Visibility | RequestedLayerState::Changes::Input;
}
+
+ if (hasBuffer) {
+ const bool frameNumberChanged =
+ bufferData->flags.test(BufferData::BufferDataChange::frameNumberChanged);
+ const uint64_t frameNumber =
+ frameNumberChanged ? bufferData->frameNumber : oldFramenumber + 1;
+ bufferData->frameNumber = frameNumber;
+
+ if ((barrierProducerId > bufferData->producerId) ||
+ ((barrierProducerId == bufferData->producerId) &&
+ (barrierFrameNumber > bufferData->frameNumber))) {
+ ALOGE("Out of order buffers detected for %s producedId=%d frameNumber=%" PRIu64
+ " -> producedId=%d frameNumber=%" PRIu64,
+ getDebugString().c_str(), bufferData->producerId, bufferData->frameNumber,
+ bufferData->producerId, frameNumber);
+ TransactionTraceWriter::getInstance().invoke("out_of_order_buffers_",
+ /*overwrite=*/false);
+ }
+
+ barrierProducerId = std::max(bufferData->producerId, barrierProducerId);
+ barrierFrameNumber = std::max(bufferData->frameNumber, barrierFrameNumber);
+ }
}
if (clientState.what & layer_state_t::eSidebandStreamChanged) {
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index f12aab7..fabcd61 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -1349,6 +1349,8 @@
mDrawingState.bufferSurfaceFrameTX =
createSurfaceFrameForBuffer(info, postTime, mTransactionName);
}
+
+ setFrameTimelineVsyncForSkippedFrames(info, postTime, mTransactionName);
}
void Layer::setFrameTimelineVsyncForBufferlessTransaction(const FrameTimelineInfo& info,
@@ -1380,11 +1382,13 @@
it->second = createSurfaceFrameForTransaction(info, postTime);
}
}
+
+ setFrameTimelineVsyncForSkippedFrames(info, postTime, mTransactionName);
}
void Layer::addSurfaceFrameDroppedForBuffer(
- std::shared_ptr<frametimeline::SurfaceFrame>& surfaceFrame) {
- surfaceFrame->setDropTime(systemTime());
+ std::shared_ptr<frametimeline::SurfaceFrame>& surfaceFrame, nsecs_t dropTime) {
+ surfaceFrame->setDropTime(dropTime);
surfaceFrame->setPresentState(PresentState::Dropped);
mFlinger->mFrameTimeline->addSurfaceFrame(surfaceFrame);
}
@@ -1434,6 +1438,32 @@
return surfaceFrame;
}
+void Layer::setFrameTimelineVsyncForSkippedFrames(const FrameTimelineInfo& info, nsecs_t postTime,
+ std::string debugName) {
+ if (info.skippedFrameVsyncId == FrameTimelineInfo::INVALID_VSYNC_ID) {
+ return;
+ }
+
+ FrameTimelineInfo skippedFrameTimelineInfo = info;
+ skippedFrameTimelineInfo.vsyncId = info.skippedFrameVsyncId;
+
+ auto surfaceFrame =
+ mFlinger->mFrameTimeline->createSurfaceFrameForToken(skippedFrameTimelineInfo,
+ mOwnerPid, mOwnerUid,
+ getSequence(), mName, debugName,
+ /*isBuffer*/ false, getGameMode());
+ surfaceFrame->setActualStartTime(skippedFrameTimelineInfo.skippedFrameStartTimeNanos);
+ // For Transactions, the post time is considered to be both queue and acquire fence time.
+ surfaceFrame->setActualQueueTime(postTime);
+ surfaceFrame->setAcquireFenceTime(postTime);
+ const auto fps = mFlinger->mScheduler->getFrameRateOverride(getOwnerUid());
+ if (fps) {
+ surfaceFrame->setRenderRate(*fps);
+ }
+ onSurfaceFrameCreated(surfaceFrame);
+ addSurfaceFrameDroppedForBuffer(surfaceFrame, postTime);
+}
+
bool Layer::setFrameRateForLayerTreeLegacy(FrameRate frameRate) {
if (mDrawingState.frameRateForLayerTree == frameRate) {
return false;
@@ -3067,7 +3097,7 @@
decrementPendingBufferCount();
if (mDrawingState.bufferSurfaceFrameTX != nullptr &&
mDrawingState.bufferSurfaceFrameTX->getPresentState() != PresentState::Presented) {
- addSurfaceFrameDroppedForBuffer(mDrawingState.bufferSurfaceFrameTX);
+ addSurfaceFrameDroppedForBuffer(mDrawingState.bufferSurfaceFrameTX, systemTime());
mDrawingState.bufferSurfaceFrameTX.reset();
}
} else if (EARLY_RELEASE_ENABLED && mLastClientCompositionFence != nullptr) {
@@ -3095,6 +3125,16 @@
return true;
}
+ if ((mDrawingState.producerId > bufferData.producerId) ||
+ ((mDrawingState.producerId == bufferData.producerId) &&
+ (mDrawingState.frameNumber > frameNumber))) {
+ ALOGE("Out of order buffers detected for %s producedId=%d frameNumber=%" PRIu64
+ " -> producedId=%d frameNumber=%" PRIu64,
+ getDebugName(), mDrawingState.producerId, mDrawingState.frameNumber,
+ bufferData.producerId, frameNumber);
+ TransactionTraceWriter::getInstance().invoke("out_of_order_buffers_", /*overwrite=*/false);
+ }
+
mDrawingState.producerId = bufferData.producerId;
mDrawingState.barrierProducerId =
std::max(mDrawingState.producerId, mDrawingState.barrierProducerId);
@@ -3102,7 +3142,6 @@
mDrawingState.barrierFrameNumber =
std::max(mDrawingState.frameNumber, mDrawingState.barrierFrameNumber);
- // TODO(b/277265947) log and flush transaction trace when we detect out of order updates
mDrawingState.releaseBufferListener = bufferData.releaseBufferListener;
mDrawingState.buffer = std::move(buffer);
mDrawingState.acquireFence = bufferData.flags.test(BufferData::BufferDataChange::fenceChanged)
@@ -3599,7 +3638,7 @@
return {inputBounds, inputBoundsValid};
}
-bool Layer::simpleBufferUpdate(const layer_state_t& s) const {
+bool Layer::isSimpleBufferUpdate(const layer_state_t& s) const {
const uint64_t requiredFlags = layer_state_t::eBufferChanged;
const uint64_t deniedFlags = layer_state_t::eProducerDisconnect | layer_state_t::eLayerChanged |
@@ -3608,51 +3647,42 @@
layer_state_t::eLayerStackChanged | layer_state_t::eAutoRefreshChanged |
layer_state_t::eReparent;
- const uint64_t allowedFlags = layer_state_t::eHasListenerCallbacksChanged |
- layer_state_t::eFrameRateSelectionPriority | layer_state_t::eFrameRateChanged |
- layer_state_t::eSurfaceDamageRegionChanged | layer_state_t::eApiChanged |
- layer_state_t::eMetadataChanged | layer_state_t::eDropInputModeChanged |
- layer_state_t::eInputInfoChanged;
-
if ((s.what & requiredFlags) != requiredFlags) {
- ALOGV("%s: false [missing required flags 0x%" PRIx64 "]", __func__,
- (s.what | requiredFlags) & ~s.what);
+ ATRACE_FORMAT_INSTANT("%s: false [missing required flags 0x%" PRIx64 "]", __func__,
+ (s.what | requiredFlags) & ~s.what);
return false;
}
if (s.what & deniedFlags) {
- ALOGV("%s: false [has denied flags 0x%" PRIx64 "]", __func__, s.what & deniedFlags);
+ ATRACE_FORMAT_INSTANT("%s: false [has denied flags 0x%" PRIx64 "]", __func__,
+ s.what & deniedFlags);
return false;
}
- if (s.what & allowedFlags) {
- ALOGV("%s: [has allowed flags 0x%" PRIx64 "]", __func__, s.what & allowedFlags);
- }
-
if (s.what & layer_state_t::ePositionChanged) {
if (mRequestedTransform.tx() != s.x || mRequestedTransform.ty() != s.y) {
- ALOGV("%s: false [ePositionChanged changed]", __func__);
+ ATRACE_FORMAT_INSTANT("%s: false [ePositionChanged changed]", __func__);
return false;
}
}
if (s.what & layer_state_t::eAlphaChanged) {
if (mDrawingState.color.a != s.color.a) {
- ALOGV("%s: false [eAlphaChanged changed]", __func__);
+ ATRACE_FORMAT_INSTANT("%s: false [eAlphaChanged changed]", __func__);
return false;
}
}
if (s.what & layer_state_t::eColorTransformChanged) {
if (mDrawingState.colorTransform != s.colorTransform) {
- ALOGV("%s: false [eColorTransformChanged changed]", __func__);
+ ATRACE_FORMAT_INSTANT("%s: false [eColorTransformChanged changed]", __func__);
return false;
}
}
if (s.what & layer_state_t::eBackgroundColorChanged) {
if (mDrawingState.bgColorLayer || s.bgColor.a != 0) {
- ALOGV("%s: false [eBackgroundColorChanged changed]", __func__);
+ ATRACE_FORMAT_INSTANT("%s: false [eBackgroundColorChanged changed]", __func__);
return false;
}
}
@@ -3662,91 +3692,92 @@
mRequestedTransform.dtdy() != s.matrix.dtdy ||
mRequestedTransform.dtdx() != s.matrix.dtdx ||
mRequestedTransform.dsdy() != s.matrix.dsdy) {
- ALOGV("%s: false [eMatrixChanged changed]", __func__);
+ ATRACE_FORMAT_INSTANT("%s: false [eMatrixChanged changed]", __func__);
return false;
}
}
if (s.what & layer_state_t::eCornerRadiusChanged) {
if (mDrawingState.cornerRadius != s.cornerRadius) {
- ALOGV("%s: false [eCornerRadiusChanged changed]", __func__);
+ ATRACE_FORMAT_INSTANT("%s: false [eCornerRadiusChanged changed]", __func__);
return false;
}
}
if (s.what & layer_state_t::eBackgroundBlurRadiusChanged) {
if (mDrawingState.backgroundBlurRadius != static_cast<int>(s.backgroundBlurRadius)) {
- ALOGV("%s: false [eBackgroundBlurRadiusChanged changed]", __func__);
+ ATRACE_FORMAT_INSTANT("%s: false [eBackgroundBlurRadiusChanged changed]", __func__);
return false;
}
}
if (s.what & layer_state_t::eBufferTransformChanged) {
if (mDrawingState.bufferTransform != s.bufferTransform) {
- ALOGV("%s: false [eBufferTransformChanged changed]", __func__);
+ ATRACE_FORMAT_INSTANT("%s: false [eBufferTransformChanged changed]", __func__);
return false;
}
}
if (s.what & layer_state_t::eTransformToDisplayInverseChanged) {
if (mDrawingState.transformToDisplayInverse != s.transformToDisplayInverse) {
- ALOGV("%s: false [eTransformToDisplayInverseChanged changed]", __func__);
+ ATRACE_FORMAT_INSTANT("%s: false [eTransformToDisplayInverseChanged changed]",
+ __func__);
return false;
}
}
if (s.what & layer_state_t::eCropChanged) {
if (mDrawingState.crop != s.crop) {
- ALOGV("%s: false [eCropChanged changed]", __func__);
+ ATRACE_FORMAT_INSTANT("%s: false [eCropChanged changed]", __func__);
return false;
}
}
if (s.what & layer_state_t::eDataspaceChanged) {
if (mDrawingState.dataspace != s.dataspace) {
- ALOGV("%s: false [eDataspaceChanged changed]", __func__);
+ ATRACE_FORMAT_INSTANT("%s: false [eDataspaceChanged changed]", __func__);
return false;
}
}
if (s.what & layer_state_t::eHdrMetadataChanged) {
if (mDrawingState.hdrMetadata != s.hdrMetadata) {
- ALOGV("%s: false [eHdrMetadataChanged changed]", __func__);
+ ATRACE_FORMAT_INSTANT("%s: false [eHdrMetadataChanged changed]", __func__);
return false;
}
}
if (s.what & layer_state_t::eSidebandStreamChanged) {
if (mDrawingState.sidebandStream != s.sidebandStream) {
- ALOGV("%s: false [eSidebandStreamChanged changed]", __func__);
+ ATRACE_FORMAT_INSTANT("%s: false [eSidebandStreamChanged changed]", __func__);
return false;
}
}
if (s.what & layer_state_t::eColorSpaceAgnosticChanged) {
if (mDrawingState.colorSpaceAgnostic != s.colorSpaceAgnostic) {
- ALOGV("%s: false [eColorSpaceAgnosticChanged changed]", __func__);
+ ATRACE_FORMAT_INSTANT("%s: false [eColorSpaceAgnosticChanged changed]", __func__);
return false;
}
}
if (s.what & layer_state_t::eShadowRadiusChanged) {
if (mDrawingState.shadowRadius != s.shadowRadius) {
- ALOGV("%s: false [eShadowRadiusChanged changed]", __func__);
+ ATRACE_FORMAT_INSTANT("%s: false [eShadowRadiusChanged changed]", __func__);
return false;
}
}
if (s.what & layer_state_t::eFixedTransformHintChanged) {
if (mDrawingState.fixedTransformHint != s.fixedTransformHint) {
- ALOGV("%s: false [eFixedTransformHintChanged changed]", __func__);
+ ATRACE_FORMAT_INSTANT("%s: false [eFixedTransformHintChanged changed]", __func__);
return false;
}
}
if (s.what & layer_state_t::eTrustedOverlayChanged) {
if (mDrawingState.isTrustedOverlay != s.isTrustedOverlay) {
- ALOGV("%s: false [eTrustedOverlayChanged changed]", __func__);
+ ATRACE_FORMAT_INSTANT("%s: false [eTrustedOverlayChanged changed]", __func__);
return false;
}
}
@@ -3755,28 +3786,28 @@
StretchEffect temp = s.stretchEffect;
temp.sanitize();
if (mDrawingState.stretchEffect != temp) {
- ALOGV("%s: false [eStretchChanged changed]", __func__);
+ ATRACE_FORMAT_INSTANT("%s: false [eStretchChanged changed]", __func__);
return false;
}
}
if (s.what & layer_state_t::eBufferCropChanged) {
if (mDrawingState.bufferCrop != s.bufferCrop) {
- ALOGV("%s: false [eBufferCropChanged changed]", __func__);
+ ATRACE_FORMAT_INSTANT("%s: false [eBufferCropChanged changed]", __func__);
return false;
}
}
if (s.what & layer_state_t::eDestinationFrameChanged) {
if (mDrawingState.destinationFrame != s.destinationFrame) {
- ALOGV("%s: false [eDestinationFrameChanged changed]", __func__);
+ ATRACE_FORMAT_INSTANT("%s: false [eDestinationFrameChanged changed]", __func__);
return false;
}
}
if (s.what & layer_state_t::eDimmingEnabledChanged) {
if (mDrawingState.dimmingEnabled != s.dimmingEnabled) {
- ALOGV("%s: false [eDimmingEnabledChanged changed]", __func__);
+ ATRACE_FORMAT_INSTANT("%s: false [eDimmingEnabledChanged changed]", __func__);
return false;
}
}
@@ -3784,12 +3815,11 @@
if (s.what & layer_state_t::eExtendedRangeBrightnessChanged) {
if (mDrawingState.currentHdrSdrRatio != s.currentHdrSdrRatio ||
mDrawingState.desiredHdrSdrRatio != s.desiredHdrSdrRatio) {
- ALOGV("%s: false [eExtendedRangeBrightnessChanged changed]", __func__);
+ ATRACE_FORMAT_INSTANT("%s: false [eExtendedRangeBrightnessChanged changed]", __func__);
return false;
}
}
- ALOGV("%s: true", __func__);
return true;
}
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index f7596e2..2fbbbdc 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -785,8 +785,8 @@
void setFrameTimelineVsyncForBufferlessTransaction(const FrameTimelineInfo& info,
nsecs_t postTime);
- void addSurfaceFrameDroppedForBuffer(
- std::shared_ptr<frametimeline::SurfaceFrame>& surfaceFrame);
+ void addSurfaceFrameDroppedForBuffer(std::shared_ptr<frametimeline::SurfaceFrame>& surfaceFrame,
+ nsecs_t dropTime);
void addSurfaceFramePresentedForBuffer(
std::shared_ptr<frametimeline::SurfaceFrame>& surfaceFrame, nsecs_t acquireFenceTime,
nsecs_t currentLatchTime);
@@ -795,6 +795,8 @@
const FrameTimelineInfo& info, nsecs_t postTime);
std::shared_ptr<frametimeline::SurfaceFrame> createSurfaceFrameForBuffer(
const FrameTimelineInfo& info, nsecs_t queueTime, std::string debugName);
+ void setFrameTimelineVsyncForSkippedFrames(const FrameTimelineInfo& info, nsecs_t postTime,
+ std::string debugName);
bool setTrustedPresentationInfo(TrustedPresentationThresholds const& thresholds,
TrustedPresentationListener const& listener);
@@ -866,7 +868,7 @@
std::string getPendingBufferCounterName() { return mBlastTransactionName; }
bool updateGeometry();
- bool simpleBufferUpdate(const layer_state_t&) const;
+ bool isSimpleBufferUpdate(const layer_state_t& s) const;
static bool isOpaqueFormat(PixelFormat format);
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index c52c912..e0ffb6b 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -909,6 +909,29 @@
ALOGE("Run StartPropertySetThread failed!");
}
+ if (mTransactionTracing) {
+ TransactionTraceWriter::getInstance().setWriterFunction([&](const std::string& prefix,
+ bool overwrite) {
+ auto writeFn = [&]() {
+ const std::string filename =
+ TransactionTracing::DIR_NAME + prefix + TransactionTracing::FILE_NAME;
+ if (overwrite) {
+ std::ifstream file(filename);
+ if (file.is_open()) {
+ return;
+ }
+ }
+ mTransactionTracing->flush();
+ mTransactionTracing->writeToFile(filename);
+ };
+ if (std::this_thread::get_id() == mMainThreadId) {
+ writeFn();
+ } else {
+ mScheduler->schedule(writeFn).get();
+ }
+ });
+ }
+
ALOGV("Done initializing");
}
@@ -4444,26 +4467,27 @@
bool SurfaceFlinger::shouldLatchUnsignaled(const sp<Layer>& layer, const layer_state_t& state,
size_t numStates, bool firstTransaction) const {
if (enableLatchUnsignaledConfig == LatchUnsignaledConfig::Disabled) {
- ALOGV("%s: false (LatchUnsignaledConfig::Disabled)", __func__);
+ ATRACE_FORMAT_INSTANT("%s: false (LatchUnsignaledConfig::Disabled)", __func__);
return false;
}
if (enableLatchUnsignaledConfig == LatchUnsignaledConfig::Always) {
- ALOGV("%s: true (LatchUnsignaledConfig::Always)", __func__);
+ ATRACE_FORMAT_INSTANT("%s: true (LatchUnsignaledConfig::Always)", __func__);
return true;
}
// We only want to latch unsignaled when a single layer is updated in this
// transaction (i.e. not a blast sync transaction).
if (numStates != 1) {
- ALOGV("%s: false (numStates=%zu)", __func__, numStates);
+ ATRACE_FORMAT_INSTANT("%s: false (numStates=%zu)", __func__, numStates);
return false;
}
if (enableLatchUnsignaledConfig == LatchUnsignaledConfig::AutoSingleLayer) {
if (!firstTransaction) {
- ALOGV("%s: false (LatchUnsignaledConfig::AutoSingleLayer; not first transaction)",
- __func__);
+ ATRACE_FORMAT_INSTANT("%s: false (LatchUnsignaledConfig::AutoSingleLayer; not first "
+ "transaction)",
+ __func__);
return false;
}
@@ -4471,19 +4495,14 @@
// as it leads to jank due to RenderEngine waiting for unsignaled buffer
// or window animations being slow.
if (mScheduler->vsyncModulator().isVsyncConfigEarly()) {
- ALOGV("%s: false (LatchUnsignaledConfig::AutoSingleLayer; isVsyncConfigEarly)",
- __func__);
+ ATRACE_FORMAT_INSTANT("%s: false (LatchUnsignaledConfig::AutoSingleLayer; "
+ "isVsyncConfigEarly)",
+ __func__);
return false;
}
}
- if (!layer->simpleBufferUpdate(state)) {
- ALOGV("%s: false (!simpleBufferUpdate)", __func__);
- return false;
- }
-
- ALOGV("%s: true", __func__);
- return true;
+ return layer->isSimpleBufferUpdate(state);
}
status_t SurfaceFlinger::setTransactionState(
@@ -6732,7 +6751,7 @@
mTransactionTracing->setBufferSize(
TransactionTracing::ACTIVE_TRACING_BUFFER_SIZE);
} else {
- mTransactionTracing->writeToFile();
+ TransactionTraceWriter::getInstance().invoke("", /* overwrite= */ true);
mTransactionTracing->setBufferSize(
TransactionTracing::CONTINUOUS_TRACING_BUFFER_SIZE);
}
@@ -7412,6 +7431,13 @@
renderArea->getHintForSeamlessTransition());
sdrWhitePointNits = state.sdrWhitePointNits;
displayBrightnessNits = state.displayBrightnessNits;
+ if (sdrWhitePointNits > 1.0f) {
+ // Restrict the amount of HDR "headroom" in the screenshot to avoid over-dimming
+ // the SDR portion. 2.0 chosen by experimentation
+ constexpr float kMaxScreenshotHeadroom = 2.0f;
+ displayBrightnessNits =
+ std::min(sdrWhitePointNits * kMaxScreenshotHeadroom, displayBrightnessNits);
+ }
if (requestedDataspace == ui::Dataspace::UNKNOWN) {
renderIntent = state.renderIntent;
diff --git a/services/surfaceflinger/TEST_MAPPING b/services/surfaceflinger/TEST_MAPPING
index 155a275..fc6c4f3 100644
--- a/services/surfaceflinger/TEST_MAPPING
+++ b/services/surfaceflinger/TEST_MAPPING
@@ -1,4 +1,9 @@
{
+ "imports": [
+ {
+ "path": "frameworks/native/libs/gui"
+ }
+ ],
"presubmit": [
{
"name": "libsurfaceflinger_unittest"
@@ -7,14 +12,6 @@
"name": "libcompositionengine_test"
},
{
- "name": "libgui_test",
- "options": [
- {
- "native-test-flag": "--gtest_filter=\"InputSurfacesTest*:MultiDisplayTests*\""
- }
- ]
- },
- {
"name": "libscheduler_test"
}
],
diff --git a/services/surfaceflinger/Tracing/TransactionTracing.cpp b/services/surfaceflinger/Tracing/TransactionTracing.cpp
index 632de01..7e330b9 100644
--- a/services/surfaceflinger/Tracing/TransactionTracing.cpp
+++ b/services/surfaceflinger/Tracing/TransactionTracing.cpp
@@ -28,6 +28,7 @@
#include "TransactionTracing.h"
namespace android {
+ANDROID_SINGLETON_STATIC_INSTANCE(android::TransactionTraceWriter)
TransactionTracing::TransactionTracing()
: mProtoParser(std::make_unique<TransactionProtoParser::FlingerDataMapper>()) {
@@ -56,7 +57,7 @@
writeToFile();
}
-status_t TransactionTracing::writeToFile(std::string filename) {
+status_t TransactionTracing::writeToFile(const std::string& filename) {
std::scoped_lock lock(mTraceLock);
proto::TransactionTraceFile fileProto = createTraceFileProto();
addStartingStateToProtoLocked(fileProto);
@@ -115,6 +116,7 @@
}
mPendingUpdates.emplace_back(update);
tryPushToTracingThread();
+ mLastUpdatedVsyncId = vsyncId;
}
void TransactionTracing::loop() {
@@ -218,19 +220,29 @@
mTransactionsAddedToBufferCv.notify_one();
}
-void TransactionTracing::flush(int64_t vsyncId) {
- while (!mPendingUpdates.empty() || !mPendingDestroyedLayers.empty()) {
- tryPushToTracingThread();
+void TransactionTracing::flush() {
+ {
+ std::scoped_lock lock(mMainThreadLock);
+ // Collect any pending transactions and wait for transactions to be added to
+ mUpdates.insert(mUpdates.end(), std::make_move_iterator(mPendingUpdates.begin()),
+ std::make_move_iterator(mPendingUpdates.end()));
+ mPendingUpdates.clear();
+ mDestroyedLayers.insert(mDestroyedLayers.end(), mPendingDestroyedLayers.begin(),
+ mPendingDestroyedLayers.end());
+ mPendingDestroyedLayers.clear();
+ mTransactionsAvailableCv.notify_one();
}
std::unique_lock<std::mutex> lock(mTraceLock);
base::ScopedLockAssertion assumeLocked(mTraceLock);
- mTransactionsAddedToBufferCv.wait(lock, [&]() REQUIRES(mTraceLock) {
- proto::TransactionTraceEntry entry;
- if (mBuffer.used() > 0) {
- entry.ParseFromString(mBuffer.back());
- }
- return mBuffer.used() > 0 && entry.vsync_id() >= vsyncId;
- });
+ mTransactionsAddedToBufferCv.wait_for(lock, std::chrono::milliseconds(100),
+ [&]() REQUIRES(mTraceLock) {
+ proto::TransactionTraceEntry entry;
+ if (mBuffer.used() > 0) {
+ entry.ParseFromString(mBuffer.back());
+ }
+ return mBuffer.used() > 0 &&
+ entry.vsync_id() >= mLastUpdatedVsyncId;
+ });
}
void TransactionTracing::onLayerRemoved(int32_t layerId) {
diff --git a/services/surfaceflinger/Tracing/TransactionTracing.h b/services/surfaceflinger/Tracing/TransactionTracing.h
index 0e56627..fa006fc 100644
--- a/services/surfaceflinger/Tracing/TransactionTracing.h
+++ b/services/surfaceflinger/Tracing/TransactionTracing.h
@@ -19,6 +19,7 @@
#include <android-base/thread_annotations.h>
#include <layerproto/TransactionProto.h>
#include <utils/Errors.h>
+#include <utils/Singleton.h>
#include <utils/Timers.h>
#include <memory>
@@ -60,10 +61,12 @@
void addQueuedTransaction(const TransactionState&);
void addCommittedTransactions(int64_t vsyncId, nsecs_t commitTime, frontend::Update& update,
const frontend::DisplayInfos&, bool displayInfoChanged);
- status_t writeToFile(std::string filename = FILE_NAME);
+ status_t writeToFile(const std::string& filename = FILE_PATH);
void setBufferSize(size_t bufferSizeInBytes);
void onLayerRemoved(int layerId);
void dump(std::string&) const;
+ // Wait until all the committed transactions for the specified vsync id are added to the buffer.
+ void flush() EXCLUDES(mMainThreadLock);
static constexpr auto CONTINUOUS_TRACING_BUFFER_SIZE = 512 * 1024;
static constexpr auto ACTIVE_TRACING_BUFFER_SIZE = 100 * 1024 * 1024;
// version 1 - switching to support new frontend
@@ -73,7 +76,9 @@
friend class TransactionTracingTest;
friend class SurfaceFlinger;
- static constexpr auto FILE_NAME = "/data/misc/wmtrace/transactions_trace.winscope";
+ static constexpr auto DIR_NAME = "/data/misc/wmtrace/";
+ static constexpr auto FILE_NAME = "/transactions_trace.winscope";
+ static constexpr auto FILE_PATH = "/data/misc/wmtrace/transactions_trace.winscope";
mutable std::mutex mTraceLock;
RingBuffer<proto::TransactionTraceFile, proto::TransactionTraceEntry> mBuffer
@@ -111,6 +116,7 @@
std::vector<uint32_t /* layerId */> mDestroyedLayers GUARDED_BY(mMainThreadLock);
std::vector<uint32_t /* layerId */> mPendingDestroyedLayers; // only accessed by main thread
+ int64_t mLastUpdatedVsyncId = -1;
proto::TransactionTraceFile createTraceFileProto() const;
void loop();
@@ -121,10 +127,21 @@
void addStartingStateToProtoLocked(proto::TransactionTraceFile& proto) REQUIRES(mTraceLock);
void updateStartingStateLocked(const proto::TransactionTraceEntry& entry) REQUIRES(mTraceLock);
// TEST
- // Wait until all the committed transactions for the specified vsync id are added to the buffer.
- void flush(int64_t vsyncId) EXCLUDES(mMainThreadLock);
// Return buffer contents as trace file proto
proto::TransactionTraceFile writeToProto() EXCLUDES(mMainThreadLock);
};
+class TransactionTraceWriter : public Singleton<TransactionTraceWriter> {
+ friend class Singleton<TransactionTracing>;
+ std::function<void(const std::string& prefix, bool overwrite)> mWriterFunction =
+ [](const std::string&, bool) {};
+
+public:
+ void setWriterFunction(
+ std::function<void(const std::string& prefix, bool overwrite)> function) {
+ mWriterFunction = std::move(function);
+ }
+ void invoke(const std::string& prefix, bool overwrite) { mWriterFunction(prefix, overwrite); }
+};
+
} // namespace android
diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
index 72a11c6..519ef44 100644
--- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
+++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
@@ -41,7 +41,7 @@
using namespace ftl::flag_operators;
bool LayerTraceGenerator::generate(const proto::TransactionTraceFile& traceFile,
- const char* outputLayersTracePath) {
+ const char* outputLayersTracePath, bool onlyLastEntry) {
if (traceFile.entry_size() == 0) {
ALOGD("Trace file is empty");
return false;
@@ -158,9 +158,11 @@
layerTracing.getFlags())
.generate(hierarchyBuilder.getHierarchy());
auto displayProtos = LayerProtoHelper::writeDisplayInfoToProto(displayInfos);
- layerTracing.notify(visibleRegionsDirty, entry.elapsed_realtime_nanos(), entry.vsync_id(),
- &layersProto, {}, &displayProtos);
- layerTracing.appendToStream(out);
+ if (!onlyLastEntry || (i == traceFile.entry_size() - 1)) {
+ layerTracing.notify(visibleRegionsDirty, entry.elapsed_realtime_nanos(),
+ entry.vsync_id(), &layersProto, {}, &displayProtos);
+ layerTracing.appendToStream(out);
+ }
}
layerTracing.disable("", /*writeToFile=*/false);
out.close();
diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.h b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.h
index ee1ea6c..e41d1e6 100644
--- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.h
+++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.h
@@ -21,6 +21,7 @@
namespace android {
class LayerTraceGenerator {
public:
- bool generate(const proto::TransactionTraceFile&, const char* outputLayersTracePath);
+ bool generate(const proto::TransactionTraceFile&, const char* outputLayersTracePath,
+ bool onlyLastEntry);
};
} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/Tracing/tools/main.cpp b/services/surfaceflinger/Tracing/tools/main.cpp
index c440c19..5ca87e4 100644
--- a/services/surfaceflinger/Tracing/tools/main.cpp
+++ b/services/surfaceflinger/Tracing/tools/main.cpp
@@ -26,9 +26,9 @@
using namespace android;
int main(int argc, char** argv) {
- if (argc > 3) {
+ if (argc > 4) {
std::cout << "Usage: " << argv[0]
- << " [transaction-trace-path] [output-layers-trace-path]\n";
+ << " [transaction-trace-path] [output-layers-trace-path] [--last-entry-only]\n";
return -1;
}
@@ -48,12 +48,16 @@
}
const char* outputLayersTracePath =
- (argc == 3) ? argv[2] : "/data/misc/wmtrace/layers_trace.winscope";
- ;
+ (argc >= 3) ? argv[2] : "/data/misc/wmtrace/layers_trace.winscope";
+
+ const bool generateLastEntryOnly =
+ argc >= 4 && std::string_view(argv[3]) == "--last-entry-only";
+
ALOGD("Generating %s...", outputLayersTracePath);
std::cout << "Generating " << outputLayersTracePath << "\n";
- if (!LayerTraceGenerator().generate(transactionTraceFile, outputLayersTracePath)) {
+ if (!LayerTraceGenerator().generate(transactionTraceFile, outputLayersTracePath,
+ generateLastEntryOnly)) {
std::cout << "Error: Failed to generate layers trace " << outputLayersTracePath;
return -1;
}
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
index 921cae4..9f0bdde 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
@@ -106,7 +106,7 @@
effectLayer->addSurfaceFramePresentedForBuffer(surfaceFrame,
mFdp.ConsumeIntegral<int64_t>() /*acquireTime*/,
mFdp.ConsumeIntegral<int64_t>() /*currentTime*/);
- effectLayer->addSurfaceFrameDroppedForBuffer(surfaceFrame1);
+ effectLayer->addSurfaceFrameDroppedForBuffer(surfaceFrame1, mFdp.ConsumeIntegral<nsecs_t>());
parent.clear();
client.clear();
diff --git a/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp
index 2b29530..b8a5e79 100644
--- a/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp
+++ b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp
@@ -59,8 +59,8 @@
std::string actualLayersTracePath =
std::string(temp_dir.path) + "/" + expectedLayersFilename + "_actual";
- EXPECT_TRUE(
- LayerTraceGenerator().generate(mTransactionTrace, actualLayersTracePath.c_str()))
+ EXPECT_TRUE(LayerTraceGenerator().generate(mTransactionTrace, actualLayersTracePath.c_str(),
+ /*onlyLastEntry=*/true))
<< "Failed to generate layers trace from " << transactionTracePath;
EXPECT_TRUE(std::filesystem::exists(std::filesystem::path(actualLayersTracePath)));
parseLayersTraceFromFile(actualLayersTracePath.c_str(), mActualLayersTraceProto);
@@ -86,9 +86,9 @@
std::vector<std::filesystem::path> TransactionTraceTestSuite::sTransactionTraces{};
struct LayerInfo {
- int32_t id;
+ uint64_t id;
std::string name;
- int32_t parent;
+ uint64_t parent;
int z;
uint64_t curr_frame;
float x;
@@ -119,8 +119,8 @@
}
struct find_id : std::unary_function<LayerInfo, bool> {
- int id;
- find_id(int id) : id(id) {}
+ uint64_t id;
+ find_id(uint64_t id) : id(id) {}
bool operator()(LayerInfo const& m) const { return m.id == id; }
};
@@ -136,9 +136,9 @@
touchableRegionBounds = touchableRegion.bounds();
}
- return {proto.id(),
+ return {static_cast<uint64_t>(proto.id()),
proto.name(),
- proto.parent(),
+ static_cast<uint64_t>(proto.parent()),
proto.z(),
proto.curr_frame(),
proto.has_position() ? proto.position().x() : -1,
@@ -150,7 +150,7 @@
static std::vector<LayerInfo> getLayerInfosFromProto(
android::surfaceflinger::LayersTraceProto& entry) {
- std::unordered_map<int32_t /* snapshotId*/, int32_t /*layerId*/> snapshotIdToLayerId;
+ std::unordered_map<uint64_t /* snapshotId*/, uint64_t /*layerId*/> snapshotIdToLayerId;
std::vector<LayerInfo> layers;
layers.reserve(static_cast<size_t>(entry.layers().layers_size()));
bool mapSnapshotIdToLayerId = false;
@@ -158,7 +158,12 @@
auto layer = entry.layers().layers(i);
LayerInfo layerInfo = getLayerInfoFromProto(layer);
- snapshotIdToLayerId[layerInfo.id] = static_cast<int32_t>(layer.original_id());
+ uint64_t layerId = layerInfo.name.find("(Mirror)") == std::string::npos
+ ? static_cast<uint64_t>(layer.original_id())
+ : static_cast<uint64_t>(layer.original_id()) | 1ull << 63;
+
+ snapshotIdToLayerId[layerInfo.id] = layerId;
+
if (layer.original_id() != 0) {
mapSnapshotIdToLayerId = true;
}
@@ -172,7 +177,7 @@
for (auto& layer : layers) {
layer.id = snapshotIdToLayerId[layer.id];
auto it = snapshotIdToLayerId.find(layer.parent);
- layer.parent = it == snapshotIdToLayerId.end() ? -1 : it->second;
+ layer.parent = it == snapshotIdToLayerId.end() ? static_cast<uint64_t>(-1) : it->second;
}
return layers;
}
@@ -189,7 +194,6 @@
std::vector<LayerInfo> expectedLayers = getLayerInfosFromProto(expectedLastEntry);
std::vector<LayerInfo> actualLayers = getLayerInfosFromProto(actualLastEntry);
- ;
size_t i = 0;
for (; i < actualLayers.size() && i < expectedLayers.size(); i++) {
@@ -197,9 +201,9 @@
find_id(expectedLayers[i].id));
EXPECT_NE(it, actualLayers.end());
EXPECT_EQ(expectedLayers[i], *it);
- ALOGV("Validating %s[%d] parent=%d z=%d frame=%" PRIu64, expectedLayers[i].name.c_str(),
- expectedLayers[i].id, expectedLayers[i].parent, expectedLayers[i].z,
- expectedLayers[i].curr_frame);
+ ALOGV("Validating %s[%" PRIu64 "] parent=%" PRIu64 " z=%d frame=%" PRIu64,
+ expectedLayers[i].name.c_str(), expectedLayers[i].id, expectedLayers[i].parent,
+ expectedLayers[i].z, expectedLayers[i].curr_frame);
}
EXPECT_EQ(expectedLayers.size(), actualLayers.size());
@@ -208,9 +212,9 @@
for (size_t j = 0; j < actualLayers.size(); j++) {
if (std::find_if(expectedLayers.begin(), expectedLayers.end(),
find_id(actualLayers[j].id)) == expectedLayers.end()) {
- ALOGD("actualLayers [%d]:%s parent=%d z=%d frame=%" PRIu64, actualLayers[j].id,
- actualLayers[j].name.c_str(), actualLayers[j].parent, actualLayers[j].z,
- actualLayers[j].curr_frame);
+ ALOGD("actualLayers [%" PRIu64 "]:%s parent=%" PRIu64 " z=%d frame=%" PRIu64,
+ actualLayers[j].id, actualLayers[j].name.c_str(), actualLayers[j].parent,
+ actualLayers[j].z, actualLayers[j].curr_frame);
}
}
FAIL();
@@ -220,9 +224,9 @@
for (size_t j = 0; j < expectedLayers.size(); j++) {
if (std::find_if(actualLayers.begin(), actualLayers.end(),
find_id(expectedLayers[j].id)) == actualLayers.end()) {
- ALOGD("expectedLayers [%d]:%s parent=%d z=%d frame=%" PRIu64, expectedLayers[j].id,
- expectedLayers[j].name.c_str(), expectedLayers[j].parent, expectedLayers[j].z,
- expectedLayers[j].curr_frame);
+ ALOGD("expectedLayers [%" PRIu64 "]:%s parent=%" PRIu64 " z=%d frame=%" PRIu64,
+ expectedLayers[j].id, expectedLayers[j].name.c_str(),
+ expectedLayers[j].parent, expectedLayers[j].z, expectedLayers[j].curr_frame);
}
}
FAIL();
diff --git a/services/surfaceflinger/tests/tracing/readme.md b/services/surfaceflinger/tests/tracing/readme.md
index 3e80a74..f545a3c 100644
--- a/services/surfaceflinger/tests/tracing/readme.md
+++ b/services/surfaceflinger/tests/tracing/readme.md
@@ -14,7 +14,9 @@
#### Workflow ####
Add transaction traces that resulted in front end bugs along
with the layer trace after fixing the issue. The layer trace
-can be generated by using the layertracegenerator tool. The
+can be generated by using the layertracegenerator tool. Use the
+--last-entry-only flag to generate only the last entry in the
+trace. This will keep the test data to a manageable size. The
main goal of this test suite is to add regression tests with
minimal effort.
diff --git a/services/surfaceflinger/tests/tracing/testdata/layers_trace_b282110579.winscope b/services/surfaceflinger/tests/tracing/testdata/layers_trace_b282110579.winscope
new file mode 100644
index 0000000..3246453
--- /dev/null
+++ b/services/surfaceflinger/tests/tracing/testdata/layers_trace_b282110579.winscope
Binary files differ
diff --git a/services/surfaceflinger/tests/tracing/testdata/transactions_trace_b282110579.winscope b/services/surfaceflinger/tests/tracing/testdata/transactions_trace_b282110579.winscope
new file mode 100644
index 0000000..ecb9431
--- /dev/null
+++ b/services/surfaceflinger/tests/tracing/testdata/transactions_trace_b282110579.winscope
Binary files differ
diff --git a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
index 92411a7..809966f 100644
--- a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
@@ -37,7 +37,7 @@
static constexpr size_t SMALL_BUFFER_SIZE = 1024;
TransactionTracing mTracing;
- void flush(int64_t vsyncId) { mTracing.flush(vsyncId); }
+ void flush() { mTracing.flush(); }
proto::TransactionTraceFile writeToProto() { return mTracing.writeToProto(); }
proto::TransactionTraceEntry bufferFront() {
@@ -57,7 +57,7 @@
std::vector<TransactionState> transactions;
update.transactions.emplace_back(transaction);
mTracing.addCommittedTransactions(vsyncId, 0, update, {}, false);
- flush(vsyncId);
+ flush();
}
void verifyEntry(const proto::TransactionTraceEntry& actualProto,
@@ -116,7 +116,7 @@
secondUpdate.transactions =
std::vector<TransactionState>(transactions.begin(), transactions.begin() + 50);
mTracing.addCommittedTransactions(secondTransactionSetVsyncId, 0, secondUpdate, {}, false);
- flush(secondTransactionSetVsyncId);
+ flush();
proto::TransactionTraceFile proto = writeToProto();
ASSERT_EQ(proto.entry().size(), 2);
@@ -158,7 +158,7 @@
VSYNC_ID_FIRST_LAYER_CHANGE = ++mVsyncId;
mTracing.addCommittedTransactions(VSYNC_ID_FIRST_LAYER_CHANGE, 0, update, {}, false);
- flush(VSYNC_ID_FIRST_LAYER_CHANGE);
+ flush();
}
// add transactions that modify the layer state further so we can test that layer state
@@ -178,7 +178,7 @@
update.transactions.emplace_back(transaction);
VSYNC_ID_SECOND_LAYER_CHANGE = ++mVsyncId;
mTracing.addCommittedTransactions(VSYNC_ID_SECOND_LAYER_CHANGE, 0, update, {}, false);
- flush(VSYNC_ID_SECOND_LAYER_CHANGE);
+ flush();
}
// remove child layer
@@ -290,7 +290,7 @@
update.transactions.emplace_back(transaction);
mTracing.addCommittedTransactions(mVsyncId, 0, update, {}, false);
- flush(mVsyncId);
+ flush();
}
}
diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp
index 5965953..af87306 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -877,6 +877,7 @@
int width, height;
int transform_hint;
int max_buffer_count;
+ int min_undequeued_buffers;
if (surface == VK_NULL_HANDLE) {
const InstanceData& instance_data = GetData(physicalDevice);
ProcHook::Extension surfaceless = ProcHook::GOOGLE_surfaceless_query;
@@ -929,17 +930,24 @@
return VK_ERROR_SURFACE_LOST_KHR;
}
+ err = window->query(window, NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
+ &min_undequeued_buffers);
+ if (err != android::OK) {
+ ALOGE("NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS query failed: %s (%d)",
+ strerror(-err), err);
+ return VK_ERROR_SURFACE_LOST_KHR;
+ }
+
if (pPresentMode && IsSharedPresentMode(pPresentMode->presentMode)) {
capabilities->minImageCount = 1;
capabilities->maxImageCount = 1;
} else if (pPresentMode && pPresentMode->presentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
- // TODO: use undequeued buffer requirement for more precise bound
- capabilities->minImageCount = std::min(max_buffer_count, 4);
+ capabilities->minImageCount =
+ std::min(max_buffer_count, min_undequeued_buffers + 2);
capabilities->maxImageCount = static_cast<uint32_t>(max_buffer_count);
} else {
- // TODO: if we're able to, provide better bounds on the number of buffers
- // for other modes as well.
- capabilities->minImageCount = std::min(max_buffer_count, 3);
+ capabilities->minImageCount =
+ std::min(max_buffer_count, min_undequeued_buffers + 1);
capabilities->maxImageCount = static_cast<uint32_t>(max_buffer_count);
}
}