[automerger skipped] Merge tm-qpr-dev-plus-aosp-without-vendor@9467136 am: 45c68f28ac -s ours
am skip reason: Merged-In Ic7e102201196285cba18e12e27c7cd4d40dcc8e4 with SHA-1 95f9197944 is already in history
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/native/+/20951565
Change-Id: I94f5a454792477c37d099fa356f800d509a376f1
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/.clang-format b/.clang-format
index 6725a1f..f63f670 100644
--- a/.clang-format
+++ b/.clang-format
@@ -12,3 +12,6 @@
PenaltyBreakBeforeFirstCallParameter: 100000
SpacesBeforeTrailingComments: 1
IncludeBlocks: Preserve
+
+DerivePointerAlignment: false
+PointerAlignment: Left
diff --git a/libs/binder/BinderRecordReplay.cpp b/libs/binder/BinderRecordReplay.cpp
index 8ba18a8..58bb106 100644
--- a/libs/binder/BinderRecordReplay.cpp
+++ b/libs/binder/BinderRecordReplay.cpp
@@ -18,6 +18,7 @@
#include <android-base/logging.h>
#include <android-base/unique_fd.h>
#include <binder/BinderRecordReplay.h>
+#include <sys/mman.h>
#include <algorithm>
using android::Parcel;
@@ -42,34 +43,38 @@
//
// A RecordedTransaction is written to a file as a sequence of Chunks.
//
-// A Chunk consists of a ChunkDescriptor, Data, and Padding.
+// A Chunk consists of a ChunkDescriptor, Data, Padding, and a Checksum.
//
-// Data and Padding may each be zero-length as specified by the
-// ChunkDescriptor.
+// The ChunkDescriptor identifies the type of Data in the chunk, and the size
+// of the Data.
//
-// The ChunkDescriptor identifies the type of data in the chunk, the size of
-// the data in bytes, and the number of zero-bytes padding to land on an
-// 8-byte boundary by the end of the Chunk.
+// The Data may be any uint32 number of bytes in length in [0-0xfffffff0].
+//
+// Padding is between [0-7] zero-bytes after the Data such that the Chunk ends
+// on an 8-byte boundary. The ChunkDescriptor's dataSize does not include the
+// size of Padding.
+//
+// The checksum is a 64-bit wide XOR of all previous data from the start of the
+// ChunkDescriptor to the end of Padding.
//
// ┌───────────────────────────┐
// │Chunk │
+// │┌────────────────────────┐ │
+// ││ChunkDescriptor │ │
+// ││┌───────────┬──────────┐│ │
+// │││chunkType │dataSize ├┼─┼─┐
+// │││uint32_t │uint32_t ││ │ │
+// ││└───────────┴──────────┘│ │ │
+// │└────────────────────────┘ │ │
+// │┌─────────────────────────┐│ │
+// ││Data ││ │
+// ││bytes * dataSize │◀─┘
+// ││ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤│
+// ││ Padding ││
+// │└───┴─────────────────────┘│
// │┌─────────────────────────┐│
-// ││ChunkDescriptor ││
-// ││┌───────────┬───────────┐││
-// │││chunkType │paddingSize│││
-// │││uint32_t │uint32_t ├┼┼───┐
-// ││├───────────┴───────────┤││ │
-// │││dataSize │││ │
-// │││uint64_t ├┼┼─┐ │
-// ││└───────────────────────┘││ │ │
-// │└─────────────────────────┘│ │ │
-// │┌─────────────────────────┐│ │ │
-// ││Data ││ │ │
-// ││bytes * dataSize │◀─┘ │
-// │└─────────────────────────┘│ │
-// │┌─────────────────────────┐│ │
-// ││Padding ││ │
-// ││bytes * paddingSize │◀───┘
+// ││checksum ││
+// ││uint64_t ││
// │└─────────────────────────┘│
// └───────────────────────────┘
//
@@ -85,20 +90,20 @@
// ║ End Chunk ║
// ╚══════════════════════╝
//
-// On reading a RecordedTransaction, an unrecognized chunk is skipped using
-// the size information in the ChunkDescriptor. Chunks are read and either
-// assimilated or skipped until an End Chunk is encountered. This has three
-// notable implications:
+// On reading a RecordedTransaction, an unrecognized chunk is checksummed
+// then skipped according to size information in the ChunkDescriptor. Chunks
+// are read and either assimilated or skipped until an End Chunk is
+// encountered. This has three notable implications:
//
// 1. Older and newer implementations should be able to read one another's
// Transactions, though there will be loss of information.
-// 2. With the exception of the End Chunk, Chunks can appear in any
-// order and even repeat, though this is not recommended.
+// 2. With the exception of the End Chunk, Chunks can appear in any order
+// and even repeat, though this is not recommended.
// 3. If any Chunk is repeated, old values will be overwritten by versions
// encountered later in the file.
//
// No effort is made to ensure the expected chunks are present. A single
-// End Chunk may therefore produce a empty, meaningless RecordedTransaction.
+// End Chunk may therefore produce an empty, meaningless RecordedTransaction.
RecordedTransaction::RecordedTransaction(RecordedTransaction&& t) noexcept {
mHeader = t.mHeader;
@@ -121,12 +126,12 @@
0};
if (t.mSent.setData(dataParcel.data(), dataParcel.dataSize()) != android::NO_ERROR) {
- LOG(INFO) << "Failed to set sent parcel data.";
+ LOG(ERROR) << "Failed to set sent parcel data.";
return std::nullopt;
}
if (t.mReply.setData(replyParcel.data(), replyParcel.dataSize()) != android::NO_ERROR) {
- LOG(INFO) << "Failed to set reply parcel data.";
+ LOG(ERROR) << "Failed to set reply parcel data.";
return std::nullopt;
}
@@ -134,94 +139,111 @@
}
enum {
- HEADER_CHUNK = 0x00000001,
- DATA_PARCEL_CHUNK = 0x00000002,
- REPLY_PARCEL_CHUNK = 0x00000003,
- INVALID_CHUNK = 0x00fffffe,
+ HEADER_CHUNK = 1,
+ DATA_PARCEL_CHUNK = 2,
+ REPLY_PARCEL_CHUNK = 3,
END_CHUNK = 0x00ffffff,
};
struct ChunkDescriptor {
uint32_t chunkType = 0;
- uint32_t padding = 0;
uint32_t dataSize = 0;
- uint32_t reserved = 0; // Future checksum
};
+static_assert(sizeof(ChunkDescriptor) % 8 == 0);
-static android::status_t readChunkDescriptor(borrowed_fd fd, ChunkDescriptor* chunkOut) {
+constexpr uint32_t kMaxChunkDataSize = 0xfffffff0;
+typedef uint64_t transaction_checksum_t;
+
+static android::status_t readChunkDescriptor(borrowed_fd fd, ChunkDescriptor* chunkOut,
+ transaction_checksum_t* sum) {
if (!android::base::ReadFully(fd, chunkOut, sizeof(ChunkDescriptor))) {
- LOG(INFO) << "Failed to read Chunk Descriptor from fd " << fd.get();
+ LOG(ERROR) << "Failed to read Chunk Descriptor from fd " << fd.get();
return android::UNKNOWN_ERROR;
}
- if (PADDING8(chunkOut->dataSize) != chunkOut->padding) {
- chunkOut->chunkType = INVALID_CHUNK;
- LOG(INFO) << "Chunk data and padding sizes do not align." << fd.get();
- return android::BAD_VALUE;
- }
+
+ *sum ^= *reinterpret_cast<transaction_checksum_t*>(chunkOut);
return android::NO_ERROR;
}
std::optional<RecordedTransaction> RecordedTransaction::fromFile(const unique_fd& fd) {
RecordedTransaction t;
ChunkDescriptor chunk;
-
+ const long pageSize = sysconf(_SC_PAGE_SIZE);
do {
- if (NO_ERROR != readChunkDescriptor(fd, &chunk)) {
- LOG(INFO) << "Failed to read chunk descriptor.";
+ transaction_checksum_t checksum = 0;
+ if (NO_ERROR != readChunkDescriptor(fd, &chunk, &checksum)) {
+ LOG(ERROR) << "Failed to read chunk descriptor.";
return std::nullopt;
}
+ off_t fdCurrentPosition = lseek(fd.get(), 0, SEEK_CUR);
+ off_t mmapPageAlignedStart = (fdCurrentPosition / pageSize) * pageSize;
+ off_t mmapPayloadStartOffset = fdCurrentPosition - mmapPageAlignedStart;
+
+ if (chunk.dataSize > kMaxChunkDataSize) {
+ LOG(ERROR) << "Chunk data exceeds maximum size.";
+ return std::nullopt;
+ }
+
+ size_t chunkPayloadSize =
+ chunk.dataSize + PADDING8(chunk.dataSize) + sizeof(transaction_checksum_t);
+
+ if (PADDING8(chunkPayloadSize) != 0) {
+ LOG(ERROR) << "Invalid chunk size, not aligned " << chunkPayloadSize;
+ return std::nullopt;
+ }
+
+ transaction_checksum_t* payloadMap = reinterpret_cast<transaction_checksum_t*>(
+ mmap(NULL, chunkPayloadSize + mmapPayloadStartOffset, PROT_READ, MAP_SHARED,
+ fd.get(), mmapPageAlignedStart));
+ payloadMap += mmapPayloadStartOffset /
+ sizeof(transaction_checksum_t); // Skip chunk descriptor and required mmap
+ // page-alignment
+ if (payloadMap == MAP_FAILED) {
+ LOG(ERROR) << "Memory mapping failed for fd " << fd.get() << ": " << errno << " "
+ << strerror(errno);
+ return std::nullopt;
+ }
+ for (size_t checksumIndex = 0;
+ checksumIndex < chunkPayloadSize / sizeof(transaction_checksum_t); checksumIndex++) {
+ checksum ^= payloadMap[checksumIndex];
+ }
+ if (checksum != 0) {
+ LOG(ERROR) << "Checksum failed.";
+ return std::nullopt;
+ }
+ lseek(fd.get(), chunkPayloadSize, SEEK_CUR);
+
switch (chunk.chunkType) {
case HEADER_CHUNK: {
if (chunk.dataSize != static_cast<uint32_t>(sizeof(TransactionHeader))) {
- LOG(INFO) << "Header Chunk indicated size " << chunk.dataSize << "; Expected "
- << sizeof(TransactionHeader) << ".";
+ LOG(ERROR) << "Header Chunk indicated size " << chunk.dataSize << "; Expected "
+ << sizeof(TransactionHeader) << ".";
return std::nullopt;
}
- if (!android::base::ReadFully(fd, &t.mHeader, chunk.dataSize)) {
- LOG(INFO) << "Failed to read transactionHeader from fd " << fd.get();
- return std::nullopt;
- }
- lseek(fd.get(), chunk.padding, SEEK_CUR);
+ t.mHeader = *reinterpret_cast<TransactionHeader*>(payloadMap);
break;
}
case DATA_PARCEL_CHUNK: {
- std::vector<uint8_t> bytes;
- bytes.resize(chunk.dataSize);
- if (!android::base::ReadFully(fd, bytes.data(), chunk.dataSize)) {
- LOG(INFO) << "Failed to read sent parcel data from fd " << fd.get();
+ if (t.mSent.setData(reinterpret_cast<const unsigned char*>(payloadMap),
+ chunk.dataSize) != android::NO_ERROR) {
+ LOG(ERROR) << "Failed to set sent parcel data.";
return std::nullopt;
}
- if (t.mSent.setData(bytes.data(), chunk.dataSize) != android::NO_ERROR) {
- LOG(INFO) << "Failed to set sent parcel data.";
- return std::nullopt;
- }
- lseek(fd.get(), chunk.padding, SEEK_CUR);
break;
}
case REPLY_PARCEL_CHUNK: {
- std::vector<uint8_t> bytes;
- bytes.resize(chunk.dataSize);
- if (!android::base::ReadFully(fd, bytes.data(), chunk.dataSize)) {
- LOG(INFO) << "Failed to read reply parcel data from fd " << fd.get();
+ if (t.mReply.setData(reinterpret_cast<const unsigned char*>(payloadMap),
+ chunk.dataSize) != android::NO_ERROR) {
+ LOG(ERROR) << "Failed to set reply parcel data.";
return std::nullopt;
}
- if (t.mReply.setData(bytes.data(), chunk.dataSize) != android::NO_ERROR) {
- LOG(INFO) << "Failed to set reply parcel data.";
- return std::nullopt;
- }
- lseek(fd.get(), chunk.padding, SEEK_CUR);
break;
}
- case INVALID_CHUNK:
- LOG(INFO) << "Invalid chunk.";
- return std::nullopt;
case END_CHUNK:
- LOG(INFO) << "Read end chunk";
- FALLTHROUGH_INTENDED;
- default:
- // Unrecognized or skippable chunk
- lseek(fd.get(), chunk.dataSize + chunk.padding, SEEK_CUR);
break;
+ default:
+ LOG(INFO) << "Unrecognized chunk.";
+ continue;
}
} while (chunk.chunkType != END_CHUNK);
@@ -230,36 +252,37 @@
android::status_t RecordedTransaction::writeChunk(borrowed_fd fd, uint32_t chunkType,
size_t byteCount, const uint8_t* data) const {
- // Write Chunk Descriptor
- // - Chunk Type
- if (!android::base::WriteFully(fd, &chunkType, sizeof(uint32_t))) {
- LOG(INFO) << "Failed to write chunk header to fd " << fd.get();
- return UNKNOWN_ERROR;
+ if (byteCount > kMaxChunkDataSize) {
+ LOG(ERROR) << "Chunk data exceeds maximum size";
+ return BAD_VALUE;
}
- // - Chunk Data Padding Size
- uint32_t additionalPaddingCount = static_cast<uint32_t>(PADDING8(byteCount));
- if (!android::base::WriteFully(fd, &additionalPaddingCount, sizeof(uint32_t))) {
- LOG(INFO) << "Failed to write chunk padding size to fd " << fd.get();
- return UNKNOWN_ERROR;
- }
- // - Chunk Data Size
- uint64_t byteCountToWrite = (uint64_t)byteCount;
- if (!android::base::WriteFully(fd, &byteCountToWrite, sizeof(uint64_t))) {
- LOG(INFO) << "Failed to write chunk size to fd " << fd.get();
- return UNKNOWN_ERROR;
- }
- if (byteCount == 0) {
- return NO_ERROR;
+ ChunkDescriptor descriptor = {.chunkType = chunkType,
+ .dataSize = static_cast<uint32_t>(byteCount)};
+ // Prepare Chunk content as byte *
+ const std::byte* descriptorBytes = reinterpret_cast<const std::byte*>(&descriptor);
+ const std::byte* dataBytes = reinterpret_cast<const std::byte*>(data);
+
+ // Add Chunk to intermediate buffer, except checksum
+ std::vector<std::byte> buffer;
+ buffer.insert(buffer.end(), descriptorBytes, descriptorBytes + sizeof(ChunkDescriptor));
+ buffer.insert(buffer.end(), dataBytes, dataBytes + byteCount);
+ std::byte zero{0};
+ buffer.insert(buffer.end(), PADDING8(byteCount), zero);
+
+ // Calculate checksum from buffer
+ transaction_checksum_t* checksumData = reinterpret_cast<transaction_checksum_t*>(buffer.data());
+ transaction_checksum_t checksumValue = 0;
+ for (size_t idx = 0; idx < (buffer.size() / sizeof(transaction_checksum_t)); idx++) {
+ checksumValue ^= checksumData[idx];
}
- if (!android::base::WriteFully(fd, data, byteCount)) {
- LOG(INFO) << "Failed to write chunk data to fd " << fd.get();
- return UNKNOWN_ERROR;
- }
+ // Write checksum to buffer
+ std::byte* checksumBytes = reinterpret_cast<std::byte*>(&checksumValue);
+ buffer.insert(buffer.end(), checksumBytes, checksumBytes + sizeof(transaction_checksum_t));
- const uint8_t zeros[7] = {0};
- if (!android::base::WriteFully(fd, zeros, additionalPaddingCount)) {
- LOG(INFO) << "Failed to write chunk padding to fd " << fd.get();
+ // Write buffer to file
+ if (!android::base::WriteFully(fd, buffer.data(), buffer.size())) {
+ LOG(ERROR) << "Failed to write chunk fd " << fd.get();
return UNKNOWN_ERROR;
}
return NO_ERROR;
@@ -269,19 +292,19 @@
if (NO_ERROR !=
writeChunk(fd, HEADER_CHUNK, sizeof(TransactionHeader),
reinterpret_cast<const uint8_t*>(&mHeader))) {
- LOG(INFO) << "Failed to write transactionHeader to fd " << fd.get();
+ LOG(ERROR) << "Failed to write transactionHeader to fd " << fd.get();
return UNKNOWN_ERROR;
}
if (NO_ERROR != writeChunk(fd, DATA_PARCEL_CHUNK, mSent.dataSize(), mSent.data())) {
- LOG(INFO) << "Failed to write sent Parcel to fd " << fd.get();
+ LOG(ERROR) << "Failed to write sent Parcel to fd " << fd.get();
return UNKNOWN_ERROR;
}
if (NO_ERROR != writeChunk(fd, REPLY_PARCEL_CHUNK, mReply.dataSize(), mReply.data())) {
- LOG(INFO) << "Failed to write reply Parcel to fd " << fd.get();
+ LOG(ERROR) << "Failed to write reply Parcel to fd " << fd.get();
return UNKNOWN_ERROR;
}
if (NO_ERROR != writeChunk(fd, END_CHUNK, 0, NULL)) {
- LOG(INFO) << "Failed to write end chunk to fd " << fd.get();
+ LOG(ERROR) << "Failed to write end chunk to fd " << fd.get();
return UNKNOWN_ERROR;
}
return NO_ERROR;
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index 6d64e1e..da58251 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -1221,6 +1221,10 @@
return NO_ERROR;
}
+ ALOGE_IF(mProcess->mDriverFD >= 0,
+ "Driver returned error (%s). This is a bug in either libbinder or the driver. This "
+ "thread's connection to %s will no longer work.",
+ statusToString(err).c_str(), mProcess->mDriverName.c_str());
return err;
}
diff --git a/libs/binder/include/binder/ParcelFileDescriptor.h b/libs/binder/include/binder/ParcelFileDescriptor.h
index 9896fd7..08d8e43 100644
--- a/libs/binder/include/binder/ParcelFileDescriptor.h
+++ b/libs/binder/include/binder/ParcelFileDescriptor.h
@@ -42,6 +42,7 @@
android::status_t writeToParcel(android::Parcel* parcel) const override;
android::status_t readFromParcel(const android::Parcel* parcel) override;
+ inline std::string toString() const { return "ParcelFileDescriptor:" + std::to_string(get()); }
inline bool operator!=(const ParcelFileDescriptor& rhs) const {
return mFd.get() != rhs.mFd.get();
}
diff --git a/libs/binder/include/binder/ParcelableHolder.h b/libs/binder/include/binder/ParcelableHolder.h
index 88790a8..40fd30a 100644
--- a/libs/binder/include/binder/ParcelableHolder.h
+++ b/libs/binder/include/binder/ParcelableHolder.h
@@ -111,6 +111,11 @@
Stability getStability() const override { return mStability; }
+ inline std::string toString() const {
+ return "ParcelableHolder:" +
+ (mParcelableName ? std::string(String8(mParcelableName.value()).c_str())
+ : "<parceled>");
+ }
inline bool operator!=(const ParcelableHolder& rhs) const {
return this != &rhs;
}
diff --git a/libs/binder/ndk/include_cpp/android/binder_to_string.h b/libs/binder/ndk/include_cpp/android/binder_to_string.h
index e3ead11..9b0d222 100644
--- a/libs/binder/ndk/include_cpp/android/binder_to_string.h
+++ b/libs/binder/ndk/include_cpp/android/binder_to_string.h
@@ -58,8 +58,6 @@
#if __has_include(<binder/RpcSession.h>)
#include <binder/IBinder.h>
#include <binder/IInterface.h>
-#include <binder/ParcelFileDescriptor.h>
-#include <binder/ParcelableHolder.h>
#define HAS_CPP_INTERFACE
#endif
@@ -152,9 +150,7 @@
#endif // HAS_NDK_INTERFACE
#ifdef HAS_CPP_INTERFACE
|| std::is_base_of_v<IInterface, _U> ||
- std::is_same_v<IBinder, _U> ||
- std::is_same_v<os::ParcelFileDescriptor, _U> ||
- std::is_same_v<os::ParcelableHolder, _U>
+ std::is_same_v<IBinder, _U>
#endif
,
std::true_type>
diff --git a/libs/binder/tests/binderRecordedTransactionTest.cpp b/libs/binder/tests/binderRecordedTransactionTest.cpp
index 2ece315..67553fc 100644
--- a/libs/binder/tests/binderRecordedTransactionTest.cpp
+++ b/libs/binder/tests/binderRecordedTransactionTest.cpp
@@ -16,6 +16,7 @@
#include <binder/BinderRecordReplay.h>
#include <gtest/gtest.h>
+#include <utils/Errors.h>
using android::Parcel;
using android::status_t;
@@ -54,3 +55,80 @@
EXPECT_EQ(retrievedTransaction->getDataParcel().readInt64(), 2);
EXPECT_EQ(retrievedTransaction->getReplyParcel().readInt32(), 99);
}
+
+TEST(BinderRecordedTransaction, Checksum) {
+ Parcel d;
+ d.writeInt32(12);
+ d.writeInt64(2);
+ Parcel r;
+ r.writeInt32(99);
+ timespec ts = {1232456, 567890};
+ auto transaction = RecordedTransaction::fromDetails(1, 42, ts, d, r, 0);
+
+ auto file = std::tmpfile();
+ auto fd = unique_fd(fcntl(fileno(file), F_DUPFD, 1));
+
+ status_t status = transaction->dumpToFile(fd);
+ ASSERT_EQ(android::NO_ERROR, status);
+
+ lseek(fd.get(), 9, SEEK_SET);
+ uint32_t badData = 0xffffffff;
+ write(fd.get(), &badData, sizeof(uint32_t));
+ std::rewind(file);
+
+ auto retrievedTransaction = RecordedTransaction::fromFile(fd);
+
+ EXPECT_FALSE(retrievedTransaction.has_value());
+}
+
+TEST(BinderRecordedTransaction, PayloadsExceedPageBoundaries) {
+ // File contents are read with mmap.
+ // This test verifies that transactions are read from portions
+ // of files that cross page boundaries and don't start at a
+ // page boundary offset of the fd.
+ const size_t pageSize = sysconf(_SC_PAGE_SIZE);
+ const size_t largeDataSize = pageSize + 100;
+ std::vector<uint8_t> largePayload;
+ uint8_t filler = 0xaa;
+ largePayload.insert(largePayload.end(), largeDataSize, filler);
+ Parcel d;
+ d.writeInt32(12);
+ d.writeInt64(2);
+ d.writeByteVector(largePayload);
+ Parcel r;
+ r.writeInt32(99);
+ timespec ts = {1232456, 567890};
+ auto transaction = RecordedTransaction::fromDetails(1, 42, ts, d, r, 0);
+
+ auto file = std::tmpfile();
+ auto fd = unique_fd(fcntl(fileno(file), F_DUPFD, 1));
+
+ // Write to file twice
+ status_t status = transaction->dumpToFile(fd);
+ ASSERT_EQ(android::NO_ERROR, status);
+ status = transaction->dumpToFile(fd);
+ ASSERT_EQ(android::NO_ERROR, status);
+
+ std::rewind(file);
+
+ for (int i = 0; i < 2; i++) {
+ auto retrievedTransaction = RecordedTransaction::fromFile(fd);
+
+ EXPECT_EQ(retrievedTransaction->getCode(), 1);
+ EXPECT_EQ(retrievedTransaction->getFlags(), 42);
+ EXPECT_EQ(retrievedTransaction->getTimestamp().tv_sec, ts.tv_sec);
+ EXPECT_EQ(retrievedTransaction->getTimestamp().tv_nsec, ts.tv_nsec);
+ EXPECT_EQ(retrievedTransaction->getDataParcel().dataSize(), d.dataSize());
+ EXPECT_EQ(retrievedTransaction->getReplyParcel().dataSize(), 4);
+ EXPECT_EQ(retrievedTransaction->getReturnedStatus(), 0);
+ EXPECT_EQ(retrievedTransaction->getVersion(), 0);
+
+ EXPECT_EQ(retrievedTransaction->getDataParcel().readInt32(), 12);
+ EXPECT_EQ(retrievedTransaction->getDataParcel().readInt64(), 2);
+ std::optional<std::vector<uint8_t>> payloadOut;
+ EXPECT_EQ(retrievedTransaction->getDataParcel().readByteVector(&payloadOut), android::OK);
+ EXPECT_EQ(payloadOut.value(), largePayload);
+
+ EXPECT_EQ(retrievedTransaction->getReplyParcel().readInt32(), 99);
+ }
+}
diff --git a/libs/binder/tests/binderUtilsHostTest.cpp b/libs/binder/tests/binderUtilsHostTest.cpp
index 4330e3e..25e286c 100644
--- a/libs/binder/tests/binderUtilsHostTest.cpp
+++ b/libs/binder/tests/binderUtilsHostTest.cpp
@@ -37,17 +37,24 @@
EXPECT_EQ(result->stdoutStr, "foo\n");
}
+template <typename T>
+auto millisSince(std::chrono::time_point<T> now) {
+ auto elapsed = std::chrono::system_clock::now() - now;
+ return std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
+}
+
TEST(UtilsHost, ExecuteLongRunning) {
- auto now = std::chrono::system_clock::now();
+ auto start = std::chrono::system_clock::now();
{
- std::vector<std::string> args{"sh", "-c",
- "sleep 0.5 && echo -n f && sleep 0.5 && echo oo && sleep 1"};
- auto result = execute(std::move(args), [](const CommandResult& commandResult) {
+ std::vector<std::string>
+ args{"sh", "-c", "sleep 0.5 && echo -n f && sleep 0.5 && echo oo && sleep 100"};
+ auto result = execute(std::move(args), [&](const CommandResult& commandResult) {
+ std::cout << millisSince(start)
+ << "ms: GOT PARTIAL COMMAND RESULT:" << commandResult.stdoutStr << std::endl;
return android::base::EndsWith(commandResult.stdoutStr, "\n");
});
- auto elapsed = std::chrono::system_clock::now() - now;
- auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
+ auto elapsedMs = millisSince(start);
EXPECT_GE(elapsedMs, 1000);
EXPECT_LT(elapsedMs, 2000);
@@ -58,22 +65,21 @@
// ~CommandResult() called, child process is killed.
// Assert that the second sleep does not finish.
- auto elapsed = std::chrono::system_clock::now() - now;
- auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
- EXPECT_LT(elapsedMs, 2000);
+ EXPECT_LT(millisSince(start), 2000);
}
TEST(UtilsHost, ExecuteLongRunning2) {
- auto now = std::chrono::system_clock::now();
+ auto start = std::chrono::system_clock::now();
{
std::vector<std::string> args{"sh", "-c",
- "sleep 2 && echo -n f && sleep 2 && echo oo && sleep 2"};
- auto result = execute(std::move(args), [](const CommandResult& commandResult) {
+ "sleep 2 && echo -n f && sleep 2 && echo oo && sleep 100"};
+ auto result = execute(std::move(args), [&](const CommandResult& commandResult) {
+ std::cout << millisSince(start)
+ << "ms: GOT PARTIAL COMMAND RESULT:" << commandResult.stdoutStr << std::endl;
return android::base::EndsWith(commandResult.stdoutStr, "\n");
});
- auto elapsed = std::chrono::system_clock::now() - now;
- auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
+ auto elapsedMs = millisSince(start);
EXPECT_GE(elapsedMs, 4000);
EXPECT_LT(elapsedMs, 6000);
@@ -84,9 +90,7 @@
// ~CommandResult() called, child process is killed.
// Assert that the second sleep does not finish.
- auto elapsed = std::chrono::system_clock::now() - now;
- auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
- EXPECT_LT(elapsedMs, 6000);
+ EXPECT_LT(millisSince(start), 6000);
}
TEST(UtilsHost, KillWithSigKill) {
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 24a5295..8ee9b14 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -480,20 +480,18 @@
mSyncedFrameNumbers.erase(callbackId.framenumber);
}
-void BLASTBufferQueue::acquireNextBufferLocked(
+status_t BLASTBufferQueue::acquireNextBufferLocked(
const std::optional<SurfaceComposerClient::Transaction*> transaction) {
// If the next transaction is set, we want to guarantee the our acquire will not fail, so don't
// include the extra buffer when checking if we can acquire the next buffer.
- const bool includeExtraAcquire = !transaction;
- const bool maxAcquired = maxBuffersAcquired(includeExtraAcquire);
- if (mNumFrameAvailable == 0 || maxAcquired) {
- BQA_LOGV("Can't process next buffer maxBuffersAcquired=%s", boolToString(maxAcquired));
- return;
+ if (mNumFrameAvailable == 0) {
+ BQA_LOGV("Can't process next buffer. No available frames");
+ return NOT_ENOUGH_DATA;
}
if (mSurfaceControl == nullptr) {
BQA_LOGE("ERROR : surface control is null");
- return;
+ return NAME_NOT_FOUND;
}
SurfaceComposerClient::Transaction localTransaction;
@@ -510,10 +508,10 @@
mBufferItemConsumer->acquireBuffer(&bufferItem, 0 /* expectedPresent */, false);
if (status == BufferQueue::NO_BUFFER_AVAILABLE) {
BQA_LOGV("Failed to acquire a buffer, err=NO_BUFFER_AVAILABLE");
- return;
+ return status;
} else if (status != OK) {
BQA_LOGE("Failed to acquire a buffer, err=%s", statusToString(status).c_str());
- return;
+ return status;
}
auto buffer = bufferItem.mGraphicBuffer;
@@ -523,7 +521,7 @@
if (buffer == nullptr) {
mBufferItemConsumer->releaseBuffer(bufferItem, Fence::NO_FENCE);
BQA_LOGE("Buffer was empty");
- return;
+ return BAD_VALUE;
}
if (rejectBuffer(bufferItem)) {
@@ -532,8 +530,7 @@
mSize.width, mSize.height, mRequestedSize.width, mRequestedSize.height,
buffer->getWidth(), buffer->getHeight(), bufferItem.mTransform);
mBufferItemConsumer->releaseBuffer(bufferItem, Fence::NO_FENCE);
- acquireNextBufferLocked(transaction);
- return;
+ return acquireNextBufferLocked(transaction);
}
mNumAcquired++;
@@ -621,6 +618,7 @@
bufferItem.mTimestamp, bufferItem.mIsAutoTimestamp ? "(auto)" : "",
static_cast<uint32_t>(mPendingTransactions.size()), bufferItem.mGraphicBuffer->getId(),
bufferItem.mAutoRefresh ? " mAutoRefresh" : "", bufferItem.mTransform);
+ return OK;
}
Rect BLASTBufferQueue::computeCrop(const BufferItem& item) {
@@ -643,31 +641,6 @@
mBufferItemConsumer->releaseBuffer(bufferItem, bufferItem.mFence);
}
-void BLASTBufferQueue::flushAndWaitForFreeBuffer(std::unique_lock<std::mutex>& lock) {
- if (!mSyncedFrameNumbers.empty() && mNumFrameAvailable > 0) {
- // We are waiting on a previous sync's transaction callback so allow another sync
- // transaction to proceed.
- //
- // We need to first flush out the transactions that were in between the two syncs.
- // We do this by merging them into mSyncTransaction so any buffer merging will get
- // a release callback invoked. The release callback will be async so we need to wait
- // on max acquired to make sure we have the capacity to acquire another buffer.
- if (maxBuffersAcquired(false /* includeExtraAcquire */)) {
- BQA_LOGD("waiting to flush shadow queue...");
- mCallbackCV.wait(lock);
- }
- while (mNumFrameAvailable > 0) {
- // flush out the shadow queue
- acquireAndReleaseBuffer();
- }
- }
-
- while (maxBuffersAcquired(false /* includeExtraAcquire */)) {
- BQA_LOGD("waiting for free buffer.");
- mCallbackCV.wait(lock);
- }
-}
-
void BLASTBufferQueue::onFrameAvailable(const BufferItem& item) {
std::function<void(SurfaceComposerClient::Transaction*)> prevCallback = nullptr;
SurfaceComposerClient::Transaction* prevTransaction = nullptr;
@@ -680,7 +653,6 @@
BQA_LOGV("onFrameAvailable-start syncTransactionSet=%s", boolToString(syncTransactionSet));
if (syncTransactionSet) {
- bool mayNeedToWaitForBuffer = true;
// If we are going to re-use the same mSyncTransaction, release the buffer that may
// already be set in the Transaction. This is to allow us a free slot early to continue
// processing a new buffer.
@@ -691,14 +663,20 @@
bufferData->frameNumber);
releaseBuffer(bufferData->generateReleaseCallbackId(),
bufferData->acquireFence);
- // Because we just released a buffer, we know there's no need to wait for a free
- // buffer.
- mayNeedToWaitForBuffer = false;
}
}
- if (mayNeedToWaitForBuffer) {
- flushAndWaitForFreeBuffer(_lock);
+ if (waitForTransactionCallback) {
+ // We are waiting on a previous sync's transaction callback so allow another sync
+ // transaction to proceed.
+ //
+ // We need to first flush out the transactions that were in between the two syncs.
+ // We do this by merging them into mSyncTransaction so any buffer merging will get
+ // a release callback invoked.
+ while (mNumFrameAvailable > 0) {
+ // flush out the shadow queue
+ acquireAndReleaseBuffer();
+ }
}
}
@@ -714,7 +692,12 @@
item.mFrameNumber, boolToString(syncTransactionSet));
if (syncTransactionSet) {
- acquireNextBufferLocked(mSyncTransaction);
+ // If there's no available buffer and we're in a sync transaction, we need to wait
+ // instead of returning since we guarantee a buffer will be acquired for the sync.
+ while (acquireNextBufferLocked(mSyncTransaction) == BufferQueue::NO_BUFFER_AVAILABLE) {
+ BQA_LOGD("waiting for available buffer");
+ mCallbackCV.wait(_lock);
+ }
// Only need a commit callback when syncing to ensure the buffer that's synced has been
// sent to SF
@@ -824,15 +807,6 @@
return mSize != bufferSize;
}
-// Check if we have acquired the maximum number of buffers.
-// Consumer can acquire an additional buffer if that buffer is not droppable. Set
-// includeExtraAcquire is true to include this buffer to the count. Since this depends on the state
-// of the buffer, the next acquire may return with NO_BUFFER_AVAILABLE.
-bool BLASTBufferQueue::maxBuffersAcquired(bool includeExtraAcquire) const {
- int maxAcquiredBuffers = mMaxAcquiredBuffers + (includeExtraAcquire ? 2 : 1);
- return mNumAcquired >= maxAcquiredBuffers;
-}
-
class BBQSurface : public Surface {
private:
std::mutex mMutex;
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index 48226b9..f074bd3 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -136,12 +136,11 @@
void createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
sp<IGraphicBufferConsumer>* outConsumer);
- void acquireNextBufferLocked(
+ status_t acquireNextBufferLocked(
const std::optional<SurfaceComposerClient::Transaction*> transaction) REQUIRES(mMutex);
Rect computeCrop(const BufferItem& item) REQUIRES(mMutex);
// Return true if we need to reject the buffer based on the scaling mode and the buffer size.
bool rejectBuffer(const BufferItem& item) REQUIRES(mMutex);
- bool maxBuffersAcquired(bool includeExtraAcquire) const REQUIRES(mMutex);
static PixelFormat convertBufferFormat(PixelFormat& format);
void mergePendingTransactions(SurfaceComposerClient::Transaction* t, uint64_t frameNumber)
REQUIRES(mMutex);
@@ -150,7 +149,6 @@
void acquireAndReleaseBuffer() REQUIRES(mMutex);
void releaseBuffer(const ReleaseCallbackId& callbackId, const sp<Fence>& releaseFence)
REQUIRES(mMutex);
- void flushAndWaitForFreeBuffer(std::unique_lock<std::mutex>& lock);
std::string mName;
// Represents the queued buffer count from buffer queue,
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index eb97a68..caf7101 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -2196,8 +2196,31 @@
// Update the temporary touch state.
BitSet32 pointerIds;
pointerIds.markBit(entry.pointerProperties[pointerIndex].id);
-
tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, pointerIds);
+
+ // If this is the pointer going down and the touched window has a wallpaper
+ // then also add the touched wallpaper windows so they are locked in for the duration
+ // of the touch gesture.
+ // We do not collect wallpapers during HOVER_MOVE or SCROLL because the wallpaper
+ // engine only supports touch events. We would need to add a mechanism similar
+ // to View.onGenericMotionEvent to enable wallpapers to handle these events.
+ if (maskedAction == AMOTION_EVENT_ACTION_DOWN ||
+ maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN) {
+ if ((targetFlags & InputTarget::FLAG_FOREGROUND) &&
+ windowHandle->getInfo()->inputConfig.test(
+ gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
+ sp<WindowInfoHandle> wallpaper = findWallpaperWindowBelow(windowHandle);
+ if (wallpaper != nullptr) {
+ int32_t wallpaperFlags = InputTarget::FLAG_WINDOW_IS_OBSCURED |
+ InputTarget::FLAG_WINDOW_IS_PARTIALLY_OBSCURED |
+ InputTarget::FLAG_DISPATCH_AS_IS;
+ if (isSplit) {
+ wallpaperFlags |= InputTarget::FLAG_SPLIT;
+ }
+ tempTouchState.addOrUpdateWindow(wallpaper, wallpaperFlags, pointerIds);
+ }
+ }
+ }
}
} else {
/* Case 2: Pointer move, up, cancel or non-splittable pointer down. */
@@ -2274,6 +2297,10 @@
BitSet32 pointerIds;
pointerIds.markBit(entry.pointerProperties[0].id);
tempTouchState.addOrUpdateWindow(newTouchedWindowHandle, targetFlags, pointerIds);
+
+ // Check if the wallpaper window should deliver the corresponding event.
+ slipWallpaperTouch(targetFlags, oldTouchedWindowHandle, newTouchedWindowHandle,
+ tempTouchState, pointerIds);
}
}
@@ -2379,39 +2406,6 @@
}
}
- // If this is the first pointer going down and the touched window has a wallpaper
- // then also add the touched wallpaper windows so they are locked in for the duration
- // of the touch gesture.
- // We do not collect wallpapers during HOVER_MOVE or SCROLL because the wallpaper
- // engine only supports touch events. We would need to add a mechanism similar
- // to View.onGenericMotionEvent to enable wallpapers to handle these events.
- if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
- sp<WindowInfoHandle> foregroundWindowHandle =
- tempTouchState.getFirstForegroundWindowHandle();
- if (foregroundWindowHandle &&
- foregroundWindowHandle->getInfo()->inputConfig.test(
- WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
- const std::vector<sp<WindowInfoHandle>>& windowHandles =
- getWindowHandlesLocked(displayId);
- for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
- const WindowInfo* info = windowHandle->getInfo();
- if (info->displayId == displayId &&
- windowHandle->getInfo()->inputConfig.test(
- WindowInfo::InputConfig::IS_WALLPAPER)) {
- BitSet32 pointerIds;
- pointerIds.markBit(entry.pointerProperties[0].id);
- tempTouchState
- .addOrUpdateWindow(windowHandle,
- InputTarget::FLAG_WINDOW_IS_OBSCURED |
- InputTarget::
- FLAG_WINDOW_IS_PARTIALLY_OBSCURED |
- InputTarget::FLAG_DISPATCH_AS_IS,
- pointerIds);
- }
- }
- }
- }
-
// Success! Output targets.
injectionResult = InputEventInjectionResult::SUCCEEDED;
@@ -3690,7 +3684,7 @@
}
void InputDispatcher::synthesizePointerDownEventsForConnectionLocked(
- const sp<Connection>& connection) {
+ const sp<Connection>& connection, int32_t targetFlags) {
if (connection->status == Connection::Status::BROKEN) {
return;
}
@@ -3718,7 +3712,7 @@
target.globalScaleFactor = windowInfo->globalScaleFactor;
}
target.inputChannel = connection->inputChannel;
- target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
+ target.flags = targetFlags;
const bool wasEmpty = connection->outboundQueue.empty();
@@ -3753,6 +3747,16 @@
}
}
+void InputDispatcher::synthesizeCancelationEventsForWindowLocked(
+ const sp<WindowInfoHandle>& windowHandle, const CancelationOptions& options) {
+ if (windowHandle != nullptr) {
+ sp<Connection> wallpaperConnection = getConnectionLocked(windowHandle->getToken());
+ if (wallpaperConnection != nullptr) {
+ synthesizeCancelationEventsForConnectionLocked(wallpaperConnection, options);
+ }
+ }
+}
+
std::unique_ptr<MotionEntry> InputDispatcher::splitMotionEvent(
const MotionEntry& originalMotionEntry, BitSet32 pointerIds) {
ALOG_ASSERT(pointerIds.value != 0);
@@ -4774,14 +4778,7 @@
touchedWindow.windowHandle->getInfo()->inputConfig.test(
gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
sp<WindowInfoHandle> wallpaper = state.getWallpaperWindow();
- if (wallpaper != nullptr) {
- sp<Connection> wallpaperConnection =
- getConnectionLocked(wallpaper->getToken());
- if (wallpaperConnection != nullptr) {
- synthesizeCancelationEventsForConnectionLocked(wallpaperConnection,
- options);
- }
- }
+ synthesizeCancelationEventsForWindowLocked(wallpaper, options);
}
}
state.windows.erase(state.windows.begin() + i);
@@ -5094,6 +5091,7 @@
// Erase old window.
int32_t oldTargetFlags = touchedWindow->targetFlags;
BitSet32 pointerIds = touchedWindow->pointerIds;
+ sp<WindowInfoHandle> fromWindowHandle = touchedWindow->windowHandle;
state->removeWindowByToken(fromToken);
// Add new window.
@@ -5125,7 +5123,10 @@
options(CancelationOptions::CANCEL_POINTER_EVENTS,
"transferring touch focus from this window to another window");
synthesizeCancelationEventsForConnectionLocked(fromConnection, options);
- synthesizePointerDownEventsForConnectionLocked(toConnection);
+ synthesizePointerDownEventsForConnectionLocked(toConnection, newTargetFlags);
+ // Check if the wallpaper window should deliver the corresponding event.
+ transferWallpaperTouch(oldTargetFlags, newTargetFlags, fromWindowHandle, toWindowHandle,
+ *state, pointerIds);
}
if (DEBUG_FOCUS) {
@@ -6410,4 +6411,97 @@
mMonitorDispatchingTimeout = timeout;
}
+void InputDispatcher::slipWallpaperTouch(int32_t targetFlags,
+ const sp<WindowInfoHandle>& oldWindowHandle,
+ const sp<WindowInfoHandle>& newWindowHandle,
+ TouchState& state, const BitSet32& pointerIds) {
+ const bool oldHasWallpaper = oldWindowHandle->getInfo()->inputConfig.test(
+ gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
+ const bool newHasWallpaper = (targetFlags & InputTarget::FLAG_FOREGROUND) &&
+ newWindowHandle->getInfo()->inputConfig.test(
+ gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
+ const sp<WindowInfoHandle> oldWallpaper =
+ oldHasWallpaper ? state.getWallpaperWindow() : nullptr;
+ const sp<WindowInfoHandle> newWallpaper =
+ newHasWallpaper ? findWallpaperWindowBelow(newWindowHandle) : nullptr;
+ if (oldWallpaper == newWallpaper) {
+ return;
+ }
+
+ if (oldWallpaper != nullptr) {
+ state.addOrUpdateWindow(oldWallpaper, InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT,
+ BitSet32(0));
+ }
+
+ if (newWallpaper != nullptr) {
+ state.addOrUpdateWindow(newWallpaper,
+ InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER |
+ InputTarget::FLAG_WINDOW_IS_OBSCURED |
+ InputTarget::FLAG_WINDOW_IS_PARTIALLY_OBSCURED,
+ pointerIds);
+ }
+}
+
+void InputDispatcher::transferWallpaperTouch(int32_t oldTargetFlags, int32_t newTargetFlags,
+ const sp<WindowInfoHandle> fromWindowHandle,
+ const sp<WindowInfoHandle> toWindowHandle,
+ TouchState& state, const BitSet32& pointerIds) {
+ const bool oldHasWallpaper = (oldTargetFlags & InputTarget::FLAG_FOREGROUND) &&
+ fromWindowHandle->getInfo()->inputConfig.test(
+ gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
+ const bool newHasWallpaper = (newTargetFlags & InputTarget::FLAG_FOREGROUND) &&
+ toWindowHandle->getInfo()->inputConfig.test(
+ gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
+
+ const sp<WindowInfoHandle> oldWallpaper =
+ oldHasWallpaper ? state.getWallpaperWindow() : nullptr;
+ const sp<WindowInfoHandle> newWallpaper =
+ newHasWallpaper ? findWallpaperWindowBelow(toWindowHandle) : nullptr;
+ if (oldWallpaper == newWallpaper) {
+ return;
+ }
+
+ if (oldWallpaper != nullptr) {
+ CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
+ "transferring touch focus to another window");
+ state.removeWindowByToken(oldWallpaper->getToken());
+ synthesizeCancelationEventsForWindowLocked(oldWallpaper, options);
+ }
+
+ if (newWallpaper != nullptr) {
+ int32_t wallpaperFlags =
+ oldTargetFlags & (InputTarget::FLAG_SPLIT | InputTarget::FLAG_DISPATCH_AS_IS);
+ wallpaperFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED |
+ InputTarget::FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
+ state.addOrUpdateWindow(newWallpaper, wallpaperFlags, pointerIds);
+ sp<Connection> wallpaperConnection = getConnectionLocked(newWallpaper->getToken());
+ if (wallpaperConnection != nullptr) {
+ sp<Connection> toConnection = getConnectionLocked(toWindowHandle->getToken());
+ toConnection->inputState.mergePointerStateTo(wallpaperConnection->inputState);
+ synthesizePointerDownEventsForConnectionLocked(wallpaperConnection, wallpaperFlags);
+ }
+ }
+}
+
+sp<WindowInfoHandle> InputDispatcher::findWallpaperWindowBelow(
+ const sp<WindowInfoHandle>& windowHandle) const {
+ const std::vector<sp<WindowInfoHandle>>& windowHandles =
+ getWindowHandlesLocked(windowHandle->getInfo()->displayId);
+ bool foundWindow = false;
+ for (const sp<WindowInfoHandle>& otherHandle : windowHandles) {
+ if (!foundWindow && otherHandle != windowHandle) {
+ continue;
+ }
+ if (windowHandle == otherHandle) {
+ foundWindow = true;
+ continue;
+ }
+
+ if (otherHandle->getInfo()->inputConfig.test(WindowInfo::InputConfig::IS_WALLPAPER)) {
+ return otherHandle;
+ }
+ }
+ return nullptr;
+}
+
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index ed89ed0..0df5894 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -621,8 +621,12 @@
const CancelationOptions& options)
REQUIRES(mLock);
- void synthesizePointerDownEventsForConnectionLocked(const sp<Connection>& connection)
- REQUIRES(mLock);
+ void synthesizePointerDownEventsForConnectionLocked(const sp<Connection>& connection,
+ int32_t targetFlags) REQUIRES(mLock);
+
+ void synthesizeCancelationEventsForWindowLocked(
+ const sp<android::gui::WindowInfoHandle>& windowHandle,
+ const CancelationOptions& options) REQUIRES(mLock);
// Splitting motion events across windows.
std::unique_ptr<MotionEntry> splitMotionEvent(const MotionEntry& originalMotionEntry,
@@ -684,6 +688,18 @@
bool recentWindowsAreOwnedByLocked(int32_t pid, int32_t uid) REQUIRES(mLock);
sp<InputReporterInterface> mReporter;
+
+ void slipWallpaperTouch(int32_t targetFlags,
+ const sp<android::gui::WindowInfoHandle>& oldWindowHandle,
+ const sp<android::gui::WindowInfoHandle>& newWindowHandle,
+ TouchState& state, const BitSet32& pointerIds) REQUIRES(mLock);
+ void transferWallpaperTouch(int32_t oldTargetFlags, int32_t newTargetFlags,
+ const sp<android::gui::WindowInfoHandle> fromWindowHandle,
+ const sp<android::gui::WindowInfoHandle> toWindowHandle,
+ TouchState& state, const BitSet32& pointerIds) REQUIRES(mLock);
+
+ sp<android::gui::WindowInfoHandle> findWallpaperWindowBelow(
+ const sp<android::gui::WindowInfoHandle>& windowHandle) const REQUIRES(mLock);
};
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index fce0f99..b2575fe 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -58,6 +58,8 @@
AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
static constexpr int32_t POINTER_2_DOWN =
AMOTION_EVENT_ACTION_POINTER_DOWN | (2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+static constexpr int32_t POINTER_0_UP =
+ AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
static constexpr int32_t POINTER_1_UP =
AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
@@ -73,6 +75,9 @@
static constexpr std::chrono::duration STALE_EVENT_TIMEOUT = 1000ms;
+static constexpr int expectedWallpaperFlags =
+ AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
+
struct PointF {
float x;
float y;
@@ -1670,8 +1675,6 @@
sp<FakeWindowHandle> wallpaperWindow =
new FakeWindowHandle(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
wallpaperWindow->setIsWallpaper(true);
- constexpr int expectedWallpaperFlags =
- AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {foregroundWindow, wallpaperWindow}}});
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -1714,8 +1717,6 @@
sp<FakeWindowHandle> wallpaperWindow =
new FakeWindowHandle(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
wallpaperWindow->setIsWallpaper(true);
- constexpr int expectedWallpaperFlags =
- AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {foregroundWindow, wallpaperWindow}}});
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -1745,24 +1746,27 @@
foregroundWindow->consumeMotionCancel();
}
+class ShouldSplitTouchFixture : public InputDispatcherTest,
+ public ::testing::WithParamInterface<bool> {};
+INSTANTIATE_TEST_SUITE_P(InputDispatcherTest, ShouldSplitTouchFixture,
+ ::testing::Values(true, false));
/**
* A single window that receives touch (on top), and a wallpaper window underneath it.
* The top window gets a multitouch gesture.
* Ensure that wallpaper gets the same gesture.
*/
-TEST_F(InputDispatcherTest, WallpaperWindow_ReceivesMultiTouch) {
+TEST_P(ShouldSplitTouchFixture, WallpaperWindowReceivesMultiTouch) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
- window->setDupTouchToWallpaper(true);
+ sp<FakeWindowHandle> foregroundWindow =
+ new FakeWindowHandle(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT);
+ foregroundWindow->setDupTouchToWallpaper(true);
+ foregroundWindow->setPreventSplitting(GetParam());
sp<FakeWindowHandle> wallpaperWindow =
new FakeWindowHandle(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
wallpaperWindow->setIsWallpaper(true);
- constexpr int expectedWallpaperFlags =
- AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window, wallpaperWindow}}});
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {foregroundWindow, wallpaperWindow}}});
// Touch down on top window
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -1771,7 +1775,7 @@
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
// Both top window and its wallpaper should receive the touch down
- window->consumeMotionDown();
+ foregroundWindow->consumeMotionDown();
wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
// Second finger down on the top window
@@ -1790,11 +1794,34 @@
InputEventInjectionSync::WAIT_FOR_RESULT))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
- window->consumeMotionPointerDown(1 /* pointerIndex */);
+ foregroundWindow->consumeMotionPointerDown(1 /* pointerIndex */);
wallpaperWindow->consumeMotionPointerDown(1 /* pointerIndex */, ADISPLAY_ID_DEFAULT,
expectedWallpaperFlags);
- window->assertNoEvents();
- wallpaperWindow->assertNoEvents();
+
+ const MotionEvent secondFingerUpEvent =
+ MotionEventBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .displayId(ADISPLAY_ID_DEFAULT)
+ .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+ .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .x(100)
+ .y(100))
+ .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .x(150)
+ .y(150))
+ .build();
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT,
+ InputEventInjectionSync::WAIT_FOR_RESULT))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ foregroundWindow->consumeMotionPointerUp(0);
+ wallpaperWindow->consumeMotionPointerUp(0, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {100, 100}))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ foregroundWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT);
+ wallpaperWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
}
/**
@@ -1821,8 +1848,6 @@
new FakeWindowHandle(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
wallpaperWindow->setFrame(Rect(0, 0, 400, 200));
wallpaperWindow->setIsWallpaper(true);
- constexpr int expectedWallpaperFlags =
- AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
mDispatcher->setInputWindows(
{{ADISPLAY_ID_DEFAULT, {leftWindow, rightWindow, wallpaperWindow}}});
@@ -1887,62 +1912,49 @@
wallpaperWindow->assertNoEvents();
}
-TEST_F(InputDispatcherTest, WallpaperWindowReceivesMultiTouch) {
+/**
+ * Two windows: a window on the left with dup touch to wallpaper and window on the right without it.
+ * The touch slips to the right window. so left window and wallpaper should receive ACTION_CANCEL
+ * The right window should receive ACTION_DOWN.
+ */
+TEST_F(InputDispatcherTest, WallpaperWindowWhenSlippery) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window =
- sp<FakeWindowHandle>::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
- window->setDupTouchToWallpaper(true);
+ sp<FakeWindowHandle> leftWindow =
+ new FakeWindowHandle(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+ leftWindow->setFrame(Rect(0, 0, 200, 200));
+ leftWindow->setDupTouchToWallpaper(true);
+ leftWindow->setSlippery(true);
+
+ sp<FakeWindowHandle> rightWindow =
+ new FakeWindowHandle(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+ rightWindow->setFrame(Rect(200, 0, 400, 200));
sp<FakeWindowHandle> wallpaperWindow =
- sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
+ new FakeWindowHandle(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
wallpaperWindow->setIsWallpaper(true);
- constexpr int expectedWallpaperFlags =
- AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
- wallpaperWindow->setPreventSplitting(true);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window, wallpaperWindow}}});
+ mDispatcher->setInputWindows(
+ {{ADISPLAY_ID_DEFAULT, {leftWindow, rightWindow, wallpaperWindow}}});
+ // Touch down on left window
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
- {50, 50}))
+ {100, 100}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
- window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+
+ // Both foreground window and its wallpaper should receive the touch down
+ leftWindow->consumeMotionDown();
wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
- const MotionEvent secondFingerDownEvent =
- MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
- .displayId(ADISPLAY_ID_DEFAULT)
- .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
- .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
- .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10))
- .build();
+ // Move to right window, the left window should receive cancel.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
- InputEventInjectionSync::WAIT_FOR_RESULT))
+ injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT, {201, 100}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
- window->consumeMotionPointerDown(1);
- wallpaperWindow->consumeMotionPointerDown(1, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
-
- const MotionEvent secondFingerUpEvent =
- MotionEventBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN)
- .displayId(ADISPLAY_ID_DEFAULT)
- .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
- .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
- .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10))
- .build();
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT,
- InputEventInjectionSync::WAIT_FOR_RESULT))
- << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
- window->consumeMotionPointerUp(1);
- wallpaperWindow->consumeMotionPointerUp(1, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
-
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {50, 50}))
- << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
- window->consumeMotionUp(ADISPLAY_ID_DEFAULT);
- wallpaperWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+ leftWindow->consumeMotionCancel();
+ rightWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+ wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
}
/**
@@ -2620,20 +2632,26 @@
// Create a couple of windows
sp<FakeWindowHandle> firstWindow =
new FakeWindowHandle(application, mDispatcher, "First Window", ADISPLAY_ID_DEFAULT);
+ firstWindow->setDupTouchToWallpaper(true);
+
sp<FakeWindowHandle> secondWindow =
new FakeWindowHandle(application, mDispatcher, "Second Window", ADISPLAY_ID_DEFAULT);
-
+ sp<FakeWindowHandle> wallpaper =
+ new FakeWindowHandle(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
+ wallpaper->setIsWallpaper(true);
// Add the windows to the dispatcher
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow}}});
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow, wallpaper}}});
// Send down to the first window
NotifyMotionArgs downMotionArgs =
generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
ADISPLAY_ID_DEFAULT);
mDispatcher->notifyMotion(&downMotionArgs);
+
// Only the first window should get the down event
firstWindow->consumeMotionDown();
secondWindow->assertNoEvents();
+ wallpaper->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
// Transfer touch to the second window
TransferFunction f = GetParam();
@@ -2642,6 +2660,7 @@
// The first window gets cancel and the second gets down
firstWindow->consumeMotionCancel();
secondWindow->consumeMotionDown();
+ wallpaper->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
// Send up event to the second window
NotifyMotionArgs upMotionArgs =
@@ -2651,6 +2670,7 @@
// The first window gets no events and the second gets up
firstWindow->assertNoEvents();
secondWindow->consumeMotionUp();
+ wallpaper->assertNoEvents();
}
/**
@@ -2772,6 +2792,65 @@
secondWindow->consumeMotionUp();
}
+TEST_P(TransferTouchFixture, TransferTouch_MultipleWallpapers) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+ // Create a couple of windows
+ sp<FakeWindowHandle> firstWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "First Window",
+ ADISPLAY_ID_DEFAULT);
+ firstWindow->setDupTouchToWallpaper(true);
+ sp<FakeWindowHandle> secondWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Second Window",
+ ADISPLAY_ID_DEFAULT);
+ secondWindow->setDupTouchToWallpaper(true);
+
+ sp<FakeWindowHandle> wallpaper1 =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper1", ADISPLAY_ID_DEFAULT);
+ wallpaper1->setIsWallpaper(true);
+
+ sp<FakeWindowHandle> wallpaper2 =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper2", ADISPLAY_ID_DEFAULT);
+ wallpaper2->setIsWallpaper(true);
+ // Add the windows to the dispatcher
+ mDispatcher->setInputWindows(
+ {{ADISPLAY_ID_DEFAULT, {firstWindow, wallpaper1, secondWindow, wallpaper2}}});
+
+ // Send down to the first window
+ NotifyMotionArgs downMotionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT);
+ mDispatcher->notifyMotion(&downMotionArgs);
+
+ // Only the first window should get the down event
+ firstWindow->consumeMotionDown();
+ secondWindow->assertNoEvents();
+ wallpaper1->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+ wallpaper2->assertNoEvents();
+
+ // Transfer touch focus to the second window
+ TransferFunction f = GetParam();
+ bool success = f(mDispatcher, firstWindow->getToken(), secondWindow->getToken());
+ ASSERT_TRUE(success);
+
+ // The first window gets cancel and the second gets down
+ firstWindow->consumeMotionCancel();
+ secondWindow->consumeMotionDown();
+ wallpaper1->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+ wallpaper2->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+
+ // Send up event to the second window
+ NotifyMotionArgs upMotionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT);
+ mDispatcher->notifyMotion(&upMotionArgs);
+ // The first window gets no events and the second gets up
+ firstWindow->assertNoEvents();
+ secondWindow->consumeMotionUp();
+ wallpaper1->assertNoEvents();
+ wallpaper2->consumeMotionUp(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+}
+
// For the cases of single pointer touch and two pointers non-split touch, the api's
// 'transferTouch' and 'transferTouchFocus' are equivalent in behaviour. They only differ
// for the case where there are multiple pointers split across several windows.
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index d1275b0..c2f3bdd 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -423,6 +423,9 @@
property_get("debug.sf.treat_170m_as_sRGB", value, "0");
mTreat170mAsSrgb = atoi(value);
+ mIgnoreHwcPhysicalDisplayOrientation =
+ base::GetBoolProperty("debug.sf.ignore_hwc_physical_display_orientation"s, false);
+
// We should be reading 'persist.sys.sf.color_saturation' here
// but since /data may be encrypted, we need to wait until after vold
// comes online to attempt to read the property. The property is
@@ -2410,7 +2413,8 @@
if (!id) {
return ui::ROTATION_0;
}
- if (getHwComposer().getComposer()->isSupported(
+ if (!mIgnoreHwcPhysicalDisplayOrientation &&
+ getHwComposer().getComposer()->isSupported(
Hwc2::Composer::OptionalFeature::PhysicalDisplayOrientation)) {
switch (getHwComposer().getPhysicalDisplayOrientation(*id)) {
case Hwc2::AidlTransform::ROT_90:
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index bb94908..87124d9 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -353,6 +353,11 @@
// on this behavior to increase contrast for some media sources.
bool mTreat170mAsSrgb = false;
+ // Allows to ignore physical orientation provided through hwc API in favour of
+ // 'ro.surface_flinger.primary_display_orientation'.
+ // TODO(b/246793311): Clean up a temporary property
+ bool mIgnoreHwcPhysicalDisplayOrientation = false;
+
protected:
// We're reference counted, never destroy SurfaceFlinger directly
virtual ~SurfaceFlinger();