Merge "Revert^2 " Remove TransferDeathRecipients when ABpBinder is deleted"" into main
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 826a8db..6b9a0a0 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -4197,14 +4197,14 @@
}
int read_file_as_long(const char *path, long int *output) {
- int fd = TEMP_FAILURE_RETRY(open(path, O_RDONLY | O_NONBLOCK | O_CLOEXEC));
- if (fd < 0) {
+ android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(path, O_RDONLY | O_NONBLOCK | O_CLOEXEC)));
+ if (fd.get() < 0) {
int err = errno;
MYLOGE("Error opening file descriptor for %s: %s\n", path, strerror(err));
return -1;
}
char buffer[50];
- ssize_t bytes_read = TEMP_FAILURE_RETRY(read(fd, buffer, sizeof(buffer)));
+ ssize_t bytes_read = TEMP_FAILURE_RETRY(read(fd.get(), buffer, sizeof(buffer)));
if (bytes_read == -1) {
MYLOGE("Error reading file %s: %s\n", path, strerror(errno));
return -2;
diff --git a/cmds/evemu-record/main.rs b/cmds/evemu-record/main.rs
index c30c00f..db3fd77 100644
--- a/cmds/evemu-record/main.rs
+++ b/cmds/evemu-record/main.rs
@@ -120,7 +120,7 @@
fn print_in_8_byte_chunks(
output: &mut impl Write,
prefix: &str,
- data: &Vec<u8>,
+ data: &[u8],
) -> Result<(), io::Error> {
for (i, byte) in data.iter().enumerate() {
if i % 8 == 0 {
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index eedf141..99f4fbd 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -287,6 +287,12 @@
}
prebuilt_etc {
+ name: "android.hardware.telephony.data.prebuilt.xml",
+ src: "android.hardware.telephony.data.xml",
+ defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
name: "android.hardware.telephony.gsm.prebuilt.xml",
src: "android.hardware.telephony.gsm.xml",
defaults: ["frameworks_native_data_etc_defaults"],
@@ -329,12 +335,30 @@
}
prebuilt_etc {
+ name: "android.hardware.vulkan.compute-0.prebuilt.xml",
+ src: "android.hardware.vulkan.compute-0.xml",
+ defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
+ name: "android.hardware.vulkan.level-1.prebuilt.xml",
+ src: "android.hardware.vulkan.level-1.xml",
+ defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
name: "android.hardware.vulkan.version-1_0_3.prebuilt.xml",
src: "android.hardware.vulkan.version-1_0_3.xml",
defaults: ["frameworks_native_data_etc_defaults"],
}
prebuilt_etc {
+ name: "android.hardware.vulkan.version-1_3.prebuilt.xml",
+ src: "android.hardware.vulkan.version-1_3.xml",
+ defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
name: "android.hardware.wifi.prebuilt.xml",
src: "android.hardware.wifi.xml",
defaults: ["frameworks_native_data_etc_defaults"],
diff --git a/include/android/input_transfer_token_jni.h b/include/android/input_transfer_token_jni.h
new file mode 100644
index 0000000..ba5f6f2
--- /dev/null
+++ b/include/android/input_transfer_token_jni.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2024 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.
+ */
+/**
+ * @addtogroup NativeActivity Native Activity
+ * @{
+ */
+/**
+ * @file input_transfer_token_jni.h
+ */
+#ifndef ANDROID_INPUT_TRANSFER_TOKEN_JNI_H
+#define ANDROID_INPUT_TRANSFER_TOKEN_JNI_H
+#include <sys/cdefs.h>
+#include <jni.h>
+
+__BEGIN_DECLS
+struct AInputTransferToken;
+
+/**
+ * AInputTransferToken can be used to request focus on or to transfer touch gesture to and from
+ * an embedded SurfaceControl
+ */
+typedef struct AInputTransferToken AInputTransferToken;
+
+/**
+ * Return the AInputTransferToken wrapped by a Java InputTransferToken object. This must be released
+ * using AInputTransferToken_release
+ *
+ * inputTransferTokenObj must be a non-null instance of android.window.InputTransferToken.
+ *
+ * Available since API level 35.
+ */
+AInputTransferToken* _Nonnull AInputTransferToken_fromJava(JNIEnv* _Nonnull env,
+ jobject _Nonnull inputTransferTokenObj) __INTRODUCED_IN(__ANDROID_API_V__);
+/**
+ * Return the Java InputTransferToken object that wraps AInputTransferToken
+ *
+ * aInputTransferToken must be non null and the returned value is an object of instance
+ * android.window.InputTransferToken.
+ *
+ * Available since API level 35.
+ */
+jobject _Nonnull AInputTransferToken_toJava(JNIEnv* _Nonnull env,
+ const AInputTransferToken* _Nonnull aInputTransferToken) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Removes a reference that was previously acquired in native.
+ *
+ * Available since API level 35.
+ */
+void AInputTransferToken_release(AInputTransferToken* _Nonnull aInputTransferToken)
+ __INTRODUCED_IN(__ANDROID_API_V__);
+
+__END_DECLS
+#endif // ANDROID_INPUT_TRANSFER_TOKEN_JNI_H
+/** @} */
diff --git a/include/android/surface_control.h b/include/android/surface_control.h
index 321737e..fb1419f 100644
--- a/include/android/surface_control.h
+++ b/include/android/surface_control.h
@@ -236,6 +236,10 @@
* it is acquired. If no acquire_fence_fd was provided, this timestamp will be set to -1.
*
* Available since API level 29.
+ *
+ * @deprecated This may return SIGNAL_PENDING because the stats can arrive before the acquire
+ * fence has signaled, depending on internal timing differences. Therefore the caller should
+ * use the acquire fence passed in to setBuffer and query the signal time.
*/
int64_t ASurfaceTransactionStats_getAcquireTime(ASurfaceTransactionStats* surface_transaction_stats,
ASurfaceControl* surface_control)
@@ -346,7 +350,7 @@
*/
void ASurfaceTransaction_setBuffer(ASurfaceTransaction* transaction,
ASurfaceControl* surface_control, AHardwareBuffer* buffer,
- int acquire_fence_fd = -1) __INTRODUCED_IN(29);
+ int acquire_fence_fd) __INTRODUCED_IN(29);
/**
* Updates the color for \a surface_control. This will make the background color for the
@@ -532,7 +536,7 @@
* using this API for formats that encode an HDR/SDR ratio as part of generating the buffer.
*
* @param surface_control The layer whose extended range brightness is being specified
- * @param currentBufferRatio The current hdr/sdr ratio of the current buffer as represented as
+ * @param currentBufferRatio The current HDR/SDR ratio of the current buffer as represented as
* peakHdrBrightnessInNits / targetSdrWhitePointInNits. For example if the
* buffer was rendered with a target SDR whitepoint of 100nits and a max
* display brightness of 200nits, this should be set to 2.0f.
@@ -546,7 +550,7 @@
*
* Must be finite && >= 1.0f
*
- * @param desiredRatio The desired hdr/sdr ratio as represented as peakHdrBrightnessInNits /
+ * @param desiredRatio The desired HDR/SDR ratio as represented as peakHdrBrightnessInNits /
* targetSdrWhitePointInNits. This can be used to communicate the max desired
* brightness range. This is similar to the "max luminance" value in other
* HDR metadata formats, but represented as a ratio of the target SDR whitepoint
@@ -579,13 +583,13 @@
float desiredRatio) __INTRODUCED_IN(__ANDROID_API_U__);
/**
- * Sets the desired hdr headroom for the layer. See: ASurfaceTransaction_setExtendedRangeBrightness,
+ * Sets the desired HDR headroom for the layer. See: ASurfaceTransaction_setExtendedRangeBrightness,
* prefer using this API for formats that conform to HDR standards like HLG or HDR10, that do not
* communicate a HDR/SDR ratio as part of generating the buffer.
*
- * @param surface_control The layer whose desired hdr headroom is being specified
+ * @param surface_control The layer whose desired HDR headroom is being specified
*
- * @param desiredHeadroom The desired hdr/sdr ratio as represented as peakHdrBrightnessInNits /
+ * @param desiredHeadroom The desired HDR/SDR ratio as represented as peakHdrBrightnessInNits /
* targetSdrWhitePointInNits. This can be used to communicate the max
* desired brightness range of the panel. The system may not be able to, or
* may choose not to, deliver the requested range.
diff --git a/include/android/surface_control_input_receiver.h b/include/android/surface_control_input_receiver.h
new file mode 100644
index 0000000..cd2c5df
--- /dev/null
+++ b/include/android/surface_control_input_receiver.h
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2024 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 <stdint.h>
+#include <android/input.h>
+#include <android/surface_control.h>
+#include <android/input_transfer_token_jni.h>
+
+__BEGIN_DECLS
+
+/**
+ * The AInputReceiver_onMotionEvent callback is invoked when the registered input channel receives
+ * a motion event.
+ *
+ * \param context Optional context provided by the client that is passed when creating the
+ * AInputReceiverCallbacks.
+ *
+ * \param motionEvent The motion event. This must be released with AInputEvent_release.
+ *
+ * Available since API level 35.
+ */
+typedef bool (*AInputReceiver_onMotionEvent)(void *_Null_unspecified context,
+ AInputEvent *_Nonnull motionEvent)
+ __INTRODUCED_IN(__ANDROID_API_V__);
+/**
+ * The AInputReceiver_onKeyEvent callback is invoked when the registered input channel receives
+ * a key event.
+ *
+ * \param context Optional context provided by the client that is passed when creating the
+ * AInputReceiverCallbacks.
+ *
+ * \param keyEvent The key event. This must be released with AInputEvent_release.
+ *
+ * Available since API level 35.
+ */
+typedef bool (*AInputReceiver_onKeyEvent)(void *_Null_unspecified context,
+ AInputEvent *_Nonnull keyEvent)
+ __INTRODUCED_IN(__ANDROID_API_V__);
+
+struct AInputReceiverCallbacks;
+
+struct AInputReceiver;
+
+/**
+ * The InputReceiver that holds the reference to the registered input channel. This must be released
+ * using AInputReceiver_release
+ *
+ * Available since API level 35.
+ */
+typedef struct AInputReceiver AInputReceiver __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Registers an input receiver for an ASurfaceControl that will receive batched input event. For
+ * those events that are batched, the invocation will happen once per AChoreographer frame, and
+ * other input events will be delivered immediately.
+ *
+ * This is different from AInputReceiver_createUnbatchedInputReceiver in that the input events are
+ * received batched. The caller must invoke AInputReceiver_release to cleanv up the resources when
+ * no longer needing to use the input receiver.
+ *
+ * \param aChoreographer The AChoreographer used for batching. This should match the
+ * rendering AChoreographer.
+ * \param hostInputTransferToken The host token to link the embedded. This is used to handle
+ * transferring touch gesture from host to embedded and for ANRs
+ * to ensure the host receives the ANR if any issues with
+ * touch on the embedded. This can be retrieved for the host window
+ * by calling AttachedSurfaceControl#getInputTransferToken()
+ * \param aSurfaceControl The ASurfaceControl to register the InputChannel for
+ * \param aInputReceiverCallbacks The SurfaceControlInputReceiver that will receive the input events
+ *
+ * Returns the reference to AInputReceiver to clean up resources when done.
+ *
+ * Available since API level 35.
+ */
+AInputReceiver* _Nonnull
+AInputReceiver_createBatchedInputReceiver(AChoreographer* _Nonnull aChoreographer,
+ const AInputTransferToken* _Nonnull hostInputTransferToken,
+ const ASurfaceControl* _Nonnull aSurfaceControl,
+ AInputReceiverCallbacks* _Nonnull aInputReceiverCallbacks)
+ __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Registers an input receiver for an ASurfaceControl that will receive every input event.
+ * This is different from AInputReceiver_createBatchedInputReceiver in that the input events are
+ * received unbatched. The caller must invoke AInputReceiver_release to clean up the resources when
+ * no longer needing to use the input receiver.
+ *
+ * \param aLooper The looper to use when invoking callbacks.
+ * \param hostInputTransferToken The host token to link the embedded. This is used to handle
+ * transferring touch gesture from host to embedded and for ANRs
+ * to ensure the host receives the ANR if any issues with
+ * touch on the embedded. This can be retrieved for the host window
+ * by calling AttachedSurfaceControl#getInputTransferToken()
+ * \param aSurfaceControl The ASurfaceControl to register the InputChannel for
+ * \param aInputReceiverCallbacks The SurfaceControlInputReceiver that will receive the input events
+ *
+ * Returns the reference to AInputReceiver to clean up resources when done.
+ *
+ * Available since API level 35.
+ */
+AInputReceiver* _Nonnull
+AInputReceiver_createUnbatchedInputReceiver(ALooper* _Nonnull aLooper,
+ const AInputTransferToken* _Nonnull hostInputTransferToken,
+ const ASurfaceControl* _Nonnull aSurfaceControl,
+ AInputReceiverCallbacks* _Nonnull aInputReceiverCallbacks)
+ __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Returns the AInputTransferToken that can be used to transfer touch gesture to or from other
+ * windows. This InputTransferToken is associated with the SurfaceControl that registered an input
+ * receiver and can be used with the host token for things like transfer touch gesture via
+ * WindowManager#transferTouchGesture().
+ *
+ * This must be released with AInputTransferToken_release.
+ *
+ * \param aInputReceiver The inputReceiver object to retrieve the AInputTransferToken for.
+ *
+ * Available since API level 35.
+ */
+const AInputTransferToken *_Nonnull
+AInputReceiver_getInputTransferToken(AInputReceiver *_Nonnull aInputReceiver)
+ __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Unregisters the input channel and deletes the AInputReceiver. This must be called on the same
+ * looper thread it was created with.
+ *
+ * \param aInputReceiver The inputReceiver object to release.
+ *
+ * Available since API level 35.
+ */
+void
+AInputReceiver_release(AInputReceiver *_Nonnull aInputReceiver) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Creates a AInputReceiverCallbacks object that is used when registering for an AInputReceiver.
+ * This must be released using AInputReceiverCallbacks_release
+ *
+ * \param context Optional context provided by the client that will be passed into the callbacks.
+ *
+ * Available since API level 35.
+ */
+AInputReceiverCallbacks* _Nonnull AInputReceiverCallbacks_create(void* _Nullable context)
+ __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Releases the AInputReceiverCallbacks. This must be called on the same
+ * looper thread the AInputReceiver was created with. The receiver will not invoke any callbacks
+ * once it's been released.
+ *
+ * Available since API level 35
+ */
+void AInputReceiverCallbacks_release(AInputReceiverCallbacks* _Nonnull callbacks)
+ __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Sets a AInputReceiver_onMotionEvent callback for an AInputReceiverCallbacks
+ *
+ * \param callbacks The callback object to set the motion event on.
+ * \param onMotionEvent The motion event that will be invoked
+ *
+ * Available since API level 35.
+ */
+void AInputReceiverCallbacks_setMotionEventCallback(AInputReceiverCallbacks* _Nonnull callbacks,
+ AInputReceiver_onMotionEvent _Nonnull onMotionEvent)
+ __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Sets a AInputReceiver_onKeyEvent callback for an AInputReceiverCallbacks
+ *
+ * \param callbacks The callback object to set the motion event on.
+ * \param onMotionEvent The key event that will be invoked
+ *
+ * Available since API level 35.
+ */
+void AInputReceiverCallbacks_setKeyEventCallback(AInputReceiverCallbacks* _Nonnull callbacks,
+ AInputReceiver_onKeyEvent _Nonnull onKeyEvent)
+ __INTRODUCED_IN(__ANDROID_API_V__);
+
+__END_DECLS
diff --git a/services/inputflinger/BlockingQueue.h b/include/input/BlockingQueue.h
similarity index 100%
rename from services/inputflinger/BlockingQueue.h
rename to include/input/BlockingQueue.h
diff --git a/include/input/Input.h b/include/input/Input.h
index ddc3768..19f4ab3 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -1192,15 +1192,17 @@
*/
struct PointerCaptureRequest {
public:
- inline PointerCaptureRequest() : enable(false), seq(0) {}
- inline PointerCaptureRequest(bool enable, uint32_t seq) : enable(enable), seq(seq) {}
+ inline PointerCaptureRequest() : window(), seq(0) {}
+ inline PointerCaptureRequest(sp<IBinder> window, uint32_t seq) : window(window), seq(seq) {}
inline bool operator==(const PointerCaptureRequest& other) const {
- return enable == other.enable && seq == other.seq;
+ return window == other.window && seq == other.seq;
}
- explicit inline operator bool() const { return enable; }
+ inline bool isEnable() const { return window != nullptr; }
- // True iff this is a request to enable Pointer Capture.
- bool enable;
+ // The requesting window.
+ // If the request is to enable the capture, this is the input token of the window that requested
+ // pointer capture. Otherwise, this is nullptr.
+ sp<IBinder> window;
// The sequence number for the request.
uint32_t seq;
diff --git a/include/input/InputConsumer.h b/include/input/InputConsumer.h
new file mode 100644
index 0000000..560e804
--- /dev/null
+++ b/include/input/InputConsumer.h
@@ -0,0 +1,249 @@
+/*
+ * Copyright 2024 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
+
+/*
+ * Native input transport.
+ *
+ * The InputConsumer is used by the application to receive events from the input dispatcher.
+ */
+
+#include "InputTransport.h"
+
+namespace android {
+
+/*
+ * Consumes input events from an input channel.
+ */
+class InputConsumer {
+public:
+ /* Create a consumer associated with an input channel. */
+ explicit InputConsumer(const std::shared_ptr<InputChannel>& channel);
+ /* Create a consumer associated with an input channel, override resampling system property */
+ explicit InputConsumer(const std::shared_ptr<InputChannel>& channel,
+ bool enableTouchResampling);
+
+ /* Destroys the consumer and releases its input channel. */
+ ~InputConsumer();
+
+ /* Gets the underlying input channel. */
+ inline std::shared_ptr<InputChannel> getChannel() { return mChannel; }
+
+ /* Consumes an input event from the input channel and copies its contents into
+ * an InputEvent object created using the specified factory.
+ *
+ * Tries to combine a series of move events into larger batches whenever possible.
+ *
+ * If consumeBatches is false, then defers consuming pending batched events if it
+ * is possible for additional samples to be added to them later. Call hasPendingBatch()
+ * to determine whether a pending batch is available to be consumed.
+ *
+ * If consumeBatches is true, then events are still batched but they are consumed
+ * immediately as soon as the input channel is exhausted.
+ *
+ * The frameTime parameter specifies the time when the current display frame started
+ * rendering in the CLOCK_MONOTONIC time base, or -1 if unknown.
+ *
+ * The returned sequence number is never 0 unless the operation failed.
+ *
+ * Returns OK on success.
+ * Returns WOULD_BLOCK if there is no event present.
+ * Returns DEAD_OBJECT if the channel's peer has been closed.
+ * Returns NO_MEMORY if the event could not be created.
+ * Other errors probably indicate that the channel is broken.
+ */
+ status_t consume(InputEventFactoryInterface* factory, bool consumeBatches, nsecs_t frameTime,
+ uint32_t* outSeq, InputEvent** outEvent);
+
+ /* Sends a finished signal to the publisher to inform it that the message
+ * with the specified sequence number has finished being process and whether
+ * the message was handled by the consumer.
+ *
+ * Returns OK on success.
+ * Returns BAD_VALUE if seq is 0.
+ * Other errors probably indicate that the channel is broken.
+ */
+ status_t sendFinishedSignal(uint32_t seq, bool handled);
+
+ status_t sendTimeline(int32_t inputEventId,
+ std::array<nsecs_t, GraphicsTimeline::SIZE> timeline);
+
+ /* Returns true if there is a pending batch.
+ *
+ * Should be called after calling consume() with consumeBatches == false to determine
+ * whether consume() should be called again later on with consumeBatches == true.
+ */
+ bool hasPendingBatch() const;
+
+ /* Returns the source of first pending batch if exist.
+ *
+ * Should be called after calling consume() with consumeBatches == false to determine
+ * whether consume() should be called again later on with consumeBatches == true.
+ */
+ int32_t getPendingBatchSource() const;
+
+ /* Returns true when there is *likely* a pending batch or a pending event in the channel.
+ *
+ * This is only a performance hint and may return false negative results. Clients should not
+ * rely on availability of the message based on the return value.
+ */
+ bool probablyHasInput() const;
+
+ std::string dump() const;
+
+private:
+ // True if touch resampling is enabled.
+ const bool mResampleTouch;
+
+ std::shared_ptr<InputChannel> mChannel;
+
+ // The current input message.
+ InputMessage mMsg;
+
+ // True if mMsg contains a valid input message that was deferred from the previous
+ // call to consume and that still needs to be handled.
+ bool mMsgDeferred;
+
+ // Batched motion events per device and source.
+ struct Batch {
+ std::vector<InputMessage> samples;
+ };
+ std::vector<Batch> mBatches;
+
+ // Touch state per device and source, only for sources of class pointer.
+ struct History {
+ nsecs_t eventTime;
+ BitSet32 idBits;
+ int32_t idToIndex[MAX_POINTER_ID + 1];
+ PointerCoords pointers[MAX_POINTERS];
+
+ void initializeFrom(const InputMessage& msg) {
+ eventTime = msg.body.motion.eventTime;
+ idBits.clear();
+ for (uint32_t i = 0; i < msg.body.motion.pointerCount; i++) {
+ uint32_t id = msg.body.motion.pointers[i].properties.id;
+ idBits.markBit(id);
+ idToIndex[id] = i;
+ pointers[i].copyFrom(msg.body.motion.pointers[i].coords);
+ }
+ }
+
+ void initializeFrom(const History& other) {
+ eventTime = other.eventTime;
+ idBits = other.idBits; // temporary copy
+ for (size_t i = 0; i < other.idBits.count(); i++) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ int32_t index = other.idToIndex[id];
+ idToIndex[id] = index;
+ pointers[index].copyFrom(other.pointers[index]);
+ }
+ idBits = other.idBits; // final copy
+ }
+
+ const PointerCoords& getPointerById(uint32_t id) const { return pointers[idToIndex[id]]; }
+
+ bool hasPointerId(uint32_t id) const { return idBits.hasBit(id); }
+ };
+ struct TouchState {
+ int32_t deviceId;
+ int32_t source;
+ size_t historyCurrent;
+ size_t historySize;
+ History history[2];
+ History lastResample;
+
+ void initialize(int32_t incomingDeviceId, int32_t incomingSource) {
+ deviceId = incomingDeviceId;
+ source = incomingSource;
+ historyCurrent = 0;
+ historySize = 0;
+ lastResample.eventTime = 0;
+ lastResample.idBits.clear();
+ }
+
+ void addHistory(const InputMessage& msg) {
+ historyCurrent ^= 1;
+ if (historySize < 2) {
+ historySize += 1;
+ }
+ history[historyCurrent].initializeFrom(msg);
+ }
+
+ const History* getHistory(size_t index) const {
+ return &history[(historyCurrent + index) & 1];
+ }
+
+ bool recentCoordinatesAreIdentical(uint32_t id) const {
+ // Return true if the two most recently received "raw" coordinates are identical
+ if (historySize < 2) {
+ return false;
+ }
+ if (!getHistory(0)->hasPointerId(id) || !getHistory(1)->hasPointerId(id)) {
+ return false;
+ }
+ float currentX = getHistory(0)->getPointerById(id).getX();
+ float currentY = getHistory(0)->getPointerById(id).getY();
+ float previousX = getHistory(1)->getPointerById(id).getX();
+ float previousY = getHistory(1)->getPointerById(id).getY();
+ if (currentX == previousX && currentY == previousY) {
+ return true;
+ }
+ return false;
+ }
+ };
+ std::vector<TouchState> mTouchStates;
+
+ // Chain of batched sequence numbers. When multiple input messages are combined into
+ // a batch, we append a record here that associates the last sequence number in the
+ // batch with the previous one. When the finished signal is sent, we traverse the
+ // chain to individually finish all input messages that were part of the batch.
+ struct SeqChain {
+ uint32_t seq; // sequence number of batched input message
+ uint32_t chain; // sequence number of previous batched input message
+ };
+ std::vector<SeqChain> mSeqChains;
+
+ // The time at which each event with the sequence number 'seq' was consumed.
+ // This data is provided in 'finishInputEvent' so that the receiving end can measure the latency
+ // This collection is populated when the event is received, and the entries are erased when the
+ // events are finished. It should not grow infinitely because if an event is not ack'd, ANR
+ // will be raised for that connection, and no further events will be posted to that channel.
+ std::unordered_map<uint32_t /*seq*/, nsecs_t /*consumeTime*/> mConsumeTimes;
+
+ status_t consumeBatch(InputEventFactoryInterface* factory, nsecs_t frameTime, uint32_t* outSeq,
+ InputEvent** outEvent);
+ status_t consumeSamples(InputEventFactoryInterface* factory, Batch& batch, size_t count,
+ uint32_t* outSeq, InputEvent** outEvent);
+
+ void updateTouchState(InputMessage& msg);
+ void resampleTouchState(nsecs_t frameTime, MotionEvent* event, const InputMessage* next);
+
+ ssize_t findBatch(int32_t deviceId, int32_t source) const;
+ ssize_t findTouchState(int32_t deviceId, int32_t source) const;
+
+ nsecs_t getConsumeTime(uint32_t seq) const;
+ void popConsumeTime(uint32_t seq);
+ status_t sendUnchainedFinishedSignal(uint32_t seq, bool handled);
+
+ static void rewriteMessage(TouchState& state, InputMessage& msg);
+ static bool canAddSample(const Batch& batch, const InputMessage* msg);
+ static ssize_t findSampleNoLaterThan(const Batch& batch, nsecs_t time);
+
+ static bool isTouchResamplingEnabled();
+};
+
+} // namespace android
diff --git a/include/input/InputConsumerNoResampling.h b/include/input/InputConsumerNoResampling.h
new file mode 100644
index 0000000..9e48b08
--- /dev/null
+++ b/include/input/InputConsumerNoResampling.h
@@ -0,0 +1,211 @@
+/**
+ * Copyright 2024 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 <utils/Looper.h>
+#include "InputTransport.h"
+
+namespace android {
+
+/**
+ * An interface to receive batched input events. Even if you don't want batching, you still have to
+ * use this interface, and some of the events will be batched if your implementation is slow to
+ * handle the incoming input.
+ */
+class InputConsumerCallbacks {
+public:
+ virtual ~InputConsumerCallbacks(){};
+ virtual void onKeyEvent(std::unique_ptr<KeyEvent> event, uint32_t seq) = 0;
+ virtual void onMotionEvent(std::unique_ptr<MotionEvent> event, uint32_t seq) = 0;
+ /**
+ * When you receive this callback, you must (eventually) call "consumeBatchedInputEvents".
+ * If you don't want batching, then call "consumeBatchedInputEvents" immediately with
+ * std::nullopt frameTime to receive the pending motion event(s).
+ * @param pendingBatchSource the source of the pending batch.
+ */
+ virtual void onBatchedInputEventPending(int32_t pendingBatchSource) = 0;
+ virtual void onFocusEvent(std::unique_ptr<FocusEvent> event, uint32_t seq) = 0;
+ virtual void onCaptureEvent(std::unique_ptr<CaptureEvent> event, uint32_t seq) = 0;
+ virtual void onDragEvent(std::unique_ptr<DragEvent> event, uint32_t seq) = 0;
+ virtual void onTouchModeEvent(std::unique_ptr<TouchModeEvent> event, uint32_t seq) = 0;
+};
+
+/**
+ * Consumes input events from an input channel.
+ *
+ * This is a re-implementation of InputConsumer that does not have resampling at the current moment.
+ * A lot of the higher-level logic has been folded into this class, to make it easier to use.
+ * In the legacy class, InputConsumer, the consumption logic was partially handled in the jni layer,
+ * as well as various actions like adding the fd to the Choreographer.
+ *
+ * TODO(b/297226446): use this instead of "InputConsumer":
+ * - Add resampling to this class
+ * - Allow various resampling strategies to be specified
+ * - Delete the old "InputConsumer" and use this class instead, renaming it to "InputConsumer".
+ * - Add tracing
+ * - Update all tests to use the new InputConsumer
+ *
+ * This class is not thread-safe. We are currently allowing the constructor to run on any thread,
+ * but all of the remaining APIs should be invoked on the looper thread only.
+ */
+class InputConsumerNoResampling final {
+public:
+ explicit InputConsumerNoResampling(const std::shared_ptr<InputChannel>& channel,
+ sp<Looper> looper, InputConsumerCallbacks& callbacks);
+ ~InputConsumerNoResampling();
+
+ /**
+ * Must be called exactly once for each event received through the callbacks.
+ */
+ void finishInputEvent(uint32_t seq, bool handled);
+ void reportTimeline(int32_t inputEventId, nsecs_t gpuCompletedTime, nsecs_t presentTime);
+ /**
+ * If you want to consume all events immediately (disable batching), the you still must call
+ * this. For frameTime, use a std::nullopt.
+ * @param frameTime the time up to which consume the events. When there's double (or triple)
+ * buffering, you may want to not consume all events currently available, because you could be
+ * still working on an older frame, but there could already have been events that arrived that
+ * are more recent.
+ * @return whether any events were actually consumed
+ */
+ bool consumeBatchedInputEvents(std::optional<nsecs_t> frameTime);
+ /**
+ * Returns true when there is *likely* a pending batch or a pending event in the channel.
+ *
+ * This is only a performance hint and may return false negative results. Clients should not
+ * rely on availability of the message based on the return value.
+ */
+ bool probablyHasInput() const;
+
+ std::string getName() { return mChannel->getName(); }
+
+ std::string dump() const;
+
+private:
+ std::shared_ptr<InputChannel> mChannel;
+ sp<Looper> mLooper;
+ InputConsumerCallbacks& mCallbacks;
+
+ // Looper-related infrastructure
+ /**
+ * This class is needed to associate the function "handleReceiveCallback" with the provided
+ * looper. The callback sent to the looper is RefBase - based, so we can't just send a reference
+ * of this class directly to the looper.
+ */
+ class LooperEventCallback : public LooperCallback {
+ public:
+ LooperEventCallback(std::function<int(int events)> callback) : mCallback(callback) {}
+ int handleEvent(int /*fd*/, int events, void* /*data*/) override {
+ return mCallback(events);
+ }
+
+ private:
+ std::function<int(int events)> mCallback;
+ };
+ sp<LooperEventCallback> mCallback;
+ /**
+ * The actual code that executes when the looper encounters available data on the InputChannel.
+ */
+ int handleReceiveCallback(int events);
+ int mFdEvents;
+ void setFdEvents(int events);
+
+ void ensureCalledOnLooperThread(const char* func) const;
+
+ // Event-reading infrastructure
+ /**
+ * A fifo queue of events to be sent to the InputChannel. We can't send all InputMessages to
+ * the channel immediately when they are produced, because it's possible that the InputChannel
+ * is blocked (if the channel buffer is full). When that happens, we don't want to drop the
+ * events. Therefore, events should only be erased from the queue after they've been
+ * successfully written to the InputChannel.
+ */
+ std::queue<InputMessage> mOutboundQueue;
+ /**
+ * Try to send all of the events in mOutboundQueue over the InputChannel. Not all events might
+ * actually get sent, because it's possible that the channel is blocked.
+ */
+ void processOutboundEvents();
+
+ /**
+ * The time at which each event with the sequence number 'seq' was consumed.
+ * This data is provided in 'finishInputEvent' so that the receiving end can measure the latency
+ * This collection is populated when the event is received, and the entries are erased when the
+ * events are finished. It should not grow infinitely because if an event is not ack'd, ANR
+ * will be raised for that connection, and no further events will be posted to that channel.
+ */
+ std::unordered_map<uint32_t /*seq*/, nsecs_t /*consumeTime*/> mConsumeTimes;
+ /**
+ * Find and return the consumeTime associated with the provided sequence number. Crashes if
+ * the provided seq number is not found.
+ */
+ nsecs_t popConsumeTime(uint32_t seq);
+
+ // Event reading and processing
+ /**
+ * Read all of the available events from the InputChannel
+ */
+ std::vector<InputMessage> readAllMessages();
+
+ /**
+ * Send InputMessage to the corresponding InputConsumerCallbacks function.
+ * @param msg
+ */
+ void handleMessage(const InputMessage& msg) const;
+
+ // Batching
+ /**
+ * Batch messages that can be batched. When an unbatchable message is encountered, send it
+ * to the InputConsumerCallbacks immediately. If there are batches remaining,
+ * notify InputConsumerCallbacks.
+ */
+ void handleMessages(std::vector<InputMessage>&& messages);
+ /**
+ * Batched InputMessages, per deviceId.
+ * For each device, we are storing a queue of batched messages. These will all be collapsed into
+ * a single MotionEvent (up to a specific frameTime) when the consumer calls
+ * `consumeBatchedInputEvents`.
+ */
+ std::map<DeviceId, std::queue<InputMessage>> mBatches;
+ /**
+ * A map from a single sequence number to several sequence numbers. This is needed because of
+ * batching. When batching is enabled, a single MotionEvent will contain several samples. Each
+ * sample came from an individual InputMessage of Type::Motion, and therefore will have to be
+ * finished individually. Therefore, when the app calls "finish" on a (possibly batched)
+ * MotionEvent, we will need to check this map in case there are multiple sequence numbers
+ * associated with a single number that the app provided.
+ *
+ * For example:
+ * Suppose we received 4 InputMessage's of type Motion, with action MOVE:
+ * InputMessage(MOVE) InputMessage(MOVE) InputMessage(MOVE) InputMessage(MOVE)
+ * seq=10 seq=11 seq=12 seq=13
+ * The app consumed them all as a batch, which means that the app received a single MotionEvent
+ * with historySize=3 and seq = 10.
+ *
+ * This map will look like:
+ * {
+ * 10: [11, 12, 13],
+ * }
+ * So the sequence number 10 will have 3 other sequence numbers associated with it.
+ * When the app calls 'finish' for seq=10, we need to call 'finish' 4 times total, for sequence
+ * numbers 10, 11, 12, 13. The app is not aware of the sequence numbers of each sample inside
+ * the batched MotionEvent that it received.
+ */
+ std::map<uint32_t, std::vector<uint32_t>> mBatchedSequenceNumbers;
+};
+
+} // namespace android
diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h
index 42dcd3c..5f9c8f5 100644
--- a/include/input/InputTransport.h
+++ b/include/input/InputTransport.h
@@ -451,236 +451,4 @@
InputVerifier mInputVerifier;
};
-/*
- * Consumes input events from an input channel.
- */
-class InputConsumer {
-public:
- /* Create a consumer associated with an input channel. */
- explicit InputConsumer(const std::shared_ptr<InputChannel>& channel);
- /* Create a consumer associated with an input channel, override resampling system property */
- explicit InputConsumer(const std::shared_ptr<InputChannel>& channel,
- bool enableTouchResampling);
-
- /* Destroys the consumer and releases its input channel. */
- ~InputConsumer();
-
- /* Gets the underlying input channel. */
- inline std::shared_ptr<InputChannel> getChannel() { return mChannel; }
-
- /* Consumes an input event from the input channel and copies its contents into
- * an InputEvent object created using the specified factory.
- *
- * Tries to combine a series of move events into larger batches whenever possible.
- *
- * If consumeBatches is false, then defers consuming pending batched events if it
- * is possible for additional samples to be added to them later. Call hasPendingBatch()
- * to determine whether a pending batch is available to be consumed.
- *
- * If consumeBatches is true, then events are still batched but they are consumed
- * immediately as soon as the input channel is exhausted.
- *
- * The frameTime parameter specifies the time when the current display frame started
- * rendering in the CLOCK_MONOTONIC time base, or -1 if unknown.
- *
- * The returned sequence number is never 0 unless the operation failed.
- *
- * Returns OK on success.
- * Returns WOULD_BLOCK if there is no event present.
- * Returns DEAD_OBJECT if the channel's peer has been closed.
- * Returns NO_MEMORY if the event could not be created.
- * Other errors probably indicate that the channel is broken.
- */
- status_t consume(InputEventFactoryInterface* factory, bool consumeBatches, nsecs_t frameTime,
- uint32_t* outSeq, InputEvent** outEvent);
-
- /* Sends a finished signal to the publisher to inform it that the message
- * with the specified sequence number has finished being process and whether
- * the message was handled by the consumer.
- *
- * Returns OK on success.
- * Returns BAD_VALUE if seq is 0.
- * Other errors probably indicate that the channel is broken.
- */
- status_t sendFinishedSignal(uint32_t seq, bool handled);
-
- status_t sendTimeline(int32_t inputEventId,
- std::array<nsecs_t, GraphicsTimeline::SIZE> timeline);
-
- /* Returns true if there is a pending batch.
- *
- * Should be called after calling consume() with consumeBatches == false to determine
- * whether consume() should be called again later on with consumeBatches == true.
- */
- bool hasPendingBatch() const;
-
- /* Returns the source of first pending batch if exist.
- *
- * Should be called after calling consume() with consumeBatches == false to determine
- * whether consume() should be called again later on with consumeBatches == true.
- */
- int32_t getPendingBatchSource() const;
-
- /* Returns true when there is *likely* a pending batch or a pending event in the channel.
- *
- * This is only a performance hint and may return false negative results. Clients should not
- * rely on availability of the message based on the return value.
- */
- bool probablyHasInput() const;
-
- std::string dump() const;
-
-private:
- // True if touch resampling is enabled.
- const bool mResampleTouch;
-
- std::shared_ptr<InputChannel> mChannel;
-
- // The current input message.
- InputMessage mMsg;
-
- // True if mMsg contains a valid input message that was deferred from the previous
- // call to consume and that still needs to be handled.
- bool mMsgDeferred;
-
- // Batched motion events per device and source.
- struct Batch {
- std::vector<InputMessage> samples;
- };
- std::vector<Batch> mBatches;
-
- // Touch state per device and source, only for sources of class pointer.
- struct History {
- nsecs_t eventTime;
- BitSet32 idBits;
- int32_t idToIndex[MAX_POINTER_ID + 1];
- PointerCoords pointers[MAX_POINTERS];
-
- void initializeFrom(const InputMessage& msg) {
- eventTime = msg.body.motion.eventTime;
- idBits.clear();
- for (uint32_t i = 0; i < msg.body.motion.pointerCount; i++) {
- uint32_t id = msg.body.motion.pointers[i].properties.id;
- idBits.markBit(id);
- idToIndex[id] = i;
- pointers[i].copyFrom(msg.body.motion.pointers[i].coords);
- }
- }
-
- void initializeFrom(const History& other) {
- eventTime = other.eventTime;
- idBits = other.idBits; // temporary copy
- for (size_t i = 0; i < other.idBits.count(); i++) {
- uint32_t id = idBits.clearFirstMarkedBit();
- int32_t index = other.idToIndex[id];
- idToIndex[id] = index;
- pointers[index].copyFrom(other.pointers[index]);
- }
- idBits = other.idBits; // final copy
- }
-
- const PointerCoords& getPointerById(uint32_t id) const {
- return pointers[idToIndex[id]];
- }
-
- bool hasPointerId(uint32_t id) const {
- return idBits.hasBit(id);
- }
- };
- struct TouchState {
- int32_t deviceId;
- int32_t source;
- size_t historyCurrent;
- size_t historySize;
- History history[2];
- History lastResample;
-
- void initialize(int32_t deviceId, int32_t source) {
- this->deviceId = deviceId;
- this->source = source;
- historyCurrent = 0;
- historySize = 0;
- lastResample.eventTime = 0;
- lastResample.idBits.clear();
- }
-
- void addHistory(const InputMessage& msg) {
- historyCurrent ^= 1;
- if (historySize < 2) {
- historySize += 1;
- }
- history[historyCurrent].initializeFrom(msg);
- }
-
- const History* getHistory(size_t index) const {
- return &history[(historyCurrent + index) & 1];
- }
-
- bool recentCoordinatesAreIdentical(uint32_t id) const {
- // Return true if the two most recently received "raw" coordinates are identical
- if (historySize < 2) {
- return false;
- }
- if (!getHistory(0)->hasPointerId(id) || !getHistory(1)->hasPointerId(id)) {
- return false;
- }
- float currentX = getHistory(0)->getPointerById(id).getX();
- float currentY = getHistory(0)->getPointerById(id).getY();
- float previousX = getHistory(1)->getPointerById(id).getX();
- float previousY = getHistory(1)->getPointerById(id).getY();
- if (currentX == previousX && currentY == previousY) {
- return true;
- }
- return false;
- }
- };
- std::vector<TouchState> mTouchStates;
-
- // Chain of batched sequence numbers. When multiple input messages are combined into
- // a batch, we append a record here that associates the last sequence number in the
- // batch with the previous one. When the finished signal is sent, we traverse the
- // chain to individually finish all input messages that were part of the batch.
- struct SeqChain {
- uint32_t seq; // sequence number of batched input message
- uint32_t chain; // sequence number of previous batched input message
- };
- std::vector<SeqChain> mSeqChains;
-
- // The time at which each event with the sequence number 'seq' was consumed.
- // This data is provided in 'finishInputEvent' so that the receiving end can measure the latency
- // This collection is populated when the event is received, and the entries are erased when the
- // events are finished. It should not grow infinitely because if an event is not ack'd, ANR
- // will be raised for that connection, and no further events will be posted to that channel.
- std::unordered_map<uint32_t /*seq*/, nsecs_t /*consumeTime*/> mConsumeTimes;
-
- status_t consumeBatch(InputEventFactoryInterface* factory,
- nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent);
- status_t consumeSamples(InputEventFactoryInterface* factory,
- Batch& batch, size_t count, uint32_t* outSeq, InputEvent** outEvent);
-
- void updateTouchState(InputMessage& msg);
- void resampleTouchState(nsecs_t frameTime, MotionEvent* event,
- const InputMessage *next);
-
- ssize_t findBatch(int32_t deviceId, int32_t source) const;
- ssize_t findTouchState(int32_t deviceId, int32_t source) const;
-
- nsecs_t getConsumeTime(uint32_t seq) const;
- void popConsumeTime(uint32_t seq);
- status_t sendUnchainedFinishedSignal(uint32_t seq, bool handled);
-
- static void rewriteMessage(TouchState& state, InputMessage& msg);
- static void initializeKeyEvent(KeyEvent* event, const InputMessage* msg);
- static void initializeMotionEvent(MotionEvent* event, const InputMessage* msg);
- static void initializeFocusEvent(FocusEvent* event, const InputMessage* msg);
- static void initializeCaptureEvent(CaptureEvent* event, const InputMessage* msg);
- static void initializeDragEvent(DragEvent* event, const InputMessage* msg);
- static void initializeTouchModeEvent(TouchModeEvent* event, const InputMessage* msg);
- static void addSample(MotionEvent* event, const InputMessage* msg);
- static bool canAddSample(const Batch& batch, const InputMessage* msg);
- static ssize_t findSampleNoLaterThan(const Batch& batch, nsecs_t time);
-
- static bool isTouchResamplingEnabled();
-};
-
} // namespace android
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index 84ff9d7..ca9b08f 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -159,6 +159,11 @@
"UtilsHost.cpp",
],
},
+ android: {
+ lto: {
+ thin: true,
+ },
+ },
},
aidl: {
@@ -219,9 +224,6 @@
"-performance-move-const-arg", // b/273486801
"portability*",
],
- lto: {
- thin: true,
- },
}
cc_library_headers {
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index 35cea81..1ffdad5 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -2980,6 +2980,7 @@
uint8_t* data = reallocZeroFree(mData, mDataCapacity, desired, mDeallocZero);
if (!data && desired > mDataCapacity) {
+ LOG_ALWAYS_FATAL("out of memory");
mError = NO_MEMORY;
return NO_MEMORY;
}
diff --git a/libs/binder/ndk/Android.bp b/libs/binder/ndk/Android.bp
index ccf3ce8..30dbddd 100644
--- a/libs/binder/ndk/Android.bp
+++ b/libs/binder/ndk/Android.bp
@@ -40,6 +40,7 @@
llndk: {
symbol_file: "libbinder_ndk.map.txt",
+ export_llndk_headers: ["libvendorsupport_llndk_headers"],
},
export_include_dirs: [
@@ -79,9 +80,11 @@
],
header_libs: [
+ "libvendorsupport_llndk_headers",
"jni_headers",
],
export_header_lib_headers: [
+ "libvendorsupport_llndk_headers",
"jni_headers",
],
diff --git a/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h b/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h
index 864ff50..3aacbe9 100644
--- a/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h
+++ b/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h
@@ -24,6 +24,13 @@
namespace aidl::android::os {
+#if defined(__ANDROID_VENDOR__)
+#define AT_LEAST_V_OR_202404 constexpr(__ANDROID_VENDOR_API__ >= 202404)
+#else
+// TODO(b/322384429) switch this to __ANDROID_API_V__ when V is finalized
+#define AT_LEAST_V_OR_202404 (__builtin_available(android __ANDROID_API_FUTURE__, *))
+#endif
+
/**
* Wrapper class that enables interop with AIDL NDK generation
* Takes ownership of the APersistableBundle* given to it in reset() and will automatically
@@ -32,7 +39,7 @@
class PersistableBundle {
public:
PersistableBundle() noexcept {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
mPBundle = APersistableBundle_new();
}
}
@@ -42,13 +49,13 @@
PersistableBundle(PersistableBundle&& other) noexcept : mPBundle(other.release()) {}
// duplicates, does not take ownership of the APersistableBundle*
PersistableBundle(const PersistableBundle& other) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
mPBundle = APersistableBundle_dup(other.mPBundle);
}
}
// duplicates, does not take ownership of the APersistableBundle*
PersistableBundle& operator=(const PersistableBundle& other) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
mPBundle = APersistableBundle_dup(other.mPBundle);
}
return *this;
@@ -58,7 +65,7 @@
binder_status_t readFromParcel(const AParcel* _Nonnull parcel) {
reset();
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
return APersistableBundle_readFromParcel(parcel, &mPBundle);
} else {
return STATUS_INVALID_OPERATION;
@@ -69,7 +76,7 @@
if (!mPBundle) {
return STATUS_BAD_VALUE;
}
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
return APersistableBundle_writeToParcel(mPBundle, parcel);
} else {
return STATUS_INVALID_OPERATION;
@@ -84,7 +91,7 @@
*/
void reset(APersistableBundle* _Nullable pBundle = nullptr) noexcept {
if (mPBundle) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
APersistableBundle_delete(mPBundle);
}
mPBundle = nullptr;
@@ -97,7 +104,7 @@
* what should be used to check for equality.
*/
bool deepEquals(const PersistableBundle& rhs) const {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
return APersistableBundle_isEqual(get(), rhs.get());
} else {
return false;
@@ -136,7 +143,7 @@
inline std::string toString() const {
if (!mPBundle) {
return "<PersistableBundle: null>";
- } else if (__builtin_available(android __ANDROID_API_V__, *)) {
+ } else if AT_LEAST_V_OR_202404 {
std::ostringstream os;
os << "<PersistableBundle: ";
os << "size: " << std::to_string(APersistableBundle_size(mPBundle));
@@ -147,7 +154,7 @@
}
int32_t size() const {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
return APersistableBundle_size(mPBundle);
} else {
return 0;
@@ -155,7 +162,7 @@
}
int32_t erase(const std::string& key) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
return APersistableBundle_erase(mPBundle, key.c_str());
} else {
return 0;
@@ -163,37 +170,37 @@
}
void putBoolean(const std::string& key, bool val) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
APersistableBundle_putBoolean(mPBundle, key.c_str(), val);
}
}
void putInt(const std::string& key, int32_t val) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
APersistableBundle_putInt(mPBundle, key.c_str(), val);
}
}
void putLong(const std::string& key, int64_t val) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
APersistableBundle_putLong(mPBundle, key.c_str(), val);
}
}
void putDouble(const std::string& key, double val) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
APersistableBundle_putDouble(mPBundle, key.c_str(), val);
}
}
void putString(const std::string& key, const std::string& val) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
APersistableBundle_putString(mPBundle, key.c_str(), val.c_str());
}
}
void putBooleanVector(const std::string& key, const std::vector<bool>& vec) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
// std::vector<bool> has no ::data().
int32_t num = vec.size();
if (num > 0) {
@@ -210,7 +217,7 @@
}
void putIntVector(const std::string& key, const std::vector<int32_t>& vec) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
int32_t num = vec.size();
if (num > 0) {
APersistableBundle_putIntVector(mPBundle, key.c_str(), vec.data(), num);
@@ -218,7 +225,7 @@
}
}
void putLongVector(const std::string& key, const std::vector<int64_t>& vec) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
int32_t num = vec.size();
if (num > 0) {
APersistableBundle_putLongVector(mPBundle, key.c_str(), vec.data(), num);
@@ -226,7 +233,7 @@
}
}
void putDoubleVector(const std::string& key, const std::vector<double>& vec) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
int32_t num = vec.size();
if (num > 0) {
APersistableBundle_putDoubleVector(mPBundle, key.c_str(), vec.data(), num);
@@ -234,7 +241,7 @@
}
}
void putStringVector(const std::string& key, const std::vector<std::string>& vec) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
int32_t num = vec.size();
if (num > 0) {
char** inVec = (char**)malloc(num * sizeof(char*));
@@ -249,13 +256,13 @@
}
}
void putPersistableBundle(const std::string& key, const PersistableBundle& pBundle) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
APersistableBundle_putPersistableBundle(mPBundle, key.c_str(), pBundle.mPBundle);
}
}
bool getBoolean(const std::string& key, bool* _Nonnull val) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
return APersistableBundle_getBoolean(mPBundle, key.c_str(), val);
} else {
return false;
@@ -263,7 +270,7 @@
}
bool getInt(const std::string& key, int32_t* _Nonnull val) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
return APersistableBundle_getInt(mPBundle, key.c_str(), val);
} else {
return false;
@@ -271,7 +278,7 @@
}
bool getLong(const std::string& key, int64_t* _Nonnull val) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
return APersistableBundle_getLong(mPBundle, key.c_str(), val);
} else {
return false;
@@ -279,7 +286,7 @@
}
bool getDouble(const std::string& key, double* _Nonnull val) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
return APersistableBundle_getDouble(mPBundle, key.c_str(), val);
} else {
return false;
@@ -291,7 +298,7 @@
}
bool getString(const std::string& key, std::string* _Nonnull val) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
char* outString = nullptr;
bool ret = APersistableBundle_getString(mPBundle, key.c_str(), &outString,
&stringAllocator, nullptr);
@@ -309,7 +316,7 @@
const char* _Nonnull, T* _Nullable, int32_t),
const APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
std::vector<T>* _Nonnull vec) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
int32_t bytes = 0;
// call first with nullptr to get required size in bytes
bytes = getVec(pBundle, key, nullptr, 0);
@@ -331,28 +338,28 @@
}
bool getBooleanVector(const std::string& key, std::vector<bool>* _Nonnull vec) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
return getVecInternal<bool>(&APersistableBundle_getBooleanVector, mPBundle, key.c_str(),
vec);
}
return false;
}
bool getIntVector(const std::string& key, std::vector<int32_t>* _Nonnull vec) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
return getVecInternal<int32_t>(&APersistableBundle_getIntVector, mPBundle, key.c_str(),
vec);
}
return false;
}
bool getLongVector(const std::string& key, std::vector<int64_t>* _Nonnull vec) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
return getVecInternal<int64_t>(&APersistableBundle_getLongVector, mPBundle, key.c_str(),
vec);
}
return false;
}
bool getDoubleVector(const std::string& key, std::vector<double>* _Nonnull vec) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
return getVecInternal<double>(&APersistableBundle_getDoubleVector, mPBundle,
key.c_str(), vec);
}
@@ -377,7 +384,7 @@
}
bool getStringVector(const std::string& key, std::vector<std::string>* _Nonnull vec) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
int32_t bytes = APersistableBundle_getStringVector(mPBundle, key.c_str(), nullptr, 0,
&stringAllocator, nullptr);
if (bytes > 0) {
@@ -394,7 +401,7 @@
}
bool getPersistableBundle(const std::string& key, PersistableBundle* _Nonnull val) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
APersistableBundle* bundle = nullptr;
bool ret = APersistableBundle_getPersistableBundle(mPBundle, key.c_str(), &bundle);
if (ret) {
@@ -426,77 +433,77 @@
}
std::set<std::string> getBooleanKeys() {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
return getKeys(&APersistableBundle_getBooleanKeys, mPBundle);
} else {
return {};
}
}
std::set<std::string> getIntKeys() {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
return getKeys(&APersistableBundle_getIntKeys, mPBundle);
} else {
return {};
}
}
std::set<std::string> getLongKeys() {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
return getKeys(&APersistableBundle_getLongKeys, mPBundle);
} else {
return {};
}
}
std::set<std::string> getDoubleKeys() {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
return getKeys(&APersistableBundle_getDoubleKeys, mPBundle);
} else {
return {};
}
}
std::set<std::string> getStringKeys() {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
return getKeys(&APersistableBundle_getStringKeys, mPBundle);
} else {
return {};
}
}
std::set<std::string> getBooleanVectorKeys() {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
return getKeys(&APersistableBundle_getBooleanVectorKeys, mPBundle);
} else {
return {};
}
}
std::set<std::string> getIntVectorKeys() {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
return getKeys(&APersistableBundle_getIntVectorKeys, mPBundle);
} else {
return {};
}
}
std::set<std::string> getLongVectorKeys() {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
return getKeys(&APersistableBundle_getLongVectorKeys, mPBundle);
} else {
return {};
}
}
std::set<std::string> getDoubleVectorKeys() {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
return getKeys(&APersistableBundle_getDoubleVectorKeys, mPBundle);
} else {
return {};
}
}
std::set<std::string> getStringVectorKeys() {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
return getKeys(&APersistableBundle_getStringVectorKeys, mPBundle);
} else {
return {};
}
}
std::set<std::string> getPersistableBundleKeys() {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if AT_LEAST_V_OR_202404 {
return getKeys(&APersistableBundle_getPersistableBundleKeys, mPBundle);
} else {
return {};
diff --git a/libs/binder/ndk/include_ndk/android/persistable_bundle.h b/libs/binder/ndk/include_ndk/android/persistable_bundle.h
index 98c0cb2..1247e8e 100644
--- a/libs/binder/ndk/include_ndk/android/persistable_bundle.h
+++ b/libs/binder/ndk/include_ndk/android/persistable_bundle.h
@@ -17,6 +17,11 @@
#pragma once
#include <android/binder_parcel.h>
+#if defined(__ANDROID_VENDOR__)
+#include <android/llndk-versioning.h>
+#else
+#define __INTRODUCED_IN_LLNDK(x)
+#endif
#include <stdbool.h>
#include <stdint.h>
#include <sys/cdefs.h>
@@ -67,25 +72,26 @@
/**
* Create a new APersistableBundle.
*
- * Available since API level __ANDROID_API_V__.
+ * Available since API level 202404.
*
* \return Pointer to a new APersistableBundle
*/
-APersistableBundle* _Nullable APersistableBundle_new() __INTRODUCED_IN(__ANDROID_API_V__);
+APersistableBundle* _Nullable APersistableBundle_new() __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
/**
* Create a new APersistableBundle based off an existing APersistableBundle.
* This is a deep copy, so the new APersistableBundle has its own values from
* copying the original underlying PersistableBundle.
*
- * Available since API level __ANDROID_API_V__.
+ * Available since API level 202404.
*
* \param pBundle to duplicate
*
* \return Pointer to a new APersistableBundle
*/
APersistableBundle* _Nullable APersistableBundle_dup(const APersistableBundle* _Nonnull pBundle)
- __INTRODUCED_IN(__ANDROID_API_V__);
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
/**
* Delete an APersistableBundle. This must always be called when finished using
@@ -93,15 +99,15 @@
*
* \param pBundle to delete. No-op if null.
*
- * Available since API level __ANDROID_API_V__.
+ * Available since API level 202404.
*/
void APersistableBundle_delete(APersistableBundle* _Nullable pBundle)
- __INTRODUCED_IN(__ANDROID_API_V__);
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
/**
* Check for equality of APersistableBundles.
*
- * Available since API level __ANDROID_API_V__.
+ * Available since API level 202404.
*
* \param lhs bundle to compare against the other param
* \param rhs bundle to compare against the other param
@@ -110,12 +116,12 @@
*/
bool APersistableBundle_isEqual(const APersistableBundle* _Nonnull lhs,
const APersistableBundle* _Nonnull rhs)
- __INTRODUCED_IN(__ANDROID_API_V__);
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
/**
* Read an APersistableBundle from an AParcel.
*
- * Available since API level __ANDROID_API_V__.
+ * Available since API level 202404.
*
* \param parcel to read from
* \param outPBundle bundle to write to
@@ -129,12 +135,12 @@
*/
binder_status_t APersistableBundle_readFromParcel(
const AParcel* _Nonnull parcel, APersistableBundle* _Nullable* _Nonnull outPBundle)
- __INTRODUCED_IN(__ANDROID_API_V__);
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
/**
* Write an APersistableBundle to an AParcel.
*
- * Available since API level __ANDROID_API_V__.
+ * Available since API level 202404.
*
* \param pBundle bundle to write to the parcel
* \param parcel to write to
@@ -149,25 +155,25 @@
*/
binder_status_t APersistableBundle_writeToParcel(const APersistableBundle* _Nonnull pBundle,
AParcel* _Nonnull parcel)
- __INTRODUCED_IN(__ANDROID_API_V__);
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
/**
* Get the size of an APersistableBundle. This is the number of mappings in the
* object.
*
- * Available since API level __ANDROID_API_V__.
+ * Available since API level 202404.
*
* \param pBundle to get the size of (number of mappings)
*
* \return number of mappings in the object
*/
int32_t APersistableBundle_size(const APersistableBundle* _Nonnull pBundle)
- __INTRODUCED_IN(__ANDROID_API_V__);
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
/**
* Erase any entries added with the provided key.
*
- * Available since API level __ANDROID_API_V__.
+ * Available since API level 202404.
*
* \param pBundle to operate on
* \param key for the mapping in UTF-8 to erase
@@ -175,7 +181,7 @@
* \return number of entries erased. Either 0 or 1.
*/
int32_t APersistableBundle_erase(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key)
- __INTRODUCED_IN(__ANDROID_API_V__);
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
/**
* Put a boolean associated with the provided key.
@@ -185,10 +191,11 @@
* \param key for the mapping in UTF-8
* \param value to put for the mapping
*
- * Available since API level __ANDROID_API_V__.
+ * Available since API level 202404.
*/
void APersistableBundle_putBoolean(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
- bool val) __INTRODUCED_IN(__ANDROID_API_V__);
+ bool val) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
/**
* Put an int32_t associated with the provided key.
@@ -198,10 +205,11 @@
* \param key for the mapping in UTF-8
* \param val value to put for the mapping
*
- * Available since API level __ANDROID_API_V__.
+ * Available since API level 202404.
*/
void APersistableBundle_putInt(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
- int32_t val) __INTRODUCED_IN(__ANDROID_API_V__);
+ int32_t val) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
/**
* Put an int64_t associated with the provided key.
@@ -211,10 +219,11 @@
* \param key for the mapping in UTF-8
* \param val value to put for the mapping
*
- * Available since API level __ANDROID_API_V__.
+ * Available since API level 202404.
*/
void APersistableBundle_putLong(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
- int64_t val) __INTRODUCED_IN(__ANDROID_API_V__);
+ int64_t val) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
/**
* Put a double associated with the provided key.
@@ -224,10 +233,11 @@
* \param key for the mapping in UTF-8
* \param val value to put for the mapping
*
- * Available since API level __ANDROID_API_V__.
+ * Available since API level 202404.
*/
void APersistableBundle_putDouble(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
- double val) __INTRODUCED_IN(__ANDROID_API_V__);
+ double val) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
/**
* Put a string associated with the provided key.
@@ -238,10 +248,11 @@
* \param key for the mapping in UTF-8
* \param vec vector to put for the mapping
*
- * Available since API level __ANDROID_API_V__.
+ * Available since API level 202404.
*/
void APersistableBundle_putString(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
- const char* _Nonnull val) __INTRODUCED_IN(__ANDROID_API_V__);
+ const char* _Nonnull val) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
/**
* Put a boolean vector associated with the provided key.
@@ -253,11 +264,12 @@
* \param vec vector to put for the mapping
* \param num number of elements in the vector
*
- * Available since API level __ANDROID_API_V__.
+ * Available since API level 202404.
*/
void APersistableBundle_putBooleanVector(APersistableBundle* _Nonnull pBundle,
const char* _Nonnull key, const bool* _Nonnull vec,
- int32_t num) __INTRODUCED_IN(__ANDROID_API_V__);
+ int32_t num) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
/**
* Put an int32_t vector associated with the provided key.
@@ -269,11 +281,11 @@
* \param vec vector to put for the mapping
* \param num number of elements in the vector
*
- * Available since API level __ANDROID_API_V__.
+ * Available since API level 202404.
*/
void APersistableBundle_putIntVector(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
const int32_t* _Nonnull vec, int32_t num)
- __INTRODUCED_IN(__ANDROID_API_V__);
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
/**
* Put an int64_t vector associated with the provided key.
@@ -285,11 +297,12 @@
* \param vec vector to put for the mapping
* \param num number of elements in the vector
*
- * Available since API level __ANDROID_API_V__.
+ * Available since API level 202404.
*/
void APersistableBundle_putLongVector(APersistableBundle* _Nonnull pBundle,
const char* _Nonnull key, const int64_t* _Nonnull vec,
- int32_t num) __INTRODUCED_IN(__ANDROID_API_V__);
+ int32_t num) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
/**
* Put a double vector associated with the provided key.
@@ -301,11 +314,12 @@
* \param vec vector to put for the mapping
* \param num number of elements in the vector
*
- * Available since API level __ANDROID_API_V__.
+ * Available since API level 202404.
*/
void APersistableBundle_putDoubleVector(APersistableBundle* _Nonnull pBundle,
const char* _Nonnull key, const double* _Nonnull vec,
- int32_t num) __INTRODUCED_IN(__ANDROID_API_V__);
+ int32_t num) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
/**
* Put a string vector associated with the provided key.
@@ -317,12 +331,12 @@
* \param vec vector to put for the mapping
* \param num number of elements in the vector
*
- * Available since API level __ANDROID_API_V__.
+ * Available since API level 202404.
*/
void APersistableBundle_putStringVector(APersistableBundle* _Nonnull pBundle,
const char* _Nonnull key,
const char* _Nullable const* _Nullable vec, int32_t num)
- __INTRODUCED_IN(__ANDROID_API_V__);
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
/**
* Put an APersistableBundle associated with the provided key.
@@ -333,17 +347,17 @@
* \param key for the mapping in UTF-8
* \param val value to put for the mapping
*
- * Available since API level __ANDROID_API_V__.
+ * Available since API level 202404.
*/
void APersistableBundle_putPersistableBundle(APersistableBundle* _Nonnull pBundle,
const char* _Nonnull key,
const APersistableBundle* _Nonnull val)
- __INTRODUCED_IN(__ANDROID_API_V__);
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
/**
* Get a boolean associated with the provided key.
*
- * Available since API level __ANDROID_API_V__.
+ * Available since API level 202404.
*
* \param pBundle to operate on
* \param key for the mapping in UTF-8
@@ -353,12 +367,12 @@
*/
bool APersistableBundle_getBoolean(const APersistableBundle* _Nonnull pBundle,
const char* _Nonnull key, bool* _Nonnull val)
- __INTRODUCED_IN(__ANDROID_API_V__);
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
/**
* Get an int32_t associated with the provided key.
*
- * Available since API level __ANDROID_API_V__.
+ * Available since API level 202404.
*
* \param pBundle to operate on
* \param key for the mapping in UTF-8
@@ -367,12 +381,13 @@
* \return true if a value exists for the provided key
*/
bool APersistableBundle_getInt(const APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
- int32_t* _Nonnull val) __INTRODUCED_IN(__ANDROID_API_V__);
+ int32_t* _Nonnull val) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
/**
* Get an int64_t associated with the provided key.
*
- * Available since API level __ANDROID_API_V__.
+ * Available since API level 202404.
*
* \param pBundle to operate on
* \param key for the mapping in UTF-8
@@ -382,12 +397,12 @@
*/
bool APersistableBundle_getLong(const APersistableBundle* _Nonnull pBundle,
const char* _Nonnull key, int64_t* _Nonnull val)
- __INTRODUCED_IN(__ANDROID_API_V__);
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
/**
* Get a double associated with the provided key.
*
- * Available since API level __ANDROID_API_V__.
+ * Available since API level 202404.
*
* \param pBundle to operate on
* \param key for the mapping in UTF-8
@@ -397,13 +412,13 @@
*/
bool APersistableBundle_getDouble(const APersistableBundle* _Nonnull pBundle,
const char* _Nonnull key, double* _Nonnull val)
- __INTRODUCED_IN(__ANDROID_API_V__);
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
/**
* Get a string associated with the provided key.
* The caller is responsible for freeing the returned data.
*
- * Available since API level __ANDROID_API_V__.
+ * Available since API level 202404.
*
* \param pBundle to operate on
* \param key for the mapping in UTF-8
@@ -418,7 +433,8 @@
int32_t APersistableBundle_getString(const APersistableBundle* _Nonnull pBundle,
const char* _Nonnull key, char* _Nullable* _Nonnull val,
APersistableBundle_stringAllocator stringAllocator,
- void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__);
+ void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
/**
* Get a boolean vector associated with the provided key and place it in the
@@ -445,7 +461,7 @@
int32_t APersistableBundle_getBooleanVector(const APersistableBundle* _Nonnull pBundle,
const char* _Nonnull key, bool* _Nullable buffer,
int32_t bufferSizeBytes)
- __INTRODUCED_IN(__ANDROID_API_V__);
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
/**
* Get an int32_t vector associated with the provided key and place it in the
@@ -471,7 +487,8 @@
*/
int32_t APersistableBundle_getIntVector(const APersistableBundle* _Nonnull pBundle,
const char* _Nonnull key, int32_t* _Nullable buffer,
- int32_t bufferSizeBytes) __INTRODUCED_IN(__ANDROID_API_V__);
+ int32_t bufferSizeBytes) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
/**
* Get an int64_t vector associated with the provided key and place it in the
@@ -497,8 +514,8 @@
*/
int32_t APersistableBundle_getLongVector(const APersistableBundle* _Nonnull pBundle,
const char* _Nonnull key, int64_t* _Nullable buffer,
- int32_t bufferSizeBytes)
- __INTRODUCED_IN(__ANDROID_API_V__);
+ int32_t bufferSizeBytes) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
/**
* Get a double vector associated with the provided key and place it in the
@@ -525,7 +542,7 @@
int32_t APersistableBundle_getDoubleVector(const APersistableBundle* _Nonnull pBundle,
const char* _Nonnull key, double* _Nullable buffer,
int32_t bufferSizeBytes)
- __INTRODUCED_IN(__ANDROID_API_V__);
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
/**
* Get a string vector associated with the provided key and place it in the
@@ -562,12 +579,12 @@
int32_t bufferSizeBytes,
APersistableBundle_stringAllocator stringAllocator,
void* _Nullable context)
- __INTRODUCED_IN(__ANDROID_API_V__);
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
/**
* Get an APersistableBundle* associated with the provided key.
*
- * Available since API level __ANDROID_API_V__.
+ * Available since API level 202404.
*
* \param pBundle to operate on
* \param key for the mapping in UTF-8
@@ -581,7 +598,7 @@
bool APersistableBundle_getPersistableBundle(const APersistableBundle* _Nonnull pBundle,
const char* _Nonnull key,
APersistableBundle* _Nullable* _Nonnull outBundle)
- __INTRODUCED_IN(__ANDROID_API_V__);
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
/**
* Get all of the keys associated with this specific type and place it in the
@@ -614,7 +631,7 @@
int32_t bufferSizeBytes,
APersistableBundle_stringAllocator stringAllocator,
void* _Nullable context)
- __INTRODUCED_IN(__ANDROID_API_V__);
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
/**
* Get all of the keys associated with this specific type and place it in the
@@ -645,7 +662,8 @@
int32_t APersistableBundle_getIntKeys(const APersistableBundle* _Nonnull pBundle,
char* _Nullable* _Nullable outKeys, int32_t bufferSizeBytes,
APersistableBundle_stringAllocator stringAllocator,
- void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__);
+ void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
/**
* Get all of the keys associated with this specific type and place it in the
@@ -676,7 +694,8 @@
int32_t APersistableBundle_getLongKeys(const APersistableBundle* _Nonnull pBundle,
char* _Nullable* _Nullable outKeys, int32_t bufferSizeBytes,
APersistableBundle_stringAllocator stringAllocator,
- void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__);
+ void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
/**
* Get all of the keys associated with this specific type and place it in the
@@ -708,8 +727,8 @@
char* _Nullable* _Nullable outKeys,
int32_t bufferSizeBytes,
APersistableBundle_stringAllocator stringAllocator,
- void* _Nullable context)
- __INTRODUCED_IN(__ANDROID_API_V__);
+ void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
/**
* Get all of the keys associated with this specific type and place it in the
@@ -741,8 +760,8 @@
char* _Nullable* _Nullable outKeys,
int32_t bufferSizeBytes,
APersistableBundle_stringAllocator stringAllocator,
- void* _Nullable context)
- __INTRODUCED_IN(__ANDROID_API_V__);
+ void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
/**
* Get all of the keys associated with this specific type and place it in the
@@ -775,7 +794,7 @@
int32_t bufferSizeBytes,
APersistableBundle_stringAllocator stringAllocator,
void* _Nullable context)
- __INTRODUCED_IN(__ANDROID_API_V__);
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
/**
* Get all of the keys associated with this specific type and place it in the
@@ -808,7 +827,7 @@
int32_t bufferSizeBytes,
APersistableBundle_stringAllocator stringAllocator,
void* _Nullable context)
- __INTRODUCED_IN(__ANDROID_API_V__);
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
/**
* Get all of the keys associated with this specific type and place it in the
@@ -841,7 +860,7 @@
int32_t bufferSizeBytes,
APersistableBundle_stringAllocator stringAllocator,
void* _Nullable context)
- __INTRODUCED_IN(__ANDROID_API_V__);
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
/**
* Get all of the keys associated with this specific type and place it in the
@@ -873,7 +892,7 @@
int32_t bufferSizeBytes,
APersistableBundle_stringAllocator stringAllocator,
void* _Nullable context)
- __INTRODUCED_IN(__ANDROID_API_V__);
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
/**
* Get all of the keys associated with this specific type and place it in the
@@ -906,7 +925,7 @@
int32_t bufferSizeBytes,
APersistableBundle_stringAllocator stringAllocator,
void* _Nullable context)
- __INTRODUCED_IN(__ANDROID_API_V__);
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
/**
* Get all of the keys associated with this specific type and place it in the
@@ -937,6 +956,6 @@
int32_t APersistableBundle_getPersistableBundleKeys(
const APersistableBundle* _Nonnull pBundle, char* _Nullable* _Nullable outKeys,
int32_t bufferSizeBytes, APersistableBundle_stringAllocator stringAllocator,
- void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__);
+ void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
__END_DECLS
diff --git a/libs/binder/ndk/libbinder_ndk.map.txt b/libs/binder/ndk/libbinder_ndk.map.txt
index de624e4..826e199 100644
--- a/libs/binder/ndk/libbinder_ndk.map.txt
+++ b/libs/binder/ndk/libbinder_ndk.map.txt
@@ -164,47 +164,88 @@
LIBBINDER_NDK35 { # introduced=VanillaIceCream
global:
APersistableBundle_readFromParcel;
+ APersistableBundle_readFromParcel; # llndk=202404
APersistableBundle_writeToParcel;
+ APersistableBundle_writeToParcel; # llndk=202404
APersistableBundle_new;
+ APersistableBundle_new; # llndk=202404
APersistableBundle_dup;
+ APersistableBundle_dup; # llndk=202404
APersistableBundle_delete;
+ APersistableBundle_delete; # llndk=202404
APersistableBundle_isEqual;
+ APersistableBundle_isEqual; # llndk=202404
APersistableBundle_size;
+ APersistableBundle_size; # llndk=202404
APersistableBundle_erase;
+ APersistableBundle_erase; # llndk=202404
APersistableBundle_putBoolean;
+ APersistableBundle_putBoolean; # llndk=202404
APersistableBundle_putInt;
+ APersistableBundle_putInt; # llndk=202404
APersistableBundle_putLong;
+ APersistableBundle_putLong; # llndk=202404
APersistableBundle_putDouble;
+ APersistableBundle_putDouble; # llndk=202404
APersistableBundle_putString;
+ APersistableBundle_putString; # llndk=202404
APersistableBundle_putBooleanVector;
+ APersistableBundle_putBooleanVector; # llndk=202404
APersistableBundle_putIntVector;
+ APersistableBundle_putIntVector; # llndk=202404
APersistableBundle_putLongVector;
+ APersistableBundle_putLongVector; # llndk=202404
APersistableBundle_putDoubleVector;
+ APersistableBundle_putDoubleVector; # llndk=202404
APersistableBundle_putStringVector;
+ APersistableBundle_putStringVector; # llndk=202404
APersistableBundle_putPersistableBundle;
+ APersistableBundle_putPersistableBundle; # llndk=202404
APersistableBundle_getBoolean;
+ APersistableBundle_getBoolean; # llndk=202404
APersistableBundle_getInt;
+ APersistableBundle_getInt; # llndk=202404
APersistableBundle_getLong;
+ APersistableBundle_getLong; # llndk=202404
APersistableBundle_getDouble;
+ APersistableBundle_getDouble; # llndk=202404
APersistableBundle_getString;
+ APersistableBundle_getString; # llndk=202404
APersistableBundle_getBooleanVector;
+ APersistableBundle_getBooleanVector; # llndk=202404
APersistableBundle_getIntVector;
+ APersistableBundle_getIntVector; # llndk=202404
APersistableBundle_getLongVector;
+ APersistableBundle_getLongVector; # llndk=202404
APersistableBundle_getDoubleVector;
+ APersistableBundle_getDoubleVector; # llndk=202404
APersistableBundle_getStringVector;
+ APersistableBundle_getStringVector; # llndk=202404
APersistableBundle_getPersistableBundle;
+ APersistableBundle_getPersistableBundle; # llndk=202404
APersistableBundle_getBooleanKeys;
+ APersistableBundle_getBooleanKeys; # llndk=202404
APersistableBundle_getIntKeys;
+ APersistableBundle_getIntKeys; # llndk=202404
APersistableBundle_getLongKeys;
+ APersistableBundle_getLongKeys; # llndk=202404
APersistableBundle_getDoubleKeys;
+ APersistableBundle_getDoubleKeys; # llndk=202404
APersistableBundle_getStringKeys;
+ APersistableBundle_getStringKeys; # llndk=202404
APersistableBundle_getBooleanVectorKeys;
+ APersistableBundle_getBooleanVectorKeys; # llndk=202404
APersistableBundle_getIntVectorKeys;
+ APersistableBundle_getIntVectorKeys; # llndk=202404
APersistableBundle_getLongVectorKeys;
+ APersistableBundle_getLongVectorKeys; # llndk=202404
APersistableBundle_getDoubleVectorKeys;
+ APersistableBundle_getDoubleVectorKeys; # llndk=202404
APersistableBundle_getStringVectorKeys;
+ APersistableBundle_getStringVectorKeys; # llndk=202404
APersistableBundle_getPersistableBundleKeys;
- AServiceManager_openDeclaredPassthroughHal; # systemapi llndk
+ APersistableBundle_getPersistableBundleKeys; # llndk=202404
+ AServiceManager_openDeclaredPassthroughHal; # systemapi llndk=202404
};
LIBBINDER_NDK_PLATFORM {
diff --git a/libs/gui/Choreographer.cpp b/libs/gui/Choreographer.cpp
index 4518b67..0c8f3fa 100644
--- a/libs/gui/Choreographer.cpp
+++ b/libs/gui/Choreographer.cpp
@@ -143,9 +143,9 @@
void Choreographer::postFrameCallbackDelayed(AChoreographer_frameCallback cb,
AChoreographer_frameCallback64 cb64,
AChoreographer_vsyncCallback vsyncCallback, void* data,
- nsecs_t delay) {
+ nsecs_t delay, CallbackType callbackType) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
- FrameCallback callback{cb, cb64, vsyncCallback, data, now + delay};
+ FrameCallback callback{cb, cb64, vsyncCallback, data, now + delay, callbackType};
{
std::lock_guard<std::mutex> _l{mLock};
mFrameCallbacks.push(callback);
@@ -285,18 +285,8 @@
}
}
-void Choreographer::dispatchVsync(nsecs_t timestamp, PhysicalDisplayId, uint32_t,
- VsyncEventData vsyncEventData) {
- std::vector<FrameCallback> callbacks{};
- {
- std::lock_guard<std::mutex> _l{mLock};
- nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
- while (!mFrameCallbacks.empty() && mFrameCallbacks.top().dueTime < now) {
- callbacks.push_back(mFrameCallbacks.top());
- mFrameCallbacks.pop();
- }
- }
- mLastVsyncEventData = vsyncEventData;
+void Choreographer::dispatchCallbacks(const std::vector<FrameCallback>& callbacks,
+ VsyncEventData vsyncEventData, nsecs_t timestamp) {
for (const auto& cb : callbacks) {
if (cb.vsyncCallback != nullptr) {
ATRACE_FORMAT("AChoreographer_vsyncCallback %" PRId64,
@@ -319,6 +309,34 @@
}
}
+void Choreographer::dispatchVsync(nsecs_t timestamp, PhysicalDisplayId, uint32_t,
+ VsyncEventData vsyncEventData) {
+ std::vector<FrameCallback> animationCallbacks{};
+ std::vector<FrameCallback> inputCallbacks{};
+ {
+ std::lock_guard<std::mutex> _l{mLock};
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+ while (!mFrameCallbacks.empty() && mFrameCallbacks.top().dueTime < now) {
+ if (mFrameCallbacks.top().callbackType == CALLBACK_INPUT) {
+ inputCallbacks.push_back(mFrameCallbacks.top());
+ } else {
+ animationCallbacks.push_back(mFrameCallbacks.top());
+ }
+ mFrameCallbacks.pop();
+ }
+ }
+ mLastVsyncEventData = vsyncEventData;
+ // Callbacks with type CALLBACK_INPUT should always run first
+ {
+ ATRACE_FORMAT("CALLBACK_INPUT");
+ dispatchCallbacks(inputCallbacks, vsyncEventData, timestamp);
+ }
+ {
+ ATRACE_FORMAT("CALLBACK_ANIMATION");
+ dispatchCallbacks(animationCallbacks, vsyncEventData, timestamp);
+ }
+}
+
void Choreographer::dispatchHotplug(nsecs_t, PhysicalDisplayId displayId, bool connected) {
ALOGV("choreographer %p ~ received hotplug event (displayId=%s, connected=%s), ignoring.", this,
to_string(displayId).c_str(), toString(connected));
@@ -407,4 +425,8 @@
return iter->second;
}
+const sp<Looper> Choreographer::getLooper() {
+ return mLooper;
+}
+
} // namespace android
diff --git a/libs/gui/WindowInfo.cpp b/libs/gui/WindowInfo.cpp
index 86bf0ee..ad0d99d 100644
--- a/libs/gui/WindowInfo.cpp
+++ b/libs/gui/WindowInfo.cpp
@@ -73,14 +73,6 @@
touchableRegion.orSelf(region);
}
-bool WindowInfo::touchableRegionContainsPoint(int32_t x, int32_t y) const {
- return touchableRegion.contains(x, y);
-}
-
-bool WindowInfo::frameContainsPoint(int32_t x, int32_t y) const {
- return x >= frame.left && x < frame.right && y >= frame.top && y < frame.bottom;
-}
-
bool WindowInfo::supportsSplitTouch() const {
return !inputConfig.test(InputConfig::PREVENT_SPLITTING);
}
diff --git a/libs/gui/include/gui/Choreographer.h b/libs/gui/include/gui/Choreographer.h
index 55a7aa7..2e5aa4a 100644
--- a/libs/gui/include/gui/Choreographer.h
+++ b/libs/gui/include/gui/Choreographer.h
@@ -28,12 +28,18 @@
namespace android {
using gui::VsyncEventData;
+enum CallbackType : int8_t {
+ CALLBACK_INPUT,
+ CALLBACK_ANIMATION,
+};
+
struct FrameCallback {
AChoreographer_frameCallback callback;
AChoreographer_frameCallback64 callback64;
AChoreographer_vsyncCallback vsyncCallback;
void* data;
nsecs_t dueTime;
+ CallbackType callbackType;
inline bool operator<(const FrameCallback& rhs) const {
// Note that this is intentionally flipped because we want callbacks due sooner to be at
@@ -78,7 +84,7 @@
void postFrameCallbackDelayed(AChoreographer_frameCallback cb,
AChoreographer_frameCallback64 cb64,
AChoreographer_vsyncCallback vsyncCallback, void* data,
- nsecs_t delay);
+ nsecs_t delay, CallbackType callbackType);
void registerRefreshRateCallback(AChoreographer_refreshRateCallback cb, void* data)
EXCLUDES(gChoreographers.lock);
void unregisterRefreshRateCallback(AChoreographer_refreshRateCallback cb, void* data);
@@ -103,12 +109,15 @@
virtual ~Choreographer() override EXCLUDES(gChoreographers.lock);
int64_t getFrameInterval() const;
bool inCallback() const;
+ const sp<Looper> getLooper();
private:
Choreographer(const Choreographer&) = delete;
void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count,
VsyncEventData vsyncEventData) override;
+ void dispatchCallbacks(const std::vector<FrameCallback>&, VsyncEventData vsyncEventData,
+ nsecs_t timestamp);
void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override;
void dispatchHotplugConnectionError(nsecs_t timestamp, int32_t connectionError) override;
void dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId, int32_t modeId,
diff --git a/libs/gui/include/gui/InputTransferToken.h b/libs/gui/include/gui/InputTransferToken.h
new file mode 100644
index 0000000..6530b50
--- /dev/null
+++ b/libs/gui/include/gui/InputTransferToken.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <binder/Binder.h>
+#include <binder/IBinder.h>
+#include <binder/Parcel.h>
+#include <private/gui/ParcelUtils.h>
+#include <utils/Errors.h>
+
+namespace android {
+struct InputTransferToken : public RefBase, Parcelable {
+public:
+ InputTransferToken() { mToken = new BBinder(); }
+
+ InputTransferToken(const sp<IBinder>& token) { mToken = token; }
+
+ status_t writeToParcel(Parcel* parcel) const override {
+ SAFE_PARCEL(parcel->writeStrongBinder, mToken);
+ return NO_ERROR;
+ }
+
+ status_t readFromParcel(const Parcel* parcel) {
+ SAFE_PARCEL(parcel->readStrongBinder, &mToken);
+ return NO_ERROR;
+ };
+
+ sp<IBinder> mToken;
+};
+
+static inline bool operator==(const sp<InputTransferToken>& token1,
+ const sp<InputTransferToken>& token2) {
+ if (token1.get() == token2.get()) {
+ return true;
+ }
+ return token1->mToken == token2->mToken;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/gui/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h
index 32d60be..2d1b51a 100644
--- a/libs/gui/include/gui/WindowInfo.h
+++ b/libs/gui/include/gui/WindowInfo.h
@@ -254,10 +254,6 @@
void addTouchableRegion(const Rect& region);
- bool touchableRegionContainsPoint(int32_t x, int32_t y) const;
-
- bool frameContainsPoint(int32_t x, int32_t y) const;
-
bool supportsSplitTouch() const;
bool isSpy() const;
diff --git a/libs/gui/tests/Android.bp b/libs/gui/tests/Android.bp
index e606b99..0f16f71 100644
--- a/libs/gui/tests/Android.bp
+++ b/libs/gui/tests/Android.bp
@@ -30,6 +30,7 @@
"BLASTBufferQueue_test.cpp",
"BufferItemConsumer_test.cpp",
"BufferQueue_test.cpp",
+ "Choreographer_test.cpp",
"CompositorTiming_test.cpp",
"CpuConsumer_test.cpp",
"EndToEndNativeInputTest.cpp",
@@ -61,6 +62,7 @@
"libSurfaceFlingerProp",
"libGLESv1_CM",
"libinput",
+ "libnativedisplay",
],
static_libs: [
diff --git a/libs/gui/tests/Choreographer_test.cpp b/libs/gui/tests/Choreographer_test.cpp
new file mode 100644
index 0000000..2ac2550
--- /dev/null
+++ b/libs/gui/tests/Choreographer_test.cpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2024 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 "Choreographer_test"
+
+#include <android-base/stringprintf.h>
+#include <android/choreographer.h>
+#include <gtest/gtest.h>
+#include <gui/Choreographer.h>
+#include <utils/Looper.h>
+#include <chrono>
+#include <future>
+#include <string>
+
+namespace android {
+class ChoreographerTest : public ::testing::Test {};
+
+struct VsyncCallback {
+ std::atomic<bool> completePromise{false};
+ std::chrono::nanoseconds frameTime{0LL};
+ std::chrono::nanoseconds receivedCallbackTime{0LL};
+
+ void onVsyncCallback(const AChoreographerFrameCallbackData* callbackData) {
+ frameTime = std::chrono::nanoseconds{
+ AChoreographerFrameCallbackData_getFrameTimeNanos(callbackData)};
+ receivedCallbackTime = std::chrono::nanoseconds{systemTime(SYSTEM_TIME_MONOTONIC)};
+ completePromise.store(true);
+ }
+
+ bool callbackReceived() { return completePromise.load(); }
+};
+
+static void vsyncCallback(const AChoreographerFrameCallbackData* callbackData, void* data) {
+ VsyncCallback* cb = static_cast<VsyncCallback*>(data);
+ cb->onVsyncCallback(callbackData);
+}
+
+TEST_F(ChoreographerTest, InputCallbackBeforeAnimation) {
+ sp<Looper> looper = Looper::prepare(0);
+ Choreographer* choreographer = Choreographer::getForThread();
+ VsyncCallback animationCb;
+ VsyncCallback inputCb;
+
+ choreographer->postFrameCallbackDelayed(nullptr, nullptr, vsyncCallback, &animationCb, 0,
+ CALLBACK_ANIMATION);
+ choreographer->postFrameCallbackDelayed(nullptr, nullptr, vsyncCallback, &inputCb, 0,
+ CALLBACK_INPUT);
+
+ nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ nsecs_t currTime;
+ int pollResult;
+ do {
+ pollResult = looper->pollOnce(16);
+ currTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ } while (!(inputCb.callbackReceived() && animationCb.callbackReceived()) &&
+ (pollResult != Looper::POLL_TIMEOUT && pollResult != Looper::POLL_ERROR) &&
+ (currTime - startTime < 3000));
+
+ ASSERT_TRUE(inputCb.callbackReceived()) << "did not receive input callback";
+ ASSERT_TRUE(animationCb.callbackReceived()) << "did not receive animation callback";
+
+ ASSERT_EQ(inputCb.frameTime, animationCb.frameTime)
+ << android::base::StringPrintf("input and animation callback frame times don't match. "
+ "inputFrameTime=%lld animationFrameTime=%lld",
+ inputCb.frameTime.count(),
+ animationCb.frameTime.count());
+
+ ASSERT_LT(inputCb.receivedCallbackTime, animationCb.receivedCallbackTime)
+ << android::base::StringPrintf("input callback was not called first. "
+ "inputCallbackTime=%lld animationCallbackTime=%lld",
+ inputCb.frameTime.count(),
+ animationCb.frameTime.count());
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp
index a9d6e8d..9791212 100644
--- a/libs/gui/tests/EndToEndNativeInputTest.cpp
+++ b/libs/gui/tests/EndToEndNativeInputTest.cpp
@@ -41,6 +41,7 @@
#include <android/os/IInputFlinger.h>
#include <gui/WindowInfo.h>
#include <input/Input.h>
+#include <input/InputConsumer.h>
#include <input/InputTransport.h>
#include <ui/DisplayMode.h>
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 8b69339..3278c23 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -135,6 +135,29 @@
],
}
+cc_library_static {
+ name: "iinputflinger_aidl_lib_static",
+ host_supported: true,
+ srcs: [
+ "android/os/IInputFlinger.aidl",
+ "android/os/InputChannelCore.aidl",
+ ],
+ shared_libs: [
+ "libbinder",
+ ],
+ whole_static_libs: [
+ "libgui_window_info_static",
+ ],
+ aidl: {
+ export_aidl_headers: true,
+ local_include_dirs: ["."],
+ include_dirs: [
+ "frameworks/native/libs/gui",
+ "frameworks/native/libs/input",
+ ],
+ },
+}
+
// Contains methods to help access C++ code from rust
cc_library_static {
name: "libinput_from_rust_to_cpp",
@@ -154,6 +177,10 @@
],
generated_sources: ["libinput_cxx_bridge_code"],
+ lto: {
+ never: true,
+ },
+
shared_libs: [
"libbase",
],
@@ -175,10 +202,10 @@
"-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
],
srcs: [
- "android/os/IInputFlinger.aidl",
- "android/os/InputChannelCore.aidl",
"AccelerationCurve.cpp",
"Input.cpp",
+ "InputConsumer.cpp",
+ "InputConsumerNoResampling.cpp",
"InputDevice.cpp",
"InputEventLabels.cpp",
"InputTransport.cpp",
@@ -239,7 +266,6 @@
static_libs: [
"inputconstants-cpp",
- "libgui_window_info_static",
"libui-types",
"libtflite_static",
"libkernelconfigs",
@@ -248,10 +274,10 @@
whole_static_libs: [
"com.android.input.flags-aconfig-cc",
"libinput_rust_ffi",
+ "iinputflinger_aidl_lib_static",
],
export_static_lib_headers: [
- "libgui_window_info_static",
"libui-types",
],
@@ -285,14 +311,6 @@
],
},
},
-
- aidl: {
- local_include_dirs: ["."],
- export_aidl_headers: true,
- include_dirs: [
- "frameworks/native/libs/gui",
- ],
- },
}
// Use bootstrap version of stats logging library.
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index ff9d9a9..61a964e 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -1034,7 +1034,8 @@
(splitPointerProperties.size() * (historySize + 1)));
if (CC_UNLIKELY(splitPointerProperties.size() != splitCount)) {
- LOG(FATAL) << "Cannot split MotionEvent: Requested splitting " << splitCount
+ // TODO(b/329107108): Promote this to a fatal check once bugs in the caller are resolved.
+ LOG(ERROR) << "Cannot split MotionEvent: Requested splitting " << splitCount
<< " pointers from the original event, but the original event only contained "
<< splitPointerProperties.size() << " of those pointers.";
}
diff --git a/libs/input/InputConsumer.cpp b/libs/input/InputConsumer.cpp
new file mode 100644
index 0000000..e0d874e
--- /dev/null
+++ b/libs/input/InputConsumer.cpp
@@ -0,0 +1,939 @@
+/**
+ * Copyright 2024 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 "InputTransport"
+#define ATRACE_TAG ATRACE_TAG_INPUT
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <math.h>
+#include <poll.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <binder/Parcel.h>
+#include <cutils/properties.h>
+#include <ftl/enum.h>
+#include <log/log.h>
+#include <utils/Trace.h>
+
+#include <com_android_input_flags.h>
+#include <input/InputConsumer.h>
+#include <input/PrintTools.h>
+#include <input/TraceTools.h>
+
+namespace input_flags = com::android::input::flags;
+
+namespace android {
+
+namespace {
+
+/**
+ * Log debug messages relating to the consumer end of the transport channel.
+ * Enable this via "adb shell setprop log.tag.InputTransportConsumer DEBUG" (requires restart)
+ */
+
+const bool DEBUG_TRANSPORT_CONSUMER =
+ __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Consumer", ANDROID_LOG_INFO);
+
+const bool IS_DEBUGGABLE_BUILD =
+#if defined(__ANDROID__)
+ android::base::GetBoolProperty("ro.debuggable", false);
+#else
+ true;
+#endif
+
+/**
+ * Log debug messages about touch event resampling.
+ *
+ * Enable this via "adb shell setprop log.tag.InputTransportResampling DEBUG".
+ * This requires a restart on non-debuggable (e.g. user) builds, but should take effect immediately
+ * on debuggable builds (e.g. userdebug).
+ */
+bool debugResampling() {
+ if (!IS_DEBUGGABLE_BUILD) {
+ static const bool DEBUG_TRANSPORT_RESAMPLING =
+ __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling",
+ ANDROID_LOG_INFO);
+ return DEBUG_TRANSPORT_RESAMPLING;
+ }
+ return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling", ANDROID_LOG_INFO);
+}
+
+void initializeKeyEvent(KeyEvent& event, const InputMessage& msg) {
+ event.initialize(msg.body.key.eventId, msg.body.key.deviceId, msg.body.key.source,
+ msg.body.key.displayId, msg.body.key.hmac, msg.body.key.action,
+ msg.body.key.flags, msg.body.key.keyCode, msg.body.key.scanCode,
+ msg.body.key.metaState, msg.body.key.repeatCount, msg.body.key.downTime,
+ msg.body.key.eventTime);
+}
+
+void initializeFocusEvent(FocusEvent& event, const InputMessage& msg) {
+ event.initialize(msg.body.focus.eventId, msg.body.focus.hasFocus);
+}
+
+void initializeCaptureEvent(CaptureEvent& event, const InputMessage& msg) {
+ event.initialize(msg.body.capture.eventId, msg.body.capture.pointerCaptureEnabled);
+}
+
+void initializeDragEvent(DragEvent& event, const InputMessage& msg) {
+ event.initialize(msg.body.drag.eventId, msg.body.drag.x, msg.body.drag.y,
+ msg.body.drag.isExiting);
+}
+
+void initializeMotionEvent(MotionEvent& event, const InputMessage& msg) {
+ uint32_t pointerCount = msg.body.motion.pointerCount;
+ PointerProperties pointerProperties[pointerCount];
+ PointerCoords pointerCoords[pointerCount];
+ for (uint32_t i = 0; i < pointerCount; i++) {
+ pointerProperties[i] = msg.body.motion.pointers[i].properties;
+ pointerCoords[i] = msg.body.motion.pointers[i].coords;
+ }
+
+ ui::Transform transform;
+ transform.set({msg.body.motion.dsdx, msg.body.motion.dtdx, msg.body.motion.tx,
+ msg.body.motion.dtdy, msg.body.motion.dsdy, msg.body.motion.ty, 0, 0, 1});
+ ui::Transform displayTransform;
+ displayTransform.set({msg.body.motion.dsdxRaw, msg.body.motion.dtdxRaw, msg.body.motion.txRaw,
+ msg.body.motion.dtdyRaw, msg.body.motion.dsdyRaw, msg.body.motion.tyRaw,
+ 0, 0, 1});
+ event.initialize(msg.body.motion.eventId, msg.body.motion.deviceId, msg.body.motion.source,
+ msg.body.motion.displayId, msg.body.motion.hmac, msg.body.motion.action,
+ msg.body.motion.actionButton, msg.body.motion.flags, msg.body.motion.edgeFlags,
+ msg.body.motion.metaState, msg.body.motion.buttonState,
+ msg.body.motion.classification, transform, msg.body.motion.xPrecision,
+ msg.body.motion.yPrecision, msg.body.motion.xCursorPosition,
+ msg.body.motion.yCursorPosition, displayTransform, msg.body.motion.downTime,
+ msg.body.motion.eventTime, pointerCount, pointerProperties, pointerCoords);
+}
+
+void addSample(MotionEvent& event, const InputMessage& msg) {
+ uint32_t pointerCount = msg.body.motion.pointerCount;
+ PointerCoords pointerCoords[pointerCount];
+ for (uint32_t i = 0; i < pointerCount; i++) {
+ pointerCoords[i] = msg.body.motion.pointers[i].coords;
+ }
+
+ event.setMetaState(event.getMetaState() | msg.body.motion.metaState);
+ event.addSample(msg.body.motion.eventTime, pointerCoords);
+}
+
+void initializeTouchModeEvent(TouchModeEvent& event, const InputMessage& msg) {
+ event.initialize(msg.body.touchMode.eventId, msg.body.touchMode.isInTouchMode);
+}
+
+// Nanoseconds per milliseconds.
+constexpr nsecs_t NANOS_PER_MS = 1000000;
+
+// Latency added during resampling. A few milliseconds doesn't hurt much but
+// reduces the impact of mispredicted touch positions.
+const std::chrono::duration RESAMPLE_LATENCY = 5ms;
+
+// Minimum time difference between consecutive samples before attempting to resample.
+const nsecs_t RESAMPLE_MIN_DELTA = 2 * NANOS_PER_MS;
+
+// Maximum time difference between consecutive samples before attempting to resample
+// by extrapolation.
+const nsecs_t RESAMPLE_MAX_DELTA = 20 * NANOS_PER_MS;
+
+// Maximum time to predict forward from the last known state, to avoid predicting too
+// far into the future. This time is further bounded by 50% of the last time delta.
+const nsecs_t RESAMPLE_MAX_PREDICTION = 8 * NANOS_PER_MS;
+
+/**
+ * System property for enabling / disabling touch resampling.
+ * Resampling extrapolates / interpolates the reported touch event coordinates to better
+ * align them to the VSYNC signal, thus resulting in smoother scrolling performance.
+ * Resampling is not needed (and should be disabled) on hardware that already
+ * has touch events triggered by VSYNC.
+ * Set to "1" to enable resampling (default).
+ * Set to "0" to disable resampling.
+ * Resampling is enabled by default.
+ */
+const char* PROPERTY_RESAMPLING_ENABLED = "ro.input.resampling";
+
+inline float lerp(float a, float b, float alpha) {
+ return a + alpha * (b - a);
+}
+
+inline bool isPointerEvent(int32_t source) {
+ return (source & AINPUT_SOURCE_CLASS_POINTER) == AINPUT_SOURCE_CLASS_POINTER;
+}
+
+bool shouldResampleTool(ToolType toolType) {
+ return toolType == ToolType::FINGER || toolType == ToolType::UNKNOWN;
+}
+
+} // namespace
+
+using android::base::Result;
+using android::base::StringPrintf;
+
+// --- InputConsumer ---
+
+InputConsumer::InputConsumer(const std::shared_ptr<InputChannel>& channel)
+ : InputConsumer(channel, isTouchResamplingEnabled()) {}
+
+InputConsumer::InputConsumer(const std::shared_ptr<InputChannel>& channel,
+ bool enableTouchResampling)
+ : mResampleTouch(enableTouchResampling), mChannel(channel), mMsgDeferred(false) {}
+
+InputConsumer::~InputConsumer() {}
+
+bool InputConsumer::isTouchResamplingEnabled() {
+ return property_get_bool(PROPERTY_RESAMPLING_ENABLED, true);
+}
+
+status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consumeBatches,
+ nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
+ ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+ "channel '%s' consumer ~ consume: consumeBatches=%s, frameTime=%" PRId64,
+ mChannel->getName().c_str(), toString(consumeBatches), frameTime);
+
+ *outSeq = 0;
+ *outEvent = nullptr;
+
+ // Fetch the next input message.
+ // Loop until an event can be returned or no additional events are received.
+ while (!*outEvent) {
+ if (mMsgDeferred) {
+ // mMsg contains a valid input message from the previous call to consume
+ // that has not yet been processed.
+ mMsgDeferred = false;
+ } else {
+ // Receive a fresh message.
+ status_t result = mChannel->receiveMessage(&mMsg);
+ if (result == OK) {
+ const auto [_, inserted] =
+ mConsumeTimes.emplace(mMsg.header.seq, systemTime(SYSTEM_TIME_MONOTONIC));
+ LOG_ALWAYS_FATAL_IF(!inserted, "Already have a consume time for seq=%" PRIu32,
+ mMsg.header.seq);
+
+ // Trace the event processing timeline - event was just read from the socket
+ ATRACE_ASYNC_BEGIN("InputConsumer processing", /*cookie=*/mMsg.header.seq);
+ }
+ if (result) {
+ // Consume the next batched event unless batches are being held for later.
+ if (consumeBatches || result != WOULD_BLOCK) {
+ result = consumeBatch(factory, frameTime, outSeq, outEvent);
+ if (*outEvent) {
+ ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+ "channel '%s' consumer ~ consumed batch event, seq=%u",
+ mChannel->getName().c_str(), *outSeq);
+ break;
+ }
+ }
+ return result;
+ }
+ }
+
+ switch (mMsg.header.type) {
+ case InputMessage::Type::KEY: {
+ KeyEvent* keyEvent = factory->createKeyEvent();
+ if (!keyEvent) return NO_MEMORY;
+
+ initializeKeyEvent(*keyEvent, mMsg);
+ *outSeq = mMsg.header.seq;
+ *outEvent = keyEvent;
+ ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+ "channel '%s' consumer ~ consumed key event, seq=%u",
+ mChannel->getName().c_str(), *outSeq);
+ break;
+ }
+
+ case InputMessage::Type::MOTION: {
+ ssize_t batchIndex = findBatch(mMsg.body.motion.deviceId, mMsg.body.motion.source);
+ if (batchIndex >= 0) {
+ Batch& batch = mBatches[batchIndex];
+ if (canAddSample(batch, &mMsg)) {
+ batch.samples.push_back(mMsg);
+ ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+ "channel '%s' consumer ~ appended to batch event",
+ mChannel->getName().c_str());
+ break;
+ } else if (isPointerEvent(mMsg.body.motion.source) &&
+ mMsg.body.motion.action == AMOTION_EVENT_ACTION_CANCEL) {
+ // No need to process events that we are going to cancel anyways
+ const size_t count = batch.samples.size();
+ for (size_t i = 0; i < count; i++) {
+ const InputMessage& msg = batch.samples[i];
+ sendFinishedSignal(msg.header.seq, false);
+ }
+ batch.samples.erase(batch.samples.begin(), batch.samples.begin() + count);
+ mBatches.erase(mBatches.begin() + batchIndex);
+ } else {
+ // We cannot append to the batch in progress, so we need to consume
+ // the previous batch right now and defer the new message until later.
+ mMsgDeferred = true;
+ status_t result = consumeSamples(factory, batch, batch.samples.size(),
+ outSeq, outEvent);
+ mBatches.erase(mBatches.begin() + batchIndex);
+ if (result) {
+ return result;
+ }
+ ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+ "channel '%s' consumer ~ consumed batch event and "
+ "deferred current event, seq=%u",
+ mChannel->getName().c_str(), *outSeq);
+ break;
+ }
+ }
+
+ // Start a new batch if needed.
+ if (mMsg.body.motion.action == AMOTION_EVENT_ACTION_MOVE ||
+ mMsg.body.motion.action == AMOTION_EVENT_ACTION_HOVER_MOVE) {
+ Batch batch;
+ batch.samples.push_back(mMsg);
+ mBatches.push_back(batch);
+ ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+ "channel '%s' consumer ~ started batch event",
+ mChannel->getName().c_str());
+ break;
+ }
+
+ MotionEvent* motionEvent = factory->createMotionEvent();
+ if (!motionEvent) return NO_MEMORY;
+
+ updateTouchState(mMsg);
+ initializeMotionEvent(*motionEvent, mMsg);
+ *outSeq = mMsg.header.seq;
+ *outEvent = motionEvent;
+
+ ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+ "channel '%s' consumer ~ consumed motion event, seq=%u",
+ mChannel->getName().c_str(), *outSeq);
+ break;
+ }
+
+ case InputMessage::Type::FINISHED:
+ case InputMessage::Type::TIMELINE: {
+ LOG_ALWAYS_FATAL("Consumed a %s message, which should never be seen by "
+ "InputConsumer!",
+ ftl::enum_string(mMsg.header.type).c_str());
+ break;
+ }
+
+ case InputMessage::Type::FOCUS: {
+ FocusEvent* focusEvent = factory->createFocusEvent();
+ if (!focusEvent) return NO_MEMORY;
+
+ initializeFocusEvent(*focusEvent, mMsg);
+ *outSeq = mMsg.header.seq;
+ *outEvent = focusEvent;
+ break;
+ }
+
+ case InputMessage::Type::CAPTURE: {
+ CaptureEvent* captureEvent = factory->createCaptureEvent();
+ if (!captureEvent) return NO_MEMORY;
+
+ initializeCaptureEvent(*captureEvent, mMsg);
+ *outSeq = mMsg.header.seq;
+ *outEvent = captureEvent;
+ break;
+ }
+
+ case InputMessage::Type::DRAG: {
+ DragEvent* dragEvent = factory->createDragEvent();
+ if (!dragEvent) return NO_MEMORY;
+
+ initializeDragEvent(*dragEvent, mMsg);
+ *outSeq = mMsg.header.seq;
+ *outEvent = dragEvent;
+ break;
+ }
+
+ case InputMessage::Type::TOUCH_MODE: {
+ TouchModeEvent* touchModeEvent = factory->createTouchModeEvent();
+ if (!touchModeEvent) return NO_MEMORY;
+
+ initializeTouchModeEvent(*touchModeEvent, mMsg);
+ *outSeq = mMsg.header.seq;
+ *outEvent = touchModeEvent;
+ break;
+ }
+ }
+ }
+ return OK;
+}
+
+status_t InputConsumer::consumeBatch(InputEventFactoryInterface* factory, nsecs_t frameTime,
+ uint32_t* outSeq, InputEvent** outEvent) {
+ status_t result;
+ for (size_t i = mBatches.size(); i > 0;) {
+ i--;
+ Batch& batch = mBatches[i];
+ if (frameTime < 0) {
+ result = consumeSamples(factory, batch, batch.samples.size(), outSeq, outEvent);
+ mBatches.erase(mBatches.begin() + i);
+ return result;
+ }
+
+ nsecs_t sampleTime = frameTime;
+ if (mResampleTouch) {
+ sampleTime -= std::chrono::nanoseconds(RESAMPLE_LATENCY).count();
+ }
+ ssize_t split = findSampleNoLaterThan(batch, sampleTime);
+ if (split < 0) {
+ continue;
+ }
+
+ result = consumeSamples(factory, batch, split + 1, outSeq, outEvent);
+ const InputMessage* next;
+ if (batch.samples.empty()) {
+ mBatches.erase(mBatches.begin() + i);
+ next = nullptr;
+ } else {
+ next = &batch.samples[0];
+ }
+ if (!result && mResampleTouch) {
+ resampleTouchState(sampleTime, static_cast<MotionEvent*>(*outEvent), next);
+ }
+ return result;
+ }
+
+ return WOULD_BLOCK;
+}
+
+status_t InputConsumer::consumeSamples(InputEventFactoryInterface* factory, Batch& batch,
+ size_t count, uint32_t* outSeq, InputEvent** outEvent) {
+ MotionEvent* motionEvent = factory->createMotionEvent();
+ if (!motionEvent) return NO_MEMORY;
+
+ uint32_t chain = 0;
+ for (size_t i = 0; i < count; i++) {
+ InputMessage& msg = batch.samples[i];
+ updateTouchState(msg);
+ if (i) {
+ SeqChain seqChain;
+ seqChain.seq = msg.header.seq;
+ seqChain.chain = chain;
+ mSeqChains.push_back(seqChain);
+ addSample(*motionEvent, msg);
+ } else {
+ initializeMotionEvent(*motionEvent, msg);
+ }
+ chain = msg.header.seq;
+ }
+ batch.samples.erase(batch.samples.begin(), batch.samples.begin() + count);
+
+ *outSeq = chain;
+ *outEvent = motionEvent;
+ return OK;
+}
+
+void InputConsumer::updateTouchState(InputMessage& msg) {
+ if (!mResampleTouch || !isPointerEvent(msg.body.motion.source)) {
+ return;
+ }
+
+ int32_t deviceId = msg.body.motion.deviceId;
+ int32_t source = msg.body.motion.source;
+
+ // Update the touch state history to incorporate the new input message.
+ // If the message is in the past relative to the most recently produced resampled
+ // touch, then use the resampled time and coordinates instead.
+ switch (msg.body.motion.action & AMOTION_EVENT_ACTION_MASK) {
+ case AMOTION_EVENT_ACTION_DOWN: {
+ ssize_t index = findTouchState(deviceId, source);
+ if (index < 0) {
+ mTouchStates.push_back({});
+ index = mTouchStates.size() - 1;
+ }
+ TouchState& touchState = mTouchStates[index];
+ touchState.initialize(deviceId, source);
+ touchState.addHistory(msg);
+ break;
+ }
+
+ case AMOTION_EVENT_ACTION_MOVE: {
+ ssize_t index = findTouchState(deviceId, source);
+ if (index >= 0) {
+ TouchState& touchState = mTouchStates[index];
+ touchState.addHistory(msg);
+ rewriteMessage(touchState, msg);
+ }
+ break;
+ }
+
+ case AMOTION_EVENT_ACTION_POINTER_DOWN: {
+ ssize_t index = findTouchState(deviceId, source);
+ if (index >= 0) {
+ TouchState& touchState = mTouchStates[index];
+ touchState.lastResample.idBits.clearBit(msg.body.motion.getActionId());
+ rewriteMessage(touchState, msg);
+ }
+ break;
+ }
+
+ case AMOTION_EVENT_ACTION_POINTER_UP: {
+ ssize_t index = findTouchState(deviceId, source);
+ if (index >= 0) {
+ TouchState& touchState = mTouchStates[index];
+ rewriteMessage(touchState, msg);
+ touchState.lastResample.idBits.clearBit(msg.body.motion.getActionId());
+ }
+ break;
+ }
+
+ case AMOTION_EVENT_ACTION_SCROLL: {
+ ssize_t index = findTouchState(deviceId, source);
+ if (index >= 0) {
+ TouchState& touchState = mTouchStates[index];
+ rewriteMessage(touchState, msg);
+ }
+ break;
+ }
+
+ case AMOTION_EVENT_ACTION_UP:
+ case AMOTION_EVENT_ACTION_CANCEL: {
+ ssize_t index = findTouchState(deviceId, source);
+ if (index >= 0) {
+ TouchState& touchState = mTouchStates[index];
+ rewriteMessage(touchState, msg);
+ mTouchStates.erase(mTouchStates.begin() + index);
+ }
+ break;
+ }
+ }
+}
+
+/**
+ * Replace the coordinates in msg with the coordinates in lastResample, if necessary.
+ *
+ * If lastResample is no longer valid for a specific pointer (i.e. the lastResample time
+ * is in the past relative to msg and the past two events do not contain identical coordinates),
+ * then invalidate the lastResample data for that pointer.
+ * If the two past events have identical coordinates, then lastResample data for that pointer will
+ * remain valid, and will be used to replace these coordinates. Thus, if a certain coordinate x0 is
+ * resampled to the new value x1, then x1 will always be used to replace x0 until some new value
+ * not equal to x0 is received.
+ */
+void InputConsumer::rewriteMessage(TouchState& state, InputMessage& msg) {
+ nsecs_t eventTime = msg.body.motion.eventTime;
+ for (uint32_t i = 0; i < msg.body.motion.pointerCount; i++) {
+ uint32_t id = msg.body.motion.pointers[i].properties.id;
+ if (state.lastResample.idBits.hasBit(id)) {
+ if (eventTime < state.lastResample.eventTime ||
+ state.recentCoordinatesAreIdentical(id)) {
+ PointerCoords& msgCoords = msg.body.motion.pointers[i].coords;
+ const PointerCoords& resampleCoords = state.lastResample.getPointerById(id);
+ ALOGD_IF(debugResampling(), "[%d] - rewrite (%0.3f, %0.3f), old (%0.3f, %0.3f)", id,
+ resampleCoords.getX(), resampleCoords.getY(), msgCoords.getX(),
+ msgCoords.getY());
+ msgCoords.setAxisValue(AMOTION_EVENT_AXIS_X, resampleCoords.getX());
+ msgCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, resampleCoords.getY());
+ msgCoords.isResampled = true;
+ } else {
+ state.lastResample.idBits.clearBit(id);
+ }
+ }
+ }
+}
+
+void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event,
+ const InputMessage* next) {
+ if (!mResampleTouch || !(isPointerEvent(event->getSource())) ||
+ event->getAction() != AMOTION_EVENT_ACTION_MOVE) {
+ return;
+ }
+
+ ssize_t index = findTouchState(event->getDeviceId(), event->getSource());
+ if (index < 0) {
+ ALOGD_IF(debugResampling(), "Not resampled, no touch state for device.");
+ return;
+ }
+
+ TouchState& touchState = mTouchStates[index];
+ if (touchState.historySize < 1) {
+ ALOGD_IF(debugResampling(), "Not resampled, no history for device.");
+ return;
+ }
+
+ // Ensure that the current sample has all of the pointers that need to be reported.
+ const History* current = touchState.getHistory(0);
+ size_t pointerCount = event->getPointerCount();
+ for (size_t i = 0; i < pointerCount; i++) {
+ uint32_t id = event->getPointerId(i);
+ if (!current->idBits.hasBit(id)) {
+ ALOGD_IF(debugResampling(), "Not resampled, missing id %d", id);
+ return;
+ }
+ }
+
+ // Find the data to use for resampling.
+ const History* other;
+ History future;
+ float alpha;
+ if (next) {
+ // Interpolate between current sample and future sample.
+ // So current->eventTime <= sampleTime <= future.eventTime.
+ future.initializeFrom(*next);
+ other = &future;
+ nsecs_t delta = future.eventTime - current->eventTime;
+ if (delta < RESAMPLE_MIN_DELTA) {
+ ALOGD_IF(debugResampling(), "Not resampled, delta time is too small: %" PRId64 " ns.",
+ delta);
+ return;
+ }
+ alpha = float(sampleTime - current->eventTime) / delta;
+ } else if (touchState.historySize >= 2) {
+ // Extrapolate future sample using current sample and past sample.
+ // So other->eventTime <= current->eventTime <= sampleTime.
+ other = touchState.getHistory(1);
+ nsecs_t delta = current->eventTime - other->eventTime;
+ if (delta < RESAMPLE_MIN_DELTA) {
+ ALOGD_IF(debugResampling(), "Not resampled, delta time is too small: %" PRId64 " ns.",
+ delta);
+ return;
+ } else if (delta > RESAMPLE_MAX_DELTA) {
+ ALOGD_IF(debugResampling(), "Not resampled, delta time is too large: %" PRId64 " ns.",
+ delta);
+ return;
+ }
+ nsecs_t maxPredict = current->eventTime + std::min(delta / 2, RESAMPLE_MAX_PREDICTION);
+ if (sampleTime > maxPredict) {
+ ALOGD_IF(debugResampling(),
+ "Sample time is too far in the future, adjusting prediction "
+ "from %" PRId64 " to %" PRId64 " ns.",
+ sampleTime - current->eventTime, maxPredict - current->eventTime);
+ sampleTime = maxPredict;
+ }
+ alpha = float(current->eventTime - sampleTime) / delta;
+ } else {
+ ALOGD_IF(debugResampling(), "Not resampled, insufficient data.");
+ return;
+ }
+
+ if (current->eventTime == sampleTime) {
+ // Prevents having 2 events with identical times and coordinates.
+ return;
+ }
+
+ // Resample touch coordinates.
+ History oldLastResample;
+ oldLastResample.initializeFrom(touchState.lastResample);
+ touchState.lastResample.eventTime = sampleTime;
+ touchState.lastResample.idBits.clear();
+ for (size_t i = 0; i < pointerCount; i++) {
+ uint32_t id = event->getPointerId(i);
+ touchState.lastResample.idToIndex[id] = i;
+ touchState.lastResample.idBits.markBit(id);
+ if (oldLastResample.hasPointerId(id) && touchState.recentCoordinatesAreIdentical(id)) {
+ // We maintain the previously resampled value for this pointer (stored in
+ // oldLastResample) when the coordinates for this pointer haven't changed since then.
+ // This way we don't introduce artificial jitter when pointers haven't actually moved.
+ // The isResampled flag isn't cleared as the values don't reflect what the device is
+ // actually reporting.
+
+ // We know here that the coordinates for the pointer haven't changed because we
+ // would've cleared the resampled bit in rewriteMessage if they had. We can't modify
+ // lastResample in place because the mapping from pointer ID to index may have changed.
+ touchState.lastResample.pointers[i] = oldLastResample.getPointerById(id);
+ continue;
+ }
+
+ PointerCoords& resampledCoords = touchState.lastResample.pointers[i];
+ const PointerCoords& currentCoords = current->getPointerById(id);
+ resampledCoords = currentCoords;
+ resampledCoords.isResampled = true;
+ if (other->idBits.hasBit(id) && shouldResampleTool(event->getToolType(i))) {
+ const PointerCoords& otherCoords = other->getPointerById(id);
+ resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_X,
+ lerp(currentCoords.getX(), otherCoords.getX(), alpha));
+ resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y,
+ lerp(currentCoords.getY(), otherCoords.getY(), alpha));
+ ALOGD_IF(debugResampling(),
+ "[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f), "
+ "other (%0.3f, %0.3f), alpha %0.3f",
+ id, resampledCoords.getX(), resampledCoords.getY(), currentCoords.getX(),
+ currentCoords.getY(), otherCoords.getX(), otherCoords.getY(), alpha);
+ } else {
+ ALOGD_IF(debugResampling(), "[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f)", id,
+ resampledCoords.getX(), resampledCoords.getY(), currentCoords.getX(),
+ currentCoords.getY());
+ }
+ }
+
+ event->addSample(sampleTime, touchState.lastResample.pointers);
+}
+
+status_t InputConsumer::sendFinishedSignal(uint32_t seq, bool handled) {
+ ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+ "channel '%s' consumer ~ sendFinishedSignal: seq=%u, handled=%s",
+ mChannel->getName().c_str(), seq, toString(handled));
+
+ if (!seq) {
+ ALOGE("Attempted to send a finished signal with sequence number 0.");
+ return BAD_VALUE;
+ }
+
+ // Send finished signals for the batch sequence chain first.
+ size_t seqChainCount = mSeqChains.size();
+ if (seqChainCount) {
+ uint32_t currentSeq = seq;
+ uint32_t chainSeqs[seqChainCount];
+ size_t chainIndex = 0;
+ for (size_t i = seqChainCount; i > 0;) {
+ i--;
+ const SeqChain& seqChain = mSeqChains[i];
+ if (seqChain.seq == currentSeq) {
+ currentSeq = seqChain.chain;
+ chainSeqs[chainIndex++] = currentSeq;
+ mSeqChains.erase(mSeqChains.begin() + i);
+ }
+ }
+ status_t status = OK;
+ while (!status && chainIndex > 0) {
+ chainIndex--;
+ status = sendUnchainedFinishedSignal(chainSeqs[chainIndex], handled);
+ }
+ if (status) {
+ // An error occurred so at least one signal was not sent, reconstruct the chain.
+ for (;;) {
+ SeqChain seqChain;
+ seqChain.seq = chainIndex != 0 ? chainSeqs[chainIndex - 1] : seq;
+ seqChain.chain = chainSeqs[chainIndex];
+ mSeqChains.push_back(seqChain);
+ if (!chainIndex) break;
+ chainIndex--;
+ }
+ return status;
+ }
+ }
+
+ // Send finished signal for the last message in the batch.
+ return sendUnchainedFinishedSignal(seq, handled);
+}
+
+status_t InputConsumer::sendTimeline(int32_t inputEventId,
+ std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline) {
+ ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+ "channel '%s' consumer ~ sendTimeline: inputEventId=%" PRId32
+ ", gpuCompletedTime=%" PRId64 ", presentTime=%" PRId64,
+ mChannel->getName().c_str(), inputEventId,
+ graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME],
+ graphicsTimeline[GraphicsTimeline::PRESENT_TIME]);
+
+ InputMessage msg;
+ msg.header.type = InputMessage::Type::TIMELINE;
+ msg.header.seq = 0;
+ msg.body.timeline.eventId = inputEventId;
+ msg.body.timeline.graphicsTimeline = std::move(graphicsTimeline);
+ return mChannel->sendMessage(&msg);
+}
+
+nsecs_t InputConsumer::getConsumeTime(uint32_t seq) const {
+ auto it = mConsumeTimes.find(seq);
+ // Consume time will be missing if either 'finishInputEvent' is called twice, or if it was
+ // called for the wrong (synthetic?) input event. Either way, it is a bug that should be fixed.
+ LOG_ALWAYS_FATAL_IF(it == mConsumeTimes.end(), "Could not find consume time for seq=%" PRIu32,
+ seq);
+ return it->second;
+}
+
+void InputConsumer::popConsumeTime(uint32_t seq) {
+ mConsumeTimes.erase(seq);
+}
+
+status_t InputConsumer::sendUnchainedFinishedSignal(uint32_t seq, bool handled) {
+ InputMessage msg;
+ msg.header.type = InputMessage::Type::FINISHED;
+ msg.header.seq = seq;
+ msg.body.finished.handled = handled;
+ msg.body.finished.consumeTime = getConsumeTime(seq);
+ status_t result = mChannel->sendMessage(&msg);
+ if (result == OK) {
+ // Remove the consume time if the socket write succeeded. We will not need to ack this
+ // message anymore. If the socket write did not succeed, we will try again and will still
+ // need consume time.
+ popConsumeTime(seq);
+
+ // Trace the event processing timeline - event was just finished
+ ATRACE_ASYNC_END("InputConsumer processing", /*cookie=*/seq);
+ }
+ return result;
+}
+
+bool InputConsumer::hasPendingBatch() const {
+ return !mBatches.empty();
+}
+
+int32_t InputConsumer::getPendingBatchSource() const {
+ if (mBatches.empty()) {
+ return AINPUT_SOURCE_CLASS_NONE;
+ }
+
+ const Batch& batch = mBatches[0];
+ const InputMessage& head = batch.samples[0];
+ return head.body.motion.source;
+}
+
+bool InputConsumer::probablyHasInput() const {
+ return hasPendingBatch() || mChannel->probablyHasInput();
+}
+
+ssize_t InputConsumer::findBatch(int32_t deviceId, int32_t source) const {
+ for (size_t i = 0; i < mBatches.size(); i++) {
+ const Batch& batch = mBatches[i];
+ const InputMessage& head = batch.samples[0];
+ if (head.body.motion.deviceId == deviceId && head.body.motion.source == source) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+ssize_t InputConsumer::findTouchState(int32_t deviceId, int32_t source) const {
+ for (size_t i = 0; i < mTouchStates.size(); i++) {
+ const TouchState& touchState = mTouchStates[i];
+ if (touchState.deviceId == deviceId && touchState.source == source) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+bool InputConsumer::canAddSample(const Batch& batch, const InputMessage* msg) {
+ const InputMessage& head = batch.samples[0];
+ uint32_t pointerCount = msg->body.motion.pointerCount;
+ if (head.body.motion.pointerCount != pointerCount ||
+ head.body.motion.action != msg->body.motion.action) {
+ return false;
+ }
+ for (size_t i = 0; i < pointerCount; i++) {
+ if (head.body.motion.pointers[i].properties != msg->body.motion.pointers[i].properties) {
+ return false;
+ }
+ }
+ return true;
+}
+
+ssize_t InputConsumer::findSampleNoLaterThan(const Batch& batch, nsecs_t time) {
+ size_t numSamples = batch.samples.size();
+ size_t index = 0;
+ while (index < numSamples && batch.samples[index].body.motion.eventTime <= time) {
+ index += 1;
+ }
+ return ssize_t(index) - 1;
+}
+
+std::string InputConsumer::dump() const {
+ std::string out;
+ out = out + "mResampleTouch = " + toString(mResampleTouch) + "\n";
+ out = out + "mChannel = " + mChannel->getName() + "\n";
+ out = out + "mMsgDeferred: " + toString(mMsgDeferred) + "\n";
+ if (mMsgDeferred) {
+ out = out + "mMsg : " + ftl::enum_string(mMsg.header.type) + "\n";
+ }
+ out += "Batches:\n";
+ for (const Batch& batch : mBatches) {
+ out += " Batch:\n";
+ for (const InputMessage& msg : batch.samples) {
+ out += android::base::StringPrintf(" Message %" PRIu32 ": %s ", msg.header.seq,
+ ftl::enum_string(msg.header.type).c_str());
+ switch (msg.header.type) {
+ case InputMessage::Type::KEY: {
+ out += android::base::StringPrintf("action=%s keycode=%" PRId32,
+ KeyEvent::actionToString(
+ msg.body.key.action),
+ msg.body.key.keyCode);
+ break;
+ }
+ case InputMessage::Type::MOTION: {
+ out = out + "action=" + MotionEvent::actionToString(msg.body.motion.action);
+ for (uint32_t i = 0; i < msg.body.motion.pointerCount; i++) {
+ const float x = msg.body.motion.pointers[i].coords.getX();
+ const float y = msg.body.motion.pointers[i].coords.getY();
+ out += android::base::StringPrintf("\n Pointer %" PRIu32
+ " : x=%.1f y=%.1f",
+ i, x, y);
+ }
+ break;
+ }
+ case InputMessage::Type::FINISHED: {
+ out += android::base::StringPrintf("handled=%s, consumeTime=%" PRId64,
+ toString(msg.body.finished.handled),
+ msg.body.finished.consumeTime);
+ break;
+ }
+ case InputMessage::Type::FOCUS: {
+ out += android::base::StringPrintf("hasFocus=%s",
+ toString(msg.body.focus.hasFocus));
+ break;
+ }
+ case InputMessage::Type::CAPTURE: {
+ out += android::base::StringPrintf("hasCapture=%s",
+ toString(msg.body.capture
+ .pointerCaptureEnabled));
+ break;
+ }
+ case InputMessage::Type::DRAG: {
+ out += android::base::StringPrintf("x=%.1f y=%.1f, isExiting=%s",
+ msg.body.drag.x, msg.body.drag.y,
+ toString(msg.body.drag.isExiting));
+ break;
+ }
+ case InputMessage::Type::TIMELINE: {
+ const nsecs_t gpuCompletedTime =
+ msg.body.timeline
+ .graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME];
+ const nsecs_t presentTime =
+ msg.body.timeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME];
+ out += android::base::StringPrintf("inputEventId=%" PRId32
+ ", gpuCompletedTime=%" PRId64
+ ", presentTime=%" PRId64,
+ msg.body.timeline.eventId, gpuCompletedTime,
+ presentTime);
+ break;
+ }
+ case InputMessage::Type::TOUCH_MODE: {
+ out += android::base::StringPrintf("isInTouchMode=%s",
+ toString(msg.body.touchMode.isInTouchMode));
+ break;
+ }
+ }
+ out += "\n";
+ }
+ }
+ if (mBatches.empty()) {
+ out += " <empty>\n";
+ }
+ out += "mSeqChains:\n";
+ for (const SeqChain& chain : mSeqChains) {
+ out += android::base::StringPrintf(" chain: seq = %" PRIu32 " chain=%" PRIu32, chain.seq,
+ chain.chain);
+ }
+ if (mSeqChains.empty()) {
+ out += " <empty>\n";
+ }
+ out += "mConsumeTimes:\n";
+ for (const auto& [seq, consumeTime] : mConsumeTimes) {
+ out += android::base::StringPrintf(" seq = %" PRIu32 " consumeTime = %" PRId64, seq,
+ consumeTime);
+ }
+ if (mConsumeTimes.empty()) {
+ out += " <empty>\n";
+ }
+ return out;
+}
+
+} // namespace android
diff --git a/libs/input/InputConsumerNoResampling.cpp b/libs/input/InputConsumerNoResampling.cpp
new file mode 100644
index 0000000..52acb51
--- /dev/null
+++ b/libs/input/InputConsumerNoResampling.cpp
@@ -0,0 +1,540 @@
+/**
+ * Copyright 2024 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 "InputTransport"
+#define ATRACE_TAG ATRACE_TAG_INPUT
+
+#include <inttypes.h>
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <cutils/properties.h>
+#include <ftl/enum.h>
+#include <utils/Trace.h>
+
+#include <com_android_input_flags.h>
+#include <input/InputConsumerNoResampling.h>
+#include <input/PrintTools.h>
+#include <input/TraceTools.h>
+
+namespace input_flags = com::android::input::flags;
+
+namespace android {
+
+namespace {
+
+/**
+ * Log debug messages relating to the consumer end of the transport channel.
+ * Enable this via "adb shell setprop log.tag.InputTransportConsumer DEBUG" (requires restart)
+ */
+const bool DEBUG_TRANSPORT_CONSUMER =
+ __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Consumer", ANDROID_LOG_INFO);
+
+std::unique_ptr<KeyEvent> createKeyEvent(const InputMessage& msg) {
+ std::unique_ptr<KeyEvent> event = std::make_unique<KeyEvent>();
+ event->initialize(msg.body.key.eventId, msg.body.key.deviceId, msg.body.key.source,
+ msg.body.key.displayId, msg.body.key.hmac, msg.body.key.action,
+ msg.body.key.flags, msg.body.key.keyCode, msg.body.key.scanCode,
+ msg.body.key.metaState, msg.body.key.repeatCount, msg.body.key.downTime,
+ msg.body.key.eventTime);
+ return event;
+}
+
+std::unique_ptr<FocusEvent> createFocusEvent(const InputMessage& msg) {
+ std::unique_ptr<FocusEvent> event = std::make_unique<FocusEvent>();
+ event->initialize(msg.body.focus.eventId, msg.body.focus.hasFocus);
+ return event;
+}
+
+std::unique_ptr<CaptureEvent> createCaptureEvent(const InputMessage& msg) {
+ std::unique_ptr<CaptureEvent> event = std::make_unique<CaptureEvent>();
+ event->initialize(msg.body.capture.eventId, msg.body.capture.pointerCaptureEnabled);
+ return event;
+}
+
+std::unique_ptr<DragEvent> createDragEvent(const InputMessage& msg) {
+ std::unique_ptr<DragEvent> event = std::make_unique<DragEvent>();
+ event->initialize(msg.body.drag.eventId, msg.body.drag.x, msg.body.drag.y,
+ msg.body.drag.isExiting);
+ return event;
+}
+
+std::unique_ptr<MotionEvent> createMotionEvent(const InputMessage& msg) {
+ std::unique_ptr<MotionEvent> event = std::make_unique<MotionEvent>();
+ const uint32_t pointerCount = msg.body.motion.pointerCount;
+ std::vector<PointerProperties> pointerProperties;
+ pointerProperties.reserve(pointerCount);
+ std::vector<PointerCoords> pointerCoords;
+ pointerCoords.reserve(pointerCount);
+ for (uint32_t i = 0; i < pointerCount; i++) {
+ pointerProperties.push_back(msg.body.motion.pointers[i].properties);
+ pointerCoords.push_back(msg.body.motion.pointers[i].coords);
+ }
+
+ ui::Transform transform;
+ transform.set({msg.body.motion.dsdx, msg.body.motion.dtdx, msg.body.motion.tx,
+ msg.body.motion.dtdy, msg.body.motion.dsdy, msg.body.motion.ty, 0, 0, 1});
+ ui::Transform displayTransform;
+ displayTransform.set({msg.body.motion.dsdxRaw, msg.body.motion.dtdxRaw, msg.body.motion.txRaw,
+ msg.body.motion.dtdyRaw, msg.body.motion.dsdyRaw, msg.body.motion.tyRaw,
+ 0, 0, 1});
+ event->initialize(msg.body.motion.eventId, msg.body.motion.deviceId, msg.body.motion.source,
+ msg.body.motion.displayId, msg.body.motion.hmac, msg.body.motion.action,
+ msg.body.motion.actionButton, msg.body.motion.flags,
+ msg.body.motion.edgeFlags, msg.body.motion.metaState,
+ msg.body.motion.buttonState, msg.body.motion.classification, transform,
+ msg.body.motion.xPrecision, msg.body.motion.yPrecision,
+ msg.body.motion.xCursorPosition, msg.body.motion.yCursorPosition,
+ displayTransform, msg.body.motion.downTime, msg.body.motion.eventTime,
+ pointerCount, pointerProperties.data(), pointerCoords.data());
+ return event;
+}
+
+void addSample(MotionEvent& event, const InputMessage& msg) {
+ uint32_t pointerCount = msg.body.motion.pointerCount;
+ std::vector<PointerCoords> pointerCoords;
+ pointerCoords.reserve(pointerCount);
+ for (uint32_t i = 0; i < pointerCount; i++) {
+ pointerCoords.push_back(msg.body.motion.pointers[i].coords);
+ }
+
+ // TODO(b/329770983): figure out if it's safe to combine events with mismatching metaState
+ event.setMetaState(event.getMetaState() | msg.body.motion.metaState);
+ event.addSample(msg.body.motion.eventTime, pointerCoords.data());
+}
+
+std::unique_ptr<TouchModeEvent> createTouchModeEvent(const InputMessage& msg) {
+ std::unique_ptr<TouchModeEvent> event = std::make_unique<TouchModeEvent>();
+ event->initialize(msg.body.touchMode.eventId, msg.body.touchMode.isInTouchMode);
+ return event;
+}
+
+std::string outboundMessageToString(const InputMessage& outboundMsg) {
+ switch (outboundMsg.header.type) {
+ case InputMessage::Type::FINISHED: {
+ return android::base::StringPrintf(" Finish: seq=%" PRIu32 " handled=%s",
+ outboundMsg.header.seq,
+ toString(outboundMsg.body.finished.handled));
+ }
+ case InputMessage::Type::TIMELINE: {
+ return android::base::
+ StringPrintf(" Timeline: inputEventId=%" PRId32 " gpuCompletedTime=%" PRId64
+ ", presentTime=%" PRId64,
+ outboundMsg.body.timeline.eventId,
+ outboundMsg.body.timeline
+ .graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME],
+ outboundMsg.body.timeline
+ .graphicsTimeline[GraphicsTimeline::PRESENT_TIME]);
+ }
+ default: {
+ LOG(FATAL) << "Outbound message must be FINISHED or TIMELINE, got "
+ << ftl::enum_string(outboundMsg.header.type);
+ return "Unreachable";
+ }
+ }
+}
+
+InputMessage createFinishedMessage(uint32_t seq, bool handled, nsecs_t consumeTime) {
+ InputMessage msg;
+ msg.header.type = InputMessage::Type::FINISHED;
+ msg.header.seq = seq;
+ msg.body.finished.handled = handled;
+ msg.body.finished.consumeTime = consumeTime;
+ return msg;
+}
+
+InputMessage createTimelineMessage(int32_t inputEventId, nsecs_t gpuCompletedTime,
+ nsecs_t presentTime) {
+ InputMessage msg;
+ msg.header.type = InputMessage::Type::TIMELINE;
+ msg.header.seq = 0;
+ msg.body.timeline.eventId = inputEventId;
+ msg.body.timeline.graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = gpuCompletedTime;
+ msg.body.timeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME] = presentTime;
+ return msg;
+}
+
+} // namespace
+
+using android::base::Result;
+using android::base::StringPrintf;
+
+// --- InputConsumerNoResampling ---
+
+InputConsumerNoResampling::InputConsumerNoResampling(const std::shared_ptr<InputChannel>& channel,
+ sp<Looper> looper,
+ InputConsumerCallbacks& callbacks)
+ : mChannel(channel), mLooper(looper), mCallbacks(callbacks), mFdEvents(0) {
+ LOG_ALWAYS_FATAL_IF(mLooper == nullptr);
+ mCallback = sp<LooperEventCallback>::make(
+ std::bind(&InputConsumerNoResampling::handleReceiveCallback, this,
+ std::placeholders::_1));
+ // In the beginning, there are no pending outbounds events; we only care about receiving
+ // incoming data.
+ setFdEvents(ALOOPER_EVENT_INPUT);
+}
+
+InputConsumerNoResampling::~InputConsumerNoResampling() {
+ ensureCalledOnLooperThread(__func__);
+ consumeBatchedInputEvents(std::nullopt);
+ while (!mOutboundQueue.empty()) {
+ processOutboundEvents();
+ // This is our last chance to ack the events. If we don't ack them here, we will get an ANR,
+ // so keep trying to send the events as long as they are present in the queue.
+ }
+ setFdEvents(0);
+}
+
+int InputConsumerNoResampling::handleReceiveCallback(int events) {
+ // Allowed return values of this function as documented in LooperCallback::handleEvent
+ constexpr int REMOVE_CALLBACK = 0;
+ constexpr int KEEP_CALLBACK = 1;
+
+ if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) {
+ // This error typically occurs when the publisher has closed the input channel
+ // as part of removing a window or finishing an IME session, in which case
+ // the consumer will soon be disposed as well.
+ if (DEBUG_TRANSPORT_CONSUMER) {
+ LOG(INFO) << "The channel was hung up or an error occurred: " << mChannel->getName();
+ }
+ return REMOVE_CALLBACK;
+ }
+
+ int handledEvents = 0;
+ if (events & ALOOPER_EVENT_INPUT) {
+ std::vector<InputMessage> messages = readAllMessages();
+ handleMessages(std::move(messages));
+ handledEvents |= ALOOPER_EVENT_INPUT;
+ }
+
+ if (events & ALOOPER_EVENT_OUTPUT) {
+ processOutboundEvents();
+ handledEvents |= ALOOPER_EVENT_OUTPUT;
+ }
+ if (handledEvents != events) {
+ LOG(FATAL) << "Mismatch: handledEvents=" << handledEvents << ", events=" << events;
+ }
+ return KEEP_CALLBACK;
+}
+
+void InputConsumerNoResampling::processOutboundEvents() {
+ while (!mOutboundQueue.empty()) {
+ const InputMessage& outboundMsg = mOutboundQueue.front();
+
+ const status_t result = mChannel->sendMessage(&outboundMsg);
+ if (result == OK) {
+ if (outboundMsg.header.type == InputMessage::Type::FINISHED) {
+ ATRACE_ASYNC_END("InputConsumer processing", /*cookie=*/outboundMsg.header.seq);
+ }
+ // Successful send. Erase the entry and keep trying to send more
+ mOutboundQueue.pop();
+ continue;
+ }
+
+ // Publisher is busy, try again later. Keep this entry (do not erase)
+ if (result == WOULD_BLOCK) {
+ setFdEvents(ALOOPER_EVENT_INPUT | ALOOPER_EVENT_OUTPUT);
+ return; // try again later
+ }
+
+ // Some other error. Give up
+ LOG(FATAL) << "Failed to send outbound event on channel '" << mChannel->getName()
+ << "'. status=" << statusToString(result) << "(" << result << ")";
+ }
+
+ // The queue is now empty. Tell looper there's no more output to expect.
+ setFdEvents(ALOOPER_EVENT_INPUT);
+}
+
+void InputConsumerNoResampling::finishInputEvent(uint32_t seq, bool handled) {
+ ensureCalledOnLooperThread(__func__);
+ mOutboundQueue.push(createFinishedMessage(seq, handled, popConsumeTime(seq)));
+ // also produce finish events for all batches for this seq (if any)
+ const auto it = mBatchedSequenceNumbers.find(seq);
+ if (it != mBatchedSequenceNumbers.end()) {
+ for (uint32_t subSeq : it->second) {
+ mOutboundQueue.push(createFinishedMessage(subSeq, handled, popConsumeTime(subSeq)));
+ }
+ mBatchedSequenceNumbers.erase(it);
+ }
+ processOutboundEvents();
+}
+
+bool InputConsumerNoResampling::probablyHasInput() const {
+ // Ideally, this would only be allowed to run on the looper thread, and in production, it will.
+ // However, for testing, it's convenient to call this while the looper thread is blocked, so
+ // we do not call ensureCalledOnLooperThread here.
+ return (!mBatches.empty()) || mChannel->probablyHasInput();
+}
+
+void InputConsumerNoResampling::reportTimeline(int32_t inputEventId, nsecs_t gpuCompletedTime,
+ nsecs_t presentTime) {
+ ensureCalledOnLooperThread(__func__);
+ mOutboundQueue.push(createTimelineMessage(inputEventId, gpuCompletedTime, presentTime));
+ processOutboundEvents();
+}
+
+nsecs_t InputConsumerNoResampling::popConsumeTime(uint32_t seq) {
+ auto it = mConsumeTimes.find(seq);
+ // Consume time will be missing if either 'finishInputEvent' is called twice, or if it was
+ // called for the wrong (synthetic?) input event. Either way, it is a bug that should be fixed.
+ LOG_ALWAYS_FATAL_IF(it == mConsumeTimes.end(), "Could not find consume time for seq=%" PRIu32,
+ seq);
+ nsecs_t consumeTime = it->second;
+ mConsumeTimes.erase(it);
+ return consumeTime;
+}
+
+void InputConsumerNoResampling::setFdEvents(int events) {
+ if (mFdEvents != events) {
+ mFdEvents = events;
+ if (events != 0) {
+ mLooper->addFd(mChannel->getFd(), 0, events, mCallback, nullptr);
+ } else {
+ mLooper->removeFd(mChannel->getFd());
+ }
+ }
+}
+
+void InputConsumerNoResampling::handleMessages(std::vector<InputMessage>&& messages) {
+ // TODO(b/297226446) : add resampling
+ for (const InputMessage& msg : messages) {
+ if (msg.header.type == InputMessage::Type::MOTION) {
+ const int32_t action = msg.body.motion.action;
+ const DeviceId deviceId = msg.body.motion.deviceId;
+ const int32_t source = msg.body.motion.source;
+ const bool batchableEvent = (action == AMOTION_EVENT_ACTION_MOVE ||
+ action == AMOTION_EVENT_ACTION_HOVER_MOVE) &&
+ (isFromSource(source, AINPUT_SOURCE_CLASS_POINTER) ||
+ isFromSource(source, AINPUT_SOURCE_CLASS_JOYSTICK));
+ if (batchableEvent) {
+ // add it to batch
+ mBatches[deviceId].emplace(msg);
+ } else {
+ // consume all pending batches for this event immediately
+ // TODO(b/329776327): figure out if this could be smarter by limiting the
+ // consumption only to the current device.
+ consumeBatchedInputEvents(std::nullopt);
+ handleMessage(msg);
+ }
+ } else {
+ // Non-motion events shouldn't force the consumption of pending batched events
+ handleMessage(msg);
+ }
+ }
+ // At the end of this, if we still have pending batches, notify the receiver about it.
+
+ // We need to carefully notify the InputConsumerCallbacks about the pending batch. The receiver
+ // could choose to consume all events when notified about the batch. That means that the
+ // "mBatches" variable could change when 'InputConsumerCallbacks::onBatchedInputEventPending' is
+ // invoked. We also can't notify the InputConsumerCallbacks in a while loop until mBatches is
+ // empty, because the receiver could choose to not consume the batch immediately.
+ std::set<int32_t> pendingBatchSources;
+ for (const auto& [_, pendingMessages] : mBatches) {
+ // Assume that all messages for a given device has the same source.
+ pendingBatchSources.insert(pendingMessages.front().body.motion.source);
+ }
+ for (const int32_t source : pendingBatchSources) {
+ const bool sourceStillRemaining =
+ std::any_of(mBatches.begin(), mBatches.end(), [=](const auto& pair) {
+ return pair.second.front().body.motion.source == source;
+ });
+ if (sourceStillRemaining) {
+ mCallbacks.onBatchedInputEventPending(source);
+ }
+ }
+}
+
+std::vector<InputMessage> InputConsumerNoResampling::readAllMessages() {
+ std::vector<InputMessage> messages;
+ while (true) {
+ InputMessage msg;
+ status_t result = mChannel->receiveMessage(&msg);
+ switch (result) {
+ case OK: {
+ const auto [_, inserted] =
+ mConsumeTimes.emplace(msg.header.seq, systemTime(SYSTEM_TIME_MONOTONIC));
+ LOG_ALWAYS_FATAL_IF(!inserted, "Already have a consume time for seq=%" PRIu32,
+ msg.header.seq);
+
+ // Trace the event processing timeline - event was just read from the socket
+ // TODO(b/329777420): distinguish between multiple instances of InputConsumer
+ // in the same process.
+ ATRACE_ASYNC_BEGIN("InputConsumer processing", /*cookie=*/msg.header.seq);
+ messages.push_back(msg);
+ break;
+ }
+ case WOULD_BLOCK: {
+ return messages;
+ }
+ case DEAD_OBJECT: {
+ LOG(FATAL) << "Got a dead object for " << mChannel->getName();
+ break;
+ }
+ case BAD_VALUE: {
+ LOG(FATAL) << "Got a bad value for " << mChannel->getName();
+ break;
+ }
+ default: {
+ LOG(FATAL) << "Unexpected error: " << result;
+ break;
+ }
+ }
+ }
+}
+
+void InputConsumerNoResampling::handleMessage(const InputMessage& msg) const {
+ switch (msg.header.type) {
+ case InputMessage::Type::KEY: {
+ std::unique_ptr<KeyEvent> keyEvent = createKeyEvent(msg);
+ mCallbacks.onKeyEvent(std::move(keyEvent), msg.header.seq);
+ break;
+ }
+
+ case InputMessage::Type::MOTION: {
+ std::unique_ptr<MotionEvent> motionEvent = createMotionEvent(msg);
+ mCallbacks.onMotionEvent(std::move(motionEvent), msg.header.seq);
+ break;
+ }
+
+ case InputMessage::Type::FINISHED:
+ case InputMessage::Type::TIMELINE: {
+ LOG_ALWAYS_FATAL("Consumed a %s message, which should never be seen by "
+ "InputConsumer on %s",
+ ftl::enum_string(msg.header.type).c_str(),
+ mChannel->getName().c_str());
+ break;
+ }
+
+ case InputMessage::Type::FOCUS: {
+ std::unique_ptr<FocusEvent> focusEvent = createFocusEvent(msg);
+ mCallbacks.onFocusEvent(std::move(focusEvent), msg.header.seq);
+ break;
+ }
+
+ case InputMessage::Type::CAPTURE: {
+ std::unique_ptr<CaptureEvent> captureEvent = createCaptureEvent(msg);
+ mCallbacks.onCaptureEvent(std::move(captureEvent), msg.header.seq);
+ break;
+ }
+
+ case InputMessage::Type::DRAG: {
+ std::unique_ptr<DragEvent> dragEvent = createDragEvent(msg);
+ mCallbacks.onDragEvent(std::move(dragEvent), msg.header.seq);
+ break;
+ }
+
+ case InputMessage::Type::TOUCH_MODE: {
+ std::unique_ptr<TouchModeEvent> touchModeEvent = createTouchModeEvent(msg);
+ mCallbacks.onTouchModeEvent(std::move(touchModeEvent), msg.header.seq);
+ break;
+ }
+ }
+}
+
+bool InputConsumerNoResampling::consumeBatchedInputEvents(
+ std::optional<nsecs_t> requestedFrameTime) {
+ ensureCalledOnLooperThread(__func__);
+ // When batching is not enabled, we want to consume all events. That's equivalent to having an
+ // infinite frameTime.
+ const nsecs_t frameTime = requestedFrameTime.value_or(std::numeric_limits<nsecs_t>::max());
+ bool producedEvents = false;
+ for (auto& [deviceId, messages] : mBatches) {
+ std::unique_ptr<MotionEvent> motion;
+ std::optional<uint32_t> firstSeqForBatch;
+ std::vector<uint32_t> sequences;
+ while (!messages.empty()) {
+ const InputMessage& msg = messages.front();
+ if (msg.body.motion.eventTime > frameTime) {
+ break;
+ }
+ if (motion == nullptr) {
+ motion = createMotionEvent(msg);
+ firstSeqForBatch = msg.header.seq;
+ const auto [_, inserted] = mBatchedSequenceNumbers.insert({*firstSeqForBatch, {}});
+ if (!inserted) {
+ LOG(FATAL) << "The sequence " << msg.header.seq << " was already present!";
+ }
+ } else {
+ addSample(*motion, msg);
+ mBatchedSequenceNumbers[*firstSeqForBatch].push_back(msg.header.seq);
+ }
+ messages.pop();
+ }
+ if (motion != nullptr) {
+ LOG_ALWAYS_FATAL_IF(!firstSeqForBatch.has_value());
+ mCallbacks.onMotionEvent(std::move(motion), *firstSeqForBatch);
+ producedEvents = true;
+ } else {
+ // This is OK, it just means that the frameTime is too old (all events that we have
+ // pending are in the future of the frametime). Maybe print a
+ // warning? If there are multiple devices active though, this might be normal and can
+ // just be ignored, unless none of them resulted in any consumption (in that case, this
+ // function would already return "false" so we could just leave it up to the caller).
+ }
+ }
+ std::erase_if(mBatches, [](const auto& pair) { return pair.second.empty(); });
+ return producedEvents;
+}
+
+void InputConsumerNoResampling::ensureCalledOnLooperThread(const char* func) const {
+ sp<Looper> callingThreadLooper = Looper::getForThread();
+ if (callingThreadLooper != mLooper) {
+ LOG(FATAL) << "The function " << func << " can only be called on the looper thread";
+ }
+}
+
+std::string InputConsumerNoResampling::dump() const {
+ ensureCalledOnLooperThread(__func__);
+ std::string out;
+ if (mOutboundQueue.empty()) {
+ out += "mOutboundQueue: <empty>\n";
+ } else {
+ out += "mOutboundQueue:\n";
+ // Make a copy of mOutboundQueue for printing destructively. Unfortunately std::queue
+ // doesn't provide a good way to iterate over the entire container.
+ std::queue<InputMessage> tmpQueue = mOutboundQueue;
+ while (!tmpQueue.empty()) {
+ out += std::string(" ") + outboundMessageToString(tmpQueue.front()) + "\n";
+ tmpQueue.pop();
+ }
+ }
+
+ if (mBatches.empty()) {
+ out += "mBatches: <empty>\n";
+ } else {
+ out += "mBatches:\n";
+ for (const auto& [deviceId, messages] : mBatches) {
+ out += " Device id ";
+ out += std::to_string(deviceId);
+ out += ":\n";
+ // Make a copy of mOutboundQueue for printing destructively. Unfortunately std::queue
+ // doesn't provide a good way to iterate over the entire container.
+ std::queue<InputMessage> tmpQueue = messages;
+ while (!tmpQueue.empty()) {
+ LOG_ALWAYS_FATAL_IF(tmpQueue.front().header.type != InputMessage::Type::MOTION);
+ std::unique_ptr<MotionEvent> motion = createMotionEvent(tmpQueue.front());
+ out += std::string(" ") + streamableToString(*motion) + "\n";
+ tmpQueue.pop();
+ }
+ }
+ }
+
+ return out;
+}
+
+} // namespace android
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index e49f4eb..1869483 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -26,10 +26,13 @@
#include <com_android_input_flags.h>
#include <input/InputTransport.h>
+#include <input/PrintTools.h>
#include <input/TraceTools.h>
namespace input_flags = com::android::input::flags;
+namespace android {
+
namespace {
/**
@@ -48,14 +51,6 @@
const bool DEBUG_CHANNEL_LIFECYCLE =
__android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Lifecycle", ANDROID_LOG_INFO);
-/**
- * Log debug messages relating to the consumer end of the transport channel.
- * Enable this via "adb shell setprop log.tag.InputTransportConsumer DEBUG" (requires restart)
- */
-
-const bool DEBUG_TRANSPORT_CONSUMER =
- __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Consumer", ANDROID_LOG_INFO);
-
const bool IS_DEBUGGABLE_BUILD =
#if defined(__ANDROID__)
android::base::GetBoolProperty("ro.debuggable", false);
@@ -78,23 +73,6 @@
return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Publisher", ANDROID_LOG_INFO);
}
-/**
- * Log debug messages about touch event resampling.
- *
- * Enable this via "adb shell setprop log.tag.InputTransportResampling DEBUG".
- * This requires a restart on non-debuggable (e.g. user) builds, but should take effect immediately
- * on debuggable builds (e.g. userdebug).
- */
-bool debugResampling() {
- if (!IS_DEBUGGABLE_BUILD) {
- static const bool DEBUG_TRANSPORT_RESAMPLING =
- __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling",
- ANDROID_LOG_INFO);
- return DEBUG_TRANSPORT_RESAMPLING;
- }
- return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling", ANDROID_LOG_INFO);
-}
-
android::base::unique_fd dupChannelFd(int fd) {
android::base::unique_fd newFd(::dup(fd));
if (!newFd.ok()) {
@@ -110,78 +88,25 @@
return newFd;
}
-} // namespace
-
-using android::base::Result;
-using android::base::StringPrintf;
-
-namespace android {
-
// Socket buffer size. The default is typically about 128KB, which is much larger than
// we really need. So we make it smaller. It just needs to be big enough to hold
// a few dozen large multi-finger motion events in the case where an application gets
// behind processing touches.
-static const size_t SOCKET_BUFFER_SIZE = 32 * 1024;
-
-// Nanoseconds per milliseconds.
-static const nsecs_t NANOS_PER_MS = 1000000;
-
-// Latency added during resampling. A few milliseconds doesn't hurt much but
-// reduces the impact of mispredicted touch positions.
-const std::chrono::duration RESAMPLE_LATENCY = 5ms;
-
-// Minimum time difference between consecutive samples before attempting to resample.
-static const nsecs_t RESAMPLE_MIN_DELTA = 2 * NANOS_PER_MS;
-
-// Maximum time difference between consecutive samples before attempting to resample
-// by extrapolation.
-static const nsecs_t RESAMPLE_MAX_DELTA = 20 * NANOS_PER_MS;
-
-// Maximum time to predict forward from the last known state, to avoid predicting too
-// far into the future. This time is further bounded by 50% of the last time delta.
-static const nsecs_t RESAMPLE_MAX_PREDICTION = 8 * NANOS_PER_MS;
-
-/**
- * System property for enabling / disabling touch resampling.
- * Resampling extrapolates / interpolates the reported touch event coordinates to better
- * align them to the VSYNC signal, thus resulting in smoother scrolling performance.
- * Resampling is not needed (and should be disabled) on hardware that already
- * has touch events triggered by VSYNC.
- * Set to "1" to enable resampling (default).
- * Set to "0" to disable resampling.
- * Resampling is enabled by default.
- */
-static const char* PROPERTY_RESAMPLING_ENABLED = "ro.input.resampling";
+constexpr size_t SOCKET_BUFFER_SIZE = 32 * 1024;
/**
* Crash if the events that are getting sent to the InputPublisher are inconsistent.
* Enable this via "adb shell setprop log.tag.InputTransportVerifyEvents DEBUG"
*/
-static bool verifyEvents() {
+bool verifyEvents() {
return input_flags::enable_outbound_event_verification() ||
__android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "VerifyEvents", ANDROID_LOG_INFO);
}
-template<typename T>
-inline static T min(const T& a, const T& b) {
- return a < b ? a : b;
-}
+} // namespace
-inline static float lerp(float a, float b, float alpha) {
- return a + alpha * (b - a);
-}
-
-inline static bool isPointerEvent(int32_t source) {
- return (source & AINPUT_SOURCE_CLASS_POINTER) == AINPUT_SOURCE_CLASS_POINTER;
-}
-
-inline static const char* toString(bool value) {
- return value ? "true" : "false";
-}
-
-static bool shouldResampleTool(ToolType toolType) {
- return toolType == ToolType::FINGER || toolType == ToolType::UNKNOWN;
-}
+using android::base::Result;
+using android::base::StringPrintf;
// --- InputMessage ---
@@ -838,819 +763,4 @@
return android::base::Error(UNKNOWN_ERROR);
}
-// --- InputConsumer ---
-
-InputConsumer::InputConsumer(const std::shared_ptr<InputChannel>& channel)
- : InputConsumer(channel, isTouchResamplingEnabled()) {}
-
-InputConsumer::InputConsumer(const std::shared_ptr<InputChannel>& channel,
- bool enableTouchResampling)
- : mResampleTouch(enableTouchResampling), mChannel(channel), mMsgDeferred(false) {}
-
-InputConsumer::~InputConsumer() {
-}
-
-bool InputConsumer::isTouchResamplingEnabled() {
- return property_get_bool(PROPERTY_RESAMPLING_ENABLED, true);
-}
-
-status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consumeBatches,
- nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
- ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
- "channel '%s' consumer ~ consume: consumeBatches=%s, frameTime=%" PRId64,
- mChannel->getName().c_str(), toString(consumeBatches), frameTime);
-
- *outSeq = 0;
- *outEvent = nullptr;
-
- // Fetch the next input message.
- // Loop until an event can be returned or no additional events are received.
- while (!*outEvent) {
- if (mMsgDeferred) {
- // mMsg contains a valid input message from the previous call to consume
- // that has not yet been processed.
- mMsgDeferred = false;
- } else {
- // Receive a fresh message.
- status_t result = mChannel->receiveMessage(&mMsg);
- if (result == OK) {
- const auto [_, inserted] =
- mConsumeTimes.emplace(mMsg.header.seq, systemTime(SYSTEM_TIME_MONOTONIC));
- LOG_ALWAYS_FATAL_IF(!inserted, "Already have a consume time for seq=%" PRIu32,
- mMsg.header.seq);
-
- // Trace the event processing timeline - event was just read from the socket
- ATRACE_ASYNC_BEGIN("InputConsumer processing", /*cookie=*/mMsg.header.seq);
- }
- if (result) {
- // Consume the next batched event unless batches are being held for later.
- if (consumeBatches || result != WOULD_BLOCK) {
- result = consumeBatch(factory, frameTime, outSeq, outEvent);
- if (*outEvent) {
- ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
- "channel '%s' consumer ~ consumed batch event, seq=%u",
- mChannel->getName().c_str(), *outSeq);
- break;
- }
- }
- return result;
- }
- }
-
- switch (mMsg.header.type) {
- case InputMessage::Type::KEY: {
- KeyEvent* keyEvent = factory->createKeyEvent();
- if (!keyEvent) return NO_MEMORY;
-
- initializeKeyEvent(keyEvent, &mMsg);
- *outSeq = mMsg.header.seq;
- *outEvent = keyEvent;
- ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
- "channel '%s' consumer ~ consumed key event, seq=%u",
- mChannel->getName().c_str(), *outSeq);
- break;
- }
-
- case InputMessage::Type::MOTION: {
- ssize_t batchIndex = findBatch(mMsg.body.motion.deviceId, mMsg.body.motion.source);
- if (batchIndex >= 0) {
- Batch& batch = mBatches[batchIndex];
- if (canAddSample(batch, &mMsg)) {
- batch.samples.push_back(mMsg);
- ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
- "channel '%s' consumer ~ appended to batch event",
- mChannel->getName().c_str());
- break;
- } else if (isPointerEvent(mMsg.body.motion.source) &&
- mMsg.body.motion.action == AMOTION_EVENT_ACTION_CANCEL) {
- // No need to process events that we are going to cancel anyways
- const size_t count = batch.samples.size();
- for (size_t i = 0; i < count; i++) {
- const InputMessage& msg = batch.samples[i];
- sendFinishedSignal(msg.header.seq, false);
- }
- batch.samples.erase(batch.samples.begin(), batch.samples.begin() + count);
- mBatches.erase(mBatches.begin() + batchIndex);
- } else {
- // We cannot append to the batch in progress, so we need to consume
- // the previous batch right now and defer the new message until later.
- mMsgDeferred = true;
- status_t result = consumeSamples(factory, batch, batch.samples.size(),
- outSeq, outEvent);
- mBatches.erase(mBatches.begin() + batchIndex);
- if (result) {
- return result;
- }
- ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
- "channel '%s' consumer ~ consumed batch event and "
- "deferred current event, seq=%u",
- mChannel->getName().c_str(), *outSeq);
- break;
- }
- }
-
- // Start a new batch if needed.
- if (mMsg.body.motion.action == AMOTION_EVENT_ACTION_MOVE ||
- mMsg.body.motion.action == AMOTION_EVENT_ACTION_HOVER_MOVE) {
- Batch batch;
- batch.samples.push_back(mMsg);
- mBatches.push_back(batch);
- ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
- "channel '%s' consumer ~ started batch event",
- mChannel->getName().c_str());
- break;
- }
-
- MotionEvent* motionEvent = factory->createMotionEvent();
- if (!motionEvent) return NO_MEMORY;
-
- updateTouchState(mMsg);
- initializeMotionEvent(motionEvent, &mMsg);
- *outSeq = mMsg.header.seq;
- *outEvent = motionEvent;
-
- ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
- "channel '%s' consumer ~ consumed motion event, seq=%u",
- mChannel->getName().c_str(), *outSeq);
- break;
- }
-
- case InputMessage::Type::FINISHED:
- case InputMessage::Type::TIMELINE: {
- LOG_ALWAYS_FATAL("Consumed a %s message, which should never be seen by "
- "InputConsumer!",
- ftl::enum_string(mMsg.header.type).c_str());
- break;
- }
-
- case InputMessage::Type::FOCUS: {
- FocusEvent* focusEvent = factory->createFocusEvent();
- if (!focusEvent) return NO_MEMORY;
-
- initializeFocusEvent(focusEvent, &mMsg);
- *outSeq = mMsg.header.seq;
- *outEvent = focusEvent;
- break;
- }
-
- case InputMessage::Type::CAPTURE: {
- CaptureEvent* captureEvent = factory->createCaptureEvent();
- if (!captureEvent) return NO_MEMORY;
-
- initializeCaptureEvent(captureEvent, &mMsg);
- *outSeq = mMsg.header.seq;
- *outEvent = captureEvent;
- break;
- }
-
- case InputMessage::Type::DRAG: {
- DragEvent* dragEvent = factory->createDragEvent();
- if (!dragEvent) return NO_MEMORY;
-
- initializeDragEvent(dragEvent, &mMsg);
- *outSeq = mMsg.header.seq;
- *outEvent = dragEvent;
- break;
- }
-
- case InputMessage::Type::TOUCH_MODE: {
- TouchModeEvent* touchModeEvent = factory->createTouchModeEvent();
- if (!touchModeEvent) return NO_MEMORY;
-
- initializeTouchModeEvent(touchModeEvent, &mMsg);
- *outSeq = mMsg.header.seq;
- *outEvent = touchModeEvent;
- break;
- }
- }
- }
- return OK;
-}
-
-status_t InputConsumer::consumeBatch(InputEventFactoryInterface* factory,
- nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
- status_t result;
- for (size_t i = mBatches.size(); i > 0; ) {
- i--;
- Batch& batch = mBatches[i];
- if (frameTime < 0) {
- result = consumeSamples(factory, batch, batch.samples.size(), outSeq, outEvent);
- mBatches.erase(mBatches.begin() + i);
- return result;
- }
-
- nsecs_t sampleTime = frameTime;
- if (mResampleTouch) {
- sampleTime -= std::chrono::nanoseconds(RESAMPLE_LATENCY).count();
- }
- ssize_t split = findSampleNoLaterThan(batch, sampleTime);
- if (split < 0) {
- continue;
- }
-
- result = consumeSamples(factory, batch, split + 1, outSeq, outEvent);
- const InputMessage* next;
- if (batch.samples.empty()) {
- mBatches.erase(mBatches.begin() + i);
- next = nullptr;
- } else {
- next = &batch.samples[0];
- }
- if (!result && mResampleTouch) {
- resampleTouchState(sampleTime, static_cast<MotionEvent*>(*outEvent), next);
- }
- return result;
- }
-
- return WOULD_BLOCK;
-}
-
-status_t InputConsumer::consumeSamples(InputEventFactoryInterface* factory,
- Batch& batch, size_t count, uint32_t* outSeq, InputEvent** outEvent) {
- MotionEvent* motionEvent = factory->createMotionEvent();
- if (! motionEvent) return NO_MEMORY;
-
- uint32_t chain = 0;
- for (size_t i = 0; i < count; i++) {
- InputMessage& msg = batch.samples[i];
- updateTouchState(msg);
- if (i) {
- SeqChain seqChain;
- seqChain.seq = msg.header.seq;
- seqChain.chain = chain;
- mSeqChains.push_back(seqChain);
- addSample(motionEvent, &msg);
- } else {
- initializeMotionEvent(motionEvent, &msg);
- }
- chain = msg.header.seq;
- }
- batch.samples.erase(batch.samples.begin(), batch.samples.begin() + count);
-
- *outSeq = chain;
- *outEvent = motionEvent;
- return OK;
-}
-
-void InputConsumer::updateTouchState(InputMessage& msg) {
- if (!mResampleTouch || !isPointerEvent(msg.body.motion.source)) {
- return;
- }
-
- int32_t deviceId = msg.body.motion.deviceId;
- int32_t source = msg.body.motion.source;
-
- // Update the touch state history to incorporate the new input message.
- // If the message is in the past relative to the most recently produced resampled
- // touch, then use the resampled time and coordinates instead.
- switch (msg.body.motion.action & AMOTION_EVENT_ACTION_MASK) {
- case AMOTION_EVENT_ACTION_DOWN: {
- ssize_t index = findTouchState(deviceId, source);
- if (index < 0) {
- mTouchStates.push_back({});
- index = mTouchStates.size() - 1;
- }
- TouchState& touchState = mTouchStates[index];
- touchState.initialize(deviceId, source);
- touchState.addHistory(msg);
- break;
- }
-
- case AMOTION_EVENT_ACTION_MOVE: {
- ssize_t index = findTouchState(deviceId, source);
- if (index >= 0) {
- TouchState& touchState = mTouchStates[index];
- touchState.addHistory(msg);
- rewriteMessage(touchState, msg);
- }
- break;
- }
-
- case AMOTION_EVENT_ACTION_POINTER_DOWN: {
- ssize_t index = findTouchState(deviceId, source);
- if (index >= 0) {
- TouchState& touchState = mTouchStates[index];
- touchState.lastResample.idBits.clearBit(msg.body.motion.getActionId());
- rewriteMessage(touchState, msg);
- }
- break;
- }
-
- case AMOTION_EVENT_ACTION_POINTER_UP: {
- ssize_t index = findTouchState(deviceId, source);
- if (index >= 0) {
- TouchState& touchState = mTouchStates[index];
- rewriteMessage(touchState, msg);
- touchState.lastResample.idBits.clearBit(msg.body.motion.getActionId());
- }
- break;
- }
-
- case AMOTION_EVENT_ACTION_SCROLL: {
- ssize_t index = findTouchState(deviceId, source);
- if (index >= 0) {
- TouchState& touchState = mTouchStates[index];
- rewriteMessage(touchState, msg);
- }
- break;
- }
-
- case AMOTION_EVENT_ACTION_UP:
- case AMOTION_EVENT_ACTION_CANCEL: {
- ssize_t index = findTouchState(deviceId, source);
- if (index >= 0) {
- TouchState& touchState = mTouchStates[index];
- rewriteMessage(touchState, msg);
- mTouchStates.erase(mTouchStates.begin() + index);
- }
- break;
- }
- }
-}
-
-/**
- * Replace the coordinates in msg with the coordinates in lastResample, if necessary.
- *
- * If lastResample is no longer valid for a specific pointer (i.e. the lastResample time
- * is in the past relative to msg and the past two events do not contain identical coordinates),
- * then invalidate the lastResample data for that pointer.
- * If the two past events have identical coordinates, then lastResample data for that pointer will
- * remain valid, and will be used to replace these coordinates. Thus, if a certain coordinate x0 is
- * resampled to the new value x1, then x1 will always be used to replace x0 until some new value
- * not equal to x0 is received.
- */
-void InputConsumer::rewriteMessage(TouchState& state, InputMessage& msg) {
- nsecs_t eventTime = msg.body.motion.eventTime;
- for (uint32_t i = 0; i < msg.body.motion.pointerCount; i++) {
- uint32_t id = msg.body.motion.pointers[i].properties.id;
- if (state.lastResample.idBits.hasBit(id)) {
- if (eventTime < state.lastResample.eventTime ||
- state.recentCoordinatesAreIdentical(id)) {
- PointerCoords& msgCoords = msg.body.motion.pointers[i].coords;
- const PointerCoords& resampleCoords = state.lastResample.getPointerById(id);
- ALOGD_IF(debugResampling(), "[%d] - rewrite (%0.3f, %0.3f), old (%0.3f, %0.3f)", id,
- resampleCoords.getX(), resampleCoords.getY(), msgCoords.getX(),
- msgCoords.getY());
- msgCoords.setAxisValue(AMOTION_EVENT_AXIS_X, resampleCoords.getX());
- msgCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, resampleCoords.getY());
- msgCoords.isResampled = true;
- } else {
- state.lastResample.idBits.clearBit(id);
- }
- }
- }
-}
-
-void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event,
- const InputMessage* next) {
- if (!mResampleTouch
- || !(isPointerEvent(event->getSource()))
- || event->getAction() != AMOTION_EVENT_ACTION_MOVE) {
- return;
- }
-
- ssize_t index = findTouchState(event->getDeviceId(), event->getSource());
- if (index < 0) {
- ALOGD_IF(debugResampling(), "Not resampled, no touch state for device.");
- return;
- }
-
- TouchState& touchState = mTouchStates[index];
- if (touchState.historySize < 1) {
- ALOGD_IF(debugResampling(), "Not resampled, no history for device.");
- return;
- }
-
- // Ensure that the current sample has all of the pointers that need to be reported.
- const History* current = touchState.getHistory(0);
- size_t pointerCount = event->getPointerCount();
- for (size_t i = 0; i < pointerCount; i++) {
- uint32_t id = event->getPointerId(i);
- if (!current->idBits.hasBit(id)) {
- ALOGD_IF(debugResampling(), "Not resampled, missing id %d", id);
- return;
- }
- }
-
- // Find the data to use for resampling.
- const History* other;
- History future;
- float alpha;
- if (next) {
- // Interpolate between current sample and future sample.
- // So current->eventTime <= sampleTime <= future.eventTime.
- future.initializeFrom(*next);
- other = &future;
- nsecs_t delta = future.eventTime - current->eventTime;
- if (delta < RESAMPLE_MIN_DELTA) {
- ALOGD_IF(debugResampling(), "Not resampled, delta time is too small: %" PRId64 " ns.",
- delta);
- return;
- }
- alpha = float(sampleTime - current->eventTime) / delta;
- } else if (touchState.historySize >= 2) {
- // Extrapolate future sample using current sample and past sample.
- // So other->eventTime <= current->eventTime <= sampleTime.
- other = touchState.getHistory(1);
- nsecs_t delta = current->eventTime - other->eventTime;
- if (delta < RESAMPLE_MIN_DELTA) {
- ALOGD_IF(debugResampling(), "Not resampled, delta time is too small: %" PRId64 " ns.",
- delta);
- return;
- } else if (delta > RESAMPLE_MAX_DELTA) {
- ALOGD_IF(debugResampling(), "Not resampled, delta time is too large: %" PRId64 " ns.",
- delta);
- return;
- }
- nsecs_t maxPredict = current->eventTime + min(delta / 2, RESAMPLE_MAX_PREDICTION);
- if (sampleTime > maxPredict) {
- ALOGD_IF(debugResampling(),
- "Sample time is too far in the future, adjusting prediction "
- "from %" PRId64 " to %" PRId64 " ns.",
- sampleTime - current->eventTime, maxPredict - current->eventTime);
- sampleTime = maxPredict;
- }
- alpha = float(current->eventTime - sampleTime) / delta;
- } else {
- ALOGD_IF(debugResampling(), "Not resampled, insufficient data.");
- return;
- }
-
- if (current->eventTime == sampleTime) {
- // Prevents having 2 events with identical times and coordinates.
- return;
- }
-
- // Resample touch coordinates.
- History oldLastResample;
- oldLastResample.initializeFrom(touchState.lastResample);
- touchState.lastResample.eventTime = sampleTime;
- touchState.lastResample.idBits.clear();
- for (size_t i = 0; i < pointerCount; i++) {
- uint32_t id = event->getPointerId(i);
- touchState.lastResample.idToIndex[id] = i;
- touchState.lastResample.idBits.markBit(id);
- if (oldLastResample.hasPointerId(id) && touchState.recentCoordinatesAreIdentical(id)) {
- // We maintain the previously resampled value for this pointer (stored in
- // oldLastResample) when the coordinates for this pointer haven't changed since then.
- // This way we don't introduce artificial jitter when pointers haven't actually moved.
- // The isResampled flag isn't cleared as the values don't reflect what the device is
- // actually reporting.
-
- // We know here that the coordinates for the pointer haven't changed because we
- // would've cleared the resampled bit in rewriteMessage if they had. We can't modify
- // lastResample in place becasue the mapping from pointer ID to index may have changed.
- touchState.lastResample.pointers[i] = oldLastResample.getPointerById(id);
- continue;
- }
-
- PointerCoords& resampledCoords = touchState.lastResample.pointers[i];
- const PointerCoords& currentCoords = current->getPointerById(id);
- resampledCoords = currentCoords;
- resampledCoords.isResampled = true;
- if (other->idBits.hasBit(id) && shouldResampleTool(event->getToolType(i))) {
- const PointerCoords& otherCoords = other->getPointerById(id);
- resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_X,
- lerp(currentCoords.getX(), otherCoords.getX(), alpha));
- resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y,
- lerp(currentCoords.getY(), otherCoords.getY(), alpha));
- ALOGD_IF(debugResampling(),
- "[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f), "
- "other (%0.3f, %0.3f), alpha %0.3f",
- id, resampledCoords.getX(), resampledCoords.getY(), currentCoords.getX(),
- currentCoords.getY(), otherCoords.getX(), otherCoords.getY(), alpha);
- } else {
- ALOGD_IF(debugResampling(), "[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f)", id,
- resampledCoords.getX(), resampledCoords.getY(), currentCoords.getX(),
- currentCoords.getY());
- }
- }
-
- event->addSample(sampleTime, touchState.lastResample.pointers);
-}
-
-status_t InputConsumer::sendFinishedSignal(uint32_t seq, bool handled) {
- ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
- "channel '%s' consumer ~ sendFinishedSignal: seq=%u, handled=%s",
- mChannel->getName().c_str(), seq, toString(handled));
-
- if (!seq) {
- ALOGE("Attempted to send a finished signal with sequence number 0.");
- return BAD_VALUE;
- }
-
- // Send finished signals for the batch sequence chain first.
- size_t seqChainCount = mSeqChains.size();
- if (seqChainCount) {
- uint32_t currentSeq = seq;
- uint32_t chainSeqs[seqChainCount];
- size_t chainIndex = 0;
- for (size_t i = seqChainCount; i > 0; ) {
- i--;
- const SeqChain& seqChain = mSeqChains[i];
- if (seqChain.seq == currentSeq) {
- currentSeq = seqChain.chain;
- chainSeqs[chainIndex++] = currentSeq;
- mSeqChains.erase(mSeqChains.begin() + i);
- }
- }
- status_t status = OK;
- while (!status && chainIndex > 0) {
- chainIndex--;
- status = sendUnchainedFinishedSignal(chainSeqs[chainIndex], handled);
- }
- if (status) {
- // An error occurred so at least one signal was not sent, reconstruct the chain.
- for (;;) {
- SeqChain seqChain;
- seqChain.seq = chainIndex != 0 ? chainSeqs[chainIndex - 1] : seq;
- seqChain.chain = chainSeqs[chainIndex];
- mSeqChains.push_back(seqChain);
- if (!chainIndex) break;
- chainIndex--;
- }
- return status;
- }
- }
-
- // Send finished signal for the last message in the batch.
- return sendUnchainedFinishedSignal(seq, handled);
-}
-
-status_t InputConsumer::sendTimeline(int32_t inputEventId,
- std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline) {
- ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
- "channel '%s' consumer ~ sendTimeline: inputEventId=%" PRId32
- ", gpuCompletedTime=%" PRId64 ", presentTime=%" PRId64,
- mChannel->getName().c_str(), inputEventId,
- graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME],
- graphicsTimeline[GraphicsTimeline::PRESENT_TIME]);
-
- InputMessage msg;
- msg.header.type = InputMessage::Type::TIMELINE;
- msg.header.seq = 0;
- msg.body.timeline.eventId = inputEventId;
- msg.body.timeline.graphicsTimeline = std::move(graphicsTimeline);
- return mChannel->sendMessage(&msg);
-}
-
-nsecs_t InputConsumer::getConsumeTime(uint32_t seq) const {
- auto it = mConsumeTimes.find(seq);
- // Consume time will be missing if either 'finishInputEvent' is called twice, or if it was
- // called for the wrong (synthetic?) input event. Either way, it is a bug that should be fixed.
- LOG_ALWAYS_FATAL_IF(it == mConsumeTimes.end(), "Could not find consume time for seq=%" PRIu32,
- seq);
- return it->second;
-}
-
-void InputConsumer::popConsumeTime(uint32_t seq) {
- mConsumeTimes.erase(seq);
-}
-
-status_t InputConsumer::sendUnchainedFinishedSignal(uint32_t seq, bool handled) {
- InputMessage msg;
- msg.header.type = InputMessage::Type::FINISHED;
- msg.header.seq = seq;
- msg.body.finished.handled = handled;
- msg.body.finished.consumeTime = getConsumeTime(seq);
- status_t result = mChannel->sendMessage(&msg);
- if (result == OK) {
- // Remove the consume time if the socket write succeeded. We will not need to ack this
- // message anymore. If the socket write did not succeed, we will try again and will still
- // need consume time.
- popConsumeTime(seq);
-
- // Trace the event processing timeline - event was just finished
- ATRACE_ASYNC_END("InputConsumer processing", /*cookie=*/seq);
- }
- return result;
-}
-
-bool InputConsumer::hasPendingBatch() const {
- return !mBatches.empty();
-}
-
-int32_t InputConsumer::getPendingBatchSource() const {
- if (mBatches.empty()) {
- return AINPUT_SOURCE_CLASS_NONE;
- }
-
- const Batch& batch = mBatches[0];
- const InputMessage& head = batch.samples[0];
- return head.body.motion.source;
-}
-
-bool InputConsumer::probablyHasInput() const {
- return hasPendingBatch() || mChannel->probablyHasInput();
-}
-
-ssize_t InputConsumer::findBatch(int32_t deviceId, int32_t source) const {
- for (size_t i = 0; i < mBatches.size(); i++) {
- const Batch& batch = mBatches[i];
- const InputMessage& head = batch.samples[0];
- if (head.body.motion.deviceId == deviceId && head.body.motion.source == source) {
- return i;
- }
- }
- return -1;
-}
-
-ssize_t InputConsumer::findTouchState(int32_t deviceId, int32_t source) const {
- for (size_t i = 0; i < mTouchStates.size(); i++) {
- const TouchState& touchState = mTouchStates[i];
- if (touchState.deviceId == deviceId && touchState.source == source) {
- return i;
- }
- }
- return -1;
-}
-
-void InputConsumer::initializeKeyEvent(KeyEvent* event, const InputMessage* msg) {
- event->initialize(msg->body.key.eventId, msg->body.key.deviceId, msg->body.key.source,
- msg->body.key.displayId, msg->body.key.hmac, msg->body.key.action,
- msg->body.key.flags, msg->body.key.keyCode, msg->body.key.scanCode,
- msg->body.key.metaState, msg->body.key.repeatCount, msg->body.key.downTime,
- msg->body.key.eventTime);
-}
-
-void InputConsumer::initializeFocusEvent(FocusEvent* event, const InputMessage* msg) {
- event->initialize(msg->body.focus.eventId, msg->body.focus.hasFocus);
-}
-
-void InputConsumer::initializeCaptureEvent(CaptureEvent* event, const InputMessage* msg) {
- event->initialize(msg->body.capture.eventId, msg->body.capture.pointerCaptureEnabled);
-}
-
-void InputConsumer::initializeDragEvent(DragEvent* event, const InputMessage* msg) {
- event->initialize(msg->body.drag.eventId, msg->body.drag.x, msg->body.drag.y,
- msg->body.drag.isExiting);
-}
-
-void InputConsumer::initializeMotionEvent(MotionEvent* event, const InputMessage* msg) {
- uint32_t pointerCount = msg->body.motion.pointerCount;
- PointerProperties pointerProperties[pointerCount];
- PointerCoords pointerCoords[pointerCount];
- for (uint32_t i = 0; i < pointerCount; i++) {
- pointerProperties[i] = msg->body.motion.pointers[i].properties;
- pointerCoords[i] = msg->body.motion.pointers[i].coords;
- }
-
- ui::Transform transform;
- transform.set({msg->body.motion.dsdx, msg->body.motion.dtdx, msg->body.motion.tx,
- msg->body.motion.dtdy, msg->body.motion.dsdy, msg->body.motion.ty, 0, 0, 1});
- ui::Transform displayTransform;
- displayTransform.set({msg->body.motion.dsdxRaw, msg->body.motion.dtdxRaw,
- msg->body.motion.txRaw, msg->body.motion.dtdyRaw,
- msg->body.motion.dsdyRaw, msg->body.motion.tyRaw, 0, 0, 1});
- event->initialize(msg->body.motion.eventId, msg->body.motion.deviceId, msg->body.motion.source,
- msg->body.motion.displayId, msg->body.motion.hmac, msg->body.motion.action,
- msg->body.motion.actionButton, msg->body.motion.flags,
- msg->body.motion.edgeFlags, msg->body.motion.metaState,
- msg->body.motion.buttonState, msg->body.motion.classification, transform,
- msg->body.motion.xPrecision, msg->body.motion.yPrecision,
- msg->body.motion.xCursorPosition, msg->body.motion.yCursorPosition,
- displayTransform, msg->body.motion.downTime, msg->body.motion.eventTime,
- pointerCount, pointerProperties, pointerCoords);
-}
-
-void InputConsumer::initializeTouchModeEvent(TouchModeEvent* event, const InputMessage* msg) {
- event->initialize(msg->body.touchMode.eventId, msg->body.touchMode.isInTouchMode);
-}
-
-void InputConsumer::addSample(MotionEvent* event, const InputMessage* msg) {
- uint32_t pointerCount = msg->body.motion.pointerCount;
- PointerCoords pointerCoords[pointerCount];
- for (uint32_t i = 0; i < pointerCount; i++) {
- pointerCoords[i] = msg->body.motion.pointers[i].coords;
- }
-
- event->setMetaState(event->getMetaState() | msg->body.motion.metaState);
- event->addSample(msg->body.motion.eventTime, pointerCoords);
-}
-
-bool InputConsumer::canAddSample(const Batch& batch, const InputMessage *msg) {
- const InputMessage& head = batch.samples[0];
- uint32_t pointerCount = msg->body.motion.pointerCount;
- if (head.body.motion.pointerCount != pointerCount
- || head.body.motion.action != msg->body.motion.action) {
- return false;
- }
- for (size_t i = 0; i < pointerCount; i++) {
- if (head.body.motion.pointers[i].properties
- != msg->body.motion.pointers[i].properties) {
- return false;
- }
- }
- return true;
-}
-
-ssize_t InputConsumer::findSampleNoLaterThan(const Batch& batch, nsecs_t time) {
- size_t numSamples = batch.samples.size();
- size_t index = 0;
- while (index < numSamples && batch.samples[index].body.motion.eventTime <= time) {
- index += 1;
- }
- return ssize_t(index) - 1;
-}
-
-std::string InputConsumer::dump() const {
- std::string out;
- out = out + "mResampleTouch = " + toString(mResampleTouch) + "\n";
- out = out + "mChannel = " + mChannel->getName() + "\n";
- out = out + "mMsgDeferred: " + toString(mMsgDeferred) + "\n";
- if (mMsgDeferred) {
- out = out + "mMsg : " + ftl::enum_string(mMsg.header.type) + "\n";
- }
- out += "Batches:\n";
- for (const Batch& batch : mBatches) {
- out += " Batch:\n";
- for (const InputMessage& msg : batch.samples) {
- out += android::base::StringPrintf(" Message %" PRIu32 ": %s ", msg.header.seq,
- ftl::enum_string(msg.header.type).c_str());
- switch (msg.header.type) {
- case InputMessage::Type::KEY: {
- out += android::base::StringPrintf("action=%s keycode=%" PRId32,
- KeyEvent::actionToString(
- msg.body.key.action),
- msg.body.key.keyCode);
- break;
- }
- case InputMessage::Type::MOTION: {
- out = out + "action=" + MotionEvent::actionToString(msg.body.motion.action);
- for (uint32_t i = 0; i < msg.body.motion.pointerCount; i++) {
- const float x = msg.body.motion.pointers[i].coords.getX();
- const float y = msg.body.motion.pointers[i].coords.getY();
- out += android::base::StringPrintf("\n Pointer %" PRIu32
- " : x=%.1f y=%.1f",
- i, x, y);
- }
- break;
- }
- case InputMessage::Type::FINISHED: {
- out += android::base::StringPrintf("handled=%s, consumeTime=%" PRId64,
- toString(msg.body.finished.handled),
- msg.body.finished.consumeTime);
- break;
- }
- case InputMessage::Type::FOCUS: {
- out += android::base::StringPrintf("hasFocus=%s",
- toString(msg.body.focus.hasFocus));
- break;
- }
- case InputMessage::Type::CAPTURE: {
- out += android::base::StringPrintf("hasCapture=%s",
- toString(msg.body.capture
- .pointerCaptureEnabled));
- break;
- }
- case InputMessage::Type::DRAG: {
- out += android::base::StringPrintf("x=%.1f y=%.1f, isExiting=%s",
- msg.body.drag.x, msg.body.drag.y,
- toString(msg.body.drag.isExiting));
- break;
- }
- case InputMessage::Type::TIMELINE: {
- const nsecs_t gpuCompletedTime =
- msg.body.timeline
- .graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME];
- const nsecs_t presentTime =
- msg.body.timeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME];
- out += android::base::StringPrintf("inputEventId=%" PRId32
- ", gpuCompletedTime=%" PRId64
- ", presentTime=%" PRId64,
- msg.body.timeline.eventId, gpuCompletedTime,
- presentTime);
- break;
- }
- case InputMessage::Type::TOUCH_MODE: {
- out += android::base::StringPrintf("isInTouchMode=%s",
- toString(msg.body.touchMode.isInTouchMode));
- break;
- }
- }
- out += "\n";
- }
- }
- if (mBatches.empty()) {
- out += " <empty>\n";
- }
- out += "mSeqChains:\n";
- for (const SeqChain& chain : mSeqChains) {
- out += android::base::StringPrintf(" chain: seq = %" PRIu32 " chain=%" PRIu32, chain.seq,
- chain.chain);
- }
- if (mSeqChains.empty()) {
- out += " <empty>\n";
- }
- out += "mConsumeTimes:\n";
- for (const auto& [seq, consumeTime] : mConsumeTimes) {
- out += android::base::StringPrintf(" seq = %" PRIu32 " consumeTime = %" PRId64, seq,
- consumeTime);
- }
- if (mConsumeTimes.empty()) {
- out += " <empty>\n";
- }
- return out;
-}
-
} // namespace android
diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig
index bdec5c3..b48b0fb 100644
--- a/libs/input/input_flags.aconfig
+++ b/libs/input/input_flags.aconfig
@@ -87,6 +87,7 @@
flag {
name: "override_key_behavior_permission_apis"
+ is_exported: true
namespace: "input"
description: "enable override key behavior permission APIs"
bug: "309018874"
@@ -115,6 +116,7 @@
flag {
name: "input_device_view_behavior_api"
+ is_exported: true
namespace: "input"
description: "Controls the API to provide InputDevice view behavior."
bug: "246946631"
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index 0485ff6..e67a65a 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -13,11 +13,13 @@
cpp_std: "c++20",
host_supported: true,
srcs: [
+ "BlockingQueue_test.cpp",
"IdGenerator_test.cpp",
"InputChannel_test.cpp",
"InputDevice_test.cpp",
"InputEvent_test.cpp",
"InputPublisherAndConsumer_test.cpp",
+ "InputPublisherAndConsumerNoResampling_test.cpp",
"InputVerifier_test.cpp",
"MotionPredictor_test.cpp",
"MotionPredictorMetricsManager_test.cpp",
diff --git a/services/inputflinger/tests/BlockingQueue_test.cpp b/libs/input/tests/BlockingQueue_test.cpp
similarity index 97%
rename from services/inputflinger/tests/BlockingQueue_test.cpp
rename to libs/input/tests/BlockingQueue_test.cpp
index 754a5c4..924b937 100644
--- a/services/inputflinger/tests/BlockingQueue_test.cpp
+++ b/libs/input/tests/BlockingQueue_test.cpp
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-#include "../BlockingQueue.h"
-
+#include <input/BlockingQueue.h>
#include <gtest/gtest.h>
#include <thread>
@@ -109,7 +108,7 @@
BlockingQueue<int> queue(capacity);
// Fill queue from a different thread
- std::thread fillQueue([&queue](){
+ std::thread fillQueue([&queue]() {
for (size_t i = 0; i < capacity; i++) {
ASSERT_TRUE(queue.push(static_cast<int>(i)));
}
@@ -136,7 +135,7 @@
std::atomic_bool hasReceivedElement = false;
// fill queue from a different thread
- std::thread waitUntilHasElements([&queue, &hasReceivedElement](){
+ std::thread waitUntilHasElements([&queue, &hasReceivedElement]() {
queue.pop(); // This should block until an element has been added
hasReceivedElement = true;
});
diff --git a/libs/input/tests/InputChannel_test.cpp b/libs/input/tests/InputChannel_test.cpp
index 60feb53..02d4c07 100644
--- a/libs/input/tests/InputChannel_test.cpp
+++ b/libs/input/tests/InputChannel_test.cpp
@@ -16,8 +16,6 @@
#include <array>
-#include "TestHelpers.h"
-
#include <unistd.h>
#include <time.h>
#include <errno.h>
diff --git a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
new file mode 100644
index 0000000..6593497
--- /dev/null
+++ b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
@@ -0,0 +1,813 @@
+/*
+ * Copyright 2024 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/logging.h>
+#include <attestation/HmacKeyManager.h>
+#include <ftl/enum.h>
+#include <gtest/gtest.h>
+#include <gui/constants.h>
+#include <input/BlockingQueue.h>
+#include <input/InputConsumerNoResampling.h>
+#include <input/InputTransport.h>
+
+using android::base::Result;
+
+namespace android {
+
+namespace {
+
+static constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION;
+static constexpr int32_t ACTION_MOVE = AMOTION_EVENT_ACTION_MOVE;
+static constexpr int32_t POINTER_1_DOWN =
+ AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+static constexpr int32_t POINTER_2_DOWN =
+ AMOTION_EVENT_ACTION_POINTER_DOWN | (2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+
+static auto constexpr TIMEOUT = 5s;
+
+struct Pointer {
+ int32_t id;
+ float x;
+ float y;
+ bool isResampled = false;
+};
+
+// A collection of arguments to be sent as publishMotionEvent(). The saved members of this struct
+// allow to check the expectations against the event acquired from the InputConsumerCallbacks. To
+// help simplify expectation checking it carries members not present in MotionEvent, like
+// |rawXScale|.
+struct PublishMotionArgs {
+ const int32_t action;
+ const nsecs_t downTime;
+ const uint32_t seq;
+ const int32_t eventId;
+ const int32_t deviceId = 1;
+ const uint32_t source = AINPUT_SOURCE_TOUCHSCREEN;
+ const int32_t displayId = ADISPLAY_ID_DEFAULT;
+ const int32_t actionButton = 0;
+ const int32_t edgeFlags = AMOTION_EVENT_EDGE_FLAG_TOP;
+ const int32_t metaState = AMETA_ALT_LEFT_ON | AMETA_ALT_ON;
+ const int32_t buttonState = AMOTION_EVENT_BUTTON_PRIMARY;
+ const MotionClassification classification = MotionClassification::AMBIGUOUS_GESTURE;
+ const float xScale = 2;
+ const float yScale = 3;
+ const float xOffset = -10;
+ const float yOffset = -20;
+ const float rawXScale = 4;
+ const float rawYScale = -5;
+ const float rawXOffset = -11;
+ const float rawYOffset = 42;
+ const float xPrecision = 0.25;
+ const float yPrecision = 0.5;
+ const float xCursorPosition = 1.3;
+ const float yCursorPosition = 50.6;
+ std::array<uint8_t, 32> hmac;
+ int32_t flags;
+ ui::Transform transform;
+ ui::Transform rawTransform;
+ const nsecs_t eventTime;
+ size_t pointerCount;
+ std::vector<PointerProperties> pointerProperties;
+ std::vector<PointerCoords> pointerCoords;
+
+ PublishMotionArgs(int32_t action, nsecs_t downTime, const std::vector<Pointer>& pointers,
+ const uint32_t seq);
+};
+
+PublishMotionArgs::PublishMotionArgs(int32_t inAction, nsecs_t inDownTime,
+ const std::vector<Pointer>& pointers, const uint32_t inSeq)
+ : action(inAction),
+ downTime(inDownTime),
+ seq(inSeq),
+ eventId(InputEvent::nextId()),
+ eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) {
+ hmac = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
+
+ flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+ if (action == AMOTION_EVENT_ACTION_CANCEL) {
+ flags |= AMOTION_EVENT_FLAG_CANCELED;
+ }
+ pointerCount = pointers.size();
+ for (size_t i = 0; i < pointerCount; i++) {
+ pointerProperties.push_back({});
+ pointerProperties[i].clear();
+ pointerProperties[i].id = pointers[i].id;
+ pointerProperties[i].toolType = ToolType::FINGER;
+
+ pointerCoords.push_back({});
+ pointerCoords[i].clear();
+ pointerCoords[i].isResampled = pointers[i].isResampled;
+ pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, pointers[i].x);
+ pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, pointers[i].y);
+ pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.5 * i);
+ pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 0.7 * i);
+ pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 1.5 * i);
+ pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 1.7 * i);
+ pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 2.5 * i);
+ pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 2.7 * i);
+ pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 3.5 * i);
+ }
+ transform.set({xScale, 0, xOffset, 0, yScale, yOffset, 0, 0, 1});
+ rawTransform.set({rawXScale, 0, rawXOffset, 0, rawYScale, rawYOffset, 0, 0, 1});
+}
+
+// Checks expectations against |motionEvent| acquired from an InputConsumer. Floating point
+// comparisons limit precision to EPSILON.
+void verifyArgsEqualToEvent(const PublishMotionArgs& args, const MotionEvent& motionEvent) {
+ EXPECT_EQ(args.eventId, motionEvent.getId());
+ EXPECT_EQ(args.deviceId, motionEvent.getDeviceId());
+ EXPECT_EQ(args.source, motionEvent.getSource());
+ EXPECT_EQ(args.displayId, motionEvent.getDisplayId());
+ EXPECT_EQ(args.hmac, motionEvent.getHmac());
+ EXPECT_EQ(args.action, motionEvent.getAction());
+ EXPECT_EQ(args.downTime, motionEvent.getDownTime());
+ EXPECT_EQ(args.flags, motionEvent.getFlags());
+ EXPECT_EQ(args.edgeFlags, motionEvent.getEdgeFlags());
+ EXPECT_EQ(args.metaState, motionEvent.getMetaState());
+ EXPECT_EQ(args.buttonState, motionEvent.getButtonState());
+ EXPECT_EQ(args.classification, motionEvent.getClassification());
+ EXPECT_EQ(args.transform, motionEvent.getTransform());
+ EXPECT_NEAR((-args.rawXOffset / args.rawXScale) * args.xScale + args.xOffset,
+ motionEvent.getRawXOffset(), EPSILON);
+ EXPECT_NEAR((-args.rawYOffset / args.rawYScale) * args.yScale + args.yOffset,
+ motionEvent.getRawYOffset(), EPSILON);
+ EXPECT_EQ(args.xPrecision, motionEvent.getXPrecision());
+ EXPECT_EQ(args.yPrecision, motionEvent.getYPrecision());
+ EXPECT_NEAR(args.xCursorPosition, motionEvent.getRawXCursorPosition(), EPSILON);
+ EXPECT_NEAR(args.yCursorPosition, motionEvent.getRawYCursorPosition(), EPSILON);
+ EXPECT_NEAR(args.xCursorPosition * args.xScale + args.xOffset, motionEvent.getXCursorPosition(),
+ EPSILON);
+ EXPECT_NEAR(args.yCursorPosition * args.yScale + args.yOffset, motionEvent.getYCursorPosition(),
+ EPSILON);
+ EXPECT_EQ(args.rawTransform, motionEvent.getRawTransform());
+ EXPECT_EQ(args.eventTime, motionEvent.getEventTime());
+ EXPECT_EQ(args.pointerCount, motionEvent.getPointerCount());
+ EXPECT_EQ(0U, motionEvent.getHistorySize());
+
+ for (size_t i = 0; i < args.pointerCount; i++) {
+ SCOPED_TRACE(i);
+ EXPECT_EQ(args.pointerProperties[i].id, motionEvent.getPointerId(i));
+ EXPECT_EQ(args.pointerProperties[i].toolType, motionEvent.getToolType(i));
+
+ const auto& pc = args.pointerCoords[i];
+ EXPECT_EQ(pc, motionEvent.getSamplePointerCoords()[i]);
+
+ EXPECT_NEAR(pc.getX() * args.rawXScale + args.rawXOffset, motionEvent.getRawX(i), EPSILON);
+ EXPECT_NEAR(pc.getY() * args.rawYScale + args.rawYOffset, motionEvent.getRawY(i), EPSILON);
+ EXPECT_NEAR(pc.getX() * args.xScale + args.xOffset, motionEvent.getX(i), EPSILON);
+ EXPECT_NEAR(pc.getY() * args.yScale + args.yOffset, motionEvent.getY(i), EPSILON);
+ EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), motionEvent.getPressure(i));
+ EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_SIZE), motionEvent.getSize(i));
+ EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), motionEvent.getTouchMajor(i));
+ EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR), motionEvent.getTouchMinor(i));
+ EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR), motionEvent.getToolMajor(i));
+ EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR), motionEvent.getToolMinor(i));
+
+ // Calculate the orientation after scaling, keeping in mind that an orientation of 0 is
+ // "up", and the positive y direction is "down".
+ const float unscaledOrientation = pc.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION);
+ const float x = sinf(unscaledOrientation) * args.xScale;
+ const float y = -cosf(unscaledOrientation) * args.yScale;
+ EXPECT_EQ(atan2f(x, -y), motionEvent.getOrientation(i));
+ }
+}
+
+void publishMotionEvent(InputPublisher& publisher, const PublishMotionArgs& a) {
+ status_t status =
+ publisher.publishMotionEvent(a.seq, a.eventId, a.deviceId, a.source, a.displayId,
+ a.hmac, a.action, a.actionButton, a.flags, a.edgeFlags,
+ a.metaState, a.buttonState, a.classification, a.transform,
+ a.xPrecision, a.yPrecision, a.xCursorPosition,
+ a.yCursorPosition, a.rawTransform, a.downTime, a.eventTime,
+ a.pointerCount, a.pointerProperties.data(),
+ a.pointerCoords.data());
+ ASSERT_EQ(OK, status) << "publisher publishMotionEvent should return OK";
+}
+
+Result<InputPublisher::ConsumerResponse> receiveConsumerResponse(
+ InputPublisher& publisher, std::chrono::milliseconds timeout) {
+ const std::chrono::time_point start = std::chrono::steady_clock::now();
+
+ while (true) {
+ Result<InputPublisher::ConsumerResponse> result = publisher.receiveConsumerResponse();
+ if (result.ok()) {
+ return result;
+ }
+ const std::chrono::duration waited = std::chrono::steady_clock::now() - start;
+ if (waited > timeout) {
+ return result;
+ }
+ }
+}
+
+void verifyFinishedSignal(InputPublisher& publisher, uint32_t seq, nsecs_t publishTime) {
+ Result<InputPublisher::ConsumerResponse> result = receiveConsumerResponse(publisher, TIMEOUT);
+ ASSERT_TRUE(result.ok()) << "receiveConsumerResponse returned " << result.error().message();
+ ASSERT_TRUE(std::holds_alternative<InputPublisher::Finished>(*result));
+ const InputPublisher::Finished& finish = std::get<InputPublisher::Finished>(*result);
+ ASSERT_EQ(seq, finish.seq)
+ << "receiveConsumerResponse should have returned the original sequence number";
+ ASSERT_TRUE(finish.handled)
+ << "receiveConsumerResponse should have set handled to consumer's reply";
+ ASSERT_GE(finish.consumeTime, publishTime)
+ << "finished signal's consume time should be greater than publish time";
+}
+
+} // namespace
+
+class InputConsumerMessageHandler : public MessageHandler {
+public:
+ InputConsumerMessageHandler(std::function<void(const Message&)> function)
+ : mFunction(function) {}
+
+private:
+ void handleMessage(const Message& message) override { mFunction(message); }
+
+ std::function<void(const Message&)> mFunction;
+};
+
+class InputPublisherAndConsumerNoResamplingTest : public testing::Test,
+ public InputConsumerCallbacks {
+protected:
+ std::unique_ptr<InputChannel> mClientChannel;
+ std::unique_ptr<InputPublisher> mPublisher;
+ std::unique_ptr<InputConsumerNoResampling> mConsumer;
+
+ std::thread mLooperThread;
+ sp<Looper> mLooper = sp<Looper>::make(/*allowNonCallbacks=*/false);
+
+ // LOOPER CONTROL
+ // Set to false when you want the looper to exit
+ std::atomic<bool> mExitLooper = false;
+ std::mutex mLock;
+
+ // Used by test to notify looper that the value of "mLooperMayProceed" has changed
+ std::condition_variable mNotifyLooperMayProceed;
+ bool mLooperMayProceed GUARDED_BY(mLock){true};
+ // Used by looper to notify the test that it's about to block on "mLooperMayProceed" -> true
+ std::condition_variable mNotifyLooperWaiting;
+ bool mLooperIsBlocked GUARDED_BY(mLock){false};
+
+ std::condition_variable mNotifyConsumerDestroyed;
+ bool mConsumerDestroyed GUARDED_BY(mLock){false};
+
+ void runLooper() {
+ static constexpr int LOOP_INDEFINITELY = -1;
+ Looper::setForThread(mLooper);
+ // Loop forever -- this thread is dedicated to servicing the looper callbacks.
+ while (!mExitLooper) {
+ mLooper->pollOnce(/*timeoutMillis=*/LOOP_INDEFINITELY);
+ }
+ }
+
+ void SetUp() override {
+ std::unique_ptr<InputChannel> serverChannel;
+ status_t result =
+ InputChannel::openInputChannelPair("channel name", serverChannel, mClientChannel);
+ ASSERT_EQ(OK, result);
+
+ mPublisher = std::make_unique<InputPublisher>(std::move(serverChannel));
+ mMessageHandler = sp<InputConsumerMessageHandler>::make(
+ [this](const Message& message) { handleMessage(message); });
+ mLooperThread = std::thread([this] { runLooper(); });
+ sendMessage(LooperMessage::CREATE_CONSUMER);
+ }
+
+ void publishAndConsumeKeyEvent();
+ void publishAndConsumeMotionStream();
+ void publishAndConsumeMotionDown(nsecs_t downTime);
+ void publishAndConsumeBatchedMotionMove(nsecs_t downTime);
+ void publishAndConsumeFocusEvent();
+ void publishAndConsumeCaptureEvent();
+ void publishAndConsumeDragEvent();
+ void publishAndConsumeTouchModeEvent();
+ void publishAndConsumeMotionEvent(int32_t action, nsecs_t downTime,
+ const std::vector<Pointer>& pointers);
+ void TearDown() override {
+ // Destroy the consumer, flushing any of the pending ack's.
+ sendMessage(LooperMessage::DESTROY_CONSUMER);
+ {
+ std::unique_lock lock(mLock);
+ base::ScopedLockAssertion assumeLocked(mLock);
+ mNotifyConsumerDestroyed.wait(lock, [this] { return mConsumerDestroyed; });
+ }
+ // Stop the looper thread so that we can destroy the object.
+ mExitLooper = true;
+ mLooper->wake();
+ mLooperThread.join();
+ }
+
+protected:
+ // Interaction with the looper thread
+ enum class LooperMessage : int {
+ CALL_PROBABLY_HAS_INPUT,
+ CREATE_CONSUMER,
+ DESTROY_CONSUMER,
+ CALL_REPORT_TIMELINE,
+ BLOCK_LOOPER,
+ };
+ void sendMessage(LooperMessage message);
+ struct ReportTimelineArgs {
+ int32_t inputEventId;
+ nsecs_t gpuCompletedTime;
+ nsecs_t presentTime;
+ };
+ // The input to the function "InputConsumer::reportTimeline". Populated on the test thread and
+ // accessed on the looper thread.
+ BlockingQueue<ReportTimelineArgs> mReportTimelineArgs;
+ // The output of calling "InputConsumer::probablyHasInput()". Populated on the looper thread and
+ // accessed on the test thread.
+ BlockingQueue<bool> mProbablyHasInputResponses;
+
+private:
+ sp<MessageHandler> mMessageHandler;
+ void handleMessage(const Message& message);
+
+ static auto constexpr NO_EVENT_TIMEOUT = 10ms;
+ // The sequence number to use when publishing the next event
+ uint32_t mSeq = 1;
+
+ BlockingQueue<std::unique_ptr<KeyEvent>> mKeyEvents;
+ BlockingQueue<std::unique_ptr<MotionEvent>> mMotionEvents;
+ BlockingQueue<std::unique_ptr<FocusEvent>> mFocusEvents;
+ BlockingQueue<std::unique_ptr<CaptureEvent>> mCaptureEvents;
+ BlockingQueue<std::unique_ptr<DragEvent>> mDragEvents;
+ BlockingQueue<std::unique_ptr<TouchModeEvent>> mTouchModeEvents;
+
+ // InputConsumerCallbacks interface
+ void onKeyEvent(std::unique_ptr<KeyEvent> event, uint32_t seq) override {
+ mKeyEvents.push(std::move(event));
+ mConsumer->finishInputEvent(seq, true);
+ }
+ void onMotionEvent(std::unique_ptr<MotionEvent> event, uint32_t seq) override {
+ mMotionEvents.push(std::move(event));
+ mConsumer->finishInputEvent(seq, true);
+ }
+ void onBatchedInputEventPending(int32_t pendingBatchSource) override {
+ if (!mConsumer->probablyHasInput()) {
+ ADD_FAILURE() << "should deterministically have input because there is a batch";
+ }
+ mConsumer->consumeBatchedInputEvents(std::nullopt);
+ };
+ void onFocusEvent(std::unique_ptr<FocusEvent> event, uint32_t seq) override {
+ mFocusEvents.push(std::move(event));
+ mConsumer->finishInputEvent(seq, true);
+ };
+ void onCaptureEvent(std::unique_ptr<CaptureEvent> event, uint32_t seq) override {
+ mCaptureEvents.push(std::move(event));
+ mConsumer->finishInputEvent(seq, true);
+ };
+ void onDragEvent(std::unique_ptr<DragEvent> event, uint32_t seq) override {
+ mDragEvents.push(std::move(event));
+ mConsumer->finishInputEvent(seq, true);
+ }
+ void onTouchModeEvent(std::unique_ptr<TouchModeEvent> event, uint32_t seq) override {
+ mTouchModeEvents.push(std::move(event));
+ mConsumer->finishInputEvent(seq, true);
+ };
+};
+
+void InputPublisherAndConsumerNoResamplingTest::sendMessage(LooperMessage message) {
+ Message msg{ftl::to_underlying(message)};
+ mLooper->sendMessage(mMessageHandler, msg);
+}
+
+void InputPublisherAndConsumerNoResamplingTest::handleMessage(const Message& message) {
+ switch (static_cast<LooperMessage>(message.what)) {
+ case LooperMessage::CALL_PROBABLY_HAS_INPUT: {
+ mProbablyHasInputResponses.push(mConsumer->probablyHasInput());
+ break;
+ }
+ case LooperMessage::CREATE_CONSUMER: {
+ mConsumer = std::make_unique<InputConsumerNoResampling>(std::move(mClientChannel),
+ mLooper, *this);
+ break;
+ }
+ case LooperMessage::DESTROY_CONSUMER: {
+ mConsumer = nullptr;
+ {
+ std::unique_lock lock(mLock);
+ mConsumerDestroyed = true;
+ }
+ mNotifyConsumerDestroyed.notify_all();
+ break;
+ }
+ case LooperMessage::CALL_REPORT_TIMELINE: {
+ std::optional<ReportTimelineArgs> args = mReportTimelineArgs.pop();
+ if (!args.has_value()) {
+ ADD_FAILURE() << "Couldn't get the 'reportTimeline' args in time";
+ return;
+ }
+ mConsumer->reportTimeline(args->inputEventId, args->gpuCompletedTime,
+ args->presentTime);
+ break;
+ }
+ case LooperMessage::BLOCK_LOOPER: {
+ {
+ std::unique_lock lock(mLock);
+ mLooperIsBlocked = true;
+ }
+ mNotifyLooperWaiting.notify_all();
+
+ {
+ std::unique_lock lock(mLock);
+ base::ScopedLockAssertion assumeLocked(mLock);
+ mNotifyLooperMayProceed.wait(lock, [this] { return mLooperMayProceed; });
+ }
+
+ {
+ std::unique_lock lock(mLock);
+ mLooperIsBlocked = false;
+ }
+ mNotifyLooperWaiting.notify_all();
+ break;
+ }
+ }
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeKeyEvent() {
+ status_t status;
+
+ const uint32_t seq = mSeq++;
+ int32_t eventId = InputEvent::nextId();
+ constexpr int32_t deviceId = 1;
+ constexpr uint32_t source = AINPUT_SOURCE_KEYBOARD;
+ constexpr int32_t displayId = ADISPLAY_ID_DEFAULT;
+ constexpr std::array<uint8_t, 32> hmac = {31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21,
+ 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10,
+ 9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
+ constexpr int32_t action = AKEY_EVENT_ACTION_DOWN;
+ constexpr int32_t flags = AKEY_EVENT_FLAG_FROM_SYSTEM;
+ constexpr int32_t keyCode = AKEYCODE_ENTER;
+ constexpr int32_t scanCode = 13;
+ constexpr int32_t metaState = AMETA_ALT_LEFT_ON | AMETA_ALT_ON;
+ constexpr int32_t repeatCount = 1;
+ constexpr nsecs_t downTime = 3;
+ constexpr nsecs_t eventTime = 4;
+ const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+ status = mPublisher->publishKeyEvent(seq, eventId, deviceId, source, displayId, hmac, action,
+ flags, keyCode, scanCode, metaState, repeatCount, downTime,
+ eventTime);
+ ASSERT_EQ(OK, status) << "publisher publishKeyEvent should return OK";
+
+ std::optional<std::unique_ptr<KeyEvent>> optKeyEvent = mKeyEvents.popWithTimeout(TIMEOUT);
+ ASSERT_TRUE(optKeyEvent.has_value()) << "consumer should have returned non-NULL event";
+ std::unique_ptr<KeyEvent> keyEvent = std::move(*optKeyEvent);
+
+ sendMessage(LooperMessage::CALL_PROBABLY_HAS_INPUT);
+ std::optional<bool> probablyHasInput = mProbablyHasInputResponses.popWithTimeout(TIMEOUT);
+ ASSERT_TRUE(probablyHasInput.has_value());
+ ASSERT_FALSE(probablyHasInput.value()) << "no events should be waiting after being consumed";
+
+ EXPECT_EQ(eventId, keyEvent->getId());
+ EXPECT_EQ(deviceId, keyEvent->getDeviceId());
+ EXPECT_EQ(source, keyEvent->getSource());
+ EXPECT_EQ(displayId, keyEvent->getDisplayId());
+ EXPECT_EQ(hmac, keyEvent->getHmac());
+ EXPECT_EQ(action, keyEvent->getAction());
+ EXPECT_EQ(flags, keyEvent->getFlags());
+ EXPECT_EQ(keyCode, keyEvent->getKeyCode());
+ EXPECT_EQ(scanCode, keyEvent->getScanCode());
+ EXPECT_EQ(metaState, keyEvent->getMetaState());
+ EXPECT_EQ(repeatCount, keyEvent->getRepeatCount());
+ EXPECT_EQ(downTime, keyEvent->getDownTime());
+ EXPECT_EQ(eventTime, keyEvent->getEventTime());
+
+ verifyFinishedSignal(*mPublisher, seq, publishTime);
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeMotionStream() {
+ const nsecs_t downTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+ publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_DOWN, downTime,
+ {Pointer{.id = 0, .x = 20, .y = 30}});
+
+ publishAndConsumeMotionEvent(POINTER_1_DOWN, downTime,
+ {Pointer{.id = 0, .x = 20, .y = 30},
+ Pointer{.id = 1, .x = 200, .y = 300}});
+
+ publishAndConsumeMotionEvent(POINTER_2_DOWN, downTime,
+ {Pointer{.id = 0, .x = 20, .y = 30},
+ Pointer{.id = 1, .x = 200, .y = 300},
+ Pointer{.id = 2, .x = 300, .y = 400}});
+
+ // Provide a consistent input stream - cancel the gesture that was started above
+ publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_CANCEL, downTime,
+ {Pointer{.id = 0, .x = 20, .y = 30},
+ Pointer{.id = 1, .x = 200, .y = 300},
+ Pointer{.id = 2, .x = 300, .y = 400}});
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeMotionDown(nsecs_t downTime) {
+ publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_DOWN, downTime,
+ {Pointer{.id = 0, .x = 20, .y = 30}});
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeBatchedMotionMove(
+ nsecs_t downTime) {
+ uint32_t seq = mSeq++;
+ const std::vector<Pointer> pointers = {Pointer{.id = 0, .x = 20, .y = 30}};
+ PublishMotionArgs args(AMOTION_EVENT_ACTION_MOVE, downTime, pointers, seq);
+ const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+ // Block the looper thread, preventing it from being able to service any of the fd callbacks.
+
+ {
+ std::scoped_lock lock(mLock);
+ mLooperMayProceed = false;
+ }
+ sendMessage(LooperMessage::BLOCK_LOOPER);
+ {
+ std::unique_lock lock(mLock);
+ mNotifyLooperWaiting.wait(lock, [this] { return mLooperIsBlocked; });
+ }
+
+ publishMotionEvent(*mPublisher, args);
+
+ // Ensure no event arrives because the UI thread is blocked
+ std::optional<std::unique_ptr<MotionEvent>> noEvent =
+ mMotionEvents.popWithTimeout(NO_EVENT_TIMEOUT);
+ ASSERT_FALSE(noEvent.has_value()) << "Got unexpected event: " << *noEvent;
+
+ Result<InputPublisher::ConsumerResponse> result = mPublisher->receiveConsumerResponse();
+ ASSERT_FALSE(result.ok());
+ ASSERT_EQ(WOULD_BLOCK, result.error().code());
+
+ // We shouldn't be calling mConsumer on the UI thread, but in this situation, the looper
+ // thread is locked, so this should be safe to do.
+ ASSERT_TRUE(mConsumer->probablyHasInput())
+ << "should deterministically have input because there is a batch";
+
+ // Now, unblock the looper thread, so that the event can arrive.
+ {
+ std::scoped_lock lock(mLock);
+ mLooperMayProceed = true;
+ }
+ mNotifyLooperMayProceed.notify_all();
+
+ std::optional<std::unique_ptr<MotionEvent>> optMotion = mMotionEvents.popWithTimeout(TIMEOUT);
+ ASSERT_TRUE(optMotion.has_value());
+ std::unique_ptr<MotionEvent> motion = std::move(*optMotion);
+ ASSERT_EQ(ACTION_MOVE, motion->getAction());
+
+ verifyFinishedSignal(*mPublisher, seq, publishTime);
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeMotionEvent(
+ int32_t action, nsecs_t downTime, const std::vector<Pointer>& pointers) {
+ uint32_t seq = mSeq++;
+ PublishMotionArgs args(action, downTime, pointers, seq);
+ nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ publishMotionEvent(*mPublisher, args);
+
+ std::optional<std::unique_ptr<MotionEvent>> optMotion = mMotionEvents.popWithTimeout(TIMEOUT);
+ ASSERT_TRUE(optMotion.has_value());
+ std::unique_ptr<MotionEvent> event = std::move(*optMotion);
+
+ verifyArgsEqualToEvent(args, *event);
+
+ verifyFinishedSignal(*mPublisher, seq, publishTime);
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeFocusEvent() {
+ status_t status;
+
+ constexpr uint32_t seq = 15;
+ int32_t eventId = InputEvent::nextId();
+ constexpr bool hasFocus = true;
+ const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+ status = mPublisher->publishFocusEvent(seq, eventId, hasFocus);
+ ASSERT_EQ(OK, status) << "publisher publishFocusEvent should return OK";
+
+ std::optional<std::unique_ptr<FocusEvent>> optFocusEvent = mFocusEvents.popWithTimeout(TIMEOUT);
+ ASSERT_TRUE(optFocusEvent.has_value()) << "consumer should have returned non-NULL event";
+ std::unique_ptr<FocusEvent> focusEvent = std::move(*optFocusEvent);
+ EXPECT_EQ(eventId, focusEvent->getId());
+ EXPECT_EQ(hasFocus, focusEvent->getHasFocus());
+
+ verifyFinishedSignal(*mPublisher, seq, publishTime);
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeCaptureEvent() {
+ status_t status;
+
+ constexpr uint32_t seq = 42;
+ int32_t eventId = InputEvent::nextId();
+ constexpr bool captureEnabled = true;
+ const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+ status = mPublisher->publishCaptureEvent(seq, eventId, captureEnabled);
+ ASSERT_EQ(OK, status) << "publisher publishCaptureEvent should return OK";
+
+ std::optional<std::unique_ptr<CaptureEvent>> optEvent = mCaptureEvents.popWithTimeout(TIMEOUT);
+ ASSERT_TRUE(optEvent.has_value()) << "consumer should have returned non-NULL event";
+ std::unique_ptr<CaptureEvent> event = std::move(*optEvent);
+
+ const CaptureEvent& captureEvent = *event;
+ EXPECT_EQ(eventId, captureEvent.getId());
+ EXPECT_EQ(captureEnabled, captureEvent.getPointerCaptureEnabled());
+
+ verifyFinishedSignal(*mPublisher, seq, publishTime);
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeDragEvent() {
+ status_t status;
+
+ constexpr uint32_t seq = 15;
+ int32_t eventId = InputEvent::nextId();
+ constexpr bool isExiting = false;
+ constexpr float x = 10;
+ constexpr float y = 15;
+ const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+ status = mPublisher->publishDragEvent(seq, eventId, x, y, isExiting);
+ ASSERT_EQ(OK, status) << "publisher publishDragEvent should return OK";
+
+ std::optional<std::unique_ptr<DragEvent>> optEvent = mDragEvents.popWithTimeout(TIMEOUT);
+ ASSERT_TRUE(optEvent.has_value()) << "consumer should have returned non-NULL event";
+ std::unique_ptr<DragEvent> event = std::move(*optEvent);
+
+ const DragEvent& dragEvent = *event;
+ EXPECT_EQ(eventId, dragEvent.getId());
+ EXPECT_EQ(isExiting, dragEvent.isExiting());
+ EXPECT_EQ(x, dragEvent.getX());
+ EXPECT_EQ(y, dragEvent.getY());
+
+ verifyFinishedSignal(*mPublisher, seq, publishTime);
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeTouchModeEvent() {
+ status_t status;
+
+ constexpr uint32_t seq = 15;
+ int32_t eventId = InputEvent::nextId();
+ constexpr bool touchModeEnabled = true;
+ const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+ status = mPublisher->publishTouchModeEvent(seq, eventId, touchModeEnabled);
+ ASSERT_EQ(OK, status) << "publisher publishTouchModeEvent should return OK";
+
+ std::optional<std::unique_ptr<TouchModeEvent>> optEvent =
+ mTouchModeEvents.popWithTimeout(TIMEOUT);
+ ASSERT_TRUE(optEvent.has_value());
+ std::unique_ptr<TouchModeEvent> event = std::move(*optEvent);
+
+ const TouchModeEvent& touchModeEvent = *event;
+ EXPECT_EQ(eventId, touchModeEvent.getId());
+ EXPECT_EQ(touchModeEnabled, touchModeEvent.isInTouchMode());
+
+ verifyFinishedSignal(*mPublisher, seq, publishTime);
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, SendTimeline) {
+ const int32_t inputEventId = 20;
+ const nsecs_t gpuCompletedTime = 30;
+ const nsecs_t presentTime = 40;
+
+ mReportTimelineArgs.emplace(inputEventId, gpuCompletedTime, presentTime);
+ sendMessage(LooperMessage::CALL_REPORT_TIMELINE);
+
+ Result<InputPublisher::ConsumerResponse> result = receiveConsumerResponse(*mPublisher, TIMEOUT);
+ ASSERT_TRUE(result.ok()) << "receiveConsumerResponse should return OK";
+ ASSERT_TRUE(std::holds_alternative<InputPublisher::Timeline>(*result));
+ const InputPublisher::Timeline& timeline = std::get<InputPublisher::Timeline>(*result);
+ ASSERT_EQ(inputEventId, timeline.inputEventId);
+ ASSERT_EQ(gpuCompletedTime, timeline.graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME]);
+ ASSERT_EQ(presentTime, timeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME]);
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishKeyEvent_EndToEnd) {
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishMotionEvent_EndToEnd) {
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeMotionStream());
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishMotionMoveEvent_EndToEnd) {
+ // Publish a DOWN event before MOVE to pass the InputVerifier checks.
+ const nsecs_t downTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeMotionDown(downTime));
+
+ // Publish the MOVE event and check expectations.
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeBatchedMotionMove(downTime));
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishFocusEvent_EndToEnd) {
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeFocusEvent());
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishCaptureEvent_EndToEnd) {
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeCaptureEvent());
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishDragEvent_EndToEnd) {
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeDragEvent());
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishTouchModeEvent_EndToEnd) {
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeTouchModeEvent());
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest,
+ PublishMotionEvent_WhenSequenceNumberIsZero_ReturnsError) {
+ status_t status;
+ const size_t pointerCount = 1;
+ PointerProperties pointerProperties[pointerCount];
+ PointerCoords pointerCoords[pointerCount];
+ for (size_t i = 0; i < pointerCount; i++) {
+ pointerProperties[i].clear();
+ pointerCoords[i].clear();
+ }
+
+ ui::Transform identityTransform;
+ status =
+ mPublisher->publishMotionEvent(0, InputEvent::nextId(), 0, 0, 0, INVALID_HMAC, 0, 0, 0,
+ 0, 0, 0, MotionClassification::NONE, identityTransform,
+ 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+ AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform,
+ 0, 0, pointerCount, pointerProperties, pointerCoords);
+ ASSERT_EQ(BAD_VALUE, status) << "publisher publishMotionEvent should return BAD_VALUE";
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest,
+ PublishMotionEvent_WhenPointerCountLessThan1_ReturnsError) {
+ status_t status;
+ const size_t pointerCount = 0;
+ PointerProperties pointerProperties[pointerCount];
+ PointerCoords pointerCoords[pointerCount];
+
+ ui::Transform identityTransform;
+ status =
+ mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, 0, INVALID_HMAC, 0, 0, 0,
+ 0, 0, 0, MotionClassification::NONE, identityTransform,
+ 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+ AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform,
+ 0, 0, pointerCount, pointerProperties, pointerCoords);
+ ASSERT_EQ(BAD_VALUE, status) << "publisher publishMotionEvent should return BAD_VALUE";
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest,
+ PublishMotionEvent_WhenPointerCountGreaterThanMax_ReturnsError) {
+ status_t status;
+ const size_t pointerCount = MAX_POINTERS + 1;
+ PointerProperties pointerProperties[pointerCount];
+ PointerCoords pointerCoords[pointerCount];
+ for (size_t i = 0; i < pointerCount; i++) {
+ pointerProperties[i].clear();
+ pointerCoords[i].clear();
+ }
+
+ ui::Transform identityTransform;
+ status =
+ mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, 0, INVALID_HMAC, 0, 0, 0,
+ 0, 0, 0, MotionClassification::NONE, identityTransform,
+ 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+ AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform,
+ 0, 0, pointerCount, pointerProperties, pointerCoords);
+ ASSERT_EQ(BAD_VALUE, status) << "publisher publishMotionEvent should return BAD_VALUE";
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishMultipleEvents_EndToEnd) {
+ const nsecs_t downTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+ publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_DOWN, downTime,
+ {Pointer{.id = 0, .x = 20, .y = 30}});
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
+ publishAndConsumeMotionEvent(POINTER_1_DOWN, downTime,
+ {Pointer{.id = 0, .x = 20, .y = 30},
+ Pointer{.id = 1, .x = 200, .y = 300}});
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeFocusEvent());
+ publishAndConsumeMotionEvent(POINTER_2_DOWN, downTime,
+ {Pointer{.id = 0, .x = 20, .y = 30},
+ Pointer{.id = 1, .x = 200, .y = 300},
+ Pointer{.id = 2, .x = 200, .y = 300}});
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeCaptureEvent());
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeDragEvent());
+ // Provide a consistent input stream - cancel the gesture that was started above
+ publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_CANCEL, downTime,
+ {Pointer{.id = 0, .x = 20, .y = 30},
+ Pointer{.id = 1, .x = 200, .y = 300},
+ Pointer{.id = 2, .x = 200, .y = 300}});
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeTouchModeEvent());
+}
+
+} // namespace android
diff --git a/libs/input/tests/InputPublisherAndConsumer_test.cpp b/libs/input/tests/InputPublisherAndConsumer_test.cpp
index 30f9a42..332831f 100644
--- a/libs/input/tests/InputPublisherAndConsumer_test.cpp
+++ b/libs/input/tests/InputPublisherAndConsumer_test.cpp
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-#include "TestHelpers.h"
-
#include <attestation/HmacKeyManager.h>
#include <gtest/gtest.h>
#include <gui/constants.h>
+#include <input/InputConsumer.h>
#include <input/InputTransport.h>
using android::base::Result;
diff --git a/libs/input/tests/TestHelpers.h b/libs/input/tests/TestHelpers.h
deleted file mode 100644
index 343d81f..0000000
--- a/libs/input/tests/TestHelpers.h
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef TESTHELPERS_H
-#define TESTHELPERS_H
-
-#include <unistd.h>
-
-#include <utils/threads.h>
-
-namespace android {
-
-class Pipe {
-public:
- int sendFd;
- int receiveFd;
-
- Pipe() {
- int fds[2];
- ::pipe(fds);
-
- receiveFd = fds[0];
- sendFd = fds[1];
- }
-
- ~Pipe() {
- if (sendFd != -1) {
- ::close(sendFd);
- }
-
- if (receiveFd != -1) {
- ::close(receiveFd);
- }
- }
-
- status_t writeSignal() {
- ssize_t nWritten = ::write(sendFd, "*", 1);
- return nWritten == 1 ? 0 : -errno;
- }
-
- status_t readSignal() {
- char buf[1];
- ssize_t nRead = ::read(receiveFd, buf, 1);
- return nRead == 1 ? 0 : nRead == 0 ? -EPIPE : -errno;
- }
-};
-
-class DelayedTask : public Thread {
- int mDelayMillis;
-
-public:
- explicit DelayedTask(int delayMillis) : mDelayMillis(delayMillis) { }
-
-protected:
- virtual ~DelayedTask() { }
-
- virtual void doTask() = 0;
-
- virtual bool threadLoop() {
- usleep(mDelayMillis * 1000);
- doTask();
- return false;
- }
-};
-
-} // namespace android
-
-#endif // TESTHELPERS_H
diff --git a/libs/input/tests/TouchResampling_test.cpp b/libs/input/tests/TouchResampling_test.cpp
index 1cb7f7b..6e23d4e 100644
--- a/libs/input/tests/TouchResampling_test.cpp
+++ b/libs/input/tests/TouchResampling_test.cpp
@@ -14,13 +14,12 @@
* limitations under the License.
*/
-#include "TestHelpers.h"
-
#include <chrono>
#include <vector>
#include <attestation/HmacKeyManager.h>
#include <gtest/gtest.h>
+#include <input/InputConsumer.h>
#include <input/InputTransport.h>
using namespace std::chrono_literals;
diff --git a/libs/nativedisplay/AChoreographer.cpp b/libs/nativedisplay/AChoreographer.cpp
index 8f005a5..bed31e2 100644
--- a/libs/nativedisplay/AChoreographer.cpp
+++ b/libs/nativedisplay/AChoreographer.cpp
@@ -148,29 +148,31 @@
void AChoreographer_postFrameCallback(AChoreographer* choreographer,
AChoreographer_frameCallback callback, void* data) {
AChoreographer_to_Choreographer(choreographer)
- ->postFrameCallbackDelayed(callback, nullptr, nullptr, data, 0);
+ ->postFrameCallbackDelayed(callback, nullptr, nullptr, data, 0, CALLBACK_ANIMATION);
}
void AChoreographer_postFrameCallbackDelayed(AChoreographer* choreographer,
AChoreographer_frameCallback callback, void* data,
long delayMillis) {
AChoreographer_to_Choreographer(choreographer)
- ->postFrameCallbackDelayed(callback, nullptr, nullptr, data, ms2ns(delayMillis));
+ ->postFrameCallbackDelayed(callback, nullptr, nullptr, data, ms2ns(delayMillis),
+ CALLBACK_ANIMATION);
}
void AChoreographer_postVsyncCallback(AChoreographer* choreographer,
AChoreographer_vsyncCallback callback, void* data) {
AChoreographer_to_Choreographer(choreographer)
- ->postFrameCallbackDelayed(nullptr, nullptr, callback, data, 0);
+ ->postFrameCallbackDelayed(nullptr, nullptr, callback, data, 0, CALLBACK_ANIMATION);
}
void AChoreographer_postFrameCallback64(AChoreographer* choreographer,
AChoreographer_frameCallback64 callback, void* data) {
AChoreographer_to_Choreographer(choreographer)
- ->postFrameCallbackDelayed(nullptr, callback, nullptr, data, 0);
+ ->postFrameCallbackDelayed(nullptr, callback, nullptr, data, 0, CALLBACK_ANIMATION);
}
void AChoreographer_postFrameCallbackDelayed64(AChoreographer* choreographer,
AChoreographer_frameCallback64 callback, void* data,
uint32_t delayMillis) {
AChoreographer_to_Choreographer(choreographer)
- ->postFrameCallbackDelayed(nullptr, callback, nullptr, data, ms2ns(delayMillis));
+ ->postFrameCallbackDelayed(nullptr, callback, nullptr, data, ms2ns(delayMillis),
+ CALLBACK_ANIMATION);
}
void AChoreographer_registerRefreshRateCallback(AChoreographer* choreographer,
AChoreographer_refreshRateCallback callback,
diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp
index b501d40..39902b1 100644
--- a/libs/renderengine/Android.bp
+++ b/libs/renderengine/Android.bp
@@ -87,6 +87,8 @@
"skia/SkiaRenderEngine.cpp",
"skia/SkiaGLRenderEngine.cpp",
"skia/SkiaVkRenderEngine.cpp",
+ "skia/VulkanInterface.cpp",
+ "skia/compat/GaneshGpuContext.cpp",
"skia/debug/CaptureTimer.cpp",
"skia/debug/CommonPool.cpp",
"skia/debug/SkiaCapture.cpp",
diff --git a/libs/renderengine/skia/AutoBackendTexture.cpp b/libs/renderengine/skia/AutoBackendTexture.cpp
index ee95e59..02e7337 100644
--- a/libs/renderengine/skia/AutoBackendTexture.cpp
+++ b/libs/renderengine/skia/AutoBackendTexture.cpp
@@ -21,12 +21,12 @@
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
#include <SkImage.h>
+#include <android/hardware_buffer.h>
#include <include/gpu/ganesh/SkImageGanesh.h>
#include <include/gpu/ganesh/SkSurfaceGanesh.h>
#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
#include <include/gpu/ganesh/vk/GrVkBackendSurface.h>
#include <include/gpu/vk/GrVkTypes.h>
-#include <android/hardware_buffer.h>
#include "ColorSpaces.h"
#include "log/log_main.h"
#include "utils/Trace.h"
@@ -35,47 +35,37 @@
namespace renderengine {
namespace skia {
-AutoBackendTexture::AutoBackendTexture(GrDirectContext* context, AHardwareBuffer* buffer,
+AutoBackendTexture::AutoBackendTexture(SkiaGpuContext* context, AHardwareBuffer* buffer,
bool isOutputBuffer, CleanupManager& cleanupMgr)
- : mCleanupMgr(cleanupMgr), mIsOutputBuffer(isOutputBuffer) {
+ : mGrContext(context->grDirectContext()),
+ mCleanupMgr(cleanupMgr),
+ mIsOutputBuffer(isOutputBuffer) {
ATRACE_CALL();
+
AHardwareBuffer_Desc desc;
AHardwareBuffer_describe(buffer, &desc);
bool createProtectedImage = 0 != (desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT);
GrBackendFormat backendFormat;
- GrBackendApi backend = context->backend();
+ GrBackendApi backend = mGrContext->backend();
if (backend == GrBackendApi::kOpenGL) {
backendFormat =
- GrAHardwareBufferUtils::GetGLBackendFormat(context, desc.format, false);
+ GrAHardwareBufferUtils::GetGLBackendFormat(mGrContext.get(), desc.format, false);
mBackendTexture =
- GrAHardwareBufferUtils::MakeGLBackendTexture(context,
- buffer,
- desc.width,
- desc.height,
- &mDeleteProc,
- &mUpdateProc,
- &mImageCtx,
- createProtectedImage,
- backendFormat,
+ GrAHardwareBufferUtils::MakeGLBackendTexture(mGrContext.get(), buffer, desc.width,
+ desc.height, &mDeleteProc,
+ &mUpdateProc, &mImageCtx,
+ createProtectedImage, backendFormat,
isOutputBuffer);
} else if (backend == GrBackendApi::kVulkan) {
- backendFormat =
- GrAHardwareBufferUtils::GetVulkanBackendFormat(context,
- buffer,
- desc.format,
- false);
+ backendFormat = GrAHardwareBufferUtils::GetVulkanBackendFormat(mGrContext.get(), buffer,
+ desc.format, false);
mBackendTexture =
- GrAHardwareBufferUtils::MakeVulkanBackendTexture(context,
- buffer,
- desc.width,
- desc.height,
- &mDeleteProc,
- &mUpdateProc,
- &mImageCtx,
- createProtectedImage,
- backendFormat,
- isOutputBuffer);
+ GrAHardwareBufferUtils::MakeVulkanBackendTexture(mGrContext.get(), buffer,
+ desc.width, desc.height,
+ &mDeleteProc, &mUpdateProc,
+ &mImageCtx, createProtectedImage,
+ backendFormat, isOutputBuffer);
} else {
LOG_ALWAYS_FATAL("Unexpected backend %u", static_cast<unsigned>(backend));
}
@@ -158,12 +148,11 @@
}
}
-sk_sp<SkImage> AutoBackendTexture::makeImage(ui::Dataspace dataspace, SkAlphaType alphaType,
- GrDirectContext* context) {
+sk_sp<SkImage> AutoBackendTexture::makeImage(ui::Dataspace dataspace, SkAlphaType alphaType) {
ATRACE_CALL();
if (mBackendTexture.isValid()) {
- mUpdateProc(mImageCtx, context);
+ mUpdateProc(mImageCtx, mGrContext.get());
}
auto colorType = mColorType;
@@ -174,7 +163,7 @@
}
sk_sp<SkImage> image =
- SkImages::BorrowTextureFrom(context, mBackendTexture, kTopLeft_GrSurfaceOrigin,
+ SkImages::BorrowTextureFrom(mGrContext.get(), mBackendTexture, kTopLeft_GrSurfaceOrigin,
colorType, alphaType, toSkColorSpace(dataspace),
releaseImageProc, this);
if (image.get()) {
@@ -190,13 +179,12 @@
return mImage;
}
-sk_sp<SkSurface> AutoBackendTexture::getOrCreateSurface(ui::Dataspace dataspace,
- GrDirectContext* context) {
+sk_sp<SkSurface> AutoBackendTexture::getOrCreateSurface(ui::Dataspace dataspace) {
ATRACE_CALL();
LOG_ALWAYS_FATAL_IF(!mIsOutputBuffer, "You can't generate a SkSurface for a read-only texture");
if (!mSurface.get() || mDataspace != dataspace) {
sk_sp<SkSurface> surface =
- SkSurfaces::WrapBackendTexture(context, mBackendTexture,
+ SkSurfaces::WrapBackendTexture(mGrContext.get(), mBackendTexture,
kTopLeft_GrSurfaceOrigin, 0, mColorType,
toSkColorSpace(dataspace), nullptr,
releaseSurfaceProc, this);
diff --git a/libs/renderengine/skia/AutoBackendTexture.h b/libs/renderengine/skia/AutoBackendTexture.h
index 509ac40..1d5b565 100644
--- a/libs/renderengine/skia/AutoBackendTexture.h
+++ b/libs/renderengine/skia/AutoBackendTexture.h
@@ -24,6 +24,7 @@
#include <ui/GraphicTypes.h>
#include "android-base/macros.h"
+#include "compat/SkiaGpuContext.h"
#include <mutex>
#include <vector>
@@ -80,7 +81,7 @@
// of shared ownership with Skia objects, so we wrap it here instead.
class LocalRef {
public:
- LocalRef(GrDirectContext* context, AHardwareBuffer* buffer, bool isOutputBuffer,
+ LocalRef(SkiaGpuContext* context, AHardwareBuffer* buffer, bool isOutputBuffer,
CleanupManager& cleanupMgr) {
mTexture = new AutoBackendTexture(context, buffer, isOutputBuffer, cleanupMgr);
mTexture->ref();
@@ -95,14 +96,13 @@
// Makes a new SkImage from the texture content.
// As SkImages are immutable but buffer content is not, we create
// a new SkImage every time.
- sk_sp<SkImage> makeImage(ui::Dataspace dataspace, SkAlphaType alphaType,
- GrDirectContext* context) {
- return mTexture->makeImage(dataspace, alphaType, context);
+ sk_sp<SkImage> makeImage(ui::Dataspace dataspace, SkAlphaType alphaType) {
+ return mTexture->makeImage(dataspace, alphaType);
}
// Makes a new SkSurface from the texture content, if needed.
- sk_sp<SkSurface> getOrCreateSurface(ui::Dataspace dataspace, GrDirectContext* context) {
- return mTexture->getOrCreateSurface(dataspace, context);
+ sk_sp<SkSurface> getOrCreateSurface(ui::Dataspace dataspace) {
+ return mTexture->getOrCreateSurface(dataspace);
}
SkColorType colorType() const { return mTexture->mColorType; }
@@ -114,8 +114,10 @@
};
private:
+ DISALLOW_COPY_AND_ASSIGN(AutoBackendTexture);
+
// Creates a GrBackendTexture whose contents come from the provided buffer.
- AutoBackendTexture(GrDirectContext* context, AHardwareBuffer* buffer, bool isOutputBuffer,
+ AutoBackendTexture(SkiaGpuContext* context, AHardwareBuffer* buffer, bool isOutputBuffer,
CleanupManager& cleanupMgr);
// The only way to invoke dtor is with unref, when mUsageCount is 0.
@@ -130,24 +132,24 @@
// Makes a new SkImage from the texture content.
// As SkImages are immutable but buffer content is not, we create
// a new SkImage every time.
- sk_sp<SkImage> makeImage(ui::Dataspace dataspace, SkAlphaType alphaType,
- GrDirectContext* context);
+ sk_sp<SkImage> makeImage(ui::Dataspace dataspace, SkAlphaType alphaType);
// Makes a new SkSurface from the texture content, if needed.
- sk_sp<SkSurface> getOrCreateSurface(ui::Dataspace dataspace, GrDirectContext* context);
+ sk_sp<SkSurface> getOrCreateSurface(ui::Dataspace dataspace);
GrBackendTexture mBackendTexture;
GrAHardwareBufferUtils::DeleteImageProc mDeleteProc;
GrAHardwareBufferUtils::UpdateImageProc mUpdateProc;
GrAHardwareBufferUtils::TexImageCtx mImageCtx;
+ // TODO: b/293371537 - Graphite abstractions for ABT.
+ const sk_sp<GrDirectContext> mGrContext = nullptr;
CleanupManager& mCleanupMgr;
static void releaseSurfaceProc(SkSurface::ReleaseContext releaseContext);
static void releaseImageProc(SkImages::ReleaseContext releaseContext);
int mUsageCount = 0;
-
const bool mIsOutputBuffer;
sk_sp<SkImage> mImage = nullptr;
sk_sp<SkSurface> mSurface = nullptr;
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index fea4129..f10c98d 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -21,6 +21,8 @@
#include "SkiaGLRenderEngine.h"
+#include "compat/SkiaGpuContext.h"
+
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GrContextOptions.h>
@@ -207,7 +209,7 @@
std::unique_ptr<SkiaGLRenderEngine> engine(new SkiaGLRenderEngine(args, display, ctxt,
placeholder, protectedContext,
protectedPlaceholder));
- engine->ensureGrContextsCreated();
+ engine->ensureContextsCreated();
ALOGI("OpenGL ES informations:");
ALOGI("vendor : %s", extensions.getVendor());
@@ -295,9 +297,7 @@
eglReleaseThread();
}
-SkiaRenderEngine::Contexts SkiaGLRenderEngine::createDirectContexts(
- const GrContextOptions& options) {
-
+SkiaRenderEngine::Contexts SkiaGLRenderEngine::createContexts() {
LOG_ALWAYS_FATAL_IF(isProtected(),
"Cannot setup contexts while already in protected mode");
@@ -306,10 +306,10 @@
LOG_ALWAYS_FATAL_IF(!glInterface.get(), "GrGLMakeNativeInterface() failed");
SkiaRenderEngine::Contexts contexts;
- contexts.first = GrDirectContexts::MakeGL(glInterface, options);
+ contexts.first = SkiaGpuContext::MakeGL_Ganesh(glInterface, mSkSLCacheMonitor);
if (supportsProtectedContentImpl()) {
useProtectedContextImpl(GrProtected::kYes);
- contexts.second = GrDirectContexts::MakeGL(glInterface, options);
+ contexts.second = SkiaGpuContext::MakeGL_Ganesh(glInterface, mSkSLCacheMonitor);
useProtectedContextImpl(GrProtected::kNo);
}
@@ -330,14 +330,14 @@
return eglMakeCurrent(mEGLDisplay, surface, surface, context) == EGL_TRUE;
}
-void SkiaGLRenderEngine::waitFence(GrDirectContext*, base::borrowed_fd fenceFd) {
+void SkiaGLRenderEngine::waitFence(SkiaGpuContext*, base::borrowed_fd fenceFd) {
if (fenceFd.get() >= 0 && !waitGpuFence(fenceFd)) {
ATRACE_NAME("SkiaGLRenderEngine::waitFence");
sync_wait(fenceFd.get(), -1);
}
}
-base::unique_fd SkiaGLRenderEngine::flushAndSubmit(GrDirectContext* grContext) {
+base::unique_fd SkiaGLRenderEngine::flushAndSubmit(SkiaGpuContext* context) {
base::unique_fd drawFence = flush();
bool requireSync = drawFence.get() < 0;
@@ -346,8 +346,8 @@
} else {
ATRACE_BEGIN("Submit(sync=false)");
}
- bool success = grContext->submit(requireSync ? GrSyncCpu::kYes :
- GrSyncCpu::kNo);
+ bool success =
+ context->grDirectContext()->submit(requireSync ? GrSyncCpu::kYes : GrSyncCpu::kNo);
ATRACE_END();
if (!success) {
ALOGE("Failed to flush RenderEngine commands");
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.h b/libs/renderengine/skia/SkiaGLRenderEngine.h
index af33110..2e22478 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.h
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.h
@@ -59,11 +59,11 @@
protected:
// Implementations of abstract SkiaRenderEngine functions specific to
// rendering backend
- virtual SkiaRenderEngine::Contexts createDirectContexts(const GrContextOptions& options);
+ virtual SkiaRenderEngine::Contexts createContexts();
bool supportsProtectedContentImpl() const override;
bool useProtectedContextImpl(GrProtected isProtected) override;
- void waitFence(GrDirectContext* grContext, base::borrowed_fd fenceFd) override;
- base::unique_fd flushAndSubmit(GrDirectContext* context) override;
+ void waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) override;
+ base::unique_fd flushAndSubmit(SkiaGpuContext* context) override;
void appendBackendSpecificInfoToDump(std::string& result) override;
private:
diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
index 6e393f0..27daeba 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -74,6 +74,7 @@
#include "Cache.h"
#include "ColorSpaces.h"
+#include "compat/SkiaGpuContext.h"
#include "filters/BlurFilter.h"
#include "filters/GaussianBlurFilter.h"
#include "filters/KawaseBlurFilter.h"
@@ -290,14 +291,12 @@
delete mBlurFilter;
}
- if (mGrContext) {
- mGrContext->flushAndSubmit(GrSyncCpu::kYes);
- mGrContext->abandonContext();
+ if (mContext) {
+ mContext->finishRenderingAndAbandonContext();
}
- if (mProtectedGrContext) {
- mProtectedGrContext->flushAndSubmit(GrSyncCpu::kYes);
- mProtectedGrContext->abandonContext();
+ if (mProtectedContext) {
+ mProtectedContext->finishRenderingAndAbandonContext();
}
}
@@ -308,24 +307,24 @@
}
// release any scratch resources before switching into a new mode
- if (getActiveGrContext()) {
- getActiveGrContext()->purgeUnlockedResources(GrPurgeResourceOptions::kScratchResourcesOnly);
+ if (getActiveContext()) {
+ getActiveContext()->purgeUnlockedScratchResources();
}
// Backend-specific way to switch to protected context
if (useProtectedContextImpl(
useProtectedContext ? GrProtected::kYes : GrProtected::kNo)) {
mInProtectedContext = useProtectedContext;
- // given that we are sharing the same thread between two GrContexts we need to
+ // given that we are sharing the same thread between two contexts we need to
// make sure that the thread state is reset when switching between the two.
- if (getActiveGrContext()) {
- getActiveGrContext()->resetContext();
+ if (getActiveContext()) {
+ getActiveContext()->resetContextIfApplicable();
}
}
}
-GrDirectContext* SkiaRenderEngine::getActiveGrContext() {
- return mInProtectedContext ? mProtectedGrContext.get() : mGrContext.get();
+SkiaGpuContext* SkiaRenderEngine::getActiveContext() {
+ return mInProtectedContext ? mProtectedContext.get() : mContext.get();
}
static float toDegrees(uint32_t transform) {
@@ -374,17 +373,12 @@
sourceTransfer != destTransfer;
}
-void SkiaRenderEngine::ensureGrContextsCreated() {
- if (mGrContext) {
+void SkiaRenderEngine::ensureContextsCreated() {
+ if (mContext) {
return;
}
- GrContextOptions options;
- options.fDisableDriverCorrectnessWorkarounds = true;
- options.fDisableDistanceFieldPaths = true;
- options.fReducedShaderVariations = true;
- options.fPersistentCache = &mSkSLCacheMonitor;
- std::tie(mGrContext, mProtectedGrContext) = createDirectContexts(options);
+ std::tie(mContext, mProtectedContext) = createContexts();
}
void SkiaRenderEngine::mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer,
@@ -413,7 +407,7 @@
// switch back after the buffer is cached). However, for non-protected content we can bind
// the texture in either GL context because they are initialized with the same share_context
// which allows the texture state to be shared between them.
- auto grContext = getActiveGrContext();
+ auto context = getActiveContext();
auto& cache = mTextureCache;
std::lock_guard<std::mutex> lock(mRenderingMutex);
@@ -424,8 +418,7 @@
isRenderable = buffer->getUsage() & GRALLOC_USAGE_HW_RENDER;
}
std::shared_ptr<AutoBackendTexture::LocalRef> imageTextureRef =
- std::make_shared<AutoBackendTexture::LocalRef>(grContext,
- buffer->toAHardwareBuffer(),
+ std::make_shared<AutoBackendTexture::LocalRef>(context, buffer->toAHardwareBuffer(),
isRenderable, mTextureCleanupMgr);
cache.insert({buffer->getId(), imageTextureRef});
}
@@ -477,7 +470,7 @@
return it->second;
}
}
- return std::make_shared<AutoBackendTexture::LocalRef>(getActiveGrContext(),
+ return std::make_shared<AutoBackendTexture::LocalRef>(getActiveContext(),
buffer->toAHardwareBuffer(),
isOutputBuffer, mTextureCleanupMgr);
}
@@ -667,8 +660,8 @@
validateOutputBufferUsage(buffer->getBuffer());
- auto grContext = getActiveGrContext();
- LOG_ALWAYS_FATAL_IF(grContext->abandoned(), "GrContext is abandoned/device lost at start of %s",
+ auto context = getActiveContext();
+ LOG_ALWAYS_FATAL_IF(context->isAbandoned(), "Context is abandoned/device lost at start of %s",
__func__);
// any AutoBackendTexture deletions will now be deferred until cleanupPostRender is called
@@ -677,10 +670,9 @@
auto surfaceTextureRef = getOrCreateBackendTexture(buffer->getBuffer(), true);
// wait on the buffer to be ready to use prior to using it
- waitFence(grContext, bufferFence);
+ waitFence(context, bufferFence);
- sk_sp<SkSurface> dstSurface =
- surfaceTextureRef->getOrCreateSurface(display.outputDataspace, grContext);
+ sk_sp<SkSurface> dstSurface = surfaceTextureRef->getOrCreateSurface(display.outputDataspace);
SkCanvas* dstCanvas = mCapture->tryCapture(dstSurface.get());
if (dstCanvas == nullptr) {
@@ -845,7 +837,7 @@
if (blurRect.width() > 0 && blurRect.height() > 0) {
if (layer.backgroundBlurRadius > 0) {
ATRACE_NAME("BackgroundBlur");
- auto blurredImage = mBlurFilter->generate(grContext, layer.backgroundBlurRadius,
+ auto blurredImage = mBlurFilter->generate(context, layer.backgroundBlurRadius,
blurInput, blurRect);
cachedBlurs[layer.backgroundBlurRadius] = blurredImage;
@@ -859,7 +851,7 @@
if (cachedBlurs[region.blurRadius] == nullptr) {
ATRACE_NAME("BlurRegion");
cachedBlurs[region.blurRadius] =
- mBlurFilter->generate(grContext, region.blurRadius, blurInput,
+ mBlurFilter->generate(context, region.blurRadius, blurInput,
blurRect);
}
@@ -948,7 +940,7 @@
// if the layer's buffer has a fence, then we must must respect the fence prior to using
// the buffer.
if (layer.source.buffer.fence != nullptr) {
- waitFence(grContext, layer.source.buffer.fence->get());
+ waitFence(context, layer.source.buffer.fence->get());
}
// isOpaque means we need to ignore the alpha in the image,
@@ -972,7 +964,7 @@
: item.isOpaque ? kOpaque_SkAlphaType
: item.usePremultipliedAlpha ? kPremul_SkAlphaType
: kUnpremul_SkAlphaType;
- sk_sp<SkImage> image = imageTextureRef->makeImage(layerDataspace, alphaType, grContext);
+ sk_sp<SkImage> image = imageTextureRef->makeImage(layerDataspace, alphaType);
auto texMatrix = getSkM44(item.textureTransform).asM33();
// textureTansform was intended to be passed directly into a shader, so when
@@ -1159,7 +1151,7 @@
skgpu::ganesh::Flush(activeSurface);
}
- auto drawFence = sp<Fence>::make(flushAndSubmit(grContext));
+ auto drawFence = sp<Fence>::make(flushAndSubmit(context));
if (ATRACE_ENABLED()) {
static gui::FenceMonitor sMonitor("RE Completion");
@@ -1169,11 +1161,11 @@
}
size_t SkiaRenderEngine::getMaxTextureSize() const {
- return mGrContext->maxTextureSize();
+ return mContext->getMaxTextureSize();
}
size_t SkiaRenderEngine::getMaxViewportDims() const {
- return mGrContext->maxRenderTargetSize();
+ return mContext->getMaxRenderTargetSize();
}
void SkiaRenderEngine::drawShadow(SkCanvas* canvas,
@@ -1199,13 +1191,13 @@
const int maxResourceBytes = size.width * size.height * SURFACE_SIZE_MULTIPLIER;
// start by resizing the current context
- getActiveGrContext()->setResourceCacheLimit(maxResourceBytes);
+ getActiveContext()->setResourceCacheLimit(maxResourceBytes);
// if it is possible to switch contexts then we will resize the other context
const bool originalProtectedState = mInProtectedContext;
useProtectedContext(!mInProtectedContext);
if (mInProtectedContext != originalProtectedState) {
- getActiveGrContext()->setResourceCacheLimit(maxResourceBytes);
+ getActiveContext()->setResourceCacheLimit(maxResourceBytes);
// reset back to the initial context that was active when this method was called
useProtectedContext(originalProtectedState);
}
@@ -1245,7 +1237,7 @@
{"skia", "Other"},
};
SkiaMemoryReporter gpuReporter(gpuResourceMap, true);
- mGrContext->dumpMemoryStatistics(&gpuReporter);
+ mContext->dumpMemoryStatistics(&gpuReporter);
StringAppendF(&result, "Skia's GPU Caches: ");
gpuReporter.logTotals(result);
gpuReporter.logOutput(result);
@@ -1269,8 +1261,8 @@
StringAppendF(&result, "\n");
SkiaMemoryReporter gpuProtectedReporter(gpuResourceMap, true);
- if (mProtectedGrContext) {
- mProtectedGrContext->dumpMemoryStatistics(&gpuProtectedReporter);
+ if (mProtectedContext) {
+ mProtectedContext->dumpMemoryStatistics(&gpuProtectedReporter);
}
StringAppendF(&result, "Skia's GPU Protected Caches: ");
gpuProtectedReporter.logTotals(result);
diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h
index e88d44c..9f892bd 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.h
+++ b/libs/renderengine/skia/SkiaRenderEngine.h
@@ -21,21 +21,21 @@
#include <sys/types.h>
#include <GrBackendSemaphore.h>
-#include <GrDirectContext.h>
#include <SkSurface.h>
#include <android-base/thread_annotations.h>
#include <renderengine/ExternalTexture.h>
#include <renderengine/RenderEngine.h>
#include <sys/types.h>
+#include <memory>
#include <mutex>
#include <unordered_map>
#include "AutoBackendTexture.h"
#include "GrContextOptions.h"
#include "SkImageInfo.h"
-#include "SkiaRenderEngine.h"
#include "android-base/macros.h"
+#include "compat/SkiaGpuContext.h"
#include "debug/SkiaCapture.h"
#include "filters/BlurFilter.h"
#include "filters/LinearEffect.h"
@@ -76,24 +76,25 @@
bool supportsProtectedContent() const override {
return supportsProtectedContentImpl();
}
- void ensureGrContextsCreated();
+ void ensureContextsCreated();
+
protected:
// This is so backends can stop the generic rendering state first before
// cleaning up backend-specific state
void finishRenderingAndAbandonContext();
// Functions that a given backend (GLES, Vulkan) must implement
- using Contexts = std::pair<sk_sp<GrDirectContext>, sk_sp<GrDirectContext>>;
- virtual Contexts createDirectContexts(const GrContextOptions& options) = 0;
+ using Contexts = std::pair<unique_ptr<SkiaGpuContext>, unique_ptr<SkiaGpuContext>>;
+ virtual Contexts createContexts() = 0;
virtual bool supportsProtectedContentImpl() const = 0;
virtual bool useProtectedContextImpl(GrProtected isProtected) = 0;
- virtual void waitFence(GrDirectContext* grContext, base::borrowed_fd fenceFd) = 0;
- virtual base::unique_fd flushAndSubmit(GrDirectContext* context) = 0;
+ virtual void waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) = 0;
+ virtual base::unique_fd flushAndSubmit(SkiaGpuContext* context) = 0;
virtual void appendBackendSpecificInfoToDump(std::string& result) = 0;
size_t getMaxTextureSize() const override final;
size_t getMaxViewportDims() const override final;
- GrDirectContext* getActiveGrContext();
+ SkiaGpuContext* getActiveContext();
bool isProtected() const { return mInProtectedContext; }
@@ -121,6 +122,8 @@
int mTotalShadersCompiled = 0;
};
+ SkSLCacheMonitor mSkSLCacheMonitor;
+
private:
void mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer,
bool isRenderable) override final;
@@ -183,12 +186,11 @@
// Mutex guarding rendering operations, so that internal state related to
// rendering that is potentially modified by multiple threads is guaranteed thread-safe.
mutable std::mutex mRenderingMutex;
- SkSLCacheMonitor mSkSLCacheMonitor;
// Graphics context used for creating surfaces and submitting commands
- sk_sp<GrDirectContext> mGrContext;
+ unique_ptr<SkiaGpuContext> mContext;
// Same as above, but for protected content (eg. DRM)
- sk_sp<GrDirectContext> mProtectedGrContext;
+ unique_ptr<SkiaGpuContext> mProtectedContext;
bool mInProtectedContext = false;
};
diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
index feb76a1..d854929 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
@@ -21,23 +21,23 @@
#include "SkiaVkRenderEngine.h"
+#include "compat/SkiaGpuContext.h"
+
#include <GrBackendSemaphore.h>
#include <GrContextOptions.h>
-#include <vk/GrVkExtensions.h>
-#include <vk/GrVkTypes.h>
+#include <GrDirectContext.h>
#include <include/gpu/ganesh/vk/GrVkBackendSemaphore.h>
#include <include/gpu/ganesh/vk/GrVkDirectContext.h>
+#include <vk/GrVkExtensions.h>
+#include <vk/GrVkTypes.h>
#include <android-base/stringprintf.h>
#include <gui/TraceUtils.h>
#include <sync/sync.h>
#include <utils/Trace.h>
-#include <cstdint>
#include <memory>
-#include <sstream>
#include <string>
-#include <vector>
#include <vulkan/vulkan.h>
#include "log/log_main.h"
@@ -45,619 +45,19 @@
namespace android {
namespace renderengine {
-struct VulkanFuncs {
- PFN_vkCreateSemaphore vkCreateSemaphore = nullptr;
- PFN_vkImportSemaphoreFdKHR vkImportSemaphoreFdKHR = nullptr;
- PFN_vkGetSemaphoreFdKHR vkGetSemaphoreFdKHR = nullptr;
- PFN_vkDestroySemaphore vkDestroySemaphore = nullptr;
-
- PFN_vkDeviceWaitIdle vkDeviceWaitIdle = nullptr;
- PFN_vkDestroyDevice vkDestroyDevice = nullptr;
- PFN_vkDestroyInstance vkDestroyInstance = nullptr;
-};
-
-// Ref-Count a semaphore
-struct DestroySemaphoreInfo {
- VkSemaphore mSemaphore;
- // We need to make sure we don't delete the VkSemaphore until it is done being used by both Skia
- // (including by the GPU) and inside SkiaVkRenderEngine. So we always start with two refs, one
- // owned by Skia and one owned by the SkiaVkRenderEngine. The refs are decremented each time
- // delete_semaphore* is called with this object. Skia will call destroy_semaphore* once it is
- // done with the semaphore and the GPU has finished work on the semaphore. SkiaVkRenderEngine
- // calls delete_semaphore* after sending the semaphore to Skia and exporting it if need be.
- int mRefs = 2;
-
- DestroySemaphoreInfo(VkSemaphore semaphore) : mSemaphore(semaphore) {}
-};
-
-namespace {
-void onVkDeviceFault(void* callbackContext, const std::string& description,
- const std::vector<VkDeviceFaultAddressInfoEXT>& addressInfos,
- const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos,
- const std::vector<std::byte>& vendorBinaryData);
-} // anonymous namespace
-
-struct VulkanInterface {
- bool initialized = false;
- VkInstance instance;
- VkPhysicalDevice physicalDevice;
- VkDevice device;
- VkQueue queue;
- int queueIndex;
- uint32_t apiVersion;
- GrVkExtensions grExtensions;
- VkPhysicalDeviceFeatures2* physicalDeviceFeatures2 = nullptr;
- VkPhysicalDeviceSamplerYcbcrConversionFeatures* samplerYcbcrConversionFeatures = nullptr;
- VkPhysicalDeviceProtectedMemoryFeatures* protectedMemoryFeatures = nullptr;
- VkPhysicalDeviceFaultFeaturesEXT* deviceFaultFeatures = nullptr;
- GrVkGetProc grGetProc;
- bool isProtected;
- bool isRealtimePriority;
-
- VulkanFuncs funcs;
-
- std::vector<std::string> instanceExtensionNames;
- std::vector<std::string> deviceExtensionNames;
-
- GrVkBackendContext getBackendContext() {
- GrVkBackendContext backendContext;
- backendContext.fInstance = instance;
- backendContext.fPhysicalDevice = physicalDevice;
- backendContext.fDevice = device;
- backendContext.fQueue = queue;
- backendContext.fGraphicsQueueIndex = queueIndex;
- backendContext.fMaxAPIVersion = apiVersion;
- backendContext.fVkExtensions = &grExtensions;
- backendContext.fDeviceFeatures2 = physicalDeviceFeatures2;
- backendContext.fGetProc = grGetProc;
- backendContext.fProtectedContext = isProtected ? GrProtected::kYes : GrProtected::kNo;
- backendContext.fDeviceLostContext = this; // VulkanInterface is long-lived
- backendContext.fDeviceLostProc = onVkDeviceFault;
- return backendContext;
- };
-
- VkSemaphore createExportableSemaphore() {
- VkExportSemaphoreCreateInfo exportInfo;
- exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO;
- exportInfo.pNext = nullptr;
- exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
-
- VkSemaphoreCreateInfo semaphoreInfo;
- semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
- semaphoreInfo.pNext = &exportInfo;
- semaphoreInfo.flags = 0;
-
- VkSemaphore semaphore;
- VkResult err = funcs.vkCreateSemaphore(device, &semaphoreInfo, nullptr, &semaphore);
- if (VK_SUCCESS != err) {
- ALOGE("%s: failed to create semaphore. err %d\n", __func__, err);
- return VK_NULL_HANDLE;
- }
-
- return semaphore;
- }
-
- // syncFd cannot be <= 0
- VkSemaphore importSemaphoreFromSyncFd(int syncFd) {
- VkSemaphoreCreateInfo semaphoreInfo;
- semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
- semaphoreInfo.pNext = nullptr;
- semaphoreInfo.flags = 0;
-
- VkSemaphore semaphore;
- VkResult err = funcs.vkCreateSemaphore(device, &semaphoreInfo, nullptr, &semaphore);
- if (VK_SUCCESS != err) {
- ALOGE("%s: failed to create import semaphore", __func__);
- return VK_NULL_HANDLE;
- }
-
- VkImportSemaphoreFdInfoKHR importInfo;
- importInfo.sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR;
- importInfo.pNext = nullptr;
- importInfo.semaphore = semaphore;
- importInfo.flags = VK_SEMAPHORE_IMPORT_TEMPORARY_BIT;
- importInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
- importInfo.fd = syncFd;
-
- err = funcs.vkImportSemaphoreFdKHR(device, &importInfo);
- if (VK_SUCCESS != err) {
- funcs.vkDestroySemaphore(device, semaphore, nullptr);
- ALOGE("%s: failed to import semaphore", __func__);
- return VK_NULL_HANDLE;
- }
-
- return semaphore;
- }
-
- int exportSemaphoreSyncFd(VkSemaphore semaphore) {
- int res;
-
- VkSemaphoreGetFdInfoKHR getFdInfo;
- getFdInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR;
- getFdInfo.pNext = nullptr;
- getFdInfo.semaphore = semaphore;
- getFdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
-
- VkResult err = funcs.vkGetSemaphoreFdKHR(device, &getFdInfo, &res);
- if (VK_SUCCESS != err) {
- ALOGE("%s: failed to export semaphore, err: %d", __func__, err);
- return -1;
- }
- return res;
- }
-
- void destroySemaphore(VkSemaphore semaphore) {
- funcs.vkDestroySemaphore(device, semaphore, nullptr);
- }
-};
-
-namespace {
-void onVkDeviceFault(void* callbackContext, const std::string& description,
- const std::vector<VkDeviceFaultAddressInfoEXT>& addressInfos,
- const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos,
- const std::vector<std::byte>& vendorBinaryData) {
- VulkanInterface* interface = static_cast<VulkanInterface*>(callbackContext);
- const std::string protectedStr = interface->isProtected ? "protected" : "non-protected";
- // The final crash string should contain as much differentiating info as possible, up to 1024
- // bytes. As this final message is constructed, the same information is also dumped to the logs
- // but in a more verbose format. Building the crash string is unsightly, so the clearer logging
- // statement is always placed first to give context.
- ALOGE("VK_ERROR_DEVICE_LOST (%s context): %s", protectedStr.c_str(), description.c_str());
- std::stringstream crashMsg;
- crashMsg << "VK_ERROR_DEVICE_LOST (" << protectedStr;
-
- if (!addressInfos.empty()) {
- ALOGE("%zu VkDeviceFaultAddressInfoEXT:", addressInfos.size());
- crashMsg << ", " << addressInfos.size() << " address info (";
- for (VkDeviceFaultAddressInfoEXT addressInfo : addressInfos) {
- ALOGE(" addressType: %d", (int)addressInfo.addressType);
- ALOGE(" reportedAddress: %" PRIu64, addressInfo.reportedAddress);
- ALOGE(" addressPrecision: %" PRIu64, addressInfo.addressPrecision);
- crashMsg << addressInfo.addressType << ":"
- << addressInfo.reportedAddress << ":"
- << addressInfo.addressPrecision << ", ";
- }
- crashMsg.seekp(-2, crashMsg.cur); // Move back to overwrite trailing ", "
- crashMsg << ")";
- }
-
- if (!vendorInfos.empty()) {
- ALOGE("%zu VkDeviceFaultVendorInfoEXT:", vendorInfos.size());
- crashMsg << ", " << vendorInfos.size() << " vendor info (";
- for (VkDeviceFaultVendorInfoEXT vendorInfo : vendorInfos) {
- ALOGE(" description: %s", vendorInfo.description);
- ALOGE(" vendorFaultCode: %" PRIu64, vendorInfo.vendorFaultCode);
- ALOGE(" vendorFaultData: %" PRIu64, vendorInfo.vendorFaultData);
- // Omit descriptions for individual vendor info structs in the crash string, as the
- // fault code and fault data fields should be enough for clustering, and the verbosity
- // isn't worth it. Additionally, vendors may just set the general description field of
- // the overall fault to the description of the first element in this list, and that
- // overall description will be placed at the end of the crash string.
- crashMsg << vendorInfo.vendorFaultCode << ":"
- << vendorInfo.vendorFaultData << ", ";
- }
- crashMsg.seekp(-2, crashMsg.cur); // Move back to overwrite trailing ", "
- crashMsg << ")";
- }
-
- if (!vendorBinaryData.empty()) {
- // TODO: b/322830575 - Log in base64, or dump directly to a file that gets put in bugreports
- ALOGE("%zu bytes of vendor-specific binary data (please notify Android's Core Graphics"
- " Stack team if you observe this message).",
- vendorBinaryData.size());
- crashMsg << ", " << vendorBinaryData.size() << " bytes binary";
- }
-
- crashMsg << "): " << description;
- LOG_ALWAYS_FATAL("%s", crashMsg.str().c_str());
-};
-} // anonymous namespace
-
-static GrVkGetProc sGetProc = [](const char* proc_name, VkInstance instance, VkDevice device) {
- if (device != VK_NULL_HANDLE) {
- return vkGetDeviceProcAddr(device, proc_name);
- }
- return vkGetInstanceProcAddr(instance, proc_name);
-};
-
-#define BAIL(fmt, ...) \
- { \
- ALOGE("%s: " fmt ", bailing", __func__, ##__VA_ARGS__); \
- return interface; \
- }
-
-#define CHECK_NONNULL(expr) \
- if ((expr) == nullptr) { \
- BAIL("[%s] null", #expr); \
- }
-
-#define VK_CHECK(expr) \
- if ((expr) != VK_SUCCESS) { \
- BAIL("[%s] failed. err = %d", #expr, expr); \
- return interface; \
- }
-
-#define VK_GET_PROC(F) \
- PFN_vk##F vk##F = (PFN_vk##F)vkGetInstanceProcAddr(VK_NULL_HANDLE, "vk" #F); \
- CHECK_NONNULL(vk##F)
-#define VK_GET_INST_PROC(instance, F) \
- PFN_vk##F vk##F = (PFN_vk##F)vkGetInstanceProcAddr(instance, "vk" #F); \
- CHECK_NONNULL(vk##F)
-#define VK_GET_DEV_PROC(device, F) \
- PFN_vk##F vk##F = (PFN_vk##F)vkGetDeviceProcAddr(device, "vk" #F); \
- CHECK_NONNULL(vk##F)
-
-VulkanInterface initVulkanInterface(bool protectedContent = false) {
- const nsecs_t timeBefore = systemTime();
- VulkanInterface interface;
-
- VK_GET_PROC(EnumerateInstanceVersion);
- uint32_t instanceVersion;
- VK_CHECK(vkEnumerateInstanceVersion(&instanceVersion));
-
- if (instanceVersion < VK_MAKE_VERSION(1, 1, 0)) {
- return interface;
- }
-
- const VkApplicationInfo appInfo = {
- VK_STRUCTURE_TYPE_APPLICATION_INFO, nullptr, "surfaceflinger", 0, "android platform", 0,
- VK_MAKE_VERSION(1, 1, 0),
- };
-
- VK_GET_PROC(EnumerateInstanceExtensionProperties);
-
- uint32_t extensionCount = 0;
- VK_CHECK(vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr));
- std::vector<VkExtensionProperties> instanceExtensions(extensionCount);
- VK_CHECK(vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount,
- instanceExtensions.data()));
- std::vector<const char*> enabledInstanceExtensionNames;
- enabledInstanceExtensionNames.reserve(instanceExtensions.size());
- interface.instanceExtensionNames.reserve(instanceExtensions.size());
- for (const auto& instExt : instanceExtensions) {
- enabledInstanceExtensionNames.push_back(instExt.extensionName);
- interface.instanceExtensionNames.push_back(instExt.extensionName);
- }
-
- const VkInstanceCreateInfo instanceCreateInfo = {
- VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
- nullptr,
- 0,
- &appInfo,
- 0,
- nullptr,
- (uint32_t)enabledInstanceExtensionNames.size(),
- enabledInstanceExtensionNames.data(),
- };
-
- VK_GET_PROC(CreateInstance);
- VkInstance instance;
- VK_CHECK(vkCreateInstance(&instanceCreateInfo, nullptr, &instance));
-
- VK_GET_INST_PROC(instance, DestroyInstance);
- interface.funcs.vkDestroyInstance = vkDestroyInstance;
- VK_GET_INST_PROC(instance, EnumeratePhysicalDevices);
- VK_GET_INST_PROC(instance, EnumerateDeviceExtensionProperties);
- VK_GET_INST_PROC(instance, GetPhysicalDeviceProperties2);
- VK_GET_INST_PROC(instance, GetPhysicalDeviceExternalSemaphoreProperties);
- VK_GET_INST_PROC(instance, GetPhysicalDeviceQueueFamilyProperties2);
- VK_GET_INST_PROC(instance, GetPhysicalDeviceFeatures2);
- VK_GET_INST_PROC(instance, CreateDevice);
-
- uint32_t physdevCount;
- VK_CHECK(vkEnumeratePhysicalDevices(instance, &physdevCount, nullptr));
- if (physdevCount == 0) {
- BAIL("Could not find any physical devices");
- }
-
- physdevCount = 1;
- VkPhysicalDevice physicalDevice;
- VkResult enumeratePhysDevsErr =
- vkEnumeratePhysicalDevices(instance, &physdevCount, &physicalDevice);
- if (enumeratePhysDevsErr != VK_SUCCESS && VK_INCOMPLETE != enumeratePhysDevsErr) {
- BAIL("vkEnumeratePhysicalDevices failed with non-VK_INCOMPLETE error: %d",
- enumeratePhysDevsErr);
- }
-
- VkPhysicalDeviceProperties2 physDevProps = {
- VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
- 0,
- {},
- };
- VkPhysicalDeviceProtectedMemoryProperties protMemProps = {
- VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_PROPERTIES,
- 0,
- {},
- };
-
- if (protectedContent) {
- physDevProps.pNext = &protMemProps;
- }
-
- vkGetPhysicalDeviceProperties2(physicalDevice, &physDevProps);
- if (physDevProps.properties.apiVersion < VK_MAKE_VERSION(1, 1, 0)) {
- BAIL("Could not find a Vulkan 1.1+ physical device");
- }
-
- if (physDevProps.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU) {
- // TODO: b/326633110 - SkiaVK is not working correctly on swiftshader path.
- BAIL("CPU implementations of Vulkan is not supported");
- }
-
- // Check for syncfd support. Bail if we cannot both import and export them.
- VkPhysicalDeviceExternalSemaphoreInfo semInfo = {
- VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO,
- nullptr,
- VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
- };
- VkExternalSemaphoreProperties semProps = {
- VK_STRUCTURE_TYPE_EXTERNAL_SEMAPHORE_PROPERTIES, nullptr, 0, 0, 0,
- };
- vkGetPhysicalDeviceExternalSemaphoreProperties(physicalDevice, &semInfo, &semProps);
-
- bool sufficientSemaphoreSyncFdSupport = (semProps.exportFromImportedHandleTypes &
- VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT) &&
- (semProps.compatibleHandleTypes & VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT) &&
- (semProps.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT) &&
- (semProps.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT);
-
- if (!sufficientSemaphoreSyncFdSupport) {
- BAIL("Vulkan device does not support sufficient external semaphore sync fd features. "
- "exportFromImportedHandleTypes 0x%x (needed 0x%x) "
- "compatibleHandleTypes 0x%x (needed 0x%x) "
- "externalSemaphoreFeatures 0x%x (needed 0x%x) ",
- semProps.exportFromImportedHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
- semProps.compatibleHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
- semProps.externalSemaphoreFeatures,
- VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT |
- VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT);
- } else {
- ALOGD("Vulkan device supports sufficient external semaphore sync fd features. "
- "exportFromImportedHandleTypes 0x%x (needed 0x%x) "
- "compatibleHandleTypes 0x%x (needed 0x%x) "
- "externalSemaphoreFeatures 0x%x (needed 0x%x) ",
- semProps.exportFromImportedHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
- semProps.compatibleHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
- semProps.externalSemaphoreFeatures,
- VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT |
- VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT);
- }
-
- uint32_t queueCount;
- vkGetPhysicalDeviceQueueFamilyProperties2(physicalDevice, &queueCount, nullptr);
- if (queueCount == 0) {
- BAIL("Could not find queues for physical device");
- }
-
- std::vector<VkQueueFamilyProperties2> queueProps(queueCount);
- std::vector<VkQueueFamilyGlobalPriorityPropertiesEXT> queuePriorityProps(queueCount);
- VkQueueGlobalPriorityKHR queuePriority = VK_QUEUE_GLOBAL_PRIORITY_MEDIUM_KHR;
- // Even though we don't yet know if the VK_EXT_global_priority extension is available,
- // we can safely add the request to the pNext chain, and if the extension is not
- // available, it will be ignored.
- for (uint32_t i = 0; i < queueCount; ++i) {
- queuePriorityProps[i].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_GLOBAL_PRIORITY_PROPERTIES_EXT;
- queuePriorityProps[i].pNext = nullptr;
- queueProps[i].pNext = &queuePriorityProps[i];
- }
- vkGetPhysicalDeviceQueueFamilyProperties2(physicalDevice, &queueCount, queueProps.data());
-
- int graphicsQueueIndex = -1;
- for (uint32_t i = 0; i < queueCount; ++i) {
- // Look at potential answers to the VK_EXT_global_priority query. If answers were
- // provided, we may adjust the queuePriority.
- if (queueProps[i].queueFamilyProperties.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
- for (uint32_t j = 0; j < queuePriorityProps[i].priorityCount; j++) {
- if (queuePriorityProps[i].priorities[j] > queuePriority) {
- queuePriority = queuePriorityProps[i].priorities[j];
- }
- }
- if (queuePriority == VK_QUEUE_GLOBAL_PRIORITY_REALTIME_KHR) {
- interface.isRealtimePriority = true;
- }
- graphicsQueueIndex = i;
- break;
- }
- }
-
- if (graphicsQueueIndex == -1) {
- BAIL("Could not find a graphics queue family");
- }
-
- uint32_t deviceExtensionCount;
- VK_CHECK(vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &deviceExtensionCount,
- nullptr));
- std::vector<VkExtensionProperties> deviceExtensions(deviceExtensionCount);
- VK_CHECK(vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &deviceExtensionCount,
- deviceExtensions.data()));
-
- std::vector<const char*> enabledDeviceExtensionNames;
- enabledDeviceExtensionNames.reserve(deviceExtensions.size());
- interface.deviceExtensionNames.reserve(deviceExtensions.size());
- for (const auto& devExt : deviceExtensions) {
- enabledDeviceExtensionNames.push_back(devExt.extensionName);
- interface.deviceExtensionNames.push_back(devExt.extensionName);
- }
-
- interface.grExtensions.init(sGetProc, instance, physicalDevice,
- enabledInstanceExtensionNames.size(),
- enabledInstanceExtensionNames.data(),
- enabledDeviceExtensionNames.size(),
- enabledDeviceExtensionNames.data());
-
- if (!interface.grExtensions.hasExtension(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, 1)) {
- BAIL("Vulkan driver doesn't support external semaphore fd");
- }
-
- interface.physicalDeviceFeatures2 = new VkPhysicalDeviceFeatures2;
- interface.physicalDeviceFeatures2->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
- interface.physicalDeviceFeatures2->pNext = nullptr;
-
- interface.samplerYcbcrConversionFeatures = new VkPhysicalDeviceSamplerYcbcrConversionFeatures;
- interface.samplerYcbcrConversionFeatures->sType =
- VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES;
- interface.samplerYcbcrConversionFeatures->pNext = nullptr;
-
- interface.physicalDeviceFeatures2->pNext = interface.samplerYcbcrConversionFeatures;
- void** tailPnext = &interface.samplerYcbcrConversionFeatures->pNext;
-
- if (protectedContent) {
- interface.protectedMemoryFeatures = new VkPhysicalDeviceProtectedMemoryFeatures;
- interface.protectedMemoryFeatures->sType =
- VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES;
- interface.protectedMemoryFeatures->pNext = nullptr;
- *tailPnext = interface.protectedMemoryFeatures;
- tailPnext = &interface.protectedMemoryFeatures->pNext;
- }
-
- if (interface.grExtensions.hasExtension(VK_EXT_DEVICE_FAULT_EXTENSION_NAME, 1)) {
- interface.deviceFaultFeatures = new VkPhysicalDeviceFaultFeaturesEXT;
- interface.deviceFaultFeatures->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FAULT_FEATURES_EXT;
- interface.deviceFaultFeatures->pNext = nullptr;
- *tailPnext = interface.deviceFaultFeatures;
- tailPnext = &interface.deviceFaultFeatures->pNext;
- }
-
- vkGetPhysicalDeviceFeatures2(physicalDevice, interface.physicalDeviceFeatures2);
- // Looks like this would slow things down and we can't depend on it on all platforms
- interface.physicalDeviceFeatures2->features.robustBufferAccess = VK_FALSE;
-
- if (protectedContent && !interface.protectedMemoryFeatures->protectedMemory) {
- BAIL("Protected memory not supported");
- }
-
- float queuePriorities[1] = {0.0f};
- void* queueNextPtr = nullptr;
-
- VkDeviceQueueGlobalPriorityCreateInfoEXT queuePriorityCreateInfo = {
- VK_STRUCTURE_TYPE_DEVICE_QUEUE_GLOBAL_PRIORITY_CREATE_INFO_EXT,
- nullptr,
- // If queue priority is supported, RE should always have realtime priority.
- queuePriority,
- };
-
- if (interface.grExtensions.hasExtension(VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME, 2)) {
- queueNextPtr = &queuePriorityCreateInfo;
- }
-
- VkDeviceQueueCreateFlags deviceQueueCreateFlags =
- (VkDeviceQueueCreateFlags)(protectedContent ? VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT : 0);
-
- const VkDeviceQueueCreateInfo queueInfo = {
- VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
- queueNextPtr,
- deviceQueueCreateFlags,
- (uint32_t)graphicsQueueIndex,
- 1,
- queuePriorities,
- };
-
- const VkDeviceCreateInfo deviceInfo = {
- VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
- interface.physicalDeviceFeatures2,
- 0,
- 1,
- &queueInfo,
- 0,
- nullptr,
- (uint32_t)enabledDeviceExtensionNames.size(),
- enabledDeviceExtensionNames.data(),
- nullptr,
- };
-
- ALOGD("Trying to create Vk device with protectedContent=%d", protectedContent);
- VkDevice device;
- VK_CHECK(vkCreateDevice(physicalDevice, &deviceInfo, nullptr, &device));
- ALOGD("Trying to create Vk device with protectedContent=%d (success)", protectedContent);
-
- VkQueue graphicsQueue;
- VK_GET_DEV_PROC(device, GetDeviceQueue2);
- const VkDeviceQueueInfo2 deviceQueueInfo2 = {VK_STRUCTURE_TYPE_DEVICE_QUEUE_INFO_2, nullptr,
- deviceQueueCreateFlags,
- (uint32_t)graphicsQueueIndex, 0};
- vkGetDeviceQueue2(device, &deviceQueueInfo2, &graphicsQueue);
-
- VK_GET_DEV_PROC(device, DeviceWaitIdle);
- VK_GET_DEV_PROC(device, DestroyDevice);
- interface.funcs.vkDeviceWaitIdle = vkDeviceWaitIdle;
- interface.funcs.vkDestroyDevice = vkDestroyDevice;
-
- VK_GET_DEV_PROC(device, CreateSemaphore);
- VK_GET_DEV_PROC(device, ImportSemaphoreFdKHR);
- VK_GET_DEV_PROC(device, GetSemaphoreFdKHR);
- VK_GET_DEV_PROC(device, DestroySemaphore);
- interface.funcs.vkCreateSemaphore = vkCreateSemaphore;
- interface.funcs.vkImportSemaphoreFdKHR = vkImportSemaphoreFdKHR;
- interface.funcs.vkGetSemaphoreFdKHR = vkGetSemaphoreFdKHR;
- interface.funcs.vkDestroySemaphore = vkDestroySemaphore;
-
- // At this point, everything's succeeded and we can continue
- interface.initialized = true;
- interface.instance = instance;
- interface.physicalDevice = physicalDevice;
- interface.device = device;
- interface.queue = graphicsQueue;
- interface.queueIndex = graphicsQueueIndex;
- interface.apiVersion = physDevProps.properties.apiVersion;
- // grExtensions already constructed
- // feature pointers already constructed
- interface.grGetProc = sGetProc;
- interface.isProtected = protectedContent;
- // funcs already initialized
-
- const nsecs_t timeAfter = systemTime();
- const float initTimeMs = static_cast<float>(timeAfter - timeBefore) / 1.0E6;
- ALOGD("%s: Success init Vulkan interface in %f ms", __func__, initTimeMs);
- return interface;
-}
-
-void teardownVulkanInterface(VulkanInterface* interface) {
- interface->initialized = false;
-
- if (interface->device != VK_NULL_HANDLE) {
- interface->funcs.vkDeviceWaitIdle(interface->device);
- interface->funcs.vkDestroyDevice(interface->device, nullptr);
- interface->device = VK_NULL_HANDLE;
- }
- if (interface->instance != VK_NULL_HANDLE) {
- interface->funcs.vkDestroyInstance(interface->instance, nullptr);
- interface->instance = VK_NULL_HANDLE;
- }
-
- if (interface->protectedMemoryFeatures) {
- delete interface->protectedMemoryFeatures;
- }
-
- if (interface->samplerYcbcrConversionFeatures) {
- delete interface->samplerYcbcrConversionFeatures;
- }
-
- if (interface->physicalDeviceFeatures2) {
- delete interface->physicalDeviceFeatures2;
- }
-
- if (interface->deviceFaultFeatures) {
- delete interface->deviceFaultFeatures;
- }
-
- interface->samplerYcbcrConversionFeatures = nullptr;
- interface->physicalDeviceFeatures2 = nullptr;
- interface->protectedMemoryFeatures = nullptr;
-}
-
-static VulkanInterface sVulkanInterface;
-static VulkanInterface sProtectedContentVulkanInterface;
+static skia::VulkanInterface sVulkanInterface;
+static skia::VulkanInterface sProtectedContentVulkanInterface;
static void sSetupVulkanInterface() {
- if (!sVulkanInterface.initialized) {
- sVulkanInterface = initVulkanInterface(false /* no protected content */);
+ if (!sVulkanInterface.isInitialized()) {
+ sVulkanInterface.init(false /* no protected content */);
// We will have to abort if non-protected VkDevice creation fails (then nothing works).
- LOG_ALWAYS_FATAL_IF(!sVulkanInterface.initialized,
+ LOG_ALWAYS_FATAL_IF(!sVulkanInterface.isInitialized(),
"Could not initialize Vulkan RenderEngine!");
}
- if (!sProtectedContentVulkanInterface.initialized) {
- sProtectedContentVulkanInterface = initVulkanInterface(true /* protected content */);
- if (!sProtectedContentVulkanInterface.initialized) {
+ if (!sProtectedContentVulkanInterface.isInitialized()) {
+ sProtectedContentVulkanInterface.init(true /* protected content */);
+ if (!sProtectedContentVulkanInterface.isInitialized()) {
ALOGE("Could not initialize protected content Vulkan RenderEngine.");
}
}
@@ -668,12 +68,12 @@
case GraphicsApi::GL:
return true;
case GraphicsApi::VK: {
- if (!sVulkanInterface.initialized) {
- sVulkanInterface = initVulkanInterface(false /* no protected content */);
+ if (!sVulkanInterface.isInitialized()) {
+ sVulkanInterface.init(false /* no protected content */);
ALOGD("%s: initialized == %s.", __func__,
- sVulkanInterface.initialized ? "true" : "false");
+ sVulkanInterface.isInitialized() ? "true" : "false");
}
- return sVulkanInterface.initialized;
+ return sVulkanInterface.isInitialized();
}
}
}
@@ -685,9 +85,9 @@
std::unique_ptr<SkiaVkRenderEngine> SkiaVkRenderEngine::create(
const RenderEngineCreationArgs& args) {
std::unique_ptr<SkiaVkRenderEngine> engine(new SkiaVkRenderEngine(args));
- engine->ensureGrContextsCreated();
+ engine->ensureContextsCreated();
- if (sVulkanInterface.initialized) {
+ if (sVulkanInterface.isInitialized()) {
ALOGD("SkiaVkRenderEngine::%s: successfully initialized SkiaVkRenderEngine", __func__);
return engine;
} else {
@@ -706,45 +106,33 @@
finishRenderingAndAbandonContext();
}
-SkiaRenderEngine::Contexts SkiaVkRenderEngine::createDirectContexts(
- const GrContextOptions& options) {
+SkiaRenderEngine::Contexts SkiaVkRenderEngine::createContexts() {
sSetupVulkanInterface();
SkiaRenderEngine::Contexts contexts;
- contexts.first = GrDirectContexts::MakeVulkan(sVulkanInterface.getBackendContext(), options);
+ contexts.first = SkiaGpuContext::MakeVulkan_Ganesh(sVulkanInterface.getGaneshBackendContext(),
+ mSkSLCacheMonitor);
if (supportsProtectedContentImpl()) {
- contexts.second =
- GrDirectContexts::MakeVulkan(sProtectedContentVulkanInterface.getBackendContext(),
- options);
+ contexts.second = SkiaGpuContext::MakeVulkan_Ganesh(sProtectedContentVulkanInterface
+ .getGaneshBackendContext(),
+ mSkSLCacheMonitor);
}
return contexts;
}
bool SkiaVkRenderEngine::supportsProtectedContentImpl() const {
- return sProtectedContentVulkanInterface.initialized;
+ return sProtectedContentVulkanInterface.isInitialized();
}
bool SkiaVkRenderEngine::useProtectedContextImpl(GrProtected) {
return true;
}
-static void delete_semaphore(void* semaphore) {
- DestroySemaphoreInfo* info = reinterpret_cast<DestroySemaphoreInfo*>(semaphore);
- --info->mRefs;
- if (!info->mRefs) {
- sVulkanInterface.destroySemaphore(info->mSemaphore);
- delete info;
- }
-}
-
-static void delete_semaphore_protected(void* semaphore) {
- DestroySemaphoreInfo* info = reinterpret_cast<DestroySemaphoreInfo*>(semaphore);
- --info->mRefs;
- if (!info->mRefs) {
- sProtectedContentVulkanInterface.destroySemaphore(info->mSemaphore);
- delete info;
- }
+static void unref_semaphore(void* semaphore) {
+ SkiaVkRenderEngine::DestroySemaphoreInfo* info =
+ reinterpret_cast<SkiaVkRenderEngine::DestroySemaphoreInfo*>(semaphore);
+ info->unref();
}
static VulkanInterface& getVulkanInterface(bool protectedContext) {
@@ -754,7 +142,7 @@
return sVulkanInterface;
}
-void SkiaVkRenderEngine::waitFence(GrDirectContext* grContext, base::borrowed_fd fenceFd) {
+void SkiaVkRenderEngine::waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) {
if (fenceFd.get() < 0) return;
int dupedFd = dup(fenceFd.get());
@@ -768,10 +156,11 @@
VkSemaphore waitSemaphore =
getVulkanInterface(isProtected()).importSemaphoreFromSyncFd(fenceDup.release());
GrBackendSemaphore beSemaphore = GrBackendSemaphores::MakeVk(waitSemaphore);
- grContext->wait(1, &beSemaphore, true /* delete after wait */);
+ context->grDirectContext()->wait(1, &beSemaphore, true /* delete after wait */);
}
-base::unique_fd SkiaVkRenderEngine::flushAndSubmit(GrDirectContext* grContext) {
+base::unique_fd SkiaVkRenderEngine::flushAndSubmit(SkiaGpuContext* context) {
+ sk_sp<GrDirectContext> grContext = context->grDirectContext();
VulkanInterface& vi = getVulkanInterface(isProtected());
VkSemaphore semaphore = vi.createExportableSemaphore();
@@ -780,10 +169,10 @@
GrFlushInfo flushInfo;
DestroySemaphoreInfo* destroySemaphoreInfo = nullptr;
if (semaphore != VK_NULL_HANDLE) {
- destroySemaphoreInfo = new DestroySemaphoreInfo(semaphore);
+ destroySemaphoreInfo = new DestroySemaphoreInfo(vi, semaphore);
flushInfo.fNumSemaphores = 1;
flushInfo.fSignalSemaphores = &backendSemaphore;
- flushInfo.fFinishedProc = isProtected() ? delete_semaphore_protected : delete_semaphore;
+ flushInfo.fFinishedProc = unref_semaphore;
flushInfo.fFinishedContext = destroySemaphoreInfo;
}
GrSemaphoresSubmitted submitted = grContext->flush(flushInfo);
@@ -803,7 +192,7 @@
int SkiaVkRenderEngine::getContextPriority() {
// EGL_CONTEXT_PRIORITY_REALTIME_NV
constexpr int kRealtimePriority = 0x3357;
- if (getVulkanInterface(isProtected()).isRealtimePriority) {
+ if (getVulkanInterface(isProtected()).isRealtimePriority()) {
return kRealtimePriority;
} else {
return 0;
@@ -812,21 +201,21 @@
void SkiaVkRenderEngine::appendBackendSpecificInfoToDump(std::string& result) {
StringAppendF(&result, "\n ------------RE Vulkan----------\n");
- StringAppendF(&result, "\n Vulkan device initialized: %d\n", sVulkanInterface.initialized);
+ StringAppendF(&result, "\n Vulkan device initialized: %d\n", sVulkanInterface.isInitialized());
StringAppendF(&result, "\n Vulkan protected device initialized: %d\n",
- sProtectedContentVulkanInterface.initialized);
+ sProtectedContentVulkanInterface.isInitialized());
- if (!sVulkanInterface.initialized) {
+ if (!sVulkanInterface.isInitialized()) {
return;
}
StringAppendF(&result, "\n Instance extensions:\n");
- for (const auto& name : sVulkanInterface.instanceExtensionNames) {
+ for (const auto& name : sVulkanInterface.getInstanceExtensionNames()) {
StringAppendF(&result, "\n %s\n", name.c_str());
}
StringAppendF(&result, "\n Device extensions:\n");
- for (const auto& name : sVulkanInterface.deviceExtensionNames) {
+ for (const auto& name : sVulkanInterface.getDeviceExtensionNames()) {
StringAppendF(&result, "\n %s\n", name.c_str());
}
}
diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.h b/libs/renderengine/skia/SkiaVkRenderEngine.h
index 52bc500..28b7595 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.h
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.h
@@ -20,6 +20,8 @@
#include <vk/GrVkBackendContext.h>
#include "SkiaRenderEngine.h"
+#include "VulkanInterface.h"
+#include "compat/SkiaGpuContext.h"
namespace android {
namespace renderengine {
@@ -32,14 +34,50 @@
int getContextPriority() override;
+ class DestroySemaphoreInfo {
+ public:
+ DestroySemaphoreInfo() = delete;
+ DestroySemaphoreInfo(const DestroySemaphoreInfo&) = delete;
+ DestroySemaphoreInfo& operator=(const DestroySemaphoreInfo&) = delete;
+ DestroySemaphoreInfo& operator=(DestroySemaphoreInfo&&) = delete;
+
+ DestroySemaphoreInfo(VulkanInterface& vulkanInterface, std::vector<VkSemaphore> semaphores)
+ : mVulkanInterface(vulkanInterface), mSemaphores(std::move(semaphores)) {}
+ DestroySemaphoreInfo(VulkanInterface& vulkanInterface, VkSemaphore semaphore)
+ : DestroySemaphoreInfo(vulkanInterface, std::vector<VkSemaphore>(1, semaphore)) {}
+
+ void unref() {
+ --mRefs;
+ if (!mRefs) {
+ for (VkSemaphore semaphore : mSemaphores) {
+ mVulkanInterface.destroySemaphore(semaphore);
+ }
+ delete this;
+ }
+ }
+
+ private:
+ ~DestroySemaphoreInfo() = default;
+
+ VulkanInterface& mVulkanInterface;
+ std::vector<VkSemaphore> mSemaphores;
+ // We need to make sure we don't delete the VkSemaphore until it is done being used by both
+ // Skia (including by the GPU) and inside SkiaVkRenderEngine. So we always start with two
+ // refs, one owned by Skia and one owned by the SkiaVkRenderEngine. The refs are decremented
+ // each time unref() is called on this object. Skia will call unref() once it is done with
+ // the semaphore and the GPU has finished work on the semaphore. SkiaVkRenderEngine calls
+ // unref() after sending the semaphore to Skia and exporting it if need be.
+ int mRefs = 2;
+ };
+
protected:
// Implementations of abstract SkiaRenderEngine functions specific to
// rendering backend
- virtual SkiaRenderEngine::Contexts createDirectContexts(const GrContextOptions& options);
+ SkiaRenderEngine::Contexts createContexts() override;
bool supportsProtectedContentImpl() const override;
bool useProtectedContextImpl(GrProtected isProtected) override;
- void waitFence(GrDirectContext* grContext, base::borrowed_fd fenceFd) override;
- base::unique_fd flushAndSubmit(GrDirectContext* context) override;
+ void waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) override;
+ base::unique_fd flushAndSubmit(SkiaGpuContext* context) override;
void appendBackendSpecificInfoToDump(std::string& result) override;
private:
diff --git a/libs/renderengine/skia/VulkanInterface.cpp b/libs/renderengine/skia/VulkanInterface.cpp
new file mode 100644
index 0000000..2f09a38
--- /dev/null
+++ b/libs/renderengine/skia/VulkanInterface.cpp
@@ -0,0 +1,582 @@
+/*
+ * Copyright 2024 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "RenderEngine"
+
+#include "VulkanInterface.h"
+
+#include <include/gpu/GpuTypes.h>
+#include <log/log_main.h>
+#include <utils/Timers.h>
+
+#include <cinttypes>
+#include <sstream>
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+GrVkBackendContext VulkanInterface::getGaneshBackendContext() {
+ GrVkBackendContext backendContext;
+ backendContext.fInstance = mInstance;
+ backendContext.fPhysicalDevice = mPhysicalDevice;
+ backendContext.fDevice = mDevice;
+ backendContext.fQueue = mQueue;
+ backendContext.fGraphicsQueueIndex = mQueueIndex;
+ backendContext.fMaxAPIVersion = mApiVersion;
+ backendContext.fVkExtensions = &mGrExtensions;
+ backendContext.fDeviceFeatures2 = mPhysicalDeviceFeatures2;
+ backendContext.fGetProc = mGrGetProc;
+ backendContext.fProtectedContext = mIsProtected ? Protected::kYes : Protected::kNo;
+ backendContext.fDeviceLostContext = this; // VulkanInterface is long-lived
+ backendContext.fDeviceLostProc = onVkDeviceFault;
+ return backendContext;
+};
+
+VkSemaphore VulkanInterface::createExportableSemaphore() {
+ VkExportSemaphoreCreateInfo exportInfo;
+ exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO;
+ exportInfo.pNext = nullptr;
+ exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
+
+ VkSemaphoreCreateInfo semaphoreInfo;
+ semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+ semaphoreInfo.pNext = &exportInfo;
+ semaphoreInfo.flags = 0;
+
+ VkSemaphore semaphore;
+ VkResult err = mFuncs.vkCreateSemaphore(mDevice, &semaphoreInfo, nullptr, &semaphore);
+ if (VK_SUCCESS != err) {
+ ALOGE("%s: failed to create semaphore. err %d\n", __func__, err);
+ return VK_NULL_HANDLE;
+ }
+
+ return semaphore;
+}
+
+// syncFd cannot be <= 0
+VkSemaphore VulkanInterface::importSemaphoreFromSyncFd(int syncFd) {
+ VkSemaphoreCreateInfo semaphoreInfo;
+ semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+ semaphoreInfo.pNext = nullptr;
+ semaphoreInfo.flags = 0;
+
+ VkSemaphore semaphore;
+ VkResult err = mFuncs.vkCreateSemaphore(mDevice, &semaphoreInfo, nullptr, &semaphore);
+ if (VK_SUCCESS != err) {
+ ALOGE("%s: failed to create import semaphore", __func__);
+ return VK_NULL_HANDLE;
+ }
+
+ VkImportSemaphoreFdInfoKHR importInfo;
+ importInfo.sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR;
+ importInfo.pNext = nullptr;
+ importInfo.semaphore = semaphore;
+ importInfo.flags = VK_SEMAPHORE_IMPORT_TEMPORARY_BIT;
+ importInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
+ importInfo.fd = syncFd;
+
+ err = mFuncs.vkImportSemaphoreFdKHR(mDevice, &importInfo);
+ if (VK_SUCCESS != err) {
+ mFuncs.vkDestroySemaphore(mDevice, semaphore, nullptr);
+ ALOGE("%s: failed to import semaphore", __func__);
+ return VK_NULL_HANDLE;
+ }
+
+ return semaphore;
+}
+
+int VulkanInterface::exportSemaphoreSyncFd(VkSemaphore semaphore) {
+ int res;
+
+ VkSemaphoreGetFdInfoKHR getFdInfo;
+ getFdInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR;
+ getFdInfo.pNext = nullptr;
+ getFdInfo.semaphore = semaphore;
+ getFdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
+ VkResult err = mFuncs.vkGetSemaphoreFdKHR(mDevice, &getFdInfo, &res);
+ if (VK_SUCCESS != err) {
+ ALOGE("%s: failed to export semaphore, err: %d", __func__, err);
+ return -1;
+ }
+ return res;
+}
+
+void VulkanInterface::destroySemaphore(VkSemaphore semaphore) {
+ mFuncs.vkDestroySemaphore(mDevice, semaphore, nullptr);
+}
+
+void VulkanInterface::onVkDeviceFault(void* callbackContext, const std::string& description,
+ const std::vector<VkDeviceFaultAddressInfoEXT>& addressInfos,
+ const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos,
+ const std::vector<std::byte>& vendorBinaryData) {
+ VulkanInterface* interface = static_cast<VulkanInterface*>(callbackContext);
+ const std::string protectedStr = interface->mIsProtected ? "protected" : "non-protected";
+ // The final crash string should contain as much differentiating info as possible, up to 1024
+ // bytes. As this final message is constructed, the same information is also dumped to the logs
+ // but in a more verbose format. Building the crash string is unsightly, so the clearer logging
+ // statement is always placed first to give context.
+ ALOGE("VK_ERROR_DEVICE_LOST (%s context): %s", protectedStr.c_str(), description.c_str());
+ std::stringstream crashMsg;
+ crashMsg << "VK_ERROR_DEVICE_LOST (" << protectedStr;
+
+ if (!addressInfos.empty()) {
+ ALOGE("%zu VkDeviceFaultAddressInfoEXT:", addressInfos.size());
+ crashMsg << ", " << addressInfos.size() << " address info (";
+ for (VkDeviceFaultAddressInfoEXT addressInfo : addressInfos) {
+ ALOGE(" addressType: %d", (int)addressInfo.addressType);
+ ALOGE(" reportedAddress: %" PRIu64, addressInfo.reportedAddress);
+ ALOGE(" addressPrecision: %" PRIu64, addressInfo.addressPrecision);
+ crashMsg << addressInfo.addressType << ":" << addressInfo.reportedAddress << ":"
+ << addressInfo.addressPrecision << ", ";
+ }
+ crashMsg.seekp(-2, crashMsg.cur); // Move back to overwrite trailing ", "
+ crashMsg << ")";
+ }
+
+ if (!vendorInfos.empty()) {
+ ALOGE("%zu VkDeviceFaultVendorInfoEXT:", vendorInfos.size());
+ crashMsg << ", " << vendorInfos.size() << " vendor info (";
+ for (VkDeviceFaultVendorInfoEXT vendorInfo : vendorInfos) {
+ ALOGE(" description: %s", vendorInfo.description);
+ ALOGE(" vendorFaultCode: %" PRIu64, vendorInfo.vendorFaultCode);
+ ALOGE(" vendorFaultData: %" PRIu64, vendorInfo.vendorFaultData);
+ // Omit descriptions for individual vendor info structs in the crash string, as the
+ // fault code and fault data fields should be enough for clustering, and the verbosity
+ // isn't worth it. Additionally, vendors may just set the general description field of
+ // the overall fault to the description of the first element in this list, and that
+ // overall description will be placed at the end of the crash string.
+ crashMsg << vendorInfo.vendorFaultCode << ":" << vendorInfo.vendorFaultData << ", ";
+ }
+ crashMsg.seekp(-2, crashMsg.cur); // Move back to overwrite trailing ", "
+ crashMsg << ")";
+ }
+
+ if (!vendorBinaryData.empty()) {
+ // TODO: b/322830575 - Log in base64, or dump directly to a file that gets put in bugreports
+ ALOGE("%zu bytes of vendor-specific binary data (please notify Android's Core Graphics"
+ " Stack team if you observe this message).",
+ vendorBinaryData.size());
+ crashMsg << ", " << vendorBinaryData.size() << " bytes binary";
+ }
+
+ crashMsg << "): " << description;
+ LOG_ALWAYS_FATAL("%s", crashMsg.str().c_str());
+};
+
+static GrVkGetProc sGetProc = [](const char* proc_name, VkInstance instance, VkDevice device) {
+ if (device != VK_NULL_HANDLE) {
+ return vkGetDeviceProcAddr(device, proc_name);
+ }
+ return vkGetInstanceProcAddr(instance, proc_name);
+};
+
+#define BAIL(fmt, ...) \
+ { \
+ ALOGE("%s: " fmt ", bailing", __func__, ##__VA_ARGS__); \
+ return; \
+ }
+
+#define CHECK_NONNULL(expr) \
+ if ((expr) == nullptr) { \
+ BAIL("[%s] null", #expr); \
+ }
+
+#define VK_CHECK(expr) \
+ if ((expr) != VK_SUCCESS) { \
+ BAIL("[%s] failed. err = %d", #expr, expr); \
+ return; \
+ }
+
+#define VK_GET_PROC(F) \
+ PFN_vk##F vk##F = (PFN_vk##F)vkGetInstanceProcAddr(VK_NULL_HANDLE, "vk" #F); \
+ CHECK_NONNULL(vk##F)
+#define VK_GET_INST_PROC(instance, F) \
+ PFN_vk##F vk##F = (PFN_vk##F)vkGetInstanceProcAddr(instance, "vk" #F); \
+ CHECK_NONNULL(vk##F)
+#define VK_GET_DEV_PROC(device, F) \
+ PFN_vk##F vk##F = (PFN_vk##F)vkGetDeviceProcAddr(device, "vk" #F); \
+ CHECK_NONNULL(vk##F)
+
+void VulkanInterface::init(bool protectedContent) {
+ if (isInitialized()) {
+ ALOGW("Called init on already initialized VulkanInterface");
+ return;
+ }
+
+ const nsecs_t timeBefore = systemTime();
+
+ VK_GET_PROC(EnumerateInstanceVersion);
+ uint32_t instanceVersion;
+ VK_CHECK(vkEnumerateInstanceVersion(&instanceVersion));
+
+ if (instanceVersion < VK_MAKE_VERSION(1, 1, 0)) {
+ return;
+ }
+
+ const VkApplicationInfo appInfo = {
+ VK_STRUCTURE_TYPE_APPLICATION_INFO, nullptr, "surfaceflinger", 0, "android platform", 0,
+ VK_MAKE_VERSION(1, 1, 0),
+ };
+
+ VK_GET_PROC(EnumerateInstanceExtensionProperties);
+
+ uint32_t extensionCount = 0;
+ VK_CHECK(vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr));
+ std::vector<VkExtensionProperties> instanceExtensions(extensionCount);
+ VK_CHECK(vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount,
+ instanceExtensions.data()));
+ std::vector<const char*> enabledInstanceExtensionNames;
+ enabledInstanceExtensionNames.reserve(instanceExtensions.size());
+ mInstanceExtensionNames.reserve(instanceExtensions.size());
+ for (const auto& instExt : instanceExtensions) {
+ enabledInstanceExtensionNames.push_back(instExt.extensionName);
+ mInstanceExtensionNames.push_back(instExt.extensionName);
+ }
+
+ const VkInstanceCreateInfo instanceCreateInfo = {
+ VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+ nullptr,
+ 0,
+ &appInfo,
+ 0,
+ nullptr,
+ (uint32_t)enabledInstanceExtensionNames.size(),
+ enabledInstanceExtensionNames.data(),
+ };
+
+ VK_GET_PROC(CreateInstance);
+ VkInstance instance;
+ VK_CHECK(vkCreateInstance(&instanceCreateInfo, nullptr, &instance));
+
+ VK_GET_INST_PROC(instance, DestroyInstance);
+ mFuncs.vkDestroyInstance = vkDestroyInstance;
+ VK_GET_INST_PROC(instance, EnumeratePhysicalDevices);
+ VK_GET_INST_PROC(instance, EnumerateDeviceExtensionProperties);
+ VK_GET_INST_PROC(instance, GetPhysicalDeviceProperties2);
+ VK_GET_INST_PROC(instance, GetPhysicalDeviceExternalSemaphoreProperties);
+ VK_GET_INST_PROC(instance, GetPhysicalDeviceQueueFamilyProperties2);
+ VK_GET_INST_PROC(instance, GetPhysicalDeviceFeatures2);
+ VK_GET_INST_PROC(instance, CreateDevice);
+
+ uint32_t physdevCount;
+ VK_CHECK(vkEnumeratePhysicalDevices(instance, &physdevCount, nullptr));
+ if (physdevCount == 0) {
+ BAIL("Could not find any physical devices");
+ }
+
+ physdevCount = 1;
+ VkPhysicalDevice physicalDevice;
+ VkResult enumeratePhysDevsErr =
+ vkEnumeratePhysicalDevices(instance, &physdevCount, &physicalDevice);
+ if (enumeratePhysDevsErr != VK_SUCCESS && VK_INCOMPLETE != enumeratePhysDevsErr) {
+ BAIL("vkEnumeratePhysicalDevices failed with non-VK_INCOMPLETE error: %d",
+ enumeratePhysDevsErr);
+ }
+
+ VkPhysicalDeviceProperties2 physDevProps = {
+ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
+ 0,
+ {},
+ };
+ VkPhysicalDeviceProtectedMemoryProperties protMemProps = {
+ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_PROPERTIES,
+ 0,
+ {},
+ };
+
+ if (protectedContent) {
+ physDevProps.pNext = &protMemProps;
+ }
+
+ vkGetPhysicalDeviceProperties2(physicalDevice, &physDevProps);
+ if (physDevProps.properties.apiVersion < VK_MAKE_VERSION(1, 1, 0)) {
+ BAIL("Could not find a Vulkan 1.1+ physical device");
+ }
+
+ if (physDevProps.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU) {
+ // TODO: b/326633110 - SkiaVK is not working correctly on swiftshader path.
+ BAIL("CPU implementations of Vulkan is not supported");
+ }
+
+ // Check for syncfd support. Bail if we cannot both import and export them.
+ VkPhysicalDeviceExternalSemaphoreInfo semInfo = {
+ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO,
+ nullptr,
+ VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
+ };
+ VkExternalSemaphoreProperties semProps = {
+ VK_STRUCTURE_TYPE_EXTERNAL_SEMAPHORE_PROPERTIES, nullptr, 0, 0, 0,
+ };
+ vkGetPhysicalDeviceExternalSemaphoreProperties(physicalDevice, &semInfo, &semProps);
+
+ bool sufficientSemaphoreSyncFdSupport = (semProps.exportFromImportedHandleTypes &
+ VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT) &&
+ (semProps.compatibleHandleTypes & VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT) &&
+ (semProps.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT) &&
+ (semProps.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT);
+
+ if (!sufficientSemaphoreSyncFdSupport) {
+ BAIL("Vulkan device does not support sufficient external semaphore sync fd features. "
+ "exportFromImportedHandleTypes 0x%x (needed 0x%x) "
+ "compatibleHandleTypes 0x%x (needed 0x%x) "
+ "externalSemaphoreFeatures 0x%x (needed 0x%x) ",
+ semProps.exportFromImportedHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
+ semProps.compatibleHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
+ semProps.externalSemaphoreFeatures,
+ VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT |
+ VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT);
+ } else {
+ ALOGD("Vulkan device supports sufficient external semaphore sync fd features. "
+ "exportFromImportedHandleTypes 0x%x (needed 0x%x) "
+ "compatibleHandleTypes 0x%x (needed 0x%x) "
+ "externalSemaphoreFeatures 0x%x (needed 0x%x) ",
+ semProps.exportFromImportedHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
+ semProps.compatibleHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
+ semProps.externalSemaphoreFeatures,
+ VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT |
+ VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT);
+ }
+
+ uint32_t queueCount;
+ vkGetPhysicalDeviceQueueFamilyProperties2(physicalDevice, &queueCount, nullptr);
+ if (queueCount == 0) {
+ BAIL("Could not find queues for physical device");
+ }
+
+ std::vector<VkQueueFamilyProperties2> queueProps(queueCount);
+ std::vector<VkQueueFamilyGlobalPriorityPropertiesEXT> queuePriorityProps(queueCount);
+ VkQueueGlobalPriorityKHR queuePriority = VK_QUEUE_GLOBAL_PRIORITY_MEDIUM_KHR;
+ // Even though we don't yet know if the VK_EXT_global_priority extension is available,
+ // we can safely add the request to the pNext chain, and if the extension is not
+ // available, it will be ignored.
+ for (uint32_t i = 0; i < queueCount; ++i) {
+ queuePriorityProps[i].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_GLOBAL_PRIORITY_PROPERTIES_EXT;
+ queuePriorityProps[i].pNext = nullptr;
+ queueProps[i].pNext = &queuePriorityProps[i];
+ }
+ vkGetPhysicalDeviceQueueFamilyProperties2(physicalDevice, &queueCount, queueProps.data());
+
+ int graphicsQueueIndex = -1;
+ for (uint32_t i = 0; i < queueCount; ++i) {
+ // Look at potential answers to the VK_EXT_global_priority query. If answers were
+ // provided, we may adjust the queuePriority.
+ if (queueProps[i].queueFamilyProperties.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
+ for (uint32_t j = 0; j < queuePriorityProps[i].priorityCount; j++) {
+ if (queuePriorityProps[i].priorities[j] > queuePriority) {
+ queuePriority = queuePriorityProps[i].priorities[j];
+ }
+ }
+ if (queuePriority == VK_QUEUE_GLOBAL_PRIORITY_REALTIME_KHR) {
+ mIsRealtimePriority = true;
+ }
+ graphicsQueueIndex = i;
+ break;
+ }
+ }
+
+ if (graphicsQueueIndex == -1) {
+ BAIL("Could not find a graphics queue family");
+ }
+
+ uint32_t deviceExtensionCount;
+ VK_CHECK(vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &deviceExtensionCount,
+ nullptr));
+ std::vector<VkExtensionProperties> deviceExtensions(deviceExtensionCount);
+ VK_CHECK(vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &deviceExtensionCount,
+ deviceExtensions.data()));
+
+ std::vector<const char*> enabledDeviceExtensionNames;
+ enabledDeviceExtensionNames.reserve(deviceExtensions.size());
+ mDeviceExtensionNames.reserve(deviceExtensions.size());
+ for (const auto& devExt : deviceExtensions) {
+ enabledDeviceExtensionNames.push_back(devExt.extensionName);
+ mDeviceExtensionNames.push_back(devExt.extensionName);
+ }
+
+ mGrExtensions.init(sGetProc, instance, physicalDevice, enabledInstanceExtensionNames.size(),
+ enabledInstanceExtensionNames.data(), enabledDeviceExtensionNames.size(),
+ enabledDeviceExtensionNames.data());
+
+ if (!mGrExtensions.hasExtension(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, 1)) {
+ BAIL("Vulkan driver doesn't support external semaphore fd");
+ }
+
+ mPhysicalDeviceFeatures2 = new VkPhysicalDeviceFeatures2;
+ mPhysicalDeviceFeatures2->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
+ mPhysicalDeviceFeatures2->pNext = nullptr;
+
+ mSamplerYcbcrConversionFeatures = new VkPhysicalDeviceSamplerYcbcrConversionFeatures;
+ mSamplerYcbcrConversionFeatures->sType =
+ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES;
+ mSamplerYcbcrConversionFeatures->pNext = nullptr;
+
+ mPhysicalDeviceFeatures2->pNext = mSamplerYcbcrConversionFeatures;
+ void** tailPnext = &mSamplerYcbcrConversionFeatures->pNext;
+
+ if (protectedContent) {
+ mProtectedMemoryFeatures = new VkPhysicalDeviceProtectedMemoryFeatures;
+ mProtectedMemoryFeatures->sType =
+ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES;
+ mProtectedMemoryFeatures->pNext = nullptr;
+ *tailPnext = mProtectedMemoryFeatures;
+ tailPnext = &mProtectedMemoryFeatures->pNext;
+ }
+
+ if (mGrExtensions.hasExtension(VK_EXT_DEVICE_FAULT_EXTENSION_NAME, 1)) {
+ mDeviceFaultFeatures = new VkPhysicalDeviceFaultFeaturesEXT;
+ mDeviceFaultFeatures->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FAULT_FEATURES_EXT;
+ mDeviceFaultFeatures->pNext = nullptr;
+ *tailPnext = mDeviceFaultFeatures;
+ tailPnext = &mDeviceFaultFeatures->pNext;
+ }
+
+ vkGetPhysicalDeviceFeatures2(physicalDevice, mPhysicalDeviceFeatures2);
+ // Looks like this would slow things down and we can't depend on it on all platforms
+ mPhysicalDeviceFeatures2->features.robustBufferAccess = VK_FALSE;
+
+ if (protectedContent && !mProtectedMemoryFeatures->protectedMemory) {
+ BAIL("Protected memory not supported");
+ }
+
+ float queuePriorities[1] = {0.0f};
+ void* queueNextPtr = nullptr;
+
+ VkDeviceQueueGlobalPriorityCreateInfoEXT queuePriorityCreateInfo = {
+ VK_STRUCTURE_TYPE_DEVICE_QUEUE_GLOBAL_PRIORITY_CREATE_INFO_EXT,
+ nullptr,
+ // If queue priority is supported, RE should always have realtime priority.
+ queuePriority,
+ };
+
+ if (mGrExtensions.hasExtension(VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME, 2)) {
+ queueNextPtr = &queuePriorityCreateInfo;
+ }
+
+ VkDeviceQueueCreateFlags deviceQueueCreateFlags =
+ (VkDeviceQueueCreateFlags)(protectedContent ? VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT : 0);
+
+ const VkDeviceQueueCreateInfo queueInfo = {
+ VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
+ queueNextPtr,
+ deviceQueueCreateFlags,
+ (uint32_t)graphicsQueueIndex,
+ 1,
+ queuePriorities,
+ };
+
+ const VkDeviceCreateInfo deviceInfo = {
+ VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
+ mPhysicalDeviceFeatures2,
+ 0,
+ 1,
+ &queueInfo,
+ 0,
+ nullptr,
+ (uint32_t)enabledDeviceExtensionNames.size(),
+ enabledDeviceExtensionNames.data(),
+ nullptr,
+ };
+
+ ALOGD("Trying to create Vk device with protectedContent=%d", protectedContent);
+ VkDevice device;
+ VK_CHECK(vkCreateDevice(physicalDevice, &deviceInfo, nullptr, &device));
+ ALOGD("Trying to create Vk device with protectedContent=%d (success)", protectedContent);
+
+ VkQueue graphicsQueue;
+ VK_GET_DEV_PROC(device, GetDeviceQueue2);
+ const VkDeviceQueueInfo2 deviceQueueInfo2 = {VK_STRUCTURE_TYPE_DEVICE_QUEUE_INFO_2, nullptr,
+ deviceQueueCreateFlags,
+ (uint32_t)graphicsQueueIndex, 0};
+ vkGetDeviceQueue2(device, &deviceQueueInfo2, &graphicsQueue);
+
+ VK_GET_DEV_PROC(device, DeviceWaitIdle);
+ VK_GET_DEV_PROC(device, DestroyDevice);
+ mFuncs.vkDeviceWaitIdle = vkDeviceWaitIdle;
+ mFuncs.vkDestroyDevice = vkDestroyDevice;
+
+ VK_GET_DEV_PROC(device, CreateSemaphore);
+ VK_GET_DEV_PROC(device, ImportSemaphoreFdKHR);
+ VK_GET_DEV_PROC(device, GetSemaphoreFdKHR);
+ VK_GET_DEV_PROC(device, DestroySemaphore);
+ mFuncs.vkCreateSemaphore = vkCreateSemaphore;
+ mFuncs.vkImportSemaphoreFdKHR = vkImportSemaphoreFdKHR;
+ mFuncs.vkGetSemaphoreFdKHR = vkGetSemaphoreFdKHR;
+ mFuncs.vkDestroySemaphore = vkDestroySemaphore;
+
+ // At this point, everything's succeeded and we can continue
+ mInitialized = true;
+ mInstance = instance;
+ mPhysicalDevice = physicalDevice;
+ mDevice = device;
+ mQueue = graphicsQueue;
+ mQueueIndex = graphicsQueueIndex;
+ mApiVersion = physDevProps.properties.apiVersion;
+ // grExtensions already constructed
+ // feature pointers already constructed
+ mGrGetProc = sGetProc;
+ mIsProtected = protectedContent;
+ // mIsRealtimePriority already initialized by constructor
+ // funcs already initialized
+
+ const nsecs_t timeAfter = systemTime();
+ const float initTimeMs = static_cast<float>(timeAfter - timeBefore) / 1.0E6;
+ ALOGD("%s: Success init Vulkan interface in %f ms", __func__, initTimeMs);
+}
+
+// TODO: b/293371537 - Iterate on this.
+// Currently unused, but copied over from its original location for potential future use. This
+// should likely be improved to walk the pNext chain of mPhysicalDeviceFeatures2 and free everything
+// like HWUI's VulkanManager. Also, not all fields are being reset.
+void VulkanInterface::teardown() {
+ mInitialized = false;
+
+ if (mDevice != VK_NULL_HANDLE) {
+ mFuncs.vkDeviceWaitIdle(mDevice);
+ mFuncs.vkDestroyDevice(mDevice, nullptr);
+ mDevice = VK_NULL_HANDLE;
+ }
+ if (mInstance != VK_NULL_HANDLE) {
+ mFuncs.vkDestroyInstance(mInstance, nullptr);
+ mInstance = VK_NULL_HANDLE;
+ }
+
+ if (mProtectedMemoryFeatures) {
+ delete mProtectedMemoryFeatures;
+ }
+
+ if (mSamplerYcbcrConversionFeatures) {
+ delete mSamplerYcbcrConversionFeatures;
+ }
+
+ if (mPhysicalDeviceFeatures2) {
+ delete mPhysicalDeviceFeatures2;
+ }
+
+ if (mDeviceFaultFeatures) {
+ delete mDeviceFaultFeatures;
+ }
+
+ mSamplerYcbcrConversionFeatures = nullptr;
+ mPhysicalDeviceFeatures2 = nullptr;
+ mProtectedMemoryFeatures = nullptr;
+ mDeviceFaultFeatures = nullptr;
+}
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
diff --git a/libs/renderengine/skia/VulkanInterface.h b/libs/renderengine/skia/VulkanInterface.h
new file mode 100644
index 0000000..512e62c
--- /dev/null
+++ b/libs/renderengine/skia/VulkanInterface.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2024 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 <include/gpu/vk/GrVkBackendContext.h>
+#include <include/gpu/vk/GrVkExtensions.h>
+
+#include <vulkan/vulkan.h>
+
+using namespace skgpu;
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+class VulkanInterface {
+public:
+ // Create an uninitialized interface. Initialize with `init`.
+ VulkanInterface() = default;
+ ~VulkanInterface() = default;
+ VulkanInterface(const VulkanInterface&) = delete;
+ VulkanInterface& operator=(const VulkanInterface&) = delete;
+ VulkanInterface& operator=(VulkanInterface&&) = delete;
+
+ void init(bool protectedContent = false);
+ void teardown();
+
+ // TODO: b/293371537 - Graphite variant (external/skia/include/gpu/vk/VulkanBackendContext.h)
+ GrVkBackendContext getGaneshBackendContext();
+ VkSemaphore createExportableSemaphore();
+ VkSemaphore importSemaphoreFromSyncFd(int syncFd);
+ int exportSemaphoreSyncFd(VkSemaphore semaphore);
+ void destroySemaphore(VkSemaphore semaphore);
+
+ bool isInitialized() const { return mInitialized; }
+ bool isRealtimePriority() const { return mIsRealtimePriority; }
+ const std::vector<std::string>& getInstanceExtensionNames() { return mInstanceExtensionNames; }
+ const std::vector<std::string>& getDeviceExtensionNames() { return mDeviceExtensionNames; }
+
+private:
+ struct VulkanFuncs {
+ PFN_vkCreateSemaphore vkCreateSemaphore = nullptr;
+ PFN_vkImportSemaphoreFdKHR vkImportSemaphoreFdKHR = nullptr;
+ PFN_vkGetSemaphoreFdKHR vkGetSemaphoreFdKHR = nullptr;
+ PFN_vkDestroySemaphore vkDestroySemaphore = nullptr;
+
+ PFN_vkDeviceWaitIdle vkDeviceWaitIdle = nullptr;
+ PFN_vkDestroyDevice vkDestroyDevice = nullptr;
+ PFN_vkDestroyInstance vkDestroyInstance = nullptr;
+ };
+
+ static void onVkDeviceFault(void* callbackContext, const std::string& description,
+ const std::vector<VkDeviceFaultAddressInfoEXT>& addressInfos,
+ const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos,
+ const std::vector<std::byte>& vendorBinaryData);
+
+ bool mInitialized = false;
+ VkInstance mInstance = VK_NULL_HANDLE;
+ VkPhysicalDevice mPhysicalDevice = VK_NULL_HANDLE;
+ VkDevice mDevice = VK_NULL_HANDLE;
+ VkQueue mQueue = VK_NULL_HANDLE;
+ int mQueueIndex = 0;
+ uint32_t mApiVersion = 0;
+ GrVkExtensions mGrExtensions;
+ VkPhysicalDeviceFeatures2* mPhysicalDeviceFeatures2 = nullptr;
+ VkPhysicalDeviceSamplerYcbcrConversionFeatures* mSamplerYcbcrConversionFeatures = nullptr;
+ VkPhysicalDeviceProtectedMemoryFeatures* mProtectedMemoryFeatures = nullptr;
+ VkPhysicalDeviceFaultFeaturesEXT* mDeviceFaultFeatures = nullptr;
+ GrVkGetProc mGrGetProc = nullptr;
+ bool mIsProtected = false;
+ bool mIsRealtimePriority = false;
+
+ VulkanFuncs mFuncs;
+
+ std::vector<std::string> mInstanceExtensionNames;
+ std::vector<std::string> mDeviceExtensionNames;
+};
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
diff --git a/libs/renderengine/skia/compat/GaneshGpuContext.cpp b/libs/renderengine/skia/compat/GaneshGpuContext.cpp
new file mode 100644
index 0000000..51c6a6c
--- /dev/null
+++ b/libs/renderengine/skia/compat/GaneshGpuContext.cpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2024 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 "GaneshGpuContext.h"
+
+#include <include/core/SkImageInfo.h>
+#include <include/core/SkSurface.h>
+#include <include/core/SkTraceMemoryDump.h>
+#include <include/gpu/GrDirectContext.h>
+#include <include/gpu/GrTypes.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <include/gpu/ganesh/gl/GrGLDirectContext.h>
+#include <include/gpu/ganesh/vk/GrVkDirectContext.h>
+#include <include/gpu/gl/GrGLInterface.h>
+#include <include/gpu/vk/GrVkBackendContext.h>
+
+#include <android-base/macros.h>
+#include <log/log_main.h>
+
+namespace android::renderengine::skia {
+
+namespace {
+// TODO: b/293371537 - Graphite variant.
+static GrContextOptions ganeshOptions(GrContextOptions::PersistentCache& skSLCacheMonitor) {
+ GrContextOptions options;
+ options.fDisableDriverCorrectnessWorkarounds = true;
+ options.fDisableDistanceFieldPaths = true;
+ options.fReducedShaderVariations = true;
+ options.fPersistentCache = &skSLCacheMonitor;
+ return options;
+}
+} // namespace
+
+std::unique_ptr<SkiaGpuContext> SkiaGpuContext::MakeGL_Ganesh(
+ sk_sp<const GrGLInterface> glInterface,
+ GrContextOptions::PersistentCache& skSLCacheMonitor) {
+ return std::make_unique<GaneshGpuContext>(
+ GrDirectContexts::MakeGL(glInterface, ganeshOptions(skSLCacheMonitor)));
+}
+
+std::unique_ptr<SkiaGpuContext> SkiaGpuContext::MakeVulkan_Ganesh(
+ const GrVkBackendContext& grVkBackendContext,
+ GrContextOptions::PersistentCache& skSLCacheMonitor) {
+ return std::make_unique<GaneshGpuContext>(
+ GrDirectContexts::MakeVulkan(grVkBackendContext, ganeshOptions(skSLCacheMonitor)));
+}
+
+GaneshGpuContext::GaneshGpuContext(sk_sp<GrDirectContext> grContext) : mGrContext(grContext) {
+ LOG_ALWAYS_FATAL_IF(mGrContext.get() == nullptr, "GrDirectContext creation failed");
+}
+
+sk_sp<GrDirectContext> GaneshGpuContext::grDirectContext() {
+ return mGrContext;
+}
+
+sk_sp<SkSurface> GaneshGpuContext::createRenderTarget(SkImageInfo imageInfo) {
+ constexpr int kSampleCount = 1; // enable AA
+ constexpr SkSurfaceProps* kProps = nullptr;
+ constexpr bool kMipmapped = false;
+ return SkSurfaces::RenderTarget(mGrContext.get(), skgpu::Budgeted::kNo, imageInfo, kSampleCount,
+ kTopLeft_GrSurfaceOrigin, kProps, kMipmapped,
+ mGrContext->supportsProtectedContent());
+}
+
+size_t GaneshGpuContext::getMaxRenderTargetSize() const {
+ return mGrContext->maxRenderTargetSize();
+};
+
+size_t GaneshGpuContext::getMaxTextureSize() const {
+ return mGrContext->maxTextureSize();
+};
+
+bool GaneshGpuContext::isAbandoned() {
+ return mGrContext->abandoned();
+}
+
+void GaneshGpuContext::setResourceCacheLimit(size_t maxResourceBytes) {
+ mGrContext->setResourceCacheLimit(maxResourceBytes);
+}
+
+void GaneshGpuContext::finishRenderingAndAbandonContext() {
+ mGrContext->flushAndSubmit(GrSyncCpu::kYes);
+ mGrContext->abandonContext();
+};
+
+void GaneshGpuContext::purgeUnlockedScratchResources() {
+ mGrContext->purgeUnlockedResources(GrPurgeResourceOptions::kScratchResourcesOnly);
+}
+
+void GaneshGpuContext::resetContextIfApplicable() {
+ mGrContext->resetContext(); // Only applicable to GL
+};
+
+void GaneshGpuContext::dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const {
+ mGrContext->dumpMemoryStatistics(traceMemoryDump);
+}
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/compat/GaneshGpuContext.h b/libs/renderengine/skia/compat/GaneshGpuContext.h
new file mode 100644
index 0000000..59001ec
--- /dev/null
+++ b/libs/renderengine/skia/compat/GaneshGpuContext.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2024 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 "SkiaGpuContext.h"
+
+#include <android-base/macros.h>
+
+namespace android::renderengine::skia {
+
+class GaneshGpuContext : public SkiaGpuContext {
+public:
+ GaneshGpuContext(sk_sp<GrDirectContext> grContext);
+ ~GaneshGpuContext() override = default;
+
+ sk_sp<GrDirectContext> grDirectContext() override;
+
+ sk_sp<SkSurface> createRenderTarget(SkImageInfo imageInfo) override;
+
+ size_t getMaxRenderTargetSize() const override;
+ size_t getMaxTextureSize() const override;
+ bool isAbandoned() override;
+ void setResourceCacheLimit(size_t maxResourceBytes) override;
+
+ void finishRenderingAndAbandonContext() override;
+ void purgeUnlockedScratchResources() override;
+ void resetContextIfApplicable() override;
+
+ void dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const override;
+
+private:
+ DISALLOW_COPY_AND_ASSIGN(GaneshGpuContext);
+
+ const sk_sp<GrDirectContext> mGrContext;
+};
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/compat/SkiaGpuContext.h b/libs/renderengine/skia/compat/SkiaGpuContext.h
new file mode 100644
index 0000000..ba167f3
--- /dev/null
+++ b/libs/renderengine/skia/compat/SkiaGpuContext.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2024 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
+
+#undef LOG_TAG
+#define LOG_TAG "RenderEngine"
+
+#include <include/core/SkSurface.h>
+#include <include/gpu/GrDirectContext.h>
+#include <include/gpu/gl/GrGLInterface.h>
+#include <include/gpu/vk/GrVkBackendContext.h>
+
+#include <log/log.h>
+
+namespace android::renderengine::skia {
+
+/**
+ * Abstraction over Ganesh and Graphite's underlying context-like objects.
+ */
+class SkiaGpuContext {
+public:
+ static std::unique_ptr<SkiaGpuContext> MakeGL_Ganesh(
+ sk_sp<const GrGLInterface> glInterface,
+ GrContextOptions::PersistentCache& skSLCacheMonitor);
+
+ // TODO: b/293371537 - Graphite variant.
+ static std::unique_ptr<SkiaGpuContext> MakeVulkan_Ganesh(
+ const GrVkBackendContext& grVkBackendContext,
+ GrContextOptions::PersistentCache& skSLCacheMonitor);
+
+ virtual ~SkiaGpuContext() = default;
+
+ // TODO: b/293371537 - Maybe expose whether this SkiaGpuContext is using Ganesh or Graphite?
+ /**
+ * Only callable on Ganesh-backed instances of SkiaGpuContext, otherwise fatal.
+ */
+ virtual sk_sp<GrDirectContext> grDirectContext() {
+ LOG_ALWAYS_FATAL("grDirectContext() called on a non-Ganesh instance of SkiaGpuContext!");
+ }
+
+ /**
+ * Notes:
+ * - The surface doesn't count against Skia's caching budgets.
+ * - Protected status is set to match the implementation's underlying context.
+ * - The origin of the surface in texture space corresponds to the top-left content pixel.
+ * - AA is always enabled.
+ */
+ virtual sk_sp<SkSurface> createRenderTarget(SkImageInfo imageInfo) = 0;
+
+ virtual bool isAbandoned() = 0;
+ virtual size_t getMaxRenderTargetSize() const = 0;
+ virtual size_t getMaxTextureSize() const = 0;
+ virtual void setResourceCacheLimit(size_t maxResourceBytes) = 0;
+
+ virtual void finishRenderingAndAbandonContext() = 0;
+ virtual void purgeUnlockedScratchResources() = 0;
+ virtual void resetContextIfApplicable() = 0; // No-op outside of GL (&& Ganesh at this point.)
+
+ virtual void dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const = 0;
+};
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/filters/BlurFilter.h b/libs/renderengine/skia/filters/BlurFilter.h
index 9cddc75..180c922 100644
--- a/libs/renderengine/skia/filters/BlurFilter.h
+++ b/libs/renderengine/skia/filters/BlurFilter.h
@@ -21,6 +21,8 @@
#include <SkRuntimeEffect.h>
#include <SkSurface.h>
+#include "../compat/SkiaGpuContext.h"
+
using namespace std;
namespace android {
@@ -38,8 +40,9 @@
virtual ~BlurFilter(){}
// Execute blur, saving it to a texture
- virtual sk_sp<SkImage> generate(GrRecordingContext* context, const uint32_t radius,
- const sk_sp<SkImage> blurInput, const SkRect& blurRect) const = 0;
+ virtual sk_sp<SkImage> generate(SkiaGpuContext* context, const uint32_t radius,
+ const sk_sp<SkImage> blurInput,
+ const SkRect& blurRect) const = 0;
/**
* Draw the blurred content (from the generate method) into the canvas.
diff --git a/libs/renderengine/skia/filters/GaussianBlurFilter.cpp b/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
index e72c501..c9499cb 100644
--- a/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
+++ b/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
@@ -42,14 +42,13 @@
GaussianBlurFilter::GaussianBlurFilter(): BlurFilter(/* maxCrossFadeRadius= */ 0.0f) {}
-sk_sp<SkImage> GaussianBlurFilter::generate(GrRecordingContext* context, const uint32_t blurRadius,
- const sk_sp<SkImage> input, const SkRect& blurRect)
- const {
+sk_sp<SkImage> GaussianBlurFilter::generate(SkiaGpuContext* context, const uint32_t blurRadius,
+ const sk_sp<SkImage> input,
+ const SkRect& blurRect) const {
// Create blur surface with the bit depth and colorspace of the original surface
SkImageInfo scaledInfo = input->imageInfo().makeWH(std::ceil(blurRect.width() * kInputScale),
std::ceil(blurRect.height() * kInputScale));
- sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(context,
- skgpu::Budgeted::kNo, scaledInfo);
+ sk_sp<SkSurface> surface = context->createRenderTarget(scaledInfo);
SkPaint paint;
paint.setBlendMode(SkBlendMode::kSrc);
diff --git a/libs/renderengine/skia/filters/GaussianBlurFilter.h b/libs/renderengine/skia/filters/GaussianBlurFilter.h
index a4febd2..878ab21 100644
--- a/libs/renderengine/skia/filters/GaussianBlurFilter.h
+++ b/libs/renderengine/skia/filters/GaussianBlurFilter.h
@@ -37,9 +37,8 @@
virtual ~GaussianBlurFilter(){}
// Execute blur, saving it to a texture
- sk_sp<SkImage> generate(GrRecordingContext* context, const uint32_t radius,
+ sk_sp<SkImage> generate(SkiaGpuContext* context, const uint32_t radius,
const sk_sp<SkImage> blurInput, const SkRect& blurRect) const override;
-
};
} // namespace skia
diff --git a/libs/renderengine/skia/filters/KawaseBlurFilter.cpp b/libs/renderengine/skia/filters/KawaseBlurFilter.cpp
index 09f09a6..7a070d7 100644
--- a/libs/renderengine/skia/filters/KawaseBlurFilter.cpp
+++ b/libs/renderengine/skia/filters/KawaseBlurFilter.cpp
@@ -73,8 +73,7 @@
return surface->makeImageSnapshot();
}
-sk_sp<SkImage> KawaseBlurFilter::generate(GrRecordingContext* context,
- const uint32_t blurRadius,
+sk_sp<SkImage> KawaseBlurFilter::generate(SkiaGpuContext* context, const uint32_t blurRadius,
const sk_sp<SkImage> input,
const SkRect& blurRect) const {
LOG_ALWAYS_FATAL_IF(context == nullptr, "%s: Needs GPU context", __func__);
@@ -108,12 +107,7 @@
input->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linear, blurMatrix);
blurBuilder.uniform("in_blurOffset") = radiusByPasses * kInputScale;
- constexpr int kSampleCount = 1;
- constexpr bool kMipmapped = false;
- constexpr SkSurfaceProps* kProps = nullptr;
- sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(context, skgpu::Budgeted::kNo, scaledInfo,
- kSampleCount, kTopLeft_GrSurfaceOrigin,
- kProps, kMipmapped, input->isProtected());
+ sk_sp<SkSurface> surface = context->createRenderTarget(scaledInfo);
LOG_ALWAYS_FATAL_IF(!surface, "%s: Failed to create surface for blurring!", __func__);
sk_sp<SkImage> tmpBlur = makeImage(surface.get(), &blurBuilder);
diff --git a/libs/renderengine/skia/filters/KawaseBlurFilter.h b/libs/renderengine/skia/filters/KawaseBlurFilter.h
index 0ac5ac8..429a537 100644
--- a/libs/renderengine/skia/filters/KawaseBlurFilter.h
+++ b/libs/renderengine/skia/filters/KawaseBlurFilter.h
@@ -42,7 +42,7 @@
virtual ~KawaseBlurFilter(){}
// Execute blur, saving it to a texture
- sk_sp<SkImage> generate(GrRecordingContext* context, const uint32_t radius,
+ sk_sp<SkImage> generate(SkiaGpuContext* context, const uint32_t radius,
const sk_sp<SkImage> blurInput, const SkRect& blurRect) const override;
private:
diff --git a/libs/tracing_perfetto/.clang-format b/libs/tracing_perfetto/.clang-format
new file mode 100644
index 0000000..f397454
--- /dev/null
+++ b/libs/tracing_perfetto/.clang-format
@@ -0,0 +1,12 @@
+BasedOnStyle: Google
+AllowShortBlocksOnASingleLine: false
+AllowShortFunctionsOnASingleLine: false
+
+ColumnLimit: 80
+ContinuationIndentWidth: 4
+CommentPragmas: NOLINT:.*
+DerivePointerAlignment: false
+IndentWidth: 2
+PointerAlignment: Left
+UseTab: Never
+PenaltyExcessCharacter: 32
\ No newline at end of file
diff --git a/libs/tracing_perfetto/Android.bp b/libs/tracing_perfetto/Android.bp
new file mode 100644
index 0000000..3a4c869
--- /dev/null
+++ b/libs/tracing_perfetto/Android.bp
@@ -0,0 +1,49 @@
+// Copyright 2024 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_shared {
+ name: "libtracing_perfetto",
+ export_include_dirs: [
+ "include",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-enum-compare",
+ "-Wno-unused-function",
+ ],
+
+ srcs: [
+ "tracing_perfetto.cpp",
+ "tracing_perfetto_internal.cpp",
+ ],
+
+ shared_libs: [
+ "libcutils",
+ "libperfetto_c",
+ "android.os.flags-aconfig-cc-host",
+ ],
+
+ host_supported: true,
+}
diff --git a/libs/tracing_perfetto/OWNERS b/libs/tracing_perfetto/OWNERS
new file mode 100644
index 0000000..e2d4b46
--- /dev/null
+++ b/libs/tracing_perfetto/OWNERS
@@ -0,0 +1,2 @@
+zezeozue@google.com
+biswarupp@google.com
\ No newline at end of file
diff --git a/libs/tracing_perfetto/TEST_MAPPING b/libs/tracing_perfetto/TEST_MAPPING
new file mode 100644
index 0000000..1805e18
--- /dev/null
+++ b/libs/tracing_perfetto/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "postsubmit": [
+ {
+ "name": "libtracing_perfetto_tests"
+ }
+ ]
+}
diff --git a/libs/tracing_perfetto/include/trace_categories.h b/libs/tracing_perfetto/include/trace_categories.h
new file mode 100644
index 0000000..6d4168b
--- /dev/null
+++ b/libs/tracing_perfetto/include/trace_categories.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2024 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.
+ */
+
+#ifndef TRACE_CATEGORIES_H
+#define TRACE_CATEGORIES_H
+
+/**
+ * Keep these in sync with frameworks/base/core/java/android/os/Trace.java.
+ */
+#define TRACE_CATEGORY_ALWAYS (1 << 0)
+#define TRACE_CATEGORY_GRAPHICS (1 << 1)
+#define TRACE_CATEGORY_INPUT (1 << 2)
+#define TRACE_CATEGORY_VIEW (1 << 3)
+#define TRACE_CATEGORY_WEBVIEW (1 << 4)
+#define TRACE_CATEGORY_WINDOW_MANAGER (1 << 5)
+#define TRACE_CATEGORY_ACTIVITY_MANAGER (1 << 6)
+#define TRACE_CATEGORY_SYNC_MANAGER (1 << 7)
+#define TRACE_CATEGORY_AUDIO (1 << 8)
+#define TRACE_CATEGORY_VIDEO (1 << 9)
+#define TRACE_CATEGORY_CAMERA (1 << 10)
+#define TRACE_CATEGORY_HAL (1 << 11)
+#define TRACE_CATEGORY_APP (1 << 12)
+#define TRACE_CATEGORY_RESOURCES (1 << 13)
+#define TRACE_CATEGORY_DALVIK (1 << 14)
+#define TRACE_CATEGORY_RS (1 << 15)
+#define TRACE_CATEGORY_BIONIC (1 << 16)
+#define TRACE_CATEGORY_POWER (1 << 17)
+#define TRACE_CATEGORY_PACKAGE_MANAGER (1 << 18)
+#define TRACE_CATEGORY_SYSTEM_SERVER (1 << 19)
+#define TRACE_CATEGORY_DATABASE (1 << 20)
+#define TRACE_CATEGORY_NETWORK (1 << 21)
+#define TRACE_CATEGORY_ADB (1 << 22)
+#define TRACE_CATEGORY_VIBRATOR (1 << 23)
+#define TRACE_CATEGORY_AIDL (1 << 24)
+#define TRACE_CATEGORY_NNAPI (1 << 25)
+#define TRACE_CATEGORY_RRO (1 << 26)
+#define TRACE_CATEGORY_THERMAL (1 << 27)
+
+// Allow all categories except TRACE_CATEGORY_APP
+#define TRACE_CATEGORIES \
+ TRACE_CATEGORY_ALWAYS | TRACE_CATEGORY_GRAPHICS | TRACE_CATEGORY_INPUT | \
+ TRACE_CATEGORY_VIEW | TRACE_CATEGORY_WEBVIEW | \
+ TRACE_CATEGORY_WINDOW_MANAGER | TRACE_CATEGORY_ACTIVITY_MANAGER | \
+ TRACE_CATEGORY_SYNC_MANAGER | TRACE_CATEGORY_AUDIO | \
+ TRACE_CATEGORY_VIDEO | TRACE_CATEGORY_CAMERA | TRACE_CATEGORY_HAL | \
+ TRACE_CATEGORY_RESOURCES | TRACE_CATEGORY_DALVIK | TRACE_CATEGORY_RS | \
+ TRACE_CATEGORY_BIONIC | TRACE_CATEGORY_POWER | \
+ TRACE_CATEGORY_PACKAGE_MANAGER | TRACE_CATEGORY_SYSTEM_SERVER | \
+ TRACE_CATEGORY_DATABASE | TRACE_CATEGORY_NETWORK | TRACE_CATEGORY_ADB | \
+ TRACE_CATEGORY_VIBRATOR | TRACE_CATEGORY_AIDL | TRACE_CATEGORY_NNAPI | \
+ TRACE_CATEGORY_RRO | TRACE_CATEGORY_THERMAL
+#endif // TRACE_CATEGORIES_H
diff --git a/libs/tracing_perfetto/include/trace_result.h b/libs/tracing_perfetto/include/trace_result.h
new file mode 100644
index 0000000..f7581fc
--- /dev/null
+++ b/libs/tracing_perfetto/include/trace_result.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2024 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.
+ */
+
+#ifndef TRACE_RESULT_H
+#define TRACE_RESULT_H
+
+namespace tracing_perfetto {
+
+enum class Result {
+ SUCCESS,
+ NOT_SUPPORTED,
+ INVALID_INPUT,
+};
+
+}
+
+#endif // TRACE_RESULT_H
diff --git a/libs/tracing_perfetto/include/tracing_perfetto.h b/libs/tracing_perfetto/include/tracing_perfetto.h
new file mode 100644
index 0000000..4e3c83f
--- /dev/null
+++ b/libs/tracing_perfetto/include/tracing_perfetto.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2024 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.
+ */
+
+#ifndef TRACING_PERFETTO_H
+#define TRACING_PERFETTO_H
+
+#include <stdint.h>
+
+#include "trace_result.h"
+
+namespace tracing_perfetto {
+
+void registerWithPerfetto(bool test = false);
+
+Result traceBegin(uint64_t category, const char* name);
+
+Result traceEnd(uint64_t category);
+
+Result traceAsyncBegin(uint64_t category, const char* name, int32_t cookie);
+
+Result traceAsyncEnd(uint64_t category, const char* name, int32_t cookie);
+
+Result traceAsyncBeginForTrack(uint64_t category, const char* name,
+ const char* trackName, int32_t cookie);
+
+Result traceAsyncEndForTrack(uint64_t category, const char* trackName,
+ int32_t cookie);
+
+Result traceInstant(uint64_t category, const char* name);
+
+Result traceInstantForTrack(uint64_t category, const char* trackName,
+ const char* name);
+
+Result traceCounter(uint64_t category, const char* name, int64_t value);
+
+uint64_t getEnabledCategories();
+
+} // namespace tracing_perfetto
+
+#endif // TRACING_PERFETTO_H
diff --git a/libs/tracing_perfetto/tests/Android.bp b/libs/tracing_perfetto/tests/Android.bp
new file mode 100644
index 0000000..a35b0e0
--- /dev/null
+++ b/libs/tracing_perfetto/tests/Android.bp
@@ -0,0 +1,45 @@
+// Copyright 2024 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_test {
+ name: "libtracing_perfetto_tests",
+ static_libs: [
+ "libflagtest",
+ "libgmock",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ shared_libs: [
+ "android.os.flags-aconfig-cc-host",
+ "libbase",
+ "libperfetto_c",
+ "libtracing_perfetto",
+ ],
+ srcs: [
+ "tracing_perfetto_test.cpp",
+ "utils.cpp",
+ ],
+ test_suites: ["device-tests"],
+}
diff --git a/libs/tracing_perfetto/tests/tracing_perfetto_test.cpp b/libs/tracing_perfetto/tests/tracing_perfetto_test.cpp
new file mode 100644
index 0000000..7716b9a
--- /dev/null
+++ b/libs/tracing_perfetto/tests/tracing_perfetto_test.cpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2024 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 "tracing_perfetto.h"
+
+#include <thread>
+
+#include <android_os.h>
+#include <flag_macros.h>
+
+#include "gtest/gtest.h"
+#include "perfetto/public/abi/data_source_abi.h"
+#include "perfetto/public/abi/heap_buffer.h"
+#include "perfetto/public/abi/pb_decoder_abi.h"
+#include "perfetto/public/abi/tracing_session_abi.h"
+#include "perfetto/public/abi/track_event_abi.h"
+#include "perfetto/public/data_source.h"
+#include "perfetto/public/pb_decoder.h"
+#include "perfetto/public/producer.h"
+#include "perfetto/public/protos/config/trace_config.pzc.h"
+#include "perfetto/public/protos/trace/interned_data/interned_data.pzc.h"
+#include "perfetto/public/protos/trace/test_event.pzc.h"
+#include "perfetto/public/protos/trace/trace.pzc.h"
+#include "perfetto/public/protos/trace/trace_packet.pzc.h"
+#include "perfetto/public/protos/trace/track_event/debug_annotation.pzc.h"
+#include "perfetto/public/protos/trace/track_event/track_descriptor.pzc.h"
+#include "perfetto/public/protos/trace/track_event/track_event.pzc.h"
+#include "perfetto/public/protos/trace/trigger.pzc.h"
+#include "perfetto/public/te_category_macros.h"
+#include "perfetto/public/te_macros.h"
+#include "perfetto/public/track_event.h"
+#include "trace_categories.h"
+#include "utils.h"
+
+namespace tracing_perfetto {
+
+using ::perfetto::shlib::test_utils::AllFieldsWithId;
+using ::perfetto::shlib::test_utils::FieldView;
+using ::perfetto::shlib::test_utils::IdFieldView;
+using ::perfetto::shlib::test_utils::MsgField;
+using ::perfetto::shlib::test_utils::PbField;
+using ::perfetto::shlib::test_utils::StringField;
+using ::perfetto::shlib::test_utils::TracingSession;
+using ::perfetto::shlib::test_utils::VarIntField;
+using ::testing::_;
+using ::testing::ElementsAre;
+using ::testing::UnorderedElementsAre;
+
+const auto PERFETTO_SDK_TRACING = ACONFIG_FLAG(android::os, perfetto_sdk_tracing);
+
+class TracingPerfettoTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ tracing_perfetto::registerWithPerfetto(true /* test */);
+ }
+};
+
+// TODO(b/303199244): Add tests for all the library functions.
+
+TEST_F_WITH_FLAGS(TracingPerfettoTest, traceInstant,
+ REQUIRES_FLAGS_ENABLED(PERFETTO_SDK_TRACING)) {
+ TracingSession tracing_session =
+ TracingSession::Builder().set_data_source_name("track_event").Build();
+ tracing_perfetto::traceInstant(TRACE_CATEGORY_INPUT, "");
+
+ tracing_session.StopBlocking();
+ std::vector<uint8_t> data = tracing_session.ReadBlocking();
+ bool found = false;
+ for (struct PerfettoPbDecoderField trace_field : FieldView(data)) {
+ ASSERT_THAT(trace_field, PbField(perfetto_protos_Trace_packet_field_number,
+ MsgField(_)));
+ IdFieldView track_event(
+ trace_field, perfetto_protos_TracePacket_track_event_field_number);
+ if (track_event.size() == 0) {
+ continue;
+ }
+ found = true;
+ IdFieldView cat_iid_fields(
+ track_event.front(),
+ perfetto_protos_TrackEvent_category_iids_field_number);
+ ASSERT_THAT(cat_iid_fields, ElementsAre(VarIntField(_)));
+ uint64_t cat_iid = cat_iid_fields.front().value.integer64;
+ EXPECT_THAT(
+ trace_field,
+ AllFieldsWithId(
+ perfetto_protos_TracePacket_interned_data_field_number,
+ ElementsAre(AllFieldsWithId(
+ perfetto_protos_InternedData_event_categories_field_number,
+ ElementsAre(MsgField(UnorderedElementsAre(
+ PbField(perfetto_protos_EventCategory_iid_field_number,
+ VarIntField(cat_iid)),
+ PbField(perfetto_protos_EventCategory_name_field_number,
+ StringField("input")))))))));
+ }
+ EXPECT_TRUE(found);
+}
+
+} // namespace tracing_perfetto
\ No newline at end of file
diff --git a/libs/tracing_perfetto/tests/utils.cpp b/libs/tracing_perfetto/tests/utils.cpp
new file mode 100644
index 0000000..9c42028
--- /dev/null
+++ b/libs/tracing_perfetto/tests/utils.cpp
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2024 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.
+ */
+
+// Copied from //external/perfetto/src/shared_lib/test/utils.cc
+
+#include "utils.h"
+
+#include "perfetto/public/abi/heap_buffer.h"
+#include "perfetto/public/pb_msg.h"
+#include "perfetto/public/pb_utils.h"
+#include "perfetto/public/protos/config/data_source_config.pzc.h"
+#include "perfetto/public/protos/config/trace_config.pzc.h"
+#include "perfetto/public/protos/config/track_event/track_event_config.pzc.h"
+#include "perfetto/public/tracing_session.h"
+
+namespace perfetto {
+namespace shlib {
+namespace test_utils {
+namespace {
+
+std::string ToHexChars(uint8_t val) {
+ std::string ret;
+ uint8_t high_nibble = (val & 0xF0) >> 4;
+ uint8_t low_nibble = (val & 0xF);
+ static const char hex_chars[] = "0123456789ABCDEF";
+ ret.push_back(hex_chars[high_nibble]);
+ ret.push_back(hex_chars[low_nibble]);
+ return ret;
+}
+
+} // namespace
+
+TracingSession TracingSession::Builder::Build() {
+ struct PerfettoPbMsgWriter writer;
+ struct PerfettoHeapBuffer* hb = PerfettoHeapBufferCreate(&writer.writer);
+
+ struct perfetto_protos_TraceConfig cfg;
+ PerfettoPbMsgInit(&cfg.msg, &writer);
+
+ {
+ struct perfetto_protos_TraceConfig_BufferConfig buffers;
+ perfetto_protos_TraceConfig_begin_buffers(&cfg, &buffers);
+
+ perfetto_protos_TraceConfig_BufferConfig_set_size_kb(&buffers, 1024);
+
+ perfetto_protos_TraceConfig_end_buffers(&cfg, &buffers);
+ }
+
+ {
+ struct perfetto_protos_TraceConfig_DataSource data_sources;
+ perfetto_protos_TraceConfig_begin_data_sources(&cfg, &data_sources);
+
+ {
+ struct perfetto_protos_DataSourceConfig ds_cfg;
+ perfetto_protos_TraceConfig_DataSource_begin_config(&data_sources,
+ &ds_cfg);
+
+ perfetto_protos_DataSourceConfig_set_cstr_name(&ds_cfg,
+ data_source_name_.c_str());
+ if (!enabled_categories_.empty() && !disabled_categories_.empty()) {
+ perfetto_protos_TrackEventConfig te_cfg;
+ perfetto_protos_DataSourceConfig_begin_track_event_config(&ds_cfg,
+ &te_cfg);
+ for (const std::string& cat : enabled_categories_) {
+ perfetto_protos_TrackEventConfig_set_enabled_categories(
+ &te_cfg, cat.data(), cat.size());
+ }
+ for (const std::string& cat : disabled_categories_) {
+ perfetto_protos_TrackEventConfig_set_disabled_categories(
+ &te_cfg, cat.data(), cat.size());
+ }
+ perfetto_protos_DataSourceConfig_end_track_event_config(&ds_cfg,
+ &te_cfg);
+ }
+
+ perfetto_protos_TraceConfig_DataSource_end_config(&data_sources, &ds_cfg);
+ }
+
+ perfetto_protos_TraceConfig_end_data_sources(&cfg, &data_sources);
+ }
+ size_t cfg_size = PerfettoStreamWriterGetWrittenSize(&writer.writer);
+ std::unique_ptr<uint8_t[]> ser(new uint8_t[cfg_size]);
+ PerfettoHeapBufferCopyInto(hb, &writer.writer, ser.get(), cfg_size);
+ PerfettoHeapBufferDestroy(hb, &writer.writer);
+
+ struct PerfettoTracingSessionImpl* ts =
+ PerfettoTracingSessionCreate(PERFETTO_BACKEND_IN_PROCESS);
+
+ PerfettoTracingSessionSetup(ts, ser.get(), cfg_size);
+
+ PerfettoTracingSessionStartBlocking(ts);
+
+ return TracingSession::Adopt(ts);
+}
+
+TracingSession TracingSession::Adopt(struct PerfettoTracingSessionImpl* session) {
+ TracingSession ret;
+ ret.session_ = session;
+ ret.stopped_ = std::make_unique<WaitableEvent>();
+ PerfettoTracingSessionSetStopCb(
+ ret.session_,
+ [](struct PerfettoTracingSessionImpl*, void* arg) {
+ static_cast<WaitableEvent*>(arg)->Notify();
+ },
+ ret.stopped_.get());
+ return ret;
+}
+
+TracingSession::TracingSession(TracingSession&& other) noexcept {
+ session_ = other.session_;
+ other.session_ = nullptr;
+ stopped_ = std::move(other.stopped_);
+ other.stopped_ = nullptr;
+}
+
+TracingSession::~TracingSession() {
+ if (!session_) {
+ return;
+ }
+ if (!stopped_->IsNotified()) {
+ PerfettoTracingSessionStopBlocking(session_);
+ stopped_->WaitForNotification();
+ }
+ PerfettoTracingSessionDestroy(session_);
+}
+
+bool TracingSession::FlushBlocking(uint32_t timeout_ms) {
+ WaitableEvent notification;
+ bool result;
+ auto* cb = new std::function<void(bool)>([&](bool success) {
+ result = success;
+ notification.Notify();
+ });
+ PerfettoTracingSessionFlushAsync(
+ session_, timeout_ms,
+ [](PerfettoTracingSessionImpl*, bool success, void* user_arg) {
+ auto* f = reinterpret_cast<std::function<void(bool)>*>(user_arg);
+ (*f)(success);
+ delete f;
+ },
+ cb);
+ notification.WaitForNotification();
+ return result;
+}
+
+void TracingSession::WaitForStopped() {
+ stopped_->WaitForNotification();
+}
+
+void TracingSession::StopBlocking() {
+ PerfettoTracingSessionStopBlocking(session_);
+}
+
+std::vector<uint8_t> TracingSession::ReadBlocking() {
+ std::vector<uint8_t> data;
+ PerfettoTracingSessionReadTraceBlocking(
+ session_,
+ [](struct PerfettoTracingSessionImpl*, const void* trace_data,
+ size_t size, bool, void* user_arg) {
+ auto& dst = *static_cast<std::vector<uint8_t>*>(user_arg);
+ auto* src = static_cast<const uint8_t*>(trace_data);
+ dst.insert(dst.end(), src, src + size);
+ },
+ &data);
+ return data;
+}
+
+} // namespace test_utils
+} // namespace shlib
+} // namespace perfetto
+
+void PrintTo(const PerfettoPbDecoderField& field, std::ostream* pos) {
+ std::ostream& os = *pos;
+ PerfettoPbDecoderStatus status =
+ static_cast<PerfettoPbDecoderStatus>(field.status);
+ switch (status) {
+ case PERFETTO_PB_DECODER_ERROR:
+ os << "MALFORMED PROTOBUF";
+ break;
+ case PERFETTO_PB_DECODER_DONE:
+ os << "DECODER DONE";
+ break;
+ case PERFETTO_PB_DECODER_OK:
+ switch (field.wire_type) {
+ case PERFETTO_PB_WIRE_TYPE_DELIMITED:
+ os << "\"";
+ for (size_t i = 0; i < field.value.delimited.len; i++) {
+ os << perfetto::shlib::test_utils::ToHexChars(
+ field.value.delimited.start[i])
+ << " ";
+ }
+ os << "\"";
+ break;
+ case PERFETTO_PB_WIRE_TYPE_VARINT:
+ os << "varint: " << field.value.integer64;
+ break;
+ case PERFETTO_PB_WIRE_TYPE_FIXED32:
+ os << "fixed32: " << field.value.integer32;
+ break;
+ case PERFETTO_PB_WIRE_TYPE_FIXED64:
+ os << "fixed64: " << field.value.integer64;
+ break;
+ }
+ break;
+ }
+}
diff --git a/libs/tracing_perfetto/tests/utils.h b/libs/tracing_perfetto/tests/utils.h
new file mode 100644
index 0000000..4353554
--- /dev/null
+++ b/libs/tracing_perfetto/tests/utils.h
@@ -0,0 +1,452 @@
+/*
+ * Copyright 2024 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.
+ */
+
+// Copied from //external/perfetto/src/shared_lib/test/utils.h
+
+#ifndef UTILS_H
+#define UTILS_H
+
+#include <cassert>
+#include <condition_variable>
+#include <cstdint>
+#include <functional>
+#include <iterator>
+#include <memory>
+#include <mutex>
+#include <ostream>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock-matchers.h"
+#include "gmock/gmock-more-matchers.h"
+#include "gtest/gtest-matchers.h"
+#include "gtest/gtest.h"
+#include "perfetto/public/abi/pb_decoder_abi.h"
+#include "perfetto/public/pb_utils.h"
+#include "perfetto/public/tracing_session.h"
+
+// Pretty printer for gtest
+void PrintTo(const PerfettoPbDecoderField& field, std::ostream*);
+
+namespace perfetto {
+namespace shlib {
+namespace test_utils {
+
+class WaitableEvent {
+ public:
+ WaitableEvent() = default;
+ void Notify() {
+ std::unique_lock<std::mutex> lock(m_);
+ notified_ = true;
+ cv_.notify_one();
+ }
+ bool WaitForNotification() {
+ std::unique_lock<std::mutex> lock(m_);
+ cv_.wait(lock, [this] { return notified_; });
+ return notified_;
+ }
+ bool IsNotified() {
+ std::unique_lock<std::mutex> lock(m_);
+ return notified_;
+ }
+
+ private:
+ std::mutex m_;
+ std::condition_variable cv_;
+ bool notified_ = false;
+};
+
+class TracingSession {
+ public:
+ class Builder {
+ public:
+ Builder() = default;
+ Builder& set_data_source_name(std::string data_source_name) {
+ data_source_name_ = std::move(data_source_name);
+ return *this;
+ }
+ Builder& add_enabled_category(std::string category) {
+ enabled_categories_.push_back(std::move(category));
+ return *this;
+ }
+ Builder& add_disabled_category(std::string category) {
+ disabled_categories_.push_back(std::move(category));
+ return *this;
+ }
+ TracingSession Build();
+
+ private:
+ std::string data_source_name_;
+ std::vector<std::string> enabled_categories_;
+ std::vector<std::string> disabled_categories_;
+ };
+
+ static TracingSession Adopt(struct PerfettoTracingSessionImpl*);
+
+ TracingSession(TracingSession&&) noexcept;
+
+ ~TracingSession();
+
+ struct PerfettoTracingSessionImpl* session() const {
+ return session_;
+ }
+
+ bool FlushBlocking(uint32_t timeout_ms);
+ void WaitForStopped();
+ void StopBlocking();
+ std::vector<uint8_t> ReadBlocking();
+
+ private:
+ TracingSession() = default;
+ struct PerfettoTracingSessionImpl* session_;
+ std::unique_ptr<WaitableEvent> stopped_;
+};
+
+template <typename FieldSkipper>
+class FieldViewBase {
+ public:
+ class Iterator {
+ public:
+ using iterator_category = std::input_iterator_tag;
+ using value_type = const PerfettoPbDecoderField;
+ using pointer = value_type;
+ using reference = value_type;
+ reference operator*() const {
+ struct PerfettoPbDecoder decoder;
+ decoder.read_ptr = read_ptr_;
+ decoder.end_ptr = end_ptr_;
+ struct PerfettoPbDecoderField field;
+ do {
+ field = PerfettoPbDecoderParseField(&decoder);
+ } while (field.status == PERFETTO_PB_DECODER_OK &&
+ skipper_.ShouldSkip(field));
+ return field;
+ }
+ Iterator& operator++() {
+ struct PerfettoPbDecoder decoder;
+ decoder.read_ptr = read_ptr_;
+ decoder.end_ptr = end_ptr_;
+ PerfettoPbDecoderSkipField(&decoder);
+ read_ptr_ = decoder.read_ptr;
+ AdvanceToFirstInterestingField();
+ return *this;
+ }
+ Iterator operator++(int) {
+ Iterator tmp = *this;
+ ++(*this);
+ return tmp;
+ }
+
+ friend bool operator==(const Iterator& a, const Iterator& b) {
+ return a.read_ptr_ == b.read_ptr_;
+ }
+ friend bool operator!=(const Iterator& a, const Iterator& b) {
+ return a.read_ptr_ != b.read_ptr_;
+ }
+
+ private:
+ Iterator(const uint8_t* read_ptr, const uint8_t* end_ptr,
+ const FieldSkipper& skipper)
+ : read_ptr_(read_ptr), end_ptr_(end_ptr), skipper_(skipper) {
+ AdvanceToFirstInterestingField();
+ }
+ void AdvanceToFirstInterestingField() {
+ struct PerfettoPbDecoder decoder;
+ decoder.read_ptr = read_ptr_;
+ decoder.end_ptr = end_ptr_;
+ struct PerfettoPbDecoderField field;
+ const uint8_t* prev_read_ptr;
+ do {
+ prev_read_ptr = decoder.read_ptr;
+ field = PerfettoPbDecoderParseField(&decoder);
+ } while (field.status == PERFETTO_PB_DECODER_OK &&
+ skipper_.ShouldSkip(field));
+ if (field.status == PERFETTO_PB_DECODER_OK) {
+ read_ptr_ = prev_read_ptr;
+ } else {
+ read_ptr_ = decoder.read_ptr;
+ }
+ }
+ friend class FieldViewBase<FieldSkipper>;
+ const uint8_t* read_ptr_;
+ const uint8_t* end_ptr_;
+ const FieldSkipper& skipper_;
+ };
+ using value_type = const PerfettoPbDecoderField;
+ using const_iterator = Iterator;
+ template <typename... Args>
+ explicit FieldViewBase(const uint8_t* begin, const uint8_t* end, Args... args)
+ : begin_(begin), end_(end), s_(args...) {
+ }
+ template <typename... Args>
+ explicit FieldViewBase(const std::vector<uint8_t>& data, Args... args)
+ : FieldViewBase(data.data(), data.data() + data.size(), args...) {
+ }
+ template <typename... Args>
+ explicit FieldViewBase(const struct PerfettoPbDecoderField& field,
+ Args... args)
+ : s_(args...) {
+ if (field.wire_type != PERFETTO_PB_WIRE_TYPE_DELIMITED) {
+ abort();
+ }
+ begin_ = field.value.delimited.start;
+ end_ = begin_ + field.value.delimited.len;
+ }
+ Iterator begin() const {
+ return Iterator(begin_, end_, s_);
+ }
+ Iterator end() const {
+ return Iterator(end_, end_, s_);
+ }
+ PerfettoPbDecoderField front() const {
+ return *begin();
+ }
+
+ size_t size() const {
+ size_t count = 0;
+ for (auto field : *this) {
+ (void)field;
+ count++;
+ }
+ return count;
+ }
+
+ bool ok() const {
+ for (auto field : *this) {
+ if (field.status != PERFETTO_PB_DECODER_OK) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private:
+ const uint8_t* begin_;
+ const uint8_t* end_;
+ FieldSkipper s_;
+};
+
+// Pretty printer for gtest
+template <typename FieldSkipper>
+void PrintTo(const FieldViewBase<FieldSkipper>& field_view, std::ostream* pos) {
+ std::ostream& os = *pos;
+ os << "{";
+ for (PerfettoPbDecoderField f : field_view) {
+ PrintTo(f, pos);
+ os << ", ";
+ }
+ os << "}";
+}
+
+class IdFieldSkipper {
+ public:
+ explicit IdFieldSkipper(uint32_t id) : id_(id) {
+ }
+ explicit IdFieldSkipper(int32_t id) : id_(static_cast<uint32_t>(id)) {
+ }
+ bool ShouldSkip(const struct PerfettoPbDecoderField& field) const {
+ return field.id != id_;
+ }
+
+ private:
+ uint32_t id_;
+};
+
+class NoFieldSkipper {
+ public:
+ NoFieldSkipper() = default;
+ bool ShouldSkip(const struct PerfettoPbDecoderField&) const {
+ return false;
+ }
+};
+
+// View over all the fields of a contiguous serialized protobuf message.
+//
+// Examples:
+//
+// for (struct PerfettoPbDecoderField field : FieldView(msg_begin, msg_end)) {
+// //...
+// }
+// FieldView fields2(/*PerfettoPbDecoderField*/ nested_field);
+// FieldView fields3(/*std::vector<uint8_t>*/ data);
+// size_t num = fields1.size(); // The number of fields.
+// bool ok = fields1.ok(); // Checks that the message is not malformed.
+using FieldView = FieldViewBase<NoFieldSkipper>;
+
+// Like `FieldView`, but only considers fields with a specific id.
+//
+// Examples:
+//
+// IdFieldView fields(msg_begin, msg_end, id)
+using IdFieldView = FieldViewBase<IdFieldSkipper>;
+
+// Matches a PerfettoPbDecoderField with the specified id. Accepts another
+// matcher to match the contents of the field.
+//
+// Example:
+// PerfettoPbDecoderField field = ...
+// EXPECT_THAT(field, PbField(900, VarIntField(5)));
+template <typename M>
+auto PbField(int32_t id, M m) {
+ return testing::AllOf(
+ testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
+ testing::Field(&PerfettoPbDecoderField::id, id), m);
+}
+
+// Matches a PerfettoPbDecoderField submessage field. Accepts a container
+// matcher for the subfields.
+//
+// Example:
+// PerfettoPbDecoderField field = ...
+// EXPECT_THAT(field, MsgField(ElementsAre(...)));
+template <typename M>
+auto MsgField(M m) {
+ auto f = [](const PerfettoPbDecoderField& field) { return FieldView(field); };
+ return testing::AllOf(
+ testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
+ testing::Field(&PerfettoPbDecoderField::wire_type,
+ PERFETTO_PB_WIRE_TYPE_DELIMITED),
+ testing::ResultOf(f, m));
+}
+
+// Matches a PerfettoPbDecoderField length delimited field. Accepts a string
+// matcher.
+//
+// Example:
+// PerfettoPbDecoderField field = ...
+// EXPECT_THAT(field, StringField("string"));
+template <typename M>
+auto StringField(M m) {
+ auto f = [](const PerfettoPbDecoderField& field) {
+ return std::string(
+ reinterpret_cast<const char*>(field.value.delimited.start),
+ field.value.delimited.len);
+ };
+ return testing::AllOf(
+ testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
+ testing::Field(&PerfettoPbDecoderField::wire_type,
+ PERFETTO_PB_WIRE_TYPE_DELIMITED),
+ testing::ResultOf(f, m));
+}
+
+// Matches a PerfettoPbDecoderField VarInt field. Accepts an integer matcher
+//
+// Example:
+// PerfettoPbDecoderField field = ...
+// EXPECT_THAT(field, VarIntField(1)));
+template <typename M>
+auto VarIntField(M m) {
+ auto f = [](const PerfettoPbDecoderField& field) {
+ return field.value.integer64;
+ };
+ return testing::AllOf(
+ testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
+ testing::Field(&PerfettoPbDecoderField::wire_type,
+ PERFETTO_PB_WIRE_TYPE_VARINT),
+ testing::ResultOf(f, m));
+}
+
+// Matches a PerfettoPbDecoderField fixed64 field. Accepts an integer matcher
+//
+// Example:
+// PerfettoPbDecoderField field = ...
+// EXPECT_THAT(field, Fixed64Field(1)));
+template <typename M>
+auto Fixed64Field(M m) {
+ auto f = [](const PerfettoPbDecoderField& field) {
+ return field.value.integer64;
+ };
+ return testing::AllOf(
+ testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
+ testing::Field(&PerfettoPbDecoderField::wire_type,
+ PERFETTO_PB_WIRE_TYPE_FIXED64),
+ testing::ResultOf(f, m));
+}
+
+// Matches a PerfettoPbDecoderField fixed32 field. Accepts an integer matcher
+//
+// Example:
+// PerfettoPbDecoderField field = ...
+// EXPECT_THAT(field, Fixed32Field(1)));
+template <typename M>
+auto Fixed32Field(M m) {
+ auto f = [](const PerfettoPbDecoderField& field) {
+ return field.value.integer32;
+ };
+ return testing::AllOf(
+ testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
+ testing::Field(&PerfettoPbDecoderField::wire_type,
+ PERFETTO_PB_WIRE_TYPE_FIXED32),
+ testing::ResultOf(f, m));
+}
+
+// Matches a PerfettoPbDecoderField double field. Accepts a double matcher
+//
+// Example:
+// PerfettoPbDecoderField field = ...
+// EXPECT_THAT(field, DoubleField(1.0)));
+template <typename M>
+auto DoubleField(M m) {
+ auto f = [](const PerfettoPbDecoderField& field) {
+ return field.value.double_val;
+ };
+ return testing::AllOf(
+ testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
+ testing::Field(&PerfettoPbDecoderField::wire_type,
+ PERFETTO_PB_WIRE_TYPE_FIXED64),
+ testing::ResultOf(f, m));
+}
+
+// Matches a PerfettoPbDecoderField float field. Accepts a float matcher
+//
+// Example:
+// PerfettoPbDecoderField field = ...
+// EXPECT_THAT(field, FloatField(1.0)));
+template <typename M>
+auto FloatField(M m) {
+ auto f = [](const PerfettoPbDecoderField& field) {
+ return field.value.float_val;
+ };
+ return testing::AllOf(
+ testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
+ testing::Field(&PerfettoPbDecoderField::wire_type,
+ PERFETTO_PB_WIRE_TYPE_FIXED32),
+ testing::ResultOf(f, m));
+}
+
+// Matches a PerfettoPbDecoderField submessage field. Accepts a container
+// matcher for the subfields.
+//
+// Example:
+// PerfettoPbDecoderField field = ...
+// EXPECT_THAT(field, AllFieldsWithId(900, ElementsAre(...)));
+template <typename M>
+auto AllFieldsWithId(int32_t id, M m) {
+ auto f = [id](const PerfettoPbDecoderField& field) {
+ return IdFieldView(field, id);
+ };
+ return testing::AllOf(
+ testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
+ testing::Field(&PerfettoPbDecoderField::wire_type,
+ PERFETTO_PB_WIRE_TYPE_DELIMITED),
+ testing::ResultOf(f, m));
+}
+
+} // namespace test_utils
+} // namespace shlib
+} // namespace perfetto
+
+#endif // UTILS_H
diff --git a/libs/tracing_perfetto/tracing_perfetto.cpp b/libs/tracing_perfetto/tracing_perfetto.cpp
new file mode 100644
index 0000000..19d1eb6
--- /dev/null
+++ b/libs/tracing_perfetto/tracing_perfetto.cpp
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2024 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 "tracing_perfetto.h"
+
+#include <cutils/trace.h>
+
+#include "perfetto/public/te_category_macros.h"
+#include "trace_categories.h"
+#include "tracing_perfetto_internal.h"
+
+namespace tracing_perfetto {
+
+void registerWithPerfetto(bool test) {
+ internal::registerWithPerfetto(test);
+}
+
+Result traceBegin(uint64_t category, const char* name) {
+ struct PerfettoTeCategory* perfettoTeCategory =
+ internal::toPerfettoCategory(category);
+ if (perfettoTeCategory != nullptr) {
+ return internal::perfettoTraceBegin(*perfettoTeCategory, name);
+ } else {
+ atrace_begin(category, name);
+ return Result::SUCCESS;
+ }
+}
+
+Result traceEnd(uint64_t category) {
+ struct PerfettoTeCategory* perfettoTeCategory =
+ internal::toPerfettoCategory(category);
+ if (perfettoTeCategory != nullptr) {
+ return internal::perfettoTraceEnd(*perfettoTeCategory);
+ } else {
+ atrace_end(category);
+ return Result::SUCCESS;
+ }
+}
+
+Result traceAsyncBegin(uint64_t category, const char* name, int32_t cookie) {
+ struct PerfettoTeCategory* perfettoTeCategory =
+ internal::toPerfettoCategory(category);
+ if (perfettoTeCategory != nullptr) {
+ return internal::perfettoTraceAsyncBegin(*perfettoTeCategory, name, cookie);
+ } else {
+ atrace_async_begin(category, name, cookie);
+ return Result::SUCCESS;
+ }
+}
+
+Result traceAsyncEnd(uint64_t category, const char* name, int32_t cookie) {
+ struct PerfettoTeCategory* perfettoTeCategory =
+ internal::toPerfettoCategory(category);
+ if (perfettoTeCategory != nullptr) {
+ return internal::perfettoTraceAsyncEnd(*perfettoTeCategory, name, cookie);
+ } else {
+ atrace_async_end(category, name, cookie);
+ return Result::SUCCESS;
+ }
+}
+
+Result traceAsyncBeginForTrack(uint64_t category, const char* name,
+ const char* trackName, int32_t cookie) {
+ struct PerfettoTeCategory* perfettoTeCategory =
+ internal::toPerfettoCategory(category);
+ if (perfettoTeCategory != nullptr) {
+ return internal::perfettoTraceAsyncBeginForTrack(*perfettoTeCategory, name, trackName, cookie);
+ } else {
+ atrace_async_for_track_begin(category, trackName, name, cookie);
+ return Result::SUCCESS;
+ }
+}
+
+Result traceAsyncEndForTrack(uint64_t category, const char* trackName,
+ int32_t cookie) {
+ struct PerfettoTeCategory* perfettoTeCategory =
+ internal::toPerfettoCategory(category);
+ if (perfettoTeCategory != nullptr) {
+ return internal::perfettoTraceAsyncEndForTrack(*perfettoTeCategory, trackName, cookie);
+ } else {
+ atrace_async_for_track_end(category, trackName, cookie);
+ return Result::SUCCESS;
+ }
+}
+
+Result traceInstant(uint64_t category, const char* name) {
+ struct PerfettoTeCategory* perfettoTeCategory =
+ internal::toPerfettoCategory(category);
+ if (perfettoTeCategory != nullptr) {
+ return internal::perfettoTraceInstant(*perfettoTeCategory, name);
+ } else {
+ atrace_instant(category, name);
+ return Result::SUCCESS;
+ }
+}
+
+Result traceInstantForTrack(uint64_t category, const char* trackName,
+ const char* name) {
+ struct PerfettoTeCategory* perfettoTeCategory =
+ internal::toPerfettoCategory(category);
+ if (perfettoTeCategory != nullptr) {
+ return internal::perfettoTraceInstantForTrack(*perfettoTeCategory, trackName, name);
+ } else {
+ atrace_instant_for_track(category, trackName, name);
+ return Result::SUCCESS;
+ }
+}
+
+Result traceCounter(uint64_t category, const char* name, int64_t value) {
+ struct PerfettoTeCategory* perfettoTeCategory =
+ internal::toPerfettoCategory(category);
+ if (perfettoTeCategory != nullptr) {
+ return internal::perfettoTraceCounter(*perfettoTeCategory, name, value);
+ } else {
+ atrace_int64(category, name, value);
+ return Result::SUCCESS;
+ }
+}
+
+uint64_t getEnabledCategories() {
+ if (internal::isPerfettoRegistered()) {
+ // TODO(b/303199244): Return only enabled categories and not all registered ones
+ return internal::getDefaultCategories();
+ } else {
+ return atrace_get_enabled_tags();
+ }
+}
+
+} // namespace tracing_perfetto
diff --git a/libs/tracing_perfetto/tracing_perfetto_internal.cpp b/libs/tracing_perfetto/tracing_perfetto_internal.cpp
new file mode 100644
index 0000000..758ace6
--- /dev/null
+++ b/libs/tracing_perfetto/tracing_perfetto_internal.cpp
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2024 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 FRAMEWORK_CATEGORIES(C) \
+ C(always, "always", "Always category") \
+ C(graphics, "graphics", "Graphics category") \
+ C(input, "input", "Input category") \
+ C(view, "view", "View category") \
+ C(webview, "webview", "WebView category") \
+ C(windowmanager, "wm", "WindowManager category") \
+ C(activitymanager, "am", "ActivityManager category") \
+ C(syncmanager, "syncmanager", "SyncManager category") \
+ C(audio, "audio", "Audio category") \
+ C(video, "video", "Video category") \
+ C(camera, "camera", "Camera category") \
+ C(hal, "hal", "HAL category") \
+ C(app, "app", "App category") \
+ C(resources, "res", "Resources category") \
+ C(dalvik, "dalvik", "Dalvik category") \
+ C(rs, "rs", "RS category") \
+ C(bionic, "bionic", "Bionic category") \
+ C(power, "power", "Power category") \
+ C(packagemanager, "packagemanager", "PackageManager category") \
+ C(systemserver, "ss", "System Server category") \
+ C(database, "database", "Database category") \
+ C(network, "network", "Network category") \
+ C(adb, "adb", "ADB category") \
+ C(vibrator, "vibrator", "Vibrator category") \
+ C(aidl, "aidl", "AIDL category") \
+ C(nnapi, "nnapi", "NNAPI category") \
+ C(rro, "rro", "RRO category") \
+ C(thermal, "thermal", "Thermal category")
+
+#include "tracing_perfetto_internal.h"
+
+#include <inttypes.h>
+
+#include <mutex>
+
+#include <android_os.h>
+
+#include "perfetto/public/compiler.h"
+#include "perfetto/public/producer.h"
+#include "perfetto/public/te_category_macros.h"
+#include "perfetto/public/te_macros.h"
+#include "perfetto/public/track_event.h"
+#include "trace_categories.h"
+#include "trace_result.h"
+
+namespace tracing_perfetto {
+
+namespace internal {
+
+namespace {
+
+PERFETTO_TE_CATEGORIES_DECLARE(FRAMEWORK_CATEGORIES);
+
+PERFETTO_TE_CATEGORIES_DEFINE(FRAMEWORK_CATEGORIES);
+
+std::atomic_bool is_perfetto_registered = false;
+
+struct PerfettoTeCategory* toCategory(uint64_t inCategory) {
+ switch (inCategory) {
+ case TRACE_CATEGORY_ALWAYS:
+ return &always;
+ case TRACE_CATEGORY_GRAPHICS:
+ return &graphics;
+ case TRACE_CATEGORY_INPUT:
+ return &input;
+ case TRACE_CATEGORY_VIEW:
+ return &view;
+ case TRACE_CATEGORY_WEBVIEW:
+ return &webview;
+ case TRACE_CATEGORY_WINDOW_MANAGER:
+ return &windowmanager;
+ case TRACE_CATEGORY_ACTIVITY_MANAGER:
+ return &activitymanager;
+ case TRACE_CATEGORY_SYNC_MANAGER:
+ return &syncmanager;
+ case TRACE_CATEGORY_AUDIO:
+ return &audio;
+ case TRACE_CATEGORY_VIDEO:
+ return &video;
+ case TRACE_CATEGORY_CAMERA:
+ return &camera;
+ case TRACE_CATEGORY_HAL:
+ return &hal;
+ case TRACE_CATEGORY_APP:
+ return &app;
+ case TRACE_CATEGORY_RESOURCES:
+ return &resources;
+ case TRACE_CATEGORY_DALVIK:
+ return &dalvik;
+ case TRACE_CATEGORY_RS:
+ return &rs;
+ case TRACE_CATEGORY_BIONIC:
+ return &bionic;
+ case TRACE_CATEGORY_POWER:
+ return &power;
+ case TRACE_CATEGORY_PACKAGE_MANAGER:
+ return &packagemanager;
+ case TRACE_CATEGORY_SYSTEM_SERVER:
+ return &systemserver;
+ case TRACE_CATEGORY_DATABASE:
+ return &database;
+ case TRACE_CATEGORY_NETWORK:
+ return &network;
+ case TRACE_CATEGORY_ADB:
+ return &adb;
+ case TRACE_CATEGORY_VIBRATOR:
+ return &vibrator;
+ case TRACE_CATEGORY_AIDL:
+ return &aidl;
+ case TRACE_CATEGORY_NNAPI:
+ return &nnapi;
+ case TRACE_CATEGORY_RRO:
+ return &rro;
+ case TRACE_CATEGORY_THERMAL:
+ return &thermal;
+ default:
+ return nullptr;
+ }
+}
+
+} // namespace
+
+bool isPerfettoRegistered() {
+ return is_perfetto_registered;
+}
+
+struct PerfettoTeCategory* toPerfettoCategory(uint64_t category) {
+ struct PerfettoTeCategory* perfettoCategory = toCategory(category);
+ if (perfettoCategory == nullptr) {
+ return nullptr;
+ }
+
+ bool enabled = PERFETTO_UNLIKELY(PERFETTO_ATOMIC_LOAD_EXPLICIT(
+ (*perfettoCategory).enabled, PERFETTO_MEMORY_ORDER_RELAXED));
+ return enabled ? perfettoCategory : nullptr;
+}
+
+void registerWithPerfetto(bool test) {
+ if (!android::os::perfetto_sdk_tracing()) {
+ return;
+ }
+
+ static std::once_flag registration;
+ std::call_once(registration, [test]() {
+ struct PerfettoProducerInitArgs args = PERFETTO_PRODUCER_INIT_ARGS_INIT();
+ args.backends = test ? PERFETTO_BACKEND_IN_PROCESS : PERFETTO_BACKEND_SYSTEM;
+ PerfettoProducerInit(args);
+ PerfettoTeInit();
+ PERFETTO_TE_REGISTER_CATEGORIES(FRAMEWORK_CATEGORIES);
+ is_perfetto_registered = true;
+ });
+}
+
+Result perfettoTraceBegin(const struct PerfettoTeCategory& category, const char* name) {
+ PERFETTO_TE(category, PERFETTO_TE_SLICE_BEGIN(name));
+ return Result::SUCCESS;
+}
+
+Result perfettoTraceEnd(const struct PerfettoTeCategory& category) {
+ PERFETTO_TE(category, PERFETTO_TE_SLICE_END());
+ return Result::SUCCESS;
+}
+
+Result perfettoTraceAsyncBeginForTrack(const struct PerfettoTeCategory& category, const char* name,
+ const char* trackName, uint64_t cookie) {
+ PERFETTO_TE(
+ category, PERFETTO_TE_SLICE_BEGIN(name),
+ PERFETTO_TE_NAMED_TRACK(trackName, cookie, PerfettoTeProcessTrackUuid()));
+ return Result::SUCCESS;
+}
+
+Result perfettoTraceAsyncEndForTrack(const struct PerfettoTeCategory& category,
+ const char* trackName, uint64_t cookie) {
+ PERFETTO_TE(
+ category, PERFETTO_TE_SLICE_END(),
+ PERFETTO_TE_NAMED_TRACK(trackName, cookie, PerfettoTeProcessTrackUuid()));
+ return Result::SUCCESS;
+}
+
+Result perfettoTraceAsyncBegin(const struct PerfettoTeCategory& category, const char* name,
+ uint64_t cookie) {
+ return perfettoTraceAsyncBeginForTrack(category, name, name, cookie);
+}
+
+Result perfettoTraceAsyncEnd(const struct PerfettoTeCategory& category, const char* name,
+ uint64_t cookie) {
+ return perfettoTraceAsyncEndForTrack(category, name, cookie);
+}
+
+Result perfettoTraceInstant(const struct PerfettoTeCategory& category, const char* name) {
+ PERFETTO_TE(category, PERFETTO_TE_INSTANT(name));
+ return Result::SUCCESS;
+}
+
+Result perfettoTraceInstantForTrack(const struct PerfettoTeCategory& category,
+ const char* trackName, const char* name) {
+ PERFETTO_TE(
+ category, PERFETTO_TE_INSTANT(name),
+ PERFETTO_TE_NAMED_TRACK(trackName, 1, PerfettoTeProcessTrackUuid()));
+ return Result::SUCCESS;
+}
+
+Result perfettoTraceCounter(const struct PerfettoTeCategory& category,
+ [[maybe_unused]] const char* name, int64_t value) {
+ PERFETTO_TE(category, PERFETTO_TE_COUNTER(),
+ PERFETTO_TE_INT_COUNTER(value));
+ return Result::SUCCESS;
+}
+
+uint64_t getDefaultCategories() {
+ return TRACE_CATEGORIES;
+}
+
+} // namespace internal
+
+} // namespace tracing_perfetto
diff --git a/libs/tracing_perfetto/tracing_perfetto_internal.h b/libs/tracing_perfetto/tracing_perfetto_internal.h
new file mode 100644
index 0000000..79e4b8f
--- /dev/null
+++ b/libs/tracing_perfetto/tracing_perfetto_internal.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2024 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.
+ */
+
+#ifndef TRACING_PERFETTO_INTERNAL_H
+#define TRACING_PERFETTO_INTERNAL_H
+
+#include <stdint.h>
+
+#include "include/trace_result.h"
+#include "perfetto/public/te_category_macros.h"
+
+namespace tracing_perfetto {
+
+namespace internal {
+
+bool isPerfettoRegistered();
+
+struct PerfettoTeCategory* toPerfettoCategory(uint64_t category);
+
+void registerWithPerfetto(bool test = false);
+
+Result perfettoTraceBegin(const struct PerfettoTeCategory& category, const char* name);
+
+Result perfettoTraceEnd(const struct PerfettoTeCategory& category);
+
+Result perfettoTraceAsyncBegin(const struct PerfettoTeCategory& category, const char* name,
+ uint64_t cookie);
+
+Result perfettoTraceAsyncEnd(const struct PerfettoTeCategory& category, const char* name,
+ uint64_t cookie);
+
+Result perfettoTraceAsyncBeginForTrack(const struct PerfettoTeCategory& category, const char* name,
+ const char* trackName, uint64_t cookie);
+
+Result perfettoTraceAsyncEndForTrack(const struct PerfettoTeCategory& category,
+ const char* trackName, uint64_t cookie);
+
+Result perfettoTraceInstant(const struct PerfettoTeCategory& category, const char* name);
+
+Result perfettoTraceInstantForTrack(const struct PerfettoTeCategory& category,
+ const char* trackName, const char* name);
+
+Result perfettoTraceCounter(const struct PerfettoTeCategory& category, const char* name,
+ int64_t value);
+
+uint64_t getDefaultCategories();
+
+} // namespace internal
+
+} // namespace tracing_perfetto
+
+#endif // TRACING_PERFETTO_INTERNAL_H
diff --git a/opengl/libs/EGL/Loader.cpp b/opengl/libs/EGL/Loader.cpp
index e487cbc..af0bcff 100644
--- a/opengl/libs/EGL/Loader.cpp
+++ b/opengl/libs/EGL/Loader.cpp
@@ -67,7 +67,6 @@
static const char* RO_DRIVER_SUFFIX_PROPERTY = "ro.hardware.egl";
static const char* RO_BOARD_PLATFORM_PROPERTY = "ro.board.platform";
static const char* ANGLE_SUFFIX_VALUE = "angle";
-static const char* VENDOR_ANGLE_BUILD = "ro.gfx.angle.supported";
static const char* HAL_SUBNAME_KEY_PROPERTIES[3] = {
PERSIST_DRIVER_SUFFIX_PROPERTY,
@@ -494,14 +493,9 @@
void* dso = nullptr;
- const bool AngleInVendor = property_get_bool(VENDOR_ANGLE_BUILD, false);
const bool isSuffixAngle = suffix != nullptr && strcmp(suffix, ANGLE_SUFFIX_VALUE) == 0;
- // Only use sphal namespace when system ANGLE binaries are not the default drivers.
- const bool useSphalNamespace = !isSuffixAngle || AngleInVendor;
-
const std::string absolutePath =
- findLibrary(libraryName, useSphalNamespace ? VENDOR_LIB_EGL_DIR : SYSTEM_LIB_PATH,
- exact);
+ findLibrary(libraryName, isSuffixAngle ? SYSTEM_LIB_PATH : VENDOR_LIB_EGL_DIR, exact);
if (absolutePath.empty()) {
// this happens often, we don't want to log an error
return nullptr;
@@ -509,8 +503,9 @@
const char* const driverAbsolutePath = absolutePath.c_str();
// Currently the default driver is unlikely to be ANGLE on most devices,
- // hence put this first.
- if (useSphalNamespace) {
+ // hence put this first. Only use sphal namespace when system ANGLE binaries
+ // are not the default drivers.
+ if (!isSuffixAngle) {
// Try to load drivers from the 'sphal' namespace, if it exist. Fall back to
// the original routine when the namespace does not exist.
// See /system/linkerconfig/contents/namespace for the configuration of the
diff --git a/services/inputflinger/InputProcessor.h b/services/inputflinger/InputProcessor.h
index dcbfebc..7a00a2d 100644
--- a/services/inputflinger/InputProcessor.h
+++ b/services/inputflinger/InputProcessor.h
@@ -22,7 +22,7 @@
#include <unordered_map>
#include <aidl/android/hardware/input/processor/IInputProcessor.h>
-#include "BlockingQueue.h"
+#include <input/BlockingQueue.h>
#include "InputListener.h"
namespace android {
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
index 3ac4285..c333814 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -26,6 +26,7 @@
namespace android {
namespace {
+
bool isFromMouse(const NotifyMotionArgs& args) {
return isFromSource(args.source, AINPUT_SOURCE_MOUSE) &&
args.pointerProperties[0].toolType == ToolType::MOUSE;
@@ -44,13 +45,23 @@
bool isStylusHoverEvent(const NotifyMotionArgs& args) {
return isStylusEvent(args.source, args.pointerProperties) && isHoverAction(args.action);
}
+
+inline void notifyPointerDisplayChange(std::optional<std::tuple<int32_t, FloatPoint>> change,
+ PointerChoreographerPolicyInterface& policy) {
+ if (!change) {
+ return;
+ }
+ const auto& [displayId, cursorPosition] = *change;
+ policy.notifyPointerDisplayIdChanged(displayId, cursorPosition);
+}
+
} // namespace
// --- PointerChoreographer ---
PointerChoreographer::PointerChoreographer(InputListenerInterface& listener,
PointerChoreographerPolicyInterface& policy)
- : mTouchControllerConstructor([this]() REQUIRES(mLock) {
+ : mTouchControllerConstructor([this]() {
return mPolicy.createPointerController(
PointerControllerInterface::ControllerType::TOUCH);
}),
@@ -62,10 +73,16 @@
mStylusPointerIconEnabled(false) {}
void PointerChoreographer::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) {
- std::scoped_lock _l(mLock);
+ PointerDisplayChange pointerDisplayChange;
- mInputDeviceInfos = args.inputDeviceInfos;
- updatePointerControllersLocked();
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+
+ mInputDeviceInfos = args.inputDeviceInfos;
+ pointerDisplayChange = updatePointerControllersLocked();
+ } // release lock
+
+ notifyPointerDisplayChange(pointerDisplayChange, mPolicy);
mNextListener.notify(args);
}
@@ -104,27 +121,38 @@
<< args.dump();
}
- auto [displayId, pc] = getDisplayIdAndMouseControllerLocked(args.displayId);
+ auto [displayId, pc] = ensureMouseControllerLocked(args.displayId);
+ NotifyMotionArgs newArgs(args);
+ newArgs.displayId = displayId;
- const float deltaX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
- const float deltaY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
- pc.move(deltaX, deltaY);
+ if (MotionEvent::isValidCursorPosition(args.xCursorPosition, args.yCursorPosition)) {
+ // This is an absolute mouse device that knows about the location of the cursor on the
+ // display, so set the cursor position to the specified location.
+ const auto [x, y] = pc.getPosition();
+ const float deltaX = args.xCursorPosition - x;
+ const float deltaY = args.yCursorPosition - y;
+ newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, deltaX);
+ newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, deltaY);
+ pc.setPosition(args.xCursorPosition, args.yCursorPosition);
+ } else {
+ // This is a relative mouse, so move the cursor by the specified amount.
+ const float deltaX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
+ const float deltaY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+ pc.move(deltaX, deltaY);
+ const auto [x, y] = pc.getPosition();
+ newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
+ newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+ newArgs.xCursorPosition = x;
+ newArgs.yCursorPosition = y;
+ }
if (canUnfadeOnDisplay(displayId)) {
pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
}
-
- const auto [x, y] = pc.getPosition();
- NotifyMotionArgs newArgs(args);
- newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
- newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
- newArgs.xCursorPosition = x;
- newArgs.yCursorPosition = y;
- newArgs.displayId = displayId;
return newArgs;
}
NotifyMotionArgs PointerChoreographer::processTouchpadEventLocked(const NotifyMotionArgs& args) {
- auto [displayId, pc] = getDisplayIdAndMouseControllerLocked(args.displayId);
+ auto [displayId, pc] = ensureMouseControllerLocked(args.displayId);
NotifyMotionArgs newArgs(args);
newArgs.displayId = displayId;
@@ -260,7 +288,7 @@
void PointerChoreographer::notifyPointerCaptureChanged(
const NotifyPointerCaptureChangedArgs& args) {
- if (args.request.enable) {
+ if (args.request.isEnable()) {
std::scoped_lock _l(mLock);
for (const auto& [_, mousePointerController] : mMousePointersByDisplay) {
mousePointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
@@ -308,17 +336,13 @@
return associatedDisplayId == ADISPLAY_ID_NONE ? mDefaultMouseDisplayId : associatedDisplayId;
}
-std::pair<int32_t, PointerControllerInterface&>
-PointerChoreographer::getDisplayIdAndMouseControllerLocked(int32_t associatedDisplayId) {
+std::pair<int32_t, PointerControllerInterface&> PointerChoreographer::ensureMouseControllerLocked(
+ int32_t associatedDisplayId) {
const int32_t displayId = getTargetMouseDisplayLocked(associatedDisplayId);
- // Get the mouse pointer controller for the display, or create one if it doesn't exist.
- auto [it, emplaced] =
- mMousePointersByDisplay.try_emplace(displayId,
- getMouseControllerConstructor(displayId));
- if (emplaced) {
- notifyPointerDisplayIdChangedLocked();
- }
+ auto it = mMousePointersByDisplay.find(displayId);
+ LOG_ALWAYS_FATAL_IF(it == mMousePointersByDisplay.end(),
+ "There is no mouse controller created for display %d", displayId);
return {displayId, *it->second};
}
@@ -333,7 +357,7 @@
return mDisplaysWithPointersHidden.find(displayId) == mDisplaysWithPointersHidden.end();
}
-void PointerChoreographer::updatePointerControllersLocked() {
+PointerChoreographer::PointerDisplayChange PointerChoreographer::updatePointerControllersLocked() {
std::set<int32_t /*displayId*/> mouseDisplaysToKeep;
std::set<DeviceId> touchDevicesToKeep;
std::set<DeviceId> stylusDevicesToKeep;
@@ -382,11 +406,12 @@
mInputDeviceInfos.end();
});
- // Notify the policy if there's a change on the pointer display ID.
- notifyPointerDisplayIdChangedLocked();
+ // Check if we need to notify the policy if there's a change on the pointer display ID.
+ return calculatePointerDisplayChangeToNotify();
}
-void PointerChoreographer::notifyPointerDisplayIdChangedLocked() {
+PointerChoreographer::PointerDisplayChange
+PointerChoreographer::calculatePointerDisplayChangeToNotify() {
int32_t displayIdToNotify = ADISPLAY_ID_NONE;
FloatPoint cursorPosition = {0, 0};
if (const auto it = mMousePointersByDisplay.find(mDefaultMouseDisplayId);
@@ -398,38 +423,49 @@
displayIdToNotify = pointerController->getDisplayId();
cursorPosition = pointerController->getPosition();
}
-
if (mNotifiedPointerDisplayId == displayIdToNotify) {
- return;
+ return {};
}
- mPolicy.notifyPointerDisplayIdChanged(displayIdToNotify, cursorPosition);
mNotifiedPointerDisplayId = displayIdToNotify;
+ return {{displayIdToNotify, cursorPosition}};
}
void PointerChoreographer::setDefaultMouseDisplayId(int32_t displayId) {
- std::scoped_lock _l(mLock);
+ PointerDisplayChange pointerDisplayChange;
- mDefaultMouseDisplayId = displayId;
- updatePointerControllersLocked();
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+
+ mDefaultMouseDisplayId = displayId;
+ pointerDisplayChange = updatePointerControllersLocked();
+ } // release lock
+
+ notifyPointerDisplayChange(pointerDisplayChange, mPolicy);
}
void PointerChoreographer::setDisplayViewports(const std::vector<DisplayViewport>& viewports) {
- std::scoped_lock _l(mLock);
- for (const auto& viewport : viewports) {
- const int32_t displayId = viewport.displayId;
- if (const auto it = mMousePointersByDisplay.find(displayId);
- it != mMousePointersByDisplay.end()) {
- it->second->setDisplayViewport(viewport);
- }
- for (const auto& [deviceId, stylusPointerController] : mStylusPointersByDevice) {
- const InputDeviceInfo* info = findInputDeviceLocked(deviceId);
- if (info && info->getAssociatedDisplayId() == displayId) {
- stylusPointerController->setDisplayViewport(viewport);
+ PointerDisplayChange pointerDisplayChange;
+
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+ for (const auto& viewport : viewports) {
+ const int32_t displayId = viewport.displayId;
+ if (const auto it = mMousePointersByDisplay.find(displayId);
+ it != mMousePointersByDisplay.end()) {
+ it->second->setDisplayViewport(viewport);
+ }
+ for (const auto& [deviceId, stylusPointerController] : mStylusPointersByDevice) {
+ const InputDeviceInfo* info = findInputDeviceLocked(deviceId);
+ if (info && info->getAssociatedDisplayId() == displayId) {
+ stylusPointerController->setDisplayViewport(viewport);
+ }
}
}
- }
- mViewports = viewports;
- notifyPointerDisplayIdChangedLocked();
+ mViewports = viewports;
+ pointerDisplayChange = calculatePointerDisplayChangeToNotify();
+ } // release lock
+
+ notifyPointerDisplayChange(pointerDisplayChange, mPolicy);
}
std::optional<DisplayViewport> PointerChoreographer::getViewportForPointerDevice(
@@ -453,21 +489,33 @@
}
void PointerChoreographer::setShowTouchesEnabled(bool enabled) {
- std::scoped_lock _l(mLock);
- if (mShowTouchesEnabled == enabled) {
- return;
- }
- mShowTouchesEnabled = enabled;
- updatePointerControllersLocked();
+ PointerDisplayChange pointerDisplayChange;
+
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+ if (mShowTouchesEnabled == enabled) {
+ return;
+ }
+ mShowTouchesEnabled = enabled;
+ pointerDisplayChange = updatePointerControllersLocked();
+ } // release lock
+
+ notifyPointerDisplayChange(pointerDisplayChange, mPolicy);
}
void PointerChoreographer::setStylusPointerIconEnabled(bool enabled) {
- std::scoped_lock _l(mLock);
- if (mStylusPointerIconEnabled == enabled) {
- return;
- }
- mStylusPointerIconEnabled = enabled;
- updatePointerControllersLocked();
+ PointerDisplayChange pointerDisplayChange;
+
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+ if (mStylusPointerIconEnabled == enabled) {
+ return;
+ }
+ mStylusPointerIconEnabled = enabled;
+ pointerDisplayChange = updatePointerControllersLocked();
+ } // release lock
+
+ notifyPointerDisplayChange(pointerDisplayChange, mPolicy);
}
bool PointerChoreographer::setPointerIcon(
diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h
index 6aab3aa..db1488b 100644
--- a/services/inputflinger/PointerChoreographer.h
+++ b/services/inputflinger/PointerChoreographer.h
@@ -109,11 +109,13 @@
void dump(std::string& dump) override;
private:
- void updatePointerControllersLocked() REQUIRES(mLock);
- void notifyPointerDisplayIdChangedLocked() REQUIRES(mLock);
+ using PointerDisplayChange =
+ std::optional<std::tuple<int32_t /*displayId*/, FloatPoint /*cursorPosition*/>>;
+ [[nodiscard]] PointerDisplayChange updatePointerControllersLocked() REQUIRES(mLock);
+ [[nodiscard]] PointerDisplayChange calculatePointerDisplayChangeToNotify() REQUIRES(mLock);
const DisplayViewport* findViewportByIdLocked(int32_t displayId) const REQUIRES(mLock);
int32_t getTargetMouseDisplayLocked(int32_t associatedDisplayId) const REQUIRES(mLock);
- std::pair<int32_t, PointerControllerInterface&> getDisplayIdAndMouseControllerLocked(
+ std::pair<int32_t /*displayId*/, PointerControllerInterface&> ensureMouseControllerLocked(
int32_t associatedDisplayId) REQUIRES(mLock);
InputDeviceInfo* findInputDeviceLocked(DeviceId deviceId) REQUIRES(mLock);
bool canUnfadeOnDisplay(int32_t displayId) REQUIRES(mLock);
diff --git a/services/inputflinger/dispatcher/CancelationOptions.h b/services/inputflinger/dispatcher/CancelationOptions.h
index 83e6a60..9c73f03 100644
--- a/services/inputflinger/dispatcher/CancelationOptions.h
+++ b/services/inputflinger/dispatcher/CancelationOptions.h
@@ -16,6 +16,8 @@
#pragma once
+#include "trace/EventTrackerInterface.h"
+
#include <input/Input.h>
#include <bitset>
#include <optional>
@@ -51,7 +53,13 @@
// The specific pointers to cancel, or nullopt to cancel all pointer events
std::optional<std::bitset<MAX_POINTER_ID + 1>> pointerIds = std::nullopt;
- CancelationOptions(Mode mode, const char* reason) : mode(mode), reason(reason) {}
+ const std::unique_ptr<trace::EventTrackerInterface>& traceTracker;
+
+ explicit CancelationOptions(Mode mode, const char* reason,
+ const std::unique_ptr<trace::EventTrackerInterface>& traceTracker)
+ : mode(mode), reason(reason), traceTracker(traceTracker) {}
+ CancelationOptions(const CancelationOptions&) = delete;
+ CancelationOptions operator=(const CancelationOptions&) = delete;
};
} // namespace inputdispatcher
diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp
index 264dc03..0246d60 100644
--- a/services/inputflinger/dispatcher/Entry.cpp
+++ b/services/inputflinger/dispatcher/Entry.cpp
@@ -110,7 +110,7 @@
std::string PointerCaptureChangedEntry::getDescription() const {
return StringPrintf("PointerCaptureChangedEvent(pointerCaptureEnabled=%s)",
- pointerCaptureRequest.enable ? "true" : "false");
+ pointerCaptureRequest.isEnable() ? "true" : "false");
}
// --- DragEntry ---
diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h
index 1298b5d..06d5c7d 100644
--- a/services/inputflinger/dispatcher/Entry.h
+++ b/services/inputflinger/dispatcher/Entry.h
@@ -140,6 +140,7 @@
mutable InterceptKeyResult interceptKeyResult; // set based on the interception result
mutable nsecs_t interceptKeyWakeupTime; // used with INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER
mutable int32_t flags;
+ // TODO(b/328618922): Refactor key repeat generation to make repeatCount non-mutable.
mutable int32_t repeatCount;
KeyEntry(int32_t id, std::shared_ptr<InjectionState> injectionState, nsecs_t eventTime,
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 10e0a36..e6b37c1 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -399,7 +399,8 @@
const InputTarget& inputTarget,
std::shared_ptr<const EventEntry> eventEntry,
ftl::Flags<InputTarget::Flags> inputTargetFlags,
- int64_t vsyncId) {
+ int64_t vsyncId,
+ trace::InputTracerInterface* tracer) {
const bool zeroCoords = inputTargetFlags.test(InputTarget::Flags::ZERO_COORDS);
const sp<WindowInfoHandle> win = inputTarget.windowHandle;
const std::optional<int32_t> windowId =
@@ -462,6 +463,10 @@
motionEntry.xCursorPosition, motionEntry.yCursorPosition,
motionEntry.downTime, motionEntry.pointerProperties,
pointerCoords);
+ if (tracer) {
+ combinedMotionEntry->traceTracker =
+ tracer->traceDerivedEvent(*combinedMotionEntry, *motionEntry.traceTracker);
+ }
std::unique_ptr<DispatchEntry> dispatchEntry =
std::make_unique<DispatchEntry>(std::move(combinedMotionEntry), inputTargetFlags,
@@ -593,6 +598,18 @@
return true;
}
+// Returns true if the given window's frame can occlude pointer events at the given display
+// location.
+bool windowOccludesTouchAt(const WindowInfo& windowInfo, int displayId, float x, float y,
+ const ui::Transform& displayTransform) {
+ if (windowInfo.displayId != displayId) {
+ return false;
+ }
+ const auto frame = displayTransform.transform(windowInfo.frame);
+ const auto p = floor(displayTransform.transform(x, y));
+ return p.x >= frame.left && p.x < frame.right && p.y >= frame.top && p.y < frame.bottom;
+}
+
bool isPointerFromStylus(const MotionEntry& entry, int32_t pointerIndex) {
return isFromSource(entry.source, AINPUT_SOURCE_STYLUS) &&
isStylusToolType(entry.pointerProperties[pointerIndex].toolType);
@@ -857,6 +874,30 @@
}
}
+class ScopedSyntheticEventTracer {
+public:
+ ScopedSyntheticEventTracer(std::unique_ptr<trace::InputTracerInterface>& tracer)
+ : mTracer(tracer) {
+ if (mTracer) {
+ mEventTracker = mTracer->createTrackerForSyntheticEvent();
+ }
+ }
+
+ ~ScopedSyntheticEventTracer() {
+ if (mTracer) {
+ mTracer->eventProcessingComplete(*mEventTracker);
+ }
+ }
+
+ const std::unique_ptr<trace::EventTrackerInterface>& getTracker() const {
+ return mEventTracker;
+ }
+
+private:
+ std::unique_ptr<trace::InputTracerInterface>& mTracer;
+ std::unique_ptr<trace::EventTrackerInterface> mEventTracker;
+};
+
} // namespace
// --- InputDispatcher ---
@@ -1474,8 +1515,9 @@
switch (entry.type) {
case EventEntry::Type::KEY: {
- CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS, reason);
const KeyEntry& keyEntry = static_cast<const KeyEntry&>(entry);
+ CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS, reason,
+ keyEntry.traceTracker);
options.displayId = keyEntry.displayId;
options.deviceId = keyEntry.deviceId;
synthesizeCancelationEventsForAllConnectionsLocked(options);
@@ -1484,13 +1526,14 @@
case EventEntry::Type::MOTION: {
const MotionEntry& motionEntry = static_cast<const MotionEntry&>(entry);
if (motionEntry.source & AINPUT_SOURCE_CLASS_POINTER) {
- CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS, reason);
+ CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS, reason,
+ motionEntry.traceTracker);
options.displayId = motionEntry.displayId;
options.deviceId = motionEntry.deviceId;
synthesizeCancelationEventsForAllConnectionsLocked(options);
} else {
CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS,
- reason);
+ reason, motionEntry.traceTracker);
options.displayId = motionEntry.displayId;
options.deviceId = motionEntry.deviceId;
synthesizeCancelationEventsForAllConnectionsLocked(options);
@@ -1625,7 +1668,9 @@
resetKeyRepeatLocked();
}
- CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS, "device was reset");
+ ScopedSyntheticEventTracer traceContext(mTracer);
+ CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS, "device was reset",
+ traceContext.getTracker());
options.deviceId = entry.deviceId;
synthesizeCancelationEventsForAllConnectionsLocked(options);
@@ -1682,7 +1727,7 @@
const bool haveWindowWithPointerCapture = mWindowTokenWithPointerCapture != nullptr;
sp<IBinder> token;
- if (entry->pointerCaptureRequest.enable) {
+ if (entry->pointerCaptureRequest.isEnable()) {
// Enable Pointer Capture.
if (haveWindowWithPointerCapture &&
(entry->pointerCaptureRequest == mCurrentPointerCaptureRequest)) {
@@ -1691,7 +1736,7 @@
ALOGI("Skipping dispatch of Pointer Capture being enabled: no state change.");
return;
}
- if (!mCurrentPointerCaptureRequest.enable) {
+ if (!mCurrentPointerCaptureRequest.isEnable()) {
// This can happen if a window requests capture and immediately releases capture.
ALOGW("No window requested Pointer Capture.");
dropReason = DropReason::NO_POINTER_CAPTURE;
@@ -1704,6 +1749,8 @@
token = mFocusResolver.getFocusedWindowToken(mFocusedDisplayId);
LOG_ALWAYS_FATAL_IF(!token, "Cannot find focused window for Pointer Capture.");
+ LOG_ALWAYS_FATAL_IF(token != entry->pointerCaptureRequest.window,
+ "Unexpected requested window for Pointer Capture.");
mWindowTokenWithPointerCapture = token;
} else {
// Disable Pointer Capture.
@@ -1723,8 +1770,8 @@
}
token = mWindowTokenWithPointerCapture;
mWindowTokenWithPointerCapture = nullptr;
- if (mCurrentPointerCaptureRequest.enable) {
- setPointerCaptureLocked(false);
+ if (mCurrentPointerCaptureRequest.isEnable()) {
+ setPointerCaptureLocked(nullptr);
}
}
@@ -1732,8 +1779,8 @@
if (connection == nullptr) {
// Window has gone away, clean up Pointer Capture state.
mWindowTokenWithPointerCapture = nullptr;
- if (mCurrentPointerCaptureRequest.enable) {
- setPointerCaptureLocked(false);
+ if (mCurrentPointerCaptureRequest.isEnable()) {
+ setPointerCaptureLocked(nullptr);
}
return;
}
@@ -1780,7 +1827,7 @@
DropReason* dropReason, nsecs_t& nextWakeupTime) {
// Preprocessing.
if (!entry->dispatchInProgress) {
- if (entry->repeatCount == 0 && entry->action == AKEY_EVENT_ACTION_DOWN &&
+ if (!entry->syntheticRepeat && entry->action == AKEY_EVENT_ACTION_DOWN &&
(entry->policyFlags & POLICY_FLAG_TRUSTED) &&
(!(entry->policyFlags & POLICY_FLAG_DISABLE_KEY_REPEAT))) {
if (mKeyRepeatState.lastKeyEntry &&
@@ -2014,7 +2061,7 @@
CancelationOptions::Mode mode(
isPointerEvent ? CancelationOptions::Mode::CANCEL_POINTER_EVENTS
: CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS);
- CancelationOptions options(mode, "input event injection failed");
+ CancelationOptions options(mode, "input event injection failed", entry->traceTracker);
options.displayId = entry->displayId;
synthesizeCancelationEventsForMonitorsLocked(options);
return true;
@@ -2120,8 +2167,9 @@
if (connection->status != Connection::Status::NORMAL) {
return;
}
+ ScopedSyntheticEventTracer traceContext(mTracer);
CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS,
- "application not responding");
+ "application not responding", traceContext.getTracker());
sp<WindowInfoHandle> windowHandle;
if (!connection->monitor) {
@@ -3069,7 +3117,7 @@
* If neither of those is true, then it means the touch can be allowed.
*/
InputDispatcher::TouchOcclusionInfo InputDispatcher::computeTouchOcclusionInfoLocked(
- const sp<WindowInfoHandle>& windowHandle, int32_t x, int32_t y) const {
+ const sp<WindowInfoHandle>& windowHandle, float x, float y) const {
const WindowInfo* windowInfo = windowHandle->getInfo();
int32_t displayId = windowInfo->displayId;
const std::vector<sp<WindowInfoHandle>>& windowHandles = getWindowHandlesLocked(displayId);
@@ -3083,7 +3131,8 @@
break; // All future windows are below us. Exit early.
}
const WindowInfo* otherInfo = otherHandle->getInfo();
- if (canBeObscuredBy(windowHandle, otherHandle) && otherInfo->frameContainsPoint(x, y) &&
+ if (canBeObscuredBy(windowHandle, otherHandle) &&
+ windowOccludesTouchAt(*otherInfo, displayId, x, y, getTransformLocked(displayId)) &&
!haveSameApplicationToken(windowInfo, otherInfo)) {
if (DEBUG_TOUCH_OCCLUSION) {
info.debugInfo.push_back(
@@ -3153,7 +3202,7 @@
}
bool InputDispatcher::isWindowObscuredAtPointLocked(const sp<WindowInfoHandle>& windowHandle,
- int32_t x, int32_t y) const {
+ float x, float y) const {
int32_t displayId = windowHandle->getInfo()->displayId;
const std::vector<sp<WindowInfoHandle>>& windowHandles = getWindowHandlesLocked(displayId);
for (const sp<WindowInfoHandle>& otherHandle : windowHandles) {
@@ -3162,7 +3211,7 @@
}
const WindowInfo* otherInfo = otherHandle->getInfo();
if (canBeObscuredBy(windowHandle, otherHandle) &&
- otherInfo->frameContainsPoint(x, y)) {
+ windowOccludesTouchAt(*otherInfo, displayId, x, y, getTransformLocked(displayId))) {
return true;
}
}
@@ -3386,7 +3435,7 @@
// Enqueue a new dispatch entry onto the outbound queue for this connection.
std::unique_ptr<DispatchEntry> dispatchEntry =
createDispatchEntry(mIdGenerator, inputTarget, eventEntry, inputTarget.flags,
- mWindowInfosVsyncId);
+ mWindowInfosVsyncId, mTracer.get());
// Use the eventEntry from dispatchEntry since the entry may have changed and can now be a
// different EventEntry than what was passed in.
@@ -3469,21 +3518,31 @@
usingCoords = pointerInfo->second;
}
}
- // Generate a new MotionEntry with a new eventId using the resolved action and
- // flags.
- resolvedMotion = std::make_shared<
- MotionEntry>(mIdGenerator.nextId(), motionEntry.injectionState,
- motionEntry.eventTime, motionEntry.deviceId,
- motionEntry.source, motionEntry.displayId,
- motionEntry.policyFlags, resolvedAction,
- motionEntry.actionButton, resolvedFlags,
- motionEntry.metaState, motionEntry.buttonState,
- motionEntry.classification, motionEntry.edgeFlags,
- motionEntry.xPrecision, motionEntry.yPrecision,
- motionEntry.xCursorPosition, motionEntry.yCursorPosition,
- motionEntry.downTime,
- usingProperties.value_or(motionEntry.pointerProperties),
- usingCoords.value_or(motionEntry.pointerCoords));
+ {
+ // Generate a new MotionEntry with a new eventId using the resolved action
+ // and flags, and set it as the resolved entry.
+ auto newEntry = std::make_shared<
+ MotionEntry>(mIdGenerator.nextId(), motionEntry.injectionState,
+ motionEntry.eventTime, motionEntry.deviceId,
+ motionEntry.source, motionEntry.displayId,
+ motionEntry.policyFlags, resolvedAction,
+ motionEntry.actionButton, resolvedFlags,
+ motionEntry.metaState, motionEntry.buttonState,
+ motionEntry.classification, motionEntry.edgeFlags,
+ motionEntry.xPrecision, motionEntry.yPrecision,
+ motionEntry.xCursorPosition,
+ motionEntry.yCursorPosition, motionEntry.downTime,
+ usingProperties.value_or(
+ motionEntry.pointerProperties),
+ usingCoords.value_or(motionEntry.pointerCoords));
+ if (mTracer) {
+ ensureEventTraced(motionEntry);
+ newEntry->traceTracker =
+ mTracer->traceDerivedEvent(*newEntry,
+ *motionEntry.traceTracker);
+ }
+ resolvedMotion = newEntry;
+ }
if (ATRACE_ENABLED()) {
std::string message = StringPrintf("Transmute MotionEvent(id=0x%" PRIx32
") to MotionEvent(id=0x%" PRIx32 ").",
@@ -3506,9 +3565,14 @@
LOG(INFO) << "Canceling pointers for device " << resolvedMotion->deviceId << " in "
<< connection->getInputChannelName() << " with event "
<< cancelEvent->getDescription();
+ if (mTracer) {
+ static_cast<MotionEntry&>(*cancelEvent).traceTracker =
+ mTracer->traceDerivedEvent(*cancelEvent, *resolvedMotion->traceTracker);
+ }
std::unique_ptr<DispatchEntry> cancelDispatchEntry =
createDispatchEntry(mIdGenerator, inputTarget, std::move(cancelEvent),
- ftl::Flags<InputTarget::Flags>(), mWindowInfosVsyncId);
+ ftl::Flags<InputTarget::Flags>(), mWindowInfosVsyncId,
+ mTracer.get());
// Send these cancel events to the queue before sending the event from the new
// device.
@@ -3742,7 +3806,8 @@
keyEntry.metaState, keyEntry.repeatCount,
keyEntry.downTime, keyEntry.eventTime);
if (mTracer) {
- mTracer->traceEventDispatch(*dispatchEntry, keyEntry.traceTracker.get());
+ ensureEventTraced(keyEntry);
+ mTracer->traceEventDispatch(*dispatchEntry, *keyEntry.traceTracker);
}
break;
}
@@ -3755,7 +3820,8 @@
const MotionEntry& motionEntry = static_cast<const MotionEntry&>(eventEntry);
status = publishMotionEvent(*connection, *dispatchEntry);
if (mTracer) {
- mTracer->traceEventDispatch(*dispatchEntry, motionEntry.traceTracker.get());
+ ensureEventTraced(motionEntry);
+ mTracer->traceEventDispatch(*dispatchEntry, *motionEntry.traceTracker);
}
break;
}
@@ -3781,9 +3847,10 @@
case EventEntry::Type::POINTER_CAPTURE_CHANGED: {
const auto& captureEntry =
static_cast<const PointerCaptureChangedEntry&>(eventEntry);
- status = connection->inputPublisher
- .publishCaptureEvent(dispatchEntry->seq, captureEntry.id,
- captureEntry.pointerCaptureRequest.enable);
+ status =
+ connection->inputPublisher
+ .publishCaptureEvent(dispatchEntry->seq, captureEntry.id,
+ captureEntry.pointerCaptureRequest.isEnable());
break;
}
@@ -4134,6 +4201,11 @@
switch (cancelationEventEntry->type) {
case EventEntry::Type::KEY: {
+ if (mTracer) {
+ static_cast<KeyEntry&>(*cancelationEventEntry).traceTracker =
+ mTracer->traceDerivedEvent(*cancelationEventEntry,
+ *options.traceTracker);
+ }
const auto& keyEntry = static_cast<const KeyEntry&>(*cancelationEventEntry);
if (window) {
addWindowTargetLocked(window, InputTarget::DispatchMode::AS_IS,
@@ -4145,6 +4217,11 @@
break;
}
case EventEntry::Type::MOTION: {
+ if (mTracer) {
+ static_cast<MotionEntry&>(*cancelationEventEntry).traceTracker =
+ mTracer->traceDerivedEvent(*cancelationEventEntry,
+ *options.traceTracker);
+ }
const auto& motionEntry = static_cast<const MotionEntry&>(*cancelationEventEntry);
if (window) {
std::bitset<MAX_POINTER_ID + 1> pointerIds;
@@ -4192,6 +4269,9 @@
}
if (targets.size() != 1) LOG(FATAL) << __func__ << ": InputTarget not created";
+ if (mTracer) {
+ mTracer->dispatchToTargetHint(*options.traceTracker, targets[0]);
+ }
enqueueDispatchEntryLocked(connection, std::move(cancelationEventEntry), targets[0]);
}
@@ -4203,7 +4283,8 @@
void InputDispatcher::synthesizePointerDownEventsForConnectionLocked(
const nsecs_t downTime, const std::shared_ptr<Connection>& connection,
- ftl::Flags<InputTarget::Flags> targetFlags) {
+ ftl::Flags<InputTarget::Flags> targetFlags,
+ const std::unique_ptr<trace::EventTrackerInterface>& traceTracker) {
if (connection->status != Connection::Status::NORMAL) {
return;
}
@@ -4232,6 +4313,10 @@
std::vector<InputTarget> targets{};
switch (downEventEntry->type) {
case EventEntry::Type::MOTION: {
+ if (mTracer) {
+ static_cast<MotionEntry&>(*downEventEntry).traceTracker =
+ mTracer->traceDerivedEvent(*downEventEntry, *traceTracker);
+ }
const auto& motionEntry = static_cast<const MotionEntry&>(*downEventEntry);
if (windowHandle != nullptr) {
std::bitset<MAX_POINTER_ID + 1> pointerIds;
@@ -4269,6 +4354,9 @@
}
if (targets.size() != 1) LOG(FATAL) << __func__ << ": InputTarget not created";
+ if (mTracer) {
+ mTracer->dispatchToTargetHint(*traceTracker, targets[0]);
+ }
enqueueDispatchEntryLocked(connection, std::move(downEventEntry), targets[0]);
}
@@ -4285,6 +4373,20 @@
MotionEvent::split(originalMotionEntry.action, originalMotionEntry.flags,
/*historySize=*/0, originalMotionEntry.pointerProperties,
originalMotionEntry.pointerCoords, pointerIds);
+ if (pointerIds.count() != pointerCoords.size()) {
+ // TODO(b/329107108): Determine why some IDs in pointerIds were not in originalMotionEntry.
+ // This is bad. We are missing some of the pointers that we expected to deliver.
+ // Most likely this indicates that we received an ACTION_MOVE events that has
+ // different pointer ids than we expected based on the previous ACTION_DOWN
+ // or ACTION_POINTER_DOWN events that caused us to decide to split the pointers
+ // in this way.
+ ALOGW("Dropping split motion event because the pointer count is %d but "
+ "we expected there to be %zu pointers. This probably means we received "
+ "a broken sequence of pointer ids from the input device: %s",
+ pointerCoords.size(), pointerIds.count(),
+ originalMotionEntry.getDescription().c_str());
+ return nullptr;
+ }
// TODO(b/327503168): Move this check inside MotionEvent::split once all callers handle it
// correctly.
@@ -4316,6 +4418,10 @@
originalMotionEntry.xCursorPosition,
originalMotionEntry.yCursorPosition, splitDownTime,
pointerProperties, pointerCoords);
+ if (mTracer) {
+ splitMotionEntry->traceTracker =
+ mTracer->traceDerivedEvent(*splitMotionEntry, *originalMotionEntry.traceTracker);
+ }
return splitMotionEntry;
}
@@ -4637,7 +4743,7 @@
void InputDispatcher::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) {
if (debugInboundEventDetails()) {
ALOGD("notifyPointerCaptureChanged - eventTime=%" PRId64 ", enabled=%s", args.eventTime,
- args.request.enable ? "true" : "false");
+ args.request.isEnable() ? "true" : "false");
}
bool needWake = false;
@@ -4810,6 +4916,10 @@
pointerCount));
transformMotionEntryForInjectionLocked(*nextInjectedEntry,
motionEvent.getTransform());
+ if (mTracer) {
+ nextInjectedEntry->traceTracker =
+ mTracer->traceInboundEvent(*nextInjectedEntry);
+ }
injectedEntries.push(std::move(nextInjectedEntry));
}
break;
@@ -5198,6 +5308,7 @@
}
LOG(INFO) << "setInputWindows displayId=" << displayId << " " << windowList;
}
+ ScopedSyntheticEventTracer traceContext(mTracer);
// Check preconditions for new input windows
for (const sp<WindowInfoHandle>& window : windowInfoHandles) {
@@ -5237,7 +5348,7 @@
std::optional<FocusResolver::FocusChanges> changes =
mFocusResolver.setInputWindows(displayId, windowHandles);
if (changes) {
- onFocusChangedLocked(*changes, removedFocusedWindowHandle);
+ onFocusChangedLocked(*changes, traceContext.getTracker(), removedFocusedWindowHandle);
}
std::unordered_map<int32_t, TouchState>::iterator stateIt =
@@ -5250,7 +5361,7 @@
LOG(INFO) << "Touched window was removed: " << touchedWindow.windowHandle->getName()
<< " in display %" << displayId;
CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
- "touched window was removed");
+ "touched window was removed", traceContext.getTracker());
synthesizeCancelationEventsForWindowLocked(touchedWindow.windowHandle, options);
// Since we are about to drop the touch, cancel the events for the wallpaper as
// well.
@@ -5351,6 +5462,7 @@
}
{ // acquire lock
std::scoped_lock _l(mLock);
+ ScopedSyntheticEventTracer traceContext(mTracer);
if (mFocusedDisplayId != displayId) {
sp<IBinder> oldFocusedWindowToken =
@@ -5363,7 +5475,8 @@
}
CancelationOptions
options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS,
- "The display which contains this window no longer has focus.");
+ "The display which contains this window no longer has focus.",
+ traceContext.getTracker());
options.displayId = ADISPLAY_ID_NONE;
synthesizeCancelationEventsForWindowLocked(windowHandle, options);
}
@@ -5588,19 +5701,22 @@
}
// Synthesize cancel for old window and down for new window.
+ ScopedSyntheticEventTracer traceContext(mTracer);
std::shared_ptr<Connection> fromConnection = getConnectionLocked(fromToken);
std::shared_ptr<Connection> toConnection = getConnectionLocked(toToken);
if (fromConnection != nullptr && toConnection != nullptr) {
fromConnection->inputState.mergePointerStateTo(toConnection->inputState);
CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
- "transferring touch from this window to another window");
+ "transferring touch from this window to another window",
+ traceContext.getTracker());
synthesizeCancelationEventsForWindowLocked(fromWindowHandle, options, fromConnection);
synthesizePointerDownEventsForConnectionLocked(downTimeInTarget, toConnection,
- newTargetFlags);
+ newTargetFlags,
+ traceContext.getTracker());
// Check if the wallpaper window should deliver the corresponding event.
transferWallpaperTouch(oldTargetFlags, newTargetFlags, fromWindowHandle, toWindowHandle,
- *state, deviceId, pointers);
+ *state, deviceId, pointers, traceContext.getTracker());
}
} // release lock
@@ -5668,7 +5784,9 @@
ALOGD("Resetting and dropping all events (%s).", reason);
}
- CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS, reason);
+ ScopedSyntheticEventTracer traceContext(mTracer);
+ CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS, reason,
+ traceContext.getTracker());
synthesizeCancelationEventsForAllConnectionsLocked(options);
resetKeyRepeatLocked();
@@ -5696,7 +5814,7 @@
std::string dump;
dump += StringPrintf(INDENT "Pointer Capture Requested: %s\n",
- toString(mCurrentPointerCaptureRequest.enable));
+ toString(mCurrentPointerCaptureRequest.isEnable()));
std::string windowName = "None";
if (mWindowTokenWithPointerCapture) {
@@ -6063,12 +6181,13 @@
return BAD_VALUE;
}
+ ScopedSyntheticEventTracer traceContext(mTracer);
for (const DeviceId deviceId : deviceIds) {
TouchState& state = *statePtr;
TouchedWindow& window = *windowPtr;
// Send cancel events to all the input channels we're stealing from.
CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
- "input channel stole pointer stream");
+ "input channel stole pointer stream", traceContext.getTracker());
options.deviceId = deviceId;
options.displayId = displayId;
std::vector<PointerProperties> pointers = window.getTouchingPointers(deviceId);
@@ -6114,7 +6233,7 @@
return;
}
- if (enabled == mCurrentPointerCaptureRequest.enable) {
+ if (enabled == mCurrentPointerCaptureRequest.isEnable()) {
ALOGW("Ignoring request to %s Pointer Capture: "
"window has %s requested pointer capture.",
enabled ? "enable" : "disable", enabled ? "already" : "not");
@@ -6130,7 +6249,7 @@
}
}
- setPointerCaptureLocked(enabled);
+ setPointerCaptureLocked(enabled ? windowToken : nullptr);
} // release lock
// Wake the thread to process command entries.
@@ -6492,7 +6611,8 @@
CancelationOptions options(CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS,
"application handled the original non-fallback key "
"or is no longer a foreground target, "
- "canceling previously dispatched fallback key");
+ "canceling previously dispatched fallback key",
+ keyEntry.traceTracker);
options.keyCode = *fallbackKeyCode;
synthesizeCancelationEventsForWindowLocked(windowHandle, options, connection);
}
@@ -6574,7 +6694,8 @@
const auto windowHandle = getWindowHandleLocked(connection->getToken());
if (windowHandle != nullptr) {
CancelationOptions options(CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS,
- "canceling fallback, policy no longer desires it");
+ "canceling fallback, policy no longer desires it",
+ keyEntry.traceTracker);
options.keyCode = *fallbackKeyCode;
synthesizeCancelationEventsForWindowLocked(windowHandle, options, connection);
}
@@ -6610,6 +6731,10 @@
*fallbackKeyCode, event.getScanCode(),
event.getMetaState(), event.getRepeatCount(),
event.getDownTime());
+ if (mTracer) {
+ newEntry->traceTracker =
+ mTracer->traceDerivedEvent(*newEntry, *keyEntry.traceTracker);
+ }
if (DEBUG_OUTBOUND_EVENT_DETAILS) {
ALOGD("Unhandled key event: Dispatching fallback key. "
"originalKeyCode=%d, fallbackKeyCode=%d, fallbackMetaState=%08x",
@@ -6708,16 +6833,19 @@
std::scoped_lock _l(mLock);
std::optional<FocusResolver::FocusChanges> changes =
mFocusResolver.setFocusedWindow(request, getWindowHandlesLocked(request.displayId));
+ ScopedSyntheticEventTracer traceContext(mTracer);
if (changes) {
- onFocusChangedLocked(*changes);
+ onFocusChangedLocked(*changes, traceContext.getTracker());
}
} // release lock
// Wake up poll loop since it may need to make new input dispatching choices.
mLooper->wake();
}
-void InputDispatcher::onFocusChangedLocked(const FocusResolver::FocusChanges& changes,
- const sp<WindowInfoHandle> removedFocusedWindowHandle) {
+void InputDispatcher::onFocusChangedLocked(
+ const FocusResolver::FocusChanges& changes,
+ const std::unique_ptr<trace::EventTrackerInterface>& traceTracker,
+ const sp<WindowInfoHandle> removedFocusedWindowHandle) {
if (changes.oldFocus) {
const auto resolvedWindow = removedFocusedWindowHandle != nullptr
? removedFocusedWindowHandle
@@ -6726,7 +6854,7 @@
LOG(FATAL) << __func__ << ": Previously focused token did not have a window";
}
CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS,
- "focus left window");
+ "focus left window", traceTracker);
synthesizeCancelationEventsForWindowLocked(resolvedWindow, options);
enqueueFocusEventLocked(changes.oldFocus, /*hasFocus=*/false, changes.reason);
}
@@ -6751,14 +6879,14 @@
}
void InputDispatcher::disablePointerCaptureForcedLocked() {
- if (!mCurrentPointerCaptureRequest.enable && !mWindowTokenWithPointerCapture) {
+ if (!mCurrentPointerCaptureRequest.isEnable() && !mWindowTokenWithPointerCapture) {
return;
}
ALOGD_IF(DEBUG_FOCUS, "Disabling Pointer Capture because the window lost focus.");
- if (mCurrentPointerCaptureRequest.enable) {
- setPointerCaptureLocked(false);
+ if (mCurrentPointerCaptureRequest.isEnable()) {
+ setPointerCaptureLocked(nullptr);
}
if (!mWindowTokenWithPointerCapture) {
@@ -6778,8 +6906,8 @@
mInboundQueue.push_front(std::move(entry));
}
-void InputDispatcher::setPointerCaptureLocked(bool enable) {
- mCurrentPointerCaptureRequest.enable = enable;
+void InputDispatcher::setPointerCaptureLocked(const sp<IBinder>& windowToken) {
+ mCurrentPointerCaptureRequest.window = windowToken;
mCurrentPointerCaptureRequest.seq++;
auto command = [this, request = mCurrentPointerCaptureRequest]() REQUIRES(mLock) {
scoped_unlock unlock(mLock);
@@ -6879,9 +7007,10 @@
void InputDispatcher::cancelCurrentTouch() {
{
std::scoped_lock _l(mLock);
+ ScopedSyntheticEventTracer traceContext(mTracer);
ALOGD("Canceling all ongoing pointer gestures on all displays.");
CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
- "cancel current touch");
+ "cancel current touch", traceContext.getTracker());
synthesizeCancelationEventsForAllConnectionsLocked(options);
mTouchStatesByDisplay.clear();
@@ -6931,12 +7060,12 @@
}
}
-void InputDispatcher::transferWallpaperTouch(ftl::Flags<InputTarget::Flags> oldTargetFlags,
- ftl::Flags<InputTarget::Flags> newTargetFlags,
- const sp<WindowInfoHandle> fromWindowHandle,
- const sp<WindowInfoHandle> toWindowHandle,
- TouchState& state, int32_t deviceId,
- const std::vector<PointerProperties>& pointers) {
+void InputDispatcher::transferWallpaperTouch(
+ ftl::Flags<InputTarget::Flags> oldTargetFlags,
+ ftl::Flags<InputTarget::Flags> newTargetFlags, const sp<WindowInfoHandle> fromWindowHandle,
+ const sp<WindowInfoHandle> toWindowHandle, TouchState& state, int32_t deviceId,
+ const std::vector<PointerProperties>& pointers,
+ const std::unique_ptr<trace::EventTrackerInterface>& traceTracker) {
const bool oldHasWallpaper = oldTargetFlags.test(InputTarget::Flags::FOREGROUND) &&
fromWindowHandle->getInfo()->inputConfig.test(
gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
@@ -6954,7 +7083,7 @@
if (oldWallpaper != nullptr) {
CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
- "transferring touch focus to another window");
+ "transferring touch focus to another window", traceTracker);
state.removeWindowByToken(oldWallpaper->getToken());
synthesizeCancelationEventsForWindowLocked(oldWallpaper, options);
}
@@ -6974,7 +7103,7 @@
getConnectionLocked(toWindowHandle->getToken());
toConnection->inputState.mergePointerStateTo(wallpaperConnection->inputState);
synthesizePointerDownEventsForConnectionLocked(downTimeInTarget, wallpaperConnection,
- wallpaperFlags);
+ wallpaperFlags, traceTracker);
}
}
}
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 269bfdd..edca63e 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -421,7 +421,8 @@
void disablePointerCaptureForcedLocked() REQUIRES(mLock);
// Set the Pointer Capture state in the Policy.
- void setPointerCaptureLocked(bool enable) REQUIRES(mLock);
+ // The window is not nullptr for requests to enable, otherwise it is nullptr.
+ void setPointerCaptureLocked(const sp<IBinder>& window) REQUIRES(mLock);
// Dispatcher state at time of last ANR.
std::string mLastAnrState GUARDED_BY(mLock);
@@ -565,11 +566,11 @@
};
TouchOcclusionInfo computeTouchOcclusionInfoLocked(
- const sp<android::gui::WindowInfoHandle>& windowHandle, int32_t x, int32_t y) const
+ const sp<android::gui::WindowInfoHandle>& windowHandle, float x, float y) const
REQUIRES(mLock);
bool isTouchTrustedLocked(const TouchOcclusionInfo& occlusionInfo) const REQUIRES(mLock);
bool isWindowObscuredAtPointLocked(const sp<android::gui::WindowInfoHandle>& windowHandle,
- int32_t x, int32_t y) const REQUIRES(mLock);
+ float x, float y) const REQUIRES(mLock);
bool isWindowObscuredLocked(const sp<android::gui::WindowInfoHandle>& windowHandle) const
REQUIRES(mLock);
std::string dumpWindowForTouchOcclusion(const android::gui::WindowInfo* info,
@@ -628,7 +629,8 @@
void synthesizePointerDownEventsForConnectionLocked(
const nsecs_t downTime, const std::shared_ptr<Connection>& connection,
- ftl::Flags<InputTarget::Flags> targetFlags) REQUIRES(mLock);
+ ftl::Flags<InputTarget::Flags> targetFlags,
+ const std::unique_ptr<trace::EventTrackerInterface>& traceTracker) REQUIRES(mLock);
// Splitting motion events across windows. When splitting motion event for a target,
// splitDownTime refers to the time of first 'down' event on that particular target
@@ -657,6 +659,7 @@
void doInterceptKeyBeforeDispatchingCommand(const sp<IBinder>& focusedWindowToken,
const KeyEntry& entry) REQUIRES(mLock);
void onFocusChangedLocked(const FocusResolver::FocusChanges& changes,
+ const std::unique_ptr<trace::EventTrackerInterface>& traceTracker,
const sp<gui::WindowInfoHandle> removedFocusedWindowHandle = nullptr)
REQUIRES(mLock);
void sendFocusChangedCommandLocked(const sp<IBinder>& oldToken, const sp<IBinder>& newToken)
@@ -704,7 +707,9 @@
const sp<android::gui::WindowInfoHandle> fromWindowHandle,
const sp<android::gui::WindowInfoHandle> toWindowHandle,
TouchState& state, int32_t deviceId,
- const std::vector<PointerProperties>& pointers) REQUIRES(mLock);
+ const std::vector<PointerProperties>& pointers,
+ const std::unique_ptr<trace::EventTrackerInterface>& traceTracker)
+ REQUIRES(mLock);
sp<android::gui::WindowInfoHandle> findWallpaperWindowBelow(
const sp<android::gui::WindowInfoHandle>& windowHandle) const REQUIRES(mLock);
diff --git a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp
index a61fa85..cee741c 100644
--- a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp
+++ b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp
@@ -72,8 +72,7 @@
}
void AndroidInputEventProtoConverter::toProtoWindowDispatchEvent(
- const InputTracingBackendInterface::WindowDispatchArgs& args,
- proto::AndroidWindowInputDispatchEvent& outProto) {
+ const WindowDispatchArgs& args, proto::AndroidWindowInputDispatchEvent& outProto) {
std::visit([&](auto entry) { outProto.set_event_id(entry.id); }, args.eventEntry);
outProto.set_vsync_id(args.vsyncId);
outProto.set_window_id(args.windowId);
diff --git a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h
index 8a46f15..ab5f9ca 100644
--- a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h
+++ b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h
@@ -32,7 +32,7 @@
static void toProtoMotionEvent(const TracedMotionEvent& event,
proto::AndroidMotionEvent& outProto);
static void toProtoKeyEvent(const TracedKeyEvent& event, proto::AndroidKeyEvent& outProto);
- static void toProtoWindowDispatchEvent(const InputTracingBackendInterface::WindowDispatchArgs&,
+ static void toProtoWindowDispatchEvent(const WindowDispatchArgs&,
proto::AndroidWindowInputDispatchEvent& outProto);
};
diff --git a/services/inputflinger/dispatcher/trace/InputTracer.cpp b/services/inputflinger/dispatcher/trace/InputTracer.cpp
index 0be64e6..47e27be 100644
--- a/services/inputflinger/dispatcher/trace/InputTracer.cpp
+++ b/services/inputflinger/dispatcher/trace/InputTracer.cpp
@@ -59,6 +59,17 @@
e.downTime, e.flags, e.repeatCount};
}
+void writeEventToBackend(const TracedEvent& event, const TracedEventArgs args,
+ InputTracingBackendInterface& backend) {
+ std::visit(Visitor{[&](const TracedMotionEvent& e) { backend.traceMotionEvent(e, args); },
+ [&](const TracedKeyEvent& e) { backend.traceKeyEvent(e, args); }},
+ event);
+}
+
+inline auto getId(const trace::TracedEvent& v) {
+ return std::visit([](const auto& event) { return event.id; }, v);
+}
+
} // namespace
// --- InputTracer ---
@@ -67,46 +78,92 @@
: mBackend(std::move(backend)) {}
std::unique_ptr<EventTrackerInterface> InputTracer::traceInboundEvent(const EventEntry& entry) {
- TracedEvent traced;
+ // This is a newly traced inbound event. Create a new state to track it and its derived events.
+ auto eventState = std::make_shared<EventState>(*this);
if (entry.type == EventEntry::Type::MOTION) {
const auto& motion = static_cast<const MotionEntry&>(entry);
- traced = createTracedEvent(motion);
+ eventState->events.emplace_back(createTracedEvent(motion));
} else if (entry.type == EventEntry::Type::KEY) {
const auto& key = static_cast<const KeyEntry&>(entry);
- traced = createTracedEvent(key);
+ eventState->events.emplace_back(createTracedEvent(key));
} else {
LOG(FATAL) << "Cannot trace EventEntry of type: " << ftl::enum_string(entry.type);
}
- return std::make_unique<EventTrackerImpl>(*this, std::move(traced));
+ return std::make_unique<EventTrackerImpl>(std::move(eventState), /*isDerived=*/false);
+}
+
+std::unique_ptr<EventTrackerInterface> InputTracer::createTrackerForSyntheticEvent() {
+ // Create a new EventState to track events derived from this tracker.
+ return std::make_unique<EventTrackerImpl>(std::make_shared<EventState>(*this),
+ /*isDerived=*/false);
}
void InputTracer::dispatchToTargetHint(const EventTrackerInterface& cookie,
const InputTarget& target) {
auto& eventState = getState(cookie);
- if (eventState.isEventProcessingComplete) {
- LOG(FATAL) << "dispatchToTargetHint() should not be called after eventProcessingComplete()";
+ if (eventState->isEventProcessingComplete) {
+ // TODO(b/210460522): Disallow adding new targets after eventProcessingComplete() is called.
+ return;
}
- // TODO(b/210460522): Determine if the event is sensitive based on the target.
+ if (isDerivedCookie(cookie)) {
+ // TODO(b/210460522): Disallow adding new targets from a derived cookie.
+ return;
+ }
+ if (target.windowHandle != nullptr) {
+ eventState->isSecure |= target.windowHandle->getInfo()->layoutParamsFlags.test(
+ gui::WindowInfo::Flag::SECURE);
+ // TODO(b/210460522): Set events as sensitive when the IME connection is active.
+ }
}
void InputTracer::eventProcessingComplete(const EventTrackerInterface& cookie) {
+ if (isDerivedCookie(cookie)) {
+ LOG(FATAL) << "Event processing cannot be set from a derived cookie.";
+ }
auto& eventState = getState(cookie);
- if (eventState.isEventProcessingComplete) {
+ if (eventState->isEventProcessingComplete) {
LOG(FATAL) << "Traced event was already logged. "
"eventProcessingComplete() was likely called more than once.";
}
+ eventState->onEventProcessingComplete();
+}
- std::visit(Visitor{[&](const TracedMotionEvent& e) { mBackend->traceMotionEvent(e); },
- [&](const TracedKeyEvent& e) { mBackend->traceKeyEvent(e); }},
- eventState.event);
- eventState.isEventProcessingComplete = true;
+std::unique_ptr<EventTrackerInterface> InputTracer::traceDerivedEvent(
+ const EventEntry& entry, const EventTrackerInterface& originalEventCookie) {
+ // This is an event derived from an already-established event. Use the same state to track
+ // this event too.
+ auto eventState = getState(originalEventCookie);
+
+ if (entry.type == EventEntry::Type::MOTION) {
+ const auto& motion = static_cast<const MotionEntry&>(entry);
+ eventState->events.emplace_back(createTracedEvent(motion));
+ } else if (entry.type == EventEntry::Type::KEY) {
+ const auto& key = static_cast<const KeyEntry&>(entry);
+ eventState->events.emplace_back(createTracedEvent(key));
+ } else {
+ LOG(FATAL) << "Cannot trace EventEntry of type: " << ftl::enum_string(entry.type);
+ }
+
+ if (eventState->isEventProcessingComplete) {
+ // It is possible for a derived event to be dispatched some time after the original event
+ // is dispatched, such as in the case of key fallback events. To account for these cases,
+ // derived events can be traced after the processing is complete for the original event.
+ const TracedEventArgs traceArgs{.isSecure = eventState->isSecure};
+ writeEventToBackend(eventState->events.back(), traceArgs, *mBackend);
+ }
+ return std::make_unique<EventTrackerImpl>(std::move(eventState), /*isDerived=*/true);
}
void InputTracer::traceEventDispatch(const DispatchEntry& dispatchEntry,
- const EventTrackerInterface* cookie) {
+ const EventTrackerInterface& cookie) {
+ auto& eventState = getState(cookie);
const EventEntry& entry = *dispatchEntry.eventEntry;
+ // TODO(b/328618922): Remove resolved key repeats after making repeatCount non-mutable.
+ // The KeyEntry's repeatCount is mutable and can be modified after an event is initially traced,
+ // so we need to find the repeatCount at the time of dispatching to trace it accurately.
+ int32_t resolvedKeyRepeatCount = 0;
TracedEvent traced;
if (entry.type == EventEntry::Type::MOTION) {
@@ -114,49 +171,80 @@
traced = createTracedEvent(motion);
} else if (entry.type == EventEntry::Type::KEY) {
const auto& key = static_cast<const KeyEntry&>(entry);
+ resolvedKeyRepeatCount = key.repeatCount;
traced = createTracedEvent(key);
} else {
LOG(FATAL) << "Cannot trace EventEntry of type: " << ftl::enum_string(entry.type);
}
- if (!cookie) {
- // This event was not tracked as an inbound event, so trace it now.
- std::visit(Visitor{[&](const TracedMotionEvent& e) { mBackend->traceMotionEvent(e); },
- [&](const TracedKeyEvent& e) { mBackend->traceKeyEvent(e); }},
- traced);
+ auto tracedEventIt =
+ std::find_if(eventState->events.begin(), eventState->events.end(),
+ [&traced](const auto& event) { return getId(traced) == getId(event); });
+ if (tracedEventIt == eventState->events.end()) {
+ LOG(FATAL)
+ << __func__
+ << ": Failed to find a previously traced event that matches the dispatched event";
}
// The vsyncId only has meaning if the event is targeting a window.
const int32_t windowId = dispatchEntry.windowId.value_or(0);
const int32_t vsyncId = dispatchEntry.windowId.has_value() ? dispatchEntry.vsyncId : 0;
- mBackend->traceWindowDispatch({std::move(traced), dispatchEntry.deliveryTime,
- dispatchEntry.resolvedFlags, dispatchEntry.targetUid, vsyncId,
- windowId, dispatchEntry.transform, dispatchEntry.rawTransform,
- /*hmac=*/{}});
+ // TODO(b/210460522): Pass HMAC into traceEventDispatch.
+ const WindowDispatchArgs windowDispatchArgs{std::move(traced),
+ dispatchEntry.deliveryTime,
+ dispatchEntry.resolvedFlags,
+ dispatchEntry.targetUid,
+ vsyncId,
+ windowId,
+ dispatchEntry.transform,
+ dispatchEntry.rawTransform,
+ /*hmac=*/{},
+ resolvedKeyRepeatCount};
+ if (eventState->isEventProcessingComplete) {
+ mBackend->traceWindowDispatch(std::move(windowDispatchArgs),
+ TracedEventArgs{.isSecure = eventState->isSecure});
+ } else {
+ eventState->pendingDispatchArgs.emplace_back(std::move(windowDispatchArgs));
+ }
}
-InputTracer::EventState& InputTracer::getState(const EventTrackerInterface& cookie) {
+std::shared_ptr<InputTracer::EventState>& InputTracer::getState(
+ const EventTrackerInterface& cookie) {
return static_cast<const EventTrackerImpl&>(cookie).mState;
}
-// --- InputTracer::EventTrackerImpl ---
+bool InputTracer::isDerivedCookie(const EventTrackerInterface& cookie) {
+ return static_cast<const EventTrackerImpl&>(cookie).mIsDerived;
+}
-InputTracer::EventTrackerImpl::EventTrackerImpl(InputTracer& tracer, TracedEvent&& event)
- : mTracer(tracer), mState(event) {}
+// --- InputTracer::EventState ---
-InputTracer::EventTrackerImpl::~EventTrackerImpl() {
- if (mState.isEventProcessingComplete) {
+void InputTracer::EventState::onEventProcessingComplete() {
+ // Write all of the events known so far to the trace.
+ const TracedEventArgs traceArgs{.isSecure = isSecure};
+ for (const auto& event : events) {
+ writeEventToBackend(event, traceArgs, *tracer.mBackend);
+ }
+ // Write all pending dispatch args to the trace.
+ for (const auto& windowDispatchArgs : pendingDispatchArgs) {
+ tracer.mBackend->traceWindowDispatch(windowDispatchArgs, traceArgs);
+ }
+ pendingDispatchArgs.clear();
+
+ isEventProcessingComplete = true;
+}
+
+InputTracer::EventState::~EventState() {
+ if (isEventProcessingComplete) {
// This event has already been written to the trace as expected.
return;
}
// The event processing was never marked as complete, so do it now.
- // TODO(b/210460522): Determine why/where the event is being destroyed before
- // eventProcessingComplete() is called.
- std::visit(Visitor{[&](const TracedMotionEvent& e) { mTracer.mBackend->traceMotionEvent(e); },
- [&](const TracedKeyEvent& e) { mTracer.mBackend->traceKeyEvent(e); }},
- mState.event);
- mState.isEventProcessingComplete = true;
+ // We should never end up here in normal operation. However, in tests, it's possible that we
+ // stop and destroy InputDispatcher without waiting for it to finish processing events, at
+ // which point an event (and thus its EventState) may be destroyed before processing finishes.
+ onEventProcessingComplete();
}
} // namespace android::inputdispatcher::trace::impl
diff --git a/services/inputflinger/dispatcher/trace/InputTracer.h b/services/inputflinger/dispatcher/trace/InputTracer.h
index 1acac6d..4ef6ca6 100644
--- a/services/inputflinger/dispatcher/trace/InputTracer.h
+++ b/services/inputflinger/dispatcher/trace/InputTracer.h
@@ -42,38 +42,50 @@
InputTracer& operator=(const InputTracer&) = delete;
std::unique_ptr<EventTrackerInterface> traceInboundEvent(const EventEntry&) override;
+ std::unique_ptr<EventTrackerInterface> createTrackerForSyntheticEvent() override;
void dispatchToTargetHint(const EventTrackerInterface&, const InputTarget&) override;
void eventProcessingComplete(const EventTrackerInterface&) override;
- void traceEventDispatch(const DispatchEntry&, const EventTrackerInterface*) override;
+ std::unique_ptr<EventTrackerInterface> traceDerivedEvent(const EventEntry&,
+ const EventTrackerInterface&) override;
+ void traceEventDispatch(const DispatchEntry&, const EventTrackerInterface&) override;
private:
std::unique_ptr<InputTracingBackendInterface> mBackend;
- // The state of a tracked event.
+ // The state of a tracked event, shared across all events derived from the original event.
struct EventState {
- explicit inline EventState(TracedEvent event) : event(std::move(event)){};
+ explicit inline EventState(InputTracer& tracer) : tracer(tracer){};
+ ~EventState();
- const TracedEvent event;
+ void onEventProcessingComplete();
+
+ InputTracer& tracer;
+ std::vector<const TracedEvent> events;
bool isEventProcessingComplete{false};
- // TODO(b/210460522): Add additional args for tracking event sensitivity and
- // dispatch target UIDs.
+ // A queue to hold dispatch args from being traced until event processing is complete.
+ std::vector<const WindowDispatchArgs> pendingDispatchArgs;
+ // True if the event is targeting at least one secure window;
+ bool isSecure{false};
};
// Get the event state associated with a tracking cookie.
- EventState& getState(const EventTrackerInterface&);
+ std::shared_ptr<EventState>& getState(const EventTrackerInterface&);
+ bool isDerivedCookie(const EventTrackerInterface&);
// Implementation of the event tracker cookie. The cookie holds the event state directly for
// convenience to avoid the overhead of tracking the state separately in InputTracer.
class EventTrackerImpl : public EventTrackerInterface {
public:
- explicit EventTrackerImpl(InputTracer&, TracedEvent&& entry);
- virtual ~EventTrackerImpl() override;
+ inline EventTrackerImpl(const std::shared_ptr<EventState>& state, bool isDerivedEvent)
+ : mState(state), mIsDerived(isDerivedEvent) {}
+ EventTrackerImpl(const EventTrackerImpl&) = default;
private:
- InputTracer& mTracer;
- mutable EventState mState;
+ mutable std::shared_ptr<EventState> mState;
+ const bool mIsDerived;
- friend EventState& InputTracer::getState(const EventTrackerInterface&);
+ friend std::shared_ptr<EventState>& InputTracer::getState(const EventTrackerInterface&);
+ friend bool InputTracer::isDerivedCookie(const EventTrackerInterface&);
};
};
diff --git a/services/inputflinger/dispatcher/trace/InputTracerInterface.h b/services/inputflinger/dispatcher/trace/InputTracerInterface.h
index c6cd7de..609d10c 100644
--- a/services/inputflinger/dispatcher/trace/InputTracerInterface.h
+++ b/services/inputflinger/dispatcher/trace/InputTracerInterface.h
@@ -54,6 +54,14 @@
virtual std::unique_ptr<EventTrackerInterface> traceInboundEvent(const EventEntry&) = 0;
/**
+ * Create a trace tracker for a synthetic event that does not stem from an inbound input event.
+ * This includes things like generating cancellations or down events for various reasons,
+ * such as ANR, pilfering, transfer touch, etc. Any key or motion events generated for this
+ * synthetic event should be traced as a derived event using {@link #traceDerivedEvent}.
+ */
+ virtual std::unique_ptr<EventTrackerInterface> createTrackerForSyntheticEvent() = 0;
+
+ /**
* Notify the tracer that the traced event will be sent to the given InputTarget.
* The tracer may change how the event is logged depending on the target. For example,
* events targeting certain UIDs may be logged as sensitive events.
@@ -76,12 +84,25 @@
virtual void eventProcessingComplete(const EventTrackerInterface&) = 0;
/**
- * Trace an input event being successfully dispatched to a window. The dispatched event may
- * be a previously traced inbound event, or it may be a synthesized event that has not been
- * previously traced. For inbound events that were previously traced, the EventTracker cookie
- * must be provided. For events that were not previously traced, the cookie must be null.
+ * Trace an input event that is derived from another event. This is used in cases where an event
+ * is modified from the original, such as when a touch is split across multiple windows, or
+ * when a HOVER_MOVE event is modified to be a HOVER_EXIT, etc. The original event's tracker
+ * must be provided, and a new EventTracker is returned that should be used to track the event's
+ * lifecycle.
+ *
+ * NOTE: The derived tracker cannot be used to change the targets of the original event, meaning
+ * it cannot be used with {@link #dispatchToTargetHint} or {@link eventProcessingComplete}.
*/
- virtual void traceEventDispatch(const DispatchEntry&, const EventTrackerInterface*) = 0;
+ virtual std::unique_ptr<EventTrackerInterface> traceDerivedEvent(
+ const EventEntry&, const EventTrackerInterface& originalEventTracker) = 0;
+
+ /**
+ * Trace an input event being successfully dispatched to a window. The dispatched event may
+ * be a previously traced inbound event, or it may be a synthesized event. All dispatched events
+ * must have been previously traced, so the trace tracker associated with the event must be
+ * provided.
+ */
+ virtual void traceEventDispatch(const DispatchEntry&, const EventTrackerInterface&) = 0;
};
} // namespace android::inputdispatcher::trace
diff --git a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h
index b0eadfe..6eef12e 100644
--- a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h
+++ b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h
@@ -74,6 +74,26 @@
/** A representation of a traced input event. */
using TracedEvent = std::variant<TracedKeyEvent, TracedMotionEvent>;
+/** Additional information about an input event being traced. */
+struct TracedEventArgs {
+ // True if the event is targeting at least one secure window.
+ bool isSecure;
+};
+
+/** Additional information about an input event being dispatched to a window. */
+struct WindowDispatchArgs {
+ TracedEvent eventEntry;
+ nsecs_t deliveryTime;
+ int32_t resolvedFlags;
+ gui::Uid targetUid;
+ int64_t vsyncId;
+ int32_t windowId;
+ ui::Transform transform;
+ ui::Transform rawTransform;
+ std::array<uint8_t, 32> hmac;
+ int32_t resolvedKeyRepeatCount;
+};
+
/**
* An interface for the tracing backend, used for setting a custom backend for testing.
*/
@@ -82,24 +102,13 @@
virtual ~InputTracingBackendInterface() = default;
/** Trace a KeyEvent. */
- virtual void traceKeyEvent(const TracedKeyEvent&) = 0;
+ virtual void traceKeyEvent(const TracedKeyEvent&, const TracedEventArgs&) = 0;
/** Trace a MotionEvent. */
- virtual void traceMotionEvent(const TracedMotionEvent&) = 0;
+ virtual void traceMotionEvent(const TracedMotionEvent&, const TracedEventArgs&) = 0;
/** Trace an event being sent to a window. */
- struct WindowDispatchArgs {
- TracedEvent eventEntry;
- nsecs_t deliveryTime;
- int32_t resolvedFlags;
- gui::Uid targetUid;
- int64_t vsyncId;
- int32_t windowId;
- ui::Transform transform;
- ui::Transform rawTransform;
- std::array<uint8_t, 32> hmac;
- };
- virtual void traceWindowDispatch(const WindowDispatchArgs&) = 0;
+ virtual void traceWindowDispatch(const WindowDispatchArgs&, const TracedEventArgs&) = 0;
};
} // namespace android::inputdispatcher::trace
diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
index 46ad9e1..8ef9ca5 100644
--- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
+++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
@@ -63,7 +63,12 @@
});
}
-void PerfettoBackend::traceMotionEvent(const TracedMotionEvent& event) {
+void PerfettoBackend::traceMotionEvent(const TracedMotionEvent& event,
+ const TracedEventArgs& args) {
+ if (args.isSecure) {
+ // For now, avoid tracing secure event entirely.
+ return;
+ }
InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) {
auto tracePacket = ctx.NewTracePacket();
auto* inputEvent = tracePacket->set_android_input_event();
@@ -72,7 +77,11 @@
});
}
-void PerfettoBackend::traceKeyEvent(const TracedKeyEvent& event) {
+void PerfettoBackend::traceKeyEvent(const TracedKeyEvent& event, const TracedEventArgs& args) {
+ if (args.isSecure) {
+ // For now, avoid tracing secure event entirely.
+ return;
+ }
InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) {
auto tracePacket = ctx.NewTracePacket();
auto* inputEvent = tracePacket->set_android_input_event();
@@ -81,13 +90,17 @@
});
}
-void PerfettoBackend::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs) {
+void PerfettoBackend::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs,
+ const TracedEventArgs& args) {
+ if (args.isSecure) {
+ // For now, avoid tracing secure event entirely.
+ return;
+ }
InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) {
auto tracePacket = ctx.NewTracePacket();
- auto* inputEventProto = tracePacket->set_android_input_event();
- auto* dispatchEventProto = inputEventProto->set_dispatcher_window_dispatch_event();
- AndroidInputEventProtoConverter::toProtoWindowDispatchEvent(dispatchArgs,
- *dispatchEventProto);
+ auto* inputEvent = tracePacket->set_android_input_event();
+ auto* dispatchEvent = inputEvent->set_dispatcher_window_dispatch_event();
+ AndroidInputEventProtoConverter::toProtoWindowDispatchEvent(dispatchArgs, *dispatchEvent);
});
}
diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
index fefcfb3..d455375 100644
--- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
+++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
@@ -48,9 +48,9 @@
PerfettoBackend();
~PerfettoBackend() override = default;
- void traceKeyEvent(const TracedKeyEvent&) override;
- void traceMotionEvent(const TracedMotionEvent&) override;
- void traceWindowDispatch(const WindowDispatchArgs&) override;
+ void traceKeyEvent(const TracedKeyEvent&, const TracedEventArgs&) override;
+ void traceMotionEvent(const TracedMotionEvent&, const TracedEventArgs&) override;
+ void traceWindowDispatch(const WindowDispatchArgs&, const TracedEventArgs&) override;
class InputEventDataSource : public perfetto::DataSource<InputEventDataSource> {
public:
diff --git a/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
index 25bc227..b1791b3 100644
--- a/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
+++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
@@ -53,23 +53,26 @@
}
template <typename Backend>
-void ThreadedBackend<Backend>::traceMotionEvent(const TracedMotionEvent& event) {
+void ThreadedBackend<Backend>::traceMotionEvent(const TracedMotionEvent& event,
+ const TracedEventArgs& traceArgs) {
std::scoped_lock lock(mLock);
- mQueue.emplace_back(event);
+ mQueue.emplace_back(event, traceArgs);
mThreadWakeCondition.notify_all();
}
template <typename Backend>
-void ThreadedBackend<Backend>::traceKeyEvent(const TracedKeyEvent& event) {
+void ThreadedBackend<Backend>::traceKeyEvent(const TracedKeyEvent& event,
+ const TracedEventArgs& traceArgs) {
std::scoped_lock lock(mLock);
- mQueue.emplace_back(event);
+ mQueue.emplace_back(event, traceArgs);
mThreadWakeCondition.notify_all();
}
template <typename Backend>
-void ThreadedBackend<Backend>::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs) {
+void ThreadedBackend<Backend>::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs,
+ const TracedEventArgs& traceArgs) {
std::scoped_lock lock(mLock);
- mQueue.emplace_back(dispatchArgs);
+ mQueue.emplace_back(dispatchArgs, traceArgs);
mThreadWakeCondition.notify_all();
}
@@ -93,11 +96,13 @@
// Trace the events into the backend without holding the lock to reduce the amount of
// work performed in the critical section.
- for (const auto& entry : entries) {
- std::visit(Visitor{[&](const TracedMotionEvent& e) { mBackend.traceMotionEvent(e); },
- [&](const TracedKeyEvent& e) { mBackend.traceKeyEvent(e); },
+ for (const auto& [entry, traceArgs] : entries) {
+ std::visit(Visitor{[&](const TracedMotionEvent& e) {
+ mBackend.traceMotionEvent(e, traceArgs);
+ },
+ [&](const TracedKeyEvent& e) { mBackend.traceKeyEvent(e, traceArgs); },
[&](const WindowDispatchArgs& args) {
- mBackend.traceWindowDispatch(args);
+ mBackend.traceWindowDispatch(args, traceArgs);
}},
entry);
}
diff --git a/services/inputflinger/dispatcher/trace/ThreadedBackend.h b/services/inputflinger/dispatcher/trace/ThreadedBackend.h
index 5776cf9..cab47af 100644
--- a/services/inputflinger/dispatcher/trace/ThreadedBackend.h
+++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.h
@@ -38,9 +38,9 @@
ThreadedBackend(Backend&& innerBackend);
~ThreadedBackend() override;
- void traceKeyEvent(const TracedKeyEvent&) override;
- void traceMotionEvent(const TracedMotionEvent&) override;
- void traceWindowDispatch(const WindowDispatchArgs&) override;
+ void traceKeyEvent(const TracedKeyEvent&, const TracedEventArgs&) override;
+ void traceMotionEvent(const TracedMotionEvent&, const TracedEventArgs&) override;
+ void traceWindowDispatch(const WindowDispatchArgs&, const TracedEventArgs&) override;
private:
std::mutex mLock;
@@ -48,11 +48,11 @@
bool mThreadExit GUARDED_BY(mLock){false};
std::condition_variable mThreadWakeCondition;
Backend mBackend;
- using TraceEntry = std::variant<TracedKeyEvent, TracedMotionEvent, WindowDispatchArgs>;
+ using TraceEntry =
+ std::pair<std::variant<TracedKeyEvent, TracedMotionEvent, WindowDispatchArgs>,
+ TracedEventArgs>;
std::vector<TraceEntry> mQueue GUARDED_BY(mLock);
- using WindowDispatchArgs = InputTracingBackendInterface::WindowDispatchArgs;
-
void threadLoop();
};
diff --git a/services/inputflinger/include/NotifyArgsBuilders.h b/services/inputflinger/include/NotifyArgsBuilders.h
index e4363a4..8ffbc11 100644
--- a/services/inputflinger/include/NotifyArgsBuilders.h
+++ b/services/inputflinger/include/NotifyArgsBuilders.h
@@ -107,7 +107,9 @@
// Set mouse cursor position for the most common cases to avoid boilerplate.
if (mSource == AINPUT_SOURCE_MOUSE &&
- !MotionEvent::isValidCursorPosition(mRawXCursorPosition, mRawYCursorPosition)) {
+ !MotionEvent::isValidCursorPosition(mRawXCursorPosition, mRawYCursorPosition) &&
+ BitSet64::hasBit(pointerCoords[0].bits, AMOTION_EVENT_AXIS_X) &&
+ BitSet64::hasBit(pointerCoords[0].bits, AMOTION_EVENT_AXIS_Y)) {
mRawXCursorPosition = pointerCoords[0].getX();
mRawYCursorPosition = pointerCoords[0].getY();
}
diff --git a/services/inputflinger/include/PointerChoreographerPolicyInterface.h b/services/inputflinger/include/PointerChoreographerPolicyInterface.h
index 8b47b55..462aedc 100644
--- a/services/inputflinger/include/PointerChoreographerPolicyInterface.h
+++ b/services/inputflinger/include/PointerChoreographerPolicyInterface.h
@@ -25,6 +25,9 @@
*
* This is the interface that PointerChoreographer uses to talk to Window Manager and other
* system components.
+ *
+ * NOTE: In general, the PointerChoreographer must not interact with the policy while
+ * holding any locks.
*/
class PointerChoreographerPolicyInterface {
public:
@@ -37,6 +40,9 @@
* for and runnable on the host, the PointerController implementation must be in a separate
* library, libinputservice, that has the additional dependencies. The PointerController
* will be mocked when testing PointerChoreographer.
+ *
+ * Since this is a factory method used to work around dependencies, it will not interact with
+ * other input components and may be called with the PointerChoreographer lock held.
*/
virtual std::shared_ptr<PointerControllerInterface> createPointerController(
PointerControllerInterface::ControllerType type) = 0;
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
index 06f10e5..c8cc5dc 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
@@ -486,7 +486,7 @@
}
void CursorInputMapper::configureOnPointerCapture(const InputReaderConfiguration& config) {
- if (config.pointerCaptureRequest.enable) {
+ if (config.pointerCaptureRequest.isEnable()) {
if (mParameters.mode == Parameters::Mode::POINTER) {
mParameters.mode = Parameters::Mode::POINTER_RELATIVE;
mSource = AINPUT_SOURCE_MOUSE_RELATIVE;
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 3c26d1d..7d27d4a 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -923,7 +923,7 @@
// Determine device mode.
if (mParameters.deviceType == Parameters::DeviceType::POINTER &&
- mConfig.pointerGesturesEnabled && !mConfig.pointerCaptureRequest.enable) {
+ mConfig.pointerGesturesEnabled && !mConfig.pointerCaptureRequest.isEnable()) {
mSource = AINPUT_SOURCE_MOUSE;
mDeviceMode = DeviceMode::POINTER;
if (hasStylus()) {
@@ -1038,7 +1038,7 @@
(mDeviceMode == DeviceMode::POINTER) ||
// - when pointer capture is enabled, to preserve the mouse cursor position;
(mParameters.deviceType == Parameters::DeviceType::POINTER &&
- mConfig.pointerCaptureRequest.enable) ||
+ mConfig.pointerCaptureRequest.isEnable()) ||
// - when we should be showing touches;
(mDeviceMode == DeviceMode::DIRECT && mConfig.showTouches) ||
// - when we should be showing a pointer icon for direct styluses.
@@ -1047,7 +1047,7 @@
if (mPointerController == nullptr) {
mPointerController = getContext()->getPointerController(getDeviceId());
}
- if (mConfig.pointerCaptureRequest.enable) {
+ if (mConfig.pointerCaptureRequest.isEnable()) {
mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
}
} else {
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index eacc66e..99f9e24 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -410,9 +410,9 @@
.setBoolValues({config.touchpadRightClickZoneEnabled});
}
std::list<NotifyArgs> out;
- if ((!changes.any() && config.pointerCaptureRequest.enable) ||
+ if ((!changes.any() && config.pointerCaptureRequest.isEnable()) ||
changes.test(InputReaderConfiguration::Change::POINTER_CAPTURE)) {
- mPointerCaptured = config.pointerCaptureRequest.enable;
+ mPointerCaptured = config.pointerCaptureRequest.isEnable();
// The motion ranges are going to change, so bump the generation to clear the cached ones.
bumpGeneration();
if (mPointerCaptured) {
diff --git a/services/inputflinger/reader/mapper/gestures/GesturesLogging.cpp b/services/inputflinger/reader/mapper/gestures/GesturesLogging.cpp
index 81b4968..26028c5 100644
--- a/services/inputflinger/reader/mapper/gestures/GesturesLogging.cpp
+++ b/services/inputflinger/reader/mapper/gestures/GesturesLogging.cpp
@@ -26,15 +26,30 @@
extern "C" {
+namespace {
+
+/**
+ * Log details of each gesture output by the gestures library.
+ * Enable this via "adb shell setprop log.tag.TouchpadInputMapperGestures DEBUG" (requires
+ * restarting the shell)
+ */
+const bool DEBUG_TOUCHPAD_GESTURES =
+ __android_log_is_loggable(ANDROID_LOG_DEBUG, "TouchpadInputMapperGestures",
+ ANDROID_LOG_INFO);
+
+} // namespace
+
void gestures_log(int verb, const char* fmt, ...) {
va_list args;
va_start(args, fmt);
if (verb == GESTURES_LOG_ERROR) {
LOG_PRI_VA(ANDROID_LOG_ERROR, LOG_TAG, fmt, args);
- } else if (verb == GESTURES_LOG_INFO) {
- LOG_PRI_VA(ANDROID_LOG_INFO, LOG_TAG, fmt, args);
- } else {
- LOG_PRI_VA(ANDROID_LOG_DEBUG, LOG_TAG, fmt, args);
+ } else if (DEBUG_TOUCHPAD_GESTURES) {
+ if (verb == GESTURES_LOG_INFO) {
+ LOG_PRI_VA(ANDROID_LOG_INFO, LOG_TAG, fmt, args);
+ } else {
+ LOG_PRI_VA(ANDROID_LOG_DEBUG, LOG_TAG, fmt, args);
+ }
}
va_end(args);
}
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index a26153e..09ae6dd 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -39,7 +39,6 @@
],
srcs: [
"AnrTracker_test.cpp",
- "BlockingQueue_test.cpp",
"CapturedTouchpadEventConverter_test.cpp",
"CursorInputMapper_test.cpp",
"EventHub_test.cpp",
diff --git a/services/inputflinger/tests/CursorInputMapper_test.cpp b/services/inputflinger/tests/CursorInputMapper_test.cpp
index de74067..c44f880 100644
--- a/services/inputflinger/tests/CursorInputMapper_test.cpp
+++ b/services/inputflinger/tests/CursorInputMapper_test.cpp
@@ -166,7 +166,7 @@
}
void setPointerCapture(bool enabled) {
- mReaderConfiguration.pointerCaptureRequest.enable = enabled;
+ mReaderConfiguration.pointerCaptureRequest.window = enabled ? sp<BBinder>::make() : nullptr;
mReaderConfiguration.pointerCaptureRequest.seq = 1;
int32_t generation = mDevice->getGeneration();
std::list<NotifyArgs> args =
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
index 9e93712..8f593b5 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
@@ -178,8 +178,8 @@
transform = t;
}
-PointerCaptureRequest FakeInputReaderPolicy::setPointerCapture(bool enabled) {
- mConfig.pointerCaptureRequest = {enabled, mNextPointerCaptureSequenceNumber++};
+PointerCaptureRequest FakeInputReaderPolicy::setPointerCapture(const sp<IBinder>& window) {
+ mConfig.pointerCaptureRequest = {window, mNextPointerCaptureSequenceNumber++};
return mConfig.pointerCaptureRequest;
}
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h
index da5085d..710bb54 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.h
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.h
@@ -68,7 +68,7 @@
TouchAffineTransformation getTouchAffineTransformation(const std::string& inputDeviceDescriptor,
ui::Rotation surfaceRotation);
void setTouchAffineTransformation(const TouchAffineTransformation t);
- PointerCaptureRequest setPointerCapture(bool enabled);
+ PointerCaptureRequest setPointerCapture(const sp<IBinder>& window);
void setShowTouches(bool enabled);
void setDefaultPointerDisplayId(int32_t pointerDisplayId);
void setPointerGestureEnabled(bool enabled);
diff --git a/services/inputflinger/tests/FakeInputTracingBackend.cpp b/services/inputflinger/tests/FakeInputTracingBackend.cpp
index 4655ee8..069b50d 100644
--- a/services/inputflinger/tests/FakeInputTracingBackend.cpp
+++ b/services/inputflinger/tests/FakeInputTracingBackend.cpp
@@ -37,10 +37,9 @@
return std::visit([](const auto& event) { return event.id; }, v);
}
-MotionEvent toInputEvent(
- const trace::TracedMotionEvent& e,
- const trace::InputTracingBackendInterface::WindowDispatchArgs& dispatchArgs,
- const std::array<uint8_t, 32>& hmac) {
+MotionEvent toInputEvent(const trace::TracedMotionEvent& e,
+ const trace::WindowDispatchArgs& dispatchArgs,
+ const std::array<uint8_t, 32>& hmac) {
MotionEvent traced;
traced.initialize(e.id, e.deviceId, e.source, e.displayId, hmac, e.action, e.actionButton,
dispatchArgs.resolvedFlags, e.edgeFlags, e.metaState, e.buttonState,
@@ -51,13 +50,12 @@
return traced;
}
-KeyEvent toInputEvent(const trace::TracedKeyEvent& e,
- const trace::InputTracingBackendInterface::WindowDispatchArgs& dispatchArgs,
+KeyEvent toInputEvent(const trace::TracedKeyEvent& e, const trace::WindowDispatchArgs& dispatchArgs,
const std::array<uint8_t, 32>& hmac) {
KeyEvent traced;
traced.initialize(e.id, e.deviceId, e.source, e.displayId, hmac, e.action,
- dispatchArgs.resolvedFlags, e.keyCode, e.scanCode, e.metaState, e.repeatCount,
- e.downTime, e.eventTime);
+ dispatchArgs.resolvedFlags, e.keyCode, e.scanCode, e.metaState,
+ dispatchArgs.resolvedKeyRepeatCount, e.downTime, e.eventTime);
return traced;
}
@@ -120,7 +118,7 @@
auto tracedDispatchesIt =
std::find_if(mTracedWindowDispatches.begin(), mTracedWindowDispatches.end(),
- [&](const WindowDispatchArgs& args) {
+ [&](const trace::WindowDispatchArgs& args) {
return args.windowId == expectedWindowId &&
getId(args.eventEntry) == expectedEvent.getId();
});
@@ -163,7 +161,8 @@
// --- FakeInputTracingBackend ---
-void FakeInputTracingBackend::traceKeyEvent(const trace::TracedKeyEvent& event) {
+void FakeInputTracingBackend::traceKeyEvent(const trace::TracedKeyEvent& event,
+ const trace::TracedEventArgs&) {
{
std::scoped_lock lock(mTrace->mLock);
mTrace->mTracedEvents.emplace(event.id, event);
@@ -171,7 +170,8 @@
mTrace->mEventTracedCondition.notify_all();
}
-void FakeInputTracingBackend::traceMotionEvent(const trace::TracedMotionEvent& event) {
+void FakeInputTracingBackend::traceMotionEvent(const trace::TracedMotionEvent& event,
+ const trace::TracedEventArgs&) {
{
std::scoped_lock lock(mTrace->mLock);
mTrace->mTracedEvents.emplace(event.id, event);
@@ -179,7 +179,8 @@
mTrace->mEventTracedCondition.notify_all();
}
-void FakeInputTracingBackend::traceWindowDispatch(const WindowDispatchArgs& args) {
+void FakeInputTracingBackend::traceWindowDispatch(const trace::WindowDispatchArgs& args,
+ const trace::TracedEventArgs&) {
{
std::scoped_lock lock(mTrace->mLock);
mTrace->mTracedWindowDispatches.push_back(args);
diff --git a/services/inputflinger/tests/FakeInputTracingBackend.h b/services/inputflinger/tests/FakeInputTracingBackend.h
index 1b3613d..ab05d6b 100644
--- a/services/inputflinger/tests/FakeInputTracingBackend.h
+++ b/services/inputflinger/tests/FakeInputTracingBackend.h
@@ -59,8 +59,7 @@
std::mutex mLock;
std::condition_variable mEventTracedCondition;
std::unordered_map<uint32_t /*eventId*/, trace::TracedEvent> mTracedEvents GUARDED_BY(mLock);
- using WindowDispatchArgs = trace::InputTracingBackendInterface::WindowDispatchArgs;
- std::vector<WindowDispatchArgs> mTracedWindowDispatches GUARDED_BY(mLock);
+ std::vector<trace::WindowDispatchArgs> mTracedWindowDispatches GUARDED_BY(mLock);
std::vector<std::pair<std::variant<KeyEvent, MotionEvent>, int32_t /*windowId*/>>
mExpectedEvents GUARDED_BY(mLock);
@@ -83,9 +82,11 @@
private:
std::shared_ptr<VerifyingTrace> mTrace;
- void traceKeyEvent(const trace::TracedKeyEvent& entry) override;
- void traceMotionEvent(const trace::TracedMotionEvent& entry) override;
- void traceWindowDispatch(const WindowDispatchArgs& entry) override;
+ void traceKeyEvent(const trace::TracedKeyEvent& entry, const trace::TracedEventArgs&) override;
+ void traceMotionEvent(const trace::TracedMotionEvent& entry,
+ const trace::TracedEventArgs&) override;
+ void traceWindowDispatch(const trace::WindowDispatchArgs& entry,
+ const trace::TracedEventArgs&) override;
};
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/tests/FakeWindowHandle.h b/services/inputflinger/tests/FakeWindowHandle.h
index fe25130..8ce61e7 100644
--- a/services/inputflinger/tests/FakeWindowHandle.h
+++ b/services/inputflinger/tests/FakeWindowHandle.h
@@ -17,6 +17,7 @@
#pragma once
#include <android-base/logging.h>
+#include <input/InputConsumer.h>
#include "../dispatcher/InputDispatcher.h"
using android::base::Result;
diff --git a/services/inputflinger/tests/FocusResolver_test.cpp b/services/inputflinger/tests/FocusResolver_test.cpp
index 2ff9c3c..cb8c3cb 100644
--- a/services/inputflinger/tests/FocusResolver_test.cpp
+++ b/services/inputflinger/tests/FocusResolver_test.cpp
@@ -20,6 +20,7 @@
#define ASSERT_FOCUS_CHANGE(_changes, _oldFocus, _newFocus) \
{ \
+ ASSERT_TRUE(_changes.has_value()); \
ASSERT_EQ(_oldFocus, _changes->oldFocus); \
ASSERT_EQ(_newFocus, _changes->newFocus); \
}
@@ -152,6 +153,38 @@
ASSERT_FOCUS_CHANGE(changes, /*from*/ invisibleWindowToken, /*to*/ nullptr);
}
+TEST(FocusResolverTest, FocusTransferToMirror) {
+ sp<IBinder> focusableWindowToken = sp<BBinder>::make();
+ auto window = sp<FakeWindowHandle>::make("Window", focusableWindowToken,
+ /*focusable=*/true, /*visible=*/true);
+ auto mirror = sp<FakeWindowHandle>::make("Mirror", focusableWindowToken,
+ /*focusable=*/true, /*visible=*/true);
+
+ FocusRequest request;
+ request.displayId = 42;
+ request.token = focusableWindowToken;
+ FocusResolver focusResolver;
+ std::optional<FocusResolver::FocusChanges> changes =
+ focusResolver.setFocusedWindow(request, {window, mirror});
+ ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ focusableWindowToken);
+
+ // The mirror window now comes on top, and the focus does not change
+ changes = focusResolver.setInputWindows(request.displayId, {mirror, window});
+ ASSERT_FALSE(changes.has_value());
+
+ // The window now comes on top while the mirror is removed, and the focus does not change
+ changes = focusResolver.setInputWindows(request.displayId, {window});
+ ASSERT_FALSE(changes.has_value());
+
+ // The window is removed but the mirror is on top, and focus does not change
+ changes = focusResolver.setInputWindows(request.displayId, {mirror});
+ ASSERT_FALSE(changes.has_value());
+
+ // All windows removed
+ changes = focusResolver.setInputWindows(request.displayId, {});
+ ASSERT_FOCUS_CHANGE(changes, /*from*/ focusableWindowToken, /*to*/ nullptr);
+}
+
TEST(FocusResolverTest, SetInputWindows) {
sp<IBinder> focusableWindowToken = sp<BBinder>::make();
std::vector<sp<WindowInfoHandle>> windows;
@@ -169,6 +202,10 @@
focusResolver.setFocusedWindow(request, windows);
ASSERT_EQ(focusableWindowToken, changes->newFocus);
+ // When there are no changes to the window, focus does not change
+ changes = focusResolver.setInputWindows(request.displayId, windows);
+ ASSERT_FALSE(changes.has_value());
+
// Window visibility changes and the window loses focus
window->setVisible(false);
changes = focusResolver.setInputWindows(request.displayId, windows);
@@ -380,18 +417,13 @@
ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ windowToken);
ASSERT_EQ(request.displayId, changes->displayId);
- // Start with a focused window
- window->setFocusable(true);
- changes = focusResolver.setInputWindows(request.displayId, windows);
- ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ windowToken);
-
// When a display is removed, all windows are removed from the display
// and our focused window loses focus
changes = focusResolver.setInputWindows(request.displayId, {});
ASSERT_FOCUS_CHANGE(changes, /*from*/ windowToken, /*to*/ nullptr);
focusResolver.displayRemoved(request.displayId);
- // When a display is readded, the window does not get focus since the request was cleared.
+ // When a display is re-added, the window does not get focus since the request was cleared.
changes = focusResolver.setInputWindows(request.displayId, windows);
ASSERT_FALSE(changes);
}
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index f0f4d93..6a6fc14 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -15,7 +15,6 @@
*/
#include "../dispatcher/InputDispatcher.h"
-#include "../BlockingQueue.h"
#include "FakeApplicationHandle.h"
#include "FakeInputTracingBackend.h"
#include "TestEventMatchers.h"
@@ -32,7 +31,9 @@
#include <flag_macros.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
+#include <input/BlockingQueue.h>
#include <input/Input.h>
+#include <input/InputConsumer.h>
#include <input/PrintTools.h>
#include <linux/input.h>
#include <sys/epoll.h>
@@ -308,17 +309,22 @@
"signal";
}
- PointerCaptureRequest assertSetPointerCaptureCalled(bool enabled) {
+ PointerCaptureRequest assertSetPointerCaptureCalled(const sp<WindowInfoHandle>& window,
+ bool enabled) {
std::unique_lock lock(mLock);
base::ScopedLockAssertion assumeLocked(mLock);
- if (!mPointerCaptureChangedCondition.wait_for(lock, 100ms,
- [this, enabled]() REQUIRES(mLock) {
- return mPointerCaptureRequest->enable ==
- enabled;
- })) {
- ADD_FAILURE() << "Timed out waiting for setPointerCapture(" << enabled
- << ") to be called.";
+ if (!mPointerCaptureChangedCondition
+ .wait_for(lock, 100ms, [this, enabled, window]() REQUIRES(mLock) {
+ if (enabled) {
+ return mPointerCaptureRequest->isEnable() &&
+ mPointerCaptureRequest->window == window->getToken();
+ } else {
+ return !mPointerCaptureRequest->isEnable();
+ }
+ })) {
+ ADD_FAILURE() << "Timed out waiting for setPointerCapture(" << window->getName() << ", "
+ << enabled << ") to be called.";
return {};
}
auto request = *mPointerCaptureRequest;
@@ -333,7 +339,7 @@
if (mPointerCaptureChangedCondition.wait_for(lock, 100ms) != std::cv_status::timeout) {
FAIL() << "Expected setPointerCapture(request) to not be called, but was called. "
"enabled = "
- << std::to_string(mPointerCaptureRequest->enable);
+ << std::to_string(mPointerCaptureRequest->isEnable());
}
mPointerCaptureRequest.reset();
}
@@ -5361,6 +5367,94 @@
window->assertNoEvents();
}
+// This test verifies the occlusion detection for all rotations of the display by tapping
+// in different locations on the display, specifically points close to the four corners of a
+// window.
+TEST_P(InputDispatcherDisplayOrientationFixture, BlockUntrustClickInDifferentOrientations) {
+ constexpr static int32_t displayWidth = 400;
+ constexpr static int32_t displayHeight = 800;
+
+ std::shared_ptr<FakeApplicationHandle> untrustedWindowApplication =
+ std::make_shared<FakeApplicationHandle>();
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+ const auto rotation = GetParam();
+
+ // Set up the display with the specified rotation.
+ const bool isRotated = rotation == ui::ROTATION_90 || rotation == ui::ROTATION_270;
+ const int32_t logicalDisplayWidth = isRotated ? displayHeight : displayWidth;
+ const int32_t logicalDisplayHeight = isRotated ? displayWidth : displayHeight;
+ const ui::Transform displayTransform(ui::Transform::toRotationFlags(rotation),
+ logicalDisplayWidth, logicalDisplayHeight);
+ addDisplayInfo(ADISPLAY_ID_DEFAULT, displayTransform);
+
+ // Create a window that not trusted.
+ const Rect untrustedWindowFrameInLogicalDisplay(100, 100, 200, 300);
+
+ const Rect untrustedWindowFrameInDisplay =
+ displayTransform.inverse().transform(untrustedWindowFrameInLogicalDisplay);
+
+ sp<FakeWindowHandle> untrustedWindow =
+ sp<FakeWindowHandle>::make(untrustedWindowApplication, mDispatcher, "UntrustedWindow",
+ ADISPLAY_ID_DEFAULT);
+ untrustedWindow->setFrame(untrustedWindowFrameInDisplay, displayTransform);
+ untrustedWindow->setTrustedOverlay(false);
+ untrustedWindow->setTouchOcclusionMode(TouchOcclusionMode::BLOCK_UNTRUSTED);
+ untrustedWindow->setTouchable(false);
+ untrustedWindow->setAlpha(1.0f);
+ untrustedWindow->setOwnerInfo(gui::Pid{1}, gui::Uid{101});
+ addWindow(untrustedWindow);
+
+ // Create a simple app window below the untrusted window.
+ const Rect simpleAppWindowFrameInLogicalDisplay(0, 0, 300, 600);
+ const Rect simpleAppWindowFrameInDisplay =
+ displayTransform.inverse().transform(simpleAppWindowFrameInLogicalDisplay);
+
+ sp<FakeWindowHandle> simpleAppWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "SimpleAppWindow",
+ ADISPLAY_ID_DEFAULT);
+ simpleAppWindow->setFrame(simpleAppWindowFrameInDisplay, displayTransform);
+ simpleAppWindow->setOwnerInfo(gui::Pid{2}, gui::Uid{202});
+ addWindow(simpleAppWindow);
+
+ // The following points in logical display space should be inside the untrusted window, so
+ // the simple window could not receive events that coordinate is these point.
+ static const std::array<vec2, 4> untrustedPoints{
+ {{100, 100}, {199.99, 100}, {100, 299.99}, {199.99, 299.99}}};
+
+ for (const auto untrustedPoint : untrustedPoints) {
+ const vec2 p = displayTransform.inverse().transform(untrustedPoint);
+ const PointF pointInDisplaySpace{p.x, p.y};
+ mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+ AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {pointInDisplaySpace}));
+ mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP,
+ AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {pointInDisplaySpace}));
+ }
+ untrustedWindow->assertNoEvents();
+ simpleAppWindow->assertNoEvents();
+ // The following points in logical display space should be outside the untrusted window, so
+ // the simple window should receive events that coordinate is these point.
+ static const std::array<vec2, 5> trustedPoints{
+ {{200, 100}, {100, 300}, {200, 300}, {100, 99.99}, {99.99, 100}}};
+ for (const auto trustedPoint : trustedPoints) {
+ const vec2 p = displayTransform.inverse().transform(trustedPoint);
+ const PointF pointInDisplaySpace{p.x, p.y};
+ mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+ AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {pointInDisplaySpace}));
+ simpleAppWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT,
+ AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED);
+ mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP,
+ AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {pointInDisplaySpace}));
+ simpleAppWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT,
+ AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED);
+ }
+ untrustedWindow->assertNoEvents();
+}
+
// Run the precision tests for all rotations.
INSTANTIATE_TEST_SUITE_P(InputDispatcherDisplayOrientationTests,
InputDispatcherDisplayOrientationFixture,
@@ -7477,6 +7571,12 @@
mWindow->consumeKeyUp(ADISPLAY_ID_DEFAULT,
/*expectedFlags=*/0);
}
+
+ void injectKeyRepeat(int32_t repeatCount) {
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, repeatCount, ADISPLAY_ID_DEFAULT))
+ << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
+ }
};
TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_ReceivesKeyRepeat) {
@@ -7565,6 +7665,17 @@
}
}
+TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_CorrectRepeatCountWhenInjectKeyRepeat) {
+ injectKeyRepeat(0);
+ mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+ for (int32_t repeatCount = 1; repeatCount <= 2; ++repeatCount) {
+ expectKeyRepeatOnce(repeatCount);
+ }
+ injectKeyRepeat(1);
+ // Expect repeatCount to be 3 instead of 1
+ expectKeyRepeatOnce(3);
+}
+
/* Test InputDispatcher for MultiDisplay */
class InputDispatcherFocusOnTwoDisplaysTest : public InputDispatcherTest {
public:
@@ -8709,9 +8820,10 @@
// Define a valid key down event that is stale (too old).
event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_NONE,
INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, /*flags=*/0, AKEYCODE_A, KEY_A,
- AMETA_NONE, /*repeatCount=*/1, eventTime, eventTime);
+ AMETA_NONE, /*repeatCount=*/0, eventTime, eventTime);
- const int32_t policyFlags = POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER;
+ const int32_t policyFlags =
+ POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER | POLICY_FLAG_DISABLE_KEY_REPEAT;
InputEventInjectionResult result =
mDispatcher->injectInputEvent(&event, /*targetUid=*/{},
@@ -9870,7 +9982,7 @@
PointerCaptureRequest requestAndVerifyPointerCapture(const sp<FakeWindowHandle>& window,
bool enabled) {
mDispatcher->requestPointerCapture(window->getToken(), enabled);
- auto request = mFakePolicy->assertSetPointerCaptureCalled(enabled);
+ auto request = mFakePolicy->assertSetPointerCaptureCalled(window, enabled);
notifyPointerCaptureChanged(request);
window->consumeCaptureEvent(enabled);
return request;
@@ -9903,7 +10015,7 @@
mWindow->consumeCaptureEvent(false);
mWindow->consumeFocusEvent(false);
mSecondWindow->consumeFocusEvent(true);
- mFakePolicy->assertSetPointerCaptureCalled(false);
+ mFakePolicy->assertSetPointerCaptureCalled(mWindow, false);
// Ensure that additional state changes from InputReader are not sent to the window.
notifyPointerCaptureChanged({});
@@ -9922,7 +10034,7 @@
notifyPointerCaptureChanged(request);
// Ensure that Pointer Capture is disabled.
- mFakePolicy->assertSetPointerCaptureCalled(false);
+ mFakePolicy->assertSetPointerCaptureCalled(mWindow, false);
mWindow->consumeCaptureEvent(false);
mWindow->assertNoEvents();
}
@@ -9932,13 +10044,13 @@
// The first window loses focus.
setFocusedWindow(mSecondWindow);
- mFakePolicy->assertSetPointerCaptureCalled(false);
+ mFakePolicy->assertSetPointerCaptureCalled(mWindow, false);
mWindow->consumeCaptureEvent(false);
// Request Pointer Capture from the second window before the notification from InputReader
// arrives.
mDispatcher->requestPointerCapture(mSecondWindow->getToken(), true);
- auto request = mFakePolicy->assertSetPointerCaptureCalled(true);
+ auto request = mFakePolicy->assertSetPointerCaptureCalled(mSecondWindow, true);
// InputReader notifies Pointer Capture was disabled (because of the focus change).
notifyPointerCaptureChanged({});
@@ -9953,11 +10065,11 @@
TEST_F(InputDispatcherPointerCaptureTests, EnableRequestFollowsSequenceNumbers) {
// App repeatedly enables and disables capture.
mDispatcher->requestPointerCapture(mWindow->getToken(), true);
- auto firstRequest = mFakePolicy->assertSetPointerCaptureCalled(true);
+ auto firstRequest = mFakePolicy->assertSetPointerCaptureCalled(mWindow, true);
mDispatcher->requestPointerCapture(mWindow->getToken(), false);
- mFakePolicy->assertSetPointerCaptureCalled(false);
+ mFakePolicy->assertSetPointerCaptureCalled(mWindow, false);
mDispatcher->requestPointerCapture(mWindow->getToken(), true);
- auto secondRequest = mFakePolicy->assertSetPointerCaptureCalled(true);
+ auto secondRequest = mFakePolicy->assertSetPointerCaptureCalled(mWindow, true);
// InputReader notifies that PointerCapture has been enabled for the first request. Since the
// first request is now stale, this should do nothing.
@@ -9974,10 +10086,10 @@
// App toggles pointer capture off and on.
mDispatcher->requestPointerCapture(mWindow->getToken(), false);
- mFakePolicy->assertSetPointerCaptureCalled(false);
+ mFakePolicy->assertSetPointerCaptureCalled(mWindow, false);
mDispatcher->requestPointerCapture(mWindow->getToken(), true);
- auto enableRequest = mFakePolicy->assertSetPointerCaptureCalled(true);
+ auto enableRequest = mFakePolicy->assertSetPointerCaptureCalled(mWindow, true);
// InputReader notifies that the latest "enable" request was processed, while skipping over the
// preceding "disable" request.
@@ -10029,6 +10141,26 @@
mWindow->assertNoEvents();
}
+using InputDispatcherPointerCaptureDeathTest = InputDispatcherPointerCaptureTests;
+
+TEST_F(InputDispatcherPointerCaptureDeathTest,
+ NotifyPointerCaptureChangedWithWrongTokenAbortsDispatcher) {
+ testing::GTEST_FLAG(death_test_style) = "threadsafe";
+ ScopedSilentDeath _silentDeath;
+
+ mDispatcher->requestPointerCapture(mWindow->getToken(), true);
+ auto request = mFakePolicy->assertSetPointerCaptureCalled(mWindow, true);
+
+ // Dispatch a pointer changed event with a wrong token.
+ request.window = mSecondWindow->getToken();
+ ASSERT_DEATH(
+ {
+ notifyPointerCaptureChanged(request);
+ mSecondWindow->consumeCaptureEvent(true);
+ },
+ "Unexpected requested window for Pointer Capture.");
+}
+
class InputDispatcherUntrustedTouchesTest : public InputDispatcherTest {
protected:
constexpr static const float MAXIMUM_OBSCURING_OPACITY = 0.8;
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 835f8b8..1d46c9a 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -1165,18 +1165,18 @@
TEST_F(InputReaderTest, ChangingPointerCaptureNotifiesInputListener) {
NotifyPointerCaptureChangedArgs args;
- auto request = mFakePolicy->setPointerCapture(true);
+ auto request = mFakePolicy->setPointerCapture(/*window=*/sp<BBinder>::make());
mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::POINTER_CAPTURE);
mReader->loopOnce();
mFakeListener->assertNotifyCaptureWasCalled(&args);
- ASSERT_TRUE(args.request.enable) << "Pointer Capture should be enabled.";
+ ASSERT_TRUE(args.request.isEnable()) << "Pointer Capture should be enabled.";
ASSERT_EQ(args.request, request) << "Pointer Capture sequence number should match.";
- mFakePolicy->setPointerCapture(false);
+ mFakePolicy->setPointerCapture(/*window=*/nullptr);
mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::POINTER_CAPTURE);
mReader->loopOnce();
mFakeListener->assertNotifyCaptureWasCalled(&args);
- ASSERT_FALSE(args.request.enable) << "Pointer Capture should be disabled.";
+ ASSERT_FALSE(args.request.isEnable()) << "Pointer Capture should be disabled.";
// Verify that the Pointer Capture state is not updated when the configuration value
// does not change.
@@ -9802,7 +9802,7 @@
prepareAxes(POSITION | ID | SLOT);
mFakeEventHub->addKey(EVENTHUB_ID, BTN_LEFT, 0, AKEYCODE_UNKNOWN, 0);
mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0);
- mFakePolicy->setPointerCapture(true);
+ mFakePolicy->setPointerCapture(/*window=*/sp<BBinder>::make());
mFakePolicy->setPointerController(fakePointerController);
MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
@@ -9934,7 +9934,7 @@
ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
// non captured touchpad should be a mouse source
- mFakePolicy->setPointerCapture(false);
+ mFakePolicy->setPointerCapture(/*window=*/nullptr);
configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
@@ -10012,14 +10012,14 @@
prepareAxes(POSITION | ID | SLOT);
mFakeEventHub->addKey(EVENTHUB_ID, BTN_LEFT, 0, AKEYCODE_UNKNOWN, 0);
mFakePolicy->setPointerController(fakePointerController);
- mFakePolicy->setPointerCapture(false);
+ mFakePolicy->setPointerCapture(/*window=*/nullptr);
MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
// uncaptured touchpad should be a pointer device
ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
// captured touchpad should be a touchpad device
- mFakePolicy->setPointerCapture(true);
+ mFakePolicy->setPointerCapture(/*window=*/sp<BBinder>::make());
configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE);
ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
}
@@ -10090,7 +10090,7 @@
prepareAbsoluteAxisResolution(xAxisResolution, yAxisResolution);
// In order to enable swipe and freeform gesture in pointer mode, pointer capture
// needs to be disabled, and the pointer gesture needs to be enabled.
- mFakePolicy->setPointerCapture(false);
+ mFakePolicy->setPointerCapture(/*window=*/nullptr);
mFakePolicy->setPointerGestureEnabled(true);
mFakePolicy->setPointerController(fakePointerController);
diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp
index e9e5061..8ddb672 100644
--- a/services/inputflinger/tests/PointerChoreographer_test.cpp
+++ b/services/inputflinger/tests/PointerChoreographer_test.cpp
@@ -355,6 +355,38 @@
AllOf(WithCoords(110, 220), WithDisplayId(DISPLAY_ID), WithCursorPosition(110, 220)));
}
+TEST_F(PointerChoreographerTest, AbsoluteMouseMovesPointerAndReturnsNewArgs) {
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+
+ // Set initial position of the PointerController.
+ pc->setPosition(100, 200);
+ const auto absoluteMousePointer = PointerBuilder(/*id=*/0, ToolType::MOUSE)
+ .axis(AMOTION_EVENT_AXIS_X, 110)
+ .axis(AMOTION_EVENT_AXIS_Y, 220);
+
+ // Make NotifyMotionArgs and notify Choreographer.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(absoluteMousePointer)
+ .deviceId(DEVICE_ID)
+ .displayId(ADISPLAY_ID_NONE)
+ .build());
+
+ // Check that the PointerController updated the position and the pointer is shown.
+ pc->assertPosition(110, 220);
+ ASSERT_TRUE(pc->isPointerShown());
+
+ // Check that x-y coordinates, displayId and cursor position are correctly updated.
+ mTestListener.assertNotifyMotionWasCalled(
+ AllOf(WithCoords(110, 220), WithRelativeMotion(10, 20), WithDisplayId(DISPLAY_ID),
+ WithCursorPosition(110, 220)));
+}
+
TEST_F(PointerChoreographerTest,
AssociatedMouseMovesPointerOnAssociatedDisplayAndDoesNotMovePointerOnDefaultDisplay) {
// Add two displays and set one to default.
@@ -413,7 +445,8 @@
{generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE_RELATIVE, ADISPLAY_ID_NONE)}});
mChoreographer.notifyPointerCaptureChanged(
NotifyPointerCaptureChangedArgs(/*id=*/2, systemTime(SYSTEM_TIME_MONOTONIC),
- PointerCaptureRequest(/*enable=*/true, /*seq=*/0)));
+ PointerCaptureRequest(/*window=*/sp<BBinder>::make(),
+ /*seq=*/0)));
// Notify motion as if pointer capture is enabled.
mChoreographer.notifyMotion(
@@ -450,7 +483,8 @@
// Enable pointer capture and check if the PointerController hid the pointer.
mChoreographer.notifyPointerCaptureChanged(
NotifyPointerCaptureChangedArgs(/*id=*/1, systemTime(SYSTEM_TIME_MONOTONIC),
- PointerCaptureRequest(/*enable=*/true, /*seq=*/0)));
+ PointerCaptureRequest(/*window=*/sp<BBinder>::make(),
+ /*seq=*/0)));
ASSERT_FALSE(pc->isPointerShown());
}
@@ -1295,7 +1329,8 @@
// Assume that pointer capture is enabled.
mChoreographer.notifyPointerCaptureChanged(
NotifyPointerCaptureChangedArgs(/*id=*/1, systemTime(SYSTEM_TIME_MONOTONIC),
- PointerCaptureRequest(/*enable=*/true, /*seq=*/0)));
+ PointerCaptureRequest(/*window=*/sp<BBinder>::make(),
+ /*seq=*/0)));
// Notify motion as if pointer capture is enabled.
mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHPAD)
@@ -1329,7 +1364,8 @@
// Enable pointer capture and check if the PointerController hid the pointer.
mChoreographer.notifyPointerCaptureChanged(
NotifyPointerCaptureChangedArgs(/*id=*/1, systemTime(SYSTEM_TIME_MONOTONIC),
- PointerCaptureRequest(/*enable=*/true, /*seq=*/0)));
+ PointerCaptureRequest(/*window=*/sp<BBinder>::make(),
+ /*seq=*/0)));
ASSERT_FALSE(pc->isPointerShown());
}
diff --git a/services/inputflinger/tests/fuzzers/BlockingQueueFuzzer.cpp b/services/inputflinger/tests/fuzzers/BlockingQueueFuzzer.cpp
index 219b662..863d0a1 100644
--- a/services/inputflinger/tests/fuzzers/BlockingQueueFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/BlockingQueueFuzzer.cpp
@@ -15,8 +15,8 @@
*/
#include <fuzzer/FuzzedDataProvider.h>
+#include <input/BlockingQueue.h>
#include <thread>
-#include "BlockingQueue.h"
// Chosen to be a number large enough for variation in fuzzer runs, but not consume too much memory.
static constexpr size_t MAX_CAPACITY = 1024;
diff --git a/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp
index c2bf275..643e8b9 100644
--- a/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp
@@ -125,6 +125,9 @@
config.touchpadTapToClickEnabled = fdp.ConsumeBool();
config.touchpadTapDraggingEnabled = fdp.ConsumeBool();
config.touchpadRightClickZoneEnabled = fdp.ConsumeBool();
+
+ config.pointerCaptureRequest.window = fdp.ConsumeBool() ? sp<BBinder>::make() : nullptr;
+ config.pointerCaptureRequest.seq = fdp.ConsumeIntegral<uint32_t>();
}
} // namespace
@@ -145,7 +148,6 @@
// Some settings are fuzzed here, as well as in the main loop, to provide randomized data to the
// TouchpadInputMapper constructor.
setTouchpadSettings(*fdp, policyConfig);
- policyConfig.pointerCaptureRequest.enable = fdp->ConsumeBool();
TouchpadInputMapper& mapper =
getMapperForDevice<ThreadSafeFuzzedDataProvider, TouchpadInputMapper>(*fdp, device,
policyConfig);
@@ -164,7 +166,6 @@
[&]() -> void { mapper.getSources(); },
[&]() -> void {
setTouchpadSettings(*fdp, policyConfig);
- policyConfig.pointerCaptureRequest.enable = fdp->ConsumeBool();
std::list<NotifyArgs> unused =
mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), policyConfig,
InputReaderConfiguration::Change(
diff --git a/services/powermanager/Android.bp b/services/powermanager/Android.bp
index 3ea08fe..4f65e77 100644
--- a/services/powermanager/Android.bp
+++ b/services/powermanager/Android.bp
@@ -20,7 +20,6 @@
"PowerHintSessionWrapper.cpp",
"PowerSaveState.cpp",
"Temperature.cpp",
- "WorkDuration.cpp",
"WorkSource.cpp",
":libpowermanager_aidl",
],
@@ -52,6 +51,10 @@
"android.hardware.power@1.3",
],
+ whole_static_libs: [
+ "android.os.hintmanager_aidl-ndk",
+ ],
+
cflags: [
"-Wall",
"-Werror",
diff --git a/services/powermanager/WorkDuration.cpp b/services/powermanager/WorkDuration.cpp
deleted file mode 100644
index bd2b10a..0000000
--- a/services/powermanager/WorkDuration.cpp
+++ /dev/null
@@ -1,52 +0,0 @@
-/**
- * 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.
- */
-
-#define LOG_TAG "WorkDuration"
-
-#include <android/WorkDuration.h>
-#include <android/performance_hint.h>
-#include <binder/Parcel.h>
-#include <utils/Log.h>
-
-namespace android::os {
-
-WorkDuration::WorkDuration(int64_t startTimestampNanos, int64_t totalDurationNanos,
- int64_t cpuDurationNanos, int64_t gpuDurationNanos)
- : timestampNanos(0),
- actualTotalDurationNanos(totalDurationNanos),
- workPeriodStartTimestampNanos(startTimestampNanos),
- actualCpuDurationNanos(cpuDurationNanos),
- actualGpuDurationNanos(gpuDurationNanos) {}
-
-status_t WorkDuration::writeToParcel(Parcel* parcel) const {
- if (parcel == nullptr) {
- ALOGE("%s: Null parcel", __func__);
- return BAD_VALUE;
- }
-
- parcel->writeInt64(workPeriodStartTimestampNanos);
- parcel->writeInt64(actualTotalDurationNanos);
- parcel->writeInt64(actualCpuDurationNanos);
- parcel->writeInt64(actualGpuDurationNanos);
- parcel->writeInt64(timestampNanos);
- return OK;
-}
-
-status_t WorkDuration::readFromParcel(const Parcel*) {
- return INVALID_OPERATION;
-}
-
-} // namespace android::os
diff --git a/services/powermanager/include/android/WorkDuration.h b/services/powermanager/include/android/WorkDuration.h
deleted file mode 100644
index 26a575f..0000000
--- a/services/powermanager/include/android/WorkDuration.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/**
- * 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 <binder/Parcelable.h>
-#include <math.h>
-
-struct AWorkDuration {};
-
-namespace android::os {
-
-/**
- * C++ Parcelable version of {@link PerformanceHintManager.WorkDuration} that can be used in
- * binder calls.
- * This file needs to be kept in sync with the WorkDuration in
- * frameworks/base/core/java/android/os/WorkDuration.java
- */
-struct WorkDuration : AWorkDuration, android::Parcelable {
- WorkDuration() = default;
- ~WorkDuration() = default;
-
- WorkDuration(int64_t workPeriodStartTimestampNanos, int64_t actualTotalDurationNanos,
- int64_t actualCpuDurationNanos, int64_t actualGpuDurationNanos);
- status_t writeToParcel(Parcel* parcel) const override;
- status_t readFromParcel(const Parcel* parcel) override;
-
- inline bool equalsWithoutTimestamp(const WorkDuration& other) const {
- return workPeriodStartTimestampNanos == other.workPeriodStartTimestampNanos &&
- actualTotalDurationNanos == other.actualTotalDurationNanos &&
- actualCpuDurationNanos == other.actualCpuDurationNanos &&
- actualGpuDurationNanos == other.actualGpuDurationNanos;
- }
-
- bool operator==(const WorkDuration& other) const {
- return timestampNanos == other.timestampNanos && equalsWithoutTimestamp(other);
- }
-
- bool operator!=(const WorkDuration& other) const { return !(*this == other); }
-
- friend std::ostream& operator<<(std::ostream& os, const WorkDuration& workDuration) {
- os << "{"
- << "workPeriodStartTimestampNanos: " << workDuration.workPeriodStartTimestampNanos
- << ", actualTotalDurationNanos: " << workDuration.actualTotalDurationNanos
- << ", actualCpuDurationNanos: " << workDuration.actualCpuDurationNanos
- << ", actualGpuDurationNanos: " << workDuration.actualGpuDurationNanos
- << ", timestampNanos: " << workDuration.timestampNanos << "}";
- return os;
- }
-
- int64_t timestampNanos;
- int64_t actualTotalDurationNanos;
- int64_t workPeriodStartTimestampNanos;
- int64_t actualCpuDurationNanos;
- int64_t actualGpuDurationNanos;
-};
-
-} // namespace android::os
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index d771803..dc69b81 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -83,11 +83,11 @@
"libprotobuf-cpp-lite",
"libsync",
"libui",
- "libinput",
"libutils",
"libSurfaceFlingerProp",
],
static_libs: [
+ "iinputflinger_aidl_lib_static",
"libaidlcommonsupport",
"libcompositionengine",
"libframetimeline",
@@ -210,7 +210,6 @@
"Scheduler/VsyncModulator.cpp",
"Scheduler/VsyncSchedule.cpp",
"ScreenCaptureOutput.cpp",
- "StartPropertySetThread.cpp",
"SurfaceFlinger.cpp",
"SurfaceFlingerDefaultFactory.cpp",
"Tracing/LayerDataSource.cpp",
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
index 18a96f4..843b5c5 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
@@ -19,6 +19,7 @@
#include <chrono>
#include <optional>
#include <vector>
+#include "utils/Timers.h"
#include <compositionengine/Display.h>
#include <compositionengine/LayerFE.h>
@@ -105,6 +106,9 @@
bool hasTrustedPresentationListener = false;
ICEPowerCallback* powerCallback = nullptr;
+
+ // System time for when frame refresh starts. Used for stats.
+ nsecs_t refreshStartTime = 0;
};
} // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
index a1d6132..a499928 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
@@ -58,8 +58,7 @@
// Called before composition starts. Should return true if this layer has
// pending updates which would require an extra display refresh cycle to
// process.
- virtual bool onPreComposition(nsecs_t refreshStartTime,
- bool updatingOutputGeometryThisFrame) = 0;
+ virtual bool onPreComposition(bool updatingOutputGeometryThisFrame) = 0;
struct ClientCompositionTargetSettings {
enum class BlurSetting {
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
index 15e4577..1b8cc27 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
@@ -43,7 +43,7 @@
MOCK_CONST_METHOD0(getCompositionState, const LayerFECompositionState*());
- MOCK_METHOD2(onPreComposition, bool(nsecs_t, bool));
+ MOCK_METHOD1(onPreComposition, bool(bool));
MOCK_CONST_METHOD1(prepareClientComposition,
std::optional<compositionengine::LayerFE::LayerSettings>(
diff --git a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
index d87eae3..b470208 100644
--- a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
@@ -181,10 +181,10 @@
bool needsAnotherUpdate = false;
- mRefreshStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ mRefreshStartTime = args.refreshStartTime;
for (auto& layer : args.layers) {
- if (layer->onPreComposition(mRefreshStartTime, args.updatingOutputGeometryThisFrame)) {
+ if (layer->onPreComposition(args.updatingOutputGeometryThisFrame)) {
needsAnotherUpdate = true;
}
}
diff --git a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
index da578e2..042010e 100644
--- a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
@@ -214,6 +214,7 @@
TEST_F(CompositionTestPreComposition, preCompositionSetsFrameTimestamp) {
const nsecs_t before = systemTime(SYSTEM_TIME_MONOTONIC);
+ mRefreshArgs.refreshStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
mEngine.preComposition(mRefreshArgs);
const nsecs_t after = systemTime(SYSTEM_TIME_MONOTONIC);
@@ -226,12 +227,9 @@
nsecs_t ts1 = 0;
nsecs_t ts2 = 0;
nsecs_t ts3 = 0;
- EXPECT_CALL(*mLayer1FE, onPreComposition(_, _))
- .WillOnce(DoAll(SaveArg<0>(&ts1), Return(false)));
- EXPECT_CALL(*mLayer2FE, onPreComposition(_, _))
- .WillOnce(DoAll(SaveArg<0>(&ts2), Return(false)));
- EXPECT_CALL(*mLayer3FE, onPreComposition(_, _))
- .WillOnce(DoAll(SaveArg<0>(&ts3), Return(false)));
+ EXPECT_CALL(*mLayer1FE, onPreComposition(_)).WillOnce(DoAll(SaveArg<0>(&ts1), Return(false)));
+ EXPECT_CALL(*mLayer2FE, onPreComposition(_)).WillOnce(DoAll(SaveArg<0>(&ts2), Return(false)));
+ EXPECT_CALL(*mLayer3FE, onPreComposition(_)).WillOnce(DoAll(SaveArg<0>(&ts3), Return(false)));
mRefreshArgs.outputs = {mOutput1};
mRefreshArgs.layers = {mLayer1FE, mLayer2FE, mLayer3FE};
@@ -245,9 +243,9 @@
}
TEST_F(CompositionTestPreComposition, preCompositionDefaultsToNoUpdateNeeded) {
- EXPECT_CALL(*mLayer1FE, onPreComposition(_, _)).WillOnce(Return(false));
- EXPECT_CALL(*mLayer2FE, onPreComposition(_, _)).WillOnce(Return(false));
- EXPECT_CALL(*mLayer3FE, onPreComposition(_, _)).WillOnce(Return(false));
+ EXPECT_CALL(*mLayer1FE, onPreComposition(_)).WillOnce(Return(false));
+ EXPECT_CALL(*mLayer2FE, onPreComposition(_)).WillOnce(Return(false));
+ EXPECT_CALL(*mLayer3FE, onPreComposition(_)).WillOnce(Return(false));
mEngine.setNeedsAnotherUpdateForTest(true);
@@ -262,9 +260,9 @@
TEST_F(CompositionTestPreComposition,
preCompositionSetsNeedsAnotherUpdateIfAtLeastOneLayerRequestsIt) {
- EXPECT_CALL(*mLayer1FE, onPreComposition(_, _)).WillOnce(Return(true));
- EXPECT_CALL(*mLayer2FE, onPreComposition(_, _)).WillOnce(Return(false));
- EXPECT_CALL(*mLayer3FE, onPreComposition(_, _)).WillOnce(Return(false));
+ EXPECT_CALL(*mLayer1FE, onPreComposition(_)).WillOnce(Return(true));
+ EXPECT_CALL(*mLayer2FE, onPreComposition(_)).WillOnce(Return(false));
+ EXPECT_CALL(*mLayer3FE, onPreComposition(_)).WillOnce(Return(false));
mRefreshArgs.outputs = {mOutput1};
mRefreshArgs.layers = {mLayer1FE, mLayer2FE, mLayer3FE};
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
index 8dfbeb8..faa5197 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
@@ -48,6 +48,7 @@
using aidl::android::hardware::power::Boost;
using aidl::android::hardware::power::Mode;
using aidl::android::hardware::power::SessionHint;
+using aidl::android::hardware::power::SessionTag;
using aidl::android::hardware::power::WorkDuration;
PowerAdvisor::~PowerAdvisor() = default;
@@ -204,13 +205,36 @@
return *mSupportsHintSession;
}
+bool PowerAdvisor::shouldCreateSessionWithConfig() {
+ return mSessionConfigSupported && FlagManager::getInstance().adpf_use_fmq_channel();
+}
+
bool PowerAdvisor::ensurePowerHintSessionRunning() {
if (mHintSession == nullptr && !mHintSessionThreadIds.empty() && usePowerHintSession()) {
- auto ret = getPowerHal().createHintSession(getpid(), static_cast<int32_t>(getuid()),
- mHintSessionThreadIds, mTargetDuration.ns());
-
- if (ret.isOk()) {
- mHintSession = ret.value();
+ if (shouldCreateSessionWithConfig()) {
+ auto ret = getPowerHal().createHintSessionWithConfig(getpid(),
+ static_cast<int32_t>(getuid()),
+ mHintSessionThreadIds,
+ mTargetDuration.ns(),
+ SessionTag::SURFACEFLINGER,
+ &mSessionConfig);
+ if (ret.isOk()) {
+ mHintSession = ret.value();
+ }
+ // If it fails the first time we try, or ever returns unsupported, assume unsupported
+ else if (mFirstConfigSupportCheck || ret.isUnsupported()) {
+ ALOGI("Hint session with config is unsupported, falling back to a legacy session");
+ mSessionConfigSupported = false;
+ }
+ mFirstConfigSupportCheck = false;
+ }
+ // Immediately try original method after, in case the first way returned unsupported
+ if (mHintSession == nullptr && !shouldCreateSessionWithConfig()) {
+ auto ret = getPowerHal().createHintSession(getpid(), static_cast<int32_t>(getuid()),
+ mHintSessionThreadIds, mTargetDuration.ns());
+ if (ret.isOk()) {
+ mHintSession = ret.value();
+ }
}
}
return mHintSession != nullptr;
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
index 1040048..13e1263 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
@@ -230,6 +230,9 @@
// this normalizes them together and takes the max of the two
Duration combineTimingEstimates(Duration totalDuration, Duration flingerDuration);
+ // Whether to use the new "createHintSessionWithConfig" method
+ bool shouldCreateSessionWithConfig() REQUIRES(mHintSessionMutex);
+
bool ensurePowerHintSessionRunning() REQUIRES(mHintSessionMutex);
std::unordered_map<DisplayId, DisplayTimingData> mDisplayTimingData;
@@ -278,6 +281,13 @@
std::promise<bool> mDelayReportActualMutexAcquisitonPromise;
bool mTimingTestingMode = false;
+ // Hint session configuration data
+ aidl::android::hardware::power::SessionConfig mSessionConfig;
+
+ // Whether createHintSessionWithConfig is supported, assume true until it fails
+ bool mSessionConfigSupported = true;
+ bool mFirstConfigSupportCheck = true;
+
// Whether we should emit ATRACE_INT data for hint sessions
static const bool sTraceHintSessionData;
diff --git a/services/surfaceflinger/FrameTracker.cpp b/services/surfaceflinger/FrameTracker.cpp
index 178c531..ca8cdc3 100644
--- a/services/surfaceflinger/FrameTracker.cpp
+++ b/services/surfaceflinger/FrameTracker.cpp
@@ -18,9 +18,6 @@
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
-// This is needed for stdint.h to define INT64_MAX in C++
-#define __STDC_LIMIT_MACROS
-
#include <inttypes.h>
#include <android-base/stringprintf.h>
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index cb0e2a1..867f3af 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -163,7 +163,9 @@
LLOGV(layerId, "requested=%" PRIu64 "flags=%" PRIu64, clientState.what, clientChanges);
if (clientState.what & layer_state_t::eFlagsChanged) {
- if ((oldFlags ^ flags) & (layer_state_t::eLayerHidden | layer_state_t::eLayerOpaque)) {
+ if ((oldFlags ^ flags) &
+ (layer_state_t::eLayerHidden | layer_state_t::eLayerOpaque |
+ layer_state_t::eLayerSecure)) {
changes |= RequestedLayerState::Changes::Visibility |
RequestedLayerState::Changes::VisibleRegion;
}
diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp
index 2dbcb84..43a4397 100644
--- a/services/surfaceflinger/LayerFE.cpp
+++ b/services/surfaceflinger/LayerFE.cpp
@@ -82,8 +82,7 @@
return mSnapshot.get();
}
-bool LayerFE::onPreComposition(nsecs_t refreshStartTime, bool) {
- mCompositionResult.refreshStartTime = refreshStartTime;
+bool LayerFE::onPreComposition(bool) {
return mSnapshot->hasReadyFrame;
}
diff --git a/services/surfaceflinger/LayerFE.h b/services/surfaceflinger/LayerFE.h
index d584fb7..66cb88b 100644
--- a/services/surfaceflinger/LayerFE.h
+++ b/services/surfaceflinger/LayerFE.h
@@ -26,9 +26,6 @@
namespace android {
struct CompositionResult {
- // TODO(b/238781169) update CE to no longer pass refreshStartTime to LayerFE::onPreComposition
- // and remove this field.
- nsecs_t refreshStartTime = 0;
std::vector<std::pair<ftl::SharedFuture<FenceResult>, ui::LayerStack>> releaseFences;
sp<Fence> lastClientCompositionFence = nullptr;
};
@@ -39,7 +36,7 @@
// compositionengine::LayerFE overrides
const compositionengine::LayerFECompositionState* getCompositionState() const override;
- bool onPreComposition(nsecs_t refreshStartTime, bool updatingOutputGeometryThisFrame) override;
+ bool onPreComposition(bool updatingOutputGeometryThisFrame) override;
void onLayerDisplayed(ftl::SharedFuture<FenceResult>, ui::LayerStack) override;
const char* getDebugName() const override;
int32_t getSequence() const override;
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index ffd3463..56c29e2 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -506,6 +506,8 @@
}
int noVoteLayers = 0;
+ // Layers that prefer the same mode ("no-op").
+ int noPreferenceLayers = 0;
int minVoteLayers = 0;
int maxVoteLayers = 0;
int explicitDefaultVoteLayers = 0;
@@ -549,10 +551,7 @@
explicitCategoryVoteLayers++;
}
if (layer.frameRateCategory == FrameRateCategory::NoPreference) {
- // Count this layer for Min vote as well. The explicit vote avoids
- // touch boost and idle for choosing a category, while Min vote is for correct
- // behavior when all layers are Min or no vote.
- minVoteLayers++;
+ noPreferenceLayers++;
}
break;
case LayerVoteType::Heuristic:
@@ -612,6 +611,16 @@
return {ranking, kNoSignals};
}
+ // If all layers are category NoPreference, use the current config.
+ if (noPreferenceLayers + noVoteLayers == layers.size()) {
+ ALOGV("All layers NoPreference");
+ const auto ascendingWithPreferred =
+ rankFrameRates(anchorGroup, RefreshRateOrder::Ascending, activeMode.getId());
+ ATRACE_FORMAT_INSTANT("%s (All layers NoPreference)",
+ to_string(ascendingWithPreferred.front().frameRateMode.fps).c_str());
+ return {ascendingWithPreferred, kNoSignals};
+ }
+
const bool smoothSwitchOnly = categorySmoothSwitchOnlyLayers > 0;
const DisplayModeId activeModeId = activeMode.getId();
@@ -643,6 +652,7 @@
ftl::enum_string(layer.frameRateCategory).c_str());
if (layer.isNoVote() || layer.frameRateCategory == FrameRateCategory::NoPreference ||
layer.vote == LayerVoteType::Min) {
+ ALOGV("%s scoring skipped due to vote", formatLayerInfo(layer, layer.weight).c_str());
continue;
}
@@ -834,13 +844,16 @@
const bool touchBoostForExplicitExact = [&] {
if (supportsAppFrameRateOverrideByContent()) {
// Enable touch boost if there are other layers besides exact
- return explicitExact + noVoteLayers != layers.size();
+ return explicitExact + noVoteLayers + explicitGteLayers != layers.size();
} else {
// Enable touch boost if there are no exact layers
return explicitExact == 0;
}
}();
+ const bool touchBoostForCategory =
+ explicitCategoryVoteLayers + noVoteLayers + explicitGteLayers != layers.size();
+
const auto touchRefreshRates = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
using fps_approx_ops::operator<;
@@ -851,6 +864,7 @@
const bool hasInteraction = signals.touch || interactiveLayers > 0;
if (hasInteraction && explicitDefaultVoteLayers == 0 && touchBoostForExplicitExact &&
+ touchBoostForCategory &&
scores.front().frameRateMode.fps < touchRefreshRates.front().frameRateMode.fps) {
ALOGV("Touch Boost");
ATRACE_FORMAT_INSTANT("%s (Touch Boost [late])",
@@ -1554,19 +1568,17 @@
case FrameRateCategory::High:
return FpsRange{90_Hz, 120_Hz};
case FrameRateCategory::Normal:
- return FpsRange{60_Hz, 90_Hz};
+ return FpsRange{60_Hz, 120_Hz};
case FrameRateCategory::Low:
- return FpsRange{30_Hz, 30_Hz};
+ return FpsRange{30_Hz, 120_Hz};
case FrameRateCategory::HighHint:
case FrameRateCategory::NoPreference:
case FrameRateCategory::Default:
LOG_ALWAYS_FATAL("Should not get fps range for frame rate category: %s",
ftl::enum_string(category).c_str());
- return FpsRange{0_Hz, 0_Hz};
default:
LOG_ALWAYS_FATAL("Invalid frame rate category for range: %s",
ftl::enum_string(category).c_str());
- return FpsRange{0_Hz, 0_Hz};
}
}
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 3f91682..d92edb8 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -565,7 +565,7 @@
}));
}
-void Scheduler::setRenderRate(PhysicalDisplayId id, Fps renderFrameRate) {
+void Scheduler::setRenderRate(PhysicalDisplayId id, Fps renderFrameRate, bool applyImmediately) {
std::scoped_lock lock(mDisplayLock);
ftl::FakeGuard guard(kMainThreadContext);
@@ -586,7 +586,7 @@
ALOGV("%s %s (%s)", __func__, to_string(mode.fps).c_str(),
to_string(mode.modePtr->getVsyncRate()).c_str());
- display.schedulePtr->getTracker().setRenderRate(renderFrameRate);
+ display.schedulePtr->getTracker().setRenderRate(renderFrameRate, applyImmediately);
}
Fps Scheduler::getNextFrameInterval(PhysicalDisplayId id,
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 09f75fd..494a91b 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -188,7 +188,7 @@
const VsyncConfiguration& getVsyncConfiguration() const { return *mVsyncConfiguration; }
// Sets the render rate for the scheduler to run at.
- void setRenderRate(PhysicalDisplayId, Fps);
+ void setRenderRate(PhysicalDisplayId, Fps, bool applyImmediately);
void enableHardwareVsync(PhysicalDisplayId) REQUIRES(kMainThreadContext);
void disableHardwareVsync(PhysicalDisplayId, bool disallow) REQUIRES(kMainThreadContext);
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
index 84ccf8e..6d6b70d 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
@@ -20,7 +20,7 @@
#include <android-base/stringprintf.h>
#include <ftl/concat.h>
-#include <utils/Trace.h>
+#include <gui/TraceUtils.h>
#include <log/log_main.h>
#include <scheduler/TimeKeeper.h>
@@ -44,6 +44,17 @@
TimePoint::fromNs(nextVsyncTime)};
}
+void traceEntry(const VSyncDispatchTimerQueueEntry& entry, nsecs_t now) {
+ if (!ATRACE_ENABLED() || !entry.wakeupTime().has_value() || !entry.targetVsync().has_value()) {
+ return;
+ }
+
+ ftl::Concat trace(ftl::truncated<5>(entry.name()), " alarm in ",
+ ns2us(*entry.wakeupTime() - now), "us; VSYNC in ",
+ ns2us(*entry.targetVsync() - now), "us");
+ ATRACE_FORMAT_INSTANT(trace.c_str());
+}
+
} // namespace
VSyncDispatch::~VSyncDispatch() = default;
@@ -87,6 +98,7 @@
ScheduleResult VSyncDispatchTimerQueueEntry::schedule(VSyncDispatch::ScheduleTiming timing,
VSyncTracker& tracker, nsecs_t now) {
+ ATRACE_NAME("VSyncDispatchTimerQueueEntry::schedule");
auto nextVsyncTime =
tracker.nextAnticipatedVSyncTimeFrom(std::max(timing.lastVsync,
now + timing.workDuration +
@@ -98,6 +110,8 @@
mArmedInfo && (nextVsyncTime > (mArmedInfo->mActualVsyncTime + mMinVsyncDistance));
bool const wouldSkipAWakeup =
mArmedInfo && ((nextWakeupTime > (mArmedInfo->mActualWakeupTime + mMinVsyncDistance)));
+ ATRACE_FORMAT_INSTANT("%s: wouldSkipAVsyncTarget=%d wouldSkipAWakeup=%d", mName.c_str(),
+ wouldSkipAVsyncTarget, wouldSkipAWakeup);
if (FlagManager::getInstance().dont_skip_on_early_ro()) {
if (wouldSkipAVsyncTarget || wouldSkipAWakeup) {
nextVsyncTime = mArmedInfo->mActualVsyncTime;
@@ -122,7 +136,7 @@
ScheduleResult VSyncDispatchTimerQueueEntry::addPendingWorkloadUpdate(
VSyncTracker& tracker, nsecs_t now, VSyncDispatch::ScheduleTiming timing) {
mWorkloadUpdateInfo = timing;
- const auto armedInfo = update(tracker, now, timing, mArmedInfo);
+ const auto armedInfo = getArmedInfo(tracker, now, timing, mArmedInfo);
return {TimePoint::fromNs(armedInfo.mActualWakeupTime),
TimePoint::fromNs(armedInfo.mActualVsyncTime)};
}
@@ -140,11 +154,13 @@
bool const nextVsyncTooClose = mLastDispatchTime &&
(nextVsyncTime - *mLastDispatchTime + mMinVsyncDistance) <= currentPeriod;
if (alreadyDispatchedForVsync) {
+ ATRACE_FORMAT_INSTANT("alreadyDispatchedForVsync");
return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + mMinVsyncDistance,
*mLastDispatchTime);
}
if (nextVsyncTooClose) {
+ ATRACE_FORMAT_INSTANT("nextVsyncTooClose");
return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + currentPeriod,
*mLastDispatchTime + currentPeriod);
}
@@ -152,9 +168,11 @@
return nextVsyncTime;
}
-auto VSyncDispatchTimerQueueEntry::update(VSyncTracker& tracker, nsecs_t now,
- VSyncDispatch::ScheduleTiming timing,
- std::optional<ArmingInfo> armedInfo) const -> ArmingInfo {
+auto VSyncDispatchTimerQueueEntry::getArmedInfo(VSyncTracker& tracker, nsecs_t now,
+ VSyncDispatch::ScheduleTiming timing,
+ std::optional<ArmingInfo> armedInfo) const
+ -> ArmingInfo {
+ ATRACE_NAME("VSyncDispatchTimerQueueEntry::getArmedInfo");
const auto earliestReadyBy = now + timing.workDuration + timing.readyDuration;
const auto earliestVsync = std::max(earliestReadyBy, timing.lastVsync);
@@ -165,29 +183,39 @@
const auto nextReadyTime = nextVsyncTime - timing.readyDuration;
const auto nextWakeupTime = nextReadyTime - timing.workDuration;
- bool const wouldSkipAVsyncTarget =
- armedInfo && (nextVsyncTime > (armedInfo->mActualVsyncTime + mMinVsyncDistance));
- bool const wouldSkipAWakeup =
- armedInfo && (nextWakeupTime > (armedInfo->mActualWakeupTime + mMinVsyncDistance));
- if (FlagManager::getInstance().dont_skip_on_early_ro() &&
- (wouldSkipAVsyncTarget || wouldSkipAWakeup)) {
- return *armedInfo;
+ if (FlagManager::getInstance().dont_skip_on_early_ro()) {
+ bool const wouldSkipAVsyncTarget =
+ armedInfo && (nextVsyncTime > (armedInfo->mActualVsyncTime + mMinVsyncDistance));
+ bool const wouldSkipAWakeup =
+ armedInfo && (nextWakeupTime > (armedInfo->mActualWakeupTime + mMinVsyncDistance));
+ ATRACE_FORMAT_INSTANT("%s: wouldSkipAVsyncTarget=%d wouldSkipAWakeup=%d", mName.c_str(),
+ wouldSkipAVsyncTarget, wouldSkipAWakeup);
+ if (wouldSkipAVsyncTarget || wouldSkipAWakeup) {
+ return *armedInfo;
+ }
}
return ArmingInfo{nextWakeupTime, nextVsyncTime, nextReadyTime};
}
void VSyncDispatchTimerQueueEntry::update(VSyncTracker& tracker, nsecs_t now) {
+ ATRACE_NAME("VSyncDispatchTimerQueueEntry::update");
if (!mArmedInfo && !mWorkloadUpdateInfo) {
return;
}
if (mWorkloadUpdateInfo) {
+ const auto workDelta = mWorkloadUpdateInfo->workDuration - mScheduleTiming.workDuration;
+ const auto readyDelta = mWorkloadUpdateInfo->readyDuration - mScheduleTiming.readyDuration;
+ const auto lastVsyncDelta = mWorkloadUpdateInfo->lastVsync - mScheduleTiming.lastVsync;
+ ATRACE_FORMAT_INSTANT("Workload updated workDelta=%" PRId64 " readyDelta=%" PRId64
+ " lastVsyncDelta=%" PRId64,
+ workDelta, readyDelta, lastVsyncDelta);
mScheduleTiming = *mWorkloadUpdateInfo;
mWorkloadUpdateInfo.reset();
}
- mArmedInfo = update(tracker, now, mScheduleTiming, mArmedInfo);
+ mArmedInfo = getArmedInfo(tracker, now, mScheduleTiming, mArmedInfo);
}
void VSyncDispatchTimerQueueEntry::disarm() {
@@ -282,6 +310,7 @@
void VSyncDispatchTimerQueue::rearmTimerSkippingUpdateFor(
nsecs_t now, CallbackMap::const_iterator skipUpdateIt) {
+ ATRACE_CALL();
std::optional<nsecs_t> min;
std::optional<nsecs_t> targetVsync;
std::optional<std::string_view> nextWakeupName;
@@ -294,7 +323,10 @@
if (it != skipUpdateIt) {
callback->update(*mTracker, now);
}
- auto const wakeupTime = *callback->wakeupTime();
+
+ traceEntry(*callback, now);
+
+ const auto wakeupTime = *callback->wakeupTime();
if (!min || *min > wakeupTime) {
nextWakeupName = callback->name();
min = wakeupTime;
@@ -303,11 +335,6 @@
}
if (min && min < mIntendedWakeupTime) {
- if (ATRACE_ENABLED() && nextWakeupName && targetVsync) {
- ftl::Concat trace(ftl::truncated<5>(*nextWakeupName), " alarm in ", ns2us(*min - now),
- "us; VSYNC in ", ns2us(*targetVsync - now), "us");
- ATRACE_NAME(trace.c_str());
- }
setTimer(*min, now);
} else {
ATRACE_NAME("cancel timer");
@@ -316,6 +343,7 @@
}
void VSyncDispatchTimerQueue::timerCallback() {
+ ATRACE_CALL();
struct Invocation {
std::shared_ptr<VSyncDispatchTimerQueueEntry> callback;
nsecs_t vsyncTimestamp;
@@ -338,8 +366,9 @@
continue;
}
- auto const readyTime = callback->readyTime();
+ traceEntry(*callback, now);
+ auto const readyTime = callback->readyTime();
auto const lagAllowance = std::max(now - mIntendedWakeupTime, static_cast<nsecs_t>(0));
if (*wakeupTime < mIntendedWakeupTime + mTimerSlack + lagAllowance) {
callback->executing();
@@ -353,6 +382,8 @@
}
for (auto const& invocation : invocations) {
+ ftl::Concat trace(ftl::truncated<5>(invocation.callback->name()));
+ ATRACE_FORMAT("%s: %s", __func__, trace.c_str());
invocation.callback->callback(invocation.vsyncTimestamp, invocation.wakeupTimestamp,
invocation.deadlineTimestamp);
}
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
index 252c09c..e4ddc03 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
@@ -91,8 +91,8 @@
};
nsecs_t adjustVsyncIfNeeded(VSyncTracker& tracker, nsecs_t nextVsyncTime) const;
- ArmingInfo update(VSyncTracker&, nsecs_t now, VSyncDispatch::ScheduleTiming,
- std::optional<ArmingInfo>) const;
+ ArmingInfo getArmedInfo(VSyncTracker&, nsecs_t now, VSyncDispatch::ScheduleTiming,
+ std::optional<ArmingInfo>) const;
const std::string mName;
const VSyncDispatch::Callback mCallback;
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index 8697696..acb3760 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -45,16 +45,28 @@
static auto constexpr kMaxPercent = 100u;
+namespace {
+int numVsyncsPerFrame(const ftl::NonNull<DisplayModePtr>& displayModePtr) {
+ const auto idealPeakRefreshPeriod = displayModePtr->getPeakFps().getPeriodNsecs();
+ const auto idealRefreshPeriod = displayModePtr->getVsyncRate().getPeriodNsecs();
+ return static_cast<int>(std::round(static_cast<float>(idealPeakRefreshPeriod) /
+ static_cast<float>(idealRefreshPeriod)));
+}
+} // namespace
+
VSyncPredictor::~VSyncPredictor() = default;
-VSyncPredictor::VSyncPredictor(ftl::NonNull<DisplayModePtr> modePtr, size_t historySize,
- size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent)
- : mId(modePtr->getPhysicalDisplayId()),
+VSyncPredictor::VSyncPredictor(std::unique_ptr<Clock> clock, ftl::NonNull<DisplayModePtr> modePtr,
+ size_t historySize, size_t minimumSamplesForPrediction,
+ uint32_t outlierTolerancePercent)
+ : mClock(std::move(clock)),
+ mId(modePtr->getPhysicalDisplayId()),
mTraceOn(property_get_bool("debug.sf.vsp_trace", false)),
kHistorySize(historySize),
kMinimumSamplesForPrediction(minimumSamplesForPrediction),
kOutlierTolerancePercent(std::min(outlierTolerancePercent, kMaxPercent)),
- mDisplayModePtr(modePtr) {
+ mDisplayModePtr(modePtr),
+ mNumVsyncsForFrame(numVsyncsPerFrame(mDisplayModePtr)) {
resetModel();
}
@@ -118,11 +130,8 @@
}
Period VSyncPredictor::minFramePeriodLocked() const {
- const auto idealPeakRefreshPeriod = mDisplayModePtr->getPeakFps().getPeriodNsecs();
- const auto numPeriods = static_cast<int>(std::round(static_cast<float>(idealPeakRefreshPeriod) /
- static_cast<float>(idealPeriod())));
const auto slope = mRateMap.find(idealPeriod())->second.slope;
- return Period::fromNs(slope * numPeriods);
+ return Period::fromNs(slope * mNumVsyncsForFrame);
}
bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) {
@@ -147,7 +156,7 @@
mKnownTimestamp = timestamp;
}
ATRACE_FORMAT_INSTANT("timestamp rejected. mKnownTimestamp was %.2fms ago",
- (systemTime() - *mKnownTimestamp) / 1e6f);
+ (mClock->now() - *mKnownTimestamp) / 1e6f);
return false;
}
@@ -250,17 +259,6 @@
return true;
}
-auto VSyncPredictor::getVsyncSequenceLocked(nsecs_t timestamp) const -> VsyncSequence {
- const auto vsync = snapToVsync(timestamp);
- if (!mLastVsyncSequence) return {vsync, 0};
-
- const auto [slope, _] = getVSyncPredictionModelLocked();
- const auto [lastVsyncTime, lastVsyncSequence] = *mLastVsyncSequence;
- const auto vsyncSequence = lastVsyncSequence +
- static_cast<int64_t>(std::round((vsync - lastVsyncTime) / static_cast<float>(slope)));
- return {vsync, vsyncSequence};
-}
-
nsecs_t VSyncPredictor::snapToVsync(nsecs_t timePoint) const {
auto const [slope, intercept] = getVSyncPredictionModelLocked();
@@ -298,51 +296,45 @@
}
nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint,
- std::optional<nsecs_t> lastVsyncOpt) const {
+ std::optional<nsecs_t> lastVsyncOpt) {
ATRACE_CALL();
std::lock_guard lock(mMutex);
- const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope;
- const auto threshold = currentPeriod / 2;
- const auto minFramePeriod = minFramePeriodLocked().ns();
- const auto lastFrameMissed =
- lastVsyncOpt && std::abs(*lastVsyncOpt - mLastMissedVsync.ns()) < threshold;
- const nsecs_t baseTime =
- FlagManager::getInstance().vrr_config() && !lastFrameMissed && lastVsyncOpt
- ? std::max(timePoint, *lastVsyncOpt + minFramePeriod - threshold)
- : timePoint;
- return snapToVsyncAlignedWithRenderRate(baseTime);
-}
-nsecs_t VSyncPredictor::snapToVsyncAlignedWithRenderRate(nsecs_t timePoint) const {
- // update the mLastVsyncSequence for reference point
- mLastVsyncSequence = getVsyncSequenceLocked(timePoint);
+ const auto now = TimePoint::fromNs(mClock->now());
+ purgeTimelines(now);
- const auto renderRatePhase = [&]() REQUIRES(mMutex) -> int {
- if (!mRenderRateOpt) return 0;
- const auto divisor =
- RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(idealPeriod()),
- *mRenderRateOpt);
- if (divisor <= 1) return 0;
-
- int mod = mLastVsyncSequence->seq % divisor;
- if (mod == 0) return 0;
-
- // This is actually a bug fix, but guarded with vrr_config since we found it with this
- // config
- if (FlagManager::getInstance().vrr_config()) {
- if (mod < 0) mod += divisor;
- }
-
- return divisor - mod;
- }();
-
- if (renderRatePhase == 0) {
- return mLastVsyncSequence->vsyncTime;
+ if (lastVsyncOpt && *lastVsyncOpt > timePoint) {
+ timePoint = *lastVsyncOpt;
}
- auto const [slope, intercept] = getVSyncPredictionModelLocked();
- const auto approximateNextVsync = mLastVsyncSequence->vsyncTime + slope * renderRatePhase;
- return snapToVsync(approximateNextVsync - slope / 2);
+ const auto model = getVSyncPredictionModelLocked();
+ const auto threshold = model.slope / 2;
+ std::optional<Period> minFramePeriodOpt;
+
+ if (mNumVsyncsForFrame > 1) {
+ minFramePeriodOpt = minFramePeriodLocked();
+ }
+
+ std::optional<TimePoint> vsyncOpt;
+ for (auto& timeline : mTimelines) {
+ vsyncOpt = timeline.nextAnticipatedVSyncTimeFrom(model, minFramePeriodOpt,
+ snapToVsync(timePoint), mMissedVsync,
+ lastVsyncOpt ? snapToVsync(*lastVsyncOpt -
+ threshold)
+ : lastVsyncOpt);
+ if (vsyncOpt) {
+ break;
+ }
+ }
+ LOG_ALWAYS_FATAL_IF(!vsyncOpt);
+
+ if (*vsyncOpt > mLastCommittedVsync) {
+ mLastCommittedVsync = *vsyncOpt;
+ ATRACE_FORMAT_INSTANT("mLastCommittedVsync in %.2fms",
+ float(mLastCommittedVsync.ns() - mClock->now()) / 1e6f);
+ }
+
+ return vsyncOpt->ns();
}
/*
@@ -353,39 +345,61 @@
* isVSyncInPhase(33.3, 30) = false
* isVSyncInPhase(50.0, 30) = true
*/
-bool VSyncPredictor::isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const {
- std::lock_guard lock(mMutex);
- const auto divisor =
- RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(idealPeriod()),
- frameRate);
- return isVSyncInPhaseLocked(timePoint, static_cast<unsigned>(divisor));
-}
-
-bool VSyncPredictor::isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) const {
- const TimePoint now = TimePoint::now();
- const auto getTimePointIn = [](TimePoint now, nsecs_t timePoint) -> float {
- return ticks<std::milli, float>(TimePoint::fromNs(timePoint) - now);
- };
- ATRACE_FORMAT("%s timePoint in: %.2f divisor: %zu", __func__, getTimePointIn(now, timePoint),
- divisor);
-
- if (divisor <= 1 || timePoint == 0) {
+bool VSyncPredictor::isVSyncInPhase(nsecs_t timePoint, Fps frameRate) {
+ if (timePoint == 0) {
return true;
}
- const nsecs_t period = mRateMap[idealPeriod()].slope;
+ std::lock_guard lock(mMutex);
+ const auto model = getVSyncPredictionModelLocked();
+ const nsecs_t period = model.slope;
const nsecs_t justBeforeTimePoint = timePoint - period / 2;
- const auto vsyncSequence = getVsyncSequenceLocked(justBeforeTimePoint);
- ATRACE_FORMAT_INSTANT("vsync in: %.2f sequence: %" PRId64,
- getTimePointIn(now, vsyncSequence.vsyncTime), vsyncSequence.seq);
- return vsyncSequence.seq % divisor == 0;
+ const auto now = TimePoint::fromNs(mClock->now());
+ const auto vsync = snapToVsync(justBeforeTimePoint);
+
+ purgeTimelines(now);
+
+ for (auto& timeline : mTimelines) {
+ if (timeline.validUntil() && timeline.validUntil()->ns() > vsync) {
+ return timeline.isVSyncInPhase(model, vsync, frameRate);
+ }
+ }
+
+ // The last timeline should always be valid
+ return mTimelines.back().isVSyncInPhase(model, vsync, frameRate);
}
-void VSyncPredictor::setRenderRate(Fps renderRate) {
+void VSyncPredictor::setRenderRate(Fps renderRate, bool applyImmediately) {
ATRACE_FORMAT("%s %s", __func__, to_string(renderRate).c_str());
ALOGV("%s %s: RenderRate %s ", __func__, to_string(mId).c_str(), to_string(renderRate).c_str());
std::lock_guard lock(mMutex);
+ const auto prevRenderRate = mRenderRateOpt;
mRenderRateOpt = renderRate;
+ const auto renderPeriodDelta =
+ prevRenderRate ? prevRenderRate->getPeriodNsecs() - renderRate.getPeriodNsecs() : 0;
+ if (applyImmediately) {
+ ATRACE_FORMAT_INSTANT("applyImmediately");
+ while (mTimelines.size() > 1) {
+ mTimelines.pop_front();
+ }
+
+ mTimelines.front().setRenderRate(renderRate);
+ return;
+ }
+
+ const bool newRenderRateIsHigher = renderPeriodDelta > renderRate.getPeriodNsecs() &&
+ mLastCommittedVsync.ns() - mClock->now() > 2 * renderRate.getPeriodNsecs();
+ if (newRenderRateIsHigher) {
+ ATRACE_FORMAT_INSTANT("newRenderRateIsHigher");
+ mTimelines.clear();
+ mLastCommittedVsync = TimePoint::fromNs(0);
+
+ } else {
+ mTimelines.back().freeze(
+ TimePoint::fromNs(mLastCommittedVsync.ns() + mIdealPeriod.ns() / 2));
+ }
+ mTimelines.emplace_back(mLastCommittedVsync, mIdealPeriod, renderRate);
+ purgeTimelines(TimePoint::fromNs(mClock->now()));
}
void VSyncPredictor::setDisplayModePtr(ftl::NonNull<DisplayModePtr> modePtr) {
@@ -401,6 +415,7 @@
std::lock_guard lock(mMutex);
mDisplayModePtr = modePtr;
+ mNumVsyncsForFrame = numVsyncsPerFrame(mDisplayModePtr);
traceInt64("VSP-setPeriod", modePtr->getVsyncRate().getPeriodNsecs());
static constexpr size_t kSizeLimit = 30;
@@ -415,8 +430,14 @@
clearTimestamps();
}
-void VSyncPredictor::ensureMinFrameDurationIsKept(TimePoint expectedPresentTime,
- TimePoint lastConfirmedPresentTime) {
+Duration VSyncPredictor::ensureMinFrameDurationIsKept(TimePoint expectedPresentTime,
+ TimePoint lastConfirmedPresentTime) {
+ ATRACE_CALL();
+
+ if (mNumVsyncsForFrame <= 1) {
+ return 0ns;
+ }
+
const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope;
const auto threshold = currentPeriod / 2;
const auto minFramePeriod = minFramePeriodLocked().ns();
@@ -442,17 +463,20 @@
if (!mPastExpectedPresentTimes.empty()) {
const auto phase = Duration(mPastExpectedPresentTimes.back() - expectedPresentTime);
if (phase > 0ns) {
- if (mLastVsyncSequence) {
- mLastVsyncSequence->vsyncTime += phase.ns();
+ for (auto& timeline : mTimelines) {
+ timeline.shiftVsyncSequence(phase);
}
mPastExpectedPresentTimes.clear();
+ return phase;
}
}
+
+ return 0ns;
}
void VSyncPredictor::onFrameBegin(TimePoint expectedPresentTime,
TimePoint lastConfirmedPresentTime) {
- ATRACE_CALL();
+ ATRACE_NAME("VSyncPredictor::onFrameBegin");
std::lock_guard lock(mMutex);
if (!mDisplayModePtr->getVrrConfig()) return;
@@ -482,11 +506,14 @@
}
}
- ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime);
+ const auto phase = ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime);
+ if (phase > 0ns) {
+ mMissedVsync = {expectedPresentTime, minFramePeriodLocked()};
+ }
}
void VSyncPredictor::onFrameMissed(TimePoint expectedPresentTime) {
- ATRACE_CALL();
+ ATRACE_NAME("VSyncPredictor::onFrameMissed");
std::lock_guard lock(mMutex);
if (!mDisplayModePtr->getVrrConfig()) return;
@@ -496,14 +523,15 @@
const auto lastConfirmedPresentTime =
TimePoint::fromNs(expectedPresentTime.ns() + currentPeriod);
- ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime);
- mLastMissedVsync = expectedPresentTime;
+ const auto phase = ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime);
+ if (phase > 0ns) {
+ mMissedVsync = {expectedPresentTime, Duration::fromNs(0)};
+ }
}
VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModel() const {
std::lock_guard lock(mMutex);
- const auto model = VSyncPredictor::getVSyncPredictionModelLocked();
- return {model.slope, model.intercept};
+ return VSyncPredictor::getVSyncPredictionModelLocked();
}
VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModelLocked() const {
@@ -524,6 +552,11 @@
mTimestamps.clear();
mLastTimestampIndex = 0;
}
+
+ mTimelines.clear();
+ mLastCommittedVsync = TimePoint::fromNs(0);
+ mIdealPeriod = Period::fromNs(idealPeriod());
+ mTimelines.emplace_back(mLastCommittedVsync, mIdealPeriod, mRenderRateOpt);
}
bool VSyncPredictor::needsMoreSamples() const {
@@ -547,6 +580,171 @@
period / 1e6f, periodInterceptTuple.slope / 1e6f,
periodInterceptTuple.intercept);
}
+ StringAppendF(&result, "\tmTimelines.size()=%zu\n", mTimelines.size());
+}
+
+void VSyncPredictor::purgeTimelines(android::TimePoint now) {
+ const auto kEnoughFramesToBreakPhase = 5;
+ if (mRenderRateOpt &&
+ mLastCommittedVsync.ns() + mRenderRateOpt->getPeriodNsecs() * kEnoughFramesToBreakPhase <
+ mClock->now()) {
+ mTimelines.clear();
+ mLastCommittedVsync = TimePoint::fromNs(0);
+ mTimelines.emplace_back(mLastCommittedVsync, mIdealPeriod, mRenderRateOpt);
+ return;
+ }
+
+ while (mTimelines.size() > 1) {
+ const auto validUntilOpt = mTimelines.front().validUntil();
+ if (validUntilOpt && *validUntilOpt < now) {
+ mTimelines.pop_front();
+ } else {
+ break;
+ }
+ }
+ LOG_ALWAYS_FATAL_IF(mTimelines.empty());
+ LOG_ALWAYS_FATAL_IF(mTimelines.back().validUntil().has_value());
+}
+
+auto VSyncPredictor::VsyncTimeline::makeVsyncSequence(TimePoint knownVsync)
+ -> std::optional<VsyncSequence> {
+ if (knownVsync.ns() == 0) return std::nullopt;
+ return std::make_optional<VsyncSequence>({knownVsync.ns(), 0});
+}
+
+VSyncPredictor::VsyncTimeline::VsyncTimeline(TimePoint knownVsync, Period idealPeriod,
+ std::optional<Fps> renderRateOpt)
+ : mIdealPeriod(idealPeriod),
+ mRenderRateOpt(renderRateOpt),
+ mLastVsyncSequence(makeVsyncSequence(knownVsync)) {}
+
+void VSyncPredictor::VsyncTimeline::freeze(TimePoint lastVsync) {
+ LOG_ALWAYS_FATAL_IF(mValidUntil.has_value());
+ ATRACE_FORMAT_INSTANT("renderRate %s valid for %.2f",
+ mRenderRateOpt ? to_string(*mRenderRateOpt).c_str() : "NA",
+ float(lastVsync.ns() - TimePoint::now().ns()) / 1e6f);
+ mValidUntil = lastVsync;
+}
+
+std::optional<TimePoint> VSyncPredictor::VsyncTimeline::nextAnticipatedVSyncTimeFrom(
+ Model model, std::optional<Period> minFramePeriodOpt, nsecs_t vsync,
+ MissedVsync missedVsync, std::optional<nsecs_t> lastVsyncOpt) {
+ ATRACE_FORMAT("renderRate %s", mRenderRateOpt ? to_string(*mRenderRateOpt).c_str() : "NA");
+
+ nsecs_t vsyncTime = snapToVsyncAlignedWithRenderRate(model, vsync);
+ const auto threshold = model.slope / 2;
+ const auto lastFrameMissed =
+ lastVsyncOpt && std::abs(*lastVsyncOpt - missedVsync.vsync.ns()) < threshold;
+ nsecs_t vsyncFixupTime = 0;
+ if (FlagManager::getInstance().vrr_config() && lastFrameMissed) {
+ // If the last frame missed is the last vsync, we already shifted the timeline. Depends on
+ // whether we skipped the frame (onFrameMissed) or not (onFrameBegin) we apply a different
+ // fixup. There is no need to to shift the vsync timeline again.
+ vsyncTime += missedVsync.fixup.ns();
+ ATRACE_FORMAT_INSTANT("lastFrameMissed");
+ } else if (minFramePeriodOpt) {
+ if (FlagManager::getInstance().vrr_config() && lastVsyncOpt) {
+ // lastVsyncOpt is based on the old timeline before we shifted it. we should correct it
+ // first before trying to use it.
+ if (mLastVsyncSequence->seq > 0) {
+ lastVsyncOpt = snapToVsyncAlignedWithRenderRate(model, *lastVsyncOpt);
+ }
+ const auto vsyncDiff = vsyncTime - *lastVsyncOpt;
+ if (vsyncDiff <= minFramePeriodOpt->ns() - threshold) {
+ vsyncFixupTime = *lastVsyncOpt + minFramePeriodOpt->ns() - vsyncTime;
+ ATRACE_FORMAT_INSTANT("minFramePeriod violation. next in %.2f which is %.2f "
+ "from "
+ "prev. "
+ "adjust by %.2f",
+ static_cast<float>(vsyncTime - TimePoint::now().ns()) / 1e6f,
+ static_cast<float>(vsyncTime - *lastVsyncOpt) / 1e6f,
+ static_cast<float>(vsyncFixupTime) / 1e6f);
+ }
+ }
+ vsyncTime += vsyncFixupTime;
+ }
+
+ ATRACE_FORMAT_INSTANT("vsync in %.2fms", float(vsyncTime - TimePoint::now().ns()) / 1e6f);
+ if (mValidUntil && vsyncTime > mValidUntil->ns()) {
+ ATRACE_FORMAT_INSTANT("no longer valid for vsync in %.2f",
+ static_cast<float>(vsyncTime - TimePoint::now().ns()) / 1e6f);
+ return std::nullopt;
+ }
+
+ // If we needed a fixup, it means that we changed the render rate and the chosen vsync would
+ // cross minFramePeriod. In that case we need to shift the entire vsync timeline.
+ if (vsyncFixupTime > 0) {
+ shiftVsyncSequence(Duration::fromNs(vsyncFixupTime));
+ }
+
+ return TimePoint::fromNs(vsyncTime);
+}
+
+auto VSyncPredictor::VsyncTimeline::getVsyncSequenceLocked(Model model, nsecs_t vsync)
+ -> VsyncSequence {
+ if (!mLastVsyncSequence) return {vsync, 0};
+
+ const auto [lastVsyncTime, lastVsyncSequence] = *mLastVsyncSequence;
+ const auto vsyncSequence = lastVsyncSequence +
+ static_cast<int64_t>(std::round((vsync - lastVsyncTime) /
+ static_cast<float>(model.slope)));
+ return {vsync, vsyncSequence};
+}
+
+nsecs_t VSyncPredictor::VsyncTimeline::snapToVsyncAlignedWithRenderRate(Model model,
+ nsecs_t vsync) {
+ // update the mLastVsyncSequence for reference point
+ mLastVsyncSequence = getVsyncSequenceLocked(model, vsync);
+
+ const auto renderRatePhase = [&]() -> int {
+ if (!mRenderRateOpt) return 0;
+ const auto divisor =
+ RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(mIdealPeriod.ns()),
+ *mRenderRateOpt);
+ if (divisor <= 1) return 0;
+
+ int mod = mLastVsyncSequence->seq % divisor;
+ if (mod == 0) return 0;
+
+ // This is actually a bug fix, but guarded with vrr_config since we found it with this
+ // config
+ if (FlagManager::getInstance().vrr_config()) {
+ if (mod < 0) mod += divisor;
+ }
+
+ return divisor - mod;
+ }();
+
+ if (renderRatePhase == 0) {
+ return mLastVsyncSequence->vsyncTime;
+ }
+
+ return mLastVsyncSequence->vsyncTime + model.slope * renderRatePhase;
+}
+
+bool VSyncPredictor::VsyncTimeline::isVSyncInPhase(Model model, nsecs_t vsync, Fps frameRate) {
+ const auto getVsyncIn = [](TimePoint now, nsecs_t timePoint) -> float {
+ return ticks<std::milli, float>(TimePoint::fromNs(timePoint) - now);
+ };
+
+ Fps displayFps = mRenderRateOpt ? *mRenderRateOpt : Fps::fromPeriodNsecs(mIdealPeriod.ns());
+ const auto divisor = RefreshRateSelector::getFrameRateDivisor(displayFps, frameRate);
+ const auto now = TimePoint::now();
+
+ if (divisor <= 1) {
+ return true;
+ }
+ const auto vsyncSequence = getVsyncSequenceLocked(model, vsync);
+ ATRACE_FORMAT_INSTANT("vsync in: %.2f sequence: %" PRId64 " divisor: %zu",
+ getVsyncIn(now, vsyncSequence.vsyncTime), vsyncSequence.seq, divisor);
+ return vsyncSequence.seq % divisor == 0;
+}
+
+void VSyncPredictor::VsyncTimeline::shiftVsyncSequence(Duration phase) {
+ if (mLastVsyncSequence) {
+ ATRACE_FORMAT_INSTANT("adjusting vsync by %.2f", static_cast<float>(phase.ns()) / 1e6f);
+ mLastVsyncSequence->vsyncTime += phase.ns();
+ }
}
} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h
index 8fd7e60..3ed1d41 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.h
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h
@@ -22,6 +22,7 @@
#include <vector>
#include <android-base/thread_annotations.h>
+#include <scheduler/TimeKeeper.h>
#include <ui/DisplayId.h>
#include "VSyncTracker.h"
@@ -31,6 +32,7 @@
class VSyncPredictor : public VSyncTracker {
public:
/*
+ * \param [in] Clock The clock abstraction. Useful for unit tests.
* \param [in] PhysicalDisplayid The display this corresponds to.
* \param [in] modePtr The initial display mode
* \param [in] historySize The internal amount of entries to store in the model.
@@ -38,13 +40,13 @@
* predicting. \param [in] outlierTolerancePercent a number 0 to 100 that will be used to filter
* samples that fall outlierTolerancePercent from an anticipated vsync event.
*/
- VSyncPredictor(ftl::NonNull<DisplayModePtr> modePtr, size_t historySize,
+ VSyncPredictor(std::unique_ptr<Clock>, ftl::NonNull<DisplayModePtr> modePtr, size_t historySize,
size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent);
~VSyncPredictor();
bool addVsyncTimestamp(nsecs_t timestamp) final EXCLUDES(mMutex);
nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint,
- std::optional<nsecs_t> lastVsyncOpt = {}) const final
+ std::optional<nsecs_t> lastVsyncOpt = {}) final
EXCLUDES(mMutex);
nsecs_t currentPeriod() const final EXCLUDES(mMutex);
Period minFramePeriod() const final EXCLUDES(mMutex);
@@ -62,11 +64,11 @@
VSyncPredictor::Model getVSyncPredictionModel() const EXCLUDES(mMutex);
- bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const final EXCLUDES(mMutex);
+ bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) final EXCLUDES(mMutex);
void setDisplayModePtr(ftl::NonNull<DisplayModePtr>) final EXCLUDES(mMutex);
- void setRenderRate(Fps) final EXCLUDES(mMutex);
+ void setRenderRate(Fps, bool applyImmediately) final EXCLUDES(mMutex);
void onFrameBegin(TimePoint expectedPresentTime, TimePoint lastConfirmedPresentTime) final
EXCLUDES(mMutex);
@@ -75,10 +77,44 @@
void dump(std::string& result) const final EXCLUDES(mMutex);
private:
+ struct VsyncSequence {
+ nsecs_t vsyncTime;
+ int64_t seq;
+ };
+
+ struct MissedVsync {
+ TimePoint vsync;
+ Duration fixup = Duration::fromNs(0);
+ };
+
+ class VsyncTimeline {
+ public:
+ VsyncTimeline(TimePoint knownVsync, Period idealPeriod, std::optional<Fps> renderRateOpt);
+ std::optional<TimePoint> nextAnticipatedVSyncTimeFrom(
+ Model model, std::optional<Period> minFramePeriodOpt, nsecs_t vsyncTime,
+ MissedVsync lastMissedVsync, std::optional<nsecs_t> lastVsyncOpt = {});
+ void freeze(TimePoint lastVsync);
+ std::optional<TimePoint> validUntil() const { return mValidUntil; }
+ bool isVSyncInPhase(Model, nsecs_t vsync, Fps frameRate);
+ void shiftVsyncSequence(Duration phase);
+ void setRenderRate(Fps renderRate) { mRenderRateOpt = renderRate; }
+
+ private:
+ nsecs_t snapToVsyncAlignedWithRenderRate(Model model, nsecs_t vsync);
+ VsyncSequence getVsyncSequenceLocked(Model, nsecs_t vsync);
+ std::optional<VsyncSequence> makeVsyncSequence(TimePoint knownVsync);
+
+ const Period mIdealPeriod = Duration::fromNs(0);
+ std::optional<Fps> mRenderRateOpt;
+ std::optional<TimePoint> mValidUntil;
+ std::optional<VsyncSequence> mLastVsyncSequence;
+ };
+
VSyncPredictor(VSyncPredictor const&) = delete;
VSyncPredictor& operator=(VSyncPredictor const&) = delete;
void clearTimestamps() REQUIRES(mMutex);
+ const std::unique_ptr<Clock> mClock;
const PhysicalDisplayId mId;
inline void traceInt64If(const char* name, int64_t value) const;
@@ -88,16 +124,10 @@
bool validate(nsecs_t timestamp) const REQUIRES(mMutex);
Model getVSyncPredictionModelLocked() const REQUIRES(mMutex);
nsecs_t snapToVsync(nsecs_t timePoint) const REQUIRES(mMutex);
- nsecs_t snapToVsyncAlignedWithRenderRate(nsecs_t timePoint) const REQUIRES(mMutex);
- bool isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) const REQUIRES(mMutex);
Period minFramePeriodLocked() const REQUIRES(mMutex);
- void ensureMinFrameDurationIsKept(TimePoint, TimePoint) REQUIRES(mMutex);
+ Duration ensureMinFrameDurationIsKept(TimePoint, TimePoint) REQUIRES(mMutex);
+ void purgeTimelines(android::TimePoint now) REQUIRES(mMutex);
- struct VsyncSequence {
- nsecs_t vsyncTime;
- int64_t seq;
- };
- VsyncSequence getVsyncSequenceLocked(nsecs_t timestamp) const REQUIRES(mMutex);
nsecs_t idealPeriod() const REQUIRES(mMutex);
bool const mTraceOn;
@@ -115,13 +145,16 @@
std::vector<nsecs_t> mTimestamps GUARDED_BY(mMutex);
ftl::NonNull<DisplayModePtr> mDisplayModePtr GUARDED_BY(mMutex);
- std::optional<Fps> mRenderRateOpt GUARDED_BY(mMutex);
-
- mutable std::optional<VsyncSequence> mLastVsyncSequence GUARDED_BY(mMutex);
+ int mNumVsyncsForFrame GUARDED_BY(mMutex);
std::deque<TimePoint> mPastExpectedPresentTimes GUARDED_BY(mMutex);
- TimePoint mLastMissedVsync GUARDED_BY(mMutex);
+ MissedVsync mMissedVsync GUARDED_BY(mMutex);
+
+ std::deque<VsyncTimeline> mTimelines GUARDED_BY(mMutex);
+ TimePoint mLastCommittedVsync GUARDED_BY(mMutex) = TimePoint::fromNs(0);
+ Period mIdealPeriod GUARDED_BY(mMutex) = Duration::fromNs(0);
+ std::optional<Fps> mRenderRateOpt GUARDED_BY(mMutex);
};
} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/VSyncTracker.h b/services/surfaceflinger/Scheduler/VSyncTracker.h
index 37bd4b4..8787cdb 100644
--- a/services/surfaceflinger/Scheduler/VSyncTracker.h
+++ b/services/surfaceflinger/Scheduler/VSyncTracker.h
@@ -56,8 +56,8 @@
* and avoid crossing the minimal frame period of a VRR display.
* \return A prediction of the timestamp of a vsync event.
*/
- virtual nsecs_t nextAnticipatedVSyncTimeFrom(
- nsecs_t timePoint, std::optional<nsecs_t> lastVsyncOpt = {}) const = 0;
+ virtual nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint,
+ std::optional<nsecs_t> lastVsyncOpt = {}) = 0;
/*
* The current period of the vsync signal.
@@ -82,7 +82,7 @@
* \param [in] timePoint A vsync timestamp
* \param [in] frameRate The frame rate to check for
*/
- virtual bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const = 0;
+ virtual bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) = 0;
/*
* Sets the active mode of the display which includes the vsync period and other VRR attributes.
@@ -102,8 +102,10 @@
* when a display is running at 120Hz but the render frame rate is 60Hz.
*
* \param [in] Fps The render rate the tracker should operate at.
+ * \param [in] applyImmediately Whether to apply the new render rate immediately regardless of
+ * already committed vsyncs.
*/
- virtual void setRenderRate(Fps) = 0;
+ virtual void setRenderRate(Fps, bool applyImmediately) = 0;
virtual void onFrameBegin(TimePoint expectedPresentTime,
TimePoint lastConfirmedPresentTime) = 0;
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
index 001938c..2fa3318 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
@@ -120,8 +120,8 @@
constexpr size_t kMinSamplesForPrediction = 6;
constexpr uint32_t kDiscardOutlierPercent = 20;
- return std::make_unique<VSyncPredictor>(modePtr, kHistorySize, kMinSamplesForPrediction,
- kDiscardOutlierPercent);
+ return std::make_unique<VSyncPredictor>(std::make_unique<SystemClock>(), modePtr, kHistorySize,
+ kMinSamplesForPrediction, kDiscardOutlierPercent);
}
VsyncSchedule::DispatchPtr VsyncSchedule::createDispatch(TrackerPtr tracker) {
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.h b/services/surfaceflinger/Scheduler/VsyncSchedule.h
index 85cd3e7..881d678 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.h
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h
@@ -81,7 +81,7 @@
bool addResyncSample(TimePoint timestamp, ftl::Optional<Period> hwcVsyncPeriod);
// TODO(b/185535769): Hide behind API.
- const VsyncTracker& getTracker() const { return *mTracker; }
+ VsyncTracker& getTracker() const { return *mTracker; }
VsyncTracker& getTracker() { return *mTracker; }
VsyncController& getController() { return *mController; }
diff --git a/services/surfaceflinger/StartPropertySetThread.cpp b/services/surfaceflinger/StartPropertySetThread.cpp
deleted file mode 100644
index f42cd53..0000000
--- a/services/surfaceflinger/StartPropertySetThread.cpp
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 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 <cutils/properties.h>
-#include "StartPropertySetThread.h"
-
-namespace android {
-
-StartPropertySetThread::StartPropertySetThread(bool timestampPropertyValue):
- Thread(false), mTimestampPropertyValue(timestampPropertyValue) {}
-
-status_t StartPropertySetThread::Start() {
- return run("SurfaceFlinger::StartPropertySetThread", PRIORITY_NORMAL);
-}
-
-bool StartPropertySetThread::threadLoop() {
- // Set property service.sf.present_timestamp, consumer need check its readiness
- property_set(kTimestampProperty, mTimestampPropertyValue ? "1" : "0");
- // Clear BootAnimation exit flag
- property_set("service.bootanim.exit", "0");
- property_set("service.bootanim.progress", "0");
- // Start BootAnimation if not started
- property_set("ctl.start", "bootanim");
- // Exit immediately
- return false;
-}
-
-} // namespace android
diff --git a/services/surfaceflinger/StartPropertySetThread.h b/services/surfaceflinger/StartPropertySetThread.h
deleted file mode 100644
index bbdcde2..0000000
--- a/services/surfaceflinger/StartPropertySetThread.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2017 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.
- */
-
-#ifndef ANDROID_STARTBOOTANIMTHREAD_H
-#define ANDROID_STARTBOOTANIMTHREAD_H
-
-#include <stddef.h>
-
-#include <utils/Mutex.h>
-#include <utils/Thread.h>
-
-namespace android {
-
-class StartPropertySetThread : public Thread {
-// Boot animation is triggered via calls to "property_set()" which can block
-// if init's executing slow operation such as 'mount_all --late' (currently
-// happening 1/10th with fsck) concurrently. Running in a separate thread
-// allows to pursue the SurfaceFlinger's init process without blocking.
-// see b/34499826.
-// Any property_set() will block during init stage so need to be offloaded
-// to this thread. see b/63844978.
-public:
- explicit StartPropertySetThread(bool timestampPropertyValue);
- status_t Start();
-private:
- virtual bool threadLoop();
- static constexpr const char* kTimestampProperty = "service.sf.present_timestamp";
- const bool mTimestampPropertyValue;
-};
-
-}
-
-#endif // ANDROID_STARTBOOTANIMTHREAD_H
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 2501f4b..bf210af 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -153,7 +153,6 @@
#include "Scheduler/VsyncConfiguration.h"
#include "Scheduler/VsyncModulator.h"
#include "ScreenCaptureOutput.h"
-#include "StartPropertySetThread.h"
#include "SurfaceFlingerProperties.h"
#include "TimeStats/TimeStats.h"
#include "TunnelModeEnabledReporter.h"
@@ -566,7 +565,11 @@
initializeDisplays();
}));
- startBootAnim();
+ mInitBootPropsFuture.callOnce([this] {
+ return std::async(std::launch::async, &SurfaceFlinger::initBootProperties, this);
+ });
+
+ mInitBootPropsFuture.wait();
}
void SurfaceFlinger::run() {
@@ -722,13 +725,10 @@
}
mBootFinished = true;
FlagManager::getMutableInstance().markBootCompleted();
- if (mStartPropertySetThread->join() != NO_ERROR) {
- ALOGE("Join StartPropertySetThread failed!");
- }
- if (mRenderEnginePrimeCacheFuture.valid()) {
- mRenderEnginePrimeCacheFuture.get();
- }
+ mInitBootPropsFuture.wait();
+ mRenderEnginePrimeCacheFuture.wait();
+
const nsecs_t now = systemTime();
const nsecs_t duration = now - mBootTime;
ALOGI("Boot is finished (%ld ms)", long(ns2ms(duration)) );
@@ -816,8 +816,6 @@
}
}
-// Do not call property_set on main thread which will be blocked by init
-// Use StartPropertySetThread instead.
void SurfaceFlinger::init() FTL_FAKE_GUARD(kMainThreadContext) {
ATRACE_CALL();
ALOGI( "SurfaceFlinger's main thread ready to run. "
@@ -919,27 +917,40 @@
ALOGW("Can't set SCHED_OTHER for primeCache");
}
- bool shouldPrimeUltraHDR =
- base::GetBoolProperty("ro.surface_flinger.prime_shader_cache.ultrahdr"s, false);
- mRenderEnginePrimeCacheFuture = getRenderEngine().primeCache(shouldPrimeUltraHDR);
+ mRenderEnginePrimeCacheFuture.callOnce([this] {
+ const bool shouldPrimeUltraHDR =
+ base::GetBoolProperty("ro.surface_flinger.prime_shader_cache.ultrahdr"s, false);
+ return getRenderEngine().primeCache(shouldPrimeUltraHDR);
+ });
if (setSchedFifo(true) != NO_ERROR) {
ALOGW("Can't set SCHED_FIFO after primeCache");
}
}
- // Inform native graphics APIs whether the present timestamp is supported:
-
- mStartPropertySetThread = getFactory().createStartPropertySetThread(mHasReliablePresentFences);
-
- if (mStartPropertySetThread->Start() != NO_ERROR) {
- ALOGE("Run StartPropertySetThread failed!");
- }
+ // Avoid blocking the main thread on `init` to set properties.
+ mInitBootPropsFuture.callOnce([this] {
+ return std::async(std::launch::async, &SurfaceFlinger::initBootProperties, this);
+ });
initTransactionTraceWriter();
ALOGV("Done initializing");
}
+// During boot, offload `initBootProperties` to another thread. `property_set` depends on
+// `property_service`, which may be delayed by slow operations like `mount_all --late` in
+// the `init` process. See b/34499826 and b/63844978.
+void SurfaceFlinger::initBootProperties() {
+ property_set("service.sf.present_timestamp", mHasReliablePresentFences ? "1" : "0");
+
+ if (base::GetBoolProperty("debug.sf.boot_animation"s, true)) {
+ // Reset and (if needed) start BootAnimation.
+ property_set("service.bootanim.exit", "0");
+ property_set("service.bootanim.progress", "0");
+ property_set("ctl.start", "bootanim");
+ }
+}
+
void SurfaceFlinger::initTransactionTraceWriter() {
if (!mTransactionTracing) {
return;
@@ -979,18 +990,6 @@
static_cast<ui::ColorMode>(base::GetIntProperty("persist.sys.sf.color_mode"s, 0));
}
-void SurfaceFlinger::startBootAnim() {
- // Start boot animation service by setting a property mailbox
- // if property setting thread is already running, Start() will be just a NOP
- mStartPropertySetThread->Start();
- // Wait until property was set
- if (mStartPropertySetThread->join() != NO_ERROR) {
- ALOGE("Join StartPropertySetThread failed!");
- }
-}
-
-// ----------------------------------------------------------------------------
-
status_t SurfaceFlinger::getSupportedFrameTimestamps(
std::vector<FrameEvent>* outSupported) const {
*outSupported = {
@@ -1239,8 +1238,8 @@
switch (display->setDesiredMode(std::move(desiredMode))) {
case DisplayDevice::DesiredModeAction::InitiateDisplayModeSwitch:
// DisplayDevice::setDesiredMode updated the render rate, so inform Scheduler.
- mScheduler->setRenderRate(displayId,
- display->refreshRateSelector().getActiveMode().fps);
+ mScheduler->setRenderRate(displayId, display->refreshRateSelector().getActiveMode().fps,
+ /*applyImmediately*/ true);
// Schedule a new frame to initiate the display mode switch.
scheduleComposite(FrameHint::kNone);
@@ -1261,7 +1260,7 @@
mScheduler->setModeChangePending(true);
break;
case DisplayDevice::DesiredModeAction::InitiateRenderRateSwitch:
- mScheduler->setRenderRate(displayId, mode.fps);
+ mScheduler->setRenderRate(displayId, mode.fps, /*applyImmediately*/ false);
if (displayId == mActiveDisplayId) {
mScheduler->updatePhaseConfiguration(mode.fps);
@@ -1382,7 +1381,7 @@
constexpr bool kAllowToEnable = true;
mScheduler->resyncToHardwareVsync(displayId, kAllowToEnable, std::move(activeModePtr).take());
- mScheduler->setRenderRate(displayId, renderFps);
+ mScheduler->setRenderRate(displayId, renderFps, /*applyImmediately*/ true);
if (displayId == mActiveDisplayId) {
mScheduler->updatePhaseConfiguration(renderFps);
@@ -2730,7 +2729,8 @@
refreshArgs.forceOutputColorMode = mForceColorMode;
refreshArgs.updatingOutputGeometryThisFrame = mVisibleRegionsDirty;
- refreshArgs.updatingGeometryThisFrame = mGeometryDirty.exchange(false) || mVisibleRegionsDirty;
+ refreshArgs.updatingGeometryThisFrame = mGeometryDirty.exchange(false) ||
+ mVisibleRegionsDirty || mDrawingState.colorMatrixChanged;
refreshArgs.internalDisplayRotationFlags = getActiveDisplayRotationFlags();
if (CC_UNLIKELY(mDrawingState.colorMatrixChanged)) {
@@ -2783,12 +2783,16 @@
}
}
+ refreshArgs.refreshStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ for (auto [layer, layerFE] : layers) {
+ layer->onPreComposition(refreshArgs.refreshStartTime);
+ }
+
mCompositionEngine->present(refreshArgs);
moveSnapshotsFromCompositionArgs(refreshArgs, layers);
for (auto [layer, layerFE] : layers) {
CompositionResult compositionResult{layerFE->stealCompositionResult()};
- layer->onPreComposition(compositionResult.refreshStartTime);
for (auto& [releaseFence, layerStack] : compositionResult.releaseFences) {
Layer* clonedFrom = layer->getClonedFrom().get();
auto owningLayer = clonedFrom ? clonedFrom : layer;
@@ -4376,7 +4380,8 @@
// The pacesetter must be registered before EventThread creation below.
mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector());
if (FlagManager::getInstance().vrr_config()) {
- mScheduler->setRenderRate(display->getPhysicalId(), activeMode.fps);
+ mScheduler->setRenderRate(display->getPhysicalId(), activeMode.fps,
+ /*applyImmediately*/ true);
}
const auto configs = mScheduler->getVsyncConfiguration().getCurrentConfigs();
@@ -4700,7 +4705,14 @@
return TransactionReadiness::NotReady;
}
- if (!mScheduler->isVsyncValid(expectedPresentTime, transaction.originUid)) {
+ const auto vsyncId = VsyncId{transaction.frameTimelineInfo.vsyncId};
+
+ // Transactions with VsyncId are already throttled by the vsyncId (i.e. Choreographer issued
+ // the vsyncId according to the frame rate override cadence) so we shouldn't throttle again
+ // when applying the transaction. Otherwise we might throttle older transactions
+ // incorrectly as the frame rate of SF changed before it drained the older transactions.
+ if (ftl::to_underlying(vsyncId) == FrameTimelineInfo::INVALID_VSYNC_ID &&
+ !mScheduler->isVsyncValid(expectedPresentTime, transaction.originUid)) {
ATRACE_FORMAT("!isVsyncValid expectedPresentTime: %" PRId64 " uid: %d", expectedPresentTime,
transaction.originUid);
return TransactionReadiness::NotReady;
@@ -4708,8 +4720,7 @@
// If the client didn't specify desiredPresentTime, use the vsyncId to determine the
// expected present time of this transaction.
- if (transaction.isAutoTimestamp &&
- frameIsEarly(expectedPresentTime, VsyncId{transaction.frameTimelineInfo.vsyncId})) {
+ if (transaction.isAutoTimestamp && frameIsEarly(expectedPresentTime, vsyncId)) {
ATRACE_FORMAT("frameIsEarly vsyncId: %" PRId64 " expectedPresentTime: %" PRId64,
transaction.frameTimelineInfo.vsyncId, expectedPresentTime);
return TransactionReadiness::NotReady;
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 005f2e6..0cc8fbb 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -89,6 +89,7 @@
#include "Tracing/TransactionTracing.h"
#include "TransactionCallbackInvoker.h"
#include "TransactionState.h"
+#include "Utils/OnceFuture.h"
#include <atomic>
#include <cstdint>
@@ -550,7 +551,7 @@
bool hasListenerCallbacks, const std::vector<ListenerCallbacks>& listenerCallbacks,
uint64_t transactionId, const std::vector<uint64_t>& mergedTransactionIds) override;
void bootFinished();
- virtual status_t getSupportedFrameTimestamps(std::vector<FrameEvent>* outSupported) const;
+ status_t getSupportedFrameTimestamps(std::vector<FrameEvent>* outSupported) const;
sp<IDisplayEventConnection> createDisplayEventConnection(
gui::ISurfaceComposer::VsyncSource vsyncSource =
gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp,
@@ -871,9 +872,6 @@
// Traverse through all the layers and compute and cache its bounds.
void computeLayerBounds();
- // Boot animation, on/off animations and screen capture
- void startBootAnim();
-
bool layersHasProtectedLayer(const std::vector<std::pair<Layer*, sp<LayerFE>>>& layers) const;
void captureScreenCommon(RenderAreaFuture, GetLayerSnapshotsFunction, ui::Size bufferSize,
@@ -1184,11 +1182,17 @@
ui::Rotation getPhysicalDisplayOrientation(DisplayId, bool isPrimary) const
REQUIRES(mStateLock);
void traverseLegacyLayers(const LayerVector::Visitor& visitor) const;
+
+ void initBootProperties();
void initTransactionTraceWriter();
- sp<StartPropertySetThread> mStartPropertySetThread;
+
surfaceflinger::Factory& mFactory;
pid_t mPid;
- std::future<void> mRenderEnginePrimeCacheFuture;
+
+ // TODO: b/328459745 - Encapsulate in a SystemProperties object.
+ utils::OnceFuture mInitBootPropsFuture;
+
+ utils::OnceFuture mRenderEnginePrimeCacheFuture;
// mStateLock has conventions related to the current thread, because only
// the main thread should modify variables protected by mStateLock.
diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
index 7e6894d..50b167d 100644
--- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
+++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
@@ -26,7 +26,6 @@
#include "FrameTracer/FrameTracer.h"
#include "Layer.h"
#include "NativeWindowSurface.h"
-#include "StartPropertySetThread.h"
#include "SurfaceFlingerDefaultFactory.h"
#include "SurfaceFlingerProperties.h"
@@ -53,11 +52,6 @@
}
}
-sp<StartPropertySetThread> DefaultFactory::createStartPropertySetThread(
- bool timestampPropertyValue) {
- return sp<StartPropertySetThread>::make(timestampPropertyValue);
-}
-
sp<DisplayDevice> DefaultFactory::createDisplayDevice(DisplayDeviceCreationArgs& creationArgs) {
return sp<DisplayDevice>::make(creationArgs);
}
diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
index 2c6de0e..540dec8 100644
--- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
+++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
@@ -29,7 +29,6 @@
std::unique_ptr<HWComposer> createHWComposer(const std::string& serviceName) override;
std::unique_ptr<scheduler::VsyncConfiguration> createVsyncConfiguration(
Fps currentRefreshRate) override;
- sp<StartPropertySetThread> createStartPropertySetThread(bool timestampPropertyValue) override;
sp<DisplayDevice> createDisplayDevice(DisplayDeviceCreationArgs&) override;
sp<GraphicBuffer> createGraphicBuffer(uint32_t width, uint32_t height, PixelFormat format,
uint32_t layerCount, uint64_t usage,
diff --git a/services/surfaceflinger/SurfaceFlingerFactory.h b/services/surfaceflinger/SurfaceFlingerFactory.h
index f310c4a..f1fbf01 100644
--- a/services/surfaceflinger/SurfaceFlingerFactory.h
+++ b/services/surfaceflinger/SurfaceFlingerFactory.h
@@ -39,7 +39,6 @@
class IGraphicBufferProducer;
class Layer;
class LayerFE;
-class StartPropertySetThread;
class SurfaceFlinger;
class TimeStats;
@@ -71,8 +70,6 @@
virtual std::unique_ptr<scheduler::VsyncConfiguration> createVsyncConfiguration(
Fps currentRefreshRate) = 0;
- virtual sp<StartPropertySetThread> createStartPropertySetThread(
- bool timestampPropertyValue) = 0;
virtual sp<DisplayDevice> createDisplayDevice(DisplayDeviceCreationArgs&) = 0;
virtual sp<GraphicBuffer> createGraphicBuffer(uint32_t width, uint32_t height,
PixelFormat format, uint32_t layerCount,
diff --git a/services/surfaceflinger/Tracing/TransactionTracing.h b/services/surfaceflinger/Tracing/TransactionTracing.h
index 6a66fff..7a0fb5e 100644
--- a/services/surfaceflinger/Tracing/TransactionTracing.h
+++ b/services/surfaceflinger/Tracing/TransactionTracing.h
@@ -24,6 +24,7 @@
#include <mutex>
#include <optional>
+#include <set>
#include <thread>
#include "FrontEnd/DisplayInfo.h"
diff --git a/services/surfaceflinger/Utils/OnceFuture.h b/services/surfaceflinger/Utils/OnceFuture.h
new file mode 100644
index 0000000..412038c
--- /dev/null
+++ b/services/surfaceflinger/Utils/OnceFuture.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2024 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 <future>
+#include <mutex>
+
+#include <android-base/thread_annotations.h>
+
+namespace android::utils {
+
+// Allows a thread to `wait` for a future produced by a different thread. The future is returned by
+// the first call to a function `F` that multiple threads may `callOnce`. If no `callOnce` happens,
+// then `wait` does nothing. Otherwise, it blocks on the future, then destroys it, which resets the
+// `OnceFuture`.
+class OnceFuture {
+public:
+ template <typename F>
+ void callOnce(F f) {
+ std::lock_guard lock(mMutex);
+ if (!mFuture.valid()) {
+ mFuture = f();
+ }
+ }
+
+ void wait() {
+ std::lock_guard lock(mMutex);
+ if (mFuture.valid()) {
+ mFuture.wait();
+ mFuture = {};
+ }
+ }
+
+private:
+ std::mutex mMutex;
+ std::future<void> mFuture GUARDED_BY(mMutex);
+};
+
+} // namespace android::utils
diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp
index 3b669c6..3c2ccbc 100644
--- a/services/surfaceflinger/common/FlagManager.cpp
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -210,7 +210,7 @@
FLAG_MANAGER_READ_ONLY_FLAG(display_protected, "")
FLAG_MANAGER_READ_ONLY_FLAG(fp16_client_target, "debug.sf.fp16_client_target")
FLAG_MANAGER_READ_ONLY_FLAG(game_default_frame_rate, "")
-FLAG_MANAGER_READ_ONLY_FLAG(enable_layer_command_batching, "")
+FLAG_MANAGER_READ_ONLY_FLAG(enable_layer_command_batching, "debug.sf.enable_layer_command_batching")
FLAG_MANAGER_READ_ONLY_FLAG(screenshot_fence_preservation, "debug.sf.screenshot_fence_preservation")
FLAG_MANAGER_READ_ONLY_FLAG(vulkan_renderengine, "debug.renderengine.vulkan")
FLAG_MANAGER_READ_ONLY_FLAG(renderable_buffer_usage, "")
diff --git a/services/surfaceflinger/common/include/common/test/FlagUtils.h b/services/surfaceflinger/common/include/common/test/FlagUtils.h
index 550c70d..d61fcb5 100644
--- a/services/surfaceflinger/common/include/common/test/FlagUtils.h
+++ b/services/surfaceflinger/common/include/common/test/FlagUtils.h
@@ -18,7 +18,10 @@
#include <common/FlagManager.h>
-#define SET_FLAG_FOR_TEST(name, value) TestFlagSetter _testflag_((name), (name), (value))
+#define SET_FLAG_FOR_TEST(name, value) \
+ TestFlagSetter _testflag_ { \
+ (name), (name), (value) \
+ }
namespace android {
class TestFlagSetter {
diff --git a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
index aecfcba..867ff55 100644
--- a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
@@ -560,4 +560,36 @@
ftl::Flags<RequestedLayerState::Changes>().string());
}
+TEST_F(LayerLifecycleManagerTest, layerSecureChangesSetsVisibilityChangeFlag) {
+ // add a default buffer and make the layer secure
+ setFlags(1, layer_state_t::eLayerSecure, layer_state_t::eLayerSecure);
+ setBuffer(1,
+ std::make_shared<renderengine::mock::
+ FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+ 1ULL /* bufferId */,
+ HAL_PIXEL_FORMAT_RGBA_8888,
+ GRALLOC_USAGE_SW_READ_NEVER /*usage*/));
+
+ mLifecycleManager.commitChanges();
+
+ // set new buffer but layer secure doesn't change
+ setBuffer(1,
+ std::make_shared<renderengine::mock::
+ FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+ 2ULL /* bufferId */,
+ HAL_PIXEL_FORMAT_RGBA_8888,
+ GRALLOC_USAGE_SW_READ_NEVER /*usage*/));
+ EXPECT_EQ(mLifecycleManager.getGlobalChanges().get(),
+ ftl::Flags<RequestedLayerState::Changes>(RequestedLayerState::Changes::Buffer |
+ RequestedLayerState::Changes::Content)
+ .get());
+ mLifecycleManager.commitChanges();
+
+ // change layer flags and confirm visibility flag is set
+ setFlags(1, layer_state_t::eLayerSecure, 0);
+ EXPECT_TRUE(
+ mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Visibility));
+ mLifecycleManager.commitChanges();
+}
+
} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
index 1d44a3e..d9343c7 100644
--- a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
@@ -18,7 +18,9 @@
#define LOG_TAG "PowerAdvisorTest"
#include <DisplayHardware/PowerAdvisor.h>
+#include <android_os.h>
#include <binder/Status.h>
+#include <common/test/FlagUtils.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <powermanager/PowerHalWrapper.h>
@@ -55,6 +57,7 @@
std::unique_ptr<PowerAdvisor> mPowerAdvisor;
MockPowerHalController* mMockPowerHalController;
std::shared_ptr<MockPowerHintSessionWrapper> mMockPowerHintSession;
+ SET_FLAG_FOR_TEST(android::os::adpf_use_fmq_channel, true);
};
bool PowerAdvisorTest::sessionExists() {
@@ -75,13 +78,14 @@
void PowerAdvisorTest::startPowerHintSession(bool returnValidSession) {
mMockPowerHintSession = std::make_shared<NiceMock<MockPowerHintSessionWrapper>>();
if (returnValidSession) {
- ON_CALL(*mMockPowerHalController, createHintSession)
- .WillByDefault([&](int32_t, int32_t, const std::vector<int32_t>&, int64_t) {
- return HalResult<std::shared_ptr<PowerHintSessionWrapper>>::
- fromStatus(ndk::ScopedAStatus::ok(), mMockPowerHintSession);
- });
+ ON_CALL(*mMockPowerHalController, createHintSessionWithConfig)
+ .WillByDefault(DoAll(SetArgPointee<5>(aidl::android::hardware::power::SessionConfig{
+ .id = 12}),
+ Return(HalResult<std::shared_ptr<PowerHintSessionWrapper>>::
+ fromStatus(binder::Status::ok(),
+ mMockPowerHintSession))));
} else {
- ON_CALL(*mMockPowerHalController, createHintSession).WillByDefault([] {
+ ON_CALL(*mMockPowerHalController, createHintSessionWithConfig).WillByDefault([] {
return HalResult<
std::shared_ptr<PowerHintSessionWrapper>>::fromStatus(ndk::ScopedAStatus::ok(),
nullptr);
@@ -287,7 +291,7 @@
}
TEST_F(PowerAdvisorTest, hintSessionOnlyCreatedOnce) {
- EXPECT_CALL(*mMockPowerHalController, createHintSession(_, _, _, _)).Times(1);
+ EXPECT_CALL(*mMockPowerHalController, createHintSessionWithConfig(_, _, _, _, _, _)).Times(1);
mPowerAdvisor->onBootFinished();
startPowerHintSession();
mPowerAdvisor->startPowerHintSession({1, 2, 3});
@@ -339,7 +343,7 @@
return HalResult<void>::fromStatus(ndk::ScopedAStatus::fromExceptionCode(-127));
});
- ON_CALL(*mMockPowerHalController, createHintSession).WillByDefault([] {
+ ON_CALL(*mMockPowerHalController, createHintSessionWithConfig).WillByDefault([] {
return HalResult<std::shared_ptr<PowerHintSessionWrapper>>::
fromStatus(ndk::ScopedAStatus::fromExceptionCode(-127), nullptr);
});
@@ -374,5 +378,17 @@
EXPECT_EQ(sessionExists(), false);
}
+TEST_F(PowerAdvisorTest, legacyHintSessionCreationStillWorks) {
+ SET_FLAG_FOR_TEST(android::os::adpf_use_fmq_channel, false);
+ mPowerAdvisor->onBootFinished();
+ mMockPowerHintSession = std::make_shared<NiceMock<MockPowerHintSessionWrapper>>();
+ EXPECT_CALL(*mMockPowerHalController, createHintSession)
+ .Times(1)
+ .WillOnce(Return(HalResult<std::shared_ptr<PowerHintSessionWrapper>>::
+ fromStatus(binder::Status::ok(), mMockPowerHintSession)));
+ mPowerAdvisor->enablePowerHintSession(true);
+ mPowerAdvisor->startPowerHintSession({1, 2, 3});
+}
+
} // namespace
} // namespace android::Hwc2::impl
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index 0a6e305..a155f5d 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -259,6 +259,50 @@
config.enableFrameRateOverride = GetParam();
return TestableRefreshRateSelector(modes, activeModeId, config);
}
+
+ template <class T>
+ void testFrameRateCategoryWithMultipleLayers(const std::initializer_list<T>& testCases,
+ const TestableRefreshRateSelector& selector) {
+ std::vector<LayerRequirement> layers;
+ for (auto testCase : testCases) {
+ ALOGI("**** %s: Testing desiredFrameRate=%s, frameRateCategory=%s", __func__,
+ to_string(testCase.desiredFrameRate).c_str(),
+ ftl::enum_string(testCase.frameRateCategory).c_str());
+
+ if (testCase.desiredFrameRate.isValid()) {
+ std::stringstream ss;
+ ss << to_string(testCase.desiredFrameRate)
+ << ftl::enum_string(testCase.frameRateCategory) << "ExplicitDefault";
+ LayerRequirement layer = {.name = ss.str(),
+ .vote = LayerVoteType::ExplicitDefault,
+ .desiredRefreshRate = testCase.desiredFrameRate,
+ .weight = 1.f};
+ layers.push_back(layer);
+ }
+
+ if (testCase.frameRateCategory != FrameRateCategory::Default) {
+ std::stringstream ss;
+ ss << "ExplicitCategory (" << ftl::enum_string(testCase.frameRateCategory) << ")";
+ LayerRequirement layer = {.name = ss.str(),
+ .vote = LayerVoteType::ExplicitCategory,
+ .frameRateCategory = testCase.frameRateCategory,
+ .weight = 1.f};
+ layers.push_back(layer);
+ }
+
+ EXPECT_EQ(testCase.expectedFrameRate,
+ selector.getBestFrameRateMode(layers).modePtr->getPeakFps())
+ << "Did not get expected frame rate for frameRate="
+ << to_string(testCase.desiredFrameRate)
+ << " category=" << ftl::enum_string(testCase.frameRateCategory);
+ EXPECT_EQ(testCase.expectedModeId,
+ selector.getBestFrameRateMode(layers).modePtr->getId())
+ << "Did not get expected DisplayModeId for modeId="
+ << ftl::to_underlying(testCase.expectedModeId)
+ << " frameRate=" << to_string(testCase.desiredFrameRate)
+ << " category=" << ftl::enum_string(testCase.frameRateCategory);
+ }
+ }
};
RefreshRateSelectorTest::RefreshRateSelectorTest() {
@@ -1496,7 +1540,7 @@
{0_Hz, FrameRateCategory::High, 90_Hz},
{0_Hz, FrameRateCategory::Normal, 60_Hz},
{0_Hz, FrameRateCategory::Low, 30_Hz},
- {0_Hz, FrameRateCategory::NoPreference, 30_Hz},
+ {0_Hz, FrameRateCategory::NoPreference, 60_Hz},
// Cases that have both desired frame rate and frame rate category requirements.
{24_Hz, FrameRateCategory::High, 120_Hz},
@@ -1542,6 +1586,98 @@
}
}
+TEST_P(RefreshRateSelectorTest,
+ getBestFrameRateMode_withFrameRateCategoryMultiLayers_30_60_90_120) {
+ auto selector = createSelector(makeModes(kMode30, kMode60, kMode90, kMode120), kModeId60);
+
+ struct Case {
+ // Params
+ Fps desiredFrameRate = 0_Hz;
+ FrameRateCategory frameRateCategory = FrameRateCategory::Default;
+
+ // Expected result
+ Fps expectedFrameRate = 0_Hz;
+ DisplayModeId expectedModeId = kModeId90;
+ };
+
+ testFrameRateCategoryWithMultipleLayers(
+ std::initializer_list<Case>{
+ {0_Hz, FrameRateCategory::High, 90_Hz},
+ {0_Hz, FrameRateCategory::NoPreference, 90_Hz},
+ {0_Hz, FrameRateCategory::Normal, 90_Hz},
+ {0_Hz, FrameRateCategory::Normal, 90_Hz},
+ {0_Hz, FrameRateCategory::NoPreference, 90_Hz},
+ },
+ selector);
+
+ testFrameRateCategoryWithMultipleLayers(
+ std::initializer_list<Case>{
+ {0_Hz, FrameRateCategory::Normal, 60_Hz, kModeId60},
+ {0_Hz, FrameRateCategory::High, 90_Hz},
+ {0_Hz, FrameRateCategory::NoPreference, 90_Hz},
+ },
+ selector);
+
+ testFrameRateCategoryWithMultipleLayers(
+ std::initializer_list<Case>{
+ {30_Hz, FrameRateCategory::High, 90_Hz},
+ {24_Hz, FrameRateCategory::High, 120_Hz, kModeId120},
+ {12_Hz, FrameRateCategory::Normal, 120_Hz, kModeId120},
+ {30_Hz, FrameRateCategory::NoPreference, 120_Hz, kModeId120},
+
+ },
+ selector);
+
+ testFrameRateCategoryWithMultipleLayers(
+ std::initializer_list<Case>{
+ {24_Hz, FrameRateCategory::Default, 120_Hz, kModeId120},
+ {30_Hz, FrameRateCategory::Default, 120_Hz, kModeId120},
+ {120_Hz, FrameRateCategory::Default, 120_Hz, kModeId120},
+ },
+ selector);
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategoryMultiLayers_60_120) {
+ auto selector = createSelector(makeModes(kMode60, kMode120), kModeId60);
+
+ struct Case {
+ // Params
+ Fps desiredFrameRate = 0_Hz;
+ FrameRateCategory frameRateCategory = FrameRateCategory::Default;
+
+ // Expected result
+ Fps expectedFrameRate = 0_Hz;
+ DisplayModeId expectedModeId = kModeId120;
+ };
+
+ testFrameRateCategoryWithMultipleLayers(std::initializer_list<
+ Case>{{0_Hz, FrameRateCategory::High, 120_Hz},
+ {0_Hz, FrameRateCategory::NoPreference,
+ 120_Hz},
+ {0_Hz, FrameRateCategory::Normal, 120_Hz},
+ {0_Hz, FrameRateCategory::Normal, 120_Hz},
+ {0_Hz, FrameRateCategory::NoPreference,
+ 120_Hz}},
+ selector);
+
+ testFrameRateCategoryWithMultipleLayers(std::initializer_list<
+ Case>{{24_Hz, FrameRateCategory::High, 120_Hz},
+ {30_Hz, FrameRateCategory::High, 120_Hz},
+ {12_Hz, FrameRateCategory::Normal,
+ 120_Hz},
+ {30_Hz, FrameRateCategory::NoPreference,
+ 120_Hz}},
+ selector);
+
+ testFrameRateCategoryWithMultipleLayers(
+ std::initializer_list<Case>{
+ {24_Hz, FrameRateCategory::Default, 120_Hz},
+ {30_Hz, FrameRateCategory::Default, 120_Hz},
+ {120_Hz, FrameRateCategory::Default, 120_Hz},
+ },
+ selector);
+}
+
TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategory_60_120) {
auto selector = createSelector(makeModes(kMode60, kMode120), kModeId60);
@@ -1665,6 +1801,7 @@
lr1.frameRateCategory = FrameRateCategory::HighHint;
lr1.name = "ExplicitCategory HighHint";
lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr2.frameRateCategory = FrameRateCategory::Default;
lr2.desiredRefreshRate = 30_Hz;
lr2.name = "30Hz ExplicitExactOrMultiple";
actualRankedFrameRates = selector.getRankedFrameRates(layers);
@@ -1691,6 +1828,269 @@
EXPECT_EQ(kModeId30, actualRankedFrameRates.ranking.front().frameRateMode.modePtr->getId());
EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch);
}
+
+ lr1.vote = LayerVoteType::ExplicitCategory;
+ lr1.frameRateCategory = FrameRateCategory::HighHint;
+ lr1.name = "ExplicitCategory HighHint";
+ lr2.vote = LayerVoteType::Heuristic;
+ lr2.desiredRefreshRate = 30_Hz;
+ lr2.name = "30Hz Heuristic";
+ actualRankedFrameRates = selector.getRankedFrameRates(layers);
+ // Gets touch boost
+ EXPECT_EQ(120_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps);
+ EXPECT_EQ(kModeId120, actualRankedFrameRates.ranking.front().frameRateMode.modePtr->getId());
+ EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch);
+
+ lr1.vote = LayerVoteType::ExplicitCategory;
+ lr1.frameRateCategory = FrameRateCategory::HighHint;
+ lr1.name = "ExplicitCategory HighHint";
+ lr2.vote = LayerVoteType::Min;
+ lr2.name = "Min";
+ actualRankedFrameRates = selector.getRankedFrameRates(layers);
+ // Gets touch boost
+ EXPECT_EQ(120_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps);
+ EXPECT_EQ(kModeId120, actualRankedFrameRates.ranking.front().frameRateMode.modePtr->getId());
+ EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch);
+
+ lr1.vote = LayerVoteType::ExplicitCategory;
+ lr1.frameRateCategory = FrameRateCategory::HighHint;
+ lr1.name = "ExplicitCategory HighHint";
+ lr2.vote = LayerVoteType::Max;
+ lr2.name = "Max";
+ actualRankedFrameRates = selector.getRankedFrameRates(layers);
+ // Gets touch boost
+ EXPECT_EQ(120_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps);
+ EXPECT_EQ(kModeId120, actualRankedFrameRates.ranking.front().frameRateMode.modePtr->getId());
+ EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch);
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategory_TouchBoost) {
+ auto selector = createSelector(makeModes(kMode24, kMode30, kMode60, kMode120), kModeId60);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
+ auto& lr1 = layers[0];
+ auto& lr2 = layers[1];
+
+ lr1.vote = LayerVoteType::ExplicitCategory;
+ lr1.frameRateCategory = FrameRateCategory::Normal;
+ lr1.name = "ExplicitCategory Normal";
+ lr2.vote = LayerVoteType::NoVote;
+ lr2.name = "NoVote";
+ auto actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true});
+ EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, actualRankedFrameRates.ranking.front().frameRateMode);
+ EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch);
+
+ // No touch boost, for example a game that uses setFrameRate(30, default compatibility).
+ lr1.vote = LayerVoteType::ExplicitCategory;
+ lr1.frameRateCategory = FrameRateCategory::Normal;
+ lr1.name = "ExplicitCategory Normal";
+ lr2.vote = LayerVoteType::ExplicitDefault;
+ lr2.desiredRefreshRate = 30_Hz;
+ lr2.name = "30Hz ExplicitDefault";
+ actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true});
+ EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, actualRankedFrameRates.ranking.front().frameRateMode);
+ EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch);
+
+ lr1.vote = LayerVoteType::ExplicitCategory;
+ lr1.frameRateCategory = FrameRateCategory::Normal;
+ lr1.name = "ExplicitCategory Normal";
+ lr2.vote = LayerVoteType::ExplicitCategory;
+ lr2.frameRateCategory = FrameRateCategory::HighHint;
+ lr2.name = "ExplicitCategory HighHint";
+ actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true});
+ EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, actualRankedFrameRates.ranking.front().frameRateMode);
+ EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch);
+
+ lr1.vote = LayerVoteType::ExplicitCategory;
+ lr1.frameRateCategory = FrameRateCategory::Normal;
+ lr1.name = "ExplicitCategory Normal";
+ lr2.vote = LayerVoteType::ExplicitCategory;
+ lr2.frameRateCategory = FrameRateCategory::Low;
+ lr2.name = "ExplicitCategory Low";
+ actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true});
+ EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, actualRankedFrameRates.ranking.front().frameRateMode);
+ EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch);
+
+ lr1.vote = LayerVoteType::ExplicitCategory;
+ lr1.frameRateCategory = FrameRateCategory::Normal;
+ lr1.name = "ExplicitCategory Normal";
+ lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr2.frameRateCategory = FrameRateCategory::Default;
+ lr2.desiredRefreshRate = 30_Hz;
+ lr2.name = "30Hz ExplicitExactOrMultiple";
+ actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true});
+ EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, actualRankedFrameRates.ranking.front().frameRateMode);
+ EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch);
+
+ lr1.vote = LayerVoteType::ExplicitCategory;
+ lr1.frameRateCategory = FrameRateCategory::Normal;
+ lr1.name = "ExplicitCategory Normal";
+ lr2.vote = LayerVoteType::ExplicitExact;
+ lr2.desiredRefreshRate = 30_Hz;
+ lr2.name = "30Hz ExplicitExact";
+ actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true});
+ if (selector.supportsAppFrameRateOverrideByContent()) {
+ EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz,
+ actualRankedFrameRates.ranking.front().frameRateMode);
+ EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch);
+ } else {
+ EXPECT_FRAME_RATE_MODE(kMode30, 30_Hz,
+ actualRankedFrameRates.ranking.front().frameRateMode);
+ EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch);
+ }
+
+ lr1.vote = LayerVoteType::ExplicitCategory;
+ lr1.frameRateCategory = FrameRateCategory::Normal;
+ lr1.name = "ExplicitCategory Normal";
+ lr2.vote = LayerVoteType::Min;
+ lr2.name = "Min";
+ actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true});
+ EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, actualRankedFrameRates.ranking.front().frameRateMode);
+ EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch);
+
+ lr1.vote = LayerVoteType::ExplicitCategory;
+ lr1.frameRateCategory = FrameRateCategory::Normal;
+ lr1.name = "ExplicitCategory Normal";
+ lr2.vote = LayerVoteType::Max;
+ lr2.name = "Max";
+ actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true});
+ EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, actualRankedFrameRates.ranking.front().frameRateMode);
+ EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch);
+
+ lr1.vote = LayerVoteType::ExplicitCategory;
+ lr1.frameRateCategory = FrameRateCategory::Normal;
+ lr1.name = "ExplicitCategory Normal";
+ lr2.vote = LayerVoteType::Heuristic;
+ lr2.name = "30Hz Heuristic";
+ actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true});
+ EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, actualRankedFrameRates.ranking.front().frameRateMode);
+ EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch);
+
+ lr1.vote = LayerVoteType::ExplicitCategory;
+ lr1.frameRateCategory = FrameRateCategory::Normal;
+ lr1.name = "ExplicitCategory Normal";
+ lr2.vote = LayerVoteType::ExplicitGte;
+ lr2.desiredRefreshRate = 30_Hz;
+ lr2.name = "30Hz ExplicitGte";
+ actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true});
+ EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, actualRankedFrameRates.ranking.front().frameRateMode);
+ EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch);
+}
+
+TEST_P(RefreshRateSelectorTest,
+ getBestFrameRateMode_withFrameRateCategory_idleTimer_60_120_nonVrr) {
+ SET_FLAG_FOR_TEST(flags::vrr_config, false);
+ using KernelIdleTimerAction = RefreshRateSelector::KernelIdleTimerAction;
+ struct LayerArg {
+ // Params
+ FrameRateCategory frameRateCategory = FrameRateCategory::Default;
+ LayerVoteType voteType = LayerVoteType::ExplicitDefault;
+
+ // Expected result
+ Fps expectedFrameRate = 0_Hz;
+ DisplayModeId expectedModeId = kModeId60;
+ };
+
+ const auto runTest = [&](const TestableRefreshRateSelector& selector,
+ const std::initializer_list<LayerArg>& layerArgs,
+ const RefreshRateSelector::GlobalSignals& signals) {
+ std::vector<LayerRequirement> layers;
+ for (auto testCase : layerArgs) {
+ ALOGI("**** %s: Testing frameRateCategory=%s", __func__,
+ ftl::enum_string(testCase.frameRateCategory).c_str());
+
+ if (testCase.frameRateCategory != FrameRateCategory::Default) {
+ std::stringstream ss;
+ ss << "ExplicitCategory (" << ftl::enum_string(testCase.frameRateCategory) << ")";
+ LayerRequirement layer = {.name = ss.str(),
+ .vote = LayerVoteType::ExplicitCategory,
+ .frameRateCategory = testCase.frameRateCategory,
+ .weight = 1.f};
+ layers.push_back(layer);
+ }
+
+ if (testCase.voteType != LayerVoteType::ExplicitDefault) {
+ std::stringstream ss;
+ ss << ftl::enum_string(testCase.voteType);
+ LayerRequirement layer = {.name = ss.str(),
+ .vote = testCase.voteType,
+ .weight = 1.f};
+ layers.push_back(layer);
+ }
+
+ EXPECT_EQ(testCase.expectedFrameRate,
+ selector.getBestFrameRateMode(layers, signals).modePtr->getPeakFps())
+ << "Did not get expected frame rate for"
+ << " category=" << ftl::enum_string(testCase.frameRateCategory);
+ EXPECT_EQ(testCase.expectedModeId,
+ selector.getBestFrameRateMode(layers, signals).modePtr->getId())
+ << "Did not get expected DisplayModeId for modeId="
+ << ftl::to_underlying(testCase.expectedModeId)
+ << " category=" << ftl::enum_string(testCase.frameRateCategory);
+ }
+ };
+
+ {
+ // IdleTimer not configured
+ auto selector = createSelector(makeModes(kMode60, kMode120), kModeId120);
+ ASSERT_EQ(0ms, selector.getIdleTimerTimeout());
+
+ runTest(selector,
+ std::initializer_list<LayerArg>{
+ // Rate does not change due to NoPreference.
+ {.frameRateCategory = FrameRateCategory::NoPreference,
+ .expectedFrameRate = 120_Hz,
+ .expectedModeId = kModeId120},
+ {.voteType = LayerVoteType::NoVote,
+ .expectedFrameRate = 120_Hz,
+ .expectedModeId = kModeId120},
+ {.frameRateCategory = FrameRateCategory::NoPreference,
+ .expectedFrameRate = 120_Hz,
+ .expectedModeId = kModeId120},
+ },
+ {.idle = false});
+ }
+
+ // IdleTimer configured
+ constexpr std::chrono::milliseconds kIdleTimerTimeoutMs = 10ms;
+ auto selector = createSelector(makeModes(kMode60, kMode120), kModeId120,
+ Config{
+ .idleTimerTimeout = kIdleTimerTimeoutMs,
+ });
+ ASSERT_EQ(KernelIdleTimerAction::TurnOn, selector.getIdleTimerAction());
+ ASSERT_EQ(kIdleTimerTimeoutMs, selector.getIdleTimerTimeout());
+ runTest(selector,
+ std::initializer_list<LayerArg>{
+ // Rate won't change immediately and will stay 120 due to NoPreference, as
+ // idle timer did not timeout yet.
+ {.frameRateCategory = FrameRateCategory::NoPreference,
+ .expectedFrameRate = 120_Hz,
+ .expectedModeId = kModeId120},
+ {.voteType = LayerVoteType::NoVote,
+ .expectedFrameRate = 120_Hz,
+ .expectedModeId = kModeId120},
+ {.frameRateCategory = FrameRateCategory::NoPreference,
+ .expectedFrameRate = 120_Hz,
+ .expectedModeId = kModeId120},
+ },
+ {.idle = false});
+
+ // Idle timer is triggered using GlobalSignals.
+ ASSERT_EQ(KernelIdleTimerAction::TurnOn, selector.getIdleTimerAction());
+ ASSERT_EQ(kIdleTimerTimeoutMs, selector.getIdleTimerTimeout());
+ runTest(selector,
+ std::initializer_list<LayerArg>{
+ {.frameRateCategory = FrameRateCategory::NoPreference,
+ .expectedFrameRate = 60_Hz,
+ .expectedModeId = kModeId60},
+ {.voteType = LayerVoteType::NoVote,
+ .expectedFrameRate = 60_Hz,
+ .expectedModeId = kModeId60},
+ {.frameRateCategory = FrameRateCategory::NoPreference,
+ .expectedFrameRate = 60_Hz,
+ .expectedModeId = kModeId60},
+ },
+ {.idle = true});
}
TEST_P(RefreshRateSelectorTest,
@@ -1716,8 +2116,7 @@
const std::initializer_list<Case> testCases = {
// These layers may switch modes because smoothSwitchOnly=false.
{FrameRateCategory::Default, false, 120_Hz, kModeId120},
- // TODO(b/266481656): Once this bug is fixed, NoPreference should be a lower frame rate.
- {FrameRateCategory::NoPreference, false, 60_Hz, kModeId60},
+ {FrameRateCategory::NoPreference, false, 120_Hz, kModeId120},
{FrameRateCategory::Low, false, 30_Hz, kModeId60},
{FrameRateCategory::Normal, false, 60_Hz, kModeId60},
{FrameRateCategory::High, false, 120_Hz, kModeId120},
@@ -1725,8 +2124,8 @@
// These layers cannot change mode due to smoothSwitchOnly, and will definitely use
// active mode (120Hz).
{FrameRateCategory::NoPreference, true, 120_Hz, kModeId120},
- {FrameRateCategory::Low, true, 120_Hz, kModeId120},
- {FrameRateCategory::Normal, true, 40_Hz, kModeId120},
+ {FrameRateCategory::Low, true, 40_Hz, kModeId120},
+ {FrameRateCategory::Normal, true, 120_Hz, kModeId120},
{FrameRateCategory::High, true, 120_Hz, kModeId120},
};
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 10e2220..d4735c7 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -25,6 +25,7 @@
#include "Scheduler/EventThread.h"
#include "Scheduler/RefreshRateSelector.h"
#include "Scheduler/VSyncPredictor.h"
+#include "Scheduler/VSyncReactor.h"
#include "TestableScheduler.h"
#include "TestableSurfaceFlinger.h"
#include "mock/DisplayHardware/MockDisplayMode.h"
@@ -56,6 +57,11 @@
using LayerHierarchyBuilder = surfaceflinger::frontend::LayerHierarchyBuilder;
using RequestedLayerState = surfaceflinger::frontend::RequestedLayerState;
+class ZeroClock : public Clock {
+public:
+ nsecs_t now() const override { return 0; }
+};
+
class SchedulerTest : public testing::Test {
protected:
class MockEventThreadConnection : public android::EventThreadConnection {
@@ -563,7 +569,8 @@
hal::VrrConfig{.minFrameIntervalNs = static_cast<int32_t>(
frameRate.getPeriodNsecs())}));
std::shared_ptr<VSyncPredictor> vrrTracker =
- std::make_shared<VSyncPredictor>(kMode, kHistorySize, kMinimumSamplesForPrediction,
+ std::make_shared<VSyncPredictor>(std::make_unique<ZeroClock>(), kMode, kHistorySize,
+ kMinimumSamplesForPrediction,
kOutlierTolerancePercent);
std::shared_ptr<RefreshRateSelector> vrrSelectorPtr =
std::make_shared<RefreshRateSelector>(makeModes(kMode), kMode->getId());
@@ -576,8 +583,10 @@
scheduler.registerDisplay(kMode->getPhysicalDisplayId(), vrrSelectorPtr, vrrTracker);
vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate);
- scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate);
+ scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate, /*applyImmediately*/ false);
vrrTracker->addVsyncTimestamp(0);
+ // Set 1000 as vsync seq #0
+ vrrTracker->nextAnticipatedVSyncTimeFrom(700);
EXPECT_EQ(Fps::fromPeriodNsecs(1000),
scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
@@ -587,20 +596,21 @@
TimePoint::fromNs(2000)));
// Not crossing the min frame period
- EXPECT_EQ(Fps::fromPeriodNsecs(1500),
+ vrrTracker->onFrameBegin(TimePoint::fromNs(2000), TimePoint::fromNs(1500));
+ EXPECT_EQ(Fps::fromPeriodNsecs(1000),
scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
TimePoint::fromNs(2500)));
// Change render rate
frameRate = Fps::fromPeriodNsecs(2000);
vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate);
- scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate);
+ scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate, /*applyImmediately*/ false);
EXPECT_EQ(Fps::fromPeriodNsecs(2000),
scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
- TimePoint::fromNs(2000)));
+ TimePoint::fromNs(4500)));
EXPECT_EQ(Fps::fromPeriodNsecs(2000),
scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
- TimePoint::fromNs(4000)));
+ TimePoint::fromNs(6500)));
}
TEST_F(SchedulerTest, resyncAllToHardwareVsync) FTL_FAKE_GUARD(kMainThreadContext) {
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index bce7729..82023b0 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -43,7 +43,6 @@
#include "RenderArea.h"
#include "Scheduler/MessageQueue.h"
#include "Scheduler/RefreshRateSelector.h"
-#include "StartPropertySetThread.h"
#include "SurfaceFlinger.h"
#include "TestableScheduler.h"
#include "mock/DisplayHardware/MockComposer.h"
@@ -94,10 +93,6 @@
return std::make_unique<scheduler::FakePhaseOffsets>();
}
- sp<StartPropertySetThread> createStartPropertySetThread(bool timestampPropertyValue) override {
- return sp<StartPropertySetThread>::make(timestampPropertyValue);
- }
-
sp<DisplayDevice> createDisplayDevice(DisplayDeviceCreationArgs& creationArgs) override {
return sp<DisplayDevice>::make(creationArgs);
}
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
index d891008..d701a97 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
@@ -48,9 +48,9 @@
Period minFramePeriod() const final { return Period::fromNs(currentPeriod()); }
void resetModel() final {}
bool needsMoreSamples() const final { return false; }
- bool isVSyncInPhase(nsecs_t, Fps) const final { return false; }
+ bool isVSyncInPhase(nsecs_t, Fps) final { return false; }
void setDisplayModePtr(ftl::NonNull<DisplayModePtr>) final {}
- void setRenderRate(Fps) final {}
+ void setRenderRate(Fps, bool) final {}
void onFrameBegin(TimePoint, TimePoint) final {}
void onFrameMissed(TimePoint) final {}
void dump(std::string&) const final {}
@@ -64,7 +64,7 @@
public:
FixedRateIdealStubTracker() : StubTracker{toNs(3ms)} {}
- nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, std::optional<nsecs_t>) const final {
+ nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, std::optional<nsecs_t>) final {
auto const floor = timePoint % mPeriod;
if (floor == 0) {
return timePoint;
@@ -77,7 +77,7 @@
public:
VRRStubTracker(nsecs_t period) : StubTracker(period) {}
- nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t time_point, std::optional<nsecs_t>) const final {
+ nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t time_point, std::optional<nsecs_t>) final {
std::lock_guard lock(mMutex);
auto const normalized_to_base = time_point - mBase;
auto const floor = (normalized_to_base) % mPeriod;
diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
index b9f3d70..aac1cac 100644
--- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
@@ -75,6 +75,28 @@
return ftl::as_non_null(createDisplayMode(DisplayModeId(0), refreshRate, kGroup, kResolution,
DEFAULT_DISPLAY_ID));
}
+
+class TestClock : public Clock {
+public:
+ TestClock() = default;
+
+ nsecs_t now() const override { return mNow; }
+ void setNow(nsecs_t now) { mNow = now; }
+
+private:
+ nsecs_t mNow = 0;
+};
+
+class ClockWrapper : public Clock {
+public:
+ ClockWrapper(std::shared_ptr<Clock> const& clock) : mClock(clock) {}
+
+ nsecs_t now() const { return mClock->now(); }
+
+private:
+ std::shared_ptr<Clock> const mClock;
+};
+
} // namespace
struct VSyncPredictorTest : testing::Test {
@@ -86,8 +108,10 @@
static constexpr size_t kOutlierTolerancePercent = 25;
static constexpr nsecs_t mMaxRoundingError = 100;
- VSyncPredictor tracker{mMode, kHistorySize, kMinimumSamplesForPrediction,
- kOutlierTolerancePercent};
+ std::shared_ptr<TestClock> mClock{std::make_shared<TestClock>()};
+
+ VSyncPredictor tracker{std::make_unique<ClockWrapper>(mClock), mMode, kHistorySize,
+ kMinimumSamplesForPrediction, kOutlierTolerancePercent};
};
TEST_F(VSyncPredictorTest, reportsAnticipatedPeriod) {
@@ -408,7 +432,8 @@
// See b/151146131
TEST_F(VSyncPredictorTest, hasEnoughPrecision) {
const auto mode = displayMode(mPeriod);
- VSyncPredictor tracker{mode, 20, kMinimumSamplesForPrediction, kOutlierTolerancePercent};
+ VSyncPredictor tracker{std::make_unique<ClockWrapper>(mClock), mode, 20,
+ kMinimumSamplesForPrediction, kOutlierTolerancePercent};
std::vector<nsecs_t> const simulatedVsyncs{840873348817, 840890049444, 840906762675,
840923581635, 840940161584, 840956868096,
840973702473, 840990256277, 841007116851,
@@ -595,44 +620,15 @@
tracker.addVsyncTimestamp(mNow);
}
- tracker.setRenderRate(Fps::fromPeriodNsecs(3 * mPeriod));
+ tracker.setRenderRate(Fps::fromPeriodNsecs(3 * mPeriod), /*applyImmediately*/ false);
- EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod));
- EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + mPeriod));
- EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1100), Eq(mNow + 4 * mPeriod));
- EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 2100), Eq(mNow + 4 * mPeriod));
- EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3100), Eq(mNow + 4 * mPeriod));
- EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 4100), Eq(mNow + 7 * mPeriod));
- EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 7 * mPeriod));
-}
-
-TEST_F(VSyncPredictorTest, setRenderRateOfDivisorIsInPhase) {
- auto last = mNow;
- for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
- EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod));
- mNow += mPeriod;
- last = mNow;
- tracker.addVsyncTimestamp(mNow);
- }
-
- const auto refreshRate = Fps::fromPeriodNsecs(mPeriod);
-
- tracker.setRenderRate(refreshRate / 4);
EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 3 * mPeriod));
- EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3 * mPeriod), Eq(mNow + 7 * mPeriod));
- EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 7 * mPeriod), Eq(mNow + 11 * mPeriod));
-
- tracker.setRenderRate(refreshRate / 2);
- EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 1 * mPeriod));
- EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod), Eq(mNow + 3 * mPeriod));
- EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3 * mPeriod), Eq(mNow + 5 * mPeriod));
- EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5 * mPeriod), Eq(mNow + 7 * mPeriod));
- EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 7 * mPeriod), Eq(mNow + 9 * mPeriod));
- EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 9 * mPeriod), Eq(mNow + 11 * mPeriod));
-
- tracker.setRenderRate(refreshRate / 6);
- EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 1 * mPeriod));
- EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod), Eq(mNow + 7 * mPeriod));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + 3 * mPeriod));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1100), Eq(mNow + 3 * mPeriod));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 2100), Eq(mNow + 3 * mPeriod));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3100), Eq(mNow + 6 * mPeriod));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 4100), Eq(mNow + 6 * mPeriod));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 6 * mPeriod));
}
TEST_F(VSyncPredictorTest, setRenderRateIsIgnoredIfNotDivisor) {
@@ -644,7 +640,7 @@
tracker.addVsyncTimestamp(mNow);
}
- tracker.setRenderRate(Fps::fromPeriodNsecs(3.5f * mPeriod));
+ tracker.setRenderRate(Fps::fromPeriodNsecs(3.5f * mPeriod), /*applyImmediately*/ false);
EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod));
EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + mPeriod));
@@ -655,6 +651,178 @@
EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 6 * mPeriod));
}
+TEST_F(VSyncPredictorTest, setRenderRateHighIsAppliedImmediately) {
+ SET_FLAG_FOR_TEST(flags::vrr_config, true);
+
+ const int32_t kGroup = 0;
+ const auto kResolution = ui::Size(1920, 1080);
+ const auto vsyncRate = Fps::fromPeriodNsecs(500);
+ const auto minFrameRate = Fps::fromPeriodNsecs(1000);
+ hal::VrrConfig vrrConfig;
+ vrrConfig.minFrameIntervalNs = minFrameRate.getPeriodNsecs();
+ const ftl::NonNull<DisplayModePtr> kMode =
+ ftl::as_non_null(createDisplayModeBuilder(DisplayModeId(0), vsyncRate, kGroup,
+ kResolution, DEFAULT_DISPLAY_ID)
+ .setVrrConfig(std::move(vrrConfig))
+ .build());
+
+ VSyncPredictor vrrTracker{std::make_unique<ClockWrapper>(mClock), kMode, kHistorySize,
+ kMinimumSamplesForPrediction, kOutlierTolerancePercent};
+
+ vrrTracker.setRenderRate(Fps::fromPeriodNsecs(1000), /*applyImmediately*/ false);
+ vrrTracker.addVsyncTimestamp(0);
+ EXPECT_EQ(1000, vrrTracker.nextAnticipatedVSyncTimeFrom(700));
+ EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1000, 1000));
+
+ // commit to a vsync in the future
+ EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
+
+ vrrTracker.setRenderRate(Fps::fromPeriodNsecs(2000), /*applyImmediately*/ false);
+ EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000));
+ EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
+ EXPECT_EQ(8000, vrrTracker.nextAnticipatedVSyncTimeFrom(6000, 6000));
+
+ EXPECT_EQ(12000, vrrTracker.nextAnticipatedVSyncTimeFrom(10000, 10000));
+
+ vrrTracker.setRenderRate(Fps::fromPeriodNsecs(3500), /*applyImmediately*/ false);
+ EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000));
+ EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
+ EXPECT_EQ(8000, vrrTracker.nextAnticipatedVSyncTimeFrom(6000, 6000));
+ EXPECT_EQ(10000, vrrTracker.nextAnticipatedVSyncTimeFrom(8000, 8000));
+ EXPECT_EQ(12000, vrrTracker.nextAnticipatedVSyncTimeFrom(10000, 10000));
+ EXPECT_EQ(15500, vrrTracker.nextAnticipatedVSyncTimeFrom(12000, 12000));
+ EXPECT_EQ(19000, vrrTracker.nextAnticipatedVSyncTimeFrom(15500, 15500));
+
+ vrrTracker.setRenderRate(Fps::fromPeriodNsecs(2500), /*applyImmediately*/ false);
+ EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000));
+ EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
+ EXPECT_EQ(8000, vrrTracker.nextAnticipatedVSyncTimeFrom(6000, 6000));
+ EXPECT_EQ(10000, vrrTracker.nextAnticipatedVSyncTimeFrom(8000, 8000));
+ EXPECT_EQ(12000, vrrTracker.nextAnticipatedVSyncTimeFrom(10000, 10000));
+ EXPECT_EQ(15500, vrrTracker.nextAnticipatedVSyncTimeFrom(12000, 12000));
+ EXPECT_EQ(19000, vrrTracker.nextAnticipatedVSyncTimeFrom(15500, 15500));
+ EXPECT_EQ(21500, vrrTracker.nextAnticipatedVSyncTimeFrom(19000, 19000));
+
+ vrrTracker.setRenderRate(Fps::fromPeriodNsecs(1000), /*applyImmediately*/ false);
+ EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000));
+ EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
+ EXPECT_EQ(7000, vrrTracker.nextAnticipatedVSyncTimeFrom(6000, 6000));
+ EXPECT_EQ(9000, vrrTracker.nextAnticipatedVSyncTimeFrom(8000, 8000));
+ EXPECT_EQ(11000, vrrTracker.nextAnticipatedVSyncTimeFrom(10000, 10000));
+ EXPECT_EQ(13000, vrrTracker.nextAnticipatedVSyncTimeFrom(12000, 12000));
+ EXPECT_EQ(17000, vrrTracker.nextAnticipatedVSyncTimeFrom(15500, 15500));
+ EXPECT_EQ(20000, vrrTracker.nextAnticipatedVSyncTimeFrom(19000, 19000));
+}
+
+TEST_F(VSyncPredictorTest, minFramePeriodDoesntApplyWhenSameWithRefreshRate) {
+ SET_FLAG_FOR_TEST(flags::vrr_config, true);
+
+ const int32_t kGroup = 0;
+ const auto kResolution = ui::Size(1920, 1080);
+ const auto vsyncRate = Fps::fromPeriodNsecs(1000);
+ const auto minFrameRate = Fps::fromPeriodNsecs(1000);
+ hal::VrrConfig vrrConfig;
+ vrrConfig.minFrameIntervalNs = minFrameRate.getPeriodNsecs();
+ const ftl::NonNull<DisplayModePtr> kMode =
+ ftl::as_non_null(createDisplayModeBuilder(DisplayModeId(0), vsyncRate, kGroup,
+ kResolution, DEFAULT_DISPLAY_ID)
+ .setVrrConfig(std::move(vrrConfig))
+ .build());
+
+ VSyncPredictor vrrTracker{std::make_unique<ClockWrapper>(mClock), kMode, kHistorySize,
+ kMinimumSamplesForPrediction, kOutlierTolerancePercent};
+
+ vrrTracker.setRenderRate(Fps::fromPeriodNsecs(1000), /*applyImmediately*/ false);
+ vrrTracker.addVsyncTimestamp(0);
+ EXPECT_EQ(1000, vrrTracker.nextAnticipatedVSyncTimeFrom(700));
+ EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1000, 1000));
+
+ // Assume that the last vsync is wrong due to a vsync drift. It shouldn't matter.
+ EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1000, 1700));
+}
+
+TEST_F(VSyncPredictorTest, setRenderRateExplicitAppliedImmediately) {
+ SET_FLAG_FOR_TEST(flags::vrr_config, true);
+
+ const int32_t kGroup = 0;
+ const auto kResolution = ui::Size(1920, 1080);
+ const auto vsyncRate = Fps::fromPeriodNsecs(500);
+ const auto minFrameRate = Fps::fromPeriodNsecs(1000);
+ hal::VrrConfig vrrConfig;
+ vrrConfig.minFrameIntervalNs = minFrameRate.getPeriodNsecs();
+ const ftl::NonNull<DisplayModePtr> kMode =
+ ftl::as_non_null(createDisplayModeBuilder(DisplayModeId(0), vsyncRate, kGroup,
+ kResolution, DEFAULT_DISPLAY_ID)
+ .setVrrConfig(std::move(vrrConfig))
+ .build());
+
+ VSyncPredictor vrrTracker{std::make_unique<ClockWrapper>(mClock), kMode, kHistorySize,
+ kMinimumSamplesForPrediction, kOutlierTolerancePercent};
+
+ vrrTracker.setRenderRate(Fps::fromPeriodNsecs(1000), /*applyImmediately*/ false);
+ vrrTracker.addVsyncTimestamp(0);
+ EXPECT_EQ(1000, vrrTracker.nextAnticipatedVSyncTimeFrom(700));
+ EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1000, 1000));
+
+ // commit to a vsync in the future
+ EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 2000));
+
+ vrrTracker.setRenderRate(Fps::fromPeriodNsecs(2000), /*applyImmediately*/ true);
+ EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4000));
+ EXPECT_EQ(7000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
+ EXPECT_EQ(9000, vrrTracker.nextAnticipatedVSyncTimeFrom(7000, 7000));
+}
+
+TEST_F(VSyncPredictorTest, selectsClosestVsyncAfterInactivity) {
+ SET_FLAG_FOR_TEST(flags::vrr_config, true);
+
+ const int32_t kGroup = 0;
+ const auto kResolution = ui::Size(1920, 1080);
+ const auto vsyncRate = Fps::fromPeriodNsecs(500);
+ const auto minFrameRate = Fps::fromPeriodNsecs(1000);
+ hal::VrrConfig vrrConfig;
+ vrrConfig.minFrameIntervalNs = minFrameRate.getPeriodNsecs();
+ const ftl::NonNull<DisplayModePtr> kMode =
+ ftl::as_non_null(createDisplayModeBuilder(DisplayModeId(0), vsyncRate, kGroup,
+ kResolution, DEFAULT_DISPLAY_ID)
+ .setVrrConfig(std::move(vrrConfig))
+ .build());
+
+ VSyncPredictor vrrTracker{std::make_unique<ClockWrapper>(mClock), kMode, kHistorySize,
+ kMinimumSamplesForPrediction, kOutlierTolerancePercent};
+
+ vrrTracker.setRenderRate(Fps::fromPeriodNsecs(5000), /*applyImmediately*/ false);
+ vrrTracker.addVsyncTimestamp(0);
+ EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4700));
+ EXPECT_EQ(10000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
+
+ mClock->setNow(50000);
+ EXPECT_EQ(50500, vrrTracker.nextAnticipatedVSyncTimeFrom(50000, 10000));
+}
+
+TEST_F(VSyncPredictorTest, returnsCorrectVsyncWhenLastIsNot) {
+ SET_FLAG_FOR_TEST(flags::vrr_config, true);
+
+ const int32_t kGroup = 0;
+ const auto kResolution = ui::Size(1920, 1080);
+ const auto vsyncRate = Fps::fromPeriodNsecs(500);
+ const auto minFrameRate = Fps::fromPeriodNsecs(1000);
+ hal::VrrConfig vrrConfig;
+ vrrConfig.minFrameIntervalNs = minFrameRate.getPeriodNsecs();
+ const ftl::NonNull<DisplayModePtr> kMode =
+ ftl::as_non_null(createDisplayModeBuilder(DisplayModeId(0), vsyncRate, kGroup,
+ kResolution, DEFAULT_DISPLAY_ID)
+ .setVrrConfig(std::move(vrrConfig))
+ .build());
+
+ VSyncPredictor vrrTracker{std::make_unique<ClockWrapper>(mClock), kMode, kHistorySize,
+ kMinimumSamplesForPrediction, kOutlierTolerancePercent};
+
+ vrrTracker.setRenderRate(Fps::fromPeriodNsecs(1000), /*applyImmediately*/ false);
+ vrrTracker.addVsyncTimestamp(0);
+ EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1234, 1234));
+}
+
TEST_F(VSyncPredictorTest, adjustsVrrTimeline) {
SET_FLAG_FOR_TEST(flags::vrr_config, true);
@@ -670,10 +838,10 @@
.setVrrConfig(std::move(vrrConfig))
.build());
- VSyncPredictor vrrTracker{kMode, kHistorySize, kMinimumSamplesForPrediction,
- kOutlierTolerancePercent};
+ VSyncPredictor vrrTracker{std::make_unique<ClockWrapper>(mClock), kMode, kHistorySize,
+ kMinimumSamplesForPrediction, kOutlierTolerancePercent};
- vrrTracker.setRenderRate(minFrameRate);
+ vrrTracker.setRenderRate(minFrameRate, /*applyImmediately*/ false);
vrrTracker.addVsyncTimestamp(0);
EXPECT_EQ(1000, vrrTracker.nextAnticipatedVSyncTimeFrom(700));
EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1000));
@@ -687,7 +855,116 @@
vrrTracker.onFrameMissed(TimePoint::fromNs(4500));
EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4500, 4500));
EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
+
+ vrrTracker.onFrameBegin(TimePoint::fromNs(7000), TimePoint::fromNs(6500));
+ EXPECT_EQ(10500, vrrTracker.nextAnticipatedVSyncTimeFrom(9000, 7000));
}
+
+TEST_F(VSyncPredictorTest, adjustsVrrTimelineTwoClients) {
+ SET_FLAG_FOR_TEST(flags::vrr_config, true);
+
+ const int32_t kGroup = 0;
+ const auto kResolution = ui::Size(1920, 1080);
+ const auto refreshRate = Fps::fromPeriodNsecs(500);
+ const auto minFrameRate = Fps::fromPeriodNsecs(1000);
+ hal::VrrConfig vrrConfig;
+ vrrConfig.minFrameIntervalNs = minFrameRate.getPeriodNsecs();
+ const ftl::NonNull<DisplayModePtr> kMode =
+ ftl::as_non_null(createDisplayModeBuilder(DisplayModeId(0), refreshRate, kGroup,
+ kResolution, DEFAULT_DISPLAY_ID)
+ .setVrrConfig(std::move(vrrConfig))
+ .build());
+
+ VSyncPredictor vrrTracker{std::make_unique<ClockWrapper>(mClock), kMode, kHistorySize,
+ kMinimumSamplesForPrediction, kOutlierTolerancePercent};
+
+ vrrTracker.setRenderRate(minFrameRate, /*applyImmediately*/ false);
+ vrrTracker.addVsyncTimestamp(0);
+
+ // App runs ahead
+ EXPECT_EQ(3000, vrrTracker.nextAnticipatedVSyncTimeFrom(2700));
+ EXPECT_EQ(4000, vrrTracker.nextAnticipatedVSyncTimeFrom(3000, 3000));
+ EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000));
+
+ // SF starts to catch up
+ EXPECT_EQ(3000, vrrTracker.nextAnticipatedVSyncTimeFrom(2700));
+ vrrTracker.onFrameBegin(TimePoint::fromNs(3000), TimePoint::fromNs(0));
+
+ // SF misses last frame (3000) and observes that when committing (4000)
+ EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
+ EXPECT_EQ(4000, vrrTracker.nextAnticipatedVSyncTimeFrom(3700));
+ vrrTracker.onFrameMissed(TimePoint::fromNs(4000));
+
+ // SF wakes up again instead of the (4000) missed frame
+ EXPECT_EQ(4500, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000));
+ vrrTracker.onFrameBegin(TimePoint::fromNs(4500), TimePoint::fromNs(4500));
+
+ // Timeline shifted. The app needs to get the next frame at (7500) as its last frame (6500) will
+ // be presented at (7500)
+ EXPECT_EQ(7500, vrrTracker.nextAnticipatedVSyncTimeFrom(6000, 6000));
+ EXPECT_EQ(5500, vrrTracker.nextAnticipatedVSyncTimeFrom(4500, 4500));
+ vrrTracker.onFrameBegin(TimePoint::fromNs(5500), TimePoint::fromNs(4500));
+
+ EXPECT_EQ(8500, vrrTracker.nextAnticipatedVSyncTimeFrom(7500, 7500));
+ EXPECT_EQ(6500, vrrTracker.nextAnticipatedVSyncTimeFrom(5500, 5500));
+ vrrTracker.onFrameBegin(TimePoint::fromNs(6500), TimePoint::fromNs(5500));
+}
+
+TEST_F(VSyncPredictorTest, renderRateIsPreservedForCommittedVsyncs) {
+ tracker.addVsyncTimestamp(1000);
+
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(6000));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(7000));
+
+ tracker.setRenderRate(Fps::fromPeriodNsecs(2000), /*applyImmediately*/ false);
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(6000));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(7000));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(7001), Eq(9000));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(8001), Eq(9000));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(9001), Eq(11000));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(10001), Eq(11000));
+
+ tracker.setRenderRate(Fps::fromPeriodNsecs(3000), /*applyImmediately*/ false);
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(6000));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(7000));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(7001), Eq(9000));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(8001), Eq(9000));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(9001), Eq(11000));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(10001), Eq(11000));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(11001), Eq(14000));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(12001), Eq(14000));
+
+ // Check the purge logic works
+ mClock->setNow(20000);
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(2000));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(8000));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(8000));
+}
+
+// b/329310308
+TEST_F(VSyncPredictorTest, renderRateChangeAfterAppliedImmediately) {
+ tracker.addVsyncTimestamp(1000);
+
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1001), Eq(2000));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(2001), Eq(3000));
+
+ tracker.setRenderRate(Fps::fromPeriodNsecs(2000), /*applyImmediately*/ true);
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1001), Eq(3000));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(3001), Eq(5000));
+
+ tracker.setRenderRate(Fps::fromPeriodNsecs(4000), /*applyImmediately*/ false);
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1001), Eq(3000));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(3001), Eq(5000));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(9000));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(9001), Eq(13000));
+}
+
} // namespace android::scheduler
// TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
index 3870983..c311901 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
@@ -29,14 +29,14 @@
MOCK_METHOD(bool, addVsyncTimestamp, (nsecs_t), (override));
MOCK_METHOD(nsecs_t, nextAnticipatedVSyncTimeFrom, (nsecs_t, std::optional<nsecs_t>),
- (const, override));
+ (override));
MOCK_METHOD(nsecs_t, currentPeriod, (), (const, override));
MOCK_METHOD(Period, minFramePeriod, (), (const, override));
MOCK_METHOD(void, resetModel, (), (override));
MOCK_METHOD(bool, needsMoreSamples, (), (const, override));
- MOCK_METHOD(bool, isVSyncInPhase, (nsecs_t, Fps), (const, override));
+ MOCK_METHOD(bool, isVSyncInPhase, (nsecs_t, Fps), (override));
MOCK_METHOD(void, setDisplayModePtr, (ftl::NonNull<DisplayModePtr>), (override));
- MOCK_METHOD(void, setRenderRate, (Fps), (override));
+ MOCK_METHOD(void, setRenderRate, (Fps, bool), (override));
MOCK_METHOD(void, onFrameBegin, (TimePoint, TimePoint), (override));
MOCK_METHOD(void, onFrameMissed, (TimePoint), (override));
MOCK_METHOD(void, dump, (std::string&), (const, override));
diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp
index 1314193..74d3d9d 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -555,8 +555,7 @@
return native_format;
}
-DataSpace GetNativeDataspace(VkColorSpaceKHR colorspace,
- PixelFormat pixelFormat) {
+DataSpace GetNativeDataspace(VkColorSpaceKHR colorspace, VkFormat format) {
switch (colorspace) {
case VK_COLOR_SPACE_SRGB_NONLINEAR_KHR:
return DataSpace::SRGB;
@@ -575,7 +574,7 @@
case VK_COLOR_SPACE_BT709_NONLINEAR_EXT:
return DataSpace::SRGB;
case VK_COLOR_SPACE_BT2020_LINEAR_EXT:
- if (pixelFormat == PixelFormat::RGBA_FP16) {
+ if (format == VK_FORMAT_R16G16B16A16_SFLOAT) {
return DataSpace::BT2020_LINEAR_EXTENDED;
} else {
return DataSpace::BT2020_LINEAR;
@@ -764,21 +763,20 @@
{VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR},
};
+ VkFormat format = VK_FORMAT_UNDEFINED;
if (colorspace_ext) {
for (VkColorSpaceKHR colorSpace :
colorSpaceSupportedByVkEXTSwapchainColorspace) {
- if (GetNativeDataspace(colorSpace, GetNativePixelFormat(
- VK_FORMAT_R8G8B8A8_UNORM)) !=
- DataSpace::UNKNOWN) {
+ format = VK_FORMAT_R8G8B8A8_UNORM;
+ if (GetNativeDataspace(colorSpace, format) != DataSpace::UNKNOWN) {
all_formats.emplace_back(
- VkSurfaceFormatKHR{VK_FORMAT_R8G8B8A8_UNORM, colorSpace});
+ VkSurfaceFormatKHR{format, colorSpace});
}
- if (GetNativeDataspace(colorSpace, GetNativePixelFormat(
- VK_FORMAT_R8G8B8A8_SRGB)) !=
- DataSpace::UNKNOWN) {
+ format = VK_FORMAT_R8G8B8A8_SRGB;
+ if (GetNativeDataspace(colorSpace, format) != DataSpace::UNKNOWN) {
all_formats.emplace_back(
- VkSurfaceFormatKHR{VK_FORMAT_R8G8B8A8_SRGB, colorSpace});
+ VkSurfaceFormatKHR{format, colorSpace});
}
}
}
@@ -787,78 +785,73 @@
// Android users. This includes the ANGLE team (a layered implementation of
// OpenGL-ES).
+ format = VK_FORMAT_R5G6B5_UNORM_PACK16;
desc.format = AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM;
if (AHardwareBuffer_isSupported(&desc)) {
- all_formats.emplace_back(VkSurfaceFormatKHR{
- VK_FORMAT_R5G6B5_UNORM_PACK16, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
+ all_formats.emplace_back(
+ VkSurfaceFormatKHR{format, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
if (colorspace_ext) {
for (VkColorSpaceKHR colorSpace :
colorSpaceSupportedByVkEXTSwapchainColorspace) {
- if (GetNativeDataspace(
- colorSpace,
- GetNativePixelFormat(VK_FORMAT_R5G6B5_UNORM_PACK16)) !=
+ if (GetNativeDataspace(colorSpace, format) !=
DataSpace::UNKNOWN) {
- all_formats.emplace_back(VkSurfaceFormatKHR{
- VK_FORMAT_R5G6B5_UNORM_PACK16, colorSpace});
+ all_formats.emplace_back(
+ VkSurfaceFormatKHR{format, colorSpace});
}
}
}
}
+ format = VK_FORMAT_R16G16B16A16_SFLOAT;
desc.format = AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT;
if (AHardwareBuffer_isSupported(&desc)) {
- all_formats.emplace_back(VkSurfaceFormatKHR{
- VK_FORMAT_R16G16B16A16_SFLOAT, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
+ all_formats.emplace_back(
+ VkSurfaceFormatKHR{format, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
if (colorspace_ext) {
for (VkColorSpaceKHR colorSpace :
colorSpaceSupportedByVkEXTSwapchainColorspace) {
- if (GetNativeDataspace(
- colorSpace,
- GetNativePixelFormat(VK_FORMAT_R16G16B16A16_SFLOAT)) !=
+ if (GetNativeDataspace(colorSpace, format) !=
DataSpace::UNKNOWN) {
- all_formats.emplace_back(VkSurfaceFormatKHR{
- VK_FORMAT_R16G16B16A16_SFLOAT, colorSpace});
+ all_formats.emplace_back(
+ VkSurfaceFormatKHR{format, colorSpace});
}
}
for (
VkColorSpaceKHR colorSpace :
colorSpaceSupportedByVkEXTSwapchainColorspaceOnFP16SurfaceOnly) {
- if (GetNativeDataspace(
- colorSpace,
- GetNativePixelFormat(VK_FORMAT_R16G16B16A16_SFLOAT)) !=
+ if (GetNativeDataspace(colorSpace, format) !=
DataSpace::UNKNOWN) {
- all_formats.emplace_back(VkSurfaceFormatKHR{
- VK_FORMAT_R16G16B16A16_SFLOAT, colorSpace});
+ all_formats.emplace_back(
+ VkSurfaceFormatKHR{format, colorSpace});
}
}
}
}
+ format = VK_FORMAT_A2B10G10R10_UNORM_PACK32;
desc.format = AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM;
if (AHardwareBuffer_isSupported(&desc)) {
all_formats.emplace_back(
- VkSurfaceFormatKHR{VK_FORMAT_A2B10G10R10_UNORM_PACK32,
- VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
+ VkSurfaceFormatKHR{format, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
if (colorspace_ext) {
for (VkColorSpaceKHR colorSpace :
colorSpaceSupportedByVkEXTSwapchainColorspace) {
- if (GetNativeDataspace(
- colorSpace, GetNativePixelFormat(
- VK_FORMAT_A2B10G10R10_UNORM_PACK32)) !=
+ if (GetNativeDataspace(colorSpace, format) !=
DataSpace::UNKNOWN) {
- all_formats.emplace_back(VkSurfaceFormatKHR{
- VK_FORMAT_A2B10G10R10_UNORM_PACK32, colorSpace});
+ all_formats.emplace_back(
+ VkSurfaceFormatKHR{format, colorSpace});
}
}
}
}
+ format = VK_FORMAT_R8_UNORM;
desc.format = AHARDWAREBUFFER_FORMAT_R8_UNORM;
if (AHardwareBuffer_isSupported(&desc)) {
if (colorspace_ext) {
- all_formats.emplace_back(VkSurfaceFormatKHR{
- VK_FORMAT_R8_UNORM, VK_COLOR_SPACE_PASS_THROUGH_EXT});
+ all_formats.emplace_back(
+ VkSurfaceFormatKHR{format, VK_COLOR_SPACE_PASS_THROUGH_EXT});
}
}
@@ -877,22 +870,18 @@
rgba10x6_formats_ext = true;
}
}
+ format = VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16;
desc.format = AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM;
if (AHardwareBuffer_isSupported(&desc) && rgba10x6_formats_ext) {
all_formats.emplace_back(
- VkSurfaceFormatKHR{VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16,
- VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
+ VkSurfaceFormatKHR{format, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
if (colorspace_ext) {
for (VkColorSpaceKHR colorSpace :
colorSpaceSupportedByVkEXTSwapchainColorspace) {
- if (GetNativeDataspace(
- colorSpace,
- GetNativePixelFormat(
- VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16)) !=
+ if (GetNativeDataspace(colorSpace, format) !=
DataSpace::UNKNOWN) {
- all_formats.emplace_back(VkSurfaceFormatKHR{
- VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16,
- colorSpace});
+ all_formats.emplace_back(
+ VkSurfaceFormatKHR{format, colorSpace});
}
}
}
@@ -1670,8 +1659,8 @@
PixelFormat native_pixel_format =
GetNativePixelFormat(create_info->imageFormat);
- DataSpace native_dataspace =
- GetNativeDataspace(create_info->imageColorSpace, native_pixel_format);
+ DataSpace native_dataspace = GetNativeDataspace(
+ create_info->imageColorSpace, create_info->imageFormat);
if (native_dataspace == DataSpace::UNKNOWN) {
ALOGE(
"CreateSwapchainKHR(VkSwapchainCreateInfoKHR.imageColorSpace = %d) "