Merge changes I4c65e433,If447795f
* changes:
Add mNamePlusId to hasClientComposition trace
Name the ScreenCaptureOutput
diff --git a/.clang-format b/.clang-format
index 6725a1f..f63f670 100644
--- a/.clang-format
+++ b/.clang-format
@@ -12,3 +12,6 @@
PenaltyBreakBeforeFirstCallParameter: 100000
SpacesBeforeTrailingComments: 1
IncludeBlocks: Preserve
+
+DerivePointerAlignment: false
+PointerAlignment: Left
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 3480d63..c71c4a0 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -19,6 +19,7 @@
libs/gui/
libs/input/
libs/nativedisplay/
+ libs/nativewindow/
libs/renderengine/
libs/ui/
libs/vr/
diff --git a/cmds/atrace/README.md b/cmds/atrace/README.md
new file mode 100644
index 0000000..faa43b2
--- /dev/null
+++ b/cmds/atrace/README.md
@@ -0,0 +1,48 @@
+# Atrace categories
+
+The atrace command (and the perfetto configuration) allow listing **categories**
+to select subsets of events to be traced.
+
+Each category can include some userspace events and some ftrace events.
+
+## Vendor categories
+
+It's possible to extend exiting categories (or to define new categories) from
+the /vendor partition in order to add hardware specific ftrace events.
+
+Since android 14, if the file `/vendor/etc/atrace/atrace_categories.txt`, atrace
+and perfetto will consider the categories and ftrace events listed there.
+
+The file contains a list of categories, and for each category (on the following
+lines, indented with one or more spaces of time), a list of ftrace events that
+should be enabled when the category is enabled.
+
+Each ftrace event should be a subdirectory in `/sys/kernel/tracing/events/` and
+should be of the form `group/event`. Listing a whole group is not supported,
+each event needs to be listed explicitly.
+
+It is not an error if an ftrace event is listed in the file, but not present on
+the tracing file system.
+
+Example:
+
+```
+gfx
+ mali/gpu_power_state
+ mali/mali_pm_status
+thermal_tj
+ thermal_exynos/thermal_cpu_pressure
+ thermal_exynos/thermal_exynos_arm_update
+```
+
+The file lists two categories (`gfx` and `thermal_tj`). When the `gfx` category
+is enabled, atrace (or perfetto) will enable
+`/sys/kernel/tracing/events/mali/gpu_power_state` and
+`/sys/kernel/tracing/events/mali/mali_pm_status`. When the `thermal_tj` category
+is enabled, atrace (or perfetto) will enable
+`/sys/kernel/tracing/events/thermal_exynos/thermal_cpu_pressure` and
+`/sys/kernel/tracing/events/thermal_exynos/thermal_exynos_arm_update`.
+
+Since android 14, if the file `/vendor/etc/atrace/atrace_categories.txt` exists
+on the file system, perfetto and atrace do not query the android.hardware.atrace
+HAL (which is deprecated).
diff --git a/cmds/atrace/atrace.cpp b/cmds/atrace/atrace.cpp
index 48d48ac..8105626 100644
--- a/cmds/atrace/atrace.cpp
+++ b/cmds/atrace/atrace.cpp
@@ -49,6 +49,7 @@
#include <android-base/file.h>
#include <android-base/macros.h>
#include <android-base/properties.h>
+#include <android-base/strings.h>
#include <android-base/stringprintf.h>
using namespace android;
@@ -62,7 +63,7 @@
using std::string;
-#define MAX_SYS_FILES 12
+#define MAX_SYS_FILES 13
const char* k_traceTagsProperty = "debug.atrace.tags.enableflags";
const char* k_userInitiatedTraceProperty = "debug.atrace.user_initiated";
@@ -73,7 +74,9 @@
const char* k_pdxServiceCategory = "pdx";
const char* k_coreServicesProp = "ro.atrace.core.services";
-typedef enum { OPT, REQ } requiredness ;
+const char* kVendorCategoriesPath = "/vendor/etc/atrace/atrace_categories.txt";
+
+typedef enum { OPT, REQ } requiredness;
struct TracingCategory {
// The name identifying the category.
@@ -189,6 +192,8 @@
{ OPT, "events/f2fs/f2fs_sync_file_exit/enable" },
{ OPT, "events/f2fs/f2fs_write_begin/enable" },
{ OPT, "events/f2fs/f2fs_write_end/enable" },
+ { OPT, "events/f2fs/f2fs_iostat/enable" },
+ { OPT, "events/f2fs/f2fs_iostat_latency/enable" },
{ OPT, "events/ext4/ext4_da_write_begin/enable" },
{ OPT, "events/ext4/ext4_da_write_end/enable" },
{ OPT, "events/ext4/ext4_sync_file_enter/enable" },
@@ -253,7 +258,20 @@
} },
};
-struct TracingVendorCategory {
+// A category in the vendor categories file.
+struct TracingVendorFileCategory {
+ // The name identifying the category.
+ std::string name;
+
+ // If the category is enabled through command.
+ bool enabled = false;
+
+ // Paths to the ftrace enable files (relative to g_traceFolder).
+ std::vector<std::string> ftrace_enable_paths;
+};
+
+// A category in the vendor HIDL HAL.
+struct TracingVendorHalCategory {
// The name identifying the category.
std::string name;
@@ -263,11 +281,8 @@
// If the category is enabled through command.
bool enabled;
- TracingVendorCategory(string &&name, string &&description, bool enabled)
- : name(std::move(name))
- , description(std::move(description))
- , enabled(enabled)
- {}
+ TracingVendorHalCategory(string&& name, string&& description, bool enabled)
+ : name(std::move(name)), description(std::move(description)), enabled(enabled) {}
};
/* Command line options */
@@ -287,8 +302,9 @@
static bool g_traceAborted = false;
static bool g_categoryEnables[arraysize(k_categories)] = {};
static std::string g_traceFolder;
+static std::vector<TracingVendorFileCategory> g_vendorFileCategories;
static sp<IAtraceDevice> g_atraceHal;
-static std::vector<TracingVendorCategory> g_vendorCategories;
+static std::vector<TracingVendorHalCategory> g_vendorHalCategories;
/* Sys file paths */
static const char* k_traceClockPath =
@@ -645,6 +661,13 @@
}
}
}
+ for (const TracingVendorFileCategory& c : g_vendorFileCategories) {
+ for (const std::string& path : c.ftrace_enable_paths) {
+ if (fileIsWritable(path.c_str())) {
+ ok &= setKernelOptionEnable(path.c_str(), false);
+ }
+ }
+ }
return ok;
}
@@ -724,7 +747,13 @@
static bool setCategoryEnable(const char* name)
{
bool vendor_found = false;
- for (auto &c : g_vendorCategories) {
+ for (auto& c : g_vendorFileCategories) {
+ if (strcmp(name, c.name.c_str()) == 0) {
+ c.enabled = true;
+ vendor_found = true;
+ }
+ }
+ for (auto& c : g_vendorHalCategories) {
if (strcmp(name, c.name.c_str()) == 0) {
c.enabled = true;
vendor_found = true;
@@ -870,6 +899,16 @@
}
}
+ for (const TracingVendorFileCategory& c : g_vendorFileCategories) {
+ if (c.enabled) {
+ for (const std::string& path : c.ftrace_enable_paths) {
+ if (fileIsWritable(path.c_str())) {
+ ok &= setKernelOptionEnable(path.c_str(), true);
+ }
+ }
+ }
+ }
+
return ok;
}
@@ -1055,7 +1094,10 @@
printf(" %10s - %s\n", c.name, c.longname);
}
}
- for (const auto &c : g_vendorCategories) {
+ for (const auto& c : g_vendorFileCategories) {
+ printf(" %10s - (VENDOR)\n", c.name.c_str());
+ }
+ for (const auto& c : g_vendorHalCategories) {
printf(" %10s - %s (HAL)\n", c.name.c_str(), c.description.c_str());
}
}
@@ -1114,8 +1156,38 @@
return true;
}
-void initVendorCategories()
-{
+void initVendorCategoriesFromFile() {
+ std::ifstream is(kVendorCategoriesPath);
+ for (std::string line; std::getline(is, line);) {
+ if (line.empty()) {
+ continue;
+ }
+ if (android::base::StartsWith(line, ' ') || android::base::StartsWith(line, '\t')) {
+ if (g_vendorFileCategories.empty()) {
+ fprintf(stderr, "Malformed vendor categories file\n");
+ exit(1);
+ return;
+ }
+ std::string_view path = std::string_view(line).substr(1);
+ while (android::base::StartsWith(path, ' ') || android::base::StartsWith(path, '\t')) {
+ path.remove_prefix(1);
+ }
+ if (path.empty()) {
+ continue;
+ }
+ std::string enable_path = "events/";
+ enable_path += path;
+ enable_path += "/enable";
+ g_vendorFileCategories.back().ftrace_enable_paths.push_back(std::move(enable_path));
+ } else {
+ TracingVendorFileCategory cat;
+ cat.name = line;
+ g_vendorFileCategories.push_back(std::move(cat));
+ }
+ }
+}
+
+void initVendorCategoriesFromHal() {
g_atraceHal = IAtraceDevice::getService();
if (g_atraceHal == nullptr) {
@@ -1123,27 +1195,34 @@
return;
}
- Return<void> ret = g_atraceHal->listCategories(
- [](const auto& list) {
- g_vendorCategories.reserve(list.size());
- for (const auto& category : list) {
- g_vendorCategories.emplace_back(category.name, category.description, false);
- }
- });
+ Return<void> ret = g_atraceHal->listCategories([](const auto& list) {
+ g_vendorHalCategories.reserve(list.size());
+ for (const auto& category : list) {
+ g_vendorHalCategories.emplace_back(category.name, category.description, false);
+ }
+ });
if (!ret.isOk()) {
fprintf(stderr, "calling atrace HAL failed: %s\n", ret.description().c_str());
}
}
-static bool setUpVendorTracing()
-{
+void initVendorCategories() {
+ // If kVendorCategoriesPath exists on the filesystem, do not use the HAL.
+ if (access(kVendorCategoriesPath, F_OK) != -1) {
+ initVendorCategoriesFromFile();
+ } else {
+ initVendorCategoriesFromHal();
+ }
+}
+
+static bool setUpVendorTracingWithHal() {
if (g_atraceHal == nullptr) {
// No atrace HAL
return true;
}
std::vector<hidl_string> categories;
- for (const auto &c : g_vendorCategories) {
+ for (const auto& c : g_vendorHalCategories) {
if (c.enabled) {
categories.emplace_back(c.name);
}
@@ -1164,15 +1243,14 @@
return true;
}
-static bool cleanUpVendorTracing()
-{
+static bool cleanUpVendorTracingWithHal() {
if (g_atraceHal == nullptr) {
// No atrace HAL
return true;
}
- if (!g_vendorCategories.size()) {
- // No vendor categories
+ if (!g_vendorHalCategories.size()) {
+ // No vendor HAL categories
return true;
}
@@ -1326,7 +1404,7 @@
if (ok && traceStart && !onlyUserspace) {
ok &= setUpKernelTracing();
- ok &= setUpVendorTracing();
+ ok &= setUpVendorTracingWithHal();
ok &= startTrace();
}
@@ -1397,7 +1475,7 @@
if (traceStop) {
cleanUpUserspaceTracing();
if (!onlyUserspace) {
- cleanUpVendorTracing();
+ cleanUpVendorTracingWithHal();
cleanUpKernelTracing();
}
}
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 69a1df2..fdee3e5 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -74,6 +74,7 @@
#include <android/hidl/manager/1.0/IServiceManager.h>
#include <android/os/IIncidentCompanion.h>
#include <binder/IServiceManager.h>
+#include <cutils/multiuser.h>
#include <cutils/native_handle.h>
#include <cutils/properties.h>
#include <cutils/sockets.h>
@@ -2619,10 +2620,13 @@
return true;
}
-static void SendBroadcast(const std::string& action, const std::vector<std::string>& args) {
+static void SendBroadcast(const std::string& action,
+ const std::vector<std::string>& args,
+ int32_t user_id) {
// clang-format off
- std::vector<std::string> am = {"/system/bin/cmd", "activity", "broadcast", "--user", "0",
- "--receiver-foreground", "--receiver-include-background", "-a", action};
+ std::vector<std::string> am = {"/system/bin/cmd", "activity", "broadcast", "--user",
+ std::to_string(user_id), "--receiver-foreground",
+ "--receiver-include-background", "-a", action};
// clang-format on
am.insert(am.end(), args.begin(), args.end());
@@ -3057,7 +3061,8 @@
};
// clang-format on
// Send STARTED broadcast for apps that listen to bugreport generation events
- SendBroadcast("com.android.internal.intent.action.BUGREPORT_STARTED", am_args);
+ SendBroadcast("com.android.internal.intent.action.BUGREPORT_STARTED",
+ am_args, multiuser_get_user_id(calling_uid));
if (options_->progress_updates_to_socket) {
dprintf(control_socket_fd_, "BEGIN:%s\n", path_.c_str());
}
@@ -3305,7 +3310,7 @@
}
void Dumpstate::onUiIntensiveBugreportDumpsFinished(int32_t calling_uid) {
- if (calling_uid == AID_SHELL || !CalledByApi()) {
+ if (multiuser_get_app_id(calling_uid) == AID_SHELL || !CalledByApi()) {
return;
}
if (listener_ != nullptr) {
@@ -3316,7 +3321,7 @@
}
void Dumpstate::MaybeCheckUserConsent(int32_t calling_uid, const std::string& calling_package) {
- if (calling_uid == AID_SHELL || !CalledByApi()) {
+ if (multiuser_get_app_id(calling_uid) == AID_SHELL || !CalledByApi()) {
// No need to get consent for shell triggered dumpstates, or not through
// bugreporting API (i.e. no fd to copy back).
return;
@@ -3398,7 +3403,7 @@
// If the caller has asked to copy the bugreport over to their directory, we need explicit
// user consent (unless the caller is Shell).
UserConsentResult consent_result;
- if (calling_uid == AID_SHELL) {
+ if (multiuser_get_app_id(calling_uid) == AID_SHELL) {
consent_result = UserConsentResult::APPROVED;
} else {
consent_result = consent_callback_->getResult();
diff --git a/cmds/dumpstate/dumpstate.rc b/cmds/dumpstate/dumpstate.rc
index a80da4e..12a7cff 100644
--- a/cmds/dumpstate/dumpstate.rc
+++ b/cmds/dumpstate/dumpstate.rc
@@ -8,6 +8,7 @@
socket dumpstate stream 0660 shell log
disabled
oneshot
+ capabilities CHOWN DAC_OVERRIDE DAC_READ_SEARCH FOWNER FSETID KILL NET_ADMIN NET_RAW SETGID SETUID SYS_PTRACE SYS_RESOURCE BLOCK_SUSPEND SYSLOG
# dumpstatez generates a zipped bugreport but also uses a socket to print the file location once
# it is finished.
@@ -16,9 +17,11 @@
class main
disabled
oneshot
+ capabilities CHOWN DAC_OVERRIDE DAC_READ_SEARCH FOWNER FSETID KILL NET_ADMIN NET_RAW SETGID SETUID SYS_PTRACE SYS_RESOURCE BLOCK_SUSPEND SYSLOG
# bugreportd starts dumpstate binder service and makes it wait for a listener to connect.
service bugreportd /system/bin/dumpstate -w
class main
disabled
oneshot
+ capabilities CHOWN DAC_OVERRIDE DAC_READ_SEARCH FOWNER FSETID KILL NET_ADMIN NET_RAW SETGID SETUID SYS_PTRACE SYS_RESOURCE BLOCK_SUSPEND SYSLOG
diff --git a/cmds/installd/installd.rc b/cmds/installd/installd.rc
index 240aa49..5b08c77 100644
--- a/cmds/installd/installd.rc
+++ b/cmds/installd/installd.rc
@@ -1,6 +1,7 @@
service installd /system/bin/installd
class main
+ capabilities CHOWN DAC_OVERRIDE DAC_READ_SEARCH FOWNER FSETID KILL SETGID SETUID SYS_ADMIN
on early-boot
mkdir /config/sdcardfs/extensions/1055
diff --git a/data/etc/android.software.ipsec_tunnel_migration.xml b/data/etc/android.software.ipsec_tunnel_migration.xml
new file mode 100644
index 0000000..c405e1e
--- /dev/null
+++ b/data/etc/android.software.ipsec_tunnel_migration.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!--
+ This is the feature indicating that the device has support for updating
+ source and destination addresses of IPsec tunnels
+-->
+
+<permissions>
+ <feature name="android.software.ipsec_tunnel_migration" />
+</permissions>
diff --git a/include/android/input.h b/include/android/input.h
index 5d19c5c..d6f9d63 100644
--- a/include/android/input.h
+++ b/include/android/input.h
@@ -778,6 +778,9 @@
* proportion of the touch pad's size. For example, if a touch pad is 1000 units wide, and a
* swipe gesture starts at X = 500 then moves to X = 400, this axis would have a value of
* -0.1.
+ *
+ * These values are relative to the state from the last event, not accumulated, so developers
+ * should make sure to process this axis value for all batched historical events.
*/
AMOTION_EVENT_AXIS_GESTURE_X_OFFSET = 48,
/**
@@ -786,6 +789,34 @@
* The same as {@link AMOTION_EVENT_AXIS_GESTURE_X_OFFSET}, but for the Y axis.
*/
AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET = 49,
+ /**
+ * Axis constant: X scroll distance axis of a motion event.
+ *
+ * - For a touch pad, reports the distance that should be scrolled in the X axis as a result of
+ * the user's two-finger scroll gesture, in display pixels.
+ *
+ * These values are relative to the state from the last event, not accumulated, so developers
+ * should make sure to process this axis value for all batched historical events.
+ */
+ AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE = 50,
+ /**
+ * Axis constant: Y scroll distance axis of a motion event.
+ *
+ * The same as {@link AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE}, but for the Y axis.
+ */
+ AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE = 51,
+ /**
+ * Axis constant: pinch scale factor of a motion event.
+ *
+ * - For a touch pad, reports the change in distance between the fingers when the user is making
+ * a pinch gesture, as a proportion of that distance when the gesture was last reported. For
+ * example, if the fingers were 50 units apart and are now 52 units apart, the scale factor
+ * would be 1.04.
+ *
+ * These values are relative to the state from the last event, not accumulated, so developers
+ * should make sure to process this axis value for all batched historical events.
+ */
+ AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR = 52,
/**
* Note: This is not an "Axis constant". It does not represent any axis, nor should it be used
@@ -793,7 +824,7 @@
* to make some computations (like iterating through all possible axes) cleaner.
* Please update the value accordingly if you add a new axis.
*/
- AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE = AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET,
+ AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE = AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR,
// NOTE: If you add a new axis here you must also add it to several other files.
// Refer to frameworks/base/core/java/android/view/MotionEvent.java for the full list.
@@ -870,6 +901,21 @@
* The current event stream represents the user swiping with two fingers on a touchpad.
*/
AMOTION_EVENT_CLASSIFICATION_TWO_FINGER_SWIPE = 3,
+ /**
+ * Classification constant: multi-finger swipe.
+ *
+ * The current event stream represents the user swiping with three or more fingers on a
+ * touchpad. Unlike two-finger swipes, these are only to be handled by the system UI, which is
+ * why they have a separate constant from two-finger swipes.
+ */
+ AMOTION_EVENT_CLASSIFICATION_MULTI_FINGER_SWIPE = 4,
+ /**
+ * Classification constant: pinch.
+ *
+ * The current event stream represents the user pinching with two fingers on a touchpad. The
+ * gesture is centered around the current cursor position.
+ */
+ AMOTION_EVENT_CLASSIFICATION_PINCH = 5,
};
/**
diff --git a/include/android/performance_hint.h b/include/android/performance_hint.h
index eed6b33..4a5bd5e 100644
--- a/include/android/performance_hint.h
+++ b/include/android/performance_hint.h
@@ -88,36 +88,6 @@
typedef struct APerformanceHintSession APerformanceHintSession;
/**
- * Hints for the session used by {@link APerformanceHint_sendHint} to signal upcoming changes
- * in the mode or workload.
- */
-enum SessionHint {
- /**
- * This hint indicates a sudden increase in CPU workload intensity. It means
- * that this hint session needs extra CPU resources immediately to meet the
- * target duration for the current work cycle.
- */
- CPU_LOAD_UP = 0,
- /**
- * This hint indicates a decrease in CPU workload intensity. It means that
- * this hint session can reduce CPU resources and still meet the target duration.
- */
- CPU_LOAD_DOWN = 1,
- /*
- * This hint indicates an upcoming CPU workload that is completely changed and
- * unknown. It means that the hint session should reset CPU resources to a known
- * baseline to prepare for an arbitrary load, and must wake up if inactive.
- */
- CPU_LOAD_RESET = 2,
- /*
- * This hint indicates that the most recent CPU workload is resuming after a
- * period of inactivity. It means that the hint session should allocate similar
- * CPU resources to what was used previously, and must wake up if inactive.
- */
- CPU_LOAD_RESUME = 3,
-};
-
-/**
* Acquire an instance of the performance hint manager.
*
* @return manager instance on success, nullptr on failure.
@@ -190,15 +160,21 @@
APerformanceHintSession* session) __INTRODUCED_IN(__ANDROID_API_T__);
/**
- * Sends performance hints to inform the hint session of changes in the workload.
+ * Set a list of threads to the performance hint session. This operation will replace
+ * the current list of threads with the given list of threads.
*
- * @param session The performance hint session instance to update.
- * @param hint The hint to send to the session.
- * @return 0 on success
+ * @param session The performance hint session instance for the threads.
+ * @param threadIds The list of threads to be associated with this session. They must be part of
+ * this app's thread group.
+ * @param size the size of the list of threadIds.
+ * @return 0 on success.
+ * EINVAL if the list of thread ids is empty or if any of the thread ids is not part of the thread group.
* EPIPE if communication with the system service has failed.
*/
-int APerformanceHint_sendHint(
- APerformanceHintSession* session, int hint) __INTRODUCED_IN(__ANDROID_API_U__);
+int APerformanceHint_setThreads(
+ APerformanceHintSession* session,
+ const int32_t* threadIds,
+ size_t size) __INTRODUCED_IN(__ANDROID_API_U__);
__END_DECLS
diff --git a/include/audiomanager/AudioManager.h b/include/audiomanager/AudioManager.h
index 6794fbf..43048db 100644
--- a/include/audiomanager/AudioManager.h
+++ b/include/audiomanager/AudioManager.h
@@ -40,9 +40,17 @@
PLAYER_UPDATE_DEVICE_ID = 5,
PLAYER_UPDATE_PORT_ID = 6,
PLAYER_UPDATE_MUTED = 7,
+ PLAYER_UPDATE_FORMAT = 8,
} player_state_t;
static constexpr char
+ kExtraPlayerEventSpatializedKey[] = "android.media.extra.PLAYER_EVENT_SPATIALIZED";
+static constexpr char
+ kExtraPlayerEventSampleRateKey[] = "android.media.extra.PLAYER_EVENT_SAMPLE_RATE";
+static constexpr char
+ kExtraPlayerEventChannelMaskKey[] = "android.media.extra.PLAYER_EVENT_CHANNEL_MASK";
+
+static constexpr char
kExtraPlayerEventMuteKey[] = "android.media.extra.PLAYER_EVENT_MUTE";
enum {
PLAYER_MUTE_MASTER = (1 << 0),
diff --git a/include/input/Input.h b/include/input/Input.h
index 015efdd..62d84e1 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -203,6 +203,13 @@
*/
vec2 transformWithoutTranslation(const ui::Transform& transform, const vec2& xy);
+/*
+ * Transform an angle on the x-y plane. An angle of 0 radians corresponds to "north" or
+ * pointing upwards in the negative Y direction, a positive angle points towards the right, and a
+ * negative angle points towards the left.
+ */
+float transformAngle(const ui::Transform& transform, float angleRadians);
+
const char* inputEventTypeToString(int32_t type);
std::string inputEventSourceToString(int32_t source);
@@ -295,6 +302,17 @@
* The current gesture represents the user swiping with two fingers on a touchpad.
*/
TWO_FINGER_SWIPE = AMOTION_EVENT_CLASSIFICATION_TWO_FINGER_SWIPE,
+ /**
+ * The current gesture represents the user swiping with three or more fingers on a touchpad.
+ * Unlike two-finger swipes, these are only to be handled by the system UI, which is why they
+ * have a separate constant from two-finger swipes.
+ */
+ MULTI_FINGER_SWIPE = AMOTION_EVENT_CLASSIFICATION_MULTI_FINGER_SWIPE,
+ /**
+ * The current gesture represents the user pinching with two fingers on a touchpad. The gesture
+ * is centered around the current cursor position.
+ */
+ PINCH = AMOTION_EVENT_CLASSIFICATION_PINCH,
};
/**
@@ -361,7 +379,7 @@
* Pointer coordinate data.
*/
struct PointerCoords {
- enum { MAX_AXES = 30 }; // 30 so that sizeof(PointerCoords) == 128
+ enum { MAX_AXES = 30 }; // 30 so that sizeof(PointerCoords) == 136
// Bitfield of axes that are present in this structure.
uint64_t bits __attribute__((aligned(8)));
@@ -370,8 +388,15 @@
// for each axis that is present in the structure according to 'bits'.
std::array<float, MAX_AXES> values;
+ // Whether these coordinate data were generated by resampling.
+ bool isResampled;
+
+ static_assert(sizeof(bool) == 1); // Ensure padding is correctly sized.
+ uint8_t empty[7];
+
inline void clear() {
BitSet64::clear(bits);
+ isResampled = false;
}
bool isEmpty() const {
@@ -762,6 +787,10 @@
AMOTION_EVENT_AXIS_ORIENTATION, pointerIndex, historicalIndex);
}
+ inline bool isResampled(size_t pointerIndex, size_t historicalIndex) const {
+ return getHistoricalRawPointerCoords(pointerIndex, historicalIndex)->isResampled;
+ }
+
ssize_t findPointerIndex(int32_t pointerId) const;
void initialize(int32_t id, int32_t deviceId, uint32_t source, int32_t displayId,
diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h
index e911734..09933d3 100644
--- a/include/input/InputDevice.h
+++ b/include/input/InputDevice.h
@@ -24,7 +24,6 @@
#include <vector>
#include <android/os/IInputConstants.h>
-#include "android/hardware/input/InputDeviceCountryCode.h"
namespace android {
@@ -205,6 +204,16 @@
int32_t id;
};
+struct KeyboardLayoutInfo {
+ explicit KeyboardLayoutInfo(std::string languageTag, std::string layoutType)
+ : languageTag(languageTag), layoutType(layoutType) {}
+
+ // A BCP 47 conformant language tag such as "en-US".
+ std::string languageTag;
+ // The layout type such as QWERTY or AZERTY.
+ std::string layoutType;
+};
+
/*
* Describes the characteristics and capabilities of an input device.
*/
@@ -226,9 +235,7 @@
void initialize(int32_t id, int32_t generation, int32_t controllerNumber,
const InputDeviceIdentifier& identifier, const std::string& alias,
- bool isExternal, bool hasMic,
- hardware::input::InputDeviceCountryCode countryCode =
- hardware::input::InputDeviceCountryCode::INVALID);
+ bool isExternal, bool hasMic);
inline int32_t getId() const { return mId; }
inline int32_t getControllerNumber() const { return mControllerNumber; }
@@ -240,7 +247,6 @@
}
inline bool isExternal() const { return mIsExternal; }
inline bool hasMic() const { return mHasMic; }
- inline hardware::input::InputDeviceCountryCode getCountryCode() const { return mCountryCode; }
inline uint32_t getSources() const { return mSources; }
const MotionRange* getMotionRange(int32_t axis, uint32_t source) const;
@@ -256,6 +262,11 @@
void setKeyboardType(int32_t keyboardType);
inline int32_t getKeyboardType() const { return mKeyboardType; }
+ void setKeyboardLayoutInfo(KeyboardLayoutInfo keyboardLayoutInfo);
+ inline const std::optional<KeyboardLayoutInfo>& getKeyboardLayoutInfo() const {
+ return mKeyboardLayoutInfo;
+ }
+
inline void setKeyCharacterMap(const std::shared_ptr<KeyCharacterMap> value) {
mKeyCharacterMap = value;
}
@@ -295,7 +306,7 @@
std::string mAlias;
bool mIsExternal;
bool mHasMic;
- hardware::input::InputDeviceCountryCode mCountryCode;
+ std::optional<KeyboardLayoutInfo> mKeyboardLayoutInfo;
uint32_t mSources;
int32_t mKeyboardType;
std::shared_ptr<KeyCharacterMap> mKeyCharacterMap;
diff --git a/include/input/KeyCharacterMap.h b/include/input/KeyCharacterMap.h
index 867a089..b5e6f65 100644
--- a/include/input/KeyCharacterMap.h
+++ b/include/input/KeyCharacterMap.h
@@ -87,6 +87,9 @@
/* Combines this key character map with the provided overlay. */
void combine(const KeyCharacterMap& overlay);
+ /* Clears already applied layout overlay */
+ void clearLayoutOverlay();
+
/* Gets the keyboard type. */
KeyboardType getKeyboardType() const;
diff --git a/include/input/MotionPredictor.h b/include/input/MotionPredictor.h
new file mode 100644
index 0000000..045e61b
--- /dev/null
+++ b/include/input/MotionPredictor.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android-base/thread_annotations.h>
+#include <android/sysprop/InputProperties.sysprop.h>
+#include <input/Input.h>
+
+namespace android {
+
+static inline bool isMotionPredictionEnabled() {
+ return sysprop::InputProperties::enable_motion_prediction().value_or(true);
+}
+
+/**
+ * Given a set of MotionEvents for the current gesture, predict the motion. The returned MotionEvent
+ * contains a set of samples in the future, up to "presentation time + offset".
+ *
+ * The typical usage is like this:
+ *
+ * MotionPredictor predictor(offset = MY_OFFSET);
+ * predictor.setExpectedPresentationTimeNanos(NEXT_PRESENT_TIME);
+ * predictor.record(DOWN_MOTION_EVENT);
+ * predictor.record(MOVE_MOTION_EVENT);
+ * prediction = predictor.predict();
+ *
+ * The presentation time should be set some time before calling .predict(). It could be set before
+ * or after the recorded motion events. Must be done on every frame.
+ *
+ * The resulting motion event will have eventTime <= (NEXT_PRESENT_TIME + MY_OFFSET). It might
+ * contain historical data, which are additional samples from the latest recorded MotionEvent's
+ * eventTime to the NEXT_PRESENT_TIME + MY_OFFSET.
+ *
+ * The offset is used to provide additional flexibility to the caller, in case the default present
+ * time (typically provided by the choreographer) does not account for some delays, or to simply
+ * reduce the aggressiveness of the prediction. Offset can be both positive and negative.
+ */
+class MotionPredictor {
+public:
+ /**
+ * Parameters:
+ * predictionTimestampOffsetNanos: additional, constant shift to apply to the target
+ * presentation time. The prediction will target the time t=(presentationTime +
+ * predictionTimestampOffsetNanos).
+ *
+ * checkEnableMotionPredition: the function to check whether the prediction should run. Used to
+ * provide an additional way of turning prediction on and off. Can be toggled at runtime.
+ */
+ MotionPredictor(nsecs_t predictionTimestampOffsetNanos,
+ std::function<bool()> checkEnableMotionPrediction = isMotionPredictionEnabled);
+ void record(const MotionEvent& event);
+ std::vector<std::unique_ptr<MotionEvent>> predict(nsecs_t timestamp);
+ bool isPredictionAvailable(int32_t deviceId, int32_t source);
+
+private:
+ std::vector<MotionEvent> mEvents;
+ const nsecs_t mPredictionTimestampOffsetNanos;
+ const std::function<bool()> mCheckMotionPredictionEnabled;
+};
+
+} // namespace android
diff --git a/include/private/performance_hint_private.h b/include/private/performance_hint_private.h
index f27f5f1..eaf3b5e 100644
--- a/include/private/performance_hint_private.h
+++ b/include/private/performance_hint_private.h
@@ -24,6 +24,51 @@
*/
void APerformanceHint_setIHintManagerForTesting(void* iManager);
+/**
+ * Hints for the session used to signal upcoming changes in the mode or workload.
+ */
+enum SessionHint {
+ /**
+ * This hint indicates a sudden increase in CPU workload intensity. It means
+ * that this hint session needs extra CPU resources immediately to meet the
+ * target duration for the current work cycle.
+ */
+ CPU_LOAD_UP = 0,
+ /**
+ * This hint indicates a decrease in CPU workload intensity. It means that
+ * this hint session can reduce CPU resources and still meet the target duration.
+ */
+ CPU_LOAD_DOWN = 1,
+ /*
+ * This hint indicates an upcoming CPU workload that is completely changed and
+ * unknown. It means that the hint session should reset CPU resources to a known
+ * baseline to prepare for an arbitrary load, and must wake up if inactive.
+ */
+ CPU_LOAD_RESET = 2,
+ /*
+ * This hint indicates that the most recent CPU workload is resuming after a
+ * period of inactivity. It means that the hint session should allocate similar
+ * CPU resources to what was used previously, and must wake up if inactive.
+ */
+ CPU_LOAD_RESUME = 3,
+};
+
+/**
+ * Sends performance hints to inform the hint session of changes in the workload.
+ *
+ * @param session The performance hint session instance to update.
+ * @param hint The hint to send to the session.
+ * @return 0 on success
+ * EPIPE if communication with the system service has failed.
+ */
+int APerformanceHint_sendHint(void* session, int hint);
+
+/**
+ * Return the list of thread ids, this API should only be used for testing only.
+ */
+int APerformanceHint_getThreadIds(void* aPerformanceHintSession,
+ int32_t* const threadIds, size_t* const size);
+
__END_DECLS
#endif // ANDROID_PRIVATE_NATIVE_PERFORMANCE_HINT_PRIVATE_H
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index f17bb7d..6bf7049 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -511,6 +511,7 @@
visibility: [
":__subpackages__",
"//packages/modules/Virtualization:__subpackages__",
+ "//device/google/cuttlefish/shared/minidroid:__subpackages__",
],
}
diff --git a/libs/binder/Binder.cpp b/libs/binder/Binder.cpp
index 5e725a9..da5affb 100644
--- a/libs/binder/Binder.cpp
+++ b/libs/binder/Binder.cpp
@@ -407,8 +407,10 @@
AutoMutex lock(e->mLock);
if (mRecordingOn) {
Parcel emptyReply;
+ timespec ts;
+ timespec_get(&ts, TIME_UTC);
auto transaction =
- android::binder::debug::RecordedTransaction::fromDetails(code, flags, data,
+ android::binder::debug::RecordedTransaction::fromDetails(code, flags, ts, data,
reply ? *reply
: emptyReply,
err);
diff --git a/libs/binder/BinderRecordReplay.cpp b/libs/binder/BinderRecordReplay.cpp
index 90c02a8..58bb106 100644
--- a/libs/binder/BinderRecordReplay.cpp
+++ b/libs/binder/BinderRecordReplay.cpp
@@ -16,10 +16,13 @@
#include <android-base/file.h>
#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
#include <binder/BinderRecordReplay.h>
+#include <sys/mman.h>
#include <algorithm>
using android::Parcel;
+using android::base::borrowed_fd;
using android::base::unique_fd;
using android::binder::debug::RecordedTransaction;
@@ -30,123 +33,278 @@
static_assert(PADDING8(7) == 1);
static_assert(PADDING8(8) == 0);
-// Transactions are sequentially recorded to the file descriptor in the following format:
+// Transactions are sequentially recorded to a file descriptor.
//
-// RecordedTransaction.TransactionHeader (32 bytes)
-// Sent Parcel data (getDataSize() bytes)
-// padding (enough bytes to align the reply Parcel data to 8 bytes)
-// Reply Parcel data (getReplySize() bytes)
-// padding (enough bytes to align the next header to 8 bytes)
-// [repeats with next transaction]
+// An individual RecordedTransaction is written with the following format:
//
-// Warning: This format is non-stable
+// WARNING: Though the following format is designed to be stable and
+// extensible, it is under active development and should be considered
+// unstable until this warning is removed.
+//
+// A RecordedTransaction is written to a file as a sequence of Chunks.
+//
+// A Chunk consists of a ChunkDescriptor, Data, Padding, and a Checksum.
+//
+// The ChunkDescriptor identifies the type of Data in the chunk, and the size
+// of the Data.
+//
+// The Data may be any uint32 number of bytes in length in [0-0xfffffff0].
+//
+// Padding is between [0-7] zero-bytes after the Data such that the Chunk ends
+// on an 8-byte boundary. The ChunkDescriptor's dataSize does not include the
+// size of Padding.
+//
+// The checksum is a 64-bit wide XOR of all previous data from the start of the
+// ChunkDescriptor to the end of Padding.
+//
+// ┌───────────────────────────┐
+// │Chunk │
+// │┌────────────────────────┐ │
+// ││ChunkDescriptor │ │
+// ││┌───────────┬──────────┐│ │
+// │││chunkType │dataSize ├┼─┼─┐
+// │││uint32_t │uint32_t ││ │ │
+// ││└───────────┴──────────┘│ │ │
+// │└────────────────────────┘ │ │
+// │┌─────────────────────────┐│ │
+// ││Data ││ │
+// ││bytes * dataSize │◀─┘
+// ││ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤│
+// ││ Padding ││
+// │└───┴─────────────────────┘│
+// │┌─────────────────────────┐│
+// ││checksum ││
+// ││uint64_t ││
+// │└─────────────────────────┘│
+// └───────────────────────────┘
+//
+// A RecordedTransaction is written as a Header Chunk with fields about the
+// transaction, a Data Parcel chunk, a Reply Parcel Chunk, and an End Chunk.
+// ┌──────────────────────┐
+// │ Header Chunk │
+// ├──────────────────────┤
+// │ Sent Parcel Chunk │
+// ├──────────────────────┤
+// │ Reply Parcel Chunk │
+// ├──────────────────────┤
+// ║ End Chunk ║
+// ╚══════════════════════╝
+//
+// On reading a RecordedTransaction, an unrecognized chunk is checksummed
+// then skipped according to size information in the ChunkDescriptor. Chunks
+// are read and either assimilated or skipped until an End Chunk is
+// encountered. This has three notable implications:
+//
+// 1. Older and newer implementations should be able to read one another's
+// Transactions, though there will be loss of information.
+// 2. With the exception of the End Chunk, Chunks can appear in any order
+// and even repeat, though this is not recommended.
+// 3. If any Chunk is repeated, old values will be overwritten by versions
+// encountered later in the file.
+//
+// No effort is made to ensure the expected chunks are present. A single
+// End Chunk may therefore produce an empty, meaningless RecordedTransaction.
RecordedTransaction::RecordedTransaction(RecordedTransaction&& t) noexcept {
- mHeader = {t.getCode(), t.getFlags(), t.getDataSize(),
- t.getReplySize(), t.getReturnedStatus(), t.getVersion()};
- mSent.setData(t.getDataParcel().data(), t.getDataSize());
- mReply.setData(t.getReplyParcel().data(), t.getReplySize());
+ mHeader = t.mHeader;
+ mSent.setData(t.getDataParcel().data(), t.getDataParcel().dataSize());
+ mReply.setData(t.getReplyParcel().data(), t.getReplyParcel().dataSize());
}
std::optional<RecordedTransaction> RecordedTransaction::fromDetails(uint32_t code, uint32_t flags,
+ timespec timestamp,
const Parcel& dataParcel,
const Parcel& replyParcel,
status_t err) {
RecordedTransaction t;
t.mHeader = {code,
flags,
- static_cast<uint64_t>(dataParcel.dataSize()),
- static_cast<uint64_t>(replyParcel.dataSize()),
static_cast<int32_t>(err),
- dataParcel.isForRpc() ? static_cast<uint32_t>(1) : static_cast<uint32_t>(0)};
+ dataParcel.isForRpc() ? static_cast<uint32_t>(1) : static_cast<uint32_t>(0),
+ static_cast<int64_t>(timestamp.tv_sec),
+ static_cast<int32_t>(timestamp.tv_nsec),
+ 0};
- if (t.mSent.setData(dataParcel.data(), t.getDataSize()) != android::NO_ERROR) {
- LOG(INFO) << "Failed to set sent parcel data.";
+ if (t.mSent.setData(dataParcel.data(), dataParcel.dataSize()) != android::NO_ERROR) {
+ LOG(ERROR) << "Failed to set sent parcel data.";
return std::nullopt;
}
- if (t.mReply.setData(replyParcel.data(), t.getReplySize()) != android::NO_ERROR) {
- LOG(INFO) << "Failed to set reply parcel data.";
+ if (t.mReply.setData(replyParcel.data(), replyParcel.dataSize()) != android::NO_ERROR) {
+ LOG(ERROR) << "Failed to set reply parcel data.";
return std::nullopt;
}
return std::optional<RecordedTransaction>(std::move(t));
}
+enum {
+ HEADER_CHUNK = 1,
+ DATA_PARCEL_CHUNK = 2,
+ REPLY_PARCEL_CHUNK = 3,
+ END_CHUNK = 0x00ffffff,
+};
+
+struct ChunkDescriptor {
+ uint32_t chunkType = 0;
+ uint32_t dataSize = 0;
+};
+static_assert(sizeof(ChunkDescriptor) % 8 == 0);
+
+constexpr uint32_t kMaxChunkDataSize = 0xfffffff0;
+typedef uint64_t transaction_checksum_t;
+
+static android::status_t readChunkDescriptor(borrowed_fd fd, ChunkDescriptor* chunkOut,
+ transaction_checksum_t* sum) {
+ if (!android::base::ReadFully(fd, chunkOut, sizeof(ChunkDescriptor))) {
+ LOG(ERROR) << "Failed to read Chunk Descriptor from fd " << fd.get();
+ return android::UNKNOWN_ERROR;
+ }
+
+ *sum ^= *reinterpret_cast<transaction_checksum_t*>(chunkOut);
+ return android::NO_ERROR;
+}
+
std::optional<RecordedTransaction> RecordedTransaction::fromFile(const unique_fd& fd) {
RecordedTransaction t;
- if (!android::base::ReadFully(fd, &t.mHeader, sizeof(mHeader))) {
- LOG(INFO) << "Failed to read transactionHeader from fd " << fd.get();
- return std::nullopt;
- }
- if (t.getVersion() != 0) {
- LOG(INFO) << "File corrupted: transaction version is not 0.";
- return std::nullopt;
- }
+ ChunkDescriptor chunk;
+ const long pageSize = sysconf(_SC_PAGE_SIZE);
+ do {
+ transaction_checksum_t checksum = 0;
+ if (NO_ERROR != readChunkDescriptor(fd, &chunk, &checksum)) {
+ LOG(ERROR) << "Failed to read chunk descriptor.";
+ return std::nullopt;
+ }
+ off_t fdCurrentPosition = lseek(fd.get(), 0, SEEK_CUR);
+ off_t mmapPageAlignedStart = (fdCurrentPosition / pageSize) * pageSize;
+ off_t mmapPayloadStartOffset = fdCurrentPosition - mmapPageAlignedStart;
- std::vector<uint8_t> bytes;
- bytes.resize(t.getDataSize());
- if (!android::base::ReadFully(fd, bytes.data(), t.getDataSize())) {
- LOG(INFO) << "Failed to read sent parcel data from fd " << fd.get();
- return std::nullopt;
- }
- if (t.mSent.setData(bytes.data(), t.getDataSize()) != android::NO_ERROR) {
- LOG(INFO) << "Failed to set sent parcel data.";
- return std::nullopt;
- }
+ if (chunk.dataSize > kMaxChunkDataSize) {
+ LOG(ERROR) << "Chunk data exceeds maximum size.";
+ return std::nullopt;
+ }
- uint8_t padding[7];
- if (!android::base::ReadFully(fd, padding, PADDING8(t.getDataSize()))) {
- LOG(INFO) << "Failed to read sent parcel padding from fd " << fd.get();
- return std::nullopt;
- }
- if (std::any_of(padding, padding + 7, [](uint8_t i) { return i != 0; })) {
- LOG(INFO) << "File corrupted: padding isn't 0.";
- return std::nullopt;
- }
+ size_t chunkPayloadSize =
+ chunk.dataSize + PADDING8(chunk.dataSize) + sizeof(transaction_checksum_t);
- bytes.resize(t.getReplySize());
- if (!android::base::ReadFully(fd, bytes.data(), t.getReplySize())) {
- LOG(INFO) << "Failed to read reply parcel data from fd " << fd.get();
- return std::nullopt;
- }
- if (t.mReply.setData(bytes.data(), t.getReplySize()) != android::NO_ERROR) {
- LOG(INFO) << "Failed to set reply parcel data.";
- return std::nullopt;
- }
+ if (PADDING8(chunkPayloadSize) != 0) {
+ LOG(ERROR) << "Invalid chunk size, not aligned " << chunkPayloadSize;
+ return std::nullopt;
+ }
- if (!android::base::ReadFully(fd, padding, PADDING8(t.getReplySize()))) {
- LOG(INFO) << "Failed to read parcel padding from fd " << fd.get();
- return std::nullopt;
- }
- if (std::any_of(padding, padding + 7, [](uint8_t i) { return i != 0; })) {
- LOG(INFO) << "File corrupted: padding isn't 0.";
- return std::nullopt;
- }
+ transaction_checksum_t* payloadMap = reinterpret_cast<transaction_checksum_t*>(
+ mmap(NULL, chunkPayloadSize + mmapPayloadStartOffset, PROT_READ, MAP_SHARED,
+ fd.get(), mmapPageAlignedStart));
+ payloadMap += mmapPayloadStartOffset /
+ sizeof(transaction_checksum_t); // Skip chunk descriptor and required mmap
+ // page-alignment
+ if (payloadMap == MAP_FAILED) {
+ LOG(ERROR) << "Memory mapping failed for fd " << fd.get() << ": " << errno << " "
+ << strerror(errno);
+ return std::nullopt;
+ }
+ for (size_t checksumIndex = 0;
+ checksumIndex < chunkPayloadSize / sizeof(transaction_checksum_t); checksumIndex++) {
+ checksum ^= payloadMap[checksumIndex];
+ }
+ if (checksum != 0) {
+ LOG(ERROR) << "Checksum failed.";
+ return std::nullopt;
+ }
+ lseek(fd.get(), chunkPayloadSize, SEEK_CUR);
+
+ switch (chunk.chunkType) {
+ case HEADER_CHUNK: {
+ if (chunk.dataSize != static_cast<uint32_t>(sizeof(TransactionHeader))) {
+ LOG(ERROR) << "Header Chunk indicated size " << chunk.dataSize << "; Expected "
+ << sizeof(TransactionHeader) << ".";
+ return std::nullopt;
+ }
+ t.mHeader = *reinterpret_cast<TransactionHeader*>(payloadMap);
+ break;
+ }
+ case DATA_PARCEL_CHUNK: {
+ if (t.mSent.setData(reinterpret_cast<const unsigned char*>(payloadMap),
+ chunk.dataSize) != android::NO_ERROR) {
+ LOG(ERROR) << "Failed to set sent parcel data.";
+ return std::nullopt;
+ }
+ break;
+ }
+ case REPLY_PARCEL_CHUNK: {
+ if (t.mReply.setData(reinterpret_cast<const unsigned char*>(payloadMap),
+ chunk.dataSize) != android::NO_ERROR) {
+ LOG(ERROR) << "Failed to set reply parcel data.";
+ return std::nullopt;
+ }
+ break;
+ }
+ case END_CHUNK:
+ break;
+ default:
+ LOG(INFO) << "Unrecognized chunk.";
+ continue;
+ }
+ } while (chunk.chunkType != END_CHUNK);
return std::optional<RecordedTransaction>(std::move(t));
}
+android::status_t RecordedTransaction::writeChunk(borrowed_fd fd, uint32_t chunkType,
+ size_t byteCount, const uint8_t* data) const {
+ if (byteCount > kMaxChunkDataSize) {
+ LOG(ERROR) << "Chunk data exceeds maximum size";
+ return BAD_VALUE;
+ }
+ ChunkDescriptor descriptor = {.chunkType = chunkType,
+ .dataSize = static_cast<uint32_t>(byteCount)};
+ // Prepare Chunk content as byte *
+ const std::byte* descriptorBytes = reinterpret_cast<const std::byte*>(&descriptor);
+ const std::byte* dataBytes = reinterpret_cast<const std::byte*>(data);
+
+ // Add Chunk to intermediate buffer, except checksum
+ std::vector<std::byte> buffer;
+ buffer.insert(buffer.end(), descriptorBytes, descriptorBytes + sizeof(ChunkDescriptor));
+ buffer.insert(buffer.end(), dataBytes, dataBytes + byteCount);
+ std::byte zero{0};
+ buffer.insert(buffer.end(), PADDING8(byteCount), zero);
+
+ // Calculate checksum from buffer
+ transaction_checksum_t* checksumData = reinterpret_cast<transaction_checksum_t*>(buffer.data());
+ transaction_checksum_t checksumValue = 0;
+ for (size_t idx = 0; idx < (buffer.size() / sizeof(transaction_checksum_t)); idx++) {
+ checksumValue ^= checksumData[idx];
+ }
+
+ // Write checksum to buffer
+ std::byte* checksumBytes = reinterpret_cast<std::byte*>(&checksumValue);
+ buffer.insert(buffer.end(), checksumBytes, checksumBytes + sizeof(transaction_checksum_t));
+
+ // Write buffer to file
+ if (!android::base::WriteFully(fd, buffer.data(), buffer.size())) {
+ LOG(ERROR) << "Failed to write chunk fd " << fd.get();
+ return UNKNOWN_ERROR;
+ }
+ return NO_ERROR;
+}
+
android::status_t RecordedTransaction::dumpToFile(const unique_fd& fd) const {
- if (!android::base::WriteFully(fd, &mHeader, sizeof(mHeader))) {
- LOG(INFO) << "Failed to write transactionHeader to fd " << fd.get();
+ if (NO_ERROR !=
+ writeChunk(fd, HEADER_CHUNK, sizeof(TransactionHeader),
+ reinterpret_cast<const uint8_t*>(&mHeader))) {
+ LOG(ERROR) << "Failed to write transactionHeader to fd " << fd.get();
return UNKNOWN_ERROR;
}
- if (!android::base::WriteFully(fd, mSent.data(), getDataSize())) {
- LOG(INFO) << "Failed to write sent parcel data to fd " << fd.get();
+ if (NO_ERROR != writeChunk(fd, DATA_PARCEL_CHUNK, mSent.dataSize(), mSent.data())) {
+ LOG(ERROR) << "Failed to write sent Parcel to fd " << fd.get();
return UNKNOWN_ERROR;
}
- const uint8_t zeros[7] = {0};
- if (!android::base::WriteFully(fd, zeros, PADDING8(getDataSize()))) {
- LOG(INFO) << "Failed to write sent parcel padding to fd " << fd.get();
+ if (NO_ERROR != writeChunk(fd, REPLY_PARCEL_CHUNK, mReply.dataSize(), mReply.data())) {
+ LOG(ERROR) << "Failed to write reply Parcel to fd " << fd.get();
return UNKNOWN_ERROR;
}
- if (!android::base::WriteFully(fd, mReply.data(), getReplySize())) {
- LOG(INFO) << "Failed to write reply parcel data to fd " << fd.get();
- return UNKNOWN_ERROR;
- }
- if (!android::base::WriteFully(fd, zeros, PADDING8(getReplySize()))) {
- LOG(INFO) << "Failed to write reply parcel padding to fd " << fd.get();
+ if (NO_ERROR != writeChunk(fd, END_CHUNK, 0, NULL)) {
+ LOG(ERROR) << "Failed to write end chunk to fd " << fd.get();
return UNKNOWN_ERROR;
}
return NO_ERROR;
@@ -160,18 +318,16 @@
return mHeader.flags;
}
-uint64_t RecordedTransaction::getDataSize() const {
- return mHeader.dataSize;
-}
-
-uint64_t RecordedTransaction::getReplySize() const {
- return mHeader.replySize;
-}
-
int32_t RecordedTransaction::getReturnedStatus() const {
return mHeader.statusReturned;
}
+timespec RecordedTransaction::getTimestamp() const {
+ time_t sec = mHeader.timestampSeconds;
+ int32_t nsec = mHeader.timestampNanoseconds;
+ return (timespec){.tv_sec = sec, .tv_nsec = nsec};
+}
+
uint32_t RecordedTransaction::getVersion() const {
return mHeader.version;
}
diff --git a/libs/binder/BpBinder.cpp b/libs/binder/BpBinder.cpp
index 54d2445..1c470a1 100644
--- a/libs/binder/BpBinder.cpp
+++ b/libs/binder/BpBinder.cpp
@@ -47,6 +47,8 @@
binder_proxy_limit_callback BpBinder::sLimitCallback;
bool BpBinder::sBinderProxyThrottleCreate = false;
+static StaticString16 kDescriptorUninit(u"<uninit descriptor>");
+
// Arbitrarily high value that probably distinguishes a bad behaving app
uint32_t BpBinder::sBinderProxyCountHighWatermark = 2500;
// Another arbitrary value a binder count needs to drop below before another callback will be called
@@ -211,6 +213,7 @@
mAlive(true),
mObitsSent(false),
mObituaries(nullptr),
+ mDescriptorCache(kDescriptorUninit),
mTrackedUid(-1) {
extendObjectLifetime(OBJECT_LIFETIME_WEAK);
}
@@ -258,12 +261,12 @@
bool BpBinder::isDescriptorCached() const {
Mutex::Autolock _l(mLock);
- return mDescriptorCache.size() ? true : false;
+ return mDescriptorCache.string() != kDescriptorUninit.string();
}
const String16& BpBinder::getInterfaceDescriptor() const
{
- if (isDescriptorCached() == false) {
+ if (!isDescriptorCached()) {
sp<BpBinder> thiz = sp<BpBinder>::fromExisting(const_cast<BpBinder*>(this));
Parcel data;
@@ -276,8 +279,7 @@
Mutex::Autolock _l(mLock);
// mDescriptorCache could have been assigned while the lock was
// released.
- if (mDescriptorCache.size() == 0)
- mDescriptorCache = res;
+ if (mDescriptorCache.string() == kDescriptorUninit.string()) mDescriptorCache = res;
}
}
@@ -369,10 +371,7 @@
if (data.dataSize() > LOG_TRANSACTIONS_OVER_SIZE) {
Mutex::Autolock _l(mLock);
ALOGW("Large outgoing transaction of %zu bytes, interface descriptor %s, code %d",
- data.dataSize(),
- mDescriptorCache.size() ? String8(mDescriptorCache).c_str()
- : "<uncached descriptor>",
- code);
+ data.dataSize(), String8(mDescriptorCache).c_str(), code);
}
if (status == DEAD_OBJECT) mAlive = 0;
@@ -647,7 +646,7 @@
if(obits != nullptr) {
if (!obits->isEmpty()) {
ALOGI("onLastStrongRef automatically unlinking death recipients: %s",
- mDescriptorCache.size() ? String8(mDescriptorCache).c_str() : "<uncached descriptor>");
+ String8(mDescriptorCache).c_str());
}
if (ipc) ipc->clearDeathNotification(binderHandle(), this);
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index c0f3e30..da58251 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -132,12 +132,21 @@
} else {
out << "\ttarget.ptr=" << btd->target.ptr;
}
- out << "\t (cookie " << btd->cookie << ")"
- << "\n"
+ out << "\t (cookie " << btd->cookie << ")\n"
<< "\tcode=" << TypeCode(btd->code) << ", flags=" << (void*)(uint64_t)btd->flags << "\n"
- << "\tdata=" << btd->data.ptr.buffer << " (" << (void*)btd->data_size << " bytes)"
- << "\n"
- << "\toffsets=" << btd->data.ptr.offsets << " (" << (void*)btd->offsets_size << " bytes)";
+ << "\tdata=" << btd->data.ptr.buffer << " (" << (void*)btd->data_size << " bytes)\n"
+ << "\toffsets=" << btd->data.ptr.offsets << " (" << (void*)btd->offsets_size << " bytes)\n";
+ return btd + 1;
+}
+
+static const void* printBinderTransactionDataSecCtx(std::ostream& out, const void* data) {
+ const binder_transaction_data_secctx* btd = (const binder_transaction_data_secctx*)data;
+
+ printBinderTransactionData(out, &btd->transaction_data);
+
+ char* secctx = (char*)btd->secctx;
+ out << "\tsecctx=" << secctx << "\n";
+
return btd+1;
}
@@ -156,6 +165,11 @@
out << "\t" << kReturnStrings[cmdIndex];
switch (code) {
+ case BR_TRANSACTION_SEC_CTX: {
+ out << ": ";
+ cmd = (const int32_t*)printBinderTransactionDataSecCtx(out, cmd);
+ } break;
+
case BR_TRANSACTION:
case BR_REPLY: {
out << ": ";
@@ -1207,6 +1221,10 @@
return NO_ERROR;
}
+ ALOGE_IF(mProcess->mDriverFD >= 0,
+ "Driver returned error (%s). This is a bug in either libbinder or the driver. This "
+ "thread's connection to %s will no longer work.",
+ statusToString(err).c_str(), mProcess->mDriverName.c_str());
return err;
}
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index ee081c4..44ff62b 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -1475,7 +1475,7 @@
#ifdef BINDER_WITH_KERNEL_IPC
flat_binder_object obj;
obj.hdr.type = BINDER_TYPE_FD;
- obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
+ obj.flags = 0;
obj.binder = 0; /* Don't pass uninitialized stack data to a remote process */
obj.handle = fd;
obj.cookie = takeOwnership ? 1 : 0;
diff --git a/libs/binder/RpcServer.cpp b/libs/binder/RpcServer.cpp
index fedc1d9..0d06e9e 100644
--- a/libs/binder/RpcServer.cpp
+++ b/libs/binder/RpcServer.cpp
@@ -50,7 +50,8 @@
RpcServer::RpcServer(std::unique_ptr<RpcTransportCtx> ctx) : mCtx(std::move(ctx)) {}
RpcServer::~RpcServer() {
- (void)shutdown();
+ RpcMutexUniqueLock _l(mLock);
+ LOG_ALWAYS_FATAL_IF(mShutdownTrigger != nullptr, "Must call shutdown() before destructor");
}
sp<RpcServer> RpcServer::make(std::unique_ptr<RpcTransportCtxFactory> rpcTransportCtxFactory) {
@@ -203,11 +204,15 @@
iovec iov{&zero, sizeof(zero)};
std::vector<std::variant<base::unique_fd, base::borrowed_fd>> fds;
- if (receiveMessageFromSocket(server.mServer, &iov, 1, &fds) < 0) {
+ ssize_t num_bytes = receiveMessageFromSocket(server.mServer, &iov, 1, &fds);
+ if (num_bytes < 0) {
int savedErrno = errno;
ALOGE("Failed recvmsg: %s", strerror(savedErrno));
return -savedErrno;
}
+ if (num_bytes == 0) {
+ return DEAD_OBJECT;
+ }
if (fds.size() != 1) {
ALOGE("Expected exactly one fd from recvmsg, got %zu", fds.size());
return -EINVAL;
@@ -242,8 +247,11 @@
socklen_t addrLen = addr.size();
RpcTransportFd clientSocket;
- if (mAcceptFn(*this, &clientSocket) != OK) {
- continue;
+ if ((status = mAcceptFn(*this, &clientSocket)) != OK) {
+ if (status == DEAD_OBJECT)
+ break;
+ else
+ continue;
}
LOG_RPC_DETAIL("accept on fd %d yields fd %d", mServer.fd.get(), clientSocket.fd.get());
diff --git a/libs/binder/include/binder/BinderRecordReplay.h b/libs/binder/include/binder/BinderRecordReplay.h
index 25ed5e5..ff983f0 100644
--- a/libs/binder/include/binder/BinderRecordReplay.h
+++ b/libs/binder/include/binder/BinderRecordReplay.h
@@ -34,17 +34,16 @@
static std::optional<RecordedTransaction> fromFile(const android::base::unique_fd& fd);
// Filled with the arguments.
static std::optional<RecordedTransaction> fromDetails(uint32_t code, uint32_t flags,
- const Parcel& data, const Parcel& reply,
- status_t err);
+ timespec timestamp, const Parcel& data,
+ const Parcel& reply, status_t err);
RecordedTransaction(RecordedTransaction&& t) noexcept;
[[nodiscard]] status_t dumpToFile(const android::base::unique_fd& fd) const;
uint32_t getCode() const;
uint32_t getFlags() const;
- uint64_t getDataSize() const;
- uint64_t getReplySize() const;
int32_t getReturnedStatus() const;
+ timespec getTimestamp() const;
uint32_t getVersion() const;
const Parcel& getDataParcel() const;
const Parcel& getReplyParcel() const;
@@ -52,15 +51,19 @@
private:
RecordedTransaction() = default;
+ android::status_t writeChunk(const android::base::borrowed_fd, uint32_t chunkType,
+ size_t byteCount, const uint8_t* data) const;
+
#pragma clang diagnostic push
#pragma clang diagnostic error "-Wpadded"
struct TransactionHeader {
uint32_t code = 0;
uint32_t flags = 0;
- uint64_t dataSize = 0;
- uint64_t replySize = 0;
int32_t statusReturned = 0;
uint32_t version = 0; // !0 iff Rpc
+ int64_t timestampSeconds = 0;
+ int32_t timestampNanoseconds = 0;
+ int32_t reserved = 0;
};
#pragma clang diagnostic pop
static_assert(sizeof(TransactionHeader) == 32);
@@ -69,10 +72,6 @@
TransactionHeader mHeader;
Parcel mSent;
Parcel mReply;
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wunused-private-field"
- uint8_t mReserved[40];
-#pragma clang diagnostic pop
};
} // namespace binder::debug
diff --git a/libs/binder/include/binder/IServiceManager.h b/libs/binder/include/binder/IServiceManager.h
index 2af512e..c78f870 100644
--- a/libs/binder/include/binder/IServiceManager.h
+++ b/libs/binder/include/binder/IServiceManager.h
@@ -198,7 +198,10 @@
{
const sp<IServiceManager> sm = defaultServiceManager();
if (sm != nullptr) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
*outService = interface_cast<INTERFACE>(sm->getService(name));
+#pragma clang diagnostic pop // getService deprecation
if ((*outService) != nullptr) return NO_ERROR;
}
return NAME_NOT_FOUND;
diff --git a/libs/binder/include/binder/ParcelFileDescriptor.h b/libs/binder/include/binder/ParcelFileDescriptor.h
index 9896fd7..08d8e43 100644
--- a/libs/binder/include/binder/ParcelFileDescriptor.h
+++ b/libs/binder/include/binder/ParcelFileDescriptor.h
@@ -42,6 +42,7 @@
android::status_t writeToParcel(android::Parcel* parcel) const override;
android::status_t readFromParcel(const android::Parcel* parcel) override;
+ inline std::string toString() const { return "ParcelFileDescriptor:" + std::to_string(get()); }
inline bool operator!=(const ParcelFileDescriptor& rhs) const {
return mFd.get() != rhs.mFd.get();
}
diff --git a/libs/binder/include/binder/ParcelableHolder.h b/libs/binder/include/binder/ParcelableHolder.h
index 88790a8..40fd30a 100644
--- a/libs/binder/include/binder/ParcelableHolder.h
+++ b/libs/binder/include/binder/ParcelableHolder.h
@@ -111,6 +111,11 @@
Stability getStability() const override { return mStability; }
+ inline std::string toString() const {
+ return "ParcelableHolder:" +
+ (mParcelableName ? std::string(String8(mParcelableName.value()).c_str())
+ : "<parceled>");
+ }
inline bool operator!=(const ParcelableHolder& rhs) const {
return this != &rhs;
}
diff --git a/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp b/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp
index 3ec049e..3ebbed6 100644
--- a/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp
+++ b/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp
@@ -23,6 +23,13 @@
struct AIBinder;
struct ARpcServer;
+struct ARpcSession;
+
+enum class ARpcSession_FileDescriptorTransportMode {
+ None,
+ Unix,
+ Trusty,
+};
// Starts an RPC server on a given port and a given root IBinder object.
// The server will only accept connections from the given CID.
@@ -40,6 +47,22 @@
// could not be started.
[[nodiscard]] ARpcServer* ARpcServer_newInitUnixDomain(AIBinder* service, const char* name);
+// Starts an RPC server that bootstraps sessions using an existing Unix domain
+// socket pair, with a given root IBinder object.
+// Callers should create a pair of SOCK_STREAM Unix domain sockets, pass one to
+// this function and the other to UnixDomainBootstrapClient(). Multiple client
+// session can be created from the client end of the pair.
+// Does not take ownership of `service`.
+// Returns an opaque handle to the running server instance, or null if the server
+// could not be started.
+[[nodiscard]] ARpcServer* ARpcServer_newUnixDomainBootstrap(AIBinder* service, int bootstrapFd);
+
+// Sets the list of supported file descriptor transport modes of this RPC server.
+void ARpcServer_setSupportedFileDescriptorTransportModes(
+ ARpcServer* handle,
+ const ARpcSession_FileDescriptorTransportMode modes[],
+ size_t modes_len);
+
// Runs ARpcServer_join() in a background thread. Immediately returns.
void ARpcServer_start(ARpcServer* server);
@@ -50,34 +73,52 @@
void ARpcServer_join(ARpcServer* server);
// Shuts down any running ARpcServer_join().
-void ARpcServer_shutdown(ARpcServer* server);
+[[nodiscard]] bool ARpcServer_shutdown(ARpcServer* server);
// Frees the ARpcServer handle and drops the reference count on the underlying
// RpcServer instance. The handle must not be reused afterwards.
// This automatically calls ARpcServer_shutdown().
void ARpcServer_free(ARpcServer* server);
-// Starts an RPC server on a given port and a given root IBinder factory.
-// RunVsockRpcServerWithFactory acts like RunVsockRpcServerCallback, but instead of
-// assigning single root IBinder object to all connections, factory is called
-// whenever a client connects, making it possible to assign unique IBinder
-// object to each client.
-bool RunVsockRpcServerWithFactory(AIBinder* (*factory)(unsigned int cid, void* context),
- void* factoryContext, unsigned int port);
+// Allocates a new RpcSession object and returns an opaque handle to it.
+[[nodiscard]] ARpcSession* ARpcSession_new();
-AIBinder* VsockRpcClient(unsigned int cid, unsigned int port);
+// Connects to an RPC server over vsock at a given CID on a given port.
+// Returns the root Binder object of the server.
+AIBinder* ARpcSession_setupVsockClient(ARpcSession* session, unsigned int cid,
+ unsigned int port);
-// Gets the service via the RPC binder with Unix domain socket with the given
-// Unix socket `name`.
-// The final Unix domain socket path name is /dev/socket/`name`.
-AIBinder* UnixDomainRpcClient(const char* name);
+// Connects to an RPC server over a Unix Domain Socket of the given name.
+// The final Unix Domain Socket path name is /dev/socket/`name`.
+// Returns the root Binder object of the server.
+AIBinder* ARpcSession_setupUnixDomainClient(ARpcSession* session, const char* name);
-// Connect to an RPC server with preconnected file descriptors.
+// Connects to an RPC server over the given bootstrap Unix domain socket.
+// Does NOT take ownership of `bootstrapFd`.
+AIBinder* ARpcSession_setupUnixDomainBootstrapClient(ARpcSession* session,
+ int bootstrapFd);
+
+// Connects to an RPC server with preconnected file descriptors.
//
// requestFd should connect to the server and return a valid file descriptor, or
// -1 if connection fails.
//
// param will be passed to requestFd. Callers can use param to pass contexts to
// the requestFd function.
-AIBinder* RpcPreconnectedClient(int (*requestFd)(void* param), void* param);
+AIBinder* ARpcSession_setupPreconnectedClient(ARpcSession* session,
+ int (*requestFd)(void* param),
+ void* param);
+
+// Sets the file descriptor transport mode for this session.
+void ARpcSession_setFileDescriptorTransportMode(ARpcSession* session,
+ ARpcSession_FileDescriptorTransportMode mode);
+
+// Sets the maximum number of incoming threads.
+void ARpcSession_setMaxIncomingThreads(ARpcSession* session, size_t threads);
+
+// Sets the maximum number of outgoing threads.
+void ARpcSession_setMaxOutgoingThreads(ARpcSession* session, size_t threads);
+
+// Decrements the refcount of the underlying RpcSession object.
+void ARpcSession_free(ARpcSession* session);
}
diff --git a/libs/binder/libbinder_rpc_unstable.cpp b/libs/binder/libbinder_rpc_unstable.cpp
index 88f8c94..e7943dd 100644
--- a/libs/binder/libbinder_rpc_unstable.cpp
+++ b/libs/binder/libbinder_rpc_unstable.cpp
@@ -35,51 +35,46 @@
// Opaque handle for RpcServer.
struct ARpcServer {};
-static sp<RpcServer> toRpcServer(ARpcServer* handle) {
- auto ref = reinterpret_cast<RpcServer*>(handle);
- return sp<RpcServer>::fromExisting(ref);
-}
+// Opaque handle for RpcSession.
+struct ARpcSession {};
-static ARpcServer* createRpcServerHandle(sp<RpcServer>& server) {
+template <typename A, typename T>
+static A* createObjectHandle(sp<T>& server) {
auto ref = server.get();
ref->incStrong(ref);
- return reinterpret_cast<ARpcServer*>(ref);
+ return reinterpret_cast<A*>(ref);
}
-static void freeRpcServerHandle(ARpcServer* handle) {
- auto ref = reinterpret_cast<RpcServer*>(handle);
+template <typename T, typename A>
+static void freeObjectHandle(A* handle) {
+ LOG_ALWAYS_FATAL_IF(handle == nullptr, "Handle cannot be null");
+ auto ref = reinterpret_cast<T*>(handle);
ref->decStrong(ref);
}
-static unsigned int cidFromStructAddr(const void* addr, size_t addrlen) {
- LOG_ALWAYS_FATAL_IF(addrlen < sizeof(sockaddr_vm), "sockaddr is truncated");
- const sockaddr_vm* vaddr = reinterpret_cast<const sockaddr_vm*>(addr);
- LOG_ALWAYS_FATAL_IF(vaddr->svm_family != AF_VSOCK, "address is not a vsock");
- return vaddr->svm_cid;
+template <typename T, typename A>
+static sp<T> handleToStrongPointer(A* handle) {
+ LOG_ALWAYS_FATAL_IF(handle == nullptr, "Handle cannot be null");
+ auto ref = reinterpret_cast<T*>(handle);
+ return sp<T>::fromExisting(ref);
+}
+
+RpcSession::FileDescriptorTransportMode toTransportMode(
+ ARpcSession_FileDescriptorTransportMode mode) {
+ switch (mode) {
+ case ARpcSession_FileDescriptorTransportMode::None:
+ return RpcSession::FileDescriptorTransportMode::NONE;
+ case ARpcSession_FileDescriptorTransportMode::Unix:
+ return RpcSession::FileDescriptorTransportMode::UNIX;
+ case ARpcSession_FileDescriptorTransportMode::Trusty:
+ return RpcSession::FileDescriptorTransportMode::TRUSTY;
+ default:
+ return RpcSession::FileDescriptorTransportMode::NONE;
+ }
}
extern "C" {
-bool RunVsockRpcServerWithFactory(AIBinder* (*factory)(unsigned int cid, void* context),
- void* factoryContext, unsigned int port) {
- auto server = RpcServer::make();
- if (status_t status = server->setupVsockServer(VMADDR_CID_ANY, port); status != OK) {
- LOG(ERROR) << "Failed to set up vsock server with port " << port
- << " error: " << statusToString(status).c_str();
- return false;
- }
- server->setPerSessionRootObject([=](const void* addr, size_t addrlen) {
- unsigned int cid = cidFromStructAddr(addr, addrlen);
- return AIBinder_toPlatformBinder(factory(cid, factoryContext));
- });
-
- server->join();
-
- // Shutdown any open sessions since server failed.
- (void)server->shutdown();
- return true;
-}
-
ARpcServer* ARpcServer_newVsock(AIBinder* service, unsigned int cid, unsigned int port) {
auto server = RpcServer::make();
@@ -96,16 +91,18 @@
}
if (cid != VMADDR_CID_ANY) {
server->setConnectionFilter([=](const void* addr, size_t addrlen) {
- unsigned int remoteCid = cidFromStructAddr(addr, addrlen);
- if (cid != remoteCid) {
- LOG(ERROR) << "Rejected vsock connection from CID " << remoteCid;
+ LOG_ALWAYS_FATAL_IF(addrlen < sizeof(sockaddr_vm), "sockaddr is truncated");
+ const sockaddr_vm* vaddr = reinterpret_cast<const sockaddr_vm*>(addr);
+ LOG_ALWAYS_FATAL_IF(vaddr->svm_family != AF_VSOCK, "address is not a vsock");
+ if (cid != vaddr->svm_cid) {
+ LOG(ERROR) << "Rejected vsock connection from CID " << vaddr->svm_cid;
return false;
}
return true;
});
}
server->setRootObject(AIBinder_toPlatformBinder(service));
- return createRpcServerHandle(server);
+ return createObjectHandle<ARpcServer>(server);
}
ARpcServer* ARpcServer_newInitUnixDomain(AIBinder* service, const char* name) {
@@ -115,33 +112,80 @@
LOG(ERROR) << "Failed to get fd for the socket:" << name;
return nullptr;
}
+ // Control socket fds are inherited from init, so they don't have O_CLOEXEC set.
+ // But we don't want any child processes to inherit the socket we are running
+ // the server on, so attempt to set the flag now.
+ if (fcntl(fd, F_SETFD, FD_CLOEXEC) != 0) {
+ LOG(WARNING) << "Failed to set CLOEXEC on control socket with name " << name
+ << " error: " << errno;
+ }
if (status_t status = server->setupRawSocketServer(std::move(fd)); status != OK) {
LOG(ERROR) << "Failed to set up Unix Domain RPC server with name " << name
<< " error: " << statusToString(status).c_str();
return nullptr;
}
server->setRootObject(AIBinder_toPlatformBinder(service));
- return createRpcServerHandle(server);
+ return createObjectHandle<ARpcServer>(server);
+}
+
+ARpcServer* ARpcServer_newUnixDomainBootstrap(AIBinder* service, int bootstrapFd) {
+ auto server = RpcServer::make();
+ auto fd = unique_fd(bootstrapFd);
+ if (!fd.ok()) {
+ LOG(ERROR) << "Invalid bootstrap fd " << bootstrapFd;
+ return nullptr;
+ }
+ if (status_t status = server->setupUnixDomainSocketBootstrapServer(std::move(fd));
+ status != OK) {
+ LOG(ERROR) << "Failed to set up Unix Domain RPC server with bootstrap fd " << bootstrapFd
+ << " error: " << statusToString(status).c_str();
+ return nullptr;
+ }
+ server->setRootObject(AIBinder_toPlatformBinder(service));
+ return createObjectHandle<ARpcServer>(server);
+}
+
+void ARpcServer_setSupportedFileDescriptorTransportModes(
+ ARpcServer* handle, const ARpcSession_FileDescriptorTransportMode modes[],
+ size_t modes_len) {
+ auto server = handleToStrongPointer<RpcServer>(handle);
+ std::vector<RpcSession::FileDescriptorTransportMode> modevec;
+ for (size_t i = 0; i < modes_len; i++) {
+ modevec.push_back(toTransportMode(modes[i]));
+ }
+ server->setSupportedFileDescriptorTransportModes(modevec);
}
void ARpcServer_start(ARpcServer* handle) {
- toRpcServer(handle)->start();
+ handleToStrongPointer<RpcServer>(handle)->start();
}
void ARpcServer_join(ARpcServer* handle) {
- toRpcServer(handle)->join();
+ handleToStrongPointer<RpcServer>(handle)->join();
}
-void ARpcServer_shutdown(ARpcServer* handle) {
- toRpcServer(handle)->shutdown();
+bool ARpcServer_shutdown(ARpcServer* handle) {
+ return handleToStrongPointer<RpcServer>(handle)->shutdown();
}
void ARpcServer_free(ARpcServer* handle) {
- freeRpcServerHandle(handle);
+ // Ignore the result of ARpcServer_shutdown - either it had been called
+ // earlier, or the RpcServer destructor will panic.
+ (void)ARpcServer_shutdown(handle);
+ freeObjectHandle<RpcServer>(handle);
}
-AIBinder* VsockRpcClient(unsigned int cid, unsigned int port) {
+ARpcSession* ARpcSession_new() {
auto session = RpcSession::make();
+ return createObjectHandle<ARpcSession>(session);
+}
+
+void ARpcSession_free(ARpcSession* handle) {
+ freeObjectHandle<RpcSession>(handle);
+}
+
+AIBinder* ARpcSession_setupVsockClient(ARpcSession* handle, unsigned int cid, unsigned int port) {
+ auto session = handleToStrongPointer<RpcSession>(handle);
if (status_t status = session->setupVsockClient(cid, port); status != OK) {
LOG(ERROR) << "Failed to set up vsock client with CID " << cid << " and port " << port
<< " error: " << statusToString(status).c_str();
@@ -150,10 +194,10 @@
return AIBinder_fromPlatformBinder(session->getRootObject());
}
-AIBinder* UnixDomainRpcClient(const char* name) {
+AIBinder* ARpcSession_setupUnixDomainClient(ARpcSession* handle, const char* name) {
std::string pathname(name);
pathname = ANDROID_SOCKET_DIR "/" + pathname;
- auto session = RpcSession::make();
+ auto session = handleToStrongPointer<RpcSession>(handle);
if (status_t status = session->setupUnixDomainClient(pathname.c_str()); status != OK) {
LOG(ERROR) << "Failed to set up Unix Domain RPC client with path: " << pathname
<< " error: " << statusToString(status).c_str();
@@ -162,8 +206,25 @@
return AIBinder_fromPlatformBinder(session->getRootObject());
}
-AIBinder* RpcPreconnectedClient(int (*requestFd)(void* param), void* param) {
- auto session = RpcSession::make();
+AIBinder* ARpcSession_setupUnixDomainBootstrapClient(ARpcSession* handle, int bootstrapFd) {
+ auto session = handleToStrongPointer<RpcSession>(handle);
+ auto fd = unique_fd(dup(bootstrapFd));
+ if (!fd.ok()) {
+ LOG(ERROR) << "Invalid bootstrap fd " << bootstrapFd;
+ return nullptr;
+ }
+ if (status_t status = session->setupUnixDomainSocketBootstrapClient(std::move(fd));
+ status != OK) {
+ LOG(ERROR) << "Failed to set up Unix Domain RPC client with bootstrap fd: " << bootstrapFd
+ << " error: " << statusToString(status).c_str();
+ return nullptr;
+ }
+ return AIBinder_fromPlatformBinder(session->getRootObject());
+}
+
+AIBinder* ARpcSession_setupPreconnectedClient(ARpcSession* handle, int (*requestFd)(void* param),
+ void* param) {
+ auto session = handleToStrongPointer<RpcSession>(handle);
auto request = [=] { return unique_fd{requestFd(param)}; };
if (status_t status = session->setupPreconnectedClient(unique_fd{}, request); status != OK) {
LOG(ERROR) << "Failed to set up vsock client. error: " << statusToString(status).c_str();
@@ -171,4 +232,20 @@
}
return AIBinder_fromPlatformBinder(session->getRootObject());
}
+
+void ARpcSession_setFileDescriptorTransportMode(ARpcSession* handle,
+ ARpcSession_FileDescriptorTransportMode mode) {
+ auto session = handleToStrongPointer<RpcSession>(handle);
+ session->setFileDescriptorTransportMode(toTransportMode(mode));
+}
+
+void ARpcSession_setMaxIncomingThreads(ARpcSession* handle, size_t threads) {
+ auto session = handleToStrongPointer<RpcSession>(handle);
+ session->setMaxIncomingThreads(threads);
+}
+
+void ARpcSession_setMaxOutgoingThreads(ARpcSession* handle, size_t threads) {
+ auto session = handleToStrongPointer<RpcSession>(handle);
+ session->setMaxOutgoingThreads(threads);
+}
}
diff --git a/libs/binder/ndk/Android.bp b/libs/binder/ndk/Android.bp
index 8ae7537..58ed418 100644
--- a/libs/binder/ndk/Android.bp
+++ b/libs/binder/ndk/Android.bp
@@ -66,10 +66,14 @@
"service_manager.cpp",
],
- shared_libs: [
+ static_libs: [
"libandroid_runtime_lazy",
"libbase",
+ ],
+
+ shared_libs: [
"libbinder",
+ "liblog",
"libutils",
],
diff --git a/libs/binder/ndk/ibinder.cpp b/libs/binder/ndk/ibinder.cpp
index 28d1f16..d0de7b9 100644
--- a/libs/binder/ndk/ibinder.cpp
+++ b/libs/binder/ndk/ibinder.cpp
@@ -75,12 +75,48 @@
AIBinder::AIBinder(const AIBinder_Class* clazz) : mClazz(clazz) {}
AIBinder::~AIBinder() {}
-std::optional<bool> AIBinder::associateClassInternal(const AIBinder_Class* clazz,
- const String16& newDescriptor, bool set) {
+// b/175635923 libcxx causes "implicit-conversion" with a string with invalid char
+static std::string SanitizeString(const String16& str) {
+ std::string sanitized{String8(str)};
+ for (auto& c : sanitized) {
+ if (!isprint(c)) {
+ c = '?';
+ }
+ }
+ return sanitized;
+}
+
+bool AIBinder::associateClass(const AIBinder_Class* clazz) {
+ if (clazz == nullptr) return false;
+
+ // If mClazz is non-null, this must have been called and cached
+ // already. So, we can safely call this first. Due to the implementation
+ // of getInterfaceDescriptor (at time of writing), two simultaneous calls
+ // may lead to extra binder transactions, but this is expected to be
+ // exceedingly rare. Once we have a binder, when we get it again later,
+ // we won't make another binder transaction here.
+ const String16& descriptor = getBinder()->getInterfaceDescriptor();
+ const String16& newDescriptor = clazz->getInterfaceDescriptor();
+
std::lock_guard<std::mutex> lock(mClazzMutex);
if (mClazz == clazz) return true;
- if (mClazz != nullptr) {
+ // If this is an ABpBinder, the first class object becomes the canonical one. The implication
+ // of this is that no API can require a proxy information to get information on how to behave.
+ // from the class itself - which should only store the interface descriptor. The functionality
+ // should be implemented by adding AIBinder_* APIs to set values on binders themselves, by
+ // setting things on AIBinder_Class which get transferred along with the binder, so that they
+ // can be read along with the BpBinder, or by modifying APIs directly (e.g. an option in
+ // onTransact).
+ //
+ // While this check is required to support linkernamespaces, one downside of it is that
+ // you may parcel code to communicate between things in the same process. However, comms
+ // between linkernamespaces like this already happen for cross-language calls like Java<->C++
+ // or Rust<->Java, and there are good stability guarantees here. This interacts with
+ // binder Stability checks exactly like any other in-process call. The stability is known
+ // to the IBinder object, so that it doesn't matter if a class object comes from
+ // a different stability level.
+ if (mClazz != nullptr && !asABpBinder()) {
const String16& currentDescriptor = mClazz->getInterfaceDescriptor();
if (newDescriptor == currentDescriptor) {
LOG(ERROR) << __func__ << ": Class descriptors '" << currentDescriptor
@@ -97,37 +133,10 @@
return false;
}
- if (set) {
- // if this is a local object, it's not one known to libbinder_ndk
- mClazz = clazz;
- return true;
- }
-
- return {};
-}
-
-// b/175635923 libcxx causes "implicit-conversion" with a string with invalid char
-static std::string SanitizeString(const String16& str) {
- std::string sanitized{String8(str)};
- for (auto& c : sanitized) {
- if (!isprint(c)) {
- c = '?';
- }
- }
- return sanitized;
-}
-
-bool AIBinder::associateClass(const AIBinder_Class* clazz) {
- if (clazz == nullptr) return false;
-
- const String16& newDescriptor = clazz->getInterfaceDescriptor();
-
- auto result = associateClassInternal(clazz, newDescriptor, false);
- if (result.has_value()) return *result;
-
- CHECK(asABpBinder() != nullptr); // ABBinder always has a descriptor
-
- const String16& descriptor = getBinder()->getInterfaceDescriptor();
+ // This will always be an O(n) comparison, but it's expected to be extremely rare.
+ // since it's an error condition. Do the comparison after we take the lock and
+ // check the pointer equality fast path. By always taking the lock, it's also
+ // more flake-proof. However, the check is not dependent on the lock.
if (descriptor != newDescriptor) {
if (getBinder()->isBinderAlive()) {
LOG(ERROR) << __func__ << ": Expecting binder to have class '" << newDescriptor
@@ -141,7 +150,14 @@
return false;
}
- return associateClassInternal(clazz, newDescriptor, true).value();
+ // A local binder being set for the first time OR
+ // ignoring a proxy binder which is set multiple time, by considering the first
+ // associated class as the canonical one.
+ if (mClazz == nullptr) {
+ mClazz = clazz;
+ }
+
+ return true;
}
ABBinder::ABBinder(const AIBinder_Class* clazz, void* userData)
@@ -325,6 +341,10 @@
return lhs->binder < rhs->binder;
}
+// WARNING: When multiple classes exist with the same interface descriptor in different
+// linkernamespaces, the first one to be associated with mClazz becomes the canonical one
+// and the only requirement on this is that the interface descriptors match. If this
+// is an ABpBinder, no other state can be referenced from mClazz.
AIBinder_Class::AIBinder_Class(const char* interfaceDescriptor, AIBinder_Class_onCreate onCreate,
AIBinder_Class_onDestroy onDestroy,
AIBinder_Class_onTransact onTransact)
@@ -632,6 +652,10 @@
(*in)->get()->markForBinder(binder->getBinder());
status_t status = android::OK;
+
+ // note - this is the only read of a value in clazz, and it comes with a warning
+ // on the API itself. Do not copy this design. Instead, attach data in a new
+ // version of the prepareTransaction function.
if (clazz->writeHeader) {
status = (*in)->get()->writeInterfaceToken(clazz->getInterfaceDescriptor());
}
diff --git a/libs/binder/ndk/ibinder_internal.h b/libs/binder/ndk/ibinder_internal.h
index d7098e8..67bb092 100644
--- a/libs/binder/ndk/ibinder_internal.h
+++ b/libs/binder/ndk/ibinder_internal.h
@@ -53,12 +53,14 @@
}
private:
- std::optional<bool> associateClassInternal(const AIBinder_Class* clazz,
- const ::android::String16& newDescriptor, bool set);
-
// AIBinder instance is instance of this class for a local object. In order to transact on a
// remote object, this also must be set for simplicity (although right now, only the
// interfaceDescriptor from it is used).
+ //
+ // WARNING: When multiple classes exist with the same interface descriptor in different
+ // linkernamespaces, the first one to be associated with mClazz becomes the canonical one
+ // and the only requirement on this is that the interface descriptors match. If this
+ // is an ABpBinder, no other state can be referenced from mClazz.
const AIBinder_Class* mClazz;
std::mutex mClazzMutex;
};
diff --git a/libs/binder/ndk/include_cpp/android/binder_to_string.h b/libs/binder/ndk/include_cpp/android/binder_to_string.h
index 2a00736..9b0d222 100644
--- a/libs/binder/ndk/include_cpp/android/binder_to_string.h
+++ b/libs/binder/ndk/include_cpp/android/binder_to_string.h
@@ -49,12 +49,17 @@
#include <android/binder_interface_utils.h>
#include <android/binder_parcelable_utils.h>
#define HAS_NDK_INTERFACE
-#else
+#endif
+
+// TODO: some things include libbinder without having access to libbase. This is
+// due to frameworks/native/include, which symlinks to libbinder headers, so even
+// though we don't use it here, we detect a different header, so that we are more
+// confident libbase will be included
+#if __has_include(<binder/RpcSession.h>)
#include <binder/IBinder.h>
#include <binder/IInterface.h>
-#include <binder/ParcelFileDescriptor.h>
-#include <binder/ParcelableHolder.h>
-#endif //_has_include
+#define HAS_CPP_INTERFACE
+#endif
namespace android {
namespace internal {
@@ -104,10 +109,12 @@
IsInstantiationOf<_U, sp>::value || // for IBinder and interface types in the C++
// backend
#endif
- IsInstantiationOf<_U, std::optional>::value || // for @nullable types in the
- // C++/NDK backends
- IsInstantiationOf<_U, std::shared_ptr>::value, // for interface types in the
- // NDK backends
+ IsInstantiationOf<_U, std::optional>::value || // for @nullable types in the
+ // C++/NDK backends
+ IsInstantiationOf<_U, std::unique_ptr>::value || // for @nullable(heap=true)
+ // in C++/NDK backends
+ IsInstantiationOf<_U, std::shared_ptr>::value, // for interface types in the
+ // NDK backends
std::true_type>
_test(int);
@@ -134,19 +141,19 @@
template <typename _T>
class ToEmptyString {
template <typename _U>
- static std::enable_if_t<
+ static std::enable_if_t<false
#ifdef HAS_NDK_INTERFACE
- std::is_base_of_v<::ndk::ICInterface, _U>
+ || std::is_base_of_v<::ndk::ICInterface, _U>
#if __ANDROID_API__ >= 31
- || std::is_same_v<::ndk::AParcelableHolder, _U>
+ || std::is_same_v<::ndk::AParcelableHolder, _U>
#endif
-#else
- std::is_base_of_v<IInterface, _U> || std::is_same_v<IBinder, _U> ||
- std::is_same_v<os::ParcelFileDescriptor, _U> ||
- std::is_same_v<os::ParcelableHolder, _U>
+#endif // HAS_NDK_INTERFACE
+#ifdef HAS_CPP_INTERFACE
+ || std::is_base_of_v<IInterface, _U> ||
+ std::is_same_v<IBinder, _U>
#endif
- ,
- std::true_type>
+ ,
+ std::true_type>
_test(int);
template <typename _U>
static std::false_type _test(...);
@@ -155,6 +162,11 @@
enum { value = decltype(_test<_T>(0))::value };
};
+template <typename _T>
+struct TypeDependentFalse {
+ enum { value = false };
+};
+
} // namespace details
template <typename _T>
@@ -214,11 +226,27 @@
out << "]";
return out.str();
} else {
- return "{no toString() implemented}";
+ static_assert(details::TypeDependentFalse<_T>::value, "no toString implemented, huh?");
}
}
} // namespace internal
} // namespace android
+#ifdef HAS_STRONG_POINTER
+#undef HAS_STRONG_POINTER
+#endif
+
+#ifdef HAS_STRING16
+#undef HAS_STRING16
+#endif
+
+#ifdef HAS_NDK_INTERFACE
+#undef HAS_NDK_INTERFACE
+#endif
+
+#ifdef HAS_CPP_INTERFACE
+#undef HAS_CPP_INTERFACE
+#endif
+
/** @} */
diff --git a/libs/binder/ndk/include_ndk/android/binder_ibinder.h b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
index 1af2119..db2d2c1 100644
--- a/libs/binder/ndk/include_ndk/android/binder_ibinder.h
+++ b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
@@ -229,6 +229,11 @@
*
* Available since API level 33.
*
+ * WARNING: this API interacts badly with linkernamespaces. For correct behavior, you must
+ * use it on all instances of a class in the same process which share the same interface
+ * descriptor. In general, it is recommended you do not use this API, because it is disabling
+ * type safety.
+ *
* \param clazz class to disable interface header on.
*/
void AIBinder_Class_disableInterfaceTokenHeader(AIBinder_Class* clazz) __INTRODUCED_IN(33);
@@ -589,6 +594,9 @@
*
* See also AIBinder_linkToDeath/AIBinder_unlinkToDeath.
*
+ * WARNING: Make sure the lifetime of this cookie is long enough. If it is dynamically
+ * allocated, it should be deleted with AIBinder_DeathRecipient_setOnUnlinked.
+ *
* Available since API level 33.
*
* \param cookie the cookie passed to AIBinder_linkToDeath.
@@ -600,6 +608,9 @@
*
* Available since API level 29.
*
+ * WARNING: Make sure the lifetime of this cookie is long enough. If it is dynamically
+ * allocated, it should be deleted with AIBinder_DeathRecipient_setOnUnlinked.
+ *
* \param onBinderDied the callback to call when this death recipient is invoked.
*
* \return the newly constructed object (or null if onBinderDied is null).
diff --git a/libs/binder/ndk/tests/iface.cpp b/libs/binder/ndk/tests/iface.cpp
index 2afe5d2..76acff5 100644
--- a/libs/binder/ndk/tests/iface.cpp
+++ b/libs/binder/ndk/tests/iface.cpp
@@ -72,6 +72,11 @@
AIBinder_Class* IFoo::kClass = AIBinder_Class_define(kIFooDescriptor, IFoo_Class_onCreate,
IFoo_Class_onDestroy, IFoo_Class_onTransact);
+// Defines the same class. Ordinarly, you would never want to do this, but it's done here
+// to simulate what would happen when multiple linker namespaces interact.
+AIBinder_Class* IFoo::kClassDupe = AIBinder_Class_define(
+ kIFooDescriptor, IFoo_Class_onCreate, IFoo_Class_onDestroy, IFoo_Class_onTransact);
+
class BpFoo : public IFoo {
public:
explicit BpFoo(AIBinder* binder) : mBinder(binder) {}
diff --git a/libs/binder/ndk/tests/include/iface/iface.h b/libs/binder/ndk/tests/include/iface/iface.h
index 7408d0c..0a562f0 100644
--- a/libs/binder/ndk/tests/include/iface/iface.h
+++ b/libs/binder/ndk/tests/include/iface/iface.h
@@ -30,8 +30,13 @@
static const char* kIFooDescriptor;
static AIBinder_Class* kClass;
+ static AIBinder_Class* kClassDupe;
// binder representing this interface with one reference count
+ // NOTE - this will create a new binder if it already exists. If you use
+ // getService for instance, you must pull outBinder. Don't use this without
+ // verifying isRemote or pointer equality. This is not a very good testing API - don't
+ // copy it - consider the AIDL-generated APIs instead.
AIBinder* getBinder();
// Takes ownership of IFoo
diff --git a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
index 9d5ef68..5b2532a 100644
--- a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
+++ b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
@@ -55,6 +55,18 @@
constexpr unsigned int kShutdownWaitTime = 10;
constexpr uint64_t kContextTestValue = 0xb4e42fb4d9a1d715;
+class MyTestFoo : public IFoo {
+ binder_status_t doubleNumber(int32_t in, int32_t* out) override {
+ *out = 2 * in;
+ LOG(INFO) << "doubleNumber (" << in << ") => " << *out;
+ return STATUS_OK;
+ }
+ binder_status_t die() override {
+ ADD_FAILURE() << "die called on local instance";
+ return STATUS_OK;
+ }
+};
+
class MyBinderNdkUnitTest : public aidl::BnBinderNdkUnitTest {
ndk::ScopedAStatus repeatInt(int32_t in, int32_t* out) {
*out = in;
@@ -296,11 +308,10 @@
}
TEST(NdkBinder, UnimplementedDump) {
- sp<IFoo> foo = IFoo::getService(IFoo::kSomeInstanceName);
+ ndk::SpAIBinder binder;
+ sp<IFoo> foo = IFoo::getService(IFoo::kSomeInstanceName, binder.getR());
ASSERT_NE(foo, nullptr);
- AIBinder* binder = foo->getBinder();
- EXPECT_EQ(OK, AIBinder_dump(binder, STDOUT_FILENO, nullptr, 0));
- AIBinder_decStrong(binder);
+ EXPECT_EQ(OK, AIBinder_dump(binder.get(), STDOUT_FILENO, nullptr, 0));
}
TEST(NdkBinder, UnimplementedShell) {
@@ -324,6 +335,24 @@
EXPECT_EQ(2, out);
}
+TEST(NdkBinder, ReassociateBpBinderWithSameDescriptor) {
+ ndk::SpAIBinder binder;
+ sp<IFoo> foo = IFoo::getService(IFoo::kSomeInstanceName, binder.getR());
+
+ EXPECT_TRUE(AIBinder_isRemote(binder.get()));
+
+ EXPECT_TRUE(AIBinder_associateClass(binder.get(), IFoo::kClassDupe));
+}
+
+TEST(NdkBinder, CantHaveTwoLocalBinderClassesWithSameDescriptor) {
+ sp<IFoo> foo = sp<MyTestFoo>::make();
+ ndk::SpAIBinder binder(foo->getBinder());
+
+ EXPECT_FALSE(AIBinder_isRemote(binder.get()));
+
+ EXPECT_FALSE(AIBinder_associateClass(binder.get(), IFoo::kClassDupe));
+}
+
TEST(NdkBinder, GetTestServiceStressTest) {
// libbinder has some complicated logic to make sure only one instance of
// ABpBinder is associated with each binder.
@@ -545,18 +574,6 @@
AIBinder_decStrong(binder);
}
-class MyTestFoo : public IFoo {
- binder_status_t doubleNumber(int32_t in, int32_t* out) override {
- *out = 2 * in;
- LOG(INFO) << "doubleNumber (" << in << ") => " << *out;
- return STATUS_OK;
- }
- binder_status_t die() override {
- ADD_FAILURE() << "die called on local instance";
- return STATUS_OK;
- }
-};
-
TEST(NdkBinder, SetInheritRt) {
// functional test in binderLibTest
sp<IFoo> foo = sp<MyTestFoo>::make();
@@ -597,7 +614,8 @@
sp<IFoo> foo = new MyTestFoo;
EXPECT_EQ(EX_NONE, foo->addService(kInstanceName));
- sp<IFoo> getFoo = IFoo::getService(kInstanceName);
+ ndk::SpAIBinder binder;
+ sp<IFoo> getFoo = IFoo::getService(kInstanceName, binder.getR());
EXPECT_EQ(foo.get(), getFoo.get());
int32_t out;
diff --git a/libs/binder/rust/rpcbinder/Android.bp b/libs/binder/rust/rpcbinder/Android.bp
index f70ebfc..afb73e9 100644
--- a/libs/binder/rust/rpcbinder/Android.bp
+++ b/libs/binder/rust/rpcbinder/Android.bp
@@ -68,10 +68,13 @@
visibility: [":__subpackages__"],
source_stem: "bindings",
bindgen_flags: [
+ "--size_t-is-usize",
"--blocklist-type",
"AIBinder",
"--raw-line",
"use binder_ndk_sys::AIBinder;",
+ "--rustified-enum",
+ "ARpcSession_FileDescriptorTransportMode",
],
rustlibs: [
"libbinder_ndk_sys",
diff --git a/libs/binder/rust/rpcbinder/src/client.rs b/libs/binder/rust/rpcbinder/src/client.rs
deleted file mode 100644
index 48c787b..0000000
--- a/libs/binder/rust/rpcbinder/src/client.rs
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-use binder::{unstable_api::new_spibinder, FromIBinder, SpIBinder, StatusCode, Strong};
-use std::ffi::CString;
-use std::os::{
- raw::{c_int, c_void},
- unix::io::RawFd,
-};
-
-/// Connects to an RPC Binder server over vsock.
-pub fn get_vsock_rpc_service(cid: u32, port: u32) -> Option<SpIBinder> {
- // SAFETY: AIBinder returned by VsockRpcClient has correct reference count,
- // and the ownership can safely be taken by new_spibinder.
- unsafe { new_spibinder(binder_rpc_unstable_bindgen::VsockRpcClient(cid, port)) }
-}
-
-/// Connects to an RPC Binder server for a particular interface over vsock.
-pub fn get_vsock_rpc_interface<T: FromIBinder + ?Sized>(
- cid: u32,
- port: u32,
-) -> Result<Strong<T>, StatusCode> {
- interface_cast(get_vsock_rpc_service(cid, port))
-}
-
-/// Connects to an RPC Binder server over Unix domain socket.
-pub fn get_unix_domain_rpc_service(socket_name: &str) -> Option<SpIBinder> {
- let socket_name = match CString::new(socket_name) {
- Ok(s) => s,
- Err(e) => {
- log::error!("Cannot convert {} to CString. Error: {:?}", socket_name, e);
- return None;
- }
- };
- // SAFETY: AIBinder returned by UnixDomainRpcClient has correct reference count,
- // and the ownership can safely be taken by new_spibinder.
- unsafe { new_spibinder(binder_rpc_unstable_bindgen::UnixDomainRpcClient(socket_name.as_ptr())) }
-}
-
-/// Connects to an RPC Binder server for a particular interface over Unix domain socket.
-pub fn get_unix_domain_rpc_interface<T: FromIBinder + ?Sized>(
- socket_name: &str,
-) -> Result<Strong<T>, StatusCode> {
- interface_cast(get_unix_domain_rpc_service(socket_name))
-}
-
-/// Connects to an RPC Binder server, using the given callback to get (and take ownership of)
-/// file descriptors already connected to it.
-pub fn get_preconnected_rpc_service(
- mut request_fd: impl FnMut() -> Option<RawFd>,
-) -> Option<SpIBinder> {
- // Double reference the factory because trait objects aren't FFI safe.
- let mut request_fd_ref: RequestFd = &mut request_fd;
- let param = &mut request_fd_ref as *mut RequestFd as *mut c_void;
-
- // SAFETY: AIBinder returned by RpcPreconnectedClient has correct reference count, and the
- // ownership can be safely taken by new_spibinder. RpcPreconnectedClient does not take ownership
- // of param, only passing it to request_fd_wrapper.
- unsafe {
- new_spibinder(binder_rpc_unstable_bindgen::RpcPreconnectedClient(
- Some(request_fd_wrapper),
- param,
- ))
- }
-}
-
-type RequestFd<'a> = &'a mut dyn FnMut() -> Option<RawFd>;
-
-unsafe extern "C" fn request_fd_wrapper(param: *mut c_void) -> c_int {
- // SAFETY: This is only ever called by RpcPreconnectedClient, within the lifetime of the
- // BinderFdFactory reference, with param being a properly aligned non-null pointer to an
- // initialized instance.
- let request_fd_ptr = param as *mut RequestFd;
- let request_fd = request_fd_ptr.as_mut().unwrap();
- if let Some(fd) = request_fd() {
- fd
- } else {
- -1
- }
-}
-
-/// Connects to an RPC Binder server for a particular interface, using the given callback to get
-/// (and take ownership of) file descriptors already connected to it.
-pub fn get_preconnected_rpc_interface<T: FromIBinder + ?Sized>(
- request_fd: impl FnMut() -> Option<RawFd>,
-) -> Result<Strong<T>, StatusCode> {
- interface_cast(get_preconnected_rpc_service(request_fd))
-}
-
-fn interface_cast<T: FromIBinder + ?Sized>(
- service: Option<SpIBinder>,
-) -> Result<Strong<T>, StatusCode> {
- if let Some(service) = service {
- FromIBinder::try_from(service)
- } else {
- Err(StatusCode::NAME_NOT_FOUND)
- }
-}
diff --git a/libs/binder/rust/rpcbinder/src/lib.rs b/libs/binder/rust/rpcbinder/src/lib.rs
index 1b719aa..a957385 100644
--- a/libs/binder/rust/rpcbinder/src/lib.rs
+++ b/libs/binder/rust/rpcbinder/src/lib.rs
@@ -16,11 +16,8 @@
//! API for RPC Binder services.
-mod client;
mod server;
+mod session;
-pub use client::{
- get_preconnected_rpc_interface, get_preconnected_rpc_service, get_unix_domain_rpc_interface,
- get_unix_domain_rpc_service, get_vsock_rpc_interface, get_vsock_rpc_service,
-};
-pub use server::{run_vsock_rpc_server_with_factory, RpcServer, RpcServerRef};
+pub use server::{RpcServer, RpcServerRef};
+pub use session::{FileDescriptorTransportMode, RpcSession, RpcSessionRef};
diff --git a/libs/binder/rust/rpcbinder/src/server.rs b/libs/binder/rust/rpcbinder/src/server.rs
index d5f1219..761b306 100644
--- a/libs/binder/rust/rpcbinder/src/server.rs
+++ b/libs/binder/rust/rpcbinder/src/server.rs
@@ -14,14 +14,13 @@
* limitations under the License.
*/
-use binder::{
- unstable_api::{AIBinder, AsNative},
- SpIBinder,
-};
+use crate::session::FileDescriptorTransportMode;
+use binder::{unstable_api::AsNative, SpIBinder};
use binder_rpc_unstable_bindgen::ARpcServer;
use foreign_types::{foreign_type, ForeignType, ForeignTypeRef};
+use std::ffi::CString;
use std::io::{Error, ErrorKind};
-use std::{ffi::CString, os::raw, ptr::null_mut};
+use std::os::unix::io::{IntoRawFd, OwnedFd};
foreign_type! {
type CType = binder_rpc_unstable_bindgen::ARpcServer;
@@ -82,6 +81,27 @@
}
}
+ /// Creates a binder RPC server that bootstraps sessions using an existing Unix domain socket
+ /// pair, with a given root IBinder object. Callers should create a pair of SOCK_STREAM Unix
+ /// domain sockets, pass one to the server and the other to the client. Multiple client session
+ /// can be created from the client end of the pair.
+ pub fn new_unix_domain_bootstrap(
+ mut service: SpIBinder,
+ bootstrap_fd: OwnedFd,
+ ) -> Result<RpcServer, Error> {
+ let service = service.as_native_mut();
+
+ // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
+ // Plus the binder objects are threadsafe.
+ // The server takes ownership of the bootstrap FD.
+ unsafe {
+ Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newUnixDomainBootstrap(
+ service,
+ bootstrap_fd.into_raw_fd(),
+ ))
+ }
+ }
+
unsafe fn checked_from_ptr(ptr: *mut ARpcServer) -> Result<RpcServer, Error> {
if ptr.is_null() {
return Err(Error::new(ErrorKind::Other, "Failed to start server"));
@@ -91,6 +111,22 @@
}
impl RpcServerRef {
+ /// Sets the list of file descriptor transport modes supported by this server.
+ pub fn set_supported_file_descriptor_transport_modes(
+ &self,
+ modes: &[FileDescriptorTransportMode],
+ ) {
+ // SAFETY - Does not keep the pointer after returning does, nor does it
+ // read past its boundary. Only passes the 'self' pointer as an opaque handle.
+ unsafe {
+ binder_rpc_unstable_bindgen::ARpcServer_setSupportedFileDescriptorTransportModes(
+ self.as_ptr(),
+ modes.as_ptr(),
+ modes.len(),
+ )
+ }
+ }
+
/// Starts a new background thread and calls join(). Returns immediately.
pub fn start(&self) {
unsafe { binder_rpc_unstable_bindgen::ARpcServer_start(self.as_ptr()) };
@@ -104,51 +140,11 @@
/// Shuts down the running RpcServer. Can be called multiple times and from
/// multiple threads. Called automatically during drop().
- pub fn shutdown(&self) {
- unsafe { binder_rpc_unstable_bindgen::ARpcServer_shutdown(self.as_ptr()) };
- }
-}
-
-type RpcServerFactoryRef<'a> = &'a mut (dyn FnMut(u32) -> Option<SpIBinder> + Send + Sync);
-
-/// Runs a binder RPC server, using the given factory function to construct a binder service
-/// implementation for each connection.
-///
-/// The current thread is joined to the binder thread pool to handle incoming messages.
-///
-/// Returns true if the server has shutdown normally, false if it failed in some way.
-pub fn run_vsock_rpc_server_with_factory(
- port: u32,
- mut factory: impl FnMut(u32) -> Option<SpIBinder> + Send + Sync,
-) -> bool {
- // Double reference the factory because trait objects aren't FFI safe.
- // NB: The type annotation is necessary to ensure that we have a `dyn` rather than an `impl`.
- let mut factory_ref: RpcServerFactoryRef = &mut factory;
- let context = &mut factory_ref as *mut RpcServerFactoryRef as *mut raw::c_void;
-
- // SAFETY: `factory_wrapper` is only ever called by `RunVsockRpcServerWithFactory`, with context
- // taking the pointer value above (so a properly aligned non-null pointer to an initialized
- // `RpcServerFactoryRef`), within the lifetime of `factory_ref` (i.e. no more calls will be made
- // after `RunVsockRpcServerWithFactory` returns).
- unsafe {
- binder_rpc_unstable_bindgen::RunVsockRpcServerWithFactory(
- Some(factory_wrapper),
- context,
- port,
- )
- }
-}
-
-unsafe extern "C" fn factory_wrapper(cid: u32, context: *mut raw::c_void) -> *mut AIBinder {
- // SAFETY: `context` was created from an `&mut RpcServerFactoryRef` by
- // `run_vsock_rpc_server_with_factory`, and we are still within the lifetime of the value it is
- // pointing to.
- let factory_ptr = context as *mut RpcServerFactoryRef;
- let factory = factory_ptr.as_mut().unwrap();
-
- if let Some(mut service) = factory(cid) {
- service.as_native_mut()
- } else {
- null_mut()
+ pub fn shutdown(&self) -> Result<(), Error> {
+ if unsafe { binder_rpc_unstable_bindgen::ARpcServer_shutdown(self.as_ptr()) } {
+ Ok(())
+ } else {
+ Err(Error::from(ErrorKind::UnexpectedEof))
+ }
}
}
diff --git a/libs/binder/rust/rpcbinder/src/session.rs b/libs/binder/rust/rpcbinder/src/session.rs
new file mode 100644
index 0000000..62fedb1
--- /dev/null
+++ b/libs/binder/rust/rpcbinder/src/session.rs
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use binder::unstable_api::new_spibinder;
+use binder::{FromIBinder, SpIBinder, StatusCode, Strong};
+use foreign_types::{foreign_type, ForeignType, ForeignTypeRef};
+use std::ffi::CString;
+use std::os::{
+ raw::{c_int, c_void},
+ unix::io::{AsRawFd, BorrowedFd, RawFd},
+};
+
+pub use binder_rpc_unstable_bindgen::ARpcSession_FileDescriptorTransportMode as FileDescriptorTransportMode;
+
+foreign_type! {
+ type CType = binder_rpc_unstable_bindgen::ARpcSession;
+ fn drop = binder_rpc_unstable_bindgen::ARpcSession_free;
+
+ /// A type that represents a foreign instance of RpcSession.
+ #[derive(Debug)]
+ pub struct RpcSession;
+ /// A borrowed RpcSession.
+ pub struct RpcSessionRef;
+}
+
+/// SAFETY - The opaque handle can be cloned freely.
+unsafe impl Send for RpcSession {}
+/// SAFETY - The underlying C++ RpcSession class is thread-safe.
+unsafe impl Sync for RpcSession {}
+
+impl RpcSession {
+ /// Allocates a new RpcSession object.
+ pub fn new() -> RpcSession {
+ // SAFETY - Takes ownership of the returned handle, which has correct refcount.
+ unsafe { RpcSession::from_ptr(binder_rpc_unstable_bindgen::ARpcSession_new()) }
+ }
+}
+
+impl Default for RpcSession {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl RpcSessionRef {
+ /// Sets the file descriptor transport mode for this session.
+ pub fn set_file_descriptor_transport_mode(&self, mode: FileDescriptorTransportMode) {
+ // SAFETY - Only passes the 'self' pointer as an opaque handle.
+ unsafe {
+ binder_rpc_unstable_bindgen::ARpcSession_setFileDescriptorTransportMode(
+ self.as_ptr(),
+ mode,
+ )
+ };
+ }
+
+ /// Sets the maximum number of incoming threads.
+ pub fn set_max_incoming_threads(&self, threads: usize) {
+ // SAFETY - Only passes the 'self' pointer as an opaque handle.
+ unsafe {
+ binder_rpc_unstable_bindgen::ARpcSession_setMaxIncomingThreads(self.as_ptr(), threads)
+ };
+ }
+
+ /// Sets the maximum number of outgoing threads.
+ pub fn set_max_outgoing_threads(&self, threads: usize) {
+ // SAFETY - Only passes the 'self' pointer as an opaque handle.
+ unsafe {
+ binder_rpc_unstable_bindgen::ARpcSession_setMaxOutgoingThreads(self.as_ptr(), threads)
+ };
+ }
+
+ /// Connects to an RPC Binder server over vsock for a particular interface.
+ pub fn setup_vsock_client<T: FromIBinder + ?Sized>(
+ &self,
+ cid: u32,
+ port: u32,
+ ) -> Result<Strong<T>, StatusCode> {
+ // SAFETY: AIBinder returned by ARpcSession_setupVsockClient has correct
+ // reference count, and the ownership can safely be taken by new_spibinder.
+ let service = unsafe {
+ new_spibinder(binder_rpc_unstable_bindgen::ARpcSession_setupVsockClient(
+ self.as_ptr(),
+ cid,
+ port,
+ ))
+ };
+ Self::get_interface(service)
+ }
+
+ /// Connects to an RPC Binder server over a names Unix Domain Socket for
+ /// a particular interface.
+ pub fn setup_unix_domain_client<T: FromIBinder + ?Sized>(
+ &self,
+ socket_name: &str,
+ ) -> Result<Strong<T>, StatusCode> {
+ let socket_name = match CString::new(socket_name) {
+ Ok(s) => s,
+ Err(e) => {
+ log::error!("Cannot convert {} to CString. Error: {:?}", socket_name, e);
+ return Err(StatusCode::NAME_NOT_FOUND);
+ }
+ };
+
+ // SAFETY: AIBinder returned by ARpcSession_setupUnixDomainClient has correct
+ // reference count, and the ownership can safely be taken by new_spibinder.
+ let service = unsafe {
+ new_spibinder(binder_rpc_unstable_bindgen::ARpcSession_setupUnixDomainClient(
+ self.as_ptr(),
+ socket_name.as_ptr(),
+ ))
+ };
+ Self::get_interface(service)
+ }
+
+ /// Connects to an RPC Binder server over a bootstrap Unix Domain Socket
+ /// for a particular interface.
+ pub fn setup_unix_domain_bootstrap_client<T: FromIBinder + ?Sized>(
+ &self,
+ bootstrap_fd: BorrowedFd,
+ ) -> Result<Strong<T>, StatusCode> {
+ // SAFETY: ARpcSession_setupUnixDomainBootstrapClient does not take
+ // ownership of bootstrap_fd. The returned AIBinder has correct
+ // reference count, and the ownership can safely be taken by new_spibinder.
+ let service = unsafe {
+ new_spibinder(binder_rpc_unstable_bindgen::ARpcSession_setupUnixDomainBootstrapClient(
+ self.as_ptr(),
+ bootstrap_fd.as_raw_fd(),
+ ))
+ };
+ Self::get_interface(service)
+ }
+
+ /// Connects to an RPC Binder server, using the given callback to get (and
+ /// take ownership of) file descriptors already connected to it.
+ pub fn setup_preconnected_client<T: FromIBinder + ?Sized>(
+ &self,
+ mut request_fd: impl FnMut() -> Option<RawFd>,
+ ) -> Result<Strong<T>, StatusCode> {
+ // Double reference the factory because trait objects aren't FFI safe.
+ let mut request_fd_ref: RequestFd = &mut request_fd;
+ let param = &mut request_fd_ref as *mut RequestFd as *mut c_void;
+
+ // SAFETY: AIBinder returned by RpcPreconnectedClient has correct reference count, and the
+ // ownership can be safely taken by new_spibinder. RpcPreconnectedClient does not take ownership
+ // of param, only passing it to request_fd_wrapper.
+ let service = unsafe {
+ new_spibinder(binder_rpc_unstable_bindgen::ARpcSession_setupPreconnectedClient(
+ self.as_ptr(),
+ Some(request_fd_wrapper),
+ param,
+ ))
+ };
+ Self::get_interface(service)
+ }
+
+ fn get_interface<T: FromIBinder + ?Sized>(
+ service: Option<SpIBinder>,
+ ) -> Result<Strong<T>, StatusCode> {
+ if let Some(service) = service {
+ FromIBinder::try_from(service)
+ } else {
+ Err(StatusCode::NAME_NOT_FOUND)
+ }
+ }
+}
+
+type RequestFd<'a> = &'a mut dyn FnMut() -> Option<RawFd>;
+
+unsafe extern "C" fn request_fd_wrapper(param: *mut c_void) -> c_int {
+ // SAFETY: This is only ever called by RpcPreconnectedClient, within the lifetime of the
+ // BinderFdFactory reference, with param being a properly aligned non-null pointer to an
+ // initialized instance.
+ let request_fd_ptr = param as *mut RequestFd;
+ let request_fd = request_fd_ptr.as_mut().unwrap();
+ request_fd().unwrap_or(-1)
+}
diff --git a/libs/binder/rust/src/parcel.rs b/libs/binder/rust/src/parcel.rs
index 53a24af..e4c568e 100644
--- a/libs/binder/rust/src/parcel.rs
+++ b/libs/binder/rust/src/parcel.rs
@@ -566,9 +566,6 @@
impl<'a> ReadableSubParcel<'a> {
/// Read a type that implements [`Deserialize`] from the sub-parcel.
pub fn read<D: Deserialize>(&self) -> Result<D> {
- // The caller should have checked this,
- // but it can't hurt to double-check
- assert!(self.has_more_data());
D::deserialize(&self.parcel)
}
diff --git a/libs/binder/rust/src/parcel/parcelable.rs b/libs/binder/rust/src/parcel/parcelable.rs
index c241e4d..6f4c375 100644
--- a/libs/binder/rust/src/parcel/parcelable.rs
+++ b/libs/binder/rust/src/parcel/parcelable.rs
@@ -23,7 +23,7 @@
use std::convert::{TryFrom, TryInto};
use std::ffi::c_void;
use std::mem::{self, ManuallyDrop, MaybeUninit};
-use std::os::raw::{c_char, c_ulong};
+use std::os::raw::c_char;
use std::ptr;
use std::slice;
@@ -103,12 +103,8 @@
unsafe extern "C" fn serialize_element<T: Serialize>(
parcel: *mut sys::AParcel,
array: *const c_void,
- index: c_ulong,
+ index: usize,
) -> status_t {
- // c_ulong and usize are the same, but we need the explicitly sized version
- // so the function signature matches what bindgen generates.
- let index = index as usize;
-
let slice: &[T] = slice::from_raw_parts(array.cast(), index + 1);
let mut parcel = match BorrowedParcel::from_raw(parcel) {
@@ -158,12 +154,8 @@
unsafe extern "C" fn deserialize_element<T: Deserialize>(
parcel: *const sys::AParcel,
array: *mut c_void,
- index: c_ulong,
+ index: usize,
) -> status_t {
- // c_ulong and usize are the same, but we need the explicitly sized version
- // so the function signature matches what bindgen generates.
- let index = index as usize;
-
let vec = &mut *(array as *mut Option<Vec<MaybeUninit<T>>>);
let vec = match vec {
Some(v) => v,
diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp
index a999d59..5db3187 100644
--- a/libs/binder/tests/Android.bp
+++ b/libs/binder/tests/Android.bp
@@ -100,6 +100,7 @@
"binderBinderUnitTest.cpp",
"binderStatusUnitTest.cpp",
"binderMemoryHeapBaseUnitTest.cpp",
+ "binderRecordedTransactionTest.cpp",
],
shared_libs: [
"libbinder",
diff --git a/libs/binder/tests/binderAllocationLimits.cpp b/libs/binder/tests/binderAllocationLimits.cpp
index 55a3916..bc40864 100644
--- a/libs/binder/tests/binderAllocationLimits.cpp
+++ b/libs/binder/tests/binderAllocationLimits.cpp
@@ -172,6 +172,28 @@
a_binder->pingBinder();
}
+TEST(BinderAllocation, InterfaceDescriptorTransaction) {
+ sp<IBinder> a_binder = GetRemoteBinder();
+
+ size_t mallocs = 0;
+ const auto on_malloc = OnMalloc([&](size_t bytes) {
+ mallocs++;
+ // Happens to be SM package length. We could switch to forking
+ // and registering our own service if it became an issue.
+#if defined(__LP64__)
+ EXPECT_EQ(bytes, 78);
+#else
+ EXPECT_EQ(bytes, 70);
+#endif
+ });
+
+ a_binder->getInterfaceDescriptor();
+ a_binder->getInterfaceDescriptor();
+ a_binder->getInterfaceDescriptor();
+
+ EXPECT_EQ(mallocs, 1);
+}
+
TEST(BinderAllocation, SmallTransaction) {
String16 empty_descriptor = String16("");
sp<IServiceManager> manager = defaultServiceManager();
diff --git a/libs/binder/tests/binderRecordedTransactionTest.cpp b/libs/binder/tests/binderRecordedTransactionTest.cpp
new file mode 100644
index 0000000..67553fc
--- /dev/null
+++ b/libs/binder/tests/binderRecordedTransactionTest.cpp
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <binder/BinderRecordReplay.h>
+#include <gtest/gtest.h>
+#include <utils/Errors.h>
+
+using android::Parcel;
+using android::status_t;
+using android::base::unique_fd;
+using android::binder::debug::RecordedTransaction;
+
+TEST(BinderRecordedTransaction, RoundTripEncoding) {
+ Parcel d;
+ d.writeInt32(12);
+ d.writeInt64(2);
+ Parcel r;
+ r.writeInt32(99);
+ timespec ts = {1232456, 567890};
+ auto transaction = RecordedTransaction::fromDetails(1, 42, ts, d, r, 0);
+
+ auto file = std::tmpfile();
+ auto fd = unique_fd(fcntl(fileno(file), F_DUPFD, 1));
+
+ status_t status = transaction->dumpToFile(fd);
+ ASSERT_EQ(android::NO_ERROR, status);
+
+ std::rewind(file);
+
+ auto retrievedTransaction = RecordedTransaction::fromFile(fd);
+
+ EXPECT_EQ(retrievedTransaction->getCode(), 1);
+ EXPECT_EQ(retrievedTransaction->getFlags(), 42);
+ EXPECT_EQ(retrievedTransaction->getTimestamp().tv_sec, ts.tv_sec);
+ EXPECT_EQ(retrievedTransaction->getTimestamp().tv_nsec, ts.tv_nsec);
+ EXPECT_EQ(retrievedTransaction->getDataParcel().dataSize(), 12);
+ EXPECT_EQ(retrievedTransaction->getReplyParcel().dataSize(), 4);
+ EXPECT_EQ(retrievedTransaction->getReturnedStatus(), 0);
+ EXPECT_EQ(retrievedTransaction->getVersion(), 0);
+
+ EXPECT_EQ(retrievedTransaction->getDataParcel().readInt32(), 12);
+ EXPECT_EQ(retrievedTransaction->getDataParcel().readInt64(), 2);
+ EXPECT_EQ(retrievedTransaction->getReplyParcel().readInt32(), 99);
+}
+
+TEST(BinderRecordedTransaction, Checksum) {
+ Parcel d;
+ d.writeInt32(12);
+ d.writeInt64(2);
+ Parcel r;
+ r.writeInt32(99);
+ timespec ts = {1232456, 567890};
+ auto transaction = RecordedTransaction::fromDetails(1, 42, ts, d, r, 0);
+
+ auto file = std::tmpfile();
+ auto fd = unique_fd(fcntl(fileno(file), F_DUPFD, 1));
+
+ status_t status = transaction->dumpToFile(fd);
+ ASSERT_EQ(android::NO_ERROR, status);
+
+ lseek(fd.get(), 9, SEEK_SET);
+ uint32_t badData = 0xffffffff;
+ write(fd.get(), &badData, sizeof(uint32_t));
+ std::rewind(file);
+
+ auto retrievedTransaction = RecordedTransaction::fromFile(fd);
+
+ EXPECT_FALSE(retrievedTransaction.has_value());
+}
+
+TEST(BinderRecordedTransaction, PayloadsExceedPageBoundaries) {
+ // File contents are read with mmap.
+ // This test verifies that transactions are read from portions
+ // of files that cross page boundaries and don't start at a
+ // page boundary offset of the fd.
+ const size_t pageSize = sysconf(_SC_PAGE_SIZE);
+ const size_t largeDataSize = pageSize + 100;
+ std::vector<uint8_t> largePayload;
+ uint8_t filler = 0xaa;
+ largePayload.insert(largePayload.end(), largeDataSize, filler);
+ Parcel d;
+ d.writeInt32(12);
+ d.writeInt64(2);
+ d.writeByteVector(largePayload);
+ Parcel r;
+ r.writeInt32(99);
+ timespec ts = {1232456, 567890};
+ auto transaction = RecordedTransaction::fromDetails(1, 42, ts, d, r, 0);
+
+ auto file = std::tmpfile();
+ auto fd = unique_fd(fcntl(fileno(file), F_DUPFD, 1));
+
+ // Write to file twice
+ status_t status = transaction->dumpToFile(fd);
+ ASSERT_EQ(android::NO_ERROR, status);
+ status = transaction->dumpToFile(fd);
+ ASSERT_EQ(android::NO_ERROR, status);
+
+ std::rewind(file);
+
+ for (int i = 0; i < 2; i++) {
+ auto retrievedTransaction = RecordedTransaction::fromFile(fd);
+
+ EXPECT_EQ(retrievedTransaction->getCode(), 1);
+ EXPECT_EQ(retrievedTransaction->getFlags(), 42);
+ EXPECT_EQ(retrievedTransaction->getTimestamp().tv_sec, ts.tv_sec);
+ EXPECT_EQ(retrievedTransaction->getTimestamp().tv_nsec, ts.tv_nsec);
+ EXPECT_EQ(retrievedTransaction->getDataParcel().dataSize(), d.dataSize());
+ EXPECT_EQ(retrievedTransaction->getReplyParcel().dataSize(), 4);
+ EXPECT_EQ(retrievedTransaction->getReturnedStatus(), 0);
+ EXPECT_EQ(retrievedTransaction->getVersion(), 0);
+
+ EXPECT_EQ(retrievedTransaction->getDataParcel().readInt32(), 12);
+ EXPECT_EQ(retrievedTransaction->getDataParcel().readInt64(), 2);
+ std::optional<std::vector<uint8_t>> payloadOut;
+ EXPECT_EQ(retrievedTransaction->getDataParcel().readByteVector(&payloadOut), android::OK);
+ EXPECT_EQ(payloadOut.value(), largePayload);
+
+ EXPECT_EQ(retrievedTransaction->getReplyParcel().readInt32(), 99);
+ }
+}
diff --git a/libs/binder/tests/binderRpcTest.cpp b/libs/binder/tests/binderRpcTest.cpp
index 739c217..8afa49b 100644
--- a/libs/binder/tests/binderRpcTest.cpp
+++ b/libs/binder/tests/binderRpcTest.cpp
@@ -372,12 +372,12 @@
ts.push_back(std::thread([&] { proc.rootIface->lockUnlock(); }));
}
- usleep(10000); // give chance for calls on other threads
+ usleep(100000); // give chance for calls on other threads
// other calls still work
EXPECT_EQ(OK, proc.rootBinder->pingBinder());
- constexpr size_t blockTimeMs = 50;
+ constexpr size_t blockTimeMs = 100;
size_t epochMsBefore = epochMillis();
// after this, we should never see a response within this time
EXPECT_OK(proc.rootIface->unlockInMsAsync(blockTimeMs));
diff --git a/libs/binder/tests/binderUtilsHostTest.cpp b/libs/binder/tests/binderUtilsHostTest.cpp
index 4330e3e..25e286c 100644
--- a/libs/binder/tests/binderUtilsHostTest.cpp
+++ b/libs/binder/tests/binderUtilsHostTest.cpp
@@ -37,17 +37,24 @@
EXPECT_EQ(result->stdoutStr, "foo\n");
}
+template <typename T>
+auto millisSince(std::chrono::time_point<T> now) {
+ auto elapsed = std::chrono::system_clock::now() - now;
+ return std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
+}
+
TEST(UtilsHost, ExecuteLongRunning) {
- auto now = std::chrono::system_clock::now();
+ auto start = std::chrono::system_clock::now();
{
- std::vector<std::string> args{"sh", "-c",
- "sleep 0.5 && echo -n f && sleep 0.5 && echo oo && sleep 1"};
- auto result = execute(std::move(args), [](const CommandResult& commandResult) {
+ std::vector<std::string>
+ args{"sh", "-c", "sleep 0.5 && echo -n f && sleep 0.5 && echo oo && sleep 100"};
+ auto result = execute(std::move(args), [&](const CommandResult& commandResult) {
+ std::cout << millisSince(start)
+ << "ms: GOT PARTIAL COMMAND RESULT:" << commandResult.stdoutStr << std::endl;
return android::base::EndsWith(commandResult.stdoutStr, "\n");
});
- auto elapsed = std::chrono::system_clock::now() - now;
- auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
+ auto elapsedMs = millisSince(start);
EXPECT_GE(elapsedMs, 1000);
EXPECT_LT(elapsedMs, 2000);
@@ -58,22 +65,21 @@
// ~CommandResult() called, child process is killed.
// Assert that the second sleep does not finish.
- auto elapsed = std::chrono::system_clock::now() - now;
- auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
- EXPECT_LT(elapsedMs, 2000);
+ EXPECT_LT(millisSince(start), 2000);
}
TEST(UtilsHost, ExecuteLongRunning2) {
- auto now = std::chrono::system_clock::now();
+ auto start = std::chrono::system_clock::now();
{
std::vector<std::string> args{"sh", "-c",
- "sleep 2 && echo -n f && sleep 2 && echo oo && sleep 2"};
- auto result = execute(std::move(args), [](const CommandResult& commandResult) {
+ "sleep 2 && echo -n f && sleep 2 && echo oo && sleep 100"};
+ auto result = execute(std::move(args), [&](const CommandResult& commandResult) {
+ std::cout << millisSince(start)
+ << "ms: GOT PARTIAL COMMAND RESULT:" << commandResult.stdoutStr << std::endl;
return android::base::EndsWith(commandResult.stdoutStr, "\n");
});
- auto elapsed = std::chrono::system_clock::now() - now;
- auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
+ auto elapsedMs = millisSince(start);
EXPECT_GE(elapsedMs, 4000);
EXPECT_LT(elapsedMs, 6000);
@@ -84,9 +90,7 @@
// ~CommandResult() called, child process is killed.
// Assert that the second sleep does not finish.
- auto elapsed = std::chrono::system_clock::now() - now;
- auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
- EXPECT_LT(elapsedMs, 6000);
+ EXPECT_LT(millisSince(start), 6000);
}
TEST(UtilsHost, KillWithSigKill) {
diff --git a/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp b/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp
index 53e7de4..08eb27a 100644
--- a/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp
+++ b/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp
@@ -199,5 +199,25 @@
binder_status_t status = genericDataParcelable.readFromParcel(p.aParcel());
FUZZ_LOG() << "status: " << status;
},
+ [](const NdkParcelAdapter& p, FuzzedDataProvider& provider) {
+ FUZZ_LOG() << "about to marshal AParcel";
+ size_t start = provider.ConsumeIntegral<size_t>();
+ // limit 1MB to avoid OOM issues
+ size_t len = provider.ConsumeIntegralInRange<size_t>(0, 1000000);
+ uint8_t buffer[len];
+ binder_status_t status = AParcel_marshal(p.aParcel(), buffer, start, len);
+ FUZZ_LOG() << "status: " << status;
+ },
+ [](const NdkParcelAdapter& /*p*/, FuzzedDataProvider& provider) {
+ FUZZ_LOG() << "about to unmarshal AParcel";
+ size_t len = provider.ConsumeIntegralInRange<size_t>(0, provider.remaining_bytes());
+ std::vector<uint8_t> parcelData = provider.ConsumeBytes<uint8_t>(len);
+ const uint8_t* buffer = parcelData.data();
+ const size_t bufferLen = parcelData.size();
+ NdkParcelAdapter adapter;
+ binder_status_t status = AParcel_unmarshal(adapter.aParcel(), buffer, bufferLen);
+ FUZZ_LOG() << "status: " << status;
+ },
+
};
// clang-format on
diff --git a/libs/binder/tests/rpc_fuzzer/main.cpp b/libs/binder/tests/rpc_fuzzer/main.cpp
index f68a561..b8ae84d 100644
--- a/libs/binder/tests/rpc_fuzzer/main.cpp
+++ b/libs/binder/tests/rpc_fuzzer/main.cpp
@@ -133,8 +133,13 @@
bool hangupBeforeShutdown = provider.ConsumeBool();
+ // b/260736889 - limit arbitrarily, due to thread resource exhaustion, which currently
+ // aborts. Servers should consider RpcServer::setConnectionFilter instead.
+ constexpr size_t kMaxConnections = 1000;
+
while (provider.remaining_bytes() > 0) {
- if (connections.empty() || provider.ConsumeBool()) {
+ if (connections.empty() ||
+ (connections.size() < kMaxConnections && provider.ConsumeBool())) {
base::unique_fd fd(TEMP_FAILURE_RETRY(socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)));
CHECK_NE(fd.get(), -1);
CHECK_EQ(0,
diff --git a/libs/binder/trusty/kernel/rules.mk b/libs/binder/trusty/kernel/rules.mk
new file mode 100644
index 0000000..ab7a50d
--- /dev/null
+++ b/libs/binder/trusty/kernel/rules.mk
@@ -0,0 +1,83 @@
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+LIBBINDER_DIR := frameworks/native/libs/binder
+LIBBASE_DIR := system/libbase
+LIBCUTILS_DIR := system/core/libcutils
+LIBUTILS_DIR := system/core/libutils
+FMTLIB_DIR := external/fmtlib
+
+MODULE_SRCS := \
+ $(LOCAL_DIR)/../logging.cpp \
+ $(LOCAL_DIR)/../TrustyStatus.cpp \
+ $(LIBBINDER_DIR)/Binder.cpp \
+ $(LIBBINDER_DIR)/BpBinder.cpp \
+ $(LIBBINDER_DIR)/FdTrigger.cpp \
+ $(LIBBINDER_DIR)/IInterface.cpp \
+ $(LIBBINDER_DIR)/IResultReceiver.cpp \
+ $(LIBBINDER_DIR)/Parcel.cpp \
+ $(LIBBINDER_DIR)/Stability.cpp \
+ $(LIBBINDER_DIR)/Status.cpp \
+ $(LIBBINDER_DIR)/Utils.cpp \
+ $(LIBBASE_DIR)/hex.cpp \
+ $(LIBBASE_DIR)/stringprintf.cpp \
+ $(LIBUTILS_DIR)/Errors.cpp \
+ $(LIBUTILS_DIR)/misc.cpp \
+ $(LIBUTILS_DIR)/RefBase.cpp \
+ $(LIBUTILS_DIR)/StrongPointer.cpp \
+ $(LIBUTILS_DIR)/Unicode.cpp \
+
+# TODO: remove the following when libbinder supports std::string
+# instead of String16 and String8 for Status and descriptors
+MODULE_SRCS += \
+ $(LIBUTILS_DIR)/SharedBuffer.cpp \
+ $(LIBUTILS_DIR)/String16.cpp \
+ $(LIBUTILS_DIR)/String8.cpp \
+
+# TODO: disable dump() transactions to get rid of Vector
+MODULE_SRCS += \
+ $(LIBUTILS_DIR)/VectorImpl.cpp \
+
+MODULE_DEFINES += \
+ LK_DEBUGLEVEL_NO_ALIASES=1 \
+
+MODULE_INCLUDES += \
+ $(LOCAL_DIR)/.. \
+
+GLOBAL_INCLUDES += \
+ $(LOCAL_DIR)/include \
+ $(LOCAL_DIR)/../include \
+ $(LIBBINDER_DIR)/include \
+ $(LIBBINDER_DIR)/ndk/include_cpp \
+ $(LIBBASE_DIR)/include \
+ $(LIBCUTILS_DIR)/include \
+ $(LIBUTILS_DIR)/include \
+ $(FMTLIB_DIR)/include \
+
+GLOBAL_COMPILEFLAGS += \
+ -DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION \
+ -DBINDER_NO_KERNEL_IPC \
+ -DBINDER_RPC_SINGLE_THREADED \
+ -D__ANDROID_VNDK__ \
+
+MODULE_DEPS += \
+ trusty/kernel/lib/libcxx-trusty \
+ trusty/kernel/lib/libcxxabi-trusty \
+
+include make/module.mk
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index a988e39..6c9c28a 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -192,6 +192,7 @@
"BitTube.cpp",
"BLASTBufferQueue.cpp",
"BufferItemConsumer.cpp",
+ "Choreographer.cpp",
"CompositorTiming.cpp",
"ConsumerBase.cpp",
"CpuConsumer.cpp",
@@ -234,6 +235,7 @@
export_header_lib_headers: [
"libgui_aidl_headers",
+ "jni_headers",
],
aidl: {
@@ -241,6 +243,7 @@
},
header_libs: [
+ "jni_headers",
"libdvr_headers",
"libgui_aidl_headers",
"libpdx_headers",
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 97e45c6..797d6ae 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -483,20 +483,18 @@
mSyncedFrameNumbers.erase(callbackId.framenumber);
}
-void BLASTBufferQueue::acquireNextBufferLocked(
+status_t BLASTBufferQueue::acquireNextBufferLocked(
const std::optional<SurfaceComposerClient::Transaction*> transaction) {
// If the next transaction is set, we want to guarantee the our acquire will not fail, so don't
// include the extra buffer when checking if we can acquire the next buffer.
- const bool includeExtraAcquire = !transaction;
- const bool maxAcquired = maxBuffersAcquired(includeExtraAcquire);
- if (mNumFrameAvailable == 0 || maxAcquired) {
- BQA_LOGV("Can't process next buffer maxBuffersAcquired=%s", boolToString(maxAcquired));
- return;
+ if (mNumFrameAvailable == 0) {
+ BQA_LOGV("Can't process next buffer. No available frames");
+ return NOT_ENOUGH_DATA;
}
if (mSurfaceControl == nullptr) {
BQA_LOGE("ERROR : surface control is null");
- return;
+ return NAME_NOT_FOUND;
}
SurfaceComposerClient::Transaction localTransaction;
@@ -513,10 +511,10 @@
mBufferItemConsumer->acquireBuffer(&bufferItem, 0 /* expectedPresent */, false);
if (status == BufferQueue::NO_BUFFER_AVAILABLE) {
BQA_LOGV("Failed to acquire a buffer, err=NO_BUFFER_AVAILABLE");
- return;
+ return status;
} else if (status != OK) {
BQA_LOGE("Failed to acquire a buffer, err=%s", statusToString(status).c_str());
- return;
+ return status;
}
auto buffer = bufferItem.mGraphicBuffer;
@@ -526,7 +524,7 @@
if (buffer == nullptr) {
mBufferItemConsumer->releaseBuffer(bufferItem, Fence::NO_FENCE);
BQA_LOGE("Buffer was empty");
- return;
+ return BAD_VALUE;
}
if (rejectBuffer(bufferItem)) {
@@ -535,8 +533,7 @@
mSize.width, mSize.height, mRequestedSize.width, mRequestedSize.height,
buffer->getWidth(), buffer->getHeight(), bufferItem.mTransform);
mBufferItemConsumer->releaseBuffer(bufferItem, Fence::NO_FENCE);
- acquireNextBufferLocked(transaction);
- return;
+ return acquireNextBufferLocked(transaction);
}
mNumAcquired++;
@@ -624,6 +621,7 @@
bufferItem.mTimestamp, bufferItem.mIsAutoTimestamp ? "(auto)" : "",
static_cast<uint32_t>(mPendingTransactions.size()), bufferItem.mGraphicBuffer->getId(),
bufferItem.mAutoRefresh ? " mAutoRefresh" : "", bufferItem.mTransform);
+ return OK;
}
Rect BLASTBufferQueue::computeCrop(const BufferItem& item) {
@@ -647,32 +645,6 @@
mBufferItemConsumer->releaseBuffer(bufferItem, bufferItem.mFence);
}
-void BLASTBufferQueue::flushAndWaitForFreeBuffer(std::unique_lock<std::mutex>& lock) {
- BBQ_TRACE();
- if (!mSyncedFrameNumbers.empty() && mNumFrameAvailable > 0) {
- // We are waiting on a previous sync's transaction callback so allow another sync
- // transaction to proceed.
- //
- // We need to first flush out the transactions that were in between the two syncs.
- // We do this by merging them into mSyncTransaction so any buffer merging will get
- // a release callback invoked. The release callback will be async so we need to wait
- // on max acquired to make sure we have the capacity to acquire another buffer.
- if (maxBuffersAcquired(false /* includeExtraAcquire */)) {
- BQA_LOGD("waiting to flush shadow queue...");
- mCallbackCV.wait(lock);
- }
- while (mNumFrameAvailable > 0) {
- // flush out the shadow queue
- acquireAndReleaseBuffer();
- }
- }
-
- while (maxBuffersAcquired(false /* includeExtraAcquire */)) {
- BQA_LOGD("waiting for free buffer.");
- mCallbackCV.wait(lock);
- }
-}
-
void BLASTBufferQueue::onFrameAvailable(const BufferItem& item) {
std::function<void(SurfaceComposerClient::Transaction*)> prevCallback = nullptr;
SurfaceComposerClient::Transaction* prevTransaction = nullptr;
@@ -685,7 +657,6 @@
BQA_LOGV("onFrameAvailable-start syncTransactionSet=%s", boolToString(syncTransactionSet));
if (syncTransactionSet) {
- bool mayNeedToWaitForBuffer = true;
// If we are going to re-use the same mSyncTransaction, release the buffer that may
// already be set in the Transaction. This is to allow us a free slot early to continue
// processing a new buffer.
@@ -696,14 +667,20 @@
bufferData->frameNumber);
releaseBuffer(bufferData->generateReleaseCallbackId(),
bufferData->acquireFence);
- // Because we just released a buffer, we know there's no need to wait for a free
- // buffer.
- mayNeedToWaitForBuffer = false;
}
}
- if (mayNeedToWaitForBuffer) {
- flushAndWaitForFreeBuffer(_lock);
+ if (waitForTransactionCallback) {
+ // We are waiting on a previous sync's transaction callback so allow another sync
+ // transaction to proceed.
+ //
+ // We need to first flush out the transactions that were in between the two syncs.
+ // We do this by merging them into mSyncTransaction so any buffer merging will get
+ // a release callback invoked.
+ while (mNumFrameAvailable > 0) {
+ // flush out the shadow queue
+ acquireAndReleaseBuffer();
+ }
}
}
@@ -719,7 +696,12 @@
item.mFrameNumber, boolToString(syncTransactionSet));
if (syncTransactionSet) {
- acquireNextBufferLocked(mSyncTransaction);
+ // If there's no available buffer and we're in a sync transaction, we need to wait
+ // instead of returning since we guarantee a buffer will be acquired for the sync.
+ while (acquireNextBufferLocked(mSyncTransaction) == BufferQueue::NO_BUFFER_AVAILABLE) {
+ BQA_LOGD("waiting for available buffer");
+ mCallbackCV.wait(_lock);
+ }
// Only need a commit callback when syncing to ensure the buffer that's synced has been
// sent to SF
@@ -829,15 +811,6 @@
return mSize != bufferSize;
}
-// Check if we have acquired the maximum number of buffers.
-// Consumer can acquire an additional buffer if that buffer is not droppable. Set
-// includeExtraAcquire is true to include this buffer to the count. Since this depends on the state
-// of the buffer, the next acquire may return with NO_BUFFER_AVAILABLE.
-bool BLASTBufferQueue::maxBuffersAcquired(bool includeExtraAcquire) const {
- int maxAcquiredBuffers = mMaxAcquiredBuffers + (includeExtraAcquire ? 2 : 1);
- return mNumAcquired >= maxAcquiredBuffers;
-}
-
class BBQSurface : public Surface {
private:
std::mutex mMutex;
diff --git a/libs/nativedisplay/Choreographer.cpp b/libs/gui/Choreographer.cpp
similarity index 99%
rename from libs/nativedisplay/Choreographer.cpp
rename to libs/gui/Choreographer.cpp
index 01e9f04..6b25b26 100644
--- a/libs/nativedisplay/Choreographer.cpp
+++ b/libs/gui/Choreographer.cpp
@@ -16,8 +16,8 @@
// #define LOG_NDEBUG 0
+#include <gui/Choreographer.h>
#include <jni.h>
-#include <nativedisplay/Choreographer.h>
#undef LOG_TAG
#define LOG_TAG "AChoreographer"
diff --git a/libs/gui/TEST_MAPPING b/libs/gui/TEST_MAPPING
index 1c43530..9415035 100644
--- a/libs/gui/TEST_MAPPING
+++ b/libs/gui/TEST_MAPPING
@@ -3,5 +3,11 @@
{
"path": "frameworks/native/libs/nativewindow"
}
+ ],
+ "postsubmit": [
+ {
+ // TODO(257123981): move this to presubmit after dealing with existing breakages.
+ "name": "libgui_test"
+ }
]
}
diff --git a/libs/gui/aidl/android/gui/OverlayProperties.aidl b/libs/gui/aidl/android/gui/OverlayProperties.aidl
index 75cea15..1af5746 100644
--- a/libs/gui/aidl/android/gui/OverlayProperties.aidl
+++ b/libs/gui/aidl/android/gui/OverlayProperties.aidl
@@ -23,4 +23,6 @@
int[] dataspaces;
}
SupportedBufferCombinations[] combinations;
+
+ boolean supportMixedColorSpaces;
}
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index 47dcc42..001d8e5 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -130,12 +130,11 @@
void createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
sp<IGraphicBufferConsumer>* outConsumer);
- void acquireNextBufferLocked(
+ status_t acquireNextBufferLocked(
const std::optional<SurfaceComposerClient::Transaction*> transaction) REQUIRES(mMutex);
Rect computeCrop(const BufferItem& item) REQUIRES(mMutex);
// Return true if we need to reject the buffer based on the scaling mode and the buffer size.
bool rejectBuffer(const BufferItem& item) REQUIRES(mMutex);
- bool maxBuffersAcquired(bool includeExtraAcquire) const REQUIRES(mMutex);
static PixelFormat convertBufferFormat(PixelFormat& format);
void mergePendingTransactions(SurfaceComposerClient::Transaction* t, uint64_t frameNumber)
REQUIRES(mMutex);
@@ -144,7 +143,6 @@
void acquireAndReleaseBuffer() REQUIRES(mMutex);
void releaseBuffer(const ReleaseCallbackId& callbackId, const sp<Fence>& releaseFence)
REQUIRES(mMutex);
- void flushAndWaitForFreeBuffer(std::unique_lock<std::mutex>& lock);
std::string mName;
// Represents the queued buffer count from buffer queue,
diff --git a/libs/nativedisplay/include/nativedisplay/Choreographer.h b/libs/gui/include/gui/Choreographer.h
similarity index 98%
rename from libs/nativedisplay/include/nativedisplay/Choreographer.h
rename to libs/gui/include/gui/Choreographer.h
index bb63f29..89a7058 100644
--- a/libs/nativedisplay/include/nativedisplay/Choreographer.h
+++ b/libs/gui/include/gui/Choreographer.h
@@ -16,8 +16,9 @@
#pragma once
+#include <android/choreographer.h>
#include <gui/DisplayEventDispatcher.h>
-#include <private/android/choreographer.h>
+#include <jni.h>
#include <utils/Looper.h>
#include <mutex>
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 34ef7b4..8f41cc1 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -26,7 +26,6 @@
filegroup {
name: "inputconstants_aidl",
srcs: [
- "android/hardware/input/InputDeviceCountryCode.aidl",
"android/os/IInputConstants.aidl",
"android/os/InputEventInjectionResult.aidl",
"android/os/InputEventInjectionSync.aidl",
@@ -50,6 +49,7 @@
"Keyboard.cpp",
"KeyCharacterMap.cpp",
"KeyLayoutMap.cpp",
+ "MotionPredictor.cpp",
"PrintTools.cpp",
"PropertyMap.cpp",
"TouchVideoFrame.cpp",
@@ -63,8 +63,9 @@
shared_libs: [
"libbase",
- "liblog",
"libcutils",
+ "liblog",
+ "libPlatformProperties",
"libvintf",
],
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index 9e8ebf3..c796439 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -46,25 +46,6 @@
namespace {
-float transformAngle(const ui::Transform& transform, float angleRadians) {
- // Construct and transform a vector oriented at the specified clockwise angle from vertical.
- // Coordinate system: down is increasing Y, right is increasing X.
- float x = sinf(angleRadians);
- float y = -cosf(angleRadians);
- vec2 transformedPoint = transform.transform(x, y);
-
- // Determine how the origin is transformed by the matrix so that we
- // can transform orientation vectors.
- const vec2 origin = transform.transform(0, 0);
-
- transformedPoint.x -= origin.x;
- transformedPoint.y -= origin.y;
-
- // Derive the transformed vector's clockwise angle from vertical.
- // The return value of atan2f is in range [-pi, pi] which conforms to the orientation API.
- return atan2f(transformedPoint.x, -transformedPoint.y);
-}
-
bool shouldDisregardTransformation(uint32_t source) {
// Do not apply any transformations to axes from joysticks, touchpads, or relative mice.
return isFromSource(source, AINPUT_SOURCE_CLASS_JOYSTICK) ||
@@ -91,6 +72,10 @@
return "DEEP_PRESS";
case MotionClassification::TWO_FINGER_SWIPE:
return "TWO_FINGER_SWIPE";
+ case MotionClassification::MULTI_FINGER_SWIPE:
+ return "MULTI_FINGER_SWIPE";
+ case MotionClassification::PINCH:
+ return "PINCH";
}
}
@@ -172,6 +157,25 @@
return transformedXy - transformedOrigin;
}
+float transformAngle(const ui::Transform& transform, float angleRadians) {
+ // Construct and transform a vector oriented at the specified clockwise angle from vertical.
+ // Coordinate system: down is increasing Y, right is increasing X.
+ float x = sinf(angleRadians);
+ float y = -cosf(angleRadians);
+ vec2 transformedPoint = transform.transform(x, y);
+
+ // Determine how the origin is transformed by the matrix so that we
+ // can transform orientation vectors.
+ const vec2 origin = transform.transform(0, 0);
+
+ transformedPoint.x -= origin.x;
+ transformedPoint.y -= origin.y;
+
+ // Derive the transformed vector's clockwise angle from vertical.
+ // The return value of atan2f is in range [-pi, pi] which conforms to the orientation API.
+ return atan2f(transformedPoint.x, -transformedPoint.y);
+}
+
const char* inputEventTypeToString(int32_t type) {
switch (type) {
case AINPUT_EVENT_TYPE_KEY: {
@@ -411,6 +415,8 @@
for (uint32_t i = 0; i < count; i++) {
values[i] = parcel->readFloat();
}
+
+ isResampled = parcel->readBool();
return OK;
}
@@ -421,6 +427,8 @@
for (uint32_t i = 0; i < count; i++) {
parcel->writeFloat(values[i]);
}
+
+ parcel->writeBool(isResampled);
return OK;
}
#endif
@@ -440,6 +448,9 @@
return false;
}
}
+ if (isResampled != other.isResampled) {
+ return false;
+ }
return true;
}
diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp
index 4751a7d..87333f2 100644
--- a/libs/input/InputDevice.cpp
+++ b/libs/input/InputDevice.cpp
@@ -26,7 +26,6 @@
#include <input/InputEventLabels.h>
using android::base::StringPrintf;
-using android::hardware::input::InputDeviceCountryCode;
namespace android {
@@ -178,7 +177,7 @@
mAlias(other.mAlias),
mIsExternal(other.mIsExternal),
mHasMic(other.mHasMic),
- mCountryCode(other.mCountryCode),
+ mKeyboardLayoutInfo(other.mKeyboardLayoutInfo),
mSources(other.mSources),
mKeyboardType(other.mKeyboardType),
mKeyCharacterMap(other.mKeyCharacterMap),
@@ -196,7 +195,7 @@
void InputDeviceInfo::initialize(int32_t id, int32_t generation, int32_t controllerNumber,
const InputDeviceIdentifier& identifier, const std::string& alias,
- bool isExternal, bool hasMic, InputDeviceCountryCode countryCode) {
+ bool isExternal, bool hasMic) {
mId = id;
mGeneration = generation;
mControllerNumber = controllerNumber;
@@ -204,7 +203,6 @@
mAlias = alias;
mIsExternal = isExternal;
mHasMic = hasMic;
- mCountryCode = countryCode;
mSources = 0;
mKeyboardType = AINPUT_KEYBOARD_TYPE_NONE;
mHasVibrator = false;
@@ -270,6 +268,10 @@
mKeyboardType = std::max(mKeyboardType, keyboardType);
}
+void InputDeviceInfo::setKeyboardLayoutInfo(KeyboardLayoutInfo layoutInfo) {
+ mKeyboardLayoutInfo = std::move(layoutInfo);
+}
+
std::vector<InputDeviceSensorInfo> InputDeviceInfo::getSensors() {
std::vector<InputDeviceSensorInfo> infos;
infos.reserve(mSensors.size());
diff --git a/libs/input/InputEventLabels.cpp b/libs/input/InputEventLabels.cpp
index dd7cbb5..7159e27 100644
--- a/libs/input/InputEventLabels.cpp
+++ b/libs/input/InputEventLabels.cpp
@@ -394,7 +394,10 @@
DEFINE_AXIS(GENERIC_15), \
DEFINE_AXIS(GENERIC_16), \
DEFINE_AXIS(GESTURE_X_OFFSET), \
- DEFINE_AXIS(GESTURE_Y_OFFSET)
+ DEFINE_AXIS(GESTURE_Y_OFFSET), \
+ DEFINE_AXIS(GESTURE_SCROLL_X_DISTANCE), \
+ DEFINE_AXIS(GESTURE_SCROLL_Y_DISTANCE), \
+ DEFINE_AXIS(GESTURE_PINCH_SCALE_FACTOR)
// NOTE: If you add new LEDs here, you must also add them to Input.h
#define LEDS_SEQUENCE \
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index 8d8433b..9f0a314 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -267,6 +267,8 @@
memcpy(&msg->body.motion.pointers[i].coords.values[0],
&body.motion.pointers[i].coords.values[0],
count * (sizeof(body.motion.pointers[i].coords.values[0])));
+ msg->body.motion.pointers[i].coords.isResampled =
+ body.motion.pointers[i].coords.isResampled;
}
break;
}
@@ -1079,6 +1081,7 @@
#endif
msgCoords.setAxisValue(AMOTION_EVENT_AXIS_X, resampleCoords.getX());
msgCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, resampleCoords.getY());
+ msgCoords.isResampled = true;
} else {
state.lastResample.idBits.clearBit(id);
}
@@ -1191,6 +1194,8 @@
// We maintain the previously resampled value for this pointer (stored in
// oldLastResample) when the coordinates for this pointer haven't changed since then.
// This way we don't introduce artificial jitter when pointers haven't actually moved.
+ // The isResampled flag isn't cleared as the values don't reflect what the device is
+ // actually reporting.
// We know here that the coordinates for the pointer haven't changed because we
// would've cleared the resampled bit in rewriteMessage if they had. We can't modify
@@ -1209,6 +1214,7 @@
lerp(currentCoords.getX(), otherCoords.getX(), alpha));
resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y,
lerp(currentCoords.getY(), otherCoords.getY(), alpha));
+ resampledCoords.isResampled = true;
#if DEBUG_RESAMPLING
ALOGD("[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f), "
"other (%0.3f, %0.3f), alpha %0.3f",
diff --git a/libs/input/KeyCharacterMap.cpp b/libs/input/KeyCharacterMap.cpp
index fa5c41f..6bfac40 100644
--- a/libs/input/KeyCharacterMap.cpp
+++ b/libs/input/KeyCharacterMap.cpp
@@ -255,6 +255,13 @@
mLayoutOverlayApplied = true;
}
+void KeyCharacterMap::clearLayoutOverlay() {
+ if (mLayoutOverlayApplied) {
+ reloadBaseFromFile();
+ mLayoutOverlayApplied = false;
+ }
+}
+
KeyCharacterMap::KeyboardType KeyCharacterMap::getKeyboardType() const {
return mType;
}
diff --git a/libs/input/MotionPredictor.cpp b/libs/input/MotionPredictor.cpp
new file mode 100644
index 0000000..0fa0f12
--- /dev/null
+++ b/libs/input/MotionPredictor.cpp
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MotionPredictor"
+
+#include <input/MotionPredictor.h>
+
+/**
+ * Log debug messages about predictions.
+ * Enable this via "adb shell setprop log.tag.MotionPredictor DEBUG"
+ */
+static bool isDebug() {
+ return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG, ANDROID_LOG_INFO);
+}
+
+namespace android {
+
+// --- MotionPredictor ---
+
+MotionPredictor::MotionPredictor(nsecs_t predictionTimestampOffsetNanos,
+ std::function<bool()> checkMotionPredictionEnabled)
+ : mPredictionTimestampOffsetNanos(predictionTimestampOffsetNanos),
+ mCheckMotionPredictionEnabled(std::move(checkMotionPredictionEnabled)) {}
+
+void MotionPredictor::record(const MotionEvent& event) {
+ mEvents.push_back({});
+ mEvents.back().copyFrom(&event, /*keepHistory=*/true);
+ if (mEvents.size() > 2) {
+ // Just need 2 samples in order to extrapolate
+ mEvents.erase(mEvents.begin());
+ }
+}
+
+/**
+ * This is an example implementation that should be replaced with the actual prediction.
+ * The returned MotionEvent should be similar to the incoming MotionEvent, except for the
+ * fields that are predicted:
+ *
+ * 1) event.getEventTime
+ * 2) event.getPointerCoords
+ *
+ * The returned event should not contain any of the real, existing data. It should only
+ * contain the predicted samples.
+ */
+std::vector<std::unique_ptr<MotionEvent>> MotionPredictor::predict(nsecs_t timestamp) {
+ if (mEvents.size() < 2) {
+ return {};
+ }
+
+ const MotionEvent& event = mEvents.back();
+ if (!isPredictionAvailable(event.getDeviceId(), event.getSource())) {
+ return {};
+ }
+
+ std::unique_ptr<MotionEvent> prediction = std::make_unique<MotionEvent>();
+ std::vector<PointerCoords> futureCoords;
+ const nsecs_t futureTime = timestamp + mPredictionTimestampOffsetNanos;
+ const nsecs_t currentTime = event.getEventTime();
+ const MotionEvent& previous = mEvents.rbegin()[1];
+ const nsecs_t oldTime = previous.getEventTime();
+ if (currentTime == oldTime) {
+ // This can happen if it's an ACTION_POINTER_DOWN event, for example.
+ return {}; // prevent division by zero.
+ }
+
+ for (size_t i = 0; i < event.getPointerCount(); i++) {
+ const int32_t pointerId = event.getPointerId(i);
+ const PointerCoords* currentPointerCoords = event.getRawPointerCoords(i);
+ const float currentX = currentPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_X);
+ const float currentY = currentPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_Y);
+
+ PointerCoords coords;
+ coords.clear();
+
+ ssize_t index = previous.findPointerIndex(pointerId);
+ if (index >= 0) {
+ // We have old data for this pointer. Compute the prediction.
+ const PointerCoords* oldPointerCoords = previous.getRawPointerCoords(index);
+ const float oldX = oldPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_X);
+ const float oldY = oldPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_Y);
+
+ // Let's do a linear interpolation while waiting for a real model
+ const float scale =
+ static_cast<float>(futureTime - currentTime) / (currentTime - oldTime);
+ const float futureX = currentX + (currentX - oldX) * scale;
+ const float futureY = currentY + (currentY - oldY) * scale;
+
+ coords.setAxisValue(AMOTION_EVENT_AXIS_X, futureX);
+ coords.setAxisValue(AMOTION_EVENT_AXIS_Y, futureY);
+ ALOGD_IF(isDebug(),
+ "Prediction by %.1f ms, (%.1f, %.1f), (%.1f, %.1f) --> (%.1f, %.1f)",
+ (futureTime - event.getEventTime()) * 1E-6, oldX, oldY, currentX, currentY,
+ futureX, futureY);
+ }
+
+ futureCoords.push_back(coords);
+ }
+
+ /**
+ * The process of adding samples is different for the first and subsequent samples:
+ * 1. Add the first sample via 'initialize' as below
+ * 2. Add subsequent samples via 'addSample'
+ */
+ prediction->initialize(event.getId(), event.getDeviceId(), event.getSource(),
+ event.getDisplayId(), event.getHmac(), event.getAction(),
+ event.getActionButton(), event.getFlags(), event.getEdgeFlags(),
+ event.getMetaState(), event.getButtonState(), event.getClassification(),
+ event.getTransform(), event.getXPrecision(), event.getYPrecision(),
+ event.getRawXCursorPosition(), event.getRawYCursorPosition(),
+ event.getRawTransform(), event.getDownTime(), futureTime,
+ event.getPointerCount(), event.getPointerProperties(),
+ futureCoords.data());
+
+ // To add more predicted samples, use 'addSample':
+ prediction->addSample(futureTime + 1, futureCoords.data());
+
+ std::vector<std::unique_ptr<MotionEvent>> out;
+ out.push_back(std::move(prediction));
+ return out;
+}
+
+bool MotionPredictor::isPredictionAvailable(int32_t /*deviceId*/, int32_t source) {
+ // Global flag override
+ if (!mCheckMotionPredictionEnabled()) {
+ ALOGD_IF(isDebug(), "Prediction not available due to flag override");
+ return false;
+ }
+
+ // Prediction is only supported for stylus sources.
+ if (!isFromSource(source, AINPUT_SOURCE_STYLUS)) {
+ ALOGD_IF(isDebug(), "Prediction not available for non-stylus source: %s",
+ inputEventSourceToString(source).c_str());
+ return false;
+ }
+ return true;
+}
+
+} // namespace android
diff --git a/libs/input/android/hardware/input/InputDeviceCountryCode.aidl b/libs/input/android/hardware/input/InputDeviceCountryCode.aidl
deleted file mode 100644
index 6bb1a60..0000000
--- a/libs/input/android/hardware/input/InputDeviceCountryCode.aidl
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hardware.input;
-
-/**
- * Constant for HID country code declared by a HID device. These constants are declared as AIDL to
- * be used by java and native input code.
- *
- * @hide
- */
-@Backing(type="int")
-enum InputDeviceCountryCode {
- /**
- * Used as default value where country code is not set in the device HID descriptor
- */
- INVALID = -1,
-
- /**
- * Used as default value when country code is not supported by the HID device. The HID
- * descriptor sets "00" as the country code in this case.
- */
- NOT_SUPPORTED = 0,
-
- /**
- * Arabic
- */
- ARABIC = 1,
-
- /**
- * Belgian
- */
- BELGIAN = 2,
-
- /**
- * Canadian (Bilingual)
- */
- CANADIAN_BILINGUAL = 3,
-
- /**
- * Canadian (French)
- */
- CANADIAN_FRENCH = 4,
-
- /**
- * Czech Republic
- */
- CZECH_REPUBLIC = 5,
-
- /**
- * Danish
- */
- DANISH = 6,
-
- /**
- * Finnish
- */
- FINNISH = 7,
-
- /**
- * French
- */
- FRENCH = 8,
-
- /**
- * German
- */
- GERMAN = 9,
-
- /**
- * Greek
- */
- GREEK = 10,
-
- /**
- * Hebrew
- */
- HEBREW = 11,
-
- /**
- * Hungary
- */
- HUNGARY = 12,
-
- /**
- * International (ISO)
- */
- INTERNATIONAL = 13,
-
- /**
- * Italian
- */
- ITALIAN = 14,
-
- /**
- * Japan (Katakana)
- */
- JAPAN = 15,
-
- /**
- * Korean
- */
- KOREAN = 16,
-
- /**
- * Latin American
- */
- LATIN_AMERICAN = 17,
-
- /**
- * Netherlands (Dutch)
- */
- DUTCH = 18,
-
- /**
- * Norwegian
- */
- NORWEGIAN = 19,
-
- /**
- * Persian
- */
- PERSIAN = 20,
-
- /**
- * Poland
- */
- POLAND = 21,
-
- /**
- * Portuguese
- */
- PORTUGUESE = 22,
-
- /**
- * Russia
- */
- RUSSIA = 23,
-
- /**
- * Slovakia
- */
- SLOVAKIA = 24,
-
- /**
- * Spanish
- */
- SPANISH = 25,
-
- /**
- * Swedish
- */
- SWEDISH = 26,
-
- /**
- * Swiss (French)
- */
- SWISS_FRENCH = 27,
-
- /**
- * Swiss (German)
- */
- SWISS_GERMAN = 28,
-
- /**
- * Switzerland
- */
- SWITZERLAND = 29,
-
- /**
- * Taiwan
- */
- TAIWAN = 30,
-
- /**
- * Turkish_Q
- */
- TURKISH_Q = 31,
-
- /**
- * UK
- */
- UK = 32,
-
- /**
- * US
- */
- US = 33,
-
- /**
- * Yugoslavia
- */
- YUGOSLAVIA = 34,
-
- /**
- * Turkish_F
- */
- TURKISH_F = 35,
-}
\ No newline at end of file
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index 5aae37d..e2c0860 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -17,6 +17,7 @@
"InputDevice_test.cpp",
"InputEvent_test.cpp",
"InputPublisherAndConsumer_test.cpp",
+ "MotionPredictor_test.cpp",
"TouchResampling_test.cpp",
"TouchVideoFrame_test.cpp",
"VelocityTracker_test.cpp",
@@ -37,6 +38,7 @@
"libbinder",
"libcutils",
"liblog",
+ "libPlatformProperties",
"libutils",
"libvintf",
],
diff --git a/libs/input/tests/InputEvent_test.cpp b/libs/input/tests/InputEvent_test.cpp
index 4b31246..8a6e983 100644
--- a/libs/input/tests/InputEvent_test.cpp
+++ b/libs/input/tests/InputEvent_test.cpp
@@ -46,6 +46,7 @@
coords.clear();
ASSERT_EQ(0ULL, coords.bits);
+ ASSERT_FALSE(coords.isResampled);
}
TEST_F(PointerCoordsTest, AxisValues) {
@@ -158,11 +159,13 @@
outCoords.readFromParcel(&parcel);
ASSERT_EQ(0ULL, outCoords.bits);
+ ASSERT_FALSE(outCoords.isResampled);
// Round trip with some values.
parcel.freeData();
inCoords.setAxisValue(2, 5);
inCoords.setAxisValue(5, 8);
+ inCoords.isResampled = true;
inCoords.writeToParcel(&parcel);
parcel.setDataPosition(0);
@@ -171,6 +174,7 @@
ASSERT_EQ(outCoords.bits, inCoords.bits);
ASSERT_EQ(outCoords.values[0], inCoords.values[0]);
ASSERT_EQ(outCoords.values[1], inCoords.values[1]);
+ ASSERT_TRUE(outCoords.isResampled);
}
@@ -263,6 +267,7 @@
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 16);
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 17);
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 18);
+ pointerCoords[0].isResampled = true;
pointerCoords[1].clear();
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 20);
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 21);
@@ -281,6 +286,7 @@
mRawTransform, ARBITRARY_DOWN_TIME, ARBITRARY_EVENT_TIME, 2,
pointerProperties, pointerCoords);
+ pointerCoords[0].clear();
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 110);
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 111);
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 112);
@@ -290,6 +296,8 @@
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 116);
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 117);
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 118);
+ pointerCoords[0].isResampled = true;
+ pointerCoords[1].clear();
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 120);
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 121);
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 122);
@@ -299,8 +307,10 @@
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 126);
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 127);
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 128);
+ pointerCoords[1].isResampled = true;
event->addSample(ARBITRARY_EVENT_TIME + 1, pointerCoords);
+ pointerCoords[0].clear();
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 210);
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 211);
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 212);
@@ -310,6 +320,7 @@
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 216);
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 217);
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 218);
+ pointerCoords[1].clear();
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 220);
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 221);
pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 222);
@@ -457,6 +468,13 @@
ASSERT_EQ(toScaledOrientation(128), event->getHistoricalOrientation(1, 1));
ASSERT_EQ(toScaledOrientation(218), event->getOrientation(0));
ASSERT_EQ(toScaledOrientation(228), event->getOrientation(1));
+
+ ASSERT_TRUE(event->isResampled(0, 0));
+ ASSERT_FALSE(event->isResampled(1, 0));
+ ASSERT_TRUE(event->isResampled(0, 1));
+ ASSERT_TRUE(event->isResampled(1, 1));
+ ASSERT_FALSE(event->isResampled(0, 2));
+ ASSERT_FALSE(event->isResampled(1, 2));
}
TEST_F(MotionEventTest, Properties) {
diff --git a/libs/input/tests/MotionPredictor_test.cpp b/libs/input/tests/MotionPredictor_test.cpp
new file mode 100644
index 0000000..d2b59a1
--- /dev/null
+++ b/libs/input/tests/MotionPredictor_test.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+#include <gui/constants.h>
+#include <input/Input.h>
+#include <input/MotionPredictor.h>
+
+namespace android {
+
+constexpr int32_t DOWN = AMOTION_EVENT_ACTION_DOWN;
+constexpr int32_t MOVE = AMOTION_EVENT_ACTION_MOVE;
+
+static MotionEvent getMotionEvent(int32_t action, float x, float y, nsecs_t eventTime) {
+ MotionEvent event;
+ constexpr size_t pointerCount = 1;
+ std::vector<PointerProperties> pointerProperties;
+ std::vector<PointerCoords> pointerCoords;
+ for (size_t i = 0; i < pointerCount; i++) {
+ PointerProperties properties;
+ properties.clear();
+ properties.id = i;
+ pointerProperties.push_back(properties);
+ PointerCoords coords;
+ coords.clear();
+ coords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
+ coords.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+ pointerCoords.push_back(coords);
+ }
+
+ ui::Transform identityTransform;
+ event.initialize(InputEvent::nextId(), /*deviceId=*/0, AINPUT_SOURCE_STYLUS,
+ ADISPLAY_ID_DEFAULT, {0}, action, /*actionButton=*/0, /*flags=*/0,
+ AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, /*buttonState=*/0,
+ MotionClassification::NONE, identityTransform, /*xPrecision=*/0.1,
+ /*yPrecision=*/0.2, /*xCursorPosition=*/280, /*yCursorPosition=*/540,
+ identityTransform, /*downTime=*/100, eventTime, pointerCount,
+ pointerProperties.data(), pointerCoords.data());
+ return event;
+}
+
+/**
+ * A linear motion should be predicted to be linear in the future
+ */
+TEST(MotionPredictorTest, LinearPrediction) {
+ MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
+ []() { return true /*enable prediction*/; });
+
+ predictor.record(getMotionEvent(DOWN, 0, 1, 0));
+ predictor.record(getMotionEvent(MOVE, 1, 3, 10));
+ predictor.record(getMotionEvent(MOVE, 2, 5, 20));
+ predictor.record(getMotionEvent(MOVE, 3, 7, 30));
+ std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict(40);
+ ASSERT_EQ(1u, predicted.size());
+ ASSERT_EQ(predicted[0]->getX(0), 4);
+ ASSERT_EQ(predicted[0]->getY(0), 9);
+}
+
+/**
+ * A still motion should be predicted to remain still
+ */
+TEST(MotionPredictorTest, StationaryPrediction) {
+ MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
+ []() { return true /*enable prediction*/; });
+
+ predictor.record(getMotionEvent(DOWN, 0, 1, 0));
+ predictor.record(getMotionEvent(MOVE, 0, 1, 10));
+ predictor.record(getMotionEvent(MOVE, 0, 1, 20));
+ predictor.record(getMotionEvent(MOVE, 0, 1, 30));
+ std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict(40);
+ ASSERT_EQ(1u, predicted.size());
+ ASSERT_EQ(predicted[0]->getX(0), 0);
+ ASSERT_EQ(predicted[0]->getY(0), 1);
+}
+
+TEST(MotionPredictorTest, IsPredictionAvailable) {
+ MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
+ []() { return true /*enable prediction*/; });
+ ASSERT_TRUE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_STYLUS));
+ ASSERT_FALSE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_TOUCHSCREEN));
+}
+
+TEST(MotionPredictorTest, Offset) {
+ MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/1,
+ []() { return true /*enable prediction*/; });
+ predictor.record(getMotionEvent(DOWN, 0, 1, 30));
+ predictor.record(getMotionEvent(MOVE, 0, 1, 35));
+ std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict(40);
+ ASSERT_EQ(1u, predicted.size());
+ ASSERT_GE(predicted[0]->getEventTime(), 41);
+}
+
+TEST(MotionPredictorTest, FlagDisablesPrediction) {
+ MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
+ []() { return false /*disable prediction*/; });
+ predictor.record(getMotionEvent(DOWN, 0, 1, 30));
+ predictor.record(getMotionEvent(MOVE, 0, 1, 35));
+ std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict(40);
+ ASSERT_EQ(0u, predicted.size());
+ ASSERT_FALSE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_STYLUS));
+ ASSERT_FALSE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_TOUCHSCREEN));
+}
+
+} // namespace android
diff --git a/libs/input/tests/StructLayout_test.cpp b/libs/input/tests/StructLayout_test.cpp
index 1c8658b..024b6d3 100644
--- a/libs/input/tests/StructLayout_test.cpp
+++ b/libs/input/tests/StructLayout_test.cpp
@@ -117,7 +117,7 @@
void TestBodySize() {
static_assert(sizeof(InputMessage::Body::Key) == 96);
- static_assert(sizeof(InputMessage::Body::Motion::Pointer) == 136);
+ static_assert(sizeof(InputMessage::Body::Motion::Pointer) == 144);
static_assert(sizeof(InputMessage::Body::Motion) ==
offsetof(InputMessage::Body::Motion, pointers) +
sizeof(InputMessage::Body::Motion::Pointer) * MAX_POINTERS);
@@ -137,8 +137,8 @@
static_assert(sizeof(InputMessage::Body) ==
offsetof(InputMessage::Body::Motion, pointers) +
sizeof(InputMessage::Body::Motion::Pointer) * MAX_POINTERS);
- static_assert(sizeof(InputMessage::Body) == 160 + 136 * 16);
- static_assert(sizeof(InputMessage::Body) == 2336);
+ static_assert(sizeof(InputMessage::Body) == 160 + 144 * 16);
+ static_assert(sizeof(InputMessage::Body) == 2464);
}
/**
@@ -148,8 +148,8 @@
* still helpful to compute to get an idea of the sizes that are involved.
*/
void TestWorstCaseInputMessageSize() {
- static_assert(sizeof(InputMessage) == /*header*/ 8 + /*body*/ 2336);
- static_assert(sizeof(InputMessage) == 2344);
+ static_assert(sizeof(InputMessage) == /*header*/ 8 + /*body*/ 2464);
+ static_assert(sizeof(InputMessage) == 2472);
}
/**
@@ -159,8 +159,8 @@
constexpr size_t pointerCount = 1;
constexpr size_t bodySize = offsetof(InputMessage::Body::Motion, pointers) +
sizeof(InputMessage::Body::Motion::Pointer) * pointerCount;
- static_assert(bodySize == 160 + 136);
- static_assert(bodySize == 296); // For the total message size, add the small header
+ static_assert(bodySize == 160 + 144);
+ static_assert(bodySize == 304); // For the total message size, add the small header
}
// --- VerifiedInputEvent ---
diff --git a/libs/input/tests/TouchResampling_test.cpp b/libs/input/tests/TouchResampling_test.cpp
index c09a8e9..d01258c 100644
--- a/libs/input/tests/TouchResampling_test.cpp
+++ b/libs/input/tests/TouchResampling_test.cpp
@@ -31,6 +31,7 @@
int32_t id;
float x;
float y;
+ bool isResampled = false;
};
struct InputEventEntry {
@@ -190,6 +191,8 @@
ASSERT_EQ(entry.pointers[p].y,
motionEvent->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y,
motionEventPointerIndex, i));
+ ASSERT_EQ(entry.pointers[p].isResampled,
+ motionEvent->isResampled(motionEventPointerIndex, i));
}
}
@@ -244,7 +247,7 @@
// id x y
{10ms, {{0, 20, 30}}, AMOTION_EVENT_ACTION_MOVE},
{20ms, {{0, 30, 30}}, AMOTION_EVENT_ACTION_MOVE},
- {25ms, {{0, 35, 30}}, AMOTION_EVENT_ACTION_MOVE}, // resampled value
+ {25ms, {{0, 35, 30, .isResampled = true}}, AMOTION_EVENT_ACTION_MOVE},
};
consumeInputEventEntries(expectedEntries, frameTime);
}
@@ -283,7 +286,7 @@
// id x y
{10ms, {{1, 20, 30}}, AMOTION_EVENT_ACTION_MOVE},
{20ms, {{1, 30, 30}}, AMOTION_EVENT_ACTION_MOVE},
- {25ms, {{1, 35, 30}}, AMOTION_EVENT_ACTION_MOVE}, // resampled value
+ {25ms, {{1, 35, 30, .isResampled = true}}, AMOTION_EVENT_ACTION_MOVE},
};
consumeInputEventEntries(expectedEntries, frameTime);
}
@@ -361,7 +364,7 @@
// id x y
{10ms, {{0, 20, 30}}, AMOTION_EVENT_ACTION_MOVE},
{20ms, {{0, 30, 30}}, AMOTION_EVENT_ACTION_MOVE},
- {25ms, {{0, 35, 30}}, AMOTION_EVENT_ACTION_MOVE}, // resampled value
+ {25ms, {{0, 35, 30, .isResampled = true}}, AMOTION_EVENT_ACTION_MOVE},
};
consumeInputEventEntries(expectedEntries, frameTime);
@@ -375,8 +378,12 @@
frameTime = 45ms + 5ms /*RESAMPLE_LATENCY*/;
expectedEntries = {
// id x y
- {40ms, {{0, 35, 30}}, AMOTION_EVENT_ACTION_MOVE}, // original event, rewritten
- {45ms, {{0, 35, 30}}, AMOTION_EVENT_ACTION_MOVE}, // resampled event, rewritten
+ {40ms,
+ {{0, 35, 30, .isResampled = true}},
+ AMOTION_EVENT_ACTION_MOVE}, // original event, rewritten
+ {45ms,
+ {{0, 35, 30, .isResampled = true}},
+ AMOTION_EVENT_ACTION_MOVE}, // resampled event, rewritten
};
consumeInputEventEntries(expectedEntries, frameTime);
}
@@ -411,7 +418,7 @@
// id x y
{10ms, {{0, 20, 30}}, AMOTION_EVENT_ACTION_MOVE},
{20ms, {{0, 30, 30}}, AMOTION_EVENT_ACTION_MOVE},
- {25ms, {{0, 35, 30}}, AMOTION_EVENT_ACTION_MOVE}, // resampled value
+ {25ms, {{0, 35, 30, .isResampled = true}}, AMOTION_EVENT_ACTION_MOVE},
};
consumeInputEventEntries(expectedEntries, frameTime);
// Above, the resampled event is at 25ms rather than at 30 ms = 35ms - RESAMPLE_LATENCY
@@ -428,8 +435,12 @@
frameTime = 50ms;
expectedEntries = {
// id x y
- {24ms, {{0, 35, 30}}, AMOTION_EVENT_ACTION_MOVE}, // original event, rewritten
- {26ms, {{0, 45, 30}}, AMOTION_EVENT_ACTION_MOVE}, // resampled event, rewritten
+ {24ms,
+ {{0, 35, 30, .isResampled = true}},
+ AMOTION_EVENT_ACTION_MOVE}, // original event, rewritten
+ {26ms,
+ {{0, 45, 30, .isResampled = true}},
+ AMOTION_EVENT_ACTION_MOVE}, // resampled event, rewritten
};
consumeInputEventEntries(expectedEntries, frameTime);
}
@@ -499,7 +510,9 @@
// id x y
{30ms, {{0, 100, 100}, {1, 500, 500}}, AMOTION_EVENT_ACTION_MOVE},
{40ms, {{0, 120, 120}, {1, 600, 600}}, AMOTION_EVENT_ACTION_MOVE},
- {45ms, {{0, 130, 130}, {1, 650, 650}}, AMOTION_EVENT_ACTION_MOVE}, // resampled value
+ {45ms,
+ {{0, 130, 130, .isResampled = true}, {1, 650, 650, .isResampled = true}},
+ AMOTION_EVENT_ACTION_MOVE},
};
consumeInputEventEntries(expectedEntries, frameTime);
@@ -518,11 +531,13 @@
*/
expectedEntries = {
{60ms,
- {{0, 130, 130}, // not 120! because it matches previous real event
- {1, 650, 650}},
+ {{0, 130, 130, .isResampled = true}, // not 120! because it matches previous real event
+ {1, 650, 650, .isResampled = true}},
AMOTION_EVENT_ACTION_MOVE},
{70ms, {{0, 130, 130}, {1, 700, 700}}, AMOTION_EVENT_ACTION_MOVE},
- {75ms, {{0, 135, 135}, {1, 750, 750}}, AMOTION_EVENT_ACTION_MOVE}, // resampled value
+ {75ms,
+ {{0, 135, 135, .isResampled = true}, {1, 750, 750, .isResampled = true}},
+ AMOTION_EVENT_ACTION_MOVE},
};
consumeInputEventEntries(expectedEntries, frameTime);
@@ -554,7 +569,7 @@
* The latest event with ACTION_MOVE was at t = 70, coord = 700.
* Use that value for resampling here: (600 - 700) / (90 - 70) * 5 + 600
*/
- {95ms, {{1, 575, 575}}, AMOTION_EVENT_ACTION_MOVE}, // resampled value
+ {95ms, {{1, 575, 575, .isResampled = true}}, AMOTION_EVENT_ACTION_MOVE},
};
consumeInputEventEntries(expectedEntries, frameTime);
}
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
index 5c9c8b6..d0de48f 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
@@ -36,18 +36,18 @@
JpegDecoder();
~JpegDecoder();
/*
- * Decompresses JPEG image to raw image (YUV420planer or grey-scale) format. After calling
- * this method, call getDecompressedImage() to get the image.
+ * Decompresses JPEG image to raw image (YUV420planer, grey-scale or RGBA) format. After
+ * calling this method, call getDecompressedImage() to get the image.
* Returns false if decompressing the image fails.
*/
- bool decompressImage(const void* image, int length);
+ bool decompressImage(const void* image, int length, bool decodeToRGBA = false);
/*
* Returns the decompressed raw image buffer pointer. This method must be called only after
* calling decompressImage().
*/
void* getDecompressedImagePtr();
/*
- * Returns the decompressed raw image buffer size. This method must be called only after
+ * Returns the decompressed raw image buffer size. This mgit ethod must be called only after
* calling decompressImage().
*/
size_t getDecompressedImageSize();
@@ -67,20 +67,42 @@
void* getXMPPtr();
/*
* Returns the decompressed XMP buffer size. This method must be called only after
- * calling decompressImage().
+ * calling decompressImage() or getCompressedImageParameters().
*/
size_t getXMPSize();
-
+ /*
+ * Returns the EXIF data from the image.
+ */
+ void* getEXIFPtr();
+ /*
+ * Returns the decompressed EXIF buffer size. This method must be called only after
+ * calling decompressImage() or getCompressedImageParameters().
+ */
+ 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.
+ */
+ int getEXIFPos() { return mExifPos; }
+ /*
+ * Decompresses metadata of the image. All vectors are owned by the caller.
+ */
bool getCompressedImageParameters(const void* image, int length,
- size_t* pWidth, size_t* pHeight,
- std::vector<uint8_t>* &iccData,
- std::vector<uint8_t>* &exifData);
+ size_t* pWidth, size_t* pHeight,
+ std::vector<uint8_t>* iccData,
+ std::vector<uint8_t>* exifData);
+ /*
+ * Extracts EXIF package and updates the EXIF position / length without decoding the image.
+ */
+ bool extractEXIF(const void* image, int length);
private:
- bool decode(const void* image, int length);
+ bool decode(const void* image, int length, bool decodeToRGBA);
// Returns false if errors occur.
bool decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest, bool isSingleChannel);
bool decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest);
+ bool decompressRGBA(jpeg_decompress_struct* cinfo, const uint8_t* dest);
bool decompressSingleChannel(jpeg_decompress_struct* cinfo, const uint8_t* dest);
// Process 16 lines of Y and 16 lines of U/V each time.
// We must pass at least 16 scanlines according to libjpeg documentation.
@@ -89,10 +111,14 @@
std::vector<JOCTET> mResultBuffer;
// The buffer that holds XMP Data.
std::vector<JOCTET> mXMPBuffer;
+ // The buffer that holds EXIF Data.
+ std::vector<JOCTET> mEXIFBuffer;
// 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 */
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
index 50ccdff..3597903 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
@@ -244,7 +244,8 @@
*
* The output is filled jpegr_info structure
* @param compressed_jpegr_image compressed JPEGR image
- * @param jpegr_info pointer to output JPEGR info
+ * @param jpegr_info pointer to output JPEGR info. Members of jpegr_info
+ * are owned by the caller
* @return NO_ERROR if JPEGR parsing succeeds, error code otherwise
*/
status_t getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image,
@@ -325,23 +326,25 @@
*
* @param compressed_jpeg_image compressed 8-bit JPEG image
* @param compress_recovery_map compressed recover map
+ * @param exif EXIF 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.
*/
status_t appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
jr_compressed_ptr compressed_recovery_map,
+ jr_exif_ptr exif,
jr_metadata_ptr metadata,
jr_compressed_ptr dest);
/*
* This method will tone map a HDR image to an SDR image.
*
- * @param uncompressed_p010_image (input) uncompressed P010 image
+ * @param src (input) uncompressed P010 image
* @param dest (output) tone mapping result as a YUV_420 image
* @return NO_ERROR if calculation succeeds, error code if error occurs.
*/
- status_t toneMap(jr_uncompressed_ptr uncompressed_p010_image,
+ status_t toneMap(jr_uncompressed_ptr src,
jr_uncompressed_ptr dest);
};
diff --git a/libs/jpegrecoverymap/jpegdecoder.cpp b/libs/jpegrecoverymap/jpegdecoder.cpp
index 0185e55..0ae6a63 100644
--- a/libs/jpegrecoverymap/jpegdecoder.cpp
+++ b/libs/jpegrecoverymap/jpegdecoder.cpp
@@ -26,8 +26,12 @@
namespace android::recoverymap {
-const uint32_t kExifMarker = JPEG_APP0 + 1;
-const uint32_t kICCMarker = JPEG_APP0 + 2;
+const uint32_t kAPP0Marker = JPEG_APP0; // JFIF
+const uint32_t kAPP1Marker = JPEG_APP0 + 1; // EXIF, XMP
+const uint32_t kAPP2Marker = JPEG_APP0 + 2; // ICC
+
+const std::string kXmpNameSpace = "http://ns.adobe.com/xap/1.0/";
+const std::string kExifIdCode = "Exif";
struct jpegr_source_mgr : jpeg_source_mgr {
jpegr_source_mgr(const uint8_t* ptr, int len);
@@ -83,12 +87,13 @@
}
JpegDecoder::JpegDecoder() {
+ mExifPos = 0;
}
JpegDecoder::~JpegDecoder() {
}
-bool JpegDecoder::decompressImage(const void* image, int length) {
+bool JpegDecoder::decompressImage(const void* image, int length, bool decodeToRGBA) {
if (image == nullptr || length <= 0) {
ALOGE("Image size can not be handled: %d", length);
return false;
@@ -96,7 +101,7 @@
mResultBuffer.clear();
mXMPBuffer.clear();
- if (!decode(image, length)) {
+ if (!decode(image, length, decodeToRGBA)) {
return false;
}
@@ -119,6 +124,13 @@
return mXMPBuffer.size();
}
+void* JpegDecoder::getEXIFPtr() {
+ return mEXIFBuffer.data();
+}
+
+size_t JpegDecoder::getEXIFSize() {
+ return mEXIFBuffer.size();
+}
size_t JpegDecoder::getDecompressedImageWidth() {
return mWidth;
@@ -128,11 +140,10 @@
return mHeight;
}
-bool JpegDecoder::decode(const void* image, int length) {
+bool JpegDecoder::decode(const void* image, int length, bool decodeToRGBA) {
jpeg_decompress_struct cinfo;
jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
jpegrerror_mgr myerr;
- string nameSpace = "http://ns.adobe.com/xap/1.0/";
cinfo.err = jpeg_std_error(&myerr.pub);
myerr.pub.error_exit = jpegrerror_exit;
@@ -143,38 +154,82 @@
}
jpeg_create_decompress(&cinfo);
- jpeg_save_markers(&cinfo, kExifMarker, 0xFFFF);
+ jpeg_save_markers(&cinfo, kAPP0Marker, 0xFFFF);
+ jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF);
+ jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF);
cinfo.src = &mgr;
jpeg_read_header(&cinfo, TRUE);
- // Save XMP Data
- for (jpeg_marker_struct* marker = cinfo.marker_list; marker; marker = marker->next) {
- if (marker->marker == kExifMarker) {
- const unsigned int len = marker->data_length;
- if (len > nameSpace.size() &&
- !strncmp(reinterpret_cast<const char*>(marker->data),
- nameSpace.c_str(), nameSpace.size())) {
- mXMPBuffer.resize(len+1, 0);
- memcpy(static_cast<void*>(mXMPBuffer.data()), marker->data, len);
- break;
- }
+ // 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...
+ // 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.
+ bool exifAppears = false;
+ bool xmpAppears = false;
+ size_t pos = 2; // position after SOI
+ for (jpeg_marker_struct* marker = cinfo.marker_list;
+ marker && !(exifAppears && xmpAppears);
+ marker = marker->next) {
+
+ pos += 4;
+ pos += marker->original_length;
+
+ if (marker->marker != kAPP1Marker) {
+ continue;
+ }
+
+ const unsigned int len = marker->data_length;
+ if (!xmpAppears &&
+ len > kXmpNameSpace.size() &&
+ !strncmp(reinterpret_cast<const char*>(marker->data),
+ kXmpNameSpace.c_str(),
+ kXmpNameSpace.size())) {
+ mXMPBuffer.resize(len+1, 0);
+ memcpy(static_cast<void*>(mXMPBuffer.data()), marker->data, len);
+ xmpAppears = true;
+ } else if (!exifAppears &&
+ len > kExifIdCode.size() &&
+ !strncmp(reinterpret_cast<const char*>(marker->data),
+ kExifIdCode.c_str(),
+ kExifIdCode.size())) {
+ mEXIFBuffer.resize(len, 0);
+ memcpy(static_cast<void*>(mEXIFBuffer.data()), marker->data, len);
+ exifAppears = true;
+ mExifPos = pos - marker->original_length;
}
}
-
mWidth = cinfo.image_width;
mHeight = cinfo.image_height;
- if (cinfo.jpeg_color_space == JCS_YCbCr) {
- mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 3 / 2, 0);
- } else if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
- mResultBuffer.resize(cinfo.image_width * cinfo.image_height, 0);
+ if (decodeToRGBA) {
+ if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
+ // We don't intend to support decoding grayscale to RGBA
+ return false;
+ }
+ // 4 bytes per pixel
+ mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 4);
+ cinfo.out_color_space = JCS_EXT_RGBA;
+ } else {
+ if (cinfo.jpeg_color_space == JCS_YCbCr) {
+ // 1 byte per pixel for Y, 0.5 byte per pixel for U+V
+ mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 3 / 2, 0);
+ } else if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
+ mResultBuffer.resize(cinfo.image_width * cinfo.image_height, 0);
+ }
+ cinfo.out_color_space = cinfo.jpeg_color_space;
+ cinfo.raw_data_out = TRUE;
}
- cinfo.raw_data_out = TRUE;
cinfo.dct_method = JDCT_IFAST;
- cinfo.out_color_space = cinfo.jpeg_color_space;
jpeg_start_decompress(&cinfo);
@@ -189,17 +244,74 @@
return true;
}
+// TODO (Fyodor/Dichen): merge this method with getCompressedImageParameters() since they have
+// similar functionality. Yet Dichen is not familiar with who's calling
+// getCompressedImageParameters(), looks like it's used by some pending CLs.
+bool JpegDecoder::extractEXIF(const void* image, int length) {
+ jpeg_decompress_struct cinfo;
+ jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
+ jpegrerror_mgr myerr;
+
+ cinfo.err = jpeg_std_error(&myerr.pub);
+ myerr.pub.error_exit = jpegrerror_exit;
+
+ if (setjmp(myerr.setjmp_buffer)) {
+ jpeg_destroy_decompress(&cinfo);
+ return false;
+ }
+ jpeg_create_decompress(&cinfo);
+
+ jpeg_save_markers(&cinfo, kAPP0Marker, 0xFFFF);
+ jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF);
+ jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF);
+
+ cinfo.src = &mgr;
+ jpeg_read_header(&cinfo, TRUE);
+
+ bool exifAppears = false;
+ size_t pos = 2; // position after SOI
+ for (jpeg_marker_struct* marker = cinfo.marker_list;
+ marker && !exifAppears;
+ marker = marker->next) {
+
+ pos += 4;
+ pos += marker->original_length;
+
+ if (marker->marker != kAPP1Marker) {
+ continue;
+ }
+
+ const unsigned int len = marker->data_length;
+ if (!exifAppears &&
+ len > kExifIdCode.size() &&
+ !strncmp(reinterpret_cast<const char*>(marker->data),
+ kExifIdCode.c_str(),
+ kExifIdCode.size())) {
+ mEXIFBuffer.resize(len, 0);
+ memcpy(static_cast<void*>(mEXIFBuffer.data()), marker->data, len);
+ exifAppears = true;
+ mExifPos = pos - marker->original_length;
+ }
+ }
+
+ jpeg_destroy_decompress(&cinfo);
+ return true;
+}
+
bool JpegDecoder::decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest,
bool isSingleChannel) {
if (isSingleChannel) {
return decompressSingleChannel(cinfo, dest);
}
- return decompressYUV(cinfo, dest);
+ if (cinfo->out_color_space == JCS_EXT_RGBA)
+ return decompressRGBA(cinfo, dest);
+ else
+ return decompressYUV(cinfo, dest);
}
bool JpegDecoder::getCompressedImageParameters(const void* image, int length,
size_t *pWidth, size_t *pHeight,
- std::vector<uint8_t> *&iccData , std::vector<uint8_t> *&exifData) {
+ std::vector<uint8_t> *iccData , std::vector<uint8_t> *exifData) {
jpeg_decompress_struct cinfo;
jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
jpegrerror_mgr myerr;
@@ -212,8 +324,8 @@
}
jpeg_create_decompress(&cinfo);
- jpeg_save_markers(&cinfo, kExifMarker, 0xFFFF);
- jpeg_save_markers(&cinfo, kICCMarker, 0xFFFF);
+ jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF);
+ jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF);
cinfo.src = &mgr;
if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) {
@@ -224,15 +336,46 @@
*pWidth = cinfo.image_width;
*pHeight = cinfo.image_height;
- //TODO: Parse iccProfile and exifData
+ //TODO: Parse iccProfile
(void)iccData;
- (void)exifData;
+ if (exifData != nullptr) {
+ bool exifAppears = false;
+ for (jpeg_marker_struct* marker = cinfo.marker_list; marker && !exifAppears;
+ marker = marker->next) {
+ if (marker->marker != kAPP1Marker) {
+ continue;
+ }
+
+ const unsigned int len = marker->data_length;
+ if (len >= kExifIdCode.size() &&
+ !strncmp(reinterpret_cast<const char*>(marker->data), kExifIdCode.c_str(),
+ kExifIdCode.size())) {
+ exifData->resize(len, 0);
+ memcpy(static_cast<void*>(exifData->data()), marker->data, len);
+ exifAppears = true;
+ }
+ }
+ }
jpeg_destroy_decompress(&cinfo);
return true;
}
+bool JpegDecoder::decompressRGBA(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
+ JSAMPLE* decodeDst = (JSAMPLE*) dest;
+ uint32_t lines = 0;
+ // TODO: use batches for more effectiveness
+ while (lines < cinfo->image_height) {
+ uint32_t ret = jpeg_read_scanlines(cinfo, &decodeDst, 1);
+ if (ret == 0) {
+ break;
+ }
+ decodeDst += cinfo->image_width * 4;
+ lines++;
+ }
+ return lines == cinfo->image_height;
+}
bool JpegDecoder::decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp
index fafc319..ee68043 100644
--- a/libs/jpegrecoverymap/recoverymap.cpp
+++ b/libs/jpegrecoverymap/recoverymap.cpp
@@ -25,7 +25,6 @@
#include <image_io/jpeg/jpeg_scanner.h>
#include <image_io/jpeg/jpeg_info_builder.h>
#include <image_io/base/data_segment_data_source.h>
-#include <utils/Log.h>
#include <memory>
#include <sstream>
@@ -82,12 +81,147 @@
return NO_ERROR;
}
+status_t Write(jr_exif_ptr destination, const void* source, size_t length, int &position) {
+ memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length);
+ position += length;
+ return NO_ERROR;
+}
+
+// If the EXIF package doesn't exist in the input JPEG, we'll create one with one entry
+// where the length is represented by this value.
+const size_t PSEUDO_EXIF_PACKAGE_LENGTH = 28;
+// If the EXIF package exists in the input JPEG, we'll add an "JR" entry where the length is
+// represented by this value.
+const size_t EXIF_J_R_ENTRY_LENGTH = 12;
+
+/*
+ * Helper function
+ * Add J R entry to existing exif, or create a new one with J R entry if it's null.
+ * EXIF syntax / change:
+ * ori:
+ * FF E1 - APP1
+ * 01 FC - size of APP1 (to be calculated)
+ * -----------------------------------------------------
+ * 45 78 69 66 00 00 - Exif\0\0 "Exif header"
+ * 49 49 2A 00 - TIFF Header
+ * 08 00 00 00 - offset to the IFD (image file directory)
+ * 06 00 - 6 entries
+ * 00 01 - Width Tag
+ * 03 00 - 'Short' type
+ * 01 00 00 00 - one entry
+ * 00 05 00 00 - image with 0x500
+ *--------------------------------------------------------------------------
+ * new:
+ * FF E1 - APP1
+ * 02 08 - new size, equals to old size + EXIF_J_R_ENTRY_LENGTH (12)
+ *-----------------------------------------------------
+ * 45 78 69 66 00 00 - Exif\0\0 "Exif header"
+ * 49 49 2A 00 - TIFF Header
+ * 08 00 00 00 - offset to the IFD (image file directory)
+ * 07 00 - +1 entry
+ * 4A 52 Custom ('J''R') Tag
+ * 07 00 - Unknown type
+ * 01 00 00 00 - one element
+ * 00 00 00 00 - empty data
+ * 00 01 - Width Tag
+ * 03 00 - 'Short' type
+ * 01 00 00 00 - one entry
+ * 00 05 00 00 - image with 0x500
+ */
+status_t updateExif(jr_exif_ptr exif, jr_exif_ptr dest) {
+ if (exif == nullptr || exif->data == nullptr) {
+ uint8_t data[PSEUDO_EXIF_PACKAGE_LENGTH] = {
+ 0x45, 0x78, 0x69, 0x66, 0x00, 0x00,
+ 0x49, 0x49, 0x2A, 0x00,
+ 0x08, 0x00, 0x00, 0x00,
+ 0x01, 0x00,
+ 0x4A, 0x52,
+ 0x07, 0x00,
+ 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00};
+ int pos = 0;
+ Write(dest, data, PSEUDO_EXIF_PACKAGE_LENGTH, pos);
+ return NO_ERROR;
+ }
+
+ int num_entry = 0;
+ uint8_t num_entry_low = 0;
+ uint8_t num_entry_high = 0;
+ bool use_big_endian = false;
+ if (reinterpret_cast<uint16_t*>(exif->data)[3] == 0x4949) {
+ num_entry_low = reinterpret_cast<uint8_t*>(exif->data)[14];
+ num_entry_high = reinterpret_cast<uint8_t*>(exif->data)[15];
+ } else if (reinterpret_cast<uint16_t*>(exif->data)[3] == 0x4d4d) {
+ use_big_endian = true;
+ num_entry_high = reinterpret_cast<uint8_t*>(exif->data)[14];
+ num_entry_low = reinterpret_cast<uint8_t*>(exif->data)[15];
+ } else {
+ return ERROR_JPEGR_METADATA_ERROR;
+ }
+ num_entry = (num_entry_high << 8) | num_entry_low;
+ num_entry += 1;
+ num_entry_low = num_entry & 0xff;
+ num_entry_high = (num_entry << 8) & 0xff;
+
+ int pos = 0;
+ Write(dest, (uint8_t*)exif->data, 14, pos);
+
+ if (use_big_endian) {
+ Write(dest, &num_entry_high, 1, pos);
+ Write(dest, &num_entry_low, 1, pos);
+ uint8_t data[EXIF_J_R_ENTRY_LENGTH] = {
+ 0x4A, 0x52,
+ 0x07, 0x00,
+ 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00};
+ Write(dest, data, EXIF_J_R_ENTRY_LENGTH, pos);
+ } else {
+ Write(dest, &num_entry_low, 1, pos);
+ Write(dest, &num_entry_high, 1, pos);
+ uint8_t data[EXIF_J_R_ENTRY_LENGTH] = {
+ 0x4A, 0x52,
+ 0x00, 0x07,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00};
+ Write(dest, data, EXIF_J_R_ENTRY_LENGTH, pos);
+ }
+
+ Write(dest, (uint8_t*)exif->data + 16, exif->length - 16, pos);
+
+ return NO_ERROR;
+}
+
+/*
+ * Helper function copies the JPEG image from without EXIF.
+ *
+ * @param dest destination of the data to be written.
+ * @param source source of data being written.
+ * @param exif_pos position of the EXIF package, which is aligned with jpegdecoder.getEXIFPos().
+ * (4 bypes offset to FF sign, the byte after FF E1 XX XX <this byte>).
+ * @param exif_size exif size without the initial 4 bytes, aligned with jpegdecoder.getEXIFSize().
+ */
+void copyJpegWithoutExif(jr_compressed_ptr dest,
+ jr_compressed_ptr source,
+ size_t exif_pos,
+ size_t exif_size) {
+ memcpy(dest, source, sizeof(jpegr_compressed_struct));
+
+ const size_t exif_offset = 4; //exif_pos has 4 bypes offset to the FF sign
+ dest->length = source->length - exif_size - exif_offset;
+ dest->data = malloc(dest->length);
+
+ memcpy(dest->data, source->data, exif_pos - exif_offset);
+ memcpy((uint8_t*)dest->data + exif_pos - exif_offset,
+ (uint8_t*)source->data + exif_pos + exif_size,
+ source->length - exif_pos - exif_size);
+}
+
/* Encode API-0 */
status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
jpegr_transfer_function hdr_tf,
jr_compressed_ptr dest,
int quality,
- jr_exif_ptr /* exif */) {
+ jr_exif_ptr exif) {
if (uncompressed_p010_image == nullptr || dest == nullptr) {
return ERROR_JPEGR_INVALID_NULL_PTR;
}
@@ -104,6 +238,9 @@
}
jpegr_uncompressed_struct uncompressed_yuv_420_image;
+ unique_ptr<uint8_t[]> uncompressed_yuv_420_image_data = make_unique<uint8_t[]>(
+ uncompressed_p010_image->width * uncompressed_p010_image->height * 3 / 2);
+ uncompressed_yuv_420_image.data = uncompressed_yuv_420_image_data.get();
JPEGR_CHECK(toneMap(uncompressed_p010_image, &uncompressed_yuv_420_image));
jpegr_uncompressed_struct map;
@@ -129,7 +266,18 @@
jpeg.data = jpeg_encoder.getCompressedImagePtr();
jpeg.length = jpeg_encoder.getCompressedImageSize();
- JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &metadata, dest));
+ jpegr_exif_struct new_exif;
+ if (exif == nullptr || exif->data == nullptr) {
+ new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH;
+ } else {
+ new_exif.length = exif->length + EXIF_J_R_ENTRY_LENGTH;
+ }
+ new_exif.data = new uint8_t[new_exif.length];
+ std::unique_ptr<uint8_t[]> new_exif_data;
+ new_exif_data.reset(reinterpret_cast<uint8_t*>(new_exif.data));
+ JPEGR_CHECK(updateExif(exif, &new_exif));
+
+ JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &new_exif, &metadata, dest));
return NO_ERROR;
}
@@ -140,7 +288,7 @@
jpegr_transfer_function hdr_tf,
jr_compressed_ptr dest,
int quality,
- jr_exif_ptr /* exif */) {
+ jr_exif_ptr exif) {
if (uncompressed_p010_image == nullptr
|| uncompressed_yuv_420_image == nullptr
|| dest == nullptr) {
@@ -186,7 +334,19 @@
jpeg.data = jpeg_encoder.getCompressedImagePtr();
jpeg.length = jpeg_encoder.getCompressedImageSize();
- JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &metadata, dest));
+ jpegr_exif_struct new_exif;
+ if (exif == nullptr || exif->data == nullptr) {
+ new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH;
+ } else {
+ new_exif.length = exif->length + EXIF_J_R_ENTRY_LENGTH;
+ }
+
+ new_exif.data = new uint8_t[new_exif.length];
+ std::unique_ptr<uint8_t[]> new_exif_data;
+ new_exif_data.reset(reinterpret_cast<uint8_t*>(new_exif.data));
+ JPEGR_CHECK(updateExif(exif, &new_exif));
+
+ JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &new_exif, &metadata, dest));
return NO_ERROR;
}
@@ -228,7 +388,47 @@
compressed_map.data = compressed_map_data.get();
JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
- JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, &metadata, dest));
+ // Extract EXIF from JPEG without decoding.
+ JpegDecoder jpeg_decoder;
+ if (!jpeg_decoder.extractEXIF(compressed_jpeg_image->data, compressed_jpeg_image->length)) {
+ return ERROR_JPEGR_DECODE_ERROR;
+ }
+
+ // Update exif.
+ jpegr_exif_struct exif;
+ exif.data = nullptr;
+ exif.length = 0;
+ jpegr_compressed_struct new_jpeg_image;
+ new_jpeg_image.data = nullptr;
+ new_jpeg_image.length = 0;
+ if (jpeg_decoder.getEXIFPos() != 0) {
+ copyJpegWithoutExif(&new_jpeg_image,
+ compressed_jpeg_image,
+ jpeg_decoder.getEXIFPos(),
+ jpeg_decoder.getEXIFSize());
+ exif.data = jpeg_decoder.getEXIFPtr();
+ exif.length = jpeg_decoder.getEXIFSize();
+ }
+
+ jpegr_exif_struct new_exif;
+ if (exif.data == nullptr) {
+ new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH;
+ } else {
+ new_exif.length = exif.length + EXIF_J_R_ENTRY_LENGTH;
+ }
+
+ new_exif.data = new uint8_t[new_exif.length];
+ std::unique_ptr<uint8_t[]> new_exif_data;
+ new_exif_data.reset(reinterpret_cast<uint8_t*>(new_exif.data));
+ JPEGR_CHECK(updateExif(&exif, &new_exif));
+
+ JPEGR_CHECK(appendRecoveryMap(
+ new_jpeg_image.data == nullptr ? compressed_jpeg_image : &new_jpeg_image,
+ &compressed_map, &new_exif, &metadata, dest));
+
+ if (new_jpeg_image.data != nullptr) {
+ free(new_jpeg_image.data);
+ }
return NO_ERROR;
}
@@ -254,6 +454,33 @@
uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
uncompressed_yuv_420_image.colorGamut = compressed_jpeg_image->colorGamut;
+ // Update exif.
+ jpegr_exif_struct exif;
+ exif.data = nullptr;
+ exif.length = 0;
+ jpegr_compressed_struct new_jpeg_image;
+ new_jpeg_image.data = nullptr;
+ new_jpeg_image.length = 0;
+ if (jpeg_decoder.getEXIFPos() != 0) {
+ copyJpegWithoutExif(&new_jpeg_image,
+ compressed_jpeg_image,
+ jpeg_decoder.getEXIFPos(),
+ jpeg_decoder.getEXIFSize());
+ exif.data = jpeg_decoder.getEXIFPtr();
+ exif.length = jpeg_decoder.getEXIFSize();
+ }
+
+ jpegr_exif_struct new_exif;
+ if (exif.data == nullptr) {
+ new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH;
+ } else {
+ new_exif.length = exif.length + EXIF_J_R_ENTRY_LENGTH;
+ }
+ new_exif.data = new uint8_t[new_exif.length];
+ std::unique_ptr<uint8_t[]> new_exif_data;
+ new_exif_data.reset(reinterpret_cast<uint8_t*>(new_exif.data));
+ JPEGR_CHECK(updateExif(&exif, &new_exif));
+
if (uncompressed_p010_image->width != uncompressed_yuv_420_image.width
|| uncompressed_p010_image->height != uncompressed_yuv_420_image.height) {
return ERROR_JPEGR_RESOLUTION_MISMATCH;
@@ -278,7 +505,13 @@
compressed_map.data = compressed_map_data.get();
JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
- JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, &metadata, dest));
+ JPEGR_CHECK(appendRecoveryMap(
+ new_jpeg_image.data == nullptr ? compressed_jpeg_image : &new_jpeg_image,
+ &compressed_map, &new_exif, &metadata, dest));
+
+ if (new_jpeg_image.data != nullptr) {
+ free(new_jpeg_image.data);
+ }
return NO_ERROR;
}
@@ -311,23 +544,37 @@
if (compressed_jpegr_image == nullptr || dest == nullptr) {
return ERROR_JPEGR_INVALID_NULL_PTR;
}
-
// TODO: fill EXIF data
(void) exif;
+ if (request_sdr) {
+ JpegDecoder jpeg_decoder;
+ if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length,
+ true)) {
+ return ERROR_JPEGR_DECODE_ERROR;
+ }
+ jpegr_uncompressed_struct uncompressed_rgba_image;
+ uncompressed_rgba_image.data = jpeg_decoder.getDecompressedImagePtr();
+ uncompressed_rgba_image.width = jpeg_decoder.getDecompressedImageWidth();
+ uncompressed_rgba_image.height = jpeg_decoder.getDecompressedImageHeight();
+ memcpy(dest->data, uncompressed_rgba_image.data,
+ uncompressed_rgba_image.width * uncompressed_rgba_image.height * 4);
+ dest->width = uncompressed_rgba_image.width;
+ dest->height = uncompressed_rgba_image.height;
+ return NO_ERROR;
+ }
+
jpegr_compressed_struct compressed_map;
jpegr_metadata metadata;
JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map));
-
JpegDecoder jpeg_decoder;
if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length)) {
return ERROR_JPEGR_DECODE_ERROR;
}
JpegDecoder recovery_map_decoder;
- if (!recovery_map_decoder.decompressImage(compressed_map.data,
- compressed_map.length)) {
+ if (!recovery_map_decoder.decompressImage(compressed_map.data, compressed_map.length)) {
return ERROR_JPEGR_DECODE_ERROR;
}
@@ -336,26 +583,17 @@
map.width = recovery_map_decoder.getDecompressedImageWidth();
map.height = recovery_map_decoder.getDecompressedImageHeight();
-
jpegr_uncompressed_struct uncompressed_yuv_420_image;
uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
if (!getMetadataFromXMP(static_cast<uint8_t*>(jpeg_decoder.getXMPPtr()),
- jpeg_decoder.getXMPSize(), &metadata)) {
+ jpeg_decoder.getXMPSize(), &metadata)) {
return ERROR_JPEGR_DECODE_ERROR;
}
- if (request_sdr) {
- memcpy(dest->data, uncompressed_yuv_420_image.data,
- uncompressed_yuv_420_image.width*uncompressed_yuv_420_image.height *3 / 2);
- dest->width = uncompressed_yuv_420_image.width;
- dest->height = uncompressed_yuv_420_image.height;
- } else {
- JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, &metadata, dest));
- }
-
+ JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, &metadata, dest));
return NO_ERROR;
}
@@ -614,67 +852,120 @@
return extractPrimaryImageAndRecoveryMap(compressed_jpegr_image, nullptr, dest);
}
+// JPEG/R structure:
+// SOI (ff d8)
+// APP1 (ff e1)
+// 2 bytes of length (2 + length of exif package)
+// EXIF package (this includes the first two bytes representing the package length)
+// APP1 (ff e1)
+// 2 bytes of length (2 + 29 + length of xmp package)
+// name space ("http://ns.adobe.com/xap/1.0/\0")
+// xmp
+// primary image (without the first two bytes (SOI) and without EXIF, may have other packages)
+// secondary image (the recovery map)
+//
+// Metadata versions we are using:
+// ECMA TR-98 for JFIF marker
+// Exif 2.2 spec for EXIF marker
+// Adobe XMP spec part 3 for XMP marker
+// ICC v4.3 spec for ICC
status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
jr_compressed_ptr compressed_recovery_map,
+ jr_exif_ptr exif,
jr_metadata_ptr metadata,
jr_compressed_ptr dest) {
if (compressed_jpeg_image == nullptr
|| compressed_recovery_map == nullptr
+ || exif == nullptr
|| metadata == nullptr
|| dest == nullptr) {
return ERROR_JPEGR_INVALID_NULL_PTR;
}
- const string xmp = generateXmp(compressed_recovery_map->length, *metadata);
- const string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
- const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
-
- // 2 bytes: APP1 sign (ff e1)
- // 29 bytes: length of name space "http://ns.adobe.com/xap/1.0/\0",
- // x bytes: length of xmp packet
-
- const int length = 3 + nameSpaceLength + xmp.size();
- const uint8_t lengthH = ((length >> 8) & 0xff);
- const uint8_t lengthL = (length & 0xff);
-
int pos = 0;
- // JPEG/R structure:
- // SOI (ff d8)
- // APP1 (ff e1)
- // 2 bytes of length (2 + 29 + length of xmp packet)
- // name space ("http://ns.adobe.com/xap/1.0/\0")
- // xmp
- // primary image (without the first two bytes, the SOI sign)
- // secondary image (the recovery map)
+ // Write SOI
JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
- JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
- JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
- JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
- JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
- JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
- JPEGR_CHECK(Write(dest, (void*)xmp.c_str(), xmp.size(), pos));
+
+ // Write EXIF
+ {
+ const int length = 2 + exif->length;
+ 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::kAPP1, 1, pos));
+ JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
+ JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
+ JPEGR_CHECK(Write(dest, exif->data, exif->length, pos));
+ }
+
+ // Prepare and write XMP
+ {
+ const string xmp = generateXmp(compressed_recovery_map->length, *metadata);
+ const string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
+ const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
+ // 2 bytes: representing the length of the package
+ // 29 bytes: length of name space "http://ns.adobe.com/xap/1.0/\0",
+ // x bytes: length of xmp packet
+ const int length = 2 + nameSpaceLength + xmp.size();
+ 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::kAPP1, 1, pos));
+ JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
+ JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
+ JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
+ JPEGR_CHECK(Write(dest, (void*)xmp.c_str(), xmp.size(), pos));
+ }
+
+ // Write primary image
JPEGR_CHECK(Write(dest,
(uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos));
+
+ // Write secondary image
JPEGR_CHECK(Write(dest, compressed_recovery_map->data, compressed_recovery_map->length, pos));
+
+ // Set back length
dest->length = pos;
+ // Done!
return NO_ERROR;
}
-status_t RecoveryMap::toneMap(jr_uncompressed_ptr uncompressed_p010_image,
+status_t RecoveryMap::toneMap(jr_uncompressed_ptr src,
jr_uncompressed_ptr dest) {
- if (uncompressed_p010_image == nullptr || dest == nullptr) {
+ if (src == nullptr || dest == nullptr) {
return ERROR_JPEGR_INVALID_NULL_PTR;
}
- dest->width = uncompressed_p010_image->width;
- dest->height = uncompressed_p010_image->height;
- unique_ptr<uint8_t[]> dest_data = make_unique<uint8_t[]>(dest->width * dest->height * 3 / 2);
- dest->data = dest_data.get();
+ dest->width = src->width;
+ dest->height = src->height;
- // TODO: Tone map algorighm here.
+ size_t pixel_count = src->width * src->height;
+ for (size_t y = 0; y < src->height; ++y) {
+ for (size_t x = 0; x < src->width; ++x) {
+ size_t pixel_y_idx = x + y * src->width;
+ size_t pixel_uv_idx = x / 2 + (y / 2) * (src->width / 2);
+
+ uint16_t y_uint = reinterpret_cast<uint16_t*>(src->data)[pixel_y_idx]
+ >> 6;
+ uint16_t u_uint = reinterpret_cast<uint16_t*>(src->data)[pixel_count + pixel_uv_idx * 2]
+ >> 6;
+ uint16_t v_uint = reinterpret_cast<uint16_t*>(src->data)[pixel_count + pixel_uv_idx * 2 + 1]
+ >> 6;
+
+ uint8_t* y = &reinterpret_cast<uint8_t*>(dest->data)[pixel_y_idx];
+ uint8_t* u = &reinterpret_cast<uint8_t*>(dest->data)[pixel_count + pixel_uv_idx];
+ uint8_t* v = &reinterpret_cast<uint8_t*>(dest->data)[pixel_count * 5 / 4 + pixel_uv_idx];
+
+ *y = static_cast<uint8_t>((y_uint >> 2) & 0xff);
+ *u = static_cast<uint8_t>((u_uint >> 2) & 0xff);
+ *v = static_cast<uint8_t>((v_uint >> 2) & 0xff);
+ }
+ }
+
+ dest->colorGamut = src->colorGamut;
return NO_ERROR;
}
diff --git a/libs/jpegrecoverymap/tests/data/raw_yuv420_image.yuv420 b/libs/jpegrecoverymap/tests/data/raw_yuv420_image.yuv420
new file mode 100644
index 0000000..c043da6
--- /dev/null
+++ b/libs/jpegrecoverymap/tests/data/raw_yuv420_image.yuv420
Binary files differ
diff --git a/libs/jpegrecoverymap/tests/recoverymap_test.cpp b/libs/jpegrecoverymap/tests/recoverymap_test.cpp
index 6dea27f..8ff12fb 100644
--- a/libs/jpegrecoverymap/tests/recoverymap_test.cpp
+++ b/libs/jpegrecoverymap/tests/recoverymap_test.cpp
@@ -22,9 +22,11 @@
#include <utils/Log.h>
#define RAW_P010_IMAGE "/sdcard/Documents/raw_p010_image.p010"
-#define RAW_P010_IMAGE_WIDTH 1280
-#define RAW_P010_IMAGE_HEIGHT 720
+#define RAW_YUV420_IMAGE "/sdcard/Documents/raw_yuv420_image.yuv420"
#define JPEG_IMAGE "/sdcard/Documents/jpeg_image.jpg"
+#define TEST_IMAGE_WIDTH 1280
+#define TEST_IMAGE_HEIGHT 720
+#define DEFAULT_JPEG_QUALITY 90
#define SAVE_ENCODING_RESULT true
#define SAVE_DECODING_RESULT true
@@ -40,6 +42,7 @@
virtual void TearDown();
struct jpegr_uncompressed_struct mRawP010Image;
+ struct jpegr_uncompressed_struct mRawYuv420Image;
struct jpegr_compressed_struct mJpegImage;
};
@@ -49,6 +52,7 @@
void RecoveryMapTest::SetUp() {}
void RecoveryMapTest::TearDown() {
free(mRawP010Image.data);
+ free(mRawYuv420Image.data);
free(mJpegImage.data);
}
@@ -108,6 +112,8 @@
ASSERT_EQ(metadata_expected.rangeScalingFactor, metadata_read.rangeScalingFactor);
}
+
+/* Test Encode API-0 and decode */
TEST_F(RecoveryMapTest, encodeFromP010ThenDecode) {
int ret;
@@ -115,17 +121,17 @@
if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) {
FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
}
- mRawP010Image.width = RAW_P010_IMAGE_WIDTH;
- mRawP010Image.height = RAW_P010_IMAGE_HEIGHT;
+ mRawP010Image.width = TEST_IMAGE_WIDTH;
+ mRawP010Image.height = TEST_IMAGE_HEIGHT;
mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100;
RecoveryMap recoveryMap;
jpegr_compressed_struct jpegR;
- jpegR.maxLength = RAW_P010_IMAGE_WIDTH * RAW_P010_IMAGE_HEIGHT * sizeof(uint8_t);
+ jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
jpegR.data = malloc(jpegR.maxLength);
ret = recoveryMap.encodeJPEGR(
- &mRawP010Image, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR, 90, nullptr);
+ &mRawP010Image, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR, DEFAULT_JPEG_QUALITY, nullptr);
if (ret != OK) {
FAIL() << "Error code is " << ret;
}
@@ -140,7 +146,7 @@
}
jpegr_uncompressed_struct decodedJpegR;
- int decodedJpegRSize = RAW_P010_IMAGE_WIDTH * RAW_P010_IMAGE_HEIGHT * 4;
+ int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4;
decodedJpegR.data = malloc(decodedJpegRSize);
ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR);
if (ret != OK) {
@@ -160,6 +166,133 @@
free(decodedJpegR.data);
}
+/* Test Encode API-1 and decode */
+TEST_F(RecoveryMapTest, encodeFromRawHdrAndSdrThenDecode) {
+ int ret;
+
+ // Load input files.
+ if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) {
+ FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
+ }
+ mRawP010Image.width = TEST_IMAGE_WIDTH;
+ mRawP010Image.height = TEST_IMAGE_HEIGHT;
+ mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100;
+
+ if (!loadFile(RAW_YUV420_IMAGE, mRawYuv420Image.data, nullptr)) {
+ FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
+ }
+ mRawYuv420Image.width = TEST_IMAGE_WIDTH;
+ mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
+ mRawYuv420Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709;
+
+ RecoveryMap recoveryMap;
+
+ jpegr_compressed_struct jpegR;
+ jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
+ jpegR.data = malloc(jpegR.maxLength);
+ ret = recoveryMap.encodeJPEGR(
+ &mRawP010Image, &mRawYuv420Image, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR,
+ DEFAULT_JPEG_QUALITY, nullptr);
+ if (ret != OK) {
+ FAIL() << "Error code is " << ret;
+ }
+ if (SAVE_ENCODING_RESULT) {
+ // Output image data to file
+ std::string filePath = "/sdcard/Documents/encoded_from_jpeg_input.jpgr";
+ std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
+ if (!imageFile.is_open()) {
+ ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
+ }
+ imageFile.write((const char*)jpegR.data, jpegR.length);
+ }
+
+ jpegr_uncompressed_struct decodedJpegR;
+ int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4;
+ decodedJpegR.data = malloc(decodedJpegRSize);
+ ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR);
+ if (ret != OK) {
+ FAIL() << "Error code is " << ret;
+ }
+ if (SAVE_DECODING_RESULT) {
+ // Output image data to file
+ std::string filePath = "/sdcard/Documents/decoded_from_jpeg_input.rgb10";
+ std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
+ if (!imageFile.is_open()) {
+ ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
+ }
+ imageFile.write((const char*)decodedJpegR.data, decodedJpegRSize);
+ }
+
+ free(jpegR.data);
+ free(decodedJpegR.data);
+}
+
+/* Test Encode API-2 and decode */
+TEST_F(RecoveryMapTest, encodeFromRawHdrAndSdrAndJpegThenDecode) {
+ int ret;
+
+ // Load input files.
+ if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) {
+ FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
+ }
+ mRawP010Image.width = TEST_IMAGE_WIDTH;
+ mRawP010Image.height = TEST_IMAGE_HEIGHT;
+ mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100;
+
+ if (!loadFile(RAW_YUV420_IMAGE, mRawYuv420Image.data, nullptr)) {
+ FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
+ }
+ mRawYuv420Image.width = TEST_IMAGE_WIDTH;
+ mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
+ mRawYuv420Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709;
+
+ if (!loadFile(JPEG_IMAGE, mJpegImage.data, &mJpegImage.length)) {
+ FAIL() << "Load file " << JPEG_IMAGE << " failed";
+ }
+ mJpegImage.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709;
+
+ RecoveryMap recoveryMap;
+
+ jpegr_compressed_struct jpegR;
+ jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
+ jpegR.data = malloc(jpegR.maxLength);
+ ret = recoveryMap.encodeJPEGR(
+ &mRawP010Image, &mRawYuv420Image, &mJpegImage, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR);
+ if (ret != OK) {
+ FAIL() << "Error code is " << ret;
+ }
+ if (SAVE_ENCODING_RESULT) {
+ // Output image data to file
+ std::string filePath = "/sdcard/Documents/encoded_from_jpeg_input.jpgr";
+ std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
+ if (!imageFile.is_open()) {
+ ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
+ }
+ imageFile.write((const char*)jpegR.data, jpegR.length);
+ }
+
+ jpegr_uncompressed_struct decodedJpegR;
+ int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4;
+ decodedJpegR.data = malloc(decodedJpegRSize);
+ ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR);
+ if (ret != OK) {
+ FAIL() << "Error code is " << ret;
+ }
+ if (SAVE_DECODING_RESULT) {
+ // Output image data to file
+ std::string filePath = "/sdcard/Documents/decoded_from_jpeg_input.rgb10";
+ std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
+ if (!imageFile.is_open()) {
+ ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
+ }
+ imageFile.write((const char*)decodedJpegR.data, decodedJpegRSize);
+ }
+
+ free(jpegR.data);
+ free(decodedJpegR.data);
+}
+
+/* Test Encode API-3 and decode */
TEST_F(RecoveryMapTest, encodeFromJpegThenDecode) {
int ret;
@@ -167,8 +300,8 @@
if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) {
FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
}
- mRawP010Image.width = RAW_P010_IMAGE_WIDTH;
- mRawP010Image.height = RAW_P010_IMAGE_HEIGHT;
+ mRawP010Image.width = TEST_IMAGE_WIDTH;
+ mRawP010Image.height = TEST_IMAGE_HEIGHT;
mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100;
if (!loadFile(JPEG_IMAGE, mJpegImage.data, &mJpegImage.length)) {
@@ -179,7 +312,7 @@
RecoveryMap recoveryMap;
jpegr_compressed_struct jpegR;
- jpegR.maxLength = RAW_P010_IMAGE_WIDTH * RAW_P010_IMAGE_HEIGHT * sizeof(uint8_t);
+ jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
jpegR.data = malloc(jpegR.maxLength);
ret = recoveryMap.encodeJPEGR(
&mRawP010Image, &mJpegImage, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR);
@@ -197,7 +330,7 @@
}
jpegr_uncompressed_struct decodedJpegR;
- int decodedJpegRSize = RAW_P010_IMAGE_WIDTH * RAW_P010_IMAGE_HEIGHT * 4;
+ int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4;
decodedJpegR.data = malloc(decodedJpegRSize);
ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR);
if (ret != OK) {
diff --git a/libs/nativedisplay/AChoreographer.cpp b/libs/nativedisplay/AChoreographer.cpp
index e64165f..66a40f1 100644
--- a/libs/nativedisplay/AChoreographer.cpp
+++ b/libs/nativedisplay/AChoreographer.cpp
@@ -16,8 +16,8 @@
#include <android-base/thread_annotations.h>
#include <android/gui/ISurfaceComposer.h>
+#include <gui/Choreographer.h>
#include <jni.h>
-#include <nativedisplay/Choreographer.h>
#include <private/android/choreographer.h>
#include <utils/Looper.h>
#include <utils/Timers.h>
diff --git a/libs/nativedisplay/Android.bp b/libs/nativedisplay/Android.bp
index 70de33d..8d8a2bc 100644
--- a/libs/nativedisplay/Android.bp
+++ b/libs/nativedisplay/Android.bp
@@ -56,7 +56,6 @@
":libgui_frame_event_aidl",
"AChoreographer.cpp",
"ADisplay.cpp",
- "Choreographer.cpp",
"surfacetexture/surface_texture.cpp",
"surfacetexture/SurfaceTexture.cpp",
"surfacetexture/ImageConsumer.cpp",
diff --git a/libs/nativewindow/AHardwareBuffer.cpp b/libs/nativewindow/AHardwareBuffer.cpp
index bbafbff..cf927db 100644
--- a/libs/nativewindow/AHardwareBuffer.cpp
+++ b/libs/nativewindow/AHardwareBuffer.cpp
@@ -617,15 +617,27 @@
static_assert(static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::R_8) ==
AHARDWAREBUFFER_FORMAT_R8_UNORM,
"HAL and AHardwareBuffer pixel format don't match");
+ static_assert(static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::R_16_UINT) ==
+ AHARDWAREBUFFER_FORMAT_R16_UINT,
+ "HAL and AHardwareBuffer pixel format don't match");
+ static_assert(static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::RG_1616_UINT) ==
+ AHARDWAREBUFFER_FORMAT_R16G16_UINT,
+ "HAL and AHardwareBuffer pixel format don't match");
+ static_assert(static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::RGBA_10101010) ==
+ AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM,
+ "HAL and AHardwareBuffer pixel format don't match");
switch (format) {
case AHARDWAREBUFFER_FORMAT_R8_UNORM:
+ case AHARDWAREBUFFER_FORMAT_R16_UINT:
+ case AHARDWAREBUFFER_FORMAT_R16G16_UINT:
case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM:
case AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM:
case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM:
case AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM:
case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT:
case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM:
+ case AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM:
case AHARDWAREBUFFER_FORMAT_BLOB:
case AHARDWAREBUFFER_FORMAT_D16_UNORM:
case AHARDWAREBUFFER_FORMAT_D24_UNORM:
@@ -677,6 +689,7 @@
return 1;
case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM:
case AHARDWAREBUFFER_FORMAT_D16_UNORM:
+ case AHARDWAREBUFFER_FORMAT_R16_UINT:
return 2;
case AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM:
case AHARDWAREBUFFER_FORMAT_D24_UNORM:
@@ -686,8 +699,10 @@
case AHARDWAREBUFFER_FORMAT_D32_FLOAT:
case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM:
case AHARDWAREBUFFER_FORMAT_D24_UNORM_S8_UINT:
+ case AHARDWAREBUFFER_FORMAT_R16G16_UINT:
return 4;
case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT:
+ case AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM:
return 8;
default:
return 0;
diff --git a/libs/nativewindow/include/android/hardware_buffer.h b/libs/nativewindow/include/android/hardware_buffer.h
index c35507b..b2e8bea 100644
--- a/libs/nativewindow/include/android/hardware_buffer.h
+++ b/libs/nativewindow/include/android/hardware_buffer.h
@@ -173,6 +173,27 @@
* OpenGL ES: GR_GL_R8
*/
AHARDWAREBUFFER_FORMAT_R8_UNORM = 0x38,
+
+ /**
+ * Corresponding formats:
+ * Vulkan: VK_FORMAT_R16_UINT
+ * OpenGL ES: GR_GL_R16UI
+ */
+ AHARDWAREBUFFER_FORMAT_R16_UINT = 0x39,
+
+ /**
+ * Corresponding formats:
+ * Vulkan: VK_FORMAT_R16G16_UINT
+ * OpenGL ES: GR_GL_RG16UI
+ */
+ AHARDWAREBUFFER_FORMAT_R16G16_UINT = 0x3a,
+
+ /**
+ * Corresponding formats:
+ * Vulkan: VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16
+ * OpenGL ES: N/A
+ */
+ AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM = 0x3b,
};
/**
diff --git a/libs/nativewindow/include/android/hardware_buffer_aidl.h b/libs/nativewindow/include/android/hardware_buffer_aidl.h
index 906d9c6..1659d54 100644
--- a/libs/nativewindow/include/android/hardware_buffer_aidl.h
+++ b/libs/nativewindow/include/android/hardware_buffer_aidl.h
@@ -83,7 +83,7 @@
class HardwareBuffer {
public:
HardwareBuffer() noexcept {}
- explicit HardwareBuffer(HardwareBuffer&& other) noexcept : mBuffer(other.release()) {}
+ HardwareBuffer(HardwareBuffer&& other) noexcept : mBuffer(other.release()) {}
~HardwareBuffer() {
reset();
@@ -119,6 +119,13 @@
inline AHardwareBuffer* _Nullable get() const { return mBuffer; }
inline explicit operator bool () const { return mBuffer != nullptr; }
+ inline bool operator!=(const HardwareBuffer& rhs) const { return get() != rhs.get(); }
+ inline bool operator<(const HardwareBuffer& rhs) const { return get() < rhs.get(); }
+ inline bool operator<=(const HardwareBuffer& rhs) const { return get() <= rhs.get(); }
+ inline bool operator==(const HardwareBuffer& rhs) const { return get() == rhs.get(); }
+ inline bool operator>(const HardwareBuffer& rhs) const { return get() > rhs.get(); }
+ inline bool operator>=(const HardwareBuffer& rhs) const { return get() >= rhs.get(); }
+
HardwareBuffer& operator=(HardwareBuffer&& other) noexcept {
reset(other.release());
return *this;
diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp
index 8d19c45..b8fd1b2 100644
--- a/libs/renderengine/Android.bp
+++ b/libs/renderengine/Android.bp
@@ -133,6 +133,8 @@
"-fvisibility=hidden",
"-Werror=format",
"-Wno-unused-parameter",
+ // TODO: Investigate reducing pinned-memory usage (b/263377839)
+ "-DRE_SKIAVK",
],
srcs: [
":librenderengine_sources",
diff --git a/libs/renderengine/include/renderengine/mock/FakeExternalTexture.h b/libs/renderengine/include/renderengine/mock/FakeExternalTexture.h
index 974e0fd..b95f011 100644
--- a/libs/renderengine/include/renderengine/mock/FakeExternalTexture.h
+++ b/libs/renderengine/include/renderengine/mock/FakeExternalTexture.h
@@ -23,7 +23,9 @@
namespace mock {
class FakeExternalTexture : public renderengine::ExternalTexture {
- const sp<GraphicBuffer> mNullBuffer = nullptr;
+ const sp<GraphicBuffer> mEmptyBuffer =
+ sp<GraphicBuffer>::make(1u, 1u, PIXEL_FORMAT_RGBA_8888,
+ GRALLOC_USAGE_SW_WRITE_OFTEN | GRALLOC_USAGE_SW_READ_OFTEN);
uint32_t mWidth;
uint32_t mHeight;
uint64_t mId;
@@ -34,7 +36,7 @@
FakeExternalTexture(uint32_t width, uint32_t height, uint64_t id, PixelFormat pixelFormat,
uint64_t usage)
: mWidth(width), mHeight(height), mId(id), mPixelFormat(pixelFormat), mUsage(usage) {}
- const sp<GraphicBuffer>& getBuffer() const { return mNullBuffer; }
+ const sp<GraphicBuffer>& getBuffer() const { return mEmptyBuffer; }
bool hasSameBuffer(const renderengine::ExternalTexture& other) const override {
return getId() == other.getId();
}
diff --git a/libs/renderengine/skia/filters/GaussianBlurFilter.cpp b/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
index f3b6ab9..511d7c9 100644
--- a/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
+++ b/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
@@ -25,6 +25,7 @@
#include <SkSize.h>
#include <SkString.h>
#include <SkSurface.h>
+#include "include/gpu/GpuTypes.h" // from Skia
#include <log/log.h>
#include <utils/Trace.h>
@@ -44,7 +45,8 @@
// Create blur surface with the bit depth and colorspace of the original surface
SkImageInfo scaledInfo = input->imageInfo().makeWH(std::ceil(blurRect.width() * kInputScale),
std::ceil(blurRect.height() * kInputScale));
- sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, scaledInfo);
+ sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(context,
+ skgpu::Budgeted::kNo, scaledInfo);
SkPaint paint;
paint.setBlendMode(SkBlendMode::kSrc);
diff --git a/libs/sensor/Sensor.cpp b/libs/sensor/Sensor.cpp
index ec0ced8..fb895f5 100644
--- a/libs/sensor/Sensor.cpp
+++ b/libs/sensor/Sensor.cpp
@@ -264,10 +264,6 @@
mStringType = SENSOR_STRING_TYPE_HEART_BEAT;
mFlags |= SENSOR_FLAG_SPECIAL_REPORTING_MODE;
break;
-
- // TODO: Placeholder for LLOB sensor type
-
-
case SENSOR_TYPE_ACCELEROMETER_UNCALIBRATED:
mStringType = SENSOR_STRING_TYPE_ACCELEROMETER_UNCALIBRATED;
mFlags |= SENSOR_FLAG_CONTINUOUS_MODE;
diff --git a/libs/ui/Android.bp b/libs/ui/Android.bp
index d33dd34..ec0ab4e 100644
--- a/libs/ui/Android.bp
+++ b/libs/ui/Android.bp
@@ -48,7 +48,6 @@
integer_overflow: true,
misc_undefined: ["bounds"],
},
-
}
cc_library_static {
@@ -135,6 +134,7 @@
"Gralloc2.cpp",
"Gralloc3.cpp",
"Gralloc4.cpp",
+ "Gralloc5.cpp",
"GraphicBuffer.cpp",
"GraphicBufferAllocator.cpp",
"GraphicBufferMapper.cpp",
@@ -176,6 +176,7 @@
"libsync",
"libutils",
"liblog",
+ "libvndksupport",
],
export_shared_lib_headers: [
@@ -214,6 +215,8 @@
"libnativewindow_headers",
"libhardware_headers",
"libui_headers",
+ "libimapper_stablec",
+ "libimapper_providerutils",
],
export_static_lib_headers: [
diff --git a/libs/ui/Gralloc2.cpp b/libs/ui/Gralloc2.cpp
index f23f10a..e9b5dec 100644
--- a/libs/ui/Gralloc2.cpp
+++ b/libs/ui/Gralloc2.cpp
@@ -161,7 +161,7 @@
return static_cast<status_t>((ret.isOk()) ? error : kTransactionError);
}
-status_t Gralloc2Mapper::importBuffer(const hardware::hidl_handle& rawHandle,
+status_t Gralloc2Mapper::importBuffer(const native_handle_t* rawHandle,
buffer_handle_t* outBufferHandle) const {
Error error;
auto ret = mMapper->importBuffer(rawHandle,
diff --git a/libs/ui/Gralloc3.cpp b/libs/ui/Gralloc3.cpp
index 15c60bc..474d381 100644
--- a/libs/ui/Gralloc3.cpp
+++ b/libs/ui/Gralloc3.cpp
@@ -138,7 +138,7 @@
return static_cast<status_t>((ret.isOk()) ? error : kTransactionError);
}
-status_t Gralloc3Mapper::importBuffer(const hardware::hidl_handle& rawHandle,
+status_t Gralloc3Mapper::importBuffer(const native_handle_t* rawHandle,
buffer_handle_t* outBufferHandle) const {
Error error;
auto ret = mMapper->importBuffer(rawHandle, [&](const auto& tmpError, const auto& tmpBuffer) {
diff --git a/libs/ui/Gralloc4.cpp b/libs/ui/Gralloc4.cpp
index f6ab7b2..7459466 100644
--- a/libs/ui/Gralloc4.cpp
+++ b/libs/ui/Gralloc4.cpp
@@ -196,7 +196,7 @@
return static_cast<status_t>((ret.isOk()) ? error : kTransactionError);
}
-status_t Gralloc4Mapper::importBuffer(const hardware::hidl_handle& rawHandle,
+status_t Gralloc4Mapper::importBuffer(const native_handle_t* rawHandle,
buffer_handle_t* outBufferHandle) const {
Error error;
auto ret = mMapper->importBuffer(rawHandle, [&](const auto& tmpError, const auto& tmpBuffer) {
@@ -1233,7 +1233,10 @@
if (mAidlAllocator) {
AllocationResult result;
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
auto status = mAidlAllocator->allocate(descriptor, bufferCount, &result);
+#pragma clang diagnostic pop // deprecation
if (!status.isOk()) {
error = status.getExceptionCode();
if (error == EX_SERVICE_SPECIFIC) {
diff --git a/libs/ui/Gralloc5.cpp b/libs/ui/Gralloc5.cpp
new file mode 100644
index 0000000..6f196b8
--- /dev/null
+++ b/libs/ui/Gralloc5.cpp
@@ -0,0 +1,903 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "Gralloc5"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include <ui/Gralloc5.h>
+
+#include <aidlcommonsupport/NativeHandle.h>
+#include <android/binder_manager.h>
+#include <android/hardware/graphics/mapper/utils/IMapperMetadataTypes.h>
+#include <binder/IPCThreadState.h>
+#include <dlfcn.h>
+#include <ui/FatVector.h>
+#include <vndksupport/linker.h>
+
+using namespace aidl::android::hardware::graphics::allocator;
+using namespace aidl::android::hardware::graphics::common;
+using namespace ::android::hardware::graphics::mapper;
+
+namespace android {
+
+static const auto kIAllocatorServiceName = IAllocator::descriptor + std::string("/default");
+static const auto kIAllocatorMinimumVersion = 2;
+
+// TODO(b/72323293, b/72703005): Remove these invalid bits from callers
+static constexpr uint64_t kRemovedUsageBits = static_cast<uint64_t>((1 << 10) | (1 << 13));
+
+typedef AIMapper_Error (*AIMapper_loadIMapperFn)(AIMapper *_Nullable *_Nonnull outImplementation);
+
+struct Gralloc5 {
+ std::shared_ptr<IAllocator> allocator;
+ AIMapper *mapper = nullptr;
+};
+
+static std::shared_ptr<IAllocator> waitForAllocator() {
+ if (__builtin_available(android 31, *)) {
+ if (!AServiceManager_isDeclared(kIAllocatorServiceName.c_str())) {
+ return nullptr;
+ }
+ auto allocator = IAllocator::fromBinder(
+ ndk::SpAIBinder(AServiceManager_waitForService(kIAllocatorServiceName.c_str())));
+ if (!allocator) {
+ ALOGE("AIDL IAllocator declared but failed to get service");
+ return nullptr;
+ }
+
+ int32_t version = 0;
+ if (!allocator->getInterfaceVersion(&version).isOk()) {
+ ALOGE("Failed to query interface version");
+ return nullptr;
+ }
+ if (version < kIAllocatorMinimumVersion) {
+ return nullptr;
+ }
+ return allocator;
+ } else {
+ // TODO: LOG_ALWAYS_FATAL("libui is not backwards compatible");
+ return nullptr;
+ }
+}
+
+static void *loadIMapperLibrary() {
+ static void *imapperLibrary = []() -> void * {
+ auto allocator = waitForAllocator();
+ std::string mapperSuffix;
+ auto status = allocator->getIMapperLibrarySuffix(&mapperSuffix);
+ if (!status.isOk()) {
+ ALOGE("Failed to get IMapper library suffix");
+ return nullptr;
+ }
+
+ std::string lib_name = "mapper." + mapperSuffix + ".so";
+ void *so = android_load_sphal_library(lib_name.c_str(), RTLD_LOCAL | RTLD_NOW);
+ if (!so) {
+ ALOGE("Failed to load %s", lib_name.c_str());
+ }
+ return so;
+ }();
+ return imapperLibrary;
+}
+
+static const Gralloc5 &getInstance() {
+ static Gralloc5 instance = []() {
+ auto allocator = waitForAllocator();
+ if (!allocator) {
+ return Gralloc5{};
+ }
+ void *so = loadIMapperLibrary();
+ if (!so) {
+ return Gralloc5{};
+ }
+ auto loadIMapper = (AIMapper_loadIMapperFn)dlsym(so, "AIMapper_loadIMapper");
+ AIMapper *mapper = nullptr;
+ AIMapper_Error error = loadIMapper(&mapper);
+ if (error != AIMAPPER_ERROR_NONE) {
+ ALOGE("AIMapper_loadIMapper failed %d", error);
+ return Gralloc5{};
+ }
+ return Gralloc5{std::move(allocator), mapper};
+ }();
+ return instance;
+}
+
+template <StandardMetadataType T>
+static auto getStandardMetadata(AIMapper *mapper, buffer_handle_t bufferHandle)
+ -> decltype(StandardMetadata<T>::value::decode(nullptr, 0)) {
+ using Value = typename StandardMetadata<T>::value;
+ // TODO: Tune for common-case better
+ FatVector<uint8_t, 128> buffer;
+ int32_t sizeRequired = mapper->v5.getStandardMetadata(bufferHandle, static_cast<int64_t>(T),
+ buffer.data(), buffer.size());
+ if (sizeRequired < 0) {
+ ALOGW_IF(-AIMAPPER_ERROR_UNSUPPORTED != sizeRequired,
+ "Unexpected error %d from valid getStandardMetadata call", -sizeRequired);
+ return std::nullopt;
+ }
+ if ((size_t)sizeRequired > buffer.size()) {
+ buffer.resize(sizeRequired);
+ sizeRequired = mapper->v5.getStandardMetadata(bufferHandle, static_cast<int64_t>(T),
+ buffer.data(), buffer.size());
+ }
+ if (sizeRequired < 0 || (size_t)sizeRequired > buffer.size()) {
+ ALOGW("getStandardMetadata failed, received %d with buffer size %zd", sizeRequired,
+ buffer.size());
+ // Generate a fail type
+ return std::nullopt;
+ }
+ return Value::decode(buffer.data(), sizeRequired);
+}
+
+template <StandardMetadataType T>
+static AIMapper_Error setStandardMetadata(AIMapper *mapper, buffer_handle_t bufferHandle,
+ const typename StandardMetadata<T>::value_type &value) {
+ using Value = typename StandardMetadata<T>::value;
+ int32_t sizeRequired = Value::encode(value, nullptr, 0);
+ if (sizeRequired < 0) {
+ ALOGW("Failed to calculate required size");
+ return static_cast<AIMapper_Error>(-sizeRequired);
+ }
+ FatVector<uint8_t, 128> buffer;
+ buffer.resize(sizeRequired);
+ sizeRequired = Value::encode(value, buffer.data(), buffer.size());
+ if (sizeRequired < 0 || (size_t)sizeRequired > buffer.size()) {
+ ALOGW("Failed to encode with calculated size %d; buffer size %zd", sizeRequired,
+ buffer.size());
+ return static_cast<AIMapper_Error>(-sizeRequired);
+ }
+ return mapper->v5.setStandardMetadata(bufferHandle, static_cast<int64_t>(T), buffer.data(),
+ sizeRequired);
+}
+
+Gralloc5Allocator::Gralloc5Allocator(const Gralloc5Mapper &mapper) : mMapper(mapper) {
+ mAllocator = getInstance().allocator;
+}
+
+bool Gralloc5Allocator::isLoaded() const {
+ return mAllocator != nullptr;
+}
+
+static uint64_t getValidUsageBits() {
+ static const uint64_t validUsageBits = []() -> uint64_t {
+ uint64_t bits = 0;
+ for (const auto bit : ndk::enum_range<BufferUsage>{}) {
+ bits |= static_cast<int64_t>(bit);
+ }
+ return bits;
+ }();
+ return validUsageBits | kRemovedUsageBits;
+}
+
+static std::optional<BufferDescriptorInfo> makeDescriptor(std::string requestorName, uint32_t width,
+ uint32_t height, PixelFormat format,
+ uint32_t layerCount, uint64_t usage) {
+ uint64_t validUsageBits = getValidUsageBits();
+ if (usage & ~validUsageBits) {
+ ALOGE("buffer descriptor contains invalid usage bits 0x%" PRIx64, usage & ~validUsageBits);
+ return std::nullopt;
+ }
+
+ BufferDescriptorInfo descriptorInfo{
+ .width = static_cast<int32_t>(width),
+ .height = static_cast<int32_t>(height),
+ .layerCount = static_cast<int32_t>(layerCount),
+ .format = static_cast<::aidl::android::hardware::graphics::common::PixelFormat>(format),
+ .usage = static_cast<BufferUsage>(usage),
+ };
+ auto nameLength = std::min(requestorName.length(), descriptorInfo.name.size() - 1);
+ memcpy(descriptorInfo.name.data(), requestorName.data(), nameLength);
+ requestorName.data()[nameLength] = 0;
+ return descriptorInfo;
+}
+
+std::string Gralloc5Allocator::dumpDebugInfo(bool less) const {
+ return mMapper.dumpBuffers(less);
+}
+
+status_t Gralloc5Allocator::allocate(std::string requestorName, uint32_t width, uint32_t height,
+ android::PixelFormat format, uint32_t layerCount,
+ uint64_t usage, uint32_t bufferCount, uint32_t *outStride,
+ buffer_handle_t *outBufferHandles, bool importBuffers) const {
+ auto descriptorInfo = makeDescriptor(requestorName, width, height, format, layerCount, usage);
+ if (!descriptorInfo) {
+ return BAD_VALUE;
+ }
+
+ AllocationResult result;
+ auto status = mAllocator->allocate2(*descriptorInfo, bufferCount, &result);
+ if (!status.isOk()) {
+ auto error = status.getExceptionCode();
+ if (error == EX_SERVICE_SPECIFIC) {
+ error = status.getServiceSpecificError();
+ }
+ if (error == OK) {
+ error = UNKNOWN_ERROR;
+ }
+ return error;
+ }
+
+ if (importBuffers) {
+ for (uint32_t i = 0; i < bufferCount; i++) {
+ auto handle = makeFromAidl(result.buffers[i]);
+ auto error = mMapper.importBuffer(handle, &outBufferHandles[i]);
+ native_handle_delete(handle);
+ if (error != NO_ERROR) {
+ for (uint32_t j = 0; j < i; j++) {
+ mMapper.freeBuffer(outBufferHandles[j]);
+ outBufferHandles[j] = nullptr;
+ }
+ return error;
+ }
+ }
+ } else {
+ for (uint32_t i = 0; i < bufferCount; i++) {
+ outBufferHandles[i] = dupFromAidl(result.buffers[i]);
+ if (!outBufferHandles[i]) {
+ for (uint32_t j = 0; j < i; j++) {
+ auto buffer = const_cast<native_handle_t *>(outBufferHandles[j]);
+ native_handle_close(buffer);
+ native_handle_delete(buffer);
+ outBufferHandles[j] = nullptr;
+ }
+ return NO_MEMORY;
+ }
+ }
+ }
+
+ *outStride = result.stride;
+
+ // Release all the resources held by AllocationResult (specifically any remaining FDs)
+ result = {};
+ // make sure the kernel driver sees BC_FREE_BUFFER and closes the fds now
+ // TODO: Re-enable this at some point if it's necessary. We can't do it now because libui
+ // is marked apex_available (b/214400477) and libbinder isn't (which of course is correct)
+ // IPCThreadState::self()->flushCommands();
+
+ return OK;
+}
+
+void Gralloc5Mapper::preload() {
+ // TODO(b/261858155): Implement. We can't bounce off of IAllocator for this because zygote can't
+ // use binder. So when an alternate strategy of retrieving the library prefix is available,
+ // use that here.
+}
+
+Gralloc5Mapper::Gralloc5Mapper() {
+ mMapper = getInstance().mapper;
+}
+
+bool Gralloc5Mapper::isLoaded() const {
+ return mMapper != nullptr && mMapper->version >= AIMAPPER_VERSION_5;
+}
+
+std::string Gralloc5Mapper::dumpBuffer(buffer_handle_t bufferHandle, bool less) const {
+ // TODO(b/261858392): Implement
+ (void)bufferHandle;
+ (void)less;
+ return {};
+}
+
+std::string Gralloc5Mapper::dumpBuffers(bool less) const {
+ // TODO(b/261858392): Implement
+ (void)less;
+ return {};
+}
+
+status_t Gralloc5Mapper::importBuffer(const native_handle_t *rawHandle,
+ buffer_handle_t *outBufferHandle) const {
+ return mMapper->v5.importBuffer(rawHandle, outBufferHandle);
+}
+
+void Gralloc5Mapper::freeBuffer(buffer_handle_t bufferHandle) const {
+ mMapper->v5.freeBuffer(bufferHandle);
+}
+
+status_t Gralloc5Mapper::validateBufferSize(buffer_handle_t bufferHandle, uint32_t width,
+ uint32_t height, PixelFormat format,
+ uint32_t layerCount, uint64_t usage,
+ uint32_t stride) const {
+ {
+ auto value = getStandardMetadata<StandardMetadataType::WIDTH>(mMapper, bufferHandle);
+ if (width != value) {
+ ALOGW("Width didn't match, expected %d got %" PRId64, width, value.value_or(-1));
+ return BAD_VALUE;
+ }
+ }
+ {
+ auto value = getStandardMetadata<StandardMetadataType::HEIGHT>(mMapper, bufferHandle);
+ if (height != value) {
+ ALOGW("Height didn't match, expected %d got %" PRId64, height, value.value_or(-1));
+ return BAD_VALUE;
+ }
+ }
+ {
+ auto value =
+ getStandardMetadata<StandardMetadataType::PIXEL_FORMAT_REQUESTED>(mMapper,
+ bufferHandle);
+ if (static_cast<::aidl::android::hardware::graphics::common::PixelFormat>(format) !=
+ value) {
+ ALOGW("Format didn't match, expected %d got %s", format,
+ value.has_value() ? toString(*value).c_str() : "<null>");
+ return BAD_VALUE;
+ }
+ }
+ {
+ auto value = getStandardMetadata<StandardMetadataType::LAYER_COUNT>(mMapper, bufferHandle);
+ if (layerCount != value) {
+ ALOGW("Layer count didn't match, expected %d got %" PRId64, layerCount,
+ value.value_or(-1));
+ return BAD_VALUE;
+ }
+ }
+ {
+ auto value = getStandardMetadata<StandardMetadataType::USAGE>(mMapper, bufferHandle);
+ if (static_cast<BufferUsage>(usage) != value) {
+ ALOGW("Usage didn't match, expected %" PRIu64 " got %" PRId64, usage,
+ static_cast<int64_t>(value.value_or(BufferUsage::CPU_READ_NEVER)));
+ return BAD_VALUE;
+ }
+ }
+ {
+ (void)stride;
+ // TODO(b/261856851): Add StandardMetadataType::STRIDE && enable this
+ // auto value = getStandardMetadata<StandardMetadataType::STRIDE>(mMapper,
+ // bufferHandle); if (static_cast<BufferUsage>(usage) != value) {
+ // ALOGW("Layer count didn't match, expected %" PRIu64 " got %" PRId64, usage,
+ // static_cast<int64_t>(value.value_or(BufferUsage::CPU_READ_NEVER)));
+ // return BAD_VALUE;
+ // }
+ }
+ return OK;
+}
+
+void Gralloc5Mapper::getTransportSize(buffer_handle_t bufferHandle, uint32_t *outNumFds,
+ uint32_t *outNumInts) const {
+ mMapper->v5.getTransportSize(bufferHandle, outNumFds, outNumInts);
+}
+
+status_t Gralloc5Mapper::lock(buffer_handle_t bufferHandle, uint64_t usage, const Rect &bounds,
+ int acquireFence, void **outData, int32_t *outBytesPerPixel,
+ int32_t *outBytesPerStride) const {
+ std::vector<ui::PlaneLayout> planeLayouts;
+ status_t err = getPlaneLayouts(bufferHandle, &planeLayouts);
+
+ if (err == NO_ERROR && !planeLayouts.empty()) {
+ if (outBytesPerPixel) {
+ int32_t bitsPerPixel = planeLayouts.front().sampleIncrementInBits;
+ for (const auto &planeLayout : planeLayouts) {
+ if (bitsPerPixel != planeLayout.sampleIncrementInBits) {
+ bitsPerPixel = -1;
+ }
+ }
+ if (bitsPerPixel >= 0 && bitsPerPixel % 8 == 0) {
+ *outBytesPerPixel = bitsPerPixel / 8;
+ } else {
+ *outBytesPerPixel = -1;
+ }
+ }
+ if (outBytesPerStride) {
+ int32_t bytesPerStride = planeLayouts.front().strideInBytes;
+ for (const auto &planeLayout : planeLayouts) {
+ if (bytesPerStride != planeLayout.strideInBytes) {
+ bytesPerStride = -1;
+ }
+ }
+ if (bytesPerStride >= 0) {
+ *outBytesPerStride = bytesPerStride;
+ } else {
+ *outBytesPerStride = -1;
+ }
+ }
+ }
+
+ auto status = mMapper->v5.lock(bufferHandle, usage, bounds, acquireFence, outData);
+
+ ALOGW_IF(status != AIMAPPER_ERROR_NONE, "lock(%p, ...) failed: %d", bufferHandle, status);
+ return static_cast<status_t>(status);
+}
+
+status_t Gralloc5Mapper::lock(buffer_handle_t bufferHandle, uint64_t usage, const Rect &bounds,
+ int acquireFence, android_ycbcr *outYcbcr) const {
+ if (!outYcbcr) {
+ return BAD_VALUE;
+ }
+
+ // TODO(b/262279301): Change the return type of ::unlock to unique_fd instead of int so that
+ // ignoring the return value "just works" instead
+ auto unlock = [this](buffer_handle_t bufferHandle) {
+ int fence = this->unlock(bufferHandle);
+ if (fence != -1) {
+ ::close(fence);
+ }
+ };
+
+ std::vector<ui::PlaneLayout> planeLayouts;
+ status_t error = getPlaneLayouts(bufferHandle, &planeLayouts);
+ if (error != NO_ERROR) {
+ return error;
+ }
+
+ void *data = nullptr;
+ error = lock(bufferHandle, usage, bounds, acquireFence, &data, nullptr, nullptr);
+ if (error != NO_ERROR) {
+ return error;
+ }
+
+ android_ycbcr ycbcr;
+
+ ycbcr.y = nullptr;
+ ycbcr.cb = nullptr;
+ ycbcr.cr = nullptr;
+ ycbcr.ystride = 0;
+ ycbcr.cstride = 0;
+ ycbcr.chroma_step = 0;
+
+ for (const auto &planeLayout : planeLayouts) {
+ for (const auto &planeLayoutComponent : planeLayout.components) {
+ if (!gralloc4::isStandardPlaneLayoutComponentType(planeLayoutComponent.type)) {
+ continue;
+ }
+
+ uint8_t *tmpData = static_cast<uint8_t *>(data) + planeLayout.offsetInBytes;
+
+ // Note that `offsetInBits` may not be a multiple of 8 for packed formats (e.g. P010)
+ // but we still want to point to the start of the first byte.
+ tmpData += (planeLayoutComponent.offsetInBits / 8);
+
+ uint64_t sampleIncrementInBytes;
+
+ auto type = static_cast<PlaneLayoutComponentType>(planeLayoutComponent.type.value);
+ switch (type) {
+ case PlaneLayoutComponentType::Y:
+ if ((ycbcr.y != nullptr) || (planeLayout.sampleIncrementInBits % 8 != 0)) {
+ unlock(bufferHandle);
+ return BAD_VALUE;
+ }
+ ycbcr.y = tmpData;
+ ycbcr.ystride = planeLayout.strideInBytes;
+ break;
+
+ case PlaneLayoutComponentType::CB:
+ case PlaneLayoutComponentType::CR:
+ if (planeLayout.sampleIncrementInBits % 8 != 0) {
+ unlock(bufferHandle);
+ return BAD_VALUE;
+ }
+
+ sampleIncrementInBytes = planeLayout.sampleIncrementInBits / 8;
+ if ((sampleIncrementInBytes != 1) && (sampleIncrementInBytes != 2) &&
+ (sampleIncrementInBytes != 4)) {
+ unlock(bufferHandle);
+ return BAD_VALUE;
+ }
+
+ if (ycbcr.cstride == 0 && ycbcr.chroma_step == 0) {
+ ycbcr.cstride = planeLayout.strideInBytes;
+ ycbcr.chroma_step = sampleIncrementInBytes;
+ } else {
+ if ((static_cast<int64_t>(ycbcr.cstride) != planeLayout.strideInBytes) ||
+ (ycbcr.chroma_step != sampleIncrementInBytes)) {
+ unlock(bufferHandle);
+ return BAD_VALUE;
+ }
+ }
+
+ if (type == PlaneLayoutComponentType::CB) {
+ if (ycbcr.cb != nullptr) {
+ unlock(bufferHandle);
+ return BAD_VALUE;
+ }
+ ycbcr.cb = tmpData;
+ } else {
+ if (ycbcr.cr != nullptr) {
+ unlock(bufferHandle);
+ return BAD_VALUE;
+ }
+ ycbcr.cr = tmpData;
+ }
+ break;
+ default:
+ break;
+ };
+ }
+ }
+
+ *outYcbcr = ycbcr;
+ return OK;
+}
+
+int Gralloc5Mapper::unlock(buffer_handle_t bufferHandle) const {
+ int fence = -1;
+ AIMapper_Error error = mMapper->v5.unlock(bufferHandle, &fence);
+ if (error != AIMAPPER_ERROR_NONE) {
+ ALOGW("unlock failed with error %d", error);
+ }
+ return fence;
+}
+
+status_t Gralloc5Mapper::isSupported(uint32_t width, uint32_t height, PixelFormat format,
+ uint32_t layerCount, uint64_t usage,
+ bool *outSupported) const {
+ auto descriptorInfo = makeDescriptor("", width, height, format, layerCount, usage);
+ if (!descriptorInfo) {
+ *outSupported = false;
+ return OK;
+ }
+ auto status = getInstance().allocator->isSupported(*descriptorInfo, outSupported);
+ if (!status.isOk()) {
+ ALOGW("IAllocator::isSupported error %d (%s)", status.getStatus(), status.getMessage());
+ *outSupported = false;
+ }
+ return OK;
+}
+
+status_t Gralloc5Mapper::getBufferId(buffer_handle_t bufferHandle, uint64_t *outBufferId) const {
+ auto value = getStandardMetadata<StandardMetadataType::BUFFER_ID>(mMapper, bufferHandle);
+ if (value.has_value()) {
+ *outBufferId = *value;
+ return OK;
+ }
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getName(buffer_handle_t bufferHandle, std::string *outName) const {
+ auto value = getStandardMetadata<StandardMetadataType::NAME>(mMapper, bufferHandle);
+ if (value.has_value()) {
+ *outName = *value;
+ return OK;
+ }
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getWidth(buffer_handle_t bufferHandle, uint64_t *outWidth) const {
+ auto value = getStandardMetadata<StandardMetadataType::WIDTH>(mMapper, bufferHandle);
+ if (value.has_value()) {
+ *outWidth = *value;
+ return OK;
+ }
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getHeight(buffer_handle_t bufferHandle, uint64_t *outHeight) const {
+ auto value = getStandardMetadata<StandardMetadataType::HEIGHT>(mMapper, bufferHandle);
+ if (value.has_value()) {
+ *outHeight = *value;
+ return OK;
+ }
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getLayerCount(buffer_handle_t bufferHandle,
+ uint64_t *outLayerCount) const {
+ auto value = getStandardMetadata<StandardMetadataType::LAYER_COUNT>(mMapper, bufferHandle);
+ if (value.has_value()) {
+ *outLayerCount = *value;
+ return OK;
+ }
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getPixelFormatRequested(buffer_handle_t bufferHandle,
+ ui::PixelFormat *outPixelFormatRequested) const {
+ auto value = getStandardMetadata<StandardMetadataType::PIXEL_FORMAT_REQUESTED>(mMapper,
+ bufferHandle);
+ if (value.has_value()) {
+ *outPixelFormatRequested = static_cast<ui::PixelFormat>(*value);
+ return OK;
+ }
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getPixelFormatFourCC(buffer_handle_t bufferHandle,
+ uint32_t *outPixelFormatFourCC) const {
+ auto value =
+ getStandardMetadata<StandardMetadataType::PIXEL_FORMAT_FOURCC>(mMapper, bufferHandle);
+ if (value.has_value()) {
+ *outPixelFormatFourCC = *value;
+ return OK;
+ }
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getPixelFormatModifier(buffer_handle_t bufferHandle,
+ uint64_t *outPixelFormatModifier) const {
+ auto value =
+ getStandardMetadata<StandardMetadataType::PIXEL_FORMAT_MODIFIER>(mMapper, bufferHandle);
+ if (value.has_value()) {
+ *outPixelFormatModifier = *value;
+ return OK;
+ }
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getUsage(buffer_handle_t bufferHandle, uint64_t *outUsage) const {
+ auto value = getStandardMetadata<StandardMetadataType::USAGE>(mMapper, bufferHandle);
+ if (value.has_value()) {
+ *outUsage = static_cast<uint64_t>(*value);
+ return OK;
+ }
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getAllocationSize(buffer_handle_t bufferHandle,
+ uint64_t *outAllocationSize) const {
+ auto value = getStandardMetadata<StandardMetadataType::ALLOCATION_SIZE>(mMapper, bufferHandle);
+ if (value.has_value()) {
+ *outAllocationSize = *value;
+ return OK;
+ }
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getProtectedContent(buffer_handle_t bufferHandle,
+ uint64_t *outProtectedContent) const {
+ auto value =
+ getStandardMetadata<StandardMetadataType::PROTECTED_CONTENT>(mMapper, bufferHandle);
+ if (value.has_value()) {
+ *outProtectedContent = *value;
+ return OK;
+ }
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getCompression(
+ buffer_handle_t bufferHandle,
+ aidl::android::hardware::graphics::common::ExtendableType *outCompression) const {
+ auto value = getStandardMetadata<StandardMetadataType::COMPRESSION>(mMapper, bufferHandle);
+ if (value.has_value()) {
+ *outCompression = *value;
+ return OK;
+ }
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getCompression(buffer_handle_t bufferHandle,
+ ui::Compression *outCompression) const {
+ auto value = getStandardMetadata<StandardMetadataType::COMPRESSION>(mMapper, bufferHandle);
+ if (!value.has_value()) {
+ return UNKNOWN_TRANSACTION;
+ }
+ if (!gralloc4::isStandardCompression(*value)) {
+ return BAD_TYPE;
+ }
+ *outCompression = gralloc4::getStandardCompressionValue(*value);
+ return OK;
+}
+
+status_t Gralloc5Mapper::getInterlaced(
+ buffer_handle_t bufferHandle,
+ aidl::android::hardware::graphics::common::ExtendableType *outInterlaced) const {
+ auto value = getStandardMetadata<StandardMetadataType::INTERLACED>(mMapper, bufferHandle);
+ if (value.has_value()) {
+ *outInterlaced = *value;
+ return OK;
+ }
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getInterlaced(buffer_handle_t bufferHandle,
+ ui::Interlaced *outInterlaced) const {
+ if (!outInterlaced) {
+ return BAD_VALUE;
+ }
+ ExtendableType interlaced;
+ status_t error = getInterlaced(bufferHandle, &interlaced);
+ if (error) {
+ return error;
+ }
+ if (!gralloc4::isStandardInterlaced(interlaced)) {
+ return BAD_TYPE;
+ }
+ *outInterlaced = gralloc4::getStandardInterlacedValue(interlaced);
+ return NO_ERROR;
+}
+
+status_t Gralloc5Mapper::getChromaSiting(
+ buffer_handle_t bufferHandle,
+ aidl::android::hardware::graphics::common::ExtendableType *outChromaSiting) const {
+ auto value = getStandardMetadata<StandardMetadataType::CHROMA_SITING>(mMapper, bufferHandle);
+ if (value.has_value()) {
+ *outChromaSiting = *value;
+ return OK;
+ }
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getChromaSiting(buffer_handle_t bufferHandle,
+ ui::ChromaSiting *outChromaSiting) const {
+ if (!outChromaSiting) {
+ return BAD_VALUE;
+ }
+ ExtendableType chromaSiting;
+ status_t error = getChromaSiting(bufferHandle, &chromaSiting);
+ if (error) {
+ return error;
+ }
+ if (!gralloc4::isStandardChromaSiting(chromaSiting)) {
+ return BAD_TYPE;
+ }
+ *outChromaSiting = gralloc4::getStandardChromaSitingValue(chromaSiting);
+ return NO_ERROR;
+}
+
+status_t Gralloc5Mapper::getPlaneLayouts(buffer_handle_t bufferHandle,
+ std::vector<ui::PlaneLayout> *outPlaneLayouts) const {
+ auto value = getStandardMetadata<StandardMetadataType::PLANE_LAYOUTS>(mMapper, bufferHandle);
+ if (value.has_value()) {
+ *outPlaneLayouts = *value;
+ return OK;
+ }
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getDataspace(buffer_handle_t bufferHandle,
+ ui::Dataspace *outDataspace) const {
+ auto value = getStandardMetadata<StandardMetadataType::DATASPACE>(mMapper, bufferHandle);
+ if (value.has_value()) {
+ *outDataspace = static_cast<ui::Dataspace>(*value);
+ return OK;
+ }
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::setDataspace(buffer_handle_t bufferHandle, ui::Dataspace dataspace) const {
+ return setStandardMetadata<StandardMetadataType::DATASPACE>(mMapper, bufferHandle,
+ static_cast<Dataspace>(dataspace));
+}
+
+status_t Gralloc5Mapper::getBlendMode(buffer_handle_t bufferHandle,
+ ui::BlendMode *outBlendMode) const {
+ auto value = getStandardMetadata<StandardMetadataType::BLEND_MODE>(mMapper, bufferHandle);
+ if (value.has_value()) {
+ *outBlendMode = static_cast<ui::BlendMode>(*value);
+ return OK;
+ }
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getSmpte2086(buffer_handle_t bufferHandle,
+ std::optional<ui::Smpte2086> *outSmpte2086) const {
+ auto value = getStandardMetadata<StandardMetadataType::SMPTE2086>(mMapper, bufferHandle);
+ if (value.has_value()) {
+ *outSmpte2086 = *value;
+ return OK;
+ }
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::setSmpte2086(buffer_handle_t bufferHandle,
+ std::optional<ui::Smpte2086> smpte2086) const {
+ return setStandardMetadata<StandardMetadataType::SMPTE2086>(mMapper, bufferHandle, smpte2086);
+}
+
+status_t Gralloc5Mapper::getCta861_3(buffer_handle_t bufferHandle,
+ std::optional<ui::Cta861_3> *outCta861_3) const {
+ auto value = getStandardMetadata<StandardMetadataType::CTA861_3>(mMapper, bufferHandle);
+ if (value.has_value()) {
+ *outCta861_3 = *value;
+ return OK;
+ }
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::setCta861_3(buffer_handle_t bufferHandle,
+ std::optional<ui::Cta861_3> cta861_3) const {
+ return setStandardMetadata<StandardMetadataType::CTA861_3>(mMapper, bufferHandle, cta861_3);
+}
+
+status_t Gralloc5Mapper::getSmpte2094_40(
+ buffer_handle_t bufferHandle, std::optional<std::vector<uint8_t>> *outSmpte2094_40) const {
+ auto value = getStandardMetadata<StandardMetadataType::SMPTE2094_40>(mMapper, bufferHandle);
+ if (value.has_value()) {
+ *outSmpte2094_40 = std::move(*value);
+ return OK;
+ }
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::setSmpte2094_40(buffer_handle_t bufferHandle,
+ std::optional<std::vector<uint8_t>> smpte2094_40) const {
+ return setStandardMetadata<StandardMetadataType::SMPTE2094_40>(mMapper, bufferHandle,
+ smpte2094_40);
+}
+
+status_t Gralloc5Mapper::getSmpte2094_10(
+ buffer_handle_t bufferHandle, std::optional<std::vector<uint8_t>> *outSmpte2094_10) const {
+ auto value = getStandardMetadata<StandardMetadataType::SMPTE2094_10>(mMapper, bufferHandle);
+ if (value.has_value()) {
+ *outSmpte2094_10 = std::move(*value);
+ return OK;
+ }
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::setSmpte2094_10(buffer_handle_t bufferHandle,
+ std::optional<std::vector<uint8_t>> smpte2094_10) const {
+ return setStandardMetadata<StandardMetadataType::SMPTE2094_10>(mMapper, bufferHandle,
+ smpte2094_10);
+}
+
+status_t Gralloc5Mapper::getDefaultPixelFormatFourCC(uint32_t, uint32_t, PixelFormat, uint32_t,
+ uint64_t, uint32_t *) const {
+ // TODO(b/261857910): Remove
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getDefaultPixelFormatModifier(uint32_t, uint32_t, PixelFormat, uint32_t,
+ uint64_t, uint64_t *) const {
+ // TODO(b/261857910): Remove
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getDefaultAllocationSize(uint32_t, uint32_t, PixelFormat, uint32_t,
+ uint64_t, uint64_t *) const {
+ // TODO(b/261857910): Remove
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getDefaultProtectedContent(uint32_t, uint32_t, PixelFormat, uint32_t,
+ uint64_t, uint64_t *) const {
+ // TODO(b/261857910): Remove
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getDefaultCompression(
+ uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t,
+ aidl::android::hardware::graphics::common::ExtendableType *) const {
+ // TODO(b/261857910): Remove
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getDefaultCompression(uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t,
+ ui::Compression *) const {
+ // TODO(b/261857910): Remove
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getDefaultInterlaced(
+ uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t,
+ aidl::android::hardware::graphics::common::ExtendableType *) const {
+ // TODO(b/261857910): Remove
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getDefaultInterlaced(uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t,
+ ui::Interlaced *) const {
+ // TODO(b/261857910): Remove
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getDefaultChromaSiting(
+ uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t,
+ aidl::android::hardware::graphics::common::ExtendableType *) const {
+ // TODO(b/261857910): Remove
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getDefaultChromaSiting(uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t,
+ ui::ChromaSiting *) const {
+ // TODO(b/261857910): Remove
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getDefaultPlaneLayouts(uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t,
+ std::vector<ui::PlaneLayout> *) const {
+ // TODO(b/261857910): Remove
+ return UNKNOWN_TRANSACTION;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/ui/GraphicBufferAllocator.cpp b/libs/ui/GraphicBufferAllocator.cpp
index 3f958ba..c0abec2 100644
--- a/libs/ui/GraphicBufferAllocator.cpp
+++ b/libs/ui/GraphicBufferAllocator.cpp
@@ -34,6 +34,7 @@
#include <ui/Gralloc2.h>
#include <ui/Gralloc3.h>
#include <ui/Gralloc4.h>
+#include <ui/Gralloc5.h>
#include <ui/GraphicBufferMapper.h>
namespace android {
@@ -48,23 +49,27 @@
GraphicBufferAllocator::alloc_rec_t> GraphicBufferAllocator::sAllocList;
GraphicBufferAllocator::GraphicBufferAllocator() : mMapper(GraphicBufferMapper::getInstance()) {
- mAllocator = std::make_unique<const Gralloc4Allocator>(
- reinterpret_cast<const Gralloc4Mapper&>(mMapper.getGrallocMapper()));
- if (mAllocator->isLoaded()) {
- return;
+ switch (mMapper.getMapperVersion()) {
+ case GraphicBufferMapper::GRALLOC_5:
+ mAllocator = std::make_unique<const Gralloc5Allocator>(
+ reinterpret_cast<const Gralloc5Mapper&>(mMapper.getGrallocMapper()));
+ break;
+ case GraphicBufferMapper::GRALLOC_4:
+ mAllocator = std::make_unique<const Gralloc4Allocator>(
+ reinterpret_cast<const Gralloc4Mapper&>(mMapper.getGrallocMapper()));
+ break;
+ case GraphicBufferMapper::GRALLOC_3:
+ mAllocator = std::make_unique<const Gralloc3Allocator>(
+ reinterpret_cast<const Gralloc3Mapper&>(mMapper.getGrallocMapper()));
+ break;
+ case GraphicBufferMapper::GRALLOC_2:
+ mAllocator = std::make_unique<const Gralloc2Allocator>(
+ reinterpret_cast<const Gralloc2Mapper&>(mMapper.getGrallocMapper()));
+ break;
}
- mAllocator = std::make_unique<const Gralloc3Allocator>(
- reinterpret_cast<const Gralloc3Mapper&>(mMapper.getGrallocMapper()));
- if (mAllocator->isLoaded()) {
- return;
- }
- mAllocator = std::make_unique<const Gralloc2Allocator>(
- reinterpret_cast<const Gralloc2Mapper&>(mMapper.getGrallocMapper()));
- if (mAllocator->isLoaded()) {
- return;
- }
-
- LOG_ALWAYS_FATAL("gralloc-allocator is missing");
+ LOG_ALWAYS_FATAL_IF(!mAllocator->isLoaded(),
+ "Failed to load matching allocator for mapper version %d",
+ mMapper.getMapperVersion());
}
GraphicBufferAllocator::~GraphicBufferAllocator() {}
diff --git a/libs/ui/GraphicBufferMapper.cpp b/libs/ui/GraphicBufferMapper.cpp
index a98e697..6002a6d 100644
--- a/libs/ui/GraphicBufferMapper.cpp
+++ b/libs/ui/GraphicBufferMapper.cpp
@@ -36,6 +36,7 @@
#include <ui/Gralloc2.h>
#include <ui/Gralloc3.h>
#include <ui/Gralloc4.h>
+#include <ui/Gralloc5.h>
#include <ui/GraphicBuffer.h>
#include <system/graphics.h>
@@ -49,9 +50,15 @@
Gralloc2Mapper::preload();
Gralloc3Mapper::preload();
Gralloc4Mapper::preload();
+ Gralloc5Mapper::preload();
}
GraphicBufferMapper::GraphicBufferMapper() {
+ mMapper = std::make_unique<const Gralloc5Mapper>();
+ if (mMapper->isLoaded()) {
+ mMapperVersion = Version::GRALLOC_5;
+ return;
+ }
mMapper = std::make_unique<const Gralloc4Mapper>();
if (mMapper->isLoaded()) {
mMapperVersion = Version::GRALLOC_4;
@@ -82,15 +89,14 @@
ALOGD("%s", s.c_str());
}
-status_t GraphicBufferMapper::importBuffer(buffer_handle_t rawHandle,
- uint32_t width, uint32_t height, uint32_t layerCount,
- PixelFormat format, uint64_t usage, uint32_t stride,
- buffer_handle_t* outHandle)
-{
+status_t GraphicBufferMapper::importBuffer(const native_handle_t* rawHandle, uint32_t width,
+ uint32_t height, uint32_t layerCount, PixelFormat format,
+ uint64_t usage, uint32_t stride,
+ buffer_handle_t* outHandle) {
ATRACE_CALL();
buffer_handle_t bufferHandle;
- status_t error = mMapper->importBuffer(hardware::hidl_handle(rawHandle), &bufferHandle);
+ status_t error = mMapper->importBuffer(rawHandle, &bufferHandle);
if (error != NO_ERROR) {
ALOGW("importBuffer(%p) failed: %d", rawHandle, error);
return error;
@@ -109,6 +115,11 @@
return NO_ERROR;
}
+status_t GraphicBufferMapper::importBufferNoValidate(const native_handle_t* rawHandle,
+ buffer_handle_t* outHandle) {
+ return mMapper->importBuffer(rawHandle, outHandle);
+}
+
void GraphicBufferMapper::getTransportSize(buffer_handle_t handle,
uint32_t* outTransportNumFds, uint32_t* outTransportNumInts)
{
diff --git a/libs/ui/PublicFormat.cpp b/libs/ui/PublicFormat.cpp
index 78e82da..c9663ed 100644
--- a/libs/ui/PublicFormat.cpp
+++ b/libs/ui/PublicFormat.cpp
@@ -14,14 +14,15 @@
* limitations under the License.
*/
-#include <ui/GraphicTypes.h> // ui::Dataspace
+#include "aidl/android/hardware/graphics/common/Dataspace.h"
#include <ui/PublicFormat.h>
+
// ----------------------------------------------------------------------------
namespace android {
// ----------------------------------------------------------------------------
-using ui::Dataspace;
+using ::aidl::android::hardware::graphics::common::Dataspace;
int mapPublicFormatToHalFormat(PublicFormat f) {
switch (f) {
@@ -29,6 +30,7 @@
case PublicFormat::DEPTH_POINT_CLOUD:
case PublicFormat::DEPTH_JPEG:
case PublicFormat::HEIC:
+ case PublicFormat::JPEG_R:
return HAL_PIXEL_FORMAT_BLOB;
case PublicFormat::DEPTH16:
return HAL_PIXEL_FORMAT_Y16;
@@ -47,7 +49,7 @@
Dataspace dataspace;
switch (f) {
case PublicFormat::JPEG:
- dataspace = Dataspace::V0_JFIF;
+ dataspace = Dataspace::JFIF;
break;
case PublicFormat::DEPTH_POINT_CLOUD:
case PublicFormat::DEPTH16:
@@ -64,7 +66,7 @@
case PublicFormat::YUV_420_888:
case PublicFormat::NV21:
case PublicFormat::YV12:
- dataspace = Dataspace::V0_JFIF;
+ dataspace = Dataspace::JFIF;
break;
case PublicFormat::DEPTH_JPEG:
dataspace = Dataspace::DYNAMIC_DEPTH;
@@ -72,6 +74,9 @@
case PublicFormat::HEIC:
dataspace = Dataspace::HEIF;
break;
+ case PublicFormat::JPEG_R:
+ dataspace = Dataspace::JPEG_R;
+ break;
default:
// Most formats map to UNKNOWN
dataspace = Dataspace::UNKNOWN;
@@ -139,14 +144,16 @@
switch (ds) {
case Dataspace::DEPTH:
return PublicFormat::DEPTH_POINT_CLOUD;
- case Dataspace::V0_JFIF:
+ case Dataspace::JFIF:
return PublicFormat::JPEG;
case Dataspace::HEIF:
return PublicFormat::HEIC;
default:
if (dataSpace == static_cast<android_dataspace>(HAL_DATASPACE_DYNAMIC_DEPTH)) {
return PublicFormat::DEPTH_JPEG;
- } else {
+ } else if (dataSpace == static_cast<android_dataspace>(Dataspace::JPEG_R)) {
+ return PublicFormat::JPEG_R;
+ }else {
// Assume otherwise-marked blobs are also JPEG
return PublicFormat::JPEG;
}
diff --git a/libs/ui/include/ui/Gralloc.h b/libs/ui/include/ui/Gralloc.h
index 6101d4b..b494cbe 100644
--- a/libs/ui/include/ui/Gralloc.h
+++ b/libs/ui/include/ui/Gralloc.h
@@ -39,14 +39,11 @@
return "";
}
- virtual status_t createDescriptor(void* bufferDescriptorInfo,
- void* outBufferDescriptor) const = 0;
-
// Import a buffer that is from another HAL, another process, or is
// cloned.
//
// The returned handle must be freed with freeBuffer.
- virtual status_t importBuffer(const hardware::hidl_handle& rawHandle,
+ virtual status_t importBuffer(const native_handle_t* rawHandle,
buffer_handle_t* outBufferHandle) const = 0;
virtual void freeBuffer(buffer_handle_t bufferHandle) const = 0;
@@ -269,11 +266,6 @@
std::vector<ui::PlaneLayout>* /*outPlaneLayouts*/) const {
return INVALID_OPERATION;
}
-
- virtual std::vector<android::hardware::graphics::mapper::V4_0::IMapper::MetadataTypeDescription>
- listSupportedMetadataTypes() const {
- return {};
- }
};
// A wrapper to IAllocator
diff --git a/libs/ui/include/ui/Gralloc2.h b/libs/ui/include/ui/Gralloc2.h
index f570c42..a7b6f492 100644
--- a/libs/ui/include/ui/Gralloc2.h
+++ b/libs/ui/include/ui/Gralloc2.h
@@ -38,9 +38,9 @@
bool isLoaded() const override;
- status_t createDescriptor(void* bufferDescriptorInfo, void* outBufferDescriptor) const override;
+ status_t createDescriptor(void* bufferDescriptorInfo, void* outBufferDescriptor) const;
- status_t importBuffer(const hardware::hidl_handle& rawHandle,
+ status_t importBuffer(const native_handle_t* rawHandle,
buffer_handle_t* outBufferHandle) const override;
void freeBuffer(buffer_handle_t bufferHandle) const override;
diff --git a/libs/ui/include/ui/Gralloc3.h b/libs/ui/include/ui/Gralloc3.h
index 93a5077..7367549 100644
--- a/libs/ui/include/ui/Gralloc3.h
+++ b/libs/ui/include/ui/Gralloc3.h
@@ -37,9 +37,9 @@
bool isLoaded() const override;
- status_t createDescriptor(void* bufferDescriptorInfo, void* outBufferDescriptor) const override;
+ status_t createDescriptor(void* bufferDescriptorInfo, void* outBufferDescriptor) const;
- status_t importBuffer(const hardware::hidl_handle& rawHandle,
+ status_t importBuffer(const native_handle_t* rawHandle,
buffer_handle_t* outBufferHandle) const override;
void freeBuffer(buffer_handle_t bufferHandle) const override;
diff --git a/libs/ui/include/ui/Gralloc4.h b/libs/ui/include/ui/Gralloc4.h
index cf023c9..6bc5ce5 100644
--- a/libs/ui/include/ui/Gralloc4.h
+++ b/libs/ui/include/ui/Gralloc4.h
@@ -42,9 +42,9 @@
std::string dumpBuffer(buffer_handle_t bufferHandle, bool less = true) const override;
std::string dumpBuffers(bool less = true) const;
- status_t createDescriptor(void* bufferDescriptorInfo, void* outBufferDescriptor) const override;
+ status_t createDescriptor(void* bufferDescriptorInfo, void* outBufferDescriptor) const;
- status_t importBuffer(const hardware::hidl_handle& rawHandle,
+ status_t importBuffer(const native_handle_t* rawHandle,
buffer_handle_t* outBufferHandle) const override;
void freeBuffer(buffer_handle_t bufferHandle) const override;
diff --git a/libs/ui/include/ui/Gralloc5.h b/libs/ui/include/ui/Gralloc5.h
new file mode 100644
index 0000000..bc10169
--- /dev/null
+++ b/libs/ui/include/ui/Gralloc5.h
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/graphics/allocator/IAllocator.h>
+#include <android/hardware/graphics/mapper/IMapper.h>
+#include <ui/Gralloc.h>
+
+namespace android {
+
+class Gralloc5Mapper : public GrallocMapper {
+public:
+public:
+ static void preload();
+
+ Gralloc5Mapper();
+
+ [[nodiscard]] bool isLoaded() const override;
+
+ [[nodiscard]] std::string dumpBuffer(buffer_handle_t bufferHandle, bool less) const override;
+
+ [[nodiscard]] std::string dumpBuffers(bool less = true) const;
+
+ [[nodiscard]] status_t importBuffer(const native_handle_t *rawHandle,
+ buffer_handle_t *outBufferHandle) const override;
+
+ void freeBuffer(buffer_handle_t bufferHandle) const override;
+
+ [[nodiscard]] status_t validateBufferSize(buffer_handle_t bufferHandle, uint32_t width,
+ uint32_t height, PixelFormat format,
+ uint32_t layerCount, uint64_t usage,
+ uint32_t stride) const override;
+
+ void getTransportSize(buffer_handle_t bufferHandle, uint32_t *outNumFds,
+ uint32_t *outNumInts) const override;
+
+ [[nodiscard]] status_t lock(buffer_handle_t bufferHandle, uint64_t usage, const Rect &bounds,
+ int acquireFence, void **outData, int32_t *outBytesPerPixel,
+ int32_t *outBytesPerStride) const override;
+
+ [[nodiscard]] status_t lock(buffer_handle_t bufferHandle, uint64_t usage, const Rect &bounds,
+ int acquireFence, android_ycbcr *ycbcr) const override;
+
+ [[nodiscard]] int unlock(buffer_handle_t bufferHandle) const override;
+
+ [[nodiscard]] status_t isSupported(uint32_t width, uint32_t height, PixelFormat format,
+ uint32_t layerCount, uint64_t usage,
+ bool *outSupported) const override;
+
+ [[nodiscard]] status_t getBufferId(buffer_handle_t bufferHandle,
+ uint64_t *outBufferId) const override;
+
+ [[nodiscard]] status_t getName(buffer_handle_t bufferHandle,
+ std::string *outName) const override;
+
+ [[nodiscard]] status_t getWidth(buffer_handle_t bufferHandle,
+ uint64_t *outWidth) const override;
+
+ [[nodiscard]] status_t getHeight(buffer_handle_t bufferHandle,
+ uint64_t *outHeight) const override;
+
+ [[nodiscard]] status_t getLayerCount(buffer_handle_t bufferHandle,
+ uint64_t *outLayerCount) const override;
+
+ [[nodiscard]] status_t getPixelFormatRequested(
+ buffer_handle_t bufferHandle, ui::PixelFormat *outPixelFormatRequested) const override;
+
+ [[nodiscard]] status_t getPixelFormatFourCC(buffer_handle_t bufferHandle,
+ uint32_t *outPixelFormatFourCC) const override;
+
+ [[nodiscard]] status_t getPixelFormatModifier(buffer_handle_t bufferHandle,
+ uint64_t *outPixelFormatModifier) const override;
+
+ [[nodiscard]] status_t getUsage(buffer_handle_t bufferHandle,
+ uint64_t *outUsage) const override;
+
+ [[nodiscard]] status_t getAllocationSize(buffer_handle_t bufferHandle,
+ uint64_t *outAllocationSize) const override;
+
+ [[nodiscard]] status_t getProtectedContent(buffer_handle_t bufferHandle,
+ uint64_t *outProtectedContent) const override;
+
+ [[nodiscard]] status_t getCompression(buffer_handle_t bufferHandle,
+ aidl::android::hardware::graphics::common::ExtendableType
+ *outCompression) const override;
+
+ [[nodiscard]] status_t getCompression(buffer_handle_t bufferHandle,
+ ui::Compression *outCompression) const override;
+
+ [[nodiscard]] status_t getInterlaced(buffer_handle_t bufferHandle,
+ aidl::android::hardware::graphics::common::ExtendableType
+ *outInterlaced) const override;
+
+ [[nodiscard]] status_t getInterlaced(buffer_handle_t bufferHandle,
+ ui::Interlaced *outInterlaced) const override;
+
+ [[nodiscard]] status_t getChromaSiting(buffer_handle_t bufferHandle,
+ aidl::android::hardware::graphics::common::ExtendableType
+ *outChromaSiting) const override;
+
+ [[nodiscard]] status_t getChromaSiting(buffer_handle_t bufferHandle,
+ ui::ChromaSiting *outChromaSiting) const override;
+
+ [[nodiscard]] status_t getPlaneLayouts(
+ buffer_handle_t bufferHandle,
+ std::vector<ui::PlaneLayout> *outPlaneLayouts) const override;
+
+ [[nodiscard]] status_t getDataspace(buffer_handle_t bufferHandle,
+ ui::Dataspace *outDataspace) const override;
+
+ [[nodiscard]] status_t setDataspace(buffer_handle_t bufferHandle,
+ ui::Dataspace dataspace) const override;
+
+ [[nodiscard]] status_t getBlendMode(buffer_handle_t bufferHandle,
+ ui::BlendMode *outBlendMode) const override;
+
+ [[nodiscard]] status_t getSmpte2086(buffer_handle_t bufferHandle,
+ std::optional<ui::Smpte2086> *outSmpte2086) const override;
+
+ [[nodiscard]] status_t setSmpte2086(buffer_handle_t bufferHandle,
+ std::optional<ui::Smpte2086> smpte2086) const override;
+
+ [[nodiscard]] status_t getCta861_3(buffer_handle_t bufferHandle,
+ std::optional<ui::Cta861_3> *outCta861_3) const override;
+
+ [[nodiscard]] status_t setCta861_3(buffer_handle_t bufferHandle,
+ std::optional<ui::Cta861_3> cta861_3) const override;
+
+ [[nodiscard]] status_t getSmpte2094_40(
+ buffer_handle_t bufferHandle,
+ std::optional<std::vector<uint8_t>> *outSmpte2094_40) const override;
+
+ [[nodiscard]] status_t setSmpte2094_40(
+ buffer_handle_t bufferHandle,
+ std::optional<std::vector<uint8_t>> smpte2094_40) const override;
+
+ [[nodiscard]] status_t getSmpte2094_10(
+ buffer_handle_t bufferHandle,
+ std::optional<std::vector<uint8_t>> *outSmpte2094_10) const override;
+
+ [[nodiscard]] status_t setSmpte2094_10(
+ buffer_handle_t bufferHandle,
+ std::optional<std::vector<uint8_t>> smpte2094_10) const override;
+
+ [[nodiscard]] status_t getDefaultPixelFormatFourCC(
+ uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount,
+ uint64_t usage, uint32_t *outPixelFormatFourCC) const override;
+
+ [[nodiscard]] status_t getDefaultPixelFormatModifier(
+ uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount,
+ uint64_t usage, uint64_t *outPixelFormatModifier) const override;
+
+ [[nodiscard]] status_t getDefaultAllocationSize(uint32_t width, uint32_t height,
+ PixelFormat format, uint32_t layerCount,
+ uint64_t usage,
+ uint64_t *outAllocationSize) const override;
+
+ [[nodiscard]] status_t getDefaultProtectedContent(uint32_t width, uint32_t height,
+ PixelFormat format, uint32_t layerCount,
+ uint64_t usage,
+ uint64_t *outProtectedContent) const override;
+
+ [[nodiscard]] status_t getDefaultCompression(
+ uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount,
+ uint64_t usage,
+ aidl::android::hardware::graphics::common::ExtendableType *outCompression)
+ const override;
+
+ [[nodiscard]] status_t getDefaultCompression(uint32_t width, uint32_t height,
+ PixelFormat format, uint32_t layerCount,
+ uint64_t usage,
+ ui::Compression *outCompression) const override;
+
+ [[nodiscard]] status_t getDefaultInterlaced(
+ uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount,
+ uint64_t usage,
+ aidl::android::hardware::graphics::common::ExtendableType *outInterlaced)
+ const override;
+
+ [[nodiscard]] status_t getDefaultInterlaced(uint32_t width, uint32_t height, PixelFormat format,
+ uint32_t layerCount, uint64_t usage,
+ ui::Interlaced *outInterlaced) const override;
+
+ [[nodiscard]] status_t getDefaultChromaSiting(
+ uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount,
+ uint64_t usage,
+ aidl::android::hardware::graphics::common::ExtendableType *outChromaSiting)
+ const override;
+
+ [[nodiscard]] status_t getDefaultChromaSiting(uint32_t width, uint32_t height,
+ PixelFormat format, uint32_t layerCount,
+ uint64_t usage,
+ ui::ChromaSiting *outChromaSiting) const override;
+
+ [[nodiscard]] status_t getDefaultPlaneLayouts(
+ uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount,
+ uint64_t usage, std::vector<ui::PlaneLayout> *outPlaneLayouts) const override;
+
+private:
+ void unlockBlocking(buffer_handle_t bufferHandle) const;
+
+ AIMapper *mMapper = nullptr;
+};
+
+class Gralloc5Allocator : public GrallocAllocator {
+public:
+ Gralloc5Allocator(const Gralloc5Mapper &mapper);
+
+ [[nodiscard]] bool isLoaded() const override;
+
+ [[nodiscard]] std::string dumpDebugInfo(bool less) const override;
+
+ [[nodiscard]] status_t allocate(std::string requestorName, uint32_t width, uint32_t height,
+ PixelFormat format, uint32_t layerCount, uint64_t usage,
+ uint32_t bufferCount, uint32_t *outStride,
+ buffer_handle_t *outBufferHandles,
+ bool importBuffers) const override;
+
+private:
+ const Gralloc5Mapper &mMapper;
+ std::shared_ptr<aidl::android::hardware::graphics::allocator::IAllocator> mAllocator;
+};
+
+} // namespace android
diff --git a/libs/ui/include/ui/GraphicBufferMapper.h b/libs/ui/include/ui/GraphicBufferMapper.h
index 507fa35..51c6e92 100644
--- a/libs/ui/include/ui/GraphicBufferMapper.h
+++ b/libs/ui/include/ui/GraphicBufferMapper.h
@@ -42,9 +42,10 @@
{
public:
enum Version {
- GRALLOC_2,
+ GRALLOC_2 = 2,
GRALLOC_3,
GRALLOC_4,
+ GRALLOC_5,
};
static void preloadHal();
static inline GraphicBufferMapper& get() { return getInstance(); }
@@ -54,10 +55,11 @@
// The imported outHandle must be freed with freeBuffer when no longer
// needed. rawHandle is owned by the caller.
- status_t importBuffer(buffer_handle_t rawHandle,
- uint32_t width, uint32_t height, uint32_t layerCount,
- PixelFormat format, uint64_t usage, uint32_t stride,
- buffer_handle_t* outHandle);
+ status_t importBuffer(const native_handle_t* rawHandle, uint32_t width, uint32_t height,
+ uint32_t layerCount, PixelFormat format, uint64_t usage, uint32_t stride,
+ buffer_handle_t* outHandle);
+
+ status_t importBufferNoValidate(const native_handle_t* rawHandle, buffer_handle_t* outHandle);
status_t freeBuffer(buffer_handle_t handle);
diff --git a/libs/ui/include/ui/PixelFormat.h b/libs/ui/include/ui/PixelFormat.h
index f422ce4..cf5c2e8 100644
--- a/libs/ui/include/ui/PixelFormat.h
+++ b/libs/ui/include/ui/PixelFormat.h
@@ -53,16 +53,19 @@
// real pixel formats supported for rendering -----------------------------
- PIXEL_FORMAT_RGBA_8888 = HAL_PIXEL_FORMAT_RGBA_8888, // 4x8-bit RGBA
- PIXEL_FORMAT_RGBX_8888 = HAL_PIXEL_FORMAT_RGBX_8888, // 4x8-bit RGB0
- PIXEL_FORMAT_RGB_888 = HAL_PIXEL_FORMAT_RGB_888, // 3x8-bit RGB
- PIXEL_FORMAT_RGB_565 = HAL_PIXEL_FORMAT_RGB_565, // 16-bit RGB
- PIXEL_FORMAT_BGRA_8888 = HAL_PIXEL_FORMAT_BGRA_8888, // 4x8-bit BGRA
- PIXEL_FORMAT_RGBA_5551 = 6, // 16-bit ARGB
- PIXEL_FORMAT_RGBA_4444 = 7, // 16-bit ARGB
- PIXEL_FORMAT_RGBA_FP16 = HAL_PIXEL_FORMAT_RGBA_FP16, // 64-bit RGBA
- PIXEL_FORMAT_RGBA_1010102 = HAL_PIXEL_FORMAT_RGBA_1010102, // 32-bit RGBA
- PIXEL_FORMAT_R_8 = 0x38,
+ PIXEL_FORMAT_RGBA_8888 = HAL_PIXEL_FORMAT_RGBA_8888, // 4x8-bit RGBA
+ PIXEL_FORMAT_RGBX_8888 = HAL_PIXEL_FORMAT_RGBX_8888, // 4x8-bit RGB0
+ PIXEL_FORMAT_RGB_888 = HAL_PIXEL_FORMAT_RGB_888, // 3x8-bit RGB
+ PIXEL_FORMAT_RGB_565 = HAL_PIXEL_FORMAT_RGB_565, // 16-bit RGB
+ PIXEL_FORMAT_BGRA_8888 = HAL_PIXEL_FORMAT_BGRA_8888, // 4x8-bit BGRA
+ PIXEL_FORMAT_RGBA_5551 = 6, // 16-bit ARGB
+ PIXEL_FORMAT_RGBA_4444 = 7, // 16-bit ARGB
+ PIXEL_FORMAT_RGBA_FP16 = HAL_PIXEL_FORMAT_RGBA_FP16, // 64-bit RGBA
+ PIXEL_FORMAT_RGBA_1010102 = HAL_PIXEL_FORMAT_RGBA_1010102, // 32-bit RGBA
+ PIXEL_FORMAT_R_8 = 0x38,
+ PIXEL_FORMAT_R_16_UINT = 0x39,
+ PIXEL_FORMAT_RG_1616_UINT = 0x3a,
+ PIXEL_FORMAT_RGBA_10101010 = 0x3b,
};
typedef int32_t PixelFormat;
diff --git a/libs/ui/include/ui/PublicFormat.h b/libs/ui/include/ui/PublicFormat.h
index aa58805..2248cca 100644
--- a/libs/ui/include/ui/PublicFormat.h
+++ b/libs/ui/include/ui/PublicFormat.h
@@ -57,6 +57,7 @@
YCBCR_P010 = 0x36,
DEPTH16 = 0x44363159,
DEPTH_JPEG = 0x69656963,
+ JPEG_R = 0x1005,
HEIC = 0x48454946,
};
diff --git a/libs/ui/include_types/ui/DataspaceUtils.h b/libs/ui/include_types/ui/DataspaceUtils.h
index a461cb4..cd31167 100644
--- a/libs/ui/include_types/ui/DataspaceUtils.h
+++ b/libs/ui/include_types/ui/DataspaceUtils.h
@@ -22,8 +22,10 @@
inline bool isHdrDataspace(ui::Dataspace dataspace) {
const auto transfer = dataspace & HAL_DATASPACE_TRANSFER_MASK;
+ const auto range = dataspace & HAL_DATASPACE_RANGE_MASK;
- return transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG;
+ return transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG ||
+ range == HAL_DATASPACE_RANGE_EXTENDED;
}
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/libs/ui/tests/DataspaceUtils_test.cpp b/libs/ui/tests/DataspaceUtils_test.cpp
index 3e09671..ffe6438 100644
--- a/libs/ui/tests/DataspaceUtils_test.cpp
+++ b/libs/ui/tests/DataspaceUtils_test.cpp
@@ -29,12 +29,13 @@
EXPECT_TRUE(isHdrDataspace(ui::Dataspace::BT2020_ITU_PQ));
EXPECT_TRUE(isHdrDataspace(ui::Dataspace::BT2020_PQ));
EXPECT_TRUE(isHdrDataspace(ui::Dataspace::BT2020_HLG));
+ // The original formulation of scRGB indicates the same white points as that
+ // of sRGB, however scRGB may be used to implement HDR.
+ EXPECT_TRUE(isHdrDataspace(ui::Dataspace::V0_SCRGB_LINEAR));
+ EXPECT_TRUE(isHdrDataspace(ui::Dataspace::V0_SCRGB));
EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_SRGB_LINEAR));
- // scRGB defines a very wide gamut but not an expanded luminance range
- EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_SCRGB_LINEAR));
EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_SRGB));
- EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_SCRGB));
EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_JFIF));
EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_BT601_625));
EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_BT601_525));
diff --git a/libs/ui/tests/colorspace_test.cpp b/libs/ui/tests/colorspace_test.cpp
index 0a4873c..3fb33b4 100644
--- a/libs/ui/tests/colorspace_test.cpp
+++ b/libs/ui/tests/colorspace_test.cpp
@@ -111,6 +111,7 @@
EXPECT_NEAR(1.0f, sRGB.getEOTF()(1.0f), 1e-6f);
EXPECT_NEAR(1.0f, sRGB.getOETF()(1.0f), 1e-6f);
+ // NOLINTNEXTLINE(clang-analyzer-security.FloatLoopCounter,cert-flp30-c)
for (float v = 0.0f; v <= 0.5f; v += 1e-3f) {
ASSERT_TRUE(v >= sRGB.getEOTF()(v));
ASSERT_TRUE(v <= sRGB.getOETF()(v));
@@ -118,6 +119,7 @@
float previousEOTF = std::numeric_limits<float>::lowest();
float previousOETF = std::numeric_limits<float>::lowest();
+ // NOLINTNEXTLINE(clang-analyzer-security.FloatLoopCounter,cert-flp30-c)
for (float v = 0.0f; v <= 1.0f; v += 1e-3f) {
ASSERT_TRUE(previousEOTF < sRGB.getEOTF()(v));
previousEOTF = sRGB.getEOTF()(v);
@@ -131,6 +133,7 @@
{0.3127f, 0.3290f}
// linear transfer functions
);
+ // NOLINTNEXTLINE(clang-analyzer-security.FloatLoopCounter,cert-flp30-c)
for (float v = 0.0f; v <= 1.0f; v += 1e-3f) {
ASSERT_EQ(v, sRGB2.getEOTF()(v));
ASSERT_EQ(v, sRGB2.getOETF()(v));
diff --git a/opengl/TEST_MAPPING b/opengl/TEST_MAPPING
index d391dce..7c50a94 100644
--- a/opengl/TEST_MAPPING
+++ b/opengl/TEST_MAPPING
@@ -2,6 +2,9 @@
"presubmit": [
{
"name": "CtsGpuToolsHostTestCases"
+ },
+ {
+ "name": "EGL_test"
}
]
}
diff --git a/opengl/libs/Android.bp b/opengl/libs/Android.bp
index 62cf255..750338b 100644
--- a/opengl/libs/Android.bp
+++ b/opengl/libs/Android.bp
@@ -160,6 +160,7 @@
srcs: [
"EGL/egl_tls.cpp",
"EGL/egl_cache.cpp",
+ "EGL/egl_cache_multifile.cpp",
"EGL/egl_display.cpp",
"EGL/egl_object.cpp",
"EGL/egl_layers.cpp",
diff --git a/opengl/libs/EGL/FileBlobCache.cpp b/opengl/libs/EGL/FileBlobCache.cpp
index 751f3be..3f7ae7e 100644
--- a/opengl/libs/EGL/FileBlobCache.cpp
+++ b/opengl/libs/EGL/FileBlobCache.cpp
@@ -185,4 +185,10 @@
}
}
+size_t FileBlobCache::getSize() {
+ if (mFilename.length() > 0) {
+ return getFlattenedSize() + cacheFileHeaderSize;
+ }
+ return 0;
+}
}
diff --git a/opengl/libs/EGL/FileBlobCache.h b/opengl/libs/EGL/FileBlobCache.h
index 393703f..8220723 100644
--- a/opengl/libs/EGL/FileBlobCache.h
+++ b/opengl/libs/EGL/FileBlobCache.h
@@ -33,6 +33,9 @@
// disk.
void writeToFile();
+ // Return the total size of the cache
+ size_t getSize();
+
private:
// mFilename is the name of the file for storing cache contents.
std::string mFilename;
diff --git a/opengl/libs/EGL/egl_cache.cpp b/opengl/libs/EGL/egl_cache.cpp
index 8348d6c..1e8a348 100644
--- a/opengl/libs/EGL/egl_cache.cpp
+++ b/opengl/libs/EGL/egl_cache.cpp
@@ -16,6 +16,8 @@
#include "egl_cache.h"
+#include <android-base/properties.h>
+#include <inttypes.h>
#include <log/log.h>
#include <private/EGL/cache.h>
#include <unistd.h>
@@ -23,16 +25,23 @@
#include <thread>
#include "../egl_impl.h"
+#include "egl_cache_multifile.h"
#include "egl_display.h"
-// Cache size limits.
+// Monolithic cache size limits.
static const size_t maxKeySize = 12 * 1024;
static const size_t maxValueSize = 64 * 1024;
static const size_t maxTotalSize = 32 * 1024 * 1024;
-// The time in seconds to wait before saving newly inserted cache entries.
+// The time in seconds to wait before saving newly inserted monolithic cache entries.
static const unsigned int deferredSaveDelay = 4;
+// Multifile cache size limit
+constexpr size_t kMultifileCacheByteLimit = 64 * 1024 * 1024;
+
+// Delay before cleaning up multifile cache entries
+static const unsigned int deferredMultifileCleanupDelaySeconds = 1;
+
namespace android {
#define BC_EXT_STR "EGL_ANDROID_blob_cache"
@@ -58,7 +67,11 @@
//
// egl_cache_t definition
//
-egl_cache_t::egl_cache_t() : mInitialized(false) {}
+egl_cache_t::egl_cache_t()
+ : mInitialized(false),
+ mMultifileMode(false),
+ mCacheByteLimit(maxTotalSize),
+ mMultifileCleanupPending(false) {}
egl_cache_t::~egl_cache_t() {}
@@ -101,6 +114,16 @@
}
}
+ // Allow forcing monolithic cache for debug purposes
+ if (base::GetProperty("debug.egl.blobcache.multifilemode", "") == "false") {
+ ALOGD("Forcing monolithic cache due to debug.egl.blobcache.multifilemode == \"false\"");
+ mMultifileMode = false;
+ }
+
+ if (mMultifileMode) {
+ mCacheByteLimit = kMultifileCacheByteLimit;
+ }
+
mInitialized = true;
}
@@ -110,6 +133,11 @@
mBlobCache->writeToFile();
}
mBlobCache = nullptr;
+ if (mMultifileMode) {
+ checkMultifileCacheSize(mCacheByteLimit);
+ }
+ mMultifileMode = false;
+ mInitialized = false;
}
void egl_cache_t::setBlob(const void* key, EGLsizeiANDROID keySize, const void* value,
@@ -122,20 +150,37 @@
}
if (mInitialized) {
- BlobCache* bc = getBlobCacheLocked();
- bc->set(key, keySize, value, valueSize);
+ if (mMultifileMode) {
+ setBlobMultifile(key, keySize, value, valueSize, mFilename);
- if (!mSavePending) {
- mSavePending = true;
- std::thread deferredSaveThread([this]() {
- sleep(deferredSaveDelay);
- std::lock_guard<std::mutex> lock(mMutex);
- if (mInitialized && mBlobCache) {
- mBlobCache->writeToFile();
- }
- mSavePending = false;
- });
- deferredSaveThread.detach();
+ if (!mMultifileCleanupPending) {
+ mMultifileCleanupPending = true;
+ // Kick off a thread to cull cache files below limit
+ std::thread deferredMultifileCleanupThread([this]() {
+ sleep(deferredMultifileCleanupDelaySeconds);
+ std::lock_guard<std::mutex> lock(mMutex);
+ // Check the size of cache and remove entries to stay under limit
+ checkMultifileCacheSize(mCacheByteLimit);
+ mMultifileCleanupPending = false;
+ });
+ deferredMultifileCleanupThread.detach();
+ }
+ } else {
+ BlobCache* bc = getBlobCacheLocked();
+ bc->set(key, keySize, value, valueSize);
+
+ if (!mSavePending) {
+ mSavePending = true;
+ std::thread deferredSaveThread([this]() {
+ sleep(deferredSaveDelay);
+ std::lock_guard<std::mutex> lock(mMutex);
+ if (mInitialized && mBlobCache) {
+ mBlobCache->writeToFile();
+ }
+ mSavePending = false;
+ });
+ deferredSaveThread.detach();
+ }
}
}
}
@@ -145,13 +190,17 @@
std::lock_guard<std::mutex> lock(mMutex);
if (keySize < 0 || valueSize < 0) {
- ALOGW("EGL_ANDROID_blob_cache set: negative sizes are not allowed");
+ ALOGW("EGL_ANDROID_blob_cache get: negative sizes are not allowed");
return 0;
}
if (mInitialized) {
- BlobCache* bc = getBlobCacheLocked();
- return bc->get(key, keySize, value, valueSize);
+ if (mMultifileMode) {
+ return getBlobMultifile(key, keySize, value, valueSize, mFilename);
+ } else {
+ BlobCache* bc = getBlobCacheLocked();
+ return bc->get(key, keySize, value, valueSize);
+ }
}
return 0;
}
@@ -161,9 +210,34 @@
mFilename = filename;
}
+void egl_cache_t::setCacheLimit(int64_t cacheByteLimit) {
+ std::lock_guard<std::mutex> lock(mMutex);
+
+ if (!mMultifileMode) {
+ // If we're not in multifile mode, ensure the cache limit is only being lowered,
+ // not increasing above the hard coded platform limit
+ if (cacheByteLimit > maxTotalSize) {
+ return;
+ }
+ }
+
+ mCacheByteLimit = cacheByteLimit;
+}
+
+size_t egl_cache_t::getCacheSize() {
+ std::lock_guard<std::mutex> lock(mMutex);
+ if (mMultifileMode) {
+ return getMultifileCacheSize();
+ }
+ if (mBlobCache) {
+ return mBlobCache->getSize();
+ }
+ return 0;
+}
+
BlobCache* egl_cache_t::getBlobCacheLocked() {
if (mBlobCache == nullptr) {
- mBlobCache.reset(new FileBlobCache(maxKeySize, maxValueSize, maxTotalSize, mFilename));
+ mBlobCache.reset(new FileBlobCache(maxKeySize, maxValueSize, mCacheByteLimit, mFilename));
}
return mBlobCache.get();
}
diff --git a/opengl/libs/EGL/egl_cache.h b/opengl/libs/EGL/egl_cache.h
index d10a615..2dcd803 100644
--- a/opengl/libs/EGL/egl_cache.h
+++ b/opengl/libs/EGL/egl_cache.h
@@ -64,6 +64,12 @@
// cache contents from one program invocation to another.
void setCacheFilename(const char* filename);
+ // Allow the fixed cache limit to be overridden
+ void setCacheLimit(int64_t cacheByteLimit);
+
+ // Return the byte total for cache file(s)
+ size_t getCacheSize();
+
private:
// Creation and (the lack of) destruction is handled internally.
egl_cache_t();
@@ -112,6 +118,16 @@
// sCache is the singleton egl_cache_t object.
static egl_cache_t sCache;
+
+ // Whether to use multiple files to store cache entries
+ bool mMultifileMode;
+
+ // Cache limit
+ int64_t mCacheByteLimit;
+
+ // Whether we've kicked off a side thread that will check the multifile
+ // cache size and remove entries if needed.
+ bool mMultifileCleanupPending;
};
}; // namespace android
diff --git a/opengl/libs/EGL/egl_cache_multifile.cpp b/opengl/libs/EGL/egl_cache_multifile.cpp
new file mode 100644
index 0000000..48e557f
--- /dev/null
+++ b/opengl/libs/EGL/egl_cache_multifile.cpp
@@ -0,0 +1,343 @@
+/*
+ ** 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.
+ */
+
+// #define LOG_NDEBUG 0
+
+#include "egl_cache_multifile.h"
+
+#include <android-base/properties.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <log/log.h>
+#include <stdio.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <utime.h>
+
+#include <algorithm>
+#include <chrono>
+#include <fstream>
+#include <limits>
+#include <locale>
+#include <map>
+#include <sstream>
+#include <unordered_map>
+
+#include <utils/JenkinsHash.h>
+
+static std::string multifileDirName = "";
+
+using namespace std::literals;
+
+namespace {
+
+// Create a directory for tracking multiple files
+void setupMultifile(const std::string& baseDir) {
+ // If we've already set up the multifile dir in this base directory, we're done
+ if (!multifileDirName.empty() && multifileDirName.find(baseDir) != std::string::npos) {
+ return;
+ }
+
+ // Otherwise, create it
+ multifileDirName = baseDir + ".multifile";
+ if (mkdir(multifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) {
+ ALOGW("Unable to create directory (%s), errno (%i)", multifileDirName.c_str(), errno);
+ }
+}
+
+// Create a filename that is based on the hash of the key
+std::string getCacheEntryFilename(const void* key, EGLsizeiANDROID keySize,
+ const std::string& baseDir) {
+ // Hash the key into a string
+ std::stringstream keyName;
+ keyName << android::JenkinsHashMixBytes(0, static_cast<const uint8_t*>(key), keySize);
+
+ // Build a filename using dir and hash
+ return baseDir + "/" + keyName.str();
+}
+
+// Determine file age based on stat modification time
+// Newer files have a higher age (time since epoch)
+time_t getFileAge(const std::string& filePath) {
+ struct stat st;
+ if (stat(filePath.c_str(), &st) == 0) {
+ ALOGD("getFileAge returning %" PRId64 " for file age", static_cast<uint64_t>(st.st_mtime));
+ return st.st_mtime;
+ } else {
+ ALOGW("Failed to stat %s", filePath.c_str());
+ return 0;
+ }
+}
+
+size_t getFileSize(const std::string& filePath) {
+ struct stat st;
+ if (stat(filePath.c_str(), &st) != 0) {
+ ALOGE("Unable to stat %s", filePath.c_str());
+ return 0;
+ }
+ return st.st_size;
+}
+
+// Walk through directory entries and track age and size
+// Then iterate through the entries, oldest first, and remove them until under the limit.
+// This will need to be updated if we move to a multilevel cache dir.
+bool applyLRU(size_t cacheLimit) {
+ // Build a multimap of files indexed by age.
+ // They will be automatically sorted smallest (oldest) to largest (newest)
+ std::multimap<time_t, std::string> agesToFiles;
+
+ // Map files to sizes
+ std::unordered_map<std::string, size_t> filesToSizes;
+
+ size_t totalCacheSize = 0;
+
+ DIR* dir;
+ struct dirent* entry;
+ if ((dir = opendir(multifileDirName.c_str())) != nullptr) {
+ while ((entry = readdir(dir)) != nullptr) {
+ if (entry->d_name == "."s || entry->d_name == ".."s) {
+ continue;
+ }
+
+ // Look up each file age
+ std::string fullPath = multifileDirName + "/" + entry->d_name;
+ time_t fileAge = getFileAge(fullPath);
+
+ // Track the files, sorted by age
+ agesToFiles.insert(std::make_pair(fileAge, fullPath));
+
+ // Also track the size so we know how much room we have freed
+ size_t fileSize = getFileSize(fullPath);
+ filesToSizes[fullPath] = fileSize;
+ totalCacheSize += fileSize;
+ }
+ closedir(dir);
+ } else {
+ ALOGE("Unable to open filename: %s", multifileDirName.c_str());
+ return false;
+ }
+
+ if (totalCacheSize <= cacheLimit) {
+ // If LRU was called on a sufficiently small cache, no need to remove anything
+ return true;
+ }
+
+ // Walk through the map of files until we're under the cache size
+ for (const auto& cacheEntryIter : agesToFiles) {
+ time_t entryAge = cacheEntryIter.first;
+ const std::string entryPath = cacheEntryIter.second;
+
+ ALOGD("Removing %s with age %ld", entryPath.c_str(), entryAge);
+ if (std::remove(entryPath.c_str()) != 0) {
+ ALOGE("Error removing %s: %s", entryPath.c_str(), std::strerror(errno));
+ return false;
+ }
+
+ totalCacheSize -= filesToSizes[entryPath];
+ if (totalCacheSize <= cacheLimit) {
+ // Success
+ ALOGV("Reduced cache to %zu", totalCacheSize);
+ return true;
+ } else {
+ ALOGD("Cache size is still too large (%zu), removing more files", totalCacheSize);
+ }
+ }
+
+ // Should never reach this return
+ return false;
+}
+
+} // namespace
+
+namespace android {
+
+void setBlobMultifile(const void* key, EGLsizeiANDROID keySize, const void* value,
+ EGLsizeiANDROID valueSize, const std::string& baseDir) {
+ if (baseDir.empty()) {
+ return;
+ }
+
+ setupMultifile(baseDir);
+ std::string filename = getCacheEntryFilename(key, keySize, multifileDirName);
+
+ ALOGD("Attempting to open filename for set: %s", filename.c_str());
+ std::ofstream outfile(filename, std::ofstream::binary);
+ if (outfile.fail()) {
+ ALOGW("Unable to open filename: %s", filename.c_str());
+ return;
+ }
+
+ // First write the key
+ outfile.write(static_cast<const char*>(key), keySize);
+ if (outfile.bad()) {
+ ALOGW("Unable to write key to filename: %s", filename.c_str());
+ outfile.close();
+ return;
+ }
+ ALOGD("Wrote %i bytes to out file for key", static_cast<int>(outfile.tellp()));
+
+ // Then write the value
+ outfile.write(static_cast<const char*>(value), valueSize);
+ if (outfile.bad()) {
+ ALOGW("Unable to write value to filename: %s", filename.c_str());
+ outfile.close();
+ return;
+ }
+ ALOGD("Wrote %i bytes to out file for full entry", static_cast<int>(outfile.tellp()));
+
+ outfile.close();
+}
+
+EGLsizeiANDROID getBlobMultifile(const void* key, EGLsizeiANDROID keySize, void* value,
+ EGLsizeiANDROID valueSize, const std::string& baseDir) {
+ if (baseDir.empty()) {
+ return 0;
+ }
+
+ setupMultifile(baseDir);
+ std::string filename = getCacheEntryFilename(key, keySize, multifileDirName);
+
+ // Open the hashed filename path
+ ALOGD("Attempting to open filename for get: %s", filename.c_str());
+ int fd = open(filename.c_str(), O_RDONLY);
+
+ // File doesn't exist, this is a MISS, return zero bytes read
+ if (fd == -1) {
+ ALOGD("Cache MISS - failed to open filename: %s, error: %s", filename.c_str(),
+ std::strerror(errno));
+ return 0;
+ }
+
+ ALOGD("Cache HIT - opened filename: %s", filename.c_str());
+
+ // Get the size of the file
+ size_t entrySize = getFileSize(filename);
+ if (keySize > entrySize) {
+ ALOGW("keySize (%lu) is larger than entrySize (%zu). This is a hash collision or modified "
+ "file",
+ keySize, entrySize);
+ close(fd);
+ return 0;
+ }
+
+ // Memory map the file
+ uint8_t* cacheEntry =
+ reinterpret_cast<uint8_t*>(mmap(nullptr, entrySize, PROT_READ, MAP_PRIVATE, fd, 0));
+ if (cacheEntry == MAP_FAILED) {
+ ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno));
+ close(fd);
+ return 0;
+ }
+
+ // Compare the incoming key with our stored version (the beginning of the entry)
+ int compare = memcmp(cacheEntry, key, keySize);
+ if (compare != 0) {
+ ALOGW("Cached key and new key do not match! This is a hash collision or modified file");
+ munmap(cacheEntry, entrySize);
+ close(fd);
+ return 0;
+ }
+
+ // Keys matched, so remaining cache is value size
+ size_t cachedValueSize = entrySize - keySize;
+
+ // Return actual value size if valueSize is not large enough
+ if (cachedValueSize > valueSize) {
+ ALOGD("Skipping file read, not enough room provided (valueSize): %lu, "
+ "returning required space as %zu",
+ valueSize, cachedValueSize);
+ munmap(cacheEntry, entrySize);
+ close(fd);
+ return cachedValueSize;
+ }
+
+ // Remaining entry following the key is the value
+ uint8_t* cachedValue = cacheEntry + keySize;
+ memcpy(value, cachedValue, cachedValueSize);
+ munmap(cacheEntry, entrySize);
+ close(fd);
+
+ ALOGD("Read %zu bytes from %s", cachedValueSize, filename.c_str());
+ return cachedValueSize;
+}
+
+// Walk through the files in our flat directory, checking the size of each one.
+// Return the total size of normal files in the directory.
+// This will need to be updated if we move to a multilevel cache dir.
+size_t getMultifileCacheSize() {
+ if (multifileDirName.empty()) {
+ return 0;
+ }
+
+ DIR* dir;
+ struct dirent* entry;
+ size_t size = 0;
+
+ ALOGD("Using %s as the multifile cache dir ", multifileDirName.c_str());
+
+ if ((dir = opendir(multifileDirName.c_str())) != nullptr) {
+ while ((entry = readdir(dir)) != nullptr) {
+ if (entry->d_name == "."s || entry->d_name == ".."s) {
+ continue;
+ }
+
+ // Add up the size of all files in the dir
+ std::string fullPath = multifileDirName + "/" + entry->d_name;
+ size += getFileSize(fullPath);
+ }
+ closedir(dir);
+ } else {
+ ALOGW("Unable to open filename: %s", multifileDirName.c_str());
+ return 0;
+ }
+
+ return size;
+}
+
+// When removing files, what fraction of the overall limit should be reached when removing files
+// A divisor of two will decrease the cache to 50%, four to 25% and so on
+constexpr uint32_t kCacheLimitDivisor = 2;
+
+// Calculate the cache size and remove old entries until under the limit
+void checkMultifileCacheSize(size_t cacheByteLimit) {
+ // Start with the value provided by egl_cache
+ size_t limit = cacheByteLimit;
+
+ // Check for a debug value
+ int debugCacheSize = base::GetIntProperty("debug.egl.blobcache.bytelimit", -1);
+ if (debugCacheSize >= 0) {
+ ALOGV("Overriding cache limit %zu with %i from debug.egl.blobcache.bytelimit", limit,
+ debugCacheSize);
+ limit = debugCacheSize;
+ }
+
+ // Tally up the initial amount of cache in use
+ size_t size = getMultifileCacheSize();
+ ALOGD("Multifile cache dir size: %zu", size);
+
+ // If size is larger than the threshold, remove files using LRU
+ if (size > limit) {
+ ALOGV("Multifile cache size is larger than %zu, removing old entries", cacheByteLimit);
+ if (!applyLRU(limit / kCacheLimitDivisor)) {
+ ALOGE("Error when clearing multifile shader cache");
+ return;
+ }
+ }
+ ALOGD("Multifile cache size after reduction: %zu", getMultifileCacheSize());
+}
+
+}; // namespace android
\ No newline at end of file
diff --git a/opengl/libs/EGL/egl_cache_multifile.h b/opengl/libs/EGL/egl_cache_multifile.h
new file mode 100644
index 0000000..ee5fe81
--- /dev/null
+++ b/opengl/libs/EGL/egl_cache_multifile.h
@@ -0,0 +1,36 @@
+/*
+ ** 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.
+ */
+
+#ifndef ANDROID_EGL_CACHE_MULTIFILE_H
+#define ANDROID_EGL_CACHE_MULTIFILE_H
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
+#include <string>
+
+namespace android {
+
+void setBlobMultifile(const void* key, EGLsizeiANDROID keySize, const void* value,
+ EGLsizeiANDROID valueSize, const std::string& baseDir);
+EGLsizeiANDROID getBlobMultifile(const void* key, EGLsizeiANDROID keySize, void* value,
+ EGLsizeiANDROID valueSize, const std::string& baseDir);
+size_t getMultifileCacheSize();
+void checkMultifileCacheSize(size_t cacheByteLimit);
+
+}; // namespace android
+
+#endif // ANDROID_EGL_CACHE_MULTIFILE_H
diff --git a/opengl/tests/EGLTest/Android.bp b/opengl/tests/EGLTest/Android.bp
index 51c9376..d96a895 100644
--- a/opengl/tests/EGLTest/Android.bp
+++ b/opengl/tests/EGLTest/Android.bp
@@ -1,4 +1,3 @@
-
package {
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
@@ -11,6 +10,7 @@
cc_test {
name: "EGL_test",
+ test_suites: ["general-tests"],
srcs: [
"egl_cache_test.cpp",
diff --git a/opengl/tests/EGLTest/EGL_test.cpp b/opengl/tests/EGLTest/EGL_test.cpp
index bbd786d..cbe4ef9 100644
--- a/opengl/tests/EGLTest/EGL_test.cpp
+++ b/opengl/tests/EGLTest/EGL_test.cpp
@@ -343,6 +343,11 @@
}
TEST_F(EGLTest, EGLDisplayP31010102) {
+ // This test has been failing since:
+ // libEGL: When driver doesn't understand P3, map sRGB-encoded P3 to sRGB
+ // https://android-review.git.corp.google.com/c/platform/frameworks/native/+/793504
+ GTEST_SKIP() << "Skipping broken test. See b/120714942 and b/117104367";
+
EGLint numConfigs;
EGLConfig config;
EGLBoolean success;
@@ -866,6 +871,12 @@
EGLConfig config;
EGLBoolean success;
+ if (!hasWideColorDisplay) {
+ // skip this test if device does not have wide-color display
+ RecordProperty("hasWideColorDisplay", false);
+ return;
+ }
+
const EGLint attrs[] = {
// clang-format off
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
@@ -951,6 +962,12 @@
TEST_F(EGLTest, EGLCreateWindowTwoColorspaces) {
EGLConfig config;
+ if (!hasWideColorDisplay) {
+ // skip this test if device does not have wide-color display
+ RecordProperty("hasWideColorDisplay", false);
+ return;
+ }
+
ASSERT_NO_FATAL_FAILURE(get8BitConfig(config));
struct MockConsumer : public BnConsumerListener {
diff --git a/opengl/tests/EGLTest/egl_cache_test.cpp b/opengl/tests/EGLTest/egl_cache_test.cpp
index c974f63..265bec4 100644
--- a/opengl/tests/EGLTest/egl_cache_test.cpp
+++ b/opengl/tests/EGLTest/egl_cache_test.cpp
@@ -24,24 +24,33 @@
#include <android-base/test_utils.h>
#include "egl_cache.h"
+#include "egl_cache_multifile.h"
#include "egl_display.h"
#include <memory>
+using namespace std::literals;
+
namespace android {
class EGLCacheTest : public ::testing::Test {
protected:
virtual void SetUp() {
mCache = egl_cache_t::get();
+ mTempFile.reset(new TemporaryFile());
+ mCache->setCacheFilename(&mTempFile->path[0]);
}
virtual void TearDown() {
- mCache->setCacheFilename("");
mCache->terminate();
+ mCache->setCacheFilename("");
+ mTempFile.reset(nullptr);
}
+ std::string getCachefileName();
+
egl_cache_t* mCache;
+ std::unique_ptr<TemporaryFile> mTempFile;
};
TEST_F(EGLCacheTest, UninitializedCacheAlwaysMisses) {
@@ -77,26 +86,8 @@
ASSERT_EQ(0xee, buf[3]);
}
-class EGLCacheSerializationTest : public EGLCacheTest {
-
-protected:
-
- virtual void SetUp() {
- EGLCacheTest::SetUp();
- mTempFile.reset(new TemporaryFile());
- }
-
- virtual void TearDown() {
- mTempFile.reset(nullptr);
- EGLCacheTest::TearDown();
- }
-
- std::unique_ptr<TemporaryFile> mTempFile;
-};
-
-TEST_F(EGLCacheSerializationTest, ReinitializedCacheContainsValues) {
+TEST_F(EGLCacheTest, ReinitializedCacheContainsValues) {
uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee };
- mCache->setCacheFilename(&mTempFile->path[0]);
mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
mCache->setBlob("abcd", 4, "efgh", 4);
mCache->terminate();
@@ -108,4 +99,109 @@
ASSERT_EQ('h', buf[3]);
}
+std::string EGLCacheTest::getCachefileName() {
+ // Return the monolithic filename unless we find the multifile dir
+ std::string cachefileName = &mTempFile->path[0];
+ std::string multifileDirName = cachefileName + ".multifile";
+
+ struct stat info;
+ if (stat(multifileDirName.c_str(), &info) == 0) {
+
+ // Ensure we only have one file to manage
+ int realFileCount = 0;
+
+ // We have a multifile dir. Return the only real file in it.
+ DIR* dir;
+ struct dirent* entry;
+ if ((dir = opendir(multifileDirName.c_str())) != nullptr) {
+ while ((entry = readdir(dir)) != nullptr) {
+ if (entry->d_name == "."s || entry->d_name == ".."s) {
+ continue;
+ }
+ cachefileName = multifileDirName + "/" + entry->d_name;
+ realFileCount++;
+ }
+ }
+
+ if (realFileCount != 1) {
+ // If there was more than one real file in the directory, this
+ // violates test assumptions
+ cachefileName = "";
+ }
+ }
+
+ return cachefileName;
+}
+
+TEST_F(EGLCacheTest, ModifiedCacheMisses) {
+ // Turn this back on if multifile becomes the default
+ GTEST_SKIP() << "Skipping test designed for multifile, see b/263574392 and b/246966894";
+
+ uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee };
+ mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
+
+ mCache->setBlob("abcd", 4, "efgh", 4);
+ ASSERT_EQ(4, mCache->getBlob("abcd", 4, buf, 4));
+ ASSERT_EQ('e', buf[0]);
+ ASSERT_EQ('f', buf[1]);
+ ASSERT_EQ('g', buf[2]);
+ ASSERT_EQ('h', buf[3]);
+
+ // Depending on the cache mode, the file will be in different locations
+ std::string cachefileName = getCachefileName();
+ ASSERT_TRUE(cachefileName.length() > 0);
+
+ // Ensure the cache file is written to disk
+ mCache->terminate();
+
+ // Stomp on the beginning of the cache file, breaking the key match
+ const long stomp = 0xbadf00d;
+ FILE *file = fopen(cachefileName.c_str(), "w");
+ fprintf(file, "%ld", stomp);
+ fflush(file);
+ fclose(file);
+
+ // Ensure no cache hit
+ mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
+ uint8_t buf2[4] = { 0xee, 0xee, 0xee, 0xee };
+ ASSERT_EQ(0, mCache->getBlob("abcd", 4, buf2, 4));
+ ASSERT_EQ(0xee, buf2[0]);
+ ASSERT_EQ(0xee, buf2[1]);
+ ASSERT_EQ(0xee, buf2[2]);
+ ASSERT_EQ(0xee, buf2[3]);
+}
+
+TEST_F(EGLCacheTest, TerminatedCacheBelowCacheLimit) {
+ uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee };
+ mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
+
+ mCache->setBlob("abcd", 4, "efgh", 4);
+ ASSERT_EQ(4, mCache->getBlob("abcd", 4, buf, 4));
+ ASSERT_EQ('e', buf[0]);
+ ASSERT_EQ('f', buf[1]);
+ ASSERT_EQ('g', buf[2]);
+ ASSERT_EQ('h', buf[3]);
+
+ mCache->setBlob("ijkl", 4, "mnop", 4);
+ ASSERT_EQ(4, mCache->getBlob("ijkl", 4, buf, 4));
+ ASSERT_EQ('m', buf[0]);
+ ASSERT_EQ('n', buf[1]);
+ ASSERT_EQ('o', buf[2]);
+ ASSERT_EQ('p', buf[3]);
+
+ mCache->setBlob("qrst", 4, "uvwx", 4);
+ ASSERT_EQ(4, mCache->getBlob("qrst", 4, buf, 4));
+ ASSERT_EQ('u', buf[0]);
+ ASSERT_EQ('v', buf[1]);
+ ASSERT_EQ('w', buf[2]);
+ ASSERT_EQ('x', buf[3]);
+
+ // Cache should contain both the key and the value
+ // So 8 bytes per entry, at least 24 bytes
+ ASSERT_GE(mCache->getCacheSize(), 24);
+ mCache->setCacheLimit(4);
+ mCache->terminate();
+ ASSERT_LE(mCache->getCacheSize(), 4);
+}
+
}
diff --git a/services/inputflinger/InputCommonConverter.cpp b/services/inputflinger/InputCommonConverter.cpp
index 628ce6f..0c93f5c 100644
--- a/services/inputflinger/InputCommonConverter.cpp
+++ b/services/inputflinger/InputCommonConverter.cpp
@@ -263,11 +263,12 @@
static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GENERIC_14) == common::Axis::GENERIC_14);
static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GENERIC_15) == common::Axis::GENERIC_15);
static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GENERIC_16) == common::Axis::GENERIC_16);
-// TODO(hcutts): add GESTURE_X_OFFSET and GESTURE_Y_OFFSET.
+// TODO(b/251196347): add GESTURE_{X,Y}_OFFSET, GESTURE_SCROLL_{X,Y}_DISTANCE, and
+// GESTURE_PINCH_SCALE_FACTOR.
// If you added a new axis, consider whether this should also be exposed as a HAL axis. Update the
// static_assert below and add the new axis here, or leave a comment summarizing your decision.
static_assert(static_cast<common::Axis>(AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE) ==
- static_cast<common::Axis>(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET));
+ static_cast<common::Axis>(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR));
static common::VideoFrame getHalVideoFrame(const TouchVideoFrame& frame) {
common::VideoFrame out;
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 4aac377..906bb1b 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -2060,6 +2060,12 @@
if (touchedWindow.windowHandle->getInfo()->supportsSplitTouch()) {
continue;
}
+ if (touchedWindow.windowHandle->getInfo()->inputConfig.test(
+ gui::WindowInfo::InputConfig::IS_WALLPAPER)) {
+ // Wallpaper window should not affect whether or not touch is split
+ continue;
+ }
+
// Eventually, touchedWindow will contain the deviceId of each pointer that's currently
// being sent there. For now, use deviceId from touch state.
if (entry.deviceId == touchState.deviceId && !touchedWindow.pointerIds.isEmpty()) {
@@ -2223,6 +2229,32 @@
tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, pointerIds,
entry.eventTime);
+
+ // If this is the pointer going down and the touched window has a wallpaper
+ // then also add the touched wallpaper windows so they are locked in for the duration
+ // of the touch gesture.
+ // We do not collect wallpapers during HOVER_MOVE or SCROLL because the wallpaper
+ // engine only supports touch events. We would need to add a mechanism similar
+ // to View.onGenericMotionEvent to enable wallpapers to handle these events.
+ if (maskedAction == AMOTION_EVENT_ACTION_DOWN ||
+ maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN) {
+ if (targetFlags.test(InputTarget::Flags::FOREGROUND) &&
+ windowHandle->getInfo()->inputConfig.test(
+ gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
+ sp<WindowInfoHandle> wallpaper = findWallpaperWindowBelow(windowHandle);
+ if (wallpaper != nullptr) {
+ ftl::Flags<InputTarget::Flags> wallpaperFlags =
+ InputTarget::Flags::WINDOW_IS_OBSCURED |
+ InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED |
+ InputTarget::Flags::DISPATCH_AS_IS;
+ if (isSplit) {
+ wallpaperFlags |= InputTarget::Flags::SPLIT;
+ }
+ tempTouchState.addOrUpdateWindow(wallpaper, wallpaperFlags, pointerIds,
+ entry.eventTime);
+ }
+ }
+ }
}
// If any existing window is pilfering pointers from newly added window, remove it
@@ -2307,6 +2339,10 @@
pointerIds.markBit(entry.pointerProperties[0].id);
tempTouchState.addOrUpdateWindow(newTouchedWindowHandle, targetFlags, pointerIds,
entry.eventTime);
+
+ // Check if the wallpaper window should deliver the corresponding event.
+ slipWallpaperTouch(targetFlags, oldTouchedWindowHandle, newTouchedWindowHandle,
+ tempTouchState, pointerIds);
}
}
@@ -2413,38 +2449,6 @@
}
}
- // If this is the first pointer going down and the touched window has a wallpaper
- // then also add the touched wallpaper windows so they are locked in for the duration
- // of the touch gesture.
- // We do not collect wallpapers during HOVER_MOVE or SCROLL because the wallpaper
- // engine only supports touch events. We would need to add a mechanism similar
- // to View.onGenericMotionEvent to enable wallpapers to handle these events.
- if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
- sp<WindowInfoHandle> foregroundWindowHandle =
- tempTouchState.getFirstForegroundWindowHandle();
- if (foregroundWindowHandle &&
- foregroundWindowHandle->getInfo()->inputConfig.test(
- WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
- const std::vector<sp<WindowInfoHandle>>& windowHandles =
- getWindowHandlesLocked(displayId);
- for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
- const WindowInfo* info = windowHandle->getInfo();
- if (info->displayId == displayId &&
- windowHandle->getInfo()->inputConfig.test(
- WindowInfo::InputConfig::IS_WALLPAPER)) {
- BitSet32 pointerIds;
- pointerIds.markBit(entry.pointerProperties[0].id);
- tempTouchState.addOrUpdateWindow(windowHandle,
- InputTarget::Flags::WINDOW_IS_OBSCURED |
- InputTarget::Flags::
- WINDOW_IS_PARTIALLY_OBSCURED |
- InputTarget::Flags::DISPATCH_AS_IS,
- pointerIds, entry.eventTime);
- }
- }
- }
- }
-
// Success! Output targets.
touchedWindows = tempTouchState.windows;
outInjectionResult = InputEventInjectionResult::SUCCEEDED;
@@ -3726,7 +3730,8 @@
}
void InputDispatcher::synthesizePointerDownEventsForConnectionLocked(
- const nsecs_t downTime, const sp<Connection>& connection) {
+ const nsecs_t downTime, const sp<Connection>& connection,
+ ftl::Flags<InputTarget::Flags> targetFlags) {
if (connection->status == Connection::Status::BROKEN) {
return;
}
@@ -3752,7 +3757,7 @@
target.globalScaleFactor = windowInfo->globalScaleFactor;
}
target.inputChannel = connection->inputChannel;
- target.flags = InputTarget::Flags::DISPATCH_AS_IS;
+ target.flags = targetFlags;
const bool wasEmpty = connection->outboundQueue.empty();
for (std::unique_ptr<EventEntry>& downEventEntry : downEvents) {
@@ -3787,6 +3792,16 @@
}
}
+void InputDispatcher::synthesizeCancelationEventsForWindowLocked(
+ const sp<WindowInfoHandle>& windowHandle, const CancelationOptions& options) {
+ if (windowHandle != nullptr) {
+ sp<Connection> wallpaperConnection = getConnectionLocked(windowHandle->getToken());
+ if (wallpaperConnection != nullptr) {
+ synthesizeCancelationEventsForConnectionLocked(wallpaperConnection, options);
+ }
+ }
+}
+
std::unique_ptr<MotionEntry> InputDispatcher::splitMotionEvent(
const MotionEntry& originalMotionEntry, BitSet32 pointerIds, nsecs_t splitDownTime) {
ALOG_ASSERT(pointerIds.value != 0);
@@ -4847,14 +4862,7 @@
touchedWindow.windowHandle->getInfo()->inputConfig.test(
gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
sp<WindowInfoHandle> wallpaper = state.getWallpaperWindow();
- if (wallpaper != nullptr) {
- sp<Connection> wallpaperConnection =
- getConnectionLocked(wallpaper->getToken());
- if (wallpaperConnection != nullptr) {
- synthesizeCancelationEventsForConnectionLocked(wallpaperConnection,
- options);
- }
- }
+ synthesizeCancelationEventsForWindowLocked(wallpaper, options);
}
}
state.windows.erase(state.windows.begin() + i);
@@ -5155,6 +5163,7 @@
// Erase old window.
ftl::Flags<InputTarget::Flags> oldTargetFlags = touchedWindow->targetFlags;
BitSet32 pointerIds = touchedWindow->pointerIds;
+ sp<WindowInfoHandle> fromWindowHandle = touchedWindow->windowHandle;
state->removeWindowByToken(fromToken);
// Add new window.
@@ -5187,7 +5196,12 @@
options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
"transferring touch focus from this window to another window");
synthesizeCancelationEventsForConnectionLocked(fromConnection, options);
- synthesizePointerDownEventsForConnectionLocked(downTimeInTarget, toConnection);
+ synthesizePointerDownEventsForConnectionLocked(downTimeInTarget, toConnection,
+ newTargetFlags);
+
+ // Check if the wallpaper window should deliver the corresponding event.
+ transferWallpaperTouch(oldTargetFlags, newTargetFlags, fromWindowHandle, toWindowHandle,
+ *state, pointerIds);
}
} // release lock
@@ -6465,4 +6479,100 @@
mMonitorDispatchingTimeout = timeout;
}
+void InputDispatcher::slipWallpaperTouch(ftl::Flags<InputTarget::Flags> targetFlags,
+ const sp<WindowInfoHandle>& oldWindowHandle,
+ const sp<WindowInfoHandle>& newWindowHandle,
+ TouchState& state, const BitSet32& pointerIds) {
+ const bool oldHasWallpaper = oldWindowHandle->getInfo()->inputConfig.test(
+ gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
+ const bool newHasWallpaper = targetFlags.test(InputTarget::Flags::FOREGROUND) &&
+ newWindowHandle->getInfo()->inputConfig.test(
+ gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
+ const sp<WindowInfoHandle> oldWallpaper =
+ oldHasWallpaper ? state.getWallpaperWindow() : nullptr;
+ const sp<WindowInfoHandle> newWallpaper =
+ newHasWallpaper ? findWallpaperWindowBelow(newWindowHandle) : nullptr;
+ if (oldWallpaper == newWallpaper) {
+ return;
+ }
+
+ if (oldWallpaper != nullptr) {
+ state.addOrUpdateWindow(oldWallpaper, InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT,
+ BitSet32(0));
+ }
+
+ if (newWallpaper != nullptr) {
+ state.addOrUpdateWindow(newWallpaper,
+ InputTarget::Flags::DISPATCH_AS_SLIPPERY_ENTER |
+ InputTarget::Flags::WINDOW_IS_OBSCURED |
+ InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED,
+ pointerIds);
+ }
+}
+
+void InputDispatcher::transferWallpaperTouch(ftl::Flags<InputTarget::Flags> oldTargetFlags,
+ ftl::Flags<InputTarget::Flags> newTargetFlags,
+ const sp<WindowInfoHandle> fromWindowHandle,
+ const sp<WindowInfoHandle> toWindowHandle,
+ TouchState& state, const BitSet32& pointerIds) {
+ const bool oldHasWallpaper = oldTargetFlags.test(InputTarget::Flags::FOREGROUND) &&
+ fromWindowHandle->getInfo()->inputConfig.test(
+ gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
+ const bool newHasWallpaper = newTargetFlags.test(InputTarget::Flags::FOREGROUND) &&
+ toWindowHandle->getInfo()->inputConfig.test(
+ gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
+
+ const sp<WindowInfoHandle> oldWallpaper =
+ oldHasWallpaper ? state.getWallpaperWindow() : nullptr;
+ const sp<WindowInfoHandle> newWallpaper =
+ newHasWallpaper ? findWallpaperWindowBelow(toWindowHandle) : nullptr;
+ if (oldWallpaper == newWallpaper) {
+ return;
+ }
+
+ if (oldWallpaper != nullptr) {
+ CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
+ "transferring touch focus to another window");
+ state.removeWindowByToken(oldWallpaper->getToken());
+ synthesizeCancelationEventsForWindowLocked(oldWallpaper, options);
+ }
+
+ if (newWallpaper != nullptr) {
+ nsecs_t downTimeInTarget = now();
+ ftl::Flags<InputTarget::Flags> wallpaperFlags =
+ oldTargetFlags & (InputTarget::Flags::SPLIT | InputTarget::Flags::DISPATCH_AS_IS);
+ wallpaperFlags |= InputTarget::Flags::WINDOW_IS_OBSCURED |
+ InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED;
+ state.addOrUpdateWindow(newWallpaper, wallpaperFlags, pointerIds, downTimeInTarget);
+ sp<Connection> wallpaperConnection = getConnectionLocked(newWallpaper->getToken());
+ if (wallpaperConnection != nullptr) {
+ sp<Connection> toConnection = getConnectionLocked(toWindowHandle->getToken());
+ toConnection->inputState.mergePointerStateTo(wallpaperConnection->inputState);
+ synthesizePointerDownEventsForConnectionLocked(downTimeInTarget, wallpaperConnection,
+ wallpaperFlags);
+ }
+ }
+}
+
+sp<WindowInfoHandle> InputDispatcher::findWallpaperWindowBelow(
+ const sp<WindowInfoHandle>& windowHandle) const {
+ const std::vector<sp<WindowInfoHandle>>& windowHandles =
+ getWindowHandlesLocked(windowHandle->getInfo()->displayId);
+ bool foundWindow = false;
+ for (const sp<WindowInfoHandle>& otherHandle : windowHandles) {
+ if (!foundWindow && otherHandle != windowHandle) {
+ continue;
+ }
+ if (windowHandle == otherHandle) {
+ foundWindow = true;
+ continue;
+ }
+
+ if (otherHandle->getInfo()->inputConfig.test(WindowInfo::InputConfig::IS_WALLPAPER)) {
+ return otherHandle;
+ }
+ }
+ return nullptr;
+}
+
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 5efb39e..a32ebd3 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -628,9 +628,14 @@
REQUIRES(mLock);
void synthesizePointerDownEventsForConnectionLocked(const nsecs_t downTime,
- const sp<Connection>& connection)
+ const sp<Connection>& connection,
+ ftl::Flags<InputTarget::Flags> targetFlags)
REQUIRES(mLock);
+ void synthesizeCancelationEventsForWindowLocked(
+ const sp<android::gui::WindowInfoHandle>& windowHandle,
+ const CancelationOptions& options) REQUIRES(mLock);
+
// Splitting motion events across windows. When splitting motion event for a target,
// splitDownTime refers to the time of first 'down' event on that particular target
std::unique_ptr<MotionEntry> splitMotionEvent(const MotionEntry& originalMotionEntry,
@@ -691,6 +696,19 @@
bool recentWindowsAreOwnedByLocked(int32_t pid, int32_t uid) REQUIRES(mLock);
sp<InputReporterInterface> mReporter;
+
+ void slipWallpaperTouch(ftl::Flags<InputTarget::Flags> targetFlags,
+ const sp<android::gui::WindowInfoHandle>& oldWindowHandle,
+ const sp<android::gui::WindowInfoHandle>& newWindowHandle,
+ TouchState& state, const BitSet32& pointerIds) REQUIRES(mLock);
+ void transferWallpaperTouch(ftl::Flags<InputTarget::Flags> oldTargetFlags,
+ ftl::Flags<InputTarget::Flags> newTargetFlags,
+ const sp<android::gui::WindowInfoHandle> fromWindowHandle,
+ const sp<android::gui::WindowInfoHandle> toWindowHandle,
+ TouchState& state, const BitSet32& pointerIds) REQUIRES(mLock);
+
+ sp<android::gui::WindowInfoHandle> findWallpaperWindowBelow(
+ const sp<android::gui::WindowInfoHandle>& windowHandle) const REQUIRES(mLock);
};
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index 3e4db43..d55ab28 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -194,6 +194,12 @@
// The device type has been updated.
CHANGE_DEVICE_TYPE = 1 << 10,
+ // The keyboard layout association has changed.
+ CHANGE_KEYBOARD_LAYOUT_ASSOCIATION = 1 << 11,
+
+ // The stylus button reporting configurations has changed.
+ CHANGE_STYLUS_BUTTON_REPORTING = 1 << 12,
+
// All devices must be reopened.
CHANGE_MUST_REOPEN = 1 << 31,
};
@@ -211,7 +217,7 @@
// Used to determine which DisplayViewport should be tied to which InputDevice.
std::unordered_map<std::string, uint8_t> portAssociations;
- // The associations between input device names and display unique ids.
+ // The associations between input device physical port locations and display unique ids.
// Used to determine which DisplayViewport should be tied to which InputDevice.
std::unordered_map<std::string, std::string> uniqueIdAssociations;
@@ -219,6 +225,10 @@
// This is used to determine which device type and source should be tied to which InputDevice.
std::unordered_map<std::string, std::string> deviceTypeAssociations;
+ // The map from the input device physical port location to the input device layout info.
+ // Can be used to determine the layout of the keyboard device.
+ std::unordered_map<std::string, KeyboardLayoutInfo> keyboardLayoutAssociations;
+
// The suggested display ID to show the cursor.
int32_t defaultPointerDisplayId;
@@ -302,6 +312,10 @@
// The set of currently disabled input devices.
std::set<int32_t> disabledDevices;
+ // True if stylus button reporting through motion events should be enabled, in which case
+ // stylus button state changes are reported through motion events.
+ bool stylusButtonMotionEventsEnabled;
+
InputReaderConfiguration()
: virtualKeyQuietTime(0),
pointerVelocityControlParameters(1.0f, 500.0f, 3000.0f,
@@ -322,7 +336,8 @@
pointerGestureMovementSpeedRatio(0.8f),
pointerGestureZoomSpeedRatio(0.3f),
showTouches(false),
- pointerCaptureRequest() {}
+ pointerCaptureRequest(),
+ stylusButtonMotionEventsEnabled(true) {}
static std::string changesToString(uint32_t changes);
diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp
index f37f0fa..f3b680b 100644
--- a/services/inputflinger/reader/Android.bp
+++ b/services/inputflinger/reader/Android.bp
@@ -29,6 +29,7 @@
"include",
"mapper",
"mapper/accumulator",
+ "mapper/gestures",
],
}
@@ -60,7 +61,9 @@
"mapper/accumulator/MultiTouchMotionAccumulator.cpp",
"mapper/accumulator/SingleTouchMotionAccumulator.cpp",
"mapper/accumulator/TouchButtonAccumulator.cpp",
+ "mapper/gestures/GestureConverter.cpp",
"mapper/gestures/GesturesLogging.cpp",
+ "mapper/gestures/HardwareStateConverter.cpp",
],
}
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index e26bc8c..43b67ca 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -62,7 +62,6 @@
#define INDENT3 " "
using android::base::StringPrintf;
-using android::hardware::input::InputDeviceCountryCode;
namespace android {
@@ -135,6 +134,46 @@
{"green", LightColor::GREEN},
{"blue", LightColor::BLUE}};
+// Mapping for country code to Layout info.
+// See bCountryCode in 6.2.1 of https://usb.org/sites/default/files/hid1_11.pdf.
+const std::unordered_map<std::int32_t, RawLayoutInfo> LAYOUT_INFOS =
+ {{0, RawLayoutInfo{.languageTag = "", .layoutType = ""}}, // NOT_SUPPORTED
+ {1, RawLayoutInfo{.languageTag = "ar-Arab", .layoutType = ""}}, // ARABIC
+ {2, RawLayoutInfo{.languageTag = "fr-BE", .layoutType = ""}}, // BELGIAN
+ {3, RawLayoutInfo{.languageTag = "fr-CA", .layoutType = ""}}, // CANADIAN_BILINGUAL
+ {4, RawLayoutInfo{.languageTag = "fr-CA", .layoutType = ""}}, // CANADIAN_FRENCH
+ {5, RawLayoutInfo{.languageTag = "cs", .layoutType = ""}}, // CZECH_REPUBLIC
+ {6, RawLayoutInfo{.languageTag = "da", .layoutType = ""}}, // DANISH
+ {7, RawLayoutInfo{.languageTag = "fi", .layoutType = ""}}, // FINNISH
+ {8, RawLayoutInfo{.languageTag = "fr-FR", .layoutType = ""}}, // FRENCH
+ {9, RawLayoutInfo{.languageTag = "de", .layoutType = ""}}, // GERMAN
+ {10, RawLayoutInfo{.languageTag = "el", .layoutType = ""}}, // GREEK
+ {11, RawLayoutInfo{.languageTag = "iw", .layoutType = ""}}, // HEBREW
+ {12, RawLayoutInfo{.languageTag = "hu", .layoutType = ""}}, // HUNGARY
+ {13, RawLayoutInfo{.languageTag = "en", .layoutType = "extended"}}, // INTERNATIONAL (ISO)
+ {14, RawLayoutInfo{.languageTag = "it", .layoutType = ""}}, // ITALIAN
+ {15, RawLayoutInfo{.languageTag = "ja", .layoutType = ""}}, // JAPAN
+ {16, RawLayoutInfo{.languageTag = "ko", .layoutType = ""}}, // KOREAN
+ {17, RawLayoutInfo{.languageTag = "es-419", .layoutType = ""}}, // LATIN_AMERICA
+ {18, RawLayoutInfo{.languageTag = "nl", .layoutType = ""}}, // DUTCH
+ {19, RawLayoutInfo{.languageTag = "nb", .layoutType = ""}}, // NORWEGIAN
+ {20, RawLayoutInfo{.languageTag = "fa", .layoutType = ""}}, // PERSIAN
+ {21, RawLayoutInfo{.languageTag = "pl", .layoutType = ""}}, // POLAND
+ {22, RawLayoutInfo{.languageTag = "pt", .layoutType = ""}}, // PORTUGUESE
+ {23, RawLayoutInfo{.languageTag = "ru", .layoutType = ""}}, // RUSSIA
+ {24, RawLayoutInfo{.languageTag = "sk", .layoutType = ""}}, // SLOVAKIA
+ {25, RawLayoutInfo{.languageTag = "es-ES", .layoutType = ""}}, // SPANISH
+ {26, RawLayoutInfo{.languageTag = "sv", .layoutType = ""}}, // SWEDISH
+ {27, RawLayoutInfo{.languageTag = "fr-CH", .layoutType = ""}}, // SWISS_FRENCH
+ {28, RawLayoutInfo{.languageTag = "de-CH", .layoutType = ""}}, // SWISS_GERMAN
+ {29, RawLayoutInfo{.languageTag = "de-CH", .layoutType = ""}}, // SWITZERLAND
+ {30, RawLayoutInfo{.languageTag = "zh-TW", .layoutType = ""}}, // TAIWAN
+ {31, RawLayoutInfo{.languageTag = "tr", .layoutType = "turkish_q"}}, // TURKISH_Q
+ {32, RawLayoutInfo{.languageTag = "en-GB", .layoutType = ""}}, // UK
+ {33, RawLayoutInfo{.languageTag = "en-US", .layoutType = ""}}, // US
+ {34, RawLayoutInfo{.languageTag = "", .layoutType = ""}}, // YUGOSLAVIA
+ {35, RawLayoutInfo{.languageTag = "tr", .layoutType = "turkish_f"}}}; // TURKISH_F
+
static std::string sha1(const std::string& in) {
SHA_CTX ctx;
SHA1_Init(&ctx);
@@ -311,21 +350,27 @@
}
/**
- * Read country code information exposed through the sysfs path.
+ * Read country code information exposed through the sysfs path and convert it to Layout info.
*/
-static InputDeviceCountryCode readCountryCodeLocked(const std::filesystem::path& sysfsRootPath) {
+static std::optional<RawLayoutInfo> readLayoutConfiguration(
+ const std::filesystem::path& sysfsRootPath) {
// Check the sysfs root path
- int hidCountryCode = static_cast<int>(InputDeviceCountryCode::INVALID);
+ int32_t hidCountryCode = -1;
std::string str;
if (base::ReadFileToString(sysfsRootPath / "country", &str)) {
hidCountryCode = std::stoi(str, nullptr, 16);
- LOG_ALWAYS_FATAL_IF(hidCountryCode > 35 || hidCountryCode < 0,
- "HID country code should be in range [0, 35]. Found country code "
- "to be %d",
- hidCountryCode);
+ // Update this condition if new supported country codes are added to HID spec.
+ if (hidCountryCode > 35 || hidCountryCode < 0) {
+ ALOGE("HID country code should be in range [0, 35], but for sysfs path %s it was %d",
+ sysfsRootPath.c_str(), hidCountryCode);
+ }
+ }
+ const auto it = LAYOUT_INFOS.find(hidCountryCode);
+ if (it != LAYOUT_INFOS.end()) {
+ return it->second;
}
- return static_cast<InputDeviceCountryCode>(hidCountryCode);
+ return std::nullopt;
}
/**
@@ -1298,13 +1343,13 @@
}
}
-InputDeviceCountryCode EventHub::getCountryCode(int32_t deviceId) const {
+std::optional<RawLayoutInfo> EventHub::getRawLayoutInfo(int32_t deviceId) const {
std::scoped_lock _l(mLock);
Device* device = getDeviceLocked(deviceId);
if (device == nullptr || !device->associatedDevice) {
- return InputDeviceCountryCode::INVALID;
+ return std::nullopt;
}
- return device->associatedDevice->countryCode;
+ return device->associatedDevice->layoutInfo;
}
void EventHub::setExcludedDevices(const std::vector<std::string>& devices) {
@@ -1371,12 +1416,17 @@
return nullptr;
}
+// If provided map is null, it will reset key character map to default KCM.
bool EventHub::setKeyboardLayoutOverlay(int32_t deviceId, std::shared_ptr<KeyCharacterMap> map) {
std::scoped_lock _l(mLock);
Device* device = getDeviceLocked(deviceId);
- if (device == nullptr || map == nullptr || device->keyMap.keyCharacterMap == nullptr) {
+ if (device == nullptr || device->keyMap.keyCharacterMap == nullptr) {
return false;
}
+ if (map == nullptr) {
+ device->keyMap.keyCharacterMap->clearLayoutOverlay();
+ return true;
+ }
device->keyMap.keyCharacterMap->combine(*map);
return true;
}
@@ -1448,9 +1498,9 @@
std::shared_ptr<const AssociatedDevice> associatedDevice = std::make_shared<AssociatedDevice>(
AssociatedDevice{.sysfsRootPath = path,
- .countryCode = readCountryCodeLocked(path),
.batteryInfos = readBatteryConfiguration(path),
- .lightInfos = readLightsConfiguration(path)});
+ .lightInfos = readLightsConfiguration(path),
+ .layoutInfo = readLayoutConfiguration(path)});
bool associatedDeviceChanged = false;
for (const auto& [id, dev] : mDevices) {
@@ -2685,9 +2735,12 @@
device->keyMap.keyLayoutFile.c_str());
dump += StringPrintf(INDENT3 "KeyCharacterMapFile: %s\n",
device->keyMap.keyCharacterMapFile.c_str());
- dump += StringPrintf(INDENT3 "CountryCode: %d\n",
- device->associatedDevice ? device->associatedDevice->countryCode
- : InputDeviceCountryCode::INVALID);
+ if (device->associatedDevice && device->associatedDevice->layoutInfo) {
+ dump += StringPrintf(INDENT3 "LanguageTag: %s\n",
+ device->associatedDevice->layoutInfo->languageTag.c_str());
+ dump += StringPrintf(INDENT3 "LayoutType: %s\n",
+ device->associatedDevice->layoutInfo->layoutType.c_str());
+ }
dump += StringPrintf(INDENT3 "ConfigurationFile: %s\n",
device->configurationFile.c_str());
dump += StringPrintf(INDENT3 "VideoDevice: %s\n",
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index 6dfe5f5..6e78e82 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -37,8 +37,6 @@
#include "TouchpadInputMapper.h"
#include "VibratorInputMapper.h"
-using android::hardware::input::InputDeviceCountryCode;
-
namespace android {
InputDevice::InputDevice(InputReaderContext* context, int32_t id, int32_t generation,
@@ -56,16 +54,6 @@
InputDevice::~InputDevice() {}
-template <typename K, typename V>
-std::optional<V> getValueByKey(const std::unordered_map<K, V>& map, K key) {
- auto it = map.find(key);
- std::optional<V> value = std::nullopt;
- if (it != map.end()) {
- value = it->second;
- }
- return value;
-}
-
bool InputDevice::isEnabled() {
if (!hasEventHubDevices()) {
return false;
@@ -266,7 +254,6 @@
mSources = 0;
mClasses = ftl::Flags<InputDeviceClass>(0);
mControllerNumber = 0;
- mCountryCode = InputDeviceCountryCode::INVALID;
for_each_subdevice([this](InputDeviceContext& context) {
mClasses |= context.getDeviceClasses();
@@ -278,16 +265,6 @@
}
mControllerNumber = controllerNumber;
}
-
- InputDeviceCountryCode countryCode = context.getCountryCode();
- if (countryCode != InputDeviceCountryCode::INVALID) {
- if (mCountryCode != InputDeviceCountryCode::INVALID && mCountryCode != countryCode) {
- ALOGW("InputDevice::configure(): %s device contains multiple unique country "
- "codes",
- getName().c_str());
- }
- mCountryCode = countryCode;
- }
});
mIsExternal = mClasses.test(InputDeviceClass::EXTERNAL);
@@ -475,7 +452,7 @@
InputDeviceInfo InputDevice::getDeviceInfo() {
InputDeviceInfo outDeviceInfo;
outDeviceInfo.initialize(mId, mGeneration, mControllerNumber, mIdentifier, mAlias, mIsExternal,
- mHasMic, mCountryCode);
+ mHasMic);
for_each_mapper(
[&outDeviceInfo](InputMapper& mapper) { mapper.populateDeviceInfo(&outDeviceInfo); });
diff --git a/services/inputflinger/reader/Macros.h b/services/inputflinger/reader/Macros.h
index e107d88..d2a7ced 100644
--- a/services/inputflinger/reader/Macros.h
+++ b/services/inputflinger/reader/Macros.h
@@ -22,6 +22,8 @@
#include <log/log.h>
#include <log/log_event_list.h>
+#include <unordered_map>
+
namespace android {
/**
* Log debug messages for each raw event received from the EventHub.
@@ -113,4 +115,14 @@
return (sources & sourceMask & ~AINPUT_SOURCE_CLASS_MASK) != 0;
}
+template <typename K, typename V>
+static inline std::optional<V> getValueByKey(const std::unordered_map<K, V>& map, K key) {
+ auto it = map.find(key);
+ std::optional<V> value = std::nullopt;
+ if (it != map.end()) {
+ value = it->second;
+ }
+ return value;
+}
+
} // namespace android
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index 8a844b2..a3ecf41 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -42,7 +42,6 @@
#include "TouchVideoDevice.h"
#include "VibrationElement.h"
-#include "android/hardware/input/InputDeviceCountryCode.h"
struct inotify_event;
@@ -207,6 +206,15 @@
bool operator!=(const RawBatteryInfo&) const = default;
};
+/* Layout information associated with the device */
+struct RawLayoutInfo {
+ std::string languageTag;
+ std::string layoutType;
+
+ bool operator==(const RawLayoutInfo&) const = default;
+ bool operator!=(const RawLayoutInfo&) const = default;
+};
+
/*
* Gets the class that owns an axis, in cases where multiple classes might claim
* the same axis for different purposes.
@@ -308,8 +316,8 @@
int32_t deviceId, int32_t lightId) const = 0;
virtual void setLightIntensities(int32_t deviceId, int32_t lightId,
std::unordered_map<LightColor, int32_t> intensities) = 0;
- /* Query Country code associated with the input device. */
- virtual hardware::input::InputDeviceCountryCode getCountryCode(int32_t deviceId) const = 0;
+ /* Query Layout info associated with the input device. */
+ virtual std::optional<RawLayoutInfo> getRawLayoutInfo(int32_t deviceId) const = 0;
/* Query current input state. */
virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const = 0;
virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const = 0;
@@ -493,7 +501,7 @@
void setLightIntensities(int32_t deviceId, int32_t lightId,
std::unordered_map<LightColor, int32_t> intensities) override final;
- hardware::input::InputDeviceCountryCode getCountryCode(int32_t deviceId) const override final;
+ std::optional<RawLayoutInfo> getRawLayoutInfo(int32_t deviceId) const override final;
void setExcludedDevices(const std::vector<std::string>& devices) override final;
@@ -556,9 +564,9 @@
struct AssociatedDevice {
// The sysfs root path of the misc device.
std::filesystem::path sysfsRootPath;
- hardware::input::InputDeviceCountryCode countryCode;
std::unordered_map<int32_t /*batteryId*/, RawBatteryInfo> batteryInfos;
std::unordered_map<int32_t /*lightId*/, RawLightInfo> lightInfos;
+ std::optional<RawLayoutInfo> layoutInfo;
bool operator==(const AssociatedDevice&) const = default;
bool operator!=(const AssociatedDevice&) const = default;
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index af59fe2..7867029 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -54,6 +54,7 @@
inline std::optional<std::string> getBluetoothAddress() const {
return mIdentifier.bluetoothAddress;
}
+ inline const std::string getLocation() const { return mIdentifier.location; }
inline ftl::Flags<InputDeviceClass> getClasses() const { return mClasses; }
inline uint32_t getSources() const { return mSources; }
inline bool hasEventHubDevices() const { return !mDevices.empty(); }
@@ -166,7 +167,6 @@
int32_t mId;
int32_t mGeneration;
int32_t mControllerNumber;
- hardware::input::InputDeviceCountryCode mCountryCode;
InputDeviceIdentifier mIdentifier;
std::string mAlias;
ftl::Flags<InputDeviceClass> mClasses;
@@ -325,9 +325,6 @@
}
inline std::vector<TouchVideoFrame> getVideoFrames() { return mEventHub->getVideoFrames(mId); }
- inline hardware::input::InputDeviceCountryCode getCountryCode() const {
- return mEventHub->getCountryCode(mId);
- }
inline int32_t getScanCodeState(int32_t scanCode) const {
return mEventHub->getScanCodeState(mId, scanCode);
}
@@ -360,6 +357,9 @@
inline bool setKeyboardLayoutOverlay(std::shared_ptr<KeyCharacterMap> map) {
return mEventHub->setKeyboardLayoutOverlay(mId, map);
}
+ inline const std::optional<RawLayoutInfo> getRawLayoutInfo() {
+ return mEventHub->getRawLayoutInfo(mId);
+ }
inline void vibrate(const VibrationElement& element) {
return mEventHub->vibrate(mId, element);
}
@@ -403,8 +403,9 @@
inline status_t enableDevice() { return mEventHub->enableDevice(mId); }
inline status_t disableDevice() { return mEventHub->disableDevice(mId); }
- inline const std::string getName() { return mDevice.getName(); }
+ inline const std::string getName() const { return mDevice.getName(); }
inline const std::string getDescriptor() { return mDevice.getDescriptor(); }
+ inline const std::string getLocation() { return mDevice.getLocation(); }
inline bool isExternal() { return mDevice.isExternal(); }
inline std::optional<uint8_t> getAssociatedDisplayPort() const {
return mDevice.getAssociatedDisplayPort();
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
index 44f0dfe..d147d60 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -120,6 +120,16 @@
info->setKeyboardType(mKeyboardType);
info->setKeyCharacterMap(getDeviceContext().getKeyCharacterMap());
+
+ if (mKeyboardLayoutInfo) {
+ info->setKeyboardLayoutInfo(*mKeyboardLayoutInfo);
+ } else {
+ std::optional<RawLayoutInfo> layoutInfo = getDeviceContext().getRawLayoutInfo();
+ if (layoutInfo) {
+ info->setKeyboardLayoutInfo(
+ KeyboardLayoutInfo(layoutInfo->languageTag, layoutInfo->layoutType));
+ }
+ }
}
void KeyboardInputMapper::dump(std::string& dump) {
@@ -129,6 +139,12 @@
dump += StringPrintf(INDENT3 "Orientation: %d\n", getOrientation());
dump += StringPrintf(INDENT3 "KeyDowns: %zu keys currently down\n", mKeyDowns.size());
dump += StringPrintf(INDENT3 "MetaState: 0x%0x\n", mMetaState);
+ dump += INDENT3 "KeyboardLayoutInfo: ";
+ if (mKeyboardLayoutInfo) {
+ dump += mKeyboardLayoutInfo->languageTag + ", " + mKeyboardLayoutInfo->layoutType + "\n";
+ } else {
+ dump += "<not set>\n";
+ }
}
std::optional<DisplayViewport> KeyboardInputMapper::findViewport(
@@ -158,6 +174,12 @@
if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
mViewport = findViewport(config);
}
+
+ if (!changes || (changes & InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUT_ASSOCIATION)) {
+ mKeyboardLayoutInfo =
+ getValueByKey(config->keyboardLayoutAssociations, getDeviceContext().getLocation());
+ }
+
return out;
}
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
index 0526fd8..da5b8ee 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
@@ -58,6 +58,7 @@
uint32_t mSource{};
int32_t mKeyboardType{};
+ std::optional<KeyboardLayoutInfo> mKeyboardLayoutInfo;
std::vector<KeyDown> mKeyDowns{}; // keys that are down
int32_t mMetaState{};
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 160f9eb..4d51aee 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -51,9 +51,8 @@
return base::StringPrintf("%dx%d", size.width, size.height);
}
-static bool isPointInRect(const Rect& rect, int32_t x, int32_t y) {
- // Consider all four sides as "inclusive".
- return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
+static bool isPointInRect(const Rect& rect, vec2 p) {
+ return p.x >= rect.left && p.x < rect.right && p.y >= rect.top && p.y < rect.bottom;
}
template <typename T>
@@ -81,29 +80,20 @@
return value >= 8 ? value - 16 : value;
}
-static std::tuple<ui::Size /*displayBounds*/, Rect /*physicalFrame*/> getNaturalDisplayInfo(
- const DisplayViewport& viewport, ui::Rotation naturalOrientation) {
+static ui::Size getNaturalDisplaySize(const DisplayViewport& viewport) {
ui::Size rotatedDisplaySize{viewport.deviceWidth, viewport.deviceHeight};
- if (naturalOrientation == ui::ROTATION_90 || naturalOrientation == ui::ROTATION_270) {
+ if (viewport.orientation == ui::ROTATION_90 || viewport.orientation == ui::ROTATION_270) {
std::swap(rotatedDisplaySize.width, rotatedDisplaySize.height);
}
+ return rotatedDisplaySize;
+}
- ui::Transform rotate(ui::Transform::toRotationFlags(naturalOrientation),
- rotatedDisplaySize.width, rotatedDisplaySize.height);
-
- Rect physicalFrame{viewport.physicalLeft, viewport.physicalTop, viewport.physicalRight,
- viewport.physicalBottom};
- physicalFrame = rotate.transform(physicalFrame);
-
- LOG_ALWAYS_FATAL_IF(!physicalFrame.isValid());
- if (physicalFrame.isEmpty()) {
- ALOGE("Viewport is not set properly: %s", viewport.toString().c_str());
- physicalFrame.right =
- physicalFrame.left + (physicalFrame.width() == 0 ? 1 : physicalFrame.width());
- physicalFrame.bottom =
- physicalFrame.top + (physicalFrame.height() == 0 ? 1 : physicalFrame.height());
+static int32_t filterButtonState(InputReaderConfiguration& config, int32_t buttonState) {
+ if (!config.stylusButtonMotionEventsEnabled) {
+ buttonState &=
+ ~(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY | AMOTION_EVENT_BUTTON_STYLUS_SECONDARY);
}
- return {rotatedDisplaySize, physicalFrame};
+ return buttonState;
}
// --- RawPointerData ---
@@ -197,18 +187,6 @@
if (mCursorScrollAccumulator.haveRelativeHWheel()) {
info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f);
}
- if (mCalibration.coverageCalibration == Calibration::CoverageCalibration::BOX) {
- const InputDeviceInfo::MotionRange& x = mOrientedRanges.x;
- const InputDeviceInfo::MotionRange& y = mOrientedRanges.y;
- info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_1, mSource, x.min, x.max, x.flat, x.fuzz,
- x.resolution);
- info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_2, mSource, y.min, y.max, y.flat, y.fuzz,
- y.resolution);
- info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_3, mSource, x.min, x.max, x.flat, x.fuzz,
- x.resolution);
- info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_4, mSource, y.min, y.max, y.flat, y.fuzz,
- y.resolution);
- }
info->setButtonUnderPad(mParameters.hasButtonUnderPad);
info->setSupportsUsi(mParameters.supportsUsi);
}
@@ -224,10 +202,10 @@
dumpDisplay(dump);
dump += StringPrintf(INDENT3 "Translation and Scaling Factors:\n");
- dump += StringPrintf(INDENT4 "XScale: %0.3f\n", mXScale);
- dump += StringPrintf(INDENT4 "YScale: %0.3f\n", mYScale);
- dump += StringPrintf(INDENT4 "XPrecision: %0.3f\n", mXPrecision);
- dump += StringPrintf(INDENT4 "YPrecision: %0.3f\n", mYPrecision);
+ mRawToDisplay.dump(dump, "RawToDisplay Transform:", INDENT4);
+ mRawRotation.dump(dump, "RawRotation Transform:", INDENT4);
+ dump += StringPrintf(INDENT4 "OrientedXPrecision: %0.3f\n", mOrientedXPrecision);
+ dump += StringPrintf(INDENT4 "OrientedYPrecision: %0.3f\n", mOrientedYPrecision);
dump += StringPrintf(INDENT4 "GeometricScale: %0.3f\n", mGeometricScale);
dump += StringPrintf(INDENT4 "PressureScale: %0.3f\n", mPressureScale);
dump += StringPrintf(INDENT4 "SizeScale: %0.3f\n", mSizeScale);
@@ -687,10 +665,10 @@
void TouchInputMapper::initializeOrientedRanges() {
// Configure X and Y factors.
- mXScale = float(mDisplayBounds.width) / mRawPointerAxes.getRawWidth();
- mYScale = float(mDisplayBounds.height) / mRawPointerAxes.getRawHeight();
- mXPrecision = 1.0f / mXScale;
- mYPrecision = 1.0f / mYScale;
+ const float orientedScaleX = mRawToDisplay.getScaleX();
+ const float orientedScaleY = mRawToDisplay.getScaleY();
+ mOrientedXPrecision = 1.0f / orientedScaleX;
+ mOrientedYPrecision = 1.0f / orientedScaleY;
mOrientedRanges.x.axis = AMOTION_EVENT_AXIS_X;
mOrientedRanges.x.source = mSource;
@@ -700,7 +678,7 @@
// Scale factor for terms that are not oriented in a particular axis.
// If the pixels are square then xScale == yScale otherwise we fake it
// by choosing an average.
- mGeometricScale = avg(mXScale, mYScale);
+ mGeometricScale = avg(orientedScaleX, orientedScaleY);
initializeSizeRanges();
@@ -817,44 +795,74 @@
// Compute oriented precision, scales and ranges.
// Note that the maximum value reported is an inclusive maximum value so it is one
// unit less than the total width or height of the display.
+ // TODO(b/20508709): Calculate the oriented ranges using the input device's raw frame.
switch (mInputDeviceOrientation) {
case ui::ROTATION_90:
case ui::ROTATION_270:
- mOrientedXPrecision = mYPrecision;
- mOrientedYPrecision = mXPrecision;
-
mOrientedRanges.x.min = 0;
mOrientedRanges.x.max = mDisplayBounds.height - 1;
mOrientedRanges.x.flat = 0;
mOrientedRanges.x.fuzz = 0;
- mOrientedRanges.x.resolution = mRawPointerAxes.y.resolution * mYScale;
+ mOrientedRanges.x.resolution = mRawPointerAxes.y.resolution * mRawToDisplay.getScaleY();
mOrientedRanges.y.min = 0;
mOrientedRanges.y.max = mDisplayBounds.width - 1;
mOrientedRanges.y.flat = 0;
mOrientedRanges.y.fuzz = 0;
- mOrientedRanges.y.resolution = mRawPointerAxes.x.resolution * mXScale;
+ mOrientedRanges.y.resolution = mRawPointerAxes.x.resolution * mRawToDisplay.getScaleX();
break;
default:
- mOrientedXPrecision = mXPrecision;
- mOrientedYPrecision = mYPrecision;
-
mOrientedRanges.x.min = 0;
mOrientedRanges.x.max = mDisplayBounds.width - 1;
mOrientedRanges.x.flat = 0;
mOrientedRanges.x.fuzz = 0;
- mOrientedRanges.x.resolution = mRawPointerAxes.x.resolution * mXScale;
+ mOrientedRanges.x.resolution = mRawPointerAxes.x.resolution * mRawToDisplay.getScaleX();
mOrientedRanges.y.min = 0;
mOrientedRanges.y.max = mDisplayBounds.height - 1;
mOrientedRanges.y.flat = 0;
mOrientedRanges.y.fuzz = 0;
- mOrientedRanges.y.resolution = mRawPointerAxes.y.resolution * mYScale;
+ mOrientedRanges.y.resolution = mRawPointerAxes.y.resolution * mRawToDisplay.getScaleY();
break;
}
}
+void TouchInputMapper::computeInputTransforms() {
+ const ui::Size rawSize{mRawPointerAxes.getRawWidth(), mRawPointerAxes.getRawHeight()};
+
+ ui::Size rotatedRawSize = rawSize;
+ if (mInputDeviceOrientation == ui::ROTATION_270 || mInputDeviceOrientation == ui::ROTATION_90) {
+ std::swap(rotatedRawSize.width, rotatedRawSize.height);
+ }
+ const auto rotationFlags = ui::Transform::toRotationFlags(-mInputDeviceOrientation);
+ mRawRotation = ui::Transform{rotationFlags};
+
+ // Step 1: Undo the raw offset so that the raw coordinate space now starts at (0, 0).
+ ui::Transform undoRawOffset;
+ undoRawOffset.set(-mRawPointerAxes.x.minValue, -mRawPointerAxes.y.minValue);
+
+ // Step 2: Rotate the raw coordinates to the expected orientation.
+ ui::Transform rotate;
+ // When rotating raw coordinates, the raw size will be used as an offset.
+ // Account for the extra unit added to the raw range when the raw size was calculated.
+ rotate.set(rotationFlags, rotatedRawSize.width - 1, rotatedRawSize.height - 1);
+
+ // Step 3: Scale the raw coordinates to the display space.
+ ui::Transform scaleToDisplay;
+ const float xScale = static_cast<float>(mDisplayBounds.width) / rotatedRawSize.width;
+ const float yScale = static_cast<float>(mDisplayBounds.height) / rotatedRawSize.height;
+ scaleToDisplay.set(xScale, 0, 0, yScale);
+
+ mRawToDisplay = (scaleToDisplay * (rotate * undoRawOffset));
+
+ // Calculate the transform that takes raw coordinates to the rotated display space.
+ ui::Transform displayToRotatedDisplay;
+ displayToRotatedDisplay.set(ui::Transform::toRotationFlags(-mViewport.orientation),
+ mViewport.deviceWidth, mViewport.deviceHeight);
+ mRawToRotatedDisplay = displayToRotatedDisplay * mRawToDisplay;
+}
+
void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) {
const DeviceMode oldDeviceMode = mDeviceMode;
@@ -926,14 +934,9 @@
if (mDeviceMode == DeviceMode::DIRECT || mDeviceMode == DeviceMode::POINTER) {
const auto oldDisplayBounds = mDisplayBounds;
- // Apply the inverse of the input device orientation so that the input device is
- // configured in the same orientation as the viewport. The input device orientation will
- // be re-applied by mInputDeviceOrientation.
- const ui::Rotation naturalDeviceOrientation =
- mViewport.orientation - mParameters.orientation;
-
- std::tie(mDisplayBounds, mPhysicalFrameInDisplay) =
- getNaturalDisplayInfo(mViewport, naturalDeviceOrientation);
+ mDisplayBounds = getNaturalDisplaySize(mViewport);
+ mPhysicalFrameInRotatedDisplay = {mViewport.physicalLeft, mViewport.physicalTop,
+ mViewport.physicalRight, mViewport.physicalBottom};
// InputReader works in the un-rotated display coordinate space, so we don't need to do
// anything if the device is already orientation-aware. If the device is not
@@ -950,10 +953,14 @@
// Apply the input device orientation for the device.
mInputDeviceOrientation = mInputDeviceOrientation + mParameters.orientation;
+ computeInputTransforms();
} else {
mDisplayBounds = rawSize;
- mPhysicalFrameInDisplay = Rect{mDisplayBounds};
+ mPhysicalFrameInRotatedDisplay = Rect{mDisplayBounds};
mInputDeviceOrientation = ui::ROTATION_0;
+ mRawToDisplay.reset();
+ mRawToDisplay.set(-mRawPointerAxes.x.minValue, -mRawPointerAxes.y.minValue);
+ mRawToRotatedDisplay = mRawToDisplay;
}
}
@@ -1036,7 +1043,8 @@
void TouchInputMapper::dumpDisplay(std::string& dump) {
dump += StringPrintf(INDENT3 "%s\n", mViewport.toString().c_str());
dump += StringPrintf(INDENT3 "DisplayBounds: %s\n", toString(mDisplayBounds).c_str());
- dump += StringPrintf(INDENT3 "PhysicalFrame: %s\n", toString(mPhysicalFrameInDisplay).c_str());
+ dump += StringPrintf(INDENT3 "PhysicalFrameInRotatedDisplay: %s\n",
+ toString(mPhysicalFrameInRotatedDisplay).c_str());
dump += StringPrintf(INDENT3 "InputDeviceOrientation: %d\n", mInputDeviceOrientation);
}
@@ -1197,19 +1205,6 @@
if (in.tryGetProperty("touch.distance.scale", distanceScale)) {
out.distanceScale = distanceScale;
}
-
- out.coverageCalibration = Calibration::CoverageCalibration::DEFAULT;
- std::string coverageCalibrationString;
- if (in.tryGetProperty("touch.coverage.calibration", coverageCalibrationString)) {
- if (coverageCalibrationString == "none") {
- out.coverageCalibration = Calibration::CoverageCalibration::NONE;
- } else if (coverageCalibrationString == "box") {
- out.coverageCalibration = Calibration::CoverageCalibration::BOX;
- } else if (coverageCalibrationString != "default") {
- ALOGW("Invalid value for touch.coverage.calibration: '%s'",
- coverageCalibrationString.c_str());
- }
- }
}
void TouchInputMapper::resolveCalibration() {
@@ -1248,11 +1243,6 @@
} else {
mCalibration.distanceCalibration = Calibration::DistanceCalibration::NONE;
}
-
- // Coverage
- if (mCalibration.coverageCalibration == Calibration::CoverageCalibration::DEFAULT) {
- mCalibration.coverageCalibration = Calibration::CoverageCalibration::NONE;
- }
}
void TouchInputMapper::dumpCalibration(std::string& dump) {
@@ -1323,17 +1313,6 @@
if (mCalibration.distanceScale) {
dump += StringPrintf(INDENT4 "touch.distance.scale: %0.3f\n", *mCalibration.distanceScale);
}
-
- switch (mCalibration.coverageCalibration) {
- case Calibration::CoverageCalibration::NONE:
- dump += INDENT4 "touch.coverage.calibration: none\n";
- break;
- case Calibration::CoverageCalibration::BOX:
- dump += INDENT4 "touch.coverage.calibration: box\n";
- break;
- default:
- ALOG_ASSERT(false);
- }
}
void TouchInputMapper::dumpAffineTransformation(std::string& dump) {
@@ -1429,8 +1408,9 @@
next.readTime = readTime;
// Sync button state.
- next.buttonState =
- mTouchButtonAccumulator.getButtonState() | mCursorButtonAccumulator.getButtonState();
+ next.buttonState = filterButtonState(mConfig,
+ mTouchButtonAccumulator.getButtonState() |
+ mCursorButtonAccumulator.getButtonState());
// Sync scroll
next.rawVScroll = mCursorScrollAccumulator.getRelativeVWheel();
@@ -1669,7 +1649,9 @@
void TouchInputMapper::applyExternalStylusButtonState(nsecs_t when) {
if (mDeviceMode == DeviceMode::DIRECT && hasExternalStylus()) {
// If any of the external buttons are already pressed by the touch device, ignore them.
- const int32_t pressedButtons = ~mCurrentRawState.buttonState & mExternalStylusState.buttons;
+ const int32_t pressedButtons =
+ filterButtonState(mConfig,
+ ~mCurrentRawState.buttonState & mExternalStylusState.buttons);
const int32_t releasedButtons =
mExternalStylusButtonsApplied & ~mExternalStylusState.buttons;
@@ -2290,20 +2272,20 @@
if (mHaveTilt) {
float tiltXAngle = (in.tiltX - mTiltXCenter) * mTiltXScale;
float tiltYAngle = (in.tiltY - mTiltYCenter) * mTiltYScale;
- orientation = atan2f(-sinf(tiltXAngle), sinf(tiltYAngle));
+ orientation = transformAngle(mRawRotation, atan2f(-sinf(tiltXAngle), sinf(tiltYAngle)));
tilt = acosf(cosf(tiltXAngle) * cosf(tiltYAngle));
} else {
tilt = 0;
switch (mCalibration.orientationCalibration) {
case Calibration::OrientationCalibration::INTERPOLATED:
- orientation = in.orientation * mOrientationScale;
+ orientation = transformAngle(mRawRotation, in.orientation * mOrientationScale);
break;
case Calibration::OrientationCalibration::VECTOR: {
int32_t c1 = signExtendNybble((in.orientation & 0xf0) >> 4);
int32_t c2 = signExtendNybble(in.orientation & 0x0f);
if (c1 != 0 || c2 != 0) {
- orientation = atan2f(c1, c2) * 0.5f;
+ orientation = transformAngle(mRawRotation, atan2f(c1, c2) * 0.5f);
float confidence = hypotf(c1, c2);
float scale = 1.0f + confidence / 16.0f;
touchMajor *= scale;
@@ -2330,76 +2312,16 @@
distance = 0;
}
- // Coverage
- int32_t rawLeft, rawTop, rawRight, rawBottom;
- switch (mCalibration.coverageCalibration) {
- case Calibration::CoverageCalibration::BOX:
- rawLeft = (in.toolMinor & 0xffff0000) >> 16;
- rawRight = in.toolMinor & 0x0000ffff;
- rawBottom = in.toolMajor & 0x0000ffff;
- rawTop = (in.toolMajor & 0xffff0000) >> 16;
- break;
- default:
- rawLeft = rawTop = rawRight = rawBottom = 0;
- break;
- }
-
- // Adjust X,Y coords for device calibration
- // TODO: Adjust coverage coords?
- float xTransformed = in.x, yTransformed = in.y;
- mAffineTransform.applyTo(xTransformed, yTransformed);
- rotateAndScale(xTransformed, yTransformed);
-
- // Adjust X, Y, and coverage coords for input device orientation.
- float left, top, right, bottom;
-
- switch (mInputDeviceOrientation) {
- case ui::ROTATION_90:
- left = float(rawTop - mRawPointerAxes.y.minValue) * mYScale;
- right = float(rawBottom - mRawPointerAxes.y.minValue) * mYScale;
- bottom = float(mRawPointerAxes.x.maxValue - rawLeft) * mXScale;
- top = float(mRawPointerAxes.x.maxValue - rawRight) * mXScale;
- orientation -= M_PI_2;
- if (mOrientedRanges.orientation && orientation < mOrientedRanges.orientation->min) {
- orientation +=
- (mOrientedRanges.orientation->max - mOrientedRanges.orientation->min);
- }
- break;
- case ui::ROTATION_180:
- left = float(mRawPointerAxes.x.maxValue - rawRight) * mXScale;
- right = float(mRawPointerAxes.x.maxValue - rawLeft) * mXScale;
- bottom = float(mRawPointerAxes.y.maxValue - rawTop) * mYScale;
- top = float(mRawPointerAxes.y.maxValue - rawBottom) * mYScale;
- orientation -= M_PI;
- if (mOrientedRanges.orientation && orientation < mOrientedRanges.orientation->min) {
- orientation +=
- (mOrientedRanges.orientation->max - mOrientedRanges.orientation->min);
- }
- break;
- case ui::ROTATION_270:
- left = float(mRawPointerAxes.y.maxValue - rawBottom) * mYScale;
- right = float(mRawPointerAxes.y.maxValue - rawTop) * mYScale;
- bottom = float(rawRight - mRawPointerAxes.x.minValue) * mXScale;
- top = float(rawLeft - mRawPointerAxes.x.minValue) * mXScale;
- orientation += M_PI_2;
- if (mOrientedRanges.orientation && orientation > mOrientedRanges.orientation->max) {
- orientation -=
- (mOrientedRanges.orientation->max - mOrientedRanges.orientation->min);
- }
- break;
- default:
- left = float(rawLeft - mRawPointerAxes.x.minValue) * mXScale;
- right = float(rawRight - mRawPointerAxes.x.minValue) * mXScale;
- bottom = float(rawBottom - mRawPointerAxes.y.minValue) * mYScale;
- top = float(rawTop - mRawPointerAxes.y.minValue) * mYScale;
- break;
- }
+ // Adjust X,Y coords for device calibration and convert to the natural display coordinates.
+ vec2 transformed = {in.x, in.y};
+ mAffineTransform.applyTo(transformed.x /*byRef*/, transformed.y /*byRef*/);
+ transformed = mRawToDisplay.transform(transformed);
// Write output coords.
PointerCoords& out = mCurrentCookedState.cookedPointerData.pointerCoords[i];
out.clear();
- out.setAxisValue(AMOTION_EVENT_AXIS_X, xTransformed);
- out.setAxisValue(AMOTION_EVENT_AXIS_Y, yTransformed);
+ out.setAxisValue(AMOTION_EVENT_AXIS_X, transformed.x);
+ out.setAxisValue(AMOTION_EVENT_AXIS_Y, transformed.y);
out.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pressure);
out.setAxisValue(AMOTION_EVENT_AXIS_SIZE, size);
out.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, touchMajor);
@@ -2407,23 +2329,16 @@
out.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, orientation);
out.setAxisValue(AMOTION_EVENT_AXIS_TILT, tilt);
out.setAxisValue(AMOTION_EVENT_AXIS_DISTANCE, distance);
- if (mCalibration.coverageCalibration == Calibration::CoverageCalibration::BOX) {
- out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_1, left);
- out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_2, top);
- out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_3, right);
- out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_4, bottom);
- } else {
- out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, toolMajor);
- out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, toolMinor);
- }
+ out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, toolMajor);
+ out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, toolMinor);
// Write output relative fields if applicable.
uint32_t id = in.id;
if (mSource == AINPUT_SOURCE_TOUCHPAD &&
mLastCookedState.cookedPointerData.hasPointerCoordsForId(id)) {
const PointerCoords& p = mLastCookedState.cookedPointerData.pointerCoordsForId(id);
- float dx = xTransformed - p.getAxisValue(AMOTION_EVENT_AXIS_X);
- float dy = yTransformed - p.getAxisValue(AMOTION_EVENT_AXIS_Y);
+ float dx = transformed.x - p.getAxisValue(AMOTION_EVENT_AXIS_X);
+ float dy = transformed.y - p.getAxisValue(AMOTION_EVENT_AXIS_Y);
out.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, dx);
out.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, dy);
}
@@ -3796,48 +3711,10 @@
return out;
}
-// Transform input device coordinates to display panel coordinates.
-void TouchInputMapper::rotateAndScale(float& x, float& y) const {
- const float xScaled = float(x - mRawPointerAxes.x.minValue) * mXScale;
- const float yScaled = float(y - mRawPointerAxes.y.minValue) * mYScale;
-
- const float xScaledMax = float(mRawPointerAxes.x.maxValue - x) * mXScale;
- const float yScaledMax = float(mRawPointerAxes.y.maxValue - y) * mYScale;
-
- // Rotate to display coordinate.
- // 0 - no swap and reverse.
- // 90 - swap x/y and reverse y.
- // 180 - reverse x, y.
- // 270 - swap x/y and reverse x.
- switch (mInputDeviceOrientation) {
- case ui::ROTATION_0:
- x = xScaled;
- y = yScaled;
- break;
- case ui::ROTATION_90:
- y = xScaledMax;
- x = yScaled;
- break;
- case ui::ROTATION_180:
- x = xScaledMax;
- y = yScaledMax;
- break;
- case ui::ROTATION_270:
- y = xScaled;
- x = yScaledMax;
- break;
- default:
- assert(false);
- }
-}
-
bool TouchInputMapper::isPointInsidePhysicalFrame(int32_t x, int32_t y) const {
- const float xScaled = (x - mRawPointerAxes.x.minValue) * mXScale;
- const float yScaled = (y - mRawPointerAxes.y.minValue) * mYScale;
-
return x >= mRawPointerAxes.x.minValue && x <= mRawPointerAxes.x.maxValue &&
y >= mRawPointerAxes.y.minValue && y <= mRawPointerAxes.y.maxValue &&
- isPointInRect(mPhysicalFrameInDisplay, xScaled, yScaled);
+ isPointInRect(mPhysicalFrameInRotatedDisplay, mRawToRotatedDisplay.transform(x, y));
}
const TouchInputMapper::VirtualKey* TouchInputMapper::findVirtualKeyHit(int32_t x, int32_t y) {
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index 50a7ea3..6e35b46 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -291,14 +291,6 @@
DistanceCalibration distanceCalibration;
std::optional<float> distanceScale;
- enum class CoverageCalibration {
- DEFAULT,
- NONE,
- BOX,
- };
-
- CoverageCalibration coverageCalibration;
-
inline void applySizeScaleAndBias(float& outSize) const {
if (sizeScale) {
outSize *= *sizeScale;
@@ -410,21 +402,26 @@
// Always starts at (0, 0).
ui::Size mDisplayBounds{ui::kInvalidSize};
- // The physical frame is the rectangle in the natural display's coordinate space that maps to
+ // The physical frame is the rectangle in the rotated display's coordinate space that maps to
// the logical display frame.
- Rect mPhysicalFrameInDisplay{Rect::INVALID_RECT};
+ Rect mPhysicalFrameInRotatedDisplay{Rect::INVALID_RECT};
// The orientation of the input device relative to that of the display panel. It specifies
// the rotation of the input device coordinates required to produce the display panel
// orientation, so it will depend on whether the device is orientation aware.
ui::Rotation mInputDeviceOrientation;
- // Translation and scaling factors, orientation-independent.
- float mXScale;
- float mXPrecision;
+ // The transform that maps the input device's raw coordinate space to the un-rotated display's
+ // coordinate space. InputReader generates events in the un-rotated display's coordinate space.
+ ui::Transform mRawToDisplay;
- float mYScale;
- float mYPrecision;
+ // The transform that maps the input device's raw coordinate space to the rotated display's
+ // coordinate space. This used to perform hit-testing of raw events with the physical frame in
+ // the rotated coordinate space. See mPhysicalFrameInRotatedDisplay.
+ ui::Transform mRawToRotatedDisplay;
+
+ // The transform used for non-planar raw axes, such as orientation and tilt.
+ ui::Transform mRawRotation;
float mGeometricScale;
@@ -813,7 +810,7 @@
static void assignPointerIds(const RawState& last, RawState& current);
- void rotateAndScale(float& x, float& y) const;
+ void computeInputTransforms();
void configureDeviceType();
};
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index 956a7aa..3b51be8 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -16,12 +16,14 @@
#include "../Macros.h"
-#include <chrono>
+#include <optional>
#include <android/input.h>
+#include <linux/input-event-codes.h>
#include <log/log_main.h>
#include "TouchCursorInputMapperCommon.h"
#include "TouchpadInputMapper.h"
+#include "ui/Rotation.h"
namespace android {
@@ -83,30 +85,14 @@
mapper->consumeGesture(gesture);
}
-uint32_t gesturesButtonToMotionEventButton(uint32_t gesturesButton) {
- switch (gesturesButton) {
- case GESTURES_BUTTON_LEFT:
- return AMOTION_EVENT_BUTTON_PRIMARY;
- case GESTURES_BUTTON_MIDDLE:
- return AMOTION_EVENT_BUTTON_TERTIARY;
- case GESTURES_BUTTON_RIGHT:
- return AMOTION_EVENT_BUTTON_SECONDARY;
- case GESTURES_BUTTON_BACK:
- return AMOTION_EVENT_BUTTON_BACK;
- case GESTURES_BUTTON_FORWARD:
- return AMOTION_EVENT_BUTTON_FORWARD;
- default:
- return 0;
- }
-}
-
} // namespace
TouchpadInputMapper::TouchpadInputMapper(InputDeviceContext& deviceContext)
: InputMapper(deviceContext),
mGestureInterpreter(NewGestureInterpreter(), DeleteGestureInterpreter),
mPointerController(getContext()->getPointerController(getDeviceId())),
- mTouchButtonAccumulator(deviceContext) {
+ mStateConverter(deviceContext),
+ mGestureConverter(*getContext(), deviceContext, getDeviceId()) {
mGestureInterpreter->Initialize(GESTURES_DEVCLASS_TOUCHPAD);
mGestureInterpreter->SetHardwareProperties(createHardwareProperties(deviceContext));
// Even though we don't explicitly delete copy/move semantics, it's safe to
@@ -116,16 +102,6 @@
mGestureInterpreter->SetCallback(gestureInterpreterCallback, this);
// TODO(b/251196347): set a property provider, so we can change gesture properties.
// TODO(b/251196347): set a timer provider, so the library can use timers.
-
- RawAbsoluteAxisInfo slotAxisInfo;
- getAbsoluteAxisInfo(ABS_MT_SLOT, &slotAxisInfo);
- if (!slotAxisInfo.valid || slotAxisInfo.maxValue <= 0) {
- ALOGW("Touchpad \"%s\" doesn't have a valid ABS_MT_SLOT axis, and probably won't work "
- "properly.",
- getDeviceName().c_str());
- }
- mMotionAccumulator.configure(getDeviceContext(), slotAxisInfo.maxValue + 1, true);
- mTouchButtonAccumulator.configure();
}
TouchpadInputMapper::~TouchpadInputMapper() {
@@ -138,83 +114,44 @@
return AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD;
}
-std::list<NotifyArgs> TouchpadInputMapper::reset(nsecs_t when) {
- mCursorButtonAccumulator.reset(getDeviceContext());
- mTouchButtonAccumulator.reset();
- mMscTimestamp = 0;
+std::list<NotifyArgs> TouchpadInputMapper::configure(nsecs_t when,
+ const InputReaderConfiguration* config,
+ uint32_t changes) {
+ if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
+ std::optional<int32_t> displayId = mPointerController->getDisplayId();
+ ui::Rotation orientation = ui::ROTATION_0;
+ if (displayId.has_value()) {
+ if (auto viewport = config->getDisplayViewportById(*displayId); viewport) {
+ orientation = getInverseRotation(viewport->orientation);
+ }
+ }
+ mGestureConverter.setOrientation(orientation);
+ }
+ return {};
+}
- mButtonState = 0;
+std::list<NotifyArgs> TouchpadInputMapper::reset(nsecs_t when) {
+ mStateConverter.reset();
+ mGestureConverter.reset();
return InputMapper::reset(when);
}
std::list<NotifyArgs> TouchpadInputMapper::process(const RawEvent* rawEvent) {
- std::list<NotifyArgs> out = {};
- if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
- out = sync(rawEvent->when, rawEvent->readTime);
+ std::optional<SelfContainedHardwareState> state = mStateConverter.processRawEvent(rawEvent);
+ if (state) {
+ return sendHardwareState(rawEvent->when, rawEvent->readTime, *state);
+ } else {
+ return {};
}
- if (rawEvent->type == EV_MSC && rawEvent->code == MSC_TIMESTAMP) {
- mMscTimestamp = rawEvent->value;
- }
- mCursorButtonAccumulator.process(rawEvent);
- mMotionAccumulator.process(rawEvent);
- mTouchButtonAccumulator.process(rawEvent);
- return out;
}
-std::list<NotifyArgs> TouchpadInputMapper::sync(nsecs_t when, nsecs_t readTime) {
- HardwareState hwState;
- // The gestures library uses doubles to represent timestamps in seconds.
- hwState.timestamp = std::chrono::duration<stime_t>(std::chrono::nanoseconds(when)).count();
- hwState.msc_timestamp =
- std::chrono::duration<stime_t>(std::chrono::microseconds(mMscTimestamp)).count();
-
- hwState.buttons_down = 0;
- if (mCursorButtonAccumulator.isLeftPressed()) {
- hwState.buttons_down |= GESTURES_BUTTON_LEFT;
- }
- if (mCursorButtonAccumulator.isMiddlePressed()) {
- hwState.buttons_down |= GESTURES_BUTTON_MIDDLE;
- }
- if (mCursorButtonAccumulator.isRightPressed()) {
- hwState.buttons_down |= GESTURES_BUTTON_RIGHT;
- }
- if (mCursorButtonAccumulator.isBackPressed() || mCursorButtonAccumulator.isSidePressed()) {
- hwState.buttons_down |= GESTURES_BUTTON_BACK;
- }
- if (mCursorButtonAccumulator.isForwardPressed() || mCursorButtonAccumulator.isExtraPressed()) {
- hwState.buttons_down |= GESTURES_BUTTON_FORWARD;
- }
-
- std::vector<FingerState> fingers;
- for (size_t i = 0; i < mMotionAccumulator.getSlotCount(); i++) {
- MultiTouchMotionAccumulator::Slot slot = mMotionAccumulator.getSlot(i);
- if (slot.isInUse()) {
- FingerState& fingerState = fingers.emplace_back();
- fingerState = {};
- fingerState.touch_major = slot.getTouchMajor();
- fingerState.touch_minor = slot.getTouchMinor();
- fingerState.width_major = slot.getToolMajor();
- fingerState.width_minor = slot.getToolMinor();
- fingerState.pressure = slot.getPressure();
- fingerState.orientation = slot.getOrientation();
- fingerState.position_x = slot.getX();
- fingerState.position_y = slot.getY();
- fingerState.tracking_id = slot.getTrackingId();
- }
- }
- hwState.fingers = fingers.data();
- hwState.finger_cnt = fingers.size();
- hwState.touch_cnt = mTouchButtonAccumulator.getTouchCount();
-
+std::list<NotifyArgs> TouchpadInputMapper::sendHardwareState(nsecs_t when, nsecs_t readTime,
+ SelfContainedHardwareState schs) {
mProcessing = true;
- mGestureInterpreter->PushHardwareState(&hwState);
+ mGestureInterpreter->PushHardwareState(&schs.state);
mProcessing = false;
- std::list<NotifyArgs> out = processGestures(when, readTime);
-
- mMotionAccumulator.finishSync();
- mMscTimestamp = 0;
- return out;
+ return processGestures(when, readTime);
}
void TouchpadInputMapper::consumeGesture(const Gesture* gesture) {
@@ -229,137 +166,10 @@
std::list<NotifyArgs> TouchpadInputMapper::processGestures(nsecs_t when, nsecs_t readTime) {
std::list<NotifyArgs> out = {};
for (Gesture& gesture : mGesturesToProcess) {
- switch (gesture.type) {
- case kGestureTypeMove:
- out.push_back(handleMove(when, readTime, gesture));
- break;
- case kGestureTypeButtonsChange:
- out += handleButtonsChange(when, readTime, gesture);
- break;
- default:
- // TODO(b/251196347): handle more gesture types.
- break;
- }
+ out += mGestureConverter.handleGesture(when, readTime, gesture);
}
mGesturesToProcess.clear();
return out;
}
-NotifyArgs TouchpadInputMapper::handleMove(nsecs_t when, nsecs_t readTime, const Gesture& gesture) {
- PointerProperties props;
- props.clear();
- props.id = 0;
- props.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
-
- mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
- mPointerController->move(gesture.details.move.dx, gesture.details.move.dy);
- mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
- float xCursorPosition, yCursorPosition;
- mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
-
- PointerCoords coords;
- coords.clear();
- coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
- coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
- coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, gesture.details.move.dx);
- coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, gesture.details.move.dy);
- const bool down = isPointerDown(mButtonState);
- coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, down ? 1.0f : 0.0f);
-
- const int32_t action = down ? AMOTION_EVENT_ACTION_MOVE : AMOTION_EVENT_ACTION_HOVER_MOVE;
- return makeMotionArgs(when, readTime, action, /* actionButton= */ 0, mButtonState,
- /* pointerCount= */ 1, &props, &coords, xCursorPosition, yCursorPosition);
-}
-
-std::list<NotifyArgs> TouchpadInputMapper::handleButtonsChange(nsecs_t when, nsecs_t readTime,
- const Gesture& gesture) {
- std::list<NotifyArgs> out = {};
-
- mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
- mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
-
- PointerProperties props;
- props.clear();
- props.id = 0;
- props.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
-
- float xCursorPosition, yCursorPosition;
- mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
-
- PointerCoords coords;
- coords.clear();
- coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
- coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
- coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0);
- coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0);
- const uint32_t buttonsPressed = gesture.details.buttons.down;
- bool pointerDown = isPointerDown(mButtonState) ||
- buttonsPressed &
- (GESTURES_BUTTON_LEFT | GESTURES_BUTTON_MIDDLE | GESTURES_BUTTON_RIGHT);
- coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pointerDown ? 1.0f : 0.0f);
-
- uint32_t newButtonState = mButtonState;
- std::list<NotifyArgs> pressEvents = {};
- for (uint32_t button = 1; button <= GESTURES_BUTTON_FORWARD; button <<= 1) {
- if (buttonsPressed & button) {
- uint32_t actionButton = gesturesButtonToMotionEventButton(button);
- newButtonState |= actionButton;
- pressEvents.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_PRESS,
- actionButton, newButtonState,
- /* pointerCount= */ 1, &props, &coords,
- xCursorPosition, yCursorPosition));
- }
- }
- if (!isPointerDown(mButtonState) && isPointerDown(newButtonState)) {
- mDownTime = when;
- out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN,
- /* actionButton= */ 0, newButtonState, /* pointerCount= */ 1,
- &props, &coords, xCursorPosition, yCursorPosition));
- }
- out.splice(out.end(), pressEvents);
-
- // The same button may be in both down and up in the same gesture, in which case we should treat
- // it as having gone down and then up. So, we treat a single button change gesture as two state
- // changes: a set of buttons going down, followed by a set of buttons going up.
- mButtonState = newButtonState;
-
- const uint32_t buttonsReleased = gesture.details.buttons.up;
- for (uint32_t button = 1; button <= GESTURES_BUTTON_FORWARD; button <<= 1) {
- if (buttonsReleased & button) {
- uint32_t actionButton = gesturesButtonToMotionEventButton(button);
- newButtonState &= ~actionButton;
- out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE,
- actionButton, newButtonState, /* pointerCount= */ 1,
- &props, &coords, xCursorPosition, yCursorPosition));
- }
- }
- if (isPointerDown(mButtonState) && !isPointerDown(newButtonState)) {
- coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.0f);
- out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /* actionButton= */ 0,
- newButtonState, /* pointerCount= */ 1, &props, &coords,
- xCursorPosition, yCursorPosition));
- }
- mButtonState = newButtonState;
- return out;
-}
-
-NotifyMotionArgs TouchpadInputMapper::makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action,
- int32_t actionButton, int32_t buttonState,
- uint32_t pointerCount,
- const PointerProperties* pointerProperties,
- const PointerCoords* pointerCoords,
- float xCursorPosition, float yCursorPosition) {
- // TODO(b/260226362): consider what the appropriate source for these events is.
- const uint32_t source = AINPUT_SOURCE_MOUSE;
-
- return NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), source,
- mPointerController->getDisplayId(), /* policyFlags= */ 0, action,
- /* actionButton= */ actionButton, /* flags= */ 0,
- getContext()->getGlobalMetaState(), buttonState,
- MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount,
- pointerProperties, pointerCoords,
- /* xPrecision= */ 1.0f, /* yPrecision= */ 1.0f, xCursorPosition,
- yCursorPosition, /* downTime= */ mDownTime, /* videoFrames= */ {});
-}
-
} // namespace android
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
index fe6b1fe..3a92211 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
@@ -16,17 +16,19 @@
#pragma once
+#include <list>
#include <memory>
+#include <vector>
#include <PointerControllerInterface.h>
#include "EventHub.h"
#include "InputDevice.h"
#include "InputMapper.h"
+#include "InputReaderBase.h"
#include "NotifyArgs.h"
-#include "accumulator/CursorButtonAccumulator.h"
-#include "accumulator/MultiTouchMotionAccumulator.h"
-#include "accumulator/TouchButtonAccumulator.h"
+#include "gestures/GestureConverter.h"
+#include "gestures/HardwareStateConverter.h"
#include "include/gestures.h"
@@ -38,41 +40,28 @@
~TouchpadInputMapper();
uint32_t getSources() const override;
+ [[nodiscard]] std::list<NotifyArgs> configure(nsecs_t when,
+ const InputReaderConfiguration* config,
+ uint32_t changes) override;
[[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
[[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
void consumeGesture(const Gesture* gesture);
private:
- [[nodiscard]] std::list<NotifyArgs> sync(nsecs_t when, nsecs_t readTime);
+ [[nodiscard]] std::list<NotifyArgs> sendHardwareState(nsecs_t when, nsecs_t readTime,
+ SelfContainedHardwareState schs);
[[nodiscard]] std::list<NotifyArgs> processGestures(nsecs_t when, nsecs_t readTime);
- NotifyArgs handleMove(nsecs_t when, nsecs_t readTime, const Gesture& gesture);
- [[nodiscard]] std::list<NotifyArgs> handleButtonsChange(nsecs_t when, nsecs_t readTime,
- const Gesture& gesture);
-
- NotifyMotionArgs makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action,
- int32_t actionButton, int32_t buttonState,
- uint32_t pointerCount,
- const PointerProperties* pointerProperties,
- const PointerCoords* pointerCoords, float xCursorPosition,
- float yCursorPosition);
std::unique_ptr<gestures::GestureInterpreter, void (*)(gestures::GestureInterpreter*)>
mGestureInterpreter;
std::shared_ptr<PointerControllerInterface> mPointerController;
- CursorButtonAccumulator mCursorButtonAccumulator;
- MultiTouchMotionAccumulator mMotionAccumulator;
- TouchButtonAccumulator mTouchButtonAccumulator;
- int32_t mMscTimestamp = 0;
+ HardwareStateConverter mStateConverter;
+ GestureConverter mGestureConverter;
bool mProcessing = false;
std::vector<Gesture> mGesturesToProcess;
-
- // The current button state according to the gestures library, but converted into MotionEvent
- // button values (AMOTION_EVENT_BUTTON_...).
- uint32_t mButtonState = 0;
- nsecs_t mDownTime = 0;
};
} // namespace android
diff --git a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp
index 2d7d73b..153236c 100644
--- a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp
@@ -25,7 +25,7 @@
clearButtons();
}
-void CursorButtonAccumulator::reset(InputDeviceContext& deviceContext) {
+void CursorButtonAccumulator::reset(const InputDeviceContext& deviceContext) {
mBtnLeft = deviceContext.isKeyPressed(BTN_LEFT);
mBtnRight = deviceContext.isKeyPressed(BTN_RIGHT);
mBtnMiddle = deviceContext.isKeyPressed(BTN_MIDDLE);
diff --git a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h
index 1380604..6960644 100644
--- a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h
+++ b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h
@@ -27,7 +27,7 @@
class CursorButtonAccumulator {
public:
CursorButtonAccumulator();
- void reset(InputDeviceContext& deviceContext);
+ void reset(const InputDeviceContext& deviceContext);
void process(const RawEvent* rawEvent);
diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
index 8746729..f6a42bd 100644
--- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
@@ -26,8 +26,8 @@
MultiTouchMotionAccumulator::MultiTouchMotionAccumulator()
: mCurrentSlot(-1), mUsingSlotsProtocol(false) {}
-void MultiTouchMotionAccumulator::configure(InputDeviceContext& deviceContext, size_t slotCount,
- bool usingSlotsProtocol) {
+void MultiTouchMotionAccumulator::configure(const InputDeviceContext& deviceContext,
+ size_t slotCount, bool usingSlotsProtocol) {
mUsingSlotsProtocol = usingSlotsProtocol;
mSlots = std::vector<Slot>(slotCount);
diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
index 62bc780..3c1a2a9 100644
--- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
+++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
@@ -72,7 +72,8 @@
MultiTouchMotionAccumulator();
- void configure(InputDeviceContext& deviceContext, size_t slotCount, bool usingSlotsProtocol);
+ void configure(const InputDeviceContext& deviceContext, size_t slotCount,
+ bool usingSlotsProtocol);
void process(const RawEvent* rawEvent);
void finishSync();
diff --git a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h
index 2e70e2e..c2aa2ad 100644
--- a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h
+++ b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h
@@ -27,7 +27,7 @@
/* Keeps track of the state of touch, stylus and tool buttons. */
class TouchButtonAccumulator {
public:
- explicit TouchButtonAccumulator(InputDeviceContext& deviceContext)
+ explicit TouchButtonAccumulator(const InputDeviceContext& deviceContext)
: mDeviceContext(deviceContext){};
void configure();
@@ -65,7 +65,7 @@
HidUsageAccumulator mHidUsageAccumulator{};
- InputDeviceContext& mDeviceContext;
+ const InputDeviceContext& mDeviceContext;
void processMappedKey(int32_t scanCode, bool down);
};
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
new file mode 100644
index 0000000..11ffd28
--- /dev/null
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
@@ -0,0 +1,421 @@
+/*
+ * 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 "gestures/GestureConverter.h"
+
+#include <android/input.h>
+#include <linux/input-event-codes.h>
+#include <log/log_main.h>
+
+#include "TouchCursorInputMapperCommon.h"
+#include "input/Input.h"
+
+namespace android {
+
+namespace {
+
+uint32_t gesturesButtonToMotionEventButton(uint32_t gesturesButton) {
+ switch (gesturesButton) {
+ case GESTURES_BUTTON_LEFT:
+ return AMOTION_EVENT_BUTTON_PRIMARY;
+ case GESTURES_BUTTON_MIDDLE:
+ return AMOTION_EVENT_BUTTON_TERTIARY;
+ case GESTURES_BUTTON_RIGHT:
+ return AMOTION_EVENT_BUTTON_SECONDARY;
+ case GESTURES_BUTTON_BACK:
+ return AMOTION_EVENT_BUTTON_BACK;
+ case GESTURES_BUTTON_FORWARD:
+ return AMOTION_EVENT_BUTTON_FORWARD;
+ default:
+ return 0;
+ }
+}
+
+} // namespace
+
+GestureConverter::GestureConverter(InputReaderContext& readerContext,
+ const InputDeviceContext& deviceContext, int32_t deviceId)
+ : mDeviceId(deviceId),
+ mReaderContext(readerContext),
+ mPointerController(readerContext.getPointerController(deviceId)) {
+ deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_X, &mXAxisInfo);
+ deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &mYAxisInfo);
+}
+
+void GestureConverter::reset() {
+ mButtonState = 0;
+}
+
+std::list<NotifyArgs> GestureConverter::handleGesture(nsecs_t when, nsecs_t readTime,
+ const Gesture& gesture) {
+ switch (gesture.type) {
+ case kGestureTypeMove:
+ return {handleMove(when, readTime, gesture)};
+ case kGestureTypeButtonsChange:
+ return handleButtonsChange(when, readTime, gesture);
+ case kGestureTypeScroll:
+ return handleScroll(when, readTime, gesture);
+ case kGestureTypeFling:
+ return {handleFling(when, readTime, gesture)};
+ case kGestureTypeSwipe:
+ return handleMultiFingerSwipe(when, readTime, 3, gesture.details.swipe.dx,
+ gesture.details.swipe.dy);
+ case kGestureTypeFourFingerSwipe:
+ return handleMultiFingerSwipe(when, readTime, 4, gesture.details.four_finger_swipe.dx,
+ gesture.details.four_finger_swipe.dy);
+ case kGestureTypeSwipeLift:
+ case kGestureTypeFourFingerSwipeLift:
+ return handleMultiFingerSwipeLift(when, readTime);
+ case kGestureTypePinch:
+ return handlePinch(when, readTime, gesture);
+ default:
+ // TODO(b/251196347): handle more gesture types.
+ return {};
+ }
+}
+
+NotifyArgs GestureConverter::handleMove(nsecs_t when, nsecs_t readTime, const Gesture& gesture) {
+ float deltaX = gesture.details.move.dx;
+ float deltaY = gesture.details.move.dy;
+ rotateDelta(mOrientation, &deltaX, &deltaY);
+
+ mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
+ mPointerController->move(deltaX, deltaY);
+ mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
+ float xCursorPosition, yCursorPosition;
+ mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+
+ PointerCoords coords;
+ coords.clear();
+ coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
+ coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+ coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, deltaX);
+ coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, deltaY);
+ const bool down = isPointerDown(mButtonState);
+ coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, down ? 1.0f : 0.0f);
+
+ const int32_t action = down ? AMOTION_EVENT_ACTION_MOVE : AMOTION_EVENT_ACTION_HOVER_MOVE;
+ return makeMotionArgs(when, readTime, action, /* actionButton= */ 0, mButtonState,
+ /* pointerCount= */ 1, mFingerProps.data(), &coords, xCursorPosition,
+ yCursorPosition);
+}
+
+std::list<NotifyArgs> GestureConverter::handleButtonsChange(nsecs_t when, nsecs_t readTime,
+ const Gesture& gesture) {
+ std::list<NotifyArgs> out = {};
+
+ mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
+ mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
+
+ float xCursorPosition, yCursorPosition;
+ mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+
+ PointerCoords coords;
+ coords.clear();
+ coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
+ coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+ coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0);
+ coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0);
+ const uint32_t buttonsPressed = gesture.details.buttons.down;
+ bool pointerDown = isPointerDown(mButtonState) ||
+ buttonsPressed &
+ (GESTURES_BUTTON_LEFT | GESTURES_BUTTON_MIDDLE | GESTURES_BUTTON_RIGHT);
+ coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pointerDown ? 1.0f : 0.0f);
+
+ uint32_t newButtonState = mButtonState;
+ std::list<NotifyArgs> pressEvents = {};
+ for (uint32_t button = 1; button <= GESTURES_BUTTON_FORWARD; button <<= 1) {
+ if (buttonsPressed & button) {
+ uint32_t actionButton = gesturesButtonToMotionEventButton(button);
+ newButtonState |= actionButton;
+ pressEvents.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_PRESS,
+ actionButton, newButtonState,
+ /* pointerCount= */ 1, mFingerProps.data(),
+ &coords, xCursorPosition, yCursorPosition));
+ }
+ }
+ if (!isPointerDown(mButtonState) && isPointerDown(newButtonState)) {
+ mDownTime = when;
+ out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN,
+ /* actionButton= */ 0, newButtonState, /* pointerCount= */ 1,
+ mFingerProps.data(), &coords, xCursorPosition,
+ yCursorPosition));
+ }
+ out.splice(out.end(), pressEvents);
+
+ // The same button may be in both down and up in the same gesture, in which case we should treat
+ // it as having gone down and then up. So, we treat a single button change gesture as two state
+ // changes: a set of buttons going down, followed by a set of buttons going up.
+ mButtonState = newButtonState;
+
+ const uint32_t buttonsReleased = gesture.details.buttons.up;
+ for (uint32_t button = 1; button <= GESTURES_BUTTON_FORWARD; button <<= 1) {
+ if (buttonsReleased & button) {
+ uint32_t actionButton = gesturesButtonToMotionEventButton(button);
+ newButtonState &= ~actionButton;
+ out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE,
+ actionButton, newButtonState, /* pointerCount= */ 1,
+ mFingerProps.data(), &coords, xCursorPosition,
+ yCursorPosition));
+ }
+ }
+ if (isPointerDown(mButtonState) && !isPointerDown(newButtonState)) {
+ coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.0f);
+ out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /* actionButton= */ 0,
+ newButtonState, /* pointerCount= */ 1, mFingerProps.data(),
+ &coords, xCursorPosition, yCursorPosition));
+ }
+ mButtonState = newButtonState;
+ return out;
+}
+
+std::list<NotifyArgs> GestureConverter::handleScroll(nsecs_t when, nsecs_t readTime,
+ const Gesture& gesture) {
+ std::list<NotifyArgs> out;
+ PointerCoords& coords = mFakeFingerCoords[0];
+ float xCursorPosition, yCursorPosition;
+ mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+ if (mCurrentClassification != MotionClassification::TWO_FINGER_SWIPE) {
+ mCurrentClassification = MotionClassification::TWO_FINGER_SWIPE;
+ coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
+ coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+ coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+ mDownTime = when;
+ out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN,
+ /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1,
+ mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
+ yCursorPosition));
+ }
+ float deltaX = gesture.details.scroll.dx;
+ float deltaY = gesture.details.scroll.dy;
+ rotateDelta(mOrientation, &deltaX, &deltaY);
+
+ coords.setAxisValue(AMOTION_EVENT_AXIS_X, coords.getAxisValue(AMOTION_EVENT_AXIS_X) - deltaX);
+ coords.setAxisValue(AMOTION_EVENT_AXIS_Y, coords.getAxisValue(AMOTION_EVENT_AXIS_Y) - deltaY);
+ // TODO(b/262876643): set AXIS_GESTURE_{X,Y}_OFFSET.
+ coords.setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE, gesture.details.scroll.dx);
+ coords.setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, gesture.details.scroll.dy);
+ out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0,
+ mButtonState, /* pointerCount= */ 1, mFingerProps.data(),
+ mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
+ return out;
+}
+
+NotifyArgs GestureConverter::handleFling(nsecs_t when, nsecs_t readTime, const Gesture& gesture) {
+ // We don't actually want to use the gestures library's fling velocity values (to ensure
+ // consistency between touchscreen and touchpad flings), so we're just using the "start fling"
+ // gestures as a marker for the end of a two-finger scroll gesture.
+ if (gesture.details.fling.fling_state != GESTURES_FLING_START ||
+ mCurrentClassification != MotionClassification::TWO_FINGER_SWIPE) {
+ return {};
+ }
+
+ float xCursorPosition, yCursorPosition;
+ mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+ mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE, 0);
+ mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, 0);
+ NotifyArgs args = makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP,
+ /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1,
+ mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
+ yCursorPosition);
+ mCurrentClassification = MotionClassification::NONE;
+ return args;
+}
+
+[[nodiscard]] std::list<NotifyArgs> GestureConverter::handleMultiFingerSwipe(nsecs_t when,
+ nsecs_t readTime,
+ uint32_t fingerCount,
+ float dx, float dy) {
+ std::list<NotifyArgs> out = {};
+ float xCursorPosition, yCursorPosition;
+ mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+ if (mCurrentClassification != MotionClassification::MULTI_FINGER_SWIPE) {
+ // If the user changes the number of fingers mid-way through a swipe (e.g. they start with
+ // three and then put a fourth finger down), the gesture library will treat it as two
+ // separate swipes with an appropriate lift event between them, so we don't have to worry
+ // about the finger count changing mid-swipe.
+ mCurrentClassification = MotionClassification::MULTI_FINGER_SWIPE;
+ mSwipeFingerCount = fingerCount;
+
+ constexpr float FAKE_FINGER_SPACING = 100;
+ float xCoord = xCursorPosition - FAKE_FINGER_SPACING * (mSwipeFingerCount - 1) / 2;
+ for (size_t i = 0; i < mSwipeFingerCount; i++) {
+ PointerCoords& coords = mFakeFingerCoords[i];
+ coords.clear();
+ coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCoord);
+ coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+ coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+ xCoord += FAKE_FINGER_SPACING;
+ }
+
+ mDownTime = when;
+ out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN,
+ /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1,
+ mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
+ yCursorPosition));
+ for (size_t i = 1; i < mSwipeFingerCount; i++) {
+ out.push_back(makeMotionArgs(when, readTime,
+ AMOTION_EVENT_ACTION_POINTER_DOWN |
+ (i << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ /* actionButton= */ 0, mButtonState,
+ /* pointerCount= */ i + 1, mFingerProps.data(),
+ mFakeFingerCoords.data(), xCursorPosition,
+ yCursorPosition));
+ }
+ }
+ // TODO(b/251196347): Set the gesture properties appropriately to avoid needing to negate the Y
+ // values.
+ float rotatedDeltaX = dx, rotatedDeltaY = -dy;
+ rotateDelta(mOrientation, &rotatedDeltaX, &rotatedDeltaY);
+ for (size_t i = 0; i < mSwipeFingerCount; i++) {
+ PointerCoords& coords = mFakeFingerCoords[i];
+ coords.setAxisValue(AMOTION_EVENT_AXIS_X,
+ coords.getAxisValue(AMOTION_EVENT_AXIS_X) + rotatedDeltaX);
+ coords.setAxisValue(AMOTION_EVENT_AXIS_Y,
+ coords.getAxisValue(AMOTION_EVENT_AXIS_Y) + rotatedDeltaY);
+ }
+ float xOffset = dx / (mXAxisInfo.maxValue - mXAxisInfo.minValue);
+ // TODO(b/251196347): Set the gesture properties appropriately to avoid needing to negate the Y
+ // values.
+ float yOffset = -dy / (mYAxisInfo.maxValue - mYAxisInfo.minValue);
+ mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET, xOffset);
+ mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, yOffset);
+ out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0,
+ mButtonState, /* pointerCount= */ mSwipeFingerCount,
+ mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
+ yCursorPosition));
+ return out;
+}
+
+[[nodiscard]] std::list<NotifyArgs> GestureConverter::handleMultiFingerSwipeLift(nsecs_t when,
+ nsecs_t readTime) {
+ std::list<NotifyArgs> out = {};
+ if (mCurrentClassification != MotionClassification::MULTI_FINGER_SWIPE) {
+ return out;
+ }
+ float xCursorPosition, yCursorPosition;
+ mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+ mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET, 0);
+ mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, 0);
+
+ for (size_t i = mSwipeFingerCount; i > 1; i--) {
+ out.push_back(makeMotionArgs(when, readTime,
+ AMOTION_EVENT_ACTION_POINTER_UP |
+ ((i - 1) << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ /* actionButton= */ 0, mButtonState, /* pointerCount= */ i,
+ mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
+ yCursorPosition));
+ }
+ out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP,
+ /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1,
+ mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
+ yCursorPosition));
+ mCurrentClassification = MotionClassification::NONE;
+ mSwipeFingerCount = 0;
+ return out;
+}
+
+[[nodiscard]] std::list<NotifyArgs> GestureConverter::handlePinch(nsecs_t when, nsecs_t readTime,
+ const Gesture& gesture) {
+ std::list<NotifyArgs> out;
+ float xCursorPosition, yCursorPosition;
+ mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+
+ // Pinch gesture phases are reported a little differently from others, in that the same details
+ // struct is used for all phases of the gesture, just with different zoom_state values. When
+ // zoom_state is START or END, dz will always be 1, so we don't need to move the pointers in
+ // those cases.
+
+ if (mCurrentClassification != MotionClassification::PINCH) {
+ LOG_ALWAYS_FATAL_IF(gesture.details.pinch.zoom_state != GESTURES_ZOOM_START,
+ "First pinch gesture does not have the START zoom state (%d instead).",
+ gesture.details.pinch.zoom_state);
+ mCurrentClassification = MotionClassification::PINCH;
+ mPinchFingerSeparation = INITIAL_PINCH_SEPARATION_PX;
+ mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 1.0);
+ mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X,
+ xCursorPosition - mPinchFingerSeparation / 2);
+ mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+ mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+ mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X,
+ xCursorPosition + mPinchFingerSeparation / 2);
+ mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+ mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+ mDownTime = when;
+ out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN,
+ /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1,
+ mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
+ yCursorPosition));
+ out.push_back(makeMotionArgs(when, readTime,
+ AMOTION_EVENT_ACTION_POINTER_DOWN |
+ 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT,
+ /* actionButton= */ 0, mButtonState, /* pointerCount= */ 2,
+ mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
+ yCursorPosition));
+ return out;
+ }
+
+ if (gesture.details.pinch.zoom_state == GESTURES_ZOOM_END) {
+ mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 1.0);
+ out.push_back(makeMotionArgs(when, readTime,
+ AMOTION_EVENT_ACTION_POINTER_UP |
+ 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT,
+ /* actionButton= */ 0, mButtonState, /* pointerCount= */ 2,
+ mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
+ yCursorPosition));
+ out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /* actionButton= */ 0,
+ mButtonState, /* pointerCount= */ 1, mFingerProps.data(),
+ mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
+ mCurrentClassification = MotionClassification::NONE;
+ mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 0);
+ return out;
+ }
+
+ mPinchFingerSeparation *= gesture.details.pinch.dz;
+ mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR,
+ gesture.details.pinch.dz);
+ mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X,
+ xCursorPosition - mPinchFingerSeparation / 2);
+ mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+ mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X,
+ xCursorPosition + mPinchFingerSeparation / 2);
+ mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+ out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0,
+ mButtonState, /* pointerCount= */ 2, mFingerProps.data(),
+ mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
+ return out;
+}
+
+NotifyMotionArgs GestureConverter::makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action,
+ int32_t actionButton, int32_t buttonState,
+ uint32_t pointerCount,
+ const PointerProperties* pointerProperties,
+ const PointerCoords* pointerCoords,
+ float xCursorPosition, float yCursorPosition) {
+ // TODO(b/260226362): consider what the appropriate source for these events is.
+ const uint32_t source = AINPUT_SOURCE_MOUSE;
+
+ return NotifyMotionArgs(mReaderContext.getNextId(), when, readTime, mDeviceId, source,
+ mPointerController->getDisplayId(), /* policyFlags= */ POLICY_FLAG_WAKE,
+ action, /* actionButton= */ actionButton, /* flags= */ 0,
+ mReaderContext.getGlobalMetaState(), buttonState,
+ mCurrentClassification, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount,
+ pointerProperties, pointerCoords, /* xPrecision= */ 1.0f,
+ /* yPrecision= */ 1.0f, xCursorPosition, yCursorPosition,
+ /* downTime= */ mDownTime, /* videoFrames= */ {});
+}
+
+} // namespace android
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
new file mode 100644
index 0000000..8e8e3d9
--- /dev/null
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
@@ -0,0 +1,101 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <array>
+#include <list>
+#include <memory>
+
+#include <PointerControllerInterface.h>
+#include <utils/Timers.h>
+
+#include "EventHub.h"
+#include "InputDevice.h"
+#include "InputReaderContext.h"
+#include "NotifyArgs.h"
+#include "ui/Rotation.h"
+
+#include "include/gestures.h"
+
+namespace android {
+
+// Converts Gesture structs from the gestures library into NotifyArgs and the appropriate
+// PointerController calls.
+class GestureConverter {
+public:
+ GestureConverter(InputReaderContext& readerContext, const InputDeviceContext& deviceContext,
+ int32_t deviceId);
+
+ void setOrientation(ui::Rotation orientation) { mOrientation = orientation; }
+ void reset();
+
+ [[nodiscard]] std::list<NotifyArgs> handleGesture(nsecs_t when, nsecs_t readTime,
+ const Gesture& gesture);
+
+private:
+ [[nodiscard]] NotifyArgs handleMove(nsecs_t when, nsecs_t readTime, const Gesture& gesture);
+ [[nodiscard]] std::list<NotifyArgs> handleButtonsChange(nsecs_t when, nsecs_t readTime,
+ const Gesture& gesture);
+ [[nodiscard]] std::list<NotifyArgs> handleScroll(nsecs_t when, nsecs_t readTime,
+ const Gesture& gesture);
+ [[nodiscard]] NotifyArgs handleFling(nsecs_t when, nsecs_t readTime, const Gesture& gesture);
+ [[nodiscard]] std::list<NotifyArgs> handleMultiFingerSwipe(nsecs_t when, nsecs_t readTime,
+ uint32_t fingerCount, float dx,
+ float dy);
+ [[nodiscard]] std::list<NotifyArgs> handleMultiFingerSwipeLift(nsecs_t when, nsecs_t readTime);
+ [[nodiscard]] std::list<NotifyArgs> handlePinch(nsecs_t when, nsecs_t readTime,
+ const Gesture& gesture);
+
+ NotifyMotionArgs makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action,
+ int32_t actionButton, int32_t buttonState,
+ uint32_t pointerCount,
+ const PointerProperties* pointerProperties,
+ const PointerCoords* pointerCoords, float xCursorPosition,
+ float yCursorPosition);
+
+ const int32_t mDeviceId;
+ InputReaderContext& mReaderContext;
+ std::shared_ptr<PointerControllerInterface> mPointerController;
+
+ ui::Rotation mOrientation = ui::ROTATION_0;
+ RawAbsoluteAxisInfo mXAxisInfo;
+ RawAbsoluteAxisInfo mYAxisInfo;
+
+ // The current button state according to the gestures library, but converted into MotionEvent
+ // button values (AMOTION_EVENT_BUTTON_...).
+ uint32_t mButtonState = 0;
+ nsecs_t mDownTime = 0;
+
+ MotionClassification mCurrentClassification = MotionClassification::NONE;
+ // Only used when mCurrentClassification is MULTI_FINGER_SWIPE.
+ uint32_t mSwipeFingerCount = 0;
+ static constexpr float INITIAL_PINCH_SEPARATION_PX = 200.0;
+ // Only used when mCurrentClassification is PINCH.
+ float mPinchFingerSeparation;
+ static constexpr size_t MAX_FAKE_FINGERS = 4;
+ // We never need any PointerProperties other than the finger tool type, so we can just keep a
+ // const array of them.
+ const std::array<PointerProperties, MAX_FAKE_FINGERS> mFingerProps = {{
+ {.id = 0, .toolType = AMOTION_EVENT_TOOL_TYPE_FINGER},
+ {.id = 1, .toolType = AMOTION_EVENT_TOOL_TYPE_FINGER},
+ {.id = 2, .toolType = AMOTION_EVENT_TOOL_TYPE_FINGER},
+ {.id = 3, .toolType = AMOTION_EVENT_TOOL_TYPE_FINGER},
+ }};
+ std::array<PointerCoords, MAX_FAKE_FINGERS> mFakeFingerCoords = {};
+};
+
+} // namespace android
diff --git a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp
new file mode 100644
index 0000000..2e175b8
--- /dev/null
+++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp
@@ -0,0 +1,109 @@
+/*
+ * 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 "gestures/HardwareStateConverter.h"
+
+#include <chrono>
+#include <vector>
+
+#include <linux/input-event-codes.h>
+
+namespace android {
+
+HardwareStateConverter::HardwareStateConverter(const InputDeviceContext& deviceContext)
+ : mDeviceContext(deviceContext), mTouchButtonAccumulator(deviceContext) {
+ RawAbsoluteAxisInfo slotAxisInfo;
+ deviceContext.getAbsoluteAxisInfo(ABS_MT_SLOT, &slotAxisInfo);
+ if (!slotAxisInfo.valid || slotAxisInfo.maxValue <= 0) {
+ ALOGW("Touchpad \"%s\" doesn't have a valid ABS_MT_SLOT axis, and probably won't work "
+ "properly.",
+ deviceContext.getName().c_str());
+ }
+ mMotionAccumulator.configure(deviceContext, slotAxisInfo.maxValue + 1, true);
+ mTouchButtonAccumulator.configure();
+}
+
+std::optional<SelfContainedHardwareState> HardwareStateConverter::processRawEvent(
+ const RawEvent* rawEvent) {
+ std::optional<SelfContainedHardwareState> out;
+ if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
+ out = produceHardwareState(rawEvent->when);
+ mMotionAccumulator.finishSync();
+ mMscTimestamp = 0;
+ }
+ if (rawEvent->type == EV_MSC && rawEvent->code == MSC_TIMESTAMP) {
+ mMscTimestamp = rawEvent->value;
+ }
+ mCursorButtonAccumulator.process(rawEvent);
+ mMotionAccumulator.process(rawEvent);
+ mTouchButtonAccumulator.process(rawEvent);
+ return out;
+}
+
+SelfContainedHardwareState HardwareStateConverter::produceHardwareState(nsecs_t when) {
+ SelfContainedHardwareState schs;
+ // The gestures library uses doubles to represent timestamps in seconds.
+ schs.state.timestamp = std::chrono::duration<stime_t>(std::chrono::nanoseconds(when)).count();
+ schs.state.msc_timestamp =
+ std::chrono::duration<stime_t>(std::chrono::microseconds(mMscTimestamp)).count();
+
+ schs.state.buttons_down = 0;
+ if (mCursorButtonAccumulator.isLeftPressed()) {
+ schs.state.buttons_down |= GESTURES_BUTTON_LEFT;
+ }
+ if (mCursorButtonAccumulator.isMiddlePressed()) {
+ schs.state.buttons_down |= GESTURES_BUTTON_MIDDLE;
+ }
+ if (mCursorButtonAccumulator.isRightPressed()) {
+ schs.state.buttons_down |= GESTURES_BUTTON_RIGHT;
+ }
+ if (mCursorButtonAccumulator.isBackPressed() || mCursorButtonAccumulator.isSidePressed()) {
+ schs.state.buttons_down |= GESTURES_BUTTON_BACK;
+ }
+ if (mCursorButtonAccumulator.isForwardPressed() || mCursorButtonAccumulator.isExtraPressed()) {
+ schs.state.buttons_down |= GESTURES_BUTTON_FORWARD;
+ }
+
+ schs.fingers.clear();
+ for (size_t i = 0; i < mMotionAccumulator.getSlotCount(); i++) {
+ MultiTouchMotionAccumulator::Slot slot = mMotionAccumulator.getSlot(i);
+ if (slot.isInUse()) {
+ FingerState& fingerState = schs.fingers.emplace_back();
+ fingerState = {};
+ fingerState.touch_major = slot.getTouchMajor();
+ fingerState.touch_minor = slot.getTouchMinor();
+ fingerState.width_major = slot.getToolMajor();
+ fingerState.width_minor = slot.getToolMinor();
+ fingerState.pressure = slot.getPressure();
+ fingerState.orientation = slot.getOrientation();
+ fingerState.position_x = slot.getX();
+ fingerState.position_y = slot.getY();
+ fingerState.tracking_id = slot.getTrackingId();
+ }
+ }
+ schs.state.fingers = schs.fingers.data();
+ schs.state.finger_cnt = schs.fingers.size();
+ schs.state.touch_cnt = mTouchButtonAccumulator.getTouchCount();
+ return schs;
+}
+
+void HardwareStateConverter::reset() {
+ mCursorButtonAccumulator.reset(mDeviceContext);
+ mTouchButtonAccumulator.reset();
+ mMscTimestamp = 0;
+}
+
+} // namespace android
diff --git a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h
new file mode 100644
index 0000000..8831299
--- /dev/null
+++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h
@@ -0,0 +1,58 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <optional>
+
+#include <utils/Timers.h>
+
+#include "EventHub.h"
+#include "InputDevice.h"
+#include "accumulator/CursorButtonAccumulator.h"
+#include "accumulator/MultiTouchMotionAccumulator.h"
+#include "accumulator/TouchButtonAccumulator.h"
+
+#include "include/gestures.h"
+
+namespace android {
+
+// A HardwareState struct, but bundled with a vector to contain its FingerStates, so you don't have
+// to worry about where that memory is allocated.
+struct SelfContainedHardwareState {
+ HardwareState state;
+ std::vector<FingerState> fingers;
+};
+
+// Converts RawEvents into the HardwareState structs used by the gestures library.
+class HardwareStateConverter {
+public:
+ HardwareStateConverter(const InputDeviceContext& deviceContext);
+
+ std::optional<SelfContainedHardwareState> processRawEvent(const RawEvent* event);
+ void reset();
+
+private:
+ SelfContainedHardwareState produceHardwareState(nsecs_t when);
+
+ const InputDeviceContext& mDeviceContext;
+ CursorButtonAccumulator mCursorButtonAccumulator;
+ MultiTouchMotionAccumulator mMotionAccumulator;
+ TouchButtonAccumulator mTouchButtonAccumulator;
+ int32_t mMscTimestamp = 0;
+};
+
+} // namespace android
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index 53d821f..58a5c31 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -44,6 +44,8 @@
"FakeInputReaderPolicy.cpp",
"FakePointerController.cpp",
"FocusResolver_test.cpp",
+ "GestureConverter_test.cpp",
+ "HardwareStateConverter_test.cpp",
"InputMapperTest.cpp",
"InputProcessor_test.cpp",
"InputProcessorConverter_test.cpp",
diff --git a/services/inputflinger/tests/FakeEventHub.cpp b/services/inputflinger/tests/FakeEventHub.cpp
index 289a780..6ac0bfb 100644
--- a/services/inputflinger/tests/FakeEventHub.cpp
+++ b/services/inputflinger/tests/FakeEventHub.cpp
@@ -120,8 +120,8 @@
getDevice(deviceId)->keyCodeStates.replaceValueFor(keyCode, state);
}
-void FakeEventHub::setCountryCode(int32_t deviceId, InputDeviceCountryCode countryCode) {
- getDevice(deviceId)->countryCode = countryCode;
+void FakeEventHub::setRawLayoutInfo(int32_t deviceId, RawLayoutInfo info) {
+ getDevice(deviceId)->layoutInfo = info;
}
void FakeEventHub::setScanCodeState(int32_t deviceId, int32_t scanCode, int32_t state) {
@@ -389,9 +389,9 @@
return AKEY_STATE_UNKNOWN;
}
-InputDeviceCountryCode FakeEventHub::getCountryCode(int32_t deviceId) const {
+std::optional<RawLayoutInfo> FakeEventHub::getRawLayoutInfo(int32_t deviceId) const {
Device* device = getDevice(deviceId);
- return device ? device->countryCode : InputDeviceCountryCode::INVALID;
+ return device ? device->layoutInfo : std::nullopt;
}
int32_t FakeEventHub::getKeyCodeState(int32_t deviceId, int32_t keyCode) const {
diff --git a/services/inputflinger/tests/FakeEventHub.h b/services/inputflinger/tests/FakeEventHub.h
index fb3c859..72f8ac0 100644
--- a/services/inputflinger/tests/FakeEventHub.h
+++ b/services/inputflinger/tests/FakeEventHub.h
@@ -30,10 +30,6 @@
#include <utils/Errors.h>
#include <utils/KeyedVector.h>
-#include "android/hardware/input/InputDeviceCountryCode.h"
-
-using android::hardware::input::InputDeviceCountryCode;
-
namespace android {
class FakeEventHub : public EventHubInterface {
@@ -67,7 +63,7 @@
BitArray<MSC_MAX> mscBitmask;
std::vector<VirtualKeyDefinition> virtualKeys;
bool enabled;
- InputDeviceCountryCode countryCode;
+ std::optional<RawLayoutInfo> layoutInfo;
status_t enable() {
enabled = true;
@@ -124,7 +120,7 @@
void addRelativeAxis(int32_t deviceId, int32_t axis);
void setAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t value);
- void setCountryCode(int32_t deviceId, InputDeviceCountryCode countryCode);
+ void setRawLayoutInfo(int32_t deviceId, RawLayoutInfo info);
void setKeyCodeState(int32_t deviceId, int32_t keyCode, int32_t state);
void setScanCodeState(int32_t deviceId, int32_t scanCode, int32_t state);
@@ -180,7 +176,7 @@
std::vector<RawEvent> getEvents(int) override;
std::vector<TouchVideoFrame> getVideoFrames(int32_t deviceId) override;
int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const override;
- InputDeviceCountryCode getCountryCode(int32_t deviceId) const override;
+ std::optional<RawLayoutInfo> getRawLayoutInfo(int32_t deviceId) const override;
int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const override;
int32_t getSwitchState(int32_t deviceId, int32_t sw) const override;
status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const override;
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
index dc7e581..bb8a30e 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
@@ -136,6 +136,11 @@
mConfig.uniqueIdAssociations.insert({inputUniqueId, displayUniqueId});
}
+void FakeInputReaderPolicy::addKeyboardLayoutAssociation(const std::string& inputUniqueId,
+ const KeyboardLayoutInfo& layoutInfo) {
+ mConfig.keyboardLayoutAssociations.insert({inputUniqueId, layoutInfo});
+}
+
void FakeInputReaderPolicy::addDisabledDevice(int32_t deviceId) {
mConfig.disabledDevices.insert(deviceId);
}
@@ -196,6 +201,10 @@
mConfig.wheelVelocityControlParameters = params;
}
+void FakeInputReaderPolicy::setStylusButtonMotionEventsEnabled(bool enabled) {
+ mConfig.stylusButtonMotionEventsEnabled = enabled;
+}
+
void FakeInputReaderPolicy::getReaderConfiguration(InputReaderConfiguration* outConfig) {
*outConfig = mConfig;
}
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h
index faa9c01..9ec3217 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.h
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.h
@@ -58,6 +58,8 @@
void addDeviceTypeAssociation(const std::string& inputPort, const std::string& type);
void addInputUniqueIdAssociation(const std::string& inputUniqueId,
const std::string& displayUniqueId);
+ void addKeyboardLayoutAssociation(const std::string& inputUniqueId,
+ const KeyboardLayoutInfo& layoutInfo);
void addDisabledDevice(int32_t deviceId);
void removeDisabledDevice(int32_t deviceId);
void setPointerController(std::shared_ptr<FakePointerController> controller);
@@ -73,6 +75,7 @@
float getPointerGestureMovementSpeedRatio();
float getPointerGestureZoomSpeedRatio();
void setVelocityControlParams(const VelocityControlParameters& params);
+ void setStylusButtonMotionEventsEnabled(bool enabled);
private:
void getReaderConfiguration(InputReaderConfiguration* outConfig) override;
diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp
index 635366b..ab7879f 100644
--- a/services/inputflinger/tests/FakePointerController.cpp
+++ b/services/inputflinger/tests/FakePointerController.cpp
@@ -16,6 +16,8 @@
#include "FakePointerController.h"
+#include <gtest/gtest.h>
+
namespace android {
void FakePointerController::setBounds(float minX, float minY, float maxX, float maxY) {
@@ -56,6 +58,13 @@
mDisplayId = viewport.displayId;
}
+void FakePointerController::assertPosition(float x, float y) {
+ float actualX, actualY;
+ getPosition(&actualX, &actualY);
+ ASSERT_NEAR(x, actualX, 1);
+ ASSERT_NEAR(y, actualY, 1);
+}
+
bool FakePointerController::getBounds(float* outMinX, float* outMinY, float* outMaxX,
float* outMaxY) const {
*outMinX = mMinX;
diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h
index f00870f..d10cbcd 100644
--- a/services/inputflinger/tests/FakePointerController.h
+++ b/services/inputflinger/tests/FakePointerController.h
@@ -38,6 +38,8 @@
int32_t getDisplayId() const override;
void setDisplayViewport(const DisplayViewport& viewport) override;
+ void assertPosition(float x, float y);
+
private:
bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const override;
void move(float deltaX, float deltaY) override;
diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp
new file mode 100644
index 0000000..36a39bb
--- /dev/null
+++ b/services/inputflinger/tests/GestureConverter_test.cpp
@@ -0,0 +1,784 @@
+/*
+ * 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 <memory>
+
+#include <EventHub.h>
+#include <gestures/GestureConverter.h>
+#include <gtest/gtest.h>
+
+#include "FakeEventHub.h"
+#include "FakeInputReaderPolicy.h"
+#include "FakePointerController.h"
+#include "InstrumentedInputReader.h"
+#include "NotifyArgs.h"
+#include "TestConstants.h"
+#include "TestInputListener.h"
+#include "TestInputListenerMatchers.h"
+#include "include/gestures.h"
+#include "ui/Rotation.h"
+
+namespace android {
+
+using testing::AllOf;
+
+class GestureConverterTest : public testing::Test {
+protected:
+ static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000;
+ static constexpr int32_t EVENTHUB_ID = 1;
+ static constexpr stime_t ARBITRARY_GESTURE_TIME = 1.2;
+ static constexpr float POINTER_X = 500;
+ static constexpr float POINTER_Y = 200;
+
+ void SetUp() {
+ mFakeEventHub = std::make_unique<FakeEventHub>();
+ mFakePolicy = sp<FakeInputReaderPolicy>::make();
+ mFakeListener = std::make_unique<TestInputListener>();
+ mReader = std::make_unique<InstrumentedInputReader>(mFakeEventHub, mFakePolicy,
+ *mFakeListener);
+ mDevice = newDevice();
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, -500, 500, 0, 0, 20);
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, -500, 500, 0, 0, 20);
+
+ mFakePointerController = std::make_shared<FakePointerController>();
+ mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
+ mFakePointerController->setPosition(POINTER_X, POINTER_Y);
+ mFakePolicy->setPointerController(mFakePointerController);
+ }
+
+ std::shared_ptr<InputDevice> newDevice() {
+ InputDeviceIdentifier identifier;
+ identifier.name = "device";
+ identifier.location = "USB1";
+ identifier.bus = 0;
+ std::shared_ptr<InputDevice> device =
+ std::make_shared<InputDevice>(mReader->getContext(), DEVICE_ID, /* generation= */ 2,
+ identifier);
+ mReader->pushNextDevice(device);
+ mFakeEventHub->addDevice(EVENTHUB_ID, identifier.name, InputDeviceClass::TOUCHPAD,
+ identifier.bus);
+ mReader->loopOnce();
+ return device;
+ }
+
+ std::shared_ptr<FakeEventHub> mFakeEventHub;
+ sp<FakeInputReaderPolicy> mFakePolicy;
+ std::unique_ptr<TestInputListener> mFakeListener;
+ std::unique_ptr<InstrumentedInputReader> mReader;
+ std::shared_ptr<InputDevice> mDevice;
+ std::shared_ptr<FakePointerController> mFakePointerController;
+};
+
+TEST_F(GestureConverterTest, Move) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+
+ Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
+ ASSERT_EQ(1u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+ WithCoords(POINTER_X - 5, POINTER_Y + 10), WithRelativeMotion(-5, 10),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), WithButtonState(0),
+ WithPressure(0.0f)));
+
+ ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 5, POINTER_Y + 10));
+}
+
+TEST_F(GestureConverterTest, Move_Rotated) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setOrientation(ui::ROTATION_90);
+
+ Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
+ ASSERT_EQ(1u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+ WithCoords(POINTER_X + 10, POINTER_Y + 5), WithRelativeMotion(10, 5),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), WithButtonState(0),
+ WithPressure(0.0f)));
+
+ ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X + 10, POINTER_Y + 5));
+}
+
+TEST_F(GestureConverterTest, ButtonsChange) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+
+ // Press left and right buttons at once
+ Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* down= */ GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT,
+ /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture);
+ ASSERT_EQ(3u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+ WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
+ AMOTION_EVENT_BUTTON_SECONDARY),
+ WithCoords(POINTER_X, POINTER_Y),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+ WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+ WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+ WithCoords(POINTER_X, POINTER_Y),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+ WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY),
+ WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
+ AMOTION_EVENT_BUTTON_SECONDARY),
+ WithCoords(POINTER_X, POINTER_Y),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+
+ // Then release the left button
+ Gesture leftUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT,
+ /* is_tap= */ false);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, leftUpGesture);
+ ASSERT_EQ(1u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+ WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+ WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY),
+ WithCoords(POINTER_X, POINTER_Y),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+
+ // Finally release the right button
+ Gesture rightUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_RIGHT,
+ /* is_tap= */ false);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, rightUpGesture);
+ ASSERT_EQ(2u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+ WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0),
+ WithCoords(POINTER_X, POINTER_Y),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
+ WithCoords(POINTER_X, POINTER_Y),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+}
+
+TEST_F(GestureConverterTest, DragWithButton) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+
+ // Press the button
+ Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* down= */ GESTURES_BUTTON_LEFT, /* up= */ GESTURES_BUTTON_NONE,
+ /* is_tap= */ false);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture);
+ ASSERT_EQ(2u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+ WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+ WithCoords(POINTER_X, POINTER_Y),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+ WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+ WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+ WithCoords(POINTER_X, POINTER_Y),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+
+ // Move
+ Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
+ ASSERT_EQ(1u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithCoords(POINTER_X - 5, POINTER_Y + 10), WithRelativeMotion(-5, 10),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER),
+ WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f)));
+
+ ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 5, POINTER_Y + 10));
+
+ // Release the button
+ Gesture upGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT,
+ /* is_tap= */ false);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, upGesture);
+ ASSERT_EQ(2u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+ WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
+ WithCoords(POINTER_X - 5, POINTER_Y + 10),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
+ WithCoords(POINTER_X - 5, POINTER_Y + 10),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+}
+
+TEST_F(GestureConverterTest, Scroll) {
+ const nsecs_t downTime = 12345;
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+
+ Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 10);
+ std::list<NotifyArgs> args = converter.handleGesture(downTime, READ_TIME, startGesture);
+ ASSERT_EQ(2u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y),
+ WithGestureScrollDistance(0, 0, EPSILON),
+ WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), WithDownTime(downTime)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithCoords(POINTER_X, POINTER_Y - 10),
+ WithGestureScrollDistance(0, 10, EPSILON),
+ WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+
+ Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 5);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithCoords(POINTER_X, POINTER_Y - 15),
+ WithGestureScrollDistance(0, 5, EPSILON),
+ WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+
+ Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
+ GESTURES_FLING_START);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+ WithCoords(POINTER_X, POINTER_Y - 15),
+ WithGestureScrollDistance(0, 0, EPSILON),
+ WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+}
+
+TEST_F(GestureConverterTest, Scroll_Rotated) {
+ const nsecs_t downTime = 12345;
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setOrientation(ui::ROTATION_90);
+
+ Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 10);
+ std::list<NotifyArgs> args = converter.handleGesture(downTime, READ_TIME, startGesture);
+ ASSERT_EQ(2u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y),
+ WithGestureScrollDistance(0, 0, EPSILON),
+ WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), WithDownTime(downTime)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithCoords(POINTER_X - 10, POINTER_Y),
+ WithGestureScrollDistance(0, 10, EPSILON),
+ WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+
+ Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 5);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithCoords(POINTER_X - 15, POINTER_Y),
+ WithGestureScrollDistance(0, 5, EPSILON),
+ WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+
+ Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
+ GESTURES_FLING_START);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+ WithCoords(POINTER_X - 15, POINTER_Y),
+ WithGestureScrollDistance(0, 0, EPSILON),
+ WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+}
+
+TEST_F(GestureConverterTest, Scroll_ClearsClassificationAndOffsetsAfterGesture) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+
+ Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 10);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+
+ Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 5);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+
+ Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
+ GESTURES_FLING_START);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+
+ Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionClassification(MotionClassification::NONE),
+ WithGestureScrollDistance(0, 0, EPSILON)));
+}
+
+TEST_F(GestureConverterTest, ThreeFingerSwipe_ClearsClassificationAndOffsetsAfterGesture) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+
+ Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0,
+ /* dy= */ 0);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+
+ Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
+
+ Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ -5,
+ /* dy= */ 10);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionClassification(MotionClassification::NONE),
+ WithGestureOffset(0, 0, EPSILON)));
+}
+
+TEST_F(GestureConverterTest, ThreeFingerSwipe_Vertical) {
+ // The gestures library will "lock" a swipe into the dimension it starts in. For example, if you
+ // start swiping up and then start moving left or right, it'll return gesture events with only Y
+ // deltas until you lift your fingers and start swiping again. That's why each of these tests
+ // only checks movement in one dimension.
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+
+ Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0,
+ /* dy= */ 10);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+ ASSERT_EQ(4u, args.size());
+
+ // Three fake fingers should be created. We don't actually care where they are, so long as they
+ // move appropriately.
+ NotifyMotionArgs arg = std::get<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(1u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+ PointerCoords finger0Start = arg.pointerCoords[0];
+ args.pop_front();
+ arg = std::get<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(2u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+ PointerCoords finger1Start = arg.pointerCoords[1];
+ args.pop_front();
+ arg = std::get<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(3u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+ PointerCoords finger2Start = arg.pointerCoords[2];
+ args.pop_front();
+
+ arg = std::get<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithGestureOffset(0, -0.01, EPSILON),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(3u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+ EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX());
+ EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX());
+ EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX());
+ EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY() - 10);
+ EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY() - 10);
+ EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY() - 10);
+
+ Gesture continueGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* dx= */ 0, /* dy= */ 5);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+ ASSERT_EQ(1u, args.size());
+ arg = std::get<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithGestureOffset(0, -0.005, EPSILON),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(3u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+ EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX());
+ EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX());
+ EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX());
+ EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY() - 15);
+ EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY() - 15);
+ EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY() - 15);
+
+ Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
+ ASSERT_EQ(3u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+ 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(3u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+ 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(2u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(1u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+}
+
+TEST_F(GestureConverterTest, ThreeFingerSwipe_Rotated) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setOrientation(ui::ROTATION_90);
+
+ Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0,
+ /* dy= */ 10);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+ ASSERT_EQ(4u, args.size());
+
+ // Three fake fingers should be created. We don't actually care where they are, so long as they
+ // move appropriately.
+ NotifyMotionArgs arg = std::get<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON),
+ WithPointerCount(1u)));
+ PointerCoords finger0Start = arg.pointerCoords[0];
+ args.pop_front();
+ arg = std::get<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u)));
+ PointerCoords finger1Start = arg.pointerCoords[1];
+ args.pop_front();
+ arg = std::get<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u)));
+ PointerCoords finger2Start = arg.pointerCoords[2];
+ args.pop_front();
+
+ arg = std::get<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithGestureOffset(0, -0.01, EPSILON), WithPointerCount(3u)));
+ EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() - 10);
+ EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() - 10);
+ EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() - 10);
+ EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY());
+ EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY());
+ EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY());
+
+ Gesture continueGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* dx= */ 0, /* dy= */ 5);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+ ASSERT_EQ(1u, args.size());
+ arg = std::get<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithGestureOffset(0, -0.005, EPSILON), WithPointerCount(3u)));
+ EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() - 15);
+ EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() - 15);
+ EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() - 15);
+ EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY());
+ EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY());
+ EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY());
+
+ Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
+ ASSERT_EQ(3u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+ 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+ 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
+ WithPointerCount(1u)));
+}
+
+TEST_F(GestureConverterTest, FourFingerSwipe_Horizontal) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+
+ Gesture startGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* dx= */ 10, /* dy= */ 0);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+ ASSERT_EQ(5u, args.size());
+
+ // Four fake fingers should be created. We don't actually care where they are, so long as they
+ // move appropriately.
+ NotifyMotionArgs arg = std::get<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(1u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+ PointerCoords finger0Start = arg.pointerCoords[0];
+ args.pop_front();
+ arg = std::get<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(2u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+ PointerCoords finger1Start = arg.pointerCoords[1];
+ args.pop_front();
+ arg = std::get<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(3u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+ PointerCoords finger2Start = arg.pointerCoords[2];
+ args.pop_front();
+ arg = std::get<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ 3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(4u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+ PointerCoords finger3Start = arg.pointerCoords[3];
+ args.pop_front();
+
+ arg = std::get<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithGestureOffset(0.01, 0, EPSILON),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(4u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+ EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() + 10);
+ EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() + 10);
+ EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() + 10);
+ EXPECT_EQ(arg.pointerCoords[3].getX(), finger3Start.getX() + 10);
+ EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY());
+ EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY());
+ EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY());
+ EXPECT_EQ(arg.pointerCoords[3].getY(), finger3Start.getY());
+
+ Gesture continueGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* dx= */ 5, /* dy= */ 0);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+ ASSERT_EQ(1u, args.size());
+ arg = std::get<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithGestureOffset(0.005, 0, EPSILON),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(4u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+ EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() + 15);
+ EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() + 15);
+ EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() + 15);
+ EXPECT_EQ(arg.pointerCoords[3].getX(), finger3Start.getX() + 15);
+ EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY());
+ EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY());
+ EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY());
+ EXPECT_EQ(arg.pointerCoords[3].getY(), finger3Start.getY());
+
+ Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
+ ASSERT_EQ(4u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+ 3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(4u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+ 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(3u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+ 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(2u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(1u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+}
+
+TEST_F(GestureConverterTest, Pinch_Inwards) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+
+ Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+ GESTURES_ZOOM_START);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+ ASSERT_EQ(2u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+ WithMotionClassification(MotionClassification::PINCH),
+ WithGesturePinchScaleFactor(1.0f, EPSILON),
+ WithCoords(POINTER_X - 100, POINTER_Y), WithPointerCount(1u),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithMotionClassification(MotionClassification::PINCH),
+ WithGesturePinchScaleFactor(1.0f, EPSILON),
+ WithPointerCoords(1, POINTER_X + 100, POINTER_Y), WithPointerCount(2u),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+
+ Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* dz= */ 0.8, GESTURES_ZOOM_UPDATE);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithMotionClassification(MotionClassification::PINCH),
+ WithGesturePinchScaleFactor(0.8f, EPSILON),
+ WithPointerCoords(0, POINTER_X - 80, POINTER_Y),
+ WithPointerCoords(1, POINTER_X + 80, POINTER_Y), WithPointerCount(2u),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+
+ Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+ GESTURES_ZOOM_END);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
+ ASSERT_EQ(2u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+ 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithMotionClassification(MotionClassification::PINCH),
+ WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+ WithMotionClassification(MotionClassification::PINCH),
+ WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+}
+
+TEST_F(GestureConverterTest, Pinch_Outwards) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+
+ Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+ GESTURES_ZOOM_START);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+ ASSERT_EQ(2u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+ WithMotionClassification(MotionClassification::PINCH),
+ WithGesturePinchScaleFactor(1.0f, EPSILON),
+ WithCoords(POINTER_X - 100, POINTER_Y), WithPointerCount(1u),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithMotionClassification(MotionClassification::PINCH),
+ WithGesturePinchScaleFactor(1.0f, EPSILON),
+ WithPointerCoords(1, POINTER_X + 100, POINTER_Y), WithPointerCount(2u),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+
+ Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* dz= */ 1.2, GESTURES_ZOOM_UPDATE);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithMotionClassification(MotionClassification::PINCH),
+ WithGesturePinchScaleFactor(1.2f, EPSILON),
+ WithPointerCoords(0, POINTER_X - 120, POINTER_Y),
+ WithPointerCoords(1, POINTER_X + 120, POINTER_Y), WithPointerCount(2u),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+
+ Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+ GESTURES_ZOOM_END);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
+ ASSERT_EQ(2u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+ 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithMotionClassification(MotionClassification::PINCH),
+ WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+ WithMotionClassification(MotionClassification::PINCH),
+ WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+}
+
+TEST_F(GestureConverterTest, Pinch_ClearsClassificationAndScaleFactorAfterGesture) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+
+ Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+ GESTURES_ZOOM_START);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+
+ Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* dz= */ 1.2, GESTURES_ZOOM_UPDATE);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
+
+ Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+ GESTURES_ZOOM_END);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
+
+ Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionClassification(MotionClassification::NONE),
+ WithGesturePinchScaleFactor(0, EPSILON)));
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/HardwareStateConverter_test.cpp b/services/inputflinger/tests/HardwareStateConverter_test.cpp
new file mode 100644
index 0000000..7921881
--- /dev/null
+++ b/services/inputflinger/tests/HardwareStateConverter_test.cpp
@@ -0,0 +1,219 @@
+/*
+ * 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 <EventHub.h>
+#include <gestures/HardwareStateConverter.h>
+#include <gtest/gtest.h>
+#include <linux/input-event-codes.h>
+
+#include "FakeEventHub.h"
+#include "FakeInputReaderPolicy.h"
+#include "InstrumentedInputReader.h"
+#include "TestConstants.h"
+#include "TestInputListener.h"
+
+namespace android {
+
+class HardwareStateConverterTest : public testing::Test {
+protected:
+ static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000;
+ static constexpr int32_t EVENTHUB_ID = 1;
+
+ void SetUp() {
+ mFakeEventHub = std::make_unique<FakeEventHub>();
+ mFakePolicy = sp<FakeInputReaderPolicy>::make();
+ mFakeListener = std::make_unique<TestInputListener>();
+ mReader = std::make_unique<InstrumentedInputReader>(mFakeEventHub, mFakePolicy,
+ *mFakeListener);
+ mDevice = newDevice();
+
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_SLOT, 0, 7, 0, 0, 0);
+ }
+
+ std::shared_ptr<InputDevice> newDevice() {
+ InputDeviceIdentifier identifier;
+ identifier.name = "device";
+ identifier.location = "USB1";
+ identifier.bus = 0;
+ std::shared_ptr<InputDevice> device =
+ std::make_shared<InputDevice>(mReader->getContext(), DEVICE_ID, /* generation= */ 2,
+ identifier);
+ mReader->pushNextDevice(device);
+ mFakeEventHub->addDevice(EVENTHUB_ID, identifier.name, InputDeviceClass::TOUCHPAD,
+ identifier.bus);
+ mReader->loopOnce();
+ return device;
+ }
+
+ void processAxis(HardwareStateConverter& conv, nsecs_t when, int32_t type, int32_t code,
+ int32_t value) {
+ RawEvent event;
+ event.when = when;
+ event.readTime = READ_TIME;
+ event.deviceId = EVENTHUB_ID;
+ event.type = type;
+ event.code = code;
+ event.value = value;
+ std::optional<SelfContainedHardwareState> schs = conv.processRawEvent(&event);
+ EXPECT_FALSE(schs.has_value());
+ }
+
+ std::optional<SelfContainedHardwareState> processSync(HardwareStateConverter& conv,
+ nsecs_t when) {
+ RawEvent event;
+ event.when = when;
+ event.readTime = READ_TIME;
+ event.deviceId = EVENTHUB_ID;
+ event.type = EV_SYN;
+ event.code = SYN_REPORT;
+ event.value = 0;
+ return conv.processRawEvent(&event);
+ }
+
+ std::shared_ptr<FakeEventHub> mFakeEventHub;
+ sp<FakeInputReaderPolicy> mFakePolicy;
+ std::unique_ptr<TestInputListener> mFakeListener;
+ std::unique_ptr<InstrumentedInputReader> mReader;
+ std::shared_ptr<InputDevice> mDevice;
+};
+
+TEST_F(HardwareStateConverterTest, OneFinger) {
+ const nsecs_t time = 1500000000;
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ HardwareStateConverter conv(deviceContext);
+
+ processAxis(conv, time, EV_ABS, ABS_MT_SLOT, 0);
+ processAxis(conv, time, EV_ABS, ABS_MT_TRACKING_ID, 123);
+ processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, 50);
+ processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 100);
+ processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MAJOR, 5);
+ processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MINOR, 4);
+ processAxis(conv, time, EV_ABS, ABS_MT_PRESSURE, 42);
+ processAxis(conv, time, EV_ABS, ABS_MT_ORIENTATION, 2);
+
+ processAxis(conv, time, EV_ABS, ABS_X, 50);
+ processAxis(conv, time, EV_ABS, ABS_Y, 100);
+ processAxis(conv, time, EV_ABS, ABS_PRESSURE, 42);
+
+ processAxis(conv, time, EV_KEY, BTN_TOUCH, 1);
+ processAxis(conv, time, EV_KEY, BTN_TOOL_FINGER, 1);
+ std::optional<SelfContainedHardwareState> schs = processSync(conv, time);
+
+ ASSERT_TRUE(schs.has_value());
+ const HardwareState& state = schs->state;
+ EXPECT_NEAR(1.5, state.timestamp, EPSILON);
+ EXPECT_EQ(0, state.buttons_down);
+ EXPECT_EQ(1, state.touch_cnt);
+
+ ASSERT_EQ(1, state.finger_cnt);
+ const FingerState& finger = state.fingers[0];
+ EXPECT_EQ(123, finger.tracking_id);
+ EXPECT_NEAR(50, finger.position_x, EPSILON);
+ EXPECT_NEAR(100, finger.position_y, EPSILON);
+ EXPECT_NEAR(5, finger.touch_major, EPSILON);
+ EXPECT_NEAR(4, finger.touch_minor, EPSILON);
+ EXPECT_NEAR(42, finger.pressure, EPSILON);
+ EXPECT_NEAR(2, finger.orientation, EPSILON);
+ EXPECT_EQ(0u, finger.flags);
+
+ EXPECT_EQ(0, state.rel_x);
+ EXPECT_EQ(0, state.rel_y);
+ EXPECT_EQ(0, state.rel_wheel);
+ EXPECT_EQ(0, state.rel_wheel_hi_res);
+ EXPECT_EQ(0, state.rel_hwheel);
+ EXPECT_NEAR(0.0, state.msc_timestamp, EPSILON);
+}
+
+TEST_F(HardwareStateConverterTest, TwoFingers) {
+ const nsecs_t time = ARBITRARY_TIME;
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ HardwareStateConverter conv(deviceContext);
+
+ processAxis(conv, time, EV_ABS, ABS_MT_SLOT, 0);
+ processAxis(conv, time, EV_ABS, ABS_MT_TRACKING_ID, 123);
+ processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, 50);
+ processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 100);
+ processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MAJOR, 5);
+ processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MINOR, 4);
+ processAxis(conv, time, EV_ABS, ABS_MT_PRESSURE, 42);
+ processAxis(conv, time, EV_ABS, ABS_MT_ORIENTATION, 2);
+
+ processAxis(conv, time, EV_ABS, ABS_MT_SLOT, 1);
+ processAxis(conv, time, EV_ABS, ABS_MT_TRACKING_ID, 456);
+ processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, -20);
+ processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 40);
+ processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MAJOR, 8);
+ processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MINOR, 7);
+ processAxis(conv, time, EV_ABS, ABS_MT_PRESSURE, 21);
+ processAxis(conv, time, EV_ABS, ABS_MT_ORIENTATION, 1);
+
+ processAxis(conv, time, EV_ABS, ABS_X, 50);
+ processAxis(conv, time, EV_ABS, ABS_Y, 100);
+ processAxis(conv, time, EV_ABS, ABS_PRESSURE, 42);
+
+ processAxis(conv, time, EV_KEY, BTN_TOUCH, 1);
+ processAxis(conv, time, EV_KEY, BTN_TOOL_DOUBLETAP, 1);
+ std::optional<SelfContainedHardwareState> schs = processSync(conv, time);
+
+ ASSERT_TRUE(schs.has_value());
+ ASSERT_EQ(2, schs->state.finger_cnt);
+ const FingerState& finger1 = schs->state.fingers[0];
+ EXPECT_EQ(123, finger1.tracking_id);
+ EXPECT_NEAR(50, finger1.position_x, EPSILON);
+ EXPECT_NEAR(100, finger1.position_y, EPSILON);
+ EXPECT_NEAR(5, finger1.touch_major, EPSILON);
+ EXPECT_NEAR(4, finger1.touch_minor, EPSILON);
+ EXPECT_NEAR(42, finger1.pressure, EPSILON);
+ EXPECT_NEAR(2, finger1.orientation, EPSILON);
+ EXPECT_EQ(0u, finger1.flags);
+
+ const FingerState& finger2 = schs->state.fingers[1];
+ EXPECT_EQ(456, finger2.tracking_id);
+ EXPECT_NEAR(-20, finger2.position_x, EPSILON);
+ EXPECT_NEAR(40, finger2.position_y, EPSILON);
+ EXPECT_NEAR(8, finger2.touch_major, EPSILON);
+ EXPECT_NEAR(7, finger2.touch_minor, EPSILON);
+ EXPECT_NEAR(21, finger2.pressure, EPSILON);
+ EXPECT_NEAR(1, finger2.orientation, EPSILON);
+ EXPECT_EQ(0u, finger2.flags);
+}
+
+TEST_F(HardwareStateConverterTest, ButtonPressed) {
+ const nsecs_t time = ARBITRARY_TIME;
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ HardwareStateConverter conv(deviceContext);
+
+ processAxis(conv, time, EV_KEY, BTN_LEFT, 1);
+ std::optional<SelfContainedHardwareState> schs = processSync(conv, time);
+
+ ASSERT_TRUE(schs.has_value());
+ EXPECT_EQ(GESTURES_BUTTON_LEFT, schs->state.buttons_down);
+}
+
+TEST_F(HardwareStateConverterTest, MscTimestamp) {
+ const nsecs_t time = ARBITRARY_TIME;
+ mFakeEventHub->setMscEvent(EVENTHUB_ID, MSC_TIMESTAMP);
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ HardwareStateConverter conv(deviceContext);
+
+ processAxis(conv, time, EV_MSC, MSC_TIMESTAMP, 1200000);
+ std::optional<SelfContainedHardwareState> schs = processSync(conv, time);
+
+ ASSERT_TRUE(schs.has_value());
+ EXPECT_NEAR(1.2, schs->state.msc_timestamp, EPSILON);
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 41c174a..864aaea 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -29,6 +29,7 @@
#include <sys/epoll.h>
#include <cinttypes>
+#include <compare>
#include <thread>
#include <unordered_set>
#include <vector>
@@ -56,12 +57,15 @@
static constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT;
static constexpr int32_t SECOND_DISPLAY_ID = 1;
+static constexpr int32_t ACTION_OUTSIDE = AMOTION_EVENT_ACTION_OUTSIDE;
static constexpr int32_t POINTER_1_DOWN =
AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
static constexpr int32_t POINTER_2_DOWN =
AMOTION_EVENT_ACTION_POINTER_DOWN | (2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
static constexpr int32_t POINTER_3_DOWN =
AMOTION_EVENT_ACTION_POINTER_DOWN | (3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+static constexpr int32_t POINTER_0_UP =
+ AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
static constexpr int32_t POINTER_1_UP =
AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
@@ -81,9 +85,13 @@
static constexpr std::chrono::duration STALE_EVENT_TIMEOUT = 1000ms;
+static constexpr int expectedWallpaperFlags =
+ AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
+
struct PointF {
float x;
float y;
+ auto operator<=>(const PointF&) const = default;
};
/**
@@ -137,6 +145,24 @@
return arg.getSource() == source;
}
+MATCHER_P2(WithCoords, x, y, "MotionEvent with specified coordinates") {
+ if (arg.getPointerCount() != 1) {
+ *result_listener << "Expected 1 pointer, got " << arg.getPointerCount();
+ return false;
+ }
+ return arg.getX(0 /*pointerIndex*/) == x && arg.getY(0 /*pointerIndex*/) == y;
+}
+
+MATCHER_P(WithPointers, pointers, "MotionEvent with specified pointers") {
+ // Build a map for the received pointers, by pointer id
+ std::map<int32_t /*pointerId*/, PointF> actualPointers;
+ for (size_t pointerIndex = 0; pointerIndex < arg.getPointerCount(); pointerIndex++) {
+ const int32_t pointerId = arg.getPointerId(pointerIndex);
+ actualPointers[pointerId] = {arg.getX(pointerIndex), arg.getY(pointerIndex)};
+ }
+ return pointers == actualPointers;
+}
+
// --- FakeInputDispatcherPolicy ---
class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface {
@@ -1717,8 +1743,6 @@
sp<FakeWindowHandle> wallpaperWindow =
sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
wallpaperWindow->setIsWallpaper(true);
- constexpr int expectedWallpaperFlags =
- AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {foregroundWindow, wallpaperWindow}}});
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -1761,8 +1785,6 @@
sp<FakeWindowHandle> wallpaperWindow =
sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
wallpaperWindow->setIsWallpaper(true);
- constexpr int expectedWallpaperFlags =
- AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {foregroundWindow, wallpaperWindow}}});
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -1792,24 +1814,27 @@
foregroundWindow->consumeMotionCancel();
}
+class ShouldSplitTouchFixture : public InputDispatcherTest,
+ public ::testing::WithParamInterface<bool> {};
+INSTANTIATE_TEST_SUITE_P(InputDispatcherTest, ShouldSplitTouchFixture,
+ ::testing::Values(true, false));
/**
* A single window that receives touch (on top), and a wallpaper window underneath it.
* The top window gets a multitouch gesture.
* Ensure that wallpaper gets the same gesture.
*/
-TEST_F(InputDispatcherTest, WallpaperWindow_ReceivesMultiTouch) {
+TEST_P(ShouldSplitTouchFixture, WallpaperWindowReceivesMultiTouch) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window =
- sp<FakeWindowHandle>::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
- window->setDupTouchToWallpaper(true);
+ sp<FakeWindowHandle> foregroundWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT);
+ foregroundWindow->setDupTouchToWallpaper(true);
+ foregroundWindow->setPreventSplitting(GetParam());
sp<FakeWindowHandle> wallpaperWindow =
sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
wallpaperWindow->setIsWallpaper(true);
- constexpr int expectedWallpaperFlags =
- AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window, wallpaperWindow}}});
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {foregroundWindow, wallpaperWindow}}});
// Touch down on top window
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -1818,7 +1843,7 @@
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
// Both top window and its wallpaper should receive the touch down
- window->consumeMotionDown();
+ foregroundWindow->consumeMotionDown();
wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
// Second finger down on the top window
@@ -1837,11 +1862,34 @@
InputEventInjectionSync::WAIT_FOR_RESULT))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
- window->consumeMotionPointerDown(1 /* pointerIndex */);
+ foregroundWindow->consumeMotionPointerDown(1 /* pointerIndex */);
wallpaperWindow->consumeMotionPointerDown(1 /* pointerIndex */, ADISPLAY_ID_DEFAULT,
expectedWallpaperFlags);
- window->assertNoEvents();
- wallpaperWindow->assertNoEvents();
+
+ const MotionEvent secondFingerUpEvent =
+ MotionEventBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .displayId(ADISPLAY_ID_DEFAULT)
+ .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+ .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .x(100)
+ .y(100))
+ .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .x(150)
+ .y(150))
+ .build();
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT,
+ InputEventInjectionSync::WAIT_FOR_RESULT))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ foregroundWindow->consumeMotionPointerUp(0);
+ wallpaperWindow->consumeMotionPointerUp(0, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {100, 100}))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ foregroundWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT);
+ wallpaperWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
}
/**
@@ -1868,8 +1916,6 @@
sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
wallpaperWindow->setFrame(Rect(0, 0, 400, 200));
wallpaperWindow->setIsWallpaper(true);
- constexpr int expectedWallpaperFlags =
- AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
mDispatcher->setInputWindows(
{{ADISPLAY_ID_DEFAULT, {leftWindow, rightWindow, wallpaperWindow}}});
@@ -1934,62 +1980,49 @@
wallpaperWindow->assertNoEvents();
}
-TEST_F(InputDispatcherTest, WallpaperWindowReceivesMultiTouch) {
+/**
+ * Two windows: a window on the left with dup touch to wallpaper and window on the right without it.
+ * The touch slips to the right window. so left window and wallpaper should receive ACTION_CANCEL
+ * The right window should receive ACTION_DOWN.
+ */
+TEST_F(InputDispatcherTest, WallpaperWindowWhenSlippery) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window =
- sp<FakeWindowHandle>::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
- window->setDupTouchToWallpaper(true);
+ sp<FakeWindowHandle> leftWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+ leftWindow->setFrame(Rect(0, 0, 200, 200));
+ leftWindow->setDupTouchToWallpaper(true);
+ leftWindow->setSlippery(true);
+
+ sp<FakeWindowHandle> rightWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+ rightWindow->setFrame(Rect(200, 0, 400, 200));
sp<FakeWindowHandle> wallpaperWindow =
sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
wallpaperWindow->setIsWallpaper(true);
- constexpr int expectedWallpaperFlags =
- AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
- wallpaperWindow->setPreventSplitting(true);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window, wallpaperWindow}}});
+ mDispatcher->setInputWindows(
+ {{ADISPLAY_ID_DEFAULT, {leftWindow, rightWindow, wallpaperWindow}}});
+ // Touch down on left window
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
- {50, 50}))
+ {100, 100}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
- window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+
+ // Both foreground window and its wallpaper should receive the touch down
+ leftWindow->consumeMotionDown();
wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
- const MotionEvent secondFingerDownEvent =
- MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
- .displayId(ADISPLAY_ID_DEFAULT)
- .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
- .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
- .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10))
- .build();
+ // Move to right window, the left window should receive cancel.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
- InputEventInjectionSync::WAIT_FOR_RESULT))
+ injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT, {201, 100}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
- window->consumeMotionPointerDown(1);
- wallpaperWindow->consumeMotionPointerDown(1, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
-
- const MotionEvent secondFingerUpEvent =
- MotionEventBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN)
- .displayId(ADISPLAY_ID_DEFAULT)
- .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
- .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
- .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10))
- .build();
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT,
- InputEventInjectionSync::WAIT_FOR_RESULT))
- << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
- window->consumeMotionPointerUp(1);
- wallpaperWindow->consumeMotionPointerUp(1, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
-
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {50, 50}))
- << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
- window->consumeMotionUp(ADISPLAY_ID_DEFAULT);
- wallpaperWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+ leftWindow->consumeMotionCancel();
+ rightWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+ wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
}
/**
@@ -2536,6 +2569,39 @@
}
/**
+ * Two windows. First is a regular window. Second does not overlap with the first, and has
+ * WATCH_OUTSIDE_TOUCH.
+ * Both windows are owned by the same UID.
+ * Tap first window. Make sure that the second window receives ACTION_OUTSIDE with correct, non-zero
+ * coordinates. The coordinates are not zeroed out because both windows are owned by the same UID.
+ */
+TEST_F(InputDispatcherTest, ActionOutsideForOwnedWindowHasValidCoordinates) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "First Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect{0, 0, 100, 100});
+
+ sp<FakeWindowHandle> outsideWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Second Window",
+ ADISPLAY_ID_DEFAULT);
+ outsideWindow->setFrame(Rect{100, 100, 200, 200});
+ outsideWindow->setWatchOutsideTouch(true);
+ // outsideWindow must be above 'window' to receive ACTION_OUTSIDE events when 'window' is tapped
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {outsideWindow, window}}});
+
+ // Tap on first window.
+ NotifyMotionArgs motionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT, {PointF{50, 50}});
+ mDispatcher->notifyMotion(&motionArgs);
+ window->consumeMotionDown();
+ // The coordinates of the tap in 'outsideWindow' are relative to its top left corner.
+ // Therefore, we should offset them by (100, 100) relative to the screen's top left corner.
+ outsideWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_OUTSIDE), WithCoords(-50, -50)));
+}
+
+/**
* This test documents the behavior of WATCH_OUTSIDE_TOUCH. The window will get ACTION_OUTSIDE when
* a another pointer causes ACTION_DOWN to be sent to another window for the first time. Only one
* ACTION_OUTSIDE event is sent per gesture.
@@ -2570,7 +2636,9 @@
motionArgs = generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
{PointF{-10, -10}, PointF{105, 105}});
mDispatcher->notifyMotion(&motionArgs);
- window->consumeMotionOutside();
+ const std::map<int32_t, PointF> expectedPointers{{0, PointF{-10, -10}}, {1, PointF{105, 105}}};
+ window->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_OUTSIDE), WithPointers(expectedPointers)));
secondWindow->consumeMotionDown();
thirdWindow->assertNoEvents();
@@ -2802,21 +2870,26 @@
sp<FakeWindowHandle> firstWindow =
sp<FakeWindowHandle>::make(application, mDispatcher, "First Window",
ADISPLAY_ID_DEFAULT);
+ firstWindow->setDupTouchToWallpaper(true);
sp<FakeWindowHandle> secondWindow =
sp<FakeWindowHandle>::make(application, mDispatcher, "Second Window",
ADISPLAY_ID_DEFAULT);
-
+ sp<FakeWindowHandle> wallpaper =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
+ wallpaper->setIsWallpaper(true);
// Add the windows to the dispatcher
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow}}});
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow, wallpaper}}});
// Send down to the first window
NotifyMotionArgs downMotionArgs =
generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
ADISPLAY_ID_DEFAULT);
mDispatcher->notifyMotion(&downMotionArgs);
+
// Only the first window should get the down event
firstWindow->consumeMotionDown();
secondWindow->assertNoEvents();
+ wallpaper->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
// Transfer touch to the second window
TransferFunction f = GetParam();
@@ -2825,6 +2898,7 @@
// The first window gets cancel and the second gets down
firstWindow->consumeMotionCancel();
secondWindow->consumeMotionDown();
+ wallpaper->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
// Send up event to the second window
NotifyMotionArgs upMotionArgs =
@@ -2834,6 +2908,7 @@
// The first window gets no events and the second gets up
firstWindow->assertNoEvents();
secondWindow->consumeMotionUp();
+ wallpaper->assertNoEvents();
}
/**
@@ -2957,6 +3032,65 @@
secondWindow->consumeMotionUp();
}
+TEST_P(TransferTouchFixture, TransferTouch_MultipleWallpapers) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+ // Create a couple of windows
+ sp<FakeWindowHandle> firstWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "First Window",
+ ADISPLAY_ID_DEFAULT);
+ firstWindow->setDupTouchToWallpaper(true);
+ sp<FakeWindowHandle> secondWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Second Window",
+ ADISPLAY_ID_DEFAULT);
+ secondWindow->setDupTouchToWallpaper(true);
+
+ sp<FakeWindowHandle> wallpaper1 =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper1", ADISPLAY_ID_DEFAULT);
+ wallpaper1->setIsWallpaper(true);
+
+ sp<FakeWindowHandle> wallpaper2 =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper2", ADISPLAY_ID_DEFAULT);
+ wallpaper2->setIsWallpaper(true);
+ // Add the windows to the dispatcher
+ mDispatcher->setInputWindows(
+ {{ADISPLAY_ID_DEFAULT, {firstWindow, wallpaper1, secondWindow, wallpaper2}}});
+
+ // Send down to the first window
+ NotifyMotionArgs downMotionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT);
+ mDispatcher->notifyMotion(&downMotionArgs);
+
+ // Only the first window should get the down event
+ firstWindow->consumeMotionDown();
+ secondWindow->assertNoEvents();
+ wallpaper1->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+ wallpaper2->assertNoEvents();
+
+ // Transfer touch focus to the second window
+ TransferFunction f = GetParam();
+ bool success = f(mDispatcher, firstWindow->getToken(), secondWindow->getToken());
+ ASSERT_TRUE(success);
+
+ // The first window gets cancel and the second gets down
+ firstWindow->consumeMotionCancel();
+ secondWindow->consumeMotionDown();
+ wallpaper1->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+ wallpaper2->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+
+ // Send up event to the second window
+ NotifyMotionArgs upMotionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT);
+ mDispatcher->notifyMotion(&upMotionArgs);
+ // The first window gets no events and the second gets up
+ firstWindow->assertNoEvents();
+ secondWindow->consumeMotionUp();
+ wallpaper1->assertNoEvents();
+ wallpaper2->consumeMotionUp(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+}
+
// For the cases of single pointer touch and two pointers non-split touch, the api's
// 'transferTouch' and 'transferTouchFocus' are equivalent in behaviour. They only differ
// for the case where there are multiple pointers split across several windows.
diff --git a/services/inputflinger/tests/InputMapperTest.cpp b/services/inputflinger/tests/InputMapperTest.cpp
index 3cd7c1b..a02ef05 100644
--- a/services/inputflinger/tests/InputMapperTest.cpp
+++ b/services/inputflinger/tests/InputMapperTest.cpp
@@ -166,11 +166,4 @@
ASSERT_NEAR(distance, coords.getAxisValue(AMOTION_EVENT_AXIS_DISTANCE), EPSILON);
}
-void InputMapperTest::assertPosition(const FakePointerController& controller, float x, float y) {
- float actualX, actualY;
- controller.getPosition(&actualX, &actualY);
- ASSERT_NEAR(x, actualX, 1);
- ASSERT_NEAR(y, actualY, 1);
-}
-
} // namespace android
diff --git a/services/inputflinger/tests/InputMapperTest.h b/services/inputflinger/tests/InputMapperTest.h
index b3401c3..63ca44c 100644
--- a/services/inputflinger/tests/InputMapperTest.h
+++ b/services/inputflinger/tests/InputMapperTest.h
@@ -89,7 +89,6 @@
float size, float touchMajor, float touchMinor, float toolMajor,
float toolMinor, float orientation, float distance,
float scaledAxisEpsilon = 1.f);
- static void assertPosition(const FakePointerController& controller, float x, float y);
};
} // namespace android
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 3516092..95d35f4 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -48,12 +48,9 @@
#include "InputMapperTest.h"
#include "InstrumentedInputReader.h"
#include "TestConstants.h"
-#include "android/hardware/input/InputDeviceCountryCode.h"
#include "input/DisplayViewport.h"
#include "input/Input.h"
-using android::hardware::input::InputDeviceCountryCode;
-
namespace android {
using namespace ftl::flag_operators;
@@ -2023,6 +2020,56 @@
WithDeviceId(touchscreenId))));
}
+TYPED_TEST(StylusButtonIntegrationTest, StylusButtonMotionEventsDisabled) {
+ TestFixture::mFakePolicy->setStylusButtonMotionEventsEnabled(false);
+ TestFixture::mReader->requestRefreshConfiguration(
+ InputReaderConfiguration::CHANGE_STYLUS_BUTTON_REPORTING);
+
+ const Point centerPoint = TestFixture::mTouchscreen->getCenterPoint();
+ const auto touchscreenId = TestFixture::mTouchscreenInfo.getId();
+ const auto stylusId = TestFixture::mStylusInfo.getId();
+
+ // Start a stylus gesture. By the time this event is processed, the configuration change that
+ // was requested is guaranteed to be completed.
+ TestFixture::mTouchscreen->sendSlot(FIRST_SLOT);
+ TestFixture::mTouchscreen->sendTrackingId(FIRST_TRACKING_ID);
+ TestFixture::mTouchscreen->sendToolType(MT_TOOL_PEN);
+ TestFixture::mTouchscreen->sendDown(centerPoint);
+ TestFixture::mTouchscreen->sendSync();
+ ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+ WithDeviceId(touchscreenId))));
+
+ // Press and release a stylus button. Each change only generates a MOVE motion event.
+ // Key events are unaffected.
+ TestFixture::mStylus->pressKey(BTN_STYLUS);
+ ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyKeyWasCalled(
+ AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), WithSource(AINPUT_SOURCE_KEYBOARD),
+ WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId))));
+ ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+ WithDeviceId(touchscreenId))));
+
+ TestFixture::mStylus->releaseKey(BTN_STYLUS);
+ ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyKeyWasCalled(
+ AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP), WithSource(AINPUT_SOURCE_KEYBOARD),
+ WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId))));
+ ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+ WithDeviceId(touchscreenId))));
+
+ // Finish the stylus gesture.
+ TestFixture::mTouchscreen->sendTrackingId(INVALID_TRACKING_ID);
+ TestFixture::mTouchscreen->sendSync();
+ ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+ WithDeviceId(touchscreenId))));
+}
+
// --- ExternalStylusIntegrationTest ---
// Verify the behavior of an external stylus. An external stylus can report pressure or button
@@ -2240,17 +2287,6 @@
ASSERT_EQ(ftl::Flags<InputDeviceClass>(0), mDevice->getClasses());
}
-TEST_F(InputDeviceTest, CountryCodeCorrectlyMapped) {
- mFakeEventHub->setCountryCode(EVENTHUB_ID, InputDeviceCountryCode::INTERNATIONAL);
-
- // Configuration
- mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD);
- InputReaderConfiguration config;
- std::list<NotifyArgs> unused = mDevice->configure(ARBITRARY_TIME, &config, 0);
-
- ASSERT_EQ(InputDeviceCountryCode::INTERNATIONAL, mDevice->getDeviceInfo().getCountryCode());
-}
-
TEST_F(InputDeviceTest, WhenDeviceCreated_EnabledIsFalse) {
ASSERT_EQ(mDevice->isEnabled(), false);
}
@@ -2763,7 +2799,7 @@
class KeyboardInputMapperTest : public InputMapperTest {
protected:
const std::string UNIQUE_ID = "local:0";
-
+ const KeyboardLayoutInfo DEVICE_KEYBOARD_LAYOUT_INFO = KeyboardLayoutInfo("en-US", "qwerty");
void prepareDisplay(ui::Rotation orientation);
void testDPadKeyRotation(KeyboardInputMapper& mapper, int32_t originalScanCode,
@@ -3582,6 +3618,38 @@
ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_CANCELED, args.flags);
}
+TEST_F(KeyboardInputMapperTest, Configure_AssignKeyboardLayoutInfo) {
+ mDevice->addMapper<KeyboardInputMapper>(EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD,
+ AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ std::list<NotifyArgs> unused =
+ mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0);
+
+ mFakePolicy->addKeyboardLayoutAssociation(DEVICE_LOCATION, DEVICE_KEYBOARD_LAYOUT_INFO);
+
+ unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+ InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUT_ASSOCIATION);
+
+ InputDeviceInfo deviceInfo = mDevice->getDeviceInfo();
+ ASSERT_EQ(DEVICE_KEYBOARD_LAYOUT_INFO.languageTag,
+ deviceInfo.getKeyboardLayoutInfo()->languageTag);
+ ASSERT_EQ(DEVICE_KEYBOARD_LAYOUT_INFO.layoutType,
+ deviceInfo.getKeyboardLayoutInfo()->layoutType);
+}
+
+TEST_F(KeyboardInputMapperTest, LayoutInfoCorrectlyMapped) {
+ mFakeEventHub->setRawLayoutInfo(EVENTHUB_ID,
+ RawLayoutInfo{.languageTag = "en", .layoutType = "extended"});
+
+ // Configuration
+ addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ InputReaderConfiguration config;
+ std::list<NotifyArgs> unused = mDevice->configure(ARBITRARY_TIME, &config, 0);
+
+ ASSERT_EQ("en", mDevice->getDeviceInfo().getKeyboardLayoutInfo()->languageTag);
+ ASSERT_EQ("extended", mDevice->getDeviceInfo().getKeyboardLayoutInfo()->layoutType);
+}
+
// --- KeyboardInputMapperTest_ExternalDevice ---
class KeyboardInputMapperTest_ExternalDevice : public InputMapperTest {
@@ -4344,7 +4412,7 @@
ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action);
ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
110.0f, 220.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
- ASSERT_NO_FATAL_FAILURE(assertPosition(*mFakePointerController, 110.0f, 220.0f));
+ ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f));
}
TEST_F(CursorInputMapperTest, Process_PointerCapture) {
@@ -4372,7 +4440,7 @@
ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
10.0f, 20.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
- ASSERT_NO_FATAL_FAILURE(assertPosition(*mFakePointerController, 100.0f, 200.0f));
+ ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(100.0f, 200.0f));
// Button press.
process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 1);
@@ -4411,7 +4479,7 @@
ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
30.0f, 40.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
- ASSERT_NO_FATAL_FAILURE(assertPosition(*mFakePointerController, 100.0f, 200.0f));
+ ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(100.0f, 200.0f));
// Disable pointer capture and check that the device generation got bumped
// and events are generated the usual way.
@@ -4431,7 +4499,7 @@
ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action);
ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
110.0f, 220.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
- ASSERT_NO_FATAL_FAILURE(assertPosition(*mFakePointerController, 110.0f, 220.0f));
+ ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f));
}
/**
@@ -4548,7 +4616,7 @@
AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(SECONDARY_DISPLAY_ID),
WithCoords(110.0f, 220.0f))));
- ASSERT_NO_FATAL_FAILURE(assertPosition(*mFakePointerController, 110.0f, 220.0f));
+ ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f));
}
TEST_F(CursorInputMapperTest, ConfigureDisplayId_WithAssociatedViewport) {
@@ -4575,7 +4643,7 @@
AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(SECONDARY_DISPLAY_ID),
WithCoords(110.0f, 220.0f))));
- ASSERT_NO_FATAL_FAILURE(assertPosition(*mFakePointerController, 110.0f, 220.0f));
+ ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f));
}
TEST_F(CursorInputMapperTest, ConfigureDisplayId_IgnoresEventsForMismatchedPointerDisplay) {
@@ -5535,7 +5603,7 @@
// Rotation 90.
clearViewports();
prepareDisplay(ui::ROTATION_90);
- processDown(mapper, toRawX(75), RAW_Y_MAX - toRawY(50) + RAW_Y_MIN);
+ processDown(mapper, toRotatedRawX(75), RAW_Y_MAX - toRotatedRawY(50) + RAW_Y_MIN);
processSync(mapper);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
@@ -5563,7 +5631,7 @@
// Rotation 270.
clearViewports();
prepareDisplay(ui::ROTATION_270);
- processDown(mapper, RAW_X_MAX - toRawX(75) + RAW_X_MIN, toRawY(50));
+ processDown(mapper, RAW_X_MAX - toRotatedRawX(75) + RAW_X_MIN, toRotatedRawY(50));
processSync(mapper);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
@@ -5700,7 +5768,7 @@
// Orientation 90, Rotation 90.
clearViewports();
prepareDisplay(ui::ROTATION_90);
- processDown(mapper, toRotatedRawX(50), toRotatedRawY(75));
+ processDown(mapper, toRawX(50), toRawY(75));
processSync(mapper);
EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
@@ -5728,8 +5796,7 @@
// Orientation 90, Rotation 270.
clearViewports();
prepareDisplay(ui::ROTATION_270);
- processDown(mapper, RAW_X_MAX - toRotatedRawX(50) + RAW_X_MIN,
- RAW_Y_MAX - toRotatedRawY(75) + RAW_Y_MIN);
+ processDown(mapper, RAW_X_MAX - toRawX(50) + RAW_X_MIN, RAW_Y_MAX - toRawY(75) + RAW_Y_MIN);
processSync(mapper);
EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
@@ -5741,6 +5808,61 @@
EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled());
}
+TEST_F(SingleTouchInputMapperTest, Process_IgnoresTouchesOutsidePhysicalFrame) {
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareButtons();
+ prepareAxes(POSITION);
+ addConfigurationProperty("touch.orientationAware", "1");
+ prepareDisplay(ui::ROTATION_0);
+ auto& mapper = addMapperAndConfigure<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 be ignored.
+ 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->assertNotifyMotionWasNotCalled());
+ }
+
+ // Move the touch into the physical frame.
+ processMove(mapper, toRawX(25), toRawY(605));
+ processSync(mapper);
+ NotifyMotionArgs args;
+ EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+ EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
+ EXPECT_NEAR(25, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1);
+ EXPECT_NEAR(605, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1);
+
+ // Once the touch down is reported, continue reporting input, even if it is outside the frame.
+ for (const auto& p : outsidePoints) {
+ processMove(mapper, toRawX(p.x), toRawY(p.y));
+ processSync(mapper);
+ EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+ EXPECT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
+ EXPECT_NEAR(p.x, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1);
+ EXPECT_NEAR(p.y, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1);
+ }
+
+ processUp(mapper);
+ processSync(mapper);
+ EXPECT_NO_FATAL_FAILURE(
+ mFakeListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP)));
+}
+
TEST_F(SingleTouchInputMapperTest, Process_AllAxes_DefaultCalibration) {
addConfigurationProperty("touch.deviceType", "touchScreen");
prepareDisplay(ui::ROTATION_0);
@@ -6505,6 +6627,46 @@
WithCoords(toDisplayX(100), toDisplayY(200)), WithButtonState(0))));
}
+TEST_F(SingleTouchInputMapperTest, StylusButtonMotionEventsDisabled) {
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(ui::ROTATION_0);
+ prepareButtons();
+ prepareAxes(POSITION);
+
+ mFakePolicy->setStylusButtonMotionEventsEnabled(false);
+
+ SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
+
+ // Press a stylus button.
+ processKey(mapper, BTN_STYLUS, 1);
+ processSync(mapper);
+
+ // Start a touch gesture and ensure that the stylus button is not reported.
+ processDown(mapper, 100, 200);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithButtonState(0))));
+
+ // Release and press the stylus button again.
+ processKey(mapper, BTN_STYLUS, 0);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithButtonState(0))));
+ processKey(mapper, BTN_STYLUS, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithButtonState(0))));
+
+ // Release the touch gesture.
+ processUp(mapper);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0))));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+}
+
TEST_F(SingleTouchInputMapperTest, WhenDeviceTypeIsSetToTouchNavigation_setsCorrectType) {
mFakePolicy->addDeviceTypeAssociation(DEVICE_LOCATION, "touchNavigation");
prepareDisplay(ui::ROTATION_0);
diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h
index 9db3422..b9d9607 100644
--- a/services/inputflinger/tests/TestInputListenerMatchers.h
+++ b/services/inputflinger/tests/TestInputListenerMatchers.h
@@ -16,6 +16,8 @@
#pragma once
+#include <cmath>
+
#include <android/input.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@@ -66,6 +68,11 @@
return arg.keyCode == keyCode;
}
+MATCHER_P(WithPointerCount, count, "MotionEvent with specified number of pointers") {
+ *result_listener << "expected " << count << " pointer(s), but got " << arg.pointerCount;
+ return arg.pointerCount == count;
+}
+
MATCHER_P2(WithCoords, x, y, "InputEvent with specified coords") {
const auto argX = arg.pointerCoords[0].getX();
const auto argY = arg.pointerCoords[0].getY();
@@ -74,6 +81,55 @@
return argX == x && argY == y;
}
+MATCHER_P3(WithPointerCoords, pointer, x, y, "InputEvent with specified coords for pointer") {
+ const auto argX = arg.pointerCoords[pointer].getX();
+ const auto argY = arg.pointerCoords[pointer].getY();
+ *result_listener << "expected pointer " << pointer << " to have coords (" << x << ", " << y
+ << "), but got (" << argX << ", " << argY << ")";
+ return argX == x && argY == y;
+}
+
+MATCHER_P2(WithRelativeMotion, x, y, "InputEvent with specified relative motion") {
+ const auto argX = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
+ const auto argY = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+ *result_listener << "expected relative motion (" << x << ", " << y << "), but got (" << argX
+ << ", " << argY << ")";
+ return argX == x && argY == y;
+}
+
+MATCHER_P3(WithGestureOffset, dx, dy, epsilon,
+ "InputEvent with specified touchpad gesture offset") {
+ const auto argGestureX = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET);
+ const auto argGestureY = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET);
+ const double xDiff = fabs(argGestureX - dx);
+ const double yDiff = fabs(argGestureY - dy);
+ *result_listener << "expected gesture offset (" << dx << ", " << dy << ") within " << epsilon
+ << ", but got (" << argGestureX << ", " << argGestureY << ")";
+ return xDiff <= epsilon && yDiff <= epsilon;
+}
+
+MATCHER_P3(WithGestureScrollDistance, x, y, epsilon,
+ "InputEvent with specified touchpad gesture scroll distance") {
+ const auto argXDistance =
+ arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE);
+ const auto argYDistance =
+ arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE);
+ const double xDiff = fabs(argXDistance - x);
+ const double yDiff = fabs(argYDistance - y);
+ *result_listener << "expected gesture offset (" << x << ", " << y << ") within " << epsilon
+ << ", but got (" << argXDistance << ", " << argYDistance << ")";
+ return xDiff <= epsilon && yDiff <= epsilon;
+}
+
+MATCHER_P2(WithGesturePinchScaleFactor, factor, epsilon,
+ "InputEvent with specified touchpad pinch gesture scale factor") {
+ const auto argScaleFactor =
+ arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR);
+ *result_listener << "expected gesture scale factor " << factor << " within " << epsilon
+ << " but got " << argScaleFactor;
+ return fabs(argScaleFactor - factor) <= epsilon;
+}
+
MATCHER_P(WithPressure, pressure, "InputEvent with specified pressure") {
const auto argPressure = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE);
*result_listener << "expected pressure " << pressure << ", but got " << argPressure;
@@ -92,14 +148,32 @@
return arg.flags == flags;
}
+MATCHER_P(WithMotionClassification, classification,
+ "InputEvent with specified MotionClassification") {
+ *result_listener << "expected classification " << motionClassificationToString(classification)
+ << ", but got " << motionClassificationToString(arg.classification);
+ return arg.classification == classification;
+}
+
MATCHER_P(WithButtonState, buttons, "InputEvent with specified button state") {
*result_listener << "expected button state " << buttons << ", but got " << arg.buttonState;
return arg.buttonState == buttons;
}
+MATCHER_P(WithActionButton, actionButton, "InputEvent with specified action button") {
+ *result_listener << "expected action button " << actionButton << ", but got "
+ << arg.actionButton;
+ return arg.actionButton == actionButton;
+}
+
MATCHER_P(WithEventTime, eventTime, "InputEvent with specified eventTime") {
*result_listener << "expected event time " << eventTime << ", but got " << arg.eventTime;
return arg.eventTime == eventTime;
}
+MATCHER_P(WithDownTime, downTime, "InputEvent with specified downTime") {
+ *result_listener << "expected down time " << downTime << ", but got " << arg.downTime;
+ return arg.downTime == downTime;
+}
+
} // namespace android
diff --git a/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp
index cc523e1..be85026 100644
--- a/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp
@@ -16,11 +16,10 @@
#include <CursorInputMapper.h>
#include <FuzzContainer.h>
-#include <fuzzer/FuzzedDataProvider.h>
namespace android {
-static void addProperty(FuzzContainer& fuzzer, std::shared_ptr<FuzzedDataProvider> fdp) {
+static void addProperty(FuzzContainer& fuzzer, std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp) {
// Pick a random property to set for the mapper to have set.
fdp->PickValueInArray<std::function<void()>>(
{[&]() -> void { fuzzer.addProperty("cursor.mode", "pointer"); },
@@ -35,7 +34,8 @@
}
extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) {
- std::shared_ptr<FuzzedDataProvider> fdp = std::make_shared<FuzzedDataProvider>(data, size);
+ std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp =
+ std::make_shared<ThreadSafeFuzzedDataProvider>(data, size);
FuzzContainer fuzzer(fdp);
CursorInputMapper& mapper = fuzzer.getMapper<CursorInputMapper>();
diff --git a/services/inputflinger/tests/fuzzers/FuzzContainer.h b/services/inputflinger/tests/fuzzers/FuzzContainer.h
index 1e0764f..76d2bcd 100644
--- a/services/inputflinger/tests/fuzzers/FuzzContainer.h
+++ b/services/inputflinger/tests/fuzzers/FuzzContainer.h
@@ -20,7 +20,6 @@
#include <InputMapper.h>
#include <InputReader.h>
#include <MapperHelpers.h>
-#include <fuzzer/FuzzedDataProvider.h>
namespace android {
@@ -31,10 +30,10 @@
std::unique_ptr<FuzzInputReaderContext> mFuzzContext;
std::unique_ptr<InputDevice> mFuzzDevice;
InputReaderConfiguration mPolicyConfig;
- std::shared_ptr<FuzzedDataProvider> mFdp;
+ std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp;
public:
- FuzzContainer(std::shared_ptr<FuzzedDataProvider> fdp) : mFdp(fdp) {
+ FuzzContainer(std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp) : mFdp(fdp) {
// Setup parameters.
std::string deviceName = mFdp->ConsumeRandomLengthString(16);
std::string deviceLocation = mFdp->ConsumeRandomLengthString(12);
diff --git a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
index 057c15d..a80839c 100644
--- a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
@@ -170,7 +170,8 @@
};
extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) {
- std::shared_ptr<FuzzedDataProvider> fdp = std::make_shared<FuzzedDataProvider>(data, size);
+ std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp =
+ std::make_shared<ThreadSafeFuzzedDataProvider>(data, size);
FuzzInputListener fuzzListener;
sp<FuzzInputReaderPolicy> fuzzPolicy = sp<FuzzInputReaderPolicy>::make(fdp);
diff --git a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp
index e880f55..8e2d677 100644
--- a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp
@@ -16,13 +16,12 @@
#include <FuzzContainer.h>
#include <KeyboardInputMapper.h>
-#include <fuzzer/FuzzedDataProvider.h>
namespace android {
const int32_t kMaxKeycodes = 100;
-static void addProperty(FuzzContainer& fuzzer, std::shared_ptr<FuzzedDataProvider> fdp) {
+static void addProperty(FuzzContainer& fuzzer, std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp) {
// Pick a random property to set for the mapper to have set.
fdp->PickValueInArray<std::function<void()>>(
{[&]() -> void { fuzzer.addProperty("keyboard.orientationAware", "1"); },
@@ -41,7 +40,8 @@
}
extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) {
- std::shared_ptr<FuzzedDataProvider> fdp = std::make_shared<FuzzedDataProvider>(data, size);
+ std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp =
+ std::make_shared<ThreadSafeFuzzedDataProvider>(data, size);
FuzzContainer fuzzer(fdp);
KeyboardInputMapper& mapper =
diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h
index cd852d6..7c9be5c 100644
--- a/services/inputflinger/tests/fuzzers/MapperHelpers.h
+++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h
@@ -13,16 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
#pragma once
#include <InputDevice.h>
#include <InputMapper.h>
#include <InputReader.h>
-#include <fuzzer/FuzzedDataProvider.h>
-#include "android/hardware/input/InputDeviceCountryCode.h"
-
-using android::hardware::input::InputDeviceCountryCode;
+#include <ThreadSafeFuzzedDataProvider.h>
constexpr size_t kValidTypes[] = {EV_SW,
EV_SYN,
@@ -66,46 +62,6 @@
BTN_TASK,
};
-constexpr InputDeviceCountryCode kCountryCodes[] = {
- InputDeviceCountryCode::INVALID,
- InputDeviceCountryCode::NOT_SUPPORTED,
- InputDeviceCountryCode::ARABIC,
- InputDeviceCountryCode::BELGIAN,
- InputDeviceCountryCode::CANADIAN_BILINGUAL,
- InputDeviceCountryCode::CANADIAN_FRENCH,
- InputDeviceCountryCode::CZECH_REPUBLIC,
- InputDeviceCountryCode::DANISH,
- InputDeviceCountryCode::FINNISH,
- InputDeviceCountryCode::FRENCH,
- InputDeviceCountryCode::GERMAN,
- InputDeviceCountryCode::GREEK,
- InputDeviceCountryCode::HEBREW,
- InputDeviceCountryCode::HUNGARY,
- InputDeviceCountryCode::INTERNATIONAL,
- InputDeviceCountryCode::ITALIAN,
- InputDeviceCountryCode::JAPAN,
- InputDeviceCountryCode::KOREAN,
- InputDeviceCountryCode::LATIN_AMERICAN,
- InputDeviceCountryCode::DUTCH,
- InputDeviceCountryCode::NORWEGIAN,
- InputDeviceCountryCode::PERSIAN,
- InputDeviceCountryCode::POLAND,
- InputDeviceCountryCode::PORTUGUESE,
- InputDeviceCountryCode::RUSSIA,
- InputDeviceCountryCode::SLOVAKIA,
- InputDeviceCountryCode::SPANISH,
- InputDeviceCountryCode::SWEDISH,
- InputDeviceCountryCode::SWISS_FRENCH,
- InputDeviceCountryCode::SWISS_GERMAN,
- InputDeviceCountryCode::SWITZERLAND,
- InputDeviceCountryCode::TAIWAN,
- InputDeviceCountryCode::TURKISH_Q,
- InputDeviceCountryCode::UK,
- InputDeviceCountryCode::US,
- InputDeviceCountryCode::YUGOSLAVIA,
- InputDeviceCountryCode::TURKISH_F,
-};
-
constexpr size_t kMaxSize = 256;
namespace android {
@@ -114,10 +70,10 @@
InputDeviceIdentifier mIdentifier;
std::vector<TouchVideoFrame> mVideoFrames;
PropertyMap mFuzzConfig;
- std::shared_ptr<FuzzedDataProvider> mFdp;
+ std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp;
public:
- FuzzEventHub(std::shared_ptr<FuzzedDataProvider> fdp) : mFdp(std::move(fdp)) {}
+ FuzzEventHub(std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp) : mFdp(std::move(fdp)) {}
~FuzzEventHub() {}
void addProperty(std::string key, std::string value) { mFuzzConfig.addProperty(key, value); }
@@ -198,8 +154,8 @@
void setLightIntensities(int32_t deviceId, int32_t lightId,
std::unordered_map<LightColor, int32_t> intensities) override{};
- InputDeviceCountryCode getCountryCode(int32_t deviceId) const override {
- return mFdp->PickValueInArray<InputDeviceCountryCode>(kCountryCodes);
+ std::optional<RawLayoutInfo> getRawLayoutInfo(int32_t deviceId) const override {
+ return std::nullopt;
};
int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const override {
@@ -264,10 +220,10 @@
};
class FuzzPointerController : public PointerControllerInterface {
- std::shared_ptr<FuzzedDataProvider> mFdp;
+ std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp;
public:
- FuzzPointerController(std::shared_ptr<FuzzedDataProvider> mFdp) : mFdp(mFdp) {}
+ FuzzPointerController(std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp) : mFdp(mFdp) {}
~FuzzPointerController() {}
bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const override {
return mFdp->ConsumeBool();
@@ -290,13 +246,13 @@
class FuzzInputReaderPolicy : public InputReaderPolicyInterface {
TouchAffineTransformation mTransform;
std::shared_ptr<FuzzPointerController> mPointerController;
- std::shared_ptr<FuzzedDataProvider> mFdp;
+ std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp;
protected:
~FuzzInputReaderPolicy() {}
public:
- FuzzInputReaderPolicy(std::shared_ptr<FuzzedDataProvider> mFdp) : mFdp(mFdp) {
+ FuzzInputReaderPolicy(std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp) : mFdp(mFdp) {
mPointerController = std::make_shared<FuzzPointerController>(mFdp);
}
void getReaderConfiguration(InputReaderConfiguration* outConfig) override {}
@@ -334,13 +290,13 @@
class FuzzInputReaderContext : public InputReaderContext {
std::shared_ptr<EventHubInterface> mEventHub;
sp<InputReaderPolicyInterface> mPolicy;
- std::shared_ptr<FuzzedDataProvider> mFdp;
+ std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp;
public:
FuzzInputReaderContext(std::shared_ptr<EventHubInterface> eventHub,
const sp<InputReaderPolicyInterface>& policy,
InputListenerInterface& listener,
- std::shared_ptr<FuzzedDataProvider> mFdp)
+ std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp)
: mEventHub(eventHub), mPolicy(policy), mFdp(mFdp) {}
~FuzzInputReaderContext() {}
void updateGlobalMetaState() override {}
diff --git a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp
index 99fd083..011455b 100644
--- a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp
@@ -16,13 +16,12 @@
#include <FuzzContainer.h>
#include <MultiTouchInputMapper.h>
-#include <fuzzer/FuzzedDataProvider.h>
namespace android {
const int32_t kMaxKeycodes = 100;
-static void addProperty(FuzzContainer& fuzzer, std::shared_ptr<FuzzedDataProvider> fdp) {
+static void addProperty(FuzzContainer& fuzzer, std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp) {
// Pick a random property to set for the mapper to have set.
fdp->PickValueInArray<std::function<void()>>(
{[&]() -> void { fuzzer.addProperty("touch.deviceType", "touchScreen"); },
@@ -58,7 +57,8 @@
}
extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) {
- std::shared_ptr<FuzzedDataProvider> fdp = std::make_shared<FuzzedDataProvider>(data, size);
+ std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp =
+ std::make_shared<ThreadSafeFuzzedDataProvider>(data, size);
FuzzContainer fuzzer(fdp);
MultiTouchInputMapper& mapper = fuzzer.getMapper<MultiTouchInputMapper>();
diff --git a/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp
index 7416ce9..c4938f2 100644
--- a/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp
@@ -16,12 +16,12 @@
#include <FuzzContainer.h>
#include <SwitchInputMapper.h>
-#include <fuzzer/FuzzedDataProvider.h>
namespace android {
extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) {
- std::shared_ptr<FuzzedDataProvider> fdp = std::make_shared<FuzzedDataProvider>(data, size);
+ std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp =
+ std::make_shared<ThreadSafeFuzzedDataProvider>(data, size);
FuzzContainer fuzzer(fdp);
SwitchInputMapper& mapper = fuzzer.getMapper<SwitchInputMapper>();
diff --git a/services/inputflinger/tests/fuzzers/ThreadSafeFuzzedDataProvider.h b/services/inputflinger/tests/fuzzers/ThreadSafeFuzzedDataProvider.h
new file mode 100644
index 0000000..2f76f18
--- /dev/null
+++ b/services/inputflinger/tests/fuzzers/ThreadSafeFuzzedDataProvider.h
@@ -0,0 +1,136 @@
+/*
+ * 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 <fuzzer/FuzzedDataProvider.h>
+
+/**
+ * A thread-safe interface to the FuzzedDataProvider
+ */
+class ThreadSafeFuzzedDataProvider : FuzzedDataProvider {
+private:
+ std::mutex mLock;
+
+public:
+ ThreadSafeFuzzedDataProvider(const uint8_t* data, size_t size)
+ : FuzzedDataProvider(data, size) {}
+
+ template <typename T>
+ std::vector<T> ConsumeBytes(size_t num_bytes) {
+ std::scoped_lock _l(mLock);
+ return FuzzedDataProvider::ConsumeBytes<T>(num_bytes);
+ }
+
+ template <typename T>
+ std::vector<T> ConsumeBytesWithTerminator(size_t num_bytes, T terminator) {
+ std::scoped_lock _l(mLock);
+ return FuzzedDataProvider::ConsumeBytesWithTerminator<T>(num_bytes, terminator);
+ }
+
+ template <typename T>
+ std::vector<T> ConsumeRemainingBytes() {
+ std::scoped_lock _l(mLock);
+ return FuzzedDataProvider::ConsumeRemainingBytes<T>();
+ }
+
+ std::string ConsumeBytesAsString(size_t num_bytes) {
+ std::scoped_lock _l(mLock);
+ return FuzzedDataProvider::ConsumeBytesAsString(num_bytes);
+ }
+
+ std::string ConsumeRandomLengthString(size_t max_length) {
+ std::scoped_lock _l(mLock);
+ return FuzzedDataProvider::ConsumeRandomLengthString(max_length);
+ }
+
+ std::string ConsumeRandomLengthString() {
+ std::scoped_lock _l(mLock);
+ return FuzzedDataProvider::ConsumeRandomLengthString();
+ }
+
+ std::string ConsumeRemainingBytesAsString() {
+ std::scoped_lock _l(mLock);
+ return FuzzedDataProvider::ConsumeRemainingBytesAsString();
+ }
+
+ template <typename T>
+ T ConsumeIntegral() {
+ std::scoped_lock _l(mLock);
+ return FuzzedDataProvider::ConsumeIntegral<T>();
+ }
+
+ template <typename T>
+ T ConsumeIntegralInRange(T min, T max) {
+ std::scoped_lock _l(mLock);
+ return FuzzedDataProvider::ConsumeIntegralInRange<T>(min, max);
+ }
+
+ template <typename T>
+ T ConsumeFloatingPoint() {
+ std::scoped_lock _l(mLock);
+ return FuzzedDataProvider::ConsumeFloatingPoint<T>();
+ }
+
+ template <typename T>
+ T ConsumeFloatingPointInRange(T min, T max) {
+ std::scoped_lock _l(mLock);
+ return FuzzedDataProvider::ConsumeFloatingPointInRange<T>(min, max);
+ }
+
+ template <typename T>
+ T ConsumeProbability() {
+ std::scoped_lock _l(mLock);
+ return FuzzedDataProvider::ConsumeProbability<T>();
+ }
+
+ bool ConsumeBool() {
+ std::scoped_lock _l(mLock);
+ return FuzzedDataProvider::ConsumeBool();
+ }
+
+ template <typename T>
+ T ConsumeEnum() {
+ std::scoped_lock _l(mLock);
+ return FuzzedDataProvider::ConsumeEnum<T>();
+ }
+
+ template <typename T, size_t size>
+ T PickValueInArray(const T (&array)[size]) {
+ std::scoped_lock _l(mLock);
+ return FuzzedDataProvider::PickValueInArray(array);
+ }
+
+ template <typename T, size_t size>
+ T PickValueInArray(const std::array<T, size>& array) {
+ std::scoped_lock _l(mLock);
+ return FuzzedDataProvider::PickValueInArray(array);
+ }
+
+ template <typename T>
+ T PickValueInArray(std::initializer_list<const T> list) {
+ std::scoped_lock _l(mLock);
+ return FuzzedDataProvider::PickValueInArray(list);
+ }
+
+ size_t ConsumeData(void* destination, size_t num_bytes) {
+ std::scoped_lock _l(mLock);
+ return FuzzedDataProvider::ConsumeData(destination, num_bytes);
+ }
+
+ size_t remaining_bytes() {
+ std::scoped_lock _l(mLock);
+ return FuzzedDataProvider::remaining_bytes();
+ }
+};
diff --git a/services/sensorservice/SensorEventConnection.cpp b/services/sensorservice/SensorEventConnection.cpp
index 82d0295..b94b1c0 100644
--- a/services/sensorservice/SensorEventConnection.cpp
+++ b/services/sensorservice/SensorEventConnection.cpp
@@ -82,7 +82,7 @@
void SensorService::SensorEventConnection::dump(String8& result) {
Mutex::Autolock _l(mConnectionLock);
result.appendFormat("\tOperating Mode: ");
- if (!mService->isWhiteListedPackage(getPackageName())) {
+ if (!mService->isAllowListedPackage(getPackageName())) {
result.append("RESTRICTED\n");
} else if (mDataInjectionMode) {
result.append("DATA_INJECTION\n");
@@ -124,7 +124,7 @@
using namespace service::SensorEventConnectionProto;
Mutex::Autolock _l(mConnectionLock);
- if (!mService->isWhiteListedPackage(getPackageName())) {
+ if (!mService->isAllowListedPackage(getPackageName())) {
proto->write(OPERATING_MODE, OP_MODE_RESTRICTED);
} else if (mDataInjectionMode) {
proto->write(OPERATING_MODE, OP_MODE_DATA_INJECTION);
@@ -850,6 +850,11 @@
// Unregister call backs.
return 0;
}
+ if (!mService->isAllowListedPackage(mPackageName)) {
+ ALOGE("App not allowed to inject data, dropping event"
+ "package=%s uid=%d", mPackageName.string(), mUid);
+ return 0;
+ }
sensors_event_t sensor_event;
memcpy(&sensor_event, buf, sizeof(sensors_event_t));
std::shared_ptr<SensorInterface> si =
diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp
index 6504b79..5c98614 100644
--- a/services/sensorservice/SensorService.cpp
+++ b/services/sensorservice/SensorService.cpp
@@ -52,6 +52,7 @@
#include "SensorEventConnection.h"
#include "SensorRecord.h"
#include "SensorRegistrationInfo.h"
+#include "SensorServiceUtils.h"
#include <inttypes.h>
#include <math.h>
@@ -64,6 +65,7 @@
#include <ctime>
#include <future>
+#include <string>
#include <private/android_filesystem_config.h>
@@ -547,55 +549,22 @@
if (args.size() > 2) {
return INVALID_OPERATION;
}
- ConnectionSafeAutolock connLock = mConnectionHolder.lock(mLock);
- SensorDevice& dev(SensorDevice::getInstance());
- if (args.size() == 2 && args[0] == String16("restrict")) {
- // If already in restricted mode. Ignore.
- if (mCurrentOperatingMode == RESTRICTED) {
- return status_t(NO_ERROR);
+ if (args.size() > 0) {
+ Mode targetOperatingMode = NORMAL;
+ std::string inputStringMode = String8(args[0]).string();
+ if (getTargetOperatingMode(inputStringMode, &targetOperatingMode)) {
+ status_t error = changeOperatingMode(args, targetOperatingMode);
+ // Dump the latest state only if no error was encountered.
+ if (error != NO_ERROR) {
+ return error;
+ }
}
- // If in any mode other than normal, ignore.
- if (mCurrentOperatingMode != NORMAL) {
- return INVALID_OPERATION;
- }
+ }
- mCurrentOperatingMode = RESTRICTED;
- // temporarily stop all sensor direct report and disable sensors
- disableAllSensorsLocked(&connLock);
- mWhiteListedPackage.setTo(String8(args[1]));
- return status_t(NO_ERROR);
- } else if (args.size() == 1 && args[0] == String16("enable")) {
- // If currently in restricted mode, reset back to NORMAL mode else ignore.
- if (mCurrentOperatingMode == RESTRICTED) {
- mCurrentOperatingMode = NORMAL;
- // enable sensors and recover all sensor direct report
- enableAllSensorsLocked(&connLock);
- }
- if (mCurrentOperatingMode == DATA_INJECTION) {
- resetToNormalModeLocked();
- }
- mWhiteListedPackage.clear();
- return status_t(NO_ERROR);
- } else if (args.size() == 2 && args[0] == String16("data_injection")) {
- if (mCurrentOperatingMode == NORMAL) {
- dev.disableAllSensors();
- status_t err = dev.setMode(DATA_INJECTION);
- if (err == NO_ERROR) {
- mCurrentOperatingMode = DATA_INJECTION;
- } else {
- // Re-enable sensors.
- dev.enableAllSensors();
- }
- mWhiteListedPackage.setTo(String8(args[1]));
- return NO_ERROR;
- } else if (mCurrentOperatingMode == DATA_INJECTION) {
- // Already in DATA_INJECTION mode. Treat this as a no_op.
- return NO_ERROR;
- } else {
- // Transition to data injection mode supported only from NORMAL mode.
- return INVALID_OPERATION;
- }
- } else if (args.size() == 1 && args[0] == String16("--proto")) {
+ ConnectionSafeAutolock connLock = mConnectionHolder.lock(mLock);
+ // Run the following logic if a transition isn't requested above based on the input
+ // argument parsing.
+ if (args.size() == 1 && args[0] == String16("--proto")) {
return dumpProtoLocked(fd, &connLock);
} else if (!mSensors.hasAnySensor()) {
result.append("No Sensors on the device\n");
@@ -654,10 +623,18 @@
result.appendFormat(" NORMAL\n");
break;
case RESTRICTED:
- result.appendFormat(" RESTRICTED : %s\n", mWhiteListedPackage.string());
+ result.appendFormat(" RESTRICTED : %s\n", mAllowListedPackage.string());
break;
case DATA_INJECTION:
- result.appendFormat(" DATA_INJECTION : %s\n", mWhiteListedPackage.string());
+ result.appendFormat(" DATA_INJECTION : %s\n", mAllowListedPackage.string());
+ break;
+ case REPLAY_DATA_INJECTION:
+ result.appendFormat(" REPLAY_DATA_INJECTION : %s\n",
+ mAllowListedPackage.string());
+ break;
+ default:
+ result.appendFormat(" UNKNOWN\n");
+ break;
}
result.appendFormat("Sensor Privacy: %s\n",
mSensorPrivacyPolicy->isSensorPrivacyEnabled() ? "enabled" : "disabled");
@@ -775,11 +752,11 @@
break;
case RESTRICTED:
proto.write(OPERATING_MODE, OP_MODE_RESTRICTED);
- proto.write(WHITELISTED_PACKAGE, std::string(mWhiteListedPackage.string()));
+ proto.write(WHITELISTED_PACKAGE, std::string(mAllowListedPackage.string()));
break;
case DATA_INJECTION:
proto.write(OPERATING_MODE, OP_MODE_DATA_INJECTION);
- proto.write(WHITELISTED_PACKAGE, std::string(mWhiteListedPackage.string()));
+ proto.write(WHITELISTED_PACKAGE, std::string(mAllowListedPackage.string()));
break;
default:
proto.write(OPERATING_MODE, OP_MODE_UNKNOWN);
@@ -1498,8 +1475,10 @@
sp<ISensorEventConnection> SensorService::createSensorEventConnection(const String8& packageName,
int requestedMode, const String16& opPackageName, const String16& attributionTag) {
- // Only 2 modes supported for a SensorEventConnection ... NORMAL and DATA_INJECTION.
- if (requestedMode != NORMAL && requestedMode != DATA_INJECTION) {
+ // Only 3 modes supported for a SensorEventConnection ... NORMAL, DATA_INJECTION and
+ // REPLAY_DATA_INJECTION.
+ if (requestedMode != NORMAL && requestedMode != DATA_INJECTION &&
+ requestedMode != REPLAY_DATA_INJECTION) {
return nullptr;
}
resetTargetSdkVersionCache(opPackageName);
@@ -1509,7 +1488,7 @@
// operating in DI mode.
if (requestedMode == DATA_INJECTION) {
if (mCurrentOperatingMode != DATA_INJECTION) return nullptr;
- if (!isWhiteListedPackage(packageName)) return nullptr;
+ if (!isAllowListedPackage(packageName)) return nullptr;
}
uid_t uid = IPCThreadState::self()->getCallingUid();
@@ -1520,8 +1499,9 @@
String16 connOpPackageName =
(opPackageName == String16("")) ? String16(connPackageName) : opPackageName;
sp<SensorEventConnection> result(new SensorEventConnection(this, uid, connPackageName,
- requestedMode == DATA_INJECTION, connOpPackageName, attributionTag));
- if (requestedMode == DATA_INJECTION) {
+ requestedMode == DATA_INJECTION || requestedMode == REPLAY_DATA_INJECTION,
+ connOpPackageName, attributionTag));
+ if (requestedMode == DATA_INJECTION || requestedMode == REPLAY_DATA_INJECTION) {
mConnectionHolder.addEventConnectionIfNotPresent(result);
// Add the associated file descriptor to the Looper for polling whenever there is data to
// be injected.
@@ -1880,8 +1860,8 @@
}
ConnectionSafeAutolock connLock = mConnectionHolder.lock(mLock);
- if (mCurrentOperatingMode != NORMAL
- && !isWhiteListedPackage(connection->getPackageName())) {
+ if (mCurrentOperatingMode != NORMAL && mCurrentOperatingMode != REPLAY_DATA_INJECTION &&
+ !isAllowListedPackage(connection->getPackageName())) {
return INVALID_OPERATION;
}
@@ -2229,6 +2209,95 @@
}
}
+bool SensorService::getTargetOperatingMode(const std::string &inputString, Mode *targetModeOut) {
+ if (inputString == std::string("restrict")) {
+ *targetModeOut = RESTRICTED;
+ return true;
+ }
+ if (inputString == std::string("enable")) {
+ *targetModeOut = NORMAL;
+ return true;
+ }
+ if (inputString == std::string("data_injection")) {
+ *targetModeOut = DATA_INJECTION;
+ return true;
+ }
+ if (inputString == std::string("replay_data_injection")) {
+ *targetModeOut = REPLAY_DATA_INJECTION;
+ return true;
+ }
+ return false;
+}
+
+status_t SensorService::changeOperatingMode(const Vector<String16>& args,
+ Mode targetOperatingMode) {
+ ConnectionSafeAutolock connLock = mConnectionHolder.lock(mLock);
+ SensorDevice& dev(SensorDevice::getInstance());
+ if (mCurrentOperatingMode == targetOperatingMode) {
+ return NO_ERROR;
+ }
+ if (targetOperatingMode != NORMAL && args.size() < 2) {
+ return INVALID_OPERATION;
+ }
+ switch (targetOperatingMode) {
+ case NORMAL:
+ // If currently in restricted mode, reset back to NORMAL mode else ignore.
+ if (mCurrentOperatingMode == RESTRICTED) {
+ mCurrentOperatingMode = NORMAL;
+ // enable sensors and recover all sensor direct report
+ enableAllSensorsLocked(&connLock);
+ }
+ if (mCurrentOperatingMode == REPLAY_DATA_INJECTION) {
+ dev.disableAllSensors();
+ }
+ if (mCurrentOperatingMode == DATA_INJECTION ||
+ mCurrentOperatingMode == REPLAY_DATA_INJECTION) {
+ resetToNormalModeLocked();
+ }
+ mAllowListedPackage.clear();
+ return status_t(NO_ERROR);
+ case RESTRICTED:
+ // If in any mode other than normal, ignore.
+ if (mCurrentOperatingMode != NORMAL) {
+ return INVALID_OPERATION;
+ }
+
+ mCurrentOperatingMode = RESTRICTED;
+ // temporarily stop all sensor direct report and disable sensors
+ disableAllSensorsLocked(&connLock);
+ mAllowListedPackage.setTo(String8(args[1]));
+ return status_t(NO_ERROR);
+ case REPLAY_DATA_INJECTION:
+ if (SensorServiceUtil::isUserBuild()) {
+ return INVALID_OPERATION;
+ }
+ FALLTHROUGH_INTENDED;
+ case DATA_INJECTION:
+ if (mCurrentOperatingMode == NORMAL) {
+ dev.disableAllSensors();
+ // Always use DATA_INJECTION here since this value goes to the HAL and the HAL
+ // doesn't have an understanding of replay vs. normal data injection.
+ status_t err = dev.setMode(DATA_INJECTION);
+ if (err == NO_ERROR) {
+ mCurrentOperatingMode = targetOperatingMode;
+ }
+ if (err != NO_ERROR || targetOperatingMode == REPLAY_DATA_INJECTION) {
+ // Re-enable sensors.
+ dev.enableAllSensors();
+ }
+ mAllowListedPackage.setTo(String8(args[1]));
+ return NO_ERROR;
+ } else {
+ // Transition to data injection mode supported only from NORMAL mode.
+ return INVALID_OPERATION;
+ }
+ break;
+ default:
+ break;
+ }
+ return NO_ERROR;
+}
+
void SensorService::checkWakeLockState() {
ConnectionSafeAutolock connLock = mConnectionHolder.lock(mLock);
checkWakeLockStateLocked(&connLock);
@@ -2258,14 +2327,14 @@
}
}
-bool SensorService::isWhiteListedPackage(const String8& packageName) {
- return (packageName.contains(mWhiteListedPackage.string()));
+bool SensorService::isAllowListedPackage(const String8& packageName) {
+ return (packageName.contains(mAllowListedPackage.string()));
}
bool SensorService::isOperationRestrictedLocked(const String16& opPackageName) {
if (mCurrentOperatingMode == RESTRICTED) {
String8 package(opPackageName);
- return !isWhiteListedPackage(package);
+ return !isAllowListedPackage(package);
}
return false;
}
diff --git a/services/sensorservice/SensorService.h b/services/sensorservice/SensorService.h
index 78df501..0798279 100644
--- a/services/sensorservice/SensorService.h
+++ b/services/sensorservice/SensorService.h
@@ -102,8 +102,7 @@
// Step Detector etc. Typically in this mode, there will be a client (a
// SensorEventConnection) which will be injecting sensor data into the HAL. Normal apps can
// unregister and register for any sensor that supports injection. Registering to sensors
- // that do not support injection will give an error. TODO: Allow exactly one
- // client to inject sensor data at a time.
+ // that do not support injection will give an error.
DATA_INJECTION = 1,
// This mode is used only for testing sensors. Each sensor can be tested in isolation with
// the required sampling_rate and maxReportLatency parameters without having to think about
@@ -116,10 +115,14 @@
// corresponding parameters if the application hasn't unregistered for sensors in the mean
// time. NOTE: Non allowlisted app whose sensors were previously deactivated may still
// receive events if a allowlisted app requests data from the same sensor.
- RESTRICTED = 2
+ RESTRICTED = 2,
+ // Mostly equivalent to DATA_INJECTION with the difference being that the injected data is
+ // delivered to all requesting apps rather than just the package allowed to inject data.
+ // This mode is only allowed to be used on development builds.
+ REPLAY_DATA_INJECTION = 3,
// State Transitions supported.
- // RESTRICTED <--- NORMAL ---> DATA_INJECTION
+ // RESTRICTED <--- NORMAL ---> DATA_INJECTION/REPLAY_DATA_INJECTION
// ---> <---
// Shell commands to switch modes in SensorService.
@@ -395,6 +398,9 @@
static bool hasPermissionForSensor(const Sensor& sensor);
static int getTargetSdkVersion(const String16& opPackageName);
static void resetTargetSdkVersionCache(const String16& opPackageName);
+ // Checks if the provided target operating mode is valid and returns the enum if it is.
+ static bool getTargetOperatingMode(const std::string &inputString, Mode *targetModeOut);
+ status_t changeOperatingMode(const Vector<String16>& args, Mode targetOperatingMode);
// SensorService acquires a partial wakelock for delivering events from wake up sensors. This
// method checks whether all the events from these wake up sensors have been delivered to the
// corresponding applications, if yes the wakelock is released.
@@ -420,7 +426,7 @@
// If SensorService is operating in RESTRICTED mode, only select whitelisted packages are
// allowed to register for or call flush on sensors. Typically only cts test packages are
// allowed.
- bool isWhiteListedPackage(const String8& packageName);
+ bool isAllowListedPackage(const String8& packageName);
// Returns true if a connection with the specified opPackageName has no access to sensors
// in the RESTRICTED mode (i.e. the service is in RESTRICTED mode, and the package is not
@@ -519,7 +525,7 @@
// applications with this packageName are allowed to activate/deactivate or call flush on
// sensors. To run CTS this is can be set to ".cts." and only CTS tests will get access to
// sensors.
- String8 mWhiteListedPackage;
+ String8 mAllowListedPackage;
int mNextSensorRegIndex;
Vector<SensorRegistrationInfo> mLastNSensorRegistrations;
diff --git a/services/sensorservice/SensorServiceUtils.cpp b/services/sensorservice/SensorServiceUtils.cpp
index 6bad962..46b4b5b 100644
--- a/services/sensorservice/SensorServiceUtils.cpp
+++ b/services/sensorservice/SensorServiceUtils.cpp
@@ -16,6 +16,7 @@
#include "SensorServiceUtils.h"
+#include <android-base/properties.h>
#include <hardware/sensors.h>
namespace android {
@@ -76,5 +77,10 @@
}
}
+bool isUserBuild() {
+ std::string buildType = android::base::GetProperty("ro.build.type", "user");
+ return "user" == buildType;
+}
+
} // namespace SensorServiceUtil
} // namespace android;
diff --git a/services/sensorservice/SensorServiceUtils.h b/services/sensorservice/SensorServiceUtils.h
index 49457cf..a6e0d6b 100644
--- a/services/sensorservice/SensorServiceUtils.h
+++ b/services/sensorservice/SensorServiceUtils.h
@@ -38,6 +38,11 @@
size_t eventSizeBySensorType(int type);
+/**
+ * Returns true if on a user (production) build.
+ */
+bool isUserBuild();
+
} // namespace SensorServiceUtil
} // namespace android;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h
index 6e9ea6f..b6a4240 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h
@@ -83,7 +83,7 @@
// If the buffer is already in the cache, the buffer is null to optimize away sending HWC the
// buffer handle.
//
- HwcSlotAndBuffer getHwcSlotAndBufferForOverride(const sp<GraphicBuffer>& buffer);
+ HwcSlotAndBuffer getOverrideHwcSlotAndBuffer(const sp<GraphicBuffer>& buffer);
//
// When a client process discards a buffer, it needs to be purged from the HWC cache.
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
index 2b383c1..b86782f 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
@@ -136,6 +136,10 @@
// cost of sending reused buffers to the HWC.
HwcBufferCache hwcBufferCache;
+ // The previously-active buffer for this layer.
+ uint64_t activeBufferId;
+ uint32_t activeBufferSlot;
+
// Set to true when overridden info has been sent to HW composer
bool stateOverridden = false;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
index 14922a4..12e063b 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
@@ -36,6 +36,7 @@
// sp<StrictMock<LayerFE>>::make()
friend class sp<LayerFE>;
friend class testing::StrictMock<LayerFE>;
+ friend class testing::NiceMock<LayerFE>;
public:
virtual ~LayerFE();
diff --git a/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp b/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp
index d64fd57..f0105b2 100644
--- a/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp
@@ -22,17 +22,12 @@
namespace android::compositionengine::impl {
HwcBufferCache::HwcBufferCache() {
- for (uint32_t i = 0; i < kMaxLayerBufferCount; i++) {
+ for (uint32_t i = kMaxLayerBufferCount; i-- > 0;) {
mFreeSlots.push(i);
}
}
HwcSlotAndBuffer HwcBufferCache::getHwcSlotAndBuffer(const sp<GraphicBuffer>& buffer) {
- // TODO(b/261930578): This is for unit tests which don't mock GraphicBuffers but instead send
- // in nullptrs.
- if (buffer == nullptr) {
- return {0, nullptr};
- }
if (auto i = mCacheByBufferId.find(buffer->getId()); i != mCacheByBufferId.end()) {
Cache& cache = i->second;
// mark this cache slot as more recently used so it won't get evicted anytime soon
@@ -42,7 +37,7 @@
return {cache(buffer), buffer};
}
-HwcSlotAndBuffer HwcBufferCache::getHwcSlotAndBufferForOverride(const sp<GraphicBuffer>& buffer) {
+HwcSlotAndBuffer HwcBufferCache::getOverrideHwcSlotAndBuffer(const sp<GraphicBuffer>& buffer) {
if (buffer == mLastOverrideBuffer) {
return {kOverrideBufferSlot, nullptr};
}
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index a7c24b6..60a2c83 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -610,16 +610,37 @@
}
}
-void OutputLayer::uncacheBuffers(std::vector<uint64_t> const& bufferIdsToUncache) {
+void OutputLayer::uncacheBuffers(const std::vector<uint64_t>& bufferIdsToUncache) {
auto& state = editState();
// Skip doing this if there is no HWC interface
if (!state.hwc) {
return;
}
- for (auto bufferId : bufferIdsToUncache) {
- state.hwc->hwcBufferCache.uncache(bufferId);
- // TODO(b/258196272): send uncache requests to Composer HAL
+ // Uncache the active buffer last so that it's the first buffer to be purged from the cache
+ // next time a buffer is sent to this layer.
+ bool uncacheActiveBuffer = false;
+
+ std::vector<uint32_t> slotsToClear;
+ for (uint64_t bufferId : bufferIdsToUncache) {
+ if (bufferId == state.hwc->activeBufferId) {
+ uncacheActiveBuffer = true;
+ } else {
+ uint32_t slot = state.hwc->hwcBufferCache.uncache(bufferId);
+ if (slot != UINT32_MAX) {
+ slotsToClear.push_back(slot);
+ }
+ }
+ }
+ if (uncacheActiveBuffer) {
+ slotsToClear.push_back(state.hwc->hwcBufferCache.uncache(state.hwc->activeBufferId));
+ }
+
+ hal::Error error =
+ state.hwc->hwcLayer->setBufferSlotsToClear(slotsToClear, state.hwc->activeBufferSlot);
+ if (error != hal::Error::NONE) {
+ ALOGE("[%s] Failed to clear buffer slots: %s (%d)", getLayerFE().getDebugName(),
+ to_string(error).c_str(), static_cast<int32_t>(error));
}
}
@@ -637,15 +658,28 @@
HwcSlotAndBuffer hwcSlotAndBuffer;
sp<Fence> hwcFence;
- // Override buffers use a special cache slot so that they don't evict client buffers.
- if (getState().overrideInfo.buffer != nullptr && !skipLayer) {
- hwcSlotAndBuffer = editState().hwc->hwcBufferCache.getHwcSlotAndBufferForOverride(
- getState().overrideInfo.buffer->getBuffer());
- hwcFence = getState().overrideInfo.acquireFence;
- } else {
- hwcSlotAndBuffer =
- editState().hwc->hwcBufferCache.getHwcSlotAndBuffer(outputIndependentState.buffer);
- hwcFence = outputIndependentState.acquireFence;
+ {
+ // Editing the state only because we update the HWC buffer cache and active buffer.
+ auto& state = editState();
+ // Override buffers use a special cache slot so that they don't evict client buffers.
+ if (state.overrideInfo.buffer != nullptr && !skipLayer) {
+ hwcSlotAndBuffer = state.hwc->hwcBufferCache.getOverrideHwcSlotAndBuffer(
+ state.overrideInfo.buffer->getBuffer());
+ hwcFence = state.overrideInfo.acquireFence;
+ // Keep track of the active buffer ID so when it's discarded we uncache it last so its
+ // slot will be used first, allowing the memory to be freed as soon as possible.
+ state.hwc->activeBufferId = state.overrideInfo.buffer->getBuffer()->getId();
+ } else {
+ hwcSlotAndBuffer =
+ state.hwc->hwcBufferCache.getHwcSlotAndBuffer(outputIndependentState.buffer);
+ hwcFence = outputIndependentState.acquireFence;
+ // Keep track of the active buffer ID so when it's discarded we uncache it last so its
+ // slot will be used first, allowing the memory to be freed as soon as possible.
+ state.hwc->activeBufferId = outputIndependentState.buffer->getId();
+ }
+ // Keep track of the active buffer slot, so we can restore it after clearing other buffer
+ // slots.
+ state.hwc->activeBufferSlot = hwcSlotAndBuffer.slot;
}
if (auto error = hwcLayer->setBuffer(hwcSlotAndBuffer.slot, hwcSlotAndBuffer.buffer, hwcFence);
diff --git a/services/surfaceflinger/CompositionEngine/tests/HwcBufferCacheTest.cpp b/services/surfaceflinger/CompositionEngine/tests/HwcBufferCacheTest.cpp
index cf72e8f..c5fb594 100644
--- a/services/surfaceflinger/CompositionEngine/tests/HwcBufferCacheTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/HwcBufferCacheTest.cpp
@@ -115,20 +115,20 @@
EXPECT_EQ(cache.uncache(mBuffer2->getId()), UINT32_MAX);
}
-TEST_F(HwcBufferCacheTest, getHwcSlotAndBufferForOverride_whenCached_returnsSameSlotAndNullBuffer) {
+TEST_F(HwcBufferCacheTest, getOverrideHwcSlotAndBuffer_whenCached_returnsSameSlotAndNullBuffer) {
HwcBufferCache cache;
sp<GraphicBuffer> outBuffer;
- HwcSlotAndBuffer originalSlotAndBuffer = cache.getHwcSlotAndBufferForOverride(mBuffer1);
+ HwcSlotAndBuffer originalSlotAndBuffer = cache.getOverrideHwcSlotAndBuffer(mBuffer1);
EXPECT_NE(originalSlotAndBuffer.slot, UINT32_MAX);
EXPECT_EQ(originalSlotAndBuffer.buffer, mBuffer1);
- HwcSlotAndBuffer finalSlotAndBuffer = cache.getHwcSlotAndBufferForOverride(mBuffer1);
+ HwcSlotAndBuffer finalSlotAndBuffer = cache.getOverrideHwcSlotAndBuffer(mBuffer1);
EXPECT_EQ(finalSlotAndBuffer.slot, originalSlotAndBuffer.slot);
EXPECT_EQ(finalSlotAndBuffer.buffer, nullptr);
}
-TEST_F(HwcBufferCacheTest, getHwcSlotAndBufferForOverride_whenSlotsFull_returnsIndependentSlot) {
+TEST_F(HwcBufferCacheTest, getOverrideHwcSlotAndBuffer_whenSlotsFull_returnsIndependentSlot) {
HwcBufferCache cache;
sp<GraphicBuffer> outBuffer;
@@ -149,7 +149,7 @@
sp<GraphicBuffer> overrideBuffer =
sp<GraphicBuffer>::make(1u, 1u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, 0u);
- HwcSlotAndBuffer overrideSlotAndBuffer = cache.getHwcSlotAndBufferForOverride(overrideBuffer);
+ HwcSlotAndBuffer overrideSlotAndBuffer = cache.getOverrideHwcSlotAndBuffer(overrideBuffer);
// expect us to have a slot number
EXPECT_NE(overrideSlotAndBuffer.slot, UINT32_MAX);
// expect this to be the first time we cached the buffer
@@ -173,7 +173,7 @@
HwcBufferCache cache;
sp<GraphicBuffer> outBuffer;
- HwcSlotAndBuffer hwcSlotAndBuffer = cache.getHwcSlotAndBufferForOverride(mBuffer1);
+ HwcSlotAndBuffer hwcSlotAndBuffer = cache.getOverrideHwcSlotAndBuffer(mBuffer1);
ASSERT_NE(hwcSlotAndBuffer.slot, UINT32_MAX);
EXPECT_EQ(cache.uncache(mBuffer1->getId()), hwcSlotAndBuffer.slot);
@@ -184,7 +184,7 @@
HwcBufferCache cache;
sp<GraphicBuffer> outBuffer;
- HwcSlotAndBuffer hwcSlotAndBuffer = cache.getHwcSlotAndBufferForOverride(mBuffer1);
+ HwcSlotAndBuffer hwcSlotAndBuffer = cache.getOverrideHwcSlotAndBuffer(mBuffer1);
ASSERT_NE(hwcSlotAndBuffer.slot, UINT32_MAX);
EXPECT_EQ(cache.uncache(mBuffer2->getId()), UINT32_MAX);
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h b/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h
index d933b94..b0b1a02 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h
@@ -56,6 +56,7 @@
MOCK_METHOD3(setBuffer,
Error(uint32_t, const android::sp<android::GraphicBuffer>&,
const android::sp<android::Fence>&));
+ MOCK_METHOD2(setBufferSlotsToClear, Error(const std::vector<uint32_t>&, uint32_t));
MOCK_METHOD1(setSurfaceDamage, Error(const android::Region&));
MOCK_METHOD1(setBlendMode, Error(hal::BlendMode));
MOCK_METHOD1(setColor, Error(aidl::android::hardware::graphics::composer3::Color));
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
index 03cf292..9ad2edb 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
@@ -42,6 +42,8 @@
using testing::_;
using testing::InSequence;
+using testing::Mock;
+using testing::NiceMock;
using testing::Return;
using testing::ReturnRef;
using testing::StrictMock;
@@ -82,13 +84,13 @@
struct OutputLayerTest : public testing::Test {
struct OutputLayer final : public impl::OutputLayer {
- OutputLayer(const compositionengine::Output& output, sp<compositionengine::LayerFE> layerFE)
+ OutputLayer(const compositionengine::Output& output, compositionengine::LayerFE& layerFE)
: mOutput(output), mLayerFE(layerFE) {}
~OutputLayer() override = default;
// compositionengine::OutputLayer overrides
const compositionengine::Output& getOutput() const override { return mOutput; }
- compositionengine::LayerFE& getLayerFE() const override { return *mLayerFE; }
+ compositionengine::LayerFE& getLayerFE() const override { return mLayerFE; }
const impl::OutputLayerCompositionState& getState() const override { return mState; }
impl::OutputLayerCompositionState& editState() override { return mState; }
@@ -96,21 +98,22 @@
void dumpState(std::string& out) const override { mState.dump(out); }
const compositionengine::Output& mOutput;
- sp<compositionengine::LayerFE> mLayerFE;
+ compositionengine::LayerFE& mLayerFE;
impl::OutputLayerCompositionState mState;
};
OutputLayerTest() {
- EXPECT_CALL(*mLayerFE, getDebugName()).WillRepeatedly(Return("Test LayerFE"));
- EXPECT_CALL(mOutput, getName()).WillRepeatedly(ReturnRef(kOutputName));
+ ON_CALL(mLayerFE, getDebugName()).WillByDefault(Return("Test LayerFE"));
+ ON_CALL(mOutput, getName()).WillByDefault(ReturnRef(kOutputName));
- EXPECT_CALL(*mLayerFE, getCompositionState()).WillRepeatedly(Return(&mLayerFEState));
- EXPECT_CALL(mOutput, getState()).WillRepeatedly(ReturnRef(mOutputState));
+ ON_CALL(mLayerFE, getCompositionState()).WillByDefault(Return(&mLayerFEState));
+ ON_CALL(mOutput, getState()).WillByDefault(ReturnRef(mOutputState));
}
- compositionengine::mock::Output mOutput;
- sp<StrictMock<compositionengine::mock::LayerFE>> mLayerFE =
- sp<StrictMock<compositionengine::mock::LayerFE>>::make();
+ NiceMock<compositionengine::mock::Output> mOutput;
+ sp<NiceMock<compositionengine::mock::LayerFE>> mLayerFE_ =
+ sp<NiceMock<compositionengine::mock::LayerFE>>::make();
+ NiceMock<compositionengine::mock::LayerFE>& mLayerFE = *mLayerFE_;
OutputLayer mOutputLayer{mOutput, mLayerFE};
LayerFECompositionState mLayerFEState;
@@ -530,7 +533,7 @@
struct OutputLayerPartialMockForUpdateCompositionState : public impl::OutputLayer {
OutputLayerPartialMockForUpdateCompositionState(const compositionengine::Output& output,
- sp<compositionengine::LayerFE> layerFE)
+ compositionengine::LayerFE& layerFE)
: mOutput(output), mLayerFE(layerFE) {}
// Mock everything called by updateCompositionState to simplify testing it.
MOCK_CONST_METHOD1(calculateOutputSourceCrop, FloatRect(uint32_t));
@@ -539,7 +542,7 @@
// compositionengine::OutputLayer overrides
const compositionengine::Output& getOutput() const override { return mOutput; }
- compositionengine::LayerFE& getLayerFE() const override { return *mLayerFE; }
+ compositionengine::LayerFE& getLayerFE() const override { return mLayerFE; }
const impl::OutputLayerCompositionState& getState() const override { return mState; }
impl::OutputLayerCompositionState& editState() override { return mState; }
@@ -547,7 +550,7 @@
MOCK_CONST_METHOD1(dumpState, void(std::string&));
const compositionengine::Output& mOutput;
- sp<compositionengine::LayerFE> mLayerFE;
+ compositionengine::LayerFE& mLayerFE;
impl::OutputLayerCompositionState mState;
};
@@ -588,7 +591,7 @@
};
TEST_F(OutputLayerUpdateCompositionStateTest, doesNothingIfNoFECompositionState) {
- EXPECT_CALL(*mLayerFE, getCompositionState()).WillOnce(Return(nullptr));
+ EXPECT_CALL(mLayerFE, getCompositionState()).WillOnce(Return(nullptr));
mOutputLayer.updateCompositionState(true, false, ui::Transform::RotationFlags::ROT_90);
}
@@ -833,7 +836,6 @@
EXPECT_CALL(mDisplayColorProfile, getSupportedPerFrameMetadata())
.WillRepeatedly(Return(kSupportedPerFrameMetadata));
}
-
// Some tests may need to simulate unsupported HWC calls
enum class SimulateUnsupported { None, ColorTransform };
@@ -952,7 +954,10 @@
const HdrMetadata OutputLayerWriteStateToHWCTest::kHdrMetadata{{/* LightFlattenable */}, 1029};
native_handle_t* OutputLayerWriteStateToHWCTest::kSidebandStreamHandle =
reinterpret_cast<native_handle_t*>(1031);
-const sp<GraphicBuffer> OutputLayerWriteStateToHWCTest::kBuffer;
+const sp<GraphicBuffer> OutputLayerWriteStateToHWCTest::kBuffer =
+ sp<GraphicBuffer>::make(1, 2, PIXEL_FORMAT_RGBA_8888,
+ AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN |
+ AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN);
const sp<GraphicBuffer> OutputLayerWriteStateToHWCTest::kOverrideBuffer =
sp<GraphicBuffer>::make(4, 5, PIXEL_FORMAT_RGBA_8888,
AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN |
@@ -968,7 +973,7 @@
{4, 5, 6, 7}};
TEST_F(OutputLayerWriteStateToHWCTest, doesNothingIfNoFECompositionState) {
- EXPECT_CALL(*mLayerFE, getCompositionState()).WillOnce(Return(nullptr));
+ EXPECT_CALL(mLayerFE, getCompositionState()).WillOnce(Return(nullptr));
mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
/*zIsOverridden*/ false, /*isPeekingThrough*/ false);
@@ -993,7 +998,7 @@
expectPerFrameCommonCalls();
expectNoSetCompositionTypeCall();
- EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
+ EXPECT_CALL(mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
/*zIsOverridden*/ false, /*isPeekingThrough*/ false);
@@ -1018,7 +1023,6 @@
mLayerFEState.compositionType = Composition::SOLID_COLOR;
expectPerFrameCommonCalls();
- EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
// Setting the composition type should happen before setting the color. We
// check this in this test only by setting up an testing::InSeqeuence
@@ -1038,8 +1042,6 @@
expectSetSidebandHandleCall();
expectSetCompositionTypeCall(Composition::SIDEBAND);
- EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
-
mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
/*zIsOverridden*/ false, /*isPeekingThrough*/ false);
}
@@ -1051,8 +1053,6 @@
expectSetHdrMetadataAndBufferCalls();
expectSetCompositionTypeCall(Composition::CURSOR);
- EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
-
mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
/*zIsOverridden*/ false, /*isPeekingThrough*/ false);
}
@@ -1064,8 +1064,6 @@
expectSetHdrMetadataAndBufferCalls();
expectSetCompositionTypeCall(Composition::DEVICE);
- EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
-
mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
/*zIsOverridden*/ false, /*isPeekingThrough*/ false);
}
@@ -1079,8 +1077,6 @@
expectSetColorCall();
expectNoSetCompositionTypeCall();
- EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
-
mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
/*zIsOverridden*/ false, /*isPeekingThrough*/ false);
}
@@ -1119,8 +1115,6 @@
expectGenericLayerMetadataCalls();
expectSetCompositionTypeCall(Composition::DEVICE);
- EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
-
mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
/*zIsOverridden*/ false, /*isPeekingThrough*/ false);
}
@@ -1133,8 +1127,6 @@
expectSetHdrMetadataAndBufferCalls();
expectSetCompositionTypeCall(Composition::DEVICE);
- EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
-
mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
/*zIsOverridden*/ false, /*isPeekingThrough*/ false);
}
@@ -1149,7 +1141,6 @@
kOverrideSurfaceDamage, kOverrideLayerBrightness);
expectSetHdrMetadataAndBufferCalls();
expectSetCompositionTypeCall(Composition::DEVICE);
- EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false));
mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ true, 0,
/*zIsOverridden*/ false, /*isPeekingThrough*/ false);
@@ -1165,7 +1156,6 @@
kOverrideSurfaceDamage, kOverrideLayerBrightness);
expectSetHdrMetadataAndBufferCalls();
expectSetCompositionTypeCall(Composition::DEVICE);
- EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false));
mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ true, 0,
/*zIsOverridden*/ false, /*isPeekingThrough*/ false);
@@ -1181,7 +1171,6 @@
kOverrideSurfaceDamage, kOverrideLayerBrightness);
expectSetHdrMetadataAndBufferCalls(kOverrideHwcSlot, kOverrideBuffer, kOverrideFence);
expectSetCompositionTypeCall(Composition::DEVICE);
- EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false));
mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
/*zIsOverridden*/ false, /*isPeekingThrough*/ false);
@@ -1197,7 +1186,6 @@
kOverrideSurfaceDamage, kOverrideLayerBrightness);
expectSetHdrMetadataAndBufferCalls(kOverrideHwcSlot, kOverrideBuffer, kOverrideFence);
expectSetCompositionTypeCall(Composition::DEVICE);
- EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false));
mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
/*zIsOverridden*/ false, /*isPeekingThrough*/ false);
@@ -1212,7 +1200,6 @@
Region::INVALID_REGION);
expectSetHdrMetadataAndBufferCalls();
expectSetCompositionTypeCall(Composition::DEVICE);
- EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false));
mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
/*zIsOverridden*/ false, /*isPeekingThrough*/ false);
@@ -1229,7 +1216,6 @@
Region::INVALID_REGION);
expectSetHdrMetadataAndBufferCalls();
expectSetCompositionTypeCall(Composition::DEVICE);
- EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
/*zIsOverridden*/ false, /*isPeekingThrough*/ false);
@@ -1247,22 +1233,20 @@
Region::INVALID_REGION);
expectSetHdrMetadataAndBufferCalls();
expectSetCompositionTypeCall(Composition::CLIENT);
- EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false));
mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
/*zIsOverridden*/ false, /*isPeekingThrough*/ false);
}
TEST_F(OutputLayerWriteStateToHWCTest, peekThroughChangesBlendMode) {
- auto peekThroughLayerFE = sp<compositionengine::mock::LayerFE>::make();
- OutputLayer peekThroughLayer{mOutput, peekThroughLayerFE};
+ auto peekThroughLayerFE = sp<NiceMock<compositionengine::mock::LayerFE>>::make();
+ OutputLayer peekThroughLayer{mOutput, *peekThroughLayerFE};
mOutputLayer.mState.overrideInfo.peekThroughLayer = &peekThroughLayer;
expectGeometryCommonCalls(kDisplayFrame, kSourceCrop, kBufferTransform,
Hwc2::IComposerClient::BlendMode::PREMULTIPLIED);
expectPerFrameCommonCalls();
- EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
/*zIsOverridden*/ false, /*isPeekingThrough*/ false);
@@ -1280,7 +1264,6 @@
TEST_F(OutputLayerWriteStateToHWCTest, zIsOverriddenSetsOverride) {
expectGeometryCommonCalls();
expectPerFrameCommonCalls();
- EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
/*zIsOverridden*/ true, /*isPeekingThrough*/
@@ -1291,7 +1274,7 @@
TEST_F(OutputLayerWriteStateToHWCTest, roundedCornersForceClientComposition) {
expectGeometryCommonCalls();
expectPerFrameCommonCalls();
- EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(true));
+ EXPECT_CALL(mLayerFE, hasRoundedCorners()).WillOnce(Return(true));
expectSetCompositionTypeCall(Composition::CLIENT);
mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
@@ -1303,7 +1286,7 @@
expectGeometryCommonCalls();
expectPerFrameCommonCalls();
expectSetHdrMetadataAndBufferCalls();
- EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(true));
+ EXPECT_CALL(mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(true));
expectSetCompositionTypeCall(Composition::DEVICE);
mLayerFEState.compositionType = Composition::DEVICE;
@@ -1322,7 +1305,6 @@
expectPerFrameCommonCalls(SimulateUnsupported::None, kDataspace, kOutputSpaceVisibleRegion,
kSurfaceDamage, kLayerBrightness, blockingRegion);
expectSetHdrMetadataAndBufferCalls();
- EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false));
expectSetCompositionTypeCall(Composition::DISPLAY_DECORATION);
mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
@@ -1331,6 +1313,93 @@
}
/*
+ * OutputLayer::uncacheBuffers
+ */
+struct OutputLayerUncacheBufferTest : public OutputLayerTest {
+ static const sp<GraphicBuffer> kBuffer1;
+ static const sp<GraphicBuffer> kBuffer2;
+ static const sp<GraphicBuffer> kBuffer3;
+ static const sp<Fence> kFence;
+
+ OutputLayerUncacheBufferTest() {
+ auto& outputLayerState = mOutputLayer.editState();
+ outputLayerState.hwc = impl::OutputLayerCompositionState::Hwc(mHwcLayer_);
+
+ mLayerFEState.compositionType = Composition::DEVICE;
+ mLayerFEState.acquireFence = kFence;
+
+ ON_CALL(mOutput, getDisplayColorProfile()).WillByDefault(Return(&mDisplayColorProfile));
+ }
+
+ std::shared_ptr<HWC2::mock::Layer> mHwcLayer_{std::make_shared<NiceMock<HWC2::mock::Layer>>()};
+ HWC2::mock::Layer& mHwcLayer = *mHwcLayer_;
+ NiceMock<mock::DisplayColorProfile> mDisplayColorProfile;
+};
+
+const sp<GraphicBuffer> OutputLayerUncacheBufferTest::kBuffer1 =
+ sp<GraphicBuffer>::make(1, 2, PIXEL_FORMAT_RGBA_8888,
+ AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN |
+ AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN);
+const sp<GraphicBuffer> OutputLayerUncacheBufferTest::kBuffer2 =
+ sp<GraphicBuffer>::make(2, 3, PIXEL_FORMAT_RGBA_8888,
+ AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN |
+ AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN);
+const sp<GraphicBuffer> OutputLayerUncacheBufferTest::kBuffer3 =
+ sp<GraphicBuffer>::make(4, 5, PIXEL_FORMAT_RGBA_8888,
+ AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN |
+ AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN);
+const sp<Fence> OutputLayerUncacheBufferTest::kFence = sp<Fence>::make();
+
+TEST_F(OutputLayerUncacheBufferTest, canUncacheAndReuseSlot) {
+ // Buffer1 is stored in slot 0
+ mLayerFEState.buffer = kBuffer1;
+ EXPECT_CALL(mHwcLayer, setBuffer(/*slot*/ 0, kBuffer1, kFence));
+ mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
+ /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+ Mock::VerifyAndClearExpectations(&mHwcLayer);
+
+ // Buffer2 is stored in slot 1
+ mLayerFEState.buffer = kBuffer2;
+ EXPECT_CALL(mHwcLayer, setBuffer(/*slot*/ 1, kBuffer2, kFence));
+ mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
+ /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+ Mock::VerifyAndClearExpectations(&mHwcLayer);
+
+ // Buffer3 is stored in slot 2
+ mLayerFEState.buffer = kBuffer3;
+ EXPECT_CALL(mHwcLayer, setBuffer(/*slot*/ 2, kBuffer3, kFence));
+ mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
+ /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+ Mock::VerifyAndClearExpectations(&mHwcLayer);
+
+ // Buffer2 becomes the active buffer again (with a nullptr) and reuses slot 1
+ mLayerFEState.buffer = kBuffer2;
+ sp<GraphicBuffer> nullBuffer = nullptr;
+ EXPECT_CALL(mHwcLayer, setBuffer(/*slot*/ 1, nullBuffer, kFence));
+ mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
+ /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+ Mock::VerifyAndClearExpectations(&mHwcLayer);
+
+ // Buffer slots are cleared
+ std::vector<uint32_t> slotsToClear = {0, 2, 1}; // order doesn't matter
+ EXPECT_CALL(mHwcLayer, setBufferSlotsToClear(slotsToClear, /*activeBufferSlot*/ 1));
+ // Uncache the active buffer in between other buffers to exercise correct algorithmic behavior.
+ mOutputLayer.uncacheBuffers({kBuffer1->getId(), kBuffer2->getId(), kBuffer3->getId()});
+ Mock::VerifyAndClearExpectations(&mHwcLayer);
+
+ // Buffer1 becomes active again, and rather than allocating a new slot, or re-using slot 0,
+ // the active buffer slot (slot 1 for Buffer2) is reused first, which allows HWC to free the
+ // memory for the active buffer. Note: slot 1 is different from the first and last buffer slot
+ // requested to be cleared in slotsToClear (slot 1), above, indicating that the algorithm
+ // correctly identifies the active buffer as the buffer in slot 1, despite ping-ponging.
+ mLayerFEState.buffer = kBuffer1;
+ EXPECT_CALL(mHwcLayer, setBuffer(/*slot*/ 1, kBuffer1, kFence));
+ mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
+ /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+ Mock::VerifyAndClearExpectations(&mHwcLayer);
+}
+
+/*
* OutputLayer::writeCursorPositionToHWC()
*/
@@ -1358,7 +1427,7 @@
const Rect OutputLayerWriteCursorPositionToHWCTest::kDefaultCursorFrame{1, 2, 3, 4};
TEST_F(OutputLayerWriteCursorPositionToHWCTest, doesNothingIfNoFECompositionState) {
- EXPECT_CALL(*mLayerFE, getCompositionState()).WillOnce(Return(nullptr));
+ EXPECT_CALL(mLayerFE, getCompositionState()).WillOnce(Return(nullptr));
mOutputLayer.writeCursorPositionToHWC();
}
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
index 806df87..e372b72 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -233,6 +233,25 @@
addReader(translate<Display>(kSingleReaderKey));
+ // If unable to read interface version, then become backwards compatible.
+ int32_t version = 1;
+ const auto status = mAidlComposerClient->getInterfaceVersion(&version);
+ if (!status.isOk()) {
+ ALOGE("getInterfaceVersion for AidlComposer constructor failed %s",
+ status.getDescription().c_str());
+ }
+ if (version == 1) {
+ mClearSlotBuffer = sp<GraphicBuffer>::make(1, 1, PIXEL_FORMAT_RGBX_8888,
+ GraphicBuffer::USAGE_HW_COMPOSER |
+ GraphicBuffer::USAGE_SW_READ_OFTEN |
+ GraphicBuffer::USAGE_SW_WRITE_OFTEN,
+ "AidlComposer");
+ if (!mClearSlotBuffer || mClearSlotBuffer->initCheck() != ::android::OK) {
+ LOG_ALWAYS_FATAL("Failed to allocate a buffer for clearing layer buffer slots");
+ return;
+ }
+ }
+
ALOGI("Loaded AIDL composer3 HAL service");
}
@@ -814,6 +833,49 @@
return error;
}
+Error AidlComposer::setLayerBufferSlotsToClear(Display display, Layer layer,
+ const std::vector<uint32_t>& slotsToClear,
+ uint32_t activeBufferSlot) {
+ if (slotsToClear.empty()) {
+ return Error::NONE;
+ }
+
+ Error error = Error::NONE;
+ mMutex.lock_shared();
+ if (auto writer = getWriter(display)) {
+ // Backwards compatible way of clearing buffer is to set the layer buffer with a placeholder
+ // buffer, using the slot that needs to cleared... tricky.
+ if (mClearSlotBuffer == nullptr) {
+ writer->get().setLayerBufferSlotsToClear(translate<int64_t>(display),
+ translate<int64_t>(layer), slotsToClear);
+ } else {
+ for (uint32_t slot : slotsToClear) {
+ // Don't clear the active buffer slot because we need to restore the active buffer
+ // after clearing the requested buffer slots with a placeholder buffer.
+ if (slot != activeBufferSlot) {
+ writer->get().setLayerBufferWithNewCommand(translate<int64_t>(display),
+ translate<int64_t>(layer), slot,
+ mClearSlotBuffer->handle,
+ /*fence*/ -1);
+ }
+ }
+ // Since we clear buffers by setting them to a placeholder buffer, we want to make
+ // sure that the last setLayerBuffer command is sent with the currently active
+ // buffer, not the placeholder buffer, so that there is no perceptual change when
+ // buffers are discarded.
+ writer->get().setLayerBufferWithNewCommand(translate<int64_t>(display),
+ translate<int64_t>(layer), activeBufferSlot,
+ // The active buffer is still cached in
+ // its slot and doesn't need a fence.
+ /*buffer*/ nullptr, /*fence*/ -1);
+ }
+ } else {
+ error = Error::BAD_DISPLAY;
+ }
+ mMutex.unlock_shared();
+ return error;
+}
+
Error AidlComposer::setLayerSurfaceDamage(Display display, Layer layer,
const std::vector<IComposerClient::Rect>& damage) {
Error error = Error::NONE;
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
index 2a043fd..9a7ade7 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
@@ -143,6 +143,9 @@
/* see setClientTarget for the purpose of slot */
Error setLayerBuffer(Display display, Layer layer, uint32_t slot,
const sp<GraphicBuffer>& buffer, int acquireFence) override;
+ Error setLayerBufferSlotsToClear(Display display, Layer layer,
+ const std::vector<uint32_t>& slotsToClear,
+ uint32_t activeBufferSlot) override;
Error setLayerSurfaceDamage(Display display, Layer layer,
const std::vector<IComposerClient::Rect>& damage) override;
Error setLayerBlendMode(Display display, Layer layer, IComposerClient::BlendMode mode) override;
@@ -280,6 +283,9 @@
// threading annotations.
ftl::SharedMutex mMutex;
+ // Buffer slots for layers are cleared by setting the slot buffer to this buffer.
+ sp<GraphicBuffer> mClearSlotBuffer;
+
// Aidl interface
using AidlIComposer = aidl::android::hardware::graphics::composer3::IComposer;
using AidlIComposerClient = aidl::android::hardware::graphics::composer3::IComposerClient;
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h
index ec23935..1c2b8b5 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h
@@ -179,6 +179,9 @@
/* see setClientTarget for the purpose of slot */
virtual Error setLayerBuffer(Display display, Layer layer, uint32_t slot,
const sp<GraphicBuffer>& buffer, int acquireFence) = 0;
+ virtual Error setLayerBufferSlotsToClear(Display display, Layer layer,
+ const std::vector<uint32_t>& slotsToClear,
+ uint32_t activeBufferSlot) = 0;
virtual Error setLayerSurfaceDamage(Display display, Layer layer,
const std::vector<IComposerClient::Rect>& damage) = 0;
virtual Error setLayerBlendMode(Display display, Layer layer,
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index d0126d0..6738f00 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -717,6 +717,16 @@
return static_cast<Error>(intError);
}
+Error Layer::setBufferSlotsToClear(const std::vector<uint32_t>& slotsToClear,
+ uint32_t activeBufferSlot) {
+ if (CC_UNLIKELY(!mDisplay)) {
+ return Error::BAD_DISPLAY;
+ }
+ auto intError = mComposer.setLayerBufferSlotsToClear(mDisplay->getId(), mId, slotsToClear,
+ activeBufferSlot);
+ return static_cast<Error>(intError);
+}
+
Error Layer::setSurfaceDamage(const Region& damage)
{
if (CC_UNLIKELY(!mDisplay)) {
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h
index 4971d19..c1c7070 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.h
+++ b/services/surfaceflinger/DisplayHardware/HWC2.h
@@ -310,6 +310,8 @@
[[nodiscard]] virtual hal::Error setBuffer(uint32_t slot,
const android::sp<android::GraphicBuffer>& buffer,
const android::sp<android::Fence>& acquireFence) = 0;
+ [[nodiscard]] virtual hal::Error setBufferSlotsToClear(
+ const std::vector<uint32_t>& slotsToClear, uint32_t activeBufferSlot) = 0;
[[nodiscard]] virtual hal::Error setSurfaceDamage(const android::Region& damage) = 0;
[[nodiscard]] virtual hal::Error setBlendMode(hal::BlendMode mode) = 0;
@@ -360,6 +362,8 @@
hal::Error setCursorPosition(int32_t x, int32_t y) override;
hal::Error setBuffer(uint32_t slot, const android::sp<android::GraphicBuffer>& buffer,
const android::sp<android::Fence>& acquireFence) override;
+ hal::Error setBufferSlotsToClear(const std::vector<uint32_t>& slotsToClear,
+ uint32_t activeBufferSlot) override;
hal::Error setSurfaceDamage(const android::Region& damage) override;
hal::Error setBlendMode(hal::BlendMode mode) override;
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
index b607df0..c9e1e79 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
@@ -186,9 +186,22 @@
return out;
}
+sp<GraphicBuffer> allocateClearSlotBuffer() {
+ sp<GraphicBuffer> buffer = sp<GraphicBuffer>::make(1, 1, PIXEL_FORMAT_RGBX_8888,
+ GraphicBuffer::USAGE_HW_COMPOSER |
+ GraphicBuffer::USAGE_SW_READ_OFTEN |
+ GraphicBuffer::USAGE_SW_WRITE_OFTEN,
+ "HidlComposer");
+ if (!buffer || buffer->initCheck() != ::android::OK) {
+ return nullptr;
+ }
+ return std::move(buffer);
+}
+
} // anonymous namespace
-HidlComposer::HidlComposer(const std::string& serviceName) : mWriter(kWriterInitialSize) {
+HidlComposer::HidlComposer(const std::string& serviceName)
+ : mClearSlotBuffer(allocateClearSlotBuffer()), mWriter(kWriterInitialSize) {
mComposer = V2_1::IComposer::getService(serviceName);
if (mComposer == nullptr) {
@@ -230,6 +243,11 @@
if (mClient == nullptr) {
LOG_ALWAYS_FATAL("failed to create composer client");
}
+
+ if (!mClearSlotBuffer) {
+ LOG_ALWAYS_FATAL("Failed to allocate a buffer for clearing layer buffer slots");
+ return;
+ }
}
bool HidlComposer::isSupported(OptionalFeature feature) const {
@@ -694,6 +712,32 @@
return Error::NONE;
}
+Error HidlComposer::setLayerBufferSlotsToClear(Display display, Layer layer,
+ const std::vector<uint32_t>& slotsToClear,
+ uint32_t activeBufferSlot) {
+ if (slotsToClear.empty()) {
+ return Error::NONE;
+ }
+ // Backwards compatible way of clearing buffer is to set the layer buffer with a placeholder
+ // buffer, using the slot that needs to cleared... tricky.
+ for (uint32_t slot : slotsToClear) {
+ // Don't clear the active buffer slot because we need to restore the active buffer after
+ // setting the requested buffer slots with a placeholder buffer.
+ if (slot != activeBufferSlot) {
+ mWriter.selectDisplay(display);
+ mWriter.selectLayer(layer);
+ mWriter.setLayerBuffer(slot, mClearSlotBuffer->handle, /*fence*/ -1);
+ }
+ }
+ // Since we clear buffers by setting them to a placeholder buffer, we want to make sure that the
+ // last setLayerBuffer command is sent with the currently active buffer, not the placeholder
+ // buffer, so that there is no perceptual change.
+ mWriter.selectDisplay(display);
+ mWriter.selectLayer(layer);
+ mWriter.setLayerBuffer(activeBufferSlot, /*buffer*/ nullptr, /*fence*/ -1);
+ return Error::NONE;
+}
+
Error HidlComposer::setLayerSurfaceDamage(Display display, Layer layer,
const std::vector<IComposerClient::Rect>& damage) {
mWriter.selectDisplay(display);
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
index 3602bbb..921add5 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
@@ -248,6 +248,9 @@
/* see setClientTarget for the purpose of slot */
Error setLayerBuffer(Display display, Layer layer, uint32_t slot,
const sp<GraphicBuffer>& buffer, int acquireFence) override;
+ Error setLayerBufferSlotsToClear(Display display, Layer layer,
+ const std::vector<uint32_t>& slotsToClear,
+ uint32_t activeBufferSlot) override;
Error setLayerSurfaceDamage(Display display, Layer layer,
const std::vector<IComposerClient::Rect>& damage) override;
Error setLayerBlendMode(Display display, Layer layer, IComposerClient::BlendMode mode) override;
@@ -362,6 +365,9 @@
sp<V2_3::IComposerClient> mClient_2_3;
sp<IComposerClient> mClient_2_4;
+ // Buffer slots for layers are cleared by setting the slot buffer to this buffer.
+ sp<GraphicBuffer> mClearSlotBuffer;
+
// 64KiB minus a small space for metadata such as read/write pointers
static constexpr size_t kWriterInitialSize = 64 * 1024 / sizeof(uint32_t) - 16;
// Max number of buffers that may be cached for a given layer
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
index cd1ba70..27a099c 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
@@ -1175,7 +1175,7 @@
std::optional<size_t> FrameTimeline::getFirstSignalFenceIndex() const {
for (size_t i = 0; i < mPendingPresentFences.size(); i++) {
const auto& [fence, _] = mPendingPresentFences[i];
- if (fence && fence->isValid() && fence->getSignalTime() != Fence::SIGNAL_TIME_PENDING) {
+ if (fence && fence->getSignalTime() != Fence::SIGNAL_TIME_PENDING) {
return i;
}
}
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index 7c9cedfa..55fa402 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -22,9 +22,9 @@
#include <android-base/stringprintf.h>
#include <cutils/properties.h>
+#include <gui/TraceUtils.h>
#include <utils/Log.h>
#include <utils/Timers.h>
-#include <utils/Trace.h>
#include <algorithm>
#include <cmath>
@@ -165,6 +165,7 @@
}
auto LayerHistory::summarize(const RefreshRateSelector& selector, nsecs_t now) -> Summary {
+ ATRACE_CALL();
Summary summary;
std::lock_guard lock(mLock);
@@ -178,6 +179,7 @@
ALOGV("%s has priority: %d %s focused", info->getName().c_str(), frameRateSelectionPriority,
layerFocused ? "" : "not");
+ ATRACE_FORMAT("%s", info->getName().c_str());
const auto vote = info->getRefreshRateVote(selector, now);
// Skip NoVote layer as those don't have any requirements
if (vote.type == LayerVoteType::NoVote) {
@@ -192,6 +194,8 @@
const float layerArea = transformed.getWidth() * transformed.getHeight();
float weight = mDisplayArea ? layerArea / mDisplayArea : 0.0f;
+ ATRACE_FORMAT_INSTANT("%s %s (%d%)", ftl::enum_string(vote.type).c_str(),
+ to_string(vote.fps).c_str(), weight * 100);
summary.push_back({info->getName(), info->getOwnerUid(), vote.type, vote.fps,
vote.seamlessness, weight, layerFocused});
@@ -204,6 +208,7 @@
}
void LayerHistory::partitionLayers(nsecs_t now) {
+ ATRACE_CALL();
const nsecs_t threshold = getActiveLayerThreshold(now);
// iterate over inactive map
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 7247e4b..f9c9023 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -29,6 +29,7 @@
#include <cutils/compiler.h>
#include <cutils/trace.h>
#include <ftl/enum.h>
+#include <gui/TraceUtils.h>
#undef LOG_TAG
#define LOG_TAG "LayerInfo"
@@ -189,6 +190,7 @@
std::optional<Fps> LayerInfo::calculateRefreshRateIfPossible(const RefreshRateSelector& selector,
nsecs_t now) {
+ ATRACE_CALL();
static constexpr float MARGIN = 1.0f; // 1Hz
if (!hasEnoughDataForHeuristic()) {
ALOGV("Not enough data");
@@ -224,18 +226,21 @@
LayerInfo::LayerVote LayerInfo::getRefreshRateVote(const RefreshRateSelector& selector,
nsecs_t now) {
+ ATRACE_CALL();
if (mLayerVote.type != LayerHistory::LayerVoteType::Heuristic) {
ALOGV("%s voted %d ", mName.c_str(), static_cast<int>(mLayerVote.type));
return mLayerVote;
}
if (isAnimating(now)) {
+ ATRACE_FORMAT_INSTANT("animating");
ALOGV("%s is animating", mName.c_str());
mLastRefreshRate.animatingOrInfrequent = true;
return {LayerHistory::LayerVoteType::Max, Fps()};
}
if (!isFrequent(now)) {
+ ATRACE_FORMAT_INSTANT("infrequent");
ALOGV("%s is infrequent", mName.c_str());
mLastRefreshRate.animatingOrInfrequent = true;
// Infrequent layers vote for mininal refresh rate for
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index a05d3df..04c2d41 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -180,7 +180,7 @@
for (auto divisor = start; divisor <= end; divisor++) {
const auto fps = mode->getFps() / divisor;
using fps_approx_ops::operator<;
- if (fps < kMinSupportedFrameRate) {
+ if (divisor > 1 && fps < kMinSupportedFrameRate) {
break;
}
@@ -354,6 +354,7 @@
float RefreshRateSelector::calculateLayerScoreLocked(const LayerRequirement& layer, Fps refreshRate,
bool isSeamlessSwitch) const {
+ ATRACE_CALL();
// Slightly prefer seamless switches.
constexpr float kSeamedSwitchPenalty = 0.95f;
const float seamlessness = isSeamlessSwitch ? 1.0f : kSeamedSwitchPenalty;
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 856fda0..74a81b7 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -661,6 +661,7 @@
template <typename S, typename T>
auto Scheduler::applyPolicy(S Policy::*statePtr, T&& newState) -> GlobalSignals {
+ ATRACE_CALL();
std::vector<display::DisplayModeRequest> modeRequests;
GlobalSignals consideredSignals;
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index b930477..ba5c4d7 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -440,6 +440,9 @@
property_get("debug.sf.treat_170m_as_sRGB", value, "0");
mTreat170mAsSrgb = atoi(value);
+ mIgnoreHwcPhysicalDisplayOrientation =
+ base::GetBoolProperty("debug.sf.ignore_hwc_physical_display_orientation"s, false);
+
// We should be reading 'persist.sys.sf.color_saturation' here
// but since /data may be encrypted, we need to wait until after vold
// comes online to attempt to read the property. The property is
@@ -1489,6 +1492,7 @@
outCombination.dataspaces = std::move(dataspaces);
outProperties->combinations.emplace_back(outCombination);
}
+ outProperties->supportMixedColorSpaces = aidlProperties.supportMixedColorSpaces;
return NO_ERROR;
}
@@ -2442,7 +2446,8 @@
if (!id) {
return ui::ROTATION_0;
}
- if (getHwComposer().getComposer()->isSupported(
+ if (!mIgnoreHwcPhysicalDisplayOrientation &&
+ getHwComposer().getComposer()->isSupported(
Hwc2::Composer::OptionalFeature::PhysicalDisplayOrientation)) {
switch (getHwComposer().getPhysicalDisplayOrientation(*id)) {
case Hwc2::AidlTransform::ROT_90:
@@ -2509,7 +2514,9 @@
const TimePoint compositeTime =
TimePoint::fromNs(mCompositionEngine->getLastFrameRefreshTimestamp());
const Duration presentLatency =
- mPresentLatencyTracker.trackPendingFrame(compositeTime, presentFenceTime);
+ !getHwComposer().hasCapability(Capability::PRESENT_FENCE_IS_NOT_RELIABLE)
+ ? mPresentLatencyTracker.trackPendingFrame(compositeTime, presentFenceTime)
+ : Duration::zero();
const auto& schedule = mScheduler->getVsyncSchedule();
const TimePoint vsyncDeadline = schedule.vsyncDeadlineAfter(presentTime);
@@ -2884,15 +2891,15 @@
const auto enableFrameRateOverride = [&] {
using Config = scheduler::RefreshRateSelector::Config;
- if (!sysprop::enable_frame_rate_override(false)) {
+ if (!sysprop::enable_frame_rate_override(true)) {
return Config::FrameRateOverride::Disabled;
}
- if (sysprop::frame_rate_override_for_native_rates(true)) {
+ if (sysprop::frame_rate_override_for_native_rates(false)) {
return Config::FrameRateOverride::AppOverrideNativeRefreshRates;
}
- if (!sysprop::frame_rate_override_global(false)) {
+ if (!sysprop::frame_rate_override_global(true)) {
return Config::FrameRateOverride::AppOverride;
}
@@ -6509,7 +6516,7 @@
[=, renderAreaFuture = std::move(renderAreaFuture)]() FTL_FAKE_GUARD(
kMainThreadContext) mutable -> ftl::SharedFuture<FenceResult> {
ScreenCaptureResults captureResults;
- std::unique_ptr<RenderArea> renderArea = renderAreaFuture.get();
+ std::shared_ptr<RenderArea> renderArea = renderAreaFuture.get();
if (!renderArea) {
ALOGW("Skipping screen capture because of invalid render area.");
if (captureListener) {
@@ -6521,7 +6528,7 @@
ftl::SharedFuture<FenceResult> renderFuture;
renderArea->render([&]() FTL_FAKE_GUARD(kMainThreadContext) {
- renderFuture = renderScreenImpl(std::move(renderArea), traverseLayers, buffer,
+ renderFuture = renderScreenImpl(renderArea, traverseLayers, buffer,
canCaptureBlackoutContent, regionSampling,
grayscale, captureResults);
});
@@ -6549,7 +6556,7 @@
}
ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl(
- std::unique_ptr<RenderArea> renderArea, TraverseLayersFunction traverseLayers,
+ std::shared_ptr<const RenderArea> renderArea, TraverseLayersFunction traverseLayers,
const std::shared_ptr<renderengine::ExternalTexture>& buffer,
bool canCaptureBlackoutContent, bool regionSampling, bool grayscale,
ScreenCaptureResults& captureResults) {
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 7bb0514..37aa3d5 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -311,6 +311,11 @@
// on this behavior to increase contrast for some media sources.
bool mTreat170mAsSrgb = false;
+ // Allows to ignore physical orientation provided through hwc API in favour of
+ // 'ro.surface_flinger.primary_display_orientation'.
+ // TODO(b/246793311): Clean up a temporary property
+ bool mIgnoreHwcPhysicalDisplayOrientation = false;
+
protected:
// We're reference counted, never destroy SurfaceFlinger directly
virtual ~SurfaceFlinger();
@@ -797,7 +802,7 @@
const std::shared_ptr<renderengine::ExternalTexture>&, bool regionSampling,
bool grayscale, const sp<IScreenCaptureListener>&);
ftl::SharedFuture<FenceResult> renderScreenImpl(
- std::unique_ptr<RenderArea>, TraverseLayersFunction,
+ std::shared_ptr<const RenderArea>, TraverseLayersFunction,
const std::shared_ptr<renderengine::ExternalTexture>&, bool canCaptureBlackoutContent,
bool regionSampling, bool grayscale, ScreenCaptureResults&) EXCLUDES(mStateLock)
REQUIRES(kMainThreadContext);
diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
index f1a6c0e..ab98dbf 100644
--- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
+++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
@@ -27,7 +27,6 @@
#include <log/log.h>
#include <mock/MockEventThread.h>
#include <renderengine/ExternalTexture.h>
-#include <renderengine/mock/FakeExternalTexture.h>
#include <renderengine/mock/RenderEngine.h>
#include <utils/String16.h>
#include <string>
@@ -95,6 +94,30 @@
}
};
+class FakeExternalTexture : public renderengine::ExternalTexture {
+ const sp<GraphicBuffer> mNullBuffer = nullptr;
+ uint32_t mWidth;
+ uint32_t mHeight;
+ uint64_t mId;
+ PixelFormat mPixelFormat;
+ uint64_t mUsage;
+
+public:
+ FakeExternalTexture(uint32_t width, uint32_t height, uint64_t id, PixelFormat pixelFormat,
+ uint64_t usage)
+ : mWidth(width), mHeight(height), mId(id), mPixelFormat(pixelFormat), mUsage(usage) {}
+ const sp<GraphicBuffer>& getBuffer() const { return mNullBuffer; }
+ bool hasSameBuffer(const renderengine::ExternalTexture& other) const override {
+ return getId() == other.getId();
+ }
+ uint32_t getWidth() const override { return mWidth; }
+ uint32_t getHeight() const override { return mHeight; }
+ uint64_t getId() const override { return mId; }
+ PixelFormat getPixelFormat() const override { return mPixelFormat; }
+ uint64_t getUsage() const override { return mUsage; }
+ ~FakeExternalTexture() = default;
+};
+
class MockSurfaceFlinger : public SurfaceFlinger {
public:
MockSurfaceFlinger(Factory& factory)
@@ -102,12 +125,10 @@
std::shared_ptr<renderengine::ExternalTexture> getExternalTextureFromBufferData(
BufferData& bufferData, const char* /* layerName */,
uint64_t /* transactionId */) override {
- return std::make_shared<renderengine::mock::FakeExternalTexture>(bufferData.getWidth(),
- bufferData.getHeight(),
- bufferData.getId(),
- bufferData
- .getPixelFormat(),
- bufferData.getUsage());
+ return std::make_shared<FakeExternalTexture>(bufferData.getWidth(), bufferData.getHeight(),
+ bufferData.getId(),
+ bufferData.getPixelFormat(),
+ bufferData.getUsage());
};
// b/220017192 migrate from transact codes to ISurfaceComposer apis
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index 06b9caa..ba77600 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -182,7 +182,9 @@
sp<mock::NativeWindow> mNativeWindow = sp<mock::NativeWindow>::make();
std::vector<sp<Layer>> mAuxiliaryLayers;
- sp<GraphicBuffer> mBuffer = sp<GraphicBuffer>::make();
+ sp<GraphicBuffer> mBuffer =
+ sp<GraphicBuffer>::make(1u, 1u, PIXEL_FORMAT_RGBA_8888,
+ GRALLOC_USAGE_SW_WRITE_OFTEN | GRALLOC_USAGE_SW_READ_OFTEN);
ANativeWindowBuffer* mNativeWindowBuffer = mBuffer->getNativeBuffer();
Hwc2::mock::Composer* mComposer = nullptr;
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
index d58e644..223f4db 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
@@ -115,7 +115,9 @@
TestableSurfaceFlinger mFlinger;
sp<mock::NativeWindow> mNativeWindow = sp<mock::NativeWindow>::make();
- sp<GraphicBuffer> mBuffer = sp<GraphicBuffer>::make();
+ sp<GraphicBuffer> mBuffer =
+ sp<GraphicBuffer>::make(1u, 1u, PIXEL_FORMAT_RGBA_8888,
+ GRALLOC_USAGE_SW_WRITE_OFTEN | GRALLOC_USAGE_SW_READ_OFTEN);
Hwc2::mock::PowerAdvisor mPowerAdvisor;
FakeDisplayInjector mFakeDisplayInjector{mFlinger, mPowerAdvisor, mNativeWindow};
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index a3b3c4c..06f45f9 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -153,6 +153,9 @@
static constexpr DisplayModeId kModeId30Frac{9};
static constexpr DisplayModeId kModeId60Frac{10};
static constexpr DisplayModeId kModeId35{11};
+ static constexpr DisplayModeId kModeId1{12};
+ static constexpr DisplayModeId kModeId5{13};
+ static constexpr DisplayModeId kModeId10{14};
static inline const ftl::NonNull<DisplayModePtr> kMode60 =
ftl::as_non_null(createDisplayMode(kModeId60, 60_Hz));
@@ -190,6 +193,12 @@
ftl::as_non_null(createDisplayMode(kModeId24, 24_Hz));
static inline const ftl::NonNull<DisplayModePtr> kMode24Frac =
ftl::as_non_null(createDisplayMode(kModeId24Frac, 23.976_Hz));
+ static inline const ftl::NonNull<DisplayModePtr> kMode1 =
+ ftl::as_non_null(createDisplayMode(kModeId1, 1_Hz));
+ static inline const ftl::NonNull<DisplayModePtr> kMode5 =
+ ftl::as_non_null(createDisplayMode(kModeId5, 5_Hz));
+ static inline const ftl::NonNull<DisplayModePtr> kMode10 =
+ ftl::as_non_null(createDisplayMode(kModeId10, 10_Hz));
// Test configurations.
static inline const DisplayModes kModes_60 = makeModes(kMode60);
@@ -212,6 +221,7 @@
static inline const DisplayModes kModes_25_30_50_60 =
makeModes(kMode60, kMode90, kMode72_G1, kMode120_G1, kMode30_G1, kMode25_G1, kMode50);
static inline const DisplayModes kModes_60_120 = makeModes(kMode60, kMode120);
+ static inline const DisplayModes kModes_1_5_10 = makeModes(kMode1, kMode5, kMode10);
// This is a typical TV configuration.
static inline const DisplayModes kModes_24_25_30_50_60_Frac =
@@ -2956,5 +2966,12 @@
EXPECT_EQ(kMode60, selector.getMinRefreshRateByPolicy());
}
+TEST_P(RefreshRateSelectorTest, SupportsLowPhysicalRefreshRates) {
+ auto selector = createSelector(kModes_1_5_10, kModeId10);
+
+ EXPECT_EQ(kMode10, selector.getMaxRefreshRateByPolicy());
+ EXPECT_EQ(kMode1, selector.getMinRefreshRateByPolicy());
+}
+
} // namespace
} // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
index 6e50059..ab732ed 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
@@ -404,11 +404,13 @@
EXPECT_EQ(PowerMode::ON, display.mutableDisplayDevice()->getPowerMode());
}
-TEST_F(SetPowerModeInternalTest, transitionsDisplayFromOffToOnPrimaryDisplay) {
+// TODO(b/262417075)
+TEST_F(SetPowerModeInternalTest, DISABLED_transitionsDisplayFromOffToOnPrimaryDisplay) {
transitionDisplayCommon<PrimaryDisplayPowerCase<TransitionOffToOnVariant>>();
}
-TEST_F(SetPowerModeInternalTest, transitionsDisplayFromOffToDozeSuspendPrimaryDisplay) {
+// TODO(b/262417075)
+TEST_F(SetPowerModeInternalTest, DISABLED_transitionsDisplayFromOffToDozeSuspendPrimaryDisplay) {
transitionDisplayCommon<PrimaryDisplayPowerCase<TransitionOffToDozeSuspendVariant>>();
}
@@ -444,11 +446,13 @@
transitionDisplayCommon<PrimaryDisplayPowerCase<TransitionOnToUnknownVariant>>();
}
-TEST_F(SetPowerModeInternalTest, transitionsDisplayFromOffToOnExternalDisplay) {
+// TODO(b/262417075)
+TEST_F(SetPowerModeInternalTest, DISABLED_transitionsDisplayFromOffToOnExternalDisplay) {
transitionDisplayCommon<ExternalDisplayPowerCase<TransitionOffToOnVariant>>();
}
-TEST_F(SetPowerModeInternalTest, transitionsDisplayFromOffToDozeSuspendExternalDisplay) {
+// TODO(b/262417075)
+TEST_F(SetPowerModeInternalTest, DISABLED_transitionsDisplayFromOffToDozeSuspendExternalDisplay) {
transitionDisplayCommon<ExternalDisplayPowerCase<TransitionOffToDozeSuspendVariant>>();
}
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 2117084..113c518 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -401,7 +401,7 @@
return mFlinger->setPowerModeInternal(display, mode);
}
- auto renderScreenImpl(std::unique_ptr<RenderArea> renderArea,
+ auto renderScreenImpl(std::shared_ptr<const RenderArea> renderArea,
SurfaceFlinger::TraverseLayersFunction traverseLayers,
const std::shared_ptr<renderengine::ExternalTexture>& buffer,
bool forSystem, bool regionSampling) {
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
index 836e3a4..2f16b7b 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
@@ -98,6 +98,8 @@
Error(Display, nsecs_t, uint32_t*, uint32_t*, int*, uint32_t*));
MOCK_METHOD4(setCursorPosition, Error(Display, Layer, int32_t, int32_t));
MOCK_METHOD5(setLayerBuffer, Error(Display, Layer, uint32_t, const sp<GraphicBuffer>&, int));
+ MOCK_METHOD4(setLayerBufferSlotsToClear,
+ Error(Display, Layer, const std::vector<uint32_t>&, uint32_t));
MOCK_METHOD3(setLayerSurfaceDamage,
Error(Display, Layer, const std::vector<IComposerClient::Rect>&));
MOCK_METHOD3(setLayerBlendMode, Error(Display, Layer, IComposerClient::BlendMode));
diff --git a/vulkan/libvulkan/driver.cpp b/vulkan/libvulkan/driver.cpp
index 9ed992b..273cdd5 100644
--- a/vulkan/libvulkan/driver.cpp
+++ b/vulkan/libvulkan/driver.cpp
@@ -636,6 +636,7 @@
case ProcHook::EXT_swapchain_colorspace:
case ProcHook::KHR_get_surface_capabilities2:
case ProcHook::GOOGLE_surfaceless_query:
+ case ProcHook::EXT_surface_maintenance1:
hook_extensions_.set(ext_bit);
// return now as these extensions do not require HAL support
return;
@@ -657,9 +658,11 @@
case ProcHook::KHR_shared_presentable_image:
case ProcHook::KHR_swapchain:
case ProcHook::EXT_hdr_metadata:
+ case ProcHook::EXT_swapchain_maintenance1:
case ProcHook::ANDROID_external_memory_android_hardware_buffer:
case ProcHook::ANDROID_native_buffer:
case ProcHook::GOOGLE_display_timing:
+ case ProcHook::KHR_external_fence_fd:
case ProcHook::EXTENSION_CORE_1_0:
case ProcHook::EXTENSION_CORE_1_1:
case ProcHook::EXTENSION_CORE_1_2:
@@ -690,16 +693,22 @@
ext_bit = ProcHook::ANDROID_native_buffer;
break;
case ProcHook::KHR_incremental_present:
- case ProcHook::GOOGLE_display_timing:
case ProcHook::KHR_shared_presentable_image:
+ case ProcHook::GOOGLE_display_timing:
hook_extensions_.set(ext_bit);
// return now as these extensions do not require HAL support
return;
+ case ProcHook::EXT_swapchain_maintenance1:
+ // map VK_KHR_swapchain_maintenance1 to KHR_external_fence_fd
+ name = VK_KHR_EXTERNAL_FENCE_FD_EXTENSION_NAME;
+ ext_bit = ProcHook::KHR_external_fence_fd;
+ break;
case ProcHook::EXT_hdr_metadata:
case ProcHook::KHR_bind_memory2:
hook_extensions_.set(ext_bit);
break;
case ProcHook::ANDROID_external_memory_android_hardware_buffer:
+ case ProcHook::KHR_external_fence_fd:
case ProcHook::EXTENSION_UNKNOWN:
// Extensions we don't need to do anything about at this level
break;
@@ -715,6 +724,7 @@
case ProcHook::KHR_surface_protected_capabilities:
case ProcHook::EXT_debug_report:
case ProcHook::EXT_swapchain_colorspace:
+ case ProcHook::EXT_surface_maintenance1:
case ProcHook::GOOGLE_surfaceless_query:
case ProcHook::ANDROID_native_buffer:
case ProcHook::EXTENSION_CORE_1_0:
@@ -747,10 +757,18 @@
if (strcmp(name, props.extensionName) != 0)
continue;
+ if (ext_bit != ProcHook::EXTENSION_UNKNOWN &&
+ hal_extensions_.test(ext_bit)) {
+ ALOGI("CreateInfoWrapper::FilterExtension: already have '%s'.", name);
+ continue;
+ }
+
filter.names[filter.name_count++] = name;
if (ext_bit != ProcHook::EXTENSION_UNKNOWN) {
if (ext_bit == ProcHook::ANDROID_native_buffer)
hook_extensions_.set(ProcHook::KHR_swapchain);
+ if (ext_bit == ProcHook::KHR_external_fence_fd)
+ hook_extensions_.set(ProcHook::EXT_swapchain_maintenance1);
hal_extensions_.set(ext_bit);
}
@@ -940,6 +958,9 @@
VK_KHR_GET_SURFACE_CAPABILITIES_2_SPEC_VERSION});
loader_extensions.push_back({VK_GOOGLE_SURFACELESS_QUERY_EXTENSION_NAME,
VK_GOOGLE_SURFACELESS_QUERY_SPEC_VERSION});
+ loader_extensions.push_back({
+ VK_EXT_SURFACE_MAINTENANCE_1_EXTENSION_NAME,
+ VK_EXT_SURFACE_MAINTENANCE_1_SPEC_VERSION});
static const VkExtensionProperties loader_debug_report_extension = {
VK_EXT_DEBUG_REPORT_EXTENSION_NAME, VK_EXT_DEBUG_REPORT_SPEC_VERSION,
@@ -1072,6 +1093,33 @@
return result;
}
+bool CanSupportSwapchainMaintenance1Extension(VkPhysicalDevice physicalDevice) {
+ const auto& driver = GetData(physicalDevice).driver;
+ if (!driver.GetPhysicalDeviceExternalFenceProperties)
+ return false;
+
+ // Requires support for external fences imported from sync fds.
+ // This is _almost_ universal on Android, but may be missing on
+ // some extremely old drivers, or on strange implementations like
+ // cuttlefish.
+ VkPhysicalDeviceExternalFenceInfo fenceInfo = {
+ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_FENCE_INFO,
+ nullptr,
+ VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT
+ };
+ VkExternalFenceProperties fenceProperties = {
+ VK_STRUCTURE_TYPE_EXTERNAL_FENCE_PROPERTIES,
+ nullptr,
+ 0, 0, 0
+ };
+
+ GetPhysicalDeviceExternalFenceProperties(physicalDevice, &fenceInfo, &fenceProperties);
+ if (fenceProperties.externalFenceFeatures & VK_EXTERNAL_FENCE_FEATURE_IMPORTABLE_BIT)
+ return true;
+
+ return false;
+}
+
VkResult EnumerateDeviceExtensionProperties(
VkPhysicalDevice physicalDevice,
const char* pLayerName,
@@ -1149,6 +1197,12 @@
VK_EXT_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN_SPEC_VERSION});
}
+ if (CanSupportSwapchainMaintenance1Extension(physicalDevice)) {
+ loader_extensions.push_back({
+ VK_EXT_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME,
+ VK_EXT_SWAPCHAIN_MAINTENANCE_1_SPEC_VERSION});
+ }
+
// enumerate our extensions first
if (!pLayerName && pProperties) {
uint32_t count = std::min(
@@ -1644,6 +1698,12 @@
imageCompressionControlSwapchainInChain = true;
} break;
+ case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SWAPCHAIN_MAINTENANCE_1_FEATURES_EXT: {
+ auto smf = reinterpret_cast<VkPhysicalDeviceSwapchainMaintenance1FeaturesEXT *>(
+ pFeats);
+ smf->swapchainMaintenance1 = true;
+ } break;
+
default:
break;
}
diff --git a/vulkan/libvulkan/driver_gen.cpp b/vulkan/libvulkan/driver_gen.cpp
index de98aa7..798af5c 100644
--- a/vulkan/libvulkan/driver_gen.cpp
+++ b/vulkan/libvulkan/driver_gen.cpp
@@ -162,6 +162,15 @@
}
}
+VKAPI_ATTR VkResult checkedReleaseSwapchainImagesEXT(VkDevice device, const VkReleaseSwapchainImagesInfoEXT* pReleaseInfo) {
+ if (GetData(device).hook_extensions[ProcHook::EXT_swapchain_maintenance1]) {
+ return ReleaseSwapchainImagesEXT(device, pReleaseInfo);
+ } else {
+ Logger(device).Err(device, "VK_EXT_swapchain_maintenance1 not enabled. vkReleaseSwapchainImagesEXT not executed.");
+ return VK_SUCCESS;
+ }
+}
+
// clang-format on
const ProcHook g_proc_hooks[] = {
@@ -545,6 +554,13 @@
nullptr,
},
{
+ "vkReleaseSwapchainImagesEXT",
+ ProcHook::DEVICE,
+ ProcHook::EXT_swapchain_maintenance1,
+ reinterpret_cast<PFN_vkVoidFunction>(ReleaseSwapchainImagesEXT),
+ reinterpret_cast<PFN_vkVoidFunction>(checkedReleaseSwapchainImagesEXT),
+ },
+ {
"vkSetHdrMetadataEXT",
ProcHook::DEVICE,
ProcHook::EXT_hdr_metadata,
@@ -580,6 +596,8 @@
if (strcmp(name, "VK_KHR_surface") == 0) return ProcHook::KHR_surface;
if (strcmp(name, "VK_KHR_surface_protected_capabilities") == 0) return ProcHook::KHR_surface_protected_capabilities;
if (strcmp(name, "VK_KHR_swapchain") == 0) return ProcHook::KHR_swapchain;
+ if (strcmp(name, "VK_EXT_swapchain_maintenance1") == 0) return ProcHook::EXT_swapchain_maintenance1;
+ if (strcmp(name, "VK_EXT_surface_maintenance1") == 0) return ProcHook::EXT_surface_maintenance1;
if (strcmp(name, "VK_ANDROID_external_memory_android_hardware_buffer") == 0) return ProcHook::ANDROID_external_memory_android_hardware_buffer;
if (strcmp(name, "VK_KHR_bind_memory2") == 0) return ProcHook::KHR_bind_memory2;
if (strcmp(name, "VK_KHR_get_physical_device_properties2") == 0) return ProcHook::KHR_get_physical_device_properties2;
@@ -587,6 +605,7 @@
if (strcmp(name, "VK_KHR_external_memory_capabilities") == 0) return ProcHook::KHR_external_memory_capabilities;
if (strcmp(name, "VK_KHR_external_semaphore_capabilities") == 0) return ProcHook::KHR_external_semaphore_capabilities;
if (strcmp(name, "VK_KHR_external_fence_capabilities") == 0) return ProcHook::KHR_external_fence_capabilities;
+ if (strcmp(name, "VK_KHR_external_fence_fd") == 0) return ProcHook::KHR_external_fence_fd;
// clang-format on
return ProcHook::EXTENSION_UNKNOWN;
}
@@ -666,6 +685,7 @@
INIT_PROC(true, dev, CreateImage);
INIT_PROC(true, dev, DestroyImage);
INIT_PROC(true, dev, AllocateCommandBuffers);
+ INIT_PROC_EXT(KHR_external_fence_fd, true, dev, ImportFenceFdKHR);
INIT_PROC(false, dev, BindImageMemory2);
INIT_PROC_EXT(KHR_bind_memory2, true, dev, BindImageMemory2KHR);
INIT_PROC(false, dev, GetDeviceQueue2);
diff --git a/vulkan/libvulkan/driver_gen.h b/vulkan/libvulkan/driver_gen.h
index 2f60086..31ba04b 100644
--- a/vulkan/libvulkan/driver_gen.h
+++ b/vulkan/libvulkan/driver_gen.h
@@ -49,6 +49,8 @@
KHR_surface,
KHR_surface_protected_capabilities,
KHR_swapchain,
+ EXT_swapchain_maintenance1,
+ EXT_surface_maintenance1,
ANDROID_external_memory_android_hardware_buffer,
KHR_bind_memory2,
KHR_get_physical_device_properties2,
@@ -56,6 +58,7 @@
KHR_external_memory_capabilities,
KHR_external_semaphore_capabilities,
KHR_external_fence_capabilities,
+ KHR_external_fence_fd,
EXTENSION_CORE_1_0,
EXTENSION_CORE_1_1,
@@ -118,6 +121,7 @@
PFN_vkCreateImage CreateImage;
PFN_vkDestroyImage DestroyImage;
PFN_vkAllocateCommandBuffers AllocateCommandBuffers;
+ PFN_vkImportFenceFdKHR ImportFenceFdKHR;
PFN_vkBindImageMemory2 BindImageMemory2;
PFN_vkBindImageMemory2KHR BindImageMemory2KHR;
PFN_vkGetDeviceQueue2 GetDeviceQueue2;
diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp
index 1bf7abb..1bff50d 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -291,6 +291,9 @@
release_fence(-1),
dequeued(false) {}
VkImage image;
+ // If the image is bound to memory, an sp to the underlying gralloc buffer.
+ // Otherwise, nullptr; the image will be bound to memory as part of
+ // AcquireNextImage.
android::sp<ANativeWindowBuffer> buffer;
// The fence is only valid when the buffer is dequeued, and should be
// -1 any other time. When valid, we own the fd, and must ensure it is
@@ -511,6 +514,10 @@
case VK_FORMAT_R8_UNORM:
native_format = android::PIXEL_FORMAT_R_8;
break;
+ // TODO: Do we need to query for VK_EXT_rgba10x6_formats here?
+ case VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16:
+ native_format = android::PIXEL_FORMAT_RGBA_10101010;
+ break;
default:
ALOGV("unsupported swapchain format %d", format);
break;
@@ -652,100 +659,40 @@
VkSurfaceCapabilitiesKHR* capabilities) {
ATRACE_CALL();
- int err;
- int width, height;
- int transform_hint;
- int max_buffer_count;
- if (surface == VK_NULL_HANDLE) {
- const InstanceData& instance_data = GetData(pdev);
- ProcHook::Extension surfaceless = ProcHook::GOOGLE_surfaceless_query;
- bool surfaceless_enabled =
- instance_data.hook_extensions.test(surfaceless);
- if (!surfaceless_enabled) {
- // It is an error to pass a surface==VK_NULL_HANDLE unless the
- // VK_GOOGLE_surfaceless_query extension is enabled
- return VK_ERROR_SURFACE_LOST_KHR;
- }
- // Support for VK_GOOGLE_surfaceless_query. The primary purpose of this
- // extension for this function is for
- // VkSurfaceProtectedCapabilitiesKHR::supportsProtected. The following
- // four values cannot be known without a surface. Default values will
- // be supplied anyway, but cannot be relied upon.
- width = 0xFFFFFFFF;
- height = 0xFFFFFFFF;
- transform_hint = VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR;
- capabilities->minImageCount = 0xFFFFFFFF;
- capabilities->maxImageCount = 0xFFFFFFFF;
+ // Implement in terms of GetPhysicalDeviceSurfaceCapabilities2KHR
+
+ VkPhysicalDeviceSurfaceInfo2KHR info2 = {
+ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR,
+ nullptr,
+ surface
+ };
+
+ VkSurfaceCapabilities2KHR caps2 = {
+ VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR,
+ nullptr,
+ {},
+ };
+
+ VkResult result = GetPhysicalDeviceSurfaceCapabilities2KHR(pdev, &info2, &caps2);
+ *capabilities = caps2.surfaceCapabilities;
+ return result;
+}
+
+// Does the call-twice and VK_INCOMPLETE handling for querying lists
+// of things, where we already have the full set built in a vector.
+template <typename T>
+VkResult CopyWithIncomplete(std::vector<T> const& things,
+ T* callerPtr, uint32_t* callerCount) {
+ VkResult result = VK_SUCCESS;
+ if (callerPtr) {
+ if (things.size() > *callerCount)
+ result = VK_INCOMPLETE;
+ *callerCount = std::min(uint32_t(things.size()), *callerCount);
+ std::copy(things.begin(), things.begin() + *callerCount, callerPtr);
} else {
- ANativeWindow* window = SurfaceFromHandle(surface)->window.get();
-
- err = window->query(window, NATIVE_WINDOW_DEFAULT_WIDTH, &width);
- if (err != android::OK) {
- ALOGE("NATIVE_WINDOW_DEFAULT_WIDTH query failed: %s (%d)",
- strerror(-err), err);
- return VK_ERROR_SURFACE_LOST_KHR;
- }
- err = window->query(window, NATIVE_WINDOW_DEFAULT_HEIGHT, &height);
- if (err != android::OK) {
- ALOGE("NATIVE_WINDOW_DEFAULT_WIDTH query failed: %s (%d)",
- strerror(-err), err);
- return VK_ERROR_SURFACE_LOST_KHR;
- }
-
- err = window->query(window, NATIVE_WINDOW_TRANSFORM_HINT,
- &transform_hint);
- if (err != android::OK) {
- ALOGE("NATIVE_WINDOW_TRANSFORM_HINT query failed: %s (%d)",
- strerror(-err), err);
- return VK_ERROR_SURFACE_LOST_KHR;
- }
-
- err = window->query(window, NATIVE_WINDOW_MAX_BUFFER_COUNT,
- &max_buffer_count);
- if (err != android::OK) {
- ALOGE("NATIVE_WINDOW_MAX_BUFFER_COUNT query failed: %s (%d)",
- strerror(-err), err);
- return VK_ERROR_SURFACE_LOST_KHR;
- }
- capabilities->minImageCount = std::min(max_buffer_count, 3);
- capabilities->maxImageCount = static_cast<uint32_t>(max_buffer_count);
+ *callerCount = things.size();
}
-
- capabilities->currentExtent =
- VkExtent2D{static_cast<uint32_t>(width), static_cast<uint32_t>(height)};
-
- // TODO(http://b/134182502): Figure out what the max extent should be.
- capabilities->minImageExtent = VkExtent2D{1, 1};
- capabilities->maxImageExtent = VkExtent2D{4096, 4096};
-
- if (capabilities->maxImageExtent.height <
- capabilities->currentExtent.height) {
- capabilities->maxImageExtent.height =
- capabilities->currentExtent.height;
- }
-
- if (capabilities->maxImageExtent.width <
- capabilities->currentExtent.width) {
- capabilities->maxImageExtent.width = capabilities->currentExtent.width;
- }
-
- capabilities->maxImageArrayLayers = 1;
-
- capabilities->supportedTransforms = kSupportedTransforms;
- capabilities->currentTransform =
- TranslateNativeToVulkanTransform(transform_hint);
-
- // On Android, window composition is a WindowManager property, not something
- // associated with the bufferqueue. It can't be changed from here.
- capabilities->supportedCompositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
-
- capabilities->supportedUsageFlags =
- VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
- VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT |
- VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
- VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
-
- return VK_SUCCESS;
+ return result;
}
VKAPI_ATTR
@@ -858,25 +805,27 @@
}
}
+ // TODO query VK_EXT_rgba10x6_formats support
+ desc.format = AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM;
+ if (AHardwareBuffer_isSupported(&desc)) {
+ all_formats.emplace_back(
+ VkSurfaceFormatKHR{VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16,
+ VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
+ if (colorspace_ext) {
+ all_formats.emplace_back(
+ VkSurfaceFormatKHR{VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16,
+ VK_COLOR_SPACE_PASS_THROUGH_EXT});
+ all_formats.emplace_back(
+ VkSurfaceFormatKHR{VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16,
+ VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT});
+ }
+ }
+
// NOTE: Any new formats that are added must be coordinated across different
// Android users. This includes the ANGLE team (a layered implementation of
// OpenGL-ES).
- VkResult result = VK_SUCCESS;
- if (formats) {
- uint32_t transfer_count = all_formats.size();
- if (transfer_count > *count) {
- transfer_count = *count;
- result = VK_INCOMPLETE;
- }
- std::copy(all_formats.begin(), all_formats.begin() + transfer_count,
- formats);
- *count = transfer_count;
- } else {
- *count = all_formats.size();
- }
-
- return result;
+ return CopyWithIncomplete(all_formats, formats, count);
}
VKAPI_ATTR
@@ -886,19 +835,134 @@
VkSurfaceCapabilities2KHR* pSurfaceCapabilities) {
ATRACE_CALL();
- VkResult result = GetPhysicalDeviceSurfaceCapabilitiesKHR(
- physicalDevice, pSurfaceInfo->surface,
- &pSurfaceCapabilities->surfaceCapabilities);
+ auto surface = pSurfaceInfo->surface;
+ auto capabilities = &pSurfaceCapabilities->surfaceCapabilities;
- VkSurfaceCapabilities2KHR* caps = pSurfaceCapabilities;
- while (caps->pNext) {
- caps = reinterpret_cast<VkSurfaceCapabilities2KHR*>(caps->pNext);
+ VkSurfacePresentModeEXT const *pPresentMode = nullptr;
+ for (auto pNext = reinterpret_cast<VkBaseInStructure const *>(pSurfaceInfo->pNext);
+ pNext; pNext = reinterpret_cast<VkBaseInStructure const *>(pNext->pNext)) {
+ switch (pNext->sType) {
+ case VK_STRUCTURE_TYPE_SURFACE_PRESENT_MODE_EXT:
+ pPresentMode = reinterpret_cast<VkSurfacePresentModeEXT const *>(pNext);
+ break;
- switch (caps->sType) {
+ default:
+ break;
+ }
+ }
+
+ int err;
+ int width, height;
+ int transform_hint;
+ int max_buffer_count;
+ if (surface == VK_NULL_HANDLE) {
+ const InstanceData& instance_data = GetData(physicalDevice);
+ ProcHook::Extension surfaceless = ProcHook::GOOGLE_surfaceless_query;
+ bool surfaceless_enabled =
+ instance_data.hook_extensions.test(surfaceless);
+ if (!surfaceless_enabled) {
+ // It is an error to pass a surface==VK_NULL_HANDLE unless the
+ // VK_GOOGLE_surfaceless_query extension is enabled
+ return VK_ERROR_SURFACE_LOST_KHR;
+ }
+ // Support for VK_GOOGLE_surfaceless_query. The primary purpose of this
+ // extension for this function is for
+ // VkSurfaceProtectedCapabilitiesKHR::supportsProtected. The following
+ // four values cannot be known without a surface. Default values will
+ // be supplied anyway, but cannot be relied upon.
+ width = 0xFFFFFFFF;
+ height = 0xFFFFFFFF;
+ transform_hint = VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR;
+ capabilities->minImageCount = 0xFFFFFFFF;
+ capabilities->maxImageCount = 0xFFFFFFFF;
+ } else {
+ ANativeWindow* window = SurfaceFromHandle(surface)->window.get();
+
+ err = window->query(window, NATIVE_WINDOW_DEFAULT_WIDTH, &width);
+ if (err != android::OK) {
+ ALOGE("NATIVE_WINDOW_DEFAULT_WIDTH query failed: %s (%d)",
+ strerror(-err), err);
+ return VK_ERROR_SURFACE_LOST_KHR;
+ }
+ err = window->query(window, NATIVE_WINDOW_DEFAULT_HEIGHT, &height);
+ if (err != android::OK) {
+ ALOGE("NATIVE_WINDOW_DEFAULT_WIDTH query failed: %s (%d)",
+ strerror(-err), err);
+ return VK_ERROR_SURFACE_LOST_KHR;
+ }
+
+ err = window->query(window, NATIVE_WINDOW_TRANSFORM_HINT,
+ &transform_hint);
+ if (err != android::OK) {
+ ALOGE("NATIVE_WINDOW_TRANSFORM_HINT query failed: %s (%d)",
+ strerror(-err), err);
+ return VK_ERROR_SURFACE_LOST_KHR;
+ }
+
+ err = window->query(window, NATIVE_WINDOW_MAX_BUFFER_COUNT,
+ &max_buffer_count);
+ if (err != android::OK) {
+ ALOGE("NATIVE_WINDOW_MAX_BUFFER_COUNT 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->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->maxImageCount = static_cast<uint32_t>(max_buffer_count);
+ }
+ }
+
+ capabilities->currentExtent =
+ VkExtent2D{static_cast<uint32_t>(width), static_cast<uint32_t>(height)};
+
+ // TODO(http://b/134182502): Figure out what the max extent should be.
+ capabilities->minImageExtent = VkExtent2D{1, 1};
+ capabilities->maxImageExtent = VkExtent2D{4096, 4096};
+
+ if (capabilities->maxImageExtent.height <
+ capabilities->currentExtent.height) {
+ capabilities->maxImageExtent.height =
+ capabilities->currentExtent.height;
+ }
+
+ if (capabilities->maxImageExtent.width <
+ capabilities->currentExtent.width) {
+ capabilities->maxImageExtent.width = capabilities->currentExtent.width;
+ }
+
+ capabilities->maxImageArrayLayers = 1;
+
+ capabilities->supportedTransforms = kSupportedTransforms;
+ capabilities->currentTransform =
+ TranslateNativeToVulkanTransform(transform_hint);
+
+ // On Android, window composition is a WindowManager property, not something
+ // associated with the bufferqueue. It can't be changed from here.
+ capabilities->supportedCompositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
+
+ capabilities->supportedUsageFlags =
+ VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
+ VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT |
+ VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
+ VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
+
+ for (auto pNext = reinterpret_cast<VkBaseOutStructure*>(pSurfaceCapabilities->pNext);
+ pNext; pNext = reinterpret_cast<VkBaseOutStructure*>(pNext->pNext)) {
+
+ switch (pNext->sType) {
case VK_STRUCTURE_TYPE_SHARED_PRESENT_SURFACE_CAPABILITIES_KHR: {
VkSharedPresentSurfaceCapabilitiesKHR* shared_caps =
- reinterpret_cast<VkSharedPresentSurfaceCapabilitiesKHR*>(
- caps);
+ reinterpret_cast<VkSharedPresentSurfaceCapabilitiesKHR*>(pNext);
// Claim same set of usage flags are supported for
// shared present modes as for other modes.
shared_caps->sharedPresentSupportedUsageFlags =
@@ -908,17 +972,64 @@
case VK_STRUCTURE_TYPE_SURFACE_PROTECTED_CAPABILITIES_KHR: {
VkSurfaceProtectedCapabilitiesKHR* protected_caps =
- reinterpret_cast<VkSurfaceProtectedCapabilitiesKHR*>(caps);
+ reinterpret_cast<VkSurfaceProtectedCapabilitiesKHR*>(pNext);
protected_caps->supportsProtected = VK_TRUE;
} break;
+ case VK_STRUCTURE_TYPE_SURFACE_PRESENT_SCALING_CAPABILITIES_EXT: {
+ VkSurfacePresentScalingCapabilitiesEXT* scaling_caps =
+ reinterpret_cast<VkSurfacePresentScalingCapabilitiesEXT*>(pNext);
+ // By default, Android stretches the buffer to fit the window,
+ // without preserving aspect ratio. Other modes are technically possible
+ // but consult with CoGS team before exposing them here!
+ scaling_caps->supportedPresentScaling = VK_PRESENT_SCALING_STRETCH_BIT_EXT;
+
+ // Since we always scale, we don't support any gravity.
+ scaling_caps->supportedPresentGravityX = 0;
+ scaling_caps->supportedPresentGravityY = 0;
+
+ // Scaled image limits are just the basic image limits
+ scaling_caps->minScaledImageExtent = capabilities->minImageExtent;
+ scaling_caps->maxScaledImageExtent = capabilities->maxImageExtent;
+ } break;
+
+ case VK_STRUCTURE_TYPE_SURFACE_PRESENT_MODE_COMPATIBILITY_EXT: {
+ VkSurfacePresentModeCompatibilityEXT* mode_caps =
+ reinterpret_cast<VkSurfacePresentModeCompatibilityEXT*>(pNext);
+
+ ALOG_ASSERT(pPresentMode,
+ "querying VkSurfacePresentModeCompatibilityEXT "
+ "requires VkSurfacePresentModeEXT to be provided");
+ std::vector<VkPresentModeKHR> compatibleModes;
+ compatibleModes.push_back(pPresentMode->presentMode);
+
+ switch (pPresentMode->presentMode) {
+ // Shared modes are both compatible with each other.
+ case VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR:
+ compatibleModes.push_back(VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR);
+ break;
+ case VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR:
+ compatibleModes.push_back(VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR);
+ break;
+ default:
+ // Other modes are only compatible with themselves.
+ // TODO: consider whether switching between FIFO and MAILBOX is reasonable
+ break;
+ }
+
+ // Note: this does not generate VK_INCOMPLETE since we're nested inside
+ // a larger query and there would be no way to determine exactly where it came from.
+ CopyWithIncomplete(compatibleModes, mode_caps->pPresentModes,
+ &mode_caps->presentModeCount);
+ } break;
+
default:
// Ignore all other extension structs
break;
}
}
- return result;
+ return VK_SUCCESS;
}
VKAPI_ATTR
@@ -1077,18 +1188,7 @@
present_modes.push_back(VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR);
}
- uint32_t num_modes = uint32_t(present_modes.size());
-
- VkResult result = VK_SUCCESS;
- if (modes) {
- if (*count < num_modes)
- result = VK_INCOMPLETE;
- *count = std::min(*count, num_modes);
- std::copy_n(present_modes.data(), *count, modes);
- } else {
- *count = num_modes;
- }
- return result;
+ return CopyWithIncomplete(present_modes, modes, count);
}
VKAPI_ATTR
@@ -1374,8 +1474,7 @@
}
VkSwapchainImageUsageFlagsANDROID swapchain_image_usage = 0;
- if (create_info->presentMode == VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR ||
- create_info->presentMode == VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR) {
+ if (IsSharedPresentMode(create_info->presentMode)) {
swapchain_image_usage |= VK_SWAPCHAIN_IMAGE_USAGE_SHARED_BIT_ANDROID;
err = native_window_set_shared_buffer_mode(window, true);
if (err != android::OK) {
@@ -1525,8 +1624,6 @@
Swapchain* swapchain = new (mem)
Swapchain(surface, num_images, create_info->presentMode,
TranslateVulkanToNativeTransform(create_info->preTransform));
- // -- Dequeue all buffers and create a VkImage for each --
- // Any failures during or after this must cancel the dequeued buffers.
VkSwapchainImageCreateInfoANDROID swapchain_image_create = {
#pragma clang diagnostic push
@@ -1543,13 +1640,18 @@
#pragma clang diagnostic pop
.pNext = &swapchain_image_create,
};
+
VkImageCreateInfo image_create = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
- .pNext = &image_native_buffer,
+ .pNext = nullptr,
.flags = createProtectedSwapchain ? VK_IMAGE_CREATE_PROTECTED_BIT : 0u,
.imageType = VK_IMAGE_TYPE_2D,
.format = create_info->imageFormat,
- .extent = {0, 0, 1},
+ .extent = {
+ create_info->imageExtent.width,
+ create_info->imageExtent.height,
+ 1
+ },
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
@@ -1560,60 +1662,87 @@
.pQueueFamilyIndices = create_info->pQueueFamilyIndices,
};
- for (uint32_t i = 0; i < num_images; i++) {
- Swapchain::Image& img = swapchain->images[i];
+ // Note: don't do deferred allocation for shared present modes. There's only one buffer
+ // involved so very little benefit.
+ if ((create_info->flags & VK_SWAPCHAIN_CREATE_DEFERRED_MEMORY_ALLOCATION_BIT_EXT) &&
+ !IsSharedPresentMode(create_info->presentMode)) {
+ // Don't want to touch the underlying gralloc buffers yet;
+ // instead just create unbound VkImages which will later be bound to memory inside
+ // AcquireNextImage.
+ VkImageSwapchainCreateInfoKHR image_swapchain_create = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_SWAPCHAIN_CREATE_INFO_KHR,
+ .pNext = nullptr,
+ .swapchain = HandleFromSwapchain(swapchain),
+ };
+ image_create.pNext = &image_swapchain_create;
- ANativeWindowBuffer* buffer;
- err = window->dequeueBuffer(window, &buffer, &img.dequeue_fence);
- if (err != android::OK) {
- ALOGE("dequeueBuffer[%u] failed: %s (%d)", i, strerror(-err), err);
- switch (-err) {
- case ENOMEM:
- result = VK_ERROR_OUT_OF_DEVICE_MEMORY;
- break;
- default:
- result = VK_ERROR_SURFACE_LOST_KHR;
- break;
+ for (uint32_t i = 0; i < num_images; i++) {
+ Swapchain::Image& img = swapchain->images[i];
+ img.buffer = nullptr;
+ img.dequeued = false;
+
+ result = dispatch.CreateImage(device, &image_create, nullptr, &img.image);
+ if (result != VK_SUCCESS) {
+ ALOGD("vkCreateImage w/ for deferred swapchain image failed: %u", result);
+ break;
}
- break;
}
- img.buffer = buffer;
- img.dequeued = true;
+ } else {
+ // -- Dequeue all buffers and create a VkImage for each --
+ // Any failures during or after this must cancel the dequeued buffers.
- image_create.extent =
- VkExtent3D{static_cast<uint32_t>(img.buffer->width),
- static_cast<uint32_t>(img.buffer->height),
- 1};
- image_native_buffer.handle = img.buffer->handle;
- image_native_buffer.stride = img.buffer->stride;
- image_native_buffer.format = img.buffer->format;
- image_native_buffer.usage = int(img.buffer->usage);
- android_convertGralloc0To1Usage(int(img.buffer->usage),
- &image_native_buffer.usage2.producer,
- &image_native_buffer.usage2.consumer);
- image_native_buffer.usage3 = img.buffer->usage;
+ for (uint32_t i = 0; i < num_images; i++) {
+ Swapchain::Image& img = swapchain->images[i];
- ATRACE_BEGIN("CreateImage");
- result =
- dispatch.CreateImage(device, &image_create, nullptr, &img.image);
- ATRACE_END();
- if (result != VK_SUCCESS) {
- ALOGD("vkCreateImage w/ native buffer failed: %u", result);
- break;
+ ANativeWindowBuffer* buffer;
+ err = window->dequeueBuffer(window, &buffer, &img.dequeue_fence);
+ if (err != android::OK) {
+ ALOGE("dequeueBuffer[%u] failed: %s (%d)", i, strerror(-err), err);
+ switch (-err) {
+ case ENOMEM:
+ result = VK_ERROR_OUT_OF_DEVICE_MEMORY;
+ break;
+ default:
+ result = VK_ERROR_SURFACE_LOST_KHR;
+ break;
+ }
+ break;
+ }
+ img.buffer = buffer;
+ img.dequeued = true;
+
+ image_native_buffer.handle = img.buffer->handle;
+ image_native_buffer.stride = img.buffer->stride;
+ image_native_buffer.format = img.buffer->format;
+ image_native_buffer.usage = int(img.buffer->usage);
+ android_convertGralloc0To1Usage(int(img.buffer->usage),
+ &image_native_buffer.usage2.producer,
+ &image_native_buffer.usage2.consumer);
+ image_native_buffer.usage3 = img.buffer->usage;
+ image_create.pNext = &image_native_buffer;
+
+ ATRACE_BEGIN("CreateImage");
+ result =
+ dispatch.CreateImage(device, &image_create, nullptr, &img.image);
+ ATRACE_END();
+ if (result != VK_SUCCESS) {
+ ALOGD("vkCreateImage w/ native buffer failed: %u", result);
+ break;
+ }
}
- }
- // -- Cancel all buffers, returning them to the queue --
- // If an error occurred before, also destroy the VkImage and release the
- // buffer reference. Otherwise, we retain a strong reference to the buffer.
- for (uint32_t i = 0; i < num_images; i++) {
- Swapchain::Image& img = swapchain->images[i];
- if (img.dequeued) {
- if (!swapchain->shared) {
- window->cancelBuffer(window, img.buffer.get(),
- img.dequeue_fence);
- img.dequeue_fence = -1;
- img.dequeued = false;
+ // -- Cancel all buffers, returning them to the queue --
+ // If an error occurred before, also destroy the VkImage and release the
+ // buffer reference. Otherwise, we retain a strong reference to the buffer.
+ for (uint32_t i = 0; i < num_images; i++) {
+ Swapchain::Image& img = swapchain->images[i];
+ if (img.dequeued) {
+ if (!swapchain->shared) {
+ window->cancelBuffer(window, img.buffer.get(),
+ img.dequeue_fence);
+ img.dequeue_fence = -1;
+ img.dequeued = false;
+ }
}
}
}
@@ -1736,6 +1865,64 @@
break;
}
}
+
+ // If this is a deferred alloc swapchain, this may be the first time we've
+ // seen a particular buffer. If so, there should be an empty slot. Find it,
+ // and bind the gralloc buffer to the VkImage for that slot. If there is no
+ // empty slot, then we dequeued an unexpected buffer. Non-deferred swapchains
+ // will also take this path, but will never have an empty slot since we
+ // populated them all upfront.
+ if (idx == swapchain.num_images) {
+ for (idx = 0; idx < swapchain.num_images; idx++) {
+ if (!swapchain.images[idx].buffer) {
+ // Note: this structure is technically required for
+ // Vulkan correctness, even though the driver is probably going
+ // to use everything from the VkNativeBufferANDROID below.
+ // This is kindof silly, but it's how we did the ANB
+ // side of VK_KHR_swapchain v69, so we're stuck with it unless
+ // we want to go tinkering with the ANB spec some more.
+ VkBindImageMemorySwapchainInfoKHR bimsi = {
+ .sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_SWAPCHAIN_INFO_KHR,
+ .pNext = nullptr,
+ .swapchain = swapchain_handle,
+ .imageIndex = idx,
+ };
+ VkNativeBufferANDROID nb = {
+ .sType = VK_STRUCTURE_TYPE_NATIVE_BUFFER_ANDROID,
+ .pNext = &bimsi,
+ .handle = buffer->handle,
+ .stride = buffer->stride,
+ .format = buffer->format,
+ .usage = int(buffer->usage),
+ };
+ VkBindImageMemoryInfo bimi = {
+ .sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO,
+ .pNext = &nb,
+ .image = swapchain.images[idx].image,
+ .memory = VK_NULL_HANDLE,
+ .memoryOffset = 0,
+ };
+ result = GetData(device).driver.BindImageMemory2(device, 1, &bimi);
+ if (result != VK_SUCCESS) {
+ // This shouldn't really happen. If it does, something is probably
+ // unrecoverably wrong with the swapchain and its images. Cancel
+ // the buffer and declare the swapchain broken.
+ ALOGE("failed to do deferred gralloc buffer bind");
+ window->cancelBuffer(window, buffer, fence_fd);
+ return VK_ERROR_OUT_OF_DATE_KHR;
+ }
+
+ swapchain.images[idx].dequeued = true;
+ swapchain.images[idx].dequeue_fence = fence_fd;
+ swapchain.images[idx].buffer = buffer;
+ break;
+ }
+ }
+ }
+
+ // The buffer doesn't match any slot. This shouldn't normally happen, but is
+ // possible if the bufferqueue is reconfigured behind libvulkan's back. If this
+ // happens, just declare the swapchain to be broken and the app will recreate it.
if (idx == swapchain.num_images) {
ALOGE("dequeueBuffer returned unrecognized buffer");
window->cancelBuffer(window, buffer, fence_fd);
@@ -1861,12 +2048,32 @@
}
}
+// EXT_swapchain_maintenance1 present mode change
+static bool SetSwapchainPresentMode(ANativeWindow *window, VkPresentModeKHR mode) {
+ // There is no dynamic switching between non-shared present modes.
+ // All we support is switching between demand and continuous refresh.
+ if (!IsSharedPresentMode(mode))
+ return true;
+
+ int err = native_window_set_auto_refresh(window,
+ mode == VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR);
+ if (err != android::OK) {
+ ALOGE("native_window_set_auto_refresh() failed: %s (%d)",
+ strerror(-err), err);
+ return false;
+ }
+
+ return true;
+}
+
static VkResult PresentOneSwapchain(
VkQueue queue,
Swapchain& swapchain,
uint32_t imageIndex,
const VkPresentRegionKHR *pRegion,
const VkPresentTimeGOOGLE *pTime,
+ VkFence presentFence,
+ const VkPresentModeKHR *pPresentMode,
uint32_t waitSemaphoreCount,
const VkSemaphore *pWaitSemaphores) {
@@ -1897,12 +2104,33 @@
ANativeWindow* window = swapchain.surface.window.get();
if (swapchain_result == VK_SUCCESS) {
+ if (presentFence != VK_NULL_HANDLE) {
+ int fence_copy = fence < 0 ? -1 : dup(fence);
+ VkImportFenceFdInfoKHR iffi = {
+ VK_STRUCTURE_TYPE_IMPORT_FENCE_FD_INFO_KHR,
+ nullptr,
+ presentFence,
+ VK_FENCE_IMPORT_TEMPORARY_BIT,
+ VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT,
+ fence_copy,
+ };
+ if (VK_SUCCESS != dispatch.ImportFenceFdKHR(device, &iffi) && fence_copy >= 0) {
+ // ImportFenceFdKHR takes ownership only if it succeeds
+ close(fence_copy);
+ }
+ }
+
if (pRegion) {
SetSwapchainSurfaceDamage(window, pRegion);
}
if (pTime) {
SetSwapchainFrameTimestamp(swapchain, pTime);
}
+ if (pPresentMode) {
+ if (!SetSwapchainPresentMode(window, *pPresentMode))
+ swapchain_result = WorstPresentResult(swapchain_result,
+ VK_ERROR_SURFACE_LOST_KHR);
+ }
err = window->queueBuffer(window, img.buffer.get(), fence);
// queueBuffer always closes fence, even on error
@@ -1983,6 +2211,9 @@
// Look at the pNext chain for supported extension structs:
const VkPresentRegionsKHR* present_regions = nullptr;
const VkPresentTimesInfoGOOGLE* present_times = nullptr;
+ const VkSwapchainPresentFenceInfoEXT* present_fences = nullptr;
+ const VkSwapchainPresentModeInfoEXT* present_modes = nullptr;
+
const VkPresentRegionsKHR* next =
reinterpret_cast<const VkPresentRegionsKHR*>(present_info->pNext);
while (next) {
@@ -1994,6 +2225,14 @@
present_times =
reinterpret_cast<const VkPresentTimesInfoGOOGLE*>(next);
break;
+ case VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_FENCE_INFO_EXT:
+ present_fences =
+ reinterpret_cast<const VkSwapchainPresentFenceInfoEXT*>(next);
+ break;
+ case VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_MODE_INFO_EXT:
+ present_modes =
+ reinterpret_cast<const VkSwapchainPresentModeInfoEXT*>(next);
+ break;
default:
ALOGV("QueuePresentKHR ignoring unrecognized pNext->sType = %x",
next->sType);
@@ -2009,6 +2248,15 @@
present_times->swapchainCount != present_info->swapchainCount,
"VkPresentTimesInfoGOOGLE::swapchainCount != "
"VkPresentInfo::swapchainCount");
+ ALOGV_IF(present_fences &&
+ present_fences->swapchainCount != present_info->swapchainCount,
+ "VkSwapchainPresentFenceInfoEXT::swapchainCount != "
+ "VkPresentInfo::swapchainCount");
+ ALOGV_IF(present_modes &&
+ present_modes->swapchainCount != present_info->swapchainCount,
+ "VkSwapchainPresentModeInfoEXT::swapchainCount != "
+ "VkPresentInfo::swapchainCount");
+
const VkPresentRegionKHR* regions =
(present_regions) ? present_regions->pRegions : nullptr;
const VkPresentTimeGOOGLE* times =
@@ -2024,6 +2272,8 @@
present_info->pImageIndices[sc],
(regions && !swapchain.mailbox_mode) ? ®ions[sc] : nullptr,
times ? ×[sc] : nullptr,
+ present_fences ? present_fences->pFences[sc] : VK_NULL_HANDLE,
+ present_modes ? &present_modes->pPresentModes[sc] : nullptr,
present_info->waitSemaphoreCount,
present_info->pWaitSemaphores);
@@ -2248,5 +2498,35 @@
out_bind_infos.empty() ? pBindInfos : out_bind_infos.data());
}
+VKAPI_ATTR
+VkResult ReleaseSwapchainImagesEXT(VkDevice /*device*/,
+ const VkReleaseSwapchainImagesInfoEXT* pReleaseInfo) {
+ ATRACE_CALL();
+
+ Swapchain& swapchain = *SwapchainFromHandle(pReleaseInfo->swapchain);
+ ANativeWindow* window = swapchain.surface.window.get();
+
+ // If in shared present mode, don't actually release the image back to the BQ.
+ // Both sides share it forever.
+ if (swapchain.shared)
+ return VK_SUCCESS;
+
+ for (uint32_t i = 0; i < pReleaseInfo->imageIndexCount; i++) {
+ Swapchain::Image& img = swapchain.images[pReleaseInfo->pImageIndices[i]];
+ window->cancelBuffer(window, img.buffer.get(), img.dequeue_fence);
+
+ // cancelBuffer has taken ownership of the dequeue fence
+ img.dequeue_fence = -1;
+ // if we're still holding a release fence, get rid of it now
+ if (img.release_fence >= 0) {
+ close(img.release_fence);
+ img.release_fence = -1;
+ }
+ img.dequeued = false;
+ }
+
+ return VK_SUCCESS;
+}
+
} // namespace driver
} // namespace vulkan
diff --git a/vulkan/libvulkan/swapchain.h b/vulkan/libvulkan/swapchain.h
index 4912ef1..280fe9b 100644
--- a/vulkan/libvulkan/swapchain.h
+++ b/vulkan/libvulkan/swapchain.h
@@ -46,6 +46,7 @@
VKAPI_ATTR VkResult GetPhysicalDeviceSurfaceFormats2KHR(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceSurfaceInfo2KHR* pSurfaceInfo, uint32_t* pSurfaceFormatCount, VkSurfaceFormat2KHR* pSurfaceFormats);
VKAPI_ATTR VkResult BindImageMemory2(VkDevice device, uint32_t bindInfoCount, const VkBindImageMemoryInfo* pBindInfos);
VKAPI_ATTR VkResult BindImageMemory2KHR(VkDevice device, uint32_t bindInfoCount, const VkBindImageMemoryInfo* pBindInfos);
+VKAPI_ATTR VkResult ReleaseSwapchainImagesEXT(VkDevice device, const VkReleaseSwapchainImagesInfoEXT* pReleaseInfo);
// clang-format on
} // namespace driver
diff --git a/vulkan/scripts/driver_generator.py b/vulkan/scripts/driver_generator.py
index af56764..78b550c 100644
--- a/vulkan/scripts/driver_generator.py
+++ b/vulkan/scripts/driver_generator.py
@@ -35,6 +35,8 @@
'VK_KHR_surface',
'VK_KHR_surface_protected_capabilities',
'VK_KHR_swapchain',
+ 'VK_EXT_swapchain_maintenance1',
+ 'VK_EXT_surface_maintenance1',
]
# Extensions known to vulkan::driver level.
@@ -46,6 +48,7 @@
'VK_KHR_external_memory_capabilities',
'VK_KHR_external_semaphore_capabilities',
'VK_KHR_external_fence_capabilities',
+ 'VK_KHR_external_fence_fd',
]
# Functions needed at vulkan::driver level.
@@ -112,6 +115,9 @@
# For promoted VK_KHR_external_fence_capabilities
'vkGetPhysicalDeviceExternalFenceProperties',
'vkGetPhysicalDeviceExternalFencePropertiesKHR',
+
+ # VK_KHR_swapchain_maintenance1 requirement
+ 'vkImportFenceFdKHR',
]
# Functions intercepted at vulkan::driver level.
diff --git a/vulkan/vkjson/vkjson.cc b/vulkan/vkjson/vkjson.cc
index da6b00a..0284192 100644
--- a/vulkan/vkjson/vkjson.cc
+++ b/vulkan/vkjson/vkjson.cc
@@ -731,7 +731,7 @@
visitor->Visit("vulkanMemoryModelAvailabilityVisibilityChains", &features->vulkanMemoryModelAvailabilityVisibilityChains) &&
visitor->Visit("shaderOutputViewportIndex", &features->shaderOutputViewportIndex) &&
visitor->Visit("shaderOutputLayer", &features->shaderOutputLayer) &&
- visitor->Visit("shaderOutputLayer", &features->shaderOutputLayer);
+ visitor->Visit("subgroupBroadcastDynamicId", &features->subgroupBroadcastDynamicId);
}
template <typename Visitor>