Merge "SurfaceFlinger: Add TrustedPresentationListener API"
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index bdd5172..a737bd3 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -167,6 +167,12 @@
}
prebuilt_etc {
+ name: "android.hardware.telephony.satellite.prebuilt.xml",
+ src: "android.hardware.telephony.satellite.xml",
+ defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
name: "android.hardware.usb.accessory.prebuilt.xml",
src: "android.hardware.usb.accessory.xml",
defaults: ["frameworks_native_data_etc_defaults"],
diff --git a/data/etc/android.hardware.telephony.satellite.xml b/data/etc/android.hardware.telephony.satellite.xml
new file mode 100644
index 0000000..5966cba
--- /dev/null
+++ b/data/etc/android.hardware.telephony.satellite.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<!-- Feature for devices that support Satellite communication via Satellite HAL APIs. -->
+<permissions>
+ <feature name="android.hardware.telephony.satellite" />
+</permissions>
diff --git a/include/input/Input.h b/include/input/Input.h
index 62d84e1..7573282 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -529,7 +529,7 @@
inline nsecs_t getEventTime() const { return mEventTime; }
static const char* getLabel(int32_t keyCode);
- static int32_t getKeyCodeFromLabel(const char* label);
+ static std::optional<int> getKeyCodeFromLabel(const char* label);
void initialize(int32_t id, int32_t deviceId, uint32_t source, int32_t displayId,
std::array<uint8_t, 32> hmac, int32_t action, int32_t flags, int32_t keyCode,
@@ -550,6 +550,8 @@
nsecs_t mEventTime;
};
+std::ostream& operator<<(std::ostream& out, const KeyEvent& event);
+
/*
* Motion events.
*/
@@ -840,7 +842,7 @@
}
static const char* getLabel(int32_t axis);
- static int32_t getAxisFromLabel(const char* label);
+ static std::optional<int> getAxisFromLabel(const char* label);
static std::string actionToString(int32_t action);
@@ -1132,6 +1134,7 @@
TYPE_ZOOM_OUT = 1019,
TYPE_GRAB = 1020,
TYPE_GRABBING = 1021,
+ TYPE_HANDWRITING = 1022,
TYPE_SPOT_HOVER = 2000,
TYPE_SPOT_TOUCH = 2001,
diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h
index 09933d3..66d3435 100644
--- a/include/input/InputDevice.h
+++ b/include/input/InputDevice.h
@@ -214,6 +214,12 @@
std::string layoutType;
};
+// The version of the Universal Stylus Initiative (USI) protocol supported by the input device.
+struct InputDeviceUsiVersion {
+ int32_t majorVersion = -1;
+ int32_t minorVersion = -1;
+};
+
/*
* Describes the characteristics and capabilities of an input device.
*/
@@ -235,7 +241,7 @@
void initialize(int32_t id, int32_t generation, int32_t controllerNumber,
const InputDeviceIdentifier& identifier, const std::string& alias,
- bool isExternal, bool hasMic);
+ bool isExternal, bool hasMic, int32_t associatedDisplayId);
inline int32_t getId() const { return mId; }
inline int32_t getControllerNumber() const { return mControllerNumber; }
@@ -295,8 +301,12 @@
std::vector<InputDeviceLightInfo> getLights();
- inline void setSupportsUsi(bool supportsUsi) { mSupportsUsi = supportsUsi; }
- inline bool supportsUsi() const { return mSupportsUsi; }
+ inline void setUsiVersion(std::optional<InputDeviceUsiVersion> usiVersion) {
+ mUsiVersion = std::move(usiVersion);
+ }
+ inline std::optional<InputDeviceUsiVersion> getUsiVersion() const { return mUsiVersion; }
+
+ inline int32_t getAssociatedDisplayId() const { return mAssociatedDisplayId; }
private:
int32_t mId;
@@ -310,8 +320,8 @@
uint32_t mSources;
int32_t mKeyboardType;
std::shared_ptr<KeyCharacterMap> mKeyCharacterMap;
- // Whether this device supports the Universal Stylus Initiative (USI) protocol for styluses.
- bool mSupportsUsi;
+ std::optional<InputDeviceUsiVersion> mUsiVersion;
+ int32_t mAssociatedDisplayId;
bool mHasVibrator;
bool mHasBattery;
diff --git a/include/input/InputEventLabels.h b/include/input/InputEventLabels.h
index b4374ac..4668fce 100644
--- a/include/input/InputEventLabels.h
+++ b/include/input/InputEventLabels.h
@@ -35,22 +35,22 @@
class InputEventLookup {
public:
- static int lookupValueByLabel(const std::unordered_map<std::string, int>& map,
- const char* literal);
+ static std::optional<int> lookupValueByLabel(const std::unordered_map<std::string, int>& map,
+ const char* literal);
static const char* lookupLabelByValue(const std::vector<InputEventLabel>& vec, int value);
- static int32_t getKeyCodeByLabel(const char* label);
+ static std::optional<int> getKeyCodeByLabel(const char* label);
static const char* getLabelByKeyCode(int32_t keyCode);
- static uint32_t getKeyFlagByLabel(const char* label);
+ static std::optional<int> getKeyFlagByLabel(const char* label);
- static int32_t getAxisByLabel(const char* label);
+ static std::optional<int> getAxisByLabel(const char* label);
static const char* getAxisLabel(int32_t axisId);
- static int32_t getLedByLabel(const char* label);
+ static std::optional<int> getLedByLabel(const char* label);
private:
static const std::unordered_map<std::string, int> KEYCODES;
diff --git a/include/input/PrintTools.h b/include/input/PrintTools.h
index e24344b..02bc201 100644
--- a/include/input/PrintTools.h
+++ b/include/input/PrintTools.h
@@ -16,18 +16,35 @@
#pragma once
+#include <bitset>
#include <map>
#include <optional>
#include <set>
#include <string>
+#include <vector>
namespace android {
+template <size_t N>
+std::string bitsetToString(const std::bitset<N>& bitset) {
+ return bitset.to_string();
+}
+
template <typename T>
inline std::string constToString(const T& v) {
return std::to_string(v);
}
+template <>
+inline std::string constToString(const bool& value) {
+ return value ? "true" : "false";
+}
+
+template <>
+inline std::string constToString(const std::vector<bool>::reference& value) {
+ return value ? "true" : "false";
+}
+
inline std::string constToString(const std::string& s) {
return s;
}
@@ -70,6 +87,19 @@
return out;
}
+/**
+ * Convert a vector to a string. The values of the vector should be of a type supported by
+ * constToString.
+ */
+template <typename T>
+std::string dumpVector(std::vector<T> values) {
+ std::string dump = constToString(values[0]);
+ for (size_t i = 1; i < values.size(); i++) {
+ dump += ", " + constToString(values[i]);
+ }
+ return dump;
+}
+
const char* toString(bool value);
/**
@@ -81,4 +111,4 @@
*/
std::string addLinePrefix(std::string str, const std::string& prefix);
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index 19445d1..808b1ec 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -76,7 +76,6 @@
srcs: [
"Binder.cpp",
- "BinderRecordReplay.cpp",
"BpBinder.cpp",
"Debug.cpp",
"FdTrigger.cpp",
@@ -84,6 +83,7 @@
"IResultReceiver.cpp",
"Parcel.cpp",
"ParcelFileDescriptor.cpp",
+ "RecordedTransaction.cpp",
"RpcSession.cpp",
"RpcServer.cpp",
"RpcState.cpp",
diff --git a/libs/binder/Binder.cpp b/libs/binder/Binder.cpp
index da5affb..3e49656 100644
--- a/libs/binder/Binder.cpp
+++ b/libs/binder/Binder.cpp
@@ -21,13 +21,13 @@
#include <android-base/logging.h>
#include <android-base/unique_fd.h>
-#include <binder/BinderRecordReplay.h>
#include <binder/BpBinder.h>
#include <binder/IInterface.h>
#include <binder/IPCThreadState.h>
#include <binder/IResultReceiver.h>
#include <binder/IShellCallback.h>
#include <binder/Parcel.h>
+#include <binder/RecordedTransaction.h>
#include <binder/RpcServer.h>
#include <cutils/compiler.h>
#include <private/android_filesystem_config.h>
@@ -409,11 +409,9 @@
Parcel emptyReply;
timespec ts;
timespec_get(&ts, TIME_UTC);
- auto transaction =
- android::binder::debug::RecordedTransaction::fromDetails(code, flags, ts, data,
- reply ? *reply
- : emptyReply,
- err);
+ auto transaction = android::binder::debug::RecordedTransaction::
+ fromDetails(getInterfaceDescriptor(), code, flags, ts, data,
+ reply ? *reply : emptyReply, err);
if (transaction) {
if (status_t err = transaction->dumpToFile(e->mRecordingFd); err != NO_ERROR) {
LOG(INFO) << "Failed to dump RecordedTransaction to file with error " << err;
diff --git a/libs/binder/BpBinder.cpp b/libs/binder/BpBinder.cpp
index 1c470a1..d03326e 100644
--- a/libs/binder/BpBinder.cpp
+++ b/libs/binder/BpBinder.cpp
@@ -388,7 +388,7 @@
{
if (isRpcBinder()) {
if (rpcSession()->getMaxIncomingThreads() < 1) {
- LOG_ALWAYS_FATAL("Cannot register a DeathRecipient without any incoming connections.");
+ ALOGE("Cannot register a DeathRecipient without any incoming connections.");
return INVALID_OPERATION;
}
} else if constexpr (!kEnableKernelIpc) {
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index 44ff62b..0aca163 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -375,6 +375,10 @@
return (mDataSize > mDataPos ? mDataSize : mDataPos);
}
+size_t Parcel::dataBufferSize() const {
+ return mDataSize;
+}
+
size_t Parcel::dataAvail() const
{
size_t result = dataSize() - dataPosition();
diff --git a/libs/binder/BinderRecordReplay.cpp b/libs/binder/RecordedTransaction.cpp
similarity index 86%
rename from libs/binder/BinderRecordReplay.cpp
rename to libs/binder/RecordedTransaction.cpp
index 58bb106..5406205 100644
--- a/libs/binder/BinderRecordReplay.cpp
+++ b/libs/binder/RecordedTransaction.cpp
@@ -17,7 +17,7 @@
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/unique_fd.h>
-#include <binder/BinderRecordReplay.h>
+#include <binder/RecordedTransaction.h>
#include <sys/mman.h>
#include <algorithm>
@@ -106,24 +106,30 @@
// End Chunk may therefore produce an empty, meaningless RecordedTransaction.
RecordedTransaction::RecordedTransaction(RecordedTransaction&& t) noexcept {
- mHeader = t.mHeader;
+ mData = t.mData;
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) {
+std::optional<RecordedTransaction> RecordedTransaction::fromDetails(
+ const String16& interfaceName, 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<int32_t>(err),
- 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};
+ t.mData.mHeader = {code,
+ flags,
+ static_cast<int32_t>(err),
+ 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};
+
+ t.mData.mInterfaceName = String8(interfaceName);
+ if (interfaceName.size() != t.mData.mInterfaceName.bytes()) {
+ LOG(ERROR) << "Interface Name is not valid. Contains characters that aren't single byte "
+ "utf-8: "
+ << interfaceName;
+ return std::nullopt;
+ }
if (t.mSent.setData(dataParcel.data(), dataParcel.dataSize()) != android::NO_ERROR) {
LOG(ERROR) << "Failed to set sent parcel data.";
@@ -142,6 +148,7 @@
HEADER_CHUNK = 1,
DATA_PARCEL_CHUNK = 2,
REPLY_PARCEL_CHUNK = 3,
+ INTERFACE_NAME_CHUNK = 4,
END_CHUNK = 0x00ffffff,
};
@@ -220,7 +227,11 @@
<< sizeof(TransactionHeader) << ".";
return std::nullopt;
}
- t.mHeader = *reinterpret_cast<TransactionHeader*>(payloadMap);
+ t.mData.mHeader = *reinterpret_cast<TransactionHeader*>(payloadMap);
+ break;
+ }
+ case INTERFACE_NAME_CHUNK: {
+ t.mData.mInterfaceName.setTo(reinterpret_cast<char*>(payloadMap), chunk.dataSize);
break;
}
case DATA_PARCEL_CHUNK: {
@@ -291,10 +302,17 @@
android::status_t RecordedTransaction::dumpToFile(const unique_fd& fd) const {
if (NO_ERROR !=
writeChunk(fd, HEADER_CHUNK, sizeof(TransactionHeader),
- reinterpret_cast<const uint8_t*>(&mHeader))) {
+ reinterpret_cast<const uint8_t*>(&(mData.mHeader)))) {
LOG(ERROR) << "Failed to write transactionHeader to fd " << fd.get();
return UNKNOWN_ERROR;
}
+ if (NO_ERROR !=
+ writeChunk(fd, INTERFACE_NAME_CHUNK, mData.mInterfaceName.size() * sizeof(uint8_t),
+ reinterpret_cast<const uint8_t*>(mData.mInterfaceName.string()))) {
+ LOG(INFO) << "Failed to write Interface Name Chunk to fd " << fd.get();
+ return UNKNOWN_ERROR;
+ }
+
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;
@@ -310,26 +328,30 @@
return NO_ERROR;
}
+const android::String8& RecordedTransaction::getInterfaceName() const {
+ return mData.mInterfaceName;
+}
+
uint32_t RecordedTransaction::getCode() const {
- return mHeader.code;
+ return mData.mHeader.code;
}
uint32_t RecordedTransaction::getFlags() const {
- return mHeader.flags;
+ return mData.mHeader.flags;
}
int32_t RecordedTransaction::getReturnedStatus() const {
- return mHeader.statusReturned;
+ return mData.mHeader.statusReturned;
}
timespec RecordedTransaction::getTimestamp() const {
- time_t sec = mHeader.timestampSeconds;
- int32_t nsec = mHeader.timestampNanoseconds;
+ time_t sec = mData.mHeader.timestampSeconds;
+ int32_t nsec = mData.mHeader.timestampNanoseconds;
return (timespec){.tv_sec = sec, .tv_nsec = nsec};
}
uint32_t RecordedTransaction::getVersion() const {
- return mHeader.version;
+ return mData.mHeader.version;
}
const Parcel& RecordedTransaction::getDataParcel() const {
diff --git a/libs/binder/TEST_MAPPING b/libs/binder/TEST_MAPPING
index 342e4a3..04cb61f 100644
--- a/libs/binder/TEST_MAPPING
+++ b/libs/binder/TEST_MAPPING
@@ -52,6 +52,9 @@
"name": "memunreachable_binder_test"
},
{
+ "name": "resolv_integration_test"
+ },
+ {
"name": "libbinderthreadstateutils_test"
},
{
@@ -96,5 +99,19 @@
{
"name": "binderLibTest"
}
+ ],
+ "kernel-presubmit": [
+ {
+ "name": "binderDriverInterfaceTest"
+ },
+ {
+ "name": "binderLibTest"
+ },
+ {
+ "name": "binderSafeInterfaceTest"
+ },
+ {
+ "name": "memunreachable_binder_test"
+ }
]
}
diff --git a/libs/binder/include/binder/Binder.h b/libs/binder/include/binder/Binder.h
index 08dbd13..d960a0b 100644
--- a/libs/binder/include/binder/Binder.h
+++ b/libs/binder/include/binder/Binder.h
@@ -106,7 +106,7 @@
const sp<IBinder>& keepAliveBinder);
// Start recording transactions to the unique_fd in data.
- // See BinderRecordReplay.h for more details.
+ // See RecordedTransaction.h for more details.
[[nodiscard]] status_t startRecordingTransactions(const Parcel& data);
// Stop the current recording.
[[nodiscard]] status_t stopRecordingTransactions();
diff --git a/libs/binder/include/binder/BpBinder.h b/libs/binder/include/binder/BpBinder.h
index 57e103d..5496d61 100644
--- a/libs/binder/include/binder/BpBinder.h
+++ b/libs/binder/include/binder/BpBinder.h
@@ -91,7 +91,7 @@
std::optional<int32_t> getDebugBinderHandle() const;
// Start recording transactions to the unique_fd.
- // See BinderRecordReplay.h for more details.
+ // See RecordedTransaction.h for more details.
status_t startRecordingBinder(const android::base::unique_fd& fd);
// Stop the current recording.
status_t stopRecordingBinder();
diff --git a/libs/binder/include/binder/Parcel.h b/libs/binder/include/binder/Parcel.h
index f730acb..162cd40 100644
--- a/libs/binder/include/binder/Parcel.h
+++ b/libs/binder/include/binder/Parcel.h
@@ -75,6 +75,7 @@
size_t dataAvail() const;
size_t dataPosition() const;
size_t dataCapacity() const;
+ size_t dataBufferSize() const;
status_t setDataSize(size_t size);
diff --git a/libs/binder/include/binder/BinderRecordReplay.h b/libs/binder/include/binder/RecordedTransaction.h
similarity index 87%
rename from libs/binder/include/binder/BinderRecordReplay.h
rename to libs/binder/include/binder/RecordedTransaction.h
index ff983f0..4966330 100644
--- a/libs/binder/include/binder/BinderRecordReplay.h
+++ b/libs/binder/include/binder/RecordedTransaction.h
@@ -26,20 +26,22 @@
// Warning: Transactions are sequentially recorded to the file descriptor in a
// non-stable format. A detailed description of the recording format can be found in
-// BinderRecordReplay.cpp.
+// RecordedTransaction.cpp.
class RecordedTransaction {
public:
// Filled with the first transaction from fd.
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,
+ static std::optional<RecordedTransaction> fromDetails(const String16& interfaceName,
+ uint32_t code, uint32_t flags,
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;
+ const String8& getInterfaceName() const;
uint32_t getCode() const;
uint32_t getFlags() const;
int32_t getReturnedStatus() const;
@@ -69,7 +71,11 @@
static_assert(sizeof(TransactionHeader) == 32);
static_assert(sizeof(TransactionHeader) % 8 == 0);
- TransactionHeader mHeader;
+ struct MovableData { // movable
+ TransactionHeader mHeader;
+ String8 mInterfaceName;
+ };
+ MovableData mData;
Parcel mSent;
Parcel mReply;
};
diff --git a/libs/binder/ndk/parcel.cpp b/libs/binder/ndk/parcel.cpp
index 8693022..b5a2e2f 100644
--- a/libs/binder/ndk/parcel.cpp
+++ b/libs/binder/ndk/parcel.cpp
@@ -695,11 +695,17 @@
if (parcel->get()->objectsCount()) {
return STATUS_INVALID_OPERATION;
}
- int32_t dataSize = AParcel_getDataSize(parcel);
+ // b/264739302 - getDataSize will return dataPos if it is greater than dataSize
+ // which will cause crashes in memcpy at later point. Instead compare with
+ // actual length of internal buffer
+ int32_t dataSize = parcel->get()->dataBufferSize();
if (len > static_cast<size_t>(dataSize) || start > static_cast<size_t>(dataSize) - len) {
return STATUS_BAD_VALUE;
}
const uint8_t* internalBuffer = parcel->get()->data();
+ if (internalBuffer == nullptr) {
+ return STATUS_UNEXPECTED_NULL;
+ }
memcpy(buffer, internalBuffer + start, len);
return STATUS_OK;
}
diff --git a/libs/binder/rust/src/lib.rs b/libs/binder/rust/src/lib.rs
index a0e61d9..0c8b48f 100644
--- a/libs/binder/rust/src/lib.rs
+++ b/libs/binder/rust/src/lib.rs
@@ -94,14 +94,12 @@
//! ```
#[macro_use]
-mod proxy;
-
-#[macro_use]
mod binder;
mod binder_async;
mod error;
mod native;
mod parcel;
+mod proxy;
mod state;
use binder_ndk_sys as sys;
diff --git a/libs/binder/rust/tests/parcel_fuzzer/parcel_fuzzer.rs b/libs/binder/rust/tests/parcel_fuzzer/parcel_fuzzer.rs
index c5c7719..29bf92c 100644
--- a/libs/binder/rust/tests/parcel_fuzzer/parcel_fuzzer.rs
+++ b/libs/binder/rust/tests/parcel_fuzzer/parcel_fuzzer.rs
@@ -17,9 +17,6 @@
#![allow(missing_docs)]
#![no_main]
-#[macro_use]
-extern crate libfuzzer_sys;
-
mod read_utils;
use crate::read_utils::READ_FUNCS;
@@ -31,7 +28,7 @@
StatusCode,
};
use binder_random_parcel_rs::create_random_parcel;
-use libfuzzer_sys::arbitrary::Arbitrary;
+use libfuzzer_sys::{arbitrary::Arbitrary, fuzz_target};
#[derive(Arbitrary, Debug)]
enum ReadOperation {
diff --git a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/service_fuzzer.rs b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/service_fuzzer.rs
index a427f28..c530382 100644
--- a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/service_fuzzer.rs
+++ b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/service_fuzzer.rs
@@ -16,8 +16,8 @@
#![allow(missing_docs)]
#![no_main]
-#[macro_use]
-extern crate libfuzzer_sys;
+
+use libfuzzer_sys::fuzz_target;
use binder::{self, BinderFeatures, Interface};
use binder_random_parcel_rs::fuzz_service;
diff --git a/libs/binder/tests/binderRecordedTransactionTest.cpp b/libs/binder/tests/binderRecordedTransactionTest.cpp
index 67553fc..2f5c8c6 100644
--- a/libs/binder/tests/binderRecordedTransactionTest.cpp
+++ b/libs/binder/tests/binderRecordedTransactionTest.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include <binder/BinderRecordReplay.h>
+#include <binder/RecordedTransaction.h>
#include <gtest/gtest.h>
#include <utils/Errors.h>
@@ -24,13 +24,16 @@
using android::binder::debug::RecordedTransaction;
TEST(BinderRecordedTransaction, RoundTripEncoding) {
+ android::String16 interfaceName("SampleInterface");
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 transaction = RecordedTransaction::fromDetails(interfaceName, 1, 42, ts, d, r, 0);
+ EXPECT_TRUE(transaction.has_value());
auto file = std::tmpfile();
auto fd = unique_fd(fcntl(fileno(file), F_DUPFD, 1));
@@ -42,6 +45,7 @@
auto retrievedTransaction = RecordedTransaction::fromFile(fd);
+ EXPECT_EQ(retrievedTransaction->getInterfaceName(), android::String8(interfaceName));
EXPECT_EQ(retrievedTransaction->getCode(), 1);
EXPECT_EQ(retrievedTransaction->getFlags(), 42);
EXPECT_EQ(retrievedTransaction->getTimestamp().tv_sec, ts.tv_sec);
@@ -57,13 +61,14 @@
}
TEST(BinderRecordedTransaction, Checksum) {
+ android::String16 interfaceName("SampleInterface");
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 transaction = RecordedTransaction::fromDetails(interfaceName, 1, 42, ts, d, r, 0);
auto file = std::tmpfile();
auto fd = unique_fd(fcntl(fileno(file), F_DUPFD, 1));
@@ -91,6 +96,7 @@
std::vector<uint8_t> largePayload;
uint8_t filler = 0xaa;
largePayload.insert(largePayload.end(), largeDataSize, filler);
+ android::String16 interfaceName("SampleInterface");
Parcel d;
d.writeInt32(12);
d.writeInt64(2);
@@ -98,7 +104,7 @@
Parcel r;
r.writeInt32(99);
timespec ts = {1232456, 567890};
- auto transaction = RecordedTransaction::fromDetails(1, 42, ts, d, r, 0);
+ auto transaction = RecordedTransaction::fromDetails(interfaceName, 1, 42, ts, d, r, 0);
auto file = std::tmpfile();
auto fd = unique_fd(fcntl(fileno(file), F_DUPFD, 1));
diff --git a/libs/binder/tests/binderRpcTest.cpp b/libs/binder/tests/binderRpcTest.cpp
index 9be5b87..36c8d8c 100644
--- a/libs/binder/tests/binderRpcTest.cpp
+++ b/libs/binder/tests/binderRpcTest.cpp
@@ -683,7 +683,7 @@
proc.expectAlreadyShutdown = true;
}
-TEST_P(BinderRpc, DeathRecipientFatalWithoutIncoming) {
+TEST_P(BinderRpc, DeathRecipientFailsWithoutIncoming) {
class MyDeathRec : public IBinder::DeathRecipient {
public:
void binderDied(const wp<IBinder>& /* who */) override {}
@@ -693,8 +693,7 @@
{.numThreads = 1, .numSessions = 1, .numIncomingConnections = 0});
auto dr = sp<MyDeathRec>::make();
- EXPECT_DEATH(proc.rootBinder->linkToDeath(dr, (void*)1, 0),
- "Cannot register a DeathRecipient without any incoming connections.");
+ EXPECT_EQ(INVALID_OPERATION, proc.rootBinder->linkToDeath(dr, (void*)1, 0));
}
TEST_P(BinderRpc, UnlinkDeathRecipient) {
diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp
index a77ca04..cefb9a7 100644
--- a/libs/gui/ISurfaceComposer.cpp
+++ b/libs/gui/ISurfaceComposer.cpp
@@ -63,7 +63,8 @@
Vector<ComposerState>& state, const Vector<DisplayState>& displays,
uint32_t flags, const sp<IBinder>& applyToken,
const InputWindowCommands& commands, int64_t desiredPresentTime,
- bool isAutoTimestamp, const client_cache_t& uncacheBuffer,
+ bool isAutoTimestamp,
+ const std::vector<client_cache_t>& uncacheBuffers,
bool hasListenerCallbacks,
const std::vector<ListenerCallbacks>& listenerCallbacks,
uint64_t transactionId) override {
@@ -87,8 +88,11 @@
SAFE_PARCEL(commands.write, data);
SAFE_PARCEL(data.writeInt64, desiredPresentTime);
SAFE_PARCEL(data.writeBool, isAutoTimestamp);
- SAFE_PARCEL(data.writeStrongBinder, uncacheBuffer.token.promote());
- SAFE_PARCEL(data.writeUint64, uncacheBuffer.id);
+ SAFE_PARCEL(data.writeUint32, static_cast<uint32_t>(uncacheBuffers.size()));
+ for (const client_cache_t& uncacheBuffer : uncacheBuffers) {
+ SAFE_PARCEL(data.writeStrongBinder, uncacheBuffer.token.promote());
+ SAFE_PARCEL(data.writeUint64, uncacheBuffer.id);
+ }
SAFE_PARCEL(data.writeBool, hasListenerCallbacks);
SAFE_PARCEL(data.writeVectorSize, listenerCallbacks);
@@ -158,11 +162,14 @@
SAFE_PARCEL(data.readInt64, &desiredPresentTime);
SAFE_PARCEL(data.readBool, &isAutoTimestamp);
- client_cache_t uncachedBuffer;
+ SAFE_PARCEL_READ_SIZE(data.readUint32, &count, data.dataSize());
+ std::vector<client_cache_t> uncacheBuffers(count);
sp<IBinder> tmpBinder;
- SAFE_PARCEL(data.readNullableStrongBinder, &tmpBinder);
- uncachedBuffer.token = tmpBinder;
- SAFE_PARCEL(data.readUint64, &uncachedBuffer.id);
+ for (size_t i = 0; i < count; i++) {
+ SAFE_PARCEL(data.readNullableStrongBinder, &tmpBinder);
+ uncacheBuffers[i].token = tmpBinder;
+ SAFE_PARCEL(data.readUint64, &uncacheBuffers[i].id);
+ }
bool hasListenerCallbacks = false;
SAFE_PARCEL(data.readBool, &hasListenerCallbacks);
@@ -182,7 +189,7 @@
return setTransactionState(frameTimelineInfo, state, displays, stateFlags, applyToken,
inputWindowCommands, desiredPresentTime, isAutoTimestamp,
- uncachedBuffer, hasListenerCallbacks, listenerCallbacks,
+ uncacheBuffers, hasListenerCallbacks, listenerCallbacks,
transactionId);
}
default: {
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 732a168..5bdeae8 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -606,11 +606,13 @@
return NO_ERROR;
}
- uint64_t cache(const sp<GraphicBuffer>& buffer) {
+ uint64_t cache(const sp<GraphicBuffer>& buffer,
+ std::optional<client_cache_t>& outUncacheBuffer) {
std::lock_guard<std::mutex> lock(mMutex);
if (mBuffers.size() >= BUFFER_CACHE_MAX_SIZE) {
- evictLeastRecentlyUsedBuffer();
+ outUncacheBuffer = findLeastRecentlyUsedBuffer();
+ mBuffers.erase(outUncacheBuffer->id);
}
buffer->addDeathCallback(removeDeadBufferCallback, nullptr);
@@ -621,16 +623,13 @@
void uncache(uint64_t cacheId) {
std::lock_guard<std::mutex> lock(mMutex);
- uncacheLocked(cacheId);
- }
-
- void uncacheLocked(uint64_t cacheId) REQUIRES(mMutex) {
- mBuffers.erase(cacheId);
- SurfaceComposerClient::doUncacheBufferTransaction(cacheId);
+ if (mBuffers.erase(cacheId)) {
+ SurfaceComposerClient::doUncacheBufferTransaction(cacheId);
+ }
}
private:
- void evictLeastRecentlyUsedBuffer() REQUIRES(mMutex) {
+ client_cache_t findLeastRecentlyUsedBuffer() REQUIRES(mMutex) {
auto itr = mBuffers.begin();
uint64_t minCounter = itr->second;
auto minBuffer = itr;
@@ -644,7 +643,8 @@
}
itr++;
}
- uncacheLocked(minBuffer->first);
+
+ return {.token = getToken(), .id = minBuffer->first};
}
uint64_t getCounter() REQUIRES(mMutex) {
@@ -782,6 +782,18 @@
InputWindowCommands inputWindowCommands;
inputWindowCommands.read(*parcel);
+ count = static_cast<size_t>(parcel->readUint32());
+ if (count > parcel->dataSize()) {
+ return BAD_VALUE;
+ }
+ std::vector<client_cache_t> uncacheBuffers(count);
+ for (size_t i = 0; i < count; i++) {
+ sp<IBinder> tmpBinder;
+ SAFE_PARCEL(parcel->readStrongBinder, &tmpBinder);
+ uncacheBuffers[i].token = tmpBinder;
+ SAFE_PARCEL(parcel->readUint64, &uncacheBuffers[i].id);
+ }
+
// Parsing was successful. Update the object.
mId = transactionId;
mTransactionNestCount = transactionNestCount;
@@ -796,6 +808,7 @@
mComposerStates = composerStates;
mInputWindowCommands = inputWindowCommands;
mApplyToken = applyToken;
+ mUncacheBuffers = std::move(uncacheBuffers);
return NO_ERROR;
}
@@ -847,6 +860,13 @@
}
mInputWindowCommands.write(*parcel);
+
+ SAFE_PARCEL(parcel->writeUint32, static_cast<uint32_t>(mUncacheBuffers.size()));
+ for (const client_cache_t& uncacheBuffer : mUncacheBuffers) {
+ SAFE_PARCEL(parcel->writeStrongBinder, uncacheBuffer.token.promote());
+ SAFE_PARCEL(parcel->writeUint64, uncacheBuffer.id);
+ }
+
return NO_ERROR;
}
@@ -914,6 +934,10 @@
}
}
+ for (const auto& cacheId : other.mUncacheBuffers) {
+ mUncacheBuffers.push_back(cacheId);
+ }
+
mInputWindowCommands.merge(other.mInputWindowCommands);
mMayContainBuffer |= other.mMayContainBuffer;
@@ -932,6 +956,7 @@
mDisplayStates.clear();
mListenerCallbacks.clear();
mInputWindowCommands.clear();
+ mUncacheBuffers.clear();
mMayContainBuffer = false;
mTransactionNestCount = 0;
mAnimation = false;
@@ -954,10 +979,10 @@
uncacheBuffer.token = BufferCache::getInstance().getToken();
uncacheBuffer.id = cacheId;
Vector<ComposerState> composerStates;
- status_t status =
- sf->setTransactionState(FrameTimelineInfo{}, composerStates, {},
- ISurfaceComposer::eOneWay, Transaction::getDefaultApplyToken(),
- {}, systemTime(), true, uncacheBuffer, false, {}, generateId());
+ status_t status = sf->setTransactionState(FrameTimelineInfo{}, composerStates, {},
+ ISurfaceComposer::eOneWay,
+ Transaction::getDefaultApplyToken(), {}, systemTime(),
+ true, {uncacheBuffer}, false, {}, generateId());
if (status != NO_ERROR) {
ALOGE_AND_TRACE("SurfaceComposerClient::doUncacheBufferTransaction - %s",
strerror(-status));
@@ -995,7 +1020,11 @@
s->bufferData->buffer = nullptr;
} else {
// Cache-miss. Include the buffer and send the new cacheId.
- cacheId = BufferCache::getInstance().cache(s->bufferData->buffer);
+ std::optional<client_cache_t> uncacheBuffer;
+ cacheId = BufferCache::getInstance().cache(s->bufferData->buffer, uncacheBuffer);
+ if (uncacheBuffer) {
+ mUncacheBuffers.push_back(*uncacheBuffer);
+ }
}
s->bufferData->flags |= BufferData::BufferDataChange::cachedBufferChanged;
s->bufferData->cachedBuffer.token = BufferCache::getInstance().getToken();
@@ -1128,8 +1157,7 @@
sp<ISurfaceComposer> sf(ComposerService::getComposerService());
sf->setTransactionState(mFrameTimelineInfo, composerStates, displayStates, flags, applyToken,
mInputWindowCommands, mDesiredPresentTime, mIsAutoTimestamp,
- {} /*uncacheBuffer - only set in doUncacheBufferTransaction*/,
- hasListenerCallbacks, listenerCallbacks, mId);
+ mUncacheBuffers, hasListenerCallbacks, listenerCallbacks, mId);
mId = generateId();
// Clear the current states and flags
diff --git a/libs/gui/aidl/android/gui/OverlayProperties.aidl b/libs/gui/aidl/android/gui/OverlayProperties.aidl
index 1af5746..5fb1a83 100644
--- a/libs/gui/aidl/android/gui/OverlayProperties.aidl
+++ b/libs/gui/aidl/android/gui/OverlayProperties.aidl
@@ -20,7 +20,9 @@
parcelable OverlayProperties {
parcelable SupportedBufferCombinations {
int[] pixelFormats;
- int[] dataspaces;
+ int[] standards;
+ int[] transfers;
+ int[] ranges;
}
SupportedBufferCombinations[] combinations;
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index 045cc2a..ae56f9f 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -113,8 +113,9 @@
const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& state,
const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime,
- bool isAutoTimestamp, const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,
- const std::vector<ListenerCallbacks>& listenerCallbacks, uint64_t transactionId) = 0;
+ bool isAutoTimestamp, const std::vector<client_cache_t>& uncacheBuffer,
+ bool hasListenerCallbacks, const std::vector<ListenerCallbacks>& listenerCallbacks,
+ uint64_t transactionId) = 0;
};
// ----------------------------------------------------------------------------
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 17ed2d8..5552775 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -222,26 +222,46 @@
uint64_t diff(const layer_state_t& other) const;
bool hasBufferChanges() const;
- // Changes to the tree structure.
- static constexpr uint64_t HIERARCHY_CHANGES = layer_state_t::eLayerChanged |
- layer_state_t::eRelativeLayerChanged | layer_state_t::eReparent |
- layer_state_t::eBackgroundColorChanged;
+ // Layer hierarchy updates.
+ static constexpr uint64_t HIERARCHY_CHANGES = layer_state_t::eBackgroundColorChanged |
+ layer_state_t::eLayerChanged | layer_state_t::eRelativeLayerChanged |
+ layer_state_t::eReparent;
+
+ // Geometry updates.
+ static constexpr uint64_t GEOMETRY_CHANGES = layer_state_t::eBufferCropChanged |
+ layer_state_t::eBufferTransformChanged | layer_state_t::eCropChanged |
+ layer_state_t::eDestinationFrameChanged | layer_state_t::eMatrixChanged |
+ layer_state_t::ePositionChanged | layer_state_t::eTransformToDisplayInverseChanged |
+ layer_state_t::eTransparentRegionChanged;
+
+ // Buffer and related updates.
+ static constexpr uint64_t BUFFER_CHANGES = layer_state_t::eApiChanged |
+ layer_state_t::eBufferChanged | layer_state_t::eBufferCropChanged |
+ layer_state_t::eBufferTransformChanged | layer_state_t::eDataspaceChanged |
+ layer_state_t::eSidebandStreamChanged | layer_state_t::eSurfaceDamageRegionChanged |
+ layer_state_t::eTransformToDisplayInverseChanged |
+ layer_state_t::eTransparentRegionChanged;
+
// Content updates.
- static constexpr uint64_t CONTENT_CHANGES = layer_state_t::eAlphaChanged |
- layer_state_t::eTransparentRegionChanged | layer_state_t::eShadowRadiusChanged |
- layer_state_t::eRenderBorderChanged | layer_state_t::eColorChanged |
- layer_state_t::eBufferChanged | layer_state_t::eDataspaceChanged |
- layer_state_t::eApiChanged | layer_state_t::eSidebandStreamChanged |
+ static constexpr uint64_t CONTENT_CHANGES = layer_state_t::BUFFER_CHANGES |
+ layer_state_t::eAlphaChanged | layer_state_t::eAutoRefreshChanged |
+ layer_state_t::eBackgroundBlurRadiusChanged | layer_state_t::eBackgroundColorChanged |
+ layer_state_t::eBlurRegionsChanged | layer_state_t::eColorChanged |
+ layer_state_t::eColorSpaceAgnosticChanged | layer_state_t::eColorTransformChanged |
+ layer_state_t::eCornerRadiusChanged | layer_state_t::eHdrMetadataChanged |
+ layer_state_t::eRenderBorderChanged | layer_state_t::eShadowRadiusChanged |
+ layer_state_t::eStretchChanged;
+
+ // Changes which invalidates the layer's visible region in CE.
+ static constexpr uint64_t CONTENT_DIRTY = layer_state_t::CONTENT_CHANGES |
+ layer_state_t::GEOMETRY_CHANGES | layer_state_t::HIERARCHY_CHANGES;
+
+ // Changes affecting child states.
+ static constexpr uint64_t AFFECTS_CHILDREN = layer_state_t::GEOMETRY_CHANGES |
+ layer_state_t::HIERARCHY_CHANGES | layer_state_t::eAlphaChanged |
layer_state_t::eColorTransformChanged | layer_state_t::eCornerRadiusChanged |
- layer_state_t::eBackgroundColorChanged | layer_state_t::eColorSpaceAgnosticChanged |
- layer_state_t::eBackgroundBlurRadiusChanged | layer_state_t::eBlurRegionsChanged |
- layer_state_t::eAutoRefreshChanged | layer_state_t::eStretchChanged;
- // Changes to content or children size.
- static constexpr uint64_t GEOMETRY_CHANGES = layer_state_t::ePositionChanged |
- layer_state_t::eMatrixChanged | layer_state_t::eTransparentRegionChanged |
- layer_state_t::eBufferCropChanged | layer_state_t::eBufferTransformChanged |
- layer_state_t::eTransformToDisplayInverseChanged | layer_state_t::eCropChanged |
- layer_state_t::eDestinationFrameChanged;
+ layer_state_t::eFlagsChanged | layer_state_t::eLayerStackChanged |
+ layer_state_t::eTrustedOverlayChanged;
bool hasValidBuffer() const;
void sanitize(int32_t permissions);
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 6c45b26..439e060 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -412,6 +412,7 @@
SortedVector<DisplayState> mDisplayStates;
std::unordered_map<sp<ITransactionCompletedListener>, CallbackInfo, TCLHash>
mListenerCallbacks;
+ std::vector<client_cache_t> mUncacheBuffers;
uint64_t mId;
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index 3014804..32d60cd 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -701,7 +701,7 @@
const sp<IBinder>& /*applyToken*/,
const InputWindowCommands& /*inputWindowCommands*/,
int64_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/,
- const client_cache_t& /*cachedBuffer*/,
+ const std::vector<client_cache_t>& /*cachedBuffer*/,
bool /*hasListenerCallbacks*/,
const std::vector<ListenerCallbacks>& /*listenerCallbacks*/,
uint64_t /*transactionId*/) override {
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index c796439..133b260 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -299,7 +299,7 @@
return InputEventLookup::getLabelByKeyCode(keyCode);
}
-int32_t KeyEvent::getKeyCodeFromLabel(const char* label) {
+std::optional<int> KeyEvent::getKeyCodeFromLabel(const char* label) {
return InputEventLookup::getKeyCodeByLabel(label);
}
@@ -343,6 +343,28 @@
return "UNKNOWN";
}
+std::ostream& operator<<(std::ostream& out, const KeyEvent& event) {
+ out << "KeyEvent { action=" << KeyEvent::actionToString(event.getAction());
+
+ out << ", keycode=" << event.getKeyCode() << "(" << KeyEvent::getLabel(event.getKeyCode())
+ << ")";
+
+ if (event.getMetaState() != 0) {
+ out << ", metaState=" << event.getMetaState();
+ }
+
+ out << ", eventTime=" << event.getEventTime();
+ out << ", downTime=" << event.getDownTime();
+ out << ", flags=" << std::hex << event.getFlags() << std::dec;
+ out << ", repeatCount=" << event.getRepeatCount();
+ out << ", deviceId=" << event.getDeviceId();
+ out << ", source=" << inputEventSourceToString(event.getSource());
+ out << ", displayId=" << event.getDisplayId();
+ out << ", eventId=" << event.getId();
+ out << "}";
+ return out;
+}
+
// --- PointerCoords ---
float PointerCoords::getAxisValue(int32_t axis) const {
@@ -869,7 +891,7 @@
return InputEventLookup::getAxisLabel(axis);
}
-int32_t MotionEvent::getAxisFromLabel(const char* label) {
+std::optional<int> MotionEvent::getAxisFromLabel(const char* label) {
return InputEventLookup::getAxisByLabel(label);
}
diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp
index 87333f2..9c7c0c1 100644
--- a/libs/input/InputDevice.cpp
+++ b/libs/input/InputDevice.cpp
@@ -22,6 +22,7 @@
#include <android-base/stringprintf.h>
#include <ftl/enum.h>
+#include <gui/constants.h>
#include <input/InputDevice.h>
#include <input/InputEventLabels.h>
@@ -166,7 +167,7 @@
// --- InputDeviceInfo ---
InputDeviceInfo::InputDeviceInfo() {
- initialize(-1, 0, -1, InputDeviceIdentifier(), "", false, false);
+ initialize(-1, 0, -1, InputDeviceIdentifier(), "", false, false, ADISPLAY_ID_NONE);
}
InputDeviceInfo::InputDeviceInfo(const InputDeviceInfo& other)
@@ -181,7 +182,8 @@
mSources(other.mSources),
mKeyboardType(other.mKeyboardType),
mKeyCharacterMap(other.mKeyCharacterMap),
- mSupportsUsi(other.mSupportsUsi),
+ mUsiVersion(other.mUsiVersion),
+ mAssociatedDisplayId(other.mAssociatedDisplayId),
mHasVibrator(other.mHasVibrator),
mHasBattery(other.mHasBattery),
mHasButtonUnderPad(other.mHasButtonUnderPad),
@@ -195,7 +197,7 @@
void InputDeviceInfo::initialize(int32_t id, int32_t generation, int32_t controllerNumber,
const InputDeviceIdentifier& identifier, const std::string& alias,
- bool isExternal, bool hasMic) {
+ bool isExternal, bool hasMic, int32_t associatedDisplayId) {
mId = id;
mGeneration = generation;
mControllerNumber = controllerNumber;
@@ -205,11 +207,12 @@
mHasMic = hasMic;
mSources = 0;
mKeyboardType = AINPUT_KEYBOARD_TYPE_NONE;
+ mAssociatedDisplayId = associatedDisplayId;
mHasVibrator = false;
mHasBattery = false;
mHasButtonUnderPad = false;
mHasSensor = false;
- mSupportsUsi = false;
+ mUsiVersion.reset();
mMotionRanges.clear();
mSensors.clear();
mLights.clear();
diff --git a/libs/input/InputEventLabels.cpp b/libs/input/InputEventLabels.cpp
index 7159e27..d97c1bb 100644
--- a/libs/input/InputEventLabels.cpp
+++ b/libs/input/InputEventLabels.cpp
@@ -438,11 +438,11 @@
const std::unordered_map<std::string, int> InputEventLookup::FLAGS = {FLAGS_SEQUENCE};
-int InputEventLookup::lookupValueByLabel(const std::unordered_map<std::string, int>& map,
- const char* literal) {
+std::optional<int> InputEventLookup::lookupValueByLabel(
+ const std::unordered_map<std::string, int>& map, const char* literal) {
std::string str(literal);
auto it = map.find(str);
- return it != map.end() ? it->second : 0;
+ return it != map.end() ? std::make_optional(it->second) : std::nullopt;
}
const char* InputEventLookup::lookupLabelByValue(const std::vector<InputEventLabel>& vec,
@@ -453,8 +453,8 @@
return nullptr;
}
-int32_t InputEventLookup::getKeyCodeByLabel(const char* label) {
- return int32_t(lookupValueByLabel(KEYCODES, label));
+std::optional<int> InputEventLookup::getKeyCodeByLabel(const char* label) {
+ return lookupValueByLabel(KEYCODES, label);
}
const char* InputEventLookup::getLabelByKeyCode(int32_t keyCode) {
@@ -464,20 +464,20 @@
return nullptr;
}
-uint32_t InputEventLookup::getKeyFlagByLabel(const char* label) {
- return uint32_t(lookupValueByLabel(FLAGS, label));
+std::optional<int> InputEventLookup::getKeyFlagByLabel(const char* label) {
+ return lookupValueByLabel(FLAGS, label);
}
-int32_t InputEventLookup::getAxisByLabel(const char* label) {
- return int32_t(lookupValueByLabel(AXES, label));
+std::optional<int> InputEventLookup::getAxisByLabel(const char* label) {
+ return lookupValueByLabel(AXES, label);
}
const char* InputEventLookup::getAxisLabel(int32_t axisId) {
return lookupLabelByValue(AXES_NAMES, axisId);
}
-int32_t InputEventLookup::getLedByLabel(const char* label) {
- return int32_t(lookupValueByLabel(LEDS, label));
+std::optional<int> InputEventLookup::getLedByLabel(const char* label) {
+ return lookupValueByLabel(LEDS, label);
}
} // namespace android
diff --git a/libs/input/KeyCharacterMap.cpp b/libs/input/KeyCharacterMap.cpp
index 6bfac40..737bd15 100644
--- a/libs/input/KeyCharacterMap.cpp
+++ b/libs/input/KeyCharacterMap.cpp
@@ -999,7 +999,7 @@
mTokenizer->skipDelimiters(WHITESPACE);
String8 keyCodeToken = mTokenizer->nextToken(WHITESPACE);
- int32_t keyCode = InputEventLookup::getKeyCodeByLabel(keyCodeToken.string());
+ std::optional<int> keyCode = InputEventLookup::getKeyCodeByLabel(keyCodeToken.string());
if (!keyCode) {
ALOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().string(),
keyCodeToken.string());
@@ -1010,19 +1010,19 @@
ALOGD("Parsed map key %s: code=%d, keyCode=%d.",
mapUsage ? "usage" : "scan code", code, keyCode);
#endif
- map.insert_or_assign(code, keyCode);
+ map.insert_or_assign(code, *keyCode);
return NO_ERROR;
}
status_t KeyCharacterMap::Parser::parseKey() {
String8 keyCodeToken = mTokenizer->nextToken(WHITESPACE);
- int32_t keyCode = InputEventLookup::getKeyCodeByLabel(keyCodeToken.string());
+ std::optional<int> keyCode = InputEventLookup::getKeyCodeByLabel(keyCodeToken.string());
if (!keyCode) {
ALOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().string(),
keyCodeToken.string());
return BAD_VALUE;
}
- if (mMap->mKeys.indexOfKey(keyCode) >= 0) {
+ if (mMap->mKeys.indexOfKey(*keyCode) >= 0) {
ALOGE("%s: Duplicate entry for key code '%s'.", mTokenizer->getLocation().string(),
keyCodeToken.string());
return BAD_VALUE;
@@ -1036,11 +1036,9 @@
return BAD_VALUE;
}
-#if DEBUG_PARSER
- ALOGD("Parsed beginning of key: keyCode=%d.", keyCode);
-#endif
- mKeyCode = keyCode;
- mMap->mKeys.add(keyCode, new Key());
+ ALOGD_IF(DEBUG_PARSER, "Parsed beginning of key: keyCode=%d.", *keyCode);
+ mKeyCode = *keyCode;
+ mMap->mKeys.add(*keyCode, new Key());
mState = STATE_KEY;
return NO_ERROR;
}
@@ -1136,7 +1134,7 @@
} else if (token == "fallback") {
mTokenizer->skipDelimiters(WHITESPACE);
token = mTokenizer->nextToken(WHITESPACE);
- int32_t keyCode = InputEventLookup::getKeyCodeByLabel(token.string());
+ std::optional<int> keyCode = InputEventLookup::getKeyCodeByLabel(token.string());
if (!keyCode) {
ALOGE("%s: Invalid key code label for fallback behavior, got '%s'.",
mTokenizer->getLocation().string(),
@@ -1148,12 +1146,12 @@
mTokenizer->getLocation().string());
return BAD_VALUE;
}
- behavior.fallbackKeyCode = keyCode;
+ behavior.fallbackKeyCode = *keyCode;
haveFallback = true;
} else if (token == "replace") {
mTokenizer->skipDelimiters(WHITESPACE);
token = mTokenizer->nextToken(WHITESPACE);
- int32_t keyCode = InputEventLookup::getKeyCodeByLabel(token.string());
+ std::optional<int> keyCode = InputEventLookup::getKeyCodeByLabel(token.string());
if (!keyCode) {
ALOGE("%s: Invalid key code label for replace, got '%s'.",
mTokenizer->getLocation().string(),
@@ -1170,7 +1168,7 @@
mTokenizer->getLocation().string());
return BAD_VALUE;
}
- behavior.replacementKeyCode = keyCode;
+ behavior.replacementKeyCode = *keyCode;
haveReplacement = true;
} else {
diff --git a/libs/input/KeyLayoutMap.cpp b/libs/input/KeyLayoutMap.cpp
index 7371033..a2649f6 100644
--- a/libs/input/KeyLayoutMap.cpp
+++ b/libs/input/KeyLayoutMap.cpp
@@ -16,6 +16,7 @@
#define LOG_TAG "KeyLayoutMap"
+#include <android-base/logging.h>
#include <android/keycodes.h>
#include <ftl/enum.h>
#include <input/InputEventLabels.h>
@@ -54,6 +55,21 @@
namespace android {
namespace {
+std::optional<int> parseInt(const char* str) {
+ char* end;
+ errno = 0;
+ const int value = strtol(str, &end, 0);
+ if (end == str) {
+ LOG(ERROR) << "Could not parse " << str;
+ return {};
+ }
+ if (errno == ERANGE) {
+ LOG(ERROR) << "Out of bounds: " << str;
+ return {};
+ }
+ return value;
+}
+
constexpr const char* WHITESPACE = " \t\r";
template <InputDeviceSensorType S>
@@ -336,16 +352,15 @@
codeToken = mTokenizer->nextToken(WHITESPACE);
}
- char* end;
- int32_t code = int32_t(strtol(codeToken.string(), &end, 0));
- if (*end) {
+ std::optional<int> code = parseInt(codeToken.string());
+ if (!code) {
ALOGE("%s: Expected key %s number, got '%s'.", mTokenizer->getLocation().string(),
mapUsage ? "usage" : "scan code", codeToken.string());
return BAD_VALUE;
}
std::unordered_map<int32_t, Key>& map =
mapUsage ? mMap->mKeysByUsageCode : mMap->mKeysByScanCode;
- if (map.find(code) != map.end()) {
+ if (map.find(*code) != map.end()) {
ALOGE("%s: Duplicate entry for key %s '%s'.", mTokenizer->getLocation().string(),
mapUsage ? "usage" : "scan code", codeToken.string());
return BAD_VALUE;
@@ -353,7 +368,7 @@
mTokenizer->skipDelimiters(WHITESPACE);
String8 keyCodeToken = mTokenizer->nextToken(WHITESPACE);
- int32_t keyCode = InputEventLookup::getKeyCodeByLabel(keyCodeToken.string());
+ std::optional<int> keyCode = InputEventLookup::getKeyCodeByLabel(keyCodeToken.string());
if (!keyCode) {
ALOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().string(),
keyCodeToken.string());
@@ -366,40 +381,39 @@
if (mTokenizer->isEol() || mTokenizer->peekChar() == '#') break;
String8 flagToken = mTokenizer->nextToken(WHITESPACE);
- uint32_t flag = InputEventLookup::getKeyFlagByLabel(flagToken.string());
+ std::optional<int> flag = InputEventLookup::getKeyFlagByLabel(flagToken.string());
if (!flag) {
ALOGE("%s: Expected key flag label, got '%s'.", mTokenizer->getLocation().string(),
flagToken.string());
return BAD_VALUE;
}
- if (flags & flag) {
+ if (flags & *flag) {
ALOGE("%s: Duplicate key flag '%s'.", mTokenizer->getLocation().string(),
flagToken.string());
return BAD_VALUE;
}
- flags |= flag;
+ flags |= *flag;
}
ALOGD_IF(DEBUG_PARSER, "Parsed key %s: code=%d, keyCode=%d, flags=0x%08x.",
- mapUsage ? "usage" : "scan code", code, keyCode, flags);
+ mapUsage ? "usage" : "scan code", *code, *keyCode, flags);
Key key;
- key.keyCode = keyCode;
+ key.keyCode = *keyCode;
key.flags = flags;
- map.insert({code, key});
+ map.insert({*code, key});
return NO_ERROR;
}
status_t KeyLayoutMap::Parser::parseAxis() {
String8 scanCodeToken = mTokenizer->nextToken(WHITESPACE);
- char* end;
- int32_t scanCode = int32_t(strtol(scanCodeToken.string(), &end, 0));
- if (*end) {
+ std::optional<int> scanCode = parseInt(scanCodeToken.string());
+ if (!scanCode) {
ALOGE("%s: Expected axis scan code number, got '%s'.", mTokenizer->getLocation().string(),
scanCodeToken.string());
return BAD_VALUE;
}
- if (mMap->mAxes.find(scanCode) != mMap->mAxes.end()) {
+ if (mMap->mAxes.find(*scanCode) != mMap->mAxes.end()) {
ALOGE("%s: Duplicate entry for axis scan code '%s'.", mTokenizer->getLocation().string(),
scanCodeToken.string());
return BAD_VALUE;
@@ -414,48 +428,53 @@
mTokenizer->skipDelimiters(WHITESPACE);
String8 axisToken = mTokenizer->nextToken(WHITESPACE);
- axisInfo.axis = InputEventLookup::getAxisByLabel(axisToken.string());
- if (axisInfo.axis < 0) {
+ std::optional<int> axis = InputEventLookup::getAxisByLabel(axisToken.string());
+ if (!axis) {
ALOGE("%s: Expected inverted axis label, got '%s'.",
mTokenizer->getLocation().string(), axisToken.string());
return BAD_VALUE;
}
+ axisInfo.axis = *axis;
} else if (token == "split") {
axisInfo.mode = AxisInfo::MODE_SPLIT;
mTokenizer->skipDelimiters(WHITESPACE);
String8 splitToken = mTokenizer->nextToken(WHITESPACE);
- axisInfo.splitValue = int32_t(strtol(splitToken.string(), &end, 0));
- if (*end) {
+ std::optional<int> splitValue = parseInt(splitToken.string());
+ if (!splitValue) {
ALOGE("%s: Expected split value, got '%s'.",
mTokenizer->getLocation().string(), splitToken.string());
return BAD_VALUE;
}
+ axisInfo.splitValue = *splitValue;
mTokenizer->skipDelimiters(WHITESPACE);
String8 lowAxisToken = mTokenizer->nextToken(WHITESPACE);
- axisInfo.axis = InputEventLookup::getAxisByLabel(lowAxisToken.string());
- if (axisInfo.axis < 0) {
+ std::optional<int> axis = InputEventLookup::getAxisByLabel(lowAxisToken.string());
+ if (!axis) {
ALOGE("%s: Expected low axis label, got '%s'.",
mTokenizer->getLocation().string(), lowAxisToken.string());
return BAD_VALUE;
}
+ axisInfo.axis = *axis;
mTokenizer->skipDelimiters(WHITESPACE);
String8 highAxisToken = mTokenizer->nextToken(WHITESPACE);
- axisInfo.highAxis = InputEventLookup::getAxisByLabel(highAxisToken.string());
- if (axisInfo.highAxis < 0) {
+ std::optional<int> highAxis = InputEventLookup::getAxisByLabel(highAxisToken.string());
+ if (!highAxis) {
ALOGE("%s: Expected high axis label, got '%s'.",
mTokenizer->getLocation().string(), highAxisToken.string());
return BAD_VALUE;
}
+ axisInfo.highAxis = *highAxis;
} else {
- axisInfo.axis = InputEventLookup::getAxisByLabel(token.string());
- if (axisInfo.axis < 0) {
+ std::optional<int> axis = InputEventLookup::getAxisByLabel(token.string());
+ if (!axis) {
ALOGE("%s: Expected axis label, 'split' or 'invert', got '%s'.",
mTokenizer->getLocation().string(), token.string());
return BAD_VALUE;
}
+ axisInfo.axis = *axis;
}
for (;;) {
@@ -467,12 +486,13 @@
if (keywordToken == "flat") {
mTokenizer->skipDelimiters(WHITESPACE);
String8 flatToken = mTokenizer->nextToken(WHITESPACE);
- axisInfo.flatOverride = int32_t(strtol(flatToken.string(), &end, 0));
- if (*end) {
+ std::optional<int> flatOverride = parseInt(flatToken.string());
+ if (!flatOverride) {
ALOGE("%s: Expected flat value, got '%s'.",
mTokenizer->getLocation().string(), flatToken.string());
return BAD_VALUE;
}
+ axisInfo.flatOverride = *flatOverride;
} else {
ALOGE("%s: Expected keyword 'flat', got '%s'.",
mTokenizer->getLocation().string(), keywordToken.string());
@@ -483,9 +503,9 @@
ALOGD_IF(DEBUG_PARSER,
"Parsed axis: scanCode=%d, mode=%d, axis=%d, highAxis=%d, "
"splitValue=%d, flatOverride=%d.",
- scanCode, axisInfo.mode, axisInfo.axis, axisInfo.highAxis, axisInfo.splitValue,
+ *scanCode, axisInfo.mode, axisInfo.axis, axisInfo.highAxis, axisInfo.splitValue,
axisInfo.flatOverride);
- mMap->mAxes.insert({scanCode, axisInfo});
+ mMap->mAxes.insert({*scanCode, axisInfo});
return NO_ERROR;
}
@@ -497,9 +517,8 @@
mTokenizer->skipDelimiters(WHITESPACE);
codeToken = mTokenizer->nextToken(WHITESPACE);
}
- char* end;
- int32_t code = int32_t(strtol(codeToken.string(), &end, 0));
- if (*end) {
+ std::optional<int> code = parseInt(codeToken.string());
+ if (!code) {
ALOGE("%s: Expected led %s number, got '%s'.", mTokenizer->getLocation().string(),
mapUsage ? "usage" : "scan code", codeToken.string());
return BAD_VALUE;
@@ -507,7 +526,7 @@
std::unordered_map<int32_t, Led>& map =
mapUsage ? mMap->mLedsByUsageCode : mMap->mLedsByScanCode;
- if (map.find(code) != map.end()) {
+ if (map.find(*code) != map.end()) {
ALOGE("%s: Duplicate entry for led %s '%s'.", mTokenizer->getLocation().string(),
mapUsage ? "usage" : "scan code", codeToken.string());
return BAD_VALUE;
@@ -515,19 +534,19 @@
mTokenizer->skipDelimiters(WHITESPACE);
String8 ledCodeToken = mTokenizer->nextToken(WHITESPACE);
- int32_t ledCode = InputEventLookup::getLedByLabel(ledCodeToken.string());
- if (ledCode < 0) {
+ std::optional<int> ledCode = InputEventLookup::getLedByLabel(ledCodeToken.string());
+ if (!ledCode) {
ALOGE("%s: Expected LED code label, got '%s'.", mTokenizer->getLocation().string(),
ledCodeToken.string());
return BAD_VALUE;
}
ALOGD_IF(DEBUG_PARSER, "Parsed led %s: code=%d, ledCode=%d.", mapUsage ? "usage" : "scan code",
- code, ledCode);
+ *code, *ledCode);
Led led;
- led.ledCode = ledCode;
- map.insert({code, led});
+ led.ledCode = *ledCode;
+ map.insert({*code, led});
return NO_ERROR;
}
@@ -565,16 +584,15 @@
// sensor 0x05 GYROSCOPE Z
status_t KeyLayoutMap::Parser::parseSensor() {
String8 codeToken = mTokenizer->nextToken(WHITESPACE);
- char* end;
- int32_t code = int32_t(strtol(codeToken.string(), &end, 0));
- if (*end) {
+ std::optional<int> code = parseInt(codeToken.string());
+ if (!code) {
ALOGE("%s: Expected sensor %s number, got '%s'.", mTokenizer->getLocation().string(),
"abs code", codeToken.string());
return BAD_VALUE;
}
std::unordered_map<int32_t, Sensor>& map = mMap->mSensorsByAbsCode;
- if (map.find(code) != map.end()) {
+ if (map.find(*code) != map.end()) {
ALOGE("%s: Duplicate entry for sensor %s '%s'.", mTokenizer->getLocation().string(),
"abs code", codeToken.string());
return BAD_VALUE;
@@ -599,13 +617,13 @@
}
int32_t sensorDataIndex = indexOpt.value();
- ALOGD_IF(DEBUG_PARSER, "Parsed sensor: abs code=%d, sensorType=%s, sensorDataIndex=%d.", code,
+ ALOGD_IF(DEBUG_PARSER, "Parsed sensor: abs code=%d, sensorType=%s, sensorDataIndex=%d.", *code,
ftl::enum_string(sensorType).c_str(), sensorDataIndex);
Sensor sensor;
sensor.sensorType = sensorType;
sensor.sensorDataIndex = sensorDataIndex;
- map.emplace(code, sensor);
+ map.emplace(*code, sensor);
return NO_ERROR;
}
diff --git a/libs/input/android/os/InputEventInjectionResult.aidl b/libs/input/android/os/InputEventInjectionResult.aidl
index 3bc7068..e80c2a5 100644
--- a/libs/input/android/os/InputEventInjectionResult.aidl
+++ b/libs/input/android/os/InputEventInjectionResult.aidl
@@ -37,4 +37,7 @@
/* Injection failed due to a timeout. */
TIMED_OUT = 3,
+
+ ftl_first=PENDING,
+ ftl_last=TIMED_OUT,
}
diff --git a/libs/input/tests/InputDevice_test.cpp b/libs/input/tests/InputDevice_test.cpp
index 2344463..ee961f0 100644
--- a/libs/input/tests/InputDevice_test.cpp
+++ b/libs/input/tests/InputDevice_test.cpp
@@ -133,6 +133,20 @@
ASSERT_EQ(*mKeyMap.keyCharacterMap, *frenchOverlaidKeyCharacterMap);
}
+TEST_F(InputDeviceKeyMapTest, keyCharacteMapBadAxisLabel) {
+ std::string klPath = base::GetExecutableDirectory() + "/data/bad_axis_label.kl";
+
+ base::Result<std::shared_ptr<KeyLayoutMap>> ret = KeyLayoutMap::load(klPath);
+ ASSERT_FALSE(ret.ok()) << "Should not be able to load KeyLayout at " << klPath;
+}
+
+TEST_F(InputDeviceKeyMapTest, keyCharacteMapBadLedLabel) {
+ std::string klPath = base::GetExecutableDirectory() + "/data/bad_led_label.kl";
+
+ base::Result<std::shared_ptr<KeyLayoutMap>> ret = KeyLayoutMap::load(klPath);
+ ASSERT_FALSE(ret.ok()) << "Should not be able to load KeyLayout at " << klPath;
+}
+
TEST(InputDeviceKeyLayoutTest, DoesNotLoadWhenRequiredKernelConfigIsMissing) {
#if !defined(__ANDROID__)
GTEST_SKIP() << "Can't check kernel configs on host";
diff --git a/libs/input/tests/data/bad_axis_label.kl b/libs/input/tests/data/bad_axis_label.kl
new file mode 100644
index 0000000..6897380
--- /dev/null
+++ b/libs/input/tests/data/bad_axis_label.kl
@@ -0,0 +1,17 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This KL should not be loaded because the axis label is not valid
+
+axis 0 DEFINITELY_NOT_AXIS_LABEL
diff --git a/libs/input/tests/data/bad_led_label.kl b/libs/input/tests/data/bad_led_label.kl
new file mode 100644
index 0000000..293c0d2
--- /dev/null
+++ b/libs/input/tests/data/bad_led_label.kl
@@ -0,0 +1,17 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This KL should not be loaded because the led label is invalid
+
+led 0 ABSOLUTELY_NOT_LED_LABEL
diff --git a/libs/jpegrecoverymap/Android.bp b/libs/jpegrecoverymap/Android.bp
index c9bed70..01318dc 100644
--- a/libs/jpegrecoverymap/Android.bp
+++ b/libs/jpegrecoverymap/Android.bp
@@ -39,6 +39,7 @@
"libjpeg",
"libjpegencoder",
"libjpegdecoder",
+ "liblog",
],
}
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
index 0fb64d3..0695bb7 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
@@ -17,12 +17,15 @@
#ifndef ANDROID_JPEGRECOVERYMAP_RECOVERYMAPMATH_H
#define ANDROID_JPEGRECOVERYMAP_RECOVERYMAPMATH_H
+#include <cmath>
#include <stdint.h>
#include <jpegrecoverymap/recoverymap.h>
namespace android::recoverymap {
+#define CLIP3(x, min, max) ((x) < (min)) ? (min) : ((x) > (max)) ? (max) : (x)
+
////////////////////////////////////////////////////////////////////////////////
// Framework
@@ -112,6 +115,76 @@
return temp /= rhs;
}
+constexpr size_t kRecoveryFactorPrecision = 10;
+constexpr size_t kRecoveryFactorNumEntries = 1 << kRecoveryFactorPrecision;
+struct RecoveryLUT {
+ RecoveryLUT(float hdrRatio) {
+ float increment = 2.0 / kRecoveryFactorNumEntries;
+ float value = -1.0f;
+ for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++, value += increment) {
+ mRecoveryTable[idx] = pow(hdrRatio, value);
+ }
+ }
+
+ ~RecoveryLUT() {
+ }
+
+ float getRecoveryFactor(float recovery) {
+ uint32_t value = static_cast<uint32_t>(((recovery + 1.0f) / 2.0f) * kRecoveryFactorNumEntries);
+ //TODO() : Remove once conversion modules have appropriate clamping in place
+ value = CLIP3(value, 0, kRecoveryFactorNumEntries - 1);
+ return mRecoveryTable[value];
+ }
+
+private:
+ float mRecoveryTable[kRecoveryFactorNumEntries];
+};
+
+struct ShepardsIDW {
+ ShepardsIDW(int mapScaleFactor) : mMapScaleFactor{mapScaleFactor} {
+ const int size = mMapScaleFactor * mMapScaleFactor * 4;
+ mWeights = new float[size];
+ mWeightsNR = new float[size];
+ mWeightsNB = new float[size];
+ mWeightsC = new float[size];
+ fillShepardsIDW(mWeights, 1, 1);
+ fillShepardsIDW(mWeightsNR, 0, 1);
+ fillShepardsIDW(mWeightsNB, 1, 0);
+ fillShepardsIDW(mWeightsC, 0, 0);
+ }
+ ~ShepardsIDW() {
+ delete[] mWeights;
+ delete[] mWeightsNR;
+ delete[] mWeightsNB;
+ delete[] mWeightsC;
+ }
+
+ int mMapScaleFactor;
+ // Image :-
+ // p00 p01 p02 p03 p04 p05 p06 p07
+ // p10 p11 p12 p13 p14 p15 p16 p17
+ // p20 p21 p22 p23 p24 p25 p26 p27
+ // p30 p31 p32 p33 p34 p35 p36 p37
+ // p40 p41 p42 p43 p44 p45 p46 p47
+ // p50 p51 p52 p53 p54 p55 p56 p57
+ // p60 p61 p62 p63 p64 p65 p66 p67
+ // p70 p71 p72 p73 p74 p75 p76 p77
+
+ // Recovery Map (for 4 scale factor) :-
+ // m00 p01
+ // m10 m11
+
+ // Recovery sample of curr 4x4, right 4x4, bottom 4x4, bottom right 4x4 are used during
+ // reconstruction. hence table weight size is 4.
+ float* mWeights;
+ // TODO: check if its ok to mWeights at places
+ float* mWeightsNR; // no right
+ float* mWeightsNB; // no bottom
+ float* mWeightsC; // no right & bottom
+
+ float euclideanDistance(float x1, float x2, float y1, float y2);
+ void fillShepardsIDW(float *weights, int incR, int incB);
+};
////////////////////////////////////////////////////////////////////////////////
// sRGB transformations
@@ -143,7 +216,8 @@
*/
float srgbInvOetf(float e_gamma);
Color srgbInvOetf(Color e_gamma);
-
+float srgbInvOetfLUT(float e_gamma);
+Color srgbInvOetfLUT(Color e_gamma);
////////////////////////////////////////////////////////////////////////////////
// Display-P3 transformations
@@ -183,6 +257,8 @@
*/
float hlgOetf(float e);
Color hlgOetf(Color e);
+float hlgOetfLUT(float e);
+Color hlgOetfLUT(Color e);
/*
* Convert from HLG to scene luminance.
@@ -191,6 +267,8 @@
*/
float hlgInvOetf(float e_gamma);
Color hlgInvOetf(Color e_gamma);
+float hlgInvOetfLUT(float e_gamma);
+Color hlgInvOetfLUT(Color e_gamma);
/*
* Convert from scene luminance to PQ.
@@ -199,6 +277,8 @@
*/
float pqOetf(float e);
Color pqOetf(Color e);
+float pqOetfLUT(float e);
+Color pqOetfLUT(Color e);
/*
* Convert from PQ to scene luminance in nits.
@@ -207,6 +287,8 @@
*/
float pqInvOetf(float e_gamma);
Color pqInvOetf(Color e_gamma);
+float pqInvOetfLUT(float e_gamma);
+Color pqInvOetfLUT(Color e_gamma);
////////////////////////////////////////////////////////////////////////////////
@@ -251,6 +333,7 @@
* value, with the given hdr ratio, to the given sdr input in the range [0, 1].
*/
Color applyRecovery(Color e, float recovery, float hdr_ratio);
+Color applyRecoveryLUT(Color e, float recovery, RecoveryLUT& recoveryLUT);
/*
* Helper for sampling from YUV 420 images.
@@ -282,7 +365,9 @@
* Sample the recovery value for the map from a given x,y coordinate on a scale
* that is map scale factor larger than the map size.
*/
-float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y);
+float sampleMap(jr_uncompressed_ptr map, float map_scale_factor, size_t x, size_t y);
+float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y,
+ ShepardsIDW& weightTables);
/*
* Convert from Color to RGBA1010102.
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h
index e61d0c4..8b2672f 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h
@@ -17,6 +17,8 @@
#ifndef ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H
#define ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H
+#include <jpegrecoverymap/recoverymap.h>
+
#include <sstream>
#include <stdint.h>
#include <string>
@@ -26,6 +28,26 @@
struct jpegr_metadata;
+// 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 used for writing data to destination.
+ *
+ * @param destination destination of the data to be written.
+ * @param source source of data being written.
+ * @param length length of the data to be written.
+ * @param position cursor in desitination where the data is to be written.
+ * @return status of succeed or error code.
+ */
+status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position);
+status_t Write(jr_exif_ptr destination, const void* source, size_t length, int &position);
+
+
/*
* Parses XMP packet and fills metadata with data from XMP
*
@@ -56,8 +78,8 @@
* <rdf:Seq>
* <rdf:li>
* <GContainer:Item
- * Item:Semantic="Primary"
- * Item:Mime="image/jpeg"
+ * GContainer:ItemSemantic="Primary"
+ * GContainer:ItemMime="image/jpeg"
* RecoveryMap:Version=”1”
* RecoveryMap:RangeScalingFactor=”1.25”
* RecoveryMap:TransferFunction=”2”/>
@@ -68,9 +90,9 @@
* </rdf:li>
* <rdf:li>
* <GContainer:Item
- * Item:Semantic="RecoveryMap"
- * Item:Mime="image/jpeg"
- * Item:Length="1000"/>
+ * GContainer:ItemSemantic="RecoveryMap"
+ * GContainer:ItemMime="image/jpeg"
+ * GContainer:ItemLength="1000"/>
* </rdf:li>
* </rdf:Seq>
* </GContainer:Directory>
@@ -83,6 +105,83 @@
* @return XMP metadata in type of string
*/
std::string generateXmp(int secondary_image_length, jpegr_metadata& metadata);
+
+/*
+ * 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 - 1 component
+ * 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 - 1 component
+ * 00 00 00 00 - empty data
+ * 00 01 - Width Tag
+ * 03 00 - 'Short' type
+ * 01 00 00 00 - 1 component
+ * 00 05 00 00 - image with 0x500
+ */
+status_t updateExif(jr_exif_ptr exif, jr_exif_ptr dest);
+
+/*
+ * Modify offsets in EXIF in place.
+ *
+ * Each tag has the following structure:
+ *
+ * 00 01 - Tag
+ * 03 00 - data format
+ * 01 00 00 00 - number of components
+ * 00 05 00 00 - value
+ *
+ * The value means offset if
+ * (1) num_of_components * bytes_per_component > 4 bytes, or
+ * (2) tag == 0x8769 (ExifOffset).
+ * In both cases, the method will add EXIF_J_R_ENTRY_LENGTH (12) to the offsets.
+ */
+void updateExifOffsets(jr_exif_ptr exif, int pos, bool use_big_endian);
+void updateExifOffsets(jr_exif_ptr exif, int pos, int num_entry, bool use_big_endian);
+
+/*
+ * Read data from the target position and target length in bytes;
+ */
+int readValue(uint8_t* data, int pos, int length, bool use_big_endian);
+
+/*
+ * Returns the length of data format in bytes
+ *
+ * ----------------------------------------------------------------------------------------------
+ * | value | 1 | 2 | 3 | 4 |
+ * | format | unsigned byte | ascii strings | unsigned short | unsigned long |
+ * | bytes/component | 1 | 1 | 2 | 4 |
+ * ----------------------------------------------------------------------------------------------
+ * | value | 5 | 6 | 7 | 8 |
+ * | format |unsigned rational| signed byte | undefined | signed short |
+ * | bytes/component | 8 | 1 | 1 | 2 |
+ * ----------------------------------------------------------------------------------------------
+ * | value | 9 | 10 | 11 | 12 |
+ * | format | signed long | signed rational | single float | double float |
+ * | bytes/component | 4 | 8 | 4 | 8 |
+ * ----------------------------------------------------------------------------------------------
+ */
+int findFormatLengthInBytes(int data_format);
}
-#endif //ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H
\ No newline at end of file
+#endif //ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H
diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp
index 6b46d40..22289de 100644
--- a/libs/jpegrecoverymap/recoverymap.cpp
+++ b/libs/jpegrecoverymap/recoverymap.cpp
@@ -25,17 +25,30 @@
#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>
#include <string>
#include <cmath>
+#include <condition_variable>
+#include <deque>
+#include <mutex>
+#include <thread>
+#include <unistd.h>
using namespace std;
using namespace photos_editing_formats::image_io;
namespace android::recoverymap {
+#define USE_SRGB_INVOETF_LUT 1
+#define USE_HLG_OETF_LUT 1
+#define USE_PQ_OETF_LUT 1
+#define USE_HLG_INVOETF_LUT 1
+#define USE_PQ_INVOETF_LUT 1
+#define USE_APPLY_RECOVERY_LUT 1
+
#define JPEGR_CHECK(x) \
{ \
status_t status = (x); \
@@ -49,6 +62,10 @@
// Map is quarter res / sixteenth size
static const size_t kMapDimensionScaleFactor = 4;
+// JPEG block size.
+// JPEG encoding / decoding will require 8 x 8 DCT transform.
+// Width must be 8 dividable, and height must be 2 dividable.
+static const size_t kJpegBlock = 8;
// JPEG compress quality (0 ~ 100) for recovery map
static const int kMapCompressQuality = 85;
@@ -62,133 +79,18 @@
1.0f,
};
-/*
- * Helper function used for writing data to destination.
- *
- * @param destination destination of the data to be written.
- * @param source source of data being written.
- * @param length length of the data to be written.
- * @param position cursor in desitination where the data is to be written.
- * @return status of succeed or error code.
- */
-status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position) {
- if (position + length > destination->maxLength) {
- return ERROR_JPEGR_BUFFER_TOO_SMALL;
- }
-
- memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length);
- position += length;
- 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;
+#define CONFIG_MULTITHREAD 1
+int GetCPUCoreCount() {
+ int cpuCoreCount = 1;
+#if CONFIG_MULTITHREAD
+#if defined(_SC_NPROCESSORS_ONLN)
+ cpuCoreCount = sysconf(_SC_NPROCESSORS_ONLN);
+#else
+ // _SC_NPROC_ONLN must be defined...
+ cpuCoreCount = sysconf(_SC_NPROC_ONLN);
+#endif
+#endif
+ return cpuCoreCount;
}
/*
@@ -230,6 +132,13 @@
return ERROR_JPEGR_INVALID_INPUT_TYPE;
}
+ if (uncompressed_p010_image->width % kJpegBlock != 0
+ || uncompressed_p010_image->height % 2 != 0) {
+ ALOGE("Image size can not be handled: %dx%d",
+ uncompressed_p010_image->width, uncompressed_p010_image->height);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
jpegr_metadata metadata;
metadata.version = kJpegrVersion;
metadata.transferFunction = hdr_tf;
@@ -304,6 +213,13 @@
return ERROR_JPEGR_RESOLUTION_MISMATCH;
}
+ if (uncompressed_p010_image->width % kJpegBlock != 0
+ || uncompressed_p010_image->height % 2 != 0) {
+ ALOGE("Image size can not be handled: %dx%d",
+ uncompressed_p010_image->width, uncompressed_p010_image->height);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
jpegr_metadata metadata;
metadata.version = kJpegrVersion;
metadata.transferFunction = hdr_tf;
@@ -369,6 +285,13 @@
return ERROR_JPEGR_RESOLUTION_MISMATCH;
}
+ if (uncompressed_p010_image->width % kJpegBlock != 0
+ || uncompressed_p010_image->height % 2 != 0) {
+ ALOGE("Image size can not be handled: %dx%d",
+ uncompressed_p010_image->width, uncompressed_p010_image->height);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
jpegr_metadata metadata;
metadata.version = kJpegrVersion;
metadata.transferFunction = hdr_tf;
@@ -444,6 +367,13 @@
return ERROR_JPEGR_INVALID_NULL_PTR;
}
+ if (uncompressed_p010_image->width % kJpegBlock != 0
+ || uncompressed_p010_image->height % 2 != 0) {
+ ALOGE("Image size can not be handled: %dx%d",
+ uncompressed_p010_image->width, uncompressed_p010_image->height);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
JpegDecoder jpeg_decoder;
if (!jpeg_decoder.decompressImage(compressed_jpeg_image->data, compressed_jpeg_image->length)) {
return ERROR_JPEGR_DECODE_ERROR;
@@ -626,6 +556,62 @@
return NO_ERROR;
}
+const int kJobSzInRows = 16;
+static_assert(kJobSzInRows > 0 && kJobSzInRows % kMapDimensionScaleFactor == 0,
+ "align job size to kMapDimensionScaleFactor");
+
+class JobQueue {
+ public:
+ bool dequeueJob(size_t& rowStart, size_t& rowEnd);
+ void enqueueJob(size_t rowStart, size_t rowEnd);
+ void markQueueForEnd();
+ void reset();
+
+ private:
+ bool mQueuedAllJobs = false;
+ std::deque<std::tuple<size_t, size_t>> mJobs;
+ std::mutex mMutex;
+ std::condition_variable mCv;
+};
+
+bool JobQueue::dequeueJob(size_t& rowStart, size_t& rowEnd) {
+ std::unique_lock<std::mutex> lock{mMutex};
+ while (true) {
+ if (mJobs.empty()) {
+ if (mQueuedAllJobs) {
+ return false;
+ } else {
+ mCv.wait(lock);
+ }
+ } else {
+ auto it = mJobs.begin();
+ rowStart = std::get<0>(*it);
+ rowEnd = std::get<1>(*it);
+ mJobs.erase(it);
+ return true;
+ }
+ }
+ return false;
+}
+
+void JobQueue::enqueueJob(size_t rowStart, size_t rowEnd) {
+ std::unique_lock<std::mutex> lock{mMutex};
+ mJobs.push_back(std::make_tuple(rowStart, rowEnd));
+ lock.unlock();
+ mCv.notify_one();
+}
+
+void JobQueue::markQueueForEnd() {
+ std::unique_lock<std::mutex> lock{mMutex};
+ mQueuedAllJobs = true;
+}
+
+void JobQueue::reset() {
+ std::unique_lock<std::mutex> lock{mMutex};
+ mJobs.clear();
+ mQueuedAllJobs = false;
+}
+
status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
jr_uncompressed_ptr uncompressed_p010_image,
jr_metadata_ptr metadata,
@@ -651,11 +637,14 @@
size_t image_height = uncompressed_yuv_420_image->height;
size_t map_width = image_width / kMapDimensionScaleFactor;
size_t map_height = image_height / kMapDimensionScaleFactor;
+ size_t map_stride = static_cast<size_t>(
+ floor((map_width + kJpegBlock - 1) / kJpegBlock)) * kJpegBlock;
+ size_t map_height_aligned = ((map_height + 1) >> 1) << 1;
- dest->width = map_width;
- dest->height = map_height;
+ dest->width = map_stride;
+ dest->height = map_height_aligned;
dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED;
- dest->data = new uint8_t[map_width * map_height];
+ dest->data = new uint8_t[map_stride * map_height_aligned];
std::unique_ptr<uint8_t[]> map_data;
map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
@@ -666,11 +655,19 @@
hdrInvOetf = identityConversion;
break;
case JPEGR_TF_HLG:
+#if USE_HLG_INVOETF_LUT
+ hdrInvOetf = hlgInvOetfLUT;
+#else
hdrInvOetf = hlgInvOetf;
+#endif
hdr_white_nits = kHlgMaxNits;
break;
case JPEGR_TF_PQ:
+#if USE_PQ_INVOETF_LUT
+ hdrInvOetf = pqInvOetfLUT;
+#else
hdrInvOetf = pqInvOetf;
+#endif
hdr_white_nits = kPqMaxNits;
break;
case JPEGR_TF_UNSPECIFIED:
@@ -697,22 +694,87 @@
return ERROR_JPEGR_INVALID_COLORGAMUT;
}
+ std::mutex mutex;
float hdr_y_nits_max = 0.0f;
double hdr_y_nits_avg = 0.0f;
- for (size_t y = 0; y < image_height; ++y) {
- for (size_t x = 0; x < image_width; ++x) {
- Color hdr_yuv_gamma = getP010Pixel(uncompressed_p010_image, x, y);
- Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
- Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
- hdr_rgb = hdrGamutConversionFn(hdr_rgb);
- float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
+ const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
+ size_t rowStep = threads == 1 ? image_height : kJobSzInRows;
+ JobQueue jobQueue;
- hdr_y_nits_avg += hdr_y_nits;
- if (hdr_y_nits > hdr_y_nits_max) {
- hdr_y_nits_max = hdr_y_nits;
+ std::function<void()> computeMetadata = [uncompressed_p010_image, hdrInvOetf,
+ hdrGamutConversionFn, luminanceFn, hdr_white_nits,
+ threads, &mutex, &hdr_y_nits_avg,
+ &hdr_y_nits_max, &jobQueue]() -> void {
+ size_t rowStart, rowEnd;
+ float hdr_y_nits_max_th = 0.0f;
+ double hdr_y_nits_avg_th = 0.0f;
+ while (jobQueue.dequeueJob(rowStart, rowEnd)) {
+ for (size_t y = rowStart; y < rowEnd; ++y) {
+ for (size_t x = 0; x < uncompressed_p010_image->width; ++x) {
+ Color hdr_yuv_gamma = getP010Pixel(uncompressed_p010_image, x, y);
+ Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
+ Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
+ hdr_rgb = hdrGamutConversionFn(hdr_rgb);
+ float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
+
+ hdr_y_nits_avg_th += hdr_y_nits;
+ if (hdr_y_nits > hdr_y_nits_max_th) {
+ hdr_y_nits_max_th = hdr_y_nits;
+ }
+ }
}
}
+ std::unique_lock<std::mutex> lock{mutex};
+ hdr_y_nits_avg += hdr_y_nits_avg_th;
+ hdr_y_nits_max = std::max(hdr_y_nits_max, hdr_y_nits_max_th);
+ };
+
+ std::function<void()> generateMap = [uncompressed_yuv_420_image, uncompressed_p010_image,
+ metadata, dest, hdrInvOetf, hdrGamutConversionFn,
+ luminanceFn, hdr_white_nits, &jobQueue]() -> void {
+ size_t rowStart, rowEnd;
+ while (jobQueue.dequeueJob(rowStart, rowEnd)) {
+ for (size_t y = rowStart; y < rowEnd; ++y) {
+ for (size_t x = 0; x < dest->width; ++x) {
+ Color sdr_yuv_gamma =
+ sampleYuv420(uncompressed_yuv_420_image, kMapDimensionScaleFactor, x, y);
+ Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma);
+#if USE_SRGB_INVOETF_LUT
+ Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma);
+#else
+ Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
+#endif
+ float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
+
+ Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
+ Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
+ Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
+ hdr_rgb = hdrGamutConversionFn(hdr_rgb);
+ float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
+
+ size_t pixel_idx = x + y * dest->width;
+ reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
+ encodeRecovery(sdr_y_nits, hdr_y_nits, metadata->rangeScalingFactor);
+ }
+ }
+ }
+ };
+
+ std::vector<std::thread> workers;
+ for (int th = 0; th < threads - 1; th++) {
+ workers.push_back(std::thread(computeMetadata));
}
+
+ // compute metadata
+ for (size_t rowStart = 0; rowStart < image_height;) {
+ size_t rowEnd = std::min(rowStart + rowStep, image_height);
+ jobQueue.enqueueJob(rowStart, rowEnd);
+ rowStart = rowEnd;
+ }
+ jobQueue.markQueueForEnd();
+ computeMetadata();
+ std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
+ workers.clear();
hdr_y_nits_avg /= image_width * image_height;
metadata->rangeScalingFactor = hdr_y_nits_max / kSdrWhiteNits;
@@ -721,26 +783,22 @@
metadata->hdr10Metadata.maxCLL = hdr_y_nits_max;
}
- for (size_t y = 0; y < map_height; ++y) {
- for (size_t x = 0; x < map_width; ++x) {
- Color sdr_yuv_gamma = sampleYuv420(uncompressed_yuv_420_image,
- kMapDimensionScaleFactor, x, y);
- Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma);
- Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
- float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
-
- Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
- Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
- Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
- hdr_rgb = hdrGamutConversionFn(hdr_rgb);
- float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
-
- size_t pixel_idx = x + y * map_width;
- reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
- encodeRecovery(sdr_y_nits, hdr_y_nits, metadata->rangeScalingFactor);
- }
+ // generate map
+ jobQueue.reset();
+ for (int th = 0; th < threads - 1; th++) {
+ workers.push_back(std::thread(generateMap));
}
+ rowStep = (threads == 1 ? image_height : kJobSzInRows) / kMapDimensionScaleFactor;
+ for (size_t rowStart = 0; rowStart < map_height;) {
+ size_t rowEnd = std::min(rowStart + rowStep, map_height);
+ jobQueue.enqueueJob(rowStart, rowEnd);
+ rowStart = rowEnd;
+ }
+ jobQueue.markQueueForEnd();
+ generateMap();
+ std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
+
map_data.release();
return NO_ERROR;
}
@@ -756,46 +814,95 @@
return ERROR_JPEGR_INVALID_NULL_PTR;
}
- size_t width = uncompressed_yuv_420_image->width;
- size_t height = uncompressed_yuv_420_image->height;
+ dest->width = uncompressed_yuv_420_image->width;
+ dest->height = uncompressed_yuv_420_image->height;
+ ShepardsIDW idwTable(kMapDimensionScaleFactor);
+ RecoveryLUT recoveryLUT(metadata->rangeScalingFactor);
- dest->width = width;
- dest->height = height;
- size_t pixel_count = width * height;
+ JobQueue jobQueue;
+ std::function<void()> applyRecMap = [uncompressed_yuv_420_image, uncompressed_recovery_map,
+ metadata, dest, &jobQueue, &idwTable,
+ &recoveryLUT]() -> void {
+ const float hdr_ratio = metadata->rangeScalingFactor;
+ size_t width = uncompressed_yuv_420_image->width;
+ size_t height = uncompressed_yuv_420_image->height;
- ColorTransformFn hdrOetf = nullptr;
- switch (metadata->transferFunction) {
- case JPEGR_TF_LINEAR:
- hdrOetf = identityConversion;
- break;
- case JPEGR_TF_HLG:
- hdrOetf = hlgOetf;
- break;
- case JPEGR_TF_PQ:
- hdrOetf = pqOetf;
- break;
- case JPEGR_TF_UNSPECIFIED:
- // Should be impossible to hit after input validation.
- return ERROR_JPEGR_INVALID_TRANS_FUNC;
- }
-
- for (size_t y = 0; y < height; ++y) {
- for (size_t x = 0; x < width; ++x) {
- Color yuv_gamma_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
- Color rgb_gamma_sdr = srgbYuvToRgb(yuv_gamma_sdr);
- Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
-
- // TODO: determine map scaling factor based on actual map dims
- float recovery = sampleMap(uncompressed_recovery_map, kMapDimensionScaleFactor, x, y);
- Color rgb_hdr = applyRecovery(rgb_sdr, recovery, metadata->rangeScalingFactor);
-
- Color rgb_gamma_hdr = hdrOetf(rgb_hdr / metadata->rangeScalingFactor);
- uint32_t rgba1010102 = colorToRgba1010102(rgb_gamma_hdr);
-
- size_t pixel_idx = x + y * width;
- reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba1010102;
+ ColorTransformFn hdrOetf = nullptr;
+ switch (metadata->transferFunction) {
+ case JPEGR_TF_LINEAR:
+ hdrOetf = identityConversion;
+ break;
+ case JPEGR_TF_HLG:
+#if USE_HLG_OETF_LUT
+ hdrOetf = hlgOetfLUT;
+#else
+ hdrOetf = hlgOetf;
+#endif
+ break;
+ case JPEGR_TF_PQ:
+#if USE_PQ_OETF_LUT
+ hdrOetf = pqOetfLUT;
+#else
+ hdrOetf = pqOetf;
+#endif
+ break;
+ case JPEGR_TF_UNSPECIFIED:
+ // Should be impossible to hit after input validation.
+ hdrOetf = identityConversion;
}
+
+ size_t rowStart, rowEnd;
+ while (jobQueue.dequeueJob(rowStart, rowEnd)) {
+ for (size_t y = rowStart; y < rowEnd; ++y) {
+ for (size_t x = 0; x < width; ++x) {
+ Color yuv_gamma_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
+ Color rgb_gamma_sdr = srgbYuvToRgb(yuv_gamma_sdr);
+#if USE_SRGB_INVOETF_LUT
+ Color rgb_sdr = srgbInvOetfLUT(rgb_gamma_sdr);
+#else
+ Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
+#endif
+ float recovery;
+ // TODO: determine map scaling factor based on actual map dims
+ size_t map_scale_factor = kMapDimensionScaleFactor;
+ // TODO: If map_scale_factor is guaranteed to be an integer, then remove the following.
+ // Currently map_scale_factor is of type size_t, but it could be changed to a float
+ // later.
+ if (map_scale_factor != floorf(map_scale_factor)) {
+ recovery = sampleMap(uncompressed_recovery_map, map_scale_factor, x, y);
+ } else {
+ recovery = sampleMap(uncompressed_recovery_map, map_scale_factor, x, y,
+ idwTable);
+ }
+#if USE_APPLY_RECOVERY_LUT
+ Color rgb_hdr = applyRecoveryLUT(rgb_sdr, recovery, recoveryLUT);
+#else
+ Color rgb_hdr = applyRecovery(rgb_sdr, recovery, hdr_ratio);
+#endif
+ Color rgb_gamma_hdr = hdrOetf(rgb_hdr / metadata->rangeScalingFactor);
+ uint32_t rgba1010102 = colorToRgba1010102(rgb_gamma_hdr);
+
+ size_t pixel_idx = x + y * width;
+ reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba1010102;
+ }
+ }
+ }
+ };
+
+ const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
+ std::vector<std::thread> workers;
+ for (int th = 0; th < threads - 1; th++) {
+ workers.push_back(std::thread(applyRecMap));
}
+ const int rowStep = threads == 1 ? uncompressed_yuv_420_image->height : kJobSzInRows;
+ for (int rowStart = 0; rowStart < uncompressed_yuv_420_image->height;) {
+ int rowEnd = std::min(rowStart + rowStep, uncompressed_yuv_420_image->height);
+ jobQueue.enqueueJob(rowStart, rowEnd);
+ rowStart = rowEnd;
+ }
+ jobQueue.markQueueForEnd();
+ applyRecMap();
+ std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
return NO_ERROR;
}
diff --git a/libs/jpegrecoverymap/recoverymapmath.cpp b/libs/jpegrecoverymap/recoverymapmath.cpp
index 9ed2949..4f21ac6 100644
--- a/libs/jpegrecoverymap/recoverymapmath.cpp
+++ b/libs/jpegrecoverymap/recoverymapmath.cpp
@@ -15,14 +15,129 @@
*/
#include <cmath>
-
+#include <vector>
#include <jpegrecoverymap/recoverymapmath.h>
namespace android::recoverymap {
+constexpr size_t kPqOETFPrecision = 10;
+constexpr size_t kPqOETFNumEntries = 1 << kPqOETFPrecision;
+
+static const std::vector<float> kPqOETF = [] {
+ std::vector<float> result;
+ float increment = 1.0 / kPqOETFNumEntries;
+ float value = 0.0f;
+ for (int idx = 0; idx < kPqOETFNumEntries; idx++, value += increment) {
+ result.push_back(pqOetf(value));
+ }
+ return result;
+}();
+
+constexpr size_t kPqInvOETFPrecision = 10;
+constexpr size_t kPqInvOETFNumEntries = 1 << kPqInvOETFPrecision;
+
+static const std::vector<float> kPqInvOETF = [] {
+ std::vector<float> result;
+ float increment = 1.0 / kPqInvOETFNumEntries;
+ float value = 0.0f;
+ for (int idx = 0; idx < kPqInvOETFNumEntries; idx++, value += increment) {
+ result.push_back(pqInvOetf(value));
+ }
+ return result;
+}();
+
+constexpr size_t kHlgOETFPrecision = 10;
+constexpr size_t kHlgOETFNumEntries = 1 << kHlgOETFPrecision;
+
+static const std::vector<float> kHlgOETF = [] {
+ std::vector<float> result;
+ float increment = 1.0 / kHlgOETFNumEntries;
+ float value = 0.0f;
+ for (int idx = 0; idx < kHlgOETFNumEntries; idx++, value += increment) {
+ result.push_back(hlgOetf(value));
+ }
+ return result;
+}();
+
+constexpr size_t kHlgInvOETFPrecision = 10;
+constexpr size_t kHlgInvOETFNumEntries = 1 << kHlgInvOETFPrecision;
+
+static const std::vector<float> kHlgInvOETF = [] {
+ std::vector<float> result;
+ float increment = 1.0 / kHlgInvOETFNumEntries;
+ float value = 0.0f;
+ for (int idx = 0; idx < kHlgInvOETFNumEntries; idx++, value += increment) {
+ result.push_back(hlgInvOetf(value));
+ }
+ return result;
+}();
+
+constexpr size_t kSRGBInvOETFPrecision = 10;
+constexpr size_t kSRGBInvOETFNumEntries = 1 << kSRGBInvOETFPrecision;
+static const std::vector<float> kSRGBInvOETF = [] {
+ std::vector<float> result;
+ float increment = 1.0 / kSRGBInvOETFNumEntries;
+ float value = 0.0f;
+ for (int idx = 0; idx < kSRGBInvOETFNumEntries; idx++, value += increment) {
+ result.push_back(srgbInvOetf(value));
+ }
+ return result;
+}();
+
+// Use Shepard's method for inverse distance weighting. For more information:
+// en.wikipedia.org/wiki/Inverse_distance_weighting#Shepard's_method
+
+float ShepardsIDW::euclideanDistance(float x1, float x2, float y1, float y2) {
+ return sqrt(((y2 - y1) * (y2 - y1)) + (x2 - x1) * (x2 - x1));
+}
+
+void ShepardsIDW::fillShepardsIDW(float *weights, int incR, int incB) {
+ for (int y = 0; y < mMapScaleFactor; y++) {
+ for (int x = 0; x < mMapScaleFactor; x++) {
+ float pos_x = ((float)x) / mMapScaleFactor;
+ float pos_y = ((float)y) / mMapScaleFactor;
+ int curr_x = floor(pos_x);
+ int curr_y = floor(pos_y);
+ int next_x = curr_x + incR;
+ int next_y = curr_y + incB;
+ float e1_distance = euclideanDistance(pos_x, curr_x, pos_y, curr_y);
+ int index = y * mMapScaleFactor * 4 + x * 4;
+ if (e1_distance == 0) {
+ weights[index++] = 1.f;
+ weights[index++] = 0.f;
+ weights[index++] = 0.f;
+ weights[index++] = 0.f;
+ } else {
+ float e1_weight = 1.f / e1_distance;
+
+ float e2_distance = euclideanDistance(pos_x, curr_x, pos_y, next_y);
+ float e2_weight = 1.f / e2_distance;
+
+ float e3_distance = euclideanDistance(pos_x, next_x, pos_y, curr_y);
+ float e3_weight = 1.f / e3_distance;
+
+ float e4_distance = euclideanDistance(pos_x, next_x, pos_y, next_y);
+ float e4_weight = 1.f / e4_distance;
+
+ float total_weight = e1_weight + e2_weight + e3_weight + e4_weight;
+
+ weights[index++] = e1_weight / total_weight;
+ weights[index++] = e2_weight / total_weight;
+ weights[index++] = e3_weight / total_weight;
+ weights[index++] = e4_weight / total_weight;
+ }
+ }
+ }
+}
+
////////////////////////////////////////////////////////////////////////////////
// sRGB transformations
+static const float kMaxPixelFloat = 1.0f;
+static float clampPixelFloat(float value) {
+ return (value < 0.0f) ? 0.0f : (value > kMaxPixelFloat) ? kMaxPixelFloat : value;
+}
+
// See IEC 61966-2-1, Equation F.7.
static const float kSrgbR = 0.2126f, kSrgbG = 0.7152f, kSrgbB = 0.0722f;
@@ -34,9 +149,9 @@
static const float kSrgbRCr = 1.402f, kSrgbGCb = 0.34414f, kSrgbGCr = 0.71414f, kSrgbBCb = 1.772f;
Color srgbYuvToRgb(Color e_gamma) {
- return {{{ e_gamma.y + kSrgbRCr * e_gamma.v,
- e_gamma.y - kSrgbGCb * e_gamma.u - kSrgbGCr * e_gamma.v,
- e_gamma.y + kSrgbBCb * e_gamma.u }}};
+ return {{{ clampPixelFloat(e_gamma.y + kSrgbRCr * e_gamma.v),
+ clampPixelFloat(e_gamma.y - kSrgbGCb * e_gamma.u - kSrgbGCr * e_gamma.v),
+ clampPixelFloat(e_gamma.y + kSrgbBCb * e_gamma.u) }}};
}
// See ECMA TR/98, Section 7.
@@ -65,6 +180,19 @@
srgbInvOetf(e_gamma.b) }}};
}
+// See IEC 61966-2-1, Equations F.5 and F.6.
+float srgbInvOetfLUT(float e_gamma) {
+ uint32_t value = static_cast<uint32_t>(e_gamma * kSRGBInvOETFNumEntries);
+ //TODO() : Remove once conversion modules have appropriate clamping in place
+ value = CLIP3(value, 0, kSRGBInvOETFNumEntries - 1);
+ return kSRGBInvOETF[value];
+}
+
+Color srgbInvOetfLUT(Color e_gamma) {
+ return {{{ srgbInvOetfLUT(e_gamma.r),
+ srgbInvOetfLUT(e_gamma.g),
+ srgbInvOetfLUT(e_gamma.b) }}};
+}
////////////////////////////////////////////////////////////////////////////////
// Display-P3 transformations
@@ -122,9 +250,9 @@
static const float kBt2100GCr = kBt2100R * kBt2100Cr / kBt2100G;
Color bt2100YuvToRgb(Color e_gamma) {
- return {{{ e_gamma.y + kBt2100Cr * e_gamma.v,
- e_gamma.y - kBt2100GCb * e_gamma.u - kBt2100GCr * e_gamma.v,
- e_gamma.y + kBt2100Cb * e_gamma.u }}};
+ return {{{ clampPixelFloat(e_gamma.y + kBt2100Cr * e_gamma.v),
+ clampPixelFloat(e_gamma.y - kBt2100GCb * e_gamma.u - kBt2100GCr * e_gamma.v),
+ clampPixelFloat(e_gamma.y + kBt2100Cb * e_gamma.u) }}};
}
// See ITU-R BT.2100-2, Table 5, HLG Reference OETF.
@@ -142,6 +270,18 @@
return {{{ hlgOetf(e.r), hlgOetf(e.g), hlgOetf(e.b) }}};
}
+float hlgOetfLUT(float e) {
+ uint32_t value = static_cast<uint32_t>(e * kHlgOETFNumEntries);
+ //TODO() : Remove once conversion modules have appropriate clamping in place
+ value = CLIP3(value, 0, kHlgOETFNumEntries - 1);
+
+ return kHlgOETF[value];
+}
+
+Color hlgOetfLUT(Color e) {
+ return {{{ hlgOetfLUT(e.r), hlgOetfLUT(e.g), hlgOetfLUT(e.b) }}};
+}
+
// See ITU-R BT.2100-2, Table 5, HLG Reference EOTF.
float hlgInvOetf(float e_gamma) {
if (e_gamma <= 0.5f) {
@@ -157,6 +297,20 @@
hlgInvOetf(e_gamma.b) }}};
}
+float hlgInvOetfLUT(float e_gamma) {
+ uint32_t value = static_cast<uint32_t>(e_gamma * kHlgInvOETFNumEntries);
+ //TODO() : Remove once conversion modules have appropriate clamping in place
+ value = CLIP3(value, 0, kHlgInvOETFNumEntries - 1);
+
+ return kHlgInvOETF[value];
+}
+
+Color hlgInvOetfLUT(Color e_gamma) {
+ return {{{ hlgInvOetfLUT(e_gamma.r),
+ hlgInvOetfLUT(e_gamma.g),
+ hlgInvOetfLUT(e_gamma.b) }}};
+}
+
// See ITU-R BT.2100-2, Table 4, Reference PQ OETF.
static const float kPqM1 = 2610.0f / 16384.0f, kPqM2 = 2523.0f / 4096.0f * 128.0f;
static const float kPqC1 = 3424.0f / 4096.0f, kPqC2 = 2413.0f / 4096.0f * 32.0f,
@@ -172,6 +326,18 @@
return {{{ pqOetf(e.r), pqOetf(e.g), pqOetf(e.b) }}};
}
+float pqOetfLUT(float e) {
+ uint32_t value = static_cast<uint32_t>(e * kPqOETFNumEntries);
+ //TODO() : Remove once conversion modules have appropriate clamping in place
+ value = CLIP3(value, 0, kPqOETFNumEntries - 1);
+
+ return kPqOETF[value];
+}
+
+Color pqOetfLUT(Color e) {
+ return {{{ pqOetfLUT(e.r), pqOetfLUT(e.g), pqOetfLUT(e.b) }}};
+}
+
// Derived from the inverse of the Reference PQ OETF.
static const float kPqInvA = 128.0f, kPqInvB = 107.0f, kPqInvC = 2413.0f, kPqInvD = 2392.0f,
kPqInvE = 6.2773946361f, kPqInvF = 0.0126833f;
@@ -192,6 +358,20 @@
pqInvOetf(e_gamma.b) }}};
}
+float pqInvOetfLUT(float e_gamma) {
+ uint32_t value = static_cast<uint32_t>(e_gamma * kPqInvOETFNumEntries);
+ //TODO() : Remove once conversion modules have appropriate clamping in place
+ value = CLIP3(value, 0, kPqInvOETFNumEntries - 1);
+
+ return kPqInvOETF[value];
+}
+
+Color pqInvOetfLUT(Color e_gamma) {
+ return {{{ pqInvOetfLUT(e_gamma.r),
+ pqInvOetfLUT(e_gamma.g),
+ pqInvOetfLUT(e_gamma.b) }}};
+}
+
////////////////////////////////////////////////////////////////////////////////
// Color conversions
@@ -294,15 +474,14 @@
return static_cast<uint8_t>(log2(gain) / log2(hdr_ratio) * 127.5f + 127.5f);
}
-static float applyRecovery(float e, float recovery, float hdr_ratio) {
- if (e <= 0.0f) return 0.0f;
- return exp2(log2(e) + recovery * log2(hdr_ratio));
+Color applyRecovery(Color e, float recovery, float hdr_ratio) {
+ float recoveryFactor = pow(hdr_ratio, recovery);
+ return e * recoveryFactor;
}
-Color applyRecovery(Color e, float recovery, float hdr_ratio) {
- return {{{ applyRecovery(e.r, recovery, hdr_ratio),
- applyRecovery(e.g, recovery, hdr_ratio),
- applyRecovery(e.b, recovery, hdr_ratio) }}};
+Color applyRecoveryLUT(Color e, float recovery, RecoveryLUT& recoveryLUT) {
+ float recoveryFactor = recoveryLUT.getRecoveryFactor(recovery);
+ return e * recoveryFactor;
}
Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y) {
@@ -378,6 +557,7 @@
return sqrt(pow(x_diff, 2.0f) + pow(y_diff, 2.0f));
}
+// TODO: If map_scale_factor is guaranteed to be an integer, then remove the following.
float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y) {
float x_map = static_cast<float>(x) / static_cast<float>(map_scale_factor);
float y_map = static_cast<float>(y) / static_cast<float>(map_scale_factor);
@@ -427,6 +607,39 @@
+ e4 * (e4_weight / total_weight);
}
+float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y,
+ ShepardsIDW& weightTables) {
+ // TODO: If map_scale_factor is guaranteed to be an integer power of 2, then optimize the
+ // following by computing log2(map_scale_factor) once and then using >> log2(map_scale_factor)
+ int x_lower = x / map_scale_factor;
+ int x_upper = x_lower + 1;
+ int y_lower = y / map_scale_factor;
+ int y_upper = y_lower + 1;
+
+ x_lower = std::min(x_lower, map->width - 1);
+ x_upper = std::min(x_upper, map->width - 1);
+ y_lower = std::min(y_lower, map->height - 1);
+ y_upper = std::min(y_upper, map->height - 1);
+
+ float e1 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_lower + y_lower * map->width]);
+ float e2 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_lower + y_upper * map->width]);
+ float e3 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_upper + y_lower * map->width]);
+ float e4 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_upper + y_upper * map->width]);
+
+ // TODO: If map_scale_factor is guaranteed to be an integer power of 2, then optimize the
+ // following by using & (map_scale_factor - 1)
+ int offset_x = x % map_scale_factor;
+ int offset_y = y % map_scale_factor;
+
+ float* weights = weightTables.mWeights;
+ if (x_lower == x_upper && y_lower == y_upper) weights = weightTables.mWeightsC;
+ else if (x_lower == x_upper) weights = weightTables.mWeightsNR;
+ else if (y_lower == y_upper) weights = weightTables.mWeightsNB;
+ weights += offset_y * map_scale_factor * 4 + offset_x * 4;
+
+ return e1 * weights[0] + e2 * weights[1] + e3 * weights[2] + e4 * weights[3];
+}
+
uint32_t colorToRgba1010102(Color e_gamma) {
return (0x3ff & static_cast<uint32_t>(e_gamma.r * 1023.0f))
| ((0x3ff & static_cast<uint32_t>(e_gamma.g * 1023.0f)) << 10)
diff --git a/libs/jpegrecoverymap/recoverymaputils.cpp b/libs/jpegrecoverymap/recoverymaputils.cpp
index 63b25f7..d5ad9a5 100644
--- a/libs/jpegrecoverymap/recoverymaputils.cpp
+++ b/libs/jpegrecoverymap/recoverymaputils.cpp
@@ -15,7 +15,6 @@
*/
#include <jpegrecoverymap/recoverymaputils.h>
-#include <jpegrecoverymap/recoverymap.h>
#include <image_io/xml/xml_reader.h>
#include <image_io/xml/xml_writer.h>
#include <image_io/base/message_handler.h>
@@ -23,6 +22,8 @@
#include <image_io/xml/xml_handler.h>
#include <image_io/xml/xml_rule.h>
+#include <utils/Log.h>
+
using namespace photos_editing_formats::image_io;
using namespace std;
@@ -41,6 +42,25 @@
return ss.str();
}
+/*
+ * Helper function used for writing data to destination.
+ */
+status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position) {
+ if (position + length > destination->maxLength) {
+ return ERROR_JPEGR_BUFFER_TOO_SMALL;
+ }
+
+ memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length);
+ position += length;
+ 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;
+}
+
// Extremely simple XML Handler - just searches for interesting elements
class XMPXmlHandler : public XmlHandler {
public:
@@ -148,41 +168,58 @@
ParseState gContainerItemState;
};
-const string XMPXmlHandler::gContainerItemName = "GContainer:Item";
-const string XMPXmlHandler::rangeScalingFactorAttrName = "RecoveryMap:RangeScalingFactor";
-const string XMPXmlHandler::transferFunctionAttrName = "RecoveryMap:TransferFunction";
+// GContainer XMP constants - URI and namespace prefix
+const string kContainerUri = "http://ns.google.com/photos/1.0/container/";
+const string kContainerPrefix = "GContainer";
+// GContainer XMP constants - element and attribute names
+const string kConDirectory = Name(kContainerPrefix, "Directory");
+const string kConItem = Name(kContainerPrefix, "Item");
+const string kConItemLength = Name(kContainerPrefix, "ItemLength");
+const string kConItemMime = Name(kContainerPrefix, "ItemMime");
+const string kConItemSemantic = Name(kContainerPrefix, "ItemSemantic");
+const string kConVersion = Name(kContainerPrefix, "Version");
-const string kContainerPrefix = "GContainer";
-const string kContainerUri = "http://ns.google.com/photos/1.0/container/";
-const string kRecoveryMapUri = "http://ns.google.com/photos/1.0/recoverymap/";
-const string kItemPrefix = "Item";
-const string kRecoveryMap = "RecoveryMap";
-const string kDirectory = "Directory";
-const string kImageJpeg = "image/jpeg";
-const string kItem = "Item";
-const string kLength = "Length";
-const string kMime = "Mime";
-const string kPrimary = "Primary";
-const string kSemantic = "Semantic";
-const string kVersion = "Version";
-const string kHdr10Metadata = "HDR10Metadata";
-const string kSt2086Metadata = "ST2086Metadata";
-const string kSt2086Coordinate = "ST2086Coordinate";
-const string kSt2086CoordinateX = "ST2086CoordinateX";
-const string kSt2086CoordinateY = "ST2086CoordinateY";
-const string kSt2086Primary = "ST2086Primary";
-const int kSt2086PrimaryRed = 0;
-const int kSt2086PrimaryGreen = 1;
-const int kSt2086PrimaryBlue = 2;
-const int kSt2086PrimaryWhite = 3;
-const int kGContainerVersion = 1;
+// GContainer XMP constants - element and attribute values
+const string kSemanticPrimary = "Primary";
+const string kSemanticRecoveryMap = "RecoveryMap";
+const string kMimeImageJpeg = "image/jpeg";
-const string kConDir = Name(kContainerPrefix, kDirectory);
-const string kContainerItem = Name(kContainerPrefix, kItem);
-const string kItemLength = Name(kItemPrefix, kLength);
-const string kItemMime = Name(kItemPrefix, kMime);
-const string kItemSemantic = Name(kItemPrefix, kSemantic);
+const int kGContainerVersion = 1;
+
+// GContainer XMP constants - names for XMP handlers
+const string XMPXmlHandler::gContainerItemName = kConItem;
+
+// RecoveryMap XMP constants - URI and namespace prefix
+const string kRecoveryMapUri = "http://ns.google.com/photos/1.0/recoverymap/";
+const string kRecoveryMapPrefix = "RecoveryMap";
+
+// RecoveryMap XMP constants - element and attribute names
+const string kMapRangeScalingFactor = Name(kRecoveryMapPrefix, "RangeScalingFactor");
+const string kMapTransferFunction = Name(kRecoveryMapPrefix, "TransferFunction");
+const string kMapVersion = Name(kRecoveryMapPrefix, "Version");
+
+const string kMapHdr10Metadata = Name(kRecoveryMapPrefix, "HDR10Metadata");
+const string kMapHdr10MaxFall = Name(kRecoveryMapPrefix, "HDR10MaxFALL");
+const string kMapHdr10MaxCll = Name(kRecoveryMapPrefix, "HDR10MaxCLL");
+
+const string kMapSt2086Metadata = Name(kRecoveryMapPrefix, "ST2086Metadata");
+const string kMapSt2086MaxLum = Name(kRecoveryMapPrefix, "ST2086MaxLuminance");
+const string kMapSt2086MinLum = Name(kRecoveryMapPrefix, "ST2086MinLuminance");
+const string kMapSt2086Primary = Name(kRecoveryMapPrefix, "ST2086Primary");
+const string kMapSt2086Coordinate = Name(kRecoveryMapPrefix, "ST2086Coordinate");
+const string kMapSt2086CoordinateX = Name(kRecoveryMapPrefix, "ST2086CoordinateX");
+const string kMapSt2086CoordinateY = Name(kRecoveryMapPrefix, "ST2086CoordinateY");
+
+// RecoveryMap XMP constants - element and attribute values
+const int kSt2086PrimaryRed = 0;
+const int kSt2086PrimaryGreen = 1;
+const int kSt2086PrimaryBlue = 2;
+const int kSt2086PrimaryWhite = 3;
+
+// RecoveryMap XMP constants - names for XMP handlers
+const string XMPXmlHandler::rangeScalingFactorAttrName = kMapRangeScalingFactor;
+const string XMPXmlHandler::transferFunctionAttrName = kMapTransferFunction;
bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata) {
string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
@@ -230,8 +267,8 @@
}
string generateXmp(int secondary_image_length, jpegr_metadata& metadata) {
- const vector<string> kConDirSeq({kConDir, string("rdf:Seq")});
- const vector<string> kLiItem({string("rdf:li"), kContainerItem});
+ const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")});
+ const vector<string> kLiItem({string("rdf:li"), kConItem});
std::stringstream ss;
photos_editing_formats::image_io::XmlWriter writer(ss);
@@ -242,83 +279,250 @@
writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
writer.StartWritingElement("rdf:Description");
writer.WriteXmlns(kContainerPrefix, kContainerUri);
- writer.WriteXmlns(kRecoveryMap, kRecoveryMapUri);
- writer.WriteElementAndContent(Name(kContainerPrefix, kVersion), kGContainerVersion);
+ writer.WriteXmlns(kRecoveryMapPrefix, kRecoveryMapUri);
+ writer.WriteElementAndContent(kConVersion, kGContainerVersion);
writer.StartWritingElements(kConDirSeq);
size_t item_depth = writer.StartWritingElements(kLiItem);
- writer.WriteAttributeNameAndValue(kItemSemantic, kPrimary);
- writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg);
- writer.WriteAttributeNameAndValue(Name(kRecoveryMap, kVersion), metadata.version);
- writer.WriteAttributeNameAndValue(
- Name(kRecoveryMap, "RangeScalingFactor"), metadata.rangeScalingFactor);
- writer.WriteAttributeNameAndValue(
- Name(kRecoveryMap, "TransferFunction"), metadata.transferFunction);
+ writer.WriteAttributeNameAndValue(kConItemSemantic, kSemanticPrimary);
+ writer.WriteAttributeNameAndValue(kConItemMime, kMimeImageJpeg);
+ writer.WriteAttributeNameAndValue(kMapVersion, metadata.version);
+ writer.WriteAttributeNameAndValue(kMapRangeScalingFactor, metadata.rangeScalingFactor);
+ writer.WriteAttributeNameAndValue(kMapTransferFunction, metadata.transferFunction);
if (metadata.transferFunction == JPEGR_TF_PQ) {
- writer.StartWritingElement(Name(kRecoveryMap, kHdr10Metadata));
+ writer.StartWritingElement(kMapHdr10Metadata);
+ writer.WriteAttributeNameAndValue(kMapHdr10MaxFall, metadata.hdr10Metadata.maxFALL);
+ writer.WriteAttributeNameAndValue(kMapHdr10MaxCll, metadata.hdr10Metadata.maxCLL);
+ writer.StartWritingElement(kMapSt2086Metadata);
writer.WriteAttributeNameAndValue(
- Name(kRecoveryMap, "HDR10MaxFALL"), metadata.hdr10Metadata.maxFALL);
+ kMapSt2086MaxLum, metadata.hdr10Metadata.st2086Metadata.maxLuminance);
writer.WriteAttributeNameAndValue(
- Name(kRecoveryMap, "HDR10MaxCLL"), metadata.hdr10Metadata.maxCLL);
- writer.StartWritingElement(Name(kRecoveryMap, kSt2086Metadata));
- writer.WriteAttributeNameAndValue(
- Name(kRecoveryMap, "ST2086MaxLuminance"),
- metadata.hdr10Metadata.st2086Metadata.maxLuminance);
- writer.WriteAttributeNameAndValue(
- Name(kRecoveryMap, "ST2086MinLuminance"),
- metadata.hdr10Metadata.st2086Metadata.minLuminance);
+ kMapSt2086MinLum, metadata.hdr10Metadata.st2086Metadata.minLuminance);
// red
- writer.StartWritingElement(Name(kRecoveryMap, kSt2086Coordinate));
- writer.WriteAttributeNameAndValue(Name(kRecoveryMap, kSt2086Primary), kSt2086PrimaryRed);
+ writer.StartWritingElement(kMapSt2086Coordinate);
+ writer.WriteAttributeNameAndValue(kMapSt2086Primary, kSt2086PrimaryRed);
writer.WriteAttributeNameAndValue(
- Name(kRecoveryMap, kSt2086CoordinateX),
- metadata.hdr10Metadata.st2086Metadata.redPrimary.x);
+ kMapSt2086CoordinateX, metadata.hdr10Metadata.st2086Metadata.redPrimary.x);
writer.WriteAttributeNameAndValue(
- Name(kRecoveryMap, kSt2086CoordinateY),
- metadata.hdr10Metadata.st2086Metadata.redPrimary.y);
+ kMapSt2086CoordinateY, metadata.hdr10Metadata.st2086Metadata.redPrimary.y);
writer.FinishWritingElement();
// green
- writer.StartWritingElement(Name(kRecoveryMap, kSt2086Coordinate));
- writer.WriteAttributeNameAndValue(Name(kRecoveryMap, kSt2086Primary), kSt2086PrimaryGreen);
+ writer.StartWritingElement(kMapSt2086Coordinate);
+ writer.WriteAttributeNameAndValue(kMapSt2086Primary, kSt2086PrimaryGreen);
writer.WriteAttributeNameAndValue(
- Name(kRecoveryMap, kSt2086CoordinateX),
- metadata.hdr10Metadata.st2086Metadata.greenPrimary.x);
+ kMapSt2086CoordinateX, metadata.hdr10Metadata.st2086Metadata.greenPrimary.x);
writer.WriteAttributeNameAndValue(
- Name(kRecoveryMap, kSt2086CoordinateY),
- metadata.hdr10Metadata.st2086Metadata.greenPrimary.y);
+ kMapSt2086CoordinateY, metadata.hdr10Metadata.st2086Metadata.greenPrimary.y);
writer.FinishWritingElement();
// blue
- writer.StartWritingElement(Name(kRecoveryMap, kSt2086Coordinate));
- writer.WriteAttributeNameAndValue(Name(kRecoveryMap, kSt2086Primary), kSt2086PrimaryBlue);
+ writer.StartWritingElement(kMapSt2086Coordinate);
+ writer.WriteAttributeNameAndValue(kMapSt2086Primary, kSt2086PrimaryBlue);
writer.WriteAttributeNameAndValue(
- Name(kRecoveryMap, kSt2086CoordinateX),
- metadata.hdr10Metadata.st2086Metadata.bluePrimary.x);
+ kMapSt2086CoordinateX, metadata.hdr10Metadata.st2086Metadata.bluePrimary.x);
writer.WriteAttributeNameAndValue(
- Name(kRecoveryMap, kSt2086CoordinateY),
- metadata.hdr10Metadata.st2086Metadata.bluePrimary.y);
+ kMapSt2086CoordinateY, metadata.hdr10Metadata.st2086Metadata.bluePrimary.y);
writer.FinishWritingElement();
// white
- writer.StartWritingElement(Name(kRecoveryMap, kSt2086Coordinate));
- writer.WriteAttributeNameAndValue(Name(kRecoveryMap, kSt2086Primary), kSt2086PrimaryWhite);
+ writer.StartWritingElement(kMapSt2086Coordinate);
+ writer.WriteAttributeNameAndValue(kMapSt2086Primary, kSt2086PrimaryWhite);
writer.WriteAttributeNameAndValue(
- Name(kRecoveryMap, kSt2086CoordinateX),
- metadata.hdr10Metadata.st2086Metadata.whitePoint.x);
+ kMapSt2086CoordinateX, metadata.hdr10Metadata.st2086Metadata.whitePoint.x);
writer.WriteAttributeNameAndValue(
- Name(kRecoveryMap, kSt2086CoordinateY),
- metadata.hdr10Metadata.st2086Metadata.whitePoint.y);
+ kMapSt2086CoordinateY, metadata.hdr10Metadata.st2086Metadata.whitePoint.y);
writer.FinishWritingElement();
}
writer.FinishWritingElementsToDepth(item_depth);
writer.StartWritingElements(kLiItem);
- writer.WriteAttributeNameAndValue(kItemSemantic, kRecoveryMap);
- writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg);
- writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length);
+ writer.WriteAttributeNameAndValue(kConItemSemantic, kSemanticRecoveryMap);
+ writer.WriteAttributeNameAndValue(kConItemMime, kMimeImageJpeg);
+ writer.WriteAttributeNameAndValue(kConItemLength, secondary_image_length);
writer.FinishWriting();
return ss.str();
}
+/*
+ * Helper function
+ * Add J R entry to existing exif, or create a new one with J R entry if it's null.
+ */
+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,
+ 0x00, 0x07,
+ 0x00, 0x00, 0x00, 0x01,
+ 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,
+ 0x07, 0x00,
+ 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00};
+ Write(dest, data, EXIF_J_R_ENTRY_LENGTH, pos);
+ }
+
+ Write(dest, (uint8_t*)exif->data + 16, exif->length - 16, pos);
+
+ updateExifOffsets(dest,
+ 28, // start from the second tag, skip the "JR" tag
+ num_entry - 1,
+ use_big_endian);
+
+ return NO_ERROR;
+}
+
+/*
+ * Helper function
+ * Modify offsets in EXIF in place.
+ */
+void updateExifOffsets(jr_exif_ptr exif, int pos, bool use_big_endian) {
+ int num_entry = readValue(reinterpret_cast<uint8_t*>(exif->data), pos, 2, use_big_endian);
+ updateExifOffsets(exif, pos + 2, num_entry, use_big_endian);
+}
+
+void updateExifOffsets(jr_exif_ptr exif, int pos, int num_entry, bool use_big_endian) {
+ for (int i = 0; i < num_entry; pos += EXIF_J_R_ENTRY_LENGTH, i++) {
+ int tag = readValue(reinterpret_cast<uint8_t*>(exif->data), pos, 2, use_big_endian);
+ bool need_to_update_offset = false;
+ if (tag == 0x8769) {
+ need_to_update_offset = true;
+ int sub_ifd_offset =
+ readValue(reinterpret_cast<uint8_t*>(exif->data), pos + 8, 4, use_big_endian)
+ + 6 // "Exif\0\0";
+ + EXIF_J_R_ENTRY_LENGTH;
+ updateExifOffsets(exif, sub_ifd_offset, use_big_endian);
+ } else {
+ int data_format =
+ readValue(reinterpret_cast<uint8_t*>(exif->data), pos + 2, 2, use_big_endian);
+ int num_of_components =
+ readValue(reinterpret_cast<uint8_t*>(exif->data), pos + 4, 4, use_big_endian);
+ int data_length = findFormatLengthInBytes(data_format) * num_of_components;
+ if (data_length > 4) {
+ need_to_update_offset = true;
+ }
+ }
+
+ if (!need_to_update_offset) {
+ continue;
+ }
+
+ int offset = readValue(reinterpret_cast<uint8_t*>(exif->data), pos + 8, 4, use_big_endian);
+
+ offset += EXIF_J_R_ENTRY_LENGTH;
+
+ if (use_big_endian) {
+ reinterpret_cast<uint8_t*>(exif->data)[pos + 11] = offset & 0xff;
+ reinterpret_cast<uint8_t*>(exif->data)[pos + 10] = (offset >> 8) & 0xff;
+ reinterpret_cast<uint8_t*>(exif->data)[pos + 9] = (offset >> 16) & 0xff;
+ reinterpret_cast<uint8_t*>(exif->data)[pos + 8] = (offset >> 24) & 0xff;
+ } else {
+ reinterpret_cast<uint8_t*>(exif->data)[pos + 8] = offset & 0xff;
+ reinterpret_cast<uint8_t*>(exif->data)[pos + 9] = (offset >> 8) & 0xff;
+ reinterpret_cast<uint8_t*>(exif->data)[pos + 10] = (offset >> 16) & 0xff;
+ reinterpret_cast<uint8_t*>(exif->data)[pos + 11] = (offset >> 24) & 0xff;
+ }
+ }
+}
+
+/*
+ * Read data from the target position and target length in bytes;
+ */
+int readValue(uint8_t* data, int pos, int length, bool use_big_endian) {
+ if (length == 2) {
+ if (use_big_endian) {
+ return (data[pos] << 8) | data[pos + 1];
+ } else {
+ return (data[pos + 1] << 8) | data[pos];
+ }
+ } else if (length == 4) {
+ if (use_big_endian) {
+ return (data[pos] << 24) | (data[pos + 1] << 16) | (data[pos + 2] << 8) | data[pos + 3];
+ } else {
+ return (data[pos + 3] << 24) | (data[pos + 2] << 16) | (data[pos + 1] << 8) | data[pos];
+ }
+ } else {
+ // Not support for now.
+ ALOGE("Error in readValue(): pos=%d, length=%d", pos, length);
+ return -1;
+ }
+}
+
+/*
+ * Helper function
+ * Returns the length of data format in bytes
+ */
+int findFormatLengthInBytes(int data_format) {
+ switch (data_format) {
+ case 1: // unsigned byte
+ case 2: // ascii strings
+ case 6: // signed byte
+ case 7: // undefined
+ return 1;
+
+ case 3: // unsigned short
+ case 8: // signed short
+ return 2;
+
+ case 4: // unsigned long
+ case 9: // signed long
+ case 11: // single float
+ return 4;
+
+ case 5: // unsigned rational
+ case 10: // signed rational
+ case 12: // double float
+ return 8;
+
+ default:
+ // should not hit here
+ ALOGE("Error in findFormatLengthInBytes(): data_format=%d", data_format);
+ return -1;
+ }
+}
+
} // namespace android::recoverymap
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/tests/recoverymap_test.cpp b/libs/jpegrecoverymap/tests/recoverymap_test.cpp
index 8ff12fb..dfab76a 100644
--- a/libs/jpegrecoverymap/tests/recoverymap_test.cpp
+++ b/libs/jpegrecoverymap/tests/recoverymap_test.cpp
@@ -15,6 +15,7 @@
*/
#include <jpegrecoverymap/recoverymap.h>
+#include <jpegrecoverymap/recoverymapmath.h>
#include <jpegrecoverymap/recoverymaputils.h>
#include <fcntl.h>
#include <fstream>
@@ -30,6 +31,7 @@
#define SAVE_ENCODING_RESULT true
#define SAVE_DECODING_RESULT true
+#define SAVE_INPUT_RGBA true
namespace android::recoverymap {
@@ -104,13 +106,22 @@
metadata_expected.transferFunction = JPEGR_TF_HLG;
metadata_expected.rangeScalingFactor = 1.25;
int length_expected = 1000;
+ const std::string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
+ const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
+
std::string xmp = generateXmp(1000, metadata_expected);
+ std::vector<uint8_t> xmpData;
+ xmpData.reserve(nameSpaceLength + xmp.size());
+ xmpData.insert(xmpData.end(), reinterpret_cast<const uint8_t*>(nameSpace.c_str()),
+ reinterpret_cast<const uint8_t*>(nameSpace.c_str()) + nameSpaceLength);
+ xmpData.insert(xmpData.end(), reinterpret_cast<const uint8_t*>(xmp.c_str()),
+ reinterpret_cast<const uint8_t*>(xmp.c_str()) + xmp.size());
+
jpegr_metadata metadata_read;
- EXPECT_TRUE(getMetadataFromXMP(reinterpret_cast<uint8_t*>(xmp[0]), xmp.size(), &metadata_read));
+ EXPECT_TRUE(getMetadataFromXMP(xmpData.data(), xmpData.size(), &metadata_read));
ASSERT_EQ(metadata_expected.transferFunction, metadata_read.transferFunction);
ASSERT_EQ(metadata_expected.rangeScalingFactor, metadata_read.rangeScalingFactor);
-
}
/* Test Encode API-0 and decode */
@@ -304,6 +315,29 @@
mRawP010Image.height = TEST_IMAGE_HEIGHT;
mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100;
+ if (SAVE_INPUT_RGBA) {
+ size_t rgbaSize = mRawP010Image.width * mRawP010Image.height * sizeof(uint32_t);
+ uint32_t *data = (uint32_t *)malloc(rgbaSize);
+
+ for (size_t y = 0; y < mRawP010Image.height; ++y) {
+ for (size_t x = 0; x < mRawP010Image.width; ++x) {
+ Color hdr_yuv_gamma = getP010Pixel(&mRawP010Image, x, y);
+ Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
+ uint32_t rgba1010102 = colorToRgba1010102(hdr_rgb_gamma);
+ size_t pixel_idx = x + y * mRawP010Image.width;
+ reinterpret_cast<uint32_t*>(data)[pixel_idx] = rgba1010102;
+ }
+ }
+
+ // Output image data to file
+ std::string filePath = "/sdcard/Documents/input_from_p010.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*)data, rgbaSize);
+ free(data);
+ }
if (!loadFile(JPEG_IMAGE, mJpegImage.data, &mJpegImage.length)) {
FAIL() << "Load file " << JPEG_IMAGE << " failed";
}
diff --git a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp
index f8dd490..1d522d1 100644
--- a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp
+++ b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp
@@ -517,6 +517,65 @@
EXPECT_RGB_NEAR(pqInvOetf(e_gamma), e);
}
+TEST_F(RecoveryMapMathTest, PqInvOetfLUT) {
+ float increment = 1.0 / 1024.0;
+ float value = 0.0f;
+ for (int idx = 0; idx < 1024; idx++, value += increment) {
+ EXPECT_FLOAT_EQ(pqInvOetf(value), pqInvOetfLUT(value));
+ }
+}
+
+TEST_F(RecoveryMapMathTest, HlgInvOetfLUT) {
+ float increment = 1.0 / 1024.0;
+ float value = 0.0f;
+ for (int idx = 0; idx < 1024; idx++, value += increment) {
+ EXPECT_FLOAT_EQ(hlgInvOetf(value), hlgInvOetfLUT(value));
+ }
+}
+
+TEST_F(RecoveryMapMathTest, pqOetfLUT) {
+ float increment = 1.0 / 1024.0;
+ float value = 0.0f;
+ for (int idx = 0; idx < 1024; idx++, value += increment) {
+ EXPECT_FLOAT_EQ(pqOetf(value), pqOetfLUT(value));
+ }
+}
+
+TEST_F(RecoveryMapMathTest, hlgOetfLUT) {
+ float increment = 1.0 / 1024.0;
+ float value = 0.0f;
+ for (int idx = 0; idx < 1024; idx++, value += increment) {
+ EXPECT_FLOAT_EQ(hlgOetf(value), hlgOetfLUT(value));
+ }
+}
+
+TEST_F(RecoveryMapMathTest, srgbInvOetfLUT) {
+ float increment = 1.0 / 1024.0;
+ float value = 0.0f;
+ for (int idx = 0; idx < 1024; idx++, value += increment) {
+ EXPECT_FLOAT_EQ(srgbInvOetf(value), srgbInvOetfLUT(value));
+ }
+}
+
+TEST_F(RecoveryMapMathTest, applyRecoveryLUT) {
+ float increment = 2.0 / kRecoveryFactorNumEntries;
+ for (float hdrRatio = 1.0f; hdrRatio <= 10.0f; hdrRatio += 1.0f) {
+ RecoveryLUT recoveryLUT(hdrRatio);
+ for (float value = -1.0f; value <= -1.0f; value += increment) {
+ EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), value, hdrRatio),
+ applyRecoveryLUT(RgbBlack(), value, recoveryLUT));
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), value, hdrRatio),
+ applyRecoveryLUT(RgbWhite(), value, recoveryLUT));
+ EXPECT_RGB_NEAR(applyRecovery(RgbRed(), value, hdrRatio),
+ applyRecoveryLUT(RgbRed(), value, recoveryLUT));
+ EXPECT_RGB_NEAR(applyRecovery(RgbGreen(), value, hdrRatio),
+ applyRecoveryLUT(RgbGreen(), value, recoveryLUT));
+ EXPECT_RGB_NEAR(applyRecovery(RgbBlue(), value, hdrRatio),
+ applyRecoveryLUT(RgbBlue(), value, recoveryLUT));
+ }
+ }
+}
+
TEST_F(RecoveryMapMathTest, PqTransferFunctionRoundtrip) {
EXPECT_FLOAT_EQ(pqInvOetf(pqOetf(0.0f)), 0.0f);
EXPECT_NEAR(pqInvOetf(pqOetf(0.01f)), 0.01f, ComparisonEpsilon());
@@ -699,6 +758,7 @@
float (*values)[4] = MapValues();
static const size_t kMapScaleFactor = 2;
+ ShepardsIDW idwTable(kMapScaleFactor);
for (size_t y = 0; y < 4 * kMapScaleFactor; ++y) {
for (size_t x = 0; x < 4 * kMapScaleFactor; ++x) {
size_t x_base = x / kMapScaleFactor;
@@ -725,7 +785,7 @@
// Instead of reimplementing the sampling algorithm, confirm that the
// sample output is within the range of the min and max of the nearest
// points.
- EXPECT_THAT(sampleMap(&image, kMapScaleFactor, x, y),
+ EXPECT_THAT(sampleMap(&image, kMapScaleFactor, x, y, idwTable),
testing::AllOf(testing::Ge(min), testing::Le(max)));
}
}
diff --git a/libs/nativewindow/include/android/hardware_buffer.h b/libs/nativewindow/include/android/hardware_buffer.h
index b2e8bea..85a5249 100644
--- a/libs/nativewindow/include/android/hardware_buffer.h
+++ b/libs/nativewindow/include/android/hardware_buffer.h
@@ -311,6 +311,16 @@
*/
AHARDWAREBUFFER_USAGE_GPU_MIPMAP_COMPLETE = 1UL << 26,
+ /**
+ * Usage: The buffer is used for front-buffer rendering. When
+ * front-buffering rendering is specified, different usages may adjust their
+ * behavior as a result. For example, when used as GPU_COLOR_OUTPUT the buffer
+ * will behave similar to a single-buffered window. When used with
+ * COMPOSER_OVERLAY, the system will try to prioritize the buffer receiving
+ * an overlay plane & avoid caching it in intermediate composition buffers.
+ */
+ AHARDWAREBUFFER_USAGE_FRONT_BUFFER = 1UL << 32,
+
AHARDWAREBUFFER_USAGE_VENDOR_0 = 1ULL << 28,
AHARDWAREBUFFER_USAGE_VENDOR_1 = 1ULL << 29,
AHARDWAREBUFFER_USAGE_VENDOR_2 = 1ULL << 30,
diff --git a/opengl/libs/EGL/Loader.cpp b/opengl/libs/EGL/Loader.cpp
index dd14bcf..34b1251 100644
--- a/opengl/libs/EGL/Loader.cpp
+++ b/opengl/libs/EGL/Loader.cpp
@@ -502,7 +502,6 @@
void* so = do_android_dlopen_ext(name.c_str(), RTLD_LOCAL | RTLD_NOW, &dlextinfo);
if (so) {
- ALOGD("dlopen_ext from APK (%s) success at %p", name.c_str(), so);
return so;
} else {
ALOGE("dlopen_ext(\"%s\") failed: %s", name.c_str(), dlerror());
diff --git a/services/automotive/display/Android.bp b/services/automotive/display/Android.bp
index 72bd292..614a78e 100644
--- a/services/automotive/display/Android.bp
+++ b/services/automotive/display/Android.bp
@@ -53,4 +53,6 @@
vintf_fragments: [
"manifest_android.frameworks.automotive.display@1.0.xml",
],
+
+ system_ext_specific: true,
}
diff --git a/services/automotive/display/android.frameworks.automotive.display@1.0-service.rc b/services/automotive/display/android.frameworks.automotive.display@1.0-service.rc
index 5c7f344..ea1077a 100644
--- a/services/automotive/display/android.frameworks.automotive.display@1.0-service.rc
+++ b/services/automotive/display/android.frameworks.automotive.display@1.0-service.rc
@@ -1,4 +1,4 @@
-service automotive_display /system/bin/android.frameworks.automotive.display@1.0-service
+service automotive_display /system_ext/bin/android.frameworks.automotive.display@1.0-service
class hal
user graphics
group automotive_evs
diff --git a/services/batteryservice/include/batteryservice/BatteryService.h b/services/batteryservice/include/batteryservice/BatteryService.h
index 178bc29..a2e4115 100644
--- a/services/batteryservice/include/batteryservice/BatteryService.h
+++ b/services/batteryservice/include/batteryservice/BatteryService.h
@@ -34,6 +34,9 @@
BATTERY_PROP_CAPACITY = 4, // equals BATTERY_PROPERTY_CAPACITY
BATTERY_PROP_ENERGY_COUNTER = 5, // equals BATTERY_PROPERTY_ENERGY_COUNTER
BATTERY_PROP_BATTERY_STATUS = 6, // equals BATTERY_PROPERTY_BATTERY_STATUS
+ BATTERY_PROP_CHARGING_POLICY = 7, // equals BATTERY_PROPERTY_CHARGING_POLICY
+ BATTERY_PROP_MANUFACTURING_DATE = 8, // equals BATTERY_PROPERTY_MANUFACTURING_DATE
+ BATTERY_PROP_FIRST_USAGE_DATE = 9, // equals BATTERY_PROPERTY_FIRST_USAGE_DATE
};
struct BatteryProperties {
diff --git a/services/batteryservice/include/batteryservice/BatteryServiceConstants.h b/services/batteryservice/include/batteryservice/BatteryServiceConstants.h
index 8a90a12..2d7072d 100644
--- a/services/batteryservice/include/batteryservice/BatteryServiceConstants.h
+++ b/services/batteryservice/include/batteryservice/BatteryServiceConstants.h
@@ -1,7 +1,5 @@
-// This file is autogenerated by hidl-gen. Do not edit manually.
-
-#ifndef HIDL_GENERATED_android_hardware_health_V1_0_EXPORTED_CONSTANTS_H_
-#define HIDL_GENERATED_android_hardware_health_V1_0_EXPORTED_CONSTANTS_H_
+#ifndef AIDL_android_hardware_health_V2_EXPORTED_CONSTANTS_H_
+#define AIDL_android_hardware_health_V2_EXPORTED_CONSTANTS_H_
#ifdef __cplusplus
extern "C" {
@@ -15,6 +13,8 @@
BATTERY_STATUS_FULL = 5,
};
+// must be kept in sync with definitions in
+// hardware/interfaces/health/aidl/android/hardware/health/BatteryHealth.aidl
enum {
BATTERY_HEALTH_UNKNOWN = 1,
BATTERY_HEALTH_GOOD = 2,
@@ -23,10 +23,23 @@
BATTERY_HEALTH_OVER_VOLTAGE = 5,
BATTERY_HEALTH_UNSPECIFIED_FAILURE = 6,
BATTERY_HEALTH_COLD = 7,
+ BATTERY_HEALTH_FAIR = 8,
+ BATTERY_HEALTH_NOT_AVAILABLE = 11,
+ BATTERY_HEALTH_INCONSISTENT = 12,
+};
+
+// must be kept in sync with definitions in
+// hardware/interfaces/health/aidl/android/hardware/health/BatteryChargingState.aidl
+enum {
+ BATTERY_STATUS_NORMAL = 1,
+ BATTERY_STATUS_TOO_COLD = 2,
+ BATTERY_STATUS_TOO_HOT = 3,
+ BATTERY_STATUS_LONG_LIFE = 4,
+ BATTERY_STATUS_ADAPTIVE = 5,
};
#ifdef __cplusplus
}
#endif
-#endif // HIDL_GENERATED_android_hardware_health_V1_0_EXPORTED_CONSTANTS_H_
+#endif // AIDL_android_hardware_health_V2_EXPORTED_CONSTANTS_H_
diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING
index 3d7242e..cacf30b 100644
--- a/services/inputflinger/TEST_MAPPING
+++ b/services/inputflinger/TEST_MAPPING
@@ -61,6 +61,7 @@
"name": "FrameworksCoreTests",
"options": [
{
+ "include-filter": "android.hardware.input",
"include-filter": "android.view.VerifiedKeyEventTest",
"include-filter": "android.view.VerifiedMotionEventTest"
}
diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp
index 7bbfb95..ce7c882 100644
--- a/services/inputflinger/dispatcher/Entry.cpp
+++ b/services/inputflinger/dispatcher/Entry.cpp
@@ -331,4 +331,28 @@
return seq;
}
+std::ostream& operator<<(std::ostream& out, const DispatchEntry& entry) {
+ out << "DispatchEntry{resolvedAction=";
+ switch (entry.eventEntry->type) {
+ case EventEntry::Type::KEY: {
+ out << KeyEvent::actionToString(entry.resolvedAction);
+ break;
+ }
+ case EventEntry::Type::MOTION: {
+ out << MotionEvent::actionToString(entry.resolvedAction);
+ break;
+ }
+ default: {
+ out << "<invalid, not a key or a motion>";
+ break;
+ }
+ }
+ std::string transform;
+ entry.transform.dump(transform, "transform");
+ out << ", resolvedFlags=" << entry.resolvedFlags
+ << ", targetFlags=" << entry.targetFlags.string() << ", transform=" << transform
+ << "} original =" << entry.eventEntry->getDescription();
+ return out;
+}
+
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h
index 3799814..8dc2a2a 100644
--- a/services/inputflinger/dispatcher/Entry.h
+++ b/services/inputflinger/dispatcher/Entry.h
@@ -254,6 +254,8 @@
static uint32_t nextSeq();
};
+std::ostream& operator<<(std::ostream& out, const DispatchEntry& entry);
+
VerifiedKeyEvent verifiedKeyEventFromKeyEntry(const KeyEntry& entry);
VerifiedMotionEvent verifiedMotionEventFromMotionEntry(const MotionEntry& entry,
const ui::Transform& rawTransform);
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 906bb1b..7974b2c 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -20,6 +20,7 @@
#define LOG_NDEBUG 1
#include <android-base/chrono_utils.h>
+#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android/os/IInputConstants.h>
@@ -554,6 +555,74 @@
return std::nullopt;
}
+/**
+ * Compare the old touch state to the new touch state, and generate the corresponding touched
+ * windows (== input targets).
+ * If a window had the hovering pointer, but now it doesn't, produce HOVER_EXIT for that window.
+ * If the pointer just entered the new window, produce HOVER_ENTER.
+ * For pointers remaining in the window, produce HOVER_MOVE.
+ */
+std::vector<TouchedWindow> getHoveringWindowsLocked(const TouchState* oldState,
+ const TouchState& newTouchState,
+ const MotionEntry& entry) {
+ std::vector<TouchedWindow> out;
+ const int32_t maskedAction = MotionEvent::getActionMasked(entry.action);
+ if (maskedAction != AMOTION_EVENT_ACTION_HOVER_ENTER &&
+ maskedAction != AMOTION_EVENT_ACTION_HOVER_MOVE &&
+ maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT) {
+ // Not a hover event - don't need to do anything
+ return out;
+ }
+
+ // We should consider all hovering pointers here. But for now, just use the first one
+ const int32_t pointerId = entry.pointerProperties[0].id;
+
+ std::set<sp<WindowInfoHandle>> oldWindows;
+ if (oldState != nullptr) {
+ oldWindows = oldState->getWindowsWithHoveringPointer(entry.deviceId, pointerId);
+ }
+
+ std::set<sp<WindowInfoHandle>> newWindows =
+ newTouchState.getWindowsWithHoveringPointer(entry.deviceId, pointerId);
+
+ // If the pointer is no longer in the new window set, send HOVER_EXIT.
+ for (const sp<WindowInfoHandle>& oldWindow : oldWindows) {
+ if (newWindows.find(oldWindow) == newWindows.end()) {
+ TouchedWindow touchedWindow;
+ touchedWindow.windowHandle = oldWindow;
+ touchedWindow.targetFlags = InputTarget::Flags::DISPATCH_AS_HOVER_EXIT;
+ touchedWindow.pointerIds.markBit(pointerId);
+ out.push_back(touchedWindow);
+ }
+ }
+
+ for (const sp<WindowInfoHandle>& newWindow : newWindows) {
+ TouchedWindow touchedWindow;
+ touchedWindow.windowHandle = newWindow;
+ if (oldWindows.find(newWindow) == oldWindows.end()) {
+ // Any windows that have this pointer now, and didn't have it before, should get
+ // HOVER_ENTER
+ touchedWindow.targetFlags = InputTarget::Flags::DISPATCH_AS_HOVER_ENTER;
+ } else {
+ // This pointer was already sent to the window. Use ACTION_HOVER_MOVE.
+ LOG_ALWAYS_FATAL_IF(maskedAction != AMOTION_EVENT_ACTION_HOVER_MOVE);
+ touchedWindow.targetFlags = InputTarget::Flags::DISPATCH_AS_IS;
+ }
+ touchedWindow.pointerIds.markBit(pointerId);
+ if (canReceiveForegroundTouches(*newWindow->getInfo())) {
+ touchedWindow.targetFlags |= InputTarget::Flags::FOREGROUND;
+ }
+ out.push_back(touchedWindow);
+ }
+ return out;
+}
+
+template <typename T>
+std::vector<T>& operator+=(std::vector<T>& left, const std::vector<T>& right) {
+ left.insert(left.end(), right.begin(), right.end());
+ return left;
+}
+
} // namespace
// --- InputDispatcher ---
@@ -956,8 +1025,7 @@
const auto [x, y] = resolveTouchedPosition(motionEntry);
const bool isStylus = isPointerFromStylus(motionEntry, 0 /*pointerIndex*/);
- sp<WindowInfoHandle> touchedWindowHandle =
- findTouchedWindowAtLocked(displayId, x, y, nullptr, isStylus);
+ auto [touchedWindowHandle, _] = findTouchedWindowAtLocked(displayId, x, y, isStylus);
if (touchedWindowHandle != nullptr &&
touchedWindowHandle->getApplicationToken() !=
mAwaitedFocusedApplication->getApplicationToken()) {
@@ -1080,15 +1148,11 @@
}
}
-sp<WindowInfoHandle> InputDispatcher::findTouchedWindowAtLocked(int32_t displayId, int32_t x,
- int32_t y, TouchState* touchState,
- bool isStylus,
- bool addOutsideTargets,
- bool ignoreDragWindow) const {
- if (addOutsideTargets && touchState == nullptr) {
- LOG_ALWAYS_FATAL("Must provide a valid touch state if adding outside targets");
- }
+std::pair<sp<WindowInfoHandle>, std::vector<InputTarget>>
+InputDispatcher::findTouchedWindowAtLocked(int32_t displayId, int32_t x, int32_t y, bool isStylus,
+ bool ignoreDragWindow) const {
// Traverse windows from front to back to find touched window.
+ std::vector<InputTarget> outsideTargets;
const auto& windowHandles = getWindowHandlesLocked(displayId);
for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
if (ignoreDragWindow && haveSameToken(windowHandle, mDragState->dragWindow)) {
@@ -1097,16 +1161,16 @@
const WindowInfo& info = *windowHandle->getInfo();
if (!info.isSpy() && windowAcceptsTouchAt(info, displayId, x, y, isStylus)) {
- return windowHandle;
+ return {windowHandle, outsideTargets};
}
- if (addOutsideTargets &&
- info.inputConfig.test(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH)) {
- touchState->addOrUpdateWindow(windowHandle, InputTarget::Flags::DISPATCH_AS_OUTSIDE,
- BitSet32(0));
+ if (info.inputConfig.test(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH)) {
+ addWindowTargetLocked(windowHandle, InputTarget::Flags::DISPATCH_AS_OUTSIDE,
+ BitSet32(0), /*firstDownTimeInTarget=*/std::nullopt,
+ outsideTargets);
}
}
- return nullptr;
+ return {nullptr, {}};
}
std::vector<sp<WindowInfoHandle>> InputDispatcher::findTouchedSpyWindowsAtLocked(
@@ -1694,15 +1758,12 @@
pilferPointersLocked(mDragState->dragWindow->getToken());
}
- std::vector<TouchedWindow> touchedWindows =
+ inputTargets =
findTouchedWindowTargetsLocked(currentTime, *entry, &conflictingPointerActions,
/*byref*/ injectionResult);
- for (const TouchedWindow& touchedWindow : touchedWindows) {
- LOG_ALWAYS_FATAL_IF(injectionResult != InputEventInjectionResult::SUCCEEDED,
- "Shouldn't be adding window if the injection didn't succeed.");
- addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
- touchedWindow.pointerIds, touchedWindow.firstDownTimeInTarget,
- inputTargets);
+ if (injectionResult != InputEventInjectionResult::SUCCEEDED) {
+ // No events should be dispatched if the injection didn't succeed
+ inputTargets = {};
}
} else {
// Non touch event. (eg. trackball)
@@ -2075,12 +2136,12 @@
return true;
}
-std::vector<TouchedWindow> InputDispatcher::findTouchedWindowTargetsLocked(
+std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked(
nsecs_t currentTime, const MotionEntry& entry, bool* outConflictingPointerActions,
InputEventInjectionResult& outInjectionResult) {
ATRACE_CALL();
- std::vector<TouchedWindow> touchedWindows;
+ std::vector<InputTarget> targets;
// For security reasons, we defer updating the touch state until we are sure that
// event injection will be allowed.
const int32_t displayId = entry.displayId;
@@ -2089,8 +2150,6 @@
// Update the touch state as needed based on the properties of the touch event.
outInjectionResult = InputEventInjectionResult::PENDING;
- sp<WindowInfoHandle> newHoverWindowHandle(mLastHoverWindowHandle);
- sp<WindowInfoHandle> newTouchedWindowHandle;
// Copy current touch state into tempTouchState.
// This state will be used to update mTouchStatesByDisplay at the end of this function.
@@ -2109,8 +2168,12 @@
const bool isHoverAction = (maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE ||
maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER ||
maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT);
- const bool newGesture = (maskedAction == AMOTION_EVENT_ACTION_DOWN ||
- maskedAction == AMOTION_EVENT_ACTION_SCROLL || isHoverAction);
+ // A DOWN could be generated from POINTER_DOWN if the initial pointers did not land into any
+ // touchable windows.
+ const bool wasDown = oldState != nullptr && oldState->isDown();
+ const bool isDown = (maskedAction == AMOTION_EVENT_ACTION_DOWN) ||
+ (maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN && !wasDown);
+ const bool newGesture = isDown || maskedAction == AMOTION_EVENT_ACTION_SCROLL || isHoverAction;
const bool isFromMouse = isFromSource(entry.source, AINPUT_SOURCE_MOUSE);
if (newGesture) {
@@ -2121,9 +2184,9 @@
displayId);
// TODO: test multiple simultaneous input streams.
outInjectionResult = InputEventInjectionResult::FAILED;
- return touchedWindows; // wrong device
+ return targets; // wrong device
}
- tempTouchState.reset();
+ tempTouchState.clearWindowsWithoutPointers();
tempTouchState.deviceId = entry.deviceId;
tempTouchState.source = entry.source;
isSplit = false;
@@ -2133,18 +2196,28 @@
displayId);
// TODO: test multiple simultaneous input streams.
outInjectionResult = InputEventInjectionResult::FAILED;
- return touchedWindows; // wrong device
+ return targets; // wrong device
+ }
+
+ if (isHoverAction) {
+ // For hover actions, we will treat 'tempTouchState' as a new state, so let's erase
+ // all of the existing hovering pointers and recompute.
+ tempTouchState.clearHoveringPointers();
}
if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {
/* Case 1: New splittable pointer going down, or need target for hover or scroll. */
const auto [x, y] = resolveTouchedPosition(entry);
const int32_t pointerIndex = getMotionEventActionPointerIndex(action);
- const bool isDown = maskedAction == AMOTION_EVENT_ACTION_DOWN;
+ // Outside targets should be added upon first dispatched DOWN event. That means, this should
+ // be a pointer that would generate ACTION_DOWN, *and* touch should not already be down.
const bool isStylus = isPointerFromStylus(entry, pointerIndex);
- newTouchedWindowHandle = findTouchedWindowAtLocked(displayId, x, y, &tempTouchState,
- isStylus, isDown /*addOutsideTargets*/);
+ auto [newTouchedWindowHandle, outsideTargets] =
+ findTouchedWindowAtLocked(displayId, x, y, isStylus);
+ if (isDown) {
+ targets += outsideTargets;
+ }
// Handle the case where we did not find a window.
if (newTouchedWindowHandle == nullptr) {
ALOGD("No new touched window at (%" PRId32 ", %" PRId32 ") in display %" PRId32, x, y,
@@ -2178,15 +2251,6 @@
isSplit = !isFromMouse;
}
- // Update hover state.
- if (newTouchedWindowHandle != nullptr) {
- if (maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT) {
- newHoverWindowHandle = nullptr;
- } else if (isHoverAction) {
- newHoverWindowHandle = newTouchedWindowHandle;
- }
- }
-
std::vector<sp<WindowInfoHandle>> newTouchedWindows =
findTouchedSpyWindowsAtLocked(displayId, x, y, isStylus);
if (newTouchedWindowHandle != nullptr) {
@@ -2206,6 +2270,18 @@
continue;
}
+ if (isHoverAction) {
+ const int32_t pointerId = entry.pointerProperties[0].id;
+ if (maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT) {
+ // Pointer left. Remove it
+ tempTouchState.removeHoveringPointer(entry.deviceId, pointerId);
+ } else {
+ // The "windowHandle" is the target of this hovering pointer.
+ tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId,
+ pointerId);
+ }
+ }
+
// Set target flags.
ftl::Flags<InputTarget::Flags> targetFlags = InputTarget::Flags::DISPATCH_AS_IS;
@@ -2225,7 +2301,9 @@
// Update the temporary touch state.
BitSet32 pointerIds;
- pointerIds.markBit(entry.pointerProperties[pointerIndex].id);
+ if (!isHoverAction) {
+ pointerIds.markBit(entry.pointerProperties[pointerIndex].id);
+ }
tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, pointerIds,
entry.eventTime);
@@ -2287,14 +2365,12 @@
const bool isStylus = isPointerFromStylus(entry, 0 /*pointerIndex*/);
sp<WindowInfoHandle> oldTouchedWindowHandle =
tempTouchState.getFirstForegroundWindowHandle();
- newTouchedWindowHandle =
- findTouchedWindowAtLocked(displayId, x, y, &tempTouchState, isStylus);
+ auto [newTouchedWindowHandle, _] = findTouchedWindowAtLocked(displayId, x, y, isStylus);
// Verify targeted injection.
if (const auto err = verifyTargetedInjection(newTouchedWindowHandle, entry); err) {
ALOGW("Dropping injected event: %s", (*err).c_str());
outInjectionResult = os::InputEventInjectionResult::TARGET_MISMATCH;
- newTouchedWindowHandle = nullptr;
goto Failed;
}
@@ -2312,9 +2388,15 @@
newTouchedWindowHandle->getName().c_str(), displayId);
}
// Make a slippery exit from the old window.
- tempTouchState.addOrUpdateWindow(oldTouchedWindowHandle,
- InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT,
- BitSet32(0));
+ BitSet32 pointerIds;
+ const int32_t pointerId = entry.pointerProperties[0].id;
+ pointerIds.markBit(pointerId);
+
+ const TouchedWindow& touchedWindow =
+ tempTouchState.getTouchedWindow(oldTouchedWindowHandle);
+ addWindowTargetLocked(oldTouchedWindowHandle,
+ InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT, pointerIds,
+ touchedWindow.firstDownTimeInTarget, targets);
// Make a slippery entrance into the new window.
if (newTouchedWindowHandle->getInfo()->supportsSplitTouch()) {
@@ -2335,14 +2417,13 @@
targetFlags |= InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED;
}
- BitSet32 pointerIds;
- 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);
+ tempTouchState, pointerId, targets);
+ tempTouchState.removeTouchedPointerFromWindow(pointerId, oldTouchedWindowHandle);
}
}
@@ -2362,36 +2443,15 @@
}
// Update dispatching for hover enter and exit.
- if (newHoverWindowHandle != mLastHoverWindowHandle) {
- // Let the previous window know that the hover sequence is over, unless we already did
- // it when dispatching it as is to newTouchedWindowHandle.
- if (mLastHoverWindowHandle != nullptr &&
- (maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT ||
- mLastHoverWindowHandle != newTouchedWindowHandle)) {
- if (DEBUG_HOVER) {
- ALOGD("Sending hover exit event to window %s.",
- mLastHoverWindowHandle->getName().c_str());
- }
- tempTouchState.addOrUpdateWindow(mLastHoverWindowHandle,
- InputTarget::Flags::DISPATCH_AS_HOVER_EXIT,
- BitSet32(0));
- }
-
- // Let the new window know that the hover sequence is starting, unless we already did it
- // when dispatching it as is to newTouchedWindowHandle.
- if (newHoverWindowHandle != nullptr &&
- (maskedAction != AMOTION_EVENT_ACTION_HOVER_ENTER ||
- newHoverWindowHandle != newTouchedWindowHandle)) {
- if (DEBUG_HOVER) {
- ALOGD("Sending hover enter event to window %s.",
- newHoverWindowHandle->getName().c_str());
- }
- tempTouchState.addOrUpdateWindow(newHoverWindowHandle,
- InputTarget::Flags::DISPATCH_AS_HOVER_ENTER,
- BitSet32(0));
+ {
+ std::vector<TouchedWindow> hoveringWindows =
+ getHoveringWindowsLocked(oldState, tempTouchState, entry);
+ for (const TouchedWindow& touchedWindow : hoveringWindows) {
+ addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
+ touchedWindow.pointerIds, touchedWindow.firstDownTimeInTarget,
+ targets);
}
}
-
// Ensure that we have at least one foreground window or at least one window that cannot be a
// foreground target. If we only have windows that are not receiving foreground touches (e.g. we
// only have windows getting ACTION_OUTSIDE), then drop the event, because there is no window
@@ -2429,30 +2489,34 @@
}
}
- // Check whether windows listening for outside touches are owned by the same UID. If it is
- // set the policy flag that we will not reveal coordinate information to this window.
+ // Check whether windows listening for outside touches are owned by the same UID. If the owner
+ // has a different UID, then we will not reveal coordinate information to this window.
if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
sp<WindowInfoHandle> foregroundWindowHandle =
tempTouchState.getFirstForegroundWindowHandle();
if (foregroundWindowHandle) {
const int32_t foregroundWindowUid = foregroundWindowHandle->getInfo()->ownerUid;
- for (const TouchedWindow& touchedWindow : tempTouchState.windows) {
- if (touchedWindow.targetFlags.test(InputTarget::Flags::DISPATCH_AS_OUTSIDE)) {
- sp<WindowInfoHandle> windowInfoHandle = touchedWindow.windowHandle;
- if (windowInfoHandle->getInfo()->ownerUid != foregroundWindowUid) {
- tempTouchState.addOrUpdateWindow(windowInfoHandle,
- InputTarget::Flags::ZERO_COORDS,
- BitSet32(0));
+ for (InputTarget& target : targets) {
+ if (target.flags.test(InputTarget::Flags::DISPATCH_AS_OUTSIDE)) {
+ sp<WindowInfoHandle> targetWindow =
+ getWindowHandleLocked(target.inputChannel->getConnectionToken());
+ if (targetWindow->getInfo()->ownerUid != foregroundWindowUid) {
+ target.flags |= InputTarget::Flags::ZERO_COORDS;
}
}
}
}
}
- // Success! Output targets.
- touchedWindows = tempTouchState.windows;
- outInjectionResult = InputEventInjectionResult::SUCCEEDED;
+ // Success! Output targets from the touch state.
+ tempTouchState.clearWindowsWithoutPointers();
+ for (const TouchedWindow& touchedWindow : tempTouchState.windows) {
+ addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
+ touchedWindow.pointerIds, touchedWindow.firstDownTimeInTarget,
+ targets);
+ }
+ outInjectionResult = InputEventInjectionResult::SUCCEEDED;
// Drop the outside or hover touch windows since we will not care about them
// in the next iteration.
tempTouchState.filterNonAsIsTouchWindows();
@@ -2473,14 +2537,16 @@
"Conflicting pointer actions: Hover received while pointer was down.");
*outConflictingPointerActions = true;
}
- tempTouchState.reset();
if (maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER ||
maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) {
tempTouchState.deviceId = entry.deviceId;
tempTouchState.source = entry.source;
}
- } else if (maskedAction == AMOTION_EVENT_ACTION_UP ||
- maskedAction == AMOTION_EVENT_ACTION_CANCEL) {
+ } else if (maskedAction == AMOTION_EVENT_ACTION_UP) {
+ // Pointer went up.
+ tempTouchState.removeTouchedPointer(entry.pointerProperties[0].id);
+ tempTouchState.clearWindowsWithoutPointers();
+ } else if (maskedAction == AMOTION_EVENT_ACTION_CANCEL) {
// All pointers up or canceled.
tempTouchState.reset();
} else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
@@ -2519,10 +2585,7 @@
mTouchStatesByDisplay.erase(displayId);
}
- // Update hover state.
- mLastHoverWindowHandle = newHoverWindowHandle;
-
- return touchedWindows;
+ return targets;
}
void InputDispatcher::finishDragAndDrop(int32_t displayId, float x, float y) {
@@ -2530,9 +2593,8 @@
// have an explicit reason to support it.
constexpr bool isStylus = false;
- const sp<WindowInfoHandle> dropWindow =
- findTouchedWindowAtLocked(displayId, x, y, nullptr /*touchState*/, isStylus,
- false /*addOutsideTargets*/, true /*ignoreDragWindow*/);
+ auto [dropWindow, _] =
+ findTouchedWindowAtLocked(displayId, x, y, isStylus, true /*ignoreDragWindow*/);
if (dropWindow) {
vec2 local = dropWindow->getInfo()->transform.transform(x, y);
sendDropWindowCommandLocked(dropWindow->getToken(), local.x, local.y);
@@ -2588,10 +2650,8 @@
// until we have an explicit reason to support it.
constexpr bool isStylus = false;
- const sp<WindowInfoHandle> hoverWindowHandle =
- findTouchedWindowAtLocked(entry.displayId, x, y, nullptr /*touchState*/,
- isStylus, false /*addOutsideTargets*/,
- true /*ignoreDragWindow*/);
+ auto [hoverWindowHandle, _] = findTouchedWindowAtLocked(entry.displayId, x, y, isStylus,
+ true /*ignoreDragWindow*/);
// enqueue drag exit if needed.
if (hoverWindowHandle != mDragState->dragHoverWindowHandle &&
!haveSameToken(hoverWindowHandle, mDragState->dragHoverWindowHandle)) {
@@ -3333,6 +3393,10 @@
case EventEntry::Type::KEY: {
const KeyEntry& keyEntry = static_cast<const KeyEntry&>(eventEntry);
std::array<uint8_t, 32> hmac = getSignature(keyEntry, *dispatchEntry);
+ if (DEBUG_OUTBOUND_EVENT_DETAILS) {
+ LOG(DEBUG) << "Publishing " << *dispatchEntry << " to "
+ << connection->getInputChannelName();
+ }
// Publish the key event.
status = connection->inputPublisher
@@ -3348,6 +3412,10 @@
}
case EventEntry::Type::MOTION: {
+ if (DEBUG_OUTBOUND_EVENT_DETAILS) {
+ LOG(DEBUG) << "Publishing " << *dispatchEntry << " to "
+ << connection->getInputChannelName();
+ }
status = publishMotionEvent(*connection, *dispatchEntry);
break;
}
@@ -4399,6 +4467,9 @@
bool needWake = false;
while (!injectedEntries.empty()) {
+ if (DEBUG_INJECTION) {
+ LOG(DEBUG) << "Injecting " << injectedEntries.front()->getDescription();
+ }
needWake |= enqueueInboundEventLocked(std::move(injectedEntries.front()));
injectedEntries.pop();
}
@@ -4461,7 +4532,8 @@
} // release lock
if (DEBUG_INJECTION) {
- ALOGD("injectInputEvent - Finished with result %d.", injectionResult);
+ LOG(DEBUG) << "injectInputEvent - Finished with result "
+ << ftl::enum_string(injectionResult);
}
return injectionResult;
@@ -4505,7 +4577,8 @@
InjectionState* injectionState = entry.injectionState;
if (injectionState) {
if (DEBUG_INJECTION) {
- ALOGD("Setting input event injection result to %d.", injectionResult);
+ LOG(DEBUG) << "Setting input event injection result to "
+ << ftl::enum_string(injectionResult);
}
if (injectionState->injectionIsAsync && !(entry.policyFlags & POLICY_FLAG_FILTERED)) {
@@ -4824,14 +4897,6 @@
updateWindowHandlesForDisplayLocked(windowInfoHandles, displayId);
const std::vector<sp<WindowInfoHandle>>& windowHandles = getWindowHandlesLocked(displayId);
- if (mLastHoverWindowHandle) {
- const WindowInfo* lastHoverWindowInfo = mLastHoverWindowHandle->getInfo();
- if (lastHoverWindowInfo->displayId == displayId &&
- std::find(windowHandles.begin(), windowHandles.end(), mLastHoverWindowHandle) ==
- windowHandles.end()) {
- mLastHoverWindowHandle = nullptr;
- }
- }
std::optional<FocusResolver::FocusChanges> changes =
mFocusResolver.setInputWindows(displayId, windowHandles);
@@ -5278,7 +5343,6 @@
mAnrTracker.clear();
mTouchStatesByDisplay.clear();
- mLastHoverWindowHandle.clear();
mReplacedKeys.clear();
}
@@ -6468,7 +6532,6 @@
synthesizeCancelationEventsForAllConnectionsLocked(options);
mTouchStatesByDisplay.clear();
- mLastHoverWindowHandle.clear();
}
// Wake up poll loop since there might be work to do.
mLooper->wake();
@@ -6482,7 +6545,10 @@
void InputDispatcher::slipWallpaperTouch(ftl::Flags<InputTarget::Flags> targetFlags,
const sp<WindowInfoHandle>& oldWindowHandle,
const sp<WindowInfoHandle>& newWindowHandle,
- TouchState& state, const BitSet32& pointerIds) {
+ TouchState& state, int32_t pointerId,
+ std::vector<InputTarget>& targets) {
+ BitSet32 pointerIds;
+ pointerIds.markBit(pointerId);
const bool oldHasWallpaper = oldWindowHandle->getInfo()->inputConfig.test(
gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
const bool newHasWallpaper = targetFlags.test(InputTarget::Flags::FOREGROUND) &&
@@ -6497,8 +6563,12 @@
}
if (oldWallpaper != nullptr) {
- state.addOrUpdateWindow(oldWallpaper, InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT,
- BitSet32(0));
+ const TouchedWindow& oldTouchedWindow = state.getTouchedWindow(oldWallpaper);
+ addWindowTargetLocked(oldWallpaper,
+ oldTouchedWindow.targetFlags |
+ InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT,
+ pointerIds, oldTouchedWindow.firstDownTimeInTarget, targets);
+ state.removeTouchedPointerFromWindow(pointerId, oldWallpaper);
}
if (newWallpaper != nullptr) {
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index a32ebd3..81f8de8 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -237,9 +237,9 @@
// to transfer focus to a new application.
std::shared_ptr<EventEntry> mNextUnblockedEvent GUARDED_BY(mLock);
- sp<android::gui::WindowInfoHandle> findTouchedWindowAtLocked(
- int32_t displayId, int32_t x, int32_t y, TouchState* touchState, bool isStylus = false,
- bool addOutsideTargets = false, bool ignoreDragWindow = false) const REQUIRES(mLock);
+ std::pair<sp<android::gui::WindowInfoHandle>, std::vector<InputTarget>>
+ findTouchedWindowAtLocked(int32_t displayId, int32_t x, int32_t y, bool isStylus = false,
+ bool ignoreDragWindow = false) const REQUIRES(mLock);
std::vector<sp<android::gui::WindowInfoHandle>> findTouchedSpyWindowsAtLocked(
int32_t displayId, int32_t x, int32_t y, bool isStylus) const REQUIRES(mLock);
@@ -530,9 +530,6 @@
// prevent unneeded wakeups.
AnrTracker mAnrTracker GUARDED_BY(mLock);
- // Contains the last window which received a hover event.
- sp<android::gui::WindowInfoHandle> mLastHoverWindowHandle GUARDED_BY(mLock);
-
void cancelEventsForAnrLocked(const sp<Connection>& connection) REQUIRES(mLock);
// If a focused application changes, we should stop counting down the "no focused window" time,
// because we will have no way of knowing when the previous application actually added a window.
@@ -546,7 +543,7 @@
sp<android::gui::WindowInfoHandle> findFocusedWindowTargetLocked(
nsecs_t currentTime, const EventEntry& entry, nsecs_t* nextWakeupTime,
android::os::InputEventInjectionResult& outInjectionResult) REQUIRES(mLock);
- std::vector<TouchedWindow> findTouchedWindowTargetsLocked(
+ std::vector<InputTarget> findTouchedWindowTargetsLocked(
nsecs_t currentTime, const MotionEntry& entry, bool* outConflictingPointerActions,
android::os::InputEventInjectionResult& outInjectionResult) REQUIRES(mLock);
std::vector<Monitor> selectResponsiveMonitorsLocked(
@@ -700,7 +697,8 @@
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);
+ TouchState& state, int32_t pointerId, std::vector<InputTarget>& targets)
+ REQUIRES(mLock);
void transferWallpaperTouch(ftl::Flags<InputTarget::Flags> oldTargetFlags,
ftl::Flags<InputTarget::Flags> newTargetFlags,
const sp<android::gui::WindowInfoHandle> fromWindowHandle,
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index c21af9e..ad37d02 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -31,10 +31,40 @@
*this = TouchState();
}
+void TouchState::removeTouchedPointer(int32_t pointerId) {
+ for (TouchedWindow& touchedWindow : windows) {
+ touchedWindow.pointerIds.clearBit(pointerId);
+ }
+}
+
+void TouchState::removeTouchedPointerFromWindow(
+ int32_t pointerId, const sp<android::gui::WindowInfoHandle>& windowHandle) {
+ for (TouchedWindow& touchedWindow : windows) {
+ if (touchedWindow.windowHandle == windowHandle) {
+ touchedWindow.pointerIds.clearBit(pointerId);
+ return;
+ }
+ }
+}
+
+void TouchState::clearHoveringPointers() {
+ for (TouchedWindow& touchedWindow : windows) {
+ touchedWindow.clearHoveringPointers();
+ }
+}
+
+void TouchState::clearWindowsWithoutPointers() {
+ std::erase_if(windows, [](const TouchedWindow& w) {
+ return w.pointerIds.isEmpty() && !w.hasHoveringPointers();
+ });
+}
+
void TouchState::addOrUpdateWindow(const sp<WindowInfoHandle>& windowHandle,
ftl::Flags<InputTarget::Flags> targetFlags, BitSet32 pointerIds,
std::optional<nsecs_t> eventTime) {
for (TouchedWindow& touchedWindow : windows) {
+ // We do not compare windows by token here because two windows that share the same token
+ // may have a different transform
if (touchedWindow.windowHandle == windowHandle) {
touchedWindow.targetFlags |= targetFlags;
if (targetFlags.test(InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT)) {
@@ -50,7 +80,6 @@
return;
}
}
-
TouchedWindow touchedWindow;
touchedWindow.windowHandle = windowHandle;
touchedWindow.targetFlags = targetFlags;
@@ -59,6 +88,21 @@
windows.push_back(touchedWindow);
}
+void TouchState::addHoveringPointerToWindow(const sp<WindowInfoHandle>& windowHandle,
+ int32_t hoveringDeviceId, int32_t hoveringPointerId) {
+ for (TouchedWindow& touchedWindow : windows) {
+ if (touchedWindow.windowHandle == windowHandle) {
+ touchedWindow.addHoveringPointer(hoveringDeviceId, hoveringPointerId);
+ return;
+ }
+ }
+
+ TouchedWindow touchedWindow;
+ touchedWindow.windowHandle = windowHandle;
+ touchedWindow.addHoveringPointer(hoveringDeviceId, hoveringPointerId);
+ windows.push_back(touchedWindow);
+}
+
void TouchState::removeWindowByToken(const sp<IBinder>& token) {
for (size_t i = 0; i < windows.size(); i++) {
if (windows[i].windowHandle->getToken() == token) {
@@ -140,11 +184,38 @@
return nullptr;
}
+const TouchedWindow& TouchState::getTouchedWindow(const sp<WindowInfoHandle>& windowHandle) const {
+ auto it = std::find_if(windows.begin(), windows.end(),
+ [&](const TouchedWindow& w) { return w.windowHandle == windowHandle; });
+ LOG_ALWAYS_FATAL_IF(it == windows.end(), "Could not find %s", windowHandle->getName().c_str());
+ return *it;
+}
+
bool TouchState::isDown() const {
return std::any_of(windows.begin(), windows.end(),
[](const TouchedWindow& window) { return !window.pointerIds.isEmpty(); });
}
+std::set<sp<WindowInfoHandle>> TouchState::getWindowsWithHoveringPointer(int32_t hoveringDeviceId,
+ int32_t pointerId) const {
+ std::set<sp<WindowInfoHandle>> out;
+ for (const TouchedWindow& window : windows) {
+ if (window.hasHoveringPointer(hoveringDeviceId, pointerId)) {
+ out.insert(window.windowHandle);
+ }
+ }
+ return out;
+}
+
+void TouchState::removeHoveringPointer(int32_t hoveringDeviceId, int32_t hoveringPointerId) {
+ for (TouchedWindow& window : windows) {
+ window.removeHoveringPointer(hoveringDeviceId, hoveringPointerId);
+ }
+ std::erase_if(windows, [](const TouchedWindow& w) {
+ return w.pointerIds.isEmpty() && !w.hasHoveringPointers();
+ });
+}
+
std::string TouchState::dump() const {
std::string out;
out += StringPrintf("deviceId=%d, source=%s\n", deviceId,
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index 77c1cdf..4025db8 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -16,6 +16,7 @@
#pragma once
+#include <set>
#include "TouchedWindow.h"
namespace android {
@@ -39,9 +40,18 @@
TouchState& operator=(const TouchState&) = default;
void reset();
+ void clearWindowsWithoutPointers();
+
+ void removeTouchedPointer(int32_t pointerId);
+ void removeTouchedPointerFromWindow(int32_t pointerId,
+ const sp<android::gui::WindowInfoHandle>& windowHandle);
void addOrUpdateWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
ftl::Flags<InputTarget::Flags> targetFlags, BitSet32 pointerIds,
std::optional<nsecs_t> eventTime = std::nullopt);
+ void addHoveringPointerToWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
+ int32_t deviceId, int32_t hoveringPointerId);
+ void removeHoveringPointer(int32_t deviceId, int32_t hoveringPointerId);
+ void clearHoveringPointers();
void removeWindowByToken(const sp<IBinder>& token);
void filterNonAsIsTouchWindows();
@@ -54,8 +64,13 @@
sp<android::gui::WindowInfoHandle> getFirstForegroundWindowHandle() const;
bool isSlippery() const;
sp<android::gui::WindowInfoHandle> getWallpaperWindow() const;
+ const TouchedWindow& getTouchedWindow(
+ const sp<android::gui::WindowInfoHandle>& windowHandle) const;
// Whether any of the windows are currently being touched
bool isDown() const;
+
+ std::set<sp<android::gui::WindowInfoHandle>> getWindowsWithHoveringPointer(
+ int32_t deviceId, int32_t pointerId) const;
std::string dump() const;
};
diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp
index af74598..3704edd 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.cpp
+++ b/services/inputflinger/dispatcher/TouchedWindow.cpp
@@ -25,11 +25,49 @@
namespace inputdispatcher {
+bool TouchedWindow::hasHoveringPointers() const {
+ return !mHoveringPointerIdsByDevice.empty();
+}
+
+void TouchedWindow::clearHoveringPointers() {
+ mHoveringPointerIdsByDevice.clear();
+}
+
+bool TouchedWindow::hasHoveringPointer(int32_t deviceId, int32_t pointerId) const {
+ auto it = mHoveringPointerIdsByDevice.find(deviceId);
+ if (it == mHoveringPointerIdsByDevice.end()) {
+ return false;
+ }
+ return it->second.test(pointerId);
+}
+
+void TouchedWindow::addHoveringPointer(int32_t deviceId, int32_t pointerId) {
+ const auto [it, _] = mHoveringPointerIdsByDevice.insert({deviceId, {}});
+ it->second.set(pointerId);
+}
+
+void TouchedWindow::removeHoveringPointer(int32_t deviceId, int32_t pointerId) {
+ const auto it = mHoveringPointerIdsByDevice.find(deviceId);
+ if (it == mHoveringPointerIdsByDevice.end()) {
+ return;
+ }
+ it->second.set(pointerId, false);
+
+ if (it->second.none()) {
+ mHoveringPointerIdsByDevice.erase(deviceId);
+ }
+}
+
std::string TouchedWindow::dump() const {
- return StringPrintf("name='%s', pointerIds=0x%0x, "
- "targetFlags=%s, firstDownTimeInTarget=%s\n",
+ std::string out;
+ std::string hoveringPointers =
+ dumpMap(mHoveringPointerIdsByDevice, constToString, bitsetToString);
+ out += StringPrintf("name='%s', pointerIds=0x%0x, targetFlags=%s, firstDownTimeInTarget=%s, "
+ "mHoveringPointerIdsByDevice=%s\n",
windowHandle->getName().c_str(), pointerIds.value,
- targetFlags.string().c_str(), toString(firstDownTimeInTarget).c_str());
+ targetFlags.string().c_str(), toString(firstDownTimeInTarget).c_str(),
+ hoveringPointers.c_str());
+ return out;
}
} // namespace inputdispatcher
diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h
index dd08323..add6b61 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.h
+++ b/services/inputflinger/dispatcher/TouchedWindow.h
@@ -17,7 +17,9 @@
#pragma once
#include <gui/WindowInfo.h>
+#include <input/Input.h>
#include <utils/BitSet.h>
+#include <bitset>
#include "InputTarget.h"
namespace android {
@@ -33,7 +35,17 @@
// Time at which the first action down occurred on this window.
// NOTE: This is not initialized in case of HOVER entry/exit and DISPATCH_AS_OUTSIDE scenario.
std::optional<nsecs_t> firstDownTimeInTarget;
+
+ bool hasHoveringPointers() const;
+
+ bool hasHoveringPointer(int32_t deviceId, int32_t pointerId) const;
+ void addHoveringPointer(int32_t deviceId, int32_t pointerId);
+ void removeHoveringPointer(int32_t deviceId, int32_t pointerId);
+ void clearHoveringPointers();
std::string dump() const;
+
+private:
+ std::map<int32_t /*deviceId*/, std::bitset<MAX_POINTERS>> mHoveringPointerIdsByDevice;
};
} // namespace inputdispatcher
diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp
index f3b680b..d29692c 100644
--- a/services/inputflinger/reader/Android.bp
+++ b/services/inputflinger/reader/Android.bp
@@ -64,6 +64,7 @@
"mapper/gestures/GestureConverter.cpp",
"mapper/gestures/GesturesLogging.cpp",
"mapper/gestures/HardwareStateConverter.cpp",
+ "mapper/gestures/PropertyProvider.cpp",
],
}
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index 43b67ca..f7b38a1 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -515,6 +515,18 @@
return deviceClasses & InputDeviceClass::JOYSTICK;
}
+// --- RawAbsoluteAxisInfo ---
+
+std::ostream& operator<<(std::ostream& out, const RawAbsoluteAxisInfo& info) {
+ if (info.valid) {
+ out << "min=" << info.minValue << ", max=" << info.maxValue << ", flat=" << info.flat
+ << ", fuzz=" << info.fuzz << ", resolution=" << info.resolution;
+ } else {
+ out << "unknown range";
+ }
+ return out;
+}
+
// --- EventHub::Device ---
EventHub::Device::Device(int fd, int32_t id, std::string path, InputDeviceIdentifier identifier,
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index 6e78e82..bd41fa5 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -210,8 +210,14 @@
// Touchscreens and touchpad devices.
static const bool ENABLE_TOUCHPAD_GESTURES_LIBRARY =
sysprop::InputProperties::enable_touchpad_gestures_library().value_or(false);
+ // TODO(b/246587538): Fix the new touchpad stack for Sony DualShock 4 (5c4, 9cc) and DualSense
+ // (ce6) touchpads, or at least load this setting from the IDC file.
+ const InputDeviceIdentifier& identifier = contextPtr->getDeviceIdentifier();
+ const bool isSonyGamepadTouchpad = identifier.vendor == 0x054c &&
+ (identifier.product == 0x05c4 || identifier.product == 0x09cc ||
+ identifier.product == 0x0ce6);
if (ENABLE_TOUCHPAD_GESTURES_LIBRARY && classes.test(InputDeviceClass::TOUCHPAD) &&
- classes.test(InputDeviceClass::TOUCH_MT)) {
+ classes.test(InputDeviceClass::TOUCH_MT) && !isSonyGamepadTouchpad) {
mappers.push_back(std::make_unique<TouchpadInputMapper>(*contextPtr));
} else if (classes.test(InputDeviceClass::TOUCH_MT)) {
mappers.push_back(std::make_unique<MultiTouchInputMapper>(*contextPtr));
@@ -452,7 +458,8 @@
InputDeviceInfo InputDevice::getDeviceInfo() {
InputDeviceInfo outDeviceInfo;
outDeviceInfo.initialize(mId, mGeneration, mControllerNumber, mIdentifier, mAlias, mIsExternal,
- mHasMic);
+ mHasMic, getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE));
+
for_each_mapper(
[&outDeviceInfo](InputMapper& mapper) { mapper.populateDeviceInfo(&outDeviceInfo); });
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index a3ecf41..86acadb 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -19,6 +19,8 @@
#include <bitset>
#include <climits>
#include <filesystem>
+#include <ostream>
+#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
@@ -77,6 +79,8 @@
inline void clear() { *this = RawAbsoluteAxisInfo(); }
};
+std::ostream& operator<<(std::ostream& out, const RawAbsoluteAxisInfo& info);
+
/*
* Input device classes.
*/
diff --git a/services/inputflinger/reader/mapper/InputMapper.cpp b/services/inputflinger/reader/mapper/InputMapper.cpp
index 8e3539c..ba2ea99 100644
--- a/services/inputflinger/reader/mapper/InputMapper.cpp
+++ b/services/inputflinger/reader/mapper/InputMapper.cpp
@@ -18,6 +18,8 @@
#include "InputMapper.h"
+#include <sstream>
+
#include "InputDevice.h"
#include "input/PrintTools.h"
@@ -120,12 +122,9 @@
void InputMapper::dumpRawAbsoluteAxisInfo(std::string& dump, const RawAbsoluteAxisInfo& axis,
const char* name) {
- if (axis.valid) {
- dump += StringPrintf(INDENT4 "%s: min=%d, max=%d, flat=%d, fuzz=%d, resolution=%d\n", name,
- axis.minValue, axis.maxValue, axis.flat, axis.fuzz, axis.resolution);
- } else {
- dump += StringPrintf(INDENT4 "%s: unknown range\n", name);
- }
+ std::stringstream out;
+ out << INDENT4 << name << ": " << axis << "\n";
+ dump += out.str();
}
void InputMapper::dumpStylusState(std::string& dump, const StylusState& state) {
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 8cd2cf0..0c57628 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -55,6 +55,10 @@
return p.x >= rect.left && p.x < rect.right && p.y >= rect.top && p.y < rect.bottom;
}
+static std::string toString(const InputDeviceUsiVersion& v) {
+ return base::StringPrintf("%d.%d", v.majorVersion, v.minorVersion);
+}
+
template <typename T>
inline static void swap(T& a, T& b) {
T temp = a;
@@ -188,7 +192,7 @@
info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f);
}
info->setButtonUnderPad(mParameters.hasButtonUnderPad);
- info->setSupportsUsi(mParameters.supportsUsi);
+ info->setUsiVersion(mParameters.usiVersion);
}
void TouchInputMapper::dump(std::string& dump) {
@@ -421,9 +425,13 @@
mParameters.wake = getDeviceContext().isExternal();
getDeviceContext().getConfiguration().tryGetProperty("touch.wake", mParameters.wake);
- mParameters.supportsUsi = false;
- getDeviceContext().getConfiguration().tryGetProperty("touch.supportsUsi",
- mParameters.supportsUsi);
+ InputDeviceUsiVersion usiVersion;
+ if (getDeviceContext().getConfiguration().tryGetProperty("touch.usiVersionMajor",
+ usiVersion.majorVersion) &&
+ getDeviceContext().getConfiguration().tryGetProperty("touch.usiVersionMinor",
+ usiVersion.minorVersion)) {
+ mParameters.usiVersion = usiVersion;
+ }
mParameters.enableForInactiveViewport = false;
getDeviceContext().getConfiguration().tryGetProperty("touch.enableForInactiveViewport",
@@ -472,7 +480,8 @@
mParameters.uniqueDisplayId.c_str());
dump += StringPrintf(INDENT4 "OrientationAware: %s\n", toString(mParameters.orientationAware));
dump += INDENT4 "Orientation: " + ftl::enum_string(mParameters.orientation) + "\n";
- dump += StringPrintf(INDENT4 "SupportsUsi: %s\n", toString(mParameters.supportsUsi));
+ dump += StringPrintf(INDENT4 "UsiVersion: %s\n",
+ toString(mParameters.usiVersion, toString).c_str());
dump += StringPrintf(INDENT4 "EnableForInactiveViewport: %s\n",
toString(mParameters.enableForInactiveViewport));
}
@@ -875,8 +884,6 @@
mDeviceMode = DeviceMode::POINTER;
if (hasStylus()) {
mSource |= AINPUT_SOURCE_STYLUS;
- } else {
- mSource |= AINPUT_SOURCE_TOUCHPAD;
}
} else if (isTouchScreen()) {
mSource = AINPUT_SOURCE_TOUCHSCREEN;
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index 6e35b46..87deb39 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -234,8 +234,8 @@
bool wake;
- // Whether the device supports the Universal Stylus Initiative (USI) protocol for styluses.
- bool supportsUsi;
+ // The Universal Stylus Initiative (USI) protocol version supported by this device.
+ std::optional<InputDeviceUsiVersion> usiVersion;
// Allows touches while the display is off.
bool enableForInactiveViewport;
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index 3b51be8..b6313a1 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -19,6 +19,7 @@
#include <optional>
#include <android/input.h>
+#include <input/PrintTools.h>
#include <linux/input-event-codes.h>
#include <log/log_main.h>
#include "TouchCursorInputMapperCommon.h"
@@ -96,11 +97,12 @@
mGestureInterpreter->Initialize(GESTURES_DEVCLASS_TOUCHPAD);
mGestureInterpreter->SetHardwareProperties(createHardwareProperties(deviceContext));
// Even though we don't explicitly delete copy/move semantics, it's safe to
- // give away a pointer to TouchpadInputMapper here because
+ // give away pointers to TouchpadInputMapper and its members here because
// 1) mGestureInterpreter's lifecycle is determined by TouchpadInputMapper, and
// 2) TouchpadInputMapper is stored as a unique_ptr and not moved.
+ mGestureInterpreter->SetPropProvider(const_cast<GesturesPropProvider*>(&gesturePropProvider),
+ &mPropertyProvider);
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.
}
@@ -108,12 +110,29 @@
if (mPointerController != nullptr) {
mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
}
+
+ // The gesture interpreter's destructor will call its property provider's free function for all
+ // gesture properties, in this case calling PropertyProvider::freeProperty using a raw pointer
+ // to mPropertyProvider. Depending on the declaration order in TouchpadInputMapper.h, this may
+ // happen after mPropertyProvider has been destructed, causing allocation errors. Depending on
+ // declaration order to avoid crashes seems rather fragile, so explicitly clear the property
+ // provider here to ensure all the freeProperty calls happen before mPropertyProvider is
+ // destructed.
+ mGestureInterpreter->SetPropProvider(nullptr, nullptr);
}
uint32_t TouchpadInputMapper::getSources() const {
return AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD;
}
+void TouchpadInputMapper::dump(std::string& dump) {
+ dump += INDENT2 "Touchpad Input Mapper:\n";
+ dump += INDENT3 "Gesture converter:\n";
+ dump += addLinePrefix(mGestureConverter.dump(), INDENT4);
+ dump += INDENT3 "Gesture properties:\n";
+ dump += addLinePrefix(mPropertyProvider.dump(), INDENT4);
+}
+
std::list<NotifyArgs> TouchpadInputMapper::configure(nsecs_t when,
const InputReaderConfiguration* config,
uint32_t changes) {
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
index 3a92211..d693bca 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
@@ -29,6 +29,7 @@
#include "NotifyArgs.h"
#include "gestures/GestureConverter.h"
#include "gestures/HardwareStateConverter.h"
+#include "gestures/PropertyProvider.h"
#include "include/gestures.h"
@@ -40,6 +41,8 @@
~TouchpadInputMapper();
uint32_t getSources() const override;
+ void dump(std::string& dump) override;
+
[[nodiscard]] std::list<NotifyArgs> configure(nsecs_t when,
const InputReaderConfiguration* config,
uint32_t changes) override;
@@ -57,6 +60,8 @@
mGestureInterpreter;
std::shared_ptr<PointerControllerInterface> mPointerController;
+ PropertyProvider mPropertyProvider;
+
HardwareStateConverter mStateConverter;
GestureConverter mGestureConverter;
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
index 11ffd28..561b1f8 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
@@ -16,7 +16,11 @@
#include "gestures/GestureConverter.h"
+#include <sstream>
+
+#include <android-base/stringprintf.h>
#include <android/input.h>
+#include <ftl/enum.h>
#include <linux/input-event-codes.h>
#include <log/log_main.h>
@@ -55,6 +59,18 @@
deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &mYAxisInfo);
}
+std::string GestureConverter::dump() const {
+ std::stringstream out;
+ out << "Orientation: " << ftl::enum_string(mOrientation) << "\n";
+ out << "Axis info:\n";
+ out << " X: " << mXAxisInfo << "\n";
+ out << " Y: " << mYAxisInfo << "\n";
+ out << StringPrintf("Button state: 0x%08x\n", mButtonState);
+ out << "Down time: " << mDownTime << "\n";
+ out << "Current classification: " << ftl::enum_string(mCurrentClassification) << "\n";
+ return out.str();
+}
+
void GestureConverter::reset() {
mButtonState = 0;
}
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
index 8e8e3d9..2ec5841 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.h
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
@@ -40,6 +40,8 @@
GestureConverter(InputReaderContext& readerContext, const InputDeviceContext& deviceContext,
int32_t deviceId);
+ std::string dump() const;
+
void setOrientation(ui::Rotation orientation) { mOrientation = orientation; }
void reset();
diff --git a/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp b/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp
new file mode 100644
index 0000000..cd18cd3
--- /dev/null
+++ b/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp
@@ -0,0 +1,236 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "../Macros.h"
+
+#include "gestures/PropertyProvider.h"
+
+#include <algorithm>
+#include <utility>
+
+#include <android-base/stringprintf.h>
+#include <ftl/enum.h>
+#include <input/PrintTools.h>
+#include <log/log_main.h>
+
+namespace android {
+
+namespace {
+
+GesturesProp* createInt(void* data, const char* name, int* loc, size_t count, const int* init) {
+ return static_cast<PropertyProvider*>(data)->createIntArrayProperty(name, loc, count, init);
+}
+
+GesturesProp* createBool(void* data, const char* name, GesturesPropBool* loc, size_t count,
+ const GesturesPropBool* init) {
+ return static_cast<PropertyProvider*>(data)->createBoolArrayProperty(name, loc, count, init);
+}
+
+GesturesProp* createString(void* data, const char* name, const char** loc, const char* const init) {
+ return static_cast<PropertyProvider*>(data)->createStringProperty(name, loc, init);
+}
+
+GesturesProp* createReal(void* data, const char* name, double* loc, size_t count,
+ const double* init) {
+ return static_cast<PropertyProvider*>(data)->createRealArrayProperty(name, loc, count, init);
+}
+
+void registerHandlers(void* data, GesturesProp* prop, void* handlerData,
+ GesturesPropGetHandler getter, GesturesPropSetHandler setter) {
+ prop->registerHandlers(handlerData, getter, setter);
+}
+
+void freeProperty(void* data, GesturesProp* prop) {
+ static_cast<PropertyProvider*>(data)->freeProperty(prop);
+}
+
+} // namespace
+
+const GesturesPropProvider gesturePropProvider = {
+ .create_int_fn = createInt,
+ .create_bool_fn = createBool,
+ .create_string_fn = createString,
+ .create_real_fn = createReal,
+ .register_handlers_fn = registerHandlers,
+ .free_fn = freeProperty,
+};
+
+bool PropertyProvider::hasProperty(const std::string name) const {
+ return mProperties.find(name) != mProperties.end();
+}
+
+GesturesProp& PropertyProvider::getProperty(const std::string name) {
+ return mProperties.at(name);
+}
+
+std::string PropertyProvider::dump() const {
+ std::string dump;
+ for (const auto& [name, property] : mProperties) {
+ dump += property.dump() + "\n";
+ }
+ return dump;
+}
+
+GesturesProp* PropertyProvider::createIntArrayProperty(const std::string name, int* loc,
+ size_t count, const int* init) {
+ const auto [it, inserted] =
+ mProperties.insert(std::pair{name, GesturesProp(name, loc, count, init)});
+ LOG_ALWAYS_FATAL_IF(!inserted, "Gesture property \"%s\" already exists.", name.c_str());
+ return &it->second;
+}
+
+GesturesProp* PropertyProvider::createBoolArrayProperty(const std::string name,
+ GesturesPropBool* loc, size_t count,
+ const GesturesPropBool* init) {
+ const auto [it, inserted] =
+ mProperties.insert(std::pair{name, GesturesProp(name, loc, count, init)});
+ LOG_ALWAYS_FATAL_IF(!inserted, "Gesture property \"%s\" already exists.", name.c_str());
+ return &it->second;
+}
+
+GesturesProp* PropertyProvider::createRealArrayProperty(const std::string name, double* loc,
+ size_t count, const double* init) {
+ const auto [it, inserted] =
+ mProperties.insert(std::pair{name, GesturesProp(name, loc, count, init)});
+ LOG_ALWAYS_FATAL_IF(!inserted, "Gesture property \"%s\" already exists.", name.c_str());
+ return &it->second;
+}
+
+GesturesProp* PropertyProvider::createStringProperty(const std::string name, const char** loc,
+ const char* const init) {
+ const auto [it, inserted] = mProperties.insert(std::pair{name, GesturesProp(name, loc, init)});
+ LOG_ALWAYS_FATAL_IF(!inserted, "Gesture property \"%s\" already exists.", name.c_str());
+ return &it->second;
+}
+
+void PropertyProvider::freeProperty(GesturesProp* prop) {
+ mProperties.erase(prop->getName());
+}
+
+} // namespace android
+
+template <typename T>
+GesturesProp::GesturesProp(std::string name, T* dataPointer, size_t count, const T* initialValues)
+ : mName(name), mCount(count), mDataPointer(dataPointer) {
+ std::copy_n(initialValues, count, dataPointer);
+}
+
+GesturesProp::GesturesProp(std::string name, const char** dataPointer,
+ const char* const initialValue)
+ : mName(name), mCount(1), mDataPointer(dataPointer) {
+ *(std::get<const char**>(mDataPointer)) = initialValue;
+}
+
+std::string GesturesProp::dump() const {
+ using android::base::StringPrintf;
+ std::string type, values;
+ switch (mDataPointer.index()) {
+ case 0:
+ type = "integer";
+ values = android::dumpVector(getIntValues());
+ break;
+ case 1:
+ type = "boolean";
+ values = android::dumpVector(getBoolValues());
+ break;
+ case 2:
+ type = "string";
+ values = getStringValue();
+ break;
+ case 3:
+ type = "real";
+ values = android::dumpVector(getRealValues());
+ break;
+ }
+ std::string typeAndSize = mCount == 1 ? type : std::to_string(mCount) + " " + type + "s";
+ return StringPrintf("%s (%s): %s", mName.c_str(), typeAndSize.c_str(), values.c_str());
+}
+
+void GesturesProp::registerHandlers(void* handlerData, GesturesPropGetHandler getter,
+ GesturesPropSetHandler setter) {
+ mHandlerData = handlerData;
+ mGetter = getter;
+ mSetter = setter;
+}
+
+std::vector<int> GesturesProp::getIntValues() const {
+ LOG_ALWAYS_FATAL_IF(!std::holds_alternative<int*>(mDataPointer),
+ "Attempt to read ints from \"%s\" gesture property.", mName.c_str());
+ return getValues<int, int>(std::get<int*>(mDataPointer));
+}
+
+std::vector<bool> GesturesProp::getBoolValues() const {
+ LOG_ALWAYS_FATAL_IF(!std::holds_alternative<GesturesPropBool*>(mDataPointer),
+ "Attempt to read bools from \"%s\" gesture property.", mName.c_str());
+ return getValues<bool, GesturesPropBool>(std::get<GesturesPropBool*>(mDataPointer));
+}
+
+std::vector<double> GesturesProp::getRealValues() const {
+ LOG_ALWAYS_FATAL_IF(!std::holds_alternative<double*>(mDataPointer),
+ "Attempt to read reals from \"%s\" gesture property.", mName.c_str());
+ return getValues<double, double>(std::get<double*>(mDataPointer));
+}
+
+std::string GesturesProp::getStringValue() const {
+ LOG_ALWAYS_FATAL_IF(!std::holds_alternative<const char**>(mDataPointer),
+ "Attempt to read a string from \"%s\" gesture property.", mName.c_str());
+ if (mGetter != nullptr) {
+ mGetter(mHandlerData);
+ }
+ return std::string(*std::get<const char**>(mDataPointer));
+}
+
+void GesturesProp::setBoolValues(const std::vector<bool>& values) {
+ LOG_ALWAYS_FATAL_IF(!std::holds_alternative<GesturesPropBool*>(mDataPointer),
+ "Attempt to write bools to \"%s\" gesture property.", mName.c_str());
+ setValues(std::get<GesturesPropBool*>(mDataPointer), values);
+}
+
+void GesturesProp::setIntValues(const std::vector<int>& values) {
+ LOG_ALWAYS_FATAL_IF(!std::holds_alternative<int*>(mDataPointer),
+ "Attempt to write ints to \"%s\" gesture property.", mName.c_str());
+ setValues(std::get<int*>(mDataPointer), values);
+}
+
+void GesturesProp::setRealValues(const std::vector<double>& values) {
+ LOG_ALWAYS_FATAL_IF(!std::holds_alternative<double*>(mDataPointer),
+ "Attempt to write reals to \"%s\" gesture property.", mName.c_str());
+ setValues(std::get<double*>(mDataPointer), values);
+}
+
+template <typename T, typename U>
+const std::vector<T> GesturesProp::getValues(U* dataPointer) const {
+ if (mGetter != nullptr) {
+ mGetter(mHandlerData);
+ }
+ std::vector<T> values;
+ values.reserve(mCount);
+ for (size_t i = 0; i < mCount; i++) {
+ values.push_back(dataPointer[i]);
+ }
+ return values;
+}
+
+template <typename T, typename U>
+void GesturesProp::setValues(T* dataPointer, const std::vector<U>& values) {
+ LOG_ALWAYS_FATAL_IF(values.size() != mCount,
+ "Attempt to write %zu values to \"%s\" gesture property, which holds %zu.",
+ values.size(), mName.c_str(), mCount);
+ std::copy(values.begin(), values.end(), dataPointer);
+ if (mSetter != nullptr) {
+ mSetter(mHandlerData);
+ }
+}
diff --git a/services/inputflinger/reader/mapper/gestures/PropertyProvider.h b/services/inputflinger/reader/mapper/gestures/PropertyProvider.h
new file mode 100644
index 0000000..c21260f
--- /dev/null
+++ b/services/inputflinger/reader/mapper/gestures/PropertyProvider.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <map>
+#include <string>
+#include <variant>
+#include <vector>
+
+#include "include/gestures.h"
+
+namespace android {
+
+// Struct containing functions that wrap PropertyProvider in a C-compatible interface.
+extern const GesturesPropProvider gesturePropProvider;
+
+// Implementation of a gestures library property provider, which provides configuration parameters.
+class PropertyProvider {
+public:
+ bool hasProperty(const std::string name) const;
+ GesturesProp& getProperty(const std::string name);
+ std::string dump() const;
+
+ // Methods to be called by the gestures library:
+ GesturesProp* createIntArrayProperty(const std::string name, int* loc, size_t count,
+ const int* init);
+ GesturesProp* createBoolArrayProperty(const std::string name, GesturesPropBool* loc,
+ size_t count, const GesturesPropBool* init);
+ GesturesProp* createRealArrayProperty(const std::string name, double* loc, size_t count,
+ const double* init);
+ GesturesProp* createStringProperty(const std::string name, const char** loc,
+ const char* const init);
+
+ void freeProperty(GesturesProp* prop);
+
+private:
+ std::map<std::string, GesturesProp> mProperties;
+};
+
+} // namespace android
+
+// Represents a single gesture property.
+//
+// Pointers to this struct will be used by the gestures library (though it can never deference
+// them). The library's API requires this to be in the top-level namespace.
+struct GesturesProp {
+public:
+ template <typename T>
+ GesturesProp(std::string name, T* dataPointer, size_t count, const T* initialValues);
+ GesturesProp(std::string name, const char** dataPointer, const char* const initialValue);
+
+ std::string dump() const;
+
+ std::string getName() const { return mName; }
+
+ size_t getCount() const { return mCount; }
+
+ void registerHandlers(void* handlerData, GesturesPropGetHandler getter,
+ GesturesPropSetHandler setter);
+
+ std::vector<int> getIntValues() const;
+ std::vector<bool> getBoolValues() const;
+ std::vector<double> getRealValues() const;
+ std::string getStringValue() const;
+
+ void setIntValues(const std::vector<int>& values);
+ void setBoolValues(const std::vector<bool>& values);
+ void setRealValues(const std::vector<double>& values);
+ // Setting string values isn't supported since we don't have a use case yet and the memory
+ // management adds additional complexity.
+
+private:
+ // Two type parameters are required for these methods, rather than one, due to the gestures
+ // library using its own bool type.
+ template <typename T, typename U>
+ const std::vector<T> getValues(U* dataPointer) const;
+ template <typename T, typename U>
+ void setValues(T* dataPointer, const std::vector<U>& values);
+
+ std::string mName;
+ size_t mCount;
+ std::variant<int*, GesturesPropBool*, const char**, double*> mDataPointer;
+ void* mHandlerData = nullptr;
+ GesturesPropGetHandler mGetter = nullptr;
+ GesturesPropSetHandler mSetter = nullptr;
+};
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index 58a5c31..af40fed 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -55,6 +55,7 @@
"LatencyTracker_test.cpp",
"NotifyArgs_test.cpp",
"PreferStylusOverTouch_test.cpp",
+ "PropertyProvider_test.cpp",
"TestInputListener.cpp",
"UinputDevice.cpp",
"UnwantedInteractionBlocker_test.cpp",
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 864aaea..3abe43a 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -2168,7 +2168,6 @@
.y(400))
.build()));
windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
- windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE));
// Move cursor into left window
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -2181,7 +2180,6 @@
.build()));
windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT));
windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
- windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE));
// Inject a series of mouse events for a mouse click
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -2239,7 +2237,6 @@
.build()));
windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT));
windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
- windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE));
// No more events
windowLeft->assertNoEvents();
@@ -2301,7 +2298,6 @@
.y(400))
.build()));
window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
-
// Inject a series of mouse events for a mouse click
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
injectMotionEvent(mDispatcher,
@@ -2359,8 +2355,38 @@
}
/**
+ * Hover over a window, and then remove that window. Make sure that HOVER_EXIT for that event
+ * is generated.
+ */
+TEST_F(InputDispatcherTest, HoverExitIsSentToRemovedWindow) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 1200, 800));
+
+ mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
+ AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+ .x(300)
+ .y(400))
+ .build()));
+ window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
+
+ // Remove the window, but keep the channel.
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {}}});
+ window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT));
+}
+
+/**
* Inject a mouse hover event followed by a tap from touchscreen.
- * In the current implementation, the tap does not cause a HOVER_EXIT event.
+ * The tap causes a HOVER_EXIT event to be generated because the current event
+ * stream's source has been switched.
*/
TEST_F(InputDispatcherTest, MouseHoverAndTouchTap) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
@@ -2380,15 +2406,16 @@
ASSERT_NO_FATAL_FAILURE(
window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
WithSource(AINPUT_SOURCE_MOUSE))));
- ASSERT_NO_FATAL_FAILURE(
- window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
- WithSource(AINPUT_SOURCE_MOUSE))));
// Tap on the window
motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
ADISPLAY_ID_DEFAULT, {{10, 10}});
mDispatcher->notifyMotion(&motionArgs);
ASSERT_NO_FATAL_FAILURE(
+ window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+ WithSource(AINPUT_SOURCE_MOUSE))));
+
+ ASSERT_NO_FATAL_FAILURE(
window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
WithSource(AINPUT_SOURCE_TOUCHSCREEN))));
@@ -2426,7 +2453,6 @@
.y(600))
.build()));
windowDefaultDisplay->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
- windowDefaultDisplay->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE));
// Remove all windows in secondary display and check that no event happens on window in
// primary display.
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 95d35f4..feda191 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -5036,7 +5036,7 @@
prepareAxes(POSITION);
SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
- ASSERT_EQ(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
+ ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
}
TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsTouchScreen_ReturnsTouchScreen) {
@@ -8865,8 +8865,8 @@
prepareAxes(POSITION);
MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
- // Check source is a touchpad that would obtain the PointerController.
- ASSERT_EQ(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
+ // Check source is mouse that would obtain the PointerController.
+ ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
NotifyMotionArgs motionArgs;
processPosition(mapper, 100, 100);
@@ -9936,11 +9936,11 @@
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
- // A non captured touchpad should have a mouse and touchpad source.
+ // non captured touchpad should be a mouse source
mFakePolicy->setPointerCapture(false);
configureDevice(InputReaderConfiguration::CHANGE_POINTER_CAPTURE);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
- ASSERT_EQ(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
+ ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
}
TEST_F(MultiTouchInputMapperTest, Process_UnCapturedTouchpadPointer) {
@@ -9999,10 +9999,10 @@
mFakePolicy->setPointerCapture(false);
MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
- // An uncaptured touchpad should be a pointer device, with additional touchpad source.
- ASSERT_EQ(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
+ // uncaptured touchpad should be a pointer device
+ ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
- // A captured touchpad should just have a touchpad source.
+ // captured touchpad should be a touchpad device
mFakePolicy->setPointerCapture(true);
configureDevice(InputReaderConfiguration::CHANGE_POINTER_CAPTURE);
ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
diff --git a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp
index bd05360..7265362 100644
--- a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp
+++ b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp
@@ -15,6 +15,7 @@
*/
#include <gtest/gtest.h>
+#include <gui/constants.h>
#include "../PreferStylusOverTouchBlocker.h"
namespace android {
@@ -438,7 +439,7 @@
InputDeviceInfo stylusDevice;
stylusDevice.initialize(STYLUS_DEVICE_ID, 1 /*generation*/, 1 /*controllerNumber*/,
{} /*identifier*/, "stylus device", false /*external*/,
- false /*hasMic*/);
+ false /*hasMic*/, ADISPLAY_ID_NONE);
notifyInputDevicesChanged({stylusDevice});
// The touchscreen device was removed, so we no longer remember anything about it. We should
// again start blocking touch events from it.
diff --git a/services/inputflinger/tests/PropertyProvider_test.cpp b/services/inputflinger/tests/PropertyProvider_test.cpp
new file mode 100644
index 0000000..42a6a9f
--- /dev/null
+++ b/services/inputflinger/tests/PropertyProvider_test.cpp
@@ -0,0 +1,286 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gestures/PropertyProvider.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "include/gestures.h"
+
+namespace android {
+
+using testing::ElementsAre;
+
+class PropertyProviderTest : public testing::Test {
+protected:
+ PropertyProvider mProvider;
+};
+
+TEST_F(PropertyProviderTest, Int_Create) {
+ const size_t COUNT = 4;
+ int intData[COUNT] = {0, 0, 0, 0};
+ int initialValues[COUNT] = {1, 2, 3, 4};
+ gesturePropProvider.create_int_fn(&mProvider, "Some Integers", intData, COUNT, initialValues);
+
+ ASSERT_TRUE(mProvider.hasProperty("Some Integers"));
+ GesturesProp& prop = mProvider.getProperty("Some Integers");
+ EXPECT_EQ(prop.getName(), "Some Integers");
+ EXPECT_EQ(prop.getCount(), COUNT);
+ EXPECT_THAT(intData, ElementsAre(1, 2, 3, 4));
+}
+
+TEST_F(PropertyProviderTest, Int_Get) {
+ const size_t COUNT = 4;
+ int intData[COUNT] = {0, 0, 0, 0};
+ int initialValues[COUNT] = {9, 9, 9, 9};
+ GesturesProp* propPtr = gesturePropProvider.create_int_fn(&mProvider, "Some Integers", intData,
+ COUNT, initialValues);
+
+ // Get handlers are supposed to be called before the property's data is accessed, so they can
+ // update it if necessary. This getter updates the values, so that the ordering can be checked.
+ GesturesPropGetHandler getter{[](void* handlerData) -> GesturesPropBool {
+ int* array = static_cast<int*>(handlerData);
+ array[0] = 1;
+ array[1] = 2;
+ array[2] = 3;
+ array[3] = 4;
+ return true;
+ }};
+ gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ intData,
+ getter, nullptr);
+
+ ASSERT_TRUE(mProvider.hasProperty("Some Integers"));
+ GesturesProp& prop = mProvider.getProperty("Some Integers");
+ EXPECT_THAT(prop.getIntValues(), ElementsAre(1, 2, 3, 4));
+}
+
+TEST_F(PropertyProviderTest, Int_Set) {
+ const size_t COUNT = 4;
+ int intData[COUNT] = {0, 0, 0, 0};
+ int initialValues[COUNT] = {9, 9, 9, 9};
+ GesturesProp* propPtr = gesturePropProvider.create_int_fn(&mProvider, "Some Integers", intData,
+ COUNT, initialValues);
+
+ struct SetterData {
+ bool setterCalled;
+ int* propertyData;
+ };
+ SetterData setterData = {false, intData};
+ GesturesPropSetHandler setter{[](void* handlerData) {
+ SetterData* data = static_cast<SetterData*>(handlerData);
+ // Set handlers should be called after the property's data has changed, so check the data.
+ EXPECT_EQ(data->propertyData[0], 1);
+ EXPECT_EQ(data->propertyData[1], 2);
+ EXPECT_EQ(data->propertyData[2], 3);
+ EXPECT_EQ(data->propertyData[3], 4);
+ data->setterCalled = true;
+ }};
+ gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ &setterData,
+ nullptr, setter);
+
+ ASSERT_TRUE(mProvider.hasProperty("Some Integers"));
+ GesturesProp& prop = mProvider.getProperty("Some Integers");
+ prop.setIntValues({1, 2, 3, 4});
+ EXPECT_THAT(intData, ElementsAre(1, 2, 3, 4));
+ EXPECT_TRUE(setterData.setterCalled);
+ EXPECT_THAT(prop.getIntValues(), ElementsAre(1, 2, 3, 4));
+}
+
+TEST_F(PropertyProviderTest, Bool_Create) {
+ const size_t COUNT = 3;
+ GesturesPropBool boolData[COUNT] = {false, false, false};
+ GesturesPropBool initialValues[COUNT] = {true, false, false};
+ gesturePropProvider.create_bool_fn(&mProvider, "Some Booleans", boolData, COUNT, initialValues);
+
+ ASSERT_TRUE(mProvider.hasProperty("Some Booleans"));
+ GesturesProp& prop = mProvider.getProperty("Some Booleans");
+ EXPECT_EQ(prop.getName(), "Some Booleans");
+ EXPECT_EQ(prop.getCount(), COUNT);
+ EXPECT_THAT(boolData, ElementsAre(true, false, false));
+}
+
+TEST_F(PropertyProviderTest, Bool_Get) {
+ const size_t COUNT = 3;
+ GesturesPropBool boolData[COUNT] = {false, false, false};
+ GesturesPropBool initialValues[COUNT] = {true, false, false};
+ GesturesProp* propPtr = gesturePropProvider.create_bool_fn(&mProvider, "Some Booleans",
+ boolData, COUNT, initialValues);
+
+ // Get handlers are supposed to be called before the property's data is accessed, so they can
+ // update it if necessary. This getter updates the values, so that the ordering can be checked.
+ GesturesPropGetHandler getter{[](void* handlerData) -> GesturesPropBool {
+ GesturesPropBool* array = static_cast<GesturesPropBool*>(handlerData);
+ array[0] = false;
+ array[1] = true;
+ array[2] = true;
+ return true;
+ }};
+ gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ boolData,
+ getter, nullptr);
+
+ ASSERT_TRUE(mProvider.hasProperty("Some Booleans"));
+ GesturesProp& prop = mProvider.getProperty("Some Booleans");
+ EXPECT_THAT(prop.getBoolValues(), ElementsAre(false, true, true));
+}
+
+TEST_F(PropertyProviderTest, Bool_Set) {
+ const size_t COUNT = 3;
+ GesturesPropBool boolData[COUNT] = {false, false, false};
+ GesturesPropBool initialValues[COUNT] = {true, false, false};
+ GesturesProp* propPtr = gesturePropProvider.create_bool_fn(&mProvider, "Some Booleans",
+ boolData, COUNT, initialValues);
+
+ struct SetterData {
+ bool setterCalled;
+ GesturesPropBool* propertyData;
+ };
+ SetterData setterData = {false, boolData};
+ GesturesPropSetHandler setter{[](void* handlerData) {
+ SetterData* data = static_cast<SetterData*>(handlerData);
+ // Set handlers should be called after the property's data has changed, so check the data.
+ EXPECT_EQ(data->propertyData[0], false);
+ EXPECT_EQ(data->propertyData[1], true);
+ EXPECT_EQ(data->propertyData[2], true);
+ data->setterCalled = true;
+ }};
+ gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ &setterData,
+ nullptr, setter);
+
+ ASSERT_TRUE(mProvider.hasProperty("Some Booleans"));
+ GesturesProp& prop = mProvider.getProperty("Some Booleans");
+ prop.setBoolValues({false, true, true});
+ EXPECT_THAT(boolData, ElementsAre(false, true, true));
+ EXPECT_TRUE(setterData.setterCalled);
+ EXPECT_THAT(prop.getBoolValues(), ElementsAre(false, true, true));
+}
+
+TEST_F(PropertyProviderTest, Real_Create) {
+ const size_t COUNT = 3;
+ double realData[COUNT] = {0.0, 0.0, 0.0};
+ double initialValues[COUNT] = {3.14, 0.7, -5.0};
+ gesturePropProvider.create_real_fn(&mProvider, "Some Reals", realData, COUNT, initialValues);
+
+ ASSERT_TRUE(mProvider.hasProperty("Some Reals"));
+ GesturesProp& prop = mProvider.getProperty("Some Reals");
+ EXPECT_EQ(prop.getName(), "Some Reals");
+ EXPECT_EQ(prop.getCount(), COUNT);
+ EXPECT_THAT(realData, ElementsAre(3.14, 0.7, -5.0));
+}
+
+TEST_F(PropertyProviderTest, Real_Get) {
+ const size_t COUNT = 3;
+ double realData[COUNT] = {0.0, 0.0, 0.0};
+ double initialValues[COUNT] = {-1.0, -1.0, -1.0};
+ GesturesProp* propPtr = gesturePropProvider.create_real_fn(&mProvider, "Some Reals", realData,
+ COUNT, initialValues);
+
+ // Get handlers are supposed to be called before the property's data is accessed, so they can
+ // update it if necessary. This getter updates the values, so that the ordering can be checked.
+ GesturesPropGetHandler getter{[](void* handlerData) -> GesturesPropBool {
+ double* array = static_cast<double*>(handlerData);
+ array[0] = 3.14;
+ array[1] = 0.7;
+ array[2] = -5.0;
+ return true;
+ }};
+ gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ realData,
+ getter, nullptr);
+
+ ASSERT_TRUE(mProvider.hasProperty("Some Reals"));
+ GesturesProp& prop = mProvider.getProperty("Some Reals");
+ EXPECT_THAT(prop.getRealValues(), ElementsAre(3.14, 0.7, -5.0));
+}
+
+TEST_F(PropertyProviderTest, Real_Set) {
+ const size_t COUNT = 3;
+ double realData[COUNT] = {0.0, 0.0, 0.0};
+ double initialValues[COUNT] = {-1.0, -1.0, -1.0};
+ GesturesProp* propPtr = gesturePropProvider.create_real_fn(&mProvider, "Some Reals", realData,
+ COUNT, initialValues);
+
+ struct SetterData {
+ bool setterCalled;
+ double* propertyData;
+ };
+ SetterData setterData = {false, realData};
+ GesturesPropSetHandler setter{[](void* handlerData) {
+ SetterData* data = static_cast<SetterData*>(handlerData);
+ // Set handlers should be called after the property's data has changed, so check the data.
+ EXPECT_EQ(data->propertyData[0], 3.14);
+ EXPECT_EQ(data->propertyData[1], 0.7);
+ EXPECT_EQ(data->propertyData[2], -5.0);
+ data->setterCalled = true;
+ }};
+ gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ &setterData,
+ nullptr, setter);
+
+ ASSERT_TRUE(mProvider.hasProperty("Some Reals"));
+ GesturesProp& prop = mProvider.getProperty("Some Reals");
+ prop.setRealValues({3.14, 0.7, -5.0});
+ EXPECT_THAT(realData, ElementsAre(3.14, 0.7, -5.0));
+ EXPECT_TRUE(setterData.setterCalled);
+ EXPECT_THAT(prop.getRealValues(), ElementsAre(3.14, 0.7, -5.0));
+}
+
+TEST_F(PropertyProviderTest, String_Create) {
+ const char* str = nullptr;
+ std::string initialValue = "Foo";
+ gesturePropProvider.create_string_fn(&mProvider, "A String", &str, initialValue.c_str());
+
+ ASSERT_TRUE(mProvider.hasProperty("A String"));
+ GesturesProp& prop = mProvider.getProperty("A String");
+ EXPECT_EQ(prop.getName(), "A String");
+ EXPECT_EQ(prop.getCount(), 1u);
+ EXPECT_STREQ(str, "Foo");
+}
+
+TEST_F(PropertyProviderTest, String_Get) {
+ const char* str = nullptr;
+ std::string initialValue = "Foo";
+ GesturesProp* propPtr = gesturePropProvider.create_string_fn(&mProvider, "A String", &str,
+ initialValue.c_str());
+
+ // Get handlers are supposed to be called before the property's data is accessed, so they can
+ // update it if necessary. This getter updates the values, so that the ordering can be checked.
+ struct GetterData {
+ const char** strPtr;
+ std::string newValue; // Have to store the new value outside getter so it stays allocated.
+ };
+ GetterData getterData = {&str, "Bar"};
+ GesturesPropGetHandler getter{[](void* handlerData) -> GesturesPropBool {
+ GetterData* data = static_cast<GetterData*>(handlerData);
+ *data->strPtr = data->newValue.c_str();
+ return true;
+ }};
+ gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ &getterData,
+ getter, nullptr);
+
+ ASSERT_TRUE(mProvider.hasProperty("A String"));
+ GesturesProp& prop = mProvider.getProperty("A String");
+ EXPECT_EQ(prop.getStringValue(), "Bar");
+}
+
+TEST_F(PropertyProviderTest, Free) {
+ int intData = 0;
+ int initialValue = 42;
+ GesturesProp* propPtr =
+ gesturePropProvider.create_int_fn(&mProvider, "Foo", &intData, 1, &initialValue);
+ gesturePropProvider.free_fn(&mProvider, propPtr);
+
+ EXPECT_FALSE(mProvider.hasProperty("Foo"));
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
index 4c84160..e12f88e 100644
--- a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
+++ b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
@@ -105,7 +105,7 @@
auto info = InputDeviceInfo();
info.initialize(DEVICE_ID, /*generation*/ 1, /*controllerNumber*/ 1, identifier, "alias",
- /*isExternal*/ false, /*hasMic*/ false);
+ /*isExternal*/ false, /*hasMic*/ false, ADISPLAY_ID_NONE);
info.addSource(AINPUT_SOURCE_TOUCHSCREEN);
info.addMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHSCREEN, 0, 1599, /*flat*/ 0,
/*fuzz*/ 0, X_RESOLUTION);
diff --git a/services/sensorservice/aidl/SensorManager.cpp b/services/sensorservice/aidl/SensorManager.cpp
index 9b03344..b7aecdf 100644
--- a/services/sensorservice/aidl/SensorManager.cpp
+++ b/services/sensorservice/aidl/SensorManager.cpp
@@ -185,12 +185,8 @@
}
::android::SensorManager& SensorManagerAidl::getInternalManager() {
- std::lock_guard<std::mutex> lock(mInternalManagerMutex);
- if (mInternalManager == nullptr) {
- mInternalManager = &::android::SensorManager::getInstanceForPackage(
- String16(ISensorManager::descriptor));
- }
- return *mInternalManager;
+ return ::android::SensorManager::getInstanceForPackage(
+ String16(ISensorManager::descriptor));
}
/* One global looper for all event queues created from this SensorManager. */
diff --git a/services/sensorservice/aidl/include/sensorserviceaidl/SensorManagerAidl.h b/services/sensorservice/aidl/include/sensorserviceaidl/SensorManagerAidl.h
index c77ee88..83496f6 100644
--- a/services/sensorservice/aidl/include/sensorserviceaidl/SensorManagerAidl.h
+++ b/services/sensorservice/aidl/include/sensorserviceaidl/SensorManagerAidl.h
@@ -57,8 +57,6 @@
::android::SensorManager& getInternalManager();
sp<Looper> getLooper();
- std::mutex mInternalManagerMutex;
- ::android::SensorManager* mInternalManager = nullptr; // does not own
sp<Looper> mLooper;
volatile bool mStopThread;
diff --git a/services/sensorservice/hidl/SensorManager.cpp b/services/sensorservice/hidl/SensorManager.cpp
index 9380600..f04712c 100644
--- a/services/sensorservice/hidl/SensorManager.cpp
+++ b/services/sensorservice/hidl/SensorManager.cpp
@@ -193,12 +193,8 @@
}
::android::SensorManager& SensorManager::getInternalManager() {
- std::lock_guard<std::mutex> lock(mInternalManagerMutex);
- if (mInternalManager == nullptr) {
- mInternalManager = &::android::SensorManager::getInstanceForPackage(
- String16(ISensorManager::descriptor));
- }
- return *mInternalManager;
+ return ::android::SensorManager::getInstanceForPackage(
+ String16(ISensorManager::descriptor));
}
Return<void> SensorManager::createEventQueue(
diff --git a/services/sensorservice/hidl/include/sensorservicehidl/SensorManager.h b/services/sensorservice/hidl/include/sensorservicehidl/SensorManager.h
index 8d7a05b..1b085ac 100644
--- a/services/sensorservice/hidl/include/sensorservicehidl/SensorManager.h
+++ b/services/sensorservice/hidl/include/sensorservicehidl/SensorManager.h
@@ -58,8 +58,6 @@
::android::SensorManager& getInternalManager();
sp<Looper> getLooper();
- std::mutex mInternalManagerMutex;
- ::android::SensorManager* mInternalManager = nullptr; // does not own
sp<Looper> mLooper;
volatile bool mStopThread;
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 5647b73..fe7cff7 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -157,6 +157,8 @@
"EventLog/EventLog.cpp",
"FrontEnd/LayerCreationArgs.cpp",
"FrontEnd/LayerHandle.cpp",
+ "FrontEnd/LayerSnapshot.cpp",
+ "FrontEnd/LayerSnapshotBuilder.cpp",
"FrontEnd/LayerHierarchy.cpp",
"FrontEnd/LayerLifecycleManager.cpp",
"FrontEnd/RequestedLayerState.cpp",
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 0982077..2b6a519 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -479,8 +479,9 @@
const auto& desiredMode = *info.modeOpt->modePtr;
// Check if we are already at the desired mode
- if (!force && refreshRateSelector().getActiveMode().modePtr->getId() == desiredMode.getId()) {
- if (refreshRateSelector().getActiveMode() == info.modeOpt) {
+ const auto currentMode = refreshRateSelector().getActiveMode();
+ if (!force && currentMode.modePtr->getId() == desiredMode.getId()) {
+ if (currentMode == info.modeOpt) {
return DesiredActiveModeAction::None;
}
@@ -488,6 +489,11 @@
return DesiredActiveModeAction::InitiateRenderRateSwitch;
}
+ // Set the render frame rate to the current physical refresh rate to schedule the next
+ // frame as soon as possible.
+ setActiveMode(currentMode.modePtr->getId(), currentMode.modePtr->getFps(),
+ currentMode.modePtr->getFps());
+
// Initiate a mode change.
mDesiredActiveModeChanged = true;
mDesiredActiveMode = info;
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.h b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
index 31074b1..d54d22d 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.h
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
@@ -314,12 +314,12 @@
virtual void parseArgs(const Vector<String16>& args, std::string& result) = 0;
// Sets the max number of display frames that can be stored. Called by SF backdoor.
- virtual void setMaxDisplayFrames(uint32_t size);
+ virtual void setMaxDisplayFrames(uint32_t size) = 0;
// Computes the historical fps for the provided set of layer IDs
// The fps is compted from the linear timeline of present timestamps for DisplayFrames
// containing at least one layer ID.
- virtual float computeFps(const std::unordered_set<int32_t>& layerIds);
+ virtual float computeFps(const std::unordered_set<int32_t>& layerIds) = 0;
// Restores the max number of display frames to default. Called by SF backdoor.
virtual void reset() = 0;
diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
index db4e8af..514a642 100644
--- a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
@@ -131,7 +131,7 @@
std::string LayerHierarchy::getDebugStringShort() const {
std::string debug = "LayerHierarchy{";
- debug += ((mLayer) ? mLayer->getDebugStringShort() : "root") + " ";
+ debug += ((mLayer) ? mLayer->getDebugString() : "root") + " ";
if (mChildren.empty()) {
debug += "no children";
} else {
@@ -401,10 +401,13 @@
return it->second;
}
-LayerHierarchy::TraversalPath LayerHierarchy::TraversalPath::ROOT_TRAVERSAL_ID =
+const LayerHierarchy::TraversalPath LayerHierarchy::TraversalPath::ROOT =
{.id = UNASSIGNED_LAYER_ID, .variant = LayerHierarchy::Attached};
std::string LayerHierarchy::TraversalPath::toString() const {
+ if (id == UNASSIGNED_LAYER_ID) {
+ return "TraversalPath{ROOT}";
+ }
std::string debugString = "TraversalPath{.id = " + std::to_string(id);
if (!mirrorRootIds.empty()) {
@@ -437,20 +440,22 @@
LayerHierarchy::Variant variant)
: mTraversalPath(traversalPath),
mParentId(traversalPath.id),
- mParentVariant(traversalPath.variant) {
+ mParentVariant(traversalPath.variant),
+ mParentDetached(traversalPath.detached) {
// Update the traversal id with the child layer id and variant. Parent id and variant are
// stored to reset the id upon destruction.
traversalPath.id = layerId;
traversalPath.variant = variant;
if (variant == LayerHierarchy::Variant::Mirror) {
traversalPath.mirrorRootIds.emplace_back(layerId);
- }
- if (variant == LayerHierarchy::Variant::Relative) {
+ } else if (variant == LayerHierarchy::Variant::Relative) {
if (std::find(traversalPath.relativeRootIds.begin(), traversalPath.relativeRootIds.end(),
layerId) != traversalPath.relativeRootIds.end()) {
traversalPath.invalidRelativeRootId = layerId;
}
traversalPath.relativeRootIds.emplace_back(layerId);
+ } else if (variant == LayerHierarchy::Variant::Detached) {
+ traversalPath.detached = true;
}
}
LayerHierarchy::ScopedAddToTraversalPath::~ScopedAddToTraversalPath() {
@@ -458,8 +463,7 @@
// the constructor.
if (mTraversalPath.variant == LayerHierarchy::Variant::Mirror) {
mTraversalPath.mirrorRootIds.pop_back();
- }
- if (mTraversalPath.variant == LayerHierarchy::Variant::Relative) {
+ } else if (mTraversalPath.variant == LayerHierarchy::Variant::Relative) {
mTraversalPath.relativeRootIds.pop_back();
}
if (mTraversalPath.invalidRelativeRootId == mTraversalPath.id) {
@@ -467,6 +471,7 @@
}
mTraversalPath.id = mParentId;
mTraversalPath.variant = mParentVariant;
+ mTraversalPath.detached = mParentDetached;
}
} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.h b/services/surfaceflinger/FrontEnd/LayerHierarchy.h
index f83a859..8cdc240 100644
--- a/services/surfaceflinger/FrontEnd/LayerHierarchy.h
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.h
@@ -63,15 +63,22 @@
// First duplicate relative root id found. If this is a valid layer id that means we are
// in a loop.
uint32_t invalidRelativeRootId = UNASSIGNED_LAYER_ID;
+ // See isAttached()
+ bool detached = false;
bool hasRelZLoop() const { return invalidRelativeRootId != UNASSIGNED_LAYER_ID; }
- bool isRelative() { return !relativeRootIds.empty(); }
+ // Returns true if this node is reached via one or more relative parents.
+ bool isRelative() const { return !relativeRootIds.empty(); }
+ // Returns true if the node or its parents are not Detached.
+ bool isAttached() const { return !detached; }
+ // Returns true if the node is a clone.
+ bool isClone() const { return !mirrorRootIds.empty(); }
bool operator==(const TraversalPath& other) const {
return id == other.id && mirrorRootIds == other.mirrorRootIds;
}
std::string toString() const;
- static TraversalPath ROOT_TRAVERSAL_ID;
+ static const TraversalPath ROOT;
};
// Helper class to add nodes to an existing traversal id and removes the
@@ -86,6 +93,7 @@
TraversalPath& mTraversalPath;
uint32_t mParentId;
LayerHierarchy::Variant mParentVariant;
+ bool mParentDetached;
};
LayerHierarchy(RequestedLayerState* layer);
@@ -98,12 +106,14 @@
// Traverse the hierarchy and visit all child variants.
void traverse(const Visitor& visitor) const {
- traverse(visitor, TraversalPath::ROOT_TRAVERSAL_ID);
+ TraversalPath root = TraversalPath::ROOT;
+ traverse(visitor, root);
}
// Traverse the hierarchy in z-order, skipping children that have relative parents.
void traverseInZOrder(const Visitor& visitor) const {
- traverseInZOrder(visitor, TraversalPath::ROOT_TRAVERSAL_ID);
+ TraversalPath root = TraversalPath::ROOT;
+ traverseInZOrder(visitor, root);
}
const RequestedLayerState* getLayer() const;
diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
index fdf60b3..5514c06 100644
--- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
@@ -210,10 +210,7 @@
layer->touchCropId = linkLayer(layer->touchCropId, layer->id);
}
- mGlobalChanges |= layer->changes &
- (RequestedLayerState::Changes::Hierarchy |
- RequestedLayerState::Changes::Geometry |
- RequestedLayerState::Changes::Content);
+ mGlobalChanges |= layer->changes;
}
}
}
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
new file mode 100644
index 0000000..d483a99
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
@@ -0,0 +1,162 @@
+/*
+ * 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 ATRACE_TAG ATRACE_TAG_GRAPHICS
+#undef LOG_TAG
+#define LOG_TAG "LayerSnapshot"
+
+#include "LayerSnapshot.h"
+
+namespace android::surfaceflinger::frontend {
+
+using namespace ftl::flag_operators;
+
+LayerSnapshot::LayerSnapshot(const RequestedLayerState& state,
+ const LayerHierarchy::TraversalPath& path)
+ : path(path) {
+ sequence = static_cast<int32_t>(state.id);
+ name = state.name;
+ textureName = state.textureName;
+ premultipliedAlpha = state.premultipliedAlpha;
+ inputInfo.name = state.name;
+ inputInfo.id = static_cast<int32_t>(state.id);
+ inputInfo.ownerUid = static_cast<int32_t>(state.ownerUid);
+ inputInfo.ownerPid = state.ownerPid;
+}
+
+// As documented in libhardware header, formats in the range
+// 0x100 - 0x1FF are specific to the HAL implementation, and
+// are known to have no alpha channel
+// TODO: move definition for device-specific range into
+// hardware.h, instead of using hard-coded values here.
+#define HARDWARE_IS_DEVICE_FORMAT(f) ((f) >= 0x100 && (f) <= 0x1FF)
+
+bool LayerSnapshot::isOpaqueFormat(PixelFormat format) {
+ if (HARDWARE_IS_DEVICE_FORMAT(format)) {
+ return true;
+ }
+ switch (format) {
+ case PIXEL_FORMAT_RGBA_8888:
+ case PIXEL_FORMAT_BGRA_8888:
+ case PIXEL_FORMAT_RGBA_FP16:
+ case PIXEL_FORMAT_RGBA_1010102:
+ case PIXEL_FORMAT_R_8:
+ return false;
+ }
+ // in all other case, we have no blending (also for unknown formats)
+ return true;
+}
+
+bool LayerSnapshot::hasBufferOrSidebandStream() const {
+ return ((sidebandStream != nullptr) || (buffer != nullptr));
+}
+
+bool LayerSnapshot::drawShadows() const {
+ return shadowSettings.length > 0.f;
+}
+
+bool LayerSnapshot::fillsColor() const {
+ return !hasBufferOrSidebandStream() && color.r >= 0.0_hf && color.g >= 0.0_hf &&
+ color.b >= 0.0_hf;
+}
+
+bool LayerSnapshot::hasBlur() const {
+ return backgroundBlurRadius > 0 || blurRegions.size() > 0;
+}
+
+bool LayerSnapshot::hasEffect() const {
+ return fillsColor() || drawShadows() || hasBlur();
+}
+
+bool LayerSnapshot::hasSomethingToDraw() const {
+ return hasEffect() || hasBufferOrSidebandStream();
+}
+
+bool LayerSnapshot::isContentOpaque() const {
+ // if we don't have a buffer or sidebandStream yet, we're translucent regardless of the
+ // layer's opaque flag.
+ if (!hasSomethingToDraw()) {
+ return false;
+ }
+
+ // if the layer has the opaque flag, then we're always opaque
+ if (layerOpaqueFlagSet) {
+ return true;
+ }
+
+ // If the buffer has no alpha channel, then we are opaque
+ if (hasBufferOrSidebandStream() &&
+ isOpaqueFormat(buffer ? buffer->getPixelFormat() : PIXEL_FORMAT_NONE)) {
+ return true;
+ }
+
+ // Lastly consider the layer opaque if drawing a color with alpha == 1.0
+ return fillsColor() && color.a == 1.0_hf;
+}
+
+bool LayerSnapshot::isHiddenByPolicy() const {
+ if (CC_UNLIKELY(invalidTransform)) {
+ ALOGW("Hide layer %s because it has invalid transformation.", name.c_str());
+ return true;
+ }
+ return isHiddenByPolicyFromParent || isHiddenByPolicyFromRelativeParent;
+}
+
+bool LayerSnapshot::getIsVisible() const {
+ if (!hasSomethingToDraw()) {
+ return false;
+ }
+
+ if (isHiddenByPolicy()) {
+ return false;
+ }
+
+ return color.a > 0.0f || hasBlur();
+}
+
+std::string LayerSnapshot::getIsVisibleReason() const {
+ if (!hasSomethingToDraw()) {
+ return "!hasSomethingToDraw";
+ }
+
+ if (isHiddenByPolicy()) {
+ return "isHiddenByPolicy";
+ }
+
+ if (color.a > 0.0f || hasBlur()) {
+ return "";
+ }
+
+ return "alpha = 0 and !hasBlur";
+}
+
+bool LayerSnapshot::canReceiveInput() const {
+ return !isHiddenByPolicy() && (!hasBufferOrSidebandStream() || color.a > 0.0f);
+}
+
+bool LayerSnapshot::isTransformValid(const ui::Transform& t) {
+ float transformDet = t.det();
+ return transformDet != 0 && !isinf(transformDet) && !isnan(transformDet);
+}
+
+std::string LayerSnapshot::getDebugString() const {
+ return "Snapshot(" + base::StringPrintf("%p", this) + "){" + path.toString() + name +
+ " isHidden=" + std::to_string(isHiddenByPolicyFromParent) +
+ " isHiddenRelative=" + std::to_string(isHiddenByPolicyFromRelativeParent) +
+ " isVisible=" + std::to_string(isVisible) + " " + getIsVisibleReason() + "}";
+}
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
new file mode 100644
index 0000000..d14bd3a
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
@@ -0,0 +1,96 @@
+/*
+ * 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 <compositionengine/LayerFECompositionState.h>
+#include <renderengine/LayerSettings.h>
+#include "LayerHierarchy.h"
+#include "RequestedLayerState.h"
+#include "android-base/stringprintf.h"
+
+namespace android::surfaceflinger::frontend {
+
+struct RoundedCornerState {
+ RoundedCornerState() = default;
+ RoundedCornerState(const FloatRect& cropRect, const vec2& radius)
+ : cropRect(cropRect), radius(radius) {}
+
+ // Rounded rectangle in local layer coordinate space.
+ FloatRect cropRect = FloatRect();
+ // Radius of the rounded rectangle.
+ vec2 radius;
+ bool hasRoundedCorners() const { return radius.x > 0.0f && radius.y > 0.0f; }
+ bool operator==(RoundedCornerState const& rhs) const {
+ return cropRect == rhs.cropRect && radius == rhs.radius;
+ }
+};
+
+// LayerSnapshot stores Layer state used by CompositionEngine and RenderEngine. Composition
+// Engine uses a pointer to LayerSnapshot (as LayerFECompositionState*) and the LayerSettings
+// passed to Render Engine are created using properties stored on this struct.
+struct LayerSnapshot : public compositionengine::LayerFECompositionState {
+ LayerSnapshot() = default;
+ LayerSnapshot(const RequestedLayerState&, const LayerHierarchy::TraversalPath&);
+
+ LayerHierarchy::TraversalPath path;
+ size_t globalZ = std::numeric_limits<ssize_t>::max();
+ bool invalidTransform = false;
+ bool isHiddenByPolicyFromParent = false;
+ bool isHiddenByPolicyFromRelativeParent = false;
+ ftl::Flags<RequestedLayerState::Changes> changes;
+ int32_t sequence;
+ std::string name;
+ uint32_t textureName;
+ bool contentOpaque;
+ bool layerOpaqueFlagSet;
+ RoundedCornerState roundedCorner;
+ FloatRect transformedBounds;
+ renderengine::ShadowSettings shadowSettings;
+ bool premultipliedAlpha;
+ bool isHdrY410;
+ bool bufferNeedsFiltering;
+ ui::Transform parentTransform;
+ Rect bufferSize;
+ Rect croppedBufferSize;
+ std::shared_ptr<renderengine::ExternalTexture> externalTexture;
+ gui::LayerMetadata layerMetadata;
+ gui::LayerMetadata relativeLayerMetadata;
+ bool hasReadyFrame;
+ ui::Transform localTransformInverse;
+ gui::WindowInfo inputInfo;
+ ui::Transform localTransform;
+ gui::DropInputMode dropInputMode;
+ bool isTrustedOverlay;
+
+ static bool isOpaqueFormat(PixelFormat format);
+ static bool isTransformValid(const ui::Transform& t);
+
+ bool canReceiveInput() const;
+ bool drawShadows() const;
+ bool fillsColor() const;
+ bool getIsVisible() const;
+ bool hasBlur() const;
+ bool hasBufferOrSidebandStream() const;
+ bool hasEffect() const;
+ bool hasSomethingToDraw() const;
+ bool isContentOpaque() const;
+ bool isHiddenByPolicy() const;
+ std::string getDebugString() const;
+ std::string getIsVisibleReason() const;
+};
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
new file mode 100644
index 0000000..bff12d7
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -0,0 +1,833 @@
+/*
+ * 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
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+#undef LOG_TAG
+#define LOG_TAG "LayerSnapshotBuilder"
+
+#include "LayerSnapshotBuilder.h"
+#include <gui/TraceUtils.h>
+#include <numeric>
+#include "DisplayHardware/HWC2.h"
+#include "DisplayHardware/Hal.h"
+#include "ftl/small_map.h"
+
+namespace android::surfaceflinger::frontend {
+
+using namespace ftl::flag_operators;
+
+namespace {
+FloatRect getMaxDisplayBounds(
+ const display::DisplayMap<ui::LayerStack, frontend::DisplayInfo>& displays) {
+ const ui::Size maxSize = [&displays] {
+ if (displays.empty()) return ui::Size{5000, 5000};
+
+ return std::accumulate(displays.begin(), displays.end(), ui::kEmptySize,
+ [](ui::Size size, const auto& pair) -> ui::Size {
+ const auto& display = pair.second;
+ return {std::max(size.getWidth(), display.info.logicalWidth),
+ std::max(size.getHeight(), display.info.logicalHeight)};
+ });
+ }();
+
+ // Ignore display bounds for now since they will be computed later. Use a large Rect bound
+ // to ensure it's bigger than an actual display will be.
+ const float xMax = static_cast<float>(maxSize.getWidth()) * 10.f;
+ const float yMax = static_cast<float>(maxSize.getHeight()) * 10.f;
+
+ return {-xMax, -yMax, xMax, yMax};
+}
+
+// Applies the given transform to the region, while protecting against overflows caused by any
+// offsets. If applying the offset in the transform to any of the Rects in the region would result
+// in an overflow, they are not added to the output Region.
+Region transformTouchableRegionSafely(const ui::Transform& t, const Region& r,
+ const std::string& debugWindowName) {
+ // Round the translation using the same rounding strategy used by ui::Transform.
+ const auto tx = static_cast<int32_t>(t.tx() + 0.5);
+ const auto ty = static_cast<int32_t>(t.ty() + 0.5);
+
+ ui::Transform transformWithoutOffset = t;
+ transformWithoutOffset.set(0.f, 0.f);
+
+ const Region transformed = transformWithoutOffset.transform(r);
+
+ // Apply the translation to each of the Rects in the region while discarding any that overflow.
+ Region ret;
+ for (const auto& rect : transformed) {
+ Rect newRect;
+ if (__builtin_add_overflow(rect.left, tx, &newRect.left) ||
+ __builtin_add_overflow(rect.top, ty, &newRect.top) ||
+ __builtin_add_overflow(rect.right, tx, &newRect.right) ||
+ __builtin_add_overflow(rect.bottom, ty, &newRect.bottom)) {
+ ALOGE("Applying transform to touchable region of window '%s' resulted in an overflow.",
+ debugWindowName.c_str());
+ continue;
+ }
+ ret.orSelf(newRect);
+ }
+ return ret;
+}
+
+/*
+ * We don't want to send the layer's transform to input, but rather the
+ * parent's transform. This is because Layer's transform is
+ * information about how the buffer is placed on screen. The parent's
+ * transform makes more sense to send since it's information about how the
+ * layer is placed on screen. This transform is used by input to determine
+ * how to go from screen space back to window space.
+ */
+ui::Transform getInputTransform(const LayerSnapshot& snapshot) {
+ if (!snapshot.hasBufferOrSidebandStream()) {
+ return snapshot.geomLayerTransform;
+ }
+ return snapshot.parentTransform;
+}
+
+/**
+ * Similar to getInputTransform, we need to update the bounds to include the transform.
+ * This is because bounds don't include the buffer transform, where the input assumes
+ * that's already included.
+ */
+Rect getInputBounds(const LayerSnapshot& snapshot) {
+ if (!snapshot.hasBufferOrSidebandStream()) {
+ return snapshot.croppedBufferSize;
+ }
+
+ if (snapshot.localTransform.getType() == ui::Transform::IDENTITY ||
+ !snapshot.croppedBufferSize.isValid()) {
+ return snapshot.croppedBufferSize;
+ }
+ return snapshot.localTransform.transform(snapshot.croppedBufferSize);
+}
+
+void fillInputFrameInfo(gui::WindowInfo& info, const ui::Transform& screenToDisplay,
+ const LayerSnapshot& snapshot) {
+ Rect tmpBounds = getInputBounds(snapshot);
+ if (!tmpBounds.isValid()) {
+ info.touchableRegion.clear();
+ // A layer could have invalid input bounds and still expect to receive touch input if it has
+ // replaceTouchableRegionWithCrop. For that case, the input transform needs to be calculated
+ // correctly to determine the coordinate space for input events. Use an empty rect so that
+ // the layer will receive input in its own layer space.
+ tmpBounds = Rect::EMPTY_RECT;
+ }
+
+ // InputDispatcher works in the display device's coordinate space. Here, we calculate the
+ // frame and transform used for the layer, which determines the bounds and the coordinate space
+ // within which the layer will receive input.
+ //
+ // The coordinate space within which each of the bounds are specified is explicitly documented
+ // in the variable name. For example "inputBoundsInLayer" is specified in layer space. A
+ // Transform converts one coordinate space to another, which is apparent in its naming. For
+ // example, "layerToDisplay" transforms layer space to display space.
+ //
+ // Coordinate space definitions:
+ // - display: The display device's coordinate space. Correlates to pixels on the display.
+ // - screen: The post-rotation coordinate space for the display, a.k.a. logical display space.
+ // - layer: The coordinate space of this layer.
+ // - input: The coordinate space in which this layer will receive input events. This could be
+ // different than layer space if a surfaceInset is used, which changes the origin
+ // of the input space.
+ const FloatRect inputBoundsInLayer = tmpBounds.toFloatRect();
+
+ // Clamp surface inset to the input bounds.
+ const auto surfaceInset = static_cast<float>(info.surfaceInset);
+ const float xSurfaceInset =
+ std::max(0.f, std::min(surfaceInset, inputBoundsInLayer.getWidth() / 2.f));
+ const float ySurfaceInset =
+ std::max(0.f, std::min(surfaceInset, inputBoundsInLayer.getHeight() / 2.f));
+
+ // Apply the insets to the input bounds.
+ const FloatRect insetBoundsInLayer(inputBoundsInLayer.left + xSurfaceInset,
+ inputBoundsInLayer.top + ySurfaceInset,
+ inputBoundsInLayer.right - xSurfaceInset,
+ inputBoundsInLayer.bottom - ySurfaceInset);
+
+ // Crop the input bounds to ensure it is within the parent's bounds.
+ const FloatRect croppedInsetBoundsInLayer =
+ snapshot.geomLayerBounds.intersect(insetBoundsInLayer);
+
+ const ui::Transform layerToScreen = getInputTransform(snapshot);
+ const ui::Transform layerToDisplay = screenToDisplay * layerToScreen;
+
+ const Rect roundedFrameInDisplay{layerToDisplay.transform(croppedInsetBoundsInLayer)};
+ info.frameLeft = roundedFrameInDisplay.left;
+ info.frameTop = roundedFrameInDisplay.top;
+ info.frameRight = roundedFrameInDisplay.right;
+ info.frameBottom = roundedFrameInDisplay.bottom;
+
+ ui::Transform inputToLayer;
+ inputToLayer.set(insetBoundsInLayer.left, insetBoundsInLayer.top);
+ const ui::Transform inputToDisplay = layerToDisplay * inputToLayer;
+
+ // InputDispatcher expects a display-to-input transform.
+ info.transform = inputToDisplay.inverse();
+
+ // The touchable region is specified in the input coordinate space. Change it to display space.
+ info.touchableRegion =
+ transformTouchableRegionSafely(inputToDisplay, info.touchableRegion, snapshot.name);
+}
+
+void handleDropInputMode(LayerSnapshot& snapshot, const LayerSnapshot& parentSnapshot) {
+ if (snapshot.inputInfo.inputConfig.test(gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL)) {
+ return;
+ }
+
+ // Check if we need to drop input unconditionally
+ const gui::DropInputMode dropInputMode = snapshot.dropInputMode;
+ if (dropInputMode == gui::DropInputMode::ALL) {
+ snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::DROP_INPUT;
+ ALOGV("Dropping input for %s as requested by policy.", snapshot.name.c_str());
+ return;
+ }
+
+ // Check if we need to check if the window is obscured by parent
+ if (dropInputMode != gui::DropInputMode::OBSCURED) {
+ return;
+ }
+
+ // Check if the parent has set an alpha on the layer
+ if (parentSnapshot.color.a != 1.0_hf) {
+ snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::DROP_INPUT;
+ ALOGV("Dropping input for %s as requested by policy because alpha=%f",
+ snapshot.name.c_str(), static_cast<float>(parentSnapshot.color.a));
+ }
+
+ // Check if the parent has cropped the buffer
+ Rect bufferSize = snapshot.croppedBufferSize;
+ if (!bufferSize.isValid()) {
+ snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED;
+ return;
+ }
+
+ // Screenbounds are the layer bounds cropped by parents, transformed to screenspace.
+ // To check if the layer has been cropped, we take the buffer bounds, apply the local
+ // layer crop and apply the same set of transforms to move to screenspace. If the bounds
+ // match then the layer has not been cropped by its parents.
+ Rect bufferInScreenSpace(snapshot.geomLayerTransform.transform(bufferSize));
+ bool croppedByParent = bufferInScreenSpace != Rect{snapshot.transformedBounds};
+
+ if (croppedByParent) {
+ snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::DROP_INPUT;
+ ALOGV("Dropping input for %s as requested by policy because buffer is cropped by parent",
+ snapshot.name.c_str());
+ } else {
+ // If the layer is not obscured by its parents (by setting an alpha or crop), then only drop
+ // input if the window is obscured. This check should be done in surfaceflinger but the
+ // logic currently resides in inputflinger. So pass the if_obscured check to input to only
+ // drop input events if the window is obscured.
+ snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED;
+ }
+}
+
+bool getBufferNeedsFiltering(const LayerSnapshot& snapshot, const ui::Size& unrotatedBufferSize) {
+ const int32_t layerWidth = static_cast<int32_t>(snapshot.geomLayerBounds.getWidth());
+ const int32_t layerHeight = static_cast<int32_t>(snapshot.geomLayerBounds.getHeight());
+ return layerWidth != unrotatedBufferSize.width || layerHeight != unrotatedBufferSize.height;
+}
+
+auto getBlendMode(const LayerSnapshot& snapshot, const RequestedLayerState& requested) {
+ auto blendMode = Hwc2::IComposerClient::BlendMode::NONE;
+ if (snapshot.alpha != 1.0f || !snapshot.isContentOpaque()) {
+ blendMode = requested.premultipliedAlpha ? Hwc2::IComposerClient::BlendMode::PREMULTIPLIED
+ : Hwc2::IComposerClient::BlendMode::COVERAGE;
+ }
+ return blendMode;
+}
+
+} // namespace
+
+LayerSnapshot LayerSnapshotBuilder::getRootSnapshot() {
+ LayerSnapshot snapshot;
+ snapshot.changes = ftl::Flags<RequestedLayerState::Changes>();
+ snapshot.isHiddenByPolicyFromParent = false;
+ snapshot.isHiddenByPolicyFromRelativeParent = false;
+ snapshot.parentTransform.reset();
+ snapshot.geomLayerTransform.reset();
+ snapshot.geomInverseLayerTransform.reset();
+ snapshot.geomLayerBounds = getMaxDisplayBounds({});
+ snapshot.roundedCorner = RoundedCornerState();
+ snapshot.stretchEffect = {};
+ snapshot.outputFilter.layerStack = ui::DEFAULT_LAYER_STACK;
+ snapshot.outputFilter.toInternalDisplay = false;
+ snapshot.isSecure = false;
+ snapshot.color.a = 1.0_hf;
+ snapshot.colorTransformIsIdentity = true;
+ snapshot.shadowRadius = 0.f;
+ snapshot.layerMetadata.mMap.clear();
+ snapshot.relativeLayerMetadata.mMap.clear();
+ snapshot.inputInfo.touchOcclusionMode = gui::TouchOcclusionMode::BLOCK_UNTRUSTED;
+ snapshot.dropInputMode = gui::DropInputMode::NONE;
+ snapshot.isTrustedOverlay = false;
+ return snapshot;
+}
+
+LayerSnapshotBuilder::LayerSnapshotBuilder() : mRootSnapshot(getRootSnapshot()) {}
+
+LayerSnapshotBuilder::LayerSnapshotBuilder(Args args) : LayerSnapshotBuilder() {
+ args.forceUpdate = true;
+ updateSnapshots(args);
+}
+
+bool LayerSnapshotBuilder::tryFastUpdate(const Args& args) {
+ if (args.forceUpdate) {
+ // force update requested, so skip the fast path
+ return false;
+ }
+
+ if (args.layerLifecycleManager.getGlobalChanges().get() == 0) {
+ // there are no changes, so just clear the change flags from before.
+ for (auto& snapshot : mSnapshots) {
+ snapshot->changes.clear();
+ snapshot->contentDirty = false;
+ }
+ return true;
+ }
+
+ if (args.layerLifecycleManager.getGlobalChanges() != RequestedLayerState::Changes::Content) {
+ // We have changes that require us to walk the hierarchy and update child layers.
+ // No fast path for you.
+ return false;
+ }
+
+ // There are only content changes which do not require any child layer snapshots to be updated.
+ ALOGV("%s", __func__);
+ ATRACE_NAME("FastPath");
+
+ // Collect layers with changes
+ ftl::SmallMap<uint32_t, RequestedLayerState*, 10> layersWithChanges;
+ for (auto& layer : args.layerLifecycleManager.getLayers()) {
+ if (layer->changes.test(RequestedLayerState::Changes::Content)) {
+ layersWithChanges.emplace_or_replace(layer->id, layer.get());
+ }
+ }
+
+ // Walk through the snapshots, clearing previous change flags and updating the snapshots
+ // if needed.
+ for (auto& snapshot : mSnapshots) {
+ snapshot->changes.clear();
+ snapshot->contentDirty = false;
+ auto it = layersWithChanges.find(snapshot->path.id);
+ if (it != layersWithChanges.end()) {
+ ALOGV("%s fast path snapshot changes = %s", __func__,
+ mRootSnapshot.changes.string().c_str());
+ LayerHierarchy::TraversalPath root = LayerHierarchy::TraversalPath::ROOT;
+ updateSnapshot(*snapshot, args, *it->second, mRootSnapshot, root);
+ }
+ }
+ return true;
+}
+
+void LayerSnapshotBuilder::updateSnapshots(const Args& args) {
+ ATRACE_NAME("UpdateSnapshots");
+ ALOGV("%s updateSnapshots force = %s", __func__, std::to_string(args.forceUpdate).c_str());
+ if (args.forceUpdate || args.displayChanges) {
+ mRootSnapshot.geomLayerBounds = getMaxDisplayBounds(args.displays);
+ }
+ if (args.displayChanges) {
+ mRootSnapshot.changes = RequestedLayerState::Changes::AffectsChildren |
+ RequestedLayerState::Changes::Geometry;
+ }
+ LayerHierarchy::TraversalPath root = LayerHierarchy::TraversalPath::ROOT;
+ for (auto& [childHierarchy, variant] : args.root.mChildren) {
+ LayerHierarchy::ScopedAddToTraversalPath addChildToPath(root,
+ childHierarchy->getLayer()->id,
+ variant);
+ updateSnapshotsInHierarchy(args, *childHierarchy, root, mRootSnapshot);
+ }
+
+ sortSnapshotsByZ(args);
+ mRootSnapshot.changes.clear();
+
+ // Destroy unreachable snapshots
+ if (args.layerLifecycleManager.getDestroyedLayers().empty()) {
+ return;
+ }
+
+ std::unordered_set<uint32_t> destroyedLayerIds;
+ for (auto& destroyedLayer : args.layerLifecycleManager.getDestroyedLayers()) {
+ destroyedLayerIds.emplace(destroyedLayer->id);
+ }
+ auto it = mSnapshots.begin();
+ while (it < mSnapshots.end()) {
+ auto& traversalPath = it->get()->path;
+ if (destroyedLayerIds.find(traversalPath.id) == destroyedLayerIds.end()) {
+ it++;
+ continue;
+ }
+
+ mIdToSnapshot.erase(traversalPath);
+ std::iter_swap(it, mSnapshots.end() - 1);
+ mSnapshots.erase(mSnapshots.end() - 1);
+ }
+}
+
+void LayerSnapshotBuilder::update(const Args& args) {
+ if (tryFastUpdate(args)) {
+ return;
+ }
+ updateSnapshots(args);
+}
+
+void LayerSnapshotBuilder::updateSnapshotsInHierarchy(const Args& args,
+ const LayerHierarchy& hierarchy,
+ LayerHierarchy::TraversalPath& traversalPath,
+ const LayerSnapshot& parentSnapshot) {
+ const RequestedLayerState* layer = hierarchy.getLayer();
+ LayerSnapshot* snapshot = getOrCreateSnapshot(traversalPath, *layer);
+ if (traversalPath.isRelative()) {
+ bool parentIsRelative = traversalPath.variant == LayerHierarchy::Variant::Relative;
+ updateRelativeState(*snapshot, parentSnapshot, parentIsRelative, args);
+ } else {
+ if (traversalPath.isAttached()) {
+ resetRelativeState(*snapshot);
+ }
+ updateSnapshot(*snapshot, args, *layer, parentSnapshot, traversalPath);
+ }
+
+ // If layer is hidden by policy we can avoid update its children. If the visibility
+ // changed this update, then we still need to set the visibility on all the children.
+ if (snapshot->isHiddenByPolicy() &&
+ (!snapshot->changes.any(RequestedLayerState::Changes::Visibility |
+ RequestedLayerState::Changes::Hierarchy))) {
+ return;
+ }
+
+ for (auto& [childHierarchy, variant] : hierarchy.mChildren) {
+ LayerHierarchy::ScopedAddToTraversalPath addChildToPath(traversalPath,
+ childHierarchy->getLayer()->id,
+ variant);
+ updateSnapshotsInHierarchy(args, *childHierarchy, traversalPath, *snapshot);
+ }
+}
+
+LayerSnapshot* LayerSnapshotBuilder::getSnapshot(uint32_t layerId) const {
+ if (layerId == UNASSIGNED_LAYER_ID) {
+ return nullptr;
+ }
+ LayerHierarchy::TraversalPath path{.id = layerId};
+ return getSnapshot(path);
+}
+
+LayerSnapshot* LayerSnapshotBuilder::getSnapshot(const LayerHierarchy::TraversalPath& id) const {
+ auto it = mIdToSnapshot.find(id);
+ return it == mIdToSnapshot.end() ? nullptr : it->second;
+}
+
+LayerSnapshot* LayerSnapshotBuilder::getOrCreateSnapshot(const LayerHierarchy::TraversalPath& id,
+ const RequestedLayerState& layer) {
+ auto snapshot = getSnapshot(id);
+ if (snapshot) {
+ return snapshot;
+ }
+
+ mSnapshots.emplace_back(std::make_unique<LayerSnapshot>(layer, id));
+ snapshot = mSnapshots.back().get();
+ snapshot->globalZ = static_cast<size_t>(mSnapshots.size()) - 1;
+ mIdToSnapshot[id] = snapshot;
+ return snapshot;
+}
+
+void LayerSnapshotBuilder::sortSnapshotsByZ(const Args& args) {
+ if (!args.forceUpdate &&
+ !args.layerLifecycleManager.getGlobalChanges().any(
+ RequestedLayerState::Changes::Hierarchy |
+ RequestedLayerState::Changes::Visibility)) {
+ // We are not force updating and there are no hierarchy or visibility changes. Avoid sorting
+ // the snapshots.
+ return;
+ }
+
+ size_t globalZ = 0;
+ args.root.traverseInZOrder(
+ [this, &globalZ](const LayerHierarchy&,
+ const LayerHierarchy::TraversalPath& traversalPath) -> bool {
+ LayerSnapshot* snapshot = getSnapshot(traversalPath);
+ if (!snapshot) {
+ return false;
+ }
+
+ if (snapshot->isHiddenByPolicy() &&
+ !snapshot->changes.test(RequestedLayerState::Changes::Visibility)) {
+ return false;
+ }
+
+ if (snapshot->isVisible) {
+ size_t oldZ = snapshot->globalZ;
+ size_t newZ = globalZ++;
+ snapshot->globalZ = newZ;
+ if (oldZ == newZ) {
+ return true;
+ }
+ mSnapshots[newZ]->globalZ = oldZ;
+ std::iter_swap(mSnapshots.begin() + static_cast<ssize_t>(oldZ),
+ mSnapshots.begin() + static_cast<ssize_t>(newZ));
+ }
+
+ return true;
+ });
+
+ while (globalZ < mSnapshots.size()) {
+ mSnapshots[globalZ]->globalZ = globalZ;
+ mSnapshots[globalZ]->isVisible = false;
+ globalZ++;
+ }
+}
+
+void LayerSnapshotBuilder::updateRelativeState(LayerSnapshot& snapshot,
+ const LayerSnapshot& parentSnapshot,
+ bool parentIsRelative, const Args& args) {
+ if (parentIsRelative) {
+ snapshot.isHiddenByPolicyFromRelativeParent = parentSnapshot.isHiddenByPolicyFromParent;
+ if (args.includeMetadata) {
+ snapshot.relativeLayerMetadata = parentSnapshot.layerMetadata;
+ }
+ } else {
+ snapshot.isHiddenByPolicyFromRelativeParent =
+ parentSnapshot.isHiddenByPolicyFromRelativeParent;
+ if (args.includeMetadata) {
+ snapshot.relativeLayerMetadata = parentSnapshot.relativeLayerMetadata;
+ }
+ }
+ snapshot.isVisible = snapshot.getIsVisible();
+}
+
+void LayerSnapshotBuilder::resetRelativeState(LayerSnapshot& snapshot) {
+ snapshot.isHiddenByPolicyFromRelativeParent = false;
+ snapshot.relativeLayerMetadata.mMap.clear();
+}
+
+uint32_t getDisplayRotationFlags(
+ const display::DisplayMap<ui::LayerStack, frontend::DisplayInfo>& displays,
+ const ui::LayerStack& layerStack) {
+ static frontend::DisplayInfo sDefaultDisplayInfo = {.isPrimary = false};
+ auto display = displays.get(layerStack).value_or(sDefaultDisplayInfo).get();
+ return display.isPrimary ? display.rotationFlags : 0;
+}
+
+void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& args,
+ const RequestedLayerState& requested,
+ const LayerSnapshot& parentSnapshot,
+ const LayerHierarchy::TraversalPath& path) {
+ // Always update flags and visibility
+ ftl::Flags<RequestedLayerState::Changes> parentChanges = parentSnapshot.changes &
+ (RequestedLayerState::Changes::Hierarchy | RequestedLayerState::Changes::Geometry |
+ RequestedLayerState::Changes::Visibility | RequestedLayerState::Changes::Metadata |
+ RequestedLayerState::Changes::AffectsChildren);
+ snapshot.changes = parentChanges | requested.changes;
+ snapshot.isHiddenByPolicyFromParent =
+ parentSnapshot.isHiddenByPolicyFromParent || requested.isHiddenByPolicy();
+ snapshot.contentDirty = requested.what & layer_state_t::CONTENT_DIRTY;
+ if (snapshot.isHiddenByPolicyFromParent) {
+ snapshot.isVisible = false;
+ return;
+ }
+
+ uint32_t displayRotationFlags =
+ getDisplayRotationFlags(args.displays, snapshot.outputFilter.layerStack);
+
+ const bool forceUpdate = args.forceUpdate ||
+ snapshot.changes.any(RequestedLayerState::Changes::Visibility |
+ RequestedLayerState::Changes::Created);
+
+ if (forceUpdate || snapshot.changes.any(RequestedLayerState::Changes::AffectsChildren)) {
+ // If root layer, use the layer stack otherwise get the parent's layer stack.
+ snapshot.color.a = parentSnapshot.color.a * requested.color.a;
+ snapshot.alpha = snapshot.color.a;
+ snapshot.isSecure =
+ parentSnapshot.isSecure || (requested.flags & layer_state_t::eLayerSecure);
+ snapshot.isTrustedOverlay = parentSnapshot.isTrustedOverlay || requested.isTrustedOverlay;
+ snapshot.outputFilter.layerStack = requested.parentId != UNASSIGNED_LAYER_ID
+ ? parentSnapshot.outputFilter.layerStack
+ : requested.layerStack;
+ snapshot.outputFilter.toInternalDisplay = parentSnapshot.outputFilter.toInternalDisplay ||
+ (requested.flags & layer_state_t::eLayerSkipScreenshot);
+ snapshot.stretchEffect = (requested.stretchEffect.hasEffect())
+ ? requested.stretchEffect
+ : parentSnapshot.stretchEffect;
+ if (!parentSnapshot.colorTransformIsIdentity) {
+ snapshot.colorTransform = parentSnapshot.colorTransform * requested.colorTransform;
+ snapshot.colorTransformIsIdentity = false;
+ } else {
+ snapshot.colorTransform = requested.colorTransform;
+ snapshot.colorTransformIsIdentity = !requested.hasColorTransform;
+ }
+ }
+
+ if (forceUpdate || requested.changes.get() != 0) {
+ snapshot.compositionType = requested.getCompositionType();
+ snapshot.dimmingEnabled = requested.dimmingEnabled;
+ snapshot.layerOpaqueFlagSet =
+ (requested.flags & layer_state_t::eLayerOpaque) == layer_state_t::eLayerOpaque;
+ }
+
+ if (forceUpdate || requested.what & layer_state_t::BUFFER_CHANGES) {
+ snapshot.acquireFence =
+ (requested.bufferData) ? requested.bufferData->acquireFence : Fence::NO_FENCE;
+ snapshot.buffer =
+ requested.externalTexture ? requested.externalTexture->getBuffer() : nullptr;
+ snapshot.bufferSize = requested.getBufferSize(displayRotationFlags);
+ snapshot.geomBufferSize = snapshot.bufferSize;
+ snapshot.croppedBufferSize = requested.getCroppedBufferSize(snapshot.bufferSize);
+ snapshot.dataspace = requested.dataspace;
+ snapshot.externalTexture = requested.externalTexture;
+ snapshot.frameNumber = (requested.bufferData) ? requested.bufferData->frameNumber : 0;
+ snapshot.geomBufferTransform = requested.bufferTransform;
+ snapshot.geomBufferUsesDisplayInverseTransform = requested.transformToDisplayInverse;
+ snapshot.geomContentCrop = requested.getBufferCrop();
+ snapshot.geomUsesSourceCrop = snapshot.hasBufferOrSidebandStream();
+ snapshot.hasProtectedContent = requested.externalTexture &&
+ requested.externalTexture->getUsage() & GRALLOC_USAGE_PROTECTED;
+ snapshot.isHdrY410 = requested.dataspace == ui::Dataspace::BT2020_ITU_PQ &&
+ requested.api == NATIVE_WINDOW_API_MEDIA &&
+ requested.bufferData->getPixelFormat() == HAL_PIXEL_FORMAT_RGBA_1010102;
+ snapshot.sidebandStream = requested.sidebandStream;
+ snapshot.surfaceDamage = requested.surfaceDamageRegion;
+ snapshot.transparentRegionHint = requested.transparentRegion;
+ }
+
+ if (forceUpdate || snapshot.changes.any(RequestedLayerState::Changes::Content)) {
+ snapshot.color.rgb = requested.getColor().rgb;
+ snapshot.isColorspaceAgnostic = requested.colorSpaceAgnostic;
+ snapshot.backgroundBlurRadius = static_cast<int>(requested.backgroundBlurRadius);
+ snapshot.blurRegions = requested.blurRegions;
+ snapshot.hdrMetadata = requested.hdrMetadata;
+ }
+
+ if (forceUpdate ||
+ snapshot.changes.any(RequestedLayerState::Changes::Hierarchy |
+ RequestedLayerState::Changes::Geometry)) {
+ updateLayerBounds(snapshot, requested, parentSnapshot, displayRotationFlags);
+ updateRoundedCorner(snapshot, requested, parentSnapshot);
+ }
+
+ if (forceUpdate ||
+ snapshot.changes.any(RequestedLayerState::Changes::Hierarchy |
+ RequestedLayerState::Changes::Geometry |
+ RequestedLayerState::Changes::Input)) {
+ static frontend::DisplayInfo sDefaultInfo = {.isSecure = false};
+ const std::optional<frontend::DisplayInfo> displayInfo =
+ args.displays.get(snapshot.outputFilter.layerStack);
+ bool noValidDisplay = !displayInfo.has_value();
+ updateInput(snapshot, requested, parentSnapshot, displayInfo.value_or(sDefaultInfo),
+ noValidDisplay, path);
+ }
+
+ // computed snapshot properties
+ updateShadows(snapshot, requested, args.globalShadowSettings);
+ if (args.includeMetadata) {
+ snapshot.layerMetadata = parentSnapshot.layerMetadata;
+ snapshot.layerMetadata.merge(requested.metadata);
+ }
+ snapshot.forceClientComposition = snapshot.isHdrY410 || snapshot.shadowSettings.length > 0 ||
+ requested.blurRegions.size() > 0 || snapshot.stretchEffect.hasEffect();
+ snapshot.isVisible = snapshot.getIsVisible();
+ snapshot.isOpaque = snapshot.isContentOpaque() && !snapshot.roundedCorner.hasRoundedCorners() &&
+ snapshot.color.a == 1.f;
+ snapshot.blendMode = getBlendMode(snapshot, requested);
+
+ ALOGV("%supdated [%d]%s changes parent:%s global:%s local:%s requested:%s %s from parent %s",
+ args.forceUpdate ? "Force " : "", requested.id, requested.name.c_str(),
+ parentSnapshot.changes.string().c_str(), snapshot.changes.string().c_str(),
+ requested.changes.string().c_str(), std::to_string(requested.what).c_str(),
+ snapshot.getDebugString().c_str(), parentSnapshot.getDebugString().c_str());
+}
+
+void LayerSnapshotBuilder::updateRoundedCorner(LayerSnapshot& snapshot,
+ const RequestedLayerState& requested,
+ const LayerSnapshot& parentSnapshot) {
+ snapshot.roundedCorner = RoundedCornerState();
+ RoundedCornerState parentRoundedCorner;
+ if (parentSnapshot.roundedCorner.hasRoundedCorners()) {
+ parentRoundedCorner = parentSnapshot.roundedCorner;
+ ui::Transform t = snapshot.localTransform.inverse();
+ parentRoundedCorner.cropRect = t.transform(parentRoundedCorner.cropRect);
+ parentRoundedCorner.radius.x *= t.getScaleX();
+ parentRoundedCorner.radius.y *= t.getScaleY();
+ }
+
+ FloatRect layerCropRect = snapshot.croppedBufferSize.toFloatRect();
+ const vec2 radius(requested.cornerRadius, requested.cornerRadius);
+ RoundedCornerState layerSettings(layerCropRect, radius);
+ const bool layerSettingsValid = layerSettings.hasRoundedCorners() && !layerCropRect.isEmpty();
+ const bool parentRoundedCornerValid = parentRoundedCorner.hasRoundedCorners();
+ if (layerSettingsValid && parentRoundedCornerValid) {
+ // If the parent and the layer have rounded corner settings, use the parent settings if
+ // the parent crop is entirely inside the layer crop. This has limitations and cause
+ // rendering artifacts. See b/200300845 for correct fix.
+ if (parentRoundedCorner.cropRect.left > layerCropRect.left &&
+ parentRoundedCorner.cropRect.top > layerCropRect.top &&
+ parentRoundedCorner.cropRect.right < layerCropRect.right &&
+ parentRoundedCorner.cropRect.bottom < layerCropRect.bottom) {
+ snapshot.roundedCorner = parentRoundedCorner;
+ } else {
+ snapshot.roundedCorner = layerSettings;
+ }
+ } else if (layerSettingsValid) {
+ snapshot.roundedCorner = layerSettings;
+ } else if (parentRoundedCornerValid) {
+ snapshot.roundedCorner = parentRoundedCorner;
+ }
+}
+
+void LayerSnapshotBuilder::updateLayerBounds(LayerSnapshot& snapshot,
+ const RequestedLayerState& requested,
+ const LayerSnapshot& parentSnapshot,
+ uint32_t displayRotationFlags) {
+ snapshot.croppedBufferSize = requested.getCroppedBufferSize(snapshot.bufferSize);
+ snapshot.geomCrop = requested.crop;
+ snapshot.localTransform = requested.getTransform(displayRotationFlags);
+ snapshot.localTransformInverse = snapshot.localTransform.inverse();
+ snapshot.geomLayerTransform = parentSnapshot.geomLayerTransform * snapshot.localTransform;
+ snapshot.invalidTransform = !LayerSnapshot::isTransformValid(snapshot.geomLayerTransform);
+ if (snapshot.invalidTransform) {
+ ALOGW("Resetting transform for %s because it has an invalid transformation.",
+ requested.getDebugStringShort().c_str());
+ snapshot.geomLayerTransform.reset();
+ }
+ snapshot.geomInverseLayerTransform = snapshot.geomLayerTransform.inverse();
+
+ FloatRect parentBounds = parentSnapshot.geomLayerBounds;
+ parentBounds = snapshot.localTransform.inverse().transform(parentBounds);
+ snapshot.geomLayerBounds =
+ (requested.externalTexture) ? snapshot.bufferSize.toFloatRect() : parentBounds;
+ if (!requested.crop.isEmpty()) {
+ snapshot.geomLayerBounds = snapshot.geomLayerBounds.intersect(requested.crop.toFloatRect());
+ }
+ snapshot.geomLayerBounds = snapshot.geomLayerBounds.intersect(parentBounds);
+ snapshot.transformedBounds = snapshot.geomLayerTransform.transform(snapshot.geomLayerBounds);
+ snapshot.parentTransform = parentSnapshot.geomLayerTransform;
+
+ // Subtract the transparent region and snap to the bounds
+ Rect bounds =
+ RequestedLayerState::reduce(snapshot.croppedBufferSize, requested.transparentRegion);
+ snapshot.cursorFrame = snapshot.geomLayerTransform.transform(bounds);
+
+ // TODO(b/238781169) use dest vs src
+ snapshot.bufferNeedsFiltering = snapshot.externalTexture &&
+ getBufferNeedsFiltering(snapshot,
+ requested.getUnrotatedBufferSize(displayRotationFlags));
+}
+
+void LayerSnapshotBuilder::updateShadows(LayerSnapshot& snapshot,
+ const RequestedLayerState& requested,
+ const renderengine::ShadowSettings& globalShadowSettings) {
+ snapshot.shadowRadius = requested.shadowRadius;
+ snapshot.shadowSettings.length = requested.shadowRadius;
+ if (snapshot.shadowRadius > 0.f) {
+ snapshot.shadowSettings = globalShadowSettings;
+
+ // Note: this preserves existing behavior of shadowing the entire layer and not cropping
+ // it if transparent regions are present. This may not be necessary since shadows are
+ // typically cast by layers without transparent regions.
+ snapshot.shadowSettings.boundaries = snapshot.geomLayerBounds;
+
+ // If the casting layer is translucent, we need to fill in the shadow underneath the
+ // layer. Otherwise the generated shadow will only be shown around the casting layer.
+ snapshot.shadowSettings.casterIsTranslucent =
+ !snapshot.isContentOpaque() || (snapshot.alpha < 1.0f);
+ snapshot.shadowSettings.ambientColor *= snapshot.alpha;
+ snapshot.shadowSettings.spotColor *= snapshot.alpha;
+ }
+}
+
+void LayerSnapshotBuilder::updateInput(LayerSnapshot& snapshot,
+ const RequestedLayerState& requested,
+ const LayerSnapshot& parentSnapshot,
+ const frontend::DisplayInfo& displayInfo,
+ bool noValidDisplay,
+ const LayerHierarchy::TraversalPath& path) {
+ snapshot.inputInfo.displayId = static_cast<int32_t>(snapshot.outputFilter.layerStack.id);
+ if (!requested.hasInputInfo()) {
+ snapshot.inputInfo.inputConfig = gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL;
+ return;
+ }
+
+ fillInputFrameInfo(snapshot.inputInfo, displayInfo.transform, snapshot);
+
+ if (noValidDisplay) {
+ // Do not let the window receive touches if it is not associated with a valid display
+ // transform. We still allow the window to receive keys and prevent ANRs.
+ snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::NOT_TOUCHABLE;
+ }
+
+ // For compatibility reasons we let layers which can receive input
+ // receive input before they have actually submitted a buffer. Because
+ // of this we use canReceiveInput instead of isVisible to check the
+ // policy-visibility, ignoring the buffer state. However for layers with
+ // hasInputInfo()==false we can use the real visibility state.
+ // We are just using these layers for occlusion detection in
+ // InputDispatcher, and obviously if they aren't visible they can't occlude
+ // anything.
+ const bool visible = requested.hasInputInfo() ? snapshot.canReceiveInput() : snapshot.isVisible;
+ snapshot.inputInfo.setInputConfig(gui::WindowInfo::InputConfig::NOT_VISIBLE, !visible);
+
+ snapshot.inputInfo.alpha = snapshot.color.a;
+ snapshot.inputInfo.touchOcclusionMode = parentSnapshot.inputInfo.touchOcclusionMode;
+ if (requested.dropInputMode == gui::DropInputMode::ALL ||
+ parentSnapshot.dropInputMode == gui::DropInputMode::ALL) {
+ snapshot.dropInputMode = gui::DropInputMode::ALL;
+ } else if (requested.dropInputMode == gui::DropInputMode::OBSCURED ||
+ parentSnapshot.dropInputMode == gui::DropInputMode::OBSCURED) {
+ snapshot.dropInputMode = gui::DropInputMode::OBSCURED;
+ } else {
+ snapshot.dropInputMode = gui::DropInputMode::NONE;
+ }
+
+ handleDropInputMode(snapshot, parentSnapshot);
+
+ // If the window will be blacked out on a display because the display does not have the secure
+ // flag and the layer has the secure flag set, then drop input.
+ if (!displayInfo.isSecure && snapshot.isSecure) {
+ snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::DROP_INPUT;
+ }
+
+ auto cropLayerSnapshot = getSnapshot(requested.touchCropId);
+ if (snapshot.inputInfo.replaceTouchableRegionWithCrop) {
+ const Rect bounds(cropLayerSnapshot ? cropLayerSnapshot->transformedBounds
+ : snapshot.transformedBounds);
+ snapshot.inputInfo.touchableRegion = Region(displayInfo.transform.transform(bounds));
+ } else if (cropLayerSnapshot) {
+ snapshot.inputInfo.touchableRegion = snapshot.inputInfo.touchableRegion.intersect(
+ displayInfo.transform.transform(Rect{cropLayerSnapshot->transformedBounds}));
+ }
+
+ // Inherit the trusted state from the parent hierarchy, but don't clobber the trusted state
+ // if it was set by WM for a known system overlay
+ if (snapshot.isTrustedOverlay) {
+ snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::TRUSTED_OVERLAY;
+ }
+
+ // If the layer is a clone, we need to crop the input region to cloned root to prevent
+ // touches from going outside the cloned area.
+ if (path.isClone()) {
+ snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::CLONE;
+ auto clonedRootSnapshot = getSnapshot(path.mirrorRootIds.back());
+ if (clonedRootSnapshot) {
+ const Rect rect =
+ displayInfo.transform.transform(Rect{clonedRootSnapshot->transformedBounds});
+ snapshot.inputInfo.touchableRegion = snapshot.inputInfo.touchableRegion.intersect(rect);
+ }
+ }
+}
+
+std::vector<std::unique_ptr<LayerSnapshot>>& LayerSnapshotBuilder::getSnapshots() {
+ return mSnapshots;
+}
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
new file mode 100644
index 0000000..33b250c
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
@@ -0,0 +1,110 @@
+/*
+ * 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 "Display/DisplayMap.h"
+#include "FrontEnd/DisplayInfo.h"
+#include "FrontEnd/LayerLifecycleManager.h"
+#include "LayerHierarchy.h"
+#include "LayerSnapshot.h"
+#include "RequestedLayerState.h"
+
+namespace android::surfaceflinger::frontend {
+
+// Walks through the layer hierarchy to build an ordered list
+// of LayerSnapshots that can be passed on to CompositionEngine.
+// This builder does a minimum amount of work to update
+// an existing set of snapshots based on hierarchy changes
+// and RequestedLayerState changes.
+
+// The builder also uses a fast path to update
+// snapshots when there are only buffer updates.
+class LayerSnapshotBuilder {
+public:
+ struct Args {
+ const LayerHierarchy& root;
+ const LayerLifecycleManager& layerLifecycleManager;
+ bool forceUpdate = false;
+ bool includeMetadata = false;
+ const display::DisplayMap<ui::LayerStack, frontend::DisplayInfo>& displays;
+ // Set to true if there were display changes since last update.
+ bool displayChanges = false;
+ const renderengine::ShadowSettings& globalShadowSettings;
+ };
+ LayerSnapshotBuilder();
+
+ // Rebuild the snapshots from scratch.
+ LayerSnapshotBuilder(Args);
+
+ // Update an existing set of snapshot using change flags in RequestedLayerState
+ // and LayerLifecycleManager. This needs to be called before
+ // LayerLifecycleManager.commitChanges is called as that function will clear all
+ // change flags.
+ void update(const Args&);
+ std::vector<std::unique_ptr<LayerSnapshot>>& getSnapshots();
+
+private:
+ friend class LayerSnapshotTest;
+ LayerSnapshot* getSnapshot(uint32_t layerId) const;
+ LayerSnapshot* getSnapshot(const LayerHierarchy::TraversalPath& id) const;
+ static LayerSnapshot getRootSnapshot();
+
+ // return true if we were able to successfully update the snapshots via
+ // the fast path.
+ bool tryFastUpdate(const Args& args);
+
+ void updateSnapshots(const Args& args);
+
+ void updateSnapshotsInHierarchy(const Args&, const LayerHierarchy& hierarchy,
+ LayerHierarchy::TraversalPath& traversalPath,
+ const LayerSnapshot& parentSnapshot);
+ void updateSnapshot(LayerSnapshot& snapshot, const Args& args, const RequestedLayerState&,
+ const LayerSnapshot& parentSnapshot,
+ const LayerHierarchy::TraversalPath& path);
+ static void updateRelativeState(LayerSnapshot& snapshot, const LayerSnapshot& parentSnapshot,
+ bool parentIsRelative, const Args& args);
+ static void resetRelativeState(LayerSnapshot& snapshot);
+ static void updateRoundedCorner(LayerSnapshot& snapshot, const RequestedLayerState& layerState,
+ const LayerSnapshot& parentSnapshot);
+ static void updateLayerBounds(LayerSnapshot& snapshot, const RequestedLayerState& layerState,
+ const LayerSnapshot& parentSnapshot,
+ uint32_t displayRotationFlags);
+ static void updateShadows(LayerSnapshot& snapshot, const RequestedLayerState& requested,
+ const renderengine::ShadowSettings& globalShadowSettings);
+ void updateInput(LayerSnapshot& snapshot, const RequestedLayerState& requested,
+ const LayerSnapshot& parentSnapshot, const frontend::DisplayInfo& displayInfo,
+ bool noValidDisplay, const LayerHierarchy::TraversalPath& path);
+ void sortSnapshotsByZ(const Args& args);
+ LayerSnapshot* getOrCreateSnapshot(const LayerHierarchy::TraversalPath& id,
+ const RequestedLayerState& layer);
+
+ struct TraversalPathHash {
+ std::size_t operator()(const LayerHierarchy::TraversalPath& key) const {
+ uint32_t hashCode = key.id * 31;
+ for (auto mirrorRoot : key.mirrorRootIds) {
+ hashCode += mirrorRoot * 31;
+ }
+ return std::hash<size_t>{}(hashCode);
+ }
+ };
+ std::unordered_map<LayerHierarchy::TraversalPath, LayerSnapshot*, TraversalPathHash>
+ mIdToSnapshot;
+ std::vector<std::unique_ptr<LayerSnapshot>> mSnapshots;
+ LayerSnapshot mRootSnapshot;
+};
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index 054382c..dcc16e8 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -118,7 +118,7 @@
void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerState) {
bool oldFlags = flags;
- Rect oldBufferSize = getBufferSize();
+ Rect oldBufferSize = getBufferSize(0);
const layer_state_t& clientState = resolvedComposerState.state;
uint64_t clientChanges = what | layer_state_t::diff(clientState);
@@ -133,7 +133,7 @@
changes |= RequestedLayerState::Changes::Geometry;
}
}
- if (clientState.what & layer_state_t::eBufferChanged && oldBufferSize != getBufferSize()) {
+ if (clientState.what & layer_state_t::eBufferChanged && oldBufferSize != getBufferSize(0)) {
changes |= RequestedLayerState::Changes::Geometry;
}
if (clientChanges & layer_state_t::HIERARCHY_CHANGES)
@@ -142,6 +142,8 @@
changes |= RequestedLayerState::Changes::Content;
if (clientChanges & layer_state_t::GEOMETRY_CHANGES)
changes |= RequestedLayerState::Changes::Geometry;
+ if (clientChanges & layer_state_t::AFFECTS_CHILDREN)
+ changes |= RequestedLayerState::Changes::AffectsChildren;
if (clientState.what & layer_state_t::eColorTransformChanged) {
static const mat4 identityMatrix = mat4();
@@ -205,7 +207,22 @@
}
}
-ui::Transform RequestedLayerState::getTransform() const {
+ui::Size RequestedLayerState::getUnrotatedBufferSize(uint32_t displayRotationFlags) const {
+ uint32_t bufferWidth = externalTexture->getWidth();
+ uint32_t bufferHeight = externalTexture->getHeight();
+ // Undo any transformations on the buffer.
+ if (bufferTransform & ui::Transform::ROT_90) {
+ std::swap(bufferWidth, bufferHeight);
+ }
+ if (transformToDisplayInverse) {
+ if (displayRotationFlags & ui::Transform::ROT_90) {
+ std::swap(bufferWidth, bufferHeight);
+ }
+ }
+ return {bufferWidth, bufferHeight};
+}
+
+ui::Transform RequestedLayerState::getTransform(uint32_t displayRotationFlags) const {
if ((flags & layer_state_t::eIgnoreDestinationFrame) || destinationFrame.isEmpty()) {
// If destination frame is not set, use the requested transform set via
// Transaction::setPosition and Transaction::setMatrix.
@@ -230,22 +247,10 @@
return transform;
}
- uint32_t bufferWidth = externalTexture->getWidth();
- uint32_t bufferHeight = externalTexture->getHeight();
- // Undo any transformations on the buffer.
- if (bufferTransform & ui::Transform::ROT_90) {
- std::swap(bufferWidth, bufferHeight);
- }
- // TODO(b/238781169) remove dep
- uint32_t invTransform = DisplayDevice::getPrimaryDisplayRotationFlags();
- if (transformToDisplayInverse) {
- if (invTransform & ui::Transform::ROT_90) {
- std::swap(bufferWidth, bufferHeight);
- }
- }
+ ui::Size bufferSize = getUnrotatedBufferSize(displayRotationFlags);
- float sx = static_cast<float>(destW) / static_cast<float>(bufferWidth);
- float sy = static_cast<float>(destH) / static_cast<float>(bufferHeight);
+ float sx = static_cast<float>(destW) / static_cast<float>(bufferSize.width);
+ float sy = static_cast<float>(destH) / static_cast<float>(bufferSize.height);
ui::Transform transform;
transform.set(sx, 0, 0, sy);
transform.set(static_cast<float>(destRect.left), static_cast<float>(destRect.top));
@@ -279,7 +284,7 @@
}
return color;
}
-Rect RequestedLayerState::getBufferSize() const {
+Rect RequestedLayerState::getBufferSize(uint32_t displayRotationFlags) const {
// for buffer state layers we use the display frame size as the buffer size.
if (!externalTexture) {
return Rect::INVALID_RECT;
@@ -294,8 +299,7 @@
}
if (transformToDisplayInverse) {
- // TODO(b/238781169) pass in display metrics (would be useful for input info as well
- uint32_t invTransform = DisplayDevice::getPrimaryDisplayRotationFlags();
+ uint32_t invTransform = displayRotationFlags;
if (invTransform & ui::Transform::ROT_90) {
std::swap(bufWidth, bufHeight);
}
@@ -304,8 +308,8 @@
return Rect(0, 0, static_cast<int32_t>(bufWidth), static_cast<int32_t>(bufHeight));
}
-Rect RequestedLayerState::getCroppedBufferSize() const {
- Rect size = getBufferSize();
+Rect RequestedLayerState::getCroppedBufferSize(const Rect& bufferSize) const {
+ Rect size = bufferSize;
if (!crop.isEmpty() && size.isValid()) {
size.intersect(crop, &size);
} else if (!crop.isEmpty()) {
@@ -367,4 +371,13 @@
return isRelativeOf && parentId != relativeParentId;
}
+bool RequestedLayerState::hasInputInfo() const {
+ if (!windowInfoHandle) {
+ return false;
+ }
+ const auto windowInfo = windowInfoHandle->getInfo();
+ return windowInfo->token != nullptr ||
+ windowInfo->inputConfig.test(gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL);
+}
+
} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.h b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
index 7849165..95240d0 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.h
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
@@ -47,22 +47,26 @@
RelativeParent = 1u << 9,
Metadata = 1u << 10,
Visibility = 1u << 11,
+ AffectsChildren = 1u << 12,
};
static Rect reduce(const Rect& win, const Region& exclude);
RequestedLayerState(const LayerCreationArgs&);
void merge(const ResolvedComposerState&);
- ui::Transform getTransform() const;
+ // Currently we only care about the primary display
+ ui::Transform getTransform(uint32_t displayRotationFlags) const;
+ ui::Size getUnrotatedBufferSize(uint32_t displayRotationFlags) const;
bool canBeDestroyed() const;
bool isRoot() const;
bool isHiddenByPolicy() const;
half4 getColor() const;
- Rect getBufferSize() const;
- Rect getCroppedBufferSize() const;
+ Rect getBufferSize(uint32_t displayRotationFlags) const;
+ Rect getCroppedBufferSize(const Rect& bufferSize) const;
Rect getBufferCrop() const;
std::string getDebugString() const;
std::string getDebugStringShort() const;
aidl::android::hardware::graphics::composer3::Composition getCompositionType() const;
bool hasValidRelativeParent() const;
+ bool hasInputInfo() const;
// Layer serial number. This gives layers an explicit ordering, so we
// have a stable sort order when their layer stack and Z-order are
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 6197e9b..df76ed0 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -128,6 +128,8 @@
using namespace ftl::flag_operators;
using base::StringAppendF;
+using frontend::LayerSnapshot;
+using frontend::RoundedCornerState;
using gui::GameMode;
using gui::LayerMetadata;
using gui::WindowInfo;
@@ -208,7 +210,7 @@
mSnapshot->name = getDebugName();
mSnapshot->textureName = mTextureName;
mSnapshot->premultipliedAlpha = mPremultipliedAlpha;
- mSnapshot->transform = {};
+ mSnapshot->parentTransform = {};
}
void Layer::onFirstRef() {
@@ -564,7 +566,7 @@
snapshot->geomLayerTransform = getTransform();
snapshot->geomInverseLayerTransform = snapshot->geomLayerTransform.inverse();
snapshot->transparentRegionHint = getActiveTransparentRegion(drawingState);
- snapshot->blurRegionTransform = getActiveTransform(drawingState).inverse();
+ snapshot->localTransformInverse = getActiveTransform(drawingState).inverse();
snapshot->blendMode = static_cast<Hwc2::IComposerClient::BlendMode>(blendMode);
snapshot->alpha = alpha;
snapshot->backgroundBlurRadius = drawingState.backgroundBlurRadius;
@@ -2751,18 +2753,18 @@
void Layer::onSurfaceFrameCreated(
const std::shared_ptr<frametimeline::SurfaceFrame>& surfaceFrame) {
- if (!hasBufferOrSidebandStreamInDrawing()) {
- return;
- }
-
while (mPendingJankClassifications.size() >= kPendingClassificationMaxSurfaceFrames) {
// Too many SurfaceFrames pending classification. The front of the deque is probably not
// tracked by FrameTimeline and will never be presented. This will only result in a memory
// leak.
- ALOGW("Removing the front of pending jank deque from layer - %s to prevent memory leak",
- mName.c_str());
- std::string miniDump = mPendingJankClassifications.front()->miniDump();
- ALOGD("Head SurfaceFrame mini dump\n%s", miniDump.c_str());
+ if (hasBufferOrSidebandStreamInDrawing()) {
+ // Only log for layers with a buffer, since we expect the jank data to be drained for
+ // these, while there may be no jank listeners for bufferless layers.
+ ALOGW("Removing the front of pending jank deque from layer - %s to prevent memory leak",
+ mName.c_str());
+ std::string miniDump = mPendingJankClassifications.front()->miniDump();
+ ALOGD("Head SurfaceFrame mini dump\n%s", miniDump.c_str());
+ }
mPendingJankClassifications.pop_front();
}
mPendingJankClassifications.emplace_back(surfaceFrame);
@@ -3617,7 +3619,7 @@
}
// If the buffer has no alpha channel, then we are opaque
- if (hasBufferOrSidebandStream() && isOpaqueFormat(getPixelFormat())) {
+ if (hasBufferOrSidebandStream() && LayerSnapshot::isOpaqueFormat(getPixelFormat())) {
return true;
}
@@ -3791,29 +3793,6 @@
(mBufferInfo.mBuffer->getUsage() & GRALLOC_USAGE_PROTECTED);
}
-// As documented in libhardware header, formats in the range
-// 0x100 - 0x1FF are specific to the HAL implementation, and
-// are known to have no alpha channel
-// TODO: move definition for device-specific range into
-// hardware.h, instead of using hard-coded values here.
-#define HARDWARE_IS_DEVICE_FORMAT(f) ((f) >= 0x100 && (f) <= 0x1FF)
-
-bool Layer::isOpaqueFormat(PixelFormat format) {
- if (HARDWARE_IS_DEVICE_FORMAT(format)) {
- return true;
- }
- switch (format) {
- case PIXEL_FORMAT_RGBA_8888:
- case PIXEL_FORMAT_BGRA_8888:
- case PIXEL_FORMAT_RGBA_FP16:
- case PIXEL_FORMAT_RGBA_1010102:
- case PIXEL_FORMAT_R_8:
- return false;
- }
- // in all other case, we have no blending (also for unknown formats)
- return true;
-}
-
bool Layer::needsFiltering(const DisplayDevice* display) const {
if (!hasBufferOrSidebandStream()) {
return false;
@@ -4004,13 +3983,15 @@
snapshot->shadowSettings.length = mEffectiveShadowRadius;
}
snapshot->contentOpaque = isOpaque(mDrawingState);
+ snapshot->layerOpaqueFlagSet =
+ (mDrawingState.flags & layer_state_t::eLayerOpaque) == layer_state_t::eLayerOpaque;
snapshot->isHdrY410 = isHdrY410();
snapshot->bufferNeedsFiltering = bufferNeedsFiltering();
sp<Layer> p = mDrawingParent.promote();
if (p != nullptr) {
- snapshot->transform = p->getTransform();
+ snapshot->parentTransform = p->getTransform();
} else {
- snapshot->transform.reset();
+ snapshot->parentTransform.reset();
}
snapshot->bufferSize = getBufferSize(mDrawingState);
snapshot->externalTexture = mBufferInfo.mBuffer;
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 63894cd..7631f5d 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -324,8 +324,8 @@
virtual sp<LayerFE> getCompositionEngineLayerFE() const;
virtual sp<LayerFE> copyCompositionEngineLayerFE() const;
- const LayerSnapshot* getLayerSnapshot() const;
- LayerSnapshot* editLayerSnapshot();
+ const frontend::LayerSnapshot* getLayerSnapshot() const;
+ frontend::LayerSnapshot* editLayerSnapshot();
// If we have received a new buffer this frame, we will pass its surface
// damage down to hardware composer. Otherwise, we must send a region with
@@ -464,7 +464,7 @@
// Returns how rounded corners should be drawn for this layer.
// A layer can override its parent's rounded corner settings if the parent's rounded
// corner crop does not intersect with its own rounded corner crop.
- virtual RoundedCornerState getRoundedCornerState() const;
+ virtual frontend::RoundedCornerState getRoundedCornerState() const;
bool hasRoundedCorners() const { return getRoundedCornerState().hasRoundedCorners(); }
@@ -762,12 +762,12 @@
*/
bool hasInputInfo() const;
- // Sets the GameMode for the tree rooted at this layer. A layer in the tree inherits this
- // GameMode unless it (or an ancestor) has GAME_MODE_METADATA.
- void setGameModeForTree(GameMode);
+ // Sets the gui::GameMode for the tree rooted at this layer. A layer in the tree inherits this
+ // gui::GameMode unless it (or an ancestor) has GAME_MODE_METADATA.
+ void setGameModeForTree(gui::GameMode);
- void setGameMode(GameMode gameMode) { mGameMode = gameMode; }
- GameMode getGameMode() const { return mGameMode; }
+ void setGameMode(gui::GameMode gameMode) { mGameMode = gameMode; }
+ gui::GameMode getGameMode() const { return mGameMode; }
virtual uid_t getOwnerUid() const { return mOwnerUid; }
@@ -843,7 +843,9 @@
void gatherBufferInfo();
void onSurfaceFrameCreated(const std::shared_ptr<frametimeline::SurfaceFrame>&);
- sp<Layer> getClonedFrom() { return mClonedFrom != nullptr ? mClonedFrom.promote() : nullptr; }
+ sp<Layer> getClonedFrom() const {
+ return mClonedFrom != nullptr ? mClonedFrom.promote() : nullptr;
+ }
bool isClone() { return mClonedFrom != nullptr; }
bool isClonedFromAlive() { return getClonedFrom() != nullptr; }
@@ -1086,7 +1088,7 @@
float mEffectiveShadowRadius = 0.f;
// Game mode for the layer. Set by WindowManagerShell and recorded by SurfaceFlingerStats.
- GameMode mGameMode = GameMode::Unsupported;
+ gui::GameMode mGameMode = gui::GameMode::Unsupported;
// A list of regions on this layer that should have blurs.
const std::vector<BlurRegion> getBlurRegions() const;
@@ -1142,7 +1144,8 @@
ui::Transform mRequestedTransform;
sp<LayerFE> mLayerFE;
- std::unique_ptr<LayerSnapshot> mSnapshot = std::make_unique<LayerSnapshot>();
+ std::unique_ptr<frontend::LayerSnapshot> mSnapshot =
+ std::make_unique<frontend::LayerSnapshot>();
friend class LayerSnapshotGuard;
};
diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp
index 363adc6..c31a2e3 100644
--- a/services/surfaceflinger/LayerFE.cpp
+++ b/services/surfaceflinger/LayerFE.cpp
@@ -148,14 +148,14 @@
case LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled:
layerSettings.backgroundBlurRadius = mSnapshot->backgroundBlurRadius;
layerSettings.blurRegions = mSnapshot->blurRegions;
- layerSettings.blurRegionTransform = mSnapshot->blurRegionTransform.asMatrix4();
+ layerSettings.blurRegionTransform = mSnapshot->localTransformInverse.asMatrix4();
break;
case LayerFE::ClientCompositionTargetSettings::BlurSetting::BackgroundBlurOnly:
layerSettings.backgroundBlurRadius = mSnapshot->backgroundBlurRadius;
break;
case LayerFE::ClientCompositionTargetSettings::BlurSetting::BlurRegionsOnly:
layerSettings.blurRegions = mSnapshot->blurRegions;
- layerSettings.blurRegionTransform = mSnapshot->blurRegionTransform.asMatrix4();
+ layerSettings.blurRegionTransform = mSnapshot->localTransformInverse.asMatrix4();
break;
case LayerFE::ClientCompositionTargetSettings::BlurSetting::Disabled:
default:
@@ -275,7 +275,7 @@
* of a camera where the buffer remains in native orientation,
* we want the pixels to always be upright.
*/
- const auto parentTransform = mSnapshot->transform;
+ const auto parentTransform = mSnapshot->parentTransform;
tr = tr * inverseOrientation(parentTransform.getOrientation());
// and finally apply it to the original texture matrix
diff --git a/services/surfaceflinger/LayerFE.h b/services/surfaceflinger/LayerFE.h
index 822bcb7..01da019 100644
--- a/services/surfaceflinger/LayerFE.h
+++ b/services/surfaceflinger/LayerFE.h
@@ -17,48 +17,12 @@
#pragma once
#include <gui/LayerMetadata.h>
-
+#include "FrontEnd/LayerSnapshot.h"
#include "compositionengine/LayerFE.h"
#include "compositionengine/LayerFECompositionState.h"
#include "renderengine/LayerSettings.h"
namespace android {
-struct RoundedCornerState {
- RoundedCornerState() = default;
- RoundedCornerState(const FloatRect& cropRect, const vec2& radius)
- : cropRect(cropRect), radius(radius) {}
-
- // Rounded rectangle in local layer coordinate space.
- FloatRect cropRect = FloatRect();
- // Radius of the rounded rectangle.
- vec2 radius;
- bool hasRoundedCorners() const { return radius.x > 0.0f && radius.y > 0.0f; }
-};
-
-// LayerSnapshot stores Layer state used by CompositionEngine and RenderEngine. Composition
-// Engine uses a pointer to LayerSnapshot (as LayerFECompositionState*) and the LayerSettings
-// passed to Render Engine are created using properties stored on this struct.
-struct LayerSnapshot : public compositionengine::LayerFECompositionState {
- int32_t sequence;
- std::string name;
- uint32_t textureName;
- bool contentOpaque;
- RoundedCornerState roundedCorner;
- StretchEffect stretchEffect;
- FloatRect transformedBounds;
- renderengine::ShadowSettings shadowSettings;
- bool premultipliedAlpha;
- bool isHdrY410;
- bool bufferNeedsFiltering;
- ui::Transform transform;
- Rect bufferSize;
- std::shared_ptr<renderengine::ExternalTexture> externalTexture;
- gui::LayerMetadata layerMetadata;
- gui::LayerMetadata relativeLayerMetadata;
- bool contentDirty;
- bool hasReadyFrame;
- ui::Transform blurRegionTransform;
-};
struct CompositionResult {
// TODO(b/238781169) update CE to no longer pass refreshStartTime to LayerFE::onPreComposition
@@ -86,7 +50,7 @@
compositionengine::LayerFE::ClientCompositionTargetSettings&) const;
CompositionResult&& stealCompositionResult();
- std::unique_ptr<LayerSnapshot> mSnapshot;
+ std::unique_ptr<surfaceflinger::frontend::LayerSnapshot> mSnapshot;
private:
std::optional<compositionengine::LayerFE::LayerSettings> prepareClientCompositionInternal(
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index 04c2d41..30821d8 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -32,6 +32,7 @@
#include <ftl/fake_guard.h>
#include <ftl/match.h>
#include <ftl/unit.h>
+#include <gui/TraceUtils.h>
#include <scheduler/FrameRateMode.h>
#include <utils/Trace.h>
@@ -416,8 +417,10 @@
// Keep the display at max frame rate for the duration of powering on the display.
if (signals.powerOnImminent) {
ALOGV("Power On Imminent");
- return {rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Descending),
- GlobalSignals{.powerOnImminent = true}};
+ const auto ranking = rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Descending);
+ ATRACE_FORMAT_INSTANT("%s (Power On Imminent)",
+ to_string(ranking.front().frameRateMode.fps).c_str());
+ return {ranking, GlobalSignals{.powerOnImminent = true}};
}
int noVoteLayers = 0;
@@ -476,8 +479,10 @@
// selected a refresh rate to see if we should apply touch boost.
if (signals.touch && !hasExplicitVoteLayers) {
ALOGV("Touch Boost");
- return {rankFrameRates(anchorGroup, RefreshRateOrder::Descending),
- GlobalSignals{.touch = true}};
+ const auto ranking = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
+ ATRACE_FORMAT_INSTANT("%s (Touch Boost)",
+ to_string(ranking.front().frameRateMode.fps).c_str());
+ return {ranking, GlobalSignals{.touch = true}};
}
// If the primary range consists of a single refresh rate then we can only
@@ -488,19 +493,26 @@
if (!signals.touch && signals.idle && !(primaryRangeIsSingleRate && hasExplicitVoteLayers)) {
ALOGV("Idle");
- return {rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Ascending),
- GlobalSignals{.idle = true}};
+ const auto ranking = rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Ascending);
+ ATRACE_FORMAT_INSTANT("%s (Idle)", to_string(ranking.front().frameRateMode.fps).c_str());
+ return {ranking, GlobalSignals{.idle = true}};
}
if (layers.empty() || noVoteLayers == layers.size()) {
ALOGV("No layers with votes");
- return {rankFrameRates(anchorGroup, RefreshRateOrder::Descending), kNoSignals};
+ const auto ranking = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
+ ATRACE_FORMAT_INSTANT("%s (No layers with votes)",
+ to_string(ranking.front().frameRateMode.fps).c_str());
+ return {ranking, kNoSignals};
}
// Only if all layers want Min we should return Min
if (noVoteLayers + minVoteLayers == layers.size()) {
ALOGV("All layers Min");
- return {rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Ascending), kNoSignals};
+ const auto ranking = rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Ascending);
+ ATRACE_FORMAT_INSTANT("%s (All layers Min)",
+ to_string(ranking.front().frameRateMode.fps).c_str());
+ return {ranking, kNoSignals};
}
// Find the best refresh rate based on score
@@ -670,8 +682,13 @@
// range instead of picking a random score from the app range.
if (noLayerScore) {
ALOGV("Layers not scored");
- return {rankFrameRates(anchorGroup, RefreshRateOrder::Descending), kNoSignals};
+ const auto descending = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
+ ATRACE_FORMAT_INSTANT("%s (Layers not scored)",
+ to_string(descending.front().frameRateMode.fps).c_str());
+ return {descending, kNoSignals};
} else {
+ ATRACE_FORMAT_INSTANT("%s (primaryRangeIsSingleRate)",
+ to_string(ranking.front().frameRateMode.fps).c_str());
return {ranking, kNoSignals};
}
}
@@ -696,17 +713,22 @@
if (signals.touch && explicitDefaultVoteLayers == 0 && touchBoostForExplicitExact &&
scores.front().frameRateMode.fps < touchRefreshRates.front().frameRateMode.fps) {
ALOGV("Touch Boost");
+ ATRACE_FORMAT_INSTANT("%s (Touch Boost [late])",
+ to_string(touchRefreshRates.front().frameRateMode.fps).c_str());
return {touchRefreshRates, GlobalSignals{.touch = true}};
}
// If we never scored any layers, and we don't favor high refresh rates, prefer to stay with the
// current config
if (noLayerScore && refreshRateOrder == RefreshRateOrder::Ascending) {
- const auto preferredDisplayMode = activeMode.getId();
- return {rankFrameRates(anchorGroup, RefreshRateOrder::Ascending, preferredDisplayMode),
- kNoSignals};
+ const auto ascendingWithPreferred =
+ rankFrameRates(anchorGroup, RefreshRateOrder::Ascending, activeMode.getId());
+ ATRACE_FORMAT_INSTANT("%s (preferredDisplayMode)",
+ to_string(ascendingWithPreferred.front().frameRateMode.fps).c_str());
+ return {ascendingWithPreferred, kNoSignals};
}
+ ATRACE_FORMAT_INSTANT("%s (scored))", to_string(ranking.front().frameRateMode.fps).c_str());
return {ranking, kNoSignals};
}
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index ed4d25e..02e12fd 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -47,7 +47,7 @@
VSyncPredictor::VSyncPredictor(nsecs_t idealPeriod, size_t historySize,
size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent)
- : mTraceOn(property_get_bool("debug.sf.vsp_trace", true)),
+ : mTraceOn(property_get_bool("debug.sf.vsp_trace", false)),
kHistorySize(historySize),
kMinimumSamplesForPrediction(minimumSamplesForPrediction),
kOutlierTolerancePercent(std::min(outlierTolerancePercent, kMaxPercent)),
@@ -61,6 +61,10 @@
}
}
+inline void VSyncPredictor::traceInt64(const char* name, int64_t value) const {
+ ATRACE_INT64(name, value);
+}
+
inline size_t VSyncPredictor::next(size_t i) const {
return (i + 1) % mTimestamps.size();
}
@@ -124,6 +128,8 @@
mTimestamps[mLastTimestampIndex] = timestamp;
}
+ traceInt64If("VSP-ts", timestamp);
+
const size_t numSamples = mTimestamps.size();
if (numSamples < kMinimumSamplesForPrediction) {
mRateMap[mIdealPeriod] = {mIdealPeriod, 0};
@@ -161,8 +167,6 @@
nsecs_t meanOrdinal = 0;
for (size_t i = 0; i < numSamples; i++) {
- traceInt64If("VSP-ts", mTimestamps[i]);
-
const auto timestamp = mTimestamps[i] - oldestTS;
vsyncTS[i] = timestamp;
meanTS += timestamp;
@@ -219,7 +223,7 @@
auto const [slope, intercept] = getVSyncPredictionModelLocked();
if (mTimestamps.empty()) {
- traceInt64If("VSP-mode", 1);
+ traceInt64("VSP-mode", 1);
auto const knownTimestamp = mKnownTimestamp ? *mKnownTimestamp : timePoint;
auto const numPeriodsOut = ((timePoint - knownTimestamp) / mIdealPeriod) + 1;
return knownTimestamp + numPeriodsOut * mIdealPeriod;
@@ -232,7 +236,7 @@
auto const ordinalRequest = (timePoint - zeroPoint + slope) / slope;
auto const prediction = (ordinalRequest * slope) + intercept + oldest;
- traceInt64If("VSP-mode", 0);
+ traceInt64("VSP-mode", 0);
traceInt64If("VSP-timePoint", timePoint);
traceInt64If("VSP-prediction", prediction);
@@ -340,6 +344,7 @@
void VSyncPredictor::setPeriod(nsecs_t period) {
ATRACE_CALL();
+ traceInt64("VSP-setPeriod", period);
std::lock_guard lock(mMutex);
static constexpr size_t kSizeLimit = 30;
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h
index 4a3ba67..305cdb0 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.h
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h
@@ -77,6 +77,7 @@
void clearTimestamps() REQUIRES(mMutex);
inline void traceInt64If(const char* name, int64_t value) const;
+ inline void traceInt64(const char* name, int64_t value) const;
bool const mTraceOn;
size_t const kHistorySize;
diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.cpp b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
index e23945d..b5f212e 100644
--- a/services/surfaceflinger/Scheduler/VSyncReactor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
@@ -129,7 +129,7 @@
}
void VSyncReactor::startPeriodTransition(nsecs_t period) {
- ATRACE_INT64("VSR-setPeriod", period);
+ ATRACE_INT64("VSR-startPeriodTransition", period);
std::lock_guard lock(mMutex);
mLastHwVsync.reset();
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 41e305e..cb3c94f 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -1152,6 +1152,10 @@
switch (display->setDesiredActiveMode(DisplayDevice::ActiveModeInfo(std::move(request)),
force)) {
case DisplayDevice::DesiredActiveModeAction::InitiateDisplayModeSwitch:
+ // Set the render rate as setDesiredActiveMode updated it.
+ mScheduler->setRenderRate(display->refreshRateSelector().getActiveMode().fps);
+
+ // Schedule a new frame to initiate the display mode switch.
scheduleComposite(FrameHint::kNone);
// Start receiving vsync samples now, so that we can detect a period
@@ -1484,14 +1488,26 @@
std::transform(combination.pixelFormats.cbegin(), combination.pixelFormats.cend(),
std::back_inserter(pixelFormats),
[](const auto& val) { return static_cast<int32_t>(val); });
- std::vector<int32_t> dataspaces;
- dataspaces.reserve(combination.dataspaces.size());
- std::transform(combination.dataspaces.cbegin(), combination.dataspaces.cend(),
- std::back_inserter(dataspaces),
+ std::vector<int32_t> standards;
+ standards.reserve(combination.standards.size());
+ std::transform(combination.standards.cbegin(), combination.standards.cend(),
+ std::back_inserter(standards),
+ [](const auto& val) { return static_cast<int32_t>(val); });
+ std::vector<int32_t> transfers;
+ transfers.reserve(combination.transfers.size());
+ std::transform(combination.transfers.cbegin(), combination.transfers.cend(),
+ std::back_inserter(transfers),
+ [](const auto& val) { return static_cast<int32_t>(val); });
+ std::vector<int32_t> ranges;
+ ranges.reserve(combination.ranges.size());
+ std::transform(combination.ranges.cbegin(), combination.ranges.cend(),
+ std::back_inserter(ranges),
[](const auto& val) { return static_cast<int32_t>(val); });
gui::OverlayProperties::SupportedBufferCombinations outCombination;
outCombination.pixelFormats = std::move(pixelFormats);
- outCombination.dataspaces = std::move(dataspaces);
+ outCombination.standards = std::move(standards);
+ outCombination.transfers = std::move(transfers);
+ outCombination.ranges = std::move(ranges);
outProperties->combinations.emplace_back(outCombination);
}
outProperties->supportMixedColorSpaces = aidlProperties.supportMixedColorSpaces;
@@ -1833,17 +1849,6 @@
return NO_ERROR;
}
-bool SurfaceFlinger::hasVisibleHdrLayer(const sp<DisplayDevice>& display) {
- bool hasHdrLayers = false;
- mDrawingState.traverse([&,
- compositionDisplay = display->getCompositionDisplay()](Layer* layer) {
- hasHdrLayers |= (layer->isVisible() &&
- compositionDisplay->includesLayer(layer->getCompositionEngineLayerFE()) &&
- isHdrDataspace(layer->getDataSpace()));
- });
- return hasHdrLayers;
-}
-
status_t SurfaceFlinger::setDisplayBrightness(const sp<IBinder>& displayToken,
const gui::DisplayBrightness& brightness) {
if (!displayToken) {
@@ -2638,7 +2643,8 @@
int32_t maxArea = 0;
mDrawingState.traverse([&, compositionDisplay = compositionDisplay](Layer* layer) {
const auto layerFe = layer->getCompositionEngineLayerFE();
- if (layer->isVisible() && compositionDisplay->includesLayer(layerFe)) {
+ if (layer->isVisible() &&
+ compositionDisplay->includesLayer(layer->getOutputFilter())) {
if (isHdrLayer(layer)) {
const auto* outputLayer =
compositionDisplay->getOutputLayerForLayer(layerFe);
@@ -2978,15 +2984,15 @@
const auto enableFrameRateOverride = [&] {
using Config = scheduler::RefreshRateSelector::Config;
- if (!sysprop::enable_frame_rate_override(true)) {
+ if (!sysprop::enable_frame_rate_override(false)) {
return Config::FrameRateOverride::Disabled;
}
- if (sysprop::frame_rate_override_for_native_rates(false)) {
+ if (sysprop::frame_rate_override_for_native_rates(true)) {
return Config::FrameRateOverride::AppOverrideNativeRefreshRates;
}
- if (!sysprop::frame_rate_override_global(true)) {
+ if (!sysprop::frame_rate_override_global(false)) {
return Config::FrameRateOverride::AppOverride;
}
@@ -3860,6 +3866,10 @@
if (const bool scheduled = mTransactionFlags.fetch_or(mask) & mask; !scheduled) {
scheduleCommit(frameHint);
+ } else if (frameHint == FrameHint::kActive) {
+ // Even if the next frame is already scheduled, we should reset the idle timer
+ // as a new activity just happened.
+ mScheduler->resetIdleTimer();
}
}
@@ -3990,7 +4000,7 @@
transaction.displays, transaction.flags,
transaction.inputWindowCommands,
transaction.desiredPresentTime, transaction.isAutoTimestamp,
- transaction.buffer, transaction.postTime,
+ std::move(transaction.uncacheBufferIds), transaction.postTime,
transaction.permissions, transaction.hasListenerCallbacks,
transaction.listenerCallbacks, transaction.originPid,
transaction.originUid, transaction.id);
@@ -4078,8 +4088,9 @@
const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& states,
const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime,
- bool isAutoTimestamp, const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,
- const std::vector<ListenerCallbacks>& listenerCallbacks, uint64_t transactionId) {
+ bool isAutoTimestamp, const std::vector<client_cache_t>& uncacheBuffers,
+ bool hasListenerCallbacks, const std::vector<ListenerCallbacks>& listenerCallbacks,
+ uint64_t transactionId) {
ATRACE_CALL();
uint32_t permissions =
@@ -4113,6 +4124,15 @@
const int originPid = ipc->getCallingPid();
const int originUid = ipc->getCallingUid();
+ std::vector<uint64_t> uncacheBufferIds;
+ uncacheBufferIds.reserve(uncacheBuffers.size());
+ for (const auto& uncacheBuffer : uncacheBuffers) {
+ sp<GraphicBuffer> buffer = ClientCache::getInstance().erase(uncacheBuffer);
+ if (buffer != nullptr) {
+ uncacheBufferIds.push_back(buffer->getId());
+ }
+ }
+
std::vector<ResolvedComposerState> resolvedStates;
resolvedStates.reserve(states.size());
for (auto& state : states) {
@@ -4130,14 +4150,22 @@
}
}
- TransactionState state{frameTimelineInfo, resolvedStates,
- displays, flags,
- applyToken, inputWindowCommands,
- desiredPresentTime, isAutoTimestamp,
- uncacheBuffer, postTime,
- permissions, hasListenerCallbacks,
- listenerCallbacks, originPid,
- originUid, transactionId};
+ TransactionState state{frameTimelineInfo,
+ resolvedStates,
+ displays,
+ flags,
+ applyToken,
+ inputWindowCommands,
+ desiredPresentTime,
+ isAutoTimestamp,
+ std::move(uncacheBufferIds),
+ postTime,
+ permissions,
+ hasListenerCallbacks,
+ listenerCallbacks,
+ originPid,
+ originUid,
+ transactionId};
if (mTransactionTracing) {
mTransactionTracing->addQueuedTransaction(state);
@@ -4161,7 +4189,7 @@
Vector<DisplayState>& displays, uint32_t flags,
const InputWindowCommands& inputWindowCommands,
const int64_t desiredPresentTime, bool isAutoTimestamp,
- const client_cache_t& uncacheBuffer,
+ const std::vector<uint64_t>& uncacheBufferIds,
const int64_t postTime, uint32_t permissions,
bool hasListenerCallbacks,
const std::vector<ListenerCallbacks>& listenerCallbacks,
@@ -4202,11 +4230,8 @@
ALOGE("Only privileged callers are allowed to send input commands.");
}
- if (uncacheBuffer.isValid()) {
- sp<GraphicBuffer> buffer = ClientCache::getInstance().erase(uncacheBuffer);
- if (buffer != nullptr) {
- mBufferIdsToUncache.push_back(buffer->getId());
- }
+ for (uint64_t uncacheBufferId : uncacheBufferIds) {
+ mBufferIdsToUncache.push_back(uncacheBufferId);
}
// If a synchronous transaction is explicitly requested without any changes, force a transaction
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index b52dc82..5e4015e 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -503,7 +503,8 @@
uint32_t flags, const sp<IBinder>& applyToken,
const InputWindowCommands& inputWindowCommands,
int64_t desiredPresentTime, bool isAutoTimestamp,
- const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,
+ const std::vector<client_cache_t>& uncacheBuffers,
+ bool hasListenerCallbacks,
const std::vector<ListenerCallbacks>& listenerCallbacks,
uint64_t transactionId) override;
void bootFinished();
@@ -675,9 +676,6 @@
void setPowerModeInternal(const sp<DisplayDevice>& display, hal::PowerMode mode)
REQUIRES(mStateLock, kMainThreadContext);
- // Returns true if the display has a visible HDR layer in its layer stack.
- bool hasVisibleHdrLayer(const sp<DisplayDevice>& display) REQUIRES(mStateLock);
-
// Returns the preferred mode for PhysicalDisplayId if the Scheduler has selected one for that
// display. Falls back to the display's defaultModeId otherwise.
ftl::Optional<scheduler::FrameRateMode> getPreferredDisplayMode(
@@ -719,16 +717,14 @@
/*
* Transactions
*/
- bool applyTransactionState(const FrameTimelineInfo& info,
- std::vector<ResolvedComposerState>& state,
- Vector<DisplayState>& displays, uint32_t flags,
- const InputWindowCommands& inputWindowCommands,
- const int64_t desiredPresentTime, bool isAutoTimestamp,
- const client_cache_t& uncacheBuffer, const int64_t postTime,
- uint32_t permissions, bool hasListenerCallbacks,
- const std::vector<ListenerCallbacks>& listenerCallbacks,
- int originPid, int originUid, uint64_t transactionId)
- REQUIRES(mStateLock);
+ bool applyTransactionState(
+ const FrameTimelineInfo& info, std::vector<ResolvedComposerState>& state,
+ Vector<DisplayState>& displays, uint32_t flags,
+ const InputWindowCommands& inputWindowCommands, const int64_t desiredPresentTime,
+ bool isAutoTimestamp, const std::vector<uint64_t>& uncacheBufferIds,
+ const int64_t postTime, uint32_t permissions, bool hasListenerCallbacks,
+ const std::vector<ListenerCallbacks>& listenerCallbacks, int originPid, int originUid,
+ uint64_t transactionId) REQUIRES(mStateLock);
// Flush pending transactions that were presented after desiredPresentTime.
bool flushTransactionQueues(VsyncId) REQUIRES(kMainThreadContext);
// Returns true if there is at least one transaction that needs to be flushed
diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/TransactionState.h
index 380301f..5025c49 100644
--- a/services/surfaceflinger/TransactionState.h
+++ b/services/surfaceflinger/TransactionState.h
@@ -43,7 +43,7 @@
const Vector<DisplayState>& displayStates, uint32_t transactionFlags,
const sp<IBinder>& applyToken, const InputWindowCommands& inputWindowCommands,
int64_t desiredPresentTime, bool isAutoTimestamp,
- const client_cache_t& uncacheBuffer, int64_t postTime, uint32_t permissions,
+ std::vector<uint64_t> uncacheBufferIds, int64_t postTime, uint32_t permissions,
bool hasListenerCallbacks, std::vector<ListenerCallbacks> listenerCallbacks,
int originPid, int originUid, uint64_t transactionId)
: frameTimelineInfo(frameTimelineInfo),
@@ -54,7 +54,7 @@
inputWindowCommands(inputWindowCommands),
desiredPresentTime(desiredPresentTime),
isAutoTimestamp(isAutoTimestamp),
- buffer(uncacheBuffer),
+ uncacheBufferIds(std::move(uncacheBufferIds)),
postTime(postTime),
permissions(permissions),
hasListenerCallbacks(hasListenerCallbacks),
@@ -91,7 +91,9 @@
if (!displays.empty()) return true;
for (const auto& state : states) {
- if (state.state.frameRateCompatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE) {
+ const bool frameRateChanged = state.state.what & layer_state_t::eFrameRateChanged;
+ if (!frameRateChanged ||
+ state.state.frameRateCompatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE) {
return true;
}
}
@@ -107,7 +109,7 @@
InputWindowCommands inputWindowCommands;
int64_t desiredPresentTime;
bool isAutoTimestamp;
- client_cache_t buffer;
+ std::vector<uint64_t> uncacheBufferIds;
int64_t postTime;
uint32_t permissions;
bool hasListenerCallbacks;
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
index 6b52a3a..83e0c0d 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
@@ -730,17 +730,18 @@
return mFlinger->mTransactionHandler.mPendingTransactionQueues;
}
- auto setTransactionState(const FrameTimelineInfo &frameTimelineInfo,
- Vector<ComposerState> &states, const Vector<DisplayState> &displays,
- uint32_t flags, const sp<IBinder> &applyToken,
- const InputWindowCommands &inputWindowCommands,
+ auto setTransactionState(const FrameTimelineInfo& frameTimelineInfo,
+ Vector<ComposerState>& states, const Vector<DisplayState>& displays,
+ uint32_t flags, const sp<IBinder>& applyToken,
+ const InputWindowCommands& inputWindowCommands,
int64_t desiredPresentTime, bool isAutoTimestamp,
- const client_cache_t &uncacheBuffer, bool hasListenerCallbacks,
- std::vector<ListenerCallbacks> &listenerCallbacks,
+ const std::vector<client_cache_t>& uncacheBuffers,
+ bool hasListenerCallbacks,
+ std::vector<ListenerCallbacks>& listenerCallbacks,
uint64_t transactionId) {
return mFlinger->setTransactionState(frameTimelineInfo, states, displays, flags, applyToken,
inputWindowCommands, desiredPresentTime,
- isAutoTimestamp, uncacheBuffer, hasListenerCallbacks,
+ isAutoTimestamp, uncacheBuffers, hasListenerCallbacks,
listenerCallbacks, transactionId);
}
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 824899b..87c3c65 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -94,6 +94,7 @@
"LayerMetadataTest.cpp",
"LayerHierarchyTest.cpp",
"LayerLifecycleManagerTest.cpp",
+ "LayerSnapshotTest.cpp",
"LayerTest.cpp",
"LayerTestUtils.cpp",
"MessageQueueTest.cpp",
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
index 8560902..33c9440 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
@@ -21,6 +21,7 @@
#include "FrontEnd/LayerHierarchy.h"
#include "FrontEnd/LayerLifecycleManager.h"
#include "Layer.h"
+#include "LayerHierarchyTest.h"
#include "gui/SurfaceComposerClient.h"
#define UPDATE_AND_VERIFY(HIERARCHY) \
@@ -31,16 +32,6 @@
namespace android::surfaceflinger::frontend {
-namespace {
-LayerCreationArgs createArgs(uint32_t id, bool canBeRoot, wp<IBinder> parent, wp<IBinder> mirror) {
- LayerCreationArgs args(nullptr, nullptr, "testlayer", 0, {}, std::make_optional(id));
- args.addToRoot = canBeRoot;
- args.parentHandle = parent;
- args.mirrorLayerHandle = mirror;
- return args;
-}
-} // namespace
-
// To run test:
/**
mp :libsurfaceflinger_unittest && adb sync; adb shell \
@@ -50,163 +41,9 @@
--gtest_brief=1
*/
-class LayerHierarchyTest : public testing::Test {
+class LayerHierarchyTest : public LayerHierarchyTestBase {
protected:
- LayerHierarchyTest() {
- // tree with 3 levels of children
- // ROOT
- // ├── 1
- // │ ├── 11
- // │ │ └── 111
- // │ ├── 12
- // │ │ ├── 121
- // │ │ └── 122
- // │ │ └── 1221
- // │ └── 13
- // └── 2
-
- createRootLayer(1);
- createRootLayer(2);
- createLayer(11, 1);
- createLayer(12, 1);
- createLayer(13, 1);
- createLayer(111, 11);
- createLayer(121, 12);
- createLayer(122, 12);
- createLayer(1221, 122);
- mLifecycleManager.commitChanges();
- }
- std::vector<uint32_t> getTraversalPath(const LayerHierarchy& hierarchy) const {
- std::vector<uint32_t> layerIds;
- hierarchy.traverse([&layerIds = layerIds](const LayerHierarchy& hierarchy,
- const LayerHierarchy::TraversalPath&) -> bool {
- layerIds.emplace_back(hierarchy.getLayer()->id);
- return true;
- });
- return layerIds;
- }
-
- std::vector<uint32_t> getTraversalPathInZOrder(const LayerHierarchy& hierarchy) const {
- std::vector<uint32_t> layerIds;
- hierarchy.traverseInZOrder(
- [&layerIds = layerIds](const LayerHierarchy& hierarchy,
- const LayerHierarchy::TraversalPath&) -> bool {
- layerIds.emplace_back(hierarchy.getLayer()->id);
- return true;
- });
- return layerIds;
- }
-
- void createRootLayer(uint32_t id) {
- sp<LayerHandle> handle = sp<LayerHandle>::make(id);
- mHandles[id] = handle;
- std::vector<std::unique_ptr<RequestedLayerState>> layers;
- layers.emplace_back(std::make_unique<RequestedLayerState>(
- createArgs(/*id=*/id, /*canBeRoot=*/true, /*parent=*/nullptr, /*mirror=*/nullptr)));
- mLifecycleManager.addLayers(std::move(layers));
- }
-
- void createLayer(uint32_t id, uint32_t parentId) {
- sp<LayerHandle> handle = sp<LayerHandle>::make(id);
- mHandles[id] = handle;
- std::vector<std::unique_ptr<RequestedLayerState>> layers;
- layers.emplace_back(std::make_unique<RequestedLayerState>(
- createArgs(/*id=*/id, /*canBeRoot=*/false, /*parent=*/mHandles[parentId],
- /*mirror=*/nullptr)));
- mLifecycleManager.addLayers(std::move(layers));
- }
-
- void reparentLayer(uint32_t id, uint32_t newParentId) {
- std::vector<TransactionState> transactions;
- transactions.emplace_back();
- transactions.back().states.push_back({});
-
- if (newParentId == UNASSIGNED_LAYER_ID) {
- transactions.back().states.front().state.parentSurfaceControlForChild = nullptr;
- } else {
- auto parentHandle = mHandles[newParentId];
- transactions.back().states.front().state.parentSurfaceControlForChild =
- sp<SurfaceControl>::make(SurfaceComposerClient::getDefault(), parentHandle,
- static_cast<int32_t>(newParentId), "Test");
- }
- transactions.back().states.front().state.what = layer_state_t::eReparent;
- transactions.back().states.front().state.surface = mHandles[id];
- mLifecycleManager.applyTransactions(transactions);
- }
-
- void reparentRelativeLayer(uint32_t id, uint32_t relativeParentId) {
- std::vector<TransactionState> transactions;
- transactions.emplace_back();
- transactions.back().states.push_back({});
-
- if (relativeParentId == UNASSIGNED_LAYER_ID) {
- transactions.back().states.front().state.what = layer_state_t::eLayerChanged;
- } else {
- auto parentHandle = mHandles[relativeParentId];
- transactions.back().states.front().state.relativeLayerSurfaceControl =
- sp<SurfaceControl>::make(SurfaceComposerClient::getDefault(), parentHandle,
- static_cast<int32_t>(relativeParentId), "test");
- transactions.back().states.front().state.what = layer_state_t::eRelativeLayerChanged;
- }
- transactions.back().states.front().state.surface = mHandles[id];
- mLifecycleManager.applyTransactions(transactions);
- }
-
- void mirrorLayer(uint32_t id, uint32_t parent, uint32_t layerToMirror) {
- auto parentHandle = (parent == UNASSIGNED_LAYER_ID) ? nullptr : mHandles[parent];
- auto mirrorHandle =
- (layerToMirror == UNASSIGNED_LAYER_ID) ? nullptr : mHandles[layerToMirror];
-
- sp<LayerHandle> handle = sp<LayerHandle>::make(id);
- mHandles[id] = handle;
- std::vector<std::unique_ptr<RequestedLayerState>> layers;
- layers.emplace_back(std::make_unique<RequestedLayerState>(
- createArgs(/*id=*/id, /*canBeRoot=*/false, /*parent=*/parentHandle,
- /*mirror=*/mHandles[layerToMirror])));
- mLifecycleManager.addLayers(std::move(layers));
- }
-
- void updateBackgroundColor(uint32_t id, half alpha) {
- std::vector<TransactionState> transactions;
- transactions.emplace_back();
- transactions.back().states.push_back({});
- transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged;
- transactions.back().states.front().state.bgColorAlpha = alpha;
- transactions.back().states.front().state.surface = mHandles[id];
- mLifecycleManager.applyTransactions(transactions);
- }
-
- void destroyLayerHandle(uint32_t id) { mLifecycleManager.onHandlesDestroyed({id}); }
-
- void updateAndVerify(LayerHierarchyBuilder& hierarchyBuilder) {
- if (mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)) {
- hierarchyBuilder.update(mLifecycleManager.getLayers(),
- mLifecycleManager.getDestroyedLayers());
- }
- mLifecycleManager.commitChanges();
-
- // rebuild layer hierarchy from scratch and verify that it matches the updated state.
- LayerHierarchyBuilder newBuilder(mLifecycleManager.getLayers());
- EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()),
- getTraversalPath(newBuilder.getHierarchy()));
- EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()),
- getTraversalPathInZOrder(newBuilder.getHierarchy()));
- EXPECT_FALSE(
- mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
- }
-
- void setZ(uint32_t id, int32_t z) {
- std::vector<TransactionState> transactions;
- transactions.emplace_back();
- transactions.back().states.push_back({});
-
- transactions.back().states.front().state.what = layer_state_t::eLayerChanged;
- transactions.back().states.front().state.layerId = static_cast<int32_t>(id);
- transactions.back().states.front().state.z = z;
- mLifecycleManager.applyTransactions(transactions);
- }
- LayerLifecycleManager mLifecycleManager;
- std::unordered_map<uint32_t, sp<LayerHandle>> mHandles;
+ LayerHierarchyTest() : LayerHierarchyTestBase() { mLifecycleManager.commitChanges(); }
};
// reparenting tests
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
new file mode 100644
index 0000000..08727f2
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
@@ -0,0 +1,252 @@
+/*
+ * 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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "FrontEnd/LayerHandle.h"
+#include "FrontEnd/LayerHierarchy.h"
+#include "FrontEnd/LayerLifecycleManager.h"
+#include "Layer.h"
+#include "gui/SurfaceComposerClient.h"
+
+namespace android::surfaceflinger::frontend {
+
+class LayerHierarchyTestBase : public testing::Test {
+protected:
+ LayerHierarchyTestBase() {
+ // tree with 3 levels of children
+ // ROOT
+ // ├── 1
+ // │ ├── 11
+ // │ │ └── 111
+ // │ ├── 12
+ // │ │ ├── 121
+ // │ │ └── 122
+ // │ │ └── 1221
+ // │ └── 13
+ // └── 2
+
+ createRootLayer(1);
+ createRootLayer(2);
+ createLayer(11, 1);
+ createLayer(12, 1);
+ createLayer(13, 1);
+ createLayer(111, 11);
+ createLayer(121, 12);
+ createLayer(122, 12);
+ createLayer(1221, 122);
+ }
+
+ LayerCreationArgs createArgs(uint32_t id, bool canBeRoot, wp<IBinder> parent,
+ wp<IBinder> mirror) {
+ LayerCreationArgs args(nullptr, nullptr, "testlayer", 0, {}, std::make_optional(id));
+ args.addToRoot = canBeRoot;
+ args.parentHandle = parent;
+ args.mirrorLayerHandle = mirror;
+ return args;
+ }
+
+ std::vector<uint32_t> getTraversalPath(const LayerHierarchy& hierarchy) const {
+ std::vector<uint32_t> layerIds;
+ hierarchy.traverse([&layerIds = layerIds](const LayerHierarchy& hierarchy,
+ const LayerHierarchy::TraversalPath&) -> bool {
+ layerIds.emplace_back(hierarchy.getLayer()->id);
+ return true;
+ });
+ return layerIds;
+ }
+
+ std::vector<uint32_t> getTraversalPathInZOrder(const LayerHierarchy& hierarchy) const {
+ std::vector<uint32_t> layerIds;
+ hierarchy.traverseInZOrder(
+ [&layerIds = layerIds](const LayerHierarchy& hierarchy,
+ const LayerHierarchy::TraversalPath&) -> bool {
+ layerIds.emplace_back(hierarchy.getLayer()->id);
+ return true;
+ });
+ return layerIds;
+ }
+
+ virtual void createRootLayer(uint32_t id) {
+ sp<LayerHandle> handle = sp<LayerHandle>::make(id);
+ mHandles[id] = handle;
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(std::make_unique<RequestedLayerState>(
+ createArgs(/*id=*/id, /*canBeRoot=*/true, /*parent=*/nullptr, /*mirror=*/nullptr)));
+ mLifecycleManager.addLayers(std::move(layers));
+ }
+
+ virtual void createLayer(uint32_t id, uint32_t parentId) {
+ sp<LayerHandle> handle = sp<LayerHandle>::make(id);
+ mHandles[id] = handle;
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(std::make_unique<RequestedLayerState>(
+ createArgs(/*id=*/id, /*canBeRoot=*/false, /*parent=*/mHandles[parentId],
+ /*mirror=*/nullptr)));
+ mLifecycleManager.addLayers(std::move(layers));
+ }
+
+ void reparentLayer(uint32_t id, uint32_t newParentId) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ if (newParentId == UNASSIGNED_LAYER_ID) {
+ transactions.back().states.front().state.parentSurfaceControlForChild = nullptr;
+ } else {
+ auto parentHandle = mHandles[newParentId];
+ transactions.back().states.front().state.parentSurfaceControlForChild =
+ sp<SurfaceControl>::make(SurfaceComposerClient::getDefault(), parentHandle,
+ static_cast<int32_t>(newParentId), "Test");
+ }
+ transactions.back().states.front().state.what = layer_state_t::eReparent;
+ transactions.back().states.front().state.surface = mHandles[id];
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void reparentRelativeLayer(uint32_t id, uint32_t relativeParentId) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ if (relativeParentId == UNASSIGNED_LAYER_ID) {
+ transactions.back().states.front().state.what = layer_state_t::eLayerChanged;
+ } else {
+ auto parentHandle = mHandles[relativeParentId];
+ transactions.back().states.front().state.relativeLayerSurfaceControl =
+ sp<SurfaceControl>::make(SurfaceComposerClient::getDefault(), parentHandle,
+ static_cast<int32_t>(relativeParentId), "test");
+ transactions.back().states.front().state.what = layer_state_t::eRelativeLayerChanged;
+ }
+ transactions.back().states.front().state.surface = mHandles[id];
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ virtual void mirrorLayer(uint32_t id, uint32_t parent, uint32_t layerToMirror) {
+ auto parentHandle = (parent == UNASSIGNED_LAYER_ID) ? nullptr : mHandles[parent];
+ auto mirrorHandle =
+ (layerToMirror == UNASSIGNED_LAYER_ID) ? nullptr : mHandles[layerToMirror];
+
+ sp<LayerHandle> handle = sp<LayerHandle>::make(id);
+ mHandles[id] = handle;
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(std::make_unique<RequestedLayerState>(
+ createArgs(/*id=*/id, /*canBeRoot=*/false, /*parent=*/parentHandle,
+ /*mirror=*/mHandles[layerToMirror])));
+ mLifecycleManager.addLayers(std::move(layers));
+ }
+
+ void updateBackgroundColor(uint32_t id, half alpha) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+ transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged;
+ transactions.back().states.front().state.bgColorAlpha = alpha;
+ transactions.back().states.front().state.surface = mHandles[id];
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void destroyLayerHandle(uint32_t id) { mLifecycleManager.onHandlesDestroyed({id}); }
+
+ void updateAndVerify(LayerHierarchyBuilder& hierarchyBuilder) {
+ if (mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)) {
+ hierarchyBuilder.update(mLifecycleManager.getLayers(),
+ mLifecycleManager.getDestroyedLayers());
+ }
+ mLifecycleManager.commitChanges();
+
+ // rebuild layer hierarchy from scratch and verify that it matches the updated state.
+ LayerHierarchyBuilder newBuilder(mLifecycleManager.getLayers());
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()),
+ getTraversalPath(newBuilder.getHierarchy()));
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()),
+ getTraversalPathInZOrder(newBuilder.getHierarchy()));
+ EXPECT_FALSE(
+ mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+ }
+
+ void setZ(uint32_t id, int32_t z) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ transactions.back().states.front().state.what = layer_state_t::eLayerChanged;
+ transactions.back().states.front().state.surface = mHandles[id];
+ transactions.back().states.front().state.layerId = static_cast<int32_t>(id);
+ transactions.back().states.front().state.z = z;
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void setCrop(uint32_t id, const Rect& crop) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ transactions.back().states.front().state.what = layer_state_t::eCropChanged;
+ transactions.back().states.front().state.surface = mHandles[id];
+ transactions.back().states.front().state.layerId = static_cast<int32_t>(id);
+ transactions.back().states.front().state.crop = crop;
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void setFlags(uint32_t id, uint32_t mask, uint32_t flags) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ transactions.back().states.front().state.what = layer_state_t::eFlagsChanged;
+ transactions.back().states.front().state.flags = flags;
+ transactions.back().states.front().state.mask = mask;
+ transactions.back().states.front().state.surface = mHandles[id];
+ transactions.back().states.front().state.layerId = static_cast<int32_t>(id);
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void setAlpha(uint32_t id, float alpha) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ transactions.back().states.front().state.what = layer_state_t::eAlphaChanged;
+ transactions.back().states.front().state.surface = mHandles[id];
+ transactions.back().states.front().state.layerId = static_cast<int32_t>(id);
+ transactions.back().states.front().state.color.a = static_cast<half>(alpha);
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void hideLayer(uint32_t id) {
+ setFlags(id, layer_state_t::eLayerHidden, layer_state_t::eLayerHidden);
+ }
+
+ void showLayer(uint32_t id) { setFlags(id, layer_state_t::eLayerHidden, 0); }
+
+ void setColor(uint32_t id, half3 rgb = half3(1._hf, 1._hf, 1._hf)) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+ transactions.back().states.front().state.what = layer_state_t::eColorChanged;
+ transactions.back().states.front().state.color.rgb = rgb;
+ transactions.back().states.front().state.surface = mHandles[id];
+ transactions.back().states.front().state.layerId = static_cast<int32_t>(id);
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ LayerLifecycleManager mLifecycleManager;
+ std::unordered_map<uint32_t, sp<LayerHandle>> mHandles;
+};
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
new file mode 100644
index 0000000..2441c06
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -0,0 +1,260 @@
+/*
+ * 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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "FrontEnd/LayerHandle.h"
+#include "FrontEnd/LayerHierarchy.h"
+#include "FrontEnd/LayerLifecycleManager.h"
+#include "FrontEnd/LayerSnapshotBuilder.h"
+#include "Layer.h"
+#include "LayerHierarchyTest.h"
+
+#define UPDATE_AND_VERIFY(BUILDER, ...) \
+ ({ \
+ SCOPED_TRACE(""); \
+ updateAndVerify((BUILDER), /*displayChanges=*/false, __VA_ARGS__); \
+ })
+
+#define UPDATE_AND_VERIFY_WITH_DISPLAY_CHANGES(BUILDER, ...) \
+ ({ \
+ SCOPED_TRACE(""); \
+ updateAndVerify((BUILDER), /*displayChanges=*/true, __VA_ARGS__); \
+ })
+
+namespace android::surfaceflinger::frontend {
+
+// To run test:
+/**
+ mp :libsurfaceflinger_unittest && adb sync; adb shell \
+ /data/nativetest/libsurfaceflinger_unittest/libsurfaceflinger_unittest \
+ --gtest_filter="LayerSnapshotTest.*" --gtest_brief=1
+*/
+
+class LayerSnapshotTest : public LayerHierarchyTestBase {
+protected:
+ LayerSnapshotTest() : LayerHierarchyTestBase() {
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ }
+
+ void createRootLayer(uint32_t id) override {
+ LayerHierarchyTestBase::createRootLayer(id);
+ setColor(id);
+ }
+
+ void createLayer(uint32_t id, uint32_t parentId) override {
+ LayerHierarchyTestBase::createLayer(id, parentId);
+ setColor(parentId);
+ }
+
+ void mirrorLayer(uint32_t id, uint32_t parent, uint32_t layerToMirror) override {
+ LayerHierarchyTestBase::mirrorLayer(id, parent, layerToMirror);
+ setColor(id);
+ }
+
+ void updateAndVerify(LayerSnapshotBuilder& actualBuilder, bool hasDisplayChanges,
+ const std::vector<uint32_t> expectedVisibleLayerIdsInZOrder) {
+ if (mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)) {
+ mHierarchyBuilder.update(mLifecycleManager.getLayers(),
+ mLifecycleManager.getDestroyedLayers());
+ }
+ LayerSnapshotBuilder::Args args{
+ .root = mHierarchyBuilder.getHierarchy(),
+ .layerLifecycleManager = mLifecycleManager,
+ .includeMetadata = false,
+ .displays = mFrontEndDisplayInfos,
+ .displayChanges = hasDisplayChanges,
+ .globalShadowSettings = globalShadowSettings,
+ };
+ actualBuilder.update(args);
+
+ // rebuild layer snapshots from scratch and verify that it matches the updated state.
+ LayerSnapshotBuilder expectedBuilder(args);
+ mLifecycleManager.commitChanges();
+ ASSERT_TRUE(expectedBuilder.getSnapshots().size() > 0);
+ ASSERT_TRUE(actualBuilder.getSnapshots().size() > 0);
+
+ std::vector<std::unique_ptr<LayerSnapshot>>& snapshots = actualBuilder.getSnapshots();
+ std::vector<uint32_t> actualVisibleLayerIdsInZOrder;
+ for (auto& snapshot : snapshots) {
+ if (!snapshot->isVisible) {
+ break;
+ }
+ actualVisibleLayerIdsInZOrder.push_back(snapshot->path.id);
+ }
+ EXPECT_EQ(expectedVisibleLayerIdsInZOrder, actualVisibleLayerIdsInZOrder);
+ }
+
+ LayerSnapshot* getSnapshot(uint32_t layerId) { return mSnapshotBuilder.getSnapshot(layerId); }
+
+ LayerHierarchyBuilder mHierarchyBuilder{{}};
+ LayerSnapshotBuilder mSnapshotBuilder;
+ std::unordered_map<uint32_t, sp<LayerHandle>> mHandles;
+ display::DisplayMap<ui::LayerStack, frontend::DisplayInfo> mFrontEndDisplayInfos;
+ renderengine::ShadowSettings globalShadowSettings;
+ static const std::vector<uint32_t> STARTING_ZORDER;
+};
+const std::vector<uint32_t> LayerSnapshotTest::STARTING_ZORDER = {1, 11, 111, 12, 121,
+ 122, 1221, 13, 2};
+
+TEST_F(LayerSnapshotTest, buildSnapshot) {
+ LayerSnapshotBuilder::Args args{
+ .root = mHierarchyBuilder.getHierarchy(),
+ .layerLifecycleManager = mLifecycleManager,
+ .includeMetadata = false,
+ .displays = mFrontEndDisplayInfos,
+ .globalShadowSettings = globalShadowSettings,
+ };
+ LayerSnapshotBuilder builder(args);
+}
+
+TEST_F(LayerSnapshotTest, updateSnapshot) {
+ LayerSnapshotBuilder::Args args{
+ .root = mHierarchyBuilder.getHierarchy(),
+ .layerLifecycleManager = mLifecycleManager,
+ .includeMetadata = false,
+ .displays = mFrontEndDisplayInfos,
+ .globalShadowSettings = globalShadowSettings,
+ };
+
+ LayerSnapshotBuilder builder;
+ builder.update(args);
+}
+
+// update using parent snapshot data
+TEST_F(LayerSnapshotTest, croppedByParent) {
+ /// MAKE ALL LAYERS VISIBLE BY DEFAULT
+ DisplayInfo info;
+ info.info.logicalHeight = 100;
+ info.info.logicalWidth = 200;
+ mFrontEndDisplayInfos.emplace_or_replace(ui::LayerStack::fromValue(1), info);
+ Rect layerCrop(0, 0, 10, 20);
+ setCrop(11, layerCrop);
+ EXPECT_TRUE(mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Geometry));
+ UPDATE_AND_VERIFY_WITH_DISPLAY_CHANGES(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_EQ(getSnapshot(11)->geomCrop, layerCrop);
+ EXPECT_EQ(getSnapshot(111)->geomLayerBounds, layerCrop.toFloatRect());
+ float maxHeight = static_cast<float>(info.info.logicalHeight * 10);
+ float maxWidth = static_cast<float>(info.info.logicalWidth * 10);
+
+ FloatRect maxDisplaySize(-maxWidth, -maxHeight, maxWidth, maxHeight);
+ EXPECT_EQ(getSnapshot(1)->geomLayerBounds, maxDisplaySize);
+}
+
+// visibility tests
+TEST_F(LayerSnapshotTest, newLayerHiddenByPolicy) {
+ createLayer(112, 11);
+ hideLayer(112);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+
+ showLayer(112);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 11, 111, 112, 12, 121, 122, 1221, 13, 2});
+}
+
+TEST_F(LayerSnapshotTest, hiddenByParent) {
+ hideLayer(11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 12, 121, 122, 1221, 13, 2});
+}
+
+TEST_F(LayerSnapshotTest, reparentShowsChild) {
+ hideLayer(11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 12, 121, 122, 1221, 13, 2});
+
+ showLayer(11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+}
+
+TEST_F(LayerSnapshotTest, reparentHidesChild) {
+ hideLayer(11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 12, 121, 122, 1221, 13, 2});
+
+ reparentLayer(121, 11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 12, 122, 1221, 13, 2});
+}
+
+TEST_F(LayerSnapshotTest, unHidingUpdatesSnapshot) {
+ hideLayer(11);
+ Rect crop(1, 2, 3, 4);
+ setCrop(111, crop);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 12, 121, 122, 1221, 13, 2});
+
+ showLayer(11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_EQ(getSnapshot(111)->geomLayerBounds, crop.toFloatRect());
+}
+
+TEST_F(LayerSnapshotTest, childBehindParentCanBeHiddenByParent) {
+ setZ(111, -1);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 111, 11, 12, 121, 122, 1221, 13, 2});
+
+ hideLayer(11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 12, 121, 122, 1221, 13, 2});
+}
+
+// relative tests
+TEST_F(LayerSnapshotTest, RelativeParentCanHideChild) {
+ reparentRelativeLayer(13, 11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 11, 13, 111, 12, 121, 122, 1221, 2});
+
+ hideLayer(11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 12, 121, 122, 1221, 2});
+}
+
+TEST_F(LayerSnapshotTest, ReparentingToHiddenRelativeParentHidesChild) {
+ hideLayer(11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 12, 121, 122, 1221, 13, 2});
+ reparentRelativeLayer(13, 11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 12, 121, 122, 1221, 2});
+}
+
+TEST_F(LayerSnapshotTest, AlphaInheritedByChildren) {
+ setAlpha(1, 0.5);
+ setAlpha(122, 0.5);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_EQ(getSnapshot(12)->alpha, 0.5f);
+ EXPECT_EQ(getSnapshot(1221)->alpha, 0.25f);
+}
+
+// Change states
+TEST_F(LayerSnapshotTest, UpdateClearsPreviousChangeStates) {
+ setCrop(1, Rect(1, 2, 3, 4));
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_TRUE(getSnapshot(1)->changes.get() != 0);
+ EXPECT_TRUE(getSnapshot(11)->changes.get() != 0);
+ setCrop(2, Rect(1, 2, 3, 4));
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_TRUE(getSnapshot(2)->changes.get() != 0);
+ EXPECT_TRUE(getSnapshot(1)->changes.get() == 0);
+ EXPECT_TRUE(getSnapshot(11)->changes.get() == 0);
+}
+
+TEST_F(LayerSnapshotTest, FastPathClearsPreviousChangeStates) {
+ setColor(11, {1._hf, 0._hf, 0._hf});
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_TRUE(getSnapshot(11)->changes.get() != 0);
+ EXPECT_TRUE(getSnapshot(1)->changes.get() == 0);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_TRUE(getSnapshot(11)->changes.get() == 0);
+}
+
+TEST_F(LayerSnapshotTest, FastPathSetsChangeFlagToContent) {
+ setColor(1, {1._hf, 0._hf, 0._hf});
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_EQ(getSnapshot(1)->changes, RequestedLayerState::Changes::Content);
+}
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 584d52c..72e0c7b 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -439,12 +439,13 @@
uint32_t flags, const sp<IBinder>& applyToken,
const InputWindowCommands& inputWindowCommands,
int64_t desiredPresentTime, bool isAutoTimestamp,
- const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,
+ const std::vector<client_cache_t>& uncacheBuffers,
+ bool hasListenerCallbacks,
std::vector<ListenerCallbacks>& listenerCallbacks,
uint64_t transactionId) {
return mFlinger->setTransactionState(frameTimelineInfo, states, displays, flags, applyToken,
inputWindowCommands, desiredPresentTime,
- isAutoTimestamp, uncacheBuffer, hasListenerCallbacks,
+ isAutoTimestamp, uncacheBuffers, hasListenerCallbacks,
listenerCallbacks, transactionId);
}
diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
index d84698f..a28d1cd 100644
--- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
@@ -102,7 +102,7 @@
int64_t desiredPresentTime = 0;
bool isAutoTimestamp = true;
FrameTimelineInfo frameTimelineInfo;
- client_cache_t uncacheBuffer;
+ std::vector<client_cache_t> uncacheBuffers;
uint64_t id = static_cast<uint64_t>(-1);
static_assert(0xffffffffffffffff == static_cast<uint64_t>(-1));
};
@@ -138,7 +138,7 @@
transaction.displays, transaction.flags,
transaction.applyToken, transaction.inputWindowCommands,
transaction.desiredPresentTime, transaction.isAutoTimestamp,
- transaction.uncacheBuffer, mHasListenerCallbacks, mCallbacks,
+ transaction.uncacheBuffers, mHasListenerCallbacks, mCallbacks,
transaction.id);
// If transaction is synchronous, SF applyTransactionState should time out (5s) wating for
@@ -165,7 +165,7 @@
transaction.displays, transaction.flags,
transaction.applyToken, transaction.inputWindowCommands,
transaction.desiredPresentTime, transaction.isAutoTimestamp,
- transaction.uncacheBuffer, mHasListenerCallbacks, mCallbacks,
+ transaction.uncacheBuffers, mHasListenerCallbacks, mCallbacks,
transaction.id);
nsecs_t returnedTime = systemTime();
@@ -196,7 +196,7 @@
transactionA.displays, transactionA.flags,
transactionA.applyToken, transactionA.inputWindowCommands,
transactionA.desiredPresentTime, transactionA.isAutoTimestamp,
- transactionA.uncacheBuffer, mHasListenerCallbacks, mCallbacks,
+ transactionA.uncacheBuffers, mHasListenerCallbacks, mCallbacks,
transactionA.id);
// This thread should not have been blocked by the above transaction
@@ -211,7 +211,7 @@
transactionB.displays, transactionB.flags,
transactionB.applyToken, transactionB.inputWindowCommands,
transactionB.desiredPresentTime, transactionB.isAutoTimestamp,
- transactionB.uncacheBuffer, mHasListenerCallbacks, mCallbacks,
+ transactionB.uncacheBuffers, mHasListenerCallbacks, mCallbacks,
transactionB.id);
// this thread should have been blocked by the above transaction
@@ -243,7 +243,7 @@
mFlinger.setTransactionState(transactionA.frameTimelineInfo, transactionA.states,
transactionA.displays, transactionA.flags, transactionA.applyToken,
transactionA.inputWindowCommands, transactionA.desiredPresentTime,
- transactionA.isAutoTimestamp, transactionA.uncacheBuffer,
+ transactionA.isAutoTimestamp, transactionA.uncacheBuffers,
mHasListenerCallbacks, mCallbacks, transactionA.id);
auto& transactionQueue = mFlinger.getTransactionQueue();
@@ -263,7 +263,7 @@
mFlinger.setTransactionState(transactionA.frameTimelineInfo, transactionA.states,
transactionA.displays, transactionA.flags, transactionA.applyToken,
transactionA.inputWindowCommands, transactionA.desiredPresentTime,
- transactionA.isAutoTimestamp, transactionA.uncacheBuffer,
+ transactionA.isAutoTimestamp, transactionA.uncacheBuffers,
mHasListenerCallbacks, mCallbacks, transactionA.id);
auto& transactionQueue = mFlinger.getTransactionQueue();
@@ -277,7 +277,7 @@
mFlinger.setTransactionState(empty.frameTimelineInfo, empty.states, empty.displays, empty.flags,
empty.applyToken, empty.inputWindowCommands,
empty.desiredPresentTime, empty.isAutoTimestamp,
- empty.uncacheBuffer, mHasListenerCallbacks, mCallbacks, empty.id);
+ empty.uncacheBuffers, mHasListenerCallbacks, mCallbacks, empty.id);
// flush transaction queue should flush as desiredPresentTime has
// passed
@@ -374,8 +374,7 @@
transaction.applyToken,
transaction.inputWindowCommands,
transaction.desiredPresentTime,
- transaction.isAutoTimestamp,
- transaction.uncacheBuffer, systemTime(), 0,
+ transaction.isAutoTimestamp, {}, systemTime(), 0,
mHasListenerCallbacks, mCallbacks, getpid(),
static_cast<int>(getuid()), transaction.id);
mFlinger.setTransactionStateInternal(transactionState);