Merge "SF FlagManager: ignore debug override during tests" into main
diff --git a/cmds/ip-up-vpn/Android.bp b/cmds/ip-up-vpn/Android.bp
deleted file mode 100644
index c746f7f..0000000
--- a/cmds/ip-up-vpn/Android.bp
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2011 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-cc_binary {
- name: "ip-up-vpn",
-
- srcs: ["ip-up-vpn.c"],
- cflags: [
- "-Wall",
- "-Werror",
- ],
- shared_libs: [
- "libcutils",
- "liblog",
- ],
-}
diff --git a/cmds/ip-up-vpn/ip-up-vpn.c b/cmds/ip-up-vpn/ip-up-vpn.c
deleted file mode 100644
index 71f0837..0000000
--- a/cmds/ip-up-vpn/ip-up-vpn.c
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "ip-up-vpn"
-
-#include <arpa/inet.h>
-#include <errno.h>
-#include <linux/if.h>
-#include <linux/route.h>
-#include <netinet/in.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-
-#include <log/log.h>
-
-#define DIR "/data/misc/vpn/"
-
-static const char *env(const char *name) {
- const char *value = getenv(name);
- return value ? value : "";
-}
-
-static int set_address(struct sockaddr *sa, const char *address) {
- sa->sa_family = AF_INET;
- errno = EINVAL;
- return inet_pton(AF_INET, address, &((struct sockaddr_in *)sa)->sin_addr);
-}
-
-/*
- * The primary goal is to create a file with VPN parameters. Currently they
- * are interface, addresses, routes, DNS servers, and search domains and VPN
- * server address. Each parameter occupies one line in the file, and it can be
- * an empty string or space-separated values. The order and the format must be
- * consistent with com.android.server.connectivity.Vpn. Here is an example.
- *
- * ppp0
- * 192.168.1.100/24
- * 0.0.0.0/0
- * 192.168.1.1 192.168.1.2
- * example.org
- * 192.0.2.1
- *
- * The secondary goal is to unify the outcome of VPN. The current baseline
- * is to have an interface configured with the given address and netmask
- * and maybe add a host route to protect the tunnel. PPP-based VPN already
- * does this, but others might not. Routes, DNS servers, and search domains
- * are handled by the framework since they can be overridden by the users.
- */
-int main(int argc, char **argv)
-{
- FILE *state = fopen(DIR ".tmp", "wb");
- if (!state) {
- ALOGE("Cannot create state: %s", strerror(errno));
- return 1;
- }
-
- if (argc >= 6) {
- /* Invoked by pppd. */
- fprintf(state, "%s\n", argv[1]);
- fprintf(state, "%s/32\n", argv[4]);
- fprintf(state, "0.0.0.0/0\n");
- fprintf(state, "%s %s\n", env("DNS1"), env("DNS2"));
- fprintf(state, "\n");
- fprintf(state, "\n");
- } else if (argc == 2) {
- /* Invoked by racoon. */
- const char *interface = env("INTERFACE");
- const char *address = env("INTERNAL_ADDR4");
- const char *routes = env("SPLIT_INCLUDE_CIDR");
-
- int s = socket(AF_INET, SOCK_DGRAM, 0);
- struct ifreq ifr;
- memset(&ifr, 0, sizeof(ifr));
-
- /* Bring up the interface. */
- ifr.ifr_flags = IFF_UP;
- strncpy(ifr.ifr_name, interface, IFNAMSIZ);
- if (ioctl(s, SIOCSIFFLAGS, &ifr)) {
- ALOGE("Cannot bring up %s: %s", interface, strerror(errno));
- fclose(state);
- return 1;
- }
-
- /* Set the address. */
- if (!set_address(&ifr.ifr_addr, address) ||
- ioctl(s, SIOCSIFADDR, &ifr)) {
- ALOGE("Cannot set address: %s", strerror(errno));
- fclose(state);
- return 1;
- }
-
- /* Set the netmask. */
- if (set_address(&ifr.ifr_netmask, env("INTERNAL_NETMASK4"))) {
- if (ioctl(s, SIOCSIFNETMASK, &ifr)) {
- ALOGE("Cannot set netmask: %s", strerror(errno));
- fclose(state);
- return 1;
- }
- }
-
- /* TODO: Send few packets to trigger phase 2? */
-
- fprintf(state, "%s\n", interface);
- fprintf(state, "%s/%s\n", address, env("INTERNAL_CIDR4"));
- fprintf(state, "%s\n", routes[0] ? routes : "0.0.0.0/0");
- fprintf(state, "%s\n", env("INTERNAL_DNS4_LIST"));
- fprintf(state, "%s\n", env("DEFAULT_DOMAIN"));
- fprintf(state, "%s\n", env("REMOTE_ADDR"));
- } else {
- ALOGE("Cannot parse parameters");
- fclose(state);
- return 1;
- }
-
- fclose(state);
- if (chmod(DIR ".tmp", 0444) || rename(DIR ".tmp", DIR "state")) {
- ALOGE("Cannot write state: %s", strerror(errno));
- return 1;
- }
- return 0;
-}
diff --git a/include/input/MotionPredictor.h b/include/input/MotionPredictor.h
index 8797962..3b6e401 100644
--- a/include/input/MotionPredictor.h
+++ b/include/input/MotionPredictor.h
@@ -19,6 +19,7 @@
#include <cstdint>
#include <memory>
#include <mutex>
+#include <optional>
#include <string>
#include <unordered_map>
@@ -57,20 +58,23 @@
*/
class MotionPredictor {
public:
+ using ReportAtomFunction = MotionPredictorMetricsManager::ReportAtomFunction;
+
/**
* Parameters:
* predictionTimestampOffsetNanos: additional, constant shift to apply to the target
* prediction time. The prediction will target the time t=(prediction time +
* predictionTimestampOffsetNanos).
*
- * modelPath: filesystem path to a TfLiteMotionPredictorModel flatbuffer, or nullptr to use the
- * default model path.
- *
- * checkEnableMotionPredition: the function to check whether the prediction should run. Used to
+ * checkEnableMotionPrediction: the function to check whether the prediction should run. Used to
* provide an additional way of turning prediction on and off. Can be toggled at runtime.
+ *
+ * reportAtomFunction: the function that will be called to report prediction metrics. If
+ * omitted, the implementation will choose a default metrics reporting mechanism.
*/
MotionPredictor(nsecs_t predictionTimestampOffsetNanos,
- std::function<bool()> checkEnableMotionPrediction = isMotionPredictionEnabled);
+ std::function<bool()> checkEnableMotionPrediction = isMotionPredictionEnabled,
+ ReportAtomFunction reportAtomFunction = {});
/**
* Record the actual motion received by the view. This event will be used for calculating the
@@ -95,6 +99,8 @@
std::optional<MotionEvent> mLastEvent;
std::optional<MotionPredictorMetricsManager> mMetricsManager;
+
+ const ReportAtomFunction mReportAtomFunction;
};
} // namespace android
diff --git a/include/input/MotionPredictorMetricsManager.h b/include/input/MotionPredictorMetricsManager.h
index 12e50ba..38472d8 100644
--- a/include/input/MotionPredictorMetricsManager.h
+++ b/include/input/MotionPredictorMetricsManager.h
@@ -18,7 +18,6 @@
#include <cstdint>
#include <functional>
#include <limits>
-#include <optional>
#include <vector>
#include <input/Input.h> // for MotionEvent
@@ -37,15 +36,33 @@
*
* This class stores AggregatedStrokeMetrics, updating them as new MotionEvents are passed in. When
* onRecord receives an UP or CANCEL event, this indicates the end of the stroke, and the final
- * AtomFields are computed and reported to the stats library.
+ * AtomFields are computed and reported to the stats library. The number of atoms reported is equal
+ * to the value of `maxNumPredictions` passed to the constructor. Each atom corresponds to one
+ * "prediction time bucket" — the amount of time into the future being predicted.
*
* If mMockLoggedAtomFields is set, the batch of AtomFields that are reported to the stats library
* for one stroke are also stored in mMockLoggedAtomFields at the time they're reported.
*/
class MotionPredictorMetricsManager {
public:
- // Note: the MetricsManager assumes that the input interval equals the prediction interval.
- MotionPredictorMetricsManager(nsecs_t predictionInterval, size_t maxNumPredictions);
+ struct AtomFields;
+
+ using ReportAtomFunction = std::function<void(const AtomFields&)>;
+
+ static void defaultReportAtomFunction(const AtomFields& atomFields);
+
+ // Parameters:
+ // • predictionInterval: the time interval between successive prediction target timestamps.
+ // Note: the MetricsManager assumes that the input interval equals the prediction interval.
+ // • maxNumPredictions: the maximum number of distinct target timestamps the prediction model
+ // will generate predictions for. The MetricsManager reports this many atoms per stroke.
+ // • [Optional] reportAtomFunction: the function that will be called to report metrics. If
+ // omitted (or if an empty function is given), the `stats_write(…)` function from the Android
+ // stats library will be used.
+ MotionPredictorMetricsManager(
+ nsecs_t predictionInterval,
+ size_t maxNumPredictions,
+ ReportAtomFunction reportAtomFunction = defaultReportAtomFunction);
// This method should be called once for each call to MotionPredictor::record, receiving the
// forwarded MotionEvent argument.
@@ -121,7 +138,7 @@
// magnitude makes it unobtainable in practice.)
static const int NO_DATA_SENTINEL = std::numeric_limits<int32_t>::min();
- // Final metrics reported in the atom.
+ // Final metric values reported in the atom.
struct AtomFields {
int deltaTimeBucketMilliseconds = 0;
@@ -140,15 +157,6 @@
int scaleInvariantOffTrajectoryRmse = NO_DATA_SENTINEL; // millipixels
};
- // Allow tests to pass in a mock AtomFields pointer.
- //
- // When metrics are reported to the stats library on stroke end, they will also be written to
- // mockLoggedAtomFields, overwriting existing data. The size of mockLoggedAtomFields will equal
- // the number of calls to stats_write for that stroke.
- void setMockLoggedAtomFields(std::vector<AtomFields>* mockLoggedAtomFields) {
- mMockLoggedAtomFields = mockLoggedAtomFields;
- }
-
private:
// The interval between consecutive predictions' target timestamps. We assume that the input
// interval also equals this value.
@@ -172,11 +180,7 @@
std::vector<AggregatedStrokeMetrics> mAggregatedMetrics;
std::vector<AtomFields> mAtomFields;
- // Non-owning pointer to the location of mock AtomFields. If present, will be filled with the
- // values reported to stats_write on each batch of reported metrics.
- //
- // This pointer must remain valid as long as the MotionPredictorMetricsManager exists.
- std::vector<AtomFields>* mMockLoggedAtomFields = nullptr;
+ const ReportAtomFunction mReportAtomFunction;
// Helper methods for the implementation of onRecord and onPredict.
@@ -196,10 +200,7 @@
// Computes the atom fields to mAtomFields from the values in mAggregatedMetrics.
void computeAtomFields();
- // Reports the metrics given by the current data in mAtomFields:
- // • If on an Android device, reports the metrics to stats_write.
- // • If mMockLoggedAtomFields is present, it will be overwritten with logged metrics, with one
- // AtomFields element per call to stats_write.
+ // Reports the current data in mAtomFields by calling mReportAtomFunction.
void reportMetrics();
};
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index 90c9ff8..f1cc5f0 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -217,6 +217,7 @@
host_supported: true,
header_libs: [
+ "liblog_stub",
"trusty_mock_headers",
],
@@ -262,9 +263,6 @@
"trusty/TrustyStatus.cpp",
"trusty/socket.cpp",
],
- shared_libs: [
- "liblog",
- ],
}
cc_defaults {
diff --git a/libs/binder/liblog_stub/Android.bp b/libs/binder/liblog_stub/Android.bp
new file mode 100644
index 0000000..f2ca22f
--- /dev/null
+++ b/libs/binder/liblog_stub/Android.bp
@@ -0,0 +1,44 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_native_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_native_license"],
+}
+
+cc_library_headers {
+ name: "liblog_stub",
+ export_include_dirs: ["include"],
+
+ host_supported: true,
+ native_bridge_supported: true,
+ product_available: true,
+ recovery_available: true,
+ vendor_available: true,
+
+ target: {
+ windows: {
+ enabled: true,
+ },
+ },
+
+ visibility: [
+ "//frameworks/native/libs/binder:__subpackages__",
+ "//system/core/libutils/binder",
+ ],
+}
diff --git a/libs/binder/liblog_stub/include/android/log.h b/libs/binder/liblog_stub/include/android/log.h
new file mode 100644
index 0000000..9dcd926
--- /dev/null
+++ b/libs/binder/liblog_stub/include/android/log.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+extern "C" {
+
+/**
+ * Android log priority values, in increasing order of priority.
+ */
+typedef enum android_LogPriority {
+ /** For internal use only. */
+ ANDROID_LOG_UNKNOWN = 0,
+ /** The default priority, for internal use only. */
+ ANDROID_LOG_DEFAULT, /* only for SetMinPriority() */
+ /** Verbose logging. Should typically be disabled for a release apk. */
+ ANDROID_LOG_VERBOSE,
+ /** Debug logging. Should typically be disabled for a release apk. */
+ ANDROID_LOG_DEBUG,
+ /** Informational logging. Should typically be disabled for a release apk. */
+ ANDROID_LOG_INFO,
+ /** Warning logging. For use with recoverable failures. */
+ ANDROID_LOG_WARN,
+ /** Error logging. For use with unrecoverable failures. */
+ ANDROID_LOG_ERROR,
+ /** Fatal logging. For use when aborting. */
+ ANDROID_LOG_FATAL,
+ /** For internal use only. */
+ ANDROID_LOG_SILENT, /* only for SetMinPriority(); must be last */
+} android_LogPriority;
+
+typedef void (*__android_logger_function)(const struct __android_log_message* log_message);
+inline void __android_log_set_logger(__android_logger_function) {}
+inline void __android_log_stderr_logger(const struct __android_log_message*) {}
+
+} // extern "C"
diff --git a/libs/binder/liblog_stub/include/log/log.h b/libs/binder/liblog_stub/include/log/log.h
new file mode 100644
index 0000000..3b65607
--- /dev/null
+++ b/libs/binder/liblog_stub/include/log/log.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdio>
+#include <cstdlib>
+
+#include <android/log.h>
+
+extern "C" {
+
+#ifndef ANDROID_LOG_STUB_MIN_PRIORITY
+#define ANDROID_LOG_STUB_MIN_PRIORITY ANDROID_LOG_INFO
+#endif
+
+#ifndef LOG_TAG
+#define LOG_TAG ""
+#endif
+
+constexpr bool __android_log_stub_is_loggable(android_LogPriority priority) {
+ return ANDROID_LOG_STUB_MIN_PRIORITY <= priority;
+}
+
+int __android_log_print(int prio, const char* tag, const char* fmt, ...)
+ __attribute__((format(printf, 3, 4)))
+#ifdef ANDROID_LOG_STUB_WEAK_PRINT
+ __attribute__((weak))
+#endif
+ ;
+
+#define IF_ALOG(priority, tag) \
+ if (__android_log_stub_is_loggable(ANDROID_##priority) && __android_log_print)
+#define IF_ALOGV() IF_ALOG(LOG_VERBOSE, LOG_TAG)
+#define IF_ALOGD() IF_ALOG(LOG_DEBUG, LOG_TAG)
+#define IF_ALOGI() IF_ALOG(LOG_INFO, LOG_TAG)
+#define IF_ALOGW() IF_ALOG(LOG_WARN, LOG_TAG)
+#define IF_ALOGE() IF_ALOG(LOG_ERROR, LOG_TAG)
+
+#define ALOG(priority, tag, fmt, ...) \
+ do { \
+ if (false)[[/*VERY*/ unlikely]] { /* ignore unused __VA_ARGS__ */ \
+ std::fprintf(stderr, fmt __VA_OPT__(, ) __VA_ARGS__); \
+ } \
+ IF_ALOG(priority, tag) { \
+ __android_log_print(ANDROID_##priority, tag, \
+ tag ": " fmt "\n" __VA_OPT__(, ) __VA_ARGS__); \
+ } \
+ if constexpr (ANDROID_##priority == ANDROID_LOG_FATAL) std::abort(); \
+ } while (false)
+#define ALOGV(...) ALOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
+#define ALOGD(...) ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)
+#define ALOGI(...) ALOG(LOG_INFO, LOG_TAG, __VA_ARGS__)
+#define ALOGW(...) ALOG(LOG_WARN, LOG_TAG, __VA_ARGS__)
+#define ALOGE(...) ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)
+#define LOG_FATAL(...) ALOG(LOG_FATAL, LOG_TAG, __VA_ARGS__)
+#define LOG_ALWAYS_FATAL LOG_FATAL
+
+#define ALOG_IF(cond, priority, tag, ...) \
+ if (cond) [[unlikely]] \
+ ALOG(priority, tag, #cond ": " __VA_ARGS__)
+#define ALOGV_IF(cond, ...) ALOG_IF(cond, LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
+#define ALOGD_IF(cond, ...) ALOG_IF(cond, LOG_DEBUG, LOG_TAG, __VA_ARGS__)
+#define ALOGI_IF(cond, ...) ALOG_IF(cond, LOG_INFO, LOG_TAG, __VA_ARGS__)
+#define ALOGW_IF(cond, ...) ALOG_IF(cond, LOG_WARN, LOG_TAG, __VA_ARGS__)
+#define ALOGE_IF(cond, ...) ALOG_IF(cond, LOG_ERROR, LOG_TAG, __VA_ARGS__)
+#define LOG_FATAL_IF(cond, ...) ALOG_IF(cond, LOG_FATAL, LOG_TAG, __VA_ARGS__)
+#define LOG_ALWAYS_FATAL_IF LOG_FATAL_IF
+#define ALOG_ASSERT(cond, ...) LOG_FATAL_IF(!(cond), ##__VA_ARGS__)
+
+inline int android_errorWriteLog(int tag, const char* subTag) {
+ ALOGE("android_errorWriteLog(%x, %s)", tag, subTag);
+ return 0;
+}
+
+} // extern "C"
diff --git a/libs/binder/ndk/include_platform/android/binder_stability.h b/libs/binder/ndk/include_platform/android/binder_stability.h
index c1f62e5..089c775 100644
--- a/libs/binder/ndk/include_platform/android/binder_stability.h
+++ b/libs/binder/ndk/include_platform/android/binder_stability.h
@@ -21,17 +21,15 @@
__BEGIN_DECLS
/**
- * Private addition to binder_flag_t.
+ * Indicates that this transaction is coupled w/ vendor.img
*/
-enum {
- /**
- * Indicates that this transaction is coupled w/ vendor.img
- */
- FLAG_PRIVATE_VENDOR = 0x10000000,
-};
+constexpr binder_flags_t FLAG_PRIVATE_VENDOR = 0x10000000;
#if defined(__ANDROID_VENDOR__)
+/**
+ * Private addition to binder_flag_t.
+ */
enum {
FLAG_PRIVATE_LOCAL = FLAG_PRIVATE_VENDOR,
};
diff --git a/libs/binder/tests/binderRpcTestCommon.h b/libs/binder/tests/binderRpcTestCommon.h
index a55edd2..62fe9e5 100644
--- a/libs/binder/tests/binderRpcTestCommon.h
+++ b/libs/binder/tests/binderRpcTestCommon.h
@@ -393,7 +393,7 @@
}
if (delayed) {
- RpcMaybeThread([=]() {
+ RpcMaybeThread([=, this]() {
ALOGE("Executing delayed callback: '%s'", value.c_str());
Status status = doCallback(callback, oneway, false, value);
ALOGE("Delayed callback status: '%s'", status.toString8().c_str());
diff --git a/libs/binder/trusty/OS.cpp b/libs/binder/trusty/OS.cpp
index 99da1eb..9869bf3 100644
--- a/libs/binder/trusty/OS.cpp
+++ b/libs/binder/trusty/OS.cpp
@@ -22,10 +22,14 @@
#endif
#include <binder/RpcTransportTipcTrusty.h>
+#include <log/log.h>
+#include <trusty_log.h>
#include "../OS.h"
#include "TrustyStatus.h"
+#include <cstdarg>
+
using android::binder::borrowed_fd;
using android::binder::unique_fd;
@@ -87,3 +91,43 @@
}
} // namespace android::binder::os
+
+int __android_log_print(int prio [[maybe_unused]], const char* tag, const char* fmt, ...) {
+#ifdef TRUSTY_USERSPACE
+#define trusty_tlog _tlog
+#define trusty_vtlog _vtlog
+#else
+ // mapping taken from kernel trusty_log.h (TLOGx)
+ int kernelLogLevel;
+ if (prio <= ANDROID_LOG_DEBUG) {
+ kernelLogLevel = LK_DEBUGLEVEL_ALWAYS;
+ } else if (prio == ANDROID_LOG_INFO) {
+ kernelLogLevel = LK_DEBUGLEVEL_SPEW;
+ } else if (prio == ANDROID_LOG_WARN) {
+ kernelLogLevel = LK_DEBUGLEVEL_INFO;
+ } else if (prio == ANDROID_LOG_ERROR) {
+ kernelLogLevel = LK_DEBUGLEVEL_CRITICAL;
+ } else { /* prio >= ANDROID_LOG_FATAL */
+ kernelLogLevel = LK_DEBUGLEVEL_CRITICAL;
+ }
+#if LK_DEBUGLEVEL_NO_ALIASES
+ auto LK_DEBUGLEVEL_kernelLogLevel = kernelLogLevel;
+#endif
+
+#define trusty_tlog(...) _tlog(kernelLogLevel, __VA_ARGS__)
+#define trusty_vtlog(...) _vtlog(kernelLogLevel, __VA_ARGS__)
+#endif
+
+ va_list args;
+ va_start(args, fmt);
+ trusty_tlog((tag[0] == '\0') ? "libbinder" : "libbinder-");
+ trusty_vtlog(fmt, args);
+ va_end(args);
+
+ return 1;
+}
+
+// TODO(b/285204695): remove once trusty mock doesn't depend on libbase
+extern "C" int __android_log_buf_print(int, int, const char*, const char*, ...) {
+ return -ENOSYS;
+}
diff --git a/libs/binder/trusty/binderRpcTest/service/manifest.json b/libs/binder/trusty/binderRpcTest/service/manifest.json
index 1c4f7ee..d2a1fc0 100644
--- a/libs/binder/trusty/binderRpcTest/service/manifest.json
+++ b/libs/binder/trusty/binderRpcTest/service/manifest.json
@@ -2,7 +2,7 @@
"uuid": "87e424e5-69d7-4bbd-8b7c-7e24812cbc94",
"app_name": "binderRpcTestService",
"min_heap": 65536,
- "min_stack": 16384,
+ "min_stack": 20480,
"mgmt_flags": {
"restart_on_exit": true,
"non_critical_app": true
diff --git a/libs/binder/trusty/include/log/log.h b/libs/binder/trusty/include/log/log.h
deleted file mode 100644
index bf877a3..0000000
--- a/libs/binder/trusty/include/log/log.h
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#define BINDER_LOG_LEVEL_NONE 0
-#define BINDER_LOG_LEVEL_NORMAL 1
-#define BINDER_LOG_LEVEL_VERBOSE 2
-
-#ifndef BINDER_LOG_LEVEL
-#define BINDER_LOG_LEVEL BINDER_LOG_LEVEL_NORMAL
-#endif // BINDER_LOG_LEVEL
-
-#ifndef TLOG_TAG
-#ifdef LOG_TAG
-#define TLOG_TAG "libbinder-" LOG_TAG
-#else // LOG_TAG
-#define TLOG_TAG "libbinder"
-#endif // LOG_TAG
-#endif // TLOG_TAG
-
-#include <stdlib.h>
-#include <trusty_log.h>
-
-static inline void __ignore_va_args__(...) {}
-
-#if BINDER_LOG_LEVEL >= BINDER_LOG_LEVEL_NORMAL
-#define ALOGD(fmt, ...) TLOGD(fmt "\n", ##__VA_ARGS__)
-#define ALOGI(fmt, ...) TLOGI(fmt "\n", ##__VA_ARGS__)
-#define ALOGW(fmt, ...) TLOGW(fmt "\n", ##__VA_ARGS__)
-#define ALOGE(fmt, ...) TLOGE(fmt "\n", ##__VA_ARGS__)
-#else // BINDER_LOG_LEVEL >= BINDER_LOG_LEVEL_NORMAL
-#define ALOGD(fmt, ...) \
- while (0) { \
- __ignore_va_args__(__VA_ARGS__); \
- }
-#define ALOGI(fmt, ...) \
- while (0) { \
- __ignore_va_args__(__VA_ARGS__); \
- }
-#define ALOGW(fmt, ...) \
- while (0) { \
- __ignore_va_args__(__VA_ARGS__); \
- }
-#define ALOGE(fmt, ...) \
- while (0) { \
- __ignore_va_args__(__VA_ARGS__); \
- }
-#endif // BINDER_LOG_LEVEL >= BINDER_LOG_LEVEL_NORMAL
-
-#if BINDER_LOG_LEVEL >= BINDER_LOG_LEVEL_VERBOSE
-#define IF_ALOGV() if (TLOG_LVL >= TLOG_LVL_INFO)
-#define ALOGV(fmt, ...) TLOGI(fmt "\n", ##__VA_ARGS__)
-#else // BINDER_LOG_LEVEL >= BINDER_LOG_LEVEL_VERBOSE
-#define IF_ALOGV() if (false)
-#define ALOGV(fmt, ...) \
- while (0) { \
- __ignore_va_args__(__VA_ARGS__); \
- }
-#endif // BINDER_LOG_LEVEL >= BINDER_LOG_LEVEL_VERBOSE
-
-#define ALOGI_IF(cond, ...) \
- do { \
- if (cond) { \
- ALOGI(#cond ": " __VA_ARGS__); \
- } \
- } while (0)
-#define ALOGE_IF(cond, ...) \
- do { \
- if (cond) { \
- ALOGE(#cond ": " __VA_ARGS__); \
- } \
- } while (0)
-#define ALOGW_IF(cond, ...) \
- do { \
- if (cond) { \
- ALOGW(#cond ": " __VA_ARGS__); \
- } \
- } while (0)
-
-#define LOG_ALWAYS_FATAL(fmt, ...) \
- do { \
- TLOGE("libbinder fatal error: " fmt "\n", ##__VA_ARGS__); \
- abort(); \
- } while (0)
-#define LOG_ALWAYS_FATAL_IF(cond, ...) \
- do { \
- if (cond) { \
- LOG_ALWAYS_FATAL(#cond ": " __VA_ARGS__); \
- } \
- } while (0)
-#define LOG_FATAL(fmt, ...) \
- do { \
- TLOGE("libbinder fatal error: " fmt "\n", ##__VA_ARGS__); \
- abort(); \
- } while (0)
-#define LOG_FATAL_IF(cond, ...) \
- do { \
- if (cond) { \
- LOG_FATAL(#cond ": " __VA_ARGS__); \
- } \
- } while (0)
-
-#define ALOG_ASSERT(cond, ...) LOG_FATAL_IF(!(cond), ##__VA_ARGS__)
-
-#define android_errorWriteLog(tag, subTag) \
- do { \
- TLOGE("android_errorWriteLog: tag:%x subTag:%s\n", tag, subTag); \
- } while (0)
diff --git a/libs/binder/trusty/include_mock/trusty_log.h b/libs/binder/trusty/include_mock/trusty_log.h
index d51e752..9aa9031 100644
--- a/libs/binder/trusty/include_mock/trusty_log.h
+++ b/libs/binder/trusty/include_mock/trusty_log.h
@@ -24,3 +24,6 @@
#define TLOGW(fmt, ...) printf(fmt, ##__VA_ARGS__)
#define TLOGE(fmt, ...) printf(fmt, ##__VA_ARGS__)
#define TLOGC(fmt, ...) printf(fmt, ##__VA_ARGS__)
+
+#define _tlog(fmt, ...) printf(fmt, ##__VA_ARGS__)
+#define _vtlog(fmt, args) vprintf(fmt, args)
diff --git a/libs/binder/trusty/kernel/rules.mk b/libs/binder/trusty/kernel/rules.mk
index 2a13ead..788184d 100644
--- a/libs/binder/trusty/kernel/rules.mk
+++ b/libs/binder/trusty/kernel/rules.mk
@@ -20,6 +20,7 @@
LIBBINDER_DIR := frameworks/native/libs/binder
# TODO(b/302723053): remove libbase after aidl prebuilt gets updated to December release
LIBBASE_DIR := system/libbase
+LIBLOG_STUB_DIR := $(LIBBINDER_DIR)/liblog_stub
LIBUTILS_BINDER_DIR := system/core/libutils/binder
FMTLIB_DIR := external/fmtlib
@@ -53,6 +54,7 @@
GLOBAL_INCLUDES += \
$(LOCAL_DIR)/include \
$(LOCAL_DIR)/../include \
+ $(LIBLOG_STUB_DIR)/include \
$(LIBBINDER_DIR)/include \
$(LIBBINDER_DIR)/ndk/include_cpp \
$(LIBBASE_DIR)/include \
diff --git a/libs/binder/trusty/rules.mk b/libs/binder/trusty/rules.mk
index e2b386d..e0f821f 100644
--- a/libs/binder/trusty/rules.mk
+++ b/libs/binder/trusty/rules.mk
@@ -20,6 +20,7 @@
LIBBINDER_DIR := frameworks/native/libs/binder
# TODO(b/302723053): remove libbase after aidl prebuilt gets updated to December release
LIBBASE_DIR := system/libbase
+LIBLOG_STUB_DIR := $(LIBBINDER_DIR)/liblog_stub
LIBUTILS_BINDER_DIR := system/core/libutils/binder
FMTLIB_DIR := external/fmtlib
@@ -54,6 +55,7 @@
MODULE_EXPORT_INCLUDES += \
$(LOCAL_DIR)/include \
+ $(LIBLOG_STUB_DIR)/include \
$(LIBBINDER_DIR)/include \
$(LIBBASE_DIR)/include \
$(LIBUTILS_BINDER_DIR)/include \
diff --git a/libs/bufferstreams/examples/app/Android.bp b/libs/bufferstreams/examples/app/Android.bp
index d6305f8..bb573c5 100644
--- a/libs/bufferstreams/examples/app/Android.bp
+++ b/libs/bufferstreams/examples/app/Android.bp
@@ -23,6 +23,9 @@
kotlincflags: [
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
],
+ optimize: {
+ proguard_flags_files: ["proguard-rules.pro"],
+ },
resource_dirs: ["res"],
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferStreamJNI.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferStreamJNI.kt
index a2db934..ede7793 100644
--- a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferStreamJNI.kt
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferStreamJNI.kt
@@ -10,18 +10,18 @@
* A native method that is implemented by the 'bufferstreamsdemoapp' native library, which is
* packaged with this application.
*/
- external fun stringFromJNI()
- external fun testBufferQueueCreation()
+ external fun stringFromJNI(): String;
+ external fun testBufferQueueCreation();
companion object {
- fun companion_stringFromJNI() {
+ fun companion_stringFromJNI(): String {
val instance = BufferStreamJNI()
- instance.stringFromJNI()
+ return instance.stringFromJNI()
}
fun companion_testBufferQueueCreation() {
val instance = BufferStreamJNI()
- instance.testBufferQueueCreation()
+ return instance.testBufferQueueCreation()
}
}
}
\ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen1.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen1.kt
index 46ce028..95e415e 100644
--- a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen1.kt
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen1.kt
@@ -4,10 +4,8 @@
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
-import androidx.compose.material3.Card
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -17,17 +15,21 @@
@Composable
fun DemoScreen1(modifier: Modifier = Modifier) {
Column(modifier = modifier, verticalArrangement = Arrangement.SpaceBetween) {
- Card(modifier = Modifier.fillMaxWidth().weight(1f, false).padding(16.dp).height(400.dp)) {
- Text("Log output", modifier = Modifier.padding(16.dp))
- }
+ LogOutput.getInstance().LogOutputComposable()
Row(modifier = Modifier.weight(1f, false).padding(16.dp)) {
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
Button(
- modifier = Modifier.fillMaxWidth(),
- onClick = { BufferStreamJNI.companion_testBufferQueueCreation() }
- ) { Text("Run") }
- OutlinedButton(modifier = Modifier.fillMaxWidth(), onClick = {}) { Text("Clear") }
+ modifier = Modifier.fillMaxWidth(),
+ onClick = { BufferStreamJNI.companion_testBufferQueueCreation() }) {
+ Text("Run")
+ }
+
+ OutlinedButton(
+ modifier = Modifier.fillMaxWidth(),
+ onClick = { LogOutput.getInstance().clearText() }) {
+ Text("Clear")
+ }
}
}
}
-}
\ No newline at end of file
+}
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/LogOutput.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/LogOutput.kt
new file mode 100644
index 0000000..3f0926f
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/LogOutput.kt
@@ -0,0 +1,65 @@
+package com.android.graphics.bufferstreamsdemoapp
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.Card
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import java.util.Collections
+
+/*
+LogOutput centralizes logging: storing, displaying, adding, and clearing log messages with
+thread safety. It is a singleton that's also accessed from C++. The private constructor will
+not allow this class to be initialized, limiting it to getInstance().
+ */
+class LogOutput private constructor() {
+ val logs = Collections.synchronizedList(mutableStateListOf<String>())
+
+ @Composable
+ fun LogOutputComposable() {
+ val rlogs = remember { logs }
+
+ Card(modifier = Modifier.fillMaxWidth().padding(16.dp).height(400.dp)) {
+ Column(
+ modifier =
+ Modifier.padding(10.dp).size(380.dp).verticalScroll(rememberScrollState())) {
+ for (log in rlogs) {
+ Text(log, modifier = Modifier.padding(0.dp))
+ }
+ }
+ }
+ }
+
+ fun clearText() {
+ logs.clear()
+ }
+
+ fun addLog(log: String) {
+ logs.add(log)
+ }
+
+ companion object {
+ @Volatile private var instance: LogOutput? = null
+
+ @JvmStatic
+ fun getInstance(): LogOutput {
+ if (instance == null) {
+ synchronized(this) {
+ if (instance == null) {
+ instance = LogOutput()
+ }
+ }
+ }
+ return instance!!
+ }
+ }
+}
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.kt
index f3f4404..2ccd8d7 100644
--- a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.kt
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.kt
@@ -31,17 +31,19 @@
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.android.graphics.bufferstreamsdemoapp.ui.theme.JetpackTheme
+import java.util.*
class MainActivity : ComponentActivity() {
-
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+
setContent {
JetpackTheme {
Surface(
- modifier = Modifier.fillMaxSize(),
- color = MaterialTheme.colorScheme.background
- ) { BufferDemosApp() }
+ modifier = Modifier.fillMaxSize(),
+ color = MaterialTheme.colorScheme.background) {
+ BufferDemosApp()
+ }
}
}
}
@@ -67,38 +69,32 @@
val backStackEntry by navController.currentBackStackEntryAsState()
// Get the name of the current screen
val currentScreen =
- BufferDemoScreen.findByRoute(
- backStackEntry?.destination?.route ?: BufferDemoScreen.Start.route
- )
+ BufferDemoScreen.findByRoute(
+ backStackEntry?.destination?.route ?: BufferDemoScreen.Start.route)
Scaffold(
- topBar = {
- BufferDemosAppBar(
- currentScreen = currentScreen,
- canNavigateBack = navController.previousBackStackEntry != null,
- navigateUp = { navController.navigateUp() }
- )
- }
- ) {
- NavHost(
+ topBar = {
+ BufferDemosAppBar(
+ currentScreen = currentScreen,
+ canNavigateBack = navController.previousBackStackEntry != null,
+ navigateUp = { navController.navigateUp() })
+ }) {
+ NavHost(
navController = navController,
startDestination = BufferDemoScreen.Start.route,
- modifier = Modifier.padding(10.dp)
- ) {
- composable(route = BufferDemoScreen.Start.route) {
- DemoList(
- onButtonClicked = {
- navController.navigate(it)
- },
- )
- }
- composable(route = BufferDemoScreen.Demo1.route) {
- DemoScreen1(modifier = Modifier.fillMaxHeight().padding(top = 100.dp))
- }
- composable(route = BufferDemoScreen.Demo2.route) { DemoScreen2() }
- composable(route = BufferDemoScreen.Demo3.route) { DemoScreen3() }
+ modifier = Modifier.padding(10.dp)) {
+ composable(route = BufferDemoScreen.Start.route) {
+ DemoList(
+ onButtonClicked = { navController.navigate(it) },
+ )
+ }
+ composable(route = BufferDemoScreen.Demo1.route) {
+ DemoScreen1(modifier = Modifier.fillMaxHeight().padding(top = 100.dp))
+ }
+ composable(route = BufferDemoScreen.Demo2.route) { DemoScreen2() }
+ composable(route = BufferDemoScreen.Demo3.route) { DemoScreen3() }
+ }
}
- }
}
@Composable
@@ -107,25 +103,25 @@
Column(modifier = modifier, verticalArrangement = Arrangement.SpaceBetween) {
Column(
- modifier = Modifier.fillMaxWidth(),
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.spacedBy(8.dp)
- ) {
- Spacer(modifier = Modifier.height(100.dp))
- Text(text = "Buffer Demos", style = MaterialTheme.typography.titleLarge)
- Spacer(modifier = Modifier.height(8.dp))
- }
+ modifier = Modifier.fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(8.dp)) {
+ Spacer(modifier = Modifier.height(100.dp))
+ Text(text = "Buffer Demos", style = MaterialTheme.typography.titleLarge)
+ Spacer(modifier = Modifier.height(8.dp))
+ }
Row(modifier = Modifier.weight(2f, false)) {
Column(
- modifier = Modifier.fillMaxWidth(),
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.spacedBy(16.dp)
- ) {
- for (item in BufferDemoScreen.values()) {
- if (item.route != BufferDemoScreen.Start.route)
- SelectDemoButton(name = stringResource(item.title), onClick = { onButtonClicked(item.route) })
+ modifier = Modifier.fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(16.dp)) {
+ for (item in BufferDemoScreen.values()) {
+ if (item.route != BufferDemoScreen.Start.route)
+ SelectDemoButton(
+ name = stringResource(item.title),
+ onClick = { onButtonClicked(item.route) })
+ }
}
- }
}
}
}
diff --git a/libs/bufferstreams/examples/app/jni/main.cpp b/libs/bufferstreams/examples/app/jni/main.cpp
index 3d3fee4..550ad22 100644
--- a/libs/bufferstreams/examples/app/jni/main.cpp
+++ b/libs/bufferstreams/examples/app/jni/main.cpp
@@ -13,25 +13,41 @@
// limitations under the License.
#include <jni.h>
+#include <string>
#include <gui/BufferQueue.h>
-extern "C"
-{
- JNIEXPORT jstring JNICALL
- Java_com_android_graphics_bufferstreamsdemoapp_BufferStreamJNI_stringFromJNI(
- JNIEnv* env,
- jobject /* this */) {
- const char* hello = "Hello from C++";
- return env->NewStringUTF(hello);
- }
+void log(JNIEnv* env, std::string l) {
+ jclass clazz = env->FindClass("com/android/graphics/bufferstreamsdemoapp/LogOutput");
+ jmethodID getInstance = env->GetStaticMethodID(clazz, "getInstance",
+ "()Lcom/android/graphics/bufferstreamsdemoapp/LogOutput;");
+ jmethodID addLog = env->GetMethodID(clazz, "addLog", "(Ljava/lang/String;)V");
+ jobject dmg = env->CallStaticObjectMethod(clazz, getInstance);
- JNIEXPORT void JNICALL
- Java_com_android_graphics_bufferstreamsdemoapp_BufferStreamJNI_testBufferQueueCreation(
- JNIEnv* /* env */,
- jobject /* this */) {
- android::sp<android::IGraphicBufferProducer> producer;
- android::sp<android::IGraphicBufferConsumer> consumer;
- android::BufferQueue::createBufferQueue(&producer, &consumer);
- }
+ jstring jlog = env->NewStringUTF(l.c_str());
+ env->CallVoidMethod(dmg, addLog, jlog);
+}
+
+extern "C" {
+
+JNIEXPORT jstring JNICALL
+Java_com_android_graphics_bufferstreamsdemoapp_BufferStreamJNI_stringFromJNI(JNIEnv* env,
+ jobject /* this */) {
+ const char* hello = "Hello from C++";
+ return env->NewStringUTF(hello);
+}
+
+JNIEXPORT void JNICALL
+Java_com_android_graphics_bufferstreamsdemoapp_BufferStreamJNI_testBufferQueueCreation(
+ JNIEnv* env, jobject /* thiz */) {
+
+ log(env, "Calling testBufferQueueCreation.");
+ android::sp<android::IGraphicBufferProducer> producer;
+ log(env, "Created producer.");
+ android::sp<android::IGraphicBufferConsumer> consumer;
+ log(env, "Created consumer.");
+ android::BufferQueue::createBufferQueue(&producer, &consumer);
+ log(env, "Created BufferQueue successfully.");
+ log(env, "Done!");
+}
}
\ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/proguard-rules.pro b/libs/bufferstreams/examples/app/proguard-rules.pro
new file mode 100644
index 0000000..7a987fc
--- /dev/null
+++ b/libs/bufferstreams/examples/app/proguard-rules.pro
@@ -0,0 +1,23 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
+
+-keep,allowoptimization,allowobfuscation class com.android.graphics.bufferstreamsdemoapp.** { *; }
diff --git a/libs/input/MotionPredictor.cpp b/libs/input/MotionPredictor.cpp
index 412931b..c4e3ff6 100644
--- a/libs/input/MotionPredictor.cpp
+++ b/libs/input/MotionPredictor.cpp
@@ -60,9 +60,11 @@
// --- MotionPredictor ---
MotionPredictor::MotionPredictor(nsecs_t predictionTimestampOffsetNanos,
- std::function<bool()> checkMotionPredictionEnabled)
+ std::function<bool()> checkMotionPredictionEnabled,
+ ReportAtomFunction reportAtomFunction)
: mPredictionTimestampOffsetNanos(predictionTimestampOffsetNanos),
- mCheckMotionPredictionEnabled(std::move(checkMotionPredictionEnabled)) {}
+ mCheckMotionPredictionEnabled(std::move(checkMotionPredictionEnabled)),
+ mReportAtomFunction(reportAtomFunction) {}
android::base::Result<void> MotionPredictor::record(const MotionEvent& event) {
if (mLastEvent && mLastEvent->getDeviceId() != event.getDeviceId()) {
@@ -90,6 +92,13 @@
mBuffers = std::make_unique<TfLiteMotionPredictorBuffers>(mModel->inputLength());
}
+ // Pass input event to the MetricsManager.
+ if (!mMetricsManager) {
+ mMetricsManager.emplace(mModel->config().predictionInterval, mModel->outputLength(),
+ mReportAtomFunction);
+ }
+ mMetricsManager->onRecord(event);
+
const int32_t action = event.getActionMasked();
if (action == AMOTION_EVENT_ACTION_UP || action == AMOTION_EVENT_ACTION_CANCEL) {
ALOGD_IF(isDebug(), "End of event stream");
@@ -135,12 +144,6 @@
}
mLastEvent->copyFrom(&event, /*keepHistory=*/false);
- // Pass input event to the MetricsManager.
- if (!mMetricsManager) {
- mMetricsManager.emplace(mModel->config().predictionInterval, mModel->outputLength());
- }
- mMetricsManager->onRecord(event);
-
return {};
}
diff --git a/libs/input/MotionPredictorMetricsManager.cpp b/libs/input/MotionPredictorMetricsManager.cpp
index 67b1032..0412d08 100644
--- a/libs/input/MotionPredictorMetricsManager.cpp
+++ b/libs/input/MotionPredictorMetricsManager.cpp
@@ -46,13 +46,36 @@
} // namespace
-MotionPredictorMetricsManager::MotionPredictorMetricsManager(nsecs_t predictionInterval,
- size_t maxNumPredictions)
+void MotionPredictorMetricsManager::defaultReportAtomFunction(
+ const MotionPredictorMetricsManager::AtomFields& atomFields) {
+ // Call stats_write logging function only on Android targets (not supported on host).
+#ifdef __ANDROID__
+ android::stats::libinput::
+ stats_write(android::stats::libinput::STYLUS_PREDICTION_METRICS_REPORTED,
+ /*stylus_vendor_id=*/0,
+ /*stylus_product_id=*/0,
+ atomFields.deltaTimeBucketMilliseconds,
+ atomFields.alongTrajectoryErrorMeanMillipixels,
+ atomFields.alongTrajectoryErrorStdMillipixels,
+ atomFields.offTrajectoryRmseMillipixels,
+ atomFields.pressureRmseMilliunits,
+ atomFields.highVelocityAlongTrajectoryRmse,
+ atomFields.highVelocityOffTrajectoryRmse,
+ atomFields.scaleInvariantAlongTrajectoryRmse,
+ atomFields.scaleInvariantOffTrajectoryRmse);
+#endif
+}
+
+MotionPredictorMetricsManager::MotionPredictorMetricsManager(
+ nsecs_t predictionInterval,
+ size_t maxNumPredictions,
+ ReportAtomFunction reportAtomFunction)
: mPredictionInterval(predictionInterval),
mMaxNumPredictions(maxNumPredictions),
mRecentGroundTruthPoints(maxNumPredictions + 1),
mAggregatedMetrics(maxNumPredictions),
- mAtomFields(maxNumPredictions) {}
+ mAtomFields(maxNumPredictions),
+ mReportAtomFunction(reportAtomFunction ? reportAtomFunction : defaultReportAtomFunction) {}
void MotionPredictorMetricsManager::onRecord(const MotionEvent& inputEvent) {
// Convert MotionEvent to GroundTruthPoint.
@@ -81,8 +104,8 @@
if (mRecentGroundTruthPoints.size() >= 2) {
computeAtomFields();
reportMetrics();
- break;
}
+ break;
}
}
}
@@ -345,28 +368,10 @@
}
void MotionPredictorMetricsManager::reportMetrics() {
- // Report one atom for each time bucket.
+ LOG_ALWAYS_FATAL_IF(!mReportAtomFunction);
+ // Report one atom for each prediction time bucket.
for (size_t i = 0; i < mAtomFields.size(); ++i) {
- // Call stats_write logging function only on Android targets (not supported on host).
-#ifdef __ANDROID__
- android::stats::libinput::
- stats_write(android::stats::libinput::STYLUS_PREDICTION_METRICS_REPORTED,
- /*stylus_vendor_id=*/0,
- /*stylus_product_id=*/0, mAtomFields[i].deltaTimeBucketMilliseconds,
- mAtomFields[i].alongTrajectoryErrorMeanMillipixels,
- mAtomFields[i].alongTrajectoryErrorStdMillipixels,
- mAtomFields[i].offTrajectoryRmseMillipixels,
- mAtomFields[i].pressureRmseMilliunits,
- mAtomFields[i].highVelocityAlongTrajectoryRmse,
- mAtomFields[i].highVelocityOffTrajectoryRmse,
- mAtomFields[i].scaleInvariantAlongTrajectoryRmse,
- mAtomFields[i].scaleInvariantOffTrajectoryRmse);
-#endif
- }
-
- // Set mock atom fields, if available.
- if (mMockLoggedAtomFields != nullptr) {
- *mMockLoggedAtomFields = mAtomFields;
+ mReportAtomFunction(mAtomFields[i]);
}
}
diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig
index 3a6af6d..54eeb39 100644
--- a/libs/input/input_flags.aconfig
+++ b/libs/input/input_flags.aconfig
@@ -83,3 +83,10 @@
description: "enable override key behavior permission APIs"
bug: "309018874"
}
+
+flag {
+ name: "remove_pointer_event_tracking_in_wm"
+ namespace: "input"
+ description: "Remove pointer event tracking in WM after the Pointer Icon Refactor"
+ bug: "315321016"
+}
diff --git a/libs/input/tests/MotionPredictorMetricsManager_test.cpp b/libs/input/tests/MotionPredictorMetricsManager_test.cpp
index b420a5a..31cc145 100644
--- a/libs/input/tests/MotionPredictorMetricsManager_test.cpp
+++ b/libs/input/tests/MotionPredictorMetricsManager_test.cpp
@@ -39,6 +39,7 @@
using GroundTruthPoint = MotionPredictorMetricsManager::GroundTruthPoint;
using PredictionPoint = MotionPredictorMetricsManager::PredictionPoint;
using AtomFields = MotionPredictorMetricsManager::AtomFields;
+using ReportAtomFunction = MotionPredictorMetricsManager::ReportAtomFunction;
inline constexpr int NANOS_PER_MILLIS = 1'000'000;
@@ -664,9 +665,16 @@
// --- MotionPredictorMetricsManager tests. ---
-// Helper function that instantiates a MetricsManager with the given mock logged AtomFields. Takes
-// vectors of ground truth and prediction points of the same length, and passes these points to the
-// MetricsManager. The format of these vectors is expected to be:
+// Creates a mock atom reporting function that appends the reported atom to the given vector.
+ReportAtomFunction createMockReportAtomFunction(std::vector<AtomFields>& reportedAtomFields) {
+ return [&reportedAtomFields](const AtomFields& atomFields) -> void {
+ reportedAtomFields.push_back(atomFields);
+ };
+}
+
+// Helper function that instantiates a MetricsManager that reports metrics to outReportedAtomFields.
+// Takes vectors of ground truth and prediction points of the same length, and passes these points
+// to the MetricsManager. The format of these vectors is expected to be:
// • groundTruthPoints: chronologically-ordered ground truth points, with at least 2 elements.
// • predictionPoints: the first index points to a vector of predictions corresponding to the
// source ground truth point with the same index.
@@ -678,15 +686,16 @@
// prediction sets (that is, excluding the first and last). Thus, groundTruthPoints and
// predictionPoints should have size at least TEST_MAX_NUM_PREDICTIONS + 2.
//
-// The passed-in outAtomFields will contain the logged AtomFields when the function returns.
+// When the function returns, outReportedAtomFields will contain the reported AtomFields.
//
// This function returns void so that it can use test assertions.
void runMetricsManager(const std::vector<GroundTruthPoint>& groundTruthPoints,
const std::vector<std::vector<PredictionPoint>>& predictionPoints,
- std::vector<AtomFields>& outAtomFields) {
+ std::vector<AtomFields>& outReportedAtomFields) {
MotionPredictorMetricsManager metricsManager(TEST_PREDICTION_INTERVAL_NANOS,
- TEST_MAX_NUM_PREDICTIONS);
- metricsManager.setMockLoggedAtomFields(&outAtomFields);
+ TEST_MAX_NUM_PREDICTIONS,
+ createMockReportAtomFunction(
+ outReportedAtomFields));
// Validate structure of groundTruthPoints and predictionPoints.
ASSERT_EQ(predictionPoints.size(), groundTruthPoints.size());
@@ -712,18 +721,18 @@
// • Input: no prediction data.
// • Expectation: no metrics should be logged.
TEST(MotionPredictorMetricsManagerTest, NoPredictions) {
- std::vector<AtomFields> mockLoggedAtomFields;
+ std::vector<AtomFields> reportedAtomFields;
MotionPredictorMetricsManager metricsManager(TEST_PREDICTION_INTERVAL_NANOS,
- TEST_MAX_NUM_PREDICTIONS);
- metricsManager.setMockLoggedAtomFields(&mockLoggedAtomFields);
+ TEST_MAX_NUM_PREDICTIONS,
+ createMockReportAtomFunction(reportedAtomFields));
metricsManager.onRecord(makeMotionEvent(
GroundTruthPoint{{.position = Eigen::Vector2f(0, 0), .pressure = 0}, .timestamp = 0}));
metricsManager.onRecord(makeLiftMotionEvent());
- // Check that mockLoggedAtomFields is still empty (as it was initialized empty), ensuring that
+ // Check that reportedAtomFields is still empty (as it was initialized empty), ensuring that
// no metrics were logged.
- EXPECT_EQ(0u, mockLoggedAtomFields.size());
+ EXPECT_EQ(0u, reportedAtomFields.size());
}
// Perfect predictions test:
@@ -744,14 +753,14 @@
groundTruthPoint.timestamp += TEST_PREDICTION_INTERVAL_NANOS;
}
- std::vector<AtomFields> atomFields;
- runMetricsManager(groundTruthPoints, predictionPoints, atomFields);
+ std::vector<AtomFields> reportedAtomFields;
+ runMetricsManager(groundTruthPoints, predictionPoints, reportedAtomFields);
- ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, atomFields.size());
+ ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, reportedAtomFields.size());
// Check that errors are all zero, or NO_DATA_SENTINEL for unreported metrics.
- for (size_t i = 0; i < atomFields.size(); ++i) {
+ for (size_t i = 0; i < reportedAtomFields.size(); ++i) {
SCOPED_TRACE(testing::Message() << "i = " << i);
- const AtomFields& atom = atomFields[i];
+ const AtomFields& atom = reportedAtomFields[i];
const nsecs_t deltaTimeBucketNanos = TEST_PREDICTION_INTERVAL_NANOS * (i + 1);
EXPECT_EQ(deltaTimeBucketNanos / NANOS_PER_MILLIS, atom.deltaTimeBucketMilliseconds);
// General errors: reported for every time bucket.
@@ -764,7 +773,7 @@
EXPECT_EQ(NO_DATA_SENTINEL, atom.highVelocityAlongTrajectoryRmse);
EXPECT_EQ(NO_DATA_SENTINEL, atom.highVelocityOffTrajectoryRmse);
// Scale-invariant errors: reported only for the last time bucket.
- if (i + 1 == atomFields.size()) {
+ if (i + 1 == reportedAtomFields.size()) {
EXPECT_EQ(0, atom.scaleInvariantAlongTrajectoryRmse);
EXPECT_EQ(0, atom.scaleInvariantOffTrajectoryRmse);
} else {
@@ -801,14 +810,14 @@
computePressureRmses(groundTruthPoints, predictionPoints);
// Run test.
- std::vector<AtomFields> atomFields;
- runMetricsManager(groundTruthPoints, predictionPoints, atomFields);
+ std::vector<AtomFields> reportedAtomFields;
+ runMetricsManager(groundTruthPoints, predictionPoints, reportedAtomFields);
// Check logged metrics match expectations.
- ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, atomFields.size());
- for (size_t i = 0; i < atomFields.size(); ++i) {
+ ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, reportedAtomFields.size());
+ for (size_t i = 0; i < reportedAtomFields.size(); ++i) {
SCOPED_TRACE(testing::Message() << "i = " << i);
- const AtomFields& atom = atomFields[i];
+ const AtomFields& atom = reportedAtomFields[i];
// Check time bucket delta matches expectation based on index and prediction interval.
const nsecs_t deltaTimeBucketNanos = TEST_PREDICTION_INTERVAL_NANOS * (i + 1);
EXPECT_EQ(deltaTimeBucketNanos / NANOS_PER_MILLIS, atom.deltaTimeBucketMilliseconds);
@@ -845,14 +854,14 @@
computeGeneralPositionErrors(groundTruthPoints, predictionPoints);
// Run test.
- std::vector<AtomFields> atomFields;
- runMetricsManager(groundTruthPoints, predictionPoints, atomFields);
+ std::vector<AtomFields> reportedAtomFields;
+ runMetricsManager(groundTruthPoints, predictionPoints, reportedAtomFields);
// Check logged metrics match expectations.
- ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, atomFields.size());
- for (size_t i = 0; i < atomFields.size(); ++i) {
+ ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, reportedAtomFields.size());
+ for (size_t i = 0; i < reportedAtomFields.size(); ++i) {
SCOPED_TRACE(testing::Message() << "i = " << i);
- const AtomFields& atom = atomFields[i];
+ const AtomFields& atom = reportedAtomFields[i];
// Check time bucket delta matches expectation based on index and prediction interval.
const nsecs_t deltaTimeBucketNanos = TEST_PREDICTION_INTERVAL_NANOS * (i + 1);
EXPECT_EQ(deltaTimeBucketNanos / NANOS_PER_MILLIS, atom.deltaTimeBucketMilliseconds);
@@ -896,14 +905,14 @@
computeGeneralPositionErrors(groundTruthPoints, predictionPoints);
// Run test.
- std::vector<AtomFields> atomFields;
- runMetricsManager(groundTruthPoints, predictionPoints, atomFields);
+ std::vector<AtomFields> reportedAtomFields;
+ runMetricsManager(groundTruthPoints, predictionPoints, reportedAtomFields);
// Check logged metrics match expectations.
- ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, atomFields.size());
- for (size_t i = 0; i < atomFields.size(); ++i) {
+ ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, reportedAtomFields.size());
+ for (size_t i = 0; i < reportedAtomFields.size(); ++i) {
SCOPED_TRACE(testing::Message() << "i = " << i);
- const AtomFields& atom = atomFields[i];
+ const AtomFields& atom = reportedAtomFields[i];
const nsecs_t deltaTimeBucketNanos = TEST_PREDICTION_INTERVAL_NANOS * (i + 1);
EXPECT_EQ(deltaTimeBucketNanos / NANOS_PER_MILLIS, atom.deltaTimeBucketMilliseconds);
@@ -926,7 +935,7 @@
// to general errors (where reported).
//
// As above, use absolute value for RMSE, since it must be non-negative.
- if (i + 2 >= atomFields.size()) {
+ if (i + 2 >= reportedAtomFields.size()) {
EXPECT_NEAR(static_cast<int>(
1000 * std::abs(generalPositionErrors[i].alongTrajectoryErrorMean)),
atom.highVelocityAlongTrajectoryRmse, 1);
@@ -946,7 +955,7 @@
// to scale-invariant errors by dividing by `strokeVelocty * TEST_MAX_NUM_PREDICTIONS`.
//
// As above, use absolute value for RMSE, since it must be non-negative.
- if (i + 1 == atomFields.size()) {
+ if (i + 1 == reportedAtomFields.size()) {
const float pathLength = strokeVelocity * TEST_MAX_NUM_PREDICTIONS;
std::vector<float> alongTrajectoryAbsoluteErrors;
std::vector<float> offTrajectoryAbsoluteErrors;
diff --git a/libs/input/tests/MotionPredictor_test.cpp b/libs/input/tests/MotionPredictor_test.cpp
index 4ac7ae9..3343114 100644
--- a/libs/input/tests/MotionPredictor_test.cpp
+++ b/libs/input/tests/MotionPredictor_test.cpp
@@ -147,4 +147,35 @@
ASSERT_FALSE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_TOUCHSCREEN));
}
+using AtomFields = MotionPredictorMetricsManager::AtomFields;
+using ReportAtomFunction = MotionPredictorMetricsManager::ReportAtomFunction;
+
+// Creates a mock atom reporting function that appends the reported atom to the given vector.
+// The passed-in pointer must not be nullptr.
+ReportAtomFunction createMockReportAtomFunction(std::vector<AtomFields>* reportedAtomFields) {
+ return [reportedAtomFields](const AtomFields& atomFields) -> void {
+ reportedAtomFields->push_back(atomFields);
+ };
+}
+
+TEST(MotionPredictorMetricsManagerIntegrationTest, ReportsMetrics) {
+ std::vector<AtomFields> reportedAtomFields;
+ MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
+ []() { return true /*enable prediction*/; },
+ createMockReportAtomFunction(&reportedAtomFields));
+
+ ASSERT_TRUE(predictor.record(getMotionEvent(DOWN, 1, 1, 0ms, /*deviceId=*/0)).ok());
+ ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 2, 2, 4ms, /*deviceId=*/0)).ok());
+ ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 3, 3, 8ms, /*deviceId=*/0)).ok());
+ ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 4, 4, 12ms, /*deviceId=*/0)).ok());
+ ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 5, 5, 16ms, /*deviceId=*/0)).ok());
+ ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 6, 6, 20ms, /*deviceId=*/0)).ok());
+ ASSERT_TRUE(predictor.record(getMotionEvent(UP, 7, 7, 24ms, /*deviceId=*/0)).ok());
+
+ // The number of atoms reported should equal the number of prediction time buckets, which is
+ // given by the prediction model's output length. For now, this value is always 5, and we
+ // hardcode it because it's not publicly accessible from the MotionPredictor.
+ EXPECT_EQ(5u, reportedAtomFields.size());
+}
+
} // namespace android
diff --git a/services/inputflinger/InputFilter.cpp b/services/inputflinger/InputFilter.cpp
index 1b8fad3..9c4a3eb 100644
--- a/services/inputflinger/InputFilter.cpp
+++ b/services/inputflinger/InputFilter.cpp
@@ -22,16 +22,19 @@
using aidl::com::android::server::inputflinger::IInputFilter;
using AidlKeyEvent = aidl::com::android::server::inputflinger::KeyEvent;
+using aidl::com::android::server::inputflinger::KeyEventAction;
+using AidlDeviceInfo = aidl::com::android::server::inputflinger::DeviceInfo;
+using aidl::android::hardware::input::common::Source;
AidlKeyEvent notifyKeyArgsToKeyEvent(const NotifyKeyArgs& args) {
AidlKeyEvent event;
event.id = args.id;
event.eventTime = args.eventTime;
event.deviceId = args.deviceId;
- event.source = args.source;
+ event.source = static_cast<Source>(args.source);
event.displayId = args.displayId;
event.policyFlags = args.policyFlags;
- event.action = args.action;
+ event.action = static_cast<KeyEventAction>(args.action);
event.flags = args.flags;
event.keyCode = args.keyCode;
event.scanCode = args.scanCode;
@@ -42,9 +45,10 @@
}
NotifyKeyArgs keyEventToNotifyKeyArgs(const AidlKeyEvent& event) {
- return NotifyKeyArgs(event.id, event.eventTime, event.readTime, event.deviceId, event.source,
- event.displayId, event.policyFlags, event.action, event.flags,
- event.keyCode, event.scanCode, event.metaState, event.downTime);
+ return NotifyKeyArgs(event.id, event.eventTime, event.readTime, event.deviceId,
+ static_cast<uint32_t>(event.source), event.displayId, event.policyFlags,
+ static_cast<int32_t>(event.action), event.flags, event.keyCode,
+ event.scanCode, event.metaState, event.downTime);
}
namespace {
@@ -71,11 +75,14 @@
void InputFilter::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) {
if (isFilterEnabled()) {
- std::vector<int32_t> deviceIds;
+ std::vector<AidlDeviceInfo> deviceInfos;
for (auto info : args.inputDeviceInfos) {
- deviceIds.push_back(info.getId());
+ AidlDeviceInfo aidlInfo;
+ aidlInfo.deviceId = info.getId();
+ aidlInfo.external = info.isExternal();
+ deviceInfos.push_back(aidlInfo);
}
- LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyInputDevicesChanged(deviceIds).isOk());
+ LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyInputDevicesChanged(deviceInfos).isOk());
}
mNextListener.notify(args);
}
@@ -122,6 +129,15 @@
return result;
}
+void InputFilter::setAccessibilityBounceKeysThreshold(nsecs_t threshold) {
+ std::scoped_lock _l(mLock);
+
+ if (mConfig.bounceKeysThresholdNs != threshold) {
+ mConfig.bounceKeysThresholdNs = threshold;
+ LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyConfigurationChanged(mConfig).isOk());
+ }
+}
+
void InputFilter::dump(std::string& dump) {
dump += "InputFilter:\n";
}
diff --git a/services/inputflinger/InputFilter.h b/services/inputflinger/InputFilter.h
index 699f3a0..06f7d0e 100644
--- a/services/inputflinger/InputFilter.h
+++ b/services/inputflinger/InputFilter.h
@@ -17,6 +17,7 @@
#pragma once
#include <aidl/com/android/server/inputflinger/IInputFlingerRust.h>
+#include <utils/Mutex.h>
#include "InputListener.h"
#include "NotifyArgs.h"
@@ -31,6 +32,7 @@
* This method may be called on any thread (usually by the input manager on a binder thread).
*/
virtual void dump(std::string& dump) = 0;
+ virtual void setAccessibilityBounceKeysThreshold(nsecs_t threshold) = 0;
};
class InputFilter : public InputFilterInterface {
@@ -39,6 +41,8 @@
using IInputFilter = aidl::com::android::server::inputflinger::IInputFilter;
using IInputFilterCallbacks =
aidl::com::android::server::inputflinger::IInputFilter::IInputFilterCallbacks;
+ using InputFilterConfiguration =
+ aidl::com::android::server::inputflinger::InputFilterConfiguration;
explicit InputFilter(InputListenerInterface& listener, IInputFlingerRust&);
~InputFilter() override = default;
@@ -51,12 +55,15 @@
void notifyVibratorState(const NotifyVibratorStateArgs& args) override;
void notifyDeviceReset(const NotifyDeviceResetArgs& args) override;
void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override;
+ void setAccessibilityBounceKeysThreshold(nsecs_t threshold) override;
void dump(std::string& dump) override;
private:
InputListenerInterface& mNextListener;
std::shared_ptr<IInputFilterCallbacks> mCallbacks;
std::shared_ptr<IInputFilter> mInputFilterRust;
+ mutable std::mutex mLock;
+ InputFilterConfiguration mConfig GUARDED_BY(mLock);
bool isFilterEnabled();
};
diff --git a/services/inputflinger/InputManager.cpp b/services/inputflinger/InputManager.cpp
index af4ba5a..296f244 100644
--- a/services/inputflinger/InputManager.cpp
+++ b/services/inputflinger/InputManager.cpp
@@ -224,6 +224,10 @@
return *mDispatcher;
}
+InputFilterInterface& InputManager::getInputFilter() {
+ return *mInputFilter;
+}
+
void InputManager::monitor() {
mReader->monitor();
mBlocker->monitor();
diff --git a/services/inputflinger/InputManager.h b/services/inputflinger/InputManager.h
index aea7bd5..fa7db37 100644
--- a/services/inputflinger/InputManager.h
+++ b/services/inputflinger/InputManager.h
@@ -102,6 +102,9 @@
/* Gets the input dispatcher. */
virtual InputDispatcherInterface& getDispatcher() = 0;
+ /* Gets the input filter */
+ virtual InputFilterInterface& getInputFilter() = 0;
+
/* Check that the input stages have not deadlocked. */
virtual void monitor() = 0;
@@ -126,6 +129,7 @@
InputProcessorInterface& getProcessor() override;
InputDeviceMetricsCollectorInterface& getMetricsCollector() override;
InputDispatcherInterface& getDispatcher() override;
+ InputFilterInterface& getInputFilter() override;
void monitor() override;
void dump(std::string& dump) override;
diff --git a/services/inputflinger/aidl/Android.bp b/services/inputflinger/aidl/Android.bp
index 314c433..d068129 100644
--- a/services/inputflinger/aidl/Android.bp
+++ b/services/inputflinger/aidl/Android.bp
@@ -17,6 +17,9 @@
srcs: ["**/*.aidl"],
unstable: true,
host_supported: true,
+ imports: [
+ "android.hardware.input.common-V1",
+ ],
backend: {
cpp: {
enabled: false,
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/DeviceInfo.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/DeviceInfo.aidl
new file mode 100644
index 0000000..b9e6a03
--- /dev/null
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/DeviceInfo.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputflinger;
+
+/**
+ * Analogous to Android's InputDeviceInfo
+ * Stores the basic information connected input devices.
+ */
+parcelable DeviceInfo {
+ int deviceId;
+ boolean external;
+}
\ No newline at end of file
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl
index 44f959e..14b41cd 100644
--- a/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl
@@ -16,6 +16,8 @@
package com.android.server.inputflinger;
+import com.android.server.inputflinger.DeviceInfo;
+import com.android.server.inputflinger.InputFilterConfiguration;
import com.android.server.inputflinger.KeyEvent;
/**
@@ -40,6 +42,9 @@
void notifyKey(in KeyEvent event);
/** Notifies if any InputDevice list changed and provides the list of connected peripherals */
- void notifyInputDevicesChanged(in int[] deviceIds);
+ void notifyInputDevicesChanged(in DeviceInfo[] deviceInfos);
+
+ /** Notifies when configuration changes */
+ void notifyConfigurationChanged(in InputFilterConfiguration config);
}
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl
new file mode 100644
index 0000000..3b2e88b
--- /dev/null
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputflinger;
+
+/**
+ * Contains data for the current Input filter configuration
+ */
+parcelable InputFilterConfiguration {
+ // Threshold value for Bounce keys filter (check bounce_keys_filter.rs)
+ long bounceKeysThresholdNs;
+}
\ No newline at end of file
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/KeyEvent.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/KeyEvent.aidl
index e213221..2cae6e1 100644
--- a/services/inputflinger/aidl/com/android/server/inputflinger/KeyEvent.aidl
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/KeyEvent.aidl
@@ -16,20 +16,24 @@
package com.android.server.inputflinger;
+import android.hardware.input.common.Source;
+import com.android.server.inputflinger.KeyEventAction;
+
/**
* Analogous to Android's native KeyEvent / NotifyKeyArgs.
* Stores the basic information about Key events.
*/
+@RustDerive(Copy=true, Clone=true, Eq=true, PartialEq=true)
parcelable KeyEvent {
int id;
int deviceId;
long downTime;
long readTime;
long eventTime;
- int source;
+ Source source;
int displayId;
int policyFlags;
- int action;
+ KeyEventAction action;
int flags;
int keyCode;
int scanCode;
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/KeyEventAction.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/KeyEventAction.aidl
new file mode 100644
index 0000000..43ee5fe
--- /dev/null
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/KeyEventAction.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputflinger;
+
+/** Different Key event actions */
+enum KeyEventAction {
+ /** The key has been pressed down. */
+ DOWN = 0,
+
+ /** The key has been released. */
+ UP = 1,
+
+ /**
+ * Multiple duplicate key events have occurred in a row, or a
+ * complex string is being delivered. The repeat_count property
+ * of the key event contains the number of times the given key
+ * code should be executed.
+ *
+ * NOTE: This is deprecated and should never be used. This just
+ * for consistency with KeyEvent actions defined in NotifyKeyArgs.
+ */
+ MULTIPLE = 2
+}
\ No newline at end of file
diff --git a/services/inputflinger/rust/Android.bp b/services/inputflinger/rust/Android.bp
index 2775bcc..2803805 100644
--- a/services/inputflinger/rust/Android.bp
+++ b/services/inputflinger/rust/Android.bp
@@ -38,6 +38,7 @@
rustlibs: [
"libcxx",
"com.android.server.inputflinger-rust",
+ "android.hardware.input.common-V1-rust",
"libbinder_rs",
"liblog_rust",
"liblogger",
diff --git a/services/inputflinger/rust/bounce_keys_filter.rs b/services/inputflinger/rust/bounce_keys_filter.rs
new file mode 100644
index 0000000..894b881
--- /dev/null
+++ b/services/inputflinger/rust/bounce_keys_filter.rs
@@ -0,0 +1,289 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! Bounce keys input filter implementation.
+//! Bounce keys is an accessibility feature to aid users who have physical disabilities, that
+//! allows the user to configure the device to ignore rapid, repeated key presses of the same key.
+use crate::input_filter::Filter;
+
+use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source;
+use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+ DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction,
+};
+use log::debug;
+use std::collections::{HashMap, HashSet};
+
+#[derive(Debug)]
+struct LastUpKeyEvent {
+ keycode: i32,
+ event_time: i64,
+}
+
+#[derive(Debug)]
+struct BlockedEvent {
+ device_id: i32,
+ keycode: i32,
+}
+
+pub struct BounceKeysFilter {
+ next: Box<dyn Filter + Send + Sync>,
+ key_event_map: HashMap<i32, LastUpKeyEvent>,
+ blocked_events: Vec<BlockedEvent>,
+ external_devices: HashSet<i32>,
+ bounce_key_threshold_ns: i64,
+}
+
+impl BounceKeysFilter {
+ /// Create a new BounceKeysFilter instance.
+ pub fn new(
+ next: Box<dyn Filter + Send + Sync>,
+ bounce_key_threshold_ns: i64,
+ ) -> BounceKeysFilter {
+ Self {
+ next,
+ key_event_map: HashMap::new(),
+ blocked_events: Vec::new(),
+ external_devices: HashSet::new(),
+ bounce_key_threshold_ns,
+ }
+ }
+}
+
+impl Filter for BounceKeysFilter {
+ fn notify_key(&mut self, event: &KeyEvent) {
+ if !(self.external_devices.contains(&event.deviceId) && event.source == Source::KEYBOARD) {
+ self.next.notify_key(event);
+ return;
+ }
+ match event.action {
+ KeyEventAction::DOWN => match self.key_event_map.get(&event.deviceId) {
+ None => self.next.notify_key(event),
+ Some(last_up_event) => {
+ if event.keyCode == last_up_event.keycode
+ && event.eventTime < last_up_event.event_time + self.bounce_key_threshold_ns
+ {
+ self.blocked_events.push(BlockedEvent {
+ device_id: event.deviceId,
+ keycode: event.keyCode,
+ });
+ debug!("Event dropped because last up was too recent");
+ } else {
+ self.key_event_map.remove(&event.deviceId);
+ self.next.notify_key(event);
+ }
+ }
+ },
+ KeyEventAction::UP => {
+ self.key_event_map.insert(
+ event.deviceId,
+ LastUpKeyEvent { keycode: event.keyCode, event_time: event.eventTime },
+ );
+ if let Some(index) = self.blocked_events.iter().position(|blocked_event| {
+ blocked_event.device_id == event.deviceId
+ && blocked_event.keycode == event.keyCode
+ }) {
+ self.blocked_events.remove(index);
+ debug!("Event dropped because key down was already dropped");
+ } else {
+ self.next.notify_key(event);
+ }
+ }
+ _ => (),
+ }
+ }
+
+ fn notify_devices_changed(&mut self, device_infos: &[DeviceInfo]) {
+ self.key_event_map.retain(|id, _| device_infos.iter().any(|x| *id == x.deviceId));
+ self.blocked_events.retain(|blocked_event| {
+ device_infos.iter().any(|x| blocked_event.device_id == x.deviceId)
+ });
+ self.external_devices.clear();
+ for device_info in device_infos {
+ if device_info.external {
+ self.external_devices.insert(device_info.deviceId);
+ }
+ }
+ self.next.notify_devices_changed(device_infos);
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::bounce_keys_filter::BounceKeysFilter;
+ use crate::input_filter::{test_filter::TestFilter, Filter};
+ use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source;
+ use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+ DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction,
+ };
+
+ static BASE_KEY_EVENT: KeyEvent = KeyEvent {
+ id: 1,
+ deviceId: 1,
+ downTime: 0,
+ readTime: 0,
+ eventTime: 0,
+ source: Source::KEYBOARD,
+ displayId: 0,
+ policyFlags: 0,
+ action: KeyEventAction::DOWN,
+ flags: 0,
+ keyCode: 1,
+ scanCode: 0,
+ metaState: 0,
+ };
+
+ #[test]
+ fn test_is_notify_key_for_external_keyboard() {
+ let mut next = TestFilter::new();
+ let mut filter = setup_filter_with_external_device(
+ Box::new(next.clone()),
+ 1, /* device_id */
+ 100, /* threshold */
+ );
+
+ let event = KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+ filter.notify_key(&event);
+ assert_eq!(next.last_event().unwrap(), event);
+
+ let event = KeyEvent { action: KeyEventAction::UP, ..BASE_KEY_EVENT };
+ filter.notify_key(&event);
+ assert_eq!(next.last_event().unwrap(), event);
+
+ next.clear();
+ let event = KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+ filter.notify_key(&event);
+ assert!(next.last_event().is_none());
+
+ let event = KeyEvent { eventTime: 100, action: KeyEventAction::UP, ..BASE_KEY_EVENT };
+ filter.notify_key(&event);
+ assert!(next.last_event().is_none());
+
+ let event = KeyEvent { eventTime: 200, action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+ filter.notify_key(&event);
+ assert_eq!(next.last_event().unwrap(), event);
+ }
+
+ #[test]
+ fn test_is_notify_key_doesnt_block_for_internal_keyboard() {
+ let next = TestFilter::new();
+ let mut filter = setup_filter_with_internal_device(
+ Box::new(next.clone()),
+ 1, /* device_id */
+ 100, /* threshold */
+ );
+
+ let event = KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+ filter.notify_key(&event);
+ assert_eq!(next.last_event().unwrap(), event);
+
+ let event = KeyEvent { action: KeyEventAction::UP, ..BASE_KEY_EVENT };
+ filter.notify_key(&event);
+ assert_eq!(next.last_event().unwrap(), event);
+
+ let event = KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+ filter.notify_key(&event);
+ assert_eq!(next.last_event().unwrap(), event);
+ }
+
+ #[test]
+ fn test_is_notify_key_doesnt_block_for_external_stylus() {
+ let next = TestFilter::new();
+ let mut filter = setup_filter_with_external_device(
+ Box::new(next.clone()),
+ 1, /* device_id */
+ 100, /* threshold */
+ );
+
+ let event =
+ KeyEvent { action: KeyEventAction::DOWN, source: Source::STYLUS, ..BASE_KEY_EVENT };
+ filter.notify_key(&event);
+ assert_eq!(next.last_event().unwrap(), event);
+
+ let event =
+ KeyEvent { action: KeyEventAction::UP, source: Source::STYLUS, ..BASE_KEY_EVENT };
+ filter.notify_key(&event);
+ assert_eq!(next.last_event().unwrap(), event);
+
+ let event =
+ KeyEvent { action: KeyEventAction::DOWN, source: Source::STYLUS, ..BASE_KEY_EVENT };
+ filter.notify_key(&event);
+ assert_eq!(next.last_event().unwrap(), event);
+ }
+
+ #[test]
+ fn test_is_notify_key_for_multiple_external_keyboards() {
+ let mut next = TestFilter::new();
+ let mut filter = setup_filter_with_devices(
+ Box::new(next.clone()),
+ &[
+ DeviceInfo { deviceId: 1, external: true },
+ DeviceInfo { deviceId: 2, external: true },
+ ],
+ 100, /* threshold */
+ );
+
+ let event = KeyEvent { deviceId: 1, action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+ filter.notify_key(&event);
+ assert_eq!(next.last_event().unwrap(), event);
+
+ let event = KeyEvent { deviceId: 1, action: KeyEventAction::UP, ..BASE_KEY_EVENT };
+ filter.notify_key(&event);
+ assert_eq!(next.last_event().unwrap(), event);
+
+ next.clear();
+ let event = KeyEvent { deviceId: 1, action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+ filter.notify_key(&event);
+ assert!(next.last_event().is_none());
+
+ let event = KeyEvent { deviceId: 2, action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+ filter.notify_key(&event);
+ assert_eq!(next.last_event().unwrap(), event);
+ }
+
+ fn setup_filter_with_external_device(
+ next: Box<dyn Filter + Send + Sync>,
+ device_id: i32,
+ threshold: i64,
+ ) -> BounceKeysFilter {
+ setup_filter_with_devices(
+ next,
+ &[DeviceInfo { deviceId: device_id, external: true }],
+ threshold,
+ )
+ }
+
+ fn setup_filter_with_internal_device(
+ next: Box<dyn Filter + Send + Sync>,
+ device_id: i32,
+ threshold: i64,
+ ) -> BounceKeysFilter {
+ setup_filter_with_devices(
+ next,
+ &[DeviceInfo { deviceId: device_id, external: false }],
+ threshold,
+ )
+ }
+
+ fn setup_filter_with_devices(
+ next: Box<dyn Filter + Send + Sync>,
+ devices: &[DeviceInfo],
+ threshold: i64,
+ ) -> BounceKeysFilter {
+ let mut filter = BounceKeysFilter::new(next, threshold);
+ filter.notify_devices_changed(devices);
+ filter
+ }
+}
diff --git a/services/inputflinger/rust/input_filter.rs b/services/inputflinger/rust/input_filter.rs
index 5851877..340ff8e 100644
--- a/services/inputflinger/rust/input_filter.rs
+++ b/services/inputflinger/rust/input_filter.rs
@@ -16,17 +16,39 @@
//! InputFilter manages all the filtering components that can intercept events, modify the events,
//! block events, etc depending on the situation. This will be used support Accessibility features
-//! like Slow keys, Bounce keys, etc.
+//! like Sticky keys, Slow keys, Bounce keys, etc.
use binder::{Interface, Strong};
use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+ DeviceInfo::DeviceInfo,
IInputFilter::{IInputFilter, IInputFilterCallbacks::IInputFilterCallbacks},
+ InputFilterConfiguration::InputFilterConfiguration,
KeyEvent::KeyEvent,
};
+use crate::bounce_keys_filter::BounceKeysFilter;
+use log::{error, info};
+use std::sync::{Arc, Mutex, RwLock};
+
+/// Interface for all the sub input filters
+pub trait Filter {
+ fn notify_key(&mut self, event: &KeyEvent);
+ fn notify_devices_changed(&mut self, device_infos: &[DeviceInfo]);
+}
+
+struct InputFilterState {
+ first_filter: Box<dyn Filter + Send + Sync>,
+ enabled: bool,
+}
+
/// The rust implementation of InputFilter
pub struct InputFilter {
- callbacks: Strong<dyn IInputFilterCallbacks>,
+ // In order to have multiple immutable references to the callbacks that is thread safe need to
+ // wrap the callbacks in Arc<RwLock<...>>
+ callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>,
+ // Access to mutable references to mutable state (includes access to filters, enabled, etc.) is
+ // guarded by Mutex for thread safety
+ state: Mutex<InputFilterState>,
}
impl Interface for InputFilter {}
@@ -34,35 +56,87 @@
impl InputFilter {
/// Create a new InputFilter instance.
pub fn new(callbacks: Strong<dyn IInputFilterCallbacks>) -> InputFilter {
- Self { callbacks }
+ let ref_callbacks = Arc::new(RwLock::new(callbacks));
+ let base_filter = Box::new(BaseFilter::new(ref_callbacks.clone()));
+ Self::create_input_filter(base_filter, ref_callbacks)
+ }
+
+ /// Create test instance of InputFilter
+ fn create_input_filter(
+ first_filter: Box<dyn Filter + Send + Sync>,
+ callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>,
+ ) -> InputFilter {
+ Self { callbacks, state: Mutex::new(InputFilterState { first_filter, enabled: false }) }
}
}
impl IInputFilter for InputFilter {
fn isEnabled(&self) -> binder::Result<bool> {
- // TODO(b/294546335): Return true if any filters are to be applied, false otherwise
- Result::Ok(false)
+ Result::Ok(self.state.lock().unwrap().enabled)
}
+
fn notifyKey(&self, event: &KeyEvent) -> binder::Result<()> {
- // TODO(b/294546335): Handle key event and modify key events here
- // Just send back the event without processing for now.
- let _ = self.callbacks.sendKeyEvent(event);
+ let first_filter = &mut self.state.lock().unwrap().first_filter;
+ first_filter.notify_key(event);
Result::Ok(())
}
- fn notifyInputDevicesChanged(&self, _device_ids: &[i32]) -> binder::Result<()> {
- // TODO(b/294546335): Update data based on device changes here
+
+ fn notifyInputDevicesChanged(&self, device_infos: &[DeviceInfo]) -> binder::Result<()> {
+ let first_filter = &mut self.state.lock().unwrap().first_filter;
+ first_filter.notify_devices_changed(device_infos);
Result::Ok(())
}
+
+ fn notifyConfigurationChanged(&self, config: &InputFilterConfiguration) -> binder::Result<()> {
+ let mut state = self.state.lock().unwrap();
+ let mut first_filter: Box<dyn Filter + Send + Sync> =
+ Box::new(BaseFilter::new(self.callbacks.clone()));
+ if config.bounceKeysThresholdNs > 0 {
+ first_filter =
+ Box::new(BounceKeysFilter::new(first_filter, config.bounceKeysThresholdNs));
+ state.enabled = true;
+ info!("Bounce keys filter is installed");
+ }
+ state.first_filter = first_filter;
+ Result::Ok(())
+ }
+}
+
+struct BaseFilter {
+ callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>,
+}
+
+impl BaseFilter {
+ fn new(callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>) -> BaseFilter {
+ Self { callbacks }
+ }
+}
+
+impl Filter for BaseFilter {
+ fn notify_key(&mut self, event: &KeyEvent) {
+ match self.callbacks.read().unwrap().sendKeyEvent(event) {
+ Ok(_) => (),
+ _ => error!("Failed to send key event back to native C++"),
+ }
+ }
+
+ fn notify_devices_changed(&mut self, _device_infos: &[DeviceInfo]) {
+ // do nothing
+ }
}
#[cfg(test)]
mod tests {
- use crate::input_filter::InputFilter;
+ use crate::input_filter::{test_filter::TestFilter, Filter, InputFilter};
+ use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source;
use binder::{Interface, Strong};
use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
- IInputFilter::IInputFilter, IInputFilter::IInputFilterCallbacks::IInputFilterCallbacks,
- KeyEvent::KeyEvent,
+ DeviceInfo::DeviceInfo, IInputFilter::IInputFilter,
+ IInputFilter::IInputFilterCallbacks::IInputFilterCallbacks,
+ InputFilterConfiguration::InputFilterConfiguration, KeyEvent::KeyEvent,
+ KeyEventAction::KeyEventAction,
};
+ use std::sync::{Arc, RwLock};
struct FakeCallbacks {}
@@ -75,31 +149,60 @@
}
#[test]
- fn test_is_enabled() {
+ fn test_not_enabled_with_default_filter() {
let fake_callbacks: Strong<dyn IInputFilterCallbacks> =
Strong::new(Box::new(FakeCallbacks {}));
- let filter: Box<dyn IInputFilter> = Box::new(InputFilter::new(fake_callbacks));
- let result = filter.isEnabled();
+ let input_filter = InputFilter::new(fake_callbacks);
+ let result = input_filter.isEnabled();
assert!(result.is_ok());
assert!(!result.unwrap());
}
#[test]
- fn test_notify_key() {
+ fn test_notify_key_with_no_filters() {
let fake_callbacks: Strong<dyn IInputFilterCallbacks> =
Strong::new(Box::new(FakeCallbacks {}));
- let filter: Box<dyn IInputFilter> = Box::new(InputFilter::new(fake_callbacks));
+ let input_filter = InputFilter::new(fake_callbacks);
let event = create_key_event();
- assert!(filter.notifyKey(&event).is_ok());
+ assert!(input_filter.notifyKey(&event).is_ok());
+ }
+
+ #[test]
+ fn test_notify_key_with_filter() {
+ let test_filter = TestFilter::new();
+ let input_filter = create_input_filter(Box::new(test_filter.clone()));
+ let event = create_key_event();
+ assert!(input_filter.notifyKey(&event).is_ok());
+ assert_eq!(test_filter.last_event().unwrap(), event);
}
#[test]
fn test_notify_devices_changed() {
+ let test_filter = TestFilter::new();
+ let input_filter = create_input_filter(Box::new(test_filter.clone()));
+ assert!(input_filter
+ .notifyInputDevicesChanged(&[DeviceInfo { deviceId: 0, external: true }])
+ .is_ok());
+ assert!(test_filter.is_device_changed_called());
+ }
+
+ #[test]
+ fn test_notify_configuration_changed_enabled_bounce_keys() {
let fake_callbacks: Strong<dyn IInputFilterCallbacks> =
Strong::new(Box::new(FakeCallbacks {}));
- let filter: Box<dyn IInputFilter> = Box::new(InputFilter::new(fake_callbacks));
- let result = filter.notifyInputDevicesChanged(&[0]);
+ let input_filter = InputFilter::new(fake_callbacks);
+ let result = input_filter
+ .notifyConfigurationChanged(&InputFilterConfiguration { bounceKeysThresholdNs: 100 });
assert!(result.is_ok());
+ let result = input_filter.isEnabled();
+ assert!(result.is_ok());
+ assert!(result.unwrap());
+ }
+
+ fn create_input_filter(filter: Box<dyn Filter + Send + Sync>) -> InputFilter {
+ let fake_callbacks: Strong<dyn IInputFilterCallbacks> =
+ Strong::new(Box::new(FakeCallbacks {}));
+ InputFilter::create_input_filter(filter, Arc::new(RwLock::new(fake_callbacks)))
}
fn create_key_event() -> KeyEvent {
@@ -109,10 +212,10 @@
downTime: 0,
readTime: 0,
eventTime: 0,
- source: 0,
+ source: Source::KEYBOARD,
displayId: 0,
policyFlags: 0,
- action: 0,
+ action: KeyEventAction::DOWN,
flags: 0,
keyCode: 0,
scanCode: 0,
@@ -120,3 +223,52 @@
}
}
}
+
+#[cfg(test)]
+pub mod test_filter {
+ use crate::input_filter::Filter;
+ use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+ DeviceInfo::DeviceInfo, KeyEvent::KeyEvent,
+ };
+ use std::sync::{Arc, RwLock, RwLockWriteGuard};
+
+ #[derive(Default)]
+ struct TestFilterInner {
+ is_device_changed_called: bool,
+ last_event: Option<KeyEvent>,
+ }
+
+ #[derive(Default, Clone)]
+ pub struct TestFilter(Arc<RwLock<TestFilterInner>>);
+
+ impl TestFilter {
+ pub fn new() -> Self {
+ Default::default()
+ }
+
+ fn inner(&mut self) -> RwLockWriteGuard<'_, TestFilterInner> {
+ self.0.write().unwrap()
+ }
+
+ pub fn last_event(&self) -> Option<KeyEvent> {
+ self.0.read().unwrap().last_event
+ }
+
+ pub fn clear(&mut self) {
+ self.inner().last_event = None
+ }
+
+ pub fn is_device_changed_called(&self) -> bool {
+ self.0.read().unwrap().is_device_changed_called
+ }
+ }
+
+ impl Filter for TestFilter {
+ fn notify_key(&mut self, event: &KeyEvent) {
+ self.inner().last_event = Some(*event);
+ }
+ fn notify_devices_changed(&mut self, _device_infos: &[DeviceInfo]) {
+ self.inner().is_device_changed_called = true;
+ }
+ }
+}
diff --git a/services/inputflinger/rust/lib.rs b/services/inputflinger/rust/lib.rs
index a4049d5..68cd480 100644
--- a/services/inputflinger/rust/lib.rs
+++ b/services/inputflinger/rust/lib.rs
@@ -19,6 +19,7 @@
//! We use cxxbridge to create IInputFlingerRust - the Rust component of inputflinger - and
//! pass it back to C++ as a local AIDL interface.
+mod bounce_keys_filter;
mod input_filter;
use crate::input_filter::InputFilter;
diff --git a/services/inputflinger/tests/CursorInputMapper_test.cpp b/services/inputflinger/tests/CursorInputMapper_test.cpp
index b55c9cc..6d6b7d8 100644
--- a/services/inputflinger/tests/CursorInputMapper_test.cpp
+++ b/services/inputflinger/tests/CursorInputMapper_test.cpp
@@ -17,6 +17,7 @@
#include "CursorInputMapper.h"
#include <android-base/logging.h>
+#include <com_android_input_flags.h>
#include <gtest/gtest.h>
#include "FakePointerController.h"
@@ -38,6 +39,12 @@
constexpr auto BUTTON_RELEASE = AMOTION_EVENT_ACTION_BUTTON_RELEASE;
constexpr auto HOVER_MOVE = AMOTION_EVENT_ACTION_HOVER_MOVE;
constexpr auto INVALID_CURSOR_POSITION = AMOTION_EVENT_INVALID_CURSOR_POSITION;
+constexpr int32_t DISPLAY_ID = 0;
+constexpr int32_t DISPLAY_WIDTH = 480;
+constexpr int32_t DISPLAY_HEIGHT = 800;
+constexpr std::optional<uint8_t> NO_PORT = std::nullopt; // no physical port is specified
+
+namespace input_flags = com::android::input::flags;
/**
* Unit tests for CursorInputMapper.
@@ -60,6 +67,11 @@
EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL))
.WillRepeatedly(Return(false));
+ mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
+ mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+ /*isActive=*/true, "local:0", NO_PORT,
+ ViewportType::INTERNAL);
+
mMapper = createInputMapper<CursorInputMapper>(*mDeviceContext, mReaderConfiguration);
}
@@ -139,6 +151,7 @@
ElementsAre(VariantWith<NotifyMotionArgs>(
AllOf(WithMotionAction(ACTION_MOVE),
WithSource(AINPUT_SOURCE_MOUSE_RELATIVE), WithCoords(10.0f, 20.0f),
+ WithRelativeMotion(10.0f, 20.0f),
WithCursorPosition(INVALID_CURSOR_POSITION,
INVALID_CURSOR_POSITION)))));
@@ -178,12 +191,17 @@
ASSERT_THAT(args,
ElementsAre(VariantWith<NotifyMotionArgs>(
AllOf(WithMotionAction(ACTION_MOVE),
- WithSource(AINPUT_SOURCE_MOUSE_RELATIVE),
- WithCoords(30.0f, 40.0f)))));
+ WithSource(AINPUT_SOURCE_MOUSE_RELATIVE), WithCoords(30.0f, 40.0f),
+ WithRelativeMotion(30.0f, 40.0f)))));
// Disable pointer capture. Afterwards, events should be generated the usual way.
setPointerCapture(false);
-
+ const auto expectedCoords = input_flags::enable_pointer_choreographer()
+ ? WithCoords(0, 0)
+ : WithCoords(INITIAL_CURSOR_X + 10.0f, INITIAL_CURSOR_Y + 20.0f);
+ const auto expectedCursorPosition = input_flags::enable_pointer_choreographer()
+ ? WithCursorPosition(INVALID_CURSOR_POSITION, INVALID_CURSOR_POSITION)
+ : WithCursorPosition(INITIAL_CURSOR_X + 10.0f, INITIAL_CURSOR_Y + 20.0f);
args.clear();
args += process(EV_REL, REL_X, 10);
args += process(EV_REL, REL_Y, 20);
@@ -191,9 +209,8 @@
ASSERT_THAT(args,
ElementsAre(VariantWith<NotifyMotionArgs>(
AllOf(WithMotionAction(HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE),
- WithCoords(INITIAL_CURSOR_X + 10.0f, INITIAL_CURSOR_Y + 20.0f),
- WithCursorPosition(INITIAL_CURSOR_X + 10.0f,
- INITIAL_CURSOR_Y + 20.0f)))));
+ expectedCoords, expectedCursorPosition,
+ WithRelativeMotion(10.0f, 20.0f)))));
}
} // namespace android
diff --git a/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp
index 214649c..0bd8fb3 100644
--- a/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp
@@ -41,14 +41,16 @@
class NotifyStreamProvider {
public:
NotifyStreamProvider(FuzzedDataProvider& fdp)
- : mFdp(fdp), mIdGenerator(IdGenerator::Source::OTHER), mVerifier("Fuzz verifier") {}
+ : mFdp(fdp), mIdGenerator(IdGenerator::Source::OTHER) {}
std::optional<NotifyMotionArgs> nextMotion() {
NotifyMotionArgs args = generateFuzzedMotionArgs(mIdGenerator, mFdp, MAX_RANDOM_DISPLAYS);
+ auto [it, _] = mVerifiers.emplace(args.displayId, "Fuzz Verifier");
+ InputVerifier& verifier = it->second;
const Result<void> result =
- mVerifier.processMovement(args.deviceId, args.source, args.action,
- args.getPointerCount(), args.pointerProperties.data(),
- args.pointerCoords.data(), args.flags);
+ verifier.processMovement(args.deviceId, args.source, args.action,
+ args.getPointerCount(), args.pointerProperties.data(),
+ args.pointerCoords.data(), args.flags);
if (result.ok()) {
return args;
}
@@ -60,7 +62,7 @@
IdGenerator mIdGenerator;
- InputVerifier mVerifier;
+ std::map<int32_t /*displayId*/, InputVerifier> mVerifiers;
};
} // namespace
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 8311617..772fcd1 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -4362,7 +4362,8 @@
int sampleSize = (layer->getChildrenCount() / 100) + 1;
layer->traverseChildren([&](Layer* layer) {
if (rand() % sampleSize == 0) {
- ALOGE("Child Layer: %s", layer->getName().c_str());
+ ALOGE("Child Layer: %s%s", layer->getName().c_str(),
+ (layer->isHandleAlive() ? "handleAlive" : ""));
}
});
}