Merge "Expose timestats device library"
diff --git a/cmds/lshal/Android.bp b/cmds/lshal/Android.bp
index 1994e56..0cbb80f 100644
--- a/cmds/lshal/Android.bp
+++ b/cmds/lshal/Android.bp
@@ -16,6 +16,7 @@
name: "liblshal",
shared_libs: [
"libbase",
+ "libbinderdebug",
"libcutils",
"libutils",
"libhidlbase",
@@ -47,6 +48,7 @@
name: "lshal_defaults",
shared_libs: [
"libbase",
+ "libbinderdebug",
"libcutils",
"libutils",
"libhidlbase",
diff --git a/cmds/lshal/ListCommand.cpp b/cmds/lshal/ListCommand.cpp
index 92958d9..22268ac 100644
--- a/cmds/lshal/ListCommand.cpp
+++ b/cmds/lshal/ListCommand.cpp
@@ -29,7 +29,6 @@
#include <android-base/file.h>
#include <android-base/logging.h>
-#include <android-base/parseint.h>
#include <android/hidl/manager/1.0/IServiceManager.h>
#include <hidl-hash/Hash.h>
#include <hidl-util/FQName.h>
@@ -203,97 +202,14 @@
lshal::getVintfInfo(getFrameworkMatrix(), fqInstance, ta, FRAMEWORK_MATRIX);
}
-static bool scanBinderContext(pid_t pid,
- const std::string &contextName,
- std::function<void(const std::string&)> eachLine) {
- std::ifstream ifs("/dev/binderfs/binder_logs/proc/" + std::to_string(pid));
- if (!ifs.is_open()) {
- ifs.open("/d/binder/proc/" + std::to_string(pid));
- if (!ifs.is_open()) {
- return false;
- }
- }
-
- static const std::regex kContextLine("^context (\\w+)$");
-
- bool isDesiredContext = false;
- std::string line;
- std::smatch match;
- while(getline(ifs, line)) {
- if (std::regex_search(line, match, kContextLine)) {
- isDesiredContext = match.str(1) == contextName;
- continue;
- }
-
- if (!isDesiredContext) {
- continue;
- }
-
- eachLine(line);
- }
- return true;
-}
-
bool ListCommand::getPidInfo(
- pid_t serverPid, PidInfo *pidInfo) const {
- static const std::regex kReferencePrefix("^\\s*node \\d+:\\s+u([0-9a-f]+)\\s+c([0-9a-f]+)\\s+");
- static const std::regex kThreadPrefix("^\\s*thread \\d+:\\s+l\\s+(\\d)(\\d)");
-
- std::smatch match;
- return scanBinderContext(serverPid, "hwbinder", [&](const std::string& line) {
- if (std::regex_search(line, match, kReferencePrefix)) {
- const std::string &ptrString = "0x" + match.str(2); // use number after c
- uint64_t ptr;
- if (!::android::base::ParseUint(ptrString.c_str(), &ptr)) {
- // Should not reach here, but just be tolerant.
- err() << "Could not parse number " << ptrString << std::endl;
- return;
- }
- const std::string proc = " proc ";
- auto pos = line.rfind(proc);
- if (pos != std::string::npos) {
- for (const std::string &pidStr : split(line.substr(pos + proc.size()), ' ')) {
- int32_t pid;
- if (!::android::base::ParseInt(pidStr, &pid)) {
- err() << "Could not parse number " << pidStr << std::endl;
- return;
- }
- pidInfo->refPids[ptr].push_back(pid);
- }
- }
-
- return;
- }
-
- if (std::regex_search(line, match, kThreadPrefix)) {
- // "1" is waiting in binder driver
- // "2" is poll. It's impossible to tell if these are in use.
- // and HIDL default code doesn't use it.
- bool isInUse = match.str(1) != "1";
- // "0" is a thread that has called into binder
- // "1" is looper thread
- // "2" is main looper thread
- bool isHwbinderThread = match.str(2) != "0";
-
- if (!isHwbinderThread) {
- return;
- }
-
- if (isInUse) {
- pidInfo->threadUsage++;
- }
-
- pidInfo->threadCount++;
- return;
- }
-
- // not reference or thread line
- return;
- });
+ pid_t serverPid, BinderPidInfo *pidInfo) const {
+ const auto& status = getBinderPidInfo(BinderDebugContext::HWBINDER, serverPid, pidInfo);
+ return status == OK;
}
-const PidInfo* ListCommand::getPidInfoCached(pid_t serverPid) {
- auto pair = mCachedPidInfos.insert({serverPid, PidInfo{}});
+const BinderPidInfo* ListCommand::getPidInfoCached(pid_t serverPid) {
+ auto pair = mCachedPidInfos.insert({serverPid, BinderPidInfo{}});
if (pair.second /* did insertion take place? */) {
if (!getPidInfo(serverPid, &pair.first->second)) {
return nullptr;
@@ -727,7 +643,7 @@
entry->arch = fromBaseArchitecture(debugInfo.arch);
if (debugInfo.pid != NO_PID) {
- const PidInfo* pidInfo = getPidInfoCached(debugInfo.pid);
+ const BinderPidInfo* pidInfo = getPidInfoCached(debugInfo.pid);
if (pidInfo == nullptr) {
handleError(IO_ERROR,
"no information for PID " + std::to_string(debugInfo.pid) +
diff --git a/cmds/lshal/ListCommand.h b/cmds/lshal/ListCommand.h
index 412aadd..561f9cb 100644
--- a/cmds/lshal/ListCommand.h
+++ b/cmds/lshal/ListCommand.h
@@ -25,6 +25,7 @@
#include <android-base/macros.h>
#include <android/hidl/manager/1.0/IServiceManager.h>
+#include <binderdebug/BinderDebug.h>
#include <hidl-util/FqInstance.h>
#include <vintf/HalManifest.h>
#include <vintf/VintfObject.h>
@@ -40,12 +41,6 @@
class Lshal;
-struct PidInfo {
- std::map<uint64_t, Pids> refPids; // pids that are referenced
- uint32_t threadUsage; // number of threads in use
- uint32_t threadCount; // number of threads total
-};
-
enum class HalType {
BINDERIZED_SERVICES = 0,
PASSTHROUGH_CLIENTS,
@@ -110,9 +105,9 @@
// Get relevant information for a PID by parsing files under
// /dev/binderfs/binder_logs or /d/binder.
// It is a virtual member function so that it can be mocked.
- virtual bool getPidInfo(pid_t serverPid, PidInfo *info) const;
+ virtual bool getPidInfo(pid_t serverPid, BinderPidInfo *info) const;
// Retrieve from mCachedPidInfos and call getPidInfo if necessary.
- const PidInfo* getPidInfoCached(pid_t serverPid);
+ const BinderPidInfo* getPidInfoCached(pid_t serverPid);
void dumpTable(const NullableOStream<std::ostream>& out) const;
void dumpVintf(const NullableOStream<std::ostream>& out) const;
@@ -191,7 +186,7 @@
std::map<pid_t, std::string> mCmdlines;
// Cache for getPidInfo.
- std::map<pid_t, PidInfo> mCachedPidInfos;
+ std::map<pid_t, BinderPidInfo> mCachedPidInfos;
// Cache for getPartition.
std::map<pid_t, Partition> mPartitions;
diff --git a/cmds/lshal/TableEntry.h b/cmds/lshal/TableEntry.h
index 3c36813..476aa04 100644
--- a/cmds/lshal/TableEntry.h
+++ b/cmds/lshal/TableEntry.h
@@ -32,7 +32,7 @@
namespace lshal {
using android::procpartition::Partition;
-using Pids = std::vector<int32_t>;
+using Pids = std::vector<pid_t>;
enum class TableColumnType : unsigned int {
INTERFACE_NAME = 0,
diff --git a/cmds/lshal/test.cpp b/cmds/lshal/test.cpp
index ba6cdf1..b6ff28d 100644
--- a/cmds/lshal/test.cpp
+++ b/cmds/lshal/test.cpp
@@ -233,12 +233,12 @@
return ListCommand::dumpVintf(out);
}
void internalPostprocess() { ListCommand::postprocess(); }
- const PidInfo* getPidInfoCached(pid_t serverPid) {
+ const BinderPidInfo* getPidInfoCached(pid_t serverPid) {
return ListCommand::getPidInfoCached(serverPid);
}
MOCK_METHOD0(postprocess, void());
- MOCK_CONST_METHOD2(getPidInfo, bool(pid_t, PidInfo*));
+ MOCK_CONST_METHOD2(getPidInfo, bool(pid_t, BinderPidInfo*));
MOCK_CONST_METHOD1(parseCmdline, std::string(pid_t));
MOCK_METHOD1(getPartition, Partition(pid_t));
@@ -299,8 +299,8 @@
static std::vector<pid_t> getClients(pid_t serverId) {
return {serverId + 1, serverId + 3};
}
-static PidInfo getPidInfoFromId(pid_t serverId) {
- PidInfo info;
+static BinderPidInfo getPidInfoFromId(pid_t serverId) {
+ BinderPidInfo info;
info.refPids[getPtr(serverId)] = getClients(serverId);
info.threadUsage = 10 + serverId;
info.threadCount = 20 + serverId;
@@ -363,7 +363,7 @@
void initMockList() {
mockList = std::make_unique<NiceMock<MockListCommand>>(lshal.get());
ON_CALL(*mockList, getPidInfo(_,_)).WillByDefault(Invoke(
- [](pid_t serverPid, PidInfo* info) {
+ [](pid_t serverPid, BinderPidInfo* info) {
*info = getPidInfoFromId(serverPid);
return true;
}));
diff --git a/data/etc/pc_core_hardware.xml b/data/etc/pc_core_hardware.xml
new file mode 100644
index 0000000..c62da0a
--- /dev/null
+++ b/data/etc/pc_core_hardware.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<!-- These are the hardware components that all handheld devices
+ must include. Devices with optional hardware must also include extra
+ hardware files, per the comments below.
+
+ Handheld devices include phones, mobile Internet devices (MIDs),
+ Personal Media Players (PMPs), small tablets (7" or less), and similar
+ devices.
+-->
+<permissions>
+ <!-- This is Android and fully CTS compatible. Basically this is for CTS tests to use. -->
+ <feature name="android.software.cts" />
+
+ <feature name="android.hardware.audio.output" />
+ <feature name="android.hardware.bluetooth" />
+ <feature name="android.hardware.microphone" />
+ <feature name="android.hardware.screen.portrait" />
+ <feature name="android.hardware.screen.landscape" />
+ <feature name="android.hardware.location" />
+ <feature name="android.hardware.location.network" />
+
+ <!-- basic system services -->
+ <feature name="android.software.app_widgets" />
+ <feature name="android.software.voice_recognizers" />
+ <feature name="android.software.backup" />
+ <feature name="android.software.home_screen" />
+ <feature name="android.software.input_methods" />
+ <feature name="android.software.picture_in_picture" />
+ <feature name="android.software.activities_on_secondary_displays" />
+ <feature name="android.software.print" />
+ <feature name="android.software.companion_device_setup" />
+ <feature name="android.software.autofill" />
+ <feature name="android.software.cant_save_state" />
+ <feature name="android.software.secure_lock_screen" />
+
+ <!-- Feature to specify if the device supports adding device admins. -->
+ <feature name="android.software.device_admin" />
+
+ <!-- Feature to specify if the device support managed users. -->
+ <feature name="android.software.managed_users" />
+
+ <!-- Feature to specify if the device supports controls. -->
+ <feature name="android.software.controls" />
+
+ <!-- Feature to specify if the device supports freeform. -->
+ <feature name="android.software.freeform_window_management" />
+ <feature name="android.hardware.type.pc" />
+</permissions>
\ No newline at end of file
diff --git a/include/audiomanager/AudioManager.h b/include/audiomanager/AudioManager.h
index 639df7a..4aa2f60 100644
--- a/include/audiomanager/AudioManager.h
+++ b/include/audiomanager/AudioManager.h
@@ -37,6 +37,7 @@
PLAYER_STATE_STARTED = 2,
PLAYER_STATE_PAUSED = 3,
PLAYER_STATE_STOPPED = 4,
+ PLAYER_UPDATE_DEVICE_ID = 5,
} player_state_t;
// must be kept in sync with definitions in AudioManager.java
diff --git a/include/audiomanager/IAudioManager.h b/include/audiomanager/IAudioManager.h
index 2f5ccb8..7d1f38f 100644
--- a/include/audiomanager/IAudioManager.h
+++ b/include/audiomanager/IAudioManager.h
@@ -49,7 +49,8 @@
audio_content_type_t content, const sp<IBinder>& player) = 0;
/*oneway*/ virtual status_t playerAttributes(audio_unique_id_t piid, audio_usage_t usage,
audio_content_type_t content)= 0;
- /*oneway*/ virtual status_t playerEvent(audio_unique_id_t piid, player_state_t event) = 0;
+ /*oneway*/ virtual status_t playerEvent(audio_unique_id_t piid, player_state_t event,
+ audio_port_handle_t deviceId) = 0;
/*oneway*/ virtual status_t releasePlayer(audio_unique_id_t piid) = 0;
virtual audio_unique_id_t trackRecorder(const sp<IBinder>& recorder) = 0;
/*oneway*/ virtual status_t recorderEvent(audio_unique_id_t riid, recorder_state_t event) = 0;
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index 90feedd..feaea63 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -83,9 +83,6 @@
// or dessert updates. Instead, apex users should use libbinder_ndk.
apex_available: [
"//apex_available:platform",
- // TODO(b/166468760) remove these three
- "com.android.media.swcodec",
- "test_com.android.media.swcodec",
],
srcs: [
@@ -183,6 +180,14 @@
],
tidy_checks_as_errors: [
"*",
+ "-clang-analyzer-core.CallAndMessage",
+ "-clang-analyzer-core.uninitialized.Assign",
+ "-clang-analyzer-unix.Malloc,",
+ "-clang-analyzer-deadcode.DeadStores",
+ "-clang-analyzer-optin.cplusplus.UninitializedObject",
+ "-misc-no-recursion",
+ "-misc-redundant-expression",
+ "-misc-unused-using-decls",
],
}
diff --git a/libs/binder/ndk/Android.bp b/libs/binder/ndk/Android.bp
index a57beee..bdb74dc 100644
--- a/libs/binder/ndk/Android.bp
+++ b/libs/binder/ndk/Android.bp
@@ -105,6 +105,14 @@
],
tidy_checks_as_errors: [
"*",
+ "-clang-analyzer-core.CallAndMessage",
+ "-clang-analyzer-core.uninitialized.Assign",
+ "-clang-analyzer-unix.Malloc,",
+ "-clang-analyzer-deadcode.DeadStores",
+ "-clang-analyzer-optin.cplusplus.UninitializedObject",
+ "-misc-no-recursion",
+ "-misc-redundant-expression",
+ "-misc-unused-using-decls",
],
}
diff --git a/libs/binderdebug/Android.bp b/libs/binderdebug/Android.bp
new file mode 100644
index 0000000..343246a
--- /dev/null
+++ b/libs/binderdebug/Android.bp
@@ -0,0 +1,28 @@
+// Copyright (C) 2020 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.
+
+cc_library {
+ name: "libbinderdebug",
+ vendor_available: true,
+ shared_libs: [
+ "libbase",
+ "libbinder",
+ ],
+ srcs: [
+ "BinderDebug.cpp",
+ ],
+ export_include_dirs: [
+ "include",
+ ],
+}
diff --git a/libs/binderdebug/BinderDebug.cpp b/libs/binderdebug/BinderDebug.cpp
new file mode 100644
index 0000000..b435dba
--- /dev/null
+++ b/libs/binderdebug/BinderDebug.cpp
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
+#include <binder/Binder.h>
+#include <sys/types.h>
+#include <fstream>
+#include <regex>
+
+#include <binderdebug/BinderDebug.h>
+
+namespace android {
+
+static std::string contextToString(BinderDebugContext context) {
+ switch (context) {
+ case BinderDebugContext::BINDER:
+ return "binder";
+ case BinderDebugContext::HWBINDER:
+ return "hwbinder";
+ case BinderDebugContext::VNDBINDER:
+ return "vndbinder";
+ default:
+ return std::string();
+ }
+}
+
+static status_t scanBinderContext(pid_t pid, const std::string& contextName,
+ std::function<void(const std::string&)> eachLine) {
+ std::ifstream ifs("/dev/binderfs/binder_logs/proc/" + std::to_string(pid));
+ if (!ifs.is_open()) {
+ ifs.open("/d/binder/proc/" + std::to_string(pid));
+ if (!ifs.is_open()) {
+ return -errno;
+ }
+ }
+ static const std::regex kContextLine("^context (\\w+)$");
+
+ bool isDesiredContext = false;
+ std::string line;
+ std::smatch match;
+ while (getline(ifs, line)) {
+ if (std::regex_search(line, match, kContextLine)) {
+ isDesiredContext = match.str(1) == contextName;
+ continue;
+ }
+ if (!isDesiredContext) {
+ continue;
+ }
+ eachLine(line);
+ }
+ return OK;
+}
+
+status_t getBinderPidInfo(BinderDebugContext context, pid_t pid, BinderPidInfo* pidInfo) {
+ std::smatch match;
+ static const std::regex kReferencePrefix("^\\s*node \\d+:\\s+u([0-9a-f]+)\\s+c([0-9a-f]+)\\s+");
+ static const std::regex kThreadPrefix("^\\s*thread \\d+:\\s+l\\s+(\\d)(\\d)");
+ std::string contextStr = contextToString(context);
+ status_t ret = scanBinderContext(pid, contextStr, [&](const std::string& line) {
+ if (std::regex_search(line, match, kReferencePrefix)) {
+ const std::string& ptrString = "0x" + match.str(2); // use number after c
+ uint64_t ptr;
+ if (!::android::base::ParseUint(ptrString.c_str(), &ptr)) {
+ // Should not reach here, but just be tolerant.
+ return;
+ }
+ const std::string proc = " proc ";
+ auto pos = line.rfind(proc);
+ if (pos != std::string::npos) {
+ for (const std::string& pidStr : base::Split(line.substr(pos + proc.size()), " ")) {
+ int32_t pid;
+ if (!::android::base::ParseInt(pidStr, &pid)) {
+ return;
+ }
+ pidInfo->refPids[ptr].push_back(pid);
+ }
+ }
+
+ return;
+ }
+ if (std::regex_search(line, match, kThreadPrefix)) {
+ // "1" is waiting in binder driver
+ // "2" is poll. It's impossible to tell if these are in use.
+ // and HIDL default code doesn't use it.
+ bool isInUse = match.str(1) != "1";
+ // "0" is a thread that has called into binder
+ // "1" is looper thread
+ // "2" is main looper thread
+ bool isBinderThread = match.str(2) != "0";
+ if (!isBinderThread) {
+ return;
+ }
+ if (isInUse) {
+ pidInfo->threadUsage++;
+ }
+
+ pidInfo->threadCount++;
+ return;
+ }
+ return;
+ });
+ return ret;
+}
+
+} // namespace android
diff --git a/libs/binderdebug/TEST_MAPPING b/libs/binderdebug/TEST_MAPPING
new file mode 100644
index 0000000..2f3353e
--- /dev/null
+++ b/libs/binderdebug/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "libbinderdebug_test"
+ }
+ ]
+}
diff --git a/libs/binderdebug/include/binderdebug/BinderDebug.h b/libs/binderdebug/include/binderdebug/BinderDebug.h
new file mode 100644
index 0000000..14a0ef3
--- /dev/null
+++ b/libs/binderdebug/include/binderdebug/BinderDebug.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <map>
+#include <vector>
+
+namespace android {
+
+struct BinderPidInfo {
+ std::map<uint64_t, std::vector<pid_t>> refPids; // cookie -> processes which hold binder
+ uint32_t threadUsage; // number of threads in use
+ uint32_t threadCount; // number of threads total
+};
+
+enum class BinderDebugContext {
+ BINDER,
+ HWBINDER,
+ VNDBINDER,
+};
+
+status_t getBinderPidInfo(BinderDebugContext context, pid_t pid, BinderPidInfo* pidInfo);
+
+} // namespace android
diff --git a/libs/binderdebug/tests/Android.bp b/libs/binderdebug/tests/Android.bp
new file mode 100644
index 0000000..4c06b1d
--- /dev/null
+++ b/libs/binderdebug/tests/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2020 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.
+
+cc_test {
+ name: "libbinderdebug_test",
+ test_suites: ["general-tests"],
+ srcs: [
+ "binderdebug_test.cpp",
+ "android/binderdebug/test/IControl.aidl",
+ ],
+ shared_libs: [
+ "libbase",
+ "libbinder",
+ "libutils",
+ ],
+ static_libs: ["libbinderdebug"],
+ cflags: ["-Wall", "-Werror"],
+ require_root: true,
+}
diff --git a/libs/binderdebug/tests/android/binderdebug/test/IControl.aidl b/libs/binderdebug/tests/android/binderdebug/test/IControl.aidl
new file mode 100644
index 0000000..8efeb63
--- /dev/null
+++ b/libs/binderdebug/tests/android/binderdebug/test/IControl.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.binderdebug.test;
+
+interface IControl {
+ // Notifies the service to continue execution
+ void Continue();
+}
diff --git a/libs/binderdebug/tests/binderdebug_test.cpp b/libs/binderdebug/tests/binderdebug_test.cpp
new file mode 100644
index 0000000..ea799c0
--- /dev/null
+++ b/libs/binderdebug/tests/binderdebug_test.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <binder/Binder.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <binder/IPCThreadState.h>
+#include <binderdebug/BinderDebug.h>
+#include <gtest/gtest.h>
+#include <semaphore.h>
+#include <thread>
+
+#include <android/binderdebug/test/BnControl.h>
+#include <android/binderdebug/test/IControl.h>
+
+namespace android {
+namespace binderdebug {
+namespace test {
+
+class Control : public BnControl {
+public:
+ Control() {sem_init(&s, 1, 0);};
+ ::android::binder::Status Continue() override;
+ sem_t s;
+};
+
+::android::binder::Status Control::Continue() {
+ IPCThreadState::self()->flushCommands();
+ sem_post(&s);
+ return binder::Status::ok();
+}
+
+TEST(BinderDebugTests, BinderPid) {
+ BinderPidInfo pidInfo;
+ const auto& status = getBinderPidInfo(BinderDebugContext::BINDER, getpid(), &pidInfo);
+ ASSERT_EQ(status, OK);
+ // There should be one referenced PID for servicemanager
+ EXPECT_TRUE(!pidInfo.refPids.empty());
+}
+
+TEST(BinderDebugTests, BinderThreads) {
+ BinderPidInfo pidInfo;
+ const auto& status = getBinderPidInfo(BinderDebugContext::BINDER, getpid(), &pidInfo);
+ ASSERT_EQ(status, OK);
+ EXPECT_TRUE(pidInfo.threadUsage <= pidInfo.threadCount);
+ // The second looper thread can sometimes take longer to spawn.
+ EXPECT_GE(pidInfo.threadCount, 1);
+}
+
+extern "C" {
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ // Create a child/client process to call into the main process so we can ensure
+ // looper thread has been registered before attempting to get the BinderPidInfo
+ pid_t pid = fork();
+ if (pid == 0) {
+ sp<IBinder> binder = android::defaultServiceManager()->getService(String16("binderdebug"));
+ sp<IControl> service;
+ if (binder != nullptr) {
+ service = android::interface_cast<IControl>(binder);
+ }
+ service->Continue();
+ exit(0);
+ }
+ sp<Control> iface = new Control;
+ android::defaultServiceManager()->addService(String16("binderdebug"), iface);
+ android::ProcessState::self()->setThreadPoolMaxThreadCount(8);
+ ProcessState::self()->startThreadPool();
+ sem_wait(&iface->s);
+
+ return RUN_ALL_TESTS();
+}
+} // extern "C"
+} // namespace test
+} // namespace binderdebug
+} // namespace android
diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp
index e46a415..405658b 100644
--- a/libs/gui/ISurfaceComposer.cpp
+++ b/libs/gui/ISurfaceComposer.cpp
@@ -1230,6 +1230,21 @@
return remote()->transact(BnSurfaceComposer::ADD_TRANSACTION_TRACE_LISTENER, data, &reply);
}
+
+ /**
+ * Get priority of the RenderEngine in surface flinger.
+ */
+ virtual int getGPUContextPriority() {
+ Parcel data, reply;
+ data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
+ status_t err =
+ remote()->transact(BnSurfaceComposer::GET_GPU_CONTEXT_PRIORITY, data, &reply);
+ if (err != NO_ERROR) {
+ ALOGE("getGPUContextPriority failed to read data: %s (%d)", strerror(-err), err);
+ return 0;
+ }
+ return reply.readInt32();
+ }
};
// Out-of-line virtual method definition to trigger vtable emission in this
@@ -2094,6 +2109,12 @@
return addTransactionTraceListener(listener);
}
+ case GET_GPU_CONTEXT_PRIORITY: {
+ CHECK_INTERFACE(ISurfaceComposer, data, reply);
+ int priority = getGPUContextPriority();
+ SAFE_PARCEL(reply->writeInt32, priority);
+ return NO_ERROR;
+ }
default: {
return BBinder::onTransact(code, data, reply, flags);
}
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 9ed7d1c..0d370d3 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -1988,6 +1988,10 @@
lightRadius);
}
+int SurfaceComposerClient::getGPUContextPriority() {
+ return ComposerService::getComposerService()->getGPUContextPriority();
+}
+
// ----------------------------------------------------------------------------
status_t ScreenshotClient::captureDisplay(const DisplayCaptureArgs& captureArgs,
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index 86e3a25..7d25d61 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -504,6 +504,11 @@
*/
virtual status_t addTransactionTraceListener(
const sp<gui::ITransactionTraceListener>& listener) = 0;
+
+ /**
+ * Gets priority of the RenderEngine in SurfaceFlinger.
+ */
+ virtual int getGPUContextPriority() = 0;
};
// ----------------------------------------------------------------------------
@@ -565,6 +570,7 @@
ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN,
SET_FRAME_TIMELINE_VSYNC,
ADD_TRANSACTION_TRACE_LISTENER,
+ GET_GPU_CONTEXT_PRIORITY,
// Always append new enum to the end.
};
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index f1845ee..3ee4a39 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -185,6 +185,11 @@
static bool getProtectedContentSupport();
/**
+ * Gets the context priority of surface flinger's render engine.
+ */
+ static int getGPUContextPriority();
+
+ /**
* Uncaches a buffer in ISurfaceComposer. It must be uncached via a transaction so that it is
* in order with other transactions that use buffers.
*/
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index ce3afa2..7761db8 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -887,6 +887,8 @@
return NO_ERROR;
}
+ int getGPUContextPriority() override { return 0; };
+
protected:
IBinder* onAsBinder() override { return nullptr; }
diff --git a/libs/renderengine/gl/ProgramCache.cpp b/libs/renderengine/gl/ProgramCache.cpp
index 7fc0499..5ff9240 100644
--- a/libs/renderengine/gl/ProgramCache.cpp
+++ b/libs/renderengine/gl/ProgramCache.cpp
@@ -740,15 +740,6 @@
if (needs.isOpaque()) {
fs << "gl_FragColor.a = 1.0;";
}
- if (needs.hasAlpha()) {
- // modulate the current alpha value with alpha set
- if (needs.isPremultiplied()) {
- // ... and the color too if we're premultiplied
- fs << "gl_FragColor *= color.a;";
- } else {
- fs << "gl_FragColor.a *= color.a;";
- }
- }
}
if (needs.hasTransformMatrix() || (needs.getInputTF() != needs.getOutputTF()) ||
@@ -768,6 +759,23 @@
}
}
+ /*
+ * Whether applying layer alpha before or after color transform doesn't matter,
+ * as long as we can undo premultiplication. But we cannot un-premultiply
+ * for color transform if the layer alpha = 0, e.g. 0 / (0 + 0.0019) = 0.
+ */
+ if (!needs.drawShadows()) {
+ if (needs.hasAlpha()) {
+ // modulate the current alpha value with alpha set
+ if (needs.isPremultiplied()) {
+ // ... and the color too if we're premultiplied
+ fs << "gl_FragColor *= color.a;";
+ } else {
+ fs << "gl_FragColor.a *= color.a;";
+ }
+ }
+ }
+
if (needs.hasRoundedCorners()) {
if (needs.isPremultiplied()) {
fs << "gl_FragColor *= vec4(applyCornerRadius(outCropCoords));";
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index 712e5e2..d9dfd8c 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -406,6 +406,12 @@
void fillBufferColorTransform();
template <typename SourceVariant>
+ void fillBufferWithColorTransformZeroLayerAlpha();
+
+ template <typename SourceVariant>
+ void fillBufferColorTransformZeroLayerAlpha();
+
+ template <typename SourceVariant>
void fillRedBufferWithRoundedCorners();
template <typename SourceVariant>
@@ -765,6 +771,36 @@
}
template <typename SourceVariant>
+void RenderEngineTest::fillBufferWithColorTransformZeroLayerAlpha() {
+ renderengine::DisplaySettings settings;
+ settings.physicalDisplay = fullscreenRect();
+ settings.clip = Rect(1, 1);
+
+ std::vector<const renderengine::LayerSettings*> layers;
+
+ renderengine::LayerSettings layer;
+ layer.geometry.boundaries = Rect(1, 1).toFloatRect();
+ SourceVariant::fillColor(layer, 0.5f, 0.25f, 0.125f, this);
+ layer.alpha = 0;
+
+ // construct a fake color matrix
+ // simple inverse color
+ settings.colorTransform = mat4(-1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 1, 1, 1, 1);
+
+ layer.geometry.boundaries = Rect(1, 1).toFloatRect();
+
+ layers.push_back(&layer);
+
+ invokeDraw(settings, layers, mBuffer);
+}
+
+template <typename SourceVariant>
+void RenderEngineTest::fillBufferColorTransformZeroLayerAlpha() {
+ fillBufferWithColorTransformZeroLayerAlpha<SourceVariant>();
+ expectBufferColor(fullscreenRect(), 0, 0, 0, 0);
+}
+
+template <typename SourceVariant>
void RenderEngineTest::fillRedBufferWithRoundedCorners() {
renderengine::DisplaySettings settings;
settings.physicalDisplay = fullscreenRect();
@@ -1240,6 +1276,13 @@
fillBufferWithRoundedCorners<ColorSourceVariant>();
}
+TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformZeroLayerAlpha_colorSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
+ fillBufferColorTransformZeroLayerAlpha<ColorSourceVariant>();
+}
+
TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_colorSource) {
const auto& renderEngineFactory = GetParam();
mRE = renderEngineFactory->createRenderEngine();
@@ -1337,6 +1380,12 @@
fillBufferWithRoundedCorners<BufferSourceVariant<ForceOpaqueBufferVariant>>();
}
+TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformZeroLayerAlpha_opaqueBufferSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
+ fillBufferColorTransformZeroLayerAlpha<BufferSourceVariant<ForceOpaqueBufferVariant>>();
+}
TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_opaqueBufferSource) {
const auto& renderEngineFactory = GetParam();
@@ -1436,6 +1485,13 @@
fillBufferWithRoundedCorners<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
}
+TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformZeroLayerAlpha_bufferSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
+ fillBufferColorTransformZeroLayerAlpha<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
+}
+
TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_bufferSource) {
const auto& renderEngineFactory = GetParam();
mRE = renderEngineFactory->createRenderEngine();
diff --git a/services/audiomanager/IAudioManager.cpp b/services/audiomanager/IAudioManager.cpp
index 6235f06..0d17265 100644
--- a/services/audiomanager/IAudioManager.cpp
+++ b/services/audiomanager/IAudioManager.cpp
@@ -84,11 +84,13 @@
return remote()->transact(PLAYER_ATTRIBUTES, data, &reply, IBinder::FLAG_ONEWAY);
}
- virtual status_t playerEvent(audio_unique_id_t piid, player_state_t event) {
+ virtual status_t playerEvent(audio_unique_id_t piid, player_state_t event,
+ audio_port_handle_t deviceId) {
Parcel data, reply;
data.writeInterfaceToken(IAudioManager::getInterfaceDescriptor());
data.writeInt32((int32_t) piid);
data.writeInt32((int32_t) event);
+ data.writeInt32((int32_t) deviceId);
return remote()->transact(PLAYER_EVENT, data, &reply, IBinder::FLAG_ONEWAY);
}
diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
index 9fea298..7c572b4 100644
--- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
+++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
@@ -96,6 +96,8 @@
void onPointerDownOutsideFocus(const sp<IBinder>& newToken) override {}
+ void setPointerCapture(bool enabled) override {}
+
InputDispatcherConfiguration mConfig;
};
diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp
index 29df00b..d8a6548 100644
--- a/services/inputflinger/dispatcher/Entry.cpp
+++ b/services/inputflinger/dispatcher/Entry.cpp
@@ -114,6 +114,22 @@
return StringPrintf("FocusEvent(hasFocus=%s)", hasFocus ? "true" : "false");
}
+// --- PointerCaptureChangedEntry ---
+
+// PointerCaptureChanged notifications always go to apps, so set the flag POLICY_FLAG_PASS_TO_USER
+// for all entries.
+PointerCaptureChangedEntry::PointerCaptureChangedEntry(int32_t id, nsecs_t eventTime,
+ bool hasPointerCapture)
+ : EventEntry(id, Type::POINTER_CAPTURE_CHANGED, eventTime, POLICY_FLAG_PASS_TO_USER),
+ pointerCaptureEnabled(hasPointerCapture) {}
+
+PointerCaptureChangedEntry::~PointerCaptureChangedEntry() {}
+
+std::string PointerCaptureChangedEntry::getDescription() const {
+ return StringPrintf("PointerCaptureChangedEvent(pointerCaptureEnabled=%s)",
+ pointerCaptureEnabled ? "true" : "false");
+}
+
// --- KeyEntry ---
KeyEntry::KeyEntry(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t source,
diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h
index 0661709..fba5514 100644
--- a/services/inputflinger/dispatcher/Entry.h
+++ b/services/inputflinger/dispatcher/Entry.h
@@ -36,6 +36,7 @@
FOCUS,
KEY,
MOTION,
+ POINTER_CAPTURE_CHANGED,
};
static const char* typeToString(Type type) {
@@ -50,6 +51,8 @@
return "KEY";
case Type::MOTION:
return "MOTION";
+ case Type::POINTER_CAPTURE_CHANGED:
+ return "POINTER_CAPTURE_CHANGED";
}
}
@@ -115,6 +118,15 @@
virtual ~FocusEntry();
};
+struct PointerCaptureChangedEntry : EventEntry {
+ bool pointerCaptureEnabled;
+
+ PointerCaptureChangedEntry(int32_t id, nsecs_t eventTime, bool hasPointerCapture);
+ std::string getDescription() const override;
+
+ virtual ~PointerCaptureChangedEntry();
+};
+
struct KeyEntry : EventEntry {
int32_t deviceId;
uint32_t source;
@@ -254,6 +266,7 @@
sp<IBinder> oldToken;
sp<IBinder> newToken;
std::string obscuringPackage;
+ bool enabled;
};
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index ed4f05a..ee5ada5 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -454,6 +454,8 @@
mInTouchMode(true),
mMaximumObscuringOpacityForTouch(1.0f),
mFocusedDisplayId(ADISPLAY_ID_DEFAULT),
+ mFocusedWindowRequestedPointerCapture(false),
+ mWindowTokenWithPointerCapture(nullptr),
mCompatService(getCompatService()) {
mLooper = new Looper(false);
mReporter = createInputReporter();
@@ -713,6 +715,14 @@
break;
}
+ case EventEntry::Type::POINTER_CAPTURE_CHANGED: {
+ const auto typedEntry =
+ std::static_pointer_cast<PointerCaptureChangedEntry>(mPendingEvent);
+ dispatchPointerCaptureChangedLocked(currentTime, typedEntry, dropReason);
+ done = true;
+ break;
+ }
+
case EventEntry::Type::KEY: {
std::shared_ptr<KeyEntry> keyEntry = std::static_pointer_cast<KeyEntry>(mPendingEvent);
if (isAppSwitchDue) {
@@ -862,7 +872,8 @@
break;
}
case EventEntry::Type::CONFIGURATION_CHANGED:
- case EventEntry::Type::DEVICE_RESET: {
+ case EventEntry::Type::DEVICE_RESET:
+ case EventEntry::Type::POINTER_CAPTURE_CHANGED: {
// nothing to do
break;
}
@@ -968,6 +979,10 @@
ALOGI("Dropped event because it is stale.");
reason = "inbound event was dropped because it is stale";
break;
+ case DropReason::NO_POINTER_CAPTURE:
+ ALOGI("Dropped event because there is no window with Pointer Capture.");
+ reason = "inbound event was dropped because there is no window with Pointer Capture";
+ break;
case DropReason::NOT_DROPPED: {
LOG_ALWAYS_FATAL("Should not be dropping a NOT_DROPPED event");
return;
@@ -991,6 +1006,9 @@
}
break;
}
+ case EventEntry::Type::POINTER_CAPTURE_CHANGED: {
+ break;
+ }
case EventEntry::Type::FOCUS:
case EventEntry::Type::CONFIGURATION_CHANGED:
case EventEntry::Type::DEVICE_RESET: {
@@ -1176,6 +1194,55 @@
dispatchEventLocked(currentTime, entry, {target});
}
+void InputDispatcher::dispatchPointerCaptureChangedLocked(
+ nsecs_t currentTime, const std::shared_ptr<PointerCaptureChangedEntry>& entry,
+ DropReason& dropReason) {
+ const bool haveWindowWithPointerCapture = mWindowTokenWithPointerCapture != nullptr;
+ if (entry->pointerCaptureEnabled == haveWindowWithPointerCapture) {
+ LOG_ALWAYS_FATAL_IF(mFocusedWindowRequestedPointerCapture,
+ "The Pointer Capture state has already been dispatched to the window.");
+ // Pointer capture was already forcefully disabled because of focus change.
+ dropReason = DropReason::NOT_DROPPED;
+ return;
+ }
+
+ // Set drop reason for early returns
+ dropReason = DropReason::NO_POINTER_CAPTURE;
+
+ sp<IBinder> token;
+ if (entry->pointerCaptureEnabled) {
+ // Enable Pointer Capture
+ if (!mFocusedWindowRequestedPointerCapture) {
+ // This can happen if a window requests capture and immediately releases capture.
+ ALOGW("No window requested Pointer Capture.");
+ return;
+ }
+ token = getValueByKey(mFocusedWindowTokenByDisplay, mFocusedDisplayId);
+ LOG_ALWAYS_FATAL_IF(!token, "Cannot find focused window for Pointer Capture.");
+ mWindowTokenWithPointerCapture = token;
+ } else {
+ // Disable Pointer Capture
+ token = mWindowTokenWithPointerCapture;
+ mWindowTokenWithPointerCapture = nullptr;
+ mFocusedWindowRequestedPointerCapture = false;
+ }
+
+ auto channel = getInputChannelLocked(token);
+ if (channel == nullptr) {
+ // Window has gone away, clean up Pointer Capture state.
+ mWindowTokenWithPointerCapture = nullptr;
+ mFocusedWindowRequestedPointerCapture = false;
+ return;
+ }
+ InputTarget target;
+ target.inputChannel = channel;
+ target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
+ entry->dispatchInProgress = true;
+ dispatchEventLocked(currentTime, entry, {target});
+
+ dropReason = DropReason::NOT_DROPPED;
+}
+
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<KeyEntry> entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) {
// Preprocessing.
@@ -1483,6 +1550,7 @@
displayId = motionEntry.displayId;
break;
}
+ case EventEntry::Type::POINTER_CAPTURE_CHANGED:
case EventEntry::Type::FOCUS:
case EventEntry::Type::CONFIGURATION_CHANGED:
case EventEntry::Type::DEVICE_RESET: {
@@ -1877,6 +1945,8 @@
}
if (isWindowObscuredAtPointLocked(newTouchedWindowHandle, x, y)) {
targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
+ } else if (isWindowObscuredLocked(newTouchedWindowHandle)) {
+ targetFlags |= InputTarget::FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
}
BitSet32 pointerIds;
@@ -2366,8 +2436,10 @@
}
void InputDispatcher::pokeUserActivityLocked(const EventEntry& eventEntry) {
- if (eventEntry.type == EventEntry::Type::FOCUS) {
- // Focus events are passed to apps, but do not represent user activity.
+ if (eventEntry.type == EventEntry::Type::FOCUS ||
+ eventEntry.type == EventEntry::Type::POINTER_CAPTURE_CHANGED) {
+ // Focus or pointer capture changed events are passed to apps, but do not represent user
+ // activity.
return;
}
int32_t displayId = getTargetDisplayId(eventEntry);
@@ -2405,7 +2477,8 @@
}
case EventEntry::Type::FOCUS:
case EventEntry::Type::CONFIGURATION_CHANGED:
- case EventEntry::Type::DEVICE_RESET: {
+ case EventEntry::Type::DEVICE_RESET:
+ case EventEntry::Type::POINTER_CAPTURE_CHANGED: {
LOG_ALWAYS_FATAL("%s events are not user activity",
EventEntry::typeToString(eventEntry.type));
break;
@@ -2617,7 +2690,8 @@
break;
}
- case EventEntry::Type::FOCUS: {
+ case EventEntry::Type::FOCUS:
+ case EventEntry::Type::POINTER_CAPTURE_CHANGED: {
break;
}
case EventEntry::Type::CONFIGURATION_CHANGED:
@@ -2821,6 +2895,7 @@
reportTouchEventForStatistics(motionEntry);
break;
}
+
case EventEntry::Type::FOCUS: {
const FocusEntry& focusEntry = static_cast<const FocusEntry&>(eventEntry);
status = connection->inputPublisher.publishFocusEvent(dispatchEntry->seq,
@@ -2830,6 +2905,15 @@
break;
}
+ case EventEntry::Type::POINTER_CAPTURE_CHANGED: {
+ const auto& captureEntry =
+ static_cast<const PointerCaptureChangedEntry&>(eventEntry);
+ status = connection->inputPublisher
+ .publishCaptureEvent(dispatchEntry->seq, captureEntry.id,
+ captureEntry.pointerCaptureEnabled);
+ break;
+ }
+
case EventEntry::Type::CONFIGURATION_CHANGED:
case EventEntry::Type::DEVICE_RESET: {
LOG_ALWAYS_FATAL("Should never start dispatch cycles for %s events",
@@ -3124,8 +3208,10 @@
static_cast<const MotionEntry&>(*cancelationEventEntry));
break;
}
- case EventEntry::Type::FOCUS: {
- LOG_ALWAYS_FATAL("Canceling focus events is not supported");
+ case EventEntry::Type::FOCUS:
+ case EventEntry::Type::POINTER_CAPTURE_CHANGED: {
+ LOG_ALWAYS_FATAL("Canceling %s events is not supported",
+ EventEntry::typeToString(cancelationEventEntry->type));
break;
}
case EventEntry::Type::CONFIGURATION_CHANGED:
@@ -3185,7 +3271,8 @@
case EventEntry::Type::KEY:
case EventEntry::Type::FOCUS:
case EventEntry::Type::CONFIGURATION_CHANGED:
- case EventEntry::Type::DEVICE_RESET: {
+ case EventEntry::Type::DEVICE_RESET:
+ case EventEntry::Type::POINTER_CAPTURE_CHANGED: {
LOG_ALWAYS_FATAL("%s event should not be found inside Connections's queue",
EventEntry::typeToString(downEventEntry->type));
break;
@@ -3562,7 +3649,17 @@
args->enabled ? "true" : "false");
#endif
- // TODO(prabirmsp): Implement.
+ bool needWake;
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+ auto entry = std::make_unique<PointerCaptureChangedEntry>(args->id, args->eventTime,
+ args->enabled);
+ needWake = enqueueInboundEventLocked(std::move(entry));
+ } // release lock
+
+ if (needWake) {
+ mLooper->wake();
+ }
}
InputEventInjectionResult InputDispatcher::injectInputEvent(
@@ -4456,6 +4553,24 @@
return dump;
}
+std::string InputDispatcher::dumpPointerCaptureStateLocked() {
+ std::string dump;
+
+ dump += StringPrintf(INDENT "FocusedWindowRequestedPointerCapture: %s\n",
+ toString(mFocusedWindowRequestedPointerCapture));
+
+ std::string windowName = "None";
+ if (mWindowTokenWithPointerCapture) {
+ const sp<InputWindowHandle> captureWindowHandle =
+ getWindowHandleLocked(mWindowTokenWithPointerCapture);
+ windowName = captureWindowHandle ? captureWindowHandle->getName().c_str()
+ : "token has capture without window";
+ }
+ dump += StringPrintf(INDENT "CurrentWindowWithPointerCapture: %s\n", windowName.c_str());
+
+ return dump;
+}
+
void InputDispatcher::dumpDispatchStateLocked(std::string& dump) {
dump += StringPrintf(INDENT "DispatchEnabled: %s\n", toString(mDispatchEnabled));
dump += StringPrintf(INDENT "DispatchFrozen: %s\n", toString(mDispatchFrozen));
@@ -4479,6 +4594,7 @@
dump += dumpFocusedWindowsLocked();
dump += dumpPendingFocusRequestsLocked();
+ dump += dumpPointerCaptureStateLocked();
if (!mTouchStatesByDisplay.empty()) {
dump += StringPrintf(INDENT "TouchStatesByDisplay:\n");
@@ -4860,6 +4976,39 @@
return OK;
}
+void InputDispatcher::requestPointerCapture(const sp<IBinder>& windowToken, bool enabled) {
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+ if (DEBUG_FOCUS) {
+ const sp<InputWindowHandle> windowHandle = getWindowHandleLocked(windowToken);
+ ALOGI("Request to %s Pointer Capture from: %s.", enabled ? "enable" : "disable",
+ windowHandle != nullptr ? windowHandle->getName().c_str()
+ : "token without window");
+ }
+
+ const sp<IBinder> focusedToken =
+ getValueByKey(mFocusedWindowTokenByDisplay, mFocusedDisplayId);
+ if (focusedToken != windowToken) {
+ ALOGW("Ignoring request to %s Pointer Capture: window does not have focus.",
+ enabled ? "enable" : "disable");
+ return;
+ }
+
+ if (enabled == mFocusedWindowRequestedPointerCapture) {
+ ALOGW("Ignoring request to %s Pointer Capture: "
+ "window has %s requested pointer capture.",
+ enabled ? "enable" : "disable", enabled ? "already" : "not");
+ return;
+ }
+
+ mFocusedWindowRequestedPointerCapture = enabled;
+ setPointerCaptureLocked(enabled);
+ } // release lock
+
+ // Wake the thread to process command entries.
+ mLooper->wake();
+}
+
std::optional<int32_t> InputDispatcher::findGestureMonitorDisplayByTokenLocked(
const sp<IBinder>& token) {
for (const auto& it : mGestureMonitorsByDisplay) {
@@ -5578,11 +5727,50 @@
enqueueFocusEventLocked(newFocusedToken, true /*hasFocus*/, reason);
}
+ // If a window has pointer capture, then it must have focus. We need to ensure that this
+ // contract is upheld when pointer capture is being disabled due to a loss of window focus.
+ // If the window loses focus before it loses pointer capture, then the window can be in a state
+ // where it has pointer capture but not focus, violating the contract. Therefore we must
+ // dispatch the pointer capture event before the focus event. Since focus events are added to
+ // the front of the queue (above), we add the pointer capture event to the front of the queue
+ // after the focus events are added. This ensures the pointer capture event ends up at the
+ // front.
+ disablePointerCaptureForcedLocked();
+
if (mFocusedDisplayId == displayId) {
notifyFocusChangedLocked(oldFocusedToken, newFocusedToken);
}
}
+void InputDispatcher::disablePointerCaptureForcedLocked() {
+ if (!mFocusedWindowRequestedPointerCapture && !mWindowTokenWithPointerCapture) {
+ return;
+ }
+
+ ALOGD_IF(DEBUG_FOCUS, "Disabling Pointer Capture because the window lost focus.");
+
+ if (mFocusedWindowRequestedPointerCapture) {
+ mFocusedWindowRequestedPointerCapture = false;
+ setPointerCaptureLocked(false);
+ }
+
+ if (!mWindowTokenWithPointerCapture) {
+ // No need to send capture changes because no window has capture.
+ return;
+ }
+
+ if (mPendingEvent != nullptr) {
+ // Move the pending event to the front of the queue. This will give the chance
+ // for the pending event to be dropped if it is a captured event.
+ mInboundQueue.push_front(mPendingEvent);
+ mPendingEvent = nullptr;
+ }
+
+ auto entry = std::make_unique<PointerCaptureChangedEntry>(mIdGenerator.nextId(), now(),
+ false /* hasCapture */);
+ mInboundQueue.push_front(std::move(entry));
+}
+
/**
* Checks if the window token can be focused on a display. The token can be focused if there is
* at least one window handle that is visible with the same token and all window handles with the
@@ -5626,4 +5814,21 @@
return FocusResult::OK;
}
+
+void InputDispatcher::setPointerCaptureLocked(bool enabled) {
+ std::unique_ptr<CommandEntry> commandEntry = std::make_unique<CommandEntry>(
+ &InputDispatcher::doSetPointerCaptureLockedInterruptible);
+ commandEntry->enabled = enabled;
+ postCommandLocked(std::move(commandEntry));
+}
+
+void InputDispatcher::doSetPointerCaptureLockedInterruptible(
+ android::inputdispatcher::CommandEntry* commandEntry) {
+ mLock.unlock();
+
+ mPolicy->setPointerCapture(commandEntry->enabled);
+
+ mLock.lock();
+}
+
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 8f58785..5d37645 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -127,6 +127,7 @@
int32_t displayId, bool isGestureMonitor, const std::string& name) override;
virtual status_t removeInputChannel(const sp<IBinder>& connectionToken) override;
virtual status_t pilferPointers(const sp<IBinder>& token) override;
+ virtual void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled) override;
std::array<uint8_t, 32> sign(const VerifiedInputEvent& event) const;
@@ -138,6 +139,7 @@
DISABLED,
BLOCKED,
STALE,
+ NO_POINTER_CAPTURE,
};
enum class FocusResult {
@@ -351,6 +353,21 @@
// Top focused display.
int32_t mFocusedDisplayId GUARDED_BY(mLock);
+ // Whether the focused window on the focused display has requested Pointer Capture.
+ // The state of this variable should always be in sync with the state of Pointer Capture in the
+ // policy, which is updated through setPointerCaptureLocked(enabled).
+ bool mFocusedWindowRequestedPointerCapture GUARDED_BY(mLock);
+
+ // The window token that has Pointer Capture.
+ // This should be in sync with PointerCaptureChangedEvents dispatched to the input channel.
+ sp<IBinder> mWindowTokenWithPointerCapture GUARDED_BY(mLock);
+
+ // Disable Pointer Capture as a result of loss of window focus.
+ void disablePointerCaptureForcedLocked() REQUIRES(mLock);
+
+ // Set the Pointer Capture state in the Policy.
+ void setPointerCaptureLocked(bool enabled) REQUIRES(mLock);
+
// Dispatcher state at time of last ANR.
std::string mLastAnrState GUARDED_BY(mLock);
@@ -370,6 +387,9 @@
DropReason* dropReason, nsecs_t* nextWakeupTime) REQUIRES(mLock);
void dispatchFocusLocked(nsecs_t currentTime, std::shared_ptr<FocusEntry> entry)
REQUIRES(mLock);
+ void dispatchPointerCaptureChangedLocked(
+ nsecs_t currentTime, const std::shared_ptr<PointerCaptureChangedEntry>& entry,
+ DropReason& dropReason) REQUIRES(mLock);
void dispatchEventLocked(nsecs_t currentTime, std::shared_ptr<EventEntry> entry,
const std::vector<InputTarget>& inputTargets) REQUIRES(mLock);
@@ -533,6 +553,7 @@
void logDispatchStateLocked() REQUIRES(mLock);
std::string dumpFocusedWindowsLocked() REQUIRES(mLock);
std::string dumpPendingFocusRequestsLocked() REQUIRES(mLock);
+ std::string dumpPointerCaptureStateLocked() REQUIRES(mLock);
// Registration.
void removeMonitorChannelLocked(const sp<IBinder>& connectionToken) REQUIRES(mLock);
@@ -575,6 +596,7 @@
void doInterceptKeyBeforeDispatchingLockedInterruptible(CommandEntry* commandEntry)
REQUIRES(mLock);
void doDispatchCycleFinishedLockedInterruptible(CommandEntry* commandEntry) REQUIRES(mLock);
+ void doSetPointerCaptureLockedInterruptible(CommandEntry* commandEntry) REQUIRES(mLock);
bool afterKeyEventLockedInterruptible(const sp<Connection>& connection,
DispatchEntry* dispatchEntry, KeyEntry& keyEntry,
bool handled) REQUIRES(mLock);
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
index 9154d48..2909d69 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
@@ -186,6 +186,13 @@
* This method may be called on any thread (usually by the input manager).
*/
virtual status_t pilferPointers(const sp<IBinder>& token) = 0;
+
+ /**
+ * Enables Pointer Capture on the specified window if the window has focus.
+ *
+ * InputDispatcher is the source of truth of Pointer Capture.
+ */
+ virtual void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled) = 0;
};
} // namespace android
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
index 1125257..c1821ab 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
@@ -134,6 +134,12 @@
* The touchedToken passed as an argument is the window that received the input event.
*/
virtual void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) = 0;
+
+ /* Change the Pointer Capture state in InputReader.
+ *
+ * InputDispatcher is solely responsible for updating the Pointer Capture state.
+ */
+ virtual void setPointerCapture(bool enabled) = 0;
};
} // namespace android
diff --git a/services/inputflinger/docs/pointer_capture.md b/services/inputflinger/docs/pointer_capture.md
new file mode 100644
index 0000000..8da699d
--- /dev/null
+++ b/services/inputflinger/docs/pointer_capture.md
@@ -0,0 +1,44 @@
+# Pointer Capture in InputFlinger
+
+## Introduction
+
+[Pointer Capture](https://developer.android.com/training/gestures/movement#pointer-capture) is a feature that was introduced to the Android input pipeline in Android 8.0 (Oreo). Pointer Capture can be enabled or disabled for an `InputWindow` through requests to `InputManagerService`. Enabling Pointer Capture performs the following changes related to the mouse cursor and the devices that control it:
+
+- The position of the mouse cursor is fixed to its location before Pointer Capture was enabled.
+- The mouse cursor is hidden.
+- Events from a mouse will be delivered with the source `SOURCE_MOUSE_RELATIVE`, and their `AXIS_X` and `AXIS_Y` will report relative position changes.
+- Events from a touchpad will be delivered with the source `SOURCE_TOUCHPAD`, and their `AXIS_X` and `AXIS_Y` will report the absolute position of each of the pointers on the touchpad.
+- Events from mouse and touchpad devices are dispatched to the focused `InputWindow`.
+- Events from devices that do not normally control the mouse cursor are not affected.
+
+`InputWindow`s can only gain Pointer Capture if they have window focus. If a window with Pointer Capture loses focus, Pointer Capture is disabled.
+
+## Pointer Capture pipeline in InputFlinger
+
+`InputDispatcher` is responsible for controlling the state of Pointer Capture. Since the feature requires changes to how events are generated, Pointer Capture is configured in `InputReader`.
+
+### Enabling Pointer Capture
+
+There are four key steps that take place when Pointer Capture is enabled:
+
+1. Requests to enable Pointer Capture are forwarded from `InputManagerService` to `InputDispatcher`.
+2. If the window that makes the request has focus, `InputDispatcher` enables the Pointer Capture state in `InputReader` through the `InputDispatcherPolicy`.
+3. When `InputReader` is successfully configured, it notifies `InputDispatcher` through the `InputListener` interface.
+4. `InputDispatcher` then notifies the `InputWindow` that Pointer Capture has been enabled by sending a special `CAPTURE` event through the `InputChannel`.
+
+### Disabling Pointer Capture
+
+Pointer Capture can be disabled in two ways: by a request through `InputManagerService`, and as a result of the `InputWindow` losing focus.
+
+When Pointer Capture is disabled by a request from the application, it follows the same pipeline as when Pointer Capture is enabled.
+
+#### Window loses Pointer Capture when it loses focus
+
+When an `InputWindow` with Pointer Capture loses focus, Pointer Capture is disabled immediately. The `InputWindow` receives a `CAPTURE` event through the `InputChannel`, followed by a `FOCUS` event to notify loss of focus.
+
+## Pointer Capture in `InputDispatcher`
+
+`InputDispatcher` tracks two pieces of state information regarding Pointer Capture:
+
+- `mFocusedWindowRequestedPointerCapture`: Whether or not the focused window has requested Pointer Capture. This is updated whenever the Dispatcher receives requests from `InputManagerService`.
+- `mWindowTokenWithPointerCapture`: The Binder token of the `InputWindow` that currently has Pointer Capture. This is only updated during the dispatch cycle. If it is not `nullptr`, it signifies that the window was notified that it has Pointer Capture.
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 5ab2ae3..bf05b9f 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -19,10 +19,10 @@
#include <android-base/stringprintf.h>
#include <android-base/thread_annotations.h>
#include <binder/Binder.h>
-#include <input/Input.h>
-
#include <gtest/gtest.h>
+#include <input/Input.h>
#include <linux/input.h>
+
#include <cinttypes>
#include <thread>
#include <unordered_set>
@@ -71,12 +71,10 @@
InputDispatcherConfiguration mConfig;
protected:
- virtual ~FakeInputDispatcherPolicy() {
- }
+ virtual ~FakeInputDispatcherPolicy() {}
public:
- FakeInputDispatcherPolicy() {
- }
+ FakeInputDispatcherPolicy() {}
void assertFilterInputEventWasCalled(const NotifyKeyArgs& args) {
assertFilterInputEventWasCalled(AINPUT_EVENT_TYPE_KEY, args.eventTime, args.action,
@@ -211,6 +209,33 @@
mConfig.keyRepeatDelay = delay;
}
+ void waitForSetPointerCapture(bool enabled) {
+ std::unique_lock lock(mLock);
+ base::ScopedLockAssertion assumeLocked(mLock);
+
+ if (!mPointerCaptureChangedCondition.wait_for(lock, 100ms,
+ [this, enabled]() REQUIRES(mLock) {
+ return mPointerCaptureEnabled &&
+ *mPointerCaptureEnabled ==
+ enabled;
+ })) {
+ FAIL() << "Timed out waiting for setPointerCapture(" << enabled << ") to be called.";
+ }
+ mPointerCaptureEnabled.reset();
+ }
+
+ void assertSetPointerCaptureNotCalled() {
+ std::unique_lock lock(mLock);
+ base::ScopedLockAssertion assumeLocked(mLock);
+
+ if (mPointerCaptureChangedCondition.wait_for(lock, 100ms) != std::cv_status::timeout) {
+ FAIL() << "Expected setPointerCapture(enabled) to not be called, but was called. "
+ "enabled = "
+ << *mPointerCaptureEnabled;
+ }
+ mPointerCaptureEnabled.reset();
+ }
+
private:
std::mutex mLock;
std::unique_ptr<InputEvent> mFilteredEvent GUARDED_BY(mLock);
@@ -218,6 +243,9 @@
sp<IBinder> mOnPointerDownToken GUARDED_BY(mLock);
std::optional<NotifySwitchArgs> mLastNotifySwitch GUARDED_BY(mLock);
+ std::condition_variable mPointerCaptureChangedCondition;
+ std::optional<bool> mPointerCaptureEnabled GUARDED_BY(mLock);
+
// ANR handling
std::queue<std::shared_ptr<InputApplicationHandle>> mAnrApplications GUARDED_BY(mLock);
std::queue<sp<IBinder>> mAnrConnectionTokens GUARDED_BY(mLock);
@@ -307,6 +335,12 @@
mOnPointerDownToken = newToken;
}
+ void setPointerCapture(bool enabled) override {
+ std::scoped_lock lock(mLock);
+ mPointerCaptureEnabled = {enabled};
+ mPointerCaptureChangedCondition.notify_all();
+ }
+
void assertFilterInputEventWasCalled(int type, nsecs_t eventTime, int32_t action,
int32_t displayId) {
std::scoped_lock lock(mLock);
@@ -342,7 +376,7 @@
mFakePolicy = new FakeInputDispatcherPolicy();
mDispatcher = new InputDispatcher(mFakePolicy);
mDispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
- //Start InputDispatcher thread
+ // Start InputDispatcher thread
ASSERT_EQ(OK, mDispatcher->start());
}
@@ -379,7 +413,6 @@
}
};
-
TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesKeyEvents) {
KeyEvent event;
@@ -574,9 +607,7 @@
}
virtual ~FakeApplicationHandle() {}
- virtual bool updateInfo() override {
- return true;
- }
+ virtual bool updateInfo() override { return true; }
void setDispatchingTimeout(std::chrono::milliseconds timeout) {
mInfo.dispatchingTimeoutMillis = timeout.count();
@@ -674,6 +705,9 @@
case AINPUT_EVENT_TYPE_FOCUS: {
FAIL() << "Use 'consumeFocusEvent' for FOCUS events";
}
+ case AINPUT_EVENT_TYPE_CAPTURE: {
+ FAIL() << "Use 'consumeCaptureEvent' for CAPTURE events";
+ }
default: {
FAIL() << mName.c_str() << ": invalid event type: " << expectedEventType;
}
@@ -696,6 +730,21 @@
EXPECT_EQ(inTouchMode, focusEvent->getInTouchMode());
}
+ void consumeCaptureEvent(bool hasCapture) {
+ const InputEvent* event = consume();
+ ASSERT_NE(nullptr, event) << mName.c_str()
+ << ": consumer should have returned non-NULL event.";
+ ASSERT_EQ(AINPUT_EVENT_TYPE_CAPTURE, event->getType())
+ << "Got " << inputEventTypeToString(event->getType())
+ << " event instead of CAPTURE event";
+
+ ASSERT_EQ(ADISPLAY_ID_NONE, event->getDisplayId())
+ << mName.c_str() << ": event displayId should always be NONE.";
+
+ const auto& captureEvent = static_cast<const CaptureEvent&>(*event);
+ EXPECT_EQ(hasCapture, captureEvent.getPointerCaptureEnabled());
+ }
+
void assertNoEvents() {
InputEvent* event = consume();
if (event == nullptr) {
@@ -713,6 +762,10 @@
FocusEvent& focusEvent = static_cast<FocusEvent&>(*event);
ADD_FAILURE() << "Received focus event, hasFocus = "
<< (focusEvent.getHasFocus() ? "true" : "false");
+ } else if (event->getType() == AINPUT_EVENT_TYPE_CAPTURE) {
+ const auto& captureEvent = static_cast<CaptureEvent&>(*event);
+ ADD_FAILURE() << "Received capture event, pointerCaptureEnabled = "
+ << (captureEvent.getPointerCaptureEnabled() ? "true" : "false");
}
FAIL() << mName.c_str()
<< ": should not have received any events, so consume() should return NULL";
@@ -810,39 +863,40 @@
}
void consumeMotionCancel(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
- int32_t expectedFlags = 0) {
+ int32_t expectedFlags = 0) {
consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL, expectedDisplayId,
expectedFlags);
}
void consumeMotionMove(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
- int32_t expectedFlags = 0) {
+ int32_t expectedFlags = 0) {
consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_MOVE, expectedDisplayId,
expectedFlags);
}
void consumeMotionDown(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
- int32_t expectedFlags = 0) {
+ int32_t expectedFlags = 0) {
consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_DOWN, expectedDisplayId,
expectedFlags);
}
void consumeMotionPointerDown(int32_t pointerIdx,
- int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, int32_t expectedFlags = 0) {
- int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN
- | (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+ int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
+ int32_t expectedFlags = 0) {
+ int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN |
+ (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
consumeEvent(AINPUT_EVENT_TYPE_MOTION, action, expectedDisplayId, expectedFlags);
}
void consumeMotionPointerUp(int32_t pointerIdx, int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
- int32_t expectedFlags = 0) {
- int32_t action = AMOTION_EVENT_ACTION_POINTER_UP
- | (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+ int32_t expectedFlags = 0) {
+ int32_t action = AMOTION_EVENT_ACTION_POINTER_UP |
+ (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
consumeEvent(AINPUT_EVENT_TYPE_MOTION, action, expectedDisplayId, expectedFlags);
}
void consumeMotionUp(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
- int32_t expectedFlags = 0) {
+ int32_t expectedFlags = 0) {
consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_UP, expectedDisplayId,
expectedFlags);
}
@@ -859,6 +913,12 @@
mInputReceiver->consumeFocusEvent(hasFocus, inTouchMode);
}
+ void consumeCaptureEvent(bool hasCapture) {
+ ASSERT_NE(mInputReceiver, nullptr)
+ << "Cannot consume events from a window with no receiver";
+ mInputReceiver->consumeCaptureEvent(hasCapture);
+ }
+
void consumeEvent(int32_t expectedEventType, int32_t expectedAction, int32_t expectedDisplayId,
int32_t expectedFlags) {
ASSERT_NE(mInputReceiver, nullptr) << "Invalid consume event on window with no receiver";
@@ -900,6 +960,11 @@
const std::string& getName() { return mName; }
+ void setOwnerInfo(int32_t ownerPid, int32_t ownerUid) {
+ mInfo.ownerPid = ownerPid;
+ mInfo.ownerUid = ownerUid;
+ }
+
private:
const std::string mName;
std::unique_ptr<FakeInputReceiver> mInputReceiver;
@@ -1138,10 +1203,14 @@
return generateMotionArgs(action, source, displayId, {PointF{100, 200}});
}
+static NotifyPointerCaptureChangedArgs generatePointerCaptureChangedArgs(bool enabled) {
+ return NotifyPointerCaptureChangedArgs(/* id */ 0, systemTime(SYSTEM_TIME_MONOTONIC), enabled);
+}
+
TEST_F(InputDispatcherTest, SetInputWindow_SingleWindowTouch) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window = new FakeWindowHandle(application, mDispatcher, "Fake Window",
- ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> window =
+ new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -1204,10 +1273,10 @@
// The foreground window should receive the first touch down event.
TEST_F(InputDispatcherTest, SetInputWindow_MultiWindowsTouch) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> windowTop = new FakeWindowHandle(application, mDispatcher, "Top",
- ADISPLAY_ID_DEFAULT);
- sp<FakeWindowHandle> windowSecond = new FakeWindowHandle(application, mDispatcher, "Second",
- ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> windowTop =
+ new FakeWindowHandle(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> windowSecond =
+ new FakeWindowHandle(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowTop, windowSecond}}});
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -1488,17 +1557,18 @@
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
// Create a couple of windows
- sp<FakeWindowHandle> firstWindow = new FakeWindowHandle(application, mDispatcher,
- "First Window", ADISPLAY_ID_DEFAULT);
- sp<FakeWindowHandle> secondWindow = new FakeWindowHandle(application, mDispatcher,
- "Second Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> firstWindow =
+ new FakeWindowHandle(application, mDispatcher, "First Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> secondWindow =
+ new FakeWindowHandle(application, mDispatcher, "Second Window", ADISPLAY_ID_DEFAULT);
// Add the windows to the dispatcher
mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow}}});
// Send down to the first window
- NotifyMotionArgs downMotionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
- AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT);
+ NotifyMotionArgs downMotionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT);
mDispatcher->notifyMotion(&downMotionArgs);
// Only the first window should get the down event
firstWindow->consumeMotionDown();
@@ -1511,8 +1581,9 @@
secondWindow->consumeMotionDown();
// Send up event to the second window
- NotifyMotionArgs upMotionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_UP,
- AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT);
+ NotifyMotionArgs upMotionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT);
mDispatcher->notifyMotion(&upMotionArgs);
// The first window gets no events and the second gets up
firstWindow->assertNoEvents();
@@ -1525,26 +1596,29 @@
PointF touchPoint = {10, 10};
// Create a couple of windows
- sp<FakeWindowHandle> firstWindow = new FakeWindowHandle(application, mDispatcher,
- "First Window", ADISPLAY_ID_DEFAULT);
- sp<FakeWindowHandle> secondWindow = new FakeWindowHandle(application, mDispatcher,
- "Second Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> firstWindow =
+ new FakeWindowHandle(application, mDispatcher, "First Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> secondWindow =
+ new FakeWindowHandle(application, mDispatcher, "Second Window", ADISPLAY_ID_DEFAULT);
// Add the windows to the dispatcher
mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow}}});
// Send down to the first window
- NotifyMotionArgs downMotionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
- AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {touchPoint});
+ NotifyMotionArgs downMotionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT, {touchPoint});
mDispatcher->notifyMotion(&downMotionArgs);
// Only the first window should get the down event
firstWindow->consumeMotionDown();
secondWindow->assertNoEvents();
// Send pointer down to the first window
- NotifyMotionArgs pointerDownMotionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_DOWN
- | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
- AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {touchPoint, touchPoint});
+ NotifyMotionArgs pointerDownMotionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {touchPoint, touchPoint});
mDispatcher->notifyMotion(&pointerDownMotionArgs);
// Only the first window should get the pointer down event
firstWindow->consumeMotionPointerDown(1);
@@ -1558,17 +1632,20 @@
secondWindow->consumeMotionPointerDown(1);
// Send pointer up to the second window
- NotifyMotionArgs pointerUpMotionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_UP
- | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
- AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {touchPoint, touchPoint});
+ NotifyMotionArgs pointerUpMotionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_UP |
+ (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {touchPoint, touchPoint});
mDispatcher->notifyMotion(&pointerUpMotionArgs);
// The first window gets nothing and the second gets pointer up
firstWindow->assertNoEvents();
secondWindow->consumeMotionPointerUp(1);
// Send up event to the second window
- NotifyMotionArgs upMotionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_UP,
- AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT);
+ NotifyMotionArgs upMotionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT);
mDispatcher->notifyMotion(&upMotionArgs);
// The first window gets nothing and the second gets up
firstWindow->assertNoEvents();
@@ -1579,15 +1656,15 @@
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
// Create a non touch modal window that supports split touch
- sp<FakeWindowHandle> firstWindow = new FakeWindowHandle(application, mDispatcher,
- "First Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> firstWindow =
+ new FakeWindowHandle(application, mDispatcher, "First Window", ADISPLAY_ID_DEFAULT);
firstWindow->setFrame(Rect(0, 0, 600, 400));
firstWindow->setFlags(InputWindowInfo::Flag::NOT_TOUCH_MODAL |
InputWindowInfo::Flag::SPLIT_TOUCH);
// Create a non touch modal window that supports split touch
- sp<FakeWindowHandle> secondWindow = new FakeWindowHandle(application, mDispatcher,
- "Second Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> secondWindow =
+ new FakeWindowHandle(application, mDispatcher, "Second Window", ADISPLAY_ID_DEFAULT);
secondWindow->setFrame(Rect(0, 400, 600, 800));
secondWindow->setFlags(InputWindowInfo::Flag::NOT_TOUCH_MODAL |
InputWindowInfo::Flag::SPLIT_TOUCH);
@@ -1599,17 +1676,20 @@
PointF pointInSecond = {300, 600};
// Send down to the first window
- NotifyMotionArgs firstDownMotionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
- AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {pointInFirst});
+ NotifyMotionArgs firstDownMotionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT, {pointInFirst});
mDispatcher->notifyMotion(&firstDownMotionArgs);
// Only the first window should get the down event
firstWindow->consumeMotionDown();
secondWindow->assertNoEvents();
// Send down to the second window
- NotifyMotionArgs secondDownMotionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_DOWN
- | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
- AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {pointInFirst, pointInSecond});
+ NotifyMotionArgs secondDownMotionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {pointInFirst, pointInSecond});
mDispatcher->notifyMotion(&secondDownMotionArgs);
// The first window gets a move and the second a down
firstWindow->consumeMotionMove();
@@ -1622,17 +1702,20 @@
secondWindow->consumeMotionPointerDown(1);
// Send pointer up to the second window
- NotifyMotionArgs pointerUpMotionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_UP
- | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
- AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {pointInFirst, pointInSecond});
+ NotifyMotionArgs pointerUpMotionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_UP |
+ (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {pointInFirst, pointInSecond});
mDispatcher->notifyMotion(&pointerUpMotionArgs);
// The first window gets nothing and the second gets pointer up
firstWindow->assertNoEvents();
secondWindow->consumeMotionPointerUp(1);
// Send up event to the second window
- NotifyMotionArgs upMotionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_UP,
- AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT);
+ NotifyMotionArgs upMotionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT);
mDispatcher->notifyMotion(&upMotionArgs);
// The first window gets nothing and the second gets up
firstWindow->assertNoEvents();
@@ -2161,6 +2244,72 @@
window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
}
+/**
+ * Launch two windows, with different owners. One window (slipperyExitWindow) has Flag::SLIPPERY,
+ * and overlaps the other window, slipperyEnterWindow. The window 'slipperyExitWindow' is on top
+ * of the 'slipperyEnterWindow'.
+ *
+ * Inject touch down into the top window. Upon receipt of the DOWN event, move the window in such
+ * a way so that the touched location is no longer covered by the top window.
+ *
+ * Next, inject a MOVE event. Because the top window already moved earlier, this event is now
+ * positioned over the bottom (slipperyEnterWindow) only. And because the top window had
+ * Flag::SLIPPERY, this will cause the top window to lose the touch event (it will receive
+ * ACTION_CANCEL instead), and the bottom window will receive a newly generated gesture (starting
+ * with ACTION_DOWN).
+ * Thus, the touch has been transferred from the top window into the bottom window, because the top
+ * window moved itself away from the touched location and had Flag::SLIPPERY.
+ *
+ * Even though the top window moved away from the touched location, it is still obscuring the bottom
+ * window. It's just not obscuring it at the touched location. That means, FLAG_WINDOW_IS_PARTIALLY_
+ * OBSCURED should be set for the MotionEvent that reaches the bottom window.
+ *
+ * In this test, we ensure that the event received by the bottom window has
+ * FLAG_WINDOW_IS_PARTIALLY_OBSCURED.
+ */
+TEST_F(InputDispatcherTest, SlipperyWindow_SetsFlagPartiallyObscured) {
+ constexpr int32_t SLIPPERY_PID = INJECTOR_PID + 1;
+ constexpr int32_t SLIPPERY_UID = INJECTOR_UID + 1;
+
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+
+ sp<FakeWindowHandle> slipperyExitWindow =
+ new FakeWindowHandle(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
+ slipperyExitWindow->setFlags(InputWindowInfo::Flag::NOT_TOUCH_MODAL |
+ InputWindowInfo::Flag::SLIPPERY);
+ // Make sure this one overlaps the bottom window
+ slipperyExitWindow->setFrame(Rect(25, 25, 75, 75));
+ // Change the owner uid/pid of the window so that it is considered to be occluding the bottom
+ // one. Windows with the same owner are not considered to be occluding each other.
+ slipperyExitWindow->setOwnerInfo(SLIPPERY_PID, SLIPPERY_UID);
+
+ sp<FakeWindowHandle> slipperyEnterWindow =
+ new FakeWindowHandle(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
+ slipperyExitWindow->setFrame(Rect(0, 0, 100, 100));
+
+ mDispatcher->setInputWindows(
+ {{ADISPLAY_ID_DEFAULT, {slipperyExitWindow, slipperyEnterWindow}}});
+
+ // Use notifyMotion instead of injecting to avoid dealing with injection permissions
+ NotifyMotionArgs args = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT, {{50, 50}});
+ mDispatcher->notifyMotion(&args);
+ slipperyExitWindow->consumeMotionDown();
+ slipperyExitWindow->setFrame(Rect(70, 70, 100, 100));
+ mDispatcher->setInputWindows(
+ {{ADISPLAY_ID_DEFAULT, {slipperyExitWindow, slipperyEnterWindow}}});
+
+ args = generateMotionArgs(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT, {{51, 51}});
+ mDispatcher->notifyMotion(&args);
+
+ slipperyExitWindow->consumeMotionCancel();
+
+ slipperyEnterWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT,
+ AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED);
+}
+
class InputDispatcherKeyRepeatTest : public InputDispatcherTest {
protected:
static constexpr nsecs_t KEY_REPEAT_TIMEOUT = 40 * 1000000; // 40 ms
@@ -2308,8 +2457,8 @@
InputDispatcherTest::SetUp();
application1 = std::make_shared<FakeApplicationHandle>();
- windowInPrimary = new FakeWindowHandle(application1, mDispatcher, "D_1",
- ADISPLAY_ID_DEFAULT);
+ windowInPrimary =
+ new FakeWindowHandle(application1, mDispatcher, "D_1", ADISPLAY_ID_DEFAULT);
// Set focus window for primary display, but focused display would be second one.
mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application1);
@@ -2319,8 +2468,8 @@
windowInPrimary->consumeFocusEvent(true);
application2 = std::make_shared<FakeApplicationHandle>();
- windowInSecondary = new FakeWindowHandle(application2, mDispatcher, "D_2",
- SECOND_DISPLAY_ID);
+ windowInSecondary =
+ new FakeWindowHandle(application2, mDispatcher, "D_2", SECOND_DISPLAY_ID);
// Set focus to second display window.
// Set focus display to second one.
mDispatcher->setFocusedDisplay(SECOND_DISPLAY_ID);
@@ -2431,7 +2580,7 @@
// Test per-display input monitors for key event.
TEST_F(InputDispatcherFocusOnTwoDisplaysTest, MonitorKeyEvent_MultiDisplay) {
- //Input monitor per display.
+ // Input monitor per display.
FakeMonitorReceiver monitorInPrimary =
FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
FakeMonitorReceiver monitorInSecondary =
@@ -2470,11 +2619,11 @@
void testNotifyMotion(int32_t displayId, bool expectToBeFiltered) {
NotifyMotionArgs motionArgs;
- motionArgs = generateMotionArgs(
- AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, displayId);
+ motionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, displayId);
mDispatcher->notifyMotion(&motionArgs);
- motionArgs = generateMotionArgs(
- AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, displayId);
+ motionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, displayId);
mDispatcher->notifyMotion(&motionArgs);
ASSERT_TRUE(mDispatcher->waitForIdle());
if (expectToBeFiltered) {
@@ -2542,8 +2691,8 @@
std::shared_ptr<FakeApplicationHandle> application =
std::make_shared<FakeApplicationHandle>();
- mUnfocusedWindow = new FakeWindowHandle(application, mDispatcher, "Top",
- ADISPLAY_ID_DEFAULT);
+ mUnfocusedWindow =
+ new FakeWindowHandle(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
mUnfocusedWindow->setFrame(Rect(0, 0, 30, 30));
// Adding FLAG_NOT_TOUCH_MODAL to ensure taps outside this window are not sent to this
// window.
@@ -2618,8 +2767,7 @@
// Have two windows, one with focus. Inject MotionEvent with source TOUCHSCREEN and action
// DOWN on the window that already has focus. Ensure no window received the
// onPointerDownOutsideFocus callback.
-TEST_F(InputDispatcherOnPointerDownOutsideFocus,
- OnPointerDownOutsideFocus_OnAlreadyFocusedWindow) {
+TEST_F(InputDispatcherOnPointerDownOutsideFocus, OnPointerDownOutsideFocus_OnAlreadyFocusedWindow) {
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
FOCUSED_WINDOW_TOUCH_POINT))
@@ -3824,4 +3972,88 @@
// window gets the pending key event
mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT);
}
+
+class InputDispatcherPointerCaptureTests : public InputDispatcherTest {
+protected:
+ std::shared_ptr<FakeApplicationHandle> mApp;
+ sp<FakeWindowHandle> mWindow;
+ sp<FakeWindowHandle> mSecondWindow;
+
+ void SetUp() override {
+ InputDispatcherTest::SetUp();
+ mApp = std::make_shared<FakeApplicationHandle>();
+ mWindow = new FakeWindowHandle(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
+ mWindow->setFocusable(true);
+ mSecondWindow = new FakeWindowHandle(mApp, mDispatcher, "TestWindow2", ADISPLAY_ID_DEFAULT);
+ mSecondWindow->setFocusable(true);
+
+ mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApp);
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mSecondWindow}}});
+
+ setFocusedWindow(mWindow);
+ mWindow->consumeFocusEvent(true);
+ }
+
+ void notifyPointerCaptureChanged(bool enabled) {
+ const NotifyPointerCaptureChangedArgs args = generatePointerCaptureChangedArgs(enabled);
+ mDispatcher->notifyPointerCaptureChanged(&args);
+ }
+
+ void requestAndVerifyPointerCapture(const sp<FakeWindowHandle>& window, bool enabled) {
+ mDispatcher->requestPointerCapture(window->getToken(), enabled);
+ mFakePolicy->waitForSetPointerCapture(enabled);
+ notifyPointerCaptureChanged(enabled);
+ window->consumeCaptureEvent(enabled);
+ }
+};
+
+TEST_F(InputDispatcherPointerCaptureTests, EnablePointerCaptureWhenFocused) {
+ // Ensure that capture cannot be obtained for unfocused windows.
+ mDispatcher->requestPointerCapture(mSecondWindow->getToken(), true);
+ mFakePolicy->assertSetPointerCaptureNotCalled();
+ mSecondWindow->assertNoEvents();
+
+ // Ensure that capture can be enabled from the focus window.
+ requestAndVerifyPointerCapture(mWindow, true);
+
+ // Ensure that capture cannot be disabled from a window that does not have capture.
+ mDispatcher->requestPointerCapture(mSecondWindow->getToken(), false);
+ mFakePolicy->assertSetPointerCaptureNotCalled();
+
+ // Ensure that capture can be disabled from the window with capture.
+ requestAndVerifyPointerCapture(mWindow, false);
+}
+
+TEST_F(InputDispatcherPointerCaptureTests, DisablesPointerCaptureAfterWindowLosesFocus) {
+ requestAndVerifyPointerCapture(mWindow, true);
+
+ setFocusedWindow(mSecondWindow);
+
+ // Ensure that the capture disabled event was sent first.
+ mWindow->consumeCaptureEvent(false);
+ mWindow->consumeFocusEvent(false);
+ mSecondWindow->consumeFocusEvent(true);
+ mFakePolicy->waitForSetPointerCapture(false);
+
+ // Ensure that additional state changes from InputReader are not sent to the window.
+ notifyPointerCaptureChanged(false);
+ notifyPointerCaptureChanged(true);
+ notifyPointerCaptureChanged(false);
+ mWindow->assertNoEvents();
+ mSecondWindow->assertNoEvents();
+ mFakePolicy->assertSetPointerCaptureNotCalled();
+}
+
+TEST_F(InputDispatcherPointerCaptureTests, UnexpectedStateChangeDisablesPointerCapture) {
+ requestAndVerifyPointerCapture(mWindow, true);
+
+ // InputReader unexpectedly disables and enables pointer capture.
+ notifyPointerCaptureChanged(false);
+ notifyPointerCaptureChanged(true);
+
+ // Ensure that Pointer Capture is disabled.
+ mWindow->consumeCaptureEvent(false);
+ mWindow->assertNoEvents();
+}
+
} // namespace android::inputdispatcher
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index e9416d6..786d331 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -4910,10 +4910,11 @@
case CAPTURE_LAYERS:
case CAPTURE_DISPLAY:
case SET_DISPLAY_BRIGHTNESS:
- case SET_FRAME_TIMELINE_VSYNC: {
+ case SET_FRAME_TIMELINE_VSYNC:
+ // This is not sensitive information, so should not require permission control.
+ case GET_GPU_CONTEXT_PRIORITY: {
return OK;
}
-
case ADD_REGION_SAMPLING_LISTENER:
case REMOVE_REGION_SAMPLING_LISTENER: {
// codes that require permission check
@@ -6335,6 +6336,13 @@
return NO_ERROR;
}
+int SurfaceFlinger::getGPUContextPriority() {
+ // TODO(b/168740533): This is a proof of concept. Once REAL time priority is available
+ // in EGL, we can return it in RenderEngine and propagate it to SurfaceFlinger. Until
+ // then return IntentFilter.SYSTEM_HIGH_PRIORITY.
+ return 1000;
+}
+
} // namespace android
#if defined(__gl_h_)
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 0509247..2e4d1ce 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -608,6 +608,8 @@
status_t addTransactionTraceListener(
const sp<gui::ITransactionTraceListener>& listener) override;
+ int getGPUContextPriority() override;
+
// Implements IBinder::DeathRecipient.
void binderDied(const wp<IBinder>& who) override;
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 030073c..7f05a75 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -388,6 +388,8 @@
return mFlinger->onTransact(code, data, reply, flags);
}
+ auto getGPUContextPriority() { return mFlinger->getGPUContextPriority(); }
+
/* ------------------------------------------------------------------------
* Read-only access to private data to assert post-conditions.
*/