Merge "Decouple HapticScale and ExternalVibratorService scale."
diff --git a/cmds/dumpsys/tests/dumpsys_test.cpp b/cmds/dumpsys/tests/dumpsys_test.cpp
index f0c19b9..b8e5ce1 100644
--- a/cmds/dumpsys/tests/dumpsys_test.cpp
+++ b/cmds/dumpsys/tests/dumpsys_test.cpp
@@ -60,6 +60,7 @@
MOCK_METHOD1(isDeclared, bool(const String16&));
MOCK_METHOD1(getDeclaredInstances, Vector<String16>(const String16&));
MOCK_METHOD1(updatableViaApex, std::optional<String16>(const String16&));
+ MOCK_METHOD1(getUpdatableNames, Vector<String16>(const String16&));
MOCK_METHOD1(getConnectionInfo, std::optional<ConnectionInfo>(const String16&));
MOCK_METHOD2(registerForNotifications, status_t(const String16&,
const sp<LocalRegistrationCallback>&));
diff --git a/cmds/servicemanager/ServiceManager.cpp b/cmds/servicemanager/ServiceManager.cpp
index 3681d5b..2684f04 100644
--- a/cmds/servicemanager/ServiceManager.cpp
+++ b/cmds/servicemanager/ServiceManager.cpp
@@ -142,6 +142,26 @@
return updatableViaApex;
}
+static std::vector<std::string> getVintfUpdatableInstances(const std::string& apexName) {
+ std::vector<std::string> instances;
+
+ forEachManifest([&](const ManifestWithDescription& mwd) {
+ mwd.manifest->forEachInstance([&](const auto& manifestInstance) {
+ if (manifestInstance.format() == vintf::HalFormat::AIDL &&
+ manifestInstance.updatableViaApex().has_value() &&
+ manifestInstance.updatableViaApex().value() == apexName) {
+ std::string aname = manifestInstance.package() + "." +
+ manifestInstance.interface() + "/" + manifestInstance.instance();
+ instances.push_back(aname);
+ }
+ return false; // continue
+ });
+ return false; // continue
+ });
+
+ return instances;
+}
+
static std::optional<ConnectionInfo> getVintfConnectionInfo(const std::string& name) {
AidlName aname;
if (!AidlName::fill(name, &aname)) return std::nullopt;
@@ -512,6 +532,30 @@
return Status::ok();
}
+Status ServiceManager::getUpdatableNames([[maybe_unused]] const std::string& apexName,
+ std::vector<std::string>* outReturn) {
+ auto ctx = mAccess->getCallingContext();
+
+ std::vector<std::string> apexUpdatableInstances;
+#ifndef VENDORSERVICEMANAGER
+ apexUpdatableInstances = getVintfUpdatableInstances(apexName);
+#endif
+
+ outReturn->clear();
+
+ for (const std::string& instance : apexUpdatableInstances) {
+ if (mAccess->canFind(ctx, instance)) {
+ outReturn->push_back(instance);
+ }
+ }
+
+ if (outReturn->size() == 0 && apexUpdatableInstances.size() != 0) {
+ return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denial");
+ }
+
+ return Status::ok();
+}
+
Status ServiceManager::getConnectionInfo(const std::string& name,
std::optional<ConnectionInfo>* outReturn) {
auto ctx = mAccess->getCallingContext();
diff --git a/cmds/servicemanager/ServiceManager.h b/cmds/servicemanager/ServiceManager.h
index 07b79f8..b24c11c 100644
--- a/cmds/servicemanager/ServiceManager.h
+++ b/cmds/servicemanager/ServiceManager.h
@@ -49,6 +49,8 @@
binder::Status getDeclaredInstances(const std::string& interface, std::vector<std::string>* outReturn) override;
binder::Status updatableViaApex(const std::string& name,
std::optional<std::string>* outReturn) override;
+ binder::Status getUpdatableNames(const std::string& apexName,
+ std::vector<std::string>* outReturn) override;
binder::Status getConnectionInfo(const std::string& name,
std::optional<ConnectionInfo>* outReturn) override;
binder::Status registerClientCallback(const std::string& name, const sp<IBinder>& service,
diff --git a/include/android/font.h b/include/android/font.h
index 8a3a474..45eb81a 100644
--- a/include/android/font.h
+++ b/include/android/font.h
@@ -31,6 +31,7 @@
#include <stdbool.h>
#include <stddef.h>
+#include <stdint.h>
#include <sys/cdefs.h>
/******************************************************************
diff --git a/include/android/font_matcher.h b/include/android/font_matcher.h
index 4417422..63b0328 100644
--- a/include/android/font_matcher.h
+++ b/include/android/font_matcher.h
@@ -75,6 +75,7 @@
#include <stdbool.h>
#include <stddef.h>
+#include <stdint.h>
#include <sys/cdefs.h>
#include <android/font.h>
diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h
index ac9c5a5..e911734 100644
--- a/include/input/InputDevice.h
+++ b/include/input/InputDevice.h
@@ -58,6 +58,9 @@
// reuse values that are not associated with an input anymore.
uint16_t nonce;
+ // The bluetooth address of the device, if known.
+ std::optional<std::string> bluetoothAddress;
+
/**
* Return InputDeviceIdentifier.name that has been adjusted as follows:
* - all characters besides alphanumerics, dash,
diff --git a/include/input/PrintTools.h b/include/input/PrintTools.h
index 55f730b..e24344b 100644
--- a/include/input/PrintTools.h
+++ b/include/input/PrintTools.h
@@ -24,16 +24,20 @@
namespace android {
template <typename T>
-std::string constToString(const T& v) {
+inline std::string constToString(const T& v) {
return std::to_string(v);
}
+inline std::string constToString(const std::string& s) {
+ return s;
+}
+
/**
* Convert an optional type to string.
*/
template <typename T>
-std::string toString(const std::optional<T>& optional,
- std::string (*toString)(const T&) = constToString) {
+inline std::string toString(const std::optional<T>& optional,
+ std::string (*toString)(const T&) = constToString) {
return optional ? toString(*optional) : "<not set>";
}
diff --git a/libs/arect/Android.bp b/libs/arect/Android.bp
index 76e3e66..5e539f2 100644
--- a/libs/arect/Android.bp
+++ b/libs/arect/Android.bp
@@ -49,6 +49,9 @@
"com.android.media",
"com.android.media.swcodec",
],
+ llndk: {
+ llndk_headers: true,
+ },
}
cc_library_static {
diff --git a/libs/binder/IServiceManager.cpp b/libs/binder/IServiceManager.cpp
index 05db774..a0c4334 100644
--- a/libs/binder/IServiceManager.cpp
+++ b/libs/binder/IServiceManager.cpp
@@ -81,6 +81,7 @@
bool isDeclared(const String16& name) override;
Vector<String16> getDeclaredInstances(const String16& interface) override;
std::optional<String16> updatableViaApex(const String16& name) override;
+ Vector<String16> getUpdatableNames(const String16& apexName) override;
std::optional<IServiceManager::ConnectionInfo> getConnectionInfo(const String16& name) override;
class RegistrationWaiter : public android::os::BnServiceCallback {
public:
@@ -479,6 +480,23 @@
return declared ? std::optional<String16>(String16(declared.value().c_str())) : std::nullopt;
}
+Vector<String16> ServiceManagerShim::getUpdatableNames(const String16& apexName) {
+ std::vector<std::string> out;
+ if (Status status = mTheRealServiceManager->getUpdatableNames(String8(apexName).c_str(), &out);
+ !status.isOk()) {
+ ALOGW("Failed to getUpdatableNames for %s: %s", String8(apexName).c_str(),
+ status.toString8().c_str());
+ return {};
+ }
+
+ Vector<String16> res;
+ res.setCapacity(out.size());
+ for (const std::string& instance : out) {
+ res.push(String16(instance.c_str()));
+ }
+ return res;
+}
+
std::optional<IServiceManager::ConnectionInfo> ServiceManagerShim::getConnectionInfo(
const String16& name) {
std::optional<os::ConnectionInfo> connectionInfo;
diff --git a/libs/binder/OS.cpp b/libs/binder/OS.cpp
index 77e401f..ce60e33 100644
--- a/libs/binder/OS.cpp
+++ b/libs/binder/OS.cpp
@@ -67,7 +67,7 @@
return RpcTransportCtxFactoryRaw::make();
}
-int sendMessageOnSocket(
+ssize_t sendMessageOnSocket(
const RpcTransportFd& socket, iovec* iovs, int niovs,
const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) {
if (ancillaryFds != nullptr && !ancillaryFds->empty()) {
@@ -112,7 +112,7 @@
return TEMP_FAILURE_RETRY(sendmsg(socket.fd.get(), &msg, MSG_NOSIGNAL));
}
-int receiveMessageFromSocket(
+ssize_t receiveMessageFromSocket(
const RpcTransportFd& socket, iovec* iovs, int niovs,
std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) {
if (ancillaryFds != nullptr) {
diff --git a/libs/binder/OS.h b/libs/binder/OS.h
index 0d38968..fecae31 100644
--- a/libs/binder/OS.h
+++ b/libs/binder/OS.h
@@ -33,11 +33,11 @@
std::unique_ptr<RpcTransportCtxFactory> makeDefaultRpcTransportCtxFactory();
-int sendMessageOnSocket(
+ssize_t sendMessageOnSocket(
const RpcTransportFd& socket, iovec* iovs, int niovs,
const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds);
-int receiveMessageFromSocket(
+ssize_t receiveMessageFromSocket(
const RpcTransportFd& socket, iovec* iovs, int niovs,
std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds);
diff --git a/libs/binder/RpcSession.cpp b/libs/binder/RpcSession.cpp
index 0059171..ce6ef2b 100644
--- a/libs/binder/RpcSession.cpp
+++ b/libs/binder/RpcSession.cpp
@@ -408,10 +408,11 @@
"Unable to detach thread. No JavaVM, but it was present before!");
LOG_RPC_DETAIL("Detaching current thread from JVM");
- if (vm->DetachCurrentThread() != JNI_OK) {
+ int ret = vm->DetachCurrentThread();
+ if (ret == JNI_OK) {
mAttached = false;
} else {
- ALOGW("Unable to detach current thread from JVM");
+ ALOGW("Unable to detach current thread from JVM (%d)", ret);
}
}
diff --git a/libs/binder/RpcTransportRaw.cpp b/libs/binder/RpcTransportRaw.cpp
index 1912d14..cd067bf 100644
--- a/libs/binder/RpcTransportRaw.cpp
+++ b/libs/binder/RpcTransportRaw.cpp
@@ -61,7 +61,8 @@
override {
bool sentFds = false;
auto send = [&](iovec* iovs, int niovs) -> ssize_t {
- int ret = sendMessageOnSocket(mSocket, iovs, niovs, sentFds ? nullptr : ancillaryFds);
+ ssize_t ret =
+ sendMessageOnSocket(mSocket, iovs, niovs, sentFds ? nullptr : ancillaryFds);
sentFds |= ret > 0;
return ret;
};
diff --git a/libs/binder/aidl/android/os/IServiceManager.aidl b/libs/binder/aidl/android/os/IServiceManager.aidl
index 5880c0a..0fb1615 100644
--- a/libs/binder/aidl/android/os/IServiceManager.aidl
+++ b/libs/binder/aidl/android/os/IServiceManager.aidl
@@ -114,6 +114,12 @@
@nullable @utf8InCpp String updatableViaApex(@utf8InCpp String name);
/**
+ * Returns all instances which are updatable via the APEX. Instance names are fully qualified
+ * like `pack.age.IFoo/default`.
+ */
+ @utf8InCpp String[] getUpdatableNames(@utf8InCpp String apexName);
+
+ /**
* If connection info is available for the given instance, returns the ConnectionInfo
*/
@nullable ConnectionInfo getConnectionInfo(@utf8InCpp String name);
diff --git a/libs/binder/include/binder/IServiceManager.h b/libs/binder/include/binder/IServiceManager.h
index 413c97f..79e771f 100644
--- a/libs/binder/include/binder/IServiceManager.h
+++ b/libs/binder/include/binder/IServiceManager.h
@@ -115,6 +115,12 @@
virtual std::optional<String16> updatableViaApex(const String16& name) = 0;
/**
+ * Returns all instances which are updatable via the APEX. Instance names are fully qualified
+ * like `pack.age.IFoo/default`.
+ */
+ virtual Vector<String16> getUpdatableNames(const String16& apexName) = 0;
+
+ /**
* If this instance has declared remote connection information, returns
* the ConnectionInfo.
*/
diff --git a/libs/binder/ndk/include_cpp/android/binder_interface_utils.h b/libs/binder/ndk/include_cpp/android/binder_interface_utils.h
index 78bcb43..81975e7 100644
--- a/libs/binder/ndk/include_cpp/android/binder_interface_utils.h
+++ b/libs/binder/ndk/include_cpp/android/binder_interface_utils.h
@@ -291,7 +291,10 @@
binder_status_t ICInterface::ICInterfaceData::onDump(AIBinder* binder, int fd, const char** args,
uint32_t numArgs) {
std::shared_ptr<ICInterface> interface = getInterface(binder);
- return interface->dump(fd, args, numArgs);
+ if (interface != nullptr) {
+ return interface->dump(fd, args, numArgs);
+ }
+ return STATUS_DEAD_OBJECT;
}
#ifdef HAS_BINDER_SHELL_COMMAND
@@ -299,7 +302,10 @@
int err, const char** argv,
uint32_t argc) {
std::shared_ptr<ICInterface> interface = getInterface(binder);
- return interface->handleShellCommand(in, out, err, argv, argc);
+ if (interface != nullptr) {
+ return interface->handleShellCommand(in, out, err, argv, argc);
+ }
+ return STATUS_DEAD_OBJECT;
}
#endif
diff --git a/libs/binder/servicedispatcher.cpp b/libs/binder/servicedispatcher.cpp
index 777f3c9..692cc95 100644
--- a/libs/binder/servicedispatcher.cpp
+++ b/libs/binder/servicedispatcher.cpp
@@ -156,6 +156,10 @@
std::optional<std::string>* _aidl_return) override {
return mImpl->updatableViaApex(name, _aidl_return);
}
+ android::binder::Status getUpdatableNames(const std::string& apexName,
+ std::vector<std::string>* _aidl_return) override {
+ return mImpl->getUpdatableNames(apexName, _aidl_return);
+ }
android::binder::Status getConnectionInfo(
const std::string& name,
std::optional<android::os::ConnectionInfo>* _aidl_return) override {
diff --git a/libs/binder/trusty/OS.cpp b/libs/binder/trusty/OS.cpp
index 397ff41..8ec9823 100644
--- a/libs/binder/trusty/OS.cpp
+++ b/libs/binder/trusty/OS.cpp
@@ -59,14 +59,14 @@
return RpcTransportCtxFactoryTipcTrusty::make();
}
-int sendMessageOnSocket(
+ssize_t sendMessageOnSocket(
const RpcTransportFd& /* socket */, iovec* /* iovs */, int /* niovs */,
const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* /* ancillaryFds */) {
errno = ENOTSUP;
return -1;
}
-int receiveMessageFromSocket(
+ssize_t receiveMessageFromSocket(
const RpcTransportFd& /* socket */, iovec* /* iovs */, int /* niovs */,
std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* /* ancillaryFds */) {
errno = ENOTSUP;
diff --git a/libs/fakeservicemanager/ServiceManager.cpp b/libs/fakeservicemanager/ServiceManager.cpp
index 6c6d7f3..480ec79 100644
--- a/libs/fakeservicemanager/ServiceManager.cpp
+++ b/libs/fakeservicemanager/ServiceManager.cpp
@@ -78,6 +78,11 @@
return std::nullopt;
}
+Vector<String16> ServiceManager::getUpdatableNames(const String16& apexName) {
+ (void)apexName;
+ return {};
+}
+
std::optional<IServiceManager::ConnectionInfo> ServiceManager::getConnectionInfo(
const String16& name) {
(void)name;
diff --git a/libs/fakeservicemanager/include/fakeservicemanager/ServiceManager.h b/libs/fakeservicemanager/include/fakeservicemanager/ServiceManager.h
index e0af5d4..ee0637e 100644
--- a/libs/fakeservicemanager/include/fakeservicemanager/ServiceManager.h
+++ b/libs/fakeservicemanager/include/fakeservicemanager/ServiceManager.h
@@ -52,6 +52,8 @@
std::optional<String16> updatableViaApex(const String16& name) override;
+ Vector<String16> getUpdatableNames(const String16& apexName) override;
+
std::optional<IServiceManager::ConnectionInfo> getConnectionInfo(const String16& name) override;
status_t registerForNotifications(const String16& name,
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index a6f6b14..c1eb8e2 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -35,6 +35,9 @@
#ifdef __linux__
#include <binder/Parcel.h>
#endif
+#if defined(__ANDROID__)
+#include <sys/random.h>
+#endif
using android::base::StringPrintf;
@@ -110,8 +113,11 @@
}
// --- IdGenerator ---
-
-static status_t getRandomBytes(uint8_t* data, size_t size) {
+#if defined(__ANDROID__)
+[[maybe_unused]]
+#endif
+static status_t
+getRandomBytes(uint8_t* data, size_t size) {
int ret = TEMP_FAILURE_RETRY(open("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOFOLLOW));
if (ret == -1) {
return -errno;
@@ -130,7 +136,22 @@
constexpr uint32_t SEQUENCE_NUMBER_MASK = ~SOURCE_MASK;
int32_t id = 0;
+#if defined(__ANDROID__)
+ // On device, prefer 'getrandom' to '/dev/urandom' because it's faster.
+ constexpr size_t BUF_LEN = sizeof(id);
+ size_t totalBytes = 0;
+ while (totalBytes < BUF_LEN) {
+ ssize_t bytes = TEMP_FAILURE_RETRY(getrandom(&id, BUF_LEN, GRND_NONBLOCK));
+ if (CC_UNLIKELY(bytes < 0)) {
+ ALOGW("Failed to fill in random number for sequence number: %s.", strerror(errno));
+ id = 0;
+ break;
+ }
+ totalBytes += bytes;
+ }
+#else
#if defined(__linux__)
+ // On host, <sys/random.h> / GRND_NONBLOCK is not available
while (true) {
status_t result = getRandomBytes(reinterpret_cast<uint8_t*>(&id), sizeof(id));
if (result == OK) {
@@ -138,6 +159,7 @@
}
}
#endif // __linux__
+#endif // __ANDROID__
return (id & SEQUENCE_NUMBER_MASK) | static_cast<int32_t>(mSource);
}
diff --git a/libs/jpegrecoverymap/Android.bp b/libs/jpegrecoverymap/Android.bp
index b52c4c7..3ab2ba8 100644
--- a/libs/jpegrecoverymap/Android.bp
+++ b/libs/jpegrecoverymap/Android.bp
@@ -35,4 +35,32 @@
shared_libs: [
"libutils",
],
+}
+
+cc_library_static {
+ name: "libjpegencoder",
+
+ shared_libs: [
+ "libjpeg",
+ ],
+
+ export_include_dirs: ["include"],
+
+ srcs: [
+ "jpegencoder.cpp",
+ ],
+}
+
+cc_library_static {
+ name: "libjpegdecoder",
+
+ shared_libs: [
+ "libjpeg",
+ ],
+
+ export_include_dirs: ["include"],
+
+ srcs: [
+ "jpegdecoder.cpp",
+ ],
}
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
new file mode 100644
index 0000000..2ab7550
--- /dev/null
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
@@ -0,0 +1,62 @@
+
+/*
+ * 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.
+ */
+// We must include cstdio before jpeglib.h. It is a requirement of libjpeg.
+#include <cstdio>
+extern "C" {
+#include <jerror.h>
+#include <jpeglib.h>
+}
+#include <utils/Errors.h>
+#include <vector>
+namespace android::recoverymap {
+/*
+ * Encapsulates a converter from JPEG to raw image (YUV420planer or grey-scale) format.
+ * This class is not thread-safe.
+ */
+class JpegDecoder {
+public:
+ JpegDecoder();
+ ~JpegDecoder();
+ /*
+ * Decompresses JPEG image to raw image (YUV420planer or grey-scale) format. After calling
+ * this method, call getDecompressedImage() to get the image.
+ * Returns false if decompressing the image fails.
+ */
+ bool decompressImage(const void* image, int length);
+ /*
+ * Returns the decompressed raw image buffer pointer. This method must be called only after
+ * calling decompressImage().
+ */
+ const void* getDecompressedImagePtr();
+ /*
+ * Returns the decompressed raw image buffer size. This method must be called only after
+ * calling decompressImage().
+ */
+ size_t getDecompressedImageSize();
+private:
+ bool decode(const void* image, int length);
+ // Returns false if errors occur.
+ bool decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest, bool isSingleChannel);
+ bool decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest);
+ bool decompressSingleChannel(jpeg_decompress_struct* cinfo, const uint8_t* dest);
+ // Process 16 lines of Y and 16 lines of U/V each time.
+ // We must pass at least 16 scanlines according to libjpeg documentation.
+ static const int kCompressBatchSize = 16;
+ // The buffer that holds the compressed result.
+ std::vector<JOCTET> mResultBuffer;
+};
+} /* namespace android */
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h
new file mode 100644
index 0000000..9641fda
--- /dev/null
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+
+// We must include cstdio before jpeglib.h. It is a requirement of libjpeg.
+#include <cstdio>
+
+extern "C" {
+#include <jerror.h>
+#include <jpeglib.h>
+}
+
+#include <utils/Errors.h>
+#include <vector>
+
+namespace android::recoverymap {
+
+/*
+ * Encapsulates a converter from raw image (YUV420planer or grey-scale) to JPEG format.
+ * This class is not thread-safe.
+ */
+class JpegEncoder {
+public:
+ JpegEncoder();
+ ~JpegEncoder();
+
+ /*
+ * Compresses YUV420Planer image to JPEG format. After calling this method, call
+ * getCompressedImage() to get the image. |quality| is the jpeg image quality parameter to use.
+ * It ranges from 1 (poorest quality) to 100 (highest quality). |iccBuffer| is the buffer of
+ * ICC segment which will be added to the compressed image.
+ * Returns false if errors occur during compression.
+ */
+ bool compressImage(const void* image, int width, int height, int quality,
+ const void* iccBuffer, unsigned int iccSize, bool isSingleChannel = false);
+
+ /*
+ * Returns the compressed JPEG buffer pointer. This method must be called only after calling
+ * compressImage().
+ */
+ const void* getCompressedImagePtr();
+
+ /*
+ * Returns the compressed JPEG buffer size. This method must be called only after calling
+ * compressImage().
+ */
+ size_t getCompressedImageSize();
+
+private:
+ // initDestination(), emptyOutputBuffer() and emptyOutputBuffer() are callback functions to be
+ // passed into jpeg library.
+ static void initDestination(j_compress_ptr cinfo);
+ static boolean emptyOutputBuffer(j_compress_ptr cinfo);
+ static void terminateDestination(j_compress_ptr cinfo);
+ static void outputErrorMessage(j_common_ptr cinfo);
+
+ // Returns false if errors occur.
+ bool encode(const void* inYuv, int width, int height, int jpegQuality,
+ const void* iccBuffer, unsigned int iccSize, bool isSingleChannel);
+ void setJpegDestination(jpeg_compress_struct* cinfo);
+ void setJpegCompressStruct(int width, int height, int quality, jpeg_compress_struct* cinfo,
+ bool isSingleChannel);
+ // Returns false if errors occur.
+ bool compress(jpeg_compress_struct* cinfo, const uint8_t* image, bool isSingleChannel);
+ bool compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yuv);
+ bool compressSingleChannel(jpeg_compress_struct* cinfo, const uint8_t* image);
+
+ // The block size for encoded jpeg image buffer.
+ static const int kBlockSize = 16384;
+ // Process 16 lines of Y and 16 lines of U/V each time.
+ // We must pass at least 16 scanlines according to libjpeg documentation.
+ static const int kCompressBatchSize = 16;
+
+ // The buffer that holds the compressed result.
+ std::vector<JOCTET> mResultBuffer;
+};
+
+} /* namespace android */
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/jpegdecoder.cpp b/libs/jpegrecoverymap/jpegdecoder.cpp
new file mode 100644
index 0000000..22a5389
--- /dev/null
+++ b/libs/jpegrecoverymap/jpegdecoder.cpp
@@ -0,0 +1,225 @@
+/*
+ * 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 <jpegrecoverymap/jpegdecoder.h>
+
+#include <cutils/log.h>
+
+#include <errno.h>
+#include <setjmp.h>
+
+namespace android::recoverymap {
+struct jpegr_source_mgr : jpeg_source_mgr {
+ jpegr_source_mgr(const uint8_t* ptr, int len);
+ ~jpegr_source_mgr();
+
+ const uint8_t* mBufferPtr;
+ size_t mBufferLength;
+};
+
+struct jpegrerror_mgr {
+ struct jpeg_error_mgr pub;
+ jmp_buf setjmp_buffer;
+};
+
+static void jpegr_init_source(j_decompress_ptr cinfo) {
+ jpegr_source_mgr* src = static_cast<jpegr_source_mgr*>(cinfo->src);
+ src->next_input_byte = static_cast<const JOCTET*>(src->mBufferPtr);
+ src->bytes_in_buffer = src->mBufferLength;
+}
+
+static boolean jpegr_fill_input_buffer(j_decompress_ptr /* cinfo */) {
+ ALOGE("%s : should not get here", __func__);
+ return FALSE;
+}
+
+static void jpegr_skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
+ jpegr_source_mgr* src = static_cast<jpegr_source_mgr*>(cinfo->src);
+
+ if (num_bytes > static_cast<long>(src->bytes_in_buffer)) {
+ ALOGE("jpegr_skip_input_data - num_bytes > (long)src->bytes_in_buffer");
+ } else {
+ src->next_input_byte += num_bytes;
+ src->bytes_in_buffer -= num_bytes;
+ }
+}
+
+static void jpegr_term_source(j_decompress_ptr /*cinfo*/) {}
+
+jpegr_source_mgr::jpegr_source_mgr(const uint8_t* ptr, int len) :
+ mBufferPtr(ptr), mBufferLength(len) {
+ init_source = jpegr_init_source;
+ fill_input_buffer = jpegr_fill_input_buffer;
+ skip_input_data = jpegr_skip_input_data;
+ resync_to_restart = jpeg_resync_to_restart;
+ term_source = jpegr_term_source;
+}
+
+jpegr_source_mgr::~jpegr_source_mgr() {}
+
+static void jpegrerror_exit(j_common_ptr cinfo) {
+ jpegrerror_mgr* err = reinterpret_cast<jpegrerror_mgr*>(cinfo->err);
+ longjmp(err->setjmp_buffer, 1);
+}
+
+JpegDecoder::JpegDecoder() {
+}
+
+JpegDecoder::~JpegDecoder() {
+}
+
+bool JpegDecoder::decompressImage(const void* image, int length) {
+ if (image == nullptr || length <= 0) {
+ ALOGE("Image size can not be handled: %d", length);
+ return false;
+ }
+
+ mResultBuffer.clear();
+ if (!decode(image, length)) {
+ return false;
+ }
+
+ return true;
+}
+
+const void* JpegDecoder::getDecompressedImagePtr() {
+ return mResultBuffer.data();
+}
+
+size_t JpegDecoder::getDecompressedImageSize() {
+ return mResultBuffer.size();
+}
+
+bool JpegDecoder::decode(const void* image, int length) {
+ jpeg_decompress_struct cinfo;
+ jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
+ jpegrerror_mgr myerr;
+ cinfo.err = jpeg_std_error(&myerr.pub);
+ myerr.pub.error_exit = jpegrerror_exit;
+
+ if (setjmp(myerr.setjmp_buffer)) {
+ jpeg_destroy_decompress(&cinfo);
+ return false;
+ }
+ jpeg_create_decompress(&cinfo);
+
+ cinfo.src = &mgr;
+ jpeg_read_header(&cinfo, TRUE);
+
+ if (cinfo.jpeg_color_space == JCS_YCbCr) {
+ mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 3 / 2, 0);
+ } else if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
+ mResultBuffer.resize(cinfo.image_width * cinfo.image_height, 0);
+ }
+
+ cinfo.raw_data_out = TRUE;
+ cinfo.dct_method = JDCT_IFAST;
+ cinfo.out_color_space = cinfo.jpeg_color_space;
+
+ jpeg_start_decompress(&cinfo);
+
+ if (!decompress(&cinfo, static_cast<const uint8_t*>(mResultBuffer.data()),
+ cinfo.jpeg_color_space == JCS_GRAYSCALE)) {
+ return false;
+ }
+
+ jpeg_finish_decompress(&cinfo);
+ jpeg_destroy_decompress(&cinfo);
+
+ return true;
+}
+
+bool JpegDecoder::decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest,
+ bool isSingleChannel) {
+ if (isSingleChannel) {
+ return decompressSingleChannel(cinfo, dest);
+ }
+ return decompressYUV(cinfo, dest);
+}
+
+bool JpegDecoder::decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
+
+ JSAMPROW y[kCompressBatchSize];
+ JSAMPROW cb[kCompressBatchSize / 2];
+ JSAMPROW cr[kCompressBatchSize / 2];
+ JSAMPARRAY planes[3] {y, cb, cr};
+
+ size_t y_plane_size = cinfo->image_width * cinfo->image_height;
+ size_t uv_plane_size = y_plane_size / 4;
+ uint8_t* y_plane = const_cast<uint8_t*>(dest);
+ uint8_t* u_plane = const_cast<uint8_t*>(dest + y_plane_size);
+ uint8_t* v_plane = const_cast<uint8_t*>(dest + y_plane_size + uv_plane_size);
+ std::unique_ptr<uint8_t[]> empty(new uint8_t[cinfo->image_width]);
+ memset(empty.get(), 0, cinfo->image_width);
+
+ while (cinfo->output_scanline < cinfo->image_height) {
+ for (int i = 0; i < kCompressBatchSize; ++i) {
+ size_t scanline = cinfo->output_scanline + i;
+ if (scanline < cinfo->image_height) {
+ y[i] = y_plane + scanline * cinfo->image_width;
+ } else {
+ y[i] = empty.get();
+ }
+ }
+ // cb, cr only have half scanlines
+ for (int i = 0; i < kCompressBatchSize / 2; ++i) {
+ size_t scanline = cinfo->output_scanline / 2 + i;
+ if (scanline < cinfo->image_height / 2) {
+ int offset = scanline * (cinfo->image_width / 2);
+ cb[i] = u_plane + offset;
+ cr[i] = v_plane + offset;
+ } else {
+ cb[i] = cr[i] = empty.get();
+ }
+ }
+
+ int processed = jpeg_read_raw_data(cinfo, planes, kCompressBatchSize);
+ if (processed != kCompressBatchSize) {
+ ALOGE("Number of processed lines does not equal input lines.");
+ return false;
+ }
+ }
+ return true;
+}
+
+bool JpegDecoder::decompressSingleChannel(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
+ JSAMPROW y[kCompressBatchSize];
+ JSAMPARRAY planes[1] {y};
+
+ uint8_t* y_plane = const_cast<uint8_t*>(dest);
+ std::unique_ptr<uint8_t[]> empty(new uint8_t[cinfo->image_width]);
+ memset(empty.get(), 0, cinfo->image_width);
+
+ while (cinfo->output_scanline < cinfo->image_height) {
+ for (int i = 0; i < kCompressBatchSize; ++i) {
+ size_t scanline = cinfo->output_scanline + i;
+ if (scanline < cinfo->image_height) {
+ y[i] = y_plane + scanline * cinfo->image_width;
+ } else {
+ y[i] = empty.get();
+ }
+ }
+
+ int processed = jpeg_read_raw_data(cinfo, planes, kCompressBatchSize);
+ if (processed != kCompressBatchSize / 2) {
+ ALOGE("Number of processed lines does not equal input lines.");
+ return false;
+ }
+ }
+ return true;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/jpegencoder.cpp b/libs/jpegrecoverymap/jpegencoder.cpp
new file mode 100644
index 0000000..d45d9b3
--- /dev/null
+++ b/libs/jpegrecoverymap/jpegencoder.cpp
@@ -0,0 +1,239 @@
+/*
+ * 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 <jpegrecoverymap/jpegencoder.h>
+
+#include <cutils/log.h>
+
+#include <errno.h>
+
+namespace android::recoverymap {
+
+// The destination manager that can access |mResultBuffer| in JpegEncoder.
+struct destination_mgr {
+public:
+ struct jpeg_destination_mgr mgr;
+ JpegEncoder* encoder;
+};
+
+JpegEncoder::JpegEncoder() {
+}
+
+JpegEncoder::~JpegEncoder() {
+}
+
+bool JpegEncoder::compressImage(const void* image, int width, int height, int quality,
+ const void* iccBuffer, unsigned int iccSize,
+ bool isSingleChannel) {
+ if (width % 8 != 0 || height % 2 != 0) {
+ ALOGE("Image size can not be handled: %dx%d", width, height);
+ return false;
+ }
+
+ mResultBuffer.clear();
+ if (!encode(image, width, height, quality, iccBuffer, iccSize, isSingleChannel)) {
+ return false;
+ }
+ ALOGI("Compressed JPEG: %d[%dx%d] -> %zu bytes",
+ (width * height * 12) / 8, width, height, mResultBuffer.size());
+ return true;
+}
+
+const void* JpegEncoder::getCompressedImagePtr() {
+ return mResultBuffer.data();
+}
+
+size_t JpegEncoder::getCompressedImageSize() {
+ return mResultBuffer.size();
+}
+
+void JpegEncoder::initDestination(j_compress_ptr cinfo) {
+ destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
+ std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer;
+ buffer.resize(kBlockSize);
+ dest->mgr.next_output_byte = &buffer[0];
+ dest->mgr.free_in_buffer = buffer.size();
+}
+
+boolean JpegEncoder::emptyOutputBuffer(j_compress_ptr cinfo) {
+ destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
+ std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer;
+ size_t oldsize = buffer.size();
+ buffer.resize(oldsize + kBlockSize);
+ dest->mgr.next_output_byte = &buffer[oldsize];
+ dest->mgr.free_in_buffer = kBlockSize;
+ return true;
+}
+
+void JpegEncoder::terminateDestination(j_compress_ptr cinfo) {
+ destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
+ std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer;
+ buffer.resize(buffer.size() - dest->mgr.free_in_buffer);
+}
+
+void JpegEncoder::outputErrorMessage(j_common_ptr cinfo) {
+ char buffer[JMSG_LENGTH_MAX];
+
+ /* Create the message */
+ (*cinfo->err->format_message) (cinfo, buffer);
+ ALOGE("%s\n", buffer);
+}
+
+bool JpegEncoder::encode(const void* image, int width, int height, int jpegQuality,
+ const void* iccBuffer, unsigned int iccSize, bool isSingleChannel) {
+ jpeg_compress_struct cinfo;
+ jpeg_error_mgr jerr;
+
+ cinfo.err = jpeg_std_error(&jerr);
+ // Override output_message() to print error log with ALOGE().
+ cinfo.err->output_message = &outputErrorMessage;
+ jpeg_create_compress(&cinfo);
+ setJpegDestination(&cinfo);
+
+ setJpegCompressStruct(width, height, jpegQuality, &cinfo, isSingleChannel);
+ jpeg_start_compress(&cinfo, TRUE);
+
+ if (iccBuffer != nullptr && iccSize > 0) {
+ jpeg_write_marker(&cinfo, JPEG_APP0 + 2, static_cast<const JOCTET*>(iccBuffer), iccSize);
+ }
+
+ if (!compress(&cinfo, static_cast<const uint8_t*>(image), isSingleChannel)) {
+ return false;
+ }
+ jpeg_finish_compress(&cinfo);
+ jpeg_destroy_compress(&cinfo);
+ return true;
+}
+
+void JpegEncoder::setJpegDestination(jpeg_compress_struct* cinfo) {
+ destination_mgr* dest = static_cast<struct destination_mgr *>((*cinfo->mem->alloc_small) (
+ (j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(destination_mgr)));
+ dest->encoder = this;
+ dest->mgr.init_destination = &initDestination;
+ dest->mgr.empty_output_buffer = &emptyOutputBuffer;
+ dest->mgr.term_destination = &terminateDestination;
+ cinfo->dest = reinterpret_cast<struct jpeg_destination_mgr*>(dest);
+}
+
+void JpegEncoder::setJpegCompressStruct(int width, int height, int quality,
+ jpeg_compress_struct* cinfo, bool isSingleChannel) {
+ cinfo->image_width = width;
+ cinfo->image_height = height;
+ if (isSingleChannel) {
+ cinfo->input_components = 1;
+ cinfo->in_color_space = JCS_GRAYSCALE;
+ } else {
+ cinfo->input_components = 3;
+ cinfo->in_color_space = JCS_YCbCr;
+ }
+ jpeg_set_defaults(cinfo);
+
+ jpeg_set_quality(cinfo, quality, TRUE);
+ jpeg_set_colorspace(cinfo, isSingleChannel ? JCS_GRAYSCALE : JCS_YCbCr);
+ cinfo->raw_data_in = TRUE;
+ cinfo->dct_method = JDCT_IFAST;
+
+ if (!isSingleChannel) {
+ // Configure sampling factors. The sampling factor is JPEG subsampling 420 because the
+ // source format is YUV420.
+ cinfo->comp_info[0].h_samp_factor = 2;
+ cinfo->comp_info[0].v_samp_factor = 2;
+ cinfo->comp_info[1].h_samp_factor = 1;
+ cinfo->comp_info[1].v_samp_factor = 1;
+ cinfo->comp_info[2].h_samp_factor = 1;
+ cinfo->comp_info[2].v_samp_factor = 1;
+ }
+}
+
+bool JpegEncoder::compress(
+ jpeg_compress_struct* cinfo, const uint8_t* image, bool isSingleChannel) {
+ if (isSingleChannel) {
+ return compressSingleChannel(cinfo, image);
+ }
+ return compressYuv(cinfo, image);
+}
+
+bool JpegEncoder::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yuv) {
+ JSAMPROW y[kCompressBatchSize];
+ JSAMPROW cb[kCompressBatchSize / 2];
+ JSAMPROW cr[kCompressBatchSize / 2];
+ JSAMPARRAY planes[3] {y, cb, cr};
+
+ size_t y_plane_size = cinfo->image_width * cinfo->image_height;
+ size_t uv_plane_size = y_plane_size / 4;
+ uint8_t* y_plane = const_cast<uint8_t*>(yuv);
+ uint8_t* u_plane = const_cast<uint8_t*>(yuv + y_plane_size);
+ uint8_t* v_plane = const_cast<uint8_t*>(yuv + y_plane_size + uv_plane_size);
+ std::unique_ptr<uint8_t[]> empty(new uint8_t[cinfo->image_width]);
+ memset(empty.get(), 0, cinfo->image_width);
+
+ while (cinfo->next_scanline < cinfo->image_height) {
+ for (int i = 0; i < kCompressBatchSize; ++i) {
+ size_t scanline = cinfo->next_scanline + i;
+ if (scanline < cinfo->image_height) {
+ y[i] = y_plane + scanline * cinfo->image_width;
+ } else {
+ y[i] = empty.get();
+ }
+ }
+ // cb, cr only have half scanlines
+ for (int i = 0; i < kCompressBatchSize / 2; ++i) {
+ size_t scanline = cinfo->next_scanline / 2 + i;
+ if (scanline < cinfo->image_height / 2) {
+ int offset = scanline * (cinfo->image_width / 2);
+ cb[i] = u_plane + offset;
+ cr[i] = v_plane + offset;
+ } else {
+ cb[i] = cr[i] = empty.get();
+ }
+ }
+
+ int processed = jpeg_write_raw_data(cinfo, planes, kCompressBatchSize);
+ if (processed != kCompressBatchSize) {
+ ALOGE("Number of processed lines does not equal input lines.");
+ return false;
+ }
+ }
+ return true;
+}
+
+bool JpegEncoder::compressSingleChannel(jpeg_compress_struct* cinfo, const uint8_t* image) {
+ JSAMPROW y[kCompressBatchSize];
+ JSAMPARRAY planes[1] {y};
+
+ uint8_t* y_plane = const_cast<uint8_t*>(image);
+ std::unique_ptr<uint8_t[]> empty(new uint8_t[cinfo->image_width]);
+ memset(empty.get(), 0, cinfo->image_width);
+
+ while (cinfo->next_scanline < cinfo->image_height) {
+ for (int i = 0; i < kCompressBatchSize; ++i) {
+ size_t scanline = cinfo->next_scanline + i;
+ if (scanline < cinfo->image_height) {
+ y[i] = y_plane + scanline * cinfo->image_width;
+ } else {
+ y[i] = empty.get();
+ }
+ }
+ int processed = jpeg_write_raw_data(cinfo, planes, kCompressBatchSize);
+ if (processed != kCompressBatchSize / 2) {
+ ALOGE("Number of processed lines does not equal input lines.");
+ return false;
+ }
+ }
+ return true;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/tests/Android.bp b/libs/jpegrecoverymap/tests/Android.bp
index 79bf723..7f37f61 100644
--- a/libs/jpegrecoverymap/tests/Android.bp
+++ b/libs/jpegrecoverymap/tests/Android.bp
@@ -30,4 +30,36 @@
static_libs: [
"libjpegrecoverymap",
],
+}
+
+cc_test {
+ name: "libjpegencoder_test",
+ test_suites: ["device-tests"],
+ srcs: [
+ "jpegencoder_test.cpp",
+ ],
+ shared_libs: [
+ "libjpeg",
+ "liblog",
+ ],
+ static_libs: [
+ "libjpegencoder",
+ "libgtest",
+ ],
+}
+
+cc_test {
+ name: "libjpegdecoder_test",
+ test_suites: ["device-tests"],
+ srcs: [
+ "jpegdecoder_test.cpp",
+ ],
+ shared_libs: [
+ "libjpeg",
+ "liblog",
+ ],
+ static_libs: [
+ "libjpegdecoder",
+ "libgtest",
+ ],
}
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/tests/data/minnie-318x240.yu12 b/libs/jpegrecoverymap/tests/data/minnie-318x240.yu12
new file mode 100644
index 0000000..7b2fc71
--- /dev/null
+++ b/libs/jpegrecoverymap/tests/data/minnie-318x240.yu12
Binary files differ
diff --git a/libs/jpegrecoverymap/tests/data/minnie-320x240-y.jpg b/libs/jpegrecoverymap/tests/data/minnie-320x240-y.jpg
new file mode 100644
index 0000000..20b5a2c
--- /dev/null
+++ b/libs/jpegrecoverymap/tests/data/minnie-320x240-y.jpg
Binary files differ
diff --git a/libs/jpegrecoverymap/tests/data/minnie-320x240-yuv.jpg b/libs/jpegrecoverymap/tests/data/minnie-320x240-yuv.jpg
new file mode 100644
index 0000000..41300f4
--- /dev/null
+++ b/libs/jpegrecoverymap/tests/data/minnie-320x240-yuv.jpg
Binary files differ
diff --git a/libs/jpegrecoverymap/tests/data/minnie-320x240.y b/libs/jpegrecoverymap/tests/data/minnie-320x240.y
new file mode 100644
index 0000000..f9d8371
--- /dev/null
+++ b/libs/jpegrecoverymap/tests/data/minnie-320x240.y
Binary files differ
diff --git a/libs/jpegrecoverymap/tests/data/minnie-320x240.yu12 b/libs/jpegrecoverymap/tests/data/minnie-320x240.yu12
new file mode 100644
index 0000000..0d66f53
--- /dev/null
+++ b/libs/jpegrecoverymap/tests/data/minnie-320x240.yu12
Binary files differ
diff --git a/libs/jpegrecoverymap/tests/jpegdecoder_test.cpp b/libs/jpegrecoverymap/tests/jpegdecoder_test.cpp
new file mode 100644
index 0000000..8e01351
--- /dev/null
+++ b/libs/jpegrecoverymap/tests/jpegdecoder_test.cpp
@@ -0,0 +1,102 @@
+/*
+ * 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 <jpegrecoverymap/jpegdecoder.h>
+#include <gtest/gtest.h>
+#include <utils/Log.h>
+
+#include <fcntl.h>
+
+namespace android::recoverymap {
+
+#define YUV_IMAGE "/sdcard/Documents/minnie-320x240-yuv.jpg"
+#define YUV_IMAGE_SIZE 20193
+#define GREY_IMAGE "/sdcard/Documents/minnie-320x240-y.jpg"
+#define GREY_IMAGE_SIZE 20193
+
+class JpegDecoderTest : public testing::Test {
+public:
+ struct Image {
+ std::unique_ptr<uint8_t[]> buffer;
+ size_t size;
+ };
+ JpegDecoderTest();
+ ~JpegDecoderTest();
+protected:
+ virtual void SetUp();
+ virtual void TearDown();
+
+ Image mYuvImage, mGreyImage;
+};
+
+JpegDecoderTest::JpegDecoderTest() {}
+
+JpegDecoderTest::~JpegDecoderTest() {}
+
+static size_t getFileSize(int fd) {
+ struct stat st;
+ if (fstat(fd, &st) < 0) {
+ ALOGW("%s : fstat failed", __func__);
+ return 0;
+ }
+ return st.st_size; // bytes
+}
+
+static bool loadFile(const char filename[], JpegDecoderTest::Image* result) {
+ int fd = open(filename, O_CLOEXEC);
+ if (fd < 0) {
+ return false;
+ }
+ int length = getFileSize(fd);
+ if (length == 0) {
+ close(fd);
+ return false;
+ }
+ result->buffer.reset(new uint8_t[length]);
+ if (read(fd, result->buffer.get(), length) != static_cast<ssize_t>(length)) {
+ close(fd);
+ return false;
+ }
+ close(fd);
+ return true;
+}
+
+void JpegDecoderTest::SetUp() {
+ if (!loadFile(YUV_IMAGE, &mYuvImage)) {
+ FAIL() << "Load file " << YUV_IMAGE << " failed";
+ }
+ mYuvImage.size = YUV_IMAGE_SIZE;
+ if (!loadFile(GREY_IMAGE, &mGreyImage)) {
+ FAIL() << "Load file " << GREY_IMAGE << " failed";
+ }
+ mGreyImage.size = GREY_IMAGE_SIZE;
+}
+
+void JpegDecoderTest::TearDown() {}
+
+TEST_F(JpegDecoderTest, decodeYuvImage) {
+ JpegDecoder decoder;
+ EXPECT_TRUE(decoder.decompressImage(mYuvImage.buffer.get(), mYuvImage.size));
+ ASSERT_GT(decoder.getDecompressedImageSize(), static_cast<uint32_t>(0));
+}
+
+TEST_F(JpegDecoderTest, decodeGreyImage) {
+ JpegDecoder decoder;
+ EXPECT_TRUE(decoder.decompressImage(mGreyImage.buffer.get(), mGreyImage.size));
+ ASSERT_GT(decoder.getDecompressedImageSize(), static_cast<uint32_t>(0));
+}
+
+}
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/tests/jpegencoder_test.cpp b/libs/jpegrecoverymap/tests/jpegencoder_test.cpp
new file mode 100644
index 0000000..4cd2a5e
--- /dev/null
+++ b/libs/jpegrecoverymap/tests/jpegencoder_test.cpp
@@ -0,0 +1,125 @@
+/*
+ * 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 <jpegrecoverymap/jpegencoder.h>
+#include <gtest/gtest.h>
+#include <utils/Log.h>
+
+#include <fcntl.h>
+
+namespace android::recoverymap {
+
+#define VALID_IMAGE "/sdcard/Documents/minnie-320x240.yu12"
+#define VALID_IMAGE_WIDTH 320
+#define VALID_IMAGE_HEIGHT 240
+#define SINGLE_CHANNEL_IMAGE "/sdcard/Documents/minnie-320x240.y"
+#define SINGLE_CHANNEL_IMAGE_WIDTH VALID_IMAGE_WIDTH
+#define SINGLE_CHANNEL_IMAGE_HEIGHT VALID_IMAGE_HEIGHT
+#define INVALID_SIZE_IMAGE "/sdcard/Documents/minnie-318x240.yu12"
+#define INVALID_SIZE_IMAGE_WIDTH 318
+#define INVALID_SIZE_IMAGE_HEIGHT 240
+#define JPEG_QUALITY 90
+
+class JpegEncoderTest : public testing::Test {
+public:
+ struct Image {
+ std::unique_ptr<uint8_t[]> buffer;
+ size_t width;
+ size_t height;
+ };
+ JpegEncoderTest();
+ ~JpegEncoderTest();
+protected:
+ virtual void SetUp();
+ virtual void TearDown();
+
+ Image mValidImage, mInvalidSizeImage, mSingleChannelImage;
+};
+
+JpegEncoderTest::JpegEncoderTest() {}
+
+JpegEncoderTest::~JpegEncoderTest() {}
+
+static size_t getFileSize(int fd) {
+ struct stat st;
+ if (fstat(fd, &st) < 0) {
+ ALOGW("%s : fstat failed", __func__);
+ return 0;
+ }
+ return st.st_size; // bytes
+}
+
+static bool loadFile(const char filename[], JpegEncoderTest::Image* result) {
+ int fd = open(filename, O_CLOEXEC);
+ if (fd < 0) {
+ return false;
+ }
+ int length = getFileSize(fd);
+ if (length == 0) {
+ close(fd);
+ return false;
+ }
+ result->buffer.reset(new uint8_t[length]);
+ if (read(fd, result->buffer.get(), length) != static_cast<ssize_t>(length)) {
+ close(fd);
+ return false;
+ }
+ close(fd);
+ return true;
+}
+
+void JpegEncoderTest::SetUp() {
+ if (!loadFile(VALID_IMAGE, &mValidImage)) {
+ FAIL() << "Load file " << VALID_IMAGE << " failed";
+ }
+ mValidImage.width = VALID_IMAGE_WIDTH;
+ mValidImage.height = VALID_IMAGE_HEIGHT;
+ if (!loadFile(INVALID_SIZE_IMAGE, &mInvalidSizeImage)) {
+ FAIL() << "Load file " << INVALID_SIZE_IMAGE << " failed";
+ }
+ mInvalidSizeImage.width = INVALID_SIZE_IMAGE_WIDTH;
+ mInvalidSizeImage.height = INVALID_SIZE_IMAGE_HEIGHT;
+ if (!loadFile(SINGLE_CHANNEL_IMAGE, &mSingleChannelImage)) {
+ FAIL() << "Load file " << SINGLE_CHANNEL_IMAGE << " failed";
+ }
+ mSingleChannelImage.width = SINGLE_CHANNEL_IMAGE_WIDTH;
+ mSingleChannelImage.height = SINGLE_CHANNEL_IMAGE_HEIGHT;
+}
+
+void JpegEncoderTest::TearDown() {}
+
+TEST_F(JpegEncoderTest, validImage) {
+ JpegEncoder encoder;
+ EXPECT_TRUE(encoder.compressImage(mValidImage.buffer.get(), mValidImage.width,
+ mValidImage.height, JPEG_QUALITY, NULL, 0));
+ ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0));
+}
+
+TEST_F(JpegEncoderTest, invalidSizeImage) {
+ JpegEncoder encoder;
+ EXPECT_FALSE(encoder.compressImage(mInvalidSizeImage.buffer.get(), mInvalidSizeImage.width,
+ mInvalidSizeImage.height, JPEG_QUALITY, NULL, 0));
+}
+
+TEST_F(JpegEncoderTest, singleChannelImage) {
+ JpegEncoder encoder;
+ EXPECT_TRUE(encoder.compressImage(mSingleChannelImage.buffer.get(), mSingleChannelImage.width,
+ mSingleChannelImage.height, JPEG_QUALITY, NULL, 0, true));
+ ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0));
+}
+
+}
+
diff --git a/libs/nativewindow/Android.bp b/libs/nativewindow/Android.bp
index 3503a9e..3b58265 100644
--- a/libs/nativewindow/Android.bp
+++ b/libs/nativewindow/Android.bp
@@ -74,6 +74,9 @@
override_export_include_dirs: [
"include",
],
+ export_llndk_headers: [
+ "libarect_headers",
+ ],
},
export_include_dirs: [
"include",
@@ -110,16 +113,14 @@
],
header_libs: [
+ "libarect_headers",
"libnativebase_headers",
"libnativewindow_headers",
],
// headers we include in our public headers
- export_static_lib_headers: [
- "libarect",
- ],
-
export_header_lib_headers: [
+ "libarect_headers",
"libnativebase_headers",
],
diff --git a/libs/ui/include/ui/DisplayId.h b/libs/ui/include/ui/DisplayId.h
index d0c03fe..3a31fa0 100644
--- a/libs/ui/include/ui/DisplayId.h
+++ b/libs/ui/include/ui/DisplayId.h
@@ -17,6 +17,7 @@
#pragma once
#include <cstdint>
+#include <ostream>
#include <string>
#include <ftl/optional.h>
@@ -67,6 +68,11 @@
return std::to_string(displayId.value);
}
+// For tests.
+inline std::ostream& operator<<(std::ostream& stream, DisplayId displayId) {
+ return stream << "DisplayId{" << displayId.value << '}';
+}
+
// DisplayId of a physical display, such as the internal display or externally connected display.
struct PhysicalDisplayId : DisplayId {
static constexpr ftl::Optional<PhysicalDisplayId> tryCast(DisplayId id) {
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index acd3d75..694c127 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -541,6 +541,17 @@
entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_Y)));
}
+std::optional<nsecs_t> getDownTime(const EventEntry& eventEntry) {
+ if (eventEntry.type == EventEntry::Type::KEY) {
+ const KeyEntry& keyEntry = static_cast<const KeyEntry&>(eventEntry);
+ return keyEntry.downTime;
+ } else if (eventEntry.type == EventEntry::Type::MOTION) {
+ const MotionEntry& motionEntry = static_cast<const MotionEntry&>(eventEntry);
+ return motionEntry.downTime;
+ }
+ return std::nullopt;
+}
+
} // namespace
// --- InputDispatcher ---
@@ -1568,9 +1579,10 @@
}
// Identify targets.
- std::vector<InputTarget> inputTargets;
- InputEventInjectionResult injectionResult =
- findFocusedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime);
+ InputEventInjectionResult injectionResult;
+ sp<WindowInfoHandle> focusedWindow =
+ findFocusedWindowTargetLocked(currentTime, *entry, nextWakeupTime,
+ /*byref*/ injectionResult);
if (injectionResult == InputEventInjectionResult::PENDING) {
return false;
}
@@ -1579,6 +1591,12 @@
if (injectionResult != InputEventInjectionResult::SUCCEEDED) {
return true;
}
+ LOG_ALWAYS_FATAL_IF(focusedWindow == nullptr);
+
+ std::vector<InputTarget> inputTargets;
+ addWindowTargetLocked(focusedWindow,
+ InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS,
+ BitSet32(0), getDownTime(*entry), inputTargets);
// Add monitor channels from event's or focused display.
addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(*entry));
@@ -1673,13 +1691,27 @@
pilferPointersLocked(mDragState->dragWindow->getToken());
}
- injectionResult =
- findTouchedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime,
- &conflictingPointerActions);
+ std::vector<TouchedWindow> touchedWindows =
+ findTouchedWindowTargetsLocked(currentTime, *entry, nextWakeupTime,
+ &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);
+ }
} else {
// Non touch event. (eg. trackball)
- injectionResult =
- findFocusedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime);
+ sp<WindowInfoHandle> focusedWindow =
+ findFocusedWindowTargetLocked(currentTime, *entry, nextWakeupTime, injectionResult);
+ if (injectionResult == InputEventInjectionResult::SUCCEEDED) {
+ LOG_ALWAYS_FATAL_IF(focusedWindow == nullptr);
+ addWindowTargetLocked(focusedWindow,
+ InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS,
+ BitSet32(0), getDownTime(*entry), inputTargets);
+ }
}
if (injectionResult == InputEventInjectionResult::PENDING) {
return false;
@@ -1881,21 +1913,11 @@
return false;
}
-static std::optional<nsecs_t> getDownTime(const EventEntry& eventEntry) {
- if (eventEntry.type == EventEntry::Type::KEY) {
- const KeyEntry& keyEntry = static_cast<const KeyEntry&>(eventEntry);
- return keyEntry.downTime;
- } else if (eventEntry.type == EventEntry::Type::MOTION) {
- const MotionEntry& motionEntry = static_cast<const MotionEntry&>(eventEntry);
- return motionEntry.downTime;
- }
- return std::nullopt;
-}
-
-InputEventInjectionResult InputDispatcher::findFocusedWindowTargetsLocked(
- nsecs_t currentTime, const EventEntry& entry, std::vector<InputTarget>& inputTargets,
- nsecs_t* nextWakeupTime) {
+sp<WindowInfoHandle> InputDispatcher::findFocusedWindowTargetLocked(
+ nsecs_t currentTime, const EventEntry& entry, nsecs_t* nextWakeupTime,
+ InputEventInjectionResult& outInjectionResult) {
std::string reason;
+ outInjectionResult = InputEventInjectionResult::FAILED; // Default result
int32_t displayId = getTargetDisplayId(entry);
sp<WindowInfoHandle> focusedWindowHandle = getFocusedWindowHandleLocked(displayId);
@@ -1908,12 +1930,12 @@
ALOGI("Dropping %s event because there is no focused window or focused application in "
"display %" PRId32 ".",
ftl::enum_string(entry.type).c_str(), displayId);
- return InputEventInjectionResult::FAILED;
+ return nullptr;
}
// Drop key events if requested by input feature
if (focusedWindowHandle != nullptr && shouldDropInput(entry, focusedWindowHandle)) {
- return InputEventInjectionResult::FAILED;
+ return nullptr;
}
// Compatibility behavior: raise ANR if there is a focused application, but no focused window.
@@ -1933,15 +1955,17 @@
"window when it finishes starting up. Will wait for %" PRId64 "ms",
mAwaitedFocusedApplication->getName().c_str(), millis(timeout));
*nextWakeupTime = *mNoFocusedWindowTimeoutTime;
- return InputEventInjectionResult::PENDING;
+ outInjectionResult = InputEventInjectionResult::PENDING;
+ return nullptr;
} else if (currentTime > *mNoFocusedWindowTimeoutTime) {
// Already raised ANR. Drop the event
ALOGE("Dropping %s event because there is no focused window",
ftl::enum_string(entry.type).c_str());
- return InputEventInjectionResult::FAILED;
+ return nullptr;
} else {
// Still waiting for the focused window
- return InputEventInjectionResult::PENDING;
+ outInjectionResult = InputEventInjectionResult::PENDING;
+ return nullptr;
}
}
@@ -1951,13 +1975,15 @@
// Verify targeted injection.
if (const auto err = verifyTargetedInjection(focusedWindowHandle, entry); err) {
ALOGW("Dropping injected event: %s", (*err).c_str());
- return InputEventInjectionResult::TARGET_MISMATCH;
+ outInjectionResult = InputEventInjectionResult::TARGET_MISMATCH;
+ return nullptr;
}
if (focusedWindowHandle->getInfo()->inputConfig.test(
WindowInfo::InputConfig::PAUSE_DISPATCHING)) {
ALOGI("Waiting because %s is paused", focusedWindowHandle->getName().c_str());
- return InputEventInjectionResult::PENDING;
+ outInjectionResult = InputEventInjectionResult::PENDING;
+ return nullptr;
}
// If the event is a key event, then we must wait for all previous events to
@@ -1974,17 +2000,13 @@
if (entry.type == EventEntry::Type::KEY) {
if (shouldWaitToSendKeyLocked(currentTime, focusedWindowHandle->getName().c_str())) {
*nextWakeupTime = *mKeyIsWaitingForEventsTimeout;
- return InputEventInjectionResult::PENDING;
+ outInjectionResult = InputEventInjectionResult::PENDING;
+ return nullptr;
}
}
- // Success! Output targets.
- addWindowTargetLocked(focusedWindowHandle,
- InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS,
- BitSet32(0), getDownTime(entry), inputTargets);
-
- // Done.
- return InputEventInjectionResult::SUCCEEDED;
+ outInjectionResult = InputEventInjectionResult::SUCCEEDED;
+ return focusedWindowHandle;
}
/**
@@ -2013,11 +2035,12 @@
return responsiveMonitors;
}
-InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked(
- nsecs_t currentTime, const MotionEntry& entry, std::vector<InputTarget>& inputTargets,
- nsecs_t* nextWakeupTime, bool* outConflictingPointerActions) {
+std::vector<TouchedWindow> InputDispatcher::findTouchedWindowTargetsLocked(
+ nsecs_t currentTime, const MotionEntry& entry, nsecs_t* nextWakeupTime,
+ bool* outConflictingPointerActions, InputEventInjectionResult& outInjectionResult) {
ATRACE_CALL();
+ std::vector<TouchedWindow> touchedWindows;
// 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;
@@ -2025,7 +2048,7 @@
const int32_t maskedAction = action & AMOTION_EVENT_ACTION_MASK;
// Update the touch state as needed based on the properties of the touch event.
- InputEventInjectionResult injectionResult = InputEventInjectionResult::PENDING;
+ outInjectionResult = InputEventInjectionResult::PENDING;
sp<WindowInfoHandle> newHoverWindowHandle(mLastHoverWindowHandle);
sp<WindowInfoHandle> newTouchedWindowHandle;
@@ -2058,7 +2081,7 @@
"in display %" PRId32,
displayId);
// TODO: test multiple simultaneous input streams.
- injectionResult = InputEventInjectionResult::FAILED;
+ outInjectionResult = InputEventInjectionResult::FAILED;
switchedDevice = false;
wrongDevice = true;
goto Failed;
@@ -2074,7 +2097,7 @@
"in display %" PRId32,
displayId);
// TODO: test multiple simultaneous input streams.
- injectionResult = InputEventInjectionResult::FAILED;
+ outInjectionResult = InputEventInjectionResult::FAILED;
switchedDevice = false;
wrongDevice = true;
goto Failed;
@@ -2100,7 +2123,7 @@
// Verify targeted injection.
if (const auto err = verifyTargetedInjection(newTouchedWindowHandle, entry); err) {
ALOGW("Dropping injected touch event: %s", (*err).c_str());
- injectionResult = os::InputEventInjectionResult::TARGET_MISMATCH;
+ outInjectionResult = os::InputEventInjectionResult::TARGET_MISMATCH;
newTouchedWindowHandle = nullptr;
goto Failed;
}
@@ -2141,7 +2164,7 @@
if (newTouchedWindows.empty()) {
ALOGI("Dropping event because there is no touchable window at (%d, %d) on display %d.",
x, y, displayId);
- injectionResult = InputEventInjectionResult::FAILED;
+ outInjectionResult = InputEventInjectionResult::FAILED;
goto Failed;
}
@@ -2193,7 +2216,7 @@
"dropped the pointer down event in display %" PRId32,
displayId);
}
- injectionResult = InputEventInjectionResult::FAILED;
+ outInjectionResult = InputEventInjectionResult::FAILED;
goto Failed;
}
@@ -2212,7 +2235,7 @@
// Verify targeted injection.
if (const auto err = verifyTargetedInjection(newTouchedWindowHandle, entry); err) {
ALOGW("Dropping injected event: %s", (*err).c_str());
- injectionResult = os::InputEventInjectionResult::TARGET_MISMATCH;
+ outInjectionResult = os::InputEventInjectionResult::TARGET_MISMATCH;
newTouchedWindowHandle = nullptr;
goto Failed;
}
@@ -2303,7 +2326,7 @@
})) {
ALOGI("Dropping event because there is no touched window on display %d to receive it: %s",
displayId, entry.getDescription().c_str());
- injectionResult = InputEventInjectionResult::FAILED;
+ outInjectionResult = InputEventInjectionResult::FAILED;
goto Failed;
}
@@ -2323,7 +2346,7 @@
ALOGW("Dropping targeted injection: At least one touched window is not owned by uid "
"%d:%s",
*entry.injectionState->targetUid, errs.c_str());
- injectionResult = InputEventInjectionResult::TARGET_MISMATCH;
+ outInjectionResult = InputEventInjectionResult::TARGET_MISMATCH;
goto Failed;
}
}
@@ -2380,13 +2403,8 @@
}
// Success! Output targets.
- injectionResult = InputEventInjectionResult::SUCCEEDED;
-
- for (const TouchedWindow& touchedWindow : tempTouchState.windows) {
- addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
- touchedWindow.pointerIds, touchedWindow.firstDownTimeInTarget,
- inputTargets);
- }
+ touchedWindows = tempTouchState.windows;
+ outInjectionResult = InputEventInjectionResult::SUCCEEDED;
// Drop the outside or hover touch windows since we will not care about them
// in the next iteration.
@@ -2471,7 +2489,7 @@
mLastHoverWindowHandle = newHoverWindowHandle;
}
- return injectionResult;
+ return touchedWindows;
}
void InputDispatcher::finishDragAndDrop(int32_t displayId, float x, float y) {
@@ -2578,7 +2596,7 @@
void InputDispatcher::addWindowTargetLocked(const sp<WindowInfoHandle>& windowHandle,
int32_t targetFlags, BitSet32 pointerIds,
std::optional<nsecs_t> firstDownTimeInTarget,
- std::vector<InputTarget>& inputTargets) {
+ std::vector<InputTarget>& inputTargets) const {
std::vector<InputTarget>::iterator it =
std::find_if(inputTargets.begin(), inputTargets.end(),
[&windowHandle](const InputTarget& inputTarget) {
@@ -3205,6 +3223,55 @@
postCommandLocked(std::move(command));
}
+status_t InputDispatcher::publishMotionEvent(Connection& connection,
+ DispatchEntry& dispatchEntry) const {
+ const EventEntry& eventEntry = *(dispatchEntry.eventEntry);
+ const MotionEntry& motionEntry = static_cast<const MotionEntry&>(eventEntry);
+
+ PointerCoords scaledCoords[MAX_POINTERS];
+ const PointerCoords* usingCoords = motionEntry.pointerCoords;
+
+ // Set the X and Y offset and X and Y scale depending on the input source.
+ if ((motionEntry.source & AINPUT_SOURCE_CLASS_POINTER) &&
+ !(dispatchEntry.targetFlags & InputTarget::FLAG_ZERO_COORDS)) {
+ float globalScaleFactor = dispatchEntry.globalScaleFactor;
+ if (globalScaleFactor != 1.0f) {
+ for (uint32_t i = 0; i < motionEntry.pointerCount; i++) {
+ scaledCoords[i] = motionEntry.pointerCoords[i];
+ // Don't apply window scale here since we don't want scale to affect raw
+ // coordinates. The scale will be sent back to the client and applied
+ // later when requesting relative coordinates.
+ scaledCoords[i].scale(globalScaleFactor, 1 /* windowXScale */,
+ 1 /* windowYScale */);
+ }
+ usingCoords = scaledCoords;
+ }
+ } else if (dispatchEntry.targetFlags & InputTarget::FLAG_ZERO_COORDS) {
+ // We don't want the dispatch target to know the coordinates
+ for (uint32_t i = 0; i < motionEntry.pointerCount; i++) {
+ scaledCoords[i].clear();
+ }
+ usingCoords = scaledCoords;
+ }
+
+ std::array<uint8_t, 32> hmac = getSignature(motionEntry, dispatchEntry);
+
+ // Publish the motion event.
+ return connection.inputPublisher
+ .publishMotionEvent(dispatchEntry.seq, dispatchEntry.resolvedEventId,
+ motionEntry.deviceId, motionEntry.source, motionEntry.displayId,
+ std::move(hmac), dispatchEntry.resolvedAction,
+ motionEntry.actionButton, dispatchEntry.resolvedFlags,
+ motionEntry.edgeFlags, motionEntry.metaState,
+ motionEntry.buttonState, motionEntry.classification,
+ dispatchEntry.transform, motionEntry.xPrecision,
+ motionEntry.yPrecision, motionEntry.xCursorPosition,
+ motionEntry.yCursorPosition, dispatchEntry.rawTransform,
+ motionEntry.downTime, motionEntry.eventTime,
+ motionEntry.pointerCount, motionEntry.pointerProperties,
+ usingCoords);
+}
+
void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
const sp<Connection>& connection) {
if (ATRACE_ENABLED()) {
@@ -3244,58 +3311,7 @@
}
case EventEntry::Type::MOTION: {
- const MotionEntry& motionEntry = static_cast<const MotionEntry&>(eventEntry);
-
- PointerCoords scaledCoords[MAX_POINTERS];
- const PointerCoords* usingCoords = motionEntry.pointerCoords;
-
- // Set the X and Y offset and X and Y scale depending on the input source.
- if ((motionEntry.source & AINPUT_SOURCE_CLASS_POINTER) &&
- !(dispatchEntry->targetFlags & InputTarget::FLAG_ZERO_COORDS)) {
- float globalScaleFactor = dispatchEntry->globalScaleFactor;
- if (globalScaleFactor != 1.0f) {
- for (uint32_t i = 0; i < motionEntry.pointerCount; i++) {
- scaledCoords[i] = motionEntry.pointerCoords[i];
- // Don't apply window scale here since we don't want scale to affect raw
- // coordinates. The scale will be sent back to the client and applied
- // later when requesting relative coordinates.
- scaledCoords[i].scale(globalScaleFactor, 1 /* windowXScale */,
- 1 /* windowYScale */);
- }
- usingCoords = scaledCoords;
- }
- } else {
- // We don't want the dispatch target to know.
- if (dispatchEntry->targetFlags & InputTarget::FLAG_ZERO_COORDS) {
- for (uint32_t i = 0; i < motionEntry.pointerCount; i++) {
- scaledCoords[i].clear();
- }
- usingCoords = scaledCoords;
- }
- }
-
- std::array<uint8_t, 32> hmac = getSignature(motionEntry, *dispatchEntry);
-
- // Publish the motion event.
- status = connection->inputPublisher
- .publishMotionEvent(dispatchEntry->seq,
- dispatchEntry->resolvedEventId,
- motionEntry.deviceId, motionEntry.source,
- motionEntry.displayId, std::move(hmac),
- dispatchEntry->resolvedAction,
- motionEntry.actionButton,
- dispatchEntry->resolvedFlags,
- motionEntry.edgeFlags, motionEntry.metaState,
- motionEntry.buttonState,
- motionEntry.classification,
- dispatchEntry->transform,
- motionEntry.xPrecision, motionEntry.yPrecision,
- motionEntry.xCursorPosition,
- motionEntry.yCursorPosition,
- dispatchEntry->rawTransform,
- motionEntry.downTime, motionEntry.eventTime,
- motionEntry.pointerCount,
- motionEntry.pointerProperties, usingCoords);
+ status = publishMotionEvent(*connection, *dispatchEntry);
break;
}
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index b20a548..dea2cae 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -542,19 +542,20 @@
void resetNoFocusedWindowTimeoutLocked() REQUIRES(mLock);
int32_t getTargetDisplayId(const EventEntry& entry);
- android::os::InputEventInjectionResult findFocusedWindowTargetsLocked(
- nsecs_t currentTime, const EventEntry& entry, std::vector<InputTarget>& inputTargets,
- nsecs_t* nextWakeupTime) REQUIRES(mLock);
- android::os::InputEventInjectionResult findTouchedWindowTargetsLocked(
- nsecs_t currentTime, const MotionEntry& entry, std::vector<InputTarget>& inputTargets,
- nsecs_t* nextWakeupTime, bool* outConflictingPointerActions) REQUIRES(mLock);
+ sp<android::gui::WindowInfoHandle> findFocusedWindowTargetLocked(
+ nsecs_t currentTime, const EventEntry& entry, nsecs_t* nextWakeupTime,
+ android::os::InputEventInjectionResult& outInjectionResult) REQUIRES(mLock);
+ std::vector<TouchedWindow> findTouchedWindowTargetsLocked(
+ nsecs_t currentTime, const MotionEntry& entry, nsecs_t* nextWakeupTime,
+ bool* outConflictingPointerActions,
+ android::os::InputEventInjectionResult& outInjectionResult) REQUIRES(mLock);
std::vector<Monitor> selectResponsiveMonitorsLocked(
const std::vector<Monitor>& gestureMonitors) const REQUIRES(mLock);
void addWindowTargetLocked(const sp<android::gui::WindowInfoHandle>& windowHandle,
int32_t targetFlags, BitSet32 pointerIds,
std::optional<nsecs_t> firstDownTimeInTarget,
- std::vector<InputTarget>& inputTargets) REQUIRES(mLock);
+ std::vector<InputTarget>& inputTargets) const REQUIRES(mLock);
void addGlobalMonitoringTargetsLocked(std::vector<InputTarget>& inputTargets, int32_t displayId)
REQUIRES(mLock);
void pokeUserActivityLocked(const EventEntry& eventEntry) REQUIRES(mLock);
@@ -601,6 +602,7 @@
void enqueueDispatchEntryLocked(const sp<Connection>& connection, std::shared_ptr<EventEntry>,
const InputTarget& inputTarget, int32_t dispatchMode)
REQUIRES(mLock);
+ status_t publishMotionEvent(Connection& connection, DispatchEntry& dispatchEntry) const;
void startDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection)
REQUIRES(mLock);
void finishDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index cacb63c..3b0f2ac 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -142,6 +142,9 @@
virtual std::optional<int32_t> getLightColor(int32_t deviceId, int32_t lightId) = 0;
/* Get light player ID */
virtual std::optional<int32_t> getLightPlayerId(int32_t deviceId, int32_t lightId) = 0;
+
+ /* Get the Bluetooth address of an input device, if known. */
+ virtual std::optional<std::string> getBluetoothAddress(int32_t deviceId) const = 0;
};
// --- InputReaderConfiguration ---
@@ -393,6 +396,8 @@
/* Gets the affine calibration associated with the specified device. */
virtual TouchAffineTransformation getTouchAffineTransformation(
const std::string& inputDeviceDescriptor, int32_t surfaceRotation) = 0;
+ /* Notifies the input reader policy that a stylus gesture has started. */
+ virtual void notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) = 0;
};
} // namespace android
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index b97c466..18d03f8 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -43,6 +43,7 @@
#include <ftl/enum.h>
#include <input/KeyCharacterMap.h>
#include <input/KeyLayoutMap.h>
+#include <input/PrintTools.h>
#include <input/VirtualKeyMap.h>
#include <openssl/sha.h>
#include <statslog.h>
@@ -134,10 +135,6 @@
{"green", LightColor::GREEN},
{"blue", LightColor::BLUE}};
-static inline const char* toString(bool value) {
- return value ? "true" : "false";
-}
-
static std::string sha1(const std::string& in) {
SHA_CTX ctx;
SHA1_Init(&ctx);
@@ -2128,6 +2125,17 @@
identifier.uniqueId = buffer;
}
+ // Attempt to get the bluetooth address of an input device from the uniqueId.
+ if (identifier.bus == BUS_BLUETOOTH &&
+ std::regex_match(identifier.uniqueId,
+ std::regex("^[A-Fa-f0-9]{2}(?::[A-Fa-f0-9]{2}){5}$"))) {
+ identifier.bluetoothAddress = identifier.uniqueId;
+ // The Bluetooth stack requires alphabetic characters to be uppercase in a valid address.
+ for (auto& c : *identifier.bluetoothAddress) {
+ c = ::toupper(c);
+ }
+ }
+
// Fill in the descriptor.
assignDescriptorLocked(identifier);
@@ -2625,9 +2633,10 @@
dump += StringPrintf(INDENT3 "ControllerNumber: %d\n", device->controllerNumber);
dump += StringPrintf(INDENT3 "UniqueId: %s\n", device->identifier.uniqueId.c_str());
dump += StringPrintf(INDENT3 "Identifier: bus=0x%04x, vendor=0x%04x, "
- "product=0x%04x, version=0x%04x\n",
+ "product=0x%04x, version=0x%04x, bluetoothAddress=%s\n",
device->identifier.bus, device->identifier.vendor,
- device->identifier.product, device->identifier.version);
+ device->identifier.product, device->identifier.version,
+ toString(device->identifier.bluetoothAddress).c_str());
dump += StringPrintf(INDENT3 "KeyLayoutFile: %s\n",
device->keyMap.keyLayoutFile.c_str());
dump += StringPrintf(INDENT3 "KeyCharacterMapFile: %s\n",
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index 428e999..f8b1b3f 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -58,6 +58,18 @@
identifier1.location == identifier2.location);
}
+static bool isStylusPointerGestureStart(const NotifyMotionArgs& motionArgs) {
+ const auto actionMasked = MotionEvent::getActionMasked(motionArgs.action);
+ if (actionMasked != AMOTION_EVENT_ACTION_HOVER_ENTER &&
+ actionMasked != AMOTION_EVENT_ACTION_DOWN &&
+ actionMasked != AMOTION_EVENT_ACTION_POINTER_DOWN) {
+ return false;
+ }
+ const auto actionIndex = MotionEvent::getActionIndex(motionArgs.action);
+ return motionArgs.pointerProperties[actionIndex].toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS ||
+ motionArgs.pointerProperties[actionIndex].toolType == AMOTION_EVENT_TOOL_TYPE_ERASER;
+}
+
// --- InputReader ---
InputReader::InputReader(std::shared_ptr<EventHubInterface> eventHub,
@@ -101,8 +113,10 @@
void InputReader::loopOnce() {
int32_t oldGeneration;
int32_t timeoutMillis;
+ // Copy some state so that we can access it outside the lock later.
bool inputDevicesChanged = false;
std::vector<InputDeviceInfo> inputDevices;
+ std::list<NotifyArgs> notifyArgs;
{ // acquire lock
std::scoped_lock _l(mLock);
@@ -127,7 +141,7 @@
mReaderIsAliveCondition.notify_all();
if (!events.empty()) {
- notifyAll(processEventsLocked(events.data(), events.size()));
+ notifyArgs += processEventsLocked(events.data(), events.size());
}
if (mNextTimeout != LLONG_MAX) {
@@ -137,7 +151,7 @@
ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f);
}
mNextTimeout = LLONG_MAX;
- notifyAll(timeoutExpiredLocked(now));
+ notifyArgs += timeoutExpiredLocked(now);
}
}
@@ -152,6 +166,16 @@
mPolicy->notifyInputDevicesChanged(inputDevices);
}
+ // Notify the policy of the start of every new stylus gesture outside the lock.
+ for (const auto& args : notifyArgs) {
+ const auto* motionArgs = std::get_if<NotifyMotionArgs>(&args);
+ if (motionArgs != nullptr && isStylusPointerGestureStart(*motionArgs)) {
+ mPolicy->notifyStylusGestureStarted(motionArgs->deviceId, motionArgs->eventTime);
+ }
+ }
+
+ notifyAll(std::move(notifyArgs));
+
// Flush queued events out to the listener.
// This must happen outside of the lock because the listener could potentially call
// back into the InputReader's methods, such as getScanCodeState, or become blocked
@@ -851,6 +875,16 @@
return std::nullopt;
}
+std::optional<std::string> InputReader::getBluetoothAddress(int32_t deviceId) const {
+ std::scoped_lock _l(mLock);
+
+ InputDevice* device = findInputDeviceLocked(deviceId);
+ if (device) {
+ return device->getBluetoothAddress();
+ }
+ return std::nullopt;
+}
+
bool InputReader::isInputDeviceEnabled(int32_t deviceId) {
std::scoped_lock _l(mLock);
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index afb1bed..b9a2b4c 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -51,6 +51,9 @@
inline int32_t getGeneration() const { return mGeneration; }
inline const std::string getName() const { return mIdentifier.name; }
inline const std::string getDescriptor() { return mIdentifier.descriptor; }
+ inline std::optional<std::string> getBluetoothAddress() const {
+ return mIdentifier.bluetoothAddress;
+ }
inline ftl::Flags<InputDeviceClass> getClasses() const { return mClasses; }
inline uint32_t getSources() const { return mSources; }
inline bool hasEventHubDevices() const { return !mDevices.empty(); }
diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h
index de268cf..4f2503a 100644
--- a/services/inputflinger/reader/include/InputReader.h
+++ b/services/inputflinger/reader/include/InputReader.h
@@ -113,6 +113,8 @@
std::optional<int32_t> getLightPlayerId(int32_t deviceId, int32_t lightId) override;
+ std::optional<std::string> getBluetoothAddress(int32_t deviceId) const override;
+
protected:
// These members are protected so they can be instrumented by test cases.
virtual std::shared_ptr<InputDevice> createDeviceLocked(int32_t deviceId,
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 98669f3..615889e 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -2844,54 +2844,20 @@
// Otherwise choose an arbitrary remaining pointer.
// This guarantees we always have an active touch id when there is at least one pointer.
// We keep the same active touch id for as long as possible.
- int32_t lastActiveTouchId = mPointerGesture.activeTouchId;
- int32_t activeTouchId = lastActiveTouchId;
- if (activeTouchId < 0) {
+ if (mPointerGesture.activeTouchId < 0) {
if (!mCurrentCookedState.fingerIdBits.isEmpty()) {
- activeTouchId = mPointerGesture.activeTouchId =
- mCurrentCookedState.fingerIdBits.firstMarkedBit();
+ mPointerGesture.activeTouchId = mCurrentCookedState.fingerIdBits.firstMarkedBit();
mPointerGesture.firstTouchTime = when;
}
- } else if (!mCurrentCookedState.fingerIdBits.hasBit(activeTouchId)) {
- if (!mCurrentCookedState.fingerIdBits.isEmpty()) {
- activeTouchId = mPointerGesture.activeTouchId =
- mCurrentCookedState.fingerIdBits.firstMarkedBit();
- } else {
- activeTouchId = mPointerGesture.activeTouchId = -1;
- }
+ } else if (!mCurrentCookedState.fingerIdBits.hasBit(mPointerGesture.activeTouchId)) {
+ mPointerGesture.activeTouchId = !mCurrentCookedState.fingerIdBits.isEmpty()
+ ? mCurrentCookedState.fingerIdBits.firstMarkedBit()
+ : -1;
}
-
- // Determine whether we are in quiet time.
- bool isQuietTime = false;
- if (activeTouchId < 0) {
- mPointerGesture.resetQuietTime();
- } else {
- isQuietTime = when < mPointerGesture.quietTime + mConfig.pointerGestureQuietInterval;
- if (!isQuietTime) {
- if ((mPointerGesture.lastGestureMode == PointerGesture::Mode::PRESS ||
- mPointerGesture.lastGestureMode == PointerGesture::Mode::SWIPE ||
- mPointerGesture.lastGestureMode == PointerGesture::Mode::FREEFORM) &&
- currentFingerCount < 2) {
- // Enter quiet time when exiting swipe or freeform state.
- // This is to prevent accidentally entering the hover state and flinging the
- // pointer when finishing a swipe and there is still one pointer left onscreen.
- isQuietTime = true;
- } else if (mPointerGesture.lastGestureMode ==
- PointerGesture::Mode::BUTTON_CLICK_OR_DRAG &&
- currentFingerCount >= 2 && !isPointerDown(mCurrentRawState.buttonState)) {
- // Enter quiet time when releasing the button and there are still two or more
- // fingers down. This may indicate that one finger was used to press the button
- // but it has not gone up yet.
- isQuietTime = true;
- }
- if (isQuietTime) {
- mPointerGesture.quietTime = when;
- }
- }
- }
+ const int32_t& activeTouchId = mPointerGesture.activeTouchId;
// Switch states based on button and pointer state.
- if (isQuietTime) {
+ if (checkForTouchpadQuietTime(when)) {
// Case 1: Quiet time. (QUIET)
ALOGD_IF(DEBUG_GESTURES, "Gestures: QUIET for next %0.3fms",
(mPointerGesture.quietTime + mConfig.pointerGestureQuietInterval - when) *
@@ -2931,24 +2897,9 @@
// Switch pointers if needed.
// Find the fastest pointer and follow it.
if (activeTouchId >= 0 && currentFingerCount > 1) {
- int32_t bestId = -1;
- float bestSpeed = mConfig.pointerGestureDragMinSwitchSpeed;
- for (BitSet32 idBits(mCurrentCookedState.fingerIdBits); !idBits.isEmpty();) {
- uint32_t id = idBits.clearFirstMarkedBit();
- std::optional<float> vx =
- mPointerGesture.velocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, id);
- std::optional<float> vy =
- mPointerGesture.velocityTracker.getVelocity(AMOTION_EVENT_AXIS_Y, id);
- if (vx && vy) {
- float speed = hypotf(*vx, *vy);
- if (speed > bestSpeed) {
- bestId = id;
- bestSpeed = speed;
- }
- }
- }
+ const auto [bestId, bestSpeed] = getFastestFinger();
if (bestId >= 0 && bestId != activeTouchId) {
- mPointerGesture.activeTouchId = activeTouchId = bestId;
+ mPointerGesture.activeTouchId = bestId;
ALOGD_IF(DEBUG_GESTURES,
"Gestures: BUTTON_CLICK_OR_DRAG switched pointers, bestId=%d, "
"bestSpeed=%0.3f",
@@ -3114,345 +3065,7 @@
}
} else {
// Case 5. At least two fingers down, button is not pressed. (PRESS, SWIPE or FREEFORM)
- // We need to provide feedback for each finger that goes down so we cannot wait
- // for the fingers to move before deciding what to do.
- //
- // The ambiguous case is deciding what to do when there are two fingers down but they
- // have not moved enough to determine whether they are part of a drag or part of a
- // freeform gesture, or just a press or long-press at the pointer location.
- //
- // When there are two fingers we start with the PRESS hypothesis and we generate a
- // down at the pointer location.
- //
- // When the two fingers move enough or when additional fingers are added, we make
- // a decision to transition into SWIPE or FREEFORM mode accordingly.
- ALOG_ASSERT(activeTouchId >= 0);
-
- bool settled = when >=
- mPointerGesture.firstTouchTime + mConfig.pointerGestureMultitouchSettleInterval;
- if (mPointerGesture.lastGestureMode != PointerGesture::Mode::PRESS &&
- mPointerGesture.lastGestureMode != PointerGesture::Mode::SWIPE &&
- mPointerGesture.lastGestureMode != PointerGesture::Mode::FREEFORM) {
- *outFinishPreviousGesture = true;
- } else if (!settled && currentFingerCount > lastFingerCount) {
- // Additional pointers have gone down but not yet settled.
- // Reset the gesture.
- ALOGD_IF(DEBUG_GESTURES,
- "Gestures: Resetting gesture since additional pointers went down for "
- "MULTITOUCH, settle time remaining %0.3fms",
- (mPointerGesture.firstTouchTime +
- mConfig.pointerGestureMultitouchSettleInterval - when) *
- 0.000001f);
- *outCancelPreviousGesture = true;
- } else {
- // Continue previous gesture.
- mPointerGesture.currentGestureMode = mPointerGesture.lastGestureMode;
- }
-
- if (*outFinishPreviousGesture || *outCancelPreviousGesture) {
- mPointerGesture.currentGestureMode = PointerGesture::Mode::PRESS;
- mPointerGesture.activeGestureId = 0;
- mPointerGesture.referenceIdBits.clear();
- mPointerVelocityControl.reset();
-
- // Use the centroid and pointer location as the reference points for the gesture.
- ALOGD_IF(DEBUG_GESTURES,
- "Gestures: Using centroid as reference for MULTITOUCH, settle time remaining "
- "%0.3fms",
- (mPointerGesture.firstTouchTime +
- mConfig.pointerGestureMultitouchSettleInterval - when) *
- 0.000001f);
- mCurrentRawState.rawPointerData
- .getCentroidOfTouchingPointers(&mPointerGesture.referenceTouchX,
- &mPointerGesture.referenceTouchY);
- mPointerController->getPosition(&mPointerGesture.referenceGestureX,
- &mPointerGesture.referenceGestureY);
- }
-
- // Clear the reference deltas for fingers not yet included in the reference calculation.
- for (BitSet32 idBits(mCurrentCookedState.fingerIdBits.value &
- ~mPointerGesture.referenceIdBits.value);
- !idBits.isEmpty();) {
- uint32_t id = idBits.clearFirstMarkedBit();
- mPointerGesture.referenceDeltas[id].dx = 0;
- mPointerGesture.referenceDeltas[id].dy = 0;
- }
- mPointerGesture.referenceIdBits = mCurrentCookedState.fingerIdBits;
-
- // Add delta for all fingers and calculate a common movement delta.
- int32_t commonDeltaRawX = 0, commonDeltaRawY = 0;
- BitSet32 commonIdBits(mLastCookedState.fingerIdBits.value &
- mCurrentCookedState.fingerIdBits.value);
- for (BitSet32 idBits(commonIdBits); !idBits.isEmpty();) {
- bool first = (idBits == commonIdBits);
- uint32_t id = idBits.clearFirstMarkedBit();
- const RawPointerData::Pointer& cpd = mCurrentRawState.rawPointerData.pointerForId(id);
- const RawPointerData::Pointer& lpd = mLastRawState.rawPointerData.pointerForId(id);
- PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id];
- delta.dx += cpd.x - lpd.x;
- delta.dy += cpd.y - lpd.y;
-
- if (first) {
- commonDeltaRawX = delta.dx;
- commonDeltaRawY = delta.dy;
- } else {
- commonDeltaRawX = calculateCommonVector(commonDeltaRawX, delta.dx);
- commonDeltaRawY = calculateCommonVector(commonDeltaRawY, delta.dy);
- }
- }
-
- // Consider transitions from PRESS to SWIPE or MULTITOUCH.
- if (mPointerGesture.currentGestureMode == PointerGesture::Mode::PRESS) {
- float dist[MAX_POINTER_ID + 1];
- int32_t distOverThreshold = 0;
- for (BitSet32 idBits(mPointerGesture.referenceIdBits); !idBits.isEmpty();) {
- uint32_t id = idBits.clearFirstMarkedBit();
- PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id];
- dist[id] = hypotf(delta.dx * mPointerXZoomScale, delta.dy * mPointerYZoomScale);
- if (dist[id] > mConfig.pointerGestureMultitouchMinDistance) {
- distOverThreshold += 1;
- }
- }
-
- // Only transition when at least two pointers have moved further than
- // the minimum distance threshold.
- if (distOverThreshold >= 2) {
- if (currentFingerCount > 2) {
- // There are more than two pointers, switch to FREEFORM.
- ALOGD_IF(DEBUG_GESTURES,
- "Gestures: PRESS transitioned to FREEFORM, number of pointers %d > 2",
- currentFingerCount);
- *outCancelPreviousGesture = true;
- mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM;
- } else {
- // There are exactly two pointers.
- BitSet32 idBits(mCurrentCookedState.fingerIdBits);
- uint32_t id1 = idBits.clearFirstMarkedBit();
- uint32_t id2 = idBits.firstMarkedBit();
- const RawPointerData::Pointer& p1 =
- mCurrentRawState.rawPointerData.pointerForId(id1);
- const RawPointerData::Pointer& p2 =
- mCurrentRawState.rawPointerData.pointerForId(id2);
- float mutualDistance = distance(p1.x, p1.y, p2.x, p2.y);
- if (mutualDistance > mPointerGestureMaxSwipeWidth) {
- // There are two pointers but they are too far apart for a SWIPE,
- // switch to FREEFORM.
- ALOGD_IF(DEBUG_GESTURES,
- "Gestures: PRESS transitioned to FREEFORM, distance %0.3f > %0.3f",
- mutualDistance, mPointerGestureMaxSwipeWidth);
- *outCancelPreviousGesture = true;
- mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM;
- } else {
- // There are two pointers. Wait for both pointers to start moving
- // before deciding whether this is a SWIPE or FREEFORM gesture.
- float dist1 = dist[id1];
- float dist2 = dist[id2];
- if (dist1 >= mConfig.pointerGestureMultitouchMinDistance &&
- dist2 >= mConfig.pointerGestureMultitouchMinDistance) {
- // Calculate the dot product of the displacement vectors.
- // When the vectors are oriented in approximately the same direction,
- // the angle betweeen them is near zero and the cosine of the angle
- // approches 1.0. Recall that dot(v1, v2) = cos(angle) * mag(v1) *
- // mag(v2).
- PointerGesture::Delta& delta1 = mPointerGesture.referenceDeltas[id1];
- PointerGesture::Delta& delta2 = mPointerGesture.referenceDeltas[id2];
- float dx1 = delta1.dx * mPointerXZoomScale;
- float dy1 = delta1.dy * mPointerYZoomScale;
- float dx2 = delta2.dx * mPointerXZoomScale;
- float dy2 = delta2.dy * mPointerYZoomScale;
- float dot = dx1 * dx2 + dy1 * dy2;
- float cosine = dot / (dist1 * dist2); // denominator always > 0
- if (cosine >= mConfig.pointerGestureSwipeTransitionAngleCosine) {
- // Pointers are moving in the same direction. Switch to SWIPE.
- ALOGD_IF(DEBUG_GESTURES,
- "Gestures: PRESS transitioned to SWIPE, "
- "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, "
- "cosine %0.3f >= %0.3f",
- dist1, mConfig.pointerGestureMultitouchMinDistance, dist2,
- mConfig.pointerGestureMultitouchMinDistance, cosine,
- mConfig.pointerGestureSwipeTransitionAngleCosine);
- mPointerGesture.currentGestureMode = PointerGesture::Mode::SWIPE;
- } else {
- // Pointers are moving in different directions. Switch to FREEFORM.
- ALOGD_IF(DEBUG_GESTURES,
- "Gestures: PRESS transitioned to FREEFORM, "
- "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, "
- "cosine %0.3f < %0.3f",
- dist1, mConfig.pointerGestureMultitouchMinDistance, dist2,
- mConfig.pointerGestureMultitouchMinDistance, cosine,
- mConfig.pointerGestureSwipeTransitionAngleCosine);
- *outCancelPreviousGesture = true;
- mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM;
- }
- }
- }
- }
- }
- } else if (mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE) {
- // Switch from SWIPE to FREEFORM if additional pointers go down.
- // Cancel previous gesture.
- if (currentFingerCount > 2) {
- ALOGD_IF(DEBUG_GESTURES,
- "Gestures: SWIPE transitioned to FREEFORM, number of pointers %d > 2",
- currentFingerCount);
- *outCancelPreviousGesture = true;
- mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM;
- }
- }
-
- // Move the reference points based on the overall group motion of the fingers
- // except in PRESS mode while waiting for a transition to occur.
- if (mPointerGesture.currentGestureMode != PointerGesture::Mode::PRESS &&
- (commonDeltaRawX || commonDeltaRawY)) {
- for (BitSet32 idBits(mPointerGesture.referenceIdBits); !idBits.isEmpty();) {
- uint32_t id = idBits.clearFirstMarkedBit();
- PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id];
- delta.dx = 0;
- delta.dy = 0;
- }
-
- mPointerGesture.referenceTouchX += commonDeltaRawX;
- mPointerGesture.referenceTouchY += commonDeltaRawY;
-
- float commonDeltaX = commonDeltaRawX * mPointerXMovementScale;
- float commonDeltaY = commonDeltaRawY * mPointerYMovementScale;
-
- rotateDelta(mInputDeviceOrientation, &commonDeltaX, &commonDeltaY);
- mPointerVelocityControl.move(when, &commonDeltaX, &commonDeltaY);
-
- mPointerGesture.referenceGestureX += commonDeltaX;
- mPointerGesture.referenceGestureY += commonDeltaY;
- }
-
- // Report gestures.
- if (mPointerGesture.currentGestureMode == PointerGesture::Mode::PRESS ||
- mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE) {
- // PRESS or SWIPE mode.
- ALOGD_IF(DEBUG_GESTURES,
- "Gestures: PRESS or SWIPE activeTouchId=%d, activeGestureId=%d, "
- "currentTouchPointerCount=%d",
- activeTouchId, mPointerGesture.activeGestureId, currentFingerCount);
- ALOG_ASSERT(mPointerGesture.activeGestureId >= 0);
-
- mPointerGesture.currentGestureIdBits.clear();
- mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
- mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
- mPointerGesture.currentGestureProperties[0].clear();
- mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId;
- mPointerGesture.currentGestureProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
- mPointerGesture.currentGestureCoords[0].clear();
- mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X,
- mPointerGesture.referenceGestureX);
- mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y,
- mPointerGesture.referenceGestureY);
- mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
- if (mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE) {
- float xOffset = static_cast<float>(commonDeltaRawX) /
- (mRawPointerAxes.x.maxValue - mRawPointerAxes.x.minValue);
- float yOffset = static_cast<float>(commonDeltaRawY) /
- (mRawPointerAxes.y.maxValue - mRawPointerAxes.y.minValue);
- mPointerGesture.currentGestureCoords[0]
- .setAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET, xOffset);
- mPointerGesture.currentGestureCoords[0]
- .setAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, yOffset);
- }
- } else if (mPointerGesture.currentGestureMode == PointerGesture::Mode::FREEFORM) {
- // FREEFORM mode.
- ALOGD_IF(DEBUG_GESTURES,
- "Gestures: FREEFORM activeTouchId=%d, activeGestureId=%d, "
- "currentTouchPointerCount=%d",
- activeTouchId, mPointerGesture.activeGestureId, currentFingerCount);
- ALOG_ASSERT(mPointerGesture.activeGestureId >= 0);
-
- mPointerGesture.currentGestureIdBits.clear();
-
- BitSet32 mappedTouchIdBits;
- BitSet32 usedGestureIdBits;
- if (mPointerGesture.lastGestureMode != PointerGesture::Mode::FREEFORM) {
- // Initially, assign the active gesture id to the active touch point
- // if there is one. No other touch id bits are mapped yet.
- if (!*outCancelPreviousGesture) {
- mappedTouchIdBits.markBit(activeTouchId);
- usedGestureIdBits.markBit(mPointerGesture.activeGestureId);
- mPointerGesture.freeformTouchToGestureIdMap[activeTouchId] =
- mPointerGesture.activeGestureId;
- } else {
- mPointerGesture.activeGestureId = -1;
- }
- } else {
- // Otherwise, assume we mapped all touches from the previous frame.
- // Reuse all mappings that are still applicable.
- mappedTouchIdBits.value = mLastCookedState.fingerIdBits.value &
- mCurrentCookedState.fingerIdBits.value;
- usedGestureIdBits = mPointerGesture.lastGestureIdBits;
-
- // Check whether we need to choose a new active gesture id because the
- // current went went up.
- for (BitSet32 upTouchIdBits(mLastCookedState.fingerIdBits.value &
- ~mCurrentCookedState.fingerIdBits.value);
- !upTouchIdBits.isEmpty();) {
- uint32_t upTouchId = upTouchIdBits.clearFirstMarkedBit();
- uint32_t upGestureId = mPointerGesture.freeformTouchToGestureIdMap[upTouchId];
- if (upGestureId == uint32_t(mPointerGesture.activeGestureId)) {
- mPointerGesture.activeGestureId = -1;
- break;
- }
- }
- }
-
- ALOGD_IF(DEBUG_GESTURES,
- "Gestures: FREEFORM follow up mappedTouchIdBits=0x%08x, "
- "usedGestureIdBits=0x%08x, activeGestureId=%d",
- mappedTouchIdBits.value, usedGestureIdBits.value,
- mPointerGesture.activeGestureId);
-
- BitSet32 idBits(mCurrentCookedState.fingerIdBits);
- for (uint32_t i = 0; i < currentFingerCount; i++) {
- uint32_t touchId = idBits.clearFirstMarkedBit();
- uint32_t gestureId;
- if (!mappedTouchIdBits.hasBit(touchId)) {
- gestureId = usedGestureIdBits.markFirstUnmarkedBit();
- mPointerGesture.freeformTouchToGestureIdMap[touchId] = gestureId;
- ALOGD_IF(DEBUG_GESTURES,
- "Gestures: FREEFORM new mapping for touch id %d -> gesture id %d",
- touchId, gestureId);
- } else {
- gestureId = mPointerGesture.freeformTouchToGestureIdMap[touchId];
- ALOGD_IF(DEBUG_GESTURES,
- "Gestures: FREEFORM existing mapping for touch id %d -> gesture id %d",
- touchId, gestureId);
- }
- mPointerGesture.currentGestureIdBits.markBit(gestureId);
- mPointerGesture.currentGestureIdToIndex[gestureId] = i;
-
- const RawPointerData::Pointer& pointer =
- mCurrentRawState.rawPointerData.pointerForId(touchId);
- float deltaX = (pointer.x - mPointerGesture.referenceTouchX) * mPointerXZoomScale;
- float deltaY = (pointer.y - mPointerGesture.referenceTouchY) * mPointerYZoomScale;
- rotateDelta(mInputDeviceOrientation, &deltaX, &deltaY);
-
- mPointerGesture.currentGestureProperties[i].clear();
- mPointerGesture.currentGestureProperties[i].id = gestureId;
- mPointerGesture.currentGestureProperties[i].toolType =
- AMOTION_EVENT_TOOL_TYPE_FINGER;
- mPointerGesture.currentGestureCoords[i].clear();
- mPointerGesture.currentGestureCoords[i]
- .setAxisValue(AMOTION_EVENT_AXIS_X,
- mPointerGesture.referenceGestureX + deltaX);
- mPointerGesture.currentGestureCoords[i]
- .setAxisValue(AMOTION_EVENT_AXIS_Y,
- mPointerGesture.referenceGestureY + deltaY);
- mPointerGesture.currentGestureCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE,
- 1.0f);
- }
-
- if (mPointerGesture.activeGestureId < 0) {
- mPointerGesture.activeGestureId =
- mPointerGesture.currentGestureIdBits.firstMarkedBit();
- ALOGD_IF(DEBUG_GESTURES, "Gestures: FREEFORM new activeGestureId=%d",
- mPointerGesture.activeGestureId);
- }
- }
+ prepareMultiFingerPointerGestures(when, outCancelPreviousGesture, outFinishPreviousGesture);
}
mPointerController->setButtonState(mCurrentRawState.buttonState);
@@ -3490,6 +3103,399 @@
return true;
}
+bool TouchInputMapper::checkForTouchpadQuietTime(nsecs_t when) {
+ if (mPointerGesture.activeTouchId < 0) {
+ mPointerGesture.resetQuietTime();
+ return false;
+ }
+
+ if (when < mPointerGesture.quietTime + mConfig.pointerGestureQuietInterval) {
+ return true;
+ }
+
+ const uint32_t currentFingerCount = mCurrentCookedState.fingerIdBits.count();
+ bool isQuietTime = false;
+ if ((mPointerGesture.lastGestureMode == PointerGesture::Mode::PRESS ||
+ mPointerGesture.lastGestureMode == PointerGesture::Mode::SWIPE ||
+ mPointerGesture.lastGestureMode == PointerGesture::Mode::FREEFORM) &&
+ currentFingerCount < 2) {
+ // Enter quiet time when exiting swipe or freeform state.
+ // This is to prevent accidentally entering the hover state and flinging the
+ // pointer when finishing a swipe and there is still one pointer left onscreen.
+ isQuietTime = true;
+ } else if (mPointerGesture.lastGestureMode == PointerGesture::Mode::BUTTON_CLICK_OR_DRAG &&
+ currentFingerCount >= 2 && !isPointerDown(mCurrentRawState.buttonState)) {
+ // Enter quiet time when releasing the button and there are still two or more
+ // fingers down. This may indicate that one finger was used to press the button
+ // but it has not gone up yet.
+ isQuietTime = true;
+ }
+ if (isQuietTime) {
+ mPointerGesture.quietTime = when;
+ }
+ return isQuietTime;
+}
+
+std::pair<int32_t, float> TouchInputMapper::getFastestFinger() {
+ int32_t bestId = -1;
+ float bestSpeed = mConfig.pointerGestureDragMinSwitchSpeed;
+ for (BitSet32 idBits(mCurrentCookedState.fingerIdBits); !idBits.isEmpty();) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ std::optional<float> vx =
+ mPointerGesture.velocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, id);
+ std::optional<float> vy =
+ mPointerGesture.velocityTracker.getVelocity(AMOTION_EVENT_AXIS_Y, id);
+ if (vx && vy) {
+ float speed = hypotf(*vx, *vy);
+ if (speed > bestSpeed) {
+ bestId = id;
+ bestSpeed = speed;
+ }
+ }
+ }
+ return std::make_pair(bestId, bestSpeed);
+}
+
+void TouchInputMapper::prepareMultiFingerPointerGestures(nsecs_t when, bool* cancelPreviousGesture,
+ bool* finishPreviousGesture) {
+ // We need to provide feedback for each finger that goes down so we cannot wait for the fingers
+ // to move before deciding what to do.
+ //
+ // The ambiguous case is deciding what to do when there are two fingers down but they have not
+ // moved enough to determine whether they are part of a drag or part of a freeform gesture, or
+ // just a press or long-press at the pointer location.
+ //
+ // When there are two fingers we start with the PRESS hypothesis and we generate a down at the
+ // pointer location.
+ //
+ // When the two fingers move enough or when additional fingers are added, we make a decision to
+ // transition into SWIPE or FREEFORM mode accordingly.
+ const int32_t activeTouchId = mPointerGesture.activeTouchId;
+ ALOG_ASSERT(activeTouchId >= 0);
+
+ const uint32_t currentFingerCount = mCurrentCookedState.fingerIdBits.count();
+ const uint32_t lastFingerCount = mLastCookedState.fingerIdBits.count();
+ bool settled =
+ when >= mPointerGesture.firstTouchTime + mConfig.pointerGestureMultitouchSettleInterval;
+ if (mPointerGesture.lastGestureMode != PointerGesture::Mode::PRESS &&
+ mPointerGesture.lastGestureMode != PointerGesture::Mode::SWIPE &&
+ mPointerGesture.lastGestureMode != PointerGesture::Mode::FREEFORM) {
+ *finishPreviousGesture = true;
+ } else if (!settled && currentFingerCount > lastFingerCount) {
+ // Additional pointers have gone down but not yet settled.
+ // Reset the gesture.
+ ALOGD_IF(DEBUG_GESTURES,
+ "Gestures: Resetting gesture since additional pointers went down for "
+ "MULTITOUCH, settle time remaining %0.3fms",
+ (mPointerGesture.firstTouchTime + mConfig.pointerGestureMultitouchSettleInterval -
+ when) * 0.000001f);
+ *cancelPreviousGesture = true;
+ } else {
+ // Continue previous gesture.
+ mPointerGesture.currentGestureMode = mPointerGesture.lastGestureMode;
+ }
+
+ if (*finishPreviousGesture || *cancelPreviousGesture) {
+ mPointerGesture.currentGestureMode = PointerGesture::Mode::PRESS;
+ mPointerGesture.activeGestureId = 0;
+ mPointerGesture.referenceIdBits.clear();
+ mPointerVelocityControl.reset();
+
+ // Use the centroid and pointer location as the reference points for the gesture.
+ ALOGD_IF(DEBUG_GESTURES,
+ "Gestures: Using centroid as reference for MULTITOUCH, settle time remaining "
+ "%0.3fms",
+ (mPointerGesture.firstTouchTime + mConfig.pointerGestureMultitouchSettleInterval -
+ when) * 0.000001f);
+ mCurrentRawState.rawPointerData
+ .getCentroidOfTouchingPointers(&mPointerGesture.referenceTouchX,
+ &mPointerGesture.referenceTouchY);
+ mPointerController->getPosition(&mPointerGesture.referenceGestureX,
+ &mPointerGesture.referenceGestureY);
+ }
+
+ // Clear the reference deltas for fingers not yet included in the reference calculation.
+ for (BitSet32 idBits(mCurrentCookedState.fingerIdBits.value &
+ ~mPointerGesture.referenceIdBits.value);
+ !idBits.isEmpty();) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ mPointerGesture.referenceDeltas[id].dx = 0;
+ mPointerGesture.referenceDeltas[id].dy = 0;
+ }
+ mPointerGesture.referenceIdBits = mCurrentCookedState.fingerIdBits;
+
+ // Add delta for all fingers and calculate a common movement delta.
+ int32_t commonDeltaRawX = 0, commonDeltaRawY = 0;
+ BitSet32 commonIdBits(mLastCookedState.fingerIdBits.value &
+ mCurrentCookedState.fingerIdBits.value);
+ for (BitSet32 idBits(commonIdBits); !idBits.isEmpty();) {
+ bool first = (idBits == commonIdBits);
+ uint32_t id = idBits.clearFirstMarkedBit();
+ const RawPointerData::Pointer& cpd = mCurrentRawState.rawPointerData.pointerForId(id);
+ const RawPointerData::Pointer& lpd = mLastRawState.rawPointerData.pointerForId(id);
+ PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id];
+ delta.dx += cpd.x - lpd.x;
+ delta.dy += cpd.y - lpd.y;
+
+ if (first) {
+ commonDeltaRawX = delta.dx;
+ commonDeltaRawY = delta.dy;
+ } else {
+ commonDeltaRawX = calculateCommonVector(commonDeltaRawX, delta.dx);
+ commonDeltaRawY = calculateCommonVector(commonDeltaRawY, delta.dy);
+ }
+ }
+
+ // Consider transitions from PRESS to SWIPE or MULTITOUCH.
+ if (mPointerGesture.currentGestureMode == PointerGesture::Mode::PRESS) {
+ float dist[MAX_POINTER_ID + 1];
+ int32_t distOverThreshold = 0;
+ for (BitSet32 idBits(mPointerGesture.referenceIdBits); !idBits.isEmpty();) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id];
+ dist[id] = hypotf(delta.dx * mPointerXZoomScale, delta.dy * mPointerYZoomScale);
+ if (dist[id] > mConfig.pointerGestureMultitouchMinDistance) {
+ distOverThreshold += 1;
+ }
+ }
+
+ // Only transition when at least two pointers have moved further than
+ // the minimum distance threshold.
+ if (distOverThreshold >= 2) {
+ if (currentFingerCount > 2) {
+ // There are more than two pointers, switch to FREEFORM.
+ ALOGD_IF(DEBUG_GESTURES,
+ "Gestures: PRESS transitioned to FREEFORM, number of pointers %d > 2",
+ currentFingerCount);
+ *cancelPreviousGesture = true;
+ mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM;
+ } else {
+ // There are exactly two pointers.
+ BitSet32 idBits(mCurrentCookedState.fingerIdBits);
+ uint32_t id1 = idBits.clearFirstMarkedBit();
+ uint32_t id2 = idBits.firstMarkedBit();
+ const RawPointerData::Pointer& p1 =
+ mCurrentRawState.rawPointerData.pointerForId(id1);
+ const RawPointerData::Pointer& p2 =
+ mCurrentRawState.rawPointerData.pointerForId(id2);
+ float mutualDistance = distance(p1.x, p1.y, p2.x, p2.y);
+ if (mutualDistance > mPointerGestureMaxSwipeWidth) {
+ // There are two pointers but they are too far apart for a SWIPE,
+ // switch to FREEFORM.
+ ALOGD_IF(DEBUG_GESTURES,
+ "Gestures: PRESS transitioned to FREEFORM, distance %0.3f > %0.3f",
+ mutualDistance, mPointerGestureMaxSwipeWidth);
+ *cancelPreviousGesture = true;
+ mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM;
+ } else {
+ // There are two pointers. Wait for both pointers to start moving
+ // before deciding whether this is a SWIPE or FREEFORM gesture.
+ float dist1 = dist[id1];
+ float dist2 = dist[id2];
+ if (dist1 >= mConfig.pointerGestureMultitouchMinDistance &&
+ dist2 >= mConfig.pointerGestureMultitouchMinDistance) {
+ // Calculate the dot product of the displacement vectors.
+ // When the vectors are oriented in approximately the same direction,
+ // the angle betweeen them is near zero and the cosine of the angle
+ // approaches 1.0. Recall that dot(v1, v2) = cos(angle) * mag(v1) *
+ // mag(v2).
+ PointerGesture::Delta& delta1 = mPointerGesture.referenceDeltas[id1];
+ PointerGesture::Delta& delta2 = mPointerGesture.referenceDeltas[id2];
+ float dx1 = delta1.dx * mPointerXZoomScale;
+ float dy1 = delta1.dy * mPointerYZoomScale;
+ float dx2 = delta2.dx * mPointerXZoomScale;
+ float dy2 = delta2.dy * mPointerYZoomScale;
+ float dot = dx1 * dx2 + dy1 * dy2;
+ float cosine = dot / (dist1 * dist2); // denominator always > 0
+ if (cosine >= mConfig.pointerGestureSwipeTransitionAngleCosine) {
+ // Pointers are moving in the same direction. Switch to SWIPE.
+ ALOGD_IF(DEBUG_GESTURES,
+ "Gestures: PRESS transitioned to SWIPE, "
+ "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, "
+ "cosine %0.3f >= %0.3f",
+ dist1, mConfig.pointerGestureMultitouchMinDistance, dist2,
+ mConfig.pointerGestureMultitouchMinDistance, cosine,
+ mConfig.pointerGestureSwipeTransitionAngleCosine);
+ mPointerGesture.currentGestureMode = PointerGesture::Mode::SWIPE;
+ } else {
+ // Pointers are moving in different directions. Switch to FREEFORM.
+ ALOGD_IF(DEBUG_GESTURES,
+ "Gestures: PRESS transitioned to FREEFORM, "
+ "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, "
+ "cosine %0.3f < %0.3f",
+ dist1, mConfig.pointerGestureMultitouchMinDistance, dist2,
+ mConfig.pointerGestureMultitouchMinDistance, cosine,
+ mConfig.pointerGestureSwipeTransitionAngleCosine);
+ *cancelPreviousGesture = true;
+ mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM;
+ }
+ }
+ }
+ }
+ }
+ } else if (mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE) {
+ // Switch from SWIPE to FREEFORM if additional pointers go down.
+ // Cancel previous gesture.
+ if (currentFingerCount > 2) {
+ ALOGD_IF(DEBUG_GESTURES,
+ "Gestures: SWIPE transitioned to FREEFORM, number of pointers %d > 2",
+ currentFingerCount);
+ *cancelPreviousGesture = true;
+ mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM;
+ }
+ }
+
+ // Move the reference points based on the overall group motion of the fingers
+ // except in PRESS mode while waiting for a transition to occur.
+ if (mPointerGesture.currentGestureMode != PointerGesture::Mode::PRESS &&
+ (commonDeltaRawX || commonDeltaRawY)) {
+ for (BitSet32 idBits(mPointerGesture.referenceIdBits); !idBits.isEmpty();) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id];
+ delta.dx = 0;
+ delta.dy = 0;
+ }
+
+ mPointerGesture.referenceTouchX += commonDeltaRawX;
+ mPointerGesture.referenceTouchY += commonDeltaRawY;
+
+ float commonDeltaX = commonDeltaRawX * mPointerXMovementScale;
+ float commonDeltaY = commonDeltaRawY * mPointerYMovementScale;
+
+ rotateDelta(mInputDeviceOrientation, &commonDeltaX, &commonDeltaY);
+ mPointerVelocityControl.move(when, &commonDeltaX, &commonDeltaY);
+
+ mPointerGesture.referenceGestureX += commonDeltaX;
+ mPointerGesture.referenceGestureY += commonDeltaY;
+ }
+
+ // Report gestures.
+ if (mPointerGesture.currentGestureMode == PointerGesture::Mode::PRESS ||
+ mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE) {
+ // PRESS or SWIPE mode.
+ ALOGD_IF(DEBUG_GESTURES,
+ "Gestures: PRESS or SWIPE activeTouchId=%d, activeGestureId=%d, "
+ "currentTouchPointerCount=%d",
+ activeTouchId, mPointerGesture.activeGestureId, currentFingerCount);
+ ALOG_ASSERT(mPointerGesture.activeGestureId >= 0);
+
+ mPointerGesture.currentGestureIdBits.clear();
+ mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
+ mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
+ mPointerGesture.currentGestureProperties[0].clear();
+ mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId;
+ mPointerGesture.currentGestureProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+ mPointerGesture.currentGestureCoords[0].clear();
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X,
+ mPointerGesture.referenceGestureX);
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y,
+ mPointerGesture.referenceGestureY);
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+ if (mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE) {
+ float xOffset = static_cast<float>(commonDeltaRawX) /
+ (mRawPointerAxes.x.maxValue - mRawPointerAxes.x.minValue);
+ float yOffset = static_cast<float>(commonDeltaRawY) /
+ (mRawPointerAxes.y.maxValue - mRawPointerAxes.y.minValue);
+ mPointerGesture.currentGestureCoords[0]
+ .setAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET, xOffset);
+ mPointerGesture.currentGestureCoords[0]
+ .setAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, yOffset);
+ }
+ } else if (mPointerGesture.currentGestureMode == PointerGesture::Mode::FREEFORM) {
+ // FREEFORM mode.
+ ALOGD_IF(DEBUG_GESTURES,
+ "Gestures: FREEFORM activeTouchId=%d, activeGestureId=%d, "
+ "currentTouchPointerCount=%d",
+ activeTouchId, mPointerGesture.activeGestureId, currentFingerCount);
+ ALOG_ASSERT(mPointerGesture.activeGestureId >= 0);
+
+ mPointerGesture.currentGestureIdBits.clear();
+
+ BitSet32 mappedTouchIdBits;
+ BitSet32 usedGestureIdBits;
+ if (mPointerGesture.lastGestureMode != PointerGesture::Mode::FREEFORM) {
+ // Initially, assign the active gesture id to the active touch point
+ // if there is one. No other touch id bits are mapped yet.
+ if (!*cancelPreviousGesture) {
+ mappedTouchIdBits.markBit(activeTouchId);
+ usedGestureIdBits.markBit(mPointerGesture.activeGestureId);
+ mPointerGesture.freeformTouchToGestureIdMap[activeTouchId] =
+ mPointerGesture.activeGestureId;
+ } else {
+ mPointerGesture.activeGestureId = -1;
+ }
+ } else {
+ // Otherwise, assume we mapped all touches from the previous frame.
+ // Reuse all mappings that are still applicable.
+ mappedTouchIdBits.value =
+ mLastCookedState.fingerIdBits.value & mCurrentCookedState.fingerIdBits.value;
+ usedGestureIdBits = mPointerGesture.lastGestureIdBits;
+
+ // Check whether we need to choose a new active gesture id because the
+ // current went went up.
+ for (BitSet32 upTouchIdBits(mLastCookedState.fingerIdBits.value &
+ ~mCurrentCookedState.fingerIdBits.value);
+ !upTouchIdBits.isEmpty();) {
+ uint32_t upTouchId = upTouchIdBits.clearFirstMarkedBit();
+ uint32_t upGestureId = mPointerGesture.freeformTouchToGestureIdMap[upTouchId];
+ if (upGestureId == uint32_t(mPointerGesture.activeGestureId)) {
+ mPointerGesture.activeGestureId = -1;
+ break;
+ }
+ }
+ }
+
+ ALOGD_IF(DEBUG_GESTURES,
+ "Gestures: FREEFORM follow up mappedTouchIdBits=0x%08x, usedGestureIdBits=0x%08x, "
+ "activeGestureId=%d",
+ mappedTouchIdBits.value, usedGestureIdBits.value, mPointerGesture.activeGestureId);
+
+ BitSet32 idBits(mCurrentCookedState.fingerIdBits);
+ for (uint32_t i = 0; i < currentFingerCount; i++) {
+ uint32_t touchId = idBits.clearFirstMarkedBit();
+ uint32_t gestureId;
+ if (!mappedTouchIdBits.hasBit(touchId)) {
+ gestureId = usedGestureIdBits.markFirstUnmarkedBit();
+ mPointerGesture.freeformTouchToGestureIdMap[touchId] = gestureId;
+ ALOGD_IF(DEBUG_GESTURES,
+ "Gestures: FREEFORM new mapping for touch id %d -> gesture id %d", touchId,
+ gestureId);
+ } else {
+ gestureId = mPointerGesture.freeformTouchToGestureIdMap[touchId];
+ ALOGD_IF(DEBUG_GESTURES,
+ "Gestures: FREEFORM existing mapping for touch id %d -> gesture id %d",
+ touchId, gestureId);
+ }
+ mPointerGesture.currentGestureIdBits.markBit(gestureId);
+ mPointerGesture.currentGestureIdToIndex[gestureId] = i;
+
+ const RawPointerData::Pointer& pointer =
+ mCurrentRawState.rawPointerData.pointerForId(touchId);
+ float deltaX = (pointer.x - mPointerGesture.referenceTouchX) * mPointerXZoomScale;
+ float deltaY = (pointer.y - mPointerGesture.referenceTouchY) * mPointerYZoomScale;
+ rotateDelta(mInputDeviceOrientation, &deltaX, &deltaY);
+
+ mPointerGesture.currentGestureProperties[i].clear();
+ mPointerGesture.currentGestureProperties[i].id = gestureId;
+ mPointerGesture.currentGestureProperties[i].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+ mPointerGesture.currentGestureCoords[i].clear();
+ mPointerGesture.currentGestureCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X,
+ mPointerGesture.referenceGestureX +
+ deltaX);
+ mPointerGesture.currentGestureCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y,
+ mPointerGesture.referenceGestureY +
+ deltaY);
+ mPointerGesture.currentGestureCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+ }
+
+ if (mPointerGesture.activeGestureId < 0) {
+ mPointerGesture.activeGestureId = mPointerGesture.currentGestureIdBits.firstMarkedBit();
+ ALOGD_IF(DEBUG_GESTURES, "Gestures: FREEFORM new activeGestureId=%d",
+ mPointerGesture.activeGestureId);
+ }
+ }
+}
+
void TouchInputMapper::moveMousePointerFromPointerDelta(nsecs_t when, uint32_t pointerId) {
const RawPointerData::Pointer& currentPointer =
mCurrentRawState.rawPointerData.pointerForId(pointerId);
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index 7b0327e..7680090 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -773,6 +773,14 @@
bool preparePointerGestures(nsecs_t when, bool* outCancelPreviousGesture,
bool* outFinishPreviousGesture, bool isTimeout);
+ // Returns true if we're in a period of "quiet time" when touchpad gestures should be ignored.
+ bool checkForTouchpadQuietTime(nsecs_t when);
+
+ std::pair<int32_t, float> getFastestFinger();
+
+ void prepareMultiFingerPointerGestures(nsecs_t when, bool* outCancelPreviousGesture,
+ bool* outFinishPreviousGesture);
+
// Moves the on-screen mouse pointer based on the movement of the pointer of the given ID
// between the last and current events. Uses a relative motion.
void moveMousePointerFromPointerDelta(nsecs_t when, uint32_t pointerId);
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index bc70584..f333306 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -244,6 +244,7 @@
bool mInputDevicesChanged GUARDED_BY(mLock){false};
std::vector<DisplayViewport> mViewports;
TouchAffineTransformation transform;
+ std::optional<int32_t /*deviceId*/> mStylusGestureNotified GUARDED_BY(mLock){};
protected:
virtual ~FakeInputReaderPolicy() {}
@@ -268,6 +269,18 @@
});
}
+ void assertStylusGestureNotified(int32_t deviceId) {
+ std::scoped_lock lock(mLock);
+ ASSERT_TRUE(mStylusGestureNotified);
+ ASSERT_EQ(deviceId, *mStylusGestureNotified);
+ mStylusGestureNotified.reset();
+ }
+
+ void assertStylusGestureNotNotified() {
+ std::scoped_lock lock(mLock);
+ ASSERT_FALSE(mStylusGestureNotified);
+ }
+
virtual void clearViewports() {
mViewports.clear();
mConfig.setDisplayViewports(mViewports);
@@ -428,6 +441,11 @@
ASSERT_NO_FATAL_FAILURE(processDevicesChanged(devicesChanged));
mInputDevicesChanged = false;
}
+
+ void notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) override {
+ std::scoped_lock<std::mutex> lock(mLock);
+ mStylusGestureNotified = deviceId;
+ }
};
// --- FakeEventHub ---
@@ -2329,6 +2347,15 @@
mTestListener.reset();
mFakePolicy.clear();
}
+
+ std::optional<InputDeviceInfo> findDeviceByName(const std::string& name) {
+ const std::vector<InputDeviceInfo> inputDevices = mFakePolicy->getInputDevices();
+ const auto& it = std::find_if(inputDevices.begin(), inputDevices.end(),
+ [&name](const InputDeviceInfo& info) {
+ return info.getIdentifier().name == name;
+ });
+ return it != inputDevices.end() ? std::make_optional(*it) : std::nullopt;
+ }
};
TEST_F(InputReaderIntegrationTest, TestInvalidDevice) {
@@ -2450,6 +2477,9 @@
mDevice = createUinputDevice<UinputTouchScreen>(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT));
ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
+ const auto info = findDeviceByName(mDevice->getName());
+ ASSERT_TRUE(info);
+ mDeviceInfo = *info;
}
void setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height,
@@ -2473,6 +2503,7 @@
}
std::unique_ptr<UinputTouchScreen> mDevice;
+ InputDeviceInfo mDeviceInfo;
};
TEST_F(TouchIntegrationTest, InputEvent_ProcessSingleTouch) {
@@ -2689,6 +2720,58 @@
ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
}
+TEST_F(TouchIntegrationTest, NotifiesPolicyWhenStylusGestureStarted) {
+ const Point centerPoint = mDevice->getCenterPoint();
+
+ // Send down with the pen tool selected. The policy should be notified of the stylus presence.
+ mDevice->sendSlot(FIRST_SLOT);
+ mDevice->sendTrackingId(FIRST_TRACKING_ID);
+ mDevice->sendToolType(MT_TOOL_PEN);
+ mDevice->sendDown(centerPoint);
+ mDevice->sendSync();
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotified(mDeviceInfo.getId()));
+
+ // Release the stylus touch.
+ mDevice->sendUp();
+ mDevice->sendSync();
+ ASSERT_NO_FATAL_FAILURE(
+ mTestListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP)));
+
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotNotified());
+
+ // Touch down with the finger, without the pen tool selected. The policy is not notified.
+ mDevice->sendTrackingId(FIRST_TRACKING_ID);
+ mDevice->sendToolType(MT_TOOL_FINGER);
+ mDevice->sendDown(centerPoint);
+ mDevice->sendSync();
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))));
+
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotNotified());
+
+ mDevice->sendUp();
+ mDevice->sendSync();
+ ASSERT_NO_FATAL_FAILURE(
+ mTestListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP)));
+
+ // Send a move event with the stylus tool without BTN_TOUCH to generate a hover enter.
+ // The policy should be notified of the stylus presence.
+ mDevice->sendTrackingId(FIRST_TRACKING_ID);
+ mDevice->sendToolType(MT_TOOL_PEN);
+ mDevice->sendMove(centerPoint);
+ mDevice->sendSync();
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotified(mDeviceInfo.getId()));
+}
+
// --- InputDeviceTest ---
class InputDeviceTest : public testing::Test {
protected:
@@ -2699,6 +2782,7 @@
static const int32_t DEVICE_CONTROLLER_NUMBER;
static const ftl::Flags<InputDeviceClass> DEVICE_CLASSES;
static const int32_t EVENTHUB_ID;
+ static const std::string DEVICE_BLUETOOTH_ADDRESS;
std::shared_ptr<FakeEventHub> mFakeEventHub;
sp<FakeInputReaderPolicy> mFakePolicy;
@@ -2715,6 +2799,7 @@
InputDeviceIdentifier identifier;
identifier.name = DEVICE_NAME;
identifier.location = DEVICE_LOCATION;
+ identifier.bluetoothAddress = DEVICE_BLUETOOTH_ADDRESS;
mDevice = std::make_shared<InputDevice>(mReader->getContext(), DEVICE_ID, DEVICE_GENERATION,
identifier);
mReader->pushNextDevice(mDevice);
@@ -2736,6 +2821,7 @@
const ftl::Flags<InputDeviceClass> InputDeviceTest::DEVICE_CLASSES =
InputDeviceClass::KEYBOARD | InputDeviceClass::TOUCH | InputDeviceClass::JOYSTICK;
const int32_t InputDeviceTest::EVENTHUB_ID = 1;
+const std::string InputDeviceTest::DEVICE_BLUETOOTH_ADDRESS = "11:AA:22:BB:33:CC";
TEST_F(InputDeviceTest, ImmutableProperties) {
ASSERT_EQ(DEVICE_ID, mDevice->getId());
@@ -3002,6 +3088,12 @@
device.dump(dumpStr, eventHubDevStr);
}
+TEST_F(InputDeviceTest, GetBluetoothAddress) {
+ const auto& address = mReader->getBluetoothAddress(DEVICE_ID);
+ ASSERT_TRUE(address);
+ ASSERT_EQ(DEVICE_BLUETOOTH_ADDRESS, *address);
+}
+
// --- InputMapperTest ---
class InputMapperTest : public testing::Test {
diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h
index ff7455b..e48f1d9 100644
--- a/services/inputflinger/tests/TestInputListenerMatchers.h
+++ b/services/inputflinger/tests/TestInputListenerMatchers.h
@@ -19,6 +19,7 @@
#include <android/input.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
+#include <input/Input.h>
namespace android {
@@ -62,6 +63,13 @@
return argPressure;
}
+MATCHER_P(WithToolType, toolType, "InputEvent with specified tool type") {
+ const auto argToolType = arg.pointerProperties[0].toolType;
+ *result_listener << "expected tool type " << motionToolTypeToString(toolType) << ", but got "
+ << motionToolTypeToString(argToolType);
+ return argToolType == toolType;
+}
+
MATCHER_P(WithFlags, flags, "InputEvent with specified flags") {
*result_listener << "expected flags " << flags << ", but got " << arg.flags;
return arg.flags == flags;
diff --git a/services/inputflinger/tests/UinputDevice.cpp b/services/inputflinger/tests/UinputDevice.cpp
index a23c873..626ad67 100644
--- a/services/inputflinger/tests/UinputDevice.cpp
+++ b/services/inputflinger/tests/UinputDevice.cpp
@@ -158,6 +158,8 @@
device->absmax[ABS_MT_POSITION_Y] = mSize.bottom - 1;
device->absmin[ABS_MT_TRACKING_ID] = RAW_ID_MIN;
device->absmax[ABS_MT_TRACKING_ID] = RAW_ID_MAX;
+ device->absmin[ABS_MT_TOOL_TYPE] = MT_TOOL_FINGER;
+ device->absmax[ABS_MT_TOOL_TYPE] = MT_TOOL_MAX;
}
void UinputTouchScreen::sendSlot(int32_t slot) {
diff --git a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
index a9f5a3a..2eed997 100644
--- a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
@@ -157,6 +157,10 @@
return reader->getKeyCodeForKeyLocation(deviceId, locationKeyCode);
}
+ std::optional<std::string> getBluetoothAddress(int32_t deviceId) const {
+ return reader->getBluetoothAddress(deviceId);
+ }
+
private:
std::unique_ptr<InputReaderInterface> reader;
};
@@ -273,6 +277,7 @@
std::chrono::microseconds(fdp->ConsumeIntegral<size_t>()),
std::chrono::microseconds(fdp->ConsumeIntegral<size_t>()));
},
+ [&]() -> void { reader->getBluetoothAddress(fdp->ConsumeIntegral<int32_t>()); },
})();
}
diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h
index bd81761..64316ba 100644
--- a/services/inputflinger/tests/fuzzers/MapperHelpers.h
+++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h
@@ -315,6 +315,7 @@
return mTransform;
}
void setTouchAffineTransformation(const TouchAffineTransformation t) { mTransform = t; }
+ void notifyStylusGestureStarted(int32_t, nsecs_t) {}
};
class FuzzInputListener : public virtual InputListenerInterface {
@@ -363,6 +364,7 @@
void updateLedMetaState(int32_t metaState) override{};
int32_t getLedMetaState() override { return mFdp->ConsumeIntegral<int32_t>(); };
+ void notifyStylusGestureStarted(int32_t, nsecs_t) {}
};
} // namespace android
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index b65f1b4..e76b191 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -156,6 +156,7 @@
"Effects/Daltonizer.cpp",
"EventLog/EventLog.cpp",
"FrontEnd/LayerCreationArgs.cpp",
+ "FrontEnd/LayerHandle.cpp",
"FrontEnd/TransactionHandler.cpp",
"FlagManager.cpp",
"FpsReporter.cpp",
diff --git a/services/surfaceflinger/Display/DisplayModeRequest.h b/services/surfaceflinger/Display/DisplayModeRequest.h
new file mode 100644
index 0000000..ac25fe0
--- /dev/null
+++ b/services/surfaceflinger/Display/DisplayModeRequest.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <ftl/non_null.h>
+
+#include "DisplayHardware/DisplayMode.h"
+
+namespace android::display {
+
+struct DisplayModeRequest {
+ ftl::NonNull<DisplayModePtr> modePtr;
+
+ // Whether to emit DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE.
+ bool emitEvent = false;
+};
+
+inline bool operator==(const DisplayModeRequest& lhs, const DisplayModeRequest& rhs) {
+ return lhs.modePtr == rhs.modePtr && lhs.emitEvent == rhs.emitEvent;
+}
+
+} // namespace android::display
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index 06a812b..7abb94b 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -41,6 +41,7 @@
#include <utils/RefBase.h>
#include <utils/Timers.h>
+#include "Display/DisplayModeRequest.h"
#include "DisplayHardware/DisplayMode.h"
#include "DisplayHardware/Hal.h"
#include "DisplayHardware/PowerAdvisor.h"
@@ -190,9 +191,20 @@
/* ------------------------------------------------------------------------
* Display mode management.
*/
+
+ // TODO(b/241285876): Replace ActiveModeInfo and DisplayModeEvent with DisplayModeRequest.
struct ActiveModeInfo {
+ using Event = scheduler::DisplayModeEvent;
+
+ ActiveModeInfo() = default;
+ ActiveModeInfo(DisplayModePtr mode, Event event) : mode(std::move(mode)), event(event) {}
+
+ explicit ActiveModeInfo(display::DisplayModeRequest&& request)
+ : ActiveModeInfo(std::move(request.modePtr).take(),
+ request.emitEvent ? Event::Changed : Event::None) {}
+
DisplayModePtr mode;
- scheduler::DisplayModeEvent event = scheduler::DisplayModeEvent::None;
+ Event event = Event::None;
bool operator!=(const ActiveModeInfo& other) const {
return mode != other.mode || event != other.event;
diff --git a/services/surfaceflinger/FrontEnd/LayerHandle.cpp b/services/surfaceflinger/FrontEnd/LayerHandle.cpp
new file mode 100644
index 0000000..75e4e3a
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/LayerHandle.cpp
@@ -0,0 +1,62 @@
+/*
+ * 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 "LayerHandle.h"
+#include <cstdint>
+#include "Layer.h"
+#include "LayerCreationArgs.h"
+#include "SurfaceFlinger.h"
+
+namespace android::surfaceflinger {
+
+LayerHandle::LayerHandle(const sp<android::SurfaceFlinger>& flinger,
+ const sp<android::Layer>& layer)
+ : mFlinger(flinger), mLayer(layer), mLayerId(static_cast<uint32_t>(layer->getSequence())) {}
+
+LayerHandle::~LayerHandle() {
+ if (mFlinger) {
+ mFlinger->onHandleDestroyed(this, mLayer, mLayerId);
+ }
+}
+
+const String16 LayerHandle::kDescriptor = String16("android.Layer.LayerHandle");
+
+sp<LayerHandle> LayerHandle::fromIBinder(const sp<IBinder>& binder) {
+ if (binder == nullptr) {
+ return nullptr;
+ }
+
+ BBinder* b = binder->localBinder();
+ if (b == nullptr || b->getInterfaceDescriptor() != LayerHandle::kDescriptor) {
+ ALOGD("handle does not have a valid descriptor");
+ return nullptr;
+ }
+
+ // We can safely cast this binder since its local and we verified its interface descriptor.
+ return sp<LayerHandle>::cast(binder);
+}
+
+sp<android::Layer> LayerHandle::getLayer(const sp<IBinder>& binder) {
+ sp<LayerHandle> handle = LayerHandle::fromIBinder(binder);
+ return handle ? handle->mLayer : nullptr;
+}
+
+uint32_t LayerHandle::getLayerId(const sp<IBinder>& binder) {
+ sp<LayerHandle> handle = LayerHandle::fromIBinder(binder);
+ return handle ? handle->mLayerId : UNASSIGNED_LAYER_ID;
+}
+
+} // namespace android::surfaceflinger
diff --git a/services/surfaceflinger/FrontEnd/LayerHandle.h b/services/surfaceflinger/FrontEnd/LayerHandle.h
new file mode 100644
index 0000000..5d0f783
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/LayerHandle.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <binder/Binder.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+class SurfaceFlinger;
+class Layer;
+} // namespace android
+
+namespace android::surfaceflinger {
+
+/*
+ * The layer handle is just a BBinder object passed to the client
+ * (remote process) -- we don't keep any reference on our side such that
+ * the dtor is called when the remote side let go of its reference.
+ *
+ * ~LayerHandle ensures that mFlinger->onLayerDestroyed() is called for
+ * this layer when the handle is destroyed.
+ */
+class LayerHandle : public BBinder {
+public:
+ LayerHandle(const sp<android::SurfaceFlinger>& flinger, const sp<android::Layer>& layer);
+ // for testing
+ LayerHandle(uint32_t layerId) : mFlinger(nullptr), mLayer(nullptr), mLayerId(layerId) {}
+ ~LayerHandle();
+
+ // Static functions to access the layer and layer id safely from an incoming binder.
+ static sp<LayerHandle> fromIBinder(const sp<IBinder>& handle);
+ static sp<android::Layer> getLayer(const sp<IBinder>& handle);
+ static uint32_t getLayerId(const sp<IBinder>& handle);
+ static const String16 kDescriptor;
+
+ const String16& getInterfaceDescriptor() const override { return kDescriptor; }
+
+private:
+ sp<android::SurfaceFlinger> mFlinger;
+ sp<android::Layer> mLayer;
+ const uint32_t mLayerId;
+};
+
+} // namespace android::surfaceflinger
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index def0dfa..9777092 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -70,6 +70,7 @@
#include "FrameTimeline.h"
#include "FrameTracer/FrameTracer.h"
#include "FrontEnd/LayerCreationArgs.h"
+#include "FrontEnd/LayerHandle.h"
#include "LayerProtoHelper.h"
#include "SurfaceFlinger.h"
#include "TimeStats/TimeStats.h"
@@ -332,7 +333,7 @@
return nullptr;
}
mGetHandleCalled = true;
- return sp<Handle>::make(mFlinger, sp<Layer>::fromExisting(this));
+ return sp<LayerHandle>::make(mFlinger, sp<Layer>::fromExisting(this));
}
// ---------------------------------------------------------------------------
@@ -774,7 +775,7 @@
}
bool Layer::setRelativeLayer(const sp<IBinder>& relativeToHandle, int32_t relativeZ) {
- sp<Layer> relative = fromHandle(relativeToHandle).promote();
+ sp<Layer> relative = LayerHandle::getLayer(relativeToHandle);
if (relative == nullptr) {
return false;
}
@@ -1014,8 +1015,10 @@
return priority == PRIORITY_FOCUSED_WITH_MODE || priority == PRIORITY_FOCUSED_WITHOUT_MODE;
};
-ui::LayerStack Layer::getLayerStack() const {
- if (const auto parent = mDrawingParent.promote()) {
+ui::LayerStack Layer::getLayerStack(LayerVector::StateSet state) const {
+ bool useDrawing = state == LayerVector::StateSet::Drawing;
+ const auto parent = useDrawing ? mDrawingParent.promote() : mCurrentParent.promote();
+ if (parent) {
return parent->getLayerStack();
}
return getDrawingState().layerStack;
@@ -1543,7 +1546,7 @@
bool Layer::reparent(const sp<IBinder>& newParentHandle) {
sp<Layer> newParent;
if (newParentHandle != nullptr) {
- newParent = fromHandle(newParentHandle).promote();
+ newParent = LayerHandle::getLayer(newParentHandle);
if (newParent == nullptr) {
ALOGE("Unable to promote Layer handle");
return false;
@@ -1936,7 +1939,8 @@
void Layer::setInputInfo(const WindowInfo& info) {
mDrawingState.inputInfo = info;
- mDrawingState.touchableRegionCrop = fromHandle(info.touchableRegionCropHandle.promote());
+ mDrawingState.touchableRegionCrop =
+ LayerHandle::getLayer(info.touchableRegionCropHandle.promote());
mDrawingState.modified = true;
mFlinger->mUpdateInputInfo = true;
setTransactionFlags(eTransactionNeeded);
@@ -2583,23 +2587,6 @@
mFlinger->mNumClones++;
}
-const String16 Layer::Handle::kDescriptor = String16("android.Layer.Handle");
-
-wp<Layer> Layer::fromHandle(const sp<IBinder>& handleBinder) {
- if (handleBinder == nullptr) {
- return nullptr;
- }
-
- BBinder* b = handleBinder->localBinder();
- if (b == nullptr || b->getInterfaceDescriptor() != Handle::kDescriptor) {
- return nullptr;
- }
-
- // We can safely cast this binder since its local and we verified its interface descriptor.
- sp<Handle> handle = sp<Handle>::cast(handleBinder);
- return handle->owner;
-}
-
bool Layer::setDropInputMode(gui::DropInputMode mode) {
if (mDrawingState.dropInputMode == mode) {
return false;
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 1773c03..a3c4e59 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -226,46 +226,6 @@
bool dimmingEnabled = true;
};
- /*
- * Trivial class, used to ensure that mFlinger->onLayerDestroyed(mLayer)
- * is called.
- */
- class LayerCleaner {
- sp<SurfaceFlinger> mFlinger;
- sp<Layer> mLayer;
- BBinder* mHandle;
-
- protected:
- ~LayerCleaner() {
- // destroy client resources
- mFlinger->onHandleDestroyed(mHandle, mLayer);
- }
-
- public:
- LayerCleaner(const sp<SurfaceFlinger>& flinger, const sp<Layer>& layer, BBinder* handle)
- : mFlinger(flinger), mLayer(layer), mHandle(handle) {}
- };
-
- /*
- * The layer handle is just a BBinder object passed to the client
- * (remote process) -- we don't keep any reference on our side such that
- * the dtor is called when the remote side let go of its reference.
- *
- * LayerCleaner ensures that mFlinger->onLayerDestroyed() is called for
- * this layer when the handle is destroyed.
- */
- class Handle : public BBinder, public LayerCleaner {
- public:
- Handle(const sp<SurfaceFlinger>& flinger, const sp<Layer>& layer)
- : LayerCleaner(flinger, layer, this), owner(layer) {}
- const String16& getInterfaceDescriptor() const override { return kDescriptor; }
-
- static const String16 kDescriptor;
- wp<Layer> owner;
- };
-
- static wp<Layer> fromHandle(const sp<IBinder>& handle);
-
explicit Layer(const LayerCreationArgs& args);
virtual ~Layer();
@@ -320,7 +280,9 @@
virtual bool setTrustedOverlay(bool);
virtual bool setFlags(uint32_t flags, uint32_t mask);
virtual bool setLayerStack(ui::LayerStack);
- virtual ui::LayerStack getLayerStack() const;
+ virtual ui::LayerStack getLayerStack(
+ LayerVector::StateSet state = LayerVector::StateSet::Drawing) const;
+
virtual bool setMetadata(const LayerMetadata& data);
virtual void setChildrenDrawingParent(const sp<Layer>&);
virtual bool reparent(const sp<IBinder>& newParentHandle);
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index 30483a2..39850c7 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -23,6 +23,7 @@
#include <chrono>
#include <cmath>
+#include <deque>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
@@ -143,8 +144,7 @@
ATRACE_INT(name.c_str(), static_cast<int>(std::round(overallScore * 100)));
- constexpr float kEpsilon = 0.0001f;
- if (std::abs(overallScore - rhs.overallScore) > kEpsilon) {
+ if (!ScoredRefreshRate::scoresEqual(overallScore, rhs.overallScore)) {
return overallScore > rhs.overallScore;
}
@@ -288,8 +288,7 @@
}
auto RefreshRateConfigs::getRankedRefreshRates(const std::vector<LayerRequirement>& layers,
- GlobalSignals signals) const
- -> std::pair<std::vector<RefreshRateRanking>, GlobalSignals> {
+ GlobalSignals signals) const -> RankedRefreshRates {
std::lock_guard lock(mLock);
if (mGetRankedRefreshRatesCache &&
@@ -304,7 +303,7 @@
auto RefreshRateConfigs::getRankedRefreshRatesLocked(const std::vector<LayerRequirement>& layers,
GlobalSignals signals) const
- -> std::pair<std::vector<RefreshRateRanking>, GlobalSignals> {
+ -> RankedRefreshRates {
using namespace fps_approx_ops;
ATRACE_CALL();
ALOGV("%s: %zu layers", __func__, layers.size());
@@ -314,8 +313,7 @@
// Keep the display at max refresh rate for the duration of powering on the display.
if (signals.powerOnImminent) {
ALOGV("Power On Imminent");
- return {getRefreshRatesByPolicyLocked(activeMode.getGroup(), RefreshRateOrder::Descending,
- /*preferredDisplayModeOpt*/ std::nullopt),
+ return {rankRefreshRates(activeMode.getGroup(), RefreshRateOrder::Descending),
GlobalSignals{.powerOnImminent = true}};
}
@@ -375,8 +373,7 @@
// selected a refresh rate to see if we should apply touch boost.
if (signals.touch && !hasExplicitVoteLayers) {
ALOGV("Touch Boost");
- return {getRefreshRatesByPolicyLocked(anchorGroup, RefreshRateOrder::Descending,
- /*preferredDisplayModeOpt*/ std::nullopt),
+ return {rankRefreshRates(anchorGroup, RefreshRateOrder::Descending),
GlobalSignals{.touch = true}};
}
@@ -388,24 +385,19 @@
if (!signals.touch && signals.idle && !(primaryRangeIsSingleRate && hasExplicitVoteLayers)) {
ALOGV("Idle");
- return {getRefreshRatesByPolicyLocked(activeMode.getGroup(), RefreshRateOrder::Ascending,
- /*preferredDisplayModeOpt*/ std::nullopt),
+ return {rankRefreshRates(activeMode.getGroup(), RefreshRateOrder::Ascending),
GlobalSignals{.idle = true}};
}
if (layers.empty() || noVoteLayers == layers.size()) {
ALOGV("No layers with votes");
- return {getRefreshRatesByPolicyLocked(anchorGroup, RefreshRateOrder::Descending,
- /*preferredDisplayModeOpt*/ std::nullopt),
- kNoSignals};
+ return {rankRefreshRates(anchorGroup, RefreshRateOrder::Descending), kNoSignals};
}
// Only if all layers want Min we should return Min
if (noVoteLayers + minVoteLayers == layers.size()) {
ALOGV("All layers Min");
- return {getRefreshRatesByPolicyLocked(activeMode.getGroup(), RefreshRateOrder::Ascending,
- /*preferredDisplayModeOpt*/ std::nullopt),
- kNoSignals};
+ return {rankRefreshRates(activeMode.getGroup(), RefreshRateOrder::Ascending), kNoSignals};
}
// Find the best refresh rate based on score
@@ -557,12 +549,13 @@
maxVoteLayers > 0 ? RefreshRateOrder::Descending : RefreshRateOrder::Ascending;
std::sort(scores.begin(), scores.end(),
RefreshRateScoreComparator{.refreshRateOrder = refreshRateOrder});
- std::vector<RefreshRateRanking> rankedRefreshRates;
- rankedRefreshRates.reserve(scores.size());
- std::transform(scores.begin(), scores.end(), back_inserter(rankedRefreshRates),
+ RefreshRateRanking ranking;
+ ranking.reserve(scores.size());
+
+ std::transform(scores.begin(), scores.end(), back_inserter(ranking),
[](const RefreshRateScore& score) {
- return RefreshRateRanking{score.modeIt->second, score.overallScore};
+ return ScoredRefreshRate{score.modeIt->second, score.overallScore};
});
const bool noLayerScore = std::all_of(scores.begin(), scores.end(), [](RefreshRateScore score) {
@@ -574,11 +567,9 @@
// range instead of picking a random score from the app range.
if (noLayerScore) {
ALOGV("Layers not scored");
- return {getRefreshRatesByPolicyLocked(anchorGroup, RefreshRateOrder::Descending,
- /*preferredDisplayModeOpt*/ std::nullopt),
- kNoSignals};
+ return {rankRefreshRates(anchorGroup, RefreshRateOrder::Descending), kNoSignals};
} else {
- return {rankedRefreshRates, kNoSignals};
+ return {ranking, kNoSignals};
}
}
@@ -596,14 +587,12 @@
}
}();
- const auto& touchRefreshRates =
- getRefreshRatesByPolicyLocked(anchorGroup, RefreshRateOrder::Descending,
- /*preferredDisplayModeOpt*/ std::nullopt);
+ const auto touchRefreshRates = rankRefreshRates(anchorGroup, RefreshRateOrder::Descending);
+
using fps_approx_ops::operator<;
if (signals.touch && explicitDefaultVoteLayers == 0 && touchBoostForExplicitExact &&
- scores.front().modeIt->second->getFps() <
- touchRefreshRates.front().displayModePtr->getFps()) {
+ scores.front().modeIt->second->getFps() < touchRefreshRates.front().modePtr->getFps()) {
ALOGV("Touch Boost");
return {touchRefreshRates, GlobalSignals{.touch = true}};
}
@@ -612,12 +601,11 @@
// current config
if (noLayerScore && refreshRateOrder == RefreshRateOrder::Ascending) {
const auto preferredDisplayMode = activeMode.getId();
- return {getRefreshRatesByPolicyLocked(anchorGroup, RefreshRateOrder::Ascending,
- preferredDisplayMode),
+ return {rankRefreshRates(anchorGroup, RefreshRateOrder::Ascending, preferredDisplayMode),
kNoSignals};
}
- return {rankedRefreshRates, kNoSignals};
+ return {ranking, kNoSignals};
}
std::unordered_map<uid_t, std::vector<const RefreshRateConfigs::LayerRequirement*>>
@@ -783,11 +771,12 @@
return mPrimaryRefreshRates.back()->second;
}
-std::vector<RefreshRateRanking> RefreshRateConfigs::getRefreshRatesByPolicyLocked(
+auto RefreshRateConfigs::rankRefreshRates(
std::optional<int> anchorGroupOpt, RefreshRateOrder refreshRateOrder,
- std::optional<DisplayModeId> preferredDisplayModeOpt) const {
- std::deque<RefreshRateRanking> rankings;
- const auto makeRanking = [&](const DisplayModeIterator it) REQUIRES(mLock) {
+ std::optional<DisplayModeId> preferredDisplayModeOpt) const -> RefreshRateRanking {
+ std::deque<ScoredRefreshRate> ranking;
+
+ const auto rankRefreshRate = [&](DisplayModeIterator it) REQUIRES(mLock) {
const auto& mode = it->second;
if (anchorGroupOpt && mode->getGroup() != anchorGroupOpt) {
return;
@@ -800,31 +789,32 @@
}
if (preferredDisplayModeOpt) {
if (*preferredDisplayModeOpt == mode->getId()) {
- rankings.push_front(RefreshRateRanking{mode, /*score*/ 1.0f});
+ constexpr float kScore = std::numeric_limits<float>::max();
+ ranking.push_front(ScoredRefreshRate{mode, kScore});
return;
}
constexpr float kNonPreferredModePenalty = 0.95f;
score *= kNonPreferredModePenalty;
}
- rankings.push_back(RefreshRateRanking{mode, score});
+ ranking.push_back(ScoredRefreshRate{mode, score});
};
if (refreshRateOrder == RefreshRateOrder::Ascending) {
- std::for_each(mPrimaryRefreshRates.begin(), mPrimaryRefreshRates.end(), makeRanking);
+ std::for_each(mPrimaryRefreshRates.begin(), mPrimaryRefreshRates.end(), rankRefreshRate);
} else {
- std::for_each(mPrimaryRefreshRates.rbegin(), mPrimaryRefreshRates.rend(), makeRanking);
+ std::for_each(mPrimaryRefreshRates.rbegin(), mPrimaryRefreshRates.rend(), rankRefreshRate);
}
- if (!rankings.empty() || !anchorGroupOpt) {
- return {rankings.begin(), rankings.end()};
+ if (!ranking.empty() || !anchorGroupOpt) {
+ return {ranking.begin(), ranking.end()};
}
ALOGW("Can't find %s refresh rate by policy with the same mode group"
" as the mode group %d",
refreshRateOrder == RefreshRateOrder::Ascending ? "min" : "max", anchorGroupOpt.value());
- return getRefreshRatesByPolicyLocked(/*anchorGroupOpt*/ std::nullopt, refreshRateOrder,
- preferredDisplayModeOpt);
+ constexpr std::optional<int> kNoAnchorGroup = std::nullopt;
+ return rankRefreshRates(kNoAnchorGroup, refreshRateOrder, preferredDisplayModeOpt);
}
DisplayModePtr RefreshRateConfigs::getActiveModePtr() const {
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index 7219584..99f81aa 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -23,6 +23,7 @@
#include <utility>
#include <variant>
+#include <ftl/concat.h>
#include <gui/DisplayEventReceiver.h>
#include <scheduler/Fps.h>
@@ -46,15 +47,6 @@
return static_cast<DisplayModeEvent>(static_cast<T>(lhs) | static_cast<T>(rhs));
}
-struct RefreshRateRanking {
- DisplayModePtr displayModePtr;
- float score = 0.0f;
-
- bool operator==(const RefreshRateRanking& ranking) const {
- return displayModePtr == ranking.displayModePtr && score == ranking.score;
- }
-};
-
using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride;
/**
@@ -208,12 +200,46 @@
return touch == other.touch && idle == other.idle &&
powerOnImminent == other.powerOnImminent;
}
+
+ auto toString() const {
+ return ftl::Concat("{touch=", touch, ", idle=", idle,
+ ", powerOnImminent=", powerOnImminent, '}');
+ }
};
- // Returns the list in the descending order of refresh rates desired
- // based on their overall score, and the GlobalSignals that were considered.
- std::pair<std::vector<RefreshRateRanking>, GlobalSignals> getRankedRefreshRates(
- const std::vector<LayerRequirement>&, GlobalSignals) const EXCLUDES(mLock);
+ struct ScoredRefreshRate {
+ DisplayModePtr modePtr;
+ float score = 0.0f;
+
+ bool operator==(const ScoredRefreshRate& other) const {
+ return modePtr == other.modePtr && score == other.score;
+ }
+
+ static bool scoresEqual(float lhs, float rhs) {
+ constexpr float kEpsilon = 0.0001f;
+ return std::abs(lhs - rhs) <= kEpsilon;
+ }
+
+ struct DescendingScore {
+ bool operator()(const ScoredRefreshRate& lhs, const ScoredRefreshRate& rhs) const {
+ return lhs.score > rhs.score && !scoresEqual(lhs.score, rhs.score);
+ }
+ };
+ };
+
+ using RefreshRateRanking = std::vector<ScoredRefreshRate>;
+
+ struct RankedRefreshRates {
+ RefreshRateRanking ranking; // Ordered by descending score.
+ GlobalSignals consideredSignals;
+
+ bool operator==(const RankedRefreshRates& other) const {
+ return ranking == other.ranking && consideredSignals == other.consideredSignals;
+ }
+ };
+
+ RankedRefreshRates getRankedRefreshRates(const std::vector<LayerRequirement>&,
+ GlobalSignals) const EXCLUDES(mLock);
FpsRange getSupportedRefreshRateRange() const EXCLUDES(mLock) {
std::lock_guard lock(mLock);
@@ -354,8 +380,8 @@
// See mActiveModeIt for thread safety.
DisplayModeIterator getActiveModeItLocked() const REQUIRES(mLock);
- std::pair<std::vector<RefreshRateRanking>, GlobalSignals> getRankedRefreshRatesLocked(
- const std::vector<LayerRequirement>&, GlobalSignals) const REQUIRES(mLock);
+ RankedRefreshRates getRankedRefreshRatesLocked(const std::vector<LayerRequirement>&,
+ GlobalSignals) const REQUIRES(mLock);
// Returns number of display frames and remainder when dividing the layer refresh period by
// display refresh period.
@@ -373,11 +399,10 @@
enum class RefreshRateOrder { Ascending, Descending };
- // Returns the rankings in RefreshRateOrder. May change at runtime.
// Only uses the primary range, not the app request range.
- std::vector<RefreshRateRanking> getRefreshRatesByPolicyLocked(
- std::optional<int> anchorGroupOpt, RefreshRateOrder,
- std::optional<DisplayModeId> preferredDisplayModeOpt) const REQUIRES(mLock);
+ RefreshRateRanking rankRefreshRates(std::optional<int> anchorGroupOpt, RefreshRateOrder,
+ std::optional<DisplayModeId> preferredDisplayModeOpt =
+ std::nullopt) const REQUIRES(mLock);
const Policy* getCurrentPolicyLocked() const REQUIRES(mLock);
bool isPolicyValidLocked(const Policy& policy) const REQUIRES(mLock);
@@ -436,7 +461,7 @@
struct GetRankedRefreshRatesCache {
std::pair<std::vector<LayerRequirement>, GlobalSignals> arguments;
- std::pair<std::vector<RefreshRateRanking>, GlobalSignals> result;
+ RankedRefreshRates result;
};
mutable std::optional<GetRankedRefreshRatesCache> mGetRankedRefreshRatesCache GUARDED_BY(mLock);
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 30f2c27..be3ebb7 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -127,11 +127,19 @@
}
void Scheduler::registerDisplay(sp<const DisplayDevice> display) {
+ if (display->isPrimary()) {
+ mLeaderDisplayId = display->getPhysicalId();
+ }
+
const bool ok = mDisplays.try_emplace(display->getPhysicalId(), std::move(display)).second;
ALOGE_IF(!ok, "%s: Duplicate display", __func__);
}
void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) {
+ if (mLeaderDisplayId == displayId) {
+ mLeaderDisplayId.reset();
+ }
+
mDisplays.erase(displayId);
}
@@ -631,9 +639,8 @@
template <typename S, typename T>
auto Scheduler::applyPolicy(S Policy::*statePtr, T&& newState) -> GlobalSignals {
- DisplayModePtr newMode;
+ std::vector<display::DisplayModeRequest> modeRequests;
GlobalSignals consideredSignals;
- std::vector<DisplayModeConfig> displayModeConfigs;
bool refreshRateChanged = false;
bool frameRateOverridesChanged;
@@ -646,42 +653,41 @@
if (currentState == newState) return {};
currentState = std::forward<T>(newState);
- displayModeConfigs = getBestDisplayModeConfigs();
+ auto modeChoices = chooseDisplayModes();
- // mPolicy holds the current mode, using the current mode we find out
- // what display is currently being tracked through the policy and
- // then find the DisplayModeConfig for that display. So that
- // later we check if the policy mode has changed for the same display in policy.
- // If mPolicy mode isn't available then we take the first display from the best display
- // modes as the candidate for policy changes and frame rate overrides.
- // TODO(b/240743786) Update the single display based assumptions and make mode changes
- // and mPolicy per display.
- const DisplayModeConfig& displayModeConfigForCurrentPolicy = mPolicy.mode
- ? *std::find_if(displayModeConfigs.begin(), displayModeConfigs.end(),
- [&](const auto& displayModeConfig) REQUIRES(mPolicyLock) {
- return displayModeConfig.displayModePtr
- ->getPhysicalDisplayId() ==
- mPolicy.mode->getPhysicalDisplayId();
- })
- : displayModeConfigs.front();
+ // TODO(b/240743786): The leader display's mode must change for any DisplayModeRequest to go
+ // through. Fix this by tracking per-display Scheduler::Policy and timers.
+ DisplayModePtr modePtr;
+ std::tie(modePtr, consideredSignals) =
+ modeChoices.get(*mLeaderDisplayId)
+ .transform([](const DisplayModeChoice& choice) {
+ return std::make_pair(choice.modePtr, choice.consideredSignals);
+ })
+ .value();
- newMode = displayModeConfigForCurrentPolicy.displayModePtr;
- consideredSignals = displayModeConfigForCurrentPolicy.signals;
- frameRateOverridesChanged = updateFrameRateOverrides(consideredSignals, newMode->getFps());
+ modeRequests.reserve(modeChoices.size());
+ for (auto& [id, choice] : modeChoices) {
+ modeRequests.emplace_back(
+ display::DisplayModeRequest{.modePtr =
+ ftl::as_non_null(std::move(choice.modePtr)),
+ .emitEvent = !choice.consideredSignals.idle});
+ }
- if (mPolicy.mode == newMode) {
+ frameRateOverridesChanged = updateFrameRateOverrides(consideredSignals, modePtr->getFps());
+
+ if (mPolicy.mode != modePtr) {
+ mPolicy.mode = modePtr;
+ refreshRateChanged = true;
+ } else {
// We don't need to change the display mode, but we might need to send an event
// about a mode change, since it was suppressed if previously considered idle.
if (!consideredSignals.idle) {
dispatchCachedReportedMode();
}
- } else {
- mPolicy.mode = newMode;
- refreshRateChanged = true;
}
}
if (refreshRateChanged) {
- mSchedulerCallback.requestDisplayModes(std::move(displayModeConfigs));
+ mSchedulerCallback.requestDisplayModes(std::move(modeRequests));
}
if (frameRateOverridesChanged) {
mSchedulerCallback.triggerOnFrameRateOverridesChanged();
@@ -689,11 +695,11 @@
return consideredSignals;
}
-std::vector<DisplayModeConfig> Scheduler::getBestDisplayModeConfigs() const {
+auto Scheduler::chooseDisplayModes() const -> DisplayModeChoiceMap {
ATRACE_CALL();
- using Rankings = std::pair<std::vector<RefreshRateRanking>, GlobalSignals>;
- display::PhysicalDisplayVector<Rankings> perDisplayRankings;
+ using RankedRefreshRates = RefreshRateConfigs::RankedRefreshRates;
+ display::PhysicalDisplayVector<RankedRefreshRates> perDisplayRanking;
// Tallies the score of a refresh rate across `displayCount` displays.
struct RefreshRateTally {
@@ -710,11 +716,11 @@
const auto globalSignals = makeGlobalSignals();
for (const auto& [id, display] : mDisplays) {
- auto [rankings, signals] =
+ auto rankedRefreshRates =
display->holdRefreshRateConfigs()
->getRankedRefreshRates(mPolicy.contentRequirements, globalSignals);
- for (const auto& [modePtr, score] : rankings) {
+ for (const auto& [modePtr, score] : rankedRefreshRates.ranking) {
const auto [it, inserted] = refreshRateTallies.try_emplace(modePtr->getFps(), score);
if (!inserted) {
@@ -724,7 +730,7 @@
}
}
- perDisplayRankings.emplace_back(std::move(rankings), signals);
+ perDisplayRanking.push_back(std::move(rankedRefreshRates));
}
auto maxScoreIt = refreshRateTallies.cbegin();
@@ -750,26 +756,27 @@
? std::make_optional(maxScoreIt->first)
: std::nullopt;
- std::vector<DisplayModeConfig> displayModeConfigs;
- displayModeConfigs.reserve(mDisplays.size());
+ DisplayModeChoiceMap modeChoices;
using fps_approx_ops::operator==;
- for (const auto& [rankings, signals] : perDisplayRankings) {
+ for (auto& [ranking, signals] : perDisplayRanking) {
if (!chosenFps) {
- displayModeConfigs.emplace_back(signals, rankings.front().displayModePtr);
+ auto& [modePtr, _] = ranking.front();
+ modeChoices.try_emplace(modePtr->getPhysicalDisplayId(),
+ DisplayModeChoice{std::move(modePtr), signals});
continue;
}
- for (const auto& ranking : rankings) {
- const auto& modePtr = ranking.displayModePtr;
+ for (auto& [modePtr, _] : ranking) {
if (modePtr->getFps() == *chosenFps) {
- displayModeConfigs.emplace_back(signals, modePtr);
+ modeChoices.try_emplace(modePtr->getPhysicalDisplayId(),
+ DisplayModeChoice{std::move(modePtr), signals});
break;
}
}
}
- return displayModeConfigs;
+ return modeChoices;
}
GlobalSignals Scheduler::makeGlobalSignals() const {
@@ -787,11 +794,11 @@
// Make sure the stored mode is up to date.
if (mPolicy.mode) {
const auto configs = holdRefreshRateConfigs();
- const auto rankings =
+ const auto ranking =
configs->getRankedRefreshRates(mPolicy.contentRequirements, makeGlobalSignals())
- .first;
+ .ranking;
- mPolicy.mode = rankings.front().displayModePtr;
+ mPolicy.mode = ranking.front().modePtr;
}
return mPolicy.mode;
}
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 6633b05..33f6126 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -38,6 +38,7 @@
#include <ui/DisplayId.h>
#include "Display/DisplayMap.h"
+#include "Display/DisplayModeRequest.h"
#include "DisplayDevice.h"
#include "EventThread.h"
#include "FrameRateOverrideMappings.h"
@@ -88,20 +89,9 @@
using GlobalSignals = RefreshRateConfigs::GlobalSignals;
-// Config representing the DisplayMode and considered signals for the Display.
-struct DisplayModeConfig {
- const GlobalSignals signals;
- const DisplayModePtr displayModePtr;
-
- DisplayModeConfig(GlobalSignals signals, DisplayModePtr displayModePtr)
- : signals(signals), displayModePtr(std::move(displayModePtr)) {}
-};
-
struct ISchedulerCallback {
- using DisplayModeEvent = scheduler::DisplayModeEvent;
-
virtual void setVsyncEnabled(bool) = 0;
- virtual void requestDisplayModes(std::vector<DisplayModeConfig>) = 0;
+ virtual void requestDisplayModes(std::vector<display::DisplayModeRequest>) = 0;
virtual void kernelTimerChanged(bool expired) = 0;
virtual void triggerOnFrameRateOverridesChanged() = 0;
@@ -278,8 +268,26 @@
template <typename S, typename T>
GlobalSignals applyPolicy(S Policy::*, T&&) EXCLUDES(mPolicyLock);
- // Returns the best display mode per display.
- std::vector<DisplayModeConfig> getBestDisplayModeConfigs() const REQUIRES(mPolicyLock);
+ struct DisplayModeChoice {
+ DisplayModeChoice(DisplayModePtr modePtr, GlobalSignals consideredSignals)
+ : modePtr(std::move(modePtr)), consideredSignals(consideredSignals) {}
+
+ DisplayModePtr modePtr;
+ GlobalSignals consideredSignals;
+
+ bool operator==(const DisplayModeChoice& other) const {
+ return modePtr == other.modePtr && consideredSignals == other.consideredSignals;
+ }
+
+ // For tests.
+ friend std::ostream& operator<<(std::ostream& stream, const DisplayModeChoice& choice) {
+ return stream << '{' << to_string(*choice.modePtr) << " considering "
+ << choice.consideredSignals.toString().c_str() << '}';
+ }
+ };
+
+ using DisplayModeChoiceMap = display::PhysicalDisplayMap<PhysicalDisplayId, DisplayModeChoice>;
+ DisplayModeChoiceMap chooseDisplayModes() const REQUIRES(mPolicyLock);
GlobalSignals makeGlobalSignals() const REQUIRES(mPolicyLock);
@@ -329,6 +337,7 @@
mutable std::mutex mPolicyLock;
display::PhysicalDisplayMap<PhysicalDisplayId, sp<const DisplayDevice>> mDisplays;
+ std::optional<PhysicalDisplayId> mLeaderDisplayId;
struct Policy {
// Policy for choosing the display mode.
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 46cd031..cfebec7 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -124,6 +124,7 @@
#include "FrameTimeline/FrameTimeline.h"
#include "FrameTracer/FrameTracer.h"
#include "FrontEnd/LayerCreationArgs.h"
+#include "FrontEnd/LayerHandle.h"
#include "HdrLayerInfoReporter.h"
#include "Layer.h"
#include "LayerProtoHelper.h"
@@ -1064,30 +1065,28 @@
return NO_ERROR;
}
-void SurfaceFlinger::setDesiredActiveMode(const ActiveModeInfo& info) {
+void SurfaceFlinger::setDesiredActiveMode(display::DisplayModeRequest&& request) {
ATRACE_CALL();
- if (!info.mode) {
- ALOGW("requested display mode is null");
- return;
- }
- auto display = getDisplayDeviceLocked(info.mode->getPhysicalDisplayId());
+ auto display = getDisplayDeviceLocked(request.modePtr->getPhysicalDisplayId());
if (!display) {
ALOGW("%s: display is no longer valid", __func__);
return;
}
- if (display->setDesiredActiveMode(info)) {
+ const Fps refreshRate = request.modePtr->getFps();
+
+ if (display->setDesiredActiveMode(DisplayDevice::ActiveModeInfo(std::move(request)))) {
scheduleComposite(FrameHint::kNone);
// Start receiving vsync samples now, so that we can detect a period
// switch.
- mScheduler->resyncToHardwareVsync(true, info.mode->getFps());
+ mScheduler->resyncToHardwareVsync(true, refreshRate);
// As we called to set period, we will call to onRefreshRateChangeCompleted once
// VsyncController model is locked.
modulateVsync(&VsyncModulator::onRefreshRateChangeInitiated);
- updatePhaseConfiguration(info.mode->getFps());
+ updatePhaseConfiguration(refreshRate);
mScheduler->setModeChangePending(true);
}
}
@@ -1179,7 +1178,7 @@
mRefreshRateStats->setRefreshRate(refreshRate);
updatePhaseConfiguration(refreshRate);
- if (upcomingModeInfo.event != DisplayModeEvent::None) {
+ if (upcomingModeInfo.event != scheduler::DisplayModeEvent::None) {
mScheduler->onPrimaryDisplayModeChanged(mAppConnectionHandle, upcomingModeInfo.mode);
}
}
@@ -1579,7 +1578,10 @@
return BAD_VALUE;
}
- const wp<Layer> stopLayer = fromHandle(stopLayerHandle);
+ // LayerHandle::getLayer promotes the layer object in a binder thread but we will not destroy
+ // the layer here since the caller has a strong ref to the layer's handle.
+ // TODO (b/238781169): replace layer with layer id
+ const wp<Layer> stopLayer = LayerHandle::getLayer(stopLayerHandle);
mRegionSamplingThread->addListener(samplingArea, stopLayer, listener);
return NO_ERROR;
}
@@ -3331,34 +3333,33 @@
mCompositionEngine->updateCursorAsync(refreshArgs);
}
-void SurfaceFlinger::requestDisplayModes(
- std::vector<scheduler::DisplayModeConfig> displayModeConfigs) {
+void SurfaceFlinger::requestDisplayModes(std::vector<display::DisplayModeRequest> modeRequests) {
if (mBootStage != BootStage::FINISHED) {
ALOGV("Currently in the boot stage, skipping display mode changes");
return;
}
ATRACE_CALL();
+
// If this is called from the main thread mStateLock must be locked before
// Currently the only way to call this function from the main thread is from
// Scheduler::chooseRefreshRateForContent
ConditionalLock lock(mStateLock, std::this_thread::get_id() != mMainThreadId);
- std::for_each(displayModeConfigs.begin(), displayModeConfigs.end(),
- [&](const auto& config) REQUIRES(mStateLock) {
- const auto& displayModePtr = config.displayModePtr;
- if (const auto display =
- getDisplayDeviceLocked(displayModePtr->getPhysicalDisplayId());
- display->refreshRateConfigs().isModeAllowed(displayModePtr->getId())) {
- const auto event = config.signals.idle ? DisplayModeEvent::None
- : DisplayModeEvent::Changed;
- setDesiredActiveMode({displayModePtr, event});
- } else {
- ALOGV("Skipping disallowed mode %d for display %" PRId64,
- displayModePtr->getId().value(), display->getPhysicalId().value);
- }
- });
+ for (auto& request : modeRequests) {
+ const auto& modePtr = request.modePtr;
+ const auto display = getDisplayDeviceLocked(modePtr->getPhysicalDisplayId());
+
+ if (!display) continue;
+
+ if (display->refreshRateConfigs().isModeAllowed(modePtr->getId())) {
+ setDesiredActiveMode(std::move(request));
+ } else {
+ ALOGV("%s: Mode %d is disallowed for display %s", __func__, modePtr->getId().value(),
+ to_string(display->getId()).c_str());
+ }
+ }
}
void SurfaceFlinger::triggerOnFrameRateOverridesChanged() {
@@ -3701,7 +3702,7 @@
using TransactionReadiness = TransactionHandler::TransactionReadiness;
auto ready = TransactionReadiness::Ready;
flushState.transaction->traverseStatesWithBuffersWhileTrue([&](const layer_state_t& s) -> bool {
- sp<Layer> layer = Layer::fromHandle(s.surface).promote();
+ sp<Layer> layer = LayerHandle::getLayer(s.surface);
const auto& transaction = *flushState.transaction;
// check for barrier frames
if (s.bufferData->hasBarrier &&
@@ -3969,7 +3970,7 @@
setClientStateLocked(frameTimelineInfo, state, desiredPresentTime, isAutoTimestamp,
postTime, permissions, transactionId);
if ((flags & eAnimation) && state.state.surface) {
- if (const auto layer = fromHandle(state.state.surface).promote()) {
+ if (const auto layer = LayerHandle::getLayer(state.state.surface)) {
using LayerUpdateType = scheduler::LayerHistory::LayerUpdateType;
mScheduler->recordLayerHistory(layer.get(),
isAutoTimestamp ? 0 : desiredPresentTime,
@@ -4110,7 +4111,7 @@
uint32_t flags = 0;
sp<Layer> layer = nullptr;
if (s.surface) {
- layer = fromHandle(s.surface).promote();
+ layer = LayerHandle::getLayer(s.surface);
} else {
// The client may provide us a null handle. Treat it as if the layer was removed.
ALOGW("Attempt to set client state with a null layer handle");
@@ -4123,7 +4124,7 @@
return 0;
}
- ui::LayerStack oldLayerStack = layer->getLayerStack();
+ ui::LayerStack oldLayerStack = layer->getLayerStack(LayerVector::StateSet::Current);
// Only set by BLAST adapter layers
if (what & layer_state_t::eProducerDisconnect) {
@@ -4392,7 +4393,8 @@
// setTransactionCompletedListener
// if the layer has been parented on to a new display, update its transform hint.
- if (((flags & eTransformHintUpdateNeeded) == 0) && oldLayerStack != layer->getLayerStack()) {
+ if (((flags & eTransformHintUpdateNeeded) == 0) &&
+ oldLayerStack != layer->getLayerStack(LayerVector::StateSet::Current)) {
flags |= eTransformHintUpdateNeeded;
}
@@ -4416,7 +4418,7 @@
LayerCreationArgs mirrorArgs(args);
{
Mutex::Autolock _l(mStateLock);
- mirrorFrom = fromHandle(mirrorFromHandle).promote();
+ mirrorFrom = LayerHandle::getLayer(mirrorFromHandle);
if (!mirrorFrom) {
return NAME_NOT_FOUND;
}
@@ -4522,19 +4524,15 @@
}
args.addToRoot = args.addToRoot && callingThreadHasUnscopedSurfaceFlingerAccess();
- wp<Layer> parent = fromHandle(args.parentHandle.promote());
+ // We can safely promote the parent layer in binder thread because we have a strong reference
+ // to the layer's handle inside this scope.
+ sp<Layer> parent = LayerHandle::getLayer(args.parentHandle.promote());
if (args.parentHandle != nullptr && parent == nullptr) {
- ALOGE("Invalid parent handle %p.", args.parentHandle.promote().get());
+ ALOGE("Invalid parent handle %p", args.parentHandle.promote().get());
args.addToRoot = false;
}
- int parentId = -1;
- // We can safely promote the layer in binder thread because we have a strong reference
- // to the layer's handle inside this scope or we were passed in a sp reference to the layer.
- sp<Layer> parentSp = parent.promote();
- if (parentSp != nullptr) {
- parentId = parentSp->getSequence();
- }
+ const int parentId = parent ? parent->getSequence() : -1;
if (mTransactionTracing) {
mTransactionTracing->onLayerAdded(outResult.handle->localBinder(), layer->sequence,
args.name, args.flags, parentId);
@@ -4573,7 +4571,7 @@
setTransactionFlags(eTransactionNeeded);
}
-void SurfaceFlinger::onHandleDestroyed(BBinder* handle, sp<Layer>& layer) {
+void SurfaceFlinger::onHandleDestroyed(BBinder* handle, sp<Layer>& layer, uint32_t /* layerId */) {
Mutex::Autolock lock(mStateLock);
markLayerPendingRemovalLocked(layer);
mBufferCountTracker.remove(handle);
@@ -6178,7 +6176,7 @@
{
Mutex::Autolock lock(mStateLock);
- parent = fromHandle(args.layerHandle).promote();
+ parent = LayerHandle::getLayer(args.layerHandle);
if (parent == nullptr) {
ALOGE("captureLayers called with an invalid or removed parent");
return NAME_NOT_FOUND;
@@ -6209,7 +6207,7 @@
reqSize = ui::Size(crop.width() * args.frameScaleX, crop.height() * args.frameScaleY);
for (const auto& handle : args.excludeHandles) {
- sp<Layer> excludeLayer = fromHandle(handle).promote();
+ sp<Layer> excludeLayer = LayerHandle::getLayer(handle);
if (excludeLayer != nullptr) {
excludeLayers.emplace(excludeLayer);
} else {
@@ -6583,18 +6581,19 @@
}
}
-std::optional<DisplayModePtr> SurfaceFlinger::getPreferredDisplayMode(
+std::optional<ftl::NonNull<DisplayModePtr>> SurfaceFlinger::getPreferredDisplayMode(
PhysicalDisplayId displayId, DisplayModeId defaultModeId) const {
if (const auto schedulerMode = mScheduler->getPreferredDisplayMode();
schedulerMode && schedulerMode->getPhysicalDisplayId() == displayId) {
- return schedulerMode;
+ return ftl::as_non_null(schedulerMode);
}
return mPhysicalDisplays.get(displayId)
.transform(&PhysicalDisplay::snapshotRef)
.and_then([&](const display::DisplaySnapshot& snapshot) {
return snapshot.displayModes().get(defaultModeId);
- });
+ })
+ .transform(&ftl::as_non_null<const DisplayModePtr&>);
}
status_t SurfaceFlinger::setDesiredDisplayModeSpecsInternal(
@@ -6650,7 +6649,7 @@
return INVALID_OPERATION;
}
- setDesiredActiveMode({std::move(preferredMode), DisplayModeEvent::Changed});
+ setDesiredActiveMode({std::move(preferredMode), .emitEvent = true});
return NO_ERROR;
}
@@ -6724,10 +6723,6 @@
return NO_ERROR;
}
-wp<Layer> SurfaceFlinger::fromHandle(const sp<IBinder>& handle) const {
- return Layer::fromHandle(handle);
-}
-
void SurfaceFlinger::onLayerFirstRef(Layer* layer) {
mNumLayers++;
if (!layer->isRemovedFromCurrentState()) {
@@ -6892,7 +6887,7 @@
parent->addChild(layer);
}
- ui::LayerStack layerStack = layer->getLayerStack();
+ ui::LayerStack layerStack = layer->getLayerStack(LayerVector::StateSet::Current);
sp<const DisplayDevice> hintDisplay;
// Find the display that includes the layer.
for (const auto& [token, display] : mDisplays) {
@@ -7016,7 +7011,7 @@
for (const auto& mirrorDisplay : mirrorDisplays) {
// Set mirror layer's default layer stack to -1 so it doesn't end up rendered on a display
// accidentally.
- sp<Layer> rootMirrorLayer = Layer::fromHandle(mirrorDisplay.rootHandle).promote();
+ sp<Layer> rootMirrorLayer = LayerHandle::getLayer(mirrorDisplay.rootHandle);
rootMirrorLayer->setLayerStack(ui::LayerStack::fromValue(-1));
for (const auto& layer : mDrawingState.layersSortedByZ) {
if (layer->getLayerStack() != mirrorDisplay.layerStack ||
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index b65dec4..85c194b 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -29,6 +29,7 @@
#include <cutils/atomic.h>
#include <cutils/compiler.h>
#include <ftl/future.h>
+#include <ftl/non_null.h>
#include <gui/BufferQueue.h>
#include <gui/CompositorTiming.h>
#include <gui/FrameTimestamps.h>
@@ -272,6 +273,11 @@
void removeHierarchyFromOffscreenLayers(Layer* layer);
void removeFromOffscreenLayers(Layer* layer);
+ // Called when all clients have released all their references to
+ // this layer. The layer may still be kept alive by its parents but
+ // the client can no longer modify this layer directly.
+ void onHandleDestroyed(BBinder* handle, sp<Layer>& layer, uint32_t layerId);
+
// TODO: Remove atomic if move dtor to main thread CL lands
std::atomic<uint32_t> mNumClones;
@@ -279,12 +285,6 @@
return mTransactionCallbackInvoker;
}
- // Converts from a binder handle to a Layer
- // Returns nullptr if the handle does not point to an existing layer.
- // Otherwise, returns a weak reference so that callers off the main-thread
- // won't accidentally hold onto the last strong reference.
- wp<Layer> fromHandle(const sp<IBinder>& handle) const;
-
// If set, disables reusing client composition buffers. This can be set by
// debug.sf.disable_client_composition_cache
bool mDisableClientCompositionCache = false;
@@ -430,10 +430,6 @@
mCounterByLayerHandle GUARDED_BY(mLock);
};
- using ActiveModeInfo = DisplayDevice::ActiveModeInfo;
- using KernelIdleTimerController =
- ::android::scheduler::RefreshRateConfigs::KernelIdleTimerController;
-
enum class BootStage {
BOOTLOADER,
BOOTANIMATION,
@@ -628,16 +624,17 @@
// ISchedulerCallback overrides:
// Toggles hardware VSYNC by calling into HWC.
+ // TODO(b/241286146): Rename for self-explanatory API.
void setVsyncEnabled(bool) override;
- // Sets the desired display mode per display if allowed by policy .
- void requestDisplayModes(std::vector<scheduler::DisplayModeConfig>) override;
- // Called when kernel idle timer has expired. Used to update the refresh rate overlay.
+ void requestDisplayModes(std::vector<display::DisplayModeRequest>) override;
void kernelTimerChanged(bool expired) override;
- // Called when the frame rate override list changed to trigger an event.
void triggerOnFrameRateOverridesChanged() override;
// Toggles the kernel idle timer on or off depending the policy decisions around refresh rates.
void toggleKernelIdleTimer() REQUIRES(mStateLock);
+
+ using KernelIdleTimerController = scheduler::RefreshRateConfigs::KernelIdleTimerController;
+
// Get the controller and timeout that will help decide how the kernel idle timer will be
// configured and what value to use as the timeout.
std::pair<std::optional<KernelIdleTimerController>, std::chrono::milliseconds>
@@ -652,8 +649,8 @@
// Show spinner with refresh rate overlay
bool mRefreshRateOverlaySpinner = false;
- // Sets the desired active mode bit. It obtains the lock, and sets mDesiredActiveMode.
- void setDesiredActiveMode(const ActiveModeInfo& info) REQUIRES(mStateLock);
+ void setDesiredActiveMode(display::DisplayModeRequest&&) REQUIRES(mStateLock);
+
status_t setActiveModeFromBackdoor(const sp<display::DisplayToken>&, DisplayModeId);
// Sets the active mode and a new refresh rate in SF.
void updateInternalStateWithChangedMode() REQUIRES(mStateLock, kMainThreadContext);
@@ -672,9 +669,8 @@
// Returns the preferred mode for PhysicalDisplayId if the Scheduler has selected one for that
// display. Falls back to the display's defaultModeId otherwise.
- std::optional<DisplayModePtr> getPreferredDisplayMode(PhysicalDisplayId,
- DisplayModeId defaultModeId) const
- REQUIRES(mStateLock);
+ std::optional<ftl::NonNull<DisplayModePtr>> getPreferredDisplayMode(
+ PhysicalDisplayId, DisplayModeId defaultModeId) const REQUIRES(mStateLock);
status_t setDesiredDisplayModeSpecsInternal(const sp<DisplayDevice>&,
const scheduler::RefreshRateConfigs::PolicyVariant&)
@@ -771,10 +767,6 @@
status_t mirrorDisplay(DisplayId displayId, const LayerCreationArgs& args,
gui::CreateSurfaceResult& outResult);
- // called when all clients have released all their references to
- // this layer meaning it is entirely safe to destroy all
- // resources associated to this layer.
- void onHandleDestroyed(BBinder* handle, sp<Layer>& layer);
void markLayerPendingRemovalLocked(const sp<Layer>& layer);
// add a layer to SurfaceFlinger
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp
index 22d80ca..14384a7 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp
@@ -150,7 +150,7 @@
sp<IBinder> handle = defaultServiceManager()->checkService(
String16(mFdp.ConsumeRandomLengthString().c_str()));
- mFlinger->fromHandle(handle);
+ LayerHandle::getLayer(handle);
mFlinger->disableExpensiveRendering();
}
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
index e2ae4f4..e555867 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
@@ -34,6 +34,7 @@
#include "DisplayHardware/ComposerHal.h"
#include "FrameTimeline/FrameTimeline.h"
#include "FrameTracer/FrameTracer.h"
+#include "FrontEnd/LayerHandle.h"
#include "Layer.h"
#include "NativeWindowSurface.h"
#include "Scheduler/EventThread.h"
@@ -766,7 +767,7 @@
auto& mutableDisplays() { return mFlinger->mDisplays; }
auto& mutableDrawingState() { return mFlinger->mDrawingState; }
- auto fromHandle(const sp<IBinder> &handle) { return mFlinger->fromHandle(handle); }
+ auto fromHandle(const sp<IBinder> &handle) { return LayerHandle::getLayer(handle); }
~TestableSurfaceFlinger() {
mutableDisplays().clear();
@@ -780,7 +781,7 @@
private:
void setVsyncEnabled(bool) override {}
- void requestDisplayModes(std::vector<scheduler::DisplayModeConfig>) override {}
+ void requestDisplayModes(std::vector<display::DisplayModeRequest>) override {}
void kernelTimerChanged(bool) override {}
void triggerOnFrameRateOverridesChanged() override {}
diff --git a/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h b/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h
index 6ee4b9b..6e4bf2b 100644
--- a/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h
+++ b/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h
@@ -29,7 +29,7 @@
using android::Hwc2::mock::PowerAdvisor;
struct FakeDisplayInjectorArgs {
- uint8_t port = 255u;
+ PhysicalDisplayId displayId = PhysicalDisplayId::fromPort(255u);
HWDisplayId hwcDisplayId = 0;
bool isPrimary = true;
};
@@ -67,7 +67,7 @@
auto compositionDisplay = compositionengine::impl::
createDisplay(mFlinger.getCompositionEngine(),
compositionengine::DisplayCreationArgsBuilder()
- .setId(PhysicalDisplayId::fromPort(args.port))
+ .setId(args.displayId)
.setPixels(kResolution)
.setPowerAdvisor(&mPowerAdvisor)
.build());
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
index 620825f..924c5be 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
@@ -17,6 +17,9 @@
#undef LOG_TAG
#define LOG_TAG "SchedulerUnittests"
+#include <algorithm>
+#include <array>
+
#include <ftl/enum.h>
#include <ftl/fake_guard.h>
#include <gmock/gmock.h>
@@ -34,15 +37,17 @@
namespace hal = android::hardware::graphics::composer::hal;
-using SetPolicyResult = RefreshRateConfigs::SetPolicyResult;
-using LayerVoteType = RefreshRateConfigs::LayerVoteType;
using LayerRequirement = RefreshRateConfigs::LayerRequirement;
+using LayerVoteType = RefreshRateConfigs::LayerVoteType;
+using SetPolicyResult = RefreshRateConfigs::SetPolicyResult;
using mock::createDisplayMode;
struct TestableRefreshRateConfigs : RefreshRateConfigs {
- using RefreshRateConfigs::RefreshRateConfigs;
using RefreshRateConfigs::RefreshRateOrder;
+ using RefreshRateConfigs::RefreshRateRanking;
+
+ using RefreshRateConfigs::RefreshRateConfigs;
void setActiveModeId(DisplayModeId modeId) {
ftl::FakeGuard guard(kMainThreadContext);
@@ -74,12 +79,10 @@
return getMaxRefreshRateByPolicyLocked(getActiveModeItLocked()->second->getGroup());
}
- std::vector<RefreshRateRanking> getRefreshRatesByPolicy(
- std::optional<int> anchorGroupOpt, RefreshRateOrder refreshRateOrder) const {
+ RefreshRateRanking rankRefreshRates(std::optional<int> anchorGroupOpt,
+ RefreshRateOrder refreshRateOrder) const {
std::lock_guard lock(mLock);
- return RefreshRateConfigs::
- getRefreshRatesByPolicyLocked(anchorGroupOpt, refreshRateOrder,
- /*preferredDisplayModeOpt*/ std::nullopt);
+ return RefreshRateConfigs::rankRefreshRates(anchorGroupOpt, refreshRateOrder);
}
const std::vector<Fps>& knownFrameRates() const { return mKnownFrameRates; }
@@ -87,14 +90,25 @@
using RefreshRateConfigs::GetRankedRefreshRatesCache;
auto& mutableGetRankedRefreshRatesCache() { return mGetRankedRefreshRatesCache; }
- auto getRankedRefreshRatesAndSignals(const std::vector<LayerRequirement>& layers,
- GlobalSignals signals) const {
- return RefreshRateConfigs::getRankedRefreshRates(layers, signals);
+ auto getRankedRefreshRates(const std::vector<LayerRequirement>& layers,
+ GlobalSignals signals) const {
+ const auto result = RefreshRateConfigs::getRankedRefreshRates(layers, signals);
+
+ EXPECT_TRUE(std::is_sorted(result.ranking.begin(), result.ranking.end(),
+ ScoredRefreshRate::DescendingScore{}));
+
+ return result;
+ }
+
+ auto getRankedRefreshRatesAsPair(const std::vector<LayerRequirement>& layers,
+ GlobalSignals signals) const {
+ const auto [ranking, consideredSignals] = getRankedRefreshRates(layers, signals);
+ return std::make_pair(ranking, consideredSignals);
}
DisplayModePtr getBestRefreshRate(const std::vector<LayerRequirement>& layers = {},
GlobalSignals signals = {}) const {
- return getRankedRefreshRatesAndSignals(layers, signals).first.front().displayModePtr;
+ return getRankedRefreshRates(layers, signals).ranking.front().modePtr;
}
SetPolicyResult setPolicy(const PolicyVariant& policy) {
@@ -109,6 +123,8 @@
class RefreshRateConfigsTest : public testing::Test {
protected:
+ using RefreshRateOrder = TestableRefreshRateConfigs::RefreshRateOrder;
+
RefreshRateConfigsTest();
~RefreshRateConfigsTest();
@@ -1050,20 +1066,17 @@
// The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the
// different group.
TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId60);
- const std::vector<RefreshRateRanking>& expectedRefreshRates = {RefreshRateRanking{kMode90},
- RefreshRateRanking{kMode60},
- RefreshRateRanking{kMode30}};
- const std::vector<RefreshRateRanking>& refreshRates =
- configs.getRefreshRatesByPolicy(configs.getActiveMode().getGroup(),
- TestableRefreshRateConfigs::RefreshRateOrder::
- Descending);
+ const auto refreshRates = configs.rankRefreshRates(configs.getActiveMode().getGroup(),
+ RefreshRateOrder::Descending);
+ const std::array expectedRefreshRates = {kMode90, kMode60, kMode30};
ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
- EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr)
- << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue()
- << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue();
+ EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
+ << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
+ << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
}
}
@@ -1071,20 +1084,17 @@
// The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the
// different group.
TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId60);
- const std::vector<RefreshRateRanking>& expectedRefreshRates = {RefreshRateRanking{kMode30},
- RefreshRateRanking{kMode60},
- RefreshRateRanking{kMode90}};
- const std::vector<RefreshRateRanking>& refreshRates =
- configs.getRefreshRatesByPolicy(configs.getActiveMode().getGroup(),
- TestableRefreshRateConfigs::RefreshRateOrder::
- Ascending);
+ const auto refreshRates = configs.rankRefreshRates(configs.getActiveMode().getGroup(),
+ RefreshRateOrder::Ascending);
+ const std::array expectedRefreshRates = {kMode30, kMode60, kMode90};
ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
- EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr)
- << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue()
- << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue();
+ EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
+ << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
+ << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
}
}
@@ -1092,23 +1102,20 @@
// The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the
// different group.
TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId72);
- const std::vector<RefreshRateRanking>& expectedRefreshRates = {RefreshRateRanking{kMode30},
- RefreshRateRanking{kMode60},
- RefreshRateRanking{kMode90}};
EXPECT_EQ(SetPolicyResult::Changed,
configs.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}, {30_Hz, 90_Hz}}));
- const std::vector<RefreshRateRanking>& refreshRates =
- configs.getRefreshRatesByPolicy(/*anchorGroupOpt*/ std::nullopt,
- TestableRefreshRateConfigs::RefreshRateOrder::
- Ascending);
+ const auto refreshRates =
+ configs.rankRefreshRates(/*anchorGroupOpt*/ std::nullopt, RefreshRateOrder::Ascending);
+ const std::array expectedRefreshRates = {kMode30, kMode60, kMode90};
ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
- EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr)
- << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue()
- << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue();
+ EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
+ << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
+ << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
}
}
@@ -1116,47 +1123,48 @@
// The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the
// different group.
TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId72);
- const std::vector<RefreshRateRanking>& expectedRefreshRates = {RefreshRateRanking{kMode90},
- RefreshRateRanking{kMode60},
- RefreshRateRanking{kMode30}};
EXPECT_EQ(SetPolicyResult::Changed,
configs.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}, {30_Hz, 90_Hz}}));
- const std::vector<RefreshRateRanking>& refreshRates =
- configs.getRefreshRatesByPolicy(/*anchorGroupOpt*/ std::nullopt,
- TestableRefreshRateConfigs::RefreshRateOrder::
- Descending);
+ const auto refreshRates =
+ configs.rankRefreshRates(/*anchorGroupOpt*/ std::nullopt, RefreshRateOrder::Descending);
+ const std::array expectedRefreshRates = {kMode90, kMode60, kMode30};
ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
- EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr)
- << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue()
- << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue();
+ EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
+ << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
+ << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
}
}
TEST_F(RefreshRateConfigsTest, powerOnImminentConsidered) {
- RefreshRateConfigs configs(kModes_60_90, kModeId60);
- std::vector<RefreshRateRanking> expectedRefreshRates = {RefreshRateRanking{kMode90},
- RefreshRateRanking{kMode60}};
+ TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
auto [refreshRates, signals] = configs.getRankedRefreshRates({}, {});
EXPECT_FALSE(signals.powerOnImminent);
+
+ std::array expectedRefreshRates = {kMode90, kMode60};
ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
- EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr)
- << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue()
- << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue();
+ EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
+ << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
+ << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
}
- std::tie(refreshRates, signals) = configs.getRankedRefreshRates({}, {.powerOnImminent = true});
+ std::tie(refreshRates, signals) =
+ configs.getRankedRefreshRatesAsPair({}, {.powerOnImminent = true});
EXPECT_TRUE(signals.powerOnImminent);
+
ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
- EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr)
- << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue()
- << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue();
+ EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
+ << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
+ << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
}
std::vector<LayerRequirement> layers = {{.weight = 1.f}};
@@ -1166,34 +1174,38 @@
lr1.name = "60Hz ExplicitExactOrMultiple";
std::tie(refreshRates, signals) =
- configs.getRankedRefreshRates(layers, {.powerOnImminent = true});
+ configs.getRankedRefreshRatesAsPair(layers, {.powerOnImminent = true});
EXPECT_TRUE(signals.powerOnImminent);
+
ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
- EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr)
- << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue()
- << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue();
+ EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
+ << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
+ << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
}
- expectedRefreshRates = {RefreshRateRanking{kMode60}, RefreshRateRanking{kMode90}};
std::tie(refreshRates, signals) =
- configs.getRankedRefreshRates(layers, {.powerOnImminent = false});
+ configs.getRankedRefreshRatesAsPair(layers, {.powerOnImminent = false});
EXPECT_FALSE(signals.powerOnImminent);
+
+ expectedRefreshRates = {kMode60, kMode90};
ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
- EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr)
- << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue()
- << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue();
+ EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
+ << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
+ << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
}
}
TEST_F(RefreshRateConfigsTest, touchConsidered) {
- RefreshRateConfigs configs(kModes_60_90, kModeId60);
+ TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
auto [_, signals] = configs.getRankedRefreshRates({}, {});
EXPECT_FALSE(signals.touch);
- std::tie(std::ignore, signals) = configs.getRankedRefreshRates({}, {.touch = true});
+ std::tie(std::ignore, signals) = configs.getRankedRefreshRatesAsPair({}, {.touch = true});
EXPECT_TRUE(signals.touch);
std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
@@ -1206,7 +1218,7 @@
lr2.vote = LayerVoteType::Heuristic;
lr2.desiredRefreshRate = 60_Hz;
lr2.name = "60Hz Heuristic";
- std::tie(std::ignore, signals) = configs.getRankedRefreshRates(layers, {.touch = true});
+ std::tie(std::ignore, signals) = configs.getRankedRefreshRatesAsPair(layers, {.touch = true});
EXPECT_TRUE(signals.touch);
lr1.vote = LayerVoteType::ExplicitDefault;
@@ -1215,7 +1227,7 @@
lr2.vote = LayerVoteType::Heuristic;
lr2.desiredRefreshRate = 60_Hz;
lr2.name = "60Hz Heuristic";
- std::tie(std::ignore, signals) = configs.getRankedRefreshRates(layers, {.touch = true});
+ std::tie(std::ignore, signals) = configs.getRankedRefreshRatesAsPair(layers, {.touch = true});
EXPECT_FALSE(signals.touch);
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -1224,7 +1236,7 @@
lr2.vote = LayerVoteType::Heuristic;
lr2.desiredRefreshRate = 60_Hz;
lr2.name = "60Hz Heuristic";
- std::tie(std::ignore, signals) = configs.getRankedRefreshRates(layers, {.touch = true});
+ std::tie(std::ignore, signals) = configs.getRankedRefreshRatesAsPair(layers, {.touch = true});
EXPECT_TRUE(signals.touch);
lr1.vote = LayerVoteType::ExplicitDefault;
@@ -1233,7 +1245,7 @@
lr2.vote = LayerVoteType::Heuristic;
lr2.desiredRefreshRate = 60_Hz;
lr2.name = "60Hz Heuristic";
- std::tie(std::ignore, signals) = configs.getRankedRefreshRates(layers, {.touch = true});
+ std::tie(std::ignore, signals) = configs.getRankedRefreshRatesAsPair(layers, {.touch = true});
EXPECT_FALSE(signals.touch);
}
@@ -1352,7 +1364,7 @@
const auto [mode, signals] =
configs.getRankedRefreshRates(layers, {.touch = true, .idle = true});
- EXPECT_EQ(mode.begin()->displayModePtr, kMode60);
+ EXPECT_EQ(mode.begin()->modePtr, kMode60);
EXPECT_FALSE(signals.touch);
}
@@ -1407,18 +1419,15 @@
lr5.name = "30Hz";
lr5.focused = true;
- std::vector<RefreshRateRanking> expectedRankings = {
- RefreshRateRanking{kMode120}, RefreshRateRanking{kMode90}, RefreshRateRanking{kMode72},
- RefreshRateRanking{kMode60}, RefreshRateRanking{kMode30},
- };
+ std::array expectedRanking = {kMode120, kMode90, kMode72, kMode60, kMode30};
+ auto actualRanking = configs.getRankedRefreshRates(layers, {}).ranking;
- std::vector<RefreshRateRanking> actualOrder =
- configs.getRankedRefreshRatesAndSignals(layers, {}).first;
- ASSERT_EQ(expectedRankings.size(), actualOrder.size());
- for (size_t i = 0; i < expectedRankings.size(); ++i) {
- EXPECT_EQ(expectedRankings[i].displayModePtr, actualOrder[i].displayModePtr)
- << "Expected fps " << expectedRankings[i].displayModePtr->getFps().getIntValue()
- << " Actual fps " << actualOrder[i].displayModePtr->getFps().getIntValue();
+ ASSERT_EQ(expectedRanking.size(), actualRanking.size());
+
+ for (size_t i = 0; i < expectedRanking.size(); ++i) {
+ EXPECT_EQ(expectedRanking[i], actualRanking[i].modePtr)
+ << "Expected fps " << expectedRanking[i]->getFps().getIntValue() << " Actual fps "
+ << actualRanking[i].modePtr->getFps().getIntValue();
}
lr1.vote = LayerVoteType::Max;
@@ -1436,18 +1445,15 @@
lr5.desiredRefreshRate = 120_Hz;
lr5.name = "120Hz";
- expectedRankings = {
- RefreshRateRanking{kMode120}, RefreshRateRanking{kMode90}, RefreshRateRanking{kMode72},
- RefreshRateRanking{kMode60}, RefreshRateRanking{kMode30},
- };
+ expectedRanking = {kMode120, kMode90, kMode72, kMode60, kMode30};
+ actualRanking = configs.getRankedRefreshRates(layers, {}).ranking;
- actualOrder = configs.getRankedRefreshRatesAndSignals(layers, {}).first;
+ ASSERT_EQ(expectedRanking.size(), actualRanking.size());
- ASSERT_EQ(expectedRankings.size(), actualOrder.size());
- for (size_t i = 0; i < expectedRankings.size(); ++i) {
- EXPECT_EQ(expectedRankings[i].displayModePtr, actualOrder[i].displayModePtr)
- << "Expected fps " << expectedRankings[i].displayModePtr->getFps().getIntValue()
- << " Actual fps " << actualOrder[i].displayModePtr->getFps().getIntValue();
+ for (size_t i = 0; i < expectedRanking.size(); ++i) {
+ EXPECT_EQ(expectedRanking[i], actualRanking[i].modePtr)
+ << "Expected fps " << expectedRanking[i]->getFps().getIntValue() << " Actual fps "
+ << actualRanking[i].modePtr->getFps().getIntValue();
}
lr1.vote = LayerVoteType::Heuristic;
@@ -1463,17 +1469,15 @@
lr5.desiredRefreshRate = 72_Hz;
lr5.name = "72Hz";
- expectedRankings = {
- RefreshRateRanking{kMode30}, RefreshRateRanking{kMode60}, RefreshRateRanking{kMode90},
- RefreshRateRanking{kMode120}, RefreshRateRanking{kMode72},
- };
+ expectedRanking = {kMode30, kMode60, kMode90, kMode120, kMode72};
+ actualRanking = configs.getRankedRefreshRates(layers, {}).ranking;
- actualOrder = configs.getRankedRefreshRatesAndSignals(layers, {}).first;
- ASSERT_EQ(expectedRankings.size(), actualOrder.size());
- for (size_t i = 0; i < expectedRankings.size(); ++i) {
- EXPECT_EQ(expectedRankings[i].displayModePtr, actualOrder[i].displayModePtr)
- << "Expected fps " << expectedRankings[i].displayModePtr->getFps().getIntValue()
- << " Actual fps " << actualOrder[i].displayModePtr->getFps().getIntValue();
+ ASSERT_EQ(expectedRanking.size(), actualRanking.size());
+
+ for (size_t i = 0; i < expectedRanking.size(); ++i) {
+ EXPECT_EQ(expectedRanking[i], actualRanking[i].modePtr)
+ << "Expected fps " << expectedRanking[i]->getFps().getIntValue() << " Actual fps "
+ << actualRanking[i].modePtr->getFps().getIntValue();
}
lr1.desiredRefreshRate = 120_Hz;
@@ -1492,17 +1496,15 @@
lr5.desiredRefreshRate = 120_Hz;
lr5.name = "120Hz-2";
- expectedRankings = {
- RefreshRateRanking{kMode90}, RefreshRateRanking{kMode60}, RefreshRateRanking{kMode120},
- RefreshRateRanking{kMode72}, RefreshRateRanking{kMode30},
- };
+ expectedRanking = {kMode90, kMode60, kMode120, kMode72, kMode30};
+ actualRanking = configs.getRankedRefreshRates(layers, {}).ranking;
- actualOrder = configs.getRankedRefreshRatesAndSignals(layers, {}).first;
- ASSERT_EQ(expectedRankings.size(), actualOrder.size());
- for (size_t i = 0; i < expectedRankings.size(); ++i) {
- EXPECT_EQ(expectedRankings[i].displayModePtr, actualOrder[i].displayModePtr)
- << "Expected fps " << expectedRankings[i].displayModePtr->getFps().getIntValue()
- << " Actual fps " << actualOrder[i].displayModePtr->getFps().getIntValue();
+ ASSERT_EQ(expectedRanking.size(), actualRanking.size());
+
+ for (size_t i = 0; i < expectedRanking.size(); ++i) {
+ EXPECT_EQ(expectedRanking[i], actualRanking[i].modePtr)
+ << "Expected fps " << expectedRanking[i]->getFps().getIntValue() << " Actual fps "
+ << actualRanking[i].modePtr->getFps().getIntValue();
}
}
@@ -1513,8 +1515,8 @@
EXPECT_EQ(SetPolicyResult::Changed,
configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}, {60_Hz, 90_Hz}}));
- const auto [mode, signals] = configs.getRankedRefreshRatesAndSignals({}, {});
- EXPECT_EQ(mode.front().displayModePtr, kMode90);
+ const auto [ranking, signals] = configs.getRankedRefreshRates({}, {});
+ EXPECT_EQ(ranking.front().modePtr, kMode90);
EXPECT_FALSE(signals.touch);
std::vector<LayerRequirement> layers = {{.weight = 1.f}};
@@ -1892,13 +1894,12 @@
layers[0].vote = voteType;
layers[0].desiredRefreshRate = 90_Hz;
- const auto [refreshRate, signals] =
- configs.getRankedRefreshRatesAndSignals(layers,
- {.touch = touchActive, .idle = true});
+ const auto [ranking, signals] =
+ configs.getRankedRefreshRates(layers, {.touch = touchActive, .idle = true});
// Refresh rate will be chosen by either touch state or idle state.
EXPECT_EQ(!touchActive, signals.idle);
- return refreshRate.front().displayModePtr->getId();
+ return ranking.front().modePtr->getId();
};
EXPECT_EQ(SetPolicyResult::Changed,
@@ -2059,12 +2060,13 @@
const auto args = std::make_pair(std::vector<LayerRequirement>{},
GlobalSignals{.touch = true, .idle = true});
- const auto result = std::make_pair(std::vector<RefreshRateRanking>{RefreshRateRanking{kMode90}},
- GlobalSignals{.touch = true});
+ const RefreshRateConfigs::RankedRefreshRates result = {{RefreshRateConfigs::ScoredRefreshRate{
+ kMode90}},
+ {.touch = true}};
configs.mutableGetRankedRefreshRatesCache() = {args, result};
- EXPECT_EQ(result, configs.getRankedRefreshRatesAndSignals(args.first, args.second));
+ EXPECT_EQ(result, configs.getRankedRefreshRates(args.first, args.second));
}
TEST_F(RefreshRateConfigsTest, getBestRefreshRate_WritesCache) {
@@ -2075,7 +2077,7 @@
std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
RefreshRateConfigs::GlobalSignals globalSignals{.touch = true, .idle = true};
- const auto result = configs.getRankedRefreshRatesAndSignals(layers, globalSignals);
+ const auto result = configs.getRankedRefreshRates(layers, globalSignals);
const auto& cache = configs.mutableGetRankedRefreshRatesCache();
ASSERT_TRUE(cache);
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 392398d..147433b 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -43,8 +43,6 @@
using MockLayer = android::mock::MockLayer;
using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector;
-constexpr PhysicalDisplayId PHYSICAL_DISPLAY_ID = PhysicalDisplayId::fromPort(255u);
-
class SchedulerTest : public testing::Test {
protected:
class MockEventThreadConnection : public android::EventThreadConnection {
@@ -61,14 +59,28 @@
SchedulerTest();
- static inline const DisplayModePtr kMode60_1 = createDisplayMode(DisplayModeId(0), 60_Hz);
- static inline const DisplayModePtr kMode120_1 = createDisplayMode(DisplayModeId(1), 120_Hz);
- static inline const DisplayModePtr kMode60_2 = createDisplayMode(DisplayModeId(2), 60_Hz);
- static inline const DisplayModePtr kMode120_2 = createDisplayMode(DisplayModeId(3), 120_Hz);
- static inline const DisplayModePtr kMode60_3 = createDisplayMode(DisplayModeId(4), 60_Hz);
+ static constexpr PhysicalDisplayId kDisplayId1 = PhysicalDisplayId::fromPort(255u);
+ static inline const DisplayModePtr kDisplay1Mode60 =
+ createDisplayMode(kDisplayId1, DisplayModeId(0), 60_Hz);
+ static inline const DisplayModePtr kDisplay1Mode120 =
+ createDisplayMode(kDisplayId1, DisplayModeId(1), 120_Hz);
+ static inline const DisplayModes kDisplay1Modes = makeModes(kDisplay1Mode60, kDisplay1Mode120);
+
+ static constexpr PhysicalDisplayId kDisplayId2 = PhysicalDisplayId::fromPort(254u);
+ static inline const DisplayModePtr kDisplay2Mode60 =
+ createDisplayMode(kDisplayId2, DisplayModeId(0), 60_Hz);
+ static inline const DisplayModePtr kDisplay2Mode120 =
+ createDisplayMode(kDisplayId2, DisplayModeId(1), 120_Hz);
+ static inline const DisplayModes kDisplay2Modes = makeModes(kDisplay2Mode60, kDisplay2Mode120);
+
+ static constexpr PhysicalDisplayId kDisplayId3 = PhysicalDisplayId::fromPort(253u);
+ static inline const DisplayModePtr kDisplay3Mode60 =
+ createDisplayMode(kDisplayId3, DisplayModeId(0), 60_Hz);
+ static inline const DisplayModes kDisplay3Modes = makeModes(kDisplay3Mode60);
std::shared_ptr<RefreshRateConfigs> mConfigs =
- std::make_shared<RefreshRateConfigs>(makeModes(kMode60_1), kMode60_1->getId());
+ std::make_shared<RefreshRateConfigs>(makeModes(kDisplay1Mode60),
+ kDisplay1Mode60->getId());
mock::SchedulerCallback mSchedulerCallback;
TestableScheduler* mScheduler = new TestableScheduler{mConfigs, mSchedulerCallback};
@@ -114,7 +126,7 @@
// The EXPECT_CALLS make sure we don't call the functions on the subsequent event threads.
EXPECT_CALL(*mEventThread, onHotplugReceived(_, _)).Times(0);
- mScheduler->onHotplugReceived(handle, PHYSICAL_DISPLAY_ID, false);
+ mScheduler->onHotplugReceived(handle, kDisplayId1, false);
EXPECT_CALL(*mEventThread, onScreenAcquired()).Times(0);
mScheduler->onScreenAcquired(handle);
@@ -138,8 +150,8 @@
ASSERT_EQ(mEventThreadConnection, connection);
EXPECT_TRUE(mScheduler->getEventConnection(mConnectionHandle));
- EXPECT_CALL(*mEventThread, onHotplugReceived(PHYSICAL_DISPLAY_ID, false)).Times(1);
- mScheduler->onHotplugReceived(mConnectionHandle, PHYSICAL_DISPLAY_ID, false);
+ EXPECT_CALL(*mEventThread, onHotplugReceived(kDisplayId1, false)).Times(1);
+ mScheduler->onHotplugReceived(mConnectionHandle, kDisplayId1, false);
EXPECT_CALL(*mEventThread, onScreenAcquired()).Times(1);
mScheduler->onScreenAcquired(mConnectionHandle);
@@ -185,8 +197,7 @@
ASSERT_EQ(1u, mScheduler->layerHistorySize());
mScheduler->setRefreshRateConfigs(
- std::make_shared<RefreshRateConfigs>(makeModes(kMode60_1, kMode120_1),
- kMode60_1->getId()));
+ std::make_shared<RefreshRateConfigs>(kDisplay1Modes, kDisplay1Mode60->getId()));
ASSERT_EQ(0u, mScheduler->getNumActiveLayers());
mScheduler->recordLayerHistory(layer.get(), 0, LayerHistory::LayerUpdateType::Buffer);
@@ -203,7 +214,7 @@
TEST_F(SchedulerTest, onNonPrimaryDisplayModeChanged_invalidParameters) {
const auto mode = DisplayMode::Builder(hal::HWConfigId(0))
.setId(DisplayModeId(111))
- .setPhysicalDisplayId(PHYSICAL_DISPLAY_ID)
+ .setPhysicalDisplayId(kDisplayId1)
.setVsyncPeriod(111111)
.build();
@@ -225,14 +236,15 @@
}
MATCHER(Is120Hz, "") {
- return isApproxEqual(arg.front().displayModePtr->getFps(), 120_Hz);
+ return isApproxEqual(arg.front().modePtr->getFps(), 120_Hz);
}
TEST_F(SchedulerTest, chooseRefreshRateForContentSelectsMaxRefreshRate) {
- auto display =
- mFakeDisplayInjector.injectInternalDisplay([&](FakeDisplayDeviceInjector& injector) {
- injector.setDisplayModes(makeModes(kMode60_1, kMode120_1), kMode60_1->getId());
- });
+ const auto display = mFakeDisplayInjector.injectInternalDisplay(
+ [&](FakeDisplayDeviceInjector& injector) {
+ injector.setDisplayModes(kDisplay1Modes, kDisplay1Mode60->getId());
+ },
+ {.displayId = kDisplayId1});
mScheduler->registerDisplay(display);
mScheduler->setRefreshRateConfigs(display->holdRefreshRateConfigs());
@@ -256,11 +268,12 @@
mScheduler->chooseRefreshRateForContent();
}
-TEST_F(SchedulerTest, getBestDisplayMode_singleDisplay) {
- auto display =
- mFakeDisplayInjector.injectInternalDisplay([&](FakeDisplayDeviceInjector& injector) {
- injector.setDisplayModes(makeModes(kMode60_1, kMode120_1), kMode60_1->getId());
- });
+TEST_F(SchedulerTest, chooseDisplayModesSingleDisplay) {
+ const auto display = mFakeDisplayInjector.injectInternalDisplay(
+ [&](FakeDisplayDeviceInjector& injector) {
+ injector.setDisplayModes(kDisplay1Modes, kDisplay1Mode60->getId());
+ },
+ {.displayId = kDisplayId1});
mScheduler->registerDisplay(display);
@@ -270,115 +283,125 @@
GlobalSignals globalSignals = {.idle = true};
mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
- std::vector<DisplayModeConfig> displayModeConfigs = mScheduler->getBestDisplayModeConfigs();
- ASSERT_EQ(1ul, displayModeConfigs.size());
- EXPECT_EQ(displayModeConfigs.front().displayModePtr, kMode60_1);
- EXPECT_EQ(displayModeConfigs.front().signals, globalSignals);
+ using DisplayModeChoice = TestableScheduler::DisplayModeChoice;
+
+ auto modeChoices = mScheduler->chooseDisplayModes();
+ ASSERT_EQ(1u, modeChoices.size());
+
+ auto choice = modeChoices.get(kDisplayId1);
+ ASSERT_TRUE(choice);
+ EXPECT_EQ(choice->get(), DisplayModeChoice(kDisplay1Mode60, globalSignals));
globalSignals = {.idle = false};
mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
- displayModeConfigs = mScheduler->getBestDisplayModeConfigs();
- ASSERT_EQ(1ul, displayModeConfigs.size());
- EXPECT_EQ(displayModeConfigs.front().displayModePtr, kMode120_1);
- EXPECT_EQ(displayModeConfigs.front().signals, globalSignals);
+
+ modeChoices = mScheduler->chooseDisplayModes();
+ ASSERT_EQ(1u, modeChoices.size());
+
+ choice = modeChoices.get(kDisplayId1);
+ ASSERT_TRUE(choice);
+ EXPECT_EQ(choice->get(), DisplayModeChoice(kDisplay1Mode120, globalSignals));
globalSignals = {.touch = true};
mScheduler->replaceTouchTimer(10);
mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
- displayModeConfigs = mScheduler->getBestDisplayModeConfigs();
- ASSERT_EQ(1ul, displayModeConfigs.size());
- EXPECT_EQ(displayModeConfigs.front().displayModePtr, kMode120_1);
- EXPECT_EQ(displayModeConfigs.front().signals, globalSignals);
- mScheduler->unregisterDisplay(display->getPhysicalId());
+ modeChoices = mScheduler->chooseDisplayModes();
+ ASSERT_EQ(1u, modeChoices.size());
+
+ choice = modeChoices.get(kDisplayId1);
+ ASSERT_TRUE(choice);
+ EXPECT_EQ(choice->get(), DisplayModeChoice(kDisplay1Mode120, globalSignals));
+
+ mScheduler->unregisterDisplay(kDisplayId1);
EXPECT_TRUE(mScheduler->mutableDisplays().empty());
}
-TEST_F(SchedulerTest, getBestDisplayModes_multipleDisplays) {
- auto display1 =
- mFakeDisplayInjector.injectInternalDisplay([&](FakeDisplayDeviceInjector& injector) {
- injector.setDisplayModes(makeModes(kMode60_1, kMode120_1), kMode60_1->getId());
- });
- auto display2 = mFakeDisplayInjector.injectInternalDisplay(
+TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) {
+ const auto display1 = mFakeDisplayInjector.injectInternalDisplay(
[&](FakeDisplayDeviceInjector& injector) {
- injector.setDisplayModes(makeModes(kMode60_2, kMode120_2), kMode60_2->getId());
+ injector.setDisplayModes(kDisplay1Modes, kDisplay1Mode60->getId());
},
- {.port = 253u, .hwcDisplayId = 42, .isPrimary = false});
+ {.displayId = kDisplayId1, .hwcDisplayId = 42, .isPrimary = true});
+ const auto display2 = mFakeDisplayInjector.injectInternalDisplay(
+ [&](FakeDisplayDeviceInjector& injector) {
+ injector.setDisplayModes(kDisplay2Modes, kDisplay2Mode60->getId());
+ },
+ {.displayId = kDisplayId2, .hwcDisplayId = 41, .isPrimary = false});
mScheduler->registerDisplay(display1);
mScheduler->registerDisplay(display2);
- std::vector<sp<DisplayDevice>> expectedDisplays = {display1, display2};
- std::vector<RefreshRateConfigs::LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
- GlobalSignals globalSignals = {.idle = true};
- std::vector<DisplayModeConfig> expectedConfigs = {DisplayModeConfig{globalSignals, kMode60_1},
- DisplayModeConfig{globalSignals, kMode60_2}};
+ using DisplayModeChoice = TestableScheduler::DisplayModeChoice;
+ TestableScheduler::DisplayModeChoiceMap expectedChoices;
- mScheduler->setContentRequirements(layers);
- mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
- std::vector<DisplayModeConfig> displayModeConfigs = mScheduler->getBestDisplayModeConfigs();
- ASSERT_EQ(displayModeConfigs.size(), expectedConfigs.size());
- for (size_t i = 0; i < expectedConfigs.size(); ++i) {
- EXPECT_EQ(expectedConfigs.at(i).displayModePtr, displayModeConfigs.at(i).displayModePtr)
- << "Expected fps " << expectedConfigs.at(i).displayModePtr->getFps().getIntValue()
- << " Actual fps "
- << displayModeConfigs.at(i).displayModePtr->getFps().getIntValue();
- EXPECT_EQ(globalSignals, displayModeConfigs.at(i).signals);
+ {
+ const GlobalSignals globalSignals = {.idle = true};
+ expectedChoices =
+ ftl::init::map<const PhysicalDisplayId&,
+ DisplayModeChoice>(kDisplayId1, kDisplay1Mode60,
+ globalSignals)(kDisplayId2, kDisplay2Mode60,
+ globalSignals);
+
+ std::vector<RefreshRateConfigs::LayerRequirement> layers = {{.weight = 1.f},
+ {.weight = 1.f}};
+ mScheduler->setContentRequirements(layers);
+ mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
+
+ const auto actualChoices = mScheduler->chooseDisplayModes();
+ EXPECT_EQ(expectedChoices, actualChoices);
}
+ {
+ const GlobalSignals globalSignals = {.idle = false};
+ expectedChoices =
+ ftl::init::map<const PhysicalDisplayId&,
+ DisplayModeChoice>(kDisplayId1, kDisplay1Mode120,
+ globalSignals)(kDisplayId2, kDisplay2Mode120,
+ globalSignals);
- expectedConfigs = std::vector<DisplayModeConfig>{DisplayModeConfig{globalSignals, kMode120_1},
- DisplayModeConfig{globalSignals, kMode120_2}};
+ mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
- globalSignals = {.idle = false};
- mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
- displayModeConfigs = mScheduler->getBestDisplayModeConfigs();
- ASSERT_EQ(expectedConfigs.size(), displayModeConfigs.size());
- for (size_t i = 0; i < expectedConfigs.size(); ++i) {
- EXPECT_EQ(expectedConfigs.at(i).displayModePtr, displayModeConfigs.at(i).displayModePtr)
- << "Expected fps " << expectedConfigs.at(i).displayModePtr->getFps().getIntValue()
- << " Actual fps "
- << displayModeConfigs.at(i).displayModePtr->getFps().getIntValue();
- EXPECT_EQ(globalSignals, displayModeConfigs.at(i).signals);
+ const auto actualChoices = mScheduler->chooseDisplayModes();
+ EXPECT_EQ(expectedChoices, actualChoices);
}
+ {
+ const GlobalSignals globalSignals = {.touch = true};
+ mScheduler->replaceTouchTimer(10);
+ mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
- globalSignals = {.touch = true};
- mScheduler->replaceTouchTimer(10);
- mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
- displayModeConfigs = mScheduler->getBestDisplayModeConfigs();
- ASSERT_EQ(expectedConfigs.size(), displayModeConfigs.size());
- for (size_t i = 0; i < expectedConfigs.size(); ++i) {
- EXPECT_EQ(expectedConfigs.at(i).displayModePtr, displayModeConfigs.at(i).displayModePtr)
- << "Expected fps " << expectedConfigs.at(i).displayModePtr->getFps().getIntValue()
- << " Actual fps "
- << displayModeConfigs.at(i).displayModePtr->getFps().getIntValue();
- EXPECT_EQ(globalSignals, displayModeConfigs.at(i).signals);
+ expectedChoices =
+ ftl::init::map<const PhysicalDisplayId&,
+ DisplayModeChoice>(kDisplayId1, kDisplay1Mode120,
+ globalSignals)(kDisplayId2, kDisplay2Mode120,
+ globalSignals);
+
+ const auto actualChoices = mScheduler->chooseDisplayModes();
+ EXPECT_EQ(expectedChoices, actualChoices);
}
+ {
+ // This display does not support 120 Hz, so we should choose 60 Hz despite the touch signal.
+ const auto display3 = mFakeDisplayInjector.injectInternalDisplay(
+ [&](FakeDisplayDeviceInjector& injector) {
+ injector.setDisplayModes(kDisplay3Modes, kDisplay3Mode60->getId());
+ },
+ {.displayId = kDisplayId3, .hwcDisplayId = 40, .isPrimary = false});
- // Filters out the 120Hz as it's not present on the display3, even with touch active
- // we select 60Hz here.
- auto display3 = mFakeDisplayInjector.injectInternalDisplay(
- [&](FakeDisplayDeviceInjector& injector) {
- injector.setDisplayModes(makeModes(kMode60_3), kMode60_3->getId());
- },
- {.port = 252u, .hwcDisplayId = 41, .isPrimary = false});
+ mScheduler->registerDisplay(display3);
- mScheduler->registerDisplay(display3);
+ const GlobalSignals globalSignals = {.touch = true};
+ mScheduler->replaceTouchTimer(10);
+ mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
- expectedDisplays = {display1, display2, display3};
- globalSignals = {.touch = true};
- mScheduler->replaceTouchTimer(10);
- expectedConfigs = std::vector<DisplayModeConfig>{DisplayModeConfig{globalSignals, kMode60_1},
- DisplayModeConfig{globalSignals, kMode60_2},
- DisplayModeConfig{globalSignals, kMode60_3}};
- mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
- displayModeConfigs = mScheduler->getBestDisplayModeConfigs();
- ASSERT_EQ(expectedConfigs.size(), displayModeConfigs.size());
- for (size_t i = 0; i < expectedConfigs.size(); ++i) {
- EXPECT_EQ(expectedConfigs.at(i).displayModePtr, displayModeConfigs.at(i).displayModePtr)
- << "Expected fps " << expectedConfigs.at(i).displayModePtr->getFps().getIntValue()
- << " Actual fps "
- << displayModeConfigs.at(i).displayModePtr->getFps().getIntValue();
- EXPECT_EQ(globalSignals, displayModeConfigs.at(i).signals);
+ expectedChoices =
+ ftl::init::map<const PhysicalDisplayId&,
+ DisplayModeChoice>(kDisplayId1, kDisplay1Mode60,
+ globalSignals)(kDisplayId2, kDisplay2Mode60,
+ globalSignals)(kDisplayId3,
+ kDisplay3Mode60,
+ globalSignals);
+
+ const auto actualChoices = mScheduler->chooseDisplayModes();
+ EXPECT_EQ(expectedChoices, actualChoices);
}
}
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 68df987..26b2b67 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -107,9 +107,12 @@
mPolicy.contentRequirements = std::move(layers);
}
- std::vector<DisplayModeConfig> getBestDisplayModeConfigs() {
+ using Scheduler::DisplayModeChoice;
+ using Scheduler::DisplayModeChoiceMap;
+
+ DisplayModeChoiceMap chooseDisplayModes() {
std::lock_guard<std::mutex> lock(mPolicyLock);
- return Scheduler::getBestDisplayModeConfigs();
+ return Scheduler::chooseDisplayModes();
}
void dispatchCachedReportedMode() {
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 89812aa..7f471bc 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -34,6 +34,7 @@
#include "FakeVsyncConfiguration.h"
#include "FrameTracer/FrameTracer.h"
#include "FrontEnd/LayerCreationArgs.h"
+#include "FrontEnd/LayerHandle.h"
#include "Layer.h"
#include "NativeWindowSurface.h"
#include "Scheduler/MessageQueue.h"
@@ -530,9 +531,7 @@
auto& mutablePrimaryHwcDisplayId() { return getHwComposer().mPrimaryHwcDisplayId; }
auto& mutableActiveDisplayId() { return mFlinger->mActiveDisplayId; }
- auto fromHandle(const sp<IBinder>& handle) {
- return mFlinger->fromHandle(handle);
- }
+ auto fromHandle(const sp<IBinder>& handle) { return LayerHandle::getLayer(handle); }
~TestableSurfaceFlinger() {
// All these pointer and container clears help ensure that GMock does
diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
index de84faa..9888f00 100644
--- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
@@ -294,7 +294,7 @@
TEST_F(TransactionApplicationTest, FromHandle) {
sp<IBinder> badHandle;
auto ret = mFlinger.fromHandle(badHandle);
- EXPECT_EQ(nullptr, ret.promote().get());
+ EXPECT_EQ(nullptr, ret.get());
}
class LatchUnsignaledTest : public TransactionApplicationTest {
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
index a83ecbc..c78b6bd 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
@@ -33,4 +33,9 @@
.build();
}
+inline DisplayModePtr createDisplayMode(PhysicalDisplayId displayId, DisplayModeId modeId,
+ Fps refreshRate) {
+ return createDisplayMode(modeId, refreshRate, {}, {}, displayId);
+}
+
} // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
index 8af2dfa..7d4b159 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
@@ -24,14 +24,14 @@
struct SchedulerCallback final : ISchedulerCallback {
MOCK_METHOD(void, setVsyncEnabled, (bool), (override));
- MOCK_METHOD(void, requestDisplayModes, (std::vector<scheduler::DisplayModeConfig>), (override));
+ MOCK_METHOD(void, requestDisplayModes, (std::vector<display::DisplayModeRequest>), (override));
MOCK_METHOD(void, kernelTimerChanged, (bool), (override));
MOCK_METHOD(void, triggerOnFrameRateOverridesChanged, (), (override));
};
struct NoOpSchedulerCallback final : ISchedulerCallback {
void setVsyncEnabled(bool) override {}
- void requestDisplayModes(std::vector<scheduler::DisplayModeConfig>) override {}
+ void requestDisplayModes(std::vector<display::DisplayModeRequest>) override {}
void kernelTimerChanged(bool) override {}
void triggerOnFrameRateOverridesChanged() override {}
};