Merge "Update keymint latest aidl for cpp version code"
diff --git a/audio/aidl/Android.bp b/audio/aidl/Android.bp
index 92d7d54..563ee62 100644
--- a/audio/aidl/Android.bp
+++ b/audio/aidl/Android.bp
@@ -113,6 +113,7 @@
"android/hardware/audio/core/AudioRoute.aidl",
"android/hardware/audio/core/IConfig.aidl",
"android/hardware/audio/core/IModule.aidl",
+ "android/hardware/audio/core/IStreamCallback.aidl",
"android/hardware/audio/core/IStreamIn.aidl",
"android/hardware/audio/core/IStreamOut.aidl",
"android/hardware/audio/core/ITelephony.aidl",
diff --git a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IModule.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IModule.aidl
index be382c5..7f960e0 100644
--- a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IModule.aidl
+++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IModule.aidl
@@ -76,6 +76,7 @@
android.hardware.audio.common.SourceMetadata sourceMetadata;
@nullable android.media.audio.common.AudioOffloadInfo offloadInfo;
long bufferSizeFrames;
+ @nullable android.hardware.audio.core.IStreamCallback callback;
}
@VintfStability
parcelable OpenOutputStreamReturn {
diff --git a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IStreamCallback.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IStreamCallback.aidl
new file mode 100644
index 0000000..5a2ab78
--- /dev/null
+++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IStreamCallback.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.audio.core;
+@VintfStability
+interface IStreamCallback {
+ oneway void onTransferReady();
+ oneway void onError();
+ oneway void onDrainReady();
+}
diff --git a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/ModuleDebug.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/ModuleDebug.aidl
index 80ee185..467d37b 100644
--- a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/ModuleDebug.aidl
+++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/ModuleDebug.aidl
@@ -35,4 +35,5 @@
@JavaDerive(equals=true, toString=true) @VintfStability
parcelable ModuleDebug {
boolean simulateDeviceConnections;
+ int streamTransientStateDelayMs;
}
diff --git a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/StreamDescriptor.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/StreamDescriptor.aidl
index 3a77ad1..3a4271b 100644
--- a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/StreamDescriptor.aidl
+++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/StreamDescriptor.aidl
@@ -42,8 +42,9 @@
const int LATENCY_UNKNOWN = -1;
@FixedSize @VintfStability
parcelable Position {
- long frames;
- long timeNs;
+ long frames = -1;
+ long timeNs = -1;
+ const long UNKNOWN = -1;
}
@Backing(type="int") @VintfStability
enum State {
@@ -53,14 +54,23 @@
PAUSED = 4,
DRAINING = 5,
DRAIN_PAUSED = 6,
+ TRANSFERRING = 7,
+ TRANSFER_PAUSED = 8,
ERROR = 100,
}
+ @Backing(type="byte") @VintfStability
+ enum DrainMode {
+ DRAIN_UNSPECIFIED = 0,
+ DRAIN_ALL = 1,
+ DRAIN_EARLY_NOTIFY = 2,
+ }
@FixedSize @VintfStability
union Command {
- int hal_reserved_exit;
+ int halReservedExit;
+ android.media.audio.common.Void getStatus;
android.media.audio.common.Void start;
int burst;
- android.media.audio.common.Void drain;
+ android.hardware.audio.core.StreamDescriptor.DrainMode drain;
android.media.audio.common.Void standby;
android.media.audio.common.Void pause;
android.media.audio.common.Void flush;
diff --git a/audio/aidl/android/hardware/audio/core/IModule.aidl b/audio/aidl/android/hardware/audio/core/IModule.aidl
index be40051..974e7e8 100644
--- a/audio/aidl/android/hardware/audio/core/IModule.aidl
+++ b/audio/aidl/android/hardware/audio/core/IModule.aidl
@@ -21,6 +21,7 @@
import android.hardware.audio.core.AudioMode;
import android.hardware.audio.core.AudioPatch;
import android.hardware.audio.core.AudioRoute;
+import android.hardware.audio.core.IStreamCallback;
import android.hardware.audio.core.IStreamIn;
import android.hardware.audio.core.IStreamOut;
import android.hardware.audio.core.ITelephony;
@@ -53,9 +54,13 @@
* the HAL module behavior that would otherwise require human intervention.
*
* The HAL module must throw an error if there is an attempt to change
- * the debug behavior for the aspect which is currently in use.
+ * the debug behavior for the aspect which is currently in use, or when
+ * the value of any of the debug flags is invalid. See 'ModuleDebug' for
+ * the full list of constraints.
*
* @param debug The debug options.
+ * @throws EX_ILLEGAL_ARGUMENT If some of the configuration parameters are
+ * invalid.
* @throws EX_ILLEGAL_STATE If the flag(s) being changed affect functionality
* which is currently in use.
*/
@@ -316,9 +321,13 @@
* 'setAudioPortConfig' method. Existence of an audio patch involving this
* port configuration is not required for successful opening of a stream.
*
- * If the port configuration has 'COMPRESS_OFFLOAD' output flag set,
- * the framework must provide additional information about the encoded
- * audio stream in 'offloadInfo' argument.
+ * If the port configuration has the 'COMPRESS_OFFLOAD' output flag set,
+ * the client must provide additional information about the encoded
+ * audio stream in the 'offloadInfo' argument.
+ *
+ * If the port configuration has the 'NON_BLOCKING' output flag set,
+ * the client must provide a callback for asynchronous notifications
+ * in the 'callback' argument.
*
* The requested buffer size is expressed in frames, thus the actual size
* in bytes depends on the audio port configuration. Also, the HAL module
@@ -354,6 +363,8 @@
* - If the offload info is not provided for an offload
* port configuration.
* - If a buffer of the requested size can not be provided.
+ * - If the callback is not provided for a non-blocking
+ * port configuration.
* @throws EX_ILLEGAL_STATE In the following cases:
* - If the port config already has a stream opened on it.
* - If the limit on the open stream count for the port has
@@ -372,6 +383,8 @@
@nullable AudioOffloadInfo offloadInfo;
/** Requested audio I/O buffer minimum size, in frames. */
long bufferSizeFrames;
+ /** Client callback interface for the non-blocking output mode. */
+ @nullable IStreamCallback callback;
}
@VintfStability
parcelable OpenOutputStreamReturn {
diff --git a/audio/aidl/android/hardware/audio/core/IStreamCallback.aidl b/audio/aidl/android/hardware/audio/core/IStreamCallback.aidl
new file mode 100644
index 0000000..440ab25
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/core/IStreamCallback.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+package android.hardware.audio.core;
+
+/**
+ * This interface is used to indicate completion of asynchronous operations.
+ * See the state machines referenced by StreamDescriptor for details.
+ */
+@VintfStability
+oneway interface IStreamCallback {
+ /**
+ * Indicate that the stream is ready for next data exchange.
+ */
+ void onTransferReady();
+ /**
+ * Indicate that an irrecoverable error has occurred during the last I/O
+ * operation. After sending this callback, the stream enters the 'ERROR'
+ * state.
+ */
+ void onError();
+ /**
+ * Indicate that the stream has finished draining. This is only used
+ * for output streams because for input streams draining is performed
+ * by the client.
+ */
+ void onDrainReady();
+}
diff --git a/audio/aidl/android/hardware/audio/core/ModuleDebug.aidl b/audio/aidl/android/hardware/audio/core/ModuleDebug.aidl
index 858a9bd..871a5c9 100644
--- a/audio/aidl/android/hardware/audio/core/ModuleDebug.aidl
+++ b/audio/aidl/android/hardware/audio/core/ModuleDebug.aidl
@@ -35,4 +35,19 @@
* profiles.
*/
boolean simulateDeviceConnections;
+ /**
+ * Must be non-negative. When set to non-zero, HAL module must delay
+ * transition from "transient" stream states (see StreamDescriptor.aidl)
+ * by the specified amount of milliseconds. The purpose of this delay
+ * is to allow VTS to test sending of stream commands while the stream is
+ * in a transient state. The delay must apply to newly created streams,
+ * it is not required to apply the delay to already opened streams.
+ *
+ * Note: the drawback of enabling this delay for asynchronous (non-blocking)
+ * modes is that sending of callbacks will also be delayed, because
+ * callbacks are sent once the stream state machine exits a transient
+ * state. Thus, it's not recommended to use it with tests that require
+ * waiting for an async callback.
+ */
+ int streamTransientStateDelayMs;
}
diff --git a/audio/aidl/android/hardware/audio/core/StreamDescriptor.aidl b/audio/aidl/android/hardware/audio/core/StreamDescriptor.aidl
index 2b1fc99..65ea9ef 100644
--- a/audio/aidl/android/hardware/audio/core/StreamDescriptor.aidl
+++ b/audio/aidl/android/hardware/audio/core/StreamDescriptor.aidl
@@ -84,13 +84,13 @@
* are different.
*
* State machines of both input and output streams start from the 'STANDBY'
- * state. Transitions between states happen naturally with changes in the
+ * state. Transitions between states happen naturally with changes in the
* states of the model elements. For simplicity, we restrict the change to one
* element only, for example, in the 'STANDBY' state, either the producer or the
* consumer can become active, but not both at the same time. States 'STANDBY',
* 'IDLE', 'READY', and '*PAUSED' are "stable"—they require an external event,
* whereas a change from the 'DRAINING' state can happen with time as the buffer
- * gets empty.
+ * gets empty, thus it's a "transient" state.
*
* The state machine for input streams is defined in the `stream-in-sm.gv` file,
* for output streams—in the `stream-out-sm.gv` file. State machines define how
@@ -100,6 +100,28 @@
* client can never observe a stream with a functioning command queue in this
* state. The 'ERROR' state is a special state which the state machine enters
* when an unrecoverable hardware error is detected by the HAL module.
+ *
+ * Non-blocking (asynchronous) modes introduce a new 'TRANSFERRING' state, which
+ * the state machine can enter after replying to the 'burst' command, instead of
+ * staying in the 'ACTIVE' state. In this case the client gets unblocked
+ * earlier, while the actual audio delivery to / from the observer is not
+ * complete yet. Once the HAL module is ready for the next transfer, it notifies
+ * the client via a oneway callback, and the machine switches to 'ACTIVE'
+ * state. The 'TRANSFERRING' state is thus "transient", similar to the
+ * 'DRAINING' state. For output streams, asynchronous transfer can be paused,
+ * and it's another new state: 'TRANSFER_PAUSED'. It differs from 'PAUSED' by
+ * the fact that no new writes are allowed. Please see 'stream-in-async-sm.gv'
+ * and 'stream-out-async-sm.gv' files for details. Below is the table summary
+ * for asynchronous only-states:
+ *
+ * Producer | Buffer state | Consumer | Applies | State
+ * active? | | active? | to |
+ * ==========|==============|==========|=========|==============================
+ * Yes | Not empty | Yes | Both | TRANSFERRING, s/w x-runs counted
+ * ----------|--------------|----------|---------|-----------------------------
+ * Yes | Not empty | No | Output | TRANSFER_PAUSED,
+ * | | | | h/w emits silence.
+ *
*/
@JavaDerive(equals=true, toString=true)
@VintfStability
@@ -116,10 +138,15 @@
@VintfStability
@FixedSize
parcelable Position {
+ /**
+ * The value used when the position can not be reported by the HAL
+ * module.
+ */
+ const long UNKNOWN = -1;
/** Frame count. */
- long frames;
+ long frames = UNKNOWN;
/** Timestamp in nanoseconds. */
- long timeNs;
+ long timeNs = UNKNOWN;
}
@VintfStability
@@ -166,11 +193,24 @@
/**
* Used for output streams only, pauses draining. This state is similar
* to the 'PAUSED' state, except that the client is not adding any
- * new data. If it emits a 'BURST' command, this brings the stream
+ * new data. If it emits a 'burst' command, this brings the stream
* into the regular 'PAUSED' state.
*/
DRAIN_PAUSED = 6,
/**
+ * Used only for streams in asynchronous mode. The stream enters this
+ * state after receiving a 'burst' command and returning control back
+ * to the client, thus unblocking it.
+ */
+ TRANSFERRING = 7,
+ /**
+ * Used only for output streams in asynchronous mode only. The stream
+ * enters this state after receiving a 'pause' command while being in
+ * the 'TRANSFERRING' state. Unlike 'PAUSED' state, this state does not
+ * accept new writes.
+ */
+ TRANSFER_PAUSED = 8,
+ /**
* The ERROR state is entered when the stream has encountered an
* irrecoverable error from the lower layer. After entering it, the
* stream can only be closed.
@@ -178,6 +218,29 @@
ERROR = 100,
}
+ @VintfStability
+ @Backing(type="byte")
+ enum DrainMode {
+ /**
+ * Unspecified—used with input streams only, because the client controls
+ * draining.
+ */
+ DRAIN_UNSPECIFIED = 0,
+ /**
+ * Used with output streams only, the HAL module indicates drain
+ * completion when all remaining audio data has been consumed.
+ */
+ DRAIN_ALL = 1,
+ /**
+ * Used with output streams only, the HAL module indicates drain
+ * completion shortly before all audio data has been consumed in order
+ * to give the client an opportunity to provide data for the next track
+ * for gapless playback. The exact amount of provided time is specific
+ * to the HAL implementation.
+ */
+ DRAIN_EARLY_NOTIFY = 2,
+ }
+
/**
* Used for sending commands to the HAL module. The client writes into
* the queue, the HAL module reads. The queue can only contain a single
@@ -198,7 +261,14 @@
* implementation must pass a random cookie as the command argument,
* which is only known to the implementation.
*/
- int hal_reserved_exit;
+ int halReservedExit;
+ /**
+ * Retrieve the current state of the stream. This command must be
+ * processed by the stream in any state. The stream must provide current
+ * positions, counters, and its state in the reply. This command must be
+ * handled by the HAL module without any observable side effects.
+ */
+ Void getStatus;
/**
* See the state machines on the applicability of this command to
* different states.
@@ -215,15 +285,14 @@
* read from the hardware into the 'audio.fmq' queue.
*
* In both cases it is allowed for this field to contain any
- * non-negative number. The value 0 can be used if the client only needs
- * to retrieve current positions and latency. Any sufficiently big value
- * which exceeds the size of the queue's area which is currently
- * available for reading or writing by the HAL module must be trimmed by
- * the HAL module to the available size. Note that the HAL module is
- * allowed to consume or provide less data than requested, and it must
- * return the amount of actually read or written data via the
- * 'Reply.fmqByteCount' field. Thus, only attempts to pass a negative
- * number must be constituted as a client's error.
+ * non-negative number. Any sufficiently big value which exceeds the
+ * size of the queue's area which is currently available for reading or
+ * writing by the HAL module must be trimmed by the HAL module to the
+ * available size. Note that the HAL module is allowed to consume or
+ * provide less data than requested, and it must return the amount of
+ * actually read or written data via the 'Reply.fmqByteCount'
+ * field. Thus, only attempts to pass a negative number must be
+ * constituted as a client's error.
*
* Differences for the MMap No IRQ mode:
*
@@ -233,13 +302,16 @@
* with sending of this command.
*
* - the value must always be set to 0.
+ *
+ * See the state machines on the applicability of this command to
+ * different states.
*/
int burst;
/**
* See the state machines on the applicability of this command to
* different states.
*/
- Void drain;
+ DrainMode drain;
/**
* See the state machines on the applicability of this command to
* different states.
@@ -286,10 +358,6 @@
* - STATUS_INVALID_OPERATION: the command is not applicable in the
* current state of the stream, or to this
* type of the stream;
- * - STATUS_NO_INIT: positions can not be reported because the mix port
- * is not connected to any producer or consumer, or
- * because the HAL module does not support positions
- * reporting for this AudioSource (on input streams).
* - STATUS_NOT_ENOUGH_DATA: a read or write error has
* occurred for the 'audio.fmq' queue;
*/
@@ -307,9 +375,11 @@
*/
int fmqByteCount;
/**
- * It is recommended to report the current position for any command.
- * If the position can not be reported, the 'status' field must be
- * set to 'NO_INIT'.
+ * It is recommended to report the current position for any command. If
+ * the position can not be reported, for example because the mix port is
+ * not connected to any producer or consumer, or because the HAL module
+ * does not support positions reporting for this AudioSource (on input
+ * streams), the 'Position::UNKNOWN' value must be used.
*
* For output streams: the moment when the specified stream position
* was presented to an external observer (i.e. presentation position).
@@ -401,6 +471,10 @@
* into 'reply' queue, and hangs on waiting on a read from
* the 'command' queue.
* 6. The client wakes up due to 5. and reads the reply.
+ * Note: in non-blocking mode, when the HAL module goes to
+ * the 'TRANSFERRING' state (as indicated by the 'reply.state'
+ * field), the client must wait for the 'IStreamCallback.onTransferReady'
+ * notification to arrive before starting the next burst.
*
* For input streams the following sequence of operations is used:
* 1. The client writes the BURST command into the 'command' queue,
@@ -415,6 +489,10 @@
* 5. The client wakes up due to 4.
* 6. The client reads the reply and audio data. The client must
* always read from the FMQ all the data it contains.
+ * Note: in non-blocking mode, when the HAL module goes to
+ * the 'TRANSFERRING' state (as indicated by the 'reply.state'
+ * field) the client must wait for the 'IStreamCallback.onTransferReady'
+ * notification to arrive before starting the next burst.
*
*/
MQDescriptor<byte, SynchronizedReadWrite> fmq;
diff --git a/audio/aidl/android/hardware/audio/core/stream-in-async-sm.gv b/audio/aidl/android/hardware/audio/core/stream-in-async-sm.gv
new file mode 100644
index 0000000..818b18e
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/core/stream-in-async-sm.gv
@@ -0,0 +1,47 @@
+// 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.
+
+// To render: dot -Tpng stream-in-async-sm.gv -o stream-in-async-sm.png
+digraph stream_in_async_state_machine {
+ node [shape=doublecircle style=filled fillcolor=black width=0.5] I;
+ node [shape=point width=0.5] F;
+ node [shape=oval width=1];
+ node [fillcolor=lightgreen] STANDBY; // buffer is empty
+ node [fillcolor=tomato] CLOSED;
+ node [fillcolor=tomato] ERROR;
+ node [style=dashed] ANY_STATE;
+ node [fillcolor=lightblue style=filled];
+ // Note that when the producer (h/w) is passive, "burst" operations
+ // complete synchronously, bypassing the TRANSFERRING state.
+ I -> STANDBY;
+ STANDBY -> IDLE [label="start"]; // producer -> active
+ IDLE -> STANDBY [label="standby"]; // producer -> passive, buffer is cleared
+ IDLE -> TRANSFERRING [label="burst"]; // consumer -> active
+ ACTIVE -> PAUSED [label="pause"]; // consumer -> passive
+ ACTIVE -> DRAINING [label="drain"]; // producer -> passive
+ ACTIVE -> TRANSFERRING [label="burst"];
+ TRANSFERRING -> ACTIVE [label="←IStreamCallback.onTransferReady"];
+ TRANSFERRING -> PAUSED [label="pause"]; // consumer -> passive
+ TRANSFERRING -> DRAINING [label="drain"]; // producer -> passive
+ PAUSED -> TRANSFERRING [label="burst"]; // consumer -> active
+ PAUSED -> STANDBY [label="flush"]; // producer -> passive, buffer is cleared
+ DRAINING -> DRAINING [label="burst"];
+ DRAINING -> ACTIVE [label="start"]; // producer -> active
+ DRAINING -> STANDBY [label="<empty buffer>"]; // consumer deactivates
+ IDLE -> ERROR [label="←IStreamCallback.onError"];
+ PAUSED -> ERROR [label="←IStreamCallback.onError"];
+ TRANSFERRING -> ERROR [label="←IStreamCallback.onError"];
+ ANY_STATE -> CLOSED [label="→IStream*.close"];
+ CLOSED -> F;
+}
diff --git a/audio/aidl/android/hardware/audio/core/stream-out-async-sm.gv b/audio/aidl/android/hardware/audio/core/stream-out-async-sm.gv
new file mode 100644
index 0000000..e25b15a
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/core/stream-out-async-sm.gv
@@ -0,0 +1,57 @@
+// 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.
+
+// To render: dot -Tpng stream-out-async-sm.gv -o stream-out-async-sm.png
+digraph stream_out_async_state_machine {
+ node [shape=doublecircle style=filled fillcolor=black width=0.5] I;
+ node [shape=point width=0.5] F;
+ node [shape=oval width=1];
+ node [fillcolor=lightgreen] STANDBY; // buffer is empty
+ node [fillcolor=lightgreen] IDLE; // buffer is empty
+ node [fillcolor=tomato] CLOSED;
+ node [fillcolor=tomato] ERROR;
+ node [style=dashed] ANY_STATE;
+ node [fillcolor=lightblue style=filled];
+ // Note that when the consumer (h/w) is passive, "burst" operations
+ // complete synchronously, bypassing the TRANSFERRING state.
+ I -> STANDBY;
+ STANDBY -> IDLE [label="start"]; // consumer -> active
+ STANDBY -> PAUSED [label="burst"]; // producer -> active
+ IDLE -> STANDBY [label="standby"]; // consumer -> passive
+ IDLE -> TRANSFERRING [label="burst"]; // producer -> active
+ ACTIVE -> PAUSED [label="pause"]; // consumer -> passive (not consuming)
+ ACTIVE -> DRAINING [label="drain"]; // producer -> passive
+ ACTIVE -> TRANSFERRING [label="burst"]; // early unblocking
+ ACTIVE -> ACTIVE [label="burst"]; // full write
+ TRANSFERRING -> ACTIVE [label="←IStreamCallback.onTransferReady"];
+ TRANSFERRING -> TRANSFER_PAUSED [label="pause"]; // consumer -> passive (not consuming)
+ TRANSFERRING -> DRAINING [label="drain"]; // producer -> passive
+ TRANSFER_PAUSED -> TRANSFERRING [label="start"]; // consumer -> active
+ TRANSFER_PAUSED -> DRAIN_PAUSED [label="drain"]; // producer -> passive
+ TRANSFER_PAUSED -> IDLE [label="flush"]; // buffer is cleared
+ PAUSED -> PAUSED [label="burst"];
+ PAUSED -> ACTIVE [label="start"]; // consumer -> active
+ PAUSED -> IDLE [label="flush"]; // producer -> passive, buffer is cleared
+ DRAINING -> IDLE [label="←IStreamCallback.onDrainReady"];
+ DRAINING -> TRANSFERRING [label="burst"]; // producer -> active
+ DRAINING -> DRAIN_PAUSED [label="pause"]; // consumer -> passive (not consuming)
+ DRAIN_PAUSED -> DRAINING [label="start"]; // consumer -> active
+ DRAIN_PAUSED -> TRANSFER_PAUSED [label="burst"]; // producer -> active
+ DRAIN_PAUSED -> IDLE [label="flush"]; // buffer is cleared
+ IDLE -> ERROR [label="←IStreamCallback.onError"];
+ DRAINING -> ERROR [label="←IStreamCallback.onError"];
+ TRANSFERRING -> ERROR [label="←IStreamCallback.onError"];
+ ANY_STATE -> CLOSED [label="→IStream*.close"];
+ CLOSED -> F;
+}
diff --git a/audio/aidl/common/StreamWorker.cpp b/audio/aidl/common/StreamWorker.cpp
index dda0e4a..0d2121c 100644
--- a/audio/aidl/common/StreamWorker.cpp
+++ b/audio/aidl/common/StreamWorker.cpp
@@ -25,15 +25,20 @@
bool ThreadController::start(const std::string& name, int priority) {
mThreadName = name;
mThreadPriority = priority;
- mWorker = std::thread(&ThreadController::workerThread, this);
+ if (kTestSingleThread != name) {
+ mWorker = std::thread(&ThreadController::workerThread, this);
+ } else {
+ // Simulate the case when the workerThread completes prior
+ // to the moment when we being waiting for its start.
+ workerThread();
+ }
std::unique_lock<std::mutex> lock(mWorkerLock);
android::base::ScopedLockAssertion lock_assertion(mWorkerLock);
mWorkerCv.wait(lock, [&]() {
android::base::ScopedLockAssertion lock_assertion(mWorkerLock);
- return mWorkerState == WorkerState::RUNNING || !mError.empty();
+ return mWorkerState != WorkerState::INITIAL || !mError.empty();
});
- mWorkerStateChangeRequest = false;
- return mWorkerState == WorkerState::RUNNING;
+ return mError.empty();
}
void ThreadController::stop() {
@@ -81,8 +86,8 @@
void ThreadController::workerThread() {
using Status = StreamLogic::Status;
- std::string error = mLogic->init();
- if (error.empty() && !mThreadName.empty()) {
+ std::string error;
+ if (!mThreadName.empty()) {
std::string compliantName(mThreadName.substr(0, 15));
if (int errCode = pthread_setname_np(pthread_self(), compliantName.c_str()); errCode != 0) {
error.append("Failed to set thread name: ").append(strerror(errCode));
@@ -94,6 +99,9 @@
error.append("Failed to set thread priority: ").append(strerror(errCode));
}
}
+ if (error.empty()) {
+ error.append(mLogic->init());
+ }
{
std::lock_guard<std::mutex> lock(mWorkerLock);
mWorkerState = error.empty() ? WorkerState::RUNNING : WorkerState::STOPPED;
diff --git a/audio/aidl/common/include/StreamWorker.h b/audio/aidl/common/include/StreamWorker.h
index ab2ec26..e9c1070 100644
--- a/audio/aidl/common/include/StreamWorker.h
+++ b/audio/aidl/common/include/StreamWorker.h
@@ -32,7 +32,7 @@
namespace internal {
class ThreadController {
- enum class WorkerState { STOPPED, RUNNING, PAUSE_REQUESTED, PAUSED, RESUME_REQUESTED };
+ enum class WorkerState { INITIAL, STOPPED, RUNNING, PAUSE_REQUESTED, PAUSED, RESUME_REQUESTED };
public:
explicit ThreadController(StreamLogic* logic) : mLogic(logic) {}
@@ -76,7 +76,7 @@
std::thread mWorker;
std::mutex mWorkerLock;
std::condition_variable mWorkerCv;
- WorkerState mWorkerState GUARDED_BY(mWorkerLock) = WorkerState::STOPPED;
+ WorkerState mWorkerState GUARDED_BY(mWorkerLock) = WorkerState::INITIAL;
std::string mError GUARDED_BY(mWorkerLock);
// The atomic lock-free variable is used to prevent priority inversions
// that can occur when a high priority worker tries to acquire the lock
@@ -90,6 +90,9 @@
std::atomic<bool> mWorkerStateChangeRequest GUARDED_BY(mWorkerLock) = false;
};
+// A special thread name used in tests only.
+static const std::string kTestSingleThread = "__testST__";
+
} // namespace internal
class StreamLogic {
diff --git a/audio/aidl/common/tests/streamworker_tests.cpp b/audio/aidl/common/tests/streamworker_tests.cpp
index 8ea8424..f7a30b9 100644
--- a/audio/aidl/common/tests/streamworker_tests.cpp
+++ b/audio/aidl/common/tests/streamworker_tests.cpp
@@ -283,4 +283,16 @@
EXPECT_EQ(priority, worker.getPriority());
}
+TEST_P(StreamWorkerTest, DeferredStartCheckNoError) {
+ stream.setStopStatus();
+ EXPECT_TRUE(worker.start(android::hardware::audio::common::internal::kTestSingleThread));
+ EXPECT_FALSE(worker.hasError());
+}
+
+TEST_P(StreamWorkerTest, DeferredStartCheckWithError) {
+ stream.setErrorStatus();
+ EXPECT_FALSE(worker.start(android::hardware::audio::common::internal::kTestSingleThread));
+ EXPECT_TRUE(worker.hasError());
+}
+
INSTANTIATE_TEST_SUITE_P(StreamWorker, StreamWorkerTest, testing::Bool());
diff --git a/audio/aidl/default/Module.cpp b/audio/aidl/default/Module.cpp
index 6863fe3..9dbd61c 100644
--- a/audio/aidl/default/Module.cpp
+++ b/audio/aidl/default/Module.cpp
@@ -97,6 +97,7 @@
}
ndk::ScopedAStatus Module::createStreamContext(int32_t in_portConfigId, int64_t in_bufferSizeFrames,
+ std::shared_ptr<IStreamCallback> asyncCallback,
StreamContext* out_context) {
if (in_bufferSizeFrames <= 0) {
LOG(ERROR) << __func__ << ": non-positive buffer size " << in_bufferSizeFrames;
@@ -135,8 +136,8 @@
StreamContext temp(
std::make_unique<StreamContext::CommandMQ>(1, true /*configureEventFlagWord*/),
std::make_unique<StreamContext::ReplyMQ>(1, true /*configureEventFlagWord*/),
- frameSize,
- std::make_unique<StreamContext::DataMQ>(frameSize * in_bufferSizeFrames));
+ frameSize, std::make_unique<StreamContext::DataMQ>(frameSize * in_bufferSizeFrames),
+ asyncCallback, mDebug.streamTransientStateDelayMs);
if (temp.isValid()) {
*out_context = std::move(temp);
} else {
@@ -242,6 +243,11 @@
<< "while having external devices connected";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
+ if (in_debug.streamTransientStateDelayMs < 0) {
+ LOG(ERROR) << __func__ << ": streamTransientStateDelayMs is negative: "
+ << in_debug.streamTransientStateDelayMs;
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
mDebug = in_debug;
return ndk::ScopedAStatus::ok();
}
@@ -456,7 +462,8 @@
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
StreamContext context;
- if (auto status = createStreamContext(in_args.portConfigId, in_args.bufferSizeFrames, &context);
+ if (auto status = createStreamContext(in_args.portConfigId, in_args.bufferSizeFrames, nullptr,
+ &context);
!status.isOk()) {
return status;
}
@@ -496,8 +503,16 @@
<< " has COMPRESS_OFFLOAD flag set, requires offload info";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
+ const bool isNonBlocking = isBitPositionFlagSet(port->flags.get<AudioIoFlags::Tag::output>(),
+ AudioOutputFlags::NON_BLOCKING);
+ if (isNonBlocking && in_args.callback == nullptr) {
+ LOG(ERROR) << __func__ << ": port id " << port->id
+ << " has NON_BLOCKING flag set, requires async callback";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
StreamContext context;
- if (auto status = createStreamContext(in_args.portConfigId, in_args.bufferSizeFrames, &context);
+ if (auto status = createStreamContext(in_args.portConfigId, in_args.bufferSizeFrames,
+ isNonBlocking ? in_args.callback : nullptr, &context);
!status.isOk()) {
return status;
}
diff --git a/audio/aidl/default/Stream.cpp b/audio/aidl/default/Stream.cpp
index 21dc4b6..d7c352f 100644
--- a/audio/aidl/default/Stream.cpp
+++ b/audio/aidl/default/Stream.cpp
@@ -87,32 +87,46 @@
void StreamWorkerCommonLogic::populateReply(StreamDescriptor::Reply* reply,
bool isConnected) const {
+ reply->status = STATUS_OK;
if (isConnected) {
- reply->status = STATUS_OK;
reply->observable.frames = mFrameCount;
reply->observable.timeNs = ::android::elapsedRealtimeNano();
} else {
- reply->status = STATUS_NO_INIT;
+ reply->observable.frames = StreamDescriptor::Position::UNKNOWN;
+ reply->observable.timeNs = StreamDescriptor::Position::UNKNOWN;
}
}
+void StreamWorkerCommonLogic::populateReplyWrongState(
+ StreamDescriptor::Reply* reply, const StreamDescriptor::Command& command) const {
+ LOG(WARNING) << "command '" << toString(command.getTag())
+ << "' can not be handled in the state " << toString(mState);
+ reply->status = STATUS_INVALID_OPERATION;
+}
+
const std::string StreamInWorkerLogic::kThreadName = "reader";
StreamInWorkerLogic::Status StreamInWorkerLogic::cycle() {
+ // Note: for input streams, draining is driven by the client, thus
+ // "empty buffer" condition can only happen while handling the 'burst'
+ // command. Thus, unlike for output streams, it does not make sense to
+ // delay the 'DRAINING' state here by 'mTransientStateDelayMs'.
+ // TODO: Add a delay for transitions of async operations when/if they added.
+
StreamDescriptor::Command command{};
if (!mCommandMQ->readBlocking(&command, 1)) {
LOG(ERROR) << __func__ << ": reading of command from MQ failed";
mState = StreamDescriptor::State::ERROR;
return Status::ABORT;
}
+ LOG(DEBUG) << __func__ << ": received command " << command.toString() << " in " << kThreadName;
StreamDescriptor::Reply reply{};
reply.status = STATUS_BAD_VALUE;
using Tag = StreamDescriptor::Command::Tag;
switch (command.getTag()) {
- case Tag::hal_reserved_exit:
- if (const int32_t cookie = command.get<Tag::hal_reserved_exit>();
+ case Tag::halReservedExit:
+ if (const int32_t cookie = command.get<Tag::halReservedExit>();
cookie == mInternalCommandCookie) {
- LOG(DEBUG) << __func__ << ": received EXIT command";
setClosed();
// This is an internal command, no need to reply.
return Status::EXIT;
@@ -120,8 +134,10 @@
LOG(WARNING) << __func__ << ": EXIT command has a bad cookie: " << cookie;
}
break;
+ case Tag::getStatus:
+ populateReply(&reply, mIsConnected);
+ break;
case Tag::start:
- LOG(DEBUG) << __func__ << ": received START read command";
if (mState == StreamDescriptor::State::STANDBY ||
mState == StreamDescriptor::State::DRAINING) {
populateReply(&reply, mIsConnected);
@@ -129,15 +145,13 @@
? StreamDescriptor::State::IDLE
: StreamDescriptor::State::ACTIVE;
} else {
- LOG(WARNING) << __func__ << ": START command can not be handled in the state "
- << toString(mState);
- reply.status = STATUS_INVALID_OPERATION;
+ populateReplyWrongState(&reply, command);
}
break;
case Tag::burst:
if (const int32_t fmqByteCount = command.get<Tag::burst>(); fmqByteCount >= 0) {
- LOG(DEBUG) << __func__ << ": received BURST read command for " << fmqByteCount
- << " bytes";
+ LOG(DEBUG) << __func__ << ": '" << toString(command.getTag()) << "' command for "
+ << fmqByteCount << " bytes";
if (mState == StreamDescriptor::State::IDLE ||
mState == StreamDescriptor::State::ACTIVE ||
mState == StreamDescriptor::State::PAUSED ||
@@ -151,69 +165,61 @@
} else if (mState == StreamDescriptor::State::DRAINING) {
// To simplify the reference code, we assume that the read operation
// has consumed all the data remaining in the hardware buffer.
- // TODO: Provide parametrization on the duration of draining to test
- // handling of commands during the 'DRAINING' state.
+ // In a real implementation, here we would either remain in
+ // the 'DRAINING' state, or transfer to 'STANDBY' depending on the
+ // buffer state.
mState = StreamDescriptor::State::STANDBY;
}
} else {
- LOG(WARNING) << __func__ << ": BURST command can not be handled in the state "
- << toString(mState);
- reply.status = STATUS_INVALID_OPERATION;
+ populateReplyWrongState(&reply, command);
}
} else {
LOG(WARNING) << __func__ << ": invalid burst byte count: " << fmqByteCount;
}
break;
case Tag::drain:
- LOG(DEBUG) << __func__ << ": received DRAIN read command";
- if (mState == StreamDescriptor::State::ACTIVE) {
- usleep(1000); // Simulate a blocking call into the driver.
- populateReply(&reply, mIsConnected);
- // Can switch the state to ERROR if a driver error occurs.
- mState = StreamDescriptor::State::DRAINING;
+ if (command.get<Tag::drain>() == StreamDescriptor::DrainMode::DRAIN_UNSPECIFIED) {
+ if (mState == StreamDescriptor::State::ACTIVE) {
+ usleep(1000); // Simulate a blocking call into the driver.
+ populateReply(&reply, mIsConnected);
+ // Can switch the state to ERROR if a driver error occurs.
+ mState = StreamDescriptor::State::DRAINING;
+ } else {
+ populateReplyWrongState(&reply, command);
+ }
} else {
- LOG(WARNING) << __func__ << ": DRAIN command can not be handled in the state "
- << toString(mState);
- reply.status = STATUS_INVALID_OPERATION;
+ LOG(WARNING) << __func__
+ << ": invalid drain mode: " << toString(command.get<Tag::drain>());
}
break;
case Tag::standby:
- LOG(DEBUG) << __func__ << ": received STANDBY read command";
if (mState == StreamDescriptor::State::IDLE) {
usleep(1000); // Simulate a blocking call into the driver.
populateReply(&reply, mIsConnected);
// Can switch the state to ERROR if a driver error occurs.
mState = StreamDescriptor::State::STANDBY;
} else {
- LOG(WARNING) << __func__ << ": FLUSH command can not be handled in the state "
- << toString(mState);
- reply.status = STATUS_INVALID_OPERATION;
+ populateReplyWrongState(&reply, command);
}
break;
case Tag::pause:
- LOG(DEBUG) << __func__ << ": received PAUSE read command";
if (mState == StreamDescriptor::State::ACTIVE) {
usleep(1000); // Simulate a blocking call into the driver.
populateReply(&reply, mIsConnected);
// Can switch the state to ERROR if a driver error occurs.
mState = StreamDescriptor::State::PAUSED;
} else {
- LOG(WARNING) << __func__ << ": PAUSE command can not be handled in the state "
- << toString(mState);
- reply.status = STATUS_INVALID_OPERATION;
+ populateReplyWrongState(&reply, command);
}
break;
case Tag::flush:
- LOG(DEBUG) << __func__ << ": received FLUSH read command";
if (mState == StreamDescriptor::State::PAUSED) {
usleep(1000); // Simulate a blocking call into the driver.
populateReply(&reply, mIsConnected);
// Can switch the state to ERROR if a driver error occurs.
mState = StreamDescriptor::State::STANDBY;
} else {
- LOG(WARNING) << __func__ << ": FLUSH command can not be handled in the state "
- << toString(mState);
- reply.status = STATUS_INVALID_OPERATION;
+ populateReplyWrongState(&reply, command);
}
break;
}
@@ -261,20 +267,52 @@
const std::string StreamOutWorkerLogic::kThreadName = "writer";
StreamOutWorkerLogic::Status StreamOutWorkerLogic::cycle() {
+ if (mState == StreamDescriptor::State::DRAINING ||
+ mState == StreamDescriptor::State::TRANSFERRING) {
+ if (auto stateDurationMs = std::chrono::duration_cast<std::chrono::milliseconds>(
+ std::chrono::steady_clock::now() - mTransientStateStart);
+ stateDurationMs >= mTransientStateDelayMs) {
+ if (mAsyncCallback == nullptr) {
+ // In blocking mode, mState can only be DRAINING.
+ mState = StreamDescriptor::State::IDLE;
+ } else {
+ // In a real implementation, the driver should notify the HAL about
+ // drain or transfer completion. In the stub, we switch unconditionally.
+ if (mState == StreamDescriptor::State::DRAINING) {
+ mState = StreamDescriptor::State::IDLE;
+ ndk::ScopedAStatus status = mAsyncCallback->onDrainReady();
+ if (!status.isOk()) {
+ LOG(ERROR) << __func__ << ": error from onDrainReady: " << status;
+ }
+ } else {
+ mState = StreamDescriptor::State::ACTIVE;
+ ndk::ScopedAStatus status = mAsyncCallback->onTransferReady();
+ if (!status.isOk()) {
+ LOG(ERROR) << __func__ << ": error from onTransferReady: " << status;
+ }
+ }
+ }
+ if (mTransientStateDelayMs.count() != 0) {
+ LOG(DEBUG) << __func__ << ": switched to state " << toString(mState)
+ << " after a timeout";
+ }
+ }
+ }
+
StreamDescriptor::Command command{};
if (!mCommandMQ->readBlocking(&command, 1)) {
LOG(ERROR) << __func__ << ": reading of command from MQ failed";
mState = StreamDescriptor::State::ERROR;
return Status::ABORT;
}
+ LOG(DEBUG) << __func__ << ": received command " << command.toString() << " in " << kThreadName;
StreamDescriptor::Reply reply{};
reply.status = STATUS_BAD_VALUE;
using Tag = StreamDescriptor::Command::Tag;
switch (command.getTag()) {
- case Tag::hal_reserved_exit:
- if (const int32_t cookie = command.get<Tag::hal_reserved_exit>();
+ case Tag::halReservedExit:
+ if (const int32_t cookie = command.get<Tag::halReservedExit>();
cookie == mInternalCommandCookie) {
- LOG(DEBUG) << __func__ << ": received EXIT command";
setClosed();
// This is an internal command, no need to reply.
return Status::EXIT;
@@ -282,8 +320,11 @@
LOG(WARNING) << __func__ << ": EXIT command has a bad cookie: " << cookie;
}
break;
- case Tag::start:
- LOG(DEBUG) << __func__ << ": received START write command";
+ case Tag::getStatus:
+ populateReply(&reply, mIsConnected);
+ break;
+ case Tag::start: {
+ bool commandAccepted = true;
switch (mState) {
case StreamDescriptor::State::STANDBY:
mState = StreamDescriptor::State::IDLE;
@@ -292,97 +333,112 @@
mState = StreamDescriptor::State::ACTIVE;
break;
case StreamDescriptor::State::DRAIN_PAUSED:
- mState = StreamDescriptor::State::PAUSED;
+ switchToTransientState(StreamDescriptor::State::DRAINING);
+ break;
+ case StreamDescriptor::State::TRANSFER_PAUSED:
+ switchToTransientState(StreamDescriptor::State::TRANSFERRING);
break;
default:
- LOG(WARNING) << __func__ << ": START command can not be handled in the state "
- << toString(mState);
- reply.status = STATUS_INVALID_OPERATION;
+ populateReplyWrongState(&reply, command);
+ commandAccepted = false;
}
- if (reply.status != STATUS_INVALID_OPERATION) {
+ if (commandAccepted) {
populateReply(&reply, mIsConnected);
}
- break;
+ } break;
case Tag::burst:
if (const int32_t fmqByteCount = command.get<Tag::burst>(); fmqByteCount >= 0) {
- LOG(DEBUG) << __func__ << ": received BURST write command for " << fmqByteCount
- << " bytes";
- if (mState !=
- StreamDescriptor::State::ERROR) { // BURST can be handled in all valid states
+ LOG(DEBUG) << __func__ << ": '" << toString(command.getTag()) << "' command for "
+ << fmqByteCount << " bytes";
+ if (mState != StreamDescriptor::State::ERROR &&
+ mState != StreamDescriptor::State::TRANSFERRING &&
+ mState != StreamDescriptor::State::TRANSFER_PAUSED) {
if (!write(fmqByteCount, &reply)) {
mState = StreamDescriptor::State::ERROR;
}
if (mState == StreamDescriptor::State::STANDBY ||
- mState == StreamDescriptor::State::DRAIN_PAUSED) {
- mState = StreamDescriptor::State::PAUSED;
+ mState == StreamDescriptor::State::DRAIN_PAUSED ||
+ mState == StreamDescriptor::State::PAUSED) {
+ if (mAsyncCallback == nullptr ||
+ mState != StreamDescriptor::State::DRAIN_PAUSED) {
+ mState = StreamDescriptor::State::PAUSED;
+ } else {
+ mState = StreamDescriptor::State::TRANSFER_PAUSED;
+ }
} else if (mState == StreamDescriptor::State::IDLE ||
- mState == StreamDescriptor::State::DRAINING) {
- mState = StreamDescriptor::State::ACTIVE;
- } // When in 'ACTIVE' and 'PAUSED' do not need to change the state.
+ mState == StreamDescriptor::State::DRAINING ||
+ mState == StreamDescriptor::State::ACTIVE) {
+ if (mAsyncCallback == nullptr || reply.fmqByteCount == fmqByteCount) {
+ mState = StreamDescriptor::State::ACTIVE;
+ } else {
+ switchToTransientState(StreamDescriptor::State::TRANSFERRING);
+ }
+ }
} else {
- LOG(WARNING) << __func__ << ": BURST command can not be handled in the state "
- << toString(mState);
- reply.status = STATUS_INVALID_OPERATION;
+ populateReplyWrongState(&reply, command);
}
} else {
LOG(WARNING) << __func__ << ": invalid burst byte count: " << fmqByteCount;
}
break;
case Tag::drain:
- LOG(DEBUG) << __func__ << ": received DRAIN write command";
- if (mState == StreamDescriptor::State::ACTIVE) {
- usleep(1000); // Simulate a blocking call into the driver.
- populateReply(&reply, mIsConnected);
- // Can switch the state to ERROR if a driver error occurs.
- mState = StreamDescriptor::State::IDLE;
- // Since there is no actual hardware that would be draining the buffer,
- // in order to simplify the reference code, we assume that draining
- // happens instantly, thus skipping the 'DRAINING' state.
- // TODO: Provide parametrization on the duration of draining to test
- // handling of commands during the 'DRAINING' state.
+ if (command.get<Tag::drain>() == StreamDescriptor::DrainMode::DRAIN_ALL ||
+ command.get<Tag::drain>() == StreamDescriptor::DrainMode::DRAIN_EARLY_NOTIFY) {
+ if (mState == StreamDescriptor::State::ACTIVE ||
+ mState == StreamDescriptor::State::TRANSFERRING) {
+ usleep(1000); // Simulate a blocking call into the driver.
+ populateReply(&reply, mIsConnected);
+ // Can switch the state to ERROR if a driver error occurs.
+ switchToTransientState(StreamDescriptor::State::DRAINING);
+ } else if (mState == StreamDescriptor::State::TRANSFER_PAUSED) {
+ mState = StreamDescriptor::State::DRAIN_PAUSED;
+ populateReply(&reply, mIsConnected);
+ } else {
+ populateReplyWrongState(&reply, command);
+ }
} else {
- LOG(WARNING) << __func__ << ": DRAIN command can not be handled in the state "
- << toString(mState);
- reply.status = STATUS_INVALID_OPERATION;
+ LOG(WARNING) << __func__
+ << ": invalid drain mode: " << toString(command.get<Tag::drain>());
}
break;
case Tag::standby:
- LOG(DEBUG) << __func__ << ": received STANDBY write command";
if (mState == StreamDescriptor::State::IDLE) {
usleep(1000); // Simulate a blocking call into the driver.
populateReply(&reply, mIsConnected);
// Can switch the state to ERROR if a driver error occurs.
mState = StreamDescriptor::State::STANDBY;
} else {
- LOG(WARNING) << __func__ << ": STANDBY command can not be handled in the state "
- << toString(mState);
- reply.status = STATUS_INVALID_OPERATION;
+ populateReplyWrongState(&reply, command);
}
break;
- case Tag::pause:
- LOG(DEBUG) << __func__ << ": received PAUSE write command";
- if (mState == StreamDescriptor::State::ACTIVE ||
- mState == StreamDescriptor::State::DRAINING) {
+ case Tag::pause: {
+ bool commandAccepted = true;
+ switch (mState) {
+ case StreamDescriptor::State::ACTIVE:
+ mState = StreamDescriptor::State::PAUSED;
+ break;
+ case StreamDescriptor::State::DRAINING:
+ mState = StreamDescriptor::State::DRAIN_PAUSED;
+ break;
+ case StreamDescriptor::State::TRANSFERRING:
+ mState = StreamDescriptor::State::TRANSFER_PAUSED;
+ break;
+ default:
+ populateReplyWrongState(&reply, command);
+ commandAccepted = false;
+ }
+ if (commandAccepted) {
populateReply(&reply, mIsConnected);
- mState = mState == StreamDescriptor::State::ACTIVE
- ? StreamDescriptor::State::PAUSED
- : StreamDescriptor::State::DRAIN_PAUSED;
- } else {
- LOG(WARNING) << __func__ << ": PAUSE command can not be handled in the state "
- << toString(mState);
- reply.status = STATUS_INVALID_OPERATION;
}
- break;
+ } break;
case Tag::flush:
- LOG(DEBUG) << __func__ << ": received FLUSH write command";
if (mState == StreamDescriptor::State::PAUSED ||
- mState == StreamDescriptor::State::DRAIN_PAUSED) {
+ mState == StreamDescriptor::State::DRAIN_PAUSED ||
+ mState == StreamDescriptor::State::TRANSFER_PAUSED) {
populateReply(&reply, mIsConnected);
mState = StreamDescriptor::State::IDLE;
} else {
- LOG(WARNING) << __func__ << ": FLUSH command can not be handled in the state "
- << toString(mState);
- reply.status = STATUS_INVALID_OPERATION;
+ populateReplyWrongState(&reply, command);
}
break;
}
@@ -450,9 +506,8 @@
void StreamCommon<Metadata, StreamWorker>::stopWorker() {
if (auto commandMQ = mContext.getCommandMQ(); commandMQ != nullptr) {
LOG(DEBUG) << __func__ << ": asking the worker to exit...";
- auto cmd =
- StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::hal_reserved_exit>(
- mContext.getInternalCommandCookie());
+ auto cmd = StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::halReservedExit>(
+ mContext.getInternalCommandCookie());
// Note: never call 'pause' and 'resume' methods of StreamWorker
// in the HAL implementation. These methods are to be used by
// the client side only. Preventing the worker loop from running
diff --git a/audio/aidl/default/include/core-impl/Module.h b/audio/aidl/default/include/core-impl/Module.h
index 0086743..f7b85ed 100644
--- a/audio/aidl/default/include/core-impl/Module.h
+++ b/audio/aidl/default/include/core-impl/Module.h
@@ -86,6 +86,7 @@
void cleanUpPatch(int32_t patchId);
ndk::ScopedAStatus createStreamContext(
int32_t in_portConfigId, int64_t in_bufferSizeFrames,
+ std::shared_ptr<IStreamCallback> asyncCallback,
::aidl::android::hardware::audio::core::StreamContext* out_context);
ndk::ScopedAStatus findPortIdForNewStream(
int32_t in_portConfigId, ::aidl::android::media::audio::common::AudioPort** port);
diff --git a/audio/aidl/default/include/core-impl/Stream.h b/audio/aidl/default/include/core-impl/Stream.h
index 5ee0f82..3c96973 100644
--- a/audio/aidl/default/include/core-impl/Stream.h
+++ b/audio/aidl/default/include/core-impl/Stream.h
@@ -17,6 +17,7 @@
#pragma once
#include <atomic>
+#include <chrono>
#include <cstdlib>
#include <map>
#include <memory>
@@ -28,6 +29,7 @@
#include <aidl/android/hardware/audio/common/SourceMetadata.h>
#include <aidl/android/hardware/audio/core/BnStreamIn.h>
#include <aidl/android/hardware/audio/core/BnStreamOut.h>
+#include <aidl/android/hardware/audio/core/IStreamCallback.h>
#include <aidl/android/hardware/audio/core/StreamDescriptor.h>
#include <aidl/android/media/audio/common/AudioOffloadInfo.h>
#include <fmq/AidlMessageQueue.h>
@@ -59,33 +61,42 @@
StreamContext() = default;
StreamContext(std::unique_ptr<CommandMQ> commandMQ, std::unique_ptr<ReplyMQ> replyMQ,
- size_t frameSize, std::unique_ptr<DataMQ> dataMQ)
+ size_t frameSize, std::unique_ptr<DataMQ> dataMQ,
+ std::shared_ptr<IStreamCallback> asyncCallback, int transientStateDelayMs)
: mCommandMQ(std::move(commandMQ)),
mInternalCommandCookie(std::rand()),
mReplyMQ(std::move(replyMQ)),
mFrameSize(frameSize),
- mDataMQ(std::move(dataMQ)) {}
+ mDataMQ(std::move(dataMQ)),
+ mAsyncCallback(asyncCallback),
+ mTransientStateDelayMs(transientStateDelayMs) {}
StreamContext(StreamContext&& other)
: mCommandMQ(std::move(other.mCommandMQ)),
mInternalCommandCookie(other.mInternalCommandCookie),
mReplyMQ(std::move(other.mReplyMQ)),
mFrameSize(other.mFrameSize),
- mDataMQ(std::move(other.mDataMQ)) {}
+ mDataMQ(std::move(other.mDataMQ)),
+ mAsyncCallback(other.mAsyncCallback),
+ mTransientStateDelayMs(other.mTransientStateDelayMs) {}
StreamContext& operator=(StreamContext&& other) {
mCommandMQ = std::move(other.mCommandMQ);
mInternalCommandCookie = other.mInternalCommandCookie;
mReplyMQ = std::move(other.mReplyMQ);
mFrameSize = other.mFrameSize;
mDataMQ = std::move(other.mDataMQ);
+ mAsyncCallback = other.mAsyncCallback;
+ mTransientStateDelayMs = other.mTransientStateDelayMs;
return *this;
}
void fillDescriptor(StreamDescriptor* desc);
+ std::shared_ptr<IStreamCallback> getAsyncCallback() const { return mAsyncCallback; }
CommandMQ* getCommandMQ() const { return mCommandMQ.get(); }
DataMQ* getDataMQ() const { return mDataMQ.get(); }
size_t getFrameSize() const { return mFrameSize; }
int getInternalCommandCookie() const { return mInternalCommandCookie; }
ReplyMQ* getReplyMQ() const { return mReplyMQ.get(); }
+ int getTransientStateDelayMs() const { return mTransientStateDelayMs; }
bool isValid() const;
void reset();
@@ -95,6 +106,8 @@
std::unique_ptr<ReplyMQ> mReplyMQ;
size_t mFrameSize;
std::unique_ptr<DataMQ> mDataMQ;
+ std::shared_ptr<IStreamCallback> mAsyncCallback;
+ int mTransientStateDelayMs;
};
class StreamWorkerCommonLogic : public ::android::hardware::audio::common::StreamLogic {
@@ -111,9 +124,17 @@
mFrameSize(context.getFrameSize()),
mCommandMQ(context.getCommandMQ()),
mReplyMQ(context.getReplyMQ()),
- mDataMQ(context.getDataMQ()) {}
+ mDataMQ(context.getDataMQ()),
+ mAsyncCallback(context.getAsyncCallback()),
+ mTransientStateDelayMs(context.getTransientStateDelayMs()) {}
std::string init() override;
void populateReply(StreamDescriptor::Reply* reply, bool isConnected) const;
+ void populateReplyWrongState(StreamDescriptor::Reply* reply,
+ const StreamDescriptor::Command& command) const;
+ void switchToTransientState(StreamDescriptor::State state) {
+ mState = state;
+ mTransientStateStart = std::chrono::steady_clock::now();
+ }
// Atomic fields are used both by the main and worker threads.
std::atomic<bool> mIsConnected = false;
@@ -125,6 +146,9 @@
StreamContext::CommandMQ* mCommandMQ;
StreamContext::ReplyMQ* mReplyMQ;
StreamContext::DataMQ* mDataMQ;
+ std::shared_ptr<IStreamCallback> mAsyncCallback;
+ const std::chrono::duration<int, std::milli> mTransientStateDelayMs;
+ std::chrono::time_point<std::chrono::steady_clock> mTransientStateStart;
// We use an array and the "size" field instead of a vector to be able to detect
// memory allocation issues.
std::unique_ptr<int8_t[]> mDataBuffer;
diff --git a/audio/aidl/vts/ModuleConfig.cpp b/audio/aidl/vts/ModuleConfig.cpp
index 33c5b72..c081402 100644
--- a/audio/aidl/vts/ModuleConfig.cpp
+++ b/audio/aidl/vts/ModuleConfig.cpp
@@ -125,21 +125,21 @@
return result;
}
+std::vector<AudioPort> ModuleConfig::getNonBlockingMixPorts(bool attachedOnly,
+ bool singlePort) const {
+ return findMixPorts(false /*isInput*/, singlePort, [&](const AudioPort& port) {
+ return isBitPositionFlagSet(port.flags.get<AudioIoFlags::Tag::output>(),
+ AudioOutputFlags::NON_BLOCKING) &&
+ (!attachedOnly || !getAttachedSinkDevicesPortsForMixPort(port).empty());
+ });
+}
+
std::vector<AudioPort> ModuleConfig::getOffloadMixPorts(bool attachedOnly, bool singlePort) const {
- std::vector<AudioPort> result;
- const auto mixPorts = getMixPorts(false /*isInput*/);
- auto offloadPortIt = mixPorts.begin();
- while (offloadPortIt != mixPorts.end()) {
- offloadPortIt = std::find_if(offloadPortIt, mixPorts.end(), [&](const AudioPort& port) {
- return isBitPositionFlagSet(port.flags.get<AudioIoFlags::Tag::output>(),
- AudioOutputFlags::COMPRESS_OFFLOAD) &&
- (!attachedOnly || !getAttachedSinkDevicesPortsForMixPort(port).empty());
- });
- if (offloadPortIt == mixPorts.end()) break;
- result.push_back(*offloadPortIt++);
- if (singlePort) break;
- }
- return result;
+ return findMixPorts(false /*isInput*/, singlePort, [&](const AudioPort& port) {
+ return isBitPositionFlagSet(port.flags.get<AudioIoFlags::Tag::output>(),
+ AudioOutputFlags::COMPRESS_OFFLOAD) &&
+ (!attachedOnly || !getAttachedSinkDevicesPortsForMixPort(port).empty());
+ });
}
std::vector<AudioPort> ModuleConfig::getAttachedDevicesPortsForMixPort(
@@ -343,6 +343,19 @@
profile.sampleRates.empty() || profile.channelMasks.empty();
}
+std::vector<AudioPort> ModuleConfig::findMixPorts(
+ bool isInput, bool singlePort, std::function<bool(const AudioPort&)> pred) const {
+ std::vector<AudioPort> result;
+ const auto mixPorts = getMixPorts(isInput);
+ for (auto mixPortIt = mixPorts.begin(); mixPortIt != mixPorts.end();) {
+ mixPortIt = std::find_if(mixPortIt, mixPorts.end(), pred);
+ if (mixPortIt == mixPorts.end()) break;
+ result.push_back(*mixPortIt++);
+ if (singlePort) break;
+ }
+ return result;
+}
+
std::vector<AudioPortConfig> ModuleConfig::generateAudioMixPortConfigs(
const std::vector<AudioPort>& ports, bool isInput, bool singleProfile) const {
std::vector<AudioPortConfig> result;
diff --git a/audio/aidl/vts/ModuleConfig.h b/audio/aidl/vts/ModuleConfig.h
index dc109a7..a85aa7f 100644
--- a/audio/aidl/vts/ModuleConfig.h
+++ b/audio/aidl/vts/ModuleConfig.h
@@ -16,6 +16,7 @@
#pragma once
+#include <functional>
#include <optional>
#include <set>
#include <utility>
@@ -48,6 +49,8 @@
std::vector<aidl::android::media::audio::common::AudioPort> getMixPorts(bool isInput) const {
return isInput ? getInputMixPorts() : getOutputMixPorts();
}
+ std::vector<aidl::android::media::audio::common::AudioPort> getNonBlockingMixPorts(
+ bool attachedOnly, bool singlePort) const;
std::vector<aidl::android::media::audio::common::AudioPort> getOffloadMixPorts(
bool attachedOnly, bool singlePort) const;
@@ -121,6 +124,9 @@
std::string toString() const;
private:
+ std::vector<aidl::android::media::audio::common::AudioPort> findMixPorts(
+ bool isInput, bool singlePort,
+ std::function<bool(const aidl::android::media::audio::common::AudioPort&)> pred) const;
std::vector<aidl::android::media::audio::common::AudioPortConfig> generateAudioMixPortConfigs(
const std::vector<aidl::android::media::audio::common::AudioPort>& ports, bool isInput,
bool singleProfile) const;
diff --git a/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp b/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp
index 5e9aa7f..79b20fe 100644
--- a/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp
@@ -15,12 +15,16 @@
*/
#include <algorithm>
+#include <chrono>
#include <cmath>
+#include <condition_variable>
#include <limits>
#include <memory>
+#include <mutex>
#include <optional>
#include <set>
#include <string>
+#include <variant>
#include <vector>
#define LOG_TAG "VtsHalAudioCore"
@@ -30,6 +34,7 @@
#include <Utils.h>
#include <aidl/Gtest.h>
#include <aidl/Vintf.h>
+#include <aidl/android/hardware/audio/core/BnStreamCallback.h>
#include <aidl/android/hardware/audio/core/IModule.h>
#include <aidl/android/hardware/audio/core/ITelephony.h>
#include <aidl/android/media/audio/common/AudioIoFlags.h>
@@ -389,15 +394,132 @@
std::unique_ptr<DataMQ> mDataMQ;
};
-class StreamLogicDriver {
+struct StreamEventReceiver {
+ virtual ~StreamEventReceiver() = default;
+ enum class Event { None, DrainReady, Error, TransferReady };
+ virtual std::tuple<int, Event> getLastEvent() const = 0;
+ virtual std::tuple<int, Event> waitForEvent(int clientEventSeq) = 0;
+ static constexpr int kEventSeqInit = -1;
+};
+std::string toString(StreamEventReceiver::Event event) {
+ switch (event) {
+ case StreamEventReceiver::Event::None:
+ return "None";
+ case StreamEventReceiver::Event::DrainReady:
+ return "DrainReady";
+ case StreamEventReceiver::Event::Error:
+ return "Error";
+ case StreamEventReceiver::Event::TransferReady:
+ return "TransferReady";
+ }
+ return std::to_string(static_cast<int32_t>(event));
+}
+
+// Transition to the next state happens either due to a command from the client,
+// or after an event received from the server.
+using TransitionTrigger = std::variant<StreamDescriptor::Command, StreamEventReceiver::Event>;
+using StateTransition = std::pair<TransitionTrigger, StreamDescriptor::State>;
+struct StateSequence {
+ virtual ~StateSequence() = default;
+ virtual void rewind() = 0;
+ virtual bool done() const = 0;
+ virtual TransitionTrigger getTrigger() = 0;
+ virtual std::set<StreamDescriptor::State> getExpectedStates() = 0;
+ virtual void advance(StreamDescriptor::State state) = 0;
+};
+
+static const StreamDescriptor::Command kGetStatusCommand =
+ StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::getStatus>(Void{});
+static const StreamDescriptor::Command kStartCommand =
+ StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::start>(Void{});
+static const StreamDescriptor::Command kBurstCommand =
+ StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::burst>(0);
+static const StreamDescriptor::Command kDrainInCommand =
+ StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::drain>(
+ StreamDescriptor::DrainMode::DRAIN_UNSPECIFIED);
+static const StreamDescriptor::Command kDrainOutAllCommand =
+ StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::drain>(
+ StreamDescriptor::DrainMode::DRAIN_ALL);
+static const StreamDescriptor::Command kDrainOutEarlyCommand =
+ StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::drain>(
+ StreamDescriptor::DrainMode::DRAIN_EARLY_NOTIFY);
+static const StreamDescriptor::Command kStandbyCommand =
+ StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::standby>(Void{});
+static const StreamDescriptor::Command kPauseCommand =
+ StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::pause>(Void{});
+static const StreamDescriptor::Command kFlushCommand =
+ StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::flush>(Void{});
+static const StreamEventReceiver::Event kTransferReadyEvent =
+ StreamEventReceiver::Event::TransferReady;
+static const StreamEventReceiver::Event kDrainReadyEvent = StreamEventReceiver::Event::DrainReady;
+
+// Handle possible bifurcations:
+// - on burst and on start: 'TRANSFERRING' -> {'ACTIVE', 'TRANSFERRING'}
+// - on pause: 'TRANSFER_PAUSED' -> {'PAUSED', 'TRANSFER_PAUSED'}
+// It is assumed that the 'steps' provided on the construction contain the sequence
+// for the async case, which gets corrected in the case when the HAL decided to do
+// a synchronous transfer.
+class SmartStateSequence : public StateSequence {
public:
+ explicit SmartStateSequence(const std::vector<StateTransition>& steps) : mSteps(steps) {}
+ explicit SmartStateSequence(std::vector<StateTransition>&& steps) : mSteps(std::move(steps)) {}
+ void rewind() override { mCurrentStep = 0; }
+ bool done() const override { return mCurrentStep >= mSteps.size(); }
+ TransitionTrigger getTrigger() override { return mSteps[mCurrentStep].first; }
+ std::set<StreamDescriptor::State> getExpectedStates() override {
+ std::set<StreamDescriptor::State> result = {getState()};
+ if (isBurstBifurcation() || isStartBifurcation()) {
+ result.insert(StreamDescriptor::State::ACTIVE);
+ } else if (isPauseBifurcation()) {
+ result.insert(StreamDescriptor::State::PAUSED);
+ }
+ return result;
+ }
+ void advance(StreamDescriptor::State state) override {
+ if (isBurstBifurcation() && state == StreamDescriptor::State::ACTIVE &&
+ mCurrentStep + 1 < mSteps.size() &&
+ mSteps[mCurrentStep + 1].first == TransitionTrigger{kTransferReadyEvent}) {
+ mCurrentStep++;
+ }
+ mCurrentStep++;
+ }
+
+ private:
+ StreamDescriptor::State getState() const { return mSteps[mCurrentStep].second; }
+ bool isBurstBifurcation() {
+ return getTrigger() == TransitionTrigger{kBurstCommand}&& getState() ==
+ StreamDescriptor::State::TRANSFERRING;
+ }
+ bool isPauseBifurcation() {
+ return getTrigger() == TransitionTrigger{kPauseCommand}&& getState() ==
+ StreamDescriptor::State::TRANSFER_PAUSED;
+ }
+ bool isStartBifurcation() {
+ return getTrigger() == TransitionTrigger{kStartCommand}&& getState() ==
+ StreamDescriptor::State::TRANSFERRING;
+ }
+ const std::vector<StateTransition> mSteps;
+ size_t mCurrentStep = 0;
+};
+
+std::string toString(const TransitionTrigger& trigger) {
+ if (std::holds_alternative<StreamDescriptor::Command>(trigger)) {
+ return std::string("'")
+ .append(toString(std::get<StreamDescriptor::Command>(trigger).getTag()))
+ .append("' command");
+ }
+ return std::string("'")
+ .append(toString(std::get<StreamEventReceiver::Event>(trigger)))
+ .append("' event");
+}
+
+struct StreamLogicDriver {
virtual ~StreamLogicDriver() = default;
// Return 'true' to stop the worker.
virtual bool done() = 0;
// For 'Writer' logic, if the 'actualSize' is 0, write is skipped.
// The 'fmqByteCount' from the returned command is passed as is to the HAL.
- virtual StreamDescriptor::Command getNextCommand(int maxDataSize,
- int* actualSize = nullptr) = 0;
+ virtual TransitionTrigger getNextTrigger(int maxDataSize, int* actualSize = nullptr) = 0;
// Return 'true' to indicate that no further processing is needed,
// for example, the driver is expecting a bad status to be returned.
// The logic cycle will return with 'CONTINUE' status. Otherwise,
@@ -410,46 +532,102 @@
class StreamCommonLogic : public StreamLogic {
protected:
- StreamCommonLogic(const StreamContext& context, StreamLogicDriver* driver)
+ StreamCommonLogic(const StreamContext& context, StreamLogicDriver* driver,
+ StreamEventReceiver* eventReceiver)
: mCommandMQ(context.getCommandMQ()),
mReplyMQ(context.getReplyMQ()),
mDataMQ(context.getDataMQ()),
mData(context.getBufferSizeBytes()),
- mDriver(driver) {}
+ mDriver(driver),
+ mEventReceiver(eventReceiver) {}
StreamContext::CommandMQ* getCommandMQ() const { return mCommandMQ; }
StreamContext::ReplyMQ* getReplyMQ() const { return mReplyMQ; }
+ StreamContext::DataMQ* getDataMQ() const { return mDataMQ; }
StreamLogicDriver* getDriver() const { return mDriver; }
+ StreamEventReceiver* getEventReceiver() const { return mEventReceiver; }
- std::string init() override { return ""; }
+ std::string init() override {
+ LOG(DEBUG) << __func__;
+ return "";
+ }
+ std::optional<StreamDescriptor::Command> maybeGetNextCommand(int* actualSize = nullptr) {
+ TransitionTrigger trigger = mDriver->getNextTrigger(mData.size(), actualSize);
+ if (StreamEventReceiver::Event* expEvent =
+ std::get_if<StreamEventReceiver::Event>(&trigger);
+ expEvent != nullptr) {
+ auto [eventSeq, event] = mEventReceiver->waitForEvent(mLastEventSeq);
+ mLastEventSeq = eventSeq;
+ if (event != *expEvent) {
+ LOG(ERROR) << __func__ << ": expected event " << toString(*expEvent) << ", got "
+ << toString(event);
+ return {};
+ }
+ // If we were waiting for an event, the new stream state must be retrieved
+ // via 'getStatus'.
+ return StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::getStatus>(
+ Void{});
+ }
+ return std::get<StreamDescriptor::Command>(trigger);
+ }
+ bool readDataFromMQ(size_t readCount) {
+ std::vector<int8_t> data(readCount);
+ if (mDataMQ->read(data.data(), readCount)) {
+ memcpy(mData.data(), data.data(), std::min(mData.size(), data.size()));
+ return true;
+ }
+ LOG(ERROR) << __func__ << ": reading of " << readCount << " bytes from MQ failed";
+ return false;
+ }
+ bool writeDataToMQ() {
+ if (mDataMQ->write(mData.data(), mData.size())) {
+ return true;
+ }
+ LOG(ERROR) << __func__ << ": writing of " << mData.size() << " bytes to MQ failed";
+ return false;
+ }
+ private:
StreamContext::CommandMQ* mCommandMQ;
StreamContext::ReplyMQ* mReplyMQ;
StreamContext::DataMQ* mDataMQ;
std::vector<int8_t> mData;
StreamLogicDriver* const mDriver;
+ StreamEventReceiver* const mEventReceiver;
+ int mLastEventSeq = StreamEventReceiver::kEventSeqInit;
};
class StreamReaderLogic : public StreamCommonLogic {
public:
- StreamReaderLogic(const StreamContext& context, StreamLogicDriver* driver)
- : StreamCommonLogic(context, driver) {}
+ StreamReaderLogic(const StreamContext& context, StreamLogicDriver* driver,
+ StreamEventReceiver* eventReceiver)
+ : StreamCommonLogic(context, driver, eventReceiver) {}
protected:
Status cycle() override {
if (getDriver()->done()) {
+ LOG(DEBUG) << __func__ << ": clean exit";
return Status::EXIT;
}
- StreamDescriptor::Command command = getDriver()->getNextCommand(mData.size());
- if (!mCommandMQ->writeBlocking(&command, 1)) {
+ StreamDescriptor::Command command;
+ if (auto maybeCommand = maybeGetNextCommand(); maybeCommand.has_value()) {
+ command = std::move(maybeCommand.value());
+ } else {
+ LOG(ERROR) << __func__ << ": no next command";
+ return Status::ABORT;
+ }
+ LOG(DEBUG) << "Writing command: " << command.toString();
+ if (!getCommandMQ()->writeBlocking(&command, 1)) {
LOG(ERROR) << __func__ << ": writing of command into MQ failed";
return Status::ABORT;
}
StreamDescriptor::Reply reply{};
- if (!mReplyMQ->readBlocking(&reply, 1)) {
- LOG(ERROR) << __func__ << ": reading of reply from MQ failed";
+ LOG(DEBUG) << "Reading reply...";
+ if (!getReplyMQ()->readBlocking(&reply, 1)) {
return Status::ABORT;
}
+ LOG(DEBUG) << "Reply received: " << reply.toString();
if (getDriver()->interceptRawReply(reply)) {
+ LOG(DEBUG) << __func__ << ": reply has been intercepted by the driver";
return Status::CONTINUE;
}
if (reply.status != STATUS_OK) {
@@ -463,11 +641,11 @@
<< ": received invalid byte count in the reply: " << reply.fmqByteCount;
return Status::ABORT;
}
- if (static_cast<size_t>(reply.fmqByteCount) != mDataMQ->availableToRead()) {
+ if (static_cast<size_t>(reply.fmqByteCount) != getDataMQ()->availableToRead()) {
LOG(ERROR) << __func__
<< ": the byte count in the reply is not the same as the amount of "
<< "data available in the MQ: " << reply.fmqByteCount
- << " != " << mDataMQ->availableToRead();
+ << " != " << getDataMQ()->availableToRead();
}
if (reply.latencyMs < 0 && reply.latencyMs != StreamDescriptor::LATENCY_UNKNOWN) {
LOG(ERROR) << __func__ << ": received invalid latency value: " << reply.latencyMs;
@@ -484,10 +662,8 @@
return Status::ABORT;
}
const bool acceptedReply = getDriver()->processValidReply(reply);
- if (const size_t readCount = mDataMQ->availableToRead(); readCount > 0) {
- std::vector<int8_t> data(readCount);
- if (mDataMQ->read(data.data(), readCount)) {
- memcpy(mData.data(), data.data(), std::min(mData.size(), data.size()));
+ if (const size_t readCount = getDataMQ()->availableToRead(); readCount > 0) {
+ if (readDataFromMQ(readCount)) {
goto checkAcceptedReply;
}
LOG(ERROR) << __func__ << ": reading of " << readCount << " data bytes from MQ failed";
@@ -505,29 +681,39 @@
class StreamWriterLogic : public StreamCommonLogic {
public:
- StreamWriterLogic(const StreamContext& context, StreamLogicDriver* driver)
- : StreamCommonLogic(context, driver) {}
+ StreamWriterLogic(const StreamContext& context, StreamLogicDriver* driver,
+ StreamEventReceiver* eventReceiver)
+ : StreamCommonLogic(context, driver, eventReceiver) {}
protected:
Status cycle() override {
if (getDriver()->done()) {
+ LOG(DEBUG) << __func__ << ": clean exit";
return Status::EXIT;
}
int actualSize = 0;
- StreamDescriptor::Command command = getDriver()->getNextCommand(mData.size(), &actualSize);
- if (actualSize != 0 && !mDataMQ->write(mData.data(), mData.size())) {
- LOG(ERROR) << __func__ << ": writing of " << mData.size() << " bytes to MQ failed";
+ StreamDescriptor::Command command;
+ if (auto maybeCommand = maybeGetNextCommand(&actualSize); maybeCommand.has_value()) {
+ command = std::move(maybeCommand.value());
+ } else {
+ LOG(ERROR) << __func__ << ": no next command";
return Status::ABORT;
}
- if (!mCommandMQ->writeBlocking(&command, 1)) {
+ if (actualSize != 0 && !writeDataToMQ()) {
+ return Status::ABORT;
+ }
+ LOG(DEBUG) << "Writing command: " << command.toString();
+ if (!getCommandMQ()->writeBlocking(&command, 1)) {
LOG(ERROR) << __func__ << ": writing of command into MQ failed";
return Status::ABORT;
}
StreamDescriptor::Reply reply{};
- if (!mReplyMQ->readBlocking(&reply, 1)) {
+ LOG(DEBUG) << "Reading reply...";
+ if (!getReplyMQ()->readBlocking(&reply, 1)) {
LOG(ERROR) << __func__ << ": reading of reply from MQ failed";
return Status::ABORT;
}
+ LOG(DEBUG) << "Reply received: " << reply.toString();
if (getDriver()->interceptRawReply(reply)) {
return Status::CONTINUE;
}
@@ -542,10 +728,10 @@
<< ": received invalid byte count in the reply: " << reply.fmqByteCount;
return Status::ABORT;
}
- if (mDataMQ->availableToWrite() != mDataMQ->getQuantumCount()) {
+ if (getDataMQ()->availableToWrite() != getDataMQ()->getQuantumCount()) {
LOG(ERROR) << __func__ << ": the HAL module did not consume all data from the data MQ: "
- << "available to write " << mDataMQ->availableToWrite()
- << ", total size: " << mDataMQ->getQuantumCount();
+ << "available to write " << getDataMQ()->availableToWrite()
+ << ", total size: " << getDataMQ()->getQuantumCount();
return Status::ABORT;
}
if (reply.latencyMs < 0 && reply.latencyMs != StreamDescriptor::LATENCY_UNKNOWN) {
@@ -571,6 +757,71 @@
};
using StreamWriter = StreamWorker<StreamWriterLogic>;
+class DefaultStreamCallback : public ::aidl::android::hardware::audio::core::BnStreamCallback,
+ public StreamEventReceiver {
+ ndk::ScopedAStatus onTransferReady() override {
+ LOG(DEBUG) << __func__;
+ putLastEvent(Event::TransferReady);
+ return ndk::ScopedAStatus::ok();
+ }
+ ndk::ScopedAStatus onError() override {
+ LOG(DEBUG) << __func__;
+ putLastEvent(Event::Error);
+ return ndk::ScopedAStatus::ok();
+ }
+ ndk::ScopedAStatus onDrainReady() override {
+ LOG(DEBUG) << __func__;
+ putLastEvent(Event::DrainReady);
+ return ndk::ScopedAStatus::ok();
+ }
+
+ public:
+ // To avoid timing out the whole test suite in case no event is received
+ // from the HAL, use a local timeout for event waiting.
+ static constexpr auto kEventTimeoutMs = std::chrono::milliseconds(1000);
+
+ StreamEventReceiver* getEventReceiver() { return this; }
+ std::tuple<int, Event> getLastEvent() const override {
+ std::lock_guard l(mLock);
+ return getLastEvent_l();
+ }
+ std::tuple<int, Event> waitForEvent(int clientEventSeq) override {
+ std::unique_lock l(mLock);
+ android::base::ScopedLockAssertion lock_assertion(mLock);
+ LOG(DEBUG) << __func__ << ": client " << clientEventSeq << ", last " << mLastEventSeq;
+ if (mCv.wait_for(l, kEventTimeoutMs, [&]() {
+ android::base::ScopedLockAssertion lock_assertion(mLock);
+ return clientEventSeq < mLastEventSeq;
+ })) {
+ } else {
+ LOG(WARNING) << __func__ << ": timed out waiting for an event";
+ putLastEvent_l(Event::None);
+ }
+ return getLastEvent_l();
+ }
+
+ private:
+ std::tuple<int, Event> getLastEvent_l() const REQUIRES(mLock) {
+ return std::make_tuple(mLastEventSeq, mLastEvent);
+ }
+ void putLastEvent(Event event) {
+ {
+ std::lock_guard l(mLock);
+ putLastEvent_l(event);
+ }
+ mCv.notify_one();
+ }
+ void putLastEvent_l(Event event) REQUIRES(mLock) {
+ mLastEventSeq++;
+ mLastEvent = event;
+ }
+
+ mutable std::mutex mLock;
+ std::condition_variable mCv;
+ int mLastEventSeq GUARDED_BY(mLock) = kEventSeqInit;
+ Event mLastEvent GUARDED_BY(mLock) = Event::None;
+};
+
template <typename T>
struct IOTraits {
static constexpr bool is_input = std::is_same_v<T, IStreamIn>;
@@ -607,6 +858,7 @@
}
Stream* get() const { return mStream.get(); }
const StreamContext* getContext() const { return mContext ? &(mContext.value()) : nullptr; }
+ StreamEventReceiver* getEventReceiver() { return mStreamCallback->getEventReceiver(); }
std::shared_ptr<Stream> getSharedPointer() const { return mStream; }
const AudioPortConfig& getPortConfig() const { return mPortConfig.get(); }
int32_t getPortId() const { return mPortConfig.getId(); }
@@ -616,6 +868,7 @@
std::shared_ptr<Stream> mStream;
StreamDescriptor mDescriptor;
std::optional<StreamContext> mContext;
+ std::shared_ptr<DefaultStreamCallback> mStreamCallback;
};
SinkMetadata GenerateSinkMetadata(const AudioPortConfig& portConfig) {
@@ -636,11 +889,15 @@
args.portConfigId = portConfig.id;
args.sinkMetadata = GenerateSinkMetadata(portConfig);
args.bufferSizeFrames = bufferSizeFrames;
+ auto callback = ndk::SharedRefBase::make<DefaultStreamCallback>();
+ // TODO: Uncomment when support for asynchronous input is implemented.
+ // args.callback = callback;
aidl::android::hardware::audio::core::IModule::OpenInputStreamReturn ret;
ScopedAStatus status = module->openInputStream(args, &ret);
if (status.isOk()) {
mStream = std::move(ret.stream);
mDescriptor = std::move(ret.desc);
+ mStreamCallback = std::move(callback);
}
return status;
}
@@ -665,11 +922,14 @@
args.sourceMetadata = GenerateSourceMetadata(portConfig);
args.offloadInfo = ModuleConfig::generateOffloadInfoIfNeeded(portConfig);
args.bufferSizeFrames = bufferSizeFrames;
+ auto callback = ndk::SharedRefBase::make<DefaultStreamCallback>();
+ args.callback = callback;
aidl::android::hardware::audio::core::IModule::OpenOutputStreamReturn ret;
ScopedAStatus status = module->openOutputStream(args, &ret);
if (status.isOk()) {
mStream = std::move(ret.stream);
mDescriptor = std::move(ret.desc);
+ mStreamCallback = std::move(callback);
}
return status;
}
@@ -1379,10 +1639,10 @@
}
}
+using CommandSequence = std::vector<StreamDescriptor::Command>;
class StreamLogicDriverInvalidCommand : public StreamLogicDriver {
public:
- StreamLogicDriverInvalidCommand(const std::vector<StreamDescriptor::Command>& commands)
- : mCommands(commands) {}
+ StreamLogicDriverInvalidCommand(const CommandSequence& commands) : mCommands(commands) {}
std::string getUnexpectedStatuses() {
// This method is intended to be called after the worker thread has joined,
@@ -1396,25 +1656,29 @@
}
bool done() override { return mNextCommand >= mCommands.size(); }
- StreamDescriptor::Command getNextCommand(int, int* actualSize) override {
+ TransitionTrigger getNextTrigger(int, int* actualSize) override {
if (actualSize != nullptr) *actualSize = 0;
return mCommands[mNextCommand++];
}
bool interceptRawReply(const StreamDescriptor::Reply& reply) override {
- if (reply.status != STATUS_BAD_VALUE) {
- std::string s = mCommands[mNextCommand - 1].toString();
+ const size_t currentCommand = mNextCommand - 1; // increased by getNextTrigger
+ const bool isLastCommand = currentCommand == mCommands.size() - 1;
+ // All but the last command should run correctly. The last command must return 'BAD_VALUE'
+ // status.
+ if ((!isLastCommand && reply.status != STATUS_OK) ||
+ (isLastCommand && reply.status != STATUS_BAD_VALUE)) {
+ std::string s = mCommands[currentCommand].toString();
s.append(", ").append(statusToString(reply.status));
mStatuses.push_back(std::move(s));
- // If the HAL does not recognize the command as invalid,
- // retrieve the data etc.
- return reply.status != STATUS_OK;
+ // Process the reply, since the worker exits in case of an error.
+ return false;
}
- return true;
+ return isLastCommand;
}
bool processValidReply(const StreamDescriptor::Reply&) override { return true; }
private:
- const std::vector<StreamDescriptor::Command> mCommands;
+ const CommandSequence mCommands;
size_t mNextCommand = 0;
std::vector<std::string> mStatuses;
};
@@ -1556,22 +1820,46 @@
}
void SendInvalidCommandImpl(const AudioPortConfig& portConfig) {
- std::vector<StreamDescriptor::Command> commands = {
- StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::hal_reserved_exit>(
- 0),
- // TODO: For proper testing of input streams, need to put the stream into
- // a state which accepts BURST commands.
- StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::burst>(-1),
- StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::burst>(
- std::numeric_limits<int32_t>::min()),
- };
- WithStream<Stream> stream(portConfig);
- ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
- StreamLogicDriverInvalidCommand driver(commands);
- typename IOTraits<Stream>::Worker worker(*stream.getContext(), &driver);
- ASSERT_TRUE(worker.start());
- worker.join();
- EXPECT_EQ("", driver.getUnexpectedStatuses());
+ using TestSequence = std::pair<std::string, CommandSequence>;
+ // The last command in 'CommandSequence' is the one that must trigger
+ // an error status. All preceding commands are to put the state machine
+ // into a state which accepts the last command.
+ std::vector<TestSequence> sequences{
+ std::make_pair(std::string("HalReservedExit"),
+ std::vector{StreamDescriptor::Command::make<
+ StreamDescriptor::Command::Tag::halReservedExit>(0)}),
+ std::make_pair(std::string("BurstNeg"),
+ std::vector{kStartCommand,
+ StreamDescriptor::Command::make<
+ StreamDescriptor::Command::Tag::burst>(-1)}),
+ std::make_pair(
+ std::string("BurstMinInt"),
+ std::vector{kStartCommand, StreamDescriptor::Command::make<
+ StreamDescriptor::Command::Tag::burst>(
+ std::numeric_limits<int32_t>::min())})};
+ if (IOTraits<Stream>::is_input) {
+ sequences.emplace_back("DrainAll",
+ std::vector{kStartCommand, kBurstCommand, kDrainOutAllCommand});
+ sequences.emplace_back(
+ "DrainEarly", std::vector{kStartCommand, kBurstCommand, kDrainOutEarlyCommand});
+ } else {
+ sequences.emplace_back("DrainUnspecified",
+ std::vector{kStartCommand, kBurstCommand, kDrainInCommand});
+ }
+ for (const auto& seq : sequences) {
+ SCOPED_TRACE(std::string("Sequence ").append(seq.first));
+ LOG(DEBUG) << __func__ << ": Sequence " << seq.first;
+ WithStream<Stream> stream(portConfig);
+ ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+ StreamLogicDriverInvalidCommand driver(seq.second);
+ typename IOTraits<Stream>::Worker worker(*stream.getContext(), &driver,
+ stream.getEventReceiver());
+ LOG(DEBUG) << __func__ << ": starting worker...";
+ ASSERT_TRUE(worker.start());
+ LOG(DEBUG) << __func__ << ": joining worker...";
+ worker.join();
+ EXPECT_EQ("", driver.getUnexpectedStatuses());
+ }
}
};
using AudioStreamIn = AudioStream<IStreamIn>;
@@ -1615,27 +1903,51 @@
GTEST_SKIP()
<< "No mix port for compressed offload that could be routed to attached devices";
}
- const auto portConfig =
- moduleConfig->getSingleConfigForMixPort(false, *offloadMixPorts.begin());
- ASSERT_TRUE(portConfig.has_value())
- << "No profiles specified for the compressed offload mix port";
+ const auto config = moduleConfig->getSingleConfigForMixPort(false, *offloadMixPorts.begin());
+ ASSERT_TRUE(config.has_value()) << "No profiles specified for the compressed offload mix port";
+ WithAudioPortConfig portConfig(config.value());
+ ASSERT_NO_FATAL_FAILURE(portConfig.SetUp(module.get()));
StreamDescriptor descriptor;
std::shared_ptr<IStreamOut> ignored;
aidl::android::hardware::audio::core::IModule::OpenOutputStreamArguments args;
- args.portConfigId = portConfig.value().id;
- args.sourceMetadata = GenerateSourceMetadata(portConfig.value());
+ args.portConfigId = portConfig.getId();
+ args.sourceMetadata = GenerateSourceMetadata(portConfig.get());
args.bufferSizeFrames = kDefaultBufferSizeFrames;
aidl::android::hardware::audio::core::IModule::OpenOutputStreamReturn ret;
EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->openOutputStream(args, &ret))
<< "when no offload info is provided for a compressed offload mix port";
}
-using CommandAndState = std::pair<StreamDescriptor::Command, StreamDescriptor::State>;
+TEST_P(AudioStreamOut, RequireAsyncCallback) {
+ const auto nonBlockingMixPorts =
+ moduleConfig->getNonBlockingMixPorts(true /*attachedOnly*/, true /*singlePort*/);
+ if (nonBlockingMixPorts.empty()) {
+ GTEST_SKIP()
+ << "No mix port for non-blocking output that could be routed to attached devices";
+ }
+ const auto config =
+ moduleConfig->getSingleConfigForMixPort(false, *nonBlockingMixPorts.begin());
+ ASSERT_TRUE(config.has_value()) << "No profiles specified for the non-blocking mix port";
+ WithAudioPortConfig portConfig(config.value());
+ ASSERT_NO_FATAL_FAILURE(portConfig.SetUp(module.get()));
+ StreamDescriptor descriptor;
+ std::shared_ptr<IStreamOut> ignored;
+ aidl::android::hardware::audio::core::IModule::OpenOutputStreamArguments args;
+ args.portConfigId = portConfig.getId();
+ args.sourceMetadata = GenerateSourceMetadata(portConfig.get());
+ args.offloadInfo = ModuleConfig::generateOffloadInfoIfNeeded(portConfig.get());
+ args.bufferSizeFrames = kDefaultBufferSizeFrames;
+ aidl::android::hardware::audio::core::IModule::OpenOutputStreamReturn ret;
+ EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->openOutputStream(args, &ret))
+ << "when no async callback is provided for a non-blocking mix port";
+}
class StreamLogicDefaultDriver : public StreamLogicDriver {
public:
- explicit StreamLogicDefaultDriver(const std::vector<CommandAndState>& commands)
- : mCommands(commands) {}
+ explicit StreamLogicDefaultDriver(std::shared_ptr<StateSequence> commands)
+ : mCommands(commands) {
+ mCommands->rewind();
+ }
// The three methods below is intended to be called after the worker
// thread has joined, thus no extra synchronization is needed.
@@ -1643,59 +1955,72 @@
bool hasRetrogradeObservablePosition() const { return mRetrogradeObservablePosition; }
std::string getUnexpectedStateTransition() const { return mUnexpectedTransition; }
- bool done() override { return mNextCommand >= mCommands.size(); }
- StreamDescriptor::Command getNextCommand(int maxDataSize, int* actualSize) override {
- auto command = mCommands[mNextCommand++].first;
- if (command.getTag() == StreamDescriptor::Command::Tag::burst) {
- if (actualSize != nullptr) {
- // In the output scenario, reduce slightly the fmqByteCount to verify
- // that the HAL module always consumes all data from the MQ.
- if (maxDataSize > 1) maxDataSize--;
- *actualSize = maxDataSize;
+ bool done() override { return mCommands->done(); }
+ TransitionTrigger getNextTrigger(int maxDataSize, int* actualSize) override {
+ auto trigger = mCommands->getTrigger();
+ if (StreamDescriptor::Command* command = std::get_if<StreamDescriptor::Command>(&trigger);
+ command != nullptr) {
+ if (command->getTag() == StreamDescriptor::Command::Tag::burst) {
+ if (actualSize != nullptr) {
+ // In the output scenario, reduce slightly the fmqByteCount to verify
+ // that the HAL module always consumes all data from the MQ.
+ if (maxDataSize > 1) maxDataSize--;
+ *actualSize = maxDataSize;
+ }
+ command->set<StreamDescriptor::Command::Tag::burst>(maxDataSize);
+ } else {
+ if (actualSize != nullptr) *actualSize = 0;
}
- command.set<StreamDescriptor::Command::Tag::burst>(maxDataSize);
- } else {
- if (actualSize != nullptr) *actualSize = 0;
}
- return command;
+ return trigger;
}
bool interceptRawReply(const StreamDescriptor::Reply&) override { return false; }
bool processValidReply(const StreamDescriptor::Reply& reply) override {
- if (mPreviousFrames.has_value()) {
- if (reply.observable.frames > mPreviousFrames.value()) {
- mObservablePositionIncrease = true;
- } else if (reply.observable.frames < mPreviousFrames.value()) {
- mRetrogradeObservablePosition = true;
+ if (reply.observable.frames != StreamDescriptor::Position::UNKNOWN) {
+ if (mPreviousFrames.has_value()) {
+ if (reply.observable.frames > mPreviousFrames.value()) {
+ mObservablePositionIncrease = true;
+ } else if (reply.observable.frames < mPreviousFrames.value()) {
+ mRetrogradeObservablePosition = true;
+ }
}
+ mPreviousFrames = reply.observable.frames;
}
- mPreviousFrames = reply.observable.frames;
- const auto& lastCommandState = mCommands[mNextCommand - 1];
- if (lastCommandState.second != reply.state) {
- std::string s = std::string("Unexpected transition from the state ")
- .append(mPreviousState)
- .append(" to ")
- .append(toString(reply.state))
- .append(" caused by the command ")
- .append(lastCommandState.first.toString());
+ auto expected = mCommands->getExpectedStates();
+ if (expected.count(reply.state) == 0) {
+ std::string s =
+ std::string("Unexpected transition from the state ")
+ .append(mPreviousState.has_value() ? toString(mPreviousState.value())
+ : "<initial state>")
+ .append(" to ")
+ .append(toString(reply.state))
+ .append(" (expected one of ")
+ .append(::android::internal::ToString(expected))
+ .append(") caused by the ")
+ .append(toString(mCommands->getTrigger()));
LOG(ERROR) << __func__ << ": " << s;
mUnexpectedTransition = std::move(s);
return false;
}
+ mCommands->advance(reply.state);
+ mPreviousState = reply.state;
return true;
}
protected:
- const std::vector<CommandAndState>& mCommands;
- size_t mNextCommand = 0;
+ std::shared_ptr<StateSequence> mCommands;
+ std::optional<StreamDescriptor::State> mPreviousState;
std::optional<int64_t> mPreviousFrames;
- std::string mPreviousState = "<initial state>";
bool mObservablePositionIncrease = false;
bool mRetrogradeObservablePosition = false;
std::string mUnexpectedTransition;
};
-using NamedCommandSequence = std::pair<std::string, std::vector<CommandAndState>>;
+enum { NAMED_CMD_NAME, NAMED_CMD_DELAY_MS, NAMED_CMD_STREAM_TYPE, NAMED_CMD_CMDS };
+enum class StreamTypeFilter { ANY, SYNC, ASYNC };
+using NamedCommandSequence =
+ std::tuple<std::string, int, StreamTypeFilter, std::shared_ptr<StateSequence>>;
enum { PARAM_MODULE_NAME, PARAM_CMD_SEQ, PARAM_SETUP_SEQ };
using StreamIoTestParameters =
std::tuple<std::string /*moduleName*/, NamedCommandSequence, bool /*useSetupSequence2*/>;
@@ -1716,7 +2041,29 @@
}
for (const auto& portConfig : allPortConfigs) {
SCOPED_TRACE(portConfig.toString());
- const auto& commandsAndStates = std::get<PARAM_CMD_SEQ>(GetParam()).second;
+ const bool isNonBlocking =
+ IOTraits<Stream>::is_input
+ ? false
+ :
+ // TODO: Uncomment when support for asynchronous input is implemented.
+ /*isBitPositionFlagSet(
+ portConfig.flags.value().template get<AudioIoFlags::Tag::input>(),
+ AudioInputFlags::NON_BLOCKING) :*/
+ isBitPositionFlagSet(portConfig.flags.value()
+ .template get<AudioIoFlags::Tag::output>(),
+ AudioOutputFlags::NON_BLOCKING);
+ if (auto streamType =
+ std::get<NAMED_CMD_STREAM_TYPE>(std::get<PARAM_CMD_SEQ>(GetParam()));
+ (isNonBlocking && streamType == StreamTypeFilter::SYNC) ||
+ (!isNonBlocking && streamType == StreamTypeFilter::ASYNC)) {
+ continue;
+ }
+ WithDebugFlags delayTransientStates = WithDebugFlags::createNested(debug);
+ delayTransientStates.flags().streamTransientStateDelayMs =
+ std::get<NAMED_CMD_DELAY_MS>(std::get<PARAM_CMD_SEQ>(GetParam()));
+ ASSERT_NO_FATAL_FAILURE(delayTransientStates.SetUp(module.get()));
+ const auto& commandsAndStates =
+ std::get<NAMED_CMD_CMDS>(std::get<PARAM_CMD_SEQ>(GetParam()));
if (!std::get<PARAM_SETUP_SEQ>(GetParam())) {
ASSERT_NO_FATAL_FAILURE(RunStreamIoCommandsImplSeq1(portConfig, commandsAndStates));
} else {
@@ -1732,7 +2079,7 @@
// Set up a patch first, then open a stream.
void RunStreamIoCommandsImplSeq1(const AudioPortConfig& portConfig,
- const std::vector<CommandAndState>& commandsAndStates) {
+ std::shared_ptr<StateSequence> commandsAndStates) {
auto devicePorts = moduleConfig->getAttachedDevicesPortsForMixPort(
IOTraits<Stream>::is_input, portConfig);
ASSERT_FALSE(devicePorts.empty());
@@ -1743,9 +2090,12 @@
WithStream<Stream> stream(patch.getPortConfig(IOTraits<Stream>::is_input));
ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
StreamLogicDefaultDriver driver(commandsAndStates);
- typename IOTraits<Stream>::Worker worker(*stream.getContext(), &driver);
+ typename IOTraits<Stream>::Worker worker(*stream.getContext(), &driver,
+ stream.getEventReceiver());
+ LOG(DEBUG) << __func__ << ": starting worker...";
ASSERT_TRUE(worker.start());
+ LOG(DEBUG) << __func__ << ": joining worker...";
worker.join();
EXPECT_FALSE(worker.hasError()) << worker.getError();
EXPECT_EQ("", driver.getUnexpectedStateTransition());
@@ -1757,11 +2107,12 @@
// Open a stream, then set up a patch for it.
void RunStreamIoCommandsImplSeq2(const AudioPortConfig& portConfig,
- const std::vector<CommandAndState>& commandsAndStates) {
+ std::shared_ptr<StateSequence> commandsAndStates) {
WithStream<Stream> stream(portConfig);
ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
StreamLogicDefaultDriver driver(commandsAndStates);
- typename IOTraits<Stream>::Worker worker(*stream.getContext(), &driver);
+ typename IOTraits<Stream>::Worker worker(*stream.getContext(), &driver,
+ stream.getEventReceiver());
auto devicePorts = moduleConfig->getAttachedDevicesPortsForMixPort(
IOTraits<Stream>::is_input, portConfig);
@@ -1770,7 +2121,9 @@
WithAudioPatch patch(IOTraits<Stream>::is_input, stream.getPortConfig(), devicePortConfig);
ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get()));
+ LOG(DEBUG) << __func__ << ": starting worker...";
ASSERT_TRUE(worker.start());
+ LOG(DEBUG) << __func__ << ": joining worker...";
worker.join();
EXPECT_FALSE(worker.hasError()) << worker.getError();
EXPECT_EQ("", driver.getUnexpectedStateTransition());
@@ -1975,103 +2328,210 @@
android::PrintInstanceNameToString);
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioStreamOut);
-static const StreamDescriptor::Command kStartCommand =
- StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::start>(Void{});
-static const StreamDescriptor::Command kBurstCommand =
- StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::burst>(0);
-static const StreamDescriptor::Command kDrainCommand =
- StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::drain>(Void{});
-static const StreamDescriptor::Command kStandbyCommand =
- StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::standby>(Void{});
-static const StreamDescriptor::Command kPauseCommand =
- StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::pause>(Void{});
-static const StreamDescriptor::Command kFlushCommand =
- StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::flush>(Void{});
-static const NamedCommandSequence kReadOrWriteSeq =
- std::make_pair(std::string("ReadOrWrite"),
- std::vector<CommandAndState>{
- std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
- std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
- std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
- std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE)});
-static const NamedCommandSequence kDrainInSeq =
- std::make_pair(std::string("Drain"),
- std::vector<CommandAndState>{
- std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
- std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
- std::make_pair(kDrainCommand, StreamDescriptor::State::DRAINING),
- std::make_pair(kStartCommand, StreamDescriptor::State::ACTIVE),
- std::make_pair(kDrainCommand, StreamDescriptor::State::DRAINING),
- // TODO: This will need to be changed once DRAIN starts taking time.
- std::make_pair(kBurstCommand, StreamDescriptor::State::STANDBY)});
-static const NamedCommandSequence kDrainOutSeq =
- std::make_pair(std::string("Drain"),
- std::vector<CommandAndState>{
- std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
- std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
- // TODO: This will need to be changed once DRAIN starts taking time.
- std::make_pair(kDrainCommand, StreamDescriptor::State::IDLE)});
-// TODO: This will need to be changed once DRAIN starts taking time so we can pause it.
-static const NamedCommandSequence kDrainPauseOutSeq = std::make_pair(
- std::string("DrainPause"),
- std::vector<CommandAndState>{std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
- std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
- std::make_pair(kDrainCommand, StreamDescriptor::State::IDLE)});
-static const NamedCommandSequence kStandbySeq =
- std::make_pair(std::string("Standby"),
- std::vector<CommandAndState>{
- std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
- std::make_pair(kStandbyCommand, StreamDescriptor::State::STANDBY),
- // Perform a read or write in order to advance observable position
- // (this is verified by tests).
- std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
- std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE)});
+// This is the value used in test sequences for which the test needs to ensure
+// that the HAL stays in a transient state long enough to receive the next command.
+static const int kStreamTransientStateTransitionDelayMs = 3000;
+
+// TODO: Add async test cases for input once it is implemented.
+
+std::shared_ptr<StateSequence> makeBurstCommands(bool isSync, size_t burstCount) {
+ const auto burst =
+ isSync ? std::vector<StateTransition>{std::make_pair(kBurstCommand,
+ StreamDescriptor::State::ACTIVE)}
+ : std::vector<StateTransition>{
+ std::make_pair(kBurstCommand, StreamDescriptor::State::TRANSFERRING),
+ std::make_pair(kTransferReadyEvent, StreamDescriptor::State::ACTIVE)};
+ std::vector<StateTransition> result{
+ std::make_pair(kStartCommand, StreamDescriptor::State::IDLE)};
+ for (size_t i = 0; i < burstCount; ++i) {
+ result.insert(result.end(), burst.begin(), burst.end());
+ }
+ return std::make_shared<SmartStateSequence>(result);
+}
+static const NamedCommandSequence kReadSeq =
+ std::make_tuple(std::string("Read"), 0, StreamTypeFilter::ANY, makeBurstCommands(true, 3));
+static const NamedCommandSequence kWriteSyncSeq = std::make_tuple(
+ std::string("Write"), 0, StreamTypeFilter::SYNC, makeBurstCommands(true, 3));
+static const NamedCommandSequence kWriteAsyncSeq = std::make_tuple(
+ std::string("Write"), 0, StreamTypeFilter::ASYNC, makeBurstCommands(false, 3));
+
+std::shared_ptr<StateSequence> makeAsyncDrainCommands(bool isInput) {
+ return std::make_shared<SmartStateSequence>(std::vector<StateTransition>{
+ std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
+ std::make_pair(kBurstCommand, isInput ? StreamDescriptor::State::ACTIVE
+ : StreamDescriptor::State::TRANSFERRING),
+ std::make_pair(isInput ? kDrainInCommand : kDrainOutAllCommand,
+ StreamDescriptor::State::DRAINING),
+ isInput ? std::make_pair(kStartCommand, StreamDescriptor::State::ACTIVE)
+ : std::make_pair(kBurstCommand, StreamDescriptor::State::TRANSFERRING),
+ std::make_pair(isInput ? kDrainInCommand : kDrainOutAllCommand,
+ StreamDescriptor::State::DRAINING)});
+}
+static const NamedCommandSequence kWriteDrainAsyncSeq =
+ std::make_tuple(std::string("WriteDrain"), kStreamTransientStateTransitionDelayMs,
+ StreamTypeFilter::ASYNC, makeAsyncDrainCommands(false));
+static const NamedCommandSequence kDrainInSeq = std::make_tuple(
+ std::string("Drain"), 0, StreamTypeFilter::ANY, makeAsyncDrainCommands(true));
+
+std::shared_ptr<StateSequence> makeDrainOutCommands(bool isSync) {
+ return std::make_shared<SmartStateSequence>(std::vector<StateTransition>{
+ std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
+ std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
+ std::make_pair(kDrainOutAllCommand, StreamDescriptor::State::DRAINING),
+ std::make_pair(isSync ? TransitionTrigger(kGetStatusCommand)
+ : TransitionTrigger(kDrainReadyEvent),
+ StreamDescriptor::State::IDLE)});
+}
+static const NamedCommandSequence kDrainOutSyncSeq = std::make_tuple(
+ std::string("Drain"), 0, StreamTypeFilter::SYNC, makeDrainOutCommands(true));
+static const NamedCommandSequence kDrainOutAsyncSeq = std::make_tuple(
+ std::string("Drain"), 0, StreamTypeFilter::ASYNC, makeDrainOutCommands(false));
+
+std::shared_ptr<StateSequence> makeDrainOutPauseCommands(bool isSync) {
+ return std::make_shared<SmartStateSequence>(std::vector<StateTransition>{
+ std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
+ std::make_pair(kBurstCommand, isSync ? StreamDescriptor::State::ACTIVE
+ : StreamDescriptor::State::TRANSFERRING),
+ std::make_pair(kDrainOutAllCommand, StreamDescriptor::State::DRAINING),
+ std::make_pair(kPauseCommand, StreamDescriptor::State::DRAIN_PAUSED),
+ std::make_pair(kStartCommand, StreamDescriptor::State::DRAINING),
+ std::make_pair(kPauseCommand, StreamDescriptor::State::DRAIN_PAUSED),
+ std::make_pair(kBurstCommand, isSync ? StreamDescriptor::State::PAUSED
+ : StreamDescriptor::State::TRANSFER_PAUSED)});
+}
+static const NamedCommandSequence kDrainPauseOutSyncSeq =
+ std::make_tuple(std::string("DrainPause"), kStreamTransientStateTransitionDelayMs,
+ StreamTypeFilter::SYNC, makeDrainOutPauseCommands(true));
+static const NamedCommandSequence kDrainPauseOutAsyncSeq =
+ std::make_tuple(std::string("DrainPause"), kStreamTransientStateTransitionDelayMs,
+ StreamTypeFilter::ASYNC, makeDrainOutPauseCommands(false));
+
+// This sequence also verifies that the capture / presentation position is not reset on standby.
+std::shared_ptr<StateSequence> makeStandbyCommands(bool isInput, bool isSync) {
+ return std::make_shared<SmartStateSequence>(std::vector<StateTransition>{
+ std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
+ std::make_pair(kStandbyCommand, StreamDescriptor::State::STANDBY),
+ std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
+ std::make_pair(kBurstCommand, isInput || isSync
+ ? StreamDescriptor::State::ACTIVE
+ : StreamDescriptor::State::TRANSFERRING),
+ std::make_pair(kPauseCommand, isInput || isSync
+ ? StreamDescriptor::State::PAUSED
+ : StreamDescriptor::State::TRANSFER_PAUSED),
+ std::make_pair(kFlushCommand, isInput ? StreamDescriptor::State::STANDBY
+ : StreamDescriptor::State::IDLE),
+ std::make_pair(isInput ? kGetStatusCommand : kStandbyCommand, // no-op for input
+ StreamDescriptor::State::STANDBY),
+ std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
+ std::make_pair(kBurstCommand, isInput || isSync
+ ? StreamDescriptor::State::ACTIVE
+ : StreamDescriptor::State::TRANSFERRING)});
+}
+static const NamedCommandSequence kStandbyInSeq = std::make_tuple(
+ std::string("Standby"), 0, StreamTypeFilter::ANY, makeStandbyCommands(true, false));
+static const NamedCommandSequence kStandbyOutSyncSeq = std::make_tuple(
+ std::string("Standby"), 0, StreamTypeFilter::SYNC, makeStandbyCommands(false, true));
+static const NamedCommandSequence kStandbyOutAsyncSeq =
+ std::make_tuple(std::string("Standby"), kStreamTransientStateTransitionDelayMs,
+ StreamTypeFilter::ASYNC, makeStandbyCommands(false, false));
+
static const NamedCommandSequence kPauseInSeq =
- std::make_pair(std::string("Pause"),
- std::vector<CommandAndState>{
- std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
- std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
- std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED),
- std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
- std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED),
- std::make_pair(kFlushCommand, StreamDescriptor::State::STANDBY)});
-static const NamedCommandSequence kPauseOutSeq =
- std::make_pair(std::string("Pause"),
- std::vector<CommandAndState>{
- std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
- std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
- std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED),
- std::make_pair(kStartCommand, StreamDescriptor::State::ACTIVE),
- std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED),
- std::make_pair(kBurstCommand, StreamDescriptor::State::PAUSED),
- std::make_pair(kStartCommand, StreamDescriptor::State::ACTIVE),
- std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED)});
-static const NamedCommandSequence kFlushInSeq =
- std::make_pair(std::string("Flush"),
- std::vector<CommandAndState>{
- std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
- std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
- std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED),
- std::make_pair(kFlushCommand, StreamDescriptor::State::STANDBY)});
-static const NamedCommandSequence kFlushOutSeq = std::make_pair(
- std::string("Flush"),
- std::vector<CommandAndState>{std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
- std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
- std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED),
- std::make_pair(kFlushCommand, StreamDescriptor::State::IDLE)});
+ std::make_tuple(std::string("Pause"), 0, StreamTypeFilter::ANY,
+ std::make_shared<SmartStateSequence>(std::vector<StateTransition>{
+ std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
+ std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
+ std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED),
+ std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
+ std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED),
+ std::make_pair(kFlushCommand, StreamDescriptor::State::STANDBY)}));
+static const NamedCommandSequence kPauseOutSyncSeq =
+ std::make_tuple(std::string("Pause"), 0, StreamTypeFilter::SYNC,
+ std::make_shared<SmartStateSequence>(std::vector<StateTransition>{
+ std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
+ std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
+ std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED),
+ std::make_pair(kStartCommand, StreamDescriptor::State::ACTIVE),
+ std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED),
+ std::make_pair(kBurstCommand, StreamDescriptor::State::PAUSED),
+ std::make_pair(kStartCommand, StreamDescriptor::State::ACTIVE),
+ std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED)}));
+/* TODO: Figure out a better way for testing sync/async bursts
+static const NamedCommandSequence kPauseOutAsyncSeq = std::make_tuple(
+ std::string("Pause"), kStreamTransientStateTransitionDelayMs, StreamTypeFilter::ASYNC,
+ std::make_shared<StaticStateSequence>(std::vector<StateTransition>{
+ std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
+ std::make_pair(kBurstCommand, StreamDescriptor::State::TRANSFERRING),
+ std::make_pair(kPauseCommand, StreamDescriptor::State::TRANSFER_PAUSED),
+ std::make_pair(kStartCommand, StreamDescriptor::State::TRANSFERRING),
+ std::make_pair(kPauseCommand, StreamDescriptor::State::TRANSFER_PAUSED),
+ std::make_pair(kDrainOutAllCommand, StreamDescriptor::State::DRAIN_PAUSED),
+ std::make_pair(kBurstCommand, StreamDescriptor::State::TRANSFER_PAUSED)}));
+*/
+
+std::shared_ptr<StateSequence> makeFlushCommands(bool isInput, bool isSync) {
+ return std::make_shared<SmartStateSequence>(std::vector<StateTransition>{
+ std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
+ std::make_pair(kBurstCommand, isInput || isSync
+ ? StreamDescriptor::State::ACTIVE
+ : StreamDescriptor::State::TRANSFERRING),
+ std::make_pair(kPauseCommand, isInput || isSync
+ ? StreamDescriptor::State::PAUSED
+ : StreamDescriptor::State::TRANSFER_PAUSED),
+ std::make_pair(kFlushCommand, isInput ? StreamDescriptor::State::STANDBY
+ : StreamDescriptor::State::IDLE)});
+}
+static const NamedCommandSequence kFlushInSeq = std::make_tuple(
+ std::string("Flush"), 0, StreamTypeFilter::ANY, makeFlushCommands(true, false));
+static const NamedCommandSequence kFlushOutSyncSeq = std::make_tuple(
+ std::string("Flush"), 0, StreamTypeFilter::SYNC, makeFlushCommands(false, true));
+static const NamedCommandSequence kFlushOutAsyncSeq =
+ std::make_tuple(std::string("Flush"), kStreamTransientStateTransitionDelayMs,
+ StreamTypeFilter::ASYNC, makeFlushCommands(false, false));
+
+std::shared_ptr<StateSequence> makeDrainPauseFlushOutCommands(bool isSync) {
+ return std::make_shared<SmartStateSequence>(std::vector<StateTransition>{
+ std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
+ std::make_pair(kBurstCommand, isSync ? StreamDescriptor::State::ACTIVE
+ : StreamDescriptor::State::TRANSFERRING),
+ std::make_pair(kDrainOutAllCommand, StreamDescriptor::State::DRAINING),
+ std::make_pair(kPauseCommand, StreamDescriptor::State::DRAIN_PAUSED),
+ std::make_pair(kFlushCommand, StreamDescriptor::State::IDLE)});
+}
+static const NamedCommandSequence kDrainPauseFlushOutSyncSeq =
+ std::make_tuple(std::string("DrainPauseFlush"), kStreamTransientStateTransitionDelayMs,
+ StreamTypeFilter::SYNC, makeDrainPauseFlushOutCommands(true));
+static const NamedCommandSequence kDrainPauseFlushOutAsyncSeq =
+ std::make_tuple(std::string("DrainPauseFlush"), kStreamTransientStateTransitionDelayMs,
+ StreamTypeFilter::ASYNC, makeDrainPauseFlushOutCommands(false));
+
+// Note, this isn't the "official" enum printer, it is only used to make the test name suffix.
+std::string PrintStreamFilterToString(StreamTypeFilter filter) {
+ switch (filter) {
+ case StreamTypeFilter::ANY:
+ return "";
+ case StreamTypeFilter::SYNC:
+ return "Sync";
+ case StreamTypeFilter::ASYNC:
+ return "Async";
+ }
+ return std::string("Unknown").append(std::to_string(static_cast<int32_t>(filter)));
+}
std::string GetStreamIoTestName(const testing::TestParamInfo<StreamIoTestParameters>& info) {
return android::PrintInstanceNameToString(
testing::TestParamInfo<std::string>{std::get<PARAM_MODULE_NAME>(info.param),
info.index})
.append("_")
- .append(std::get<PARAM_CMD_SEQ>(info.param).first)
+ .append(std::get<NAMED_CMD_NAME>(std::get<PARAM_CMD_SEQ>(info.param)))
+ .append(PrintStreamFilterToString(
+ std::get<NAMED_CMD_STREAM_TYPE>(std::get<PARAM_CMD_SEQ>(info.param))))
.append("_SetupSeq")
.append(std::get<PARAM_SETUP_SEQ>(info.param) ? "2" : "1");
}
+
INSTANTIATE_TEST_SUITE_P(
AudioStreamIoInTest, AudioStreamIoIn,
testing::Combine(testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)),
- testing::Values(kReadOrWriteSeq, kDrainInSeq, kStandbySeq, kPauseInSeq,
+ testing::Values(kReadSeq, kDrainInSeq, kStandbyInSeq, kPauseInSeq,
kFlushInSeq),
testing::Values(false, true)),
GetStreamIoTestName);
@@ -2079,8 +2539,13 @@
INSTANTIATE_TEST_SUITE_P(
AudioStreamIoOutTest, AudioStreamIoOut,
testing::Combine(testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)),
- testing::Values(kReadOrWriteSeq, kDrainOutSeq, kDrainPauseOutSeq,
- kStandbySeq, kPauseOutSeq, kFlushOutSeq),
+ testing::Values(kWriteSyncSeq, kWriteAsyncSeq, kWriteDrainAsyncSeq,
+ kDrainOutSyncSeq, kDrainPauseOutSyncSeq,
+ kDrainPauseOutAsyncSeq, kStandbyOutSyncSeq,
+ kStandbyOutAsyncSeq,
+ kPauseOutSyncSeq, // kPauseOutAsyncSeq,
+ kFlushOutSyncSeq, kFlushOutAsyncSeq,
+ kDrainPauseFlushOutSyncSeq, kDrainPauseFlushOutAsyncSeq),
testing::Values(false, true)),
GetStreamIoTestName);
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioStreamIoOut);
@@ -2095,7 +2560,6 @@
void OnTestStart(const ::testing::TestInfo& test_info) override {
TraceTestState("Started", test_info);
}
-
void OnTestEnd(const ::testing::TestInfo& test_info) override {
TraceTestState("Completed", test_info);
}
@@ -2109,6 +2573,7 @@
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
+ android::base::SetMinimumLogSeverity(::android::base::DEBUG);
ABinderProcess_setThreadPoolMaxThreadCount(1);
ABinderProcess_startThreadPool();
return RUN_ALL_TESTS();
diff --git a/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp b/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp
index 505c54c..55d4e1d 100644
--- a/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp
+++ b/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp
@@ -957,10 +957,16 @@
ASSERT_NO_FATAL_FAILURE(createPatchIfNeeded());
const int presentationeEndPrecisionMs = 1000;
const int sampleRate = 44100;
+ // The duration of sine882hz3s.mp3 is: 3 seconds + (576 + 756) samples.
+ // This is a mono file, thus 1 frame = 1 sample for it.
+ const int fullTrackDurationMs = 3000 + (576 + 756) * 1000 / sampleRate;
const int significantSampleNumber = (presentationeEndPrecisionMs * sampleRate) / 1000;
+ // 'delay' is the amount of frames ignored at the beginning, 'padding' is the amount of frames
+ // ignored at the end of the track. Extra 1000 samples are requested for trimming to reduce the
+ // test running time.
const int delay = 576 + 1000;
const int padding = 756 + 1000;
- const int durationMs = 3000 - 44;
+ const int durationMs = fullTrackDurationMs - (delay + padding) * 1000 / sampleRate;
auto start = std::chrono::steady_clock::now();
auto callbacks = sp<OffloadCallbacks>::make();
std::mutex presentationEndLock;
diff --git a/automotive/remoteaccess/test_grpc_server/impl/src/main.cpp b/automotive/remoteaccess/test_grpc_server/impl/src/main.cpp
index 52698b5..d3f519c 100644
--- a/automotive/remoteaccess/test_grpc_server/impl/src/main.cpp
+++ b/automotive/remoteaccess/test_grpc_server/impl/src/main.cpp
@@ -28,20 +28,23 @@
using ::grpc::ServerBuilder;
using ::grpc::ServerWriter;
-void RunServer() {
- std::string serverAddress(GRPC_SERVICE_ADDRESS);
+void RunServer(const std::string& serviceAddr) {
std::shared_ptr<TestWakeupClientServiceImpl> service =
std::make_unique<TestWakeupClientServiceImpl>();
ServerBuilder builder;
- builder.AddListeningPort(serverAddress, grpc::InsecureServerCredentials());
+ builder.AddListeningPort(serviceAddr, grpc::InsecureServerCredentials());
builder.RegisterService(service.get());
std::unique_ptr<Server> server(builder.BuildAndStart());
- printf("Test Remote Access GRPC Server listening on %s\n", serverAddress.c_str());
+ printf("Test Remote Access GRPC Server listening on %s\n", serviceAddr.c_str());
server->Wait();
}
int main(int argc, char** argv) {
- RunServer();
+ std::string serviceAddr = GRPC_SERVICE_ADDRESS;
+ if (argc > 1) {
+ serviceAddr = argv[1];
+ }
+ RunServer(serviceAddr);
return 0;
}
diff --git a/compatibility_matrices/compatibility_matrix.current.xml b/compatibility_matrices/compatibility_matrix.current.xml
index 6a1a426..cf53a1d 100644
--- a/compatibility_matrices/compatibility_matrix.current.xml
+++ b/compatibility_matrices/compatibility_matrix.current.xml
@@ -319,7 +319,7 @@
</hal>
<hal format="aidl" optional="true">
<name>android.hardware.graphics.allocator</name>
- <version>1</version>
+ <version>1-2</version>
<interface>
<name>IAllocator</name>
<instance>default</instance>
@@ -344,7 +344,7 @@
<instance>default</instance>
</interface>
</hal>
- <hal format="hidl" optional="false">
+ <hal format="hidl" optional="true">
<name>android.hardware.graphics.mapper</name>
<!-- New, non-Go devices should use 4.0, tested in vts_treble_vintf_vendor_test -->
<version>2.1</version>
@@ -599,15 +599,6 @@
<instance>default</instance>
</interface>
</hal>
- <hal format="hidl" optional="true">
- <name>android.hardware.secure_element</name>
- <version>1.0-2</version>
- <interface>
- <name>ISecureElement</name>
- <regex-instance>eSE[1-9][0-9]*</regex-instance>
- <regex-instance>SIM[1-9][0-9]*</regex-instance>
- </interface>
- </hal>
<hal format="aidl" optional="true">
<name>android.hardware.secure_element</name>
<version>1</version>
diff --git a/graphics/Android.bp b/graphics/Android.bp
index b48844d..cdd81ed 100644
--- a/graphics/Android.bp
+++ b/graphics/Android.bp
@@ -19,14 +19,14 @@
cc_defaults {
name: "android.hardware.graphics.allocator-ndk_static",
static_libs: [
- "android.hardware.graphics.allocator-V1-ndk",
+ "android.hardware.graphics.allocator-V2-ndk",
],
}
cc_defaults {
name: "android.hardware.graphics.allocator-ndk_shared",
shared_libs: [
- "android.hardware.graphics.allocator-V1-ndk",
+ "android.hardware.graphics.allocator-V2-ndk",
],
}
diff --git a/graphics/allocator/aidl/Android.bp b/graphics/allocator/aidl/Android.bp
index 9edc555..66a7603 100644
--- a/graphics/allocator/aidl/Android.bp
+++ b/graphics/allocator/aidl/Android.bp
@@ -14,7 +14,7 @@
enabled: true,
support_system_process: true,
},
- vndk_use_version: "1",
+ vndk_use_version: "2",
srcs: ["android/hardware/graphics/allocator/*.aidl"],
imports: [
"android.hardware.common-V2",
diff --git a/graphics/allocator/aidl/aidl_api/android.hardware.graphics.allocator/current/android/hardware/graphics/allocator/BufferDescriptorInfo.aidl b/graphics/allocator/aidl/aidl_api/android.hardware.graphics.allocator/current/android/hardware/graphics/allocator/BufferDescriptorInfo.aidl
new file mode 100644
index 0000000..980e246
--- /dev/null
+++ b/graphics/allocator/aidl/aidl_api/android.hardware.graphics.allocator/current/android/hardware/graphics/allocator/BufferDescriptorInfo.aidl
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.graphics.allocator;
+@VintfStability
+parcelable BufferDescriptorInfo {
+ byte[128] name;
+ int width;
+ int height;
+ int layerCount;
+ android.hardware.graphics.common.PixelFormat format = android.hardware.graphics.common.PixelFormat.UNSPECIFIED;
+ android.hardware.graphics.common.BufferUsage usage = android.hardware.graphics.common.BufferUsage.CPU_READ_NEVER;
+ long reservedSize;
+}
diff --git a/graphics/allocator/aidl/aidl_api/android.hardware.graphics.allocator/current/android/hardware/graphics/allocator/IAllocator.aidl b/graphics/allocator/aidl/aidl_api/android.hardware.graphics.allocator/current/android/hardware/graphics/allocator/IAllocator.aidl
index fe0b0a2..48bef16 100644
--- a/graphics/allocator/aidl/aidl_api/android.hardware.graphics.allocator/current/android/hardware/graphics/allocator/IAllocator.aidl
+++ b/graphics/allocator/aidl/aidl_api/android.hardware.graphics.allocator/current/android/hardware/graphics/allocator/IAllocator.aidl
@@ -34,5 +34,11 @@
package android.hardware.graphics.allocator;
@VintfStability
interface IAllocator {
+ /**
+ * @deprecated As of android.hardware.graphics.allocator-V2, this is deprecated & replaced with allocate2
+ */
android.hardware.graphics.allocator.AllocationResult allocate(in byte[] descriptor, in int count);
+ android.hardware.graphics.allocator.AllocationResult allocate2(in android.hardware.graphics.allocator.BufferDescriptorInfo descriptor, in int count);
+ boolean isSupported(in android.hardware.graphics.allocator.BufferDescriptorInfo descriptor);
+ String getIMapperLibrarySuffix();
}
diff --git a/graphics/allocator/aidl/android/hardware/graphics/allocator/BufferDescriptorInfo.aidl b/graphics/allocator/aidl/android/hardware/graphics/allocator/BufferDescriptorInfo.aidl
new file mode 100644
index 0000000..ffc50b8
--- /dev/null
+++ b/graphics/allocator/aidl/android/hardware/graphics/allocator/BufferDescriptorInfo.aidl
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+package android.hardware.graphics.allocator;
+
+import android.hardware.graphics.common.BufferUsage;
+import android.hardware.graphics.common.PixelFormat;
+
+@VintfStability
+parcelable BufferDescriptorInfo {
+ /**
+ * The name of the buffer in ASCII. Useful for debugging/tracing.
+ */
+ byte[128] name;
+
+ /**
+ * The width specifies how many columns of pixels must be in the
+ * allocated buffer, but does not necessarily represent the offset in
+ * columns between the same column in adjacent rows. The rows may be
+ * padded.
+ */
+ int width;
+
+ /**
+ * The height specifies how many rows of pixels must be in the
+ * allocated buffer.
+ */
+ int height;
+
+ /**
+ * The number of image layers that must be in the allocated buffer.
+ */
+ int layerCount;
+
+ /**
+ * Buffer pixel format. See PixelFormat.aidl in graphics/common for
+ * valid values
+ */
+ PixelFormat format = PixelFormat.UNSPECIFIED;
+
+ /**
+ * Buffer usage mask; valid flags can be found in the definition of
+ * BufferUsage.aidl in graphics/common
+ */
+ BufferUsage usage = BufferUsage.CPU_READ_NEVER;
+
+ /**
+ * The size in bytes of the reserved region associated with the buffer.
+ * See getReservedRegion for more information.
+ */
+ long reservedSize;
+}
diff --git a/graphics/allocator/aidl/android/hardware/graphics/allocator/IAllocator.aidl b/graphics/allocator/aidl/android/hardware/graphics/allocator/IAllocator.aidl
index 92dfd4f..71cebd6 100644
--- a/graphics/allocator/aidl/android/hardware/graphics/allocator/IAllocator.aidl
+++ b/graphics/allocator/aidl/android/hardware/graphics/allocator/IAllocator.aidl
@@ -17,6 +17,7 @@
package android.hardware.graphics.allocator;
import android.hardware.graphics.allocator.AllocationResult;
+import android.hardware.graphics.allocator.BufferDescriptorInfo;
@VintfStability
interface IAllocator {
@@ -31,6 +32,43 @@
* @param count The number of buffers to allocate.
* @return An AllocationResult containing the result of the allocation
* @throws AllocationError on failure
+ * @deprecated As of android.hardware.graphics.allocator-V2, this is deprecated & replaced with
+ * allocate2
*/
AllocationResult allocate(in byte[] descriptor, in int count);
+
+ /**
+ * Allocates buffers with the properties specified by the descriptor.
+ *
+ * Allocations should be optimized for usage bits provided in the
+ * descriptor.
+ *
+ * @param descriptor Properties of the buffers to allocate. This must be
+ * obtained from IMapper::createDescriptor().
+ * @param count The number of buffers to allocate.
+ * @return An AllocationResult containing the result of the allocation
+ * @throws AllocationError on failure
+ */
+ AllocationResult allocate2(in BufferDescriptorInfo descriptor, in int count);
+
+ /**
+ * Test whether the given BufferDescriptorInfo is allocatable.
+ *
+ * If this function returns true, it means that a buffer with the given
+ * description can be allocated on this implementation, unless resource
+ * exhaustion occurs. If this function returns false, it means that the
+ * allocation of the given description will never succeed.
+ *
+ * @param description the description of the buffer
+ * @return supported whether the description is supported
+ */
+ boolean isSupported(in BufferDescriptorInfo descriptor);
+
+ /**
+ * Retrieve the library suffix to load for the IMapper SP-HAL. This library must implement the
+ * IMapper stable-C interface (android/hardware/graphics/mapper/IMapper.h).
+ *
+ * The library that will attempt to be loaded is "/vendor/lib[64]/hw/mapper.<imapper_suffix>.so"
+ */
+ String getIMapperLibrarySuffix();
}
diff --git a/graphics/allocator/aidl/vts/Android.bp b/graphics/allocator/aidl/vts/Android.bp
index a38af14..630ab2a 100644
--- a/graphics/allocator/aidl/vts/Android.bp
+++ b/graphics/allocator/aidl/vts/Android.bp
@@ -55,6 +55,7 @@
],
header_libs: [
"libhwui_internal_headers",
+ "libimapper_stablec",
],
cflags: [
"-Wall",
diff --git a/graphics/allocator/aidl/vts/VtsHalGraphicsAllocatorAidl_TargetTest.cpp b/graphics/allocator/aidl/vts/VtsHalGraphicsAllocatorAidl_TargetTest.cpp
index 59af5cf..09f1c15 100644
--- a/graphics/allocator/aidl/vts/VtsHalGraphicsAllocatorAidl_TargetTest.cpp
+++ b/graphics/allocator/aidl/vts/VtsHalGraphicsAllocatorAidl_TargetTest.cpp
@@ -25,7 +25,10 @@
#include <aidl/android/hardware/graphics/common/PixelFormat.h>
#include <aidlcommonsupport/NativeHandle.h>
#include <android/binder_manager.h>
+#include <android/dlext.h>
#include <android/hardware/graphics/mapper/4.0/IMapper.h>
+#include <android/hardware/graphics/mapper/IMapper.h>
+#include <dlfcn.h>
#include <gtest/gtest.h>
#include <hidl/GtestPrinter.h>
#include <hidl/ServiceManagement.h>
@@ -33,6 +36,7 @@
#include <renderthread/EglManager.h>
#include <utils/GLUtils.h>
#include <vndk/hardware_buffer.h>
+#include <vndksupport/linker.h>
#include <initializer_list>
#include <optional>
#include <string>
@@ -42,60 +46,70 @@
using namespace aidl::android::hardware::graphics::common;
using namespace android;
using namespace android::hardware;
-using namespace android::hardware::graphics::mapper::V4_0;
+using IMapper4 = android::hardware::graphics::mapper::V4_0::IMapper;
+using Error = android::hardware::graphics::mapper::V4_0::Error;
+using android::hardware::graphics::mapper::V4_0::BufferDescriptor;
using android::uirenderer::AutoEglImage;
using android::uirenderer::AutoGLFramebuffer;
using android::uirenderer::AutoSkiaGlTexture;
using android::uirenderer::renderthread::EglManager;
-static constexpr uint64_t pack(const std::initializer_list<BufferUsage>& usages) {
- uint64_t ret = 0;
- for (const auto u : usages) {
- ret |= static_cast<uint64_t>(u);
- }
- return ret;
+typedef AIMapper_Error (*AIMapper_loadIMapperFn)(AIMapper* _Nullable* _Nonnull outImplementation);
+
+inline BufferUsage operator|(BufferUsage lhs, BufferUsage rhs) {
+ using T = std::underlying_type_t<BufferUsage>;
+ return static_cast<BufferUsage>(static_cast<T>(lhs) | static_cast<T>(rhs));
}
-static constexpr hardware::graphics::common::V1_2::PixelFormat cast(PixelFormat format) {
- return static_cast<hardware::graphics::common::V1_2::PixelFormat>(format);
+inline BufferUsage& operator|=(BufferUsage& lhs, BufferUsage rhs) {
+ lhs = lhs | rhs;
+ return lhs;
}
+static IMapper4::BufferDescriptorInfo convert(const BufferDescriptorInfo& info) {
+ return IMapper4::BufferDescriptorInfo{
+ .name{reinterpret_cast<const char*>(info.name.data())},
+ .width = static_cast<uint32_t>(info.width),
+ .height = static_cast<uint32_t>(info.height),
+ .layerCount = static_cast<uint32_t>(info.layerCount),
+ .format = static_cast<hardware::graphics::common::V1_2::PixelFormat>(info.format),
+ .usage = static_cast<uint64_t>(info.usage),
+ .reservedSize = 0,
+ };
+}
+
+class GraphicsTestsBase;
+
class BufferHandle {
- sp<IMapper> mMapper;
+ GraphicsTestsBase& mTestBase;
native_handle_t* mRawHandle;
bool mImported = false;
uint32_t mStride;
- const IMapper::BufferDescriptorInfo mInfo;
+ const BufferDescriptorInfo mInfo;
BufferHandle(const BufferHandle&) = delete;
void operator=(const BufferHandle&) = delete;
public:
- BufferHandle(const sp<IMapper> mapper, native_handle_t* handle, bool imported, uint32_t stride,
- const IMapper::BufferDescriptorInfo& info)
- : mMapper(mapper), mRawHandle(handle), mImported(imported), mStride(stride), mInfo(info) {}
+ BufferHandle(GraphicsTestsBase& testBase, native_handle_t* handle, bool imported,
+ uint32_t stride, const BufferDescriptorInfo& info)
+ : mTestBase(testBase),
+ mRawHandle(handle),
+ mImported(imported),
+ mStride(stride),
+ mInfo(info) {}
- ~BufferHandle() {
- if (mRawHandle == nullptr) return;
-
- if (mImported) {
- Error error = mMapper->freeBuffer(mRawHandle);
- EXPECT_EQ(Error::NONE, error) << "failed to free buffer " << mRawHandle;
- } else {
- native_handle_close(mRawHandle);
- native_handle_delete(mRawHandle);
- }
- }
+ ~BufferHandle();
uint32_t stride() const { return mStride; }
AHardwareBuffer_Desc describe() const {
return {
- .width = mInfo.width,
- .height = mInfo.height,
- .layers = mInfo.layerCount,
+ .width = static_cast<uint32_t>(mInfo.width),
+ .height = static_cast<uint32_t>(mInfo.height),
+ .layers = static_cast<uint32_t>(mInfo.layerCount),
.format = static_cast<uint32_t>(mInfo.format),
- .usage = mInfo.usage,
+ .usage = static_cast<uint64_t>(mInfo.usage),
.stride = stride(),
.rfu0 = 0,
.rfu1 = 0,
@@ -114,25 +128,43 @@
class GraphicsTestsBase {
private:
+ friend class BufferHandle;
+ int32_t mIAllocatorVersion = 1;
std::shared_ptr<IAllocator> mAllocator;
- sp<IMapper> mMapper;
+ sp<IMapper4> mMapper4;
+ AIMapper* mAIMapper = nullptr;
protected:
- void Initialize(std::string allocatorService, std::string mapperService) {
+ void Initialize(std::string allocatorService) {
mAllocator = IAllocator::fromBinder(
ndk::SpAIBinder(AServiceManager_checkService(allocatorService.c_str())));
- mMapper = IMapper::getService(mapperService);
+ ASSERT_TRUE(mAllocator->getInterfaceVersion(&mIAllocatorVersion).isOk());
+ if (mIAllocatorVersion >= 2) {
+ std::string mapperSuffix;
+ auto status = mAllocator->getIMapperLibrarySuffix(&mapperSuffix);
+ ASSERT_TRUE(status.isOk());
+ std::string lib_name = "mapper." + mapperSuffix + ".so";
+ void* so = android_load_sphal_library(lib_name.c_str(), RTLD_LOCAL | RTLD_NOW);
+ ASSERT_NE(nullptr, so) << "Failed to load " << lib_name;
+ auto loadIMapper = (AIMapper_loadIMapperFn)dlsym(so, "AIMapper_loadIMapper");
+ ASSERT_NE(nullptr, loadIMapper) << "AIMapper_locaIMapper missing from " << lib_name;
+ ASSERT_EQ(AIMAPPER_ERROR_NONE, loadIMapper(&mAIMapper));
+ ASSERT_NE(mAIMapper, nullptr);
+ } else {
+ // Don't have IMapper 5, fall back to IMapper 4
+ mMapper4 = IMapper4::getService();
+ ASSERT_NE(nullptr, mMapper4.get()) << "failed to get mapper service";
+ ASSERT_FALSE(mMapper4->isRemote()) << "mapper is not in passthrough mode";
+ }
ASSERT_NE(nullptr, mAllocator.get()) << "failed to get allocator service";
- ASSERT_NE(nullptr, mMapper.get()) << "failed to get mapper service";
- ASSERT_FALSE(mMapper->isRemote()) << "mapper is not in passthrough mode";
}
- public:
- BufferDescriptor createDescriptor(const IMapper::BufferDescriptorInfo& descriptorInfo) {
+ private:
+ BufferDescriptor createDescriptor(const BufferDescriptorInfo& descriptorInfo) {
BufferDescriptor descriptor;
- mMapper->createDescriptor(
- descriptorInfo, [&](const auto& tmpError, const auto& tmpDescriptor) {
+ mMapper4->createDescriptor(
+ convert(descriptorInfo), [&](const auto& tmpError, const auto& tmpDescriptor) {
ASSERT_EQ(Error::NONE, tmpError) << "failed to create descriptor";
descriptor = tmpDescriptor;
});
@@ -140,14 +172,22 @@
return descriptor;
}
- std::unique_ptr<BufferHandle> allocate(const IMapper::BufferDescriptorInfo& descriptorInfo) {
- auto descriptor = createDescriptor(descriptorInfo);
- if (::testing::Test::HasFatalFailure()) {
- return nullptr;
- }
-
+ public:
+ std::unique_ptr<BufferHandle> allocate(const BufferDescriptorInfo& descriptorInfo) {
AllocationResult result;
- auto status = mAllocator->allocate(descriptor, 1, &result);
+ ::ndk::ScopedAStatus status;
+ if (mIAllocatorVersion >= 2) {
+ status = mAllocator->allocate2(descriptorInfo, 1, &result);
+ } else {
+ auto descriptor = createDescriptor(descriptorInfo);
+ if (::testing::Test::HasFatalFailure()) {
+ return nullptr;
+ }
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ status = mAllocator->allocate(descriptor, 1, &result);
+#pragma clang diagnostic pop // deprecation
+ }
if (!status.isOk()) {
status_t error = status.getExceptionCode();
if (error == EX_SERVICE_SPECIFIC) {
@@ -158,28 +198,48 @@
}
return nullptr;
} else {
- return std::make_unique<BufferHandle>(mMapper, dupFromAidl(result.buffers[0]), false,
+ return std::make_unique<BufferHandle>(*this, dupFromAidl(result.buffers[0]), false,
result.stride, descriptorInfo);
}
}
- bool isSupported(const IMapper::BufferDescriptorInfo& descriptorInfo) {
+ bool isSupported(const BufferDescriptorInfo& descriptorInfo) {
bool ret = false;
- EXPECT_TRUE(mMapper->isSupported(descriptorInfo,
- [&](auto error, bool supported) {
- ASSERT_EQ(Error::NONE, error);
- ret = supported;
- })
- .isOk());
+ if (mIAllocatorVersion >= 2) {
+ EXPECT_TRUE(mAllocator->isSupported(descriptorInfo, &ret).isOk());
+ } else {
+ EXPECT_TRUE(mMapper4->isSupported(convert(descriptorInfo),
+ [&](auto error, bool supported) {
+ ASSERT_EQ(Error::NONE, error);
+ ret = supported;
+ })
+ .isOk());
+ }
return ret;
}
};
-class GraphicsAllocatorAidlTests
- : public GraphicsTestsBase,
- public ::testing::TestWithParam<std::tuple<std::string, std::string>> {
+BufferHandle::~BufferHandle() {
+ if (mRawHandle == nullptr) return;
+
+ if (mImported) {
+ if (mTestBase.mAIMapper) {
+ AIMapper_Error error = mTestBase.mAIMapper->v5.freeBuffer(mRawHandle);
+ EXPECT_EQ(AIMAPPER_ERROR_NONE, error);
+ } else {
+ Error error = mTestBase.mMapper4->freeBuffer(mRawHandle);
+ EXPECT_EQ(Error::NONE, error) << "failed to free buffer " << mRawHandle;
+ }
+ } else {
+ native_handle_close(mRawHandle);
+ native_handle_delete(mRawHandle);
+ }
+}
+
+class GraphicsAllocatorAidlTests : public GraphicsTestsBase,
+ public ::testing::TestWithParam<std::string> {
public:
- void SetUp() override { Initialize(std::get<0>(GetParam()), std::get<1>(GetParam())); }
+ void SetUp() override { Initialize(GetParam()); }
void TearDown() override {}
};
@@ -191,22 +251,22 @@
class GraphicsFrontBufferTests
: public GraphicsTestsBase,
- public ::testing::TestWithParam<std::tuple<std::string, std::string, FlushMethod>> {
+ public ::testing::TestWithParam<std::tuple<std::string, FlushMethod>> {
private:
EglManager eglManager;
std::function<void(EglManager&)> flush;
public:
void SetUp() override {
- Initialize(std::get<0>(GetParam()), std::get<1>(GetParam()));
- flush = std::get<2>(GetParam()).func;
+ Initialize(std::get<0>(GetParam()));
+ flush = std::get<1>(GetParam()).func;
eglManager.initialize();
}
void TearDown() override { eglManager.destroy(); }
void fillWithGpu(AHardwareBuffer* buffer, float red, float green, float blue, float alpha) {
- const EGLClientBuffer clientBuffer = eglGetNativeClientBufferANDROID(buffer);
+ EGLClientBuffer clientBuffer = eglGetNativeClientBufferANDROID(buffer);
AutoEglImage eglImage(eglManager.eglDisplay(), clientBuffer);
AutoSkiaGlTexture glTexture;
AutoGLFramebuffer glFbo;
@@ -235,26 +295,14 @@
}
};
-TEST_P(GraphicsAllocatorAidlTests, CreateDescriptorBasic) {
- ASSERT_NO_FATAL_FAILURE(createDescriptor({
- .name = "CPU_8888",
- .width = 64,
- .height = 64,
- .layerCount = 1,
- .format = cast(PixelFormat::RGBA_8888),
- .usage = pack({BufferUsage::CPU_WRITE_OFTEN, BufferUsage::CPU_READ_OFTEN}),
- .reservedSize = 0,
- }));
-}
-
TEST_P(GraphicsAllocatorAidlTests, CanAllocate) {
auto buffer = allocate({
- .name = "CPU_8888",
+ .name = {"CPU_8888"},
.width = 64,
.height = 64,
.layerCount = 1,
- .format = cast(PixelFormat::RGBA_8888),
- .usage = pack({BufferUsage::CPU_WRITE_OFTEN, BufferUsage::CPU_READ_OFTEN}),
+ .format = PixelFormat::RGBA_8888,
+ .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN,
.reservedSize = 0,
});
ASSERT_NE(nullptr, buffer.get());
@@ -262,14 +310,14 @@
}
TEST_P(GraphicsFrontBufferTests, FrontBufferGpuToCpu) {
- IMapper::BufferDescriptorInfo info{
- .name = "CPU_8888",
+ BufferDescriptorInfo info{
+ .name = {"CPU_8888"},
.width = 64,
.height = 64,
.layerCount = 1,
- .format = cast(PixelFormat::RGBA_8888),
- .usage = pack({BufferUsage::GPU_RENDER_TARGET, BufferUsage::CPU_READ_OFTEN,
- BufferUsage::FRONT_BUFFER}),
+ .format = PixelFormat::RGBA_8888,
+ .usage = BufferUsage::GPU_RENDER_TARGET | BufferUsage::CPU_READ_OFTEN |
+ BufferUsage::FRONT_BUFFER,
.reservedSize = 0,
};
const bool supported = isSupported(info);
@@ -304,14 +352,14 @@
}
TEST_P(GraphicsFrontBufferTests, FrontBufferGpuToGpu) {
- IMapper::BufferDescriptorInfo info{
- .name = "CPU_8888",
+ BufferDescriptorInfo info{
+ .name = {"CPU_8888"},
.width = 64,
.height = 64,
.layerCount = 1,
- .format = cast(PixelFormat::RGBA_8888),
- .usage = pack({BufferUsage::GPU_RENDER_TARGET, BufferUsage::GPU_TEXTURE,
- BufferUsage::FRONT_BUFFER}),
+ .format = PixelFormat::RGBA_8888,
+ .usage = BufferUsage::GPU_RENDER_TARGET | BufferUsage::GPU_TEXTURE |
+ BufferUsage::FRONT_BUFFER,
.reservedSize = 0,
};
const bool supported = isSupported(info);
@@ -344,11 +392,9 @@
}
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GraphicsAllocatorAidlTests);
-INSTANTIATE_TEST_CASE_P(
- PerInstance, GraphicsAllocatorAidlTests,
- testing::Combine(testing::ValuesIn(getAidlHalInstanceNames(IAllocator::descriptor)),
- testing::ValuesIn(getAllHalInstanceNames(IMapper::descriptor))),
- PrintInstanceTupleNameToString<>);
+INSTANTIATE_TEST_CASE_P(PerInstance, GraphicsAllocatorAidlTests,
+ testing::ValuesIn(getAidlHalInstanceNames(IAllocator::descriptor)),
+ PrintInstanceNameToString);
const auto FlushMethodsValues = testing::Values(
FlushMethod{"glFinish", [](EglManager&) { glFinish(); }},
@@ -362,7 +408,7 @@
}},
FlushMethod{"eglClientWaitSync", [](EglManager& eglManager) {
EGLDisplay display = eglManager.eglDisplay();
- EGLSyncKHR fence = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, NULL);
+ EGLSyncKHR fence = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, nullptr);
eglClientWaitSyncKHR(display, fence, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR,
EGL_FOREVER_KHR);
eglDestroySyncKHR(display, fence);
@@ -371,9 +417,8 @@
INSTANTIATE_TEST_CASE_P(
PerInstance, GraphicsFrontBufferTests,
testing::Combine(testing::ValuesIn(getAidlHalInstanceNames(IAllocator::descriptor)),
- testing::ValuesIn(getAllHalInstanceNames(IMapper::descriptor)),
FlushMethodsValues),
[](auto info) -> std::string {
- std::string name = std::to_string(info.index) + "/" + std::get<2>(info.param).name;
+ std::string name = std::to_string(info.index) + "/" + std::get<1>(info.param).name;
return Sanitize(name);
- });
+ });
\ No newline at end of file
diff --git a/graphics/mapper/stable-c/Android.bp b/graphics/mapper/stable-c/Android.bp
new file mode 100644
index 0000000..c03f67e
--- /dev/null
+++ b/graphics/mapper/stable-c/Android.bp
@@ -0,0 +1,104 @@
+/**
+ * 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.
+ */
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "hardware_interfaces_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["hardware_interfaces_license"],
+}
+
+cc_library_headers {
+ name: "libimapper_stablec",
+ export_include_dirs: ["include"],
+ vendor_available: true,
+ header_libs: [
+ "libarect_headers",
+ ],
+ export_header_lib_headers: [
+ "libarect_headers",
+ ],
+}
+
+cc_library_headers {
+ name: "libimapper_providerutils",
+ vendor_available: true,
+ export_include_dirs: ["implutils/include"],
+ header_libs: [
+ "libbase_headers",
+ "libimapper_stablec",
+ ],
+ export_header_lib_headers: [
+ "libbase_headers",
+ "libimapper_stablec",
+ ],
+}
+
+cc_test {
+ name: "libimapper_providerutils_tests",
+ defaults: [
+ "android.hardware.graphics.allocator-ndk_shared",
+ "android.hardware.graphics.common-ndk_shared",
+ ],
+ header_libs: [
+ "libimapper_providerutils",
+ ],
+ srcs: [
+ "implutils/impltests.cpp",
+ ],
+ visibility: [":__subpackages__"],
+ cpp_std: "experimental",
+}
+
+cc_test {
+ name: "VtsHalGraphicsMapperStableC_TargetTest",
+ cpp_std: "experimental",
+ defaults: [
+ "VtsHalTargetTestDefaults",
+ "use_libaidlvintf_gtest_helper_static",
+ "android.hardware.graphics.allocator-ndk_shared",
+ "android.hardware.graphics.common-ndk_shared",
+ ],
+ srcs: [
+ "vts/VtsHalGraphicsMapperStableC_TargetTest.cpp",
+ ],
+
+ shared_libs: [
+ "libbinder_ndk",
+ "libbase",
+ "libsync",
+ "libvndksupport",
+ ],
+ static_libs: [
+ "libaidlcommonsupport",
+ "libgralloctypes",
+ "libgtest",
+ ],
+ header_libs: [
+ "libimapper_stablec",
+ "libimapper_providerutils",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
+}
diff --git a/graphics/mapper/stable-c/implutils/impltests.cpp b/graphics/mapper/stable-c/implutils/impltests.cpp
new file mode 100644
index 0000000..9c5d70b
--- /dev/null
+++ b/graphics/mapper/stable-c/implutils/impltests.cpp
@@ -0,0 +1,314 @@
+/*
+ * 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include <android/hardware/graphics/mapper/utils/IMapperMetadataTypes.h>
+#include <android/hardware/graphics/mapper/utils/IMapperProvider.h>
+#include <vector>
+
+using namespace ::android::hardware::graphics::mapper;
+using namespace ::aidl::android::hardware::graphics::common;
+
+// These tests are primarily interested in hitting all the different *types* that can be
+// serialized/deserialized than in exhaustively testing all the StandardMetadataTypes.
+// Exhaustive testing of the actual metadata types is relegated for IMapper's VTS suite
+// where meaning & correctness of values are more narrowly defined (eg, read-only values)
+
+TEST(Metadata, setGetBufferId) {
+ using BufferId = StandardMetadata<StandardMetadataType::BUFFER_ID>::value;
+
+ std::vector<char> buffer;
+ buffer.resize(12, 0);
+ *reinterpret_cast<int64_t*>(buffer.data()) = 42;
+
+ EXPECT_EQ(8, BufferId::encode(18, buffer.data(), 0));
+ EXPECT_EQ(42, *reinterpret_cast<int64_t*>(buffer.data()));
+ EXPECT_EQ(8, BufferId::encode(18, buffer.data(), buffer.size()));
+ EXPECT_EQ(18, *reinterpret_cast<int64_t*>(buffer.data()));
+ EXPECT_FALSE(BufferId::decode(buffer.data(), 0));
+ auto read = BufferId::decode(buffer.data(), buffer.size());
+ EXPECT_TRUE(read.has_value());
+ EXPECT_EQ(18, read.value_or(0));
+}
+
+TEST(Metadata, setGetDataspace) {
+ using DataspaceValue = StandardMetadata<StandardMetadataType::DATASPACE>::value;
+ using intType = std::underlying_type_t<Dataspace>;
+ std::vector<char> buffer;
+ buffer.resize(12, 0);
+
+ EXPECT_EQ(4, DataspaceValue::encode(Dataspace::BT2020, buffer.data(), 0));
+ EXPECT_EQ(0, *reinterpret_cast<intType*>(buffer.data()));
+ EXPECT_EQ(4, DataspaceValue::encode(Dataspace::BT2020, buffer.data(), buffer.size()));
+ EXPECT_EQ(static_cast<intType>(Dataspace::BT2020), *reinterpret_cast<intType*>(buffer.data()));
+ EXPECT_FALSE(DataspaceValue::decode(buffer.data(), 0));
+ auto read = DataspaceValue::decode(buffer.data(), buffer.size());
+ ASSERT_TRUE(read.has_value());
+ EXPECT_EQ(Dataspace::BT2020, *read);
+}
+
+TEST(Metadata, setGetValidName) {
+ using NameValue = StandardMetadata<StandardMetadataType::NAME>::value;
+
+ std::vector<char> buffer;
+ buffer.resize(100, 'a');
+ buffer[buffer.size() - 1] = '\0';
+
+ // len("Hello") + sizeof(int64)
+ constexpr int expectedSize = 5 + sizeof(int64_t);
+ EXPECT_EQ(expectedSize, NameValue::encode("Hello", buffer.data(), buffer.size()));
+ EXPECT_EQ(5, *reinterpret_cast<int64_t*>(buffer.data()));
+ // Verify didn't write past the end of the desired size
+ EXPECT_EQ('a', buffer[expectedSize]);
+
+ auto readValue = NameValue::decode(buffer.data(), buffer.size());
+ ASSERT_TRUE(readValue.has_value());
+ EXPECT_EQ(5, readValue->length());
+ EXPECT_EQ("Hello", *readValue);
+}
+
+TEST(Metadata, setGetInvalidName) {
+ using NameValue = StandardMetadata<StandardMetadataType::NAME>::value;
+
+ std::vector<char> buffer;
+ buffer.resize(12, 'a');
+ buffer[buffer.size() - 1] = '\0';
+
+ // len("This is a long string") + sizeof(int64)
+ constexpr int expectedSize = 21 + sizeof(int64_t);
+ EXPECT_EQ(expectedSize,
+ NameValue::encode("This is a long string", buffer.data(), buffer.size()));
+ EXPECT_EQ(21, *reinterpret_cast<int64_t*>(buffer.data()));
+ // Verify didn't write the too-long string
+ EXPECT_EQ('a', buffer[9]);
+ EXPECT_EQ('\0', buffer[buffer.size() - 1]);
+
+ auto readValue = NameValue::decode(buffer.data(), buffer.size());
+ EXPECT_FALSE(readValue.has_value());
+ readValue = NameValue::decode(buffer.data(), 0);
+ ASSERT_FALSE(readValue.has_value());
+}
+
+TEST(Metadata, wouldOverflowName) {
+ using NameValue = StandardMetadata<StandardMetadataType::NAME>::value;
+ std::vector<char> buffer(100, 0);
+
+ // int_max + sizeof(int64) overflows int32
+ std::string_view bad_string{"badbeef", std::numeric_limits<int32_t>::max()};
+ EXPECT_EQ(-AIMAPPER_ERROR_BAD_VALUE,
+ NameValue::encode(bad_string, buffer.data(), buffer.size()));
+
+ // check barely overflows
+ bad_string = std::string_view{"badbeef", std::numeric_limits<int32_t>::max() - 7};
+ EXPECT_EQ(-AIMAPPER_ERROR_BAD_VALUE,
+ NameValue::encode(bad_string, buffer.data(), buffer.size()));
+}
+
+TEST(Metadata, setGetCompression) {
+ using CompressionValue = StandardMetadata<StandardMetadataType::COMPRESSION>::value;
+ ExtendableType myCompression{"bestest_compression_ever", 42};
+ std::vector<char> buffer(100, '\0');
+ const int expectedSize = myCompression.name.length() + sizeof(int64_t) + sizeof(int64_t);
+ EXPECT_EQ(expectedSize, CompressionValue::encode(myCompression, buffer.data(), 0));
+ EXPECT_EQ(0, buffer[0]);
+ EXPECT_EQ(expectedSize, CompressionValue::encode(myCompression, buffer.data(), buffer.size()));
+ EXPECT_EQ(myCompression.name.length(), *reinterpret_cast<int64_t*>(buffer.data()));
+ EXPECT_FALSE(CompressionValue::decode(buffer.data(), 0).has_value());
+ auto read = CompressionValue::decode(buffer.data(), buffer.size());
+ ASSERT_TRUE(read.has_value());
+ EXPECT_EQ(myCompression, read.value());
+}
+
+TEST(Metadata, setGetPlaneLayout) {
+ using PlaneLayoutValue = StandardMetadata<StandardMetadataType::PLANE_LAYOUTS>::value;
+ PlaneLayout myPlaneLayout;
+ myPlaneLayout.offsetInBytes = 10;
+ myPlaneLayout.sampleIncrementInBits = 11;
+ myPlaneLayout.strideInBytes = 12;
+ myPlaneLayout.widthInSamples = 13;
+ myPlaneLayout.heightInSamples = 14;
+ myPlaneLayout.totalSizeInBytes = 15;
+ myPlaneLayout.horizontalSubsampling = 16;
+ myPlaneLayout.verticalSubsampling = 17;
+
+ myPlaneLayout.components.resize(3);
+ for (int i = 0; i < myPlaneLayout.components.size(); i++) {
+ auto& it = myPlaneLayout.components[i];
+ it.type = ExtendableType{"Plane ID", 40 + i};
+ it.offsetInBits = 20 + i;
+ it.sizeInBits = 30 + i;
+ }
+
+ std::vector<PlaneLayout> layouts{myPlaneLayout, PlaneLayout{}};
+
+ std::vector<char> buffer(5000, '\0');
+ constexpr int componentSize = 8 + (4 * sizeof(int64_t));
+ constexpr int firstLayoutSize = (8 + 1) * sizeof(int64_t) + (3 * componentSize);
+ constexpr int secondLayoutSize = (8 + 1) * sizeof(int64_t);
+ constexpr int expectedSize = firstLayoutSize + secondLayoutSize + sizeof(int64_t);
+ EXPECT_EQ(expectedSize, PlaneLayoutValue::encode(layouts, buffer.data(), 0));
+ EXPECT_EQ(0, buffer[0]);
+ EXPECT_EQ(expectedSize, PlaneLayoutValue::encode(layouts, buffer.data(), buffer.size()));
+ EXPECT_EQ(3, reinterpret_cast<int64_t*>(buffer.data())[1]);
+ EXPECT_EQ(8, reinterpret_cast<int64_t*>(buffer.data())[2]);
+ EXPECT_EQ(40, reinterpret_cast<int64_t*>(buffer.data())[4]);
+ EXPECT_EQ(31, reinterpret_cast<int64_t*>(buffer.data())[11]);
+ EXPECT_EQ(22, reinterpret_cast<int64_t*>(buffer.data())[15]);
+ EXPECT_EQ(10, reinterpret_cast<int64_t*>(buffer.data())[17]);
+ EXPECT_EQ(11, reinterpret_cast<int64_t*>(buffer.data())[18]);
+ EXPECT_FALSE(PlaneLayoutValue::decode(buffer.data(), 0).has_value());
+ auto read = PlaneLayoutValue::decode(buffer.data(), buffer.size());
+ ASSERT_TRUE(read.has_value());
+ EXPECT_EQ(layouts, *read);
+}
+
+TEST(Metadata, setGetRects) {
+ using RectsValue = StandardMetadata<StandardMetadataType::CROP>::value;
+ std::vector<uint8_t> buffer(500, 0);
+ std::vector<Rect> cropRects{2};
+ cropRects[0] = Rect{10, 11, 12, 13};
+ cropRects[1] = Rect{20, 21, 22, 23};
+
+ constexpr int expectedSize = sizeof(int64_t) + (8 * sizeof(int32_t));
+ EXPECT_EQ(expectedSize, RectsValue::encode(cropRects, buffer.data(), buffer.size()));
+ EXPECT_EQ(2, reinterpret_cast<int64_t*>(buffer.data())[0]);
+ EXPECT_EQ(10, reinterpret_cast<int32_t*>(buffer.data())[2]);
+ auto read = RectsValue::decode(buffer.data(), buffer.size());
+ ASSERT_TRUE(read.has_value());
+ EXPECT_EQ(cropRects.size(), read->size());
+ EXPECT_EQ(cropRects, *read);
+}
+
+TEST(Metadata, setGetSmpte2086) {
+ using Smpte2086Value = StandardMetadata<StandardMetadataType::SMPTE2086>::value;
+ Smpte2086 source;
+ source.minLuminance = 12.335f;
+ source.maxLuminance = 452.889f;
+ source.whitePoint = XyColor{-6.f, -9.f};
+ source.primaryRed = XyColor{.1f, .2f};
+ source.primaryGreen = XyColor{.3f, .4f};
+ source.primaryBlue = XyColor{.5f, .6f};
+
+ constexpr int expectedSize = 10 * sizeof(float);
+ std::vector<uint8_t> buffer(500, 0);
+ EXPECT_EQ(expectedSize, Smpte2086Value::encode(source, buffer.data(), buffer.size()));
+ auto read = Smpte2086Value::decode(buffer.data(), buffer.size());
+ ASSERT_TRUE(read.has_value());
+ ASSERT_TRUE(read->has_value());
+ EXPECT_EQ(source, read->value());
+
+ // A valid encoding of a nullopt
+ read = Smpte2086Value::decode(nullptr, 0);
+ ASSERT_TRUE(read.has_value());
+ EXPECT_FALSE(read->has_value());
+}
+
+TEST(Metadata, setGetCta861_3) {
+ using Cta861_3Value = StandardMetadata<StandardMetadataType::CTA861_3>::value;
+ Cta861_3 source;
+ source.maxFrameAverageLightLevel = 244.55f;
+ source.maxContentLightLevel = 202.202f;
+
+ constexpr int expectedSize = 2 * sizeof(float);
+ std::vector<uint8_t> buffer(500, 0);
+ EXPECT_EQ(expectedSize, Cta861_3Value::encode(source, buffer.data(), buffer.size()));
+ auto read = Cta861_3Value::decode(buffer.data(), buffer.size());
+ ASSERT_TRUE(read.has_value());
+ ASSERT_TRUE(read->has_value());
+ EXPECT_EQ(source, read->value());
+
+ // A valid encoding of a nullopt
+ read = Cta861_3Value::decode(nullptr, 0);
+ ASSERT_TRUE(read.has_value());
+ EXPECT_FALSE(read->has_value());
+}
+
+TEST(Metadata, setGetSmpte2094_10) {
+ using SMPTE2094_10Value = StandardMetadata<StandardMetadataType::SMPTE2094_10>::value;
+
+ std::vector<uint8_t> buffer(500, 0);
+ EXPECT_EQ(0, SMPTE2094_10Value::encode(std::nullopt, buffer.data(), buffer.size()));
+ auto read = SMPTE2094_10Value::decode(buffer.data(), 0);
+ ASSERT_TRUE(read.has_value());
+ EXPECT_FALSE(read->has_value());
+
+ const std::vector<uint8_t> emptyBuffer;
+ EXPECT_EQ(sizeof(int64_t),
+ SMPTE2094_10Value::encode(emptyBuffer, buffer.data(), buffer.size()));
+ read = SMPTE2094_10Value::decode(buffer.data(), buffer.size());
+ ASSERT_TRUE(read.has_value());
+ ASSERT_TRUE(read->has_value());
+ EXPECT_EQ(0, read->value().size());
+
+ const std::vector<uint8_t> simpleBuffer{0, 1, 2, 3, 4, 5};
+ EXPECT_EQ(sizeof(int64_t) + 6,
+ SMPTE2094_10Value::encode(simpleBuffer, buffer.data(), buffer.size()));
+ read = SMPTE2094_10Value::decode(buffer.data(), buffer.size());
+ ASSERT_TRUE(read.has_value());
+ ASSERT_TRUE(read->has_value());
+ EXPECT_EQ(6, read->value().size());
+ EXPECT_EQ(simpleBuffer, read->value());
+}
+
+TEST(MetadataProvider, bufferId) {
+ using BufferId = StandardMetadata<StandardMetadataType::BUFFER_ID>::value;
+ std::vector<uint8_t> buffer(500, 0);
+ int result = provideStandardMetadata(StandardMetadataType::BUFFER_ID, buffer.data(),
+ buffer.size(), []<StandardMetadataType T>(auto&& provide) {
+ if constexpr (T == StandardMetadataType::BUFFER_ID) {
+ return provide(42);
+ }
+ return 0;
+ });
+
+ EXPECT_EQ(8, result);
+ auto read = BufferId::decode(buffer.data(), buffer.size());
+ EXPECT_EQ(42, read.value_or(0));
+}
+
+TEST(MetadataProvider, allJumpsWork) {
+ const auto& values = ndk::internal::enum_values<StandardMetadataType>;
+ auto get = [](StandardMetadataType type) -> int {
+ return provideStandardMetadata(type, nullptr, 0, []<StandardMetadataType T>(auto&&) {
+ return static_cast<int>(T) + 100;
+ });
+ };
+
+ for (auto& type : values) {
+ const int expected = type == StandardMetadataType::INVALID ? -AIMAPPER_ERROR_UNSUPPORTED
+ : static_cast<int>(type) + 100;
+ EXPECT_EQ(expected, get(type));
+ }
+}
+
+TEST(MetadataProvider, invalid) {
+ int result = provideStandardMetadata(StandardMetadataType::INVALID, nullptr, 0,
+ []<StandardMetadataType T>(auto&&) { return 10; });
+
+ EXPECT_EQ(-AIMAPPER_ERROR_UNSUPPORTED, result);
+}
+
+TEST(MetadataProvider, outOfBounds) {
+ int result = provideStandardMetadata(static_cast<StandardMetadataType>(-1), nullptr, 0,
+ []<StandardMetadataType T>(auto&&) { return 10; });
+ EXPECT_EQ(-AIMAPPER_ERROR_UNSUPPORTED, result) << "-1 should have resulted in UNSUPPORTED";
+
+ result = provideStandardMetadata(static_cast<StandardMetadataType>(100), nullptr, 0,
+ []<StandardMetadataType T>(auto&&) { return 10; });
+ EXPECT_EQ(-AIMAPPER_ERROR_UNSUPPORTED, result)
+ << "100 (out of range) should have resulted in UNSUPPORTED";
+}
diff --git a/graphics/mapper/stable-c/implutils/include/android/hardware/graphics/mapper/utils/IMapperMetadataTypes.h b/graphics/mapper/stable-c/implutils/include/android/hardware/graphics/mapper/utils/IMapperMetadataTypes.h
new file mode 100644
index 0000000..7861af8
--- /dev/null
+++ b/graphics/mapper/stable-c/implutils/include/android/hardware/graphics/mapper/utils/IMapperMetadataTypes.h
@@ -0,0 +1,576 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/graphics/common/BlendMode.h>
+#include <aidl/android/hardware/graphics/common/BufferUsage.h>
+#include <aidl/android/hardware/graphics/common/Cta861_3.h>
+#include <aidl/android/hardware/graphics/common/Dataspace.h>
+#include <aidl/android/hardware/graphics/common/ExtendableType.h>
+#include <aidl/android/hardware/graphics/common/PixelFormat.h>
+#include <aidl/android/hardware/graphics/common/PlaneLayout.h>
+#include <aidl/android/hardware/graphics/common/PlaneLayoutComponent.h>
+#include <aidl/android/hardware/graphics/common/Rect.h>
+#include <aidl/android/hardware/graphics/common/Smpte2086.h>
+#include <aidl/android/hardware/graphics/common/StandardMetadataType.h>
+#include <aidl/android/hardware/graphics/common/XyColor.h>
+#include <android/hardware/graphics/mapper/IMapper.h>
+
+#include <cinttypes>
+#include <string_view>
+#include <type_traits>
+#include <vector>
+
+namespace android::hardware::graphics::mapper {
+
+using ::aidl::android::hardware::graphics::common::BlendMode;
+using ::aidl::android::hardware::graphics::common::BufferUsage;
+using ::aidl::android::hardware::graphics::common::Cta861_3;
+using ::aidl::android::hardware::graphics::common::Dataspace;
+using ::aidl::android::hardware::graphics::common::ExtendableType;
+using ::aidl::android::hardware::graphics::common::PixelFormat;
+using ::aidl::android::hardware::graphics::common::PlaneLayout;
+using ::aidl::android::hardware::graphics::common::PlaneLayoutComponent;
+using ::aidl::android::hardware::graphics::common::Rect;
+using ::aidl::android::hardware::graphics::common::Smpte2086;
+using ::aidl::android::hardware::graphics::common::StandardMetadataType;
+using ::aidl::android::hardware::graphics::common::XyColor;
+
+class MetadataWriter {
+ private:
+ uint8_t* _Nonnull mDest;
+ size_t mSizeRemaining = 0;
+ int32_t mDesiredSize = 0;
+
+ void* _Nullable reserve(size_t sizeToWrite) {
+ if (mDesiredSize < 0) {
+ // Error state
+ return nullptr;
+ }
+ if (__builtin_add_overflow(mDesiredSize, sizeToWrite, &mDesiredSize)) {
+ // Overflowed, abort writing any further data
+ mDesiredSize = -AIMAPPER_ERROR_BAD_VALUE;
+ mSizeRemaining = 0;
+ return nullptr;
+ }
+ if (sizeToWrite > mSizeRemaining) {
+ mSizeRemaining = 0;
+ return nullptr;
+ } else {
+ mSizeRemaining -= sizeToWrite;
+ uint8_t* whereToWrite = mDest;
+ mDest += sizeToWrite;
+ return whereToWrite;
+ }
+ }
+
+ public:
+ explicit MetadataWriter(void* _Nullable destBuffer, size_t destBufferSize)
+ : mDest(reinterpret_cast<uint8_t*>(destBuffer)), mSizeRemaining(destBufferSize) {}
+
+ int32_t desiredSize() const { return mDesiredSize; }
+
+ template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
+ MetadataWriter& write(T value) {
+ auto sizeToWrite = sizeof(T);
+ if (void* dest = reserve(sizeToWrite)) {
+ memcpy(dest, &value, sizeToWrite);
+ }
+ return *this;
+ }
+
+ MetadataWriter& write(float value) {
+ auto sizeToWrite = sizeof(float);
+ if (void* dest = reserve(sizeToWrite)) {
+ memcpy(dest, &value, sizeToWrite);
+ }
+ return *this;
+ }
+
+ MetadataWriter& write(const std::string_view& value) {
+ auto sizeToWrite = value.length();
+ write<int64_t>(sizeToWrite);
+ if (void* dest = reserve(sizeToWrite)) {
+ memcpy(dest, value.data(), sizeToWrite);
+ }
+ return *this;
+ }
+
+ MetadataWriter& write(const std::vector<uint8_t>& value) {
+ auto sizeToWrite = value.size();
+ write<int64_t>(sizeToWrite);
+ if (void* dest = reserve(sizeToWrite)) {
+ memcpy(dest, value.data(), sizeToWrite);
+ }
+ return *this;
+ }
+
+ MetadataWriter& write(const ExtendableType& value) {
+ return write(value.name).write(value.value);
+ }
+
+ MetadataWriter& write(const XyColor& value) { return write(value.x).write(value.y); }
+};
+
+class MetadataReader {
+ private:
+ const uint8_t* _Nonnull mSrc;
+ size_t mSizeRemaining = 0;
+ bool mOk = true;
+
+ const void* _Nullable advance(size_t size) {
+ if (mOk && mSizeRemaining >= size) {
+ const void* buf = mSrc;
+ mSrc += size;
+ mSizeRemaining -= size;
+ return buf;
+ }
+ mOk = false;
+ return nullptr;
+ }
+
+ public:
+ explicit MetadataReader(const void* _Nonnull metadata, size_t metadataSize)
+ : mSrc(reinterpret_cast<const uint8_t*>(metadata)), mSizeRemaining(metadataSize) {}
+
+ [[nodiscard]] size_t remaining() const { return mSizeRemaining; }
+ [[nodiscard]] bool ok() const { return mOk; }
+
+ template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
+ MetadataReader& read(T& dest) {
+ if (const void* src = advance(sizeof(T))) {
+ memcpy(&dest, src, sizeof(T));
+ }
+ return *this;
+ }
+
+ MetadataReader& read(float& dest) {
+ if (const void* src = advance(sizeof(float))) {
+ memcpy(&dest, src, sizeof(float));
+ }
+ return *this;
+ }
+
+ MetadataReader& read(std::string& dest) {
+ dest = readString();
+ return *this;
+ }
+
+ MetadataReader& read(ExtendableType& dest) {
+ dest.name = readString();
+ read(dest.value);
+ return *this;
+ }
+
+ MetadataReader& read(XyColor& dest) {
+ read(dest.x);
+ read(dest.y);
+ return *this;
+ }
+
+ template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
+ [[nodiscard]] std::optional<T> readInt() {
+ auto sizeToRead = sizeof(T);
+ if (const void* src = advance(sizeof(T))) {
+ T ret;
+ memcpy(&ret, src, sizeToRead);
+ return ret;
+ }
+ return std::nullopt;
+ }
+
+ [[nodiscard]] std::string_view readString() {
+ auto lengthOpt = readInt<int64_t>();
+ if (!lengthOpt) {
+ return std::string_view{};
+ }
+ size_t length = lengthOpt.value();
+ if (const void* src = advance(length)) {
+ return std::string_view{reinterpret_cast<const char*>(src), length};
+ }
+ return std::string_view{};
+ }
+
+ [[nodiscard]] std::optional<ExtendableType> readExtendable() {
+ ExtendableType ret;
+ ret.name = readString();
+ auto value = readInt<int64_t>();
+ if (value) {
+ ret.value = value.value();
+ return ret;
+ } else {
+ return std::nullopt;
+ }
+ }
+
+ [[nodiscard]] std::vector<uint8_t> readBuffer() {
+ std::vector<uint8_t> ret;
+ size_t length = readInt<int64_t>().value_or(0);
+ if (const void* src = advance(length)) {
+ ret.resize(length);
+ memcpy(ret.data(), src, length);
+ }
+ return ret;
+ }
+};
+
+template <typename T, class Enable = void>
+struct MetadataValue {};
+
+template <typename T>
+struct MetadataValue<T, std::enable_if_t<std::is_integral_v<T>>> {
+ [[nodiscard]] static int32_t encode(T value, void* _Nullable destBuffer,
+ size_t destBufferSize) {
+ return MetadataWriter{destBuffer, destBufferSize}.write(value).desiredSize();
+ }
+
+ [[nodiscard]] static std::optional<T> decode(const void* _Nonnull metadata,
+ size_t metadataSize) {
+ return MetadataReader{metadata, metadataSize}.readInt<T>();
+ }
+};
+
+template <typename T>
+struct MetadataValue<T, std::enable_if_t<std::is_enum_v<T>>> {
+ [[nodiscard]] static int32_t encode(T value, void* _Nullable destBuffer,
+ size_t destBufferSize) {
+ return MetadataWriter{destBuffer, destBufferSize}
+ .write(static_cast<std::underlying_type_t<T>>(value))
+ .desiredSize();
+ }
+
+ [[nodiscard]] static std::optional<T> decode(const void* _Nonnull metadata,
+ size_t metadataSize) {
+ std::underlying_type_t<T> temp;
+ return MetadataReader{metadata, metadataSize}.read(temp).ok()
+ ? std::optional<T>(static_cast<T>(temp))
+ : std::nullopt;
+ }
+};
+
+template <>
+struct MetadataValue<std::string> {
+ [[nodiscard]] static int32_t encode(const std::string_view& value, void* _Nullable destBuffer,
+ size_t destBufferSize) {
+ return MetadataWriter{destBuffer, destBufferSize}.write(value).desiredSize();
+ }
+
+ [[nodiscard]] static std::optional<std::string> decode(const void* _Nonnull metadata,
+ size_t metadataSize) {
+ auto reader = MetadataReader{metadata, metadataSize};
+ auto result = reader.readString();
+ return reader.ok() ? std::optional<std::string>{result} : std::nullopt;
+ }
+};
+
+template <>
+struct MetadataValue<ExtendableType> {
+ static_assert(sizeof(int64_t) == sizeof(ExtendableType::value));
+
+ [[nodiscard]] static int32_t encode(const ExtendableType& value, void* _Nullable destBuffer,
+ size_t destBufferSize) {
+ return MetadataWriter{destBuffer, destBufferSize}.write(value).desiredSize();
+ }
+
+ [[nodiscard]] static std::optional<ExtendableType> decode(const void* _Nonnull metadata,
+ size_t metadataSize) {
+ return MetadataReader{metadata, metadataSize}.readExtendable();
+ }
+};
+
+template <>
+struct MetadataValue<std::vector<PlaneLayout>> {
+ [[nodiscard]] static int32_t encode(const std::vector<PlaneLayout>& values,
+ void* _Nullable destBuffer, size_t destBufferSize) {
+ MetadataWriter writer{destBuffer, destBufferSize};
+ writer.write<int64_t>(values.size());
+ for (const auto& value : values) {
+ writer.write<int64_t>(value.components.size());
+ for (const auto& component : value.components) {
+ writer.write(component.type)
+ .write<int64_t>(component.offsetInBits)
+ .write<int64_t>(component.sizeInBits);
+ }
+ writer.write<int64_t>(value.offsetInBytes)
+ .write<int64_t>(value.sampleIncrementInBits)
+ .write<int64_t>(value.strideInBytes)
+ .write<int64_t>(value.widthInSamples)
+ .write<int64_t>(value.heightInSamples)
+ .write<int64_t>(value.totalSizeInBytes)
+ .write<int64_t>(value.horizontalSubsampling)
+ .write<int64_t>(value.verticalSubsampling);
+ }
+ return writer.desiredSize();
+ }
+
+ using DecodeResult = std::optional<std::vector<PlaneLayout>>;
+ [[nodiscard]] static DecodeResult decode(const void* _Nonnull metadata, size_t metadataSize) {
+ std::vector<PlaneLayout> values;
+ MetadataReader reader{metadata, metadataSize};
+ auto numPlanes = reader.readInt<int64_t>().value_or(0);
+ values.reserve(numPlanes);
+ for (int i = 0; i < numPlanes && reader.ok(); i++) {
+ PlaneLayout& value = values.emplace_back();
+ auto numPlaneComponents = reader.readInt<int64_t>().value_or(0);
+ value.components.reserve(numPlaneComponents);
+ for (int i = 0; i < numPlaneComponents && reader.ok(); i++) {
+ PlaneLayoutComponent& component = value.components.emplace_back();
+ reader.read(component.type)
+ .read<int64_t>(component.offsetInBits)
+ .read<int64_t>(component.sizeInBits);
+ }
+ reader.read<int64_t>(value.offsetInBytes)
+ .read<int64_t>(value.sampleIncrementInBits)
+ .read<int64_t>(value.strideInBytes)
+ .read<int64_t>(value.widthInSamples)
+ .read<int64_t>(value.heightInSamples)
+ .read<int64_t>(value.totalSizeInBytes)
+ .read<int64_t>(value.horizontalSubsampling)
+ .read<int64_t>(value.verticalSubsampling);
+ }
+ return reader.ok() ? DecodeResult{std::move(values)} : std::nullopt;
+ }
+};
+
+template <>
+struct MetadataValue<std::vector<Rect>> {
+ [[nodiscard]] static int32_t encode(const std::vector<Rect>& value, void* _Nullable destBuffer,
+ size_t destBufferSize) {
+ MetadataWriter writer{destBuffer, destBufferSize};
+ writer.write<int64_t>(value.size());
+ for (auto& rect : value) {
+ writer.write<int32_t>(rect.left)
+ .write<int32_t>(rect.top)
+ .write<int32_t>(rect.right)
+ .write<int32_t>(rect.bottom);
+ }
+ return writer.desiredSize();
+ }
+
+ using DecodeResult = std::optional<std::vector<Rect>>;
+ [[nodiscard]] static DecodeResult decode(const void* _Nonnull metadata, size_t metadataSize) {
+ MetadataReader reader{metadata, metadataSize};
+ std::vector<Rect> value;
+ auto numRects = reader.readInt<int64_t>().value_or(0);
+ value.reserve(numRects);
+ for (int i = 0; i < numRects && reader.ok(); i++) {
+ Rect& rect = value.emplace_back();
+ reader.read<int32_t>(rect.left)
+ .read<int32_t>(rect.top)
+ .read<int32_t>(rect.right)
+ .read<int32_t>(rect.bottom);
+ }
+ return reader.ok() ? DecodeResult{std::move(value)} : std::nullopt;
+ }
+};
+
+template <>
+struct MetadataValue<std::optional<Smpte2086>> {
+ [[nodiscard]] static int32_t encode(const std::optional<Smpte2086>& optValue,
+ void* _Nullable destBuffer, size_t destBufferSize) {
+ if (optValue.has_value()) {
+ const auto& value = *optValue;
+ return MetadataWriter{destBuffer, destBufferSize}
+ .write(value.primaryRed)
+ .write(value.primaryGreen)
+ .write(value.primaryBlue)
+ .write(value.whitePoint)
+ .write(value.maxLuminance)
+ .write(value.minLuminance)
+ .desiredSize();
+ } else {
+ return 0;
+ }
+ }
+
+ // Double optional because the value type itself is an optional<>
+ using DecodeResult = std::optional<std::optional<Smpte2086>>;
+ [[nodiscard]] static DecodeResult decode(const void* _Nullable metadata, size_t metadataSize) {
+ std::optional<Smpte2086> optValue{std::nullopt};
+ if (metadataSize > 0) {
+ Smpte2086 value;
+ MetadataReader reader{metadata, metadataSize};
+ reader.read(value.primaryRed)
+ .read(value.primaryGreen)
+ .read(value.primaryBlue)
+ .read(value.whitePoint)
+ .read(value.maxLuminance)
+ .read(value.minLuminance);
+ if (reader.ok()) {
+ optValue = std::move(value);
+ } else {
+ return std::nullopt;
+ }
+ }
+ return DecodeResult{std::move(optValue)};
+ }
+};
+
+template <>
+struct MetadataValue<std::optional<Cta861_3>> {
+ [[nodiscard]] static int32_t encode(const std::optional<Cta861_3>& optValue,
+ void* _Nullable destBuffer, size_t destBufferSize) {
+ if (optValue.has_value()) {
+ const auto& value = *optValue;
+ return MetadataWriter{destBuffer, destBufferSize}
+ .write(value.maxContentLightLevel)
+ .write(value.maxFrameAverageLightLevel)
+ .desiredSize();
+ } else {
+ return 0;
+ }
+ }
+
+ // Double optional because the value type itself is an optional<>
+ using DecodeResult = std::optional<std::optional<Cta861_3>>;
+ [[nodiscard]] static DecodeResult decode(const void* _Nullable metadata, size_t metadataSize) {
+ std::optional<Cta861_3> optValue{std::nullopt};
+ if (metadataSize > 0) {
+ MetadataReader reader{metadata, metadataSize};
+ Cta861_3 value;
+ reader.read(value.maxContentLightLevel).read(value.maxFrameAverageLightLevel);
+ if (reader.ok()) {
+ optValue = std::move(value);
+ } else {
+ return std::nullopt;
+ }
+ }
+ return DecodeResult{std::move(optValue)};
+ }
+};
+
+template <>
+struct MetadataValue<std::optional<std::vector<uint8_t>>> {
+ [[nodiscard]] static int32_t encode(const std::optional<std::vector<uint8_t>>& value,
+ void* _Nullable destBuffer, size_t destBufferSize) {
+ if (!value.has_value()) {
+ return 0;
+ }
+ return MetadataWriter{destBuffer, destBufferSize}.write(*value).desiredSize();
+ }
+
+ using DecodeResult = std::optional<std::optional<std::vector<uint8_t>>>;
+ [[nodiscard]] static DecodeResult decode(const void* _Nonnull metadata, size_t metadataSize) {
+ std::optional<std::vector<uint8_t>> optValue;
+ if (metadataSize > 0) {
+ MetadataReader reader{metadata, metadataSize};
+ auto value = reader.readBuffer();
+ if (reader.ok()) {
+ optValue = std::move(value);
+ } else {
+ return std::nullopt;
+ }
+ }
+ return DecodeResult{std::move(optValue)};
+ }
+};
+
+template <StandardMetadataType>
+struct StandardMetadata {};
+
+#define DEFINE_TYPE(name, typeArg) \
+ template <> \
+ struct StandardMetadata<StandardMetadataType::name> { \
+ using value_type = typeArg; \
+ using value = MetadataValue<value_type>; \
+ static_assert( \
+ StandardMetadataType::name == \
+ ndk::internal::enum_values<StandardMetadataType>[static_cast<size_t>( \
+ StandardMetadataType::name)], \
+ "StandardMetadataType must have equivalent value to index"); \
+ }
+
+DEFINE_TYPE(BUFFER_ID, uint64_t);
+DEFINE_TYPE(NAME, std::string);
+DEFINE_TYPE(WIDTH, uint64_t);
+DEFINE_TYPE(HEIGHT, uint64_t);
+DEFINE_TYPE(LAYER_COUNT, uint64_t);
+DEFINE_TYPE(PIXEL_FORMAT_REQUESTED, PixelFormat);
+DEFINE_TYPE(PIXEL_FORMAT_FOURCC, uint32_t);
+DEFINE_TYPE(PIXEL_FORMAT_MODIFIER, uint64_t);
+DEFINE_TYPE(USAGE, BufferUsage);
+DEFINE_TYPE(ALLOCATION_SIZE, uint64_t);
+DEFINE_TYPE(PROTECTED_CONTENT, uint64_t);
+DEFINE_TYPE(COMPRESSION, ExtendableType);
+DEFINE_TYPE(INTERLACED, ExtendableType);
+DEFINE_TYPE(CHROMA_SITING, ExtendableType);
+DEFINE_TYPE(PLANE_LAYOUTS, std::vector<PlaneLayout>);
+DEFINE_TYPE(CROP, std::vector<Rect>);
+DEFINE_TYPE(DATASPACE, Dataspace);
+DEFINE_TYPE(BLEND_MODE, BlendMode);
+DEFINE_TYPE(SMPTE2086, std::optional<Smpte2086>);
+DEFINE_TYPE(CTA861_3, std::optional<Cta861_3>);
+DEFINE_TYPE(SMPTE2094_10, std::optional<std::vector<uint8_t>>);
+DEFINE_TYPE(SMPTE2094_40, std::optional<std::vector<uint8_t>>);
+
+#undef DEFINE_TYPE
+
+template <typename F, std::size_t... I>
+void invokeWithStandardMetadata(F&& f, StandardMetadataType type, std::index_sequence<I...>) {
+ // Setup the jump table, mapping from each type to a springboard that invokes the template
+ // function with the appropriate concrete type
+ using F_PTR = decltype(&f);
+ using THUNK = void (*)(F_PTR);
+ static constexpr auto jump = std::array<THUNK, sizeof...(I)>{[](F_PTR fp) {
+ constexpr StandardMetadataType type = ndk::internal::enum_values<StandardMetadataType>[I];
+ if constexpr (type != StandardMetadataType::INVALID) {
+ (*fp)(StandardMetadata<type>{});
+ }
+ }...};
+
+ auto index = static_cast<size_t>(type);
+ if (index >= 0 && index < jump.size()) {
+ jump[index](&f);
+ }
+}
+
+template <typename F, typename StandardMetadataSequence = std::make_index_sequence<
+ ndk::internal::enum_values<StandardMetadataType>.size()>>
+int32_t provideStandardMetadata(StandardMetadataType type, void* _Nullable destBuffer,
+ size_t destBufferSize, F&& f) {
+ int32_t retVal = -AIMAPPER_ERROR_UNSUPPORTED;
+ invokeWithStandardMetadata(
+ [&]<StandardMetadataType T>(StandardMetadata<T>) {
+ retVal = f.template operator()<T>(
+ [&](const typename StandardMetadata<T>::value_type& value) -> int32_t {
+ return StandardMetadata<T>::value::encode(value, destBuffer,
+ destBufferSize);
+ });
+ },
+ type, StandardMetadataSequence{});
+ return retVal;
+}
+
+template <typename F, typename StandardMetadataSequence = std::make_index_sequence<
+ ndk::internal::enum_values<StandardMetadataType>.size()>>
+AIMapper_Error applyStandardMetadata(StandardMetadataType type, const void* _Nonnull metadata,
+ size_t metadataSize, F&& f) {
+ AIMapper_Error retVal = AIMAPPER_ERROR_UNSUPPORTED;
+ invokeWithStandardMetadata(
+ [&]<StandardMetadataType T>(StandardMetadata<T>) {
+ auto value = StandardMetadata<T>::value::decode(metadata, metadataSize);
+ if (value.has_value()) {
+ retVal = f.template operator()<T>(std::move(*value));
+ } else {
+ retVal = AIMAPPER_ERROR_BAD_VALUE;
+ }
+ },
+ type, StandardMetadataSequence{});
+ return retVal;
+}
+
+} // namespace android::hardware::graphics::mapper
\ No newline at end of file
diff --git a/graphics/mapper/stable-c/implutils/include/android/hardware/graphics/mapper/utils/IMapperProvider.h b/graphics/mapper/stable-c/implutils/include/android/hardware/graphics/mapper/utils/IMapperProvider.h
new file mode 100644
index 0000000..957fdc9
--- /dev/null
+++ b/graphics/mapper/stable-c/implutils/include/android/hardware/graphics/mapper/utils/IMapperProvider.h
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android-base/unique_fd.h>
+#include <android/hardware/graphics/mapper/IMapper.h>
+#include <log/log.h>
+
+#include <mutex>
+#include <optional>
+#include <type_traits>
+
+/**
+ * Helper utilities for providing an IMapper-StableC implementation.
+ */
+
+namespace vendor::mapper {
+
+/**
+ * Extend from this interface to provide Version 5 of the IMapper interface
+ */
+struct IMapperV5Impl {
+ static const auto version = AIMAPPER_VERSION_5;
+ virtual ~IMapperV5Impl() = default;
+
+ virtual AIMapper_Error importBuffer(const native_handle_t* _Nonnull handle,
+ buffer_handle_t _Nullable* _Nonnull outBufferHandle) = 0;
+
+ virtual AIMapper_Error freeBuffer(buffer_handle_t _Nonnull buffer) = 0;
+
+ virtual AIMapper_Error getTransportSize(buffer_handle_t _Nonnull buffer,
+ uint32_t* _Nonnull outNumFds,
+ uint32_t* _Nonnull outNumInts) = 0;
+
+ virtual AIMapper_Error lock(buffer_handle_t _Nonnull buffer, uint64_t cpuUsage,
+ ARect accessRegion, int acquireFence,
+ void* _Nullable* _Nonnull outData) = 0;
+
+ virtual AIMapper_Error unlock(buffer_handle_t _Nonnull buffer, int* _Nonnull releaseFence) = 0;
+
+ virtual AIMapper_Error flushLockedBuffer(buffer_handle_t _Nonnull buffer) = 0;
+
+ virtual AIMapper_Error rereadLockedBuffer(buffer_handle_t _Nonnull buffer) = 0;
+
+ virtual int32_t getMetadata(buffer_handle_t _Nonnull buffer, AIMapper_MetadataType metadataType,
+ void* _Nullable destBuffer, size_t destBufferSize) = 0;
+
+ virtual int32_t getStandardMetadata(buffer_handle_t _Nonnull buffer,
+ int64_t standardMetadataType, void* _Nullable destBuffer,
+ size_t destBufferSize) = 0;
+
+ virtual AIMapper_Error setMetadata(buffer_handle_t _Nonnull buffer,
+ AIMapper_MetadataType metadataType,
+ const void* _Nonnull metadata, size_t metadataSize) = 0;
+
+ virtual AIMapper_Error setStandardMetadata(buffer_handle_t _Nonnull buffer,
+ int64_t standardMetadataType,
+ const void* _Nonnull metadata,
+ size_t metadataSize) = 0;
+
+ virtual AIMapper_Error listSupportedMetadataTypes(
+ const AIMapper_MetadataTypeDescription* _Nullable* _Nonnull outDescriptionList,
+ size_t* _Nonnull outNumberOfDescriptions) = 0;
+
+ virtual AIMapper_Error dumpBuffer(buffer_handle_t _Nonnull bufferHandle,
+ AIMapper_DumpBufferCallback _Nonnull dumpBufferCallback,
+ void* _Null_unspecified context) = 0;
+
+ virtual AIMapper_Error dumpAllBuffers(
+ AIMapper_BeginDumpBufferCallback _Nonnull beginDumpBufferCallback,
+ AIMapper_DumpBufferCallback _Nonnull dumpBufferCallback,
+ void* _Null_unspecified context) = 0;
+
+ virtual AIMapper_Error getReservedRegion(buffer_handle_t _Nonnull buffer,
+ void* _Nullable* _Nonnull outReservedRegion,
+ uint64_t* _Nonnull outReservedSize) = 0;
+};
+
+namespace provider {
+#ifndef __cpp_inline_variables
+#error "Only C++17 & newer is supported; inline variables is missing"
+#endif
+
+inline void* _Nullable sIMapperInstance = nullptr;
+} // namespace provider
+
+template <typename IMPL>
+class IMapperProvider {
+ private:
+ static_assert(IMPL::version >= AIMAPPER_VERSION_5, "Must be at least AIMAPPER_VERSION_5");
+ static_assert(std::is_final_v<IMPL>, "Implementation must be final");
+ static_assert(std::is_constructible_v<IMPL>, "Implementation must have a no-args constructor");
+
+ std::once_flag mLoadOnceFlag;
+ std::optional<IMPL> mImpl;
+ AIMapper mMapper = {};
+
+ static IMPL& impl() {
+ return *reinterpret_cast<IMapperProvider<IMPL>*>(provider::sIMapperInstance)->mImpl;
+ }
+
+ void bindV5() {
+ mMapper.v5 = {
+ .importBuffer = [](const native_handle_t* _Nonnull handle,
+ buffer_handle_t _Nullable* _Nonnull outBufferHandle)
+ -> AIMapper_Error { return impl().importBuffer(handle, outBufferHandle); },
+
+ .freeBuffer = [](buffer_handle_t _Nonnull buffer) -> AIMapper_Error {
+ return impl().freeBuffer(buffer);
+ },
+
+ .getTransportSize = [](buffer_handle_t _Nonnull buffer,
+ uint32_t* _Nonnull outNumFds,
+ uint32_t* _Nonnull outNumInts) -> AIMapper_Error {
+ return impl().getTransportSize(buffer, outNumFds, outNumInts);
+ },
+
+ .lock = [](buffer_handle_t _Nonnull buffer, uint64_t cpuUsage, ARect accessRegion,
+ int acquireFence, void* _Nullable* _Nonnull outData) -> AIMapper_Error {
+ return impl().lock(buffer, cpuUsage, accessRegion, acquireFence, outData);
+ },
+
+ .unlock = [](buffer_handle_t _Nonnull buffer, int* _Nonnull releaseFence)
+ -> AIMapper_Error { return impl().unlock(buffer, releaseFence); },
+
+ .flushLockedBuffer = [](buffer_handle_t _Nonnull buffer) -> AIMapper_Error {
+ return impl().flushLockedBuffer(buffer);
+ },
+
+ .rereadLockedBuffer = [](buffer_handle_t _Nonnull buffer) -> AIMapper_Error {
+ return impl().rereadLockedBuffer(buffer);
+ },
+
+ .getMetadata = [](buffer_handle_t _Nonnull buffer,
+ AIMapper_MetadataType metadataType, void* _Nullable destBuffer,
+ size_t destBufferSize) -> int32_t {
+ return impl().getMetadata(buffer, metadataType, destBuffer, destBufferSize);
+ },
+
+ .getStandardMetadata = [](buffer_handle_t _Nonnull buffer,
+ int64_t standardMetadataType, void* _Nullable destBuffer,
+ size_t destBufferSize) -> int32_t {
+ return impl().getStandardMetadata(buffer, standardMetadataType, destBuffer,
+ destBufferSize);
+ },
+
+ .setMetadata = [](buffer_handle_t _Nonnull buffer,
+ AIMapper_MetadataType metadataType, const void* _Nonnull metadata,
+ size_t metadataSize) -> AIMapper_Error {
+ return impl().setMetadata(buffer, metadataType, metadata, metadataSize);
+ },
+
+ .setStandardMetadata =
+ [](buffer_handle_t _Nonnull buffer, int64_t standardMetadataType,
+ const void* _Nonnull metadata, size_t metadataSize) -> AIMapper_Error {
+ return impl().setStandardMetadata(buffer, standardMetadataType, metadata,
+ metadataSize);
+ },
+
+ .listSupportedMetadataTypes =
+ [](const AIMapper_MetadataTypeDescription* _Nullable* _Nonnull outDescriptionList,
+ size_t* _Nonnull outNumberOfDescriptions) -> AIMapper_Error {
+ return impl().listSupportedMetadataTypes(outDescriptionList,
+ outNumberOfDescriptions);
+ },
+
+ .dumpBuffer = [](buffer_handle_t _Nonnull bufferHandle,
+ AIMapper_DumpBufferCallback _Nonnull dumpBufferCallback,
+ void* _Null_unspecified context) -> AIMapper_Error {
+ return impl().dumpBuffer(bufferHandle, dumpBufferCallback, context);
+ },
+
+ .dumpAllBuffers =
+ [](AIMapper_BeginDumpBufferCallback _Nonnull beginDumpBufferCallback,
+ AIMapper_DumpBufferCallback _Nonnull dumpBufferCallback,
+ void* _Null_unspecified context) {
+ return impl().dumpAllBuffers(beginDumpBufferCallback,
+ dumpBufferCallback, context);
+ },
+
+ .getReservedRegion = [](buffer_handle_t _Nonnull buffer,
+ void* _Nullable* _Nonnull outReservedRegion,
+ uint64_t* _Nonnull outReservedSize) -> AIMapper_Error {
+ return impl().getReservedRegion(buffer, outReservedRegion, outReservedSize);
+ },
+ };
+ }
+
+ public:
+ explicit IMapperProvider() = default;
+
+ AIMapper_Error load(AIMapper* _Nullable* _Nonnull outImplementation) {
+ std::call_once(mLoadOnceFlag, [this] {
+ LOG_ALWAYS_FATAL_IF(provider::sIMapperInstance != nullptr,
+ "AIMapper implementation already loaded!");
+ provider::sIMapperInstance = this;
+ mImpl.emplace();
+ mMapper.version = IMPL::version;
+ if (IMPL::version >= AIMAPPER_VERSION_5) {
+ bindV5();
+ }
+ });
+ *outImplementation = &mMapper;
+ return AIMAPPER_ERROR_NONE;
+ }
+};
+
+} // namespace vendor::mapper
diff --git a/graphics/mapper/stable-c/include/android/hardware/graphics/mapper/IMapper.h b/graphics/mapper/stable-c/include/android/hardware/graphics/mapper/IMapper.h
new file mode 100644
index 0000000..f27b0f4
--- /dev/null
+++ b/graphics/mapper/stable-c/include/android/hardware/graphics/mapper/IMapper.h
@@ -0,0 +1,689 @@
+/*
+ * 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.
+ */
+
+/**
+ * IMapper Stable-C HAL interface
+ *
+ * This file represents the sphal interface between libui & the IMapper HAL implementation.
+ * A vendor implementation of this interface is retrieved by looking up the vendor imapper
+ * implementation library via the IAllocator AIDL interface.
+ *
+ * This interface is not intended for general use.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+#include <cinttypes>
+#include <cstddef>
+#include <type_traits>
+
+#include <android/rect.h>
+#include <cutils/native_handle.h>
+
+__BEGIN_DECLS
+
+/**
+ * AIMapper versioning
+ *
+ * IMapper versions 0-1 are pre-treble
+ * IMapper versions 2-4 are HIDL
+ * C-style AIMapper API starts at 5
+ */
+enum AIMapper_Version : uint32_t {
+ AIMAPPER_VERSION_5 = 5,
+};
+
+/**
+ * Possible AIMapper errors
+ * Values are the same as IMapper 4.0's Error type for simplicity
+ */
+enum AIMapper_Error : int32_t {
+ /**
+ * No error.
+ */
+ AIMAPPER_ERROR_NONE = 0,
+ /**
+ * Invalid BufferDescriptor.
+ */
+ AIMAPPER_ERROR_BAD_DESCRIPTOR = 1,
+ /**
+ * Invalid buffer handle.
+ */
+ AIMAPPER_ERROR_BAD_BUFFER = 2,
+ /**
+ * Invalid HardwareBufferDescription.
+ */
+ AIMAPPER_ERROR_BAD_VALUE = 3,
+ /**
+ * Resource unavailable.
+ */
+ AIMAPPER_ERROR_NO_RESOURCES = 5,
+ /**
+ * Permanent failure.
+ */
+ AIMAPPER_ERROR_UNSUPPORTED = 7,
+};
+
+/**
+ * MetadataType represents the different types of buffer metadata that could be
+ * associated with a buffer. It is used by IMapper to help get and set buffer metadata
+ * on the buffer's native handle.
+ *
+ * Standard buffer metadata will have the name field set to
+ * "android.hardware.graphics.common.StandardMetadataType" and will contain values
+ * from StandardMetadataType.aidl.
+ *
+ * Vendor-provided metadata should be prefixed with a "vendor.mycompanyname.*" namespace. It is
+ * recommended that the metadata follows the pattern of StandardMetadaType.aidl. That is, an
+ * aidl-defined enum with @VendorStability on it and the naming then matching that type such
+ * as "vendor.mycompanyname.graphics.common.MetadataType" with the value field then set to the
+ * aidl's enum value.
+ *
+ * Each company should create their own enum & namespace. The name
+ * field prevents values from different companies from colliding.
+ */
+typedef struct AIMapper_MetadataType {
+ const char* _Nonnull name;
+ int64_t value;
+} AIMapper_MetadataType;
+
+typedef struct AIMapper_MetadataTypeDescription {
+ /**
+ * The `name` of the metadataType must be valid for the lifetime of the process
+ */
+ AIMapper_MetadataType metadataType;
+ /**
+ * description should contain a string representation of the MetadataType.
+ *
+ * For example: "MyExampleMetadataType is a 64-bit timestamp in nanoseconds
+ * that indicates when a buffer is decoded. It is set by the media HAL after
+ * a buffer is decoded. It is used by the display HAL for hardware
+ * synchronization".
+ *
+ * This field is required for any non-StandardMetadataTypes. For StandardMetadataTypes this
+ * field may be null. The lifetime of this pointer must be valid for the duration of the
+ * process (that is, a static const char*).
+ */
+ const char* _Nullable description;
+ /**
+ * isGettable represents if the MetadataType can be get.
+ */
+ bool isGettable;
+ /**
+ * isSettable represents if the MetadataType can be set.
+ */
+ bool isSettable;
+
+ /** Reserved for future use; must be zero-initialized currently */
+ uint8_t reserved[32];
+} AIMapper_MetadataTypeDescription;
+
+/**
+ * Callback that is passed to dumpBuffer.
+ *
+ * @param context The caller-provided void* that was passed to dumpBuffer.
+ * @param metadataType The type of the metadata passed to the callback
+ * @param value A pointer to the value of the metadata. The lifetime of this pointer is only
+ * valid for the duration of the call
+ * @param valueSize The size of the value buffer.
+ */
+typedef void (*AIMapper_DumpBufferCallback)(void* _Null_unspecified context,
+ AIMapper_MetadataType metadataType,
+ const void* _Nonnull value, size_t valueSize);
+
+/**
+ * Callback that is passed to dumpAllBuffers.
+ *
+ * Indicates that a buffer is about to be dumped. Will be followed by N calls to
+ * AIMapper_DumpBufferCallback for all the metadata for this buffer.
+ *
+ * @param context The caller-provided void* that was passed to dumpAllBuffers.
+ */
+typedef void (*AIMapper_BeginDumpBufferCallback)(void* _Null_unspecified context);
+
+/**
+ * Implementation of AIMAPPER_VERSION_5
+ * All functions must not be null & must provide a valid implementation.
+ */
+typedef struct AIMapperV5 {
+ /**
+ * Imports a raw buffer handle to create an imported buffer handle for use
+ * with the rest of the mapper or with other in-process libraries.
+ *
+ * A buffer handle is considered raw when it is cloned (e.g., with
+ * `native_handle_clone()`) from another buffer handle locally, or when it
+ * is received from another HAL server/client or another process. A raw
+ * buffer handle must not be used to access the underlying graphic
+ * buffer. It must be imported to create an imported handle first.
+ *
+ * This function must at least validate the raw handle before creating the
+ * imported handle. It must also support importing the same raw handle
+ * multiple times to create multiple imported handles. The imported handle
+ * must be considered valid everywhere in the process, including in
+ * another instance of the mapper.
+ *
+ * Because of passthrough HALs, a raw buffer handle received from a HAL
+ * may actually have been imported in the process. importBuffer() must treat
+ * such a handle as if it is raw and must not return `BAD_BUFFER`. The
+ * returned handle is independent from the input handle as usual, and
+ * freeBuffer() must be called on it when it is no longer needed.
+ *
+ * @param handle Raw buffer handle to import.
+ * @param outBufferHandle The resulting imported buffer handle.
+ * @return Error status of the call, which may be
+ * - `NONE` upon success.
+ * - `BAD_BUFFER` if the raw handle is invalid.
+ * - `NO_RESOURCES` if the raw handle cannot be imported due to
+ * unavailability of resources.
+ */
+ AIMapper_Error (*_Nonnull importBuffer)(const native_handle_t* _Nonnull handle,
+ buffer_handle_t _Nullable* _Nonnull outBufferHandle);
+
+ /**
+ * Frees a buffer handle. Buffer handles returned by importBuffer() must be
+ * freed with this function when no longer needed.
+ *
+ * This function must free up all resources allocated by importBuffer() for
+ * the imported handle. For example, if the imported handle was created
+ * with `native_handle_create()`, this function must call
+ * `native_handle_close()` and `native_handle_delete()`.
+ *
+ * @param buffer Imported buffer handle.
+ * @return error Error status of the call, which may be
+ * - `NONE` upon success.
+ * - `BAD_BUFFER` if the buffer is invalid.
+ */
+ AIMapper_Error (*_Nonnull freeBuffer)(buffer_handle_t _Nonnull buffer);
+
+ /**
+ * Calculates the transport size of a buffer. An imported buffer handle is a
+ * raw buffer handle with the process-local runtime data appended. This
+ * function, for example, allows a caller to omit the process-local runtime
+ * data at the tail when serializing the imported buffer handle.
+ *
+ * Note that a client might or might not omit the process-local runtime data
+ * when sending an imported buffer handle. The mapper must support both
+ * cases on the receiving end.
+ *
+ * @param buffer Buffer to get the transport size from.
+ * @param outNumFds The number of file descriptors needed for transport.
+ * @param outNumInts The number of integers needed for transport.
+ * @return error Error status of the call, which may be
+ * - `NONE` upon success.
+ * - `BAD_BUFFER` if the buffer is invalid.
+ */
+ AIMapper_Error (*_Nonnull getTransportSize)(buffer_handle_t _Nonnull buffer,
+ uint32_t* _Nonnull outNumFds,
+ uint32_t* _Nonnull outNumInts);
+
+ /**
+ * Locks the given buffer for the specified CPU usage.
+ *
+ * Locking the same buffer simultaneously from multiple threads is
+ * permitted, but if any of the threads attempt to lock the buffer for
+ * writing, the behavior is undefined, except that it must not cause
+ * process termination or block the client indefinitely. Leaving the
+ * buffer content in an indeterminate state or returning an error are both
+ * acceptable.
+ *
+ * 1D buffers (width = size in bytes, height = 1, pixel_format = BLOB) must
+ * "lock in place". The buffers must be directly accessible via mapping.
+ *
+ * The client must not modify the content of the buffer outside of
+ * @p accessRegion, and the device need not guarantee that content outside
+ * of @p accessRegion is valid for reading. The result of reading or writing
+ * outside of @p accessRegion is undefined, except that it must not cause
+ * process termination.
+ *
+ * An accessRegion of all-zeros means the entire buffer. That is, it is
+ * equivalent to '(0,0)-(buffer width, buffer height)'.
+ *
+ * This function can lock both single-planar and multi-planar formats. The caller
+ * should use get() to get information about the buffer they are locking.
+ * get() can be used to get information about the planes, offsets, stride,
+ * etc.
+ *
+ * This function must also work on buffers with
+ * `AHARDWAREBUFFER_FORMAT_Y8Cb8Cr8_*` if supported by the device, as well
+ * as with any other formats requested by multimedia codecs when they are
+ * configured with a flexible-YUV-compatible color format.
+ *
+ * On success, @p data must be filled with a pointer to the locked buffer
+ * memory. This address will represent the top-left corner of the entire
+ * buffer, even if @p accessRegion does not begin at the top-left corner.
+ *
+ * The locked buffer must adhere to the format requested at allocation time
+ * in the BufferDescriptorInfo.
+ *
+ * @param buffer Buffer to lock.
+ * @param cpuUsage CPU usage flags to request. See BufferUsage.aidl for possible values.
+ * @param accessRegion Portion of the buffer that the client intends to
+ * access.
+ * @param acquireFence Handle containing a file descriptor referring to a
+ * sync fence object, which will be signaled when it is safe for the
+ * mapper to lock the buffer. @p acquireFence may be an empty fence (-1) if
+ * it is already safe to lock. Ownership is passed to the callee and it is the
+ * implementations responsibility to ensure it is closed even when an error
+ * occurs.
+ * @param outData CPU-accessible pointer to the buffer data.
+ * @return error Error status of the call, which may be
+ * - `NONE` upon success.
+ * - `BAD_BUFFER` if the buffer is invalid or is incompatible with this
+ * function.
+ * - `BAD_VALUE` if @p cpuUsage is 0, contains non-CPU usage flags, or
+ * is incompatible with the buffer. Also if the @p accessRegion is
+ * outside the bounds of the buffer or the accessRegion is invalid.
+ * - `NO_RESOURCES` if the buffer cannot be locked at this time. Note
+ * that locking may succeed at a later time.
+ * @return data CPU-accessible pointer to the buffer data.
+ */
+ AIMapper_Error (*_Nonnull lock)(buffer_handle_t _Nonnull buffer, uint64_t cpuUsage,
+ ARect accessRegion, int acquireFence,
+ void* _Nullable* _Nonnull outData);
+
+ /**
+ * Unlocks a buffer to indicate all CPU accesses to the buffer have
+ * completed.
+ *
+ * @param buffer Buffer to unlock.
+ * @param releaseFence Handle containing a file descriptor referring to a
+ * sync fence object. The sync fence object will be signaled when the
+ * mapper has completed any pending work. @p releaseFence may be an
+ * empty fence (-1).
+ * @return error Error status of the call, which may be
+ * - `NONE` upon success.
+ * - `BAD_BUFFER` if the buffer is invalid or not locked.
+ */
+ AIMapper_Error (*_Nonnull unlock)(buffer_handle_t _Nonnull buffer, int* _Nonnull releaseFence);
+
+ /**
+ * Flushes the contents of a locked buffer.
+ *
+ * This function flushes the CPUs caches for the range of all the buffer's
+ * planes and metadata. This should behave similarly to unlock() except the
+ * buffer should remain mapped to the CPU.
+ *
+ * The client is still responsible for calling unlock() when it is done
+ * with all CPU accesses to the buffer.
+ *
+ * If non-CPU blocks are simultaneously writing the buffer, the locked
+ * copy should still be flushed but what happens is undefined except that
+ * it should not cause any crashes.
+ *
+ * @param buffer Buffer to flush.
+ * @return error Error status of the call, which may be
+ * - `NONE` upon success.
+ * - `BAD_BUFFER` if the buffer is invalid or not locked.
+ */
+ AIMapper_Error (*_Nonnull flushLockedBuffer)(buffer_handle_t _Nonnull buffer);
+
+ /**
+ * Rereads the contents of a locked buffer.
+ *
+ * This should fetch the most recent copy of the locked buffer.
+ *
+ * It may reread locked copies of the buffer in other processes.
+ *
+ * The client is still responsible for calling unlock() when it is done
+ * with all CPU accesses to the buffer.
+ *
+ * @param buffer Buffer to reread.
+ * @return error Error status of the call, which may be
+ * - `NONE` upon success.
+ * - `BAD_BUFFER` if the buffer is invalid or not locked.
+ * - `NO_RESOURCES` if the buffer cannot be reread at this time. Note
+ * that rereading may succeed at a later time.
+ */
+ AIMapper_Error (*_Nonnull rereadLockedBuffer)(buffer_handle_t _Nonnull buffer);
+
+ /**
+ * Description for get(...), set(...) and getFromBufferDescriptorInfo(...)
+ *
+ * ------------ Overview -----------------------------------
+ * Gralloc 4 adds support for getting and setting buffer metadata on a buffer.
+ *
+ * To get buffer metadata, the client passes in a buffer handle and a token that
+ * represents the type of buffer metadata they would like to get. IMapper returns
+ * a byte stream that contains the buffer metadata. To set the buffer metadata, the
+ * client passes in a buffer handle and a token that represents the type of buffer
+ * metadata they would like to set and a byte stream that contains the buffer metadata
+ * they are setting.
+ *
+ * Buffer metadata is global for a buffer. When the metadata is set on the buffer
+ * in a process, the updated metadata should be available to all other processes.
+ * Please see "Storing and Propagating Metadata" below for more details.
+ *
+ * The getter and setter functions have been optimized for easy vendor extension.
+ * They do not require a formal extension to add support for getting and setting
+ * vendor defined buffer metadata. See "Buffer Metadata Token" and
+ * "Buffer Metadata Stream" below for more details.
+ *
+ * ------------ Storing and Propagating Metadata -----------
+ * Buffer metadata must be global. Any changes to the metadata must be propagated
+ * to all other processes immediately. Vendors may chose how they would like support
+ * this functionality.
+ *
+ * We recommend supporting this functionality by allocating an extra page of shared
+ * memory and storing it in the buffer's native_handle_t. The buffer metadata can
+ * be stored in the extra page of shared memory. Set operations are automatically
+ * propagated to all other processes.
+ *
+ * ------------ Buffer Metadata Synchronization ------------
+ * There are no explicit buffer metadata synchronization primitives. Many devices
+ * before gralloc 4 already support getting and setting of global buffer metadata
+ * with no explicit synchronization primitives. Adding synchronization primitives
+ * would just add unnecessary complexity.
+ *
+ * The general rule is if a process has permission to write to a buffer, they
+ * have permission to write to the buffer's writable metadata. If a process has permission
+ * to read from a buffer, they have permission to read the buffer's metadata.
+ *
+ * There is one exception to this rule. Fences CANNOT be used to protect a buffer's
+ * metadata. A process should finish writing to a buffer's metadata before
+ * sending the buffer to another process that will read or write to the buffer.
+ * This exception is needed because sometimes userspace needs to read the
+ * buffer's metadata before the buffer's contents are ready.
+ *
+ * As a simple example: an app renders to a buffer and then displays the buffer.
+ * In this example when the app renders to the buffer, both the buffer and its
+ * metadata need to be updated. The app's process queues up its work on the GPU
+ * and gets back an acquire fence. The app's process must update the buffer's
+ * metadata before enqueuing the buffer to SurfaceFlinger. The app process CANNOT
+ * update the buffer's metadata after enqueuing the buffer. When HardwareComposer
+ * receives the buffer, it is immediately safe to read the buffer's metadata
+ * and use it to program the display driver. To read the buffer's contents,
+ * display driver must still wait on the acquire fence.
+ *
+ * ------------ Buffer Metadata Token ----------------------
+ * In order to allow arbitrary vendor defined metadata, the token used to access
+ * metadata is defined defined as a struct that has a string representing
+ * the enum type and an int that represents the enum value. The string protects
+ * different enum values from colliding.
+ *
+ * The token struct (MetadataType) is defined as a C struct since it
+ * is passed into a C function. The standard buffer metadata types are NOT
+ * defined as a C enum but instead as an AIDL enum to allow for broader usage across
+ * other HALs and libraries. By putting the enum in the
+ * stable AIDL (hardware/interfaces/graphics/common/aidl/android/hardware/
+ * graphics/common/StandardMetadataType.aidl), vendors will be able to optionally
+ * choose to support future standard buffer metadata types without upgrading
+ * IMapper versions. For more information see the description of "struct MetadataType".
+ *
+ * ------------ Buffer Metadata Stream ---------------------
+ * The buffer metadata is get and set as a void* buffer. By getting
+ * and setting buffer metadata as a generic buffer, vendors can use the standard
+ * getters and setter functions defined here. Vendors do NOT need to add their own
+ * getters and setter functions for each new type of buffer metadata.
+ *
+ * Converting buffer metadata into a byte stream can be non-trivial. For the standard
+ * buffer metadata types defined in StandardMetadataType.aidl, there are also
+ * support functions that will encode the buffer metadata into a byte stream
+ * and decode the buffer metadata from a byte stream. We STRONGLY recommend using
+ * these support functions. The framework will use them when getting and setting
+ * metadata. The support functions are defined in
+ * frameworks/native/libs/gralloc/types/include/gralloctypes/Gralloc4.h.
+ */
+
+ /**
+ * Gets the buffer metadata for a given MetadataType.
+ *
+ * Buffer metadata can be changed after allocation so clients should avoid "caching"
+ * the buffer metadata. For example, if the video resolution changes and the buffers
+ * are not reallocated, several buffer metadata values may change without warning.
+ * Clients should not expect the values to be constant. They should requery them every
+ * frame. The only exception is buffer metadata that is determined at allocation
+ * time. For StandardMetadataType values, only BUFFER_ID, NAME, WIDTH,
+ * HEIGHT, LAYER_COUNT, PIXEL_FORMAT_REQUESTED and USAGE are safe to cache because
+ * they are determined at allocation time.
+ *
+ * @param buffer Buffer containing desired metadata
+ * @param metadataType MetadataType for the metadata value being queried
+ * @param destBuffer Pointer to a buffer in which to store the result of the get() call; if
+ * null, the computed output size or error must still be returned.
+ * @param destBufferSize How large the destBuffer buffer is. If destBuffer is null this must be
+ * 0.
+ * @return The number of bytes written to `destBuffer` or which would have been written
+ * if `destBufferSize` was large enough.
+ * A negative value indicates an error, which may be
+ * - `BAD_BUFFER` if the raw handle is invalid.
+ * - `UNSUPPORTED` when metadataType is unknown/unsupported.
+ * IMapper must support getting all StandardMetadataType.aidl values defined
+ * at the time the device first launches.
+ */
+ int32_t (*_Nonnull getMetadata)(buffer_handle_t _Nonnull buffer,
+ AIMapper_MetadataType metadataType, void* _Nullable destBuffer,
+ size_t destBufferSize);
+
+ /**
+ * Gets the buffer metadata for a StandardMetadataType.
+ *
+ * This is equivalent to `getMetadata` when passed an AIMapper_MetadataType with name
+ * set to "android.hardware.graphics.common.StandardMetadataType"
+ *
+ * Buffer metadata can be changed after allocation so clients should avoid "caching"
+ * the buffer metadata. For example, if the video resolution changes and the buffers
+ * are not reallocated, several buffer metadata values may change without warning.
+ * Clients should not expect the values to be constant. They should requery them every
+ * frame. The only exception is buffer metadata that is determined at allocation
+ * time. For StandardMetadataType values, only BUFFER_ID, NAME, WIDTH,
+ * HEIGHT, LAYER_COUNT, PIXEL_FORMAT_REQUESTED and USAGE are safe to cache because
+ * they are determined at allocation time.
+ *
+ * @param buffer Buffer containing desired metadata
+ * @param standardMetadataType StandardMetadataType for the metadata value being queried
+ * @param destBuffer Pointer to a buffer in which to store the result of the get() call; if
+ * null, the computed output size or error must still be returned.
+ * @param destBufferSize How large the destBuffer buffer is. If destBuffer is null this must be
+ * 0.
+ * @return The number of bytes written to `destBuffer` or which would have been written
+ * if `destBufferSize` was large enough.
+ * A negative value indicates an error, which may be
+ * - `BAD_BUFFER` if the raw handle is invalid.
+ * - `UNSUPPORTED` when metadataType is unknown/unsupported.
+ * IMapper must support getting all StandardMetadataType.aidl values defined
+ * at the time the device first launches.
+ */
+ int32_t (*_Nonnull getStandardMetadata)(buffer_handle_t _Nonnull buffer,
+ int64_t standardMetadataType,
+ void* _Nullable destBuffer, size_t destBufferSize);
+
+ /**
+ * Sets the global value for a given MetadataType.
+ *
+ * Metadata fields are not required to be settable. This function can
+ * return Error::UNSUPPORTED whenever it doesn't support setting a
+ * particular Metadata field.
+ *
+ * The framework will attempt to set the following StandardMetadataType
+ * values: DATASPACE, SMPTE2086, CTA861_3, SMPTE2094_40 and BLEND_MODE.
+ * We require everyone to support setting those fields. If a device's Composer
+ * implementation supports a field, it should be supported here. Over time these
+ * metadata fields will be moved out of Composer/BufferQueue/etc. and into the
+ * buffer's Metadata fields.
+ *
+ * @param buffer Buffer receiving desired metadata
+ * @param metadataType MetadataType for the metadata value being set
+ * @param metadata Pointer to a buffer of bytes representing the value associated with
+ * @param metadataSize The size of the metadata buffer
+ * @return error Error status of the call, which may be
+ * - `NONE` upon success.
+ * - `BAD_BUFFER` if the raw handle is invalid.
+ * - `BAD_VALUE` when the field is constant and can never be set (such as
+ * BUFFER_ID, NAME, WIDTH, HEIGHT, LAYER_COUNT, PIXEL_FORMAT_REQUESTED and
+ * USAGE)
+ * - `NO_RESOURCES` if the set cannot be fulfilled due to unavailability of
+ * resources.
+ * - `UNSUPPORTED` when metadataType is unknown/unsupported or setting
+ * it is unsupported. Unsupported should also be returned if the metadata
+ * is malformed.
+ */
+ AIMapper_Error (*_Nonnull setMetadata)(buffer_handle_t _Nonnull buffer,
+ AIMapper_MetadataType metadataType,
+ const void* _Nonnull metadata, size_t metadataSize);
+
+ /**
+ * Sets the global value for a given MetadataType.
+ *
+ * This is equivalent to `setMetadata` when passed an AIMapper_MetadataType with name
+ * set to "android.hardware.graphics.common.StandardMetadataType"
+ *
+ * Metadata fields are not required to be settable. This function can
+ * return Error::UNSUPPORTED whenever it doesn't support setting a
+ * particular Metadata field.
+ *
+ * The framework will attempt to set the following StandardMetadataType
+ * values: DATASPACE, SMPTE2086, CTA861_3, SMPTE2094_40 and BLEND_MODE.
+ * We require everyone to support setting those fields. If a device's Composer
+ * implementation supports a field, it should be supported here. Over time these
+ * metadata fields will be moved out of Composer/BufferQueue/etc. and into the
+ * buffer's Metadata fields.
+ *
+ * @param buffer Buffer receiving desired metadata
+ * @param standardMetadataType StandardMetadataType for the metadata value being set
+ * @param metadata Pointer to a buffer of bytes representing the value associated with
+ * @param metadataSize The size of the metadata buffer
+ * @return error Error status of the call, which may be
+ * - `NONE` upon success.
+ * - `BAD_BUFFER` if the raw handle is invalid.
+ * - `BAD_VALUE` when the field is constant and can never be set (such as
+ * BUFFER_ID, NAME, WIDTH, HEIGHT, LAYER_COUNT, PIXEL_FORMAT_REQUESTED and
+ * USAGE)
+ * - `NO_RESOURCES` if the set cannot be fulfilled due to unavailability of
+ * resources.
+ * - `UNSUPPORTED` when metadataType is unknown/unsupported or setting
+ * it is unsupported. Unsupported should also be returned if the metadata
+ * is malformed.
+ */
+ AIMapper_Error (*_Nonnull setStandardMetadata)(buffer_handle_t _Nonnull buffer,
+ int64_t standardMetadataType,
+ const void* _Nonnull metadata,
+ size_t metadataSize);
+
+ /**
+ * Lists all the MetadataTypes supported by IMapper as well as a description
+ * of each supported MetadataType. For StandardMetadataTypes, the description
+ * string can be left empty.
+ *
+ * This list is expected to be static & thus the returned array must be valid for the
+ * lifetime of the process.
+ *
+ * @param outDescriptionList The list of descriptions
+ * @param outNumberOfDescriptions How many descriptions are in `outDescriptionList`
+ * @return error Error status of the call, which may be
+ * - `NONE` upon success.
+ * - `UNSUPPORTED` if there's any error
+ */
+ AIMapper_Error (*_Nonnull listSupportedMetadataTypes)(
+ const AIMapper_MetadataTypeDescription* _Nullable* _Nonnull outDescriptionList,
+ size_t* _Nonnull outNumberOfDescriptions);
+
+ /**
+ * Dumps a buffer's metadata.
+ *
+ * @param buffer The buffer to dump the metadata for
+ * @param dumpBufferCallback Callback that will be invoked for each of the metadata fields
+ * @param context A caller-provided context to be passed to the dumpBufferCallback
+ * @return error Error status of the call, which may be
+ * - `NONE` upon success.
+ * - `BAD_BUFFER` if the raw handle is invalid.
+ * - `NO_RESOURCES` if the get cannot be fulfilled due to unavailability of
+ * resources.
+ */
+ AIMapper_Error (*_Nonnull dumpBuffer)(buffer_handle_t _Nonnull buffer,
+ AIMapper_DumpBufferCallback _Nonnull dumpBufferCallback,
+ void* _Null_unspecified context);
+
+ /**
+ * Dump the metadata for all imported buffers in the current process
+ *
+ * The HAL implementation should invoke beginDumpCallback before dumping a buffer's metadata,
+ * followed by N calls to dumpBufferCallback for that buffer's metadata fields. The call
+ * sequence should follow this pseudocode:
+ *
+ * for (auto buffer : gListOfImportedBuffers) {
+ * beginDumpCallback(context);
+ * for (auto metadata : buffer->allMetadata()) {
+ * dumpBufferCallback(context, metadata...);
+ * }
+ * }
+ *
+ * @param beginDumpCallback Signals that a buffer is about to be dumped
+ * @param dumpBufferCallback Callback that will be invoked for each of the metadata fields
+ * @param context A caller-provided context to be passed to beginDumpCallback and
+ * dumpBufferCallback
+ * @return error Error status of the call, which may be
+ * - `NONE` upon success.
+ * - `BAD_BUFFER` if the raw handle is invalid.
+ * - `NO_RESOURCES` if the get cannot be fulfilled due to unavailability of
+ * resources.
+ */
+ AIMapper_Error (*_Nonnull dumpAllBuffers)(
+ AIMapper_BeginDumpBufferCallback _Nonnull beginDumpCallback,
+ AIMapper_DumpBufferCallback _Nonnull dumpBufferCallback,
+ void* _Null_unspecified context);
+
+ /**
+ * Returns the region of shared memory associated with the buffer that is
+ * reserved for client use.
+ *
+ * The shared memory may be allocated from any shared memory allocator.
+ * The shared memory must be CPU-accessible and virtually contiguous. The
+ * starting address must be word-aligned.
+ *
+ * This function may only be called after importBuffer() has been called by the
+ * client. The reserved region must remain accessible until freeBuffer() has
+ * been called. After freeBuffer() has been called, the client must not access
+ * the reserved region.
+ *
+ * This reserved memory may be used in future versions of Android to
+ * help clients implement backwards compatible features without requiring
+ * IAllocator/IMapper updates.
+ *
+ * @param buffer Imported buffer handle.
+ * @param outReservedRegion CPU-accessible pointer to the reserved region
+ * @param outReservedSize the size of the reservedRegion that was requested
+ * in the BufferDescriptorInfo.
+ * @return error Error status of the call, which may be
+ * - `NONE` upon success.
+ * - `BAD_BUFFER` if the buffer is invalid.
+ */
+ AIMapper_Error (*_Nonnull getReservedRegion)(buffer_handle_t _Nonnull buffer,
+ void* _Nullable* _Nonnull outReservedRegion,
+ uint64_t* _Nonnull outReservedSize);
+
+} AIMapperV5;
+
+/**
+ * Return value for AIMapper_loadIMapper
+ *
+ * Note: This struct's size is not fixed and callers must never store it by-value as a result.
+ * Only fields up to those covered by `version` are allowed to be accessed.
+ */
+typedef struct AIMapper {
+ alignas(alignof(max_align_t)) AIMapper_Version version;
+ AIMapperV5 v5;
+} AIMapper;
+
+/**
+ * Loads the vendor-provided implementation of AIMapper
+ * @return Error status of the call.
+ * - `NONE` upon success
+ * - `UNSUPPORTED` if no implementation is available
+ */
+AIMapper_Error AIMapper_loadIMapper(AIMapper* _Nullable* _Nonnull outImplementation);
+
+__END_DECLS
\ No newline at end of file
diff --git a/graphics/mapper/stable-c/vts/VtsHalGraphicsMapperStableC_TargetTest.cpp b/graphics/mapper/stable-c/vts/VtsHalGraphicsMapperStableC_TargetTest.cpp
new file mode 100644
index 0000000..6ab11a3
--- /dev/null
+++ b/graphics/mapper/stable-c/vts/VtsHalGraphicsMapperStableC_TargetTest.cpp
@@ -0,0 +1,1565 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "VtsHalGraphicsMapperStableC_TargetTest"
+
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/graphics/allocator/AllocationError.h>
+#include <aidl/android/hardware/graphics/allocator/AllocationResult.h>
+#include <aidl/android/hardware/graphics/allocator/IAllocator.h>
+#include <aidl/android/hardware/graphics/common/BufferUsage.h>
+#include <aidl/android/hardware/graphics/common/PixelFormat.h>
+#include <aidlcommonsupport/NativeHandle.h>
+#include <android/binder_manager.h>
+#include <android/dlext.h>
+#include <android/hardware/graphics/mapper/IMapper.h>
+#include <android/hardware/graphics/mapper/utils/IMapperMetadataTypes.h>
+#include <gralloctypes/Gralloc4.h>
+#include <hidl/GtestPrinter.h>
+#include <system/graphics.h>
+
+#include <dlfcn.h>
+#include <drm/drm_fourcc.h>
+#include <gtest/gtest.h>
+#include <vndksupport/linker.h>
+#include <initializer_list>
+#include <optional>
+#include <string>
+#include <tuple>
+#include <vector>
+
+using namespace aidl::android::hardware::graphics::allocator;
+using namespace aidl::android::hardware::graphics::common;
+using namespace android;
+using namespace android::hardware;
+using namespace ::android::hardware::graphics::mapper;
+
+typedef AIMapper_Error (*AIMapper_loadIMapperFn)(AIMapper* _Nullable* _Nonnull outImplementation);
+
+inline constexpr BufferUsage operator|(BufferUsage lhs, BufferUsage rhs) {
+ using T = std::underlying_type_t<BufferUsage>;
+ return static_cast<BufferUsage>(static_cast<T>(lhs) | static_cast<T>(rhs));
+}
+
+inline BufferUsage& operator|=(BufferUsage& lhs, BufferUsage rhs) {
+ lhs = lhs | rhs;
+ return lhs;
+}
+
+struct YCbCr {
+ android_ycbcr yCbCr;
+ int64_t horizontalSubSampling;
+ int64_t verticalSubSampling;
+};
+
+class BufferHandle {
+ AIMapper* mIMapper;
+ buffer_handle_t mHandle = nullptr;
+
+ public:
+ explicit BufferHandle(AIMapper* mapper, native_handle_t* rawHandle) : mIMapper(mapper) {
+ EXPECT_EQ(AIMAPPER_ERROR_NONE, mIMapper->v5.importBuffer(rawHandle, &mHandle));
+ }
+
+ explicit BufferHandle(BufferHandle&& other) { *this = std::move(other); }
+
+ BufferHandle& operator=(BufferHandle&& other) noexcept {
+ reset();
+ mIMapper = other.mIMapper;
+ mHandle = other.mHandle;
+ other.mHandle = nullptr;
+ return *this;
+ }
+
+ ~BufferHandle() { reset(); }
+
+ constexpr explicit operator bool() const noexcept { return mHandle != nullptr; }
+
+ buffer_handle_t operator*() const noexcept { return mHandle; }
+
+ void reset() {
+ if (mHandle != nullptr) {
+ EXPECT_EQ(AIMAPPER_ERROR_NONE, mIMapper->v5.freeBuffer(mHandle));
+ mHandle = nullptr;
+ }
+ }
+};
+
+class BufferAllocation {
+ AIMapper* mIMapper;
+ native_handle_t* mRawHandle;
+ uint32_t mStride;
+ const BufferDescriptorInfo mInfo;
+
+ public:
+ BufferAllocation(const BufferAllocation&) = delete;
+ void operator=(const BufferAllocation&) = delete;
+
+ BufferAllocation(AIMapper* mapper, native_handle_t* handle, uint32_t stride,
+ const BufferDescriptorInfo& info)
+ : mIMapper(mapper), mRawHandle(handle), mStride(stride), mInfo(info) {}
+
+ ~BufferAllocation() {
+ if (mRawHandle == nullptr) return;
+
+ native_handle_close(mRawHandle);
+ native_handle_delete(mRawHandle);
+ }
+
+ uint32_t stride() const { return mStride; }
+ const BufferDescriptorInfo& info() const { return mInfo; }
+
+ BufferHandle import() { return BufferHandle{mIMapper, mRawHandle}; }
+
+ const native_handle_t* rawHandle() const { return mRawHandle; }
+};
+
+class GraphicsTestsBase {
+ private:
+ friend class BufferAllocation;
+ int32_t mIAllocatorVersion = 1;
+ std::shared_ptr<IAllocator> mAllocator;
+ AIMapper* mIMapper = nullptr;
+ AIMapper_loadIMapperFn mIMapperLoader;
+
+ protected:
+ void Initialize(std::shared_ptr<IAllocator> allocator) {
+ mAllocator = allocator;
+ ASSERT_NE(nullptr, mAllocator.get()) << "failed to get allocator service";
+ ASSERT_TRUE(mAllocator->getInterfaceVersion(&mIAllocatorVersion).isOk());
+ ASSERT_GE(mIAllocatorVersion, 2);
+ std::string mapperSuffix;
+ auto status = mAllocator->getIMapperLibrarySuffix(&mapperSuffix);
+ ASSERT_TRUE(status.isOk()) << "Failed to get IMapper library suffix";
+ std::string lib_name = "mapper." + mapperSuffix + ".so";
+ void* so = android_load_sphal_library(lib_name.c_str(), RTLD_LOCAL | RTLD_NOW);
+ ASSERT_NE(nullptr, so) << "Failed to load " << lib_name;
+ mIMapperLoader = (AIMapper_loadIMapperFn)dlsym(so, "AIMapper_loadIMapper");
+ ASSERT_NE(nullptr, mIMapperLoader) << "AIMapper_locaIMapper missing from " << lib_name;
+ ASSERT_EQ(AIMAPPER_ERROR_NONE, mIMapperLoader(&mIMapper));
+ ASSERT_NE(mIMapper, nullptr);
+ }
+
+ public:
+ AIMapper_loadIMapperFn getIMapperLoader() const { return mIMapperLoader; }
+
+ std::unique_ptr<BufferAllocation> allocate(const BufferDescriptorInfo& descriptorInfo) {
+ AllocationResult result;
+ ::ndk::ScopedAStatus status = mAllocator->allocate2(descriptorInfo, 1, &result);
+ if (!status.isOk()) {
+ status_t error = status.getExceptionCode();
+ if (error == EX_SERVICE_SPECIFIC) {
+ error = status.getServiceSpecificError();
+ EXPECT_NE(OK, error) << "Failed to set error properly";
+ } else {
+ EXPECT_EQ(OK, error) << "Allocation transport failure";
+ }
+ return nullptr;
+ } else {
+ return std::make_unique<BufferAllocation>(mIMapper, dupFromAidl(result.buffers[0]),
+ result.stride, descriptorInfo);
+ }
+ }
+
+ std::unique_ptr<BufferAllocation> allocateGeneric() {
+ return allocate({
+ .name = {"VTS_TEMP"},
+ .width = 64,
+ .height = 64,
+ .layerCount = 1,
+ .format = PixelFormat::RGBA_8888,
+ .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN,
+ .reservedSize = 0,
+ });
+ }
+
+ bool isSupported(const BufferDescriptorInfo& descriptorInfo) {
+ bool ret = false;
+ EXPECT_TRUE(mAllocator->isSupported(descriptorInfo, &ret).isOk());
+ return ret;
+ }
+
+ AIMapper* mapper() const { return mIMapper; }
+
+ template <StandardMetadataType T>
+ auto getStandardMetadata(buffer_handle_t bufferHandle)
+ -> decltype(StandardMetadata<T>::value::decode(nullptr, 0)) {
+ using Value = typename StandardMetadata<T>::value;
+ std::vector<uint8_t> buffer;
+ // Initial guess
+ buffer.resize(512);
+ int32_t sizeRequired = mapper()->v5.getStandardMetadata(
+ bufferHandle, static_cast<int64_t>(T), buffer.data(), buffer.size());
+ if (sizeRequired < 0) {
+ EXPECT_EQ(-AIMAPPER_ERROR_UNSUPPORTED, sizeRequired)
+ << "Received something other than UNSUPPORTED from valid getStandardMetadata "
+ "call";
+ return std::nullopt;
+ }
+ if (sizeRequired > buffer.size()) {
+ buffer.resize(sizeRequired);
+ sizeRequired = mapper()->v5.getStandardMetadata(bufferHandle, static_cast<int64_t>(T),
+ buffer.data(), buffer.size());
+ }
+ if (sizeRequired < 0 || sizeRequired >= buffer.size()) {
+ ADD_FAILURE() << "getStandardMetadata failed, received " << sizeRequired
+ << " with buffer size " << buffer.size();
+ // Generate a fail type
+ return std::nullopt;
+ }
+ return Value::decode(buffer.data(), sizeRequired);
+ }
+
+ template <StandardMetadataType T>
+ AIMapper_Error setStandardMetadata(buffer_handle_t bufferHandle,
+ const typename StandardMetadata<T>::value_type& value) {
+ using Value = typename StandardMetadata<T>::value;
+ int32_t sizeRequired = Value::encode(value, nullptr, 0);
+ if (sizeRequired < 0) {
+ EXPECT_GE(sizeRequired, 0) << "Failed to calculate required size";
+ return static_cast<AIMapper_Error>(-sizeRequired);
+ }
+ std::vector<uint8_t> buffer;
+ buffer.resize(sizeRequired);
+ sizeRequired = Value::encode(value, buffer.data(), buffer.size());
+ if (sizeRequired < 0 || sizeRequired > buffer.size()) {
+ ADD_FAILURE() << "Failed to encode with calculated size " << sizeRequired
+ << "; buffer size" << buffer.size();
+ return static_cast<AIMapper_Error>(-sizeRequired);
+ }
+ return mapper()->v5.setStandardMetadata(bufferHandle, static_cast<int64_t>(T),
+ buffer.data(), sizeRequired);
+ }
+
+ void verifyRGBA8888PlaneLayouts(const std::vector<PlaneLayout>& planeLayouts) {
+ ASSERT_EQ(1, planeLayouts.size());
+
+ const auto& planeLayout = planeLayouts.front();
+
+ ASSERT_EQ(4, planeLayout.components.size());
+
+ int64_t offsetInBitsR = -1;
+ int64_t offsetInBitsG = -1;
+ int64_t offsetInBitsB = -1;
+ int64_t offsetInBitsA = -1;
+
+ for (const auto& component : planeLayout.components) {
+ if (!gralloc4::isStandardPlaneLayoutComponentType(component.type)) {
+ continue;
+ }
+ EXPECT_EQ(8, component.sizeInBits);
+ if (component.type.value == gralloc4::PlaneLayoutComponentType_R.value) {
+ offsetInBitsR = component.offsetInBits;
+ }
+ if (component.type.value == gralloc4::PlaneLayoutComponentType_G.value) {
+ offsetInBitsG = component.offsetInBits;
+ }
+ if (component.type.value == gralloc4::PlaneLayoutComponentType_B.value) {
+ offsetInBitsB = component.offsetInBits;
+ }
+ if (component.type.value == gralloc4::PlaneLayoutComponentType_A.value) {
+ offsetInBitsA = component.offsetInBits;
+ }
+ }
+
+ EXPECT_EQ(0, offsetInBitsR);
+ EXPECT_EQ(8, offsetInBitsG);
+ EXPECT_EQ(16, offsetInBitsB);
+ EXPECT_EQ(24, offsetInBitsA);
+
+ EXPECT_EQ(0, planeLayout.offsetInBytes);
+ EXPECT_EQ(32, planeLayout.sampleIncrementInBits);
+ // Skip testing stride because any stride is valid
+ EXPECT_LE(planeLayout.widthInSamples * planeLayout.heightInSamples * 4,
+ planeLayout.totalSizeInBytes);
+ EXPECT_EQ(1, planeLayout.horizontalSubsampling);
+ EXPECT_EQ(1, planeLayout.verticalSubsampling);
+ }
+
+ void fillRGBA8888(uint8_t* data, uint32_t height, size_t strideInBytes, size_t widthInBytes) {
+ for (uint32_t y = 0; y < height; y++) {
+ memset(data, y, widthInBytes);
+ data += strideInBytes;
+ }
+ }
+
+ void verifyRGBA8888(const buffer_handle_t bufferHandle, const uint8_t* data, uint32_t height,
+ size_t strideInBytes, size_t widthInBytes) {
+ auto decodeResult = getStandardMetadata<StandardMetadataType::PLANE_LAYOUTS>(bufferHandle);
+ ASSERT_TRUE(decodeResult.has_value());
+ const auto& planeLayouts = *decodeResult;
+ ASSERT_TRUE(planeLayouts.size() > 0);
+
+ verifyRGBA8888PlaneLayouts(planeLayouts);
+
+ for (uint32_t y = 0; y < height; y++) {
+ for (size_t i = 0; i < widthInBytes; i++) {
+ EXPECT_EQ(static_cast<uint8_t>(y), data[i]);
+ }
+ data += strideInBytes;
+ }
+ }
+
+ void traverseYCbCrData(const android_ycbcr& yCbCr, int32_t width, int32_t height,
+ int64_t hSubsampling, int64_t vSubsampling,
+ std::function<void(uint8_t*, uint8_t)> traverseFuncion) {
+ auto yData = static_cast<uint8_t*>(yCbCr.y);
+ auto cbData = static_cast<uint8_t*>(yCbCr.cb);
+ auto crData = static_cast<uint8_t*>(yCbCr.cr);
+ auto yStride = yCbCr.ystride;
+ auto cStride = yCbCr.cstride;
+ auto chromaStep = yCbCr.chroma_step;
+
+ for (uint32_t y = 0; y < height; y++) {
+ for (uint32_t x = 0; x < width; x++) {
+ auto val = static_cast<uint8_t>(height * y + x);
+
+ traverseFuncion(yData + yStride * y + x, val);
+
+ if (y % vSubsampling == 0 && x % hSubsampling == 0) {
+ uint32_t subSampleX = x / hSubsampling;
+ uint32_t subSampleY = y / vSubsampling;
+ const auto subSampleOffset = cStride * subSampleY + chromaStep * subSampleX;
+ const auto subSampleVal =
+ static_cast<uint8_t>(height * subSampleY + subSampleX);
+
+ traverseFuncion(cbData + subSampleOffset, subSampleVal);
+ traverseFuncion(crData + subSampleOffset, subSampleVal + 1);
+ }
+ }
+ }
+ }
+
+ void fillYCbCrData(const android_ycbcr& yCbCr, int32_t width, int32_t height,
+ int64_t hSubsampling, int64_t vSubsampling) {
+ traverseYCbCrData(yCbCr, width, height, hSubsampling, vSubsampling,
+ [](auto address, auto fillingData) { *address = fillingData; });
+ }
+
+ void verifyYCbCrData(const android_ycbcr& yCbCr, int32_t width, int32_t height,
+ int64_t hSubsampling, int64_t vSubsampling) {
+ traverseYCbCrData(
+ yCbCr, width, height, hSubsampling, vSubsampling,
+ [](auto address, auto expectedData) { EXPECT_EQ(*address, expectedData); });
+ }
+
+ constexpr uint64_t bitsToBytes(int64_t bits) { return bits / 8; }
+ constexpr uint64_t bytesToBits(int64_t bytes) { return bytes * 8; }
+
+ void getAndroidYCbCr(buffer_handle_t bufferHandle, uint8_t* data, android_ycbcr* outYCbCr,
+ int64_t* hSubsampling, int64_t* vSubsampling) {
+ auto decodeResult = getStandardMetadata<StandardMetadataType::PLANE_LAYOUTS>(bufferHandle);
+ ASSERT_TRUE(decodeResult.has_value());
+ const auto& planeLayouts = *decodeResult;
+ ASSERT_TRUE(planeLayouts.size() > 0);
+
+ outYCbCr->y = nullptr;
+ outYCbCr->cb = nullptr;
+ outYCbCr->cr = nullptr;
+ outYCbCr->ystride = 0;
+ outYCbCr->cstride = 0;
+ outYCbCr->chroma_step = 0;
+
+ for (const auto& planeLayout : planeLayouts) {
+ for (const auto& planeLayoutComponent : planeLayout.components) {
+ if (!gralloc4::isStandardPlaneLayoutComponentType(planeLayoutComponent.type)) {
+ continue;
+ }
+ ASSERT_EQ(0, planeLayoutComponent.offsetInBits % 8);
+
+ uint8_t* tmpData = data + planeLayout.offsetInBytes +
+ bitsToBytes(planeLayoutComponent.offsetInBits);
+ uint64_t sampleIncrementInBytes;
+
+ auto type = static_cast<PlaneLayoutComponentType>(planeLayoutComponent.type.value);
+ switch (type) {
+ case PlaneLayoutComponentType::Y:
+ ASSERT_EQ(nullptr, outYCbCr->y);
+ ASSERT_EQ(8, planeLayoutComponent.sizeInBits);
+ ASSERT_EQ(8, planeLayout.sampleIncrementInBits);
+ outYCbCr->y = tmpData;
+ outYCbCr->ystride = planeLayout.strideInBytes;
+ break;
+
+ case PlaneLayoutComponentType::CB:
+ case PlaneLayoutComponentType::CR:
+ ASSERT_EQ(0, planeLayout.sampleIncrementInBits % 8);
+
+ sampleIncrementInBytes = planeLayout.sampleIncrementInBits / 8;
+ ASSERT_TRUE(sampleIncrementInBytes == 1 || sampleIncrementInBytes == 2);
+
+ if (outYCbCr->cstride == 0 && outYCbCr->chroma_step == 0) {
+ outYCbCr->cstride = planeLayout.strideInBytes;
+ outYCbCr->chroma_step = sampleIncrementInBytes;
+ } else {
+ ASSERT_EQ(outYCbCr->cstride, planeLayout.strideInBytes);
+ ASSERT_EQ(outYCbCr->chroma_step, sampleIncrementInBytes);
+ }
+
+ if (*hSubsampling == 0 && *vSubsampling == 0) {
+ *hSubsampling = planeLayout.horizontalSubsampling;
+ *vSubsampling = planeLayout.verticalSubsampling;
+ } else {
+ ASSERT_EQ(*hSubsampling, planeLayout.horizontalSubsampling);
+ ASSERT_EQ(*vSubsampling, planeLayout.verticalSubsampling);
+ }
+
+ if (type == PlaneLayoutComponentType::CB) {
+ ASSERT_EQ(nullptr, outYCbCr->cb);
+ outYCbCr->cb = tmpData;
+ } else {
+ ASSERT_EQ(nullptr, outYCbCr->cr);
+ outYCbCr->cr = tmpData;
+ }
+ break;
+ default:
+ break;
+ };
+ }
+ }
+
+ ASSERT_NE(nullptr, outYCbCr->y);
+ ASSERT_NE(nullptr, outYCbCr->cb);
+ ASSERT_NE(nullptr, outYCbCr->cr);
+ }
+
+ YCbCr getAndroidYCbCr_P010(const native_handle_t* bufferHandle, uint8_t* data) {
+ YCbCr yCbCr_P010;
+ auto decodeResult = getStandardMetadata<StandardMetadataType::PLANE_LAYOUTS>(bufferHandle);
+ if (!decodeResult.has_value()) {
+ ADD_FAILURE() << "failed to get plane layout";
+ return YCbCr{};
+ }
+ const auto& planeLayouts = *decodeResult;
+ EXPECT_EQ(2, planeLayouts.size());
+ EXPECT_EQ(1, planeLayouts[0].components.size());
+ EXPECT_EQ(2, planeLayouts[1].components.size());
+
+ yCbCr_P010.yCbCr.y = nullptr;
+ yCbCr_P010.yCbCr.cb = nullptr;
+ yCbCr_P010.yCbCr.cr = nullptr;
+ yCbCr_P010.yCbCr.ystride = 0;
+ yCbCr_P010.yCbCr.cstride = 0;
+ yCbCr_P010.yCbCr.chroma_step = 0;
+ int64_t cb_offset = 0;
+ int64_t cr_offset = 0;
+
+ for (const auto& planeLayout : planeLayouts) {
+ for (const auto& planeLayoutComponent : planeLayout.components) {
+ if (!gralloc4::isStandardPlaneLayoutComponentType(planeLayoutComponent.type)) {
+ continue;
+ }
+
+ uint8_t* tmpData = data + planeLayout.offsetInBytes +
+ bitsToBytes(planeLayoutComponent.offsetInBits);
+ uint64_t sampleIncrementInBytes = 0;
+ auto type = static_cast<PlaneLayoutComponentType>(planeLayoutComponent.type.value);
+ switch (type) {
+ case PlaneLayoutComponentType::Y:
+ // For specs refer:
+ // https://docs.microsoft.com/en-us/windows/win32/medfound/10-bit-and-16-bit-yuv-video-formats
+ EXPECT_EQ(6, planeLayoutComponent.offsetInBits);
+ EXPECT_EQ(nullptr, yCbCr_P010.yCbCr.y);
+ EXPECT_EQ(10, planeLayoutComponent.sizeInBits);
+ EXPECT_EQ(16, planeLayout.sampleIncrementInBits);
+
+ yCbCr_P010.yCbCr.y = tmpData;
+ yCbCr_P010.yCbCr.ystride = planeLayout.strideInBytes;
+ break;
+
+ case PlaneLayoutComponentType::CB:
+ case PlaneLayoutComponentType::CR:
+ sampleIncrementInBytes = bitsToBytes(planeLayout.sampleIncrementInBits);
+ EXPECT_EQ(4, sampleIncrementInBytes);
+
+ if (yCbCr_P010.yCbCr.cstride == 0 && yCbCr_P010.yCbCr.chroma_step == 0) {
+ yCbCr_P010.yCbCr.cstride = planeLayout.strideInBytes;
+ yCbCr_P010.yCbCr.chroma_step = sampleIncrementInBytes;
+ } else {
+ EXPECT_EQ(yCbCr_P010.yCbCr.cstride, planeLayout.strideInBytes);
+ EXPECT_EQ(yCbCr_P010.yCbCr.chroma_step, sampleIncrementInBytes);
+ }
+
+ if (yCbCr_P010.horizontalSubSampling == 0 &&
+ yCbCr_P010.verticalSubSampling == 0) {
+ yCbCr_P010.horizontalSubSampling = planeLayout.horizontalSubsampling;
+ yCbCr_P010.verticalSubSampling = planeLayout.verticalSubsampling;
+ } else {
+ EXPECT_EQ(yCbCr_P010.horizontalSubSampling,
+ planeLayout.horizontalSubsampling);
+ EXPECT_EQ(yCbCr_P010.verticalSubSampling,
+ planeLayout.verticalSubsampling);
+ }
+
+ if (type == PlaneLayoutComponentType::CB) {
+ EXPECT_EQ(nullptr, yCbCr_P010.yCbCr.cb);
+ yCbCr_P010.yCbCr.cb = tmpData;
+ cb_offset = planeLayoutComponent.offsetInBits;
+ } else {
+ EXPECT_EQ(nullptr, yCbCr_P010.yCbCr.cr);
+ yCbCr_P010.yCbCr.cr = tmpData;
+ cr_offset = planeLayoutComponent.offsetInBits;
+ }
+ break;
+ default:
+ break;
+ };
+ }
+ }
+
+ EXPECT_EQ(cb_offset + bytesToBits(2), cr_offset);
+ EXPECT_NE(nullptr, yCbCr_P010.yCbCr.y);
+ EXPECT_NE(nullptr, yCbCr_P010.yCbCr.cb);
+ EXPECT_NE(nullptr, yCbCr_P010.yCbCr.cr);
+ return yCbCr_P010;
+ }
+};
+
+class GraphicsMapperStableCTests
+ : public GraphicsTestsBase,
+ public ::testing::TestWithParam<std::tuple<std::string, std::shared_ptr<IAllocator>>> {
+ public:
+ void SetUp() override { Initialize(std::get<1>(GetParam())); }
+
+ void TearDown() override {}
+};
+
+TEST_P(GraphicsMapperStableCTests, AllV5CallbacksDefined) {
+ ASSERT_GE(mapper()->version, AIMAPPER_VERSION_5);
+
+ EXPECT_TRUE(mapper()->v5.importBuffer);
+ EXPECT_TRUE(mapper()->v5.freeBuffer);
+ EXPECT_TRUE(mapper()->v5.getTransportSize);
+ EXPECT_TRUE(mapper()->v5.lock);
+ EXPECT_TRUE(mapper()->v5.unlock);
+ EXPECT_TRUE(mapper()->v5.flushLockedBuffer);
+ EXPECT_TRUE(mapper()->v5.rereadLockedBuffer);
+ EXPECT_TRUE(mapper()->v5.getMetadata);
+ EXPECT_TRUE(mapper()->v5.getStandardMetadata);
+ EXPECT_TRUE(mapper()->v5.setMetadata);
+ EXPECT_TRUE(mapper()->v5.setStandardMetadata);
+ EXPECT_TRUE(mapper()->v5.listSupportedMetadataTypes);
+ EXPECT_TRUE(mapper()->v5.dumpBuffer);
+ EXPECT_TRUE(mapper()->v5.getReservedRegion);
+}
+
+TEST_P(GraphicsMapperStableCTests, DualLoadIsIdentical) {
+ ASSERT_GE(mapper()->version, AIMAPPER_VERSION_5);
+ AIMapper* secondMapper;
+ ASSERT_EQ(AIMAPPER_ERROR_NONE, getIMapperLoader()(&secondMapper));
+
+ EXPECT_EQ(secondMapper->v5.importBuffer, mapper()->v5.importBuffer);
+ EXPECT_EQ(secondMapper->v5.freeBuffer, mapper()->v5.freeBuffer);
+ EXPECT_EQ(secondMapper->v5.getTransportSize, mapper()->v5.getTransportSize);
+ EXPECT_EQ(secondMapper->v5.lock, mapper()->v5.lock);
+ EXPECT_EQ(secondMapper->v5.unlock, mapper()->v5.unlock);
+ EXPECT_EQ(secondMapper->v5.flushLockedBuffer, mapper()->v5.flushLockedBuffer);
+ EXPECT_EQ(secondMapper->v5.rereadLockedBuffer, mapper()->v5.rereadLockedBuffer);
+ EXPECT_EQ(secondMapper->v5.getMetadata, mapper()->v5.getMetadata);
+ EXPECT_EQ(secondMapper->v5.getStandardMetadata, mapper()->v5.getStandardMetadata);
+ EXPECT_EQ(secondMapper->v5.setMetadata, mapper()->v5.setMetadata);
+ EXPECT_EQ(secondMapper->v5.setStandardMetadata, mapper()->v5.setStandardMetadata);
+ EXPECT_EQ(secondMapper->v5.listSupportedMetadataTypes, mapper()->v5.listSupportedMetadataTypes);
+ EXPECT_EQ(secondMapper->v5.dumpBuffer, mapper()->v5.dumpBuffer);
+ EXPECT_EQ(secondMapper->v5.getReservedRegion, mapper()->v5.getReservedRegion);
+}
+
+TEST_P(GraphicsMapperStableCTests, CanAllocate) {
+ auto buffer = allocate({
+ .name = {"VTS_TEMP"},
+ .width = 64,
+ .height = 64,
+ .layerCount = 1,
+ .format = PixelFormat::RGBA_8888,
+ .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN,
+ .reservedSize = 0,
+ });
+ ASSERT_NE(nullptr, buffer.get());
+ EXPECT_GE(buffer->stride(), 64);
+}
+
+TEST_P(GraphicsMapperStableCTests, ImportFreeBuffer) {
+ auto buffer = allocate({
+ .name = {"VTS_TEMP"},
+ .width = 64,
+ .height = 64,
+ .layerCount = 1,
+ .format = PixelFormat::RGBA_8888,
+ .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN,
+ .reservedSize = 0,
+ });
+ ASSERT_NE(nullptr, buffer.get());
+ EXPECT_GE(buffer->stride(), 64);
+
+ {
+ auto import1 = buffer->import();
+ auto import2 = buffer->import();
+ EXPECT_TRUE(import1);
+ EXPECT_TRUE(import2);
+ EXPECT_NE(*import1, *import2);
+ }
+}
+
+/**
+ * Test IMapper::importBuffer and IMapper::freeBuffer cross mapper instances.
+ */
+TEST_P(GraphicsMapperStableCTests, ImportFreeBufferSingleton) {
+ auto buffer = allocate({
+ .name = {"VTS_TEMP"},
+ .width = 64,
+ .height = 64,
+ .layerCount = 1,
+ .format = PixelFormat::RGBA_8888,
+ .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN,
+ .reservedSize = 0,
+ });
+ ASSERT_NE(nullptr, buffer.get());
+ EXPECT_GE(buffer->stride(), 64);
+
+ buffer_handle_t bufferHandle = nullptr;
+ ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.importBuffer(buffer->rawHandle(), &bufferHandle));
+ ASSERT_NE(nullptr, bufferHandle);
+
+ AIMapper* secondMapper;
+ ASSERT_EQ(AIMAPPER_ERROR_NONE, getIMapperLoader()(&secondMapper));
+ ASSERT_EQ(AIMAPPER_ERROR_NONE, secondMapper->v5.freeBuffer(bufferHandle));
+}
+
+/**
+ * Test IMapper::importBuffer with invalid buffers.
+ */
+TEST_P(GraphicsMapperStableCTests, ImportBufferNegative) {
+ native_handle_t* invalidHandle = nullptr;
+ buffer_handle_t bufferHandle = nullptr;
+ EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.importBuffer(invalidHandle, &bufferHandle))
+ << "importBuffer with nullptr did not fail with BAD_BUFFER";
+
+ invalidHandle = native_handle_create(0, 0);
+ EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.importBuffer(invalidHandle, &bufferHandle))
+ << "importBuffer with invalid handle did not fail with BAD_BUFFER";
+ native_handle_delete(invalidHandle);
+}
+
+/**
+ * Test IMapper::freeBuffer with invalid buffers.
+ */
+TEST_P(GraphicsMapperStableCTests, FreeBufferNegative) {
+ native_handle_t* bufferHandle = nullptr;
+ EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.freeBuffer(bufferHandle))
+ << "freeBuffer with nullptr did not fail with BAD_BUFFER";
+
+ bufferHandle = native_handle_create(0, 0);
+ EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.freeBuffer(bufferHandle))
+ << "freeBuffer with invalid handle did not fail with BAD_BUFFER";
+ native_handle_delete(bufferHandle);
+
+ auto buffer = allocateGeneric();
+ EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.freeBuffer(buffer->rawHandle()))
+ << "freeBuffer with un-imported handle did not fail with BAD_BUFFER";
+}
+
+/**
+ * Test IMapper::lock and IMapper::unlock.
+ */
+TEST_P(GraphicsMapperStableCTests, LockUnlockBasic) {
+ constexpr auto usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN;
+ auto buffer = allocate({
+ .name = {"VTS_TEMP"},
+ .width = 64,
+ .height = 64,
+ .layerCount = 1,
+ .format = PixelFormat::RGBA_8888,
+ .usage = usage,
+ .reservedSize = 0,
+ });
+ ASSERT_NE(nullptr, buffer.get());
+
+ // lock buffer for writing
+ const auto& info = buffer->info();
+ const auto stride = buffer->stride();
+ const ARect region{0, 0, info.width, info.height};
+ auto handle = buffer->import();
+ uint8_t* data = nullptr;
+ ASSERT_EQ(AIMAPPER_ERROR_NONE,
+ mapper()->v5.lock(*handle, static_cast<int64_t>(usage), region, -1, (void**)&data));
+
+ // RGBA_8888
+ fillRGBA8888(data, info.height, stride * 4, info.width * 4);
+
+ int releaseFence = -1;
+ ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence));
+
+ // lock again for reading
+ ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, static_cast<int64_t>(usage), region,
+ releaseFence, (void**)&data));
+ releaseFence = -1;
+
+ ASSERT_NO_FATAL_FAILURE(verifyRGBA8888(*handle, data, info.height, stride * 4, info.width * 4));
+
+ releaseFence = -1;
+ ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence));
+ if (releaseFence != -1) {
+ close(releaseFence);
+ }
+}
+
+/**
+ * Test multiple operations associated with different color formats
+ */
+TEST_P(GraphicsMapperStableCTests, Lock_YCRCB_420_SP) {
+ BufferDescriptorInfo info{
+ .name = {"VTS_TEMP"},
+ .width = 64,
+ .height = 64,
+ .layerCount = 1,
+ .format = PixelFormat::YCRCB_420_SP,
+ .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN,
+ .reservedSize = 0,
+ };
+ auto buffer = allocate(info);
+ if (!buffer) {
+ ASSERT_FALSE(isSupported(info));
+ GTEST_SUCCEED() << "YCRCB_420_SP format is unsupported";
+ return;
+ }
+
+ // lock buffer for writing
+ const ARect region{0, 0, info.width, info.height};
+ auto handle = buffer->import();
+ uint8_t* data = nullptr;
+ ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, static_cast<int64_t>(info.usage),
+ region, -1, (void**)&data));
+
+ android_ycbcr yCbCr;
+ int64_t hSubsampling = 0;
+ int64_t vSubsampling = 0;
+ ASSERT_NO_FATAL_FAILURE(getAndroidYCbCr(*handle, data, &yCbCr, &hSubsampling, &vSubsampling));
+
+ constexpr uint32_t kCbCrSubSampleFactor = 2;
+ ASSERT_EQ(kCbCrSubSampleFactor, hSubsampling);
+ ASSERT_EQ(kCbCrSubSampleFactor, vSubsampling);
+
+ auto cbData = static_cast<uint8_t*>(yCbCr.cb);
+ auto crData = static_cast<uint8_t*>(yCbCr.cr);
+ ASSERT_EQ(crData + 1, cbData);
+ ASSERT_EQ(2, yCbCr.chroma_step);
+
+ fillYCbCrData(yCbCr, info.width, info.height, hSubsampling, vSubsampling);
+
+ int releaseFence = -1;
+ ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence));
+
+ // lock again for reading
+ ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, static_cast<int64_t>(info.usage),
+ region, releaseFence, (void**)&data));
+ releaseFence = -1;
+
+ ASSERT_NO_FATAL_FAILURE(getAndroidYCbCr(*handle, data, &yCbCr, &hSubsampling, &vSubsampling));
+
+ verifyYCbCrData(yCbCr, info.width, info.height, hSubsampling, vSubsampling);
+
+ releaseFence = -1;
+ ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence));
+ if (releaseFence != -1) {
+ close(releaseFence);
+ }
+}
+
+TEST_P(GraphicsMapperStableCTests, YV12SubsampleMetadata) {
+ BufferDescriptorInfo info{
+ .name = {"VTS_TEMP"},
+ .width = 64,
+ .height = 64,
+ .layerCount = 1,
+ .format = PixelFormat::YV12,
+ .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN,
+ .reservedSize = 0,
+ };
+ auto buffer = allocate(info);
+ ASSERT_NE(nullptr, buffer.get());
+
+ // lock buffer for writing
+ const ARect region{0, 0, info.width, info.height};
+ auto handle = buffer->import();
+ uint8_t* data = nullptr;
+ ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, static_cast<int64_t>(info.usage),
+ region, -1, (void**)&data));
+
+ auto decodeResult = getStandardMetadata<StandardMetadataType::PLANE_LAYOUTS>(*handle);
+ ASSERT_TRUE(decodeResult.has_value());
+ const auto& planeLayouts = *decodeResult;
+
+ ASSERT_EQ(3, planeLayouts.size());
+
+ auto yPlane = planeLayouts[0];
+ auto crPlane = planeLayouts[1];
+ auto cbPlane = planeLayouts[2];
+
+ constexpr uint32_t kCbCrSubSampleFactor = 2;
+ EXPECT_EQ(kCbCrSubSampleFactor, crPlane.horizontalSubsampling);
+ EXPECT_EQ(kCbCrSubSampleFactor, crPlane.verticalSubsampling);
+
+ EXPECT_EQ(kCbCrSubSampleFactor, cbPlane.horizontalSubsampling);
+ EXPECT_EQ(kCbCrSubSampleFactor, cbPlane.verticalSubsampling);
+
+ const long chromaSampleWidth = info.width / kCbCrSubSampleFactor;
+ const long chromaSampleHeight = info.height / kCbCrSubSampleFactor;
+
+ EXPECT_EQ(info.width, yPlane.widthInSamples);
+ EXPECT_EQ(info.height, yPlane.heightInSamples);
+
+ EXPECT_EQ(chromaSampleWidth, crPlane.widthInSamples);
+ EXPECT_EQ(chromaSampleHeight, crPlane.heightInSamples);
+
+ EXPECT_EQ(chromaSampleWidth, cbPlane.widthInSamples);
+ EXPECT_EQ(chromaSampleHeight, cbPlane.heightInSamples);
+
+ EXPECT_LE(crPlane.widthInSamples, crPlane.strideInBytes);
+ EXPECT_LE(cbPlane.widthInSamples, cbPlane.strideInBytes);
+
+ int releaseFence = -1;
+ ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence));
+ if (releaseFence != -1) {
+ close(releaseFence);
+ }
+}
+
+TEST_P(GraphicsMapperStableCTests, Lock_YV12) {
+ BufferDescriptorInfo info{
+ .name = {"VTS_TEMP"},
+ .width = 64,
+ .height = 64,
+ .layerCount = 1,
+ .format = PixelFormat::YV12,
+ .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN,
+ .reservedSize = 0,
+ };
+ auto buffer = allocate(info);
+ ASSERT_NE(nullptr, buffer.get());
+
+ // lock buffer for writing
+ const ARect region{0, 0, info.width, info.height};
+ auto handle = buffer->import();
+ uint8_t* data = nullptr;
+ ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, static_cast<int64_t>(info.usage),
+ region, -1, (void**)&data));
+
+ android_ycbcr yCbCr;
+ int64_t hSubsampling = 0;
+ int64_t vSubsampling = 0;
+ ASSERT_NO_FATAL_FAILURE(getAndroidYCbCr(*handle, data, &yCbCr, &hSubsampling, &vSubsampling));
+
+ constexpr uint32_t kCbCrSubSampleFactor = 2;
+ ASSERT_EQ(kCbCrSubSampleFactor, hSubsampling);
+ ASSERT_EQ(kCbCrSubSampleFactor, vSubsampling);
+
+ auto cbData = static_cast<uint8_t*>(yCbCr.cb);
+ auto crData = static_cast<uint8_t*>(yCbCr.cr);
+ ASSERT_EQ(crData + yCbCr.cstride * info.height / vSubsampling, cbData);
+ ASSERT_EQ(1, yCbCr.chroma_step);
+
+ fillYCbCrData(yCbCr, info.width, info.height, hSubsampling, vSubsampling);
+
+ int releaseFence = -1;
+ ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence));
+
+ // lock again for reading
+ ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, static_cast<int64_t>(info.usage),
+ region, releaseFence, (void**)&data));
+ releaseFence = -1;
+
+ ASSERT_NO_FATAL_FAILURE(getAndroidYCbCr(*handle, data, &yCbCr, &hSubsampling, &vSubsampling));
+
+ verifyYCbCrData(yCbCr, info.width, info.height, hSubsampling, vSubsampling);
+
+ ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence));
+ if (releaseFence != -1) {
+ close(releaseFence);
+ }
+}
+
+TEST_P(GraphicsMapperStableCTests, Lock_YCBCR_420_888) {
+ BufferDescriptorInfo info{
+ .name = {"VTS_TEMP"},
+ .width = 64,
+ .height = 64,
+ .layerCount = 1,
+ .format = PixelFormat::YCBCR_420_888,
+ .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN,
+ .reservedSize = 0,
+ };
+ auto buffer = allocate(info);
+ ASSERT_NE(nullptr, buffer.get());
+
+ // lock buffer for writing
+ const ARect region{0, 0, info.width, info.height};
+ auto handle = buffer->import();
+ uint8_t* data = nullptr;
+ ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, static_cast<int64_t>(info.usage),
+ region, -1, (void**)&data));
+
+ android_ycbcr yCbCr;
+ int64_t hSubsampling = 0;
+ int64_t vSubsampling = 0;
+ ASSERT_NO_FATAL_FAILURE(getAndroidYCbCr(*handle, data, &yCbCr, &hSubsampling, &vSubsampling));
+
+ constexpr uint32_t kCbCrSubSampleFactor = 2;
+ ASSERT_EQ(kCbCrSubSampleFactor, hSubsampling);
+ ASSERT_EQ(kCbCrSubSampleFactor, vSubsampling);
+
+ fillYCbCrData(yCbCr, info.width, info.height, hSubsampling, vSubsampling);
+
+ int releaseFence = -1;
+ ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence));
+
+ // lock again for reading
+ ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, static_cast<int64_t>(info.usage),
+ region, releaseFence, (void**)&data));
+ releaseFence = -1;
+
+ ASSERT_NO_FATAL_FAILURE(getAndroidYCbCr(*handle, data, &yCbCr, &hSubsampling, &vSubsampling));
+
+ verifyYCbCrData(yCbCr, info.width, info.height, hSubsampling, vSubsampling);
+
+ ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence));
+ if (releaseFence != -1) {
+ close(releaseFence);
+ }
+}
+
+TEST_P(GraphicsMapperStableCTests, Lock_RAW10) {
+ BufferDescriptorInfo info{
+ .name = {"VTS_TEMP"},
+ .width = 64,
+ .height = 64,
+ .layerCount = 1,
+ .format = PixelFormat::RAW10,
+ .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN,
+ .reservedSize = 0,
+ };
+ auto buffer = allocate(info);
+ if (!buffer) {
+ ASSERT_FALSE(isSupported(info));
+ GTEST_SUCCEED() << "RAW10 format is unsupported";
+ return;
+ }
+
+ // lock buffer for writing
+ const ARect region{0, 0, info.width, info.height};
+ auto handle = buffer->import();
+ uint8_t* data = nullptr;
+ ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, static_cast<int64_t>(info.usage),
+ region, -1, (void**)&data));
+
+ auto decodeResult = getStandardMetadata<StandardMetadataType::PLANE_LAYOUTS>(*handle);
+ ASSERT_TRUE(decodeResult.has_value());
+ const auto& planeLayouts = *decodeResult;
+
+ ASSERT_EQ(1, planeLayouts.size());
+ auto planeLayout = planeLayouts[0];
+
+ EXPECT_EQ(0, planeLayout.sampleIncrementInBits);
+ EXPECT_EQ(1, planeLayout.horizontalSubsampling);
+ EXPECT_EQ(1, planeLayout.verticalSubsampling);
+
+ ASSERT_EQ(1, planeLayout.components.size());
+ auto planeLayoutComponent = planeLayout.components[0];
+
+ EXPECT_EQ(PlaneLayoutComponentType::RAW,
+ static_cast<PlaneLayoutComponentType>(planeLayoutComponent.type.value));
+ EXPECT_EQ(0, planeLayoutComponent.offsetInBits % 8);
+ EXPECT_EQ(-1, planeLayoutComponent.sizeInBits);
+
+ int releaseFence = -1;
+ ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence));
+ if (releaseFence != -1) {
+ close(releaseFence);
+ }
+}
+
+TEST_P(GraphicsMapperStableCTests, Lock_RAW12) {
+ BufferDescriptorInfo info{
+ .name = {"VTS_TEMP"},
+ .width = 64,
+ .height = 64,
+ .layerCount = 1,
+ .format = PixelFormat::RAW12,
+ .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN,
+ .reservedSize = 0,
+ };
+ auto buffer = allocate(info);
+ if (!buffer) {
+ ASSERT_FALSE(isSupported(info));
+ GTEST_SUCCEED() << "RAW12 format is unsupported";
+ return;
+ }
+
+ // lock buffer for writing
+ const ARect region{0, 0, info.width, info.height};
+ auto handle = buffer->import();
+ uint8_t* data = nullptr;
+ ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, static_cast<int64_t>(info.usage),
+ region, -1, (void**)&data));
+
+ auto decodeResult = getStandardMetadata<StandardMetadataType::PLANE_LAYOUTS>(*handle);
+ ASSERT_TRUE(decodeResult.has_value());
+ const auto& planeLayouts = *decodeResult;
+
+ ASSERT_EQ(1, planeLayouts.size());
+ auto planeLayout = planeLayouts[0];
+
+ EXPECT_EQ(0, planeLayout.sampleIncrementInBits);
+ EXPECT_EQ(1, planeLayout.horizontalSubsampling);
+ EXPECT_EQ(1, planeLayout.verticalSubsampling);
+
+ ASSERT_EQ(1, planeLayout.components.size());
+ auto planeLayoutComponent = planeLayout.components[0];
+
+ EXPECT_EQ(PlaneLayoutComponentType::RAW,
+ static_cast<PlaneLayoutComponentType>(planeLayoutComponent.type.value));
+ EXPECT_EQ(0, planeLayoutComponent.offsetInBits % 8);
+ EXPECT_EQ(-1, planeLayoutComponent.sizeInBits);
+
+ int releaseFence = -1;
+ ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence));
+ if (releaseFence != -1) {
+ close(releaseFence);
+ }
+}
+
+TEST_P(GraphicsMapperStableCTests, Lock_YCBCR_P010) {
+ BufferDescriptorInfo info{
+ .name = {"VTS_TEMP"},
+ .width = 64,
+ .height = 64,
+ .layerCount = 1,
+ .format = PixelFormat::YCBCR_P010,
+ .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN,
+ .reservedSize = 0,
+ };
+ auto buffer = allocate(info);
+ if (!buffer) {
+ ASSERT_FALSE(isSupported(info));
+ GTEST_SUCCEED() << "YCBCR_P010 format is unsupported";
+ return;
+ }
+
+ // lock buffer for writing
+ const ARect region{0, 0, info.width, info.height};
+ auto handle = buffer->import();
+ uint8_t* data = nullptr;
+ ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, static_cast<int64_t>(info.usage),
+ region, -1, (void**)&data));
+
+ YCbCr yCbCr;
+ ASSERT_NO_FATAL_FAILURE(yCbCr = getAndroidYCbCr_P010(*handle, data));
+
+ constexpr uint32_t kCbCrSubSampleFactor = 2;
+ ASSERT_EQ(kCbCrSubSampleFactor, yCbCr.horizontalSubSampling);
+ ASSERT_EQ(kCbCrSubSampleFactor, yCbCr.verticalSubSampling);
+
+ ASSERT_EQ(0, info.height % 2);
+
+ // fill the data
+ fillYCbCrData(yCbCr.yCbCr, info.width, info.height, yCbCr.horizontalSubSampling,
+ yCbCr.verticalSubSampling);
+ // verify the YCbCr data
+ verifyYCbCrData(yCbCr.yCbCr, info.width, info.height, yCbCr.horizontalSubSampling,
+ yCbCr.verticalSubSampling);
+
+ int releaseFence = -1;
+ ASSERT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence));
+ if (releaseFence != -1) {
+ close(releaseFence);
+ }
+}
+
+TEST_P(GraphicsMapperStableCTests, LockBadAccessRegion) {
+ auto buffer = allocateGeneric();
+ ASSERT_NE(nullptr, buffer);
+ const auto& info = buffer->info();
+
+ // lock buffer for writing
+ const ARect region{0, 0, info.width * 2, info.height * 2};
+ auto handle = buffer->import();
+ uint8_t* data = nullptr;
+ EXPECT_EQ(AIMAPPER_ERROR_BAD_VALUE, mapper()->v5.lock(*handle, static_cast<int64_t>(info.usage),
+ region, -1, (void**)&data));
+}
+
+TEST_P(GraphicsMapperStableCTests, UnlockNegative) {
+ native_handle_t* invalidHandle = nullptr;
+ int releaseFence = -1;
+ EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.unlock(invalidHandle, &releaseFence))
+ << "unlock with nullptr did not fail with BAD_BUFFER";
+
+ invalidHandle = native_handle_create(0, 0);
+ EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.unlock(invalidHandle, &releaseFence))
+ << "unlock with invalid handle did not fail with BAD_BUFFER";
+ native_handle_delete(invalidHandle);
+
+ auto buffer = allocateGeneric();
+ EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.unlock(buffer->rawHandle(), &releaseFence))
+ << "unlock with un-imported handle did not fail with BAD_BUFFER";
+}
+
+TEST_P(GraphicsMapperStableCTests, UnlockNotImported) {
+ int releaseFence = -1;
+ auto buffer = allocateGeneric();
+ ASSERT_TRUE(buffer);
+ EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.unlock(buffer->rawHandle(), &releaseFence))
+ << "unlock with un-imported handle did not fail with BAD_BUFFER";
+}
+
+TEST_P(GraphicsMapperStableCTests, UnlockNotLocked) {
+ int releaseFence = -1;
+ auto buffer = allocateGeneric();
+ ASSERT_TRUE(buffer);
+ auto bufferHandle = buffer->import();
+ ASSERT_TRUE(bufferHandle);
+ EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.unlock(*bufferHandle, &releaseFence))
+ << "unlock with unlocked handle did not fail with BAD_BUFFER";
+}
+
+TEST_P(GraphicsMapperStableCTests, LockUnlockNested) {
+ auto buffer = allocateGeneric();
+ ASSERT_TRUE(buffer);
+ auto bufferHandle = buffer->import();
+ ASSERT_TRUE(bufferHandle);
+ const ARect region{0, 0, buffer->info().width, buffer->info().height};
+ auto usage = static_cast<int64_t>(buffer->info().usage);
+ auto handle = buffer->import();
+ uint8_t* data = nullptr;
+ EXPECT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, usage, region, -1, (void**)&data));
+ EXPECT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.lock(*handle, usage, region, -1, (void**)&data))
+ << "Second lock failed";
+ int releaseFence = -1;
+ EXPECT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence));
+ if (releaseFence != -1) {
+ close(releaseFence);
+ releaseFence = -1;
+ }
+ EXPECT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*handle, &releaseFence))
+ << "Second unlock failed";
+ if (releaseFence != -1) {
+ close(releaseFence);
+ releaseFence = -1;
+ }
+ EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.unlock(*handle, &releaseFence))
+ << "Third, unmatched, unlock should have failed with BAD_BUFFER";
+}
+
+TEST_P(GraphicsMapperStableCTests, FlushRereadBasic) {
+ auto buffer = allocateGeneric();
+ ASSERT_TRUE(buffer);
+ auto bufferHandle = buffer->import();
+ ASSERT_TRUE(bufferHandle);
+ const auto& info = buffer->info();
+ const auto stride = buffer->stride();
+ const ARect region{0, 0, buffer->info().width, buffer->info().height};
+
+ auto writeHandle = buffer->import();
+ auto readHandle = buffer->import();
+ ASSERT_TRUE(writeHandle && readHandle);
+
+ // lock buffer for writing
+
+ uint8_t* writeData;
+ EXPECT_EQ(AIMAPPER_ERROR_NONE,
+ mapper()->v5.lock(*writeHandle, static_cast<uint64_t>(BufferUsage::CPU_WRITE_OFTEN),
+ region, -1, (void**)&writeData));
+
+ uint8_t* readData;
+ EXPECT_EQ(AIMAPPER_ERROR_NONE,
+ mapper()->v5.lock(*readHandle, static_cast<uint64_t>(BufferUsage::CPU_READ_OFTEN),
+ region, -1, (void**)&readData));
+
+ fillRGBA8888(writeData, info.height, stride * 4, info.width * 4);
+
+ EXPECT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.flushLockedBuffer(*writeHandle));
+ EXPECT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.rereadLockedBuffer(*readHandle));
+
+ ASSERT_NO_FATAL_FAILURE(
+ verifyRGBA8888(*readHandle, readData, info.height, stride * 4, info.width * 4));
+
+ int releaseFence = -1;
+
+ EXPECT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*readHandle, &releaseFence));
+ if (releaseFence != -1) {
+ close(releaseFence);
+ releaseFence = -1;
+ }
+
+ EXPECT_EQ(AIMAPPER_ERROR_NONE, mapper()->v5.unlock(*writeHandle, &releaseFence));
+ if (releaseFence != -1) {
+ close(releaseFence);
+ releaseFence = -1;
+ }
+}
+
+TEST_P(GraphicsMapperStableCTests, FlushLockedBufferBadBuffer) {
+ // Amazingly this is enough to make the compiler happy even though flushLockedBuffer
+ // is _Nonnull :shrug:
+ buffer_handle_t badBuffer = nullptr;
+ EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.flushLockedBuffer(badBuffer));
+}
+
+TEST_P(GraphicsMapperStableCTests, RereadLockedBufferBadBuffer) {
+ buffer_handle_t badBuffer = nullptr;
+ EXPECT_EQ(AIMAPPER_ERROR_BAD_BUFFER, mapper()->v5.rereadLockedBuffer(badBuffer));
+}
+
+TEST_P(GraphicsMapperStableCTests, GetBufferId) {
+ auto buffer = allocateGeneric();
+ auto bufferHandle = buffer->import();
+ auto bufferId = getStandardMetadata<StandardMetadataType::BUFFER_ID>(*bufferHandle);
+ ASSERT_TRUE(bufferId.has_value());
+
+ auto buffer2 = allocateGeneric();
+ auto bufferHandle2 = buffer2->import();
+ auto bufferId2 = getStandardMetadata<StandardMetadataType::BUFFER_ID>(*bufferHandle2);
+ ASSERT_TRUE(bufferId2.has_value());
+
+ EXPECT_NE(*bufferId, *bufferId2);
+}
+
+TEST_P(GraphicsMapperStableCTests, GetName) {
+ auto buffer = allocate({
+ .name = {"Hello, World!"},
+ .width = 64,
+ .height = 64,
+ .layerCount = 1,
+ .format = PixelFormat::RGBA_8888,
+ .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN,
+ .reservedSize = 0,
+ });
+ auto bufferHandle = buffer->import();
+ auto name = getStandardMetadata<StandardMetadataType::NAME>(*bufferHandle);
+ ASSERT_TRUE(name.has_value());
+ EXPECT_EQ(*name, "Hello, World!");
+}
+
+TEST_P(GraphicsMapperStableCTests, GetWidthHeight) {
+ auto buffer = allocate({
+ .name = {"Hello, World!"},
+ .width = 64,
+ .height = 128,
+ .layerCount = 1,
+ .format = PixelFormat::RGBA_8888,
+ .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN,
+ .reservedSize = 0,
+ });
+ auto bufferHandle = buffer->import();
+ auto value = getStandardMetadata<StandardMetadataType::WIDTH>(*bufferHandle);
+ ASSERT_TRUE(value.has_value());
+ EXPECT_EQ(*value, 64);
+ value = getStandardMetadata<StandardMetadataType::HEIGHT>(*bufferHandle);
+ ASSERT_TRUE(value.has_value());
+ EXPECT_EQ(*value, 128);
+}
+
+TEST_P(GraphicsMapperStableCTests, GetLayerCount) {
+ auto buffer = allocateGeneric();
+ auto bufferHandle = buffer->import();
+ auto value = getStandardMetadata<StandardMetadataType::LAYER_COUNT>(*bufferHandle);
+ ASSERT_TRUE(value.has_value());
+ EXPECT_EQ(*value, buffer->info().layerCount);
+}
+
+TEST_P(GraphicsMapperStableCTests, GetPixelFormatRequested) {
+ auto buffer = allocateGeneric();
+ auto bufferHandle = buffer->import();
+ auto value = getStandardMetadata<StandardMetadataType::PIXEL_FORMAT_REQUESTED>(*bufferHandle);
+ ASSERT_TRUE(value.has_value());
+ EXPECT_EQ(*value, buffer->info().format);
+}
+
+TEST_P(GraphicsMapperStableCTests, GetPixelFormatFourCC) {
+ auto buffer = allocate({
+ .name = {"Hello, World!"},
+ .width = 64,
+ .height = 128,
+ .layerCount = 1,
+ .format = PixelFormat::RGBA_8888,
+ .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN,
+ .reservedSize = 0,
+ });
+ {
+ auto bufferHandle = buffer->import();
+ auto value = getStandardMetadata<StandardMetadataType::PIXEL_FORMAT_FOURCC>(*bufferHandle);
+ ASSERT_TRUE(value.has_value());
+ EXPECT_EQ(*value, DRM_FORMAT_ABGR8888);
+ }
+
+ buffer = allocate({
+ .name = {"yv12"},
+ .width = 64,
+ .height = 128,
+ .layerCount = 1,
+ .format = PixelFormat::YV12,
+ .usage = BufferUsage::CPU_WRITE_OFTEN | BufferUsage::CPU_READ_OFTEN,
+ .reservedSize = 0,
+ });
+ {
+ auto bufferHandle = buffer->import();
+ auto value = getStandardMetadata<StandardMetadataType::PIXEL_FORMAT_FOURCC>(*bufferHandle);
+ ASSERT_TRUE(value.has_value());
+ EXPECT_EQ(*value, DRM_FORMAT_YVU420);
+ }
+}
+
+TEST_P(GraphicsMapperStableCTests, GetPixelFormatModifier) {
+ auto buffer = allocateGeneric();
+ auto bufferHandle = buffer->import();
+ auto value = getStandardMetadata<StandardMetadataType::PIXEL_FORMAT_MODIFIER>(*bufferHandle);
+ ASSERT_TRUE(value.has_value());
+ // Only the upper 8-bits are defined and is just the vendor ID, the lower 56 bits are
+ // then vendor specific. So there's not anything useful to assert here beyond just that
+ // we successfully queried a value
+}
+
+TEST_P(GraphicsMapperStableCTests, GetUsage) {
+ auto buffer = allocateGeneric();
+ auto bufferHandle = buffer->import();
+ auto value = getStandardMetadata<StandardMetadataType::USAGE>(*bufferHandle);
+ ASSERT_TRUE(value.has_value());
+ EXPECT_EQ(buffer->info().usage, *value);
+}
+
+TEST_P(GraphicsMapperStableCTests, GetAllocationSize) {
+ auto buffer = allocateGeneric();
+ auto bufferHandle = buffer->import();
+ auto value = getStandardMetadata<StandardMetadataType::ALLOCATION_SIZE>(*bufferHandle);
+ ASSERT_TRUE(value.has_value());
+ const auto estimatedSize = buffer->stride() * buffer->info().height * 4;
+ // This buffer has CPU usage, so we expect at least stride * height * 4 since it should be
+ // generally linear uncompressed.
+ EXPECT_GE(*value, estimatedSize)
+ << "Expected allocation size to be at least stride * height * 4bpp";
+ // Might need refining, but hopefully this a generous-enough upper-bound?
+ EXPECT_LT(*value, estimatedSize * 2)
+ << "Expected allocation size to less than double stride * height * 4bpp";
+}
+
+TEST_P(GraphicsMapperStableCTests, GetProtectedContent) {
+ const BufferDescriptorInfo info{
+ .name = {"prot8888"},
+ .width = 64,
+ .height = 64,
+ .layerCount = 1,
+ .format = PixelFormat::RGBA_8888,
+ .usage = BufferUsage::PROTECTED | BufferUsage::COMPOSER_OVERLAY,
+ .reservedSize = 0,
+ };
+ auto buffer = allocate(info);
+ if (!buffer) {
+ ASSERT_FALSE(isSupported(info))
+ << "Allocation of trivial sized buffer failed, so isSupported() must be false";
+ GTEST_SUCCEED() << "PROTECTED RGBA_8888 is unsupported";
+ return;
+ }
+ auto bufferHandle = buffer->import();
+ auto value = getStandardMetadata<StandardMetadataType::PROTECTED_CONTENT>(*bufferHandle);
+ ASSERT_TRUE(value.has_value());
+ EXPECT_EQ(*value, 1);
+}
+
+TEST_P(GraphicsMapperStableCTests, GetCompression) {
+ auto buffer = allocateGeneric();
+ ASSERT_TRUE(buffer);
+ auto bufferHandle = buffer->import();
+ ASSERT_TRUE(bufferHandle);
+ auto value = getStandardMetadata<StandardMetadataType::COMPRESSION>(*bufferHandle);
+ ASSERT_TRUE(value.has_value());
+ EXPECT_EQ(gralloc4::Compression_None.name, value->name);
+ EXPECT_EQ(gralloc4::Compression_None.value, value->value);
+}
+
+TEST_P(GraphicsMapperStableCTests, GetInterlaced) {
+ auto buffer = allocateGeneric();
+ ASSERT_TRUE(buffer);
+ auto bufferHandle = buffer->import();
+ ASSERT_TRUE(bufferHandle);
+ auto value = getStandardMetadata<StandardMetadataType::INTERLACED>(*bufferHandle);
+ ASSERT_TRUE(value.has_value());
+ EXPECT_EQ(gralloc4::Interlaced_None.name, value->name);
+ EXPECT_EQ(gralloc4::Interlaced_None.value, value->value);
+}
+
+TEST_P(GraphicsMapperStableCTests, GetChromaSiting) {
+ auto buffer = allocateGeneric();
+ ASSERT_TRUE(buffer);
+ auto bufferHandle = buffer->import();
+ ASSERT_TRUE(bufferHandle);
+ auto value = getStandardMetadata<StandardMetadataType::CHROMA_SITING>(*bufferHandle);
+ ASSERT_TRUE(value.has_value());
+ EXPECT_EQ(gralloc4::ChromaSiting_None.name, value->name);
+ EXPECT_EQ(gralloc4::ChromaSiting_None.value, value->value);
+}
+
+TEST_P(GraphicsMapperStableCTests, GetPlaneLayouts) {
+ auto buffer = allocateGeneric();
+ ASSERT_TRUE(buffer);
+ auto bufferHandle = buffer->import();
+ ASSERT_TRUE(bufferHandle);
+ auto value = getStandardMetadata<StandardMetadataType::PLANE_LAYOUTS>(*bufferHandle);
+ ASSERT_TRUE(value.has_value());
+ ASSERT_NO_FATAL_FAILURE(verifyRGBA8888PlaneLayouts(*value));
+}
+
+TEST_P(GraphicsMapperStableCTests, GetCrop) {
+ auto buffer = allocateGeneric();
+ ASSERT_TRUE(buffer);
+ auto bufferHandle = buffer->import();
+ ASSERT_TRUE(bufferHandle);
+ auto value = getStandardMetadata<StandardMetadataType::CROP>(*bufferHandle);
+ ASSERT_TRUE(value.has_value());
+ EXPECT_EQ(1, value->size());
+ const Rect expected{0, 0, buffer->info().width, buffer->info().height};
+ EXPECT_EQ(expected, value->at(0));
+}
+
+TEST_P(GraphicsMapperStableCTests, GetSetDataspace) {
+ auto buffer = allocateGeneric();
+ ASSERT_TRUE(buffer);
+ auto bufferHandle = buffer->import();
+ ASSERT_TRUE(bufferHandle);
+ auto value = getStandardMetadata<StandardMetadataType::DATASPACE>(*bufferHandle);
+ ASSERT_TRUE(value.has_value());
+ EXPECT_EQ(Dataspace::UNKNOWN, *value);
+ EXPECT_EQ(AIMAPPER_ERROR_NONE, setStandardMetadata<StandardMetadataType::DATASPACE>(
+ *bufferHandle, Dataspace::DISPLAY_P3));
+ value = getStandardMetadata<StandardMetadataType::DATASPACE>(*bufferHandle);
+ ASSERT_TRUE(value.has_value());
+ EXPECT_EQ(Dataspace::DISPLAY_P3, *value);
+}
+
+TEST_P(GraphicsMapperStableCTests, GetSetBlendMode) {
+ auto buffer = allocateGeneric();
+ ASSERT_TRUE(buffer);
+ auto bufferHandle = buffer->import();
+ ASSERT_TRUE(bufferHandle);
+ auto value = getStandardMetadata<StandardMetadataType::BLEND_MODE>(*bufferHandle);
+ ASSERT_TRUE(value.has_value());
+ EXPECT_EQ(BlendMode::INVALID, *value);
+ EXPECT_EQ(AIMAPPER_ERROR_NONE, setStandardMetadata<StandardMetadataType::BLEND_MODE>(
+ *bufferHandle, BlendMode::COVERAGE));
+ value = getStandardMetadata<StandardMetadataType::BLEND_MODE>(*bufferHandle);
+ ASSERT_TRUE(value.has_value());
+ EXPECT_EQ(BlendMode::COVERAGE, *value);
+}
+
+TEST_P(GraphicsMapperStableCTests, GetSetSmpte2086) {
+ auto buffer = allocateGeneric();
+ ASSERT_TRUE(buffer);
+ auto bufferHandle = buffer->import();
+ ASSERT_TRUE(bufferHandle);
+ auto value = getStandardMetadata<StandardMetadataType::SMPTE2086>(*bufferHandle);
+ ASSERT_TRUE(value.has_value());
+ EXPECT_FALSE(value->has_value());
+
+ // TODO: Maybe use something resembling real values, but validation isn't supposed to happen
+ // here anyway so :shrug:
+ const Smpte2086 awesomeHdr{
+ XyColor{1.f, 1.f}, XyColor{2.f, 2.f}, XyColor{3.f, 3.f},
+ XyColor{400.f, 1000.f}, 100000.0f, 0.0001f,
+ };
+ EXPECT_EQ(AIMAPPER_ERROR_NONE,
+ setStandardMetadata<StandardMetadataType::SMPTE2086>(*bufferHandle, awesomeHdr));
+ value = getStandardMetadata<StandardMetadataType::SMPTE2086>(*bufferHandle);
+ ASSERT_TRUE(value.has_value());
+ ASSERT_TRUE(value->has_value());
+ EXPECT_EQ(awesomeHdr, *value);
+
+ EXPECT_EQ(AIMAPPER_ERROR_NONE,
+ setStandardMetadata<StandardMetadataType::SMPTE2086>(*bufferHandle, std::nullopt));
+ value = getStandardMetadata<StandardMetadataType::SMPTE2086>(*bufferHandle);
+ ASSERT_TRUE(value.has_value());
+ EXPECT_FALSE(value->has_value());
+}
+
+TEST_P(GraphicsMapperStableCTests, GetCta861_3) {
+ auto buffer = allocateGeneric();
+ ASSERT_TRUE(buffer);
+ auto bufferHandle = buffer->import();
+ ASSERT_TRUE(bufferHandle);
+ auto value = getStandardMetadata<StandardMetadataType::CTA861_3>(*bufferHandle);
+ ASSERT_TRUE(value.has_value());
+ EXPECT_FALSE(value->has_value());
+
+ const Cta861_3 genericHlgish{1000.f, 140.f};
+ EXPECT_EQ(AIMAPPER_ERROR_NONE,
+ setStandardMetadata<StandardMetadataType::CTA861_3>(*bufferHandle, genericHlgish));
+ value = getStandardMetadata<StandardMetadataType::CTA861_3>(*bufferHandle);
+ ASSERT_TRUE(value.has_value());
+ ASSERT_TRUE(value->has_value());
+ EXPECT_EQ(genericHlgish, *value);
+
+ EXPECT_EQ(AIMAPPER_ERROR_NONE,
+ setStandardMetadata<StandardMetadataType::CTA861_3>(*bufferHandle, std::nullopt));
+ value = getStandardMetadata<StandardMetadataType::CTA861_3>(*bufferHandle);
+ ASSERT_TRUE(value.has_value());
+ EXPECT_FALSE(value->has_value());
+}
+
+TEST_P(GraphicsMapperStableCTests, GetSmpte2094_10) {
+ auto buffer = allocateGeneric();
+ ASSERT_TRUE(buffer);
+ auto bufferHandle = buffer->import();
+ ASSERT_TRUE(bufferHandle);
+ auto value = getStandardMetadata<StandardMetadataType::SMPTE2094_10>(*bufferHandle);
+ if (value.has_value()) {
+ EXPECT_FALSE(value->has_value());
+ }
+}
+
+TEST_P(GraphicsMapperStableCTests, GetSmpte2094_40) {
+ auto buffer = allocateGeneric();
+ ASSERT_TRUE(buffer);
+ auto bufferHandle = buffer->import();
+ ASSERT_TRUE(bufferHandle);
+ auto value = getStandardMetadata<StandardMetadataType::SMPTE2094_40>(*bufferHandle);
+ ASSERT_TRUE(value.has_value());
+ EXPECT_FALSE(value->has_value());
+}
+
+std::vector<std::tuple<std::string, std::shared_ptr<IAllocator>>> getIAllocatorsAtLeastVersion(
+ int32_t minVersion) {
+ auto instanceNames = getAidlHalInstanceNames(IAllocator::descriptor);
+ std::vector<std::tuple<std::string, std::shared_ptr<IAllocator>>> filteredInstances;
+ filteredInstances.reserve(instanceNames.size());
+ for (const auto& name : instanceNames) {
+ auto allocator =
+ IAllocator::fromBinder(ndk::SpAIBinder(AServiceManager_checkService(name.c_str())));
+ int32_t version = 0;
+ if (allocator->getInterfaceVersion(&version).isOk()) {
+ if (version >= minVersion) {
+ filteredInstances.emplace_back(name, std::move(allocator));
+ }
+ }
+ }
+ return filteredInstances;
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GraphicsMapperStableCTests);
+INSTANTIATE_TEST_CASE_P(PerInstance, GraphicsMapperStableCTests,
+ testing::ValuesIn(getIAllocatorsAtLeastVersion(2)),
+ [](auto info) -> std::string {
+ std::string name =
+ std::to_string(info.index) + "/" + std::get<0>(info.param);
+ return Sanitize(name);
+ });
\ No newline at end of file
diff --git a/secure_element/aidl/Android.bp b/secure_element/aidl/Android.bp
index 5a529a4..6450eb4 100644
--- a/secure_element/aidl/Android.bp
+++ b/secure_element/aidl/Android.bp
@@ -1,3 +1,12 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "hardware_interfaces_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["hardware_interfaces_license"],
+}
+
aidl_interface {
name: "android.hardware.secure_element",
vendor_available: true,
diff --git a/secure_element/aidl/default/Android.bp b/secure_element/aidl/default/Android.bp
index c604b68..d1bb393 100644
--- a/secure_element/aidl/default/Android.bp
+++ b/secure_element/aidl/default/Android.bp
@@ -1,3 +1,12 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "hardware_interfaces_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["hardware_interfaces_license"],
+}
+
cc_binary {
name: "android.hardware.secure_element-service.example",
relative_install_path: "hw",
diff --git a/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp b/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp
index 97fe08a..6d9c8c9 100644
--- a/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp
+++ b/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp
@@ -181,15 +181,6 @@
return params;
}
- void checkMacedPubkeyVersioned(const MacedPublicKey& macedPubKey, bool testMode,
- vector<uint8_t>* payload_value) {
- if (rpcHardwareInfo.versionNumber >= VERSION_WITHOUT_TEST_MODE) {
- check_maced_pubkey(macedPubKey, false, payload_value);
- } else {
- check_maced_pubkey(macedPubKey, testMode, payload_value);
- }
- }
-
protected:
std::shared_ptr<IRemotelyProvisionedComponent> provisionable_;
RpcHardwareInfo rpcHardwareInfo;
@@ -279,7 +270,7 @@
auto status = provisionable_->generateEcdsaP256KeyPair(testMode, &macedPubKey, &privateKeyBlob);
ASSERT_TRUE(status.isOk());
vector<uint8_t> coseKeyData;
- checkMacedPubkeyVersioned(macedPubKey, testMode, &coseKeyData);
+ check_maced_pubkey(macedPubKey, testMode, &coseKeyData);
}
/**
@@ -302,7 +293,7 @@
auto status = provisionable_->generateEcdsaP256KeyPair(testMode, &macedPubKey, &privateKeyBlob);
ASSERT_TRUE(status.isOk());
vector<uint8_t> coseKeyData;
- checkMacedPubkeyVersioned(macedPubKey, testMode, &coseKeyData);
+ check_maced_pubkey(macedPubKey, testMode, &coseKeyData);
AttestationKey attestKey;
attestKey.keyBlob = std::move(privateKeyBlob);
@@ -357,7 +348,7 @@
bool testMode = true;
auto status = provisionable_->generateEcdsaP256KeyPair(testMode, &macedPubKey, &privateKeyBlob);
ASSERT_TRUE(status.isOk());
- checkMacedPubkeyVersioned(macedPubKey, testMode, nullptr);
+ check_maced_pubkey(macedPubKey, testMode, nullptr);
}
class CertificateRequestTestBase : public VtsRemotelyProvisionedComponentTests {
@@ -382,7 +373,7 @@
ASSERT_TRUE(status.isOk()) << status.getMessage();
vector<uint8_t> payload_value;
- checkMacedPubkeyVersioned(key, testMode, &payload_value);
+ check_maced_pubkey(key, testMode, &payload_value);
cborKeysToSign_.add(cppbor::EncodedItem(payload_value));
}
}
@@ -401,8 +392,16 @@
CertificateRequestTestBase::SetUp();
if (rpcHardwareInfo.versionNumber >= VERSION_WITHOUT_TEST_MODE) {
- GTEST_SKIP() << "This test case only applies to RKP v1 and v2. "
- << "RKP version discovered: " << rpcHardwareInfo.versionNumber;
+ bytevec keysToSignMac;
+ DeviceInfo deviceInfo;
+ ProtectedData protectedData;
+ auto status = provisionable_->generateCertificateRequest(
+ false, {}, {}, {}, &deviceInfo, &protectedData, &keysToSignMac);
+ if (!status.isOk() && (status.getServiceSpecificError() ==
+ BnRemotelyProvisionedComponent::STATUS_REMOVED)) {
+ GTEST_SKIP() << "This test case applies to RKP v3+ only if "
+ << "generateCertificateRequest() is implemented.";
+ }
}
}
};
@@ -769,30 +768,17 @@
}
/**
- * Generate a non-empty certificate request in prod mode, with test keys. Test mode must be
- * ignored, i.e. test must pass.
+ * Generate a non-empty certificate request in prod mode, with test keys. Must fail with
+ * STATUS_TEST_KEY_IN_PRODUCTION_REQUEST.
*/
TEST_P(CertificateRequestV2Test, NonEmptyRequest_testKeyInProdCert) {
generateKeys(true /* testMode */, 1 /* numKeys */);
bytevec csr;
auto status = provisionable_->generateCertificateRequestV2(keysToSign_, challenge_, &csr);
- ASSERT_TRUE(status.isOk()) << status.getMessage();
-}
-
-/**
- * Call generateCertificateRequest(). Make sure it's removed.
- */
-TEST_P(CertificateRequestV2Test, CertificateRequestV1Removed) {
- generateTestEekChain(2);
- bytevec keysToSignMac;
- DeviceInfo deviceInfo;
- ProtectedData protectedData;
- auto status = provisionable_->generateCertificateRequest(
- true /* testMode */, {} /* keysToSign */, testEekChain_.chain, challenge_, &deviceInfo,
- &protectedData, &keysToSignMac);
ASSERT_FALSE(status.isOk()) << status.getMessage();
- EXPECT_EQ(status.getServiceSpecificError(), BnRemotelyProvisionedComponent::STATUS_REMOVED);
+ ASSERT_EQ(status.getServiceSpecificError(),
+ BnRemotelyProvisionedComponent::STATUS_TEST_KEY_IN_PRODUCTION_REQUEST);
}
INSTANTIATE_REM_PROV_AIDL_TEST(CertificateRequestV2Test);
diff --git a/security/rkp/CHANGELOG.md b/security/rkp/CHANGELOG.md
index c3e3609..715cf28 100644
--- a/security/rkp/CHANGELOG.md
+++ b/security/rkp/CHANGELOG.md
@@ -30,7 +30,8 @@
* `version` has moved to a top-level field within the CSR generated by the HAL.
* IRemotelyProvisionedComponent
* The need for an EEK has been removed. There is no longer an encrypted portion of the CSR.
- * Test mode has been removed.
+ * Keys for new CSR format must be generated with test mode set to false, effectively removing test
+ mode in the new CSR flow. Old behavior is kept unchanged for backwards compatibility.
* The schema for the CSR itself has been significantly simplified, please see
IRemotelyProvisionedComponent.aidl for more details. Notably,
* the chain of signing, MACing, and encryption operations has been replaced with a single
diff --git a/security/rkp/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl b/security/rkp/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl
index 2fc780c..5485db3 100644
--- a/security/rkp/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl
+++ b/security/rkp/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl
@@ -132,11 +132,7 @@
* generateKeyPair generates a new ECDSA P-256 key pair that can be attested by the remote
* server.
*
- * @param in boolean testMode this field is now deprecated. It is ignored by the implementation
- * in v3, but retained to simplify backwards compatibility support. V1 and V2
- * implementations must still respect the testMode flag.
- *
- * testMode indicates whether the generated key is for testing only. Test keys
+ * @param in boolean testMode indicates whether the generated key is for testing only. Test keys
* are marked (see the definition of PublicKey in the MacedPublicKey structure) to
* prevent them from being confused with production keys.
*
@@ -150,8 +146,8 @@
byte[] generateEcdsaP256KeyPair(in boolean testMode, out MacedPublicKey macedPublicKey);
/**
- * This method has been removed in version 3 of the HAL. The header is kept around for
- * backwards compatibility purposes. From v3, this method should raise a
+ * This method can be removed in version 3 of the HAL. The header is kept around for
+ * backwards compatibility purposes. From v3, this method is allowed to raise a
* ServiceSpecificException with an error code of STATUS_REMOVED.
*
* For v1 and v2 implementations:
@@ -306,7 +302,9 @@
*
* @param in MacedPublicKey[] keysToSign contains the set of keys to certify. The
* IRemotelyProvisionedComponent must validate the MACs on each key. If any entry in the
- * array lacks a valid MAC, the method must return STATUS_INVALID_MAC.
+ * array lacks a valid MAC, the method must return STATUS_INVALID_MAC. This method must
+ * not accept test keys. If any entry in the array is a test key, the method must return
+ * STATUS_TEST_KEY_IN_PRODUCTION_REQUEST.
*
* @param in challenge contains a byte string from the provisioning server which will be
* included in the signed data of the CSR structure. Different provisioned backends may
diff --git a/sensors/1.0/default/Android.bp b/sensors/1.0/default/Android.bp
index 2e4e1b0..bb31050 100644
--- a/sensors/1.0/default/Android.bp
+++ b/sensors/1.0/default/Android.bp
@@ -44,6 +44,12 @@
"libhidlbase",
"android.hardware.sensors@1.0",
],
+ whole_static_libs: [
+ "sensors_common_convert",
+ ],
+ export_static_lib_headers: [
+ "sensors_common_convert",
+ ],
local_include_dirs: ["include/sensors"],
export_shared_lib_headers: [
"libhardware",
diff --git a/sensors/1.0/default/convert.cpp b/sensors/1.0/default/convert.cpp
index 43ee327..ae71a97 100644
--- a/sensors/1.0/default/convert.cpp
+++ b/sensors/1.0/default/convert.cpp
@@ -196,6 +196,11 @@
}
}
+void convertFromASensorEvent(const ASensorEvent& src, Event* dst) {
+ convertFromSensorEvent(
+ android::hardware::sensors::implementation::common::convertASensorEvent(src), dst);
+}
+
void convertToSensorEvent(const Event &src, sensors_event_t *dst) {
*dst = {.version = sizeof(sensors_event_t),
.sensor = src.sensorHandle,
diff --git a/sensors/1.0/default/include/sensors/convert.h b/sensors/1.0/default/include/sensors/convert.h
index c3a0125..ae773df 100644
--- a/sensors/1.0/default/include/sensors/convert.h
+++ b/sensors/1.0/default/include/sensors/convert.h
@@ -20,6 +20,7 @@
#include <android/hardware/sensors/1.0/ISensors.h>
#include <hardware/sensors.h>
+#include <sensors/common_convert.h>
namespace android {
namespace hardware {
@@ -31,6 +32,7 @@
void convertToSensor(const SensorInfo &src, sensor_t *dst);
void convertFromSensorEvent(const sensors_event_t &src, Event *dst);
+void convertFromASensorEvent(const ASensorEvent& src, Event* dst);
void convertToSensorEvent(const Event &src, sensors_event_t *dst);
bool convertFromSharedMemInfo(const SharedMemInfo& memIn, sensors_direct_mem_t *memOut);
diff --git a/sensors/aidl/convert/Android.bp b/sensors/aidl/convert/Android.bp
index d47de8e..0b31597 100644
--- a/sensors/aidl/convert/Android.bp
+++ b/sensors/aidl/convert/Android.bp
@@ -37,6 +37,12 @@
"libutils",
"android.hardware.sensors-V1-ndk",
],
+ whole_static_libs: [
+ "sensors_common_convert",
+ ],
+ export_static_lib_headers: [
+ "sensors_common_convert",
+ ],
local_include_dirs: ["include/aidl/sensors"],
export_shared_lib_headers: [
"libhardware",
diff --git a/sensors/aidl/convert/convert.cpp b/sensors/aidl/convert/convert.cpp
index 415f435..abd4d55 100644
--- a/sensors/aidl/convert/convert.cpp
+++ b/sensors/aidl/convert/convert.cpp
@@ -490,6 +490,10 @@
}
}
+void convertFromASensorEvent(const ASensorEvent& src, Event* dst) {
+ convertFromSensorEvent(common::convertASensorEvent(src), dst);
+}
+
} // namespace implementation
} // namespace sensors
} // namespace hardware
diff --git a/sensors/aidl/convert/include/aidl/sensors/convert.h b/sensors/aidl/convert/include/aidl/sensors/convert.h
index 702b226..44504fe 100644
--- a/sensors/aidl/convert/include/aidl/sensors/convert.h
+++ b/sensors/aidl/convert/include/aidl/sensors/convert.h
@@ -18,6 +18,7 @@
#include <aidl/android/hardware/sensors/ISensors.h>
#include <hardware/sensors.h>
+#include <sensors/common_convert.h>
namespace android {
namespace hardware {
@@ -29,6 +30,7 @@
void convertToSensorEvent(const aidl::android::hardware::sensors::Event& src, sensors_event_t* dst);
void convertFromSensorEvent(const sensors_event_t& src,
aidl::android::hardware::sensors::Event* dst);
+void convertFromASensorEvent(const ASensorEvent& src, aidl::android::hardware::sensors::Event* dst);
} // namespace implementation
} // namespace sensors
diff --git a/sensors/common/convert/Android.bp b/sensors/common/convert/Android.bp
new file mode 100644
index 0000000..230665e
--- /dev/null
+++ b/sensors/common/convert/Android.bp
@@ -0,0 +1,42 @@
+// 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.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library {
+ name: "sensors_common_convert",
+ srcs: [
+ "convert.cpp",
+ ],
+ vendor_available: true,
+ host_supported: true,
+ local_include_dirs: ["include"],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ shared_libs: [
+ "libhardware",
+ ],
+ header_libs: [
+ "libandroid_sensor_headers",
+ ],
+
+ export_include_dirs: ["include"],
+ export_header_lib_headers: [
+ "libandroid_sensor_headers",
+ ],
+}
diff --git a/sensors/common/convert/convert.cpp b/sensors/common/convert/convert.cpp
new file mode 100644
index 0000000..27de32c
--- /dev/null
+++ b/sensors/common/convert/convert.cpp
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+#include <sensors/common_convert.h>
+#include <cstring>
+
+namespace android {
+namespace hardware {
+namespace sensors {
+namespace implementation {
+namespace common {
+
+sensors_event_t convertASensorEvent(const ASensorEvent& src) {
+ // Attempt to ensure these types are compatible.
+ static_assert(sizeof(sensors_event_t) == sizeof(ASensorEvent));
+ static_assert(offsetof(sensors_event_t, timestamp) == offsetof(ASensorEvent, timestamp));
+ static_assert(offsetof(sensors_event_t, flags) == offsetof(ASensorEvent, flags));
+
+ // TODO(b/259711109) Follow up work to handle this in a safer way.
+ return *reinterpret_cast<const sensors_event_t*>(&src);
+}
+
+} // namespace common
+} // namespace implementation
+} // namespace sensors
+} // namespace hardware
+} // namespace android
diff --git a/sensors/common/convert/include/sensors/common_convert.h b/sensors/common/convert/include/sensors/common_convert.h
new file mode 100644
index 0000000..a281369
--- /dev/null
+++ b/sensors/common/convert/include/sensors/common_convert.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android/sensor.h>
+#include <hardware/sensors.h>
+
+namespace android {
+namespace hardware {
+namespace sensors {
+namespace implementation {
+namespace common {
+
+sensors_event_t convertASensorEvent(const ASensorEvent& aEvent);
+
+} // namespace common
+} // namespace implementation
+} // namespace sensors
+} // namespace hardware
+} // namespace android
diff --git a/thermal/aidl/aidl_api/android.hardware.thermal/current/android/hardware/thermal/CoolingDevice.aidl b/thermal/aidl/aidl_api/android.hardware.thermal/current/android/hardware/thermal/CoolingDevice.aidl
index 50be508..dfd8686 100644
--- a/thermal/aidl/aidl_api/android.hardware.thermal/current/android/hardware/thermal/CoolingDevice.aidl
+++ b/thermal/aidl/aidl_api/android.hardware.thermal/current/android/hardware/thermal/CoolingDevice.aidl
@@ -32,6 +32,7 @@
// later when a module using the interface is updated, e.g., Mainline modules.
package android.hardware.thermal;
+/* @hide */
@VintfStability
parcelable CoolingDevice {
android.hardware.thermal.CoolingType type;
diff --git a/thermal/aidl/aidl_api/android.hardware.thermal/current/android/hardware/thermal/CoolingType.aidl b/thermal/aidl/aidl_api/android.hardware.thermal/current/android/hardware/thermal/CoolingType.aidl
index 57c8939..d2eb389 100644
--- a/thermal/aidl/aidl_api/android.hardware.thermal/current/android/hardware/thermal/CoolingType.aidl
+++ b/thermal/aidl/aidl_api/android.hardware.thermal/current/android/hardware/thermal/CoolingType.aidl
@@ -32,6 +32,7 @@
// later when a module using the interface is updated, e.g., Mainline modules.
package android.hardware.thermal;
+/* @hide */
@Backing(type="int") @VintfStability
enum CoolingType {
FAN = 0,
diff --git a/thermal/aidl/aidl_api/android.hardware.thermal/current/android/hardware/thermal/IThermal.aidl b/thermal/aidl/aidl_api/android.hardware.thermal/current/android/hardware/thermal/IThermal.aidl
index 0aed5ec..c9b6cab 100644
--- a/thermal/aidl/aidl_api/android.hardware.thermal/current/android/hardware/thermal/IThermal.aidl
+++ b/thermal/aidl/aidl_api/android.hardware.thermal/current/android/hardware/thermal/IThermal.aidl
@@ -32,6 +32,7 @@
// later when a module using the interface is updated, e.g., Mainline modules.
package android.hardware.thermal;
+/* @hide */
@VintfStability
interface IThermal {
android.hardware.thermal.CoolingDevice[] getCoolingDevices();
diff --git a/thermal/aidl/aidl_api/android.hardware.thermal/current/android/hardware/thermal/IThermalChangedCallback.aidl b/thermal/aidl/aidl_api/android.hardware.thermal/current/android/hardware/thermal/IThermalChangedCallback.aidl
index 6b3f922..5e1d753 100644
--- a/thermal/aidl/aidl_api/android.hardware.thermal/current/android/hardware/thermal/IThermalChangedCallback.aidl
+++ b/thermal/aidl/aidl_api/android.hardware.thermal/current/android/hardware/thermal/IThermalChangedCallback.aidl
@@ -32,6 +32,7 @@
// later when a module using the interface is updated, e.g., Mainline modules.
package android.hardware.thermal;
+/* @hide */
@VintfStability
interface IThermalChangedCallback {
oneway void notifyThrottling(in android.hardware.thermal.Temperature temperature);
diff --git a/thermal/aidl/aidl_api/android.hardware.thermal/current/android/hardware/thermal/Temperature.aidl b/thermal/aidl/aidl_api/android.hardware.thermal/current/android/hardware/thermal/Temperature.aidl
index 7156415..3bf08bf 100644
--- a/thermal/aidl/aidl_api/android.hardware.thermal/current/android/hardware/thermal/Temperature.aidl
+++ b/thermal/aidl/aidl_api/android.hardware.thermal/current/android/hardware/thermal/Temperature.aidl
@@ -32,6 +32,7 @@
// later when a module using the interface is updated, e.g., Mainline modules.
package android.hardware.thermal;
+/* @hide */
@VintfStability
parcelable Temperature {
android.hardware.thermal.TemperatureType type;
diff --git a/thermal/aidl/aidl_api/android.hardware.thermal/current/android/hardware/thermal/TemperatureThreshold.aidl b/thermal/aidl/aidl_api/android.hardware.thermal/current/android/hardware/thermal/TemperatureThreshold.aidl
index 6da561f..c5ca4b9 100644
--- a/thermal/aidl/aidl_api/android.hardware.thermal/current/android/hardware/thermal/TemperatureThreshold.aidl
+++ b/thermal/aidl/aidl_api/android.hardware.thermal/current/android/hardware/thermal/TemperatureThreshold.aidl
@@ -32,6 +32,7 @@
// later when a module using the interface is updated, e.g., Mainline modules.
package android.hardware.thermal;
+/* @hide */
@VintfStability
parcelable TemperatureThreshold {
android.hardware.thermal.TemperatureType type;
diff --git a/thermal/aidl/aidl_api/android.hardware.thermal/current/android/hardware/thermal/TemperatureType.aidl b/thermal/aidl/aidl_api/android.hardware.thermal/current/android/hardware/thermal/TemperatureType.aidl
index c6a08c1..0a9efdd 100644
--- a/thermal/aidl/aidl_api/android.hardware.thermal/current/android/hardware/thermal/TemperatureType.aidl
+++ b/thermal/aidl/aidl_api/android.hardware.thermal/current/android/hardware/thermal/TemperatureType.aidl
@@ -32,6 +32,7 @@
// later when a module using the interface is updated, e.g., Mainline modules.
package android.hardware.thermal;
+/* @hide */
@Backing(type="int") @VintfStability
enum TemperatureType {
UNKNOWN = -1,
diff --git a/thermal/aidl/aidl_api/android.hardware.thermal/current/android/hardware/thermal/ThrottlingSeverity.aidl b/thermal/aidl/aidl_api/android.hardware.thermal/current/android/hardware/thermal/ThrottlingSeverity.aidl
index e86b581..8fe3df6 100644
--- a/thermal/aidl/aidl_api/android.hardware.thermal/current/android/hardware/thermal/ThrottlingSeverity.aidl
+++ b/thermal/aidl/aidl_api/android.hardware.thermal/current/android/hardware/thermal/ThrottlingSeverity.aidl
@@ -32,6 +32,7 @@
// later when a module using the interface is updated, e.g., Mainline modules.
package android.hardware.thermal;
+/* @hide */
@Backing(type="int") @VintfStability
enum ThrottlingSeverity {
NONE = 0,
diff --git a/thermal/aidl/android/hardware/thermal/CoolingDevice.aidl b/thermal/aidl/android/hardware/thermal/CoolingDevice.aidl
index 6d974a5..1f2360d 100644
--- a/thermal/aidl/android/hardware/thermal/CoolingDevice.aidl
+++ b/thermal/aidl/android/hardware/thermal/CoolingDevice.aidl
@@ -18,6 +18,7 @@
import android.hardware.thermal.CoolingType;
+/* @hide */
@VintfStability
parcelable CoolingDevice {
/**
diff --git a/thermal/aidl/android/hardware/thermal/CoolingType.aidl b/thermal/aidl/android/hardware/thermal/CoolingType.aidl
index 1b430d2..08beb55 100644
--- a/thermal/aidl/android/hardware/thermal/CoolingType.aidl
+++ b/thermal/aidl/android/hardware/thermal/CoolingType.aidl
@@ -18,6 +18,7 @@
/**
* Device cooling device types
+ * @hide
*/
@VintfStability
@Backing(type="int")
diff --git a/thermal/aidl/android/hardware/thermal/IThermal.aidl b/thermal/aidl/android/hardware/thermal/IThermal.aidl
index 8b79cb4..dd87b3a 100644
--- a/thermal/aidl/android/hardware/thermal/IThermal.aidl
+++ b/thermal/aidl/android/hardware/thermal/IThermal.aidl
@@ -23,6 +23,7 @@
import android.hardware.thermal.TemperatureThreshold;
import android.hardware.thermal.TemperatureType;
+/* @hide */
@VintfStability
interface IThermal {
/**
diff --git a/thermal/aidl/android/hardware/thermal/IThermalChangedCallback.aidl b/thermal/aidl/android/hardware/thermal/IThermalChangedCallback.aidl
index 6fe2dac..105f085 100644
--- a/thermal/aidl/android/hardware/thermal/IThermalChangedCallback.aidl
+++ b/thermal/aidl/android/hardware/thermal/IThermalChangedCallback.aidl
@@ -20,6 +20,7 @@
/**
* IThermalChangedCallback send throttling notification to clients.
+ * @hide
*/
@VintfStability
interface IThermalChangedCallback {
diff --git a/thermal/aidl/android/hardware/thermal/Temperature.aidl b/thermal/aidl/android/hardware/thermal/Temperature.aidl
index f0041ed..281d68d 100644
--- a/thermal/aidl/android/hardware/thermal/Temperature.aidl
+++ b/thermal/aidl/android/hardware/thermal/Temperature.aidl
@@ -19,6 +19,7 @@
import android.hardware.thermal.TemperatureType;
import android.hardware.thermal.ThrottlingSeverity;
+/* @hide */
@VintfStability
parcelable Temperature {
/**
diff --git a/thermal/aidl/android/hardware/thermal/TemperatureThreshold.aidl b/thermal/aidl/android/hardware/thermal/TemperatureThreshold.aidl
index 9ecdab3..8065f76 100644
--- a/thermal/aidl/android/hardware/thermal/TemperatureThreshold.aidl
+++ b/thermal/aidl/android/hardware/thermal/TemperatureThreshold.aidl
@@ -18,6 +18,7 @@
import android.hardware.thermal.TemperatureType;
+/* @hide */
@VintfStability
parcelable TemperatureThreshold {
/**
diff --git a/thermal/aidl/android/hardware/thermal/TemperatureType.aidl b/thermal/aidl/android/hardware/thermal/TemperatureType.aidl
index aebe7ce..467d096 100644
--- a/thermal/aidl/android/hardware/thermal/TemperatureType.aidl
+++ b/thermal/aidl/android/hardware/thermal/TemperatureType.aidl
@@ -18,6 +18,7 @@
/**
* Device temperature types
+ * @hide
*/
@VintfStability
@Backing(type="int")
diff --git a/thermal/aidl/android/hardware/thermal/ThrottlingSeverity.aidl b/thermal/aidl/android/hardware/thermal/ThrottlingSeverity.aidl
index 29f0724..c66e6c2 100644
--- a/thermal/aidl/android/hardware/thermal/ThrottlingSeverity.aidl
+++ b/thermal/aidl/android/hardware/thermal/ThrottlingSeverity.aidl
@@ -18,6 +18,7 @@
/**
* Device throttling severity
+ * @hide
*/
@VintfStability
@Backing(type="int")
diff --git a/vibrator/aidl/default/main.cpp b/vibrator/aidl/default/main.cpp
index feba2c7..7375889 100644
--- a/vibrator/aidl/default/main.cpp
+++ b/vibrator/aidl/default/main.cpp
@@ -29,15 +29,15 @@
// make a default vibrator service
auto vib = ndk::SharedRefBase::make<Vibrator>();
- const std::string vibName = std::string() + Vibrator::descriptor + "/default";
- binder_status_t status = AServiceManager_addService(vib->asBinder().get(), vibName.c_str());
+ binder_status_t status = AServiceManager_addService(
+ vib->asBinder().get(), Vibrator::makeServiceName("default").c_str());
CHECK_EQ(status, STATUS_OK);
// make the vibrator manager service with a different vibrator
auto managedVib = ndk::SharedRefBase::make<Vibrator>();
auto vibManager = ndk::SharedRefBase::make<VibratorManager>(std::move(managedVib));
- const std::string vibManagerName = std::string() + VibratorManager::descriptor + "/default";
- status = AServiceManager_addService(vibManager->asBinder().get(), vibManagerName.c_str());
+ status = AServiceManager_addService(vibManager->asBinder().get(),
+ VibratorManager::makeServiceName("default").c_str());
CHECK_EQ(status, STATUS_OK);
ABinderProcess_joinThreadPool();