Merge "SF: Short-circuit late touch boost" into main
diff --git a/cmds/bugreport/OWNERS b/cmds/bugreport/OWNERS
index 5f56531..41bfd26 100644
--- a/cmds/bugreport/OWNERS
+++ b/cmds/bugreport/OWNERS
@@ -1,5 +1,5 @@
set noparent
-gavincorkery@google.com
+ronish@google.com
nandana@google.com
jsharkey@android.com
diff --git a/cmds/bugreportz/OWNERS b/cmds/bugreportz/OWNERS
index 5f56531..41bfd26 100644
--- a/cmds/bugreportz/OWNERS
+++ b/cmds/bugreportz/OWNERS
@@ -1,5 +1,5 @@
set noparent
-gavincorkery@google.com
+ronish@google.com
nandana@google.com
jsharkey@android.com
diff --git a/cmds/dumpstate/OWNERS b/cmds/dumpstate/OWNERS
index ab81ecf..c24bf39 100644
--- a/cmds/dumpstate/OWNERS
+++ b/cmds/dumpstate/OWNERS
@@ -1,6 +1,6 @@
set noparent
-gavincorkery@google.com
+ronish@google.com
nandana@google.com
jsharkey@android.com
smoreland@google.com
\ No newline at end of file
diff --git a/cmds/dumpsys/OWNERS b/cmds/dumpsys/OWNERS
index 97a63ca..03143ae 100644
--- a/cmds/dumpsys/OWNERS
+++ b/cmds/dumpsys/OWNERS
@@ -1,6 +1,6 @@
set noparent
-gavincorkery@google.com
+ronish@google.com
nandana@google.com
jsharkey@android.com
diff --git a/cmds/lshal/ListCommand.cpp b/cmds/lshal/ListCommand.cpp
index 870e8eb..0c1feb8 100644
--- a/cmds/lshal/ListCommand.cpp
+++ b/cmds/lshal/ListCommand.cpp
@@ -731,16 +731,17 @@
[hashChain](const auto& ret) { *hashChain = std::move(ret); });
if (!hashRet.isOk()) {
handleError(TRANSACTION_ERROR, "getHashChain failed: " + hashRet.description());
+ break; // skip getHashChain
}
if (static_cast<size_t>(hashIndex) >= hashChain->size()) {
handleError(BAD_IMPL,
"interfaceChain indicates position " + std::to_string(hashIndex) +
" but getHashChain returns " + std::to_string(hashChain->size()) +
" hashes");
- } else {
- auto&& hashArray = (*hashChain)[hashIndex];
- entry->hash = android::base::HexString(hashArray.data(), hashArray.size());
+ break; // skip getHashChain
}
+ auto&& hashArray = (*hashChain)[hashIndex];
+ entry->hash = android::base::HexString(hashArray.data(), hashArray.size());
} while (0);
if (status == OK) {
entry->serviceStatus = ServiceStatus::ALIVE;
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index da232a5..99f4fbd 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -335,6 +335,12 @@
}
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"],
diff --git a/include/android/OWNERS b/include/android/OWNERS
index 38f9c55..fad8c1b 100644
--- a/include/android/OWNERS
+++ b/include/android/OWNERS
@@ -1 +1,5 @@
-per-file input.h, keycodes.h = file:platform/frameworks/base:/INPUT_OWNERS
+per-file input.h,keycodes.h = file:platform/frameworks/base:/INPUT_OWNERS
+
+# Window manager
+per-file surface_control_input_receiver.h = file:platform/frameworks/base:/services/core/java/com/android/server/wm/OWNERS
+per-file input_transfer_token.h = file:platform/frameworks/base:/services/core/java/com/android/server/wm/OWNERS
diff --git a/include/android/input_transfer_token_jni.h b/include/android/input_transfer_token_jni.h
index ba5f6f2..92fe9b6 100644
--- a/include/android/input_transfer_token_jni.h
+++ b/include/android/input_transfer_token_jni.h
@@ -20,8 +20,9 @@
/**
* @file input_transfer_token_jni.h
*/
-#ifndef ANDROID_INPUT_TRANSFER_TOKEN_JNI_H
-#define ANDROID_INPUT_TRANSFER_TOKEN_JNI_H
+
+#pragma once
+
#include <sys/cdefs.h>
#include <jni.h>
@@ -60,9 +61,8 @@
*
* Available since API level 35.
*/
-void AInputTransferToken_release(AInputTransferToken* _Nonnull aInputTransferToken)
+void AInputTransferToken_release(AInputTransferToken* _Nullable 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 082387e..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)
diff --git a/include/android/surface_control_input_receiver.h b/include/android/surface_control_input_receiver.h
new file mode 100644
index 0000000..bdc5249
--- /dev/null
+++ b/include/android/surface_control_input_receiver.h
@@ -0,0 +1,202 @@
+/*
+ * 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.
+ */
+/**
+ * @addtogroup NativeActivity Native Activity
+ * @{
+ */
+/**
+ * @file surface_control_input_receiver.h
+ */
+
+#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 clean 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 *_Nullable 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* _Nullable 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/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 aca4b62..5f9c8f5 100644
--- a/include/input/InputTransport.h
+++ b/include/input/InputTransport.h
@@ -451,229 +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 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/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/ibinder.cpp b/libs/binder/ndk/ibinder.cpp
index bf7a0ba..e2ede3f 100644
--- a/libs/binder/ndk/ibinder.cpp
+++ b/libs/binder/ndk/ibinder.cpp
@@ -258,11 +258,24 @@
}
}
+void ABBinder::addDeathRecipient(const ::android::sp<AIBinder_DeathRecipient>& /* recipient */,
+ void* /* cookie */) {
+ LOG_ALWAYS_FATAL("Should not reach this. Can't linkToDeath local binders.");
+}
+
ABpBinder::ABpBinder(const ::android::sp<::android::IBinder>& binder)
: AIBinder(nullptr /*clazz*/), mRemote(binder) {
LOG_ALWAYS_FATAL_IF(binder == nullptr, "binder == nullptr");
}
-ABpBinder::~ABpBinder() {}
+
+ABpBinder::~ABpBinder() {
+ for (auto& recip : mDeathRecipients) {
+ sp<AIBinder_DeathRecipient> strongRecip = recip.recipient.promote();
+ if (strongRecip) {
+ strongRecip->pruneThisTransferEntry(getBinder(), recip.cookie);
+ }
+ }
+}
sp<AIBinder> ABpBinder::lookupOrCreateFromBinder(const ::android::sp<::android::IBinder>& binder) {
if (binder == nullptr) {
@@ -301,6 +314,12 @@
return ret;
}
+void ABpBinder::addDeathRecipient(const ::android::sp<AIBinder_DeathRecipient>& recipient,
+ void* cookie) {
+ std::lock_guard<std::mutex> l(mDeathRecipientsMutex);
+ mDeathRecipients.emplace_back(recipient, cookie);
+}
+
struct AIBinder_Weak {
wp<AIBinder> binder;
};
@@ -426,6 +445,17 @@
LOG_ALWAYS_FATAL_IF(onDied == nullptr, "onDied == nullptr");
}
+void AIBinder_DeathRecipient::pruneThisTransferEntry(const sp<IBinder>& who, void* cookie) {
+ std::lock_guard<std::mutex> l(mDeathRecipientsMutex);
+ mDeathRecipients.erase(std::remove_if(mDeathRecipients.begin(), mDeathRecipients.end(),
+ [&](const sp<TransferDeathRecipient>& tdr) {
+ auto tdrWho = tdr->getWho();
+ return tdrWho != nullptr && tdrWho.promote() == who &&
+ cookie == tdr->getCookie();
+ }),
+ mDeathRecipients.end());
+}
+
void AIBinder_DeathRecipient::pruneDeadTransferEntriesLocked() {
mDeathRecipients.erase(std::remove_if(mDeathRecipients.begin(), mDeathRecipients.end(),
[](const sp<TransferDeathRecipient>& tdr) {
@@ -554,8 +584,11 @@
return STATUS_UNEXPECTED_NULL;
}
- // returns binder_status_t
- return recipient->linkToDeath(binder->getBinder(), cookie);
+ binder_status_t ret = recipient->linkToDeath(binder->getBinder(), cookie);
+ if (ret == STATUS_OK) {
+ binder->addDeathRecipient(recipient, cookie);
+ }
+ return ret;
}
binder_status_t AIBinder_unlinkToDeath(AIBinder* binder, AIBinder_DeathRecipient* recipient,
diff --git a/libs/binder/ndk/ibinder_internal.h b/libs/binder/ndk/ibinder_internal.h
index 9d5368f..f5b738c 100644
--- a/libs/binder/ndk/ibinder_internal.h
+++ b/libs/binder/ndk/ibinder_internal.h
@@ -51,6 +51,8 @@
::android::sp<::android::IBinder> binder = const_cast<AIBinder*>(this)->getBinder();
return binder->remoteBinder() != nullptr;
}
+ virtual void addDeathRecipient(const ::android::sp<AIBinder_DeathRecipient>& recipient,
+ void* cookie) = 0;
private:
// AIBinder instance is instance of this class for a local object. In order to transact on a
@@ -78,6 +80,8 @@
::android::status_t dump(int fd, const ::android::Vector<::android::String16>& args) override;
::android::status_t onTransact(uint32_t code, const ::android::Parcel& data,
::android::Parcel* reply, binder_flags_t flags) override;
+ void addDeathRecipient(const ::android::sp<AIBinder_DeathRecipient>& /* recipient */,
+ void* /* cookie */) override;
private:
ABBinder(const AIBinder_Class* clazz, void* userData);
@@ -106,12 +110,20 @@
bool isServiceFuzzing() const { return mServiceFuzzing; }
void setServiceFuzzing() { mServiceFuzzing = true; }
+ void addDeathRecipient(const ::android::sp<AIBinder_DeathRecipient>& recipient,
+ void* cookie) override;
private:
friend android::sp<ABpBinder>;
explicit ABpBinder(const ::android::sp<::android::IBinder>& binder);
::android::sp<::android::IBinder> mRemote;
bool mServiceFuzzing = false;
+ struct DeathRecipientInfo {
+ android::wp<AIBinder_DeathRecipient> recipient;
+ void* cookie;
+ };
+ std::mutex mDeathRecipientsMutex;
+ std::vector<DeathRecipientInfo> mDeathRecipients;
};
struct AIBinder_Class {
@@ -183,6 +195,7 @@
binder_status_t linkToDeath(const ::android::sp<::android::IBinder>&, void* cookie);
binder_status_t unlinkToDeath(const ::android::sp<::android::IBinder>& binder, void* cookie);
void setOnUnlinked(AIBinder_DeathRecipient_onBinderUnlinked onUnlinked);
+ void pruneThisTransferEntry(const ::android::sp<::android::IBinder>&, void* cookie);
private:
// When the user of this API deletes a Bp object but not the death recipient, the
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..d570eab 100644
--- a/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h
+++ b/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h
@@ -22,6 +22,19 @@
#include <set>
#include <sstream>
+// Include llndk-versioning.h only for vendor build as it is not available for NDK headers.
+#if defined(__ANDROID_VENDOR__)
+#include <android/llndk-versioning.h>
+#else // __ANDROID_VENDOR__
+#if defined(API_LEVEL_AT_LEAST)
+// Redefine API_LEVEL_AT_LEAST here to replace the version to __ANDROID_API_FUTURE__ as a workaround
+#undef API_LEVEL_AT_LEAST
+#endif
+// TODO(b/322384429) switch this __ANDROID_API_FUTURE__ to sdk_api_level when V is finalized
+#define API_LEVEL_AT_LEAST(sdk_api_level, vendor_api_level) \
+ (__builtin_available(android __ANDROID_API_FUTURE__, *))
+#endif // __ANDROID_VENDOR__
+
namespace aidl::android::os {
/**
@@ -32,7 +45,7 @@
class PersistableBundle {
public:
PersistableBundle() noexcept {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
mPBundle = APersistableBundle_new();
}
}
@@ -42,13 +55,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 API_LEVEL_AT_LEAST(__ANDROID_API_V__, 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 API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
mPBundle = APersistableBundle_dup(other.mPBundle);
}
return *this;
@@ -58,7 +71,7 @@
binder_status_t readFromParcel(const AParcel* _Nonnull parcel) {
reset();
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
return APersistableBundle_readFromParcel(parcel, &mPBundle);
} else {
return STATUS_INVALID_OPERATION;
@@ -69,7 +82,7 @@
if (!mPBundle) {
return STATUS_BAD_VALUE;
}
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
return APersistableBundle_writeToParcel(mPBundle, parcel);
} else {
return STATUS_INVALID_OPERATION;
@@ -84,7 +97,7 @@
*/
void reset(APersistableBundle* _Nullable pBundle = nullptr) noexcept {
if (mPBundle) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
APersistableBundle_delete(mPBundle);
}
mPBundle = nullptr;
@@ -97,7 +110,7 @@
* what should be used to check for equality.
*/
bool deepEquals(const PersistableBundle& rhs) const {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
return APersistableBundle_isEqual(get(), rhs.get());
} else {
return false;
@@ -136,7 +149,7 @@
inline std::string toString() const {
if (!mPBundle) {
return "<PersistableBundle: null>";
- } else if (__builtin_available(android __ANDROID_API_V__, *)) {
+ } else if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
std::ostringstream os;
os << "<PersistableBundle: ";
os << "size: " << std::to_string(APersistableBundle_size(mPBundle));
@@ -147,7 +160,7 @@
}
int32_t size() const {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
return APersistableBundle_size(mPBundle);
} else {
return 0;
@@ -155,7 +168,7 @@
}
int32_t erase(const std::string& key) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
return APersistableBundle_erase(mPBundle, key.c_str());
} else {
return 0;
@@ -163,37 +176,37 @@
}
void putBoolean(const std::string& key, bool val) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 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 API_LEVEL_AT_LEAST(__ANDROID_API_V__, 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 API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
APersistableBundle_putLong(mPBundle, key.c_str(), val);
}
}
void putDouble(const std::string& key, double val) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 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 API_LEVEL_AT_LEAST(__ANDROID_API_V__, 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 API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
// std::vector<bool> has no ::data().
int32_t num = vec.size();
if (num > 0) {
@@ -210,7 +223,7 @@
}
void putIntVector(const std::string& key, const std::vector<int32_t>& vec) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
int32_t num = vec.size();
if (num > 0) {
APersistableBundle_putIntVector(mPBundle, key.c_str(), vec.data(), num);
@@ -218,7 +231,7 @@
}
}
void putLongVector(const std::string& key, const std::vector<int64_t>& vec) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
int32_t num = vec.size();
if (num > 0) {
APersistableBundle_putLongVector(mPBundle, key.c_str(), vec.data(), num);
@@ -226,7 +239,7 @@
}
}
void putDoubleVector(const std::string& key, const std::vector<double>& vec) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
int32_t num = vec.size();
if (num > 0) {
APersistableBundle_putDoubleVector(mPBundle, key.c_str(), vec.data(), num);
@@ -234,7 +247,7 @@
}
}
void putStringVector(const std::string& key, const std::vector<std::string>& vec) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
int32_t num = vec.size();
if (num > 0) {
char** inVec = (char**)malloc(num * sizeof(char*));
@@ -249,13 +262,13 @@
}
}
void putPersistableBundle(const std::string& key, const PersistableBundle& pBundle) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 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 API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
return APersistableBundle_getBoolean(mPBundle, key.c_str(), val);
} else {
return false;
@@ -263,7 +276,7 @@
}
bool getInt(const std::string& key, int32_t* _Nonnull val) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
return APersistableBundle_getInt(mPBundle, key.c_str(), val);
} else {
return false;
@@ -271,7 +284,7 @@
}
bool getLong(const std::string& key, int64_t* _Nonnull val) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
return APersistableBundle_getLong(mPBundle, key.c_str(), val);
} else {
return false;
@@ -279,7 +292,7 @@
}
bool getDouble(const std::string& key, double* _Nonnull val) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
return APersistableBundle_getDouble(mPBundle, key.c_str(), val);
} else {
return false;
@@ -291,7 +304,7 @@
}
bool getString(const std::string& key, std::string* _Nonnull val) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
char* outString = nullptr;
bool ret = APersistableBundle_getString(mPBundle, key.c_str(), &outString,
&stringAllocator, nullptr);
@@ -309,7 +322,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 API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
int32_t bytes = 0;
// call first with nullptr to get required size in bytes
bytes = getVec(pBundle, key, nullptr, 0);
@@ -331,28 +344,28 @@
}
bool getBooleanVector(const std::string& key, std::vector<bool>* _Nonnull vec) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 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 API_LEVEL_AT_LEAST(__ANDROID_API_V__, 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 API_LEVEL_AT_LEAST(__ANDROID_API_V__, 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 API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
return getVecInternal<double>(&APersistableBundle_getDoubleVector, mPBundle,
key.c_str(), vec);
}
@@ -377,7 +390,7 @@
}
bool getStringVector(const std::string& key, std::vector<std::string>* _Nonnull vec) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
int32_t bytes = APersistableBundle_getStringVector(mPBundle, key.c_str(), nullptr, 0,
&stringAllocator, nullptr);
if (bytes > 0) {
@@ -394,7 +407,7 @@
}
bool getPersistableBundle(const std::string& key, PersistableBundle* _Nonnull val) {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
APersistableBundle* bundle = nullptr;
bool ret = APersistableBundle_getPersistableBundle(mPBundle, key.c_str(), &bundle);
if (ret) {
@@ -426,77 +439,77 @@
}
std::set<std::string> getBooleanKeys() {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
return getKeys(&APersistableBundle_getBooleanKeys, mPBundle);
} else {
return {};
}
}
std::set<std::string> getIntKeys() {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
return getKeys(&APersistableBundle_getIntKeys, mPBundle);
} else {
return {};
}
}
std::set<std::string> getLongKeys() {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
return getKeys(&APersistableBundle_getLongKeys, mPBundle);
} else {
return {};
}
}
std::set<std::string> getDoubleKeys() {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
return getKeys(&APersistableBundle_getDoubleKeys, mPBundle);
} else {
return {};
}
}
std::set<std::string> getStringKeys() {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
return getKeys(&APersistableBundle_getStringKeys, mPBundle);
} else {
return {};
}
}
std::set<std::string> getBooleanVectorKeys() {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
return getKeys(&APersistableBundle_getBooleanVectorKeys, mPBundle);
} else {
return {};
}
}
std::set<std::string> getIntVectorKeys() {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
return getKeys(&APersistableBundle_getIntVectorKeys, mPBundle);
} else {
return {};
}
}
std::set<std::string> getLongVectorKeys() {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
return getKeys(&APersistableBundle_getLongVectorKeys, mPBundle);
} else {
return {};
}
}
std::set<std::string> getDoubleVectorKeys() {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
return getKeys(&APersistableBundle_getDoubleVectorKeys, mPBundle);
} else {
return {};
}
}
std::set<std::string> getStringVectorKeys() {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
return getKeys(&APersistableBundle_getStringVectorKeys, mPBundle);
} else {
return {};
}
}
std::set<std::string> getPersistableBundleKeys() {
- if (__builtin_available(android __ANDROID_API_V__, *)) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 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..42ae15a 100644
--- a/libs/binder/ndk/include_ndk/android/persistable_bundle.h
+++ b/libs/binder/ndk/include_ndk/android/persistable_bundle.h
@@ -17,6 +17,13 @@
#pragma once
#include <android/binder_parcel.h>
+#if defined(__ANDROID_VENDOR__)
+#include <android/llndk-versioning.h>
+#else
+#if !defined(__INTRODUCED_IN_LLNDK)
+#define __INTRODUCED_IN_LLNDK(level) __attribute__((annotate("introduced_in_llndk=" #level)))
+#endif
+#endif // __ANDROID_VENDOR__
#include <stdbool.h>
#include <stdint.h>
#include <sys/cdefs.h>
@@ -67,25 +74,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 +101,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 +118,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 +137,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 +157,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 +183,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 +193,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 +207,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 +221,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 +235,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 +250,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 +266,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 +283,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 +299,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 +316,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 +333,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 +349,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 +369,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 +383,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 +399,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 +414,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 +435,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 +463,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 +489,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 +516,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 +544,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 +581,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 +600,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 +633,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 +664,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 +696,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 +729,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 +762,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 +796,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 +829,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 +862,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 +894,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 +927,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 +958,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/include_platform/android/binder_manager.h b/libs/binder/ndk/include_platform/android/binder_manager.h
index a905dff..52edae4 100644
--- a/libs/binder/ndk/include_platform/android/binder_manager.h
+++ b/libs/binder/ndk/include_platform/android/binder_manager.h
@@ -18,6 +18,7 @@
#include <android/binder_ibinder.h>
#include <android/binder_status.h>
+#include <android/llndk-versioning.h>
#include <sys/cdefs.h>
__BEGIN_DECLS
@@ -252,9 +253,8 @@
* \return the result of dlopen of the specified HAL
*/
void* AServiceManager_openDeclaredPassthroughHal(const char* interface, const char* instance,
- int flag)
- // TODO(b/302113279) use __INTRODUCED_LLNDK for vendor variants
- __INTRODUCED_IN(__ANDROID_API_V__);
+ int flag) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
/**
* Prevent lazy services without client from shutting down their process
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/binder/ndk/tests/iface.cpp b/libs/binder/ndk/tests/iface.cpp
index 3ee36cd..ca92727 100644
--- a/libs/binder/ndk/tests/iface.cpp
+++ b/libs/binder/ndk/tests/iface.cpp
@@ -25,6 +25,7 @@
const char* IFoo::kSomeInstanceName = "libbinder_ndk-test-IFoo";
const char* IFoo::kInstanceNameToDieFor = "libbinder_ndk-test-IFoo-to-die";
+const char* IFoo::kInstanceNameToDieFor2 = "libbinder_ndk-test-IFoo-to-die2";
const char* IFoo::kIFooDescriptor = "my-special-IFoo-class";
struct IFoo_Class_Data {
diff --git a/libs/binder/ndk/tests/include/iface/iface.h b/libs/binder/ndk/tests/include/iface/iface.h
index 0a562f0..0cdd50b 100644
--- a/libs/binder/ndk/tests/include/iface/iface.h
+++ b/libs/binder/ndk/tests/include/iface/iface.h
@@ -27,6 +27,7 @@
public:
static const char* kSomeInstanceName;
static const char* kInstanceNameToDieFor;
+ static const char* kInstanceNameToDieFor2;
static const char* kIFooDescriptor;
static AIBinder_Class* kClass;
diff --git a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
index 966ec95..ce63b82 100644
--- a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
+++ b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
@@ -536,6 +536,7 @@
bool deathReceived = false;
std::function<void(void)> onDeath = [&] {
+ std::unique_lock<std::mutex> lockDeath(deathMutex);
std::cerr << "Binder died (as requested)." << std::endl;
deathReceived = true;
deathCv.notify_one();
@@ -547,6 +548,7 @@
bool wasDeathReceivedFirst = false;
std::function<void(void)> onUnlink = [&] {
+ std::unique_lock<std::mutex> lockUnlink(unlinkMutex);
std::cerr << "Binder unlinked (as requested)." << std::endl;
wasDeathReceivedFirst = deathReceived;
unlinkReceived = true;
@@ -560,7 +562,6 @@
EXPECT_EQ(STATUS_OK, AIBinder_linkToDeath(binder, recipient, static_cast<void*>(cookie)));
- // the binder driver should return this if the service dies during the transaction
EXPECT_EQ(STATUS_DEAD_OBJECT, foo->die());
foo = nullptr;
@@ -579,6 +580,123 @@
binder = nullptr;
}
+TEST(NdkBinder, DeathRecipientDropBinderNoDeath) {
+ using namespace std::chrono_literals;
+
+ std::mutex deathMutex;
+ std::condition_variable deathCv;
+ bool deathReceived = false;
+
+ std::function<void(void)> onDeath = [&] {
+ std::unique_lock<std::mutex> lockDeath(deathMutex);
+ std::cerr << "Binder died (as requested)." << std::endl;
+ deathReceived = true;
+ deathCv.notify_one();
+ };
+
+ std::mutex unlinkMutex;
+ std::condition_variable unlinkCv;
+ bool unlinkReceived = false;
+ bool wasDeathReceivedFirst = false;
+
+ std::function<void(void)> onUnlink = [&] {
+ std::unique_lock<std::mutex> lockUnlink(unlinkMutex);
+ std::cerr << "Binder unlinked (as requested)." << std::endl;
+ wasDeathReceivedFirst = deathReceived;
+ unlinkReceived = true;
+ unlinkCv.notify_one();
+ };
+
+ // keep the death recipient around
+ ndk::ScopedAIBinder_DeathRecipient recipient(AIBinder_DeathRecipient_new(LambdaOnDeath));
+ AIBinder_DeathRecipient_setOnUnlinked(recipient.get(), LambdaOnUnlink);
+
+ {
+ AIBinder* binder;
+ sp<IFoo> foo = IFoo::getService(IFoo::kInstanceNameToDieFor2, &binder);
+ ASSERT_NE(nullptr, foo.get());
+ ASSERT_NE(nullptr, binder);
+
+ DeathRecipientCookie* cookie = new DeathRecipientCookie{&onDeath, &onUnlink};
+
+ EXPECT_EQ(STATUS_OK,
+ AIBinder_linkToDeath(binder, recipient.get(), static_cast<void*>(cookie)));
+ // let the sp<IFoo> and AIBinder fall out of scope
+ AIBinder_decStrong(binder);
+ binder = nullptr;
+ }
+
+ {
+ std::unique_lock<std::mutex> lockDeath(deathMutex);
+ EXPECT_FALSE(deathCv.wait_for(lockDeath, 100ms, [&] { return deathReceived; }));
+ EXPECT_FALSE(deathReceived);
+ }
+
+ {
+ std::unique_lock<std::mutex> lockUnlink(unlinkMutex);
+ EXPECT_TRUE(deathCv.wait_for(lockUnlink, 1s, [&] { return unlinkReceived; }));
+ EXPECT_TRUE(unlinkReceived);
+ EXPECT_FALSE(wasDeathReceivedFirst);
+ }
+}
+
+TEST(NdkBinder, DeathRecipientDropBinderOnDied) {
+ using namespace std::chrono_literals;
+
+ std::mutex deathMutex;
+ std::condition_variable deathCv;
+ bool deathReceived = false;
+
+ sp<IFoo> foo;
+ AIBinder* binder;
+ std::function<void(void)> onDeath = [&] {
+ std::unique_lock<std::mutex> lockDeath(deathMutex);
+ std::cerr << "Binder died (as requested)." << std::endl;
+ deathReceived = true;
+ AIBinder_decStrong(binder);
+ binder = nullptr;
+ deathCv.notify_one();
+ };
+
+ std::mutex unlinkMutex;
+ std::condition_variable unlinkCv;
+ bool unlinkReceived = false;
+ bool wasDeathReceivedFirst = false;
+
+ std::function<void(void)> onUnlink = [&] {
+ std::unique_lock<std::mutex> lockUnlink(unlinkMutex);
+ std::cerr << "Binder unlinked (as requested)." << std::endl;
+ wasDeathReceivedFirst = deathReceived;
+ unlinkReceived = true;
+ unlinkCv.notify_one();
+ };
+
+ ndk::ScopedAIBinder_DeathRecipient recipient(AIBinder_DeathRecipient_new(LambdaOnDeath));
+ AIBinder_DeathRecipient_setOnUnlinked(recipient.get(), LambdaOnUnlink);
+
+ foo = IFoo::getService(IFoo::kInstanceNameToDieFor2, &binder);
+ ASSERT_NE(nullptr, foo.get());
+ ASSERT_NE(nullptr, binder);
+
+ DeathRecipientCookie* cookie = new DeathRecipientCookie{&onDeath, &onUnlink};
+ EXPECT_EQ(STATUS_OK, AIBinder_linkToDeath(binder, recipient.get(), static_cast<void*>(cookie)));
+
+ EXPECT_EQ(STATUS_DEAD_OBJECT, foo->die());
+
+ {
+ std::unique_lock<std::mutex> lockDeath(deathMutex);
+ EXPECT_TRUE(deathCv.wait_for(lockDeath, 1s, [&] { return deathReceived; }));
+ EXPECT_TRUE(deathReceived);
+ }
+
+ {
+ std::unique_lock<std::mutex> lockUnlink(unlinkMutex);
+ EXPECT_TRUE(deathCv.wait_for(lockUnlink, 100ms, [&] { return unlinkReceived; }));
+ EXPECT_TRUE(unlinkReceived);
+ EXPECT_TRUE(wasDeathReceivedFirst);
+ }
+}
+
TEST(NdkBinder, RetrieveNonNdkService) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@@ -958,6 +1076,10 @@
}
if (fork() == 0) {
prctl(PR_SET_PDEATHSIG, SIGHUP);
+ return manualThreadPoolService(IFoo::kInstanceNameToDieFor2);
+ }
+ if (fork() == 0) {
+ prctl(PR_SET_PDEATHSIG, SIGHUP);
return manualPollingService(IFoo::kSomeInstanceName);
}
if (fork() == 0) {
diff --git a/libs/gui/Choreographer.cpp b/libs/gui/Choreographer.cpp
index 54290cd..0c8f3fa 100644
--- a/libs/gui/Choreographer.cpp
+++ b/libs/gui/Choreographer.cpp
@@ -425,4 +425,8 @@
return iter->second;
}
+const sp<Looper> Choreographer::getLooper() {
+ return mLooper;
+}
+
} // namespace android
diff --git a/libs/gui/LayerMetadata.cpp b/libs/gui/LayerMetadata.cpp
index 4e12fd3..535a021 100644
--- a/libs/gui/LayerMetadata.cpp
+++ b/libs/gui/LayerMetadata.cpp
@@ -100,27 +100,31 @@
int32_t LayerMetadata::getInt32(uint32_t key, int32_t fallback) const {
if (!has(key)) return fallback;
const std::vector<uint8_t>& data = mMap.at(key);
- if (data.size() < sizeof(uint32_t)) return fallback;
- Parcel p;
- p.setData(data.data(), data.size());
- return p.readInt32();
+
+ // TODO: should handle when not equal?
+ if (data.size() < sizeof(int32_t)) return fallback;
+
+ int32_t result;
+ memcpy(&result, data.data(), sizeof(result));
+ return result;
}
void LayerMetadata::setInt32(uint32_t key, int32_t value) {
std::vector<uint8_t>& data = mMap[key];
- Parcel p;
- p.writeInt32(value);
- data.resize(p.dataSize());
- memcpy(data.data(), p.data(), p.dataSize());
+ data.resize(sizeof(value));
+ memcpy(data.data(), &value, sizeof(value));
}
std::optional<int64_t> LayerMetadata::getInt64(uint32_t key) const {
if (!has(key)) return std::nullopt;
const std::vector<uint8_t>& data = mMap.at(key);
+
+ // TODO: should handle when not equal?
if (data.size() < sizeof(int64_t)) return std::nullopt;
- Parcel p;
- p.setData(data.data(), data.size());
- return p.readInt64();
+
+ int64_t result;
+ memcpy(&result, data.data(), sizeof(result));
+ return result;
}
std::string LayerMetadata::itemToString(uint32_t key, const char* separator) const {
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 1e0aacd..0a28799 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -90,7 +90,6 @@
fixedTransformHint(ui::Transform::ROT_INVALID),
autoRefresh(false),
isTrustedOverlay(false),
- borderEnabled(false),
bufferCrop(Rect::INVALID_RECT),
destinationFrame(Rect::INVALID_RECT),
dropInputMode(gui::DropInputMode::NONE) {
@@ -122,12 +121,6 @@
SAFE_PARCEL(output.write, transparentRegion);
SAFE_PARCEL(output.writeUint32, bufferTransform);
SAFE_PARCEL(output.writeBool, transformToDisplayInverse);
- SAFE_PARCEL(output.writeBool, borderEnabled);
- SAFE_PARCEL(output.writeFloat, borderWidth);
- SAFE_PARCEL(output.writeFloat, borderColor.r);
- SAFE_PARCEL(output.writeFloat, borderColor.g);
- SAFE_PARCEL(output.writeFloat, borderColor.b);
- SAFE_PARCEL(output.writeFloat, borderColor.a);
SAFE_PARCEL(output.writeUint32, static_cast<uint32_t>(dataspace));
SAFE_PARCEL(output.write, hdrMetadata);
SAFE_PARCEL(output.write, surfaceDamageRegion);
@@ -238,17 +231,6 @@
SAFE_PARCEL(input.read, transparentRegion);
SAFE_PARCEL(input.readUint32, &bufferTransform);
SAFE_PARCEL(input.readBool, &transformToDisplayInverse);
- SAFE_PARCEL(input.readBool, &borderEnabled);
- SAFE_PARCEL(input.readFloat, &tmpFloat);
- borderWidth = tmpFloat;
- SAFE_PARCEL(input.readFloat, &tmpFloat);
- borderColor.r = tmpFloat;
- SAFE_PARCEL(input.readFloat, &tmpFloat);
- borderColor.g = tmpFloat;
- SAFE_PARCEL(input.readFloat, &tmpFloat);
- borderColor.b = tmpFloat;
- SAFE_PARCEL(input.readFloat, &tmpFloat);
- borderColor.a = tmpFloat;
uint32_t tmpUint32 = 0;
SAFE_PARCEL(input.readUint32, &tmpUint32);
@@ -659,12 +641,6 @@
what |= eShadowRadiusChanged;
shadowRadius = other.shadowRadius;
}
- if (other.what & eRenderBorderChanged) {
- what |= eRenderBorderChanged;
- borderEnabled = other.borderEnabled;
- borderWidth = other.borderWidth;
- borderColor = other.borderColor;
- }
if (other.what & eDefaultFrameRateCompatibilityChanged) {
what |= eDefaultFrameRateCompatibilityChanged;
defaultFrameRateCompatibility = other.defaultFrameRateCompatibility;
@@ -794,7 +770,6 @@
CHECK_DIFF2(diff, eBackgroundColorChanged, other, bgColor, bgColorDataspace);
if (other.what & eMetadataChanged) diff |= eMetadataChanged;
CHECK_DIFF(diff, eShadowRadiusChanged, other, shadowRadius);
- CHECK_DIFF3(diff, eRenderBorderChanged, other, borderEnabled, borderWidth, borderColor);
CHECK_DIFF(diff, eDefaultFrameRateCompatibilityChanged, other, defaultFrameRateCompatibility);
CHECK_DIFF(diff, eFrameRateSelectionPriority, other, frameRateSelectionPriority);
CHECK_DIFF3(diff, eFrameRateChanged, other, frameRate, frameRateCompatibility,
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 4f1356b..1d2ea3e 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -2250,23 +2250,6 @@
return *this;
}
-SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::enableBorder(
- const sp<SurfaceControl>& sc, bool shouldEnable, float width, const half4& color) {
- layer_state_t* s = getLayerState(sc);
- if (!s) {
- mStatus = BAD_INDEX;
- return *this;
- }
-
- s->what |= layer_state_t::eRenderBorderChanged;
- s->borderEnabled = shouldEnable;
- s->borderWidth = width;
- s->borderColor = color;
-
- registerSurfaceControlForCallback(sc);
- return *this;
-}
-
// ---------------------------------------------------------------------------
DisplayState& SurfaceComposerClient::Transaction::getDisplayState(const sp<IBinder>& token) {
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/aidl/android/gui/DisplayModeSpecs.aidl b/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl
index af138c7..13962fe 100644
--- a/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl
+++ b/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl
@@ -42,6 +42,17 @@
}
/**
+ * Refers to the time after which the idle screen's refresh rate is to be reduced
+ */
+ parcelable IdleScreenRefreshRateConfig {
+
+ /**
+ * The timeout value in milli seconds
+ */
+ int timeoutMillis;
+ }
+
+ /**
* Base mode ID. This is what system defaults to for all other settings, or
* if the refresh rate range is not available.
*/
@@ -72,4 +83,13 @@
* never smaller.
*/
RefreshRateRanges appRequestRanges;
+
+ /**
+ * The config to represent the maximum time (in ms) for which the display can remain in an idle
+ * state before reducing the refresh rate to conserve power.
+ * Null value refers that the device is not configured to dynamically reduce the refresh rate
+ * based on external conditions.
+ * -1 refers to the current conditions requires no timeout
+ */
+ @nullable IdleScreenRefreshRateConfig idleScreenRefreshRateConfig;
}
diff --git a/libs/gui/include/gui/Choreographer.h b/libs/gui/include/gui/Choreographer.h
index fc79b03..2e5aa4a 100644
--- a/libs/gui/include/gui/Choreographer.h
+++ b/libs/gui/include/gui/Choreographer.h
@@ -109,6 +109,7 @@
virtual ~Choreographer() override EXCLUDES(gChoreographers.lock);
int64_t getFrameInterval() const;
bool inCallback() const;
+ const sp<Looper> getLooper();
private:
Choreographer(const Choreographer&) = delete;
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 0fedea7..ca7acf9 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -179,7 +179,6 @@
eCachingHintChanged = 0x00000200,
eDimmingEnabledChanged = 0x00000400,
eShadowRadiusChanged = 0x00000800,
- eRenderBorderChanged = 0x00001000,
eBufferCropChanged = 0x00002000,
eRelativeLayerChanged = 0x00004000,
eReparent = 0x00008000,
@@ -258,8 +257,8 @@
layer_state_t::eBlurRegionsChanged | layer_state_t::eColorChanged |
layer_state_t::eColorSpaceAgnosticChanged | layer_state_t::eColorTransformChanged |
layer_state_t::eCornerRadiusChanged | layer_state_t::eDimmingEnabledChanged |
- layer_state_t::eHdrMetadataChanged | layer_state_t::eRenderBorderChanged |
- layer_state_t::eShadowRadiusChanged | layer_state_t::eStretchChanged;
+ layer_state_t::eHdrMetadataChanged | layer_state_t::eShadowRadiusChanged |
+ layer_state_t::eStretchChanged;
// Changes which invalidates the layer's visible region in CE.
static constexpr uint64_t CONTENT_DIRTY = layer_state_t::CONTENT_CHANGES |
@@ -388,11 +387,6 @@
// should be trusted for input occlusion detection purposes
bool isTrustedOverlay;
- // Flag to indicate if border needs to be enabled on the layer
- bool borderEnabled;
- float borderWidth;
- half4 borderColor;
-
// Stretch effect to be applied to this layer
StretchEffect stretchEffect;
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 2888826..79224e6 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -744,9 +744,6 @@
const Rect& destinationFrame);
Transaction& setDropInputMode(const sp<SurfaceControl>& sc, gui::DropInputMode mode);
- Transaction& enableBorder(const sp<SurfaceControl>& sc, bool shouldEnable, float width,
- const half4& color);
-
status_t setDisplaySurface(const sp<IBinder>& token,
const sp<IGraphicBufferProducer>& bufferProducer);
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/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 6b8bc01..3278c23 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -204,6 +204,8 @@
srcs: [
"AccelerationCurve.cpp",
"Input.cpp",
+ "InputConsumer.cpp",
+ "InputConsumerNoResampling.cpp",
"InputDevice.cpp",
"InputEventLabels.cpp",
"InputTransport.cpp",
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 b3a36eb..1869483 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -51,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);
@@ -81,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()) {
@@ -113,103 +88,11 @@
return newFd;
}
-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);
-}
-
// 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 constexpr size_t SOCKET_BUFFER_SIZE = 32 * 1024;
-
-// Nanoseconds per milliseconds.
-static 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.
-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.
@@ -220,18 +103,6 @@
__android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "VerifyEvents", ANDROID_LOG_INFO);
}
-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;
@@ -892,756 +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 + 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 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;
-}
-
-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/MotionPredictor.cpp b/libs/input/MotionPredictor.cpp
index c4e3ff6..e836a4c 100644
--- a/libs/input/MotionPredictor.cpp
+++ b/libs/input/MotionPredictor.cpp
@@ -22,17 +22,21 @@
#include <cmath>
#include <cstddef>
#include <cstdint>
+#include <limits>
#include <string>
#include <vector>
#include <android-base/logging.h>
#include <android-base/strings.h>
#include <android/input.h>
+#include <com_android_input_flags.h>
#include <attestation/HmacKeyManager.h>
#include <ftl/enum.h>
#include <input/TfLiteMotionPredictor.h>
+namespace input_flags = com::android::input::flags;
+
namespace android {
namespace {
@@ -197,7 +201,14 @@
// device starts to speed up, but avoids producing noisy predictions as it slows down.
break;
}
- // TODO(b/266747654): Stop predictions if confidence is < some threshold.
+ if (input_flags::enable_jerk_prediction_pruning()) {
+ // TODO(b/266747654): Stop predictions if confidence is < some threshold
+ // Arbitrarily high pruning index, will correct once jerk thresholding is implemented.
+ const size_t upperBoundPredictionIndex = std::numeric_limits<size_t>::max();
+ if (i > upperBoundPredictionIndex) {
+ break;
+ }
+ }
const TfLiteMotionPredictorSample::Point predictedPoint =
convertPrediction(axisFrom, axisTo, predictedR[i], predictedPhi[i]);
diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig
index b48b0fb..c1f5e2a 100644
--- a/libs/input/input_flags.aconfig
+++ b/libs/input/input_flags.aconfig
@@ -128,3 +128,10 @@
description: "Enable fling scrolling to be stopped by putting a finger on the touchpad again"
bug: "281106755"
}
+
+flag {
+ name: "enable_jerk_prediction_pruning"
+ namespace: "input"
+ description: "Enable prediction pruning based on jerk thresholds."
+ bug: "266747654"
+}
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index 93af4c2..e67a65a 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -19,6 +19,7 @@
"InputDevice_test.cpp",
"InputEvent_test.cpp",
"InputPublisherAndConsumer_test.cpp",
+ "InputPublisherAndConsumerNoResampling_test.cpp",
"InputVerifier_test.cpp",
"MotionPredictor_test.cpp",
"MotionPredictorMetricsManager_test.cpp",
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 b5fab49..332831f 100644
--- a/libs/input/tests/InputPublisherAndConsumer_test.cpp
+++ b/libs/input/tests/InputPublisherAndConsumer_test.cpp
@@ -17,6 +17,7 @@
#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/TouchResampling_test.cpp b/libs/input/tests/TouchResampling_test.cpp
index 0b0bb63..6e23d4e 100644
--- a/libs/input/tests/TouchResampling_test.cpp
+++ b/libs/input/tests/TouchResampling_test.cpp
@@ -19,6 +19,7 @@
#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/renderengine/Android.bp b/libs/renderengine/Android.bp
index 09d7cb5..0defc7e 100644
--- a/libs/renderengine/Android.bp
+++ b/libs/renderengine/Android.bp
@@ -83,11 +83,14 @@
"skia/AutoBackendTexture.cpp",
"skia/Cache.cpp",
"skia/ColorSpaces.cpp",
+ "skia/GaneshVkRenderEngine.cpp",
"skia/GLExtensions.cpp",
"skia/SkiaRenderEngine.cpp",
"skia/SkiaGLRenderEngine.cpp",
"skia/SkiaVkRenderEngine.cpp",
"skia/VulkanInterface.cpp",
+ "skia/compat/GaneshBackendTexture.cpp",
+ "skia/compat/GaneshGpuContext.cpp",
"skia/debug/CaptureTimer.cpp",
"skia/debug/CommonPool.cpp",
"skia/debug/SkiaCapture.cpp",
diff --git a/libs/renderengine/include/renderengine/BorderRenderInfo.h b/libs/renderengine/include/renderengine/BorderRenderInfo.h
deleted file mode 100644
index 0ee6661..0000000
--- a/libs/renderengine/include/renderengine/BorderRenderInfo.h
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-#include <math/mat4.h>
-#include <ui/Region.h>
-
-namespace android {
-namespace renderengine {
-
-struct BorderRenderInfo {
- float width = 0;
- half4 color;
- Region combinedRegion;
-
- bool operator==(const BorderRenderInfo& rhs) const {
- return (width == rhs.width && color == rhs.color &&
- combinedRegion.hasSameRects(rhs.combinedRegion));
- }
-};
-
-} // namespace renderengine
-} // namespace android
\ No newline at end of file
diff --git a/libs/renderengine/include/renderengine/DisplaySettings.h b/libs/renderengine/include/renderengine/DisplaySettings.h
index 8d7c13c..deb6253 100644
--- a/libs/renderengine/include/renderengine/DisplaySettings.h
+++ b/libs/renderengine/include/renderengine/DisplaySettings.h
@@ -22,7 +22,6 @@
#include <math/mat4.h>
#include <renderengine/PrintMatrix.h>
-#include <renderengine/BorderRenderInfo.h>
#include <ui/DisplayId.h>
#include <ui/GraphicTypes.h>
#include <ui/Rect.h>
@@ -87,8 +86,6 @@
// Configures the rendering intent of the output display. This is used for tonemapping.
aidl::android::hardware::graphics::composer3::RenderIntent renderIntent =
aidl::android::hardware::graphics::composer3::RenderIntent::TONE_MAP_COLORIMETRIC;
-
- std::vector<renderengine::BorderRenderInfo> borderInfoList;
};
static inline bool operator==(const DisplaySettings& lhs, const DisplaySettings& rhs) {
@@ -100,8 +97,7 @@
lhs.deviceHandlesColorTransform == rhs.deviceHandlesColorTransform &&
lhs.orientation == rhs.orientation &&
lhs.targetLuminanceNits == rhs.targetLuminanceNits &&
- lhs.dimmingStage == rhs.dimmingStage && lhs.renderIntent == rhs.renderIntent &&
- lhs.borderInfoList == rhs.borderInfoList;
+ lhs.dimmingStage == rhs.dimmingStage && lhs.renderIntent == rhs.renderIntent;
}
static const char* orientation_to_string(uint32_t orientation) {
diff --git a/libs/renderengine/skia/AutoBackendTexture.cpp b/libs/renderengine/skia/AutoBackendTexture.cpp
index ee95e59..8aeef9f 100644
--- a/libs/renderengine/skia/AutoBackendTexture.cpp
+++ b/libs/renderengine/skia/AutoBackendTexture.cpp
@@ -20,81 +20,21 @@
#define LOG_TAG "RenderEngine"
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
-#include <SkImage.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"
+#include <include/core/SkImage.h>
+#include <include/core/SkSurface.h>
+
+#include "compat/SkiaBackendTexture.h"
+
+#include <log/log_main.h>
+#include <utils/Trace.h>
namespace android {
namespace renderengine {
namespace skia {
-AutoBackendTexture::AutoBackendTexture(GrDirectContext* context, AHardwareBuffer* buffer,
- bool isOutputBuffer, CleanupManager& cleanupMgr)
- : 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();
- if (backend == GrBackendApi::kOpenGL) {
- backendFormat =
- GrAHardwareBufferUtils::GetGLBackendFormat(context, desc.format, false);
- mBackendTexture =
- GrAHardwareBufferUtils::MakeGLBackendTexture(context,
- buffer,
- desc.width,
- desc.height,
- &mDeleteProc,
- &mUpdateProc,
- &mImageCtx,
- createProtectedImage,
- backendFormat,
- isOutputBuffer);
- } else if (backend == GrBackendApi::kVulkan) {
- backendFormat =
- GrAHardwareBufferUtils::GetVulkanBackendFormat(context,
- buffer,
- desc.format,
- false);
- mBackendTexture =
- GrAHardwareBufferUtils::MakeVulkanBackendTexture(context,
- buffer,
- desc.width,
- desc.height,
- &mDeleteProc,
- &mUpdateProc,
- &mImageCtx,
- createProtectedImage,
- backendFormat,
- isOutputBuffer);
- } else {
- LOG_ALWAYS_FATAL("Unexpected backend %u", static_cast<unsigned>(backend));
- }
-
- mColorType = GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(desc.format);
- if (!mBackendTexture.isValid() || !desc.width || !desc.height) {
- LOG_ALWAYS_FATAL("Failed to create a valid texture. [%p]:[%d,%d] isProtected:%d "
- "isWriteable:%d format:%d",
- this, desc.width, desc.height, createProtectedImage, isOutputBuffer,
- desc.format);
- }
-}
-
-AutoBackendTexture::~AutoBackendTexture() {
- if (mBackendTexture.isValid()) {
- mDeleteProc(mImageCtx);
- mBackendTexture = {};
- }
-}
+AutoBackendTexture::AutoBackendTexture(std::unique_ptr<SkiaBackendTexture> backendTexture,
+ CleanupManager& cleanupMgr)
+ : mCleanupMgr(cleanupMgr), mBackendTexture(std::move(backendTexture)) {}
void AutoBackendTexture::unref(bool releaseLocalResources) {
if (releaseLocalResources) {
@@ -122,95 +62,32 @@
textureRelease->unref(false);
}
-void logFatalTexture(const char* msg, const GrBackendTexture& tex, ui::Dataspace dataspace,
- SkColorType colorType) {
- switch (tex.backend()) {
- case GrBackendApi::kOpenGL: {
- GrGLTextureInfo textureInfo;
- bool retrievedTextureInfo = GrBackendTextures::GetGLTextureInfo(tex, &textureInfo);
- LOG_ALWAYS_FATAL("%s isTextureValid:%d dataspace:%d"
- "\n\tGrBackendTexture: (%i x %i) hasMipmaps: %i isProtected: %i "
- "texType: %i\n\t\tGrGLTextureInfo: success: %i fTarget: %u fFormat: %u"
- " colorType %i",
- msg, tex.isValid(), static_cast<int32_t>(dataspace), tex.width(),
- tex.height(), tex.hasMipmaps(), tex.isProtected(),
- static_cast<int>(tex.textureType()), retrievedTextureInfo,
- textureInfo.fTarget, textureInfo.fFormat, colorType);
- break;
- }
- case GrBackendApi::kVulkan: {
- GrVkImageInfo imageInfo;
- bool retrievedImageInfo = GrBackendTextures::GetVkImageInfo(tex, &imageInfo);
- LOG_ALWAYS_FATAL("%s isTextureValid:%d dataspace:%d"
- "\n\tGrBackendTexture: (%i x %i) hasMipmaps: %i isProtected: %i "
- "texType: %i\n\t\tVkImageInfo: success: %i fFormat: %i "
- "fSampleCount: %u fLevelCount: %u colorType %i",
- msg, tex.isValid(), static_cast<int32_t>(dataspace), tex.width(),
- tex.height(), tex.hasMipmaps(), tex.isProtected(),
- static_cast<int>(tex.textureType()), retrievedImageInfo,
- imageInfo.fFormat, imageInfo.fSampleCount, imageInfo.fLevelCount,
- colorType);
- break;
- }
- default:
- LOG_ALWAYS_FATAL("%s Unexpected backend %u", msg, static_cast<unsigned>(tex.backend()));
- break;
- }
-}
-
-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);
- }
-
- auto colorType = mColorType;
- if (alphaType == kOpaque_SkAlphaType) {
- if (colorType == kRGBA_8888_SkColorType) {
- colorType = kRGB_888x_SkColorType;
- }
- }
-
- sk_sp<SkImage> image =
- SkImages::BorrowTextureFrom(context, mBackendTexture, kTopLeft_GrSurfaceOrigin,
- colorType, alphaType, toSkColorSpace(dataspace),
- releaseImageProc, this);
- if (image.get()) {
- // The following ref will be counteracted by releaseProc, when SkImage is discarded.
- ref();
- }
+ sk_sp<SkImage> image = mBackendTexture->makeImage(alphaType, dataspace, releaseImageProc, this);
+ // The following ref will be counteracted by releaseProc, when SkImage is discarded.
+ ref();
mImage = image;
mDataspace = dataspace;
- if (!mImage) {
- logFatalTexture("Unable to generate SkImage.", mBackendTexture, dataspace, colorType);
- }
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");
+ LOG_ALWAYS_FATAL_IF(!mBackendTexture->isOutputBuffer(),
+ "You can't generate an SkSurface for a read-only texture");
if (!mSurface.get() || mDataspace != dataspace) {
sk_sp<SkSurface> surface =
- SkSurfaces::WrapBackendTexture(context, mBackendTexture,
- kTopLeft_GrSurfaceOrigin, 0, mColorType,
- toSkColorSpace(dataspace), nullptr,
- releaseSurfaceProc, this);
- if (surface.get()) {
- // The following ref will be counteracted by releaseProc, when SkSurface is discarded.
- ref();
- }
+ mBackendTexture->makeSurface(dataspace, releaseSurfaceProc, this);
+ // The following ref will be counteracted by releaseProc, when SkSurface is discarded.
+ ref();
+
mSurface = surface;
}
mDataspace = dataspace;
- if (!mSurface) {
- logFatalTexture("Unable to generate SkSurface.", mBackendTexture, dataspace, mColorType);
- }
return mSurface;
}
diff --git a/libs/renderengine/skia/AutoBackendTexture.h b/libs/renderengine/skia/AutoBackendTexture.h
index 509ac40..74daf47 100644
--- a/libs/renderengine/skia/AutoBackendTexture.h
+++ b/libs/renderengine/skia/AutoBackendTexture.h
@@ -16,7 +16,6 @@
#pragma once
-#include <GrAHardwareBufferUtils.h>
#include <GrDirectContext.h>
#include <SkImage.h>
#include <SkSurface.h>
@@ -24,8 +23,9 @@
#include <ui/GraphicTypes.h>
#include "android-base/macros.h"
+#include "compat/SkiaBackendTexture.h"
-#include <mutex>
+#include <memory>
#include <vector>
namespace android {
@@ -80,9 +80,8 @@
// of shared ownership with Skia objects, so we wrap it here instead.
class LocalRef {
public:
- LocalRef(GrDirectContext* context, AHardwareBuffer* buffer, bool isOutputBuffer,
- CleanupManager& cleanupMgr) {
- mTexture = new AutoBackendTexture(context, buffer, isOutputBuffer, cleanupMgr);
+ LocalRef(std::unique_ptr<SkiaBackendTexture> backendTexture, CleanupManager& cleanupMgr) {
+ mTexture = new AutoBackendTexture(std::move(backendTexture), cleanupMgr);
mTexture->ref();
}
@@ -95,17 +94,16 @@
// 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; }
+ SkColorType colorType() const { return mTexture->mBackendTexture->internalColorType(); }
DISALLOW_COPY_AND_ASSIGN(LocalRef);
@@ -114,12 +112,15 @@
};
private:
- // Creates a GrBackendTexture whose contents come from the provided buffer.
- AutoBackendTexture(GrDirectContext* context, AHardwareBuffer* buffer, bool isOutputBuffer,
+ DISALLOW_COPY_AND_ASSIGN(AutoBackendTexture);
+
+ // Creates an AutoBackendTexture to manage the lifecycle of a given SkiaBackendTexture, which is
+ // in turn backed by an underlying backend-specific texture type.
+ AutoBackendTexture(std::unique_ptr<SkiaBackendTexture> backendTexture,
CleanupManager& cleanupMgr);
// The only way to invoke dtor is with unref, when mUsageCount is 0.
- ~AutoBackendTexture();
+ ~AutoBackendTexture() = default;
void ref() { mUsageCount++; }
@@ -130,29 +131,21 @@
// 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);
-
- GrBackendTexture mBackendTexture;
- GrAHardwareBufferUtils::DeleteImageProc mDeleteProc;
- GrAHardwareBufferUtils::UpdateImageProc mUpdateProc;
- GrAHardwareBufferUtils::TexImageCtx mImageCtx;
+ sk_sp<SkSurface> getOrCreateSurface(ui::Dataspace dataspace);
CleanupManager& mCleanupMgr;
static void releaseSurfaceProc(SkSurface::ReleaseContext releaseContext);
static void releaseImageProc(SkImages::ReleaseContext releaseContext);
+ std::unique_ptr<SkiaBackendTexture> mBackendTexture;
int mUsageCount = 0;
-
- const bool mIsOutputBuffer;
sk_sp<SkImage> mImage = nullptr;
sk_sp<SkSurface> mSurface = nullptr;
ui::Dataspace mDataspace = ui::Dataspace::UNKNOWN;
- SkColorType mColorType = kUnknown_SkColorType;
};
} // namespace skia
diff --git a/libs/renderengine/skia/GaneshVkRenderEngine.cpp b/libs/renderengine/skia/GaneshVkRenderEngine.cpp
new file mode 100644
index 0000000..e76a4c3
--- /dev/null
+++ b/libs/renderengine/skia/GaneshVkRenderEngine.cpp
@@ -0,0 +1,84 @@
+/*
+ * 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 "GaneshVkRenderEngine.h"
+
+#undef LOG_TAG
+#define LOG_TAG "RenderEngine"
+
+#include <include/gpu/ganesh/vk/GrVkBackendSemaphore.h>
+
+#include <log/log_main.h>
+#include <sync/sync.h>
+
+namespace android::renderengine::skia {
+
+// Ganesh-specific function signature for fFinishedProc callback.
+static void unref_semaphore(void* semaphore) {
+ SkiaVkRenderEngine::DestroySemaphoreInfo* info =
+ reinterpret_cast<SkiaVkRenderEngine::DestroySemaphoreInfo*>(semaphore);
+ info->unref();
+}
+
+void GaneshVkRenderEngine::waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) {
+ if (fenceFd.get() < 0) return;
+
+ const int dupedFd = dup(fenceFd.get());
+ if (dupedFd < 0) {
+ ALOGE("failed to create duplicate fence fd: %d", dupedFd);
+ sync_wait(fenceFd.get(), -1);
+ return;
+ }
+
+ base::unique_fd fenceDup(dupedFd);
+ VkSemaphore waitSemaphore =
+ getVulkanInterface(isProtected()).importSemaphoreFromSyncFd(fenceDup.release());
+ GrBackendSemaphore beSemaphore = GrBackendSemaphores::MakeVk(waitSemaphore);
+ constexpr bool kDeleteAfterWait = true;
+ context->grDirectContext()->wait(1, &beSemaphore, kDeleteAfterWait);
+}
+
+base::unique_fd GaneshVkRenderEngine::flushAndSubmit(SkiaGpuContext* context) {
+ sk_sp<GrDirectContext> grContext = context->grDirectContext();
+ VulkanInterface& vi = getVulkanInterface(isProtected());
+ VkSemaphore semaphore = vi.createExportableSemaphore();
+
+ GrBackendSemaphore backendSemaphore = GrBackendSemaphores::MakeVk(semaphore);
+
+ GrFlushInfo flushInfo;
+ DestroySemaphoreInfo* destroySemaphoreInfo = nullptr;
+ if (semaphore != VK_NULL_HANDLE) {
+ destroySemaphoreInfo = new DestroySemaphoreInfo(vi, semaphore);
+ flushInfo.fNumSemaphores = 1;
+ flushInfo.fSignalSemaphores = &backendSemaphore;
+ flushInfo.fFinishedProc = unref_semaphore;
+ flushInfo.fFinishedContext = destroySemaphoreInfo;
+ }
+ GrSemaphoresSubmitted submitted = grContext->flush(flushInfo);
+ grContext->submit(GrSyncCpu::kNo);
+ int drawFenceFd = -1;
+ if (semaphore != VK_NULL_HANDLE) {
+ if (GrSemaphoresSubmitted::kYes == submitted) {
+ drawFenceFd = vi.exportSemaphoreSyncFd(semaphore);
+ }
+ // Now that drawFenceFd has been created, we can delete our reference to this semaphore
+ flushInfo.fFinishedProc(destroySemaphoreInfo);
+ }
+ base::unique_fd res(drawFenceFd);
+ return res;
+}
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/GaneshVkRenderEngine.h b/libs/renderengine/skia/GaneshVkRenderEngine.h
new file mode 100644
index 0000000..90e2487
--- /dev/null
+++ b/libs/renderengine/skia/GaneshVkRenderEngine.h
@@ -0,0 +1,34 @@
+/*
+ * 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 "skia/SkiaVkRenderEngine.h"
+
+namespace android::renderengine::skia {
+
+class GaneshVkRenderEngine : public SkiaVkRenderEngine {
+ friend std::unique_ptr<SkiaVkRenderEngine> SkiaVkRenderEngine::create(
+ const RenderEngineCreationArgs& args);
+
+protected:
+ GaneshVkRenderEngine(const RenderEngineCreationArgs& args) : SkiaVkRenderEngine(args) {}
+
+ void waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) override;
+ base::unique_fd flushAndSubmit(SkiaGpuContext* context) override;
+};
+
+} // namespace android::renderengine::skia
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..c12c119 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -74,11 +74,13 @@
#include "Cache.h"
#include "ColorSpaces.h"
+#include "compat/SkiaGpuContext.h"
#include "filters/BlurFilter.h"
#include "filters/GaussianBlurFilter.h"
#include "filters/KawaseBlurFilter.h"
#include "filters/LinearEffect.h"
#include "log/log_main.h"
+#include "skia/compat/SkiaBackendTexture.h"
#include "skia/debug/SkiaCapture.h"
#include "skia/debug/SkiaMemoryReporter.h"
#include "skia/filters/StretchShaderFactory.h"
@@ -290,14 +292,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 +308,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 +374,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 +408,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);
@@ -423,10 +418,11 @@
if (FlagManager::getInstance().renderable_buffer_usage()) {
isRenderable = buffer->getUsage() & GRALLOC_USAGE_HW_RENDER;
}
- std::shared_ptr<AutoBackendTexture::LocalRef> imageTextureRef =
- std::make_shared<AutoBackendTexture::LocalRef>(grContext,
- buffer->toAHardwareBuffer(),
- isRenderable, mTextureCleanupMgr);
+ std::unique_ptr<SkiaBackendTexture> backendTexture =
+ context->makeBackendTexture(buffer->toAHardwareBuffer(), isRenderable);
+ auto imageTextureRef =
+ std::make_shared<AutoBackendTexture::LocalRef>(std::move(backendTexture),
+ mTextureCleanupMgr);
cache.insert({buffer->getId(), imageTextureRef});
}
}
@@ -477,9 +473,10 @@
return it->second;
}
}
- return std::make_shared<AutoBackendTexture::LocalRef>(getActiveGrContext(),
- buffer->toAHardwareBuffer(),
- isOutputBuffer, mTextureCleanupMgr);
+ std::unique_ptr<SkiaBackendTexture> backendTexture =
+ getActiveContext()->makeBackendTexture(buffer->toAHardwareBuffer(), isOutputBuffer);
+ return std::make_shared<AutoBackendTexture::LocalRef>(std::move(backendTexture),
+ mTextureCleanupMgr);
}
bool SkiaRenderEngine::canSkipPostRenderCleanup() const {
@@ -667,8 +664,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 +674,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 +841,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 +855,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 +944,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 +968,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
@@ -1131,25 +1127,6 @@
skgpu::ganesh::Flush(activeSurface);
}
}
- for (const auto& borderRenderInfo : display.borderInfoList) {
- SkPaint p;
- p.setColor(SkColor4f{borderRenderInfo.color.r, borderRenderInfo.color.g,
- borderRenderInfo.color.b, borderRenderInfo.color.a});
- p.setAntiAlias(true);
- p.setStyle(SkPaint::kStroke_Style);
- p.setStrokeWidth(borderRenderInfo.width);
- SkRegion sk_region;
- SkPath path;
-
- // Construct a final SkRegion using Regions
- for (const auto& r : borderRenderInfo.combinedRegion) {
- sk_region.op({r.left, r.top, r.right, r.bottom}, SkRegion::kUnion_Op);
- }
-
- sk_region.getBoundaryPath(&path);
- canvas->drawPath(path, p);
- path.close();
- }
surfaceAutoSaveRestore.restore();
mCapture->endCapture();
@@ -1159,7 +1136,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 +1146,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 +1176,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 +1222,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 +1246,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..a39adbe 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,26 @@
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();
+ // TODO: b/293371537 - Return reference instead of pointer? (Cleanup)
+ SkiaGpuContext* getActiveContext();
bool isProtected() const { return mInProtectedContext; }
@@ -121,6 +123,8 @@
int mTotalShadersCompiled = 0;
};
+ SkSLCacheMonitor mSkSLCacheMonitor;
+
private:
void mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer,
bool isRenderable) override final;
@@ -183,12 +187,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 f2f1b5d..473fa0a 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
@@ -21,12 +21,16 @@
#include "SkiaVkRenderEngine.h"
+#include "GaneshVkRenderEngine.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>
@@ -81,8 +85,9 @@
std::unique_ptr<SkiaVkRenderEngine> SkiaVkRenderEngine::create(
const RenderEngineCreationArgs& args) {
- std::unique_ptr<SkiaVkRenderEngine> engine(new SkiaVkRenderEngine(args));
- engine->ensureGrContextsCreated();
+ // TODO: b/293371537 - Ganesh vs. Graphite subclass based on flag in RenderEngineCreationArgs
+ std::unique_ptr<SkiaVkRenderEngine> engine(new GaneshVkRenderEngine(args));
+ engine->ensureContextsCreated();
if (sVulkanInterface.isInitialized()) {
ALOGD("SkiaVkRenderEngine::%s: successfully initialized SkiaVkRenderEngine", __func__);
@@ -103,16 +108,16 @@
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;
@@ -126,65 +131,13 @@
return true;
}
-static void unref_semaphore(void* semaphore) {
- SkiaVkRenderEngine::DestroySemaphoreInfo* info =
- reinterpret_cast<SkiaVkRenderEngine::DestroySemaphoreInfo*>(semaphore);
- info->unref();
-}
-
-static VulkanInterface& getVulkanInterface(bool protectedContext) {
+VulkanInterface& SkiaVkRenderEngine::getVulkanInterface(bool protectedContext) {
if (protectedContext) {
return sProtectedContentVulkanInterface;
}
return sVulkanInterface;
}
-void SkiaVkRenderEngine::waitFence(GrDirectContext* grContext, base::borrowed_fd fenceFd) {
- if (fenceFd.get() < 0) return;
-
- int dupedFd = dup(fenceFd.get());
- if (dupedFd < 0) {
- ALOGE("failed to create duplicate fence fd: %d", dupedFd);
- sync_wait(fenceFd.get(), -1);
- return;
- }
-
- base::unique_fd fenceDup(dupedFd);
- VkSemaphore waitSemaphore =
- getVulkanInterface(isProtected()).importSemaphoreFromSyncFd(fenceDup.release());
- GrBackendSemaphore beSemaphore = GrBackendSemaphores::MakeVk(waitSemaphore);
- grContext->wait(1, &beSemaphore, true /* delete after wait */);
-}
-
-base::unique_fd SkiaVkRenderEngine::flushAndSubmit(GrDirectContext* grContext) {
- VulkanInterface& vi = getVulkanInterface(isProtected());
- VkSemaphore semaphore = vi.createExportableSemaphore();
-
- GrBackendSemaphore backendSemaphore = GrBackendSemaphores::MakeVk(semaphore);
-
- GrFlushInfo flushInfo;
- DestroySemaphoreInfo* destroySemaphoreInfo = nullptr;
- if (semaphore != VK_NULL_HANDLE) {
- destroySemaphoreInfo = new DestroySemaphoreInfo(vi, semaphore);
- flushInfo.fNumSemaphores = 1;
- flushInfo.fSignalSemaphores = &backendSemaphore;
- flushInfo.fFinishedProc = unref_semaphore;
- flushInfo.fFinishedContext = destroySemaphoreInfo;
- }
- GrSemaphoresSubmitted submitted = grContext->flush(flushInfo);
- grContext->submit(GrSyncCpu::kNo);
- int drawFenceFd = -1;
- if (semaphore != VK_NULL_HANDLE) {
- if (GrSemaphoresSubmitted::kYes == submitted) {
- drawFenceFd = vi.exportSemaphoreSyncFd(semaphore);
- }
- // Now that drawFenceFd has been created, we can delete our reference to this semaphore
- flushInfo.fFinishedProc(destroySemaphoreInfo);
- }
- base::unique_fd res(drawFenceFd);
- return res;
-}
-
int SkiaVkRenderEngine::getContextPriority() {
// EGL_CONTEXT_PRIORITY_REALTIME_NV
constexpr int kRealtimePriority = 0x3357;
diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.h b/libs/renderengine/skia/SkiaVkRenderEngine.h
index ca0dcbf..5fd911f 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.h
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.h
@@ -21,6 +21,7 @@
#include "SkiaRenderEngine.h"
#include "VulkanInterface.h"
+#include "compat/SkiaGpuContext.h"
namespace android {
namespace renderengine {
@@ -70,20 +71,21 @@
};
protected:
+ // Redeclare parent functions that Ganesh vs. Graphite subclasses must implement.
+ virtual void waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) override = 0;
+ virtual base::unique_fd flushAndSubmit(SkiaGpuContext* context) override = 0;
+
+ SkiaVkRenderEngine(const RenderEngineCreationArgs& args);
+
// Implementations of abstract SkiaRenderEngine functions specific to
- // rendering backend
- virtual SkiaRenderEngine::Contexts createDirectContexts(const GrContextOptions& options);
+ // Vulkan, but shareable between Ganesh and Graphite.
+ 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 appendBackendSpecificInfoToDump(std::string& result) override;
-private:
- SkiaVkRenderEngine(const RenderEngineCreationArgs& args);
- base::unique_fd flush();
-
- GrVkBackendContext mBackendContext;
+ // TODO: b/300533018 - refactor this to be non-static
+ static VulkanInterface& getVulkanInterface(bool protectedContext);
};
} // namespace skia
diff --git a/libs/renderengine/skia/VulkanInterface.cpp b/libs/renderengine/skia/VulkanInterface.cpp
index 453cdc1..2f09a38 100644
--- a/libs/renderengine/skia/VulkanInterface.cpp
+++ b/libs/renderengine/skia/VulkanInterface.cpp
@@ -30,7 +30,7 @@
namespace renderengine {
namespace skia {
-GrVkBackendContext VulkanInterface::getBackendContext() {
+GrVkBackendContext VulkanInterface::getGaneshBackendContext() {
GrVkBackendContext backendContext;
backendContext.fInstance = mInstance;
backendContext.fPhysicalDevice = mPhysicalDevice;
diff --git a/libs/renderengine/skia/VulkanInterface.h b/libs/renderengine/skia/VulkanInterface.h
index c3936d9..512e62c 100644
--- a/libs/renderengine/skia/VulkanInterface.h
+++ b/libs/renderengine/skia/VulkanInterface.h
@@ -40,7 +40,7 @@
void teardown();
// TODO: b/293371537 - Graphite variant (external/skia/include/gpu/vk/VulkanBackendContext.h)
- GrVkBackendContext getBackendContext();
+ GrVkBackendContext getGaneshBackendContext();
VkSemaphore createExportableSemaphore();
VkSemaphore importSemaphoreFromSyncFd(int syncFd);
int exportSemaphoreSyncFd(VkSemaphore semaphore);
diff --git a/libs/renderengine/skia/compat/GaneshBackendTexture.cpp b/libs/renderengine/skia/compat/GaneshBackendTexture.cpp
new file mode 100644
index 0000000..d246466
--- /dev/null
+++ b/libs/renderengine/skia/compat/GaneshBackendTexture.cpp
@@ -0,0 +1,161 @@
+/*
+ * 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 "GaneshBackendTexture.h"
+
+#undef LOG_TAG
+#define LOG_TAG "RenderEngine"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include <include/core/SkImage.h>
+#include <include/gpu/GrDirectContext.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 "skia/ColorSpaces.h"
+#include "skia/compat/SkiaBackendTexture.h"
+
+#include <android/hardware_buffer.h>
+#include <log/log_main.h>
+#include <utils/Trace.h>
+
+namespace android::renderengine::skia {
+
+GaneshBackendTexture::GaneshBackendTexture(sk_sp<GrDirectContext> grContext,
+ AHardwareBuffer* buffer, bool isOutputBuffer)
+ : SkiaBackendTexture(buffer, isOutputBuffer), mGrContext(grContext) {
+ ATRACE_CALL();
+ AHardwareBuffer_Desc desc;
+ AHardwareBuffer_describe(buffer, &desc);
+ const bool createProtectedImage = 0 != (desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT);
+
+ GrBackendFormat backendFormat;
+ const GrBackendApi graphicsApi = grContext->backend();
+ if (graphicsApi == GrBackendApi::kOpenGL) {
+ backendFormat =
+ GrAHardwareBufferUtils::GetGLBackendFormat(grContext.get(), desc.format, false);
+ mBackendTexture =
+ GrAHardwareBufferUtils::MakeGLBackendTexture(grContext.get(), buffer, desc.width,
+ desc.height, &mDeleteProc,
+ &mUpdateProc, &mImageCtx,
+ createProtectedImage, backendFormat,
+ isOutputBuffer);
+ } else if (graphicsApi == GrBackendApi::kVulkan) {
+ backendFormat = GrAHardwareBufferUtils::GetVulkanBackendFormat(grContext.get(), buffer,
+ desc.format, false);
+ mBackendTexture =
+ GrAHardwareBufferUtils::MakeVulkanBackendTexture(grContext.get(), buffer,
+ desc.width, desc.height,
+ &mDeleteProc, &mUpdateProc,
+ &mImageCtx, createProtectedImage,
+ backendFormat, isOutputBuffer);
+ } else {
+ LOG_ALWAYS_FATAL("Unexpected graphics API %u", static_cast<unsigned>(graphicsApi));
+ }
+
+ if (!mBackendTexture.isValid() || !desc.width || !desc.height) {
+ LOG_ALWAYS_FATAL("Failed to create a valid texture. [%p]:[%d,%d] isProtected:%d "
+ "isWriteable:%d format:%d",
+ this, desc.width, desc.height, createProtectedImage, isOutputBuffer,
+ desc.format);
+ }
+}
+
+GaneshBackendTexture::~GaneshBackendTexture() {
+ if (mBackendTexture.isValid()) {
+ mDeleteProc(mImageCtx);
+ mBackendTexture = {};
+ }
+}
+
+sk_sp<SkImage> GaneshBackendTexture::makeImage(SkAlphaType alphaType, ui::Dataspace dataspace,
+ TextureReleaseProc releaseImageProc,
+ ReleaseContext releaseContext) {
+ if (mBackendTexture.isValid()) {
+ mUpdateProc(mImageCtx, mGrContext.get());
+ }
+
+ const SkColorType colorType = colorTypeForImage(alphaType);
+ sk_sp<SkImage> image =
+ SkImages::BorrowTextureFrom(mGrContext.get(), mBackendTexture, kTopLeft_GrSurfaceOrigin,
+ colorType, alphaType, toSkColorSpace(dataspace),
+ releaseImageProc, releaseContext);
+ if (!image) {
+ logFatalTexture("Unable to generate SkImage.", dataspace, colorType);
+ }
+ return image;
+}
+
+sk_sp<SkSurface> GaneshBackendTexture::makeSurface(ui::Dataspace dataspace,
+ TextureReleaseProc releaseSurfaceProc,
+ ReleaseContext releaseContext) {
+ const SkColorType colorType = internalColorType();
+ sk_sp<SkSurface> surface =
+ SkSurfaces::WrapBackendTexture(mGrContext.get(), mBackendTexture,
+ kTopLeft_GrSurfaceOrigin, 0, colorType,
+ toSkColorSpace(dataspace), nullptr, releaseSurfaceProc,
+ releaseContext);
+ if (!surface) {
+ logFatalTexture("Unable to generate SkSurface.", dataspace, colorType);
+ }
+ return surface;
+}
+
+void GaneshBackendTexture::logFatalTexture(const char* msg, ui::Dataspace dataspace,
+ SkColorType colorType) {
+ switch (mBackendTexture.backend()) {
+ case GrBackendApi::kOpenGL: {
+ GrGLTextureInfo textureInfo;
+ bool retrievedTextureInfo =
+ GrBackendTextures::GetGLTextureInfo(mBackendTexture, &textureInfo);
+ LOG_ALWAYS_FATAL("%s isTextureValid:%d dataspace:%d"
+ "\n\tGrBackendTexture: (%i x %i) hasMipmaps: %i isProtected: %i "
+ "texType: %i\n\t\tGrGLTextureInfo: success: %i fTarget: %u fFormat: %u"
+ " colorType %i",
+ msg, mBackendTexture.isValid(), static_cast<int32_t>(dataspace),
+ mBackendTexture.width(), mBackendTexture.height(),
+ mBackendTexture.hasMipmaps(), mBackendTexture.isProtected(),
+ static_cast<int>(mBackendTexture.textureType()), retrievedTextureInfo,
+ textureInfo.fTarget, textureInfo.fFormat, colorType);
+ break;
+ }
+ case GrBackendApi::kVulkan: {
+ GrVkImageInfo imageInfo;
+ bool retrievedImageInfo =
+ GrBackendTextures::GetVkImageInfo(mBackendTexture, &imageInfo);
+ LOG_ALWAYS_FATAL("%s isTextureValid:%d dataspace:%d"
+ "\n\tGrBackendTexture: (%i x %i) hasMipmaps: %i isProtected: %i "
+ "texType: %i\n\t\tVkImageInfo: success: %i fFormat: %i "
+ "fSampleCount: %u fLevelCount: %u colorType %i",
+ msg, mBackendTexture.isValid(), static_cast<int32_t>(dataspace),
+ mBackendTexture.width(), mBackendTexture.height(),
+ mBackendTexture.hasMipmaps(), mBackendTexture.isProtected(),
+ static_cast<int>(mBackendTexture.textureType()), retrievedImageInfo,
+ imageInfo.fFormat, imageInfo.fSampleCount, imageInfo.fLevelCount,
+ colorType);
+ break;
+ }
+ default:
+ LOG_ALWAYS_FATAL("%s Unexpected backend %u", msg,
+ static_cast<unsigned>(mBackendTexture.backend()));
+ break;
+ }
+}
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/compat/GaneshBackendTexture.h b/libs/renderengine/skia/compat/GaneshBackendTexture.h
new file mode 100644
index 0000000..5cf8647
--- /dev/null
+++ b/libs/renderengine/skia/compat/GaneshBackendTexture.h
@@ -0,0 +1,57 @@
+/*
+ * 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 "SkiaBackendTexture.h"
+#include "ui/GraphicTypes.h"
+
+#include <include/android/GrAHardwareBufferUtils.h>
+#include <include/core/SkColorSpace.h>
+#include <include/gpu/GrDirectContext.h>
+
+#include <android-base/macros.h>
+
+namespace android::renderengine::skia {
+
+class GaneshBackendTexture : public SkiaBackendTexture {
+public:
+ // Creates an internal GrBackendTexture whose contents come from the provided buffer.
+ GaneshBackendTexture(sk_sp<GrDirectContext> grContext, AHardwareBuffer* buffer,
+ bool isOutputBuffer);
+
+ ~GaneshBackendTexture() override;
+
+ sk_sp<SkImage> makeImage(SkAlphaType alphaType, ui::Dataspace dataspace,
+ TextureReleaseProc releaseImageProc,
+ ReleaseContext releaseContext) override;
+
+ sk_sp<SkSurface> makeSurface(ui::Dataspace dataspace, TextureReleaseProc releaseSurfaceProc,
+ ReleaseContext releaseContext) override;
+
+private:
+ DISALLOW_COPY_AND_ASSIGN(GaneshBackendTexture);
+
+ void logFatalTexture(const char* msg, ui::Dataspace dataspace, SkColorType colorType);
+
+ const sk_sp<GrDirectContext> mGrContext;
+ GrBackendTexture mBackendTexture;
+ GrAHardwareBufferUtils::DeleteImageProc mDeleteProc;
+ GrAHardwareBufferUtils::UpdateImageProc mUpdateProc;
+ GrAHardwareBufferUtils::TexImageCtx mImageCtx;
+};
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/compat/GaneshGpuContext.cpp b/libs/renderengine/skia/compat/GaneshGpuContext.cpp
new file mode 100644
index 0000000..87f00e4
--- /dev/null
+++ b/libs/renderengine/skia/compat/GaneshGpuContext.cpp
@@ -0,0 +1,121 @@
+/*
+ * 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 "../AutoBackendTexture.h"
+#include "GaneshBackendTexture.h"
+#include "skia/compat/SkiaBackendTexture.h"
+
+#include <android-base/macros.h>
+#include <log/log_main.h>
+#include <memory>
+
+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;
+}
+
+std::unique_ptr<SkiaBackendTexture> GaneshGpuContext::makeBackendTexture(AHardwareBuffer* buffer,
+ bool isOutputBuffer) {
+ return std::make_unique<GaneshBackendTexture>(mGrContext, buffer, isOutputBuffer);
+}
+
+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..ec0162d
--- /dev/null
+++ b/libs/renderengine/skia/compat/GaneshGpuContext.h
@@ -0,0 +1,54 @@
+/*
+ * 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;
+
+ std::unique_ptr<SkiaBackendTexture> makeBackendTexture(AHardwareBuffer* buffer,
+ bool isOutputBuffer) 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/SkiaBackendTexture.h b/libs/renderengine/skia/compat/SkiaBackendTexture.h
new file mode 100644
index 0000000..09877a5
--- /dev/null
+++ b/libs/renderengine/skia/compat/SkiaBackendTexture.h
@@ -0,0 +1,85 @@
+/*
+ * 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/android/GrAHardwareBufferUtils.h>
+#include <include/core/SkColorSpace.h>
+#include <include/gpu/GrDirectContext.h>
+
+#include <android/hardware_buffer.h>
+#include <ui/GraphicTypes.h>
+
+namespace android::renderengine::skia {
+
+/**
+ * Abstraction over a Skia backend-specific texture type.
+ *
+ * This class does not do any lifecycle management, and should typically be wrapped in an
+ * AutoBackendTexture::LocalRef. Typically created via SkiaGpuContext::makeBackendTexture(...).
+ */
+class SkiaBackendTexture {
+public:
+ SkiaBackendTexture(AHardwareBuffer* buffer, bool isOutputBuffer)
+ : mIsOutputBuffer(isOutputBuffer) {
+ AHardwareBuffer_Desc desc;
+ AHardwareBuffer_describe(buffer, &desc);
+
+ mColorType = GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(desc.format);
+ }
+ virtual ~SkiaBackendTexture() = default;
+
+ // These two definitions mirror Skia's own types used for texture release callbacks, which are
+ // re-declared multiple times between context-specific implementation headers for Ganesh vs.
+ // Graphite, and within the context of SkImages vs. SkSurfaces. Our own re-declaration allows us
+ // to not pull in any implementation-specific headers here.
+ using ReleaseContext = void*;
+ using TextureReleaseProc = void (*)(ReleaseContext);
+
+ // Guaranteed to be non-null (crashes otherwise). An opaque alphaType may coerce the internal
+ // color type to RBGX.
+ virtual sk_sp<SkImage> makeImage(SkAlphaType alphaType, ui::Dataspace dataspace,
+ TextureReleaseProc releaseImageProc,
+ ReleaseContext releaseContext) = 0;
+
+ // Guaranteed to be non-null (crashes otherwise).
+ virtual sk_sp<SkSurface> makeSurface(ui::Dataspace dataspace,
+ TextureReleaseProc releaseSurfaceProc,
+ ReleaseContext releaseContext) = 0;
+
+ bool isOutputBuffer() const { return mIsOutputBuffer; }
+
+ SkColorType internalColorType() const { return mColorType; }
+
+protected:
+ // Strip alpha channel from rawColorType if alphaType is opaque (note: only works for RGBA_8888)
+ SkColorType colorTypeForImage(SkAlphaType alphaType) const {
+ if (alphaType == kOpaque_SkAlphaType) {
+ // TODO: b/40043126 - Support RGBX SkColorType for F16 and support it and 101010x as a
+ // source
+ if (internalColorType() == kRGBA_8888_SkColorType) {
+ return kRGB_888x_SkColorType;
+ }
+ }
+ return internalColorType();
+ }
+
+private:
+ const bool mIsOutputBuffer;
+ SkColorType mColorType = kUnknown_SkColorType;
+};
+
+} // 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..ddf1642
--- /dev/null
+++ b/libs/renderengine/skia/compat/SkiaGpuContext.h
@@ -0,0 +1,83 @@
+/*
+ * 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 "SkiaBackendTexture.h"
+
+#include <log/log.h>
+
+#include <memory>
+
+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!");
+ }
+
+ virtual std::unique_ptr<SkiaBackendTexture> makeBackendTexture(AHardwareBuffer* buffer,
+ bool isOutputBuffer) = 0;
+
+ /**
+ * 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/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index 7b8eb84..3a07205 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -2490,51 +2490,6 @@
expectBufferColor(rect, 0, 128, 0, 128);
}
-TEST_P(RenderEngineTest, testBorder) {
- if (!GetParam()->apiSupported()) {
- GTEST_SKIP();
- }
-
- initializeRenderEngine();
-
- const ui::Dataspace dataspace = ui::Dataspace::V0_SRGB;
-
- const auto displayRect = Rect(1080, 2280);
- renderengine::DisplaySettings display{
- .physicalDisplay = displayRect,
- .clip = displayRect,
- .outputDataspace = dataspace,
- };
- display.borderInfoList.clear();
- renderengine::BorderRenderInfo info;
- info.combinedRegion = Region(Rect(99, 99, 199, 199));
- info.width = 20.0f;
- info.color = half4{1.0f, 128.0f / 255.0f, 0.0f, 1.0f};
- display.borderInfoList.emplace_back(info);
-
- const auto greenBuffer = allocateAndFillSourceBuffer(1, 1, ubyte4(0, 255, 0, 255));
- const renderengine::LayerSettings greenLayer{
- .geometry.boundaries = FloatRect(0.f, 0.f, 1.f, 1.f),
- .source =
- renderengine::PixelSource{
- .buffer =
- renderengine::Buffer{
- .buffer = greenBuffer,
- .usePremultipliedAlpha = true,
- },
- },
- .alpha = 1.0f,
- .sourceDataspace = dataspace,
- .whitePointNits = 200.f,
- };
-
- std::vector<renderengine::LayerSettings> layers;
- layers.emplace_back(greenLayer);
- invokeDraw(display, layers);
-
- expectBufferColor(Rect(99, 99, 101, 101), 255, 128, 0, 255, 1);
-}
-
TEST_P(RenderEngineTest, testDimming) {
if (!GetParam()->apiSupported()) {
GTEST_SKIP();
diff --git a/libs/sensor/OWNERS b/libs/sensor/OWNERS
index 90c2330..7347ac7 100644
--- a/libs/sensor/OWNERS
+++ b/libs/sensor/OWNERS
@@ -1,3 +1 @@
-arthuri@google.com
bduddie@google.com
-stange@google.com
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/tracing_perfetto.h b/libs/tracing_perfetto/include/tracing_perfetto.h
index 4e3c83f..2c1c2a4 100644
--- a/libs/tracing_perfetto/include/tracing_perfetto.h
+++ b/libs/tracing_perfetto/include/tracing_perfetto.h
@@ -46,7 +46,7 @@
Result traceCounter(uint64_t category, const char* name, int64_t value);
-uint64_t getEnabledCategories();
+bool isTagEnabled(uint64_t category);
} // namespace tracing_perfetto
diff --git a/libs/tracing_perfetto/tracing_perfetto.cpp b/libs/tracing_perfetto/tracing_perfetto.cpp
index 19d1eb6..6f716ee 100644
--- a/libs/tracing_perfetto/tracing_perfetto.cpp
+++ b/libs/tracing_perfetto/tracing_perfetto.cpp
@@ -130,12 +130,13 @@
}
}
-uint64_t getEnabledCategories() {
- if (internal::isPerfettoRegistered()) {
- // TODO(b/303199244): Return only enabled categories and not all registered ones
- return internal::getDefaultCategories();
+bool isTagEnabled(uint64_t category) {
+ struct PerfettoTeCategory* perfettoTeCategory =
+ internal::toPerfettoCategory(category);
+ if (perfettoTeCategory != nullptr) {
+ return true;
} else {
- return atrace_get_enabled_tags();
+ return (atrace_get_enabled_tags() & category) != 0;
}
}
diff --git a/libs/tracing_perfetto/tracing_perfetto_internal.cpp b/libs/tracing_perfetto/tracing_perfetto_internal.cpp
index 976db7e..758ace6 100644
--- a/libs/tracing_perfetto/tracing_perfetto_internal.cpp
+++ b/libs/tracing_perfetto/tracing_perfetto_internal.cpp
@@ -143,6 +143,10 @@
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;
diff --git a/libs/ui/Gralloc5.cpp b/libs/ui/Gralloc5.cpp
index 11064ae..f14a5cf 100644
--- a/libs/ui/Gralloc5.cpp
+++ b/libs/ui/Gralloc5.cpp
@@ -23,6 +23,7 @@
#include <aidlcommonsupport/NativeHandle.h>
#include <android/binder_manager.h>
#include <android/hardware/graphics/mapper/utils/IMapperMetadataTypes.h>
+#include <android/llndk-versioning.h>
#include <binder/IPCThreadState.h>
#include <dlfcn.h>
#include <ui/FatVector.h>
@@ -91,8 +92,7 @@
void* so = nullptr;
// TODO(b/322384429) switch this to __ANDROID_API_V__ when V is finalized
- // TODO(b/302113279) use __ANDROID_VENDOR_API__ for vendor variant
- if (__builtin_available(android __ANDROID_API_FUTURE__, *)) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_FUTURE__, 202404) {
so = AServiceManager_openDeclaredPassthroughHal("mapper", mapperSuffix.c_str(),
RTLD_LOCAL | RTLD_NOW);
} else {
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/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
index c333814..1aa1077 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -37,6 +37,11 @@
args.pointerProperties[0].toolType == ToolType::FINGER;
}
+bool isFromDrawingTablet(const NotifyMotionArgs& args) {
+ return isFromSource(args.source, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS) &&
+ isStylusToolType(args.pointerProperties[0].toolType);
+}
+
bool isHoverAction(int32_t action) {
return action == AMOTION_EVENT_ACTION_HOVER_ENTER ||
action == AMOTION_EVENT_ACTION_HOVER_MOVE || action == AMOTION_EVENT_ACTION_HOVER_EXIT;
@@ -46,6 +51,13 @@
return isStylusEvent(args.source, args.pointerProperties) && isHoverAction(args.action);
}
+bool isMouseOrTouchpad(uint32_t sources) {
+ // Check if this is a mouse or touchpad, but not a drawing tablet.
+ return isFromSource(sources, AINPUT_SOURCE_MOUSE_RELATIVE) ||
+ (isFromSource(sources, AINPUT_SOURCE_MOUSE) &&
+ !isFromSource(sources, AINPUT_SOURCE_STYLUS));
+}
+
inline void notifyPointerDisplayChange(std::optional<std::tuple<int32_t, FloatPoint>> change,
PointerChoreographerPolicyInterface& policy) {
if (!change) {
@@ -55,6 +67,18 @@
policy.notifyPointerDisplayIdChanged(displayId, cursorPosition);
}
+void setIconForController(const std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle>& icon,
+ PointerControllerInterface& controller) {
+ if (std::holds_alternative<std::unique_ptr<SpriteIcon>>(icon)) {
+ if (std::get<std::unique_ptr<SpriteIcon>>(icon) == nullptr) {
+ LOG(FATAL) << "SpriteIcon should not be null";
+ }
+ controller.setCustomPointerIcon(*std::get<std::unique_ptr<SpriteIcon>>(icon));
+ } else {
+ controller.updatePointerIcon(std::get<PointerIconStyle>(icon));
+ }
+}
+
} // namespace
// --- PointerChoreographer ---
@@ -107,6 +131,8 @@
return processMouseEventLocked(args);
} else if (isFromTouchpad(args)) {
return processTouchpadEventLocked(args);
+ } else if (isFromDrawingTablet(args)) {
+ processDrawingTabletEventLocked(args);
} else if (mStylusPointerIconEnabled && isStylusHoverEvent(args)) {
processStylusHoverEventLocked(args);
} else if (isFromSource(args.source, AINPUT_SOURCE_TOUCHSCREEN)) {
@@ -189,6 +215,36 @@
return newArgs;
}
+void PointerChoreographer::processDrawingTabletEventLocked(const android::NotifyMotionArgs& args) {
+ if (args.displayId == ADISPLAY_ID_NONE) {
+ return;
+ }
+
+ if (args.getPointerCount() != 1) {
+ LOG(WARNING) << "Only drawing tablet events with a single pointer are currently supported: "
+ << args.dump();
+ }
+
+ // Use a mouse pointer controller for drawing tablets, or create one if it doesn't exist.
+ auto [it, _] = mDrawingTabletPointersByDevice.try_emplace(args.deviceId,
+ getMouseControllerConstructor(
+ args.displayId));
+
+ PointerControllerInterface& pc = *it->second;
+
+ const float x = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X);
+ const float y = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y);
+ pc.setPosition(x, y);
+ if (args.action == AMOTION_EVENT_ACTION_HOVER_EXIT) {
+ // TODO(b/315815559): Do not fade and reset the icon if the hover exit will be followed
+ // immediately by a DOWN event.
+ pc.fade(PointerControllerInterface::Transition::IMMEDIATE);
+ pc.updatePointerIcon(PointerIconStyle::TYPE_NOT_SPECIFIED);
+ } else if (canUnfadeOnDisplay(args.displayId)) {
+ pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
+ }
+}
+
/**
* When screen is touched, fade the mouse pointer on that display. We only call fade for
* ACTION_DOWN events.This would allow both mouse and touch to be used at the same time if the
@@ -255,6 +311,8 @@
const float y = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y);
pc.setPosition(x, y);
if (args.action == AMOTION_EVENT_ACTION_HOVER_EXIT) {
+ // TODO(b/315815559): Do not fade and reset the icon if the hover exit will be followed
+ // immediately by a DOWN event.
pc.fade(PointerControllerInterface::Transition::IMMEDIATE);
pc.updatePointerIcon(PointerIconStyle::TYPE_NOT_SPECIFIED);
} else if (canUnfadeOnDisplay(args.displayId)) {
@@ -284,6 +342,7 @@
std::scoped_lock _l(mLock);
mTouchPointersByDevice.erase(args.deviceId);
mStylusPointersByDevice.erase(args.deviceId);
+ mDrawingTabletPointersByDevice.erase(args.deviceId);
}
void PointerChoreographer::notifyPointerCaptureChanged(
@@ -320,6 +379,11 @@
std::string pointerControllerDump = addLinePrefix(stylusPointerController->dump(), INDENT);
dump += INDENT + std::to_string(deviceId) + " : " + pointerControllerDump;
}
+ dump += INDENT "DrawingTabletControllers:\n";
+ for (const auto& [deviceId, drawingTabletController] : mDrawingTabletPointersByDevice) {
+ std::string pointerControllerDump = addLinePrefix(drawingTabletController->dump(), INDENT);
+ dump += INDENT + std::to_string(deviceId) + " : " + pointerControllerDump;
+ }
dump += "\n";
}
@@ -361,13 +425,13 @@
std::set<int32_t /*displayId*/> mouseDisplaysToKeep;
std::set<DeviceId> touchDevicesToKeep;
std::set<DeviceId> stylusDevicesToKeep;
+ std::set<DeviceId> drawingTabletDevicesToKeep;
// Mark the displayIds or deviceIds of PointerControllers currently needed, and create
// new PointerControllers if necessary.
for (const auto& info : mInputDeviceInfos) {
const uint32_t sources = info.getSources();
- if (isFromSource(sources, AINPUT_SOURCE_MOUSE) ||
- isFromSource(sources, AINPUT_SOURCE_MOUSE_RELATIVE)) {
+ if (isMouseOrTouchpad(sources)) {
const int32_t displayId = getTargetMouseDisplayLocked(info.getAssociatedDisplayId());
mouseDisplaysToKeep.insert(displayId);
// For mice, show the cursor immediately when the device is first connected or
@@ -388,6 +452,10 @@
info.getAssociatedDisplayId() != ADISPLAY_ID_NONE) {
stylusDevicesToKeep.insert(info.getId());
}
+ if (isFromSource(sources, AINPUT_SOURCE_STYLUS | AINPUT_SOURCE_MOUSE) &&
+ info.getAssociatedDisplayId() != ADISPLAY_ID_NONE) {
+ drawingTabletDevicesToKeep.insert(info.getId());
+ }
}
// Remove PointerControllers no longer needed.
@@ -400,6 +468,9 @@
std::erase_if(mStylusPointersByDevice, [&stylusDevicesToKeep](const auto& pair) {
return stylusDevicesToKeep.find(pair.first) == stylusDevicesToKeep.end();
});
+ std::erase_if(mDrawingTabletPointersByDevice, [&drawingTabletDevicesToKeep](const auto& pair) {
+ return drawingTabletDevicesToKeep.find(pair.first) == drawingTabletDevicesToKeep.end();
+ });
std::erase_if(mMouseDevices, [&](DeviceId id) REQUIRES(mLock) {
return std::find_if(mInputDeviceInfos.begin(), mInputDeviceInfos.end(),
[id](const auto& info) { return info.getId() == id; }) ==
@@ -460,6 +531,12 @@
stylusPointerController->setDisplayViewport(viewport);
}
}
+ for (const auto& [deviceId, drawingTabletController] : mDrawingTabletPointersByDevice) {
+ const InputDeviceInfo* info = findInputDeviceLocked(deviceId);
+ if (info && info->getAssociatedDisplayId() == displayId) {
+ drawingTabletController->setDisplayViewport(viewport);
+ }
+ }
}
mViewports = viewports;
pointerDisplayChange = calculatePointerDisplayChangeToNotify();
@@ -533,42 +610,35 @@
return false;
}
const uint32_t sources = info->getSources();
- const auto stylusPointerIt = mStylusPointersByDevice.find(deviceId);
- if (isFromSource(sources, AINPUT_SOURCE_STYLUS) &&
- stylusPointerIt != mStylusPointersByDevice.end()) {
- if (std::holds_alternative<std::unique_ptr<SpriteIcon>>(icon)) {
- if (std::get<std::unique_ptr<SpriteIcon>>(icon) == nullptr) {
- LOG(FATAL) << "SpriteIcon should not be null";
- }
- stylusPointerIt->second->setCustomPointerIcon(
- *std::get<std::unique_ptr<SpriteIcon>>(icon));
- } else {
- stylusPointerIt->second->updatePointerIcon(std::get<PointerIconStyle>(icon));
+ if (isFromSource(sources, AINPUT_SOURCE_STYLUS | AINPUT_SOURCE_MOUSE)) {
+ auto it = mDrawingTabletPointersByDevice.find(deviceId);
+ if (it != mDrawingTabletPointersByDevice.end()) {
+ setIconForController(icon, *it->second);
+ return true;
}
- } else if (isFromSource(sources, AINPUT_SOURCE_MOUSE)) {
- if (const auto mousePointerIt = mMousePointersByDisplay.find(displayId);
- mousePointerIt != mMousePointersByDisplay.end()) {
- if (std::holds_alternative<std::unique_ptr<SpriteIcon>>(icon)) {
- if (std::get<std::unique_ptr<SpriteIcon>>(icon) == nullptr) {
- LOG(FATAL) << "SpriteIcon should not be null";
- }
- mousePointerIt->second->setCustomPointerIcon(
- *std::get<std::unique_ptr<SpriteIcon>>(icon));
- } else {
- mousePointerIt->second->updatePointerIcon(std::get<PointerIconStyle>(icon));
- }
+ }
+ if (isFromSource(sources, AINPUT_SOURCE_STYLUS)) {
+ auto it = mStylusPointersByDevice.find(deviceId);
+ if (it != mStylusPointersByDevice.end()) {
+ setIconForController(icon, *it->second);
+ return true;
+ }
+ }
+ if (isFromSource(sources, AINPUT_SOURCE_MOUSE)) {
+ auto it = mMousePointersByDisplay.find(displayId);
+ if (it != mMousePointersByDisplay.end()) {
+ setIconForController(icon, *it->second);
+ return true;
} else {
LOG(WARNING) << "No mouse pointer controller found for display " << displayId
<< ", device " << deviceId << ".";
return false;
}
- } else {
- LOG(WARNING) << "Cannot set pointer icon for display " << displayId << ", device "
- << deviceId << ".";
- return false;
}
- return true;
+ LOG(WARNING) << "Cannot set pointer icon for display " << displayId << ", device " << deviceId
+ << ".";
+ return false;
}
void PointerChoreographer::setPointerIconVisibility(int32_t displayId, bool visible) {
diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h
index db1488b..a3c210e 100644
--- a/services/inputflinger/PointerChoreographer.h
+++ b/services/inputflinger/PointerChoreographer.h
@@ -123,6 +123,7 @@
NotifyMotionArgs processMotion(const NotifyMotionArgs& args);
NotifyMotionArgs processMouseEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
NotifyMotionArgs processTouchpadEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
+ void processDrawingTabletEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
void processTouchscreenAndStylusEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
void processStylusHoverEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
void processDeviceReset(const NotifyDeviceResetArgs& args);
@@ -144,6 +145,8 @@
GUARDED_BY(mLock);
std::map<DeviceId, std::shared_ptr<PointerControllerInterface>> mStylusPointersByDevice
GUARDED_BY(mLock);
+ std::map<DeviceId, std::shared_ptr<PointerControllerInterface>> mDrawingTabletPointersByDevice
+ GUARDED_BY(mLock);
int32_t mDefaultMouseDisplayId GUARDED_BY(mLock);
int32_t mNotifiedPointerDisplayId GUARDED_BY(mLock);
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index dc220fe..2df86a0 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -88,12 +88,13 @@
}
// Create the input tracing backend that writes to perfetto from a single thread.
-std::unique_ptr<trace::InputTracingBackendInterface> createInputTracingBackendIfEnabled() {
+std::unique_ptr<trace::InputTracingBackendInterface> createInputTracingBackendIfEnabled(
+ trace::impl::PerfettoBackend::GetPackageUid getPackageUid) {
if (!isInputTracingEnabled()) {
return nullptr;
}
return std::make_unique<trace::impl::ThreadedBackend<trace::impl::PerfettoBackend>>(
- trace::impl::PerfettoBackend());
+ trace::impl::PerfettoBackend(getPackageUid));
}
template <class Entry>
@@ -598,6 +599,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);
@@ -891,7 +904,9 @@
// --- InputDispatcher ---
InputDispatcher::InputDispatcher(InputDispatcherPolicyInterface& policy)
- : InputDispatcher(policy, createInputTracingBackendIfEnabled()) {}
+ : InputDispatcher(policy, createInputTracingBackendIfEnabled([&policy](std::string pkg) {
+ return policy.getPackageUid(pkg);
+ })) {}
InputDispatcher::InputDispatcher(InputDispatcherPolicyInterface& policy,
std::unique_ptr<trace::InputTracingBackendInterface> traceBackend)
@@ -3105,7 +3120,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);
@@ -3119,7 +3134,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(
@@ -3189,7 +3205,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) {
@@ -3198,7 +3214,7 @@
}
const WindowInfo* otherInfo = otherHandle->getInfo();
if (canBeObscuredBy(windowHandle, otherHandle) &&
- otherInfo->frameContainsPoint(x, y)) {
+ windowOccludesTouchAt(*otherInfo, displayId, x, y, getTransformLocked(displayId))) {
return true;
}
}
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 13571b3..edca63e 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -566,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,
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
index 62c2b02..91a3e3f 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
@@ -163,6 +163,9 @@
virtual void notifyDeviceInteraction(DeviceId deviceId, nsecs_t timestamp,
const std::set<gui::Uid>& uids) = 0;
+ /* Get the UID associated with the given package. */
+ virtual gui::Uid getPackageUid(std::string package) = 0;
+
private:
// Additional key latency in case a connection is still processing some motion events.
// This will help with the case when a user touched a button that opens a new window,
diff --git a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp
index a61fa85..c431fb7 100644
--- a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp
+++ b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp
@@ -21,8 +21,26 @@
namespace android::inputdispatcher::trace {
+namespace {
+
+using namespace ftl::flag_operators;
+
+// The trace config to use for maximal tracing.
+const impl::TraceConfig CONFIG_TRACE_ALL{
+ .flags = impl::TraceFlag::TRACE_DISPATCHER_INPUT_EVENTS |
+ impl::TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH,
+ .rules = {impl::TraceRule{.level = impl::TraceLevel::TRACE_LEVEL_COMPLETE,
+ .matchAllPackages = {},
+ .matchAnyPackages = {},
+ .matchSecure{},
+ .matchImeConnectionActive = {}}},
+};
+
+} // namespace
+
void AndroidInputEventProtoConverter::toProtoMotionEvent(const TracedMotionEvent& event,
- proto::AndroidMotionEvent& outProto) {
+ proto::AndroidMotionEvent& outProto,
+ bool isRedacted) {
outProto.set_event_id(event.id);
outProto.set_event_time_nanos(event.eventTime);
outProto.set_down_time_nanos(event.downTime);
@@ -31,11 +49,15 @@
outProto.set_device_id(event.deviceId);
outProto.set_display_id(event.displayId);
outProto.set_classification(static_cast<int32_t>(event.classification));
- outProto.set_cursor_position_x(event.xCursorPosition);
- outProto.set_cursor_position_y(event.yCursorPosition);
outProto.set_flags(event.flags);
outProto.set_policy_flags(event.policyFlags);
+ if (!isRedacted) {
+ outProto.set_cursor_position_x(event.xCursorPosition);
+ outProto.set_cursor_position_y(event.yCursorPosition);
+ outProto.set_meta_state(event.metaState);
+ }
+
for (uint32_t i = 0; i < event.pointerProperties.size(); i++) {
auto* pointer = outProto.add_pointer();
@@ -49,13 +71,17 @@
const auto axis = bits.clearFirstMarkedBit();
auto axisEntry = pointer->add_axis_value();
axisEntry->set_axis(axis);
- axisEntry->set_value(coords.values[axisIndex]);
+
+ if (!isRedacted) {
+ axisEntry->set_value(coords.values[axisIndex]);
+ }
}
}
}
void AndroidInputEventProtoConverter::toProtoKeyEvent(const TracedKeyEvent& event,
- proto::AndroidKeyEvent& outProto) {
+ proto::AndroidKeyEvent& outProto,
+ bool isRedacted) {
outProto.set_event_id(event.id);
outProto.set_event_time_nanos(event.eventTime);
outProto.set_down_time_nanos(event.downTime);
@@ -63,22 +89,28 @@
outProto.set_action(event.action);
outProto.set_device_id(event.deviceId);
outProto.set_display_id(event.displayId);
- outProto.set_key_code(event.keyCode);
- outProto.set_scan_code(event.scanCode);
- outProto.set_meta_state(event.metaState);
outProto.set_repeat_count(event.repeatCount);
outProto.set_flags(event.flags);
outProto.set_policy_flags(event.policyFlags);
+
+ if (!isRedacted) {
+ outProto.set_key_code(event.keyCode);
+ outProto.set_scan_code(event.scanCode);
+ outProto.set_meta_state(event.metaState);
+ }
}
void AndroidInputEventProtoConverter::toProtoWindowDispatchEvent(
- const InputTracingBackendInterface::WindowDispatchArgs& args,
- proto::AndroidWindowInputDispatchEvent& outProto) {
+ const WindowDispatchArgs& args, proto::AndroidWindowInputDispatchEvent& outProto,
+ bool isRedacted) {
std::visit([&](auto entry) { outProto.set_event_id(entry.id); }, args.eventEntry);
outProto.set_vsync_id(args.vsyncId);
outProto.set_window_id(args.windowId);
outProto.set_resolved_flags(args.resolvedFlags);
+ if (isRedacted) {
+ return;
+ }
if (auto* motion = std::get_if<TracedMotionEvent>(&args.eventEntry); motion != nullptr) {
for (size_t i = 0; i < motion->pointerProperties.size(); i++) {
auto* pointerProto = outProto.add_dispatched_pointer();
@@ -106,4 +138,65 @@
}
}
+impl::TraceConfig AndroidInputEventProtoConverter::parseConfig(
+ proto::AndroidInputEventConfig::Decoder& protoConfig) {
+ if (protoConfig.has_mode() &&
+ protoConfig.mode() == proto::AndroidInputEventConfig::TRACE_MODE_TRACE_ALL) {
+ // User has requested the preset for maximal tracing
+ return CONFIG_TRACE_ALL;
+ }
+
+ impl::TraceConfig config;
+
+ // Parse trace flags
+ if (protoConfig.has_trace_dispatcher_input_events() &&
+ protoConfig.trace_dispatcher_input_events()) {
+ config.flags |= impl::TraceFlag::TRACE_DISPATCHER_INPUT_EVENTS;
+ }
+ if (protoConfig.has_trace_dispatcher_window_dispatch() &&
+ protoConfig.trace_dispatcher_window_dispatch()) {
+ config.flags |= impl::TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH;
+ }
+
+ // Parse trace rules
+ auto rulesIt = protoConfig.rules();
+ while (rulesIt) {
+ proto::AndroidInputEventConfig::TraceRule::Decoder protoRule{rulesIt->as_bytes()};
+ config.rules.emplace_back();
+ auto& rule = config.rules.back();
+
+ rule.level = protoRule.has_trace_level()
+ ? static_cast<impl::TraceLevel>(protoRule.trace_level())
+ : impl::TraceLevel::TRACE_LEVEL_NONE;
+
+ if (protoRule.has_match_all_packages()) {
+ auto pkgIt = protoRule.match_all_packages();
+ while (pkgIt) {
+ rule.matchAllPackages.emplace_back(pkgIt->as_std_string());
+ pkgIt++;
+ }
+ }
+
+ if (protoRule.has_match_any_packages()) {
+ auto pkgIt = protoRule.match_any_packages();
+ while (pkgIt) {
+ rule.matchAnyPackages.emplace_back(pkgIt->as_std_string());
+ pkgIt++;
+ }
+ }
+
+ if (protoRule.has_match_secure()) {
+ rule.matchSecure = protoRule.match_secure();
+ }
+
+ if (protoRule.has_match_ime_connection_active()) {
+ rule.matchImeConnectionActive = protoRule.match_ime_connection_active();
+ }
+
+ rulesIt++;
+ }
+
+ return config;
+}
+
} // namespace android::inputdispatcher::trace
diff --git a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h
index 8a46f15..887913f 100644
--- a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h
+++ b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h
@@ -16,9 +16,11 @@
#pragma once
+#include <perfetto/config/android/android_input_event_config.pbzero.h>
#include <perfetto/trace/android/android_input_event.pbzero.h>
#include "InputTracingBackendInterface.h"
+#include "InputTracingPerfettoBackendConfig.h"
namespace proto = perfetto::protos::pbzero;
@@ -30,10 +32,14 @@
class AndroidInputEventProtoConverter {
public:
static void toProtoMotionEvent(const TracedMotionEvent& event,
- proto::AndroidMotionEvent& outProto);
- static void toProtoKeyEvent(const TracedKeyEvent& event, proto::AndroidKeyEvent& outProto);
- static void toProtoWindowDispatchEvent(const InputTracingBackendInterface::WindowDispatchArgs&,
- proto::AndroidWindowInputDispatchEvent& outProto);
+ proto::AndroidMotionEvent& outProto, bool isRedacted);
+ static void toProtoKeyEvent(const TracedKeyEvent& event, proto::AndroidKeyEvent& outProto,
+ bool isRedacted);
+ static void toProtoWindowDispatchEvent(const WindowDispatchArgs&,
+ proto::AndroidWindowInputDispatchEvent& outProto,
+ bool isRedacted);
+
+ static impl::TraceConfig parseConfig(proto::AndroidInputEventConfig::Decoder& protoConfig);
};
} // namespace android::inputdispatcher::trace
diff --git a/services/inputflinger/dispatcher/trace/InputTracer.cpp b/services/inputflinger/dispatcher/trace/InputTracer.cpp
index 49e6e21..55ed5c6 100644
--- a/services/inputflinger/dispatcher/trace/InputTracer.cpp
+++ b/services/inputflinger/dispatcher/trace/InputTracer.cpp
@@ -19,6 +19,7 @@
#include "InputTracer.h"
#include <android-base/logging.h>
+#include <private/android_filesystem_config.h>
namespace android::inputdispatcher::trace::impl {
@@ -30,7 +31,7 @@
using V::operator()...;
};
-TracedEvent createTracedEvent(const MotionEntry& e) {
+TracedEvent createTracedEvent(const MotionEntry& e, EventType type) {
return TracedMotionEvent{e.id,
e.eventTime,
e.policyFlags,
@@ -50,18 +51,20 @@
e.yCursorPosition,
e.downTime,
e.pointerProperties,
- e.pointerCoords};
+ e.pointerCoords,
+ type};
}
-TracedEvent createTracedEvent(const KeyEntry& e) {
+TracedEvent createTracedEvent(const KeyEntry& e, EventType type) {
return TracedKeyEvent{e.id, e.eventTime, e.policyFlags, e.deviceId, e.source,
e.displayId, e.action, e.keyCode, e.scanCode, e.metaState,
- e.downTime, e.flags, e.repeatCount};
+ e.downTime, e.flags, e.repeatCount, type};
}
-void writeEventToBackend(const TracedEvent& event, InputTracingBackendInterface& backend) {
- std::visit(Visitor{[&](const TracedMotionEvent& e) { backend.traceMotionEvent(e); },
- [&](const TracedKeyEvent& e) { backend.traceKeyEvent(e); }},
+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);
}
@@ -69,6 +72,24 @@
return std::visit([](const auto& event) { return event.id; }, v);
}
+// Helper class to extract relevant information from InputTarget.
+struct InputTargetInfo {
+ gui::Uid uid;
+ bool isSecureWindow;
+};
+
+InputTargetInfo getTargetInfo(const InputTarget& target) {
+ if (target.windowHandle == nullptr) {
+ if (!target.connection->monitor) {
+ LOG(FATAL) << __func__ << ": Window is not set for non-monitor target";
+ }
+ // This is a global monitor, assume its target is the system.
+ return {.uid = gui::Uid{AID_SYSTEM}, .isSecureWindow = false};
+ }
+ return {target.windowHandle->getInfo()->ownerUid,
+ target.windowHandle->getInfo()->layoutParamsFlags.test(gui::WindowInfo::Flag::SECURE)};
+}
+
} // namespace
// --- InputTracer ---
@@ -82,10 +103,10 @@
if (entry.type == EventEntry::Type::MOTION) {
const auto& motion = static_cast<const MotionEntry&>(entry);
- eventState->events.emplace_back(createTracedEvent(motion));
+ eventState->events.emplace_back(createTracedEvent(motion, EventType::INBOUND));
} else if (entry.type == EventEntry::Type::KEY) {
const auto& key = static_cast<const KeyEntry&>(entry);
- eventState->events.emplace_back(createTracedEvent(key));
+ eventState->events.emplace_back(createTracedEvent(key, EventType::INBOUND));
} else {
LOG(FATAL) << "Cannot trace EventEntry of type: " << ftl::enum_string(entry.type);
}
@@ -101,15 +122,26 @@
void InputTracer::dispatchToTargetHint(const EventTrackerInterface& cookie,
const InputTarget& target) {
- if (isDerivedCookie(cookie)) {
- LOG(FATAL) << "Event target cannot be updated from a derived cookie.";
- }
auto& eventState = getState(cookie);
+ const InputTargetInfo& targetInfo = getTargetInfo(target);
if (eventState->isEventProcessingComplete) {
- // TODO(b/210460522): Disallow adding new targets after eventProcessingComplete() is called.
+ // Disallow adding new targets after eventProcessingComplete() is called.
+ if (eventState->targets.find(targetInfo.uid) == eventState->targets.end()) {
+ LOG(FATAL) << __func__ << ": Cannot add new target after eventProcessingComplete";
+ }
return;
}
- // TODO(b/210460522): Determine if the event is sensitive based on the target.
+ if (isDerivedCookie(cookie)) {
+ // Disallow adding new targets from a derived cookie.
+ if (eventState->targets.find(targetInfo.uid) == eventState->targets.end()) {
+ LOG(FATAL) << __func__ << ": Cannot add new target from a derived cookie";
+ }
+ return;
+ }
+
+ eventState->targets.emplace(targetInfo.uid);
+ eventState->isSecure |= targetInfo.isSecureWindow;
+ // TODO(b/210460522): Set events as sensitive when the IME connection is active.
}
void InputTracer::eventProcessingComplete(const EventTrackerInterface& cookie) {
@@ -132,10 +164,10 @@
if (entry.type == EventEntry::Type::MOTION) {
const auto& motion = static_cast<const MotionEntry&>(entry);
- eventState->events.emplace_back(createTracedEvent(motion));
+ eventState->events.emplace_back(createTracedEvent(motion, EventType::SYNTHESIZED));
} else if (entry.type == EventEntry::Type::KEY) {
const auto& key = static_cast<const KeyEntry&>(entry);
- eventState->events.emplace_back(createTracedEvent(key));
+ eventState->events.emplace_back(createTracedEvent(key, EventType::SYNTHESIZED));
} else {
LOG(FATAL) << "Cannot trace EventEntry of type: " << ftl::enum_string(entry.type);
}
@@ -144,7 +176,10 @@
// 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.
- writeEventToBackend(eventState->events.back(), *mBackend);
+ const auto& event = eventState->events.back();
+ const TracedEventArgs traceArgs{.isSecure = eventState->isSecure,
+ .targets = eventState->targets};
+ writeEventToBackend(event, std::move(traceArgs), *mBackend);
}
return std::make_unique<EventTrackerImpl>(std::move(eventState), /*isDerived=*/true);
}
@@ -153,37 +188,34 @@
const EventTrackerInterface& cookie) {
auto& eventState = getState(cookie);
const EventEntry& entry = *dispatchEntry.eventEntry;
+ const int32_t eventId = entry.id;
// 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) {
- const auto& motion = static_cast<const MotionEntry&>(entry);
- 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 (entry.type == EventEntry::Type::KEY) {
+ resolvedKeyRepeatCount = static_cast<const KeyEntry&>(entry).repeatCount;
}
auto tracedEventIt =
std::find_if(eventState->events.begin(), eventState->events.end(),
- [&traced](const auto& event) { return getId(traced) == getId(event); });
+ [eventId](const auto& event) { return eventId == getId(event); });
if (tracedEventIt == eventState->events.end()) {
LOG(FATAL)
<< __func__
<< ": Failed to find a previously traced event that matches the dispatched event";
}
+ if (eventState->targets.count(dispatchEntry.targetUid) == 0) {
+ LOG(FATAL) << __func__ << ": Event is being dispatched to UID that it is not targeting";
+ }
+
// 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;
- const WindowDispatchArgs windowDispatchArgs{std::move(traced),
+ // TODO(b/210460522): Pass HMAC into traceEventDispatch.
+ const WindowDispatchArgs windowDispatchArgs{*tracedEventIt,
dispatchEntry.deliveryTime,
dispatchEntry.resolvedFlags,
dispatchEntry.targetUid,
@@ -194,7 +226,9 @@
/*hmac=*/{},
resolvedKeyRepeatCount};
if (eventState->isEventProcessingComplete) {
- mBackend->traceWindowDispatch(std::move(windowDispatchArgs));
+ const TracedEventArgs traceArgs{.isSecure = eventState->isSecure,
+ .targets = eventState->targets};
+ mBackend->traceWindowDispatch(std::move(windowDispatchArgs), std::move(traceArgs));
} else {
eventState->pendingDispatchArgs.emplace_back(std::move(windowDispatchArgs));
}
@@ -214,11 +248,23 @@
void InputTracer::EventState::onEventProcessingComplete() {
// Write all of the events known so far to the trace.
for (const auto& event : events) {
- writeEventToBackend(event, *tracer.mBackend);
+ const TracedEventArgs traceArgs{.isSecure = isSecure, .targets = targets};
+ writeEventToBackend(event, traceArgs, *tracer.mBackend);
}
// Write all pending dispatch args to the trace.
for (const auto& windowDispatchArgs : pendingDispatchArgs) {
- tracer.mBackend->traceWindowDispatch(windowDispatchArgs);
+ auto tracedEventIt =
+ std::find_if(events.begin(), events.end(),
+ [id = getId(windowDispatchArgs.eventEntry)](const auto& event) {
+ return id == getId(event);
+ });
+ if (tracedEventIt == events.end()) {
+ LOG(FATAL) << __func__
+ << ": Failed to find a previously traced event that matches the dispatched "
+ "event";
+ }
+ const TracedEventArgs traceArgs{.isSecure = isSecure, .targets = targets};
+ tracer.mBackend->traceWindowDispatch(windowDispatchArgs, std::move(traceArgs));
}
pendingDispatchArgs.clear();
diff --git a/services/inputflinger/dispatcher/trace/InputTracer.h b/services/inputflinger/dispatcher/trace/InputTracer.h
index 8da9632..717bc1f 100644
--- a/services/inputflinger/dispatcher/trace/InputTracer.h
+++ b/services/inputflinger/dispatcher/trace/InputTracer.h
@@ -52,8 +52,6 @@
private:
std::unique_ptr<InputTracingBackendInterface> mBackend;
- using WindowDispatchArgs = InputTracingBackendInterface::WindowDispatchArgs;
-
// The state of a tracked event, shared across all events derived from the original event.
struct EventState {
explicit inline EventState(InputTracer& tracer) : tracer(tracer){};
@@ -66,8 +64,10 @@
bool isEventProcessingComplete{false};
// A queue to hold dispatch args from being traced until event processing is complete.
std::vector<const WindowDispatchArgs> pendingDispatchArgs;
- // TODO(b/210460522): Add additional args for tracking event sensitivity and
- // dispatch target UIDs.
+ // True if the event is targeting at least one secure window;
+ bool isSecure{false};
+ // The list of all possible UIDs that this event could be targeting.
+ std::set<gui::Uid> targets;
};
// Get the event state associated with a tracking cookie.
diff --git a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h
index 94a86b9..3ff7fab 100644
--- a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h
+++ b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h
@@ -21,12 +21,27 @@
#include <ui/Transform.h>
#include <array>
+#include <set>
#include <variant>
#include <vector>
namespace android::inputdispatcher::trace {
/**
+ * Describes the type of this event being traced, with respect to InputDispatcher.
+ */
+enum class EventType {
+ // This is an event that was reported through the InputListener interface or was injected.
+ INBOUND,
+ // This is an event that was synthesized within InputDispatcher; either being derived
+ // from an inbound event (e.g. a split motion event), or synthesized completely
+ // (e.g. a CANCEL event generated when the inbound stream is not canceled).
+ SYNTHESIZED,
+
+ ftl_last = SYNTHESIZED,
+};
+
+/**
* A representation of an Android KeyEvent used by the tracing backend.
*/
struct TracedKeyEvent {
@@ -43,6 +58,7 @@
nsecs_t downTime;
int32_t flags;
int32_t repeatCount;
+ EventType eventType;
};
/**
@@ -69,11 +85,34 @@
nsecs_t downTime;
std::vector<PointerProperties> pointerProperties;
std::vector<PointerCoords> pointerCoords;
+ EventType eventType;
};
/** 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;
+ // The list of possible UIDs that this event could be targeting.
+ std::set<gui::Uid> targets;
+};
+
+/** 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,25 +121,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;
- int32_t resolvedKeyRepeatCount;
- };
- 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..b76bec3 100644
--- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
+++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
@@ -22,6 +22,7 @@
#include <android-base/logging.h>
#include <perfetto/trace/android/android_input_event.pbzero.h>
+#include <private/android_filesystem_config.h>
namespace android::inputdispatcher::trace::impl {
@@ -29,24 +30,125 @@
constexpr auto INPUT_EVENT_TRACE_DATA_SOURCE_NAME = "android.input.inputevent";
+bool isPermanentlyAllowed(gui::Uid uid) {
+ switch (uid.val()) {
+ case AID_SYSTEM:
+ case AID_SHELL:
+ case AID_ROOT:
+ return true;
+ default:
+ return false;
+ }
+}
+
} // namespace
// --- PerfettoBackend::InputEventDataSource ---
-void PerfettoBackend::InputEventDataSource::OnStart(const perfetto::DataSourceBase::StartArgs&) {
- LOG(INFO) << "Starting perfetto trace for: " << INPUT_EVENT_TRACE_DATA_SOURCE_NAME;
+PerfettoBackend::InputEventDataSource::InputEventDataSource() : mInstanceId(sNextInstanceId++) {}
+
+void PerfettoBackend::InputEventDataSource::OnSetup(const InputEventDataSource::SetupArgs& args) {
+ LOG(INFO) << "Setting up perfetto trace for: " << INPUT_EVENT_TRACE_DATA_SOURCE_NAME
+ << ", instanceId: " << mInstanceId;
+ const auto rawConfig = args.config->android_input_event_config_raw();
+ auto protoConfig = perfetto::protos::pbzero::AndroidInputEventConfig::Decoder{rawConfig};
+
+ mConfig = AndroidInputEventProtoConverter::parseConfig(protoConfig);
}
-void PerfettoBackend::InputEventDataSource::OnStop(const perfetto::DataSourceBase::StopArgs&) {
- LOG(INFO) << "Stopping perfetto trace for: " << INPUT_EVENT_TRACE_DATA_SOURCE_NAME;
+void PerfettoBackend::InputEventDataSource::OnStart(const InputEventDataSource::StartArgs&) {
+ LOG(INFO) << "Starting perfetto trace for: " << INPUT_EVENT_TRACE_DATA_SOURCE_NAME
+ << ", instanceId: " << mInstanceId;
+}
+
+void PerfettoBackend::InputEventDataSource::OnStop(const InputEventDataSource::StopArgs&) {
+ LOG(INFO) << "Stopping perfetto trace for: " << INPUT_EVENT_TRACE_DATA_SOURCE_NAME
+ << ", instanceId: " << mInstanceId;
InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { ctx.Flush(); });
}
+void PerfettoBackend::InputEventDataSource::initializeUidMap(GetPackageUid getPackageUid) {
+ if (mUidMap.has_value()) {
+ return;
+ }
+
+ mUidMap = {{}};
+ for (const auto& rule : mConfig.rules) {
+ for (const auto& package : rule.matchAllPackages) {
+ mUidMap->emplace(package, getPackageUid(package));
+ }
+ for (const auto& package : rule.matchAnyPackages) {
+ mUidMap->emplace(package, getPackageUid(package));
+ }
+ }
+}
+
+bool PerfettoBackend::InputEventDataSource::shouldIgnoreTracedInputEvent(
+ const EventType& type) const {
+ if (!getFlags().test(TraceFlag::TRACE_DISPATCHER_INPUT_EVENTS)) {
+ // Ignore all input events.
+ return true;
+ }
+ if (!getFlags().test(TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH) &&
+ type != EventType::INBOUND) {
+ // When window dispatch tracing is disabled, ignore any events that are not inbound events.
+ return true;
+ }
+ return false;
+}
+
+TraceLevel PerfettoBackend::InputEventDataSource::resolveTraceLevel(
+ const TracedEventArgs& args) const {
+ // Check for matches with the rules in the order that they are defined.
+ for (const auto& rule : mConfig.rules) {
+ if (ruleMatches(rule, args)) {
+ return rule.level;
+ }
+ }
+ // The event is not traced if it matched zero rules.
+ return TraceLevel::TRACE_LEVEL_NONE;
+}
+
+bool PerfettoBackend::InputEventDataSource::ruleMatches(const TraceRule& rule,
+ const TracedEventArgs& args) const {
+ // By default, a rule will match all events. Return early if the rule does not match.
+
+ // Match the event if it is directed to a secure window.
+ if (rule.matchSecure.has_value() && *rule.matchSecure != args.isSecure) {
+ return false;
+ }
+
+ // Match the event if all of its target packages are explicitly allowed in the "match all" list.
+ if (!rule.matchAllPackages.empty() &&
+ !std::all_of(args.targets.begin(), args.targets.end(), [&](const auto& uid) {
+ return isPermanentlyAllowed(uid) ||
+ std::any_of(rule.matchAllPackages.begin(), rule.matchAllPackages.end(),
+ [&](const auto& pkg) { return uid == mUidMap->at(pkg); });
+ })) {
+ return false;
+ }
+
+ // Match the event if any of its target packages are allowed in the "match any" list.
+ if (!rule.matchAnyPackages.empty() &&
+ !std::any_of(args.targets.begin(), args.targets.end(), [&](const auto& uid) {
+ return std::any_of(rule.matchAnyPackages.begin(), rule.matchAnyPackages.end(),
+ [&](const auto& pkg) { return uid == mUidMap->at(pkg); });
+ })) {
+ return false;
+ }
+
+ // The event matches all matchers specified in the rule.
+ return true;
+}
+
// --- PerfettoBackend ---
std::once_flag PerfettoBackend::sDataSourceRegistrationFlag{};
-PerfettoBackend::PerfettoBackend() {
+std::atomic<int32_t> PerfettoBackend::sNextInstanceId{1};
+
+PerfettoBackend::PerfettoBackend(GetPackageUid getPackagesForUid)
+ : mGetPackageUid(getPackagesForUid) {
// Use a once-flag to ensure that the data source is only registered once per boot, since
// we never unregister the InputEventDataSource.
std::call_once(sDataSourceRegistrationFlag, []() {
@@ -63,31 +165,67 @@
});
}
-void PerfettoBackend::traceMotionEvent(const TracedMotionEvent& event) {
+void PerfettoBackend::traceMotionEvent(const TracedMotionEvent& event,
+ const TracedEventArgs& args) {
InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) {
+ auto dataSource = ctx.GetDataSourceLocked();
+ dataSource->initializeUidMap(mGetPackageUid);
+ if (dataSource->shouldIgnoreTracedInputEvent(event.eventType)) {
+ return;
+ }
+ const TraceLevel traceLevel = dataSource->resolveTraceLevel(args);
+ if (traceLevel == TraceLevel::TRACE_LEVEL_NONE) {
+ return;
+ }
+ const bool isRedacted = traceLevel == TraceLevel::TRACE_LEVEL_REDACTED;
auto tracePacket = ctx.NewTracePacket();
auto* inputEvent = tracePacket->set_android_input_event();
- auto* dispatchMotion = inputEvent->set_dispatcher_motion_event();
- AndroidInputEventProtoConverter::toProtoMotionEvent(event, *dispatchMotion);
+ auto* dispatchMotion = isRedacted ? inputEvent->set_dispatcher_motion_event_redacted()
+ : inputEvent->set_dispatcher_motion_event();
+ AndroidInputEventProtoConverter::toProtoMotionEvent(event, *dispatchMotion, isRedacted);
});
}
-void PerfettoBackend::traceKeyEvent(const TracedKeyEvent& event) {
+void PerfettoBackend::traceKeyEvent(const TracedKeyEvent& event, const TracedEventArgs& args) {
InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) {
+ auto dataSource = ctx.GetDataSourceLocked();
+ dataSource->initializeUidMap(mGetPackageUid);
+ if (dataSource->shouldIgnoreTracedInputEvent(event.eventType)) {
+ return;
+ }
+ const TraceLevel traceLevel = dataSource->resolveTraceLevel(args);
+ if (traceLevel == TraceLevel::TRACE_LEVEL_NONE) {
+ return;
+ }
+ const bool isRedacted = traceLevel == TraceLevel::TRACE_LEVEL_REDACTED;
auto tracePacket = ctx.NewTracePacket();
auto* inputEvent = tracePacket->set_android_input_event();
- auto* dispatchKey = inputEvent->set_dispatcher_key_event();
- AndroidInputEventProtoConverter::toProtoKeyEvent(event, *dispatchKey);
+ auto* dispatchKey = isRedacted ? inputEvent->set_dispatcher_key_event_redacted()
+ : inputEvent->set_dispatcher_key_event();
+ AndroidInputEventProtoConverter::toProtoKeyEvent(event, *dispatchKey, isRedacted);
});
}
-void PerfettoBackend::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs) {
+void PerfettoBackend::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs,
+ const TracedEventArgs& args) {
InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) {
+ auto dataSource = ctx.GetDataSourceLocked();
+ dataSource->initializeUidMap(mGetPackageUid);
+ if (!dataSource->getFlags().test(TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH)) {
+ return;
+ }
+ const TraceLevel traceLevel = dataSource->resolveTraceLevel(args);
+ if (traceLevel == TraceLevel::TRACE_LEVEL_NONE) {
+ return;
+ }
+ const bool isRedacted = traceLevel == TraceLevel::TRACE_LEVEL_REDACTED;
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 = isRedacted
+ ? inputEvent->set_dispatcher_window_dispatch_event_redacted()
+ : inputEvent->set_dispatcher_window_dispatch_event();
+ AndroidInputEventProtoConverter::toProtoWindowDispatchEvent(dispatchArgs, *dispatchEvent,
+ isRedacted);
});
}
diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
index fefcfb3..af1c6b7 100644
--- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
+++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
@@ -18,8 +18,12 @@
#include "InputTracingBackendInterface.h"
+#include "InputTracingPerfettoBackendConfig.h"
+
+#include <ftl/flags.h>
#include <perfetto/tracing.h>
#include <mutex>
+#include <set>
namespace android::inputdispatcher::trace::impl {
@@ -45,22 +49,46 @@
*/
class PerfettoBackend : public InputTracingBackendInterface {
public:
- PerfettoBackend();
+ using GetPackageUid = std::function<gui::Uid(std::string)>;
+
+ explicit PerfettoBackend(GetPackageUid);
~PerfettoBackend() override = default;
- void traceKeyEvent(const TracedKeyEvent&) override;
- void traceMotionEvent(const TracedMotionEvent&) override;
- void traceWindowDispatch(const WindowDispatchArgs&) override;
-
- class InputEventDataSource : public perfetto::DataSource<InputEventDataSource> {
- public:
- void OnSetup(const SetupArgs&) override {}
- void OnStart(const StartArgs&) override;
- void OnStop(const StopArgs&) override;
- };
+ void traceKeyEvent(const TracedKeyEvent&, const TracedEventArgs&) override;
+ void traceMotionEvent(const TracedMotionEvent&, const TracedEventArgs&) override;
+ void traceWindowDispatch(const WindowDispatchArgs&, const TracedEventArgs&) override;
private:
+ // Implementation of the perfetto data source.
+ // Each instance of the InputEventDataSource represents a different tracing session.
+ class InputEventDataSource : public perfetto::DataSource<InputEventDataSource> {
+ public:
+ explicit InputEventDataSource();
+
+ void OnSetup(const SetupArgs&) override;
+ void OnStart(const StartArgs&) override;
+ void OnStop(const StopArgs&) override;
+
+ void initializeUidMap(GetPackageUid);
+ bool shouldIgnoreTracedInputEvent(const EventType&) const;
+ inline ftl::Flags<TraceFlag> getFlags() const { return mConfig.flags; }
+ TraceLevel resolveTraceLevel(const TracedEventArgs&) const;
+
+ private:
+ const int32_t mInstanceId;
+ TraceConfig mConfig;
+
+ bool ruleMatches(const TraceRule&, const TracedEventArgs&) const;
+
+ std::optional<std::map<std::string, gui::Uid>> mUidMap;
+ };
+
+ // TODO(b/330360505): Query the native package manager directly from the data source,
+ // and remove this.
+ GetPackageUid mGetPackageUid;
+
static std::once_flag sDataSourceRegistrationFlag;
+ static std::atomic<int32_t> sNextInstanceId;
};
} // namespace android::inputdispatcher::trace::impl
diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackendConfig.h b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackendConfig.h
new file mode 100644
index 0000000..536e32b
--- /dev/null
+++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackendConfig.h
@@ -0,0 +1,60 @@
+/*
+ * 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 <ftl/enum.h>
+#include <ftl/flags.h>
+#include <perfetto/config/android/android_input_event_config.pbzero.h>
+#include <vector>
+
+namespace android::inputdispatcher::trace::impl {
+
+/** Flags representing the configurations that are enabled in the trace. */
+enum class TraceFlag : uint32_t {
+ // Trace details about input events processed by InputDispatcher.
+ TRACE_DISPATCHER_INPUT_EVENTS = 0x1,
+ // Trace details about an event being sent to a window by InputDispatcher.
+ TRACE_DISPATCHER_WINDOW_DISPATCH = 0x2,
+
+ ftl_last = TRACE_DISPATCHER_WINDOW_DISPATCH,
+};
+
+/** Representation of AndroidInputEventConfig::TraceLevel. */
+using TraceLevel = perfetto::protos::pbzero::AndroidInputEventConfig::TraceLevel;
+
+/** Representation of AndroidInputEventConfig::TraceRule. */
+struct TraceRule {
+ TraceLevel level;
+
+ std::vector<std::string> matchAllPackages;
+ std::vector<std::string> matchAnyPackages;
+ std::optional<bool> matchSecure;
+ std::optional<bool> matchImeConnectionActive;
+};
+
+/**
+ * A complete configuration for a tracing session.
+ *
+ * The trace rules are applied as documented in the perfetto config:
+ * /external/perfetto/protos/perfetto/config/android/android_input_event_config.proto
+ */
+struct TraceConfig {
+ ftl::Flags<TraceFlag> flags;
+ std::vector<TraceRule> rules;
+};
+
+} // namespace android::inputdispatcher::trace::impl
diff --git a/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
index 25bc227..1614789 100644
--- a/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
+++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
@@ -38,10 +38,10 @@
template <typename Backend>
ThreadedBackend<Backend>::ThreadedBackend(Backend&& innerBackend)
- : mTracerThread(
+ : mBackend(std::move(innerBackend)),
+ mTracerThread(
"InputTracer", [this]() { threadLoop(); },
- [this]() { mThreadWakeCondition.notify_all(); }),
- mBackend(std::move(innerBackend)) {}
+ [this]() { mThreadWakeCondition.notify_all(); }) {}
template <typename Backend>
ThreadedBackend<Backend>::~ThreadedBackend() {
@@ -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..3bfc72b 100644
--- a/services/inputflinger/dispatcher/trace/ThreadedBackend.h
+++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.h
@@ -38,20 +38,24 @@
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;
- InputThread mTracerThread;
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;
+ // InputThread stops when its destructor is called. Initialize it last so that it is the
+ // first thing to be destructed. This will guarantee the thread will not access other
+ // members that have already been destructed.
+ InputThread mTracerThread;
void threadLoop();
};
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index 2baf576..4d8ffb6 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -509,13 +509,8 @@
// Touchscreens and touchpad devices.
static const bool ENABLE_TOUCHPAD_GESTURES_LIBRARY =
sysprop::InputProperties::enable_touchpad_gestures_library().value_or(true);
- // TODO(b/272518665): Fix the new touchpad stack for Sony DualShock 4 (5c4, 9cc) touchpads, or
- // at least load this setting from the IDC file.
- const InputDeviceIdentifier identifier = contextPtr.getDeviceIdentifier();
- const bool isSonyDualShock4Touchpad = identifier.vendor == 0x054c &&
- (identifier.product == 0x05c4 || identifier.product == 0x09cc);
if (ENABLE_TOUCHPAD_GESTURES_LIBRARY && classes.test(InputDeviceClass::TOUCHPAD) &&
- classes.test(InputDeviceClass::TOUCH_MT) && !isSonyDualShock4Touchpad) {
+ classes.test(InputDeviceClass::TOUCH_MT)) {
mappers.push_back(createInputMapper<TouchpadInputMapper>(contextPtr, readerConfig));
} else if (classes.test(InputDeviceClass::TOUCH_MT)) {
mappers.push_back(createInputMapper<MultiTouchInputMapper>(contextPtr, readerConfig));
diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.h b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
index fb2db06..e0a7324 100644
--- a/services/inputflinger/tests/FakeInputDispatcherPolicy.h
+++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
@@ -86,6 +86,8 @@
void notifyDeviceInteraction(DeviceId deviceId, nsecs_t timestamp,
const std::set<gui::Uid>& uids) override {}
+
+ gui::Uid getPackageUid(std::string) override { return gui::Uid::INVALID; }
};
} // namespace android
diff --git a/services/inputflinger/tests/FakeInputTracingBackend.cpp b/services/inputflinger/tests/FakeInputTracingBackend.cpp
index 08738e3..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,8 +50,7 @@
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,
@@ -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/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index b3b2ee2..9e3a4f1 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -33,6 +33,7 @@
#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>
@@ -659,6 +660,8 @@
verify(*mFilteredEvent);
mFilteredEvent = nullptr;
}
+
+ gui::Uid getPackageUid(std::string) override { return gui::Uid::INVALID; }
};
} // namespace
@@ -5366,6 +5369,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,
diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp
index 8ddb672..3b2565e 100644
--- a/services/inputflinger/tests/PointerChoreographer_test.cpp
+++ b/services/inputflinger/tests/PointerChoreographer_test.cpp
@@ -46,6 +46,7 @@
constexpr int32_t ANOTHER_DISPLAY_ID = 10;
constexpr int32_t DISPLAY_WIDTH = 480;
constexpr int32_t DISPLAY_HEIGHT = 800;
+constexpr auto DRAWING_TABLET_SOURCE = AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS;
const auto MOUSE_POINTER = PointerBuilder(/*id=*/0, ToolType::MOUSE)
.axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10)
@@ -759,12 +760,28 @@
assertPointerControllerRemoved(pc);
}
-TEST_F(PointerChoreographerTest,
- WhenStylusPointerIconEnabledAndDisabledDoesNotCreatePointerController) {
+using StylusFixtureParam =
+ std::tuple</*name*/ std::string_view, /*source*/ uint32_t, ControllerType>;
+
+class StylusTestFixture : public PointerChoreographerTest,
+ public ::testing::WithParamInterface<StylusFixtureParam> {};
+
+INSTANTIATE_TEST_SUITE_P(PointerChoreographerTest, StylusTestFixture,
+ ::testing::Values(std::make_tuple("DirectStylus", AINPUT_SOURCE_STYLUS,
+ ControllerType::STYLUS),
+ std::make_tuple("DrawingTablet", DRAWING_TABLET_SOURCE,
+ ControllerType::MOUSE)),
+ [](const testing::TestParamInfo<StylusFixtureParam>& p) {
+ return std::string{std::get<0>(p.param)};
+ });
+
+TEST_P(StylusTestFixture, WhenStylusPointerIconEnabledAndDisabledDoesNotCreatePointerController) {
+ const auto& [name, source, controllerType] = GetParam();
+
// Disable stylus pointer icon and add a stylus device.
mChoreographer.setStylusPointerIconEnabled(false);
mChoreographer.notifyInputDevicesChanged(
- {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
assertPointerControllerNotCreated();
// Enable stylus pointer icon. PointerController still should not be created.
@@ -772,25 +789,25 @@
assertPointerControllerNotCreated();
}
-TEST_F(PointerChoreographerTest, WhenStylusHoverEventOccursCreatesPointerController) {
+TEST_P(StylusTestFixture, WhenStylusHoverEventOccursCreatesPointerController) {
+ const auto& [name, source, controllerType] = GetParam();
+
// Add a stylus device and enable stylus pointer icon.
mChoreographer.notifyInputDevicesChanged(
- {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
mChoreographer.setStylusPointerIconEnabled(true);
assertPointerControllerNotCreated();
// Emit hover event. Now PointerController should be created.
- mChoreographer.notifyMotion(
- MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
- .pointer(STYLUS_POINTER)
- .deviceId(DEVICE_ID)
- .displayId(DISPLAY_ID)
- .build());
- assertPointerControllerCreated(ControllerType::STYLUS);
+ mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ assertPointerControllerCreated(controllerType);
}
-TEST_F(PointerChoreographerTest,
- WhenStylusPointerIconDisabledAndHoverEventOccursDoesNotCreatePointerController) {
+TEST_F(PointerChoreographerTest, StylusHoverEventWhenStylusPointerIconDisabled) {
// Add a stylus device and disable stylus pointer icon.
mChoreographer.notifyInputDevicesChanged(
{/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
@@ -807,25 +824,43 @@
assertPointerControllerNotCreated();
}
-TEST_F(PointerChoreographerTest, WhenStylusDeviceIsRemovedRemovesPointerController) {
- // Make sure the PointerController is created.
+TEST_F(PointerChoreographerTest, DrawingTabletHoverEventWhenStylusPointerIconDisabled) {
+ // Add a drawing tablet and disable stylus pointer icon.
mChoreographer.notifyInputDevicesChanged(
- {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
- mChoreographer.setStylusPointerIconEnabled(true);
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, DISPLAY_ID)}});
+ mChoreographer.setStylusPointerIconEnabled(false);
+ assertPointerControllerNotCreated();
+
+ // Emit hover event. Drawing tablets are not affected by "stylus pointer icon" setting.
mChoreographer.notifyMotion(
- MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, DRAWING_TABLET_SOURCE)
.pointer(STYLUS_POINTER)
.deviceId(DEVICE_ID)
.displayId(DISPLAY_ID)
.build());
- auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+ assertPointerControllerCreated(ControllerType::MOUSE);
+}
+
+TEST_P(StylusTestFixture, WhenStylusDeviceIsRemovedRemovesPointerController) {
+ const auto& [name, source, controllerType] = GetParam();
+
+ // Make sure the PointerController is created.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
+ mChoreographer.setStylusPointerIconEnabled(true);
+ mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto pc = assertPointerControllerCreated(controllerType);
// Remove the device.
mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}});
assertPointerControllerRemoved(pc);
}
-TEST_F(PointerChoreographerTest, WhenStylusPointerIconDisabledRemovesPointerController) {
+TEST_F(PointerChoreographerTest, StylusPointerIconDisabledRemovesPointerController) {
// Make sure the PointerController is created.
mChoreographer.notifyInputDevicesChanged(
{/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
@@ -843,38 +878,59 @@
assertPointerControllerRemoved(pc);
}
-TEST_F(PointerChoreographerTest, SetsViewportForStylusPointerController) {
+TEST_F(PointerChoreographerTest,
+ StylusPointerIconDisabledDoesNotRemoveDrawingTabletPointerController) {
+ // Make sure the PointerController is created.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, DISPLAY_ID)}});
+ mChoreographer.setStylusPointerIconEnabled(true);
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, DRAWING_TABLET_SOURCE)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+
+ // Disable stylus pointer icon. This should not affect drawing tablets.
+ mChoreographer.setStylusPointerIconEnabled(false);
+ assertPointerControllerNotRemoved(pc);
+}
+
+TEST_P(StylusTestFixture, SetsViewportForStylusPointerController) {
+ const auto& [name, source, controllerType] = GetParam();
+
// Set viewport.
mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
// Make sure the PointerController is created.
mChoreographer.notifyInputDevicesChanged(
- {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
mChoreographer.setStylusPointerIconEnabled(true);
- mChoreographer.notifyMotion(
- MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
- .pointer(STYLUS_POINTER)
- .deviceId(DEVICE_ID)
- .displayId(DISPLAY_ID)
- .build());
- auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+ mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto pc = assertPointerControllerCreated(controllerType);
// Check that viewport is set for the PointerController.
pc->assertViewportSet(DISPLAY_ID);
}
-TEST_F(PointerChoreographerTest, WhenViewportIsSetLaterSetsViewportForStylusPointerController) {
+TEST_P(StylusTestFixture, WhenViewportIsSetLaterSetsViewportForStylusPointerController) {
+ const auto& [name, source, controllerType] = GetParam();
+
// Make sure the PointerController is created.
mChoreographer.notifyInputDevicesChanged(
- {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
mChoreographer.setStylusPointerIconEnabled(true);
- mChoreographer.notifyMotion(
- MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
- .pointer(STYLUS_POINTER)
- .deviceId(DEVICE_ID)
- .displayId(DISPLAY_ID)
- .build());
- auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+ mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto pc = assertPointerControllerCreated(controllerType);
// Check that viewport is unset.
pc->assertViewportNotSet();
@@ -886,19 +942,19 @@
pc->assertViewportSet(DISPLAY_ID);
}
-TEST_F(PointerChoreographerTest,
- WhenViewportDoesNotMatchDoesNotSetViewportForStylusPointerController) {
+TEST_P(StylusTestFixture, WhenViewportDoesNotMatchDoesNotSetViewportForStylusPointerController) {
+ const auto& [name, source, controllerType] = GetParam();
+
// Make sure the PointerController is created.
mChoreographer.notifyInputDevicesChanged(
- {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
mChoreographer.setStylusPointerIconEnabled(true);
- mChoreographer.notifyMotion(
- MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
- .pointer(STYLUS_POINTER)
- .deviceId(DEVICE_ID)
- .displayId(DISPLAY_ID)
- .build());
- auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+ mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto pc = assertPointerControllerCreated(controllerType);
// Check that viewport is unset.
pc->assertViewportNotSet();
@@ -910,24 +966,25 @@
pc->assertViewportNotSet();
}
-TEST_F(PointerChoreographerTest, StylusHoverManipulatesPointer) {
+TEST_P(StylusTestFixture, StylusHoverManipulatesPointer) {
+ const auto& [name, source, controllerType] = GetParam();
+
mChoreographer.setStylusPointerIconEnabled(true);
mChoreographer.notifyInputDevicesChanged(
- {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
// Emit hover enter event. This is for creating PointerController.
- mChoreographer.notifyMotion(
- MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
- .pointer(STYLUS_POINTER)
- .deviceId(DEVICE_ID)
- .displayId(DISPLAY_ID)
- .build());
- auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+ mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto pc = assertPointerControllerCreated(controllerType);
// Emit hover move event. After bounds are set, PointerController will update the position.
mChoreographer.notifyMotion(
- MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, source)
.pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(150).y(250))
.deviceId(DEVICE_ID)
.displayId(DISPLAY_ID)
@@ -937,7 +994,7 @@
// Emit hover exit event.
mChoreographer.notifyMotion(
- MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS)
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_EXIT, source)
.pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(150).y(250))
.deviceId(DEVICE_ID)
.displayId(DISPLAY_ID)
@@ -946,38 +1003,38 @@
ASSERT_FALSE(pc->isPointerShown());
}
-TEST_F(PointerChoreographerTest, StylusHoverManipulatesPointerForTwoDisplays) {
+TEST_P(StylusTestFixture, StylusHoverManipulatesPointerForTwoDisplays) {
+ const auto& [name, source, controllerType] = GetParam();
+
mChoreographer.setStylusPointerIconEnabled(true);
// Add two stylus devices associated to different displays.
mChoreographer.notifyInputDevicesChanged(
{/*id=*/0,
- {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID),
- generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_STYLUS, ANOTHER_DISPLAY_ID)}});
+ {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID),
+ generateTestDeviceInfo(SECOND_DEVICE_ID, source, ANOTHER_DISPLAY_ID)}});
mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
// Emit hover event with first device. This is for creating PointerController.
- mChoreographer.notifyMotion(
- MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
- .pointer(STYLUS_POINTER)
- .deviceId(DEVICE_ID)
- .displayId(DISPLAY_ID)
- .build());
- auto firstDisplayPc = assertPointerControllerCreated(ControllerType::STYLUS);
+ mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto firstDisplayPc = assertPointerControllerCreated(controllerType);
// Emit hover event with second device. This is for creating PointerController.
- mChoreographer.notifyMotion(
- MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
- .pointer(STYLUS_POINTER)
- .deviceId(SECOND_DEVICE_ID)
- .displayId(ANOTHER_DISPLAY_ID)
- .build());
+ mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+ .pointer(STYLUS_POINTER)
+ .deviceId(SECOND_DEVICE_ID)
+ .displayId(ANOTHER_DISPLAY_ID)
+ .build());
// There should be another PointerController created.
- auto secondDisplayPc = assertPointerControllerCreated(ControllerType::STYLUS);
+ auto secondDisplayPc = assertPointerControllerCreated(controllerType);
// Emit hover event with first device.
mChoreographer.notifyMotion(
- MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, source)
.pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(150).y(250))
.deviceId(DEVICE_ID)
.displayId(DISPLAY_ID)
@@ -989,7 +1046,7 @@
// Emit hover event with second device.
mChoreographer.notifyMotion(
- MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, source)
.pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(250).y(350))
.deviceId(SECOND_DEVICE_ID)
.displayId(ANOTHER_DISPLAY_ID)
@@ -1004,19 +1061,20 @@
ASSERT_TRUE(firstDisplayPc->isPointerShown());
}
-TEST_F(PointerChoreographerTest, WhenStylusDeviceIsResetRemovesPointer) {
+TEST_P(StylusTestFixture, WhenStylusDeviceIsResetRemovesPointer) {
+ const auto& [name, source, controllerType] = GetParam();
+
// Make sure the PointerController is created and there is a pointer.
mChoreographer.notifyInputDevicesChanged(
- {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
mChoreographer.setStylusPointerIconEnabled(true);
mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
- mChoreographer.notifyMotion(
- MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
- .pointer(STYLUS_POINTER)
- .deviceId(DEVICE_ID)
- .displayId(DISPLAY_ID)
- .build());
- auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+ mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto pc = assertPointerControllerCreated(controllerType);
ASSERT_TRUE(pc->isPointerShown());
// Reset the device and see the pointer controller was removed.
@@ -1460,19 +1518,20 @@
firstMousePc->assertPointerIconNotSet();
}
-TEST_F(PointerChoreographerTest, SetsPointerIconForStylus) {
+TEST_P(StylusTestFixture, SetsPointerIconForStylus) {
+ const auto& [name, source, controllerType] = GetParam();
+
// Make sure there is a PointerController.
mChoreographer.notifyInputDevicesChanged(
- {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
mChoreographer.setStylusPointerIconEnabled(true);
mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
- mChoreographer.notifyMotion(
- MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
- .pointer(STYLUS_POINTER)
- .deviceId(DEVICE_ID)
- .displayId(DISPLAY_ID)
- .build());
- auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+ mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto pc = assertPointerControllerCreated(controllerType);
pc->assertPointerIconNotSet();
// Set pointer icon for the device.
@@ -1485,28 +1544,28 @@
pc->assertPointerIconNotSet();
// The stylus stops hovering. This should cause the icon to be reset.
- mChoreographer.notifyMotion(
- MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS)
- .pointer(STYLUS_POINTER)
- .deviceId(DEVICE_ID)
- .displayId(DISPLAY_ID)
- .build());
+ mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_EXIT, source)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
pc->assertPointerIconSet(PointerIconStyle::TYPE_NOT_SPECIFIED);
}
-TEST_F(PointerChoreographerTest, SetsCustomPointerIconForStylus) {
+TEST_P(StylusTestFixture, SetsCustomPointerIconForStylus) {
+ const auto& [name, source, controllerType] = GetParam();
+
// Make sure there is a PointerController.
mChoreographer.notifyInputDevicesChanged(
- {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
mChoreographer.setStylusPointerIconEnabled(true);
mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
- mChoreographer.notifyMotion(
- MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
- .pointer(STYLUS_POINTER)
- .deviceId(DEVICE_ID)
- .displayId(DISPLAY_ID)
- .build());
- auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+ mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto pc = assertPointerControllerCreated(controllerType);
pc->assertCustomPointerIconNotSet();
// Set custom pointer icon for the device.
@@ -1522,28 +1581,28 @@
pc->assertCustomPointerIconNotSet();
}
-TEST_F(PointerChoreographerTest, SetsPointerIconForTwoStyluses) {
+TEST_P(StylusTestFixture, SetsPointerIconForTwoStyluses) {
+ const auto& [name, source, controllerType] = GetParam();
+
// Make sure there are two StylusPointerControllers. They can be on a same display.
mChoreographer.setStylusPointerIconEnabled(true);
mChoreographer.notifyInputDevicesChanged(
{/*id=*/0,
- {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID),
- generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID),
+ generateTestDeviceInfo(SECOND_DEVICE_ID, source, DISPLAY_ID)}});
mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
- mChoreographer.notifyMotion(
- MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
- .pointer(STYLUS_POINTER)
- .deviceId(DEVICE_ID)
- .displayId(DISPLAY_ID)
- .build());
- auto firstStylusPc = assertPointerControllerCreated(ControllerType::STYLUS);
- mChoreographer.notifyMotion(
- MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
- .pointer(STYLUS_POINTER)
- .deviceId(SECOND_DEVICE_ID)
- .displayId(DISPLAY_ID)
- .build());
- auto secondStylusPc = assertPointerControllerCreated(ControllerType::STYLUS);
+ mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto firstStylusPc = assertPointerControllerCreated(controllerType);
+ mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+ .pointer(STYLUS_POINTER)
+ .deviceId(SECOND_DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto secondStylusPc = assertPointerControllerCreated(controllerType);
// Set pointer icon for one stylus.
ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID, DEVICE_ID));
@@ -1557,14 +1616,16 @@
firstStylusPc->assertPointerIconNotSet();
}
-TEST_F(PointerChoreographerTest, SetsPointerIconForMouseAndStylus) {
+TEST_P(StylusTestFixture, SetsPointerIconForMouseAndStylus) {
+ const auto& [name, source, controllerType] = GetParam();
+
// Make sure there are PointerControllers for a mouse and a stylus.
mChoreographer.setStylusPointerIconEnabled(true);
mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
mChoreographer.notifyInputDevicesChanged(
{/*id=*/0,
{generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE),
- generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ generateTestDeviceInfo(SECOND_DEVICE_ID, source, DISPLAY_ID)}});
mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
mChoreographer.notifyMotion(
MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
@@ -1573,13 +1634,12 @@
.displayId(ADISPLAY_ID_NONE)
.build());
auto mousePc = assertPointerControllerCreated(ControllerType::MOUSE);
- mChoreographer.notifyMotion(
- MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
- .pointer(STYLUS_POINTER)
- .deviceId(SECOND_DEVICE_ID)
- .displayId(DISPLAY_ID)
- .build());
- auto stylusPc = assertPointerControllerCreated(ControllerType::STYLUS);
+ mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+ .pointer(STYLUS_POINTER)
+ .deviceId(SECOND_DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto stylusPc = assertPointerControllerCreated(controllerType);
// Set pointer icon for the mouse.
ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID, DEVICE_ID));
@@ -1688,7 +1748,9 @@
ASSERT_FALSE(touchpadPc->isPointerShown());
}
-TEST_F(PointerChoreographerTest, SetPointerIconVisibilityHidesPointerForStylus) {
+TEST_P(StylusTestFixture, SetPointerIconVisibilityHidesPointerForStylus) {
+ const auto& [name, source, controllerType] = GetParam();
+
mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
mChoreographer.setStylusPointerIconEnabled(true);
@@ -1696,15 +1758,14 @@
mChoreographer.setPointerIconVisibility(DISPLAY_ID, false);
mChoreographer.notifyInputDevicesChanged(
- {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
- mChoreographer.notifyMotion(
- MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
- .pointer(STYLUS_POINTER)
- .deviceId(DEVICE_ID)
- .displayId(DISPLAY_ID)
- .build());
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
+ mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID, DEVICE_ID));
- auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+ auto pc = assertPointerControllerCreated(controllerType);
pc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT);
// The pointer should not be visible.
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/CompositionEngine/include/compositionengine/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h
index 7c10fa5..e32cc02 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h
@@ -73,6 +73,9 @@
// TODO(b/121291683): These will become private/internal
virtual void preComposition(CompositionRefreshArgs&) = 0;
+ // Resolves any unfulfilled promises for release fences
+ virtual void postComposition(CompositionRefreshArgs&) = 0;
+
virtual FeatureFlags getFeatureFlags() const = 0;
// Debugging
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
index 843b5c5..dd0f985 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
@@ -34,12 +34,6 @@
using Layers = std::vector<sp<compositionengine::LayerFE>>;
using Outputs = std::vector<std::shared_ptr<compositionengine::Output>>;
-struct BorderRenderInfo {
- float width = 0;
- half4 color;
- std::vector<int32_t> layerIds;
-};
-
// Interface of composition engine power hint callback.
struct ICEPowerCallback {
virtual void notifyCpuLoadUp() = 0;
@@ -101,8 +95,6 @@
// TODO (b/255601557): Calculate per display.
std::optional<std::chrono::steady_clock::time_point> scheduledFrameTime;
- std::vector<BorderRenderInfo> borderInfoList;
-
bool hasTrustedPresentationListener = false;
ICEPowerCallback* powerCallback = nullptr;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
index a499928..4e080b3 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
@@ -133,6 +133,15 @@
uint64_t frameNumber = 0;
};
+ // Describes the states of the release fence. Checking the states allows checks
+ // to ensure that set_value() is not called on the same promise multiple times,
+ // and can indicate if the promise has been fulfilled.
+ enum class ReleaseFencePromiseStatus {
+ UNINITIALIZED, // Promise not created
+ INITIALIZED, // Promise created, fence has not been set
+ FULFILLED // Promise fulfilled, fence is set
+ };
+
// Returns the LayerSettings to pass to RenderEngine::drawLayers. The state may contain shadows
// casted by the layer or the content of the layer itself. If the layer does not render then an
// empty optional will be returned.
@@ -142,6 +151,19 @@
// Called after the layer is displayed to update the presentation fence
virtual void onLayerDisplayed(ftl::SharedFuture<FenceResult>, ui::LayerStack layerStack) = 0;
+ // Initializes a promise for a buffer release fence and provides the future for that
+ // fence. This should only be called when a promise has not yet been created, or
+ // after the previous promise has already been fulfilled. Attempting to call this
+ // when an existing promise is INITIALIZED will fail because the promise has not
+ // yet been fulfilled.
+ virtual ftl::Future<FenceResult> createReleaseFenceFuture() = 0;
+
+ // Sets promise with its buffer's release fence
+ virtual void setReleaseFence(const FenceResult& releaseFence) = 0;
+
+ // Checks if the buffer's release fence has been set
+ virtual LayerFE::ReleaseFencePromiseStatus getReleaseFencePromiseStatus() = 0;
+
// Gets some kind of identifier for the layer for debug purposes.
virtual const char* getDebugName() const = 0;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
index f1d6f52..e7d0afc 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
@@ -321,7 +321,9 @@
const Region& flashRegion,
std::vector<LayerFE::LayerSettings>& clientCompositionLayers) = 0;
virtual void setExpensiveRenderingExpected(bool enabled) = 0;
+ virtual void setHintSessionGpuStart(TimePoint startTime) = 0;
virtual void setHintSessionGpuFence(std::unique_ptr<FenceTime>&& gpuFence) = 0;
+ virtual void setHintSessionRequiresRenderEngine(bool requiresRenderEngine) = 0;
virtual bool isPowerHintSessionEnabled() = 0;
virtual void cacheClientCompositionRequests(uint32_t cacheSize) = 0;
virtual bool canPredictCompositionStrategy(const CompositionRefreshArgs&) = 0;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h
index c699557..45208dd 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h
@@ -48,6 +48,8 @@
void preComposition(CompositionRefreshArgs&) override;
+ void postComposition(CompositionRefreshArgs&) override;
+
FeatureFlags getFeatureFlags() const override;
// Debugging
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
index 2dc9a1a..eaffa9e 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
@@ -93,7 +93,9 @@
private:
bool isPowerHintSessionEnabled() override;
+ void setHintSessionGpuStart(TimePoint startTime) override;
void setHintSessionGpuFence(std::unique_ptr<FenceTime>&& gpuFence) override;
+ void setHintSessionRequiresRenderEngine(bool requiresRenderEngine) override;
DisplayId mId;
bool mIsDisconnected = false;
Hwc2::PowerAdvisor* mPowerAdvisor = nullptr;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
index 911d67b..3671f15 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
@@ -144,7 +144,9 @@
std::vector<LayerFE*>& outLayerFEs) override;
void appendRegionFlashRequests(const Region&, std::vector<LayerFE::LayerSettings>&) override;
void setExpensiveRenderingExpected(bool enabled) override;
+ void setHintSessionGpuStart(TimePoint startTime) override;
void setHintSessionGpuFence(std::unique_ptr<FenceTime>&& gpuFence) override;
+ void setHintSessionRequiresRenderEngine(bool requiresRenderEngine) override;
bool isPowerHintSessionEnabled() override;
void dumpBase(std::string&) const;
@@ -162,7 +164,6 @@
private:
void dirtyEntireOutput();
- void updateCompositionStateForBorder(const compositionengine::CompositionRefreshArgs&);
compositionengine::OutputLayer* findLayerRequestingBackgroundComposition() const;
void finishPrepareFrame();
ui::Dataspace getBestDataspace(ui::Dataspace*, bool*) const;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
index 6b1c318..f8ffde1 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
@@ -34,7 +34,6 @@
#include <compositionengine/CompositionRefreshArgs.h>
#include <compositionengine/ProjectionSpace.h>
-#include <renderengine/BorderRenderInfo.h>
#include <ui/LayerStack.h>
#include <ui/Rect.h>
#include <ui/Region.h>
@@ -166,8 +165,6 @@
bool treat170mAsSrgb = false;
- std::vector<renderengine::BorderRenderInfo> borderInfoList;
-
uint64_t lastOutputLayerHash = 0;
uint64_t outputLayerHash = 0;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h
index 9b2387b..a1b7282 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h
@@ -52,6 +52,7 @@
MOCK_METHOD1(updateCursorAsync, void(CompositionRefreshArgs&));
MOCK_METHOD1(preComposition, void(CompositionRefreshArgs&));
+ MOCK_METHOD1(postComposition, void(CompositionRefreshArgs&));
MOCK_CONST_METHOD0(getFeatureFlags, FeatureFlags());
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
index 1b8cc27..05a5d38 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
@@ -20,6 +20,7 @@
#include <compositionengine/LayerFECompositionState.h>
#include <gmock/gmock.h>
#include <ui/Fence.h>
+#include "ui/FenceResult.h"
namespace android::compositionengine::mock {
@@ -52,6 +53,9 @@
MOCK_METHOD(void, onLayerDisplayed, (ftl::SharedFuture<FenceResult>, ui::LayerStack),
(override));
+ MOCK_METHOD0(createReleaseFenceFuture, ftl::Future<FenceResult>());
+ MOCK_METHOD1(setReleaseFence, void(const FenceResult&));
+ MOCK_METHOD0(getReleaseFencePromiseStatus, LayerFE::ReleaseFencePromiseStatus());
MOCK_CONST_METHOD0(getDebugName, const char*());
MOCK_CONST_METHOD0(getSequence, int32_t());
MOCK_CONST_METHOD0(hasRoundedCorners, bool());
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
index 95ea3a4..019a058 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
@@ -134,7 +134,9 @@
MOCK_METHOD1(canPredictCompositionStrategy, bool(const CompositionRefreshArgs&));
MOCK_METHOD1(setPredictCompositionStrategy, void(bool));
MOCK_METHOD1(setTreat170mAsSrgb, void(bool));
+ MOCK_METHOD(void, setHintSessionGpuStart, (TimePoint startTime));
MOCK_METHOD(void, setHintSessionGpuFence, (std::unique_ptr<FenceTime> && gpuFence));
+ MOCK_METHOD(void, setHintSessionRequiresRenderEngine, (bool requiresRenderEngine));
MOCK_METHOD(bool, isPowerHintSessionEnabled, ());
};
diff --git a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
index b470208..4c77687 100644
--- a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
@@ -162,6 +162,7 @@
future.get();
}
}
+ postComposition(args);
}
void CompositionEngine::updateCursorAsync(CompositionRefreshArgs& args) {
@@ -192,6 +193,34 @@
mNeedsAnotherUpdate = needsAnotherUpdate;
}
+// If a buffer is latched but the layer is not presented, such as when
+// obscured by another layer, the previous buffer needs to be released. We find
+// these buffers and fire a NO_FENCE to release it. This ensures that all
+// promises for buffer releases are fulfilled at the end of composition.
+void CompositionEngine::postComposition(CompositionRefreshArgs& args) {
+ if (FlagManager::getInstance().ce_fence_promise()) {
+ ATRACE_CALL();
+ ALOGV(__FUNCTION__);
+
+ for (auto& layerFE : args.layers) {
+ if (layerFE->getReleaseFencePromiseStatus() ==
+ LayerFE::ReleaseFencePromiseStatus::INITIALIZED) {
+ layerFE->setReleaseFence(Fence::NO_FENCE);
+ }
+ }
+
+ // List of layersWithQueuedFrames does not necessarily overlap with
+ // list of layers, so those layersWithQueuedFrames also need any
+ // unfulfilled promises to be resolved for completeness.
+ for (auto& layerFE : args.layersWithQueuedFrames) {
+ if (layerFE->getReleaseFencePromiseStatus() ==
+ LayerFE::ReleaseFencePromiseStatus::INITIALIZED) {
+ layerFE->setReleaseFence(Fence::NO_FENCE);
+ }
+ }
+ }
+}
+
FeatureFlags CompositionEngine::getFeatureFlags() const {
return {};
}
diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp
index 6428d08..3d35704 100644
--- a/services/surfaceflinger/CompositionEngine/src/Display.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp
@@ -252,10 +252,6 @@
auto& hwc = getCompositionEngine().getHwComposer();
const bool requiresClientComposition = anyLayersRequireClientComposition();
- if (isPowerHintSessionEnabled()) {
- mPowerAdvisor->setRequiresClientComposition(mId, requiresClientComposition);
- }
-
const TimePoint hwcValidateStartTime = TimePoint::now();
if (status_t result = hwc.getDeviceCompositionChanges(*halDisplayId, requiresClientComposition,
@@ -416,10 +412,20 @@
return mPowerAdvisor != nullptr && mPowerAdvisor->usePowerHintSession();
}
+// For ADPF GPU v0 this is expected to set start time to when the GPU commands are submitted with
+// fence returned, i.e. when RenderEngine flushes the commands and returns the draw fence.
+void Display::setHintSessionGpuStart(TimePoint startTime) {
+ mPowerAdvisor->setGpuStartTime(mId, startTime);
+}
+
void Display::setHintSessionGpuFence(std::unique_ptr<FenceTime>&& gpuFence) {
mPowerAdvisor->setGpuFenceTime(mId, std::move(gpuFence));
}
+void Display::setHintSessionRequiresRenderEngine(bool requiresRenderEngine) {
+ mPowerAdvisor->setRequiresRenderEngine(mId, requiresRenderEngine);
+}
+
void Display::finishFrame(GpuCompositionResult&& result) {
// We only need to actually compose the display if:
// 1) It is being handled by hardware composer, which may need this to
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index 921e05d..1f01b57 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -16,6 +16,7 @@
#include <SurfaceFlingerProperties.sysprop.h>
#include <android-base/stringprintf.h>
+#include <common/FlagManager.h>
#include <compositionengine/CompositionEngine.h>
#include <compositionengine/CompositionRefreshArgs.h>
#include <compositionengine/DisplayColorProfile.h>
@@ -463,6 +464,10 @@
setColorTransform(refreshArgs);
beginFrame();
+ if (isPowerHintSessionEnabled()) {
+ // always reset the flag before the composition prediction
+ setHintSessionRequiresRenderEngine(false);
+ }
GpuCompositionResult result;
const bool predictCompositionStrategy = canPredictCompositionStrategy(refreshArgs);
if (predictCompositionStrategy) {
@@ -818,44 +823,6 @@
forceClientComposition = false;
}
}
-
- updateCompositionStateForBorder(refreshArgs);
-}
-
-void Output::updateCompositionStateForBorder(
- const compositionengine::CompositionRefreshArgs& refreshArgs) {
- std::unordered_map<int32_t, const Region*> layerVisibleRegionMap;
- // Store a map of layerId to their computed visible region.
- for (auto* layer : getOutputLayersOrderedByZ()) {
- int layerId = (layer->getLayerFE()).getSequence();
- layerVisibleRegionMap[layerId] = &((layer->getState()).visibleRegion);
- }
- OutputCompositionState& outputCompositionState = editState();
- outputCompositionState.borderInfoList.clear();
- bool clientComposeTopLayer = false;
- for (const auto& borderInfo : refreshArgs.borderInfoList) {
- renderengine::BorderRenderInfo info;
- for (const auto& id : borderInfo.layerIds) {
- info.combinedRegion.orSelf(*(layerVisibleRegionMap[id]));
- }
-
- if (!info.combinedRegion.isEmpty()) {
- info.width = borderInfo.width;
- info.color = borderInfo.color;
- outputCompositionState.borderInfoList.emplace_back(std::move(info));
- clientComposeTopLayer = true;
- }
- }
-
- // In this situation we must client compose the top layer instead of using hwc
- // because we want to draw the border above all else.
- // This could potentially cause a bit of a performance regression if the top
- // layer would have been rendered using hwc originally.
- // TODO(b/227656283): Measure system's performance before enabling the border feature
- if (clientComposeTopLayer) {
- auto topLayer = getOutputLayerOrderedByZByIndex(getOutputLayerCount() - 1);
- (topLayer->editState()).forceClientComposition = true;
- }
}
void Output::planComposition() {
@@ -1247,8 +1214,7 @@
if (!optReadyFence) {
return;
}
-
- if (isPowerHintSessionEnabled()) {
+ if (isPowerHintSessionEnabled() && !FlagManager::getInstance().adpf_gpu_sf()) {
// get fence end time to know when gpu is complete in display
setHintSessionGpuFence(
std::make_unique<FenceTime>(sp<Fence>::make(dup(optReadyFence->get()))));
@@ -1392,8 +1358,20 @@
// If rendering was not successful, remove the request from the cache.
mClientCompositionRequestCache->remove(tex->getBuffer()->getId());
}
-
const auto fence = std::move(fenceResult).value_or(Fence::NO_FENCE);
+ if (isPowerHintSessionEnabled()) {
+ if (fence != Fence::NO_FENCE && fence->isValid() &&
+ !outputCompositionState.reusedClientComposition) {
+ setHintSessionRequiresRenderEngine(true);
+ if (FlagManager::getInstance().adpf_gpu_sf()) {
+ // the order of the two calls here matters as we should check if the previously
+ // tracked fence has signaled first and archive the previous start time
+ setHintSessionGpuStart(TimePoint::now());
+ setHintSessionGpuFence(
+ std::make_unique<FenceTime>(sp<Fence>::make(dup(fence->get()))));
+ }
+ }
+ }
if (auto timeStats = getCompositionEngine().getTimeStats()) {
if (fence->isValid()) {
@@ -1443,13 +1421,6 @@
// Compute the global color transform matrix.
clientCompositionDisplay.colorTransform = outputState.colorTransformMatrix;
- for (auto& info : outputState.borderInfoList) {
- renderengine::BorderRenderInfo borderInfo;
- borderInfo.width = info.width;
- borderInfo.color = info.color;
- borderInfo.combinedRegion = info.combinedRegion;
- clientCompositionDisplay.borderInfoList.emplace_back(std::move(borderInfo));
- }
clientCompositionDisplay.deviceHandlesColorTransform =
outputState.usesDeviceComposition || getSkipColorTransform();
return clientCompositionDisplay;
@@ -1576,10 +1547,18 @@
// The base class does nothing with this call.
}
+void Output::setHintSessionGpuStart(TimePoint) {
+ // The base class does nothing with this call.
+}
+
void Output::setHintSessionGpuFence(std::unique_ptr<FenceTime>&&) {
// The base class does nothing with this call.
}
+void Output::setHintSessionRequiresRenderEngine(bool) {
+ // The base class does nothing with this call.
+}
+
bool Output::isPowerHintSessionEnabled() {
return false;
}
@@ -1621,9 +1600,13 @@
releaseFence =
Fence::merge("LayerRelease", releaseFence, frame.clientTargetAcquireFence);
}
- layer->getLayerFE()
- .onLayerDisplayed(ftl::yield<FenceResult>(std::move(releaseFence)).share(),
- outputState.layerFilter.layerStack);
+ if (FlagManager::getInstance().ce_fence_promise()) {
+ layer->getLayerFE().setReleaseFence(releaseFence);
+ } else {
+ layer->getLayerFE()
+ .onLayerDisplayed(ftl::yield<FenceResult>(std::move(releaseFence)).share(),
+ outputState.layerFilter.layerStack);
+ }
}
// We've got a list of layers needing fences, that are disjoint with
@@ -1631,8 +1614,12 @@
// supply them with the present fence.
for (auto& weakLayer : mReleasedLayers) {
if (const auto layer = weakLayer.promote()) {
- layer->onLayerDisplayed(ftl::yield<FenceResult>(frame.presentFence).share(),
- outputState.layerFilter.layerStack);
+ if (FlagManager::getInstance().ce_fence_promise()) {
+ layer->setReleaseFence(frame.presentFence);
+ } else {
+ layer->onLayerDisplayed(ftl::yield<FenceResult>(frame.presentFence).share(),
+ outputState.layerFilter.layerStack);
+ }
}
}
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
index 39cf671..6683e67 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
@@ -67,11 +67,6 @@
out.append("\n ");
dumpVal(out, "treat170mAsSrgb", treat170mAsSrgb);
-
- out.append("\n");
- for (const auto& borderRenderInfo : borderInfoList) {
- dumpVal(out, "borderRegion", borderRenderInfo.combinedRegion);
- }
out.append("\n");
}
diff --git a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
index 042010e..639164d 100644
--- a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
@@ -28,6 +28,7 @@
#include "MockHWComposer.h"
#include "TimeStats/TimeStats.h"
+#include "gmock/gmock.h"
#include <variant>
@@ -90,14 +91,16 @@
// These are the overridable functions CompositionEngine::present() may
// call, and have separate test coverage.
MOCK_METHOD1(preComposition, void(CompositionRefreshArgs&));
+ MOCK_METHOD1(postComposition, void(CompositionRefreshArgs&));
};
StrictMock<CompositionEnginePartialMock> mEngine;
};
TEST_F(CompositionEnginePresentTest, worksWithEmptyRequest) {
- // present() always calls preComposition()
+ // present() always calls preComposition() and postComposition()
EXPECT_CALL(mEngine, preComposition(Ref(mRefreshArgs)));
+ EXPECT_CALL(mEngine, postComposition(Ref(mRefreshArgs)));
mEngine.present(mRefreshArgs);
}
@@ -126,6 +129,9 @@
EXPECT_CALL(*mOutput3, present(Ref(mRefreshArgs)))
.WillOnce(Return(ftl::yield<std::monostate>({})));
+ // present() always calls postComposition()
+ EXPECT_CALL(mEngine, postComposition(Ref(mRefreshArgs)));
+
mRefreshArgs.outputs = {mOutput1, mOutput2, mOutput3};
mEngine.present(mRefreshArgs);
}
@@ -481,5 +487,29 @@
mEngine.present(mRefreshArgs);
}
+struct CompositionEnginePostCompositionTest : public CompositionEngineTest {
+ sp<StrictMock<mock::LayerFE>> mLayer1FE = sp<StrictMock<mock::LayerFE>>::make();
+ sp<StrictMock<mock::LayerFE>> mLayer2FE = sp<StrictMock<mock::LayerFE>>::make();
+ sp<StrictMock<mock::LayerFE>> mLayer3FE = sp<StrictMock<mock::LayerFE>>::make();
+};
+
+TEST_F(CompositionEnginePostCompositionTest, postCompositionReleasesAllFences) {
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, true);
+ ASSERT_TRUE(FlagManager::getInstance().ce_fence_promise());
+
+ EXPECT_CALL(*mLayer1FE, getReleaseFencePromiseStatus)
+ .WillOnce(Return(LayerFE::ReleaseFencePromiseStatus::FULFILLED));
+ EXPECT_CALL(*mLayer2FE, getReleaseFencePromiseStatus)
+ .WillOnce(Return(LayerFE::ReleaseFencePromiseStatus::FULFILLED));
+ EXPECT_CALL(*mLayer3FE, getReleaseFencePromiseStatus)
+ .WillOnce(Return(LayerFE::ReleaseFencePromiseStatus::INITIALIZED));
+ mRefreshArgs.layers = {mLayer1FE, mLayer2FE, mLayer3FE};
+
+ EXPECT_CALL(*mLayer1FE, setReleaseFence(_)).Times(0);
+ EXPECT_CALL(*mLayer2FE, setReleaseFence(_)).Times(0);
+ EXPECT_CALL(*mLayer3FE, setReleaseFence(_)).Times(1);
+
+ mEngine.postComposition(mRefreshArgs);
+}
} // namespace
} // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h b/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h
index 7253354..d0843a2 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h
@@ -42,6 +42,7 @@
MOCK_METHOD(void, reportActualWorkDuration, (), (override));
MOCK_METHOD(void, enablePowerHintSession, (bool enabled), (override));
MOCK_METHOD(bool, startPowerHintSession, (std::vector<int32_t> && threadIds), (override));
+ MOCK_METHOD(void, setGpuStartTime, (DisplayId displayId, TimePoint startTime), (override));
MOCK_METHOD(void, setGpuFenceTime,
(DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime), (override));
MOCK_METHOD(void, setHwcValidateTiming,
@@ -51,8 +52,8 @@
(DisplayId displayId, TimePoint presentStartTime, TimePoint presentEndTime),
(override));
MOCK_METHOD(void, setSkippedValidate, (DisplayId displayId, bool skipped), (override));
- MOCK_METHOD(void, setRequiresClientComposition,
- (DisplayId displayId, bool requiresClientComposition), (override));
+ MOCK_METHOD(void, setRequiresRenderEngine, (DisplayId displayId, bool requiresRenderEngine),
+ (override));
MOCK_METHOD(void, setExpectedPresentTime, (TimePoint expectedPresentTime), (override));
MOCK_METHOD(void, setSfPresentTiming, (TimePoint presentFenceTime, TimePoint presentEndTime),
(override));
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index 799c7ed..b4789f1 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -2017,8 +2017,15 @@
MOCK_METHOD0(presentFrameAndReleaseLayers, void());
MOCK_METHOD1(renderCachedSets, void(const compositionengine::CompositionRefreshArgs&));
MOCK_METHOD1(canPredictCompositionStrategy, bool(const CompositionRefreshArgs&));
+ MOCK_METHOD(void, setHintSessionRequiresRenderEngine, (bool requiresRenderEngine),
+ (override));
+ MOCK_METHOD(bool, isPowerHintSessionEnabled, (), (override));
};
+ OutputPresentTest() {
+ EXPECT_CALL(mOutput, isPowerHintSessionEnabled()).WillRepeatedly(Return(true));
+ }
+
StrictMock<OutputPartialMock> mOutput;
};
@@ -2032,6 +2039,7 @@
EXPECT_CALL(mOutput, writeCompositionState(Ref(args)));
EXPECT_CALL(mOutput, setColorTransform(Ref(args)));
EXPECT_CALL(mOutput, beginFrame());
+ EXPECT_CALL(mOutput, setHintSessionRequiresRenderEngine(false));
EXPECT_CALL(mOutput, canPredictCompositionStrategy(Ref(args))).WillOnce(Return(false));
EXPECT_CALL(mOutput, prepareFrame());
EXPECT_CALL(mOutput, devOptRepaintFlash(Ref(args)));
@@ -2052,6 +2060,7 @@
EXPECT_CALL(mOutput, writeCompositionState(Ref(args)));
EXPECT_CALL(mOutput, setColorTransform(Ref(args)));
EXPECT_CALL(mOutput, beginFrame());
+ EXPECT_CALL(mOutput, setHintSessionRequiresRenderEngine(false));
EXPECT_CALL(mOutput, canPredictCompositionStrategy(Ref(args))).WillOnce(Return(true));
EXPECT_CALL(mOutput, prepareFrameAsync());
EXPECT_CALL(mOutput, devOptRepaintFlash(Ref(args)));
@@ -2989,6 +2998,9 @@
MOCK_METHOD0(updateProtectedContentState, void());
MOCK_METHOD2(dequeueRenderBuffer,
bool(base::unique_fd*, std::shared_ptr<renderengine::ExternalTexture>*));
+ MOCK_METHOD(void, setHintSessionGpuFence, (std::unique_ptr<FenceTime> && gpuFence),
+ (override));
+ MOCK_METHOD(bool, isPowerHintSessionEnabled, (), (override));
};
OutputFinishFrameTest() {
@@ -2997,6 +3009,8 @@
mOutput.setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(mRenderSurface));
EXPECT_CALL(mOutput, getCompositionEngine()).WillRepeatedly(ReturnRef(mCompositionEngine));
EXPECT_CALL(mCompositionEngine, getRenderEngine()).WillRepeatedly(ReturnRef(mRenderEngine));
+ EXPECT_CALL(mOutput, isPowerHintSessionEnabled()).WillRepeatedly(Return(true));
+ SET_FLAG_FOR_TEST(flags::adpf_gpu_sf, false);
}
StrictMock<OutputPartialMock> mOutput;
@@ -3031,6 +3045,23 @@
EXPECT_CALL(mOutput, dequeueRenderBuffer(_, _)).WillOnce(Return(true));
EXPECT_CALL(mOutput, composeSurfaces(RegionEq(Region::INVALID_REGION), _, _))
.WillOnce(Return(ByMove(base::unique_fd())));
+ EXPECT_CALL(mOutput, setHintSessionGpuFence(_));
+ EXPECT_CALL(*mRenderSurface, queueBuffer(_, 1.f));
+
+ impl::GpuCompositionResult result;
+ mOutput.finishFrame(std::move(result));
+}
+
+TEST_F(OutputFinishFrameTest, queuesBufferIfComposeSurfacesReturnsAFenceWithAdpfGpuOn) {
+ SET_FLAG_FOR_TEST(flags::adpf_gpu_sf, true);
+ mOutput.mState.isEnabled = true;
+
+ InSequence seq;
+ EXPECT_CALL(mOutput, updateProtectedContentState());
+ EXPECT_CALL(mOutput, dequeueRenderBuffer(_, _)).WillOnce(Return(true));
+ EXPECT_CALL(mOutput, composeSurfaces(RegionEq(Region::INVALID_REGION), _, _))
+ .WillOnce(Return(ByMove(base::unique_fd())));
+ EXPECT_CALL(mOutput, setHintSessionGpuFence(_)).Times(0);
EXPECT_CALL(*mRenderSurface, queueBuffer(_, 1.f));
impl::GpuCompositionResult result;
@@ -3058,6 +3089,7 @@
.WillOnce(DoAll(SetArgPointee<1>(texture), Return(true)));
EXPECT_CALL(mOutput, composeSurfaces(RegionEq(Region::INVALID_REGION), _, _))
.WillOnce(Return(ByMove(base::unique_fd())));
+ EXPECT_CALL(mOutput, setHintSessionGpuFence(_));
EXPECT_CALL(*mRenderSurface, queueBuffer(_, 2.f));
impl::GpuCompositionResult result;
@@ -3068,6 +3100,7 @@
mOutput.mState.isEnabled = true;
mOutput.mState.strategyPrediction = CompositionStrategyPredictionState::SUCCESS;
InSequence seq;
+ EXPECT_CALL(mOutput, setHintSessionGpuFence(_));
EXPECT_CALL(*mRenderSurface, queueBuffer(_, 1.f));
impl::GpuCompositionResult result;
@@ -3090,6 +3123,7 @@
composeSurfaces(RegionEq(Region::INVALID_REGION), result.buffer,
Eq(ByRef(result.fence))))
.WillOnce(Return(ByMove(base::unique_fd())));
+ EXPECT_CALL(mOutput, setHintSessionGpuFence(_));
EXPECT_CALL(*mRenderSurface, queueBuffer(_, 1.f));
mOutput.finishFrame(std::move(result));
}
@@ -3164,6 +3198,8 @@
}
TEST_F(OutputPostFramebufferTest, releaseFencesAreSentToLayerFE) {
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, false);
+ ASSERT_FALSE(FlagManager::getInstance().ce_fence_promise());
// Simulate getting release fences from each layer, and ensure they are passed to the
// front-end layer interface for each layer correctly.
@@ -3205,7 +3241,51 @@
mOutput.presentFrameAndReleaseLayers();
}
+TEST_F(OutputPostFramebufferTest, releaseFencesAreSetInLayerFE) {
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, true);
+ ASSERT_TRUE(FlagManager::getInstance().ce_fence_promise());
+ // Simulate getting release fences from each layer, and ensure they are passed to the
+ // front-end layer interface for each layer correctly.
+
+ mOutput.mState.isEnabled = true;
+
+ // Create three unique fence instances
+ sp<Fence> layer1Fence = sp<Fence>::make();
+ sp<Fence> layer2Fence = sp<Fence>::make();
+ sp<Fence> layer3Fence = sp<Fence>::make();
+
+ Output::FrameFences frameFences;
+ frameFences.layerFences.emplace(&mLayer1.hwc2Layer, layer1Fence);
+ frameFences.layerFences.emplace(&mLayer2.hwc2Layer, layer2Fence);
+ frameFences.layerFences.emplace(&mLayer3.hwc2Layer, layer3Fence);
+
+ EXPECT_CALL(mOutput, presentFrame()).WillOnce(Return(frameFences));
+ EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted());
+
+ // Compare the pointers values of each fence to make sure the correct ones
+ // are passed. This happens to work with the current implementation, but
+ // would not survive certain calls like Fence::merge() which would return a
+ // new instance.
+ EXPECT_CALL(*mLayer1.layerFE, setReleaseFence(_))
+ .WillOnce([&layer1Fence](FenceResult releaseFence) {
+ EXPECT_EQ(FenceResult(layer1Fence), releaseFence);
+ });
+ EXPECT_CALL(*mLayer2.layerFE, setReleaseFence(_))
+ .WillOnce([&layer2Fence](FenceResult releaseFence) {
+ EXPECT_EQ(FenceResult(layer2Fence), releaseFence);
+ });
+ EXPECT_CALL(*mLayer3.layerFE, setReleaseFence(_))
+ .WillOnce([&layer3Fence](FenceResult releaseFence) {
+ EXPECT_EQ(FenceResult(layer3Fence), releaseFence);
+ });
+
+ mOutput.presentFrameAndReleaseLayers();
+}
+
TEST_F(OutputPostFramebufferTest, releaseFencesIncludeClientTargetAcquireFence) {
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, false);
+ ASSERT_FALSE(FlagManager::getInstance().ce_fence_promise());
+
mOutput.mState.isEnabled = true;
mOutput.mState.usesClientComposition = true;
@@ -3228,7 +3308,35 @@
mOutput.presentFrameAndReleaseLayers();
}
+TEST_F(OutputPostFramebufferTest, setReleaseFencesIncludeClientTargetAcquireFence) {
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, true);
+ ASSERT_TRUE(FlagManager::getInstance().ce_fence_promise());
+
+ mOutput.mState.isEnabled = true;
+ mOutput.mState.usesClientComposition = true;
+
+ Output::FrameFences frameFences;
+ frameFences.clientTargetAcquireFence = sp<Fence>::make();
+ frameFences.layerFences.emplace(&mLayer1.hwc2Layer, sp<Fence>::make());
+ frameFences.layerFences.emplace(&mLayer2.hwc2Layer, sp<Fence>::make());
+ frameFences.layerFences.emplace(&mLayer3.hwc2Layer, sp<Fence>::make());
+
+ EXPECT_CALL(mOutput, presentFrame()).WillOnce(Return(frameFences));
+ EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted());
+
+ // Fence::merge is called, and since none of the fences are actually valid,
+ // Fence::NO_FENCE is returned and passed to each setReleaseFence() call.
+ // This is the best we can do without creating a real kernel fence object.
+ EXPECT_CALL(*mLayer1.layerFE, setReleaseFence).WillOnce(Return());
+ EXPECT_CALL(*mLayer2.layerFE, setReleaseFence).WillOnce(Return());
+ EXPECT_CALL(*mLayer3.layerFE, setReleaseFence).WillOnce(Return());
+ mOutput.presentFrameAndReleaseLayers();
+}
+
TEST_F(OutputPostFramebufferTest, releasedLayersSentPresentFence) {
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, false);
+ ASSERT_FALSE(FlagManager::getInstance().ce_fence_promise());
+
mOutput.mState.isEnabled = true;
mOutput.mState.usesClientComposition = true;
@@ -3276,6 +3384,54 @@
EXPECT_TRUE(mOutput.getReleasedLayersForTest().empty());
}
+TEST_F(OutputPostFramebufferTest, setReleasedLayersSentPresentFence) {
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, true);
+ ASSERT_TRUE(FlagManager::getInstance().ce_fence_promise());
+
+ mOutput.mState.isEnabled = true;
+ mOutput.mState.usesClientComposition = true;
+
+ // This should happen even if there are no (current) output layers.
+ EXPECT_CALL(mOutput, getOutputLayerCount()).WillOnce(Return(0u));
+
+ // Load up the released layers with some mock instances
+ sp<StrictMock<mock::LayerFE>> releasedLayer1 = sp<StrictMock<mock::LayerFE>>::make();
+ sp<StrictMock<mock::LayerFE>> releasedLayer2 = sp<StrictMock<mock::LayerFE>>::make();
+ sp<StrictMock<mock::LayerFE>> releasedLayer3 = sp<StrictMock<mock::LayerFE>>::make();
+ Output::ReleasedLayers layers;
+ layers.push_back(releasedLayer1);
+ layers.push_back(releasedLayer2);
+ layers.push_back(releasedLayer3);
+ mOutput.setReleasedLayers(std::move(layers));
+
+ // Set up a fake present fence
+ sp<Fence> presentFence = sp<Fence>::make();
+ Output::FrameFences frameFences;
+ frameFences.presentFence = presentFence;
+
+ EXPECT_CALL(mOutput, presentFrame()).WillOnce(Return(frameFences));
+ EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted());
+
+ // Each released layer should be given the presentFence.
+ EXPECT_CALL(*releasedLayer1, setReleaseFence(_))
+ .WillOnce([&presentFence](FenceResult fenceResult) {
+ EXPECT_EQ(FenceResult(presentFence), fenceResult);
+ });
+ EXPECT_CALL(*releasedLayer2, setReleaseFence(_))
+ .WillOnce([&presentFence](FenceResult fenceResult) {
+ EXPECT_EQ(FenceResult(presentFence), fenceResult);
+ });
+ EXPECT_CALL(*releasedLayer3, setReleaseFence(_))
+ .WillOnce([&presentFence](FenceResult fenceResult) {
+ EXPECT_EQ(FenceResult(presentFence), fenceResult);
+ });
+
+ mOutput.presentFrameAndReleaseLayers();
+
+ // After the call the list of released layers should have been cleared.
+ EXPECT_TRUE(mOutput.getReleasedLayersForTest().empty());
+}
+
/*
* Output::composeSurfaces()
*/
@@ -3293,8 +3449,10 @@
MOCK_METHOD2(appendRegionFlashRequests,
void(const Region&, std::vector<LayerFE::LayerSettings>&));
MOCK_METHOD1(setExpensiveRenderingExpected, void(bool));
+ MOCK_METHOD(void, setHintSessionGpuStart, (TimePoint startTime), (override));
MOCK_METHOD(void, setHintSessionGpuFence, (std::unique_ptr<FenceTime> && gpuFence),
(override));
+ MOCK_METHOD(void, setHintSessionRequiresRenderEngine, (bool), (override));
MOCK_METHOD(bool, isPowerHintSessionEnabled, (), (override));
};
@@ -3325,6 +3483,8 @@
EXPECT_CALL(mCompositionEngine, getTimeStats()).WillRepeatedly(Return(mTimeStats.get()));
EXPECT_CALL(*mDisplayColorProfile, getHdrCapabilities())
.WillRepeatedly(ReturnRef(kHdrCapabilities));
+ EXPECT_CALL(mOutput, isPowerHintSessionEnabled()).WillRepeatedly(Return(true));
+ SET_FLAG_FOR_TEST(flags::adpf_gpu_sf, false);
}
struct ExecuteState : public CallOrderStateMachineHelper<TestType, ExecuteState> {
@@ -3590,6 +3750,9 @@
EXPECT_FALSE(mOutput.mState.reusedClientComposition);
// We do not expect another call to draw layers.
+ EXPECT_CALL(mOutput, setHintSessionRequiresRenderEngine(_)).Times(0);
+ EXPECT_CALL(mOutput, setHintSessionGpuStart(_)).Times(0);
+ EXPECT_CALL(mOutput, setHintSessionGpuFence(_)).Times(0);
verify().execute().expectAFenceWasReturned();
EXPECT_TRUE(mOutput.mState.reusedClientComposition);
}
@@ -3618,14 +3781,62 @@
EXPECT_CALL(*mRenderSurface, dequeueBuffer(_))
.WillOnce(Return(mOutputBuffer))
.WillOnce(Return(otherOutputBuffer));
+ base::unique_fd fd(open("/dev/null", O_RDONLY));
EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, _))
.WillRepeatedly([&](const renderengine::DisplaySettings&,
const std::vector<renderengine::LayerSettings>&,
const std::shared_ptr<renderengine::ExternalTexture>&,
base::unique_fd&&) -> ftl::Future<FenceResult> {
- return ftl::yield<FenceResult>(Fence::NO_FENCE);
+ return ftl::yield<FenceResult>(sp<Fence>::make(std::move(fd)));
});
+ EXPECT_CALL(mOutput, setHintSessionRequiresRenderEngine(true));
+ EXPECT_CALL(mOutput, setHintSessionGpuStart(_)).Times(0);
+ EXPECT_CALL(mOutput, setHintSessionGpuFence(_)).Times(0);
+ verify().execute().expectAFenceWasReturned();
+ EXPECT_FALSE(mOutput.mState.reusedClientComposition);
+
+ verify().execute().expectAFenceWasReturned();
+ EXPECT_FALSE(mOutput.mState.reusedClientComposition);
+}
+
+TEST_F(OutputComposeSurfacesTest, clientCompositionIfBufferChangesWithAdpfGpuOn) {
+ SET_FLAG_FOR_TEST(flags::adpf_gpu_sf, true);
+ LayerFE::LayerSettings r1;
+ LayerFE::LayerSettings r2;
+
+ r1.geometry.boundaries = FloatRect{1, 2, 3, 4};
+ r2.geometry.boundaries = FloatRect{5, 6, 7, 8};
+
+ EXPECT_CALL(mOutput, getSkipColorTransform()).WillRepeatedly(Return(false));
+ EXPECT_CALL(*mDisplayColorProfile, hasWideColorGamut()).WillRepeatedly(Return(true));
+ EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false));
+ EXPECT_CALL(mRenderEngine, isProtected()).WillRepeatedly(Return(false));
+ EXPECT_CALL(mOutput, generateClientCompositionRequests(_, kDefaultOutputDataspace, _))
+ .WillRepeatedly(Return(std::vector<LayerFE::LayerSettings>{r1, r2}));
+ EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _))
+ .WillRepeatedly(Return());
+
+ const auto otherOutputBuffer = std::make_shared<
+ renderengine::impl::
+ ExternalTexture>(sp<GraphicBuffer>::make(), mRenderEngine,
+ renderengine::impl::ExternalTexture::Usage::READABLE |
+ renderengine::impl::ExternalTexture::Usage::WRITEABLE);
+ EXPECT_CALL(*mRenderSurface, dequeueBuffer(_))
+ .WillOnce(Return(mOutputBuffer))
+ .WillOnce(Return(otherOutputBuffer));
+ base::unique_fd fd(open("/dev/null", O_RDONLY));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, _))
+ .WillRepeatedly([&](const renderengine::DisplaySettings&,
+ const std::vector<renderengine::LayerSettings>&,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
+ base::unique_fd&&) -> ftl::Future<FenceResult> {
+ return ftl::yield<FenceResult>(sp<Fence>::make(std::move(fd)));
+ });
+
+ EXPECT_CALL(mOutput, setHintSessionRequiresRenderEngine(true));
+ EXPECT_CALL(mOutput, setHintSessionGpuStart(_));
+ EXPECT_CALL(mOutput, setHintSessionGpuFence(_));
verify().execute().expectAFenceWasReturned();
EXPECT_FALSE(mOutput.mState.reusedClientComposition);
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
index faa5197..96cf84c 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
@@ -270,27 +270,30 @@
return;
}
ATRACE_CALL();
- std::optional<Duration> actualDuration = estimateWorkDuration();
- if (!actualDuration.has_value() || actualDuration < 0ns) {
+ std::optional<WorkDuration> actualDuration = estimateWorkDuration();
+ if (!actualDuration.has_value() || actualDuration->durationNanos < 0) {
ALOGV("Failed to send actual work duration, skipping");
return;
}
- actualDuration = std::make_optional(*actualDuration + sTargetSafetyMargin);
- mActualDuration = actualDuration;
-
+ actualDuration->durationNanos += sTargetSafetyMargin.ns();
if (sTraceHintSessionData) {
- ATRACE_INT64("Measured duration", actualDuration->ns());
- ATRACE_INT64("Target error term", Duration{*actualDuration - mTargetDuration}.ns());
- ATRACE_INT64("Reported duration", actualDuration->ns());
+ ATRACE_INT64("Measured duration", actualDuration->durationNanos);
+ ATRACE_INT64("Target error term", actualDuration->durationNanos - mTargetDuration.ns());
+ ATRACE_INT64("Reported duration", actualDuration->durationNanos);
+ if (FlagManager::getInstance().adpf_gpu_sf()) {
+ ATRACE_INT64("Reported cpu duration", actualDuration->cpuDurationNanos);
+ ATRACE_INT64("Reported gpu duration", actualDuration->gpuDurationNanos);
+ }
ATRACE_INT64("Reported target", mLastTargetDurationSent.ns());
ATRACE_INT64("Reported target error term",
- Duration{*actualDuration - mLastTargetDurationSent}.ns());
+ actualDuration->durationNanos - mLastTargetDurationSent.ns());
}
- ALOGV("Sending actual work duration of: %" PRId64 " on reported target: %" PRId64
- " with error: %" PRId64,
- actualDuration->ns(), mLastTargetDurationSent.ns(),
- Duration{*actualDuration - mLastTargetDurationSent}.ns());
+ ALOGV("Sending actual work duration of: %" PRId64 " with cpu: %" PRId64 " and gpu: %" PRId64
+ " on reported target: %" PRId64 " with error: %" PRId64,
+ actualDuration->durationNanos, actualDuration->cpuDurationNanos,
+ actualDuration->gpuDurationNanos, mLastTargetDurationSent.ns(),
+ actualDuration->durationNanos - mLastTargetDurationSent.ns());
if (mTimingTestingMode) {
mDelayReportActualMutexAcquisitonPromise.get_future().wait();
@@ -303,17 +306,7 @@
ALOGV("Hint session not running and could not be started, skipping");
return;
}
-
- WorkDuration duration{
- .timeStampNanos = TimePoint::now().ns(),
- // TODO(b/284324521): Correctly calculate total duration.
- .durationNanos = actualDuration->ns(),
- .workPeriodStartTimestampNanos = mCommitStartTimes[0].ns(),
- .cpuDurationNanos = actualDuration->ns(),
- // TODO(b/284324521): Calculate RenderEngine GPU time.
- .gpuDurationNanos = 0,
- };
- mHintSessionQueue.push_back(duration);
+ mHintSessionQueue.push_back(*actualDuration);
auto ret = mHintSession->reportActualWorkDuration(mHintSessionQueue);
if (!ret.isOk()) {
@@ -348,11 +341,36 @@
return ensurePowerHintSessionRunning();
}
-void PowerAdvisor::setGpuFenceTime(DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime) {
+void PowerAdvisor::setGpuStartTime(DisplayId displayId, TimePoint startTime) {
DisplayTimingData& displayData = mDisplayTimingData[displayId];
if (displayData.gpuEndFenceTime) {
nsecs_t signalTime = displayData.gpuEndFenceTime->getSignalTime();
if (signalTime != Fence::SIGNAL_TIME_INVALID && signalTime != Fence::SIGNAL_TIME_PENDING) {
+ displayData.lastValidGpuStartTime = displayData.gpuStartTime;
+ displayData.lastValidGpuEndTime = TimePoint::fromNs(signalTime);
+ for (auto&& [_, otherDisplayData] : mDisplayTimingData) {
+ if (!otherDisplayData.lastValidGpuStartTime.has_value() ||
+ !otherDisplayData.lastValidGpuEndTime.has_value())
+ continue;
+ if ((*otherDisplayData.lastValidGpuStartTime < *displayData.gpuStartTime) &&
+ (*otherDisplayData.lastValidGpuEndTime > *displayData.gpuStartTime)) {
+ displayData.lastValidGpuStartTime = *otherDisplayData.lastValidGpuEndTime;
+ break;
+ }
+ }
+ }
+ displayData.gpuEndFenceTime = nullptr;
+ }
+ displayData.gpuStartTime = startTime;
+}
+
+void PowerAdvisor::setGpuFenceTime(DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime) {
+ DisplayTimingData& displayData = mDisplayTimingData[displayId];
+ if (displayData.gpuEndFenceTime && !FlagManager::getInstance().adpf_gpu_sf()) {
+ nsecs_t signalTime = displayData.gpuEndFenceTime->getSignalTime();
+ if (signalTime != Fence::SIGNAL_TIME_INVALID && signalTime != Fence::SIGNAL_TIME_PENDING) {
+ displayData.lastValidGpuStartTime = displayData.gpuStartTime;
+ displayData.lastValidGpuEndTime = TimePoint::fromNs(signalTime);
for (auto&& [_, otherDisplayData] : mDisplayTimingData) {
// If the previous display started before us but ended after we should have
// started, then it likely delayed our start time and we must compensate for that.
@@ -365,12 +383,12 @@
break;
}
}
- displayData.lastValidGpuStartTime = displayData.gpuStartTime;
- displayData.lastValidGpuEndTime = TimePoint::fromNs(signalTime);
}
}
displayData.gpuEndFenceTime = std::move(fenceTime);
- displayData.gpuStartTime = TimePoint::now();
+ if (!FlagManager::getInstance().adpf_gpu_sf()) {
+ displayData.gpuStartTime = TimePoint::now();
+ }
}
void PowerAdvisor::setHwcValidateTiming(DisplayId displayId, TimePoint validateStartTime,
@@ -391,9 +409,8 @@
mDisplayTimingData[displayId].skippedValidate = skipped;
}
-void PowerAdvisor::setRequiresClientComposition(DisplayId displayId,
- bool requiresClientComposition) {
- mDisplayTimingData[displayId].usedClientComposition = requiresClientComposition;
+void PowerAdvisor::setRequiresRenderEngine(DisplayId displayId, bool requiresRenderEngine) {
+ mDisplayTimingData[displayId].requiresRenderEngine = requiresRenderEngine;
}
void PowerAdvisor::setExpectedPresentTime(TimePoint expectedPresentTime) {
@@ -401,8 +418,8 @@
}
void PowerAdvisor::setSfPresentTiming(TimePoint presentFenceTime, TimePoint presentEndTime) {
- mLastSfPresentEndTime = presentEndTime;
mLastPresentFenceTime = presentFenceTime;
+ mLastSfPresentEndTime = presentEndTime;
}
void PowerAdvisor::setFrameDelay(Duration frameDelayDuration) {
@@ -443,7 +460,7 @@
return sortedDisplays;
}
-std::optional<Duration> PowerAdvisor::estimateWorkDuration() {
+std::optional<WorkDuration> PowerAdvisor::estimateWorkDuration() {
if (!mExpectedPresentTimes.isFull() || !mCommitStartTimes.isFull()) {
return std::nullopt;
}
@@ -462,11 +479,10 @@
// used to accumulate gpu time as we iterate over the active displays
std::optional<TimePoint> estimatedGpuEndTime;
- // The timing info for the previously calculated display, if there was one
- std::optional<DisplayTimeline> previousDisplayTiming;
std::vector<DisplayId>&& displayIds =
getOrderedDisplayIds(&DisplayTimingData::hwcPresentStartTime);
DisplayTimeline displayTiming;
+ std::optional<GpuTimeline> firstGpuTimeline;
// Iterate over the displays that use hwc in the same order they are presented
for (DisplayId displayId : displayIds) {
@@ -478,14 +494,6 @@
displayTiming = displayData.calculateDisplayTimeline(mLastPresentFenceTime);
- // If this is the first display, include the duration before hwc present starts
- if (!previousDisplayTiming.has_value()) {
- estimatedHwcEndTime += displayTiming.hwcPresentStartTime - mCommitStartTimes[0];
- } else { // Otherwise add the time since last display's hwc present finished
- estimatedHwcEndTime +=
- displayTiming.hwcPresentStartTime - previousDisplayTiming->hwcPresentEndTime;
- }
-
// Update predicted present finish time with this display's present time
estimatedHwcEndTime = displayTiming.hwcPresentEndTime;
@@ -500,6 +508,9 @@
// Estimate the reference frame's gpu timing
auto gpuTiming = displayData.estimateGpuTiming(previousValidGpuEndTime);
if (gpuTiming.has_value()) {
+ if (!firstGpuTimeline.has_value()) {
+ firstGpuTimeline = gpuTiming;
+ }
previousValidGpuEndTime = gpuTiming->startTime + gpuTiming->duration;
// Estimate the prediction frame's gpu end time from the reference frame
@@ -507,9 +518,7 @@
estimatedGpuEndTime.value_or(TimePoint{0ns})) +
gpuTiming->duration;
}
- previousDisplayTiming = displayTiming;
}
- ATRACE_INT64("Idle duration", idleDuration.ns());
TimePoint estimatedFlingerEndTime = mLastSfPresentEndTime;
@@ -522,15 +531,34 @@
Duration totalDuration = mFrameDelayDuration +
std::max(estimatedHwcEndTime, estimatedGpuEndTime.value_or(TimePoint{0ns})) -
mCommitStartTimes[0];
+ Duration totalDurationWithoutGpu =
+ mFrameDelayDuration + estimatedHwcEndTime - mCommitStartTimes[0];
// We finish SurfaceFlinger when post-composition finishes, so add that in here
Duration flingerDuration =
estimatedFlingerEndTime + mLastPostcompDuration - mCommitStartTimes[0];
+ Duration estimatedGpuDuration = firstGpuTimeline.has_value()
+ ? estimatedGpuEndTime.value_or(TimePoint{0ns}) - firstGpuTimeline->startTime
+ : Duration::fromNs(0);
// Combine the two timings into a single normalized one
Duration combinedDuration = combineTimingEstimates(totalDuration, flingerDuration);
+ Duration cpuDuration = combineTimingEstimates(totalDurationWithoutGpu, flingerDuration);
- return std::make_optional(combinedDuration);
+ WorkDuration duration{
+ .timeStampNanos = TimePoint::now().ns(),
+ .durationNanos = combinedDuration.ns(),
+ .workPeriodStartTimestampNanos = mCommitStartTimes[0].ns(),
+ .cpuDurationNanos = FlagManager::getInstance().adpf_gpu_sf() ? cpuDuration.ns() : 0,
+ .gpuDurationNanos =
+ FlagManager::getInstance().adpf_gpu_sf() ? estimatedGpuDuration.ns() : 0,
+ };
+ if (sTraceHintSessionData) {
+ ATRACE_INT64("Idle duration", idleDuration.ns());
+ ATRACE_INT64("Total duration", totalDuration.ns());
+ ATRACE_INT64("Flinger duration", flingerDuration.ns());
+ }
+ return std::make_optional(duration);
}
Duration PowerAdvisor::combineTimingEstimates(Duration totalDuration, Duration flingerDuration) {
@@ -581,7 +609,7 @@
std::optional<PowerAdvisor::GpuTimeline> PowerAdvisor::DisplayTimingData::estimateGpuTiming(
std::optional<TimePoint> previousEndTime) {
- if (!(usedClientComposition && lastValidGpuStartTime.has_value() && gpuEndFenceTime)) {
+ if (!(requiresRenderEngine && lastValidGpuStartTime.has_value() && gpuEndFenceTime)) {
return std::nullopt;
}
const TimePoint latestGpuStartTime =
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
index 13e1263..60967b0 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
@@ -68,6 +68,8 @@
virtual void enablePowerHintSession(bool enabled) = 0;
// Initializes the power hint session
virtual bool startPowerHintSession(std::vector<int32_t>&& threadIds) = 0;
+ // Provides PowerAdvisor with gpu start time
+ virtual void setGpuStartTime(DisplayId displayId, TimePoint startTime) = 0;
// Provides PowerAdvisor with a copy of the gpu fence so it can determine the gpu end time
virtual void setGpuFenceTime(DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime) = 0;
// Reports the start and end times of a hwc validate call this frame for a given display
@@ -80,9 +82,8 @@
virtual void setExpectedPresentTime(TimePoint expectedPresentTime) = 0;
// Reports the most recent present fence time and end time once known
virtual void setSfPresentTiming(TimePoint presentFenceTime, TimePoint presentEndTime) = 0;
- // Reports whether a display used client composition this frame
- virtual void setRequiresClientComposition(DisplayId displayId,
- bool requiresClientComposition) = 0;
+ // Reports whether a display requires RenderEngine to draw
+ virtual void setRequiresRenderEngine(DisplayId displayId, bool requiresRenderEngine) = 0;
// Reports whether a given display skipped validation this frame
virtual void setSkippedValidate(DisplayId displayId, bool skipped) = 0;
// Reports when a hwc present is delayed, and the time that it will resume
@@ -125,13 +126,14 @@
void reportActualWorkDuration() override;
void enablePowerHintSession(bool enabled) override;
bool startPowerHintSession(std::vector<int32_t>&& threadIds) override;
+ void setGpuStartTime(DisplayId displayId, TimePoint startTime) override;
void setGpuFenceTime(DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime) override;
void setHwcValidateTiming(DisplayId displayId, TimePoint validateStartTime,
TimePoint validateEndTime) override;
void setHwcPresentTiming(DisplayId displayId, TimePoint presentStartTime,
TimePoint presentEndTime) override;
void setSkippedValidate(DisplayId displayId, bool skipped) override;
- void setRequiresClientComposition(DisplayId displayId, bool requiresClientComposition) override;
+ void setRequiresRenderEngine(DisplayId displayId, bool requiresRenderEngine);
void setExpectedPresentTime(TimePoint expectedPresentTime) override;
void setSfPresentTiming(TimePoint presentFenceTime, TimePoint presentEndTime) override;
void setHwcPresentDelayedTime(DisplayId displayId, TimePoint earliestFrameStartTime) override;
@@ -192,7 +194,7 @@
std::optional<TimePoint> hwcValidateStartTime;
std::optional<TimePoint> hwcValidateEndTime;
std::optional<TimePoint> hwcPresentDelayedTime;
- bool usedClientComposition = false;
+ bool requiresRenderEngine = false;
bool skippedValidate = false;
// Calculate high-level timing milestones from more granular display timing data
DisplayTimeline calculateDisplayTimeline(TimePoint fenceTime);
@@ -224,8 +226,8 @@
// Filter and sort the display ids by a given property
std::vector<DisplayId> getOrderedDisplayIds(
std::optional<TimePoint> DisplayTimingData::*sortBy);
- // Estimates a frame's total work duration including gpu time.
- std::optional<Duration> estimateWorkDuration();
+ // Estimates a frame's total work duration including gpu and gpu time.
+ std::optional<aidl::android::hardware::power::WorkDuration> estimateWorkDuration();
// There are two different targets and actual work durations we care about,
// this normalizes them together and takes the max of the two
Duration combineTimingEstimates(Duration totalDuration, Duration flingerDuration);
@@ -235,7 +237,6 @@
bool ensurePowerHintSessionRunning() REQUIRES(mHintSessionMutex);
std::unordered_map<DisplayId, DisplayTimingData> mDisplayTimingData;
-
// Current frame's delay
Duration mFrameDelayDuration{0ns};
// Last frame's post-composition duration
@@ -272,7 +273,6 @@
std::vector<aidl::android::hardware::power::WorkDuration> mHintSessionQueue;
// The latest values we have received for target and actual
Duration mTargetDuration = kDefaultTargetDuration;
- std::optional<Duration> mActualDuration;
// The list of thread ids, stored so we can restart the session from this class if needed
std::vector<int32_t> mHintSessionThreadIds;
Duration mLastTargetDurationSent = kDefaultTargetDuration;
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/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 736fec6..cd2120e 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -15,6 +15,7 @@
*/
// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#include "TransactionCallbackInvoker.h"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
@@ -150,7 +151,6 @@
mWindowType(static_cast<WindowInfo::Type>(
args.metadata.getInt32(gui::METADATA_WINDOW_TYPE, 0))),
mLayerCreationFlags(args.flags),
- mBorderEnabled(false),
mLegacyLayerFE(args.flinger->getFactory().createLayerFE(mName)) {
ALOGV("Creating Layer %s", getDebugName());
@@ -1235,28 +1235,6 @@
return StretchEffect{};
}
-bool Layer::enableBorder(bool shouldEnable, float width, const half4& color) {
- if (mBorderEnabled == shouldEnable && mBorderWidth == width && mBorderColor == color) {
- return false;
- }
- mBorderEnabled = shouldEnable;
- mBorderWidth = width;
- mBorderColor = color;
- return true;
-}
-
-bool Layer::isBorderEnabled() {
- return mBorderEnabled;
-}
-
-float Layer::getBorderWidth() {
- return mBorderWidth;
-}
-
-const half4& Layer::getBorderColor() {
- return mBorderColor;
-}
-
bool Layer::propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool overrideChildren,
bool* transactionNeeded) {
// Gets the frame rate to propagate to children.
@@ -2912,9 +2890,7 @@
currentMaxAcquiredBufferCount);
}
-void Layer::onLayerDisplayed(ftl::SharedFuture<FenceResult> futureFenceResult,
- ui::LayerStack layerStack,
- std::function<FenceResult(FenceResult)>&& continuation) {
+sp<CallbackHandle> Layer::findCallbackHandle() {
// If we are displayed on multiple displays in a single composition cycle then we would
// need to do careful tracking to enable the use of the mLastClientCompositionFence.
// For example we can only use it if all the displays are client comp, and we need
@@ -2949,6 +2925,53 @@
break;
}
}
+ return ch;
+}
+
+void Layer::prepareReleaseCallbacks(ftl::Future<FenceResult> futureFenceResult,
+ ui::LayerStack layerStack) {
+ sp<CallbackHandle> ch = findCallbackHandle();
+
+ if (ch != nullptr) {
+ ch->previousReleaseCallbackId = mPreviousReleaseCallbackId;
+ ch->previousReleaseFences.emplace_back(std::move(futureFenceResult));
+ ch->name = mName;
+ } else {
+ // If we didn't get a release callback yet, e.g. some scenarios when capturing
+ // screenshots asynchronously, then make sure we don't drop the fence.
+ mAdditionalPreviousReleaseFences.emplace_back(std::move(futureFenceResult));
+ std::vector<ftl::Future<FenceResult>> mergedFences;
+ sp<Fence> prevFence = nullptr;
+ // For a layer that's frequently screenshotted, try to merge fences to make sure we
+ // don't grow unbounded.
+ for (auto& futureReleaseFence : mAdditionalPreviousReleaseFences) {
+ auto result = futureReleaseFence.wait_for(0s);
+ if (result != std::future_status::ready) {
+ mergedFences.emplace_back(std::move(futureReleaseFence));
+ continue;
+ }
+ mergeFence(getDebugName(), futureReleaseFence.get().value_or(Fence::NO_FENCE),
+ prevFence);
+ }
+ if (prevFence != nullptr) {
+ mergedFences.emplace_back(ftl::yield(FenceResult(std::move(prevFence))));
+ }
+ mAdditionalPreviousReleaseFences.swap(mergedFences);
+ }
+
+ if (mBufferInfo.mBuffer) {
+ mPreviouslyPresentedLayerStacks.push_back(layerStack);
+ }
+
+ if (mDrawingState.frameNumber > 0) {
+ mDrawingState.previousFrameNumber = mDrawingState.frameNumber;
+ }
+}
+
+void Layer::onLayerDisplayed(ftl::SharedFuture<FenceResult> futureFenceResult,
+ ui::LayerStack layerStack,
+ std::function<FenceResult(FenceResult)>&& continuation) {
+ sp<CallbackHandle> ch = findCallbackHandle();
if (!FlagManager::getInstance().screenshot_fence_preservation() && continuation) {
futureFenceResult = ftl::Future(futureFenceResult).then(std::move(continuation)).share();
@@ -2956,32 +2979,32 @@
if (ch != nullptr) {
ch->previousReleaseCallbackId = mPreviousReleaseCallbackId;
- ch->previousReleaseFences.emplace_back(std::move(futureFenceResult));
+ ch->previousSharedReleaseFences.emplace_back(std::move(futureFenceResult));
ch->name = mName;
} else if (FlagManager::getInstance().screenshot_fence_preservation()) {
// If we didn't get a release callback yet, e.g. some scenarios when capturing screenshots
// asynchronously, then make sure we don't drop the fence.
- mAdditionalPreviousReleaseFences.emplace_back(std::move(futureFenceResult),
- std::move(continuation));
+ mPreviousReleaseFenceAndContinuations.emplace_back(std::move(futureFenceResult),
+ std::move(continuation));
std::vector<FenceAndContinuation> mergedFences;
sp<Fence> prevFence = nullptr;
// For a layer that's frequently screenshotted, try to merge fences to make sure we don't
// grow unbounded.
- for (const auto& futureAndContinution : mAdditionalPreviousReleaseFences) {
- auto result = futureAndContinution.future.wait_for(0s);
+ for (const auto& futureAndContinuation : mPreviousReleaseFenceAndContinuations) {
+ auto result = futureAndContinuation.future.wait_for(0s);
if (result != std::future_status::ready) {
- mergedFences.emplace_back(futureAndContinution);
+ mergedFences.emplace_back(futureAndContinuation);
continue;
}
- mergeFence(getDebugName(), futureAndContinution.chain().get().value_or(Fence::NO_FENCE),
- prevFence);
+ mergeFence(getDebugName(),
+ futureAndContinuation.chain().get().value_or(Fence::NO_FENCE), prevFence);
}
if (prevFence != nullptr) {
mergedFences.emplace_back(ftl::yield(FenceResult(std::move(prevFence))).share());
}
- mAdditionalPreviousReleaseFences.swap(mergedFences);
+ mPreviousReleaseFenceAndContinuations.swap(mergedFences);
}
if (mBufferInfo.mBuffer) {
@@ -3481,16 +3504,23 @@
handle->acquireTimeOrFence = mCallbackHandleAcquireTimeOrFence;
handle->frameNumber = mDrawingState.frameNumber;
handle->previousFrameNumber = mDrawingState.previousFrameNumber;
- if (FlagManager::getInstance().screenshot_fence_preservation() &&
+ if (FlagManager::getInstance().ce_fence_promise() &&
mPreviousReleaseBufferEndpoint == handle->listener) {
// Add fences from previous screenshots now so that they can be dispatched to the
// client.
- for (const auto& futureAndContinution : mAdditionalPreviousReleaseFences) {
- handle->previousReleaseFences.emplace_back(futureAndContinution.chain());
+ for (auto& futureReleaseFence : mAdditionalPreviousReleaseFences) {
+ handle->previousReleaseFences.emplace_back(std::move(futureReleaseFence));
}
mAdditionalPreviousReleaseFences.clear();
+ } else if (FlagManager::getInstance().screenshot_fence_preservation() &&
+ mPreviousReleaseBufferEndpoint == handle->listener) {
+ // Add fences from previous screenshots now so that they can be dispatched to the
+ // client.
+ for (const auto& futureAndContinution : mPreviousReleaseFenceAndContinuations) {
+ handle->previousSharedReleaseFences.emplace_back(futureAndContinution.chain());
+ }
+ mPreviousReleaseFenceAndContinuations.clear();
}
-
// Store so latched time and release fence can be set
mDrawingState.callbackHandles.push_back(handle);
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 0ceecec..d283d6f 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -559,6 +559,14 @@
void onLayerDisplayed(ftl::SharedFuture<FenceResult>, ui::LayerStack layerStack,
std::function<FenceResult(FenceResult)>&& continuation = nullptr);
+ // Tracks mLastClientCompositionFence and gets the callback handle for this layer.
+ sp<CallbackHandle> findCallbackHandle();
+
+ // Adds the future release fence to a list of fences that are used to release the
+ // last presented buffer. Also keeps track of the layerstack in a list of previous
+ // layerstacks that have been presented.
+ void prepareReleaseCallbacks(ftl::Future<FenceResult>, ui::LayerStack layerStack);
+
void setWasClientComposed(const sp<Fence>& fence) {
mLastClientCompositionFence = fence;
mClearClientCompositionFenceOnLayerDisplayed = false;
@@ -874,10 +882,6 @@
bool setStretchEffect(const StretchEffect& effect);
StretchEffect getStretchEffect() const;
- bool enableBorder(bool shouldEnable, float width, const half4& color);
- bool isBorderEnabled();
- float getBorderWidth();
- const half4& getBorderColor();
bool setBufferCrop(const Rect& /* bufferCrop */);
bool setDestinationFrame(const Rect& /* destinationFrame */);
@@ -934,6 +938,7 @@
// the release fences from the correct displays when we release the last buffer
// from the layer.
std::vector<ui::LayerStack> mPreviouslyPresentedLayerStacks;
+
struct FenceAndContinuation {
ftl::SharedFuture<FenceResult> future;
std::function<FenceResult(FenceResult)> continuation;
@@ -946,7 +951,16 @@
}
}
};
- std::vector<FenceAndContinuation> mAdditionalPreviousReleaseFences;
+ std::vector<FenceAndContinuation> mPreviousReleaseFenceAndContinuations;
+
+ // Release fences for buffers that have not yet received a release
+ // callback. A release callback may not be given when capturing
+ // screenshots asynchronously. There may be no buffer update for the
+ // layer, but the layer will still be composited on the screen in every
+ // frame. Kepping track of these fences ensures that they are not dropped
+ // and can be dispatched to the client at a later time.
+ std::vector<ftl::Future<FenceResult>> mAdditionalPreviousReleaseFences;
+
// Exposed so SurfaceFlinger can assert that it's held
const sp<SurfaceFlinger> mFlinger;
@@ -1238,10 +1252,6 @@
bool findInHierarchy(const sp<Layer>&);
- bool mBorderEnabled = false;
- float mBorderWidth;
- half4 mBorderColor;
-
void setTransformHintLegacy(ui::Transform::RotationFlags);
void releasePreviousBuffer();
void resetDrawingStateBufferInfo();
diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp
index 43a4397..620edca 100644
--- a/services/surfaceflinger/LayerFE.cpp
+++ b/services/surfaceflinger/LayerFE.cpp
@@ -27,6 +27,8 @@
#include "LayerFE.h"
#include "SurfaceFlinger.h"
+#include "ui/FenceResult.h"
+#include "ui/LayerStack.h"
namespace android {
@@ -387,4 +389,29 @@
return mSnapshot->externalTexture ? mSnapshot->externalTexture->getBuffer() : nullptr;
}
+void LayerFE::setReleaseFence(const FenceResult& releaseFence) {
+ // Promises should not be fulfilled more than once. This case can occur if virtual
+ // displays with the same layerstack ID are being created and destroyed in quick
+ // succession, such as in tests. This would result in a race condition in which
+ // multiple displays have the same layerstack ID within the same vsync interval.
+ if (mReleaseFencePromiseStatus == ReleaseFencePromiseStatus::FULFILLED) {
+ return;
+ }
+ mReleaseFence.set_value(releaseFence);
+ mReleaseFencePromiseStatus = ReleaseFencePromiseStatus::FULFILLED;
+}
+
+// LayerFEs are reused and a new fence needs to be created whevever a buffer is latched.
+ftl::Future<FenceResult> LayerFE::createReleaseFenceFuture() {
+ if (mReleaseFencePromiseStatus == ReleaseFencePromiseStatus::INITIALIZED) {
+ LOG_ALWAYS_FATAL("Attempting to create a new promise while one is still unfulfilled.");
+ }
+ mReleaseFence = std::promise<FenceResult>();
+ mReleaseFencePromiseStatus = ReleaseFencePromiseStatus::INITIALIZED;
+ return mReleaseFence.get_future();
+}
+
+LayerFE::ReleaseFencePromiseStatus LayerFE::getReleaseFencePromiseStatus() {
+ return mReleaseFencePromiseStatus;
+}
} // namespace android
diff --git a/services/surfaceflinger/LayerFE.h b/services/surfaceflinger/LayerFE.h
index 66cb88b..019fa22 100644
--- a/services/surfaceflinger/LayerFE.h
+++ b/services/surfaceflinger/LayerFE.h
@@ -22,6 +22,9 @@
#include "compositionengine/LayerFE.h"
#include "compositionengine/LayerFECompositionState.h"
#include "renderengine/LayerSettings.h"
+#include "ui/LayerStack.h"
+
+#include <ftl/future.h>
namespace android {
@@ -47,6 +50,9 @@
std::optional<compositionengine::LayerFE::LayerSettings> prepareClientComposition(
compositionengine::LayerFE::ClientCompositionTargetSettings&) const;
CompositionResult&& stealCompositionResult();
+ ftl::Future<FenceResult> createReleaseFenceFuture() override;
+ void setReleaseFence(const FenceResult& releaseFence) override;
+ LayerFE::ReleaseFencePromiseStatus getReleaseFencePromiseStatus() override;
std::unique_ptr<surfaceflinger::frontend::LayerSnapshot> mSnapshot;
@@ -76,6 +82,8 @@
CompositionResult mCompositionResult;
std::string mName;
+ std::promise<FenceResult> mReleaseFence;
+ ReleaseFencePromiseStatus mReleaseFencePromiseStatus = ReleaseFencePromiseStatus::UNINITIALIZED;
};
} // namespace android
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index b8d5e76..974c837 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -40,14 +40,15 @@
namespace {
-bool isLayerActive(const LayerInfo& info, nsecs_t threshold) {
+bool isLayerActive(const LayerInfo& info, nsecs_t threshold, bool isVrrDevice) {
if (FlagManager::getInstance().misc1() && !info.isVisible()) {
return false;
}
// Layers with an explicit frame rate or frame rate category are kept active,
// but ignore NoVote.
- if (info.getSetFrameRateVote().isValid() && !info.getSetFrameRateVote().isNoVote()) {
+ const auto frameRate = info.getSetFrameRateVote();
+ if (frameRate.isValid() && !frameRate.isNoVote() && frameRate.isVoteValidForMrr(isVrrDevice)) {
return true;
}
@@ -194,7 +195,7 @@
std::lock_guard lock(mLock);
- partitionLayers(now);
+ partitionLayers(now, selector.isVrrDevice());
for (const auto& [key, value] : mActiveLayerInfos) {
auto& info = value.second;
@@ -236,7 +237,7 @@
return summary;
}
-void LayerHistory::partitionLayers(nsecs_t now) {
+void LayerHistory::partitionLayers(nsecs_t now, bool isVrrDevice) {
ATRACE_CALL();
const nsecs_t threshold = getActiveLayerThreshold(now);
@@ -244,7 +245,7 @@
LayerInfos::iterator it = mInactiveLayerInfos.begin();
while (it != mInactiveLayerInfos.end()) {
auto& [layerUnsafe, info] = it->second;
- if (isLayerActive(*info, threshold)) {
+ if (isLayerActive(*info, threshold, isVrrDevice)) {
// move this to the active map
mActiveLayerInfos.insert({it->first, std::move(it->second)});
@@ -262,7 +263,7 @@
it = mActiveLayerInfos.begin();
while (it != mActiveLayerInfos.end()) {
auto& [layerUnsafe, info] = it->second;
- if (isLayerActive(*info, threshold)) {
+ if (isLayerActive(*info, threshold, isVrrDevice)) {
// Set layer vote if set
const auto frameRate = info->getSetFrameRateVote();
@@ -305,7 +306,7 @@
trace(*info, gameFrameRateOverrideVoteType,
gameModeFrameRateOverride.getIntValue());
}
- } else if (frameRate.isValid()) {
+ } else if (frameRate.isValid() && frameRate.isVoteValidForMrr(isVrrDevice)) {
info->setLayerVote({setFrameRateVoteType, frameRate.vote.rate,
frameRate.vote.seamlessness, frameRate.category});
if (CC_UNLIKELY(mTraceEnabled)) {
@@ -321,14 +322,30 @@
gameDefaultFrameRateOverride.getIntValue());
}
} else {
+ if (frameRate.isValid() && !frameRate.isVoteValidForMrr(isVrrDevice)) {
+ ATRACE_FORMAT_INSTANT("Reset layer to ignore explicit vote on MRR %s: %s "
+ "%s %s",
+ info->getName().c_str(),
+ ftl::enum_string(frameRate.vote.type).c_str(),
+ to_string(frameRate.vote.rate).c_str(),
+ ftl::enum_string(frameRate.category).c_str());
+ }
info->resetLayerVote();
}
} else {
- if (frameRate.isValid()) {
+ if (frameRate.isValid() && frameRate.isVoteValidForMrr(isVrrDevice)) {
const auto type = info->isVisible() ? voteType : LayerVoteType::NoVote;
info->setLayerVote({type, frameRate.vote.rate, frameRate.vote.seamlessness,
frameRate.category});
} else {
+ if (!frameRate.isVoteValidForMrr(isVrrDevice)) {
+ ATRACE_FORMAT_INSTANT("Reset layer to ignore explicit vote on MRR %s: %s "
+ "%s %s",
+ info->getName().c_str(),
+ ftl::enum_string(frameRate.vote.type).c_str(),
+ to_string(frameRate.vote.rate).c_str(),
+ ftl::enum_string(frameRate.category).c_str());
+ }
info->resetLayerVote();
}
}
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index a6f1b56..c09f148 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.h
+++ b/services/surfaceflinger/Scheduler/LayerHistory.h
@@ -111,9 +111,12 @@
std::string dumpGameFrameRateOverridesLocked() const REQUIRES(mLock);
// Iterates over layers maps moving all active layers to mActiveLayerInfos and all inactive
- // layers to mInactiveLayerInfos.
+ // layers to mInactiveLayerInfos. Layer's active state is determined by multiple factors
+ // such as update activity, visibility, and frame rate vote.
// worst case time complexity is O(2 * inactive + active)
- void partitionLayers(nsecs_t now) REQUIRES(mLock);
+ // now: the current time (system time) when calling the method
+ // isVrrDevice: true if the device has DisplayMode with VrrConfig specified.
+ void partitionLayers(nsecs_t now, bool isVrrDevice) REQUIRES(mLock);
enum class LayerStatus {
NotFound,
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 9745452..1bc4ac2 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -566,6 +566,18 @@
return isNoVote() || vote.rate.isValid() || category != FrameRateCategory::Default;
}
+bool LayerInfo::FrameRate::isVoteValidForMrr(bool isVrrDevice) const {
+ if (isVrrDevice || FlagManager::getInstance().frame_rate_category_mrr()) {
+ return true;
+ }
+
+ if (category == FrameRateCategory::Default && vote.type != FrameRateCompatibility::Gte) {
+ return true;
+ }
+
+ return false;
+}
+
std::ostream& operator<<(std::ostream& stream, const LayerInfo::FrameRate& rate) {
return stream << "{rate=" << rate.vote.rate << " type=" << ftl::enum_string(rate.vote.type)
<< " seamlessness=" << ftl::enum_string(rate.vote.seamlessness) << '}';
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index 326e444..40903ed 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -146,6 +146,10 @@
// selection.
bool isNoVote() const;
+ // Checks whether the given FrameRate's vote specifications is valid for MRR devices
+ // given the current flagging.
+ bool isVoteValidForMrr(bool isVrrDevice) const;
+
private:
static Seamlessness getSeamlessness(Fps rate, Seamlessness seamlessness) {
if (!rate.isValid()) {
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index 483ea89..eac527b 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -474,21 +474,23 @@
}
auto RefreshRateSelector::getRankedFrameRates(const std::vector<LayerRequirement>& layers,
- GlobalSignals signals) const -> RankedFrameRates {
+ GlobalSignals signals, Fps pacesetterFps) const
+ -> RankedFrameRates {
+ GetRankedFrameRatesCache cache{layers, signals, pacesetterFps};
+
std::lock_guard lock(mLock);
- if (mGetRankedFrameRatesCache &&
- mGetRankedFrameRatesCache->arguments == std::make_pair(layers, signals)) {
+ if (mGetRankedFrameRatesCache && mGetRankedFrameRatesCache->matches(cache)) {
return mGetRankedFrameRatesCache->result;
}
- const auto result = getRankedFrameRatesLocked(layers, signals);
- mGetRankedFrameRatesCache = GetRankedFrameRatesCache{{layers, signals}, result};
- return result;
+ cache.result = getRankedFrameRatesLocked(layers, signals, pacesetterFps);
+ mGetRankedFrameRatesCache = std::move(cache);
+ return mGetRankedFrameRatesCache->result;
}
auto RefreshRateSelector::getRankedFrameRatesLocked(const std::vector<LayerRequirement>& layers,
- GlobalSignals signals) const
+ GlobalSignals signals, Fps pacesetterFps) const
-> RankedFrameRates {
using namespace fps_approx_ops;
ATRACE_CALL();
@@ -496,6 +498,24 @@
const auto& activeMode = *getActiveModeLocked().modePtr;
+ if (pacesetterFps.isValid()) {
+ ALOGV("Follower display");
+
+ const auto ranking = rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Descending,
+ std::nullopt, [&](FrameRateMode mode) {
+ return mode.modePtr->getPeakFps() == pacesetterFps;
+ });
+
+ if (!ranking.empty()) {
+ ATRACE_FORMAT_INSTANT("%s (Follower display)",
+ to_string(ranking.front().frameRateMode.fps).c_str());
+
+ return {ranking, kNoSignals, pacesetterFps};
+ }
+
+ ALOGW("Follower display cannot follow the pacesetter");
+ }
+
// Keep the display at max frame rate for the duration of powering on the display.
if (signals.powerOnImminent) {
ALOGV("Power On Imminent");
@@ -506,6 +526,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 +571,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 +631,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 +672,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;
}
@@ -1218,6 +1248,8 @@
LOG_ALWAYS_FATAL_IF(!activeModeOpt);
mActiveModeOpt.emplace(FrameRateMode{renderFrameRate, ftl::as_non_null(activeModeOpt->get())});
+ mIsVrrDevice = FlagManager::getInstance().vrr_config() &&
+ activeModeOpt->get()->getVrrConfig().has_value();
}
RefreshRateSelector::RefreshRateSelector(DisplayModes modes, DisplayModeId activeModeId,
@@ -1431,7 +1463,8 @@
}
return str;
};
- ALOGV("%s render rates: %s", rangeName, stringifyModes().c_str());
+ ALOGV("%s render rates: %s, isVrrDevice? %d", rangeName, stringifyModes().c_str(),
+ mIsVrrDevice);
return frameRateModes;
};
@@ -1440,6 +1473,11 @@
mAppRequestFrameRates = filterRefreshRates(policy->appRequestRanges, "app request");
}
+bool RefreshRateSelector::isVrrDevice() const {
+ std::lock_guard lock(mLock);
+ return mIsVrrDevice;
+}
+
Fps RefreshRateSelector::findClosestKnownFrameRate(Fps frameRate) const {
using namespace fps_approx_ops;
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
index 6051e89..a0e2785 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
@@ -233,14 +233,18 @@
struct RankedFrameRates {
FrameRateRanking ranking; // Ordered by descending score.
GlobalSignals consideredSignals;
+ Fps pacesetterFps;
bool operator==(const RankedFrameRates& other) const {
- return ranking == other.ranking && consideredSignals == other.consideredSignals;
+ return ranking == other.ranking && consideredSignals == other.consideredSignals &&
+ isApproxEqual(pacesetterFps, other.pacesetterFps);
}
};
- RankedFrameRates getRankedFrameRates(const std::vector<LayerRequirement>&, GlobalSignals) const
- EXCLUDES(mLock);
+ // If valid, `pacesetterFps` (used by follower displays) filters the ranking to modes matching
+ // that refresh rate.
+ RankedFrameRates getRankedFrameRates(const std::vector<LayerRequirement>&, GlobalSignals,
+ Fps pacesetterFps = {}) const EXCLUDES(mLock);
FpsRange getSupportedRefreshRateRange() const EXCLUDES(mLock) {
std::lock_guard lock(mLock);
@@ -406,6 +410,8 @@
std::chrono::milliseconds getIdleTimerTimeout();
+ bool isVrrDevice() const;
+
private:
friend struct TestableRefreshRateSelector;
@@ -415,7 +421,8 @@
const FrameRateMode& getActiveModeLocked() const REQUIRES(mLock);
RankedFrameRates getRankedFrameRatesLocked(const std::vector<LayerRequirement>& layers,
- GlobalSignals signals) const REQUIRES(mLock);
+ GlobalSignals signals, Fps pacesetterFps) const
+ REQUIRES(mLock);
// Returns number of display frames and remainder when dividing the layer refresh period by
// display refresh period.
@@ -513,6 +520,9 @@
std::vector<FrameRateMode> mPrimaryFrameRates GUARDED_BY(mLock);
std::vector<FrameRateMode> mAppRequestFrameRates GUARDED_BY(mLock);
+ // Caches whether the device is VRR-compatible based on the active display mode.
+ bool mIsVrrDevice GUARDED_BY(mLock) = false;
+
Policy mDisplayManagerPolicy GUARDED_BY(mLock);
std::optional<Policy> mOverridePolicy GUARDED_BY(mLock);
@@ -534,8 +544,16 @@
Config::FrameRateOverride mFrameRateOverrideConfig;
struct GetRankedFrameRatesCache {
- std::pair<std::vector<LayerRequirement>, GlobalSignals> arguments;
+ std::vector<LayerRequirement> layers;
+ GlobalSignals signals;
+ Fps pacesetterFps;
+
RankedFrameRates result;
+
+ bool matches(const GetRankedFrameRatesCache& other) const {
+ return layers == other.layers && signals == other.signals &&
+ isApproxEqual(pacesetterFps, other.pacesetterFps);
+ }
};
mutable std::optional<GetRankedFrameRatesCache> mGetRankedFrameRatesCache GUARDED_BY(mLock);
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index d92edb8..7968096 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -306,8 +306,11 @@
const auto pacesetterOpt = pacesetterDisplayLocked();
LOG_ALWAYS_FATAL_IF(!pacesetterOpt);
const Display& pacesetter = *pacesetterOpt;
- return std::make_pair(pacesetter.selectorPtr->getActiveMode().fps,
- pacesetter.schedulePtr->period());
+ const FrameRateMode& frameRateMode = pacesetter.selectorPtr->getActiveMode();
+ const auto refreshRate = frameRateMode.fps;
+ const auto displayVsync = frameRateMode.modePtr->getVsyncRate();
+ const auto numPeriod = RefreshRateSelector::getFrameRateDivisor(displayVsync, refreshRate);
+ return std::make_pair(refreshRate, numPeriod * pacesetter.schedulePtr->period());
}();
const Period currentPeriod = period != Period::zero() ? period : refreshRate.getPeriod();
@@ -1146,38 +1149,31 @@
auto Scheduler::chooseDisplayModes() const -> DisplayModeChoiceMap {
ATRACE_CALL();
- using RankedRefreshRates = RefreshRateSelector::RankedFrameRates;
- ui::PhysicalDisplayVector<RankedRefreshRates> perDisplayRanking;
+ DisplayModeChoiceMap modeChoices;
const auto globalSignals = makeGlobalSignals();
- Fps pacesetterFps;
+
+ const Fps pacesetterFps = [&]() REQUIRES(mPolicyLock, mDisplayLock, kMainThreadContext) {
+ auto rankedFrameRates =
+ pacesetterSelectorPtrLocked()->getRankedFrameRates(mPolicy.contentRequirements,
+ globalSignals);
+
+ const Fps pacesetterFps = rankedFrameRates.ranking.front().frameRateMode.fps;
+
+ modeChoices.try_emplace(*mPacesetterDisplayId,
+ DisplayModeChoice::from(std::move(rankedFrameRates)));
+ return pacesetterFps;
+ }();
for (const auto& [id, display] : mDisplays) {
+ if (id == *mPacesetterDisplayId) continue;
+
auto rankedFrameRates =
- display.selectorPtr->getRankedFrameRates(mPolicy.contentRequirements,
- globalSignals);
- if (id == *mPacesetterDisplayId) {
- pacesetterFps = rankedFrameRates.ranking.front().frameRateMode.fps;
- }
- perDisplayRanking.push_back(std::move(rankedFrameRates));
+ display.selectorPtr->getRankedFrameRates(mPolicy.contentRequirements, globalSignals,
+ pacesetterFps);
+
+ modeChoices.try_emplace(id, DisplayModeChoice::from(std::move(rankedFrameRates)));
}
- DisplayModeChoiceMap modeChoices;
- using fps_approx_ops::operator==;
-
- for (auto& [rankings, signals] : perDisplayRanking) {
- const auto chosenFrameRateMode =
- ftl::find_if(rankings,
- [&](const auto& ranking) {
- return ranking.frameRateMode.fps == pacesetterFps;
- })
- .transform([](const auto& scoredFrameRate) {
- return scoredFrameRate.get().frameRateMode;
- })
- .value_or(rankings.front().frameRateMode);
-
- modeChoices.try_emplace(chosenFrameRateMode.modePtr->getPhysicalDisplayId(),
- DisplayModeChoice{chosenFrameRateMode, signals});
- }
return modeChoices;
}
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 494a91b..ccb3aa7 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -402,6 +402,11 @@
DisplayModeChoice(FrameRateMode mode, GlobalSignals consideredSignals)
: mode(std::move(mode)), consideredSignals(consideredSignals) {}
+ static DisplayModeChoice from(RefreshRateSelector::RankedFrameRates rankedFrameRates) {
+ return {rankedFrameRates.ranking.front().frameRateMode,
+ rankedFrameRates.consideredSignals};
+ }
+
FrameRateMode mode;
GlobalSignals consideredSignals;
@@ -437,6 +442,7 @@
// IEventThreadCallback overrides
bool throttleVsync(TimePoint, uid_t) override;
+ // Get frame interval
Period getVsyncPeriod(uid_t) override EXCLUDES(mDisplayLock);
void resync() override EXCLUDES(mDisplayLock);
void onExpectedPresentTimePosted(TimePoint expectedPresentTime) override EXCLUDES(mDisplayLock);
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index db1930d..b3c1f6b 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -377,15 +377,20 @@
mRenderRateOpt = renderRate;
const auto renderPeriodDelta =
prevRenderRate ? prevRenderRate->getPeriodNsecs() - renderRate.getPeriodNsecs() : 0;
- const bool newRenderRateIsHigher = renderPeriodDelta > renderRate.getPeriodNsecs() &&
- mLastCommittedVsync.ns() - mClock->now() > 2 * renderRate.getPeriodNsecs();
if (applyImmediately) {
+ ATRACE_FORMAT_INSTANT("applyImmediately");
while (mTimelines.size() > 1) {
mTimelines.pop_front();
}
mTimelines.front().setRenderRate(renderRate);
- } else if (newRenderRateIsHigher) {
+ 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);
@@ -630,33 +635,30 @@
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);
- }
+ } else if (FlagManager::getInstance().vrr_config() && minFramePeriodOpt && mRenderRateOpt &&
+ lastVsyncOpt) {
+ // lastVsyncOpt is based on the old timeline before we shifted it. we should correct it
+ // first before trying to use it.
+ lastVsyncOpt = snapToVsyncAlignedWithRenderRate(model, *lastVsyncOpt);
+ const auto vsyncDiff = vsyncTime - *lastVsyncOpt;
+ if (vsyncDiff <= minFramePeriodOpt->ns() - threshold) {
+ // avoid a duplicate vsync
+ ATRACE_FORMAT_INSTANT("skipping a vsync to avoid duplicate frame. next in %.2f which "
+ "is %.2f "
+ "from "
+ "prev. "
+ "adjust by %.2f",
+ static_cast<float>(vsyncTime - TimePoint::now().ns()) / 1e6f,
+ static_cast<float>(vsyncDiff) / 1e6f,
+ static_cast<float>(mRenderRateOpt->getPeriodNsecs()) / 1e6f);
+ vsyncTime += mRenderRateOpt->getPeriodNsecs();
}
- vsyncTime += vsyncFixupTime;
}
ATRACE_FORMAT_INSTANT("vsync in %.2fms", float(vsyncTime - TimePoint::now().ns()) / 1e6f);
@@ -666,12 +668,6 @@
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);
}
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index bf210af..1265540 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -40,6 +40,7 @@
#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
#include <binder/PermissionCache.h>
+#include <common/FlagManager.h>
#include <compositionengine/CompositionEngine.h>
#include <compositionengine/CompositionRefreshArgs.h>
#include <compositionengine/Display.h>
@@ -167,10 +168,6 @@
#define NO_THREAD_SAFETY_ANALYSIS \
_Pragma("GCC error \"Prefer <ftl/fake_guard.h> or MutexUtils.h helpers.\"")
-// To enable layer borders in the system, change the below flag to true.
-#undef DOES_CONTAIN_BORDER
-#define DOES_CONTAIN_BORDER false
-
namespace android {
using namespace std::chrono_literals;
using namespace std::string_literals;
@@ -578,11 +575,11 @@
sp<IBinder> SurfaceFlinger::createDisplay(const String8& displayName, bool secure,
float requestedRefreshRate) {
- // onTransact already checks for some permissions, but adding an additional check here.
- // This is to ensure that only system and graphics can request to create a secure
+ // SurfaceComposerAIDL checks for some permissions, but adding an additional check here.
+ // This is to ensure that only root, system, and graphics can request to create a secure
// display. Secure displays can show secure content so we add an additional restriction on it.
const int uid = IPCThreadState::self()->getCallingUid();
- if (secure && uid != AID_GRAPHICS && uid != AID_SYSTEM) {
+ if (secure && uid != AID_ROOT && uid != AID_GRAPHICS && uid != AID_SYSTEM) {
ALOGE("Only privileged processes can create a secure display");
return nullptr;
}
@@ -2702,27 +2699,14 @@
mLayerMetadataSnapshotNeeded = false;
}
- if (DOES_CONTAIN_BORDER) {
- refreshArgs.borderInfoList.clear();
- mDrawingState.traverse([&refreshArgs](Layer* layer) {
- if (layer->isBorderEnabled()) {
- compositionengine::BorderRenderInfo info;
- info.width = layer->getBorderWidth();
- info.color = layer->getBorderColor();
- layer->traverse(LayerVector::StateSet::Drawing, [&info](Layer* ilayer) {
- info.layerIds.push_back(ilayer->getSequence());
- });
- refreshArgs.borderInfoList.emplace_back(std::move(info));
- }
- });
- }
-
refreshArgs.bufferIdsToUncache = std::move(mBufferIdsToUncache);
- refreshArgs.layersWithQueuedFrames.reserve(mLayersWithQueuedFrames.size());
- for (auto layer : mLayersWithQueuedFrames) {
- if (auto layerFE = layer->getCompositionEngineLayerFE())
- refreshArgs.layersWithQueuedFrames.push_back(layerFE);
+ if (!FlagManager::getInstance().ce_fence_promise()) {
+ refreshArgs.layersWithQueuedFrames.reserve(mLayersWithQueuedFrames.size());
+ for (auto& layer : mLayersWithQueuedFrames) {
+ if (const auto& layerFE = layer->getCompositionEngineLayerFE())
+ refreshArgs.layersWithQueuedFrames.push_back(layerFE);
+ }
}
refreshArgs.outputColorSetting = mDisplayColorSetting;
@@ -2784,22 +2768,56 @@
}
refreshArgs.refreshStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
- for (auto [layer, layerFE] : layers) {
+ for (auto& [layer, layerFE] : layers) {
layer->onPreComposition(refreshArgs.refreshStartTime);
}
- mCompositionEngine->present(refreshArgs);
- moveSnapshotsFromCompositionArgs(refreshArgs, layers);
-
- for (auto [layer, layerFE] : layers) {
- CompositionResult compositionResult{layerFE->stealCompositionResult()};
- for (auto& [releaseFence, layerStack] : compositionResult.releaseFences) {
- Layer* clonedFrom = layer->getClonedFrom().get();
- auto owningLayer = clonedFrom ? clonedFrom : layer;
- owningLayer->onLayerDisplayed(std::move(releaseFence), layerStack);
+ if (FlagManager::getInstance().ce_fence_promise()) {
+ for (auto& [layer, layerFE] : layers) {
+ attachReleaseFenceFutureToLayer(layer, layerFE,
+ layerFE->mSnapshot->outputFilter.layerStack);
}
- if (compositionResult.lastClientCompositionFence) {
- layer->setWasClientComposed(compositionResult.lastClientCompositionFence);
+
+ refreshArgs.layersWithQueuedFrames.reserve(mLayersWithQueuedFrames.size());
+ for (auto& layer : mLayersWithQueuedFrames) {
+ if (const auto& layerFE = layer->getCompositionEngineLayerFE()) {
+ refreshArgs.layersWithQueuedFrames.push_back(layerFE);
+ // Some layers are not displayed and do not yet have a future release fence
+ if (layerFE->getReleaseFencePromiseStatus() ==
+ LayerFE::ReleaseFencePromiseStatus::UNINITIALIZED ||
+ layerFE->getReleaseFencePromiseStatus() ==
+ LayerFE::ReleaseFencePromiseStatus::FULFILLED) {
+ // layerStack is invalid because layer is not on a display
+ attachReleaseFenceFutureToLayer(layer.get(), layerFE.get(),
+ ui::INVALID_LAYER_STACK);
+ }
+ }
+ }
+
+ mCompositionEngine->present(refreshArgs);
+ moveSnapshotsFromCompositionArgs(refreshArgs, layers);
+
+ for (auto& [layer, layerFE] : layers) {
+ CompositionResult compositionResult{layerFE->stealCompositionResult()};
+ if (compositionResult.lastClientCompositionFence) {
+ layer->setWasClientComposed(compositionResult.lastClientCompositionFence);
+ }
+ }
+
+ } else {
+ mCompositionEngine->present(refreshArgs);
+ moveSnapshotsFromCompositionArgs(refreshArgs, layers);
+
+ for (auto [layer, layerFE] : layers) {
+ CompositionResult compositionResult{layerFE->stealCompositionResult()};
+ for (auto& [releaseFence, layerStack] : compositionResult.releaseFences) {
+ Layer* clonedFrom = layer->getClonedFrom().get();
+ auto owningLayer = clonedFrom ? clonedFrom : layer;
+ owningLayer->onLayerDisplayed(std::move(releaseFence), layerStack);
+ }
+ if (compositionResult.lastClientCompositionFence) {
+ layer->setWasClientComposed(compositionResult.lastClientCompositionFence);
+ }
}
}
@@ -3065,8 +3083,13 @@
auto optDisplay = layerStackToDisplay.get(layerStack);
if (optDisplay && !optDisplay->get()->isVirtual()) {
auto fence = getHwComposer().getPresentFence(optDisplay->get()->getPhysicalId());
- layer->onLayerDisplayed(ftl::yield<FenceResult>(fence).share(),
- ui::INVALID_LAYER_STACK);
+ if (FlagManager::getInstance().ce_fence_promise()) {
+ layer->prepareReleaseCallbacks(ftl::yield<FenceResult>(fence),
+ ui::INVALID_LAYER_STACK);
+ } else {
+ layer->onLayerDisplayed(ftl::yield<FenceResult>(fence).share(),
+ ui::INVALID_LAYER_STACK);
+ }
}
}
layer->releasePendingBuffer(presentTime.ns());
@@ -5461,11 +5484,6 @@
if (what & layer_state_t::eBlurRegionsChanged) {
if (layer->setBlurRegions(s.blurRegions)) flags |= eTraversalNeeded;
}
- if (what & layer_state_t::eRenderBorderChanged) {
- if (layer->enableBorder(s.borderEnabled, s.borderWidth, s.borderColor)) {
- flags |= eTraversalNeeded;
- }
- }
if (what & layer_state_t::eLayerStackChanged) {
ssize_t idx = mCurrentState.layersSortedByZ.indexOf(layer);
// We only allow setting layer stacks for top level layers,
@@ -6244,9 +6262,9 @@
{"--frontend"s, mainThreadDumper(&SurfaceFlinger::dumpFrontEnd)},
{"--hdrinfo"s, dumper(&SurfaceFlinger::dumpHdrInfo)},
{"--hwclayers"s, mainThreadDumper(&SurfaceFlinger::dumpHwcLayersMinidump)},
- {"--latency"s, argsDumper(&SurfaceFlinger::dumpStatsLocked)},
- {"--latency-clear"s, argsDumper(&SurfaceFlinger::clearStatsLocked)},
- {"--list"s, dumper(&SurfaceFlinger::listLayersLocked)},
+ {"--latency"s, argsMainThreadDumper(&SurfaceFlinger::dumpStats)},
+ {"--latency-clear"s, argsMainThreadDumper(&SurfaceFlinger::clearStats)},
+ {"--list"s, mainThreadDumper(&SurfaceFlinger::listLayers)},
{"--planner"s, argsDumper(&SurfaceFlinger::dumpPlannerInfo)},
{"--scheduler"s, dumper(&SurfaceFlinger::dumpScheduler)},
{"--timestats"s, protoDumper(&SurfaceFlinger::dumpTimeStats)},
@@ -6279,28 +6297,29 @@
return doDump(fd, DumpArgs(), asProto);
}
-void SurfaceFlinger::listLayersLocked(std::string& result) const {
- mCurrentState.traverseInZOrder(
- [&](Layer* layer) { StringAppendF(&result, "%s\n", layer->getDebugName()); });
+void SurfaceFlinger::listLayers(std::string& result) const {
+ for (const auto& layer : mLayerLifecycleManager.getLayers()) {
+ StringAppendF(&result, "%s\n", layer->getDebugString().c_str());
+ }
}
-void SurfaceFlinger::dumpStatsLocked(const DumpArgs& args, std::string& result) const {
+void SurfaceFlinger::dumpStats(const DumpArgs& args, std::string& result) const {
StringAppendF(&result, "%" PRId64 "\n", getVsyncPeriodFromHWC());
if (args.size() < 2) return;
const auto name = String8(args[1]);
- mCurrentState.traverseInZOrder([&](Layer* layer) {
+ traverseLegacyLayers([&](Layer* layer) {
if (layer->getName() == name.c_str()) {
layer->dumpFrameStats(result);
}
});
}
-void SurfaceFlinger::clearStatsLocked(const DumpArgs& args, std::string&) {
+void SurfaceFlinger::clearStats(const DumpArgs& args, std::string&) {
const bool clearAll = args.size() < 2;
const auto name = clearAll ? String8() : String8(args[1]);
- mCurrentState.traverse([&](Layer* layer) {
+ traverseLegacyLayers([&](Layer* layer) {
if (clearAll || layer->getName() == name.c_str()) {
layer->clearFrameStats();
}
@@ -7998,7 +8017,7 @@
// really small crop or frameScale
if (reqSize.width <= 0 || reqSize.height <= 0) {
- ALOGW("Failed to captureLayes: crop or scale too small");
+ ALOGW("Failed to captureLayers: crop or scale too small");
invokeScreenCaptureError(BAD_VALUE, captureListener);
return;
}
@@ -8072,6 +8091,17 @@
args.allowProtected, args.grayscale, captureListener);
}
+// Creates a Future release fence for a layer and keeps track of it in a list to
+// release the buffer when the Future is complete. Calls from composittion
+// involve needing to refresh the composition start time for stats.
+void SurfaceFlinger::attachReleaseFenceFutureToLayer(Layer* layer, LayerFE* layerFE,
+ ui::LayerStack layerStack) {
+ ftl::Future<FenceResult> futureFence = layerFE->createReleaseFenceFuture();
+ Layer* clonedFrom = layer->getClonedFrom().get();
+ auto owningLayer = clonedFrom ? clonedFrom : layer;
+ owningLayer->prepareReleaseCallbacks(std::move(futureFence), layerStack);
+}
+
bool SurfaceFlinger::layersHasProtectedLayer(
const std::vector<std::pair<Layer*, sp<LayerFE>>>& layers) const {
bool protectedLayerFound = false;
@@ -8107,7 +8137,6 @@
const bool supportsProtected = getRenderEngine().supportsProtectedContent();
bool hasProtectedLayer = false;
if (allowProtected && supportsProtected) {
- // Snapshots must be taken from the main thread.
auto layers = mScheduler->schedule([=]() { return getLayerSnapshots(); }).get();
hasProtectedLayer = layersHasProtectedLayer(layers);
}
@@ -8148,6 +8177,14 @@
auto takeScreenshotFn = [=, this, renderAreaFuture = std::move(renderAreaFuture)]() REQUIRES(
kMainThreadContext) mutable -> ftl::SharedFuture<FenceResult> {
+ // LayerSnapshots must be obtained from the main thread.
+ auto layers = getLayerSnapshots();
+ if (FlagManager::getInstance().ce_fence_promise()) {
+ for (auto& [layer, layerFE] : layers) {
+ attachReleaseFenceFutureToLayer(layer, layerFE.get(), ui::INVALID_LAYER_STACK);
+ }
+ }
+
ScreenCaptureResults captureResults;
std::shared_ptr<RenderArea> renderArea = renderAreaFuture.get();
if (!renderArea) {
@@ -8161,8 +8198,8 @@
ftl::SharedFuture<FenceResult> renderFuture;
renderArea->render([&]() FTL_FAKE_GUARD(kMainThreadContext) {
- renderFuture = renderScreenImpl(renderArea, getLayerSnapshots, buffer, regionSampling,
- grayscale, isProtected, captureResults);
+ renderFuture = renderScreenImpl(renderArea, buffer, regionSampling, grayscale,
+ isProtected, captureResults, layers);
});
if (captureListener) {
@@ -8179,6 +8216,10 @@
return renderFuture;
};
+ // TODO(b/294936197): Run takeScreenshotsFn() in a binder thread to reduce the number
+ // of calls on the main thread. renderAreaFuture runs on the main thread and should
+ // no longer be a future, so that it does not need to make an additional jump on the
+ // main thread whenever get() is called.
auto future =
mScheduler->schedule(FTL_FAKE_GUARD(kMainThreadContext, std::move(takeScreenshotFn)));
@@ -8194,9 +8235,17 @@
std::shared_ptr<const RenderArea> renderArea, GetLayerSnapshotsFunction getLayerSnapshots,
const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
bool grayscale, bool isProtected, ScreenCaptureResults& captureResults) {
- ATRACE_CALL();
-
auto layers = getLayerSnapshots();
+ return renderScreenImpl(renderArea, buffer, regionSampling, grayscale, isProtected,
+ captureResults, layers);
+}
+
+ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl(
+ std::shared_ptr<const RenderArea> renderArea,
+ const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
+ bool grayscale, bool isProtected, ScreenCaptureResults& captureResults,
+ std::vector<std::pair<Layer*, sp<android::LayerFE>>>& layers) {
+ ATRACE_CALL();
for (auto& [_, layerFE] : layers) {
frontend::LayerSnapshot* snapshot = layerFE->mSnapshot.get();
@@ -8350,24 +8399,26 @@
auto presentFuture = mRenderEngine->isThreaded() ? ftl::defer(std::move(present)).share()
: ftl::yield(present()).share();
- for (auto& [layer, layerFE] : layers) {
- layer->onLayerDisplayed(presentFuture, ui::INVALID_LAYER_STACK,
- [layerFE = std::move(layerFE)](FenceResult) {
- if (FlagManager::getInstance()
- .screenshot_fence_preservation()) {
- const auto compositionResult =
- layerFE->stealCompositionResult();
- const auto& fences = compositionResult.releaseFences;
- // CompositionEngine may choose to cull layers that
- // aren't visible, so pass a non-fence.
- return fences.empty() ? Fence::NO_FENCE
- : fences.back().first.get();
- } else {
- return layerFE->stealCompositionResult()
- .releaseFences.back()
- .first.get();
- }
- });
+ if (!FlagManager::getInstance().ce_fence_promise()) {
+ for (auto& [layer, layerFE] : layers) {
+ layer->onLayerDisplayed(presentFuture, ui::INVALID_LAYER_STACK,
+ [layerFE = std::move(layerFE)](FenceResult) {
+ if (FlagManager::getInstance()
+ .screenshot_fence_preservation()) {
+ const auto compositionResult =
+ layerFE->stealCompositionResult();
+ const auto& fences = compositionResult.releaseFences;
+ // CompositionEngine may choose to cull layers that
+ // aren't visible, so pass a non-fence.
+ return fences.empty() ? Fence::NO_FENCE
+ : fences.back().first.get();
+ } else {
+ return layerFE->stealCompositionResult()
+ .releaseFences.back()
+ .first.get();
+ }
+ });
+ }
}
return presentFuture;
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 0cc8fbb..c106abd 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -509,10 +509,7 @@
return lockedDumper(std::bind(dump, this, _1, _2, _3));
}
- template <typename F, std::enable_if_t<std::is_member_function_pointer_v<F>>* = nullptr>
- Dumper mainThreadDumper(F dump) {
- using namespace std::placeholders;
- Dumper dumper = std::bind(dump, this, _3);
+ Dumper mainThreadDumperImpl(Dumper dumper) {
return [this, dumper](const DumpArgs& args, bool asProto, std::string& result) -> void {
mScheduler
->schedule(
@@ -522,6 +519,18 @@
};
}
+ template <typename F, std::enable_if_t<std::is_member_function_pointer_v<F>>* = nullptr>
+ Dumper mainThreadDumper(F dump) {
+ using namespace std::placeholders;
+ return mainThreadDumperImpl(std::bind(dump, this, _3));
+ }
+
+ template <typename F, std::enable_if_t<std::is_member_function_pointer_v<F>>* = nullptr>
+ Dumper argsMainThreadDumper(F dump) {
+ using namespace std::placeholders;
+ return mainThreadDumperImpl(std::bind(dump, this, _1, _3));
+ }
+
// Maximum allowed number of display frames that can be set through backdoor
static const int MAX_ALLOWED_DISPLAY_FRAMES = 2048;
@@ -872,6 +881,11 @@
// Traverse through all the layers and compute and cache its bounds.
void computeLayerBounds();
+ // Creates a promise for a future release fence for a layer. This allows for
+ // the layer to keep track of when its buffer can be released.
+ void attachReleaseFenceFutureToLayer(Layer* layer, LayerFE* layerFE, ui::LayerStack layerStack);
+
+ // Checks if a protected layer exists in a list of layers.
bool layersHasProtectedLayer(const std::vector<std::pair<Layer*, sp<LayerFE>>>& layers) const;
void captureScreenCommon(RenderAreaFuture, GetLayerSnapshotsFunction, ui::Size bufferSize,
@@ -883,12 +897,22 @@
const std::shared_ptr<renderengine::ExternalTexture>&, bool regionSampling,
bool grayscale, bool isProtected, const sp<IScreenCaptureListener>&);
+ // Overloaded version of renderScreenImpl that is used when layer snapshots have
+ // not yet been captured, and thus cannot yet be passed in as a parameter.
+ // Needed for TestableSurfaceFlinger.
ftl::SharedFuture<FenceResult> renderScreenImpl(
std::shared_ptr<const RenderArea>, GetLayerSnapshotsFunction,
const std::shared_ptr<renderengine::ExternalTexture>&, bool regionSampling,
bool grayscale, bool isProtected, ScreenCaptureResults&) EXCLUDES(mStateLock)
REQUIRES(kMainThreadContext);
+ ftl::SharedFuture<FenceResult> renderScreenImpl(
+ std::shared_ptr<const RenderArea>,
+ const std::shared_ptr<renderengine::ExternalTexture>&, bool regionSampling,
+ bool grayscale, bool isProtected, ScreenCaptureResults&,
+ std::vector<std::pair<Layer*, sp<android::LayerFE>>>& layers) EXCLUDES(mStateLock)
+ REQUIRES(kMainThreadContext);
+
// If the uid provided is not UNSET_UID, the traverse will skip any layers that don't have a
// matching ownerUid
void traverseLayersInLayerStack(ui::LayerStack, const int32_t uid,
@@ -1113,9 +1137,9 @@
void dumpHwcLayersMinidumpLockedLegacy(std::string& result) const REQUIRES(mStateLock);
void appendSfConfigString(std::string& result) const;
- void listLayersLocked(std::string& result) const;
- void dumpStatsLocked(const DumpArgs& args, std::string& result) const REQUIRES(mStateLock);
- void clearStatsLocked(const DumpArgs& args, std::string& result);
+ void listLayers(std::string& result) const;
+ void dumpStats(const DumpArgs& args, std::string& result) const REQUIRES(mStateLock);
+ void clearStats(const DumpArgs& args, std::string& result);
void dumpTimeStats(const DumpArgs& args, bool asProto, std::string& result) const;
void dumpFrameTimeline(const DumpArgs& args, std::string& result) const;
void logFrameStats(TimePoint now) REQUIRES(kMainThreadContext);
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/TransactionCallbackInvoker.cpp b/services/surfaceflinger/TransactionCallbackInvoker.cpp
index 7b5298c..222ae30 100644
--- a/services/surfaceflinger/TransactionCallbackInvoker.cpp
+++ b/services/surfaceflinger/TransactionCallbackInvoker.cpp
@@ -30,6 +30,7 @@
#include <cinttypes>
#include <binder/IInterface.h>
+#include <common/FlagManager.h>
#include <utils/RefBase.h>
namespace android {
@@ -128,9 +129,17 @@
sp<IBinder> surfaceControl = handle->surfaceControl.promote();
if (surfaceControl) {
sp<Fence> prevFence = nullptr;
- for (const auto& future : handle->previousReleaseFences) {
- mergeFence(handle->name.c_str(), future.get().value_or(Fence::NO_FENCE), prevFence);
+
+ if (FlagManager::getInstance().ce_fence_promise()) {
+ for (auto& future : handle->previousReleaseFences) {
+ mergeFence(handle->name.c_str(), future.get().value_or(Fence::NO_FENCE), prevFence);
+ }
+ } else {
+ for (const auto& future : handle->previousSharedReleaseFences) {
+ mergeFence(handle->name.c_str(), future.get().value_or(Fence::NO_FENCE), prevFence);
+ }
}
+
handle->previousReleaseFence = prevFence;
handle->previousReleaseFences.clear();
diff --git a/services/surfaceflinger/TransactionCallbackInvoker.h b/services/surfaceflinger/TransactionCallbackInvoker.h
index 245398f..cb7150b 100644
--- a/services/surfaceflinger/TransactionCallbackInvoker.h
+++ b/services/surfaceflinger/TransactionCallbackInvoker.h
@@ -46,7 +46,8 @@
bool releasePreviousBuffer = false;
std::string name;
sp<Fence> previousReleaseFence;
- std::vector<ftl::SharedFuture<FenceResult>> previousReleaseFences;
+ std::vector<ftl::Future<FenceResult>> previousReleaseFences;
+ std::vector<ftl::SharedFuture<FenceResult>> previousSharedReleaseFences;
std::variant<nsecs_t, sp<Fence>> acquireTimeOrFence = -1;
nsecs_t latchTime = -1;
std::optional<uint32_t> transformHint = std::nullopt;
diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp
index 3b669c6..4b34a55 100644
--- a/services/surfaceflinger/common/FlagManager.cpp
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -110,11 +110,13 @@
/// Trunk stable server flags ///
DUMP_SERVER_FLAG(refresh_rate_overlay_on_external_display);
+ DUMP_SERVER_FLAG(adpf_gpu_sf);
DUMP_SERVER_FLAG(adpf_use_fmq_channel);
/// Trunk stable readonly flags ///
DUMP_READ_ONLY_FLAG(connected_display);
DUMP_READ_ONLY_FLAG(enable_small_area_detection);
+ DUMP_READ_ONLY_FLAG(frame_rate_category_mrr);
DUMP_READ_ONLY_FLAG(misc1);
DUMP_READ_ONLY_FLAG(vrr_config);
DUMP_READ_ONLY_FLAG(hotplug2);
@@ -134,6 +136,7 @@
DUMP_READ_ONLY_FLAG(restore_blur_step);
DUMP_READ_ONLY_FLAG(dont_skip_on_early_ro);
DUMP_READ_ONLY_FLAG(protected_if_client);
+ DUMP_READ_ONLY_FLAG(ce_fence_promise);
#undef DUMP_READ_ONLY_FLAG
#undef DUMP_SERVER_FLAG
#undef DUMP_FLAG_INTERVAL
@@ -197,6 +200,7 @@
/// Trunk stable readonly flags ///
FLAG_MANAGER_READ_ONLY_FLAG(connected_display, "")
FLAG_MANAGER_READ_ONLY_FLAG(enable_small_area_detection, "")
+FLAG_MANAGER_READ_ONLY_FLAG(frame_rate_category_mrr, "debug.sf.frame_rate_category_mrr")
FLAG_MANAGER_READ_ONLY_FLAG(misc1, "")
FLAG_MANAGER_READ_ONLY_FLAG(vrr_config, "debug.sf.enable_vrr_config")
FLAG_MANAGER_READ_ONLY_FLAG(hotplug2, "")
@@ -210,16 +214,18 @@
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, "")
FLAG_MANAGER_READ_ONLY_FLAG(restore_blur_step, "debug.renderengine.restore_blur_step")
FLAG_MANAGER_READ_ONLY_FLAG(dont_skip_on_early_ro, "")
FLAG_MANAGER_READ_ONLY_FLAG(protected_if_client, "")
+FLAG_MANAGER_READ_ONLY_FLAG(ce_fence_promise, "");
/// Trunk stable server flags ///
FLAG_MANAGER_SERVER_FLAG(refresh_rate_overlay_on_external_display, "")
+FLAG_MANAGER_SERVER_FLAG(adpf_gpu_sf, "")
/// Trunk stable server flags from outside SurfaceFlinger ///
FLAG_MANAGER_SERVER_FLAG_IMPORTED(adpf_use_fmq_channel, "", android::os)
diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h
index 763963e..320e34b 100644
--- a/services/surfaceflinger/common/include/common/FlagManager.h
+++ b/services/surfaceflinger/common/include/common/FlagManager.h
@@ -49,10 +49,12 @@
/// Trunk stable server flags ///
bool refresh_rate_overlay_on_external_display() const;
+ bool adpf_gpu_sf() const;
bool adpf_use_fmq_channel() const;
/// Trunk stable readonly flags ///
bool connected_display() const;
+ bool frame_rate_category_mrr() const;
bool enable_small_area_detection() const;
bool misc1() const;
bool vrr_config() const;
@@ -73,6 +75,7 @@
bool restore_blur_step() const;
bool dont_skip_on_early_ro() const;
bool protected_if_client() const;
+ bool ce_fence_promise() const;
protected:
// overridden for unit tests
diff --git a/services/surfaceflinger/surfaceflinger_flags_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
index 5451752..0a5cde3 100644
--- a/services/surfaceflinger/surfaceflinger_flags_new.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
@@ -4,10 +4,39 @@
container: "system"
flag {
+ name: "adpf_gpu_sf"
+ namespace: "game"
+ description: "Guards use of the sending ADPF GPU duration hint and load hints from SurfaceFlinger to Power HAL"
+ bug: "284324521"
+} # adpf_gpu_sf
+
+flag {
+ name: "ce_fence_promise"
+ namespace: "window_surfaces"
+ description: "Moves logic for buffer release fences into LayerFE"
+ bug: "294936197"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "dont_skip_on_early_ro2"
namespace: "core_graphics"
description: "This flag is guarding the behaviour where SurfaceFlinger is trying to opportunistically present a frame when the configuration change from late to early"
bug: "273702768"
} # dont_skip_on_early_ro2
+flag {
+ name: "frame_rate_category_mrr"
+ namespace: "core_graphics"
+ description: "Enable to use frame rate category and newer frame rate votes such as GTE in SurfaceFlinger scheduler, to guard dVRR changes from MRR devices"
+ bug: "330224639"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+} # frame_rate_category_mrr
+
# IMPORTANT - please keep alphabetize to reduce merge conflicts
diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp
index 925fe0b..38fc977 100644
--- a/services/surfaceflinger/tests/Android.bp
+++ b/services/surfaceflinger/tests/Android.bp
@@ -38,10 +38,10 @@
"DereferenceSurfaceControl_test.cpp",
"DisplayConfigs_test.cpp",
"DisplayEventReceiver_test.cpp",
+ "Dumpsys_test.cpp",
"EffectLayer_test.cpp",
"HdrSdrRatioOverlay_test.cpp",
"InvalidHandles_test.cpp",
- "LayerBorder_test.cpp",
"LayerCallback_test.cpp",
"LayerRenderTypeTransaction_test.cpp",
"LayerState_test.cpp",
@@ -67,6 +67,7 @@
static_libs: [
"liblayers_proto",
"android.hardware.graphics.composer@2.1",
+ "libsurfaceflinger_common",
],
shared_libs: [
"android.hardware.graphics.common@1.2",
@@ -82,6 +83,7 @@
"libprotobuf-cpp-full",
"libui",
"libutils",
+ "server_configurable_flags",
],
header_libs: [
"libnativewindow_headers",
diff --git a/services/surfaceflinger/tests/Credentials_test.cpp b/services/surfaceflinger/tests/Credentials_test.cpp
index 822ac4d..9b83713 100644
--- a/services/surfaceflinger/tests/Credentials_test.cpp
+++ b/services/surfaceflinger/tests/Credentials_test.cpp
@@ -241,7 +241,7 @@
// Check with root.
{
UIDFaker f(AID_ROOT);
- ASSERT_FALSE(condition());
+ ASSERT_TRUE(condition());
}
// Check as a Graphics user.
diff --git a/services/surfaceflinger/tests/Dumpsys_test.cpp b/services/surfaceflinger/tests/Dumpsys_test.cpp
new file mode 100644
index 0000000..c3914e5
--- /dev/null
+++ b/services/surfaceflinger/tests/Dumpsys_test.cpp
@@ -0,0 +1,109 @@
+/*
+ * 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.
+ */
+
+#include <android/native_window.h>
+#include <gtest/gtest.h>
+#include <gui/SurfaceComposerClient.h>
+#include "android-base/stringprintf.h"
+#include "utils/Errors.h"
+
+namespace android {
+
+namespace {
+status_t runShellCommand(const std::string& cmd, std::string& result) {
+ FILE* pipe = popen(cmd.c_str(), "r");
+ if (!pipe) {
+ return UNKNOWN_ERROR;
+ }
+
+ char buffer[1024];
+ while (fgets(buffer, sizeof(buffer), pipe) != NULL) {
+ result += buffer;
+ }
+
+ pclose(pipe);
+ return OK;
+}
+} // namespace
+
+using android::hardware::graphics::common::V1_1::BufferUsage;
+
+class WaitForCompletedCallback {
+public:
+ WaitForCompletedCallback() = default;
+ ~WaitForCompletedCallback() = default;
+
+ static void transactionCompletedCallback(void* callbackContext, nsecs_t /* latchTime */,
+ const sp<Fence>& /* presentFence */,
+ const std::vector<SurfaceControlStats>& /* stats */) {
+ ASSERT_NE(callbackContext, nullptr) << "failed to get callback context";
+ WaitForCompletedCallback* context = static_cast<WaitForCompletedCallback*>(callbackContext);
+ context->notify();
+ }
+
+ void wait() {
+ std::unique_lock lock(mMutex);
+ cv.wait(lock, [this] { return mCallbackReceived; });
+ }
+
+ void notify() {
+ std::unique_lock lock(mMutex);
+ mCallbackReceived = true;
+ cv.notify_one();
+ }
+
+private:
+ std::mutex mMutex;
+ std::condition_variable cv;
+ bool mCallbackReceived = false;
+};
+
+TEST(Dumpsys, listLayers) {
+ sp<SurfaceComposerClient> client = sp<SurfaceComposerClient>::make();
+ ASSERT_EQ(NO_ERROR, client->initCheck());
+ auto newLayer =
+ client->createSurface(String8("MY_TEST_LAYER"), 100, 100, PIXEL_FORMAT_RGBA_8888, 0);
+ std::string layersAsString;
+ EXPECT_EQ(OK, runShellCommand("dumpsys SurfaceFlinger --list", layersAsString));
+ EXPECT_NE(strstr(layersAsString.c_str(), ""), nullptr);
+}
+
+TEST(Dumpsys, stats) {
+ sp<SurfaceComposerClient> client = sp<SurfaceComposerClient>::make();
+ ASSERT_EQ(NO_ERROR, client->initCheck());
+ auto newLayer =
+ client->createSurface(String8("MY_TEST_LAYER"), 100, 100, PIXEL_FORMAT_RGBA_8888, 0);
+ uint64_t usageFlags = BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN |
+ BufferUsage::COMPOSER_OVERLAY | BufferUsage::GPU_TEXTURE;
+
+ sp<GraphicBuffer> buffer =
+ sp<GraphicBuffer>::make(1u, 1u, PIXEL_FORMAT_RGBA_8888, 1u, usageFlags, "test");
+
+ WaitForCompletedCallback callback;
+ SurfaceComposerClient::Transaction()
+ .setBuffer(newLayer, buffer)
+ .addTransactionCompletedCallback(WaitForCompletedCallback::transactionCompletedCallback,
+ &callback)
+ .apply();
+ callback.wait();
+ std::string stats;
+ std::string layerName = base::StringPrintf("MY_TEST_LAYER#%d", newLayer->getLayerId());
+ EXPECT_EQ(OK, runShellCommand("dumpsys SurfaceFlinger --latency " + layerName, stats));
+ EXPECT_NE(std::string(""), stats);
+ EXPECT_EQ(OK, runShellCommand("dumpsys SurfaceFlinger --latency-clear " + layerName, stats));
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/LayerBorder_test.cpp b/services/surfaceflinger/tests/LayerBorder_test.cpp
deleted file mode 100644
index 00e134b..0000000
--- a/services/surfaceflinger/tests/LayerBorder_test.cpp
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-// TODO: Amend all tests when screenshots become fully reworked for borders
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
-#include <chrono> // std::chrono::seconds
-#include <thread> // std::this_thread::sleep_for
-#include "LayerTransactionTest.h"
-
-namespace android {
-
-class LayerBorderTest : public LayerTransactionTest {
-protected:
- virtual void SetUp() {
- LayerTransactionTest::SetUp();
- ASSERT_EQ(NO_ERROR, mClient->initCheck());
-
- toHalf3 = ColorTransformHelper::toHalf3;
- toHalf4 = ColorTransformHelper::toHalf4;
-
- const auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
- ASSERT_FALSE(ids.empty());
- const auto display = SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
- ASSERT_FALSE(display == nullptr);
- mColorOrange = toHalf4({255, 140, 0, 255});
- mParentLayer = createColorLayer("Parent layer", Color::RED);
-
- mContainerLayer = mClient->createSurface(String8("Container Layer"), 0 /* width */,
- 0 /* height */, PIXEL_FORMAT_RGBA_8888,
- ISurfaceComposerClient::eFXSurfaceContainer |
- ISurfaceComposerClient::eNoColorFill,
- mParentLayer->getHandle());
- EXPECT_NE(nullptr, mContainerLayer.get()) << "failed to create container layer";
-
- mEffectLayer1 = mClient->createSurface(String8("Effect Layer"), 0 /* width */,
- 0 /* height */, PIXEL_FORMAT_RGBA_8888,
- ISurfaceComposerClient::eFXSurfaceEffect |
- ISurfaceComposerClient::eNoColorFill,
- mContainerLayer->getHandle());
- EXPECT_NE(nullptr, mEffectLayer1.get()) << "failed to create effect layer 1";
-
- mEffectLayer2 = mClient->createSurface(String8("Effect Layer"), 0 /* width */,
- 0 /* height */, PIXEL_FORMAT_RGBA_8888,
- ISurfaceComposerClient::eFXSurfaceEffect |
- ISurfaceComposerClient::eNoColorFill,
- mContainerLayer->getHandle());
-
- EXPECT_NE(nullptr, mEffectLayer2.get()) << "failed to create effect layer 2";
-
- asTransaction([&](Transaction& t) {
- t.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK);
- t.setLayer(mParentLayer, INT32_MAX - 20).show(mParentLayer);
- t.setFlags(mParentLayer, layer_state_t::eLayerOpaque, layer_state_t::eLayerOpaque);
-
- t.setColor(mEffectLayer1, toHalf3(Color::BLUE));
-
- t.setColor(mEffectLayer2, toHalf3(Color::GREEN));
- });
- }
-
- virtual void TearDown() {
- // Uncomment the line right below when running any of the tests
- // std::this_thread::sleep_for (std::chrono::seconds(30));
- LayerTransactionTest::TearDown();
- mParentLayer = 0;
- }
-
- std::function<half3(Color)> toHalf3;
- std::function<half4(Color)> toHalf4;
- sp<SurfaceControl> mParentLayer, mContainerLayer, mEffectLayer1, mEffectLayer2;
- half4 mColorOrange;
-};
-
-TEST_F(LayerBorderTest, OverlappingVisibleRegions) {
- asTransaction([&](Transaction& t) {
- t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
- t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
-
- t.enableBorder(mContainerLayer, true, 20, mColorOrange);
- t.show(mEffectLayer1);
- t.show(mEffectLayer2);
- t.show(mContainerLayer);
- });
-}
-
-TEST_F(LayerBorderTest, PartiallyCoveredVisibleRegion) {
- asTransaction([&](Transaction& t) {
- t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
- t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
-
- t.enableBorder(mEffectLayer1, true, 20, mColorOrange);
- t.show(mEffectLayer1);
- t.show(mEffectLayer2);
- t.show(mContainerLayer);
- });
-}
-
-TEST_F(LayerBorderTest, NonOverlappingVisibleRegion) {
- asTransaction([&](Transaction& t) {
- t.setCrop(mEffectLayer1, Rect(0, 0, 200, 200));
- t.setCrop(mEffectLayer2, Rect(400, 400, 600, 600));
-
- t.enableBorder(mContainerLayer, true, 20, mColorOrange);
- t.show(mEffectLayer1);
- t.show(mEffectLayer2);
- t.show(mContainerLayer);
- });
-}
-
-TEST_F(LayerBorderTest, EmptyVisibleRegion) {
- asTransaction([&](Transaction& t) {
- t.setCrop(mEffectLayer1, Rect(200, 200, 400, 400));
- t.setCrop(mEffectLayer2, Rect(0, 0, 600, 600));
-
- t.enableBorder(mEffectLayer1, true, 20, mColorOrange);
- t.show(mEffectLayer1);
- t.show(mEffectLayer2);
- t.show(mContainerLayer);
- });
-}
-
-TEST_F(LayerBorderTest, ZOrderAdjustment) {
- asTransaction([&](Transaction& t) {
- t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
- t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
- t.setLayer(mParentLayer, 10);
- t.setLayer(mEffectLayer1, 30);
- t.setLayer(mEffectLayer2, 20);
-
- t.enableBorder(mEffectLayer1, true, 20, mColorOrange);
- t.show(mEffectLayer1);
- t.show(mEffectLayer2);
- t.show(mContainerLayer);
- });
-}
-
-TEST_F(LayerBorderTest, GrandChildHierarchy) {
- sp<SurfaceControl> containerLayer2 =
- mClient->createSurface(String8("Container Layer"), 0 /* width */, 0 /* height */,
- PIXEL_FORMAT_RGBA_8888,
- ISurfaceComposerClient::eFXSurfaceContainer |
- ISurfaceComposerClient::eNoColorFill,
- mContainerLayer->getHandle());
- EXPECT_NE(nullptr, containerLayer2.get()) << "failed to create container layer 2";
-
- sp<SurfaceControl> effectLayer3 =
- mClient->createSurface(String8("Effect Layer"), 0 /* width */, 0 /* height */,
- PIXEL_FORMAT_RGBA_8888,
- ISurfaceComposerClient::eFXSurfaceEffect |
- ISurfaceComposerClient::eNoColorFill,
- containerLayer2->getHandle());
-
- asTransaction([&](Transaction& t) {
- t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
- t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
- t.setCrop(effectLayer3, Rect(400, 400, 800, 800));
- t.setColor(effectLayer3, toHalf3(Color::BLUE));
-
- t.enableBorder(mContainerLayer, true, 20, mColorOrange);
- t.show(mEffectLayer1);
- t.show(mEffectLayer2);
- t.show(effectLayer3);
- t.show(mContainerLayer);
- });
-}
-
-TEST_F(LayerBorderTest, TransparentAlpha) {
- asTransaction([&](Transaction& t) {
- t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
- t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
- t.setAlpha(mEffectLayer1, 0.0f);
-
- t.enableBorder(mContainerLayer, true, 20, mColorOrange);
- t.show(mEffectLayer1);
- t.show(mEffectLayer2);
- t.show(mContainerLayer);
- });
-}
-
-TEST_F(LayerBorderTest, SemiTransparentAlpha) {
- asTransaction([&](Transaction& t) {
- t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
- t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
- t.setAlpha(mEffectLayer2, 0.5f);
-
- t.enableBorder(mEffectLayer2, true, 20, mColorOrange);
- t.show(mEffectLayer1);
- t.show(mEffectLayer2);
- t.show(mContainerLayer);
- });
-}
-
-TEST_F(LayerBorderTest, InvisibleLayers) {
- asTransaction([&](Transaction& t) {
- t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
- t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
-
- t.enableBorder(mContainerLayer, true, 20, mColorOrange);
- t.hide(mEffectLayer2);
- t.show(mContainerLayer);
- });
-}
-
-TEST_F(LayerBorderTest, LayerWithBuffer) {
- asTransaction([&](Transaction& t) {
- t.hide(mEffectLayer1);
- t.hide(mEffectLayer2);
- t.show(mContainerLayer);
-
- sp<SurfaceControl> layer =
- mClient->createSurface(String8("BufferState"), 0 /* width */, 0 /* height */,
- PIXEL_FORMAT_RGBA_8888,
- ISurfaceComposerClient::eFXSurfaceBufferState,
- mContainerLayer->getHandle());
-
- sp<GraphicBuffer> buffer =
- sp<GraphicBuffer>::make(400u, 400u, PIXEL_FORMAT_RGBA_8888, 1u,
- BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN |
- BufferUsage::COMPOSER_OVERLAY |
- BufferUsage::GPU_TEXTURE,
- "test");
- TransactionUtils::fillGraphicBufferColor(buffer, Rect(0, 0, 200, 200), Color::GREEN);
- TransactionUtils::fillGraphicBufferColor(buffer, Rect(200, 200, 400, 400), Color::BLUE);
-
- t.setBuffer(layer, buffer);
- t.setPosition(layer, 100, 100);
- t.show(layer);
- t.enableBorder(mContainerLayer, true, 20, mColorOrange);
- });
-}
-
-TEST_F(LayerBorderTest, CustomWidth) {
- asTransaction([&](Transaction& t) {
- t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
- t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
-
- t.enableBorder(mContainerLayer, true, 50, mColorOrange);
- t.show(mEffectLayer1);
- t.show(mEffectLayer2);
- t.show(mContainerLayer);
- });
-}
-
-TEST_F(LayerBorderTest, CustomColor) {
- asTransaction([&](Transaction& t) {
- t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
- t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
-
- t.enableBorder(mContainerLayer, true, 20, toHalf4({255, 0, 255, 255}));
- t.show(mEffectLayer1);
- t.show(mEffectLayer2);
- t.show(mContainerLayer);
- });
-}
-
-TEST_F(LayerBorderTest, CustomWidthAndColorAndOpacity) {
- asTransaction([&](Transaction& t) {
- t.setCrop(mEffectLayer1, Rect(0, 0, 200, 200));
- t.setCrop(mEffectLayer2, Rect(400, 400, 600, 600));
-
- t.enableBorder(mContainerLayer, true, 40, toHalf4({255, 255, 0, 128}));
- t.show(mEffectLayer1);
- t.show(mEffectLayer2);
- t.show(mContainerLayer);
- });
-}
-
-} // namespace android
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/tests/LayerCallback_test.cpp b/services/surfaceflinger/tests/LayerCallback_test.cpp
index 79886bd..b4496d3 100644
--- a/services/surfaceflinger/tests/LayerCallback_test.cpp
+++ b/services/surfaceflinger/tests/LayerCallback_test.cpp
@@ -1295,4 +1295,74 @@
}
}
+TEST_F(LayerCallbackTest, OccludedLayerHasReleaseCallback) {
+ sp<SurfaceControl> layer1, layer2;
+ ASSERT_NO_FATAL_FAILURE(layer1 = createLayerWithBuffer());
+ ASSERT_NO_FATAL_FAILURE(layer2 = createLayerWithBuffer());
+
+ Transaction transaction1, transaction2;
+ CallbackHelper callback1a, callback1b, callback2a, callback2b;
+ int err = fillTransaction(transaction1, &callback1a, layer1);
+ if (err) {
+ GTEST_SUCCEED() << "test not supported";
+ return;
+ }
+ err = fillTransaction(transaction2, &callback2a, layer2);
+ if (err) {
+ GTEST_SUCCEED() << "test not supported";
+ return;
+ }
+
+ ui::Size bufferSize = getBufferSize();
+
+ // Occlude layer1 with layer2
+ TransactionUtils::setFrame(transaction1, layer1,
+ Rect(0, 0, bufferSize.width, bufferSize.height), Rect(0, 0, 32, 32));
+ TransactionUtils::setFrame(transaction2, layer2,
+ Rect(0, 0, bufferSize.width, bufferSize.height), Rect(0, 0, 32, 32));
+ transaction1.apply();
+ transaction2.apply();
+
+ ExpectedResult expected1a, expected1b, expected2a, expected2b;
+ expected1a.addSurface(ExpectedResult::Transaction::PRESENTED, {layer1},
+ ExpectedResult::Buffer::ACQUIRED,
+ ExpectedResult::PreviousBuffer::NOT_RELEASED);
+
+ expected2a.addSurface(ExpectedResult::Transaction::PRESENTED, {layer2},
+ ExpectedResult::Buffer::ACQUIRED,
+ ExpectedResult::PreviousBuffer::NOT_RELEASED);
+
+ EXPECT_NO_FATAL_FAILURE(waitForCallback(callback1a, expected1a, true));
+ EXPECT_NO_FATAL_FAILURE(waitForCallback(callback2a, expected2a, true));
+
+ // Submit new buffers so previous buffers can be released
+ err = fillTransaction(transaction1, &callback1b, layer1);
+ if (err) {
+ GTEST_SUCCEED() << "test not supported";
+ return;
+ }
+ err = fillTransaction(transaction2, &callback2b, layer2);
+ if (err) {
+ GTEST_SUCCEED() << "test not supported";
+ return;
+ }
+
+ TransactionUtils::setFrame(transaction1, layer1,
+ Rect(0, 0, bufferSize.width, bufferSize.height), Rect(0, 0, 32, 32));
+ TransactionUtils::setFrame(transaction2, layer2,
+ Rect(0, 0, bufferSize.width, bufferSize.height), Rect(0, 0, 32, 32));
+ transaction1.apply();
+ transaction2.apply();
+
+ expected1b.addSurface(ExpectedResult::Transaction::PRESENTED, {layer1},
+ ExpectedResult::Buffer::ACQUIRED,
+ ExpectedResult::PreviousBuffer::RELEASED);
+
+ expected2b.addSurface(ExpectedResult::Transaction::PRESENTED, {layer2},
+ ExpectedResult::Buffer::ACQUIRED,
+ ExpectedResult::PreviousBuffer::RELEASED);
+
+ EXPECT_NO_FATAL_FAILURE(waitForCallback(callback1b, expected1b, true));
+ EXPECT_NO_FATAL_FAILURE(waitForCallback(callback2b, expected2b, true));
+}
} // namespace android
diff --git a/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp b/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp
index 15ff696..7fce7e9 100644
--- a/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp
+++ b/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp
@@ -18,9 +18,12 @@
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#include <common/FlagManager.h>
#include <ui/DisplayState.h>
#include "LayerTransactionTest.h"
+#include "gui/SurfaceComposerClient.h"
+#include "ui/DisplayId.h"
namespace android {
@@ -37,7 +40,8 @@
const auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
ASSERT_FALSE(ids.empty());
- mMainDisplay = SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
+ mMainDisplayId = ids.front();
+ mMainDisplay = SurfaceComposerClient::getPhysicalDisplayToken(mMainDisplayId);
SurfaceComposerClient::getDisplayState(mMainDisplay, &mMainDisplayState);
SurfaceComposerClient::getActiveDisplayMode(mMainDisplay, &mMainDisplayMode);
@@ -85,6 +89,7 @@
ui::DisplayState mMainDisplayState;
ui::DisplayMode mMainDisplayMode;
sp<IBinder> mMainDisplay;
+ PhysicalDisplayId mMainDisplayId;
sp<IBinder> mVirtualDisplay;
sp<IGraphicBufferProducer> mProducer;
sp<SurfaceControl> mColorLayer;
@@ -119,6 +124,9 @@
createDisplay(mMainDisplayState.layerStackSpaceRect, ui::DEFAULT_LAYER_STACK);
createColorLayer(ui::DEFAULT_LAYER_STACK);
+ sp<SurfaceControl> mirrorSc =
+ SurfaceComposerClient::getDefault()->mirrorDisplay(mMainDisplayId);
+
asTransaction([&](Transaction& t) { t.setPosition(mColorLayer, 10, 10); });
// Verify color layer renders correctly on main display and it is mirrored on the
@@ -133,6 +141,37 @@
sc->expectColor(Rect(0, 0, 9, 9), {0, 0, 0, 255});
}
+TEST_F(MultiDisplayLayerBoundsTest, RenderLayerWithPromisedFenceInMirroredVirtualDisplay) {
+ // Create a display and use a unique layerstack ID for mirrorDisplay() so
+ // the contents of the main display are mirrored on to the virtual display.
+
+ // A unique layerstack ID must be used because sharing the same layerFE
+ // with more than one display is unsupported. A unique layerstack ensures
+ // that a different layerFE is used between displays.
+ constexpr ui::LayerStack layerStack{77687666}; // ASCII for MDLB (MultiDisplayLayerBounds)
+ createDisplay(mMainDisplayState.layerStackSpaceRect, layerStack);
+ createColorLayer(ui::DEFAULT_LAYER_STACK);
+
+ sp<SurfaceControl> mirrorSc =
+ SurfaceComposerClient::getDefault()->mirrorDisplay(mMainDisplayId);
+
+ asTransaction([&](Transaction& t) {
+ t.setPosition(mColorLayer, 10, 10);
+ t.setLayerStack(mirrorSc, layerStack);
+ });
+
+ // Verify color layer renders correctly on main display and it is mirrored on the
+ // virtual display.
+ std::unique_ptr<ScreenCapture> sc;
+ ScreenCapture::captureScreen(&sc, mMainDisplay);
+ sc->expectColor(Rect(10, 10, 40, 50), mExpectedColor);
+ sc->expectColor(Rect(0, 0, 9, 9), {0, 0, 0, 255});
+
+ ScreenCapture::captureScreen(&sc, mVirtualDisplay);
+ sc->expectColor(Rect(10, 10, 40, 50), mExpectedColor);
+ sc->expectColor(Rect(0, 0, 9, 9), {0, 0, 0, 255});
+}
+
} // namespace android
// TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/tests/TransactionTestHarnesses.h b/services/surfaceflinger/tests/TransactionTestHarnesses.h
index 797a64c..87e6d3e 100644
--- a/services/surfaceflinger/tests/TransactionTestHarnesses.h
+++ b/services/surfaceflinger/tests/TransactionTestHarnesses.h
@@ -16,9 +16,11 @@
#ifndef ANDROID_TRANSACTION_TEST_HARNESSES
#define ANDROID_TRANSACTION_TEST_HARNESSES
+#include <common/FlagManager.h>
#include <ui/DisplayState.h>
#include "LayerTransactionTest.h"
+#include "ui/LayerStack.h"
namespace android {
@@ -36,9 +38,10 @@
case RenderPath::VIRTUAL_DISPLAY:
const auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
+ const PhysicalDisplayId displayId = ids.front();
const auto displayToken = ids.empty()
? nullptr
- : SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
+ : SurfaceComposerClient::getPhysicalDisplayToken(displayId);
ui::DisplayState displayState;
SurfaceComposerClient::getDisplayState(displayToken, &displayState);
@@ -66,11 +69,21 @@
vDisplay = SurfaceComposerClient::createDisplay(String8("VirtualDisplay"),
false /*secure*/);
+ constexpr ui::LayerStack layerStack{
+ 848472}; // ASCII for TTH (TransactionTestHarnesses)
+ sp<SurfaceControl> mirrorSc =
+ SurfaceComposerClient::getDefault()->mirrorDisplay(displayId);
+
SurfaceComposerClient::Transaction t;
t.setDisplaySurface(vDisplay, producer);
- t.setDisplayLayerStack(vDisplay, ui::DEFAULT_LAYER_STACK);
t.setDisplayProjection(vDisplay, displayState.orientation,
Rect(displayState.layerStackSpaceRect), Rect(resolution));
+ if (FlagManager::getInstance().ce_fence_promise()) {
+ t.setDisplayLayerStack(vDisplay, layerStack);
+ t.setLayerStack(mirrorSc, layerStack);
+ } else {
+ t.setDisplayLayerStack(vDisplay, ui::DEFAULT_LAYER_STACK);
+ }
t.apply();
SurfaceComposerClient::Transaction().apply(true);
@@ -85,6 +98,15 @@
constexpr bool kContainsHdr = false;
auto sc = std::make_unique<ScreenCapture>(item.mGraphicBuffer, kContainsHdr);
itemConsumer->releaseBuffer(item);
+
+ // Possible race condition with destroying virtual displays, in which
+ // CompositionEngine::present may attempt to be called on the same
+ // display multiple times. The layerStack is set to invalid here so
+ // that the display is ignored if that scenario occurs.
+ if (FlagManager::getInstance().ce_fence_promise()) {
+ t.setLayerStack(mirrorSc, ui::INVALID_LAYER_STACK);
+ t.apply(true);
+ }
SurfaceComposerClient::destroyDisplay(vDisplay);
return sc;
}
diff --git a/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp b/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp
index 0adf0b6..51b5f40 100644
--- a/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp
@@ -85,7 +85,7 @@
EXPECT_EQ(false, mFlagManager.test_flag());
}
-TEST_F(FlagManagerTest, creashesIfQueriedBeforeBoot) {
+TEST_F(FlagManagerTest, crashesIfQueriedBeforeBoot) {
mFlagManager.markBootIncomplete();
EXPECT_DEATH(FlagManager::getInstance()
.refresh_rate_overlay_on_external_display(), "");
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
index 110f324..2fb80b1 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
@@ -274,6 +274,8 @@
}
TEST_F(LayerHistoryIntegrationTest, oneLayerExplicitCategory) {
+ SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true);
+
createLegacyAndFrontedEndLayer(1);
setFrameRateCategory(1, ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH);
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index 9b8ff42..c63aaeb 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -548,7 +548,41 @@
EXPECT_EQ(0, frequentLayerCount(time));
}
+TEST_F(LayerHistoryTest, oneLayerExplicitVoteWithCategory_vrrFeatureOff) {
+ SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, false);
+
+ auto layer = createLayer();
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree())
+ .WillRepeatedly(
+ Return(Layer::FrameRate(73.4_Hz, Layer::FrameRateCompatibility::Default,
+ Seamlessness::OnlySeamless, FrameRateCategory::High)));
+
+ // Set default to Min so it is obvious that the vote reset triggered.
+ setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Min);
+
+ EXPECT_EQ(1, layerCount());
+ EXPECT_EQ(0, activeLayerCount());
+
+ nsecs_t time = systemTime();
+ for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
+ time += HI_FPS_PERIOD;
+ }
+
+ // There is only 1 LayerRequirement due to the disabled flag frame_rate_category_mrr.
+ ASSERT_EQ(1, summarizeLayerHistory(time).size());
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+ EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+ EXPECT_EQ(FrameRateCategory::Default, summarizeLayerHistory(time)[0].frameRateCategory);
+}
+
TEST_F(LayerHistoryTest, oneLayerExplicitCategory) {
+ SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true);
+
auto layer = createLayer();
EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
EXPECT_CALL(*layer, getFrameRateForLayerTree())
@@ -588,6 +622,8 @@
// This test case should be the same as oneLayerNoVote except instead of layer vote is NoVote,
// the category is NoPreference.
TEST_F(LayerHistoryTest, oneLayerCategoryNoPreference) {
+ SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true);
+
auto layer = createLayer();
EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
EXPECT_CALL(*layer, getFrameRateForLayerTree())
@@ -617,6 +653,8 @@
}
TEST_F(LayerHistoryTest, oneLayerExplicitVoteWithCategory) {
+ SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true);
+
auto layer = createLayer();
EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
EXPECT_CALL(*layer, getFrameRateForLayerTree())
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index 3baa48d..94989aa 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -17,6 +17,7 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
+#include <common/test/FlagUtils.h>
#include <renderengine/mock/FakeExternalTexture.h>
#include "FrontEnd/LayerHierarchy.h"
@@ -26,6 +27,8 @@
#include "LayerHierarchyTest.h"
#include "ui/GraphicTypes.h"
+#include <com_android_graphics_surfaceflinger_flags.h>
+
#define UPDATE_AND_VERIFY(BUILDER, ...) \
({ \
SCOPED_TRACE(""); \
@@ -42,6 +45,7 @@
using ftl::Flags;
using namespace ftl::flag_operators;
+using namespace com::android::graphics::surfaceflinger;
// To run test:
/**
@@ -668,6 +672,8 @@
// This test is similar to "frameRate" test case but checks that the setFrameRateCategory API
// interaction also works correctly with the setFrameRate API within SF frontend.
TEST_F(LayerSnapshotTest, frameRateWithCategory) {
+ SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true);
+
// ROOT
// ├── 1
// │ ├── 11 (frame rate set to 244.f)
@@ -864,6 +870,8 @@
}
TEST_F(LayerSnapshotTest, frameRateSelectionStrategyWithCategory) {
+ SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true);
+
// ROOT
// ├── 1
// │ ├── 11
diff --git a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
index d9343c7..86ad86e 100644
--- a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
@@ -20,6 +20,8 @@
#include <DisplayHardware/PowerAdvisor.h>
#include <android_os.h>
#include <binder/Status.h>
+#include <com_android_graphics_surfaceflinger_flags.h>
+#include <common/FlagManager.h>
#include <common/test/FlagUtils.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@@ -51,6 +53,20 @@
void setTimingTestingMode(bool testinMode);
void allowReportActualToAcquireMutex();
bool sessionExists();
+ int64_t toNanos(Duration d);
+
+ struct GpuTestConfig {
+ bool adpfGpuFlagOn;
+ Duration frame1GpuFenceDuration;
+ Duration frame2GpuFenceDuration;
+ Duration vsyncPeriod;
+ Duration presentDuration = 0ms;
+ Duration postCompDuration = 0ms;
+ bool frame1RequiresRenderEngine;
+ bool frame2RequiresRenderEngine;
+ };
+
+ WorkDuration testGpuScenario(GpuTestConfig& config);
protected:
TestableSurfaceFlinger mFlinger;
@@ -65,6 +81,10 @@
return mPowerAdvisor->mHintSession != nullptr;
}
+int64_t PowerAdvisorTest::toNanos(Duration d) {
+ return std::chrono::nanoseconds(d).count();
+}
+
void PowerAdvisorTest::SetUp() {
mPowerAdvisor = std::make_unique<impl::PowerAdvisor>(*mFlinger.flinger());
mPowerAdvisor->mPowerHal = std::make_unique<NiceMock<MockPowerHalController>>();
@@ -117,6 +137,71 @@
mPowerAdvisor->mDelayReportActualMutexAcquisitonPromise.set_value(true);
}
+WorkDuration PowerAdvisorTest::testGpuScenario(GpuTestConfig& config) {
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::adpf_gpu_sf,
+ config.adpfGpuFlagOn);
+ mPowerAdvisor->onBootFinished();
+ startPowerHintSession();
+
+ std::vector<DisplayId> displayIds{PhysicalDisplayId::fromPort(42u), GpuVirtualDisplayId(0),
+ GpuVirtualDisplayId(1)};
+ mPowerAdvisor->setDisplays(displayIds);
+ auto display1 = displayIds[0];
+ // 60hz
+
+ TimePoint startTime = TimePoint::now();
+ // advisor only starts on frame 2 so do an initial frame
+ fakeBasicFrameTiming(startTime, config.vsyncPeriod);
+ setExpectedTiming(config.vsyncPeriod, startTime + config.vsyncPeriod);
+
+ // report GPU
+ mPowerAdvisor->setRequiresRenderEngine(display1, config.frame1RequiresRenderEngine);
+ if (config.adpfGpuFlagOn) {
+ mPowerAdvisor->setGpuStartTime(display1, startTime);
+ }
+ if (config.frame1GpuFenceDuration.count() == Fence::SIGNAL_TIME_PENDING) {
+ mPowerAdvisor->setGpuFenceTime(display1,
+ std::make_unique<FenceTime>(Fence::SIGNAL_TIME_PENDING));
+ } else {
+ TimePoint end = startTime + config.frame1GpuFenceDuration;
+ mPowerAdvisor->setGpuFenceTime(display1, std::make_unique<FenceTime>(end.ns()));
+ }
+
+ // increment the frame
+ std::this_thread::sleep_for(config.vsyncPeriod);
+ startTime = TimePoint::now();
+ fakeBasicFrameTiming(startTime, config.vsyncPeriod);
+ setExpectedTiming(config.vsyncPeriod, startTime + config.vsyncPeriod);
+
+ // report GPU
+ mPowerAdvisor->setRequiresRenderEngine(display1, config.frame2RequiresRenderEngine);
+ if (config.adpfGpuFlagOn) {
+ mPowerAdvisor->setGpuStartTime(display1, startTime);
+ }
+ if (config.frame2GpuFenceDuration.count() == Fence::SIGNAL_TIME_PENDING) {
+ mPowerAdvisor->setGpuFenceTime(display1,
+ std::make_unique<FenceTime>(Fence::SIGNAL_TIME_PENDING));
+ } else {
+ TimePoint end = startTime + config.frame2GpuFenceDuration;
+ mPowerAdvisor->setGpuFenceTime(display1, std::make_unique<FenceTime>(end.ns()));
+ }
+ mPowerAdvisor->setSfPresentTiming(startTime, startTime + config.presentDuration);
+ mPowerAdvisor->setCompositeEnd(startTime + config.presentDuration + config.postCompDuration);
+
+ // don't report timing for the HWC
+ mPowerAdvisor->setHwcValidateTiming(displayIds[0], startTime, startTime);
+ mPowerAdvisor->setHwcPresentTiming(displayIds[0], startTime, startTime);
+
+ std::vector<aidl::android::hardware::power::WorkDuration> durationReq;
+ EXPECT_CALL(*mMockPowerHintSession, reportActualWorkDuration(_))
+ .Times(1)
+ .WillOnce(DoAll(testing::SaveArg<0>(&durationReq),
+ testing::Return(testing::ByMove(HalResult<void>::ok()))));
+ mPowerAdvisor->reportActualWorkDuration();
+ EXPECT_EQ(durationReq.size(), 1u);
+ return durationReq[0];
+}
+
Duration PowerAdvisorTest::getFenceWaitDelayDuration(bool skipValidate) {
return (skipValidate ? PowerAdvisor::kFenceWaitStartDelaySkippedValidate
: PowerAdvisor::kFenceWaitStartDelayValidated);
@@ -390,5 +475,148 @@
mPowerAdvisor->startPowerHintSession({1, 2, 3});
}
+TEST_F(PowerAdvisorTest, setGpuFenceTime_cpuThenGpuFrames) {
+ GpuTestConfig config{
+ .adpfGpuFlagOn = false,
+ // faked buffer fence time for testing
+ .frame1GpuFenceDuration = 41ms,
+ .frame2GpuFenceDuration = 31ms,
+ .vsyncPeriod = 10ms,
+ .presentDuration = 2ms,
+ .postCompDuration = 8ms,
+ .frame1RequiresRenderEngine = false,
+ .frame2RequiresRenderEngine = true,
+ };
+ WorkDuration res = testGpuScenario(config);
+ EXPECT_EQ(res.gpuDurationNanos, 0L);
+ EXPECT_EQ(res.cpuDurationNanos, 0L);
+ EXPECT_GE(res.durationNanos, toNanos(30ms + getErrorMargin()));
+ EXPECT_LE(res.durationNanos, toNanos(31ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, setGpuFenceTime_cpuThenGpuFrames_flagOn) {
+ GpuTestConfig config{
+ .adpfGpuFlagOn = true,
+ .frame1GpuFenceDuration = 40ms,
+ .frame2GpuFenceDuration = 30ms,
+ .vsyncPeriod = 10ms,
+ .presentDuration = 2ms,
+ .postCompDuration = 8ms,
+ .frame1RequiresRenderEngine = false,
+ .frame2RequiresRenderEngine = true,
+ };
+ WorkDuration res = testGpuScenario(config);
+ EXPECT_EQ(res.gpuDurationNanos, toNanos(30ms));
+ EXPECT_EQ(res.cpuDurationNanos, toNanos(10ms));
+ EXPECT_EQ(res.durationNanos, toNanos(30ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, setGpuFenceTime_gpuThenCpuFrames) {
+ GpuTestConfig config{
+ .adpfGpuFlagOn = false,
+ // faked fence time for testing
+ .frame1GpuFenceDuration = 41ms,
+ .frame2GpuFenceDuration = 31ms,
+ .vsyncPeriod = 10ms,
+ .presentDuration = 2ms,
+ .postCompDuration = 8ms,
+ .frame1RequiresRenderEngine = true,
+ .frame2RequiresRenderEngine = false,
+ };
+ WorkDuration res = testGpuScenario(config);
+ EXPECT_EQ(res.gpuDurationNanos, 0L);
+ EXPECT_EQ(res.cpuDurationNanos, 0L);
+ EXPECT_EQ(res.durationNanos, toNanos(10ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, setGpuFenceTime_gpuThenCpuFrames_flagOn) {
+ GpuTestConfig config{
+ .adpfGpuFlagOn = true,
+ .frame1GpuFenceDuration = 40ms,
+ .frame2GpuFenceDuration = 30ms,
+ .vsyncPeriod = 10ms,
+ .presentDuration = 2ms,
+ .postCompDuration = 8ms,
+ .frame1RequiresRenderEngine = true,
+ .frame2RequiresRenderEngine = false,
+ };
+ WorkDuration res = testGpuScenario(config);
+ EXPECT_EQ(res.gpuDurationNanos, 0L);
+ EXPECT_EQ(res.cpuDurationNanos, toNanos(10ms));
+ EXPECT_EQ(res.durationNanos, toNanos(10ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, setGpuFenceTime_twoSignaledGpuFrames) {
+ GpuTestConfig config{
+ .adpfGpuFlagOn = false,
+ // added a margin as a workaround since we set GPU start time at the time of fence set
+ // call
+ .frame1GpuFenceDuration = 31ms,
+ .frame2GpuFenceDuration = 51ms,
+ .vsyncPeriod = 10ms,
+ .presentDuration = 2ms,
+ .postCompDuration = 8ms,
+ .frame1RequiresRenderEngine = true,
+ .frame2RequiresRenderEngine = true,
+ };
+ WorkDuration res = testGpuScenario(config);
+ EXPECT_EQ(res.gpuDurationNanos, 0L);
+ EXPECT_EQ(res.cpuDurationNanos, 0L);
+ EXPECT_GE(res.durationNanos, toNanos(50ms + getErrorMargin()));
+ EXPECT_LE(res.durationNanos, toNanos(51ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, setGpuFenceTime_twoSignaledGpuFenceFrames_flagOn) {
+ GpuTestConfig config{
+ .adpfGpuFlagOn = true,
+ .frame1GpuFenceDuration = 30ms,
+ .frame2GpuFenceDuration = 50ms,
+ .vsyncPeriod = 10ms,
+ .presentDuration = 2ms,
+ .postCompDuration = 8ms,
+ .frame1RequiresRenderEngine = true,
+ .frame2RequiresRenderEngine = true,
+ };
+ WorkDuration res = testGpuScenario(config);
+ EXPECT_EQ(res.gpuDurationNanos, toNanos(50ms));
+ EXPECT_EQ(res.cpuDurationNanos, toNanos(10ms));
+ EXPECT_EQ(res.durationNanos, toNanos(50ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, setGpuFenceTime_UnsingaledGpuFenceFrameUsingPreviousFrame) {
+ GpuTestConfig config{
+ .adpfGpuFlagOn = false,
+ .frame1GpuFenceDuration = 31ms,
+ .frame2GpuFenceDuration = Duration::fromNs(Fence::SIGNAL_TIME_PENDING),
+ .vsyncPeriod = 10ms,
+ .presentDuration = 2ms,
+ .postCompDuration = 8ms,
+ .frame1RequiresRenderEngine = true,
+ .frame2RequiresRenderEngine = true,
+ };
+ WorkDuration res = testGpuScenario(config);
+ EXPECT_EQ(res.gpuDurationNanos, 0L);
+ EXPECT_EQ(res.cpuDurationNanos, 0L);
+ EXPECT_GE(res.durationNanos, toNanos(30ms + getErrorMargin()));
+ EXPECT_LE(res.durationNanos, toNanos(31ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, setGpuFenceTime_UnsingaledGpuFenceFrameUsingPreviousFrame_flagOn) {
+ GpuTestConfig config{
+ .adpfGpuFlagOn = true,
+ .frame1GpuFenceDuration = 30ms,
+ .frame2GpuFenceDuration = Duration::fromNs(Fence::SIGNAL_TIME_PENDING),
+ .vsyncPeriod = 10ms,
+ .presentDuration = 22ms,
+ .postCompDuration = 88ms,
+ .frame1RequiresRenderEngine = true,
+ .frame2RequiresRenderEngine = true,
+ };
+ WorkDuration res = testGpuScenario(config);
+ EXPECT_EQ(res.gpuDurationNanos, toNanos(30ms));
+ EXPECT_EQ(res.cpuDurationNanos, toNanos(110ms));
+ EXPECT_EQ(res.durationNanos, toNanos(110ms + getErrorMargin()));
+}
+
} // namespace
} // namespace android::Hwc2::impl
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index fe0e3d1..0ede612 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -103,8 +103,9 @@
auto& mutableGetRankedRefreshRatesCache() { return mGetRankedFrameRatesCache; }
auto getRankedFrameRates(const std::vector<LayerRequirement>& layers,
- GlobalSignals signals = {}) const {
- const auto result = RefreshRateSelector::getRankedFrameRates(layers, signals);
+ GlobalSignals signals = {}, Fps pacesetterFps = {}) const {
+ const auto result =
+ RefreshRateSelector::getRankedFrameRates(layers, signals, pacesetterFps);
EXPECT_TRUE(std::is_sorted(result.ranking.begin(), result.ranking.end(),
ScoredFrameRate::DescendingScore{}));
@@ -114,8 +115,8 @@
auto getRankedRefreshRatesAsPair(const std::vector<LayerRequirement>& layers,
GlobalSignals signals) const {
- const auto [ranking, consideredSignals] = getRankedFrameRates(layers, signals);
- return std::make_pair(ranking, consideredSignals);
+ const auto result = getRankedFrameRates(layers, signals);
+ return std::make_pair(result.ranking, result.consideredSignals);
}
FrameRateMode getBestFrameRateMode(const std::vector<LayerRequirement>& layers = {},
@@ -295,6 +296,12 @@
<< "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);
}
}
};
@@ -1381,7 +1388,7 @@
TEST_P(RefreshRateSelectorTest, powerOnImminentConsidered) {
auto selector = createSelector(kModes_60_90, kModeId60);
- auto [refreshRates, signals] = selector.getRankedFrameRates({}, {});
+ auto [refreshRates, signals, _] = selector.getRankedFrameRates({}, {});
EXPECT_FALSE(signals.powerOnImminent);
auto expectedRefreshRates = []() -> std::vector<FrameRateMode> {
@@ -1465,10 +1472,32 @@
}
}
+TEST_P(RefreshRateSelectorTest, pacesetterConsidered) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
+ constexpr RefreshRateSelector::GlobalSignals kNoSignals;
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].vote = LayerVoteType::Min;
+
+ // The pacesetterFps takes precedence over the LayerRequirement.
+ {
+ const auto result = selector.getRankedFrameRates(layers, {}, 90_Hz);
+ EXPECT_EQ(kMode90, result.ranking.front().frameRateMode.modePtr);
+ EXPECT_EQ(kNoSignals, result.consideredSignals);
+ }
+
+ // The pacesetterFps takes precedence over GlobalSignals.
+ {
+ const auto result = selector.getRankedFrameRates(layers, {.touch = true}, 60_Hz);
+ EXPECT_EQ(kMode60, result.ranking.front().frameRateMode.modePtr);
+ EXPECT_EQ(kNoSignals, result.consideredSignals);
+ }
+}
+
TEST_P(RefreshRateSelectorTest, touchConsidered) {
auto selector = createSelector(kModes_60_90, kModeId60);
- auto [_, signals] = selector.getRankedFrameRates({}, {});
+ auto signals = selector.getRankedFrameRates({}, {}).consideredSignals;
EXPECT_FALSE(signals.touch);
std::tie(std::ignore, signals) = selector.getRankedRefreshRatesAsPair({}, {.touch = true});
@@ -1534,7 +1563,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},
@@ -1591,6 +1620,7 @@
// Expected result
Fps expectedFrameRate = 0_Hz;
+ DisplayModeId expectedModeId = kModeId90;
};
testFrameRateCategoryWithMultipleLayers(
@@ -1605,7 +1635,7 @@
testFrameRateCategoryWithMultipleLayers(
std::initializer_list<Case>{
- {0_Hz, FrameRateCategory::Normal, 60_Hz},
+ {0_Hz, FrameRateCategory::Normal, 60_Hz, kModeId60},
{0_Hz, FrameRateCategory::High, 90_Hz},
{0_Hz, FrameRateCategory::NoPreference, 90_Hz},
},
@@ -1614,18 +1644,18 @@
testFrameRateCategoryWithMultipleLayers(
std::initializer_list<Case>{
{30_Hz, FrameRateCategory::High, 90_Hz},
- {24_Hz, FrameRateCategory::High, 120_Hz},
- {12_Hz, FrameRateCategory::Normal, 120_Hz},
- {30_Hz, FrameRateCategory::NoPreference, 120_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},
- {30_Hz, FrameRateCategory::Default, 120_Hz},
- {120_Hz, FrameRateCategory::Default, 120_Hz},
+ {24_Hz, FrameRateCategory::Default, 120_Hz, kModeId120},
+ {30_Hz, FrameRateCategory::Default, 120_Hz, kModeId120},
+ {120_Hz, FrameRateCategory::Default, 120_Hz, kModeId120},
},
selector);
}
@@ -1640,6 +1670,7 @@
// Expected result
Fps expectedFrameRate = 0_Hz;
+ DisplayModeId expectedModeId = kModeId120;
};
testFrameRateCategoryWithMultipleLayers(std::initializer_list<
@@ -1970,6 +2001,122 @@
}
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,
getBestFrameRateMode_withFrameRateCategory_smoothSwitchOnly_60_120_nonVrr) {
if (GetParam() != Config::FrameRateOverride::Enabled) {
return;
@@ -1992,8 +2139,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},
@@ -2240,7 +2386,7 @@
lr.name = "60Hz ExplicitDefault";
lr.focused = true;
- const auto [rankedFrameRate, signals] =
+ const auto [rankedFrameRate, signals, _] =
selector.getRankedFrameRates(layers, {.touch = true, .idle = true});
EXPECT_EQ(rankedFrameRate.begin()->frameRateMode.modePtr, kMode60);
@@ -2464,7 +2610,7 @@
EXPECT_EQ(SetPolicyResult::Changed,
selector.setDisplayManagerPolicy({kModeId90, {k90, k90}, {k60_90, k60_90}}));
- const auto [ranking, signals] = selector.getRankedFrameRates({}, {});
+ const auto [ranking, signals, _] = selector.getRankedFrameRates({}, {});
EXPECT_EQ(ranking.front().frameRateMode.modePtr, kMode90);
EXPECT_FALSE(signals.touch);
@@ -2848,7 +2994,7 @@
layers[0].vote = voteType;
layers[0].desiredRefreshRate = 90_Hz;
- const auto [ranking, signals] =
+ const auto [ranking, signals, _] =
selector.getRankedFrameRates(layers, {.touch = touchActive, .idle = true});
// Refresh rate will be chosen by either touch state or idle state.
@@ -2998,16 +3144,17 @@
auto selector = createSelector(kModes_30_60_72_90_120, kModeId60);
using GlobalSignals = RefreshRateSelector::GlobalSignals;
- const auto args = std::make_pair(std::vector<LayerRequirement>{},
- GlobalSignals{.touch = true, .idle = true});
-
const RefreshRateSelector::RankedFrameRates result = {{RefreshRateSelector::ScoredFrameRate{
{90_Hz, kMode90}}},
GlobalSignals{.touch = true}};
- selector.mutableGetRankedRefreshRatesCache() = {args, result};
+ selector.mutableGetRankedRefreshRatesCache() = {.layers = std::vector<LayerRequirement>{},
+ .signals = GlobalSignals{.touch = true,
+ .idle = true},
+ .result = result};
- EXPECT_EQ(result, selector.getRankedFrameRates(args.first, args.second));
+ const auto& cache = *selector.mutableGetRankedRefreshRatesCache();
+ EXPECT_EQ(result, selector.getRankedFrameRates(cache.layers, cache.signals));
}
TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_WritesCache) {
@@ -3015,15 +3162,18 @@
EXPECT_FALSE(selector.mutableGetRankedRefreshRatesCache());
- std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
- RefreshRateSelector::GlobalSignals globalSignals{.touch = true, .idle = true};
+ const std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
+ const RefreshRateSelector::GlobalSignals globalSignals{.touch = true, .idle = true};
+ const Fps pacesetterFps = 60_Hz;
- const auto result = selector.getRankedFrameRates(layers, globalSignals);
+ const auto result = selector.getRankedFrameRates(layers, globalSignals, pacesetterFps);
const auto& cache = selector.mutableGetRankedRefreshRatesCache();
ASSERT_TRUE(cache);
- EXPECT_EQ(cache->arguments, std::make_pair(layers, globalSignals));
+ EXPECT_EQ(cache->layers, layers);
+ EXPECT_EQ(cache->signals, globalSignals);
+ EXPECT_EQ(cache->pacesetterFps, pacesetterFps);
EXPECT_EQ(cache->result, result);
}
@@ -3950,7 +4100,7 @@
layers[0].vote = voteType;
layers[0].desiredRefreshRate = 90_Hz;
- const auto [ranking, signals] =
+ const auto [ranking, signals, _] =
selector.getRankedFrameRates(layers, {.touch = touchActive, .idle = true});
// Refresh rate will be chosen by either touch state or idle state.
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index d4735c7..7479a4f 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -362,7 +362,7 @@
globalSignals)(kDisplayId2,
FrameRateMode{60_Hz,
kDisplay2Mode60},
- globalSignals);
+ GlobalSignals{});
std::vector<RefreshRateSelector::LayerRequirement> layers = {{.weight = 1.f},
{.weight = 1.f}};
@@ -381,7 +381,7 @@
globalSignals)(kDisplayId2,
FrameRateMode{120_Hz,
kDisplay2Mode120},
- globalSignals);
+ GlobalSignals{});
mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
@@ -400,7 +400,7 @@
globalSignals)(kDisplayId2,
FrameRateMode{120_Hz,
kDisplay2Mode120},
- globalSignals);
+ GlobalSignals{});
const auto actualChoices = mScheduler->chooseDisplayModes();
EXPECT_EQ(expectedChoices, actualChoices);
@@ -422,10 +422,10 @@
DisplayModeChoice>(kDisplayId1, FrameRateMode{120_Hz, kDisplay1Mode120},
globalSignals)(kDisplayId2,
FrameRateMode{120_Hz, kDisplay2Mode120},
- globalSignals)(kDisplayId3,
- FrameRateMode{60_Hz,
- kDisplay3Mode60},
- globalSignals);
+ GlobalSignals{})(kDisplayId3,
+ FrameRateMode{60_Hz,
+ kDisplay3Mode60},
+ GlobalSignals{});
const auto actualChoices = mScheduler->chooseDisplayModes();
EXPECT_EQ(expectedChoices, actualChoices);
@@ -440,12 +440,12 @@
expectedChoices = ftl::init::map<
const PhysicalDisplayId&,
DisplayModeChoice>(kDisplayId1, FrameRateMode{60_Hz, kDisplay1Mode60},
- globalSignals)(kDisplayId2,
- FrameRateMode{60_Hz, kDisplay2Mode60},
- globalSignals)(kDisplayId3,
- FrameRateMode{60_Hz,
- kDisplay3Mode60},
- globalSignals);
+ GlobalSignals{})(kDisplayId2,
+ FrameRateMode{60_Hz, kDisplay2Mode60},
+ GlobalSignals{})(kDisplayId3,
+ FrameRateMode{60_Hz,
+ kDisplay3Mode60},
+ globalSignals);
const auto actualChoices = mScheduler->chooseDisplayModes();
EXPECT_EQ(expectedChoices, actualChoices);
@@ -607,10 +607,10 @@
EXPECT_EQ(Fps::fromPeriodNsecs(2000),
scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
- TimePoint::fromNs(4500)));
+ TimePoint::fromNs(5500)));
EXPECT_EQ(Fps::fromPeriodNsecs(2000),
scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
- TimePoint::fromNs(6500)));
+ TimePoint::fromNs(7500)));
}
TEST_F(SchedulerTest, resyncAllToHardwareVsync) FTL_FAKE_GUARD(kMainThreadContext) {
diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
index 48707cb..eafba0a 100644
--- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
@@ -704,14 +704,17 @@
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));
+ EXPECT_EQ(5500, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000));
+ EXPECT_EQ(6500, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
+ EXPECT_EQ(7500, vrrTracker.nextAnticipatedVSyncTimeFrom(6000, 6000));
+ EXPECT_EQ(9500, vrrTracker.nextAnticipatedVSyncTimeFrom(8000, 8000));
+ EXPECT_EQ(11500, vrrTracker.nextAnticipatedVSyncTimeFrom(10000, 10000));
+ EXPECT_EQ(13500, vrrTracker.nextAnticipatedVSyncTimeFrom(12000, 12000));
+ EXPECT_EQ(16500, vrrTracker.nextAnticipatedVSyncTimeFrom(15500, 15500));
+ EXPECT_EQ(20500, vrrTracker.nextAnticipatedVSyncTimeFrom(19000, 19000));
+
+ // matches the previous cadence
+ EXPECT_EQ(21500, vrrTracker.nextAnticipatedVSyncTimeFrom(20500, 20500));
}
TEST_F(VSyncPredictorTest, minFramePeriodDoesntApplyWhenSameWithRefreshRate) {
@@ -820,7 +823,7 @@
vrrTracker.setRenderRate(Fps::fromPeriodNsecs(1000), /*applyImmediately*/ false);
vrrTracker.addVsyncTimestamp(0);
- EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1234, 1234));
+ EXPECT_EQ(2500, vrrTracker.nextAnticipatedVSyncTimeFrom(1234, 1234));
}
TEST_F(VSyncPredictorTest, adjustsVrrTimeline) {
@@ -944,6 +947,27 @@
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/DisplayHardware/MockPowerAdvisor.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h
index 8e8eb1d..e8630ba 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h
@@ -40,6 +40,7 @@
MOCK_METHOD(void, reportActualWorkDuration, (), (override));
MOCK_METHOD(void, enablePowerHintSession, (bool enabled), (override));
MOCK_METHOD(bool, startPowerHintSession, (std::vector<int32_t> && threadIds), (override));
+ MOCK_METHOD(void, setGpuStartTime, (DisplayId displayId, TimePoint startTime), (override));
MOCK_METHOD(void, setGpuFenceTime,
(DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime), (override));
MOCK_METHOD(void, setHwcValidateTiming,
@@ -49,8 +50,8 @@
(DisplayId displayId, TimePoint presentStartTime, TimePoint presentEndTime),
(override));
MOCK_METHOD(void, setSkippedValidate, (DisplayId displayId, bool skipped), (override));
- MOCK_METHOD(void, setRequiresClientComposition,
- (DisplayId displayId, bool requiresClientComposition), (override));
+ MOCK_METHOD(void, setRequiresRenderEngine, (DisplayId displayId, bool requiresRenderEngine),
+ (override));
MOCK_METHOD(void, setExpectedPresentTime, (TimePoint expectedPresentTime), (override));
MOCK_METHOD(void, setSfPresentTiming, (TimePoint presentFenceTime, TimePoint presentEndTime),
(override));
diff --git a/services/surfaceflinger/tests/utils/ColorUtils.h b/services/surfaceflinger/tests/utils/ColorUtils.h
index 38c422a..07916b6 100644
--- a/services/surfaceflinger/tests/utils/ColorUtils.h
+++ b/services/surfaceflinger/tests/utils/ColorUtils.h
@@ -33,10 +33,6 @@
static const Color WHITE;
static const Color BLACK;
static const Color TRANSPARENT;
-
- half3 toHalf3() { return half3{r / 255.0f, g / 255.0f, b / 255.0f}; }
-
- half4 toHalf4() { return half4{r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f}; }
};
const Color Color::RED{255, 0, 0, 255};
@@ -85,14 +81,6 @@
}
color = ret;
}
-
- static half3 toHalf3(const Color& color) {
- return half3{color.r / 255.0f, color.g / 255.0f, color.b / 255.0f};
- }
-
- static half4 toHalf4(const Color& color) {
- return half4{color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f};
- }
};
} // namespace
} // namespace android
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) "