Merge ab/7061308 into stage.
Bug: 180401296
Merged-In: I1512f0aee1f960e809a42448b9aafed0de3dbc2a
Change-Id: I12dddc8066b2477f7dce5778ab3dd96d6e43afc4
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 6740bb5..2116e21 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -4,6 +4,7 @@
[Builtin Hooks]
bpfmt = true
clang_format = true
+aidl_format = true
[Hook Scripts]
aosp_hook_confirmationui = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD_COMMIT} confirmationui
diff --git a/audio/5.0/config/api/current.txt b/audio/5.0/config/api/current.txt
index a1d8e1e..dbb5d3b 100644
--- a/audio/5.0/config/api/current.txt
+++ b/audio/5.0/config/api/current.txt
@@ -133,6 +133,7 @@
enum_constant public static final audio.policy.configuration.V5_0.AudioFormat AUDIO_FORMAT_LDAC;
enum_constant public static final audio.policy.configuration.V5_0.AudioFormat AUDIO_FORMAT_LHDC;
enum_constant public static final audio.policy.configuration.V5_0.AudioFormat AUDIO_FORMAT_LHDC_LL;
+ enum_constant public static final audio.policy.configuration.V5_0.AudioFormat AUDIO_FORMAT_MAT;
enum_constant public static final audio.policy.configuration.V5_0.AudioFormat AUDIO_FORMAT_MAT_1_0;
enum_constant public static final audio.policy.configuration.V5_0.AudioFormat AUDIO_FORMAT_MAT_2_0;
enum_constant public static final audio.policy.configuration.V5_0.AudioFormat AUDIO_FORMAT_MAT_2_1;
@@ -198,7 +199,7 @@
public static class DevicePorts.DevicePort {
ctor public DevicePorts.DevicePort();
method public String getAddress();
- method public java.util.List<audio.policy.configuration.V5_0.AudioFormat> getEncodedFormats();
+ method public java.util.List<java.lang.String> getEncodedFormats();
method public audio.policy.configuration.V5_0.Gains getGains();
method public java.util.List<audio.policy.configuration.V5_0.Profile> getProfile();
method public audio.policy.configuration.V5_0.Role getRole();
@@ -206,7 +207,7 @@
method public String getType();
method public boolean get_default();
method public void setAddress(String);
- method public void setEncodedFormats(java.util.List<audio.policy.configuration.V5_0.AudioFormat>);
+ method public void setEncodedFormats(java.util.List<java.lang.String>);
method public void setGains(audio.policy.configuration.V5_0.Gains);
method public void setRole(audio.policy.configuration.V5_0.Role);
method public void setTagName(String);
@@ -379,10 +380,10 @@
public static class SurroundFormats.Format {
ctor public SurroundFormats.Format();
- method public audio.policy.configuration.V5_0.AudioFormat getName();
- method public java.util.List<audio.policy.configuration.V5_0.AudioFormat> getSubformats();
- method public void setName(audio.policy.configuration.V5_0.AudioFormat);
- method public void setSubformats(java.util.List<audio.policy.configuration.V5_0.AudioFormat>);
+ method public String getName();
+ method public java.util.List<java.lang.String> getSubformats();
+ method public void setName(String);
+ method public void setSubformats(java.util.List<java.lang.String>);
}
public class SurroundSound {
diff --git a/audio/5.0/config/audio_policy_configuration.xsd b/audio/5.0/config/audio_policy_configuration.xsd
index 284d2e2..f92136c 100644
--- a/audio/5.0/config/audio_policy_configuration.xsd
+++ b/audio/5.0/config/audio_policy_configuration.xsd
@@ -361,6 +361,7 @@
<xs:enumeration value="AUDIO_FORMAT_AC4"/>
<xs:enumeration value="AUDIO_FORMAT_LDAC"/>
<xs:enumeration value="AUDIO_FORMAT_E_AC3_JOC"/>
+ <xs:enumeration value="AUDIO_FORMAT_MAT"/>
<xs:enumeration value="AUDIO_FORMAT_MAT_1_0"/>
<xs:enumeration value="AUDIO_FORMAT_MAT_2_0"/>
<xs:enumeration value="AUDIO_FORMAT_MAT_2_1"/>
@@ -610,13 +611,13 @@
</xs:sequence>
</xs:complexType>
<xs:simpleType name="audioFormatsList">
- <xs:list itemType="audioFormat" />
+ <xs:list itemType="extendableAudioFormat" />
</xs:simpleType>
<xs:complexType name="surroundFormats">
<xs:sequence>
<xs:element name="format" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
- <xs:attribute name="name" type="audioFormat" use="required"/>
+ <xs:attribute name="name" type="extendableAudioFormat" use="required"/>
<xs:attribute name="subformats" type="audioFormatsList" />
</xs:complexType>
</xs:element>
diff --git a/audio/6.0/config/api/current.txt b/audio/6.0/config/api/current.txt
index 6b49e5e..01db90e 100644
--- a/audio/6.0/config/api/current.txt
+++ b/audio/6.0/config/api/current.txt
@@ -133,6 +133,7 @@
enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_LDAC;
enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_LHDC;
enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_LHDC_LL;
+ enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_MAT;
enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_MAT_1_0;
enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_MAT_2_0;
enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_MAT_2_1;
@@ -198,7 +199,7 @@
public static class DevicePorts.DevicePort {
ctor public DevicePorts.DevicePort();
method public String getAddress();
- method public java.util.List<audio.policy.configuration.V6_0.AudioFormat> getEncodedFormats();
+ method public java.util.List<java.lang.String> getEncodedFormats();
method public audio.policy.configuration.V6_0.Gains getGains();
method public java.util.List<audio.policy.configuration.V6_0.Profile> getProfile();
method public audio.policy.configuration.V6_0.Role getRole();
@@ -206,7 +207,7 @@
method public String getType();
method public boolean get_default();
method public void setAddress(String);
- method public void setEncodedFormats(java.util.List<audio.policy.configuration.V6_0.AudioFormat>);
+ method public void setEncodedFormats(java.util.List<java.lang.String>);
method public void setGains(audio.policy.configuration.V6_0.Gains);
method public void setRole(audio.policy.configuration.V6_0.Role);
method public void setTagName(String);
@@ -390,10 +391,10 @@
public static class SurroundFormats.Format {
ctor public SurroundFormats.Format();
- method public audio.policy.configuration.V6_0.AudioFormat getName();
- method public java.util.List<audio.policy.configuration.V6_0.AudioFormat> getSubformats();
- method public void setName(audio.policy.configuration.V6_0.AudioFormat);
- method public void setSubformats(java.util.List<audio.policy.configuration.V6_0.AudioFormat>);
+ method public String getName();
+ method public java.util.List<java.lang.String> getSubformats();
+ method public void setName(String);
+ method public void setSubformats(java.util.List<java.lang.String>);
}
public class SurroundSound {
diff --git a/audio/6.0/config/audio_policy_configuration.xsd b/audio/6.0/config/audio_policy_configuration.xsd
index 341c6b3..c2b8c5d 100644
--- a/audio/6.0/config/audio_policy_configuration.xsd
+++ b/audio/6.0/config/audio_policy_configuration.xsd
@@ -363,6 +363,7 @@
<xs:enumeration value="AUDIO_FORMAT_AC4"/>
<xs:enumeration value="AUDIO_FORMAT_LDAC"/>
<xs:enumeration value="AUDIO_FORMAT_E_AC3_JOC"/>
+ <xs:enumeration value="AUDIO_FORMAT_MAT"/>
<xs:enumeration value="AUDIO_FORMAT_MAT_1_0"/>
<xs:enumeration value="AUDIO_FORMAT_MAT_2_0"/>
<xs:enumeration value="AUDIO_FORMAT_MAT_2_1"/>
@@ -613,13 +614,13 @@
</xs:sequence>
</xs:complexType>
<xs:simpleType name="audioFormatsList">
- <xs:list itemType="audioFormat" />
+ <xs:list itemType="extendableAudioFormat" />
</xs:simpleType>
<xs:complexType name="surroundFormats">
<xs:sequence>
<xs:element name="format" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
- <xs:attribute name="name" type="audioFormat" use="required"/>
+ <xs:attribute name="name" type="extendableAudioFormat" use="required"/>
<xs:attribute name="subformats" type="audioFormatsList" />
</xs:complexType>
</xs:element>
diff --git a/audio/7.0/IDevice.hal b/audio/7.0/IDevice.hal
index e30e545..e423f29 100644
--- a/audio/7.0/IDevice.hal
+++ b/audio/7.0/IDevice.hal
@@ -103,6 +103,11 @@
* If the stream can not be opened with the proposed audio config,
* HAL must provide suggested values for the audio config.
*
+ * Note: INVALID_ARGUMENTS is returned both in the case when the
+ * HAL can not use the provided config and in the case when
+ * the value of any argument is invalid. In the latter case the
+ * HAL must provide a default initialized suggested config.
+ *
* @param ioHandle handle assigned by AudioFlinger.
* @param device device type and (if needed) address.
* @param config stream configuration.
@@ -111,7 +116,8 @@
May be used by implementations to configure hardware effects.
* @return retval operation completion status.
* @return outStream created output stream.
- * @return suggestedConfig in case of invalid parameters, suggested config.
+ * @return suggestedConfig in the case of rejection of the proposed config,
+ * a config suggested by the HAL.
*/
openOutputStream(
AudioIoHandle ioHandle,
@@ -128,6 +134,11 @@
* If the stream can not be opened with the proposed audio config,
* HAL must provide suggested values for the audio config.
*
+ * Note: INVALID_ARGUMENTS is returned both in the case when the
+ * HAL can not use the provided config and in the case when
+ * the value of any argument is invalid. In the latter case the
+ * HAL must provide a default initialized suggested config.
+ *
* @param ioHandle handle assigned by AudioFlinger.
* @param device device type and (if needed) address.
* @param config stream configuration.
@@ -136,7 +147,8 @@
* May be used by implementations to configure processing effects.
* @return retval operation completion status.
* @return inStream in case of success, created input stream.
- * @return suggestedConfig in case of invalid parameters, suggested config.
+ * @return suggestedConfig in the case of rejection of the proposed config,
+ * a config suggested by the HAL.
*/
openInputStream(
AudioIoHandle ioHandle,
@@ -162,6 +174,9 @@
* Creates an audio patch between several source and sink ports. The handle
* is allocated by the HAL and must be unique for this audio HAL module.
*
+ * Optional method. HAL must support it if 'supportsAudioPatches' returns
+ * 'true'.
+ *
* @param sources patch sources.
* @param sinks patch sinks.
* @return retval operation completion status.
@@ -177,6 +192,9 @@
* as the HAL module can figure out a way of switching the route without
* causing audio disruption.
*
+ * Optional method. HAL must support it if 'supportsAudioPatches' returns
+ * 'true'.
+ *
* @param previousPatch handle of the previous patch to update.
* @param sources new patch sources.
* @param sinks new patch sinks.
@@ -192,6 +210,9 @@
/**
* Release an audio patch.
*
+ * Optional method. HAL must support it if 'supportsAudioPatches' returns
+ * 'true'.
+ *
* @param patch patch handle.
* @return retval operation completion status.
*/
diff --git a/audio/7.0/IStream.hal b/audio/7.0/IStream.hal
index ab9aa7d..393e38f 100644
--- a/audio/7.0/IStream.hal
+++ b/audio/7.0/IStream.hal
@@ -73,14 +73,14 @@
/**
* Sets stream parameters. Only sets parameters that are specified.
- * See the description of AudioConfigBase for the details.
*
* Optional method. If implemented, only called on a stopped stream.
*
* @param config basic stream configuration.
* @return retval operation completion status.
*/
- setAudioProperties(AudioConfigBase config) generates (Result retval);
+ setAudioProperties(AudioConfigBaseOptional config)
+ generates (Result retval);
/**
* Applies audio effect to the stream.
diff --git a/audio/7.0/IStreamIn.hal b/audio/7.0/IStreamIn.hal
index 0a3f24b..bf9ae52 100644
--- a/audio/7.0/IStreamIn.hal
+++ b/audio/7.0/IStreamIn.hal
@@ -41,6 +41,18 @@
setGain(float gain) generates (Result retval);
/**
+ * Called when the metadata of the stream's sink has been changed.
+ * Optional method
+ *
+ * @param sinkMetadata Description of the audio that is suggested by the clients.
+ * @return retval operation completion status.
+ * If any of the metadata fields contains an invalid value,
+ * returns INVALID_ARGUMENTS.
+ * If method isn't supported by the HAL returns NOT_SUPPORTED.
+ */
+ updateSinkMetadata(SinkMetadata sinkMetadata) generates (Result retval);
+
+ /**
* Commands that can be executed on the driver reader thread.
*/
enum ReadCommand : int32_t {
@@ -82,12 +94,6 @@
};
/**
- * Called when the metadata of the stream's sink has been changed.
- * @param sinkMetadata Description of the audio that is suggested by the clients.
- */
- updateSinkMetadata(SinkMetadata sinkMetadata);
-
- /**
* Set up required transports for receiving audio buffers from the driver.
*
* The transport consists of three message queues:
diff --git a/audio/7.0/IStreamOut.hal b/audio/7.0/IStreamOut.hal
index 38d750f..4daab26 100644
--- a/audio/7.0/IStreamOut.hal
+++ b/audio/7.0/IStreamOut.hal
@@ -45,6 +45,18 @@
setVolume(float left, float right) generates (Result retval);
/**
+ * Called when the metadata of the stream's source has been changed.
+ * Optional method
+ *
+ * @param sourceMetadata Description of the audio that is played by the clients.
+ * @return retval operation completion status.
+ * If any of the metadata fields contains an invalid value,
+ * returns INVALID_ARGUMENTS.
+ * If method isn't supported by the HAL returns NOT_SUPPORTED.
+ */
+ updateSourceMetadata(SourceMetadata sourceMetadata) generates (Result retval);
+
+ /**
* Commands that can be executed on the driver writer thread.
*/
enum WriteCommand : int32_t {
@@ -77,12 +89,6 @@
};
/**
- * Called when the metadata of the stream's source has been changed.
- * @param sourceMetadata Description of the audio that is played by the clients.
- */
- updateSourceMetadata(SourceMetadata sourceMetadata);
-
- /**
* Set up required transports for passing audio buffers to the driver.
*
* The transport consists of three message queues:
diff --git a/audio/7.0/config/api/current.txt b/audio/7.0/config/api/current.txt
index 1da8b09..48093c5 100644
--- a/audio/7.0/config/api/current.txt
+++ b/audio/7.0/config/api/current.txt
@@ -198,6 +198,7 @@
enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_APTX_HD;
enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_APTX_TWSP;
enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_CELT;
+ enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_DEFAULT;
enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_DOLBY_TRUEHD;
enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_DSD;
enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_DTS;
@@ -211,6 +212,7 @@
enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_FLAC;
enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_HE_AAC_V1;
enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_HE_AAC_V2;
+ enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_IEC60958;
enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_IEC61937;
enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_LC3;
enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_LDAC;
@@ -222,6 +224,10 @@
enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_MAT_2_1;
enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_MP2;
enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_MP3;
+ enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_MPEGH_BL_L3;
+ enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_MPEGH_BL_L4;
+ enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_MPEGH_LC_L3;
+ enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_MPEGH_LC_L4;
enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_OPUS;
enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_PCM_16_BIT;
enum_constant public static final android.audio.policy.configuration.V7_0.AudioFormat AUDIO_FORMAT_PCM_24_BIT_PACKED;
@@ -250,7 +256,6 @@
enum_constant public static final android.audio.policy.configuration.V7_0.AudioInOutFlag AUDIO_INPUT_FLAG_HW_AV_SYNC;
enum_constant public static final android.audio.policy.configuration.V7_0.AudioInOutFlag AUDIO_INPUT_FLAG_HW_HOTWORD;
enum_constant public static final android.audio.policy.configuration.V7_0.AudioInOutFlag AUDIO_INPUT_FLAG_MMAP_NOIRQ;
- enum_constant public static final android.audio.policy.configuration.V7_0.AudioInOutFlag AUDIO_INPUT_FLAG_NONE;
enum_constant public static final android.audio.policy.configuration.V7_0.AudioInOutFlag AUDIO_INPUT_FLAG_RAW;
enum_constant public static final android.audio.policy.configuration.V7_0.AudioInOutFlag AUDIO_INPUT_FLAG_SYNC;
enum_constant public static final android.audio.policy.configuration.V7_0.AudioInOutFlag AUDIO_INPUT_FLAG_VOIP_TX;
@@ -263,7 +268,6 @@
enum_constant public static final android.audio.policy.configuration.V7_0.AudioInOutFlag AUDIO_OUTPUT_FLAG_IEC958_NONAUDIO;
enum_constant public static final android.audio.policy.configuration.V7_0.AudioInOutFlag AUDIO_OUTPUT_FLAG_INCALL_MUSIC;
enum_constant public static final android.audio.policy.configuration.V7_0.AudioInOutFlag AUDIO_OUTPUT_FLAG_MMAP_NOIRQ;
- enum_constant public static final android.audio.policy.configuration.V7_0.AudioInOutFlag AUDIO_OUTPUT_FLAG_NONE;
enum_constant public static final android.audio.policy.configuration.V7_0.AudioInOutFlag AUDIO_OUTPUT_FLAG_NON_BLOCKING;
enum_constant public static final android.audio.policy.configuration.V7_0.AudioInOutFlag AUDIO_OUTPUT_FLAG_PRIMARY;
enum_constant public static final android.audio.policy.configuration.V7_0.AudioInOutFlag AUDIO_OUTPUT_FLAG_RAW;
@@ -541,7 +545,7 @@
public enum Version {
method @NonNull public String getRawName();
- enum_constant public static final android.audio.policy.configuration.V7_0.Version _1_0;
+ enum_constant public static final android.audio.policy.configuration.V7_0.Version _7_0;
}
public class Volume {
diff --git a/audio/7.0/config/audio_policy_configuration.xsd b/audio/7.0/config/audio_policy_configuration.xsd
index 56b3a27..531572b 100644
--- a/audio/7.0/config/audio_policy_configuration.xsd
+++ b/audio/7.0/config/audio_policy_configuration.xsd
@@ -20,7 +20,7 @@
<!-- List the config versions supported by audio policy. -->
<xs:simpleType name="version">
<xs:restriction base="xs:decimal">
- <xs:enumeration value="1.0"/>
+ <xs:enumeration value="7.0"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="halVersion">
@@ -159,13 +159,9 @@
<xs:annotation>
<xs:documentation xml:lang="en">
The flags indicate suggested stream attributes supported by the profile.
- Use of AUDIO_{INPUT|OUTPUT}_FLAG_NONE in the XML file isn't required
- as empty flag lists are allowed. However these constants are useful for
- representing an empty enum value.
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:string">
- <xs:enumeration value="AUDIO_OUTPUT_FLAG_NONE" />
<xs:enumeration value="AUDIO_OUTPUT_FLAG_DIRECT" />
<xs:enumeration value="AUDIO_OUTPUT_FLAG_PRIMARY" />
<xs:enumeration value="AUDIO_OUTPUT_FLAG_FAST" />
@@ -181,7 +177,6 @@
<xs:enumeration value="AUDIO_OUTPUT_FLAG_MMAP_NOIRQ" />
<xs:enumeration value="AUDIO_OUTPUT_FLAG_VOIP_RX" />
<xs:enumeration value="AUDIO_OUTPUT_FLAG_INCALL_MUSIC" />
- <xs:enumeration value="AUDIO_INPUT_FLAG_NONE" />
<xs:enumeration value="AUDIO_INPUT_FLAG_FAST" />
<xs:enumeration value="AUDIO_INPUT_FLAG_HW_HOTWORD" />
<xs:enumeration value="AUDIO_INPUT_FLAG_RAW" />
@@ -329,6 +324,7 @@
</xs:simpleType>
<xs:simpleType name="audioFormat">
<xs:restriction base="xs:string">
+ <xs:enumeration value="AUDIO_FORMAT_DEFAULT" />
<xs:enumeration value="AUDIO_FORMAT_PCM_16_BIT" />
<xs:enumeration value="AUDIO_FORMAT_PCM_8_BIT"/>
<xs:enumeration value="AUDIO_FORMAT_PCM_32_BIT"/>
@@ -406,6 +402,11 @@
<xs:enumeration value="AUDIO_FORMAT_LHDC_LL"/>
<xs:enumeration value="AUDIO_FORMAT_APTX_TWSP"/>
<xs:enumeration value="AUDIO_FORMAT_LC3"/>
+ <xs:enumeration value="AUDIO_FORMAT_MPEGH_BL_L3"/>
+ <xs:enumeration value="AUDIO_FORMAT_MPEGH_BL_L4"/>
+ <xs:enumeration value="AUDIO_FORMAT_MPEGH_LC_L3"/>
+ <xs:enumeration value="AUDIO_FORMAT_MPEGH_LC_L4"/>
+ <xs:enumeration value="AUDIO_FORMAT_IEC60958"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="extendableAudioFormat">
diff --git a/audio/7.0/config/update_audio_policy_config.sh b/audio/7.0/config/update_audio_policy_config.sh
index 051a0df..159fa35 100755
--- a/audio/7.0/config/update_audio_policy_config.sh
+++ b/audio/7.0/config/update_audio_policy_config.sh
@@ -113,6 +113,9 @@
echo "Press Ctrl-C to cancel, Enter to continue"
read
+# Update 'audioPolicyConfiguration version="1.0"' -> 7.0 in the main file
+sed -i -r -e 's/(audioPolicyConfiguration version=")1.0/\17.0/' ${SOURCE_CONFIG}
+
updateFile() {
FILE=$1
ATTR=$2
diff --git a/audio/README.md b/audio/README.md
index b77b9ba..1938ad4 100644
--- a/audio/README.md
+++ b/audio/README.md
@@ -7,47 +7,49 @@
## Directory Structure
-* `2.0` -- version 2.0 of the core HIDL API. Note that `.hal` files
+* `2.0` — version 2.0 of the core HIDL API. Note that `.hal` files
can not be moved into the `core` directory because that would change
its namespace and include path.
- - `config` -- the XSD schema for the Audio Policy Manager
+ - `config` — the XSD schema for the Audio Policy Manager
configuration file.
-* `4.0` -- version 4.0 of the core HIDL API.
+* `4.0` — version 4.0 of the core HIDL API.
* ...
-* `common` -- common types for audio core and effect HIDL API.
- - `2.0` -- version 2.0 of the common types HIDL API.
- - `4.0` -- version 4.0.
+* `common` — common types for audio core and effect HIDL API.
+ - `2.0` — version 2.0 of the common types HIDL API.
+ - `4.0` — version 4.0.
- ...
- - `7.0` -- version 7.0.
- - `example` -- example implementation of the core and effect
+ - `7.0` — version 7.0.
+ - `example` — example implementation of the core and effect
V7.0 API. It represents a "fake" audio HAL that doesn't
actually communicate with hardware.
- - `all-versions` -- code common to all version of both core and effect API.
- - `default` -- shared code of the default implementation.
- - `service` -- vendor HAL service for hosting the default
+ - `all-versions` — code common to all version of both core and effect API.
+ - `default` — shared code of the default implementation.
+ - `service` — vendor HAL service for hosting the default
implementation.
- - `test` -- utilities used by tests.
- - `util` -- utilities used by both implementation and tests.
-* `core` -- VTS tests and the default implementation of the core API
+ - `test` — utilities used by tests.
+ - `util` — utilities used by both implementation and tests.
+* `core` — VTS tests and the default implementation of the core API
(not HIDL API, it's in `audio/N.M`).
- - `7.0` -- code specific to version V7.0 of the core HIDL API
- - `all-versions` -- the code is common between all versions,
+ - `7.0` — code specific to version V7.0 of the core HIDL API
+ - `all-versions` — the code is common between all versions,
version-specific parts are enclosed into conditional directives
of preprocessor or reside in dedicated files.
- - `default` -- code that wraps the legacy API (from
+ - `default` — code that wraps the legacy API (from
`hardware/libhardware`).
+ - `util` — utilities for the default implementation.
- `vts` VTS tests for the core HIDL API.
-* `effect` -- same for the effect HIDL API.
+* `effect` — same for the effect HIDL API.
- `2.0`
- - `config` -- the XSD schema for the Audio Effects configuration
- file.
+ - `config` — the XSD schema for the Audio Effects configuration file.
- `4.0`
- ...
- `all-versions`
- - `default`
- - `vts`
-* `policy` -- Configurable Audio Policy schemes.
- - `1.0` -- note that versions of CAP are not linked to the versions
+ - `default` — code that wraps the legacy API (from
+ `hardware/libhardware`).
+ - `util` — utilities for the default implementation.
+ - `vts` VTS tests for the effect HIDL API.
+* `policy` — Configurable Audio Policy schemes.
+ - `1.0` — note that versions of CAP are not linked to the versions
of audio HAL.
- - `vts` -- VTS tests for validating actual configuration files.
- - `xml` -- XSD schemas for CAP configuration files.
+ - `vts` — VTS tests for validating actual configuration files.
+ - `xml` — XSD schemas for CAP configuration files.
diff --git a/audio/common/7.0/enums/include/android_audio_policy_configuration_V7_0-enums.h b/audio/common/7.0/enums/include/android_audio_policy_configuration_V7_0-enums.h
index b7c1cc9..b427f3a 100644
--- a/audio/common/7.0/enums/include/android_audio_policy_configuration_V7_0-enums.h
+++ b/audio/common/7.0/enums/include/android_audio_policy_configuration_V7_0-enums.h
@@ -212,12 +212,17 @@
return isOutputDevice(stringToAudioDevice(device));
}
-static inline bool isVendorExtension(const std::string& device) {
+static inline bool maybeVendorExtension(const std::string& s) {
+ // Only checks whether the string starts with the "vendor prefix".
+ static const std::string vendorPrefix = "VX_";
+ return s.size() > vendorPrefix.size() && s.substr(0, vendorPrefix.size()) == vendorPrefix;
+}
+
+static inline bool isVendorExtension(const std::string& s) {
// Must match the "vendorExtension" rule from the XSD file.
static const std::string vendorPrefix = "VX_";
- return device.size() > vendorPrefix.size() &&
- device.substr(0, vendorPrefix.size()) == vendorPrefix &&
- std::all_of(device.begin() + vendorPrefix.size(), device.end(),
+ return maybeVendorExtension(s) &&
+ std::all_of(s.begin() + vendorPrefix.size(), s.end(),
[](unsigned char c) { return c == '_' || std::isalnum(c); });
}
diff --git a/audio/common/7.0/example/Effect.cpp b/audio/common/7.0/example/Effect.cpp
index 27f28c6..5788811 100644
--- a/audio/common/7.0/example/Effect.cpp
+++ b/audio/common/7.0/example/Effect.cpp
@@ -107,14 +107,13 @@
}
Return<void> Effect::getConfig(getConfig_cb _hidl_cb) {
- const EffectConfig config = {
- {} /* inputCfg */,
- // outputCfg
- {{} /* buffer */,
- {toString(xsd::AudioFormat::AUDIO_FORMAT_PCM_16_BIT), 48000 /* samplingRateHz */,
- toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO)}, /* base */
- EffectBufferAccess::ACCESS_ACCUMULATE,
- 0 /* mask */}};
+ EffectConfig config;
+ // inputCfg left unspecified.
+ config.outputCfg.base.format.value(toString(xsd::AudioFormat::AUDIO_FORMAT_PCM_16_BIT));
+ config.outputCfg.base.sampleRateHz.value(48000);
+ config.outputCfg.base.channelMask.value(
+ toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO));
+ config.outputCfg.accessMode.value(EffectBufferAccess::ACCESS_ACCUMULATE);
_hidl_cb(Result::OK, config);
return Void();
}
diff --git a/audio/common/7.0/types.hal b/audio/common/7.0/types.hal
index ed56c73..bea0705 100644
--- a/audio/common/7.0/types.hal
+++ b/audio/common/7.0/types.hal
@@ -61,6 +61,8 @@
* Audio stream type describing the intended use case of a stream.
* See 'audioStreamType' in audio_policy_configuration.xsd for the
* list of allowed values.
+ *
+ * An empty string is used to specify the "default" stream type.
*/
typedef string AudioStreamType;
@@ -118,9 +120,28 @@
* Base configuration attributes applicable to any stream of audio.
*/
struct AudioConfigBase {
- AudioFormat format; // empty means 'unspecified'
- uint32_t sampleRateHz; // 0 means 'unspecified'
- AudioChannelMask channelMask; // empty means 'unspecified'
+ AudioFormat format;
+ uint32_t sampleRateHz;
+ AudioChannelMask channelMask;
+};
+
+/**
+ * Base configuration attributes applicable to any stream of audio.
+ * Any attribute may be left unspecified.
+ */
+struct AudioConfigBaseOptional {
+ safe_union Format {
+ Monostate unspecified;
+ AudioFormat value;
+ } format;
+ safe_union SampleRate {
+ Monostate unspecified;
+ uint32_t value;
+ } sampleRateHz;
+ safe_union ChannelMask {
+ Monostate unspecified;
+ AudioChannelMask value;
+ } channelMask;
};
/**
@@ -270,10 +291,22 @@
*/
struct AudioConfig {
AudioConfigBase base;
- AudioOffloadInfo offloadInfo;
+ safe_union OffloadInfo {
+ Monostate unspecified;
+ AudioOffloadInfo info;
+ } offloadInfo;
uint64_t frameCount;
};
+/**
+ * AudioTag is an additional use case qualifier complementing
+ * AudioUsage and AudioContentType. Tags are set by vendor specific applications
+ * and must be prefixed by "VX_". Vendor must namespace their tag
+ * names to avoid conflicts. See 'vendorExtension' in audio_policy_configuration.xsd
+ * for a formal definition.
+ */
+typedef string AudioTag;
+
/** Metadata of a playback track for a StreamOut. */
struct PlaybackTrackMetadata {
AudioUsage usage;
@@ -284,6 +317,9 @@
* Must not be negative.
*/
float gain;
+ AudioChannelMask channelMask;
+ /** Tags from AudioTrack audio atttributes */
+ vec<AudioTag> tags;
};
/** Metadatas of the source of a StreamOut. */
@@ -307,6 +343,9 @@
Monostate unspecified;
DeviceAddress device;
} destination;
+ AudioChannelMask channelMask;
+ /** Tags from AudioTrack audio atttributes */
+ vec<AudioTag> tags;
};
/** Metadatas of the sink of a StreamIn. */
@@ -421,11 +460,9 @@
*/
AudioPortHandle id;
/**
- * Basic parameters: sampling rate, format, channel mask. Only some of the
- * parameters (or none) may be set. See the documentation of the
- * AudioConfigBase struct.
+ * Basic parameters: sampling rate, format, channel mask.
*/
- AudioConfigBase base;
+ AudioConfigBaseOptional base;
/** Associated gain control. */
safe_union OptionalGain {
Monostate unspecified;
diff --git a/audio/common/all-versions/default/7.0/HidlUtils.cpp b/audio/common/all-versions/default/7.0/HidlUtils.cpp
index c985a70..2949fac 100644
--- a/audio/common/all-versions/default/7.0/HidlUtils.cpp
+++ b/audio/common/all-versions/default/7.0/HidlUtils.cpp
@@ -16,11 +16,13 @@
#include <stdio.h>
#include <string.h>
+#include <algorithm>
#define LOG_TAG "HidlUtils"
#include <log/log.h>
#include <android_audio_policy_configuration_V7_0-enums.h>
+#include <common/all-versions/HidlSupport.h>
#include <common/all-versions/VersionUtils.h>
#include "HidlUtils.h"
@@ -146,6 +148,59 @@
return result;
}
+status_t HidlUtils::audioConfigBaseOptionalFromHal(const audio_config_base_t& halConfigBase,
+ bool isInput, bool formatSpecified,
+ bool sampleRateSpecified,
+ bool channelMaskSpecified,
+ AudioConfigBaseOptional* configBase) {
+ status_t result = NO_ERROR;
+ if (formatSpecified) {
+ AudioFormat value;
+ CONVERT_CHECKED(audioFormatFromHal(halConfigBase.format, &value), result);
+ configBase->format.value(std::move(value));
+ } else {
+ configBase->format.unspecified({});
+ }
+ if (sampleRateSpecified) {
+ configBase->sampleRateHz.value(halConfigBase.sample_rate);
+ } else {
+ configBase->sampleRateHz.unspecified({});
+ }
+ if (channelMaskSpecified) {
+ AudioChannelMask value;
+ CONVERT_CHECKED(audioChannelMaskFromHal(halConfigBase.channel_mask, isInput, &value),
+ result);
+ configBase->channelMask.value(std::move(value));
+ }
+ return result;
+}
+
+status_t HidlUtils::audioConfigBaseOptionalToHal(const AudioConfigBaseOptional& configBase,
+ audio_config_base_t* halConfigBase,
+ bool* formatSpecified, bool* sampleRateSpecified,
+ bool* channelMaskSpecified) {
+ status_t result = NO_ERROR;
+ *formatSpecified = configBase.format.getDiscriminator() ==
+ AudioConfigBaseOptional::Format::hidl_discriminator::value;
+ if (*formatSpecified) {
+ CONVERT_CHECKED(audioFormatToHal(configBase.format.value(), &halConfigBase->format),
+ result);
+ }
+ *sampleRateSpecified = configBase.sampleRateHz.getDiscriminator() ==
+ AudioConfigBaseOptional::SampleRate::hidl_discriminator::value;
+ if (*sampleRateSpecified) {
+ halConfigBase->sample_rate = configBase.sampleRateHz.value();
+ }
+ *channelMaskSpecified = configBase.channelMask.getDiscriminator() ==
+ AudioConfigBaseOptional::ChannelMask::hidl_discriminator::value;
+ if (*channelMaskSpecified) {
+ CONVERT_CHECKED(
+ audioChannelMaskToHal(configBase.channelMask.value(), &halConfigBase->channel_mask),
+ result);
+ }
+ return result;
+}
+
status_t HidlUtils::audioContentTypeFromHal(const audio_content_type_t halContentType,
AudioContentType* contentType) {
*contentType = audio_content_type_to_string(halContentType);
@@ -227,7 +282,7 @@
hidl_vec<AudioGainMode>* gainModeMask) {
status_t status = NO_ERROR;
std::vector<AudioGainMode> result;
- for (uint32_t bit = 0; bit < sizeof(audio_gain_mode_t) * 8; ++bit) {
+ for (uint32_t bit = 0; halGainModeMask != 0 && bit < sizeof(audio_gain_mode_t) * 8; ++bit) {
audio_gain_mode_t flag = static_cast<audio_gain_mode_t>(1u << bit);
if ((flag & halGainModeMask) == flag) {
AudioGainMode flagStr = audio_gain_mode_to_string(flag);
@@ -237,6 +292,7 @@
ALOGE("Unknown audio gain mode value 0x%X", flag);
status = BAD_VALUE;
}
+ halGainModeMask = static_cast<audio_gain_mode_t>(halGainModeMask & ~flag);
}
}
*gainModeMask = result;
@@ -279,25 +335,35 @@
return BAD_VALUE;
}
+// The "default" value of audio_stream_type_t is represented by an empty string.
status_t HidlUtils::audioStreamTypeFromHal(audio_stream_type_t halStreamType,
AudioStreamType* streamType) {
- *streamType = audio_stream_type_to_string(halStreamType);
- if (!streamType->empty() && !xsd::isUnknownAudioStreamType(*streamType)) {
+ if (halStreamType != AUDIO_STREAM_DEFAULT) {
+ *streamType = audio_stream_type_to_string(halStreamType);
+ if (!streamType->empty() && !xsd::isUnknownAudioStreamType(*streamType)) {
+ return NO_ERROR;
+ }
+ ALOGE("Unknown audio stream type value 0x%X", halStreamType);
+ return BAD_VALUE;
+ } else {
+ *streamType = "";
return NO_ERROR;
}
- ALOGE("Unknown audio stream type value 0x%X", halStreamType);
- return BAD_VALUE;
}
status_t HidlUtils::audioStreamTypeToHal(const AudioStreamType& streamType,
audio_stream_type_t* halStreamType) {
- if (!xsd::isUnknownAudioStreamType(streamType) &&
- audio_stream_type_from_string(streamType.c_str(), halStreamType)) {
+ if (!streamType.empty()) {
+ if (!xsd::isUnknownAudioStreamType(streamType) &&
+ audio_stream_type_from_string(streamType.c_str(), halStreamType)) {
+ return NO_ERROR;
+ }
+ ALOGE("Unknown audio stream type \"%s\"", streamType.c_str());
+ return BAD_VALUE;
+ } else {
+ *halStreamType = AUDIO_STREAM_DEFAULT;
return NO_ERROR;
}
- ALOGE("Unknown audio stream type \"%s\"", streamType.c_str());
- *halStreamType = AUDIO_STREAM_DEFAULT;
- return BAD_VALUE;
}
status_t HidlUtils::audioConfigFromHal(const audio_config_t& halConfig, bool isInput,
@@ -306,7 +372,12 @@
audio_config_base_t halConfigBase = {halConfig.sample_rate, halConfig.channel_mask,
halConfig.format};
CONVERT_CHECKED(audioConfigBaseFromHal(halConfigBase, isInput, &config->base), result);
- CONVERT_CHECKED(audioOffloadInfoFromHal(halConfig.offload_info, &config->offloadInfo), result);
+ if (halConfig.offload_info.sample_rate != 0) {
+ config->offloadInfo.info({});
+ CONVERT_CHECKED(
+ audioOffloadInfoFromHal(halConfig.offload_info, &config->offloadInfo.info()),
+ result);
+ }
config->frameCount = halConfig.frame_count;
return result;
}
@@ -319,7 +390,11 @@
halConfig->sample_rate = halConfigBase.sample_rate;
halConfig->channel_mask = halConfigBase.channel_mask;
halConfig->format = halConfigBase.format;
- CONVERT_CHECKED(audioOffloadInfoToHal(config.offloadInfo, &halConfig->offload_info), result);
+ if (config.offloadInfo.getDiscriminator() ==
+ AudioConfig::OffloadInfo::hidl_discriminator::info) {
+ CONVERT_CHECKED(audioOffloadInfoToHal(config.offloadInfo.info(), &halConfig->offload_info),
+ result);
+ }
halConfig->frame_count = config.frameCount;
return result;
}
@@ -498,23 +573,14 @@
audio_port_config_has_input_direction(&halConfig), isInput);
result = BAD_VALUE;
}
- if (halConfig.config_mask & AUDIO_PORT_CONFIG_SAMPLE_RATE) {
- config->base.sampleRateHz = halConfig.sample_rate;
- } else {
- config->base.sampleRateHz = {};
- }
- if (halConfig.config_mask & AUDIO_PORT_CONFIG_CHANNEL_MASK) {
- CONVERT_CHECKED(
- audioChannelMaskFromHal(halConfig.channel_mask, isInput, &config->base.channelMask),
- result);
- } else {
- config->base.channelMask = {};
- }
- if (halConfig.config_mask & AUDIO_PORT_CONFIG_FORMAT) {
- CONVERT_CHECKED(audioFormatFromHal(halConfig.format, &config->base.format), result);
- } else {
- config->base.format = {};
- }
+ audio_config_base_t halConfigBase = {halConfig.sample_rate, halConfig.channel_mask,
+ halConfig.format};
+ CONVERT_CHECKED(
+ audioConfigBaseOptionalFromHal(
+ halConfigBase, isInput, halConfig.config_mask & AUDIO_PORT_CONFIG_FORMAT,
+ halConfig.config_mask & AUDIO_PORT_CONFIG_SAMPLE_RATE,
+ halConfig.config_mask & AUDIO_PORT_CONFIG_CHANNEL_MASK, &config->base),
+ result);
if (halConfig.config_mask & AUDIO_PORT_CONFIG_GAIN) {
config->gain.config({});
CONVERT_CHECKED(audioGainConfigFromHal(halConfig.gain, isInput, &config->gain.config()),
@@ -530,19 +596,23 @@
status_t result = NO_ERROR;
memset(halConfig, 0, sizeof(audio_port_config));
halConfig->id = config.id;
- halConfig->config_mask = {};
- if (config.base.sampleRateHz != 0) {
+ halConfig->config_mask = 0;
+ audio_config_base_t halConfigBase = AUDIO_CONFIG_BASE_INITIALIZER;
+ bool formatSpecified = false, sRateSpecified = false, channelMaskSpecified = false;
+ CONVERT_CHECKED(audioConfigBaseOptionalToHal(config.base, &halConfigBase, &formatSpecified,
+ &sRateSpecified, &channelMaskSpecified),
+ result);
+ if (sRateSpecified) {
halConfig->config_mask |= AUDIO_PORT_CONFIG_SAMPLE_RATE;
- halConfig->sample_rate = config.base.sampleRateHz;
+ halConfig->sample_rate = halConfigBase.sample_rate;
}
- if (!config.base.channelMask.empty()) {
+ if (channelMaskSpecified) {
halConfig->config_mask |= AUDIO_PORT_CONFIG_CHANNEL_MASK;
- CONVERT_CHECKED(audioChannelMaskToHal(config.base.channelMask, &halConfig->channel_mask),
- result);
+ halConfig->channel_mask = halConfigBase.channel_mask;
}
- if (!config.base.format.empty()) {
+ if (formatSpecified) {
halConfig->config_mask |= AUDIO_PORT_CONFIG_FORMAT;
- CONVERT_CHECKED(audioFormatToHal(config.base.format, &halConfig->format), result);
+ halConfig->format = halConfigBase.format;
}
if (config.gain.getDiscriminator() ==
AudioPortConfig::OptionalGain::hidl_discriminator::config) {
@@ -800,6 +870,75 @@
return result;
}
+status_t HidlUtils::audioTagsFromHal(const std::vector<std::string>& strTags,
+ hidl_vec<AudioTag>* tags) {
+ status_t result = NO_ERROR;
+ tags->resize(strTags.size());
+ size_t to = 0;
+ for (size_t from = 0; from < strTags.size(); ++from) {
+ const auto& tag = strTags[from];
+ if (xsd::isVendorExtension(tag)) {
+ (*tags)[to++] = tag;
+ } else {
+ ALOGE("Vendor extension tag is ill-formed: \"%s\"", tag.c_str());
+ result = BAD_VALUE;
+ }
+ }
+ if (to != strTags.size()) {
+ tags->resize(to);
+ }
+ return result;
+}
+
+status_t HidlUtils::audioTagsToHal(const hidl_vec<AudioTag>& tags, char* halTags) {
+ memset(halTags, 0, AUDIO_ATTRIBUTES_TAGS_MAX_SIZE);
+ status_t result = NO_ERROR;
+ std::ostringstream halTagsBuffer;
+ bool hasValue = false;
+ for (const auto& tag : tags) {
+ if (hasValue) {
+ halTagsBuffer << sAudioTagSeparator;
+ }
+ if (xsd::isVendorExtension(tag) && strchr(tag.c_str(), sAudioTagSeparator) == nullptr) {
+ halTagsBuffer << tag;
+ hasValue = true;
+ } else {
+ ALOGE("Vendor extension tag is ill-formed: \"%s\"", tag.c_str());
+ result = BAD_VALUE;
+ }
+ }
+ std::string fullHalTags{std::move(halTagsBuffer.str())};
+ strncpy(halTags, fullHalTags.c_str(), AUDIO_ATTRIBUTES_TAGS_MAX_SIZE);
+ CONVERT_CHECKED(fullHalTags.length() <= AUDIO_ATTRIBUTES_TAGS_MAX_SIZE ? NO_ERROR : BAD_VALUE,
+ result);
+ return result;
+}
+
+hidl_vec<AudioTag> HidlUtils::filterOutNonVendorTags(const hidl_vec<AudioTag>& tags) {
+ hidl_vec<AudioTag> result;
+ result.resize(tags.size());
+ size_t resultIdx = 0;
+ for (const auto& tag : tags) {
+ if (xsd::maybeVendorExtension(tag)) {
+ result[resultIdx++] = tag;
+ }
+ }
+ if (resultIdx != result.size()) {
+ result.resize(resultIdx);
+ }
+ return result;
+}
+
+std::vector<std::string> HidlUtils::filterOutNonVendorTags(const std::vector<std::string>& tags) {
+ std::vector<std::string> result;
+ std::copy_if(tags.begin(), tags.end(), std::back_inserter(result), xsd::maybeVendorExtension);
+ return result;
+}
+
+std::vector<std::string> HidlUtils::splitAudioTags(const char* halTags) {
+ return utils::splitString(halTags, sAudioTagSeparator);
+}
+
status_t HidlUtils::deviceAddressFromHal(audio_devices_t halDeviceType,
const char* halDeviceAddress, DeviceAddress* device) {
status_t result = NO_ERROR;
diff --git a/audio/common/all-versions/default/Android.bp b/audio/common/all-versions/default/Android.bp
index 45f0b8f..29a3d6f 100644
--- a/audio/common/all-versions/default/Android.bp
+++ b/audio/common/all-versions/default/Android.bp
@@ -114,7 +114,7 @@
],
}
-cc_library_shared {
+cc_library {
name: "android.hardware.audio.common@6.0-util",
defaults: ["android.hardware.audio.common-util_default"],
srcs: [":android.hardware.audio.common-util@2-6"],
@@ -152,6 +152,32 @@
// Note: this isn't a VTS test, but rather a unit test
// to verify correctness of conversion utilities.
cc_test {
+ name: "android.hardware.audio.common@6.0-util_tests",
+ defaults: ["android.hardware.audio.common-util_default"],
+
+ srcs: ["tests/hidlutils6_tests.cpp"],
+
+ // Use static linking to allow running in presubmit on
+ // targets that don't have HAL V6.
+ static_libs: [
+ "android.hardware.audio.common@6.0",
+ "android.hardware.audio.common@6.0-util",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ "-DMAJOR_VERSION=6",
+ "-DMINOR_VERSION=0",
+ "-include common/all-versions/VersionMacro.h",
+ ],
+
+ test_suites: ["device-tests"],
+}
+
+// Note: this isn't a VTS test, but rather a unit test
+// to verify correctness of conversion utilities.
+cc_test {
name: "android.hardware.audio.common@7.0-util_tests",
defaults: ["android.hardware.audio.common-util_default"],
diff --git a/audio/common/all-versions/default/HidlUtils.h b/audio/common/all-versions/default/HidlUtils.h
index a0bd1bc..dd4ca4d 100644
--- a/audio/common/all-versions/default/HidlUtils.h
+++ b/audio/common/all-versions/default/HidlUtils.h
@@ -20,6 +20,8 @@
#include PATH(android/hardware/audio/common/FILE_VERSION/types.h)
#include <memory>
+#include <string>
+#include <vector>
#include <system/audio.h>
@@ -77,6 +79,8 @@
#endif
#if MAJOR_VERSION >= 7
+ static constexpr char sAudioTagSeparator = ';';
+
static status_t audioChannelMaskFromHal(audio_channel_mask_t halChannelMask, bool isInput,
AudioChannelMask* channelMask);
static status_t audioChannelMasksFromHal(const std::vector<std::string>& halChannelMasks,
@@ -87,6 +91,15 @@
AudioConfigBase* configBase);
static status_t audioConfigBaseToHal(const AudioConfigBase& configBase,
audio_config_base_t* halConfigBase);
+ static status_t audioConfigBaseOptionalFromHal(const audio_config_base_t& halConfigBase,
+ bool isInput, bool formatSpecified,
+ bool sampleRateSpecified,
+ bool channelMaskSpecified,
+ AudioConfigBaseOptional* configBase);
+ static status_t audioConfigBaseOptionalToHal(const AudioConfigBaseOptional& configBase,
+ audio_config_base_t* halConfigBase,
+ bool* formatSpecified, bool* sampleRateSpecified,
+ bool* channelMaskSpecified);
static status_t audioDeviceTypeFromHal(audio_devices_t halDevice, AudioDevice* device);
static status_t audioDeviceTypeToHal(const AudioDevice& device, audio_devices_t* halDevice);
static status_t audioFormatFromHal(audio_format_t halFormat, AudioFormat* format);
@@ -107,6 +120,12 @@
AudioStreamType* streamType);
static status_t audioStreamTypeToHal(const AudioStreamType& streamType,
audio_stream_type_t* halStreamType);
+ static status_t audioTagsFromHal(const std::vector<std::string>& strTags,
+ hidl_vec<AudioTag>* tags);
+ static status_t audioTagsToHal(const hidl_vec<AudioTag>& tags, char* halTags);
+ static hidl_vec<AudioTag> filterOutNonVendorTags(const hidl_vec<AudioTag>& tags);
+ static std::vector<std::string> filterOutNonVendorTags(const std::vector<std::string>& tags);
+ static std::vector<std::string> splitAudioTags(const char* halTags);
private:
static status_t audioIndexChannelMaskFromHal(audio_channel_mask_t halChannelMask,
@@ -126,6 +145,7 @@
struct audio_port_config_device_ext* device,
struct audio_port_config_mix_ext* mix,
struct audio_port_config_session_ext* session);
+
#endif // MAJOR_VERSION >= 7
// V4 and below have DeviceAddress defined in the 'core' interface.
@@ -190,6 +210,9 @@
*halDeviceType == AUDIO_DEVICE_IN_REMOTE_SUBMIX) {
snprintf(halDeviceAddress, AUDIO_DEVICE_MAX_ADDRESS_LEN, "%s",
device.rSubmixAddress.c_str());
+ } else {
+ // Fall back to bus address for other device types, e.g. for microphones.
+ snprintf(halDeviceAddress, AUDIO_DEVICE_MAX_ADDRESS_LEN, "%s", device.busAddress.c_str());
}
return NO_ERROR;
}
@@ -229,6 +252,7 @@
device->rSubmixAddress = halDeviceAddress;
return OK;
}
+ // Fall back to bus address for other device types, e.g. for microphones.
device->busAddress = halDeviceAddress;
return NO_ERROR;
}
diff --git a/audio/common/all-versions/default/TEST_MAPPING b/audio/common/all-versions/default/TEST_MAPPING
index 4316ccf..c965113 100644
--- a/audio/common/all-versions/default/TEST_MAPPING
+++ b/audio/common/all-versions/default/TEST_MAPPING
@@ -1,6 +1,9 @@
{
"presubmit": [
{
+ "name": "android.hardware.audio.common@6.0-util_tests"
+ },
+ {
"name": "android.hardware.audio.common@7.0-util_tests"
}
]
diff --git a/audio/common/all-versions/default/UuidUtils.cpp b/audio/common/all-versions/default/UuidUtils.cpp
index 85edc7b..6c4c94d 100644
--- a/audio/common/all-versions/default/UuidUtils.cpp
+++ b/audio/common/all-versions/default/UuidUtils.cpp
@@ -42,6 +42,14 @@
memcpy(halUuid->node, uuid.node.data(), uuid.node.size());
}
+std::string UuidUtils::uuidToString(const audio_uuid_t& halUuid) {
+ char str[64];
+ snprintf(str, sizeof(str), "%08x-%04x-%04x-%04x-%02x%02x%02x%02x%02x%02x", halUuid.timeLow,
+ halUuid.timeMid, halUuid.timeHiAndVersion, halUuid.clockSeq, halUuid.node[0],
+ halUuid.node[1], halUuid.node[2], halUuid.node[3], halUuid.node[4], halUuid.node[5]);
+ return str;
+}
+
} // namespace implementation
} // namespace CPP_VERSION
} // namespace common
diff --git a/audio/common/all-versions/default/UuidUtils.h b/audio/common/all-versions/default/UuidUtils.h
index 38db48a..cd04fb0 100644
--- a/audio/common/all-versions/default/UuidUtils.h
+++ b/audio/common/all-versions/default/UuidUtils.h
@@ -17,14 +17,14 @@
#ifndef android_hardware_audio_Uuid_Utils_H_
#define android_hardware_audio_Uuid_Utils_H_
+#include <string>
+
// clang-format off
#include PATH(android/hardware/audio/common/FILE_VERSION/types.h)
// clang-format on
#include <system/audio.h>
-using ::android::hardware::hidl_vec;
-
namespace android {
namespace hardware {
namespace audio {
@@ -38,6 +38,7 @@
public:
static void uuidFromHal(const audio_uuid_t& halUuid, Uuid* uuid);
static void uuidToHal(const Uuid& uuid, audio_uuid_t* halUuid);
+ static std::string uuidToString(const audio_uuid_t& halUuid);
};
} // namespace implementation
diff --git a/audio/common/all-versions/default/service/service.cpp b/audio/common/all-versions/default/service/service.cpp
index 710ddce..bbc14ad 100644
--- a/audio/common/all-versions/default/service/service.cpp
+++ b/audio/common/all-versions/default/service/service.cpp
@@ -63,17 +63,17 @@
const std::vector<InterfacesList> mandatoryInterfaces = {
{
"Audio Core API",
+ "android.hardware.audio@7.0::IDevicesFactory",
"android.hardware.audio@6.0::IDevicesFactory",
"android.hardware.audio@5.0::IDevicesFactory",
"android.hardware.audio@4.0::IDevicesFactory",
- "android.hardware.audio@2.0::IDevicesFactory"
},
{
"Audio Effect API",
+ "android.hardware.audio.effect@7.0::IEffectsFactory",
"android.hardware.audio.effect@6.0::IEffectsFactory",
"android.hardware.audio.effect@5.0::IEffectsFactory",
"android.hardware.audio.effect@4.0::IEffectsFactory",
- "android.hardware.audio.effect@2.0::IEffectsFactory",
}
};
diff --git a/audio/common/all-versions/default/tests/hidlutils6_tests.cpp b/audio/common/all-versions/default/tests/hidlutils6_tests.cpp
new file mode 100644
index 0000000..3a24e75
--- /dev/null
+++ b/audio/common/all-versions/default/tests/hidlutils6_tests.cpp
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2021 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>
+
+#define LOG_TAG "HidlUtils_Test"
+#include <log/log.h>
+
+#include <HidlUtils.h>
+#include <system/audio.h>
+
+using namespace android;
+using namespace ::android::hardware::audio::common::CPP_VERSION;
+using ::android::hardware::audio::common::CPP_VERSION::implementation::HidlUtils;
+
+// Not generated automatically because DeviceAddress contains
+// an union.
+//
+// operator== must be defined in the same namespace as the data type.
+namespace android::hardware::audio::common::CPP_VERSION {
+
+inline bool operator==(const DeviceAddress& lhs, const DeviceAddress& rhs) {
+ if (lhs.device != rhs.device) return false;
+ audio_devices_t halDeviceType = static_cast<audio_devices_t>(lhs.device);
+ if (audio_is_a2dp_out_device(halDeviceType) || audio_is_a2dp_in_device(halDeviceType)) {
+ return lhs.address.mac == rhs.address.mac;
+ } else if (halDeviceType == AUDIO_DEVICE_OUT_IP || halDeviceType == AUDIO_DEVICE_IN_IP) {
+ return lhs.address.ipv4 == rhs.address.ipv4;
+ } else if (audio_is_usb_out_device(halDeviceType) || audio_is_usb_in_device(halDeviceType)) {
+ return lhs.address.alsa == rhs.address.alsa;
+ } else if (halDeviceType == AUDIO_DEVICE_OUT_REMOTE_SUBMIX ||
+ halDeviceType == AUDIO_DEVICE_IN_REMOTE_SUBMIX) {
+ return lhs.rSubmixAddress == rhs.rSubmixAddress;
+ }
+ // busAddress field can be used for types other than bus, e.g. for microphones.
+ return lhs.busAddress == rhs.busAddress;
+}
+
+} // namespace android::hardware::audio::common::CPP_VERSION
+
+static void ConvertDeviceAddress(const DeviceAddress& device) {
+ audio_devices_t halDeviceType;
+ char halDeviceAddress[AUDIO_DEVICE_MAX_ADDRESS_LEN] = {};
+ EXPECT_EQ(NO_ERROR, HidlUtils::deviceAddressToHal(device, &halDeviceType, halDeviceAddress));
+ DeviceAddress deviceBack;
+ EXPECT_EQ(NO_ERROR,
+ HidlUtils::deviceAddressFromHal(halDeviceType, halDeviceAddress, &deviceBack));
+ EXPECT_EQ(device, deviceBack);
+}
+
+TEST(HidlUtils6, ConvertUniqueDeviceAddress) {
+ DeviceAddress speaker;
+ speaker.device = AudioDevice::OUT_SPEAKER;
+ ConvertDeviceAddress(speaker);
+
+ DeviceAddress micWithAddress;
+ micWithAddress.device = AudioDevice::IN_BUILTIN_MIC;
+ micWithAddress.busAddress = "bottom";
+ ConvertDeviceAddress(micWithAddress);
+}
+
+TEST(HidlUtils6, ConvertA2dpDeviceAddress) {
+ DeviceAddress a2dpSpeaker;
+ a2dpSpeaker.device = AudioDevice::OUT_BLUETOOTH_A2DP_SPEAKER;
+ a2dpSpeaker.address.mac = std::array<uint8_t, 6>{1, 2, 3, 4, 5, 6};
+ ConvertDeviceAddress(a2dpSpeaker);
+}
+
+TEST(HidlUtils6, ConvertIpv4DeviceAddress) {
+ DeviceAddress ipv4;
+ ipv4.device = AudioDevice::OUT_IP;
+ ipv4.address.ipv4 = std::array<uint8_t, 4>{1, 2, 3, 4};
+ ConvertDeviceAddress(ipv4);
+}
+
+TEST(HidlUtils6, ConvertUsbDeviceAddress) {
+ DeviceAddress usbHeadset;
+ usbHeadset.device = AudioDevice::OUT_USB_HEADSET;
+ usbHeadset.address.alsa = {1, 2};
+ ConvertDeviceAddress(usbHeadset);
+}
+
+TEST(HidlUtils6, ConvertBusDeviceAddress) {
+ DeviceAddress bus;
+ bus.device = AudioDevice::OUT_BUS;
+ bus.busAddress = "bus_device";
+ ConvertDeviceAddress(bus);
+}
+
+TEST(HidlUtils6, ConvertRSubmixDeviceAddress) {
+ DeviceAddress rSubmix;
+ rSubmix.device = AudioDevice::OUT_REMOTE_SUBMIX;
+ rSubmix.rSubmixAddress = AUDIO_REMOTE_SUBMIX_DEVICE_ADDRESS;
+ ConvertDeviceAddress(rSubmix);
+}
diff --git a/audio/common/all-versions/default/tests/hidlutils_tests.cpp b/audio/common/all-versions/default/tests/hidlutils_tests.cpp
index 22571c0..ec6bdf3 100644
--- a/audio/common/all-versions/default/tests/hidlutils_tests.cpp
+++ b/audio/common/all-versions/default/tests/hidlutils_tests.cpp
@@ -35,30 +35,27 @@
using namespace ::android::audio::policy::configuration::V7_0;
}
-static constexpr audio_channel_mask_t kInvalidHalChannelMask =
- static_cast<audio_channel_mask_t>(0xFFFFFFFFU);
+static constexpr audio_channel_mask_t kInvalidHalChannelMask = AUDIO_CHANNEL_INVALID;
static constexpr audio_content_type_t kInvalidHalContentType =
static_cast<audio_content_type_t>(0xFFFFFFFFU);
static constexpr audio_devices_t kInvalidHalDevice = static_cast<audio_devices_t>(0xFFFFFFFFU);
-static constexpr audio_format_t kInvalidHalFormat = static_cast<audio_format_t>(0xFFFFFFFFU);
+static constexpr audio_format_t kInvalidHalFormat = AUDIO_FORMAT_INVALID;
static constexpr audio_gain_mode_t kInvalidHalGainMode =
static_cast<audio_gain_mode_t>(0xFFFFFFFFU);
-static constexpr audio_source_t kInvalidHalSource = static_cast<audio_source_t>(0xFFFFFFFFU);
-static constexpr audio_stream_type_t kInvalidHalStreamType =
- static_cast<audio_stream_type_t>(0xFFFFFFFFU);
+// AUDIO_SOURCE_INVALID is framework-only.
+static constexpr audio_source_t kInvalidHalSource = static_cast<audio_source_t>(-1);
+// AUDIO_STREAM_DEFAULT is framework-only
+static constexpr audio_stream_type_t kInvalidHalStreamType = static_cast<audio_stream_type_t>(-2);
static constexpr audio_usage_t kInvalidHalUsage = static_cast<audio_usage_t>(0xFFFFFFFFU);
TEST(HidlUtils, ConvertInvalidChannelMask) {
AudioChannelMask invalid;
- EXPECT_EQ(BAD_VALUE, HidlUtils::audioChannelMaskFromHal(AUDIO_CHANNEL_INVALID,
- false /*isInput*/, &invalid));
- EXPECT_EQ(BAD_VALUE, HidlUtils::audioChannelMaskFromHal(AUDIO_CHANNEL_INVALID, true /*isInput*/,
- &invalid));
EXPECT_EQ(BAD_VALUE, HidlUtils::audioChannelMaskFromHal(kInvalidHalChannelMask,
false /*isInput*/, &invalid));
EXPECT_EQ(BAD_VALUE, HidlUtils::audioChannelMaskFromHal(kInvalidHalChannelMask,
true /*isInput*/, &invalid));
audio_channel_mask_t halInvalid;
+ EXPECT_EQ(BAD_VALUE, HidlUtils::audioChannelMaskToHal("", &halInvalid));
// INVALID channel mask is not in XSD thus it's not allowed for transfer over HIDL.
EXPECT_EQ(BAD_VALUE, HidlUtils::audioChannelMaskToHal("AUDIO_CHANNEL_INVALID", &halInvalid));
EXPECT_EQ(BAD_VALUE, HidlUtils::audioChannelMaskToHal("random string", &halInvalid));
@@ -148,40 +145,241 @@
}
}
+static AudioConfigBase generateValidConfigBase(bool isInput) {
+ AudioConfigBase configBase;
+ configBase.sampleRateHz = 44100;
+ configBase.format = toString(xsd::AudioFormat::AUDIO_FORMAT_PCM_16_BIT);
+ configBase.channelMask = isInput ? toString(xsd::AudioChannelMask::AUDIO_CHANNEL_IN_STEREO)
+ : toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO);
+ return configBase;
+}
+
TEST(HidlUtils, ConvertInvalidConfigBase) {
AudioConfigBase invalid;
- EXPECT_EQ(BAD_VALUE, HidlUtils::audioConfigBaseFromHal({.sample_rate = 0,
- .channel_mask = kInvalidHalChannelMask,
- .format = kInvalidHalFormat},
- false /*isInput*/, &invalid));
- EXPECT_EQ(BAD_VALUE, HidlUtils::audioConfigBaseFromHal({.sample_rate = 0,
- .channel_mask = kInvalidHalChannelMask,
- .format = kInvalidHalFormat},
- true /*isInput*/, &invalid));
+ audio_config_base_t halInvalidChannelMask = AUDIO_CONFIG_BASE_INITIALIZER;
+ halInvalidChannelMask.channel_mask = kInvalidHalChannelMask;
+ EXPECT_EQ(BAD_VALUE, HidlUtils::audioConfigBaseFromHal(halInvalidChannelMask, false /*isInput*/,
+ &invalid));
+ EXPECT_EQ(BAD_VALUE,
+ HidlUtils::audioConfigBaseFromHal(halInvalidChannelMask, true /*isInput*/, &invalid));
+ audio_config_base_t halInvalidFormat = AUDIO_CONFIG_BASE_INITIALIZER;
+ halInvalidFormat.format = kInvalidHalFormat;
+ EXPECT_EQ(BAD_VALUE,
+ HidlUtils::audioConfigBaseFromHal(halInvalidFormat, false /*isInput*/, &invalid));
+ EXPECT_EQ(BAD_VALUE,
+ HidlUtils::audioConfigBaseFromHal(halInvalidFormat, true /*isInput*/, &invalid));
+
audio_config_base_t halInvalid;
- invalid.sampleRateHz = 0;
- invalid.channelMask = "random string";
- invalid.format = "random string";
- EXPECT_EQ(BAD_VALUE, HidlUtils::audioConfigBaseToHal(invalid, &halInvalid));
+ AudioConfigBase invalidChannelMask = generateValidConfigBase(false /*isInput*/);
+ invalidChannelMask.channelMask = "random string";
+ EXPECT_EQ(BAD_VALUE, HidlUtils::audioConfigBaseToHal(invalidChannelMask, &halInvalid));
+ AudioConfigBase invalidFormat = generateValidConfigBase(false /*isInput*/);
+ invalidFormat.format = "random string";
+ EXPECT_EQ(BAD_VALUE, HidlUtils::audioConfigBaseToHal(invalidFormat, &halInvalid));
+}
+
+TEST(HidlUtils, ConvertConfigBaseDefault) {
+ audio_config_base_t halBaseDefault = AUDIO_CONFIG_BASE_INITIALIZER;
+ AudioConfigBase baseDefaultOut, baseDefaultIn;
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseFromHal(halBaseDefault, false /*isInput*/,
+ &baseDefaultOut));
+ EXPECT_EQ(NO_ERROR,
+ HidlUtils::audioConfigBaseFromHal(halBaseDefault, true /*isInput*/, &baseDefaultIn));
+ EXPECT_EQ(baseDefaultOut, baseDefaultIn);
+ audio_config_base_t halBaseDefaultBack;
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseToHal(baseDefaultOut, &halBaseDefaultBack));
+ EXPECT_EQ(halBaseDefault.sample_rate, halBaseDefaultBack.sample_rate);
+ EXPECT_EQ(halBaseDefault.channel_mask, halBaseDefaultBack.channel_mask);
+ EXPECT_EQ(halBaseDefault.format, halBaseDefaultBack.format);
}
TEST(HidlUtils, ConvertConfigBase) {
- AudioConfigBase configBase;
- configBase.sampleRateHz = 44100;
- configBase.channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO);
- configBase.format = toString(xsd::AudioFormat::AUDIO_FORMAT_PCM_16_BIT);
- audio_config_base_t halConfigBase;
- EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseToHal(configBase, &halConfigBase));
- AudioConfigBase configBaseBack;
+ AudioConfigBase configBaseOut = generateValidConfigBase(false /*isInput*/);
+ audio_config_base_t halConfigBaseOut;
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseToHal(configBaseOut, &halConfigBaseOut));
+ AudioConfigBase configBaseOutBack;
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseFromHal(halConfigBaseOut, false /*isInput*/,
+ &configBaseOutBack));
+ EXPECT_EQ(configBaseOut, configBaseOutBack);
+
+ AudioConfigBase configBaseIn = generateValidConfigBase(true /*isInput*/);
+ audio_config_base_t halConfigBaseIn;
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseToHal(configBaseIn, &halConfigBaseIn));
+ AudioConfigBase configBaseInBack;
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseFromHal(halConfigBaseIn, true /*isInput*/,
+ &configBaseInBack));
+ EXPECT_EQ(configBaseIn, configBaseInBack);
+}
+
+TEST(HidlUtils, ConvertInvalidConfigBaseOptional) {
+ AudioConfigBaseOptional invalid;
+ audio_config_base_t halInvalidChannelMask = AUDIO_CONFIG_BASE_INITIALIZER;
+ halInvalidChannelMask.channel_mask = kInvalidHalChannelMask;
+ EXPECT_EQ(BAD_VALUE,
+ HidlUtils::audioConfigBaseOptionalFromHal(
+ halInvalidChannelMask, false /*isInput*/, false /*formatSpecified*/,
+ false /*sampleRateSpecified*/, true /*channelMaskSpecified*/, &invalid));
+ EXPECT_EQ(BAD_VALUE,
+ HidlUtils::audioConfigBaseOptionalFromHal(
+ halInvalidChannelMask, true /*isInput*/, false /*formatSpecified*/,
+ false /*sampleRateSpecified*/, true /*channelMaskSpecified*/, &invalid));
+ // Unspecified invalid values are ignored
+ AudioConfigBaseOptional unspecified;
EXPECT_EQ(NO_ERROR,
- HidlUtils::audioConfigBaseFromHal(halConfigBase, false /*isInput*/, &configBaseBack));
- EXPECT_EQ(configBase, configBaseBack);
+ HidlUtils::audioConfigBaseOptionalFromHal(
+ halInvalidChannelMask, false /*isInput*/, false /*formatSpecified*/,
+ false /*sampleRateSpecified*/, false /*channelMaskSpecified*/, &unspecified));
+ EXPECT_EQ(NO_ERROR,
+ HidlUtils::audioConfigBaseOptionalFromHal(
+ halInvalidChannelMask, true /*isInput*/, false /*formatSpecified*/,
+ false /*sampleRateSpecified*/, false /*channelMaskSpecified*/, &unspecified));
+ audio_config_base_t halInvalidFormat = AUDIO_CONFIG_BASE_INITIALIZER;
+ halInvalidFormat.format = kInvalidHalFormat;
+ EXPECT_EQ(BAD_VALUE,
+ HidlUtils::audioConfigBaseOptionalFromHal(
+ halInvalidFormat, false /*isInput*/, true /*formatSpecified*/,
+ false /*sampleRateSpecified*/, false /*channelMaskSpecified*/, &invalid));
+ EXPECT_EQ(BAD_VALUE,
+ HidlUtils::audioConfigBaseOptionalFromHal(
+ halInvalidFormat, true /*isInput*/, true /*formatSpecified*/,
+ false /*sampleRateSpecified*/, false /*channelMaskSpecified*/, &invalid));
+ EXPECT_EQ(NO_ERROR,
+ HidlUtils::audioConfigBaseOptionalFromHal(
+ halInvalidFormat, false /*isInput*/, false /*formatSpecified*/,
+ false /*sampleRateSpecified*/, false /*channelMaskSpecified*/, &unspecified));
+ EXPECT_EQ(NO_ERROR,
+ HidlUtils::audioConfigBaseOptionalFromHal(
+ halInvalidFormat, true /*isInput*/, false /*formatSpecified*/,
+ false /*sampleRateSpecified*/, false /*channelMaskSpecified*/, &unspecified));
+
+ audio_config_base_t halInvalid;
+ AudioConfigBaseOptional invalidChannelMask;
+ bool formatSpecified, sampleRateSpecified, channelMaskSpecified;
+ invalidChannelMask.channelMask.value("random string");
+ EXPECT_EQ(BAD_VALUE, HidlUtils::audioConfigBaseOptionalToHal(
+ invalidChannelMask, &halInvalid, &formatSpecified,
+ &sampleRateSpecified, &channelMaskSpecified));
+ AudioConfigBaseOptional invalidFormat;
+ invalidFormat.format.value("random string");
+ EXPECT_EQ(BAD_VALUE,
+ HidlUtils::audioConfigBaseOptionalToHal(invalidFormat, &halInvalid, &formatSpecified,
+ &sampleRateSpecified, &channelMaskSpecified));
+}
+
+TEST(HidlUtils, ConvertConfigBaseOptionalDefault) {
+ audio_config_base_t halBaseDefault = AUDIO_CONFIG_BASE_INITIALIZER;
+ AudioConfigBaseOptional baseDefaultUnspecOut, baseDefaultUnspecIn;
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseOptionalFromHal(
+ halBaseDefault, false /*isInput*/, false /*formatSpecified*/,
+ false /*sampleRateSpecified*/, false /*channelMaskSpecified*/,
+ &baseDefaultUnspecOut));
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseOptionalFromHal(
+ halBaseDefault, true /*isInput*/, false /*formatSpecified*/,
+ false /*sampleRateSpecified*/, false /*channelMaskSpecified*/,
+ &baseDefaultUnspecIn));
+ EXPECT_EQ(baseDefaultUnspecOut, baseDefaultUnspecIn);
+ audio_config_base_t halBaseDefaultUnspecBack = AUDIO_CONFIG_BASE_INITIALIZER;
+ bool formatSpecified, sampleRateSpecified, channelMaskSpecified;
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseOptionalToHal(
+ baseDefaultUnspecOut, &halBaseDefaultUnspecBack, &formatSpecified,
+ &sampleRateSpecified, &channelMaskSpecified));
+ EXPECT_FALSE(formatSpecified);
+ EXPECT_FALSE(sampleRateSpecified);
+ EXPECT_FALSE(channelMaskSpecified);
+ EXPECT_EQ(halBaseDefault.sample_rate, halBaseDefaultUnspecBack.sample_rate);
+ EXPECT_EQ(halBaseDefault.channel_mask, halBaseDefaultUnspecBack.channel_mask);
+ EXPECT_EQ(halBaseDefault.format, halBaseDefaultUnspecBack.format);
+
+ AudioConfigBaseOptional baseDefaultSpecOut, baseDefaultSpecIn;
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseOptionalFromHal(
+ halBaseDefault, false /*isInput*/, true /*formatSpecified*/,
+ true /*sampleRateSpecified*/, true /*channelMaskSpecified*/,
+ &baseDefaultSpecOut));
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseOptionalFromHal(
+ halBaseDefault, true /*isInput*/, true /*formatSpecified*/,
+ true /*sampleRateSpecified*/, true /*channelMaskSpecified*/,
+ &baseDefaultSpecIn));
+ EXPECT_EQ(baseDefaultSpecOut, baseDefaultSpecIn);
+ audio_config_base_t halBaseDefaultSpecBack;
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseOptionalToHal(
+ baseDefaultSpecOut, &halBaseDefaultSpecBack, &formatSpecified,
+ &sampleRateSpecified, &channelMaskSpecified));
+ EXPECT_TRUE(formatSpecified);
+ EXPECT_TRUE(sampleRateSpecified);
+ EXPECT_TRUE(channelMaskSpecified);
+ EXPECT_EQ(halBaseDefault.sample_rate, halBaseDefaultSpecBack.sample_rate);
+ EXPECT_EQ(halBaseDefault.channel_mask, halBaseDefaultSpecBack.channel_mask);
+ EXPECT_EQ(halBaseDefault.format, halBaseDefaultSpecBack.format);
+}
+
+TEST(HidlUtils, ConvertConfigBaseOptionalEmpty) {
+ AudioConfigBaseOptional empty;
+ bool formatSpecified, sampleRateSpecified, channelMaskSpecified;
+ audio_config_base_t halEmpty = AUDIO_CONFIG_BASE_INITIALIZER;
+ EXPECT_EQ(NO_ERROR,
+ HidlUtils::audioConfigBaseOptionalToHal(empty, &halEmpty, &formatSpecified,
+ &sampleRateSpecified, &channelMaskSpecified));
+ EXPECT_FALSE(formatSpecified);
+ EXPECT_FALSE(sampleRateSpecified);
+ EXPECT_FALSE(channelMaskSpecified);
+ AudioConfigBaseOptional emptyOutBack, emptyInBack;
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseOptionalFromHal(
+ halEmpty, false /*isInput*/, formatSpecified, sampleRateSpecified,
+ channelMaskSpecified, &emptyOutBack));
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseOptionalFromHal(
+ halEmpty, true /*isInput*/, formatSpecified, sampleRateSpecified,
+ channelMaskSpecified, &emptyInBack));
+ EXPECT_EQ(emptyOutBack, emptyInBack);
+ EXPECT_EQ(empty, emptyOutBack);
+}
+
+TEST(HidlUtils, ConvertConfigBaseOptional) {
+ AudioConfigBase validBaseOut = generateValidConfigBase(false /*isInput*/);
+ AudioConfigBaseOptional configBaseOut;
+ configBaseOut.format.value(validBaseOut.format);
+ configBaseOut.sampleRateHz.value(validBaseOut.sampleRateHz);
+ configBaseOut.channelMask.value(validBaseOut.channelMask);
+ audio_config_base_t halConfigBaseOut;
+ bool formatSpecified, sampleRateSpecified, channelMaskSpecified;
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseOptionalToHal(
+ configBaseOut, &halConfigBaseOut, &formatSpecified,
+ &sampleRateSpecified, &channelMaskSpecified));
+ EXPECT_TRUE(formatSpecified);
+ EXPECT_TRUE(sampleRateSpecified);
+ EXPECT_TRUE(channelMaskSpecified);
+ AudioConfigBaseOptional configBaseOutBack;
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseOptionalFromHal(
+ halConfigBaseOut, false /*isInput*/, formatSpecified,
+ sampleRateSpecified, channelMaskSpecified, &configBaseOutBack));
+ EXPECT_EQ(configBaseOut, configBaseOutBack);
+
+ AudioConfigBase validBaseIn = generateValidConfigBase(true /*isInput*/);
+ AudioConfigBaseOptional configBaseIn;
+ configBaseIn.format.value(validBaseIn.format);
+ configBaseIn.sampleRateHz.value(validBaseIn.sampleRateHz);
+ configBaseIn.channelMask.value(validBaseIn.channelMask);
+ audio_config_base_t halConfigBaseIn;
+ formatSpecified = false;
+ sampleRateSpecified = false;
+ channelMaskSpecified = false;
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseOptionalToHal(
+ configBaseIn, &halConfigBaseIn, &formatSpecified,
+ &sampleRateSpecified, &channelMaskSpecified));
+ EXPECT_TRUE(formatSpecified);
+ EXPECT_TRUE(sampleRateSpecified);
+ EXPECT_TRUE(channelMaskSpecified);
+ AudioConfigBaseOptional configBaseInBack;
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigBaseOptionalFromHal(
+ halConfigBaseIn, true /*isInput*/, formatSpecified,
+ sampleRateSpecified, channelMaskSpecified, &configBaseInBack));
+ EXPECT_EQ(configBaseIn, configBaseInBack);
}
TEST(HidlUtils, ConvertInvalidContentType) {
AudioContentType invalid;
EXPECT_EQ(BAD_VALUE, HidlUtils::audioContentTypeFromHal(kInvalidHalContentType, &invalid));
audio_content_type_t halInvalid;
+ EXPECT_EQ(BAD_VALUE, HidlUtils::audioContentTypeToHal("", &halInvalid));
EXPECT_EQ(BAD_VALUE, HidlUtils::audioContentTypeToHal("random string", &halInvalid));
}
@@ -202,6 +400,7 @@
AudioDevice invalid;
EXPECT_EQ(BAD_VALUE, HidlUtils::audioDeviceTypeFromHal(kInvalidHalDevice, &invalid));
audio_devices_t halInvalid;
+ EXPECT_EQ(BAD_VALUE, HidlUtils::audioDeviceTypeToHal("", &halInvalid));
EXPECT_EQ(BAD_VALUE, HidlUtils::audioDeviceTypeToHal("random string", &halInvalid));
}
@@ -233,6 +432,7 @@
// The enums module is too small to have unit tests on its own.
TEST(HidlUtils, VendorExtension) {
EXPECT_TRUE(xsd::isVendorExtension("VX_GOOGLE_VR_42"));
+ EXPECT_FALSE(xsd::isVendorExtension(""));
EXPECT_FALSE(xsd::isVendorExtension("random string"));
EXPECT_FALSE(xsd::isVendorExtension("VX_"));
EXPECT_FALSE(xsd::isVendorExtension("VX_GOOGLE_$$"));
@@ -276,6 +476,11 @@
DeviceAddress speaker;
speaker.deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_OUT_SPEAKER);
ConvertDeviceAddress(speaker);
+
+ DeviceAddress micWithAddress;
+ micWithAddress.deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_IN_BUILTIN_MIC);
+ micWithAddress.address.id("bottom");
+ ConvertDeviceAddress(micWithAddress);
}
TEST(HidlUtils, ConvertA2dpDeviceAddress) {
@@ -347,6 +552,9 @@
AudioFormat invalid;
EXPECT_EQ(BAD_VALUE, HidlUtils::audioFormatFromHal(kInvalidHalFormat, &invalid));
audio_format_t halInvalid;
+ EXPECT_EQ(BAD_VALUE, HidlUtils::audioFormatToHal("", &halInvalid));
+ // INVALID format is not in XSD thus it's not allowed for transfer over HIDL.
+ EXPECT_EQ(BAD_VALUE, HidlUtils::audioFormatToHal("AUDIO_FORMAT_INVALID", &halInvalid));
EXPECT_EQ(BAD_VALUE, HidlUtils::audioFormatToHal("random string", &halInvalid));
}
@@ -357,8 +565,9 @@
AudioFormat formatBack;
EXPECT_EQ(NO_ERROR, HidlUtils::audioFormatToHal(format, &halFormat))
<< "Conversion of \"" << format << "\" failed";
- EXPECT_TRUE(audio_is_valid_format(halFormat))
- << "Converted format \"" << format << "\" is invalid";
+ EXPECT_EQ(enumVal != xsd::AudioFormat::AUDIO_FORMAT_DEFAULT,
+ audio_is_valid_format(halFormat))
+ << "Validity of \"" << format << "\" is not as expected";
EXPECT_EQ(NO_ERROR, HidlUtils::audioFormatFromHal(halFormat, &formatBack))
<< "Conversion of format " << halFormat << " failed";
EXPECT_EQ(format, formatBack);
@@ -430,6 +639,9 @@
AudioSource invalid;
EXPECT_EQ(BAD_VALUE, HidlUtils::audioSourceFromHal(kInvalidHalSource, &invalid));
audio_source_t halInvalid;
+ EXPECT_EQ(BAD_VALUE, HidlUtils::audioSourceToHal("", &halInvalid));
+ // INVALID source is not in XSD thus it's not allowed for transfer over HIDL.
+ EXPECT_EQ(BAD_VALUE, HidlUtils::audioSourceToHal("AUDIO_SOURCE_INVALID", &halInvalid));
EXPECT_EQ(BAD_VALUE, HidlUtils::audioSourceToHal("random string", &halInvalid));
}
@@ -456,6 +668,15 @@
EXPECT_EQ(BAD_VALUE, HidlUtils::audioStreamTypeToHal("random string", &halInvalid));
}
+TEST(HidlUtils, ConvertDefaultStreamType) {
+ AudioStreamType streamDefault = "";
+ audio_stream_type_t halStreamDefault;
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioStreamTypeToHal(streamDefault, &halStreamDefault));
+ AudioStreamType streamDefaultBack;
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioStreamTypeFromHal(halStreamDefault, &streamDefaultBack));
+ EXPECT_EQ(streamDefault, streamDefaultBack);
+}
+
TEST(HidlUtils, ConvertStreamType) {
for (const auto enumVal : xsdc_enum_range<xsd::AudioStreamType>{}) {
const AudioStreamType streamType = toString(enumVal);
@@ -524,6 +745,7 @@
AudioUsage invalid;
EXPECT_EQ(BAD_VALUE, HidlUtils::audioUsageFromHal(kInvalidHalUsage, &invalid));
audio_usage_t halInvalid;
+ EXPECT_EQ(BAD_VALUE, HidlUtils::audioUsageToHal("", &halInvalid));
EXPECT_EQ(BAD_VALUE, HidlUtils::audioUsageToHal("random string", &halInvalid));
}
@@ -543,7 +765,7 @@
TEST(HidlUtils, ConvertInvalidOffloadInfo) {
AudioOffloadInfo invalid;
audio_offload_info_t halInvalid = AUDIO_INFO_INITIALIZER;
- halInvalid.channel_mask = AUDIO_CHANNEL_INVALID;
+ halInvalid.channel_mask = kInvalidHalChannelMask;
halInvalid.format = kInvalidHalFormat;
EXPECT_EQ(BAD_VALUE, HidlUtils::audioOffloadInfoFromHal(halInvalid, &invalid));
invalid.base.channelMask = "random string";
@@ -553,9 +775,7 @@
TEST(HidlUtils, ConvertOffloadInfo) {
AudioOffloadInfo offloadInfo = {};
- offloadInfo.base.sampleRateHz = 44100;
- offloadInfo.base.channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO);
- offloadInfo.base.format = toString(xsd::AudioFormat::AUDIO_FORMAT_PCM_16_BIT);
+ offloadInfo.base = generateValidConfigBase(false /*isInput*/);
offloadInfo.streamType = toString(xsd::AudioStreamType::AUDIO_STREAM_MUSIC);
offloadInfo.bitRatePerSecond = 320;
offloadInfo.durationMicroseconds = -1;
@@ -574,31 +794,87 @@
TEST(HidlUtils, ConvertInvalidConfig) {
AudioConfig invalid;
- audio_config_t halInvalid = AUDIO_CONFIG_INITIALIZER;
- halInvalid.channel_mask = AUDIO_CHANNEL_INVALID;
- halInvalid.format = kInvalidHalFormat;
- EXPECT_EQ(BAD_VALUE, HidlUtils::audioConfigFromHal(halInvalid, false /*isInput*/, &invalid));
- EXPECT_EQ(BAD_VALUE, HidlUtils::audioConfigFromHal(halInvalid, true /*isInput*/, &invalid));
- invalid.base.channelMask = "random string";
- invalid.base.format = "random string";
- EXPECT_EQ(BAD_VALUE, HidlUtils::audioConfigToHal(invalid, &halInvalid));
+ audio_config_t halInvalidChannelMask = AUDIO_CONFIG_INITIALIZER;
+ halInvalidChannelMask.channel_mask = kInvalidHalChannelMask;
+ EXPECT_EQ(BAD_VALUE,
+ HidlUtils::audioConfigFromHal(halInvalidChannelMask, false /*isInput*/, &invalid));
+ EXPECT_EQ(BAD_VALUE,
+ HidlUtils::audioConfigFromHal(halInvalidChannelMask, true /*isInput*/, &invalid));
+ audio_config_t halInvalidFormat = AUDIO_CONFIG_INITIALIZER;
+ halInvalidFormat.format = kInvalidHalFormat;
+ EXPECT_EQ(BAD_VALUE,
+ HidlUtils::audioConfigFromHal(halInvalidFormat, false /*isInput*/, &invalid));
+ EXPECT_EQ(BAD_VALUE,
+ HidlUtils::audioConfigFromHal(halInvalidFormat, true /*isInput*/, &invalid));
+
+ AudioConfig invalidChannelMask;
+ audio_config_t halInvalid;
+ invalidChannelMask.base.channelMask = "random string";
+ invalidChannelMask.base.format = toString(xsd::AudioFormat::AUDIO_FORMAT_DEFAULT);
+ EXPECT_EQ(BAD_VALUE, HidlUtils::audioConfigToHal(invalidChannelMask, &halInvalid));
+ AudioConfig invalidFormat;
+ invalidFormat.base.format = "random string";
+ invalidFormat.base.channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_NONE);
+ EXPECT_EQ(BAD_VALUE, HidlUtils::audioConfigToHal(invalidFormat, &halInvalid));
+}
+
+TEST(HidlUtils, ConvertConfigDefault) {
+ audio_config_t halDefault = AUDIO_CONFIG_INITIALIZER;
+ AudioConfig defaultOut, defaultIn;
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigFromHal(halDefault, false /*isInput*/, &defaultOut));
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigFromHal(halDefault, true /*isInput*/, &defaultIn));
+ EXPECT_EQ(defaultOut, defaultIn);
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigToHal(defaultOut, &halDefault));
+
+ // Note: empty channel mask and config are not valid values.
+ AudioConfig defaultCfg{};
+ defaultCfg.base.channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_NONE);
+ defaultCfg.base.format = toString(xsd::AudioFormat::AUDIO_FORMAT_DEFAULT);
+ audio_config_t halDefaultCfg;
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigToHal(defaultCfg, &halDefaultCfg));
+ AudioConfig defaultCfgBackOut, defaultCfgBackIn;
+ EXPECT_EQ(NO_ERROR,
+ HidlUtils::audioConfigFromHal(halDefaultCfg, false /*isInput*/, &defaultCfgBackOut));
+ EXPECT_EQ(NO_ERROR,
+ HidlUtils::audioConfigFromHal(halDefaultCfg, true /*isInput*/, &defaultCfgBackIn));
+ EXPECT_EQ(defaultCfgBackOut, defaultCfgBackIn);
+ EXPECT_EQ(defaultCfg, defaultCfgBackOut);
}
TEST(HidlUtils, ConvertConfig) {
+ AudioConfig configOut{};
+ configOut.base = generateValidConfigBase(false /*isInput*/);
+ audio_config_t halConfigOut;
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigToHal(configOut, &halConfigOut));
+ AudioConfig configOutBack;
+ EXPECT_EQ(NO_ERROR,
+ HidlUtils::audioConfigFromHal(halConfigOut, false /*isInput*/, &configOutBack));
+ EXPECT_EQ(configOut, configOutBack);
+
+ AudioConfig configIn{};
+ configIn.base = generateValidConfigBase(true /*isInput*/);
+ audio_config_t halConfigIn;
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigToHal(configIn, &halConfigIn));
+ AudioConfig configInBack;
+ EXPECT_EQ(NO_ERROR,
+ HidlUtils::audioConfigFromHal(halConfigIn, true /*isInput*/, &configInBack));
+ EXPECT_EQ(configIn, configInBack);
+}
+
+TEST(HidlUtils, ConvertConfigWithOffloadInfo) {
AudioConfig config = {};
- config.base.sampleRateHz = 44100;
- config.base.channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO);
- config.base.format = toString(xsd::AudioFormat::AUDIO_FORMAT_PCM_16_BIT);
- config.offloadInfo.base = config.base;
- config.offloadInfo.streamType = toString(xsd::AudioStreamType::AUDIO_STREAM_MUSIC);
- config.offloadInfo.bitRatePerSecond = 320;
- config.offloadInfo.durationMicroseconds = -1;
- config.offloadInfo.bitWidth = 16;
- config.offloadInfo.bufferSize = 1024;
- config.offloadInfo.usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA);
- config.offloadInfo.encapsulationMode = AudioEncapsulationMode::ELEMENTARY_STREAM;
- config.offloadInfo.contentId = 42;
- config.offloadInfo.syncId = 13;
+ config.base = generateValidConfigBase(false /*isInput*/);
+ config.offloadInfo.info(
+ AudioOffloadInfo{.base = config.base,
+ .streamType = toString(xsd::AudioStreamType::AUDIO_STREAM_MUSIC),
+ .bitRatePerSecond = 320,
+ .durationMicroseconds = -1,
+ .bitWidth = 16,
+ .bufferSize = 1024,
+ .usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
+ .encapsulationMode = AudioEncapsulationMode::ELEMENTARY_STREAM,
+ .contentId = 42,
+ .syncId = 13});
audio_config_t halConfig;
EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigToHal(config, &halConfig));
AudioConfig configBack;
@@ -642,18 +918,18 @@
halInvalid.type = AUDIO_PORT_TYPE_MIX;
halInvalid.role = AUDIO_PORT_ROLE_NONE; // note: this is valid.
halInvalid.config_mask = AUDIO_PORT_CONFIG_CHANNEL_MASK;
- halInvalid.channel_mask = AUDIO_CHANNEL_INVALID;
+ halInvalid.channel_mask = kInvalidHalChannelMask;
EXPECT_EQ(BAD_VALUE, HidlUtils::audioPortConfigFromHal(halInvalid, &invalid));
- invalid.base.channelMask = "random string";
+ invalid.base.channelMask.value("random string");
EXPECT_EQ(BAD_VALUE, HidlUtils::audioPortConfigToHal(invalid, &halInvalid));
}
TEST(HidlUtils, ConvertAudioPortConfig) {
AudioPortConfig config = {};
config.id = 42;
- config.base.sampleRateHz = 44100;
- config.base.channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO);
- config.base.format = toString(xsd::AudioFormat::AUDIO_FORMAT_PCM_16_BIT);
+ config.base.sampleRateHz.value(44100);
+ config.base.channelMask.value(toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO));
+ config.base.format.value(toString(xsd::AudioFormat::AUDIO_FORMAT_PCM_16_BIT));
config.gain.config({});
config.gain.config().channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO);
config.ext.device({});
@@ -707,3 +983,74 @@
EXPECT_EQ(NO_ERROR, HidlUtils::audioPortToHal(portBack, &halPortBack));
EXPECT_TRUE(audio_ports_v7_are_equal(&halPort, &halPortBack));
}
+
+TEST(HidlUtils, ConvertInvalidAudioTags) {
+ char halTag[AUDIO_ATTRIBUTES_TAGS_MAX_SIZE] = {};
+
+ hidl_vec<AudioTag> emptyTag = {{""}};
+ EXPECT_EQ(BAD_VALUE, HidlUtils::audioTagsToHal(emptyTag, halTag));
+
+ hidl_vec<AudioTag> longTag = {{std::string(AUDIO_ATTRIBUTES_TAGS_MAX_SIZE + 1, 'A')}};
+ EXPECT_EQ(BAD_VALUE, HidlUtils::audioTagsToHal(longTag, halTag));
+
+ hidl_vec<AudioTag> tagSeparator = {
+ {std::string(AUDIO_ATTRIBUTES_TAGS_MAX_SIZE - 1, HidlUtils::sAudioTagSeparator)}};
+ EXPECT_EQ(BAD_VALUE, HidlUtils::audioTagsToHal(tagSeparator, halTag));
+
+ hidl_vec<AudioTag> notExtensions = {{"", "random string", "VX_", "VX_GOOGLE_$$"}};
+ EXPECT_EQ(BAD_VALUE, HidlUtils::audioTagsToHal(notExtensions, halTag));
+}
+
+TEST(HidlUtils, ConvertAudioTags) {
+ hidl_vec<AudioTag> emptyTags;
+ char halEmptyTags[AUDIO_ATTRIBUTES_TAGS_MAX_SIZE] = {};
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioTagsToHal(emptyTags, halEmptyTags));
+ hidl_vec<AudioTag> emptyTagsBack;
+ EXPECT_EQ(NO_ERROR,
+ HidlUtils::audioTagsFromHal(HidlUtils::splitAudioTags(halEmptyTags), &emptyTagsBack));
+ EXPECT_EQ(emptyTags, emptyTagsBack);
+
+ hidl_vec<AudioTag> oneTag = {{"VX_GOOGLE_VR"}};
+ char halOneTag[AUDIO_ATTRIBUTES_TAGS_MAX_SIZE] = {};
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioTagsToHal(oneTag, halOneTag));
+ hidl_vec<AudioTag> oneTagBack;
+ EXPECT_EQ(NO_ERROR,
+ HidlUtils::audioTagsFromHal(HidlUtils::splitAudioTags(halOneTag), &oneTagBack));
+ EXPECT_EQ(oneTag, oneTagBack);
+
+ hidl_vec<AudioTag> twoTags = {{"VX_GOOGLE_VR_42", "VX_GOOGLE_1E100"}};
+ char halTwoTags[AUDIO_ATTRIBUTES_TAGS_MAX_SIZE] = {};
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioTagsToHal(twoTags, halTwoTags));
+ hidl_vec<AudioTag> twoTagsBack;
+ EXPECT_EQ(NO_ERROR,
+ HidlUtils::audioTagsFromHal(HidlUtils::splitAudioTags(halTwoTags), &twoTagsBack));
+ EXPECT_EQ(twoTags, twoTagsBack);
+}
+
+template <typename T>
+class FilterTest : public ::testing::Test {};
+using FilterTestTypeParams = ::testing::Types<hidl_vec<AudioTag>, std::vector<std::string>>;
+TYPED_TEST_SUITE(FilterTest, FilterTestTypeParams);
+
+TYPED_TEST(FilterTest, FilterOutNonVendorTags) {
+ TypeParam emptyTags;
+ EXPECT_EQ(emptyTags, HidlUtils::filterOutNonVendorTags(emptyTags));
+
+ TypeParam allVendorTags = {{"VX_GOOGLE_VR_42", "VX_GOOGLE_1E100"}};
+ EXPECT_EQ(allVendorTags, HidlUtils::filterOutNonVendorTags(allVendorTags));
+
+ TypeParam oneVendorTag = {{"", "VX_GOOGLE_VR", "random_string"}};
+ TypeParam oneVendorTagOnly = HidlUtils::filterOutNonVendorTags(oneVendorTag);
+ EXPECT_EQ(1, oneVendorTagOnly.size());
+ EXPECT_EQ(oneVendorTag[1], oneVendorTagOnly[0]);
+
+ // The vendor extension isn't valid, however it must not be filtered out
+ // so the converter can detect the issue.
+ TypeParam oneMaybeVendorTag = {{"", "random string", "VX_GOOGLE_$$"}};
+ TypeParam oneMaybeVendorTagOnly = HidlUtils::filterOutNonVendorTags(oneMaybeVendorTag);
+ EXPECT_EQ(1, oneMaybeVendorTagOnly.size());
+ EXPECT_EQ(oneMaybeVendorTag[2], oneMaybeVendorTagOnly[0]);
+
+ TypeParam noVendorTags = {{"", "random string", "V_"}};
+ EXPECT_EQ(emptyTags, HidlUtils::filterOutNonVendorTags(noVendorTags));
+}
diff --git a/audio/common/all-versions/util/include/common/all-versions/HidlSupport.h b/audio/common/all-versions/util/include/common/all-versions/HidlSupport.h
index b514a43..d7802cf 100644
--- a/audio/common/all-versions/util/include/common/all-versions/HidlSupport.h
+++ b/audio/common/all-versions/util/include/common/all-versions/HidlSupport.h
@@ -20,6 +20,9 @@
#include <hidl/HidlSupport.h>
#include <algorithm>
+#include <sstream>
+#include <string>
+#include <vector>
namespace android::hardware::audio::common::utils {
@@ -29,6 +32,16 @@
return std::find(values.begin(), values.end(), e) != values.end();
}
+static inline std::vector<std::string> splitString(const std::string& s, char separator) {
+ std::istringstream iss(s);
+ std::string t;
+ std::vector<std::string> result;
+ while (std::getline(iss, t, separator)) {
+ result.push_back(std::move(t));
+ }
+ return result;
+}
+
} // namespace android::hardware::audio::common::utils
#endif // android_hardware_audio_common_HidlSupport_H_
diff --git a/audio/core/all-versions/default/Android.bp b/audio/core/all-versions/default/Android.bp
index e0f0860..dcff425 100644
--- a/audio/core/all-versions/default/Android.bp
+++ b/audio/core/all-versions/default/Android.bp
@@ -1,7 +1,6 @@
filegroup {
name: "android.hardware.audio-impl_srcs",
srcs: [
- "Conversions.cpp",
"Device.cpp",
"DevicesFactory.cpp",
"ParametersUtil.cpp",
@@ -63,6 +62,7 @@
defaults: ["android.hardware.audio-impl_default"],
shared_libs: [
"android.hardware.audio@2.0",
+ "android.hardware.audio@2.0-util",
"android.hardware.audio.common@2.0",
"android.hardware.audio.common@2.0-util",
],
@@ -79,6 +79,7 @@
shared_libs: [
"android.hardware.audio@4.0",
+ "android.hardware.audio@4.0-util",
"android.hardware.audio.common@4.0",
"android.hardware.audio.common@4.0-util",
],
@@ -94,6 +95,7 @@
defaults: ["android.hardware.audio-impl_default"],
shared_libs: [
"android.hardware.audio@5.0",
+ "android.hardware.audio@5.0-util",
"android.hardware.audio.common@5.0",
"android.hardware.audio.common@5.0-util",
],
@@ -109,6 +111,7 @@
defaults: ["android.hardware.audio-impl_default"],
shared_libs: [
"android.hardware.audio@6.0",
+ "android.hardware.audio@6.0-util",
"android.hardware.audio.common@6.0",
"android.hardware.audio.common@6.0-util",
],
@@ -129,6 +132,7 @@
defaults: ["android.hardware.audio-impl_default"],
shared_libs: [
"android.hardware.audio@7.0",
+ "android.hardware.audio@7.0-util",
"android.hardware.audio.common@7.0",
"android.hardware.audio.common@7.0-enums",
"android.hardware.audio.common@7.0-util",
diff --git a/audio/core/all-versions/default/Conversions.cpp b/audio/core/all-versions/default/Conversions.cpp
deleted file mode 100644
index 8e0a140..0000000
--- a/audio/core/all-versions/default/Conversions.cpp
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2018 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 "core/default/Conversions.h"
-
-#include <stdio.h>
-
-#if MAJOR_VERSION >= 7
-#include <android_audio_policy_configuration_V7_0-enums.h>
-#endif
-#include <HidlUtils.h>
-#include <log/log.h>
-
-namespace android {
-namespace hardware {
-namespace audio {
-namespace CPP_VERSION {
-namespace implementation {
-
-using ::android::hardware::audio::common::CPP_VERSION::implementation::HidlUtils;
-
-#if MAJOR_VERSION <= 6
-std::string deviceAddressToHal(const DeviceAddress& address) {
- audio_devices_t halDevice;
- char halAddress[AUDIO_DEVICE_MAX_ADDRESS_LEN];
- (void)deviceAddressToHal(address, &halDevice, halAddress);
- return halAddress;
-}
-#endif
-
-status_t deviceAddressToHal(const DeviceAddress& device, audio_devices_t* halDeviceType,
- char* halDeviceAddress) {
-#if MAJOR_VERSION >= 5
- return HidlUtils::deviceAddressToHal(device, halDeviceType, halDeviceAddress);
-#else
- return HidlUtils::deviceAddressToHalImpl(device, halDeviceType, halDeviceAddress);
-#endif
-}
-
-status_t deviceAddressFromHal(audio_devices_t halDeviceType, const char* halDeviceAddress,
- DeviceAddress* device) {
-#if MAJOR_VERSION >= 5
- return HidlUtils::deviceAddressFromHal(halDeviceType, halDeviceAddress, device);
-#else
- return HidlUtils::deviceAddressFromHalImpl(halDeviceType, halDeviceAddress, device);
-#endif
-}
-
-#if MAJOR_VERSION >= 4
-bool halToMicrophoneCharacteristics(MicrophoneInfo* pDst,
- const struct audio_microphone_characteristic_t& src) {
- bool status = false;
- if (pDst != NULL) {
- pDst->deviceId = src.device_id;
-
- if (deviceAddressFromHal(src.device, src.address, &pDst->deviceAddress) != OK) {
- return false;
- }
- pDst->channelMapping.resize(AUDIO_CHANNEL_COUNT_MAX);
- for (size_t ch = 0; ch < pDst->channelMapping.size(); ch++) {
- pDst->channelMapping[ch] = AudioMicrophoneChannelMapping(src.channel_mapping[ch]);
- }
- pDst->location = AudioMicrophoneLocation(src.location);
- pDst->group = (AudioMicrophoneGroup)src.group;
- pDst->indexInTheGroup = (uint32_t)src.index_in_the_group;
- pDst->sensitivity = src.sensitivity;
- pDst->maxSpl = src.max_spl;
- pDst->minSpl = src.min_spl;
- pDst->directionality = AudioMicrophoneDirectionality(src.directionality);
- pDst->frequencyResponse.resize(src.num_frequency_responses);
- for (size_t k = 0; k < src.num_frequency_responses; k++) {
- pDst->frequencyResponse[k].frequency = src.frequency_responses[0][k];
- pDst->frequencyResponse[k].level = src.frequency_responses[1][k];
- }
- pDst->position.x = src.geometric_location.x;
- pDst->position.y = src.geometric_location.y;
- pDst->position.z = src.geometric_location.z;
-
- pDst->orientation.x = src.orientation.x;
- pDst->orientation.y = src.orientation.y;
- pDst->orientation.z = src.orientation.z;
-
- status = true;
- }
- return status;
-}
-#endif // MAJOR_VERSION >= 4
-
-#if MAJOR_VERSION >= 7
-namespace xsd {
-using namespace ::android::audio::policy::configuration::V7_0;
-}
-
-bool audioInputFlagsToHal(const hidl_vec<AudioInOutFlag>& flags, audio_input_flags_t* halFlags) {
- bool success = true;
- *halFlags = {};
- for (const auto& flag : flags) {
- audio_input_flags_t halFlag;
- if (!xsd::isUnknownAudioInOutFlag(flag) &&
- audio_input_flag_from_string(flag.c_str(), &halFlag)) {
- *halFlags = static_cast<audio_input_flags_t>(*halFlags | halFlag);
- } else {
- ALOGE("Unknown audio input flag \"%s\"", flag.c_str());
- success = false;
- }
- }
- return success;
-}
-
-bool audioOutputFlagsToHal(const hidl_vec<AudioInOutFlag>& flags, audio_output_flags_t* halFlags) {
- bool success = true;
- *halFlags = {};
- for (const auto& flag : flags) {
- audio_output_flags_t halFlag;
- if (!xsd::isUnknownAudioInOutFlag(flag) &&
- audio_output_flag_from_string(flag.c_str(), &halFlag)) {
- *halFlags = static_cast<audio_output_flags_t>(*halFlags | halFlag);
- } else {
- ALOGE("Unknown audio output flag \"%s\"", flag.c_str());
- success = false;
- }
- }
- return success;
-}
-#endif
-
-} // namespace implementation
-} // namespace CPP_VERSION
-} // namespace audio
-} // namespace hardware
-} // namespace android
diff --git a/audio/core/all-versions/default/Device.cpp b/audio/core/all-versions/default/Device.cpp
index bb69f0b..70a1a4d 100644
--- a/audio/core/all-versions/default/Device.cpp
+++ b/audio/core/all-versions/default/Device.cpp
@@ -17,9 +17,7 @@
#define LOG_TAG "DeviceHAL"
#include "core/default/Device.h"
-#include <HidlUtils.h>
#include "common/all-versions/default/EffectMap.h"
-#include "core/default/Conversions.h"
#include "core/default/StreamIn.h"
#include "core/default/StreamOut.h"
#include "core/default/Util.h"
@@ -33,6 +31,8 @@
#include <android/log.h>
+#include <HidlUtils.h>
+
namespace android {
namespace hardware {
namespace audio {
@@ -135,13 +135,14 @@
Return<void> Device::getInputBufferSize(const AudioConfig& config, getInputBufferSize_cb _hidl_cb) {
audio_config_t halConfig;
- HidlUtils::audioConfigToHal(config, &halConfig);
- size_t halBufferSize = mDevice->get_input_buffer_size(mDevice, &halConfig);
Result retval(Result::INVALID_ARGUMENTS);
uint64_t bufferSize = 0;
- if (halBufferSize != 0) {
- retval = Result::OK;
- bufferSize = halBufferSize;
+ if (HidlUtils::audioConfigToHal(config, &halConfig) == NO_ERROR) {
+ size_t halBufferSize = mDevice->get_input_buffer_size(mDevice, &halConfig);
+ if (halBufferSize != 0) {
+ retval = Result::OK;
+ bufferSize = halBufferSize;
+ }
}
_hidl_cb(retval, bufferSize);
return Void();
@@ -153,15 +154,17 @@
const AudioOutputFlags& flags,
AudioConfig* suggestedConfig) {
audio_config_t halConfig;
- HidlUtils::audioConfigToHal(config, &halConfig);
+ if (HidlUtils::audioConfigToHal(config, &halConfig) != NO_ERROR) {
+ return {Result::INVALID_ARGUMENTS, nullptr};
+ }
audio_stream_out_t* halStream;
audio_devices_t halDevice;
char halDeviceAddress[AUDIO_DEVICE_MAX_ADDRESS_LEN];
- if (deviceAddressToHal(device, &halDevice, halDeviceAddress) != NO_ERROR) {
+ if (CoreUtils::deviceAddressToHal(device, &halDevice, halDeviceAddress) != NO_ERROR) {
return {Result::INVALID_ARGUMENTS, nullptr};
}
audio_output_flags_t halFlags;
- if (!audioOutputFlagsToHal(flags, &halFlags)) {
+ if (CoreUtils::audioOutputFlagsToHal(flags, &halFlags) != NO_ERROR) {
return {Result::INVALID_ARGUMENTS, nullptr};
}
ALOGV("open_output_stream handle: %d devices: %x flags: %#x "
@@ -186,16 +189,18 @@
int32_t ioHandle, const DeviceAddress& device, const AudioConfig& config,
const AudioInputFlags& flags, AudioSource source, AudioConfig* suggestedConfig) {
audio_config_t halConfig;
- HidlUtils::audioConfigToHal(config, &halConfig);
+ if (HidlUtils::audioConfigToHal(config, &halConfig) != NO_ERROR) {
+ return {Result::INVALID_ARGUMENTS, nullptr};
+ }
audio_stream_in_t* halStream;
audio_devices_t halDevice;
char halDeviceAddress[AUDIO_DEVICE_MAX_ADDRESS_LEN];
- if (deviceAddressToHal(device, &halDevice, halDeviceAddress) != NO_ERROR) {
+ if (CoreUtils::deviceAddressToHal(device, &halDevice, halDeviceAddress) != NO_ERROR) {
return {Result::INVALID_ARGUMENTS, nullptr};
}
audio_input_flags_t halFlags;
audio_source_t halSource;
- if (!audioInputFlagsToHal(flags, &halFlags) ||
+ if (CoreUtils::audioInputFlagsToHal(flags, &halFlags) != NO_ERROR ||
HidlUtils::audioSourceToHal(source, &halSource) != NO_ERROR) {
return {Result::INVALID_ARGUMENTS, nullptr};
}
@@ -248,6 +253,17 @@
#endif
const SourceMetadata& sourceMetadata,
openOutputStream_cb _hidl_cb) {
+#if MAJOR_VERSION <= 6
+ if (status_t status = CoreUtils::sourceMetadataToHal(sourceMetadata, nullptr);
+ status != NO_ERROR) {
+#else
+ if (status_t status = CoreUtils::sourceMetadataToHalV7(sourceMetadata,
+ false /*ignoreNonVendorTags*/, nullptr);
+ status != NO_ERROR) {
+#endif
+ _hidl_cb(analyzeStatus("sourceMetadataToHal", status), nullptr, AudioConfig{});
+ return Void();
+ }
AudioConfig suggestedConfig;
auto [result, streamOut] =
openOutputStreamImpl(ioHandle, device, config, flags, &suggestedConfig);
@@ -271,7 +287,17 @@
// This should never happen, the framework must not create as stream
// if there is no client
ALOGE("openInputStream called without tracks connected");
- _hidl_cb(Result::INVALID_ARGUMENTS, nullptr, AudioConfig());
+ _hidl_cb(Result::INVALID_ARGUMENTS, nullptr, AudioConfig{});
+ return Void();
+ }
+#if MAJOR_VERSION <= 6
+ if (status_t status = CoreUtils::sinkMetadataToHal(sinkMetadata, nullptr); status != NO_ERROR) {
+#else
+ if (status_t status = CoreUtils::sinkMetadataToHalV7(sinkMetadata,
+ false /*ignoreNonVendorTags*/, nullptr);
+ status != NO_ERROR) {
+#endif
+ _hidl_cb(analyzeStatus("sinkMetadataToHal", status), nullptr, AudioConfig{});
return Void();
}
// Pick the first one as the main.
@@ -304,11 +330,17 @@
const hidl_vec<AudioPortConfig>& sinks) {
Result retval(Result::NOT_SUPPORTED);
if (version() >= AUDIO_DEVICE_API_VERSION_3_0) {
- std::unique_ptr<audio_port_config[]> halSources;
- HidlUtils::audioPortConfigsToHal(sources, &halSources);
- std::unique_ptr<audio_port_config[]> halSinks;
- HidlUtils::audioPortConfigsToHal(sinks, &halSinks);
audio_patch_handle_t halPatch = static_cast<audio_patch_handle_t>(patch);
+ std::unique_ptr<audio_port_config[]> halSources;
+ if (status_t status = HidlUtils::audioPortConfigsToHal(sources, &halSources);
+ status != NO_ERROR) {
+ return {analyzeStatus("audioPortConfigsToHal;sources", status), patch};
+ }
+ std::unique_ptr<audio_port_config[]> halSinks;
+ if (status_t status = HidlUtils::audioPortConfigsToHal(sinks, &halSinks);
+ status != NO_ERROR) {
+ return {analyzeStatus("audioPortConfigsToHal;sinks", status), patch};
+ }
retval = analyzeStatus("create_audio_patch",
mDevice->create_audio_patch(mDevice, sources.size(), &halSources[0],
sinks.size(), &halSinks[0], &halPatch));
@@ -343,7 +375,10 @@
Return<Result> Device::setAudioPortConfig(const AudioPortConfig& config) {
if (version() >= AUDIO_DEVICE_API_VERSION_3_0) {
struct audio_port_config halPortConfig;
- HidlUtils::audioPortConfigToHal(config, &halPortConfig);
+ if (status_t status = HidlUtils::audioPortConfigToHal(config, &halPortConfig);
+ status != NO_ERROR) {
+ return analyzeStatus("audioPortConfigToHal", status);
+ }
return analyzeStatus("set_audio_port_config",
mDevice->set_audio_port_config(mDevice, &halPortConfig));
}
@@ -414,7 +449,7 @@
mDevice->get_microphones(mDevice, &mic_array[0], &actual_mics) == 0) {
microphones.resize(actual_mics);
for (size_t i = 0; i < actual_mics; ++i) {
- halToMicrophoneCharacteristics(µphones[i], mic_array[i]);
+ (void)CoreUtils::microphoneInfoFromHal(mic_array[i], µphones[i]);
}
retval = Result::OK;
}
diff --git a/audio/core/all-versions/default/ParametersUtil.cpp b/audio/core/all-versions/default/ParametersUtil.cpp
index 694eb73..4d53645 100644
--- a/audio/core/all-versions/default/ParametersUtil.cpp
+++ b/audio/core/all-versions/default/ParametersUtil.cpp
@@ -15,11 +15,12 @@
*/
#include "core/default/ParametersUtil.h"
-#include "core/default/Conversions.h"
#include "core/default/Util.h"
#include <system/audio.h>
+#include <util/CoreUtils.h>
+
namespace android {
namespace hardware {
namespace audio {
@@ -153,7 +154,7 @@
Result ParametersUtil::setParam(const char* name, const DeviceAddress& address) {
audio_devices_t halDeviceType;
char halDeviceAddress[AUDIO_DEVICE_MAX_ADDRESS_LEN];
- if (deviceAddressToHal(address, &halDeviceType, halDeviceAddress) != NO_ERROR) {
+ if (CoreUtils::deviceAddressToHal(address, &halDeviceType, halDeviceAddress) != NO_ERROR) {
return Result::INVALID_ARGUMENTS;
}
AudioParameter params{String8(halDeviceAddress)};
diff --git a/audio/core/all-versions/default/Stream.cpp b/audio/core/all-versions/default/Stream.cpp
index c74079d..7e32573 100644
--- a/audio/core/all-versions/default/Stream.cpp
+++ b/audio/core/all-versions/default/Stream.cpp
@@ -17,8 +17,8 @@
#define LOG_TAG "StreamHAL"
#include "core/default/Stream.h"
+#include "common/all-versions/HidlSupport.h"
#include "common/all-versions/default/EffectMap.h"
-#include "core/default/Conversions.h"
#include "core/default/Util.h"
#include <inttypes.h>
@@ -29,6 +29,7 @@
#include <hardware/audio_effect.h>
#include <media/AudioContainers.h>
#include <media/TypeConverter.h>
+#include <util/CoreUtils.h>
namespace android {
namespace hardware {
@@ -37,6 +38,7 @@
namespace implementation {
using ::android::hardware::audio::common::CPP_VERSION::implementation::HidlUtils;
+using ::android::hardware::audio::common::utils::splitString;
Stream::Stream(bool isInput, audio_stream_t* stream) : mIsInput(isInput), mStream(stream) {
(void)mIsInput; // prevent 'unused field' warnings in pre-V7 versions.
@@ -220,7 +222,7 @@
// Ensure that the separator is one character, despite that it's defined as a C string.
static_assert(sizeof(AUDIO_PARAMETER_VALUE_LIST_SEPARATOR) == 2);
std::vector<std::string> halFormats =
- util::splitString(halListValue.string(), AUDIO_PARAMETER_VALUE_LIST_SEPARATOR[0]);
+ splitString(halListValue.string(), AUDIO_PARAMETER_VALUE_LIST_SEPARATOR[0]);
hidl_vec<AudioFormat> formats;
(void)HidlUtils::audioFormatsFromHal(halFormats, &formats);
std::vector<AudioProfile> tempProfiles;
@@ -235,7 +237,7 @@
result = getParam(AudioParameter::keyStreamSupportedSamplingRates, &halListValue, context);
if (result != Result::OK) break;
std::vector<std::string> halSampleRates =
- util::splitString(halListValue.string(), AUDIO_PARAMETER_VALUE_LIST_SEPARATOR[0]);
+ splitString(halListValue.string(), AUDIO_PARAMETER_VALUE_LIST_SEPARATOR[0]);
hidl_vec<uint32_t> sampleRates;
sampleRates.resize(halSampleRates.size());
for (size_t i = 0; i < sampleRates.size(); ++i) {
@@ -245,7 +247,7 @@
result = getParam(AudioParameter::keyStreamSupportedChannels, &halListValue, context);
if (result != Result::OK) break;
std::vector<std::string> halChannelMasks =
- util::splitString(halListValue.string(), AUDIO_PARAMETER_VALUE_LIST_SEPARATOR[0]);
+ splitString(halListValue.string(), AUDIO_PARAMETER_VALUE_LIST_SEPARATOR[0]);
hidl_vec<AudioChannelMask> channelMasks;
(void)HidlUtils::audioChannelMasksFromHal(halChannelMasks, &channelMasks);
// Create a profile.
@@ -276,23 +278,36 @@
return Void();
}
-Return<Result> Stream::setAudioProperties(const AudioConfigBase& config) {
- audio_config_base_t halConfigBase = {};
- status_t status = HidlUtils::audioConfigBaseToHal(config, &halConfigBase);
+Return<Result> Stream::setAudioProperties(const AudioConfigBaseOptional& config) {
+ audio_config_base_t halConfigBase = AUDIO_CONFIG_BASE_INITIALIZER;
+ bool formatSpecified, sRateSpecified, channelMaskSpecified;
+ status_t status = HidlUtils::audioConfigBaseOptionalToHal(
+ config, &halConfigBase, &formatSpecified, &sRateSpecified, &channelMaskSpecified);
if (status != NO_ERROR) {
return Stream::analyzeStatus("set_audio_properties", status);
}
- if (Result result = setParam(AudioParameter::keySamplingRate,
- static_cast<int>(halConfigBase.sample_rate));
- result != Result::OK) {
- return result;
+ if (sRateSpecified) {
+ if (Result result = setParam(AudioParameter::keySamplingRate,
+ static_cast<int>(halConfigBase.sample_rate));
+ result != Result::OK) {
+ return result;
+ }
}
- if (Result result =
- setParam(AudioParameter::keyChannels, static_cast<int>(halConfigBase.channel_mask));
- result != Result::OK) {
- return result;
+ if (channelMaskSpecified) {
+ if (Result result = setParam(AudioParameter::keyChannels,
+ static_cast<int>(halConfigBase.channel_mask));
+ result != Result::OK) {
+ return result;
+ }
}
- return setParam(AudioParameter::keyFormat, static_cast<int>(halConfigBase.format));
+ if (formatSpecified) {
+ if (Result result =
+ setParam(AudioParameter::keyFormat, static_cast<int>(halConfigBase.format));
+ result != Result::OK) {
+ return result;
+ }
+ }
+ return Result::OK;
}
#endif // MAJOR_VERSION <= 6
@@ -358,9 +373,10 @@
hidl_vec<DeviceAddress> devices;
if (retval == Result::OK) {
devices.resize(1);
- retval = Stream::analyzeStatus("get_devices",
- deviceAddressFromHal(static_cast<audio_devices_t>(halDevice),
- nullptr, &devices[0]));
+ retval = Stream::analyzeStatus(
+ "get_devices",
+ CoreUtils::deviceAddressFromHal(static_cast<audio_devices_t>(halDevice), nullptr,
+ &devices[0]));
}
_hidl_cb(retval, devices);
return Void();
diff --git a/audio/core/all-versions/default/StreamIn.cpp b/audio/core/all-versions/default/StreamIn.cpp
index ead7204..599f3c3 100644
--- a/audio/core/all-versions/default/StreamIn.cpp
+++ b/audio/core/all-versions/default/StreamIn.cpp
@@ -17,7 +17,6 @@
#define LOG_TAG "StreamInHAL"
#include "core/default/StreamIn.h"
-#include "core/default/Conversions.h"
#include "core/default/Util.h"
#include "common/all-versions/HidlSupport.h"
@@ -27,6 +26,7 @@
#include <HidlUtils.h>
#include <android/log.h>
#include <hardware/audio.h>
+#include <util/CoreUtils.h>
#include <utils/Trace.h>
#include <cmath>
#include <memory>
@@ -233,7 +233,7 @@
return mStreamCommon->getSupportedProfiles(_hidl_cb);
}
-Return<Result> StreamIn::setAudioProperties(const AudioConfigBase& config) {
+Return<Result> StreamIn::setAudioProperties(const AudioConfigBaseOptional& config) {
return mStreamCommon->setAudioProperties(config);
}
@@ -478,31 +478,74 @@
}
#if MAJOR_VERSION >= 4
-Return<void> StreamIn::updateSinkMetadata(const SinkMetadata& sinkMetadata) {
- if (mStream->update_sink_metadata == nullptr) {
- return Void(); // not supported by the HAL
- }
+Result StreamIn::doUpdateSinkMetadata(const SinkMetadata& sinkMetadata) {
std::vector<record_track_metadata> halTracks;
- halTracks.reserve(sinkMetadata.tracks.size());
- for (auto& metadata : sinkMetadata.tracks) {
- record_track_metadata halTrackMetadata = {.gain = metadata.gain};
- (void)HidlUtils::audioSourceToHal(metadata.source, &halTrackMetadata.source);
-#if MAJOR_VERSION >= 5
- if (metadata.destination.getDiscriminator() ==
- RecordTrackMetadata::Destination::hidl_discriminator::device) {
- (void)deviceAddressToHal(metadata.destination.device(), &halTrackMetadata.dest_device,
- halTrackMetadata.dest_device_address);
+#if MAJOR_VERSION <= 6
+ (void)CoreUtils::sinkMetadataToHal(sinkMetadata, &halTracks);
+#else
+ // Validate whether a conversion to V7 is possible. This is needed
+ // to have a consistent behavior of the HAL regardless of the API
+ // version of the legacy HAL (and also to be consistent with openInputStream).
+ std::vector<record_track_metadata_v7> halTracksV7;
+ if (status_t status = CoreUtils::sinkMetadataToHalV7(
+ sinkMetadata, false /*ignoreNonVendorTags*/, &halTracksV7);
+ status == NO_ERROR) {
+ halTracks.reserve(halTracksV7.size());
+ for (auto metadata_v7 : halTracksV7) {
+ halTracks.push_back(std::move(metadata_v7.base));
}
-#endif
- halTracks.push_back(halTrackMetadata);
+ } else {
+ return Stream::analyzeStatus("sinkMetadataToHal", status);
}
+#endif // MAJOR_VERSION <= 6
const sink_metadata_t halMetadata = {
.track_count = halTracks.size(),
.tracks = halTracks.data(),
};
mStream->update_sink_metadata(mStream, &halMetadata);
+ return Result::OK;
+}
+
+#if MAJOR_VERSION >= 7
+Result StreamIn::doUpdateSinkMetadataV7(const SinkMetadata& sinkMetadata) {
+ std::vector<record_track_metadata_v7> halTracks;
+ if (status_t status = CoreUtils::sinkMetadataToHalV7(sinkMetadata,
+ false /*ignoreNonVendorTags*/, &halTracks);
+ status != NO_ERROR) {
+ return Stream::analyzeStatus("sinkMetadataToHal", status);
+ }
+ const sink_metadata_v7_t halMetadata = {
+ .track_count = halTracks.size(),
+ .tracks = halTracks.data(),
+ };
+ mStream->update_sink_metadata_v7(mStream, &halMetadata);
+ return Result::OK;
+}
+#endif // MAJOR_VERSION >= 7
+
+#if MAJOR_VERSION <= 6
+Return<void> StreamIn::updateSinkMetadata(const SinkMetadata& sinkMetadata) {
+ if (mStream->update_sink_metadata == nullptr) {
+ return Void(); // not supported by the HAL
+ }
+ (void)doUpdateSinkMetadata(sinkMetadata);
return Void();
}
+#elif MAJOR_VERSION >= 7
+Return<Result> StreamIn::updateSinkMetadata(const SinkMetadata& sinkMetadata) {
+ if (mDevice->version() < AUDIO_DEVICE_API_VERSION_3_2) {
+ if (mStream->update_sink_metadata == nullptr) {
+ return Result::NOT_SUPPORTED;
+ }
+ return doUpdateSinkMetadata(sinkMetadata);
+ } else {
+ if (mStream->update_sink_metadata_v7 == nullptr) {
+ return Result::NOT_SUPPORTED;
+ }
+ return doUpdateSinkMetadataV7(sinkMetadata);
+ }
+}
+#endif
Return<void> StreamIn::getActiveMicrophones(getActiveMicrophones_cb _hidl_cb) {
Result retval = Result::NOT_SUPPORTED;
@@ -514,7 +557,7 @@
mStream->get_active_microphones(mStream, &mic_array[0], &actual_mics) == 0) {
microphones.resize(actual_mics);
for (size_t i = 0; i < actual_mics; ++i) {
- halToMicrophoneCharacteristics(µphones[i], mic_array[i]);
+ (void)CoreUtils::microphoneInfoFromHal(mic_array[i], µphones[i]);
}
retval = Result::OK;
}
diff --git a/audio/core/all-versions/default/StreamOut.cpp b/audio/core/all-versions/default/StreamOut.cpp
index 5633cbb..a089f6b 100644
--- a/audio/core/all-versions/default/StreamOut.cpp
+++ b/audio/core/all-versions/default/StreamOut.cpp
@@ -29,6 +29,7 @@
#include <HidlUtils.h>
#include <android/log.h>
#include <hardware/audio.h>
+#include <util/CoreUtils.h>
#include <utils/Trace.h>
namespace android {
@@ -238,7 +239,7 @@
return mStreamCommon->getSupportedProfiles(_hidl_cb);
}
-Return<Result> StreamOut::setAudioProperties(const AudioConfigBase& config) {
+Return<Result> StreamOut::setAudioProperties(const AudioConfigBaseOptional& config) {
return mStreamCommon->setAudioProperties(config);
}
@@ -585,26 +586,75 @@
}
#if MAJOR_VERSION >= 4
-Return<void> StreamOut::updateSourceMetadata(const SourceMetadata& sourceMetadata) {
- if (mStream->update_source_metadata == nullptr) {
- return Void(); // not supported by the HAL
- }
+Result StreamOut::doUpdateSourceMetadata(const SourceMetadata& sourceMetadata) {
std::vector<playback_track_metadata_t> halTracks;
- halTracks.reserve(sourceMetadata.tracks.size());
- for (auto& metadata : sourceMetadata.tracks) {
- playback_track_metadata_t halTrackMetadata = {.gain = metadata.gain};
- (void)HidlUtils::audioUsageToHal(metadata.usage, &halTrackMetadata.usage);
- (void)HidlUtils::audioContentTypeToHal(metadata.contentType,
- &halTrackMetadata.content_type);
- halTracks.push_back(std::move(halTrackMetadata));
+#if MAJOR_VERSION <= 6
+ (void)CoreUtils::sourceMetadataToHal(sourceMetadata, &halTracks);
+#else
+ // Validate whether a conversion to V7 is possible. This is needed
+ // to have a consistent behavior of the HAL regardless of the API
+ // version of the legacy HAL (and also to be consistent with openOutputStream).
+ std::vector<playback_track_metadata_v7> halTracksV7;
+ if (status_t status = CoreUtils::sourceMetadataToHalV7(
+ sourceMetadata, false /*ignoreNonVendorTags*/, &halTracksV7);
+ status == NO_ERROR) {
+ halTracks.reserve(halTracksV7.size());
+ for (auto metadata_v7 : halTracksV7) {
+ halTracks.push_back(std::move(metadata_v7.base));
+ }
+ } else {
+ return Stream::analyzeStatus("sourceMetadataToHal", status);
}
+#endif // MAJOR_VERSION <= 6
const source_metadata_t halMetadata = {
.track_count = halTracks.size(),
.tracks = halTracks.data(),
};
mStream->update_source_metadata(mStream, &halMetadata);
+ return Result::OK;
+}
+
+#if MAJOR_VERSION >= 7
+Result StreamOut::doUpdateSourceMetadataV7(const SourceMetadata& sourceMetadata) {
+ std::vector<playback_track_metadata_v7> halTracks;
+ if (status_t status = CoreUtils::sourceMetadataToHalV7(
+ sourceMetadata, false /*ignoreNonVendorTags*/, &halTracks);
+ status != NO_ERROR) {
+ return Stream::analyzeStatus("sourceMetadataToHal", status);
+ }
+ const source_metadata_v7_t halMetadata = {
+ .track_count = halTracks.size(),
+ .tracks = halTracks.data(),
+ };
+ mStream->update_source_metadata_v7(mStream, &halMetadata);
+ return Result::OK;
+}
+#endif // MAJOR_VERSION >= 7
+
+#if MAJOR_VERSION <= 6
+Return<void> StreamOut::updateSourceMetadata(const SourceMetadata& sourceMetadata) {
+ if (mStream->update_source_metadata == nullptr) {
+ return Void(); // not supported by the HAL
+ }
+ (void)doUpdateSourceMetadata(sourceMetadata);
return Void();
}
+#elif MAJOR_VERSION >= 7
+Return<Result> StreamOut::updateSourceMetadata(const SourceMetadata& sourceMetadata) {
+ if (mDevice->version() < AUDIO_DEVICE_API_VERSION_3_2) {
+ if (mStream->update_source_metadata == nullptr) {
+ return Result::NOT_SUPPORTED;
+ }
+ return doUpdateSourceMetadata(sourceMetadata);
+ } else {
+ if (mStream->update_source_metadata_v7 == nullptr) {
+ return Result::NOT_SUPPORTED;
+ }
+ return doUpdateSourceMetadataV7(sourceMetadata);
+ }
+}
+#endif
+
Return<Result> StreamOut::selectPresentation(int32_t /*presentationId*/, int32_t /*programId*/) {
return Result::NOT_SUPPORTED; // TODO: propagate to legacy
}
@@ -612,32 +662,65 @@
#if MAJOR_VERSION >= 6
Return<void> StreamOut::getDualMonoMode(getDualMonoMode_cb _hidl_cb) {
- _hidl_cb(Result::NOT_SUPPORTED, DualMonoMode::OFF);
+ audio_dual_mono_mode_t mode = AUDIO_DUAL_MONO_MODE_OFF;
+ Result retval = mStream->get_dual_mono_mode != nullptr
+ ? Stream::analyzeStatus("get_dual_mono_mode",
+ mStream->get_dual_mono_mode(mStream, &mode))
+ : Result::NOT_SUPPORTED;
+ _hidl_cb(retval, DualMonoMode(mode));
return Void();
}
-Return<Result> StreamOut::setDualMonoMode(DualMonoMode /*mode*/) {
- return Result::NOT_SUPPORTED;
+Return<Result> StreamOut::setDualMonoMode(DualMonoMode mode) {
+ return mStream->set_dual_mono_mode != nullptr
+ ? Stream::analyzeStatus(
+ "set_dual_mono_mode",
+ mStream->set_dual_mono_mode(mStream,
+ static_cast<audio_dual_mono_mode_t>(mode)))
+ : Result::NOT_SUPPORTED;
}
Return<void> StreamOut::getAudioDescriptionMixLevel(getAudioDescriptionMixLevel_cb _hidl_cb) {
- _hidl_cb(Result::NOT_SUPPORTED, -std::numeric_limits<float>::infinity());
+ float leveldB = -std::numeric_limits<float>::infinity();
+ Result retval = mStream->get_audio_description_mix_level != nullptr
+ ? Stream::analyzeStatus(
+ "get_audio_description_mix_level",
+ mStream->get_audio_description_mix_level(mStream, &leveldB))
+ : Result::NOT_SUPPORTED;
+ _hidl_cb(retval, leveldB);
return Void();
}
-Return<Result> StreamOut::setAudioDescriptionMixLevel(float /*leveldB*/) {
- return Result::NOT_SUPPORTED;
+Return<Result> StreamOut::setAudioDescriptionMixLevel(float leveldB) {
+ return mStream->set_audio_description_mix_level != nullptr
+ ? Stream::analyzeStatus(
+ "set_audio_description_mix_level",
+ mStream->set_audio_description_mix_level(mStream, leveldB))
+ : Result::NOT_SUPPORTED;
}
Return<void> StreamOut::getPlaybackRateParameters(getPlaybackRateParameters_cb _hidl_cb) {
- _hidl_cb(Result::NOT_SUPPORTED,
- // Same as AUDIO_PLAYBACK_RATE_INITIALIZER
- PlaybackRate{1.0f, 1.0f, TimestretchMode::DEFAULT, TimestretchFallbackMode::FAIL});
+ audio_playback_rate_t rate = AUDIO_PLAYBACK_RATE_INITIALIZER;
+ Result retval =
+ mStream->get_playback_rate_parameters != nullptr
+ ? Stream::analyzeStatus("get_playback_rate_parameters",
+ mStream->get_playback_rate_parameters(mStream, &rate))
+ : Result::NOT_SUPPORTED;
+ _hidl_cb(retval,
+ PlaybackRate{rate.mSpeed, rate.mPitch, static_cast<TimestretchMode>(rate.mStretchMode),
+ static_cast<TimestretchFallbackMode>(rate.mFallbackMode)});
return Void();
}
-Return<Result> StreamOut::setPlaybackRateParameters(const PlaybackRate& /*playbackRate*/) {
- return Result::NOT_SUPPORTED;
+Return<Result> StreamOut::setPlaybackRateParameters(const PlaybackRate& playbackRate) {
+ audio_playback_rate_t rate = {
+ playbackRate.speed, playbackRate.pitch,
+ static_cast<audio_timestretch_stretch_mode_t>(playbackRate.timestretchMode),
+ static_cast<audio_timestretch_fallback_mode_t>(playbackRate.fallbackMode)};
+ return mStream->set_playback_rate_parameters != nullptr
+ ? Stream::analyzeStatus("set_playback_rate_parameters",
+ mStream->set_playback_rate_parameters(mStream, &rate))
+ : Result::NOT_SUPPORTED;
}
Return<Result> StreamOut::setEventCallback(const sp<IStreamOutEventCallback>& callback) {
diff --git a/audio/core/all-versions/default/TEST_MAPPING b/audio/core/all-versions/default/TEST_MAPPING
new file mode 100644
index 0000000..d53c97a
--- /dev/null
+++ b/audio/core/all-versions/default/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "android.hardware.audio@7.0-util_tests"
+ }
+ ]
+}
diff --git a/audio/core/all-versions/default/include/core/default/Conversions.h b/audio/core/all-versions/default/include/core/default/Conversions.h
deleted file mode 100644
index 2372771..0000000
--- a/audio/core/all-versions/default/include/core/default/Conversions.h
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_HARDWARE_AUDIO_CONVERSIONS_H_
-#define ANDROID_HARDWARE_AUDIO_CONVERSIONS_H_
-
-#include PATH(android/hardware/audio/FILE_VERSION/types.h)
-
-#include <string>
-
-#include <system/audio.h>
-
-#include <VersionUtils.h>
-
-namespace android {
-namespace hardware {
-namespace audio {
-namespace CPP_VERSION {
-namespace implementation {
-
-using ::android::hardware::hidl_vec;
-using namespace ::android::hardware::audio::common::CPP_VERSION;
-using namespace ::android::hardware::audio::CPP_VERSION;
-
-#if MAJOR_VERSION <= 6
-// Temporary version for compatibility with forks of the default implementation.
-// Will be removed, do not use!
-std::string deviceAddressToHal(const DeviceAddress& address);
-#endif
-
-status_t deviceAddressToHal(const DeviceAddress& device, audio_devices_t* halDeviceType,
- char* halDeviceAddress);
-status_t deviceAddressFromHal(audio_devices_t halDeviceType, const char* halDeviceAddress,
- DeviceAddress* device);
-
-#if MAJOR_VERSION >= 4
-bool halToMicrophoneCharacteristics(MicrophoneInfo* pDst,
- const struct audio_microphone_characteristic_t& src);
-#endif
-
-#if MAJOR_VERSION <= 6
-using AudioInputFlags =
- ::android::hardware::audio::common::CPP_VERSION::implementation::AudioInputFlagBitfield;
-using AudioOutputFlags =
- ::android::hardware::audio::common::CPP_VERSION::implementation::AudioOutputFlagBitfield;
-
-inline bool audioInputFlagsToHal(AudioInputFlags flags, audio_input_flags_t* halFlags) {
- *halFlags = static_cast<audio_input_flags_t>(flags);
- return true;
-}
-
-inline bool audioOutputFlagsToHal(AudioOutputFlags flags, audio_output_flags_t* halFlags) {
- *halFlags = static_cast<audio_output_flags_t>(flags);
- return true;
-}
-#else
-bool audioInputFlagsToHal(const hidl_vec<AudioInOutFlag>& flags, audio_input_flags_t* halFlags);
-bool audioOutputFlagsToHal(const hidl_vec<AudioInOutFlag>& flags, audio_output_flags_t* halFlags);
-#endif
-
-} // namespace implementation
-} // namespace CPP_VERSION
-} // namespace audio
-} // namespace hardware
-} // namespace android
-
-#endif // ANDROID_HARDWARE_AUDIO_CONVERSIONS_H_
diff --git a/audio/core/all-versions/default/include/core/default/Device.h b/audio/core/all-versions/default/include/core/default/Device.h
index 461c253..5851fc9 100644
--- a/audio/core/all-versions/default/include/core/default/Device.h
+++ b/audio/core/all-versions/default/include/core/default/Device.h
@@ -31,6 +31,7 @@
#include <hidl/MQDescriptor.h>
#include <VersionUtils.h>
+#include <util/CoreUtils.h>
namespace android {
namespace hardware {
@@ -43,17 +44,10 @@
using ::android::hardware::hidl_vec;
using ::android::hardware::Return;
using ::android::hardware::Void;
-#if MAJOR_VERSION <= 6
-using AudioInputFlags =
- ::android::hardware::audio::common::CPP_VERSION::implementation::AudioInputFlagBitfield;
-using AudioOutputFlags =
- ::android::hardware::audio::common::CPP_VERSION::implementation::AudioOutputFlagBitfield;
-#else
-using AudioInputFlags = hidl_vec<::android::hardware::audio::CPP_VERSION::AudioInOutFlag>;
-using AudioOutputFlags = hidl_vec<::android::hardware::audio::CPP_VERSION::AudioInOutFlag>;
-#endif
using namespace ::android::hardware::audio::common::CPP_VERSION;
using namespace ::android::hardware::audio::CPP_VERSION;
+using AudioInputFlags = CoreUtils::AudioInputFlags;
+using AudioOutputFlags = CoreUtils::AudioOutputFlags;
struct Device : public IDevice, public ParametersUtil {
explicit Device(audio_hw_device_t* device);
@@ -146,6 +140,8 @@
void closeOutputStream(audio_stream_out_t* stream);
audio_hw_device_t* device() const { return mDevice; }
+ uint32_t version() const { return mDevice->common.version; }
+
private:
bool mIsClosed;
audio_hw_device_t* mDevice;
@@ -161,8 +157,6 @@
// Methods from ParametersUtil.
char* halGetParameters(const char* keys) override;
int halSetParameters(const char* keysAndValues) override;
-
- uint32_t version() const { return mDevice->common.version; }
};
} // namespace implementation
diff --git a/audio/core/all-versions/default/include/core/default/Stream.h b/audio/core/all-versions/default/include/core/default/Stream.h
index 0865992..66d60e3 100644
--- a/audio/core/all-versions/default/include/core/default/Stream.h
+++ b/audio/core/all-versions/default/include/core/default/Stream.h
@@ -77,7 +77,7 @@
Return<Result> setFormat(AudioFormat format) override;
#else
Return<void> getSupportedProfiles(getSupportedProfiles_cb _hidl_cb) override;
- Return<Result> setAudioProperties(const AudioConfigBase& config) override;
+ Return<Result> setAudioProperties(const AudioConfigBaseOptional& config) override;
#endif // MAJOR_VERSION <= 6
Return<void> getAudioProperties(getAudioProperties_cb _hidl_cb) override;
Return<Result> addEffect(uint64_t effectId) override;
diff --git a/audio/core/all-versions/default/include/core/default/StreamIn.h b/audio/core/all-versions/default/include/core/default/StreamIn.h
index b861c6c..a980f3f 100644
--- a/audio/core/all-versions/default/include/core/default/StreamIn.h
+++ b/audio/core/all-versions/default/include/core/default/StreamIn.h
@@ -72,7 +72,7 @@
Return<Result> setFormat(AudioFormat format) override;
#else
Return<void> getSupportedProfiles(getSupportedProfiles_cb _hidl_cb) override;
- Return<Result> setAudioProperties(const AudioConfigBase& config) override;
+ Return<Result> setAudioProperties(const AudioConfigBaseOptional& config) override;
#endif // MAJOR_VERSION <= 6
Return<void> getAudioProperties(getAudioProperties_cb _hidl_cb) override;
Return<Result> addEffect(uint64_t effectId) override;
@@ -114,9 +114,13 @@
Return<void> createMmapBuffer(int32_t minSizeFrames, createMmapBuffer_cb _hidl_cb) override;
Return<void> getMmapPosition(getMmapPosition_cb _hidl_cb) override;
#if MAJOR_VERSION >= 4
+#if MAJOR_VERSION <= 6
Return<void> updateSinkMetadata(const SinkMetadata& sinkMetadata) override;
- Return<void> getActiveMicrophones(getActiveMicrophones_cb _hidl_cb) override;
+#else
+ Return<Result> updateSinkMetadata(const SinkMetadata& sinkMetadata) override;
#endif
+ Return<void> getActiveMicrophones(getActiveMicrophones_cb _hidl_cb) override;
+#endif // MAJOR_VERSION >= 4
#if MAJOR_VERSION >= 5
Return<Result> setMicrophoneDirection(MicrophoneDirection direction) override;
Return<Result> setMicrophoneFieldDimension(float zoom) override;
@@ -124,7 +128,14 @@
static Result getCapturePositionImpl(audio_stream_in_t* stream, uint64_t* frames,
uint64_t* time);
- private:
+ private:
+#if MAJOR_VERSION >= 4
+ Result doUpdateSinkMetadata(const SinkMetadata& sinkMetadata);
+#if MAJOR_VERSION >= 7
+ Result doUpdateSinkMetadataV7(const SinkMetadata& sinkMetadata);
+#endif
+#endif // MAJOR_VERSION >= 4
+
const sp<Device> mDevice;
audio_stream_in_t* mStream;
const sp<Stream> mStreamCommon;
diff --git a/audio/core/all-versions/default/include/core/default/StreamOut.h b/audio/core/all-versions/default/include/core/default/StreamOut.h
index 9f64e3e..ccc1c1a 100644
--- a/audio/core/all-versions/default/include/core/default/StreamOut.h
+++ b/audio/core/all-versions/default/include/core/default/StreamOut.h
@@ -72,7 +72,7 @@
Return<Result> setFormat(AudioFormat format) override;
#else
Return<void> getSupportedProfiles(getSupportedProfiles_cb _hidl_cb) override;
- Return<Result> setAudioProperties(const AudioConfigBase& config) override;
+ Return<Result> setAudioProperties(const AudioConfigBaseOptional& config) override;
#endif // MAJOR_VERSION <= 6
Return<void> getAudioProperties(getAudioProperties_cb _hidl_cb) override;
Return<Result> addEffect(uint64_t effectId) override;
@@ -123,9 +123,13 @@
Return<void> createMmapBuffer(int32_t minSizeFrames, createMmapBuffer_cb _hidl_cb) override;
Return<void> getMmapPosition(getMmapPosition_cb _hidl_cb) override;
#if MAJOR_VERSION >= 4
- Return<void> updateSourceMetadata(const SourceMetadata& sourceMetadata) override;
Return<Result> selectPresentation(int32_t presentationId, int32_t programId) override;
+#if MAJOR_VERSION <= 6
+ Return<void> updateSourceMetadata(const SourceMetadata& sourceMetadata) override;
+#else
+ Return<Result> updateSourceMetadata(const SourceMetadata& sourceMetadata) override;
#endif
+#endif // MAJOR_VERSION >= 4
#if MAJOR_VERSION >= 6
Return<void> getDualMonoMode(getDualMonoMode_cb _hidl_cb) override;
Return<Result> setDualMonoMode(DualMonoMode mode) override;
@@ -143,6 +147,13 @@
#endif
private:
+#if MAJOR_VERSION >= 4
+ Result doUpdateSourceMetadata(const SourceMetadata& sourceMetadata);
+#if MAJOR_VERSION >= 7
+ Result doUpdateSourceMetadataV7(const SourceMetadata& sourceMetadata);
+#endif
+#endif // MAJOR_VERSION >= 4
+
const sp<Device> mDevice;
audio_stream_out_t* mStream;
const sp<Stream> mStreamCommon;
diff --git a/audio/core/all-versions/default/include/core/default/Util.h b/audio/core/all-versions/default/include/core/default/Util.h
index 3d629cd..78ae03e 100644
--- a/audio/core/all-versions/default/include/core/default/Util.h
+++ b/audio/core/all-versions/default/include/core/default/Util.h
@@ -20,8 +20,6 @@
#include PATH(android/hardware/audio/FILE_VERSION/types.h)
#include <algorithm>
-#include <sstream>
-#include <string>
#include <vector>
#include <system/audio.h>
@@ -72,16 +70,6 @@
return analyzeStatus(status);
}
-static inline std::vector<std::string> splitString(const std::string& s, char separator) {
- std::istringstream iss(s);
- std::string t;
- std::vector<std::string> result;
- while (std::getline(iss, t, separator)) {
- result.push_back(std::move(t));
- }
- return result;
-}
-
} // namespace util
} // namespace implementation
} // namespace CPP_VERSION
diff --git a/audio/core/all-versions/default/util/Android.bp b/audio/core/all-versions/default/util/Android.bp
new file mode 100644
index 0000000..447184b
--- /dev/null
+++ b/audio/core/all-versions/default/util/Android.bp
@@ -0,0 +1,139 @@
+cc_defaults {
+ name: "android.hardware.audio-util_default",
+ defaults: ["hidl_defaults"],
+
+ vendor_available: true,
+
+ export_include_dirs: ["include"],
+
+ srcs: [
+ "CoreUtils.cpp",
+ ],
+
+ shared_libs: [
+ "liblog",
+ "libutils",
+ "libhidlbase",
+ "android.hardware.audio.common-util",
+ ],
+ export_shared_lib_headers: [
+ "android.hardware.audio.common-util",
+ ],
+
+ header_libs: [
+ "libaudio_system_headers",
+ "libhardware_headers",
+ ],
+}
+
+cc_library_shared {
+ name: "android.hardware.audio@2.0-util",
+ defaults: ["android.hardware.audio-util_default"],
+ shared_libs: [
+ "android.hardware.audio.common@2.0",
+ "android.hardware.audio.common@2.0-util",
+ "android.hardware.audio@2.0",
+ ],
+ cflags: [
+ "-DMAJOR_VERSION=2",
+ "-DMINOR_VERSION=0",
+ "-include common/all-versions/VersionMacro.h",
+ ],
+}
+
+cc_library_shared {
+ name: "android.hardware.audio@4.0-util",
+ defaults: ["android.hardware.audio-util_default"],
+ shared_libs: [
+ "android.hardware.audio.common@4.0",
+ "android.hardware.audio.common@4.0-util",
+ "android.hardware.audio@4.0",
+ ],
+ cflags: [
+ "-DMAJOR_VERSION=4",
+ "-DMINOR_VERSION=0",
+ "-include common/all-versions/VersionMacro.h",
+ ],
+}
+
+cc_library_shared {
+ name: "android.hardware.audio@5.0-util",
+ defaults: ["android.hardware.audio-util_default"],
+ shared_libs: [
+ "android.hardware.audio.common@5.0",
+ "android.hardware.audio.common@5.0-util",
+ "android.hardware.audio@5.0",
+ ],
+ cflags: [
+ "-DMAJOR_VERSION=5",
+ "-DMINOR_VERSION=0",
+ "-include common/all-versions/VersionMacro.h",
+ ],
+}
+
+cc_library_shared {
+ name: "android.hardware.audio@6.0-util",
+ defaults: ["android.hardware.audio-util_default"],
+ shared_libs: [
+ "android.hardware.audio.common@6.0",
+ "android.hardware.audio.common@6.0-util",
+ "android.hardware.audio@6.0",
+ ],
+ cflags: [
+ "-DMAJOR_VERSION=6",
+ "-DMINOR_VERSION=0",
+ "-include common/all-versions/VersionMacro.h",
+ ],
+}
+
+cc_library {
+ name: "android.hardware.audio@7.0-util",
+ defaults: ["android.hardware.audio-util_default"],
+ shared_libs: [
+ "android.hardware.audio.common@7.0",
+ "android.hardware.audio.common@7.0-enums",
+ "android.hardware.audio.common@7.0-util",
+ "android.hardware.audio@7.0",
+ "libbase",
+ "libxml2",
+ ],
+ cflags: [
+ "-DMAJOR_VERSION=7",
+ "-DMINOR_VERSION=0",
+ "-include common/all-versions/VersionMacro.h",
+ ],
+}
+
+// Note: this isn't a VTS test, but rather a unit test
+// to verify correctness of conversion utilities.
+cc_test {
+ name: "android.hardware.audio@7.0-util_tests",
+ defaults: ["android.hardware.audio-util_default"],
+
+ srcs: ["tests/coreutils_tests.cpp"],
+
+ // Use static linking to allow running in presubmit on
+ // targets that don't have HAL V7.
+ static_libs: [
+ "android.hardware.audio.common@7.0",
+ "android.hardware.audio.common@7.0-enums",
+ "android.hardware.audio.common@7.0-util",
+ "android.hardware.audio@7.0",
+ "android.hardware.audio@7.0-util",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "libxml2",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ "-DMAJOR_VERSION=7",
+ "-DMINOR_VERSION=0",
+ "-include common/all-versions/VersionMacro.h",
+ ],
+
+ test_suites: ["device-tests"],
+}
diff --git a/audio/core/all-versions/default/util/CoreUtils.cpp b/audio/core/all-versions/default/util/CoreUtils.cpp
new file mode 100644
index 0000000..14f76f3
--- /dev/null
+++ b/audio/core/all-versions/default/util/CoreUtils.cpp
@@ -0,0 +1,476 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#if MAJOR_VERSION >= 7
+#include <android_audio_policy_configuration_V7_0-enums.h>
+#endif
+#include <HidlUtils.h>
+#include <log/log.h>
+
+#include "util/CoreUtils.h"
+
+using ::android::hardware::audio::common::CPP_VERSION::implementation::HidlUtils;
+#if MAJOR_VERSION >= 7
+namespace xsd {
+using namespace ::android::audio::policy::configuration::V7_0;
+}
+#endif
+
+namespace android {
+namespace hardware {
+namespace audio {
+namespace CPP_VERSION {
+namespace implementation {
+
+#define CONVERT_CHECKED(expr, result) \
+ if (status_t status = (expr); status != NO_ERROR) { \
+ result = status; \
+ }
+
+status_t CoreUtils::deviceAddressToHal(const DeviceAddress& device, audio_devices_t* halDeviceType,
+ char* halDeviceAddress) {
+#if MAJOR_VERSION >= 5
+ return HidlUtils::deviceAddressToHal(device, halDeviceType, halDeviceAddress);
+#else
+ return HidlUtils::deviceAddressToHalImpl(device, halDeviceType, halDeviceAddress);
+#endif
+}
+
+status_t CoreUtils::deviceAddressFromHal(audio_devices_t halDeviceType,
+ const char* halDeviceAddress, DeviceAddress* device) {
+#if MAJOR_VERSION >= 5
+ return HidlUtils::deviceAddressFromHal(halDeviceType, halDeviceAddress, device);
+#else
+ return HidlUtils::deviceAddressFromHalImpl(halDeviceType, halDeviceAddress, device);
+#endif
+}
+
+#if MAJOR_VERSION >= 4
+status_t CoreUtils::microphoneInfoFromHal(
+ const struct audio_microphone_characteristic_t& halMicInfo, MicrophoneInfo* micInfo) {
+ status_t result = NO_ERROR;
+ micInfo->deviceId = halMicInfo.device_id;
+ CONVERT_CHECKED(
+ deviceAddressFromHal(halMicInfo.device, halMicInfo.address, &micInfo->deviceAddress),
+ result);
+ size_t chCount;
+ for (chCount = 0; chCount < AUDIO_CHANNEL_COUNT_MAX; ++chCount) {
+ if (halMicInfo.channel_mapping[chCount] == AUDIO_MICROPHONE_CHANNEL_MAPPING_UNUSED) {
+ break;
+ }
+ }
+ micInfo->channelMapping.resize(chCount);
+ for (size_t ch = 0; ch < micInfo->channelMapping.size(); ch++) {
+ micInfo->channelMapping[ch] = AudioMicrophoneChannelMapping(halMicInfo.channel_mapping[ch]);
+ }
+ micInfo->location = AudioMicrophoneLocation(halMicInfo.location);
+ micInfo->group = AudioMicrophoneGroup(halMicInfo.group);
+ micInfo->indexInTheGroup = static_cast<uint32_t>(halMicInfo.index_in_the_group);
+ micInfo->sensitivity = halMicInfo.sensitivity;
+ micInfo->maxSpl = halMicInfo.max_spl;
+ micInfo->minSpl = halMicInfo.min_spl;
+ micInfo->directionality = AudioMicrophoneDirectionality(halMicInfo.directionality);
+ micInfo->frequencyResponse.resize(halMicInfo.num_frequency_responses);
+ for (size_t k = 0; k < halMicInfo.num_frequency_responses; k++) {
+ micInfo->frequencyResponse[k].frequency = halMicInfo.frequency_responses[0][k];
+ micInfo->frequencyResponse[k].level = halMicInfo.frequency_responses[1][k];
+ }
+ micInfo->position.x = halMicInfo.geometric_location.x;
+ micInfo->position.y = halMicInfo.geometric_location.y;
+ micInfo->position.z = halMicInfo.geometric_location.z;
+ micInfo->orientation.x = halMicInfo.orientation.x;
+ micInfo->orientation.y = halMicInfo.orientation.y;
+ micInfo->orientation.z = halMicInfo.orientation.z;
+ return result;
+}
+
+status_t CoreUtils::microphoneInfoToHal(const MicrophoneInfo& micInfo,
+ audio_microphone_characteristic_t* halMicInfo) {
+ status_t result = NO_ERROR;
+ strncpy(halMicInfo->device_id, micInfo.deviceId.c_str(), AUDIO_MICROPHONE_ID_MAX_LEN);
+ halMicInfo->device_id[AUDIO_MICROPHONE_ID_MAX_LEN - 1] = '\0';
+ if (micInfo.deviceId.size() >= AUDIO_MICROPHONE_ID_MAX_LEN) {
+ ALOGE("HIDL MicrophoneInfo device ID is too long: %zu", micInfo.deviceId.size());
+ result = BAD_VALUE;
+ }
+ CONVERT_CHECKED(
+ deviceAddressToHal(micInfo.deviceAddress, &halMicInfo->device, halMicInfo->address),
+ result);
+ if (micInfo.channelMapping.size() > AUDIO_CHANNEL_COUNT_MAX) {
+ ALOGE("HIDL MicrophoneInfo has too many channelMapping elements: %zu",
+ micInfo.channelMapping.size());
+ result = BAD_VALUE;
+ }
+ size_t ch;
+ for (ch = 0; ch < micInfo.channelMapping.size() && ch < AUDIO_CHANNEL_COUNT_MAX; ch++) {
+ halMicInfo->channel_mapping[ch] =
+ static_cast<audio_microphone_channel_mapping_t>(micInfo.channelMapping[ch]);
+ }
+ for (; ch < AUDIO_CHANNEL_COUNT_MAX; ch++) {
+ halMicInfo->channel_mapping[ch] = AUDIO_MICROPHONE_CHANNEL_MAPPING_UNUSED;
+ }
+ halMicInfo->location = static_cast<audio_microphone_location_t>(micInfo.location);
+ halMicInfo->group = static_cast<audio_microphone_group_t>(micInfo.group);
+ halMicInfo->index_in_the_group = static_cast<unsigned int>(micInfo.indexInTheGroup);
+ halMicInfo->sensitivity = micInfo.sensitivity;
+ halMicInfo->max_spl = micInfo.maxSpl;
+ halMicInfo->min_spl = micInfo.minSpl;
+ halMicInfo->directionality =
+ static_cast<audio_microphone_directionality_t>(micInfo.directionality);
+ halMicInfo->num_frequency_responses =
+ static_cast<unsigned int>(micInfo.frequencyResponse.size());
+ if (halMicInfo->num_frequency_responses > AUDIO_MICROPHONE_MAX_FREQUENCY_RESPONSES) {
+ ALOGE("HIDL MicrophoneInfo has too many frequency responses: %u",
+ halMicInfo->num_frequency_responses);
+ halMicInfo->num_frequency_responses = AUDIO_MICROPHONE_MAX_FREQUENCY_RESPONSES;
+ result = BAD_VALUE;
+ }
+ for (size_t k = 0; k < halMicInfo->num_frequency_responses; k++) {
+ halMicInfo->frequency_responses[0][k] = micInfo.frequencyResponse[k].frequency;
+ halMicInfo->frequency_responses[1][k] = micInfo.frequencyResponse[k].level;
+ }
+ halMicInfo->geometric_location.x = micInfo.position.x;
+ halMicInfo->geometric_location.y = micInfo.position.y;
+ halMicInfo->geometric_location.z = micInfo.position.z;
+ halMicInfo->orientation.x = micInfo.orientation.x;
+ halMicInfo->orientation.y = micInfo.orientation.y;
+ halMicInfo->orientation.z = micInfo.orientation.z;
+ return result;
+}
+
+status_t CoreUtils::sinkMetadataFromHal(const std::vector<record_track_metadata_t>& halTracks,
+ SinkMetadata* sinkMetadata) {
+ status_t result = NO_ERROR;
+ sinkMetadata->tracks.resize(halTracks.size());
+ for (size_t i = 0; i < sinkMetadata->tracks.size(); ++i) {
+ const auto& halTrackMetadata = halTracks[i];
+ RecordTrackMetadata trackMetadata{};
+ CONVERT_CHECKED(
+ HidlUtils::audioSourceFromHal(halTrackMetadata.source, &trackMetadata.source),
+ result);
+ trackMetadata.gain = halTrackMetadata.gain;
+#if MAJOR_VERSION >= 5
+ if (halTrackMetadata.dest_device != AUDIO_DEVICE_NONE) {
+ DeviceAddress address;
+ if (status_t status =
+ deviceAddressFromHal(halTrackMetadata.dest_device,
+ halTrackMetadata.dest_device_address, &address);
+ status == NO_ERROR) {
+ trackMetadata.destination.device(std::move(address));
+ } else {
+ result = status;
+ }
+ }
+#if MAJOR_VERSION >= 7
+ trackMetadata.channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_NONE);
+#endif
+#endif // MAJOR_VERSION >= 5
+ sinkMetadata->tracks[i] = std::move(trackMetadata);
+ }
+ return result;
+}
+
+status_t CoreUtils::sinkMetadataFromHalV7(const std::vector<record_track_metadata_v7_t>& halTracks,
+ bool ignoreNonVendorTags, SinkMetadata* sinkMetadata) {
+ std::vector<record_track_metadata_t> bases;
+ bases.reserve(halTracks.size());
+ std::transform(halTracks.begin(), halTracks.end(), std::back_inserter(bases),
+ [](const record_track_metadata_v7_t& src) -> record_track_metadata_t {
+ record_track_metadata_t result;
+ record_track_metadata_from_v7(&result, &src);
+ return result;
+ });
+ status_t result = sinkMetadataFromHal(bases, sinkMetadata);
+#if MAJOR_VERSION >= 7
+ for (size_t i = 0; i < halTracks.size(); ++i) {
+ auto& trackMetadata = sinkMetadata->tracks[i];
+ const auto& halTrackMetadata = halTracks[i];
+ CONVERT_CHECKED(
+ HidlUtils::audioChannelMaskFromHal(halTrackMetadata.channel_mask, true /*isInput*/,
+ &trackMetadata.channelMask),
+ result);
+ std::vector<std::string> strTags = HidlUtils::splitAudioTags(halTrackMetadata.tags);
+ if (ignoreNonVendorTags) {
+ strTags = HidlUtils::filterOutNonVendorTags(strTags);
+ }
+ CONVERT_CHECKED(HidlUtils::audioTagsFromHal(strTags, &trackMetadata.tags), result);
+ }
+#else
+ (void)ignoreNonVendorTags;
+#endif
+ return result;
+}
+
+status_t CoreUtils::sinkMetadataToHal(const SinkMetadata& sinkMetadata,
+ std::vector<record_track_metadata_t>* halTracks) {
+ status_t result = NO_ERROR;
+ if (halTracks != nullptr) {
+ halTracks->reserve(sinkMetadata.tracks.size());
+ }
+ for (auto& trackMetadata : sinkMetadata.tracks) {
+ record_track_metadata halTrackMetadata{.gain = trackMetadata.gain};
+ CONVERT_CHECKED(HidlUtils::audioSourceToHal(trackMetadata.source, &halTrackMetadata.source),
+ result);
+#if MAJOR_VERSION >= 5
+ if (trackMetadata.destination.getDiscriminator() ==
+ RecordTrackMetadata::Destination::hidl_discriminator::device) {
+ CONVERT_CHECKED(deviceAddressToHal(trackMetadata.destination.device(),
+ &halTrackMetadata.dest_device,
+ halTrackMetadata.dest_device_address),
+ result);
+ }
+#endif
+ if (halTracks != nullptr) {
+ halTracks->push_back(std::move(halTrackMetadata));
+ }
+ }
+ return result;
+}
+
+status_t CoreUtils::sinkMetadataToHalV7(const SinkMetadata& sinkMetadata, bool ignoreNonVendorTags,
+ std::vector<record_track_metadata_v7_t>* halTracks) {
+ std::vector<record_track_metadata> bases;
+ status_t result = sinkMetadataToHal(sinkMetadata, halTracks != nullptr ? &bases : nullptr);
+ if (halTracks != nullptr) {
+ halTracks->reserve(sinkMetadata.tracks.size());
+ }
+ for (size_t i = 0; i < sinkMetadata.tracks.size(); ++i) {
+ record_track_metadata_v7_t halTrackMetadata;
+ if (halTracks != nullptr) {
+ record_track_metadata_to_v7(&halTrackMetadata, &bases[i]);
+ }
+#if MAJOR_VERSION >= 7
+ const auto& trackMetadata = sinkMetadata.tracks[i];
+ CONVERT_CHECKED(HidlUtils::audioChannelMaskToHal(trackMetadata.channelMask,
+ &halTrackMetadata.channel_mask),
+ result);
+ if (ignoreNonVendorTags) {
+ CONVERT_CHECKED(
+ HidlUtils::audioTagsToHal(HidlUtils::filterOutNonVendorTags(trackMetadata.tags),
+ halTrackMetadata.tags),
+ result);
+ } else {
+ CONVERT_CHECKED(HidlUtils::audioTagsToHal(trackMetadata.tags, halTrackMetadata.tags),
+ result);
+ }
+#else
+ (void)ignoreNonVendorTags;
+#endif
+ if (halTracks != nullptr) {
+ halTracks->push_back(std::move(halTrackMetadata));
+ }
+ }
+ return result;
+}
+
+status_t CoreUtils::sourceMetadataFromHal(const std::vector<playback_track_metadata_t>& halTracks,
+ SourceMetadata* sourceMetadata) {
+ status_t result = NO_ERROR;
+ sourceMetadata->tracks.resize(halTracks.size());
+ for (size_t i = 0; i < sourceMetadata->tracks.size(); ++i) {
+ const auto& halTrackMetadata = halTracks[i];
+ PlaybackTrackMetadata trackMetadata{};
+ CONVERT_CHECKED(HidlUtils::audioUsageFromHal(halTrackMetadata.usage, &trackMetadata.usage),
+ result);
+ CONVERT_CHECKED(HidlUtils::audioContentTypeFromHal(halTrackMetadata.content_type,
+ &trackMetadata.contentType),
+ result);
+ trackMetadata.gain = halTrackMetadata.gain;
+#if MAJOR_VERSION >= 7
+ trackMetadata.channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_NONE);
+#endif
+ sourceMetadata->tracks[i] = std::move(trackMetadata);
+ }
+ return result;
+}
+
+status_t CoreUtils::sourceMetadataFromHalV7(
+ const std::vector<playback_track_metadata_v7_t>& halTracks, bool ignoreNonVendorTags,
+ SourceMetadata* sourceMetadata) {
+ std::vector<playback_track_metadata_t> bases;
+ bases.reserve(halTracks.size());
+ std::transform(halTracks.begin(), halTracks.end(), std::back_inserter(bases),
+ [](const playback_track_metadata_v7_t& src) -> playback_track_metadata_t {
+ playback_track_metadata_t result;
+ playback_track_metadata_from_v7(&result, &src);
+ return result;
+ });
+ status_t result = sourceMetadataFromHal(bases, sourceMetadata);
+#if MAJOR_VERSION >= 7
+ for (size_t i = 0; i < halTracks.size(); ++i) {
+ auto& trackMetadata = sourceMetadata->tracks[i];
+ const auto& halTrackMetadata = halTracks[i];
+ CONVERT_CHECKED(
+ HidlUtils::audioChannelMaskFromHal(halTrackMetadata.channel_mask, false /*isInput*/,
+ &trackMetadata.channelMask),
+ result);
+ std::vector<std::string> strTags = HidlUtils::splitAudioTags(halTrackMetadata.tags);
+ if (ignoreNonVendorTags) {
+ strTags = HidlUtils::filterOutNonVendorTags(strTags);
+ }
+ CONVERT_CHECKED(HidlUtils::audioTagsFromHal(strTags, &trackMetadata.tags), result);
+ }
+#else
+ (void)ignoreNonVendorTags;
+#endif
+ return result;
+}
+
+status_t CoreUtils::sourceMetadataToHal(const SourceMetadata& sourceMetadata,
+ std::vector<playback_track_metadata_t>* halTracks) {
+ status_t result = NO_ERROR;
+ if (halTracks != nullptr) {
+ halTracks->reserve(sourceMetadata.tracks.size());
+ }
+ for (auto& trackMetadata : sourceMetadata.tracks) {
+ playback_track_metadata_t halTrackMetadata{.gain = trackMetadata.gain};
+ CONVERT_CHECKED(HidlUtils::audioUsageToHal(trackMetadata.usage, &halTrackMetadata.usage),
+ result);
+ CONVERT_CHECKED(HidlUtils::audioContentTypeToHal(trackMetadata.contentType,
+ &halTrackMetadata.content_type),
+ result);
+ if (halTracks != nullptr) {
+ halTracks->push_back(std::move(halTrackMetadata));
+ }
+ }
+ return result;
+}
+
+status_t CoreUtils::sourceMetadataToHalV7(const SourceMetadata& sourceMetadata,
+ bool ignoreNonVendorTags,
+ std::vector<playback_track_metadata_v7_t>* halTracks) {
+ std::vector<playback_track_metadata_t> bases;
+ status_t result = sourceMetadataToHal(sourceMetadata, halTracks != nullptr ? &bases : nullptr);
+ if (halTracks != nullptr) {
+ halTracks->reserve(sourceMetadata.tracks.size());
+ }
+ for (size_t i = 0; i < sourceMetadata.tracks.size(); ++i) {
+ playback_track_metadata_v7_t halTrackMetadata;
+ if (halTracks != nullptr) {
+ playback_track_metadata_to_v7(&halTrackMetadata, &bases[i]);
+ }
+#if MAJOR_VERSION >= 7
+ const auto& trackMetadata = sourceMetadata.tracks[i];
+ CONVERT_CHECKED(HidlUtils::audioChannelMaskToHal(trackMetadata.channelMask,
+ &halTrackMetadata.channel_mask),
+ result);
+ if (ignoreNonVendorTags) {
+ CONVERT_CHECKED(
+ HidlUtils::audioTagsToHal(HidlUtils::filterOutNonVendorTags(trackMetadata.tags),
+ halTrackMetadata.tags),
+ result);
+ } else {
+ CONVERT_CHECKED(HidlUtils::audioTagsToHal(trackMetadata.tags, halTrackMetadata.tags),
+ result);
+ }
+#else
+ (void)ignoreNonVendorTags;
+#endif
+ if (halTracks != nullptr) {
+ halTracks->push_back(std::move(halTrackMetadata));
+ }
+ }
+ return result;
+}
+#endif // MAJOR_VERSION >= 4
+
+#if MAJOR_VERSION >= 7
+namespace xsd {
+using namespace ::android::audio::policy::configuration::V7_0;
+}
+
+status_t CoreUtils::audioInputFlagsFromHal(audio_input_flags_t halFlagMask,
+ AudioInputFlags* flags) {
+ status_t status = NO_ERROR;
+ std::vector<AudioInOutFlag> result;
+ for (uint32_t bit = 0; halFlagMask != 0 && bit < sizeof(audio_input_flags_t) * 8; ++bit) {
+ audio_input_flags_t flag = static_cast<audio_input_flags_t>(1u << bit);
+ if ((flag & halFlagMask) == flag) {
+ AudioInOutFlag flagStr = audio_input_flag_to_string(flag);
+ if (!flagStr.empty() && !xsd::isUnknownAudioInOutFlag(flagStr)) {
+ result.push_back(flagStr);
+ } else {
+ ALOGE("Unknown audio input flag value 0x%X", flag);
+ status = BAD_VALUE;
+ }
+ halFlagMask = static_cast<audio_input_flags_t>(halFlagMask & ~flag);
+ }
+ }
+ *flags = result;
+ return status;
+}
+
+status_t CoreUtils::audioInputFlagsToHal(const AudioInputFlags& flags,
+ audio_input_flags_t* halFlagMask) {
+ status_t status = NO_ERROR;
+ *halFlagMask = {};
+ for (const auto& flag : flags) {
+ audio_input_flags_t halFlag;
+ if (!xsd::isUnknownAudioInOutFlag(flag) &&
+ audio_input_flag_from_string(flag.c_str(), &halFlag)) {
+ *halFlagMask = static_cast<audio_input_flags_t>(*halFlagMask | halFlag);
+ } else {
+ ALOGE("Unknown audio input flag \"%s\"", flag.c_str());
+ status = BAD_VALUE;
+ }
+ }
+ return status;
+}
+
+status_t CoreUtils::audioOutputFlagsFromHal(audio_output_flags_t halFlagMask,
+ AudioOutputFlags* flags) {
+ status_t status = NO_ERROR;
+ std::vector<AudioInOutFlag> result;
+ for (uint32_t bit = 0; halFlagMask != 0 && bit < sizeof(audio_output_flags_t) * 8; ++bit) {
+ audio_output_flags_t flag = static_cast<audio_output_flags_t>(1u << bit);
+ if ((flag & halFlagMask) == flag) {
+ AudioInOutFlag flagStr = audio_output_flag_to_string(flag);
+ if (!flagStr.empty() && !xsd::isUnknownAudioInOutFlag(flagStr)) {
+ result.push_back(flagStr);
+ } else {
+ ALOGE("Unknown audio output flag value 0x%X", flag);
+ status = BAD_VALUE;
+ }
+ halFlagMask = static_cast<audio_output_flags_t>(halFlagMask & ~flag);
+ }
+ }
+ *flags = result;
+ return status;
+}
+
+status_t CoreUtils::audioOutputFlagsToHal(const AudioOutputFlags& flags,
+ audio_output_flags_t* halFlagMask) {
+ status_t status = NO_ERROR;
+ *halFlagMask = {};
+ for (const auto& flag : flags) {
+ audio_output_flags_t halFlag;
+ if (!xsd::isUnknownAudioInOutFlag(flag) &&
+ audio_output_flag_from_string(flag.c_str(), &halFlag)) {
+ *halFlagMask = static_cast<audio_output_flags_t>(*halFlagMask | halFlag);
+ } else {
+ ALOGE("Unknown audio output flag \"%s\"", flag.c_str());
+ status = BAD_VALUE;
+ }
+ }
+ return status;
+}
+#endif
+
+} // namespace implementation
+} // namespace CPP_VERSION
+} // namespace audio
+} // namespace hardware
+} // namespace android
diff --git a/audio/core/all-versions/default/util/include/util/CoreUtils.h b/audio/core/all-versions/default/util/include/util/CoreUtils.h
new file mode 100644
index 0000000..1e5272a
--- /dev/null
+++ b/audio/core/all-versions/default/util/include/util/CoreUtils.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2021 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
+
+// clang-format off
+#include PATH(android/hardware/audio/FILE_VERSION/types.h)
+// clang-format off
+
+#include <vector>
+
+#include <system/audio.h>
+
+#include <common/all-versions/VersionUtils.h>
+#include <VersionUtils.h>
+
+namespace android {
+namespace hardware {
+namespace audio {
+namespace CPP_VERSION {
+namespace implementation {
+
+using ::android::hardware::audio::common::utils::EnumBitfield;
+using ::android::hardware::hidl_vec;
+using namespace ::android::hardware::audio::common::CPP_VERSION;
+using namespace ::android::hardware::audio::CPP_VERSION;
+
+struct CoreUtils {
+ // Note: the converters for DeviceAddress have to be in CoreUtils for HAL V4
+ // because DeviceAddress used to be defined in the core HAL. For V5 and above
+ // these functions simply delegate to HidlUtils.
+ static status_t deviceAddressToHal(const DeviceAddress& device, audio_devices_t* halDeviceType,
+ char* halDeviceAddress);
+ static status_t deviceAddressFromHal(audio_devices_t halDeviceType, const char* halDeviceAddress,
+ DeviceAddress* device);
+#if MAJOR_VERSION >= 4
+ static status_t microphoneInfoFromHal(const struct audio_microphone_characteristic_t& halMicInfo,
+ MicrophoneInfo* micInfo);
+ static status_t microphoneInfoToHal(const MicrophoneInfo& micInfo, audio_microphone_characteristic_t* halMicInfo);
+ // Note: {Sink|Source}Metadata types are defined in 'common' (since V5), so they can be used
+ // by the BT HAL. However, the converters are defined here, not in HidlUtils to avoid adding
+ // conditionals to handle V4. The converters are only used by 'core' HAL anyways.
+ static status_t sinkMetadataFromHal(const std::vector<record_track_metadata_t>& halTracks,
+ SinkMetadata* sinkMetadata);
+ static status_t sinkMetadataFromHalV7(const std::vector<record_track_metadata_v7_t>& halTracks,
+ bool ignoreNonVendorTags, SinkMetadata* sinkMetadata);
+ static status_t sinkMetadataToHal(const SinkMetadata& sinkMetadata,
+ std::vector<record_track_metadata_t>* halTracks);
+ static status_t sinkMetadataToHalV7(const SinkMetadata& sinkMetadata, bool ignoreNonVendorTags,
+ std::vector<record_track_metadata_v7_t>* halTracks);
+ static status_t sourceMetadataFromHal(const std::vector<playback_track_metadata_t>& halTracks,
+ SourceMetadata* sourceMetadata);
+ static status_t sourceMetadataFromHalV7(const std::vector<playback_track_metadata_v7_t>& halTracks,
+ bool ignoreNonVendorTags, SourceMetadata* sourceMetadata);
+ static status_t sourceMetadataToHal(const SourceMetadata& sourceMetadata,
+ std::vector<playback_track_metadata_t>* halTracks);
+ static status_t sourceMetadataToHalV7(const SourceMetadata& sourceMetadata, bool ignoreNonVendorTags,
+ std::vector<playback_track_metadata_v7_t>* halTracks);
+#endif
+
+#if MAJOR_VERSION <= 6
+ using AudioInputFlags =
+ ::android::hardware::audio::common::CPP_VERSION::implementation::AudioInputFlagBitfield;
+ using AudioOutputFlags =
+ ::android::hardware::audio::common::CPP_VERSION::implementation::AudioOutputFlagBitfield;
+ static inline status_t audioInputFlagsFromHal(audio_input_flags_t halFlagMask, AudioInputFlags* flags) {
+ *flags = EnumBitfield<AudioInputFlag>(halFlagMask);
+ return NO_ERROR;
+ }
+ static inline status_t audioInputFlagsToHal(AudioInputFlags flags, audio_input_flags_t* halFlagMask) {
+ *halFlagMask = static_cast<audio_input_flags_t>(flags);
+ return NO_ERROR;
+ }
+ static inline status_t audioOutputFlagsFromHal(audio_output_flags_t halFlagMask, AudioOutputFlags* flags) {
+ *flags = EnumBitfield<AudioOutputFlag>(halFlagMask);
+ return NO_ERROR;
+ }
+ static inline status_t audioOutputFlagsToHal(AudioOutputFlags flags, audio_output_flags_t* halFlagMask) {
+ *halFlagMask = static_cast<audio_output_flags_t>(flags);
+ return NO_ERROR;
+ }
+#else
+ using AudioInputFlags = hidl_vec<::android::hardware::audio::CPP_VERSION::AudioInOutFlag>;
+ using AudioOutputFlags = hidl_vec<::android::hardware::audio::CPP_VERSION::AudioInOutFlag>;
+ static status_t audioInputFlagsFromHal(audio_input_flags_t halFlagMask, AudioInputFlags* flags);
+ static status_t audioInputFlagsToHal(const AudioInputFlags& flags, audio_input_flags_t* halFlagMask);
+ static status_t audioOutputFlagsFromHal(audio_output_flags_t halFlagMask, AudioOutputFlags* flags);
+ static status_t audioOutputFlagsToHal(const AudioOutputFlags& flags, audio_output_flags_t* halFlagMask);
+#endif
+
+};
+
+} // namespace implementation
+} // namespace CPP_VERSION
+} // namespace audio
+} // namespace hardware
+} // namespace android
diff --git a/audio/core/all-versions/default/util/tests/coreutils_tests.cpp b/audio/core/all-versions/default/util/tests/coreutils_tests.cpp
new file mode 100644
index 0000000..0c18482
--- /dev/null
+++ b/audio/core/all-versions/default/util/tests/coreutils_tests.cpp
@@ -0,0 +1,525 @@
+/*
+ * Copyright (C) 2021 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 <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#define LOG_TAG "CoreUtils_Test"
+#include <log/log.h>
+
+#include <android_audio_policy_configuration_V7_0-enums.h>
+#include <system/audio.h>
+#include <util/CoreUtils.h>
+#include <xsdc/XsdcSupport.h>
+
+using namespace android;
+using namespace ::android::hardware::audio::common::CPP_VERSION;
+using namespace ::android::hardware::audio::CPP_VERSION;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::audio::CPP_VERSION::implementation::CoreUtils;
+namespace xsd {
+using namespace ::android::audio::policy::configuration::V7_0;
+}
+
+static constexpr audio_channel_mask_t kInvalidHalChannelMask = AUDIO_CHANNEL_INVALID;
+static constexpr audio_content_type_t kInvalidHalContentType =
+ static_cast<audio_content_type_t>(0xFFFFFFFFU);
+static constexpr audio_devices_t kInvalidHalDevice = static_cast<audio_devices_t>(0xFFFFFFFFU);
+static constexpr audio_input_flags_t kInvalidInputFlags =
+ static_cast<audio_input_flags_t>(0xFFFFFFFFU);
+static constexpr audio_output_flags_t kInvalidOutputFlags =
+ static_cast<audio_output_flags_t>(0xFFFFFFFFU);
+// AUDIO_SOURCE_INVALID is framework-only.
+static constexpr audio_source_t kInvalidHalSource = static_cast<audio_source_t>(-1);
+static constexpr audio_usage_t kInvalidHalUsage = static_cast<audio_usage_t>(0xFFFFFFFFU);
+
+static bool isInputFlag(xsd::AudioInOutFlag flag) {
+ return toString(flag).find("_INPUT_FLAG_") != std::string::npos;
+}
+
+static bool isOutputFlag(xsd::AudioInOutFlag flag) {
+ return toString(flag).find("_OUTPUT_FLAG_") != std::string::npos;
+}
+
+TEST(CoreUtils, ConvertInvalidInputFlagMask) {
+ CoreUtils::AudioInputFlags invalid;
+ EXPECT_EQ(BAD_VALUE, CoreUtils::audioInputFlagsFromHal(kInvalidInputFlags, &invalid));
+ audio_input_flags_t halInvalid;
+ invalid.resize(1);
+ invalid[0] = "random string";
+ EXPECT_EQ(BAD_VALUE, CoreUtils::audioInputFlagsToHal(invalid, &halInvalid));
+}
+
+TEST(CoreUtils, ConvertInputFlagMask) {
+ CoreUtils::AudioInputFlags emptyInputFlags;
+ audio_input_flags_t halEmptyInputFlags;
+ EXPECT_EQ(NO_ERROR, CoreUtils::audioInputFlagsToHal(emptyInputFlags, &halEmptyInputFlags));
+ EXPECT_EQ(AUDIO_INPUT_FLAG_NONE, halEmptyInputFlags);
+ CoreUtils::AudioInputFlags emptyInputFlagsBack;
+ EXPECT_EQ(NO_ERROR,
+ CoreUtils::audioInputFlagsFromHal(halEmptyInputFlags, &emptyInputFlagsBack));
+ EXPECT_EQ(emptyInputFlags, emptyInputFlagsBack);
+ CoreUtils::AudioInputFlags emptyInputFlagsFromNone;
+ EXPECT_EQ(NO_ERROR,
+ CoreUtils::audioInputFlagsFromHal(AUDIO_INPUT_FLAG_NONE, &emptyInputFlagsFromNone));
+ EXPECT_EQ(emptyInputFlags, emptyInputFlagsFromNone);
+
+ std::vector<std::string> allEnumValues;
+ for (const auto enumVal : xsdc_enum_range<xsd::AudioInOutFlag>{}) {
+ if (isInputFlag(enumVal)) {
+ allEnumValues.push_back(toString(enumVal));
+ }
+ }
+ CoreUtils::AudioInputFlags allInputFlags;
+ allInputFlags.resize(allEnumValues.size());
+ for (size_t i = 0; i < allEnumValues.size(); ++i) {
+ allInputFlags[i] = allEnumValues[i];
+ }
+ audio_input_flags_t halAllInputFlags;
+ EXPECT_EQ(NO_ERROR, CoreUtils::audioInputFlagsToHal(allInputFlags, &halAllInputFlags));
+ CoreUtils::AudioInputFlags allInputFlagsBack;
+ EXPECT_EQ(NO_ERROR, CoreUtils::audioInputFlagsFromHal(halAllInputFlags, &allInputFlagsBack));
+ EXPECT_EQ(allInputFlags, allInputFlagsBack);
+}
+
+TEST(CoreUtils, ConvertInvalidOutputFlagMask) {
+ CoreUtils::AudioOutputFlags invalid;
+ EXPECT_EQ(BAD_VALUE, CoreUtils::audioOutputFlagsFromHal(kInvalidOutputFlags, &invalid));
+ audio_output_flags_t halInvalid;
+ invalid.resize(1);
+ invalid[0] = "random string";
+ EXPECT_EQ(BAD_VALUE, CoreUtils::audioOutputFlagsToHal(invalid, &halInvalid));
+}
+
+TEST(CoreUtils, ConvertOutputFlagMask) {
+ CoreUtils::AudioOutputFlags emptyOutputFlags;
+ audio_output_flags_t halEmptyOutputFlags;
+ EXPECT_EQ(NO_ERROR, CoreUtils::audioOutputFlagsToHal(emptyOutputFlags, &halEmptyOutputFlags));
+ EXPECT_EQ(AUDIO_OUTPUT_FLAG_NONE, halEmptyOutputFlags);
+ CoreUtils::AudioOutputFlags emptyOutputFlagsBack;
+ EXPECT_EQ(NO_ERROR,
+ CoreUtils::audioOutputFlagsFromHal(halEmptyOutputFlags, &emptyOutputFlagsBack));
+ EXPECT_EQ(emptyOutputFlags, emptyOutputFlagsBack);
+ CoreUtils::AudioOutputFlags emptyOutputFlagsFromNone;
+ EXPECT_EQ(NO_ERROR, CoreUtils::audioOutputFlagsFromHal(AUDIO_OUTPUT_FLAG_NONE,
+ &emptyOutputFlagsFromNone));
+ EXPECT_EQ(emptyOutputFlags, emptyOutputFlagsFromNone);
+
+ std::vector<std::string> allEnumValues;
+ for (const auto enumVal : xsdc_enum_range<xsd::AudioInOutFlag>{}) {
+ if (isOutputFlag(enumVal)) {
+ allEnumValues.push_back(toString(enumVal));
+ }
+ }
+ CoreUtils::AudioOutputFlags allOutputFlags;
+ allOutputFlags.resize(allEnumValues.size());
+ for (size_t i = 0; i < allEnumValues.size(); ++i) {
+ allOutputFlags[i] = allEnumValues[i];
+ }
+ audio_output_flags_t halAllOutputFlags;
+ EXPECT_EQ(NO_ERROR, CoreUtils::audioOutputFlagsToHal(allOutputFlags, &halAllOutputFlags));
+ CoreUtils::AudioOutputFlags allOutputFlagsBack;
+ EXPECT_EQ(NO_ERROR, CoreUtils::audioOutputFlagsFromHal(halAllOutputFlags, &allOutputFlagsBack));
+ EXPECT_EQ(allOutputFlags, allOutputFlagsBack);
+}
+
+static MicrophoneInfo generateValidMicrophoneInfo() {
+ MicrophoneInfo micInfo{};
+ micInfo.deviceAddress.deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_IN_BUILTIN_MIC);
+ micInfo.channelMapping.resize(1);
+ micInfo.channelMapping[0] = AudioMicrophoneChannelMapping::DIRECT;
+ micInfo.location = AudioMicrophoneLocation::MAINBODY_MOVABLE;
+ micInfo.group = 42;
+ micInfo.indexInTheGroup = 13;
+ micInfo.sensitivity = 65.5;
+ micInfo.maxSpl = 100.5;
+ micInfo.minSpl = 36.6;
+ micInfo.directionality = AudioMicrophoneDirectionality::HYPER_CARDIOID;
+ micInfo.frequencyResponse.resize(1);
+ micInfo.frequencyResponse[0].frequency = 1000;
+ micInfo.frequencyResponse[0].level = 85;
+ micInfo.position.x = 0;
+ micInfo.position.y = 1;
+ micInfo.position.z = 0;
+ micInfo.orientation.x = 0;
+ micInfo.orientation.y = 0;
+ micInfo.orientation.z = 1;
+ return micInfo;
+}
+
+TEST(CoreUtils, ConvertInvalidMicrophoneInfo) {
+ MicrophoneInfo invalid;
+ audio_microphone_characteristic_t halInvalid{};
+ halInvalid.device = kInvalidHalDevice;
+ EXPECT_EQ(BAD_VALUE, CoreUtils::microphoneInfoFromHal(halInvalid, &invalid));
+
+ MicrophoneInfo oversizeDeviceId = generateValidMicrophoneInfo();
+ oversizeDeviceId.deviceId = std::string(AUDIO_MICROPHONE_ID_MAX_LEN + 1, 'A');
+ EXPECT_EQ(BAD_VALUE, CoreUtils::microphoneInfoToHal(oversizeDeviceId, &halInvalid));
+ MicrophoneInfo invalidDeviceType = generateValidMicrophoneInfo();
+ invalidDeviceType.deviceAddress.deviceType = "random string";
+ EXPECT_EQ(BAD_VALUE, CoreUtils::microphoneInfoToHal(invalidDeviceType, &halInvalid));
+ MicrophoneInfo oversizeChannelMapping = generateValidMicrophoneInfo();
+ oversizeChannelMapping.channelMapping.resize(AUDIO_CHANNEL_COUNT_MAX + 1);
+ EXPECT_EQ(BAD_VALUE, CoreUtils::microphoneInfoToHal(oversizeChannelMapping, &halInvalid));
+ MicrophoneInfo oversizeFrequencyResponses = generateValidMicrophoneInfo();
+ oversizeFrequencyResponses.frequencyResponse.resize(AUDIO_MICROPHONE_MAX_FREQUENCY_RESPONSES +
+ 1);
+ EXPECT_EQ(BAD_VALUE, CoreUtils::microphoneInfoToHal(oversizeFrequencyResponses, &halInvalid));
+}
+
+TEST(CoreUtils, ConvertMicrophoneInfo) {
+ MicrophoneInfo micInfo = generateValidMicrophoneInfo();
+ audio_microphone_characteristic_t halMicInfo;
+ EXPECT_EQ(NO_ERROR, CoreUtils::microphoneInfoToHal(micInfo, &halMicInfo));
+ MicrophoneInfo micInfoBack;
+ EXPECT_EQ(NO_ERROR, CoreUtils::microphoneInfoFromHal(halMicInfo, &micInfoBack));
+ EXPECT_EQ(micInfo, micInfoBack);
+}
+
+static RecordTrackMetadata generateMinimalRecordTrackMetadata() {
+ RecordTrackMetadata metadata{};
+ metadata.source = toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT);
+ metadata.channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_NONE);
+ return metadata;
+}
+
+static RecordTrackMetadata generateValidRecordTrackMetadata() {
+ RecordTrackMetadata metadata = generateMinimalRecordTrackMetadata();
+ metadata.tags.resize(1);
+ metadata.tags[0] = "VX_GOOGLE_42";
+ metadata.channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_IN_MONO);
+ metadata.gain = 1.0;
+ return metadata;
+}
+
+static RecordTrackMetadata generateValidRecordTrackMetadataWithDevice() {
+ RecordTrackMetadata metadata = generateValidRecordTrackMetadata();
+ metadata.destination.device({});
+ metadata.destination.device().deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_OUT_SPEAKER);
+ return metadata;
+}
+
+using SinkTracks = hidl_vec<RecordTrackMetadata>;
+
+TEST(CoreUtils, ConvertInvalidSinkMetadata) {
+ SinkMetadata invalidSource;
+ invalidSource.tracks = SinkTracks{generateMinimalRecordTrackMetadata()};
+ invalidSource.tracks[0].source = "random string";
+ EXPECT_EQ(BAD_VALUE, CoreUtils::sinkMetadataToHal(invalidSource, nullptr));
+ EXPECT_EQ(BAD_VALUE, CoreUtils::sinkMetadataToHalV7(invalidSource,
+ false /*ignoreNonVendorTags*/, nullptr));
+ EXPECT_EQ(BAD_VALUE,
+ CoreUtils::sinkMetadataToHalV7(invalidSource, true /*ignoreNonVendorTags*/, nullptr));
+ SinkMetadata invalidDeviceType;
+ invalidDeviceType.tracks = SinkTracks{generateValidRecordTrackMetadataWithDevice()};
+ invalidDeviceType.tracks[0].destination.device().deviceType = "random string";
+ EXPECT_EQ(BAD_VALUE, CoreUtils::sinkMetadataToHal(invalidDeviceType, nullptr));
+ EXPECT_EQ(BAD_VALUE, CoreUtils::sinkMetadataToHalV7(invalidDeviceType,
+ false /*ignoreNonVendorTags*/, nullptr));
+ EXPECT_EQ(BAD_VALUE, CoreUtils::sinkMetadataToHalV7(invalidDeviceType,
+ true /*ignoreNonVendorTags*/, nullptr));
+ SinkMetadata invalidChannelMask;
+ invalidChannelMask.tracks = SinkTracks{generateValidRecordTrackMetadata()};
+ invalidChannelMask.tracks[0].channelMask = "random string";
+ // Channel mask is sliced away by 'sinkMetadataToHal'
+ EXPECT_EQ(NO_ERROR, CoreUtils::sinkMetadataToHal(invalidChannelMask, nullptr));
+ EXPECT_EQ(BAD_VALUE, CoreUtils::sinkMetadataToHalV7(invalidChannelMask,
+ false /*ignoreNonVendorTags*/, nullptr));
+ EXPECT_EQ(BAD_VALUE, CoreUtils::sinkMetadataToHalV7(invalidChannelMask,
+ true /*ignoreNonVendorTags*/, nullptr));
+ SinkMetadata invalidTags;
+ invalidTags.tracks = SinkTracks{generateValidRecordTrackMetadata()};
+ invalidTags.tracks[0].tags[0] = "random string";
+ // Tags are sliced away by 'sinkMetadataToHal'
+ EXPECT_EQ(NO_ERROR, CoreUtils::sinkMetadataToHal(invalidTags, nullptr));
+ EXPECT_EQ(BAD_VALUE,
+ CoreUtils::sinkMetadataToHalV7(invalidTags, false /*ignoreNonVendorTags*/, nullptr));
+ // Non-vendor tags should be filtered out.
+ EXPECT_EQ(NO_ERROR,
+ CoreUtils::sinkMetadataToHalV7(invalidTags, true /*ignoreNonVendorTags*/, nullptr));
+
+ // Verify that a default-initialized metadata is valid.
+ std::vector<record_track_metadata_t> halValid(1, record_track_metadata_t{});
+ std::vector<record_track_metadata_v7_t> halValidV7(1, record_track_metadata_v7_t{});
+ SinkMetadata valid;
+ EXPECT_EQ(NO_ERROR, CoreUtils::sinkMetadataFromHal(halValid, &valid));
+ EXPECT_EQ(NO_ERROR,
+ CoreUtils::sinkMetadataFromHalV7(halValidV7, false /*ignoreNonVendorTags*/, &valid));
+ EXPECT_EQ(NO_ERROR,
+ CoreUtils::sinkMetadataFromHalV7(halValidV7, true /*ignoreNonVendorTags*/, &valid));
+
+ std::vector<record_track_metadata_t> halInvalidSource = {{.source = kInvalidHalSource}};
+ std::vector<record_track_metadata_v7_t> halInvalidSourceV7 = {
+ {.base = {.source = kInvalidHalSource}}};
+ EXPECT_EQ(BAD_VALUE, CoreUtils::sinkMetadataFromHal(halInvalidSource, &invalidSource));
+ EXPECT_EQ(BAD_VALUE,
+ CoreUtils::sinkMetadataFromHalV7(halInvalidSourceV7, false /*ignoreNonVendorTags*/,
+ &invalidSource));
+ EXPECT_EQ(BAD_VALUE, CoreUtils::sinkMetadataFromHalV7(
+ halInvalidSourceV7, true /*ignoreNonVendorTags*/, &invalidSource));
+ std::vector<record_track_metadata_t> halInvalidDeviceType = {
+ {.dest_device = kInvalidHalDevice}};
+ std::vector<record_track_metadata_v7_t> halInvalidDeviceTypeV7 = {
+ {.base = {.dest_device = kInvalidHalDevice}}};
+ EXPECT_EQ(BAD_VALUE, CoreUtils::sinkMetadataFromHal(halInvalidDeviceType, &invalidDeviceType));
+ EXPECT_EQ(BAD_VALUE,
+ CoreUtils::sinkMetadataFromHalV7(halInvalidDeviceTypeV7,
+ false /*ignoreNonVendorTags*/, &invalidDeviceType));
+ EXPECT_EQ(BAD_VALUE,
+ CoreUtils::sinkMetadataFromHalV7(halInvalidDeviceTypeV7, true /*ignoreNonVendorTags*/,
+ &invalidDeviceType));
+ std::vector<record_track_metadata_v7_t> halInvalidChannelMaskV7 = {
+ {.channel_mask = kInvalidHalChannelMask}};
+ EXPECT_EQ(BAD_VALUE,
+ CoreUtils::sinkMetadataFromHalV7(halInvalidChannelMaskV7,
+ false /*ignoreNonVendorTags*/, &invalidChannelMask));
+ EXPECT_EQ(BAD_VALUE,
+ CoreUtils::sinkMetadataFromHalV7(halInvalidChannelMaskV7,
+ true /*ignoreNonVendorTags*/, &invalidChannelMask));
+ std::vector<record_track_metadata_v7_t> halInvalidTagsV7 = {{.tags = "random string"}};
+ EXPECT_EQ(BAD_VALUE, CoreUtils::sinkMetadataFromHalV7(
+ halInvalidTagsV7, false /*ignoreNonVendorTags*/, &invalidTags));
+ // Non-vendor tags should be filtered out.
+ EXPECT_EQ(NO_ERROR, CoreUtils::sinkMetadataFromHalV7(
+ halInvalidTagsV7, true /*ignoreNonVendorTags*/, &invalidTags));
+}
+
+TEST(CoreUtils, ConvertEmptySinkMetadata) {
+ SinkMetadata emptySinkMetadata;
+ std::vector<record_track_metadata_t> halEmptySinkMetadata;
+ EXPECT_EQ(NO_ERROR, CoreUtils::sinkMetadataToHal(emptySinkMetadata, &halEmptySinkMetadata));
+ EXPECT_TRUE(halEmptySinkMetadata.empty());
+ SinkMetadata emptySinkMetadataBack;
+ EXPECT_EQ(NO_ERROR,
+ CoreUtils::sinkMetadataFromHal(halEmptySinkMetadata, &emptySinkMetadataBack));
+ EXPECT_EQ(emptySinkMetadata, emptySinkMetadataBack);
+ std::vector<record_track_metadata_v7_t> halEmptySinkMetadataV7;
+ EXPECT_EQ(NO_ERROR,
+ CoreUtils::sinkMetadataToHalV7(emptySinkMetadata, false /*ignoreNonVendorTags*/,
+ &halEmptySinkMetadataV7));
+ EXPECT_TRUE(halEmptySinkMetadataV7.empty());
+ SinkMetadata emptySinkMetadataBackFromV7;
+ EXPECT_EQ(NO_ERROR, CoreUtils::sinkMetadataFromHalV7(halEmptySinkMetadataV7,
+ false /*ignoreNonVendorTags*/,
+ &emptySinkMetadataBackFromV7));
+ EXPECT_EQ(emptySinkMetadata, emptySinkMetadataBackFromV7);
+}
+
+class SinkMetadataConvertTest : public ::testing::TestWithParam<SinkTracks> {};
+
+TEST_P(SinkMetadataConvertTest, ToFromHal) {
+ SinkMetadata sinkMetadata;
+ sinkMetadata.tracks = GetParam();
+ std::vector<record_track_metadata_t> halSinkMetadata;
+ EXPECT_EQ(NO_ERROR, CoreUtils::sinkMetadataToHal(sinkMetadata, &halSinkMetadata));
+ EXPECT_EQ(sinkMetadata.tracks.size(), halSinkMetadata.size());
+ SinkMetadata sinkMetadataBackTrimmed;
+ EXPECT_EQ(NO_ERROR, CoreUtils::sinkMetadataFromHal(halSinkMetadata, &sinkMetadataBackTrimmed));
+ // Can't compare 'sinkMetadata' to 'sinkMetadataBackTrimmed'
+ std::vector<record_track_metadata_v7_t> halSinkMetadataV7;
+ EXPECT_EQ(NO_ERROR, CoreUtils::sinkMetadataToHalV7(sinkMetadata, false /*ignoreNonVendorTags*/,
+ &halSinkMetadataV7));
+ EXPECT_EQ(sinkMetadata.tracks.size(), halSinkMetadataV7.size());
+ SinkMetadata sinkMetadataBackFromV7;
+ EXPECT_EQ(NO_ERROR,
+ CoreUtils::sinkMetadataFromHalV7(halSinkMetadataV7, false /*ignoreNonVendorTags*/,
+ &sinkMetadataBackFromV7));
+ EXPECT_EQ(sinkMetadata, sinkMetadataBackFromV7);
+ std::vector<record_track_metadata_v7_t> halSinkMetadataV7FromTrimmed;
+ EXPECT_EQ(NO_ERROR,
+ CoreUtils::sinkMetadataToHalV7(sinkMetadataBackTrimmed, false /*ignoreNonVendorTags*/,
+ &halSinkMetadataV7FromTrimmed));
+ EXPECT_EQ(sinkMetadata.tracks.size(), halSinkMetadataV7FromTrimmed.size());
+ SinkMetadata sinkMetadataBackFromV7Trimmed;
+ EXPECT_EQ(NO_ERROR, CoreUtils::sinkMetadataFromHalV7(halSinkMetadataV7FromTrimmed,
+ false /*ignoreNonVendorTags*/,
+ &sinkMetadataBackFromV7Trimmed));
+ EXPECT_EQ(sinkMetadataBackTrimmed, sinkMetadataBackFromV7Trimmed);
+}
+
+INSTANTIATE_TEST_SUITE_P(ValidRecordTrackMetadatas, SinkMetadataConvertTest,
+ ::testing::Values(SinkTracks{generateMinimalRecordTrackMetadata()},
+ SinkTracks{generateValidRecordTrackMetadata()},
+ SinkTracks{generateValidRecordTrackMetadataWithDevice()},
+ SinkTracks{
+ generateMinimalRecordTrackMetadata(),
+ generateValidRecordTrackMetadata(),
+ generateValidRecordTrackMetadataWithDevice()}));
+
+static PlaybackTrackMetadata generateMinimalPlaybackTrackMetadata() {
+ PlaybackTrackMetadata metadata{};
+ metadata.usage = toString(xsd::AudioUsage::AUDIO_USAGE_UNKNOWN);
+ metadata.contentType = toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_UNKNOWN);
+ metadata.channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_NONE);
+ return metadata;
+}
+
+static PlaybackTrackMetadata generateValidPlaybackTrackMetadata() {
+ PlaybackTrackMetadata metadata = generateMinimalPlaybackTrackMetadata();
+ metadata.tags.resize(1);
+ metadata.tags[0] = "VX_GOOGLE_42";
+ metadata.channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_MONO);
+ metadata.gain = 1.0;
+ return metadata;
+}
+
+using SourceTracks = hidl_vec<PlaybackTrackMetadata>;
+
+TEST(CoreUtils, ConvertInvalidSourceMetadata) {
+ SourceMetadata invalidUsage;
+ invalidUsage.tracks = SourceTracks{generateMinimalPlaybackTrackMetadata()};
+ invalidUsage.tracks[0].usage = "random string";
+ EXPECT_EQ(BAD_VALUE, CoreUtils::sourceMetadataToHal(invalidUsage, nullptr));
+ EXPECT_EQ(BAD_VALUE, CoreUtils::sourceMetadataToHalV7(invalidUsage,
+ false /*ignoreNonVendorTags*/, nullptr));
+ SourceMetadata invalidContentType;
+ invalidContentType.tracks = SourceTracks{generateMinimalPlaybackTrackMetadata()};
+ invalidContentType.tracks[0].contentType = "random string";
+ EXPECT_EQ(BAD_VALUE, CoreUtils::sourceMetadataToHal(invalidContentType, nullptr));
+ EXPECT_EQ(BAD_VALUE, CoreUtils::sourceMetadataToHalV7(invalidContentType,
+ false /*ignoreNonVendorTags*/, nullptr));
+ EXPECT_EQ(BAD_VALUE, CoreUtils::sourceMetadataToHalV7(invalidContentType,
+ true /*ignoreNonVendorTags*/, nullptr));
+ SourceMetadata invalidChannelMask;
+ invalidChannelMask.tracks = SourceTracks{generateValidPlaybackTrackMetadata()};
+ invalidChannelMask.tracks[0].channelMask = "random string";
+ // Channel mask is sliced away by 'sourceMetadataToHal'
+ EXPECT_EQ(NO_ERROR, CoreUtils::sourceMetadataToHal(invalidChannelMask, nullptr));
+ EXPECT_EQ(BAD_VALUE, CoreUtils::sourceMetadataToHalV7(invalidChannelMask,
+ false /*ignoreNonVendorTags*/, nullptr));
+ EXPECT_EQ(BAD_VALUE, CoreUtils::sourceMetadataToHalV7(invalidChannelMask,
+ true /*ignoreNonVendorTags*/, nullptr));
+ SourceMetadata invalidTags;
+ invalidTags.tracks = SourceTracks{generateValidPlaybackTrackMetadata()};
+ invalidTags.tracks[0].tags[0] = "random string";
+ // Tags are sliced away by 'sourceMetadataToHal'
+ EXPECT_EQ(NO_ERROR, CoreUtils::sourceMetadataToHal(invalidTags, nullptr));
+ EXPECT_EQ(BAD_VALUE, CoreUtils::sourceMetadataToHalV7(invalidTags,
+ false /*ignoreNonVendorTags*/, nullptr));
+ // Non-vendor tags should be filtered out.
+ EXPECT_EQ(NO_ERROR,
+ CoreUtils::sourceMetadataToHalV7(invalidTags, true /*ignoreNonVendorTags*/, nullptr));
+
+ // Verify that a default-initialized metadata is valid.
+ std::vector<playback_track_metadata_t> halValid(1, playback_track_metadata_t{});
+ std::vector<playback_track_metadata_v7_t> halValidV7(1, playback_track_metadata_v7_t{});
+ SourceMetadata valid;
+ EXPECT_EQ(NO_ERROR, CoreUtils::sourceMetadataFromHal(halValid, &valid));
+ EXPECT_EQ(NO_ERROR, CoreUtils::sourceMetadataFromHalV7(halValidV7,
+ false /*ignoreNonVendorTags*/, &valid));
+ EXPECT_EQ(NO_ERROR,
+ CoreUtils::sourceMetadataFromHalV7(halValidV7, true /*ignoreNonVendorTags*/, &valid));
+
+ std::vector<playback_track_metadata_t> halInvalidUsage = {{.usage = kInvalidHalUsage}};
+ std::vector<playback_track_metadata_v7_t> halInvalidUsageV7 = {
+ {.base = {.usage = kInvalidHalUsage}}};
+ EXPECT_EQ(BAD_VALUE, CoreUtils::sourceMetadataFromHal(halInvalidUsage, &invalidUsage));
+ EXPECT_EQ(BAD_VALUE, CoreUtils::sourceMetadataFromHalV7(
+ halInvalidUsageV7, false /*ignoreNonVendorTags*/, &invalidUsage));
+ EXPECT_EQ(BAD_VALUE, CoreUtils::sourceMetadataFromHalV7(
+ halInvalidUsageV7, true /*ignoreNonVendorTags*/, &invalidUsage));
+ std::vector<playback_track_metadata_t> halInvalidContentType = {
+ {.content_type = kInvalidHalContentType}};
+ std::vector<playback_track_metadata_v7_t> halInvalidContentTypeV7 = {
+ {.base = {.content_type = kInvalidHalContentType}}};
+ EXPECT_EQ(BAD_VALUE,
+ CoreUtils::sourceMetadataFromHal(halInvalidContentType, &invalidContentType));
+ EXPECT_EQ(BAD_VALUE,
+ CoreUtils::sourceMetadataFromHalV7(
+ halInvalidContentTypeV7, false /*ignoreNonVendorTags*/, &invalidContentType));
+ EXPECT_EQ(BAD_VALUE,
+ CoreUtils::sourceMetadataFromHalV7(
+ halInvalidContentTypeV7, true /*ignoreNonVendorTags*/, &invalidContentType));
+ std::vector<playback_track_metadata_v7_t> halInvalidChannelMaskV7 = {
+ {.channel_mask = kInvalidHalChannelMask}};
+ EXPECT_EQ(BAD_VALUE,
+ CoreUtils::sourceMetadataFromHalV7(
+ halInvalidChannelMaskV7, false /*ignoreNonVendorTags*/, &invalidChannelMask));
+ EXPECT_EQ(BAD_VALUE,
+ CoreUtils::sourceMetadataFromHalV7(
+ halInvalidChannelMaskV7, true /*ignoreNonVendorTags*/, &invalidChannelMask));
+ std::vector<playback_track_metadata_v7_t> halInvalidTagsV7 = {{.tags = "random string"}};
+ EXPECT_EQ(BAD_VALUE, CoreUtils::sourceMetadataFromHalV7(
+ halInvalidTagsV7, false /*ignoreNonVendorTags*/, &invalidTags));
+ // Non-vendor tags should be filtered out.
+ EXPECT_EQ(NO_ERROR, CoreUtils::sourceMetadataFromHalV7(
+ halInvalidTagsV7, true /*ignoreNonVendorTags*/, &invalidTags));
+}
+
+TEST(CoreUtils, ConvertEmptySourceMetadata) {
+ SourceMetadata emptySourceMetadata;
+ std::vector<playback_track_metadata_t> halEmptySourceMetadata;
+ EXPECT_EQ(NO_ERROR,
+ CoreUtils::sourceMetadataToHal(emptySourceMetadata, &halEmptySourceMetadata));
+ EXPECT_TRUE(halEmptySourceMetadata.empty());
+ SourceMetadata emptySourceMetadataBack;
+ EXPECT_EQ(NO_ERROR,
+ CoreUtils::sourceMetadataFromHal(halEmptySourceMetadata, &emptySourceMetadataBack));
+ EXPECT_EQ(emptySourceMetadata, emptySourceMetadataBack);
+ std::vector<playback_track_metadata_v7_t> halEmptySourceMetadataV7;
+ EXPECT_EQ(NO_ERROR,
+ CoreUtils::sourceMetadataToHalV7(emptySourceMetadata, false /*ignoreNonVendorTags*/,
+ &halEmptySourceMetadataV7));
+ EXPECT_TRUE(halEmptySourceMetadataV7.empty());
+ SourceMetadata emptySourceMetadataBackFromV7;
+ EXPECT_EQ(NO_ERROR, CoreUtils::sourceMetadataFromHalV7(halEmptySourceMetadataV7,
+ false /*ignoreNonVendorTags*/,
+ &emptySourceMetadataBackFromV7));
+ EXPECT_EQ(emptySourceMetadata, emptySourceMetadataBackFromV7);
+}
+
+class SourceMetadataConvertTest : public ::testing::TestWithParam<SourceTracks> {};
+
+TEST_P(SourceMetadataConvertTest, ToFromHal) {
+ SourceMetadata sourceMetadata;
+ sourceMetadata.tracks = GetParam();
+ std::vector<playback_track_metadata_t> halSourceMetadata;
+ EXPECT_EQ(NO_ERROR, CoreUtils::sourceMetadataToHal(sourceMetadata, &halSourceMetadata));
+ EXPECT_EQ(sourceMetadata.tracks.size(), halSourceMetadata.size());
+ SourceMetadata sourceMetadataBackTrimmed;
+ EXPECT_EQ(NO_ERROR,
+ CoreUtils::sourceMetadataFromHal(halSourceMetadata, &sourceMetadataBackTrimmed));
+ // Can't compare 'sourceMetadata' to 'sourceMetadataBackTrimmed'
+ std::vector<playback_track_metadata_v7_t> halSourceMetadataV7;
+ EXPECT_EQ(NO_ERROR,
+ CoreUtils::sourceMetadataToHalV7(sourceMetadata, false /*ignoreNonVendorTags*/,
+ &halSourceMetadataV7));
+ EXPECT_EQ(sourceMetadata.tracks.size(), halSourceMetadataV7.size());
+ SourceMetadata sourceMetadataBackFromV7;
+ EXPECT_EQ(NO_ERROR,
+ CoreUtils::sourceMetadataFromHalV7(halSourceMetadataV7, false /*ignoreNonVendorTags*/,
+ &sourceMetadataBackFromV7));
+ EXPECT_EQ(sourceMetadata, sourceMetadataBackFromV7);
+ std::vector<playback_track_metadata_v7_t> halSourceMetadataV7FromTrimmed;
+ EXPECT_EQ(NO_ERROR, CoreUtils::sourceMetadataToHalV7(sourceMetadataBackTrimmed,
+ false /*ignoreNonVendorTags*/,
+ &halSourceMetadataV7FromTrimmed));
+ EXPECT_EQ(sourceMetadata.tracks.size(), halSourceMetadataV7FromTrimmed.size());
+ SourceMetadata sourceMetadataBackFromV7Trimmed;
+ EXPECT_EQ(NO_ERROR, CoreUtils::sourceMetadataFromHalV7(halSourceMetadataV7FromTrimmed,
+ false /*ignoreNonVendorTags*/,
+ &sourceMetadataBackFromV7Trimmed));
+ EXPECT_EQ(sourceMetadataBackTrimmed, sourceMetadataBackFromV7Trimmed);
+}
+
+INSTANTIATE_TEST_SUITE_P(ValidPlaybackTrackMetadatas, SourceMetadataConvertTest,
+ ::testing::Values(SourceTracks{generateMinimalPlaybackTrackMetadata()},
+ SourceTracks{generateValidPlaybackTrackMetadata()},
+ SourceTracks{generateMinimalPlaybackTrackMetadata(),
+ generateValidPlaybackTrackMetadata()}));
diff --git a/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp b/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp
index 9a4a8b2..f87e5ed 100644
--- a/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp
+++ b/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp
@@ -72,7 +72,10 @@
config.base.format = toString(xsd::AudioFormat::AUDIO_FORMAT_PCM_16_BIT);
hidl_vec<hidl_string> flags;
const SinkMetadata initMetadata = {
- {{.source = toString(xsd::AudioSource::AUDIO_SOURCE_MIC), .gain = 1}}};
+ {{.source = toString(xsd::AudioSource::AUDIO_SOURCE_MIC),
+ .gain = 1,
+ .tags = {},
+ .channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_IN_MONO)}}};
#endif
EventFlag* efGroup;
for (auto microphone : microphones) {
@@ -234,22 +237,14 @@
TEST_P(InputStreamTest, updateSinkMetadata) {
doc::test("The HAL should not crash on metadata change");
-
#if MAJOR_VERSION <= 6
hidl_enum_range<AudioSource> range;
-#elif MAJOR_VERSION >= 7
- xsdc_enum_range<android::audio::policy::configuration::V7_0::AudioSource> range;
-#endif
// Test all possible track configuration
for (auto source : range) {
for (float volume : {0.0, 0.5, 1.0}) {
-#if MAJOR_VERSION <= 6
const SinkMetadata metadata = {{{.source = source, .gain = volume}}};
-#elif MAJOR_VERSION >= 7
- const SinkMetadata metadata = {{{.source = toString(source), .gain = volume}}};
-#endif
ASSERT_OK(stream->updateSinkMetadata(metadata))
- << "source=" << toString(source) << ", volume=" << volume;
+ << "source=" << toString(source) << ", volume=" << volume;
}
}
@@ -257,9 +252,30 @@
// Set no metadata as if all stream track had stopped
ASSERT_OK(stream->updateSinkMetadata({}));
-
// Restore initial
ASSERT_OK(stream->updateSinkMetadata(initMetadata));
+
+#elif MAJOR_VERSION >= 7
+ xsdc_enum_range<android::audio::policy::configuration::V7_0::AudioSource> range;
+ // Test all possible track configuration
+ for (auto source : range) {
+ for (float volume : {0.0, 0.5, 1.0}) {
+ const SinkMetadata metadata = {
+ {{.source = toString(source),
+ .gain = volume,
+ .tags = {},
+ .channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_IN_MONO)}}};
+ ASSERT_RESULT(okOrNotSupported, stream->updateSinkMetadata(metadata))
+ << "source=" << toString(source) << ", volume=" << volume;
+ }
+ }
+ // Do not test concurrent capture as this is not officially supported
+
+ // Set no metadata as if all stream track had stopped
+ ASSERT_RESULT(okOrNotSupported, stream->updateSinkMetadata({}));
+ // Restore initial
+ ASSERT_RESULT(okOrNotSupported, stream->updateSinkMetadata(initMetadata));
+#endif
}
TEST_P(OutputStreamTest, SelectPresentation) {
@@ -269,56 +285,82 @@
TEST_P(OutputStreamTest, updateSourceMetadata) {
doc::test("The HAL should not crash on metadata change");
-
#if MAJOR_VERSION <= 6
hidl_enum_range<AudioUsage> usageRange;
hidl_enum_range<AudioContentType> contentRange;
-#elif MAJOR_VERSION >= 7
- xsdc_enum_range<android::audio::policy::configuration::V7_0::AudioUsage> usageRange;
- xsdc_enum_range<android::audio::policy::configuration::V7_0::AudioContentType> contentRange;
-#endif
// Test all possible track configuration
for (auto usage : usageRange) {
for (auto content : contentRange) {
for (float volume : {0.0, 0.5, 1.0}) {
-#if MAJOR_VERSION <= 6
const SourceMetadata metadata = {{{usage, content, volume}}};
-#elif MAJOR_VERSION >= 7
- const SourceMetadata metadata = {{{toString(usage), toString(content), volume}}};
-#endif
ASSERT_OK(stream->updateSourceMetadata(metadata))
<< "usage=" << toString(usage) << ", content=" << toString(content)
<< ", volume=" << volume;
}
}
}
-
- // clang-format off
// Set many track of different configuration
+ // clang-format off
ASSERT_OK(stream->updateSourceMetadata(
-#if MAJOR_VERSION <= 6
{{{AudioUsage::MEDIA, AudioContentType::MUSIC, 0.1},
{AudioUsage::VOICE_COMMUNICATION, AudioContentType::SPEECH, 1.0},
{AudioUsage::ALARM, AudioContentType::SONIFICATION, 0.0},
{AudioUsage::ASSISTANT, AudioContentType::UNKNOWN, 0.3}}}
-#elif MAJOR_VERSION >= 7
- {{{toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
- toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC), 0.1},
- {toString(xsd::AudioUsage::AUDIO_USAGE_VOICE_COMMUNICATION),
- toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_SPEECH), 1.0},
- {toString(xsd::AudioUsage::AUDIO_USAGE_ALARM),
- toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_SONIFICATION), 0.0},
- {toString(xsd::AudioUsage::AUDIO_USAGE_ASSISTANT),
- toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_UNKNOWN), 0.3}}}
-#endif
));
// clang-format on
-
// Set no metadata as if all stream track had stopped
ASSERT_OK(stream->updateSourceMetadata({}));
-
// Restore initial
ASSERT_OK(stream->updateSourceMetadata(initMetadata));
+#elif MAJOR_VERSION >= 7
+ xsdc_enum_range<android::audio::policy::configuration::V7_0::AudioUsage> usageRange;
+ xsdc_enum_range<android::audio::policy::configuration::V7_0::AudioContentType> contentRange;
+ // Test all possible track configuration
+ for (auto usage : usageRange) {
+ for (auto content : contentRange) {
+ for (float volume : {0.0, 0.5, 1.0}) {
+ const SourceMetadata metadata = {
+ {{toString(usage),
+ toString(content),
+ volume,
+ toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO),
+ {} /* tags */}}};
+ ASSERT_RESULT(okOrNotSupported, stream->updateSourceMetadata(metadata))
+ << "usage=" << toString(usage) << ", content=" << toString(content)
+ << ", volume=" << volume;
+ }
+ }
+ }
+ // Set many track of different configuration
+ // clang-format off
+ ASSERT_RESULT(okOrNotSupported, stream->updateSourceMetadata(
+ {{{toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
+ toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC),
+ 0.1, // gain
+ toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO),
+ {}}, // tags
+ {toString(xsd::AudioUsage::AUDIO_USAGE_VOICE_COMMUNICATION),
+ toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_SPEECH),
+ 1.0,
+ toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_MONO),
+ {}},
+ {toString(xsd::AudioUsage::AUDIO_USAGE_ALARM),
+ toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_SONIFICATION),
+ 0.0,
+ toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO),
+ {}},
+ {toString(xsd::AudioUsage::AUDIO_USAGE_ASSISTANT),
+ toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_UNKNOWN),
+ 0.3,
+ toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_MONO),
+ {}}}}
+ ));
+ // clang-format on
+ // Set no metadata as if all stream track had stopped
+ ASSERT_RESULT(okOrNotSupported, stream->updateSourceMetadata({}));
+ // Restore initial
+ ASSERT_RESULT(okOrNotSupported, stream->updateSourceMetadata(initMetadata));
+#endif
}
TEST_P(AudioPrimaryHidlTest, setMode) {
diff --git a/audio/core/all-versions/vts/functional/6.0/AudioPrimaryHidlHalTest.cpp b/audio/core/all-versions/vts/functional/6.0/AudioPrimaryHidlHalTest.cpp
index bd8de2d..0ebe4c2 100644
--- a/audio/core/all-versions/vts/functional/6.0/AudioPrimaryHidlHalTest.cpp
+++ b/audio/core/all-versions/vts/functional/6.0/AudioPrimaryHidlHalTest.cpp
@@ -18,137 +18,131 @@
#include "5.0/AudioPrimaryHidlHalTest.cpp"
#if MAJOR_VERSION <= 6
-const std::vector<DeviceConfigParameter>& getOutputDeviceConfigParameters() {
- static std::vector<DeviceConfigParameter> parameters = [] {
- std::vector<DeviceConfigParameter> result;
- for (const auto& device : getDeviceParameters()) {
- auto module =
- getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device));
- for (const auto& ioProfile : module->getOutputProfiles()) {
- for (const auto& profile : ioProfile->getAudioProfiles()) {
- const auto& channels = profile->getChannels();
- const auto& sampleRates = profile->getSampleRates();
- auto configs = ConfigHelper::combineAudioConfig(
- std::vector<audio_channel_mask_t>(channels.begin(), channels.end()),
- std::vector<uint32_t>(sampleRates.begin(), sampleRates.end()),
- profile->getFormat());
- auto flags = ioProfile->getFlags();
- for (auto& config : configs) {
- // Some combinations of flags declared in the config file require special
- // treatment.
- if (flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) {
- config.offloadInfo.sampleRateHz = config.sampleRateHz;
- config.offloadInfo.channelMask = config.channelMask;
- config.offloadInfo.format = config.format;
- config.offloadInfo.streamType = AudioStreamType::MUSIC;
- config.offloadInfo.bitRatePerSecond = 320;
- config.offloadInfo.durationMicroseconds = -1;
- config.offloadInfo.bitWidth = 16;
- config.offloadInfo.bufferSize = 256; // arbitrary value
- config.offloadInfo.usage = AudioUsage::MEDIA;
- result.emplace_back(device, config,
- AudioOutputFlag(AudioOutputFlag::COMPRESS_OFFLOAD |
- AudioOutputFlag::DIRECT));
- } else {
- if (flags & AUDIO_OUTPUT_FLAG_PRIMARY) { // ignore the flag
- flags &= ~AUDIO_OUTPUT_FLAG_PRIMARY;
- }
- result.emplace_back(device, config, AudioOutputFlag(flags));
+static std::vector<DeviceConfigParameter> generateOutputDeviceConfigParameters(
+ bool oneProfilePerDevice) {
+ std::vector<DeviceConfigParameter> result;
+ for (const auto& device : getDeviceParameters()) {
+ auto module =
+ getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device));
+ for (const auto& ioProfile : module->getOutputProfiles()) {
+ for (const auto& profile : ioProfile->getAudioProfiles()) {
+ const auto& channels = profile->getChannels();
+ const auto& sampleRates = profile->getSampleRates();
+ auto configs = ConfigHelper::combineAudioConfig(
+ std::vector<audio_channel_mask_t>(channels.begin(), channels.end()),
+ std::vector<uint32_t>(sampleRates.begin(), sampleRates.end()),
+ profile->getFormat());
+ auto flags = ioProfile->getFlags();
+ for (auto& config : configs) {
+ // Some combinations of flags declared in the config file require special
+ // treatment.
+ if (flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) {
+ config.offloadInfo.sampleRateHz = config.sampleRateHz;
+ config.offloadInfo.channelMask = config.channelMask;
+ config.offloadInfo.format = config.format;
+ config.offloadInfo.streamType = AudioStreamType::MUSIC;
+ config.offloadInfo.bitRatePerSecond = 320;
+ config.offloadInfo.durationMicroseconds = -1;
+ config.offloadInfo.bitWidth = 16;
+ config.offloadInfo.bufferSize = 256; // arbitrary value
+ config.offloadInfo.usage = AudioUsage::MEDIA;
+ result.emplace_back(device, config,
+ AudioOutputFlag(AudioOutputFlag::COMPRESS_OFFLOAD |
+ AudioOutputFlag::DIRECT));
+ } else {
+ if (flags & AUDIO_OUTPUT_FLAG_PRIMARY) { // ignore the flag
+ flags &= ~AUDIO_OUTPUT_FLAG_PRIMARY;
}
+ result.emplace_back(device, config, AudioOutputFlag(flags));
}
+ if (oneProfilePerDevice) break;
}
+ if (oneProfilePerDevice) break;
}
+ if (oneProfilePerDevice) break;
}
- return result;
- }();
+ }
+ return result;
+}
+
+const std::vector<DeviceConfigParameter>& getOutputDeviceConfigParameters() {
+ static std::vector<DeviceConfigParameter> parameters =
+ generateOutputDeviceConfigParameters(false);
return parameters;
}
-const std::vector<DeviceConfigParameter>& getInputDeviceConfigParameters() {
- static std::vector<DeviceConfigParameter> parameters = [] {
- std::vector<DeviceConfigParameter> result;
- for (const auto& device : getDeviceParameters()) {
- auto module =
- getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device));
- for (const auto& ioProfile : module->getInputProfiles()) {
- for (const auto& profile : ioProfile->getAudioProfiles()) {
- const auto& channels = profile->getChannels();
- const auto& sampleRates = profile->getSampleRates();
- auto configs = ConfigHelper::combineAudioConfig(
- std::vector<audio_channel_mask_t>(channels.begin(), channels.end()),
- std::vector<uint32_t>(sampleRates.begin(), sampleRates.end()),
- profile->getFormat());
- for (const auto& config : configs) {
- result.emplace_back(device, config, AudioInputFlag(ioProfile->getFlags()));
- }
+const std::vector<DeviceConfigParameter>& getOutputDeviceSingleConfigParameters() {
+ static std::vector<DeviceConfigParameter> parameters =
+ generateOutputDeviceConfigParameters(true);
+ return parameters;
+}
+
+static std::vector<DeviceConfigParameter> generateInputDeviceConfigParameters(
+ bool oneProfilePerDevice) {
+ std::vector<DeviceConfigParameter> result;
+ for (const auto& device : getDeviceParameters()) {
+ auto module =
+ getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device));
+ for (const auto& ioProfile : module->getInputProfiles()) {
+ for (const auto& profile : ioProfile->getAudioProfiles()) {
+ const auto& channels = profile->getChannels();
+ const auto& sampleRates = profile->getSampleRates();
+ auto configs = ConfigHelper::combineAudioConfig(
+ std::vector<audio_channel_mask_t>(channels.begin(), channels.end()),
+ std::vector<uint32_t>(sampleRates.begin(), sampleRates.end()),
+ profile->getFormat());
+ for (const auto& config : configs) {
+ result.emplace_back(device, config, AudioInputFlag(ioProfile->getFlags()));
+ if (oneProfilePerDevice) break;
}
+ if (oneProfilePerDevice) break;
}
+ if (oneProfilePerDevice) break;
}
- return result;
- }();
+ }
+ return result;
+}
+
+const std::vector<DeviceConfigParameter>& getInputDeviceConfigParameters() {
+ static std::vector<DeviceConfigParameter> parameters =
+ generateInputDeviceConfigParameters(false);
+ return parameters;
+}
+
+const std::vector<DeviceConfigParameter>& getInputDeviceSingleConfigParameters() {
+ static std::vector<DeviceConfigParameter> parameters =
+ generateInputDeviceConfigParameters(true);
return parameters;
}
#endif // MAJOR_VERSION <= 6
-TEST_P(AudioHidlDeviceTest, CloseDeviceWithOpenedOutputStreams) {
- doc::test("Verify that a device can't be closed if there are streams opened");
-#if MAJOR_VERSION <= 6
- DeviceAddress address{.device = AudioDevice::OUT_DEFAULT};
- SourceMetadata initMetadata = {{{AudioUsage::MEDIA, AudioContentType::MUSIC, 1 /* gain */}}};
- auto flags = hidl_bitfield<AudioOutputFlag>(AudioOutputFlag::NONE);
-#elif MAJOR_VERSION >= 7
- DeviceAddress address{.deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_OUT_DEFAULT)};
- SourceMetadata initMetadata = {
- {{toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
- toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC), 1 /* gain */}}};
- hidl_vec<AudioInOutFlag> flags;
-#endif
- AudioConfig config{};
- sp<IStreamOut> stream;
- StreamHelper<IStreamOut> helper(stream);
- AudioConfig suggestedConfig{};
- ASSERT_NO_FATAL_FAILURE(helper.open(
- [&](AudioIoHandle handle, AudioConfig config, auto cb) {
- return getDevice()->openOutputStream(handle, address, config, flags, initMetadata,
- cb);
- },
- config, &res, &suggestedConfig));
+class SingleConfigOutputStreamTest : public OutputStreamTest {};
+TEST_P(SingleConfigOutputStreamTest, CloseDeviceWithOpenedOutputStreams) {
+ doc::test("Verify that a device can't be closed if there are output streams opened");
+ // Opening of the stream is done in SetUp.
ASSERT_RESULT(Result::INVALID_STATE, getDevice()->close());
- ASSERT_NO_FATAL_FAILURE(helper.close(true /*clear*/, &res));
+ ASSERT_OK(closeStream(true /*clear*/));
ASSERT_OK(getDevice()->close());
ASSERT_TRUE(resetDevice());
}
+INSTANTIATE_TEST_CASE_P(SingleConfigOutputStream, SingleConfigOutputStreamTest,
+ ::testing::ValuesIn(getOutputDeviceSingleConfigParameters()),
+ &DeviceConfigParameterToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SingleConfigOutputStreamTest);
-TEST_P(AudioHidlDeviceTest, CloseDeviceWithOpenedInputStreams) {
- doc::test("Verify that a device can't be closed if there are streams opened");
- if (!getCachedPolicyConfig().haveInputProfilesInModule(getDeviceName())) {
- GTEST_SKIP() << "Device doesn't have input profiles";
- }
-#if MAJOR_VERSION <= 6
- DeviceAddress address{.device = AudioDevice::IN_DEFAULT};
- SinkMetadata initMetadata = {{{.source = AudioSource::MIC, .gain = 1}}};
- auto flags = hidl_bitfield<AudioInputFlag>(AudioInputFlag::NONE);
-#elif MAJOR_VERSION >= 7
- DeviceAddress address{.deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_IN_DEFAULT)};
- SinkMetadata initMetadata = {
- {{.source = toString(xsd::AudioSource::AUDIO_SOURCE_MIC), .gain = 1}}};
- hidl_vec<AudioInOutFlag> flags;
-#endif
- AudioConfig config{};
- sp<IStreamIn> stream;
- StreamHelper<IStreamIn> helper(stream);
- AudioConfig suggestedConfig{};
- ASSERT_NO_FATAL_FAILURE(helper.open(
- [&](AudioIoHandle handle, AudioConfig config, auto cb) {
- return getDevice()->openInputStream(handle, address, config, flags, initMetadata,
- cb);
- },
- config, &res, &suggestedConfig));
+class SingleConfigInputStreamTest : public InputStreamTest {};
+TEST_P(SingleConfigInputStreamTest, CloseDeviceWithOpenedInputStreams) {
+ doc::test("Verify that a device can't be closed if there are input streams opened");
+ // Opening of the stream is done in SetUp.
ASSERT_RESULT(Result::INVALID_STATE, getDevice()->close());
- ASSERT_NO_FATAL_FAILURE(helper.close(true /*clear*/, &res));
+ ASSERT_OK(closeStream(true /*clear*/));
ASSERT_OK(getDevice()->close());
ASSERT_TRUE(resetDevice());
}
+INSTANTIATE_TEST_CASE_P(SingleConfigInputStream, SingleConfigInputStreamTest,
+ ::testing::ValuesIn(getInputDeviceSingleConfigParameters()),
+ &DeviceConfigParameterToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SingleConfigInputStreamTest);
TEST_P(AudioPatchHidlTest, UpdatePatchInvalidHandle) {
doc::test("Verify that passing an invalid handle to updateAudioPatch is checked");
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 6bb7995..be1ffbb 100644
--- a/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp
+++ b/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp
@@ -25,7 +25,6 @@
for (auto channelMask : channelMasks) {
for (auto sampleRate : sampleRates) {
AudioConfig config{};
- // leave offloadInfo to 0
config.base.channelMask = toString(channelMask);
config.base.sampleRateHz = sampleRate;
config.base.format = format;
@@ -35,56 +34,158 @@
return configs;
}
+static std::tuple<std::vector<AudioInOutFlag>, bool> generateOutFlags(
+ const xsd::MixPorts::MixPort& mixPort) {
+ static const std::vector<AudioInOutFlag> offloadFlags = {
+ toString(xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD),
+ toString(xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_DIRECT)};
+ std::vector<AudioInOutFlag> flags;
+ bool isOffload = false;
+ if (mixPort.hasFlags()) {
+ auto xsdFlags = mixPort.getFlags();
+ isOffload = std::find(xsdFlags.begin(), xsdFlags.end(),
+ xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) !=
+ xsdFlags.end();
+ if (!isOffload) {
+ for (auto flag : xsdFlags) {
+ if (flag != xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_PRIMARY) {
+ flags.push_back(toString(flag));
+ }
+ }
+ } else {
+ flags = offloadFlags;
+ }
+ }
+ return {flags, isOffload};
+}
+
+static AudioOffloadInfo generateOffloadInfo(const AudioConfigBase& base) {
+ return AudioOffloadInfo{
+ .base = base,
+ .streamType = toString(xsd::AudioStreamType::AUDIO_STREAM_MUSIC),
+ .usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
+ .bitRatePerSecond = 320,
+ .durationMicroseconds = -1,
+ .bitWidth = 16,
+ .bufferSize = 256 // arbitrary value
+ };
+}
+
+static std::vector<DeviceConfigParameter> generateOutputDeviceConfigParameters(
+ bool oneProfilePerDevice) {
+ std::vector<DeviceConfigParameter> result;
+ for (const auto& device : getDeviceParameters()) {
+ auto module =
+ getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device));
+ if (!module || !module->getFirstMixPorts()) break;
+ for (const auto& mixPort : module->getFirstMixPorts()->getMixPort()) {
+ if (mixPort.getRole() != xsd::Role::source) continue; // not an output profile
+ auto [flags, isOffload] = generateOutFlags(mixPort);
+ for (const auto& profile : mixPort.getProfile()) {
+ auto configs = combineAudioConfig(profile.getChannelMasks(),
+ profile.getSamplingRates(), profile.getFormat());
+ for (auto& config : configs) {
+ // Some combinations of flags declared in the config file require special
+ // treatment.
+ if (isOffload) {
+ config.offloadInfo.info(generateOffloadInfo(config.base));
+ }
+ result.emplace_back(device, config, flags);
+ if (oneProfilePerDevice) break;
+ }
+ if (oneProfilePerDevice) break;
+ }
+ if (oneProfilePerDevice) break;
+ }
+ }
+ return result;
+}
+
const std::vector<DeviceConfigParameter>& getOutputDeviceConfigParameters() {
- static std::vector<DeviceConfigParameter> parameters = [] {
+ static std::vector<DeviceConfigParameter> parameters =
+ generateOutputDeviceConfigParameters(false);
+ return parameters;
+}
+
+const std::vector<DeviceConfigParameter>& getOutputDeviceSingleConfigParameters() {
+ static std::vector<DeviceConfigParameter> parameters =
+ generateOutputDeviceConfigParameters(true);
+ return parameters;
+}
+
+const std::vector<DeviceConfigParameter>& getOutputDeviceInvalidConfigParameters(
+ bool generateInvalidFlags = true) {
+ static std::vector<DeviceConfigParameter> parameters = [&] {
std::vector<DeviceConfigParameter> result;
- const std::vector<AudioInOutFlag> offloadFlags = {
- toString(xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD),
- toString(xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_DIRECT)};
for (const auto& device : getDeviceParameters()) {
auto module =
getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device));
if (!module || !module->getFirstMixPorts()) break;
+ bool hasRegularConfig = false, hasOffloadConfig = false;
for (const auto& mixPort : module->getFirstMixPorts()->getMixPort()) {
if (mixPort.getRole() != xsd::Role::source) continue; // not an output profile
- std::vector<AudioInOutFlag> flags;
- bool isOffload = false;
- if (mixPort.hasFlags()) {
- auto xsdFlags = mixPort.getFlags();
- isOffload =
- std::find(xsdFlags.begin(), xsdFlags.end(),
- xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) !=
- xsdFlags.end();
- if (!isOffload) {
- for (auto flag : xsdFlags) {
- if (flag != xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_PRIMARY) {
- flags.push_back(toString(flag));
- }
- }
- } else {
- flags = offloadFlags;
- }
- }
+ auto [validFlags, isOffload] = generateOutFlags(mixPort);
+ if ((!isOffload && hasRegularConfig) || (isOffload && hasOffloadConfig)) continue;
for (const auto& profile : mixPort.getProfile()) {
- auto configs =
- combineAudioConfig(profile.getChannelMasks(),
- profile.getSamplingRates(), profile.getFormat());
- for (auto& config : configs) {
- // Some combinations of flags declared in the config file require special
- // treatment.
+ if (!profile.hasFormat() || !profile.hasSamplingRates() ||
+ !profile.hasChannelMasks())
+ continue;
+ AudioConfigBase validBase = {
+ profile.getFormat(),
+ static_cast<uint32_t>(profile.getSamplingRates()[0]),
+ toString(profile.getChannelMasks()[0])};
+ {
+ AudioConfig config{.base = validBase};
+ config.base.channelMask = "random_string";
if (isOffload) {
- config.offloadInfo.base = config.base;
- config.offloadInfo.streamType =
- toString(xsd::AudioStreamType::AUDIO_STREAM_MUSIC);
- config.offloadInfo.usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA);
- config.offloadInfo.bitRatePerSecond = 320;
- config.offloadInfo.durationMicroseconds = -1;
- config.offloadInfo.bitWidth = 16;
- config.offloadInfo.bufferSize = 256; // arbitrary value
+ config.offloadInfo.info(generateOffloadInfo(validBase));
}
+ result.emplace_back(device, config, validFlags);
+ }
+ {
+ AudioConfig config{.base = validBase};
+ config.base.format = "random_string";
+ if (isOffload) {
+ config.offloadInfo.info(generateOffloadInfo(validBase));
+ }
+ result.emplace_back(device, config, validFlags);
+ }
+ if (generateInvalidFlags) {
+ AudioConfig config{.base = validBase};
+ if (isOffload) {
+ config.offloadInfo.info(generateOffloadInfo(validBase));
+ }
+ std::vector<AudioInOutFlag> flags = {"random_string", ""};
result.emplace_back(device, config, flags);
}
+ if (isOffload) {
+ {
+ AudioConfig config{.base = validBase};
+ config.offloadInfo.info(generateOffloadInfo(validBase));
+ config.offloadInfo.info().base.channelMask = "random_string";
+ }
+ {
+ AudioConfig config{.base = validBase};
+ config.offloadInfo.info(generateOffloadInfo(validBase));
+ config.offloadInfo.info().base.format = "random_string";
+ }
+ {
+ AudioConfig config{.base = validBase};
+ config.offloadInfo.info(generateOffloadInfo(validBase));
+ config.offloadInfo.info().streamType = "random_string";
+ }
+ {
+ AudioConfig config{.base = validBase};
+ config.offloadInfo.info(generateOffloadInfo(validBase));
+ config.offloadInfo.info().usage = "random_string";
+ }
+ hasOffloadConfig = true;
+ } else {
+ hasRegularConfig = true;
+ }
+ break;
}
+ if (hasOffloadConfig && hasRegularConfig) break;
}
}
return result;
@@ -92,32 +193,563 @@
return parameters;
}
+static std::vector<DeviceConfigParameter> generateInputDeviceConfigParameters(
+ bool oneProfilePerDevice) {
+ std::vector<DeviceConfigParameter> result;
+ for (const auto& device : getDeviceParameters()) {
+ auto module =
+ getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device));
+ if (!module || !module->getFirstMixPorts()) break;
+ for (const auto& mixPort : module->getFirstMixPorts()->getMixPort()) {
+ if (mixPort.getRole() != xsd::Role::sink) continue; // not an input profile
+ std::vector<AudioInOutFlag> flags;
+ if (mixPort.hasFlags()) {
+ std::transform(mixPort.getFlags().begin(), mixPort.getFlags().end(),
+ std::back_inserter(flags), [](auto flag) { return toString(flag); });
+ }
+ for (const auto& profile : mixPort.getProfile()) {
+ auto configs = combineAudioConfig(profile.getChannelMasks(),
+ profile.getSamplingRates(), profile.getFormat());
+ for (const auto& config : configs) {
+ result.emplace_back(device, config, flags);
+ if (oneProfilePerDevice) break;
+ }
+ if (oneProfilePerDevice) break;
+ }
+ if (oneProfilePerDevice) break;
+ }
+ }
+ return result;
+}
+
const std::vector<DeviceConfigParameter>& getInputDeviceConfigParameters() {
- static std::vector<DeviceConfigParameter> parameters = [] {
+ static std::vector<DeviceConfigParameter> parameters =
+ generateInputDeviceConfigParameters(false);
+ return parameters;
+}
+
+const std::vector<DeviceConfigParameter>& getInputDeviceSingleConfigParameters() {
+ static std::vector<DeviceConfigParameter> parameters =
+ generateInputDeviceConfigParameters(true);
+ return parameters;
+}
+
+const std::vector<DeviceConfigParameter>& getInputDeviceInvalidConfigParameters(
+ bool generateInvalidFlags = true) {
+ static std::vector<DeviceConfigParameter> parameters = [&] {
std::vector<DeviceConfigParameter> result;
for (const auto& device : getDeviceParameters()) {
auto module =
getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device));
if (!module || !module->getFirstMixPorts()) break;
+ bool hasConfig = false;
for (const auto& mixPort : module->getFirstMixPorts()->getMixPort()) {
if (mixPort.getRole() != xsd::Role::sink) continue; // not an input profile
- std::vector<AudioInOutFlag> flags;
+ std::vector<AudioInOutFlag> validFlags;
if (mixPort.hasFlags()) {
std::transform(mixPort.getFlags().begin(), mixPort.getFlags().end(),
- std::back_inserter(flags),
+ std::back_inserter(validFlags),
[](auto flag) { return toString(flag); });
}
for (const auto& profile : mixPort.getProfile()) {
- auto configs =
- combineAudioConfig(profile.getChannelMasks(),
- profile.getSamplingRates(), profile.getFormat());
- for (const auto& config : configs) {
+ if (!profile.hasFormat() || !profile.hasSamplingRates() ||
+ !profile.hasChannelMasks())
+ continue;
+ AudioConfigBase validBase = {
+ profile.getFormat(),
+ static_cast<uint32_t>(profile.getSamplingRates()[0]),
+ toString(profile.getChannelMasks()[0])};
+ {
+ AudioConfig config{.base = validBase};
+ config.base.channelMask = "random_string";
+ result.emplace_back(device, config, validFlags);
+ }
+ {
+ AudioConfig config{.base = validBase};
+ config.base.format = "random_string";
+ result.emplace_back(device, config, validFlags);
+ }
+ if (generateInvalidFlags) {
+ AudioConfig config{.base = validBase};
+ std::vector<AudioInOutFlag> flags = {"random_string", ""};
result.emplace_back(device, config, flags);
}
+ hasConfig = true;
+ break;
}
+ if (hasConfig) break;
}
}
return result;
}();
return parameters;
}
+
+class InvalidInputConfigNoFlagsTest : public AudioHidlTestWithDeviceConfigParameter {};
+TEST_P(InvalidInputConfigNoFlagsTest, InputBufferSizeTest) {
+ doc::test("Verify that invalid config is rejected by IDevice::getInputBufferSize method.");
+ uint64_t bufferSize;
+ ASSERT_OK(getDevice()->getInputBufferSize(getConfig(), returnIn(res, bufferSize)));
+ EXPECT_EQ(Result::INVALID_ARGUMENTS, res);
+}
+INSTANTIATE_TEST_CASE_P(
+ InputBufferSizeInvalidConfig, InvalidInputConfigNoFlagsTest,
+ ::testing::ValuesIn(getInputDeviceInvalidConfigParameters(false /*generateInvalidFlags*/)),
+ &DeviceConfigParameterToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(InvalidInputConfigNoFlagsTest);
+
+static const DeviceAddress& getValidInputDeviceAddress() {
+ static const DeviceAddress valid = {
+ .deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_IN_DEFAULT)};
+ return valid;
+}
+
+static const DeviceAddress& getValidOutputDeviceAddress() {
+ static const DeviceAddress valid = {
+ .deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_OUT_DEFAULT)};
+ return valid;
+}
+
+static const DeviceAddress& getInvalidDeviceAddress() {
+ static const DeviceAddress valid = {.deviceType = "random_string"};
+ return valid;
+}
+
+TEST_P(AudioHidlDeviceTest, SetConnectedStateInvalidDeviceAddress) {
+ doc::test("Check that invalid device address is rejected by IDevice::setConnectedState");
+ EXPECT_RESULT(Result::INVALID_ARGUMENTS,
+ getDevice()->setConnectedState(getInvalidDeviceAddress(), true));
+ EXPECT_RESULT(Result::INVALID_ARGUMENTS,
+ getDevice()->setConnectedState(getInvalidDeviceAddress(), false));
+}
+
+static std::vector<AudioPortConfig>& generatePortConfigs(bool valid) {
+ enum { // Note: This is for convenience when deriving "invalid" configs from "valid".
+ PORT_CONF_MINIMAL,
+ PORT_CONF_WITH_GAIN,
+ PORT_CONF_EXT_DEVICE,
+ PORT_CONF_EXT_MIX_SOURCE,
+ PORT_CONF_EXT_MIX_SINK,
+ PORT_CONF_EXT_SESSION
+ };
+ static std::vector<AudioPortConfig> valids = [] {
+ std::vector<AudioPortConfig> result;
+ result.reserve(PORT_CONF_EXT_SESSION + 1);
+ result.push_back(AudioPortConfig{});
+ AudioPortConfig configWithGain{};
+ configWithGain.gain.config(AudioGainConfig{
+ .index = 0,
+ .mode = {toString(xsd::AudioGainMode::AUDIO_GAIN_MODE_JOINT)},
+ .channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_MONO),
+ .rampDurationMs = 1});
+ configWithGain.gain.config().values.resize(1);
+ configWithGain.gain.config().values[0] = 1000;
+ result.push_back(std::move(configWithGain));
+ AudioPortConfig configWithPortExtDevice{};
+ configWithPortExtDevice.ext.device(getValidOutputDeviceAddress());
+ result.push_back(std::move(configWithPortExtDevice));
+ AudioPortConfig configWithPortExtMixSource{};
+ configWithPortExtMixSource.ext.mix({});
+ configWithPortExtMixSource.ext.mix().useCase.stream(
+ toString(xsd::AudioStreamType::AUDIO_STREAM_VOICE_CALL));
+ result.push_back(std::move(configWithPortExtMixSource));
+ AudioPortConfig configWithPortExtMixSink{};
+ configWithPortExtMixSink.ext.mix({});
+ configWithPortExtMixSink.ext.mix().useCase.source(
+ toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT));
+ result.push_back(std::move(configWithPortExtMixSink));
+ AudioPortConfig configWithPortExtSession{};
+ configWithPortExtSession.ext.session(
+ static_cast<AudioSession>(AudioSessionConsts::OUTPUT_MIX));
+ result.push_back(std::move(configWithPortExtSession));
+ return result;
+ }();
+ static std::vector<AudioPortConfig> invalids = [&] {
+ std::vector<AudioPortConfig> result;
+ AudioPortConfig invalidBaseChannelMask = valids[PORT_CONF_MINIMAL];
+ invalidBaseChannelMask.base.channelMask.value("random_string");
+ result.push_back(std::move(invalidBaseChannelMask));
+ AudioPortConfig invalidBaseFormat = valids[PORT_CONF_MINIMAL];
+ invalidBaseFormat.base.format.value("random_string");
+ result.push_back(std::move(invalidBaseFormat));
+ AudioPortConfig invalidGainMode = valids[PORT_CONF_WITH_GAIN];
+ invalidGainMode.gain.config().mode = {{"random_string"}};
+ result.push_back(std::move(invalidGainMode));
+ AudioPortConfig invalidGainChannelMask = valids[PORT_CONF_WITH_GAIN];
+ invalidGainChannelMask.gain.config().channelMask = "random_string";
+ result.push_back(std::move(invalidGainChannelMask));
+ AudioPortConfig invalidDeviceType = valids[PORT_CONF_EXT_DEVICE];
+ invalidDeviceType.ext.device().deviceType = "random_string";
+ result.push_back(std::move(invalidDeviceType));
+ AudioPortConfig invalidStreamType = valids[PORT_CONF_EXT_MIX_SOURCE];
+ invalidStreamType.ext.mix().useCase.stream() = "random_string";
+ result.push_back(std::move(invalidStreamType));
+ AudioPortConfig invalidSource = valids[PORT_CONF_EXT_MIX_SINK];
+ invalidSource.ext.mix().useCase.source() = "random_string";
+ result.push_back(std::move(invalidSource));
+ return result;
+ }();
+ return valid ? valids : invalids;
+}
+
+TEST_P(AudioHidlDeviceTest, SetAudioPortConfigInvalidArguments) {
+ doc::test("Check that invalid port configs are rejected by IDevice::setAudioPortConfig");
+ for (const auto& invalidConfig : generatePortConfigs(false /*valid*/)) {
+ EXPECT_RESULT(invalidArgsOrNotSupported, getDevice()->setAudioPortConfig(invalidConfig))
+ << ::testing::PrintToString(invalidConfig);
+ }
+}
+
+TEST_P(AudioPatchHidlTest, CreatePatchInvalidArguments) {
+ doc::test("Check that invalid port configs are rejected by IDevice::createAudioPatch");
+ // Note that HAL actually might reject the proposed source / sink combo
+ // due to other reasons than presence of invalid enum-strings.
+ // TODO: Come up with a way to guarantee validity of a source / sink combo.
+ for (const auto& validSource : generatePortConfigs(true /*valid*/)) {
+ for (const auto& invalidSink : generatePortConfigs(false /*valid*/)) {
+ AudioPatchHandle handle;
+ EXPECT_OK(getDevice()->createAudioPatch(hidl_vec<AudioPortConfig>{validSource},
+ hidl_vec<AudioPortConfig>{invalidSink},
+ returnIn(res, handle)));
+ EXPECT_EQ(Result::INVALID_ARGUMENTS, res)
+ << "Source: " << ::testing::PrintToString(validSource)
+ << "; Sink: " << ::testing::PrintToString(invalidSink);
+ }
+ }
+ for (const auto& validSink : generatePortConfigs(true /*valid*/)) {
+ for (const auto& invalidSource : generatePortConfigs(false /*valid*/)) {
+ AudioPatchHandle handle;
+ EXPECT_OK(getDevice()->createAudioPatch(hidl_vec<AudioPortConfig>{invalidSource},
+ hidl_vec<AudioPortConfig>{validSink},
+ returnIn(res, handle)));
+ EXPECT_EQ(Result::INVALID_ARGUMENTS, res)
+ << "Source: " << ::testing::PrintToString(invalidSource)
+ << "; Sink: " << ::testing::PrintToString(validSink);
+ }
+ }
+}
+
+TEST_P(AudioPatchHidlTest, UpdatePatchInvalidArguments) {
+ doc::test("Check that invalid port configs are rejected by IDevice::updateAudioPatch");
+ // Note that HAL actually might reject the proposed source / sink combo
+ // due to other reasons than presence of invalid enum-strings.
+ // TODO: Come up with a way to guarantee validity of a source / sink combo.
+ for (const auto& validSource : generatePortConfigs(true /*valid*/)) {
+ for (const auto& invalidSink : generatePortConfigs(false /*valid*/)) {
+ AudioPatchHandle handle{};
+ EXPECT_OK(getDevice()->updateAudioPatch(handle, hidl_vec<AudioPortConfig>{validSource},
+ hidl_vec<AudioPortConfig>{invalidSink},
+ returnIn(res, handle)));
+ EXPECT_EQ(Result::INVALID_ARGUMENTS, res)
+ << "Source: " << ::testing::PrintToString(validSource)
+ << "; Sink: " << ::testing::PrintToString(invalidSink);
+ }
+ }
+ for (const auto& validSink : generatePortConfigs(true /*valid*/)) {
+ for (const auto& invalidSource : generatePortConfigs(false /*valid*/)) {
+ AudioPatchHandle handle{};
+ EXPECT_OK(getDevice()->updateAudioPatch(
+ handle, hidl_vec<AudioPortConfig>{invalidSource},
+ hidl_vec<AudioPortConfig>{validSink}, returnIn(res, handle)));
+ EXPECT_EQ(Result::INVALID_ARGUMENTS, res)
+ << "Source: " << ::testing::PrintToString(invalidSource)
+ << "; Sink: " << ::testing::PrintToString(validSink);
+ }
+ }
+}
+
+enum { PARAM_DEVICE_CONFIG, PARAM_ADDRESS, PARAM_METADATA };
+enum { INDEX_SINK, INDEX_SOURCE };
+using SinkOrSourceMetadata = std::variant<SinkMetadata, SourceMetadata>;
+using StreamOpenParameter = std::tuple<DeviceConfigParameter, DeviceAddress, SinkOrSourceMetadata>;
+
+static std::string StreamOpenParameterToString(
+ const ::testing::TestParamInfo<StreamOpenParameter>& info) {
+ return DeviceConfigParameterToString(::testing::TestParamInfo<DeviceConfigParameter>{
+ std::get<PARAM_DEVICE_CONFIG>(info.param), info.index}) +
+ "__" +
+ SanitizeStringForGTestName(
+ ::testing::PrintToString(std::get<PARAM_ADDRESS>(info.param))) +
+ "__" +
+ SanitizeStringForGTestName(std::visit(
+ [](auto&& arg) -> std::string { return ::testing::PrintToString(arg); },
+ std::get<PARAM_METADATA>(info.param)));
+}
+
+class StreamOpenTest : public HidlTest, public ::testing::WithParamInterface<StreamOpenParameter> {
+ protected:
+ void SetUp() override {
+ ASSERT_NO_FATAL_FAILURE(HidlTest::SetUp()); // setup base
+ ASSERT_TRUE(getDevicesFactory() != nullptr);
+ ASSERT_TRUE(getDevice() != nullptr);
+ }
+ const std::string& getFactoryName() const override {
+ return std::get<PARAM_FACTORY_NAME>(
+ std::get<PARAM_DEVICE>(std::get<PARAM_DEVICE_CONFIG>(GetParam())));
+ }
+ const std::string& getDeviceName() const override {
+ return std::get<PARAM_DEVICE_NAME>(
+ std::get<PARAM_DEVICE>(std::get<PARAM_DEVICE_CONFIG>(GetParam())));
+ }
+ const AudioConfig& getConfig() const {
+ return std::get<PARAM_CONFIG>(std::get<PARAM_DEVICE_CONFIG>(GetParam()));
+ }
+ const hidl_vec<AudioInOutFlag> getFlags() const {
+ return std::get<PARAM_FLAGS>(std::get<PARAM_DEVICE_CONFIG>(GetParam()));
+ }
+ const DeviceAddress& getDeviceAddress() const { return std::get<PARAM_ADDRESS>(GetParam()); }
+ bool isParamForInputStream() const {
+ return std::get<PARAM_METADATA>(GetParam()).index() == INDEX_SINK;
+ }
+ const SinkMetadata& getSinkMetadata() const {
+ return std::get<INDEX_SINK>(std::get<PARAM_METADATA>(GetParam()));
+ }
+ const SourceMetadata& getSourceMetadata() const {
+ return std::get<INDEX_SOURCE>(std::get<PARAM_METADATA>(GetParam()));
+ }
+};
+
+static const RecordTrackMetadata& getValidRecordTrackMetadata() {
+ static const RecordTrackMetadata valid = {
+ .source = toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT), .gain = 1};
+ return valid;
+}
+
+static const RecordTrackMetadata& getValidRecordTrackMetadataWithDest() {
+ static const RecordTrackMetadata valid = {
+ .source = toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT),
+ .gain = 1,
+ .destination = [] {
+ RecordTrackMetadata::Destination dest;
+ dest.device(getValidOutputDeviceAddress());
+ return dest;
+ }()};
+ return valid;
+}
+
+static const RecordTrackMetadata& getInvalidSourceRecordTrackMetadata() {
+ static const RecordTrackMetadata invalid = {.source = "random_string", .gain = 1};
+ return invalid;
+}
+
+static const RecordTrackMetadata& getRecordTrackMetadataWithInvalidDest() {
+ static const RecordTrackMetadata invalid = {
+ .source = toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT),
+ .gain = 1,
+ .destination = [] {
+ RecordTrackMetadata::Destination dest;
+ dest.device(getInvalidDeviceAddress());
+ return dest;
+ }()};
+ return invalid;
+}
+
+static const RecordTrackMetadata& getInvalidChannelMaskRecordTrackMetadata() {
+ static const RecordTrackMetadata invalid = {
+ .source = toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT),
+ .gain = 1,
+ .channelMask = "random_string"};
+ return invalid;
+}
+
+static const RecordTrackMetadata& getInvalidTagsRecordTrackMetadata() {
+ static const RecordTrackMetadata invalid = {
+ .source = toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT),
+ .gain = 1,
+ .tags = {{"random_string"}}};
+ return invalid;
+}
+
+static const PlaybackTrackMetadata& getValidPlaybackTrackMetadata() {
+ static const PlaybackTrackMetadata valid = {
+ .usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
+ .contentType = toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC),
+ .gain = 1};
+ return valid;
+}
+
+static const PlaybackTrackMetadata& getInvalidUsagePlaybackTrackMetadata() {
+ static const PlaybackTrackMetadata invalid = {
+ .usage = "random_string",
+ .contentType = toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC),
+ .gain = 1};
+ return invalid;
+}
+
+static const PlaybackTrackMetadata& getInvalidContentTypePlaybackTrackMetadata() {
+ static const PlaybackTrackMetadata invalid = {
+ .usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
+ .contentType = "random_string",
+ .gain = 1};
+ return invalid;
+}
+
+static const PlaybackTrackMetadata& getInvalidChannelMaskPlaybackTrackMetadata() {
+ static const PlaybackTrackMetadata invalid = {
+ .usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
+ .contentType = toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC),
+ .gain = 1,
+ .channelMask = "random_string"};
+ return invalid;
+}
+
+static const PlaybackTrackMetadata& getInvalidTagsPlaybackTrackMetadata() {
+ static const PlaybackTrackMetadata invalid = {
+ .usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
+ .contentType = toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC),
+ .gain = 1,
+ .tags = {{"random_string"}}};
+ return invalid;
+}
+
+static const std::vector<SourceMetadata>& getInvalidSourceMetadatas() {
+ static const std::vector<SourceMetadata> invalids = {
+ SourceMetadata{.tracks = {{getInvalidUsagePlaybackTrackMetadata()}}},
+ SourceMetadata{.tracks = {{getInvalidContentTypePlaybackTrackMetadata()}}},
+ SourceMetadata{.tracks = {{getInvalidChannelMaskPlaybackTrackMetadata()}}},
+ SourceMetadata{.tracks = {{getInvalidTagsPlaybackTrackMetadata()}}},
+ SourceMetadata{.tracks = {{getValidPlaybackTrackMetadata(),
+ getInvalidUsagePlaybackTrackMetadata()}}},
+ SourceMetadata{.tracks = {{getValidPlaybackTrackMetadata(),
+ getInvalidContentTypePlaybackTrackMetadata()}}},
+ SourceMetadata{.tracks = {{getValidPlaybackTrackMetadata(),
+ getInvalidChannelMaskPlaybackTrackMetadata()}}},
+ SourceMetadata{.tracks = {{getValidPlaybackTrackMetadata(),
+ getInvalidTagsPlaybackTrackMetadata()}}}};
+ return invalids;
+}
+static const std::vector<SinkMetadata>& getInvalidSinkMetadatas() {
+ static const std::vector<SinkMetadata> invalids = {
+ SinkMetadata{.tracks = {{getInvalidSourceRecordTrackMetadata()}}},
+ SinkMetadata{.tracks = {{getRecordTrackMetadataWithInvalidDest()}}},
+ SinkMetadata{.tracks = {{getInvalidChannelMaskRecordTrackMetadata()}}},
+ SinkMetadata{.tracks = {{getInvalidTagsRecordTrackMetadata()}}},
+ SinkMetadata{.tracks = {{getValidRecordTrackMetadata(),
+ getInvalidSourceRecordTrackMetadata()}}},
+ SinkMetadata{.tracks = {{getValidRecordTrackMetadata(),
+ getRecordTrackMetadataWithInvalidDest()}}},
+ SinkMetadata{.tracks = {{getValidRecordTrackMetadata(),
+ getInvalidChannelMaskRecordTrackMetadata()}}},
+ SinkMetadata{.tracks = {{getValidRecordTrackMetadata(),
+ getInvalidTagsRecordTrackMetadata()}}}};
+ return invalids;
+}
+template <typename T>
+static inline std::vector<SinkOrSourceMetadata> wrapMetadata(const std::vector<T>& metadata) {
+ return std::vector<SinkOrSourceMetadata>{metadata.begin(), metadata.end()};
+}
+
+TEST_P(StreamOpenTest, OpenInputOrOutputStreamTest) {
+ doc::test(
+ "Verify that invalid arguments are rejected by "
+ "IDevice::open{Input|Output}Stream method.");
+ AudioConfig suggestedConfig{};
+ if (isParamForInputStream()) {
+ sp<IStreamIn> stream;
+ ASSERT_OK(getDevice()->openInputStream(AudioIoHandle{}, getDeviceAddress(), getConfig(),
+ getFlags(), getSinkMetadata(),
+ returnIn(res, stream, suggestedConfig)));
+ ASSERT_TRUE(stream == nullptr);
+ } else {
+ sp<IStreamOut> stream;
+ ASSERT_OK(getDevice()->openOutputStream(AudioIoHandle{}, getDeviceAddress(), getConfig(),
+ getFlags(), getSourceMetadata(),
+ returnIn(res, stream, suggestedConfig)));
+ ASSERT_TRUE(stream == nullptr);
+ }
+ EXPECT_EQ(Result::INVALID_ARGUMENTS, res);
+ EXPECT_EQ(AudioConfig{}, suggestedConfig);
+}
+INSTANTIATE_TEST_CASE_P(
+ InputStreamInvalidConfig, StreamOpenTest,
+ ::testing::Combine(::testing::ValuesIn(getInputDeviceInvalidConfigParameters()),
+ ::testing::Values(getValidInputDeviceAddress()),
+ ::testing::Values(SinkMetadata{
+ .tracks = {{getValidRecordTrackMetadata(),
+ getValidRecordTrackMetadataWithDest()}}})),
+ &StreamOpenParameterToString);
+INSTANTIATE_TEST_CASE_P(
+ InputStreamInvalidAddress, StreamOpenTest,
+ ::testing::Combine(::testing::ValuesIn(getInputDeviceSingleConfigParameters()),
+ ::testing::Values(getInvalidDeviceAddress()),
+ ::testing::Values(SinkMetadata{
+ .tracks = {{getValidRecordTrackMetadata(),
+ getValidRecordTrackMetadataWithDest()}}})),
+ &StreamOpenParameterToString);
+INSTANTIATE_TEST_CASE_P(
+ InputStreamInvalidMetadata, StreamOpenTest,
+ ::testing::Combine(::testing::ValuesIn(getInputDeviceSingleConfigParameters()),
+ ::testing::Values(getValidInputDeviceAddress()),
+ ::testing::ValuesIn(wrapMetadata(getInvalidSinkMetadatas()))),
+ &StreamOpenParameterToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(StreamOpenTest);
+
+INSTANTIATE_TEST_CASE_P(
+ OutputStreamInvalidConfig, StreamOpenTest,
+ ::testing::Combine(::testing::ValuesIn(getOutputDeviceInvalidConfigParameters()),
+ ::testing::Values(getValidOutputDeviceAddress()),
+ ::testing::Values(SourceMetadata{
+ .tracks = {{getValidPlaybackTrackMetadata()}}})),
+ &StreamOpenParameterToString);
+INSTANTIATE_TEST_CASE_P(
+ OutputStreamInvalidAddress, StreamOpenTest,
+ ::testing::Combine(::testing::ValuesIn(getOutputDeviceSingleConfigParameters()),
+ ::testing::Values(getInvalidDeviceAddress()),
+ ::testing::Values(SourceMetadata{
+ .tracks = {{getValidPlaybackTrackMetadata()}}})),
+ &StreamOpenParameterToString);
+INSTANTIATE_TEST_CASE_P(
+ OutputStreamInvalidMetadata, StreamOpenTest,
+ ::testing::Combine(::testing::ValuesIn(getOutputDeviceSingleConfigParameters()),
+ ::testing::Values(getValidOutputDeviceAddress()),
+ ::testing::ValuesIn(wrapMetadata(getInvalidSourceMetadatas()))),
+ &StreamOpenParameterToString);
+
+#define TEST_SINGLE_CONFIG_IO_STREAM(test_name, documentation, code) \
+ TEST_P(SingleConfigInputStreamTest, test_name) { \
+ doc::test(documentation); \
+ code; \
+ } \
+ TEST_P(SingleConfigOutputStreamTest, test_name) { \
+ doc::test(documentation); \
+ code; \
+ }
+
+static void testSetDevicesInvalidDeviceAddress(IStream* stream) {
+ ASSERT_RESULT(Result::INVALID_ARGUMENTS, stream->setDevices({getInvalidDeviceAddress()}));
+}
+TEST_SINGLE_CONFIG_IO_STREAM(
+ SetDevicesInvalidDeviceAddress,
+ "Verify that invalid device address is rejected by IStream::setDevices",
+ areAudioPatchesSupported() ? doc::partialTest("Audio patches are supported")
+ : testSetDevicesInvalidDeviceAddress(stream.get()));
+
+static void testSetAudioPropertiesInvalidArguments(IStream* stream) {
+ AudioConfigBaseOptional invalidFormat;
+ invalidFormat.format.value("random_string");
+ ASSERT_RESULT(invalidArgsOrNotSupported, stream->setAudioProperties(invalidFormat));
+
+ AudioConfigBaseOptional invalidChannelMask;
+ invalidChannelMask.channelMask.value("random_string");
+ ASSERT_RESULT(invalidArgsOrNotSupported, stream->setAudioProperties(invalidChannelMask));
+}
+TEST_SINGLE_CONFIG_IO_STREAM(
+ SetAudioPropertiesInvalidArguments,
+ "Verify that invalid arguments are rejected by IStream::setAudioProperties",
+ testSetAudioPropertiesInvalidArguments(stream.get()));
+
+TEST_P(SingleConfigOutputStreamTest, UpdateInvalidSourceMetadata) {
+ doc::test("Verify that invalid metadata is rejected by IStreamOut::updateSourceMetadata");
+ for (const auto& metadata : getInvalidSourceMetadatas()) {
+ ASSERT_RESULT(invalidArgsOrNotSupported, stream->updateSourceMetadata(metadata))
+ << ::testing::PrintToString(metadata);
+ }
+}
+
+TEST_P(SingleConfigInputStreamTest, UpdateInvalidSinkMetadata) {
+ doc::test("Verify that invalid metadata is rejected by IStreamIn::updateSinkMetadata");
+ for (const auto& metadata : getInvalidSinkMetadatas()) {
+ ASSERT_RESULT(invalidArgsOrNotSupported, stream->updateSinkMetadata(metadata))
+ << ::testing::PrintToString(metadata);
+ }
+}
diff --git a/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h b/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h
index 43c44cb..61e99e8 100644
--- a/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h
+++ b/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h
@@ -522,7 +522,9 @@
#if MAJOR_VERSION >= 6
const std::vector<DeviceConfigParameter>& getInputDeviceConfigParameters();
+const std::vector<DeviceConfigParameter>& getInputDeviceSingleConfigParameters();
const std::vector<DeviceConfigParameter>& getOutputDeviceConfigParameters();
+const std::vector<DeviceConfigParameter>& getOutputDeviceSingleConfigParameters();
#endif
#if MAJOR_VERSION >= 4
@@ -883,7 +885,7 @@
AudioHidlTestWithDeviceConfigParameter::TearDown();
}
- protected:
+ protected:
AudioConfig audioConfig;
DeviceAddress address = {};
sp<Stream> stream;
@@ -926,7 +928,9 @@
const SourceMetadata initMetadata = {
{ { toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC),
- 1 /* gain */ } }};
+ 1 /* gain */,
+ toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO),
+ {} } }};
#endif
};
TEST_P(OutputStreamTest, OpenOutputStreamTest) {
@@ -971,7 +975,7 @@
ASSERT_NO_FATAL_FAILURE(OpenStreamTest::SetUp()); // setup base
#if MAJOR_VERSION <= 6
address.device = AudioDevice::IN_DEFAULT;
-#elif MAJOR_VERSION <= 7
+#elif MAJOR_VERSION >= 7
address.deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_IN_DEFAULT);
#endif
const AudioConfig& config = getConfig();
@@ -986,12 +990,15 @@
protected:
#if MAJOR_VERSION == 2
- const AudioSource initMetadata = AudioSource::DEFAULT;
+ const AudioSource initMetadata = AudioSource::DEFAULT;
#elif MAJOR_VERSION >= 4 && MAJOR_VERSION <= 6
const SinkMetadata initMetadata = {{ {.source = AudioSource::DEFAULT, .gain = 1 } }};
#elif MAJOR_VERSION >= 7
const SinkMetadata initMetadata = {
- {{.source = toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT), .gain = 1}}};
+ {{.source = toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT),
+ .gain = 1,
+ .tags = {},
+ .channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_IN_MONO)}}};
#endif
};
@@ -1148,13 +1155,14 @@
for (const auto& profile : profiles) {
for (const auto& sampleRate : profile.sampleRates) {
for (const auto& channelMask : profile.channelMasks) {
- AudioConfigBase config{.format = profile.format,
- .sampleRateHz = sampleRate,
- .channelMask = {{channelMask}}};
+ AudioConfigBaseOptional config;
+ config.format.value(profile.format);
+ config.sampleRateHz.value(sampleRate);
+ config.channelMask.value(channelMask);
auto ret = stream->setAudioProperties(config);
EXPECT_TRUE(ret.isOk());
- EXPECT_EQ(Result::OK, ret) << config.format << "; " << config.sampleRateHz << "; "
- << toString(config.channelMask);
+ EXPECT_EQ(Result::OK, ret)
+ << profile.format << "; " << sampleRate << "; " << channelMask;
}
}
}
@@ -1162,7 +1170,7 @@
TEST_IO_STREAM(SetAudioProperties, "Call setAudioProperties for all supported profiles",
testSetAudioProperties(stream.get()))
-#endif
+#endif // MAJOR_VERSION <= 6
static void testGetAudioProperties(IStream* stream, AudioConfig expectedConfig) {
#if MAJOR_VERSION <= 6
diff --git a/audio/effect/7.0/types.hal b/audio/effect/7.0/types.hal
index c4cb213..bb2d7b3 100644
--- a/audio/effect/7.0/types.hal
+++ b/audio/effect/7.0/types.hal
@@ -17,6 +17,7 @@
package android.hardware.audio.effect@7.0;
import android.hardware.audio.common@7.0;
+import android.hidl.safe_union@1.0;
enum Result : int32_t {
OK,
@@ -248,32 +249,19 @@
};
/**
- * Determines what fields of EffectBufferConfig need to be considered.
- */
-@export(name="", value_prefix="EFFECT_CONFIG_")
-enum EffectConfigParameters : int32_t {
- /** Buffer field. */
- BUFFER = 0x0001,
- /** Sampling rate. */
- SMP_RATE = 0x0002,
- /** Channels. */
- CHANNELS = 0x0004,
- /** Format. */
- FORMAT = 0x0008,
- /** Access mode. */
- ACC_MODE = 0x0010,
- // Note that the 2.0 ALL have been moved to an helper function
-};
-
-/**
* The buffer config structure specifies the input or output audio format
* to be used by the effect engine.
*/
struct EffectBufferConfig {
- AudioBuffer buffer;
- AudioConfigBase base;
- EffectBufferAccess accessMode;
- bitfield<EffectConfigParameters> mask;
+ safe_union OptionalBuffer {
+ Monostate unspecified;
+ AudioBuffer buf;
+ } buffer;
+ AudioConfigBaseOptional base;
+ safe_union OptionalAccessMode {
+ Monostate unspecified;
+ EffectBufferAccess value;
+ } accessMode;
};
struct EffectConfig {
diff --git a/audio/effect/all-versions/default/Android.bp b/audio/effect/all-versions/default/Android.bp
index a0cd612..99b1120 100644
--- a/audio/effect/all-versions/default/Android.bp
+++ b/audio/effect/all-versions/default/Android.bp
@@ -8,7 +8,6 @@
"AudioBufferManager.cpp",
"AutomaticGainControlEffect.cpp",
"BassBoostEffect.cpp",
- "Conversions.cpp",
"DownmixEffect.cpp",
"Effect.cpp",
"EffectsFactory.cpp",
@@ -51,6 +50,7 @@
"android.hardware.audio.common@2.0",
"android.hardware.audio.common@2.0-util",
"android.hardware.audio.effect@2.0",
+ "android.hardware.audio.effect@2.0-util",
],
cflags: [
"-DMAJOR_VERSION=2",
@@ -66,6 +66,7 @@
"android.hardware.audio.common@4.0",
"android.hardware.audio.common@4.0-util",
"android.hardware.audio.effect@4.0",
+ "android.hardware.audio.effect@4.0-util",
],
cflags: [
"-DMAJOR_VERSION=4",
@@ -81,6 +82,7 @@
"android.hardware.audio.common@5.0",
"android.hardware.audio.common@5.0-util",
"android.hardware.audio.effect@5.0",
+ "android.hardware.audio.effect@5.0-util",
],
cflags: [
"-DMAJOR_VERSION=5",
@@ -96,6 +98,7 @@
"android.hardware.audio.common@6.0",
"android.hardware.audio.common@6.0-util",
"android.hardware.audio.effect@6.0",
+ "android.hardware.audio.effect@6.0-util",
],
cflags: [
"-DMAJOR_VERSION=6",
@@ -111,6 +114,7 @@
"android.hardware.audio.common@7.0",
"android.hardware.audio.common@7.0-util",
"android.hardware.audio.effect@7.0",
+ "android.hardware.audio.effect@7.0-util",
],
cflags: [
"-DMAJOR_VERSION=7",
diff --git a/audio/effect/all-versions/default/Conversions.cpp b/audio/effect/all-versions/default/Conversions.cpp
deleted file mode 100644
index 0cc8767..0000000
--- a/audio/effect/all-versions/default/Conversions.cpp
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2018 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 "Conversions.h"
-#include "UuidUtils.h"
-
-#include <memory.h>
-#include <stdio.h>
-
-#include <common/all-versions/VersionUtils.h>
-
-using ::android::hardware::audio::common::utils::EnumBitfield;
-
-namespace android {
-namespace hardware {
-namespace audio {
-namespace effect {
-namespace CPP_VERSION {
-namespace implementation {
-
-using ::android::hardware::audio::common::CPP_VERSION::implementation::UuidUtils;
-
-void effectDescriptorFromHal(const effect_descriptor_t& halDescriptor,
- EffectDescriptor* descriptor) {
- UuidUtils::uuidFromHal(halDescriptor.type, &descriptor->type);
- UuidUtils::uuidFromHal(halDescriptor.uuid, &descriptor->uuid);
- descriptor->flags = EnumBitfield<EffectFlags>(halDescriptor.flags);
- descriptor->cpuLoad = halDescriptor.cpuLoad;
- descriptor->memoryUsage = halDescriptor.memoryUsage;
- memcpy(descriptor->name.data(), halDescriptor.name, descriptor->name.size());
- memcpy(descriptor->implementor.data(), halDescriptor.implementor,
- descriptor->implementor.size());
-}
-
-std::string uuidToString(const effect_uuid_t& halUuid) {
- char str[64];
- snprintf(str, sizeof(str), "%08x-%04x-%04x-%04x-%02x%02x%02x%02x%02x%02x", halUuid.timeLow,
- halUuid.timeMid, halUuid.timeHiAndVersion, halUuid.clockSeq, halUuid.node[0],
- halUuid.node[1], halUuid.node[2], halUuid.node[3], halUuid.node[4], halUuid.node[5]);
- return str;
-}
-
-} // namespace implementation
-} // namespace CPP_VERSION
-} // namespace effect
-} // namespace audio
-} // namespace hardware
-} // namespace android
diff --git a/audio/effect/all-versions/default/Conversions.h b/audio/effect/all-versions/default/Conversions.h
deleted file mode 100644
index 75aab24..0000000
--- a/audio/effect/all-versions/default/Conversions.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_HARDWARE_AUDIO_EFFECT_CONVERSIONS_H_
-#define ANDROID_HARDWARE_AUDIO_EFFECT_CONVERSIONS_H_
-
-#include PATH(android/hardware/audio/effect/FILE_VERSION/types.h)
-
-#include <string>
-
-#include <system/audio_effect.h>
-
-namespace android {
-namespace hardware {
-namespace audio {
-namespace effect {
-namespace CPP_VERSION {
-namespace implementation {
-
-using ::android::hardware::audio::effect::CPP_VERSION::EffectDescriptor;
-
-void effectDescriptorFromHal(const effect_descriptor_t& halDescriptor,
- EffectDescriptor* descriptor);
-std::string uuidToString(const effect_uuid_t& halUuid);
-
-} // namespace implementation
-} // namespace CPP_VERSION
-} // namespace effect
-} // namespace audio
-} // namespace hardware
-} // namespace android
-
-#endif // ANDROID_HARDWARE_AUDIO_EFFECT_CONVERSIONS_H_
diff --git a/audio/effect/all-versions/default/Effect.cpp b/audio/effect/all-versions/default/Effect.cpp
index edd364c..ccfc6b2 100644
--- a/audio/effect/all-versions/default/Effect.cpp
+++ b/audio/effect/all-versions/default/Effect.cpp
@@ -19,7 +19,6 @@
#define LOG_TAG "EffectHAL"
#define ATRACE_TAG ATRACE_TAG_AUDIO
-#include "Conversions.h"
#include "Effect.h"
#include "common/all-versions/default/EffectMap.h"
@@ -30,6 +29,7 @@
#include <HidlUtils.h>
#include <android/log.h>
#include <media/EffectsFactoryApi.h>
+#include <util/EffectUtils.h>
#include <utils/Trace.h>
#include "VersionUtils.h"
@@ -202,34 +202,6 @@
halConfig->aux_channels = static_cast<audio_channel_mask_t>(config.auxChannels);
}
-void Effect::effectBufferConfigFromHal(const buffer_config_t& halConfig,
- EffectBufferConfig* config) {
- config->buffer.id = 0;
- config->buffer.frameCount = 0;
- config->samplingRateHz = halConfig.samplingRate;
- config->channels = AudioChannelBitfield(halConfig.channels);
- config->format = AudioFormat(halConfig.format);
- config->accessMode = EffectBufferAccess(halConfig.accessMode);
- config->mask = static_cast<decltype(config->mask)>(halConfig.mask);
-}
-
-// static
-void Effect::effectBufferConfigToHal(const EffectBufferConfig& config, buffer_config_t* halConfig) {
- // Note: setting the buffers directly is considered obsolete. They need to be set
- // using 'setProcessBuffers'.
- halConfig->buffer.frameCount = 0;
- halConfig->buffer.raw = NULL;
- halConfig->samplingRate = config.samplingRateHz;
- halConfig->channels = static_cast<uint32_t>(config.channels);
- // Note: The framework code does not use BP.
- halConfig->bufferProvider.cookie = NULL;
- halConfig->bufferProvider.getBuffer = NULL;
- halConfig->bufferProvider.releaseBuffer = NULL;
- halConfig->format = static_cast<uint8_t>(config.format);
- halConfig->accessMode = static_cast<uint8_t>(config.accessMode);
- halConfig->mask = static_cast<uint8_t>(config.mask);
-}
-
#else // MAJOR_VERSION <= 6
void Effect::effectAuxChannelsConfigFromHal(const channel_config_t& halConfig,
@@ -247,50 +219,8 @@
(void)HidlUtils::audioChannelMaskToHal(config.auxChannels, &halConfig->aux_channels);
}
-void Effect::effectBufferConfigFromHal(const buffer_config_t& halConfig,
- EffectBufferConfig* config) {
- config->buffer.id = 0;
- config->buffer.frameCount = 0;
- audio_config_base_t halConfigBase = {halConfig.samplingRate,
- static_cast<audio_channel_mask_t>(halConfig.channels),
- static_cast<audio_format_t>(halConfig.format)};
- (void)HidlUtils::audioConfigBaseFromHal(halConfigBase, mIsInput, &config->base);
- config->accessMode = EffectBufferAccess(halConfig.accessMode);
- config->mask = static_cast<decltype(config->mask)>(halConfig.mask);
-}
-
-// static
-void Effect::effectBufferConfigToHal(const EffectBufferConfig& config, buffer_config_t* halConfig) {
- // Note: setting the buffers directly is considered obsolete. They need to be set
- // using 'setProcessBuffers'.
- halConfig->buffer.frameCount = 0;
- halConfig->buffer.raw = nullptr;
- audio_config_base_t halConfigBase;
- (void)HidlUtils::audioConfigBaseToHal(config.base, &halConfigBase);
- halConfig->samplingRate = halConfigBase.sample_rate;
- halConfig->channels = halConfigBase.channel_mask;
- halConfig->format = halConfigBase.format;
- // Note: The framework code does not use BP.
- halConfig->bufferProvider.cookie = nullptr;
- halConfig->bufferProvider.getBuffer = nullptr;
- halConfig->bufferProvider.releaseBuffer = nullptr;
- halConfig->accessMode = static_cast<uint8_t>(config.accessMode);
- halConfig->mask = static_cast<uint8_t>(config.mask);
-}
-
#endif // MAJOR_VERSION <= 6
-void Effect::effectConfigFromHal(const effect_config_t& halConfig, EffectConfig* config) {
- effectBufferConfigFromHal(halConfig.inputCfg, &config->inputCfg);
- effectBufferConfigFromHal(halConfig.outputCfg, &config->outputCfg);
-}
-
-// static
-void Effect::effectConfigToHal(const EffectConfig& config, effect_config_t* halConfig) {
- effectBufferConfigToHal(config.inputCfg, &halConfig->inputCfg);
- effectBufferConfigToHal(config.outputCfg, &halConfig->outputCfg);
-}
-
// static
void Effect::effectOffloadParamToHal(const EffectOffloadParameter& offload,
effect_offload_param_t* halOffload) {
@@ -355,7 +285,7 @@
(*mHandle)->command(mHandle, commandCode, 0, NULL, &halResultSize, &halConfig);
EffectConfig config;
if (status == OK) {
- effectConfigFromHal(halConfig, &config);
+ status = EffectUtils::effectConfigFromHal(halConfig, mIsInput, &config);
}
cb(analyzeCommandStatus(commandName, sContextCallToCommand, status), config);
}
@@ -520,7 +450,7 @@
const sp<IEffectBufferProviderCallback>& inputBufferProvider,
const sp<IEffectBufferProviderCallback>& outputBufferProvider) {
effect_config_t halConfig;
- effectConfigToHal(config, &halConfig);
+ EffectUtils::effectConfigToHal(config, &halConfig);
if (inputBufferProvider != 0) {
LOG_FATAL("Using input buffer provider is not supported");
}
@@ -715,7 +645,7 @@
status_t status = (*mHandle)->get_descriptor(mHandle, &halDescriptor);
EffectDescriptor descriptor;
if (status == OK) {
- effectDescriptorFromHal(halDescriptor, &descriptor);
+ status = EffectUtils::effectDescriptorFromHal(halDescriptor, &descriptor);
}
_hidl_cb(analyzeStatus("get_descriptor", "", sContextCallFunction, status), descriptor);
return Void();
diff --git a/audio/effect/all-versions/default/Effect.h b/audio/effect/all-versions/default/Effect.h
index 9aa47ea..d5218f7 100644
--- a/audio/effect/all-versions/default/Effect.h
+++ b/audio/effect/all-versions/default/Effect.h
@@ -203,11 +203,6 @@
EffectAuxChannelsConfig* config);
static void effectAuxChannelsConfigToHal(const EffectAuxChannelsConfig& config,
channel_config_t* halConfig);
- void effectBufferConfigFromHal(const buffer_config_t& halConfig, EffectBufferConfig* config);
- static void effectBufferConfigToHal(const EffectBufferConfig& config,
- buffer_config_t* halConfig);
- void effectConfigFromHal(const effect_config_t& halConfig, EffectConfig* config);
- static void effectConfigToHal(const EffectConfig& config, effect_config_t* halConfig);
static void effectOffloadParamToHal(const EffectOffloadParameter& offload,
effect_offload_param_t* halOffload);
static std::vector<uint8_t> parameterToHal(uint32_t paramSize, const void* paramData,
diff --git a/audio/effect/all-versions/default/EffectsFactory.cpp b/audio/effect/all-versions/default/EffectsFactory.cpp
index 1ea990b..eb1cb49 100644
--- a/audio/effect/all-versions/default/EffectsFactory.cpp
+++ b/audio/effect/all-versions/default/EffectsFactory.cpp
@@ -19,7 +19,6 @@
#include "AcousticEchoCancelerEffect.h"
#include "AutomaticGainControlEffect.h"
#include "BassBoostEffect.h"
-#include "Conversions.h"
#include "DownmixEffect.h"
#include "Effect.h"
#include "EnvironmentalReverbEffect.h"
@@ -27,11 +26,11 @@
#include "LoudnessEnhancerEffect.h"
#include "NoiseSuppressionEffect.h"
#include "PresetReverbEffect.h"
-#include "UuidUtils.h"
#include "VirtualizerEffect.h"
#include "VisualizerEffect.h"
#include "common/all-versions/default/EffectMap.h"
+#include <UuidUtils.h>
#include <android/log.h>
#include <media/EffectsFactoryApi.h>
#include <system/audio_effects/effect_aec.h>
@@ -45,6 +44,7 @@
#include <system/audio_effects/effect_presetreverb.h>
#include <system/audio_effects/effect_virtualizer.h>
#include <system/audio_effects/effect_visualizer.h>
+#include <util/EffectUtils.h>
namespace android {
namespace hardware {
@@ -107,7 +107,7 @@
effect_descriptor_t halDescriptor;
status = EffectQueryEffect(i, &halDescriptor);
if (status == OK) {
- effectDescriptorFromHal(halDescriptor, &result[i]);
+ EffectUtils::effectDescriptorFromHal(halDescriptor, &result[i]);
} else {
ALOGE("Error querying effect at position %d / %d: %s", i, numEffects,
strerror(-status));
@@ -141,11 +141,11 @@
effect_descriptor_t halDescriptor;
status_t status = EffectGetDescriptor(&halUuid, &halDescriptor);
EffectDescriptor descriptor;
- effectDescriptorFromHal(halDescriptor, &descriptor);
+ EffectUtils::effectDescriptorFromHal(halDescriptor, &descriptor);
Result retval(Result::OK);
if (status != OK) {
- ALOGE("Error querying effect descriptor for %s: %s", uuidToString(halUuid).c_str(),
- strerror(-status));
+ ALOGE("Error querying effect descriptor for %s: %s",
+ UuidUtils::uuidToString(halUuid).c_str(), strerror(-status));
if (status == -ENOENT) {
retval = Result::INVALID_ARGUMENTS;
} else {
@@ -191,13 +191,14 @@
effect = dispatchEffectInstanceCreation(halDescriptor, handle);
effectId = EffectMap::getInstance().add(handle);
} else {
- ALOGE("Error querying effect descriptor for %s: %s", uuidToString(halUuid).c_str(),
- strerror(-status));
+ ALOGE("Error querying effect descriptor for %s: %s",
+ UuidUtils::uuidToString(halUuid).c_str(), strerror(-status));
EffectRelease(handle);
}
}
if (status != OK) {
- ALOGE("Error creating effect %s: %s", uuidToString(halUuid).c_str(), strerror(-status));
+ ALOGE("Error creating effect %s: %s", UuidUtils::uuidToString(halUuid).c_str(),
+ strerror(-status));
if (status == -ENOENT) {
retval = Result::INVALID_ARGUMENTS;
} else {
diff --git a/audio/effect/all-versions/default/TEST_MAPPING b/audio/effect/all-versions/default/TEST_MAPPING
new file mode 100644
index 0000000..b66e4e4
--- /dev/null
+++ b/audio/effect/all-versions/default/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "android.hardware.audio.effect@7.0-util_tests"
+ }
+ ]
+}
diff --git a/audio/effect/all-versions/default/util/Android.bp b/audio/effect/all-versions/default/util/Android.bp
new file mode 100644
index 0000000..5ba19d3
--- /dev/null
+++ b/audio/effect/all-versions/default/util/Android.bp
@@ -0,0 +1,136 @@
+cc_defaults {
+ name: "android.hardware.audio.effect-util_default",
+ defaults: ["hidl_defaults"],
+
+ vendor_available: true,
+
+ export_include_dirs: ["include"],
+
+ srcs: [
+ "EffectUtils.cpp",
+ ],
+
+ shared_libs: [
+ "liblog",
+ "libutils",
+ "libhidlbase",
+ "android.hardware.audio.common-util",
+ ],
+ export_shared_lib_headers: [
+ "android.hardware.audio.common-util",
+ ],
+
+ header_libs: [
+ "libaudio_system_headers",
+ "libhardware_headers",
+ ],
+}
+
+cc_library_shared {
+ name: "android.hardware.audio.effect@2.0-util",
+ defaults: ["android.hardware.audio.effect-util_default"],
+ shared_libs: [
+ "android.hardware.audio.common@2.0",
+ "android.hardware.audio.common@2.0-util",
+ "android.hardware.audio.effect@2.0",
+ ],
+ cflags: [
+ "-DMAJOR_VERSION=2",
+ "-DMINOR_VERSION=0",
+ "-include common/all-versions/VersionMacro.h",
+ ],
+}
+
+cc_library_shared {
+ name: "android.hardware.audio.effect@4.0-util",
+ defaults: ["android.hardware.audio.effect-util_default"],
+ shared_libs: [
+ "android.hardware.audio.common@4.0",
+ "android.hardware.audio.common@4.0-util",
+ "android.hardware.audio.effect@4.0",
+ ],
+ cflags: [
+ "-DMAJOR_VERSION=4",
+ "-DMINOR_VERSION=0",
+ "-include common/all-versions/VersionMacro.h",
+ ],
+}
+
+cc_library_shared {
+ name: "android.hardware.audio.effect@5.0-util",
+ defaults: ["android.hardware.audio.effect-util_default"],
+ shared_libs: [
+ "android.hardware.audio.common@5.0",
+ "android.hardware.audio.common@5.0-util",
+ "android.hardware.audio.effect@5.0",
+ ],
+ cflags: [
+ "-DMAJOR_VERSION=5",
+ "-DMINOR_VERSION=0",
+ "-include common/all-versions/VersionMacro.h",
+ ],
+}
+
+cc_library_shared {
+ name: "android.hardware.audio.effect@6.0-util",
+ defaults: ["android.hardware.audio.effect-util_default"],
+ shared_libs: [
+ "android.hardware.audio.common@6.0",
+ "android.hardware.audio.common@6.0-util",
+ "android.hardware.audio.effect@6.0",
+ ],
+ cflags: [
+ "-DMAJOR_VERSION=6",
+ "-DMINOR_VERSION=0",
+ "-include common/all-versions/VersionMacro.h",
+ ],
+}
+
+cc_library {
+ name: "android.hardware.audio.effect@7.0-util",
+ defaults: ["android.hardware.audio.effect-util_default"],
+ shared_libs: [
+ "android.hardware.audio.common@7.0",
+ "android.hardware.audio.common@7.0-util",
+ "android.hardware.audio.effect@7.0",
+ ],
+ cflags: [
+ "-DMAJOR_VERSION=7",
+ "-DMINOR_VERSION=0",
+ "-include common/all-versions/VersionMacro.h",
+ ],
+}
+
+// Note: this isn't a VTS test, but rather a unit test
+// to verify correctness of conversion utilities.
+cc_test {
+ name: "android.hardware.audio.effect@7.0-util_tests",
+ defaults: ["android.hardware.audio.effect-util_default"],
+
+ srcs: ["tests/effectutils_tests.cpp"],
+
+ // Use static linking to allow running in presubmit on
+ // targets that don't have HAL V7.
+ static_libs: [
+ "android.hardware.audio.common@7.0",
+ "android.hardware.audio.common@7.0-enums",
+ "android.hardware.audio.common@7.0-util",
+ "android.hardware.audio.effect@7.0",
+ "android.hardware.audio.effect@7.0-util",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "libxml2",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ "-DMAJOR_VERSION=7",
+ "-DMINOR_VERSION=0",
+ "-include common/all-versions/VersionMacro.h",
+ ],
+
+ test_suites: ["device-tests"],
+}
diff --git a/audio/effect/all-versions/default/util/EffectUtils.cpp b/audio/effect/all-versions/default/util/EffectUtils.cpp
new file mode 100644
index 0000000..1c0419a
--- /dev/null
+++ b/audio/effect/all-versions/default/util/EffectUtils.cpp
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2021 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 <memory.h>
+
+#include <HidlUtils.h>
+#include <UuidUtils.h>
+#include <common/all-versions/VersionUtils.h>
+
+#include "util/EffectUtils.h"
+
+using ::android::hardware::audio::common::CPP_VERSION::implementation::HidlUtils;
+using ::android::hardware::audio::common::CPP_VERSION::implementation::UuidUtils;
+using ::android::hardware::audio::common::utils::EnumBitfield;
+
+namespace android {
+namespace hardware {
+namespace audio {
+namespace effect {
+namespace CPP_VERSION {
+namespace implementation {
+
+using namespace ::android::hardware::audio::common::CPP_VERSION;
+
+#define CONVERT_CHECKED(expr, result) \
+ if (status_t status = (expr); status != NO_ERROR) { \
+ result = status; \
+ }
+
+#if MAJOR_VERSION <= 6
+
+status_t EffectUtils::effectBufferConfigFromHal(const buffer_config_t& halConfig, bool /*isInput*/,
+ EffectBufferConfig* config) {
+ config->buffer.id = 0;
+ config->buffer.frameCount = 0;
+ config->samplingRateHz = halConfig.samplingRate;
+ config->channels = EnumBitfield<AudioChannelMask>(halConfig.channels);
+ config->format = AudioFormat(halConfig.format);
+ config->accessMode = EffectBufferAccess(halConfig.accessMode);
+ config->mask = EnumBitfield<EffectConfigParameters>(halConfig.mask);
+ return NO_ERROR;
+}
+
+status_t EffectUtils::effectBufferConfigToHal(const EffectBufferConfig& config,
+ buffer_config_t* halConfig) {
+ // Note: setting the buffers directly is considered obsolete. They need to be set
+ // using 'setProcessBuffers'.
+ halConfig->buffer.frameCount = 0;
+ halConfig->buffer.raw = nullptr;
+ halConfig->samplingRate = config.samplingRateHz;
+ halConfig->channels = static_cast<uint32_t>(config.channels);
+ // Note: The framework code does not use BP.
+ halConfig->bufferProvider.cookie = nullptr;
+ halConfig->bufferProvider.getBuffer = nullptr;
+ halConfig->bufferProvider.releaseBuffer = nullptr;
+ halConfig->format = static_cast<uint8_t>(config.format);
+ halConfig->accessMode = static_cast<uint8_t>(config.accessMode);
+ halConfig->mask = static_cast<uint8_t>(config.mask);
+ return NO_ERROR;
+}
+
+#else
+
+status_t EffectUtils::effectBufferConfigFromHal(const buffer_config_t& halConfig, bool isInput,
+ EffectBufferConfig* config) {
+ status_t result = NO_ERROR;
+ config->buffer.unspecified();
+ audio_config_base_t halConfigBase = {halConfig.samplingRate,
+ static_cast<audio_channel_mask_t>(halConfig.channels),
+ static_cast<audio_format_t>(halConfig.format)};
+ CONVERT_CHECKED(HidlUtils::audioConfigBaseOptionalFromHal(
+ halConfigBase, isInput, halConfig.mask & EFFECT_CONFIG_FORMAT,
+ halConfig.mask & EFFECT_CONFIG_SMP_RATE,
+ halConfig.mask & EFFECT_CONFIG_CHANNELS, &config->base),
+ result);
+ if (halConfig.mask & EFFECT_CONFIG_ACC_MODE) {
+ config->accessMode.value(EffectBufferAccess(halConfig.accessMode));
+ }
+ return result;
+}
+
+status_t EffectUtils::effectBufferConfigToHal(const EffectBufferConfig& config,
+ buffer_config_t* halConfig) {
+ status_t result = NO_ERROR;
+ // Note: setting the buffers directly is considered obsolete. They need to be set
+ // using 'setProcessBuffers'.
+ halConfig->buffer.frameCount = 0;
+ halConfig->buffer.raw = nullptr;
+ audio_config_base_t halConfigBase = AUDIO_CONFIG_BASE_INITIALIZER;
+ bool formatSpecified = false, sRateSpecified = false, channelMaskSpecified = false;
+ CONVERT_CHECKED(
+ HidlUtils::audioConfigBaseOptionalToHal(config.base, &halConfigBase, &formatSpecified,
+ &sRateSpecified, &channelMaskSpecified),
+ result);
+ halConfig->mask = 0;
+ if (sRateSpecified) {
+ halConfig->mask |= EFFECT_CONFIG_SMP_RATE;
+ halConfig->samplingRate = halConfigBase.sample_rate;
+ }
+ if (channelMaskSpecified) {
+ halConfig->mask |= EFFECT_CONFIG_CHANNELS;
+ halConfig->channels = halConfigBase.channel_mask;
+ }
+ if (formatSpecified) {
+ halConfig->mask |= EFFECT_CONFIG_FORMAT;
+ halConfig->format = halConfigBase.format;
+ }
+ // Note: The framework code does not use BP.
+ halConfig->bufferProvider.cookie = nullptr;
+ halConfig->bufferProvider.getBuffer = nullptr;
+ halConfig->bufferProvider.releaseBuffer = nullptr;
+ if (config.accessMode.getDiscriminator() ==
+ EffectBufferConfig::OptionalAccessMode::hidl_discriminator::value) {
+ halConfig->mask |= EFFECT_CONFIG_ACC_MODE;
+ halConfig->accessMode = static_cast<uint8_t>(config.accessMode.value());
+ }
+ return result;
+}
+
+#endif // MAJOR_VERSION >= 6
+
+status_t EffectUtils::effectConfigFromHal(const effect_config_t& halConfig, bool isInput,
+ EffectConfig* config) {
+ status_t result = NO_ERROR;
+ CONVERT_CHECKED(effectBufferConfigFromHal(halConfig.inputCfg, isInput, &config->inputCfg),
+ result);
+ CONVERT_CHECKED(effectBufferConfigFromHal(halConfig.outputCfg, isInput, &config->outputCfg),
+ result);
+ return result;
+}
+
+status_t EffectUtils::effectConfigToHal(const EffectConfig& config, effect_config_t* halConfig) {
+ status_t result = NO_ERROR;
+ CONVERT_CHECKED(effectBufferConfigToHal(config.inputCfg, &halConfig->inputCfg), result);
+ CONVERT_CHECKED(effectBufferConfigToHal(config.outputCfg, &halConfig->outputCfg), result);
+ return result;
+}
+
+status_t EffectUtils::effectDescriptorFromHal(const effect_descriptor_t& halDescriptor,
+ EffectDescriptor* descriptor) {
+ UuidUtils::uuidFromHal(halDescriptor.type, &descriptor->type);
+ UuidUtils::uuidFromHal(halDescriptor.uuid, &descriptor->uuid);
+ descriptor->flags = EnumBitfield<EffectFlags>(halDescriptor.flags);
+ descriptor->cpuLoad = halDescriptor.cpuLoad;
+ descriptor->memoryUsage = halDescriptor.memoryUsage;
+ memcpy(descriptor->name.data(), halDescriptor.name, descriptor->name.size());
+ memcpy(descriptor->implementor.data(), halDescriptor.implementor,
+ descriptor->implementor.size());
+ return NO_ERROR;
+}
+
+status_t EffectUtils::effectDescriptorToHal(const EffectDescriptor& descriptor,
+ effect_descriptor_t* halDescriptor) {
+ UuidUtils::uuidToHal(descriptor.type, &halDescriptor->type);
+ UuidUtils::uuidToHal(descriptor.uuid, &halDescriptor->uuid);
+ halDescriptor->flags = static_cast<uint32_t>(descriptor.flags);
+ halDescriptor->cpuLoad = descriptor.cpuLoad;
+ halDescriptor->memoryUsage = descriptor.memoryUsage;
+ memcpy(halDescriptor->name, descriptor.name.data(), descriptor.name.size());
+ memcpy(halDescriptor->implementor, descriptor.implementor.data(),
+ descriptor.implementor.size());
+ return NO_ERROR;
+}
+
+} // namespace implementation
+} // namespace CPP_VERSION
+} // namespace effect
+} // namespace audio
+} // namespace hardware
+} // namespace android
diff --git a/audio/effect/all-versions/default/util/include/util/EffectUtils.h b/audio/effect/all-versions/default/util/include/util/EffectUtils.h
new file mode 100644
index 0000000..23963b4
--- /dev/null
+++ b/audio/effect/all-versions/default/util/include/util/EffectUtils.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 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
+
+// clang-format off
+#include PATH(android/hardware/audio/effect/FILE_VERSION/types.h)
+// clang-format on
+
+#include <system/audio_effect.h>
+
+namespace android {
+namespace hardware {
+namespace audio {
+namespace effect {
+namespace CPP_VERSION {
+namespace implementation {
+
+using namespace ::android::hardware::audio::effect::CPP_VERSION;
+
+struct EffectUtils {
+ static status_t effectBufferConfigFromHal(const buffer_config_t& halConfig, bool isInput,
+ EffectBufferConfig* config);
+ static status_t effectBufferConfigToHal(const EffectBufferConfig& config,
+ buffer_config_t* halConfig);
+ static status_t effectConfigFromHal(const effect_config_t& halConfig, bool isInput,
+ EffectConfig* config);
+ static status_t effectConfigToHal(const EffectConfig& config, effect_config_t* halConfig);
+ static status_t effectDescriptorFromHal(const effect_descriptor_t& halDescriptor,
+ EffectDescriptor* descriptor);
+ static status_t effectDescriptorToHal(const EffectDescriptor& descriptor,
+ effect_descriptor_t* halDescriptor);
+};
+
+} // namespace implementation
+} // namespace CPP_VERSION
+} // namespace effect
+} // namespace audio
+} // namespace hardware
+} // namespace android
diff --git a/audio/effect/all-versions/default/util/tests/effectutils_tests.cpp b/audio/effect/all-versions/default/util/tests/effectutils_tests.cpp
new file mode 100644
index 0000000..7eb8cd2
--- /dev/null
+++ b/audio/effect/all-versions/default/util/tests/effectutils_tests.cpp
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2021 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 <string>
+
+#include <gtest/gtest.h>
+
+#define LOG_TAG "EffectUtils_Test"
+#include <log/log.h>
+
+#include <android_audio_policy_configuration_V7_0-enums.h>
+#include <system/audio_effect.h>
+#include <util/EffectUtils.h>
+#include <xsdc/XsdcSupport.h>
+
+using namespace android;
+using namespace ::android::hardware::audio::common::CPP_VERSION;
+using namespace ::android::hardware::audio::effect::CPP_VERSION;
+using ::android::hardware::audio::effect::CPP_VERSION::implementation::EffectUtils;
+namespace xsd {
+using namespace ::android::audio::policy::configuration::V7_0;
+}
+
+static constexpr audio_channel_mask_t kInvalidHalChannelMask = AUDIO_CHANNEL_INVALID;
+static constexpr audio_format_t kInvalidHalFormat = AUDIO_FORMAT_INVALID;
+
+// Not generated automatically because AudioBuffer contains
+// instances of hidl_memory which can't be compared properly
+// in general case due to presence of handles.
+//
+// However, in this particular case, handles must not present
+// thus comparison is possible.
+//
+// operator== must be defined in the same namespace as the structures.
+namespace android {
+namespace hardware {
+namespace audio {
+namespace effect {
+namespace CPP_VERSION {
+inline bool operator==(const AudioBuffer& lhs, const AudioBuffer& rhs) {
+ return lhs.id == rhs.id && lhs.frameCount == rhs.frameCount && lhs.data.handle() == nullptr &&
+ rhs.data.handle() == nullptr;
+}
+
+inline bool operator==(const EffectBufferConfig& lhs, const EffectBufferConfig& rhs) {
+ return lhs.buffer.getDiscriminator() == rhs.buffer.getDiscriminator() &&
+ (lhs.buffer.getDiscriminator() ==
+ EffectBufferConfig::OptionalBuffer::hidl_discriminator::unspecified ||
+ lhs.buffer.buf() == rhs.buffer.buf()) &&
+ lhs.base == rhs.base && lhs.accessMode == rhs.accessMode;
+}
+
+inline bool operator==(const EffectConfig& lhs, const EffectConfig& rhs) {
+ return lhs.inputCfg == rhs.inputCfg && lhs.outputCfg == rhs.outputCfg;
+}
+} // namespace CPP_VERSION
+} // namespace effect
+} // namespace audio
+} // namespace hardware
+} // namespace android
+
+TEST(EffectUtils, ConvertInvalidBufferConfig) {
+ buffer_config_t halInvalid;
+ EffectBufferConfig invalidChannelMask;
+ invalidChannelMask.base.channelMask.value("random string");
+ EXPECT_EQ(BAD_VALUE, EffectUtils::effectBufferConfigToHal(invalidChannelMask, &halInvalid));
+ EffectBufferConfig invalidFormat;
+ invalidFormat.base.format.value("random string");
+ EXPECT_EQ(BAD_VALUE, EffectUtils::effectBufferConfigToHal(invalidFormat, &halInvalid));
+
+ buffer_config_t halInvalidChannelMask;
+ EffectBufferConfig invalid;
+ halInvalidChannelMask.channels = kInvalidHalChannelMask;
+ halInvalidChannelMask.mask = EFFECT_CONFIG_CHANNELS;
+ EXPECT_EQ(BAD_VALUE, EffectUtils::effectBufferConfigFromHal(halInvalidChannelMask,
+ false /*isInput*/, &invalid));
+ EXPECT_EQ(BAD_VALUE, EffectUtils::effectBufferConfigFromHal(halInvalidChannelMask,
+ true /*isInput*/, &invalid));
+ buffer_config_t halInvalidFormat;
+ halInvalidFormat.format = (uint8_t)kInvalidHalFormat;
+ halInvalidFormat.mask = EFFECT_CONFIG_FORMAT;
+ EXPECT_EQ(BAD_VALUE, EffectUtils::effectBufferConfigFromHal(halInvalidFormat, false /*isInput*/,
+ &invalid));
+ EXPECT_EQ(BAD_VALUE,
+ EffectUtils::effectBufferConfigFromHal(halInvalidFormat, true /*isInput*/, &invalid));
+}
+
+TEST(EffectUtils, ConvertBufferConfig) {
+ EffectBufferConfig empty;
+ buffer_config_t halEmpty;
+ EXPECT_EQ(NO_ERROR, EffectUtils::effectBufferConfigToHal(empty, &halEmpty));
+ EffectBufferConfig emptyBackOut;
+ EXPECT_EQ(NO_ERROR,
+ EffectUtils::effectBufferConfigFromHal(halEmpty, false /*isInput*/, &emptyBackOut));
+ EXPECT_EQ(empty, emptyBackOut);
+ EffectBufferConfig emptyBackIn;
+ EXPECT_EQ(NO_ERROR,
+ EffectUtils::effectBufferConfigFromHal(halEmpty, true /*isInput*/, &emptyBackIn));
+ EXPECT_EQ(empty, emptyBackIn);
+
+ EffectBufferConfig chanMask;
+ chanMask.base.channelMask.value(toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO));
+ buffer_config_t halChanMask;
+ EXPECT_EQ(NO_ERROR, EffectUtils::effectBufferConfigToHal(chanMask, &halChanMask));
+ EffectBufferConfig chanMaskBack;
+ EXPECT_EQ(NO_ERROR, EffectUtils::effectBufferConfigFromHal(halChanMask, false /*isInput*/,
+ &chanMaskBack));
+ EXPECT_EQ(chanMask, chanMaskBack);
+
+ EffectBufferConfig format;
+ format.base.format.value(toString(xsd::AudioFormat::AUDIO_FORMAT_PCM_16_BIT));
+ buffer_config_t halFormat;
+ EXPECT_EQ(NO_ERROR, EffectUtils::effectBufferConfigToHal(format, &halFormat));
+ EffectBufferConfig formatBackOut;
+ EXPECT_EQ(NO_ERROR,
+ EffectUtils::effectBufferConfigFromHal(halFormat, false /*isInput*/, &formatBackOut));
+ EXPECT_EQ(format, formatBackOut);
+ EffectBufferConfig formatBackIn;
+ EXPECT_EQ(NO_ERROR,
+ EffectUtils::effectBufferConfigFromHal(halFormat, true /*isInput*/, &formatBackIn));
+ EXPECT_EQ(format, formatBackIn);
+}
+
+TEST(EffectUtils, ConvertDescriptor) {
+ EffectDescriptor desc{};
+ effect_descriptor_t halDesc;
+ EXPECT_EQ(NO_ERROR, EffectUtils::effectDescriptorToHal(desc, &halDesc));
+ EffectDescriptor descBack;
+ EXPECT_EQ(NO_ERROR, EffectUtils::effectDescriptorFromHal(halDesc, &descBack));
+ EXPECT_EQ(desc, descBack);
+}
diff --git a/audio/effect/all-versions/vts/functional/VtsHalAudioEffectTargetTest.cpp b/audio/effect/all-versions/vts/functional/VtsHalAudioEffectTargetTest.cpp
index d39fbcd..15a2fd9 100644
--- a/audio/effect/all-versions/vts/functional/VtsHalAudioEffectTargetTest.cpp
+++ b/audio/effect/all-versions/vts/functional/VtsHalAudioEffectTargetTest.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#include <vector>
+
#define LOG_TAG "AudioEffectHidlHalTest"
#include <android-base/logging.h>
#if MAJOR_VERSION <= 6
@@ -262,8 +264,10 @@
*channelCount = audio_channel_count_from_out_mask(
static_cast<audio_channel_mask_t>(currentConfig.outputCfg.channels));
#else
+ ASSERT_EQ(AudioConfigBaseOptional::ChannelMask::hidl_discriminator::value,
+ currentConfig.outputCfg.base.channelMask.getDiscriminator());
*channelCount = android::audio::policy::configuration::V7_0::getChannelCount(
- currentConfig.outputCfg.base.channelMask);
+ currentConfig.outputCfg.base.channelMask.value());
ASSERT_NE(*channelCount, 0);
#endif
}
@@ -309,6 +313,47 @@
EXPECT_EQ(Result::OK, ret2);
}
+#if MAJOR_VERSION >= 7
+std::vector<EffectBufferConfig> generateInvalidConfigs(const EffectBufferConfig& src) {
+ std::vector<EffectBufferConfig> result;
+ EffectBufferConfig invalidFormat = src;
+ invalidFormat.base.format.value("random_string");
+ result.push_back(std::move(invalidFormat));
+ EffectBufferConfig invalidChannelMask = src;
+ invalidChannelMask.base.channelMask.value("random_string");
+ result.push_back(std::move(invalidChannelMask));
+ return result;
+}
+
+TEST_P(AudioEffectHidlTest, SetConfigInvalidArguments) {
+ description("Verify that invalid arguments are rejected by SetConfig");
+ Result retval = Result::NOT_INITIALIZED;
+ EffectConfig currentConfig;
+ Return<void> ret = effect->getConfig([&](Result r, const EffectConfig& conf) {
+ retval = r;
+ if (r == Result::OK) {
+ currentConfig = conf;
+ }
+ });
+ EXPECT_TRUE(ret.isOk());
+ EXPECT_EQ(Result::OK, retval);
+ for (const auto& invalidInputCfg : generateInvalidConfigs(currentConfig.inputCfg)) {
+ EffectConfig invalidConfig = currentConfig;
+ invalidConfig.inputCfg = invalidInputCfg;
+ Return<Result> ret = effect->setConfig(invalidConfig, nullptr, nullptr);
+ EXPECT_TRUE(ret.isOk());
+ EXPECT_EQ(Result::INVALID_ARGUMENTS, ret);
+ }
+ for (const auto& invalidOutputCfg : generateInvalidConfigs(currentConfig.outputCfg)) {
+ EffectConfig invalidConfig = currentConfig;
+ invalidConfig.outputCfg = invalidOutputCfg;
+ Return<Result> ret = effect->setConfig(invalidConfig, nullptr, nullptr);
+ EXPECT_TRUE(ret.isOk());
+ EXPECT_EQ(Result::INVALID_ARGUMENTS, ret);
+ }
+}
+#endif
+
TEST_P(AudioEffectHidlTest, GetConfigReverse) {
description("Verify that GetConfigReverse does not crash");
Return<void> ret = effect->getConfigReverse([&](Result, const EffectConfig&) {});
@@ -352,17 +397,22 @@
rhs.data.handle() == nullptr;
}
+#if MAJOR_VERSION <= 6
inline bool operator==(const EffectBufferConfig& lhs, const EffectBufferConfig& rhs) {
return lhs.buffer == rhs.buffer &&
-#if MAJOR_VERSION <= 6
lhs.samplingRateHz == rhs.samplingRateHz && lhs.channels == rhs.channels &&
lhs.format == rhs.format &&
-#else
- lhs.base.sampleRateHz == rhs.base.sampleRateHz &&
- lhs.base.channelMask == rhs.base.channelMask && lhs.base.format == rhs.base.format &&
-#endif
lhs.accessMode == rhs.accessMode && lhs.mask == rhs.mask;
}
+#else
+inline bool operator==(const EffectBufferConfig& lhs, const EffectBufferConfig& rhs) {
+ return lhs.buffer.getDiscriminator() == rhs.buffer.getDiscriminator() &&
+ (lhs.buffer.getDiscriminator() ==
+ EffectBufferConfig::OptionalBuffer::hidl_discriminator::unspecified ||
+ lhs.buffer.buf() == rhs.buffer.buf()) &&
+ lhs.base == rhs.base && lhs.accessMode == rhs.accessMode;
+}
+#endif // MAJOR_VERSION <= 6
inline bool operator==(const EffectConfig& lhs, const EffectConfig& rhs) {
return lhs.inputCfg == rhs.inputCfg && lhs.outputCfg == rhs.outputCfg;
@@ -413,6 +463,16 @@
EXPECT_EQ(Result::OK, ret);
}
+#if MAJOR_VERSION >= 7
+TEST_P(AudioEffectHidlTest, SetDeviceInvalidDeviceAddress) {
+ description("Verify that invalid device address is rejected by SetDevice");
+ DeviceAddress device{.deviceType = "random_string"};
+ Return<Result> ret = effect->setDevice(device);
+ EXPECT_TRUE(ret.isOk());
+ EXPECT_EQ(Result::INVALID_ARGUMENTS, ret);
+}
+#endif
+
TEST_P(AudioEffectHidlTest, SetDevice) {
description("Verify that SetDevice works for an output chain effect");
#if MAJOR_VERSION <= 6
@@ -468,6 +528,17 @@
EXPECT_TRUE(ret.isOk());
}
+#if MAJOR_VERSION >= 7
+TEST_P(AudioEffectHidlTest, SetInputDeviceInvalidDeviceAddress) {
+ description("Verify that invalid device address is rejected by SetInputDevice");
+ DeviceAddress device{.deviceType = "random_string"};
+ Return<Result> ret = effect->setInputDevice(device);
+ EXPECT_TRUE(ret.isOk());
+ EXPECT_TRUE(ret == Result::INVALID_ARGUMENTS || ret == Result::NOT_SUPPORTED)
+ << ::testing::PrintToString(ret);
+}
+#endif
+
TEST_P(AudioEffectHidlTest, SetInputDevice) {
description("Verify that SetInputDevice does not crash");
#if MAJOR_VERSION <= 6
@@ -479,6 +550,16 @@
EXPECT_TRUE(ret.isOk());
}
+#if MAJOR_VERSION >= 7
+TEST_P(AudioEffectHidlTest, SetInvalidAudioSource) {
+ description("Verify that an invalid audio source is rejected by SetAudioSource");
+ Return<Result> ret = effect->setAudioSource("random_string");
+ ASSERT_TRUE(ret.isOk());
+ EXPECT_TRUE(ret == Result::INVALID_ARGUMENTS || ret == Result::NOT_SUPPORTED)
+ << ::testing::PrintToString(ret);
+}
+#endif
+
TEST_P(AudioEffectHidlTest, SetAudioSource) {
description("Verify that SetAudioSource does not crash");
#if MAJOR_VERSION <= 6
diff --git a/authsecret/aidl/Android.bp b/authsecret/aidl/Android.bp
new file mode 100644
index 0000000..0a05034
--- /dev/null
+++ b/authsecret/aidl/Android.bp
@@ -0,0 +1,16 @@
+aidl_interface {
+ name: "android.hardware.authsecret",
+ vendor_available: true,
+ srcs: ["android/hardware/authsecret/*.aidl"],
+ stability: "vintf",
+ backend: {
+ java: {
+ platform_apis: true,
+ },
+ ndk: {
+ vndk: {
+ enabled: true,
+ },
+ },
+ },
+}
diff --git a/authsecret/aidl/aidl_api/android.hardware.authsecret/current/android/hardware/authsecret/IAuthSecret.aidl b/authsecret/aidl/aidl_api/android.hardware.authsecret/current/android/hardware/authsecret/IAuthSecret.aidl
new file mode 100644
index 0000000..14e8325
--- /dev/null
+++ b/authsecret/aidl/aidl_api/android.hardware.authsecret/current/android/hardware/authsecret/IAuthSecret.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// 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.authsecret;
+@VintfStability
+interface IAuthSecret {
+ oneway void setPrimaryUserCredential(in byte[] secret);
+}
diff --git a/authsecret/aidl/android/hardware/authsecret/IAuthSecret.aidl b/authsecret/aidl/android/hardware/authsecret/IAuthSecret.aidl
new file mode 100644
index 0000000..3849ec0
--- /dev/null
+++ b/authsecret/aidl/android/hardware/authsecret/IAuthSecret.aidl
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.authsecret;
+
+/**
+ * This security HAL allows vendor components to be cryptographically tied to
+ * the primary user's credential. For example, security hardware can require
+ * proof that the credential is known before applying updates.
+ *
+ */
+@VintfStability
+interface IAuthSecret {
+ /**
+ * When the primary user is unlocked, this method is passed a secret to
+ * prove that is has been successfully unlocked. The primary user can either
+ * be unlocked by a person entering their credential or by another party
+ * using an escrow token e.g. a device administrator.
+ *
+ * The first time this is called, the secret must be used to provision state
+ * that depends on the primary user's secret. The same secret must be passed
+ * on each call until the next factory reset.
+ *
+ * Upon factory reset, any dependence on the secret must be removed as that
+ * secret is now lost and must never be derived again. A new secret must be
+ * created for the new primary user which must be used to newly provision
+ * state the first time this method is called after factory reset.
+ *
+ * The secret must be at least 16 bytes, or the secret must be dropped.
+ *
+ * @param secret blob derived from the primary user's credential.
+ */
+ oneway void setPrimaryUserCredential(in byte[] secret);
+}
diff --git a/authsecret/aidl/default/Android.bp b/authsecret/aidl/default/Android.bp
new file mode 100644
index 0000000..44e0711
--- /dev/null
+++ b/authsecret/aidl/default/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_binary {
+ name: "android.hardware.authsecret-service.example",
+ relative_install_path: "hw",
+ init_rc: ["android.hardware.authsecret-service.example.rc"],
+ vintf_fragments: ["android.hardware.authsecret-service.example.xml"],
+ vendor: true,
+ srcs: [
+ "service.cpp",
+ "AuthSecret.cpp",
+ ],
+ shared_libs: [
+ "android.hardware.authsecret-V1-ndk_platform",
+ "libbase",
+ "libbinder_ndk",
+ ],
+}
diff --git a/authsecret/aidl/default/AuthSecret.cpp b/authsecret/aidl/default/AuthSecret.cpp
new file mode 100644
index 0000000..9645e4d
--- /dev/null
+++ b/authsecret/aidl/default/AuthSecret.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2018 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 "AuthSecret.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace authsecret {
+
+// Methods from ::android::hardware::authsecret::IAuthSecret follow.
+::ndk::ScopedAStatus AuthSecret::setPrimaryUserCredential(const std::vector<uint8_t>& in_secret) {
+ (void)in_secret;
+ return ::ndk::ScopedAStatus::ok();
+}
+
+} // namespace authsecret
+} // namespace hardware
+} // namespace android
+} // aidl
diff --git a/authsecret/aidl/default/AuthSecret.h b/authsecret/aidl/default/AuthSecret.h
new file mode 100644
index 0000000..b40fc48
--- /dev/null
+++ b/authsecret/aidl/default/AuthSecret.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/authsecret/BnAuthSecret.h>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace authsecret {
+
+struct AuthSecret : public BnAuthSecret {
+ AuthSecret() = default;
+
+ // Methods from ::android::hardware::authsecret::IAuthSecret follow.
+ ::ndk::ScopedAStatus setPrimaryUserCredential(const std::vector<uint8_t>& in_secret) override;
+
+};
+
+} // namespace authsecret
+} // namespace hardware
+} // namespace android
+} // aidl
diff --git a/authsecret/aidl/default/android.hardware.authsecret-service.example.rc b/authsecret/aidl/default/android.hardware.authsecret-service.example.rc
new file mode 100644
index 0000000..fef6e24
--- /dev/null
+++ b/authsecret/aidl/default/android.hardware.authsecret-service.example.rc
@@ -0,0 +1,4 @@
+service vendor.authsecret_default /vendor/bin/hw/android.hardware.authsecret-service.example
+ class hal
+ user hsm
+ group hsm
diff --git a/authsecret/aidl/default/android.hardware.authsecret-service.example.xml b/authsecret/aidl/default/android.hardware.authsecret-service.example.xml
new file mode 100644
index 0000000..9d4e162
--- /dev/null
+++ b/authsecret/aidl/default/android.hardware.authsecret-service.example.xml
@@ -0,0 +1,10 @@
+<manifest version="1.0" type="device">
+ <hal format="aidl">
+ <name>android.hardware.authsecret</name>
+ <version>1</version>
+ <interface>
+ <name>IAuthSecret</name>
+ <instance>default</instance>
+ </interface>
+ </hal>
+</manifest>
diff --git a/authsecret/aidl/default/service.cpp b/authsecret/aidl/default/service.cpp
new file mode 100644
index 0000000..efecf10
--- /dev/null
+++ b/authsecret/aidl/default/service.cpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include "AuthSecret.h"
+
+using ::aidl::android::hardware::authsecret::AuthSecret;
+
+int main() {
+ ABinderProcess_setThreadPoolMaxThreadCount(0);
+ std::shared_ptr<AuthSecret> authsecret = ndk::SharedRefBase::make<AuthSecret>();
+
+ const std::string instance = std::string() + AuthSecret::descriptor + "/default";
+ binder_status_t status = AServiceManager_addService(authsecret->asBinder().get(), instance.c_str());
+ CHECK(status == STATUS_OK);
+
+ ABinderProcess_joinThreadPool();
+ return -1; // Should never be reached
+}
diff --git a/authsecret/aidl/vts/Android.bp b/authsecret/aidl/vts/Android.bp
new file mode 100644
index 0000000..29b3bcc
--- /dev/null
+++ b/authsecret/aidl/vts/Android.bp
@@ -0,0 +1,31 @@
+//
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_test {
+ name: "VtsHalAuthSecretTargetTest",
+ defaults: [
+ "VtsHalTargetTestDefaults",
+ "use_libaidlvintf_gtest_helper_static",
+ ],
+ srcs: ["VtsHalAuthSecretTargetTest.cpp"],
+ static_libs: ["android.hardware.authsecret-V1-ndk_platform"],
+ shared_libs: ["libbinder_ndk"],
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
+ require_root: true,
+}
diff --git a/authsecret/aidl/vts/OWNERS b/authsecret/aidl/vts/OWNERS
new file mode 100644
index 0000000..40d95e4
--- /dev/null
+++ b/authsecret/aidl/vts/OWNERS
@@ -0,0 +1,2 @@
+chengyouho@google.com
+frankwoo@google.com
diff --git a/authsecret/aidl/vts/VtsHalAuthSecretTargetTest.cpp b/authsecret/aidl/vts/VtsHalAuthSecretTargetTest.cpp
new file mode 100644
index 0000000..31c2834
--- /dev/null
+++ b/authsecret/aidl/vts/VtsHalAuthSecretTargetTest.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+
+#include <aidl/android/hardware/authsecret/IAuthSecret.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+using ::aidl::android::hardware::authsecret::IAuthSecret;
+
+using ::ndk::SpAIBinder;
+
+/**
+ * There is no expected behaviour that can be tested so these tests check the
+ * HAL doesn't crash with different execution orders.
+ */
+class AuthSecretAidlTest : public testing::TestWithParam<std::string> {
+ public:
+ virtual void SetUp() override {
+ authsecret = IAuthSecret::fromBinder(
+ SpAIBinder(AServiceManager_waitForService(GetParam().c_str())));
+ ASSERT_NE(authsecret, nullptr);
+
+ // Notify LSS to generate PIN code '1234' and corresponding secret.
+ (void)system("cmd lock_settings set-pin 1234");
+
+ // All tests must enroll the correct secret first as this cannot be changed
+ // without a factory reset and the order of tests could change.
+ authsecret->setPrimaryUserCredential(CORRECT_SECRET);
+ }
+
+ static void TearDownTestSuite() {
+ // clean up PIN code after testing
+ (void)system("cmd lock_settings clear --old 1234");
+ }
+
+ std::shared_ptr<IAuthSecret> authsecret;
+ std::vector<uint8_t> CORRECT_SECRET{61, 93, 124, 240, 5, 0, 7, 201, 9, 129, 11, 12, 0, 14, 0, 16};
+ std::vector<uint8_t> WRONG_SECRET{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
+};
+
+/* Provision the primary user with a secret. */
+TEST_P(AuthSecretAidlTest, provisionPrimaryUserCredential) {
+ // Secret provisioned by SetUp()
+}
+
+/* Provision the primary user with a secret and pass the secret again. */
+TEST_P(AuthSecretAidlTest, provisionPrimaryUserCredentialAndPassAgain) {
+ // Secret provisioned by SetUp()
+ authsecret->setPrimaryUserCredential(CORRECT_SECRET);
+}
+
+/* Provision the primary user with a secret and pass the secret again repeatedly. */
+TEST_P(AuthSecretAidlTest, provisionPrimaryUserCredentialAndPassAgainMultipleTimes) {
+ // Secret provisioned by SetUp()
+ constexpr int N = 5;
+ for (int i = 0; i < N; ++i) {
+ authsecret->setPrimaryUserCredential(CORRECT_SECRET);
+ }
+}
+
+/* Provision the primary user with a secret and then pass the wrong secret. This
+ * should never happen and is an framework bug if it does. As the secret is
+ * wrong, the HAL implementation may not be able to function correctly but it
+ * should fail gracefully. */
+TEST_P(AuthSecretAidlTest, provisionPrimaryUserCredentialAndWrongSecret) {
+ // Secret provisioned by SetUp()
+ authsecret->setPrimaryUserCredential(WRONG_SECRET);
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AuthSecretAidlTest);
+INSTANTIATE_TEST_SUITE_P(
+ PerInstance, AuthSecretAidlTest,
+ testing::ValuesIn(android::getAidlHalInstanceNames(IAuthSecret::descriptor)),
+ android::PrintInstanceNameToString);
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ ABinderProcess_setThreadPoolMaxThreadCount(1);
+ ABinderProcess_startThreadPool();
+ return RUN_ALL_TESTS();
+}
diff --git a/automotive/occupant_awareness/aidl/default/Android.bp b/automotive/occupant_awareness/aidl/default/Android.bp
index 1b2fba2..1e50930 100644
--- a/automotive/occupant_awareness/aidl/default/Android.bp
+++ b/automotive/occupant_awareness/aidl/default/Android.bp
@@ -27,6 +27,6 @@
"libbase",
"libbinder_ndk",
"libutils",
- "android.hardware.automotive.occupant_awareness-ndk_platform",
+ "android.hardware.automotive.occupant_awareness-V1-ndk_platform",
],
}
diff --git a/automotive/occupant_awareness/aidl/mock/Android.bp b/automotive/occupant_awareness/aidl/mock/Android.bp
index 4b30866..64ca733 100644
--- a/automotive/occupant_awareness/aidl/mock/Android.bp
+++ b/automotive/occupant_awareness/aidl/mock/Android.bp
@@ -27,6 +27,6 @@
"libbase",
"libbinder_ndk",
"libutils",
- "android.hardware.automotive.occupant_awareness-ndk_platform",
+ "android.hardware.automotive.occupant_awareness-V1-ndk_platform",
],
}
diff --git a/automotive/occupant_awareness/aidl/vts/functional/Android.bp b/automotive/occupant_awareness/aidl/vts/functional/Android.bp
index 514b0af..dbd0538 100644
--- a/automotive/occupant_awareness/aidl/vts/functional/Android.bp
+++ b/automotive/occupant_awareness/aidl/vts/functional/Android.bp
@@ -9,7 +9,7 @@
"libbinder",
],
static_libs: [
- "android.hardware.automotive.occupant_awareness-cpp",
+ "android.hardware.automotive.occupant_awareness-V1-cpp",
],
test_suites: [
"vts",
diff --git a/automotive/vehicle/2.0/default/Android.bp b/automotive/vehicle/2.0/default/Android.bp
index 4c0963d..312ad11 100644
--- a/automotive/vehicle/2.0/default/Android.bp
+++ b/automotive/vehicle/2.0/default/Android.bp
@@ -32,7 +32,7 @@
defaults: ["vhal_v2_0_defaults"],
shared_libs: [
"libbinder_ndk",
- "carwatchdog_aidl_interface-ndk_platform",
+ "carwatchdog_aidl_interface-V2-ndk_platform",
],
}
diff --git a/bluetooth/1.0/default/test/bluetooth_address_test.cc b/bluetooth/1.0/default/test/bluetooth_address_test.cc
index ee52d33..422d6a3 100644
--- a/bluetooth/1.0/default/test/bluetooth_address_test.cc
+++ b/bluetooth/1.0/default/test/bluetooth_address_test.cc
@@ -14,7 +14,6 @@
// limitations under the License.
//
-#include <cutils/properties.h>
#include <errno.h>
#include <fcntl.h>
#include <gtest/gtest.h>
@@ -39,12 +38,6 @@
"00:00:00:00:00:00";
constexpr uint8_t kZeros_bytes[BluetoothAddress::kBytes] = {0x00, 0x00, 0x00,
0x00, 0x00, 0x00};
-constexpr char kTestAddrBad1[BluetoothAddress::kStringLength + 1] =
- "bb:aa:dd:00:00:01";
-constexpr uint8_t kTestAddrBad1_bytes[BluetoothAddress::kBytes] = {
- 0xbb, 0xaa, 0xdd, 0x00, 0x00, 0x01};
-
-constexpr char kAddrPath[] = "/tmp/my_address_in_a_file.txt";
class BluetoothAddressTest : public ::testing::Test {
public:
@@ -120,45 +113,6 @@
EXPECT_FALSE(memcmp(addrA, addrB, BluetoothAddress::kStringLength) == 0);
}
-TEST_F(BluetoothAddressTest, get_local_address) {
- EXPECT_TRUE(property_set(PERSIST_BDADDR_PROPERTY, "") == 0);
- EXPECT_TRUE(property_set(FACTORY_BDADDR_PROPERTY, "") == 0);
- uint8_t address[BluetoothAddress::kBytes];
-
- // File contains a non-zero Address.
- FileWriteString(kAddrPath, kTestAddr1);
- EXPECT_TRUE(property_set(PROPERTY_BT_BDADDR_PATH, kAddrPath) == 0);
- EXPECT_TRUE(BluetoothAddress::get_local_address(address));
- EXPECT_TRUE(memcmp(address, kTestAddr1_bytes, BluetoothAddress::kBytes) == 0);
-
- // File contains a zero address. A random address will be generated.
- FileWriteString(kAddrPath, kZeros);
- EXPECT_TRUE(property_set(PROPERTY_BT_BDADDR_PATH, kAddrPath) == 0);
- EXPECT_TRUE(property_set(PERSIST_BDADDR_PROPERTY, kTestAddrBad1) == 0);
- EXPECT_TRUE(BluetoothAddress::get_local_address(address));
- EXPECT_TRUE(memcmp(address, kZeros_bytes, BluetoothAddress::kBytes) != 0);
- char prop[PROP_VALUE_MAX] = "Before reading";
- EXPECT_TRUE(property_get(PERSIST_BDADDR_PROPERTY, prop, NULL) ==
- BluetoothAddress::kStringLength);
- char address_str[BluetoothAddress::kStringLength + 1];
- BluetoothAddress::bytes_to_string(address, address_str);
- EXPECT_TRUE(memcmp(address_str, prop, BluetoothAddress::kStringLength) == 0);
-
- // Factory property contains an address.
- EXPECT_TRUE(property_set(PERSIST_BDADDR_PROPERTY, kTestAddrBad1) == 0);
- EXPECT_TRUE(property_set(FACTORY_BDADDR_PROPERTY, kTestAddr1) == 0);
- EXPECT_TRUE(BluetoothAddress::get_local_address(address));
- EXPECT_TRUE(memcmp(address, kTestAddr1_bytes, BluetoothAddress::kBytes) == 0);
-
- // Persistent property contains an address.
- memcpy(address, kTestAddrBad1_bytes, BluetoothAddress::kBytes);
- EXPECT_TRUE(property_set(PERSIST_BDADDR_PROPERTY, kTestAddr1) == 0);
- EXPECT_TRUE(property_set(FACTORY_BDADDR_PROPERTY, "") == 0);
- EXPECT_TRUE(property_set(PROPERTY_BT_BDADDR_PATH, "") == 0);
- EXPECT_TRUE(BluetoothAddress::get_local_address(address));
- EXPECT_TRUE(memcmp(address, kTestAddr1_bytes, BluetoothAddress::kBytes) == 0);
-}
-
} // namespace implementation
} // namespace V1_0
} // namespace bluetooth
diff --git a/bluetooth/audio/2.0/default/Android.bp b/bluetooth/audio/2.0/default/Android.bp
index 0db0028..8ed631e 100644
--- a/bluetooth/audio/2.0/default/Android.bp
+++ b/bluetooth/audio/2.0/default/Android.bp
@@ -23,24 +23,3 @@
"libutils",
],
}
-
-cc_library_shared {
- name: "libbluetooth_audio_session",
- defaults: ["hidl_defaults"],
- vendor: true,
- srcs: [
- "session/BluetoothAudioSession.cpp",
- "session/BluetoothAudioSupportedCodecsDB.cpp",
- ],
- export_include_dirs: ["session/"],
- header_libs: ["libhardware_headers"],
- shared_libs: [
- "android.hardware.bluetooth.audio@2.0",
- "libbase",
- "libcutils",
- "libfmq",
- "libhidlbase",
- "liblog",
- "libutils",
- ],
-}
diff --git a/bluetooth/audio/2.1/default/A2dpOffloadAudioProvider.cpp b/bluetooth/audio/2.1/default/A2dpOffloadAudioProvider.cpp
index b4a61b6..3fe1a4d 100644
--- a/bluetooth/audio/2.1/default/A2dpOffloadAudioProvider.cpp
+++ b/bluetooth/audio/2.1/default/A2dpOffloadAudioProvider.cpp
@@ -22,8 +22,8 @@
#include <fmq/MessageQueue.h>
#include <hidl/MQDescriptor.h>
-#include "BluetoothAudioSessionReport.h"
-#include "BluetoothAudioSupportedCodecsDB.h"
+#include "BluetoothAudioSessionReport_2_1.h"
+#include "BluetoothAudioSupportedCodecsDB_2_1.h"
namespace android {
namespace hardware {
@@ -32,7 +32,7 @@
namespace V2_1 {
namespace implementation {
-using ::android::bluetooth::audio::BluetoothAudioSessionReport;
+using ::android::bluetooth::audio::BluetoothAudioSessionReport_2_1;
using ::android::hardware::kSynchronizedReadWrite;
using ::android::hardware::MessageQueue;
using ::android::hardware::Void;
@@ -81,8 +81,8 @@
Return<void> A2dpOffloadAudioProvider::onSessionReady(
startSession_cb _hidl_cb) {
- BluetoothAudioSessionReport::OnSessionStarted(session_type_, stack_iface_,
- nullptr, audio_config_);
+ BluetoothAudioSessionReport_2_1::OnSessionStarted(session_type_, stack_iface_,
+ nullptr, audio_config_);
_hidl_cb(BluetoothAudioStatus::SUCCESS, DataMQ::Descriptor());
return Void();
}
diff --git a/bluetooth/audio/2.1/default/A2dpSoftwareAudioProvider.cpp b/bluetooth/audio/2.1/default/A2dpSoftwareAudioProvider.cpp
index a67c341..a37176b 100644
--- a/bluetooth/audio/2.1/default/A2dpSoftwareAudioProvider.cpp
+++ b/bluetooth/audio/2.1/default/A2dpSoftwareAudioProvider.cpp
@@ -20,8 +20,8 @@
#include <android-base/logging.h>
-#include "BluetoothAudioSessionReport.h"
-#include "BluetoothAudioSupportedCodecsDB.h"
+#include "BluetoothAudioSessionReport_2_1.h"
+#include "BluetoothAudioSupportedCodecsDB_2_1.h"
namespace android {
namespace hardware {
@@ -30,7 +30,7 @@
namespace V2_1 {
namespace implementation {
-using ::android::bluetooth::audio::BluetoothAudioSessionReport;
+using ::android::bluetooth::audio::BluetoothAudioSessionReport_2_1;
using ::android::hardware::Void;
using ::android::hardware::bluetooth::audio::V2_0::AudioConfiguration;
@@ -96,7 +96,7 @@
Return<void> A2dpSoftwareAudioProvider::onSessionReady(
startSession_cb _hidl_cb) {
if (mDataMQ && mDataMQ->isValid()) {
- BluetoothAudioSessionReport::OnSessionStarted(
+ BluetoothAudioSessionReport_2_1::OnSessionStarted(
session_type_, stack_iface_, mDataMQ->getDesc(), audio_config_);
_hidl_cb(BluetoothAudioStatus::SUCCESS, *mDataMQ->getDesc());
} else {
diff --git a/bluetooth/audio/2.1/default/Android.bp b/bluetooth/audio/2.1/default/Android.bp
index 5381fec..c05aa3f 100644
--- a/bluetooth/audio/2.1/default/Android.bp
+++ b/bluetooth/audio/2.1/default/Android.bp
@@ -16,29 +16,7 @@
"android.hardware.bluetooth.audio@2.0",
"android.hardware.bluetooth.audio@2.1",
"libbase",
- "libbluetooth_audio_session_2_1",
- "libcutils",
- "libfmq",
- "libhidlbase",
- "liblog",
- "libutils",
- ],
-}
-
-cc_library_shared {
- name: "libbluetooth_audio_session_2_1",
- defaults: ["hidl_defaults"],
- vendor: true,
- srcs: [
- "session/BluetoothAudioSession.cpp",
- "session/BluetoothAudioSupportedCodecsDB.cpp",
- ],
- export_include_dirs: ["session/"],
- header_libs: ["libhardware_headers"],
- shared_libs: [
- "android.hardware.bluetooth.audio@2.0",
- "android.hardware.bluetooth.audio@2.1",
- "libbase",
+ "libbluetooth_audio_session",
"libcutils",
"libfmq",
"libhidlbase",
diff --git a/bluetooth/audio/2.1/default/BluetoothAudioProvider.cpp b/bluetooth/audio/2.1/default/BluetoothAudioProvider.cpp
index 73fe06c..38889ae 100644
--- a/bluetooth/audio/2.1/default/BluetoothAudioProvider.cpp
+++ b/bluetooth/audio/2.1/default/BluetoothAudioProvider.cpp
@@ -20,8 +20,8 @@
#include <android-base/logging.h>
-#include "BluetoothAudioSessionReport.h"
-#include "BluetoothAudioSupportedCodecsDB.h"
+#include "BluetoothAudioSessionReport_2_1.h"
+#include "BluetoothAudioSupportedCodecsDB_2_1.h"
namespace android {
namespace hardware {
@@ -30,7 +30,7 @@
namespace V2_1 {
namespace implementation {
-using ::android::bluetooth::audio::BluetoothAudioSessionReport;
+using ::android::bluetooth::audio::BluetoothAudioSessionReport_2_1;
using ::android::hardware::kSynchronizedReadWrite;
using ::android::hardware::MessageQueue;
using ::android::hardware::Void;
@@ -105,8 +105,8 @@
* HAL server should start the streaming on data path.
*/
if (stack_iface_) {
- BluetoothAudioSessionReport::ReportControlStatus(session_type_, true,
- status);
+ BluetoothAudioSessionReport_2_1::ReportControlStatus(session_type_, true,
+ status);
} else {
LOG(WARNING) << __func__ << " - SessionType=" << toString(session_type_)
<< ", status=" << toString(status) << " has NO session";
@@ -125,8 +125,8 @@
* HAL server should suspend the streaming on data path.
*/
if (stack_iface_) {
- BluetoothAudioSessionReport::ReportControlStatus(session_type_, false,
- status);
+ BluetoothAudioSessionReport_2_1::ReportControlStatus(session_type_, false,
+ status);
} else {
LOG(WARNING) << __func__ << " - SessionType=" << toString(session_type_)
<< ", status=" << toString(status) << " has NO session";
@@ -139,7 +139,7 @@
LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_);
if (stack_iface_) {
- BluetoothAudioSessionReport::OnSessionEnded(session_type_);
+ BluetoothAudioSessionReport_2_1::OnSessionEnded(session_type_);
stack_iface_->unlinkToDeath(death_recipient_);
} else {
LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_)
diff --git a/bluetooth/audio/2.1/default/BluetoothAudioProvidersFactory.cpp b/bluetooth/audio/2.1/default/BluetoothAudioProvidersFactory.cpp
index adf2717..e1b1ac6 100644
--- a/bluetooth/audio/2.1/default/BluetoothAudioProvidersFactory.cpp
+++ b/bluetooth/audio/2.1/default/BluetoothAudioProvidersFactory.cpp
@@ -20,7 +20,7 @@
#include <android-base/logging.h>
-#include "BluetoothAudioSupportedCodecsDB.h"
+#include "BluetoothAudioSupportedCodecsDB_2_1.h"
namespace android {
namespace hardware {
diff --git a/bluetooth/audio/2.1/default/HearingAidAudioProvider.cpp b/bluetooth/audio/2.1/default/HearingAidAudioProvider.cpp
index aded7e1..712bd4f 100644
--- a/bluetooth/audio/2.1/default/HearingAidAudioProvider.cpp
+++ b/bluetooth/audio/2.1/default/HearingAidAudioProvider.cpp
@@ -20,8 +20,8 @@
#include <android-base/logging.h>
-#include "BluetoothAudioSessionReport.h"
-#include "BluetoothAudioSupportedCodecsDB.h"
+#include "BluetoothAudioSessionReport_2_1.h"
+#include "BluetoothAudioSupportedCodecsDB_2_1.h"
namespace android {
namespace hardware {
@@ -30,7 +30,7 @@
namespace V2_1 {
namespace implementation {
-using ::android::bluetooth::audio::BluetoothAudioSessionReport;
+using ::android::bluetooth::audio::BluetoothAudioSessionReport_2_1;
using ::android::hardware::Void;
using ::android::hardware::bluetooth::audio::V2_0::AudioConfiguration;
@@ -95,7 +95,7 @@
Return<void> HearingAidAudioProvider::onSessionReady(startSession_cb _hidl_cb) {
if (mDataMQ && mDataMQ->isValid()) {
- BluetoothAudioSessionReport::OnSessionStarted(
+ BluetoothAudioSessionReport_2_1::OnSessionStarted(
session_type_, stack_iface_, mDataMQ->getDesc(), audio_config_);
_hidl_cb(BluetoothAudioStatus::SUCCESS, *mDataMQ->getDesc());
} else {
diff --git a/bluetooth/audio/2.1/default/LeAudioAudioProvider.cpp b/bluetooth/audio/2.1/default/LeAudioAudioProvider.cpp
index 1fa2dce..2ebf6c5 100644
--- a/bluetooth/audio/2.1/default/LeAudioAudioProvider.cpp
+++ b/bluetooth/audio/2.1/default/LeAudioAudioProvider.cpp
@@ -21,8 +21,8 @@
#include <android-base/logging.h>
-#include "BluetoothAudioSessionReport.h"
-#include "BluetoothAudioSupportedCodecsDB.h"
+#include "BluetoothAudioSessionReport_2_1.h"
+#include "BluetoothAudioSupportedCodecsDB_2_1.h"
namespace android {
namespace hardware {
@@ -31,7 +31,7 @@
namespace V2_1 {
namespace implementation {
-using ::android::bluetooth::audio::BluetoothAudioSessionReport;
+using ::android::bluetooth::audio::BluetoothAudioSessionReport_2_1;
using ::android::hardware::Void;
using ::android::hardware::bluetooth::audio::V2_0::BitsPerSample;
using ::android::hardware::bluetooth::audio::V2_0::ChannelMode;
@@ -91,35 +91,30 @@
uint32_t kDataMqSize = 0;
switch (audioConfig.pcmConfig().sampleRate) {
+ case SampleRate::RATE_8000:
+ kDataMqSize = 8000;
+ break;
case SampleRate::RATE_16000:
kDataMqSize = 16000;
break;
case SampleRate::RATE_24000:
kDataMqSize = 24000;
break;
+ case SampleRate::RATE_32000:
+ kDataMqSize = 32000;
+ break;
case SampleRate::RATE_44100:
kDataMqSize = 44100;
break;
case SampleRate::RATE_48000:
kDataMqSize = 48000;
break;
- case SampleRate::RATE_88200:
- kDataMqSize = 88200;
- break;
- case SampleRate::RATE_96000:
- kDataMqSize = 96000;
- break;
- case SampleRate::RATE_176400:
- kDataMqSize = 176400;
- break;
- case SampleRate::RATE_192000:
- kDataMqSize = 192000;
- break;
default:
- /* This should never happen it would be caught while validating
- * parameters.
- */
- break;
+ LOG(WARNING) << __func__ << " - Unsupported sampling frequency="
+ << toString(audioConfig.pcmConfig());
+ _hidl_cb(BluetoothAudioStatus::UNSUPPORTED_CODEC_CONFIGURATION,
+ DataMQ::Descriptor());
+ return Void();
}
/* Number of samples per millisecond */
@@ -184,7 +179,7 @@
Return<void> LeAudioAudioProvider::onSessionReady(startSession_cb _hidl_cb) {
if (mDataMQ && mDataMQ->isValid()) {
- BluetoothAudioSessionReport::OnSessionStarted(
+ BluetoothAudioSessionReport_2_1::OnSessionStarted(
session_type_, stack_iface_, mDataMQ->getDesc(), audio_config_);
_hidl_cb(BluetoothAudioStatus::SUCCESS, *mDataMQ->getDesc());
} else {
diff --git a/bluetooth/audio/2.1/default/session/BluetoothAudioSession.cpp b/bluetooth/audio/2.1/default/session/BluetoothAudioSession.cpp
deleted file mode 100644
index ea2c54a..0000000
--- a/bluetooth/audio/2.1/default/session/BluetoothAudioSession.cpp
+++ /dev/null
@@ -1,467 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "BTAudioProviderSession"
-
-#include "BluetoothAudioSession.h"
-
-#include <android-base/logging.h>
-#include <android-base/stringprintf.h>
-
-namespace android {
-namespace bluetooth {
-namespace audio {
-
-using ::android::hardware::audio::common::V5_0::AudioContentType;
-using ::android::hardware::audio::common::V5_0::AudioUsage;
-using ::android::hardware::audio::common::V5_0::PlaybackTrackMetadata;
-using ::android::hardware::audio::common::V5_0::SourceMetadata;
-using ::android::hardware::bluetooth::audio::V2_0::CodecType;
-using ::android::hardware::bluetooth::audio::V2_0::TimeSpec;
-
-const CodecConfiguration BluetoothAudioSession::kInvalidCodecConfiguration = {
- .codecType = CodecType::UNKNOWN,
- .encodedAudioBitrate = 0x00000000,
- .peerMtu = 0xffff,
- .isScmstEnabled = false,
- .config = {}};
-AudioConfiguration BluetoothAudioSession::invalidSoftwareAudioConfiguration =
- {};
-AudioConfiguration BluetoothAudioSession::invalidOffloadAudioConfiguration = {};
-
-static constexpr int kFmqSendTimeoutMs = 1000; // 1000 ms timeout for sending
-static constexpr int kFmqReceiveTimeoutMs =
- 1000; // 1000 ms timeout for receiving
-static constexpr int kWritePollMs = 1; // polled non-blocking interval
-static constexpr int kReadPollMs = 1; // polled non-blocking interval
-
-static inline timespec timespec_convert_from_hal(const TimeSpec& TS) {
- return {.tv_sec = static_cast<long>(TS.tvSec),
- .tv_nsec = static_cast<long>(TS.tvNSec)};
-}
-
-BluetoothAudioSession::BluetoothAudioSession(const SessionType& session_type)
- : session_type_(session_type), stack_iface_(nullptr), mDataMQ(nullptr) {
- invalidSoftwareAudioConfiguration.pcmConfig(kInvalidPcmParameters);
- invalidOffloadAudioConfiguration.codecConfig(kInvalidCodecConfiguration);
-}
-
-// The report function is used to report that the Bluetooth stack has started
-// this session without any failure, and will invoke session_changed_cb_ to
-// notify those registered bluetooth_audio outputs
-void BluetoothAudioSession::OnSessionStarted(
- const sp<IBluetoothAudioPort> stack_iface, const DataMQ::Descriptor* dataMQ,
- const AudioConfiguration& audio_config) {
- std::lock_guard<std::recursive_mutex> guard(mutex_);
- if (stack_iface == nullptr) {
- LOG(ERROR) << __func__ << " - SessionType=" << toString(session_type_)
- << ", IBluetoothAudioPort Invalid";
- } else if (!UpdateAudioConfig(audio_config)) {
- LOG(ERROR) << __func__ << " - SessionType=" << toString(session_type_)
- << ", AudioConfiguration=" << toString(audio_config)
- << " Invalid";
- } else if (!UpdateDataPath(dataMQ)) {
- LOG(ERROR) << __func__ << " - SessionType=" << toString(session_type_)
- << " DataMQ Invalid";
- audio_config_ =
- (session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH
- ? kInvalidOffloadAudioConfiguration
- : kInvalidSoftwareAudioConfiguration);
- } else {
- stack_iface_ = stack_iface;
- LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_)
- << ", AudioConfiguration=" << toString(audio_config);
- ReportSessionStatus();
- }
-}
-
-// The report function is used to report that the Bluetooth stack has ended the
-// session, and will invoke session_changed_cb_ to notify registered
-// bluetooth_audio outputs
-void BluetoothAudioSession::OnSessionEnded() {
- std::lock_guard<std::recursive_mutex> guard(mutex_);
- bool toggled = IsSessionReady();
- LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_);
- audio_config_ = (session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH
- ? kInvalidOffloadAudioConfiguration
- : kInvalidSoftwareAudioConfiguration);
- stack_iface_ = nullptr;
- UpdateDataPath(nullptr);
- if (toggled) {
- ReportSessionStatus();
- }
-}
-
-// invoking the registered session_changed_cb_
-void BluetoothAudioSession::ReportSessionStatus() {
- // This is locked already by OnSessionStarted / OnSessionEnded
- if (observers_.empty()) {
- LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_)
- << " has NO port state observer";
- return;
- }
- for (auto& observer : observers_) {
- uint16_t cookie = observer.first;
- std::shared_ptr<struct PortStatusCallbacks> cb = observer.second;
- LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_)
- << " notify to bluetooth_audio=0x"
- << android::base::StringPrintf("%04x", cookie);
- cb->session_changed_cb_(cookie);
- }
-}
-
-// The report function is used to report that the Bluetooth stack has notified
-// the result of startStream or suspendStream, and will invoke
-// control_result_cb_ to notify registered bluetooth_audio outputs
-void BluetoothAudioSession::ReportControlStatus(
- bool start_resp, const BluetoothAudioStatus& status) {
- std::lock_guard<std::recursive_mutex> guard(mutex_);
- if (observers_.empty()) {
- LOG(WARNING) << __func__ << " - SessionType=" << toString(session_type_)
- << " has NO port state observer";
- return;
- }
- for (auto& observer : observers_) {
- uint16_t cookie = observer.first;
- std::shared_ptr<struct PortStatusCallbacks> cb = observer.second;
- LOG(INFO) << __func__ << " - status=" << toString(status)
- << " for SessionType=" << toString(session_type_)
- << ", bluetooth_audio=0x"
- << android::base::StringPrintf("%04x", cookie)
- << (start_resp ? " started" : " suspended");
- cb->control_result_cb_(cookie, start_resp, status);
- }
-}
-
-// The function helps to check if this session is ready or not
-// @return: true if the Bluetooth stack has started the specified session
-bool BluetoothAudioSession::IsSessionReady() {
- std::lock_guard<std::recursive_mutex> guard(mutex_);
- bool dataMQ_valid =
- (session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH ||
- (mDataMQ != nullptr && mDataMQ->isValid()));
- return stack_iface_ != nullptr && dataMQ_valid;
-}
-
-bool BluetoothAudioSession::UpdateDataPath(const DataMQ::Descriptor* dataMQ) {
- if (dataMQ == nullptr) {
- // usecase of reset by nullptr
- mDataMQ = nullptr;
- return true;
- }
- std::unique_ptr<DataMQ> tempDataMQ;
- tempDataMQ.reset(new DataMQ(*dataMQ));
- if (!tempDataMQ || !tempDataMQ->isValid()) {
- mDataMQ = nullptr;
- return false;
- }
- mDataMQ = std::move(tempDataMQ);
- return true;
-}
-
-bool BluetoothAudioSession::UpdateAudioConfig(
- const AudioConfiguration& audio_config) {
- bool is_software_session =
- (session_type_ == SessionType::A2DP_SOFTWARE_ENCODING_DATAPATH ||
- session_type_ == SessionType::HEARING_AID_SOFTWARE_ENCODING_DATAPATH ||
- session_type_ == SessionType::LE_AUDIO_SOFTWARE_ENCODING_DATAPATH ||
- session_type_ == SessionType::LE_AUDIO_SOFTWARE_DECODED_DATAPATH);
- bool is_offload_session =
- (session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH);
- auto audio_config_discriminator = audio_config.getDiscriminator();
- bool is_software_audio_config =
- (is_software_session &&
- audio_config_discriminator ==
- AudioConfiguration::hidl_discriminator::pcmConfig);
- bool is_offload_audio_config =
- (is_offload_session &&
- audio_config_discriminator ==
- AudioConfiguration::hidl_discriminator::codecConfig);
- if (!is_software_audio_config && !is_offload_audio_config) {
- return false;
- }
- audio_config_ = audio_config;
- return true;
-}
-
-// The control function helps the bluetooth_audio module to register
-// PortStatusCallbacks
-// @return: cookie - the assigned number to this bluetooth_audio output
-uint16_t BluetoothAudioSession::RegisterStatusCback(
- const PortStatusCallbacks& cbacks) {
- std::lock_guard<std::recursive_mutex> guard(mutex_);
- uint16_t cookie = ObserversCookieGetInitValue(session_type_);
- uint16_t cookie_upper_bound = ObserversCookieGetUpperBound(session_type_);
-
- while (cookie < cookie_upper_bound) {
- if (observers_.find(cookie) == observers_.end()) {
- break;
- }
- ++cookie;
- }
- if (cookie >= cookie_upper_bound) {
- LOG(ERROR) << __func__ << " - SessionType=" << toString(session_type_)
- << " has " << observers_.size()
- << " observers already (No Resource)";
- return kObserversCookieUndefined;
- }
- std::shared_ptr<struct PortStatusCallbacks> cb =
- std::make_shared<struct PortStatusCallbacks>();
- *cb = cbacks;
- observers_[cookie] = cb;
- return cookie;
-}
-
-// The control function helps the bluetooth_audio module to unregister
-// PortStatusCallbacks
-// @param: cookie - indicates which bluetooth_audio output is
-void BluetoothAudioSession::UnregisterStatusCback(uint16_t cookie) {
- std::lock_guard<std::recursive_mutex> guard(mutex_);
- if (observers_.erase(cookie) != 1) {
- LOG(WARNING) << __func__ << " - SessionType=" << toString(session_type_)
- << " no such provider=0x"
- << android::base::StringPrintf("%04x", cookie);
- }
-}
-
-// The control function is for the bluetooth_audio module to get the current
-// AudioConfiguration
-const AudioConfiguration& BluetoothAudioSession::GetAudioConfig() {
- std::lock_guard<std::recursive_mutex> guard(mutex_);
- if (IsSessionReady()) {
- return audio_config_;
- } else if (session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH) {
- return kInvalidOffloadAudioConfiguration;
- } else {
- return kInvalidSoftwareAudioConfiguration;
- }
-}
-
-// Those control functions are for the bluetooth_audio module to start, suspend,
-// stop stream, to check position, and to update metadata.
-bool BluetoothAudioSession::StartStream() {
- std::lock_guard<std::recursive_mutex> guard(mutex_);
- if (!IsSessionReady()) {
- LOG(DEBUG) << __func__ << " - SessionType=" << toString(session_type_)
- << " has NO session";
- return false;
- }
- auto hal_retval = stack_iface_->startStream();
- if (!hal_retval.isOk()) {
- LOG(WARNING) << __func__ << " - IBluetoothAudioPort SessionType="
- << toString(session_type_) << " failed";
- return false;
- }
- return true;
-}
-
-bool BluetoothAudioSession::SuspendStream() {
- std::lock_guard<std::recursive_mutex> guard(mutex_);
- if (!IsSessionReady()) {
- LOG(DEBUG) << __func__ << " - SessionType=" << toString(session_type_)
- << " has NO session";
- return false;
- }
- auto hal_retval = stack_iface_->suspendStream();
- if (!hal_retval.isOk()) {
- LOG(WARNING) << __func__ << " - IBluetoothAudioPort SessionType="
- << toString(session_type_) << " failed";
- return false;
- }
- return true;
-}
-
-void BluetoothAudioSession::StopStream() {
- std::lock_guard<std::recursive_mutex> guard(mutex_);
- if (!IsSessionReady()) {
- return;
- }
- auto hal_retval = stack_iface_->stopStream();
- if (!hal_retval.isOk()) {
- LOG(WARNING) << __func__ << " - IBluetoothAudioPort SessionType="
- << toString(session_type_) << " failed";
- }
-}
-
-bool BluetoothAudioSession::GetPresentationPosition(
- uint64_t* remote_delay_report_ns, uint64_t* total_bytes_readed,
- timespec* data_position) {
- std::lock_guard<std::recursive_mutex> guard(mutex_);
- if (!IsSessionReady()) {
- LOG(DEBUG) << __func__ << " - SessionType=" << toString(session_type_)
- << " has NO session";
- return false;
- }
- bool retval = false;
- auto hal_retval = stack_iface_->getPresentationPosition(
- [&retval, &remote_delay_report_ns, &total_bytes_readed, &data_position](
- BluetoothAudioStatus status,
- const uint64_t& remoteDeviceAudioDelayNanos,
- uint64_t transmittedOctets,
- const TimeSpec& transmittedOctetsTimeStamp) {
- if (status == BluetoothAudioStatus::SUCCESS) {
- if (remote_delay_report_ns)
- *remote_delay_report_ns = remoteDeviceAudioDelayNanos;
- if (total_bytes_readed) *total_bytes_readed = transmittedOctets;
- if (data_position)
- *data_position =
- timespec_convert_from_hal(transmittedOctetsTimeStamp);
- retval = true;
- }
- });
- if (!hal_retval.isOk()) {
- LOG(WARNING) << __func__ << " - IBluetoothAudioPort SessionType="
- << toString(session_type_) << " failed";
- return false;
- }
- return retval;
-}
-
-void BluetoothAudioSession::UpdateTracksMetadata(
- const struct source_metadata* source_metadata) {
- std::lock_guard<std::recursive_mutex> guard(mutex_);
- if (!IsSessionReady()) {
- LOG(DEBUG) << __func__ << " - SessionType=" << toString(session_type_)
- << " has NO session";
- return;
- }
-
- ssize_t track_count = source_metadata->track_count;
- LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_) << ", "
- << track_count << " track(s)";
- if (session_type_ == SessionType::A2DP_SOFTWARE_ENCODING_DATAPATH ||
- session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH) {
- return;
- }
-
- struct playback_track_metadata* track = source_metadata->tracks;
- SourceMetadata sourceMetadata;
- PlaybackTrackMetadata* halMetadata;
-
- sourceMetadata.tracks.resize(track_count);
- halMetadata = sourceMetadata.tracks.data();
- while (track_count && track) {
- halMetadata->usage = static_cast<AudioUsage>(track->usage);
- halMetadata->contentType =
- static_cast<AudioContentType>(track->content_type);
- halMetadata->gain = track->gain;
- LOG(VERBOSE) << __func__ << " - SessionType=" << toString(session_type_)
- << ", usage=" << toString(halMetadata->usage)
- << ", content=" << toString(halMetadata->contentType)
- << ", gain=" << halMetadata->gain;
- --track_count;
- ++track;
- ++halMetadata;
- }
- auto hal_retval = stack_iface_->updateMetadata(sourceMetadata);
- if (!hal_retval.isOk()) {
- LOG(WARNING) << __func__ << " - IBluetoothAudioPort SessionType="
- << toString(session_type_) << " failed";
- }
-}
-
-// The control function writes stream to FMQ
-size_t BluetoothAudioSession::OutWritePcmData(const void* buffer,
- size_t bytes) {
- if (buffer == nullptr || !bytes) return 0;
- size_t totalWritten = 0;
- int ms_timeout = kFmqSendTimeoutMs;
- do {
- std::unique_lock<std::recursive_mutex> lock(mutex_);
- if (!IsSessionReady()) break;
- size_t availableToWrite = mDataMQ->availableToWrite();
- if (availableToWrite) {
- if (availableToWrite > (bytes - totalWritten)) {
- availableToWrite = bytes - totalWritten;
- }
-
- if (!mDataMQ->write(static_cast<const uint8_t*>(buffer) + totalWritten,
- availableToWrite)) {
- ALOGE("FMQ datapath writing %zu/%zu failed", totalWritten, bytes);
- return totalWritten;
- }
- totalWritten += availableToWrite;
- } else if (ms_timeout >= kWritePollMs) {
- lock.unlock();
- usleep(kWritePollMs * 1000);
- ms_timeout -= kWritePollMs;
- } else {
- ALOGD("out data %zu/%zu overflow %d ms", totalWritten, bytes,
- (kFmqSendTimeoutMs - ms_timeout));
- return totalWritten;
- }
- } while (totalWritten < bytes);
- return totalWritten;
-}
-
-// The control function reads stream from FMQ
-size_t BluetoothAudioSession::InReadPcmData(void* buffer, size_t bytes) {
- if (buffer == nullptr || !bytes) return 0;
- size_t totalRead = 0;
- int ms_timeout = kFmqReceiveTimeoutMs;
- do {
- std::unique_lock<std::recursive_mutex> lock(mutex_);
- if (!IsSessionReady()) break;
- size_t availableToRead = mDataMQ->availableToRead();
- if (availableToRead) {
- if (availableToRead > (bytes - totalRead)) {
- availableToRead = bytes - totalRead;
- }
- if (!mDataMQ->read(static_cast<uint8_t*>(buffer) + totalRead,
- availableToRead)) {
- ALOGE("FMQ datapath reading %zu/%zu failed", totalRead, bytes);
- return totalRead;
- }
- totalRead += availableToRead;
- } else if (ms_timeout >= kReadPollMs) {
- lock.unlock();
- usleep(kReadPollMs * 1000);
- ms_timeout -= kReadPollMs;
- continue;
- } else {
- ALOGD("in data %zu/%zu overflow %d ms", totalRead, bytes,
- (kFmqReceiveTimeoutMs - ms_timeout));
- return totalRead;
- }
- } while (totalRead < bytes);
- return totalRead;
-}
-
-std::unique_ptr<BluetoothAudioSessionInstance>
- BluetoothAudioSessionInstance::instance_ptr =
- std::unique_ptr<BluetoothAudioSessionInstance>(
- new BluetoothAudioSessionInstance());
-
-// API to fetch the session
-std::shared_ptr<BluetoothAudioSession>
-BluetoothAudioSessionInstance::GetSessionInstance(
- const SessionType& session_type) {
- std::lock_guard<std::mutex> guard(instance_ptr->mutex_);
- if (!instance_ptr->sessions_map_.empty()) {
- auto entry = instance_ptr->sessions_map_.find(session_type);
- if (entry != instance_ptr->sessions_map_.end()) {
- return entry->second;
- }
- }
- std::shared_ptr<BluetoothAudioSession> session_ptr =
- std::make_shared<BluetoothAudioSession>(session_type);
- instance_ptr->sessions_map_[session_type] = session_ptr;
- return session_ptr;
-}
-
-} // namespace audio
-} // namespace bluetooth
-} // namespace android
diff --git a/bluetooth/audio/2.1/default/session/BluetoothAudioSession.h b/bluetooth/audio/2.1/default/session/BluetoothAudioSession.h
deleted file mode 100644
index 7bc12e6..0000000
--- a/bluetooth/audio/2.1/default/session/BluetoothAudioSession.h
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <mutex>
-#include <unordered_map>
-
-#include <android/hardware/bluetooth/audio/2.0/IBluetoothAudioPort.h>
-#include <android/hardware/bluetooth/audio/2.1/types.h>
-#include <fmq/MessageQueue.h>
-#include <hardware/audio.h>
-#include <hidl/MQDescriptor.h>
-
-namespace android {
-namespace bluetooth {
-namespace audio {
-
-using ::android::sp;
-using ::android::hardware::kSynchronizedReadWrite;
-using ::android::hardware::MessageQueue;
-using ::android::hardware::bluetooth::audio::V2_0::BitsPerSample;
-using ::android::hardware::bluetooth::audio::V2_0::ChannelMode;
-using ::android::hardware::bluetooth::audio::V2_0::CodecConfiguration;
-using ::android::hardware::bluetooth::audio::V2_0::IBluetoothAudioPort;
-using ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration;
-using ::android::hardware::bluetooth::audio::V2_1::PcmParameters;
-using ::android::hardware::bluetooth::audio::V2_1::SampleRate;
-using ::android::hardware::bluetooth::audio::V2_1::SessionType;
-
-using BluetoothAudioStatus =
- ::android::hardware::bluetooth::audio::V2_0::Status;
-
-using DataMQ = MessageQueue<uint8_t, kSynchronizedReadWrite>;
-
-static constexpr uint16_t kObserversCookieSize = 0x0010; // 0x0000 ~ 0x000f
-constexpr uint16_t kObserversCookieUndefined =
- (static_cast<uint16_t>(SessionType::UNKNOWN) << 8 & 0xff00);
-inline SessionType ObserversCookieGetSessionType(uint16_t cookie) {
- return static_cast<SessionType>(cookie >> 8 & 0x00ff);
-}
-inline uint16_t ObserversCookieGetInitValue(SessionType session_type) {
- return (static_cast<uint16_t>(session_type) << 8 & 0xff00);
-}
-inline uint16_t ObserversCookieGetUpperBound(SessionType session_type) {
- return (static_cast<uint16_t>(session_type) << 8 & 0xff00) +
- kObserversCookieSize;
-}
-
-// This presents the callbacks of started / suspended and session changed,
-// and the bluetooth_audio module uses to receive the status notification
-struct PortStatusCallbacks {
- // control_result_cb_ - when the Bluetooth stack reports results of
- // streamStarted or streamSuspended, the BluetoothAudioProvider will invoke
- // this callback to report to the bluetooth_audio module.
- // @param: cookie - indicates which bluetooth_audio output should handle
- // @param: start_resp - this report is for startStream or not
- // @param: status - the result of startStream
- std::function<void(uint16_t cookie, bool start_resp,
- const BluetoothAudioStatus& status)>
- control_result_cb_;
- // session_changed_cb_ - when the Bluetooth stack start / end session, the
- // BluetoothAudioProvider will invoke this callback to notify to the
- // bluetooth_audio module.
- // @param: cookie - indicates which bluetooth_audio output should handle
- std::function<void(uint16_t cookie)> session_changed_cb_;
-};
-
-class BluetoothAudioSession {
- private:
- // using recursive_mutex to allow hwbinder to re-enter again.
- std::recursive_mutex mutex_;
- SessionType session_type_;
-
- // audio control path to use for both software and offloading
- sp<IBluetoothAudioPort> stack_iface_;
- // Audio path (FMQ) for software encoding/decoded data
- std::unique_ptr<DataMQ> mDataMQ;
- // audio data configuration for both software and offloading
- AudioConfiguration audio_config_;
-
- static AudioConfiguration invalidSoftwareAudioConfiguration;
- static AudioConfiguration invalidOffloadAudioConfiguration;
-
- // saving those registered bluetooth_audio's callbacks
- std::unordered_map<uint16_t, std::shared_ptr<struct PortStatusCallbacks>>
- observers_;
-
- bool UpdateDataPath(const DataMQ::Descriptor* dataMQ);
- bool UpdateAudioConfig(const AudioConfiguration& audio_config);
- // invoking the registered session_changed_cb_
- void ReportSessionStatus();
-
- public:
- BluetoothAudioSession(const SessionType& session_type);
-
- // The function helps to check if this session is ready or not
- // @return: true if the Bluetooth stack has started the specified session
- bool IsSessionReady();
-
- // The report function is used to report that the Bluetooth stack has started
- // this session without any failure, and will invoke session_changed_cb_ to
- // notify those registered bluetooth_audio outputs
- void OnSessionStarted(const sp<IBluetoothAudioPort> stack_iface,
- const DataMQ::Descriptor* dataMQ,
- const AudioConfiguration& audio_config);
-
- // The report function is used to report that the Bluetooth stack has ended
- // the session, and will invoke session_changed_cb_ to notify registered
- // bluetooth_audio outputs
- void OnSessionEnded();
-
- // The report function is used to report that the Bluetooth stack has notified
- // the result of startStream or suspendStream, and will invoke
- // control_result_cb_ to notify registered bluetooth_audio outputs
- void ReportControlStatus(bool start_resp, const BluetoothAudioStatus& status);
-
- // The control function helps the bluetooth_audio module to register
- // PortStatusCallbacks
- // @return: cookie - the assigned number to this bluetooth_audio output
- uint16_t RegisterStatusCback(const PortStatusCallbacks& cbacks);
-
- // The control function helps the bluetooth_audio module to unregister
- // PortStatusCallbacks
- // @param: cookie - indicates which bluetooth_audio output is
- void UnregisterStatusCback(uint16_t cookie);
-
- // The control function is for the bluetooth_audio module to get the current
- // AudioConfiguration
- const AudioConfiguration& GetAudioConfig();
-
- // Those control functions are for the bluetooth_audio module to start,
- // suspend, stop stream, to check position, and to update metadata.
- bool StartStream();
- bool SuspendStream();
- void StopStream();
- bool GetPresentationPosition(uint64_t* remote_delay_report_ns,
- uint64_t* total_bytes_readed,
- timespec* data_position);
- void UpdateTracksMetadata(const struct source_metadata* source_metadata);
-
- // The control function writes stream to FMQ
- size_t OutWritePcmData(const void* buffer, size_t bytes);
- // The control function read stream from FMQ
- size_t InReadPcmData(void* buffer, size_t bytes);
-
- static constexpr PcmParameters kInvalidPcmParameters = {
- .sampleRate = SampleRate::RATE_UNKNOWN,
- .channelMode = ChannelMode::UNKNOWN,
- .bitsPerSample = BitsPerSample::BITS_UNKNOWN,
- .dataIntervalUs = 0,
- };
- // can't be constexpr because of non-literal type
- static const CodecConfiguration kInvalidCodecConfiguration;
-
- static constexpr AudioConfiguration& kInvalidSoftwareAudioConfiguration =
- invalidSoftwareAudioConfiguration;
- static constexpr AudioConfiguration& kInvalidOffloadAudioConfiguration =
- invalidOffloadAudioConfiguration;
-};
-
-class BluetoothAudioSessionInstance {
- public:
- // The API is to fetch the specified session
- static std::shared_ptr<BluetoothAudioSession> GetSessionInstance(
- const SessionType& session_type);
-
- private:
- static std::unique_ptr<BluetoothAudioSessionInstance> instance_ptr;
- std::mutex mutex_;
- std::unordered_map<SessionType, std::shared_ptr<BluetoothAudioSession>>
- sessions_map_;
-};
-
-} // namespace audio
-} // namespace bluetooth
-} // namespace android
diff --git a/bluetooth/audio/2.1/default/session/BluetoothAudioSessionControl.h b/bluetooth/audio/2.1/default/session/BluetoothAudioSessionControl.h
deleted file mode 100644
index 017a611..0000000
--- a/bluetooth/audio/2.1/default/session/BluetoothAudioSessionControl.h
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include "BluetoothAudioSession.h"
-
-namespace android {
-namespace bluetooth {
-namespace audio {
-
-class BluetoothAudioSessionControl {
- public:
- // The control API helps to check if session is ready or not
- // @return: true if the Bluetooth stack has started th specified session
- static bool IsSessionReady(const SessionType& session_type) {
- std::shared_ptr<BluetoothAudioSession> session_ptr =
- BluetoothAudioSessionInstance::GetSessionInstance(session_type);
- if (session_ptr != nullptr) {
- return session_ptr->IsSessionReady();
- }
- return false;
- }
-
- // The control API helps the bluetooth_audio module to register
- // PortStatusCallbacks
- // @return: cookie - the assigned number to this bluetooth_audio output
- static uint16_t RegisterControlResultCback(
- const SessionType& session_type, const PortStatusCallbacks& cbacks) {
- std::shared_ptr<BluetoothAudioSession> session_ptr =
- BluetoothAudioSessionInstance::GetSessionInstance(session_type);
- if (session_ptr != nullptr) {
- return session_ptr->RegisterStatusCback(cbacks);
- }
- return kObserversCookieUndefined;
- }
-
- // The control API helps the bluetooth_audio module to unregister
- // PortStatusCallbacks
- // @param: cookie - indicates which bluetooth_audio output is
- static void UnregisterControlResultCback(const SessionType& session_type,
- uint16_t cookie) {
- std::shared_ptr<BluetoothAudioSession> session_ptr =
- BluetoothAudioSessionInstance::GetSessionInstance(session_type);
- if (session_ptr != nullptr) {
- session_ptr->UnregisterStatusCback(cookie);
- }
- }
-
- // The control API for the bluetooth_audio module to get current
- // AudioConfiguration
- static const AudioConfiguration& GetAudioConfig(
- const SessionType& session_type) {
- std::shared_ptr<BluetoothAudioSession> session_ptr =
- BluetoothAudioSessionInstance::GetSessionInstance(session_type);
- if (session_ptr != nullptr) {
- return session_ptr->GetAudioConfig();
- } else if (session_type == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH) {
- return BluetoothAudioSession::kInvalidOffloadAudioConfiguration;
- } else {
- return BluetoothAudioSession::kInvalidSoftwareAudioConfiguration;
- }
- }
-
- // Those control APIs for the bluetooth_audio module to start / suspend / stop
- // stream, to check position, and to update metadata.
- static bool StartStream(const SessionType& session_type) {
- std::shared_ptr<BluetoothAudioSession> session_ptr =
- BluetoothAudioSessionInstance::GetSessionInstance(session_type);
- if (session_ptr != nullptr) {
- return session_ptr->StartStream();
- }
- return false;
- }
-
- static bool SuspendStream(const SessionType& session_type) {
- std::shared_ptr<BluetoothAudioSession> session_ptr =
- BluetoothAudioSessionInstance::GetSessionInstance(session_type);
- if (session_ptr != nullptr) {
- return session_ptr->SuspendStream();
- }
- return false;
- }
-
- static void StopStream(const SessionType& session_type) {
- std::shared_ptr<BluetoothAudioSession> session_ptr =
- BluetoothAudioSessionInstance::GetSessionInstance(session_type);
- if (session_ptr != nullptr) {
- session_ptr->StopStream();
- }
- }
-
- static bool GetPresentationPosition(const SessionType& session_type,
- uint64_t* remote_delay_report_ns,
- uint64_t* total_bytes_readed,
- timespec* data_position) {
- std::shared_ptr<BluetoothAudioSession> session_ptr =
- BluetoothAudioSessionInstance::GetSessionInstance(session_type);
- if (session_ptr != nullptr) {
- return session_ptr->GetPresentationPosition(
- remote_delay_report_ns, total_bytes_readed, data_position);
- }
- return false;
- }
-
- static void UpdateTracksMetadata(
- const SessionType& session_type,
- const struct source_metadata* source_metadata) {
- std::shared_ptr<BluetoothAudioSession> session_ptr =
- BluetoothAudioSessionInstance::GetSessionInstance(session_type);
- if (session_ptr != nullptr) {
- session_ptr->UpdateTracksMetadata(source_metadata);
- }
- }
-
- // The control API writes stream to FMQ
- static size_t OutWritePcmData(const SessionType& session_type,
- const void* buffer, size_t bytes) {
- std::shared_ptr<BluetoothAudioSession> session_ptr =
- BluetoothAudioSessionInstance::GetSessionInstance(session_type);
- if (session_ptr != nullptr) {
- return session_ptr->OutWritePcmData(buffer, bytes);
- }
- return 0;
- }
-
- // The control API reads stream from FMQ
- static size_t InReadPcmData(const SessionType& session_type, void* buffer,
- size_t bytes) {
- std::shared_ptr<BluetoothAudioSession> session_ptr =
- BluetoothAudioSessionInstance::GetSessionInstance(session_type);
- if (session_ptr != nullptr) {
- return session_ptr->InReadPcmData(buffer, bytes);
- }
- return 0;
- }
-};
-
-} // namespace audio
-} // namespace bluetooth
-} // namespace android
diff --git a/bluetooth/audio/2.1/default/session/BluetoothAudioSessionReport.h b/bluetooth/audio/2.1/default/session/BluetoothAudioSessionReport.h
deleted file mode 100644
index 267bf8f..0000000
--- a/bluetooth/audio/2.1/default/session/BluetoothAudioSessionReport.h
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include "BluetoothAudioSession.h"
-
-namespace android {
-namespace bluetooth {
-namespace audio {
-
-class BluetoothAudioSessionReport {
- public:
- // The API reports the Bluetooth stack has started the session, and will
- // inform registered bluetooth_audio session
- static void OnSessionStarted(const SessionType& session_type,
- const sp<IBluetoothAudioPort> host_iface,
- const DataMQ::Descriptor* dataMQ,
- const AudioConfiguration& audio_config) {
- std::shared_ptr<BluetoothAudioSession> session_ptr =
- BluetoothAudioSessionInstance::GetSessionInstance(session_type);
- if (session_ptr != nullptr) {
- session_ptr->OnSessionStarted(host_iface, dataMQ, audio_config);
- }
- }
- // The API reports the Bluetooth stack has ended the session, and will
- // inform registered bluetooth_audio outputs
- static void OnSessionEnded(const SessionType& session_type) {
- std::shared_ptr<BluetoothAudioSession> session_ptr =
- BluetoothAudioSessionInstance::GetSessionInstance(session_type);
- if (session_ptr != nullptr) {
- session_ptr->OnSessionEnded();
- }
- }
- // The API reports the Bluetooth stack has replied the result of startStream
- // or suspendStream, and will inform registered bluetooth_audio outputs
- static void ReportControlStatus(const SessionType& session_type,
- const bool& start_resp,
- const BluetoothAudioStatus& status) {
- std::shared_ptr<BluetoothAudioSession> session_ptr =
- BluetoothAudioSessionInstance::GetSessionInstance(session_type);
- if (session_ptr != nullptr) {
- session_ptr->ReportControlStatus(start_resp, status);
- }
- }
-};
-
-} // namespace audio
-} // namespace bluetooth
-} // namespace android
diff --git a/bluetooth/audio/2.1/default/session/BluetoothAudioSupportedCodecsDB.cpp b/bluetooth/audio/2.1/default/session/BluetoothAudioSupportedCodecsDB.cpp
deleted file mode 100644
index d15db49..0000000
--- a/bluetooth/audio/2.1/default/session/BluetoothAudioSupportedCodecsDB.cpp
+++ /dev/null
@@ -1,487 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "BTAudioProviderSessionCodecsDB"
-
-#include "BluetoothAudioSupportedCodecsDB.h"
-
-#include <android-base/logging.h>
-
-namespace android {
-namespace bluetooth {
-namespace audio {
-
-using ::android::hardware::bluetooth::audio::V2_0::AacObjectType;
-using ::android::hardware::bluetooth::audio::V2_0::AacParameters;
-using ::android::hardware::bluetooth::audio::V2_0::AacVariableBitRate;
-using ::android::hardware::bluetooth::audio::V2_0::AptxParameters;
-using ::android::hardware::bluetooth::audio::V2_0::BitsPerSample;
-using ::android::hardware::bluetooth::audio::V2_0::ChannelMode;
-using ::android::hardware::bluetooth::audio::V2_0::CodecType;
-using ::android::hardware::bluetooth::audio::V2_0::LdacChannelMode;
-using ::android::hardware::bluetooth::audio::V2_0::LdacParameters;
-using ::android::hardware::bluetooth::audio::V2_0::LdacQualityIndex;
-using ::android::hardware::bluetooth::audio::V2_0::SbcAllocMethod;
-using ::android::hardware::bluetooth::audio::V2_0::SbcBlockLength;
-using ::android::hardware::bluetooth::audio::V2_0::SbcChannelMode;
-using ::android::hardware::bluetooth::audio::V2_0::SbcNumSubbands;
-using ::android::hardware::bluetooth::audio::V2_0::SbcParameters;
-using ::android::hardware::bluetooth::audio::V2_1::SampleRate;
-
-// Default Supported PCM Parameters
-static const ::android::hardware::bluetooth::audio::V2_0::PcmParameters
- kDefaultSoftwarePcmCapabilities = {
- .sampleRate = static_cast<
- android::hardware::bluetooth::audio::V2_0::SampleRate>(
- android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_44100 |
- android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_48000 |
- android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_88200 |
- android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_96000 |
- android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_16000),
- .channelMode =
- static_cast<ChannelMode>(ChannelMode::MONO | ChannelMode::STEREO),
- .bitsPerSample = static_cast<BitsPerSample>(BitsPerSample::BITS_16 |
- BitsPerSample::BITS_24 |
- BitsPerSample::BITS_32)};
-
-static const PcmParameters kDefaultSoftwarePcmCapabilities_2_1 = {
- .sampleRate = static_cast<SampleRate>(
- SampleRate::RATE_48000 | SampleRate::RATE_44100 |
- SampleRate::RATE_32000 | SampleRate::RATE_24000 |
- SampleRate::RATE_16000 | SampleRate::RATE_8000),
- .channelMode =
- static_cast<ChannelMode>(ChannelMode::MONO | ChannelMode::STEREO),
- .bitsPerSample = static_cast<BitsPerSample>(BitsPerSample::BITS_16)};
-
-// Default Supported Codecs
-// SBC: mSampleRate:(44100), mBitsPerSample:(16), mChannelMode:(MONO|STEREO)
-// all blocks | subbands 8 | Loudness
-static const SbcParameters kDefaultOffloadSbcCapability = {
- .sampleRate =
- android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_44100,
- .channelMode = static_cast<SbcChannelMode>(SbcChannelMode::MONO |
- SbcChannelMode::JOINT_STEREO),
- .blockLength = static_cast<SbcBlockLength>(
- SbcBlockLength::BLOCKS_4 | SbcBlockLength::BLOCKS_8 |
- SbcBlockLength::BLOCKS_12 | SbcBlockLength::BLOCKS_16),
- .numSubbands = SbcNumSubbands::SUBBAND_8,
- .allocMethod = SbcAllocMethod::ALLOC_MD_L,
- .bitsPerSample = BitsPerSample::BITS_16,
- .minBitpool = 2,
- .maxBitpool = 53};
-
-// AAC: mSampleRate:(44100), mBitsPerSample:(16), mChannelMode:(STEREO)
-static const AacParameters kDefaultOffloadAacCapability = {
- .objectType = AacObjectType::MPEG2_LC,
- .sampleRate =
- android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_44100,
- .channelMode = ChannelMode::STEREO,
- .variableBitRateEnabled = AacVariableBitRate::ENABLED,
- .bitsPerSample = BitsPerSample::BITS_16};
-
-// LDAC: mSampleRate:(44100|48000|88200|96000), mBitsPerSample:(16|24|32),
-// mChannelMode:(DUAL|STEREO)
-static const LdacParameters kDefaultOffloadLdacCapability = {
- .sampleRate =
- static_cast<android::hardware::bluetooth::audio::V2_0::SampleRate>(
- android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_44100 |
- android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_48000 |
- android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_88200 |
- android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_96000),
- .channelMode = static_cast<LdacChannelMode>(LdacChannelMode::DUAL |
- LdacChannelMode::STEREO),
- .qualityIndex = LdacQualityIndex::QUALITY_HIGH,
- .bitsPerSample = static_cast<BitsPerSample>(BitsPerSample::BITS_16 |
- BitsPerSample::BITS_24 |
- BitsPerSample::BITS_32)};
-
-// aptX: mSampleRate:(44100|48000), mBitsPerSample:(16), mChannelMode:(STEREO)
-static const AptxParameters kDefaultOffloadAptxCapability = {
- .sampleRate =
- static_cast<android::hardware::bluetooth::audio::V2_0::SampleRate>(
- android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_44100 |
- android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_48000),
- .channelMode = ChannelMode::STEREO,
- .bitsPerSample = BitsPerSample::BITS_16,
-};
-
-// aptX HD: mSampleRate:(44100|48000), mBitsPerSample:(24),
-// mChannelMode:(STEREO)
-static const AptxParameters kDefaultOffloadAptxHdCapability = {
- .sampleRate =
- static_cast<android::hardware::bluetooth::audio::V2_0::SampleRate>(
- android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_44100 |
- android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_48000),
- .channelMode = ChannelMode::STEREO,
- .bitsPerSample = BitsPerSample::BITS_24,
-};
-
-const std::vector<CodecCapabilities> kDefaultOffloadA2dpCodecCapabilities = {
- {.codecType = CodecType::SBC, .capabilities = {}},
- {.codecType = CodecType::AAC, .capabilities = {}},
- {.codecType = CodecType::LDAC, .capabilities = {}},
- {.codecType = CodecType::APTX, .capabilities = {}},
- {.codecType = CodecType::APTX_HD, .capabilities = {}}};
-
-static bool IsSingleBit(uint32_t bitmasks, uint32_t bitfield) {
- bool single = false;
- uint32_t test_bit = 0x00000001;
- while (test_bit <= bitmasks && test_bit <= bitfield) {
- if (bitfield & test_bit && bitmasks & test_bit) {
- if (single) return false;
- single = true;
- }
- if (test_bit == 0x80000000) break;
- test_bit <<= 1;
- }
- return single;
-}
-
-static bool IsOffloadSbcConfigurationValid(
- const CodecConfiguration::CodecSpecific& codec_specific);
-static bool IsOffloadAacConfigurationValid(
- const CodecConfiguration::CodecSpecific& codec_specific);
-static bool IsOffloadLdacConfigurationValid(
- const CodecConfiguration::CodecSpecific& codec_specific);
-static bool IsOffloadAptxConfigurationValid(
- const CodecConfiguration::CodecSpecific& codec_specific);
-static bool IsOffloadAptxHdConfigurationValid(
- const CodecConfiguration::CodecSpecific& codec_specific);
-
-static bool IsOffloadSbcConfigurationValid(
- const CodecConfiguration::CodecSpecific& codec_specific) {
- if (codec_specific.getDiscriminator() !=
- CodecConfiguration::CodecSpecific::hidl_discriminator::sbcConfig) {
- LOG(WARNING) << __func__
- << ": Invalid CodecSpecific=" << toString(codec_specific);
- return false;
- }
- const SbcParameters sbc_data = codec_specific.sbcConfig();
- if (!IsSingleBit(static_cast<uint32_t>(sbc_data.sampleRate), 0xff) ||
- !IsSingleBit(static_cast<uint32_t>(sbc_data.channelMode), 0x0f) ||
- !IsSingleBit(static_cast<uint32_t>(sbc_data.blockLength), 0xf0) ||
- !IsSingleBit(static_cast<uint32_t>(sbc_data.numSubbands), 0x0c) ||
- !IsSingleBit(static_cast<uint32_t>(sbc_data.allocMethod), 0x03) ||
- !IsSingleBit(static_cast<uint32_t>(sbc_data.bitsPerSample), 0x07) ||
- sbc_data.minBitpool > sbc_data.maxBitpool) {
- LOG(WARNING) << __func__
- << ": Invalid CodecSpecific=" << toString(codec_specific);
- return false;
- } else if ((sbc_data.sampleRate & kDefaultOffloadSbcCapability.sampleRate) &&
- (sbc_data.channelMode &
- kDefaultOffloadSbcCapability.channelMode) &&
- (sbc_data.blockLength &
- kDefaultOffloadSbcCapability.blockLength) &&
- (sbc_data.numSubbands &
- kDefaultOffloadSbcCapability.numSubbands) &&
- (sbc_data.allocMethod &
- kDefaultOffloadSbcCapability.allocMethod) &&
- (sbc_data.bitsPerSample &
- kDefaultOffloadSbcCapability.bitsPerSample) &&
- (kDefaultOffloadSbcCapability.minBitpool <= sbc_data.minBitpool &&
- sbc_data.maxBitpool <= kDefaultOffloadSbcCapability.maxBitpool)) {
- return true;
- }
- LOG(WARNING) << __func__
- << ": Unsupported CodecSpecific=" << toString(codec_specific);
- return false;
-}
-
-static bool IsOffloadAacConfigurationValid(
- const CodecConfiguration::CodecSpecific& codec_specific) {
- if (codec_specific.getDiscriminator() !=
- CodecConfiguration::CodecSpecific::hidl_discriminator::aacConfig) {
- LOG(WARNING) << __func__
- << ": Invalid CodecSpecific=" << toString(codec_specific);
- return false;
- }
- const AacParameters aac_data = codec_specific.aacConfig();
- if (!IsSingleBit(static_cast<uint32_t>(aac_data.objectType), 0xf0) ||
- !IsSingleBit(static_cast<uint32_t>(aac_data.sampleRate), 0xff) ||
- !IsSingleBit(static_cast<uint32_t>(aac_data.channelMode), 0x03) ||
- !IsSingleBit(static_cast<uint32_t>(aac_data.bitsPerSample), 0x07)) {
- LOG(WARNING) << __func__
- << ": Invalid CodecSpecific=" << toString(codec_specific);
- return false;
- } else if ((aac_data.objectType & kDefaultOffloadAacCapability.objectType) &&
- (aac_data.sampleRate & kDefaultOffloadAacCapability.sampleRate) &&
- (aac_data.channelMode &
- kDefaultOffloadAacCapability.channelMode) &&
- (aac_data.variableBitRateEnabled == AacVariableBitRate::DISABLED ||
- kDefaultOffloadAacCapability.variableBitRateEnabled ==
- AacVariableBitRate::ENABLED) &&
- (aac_data.bitsPerSample &
- kDefaultOffloadAacCapability.bitsPerSample)) {
- return true;
- }
- LOG(WARNING) << __func__
- << ": Unsupported CodecSpecific=" << toString(codec_specific);
- return false;
-}
-
-static bool IsOffloadLdacConfigurationValid(
- const CodecConfiguration::CodecSpecific& codec_specific) {
- if (codec_specific.getDiscriminator() !=
- CodecConfiguration::CodecSpecific::hidl_discriminator::ldacConfig) {
- LOG(WARNING) << __func__
- << ": Invalid CodecSpecific=" << toString(codec_specific);
- return false;
- }
- const LdacParameters ldac_data = codec_specific.ldacConfig();
- if (!IsSingleBit(static_cast<uint32_t>(ldac_data.sampleRate), 0xff) ||
- !IsSingleBit(static_cast<uint32_t>(ldac_data.channelMode), 0x07) ||
- (ldac_data.qualityIndex > LdacQualityIndex::QUALITY_LOW &&
- ldac_data.qualityIndex != LdacQualityIndex::QUALITY_ABR) ||
- !IsSingleBit(static_cast<uint32_t>(ldac_data.bitsPerSample), 0x07)) {
- LOG(WARNING) << __func__
- << ": Invalid CodecSpecific=" << toString(codec_specific);
- return false;
- } else if ((ldac_data.sampleRate &
- kDefaultOffloadLdacCapability.sampleRate) &&
- (ldac_data.channelMode &
- kDefaultOffloadLdacCapability.channelMode) &&
- (ldac_data.bitsPerSample &
- kDefaultOffloadLdacCapability.bitsPerSample)) {
- return true;
- }
- LOG(WARNING) << __func__
- << ": Unsupported CodecSpecific=" << toString(codec_specific);
- return false;
-}
-
-static bool IsOffloadAptxConfigurationValid(
- const CodecConfiguration::CodecSpecific& codec_specific) {
- if (codec_specific.getDiscriminator() !=
- CodecConfiguration::CodecSpecific::hidl_discriminator::aptxConfig) {
- LOG(WARNING) << __func__
- << ": Invalid CodecSpecific=" << toString(codec_specific);
- return false;
- }
- const AptxParameters aptx_data = codec_specific.aptxConfig();
- if (!IsSingleBit(static_cast<uint32_t>(aptx_data.sampleRate), 0xff) ||
- !IsSingleBit(static_cast<uint32_t>(aptx_data.channelMode), 0x03) ||
- !IsSingleBit(static_cast<uint32_t>(aptx_data.bitsPerSample), 0x07)) {
- LOG(WARNING) << __func__
- << ": Invalid CodecSpecific=" << toString(codec_specific);
- return false;
- } else if ((aptx_data.sampleRate &
- kDefaultOffloadAptxCapability.sampleRate) &&
- (aptx_data.channelMode &
- kDefaultOffloadAptxCapability.channelMode) &&
- (aptx_data.bitsPerSample &
- kDefaultOffloadAptxCapability.bitsPerSample)) {
- return true;
- }
- LOG(WARNING) << __func__
- << ": Unsupported CodecSpecific=" << toString(codec_specific);
- return false;
-}
-
-static bool IsOffloadAptxHdConfigurationValid(
- const CodecConfiguration::CodecSpecific& codec_specific) {
- if (codec_specific.getDiscriminator() !=
- CodecConfiguration::CodecSpecific::hidl_discriminator::aptxConfig) {
- LOG(WARNING) << __func__
- << ": Invalid CodecSpecific=" << toString(codec_specific);
- return false;
- }
- const AptxParameters aptx_data = codec_specific.aptxConfig();
- if (!IsSingleBit(static_cast<uint32_t>(aptx_data.sampleRate), 0xff) ||
- !IsSingleBit(static_cast<uint32_t>(aptx_data.channelMode), 0x03) ||
- !IsSingleBit(static_cast<uint32_t>(aptx_data.bitsPerSample), 0x07)) {
- LOG(WARNING) << __func__
- << ": Invalid CodecSpecific=" << toString(codec_specific);
- return false;
- } else if ((aptx_data.sampleRate &
- kDefaultOffloadAptxHdCapability.sampleRate) &&
- (aptx_data.channelMode &
- kDefaultOffloadAptxHdCapability.channelMode) &&
- (aptx_data.bitsPerSample &
- kDefaultOffloadAptxHdCapability.bitsPerSample)) {
- return true;
- }
- LOG(WARNING) << __func__
- << ": Unsupported CodecSpecific=" << toString(codec_specific);
- return false;
-}
-
-std::vector<::android::hardware::bluetooth::audio::V2_0::PcmParameters>
-GetSoftwarePcmCapabilities() {
- return std::vector<
- ::android::hardware::bluetooth::audio::V2_0::PcmParameters>(
- 1, kDefaultSoftwarePcmCapabilities);
-}
-
-std::vector<PcmParameters> GetSoftwarePcmCapabilities_2_1() {
- return std::vector<PcmParameters>(1, kDefaultSoftwarePcmCapabilities_2_1);
-}
-
-std::vector<CodecCapabilities> GetOffloadCodecCapabilities(
- const ::android::hardware::bluetooth::audio::V2_0::SessionType&
- session_type) {
- return GetOffloadCodecCapabilities(static_cast<SessionType>(session_type));
-}
-
-std::vector<CodecCapabilities> GetOffloadCodecCapabilities(
- const SessionType& session_type) {
- if (session_type != SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH) {
- return std::vector<CodecCapabilities>(0);
- }
- std::vector<CodecCapabilities> offload_a2dp_codec_capabilities =
- kDefaultOffloadA2dpCodecCapabilities;
- for (auto& codec_capability : offload_a2dp_codec_capabilities) {
- switch (codec_capability.codecType) {
- case CodecType::SBC:
- codec_capability.capabilities.sbcCapabilities(
- kDefaultOffloadSbcCapability);
- break;
- case CodecType::AAC:
- codec_capability.capabilities.aacCapabilities(
- kDefaultOffloadAacCapability);
- break;
- case CodecType::LDAC:
- codec_capability.capabilities.ldacCapabilities(
- kDefaultOffloadLdacCapability);
- break;
- case CodecType::APTX:
- codec_capability.capabilities.aptxCapabilities(
- kDefaultOffloadAptxCapability);
- break;
- case CodecType::APTX_HD:
- codec_capability.capabilities.aptxCapabilities(
- kDefaultOffloadAptxHdCapability);
- break;
- case CodecType::UNKNOWN:
- codec_capability = {};
- break;
- }
- }
- return offload_a2dp_codec_capabilities;
-}
-
-bool IsSoftwarePcmConfigurationValid(
- const ::android::hardware::bluetooth::audio::V2_0::PcmParameters&
- pcm_config) {
- if ((pcm_config.sampleRate !=
- android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_44100 &&
- pcm_config.sampleRate !=
- android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_48000 &&
- pcm_config.sampleRate !=
- android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_88200 &&
- pcm_config.sampleRate !=
- android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_96000 &&
- pcm_config.sampleRate !=
- android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_16000 &&
- pcm_config.sampleRate !=
- android::hardware::bluetooth::audio::V2_0::SampleRate::RATE_24000) ||
- (pcm_config.bitsPerSample != BitsPerSample::BITS_16 &&
- pcm_config.bitsPerSample != BitsPerSample::BITS_24 &&
- pcm_config.bitsPerSample != BitsPerSample::BITS_32) ||
- (pcm_config.channelMode != ChannelMode::MONO &&
- pcm_config.channelMode != ChannelMode::STEREO)) {
- LOG(WARNING) << __func__
- << ": Invalid PCM Configuration=" << toString(pcm_config);
- return false;
- } else if (pcm_config.sampleRate &
- kDefaultSoftwarePcmCapabilities.sampleRate &&
- pcm_config.bitsPerSample &
- kDefaultSoftwarePcmCapabilities.bitsPerSample &&
- pcm_config.channelMode &
- kDefaultSoftwarePcmCapabilities.channelMode) {
- return true;
- }
- LOG(WARNING) << __func__
- << ": Unsupported PCM Configuration=" << toString(pcm_config);
- return false;
-}
-
-bool IsSoftwarePcmConfigurationValid_2_1(const PcmParameters& pcm_config) {
- if ((pcm_config.sampleRate != SampleRate::RATE_44100 &&
- pcm_config.sampleRate != SampleRate::RATE_48000 &&
- pcm_config.sampleRate != SampleRate::RATE_88200 &&
- pcm_config.sampleRate != SampleRate::RATE_96000 &&
- pcm_config.sampleRate != SampleRate::RATE_16000 &&
- pcm_config.sampleRate != SampleRate::RATE_24000) ||
- (pcm_config.bitsPerSample != BitsPerSample::BITS_16 &&
- pcm_config.bitsPerSample != BitsPerSample::BITS_24 &&
- pcm_config.bitsPerSample != BitsPerSample::BITS_32) ||
- (pcm_config.channelMode != ChannelMode::MONO &&
- pcm_config.channelMode != ChannelMode::STEREO)) {
- LOG(WARNING) << __func__
- << ": Invalid PCM Configuration=" << toString(pcm_config);
- return false;
- } else if (pcm_config.sampleRate &
- kDefaultSoftwarePcmCapabilities_2_1.sampleRate &&
- pcm_config.bitsPerSample &
- kDefaultSoftwarePcmCapabilities_2_1.bitsPerSample &&
- pcm_config.channelMode &
- kDefaultSoftwarePcmCapabilities_2_1.channelMode &&
- pcm_config.dataIntervalUs != 0) {
- return true;
- }
- LOG(WARNING) << __func__
- << ": Unsupported PCM Configuration=" << toString(pcm_config);
- return false;
-}
-
-bool IsOffloadCodecConfigurationValid(const SessionType& session_type,
- const CodecConfiguration& codec_config) {
- if (session_type != SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH) {
- LOG(ERROR) << __func__
- << ": Invalid SessionType=" << toString(session_type);
- return false;
- } else if (codec_config.encodedAudioBitrate < 0x00000001 ||
- 0x00ffffff < codec_config.encodedAudioBitrate) {
- LOG(ERROR) << __func__ << ": Unsupported Codec Configuration="
- << toString(codec_config);
- return false;
- }
- const CodecConfiguration::CodecSpecific& codec_specific = codec_config.config;
- switch (codec_config.codecType) {
- case CodecType::SBC:
- if (IsOffloadSbcConfigurationValid(codec_specific)) {
- return true;
- }
- return false;
- case CodecType::AAC:
- if (IsOffloadAacConfigurationValid(codec_specific)) {
- return true;
- }
- return false;
- case CodecType::LDAC:
- if (IsOffloadLdacConfigurationValid(codec_specific)) {
- return true;
- }
- return false;
- case CodecType::APTX:
- if (IsOffloadAptxConfigurationValid(codec_specific)) {
- return true;
- }
- return false;
- case CodecType::APTX_HD:
- if (IsOffloadAptxHdConfigurationValid(codec_specific)) {
- return true;
- }
- return false;
- case CodecType::UNKNOWN:
- return false;
- }
- return false;
-}
-
-} // namespace audio
-} // namespace bluetooth
-} // namespace android
diff --git a/bluetooth/audio/2.1/default/session/BluetoothAudioSupportedCodecsDB.h b/bluetooth/audio/2.1/default/session/BluetoothAudioSupportedCodecsDB.h
deleted file mode 100644
index 9b2f680..0000000
--- a/bluetooth/audio/2.1/default/session/BluetoothAudioSupportedCodecsDB.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <android/hardware/bluetooth/audio/2.0/types.h>
-#include <android/hardware/bluetooth/audio/2.1/types.h>
-
-namespace android {
-namespace bluetooth {
-namespace audio {
-
-using ::android::hardware::bluetooth::audio::V2_0::CodecCapabilities;
-using ::android::hardware::bluetooth::audio::V2_0::CodecConfiguration;
-using ::android::hardware::bluetooth::audio::V2_1::PcmParameters;
-using ::android::hardware::bluetooth::audio::V2_1::SessionType;
-
-std::vector<::android::hardware::bluetooth::audio::V2_0::PcmParameters>
-GetSoftwarePcmCapabilities();
-std::vector<PcmParameters> GetSoftwarePcmCapabilities_2_1();
-std::vector<CodecCapabilities> GetOffloadCodecCapabilities(
- const SessionType& session_type);
-std::vector<CodecCapabilities> GetOffloadCodecCapabilities(
- const ::android::hardware::bluetooth::audio::V2_0::SessionType&
- session_type);
-
-bool IsSoftwarePcmConfigurationValid_2_1(const PcmParameters& pcm_config);
-bool IsSoftwarePcmConfigurationValid(
- const ::android::hardware::bluetooth::audio::V2_0::PcmParameters&
- pcm_config);
-bool IsOffloadCodecConfigurationValid(const SessionType& session_type,
- const CodecConfiguration& codec_config);
-
-} // namespace audio
-} // namespace bluetooth
-} // namespace android
diff --git a/bluetooth/audio/2.1/vts/functional/VtsHalBluetoothAudioV2_1TargetTest.cpp b/bluetooth/audio/2.1/vts/functional/VtsHalBluetoothAudioV2_1TargetTest.cpp
index 37d1281..57fa07b 100644
--- a/bluetooth/audio/2.1/vts/functional/VtsHalBluetoothAudioV2_1TargetTest.cpp
+++ b/bluetooth/audio/2.1/vts/functional/VtsHalBluetoothAudioV2_1TargetTest.cpp
@@ -1005,8 +1005,10 @@
BluetoothAudioProvidersFactoryHidlTest::TearDown();
}
- static constexpr SampleRate le_audio_output_sample_rates_[3] = {
- SampleRate::RATE_UNKNOWN, SampleRate::RATE_16000, SampleRate::RATE_24000};
+ static constexpr SampleRate le_audio_output_sample_rates_[11] = {
+ SampleRate::RATE_UNKNOWN, SampleRate::RATE_8000, SampleRate::RATE_16000,
+ SampleRate::RATE_24000, SampleRate::RATE_32000, SampleRate::RATE_44100,
+ SampleRate::RATE_48000};
static constexpr BitsPerSample le_audio_output_bits_per_samples_[3] = {
BitsPerSample::BITS_UNKNOWN, BitsPerSample::BITS_16,
BitsPerSample::BITS_24};
@@ -1030,7 +1032,7 @@
* stopped with different PCM config
*/
TEST_P(BluetoothAudioProviderLeAudioOutputSoftwareHidlTest,
- StartAndEndLeAudioOutputSessionWithPossiblePcmConfig) {
+ DISABLED_StartAndEndLeAudioOutputSessionWithPossiblePcmConfig) {
bool is_codec_config_valid;
std::unique_ptr<DataMQ> tempDataMQ;
auto hidl_cb = [&is_codec_config_valid, &tempDataMQ](
@@ -1097,8 +1099,10 @@
BluetoothAudioProvidersFactoryHidlTest::TearDown();
}
- static constexpr SampleRate le_audio_output_sample_rates_[3] = {
- SampleRate::RATE_UNKNOWN, SampleRate::RATE_16000, SampleRate::RATE_24000};
+ static constexpr SampleRate le_audio_output_sample_rates_[11] = {
+ SampleRate::RATE_UNKNOWN, SampleRate::RATE_8000, SampleRate::RATE_16000,
+ SampleRate::RATE_24000, SampleRate::RATE_32000, SampleRate::RATE_44100,
+ SampleRate::RATE_48000};
static constexpr BitsPerSample le_audio_output_bits_per_samples_[3] = {
BitsPerSample::BITS_UNKNOWN, BitsPerSample::BITS_16,
BitsPerSample::BITS_24};
@@ -1122,7 +1126,7 @@
* stopped with different PCM config
*/
TEST_P(BluetoothAudioProviderLeAudioInputSoftwareHidlTest,
- StartAndEndLeAudioInputSessionWithPossiblePcmConfig) {
+ DISABLED_StartAndEndLeAudioInputSessionWithPossiblePcmConfig) {
bool is_codec_config_valid;
std::unique_ptr<DataMQ> tempDataMQ;
auto hidl_cb = [&is_codec_config_valid, &tempDataMQ](
diff --git a/bluetooth/audio/utils/Android.bp b/bluetooth/audio/utils/Android.bp
new file mode 100644
index 0000000..35476d2
--- /dev/null
+++ b/bluetooth/audio/utils/Android.bp
@@ -0,0 +1,23 @@
+cc_library_shared {
+ name: "libbluetooth_audio_session",
+ defaults: ["hidl_defaults"],
+ vendor: true,
+ srcs: [
+ "session/BluetoothAudioSession.cpp",
+ "session/BluetoothAudioSession_2_1.cpp",
+ "session/BluetoothAudioSupportedCodecsDB.cpp",
+ "session/BluetoothAudioSupportedCodecsDB_2_1.cpp",
+ ],
+ export_include_dirs: ["session/"],
+ header_libs: ["libhardware_headers"],
+ shared_libs: [
+ "android.hardware.bluetooth.audio@2.0",
+ "android.hardware.bluetooth.audio@2.1",
+ "libbase",
+ "libcutils",
+ "libfmq",
+ "libhidlbase",
+ "liblog",
+ "libutils",
+ ],
+}
diff --git a/bluetooth/audio/2.0/default/session/BluetoothAudioSession.cpp b/bluetooth/audio/utils/session/BluetoothAudioSession.cpp
similarity index 100%
rename from bluetooth/audio/2.0/default/session/BluetoothAudioSession.cpp
rename to bluetooth/audio/utils/session/BluetoothAudioSession.cpp
diff --git a/bluetooth/audio/2.0/default/session/BluetoothAudioSession.h b/bluetooth/audio/utils/session/BluetoothAudioSession.h
similarity index 100%
rename from bluetooth/audio/2.0/default/session/BluetoothAudioSession.h
rename to bluetooth/audio/utils/session/BluetoothAudioSession.h
diff --git a/bluetooth/audio/2.0/default/session/BluetoothAudioSessionControl.h b/bluetooth/audio/utils/session/BluetoothAudioSessionControl.h
similarity index 100%
rename from bluetooth/audio/2.0/default/session/BluetoothAudioSessionControl.h
rename to bluetooth/audio/utils/session/BluetoothAudioSessionControl.h
diff --git a/bluetooth/audio/utils/session/BluetoothAudioSessionControl_2_1.h b/bluetooth/audio/utils/session/BluetoothAudioSessionControl_2_1.h
new file mode 100644
index 0000000..86af468
--- /dev/null
+++ b/bluetooth/audio/utils/session/BluetoothAudioSessionControl_2_1.h
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2018 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 "BluetoothAudioSession_2_1.h"
+
+namespace android {
+namespace bluetooth {
+namespace audio {
+
+class BluetoothAudioSessionControl_2_1 {
+ using SessionType_2_1 =
+ ::android::hardware::bluetooth::audio::V2_1::SessionType;
+
+ public:
+ // The control API helps to check if session is ready or not
+ // @return: true if the Bluetooth stack has started th specified session
+ static bool IsSessionReady(const SessionType_2_1& session_type) {
+ std::shared_ptr<BluetoothAudioSession_2_1> session_ptr =
+ BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type);
+ if (session_ptr != nullptr) {
+ return session_ptr->GetAudioSession()->IsSessionReady();
+ }
+ return false;
+ }
+
+ // The control API helps the bluetooth_audio module to register
+ // PortStatusCallbacks
+ // @return: cookie - the assigned number to this bluetooth_audio output
+ static uint16_t RegisterControlResultCback(
+ const SessionType_2_1& session_type, const PortStatusCallbacks& cbacks) {
+ std::shared_ptr<BluetoothAudioSession_2_1> session_ptr =
+ BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type);
+ if (session_ptr != nullptr) {
+ return session_ptr->GetAudioSession()->RegisterStatusCback(cbacks);
+ }
+ return kObserversCookieUndefined;
+ }
+
+ // The control API helps the bluetooth_audio module to unregister
+ // PortStatusCallbacks
+ // @param: cookie - indicates which bluetooth_audio output is
+ static void UnregisterControlResultCback(const SessionType_2_1& session_type,
+ uint16_t cookie) {
+ std::shared_ptr<BluetoothAudioSession_2_1> session_ptr =
+ BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type);
+ if (session_ptr != nullptr) {
+ session_ptr->GetAudioSession()->UnregisterStatusCback(cookie);
+ }
+ }
+
+ // The control API for the bluetooth_audio module to get current
+ // AudioConfiguration
+ static const AudioConfiguration& GetAudioConfig(
+ const SessionType_2_1& session_type) {
+ std::shared_ptr<BluetoothAudioSession_2_1> session_ptr =
+ BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type);
+ if (session_ptr != nullptr) {
+ // TODO: map 2.1 to 2.0 audio config inside GetAudioConfig?
+ return session_ptr->GetAudioSession()->GetAudioConfig();
+ } else if (session_type ==
+ SessionType_2_1::A2DP_HARDWARE_OFFLOAD_DATAPATH) {
+ return BluetoothAudioSession::kInvalidOffloadAudioConfiguration;
+ } else {
+ return BluetoothAudioSession::kInvalidSoftwareAudioConfiguration;
+ }
+ }
+
+ // Those control APIs for the bluetooth_audio module to start / suspend / stop
+ // stream, to check position, and to update metadata.
+ static bool StartStream(const SessionType_2_1& session_type) {
+ std::shared_ptr<BluetoothAudioSession_2_1> session_ptr =
+ BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type);
+ if (session_ptr != nullptr) {
+ return session_ptr->GetAudioSession()->StartStream();
+ }
+ return false;
+ }
+
+ static bool SuspendStream(const SessionType_2_1& session_type) {
+ std::shared_ptr<BluetoothAudioSession_2_1> session_ptr =
+ BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type);
+ if (session_ptr != nullptr) {
+ return session_ptr->GetAudioSession()->SuspendStream();
+ }
+ return false;
+ }
+
+ static void StopStream(const SessionType_2_1& session_type) {
+ std::shared_ptr<BluetoothAudioSession_2_1> session_ptr =
+ BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type);
+ if (session_ptr != nullptr) {
+ session_ptr->GetAudioSession()->StopStream();
+ }
+ }
+
+ static bool GetPresentationPosition(const SessionType_2_1& session_type,
+ uint64_t* remote_delay_report_ns,
+ uint64_t* total_bytes_readed,
+ timespec* data_position) {
+ std::shared_ptr<BluetoothAudioSession_2_1> session_ptr =
+ BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type);
+ if (session_ptr != nullptr) {
+ return session_ptr->GetAudioSession()->GetPresentationPosition(
+ remote_delay_report_ns, total_bytes_readed, data_position);
+ }
+ return false;
+ }
+
+ static void UpdateTracksMetadata(
+ const SessionType_2_1& session_type,
+ const struct source_metadata* source_metadata) {
+ std::shared_ptr<BluetoothAudioSession_2_1> session_ptr =
+ BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type);
+ if (session_ptr != nullptr) {
+ session_ptr->GetAudioSession()->UpdateTracksMetadata(source_metadata);
+ }
+ }
+
+ // The control API writes stream to FMQ
+ static size_t OutWritePcmData(const SessionType_2_1& session_type,
+ const void* buffer, size_t bytes) {
+ std::shared_ptr<BluetoothAudioSession_2_1> session_ptr =
+ BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type);
+ if (session_ptr != nullptr) {
+ return session_ptr->GetAudioSession()->OutWritePcmData(buffer, bytes);
+ }
+ return 0;
+ }
+};
+
+} // namespace audio
+} // namespace bluetooth
+} // namespace android
diff --git a/bluetooth/audio/2.0/default/session/BluetoothAudioSessionReport.h b/bluetooth/audio/utils/session/BluetoothAudioSessionReport.h
similarity index 100%
rename from bluetooth/audio/2.0/default/session/BluetoothAudioSessionReport.h
rename to bluetooth/audio/utils/session/BluetoothAudioSessionReport.h
diff --git a/bluetooth/audio/utils/session/BluetoothAudioSessionReport_2_1.h b/bluetooth/audio/utils/session/BluetoothAudioSessionReport_2_1.h
new file mode 100644
index 0000000..ab30536
--- /dev/null
+++ b/bluetooth/audio/utils/session/BluetoothAudioSessionReport_2_1.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2018 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 "BluetoothAudioSession_2_1.h"
+
+namespace android {
+namespace bluetooth {
+namespace audio {
+
+class BluetoothAudioSessionReport_2_1 {
+ public:
+ // The API reports the Bluetooth stack has started the session, and will
+ // inform registered bluetooth_audio outputs
+ static void OnSessionStarted(
+ const ::android::hardware::bluetooth::audio::V2_1::SessionType&
+ session_type,
+ const sp<IBluetoothAudioPort> host_iface,
+ const DataMQ::Descriptor* dataMQ,
+ const ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration&
+ audio_config) {
+ std::shared_ptr<BluetoothAudioSession_2_1> session_ptr =
+ BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type);
+ if (session_ptr != nullptr) {
+ session_ptr->OnSessionStarted(host_iface, dataMQ, audio_config);
+ }
+ }
+ // The API reports the Bluetooth stack has ended the session, and will
+ // inform registered bluetooth_audio outputs
+ static void OnSessionEnded(
+ const ::android::hardware::bluetooth::audio::V2_1::SessionType&
+ session_type) {
+ std::shared_ptr<BluetoothAudioSession_2_1> session_ptr =
+ BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type);
+ if (session_ptr != nullptr) {
+ session_ptr->GetAudioSession()->OnSessionEnded();
+ }
+ }
+ // The API reports the Bluetooth stack has replied the result of startStream
+ // or suspendStream, and will inform registered bluetooth_audio outputs
+ static void ReportControlStatus(
+ const ::android::hardware::bluetooth::audio::V2_1::SessionType&
+ session_type,
+ const bool& start_resp, const BluetoothAudioStatus& status) {
+ std::shared_ptr<BluetoothAudioSession_2_1> session_ptr =
+ BluetoothAudioSessionInstance_2_1::GetSessionInstance(session_type);
+ if (session_ptr != nullptr) {
+ session_ptr->GetAudioSession()->ReportControlStatus(start_resp, status);
+ }
+ }
+};
+
+} // namespace audio
+} // namespace bluetooth
+} // namespace android
diff --git a/bluetooth/audio/utils/session/BluetoothAudioSession_2_1.cpp b/bluetooth/audio/utils/session/BluetoothAudioSession_2_1.cpp
new file mode 100644
index 0000000..9e1baf4
--- /dev/null
+++ b/bluetooth/audio/utils/session/BluetoothAudioSession_2_1.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "BTAudioProviderSession_2_1"
+
+#include "BluetoothAudioSession_2_1.h"
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+namespace android {
+namespace bluetooth {
+namespace audio {
+using SessionType_2_1 =
+ ::android::hardware::bluetooth::audio::V2_1::SessionType;
+using SessionType_2_0 =
+ ::android::hardware::bluetooth::audio::V2_0::SessionType;
+
+namespace {
+bool is_2_0_session_type(
+ const ::android::hardware::bluetooth::audio::V2_1::SessionType&
+ session_type) {
+ if (session_type == SessionType_2_1::A2DP_SOFTWARE_ENCODING_DATAPATH ||
+ session_type == SessionType_2_1::A2DP_HARDWARE_OFFLOAD_DATAPATH ||
+ session_type == SessionType_2_1::HEARING_AID_SOFTWARE_ENCODING_DATAPATH) {
+ return true;
+ } else {
+ return false;
+ }
+}
+} // namespace
+
+BluetoothAudioSession_2_1::BluetoothAudioSession_2_1(
+ const ::android::hardware::bluetooth::audio::V2_1::SessionType&
+ session_type)
+ : audio_session(BluetoothAudioSessionInstance::GetSessionInstance(
+ static_cast<SessionType_2_0>(session_type))) {
+ if (is_2_0_session_type(session_type)) {
+ session_type_2_1_ = (SessionType_2_1::UNKNOWN);
+ } else {
+ session_type_2_1_ = (session_type);
+ }
+}
+
+std::shared_ptr<BluetoothAudioSession>
+BluetoothAudioSession_2_1::GetAudioSession() {
+ return audio_session;
+}
+
+// The report function is used to report that the Bluetooth stack has started
+// this session without any failure, and will invoke session_changed_cb_ to
+// notify those registered bluetooth_audio outputs
+void BluetoothAudioSession_2_1::OnSessionStarted(
+ const sp<IBluetoothAudioPort> stack_iface, const DataMQ::Descriptor* dataMQ,
+ const ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration&
+ audio_config) {
+ if (session_type_2_1_ == SessionType_2_1::UNKNOWN) {
+ ::android::hardware::bluetooth::audio::V2_0::AudioConfiguration config;
+ if (audio_config.getDiscriminator() ==
+ ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration::
+ hidl_discriminator::codecConfig) {
+ config.codecConfig(audio_config.codecConfig());
+ } else {
+ auto& tmpPcm = audio_config.pcmConfig();
+ config.pcmConfig(
+ ::android::hardware::bluetooth::audio::V2_0::PcmParameters{
+ .sampleRate = static_cast<SampleRate>(tmpPcm.sampleRate),
+ .channelMode = tmpPcm.channelMode,
+ .bitsPerSample = tmpPcm.bitsPerSample
+ /*dataIntervalUs is not passed to 2.0 */
+ });
+ }
+
+ audio_session->OnSessionStarted(stack_iface, dataMQ, config);
+ } else {
+ LOG(FATAL) << " Not implemented yet!!";
+ }
+}
+
+std::unique_ptr<BluetoothAudioSessionInstance_2_1>
+ BluetoothAudioSessionInstance_2_1::instance_ptr =
+ std::unique_ptr<BluetoothAudioSessionInstance_2_1>(
+ new BluetoothAudioSessionInstance_2_1());
+
+// API to fetch the session of A2DP / Hearing Aid
+std::shared_ptr<BluetoothAudioSession_2_1>
+BluetoothAudioSessionInstance_2_1::GetSessionInstance(
+ const SessionType_2_1& session_type) {
+ std::lock_guard<std::mutex> guard(instance_ptr->mutex_);
+ if (!instance_ptr->sessions_map_.empty()) {
+ auto entry = instance_ptr->sessions_map_.find(session_type);
+ if (entry != instance_ptr->sessions_map_.end()) {
+ return entry->second;
+ }
+ }
+ std::shared_ptr<BluetoothAudioSession_2_1> session_ptr =
+ std::make_shared<BluetoothAudioSession_2_1>(session_type);
+ instance_ptr->sessions_map_[session_type] = session_ptr;
+ return session_ptr;
+}
+
+} // namespace audio
+} // namespace bluetooth
+} // namespace android
diff --git a/bluetooth/audio/utils/session/BluetoothAudioSession_2_1.h b/bluetooth/audio/utils/session/BluetoothAudioSession_2_1.h
new file mode 100644
index 0000000..0e9c12b
--- /dev/null
+++ b/bluetooth/audio/utils/session/BluetoothAudioSession_2_1.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2018 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/hardware/bluetooth/audio/2.1/types.h>
+#include "BluetoothAudioSession.h"
+
+#include <mutex>
+#include <unordered_map>
+
+namespace android {
+namespace bluetooth {
+namespace audio {
+
+class BluetoothAudioSession_2_1 {
+ private:
+ std::shared_ptr<BluetoothAudioSession> audio_session;
+
+ ::android::hardware::bluetooth::audio::V2_1::SessionType session_type_2_1_;
+
+ // audio data configuration for both software and offloading
+ ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration
+ audio_config_2_1_;
+
+ bool UpdateAudioConfig(
+ const ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration&
+ audio_config);
+
+ public:
+ BluetoothAudioSession_2_1(
+ const ::android::hardware::bluetooth::audio::V2_1::SessionType&
+ session_type);
+
+ std::shared_ptr<BluetoothAudioSession> GetAudioSession();
+
+ // The report function is used to report that the Bluetooth stack has started
+ // this session without any failure, and will invoke session_changed_cb_ to
+ // notify those registered bluetooth_audio outputs
+ void OnSessionStarted(
+ const sp<IBluetoothAudioPort> stack_iface,
+ const DataMQ::Descriptor* dataMQ,
+ const ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration&
+ audio_config);
+
+ // The control function is for the bluetooth_audio module to get the current
+ // AudioConfiguration
+ const ::android::hardware::bluetooth::audio::V2_1::AudioConfiguration&
+ GetAudioConfig();
+};
+
+class BluetoothAudioSessionInstance_2_1 {
+ public:
+ // The API is to fetch the specified session of A2DP / Hearing Aid
+ static std::shared_ptr<BluetoothAudioSession_2_1> GetSessionInstance(
+ const ::android::hardware::bluetooth::audio::V2_1::SessionType&
+ session_type);
+
+ private:
+ static std::unique_ptr<BluetoothAudioSessionInstance_2_1> instance_ptr;
+ std::mutex mutex_;
+ std::unordered_map<::android::hardware::bluetooth::audio::V2_1::SessionType,
+ std::shared_ptr<BluetoothAudioSession_2_1>>
+ sessions_map_;
+};
+
+} // namespace audio
+} // namespace bluetooth
+} // namespace android
diff --git a/bluetooth/audio/2.0/default/session/BluetoothAudioSupportedCodecsDB.cpp b/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB.cpp
similarity index 100%
rename from bluetooth/audio/2.0/default/session/BluetoothAudioSupportedCodecsDB.cpp
rename to bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB.cpp
diff --git a/bluetooth/audio/2.0/default/session/BluetoothAudioSupportedCodecsDB.h b/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB.h
similarity index 100%
rename from bluetooth/audio/2.0/default/session/BluetoothAudioSupportedCodecsDB.h
rename to bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB.h
diff --git a/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB_2_1.cpp b/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB_2_1.cpp
new file mode 100644
index 0000000..8b0b0f7
--- /dev/null
+++ b/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB_2_1.cpp
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "BTAudioProviderSessionCodecsDB_2_1"
+
+#include "BluetoothAudioSupportedCodecsDB_2_1.h"
+
+#include <android-base/logging.h>
+
+namespace android {
+namespace bluetooth {
+namespace audio {
+
+using ::android::hardware::bluetooth::audio::V2_0::BitsPerSample;
+using ::android::hardware::bluetooth::audio::V2_0::ChannelMode;
+
+using SampleRate_2_0 = ::android::hardware::bluetooth::audio::V2_0::SampleRate;
+using SampleRate_2_1 = ::android::hardware::bluetooth::audio::V2_1::SampleRate;
+
+using SessionType_2_1 =
+ ::android::hardware::bluetooth::audio::V2_1::SessionType;
+using SessionType_2_0 =
+ ::android::hardware::bluetooth::audio::V2_0::SessionType;
+
+namespace {
+bool is_2_0_session_type(
+ const ::android::hardware::bluetooth::audio::V2_1::SessionType&
+ session_type) {
+ if (session_type == SessionType_2_1::A2DP_SOFTWARE_ENCODING_DATAPATH ||
+ session_type == SessionType_2_1::A2DP_HARDWARE_OFFLOAD_DATAPATH ||
+ session_type == SessionType_2_1::HEARING_AID_SOFTWARE_ENCODING_DATAPATH) {
+ return true;
+ } else {
+ return false;
+ }
+}
+} // namespace
+
+static const ::android::hardware::bluetooth::audio::V2_1::PcmParameters
+ kDefaultSoftwarePcmCapabilities_2_1 = {
+ .sampleRate = static_cast<SampleRate_2_1>(
+ SampleRate_2_1::RATE_44100 | SampleRate_2_1::RATE_48000 |
+ SampleRate_2_1::RATE_88200 | SampleRate_2_1::RATE_96000 |
+ SampleRate_2_1::RATE_16000 | SampleRate_2_1::RATE_24000),
+ .channelMode =
+ static_cast<ChannelMode>(ChannelMode::MONO | ChannelMode::STEREO),
+ .bitsPerSample = static_cast<BitsPerSample>(BitsPerSample::BITS_16 |
+ BitsPerSample::BITS_24 |
+ BitsPerSample::BITS_32)};
+
+std::vector<::android::hardware::bluetooth::audio::V2_1::PcmParameters>
+GetSoftwarePcmCapabilities_2_1() {
+ return std::vector<
+ ::android::hardware::bluetooth::audio::V2_1::PcmParameters>(
+ 1, kDefaultSoftwarePcmCapabilities_2_1);
+}
+
+std::vector<CodecCapabilities> GetOffloadCodecCapabilities(
+ const ::android::hardware::bluetooth::audio::V2_1::SessionType&
+ session_type) {
+ if (is_2_0_session_type(session_type)) {
+ return GetOffloadCodecCapabilities(
+ static_cast<SessionType_2_0>(session_type));
+ }
+ return std::vector<CodecCapabilities>(0);
+}
+
+bool IsSoftwarePcmConfigurationValid_2_1(
+ const ::android::hardware::bluetooth::audio::V2_1::PcmParameters&
+ pcm_config) {
+ if ((pcm_config.sampleRate != SampleRate_2_1::RATE_44100 &&
+ pcm_config.sampleRate != SampleRate_2_1::RATE_48000 &&
+ pcm_config.sampleRate != SampleRate_2_1::RATE_88200 &&
+ pcm_config.sampleRate != SampleRate_2_1::RATE_96000 &&
+ pcm_config.sampleRate != SampleRate_2_1::RATE_16000 &&
+ pcm_config.sampleRate != SampleRate_2_1::RATE_24000) ||
+ (pcm_config.bitsPerSample != BitsPerSample::BITS_16 &&
+ pcm_config.bitsPerSample != BitsPerSample::BITS_24 &&
+ pcm_config.bitsPerSample != BitsPerSample::BITS_32) ||
+ (pcm_config.channelMode != ChannelMode::MONO &&
+ pcm_config.channelMode != ChannelMode::STEREO)) {
+ LOG(WARNING) << __func__
+ << ": Invalid PCM Configuration=" << toString(pcm_config);
+ return false;
+ } else if (pcm_config.sampleRate &
+ kDefaultSoftwarePcmCapabilities_2_1.sampleRate &&
+ pcm_config.bitsPerSample &
+ kDefaultSoftwarePcmCapabilities_2_1.bitsPerSample &&
+ pcm_config.channelMode &
+ kDefaultSoftwarePcmCapabilities_2_1.channelMode &&
+ pcm_config.dataIntervalUs != 0) {
+ return true;
+ }
+ LOG(WARNING) << __func__
+ << ": Unsupported PCM Configuration=" << toString(pcm_config);
+ return false;
+}
+
+bool IsOffloadCodecConfigurationValid(
+ const ::android::hardware::bluetooth::audio::V2_1::SessionType&
+ session_type,
+ const ::android::hardware::bluetooth::audio::V2_0::CodecConfiguration&
+ codec_config) {
+ if (is_2_0_session_type(session_type)) {
+ return IsOffloadCodecConfigurationValid(
+ static_cast<SessionType_2_0>(session_type), codec_config);
+ }
+
+ return false;
+}
+
+} // namespace audio
+} // namespace bluetooth
+} // namespace android
diff --git a/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB_2_1.h b/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB_2_1.h
new file mode 100644
index 0000000..746d9c0
--- /dev/null
+++ b/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB_2_1.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "BluetoothAudioSupportedCodecsDB.h"
+
+#include <android/hardware/bluetooth/audio/2.1/types.h>
+
+namespace android {
+namespace bluetooth {
+namespace audio {
+
+std::vector<::android::hardware::bluetooth::audio::V2_1::PcmParameters>
+GetSoftwarePcmCapabilities_2_1();
+std::vector<::android::hardware::bluetooth::audio::V2_0::CodecCapabilities>
+GetOffloadCodecCapabilities(
+ const ::android::hardware::bluetooth::audio::V2_1::SessionType&
+ session_type);
+
+bool IsSoftwarePcmConfigurationValid_2_1(
+ const ::android::hardware::bluetooth::audio::V2_1::PcmParameters&
+ pcm_config);
+
+bool IsOffloadCodecConfigurationValid(
+ const ::android::hardware::bluetooth::audio::V2_1::SessionType&
+ session_type,
+ const ::android::hardware::bluetooth::audio::V2_0::CodecConfiguration&
+ codec_config);
+
+} // namespace audio
+} // namespace bluetooth
+} // namespace android
diff --git a/broadcastradio/2.0/vts/functional/VtsHalBroadcastradioV2_0TargetTest.cpp b/broadcastradio/2.0/vts/functional/VtsHalBroadcastradioV2_0TargetTest.cpp
index ca57243..ce50f25 100644
--- a/broadcastradio/2.0/vts/functional/VtsHalBroadcastradioV2_0TargetTest.cpp
+++ b/broadcastradio/2.0/vts/functional/VtsHalBroadcastradioV2_0TargetTest.cpp
@@ -415,7 +415,7 @@
TEST_P(BroadcastRadioHalTest, FmTune) {
ASSERT_TRUE(openSession());
- uint64_t freq = 100100; // 100.1 FM
+ uint64_t freq = 90900; // 90.9 FM
auto sel = make_selector_amfm(freq);
/* TODO(b/69958777): there is a race condition between tune() and onCurrentProgramInfoChanged
diff --git a/camera/provider/2.4/default/service.cpp b/camera/provider/2.4/default/service.cpp
index 84f4839..0a4f787 100644
--- a/camera/provider/2.4/default/service.cpp
+++ b/camera/provider/2.4/default/service.cpp
@@ -22,9 +22,9 @@
#include <android/hardware/camera/provider/2.4/ICameraProvider.h>
#include <binder/ProcessState.h>
-#include <cutils/memory.h>
#include <cutils/properties.h>
#include <hidl/LegacySupport.h>
+#include <malloc.h>
using android::status_t;
using android::hardware::defaultLazyPassthroughServiceImplementation;
@@ -46,7 +46,13 @@
// b/166675194
if (property_get_bool("ro.vendor.camera.provider24.disable_mem_init", false)) {
- process_disable_memory_mitigations();
+ if (mallopt(M_BIONIC_ZERO_INIT, 0) == 0) {
+ // Note - heap initialization is only present on devices with Scudo.
+ // Devices with jemalloc don't have heap-init, and thus the mallopt
+ // will fail. On these devices, you probably just want to remove the
+ // property.
+ ALOGE("Disabling heap initialization failed.");
+ }
}
status_t status;
diff --git a/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp b/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp
index 2099dc0..7e6b5ad 100644
--- a/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp
+++ b/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp
@@ -888,6 +888,8 @@
static Status getSystemCameraKind(const camera_metadata_t* staticMeta,
SystemCameraKind* systemCameraKind);
+ static V3_2::DataspaceFlags getDataspace(PixelFormat format);
+
void processCaptureRequestInternal(uint64_t bufferusage, RequestTemplate reqTemplate,
bool useSecureOnlyCameras);
@@ -3178,7 +3180,6 @@
}
}
-
// Verify that all supported stream formats and sizes can be configured
// successfully.
TEST_P(CameraHidlTest, configureStreamsAvailableOutputs) {
@@ -3221,17 +3222,7 @@
uint32_t streamConfigCounter = 0;
for (auto& it : outputStreams) {
V3_2::Stream stream3_2;
- V3_2::DataspaceFlags dataspaceFlag = 0;
- switch (static_cast<PixelFormat>(it.format)) {
- case PixelFormat::BLOB:
- dataspaceFlag = static_cast<V3_2::DataspaceFlags>(Dataspace::V0_JFIF);
- break;
- case PixelFormat::Y16:
- dataspaceFlag = static_cast<V3_2::DataspaceFlags>(Dataspace::DEPTH);
- break;
- default:
- dataspaceFlag = static_cast<V3_2::DataspaceFlags>(Dataspace::UNKNOWN);
- }
+ V3_2::DataspaceFlags dataspaceFlag = getDataspace(static_cast<PixelFormat>(it.format));
stream3_2 = {streamId,
StreamType::OUTPUT,
static_cast<uint32_t>(it.width),
@@ -3351,17 +3342,8 @@
size_t j = 0;
for (const auto& it : outputStreams) {
V3_2::Stream stream3_2;
- V3_2::DataspaceFlags dataspaceFlag = 0;
- switch (static_cast<PixelFormat>(it.format)) {
- case PixelFormat::BLOB:
- dataspaceFlag = static_cast<V3_2::DataspaceFlags>(Dataspace::V0_JFIF);
- break;
- case PixelFormat::Y16:
- dataspaceFlag = static_cast<V3_2::DataspaceFlags>(Dataspace::DEPTH);
- break;
- default:
- dataspaceFlag = static_cast<V3_2::DataspaceFlags>(Dataspace::UNKNOWN);
- }
+ V3_2::DataspaceFlags dataspaceFlag = getDataspace(
+ static_cast<PixelFormat>(it.format));
stream3_2 = {streamId++,
StreamType::OUTPUT,
static_cast<uint32_t>(it.width),
@@ -5901,6 +5883,23 @@
return ret;
}
+// Select an appropriate dataspace given a specific pixel format.
+V3_2::DataspaceFlags CameraHidlTest::getDataspace(PixelFormat format) {
+ switch (format) {
+ case PixelFormat::BLOB:
+ return static_cast<V3_2::DataspaceFlags>(Dataspace::V0_JFIF);
+ case PixelFormat::Y16:
+ return static_cast<V3_2::DataspaceFlags>(Dataspace::DEPTH);
+ case PixelFormat::RAW16:
+ case PixelFormat::RAW_OPAQUE:
+ case PixelFormat::RAW10:
+ case PixelFormat::RAW12:
+ return static_cast<V3_2::DataspaceFlags>(Dataspace::ARBITRARY);
+ default:
+ return static_cast<V3_2::DataspaceFlags>(Dataspace::UNKNOWN);
+ }
+}
+
// Check whether this is a monochrome camera using the static camera characteristics.
Status CameraHidlTest::isMonochromeCamera(const camera_metadata_t *staticMeta) {
Status ret = Status::METHOD_NOT_SUPPORTED;
@@ -6275,17 +6274,8 @@
ASSERT_EQ(Status::OK, rc);
ASSERT_FALSE(outputStreams.empty());
- V3_2::DataspaceFlags dataspaceFlag = 0;
- switch (static_cast<PixelFormat>(outputStreams[idx].format)) {
- case PixelFormat::BLOB:
- dataspaceFlag = static_cast<V3_2::DataspaceFlags>(Dataspace::V0_JFIF);
- break;
- case PixelFormat::Y16:
- dataspaceFlag = static_cast<V3_2::DataspaceFlags>(Dataspace::DEPTH);
- break;
- default:
- dataspaceFlag = static_cast<V3_2::DataspaceFlags>(Dataspace::UNKNOWN);
- }
+ V3_2::DataspaceFlags dataspaceFlag = getDataspace(
+ static_cast<PixelFormat>(outputStreams[idx].format));
::android::hardware::hidl_vec<V3_4::Stream> streams3_4(/*size*/1);
V3_4::Stream stream3_4 = {{ 0 /*streamId*/, StreamType::OUTPUT,
diff --git a/common/fmq/aidl/Android.bp b/common/fmq/aidl/Android.bp
index 004adab..9389712 100644
--- a/common/fmq/aidl/Android.bp
+++ b/common/fmq/aidl/Android.bp
@@ -9,6 +9,9 @@
srcs: [
"android/hardware/common/fmq/*.aidl",
],
+ imports: [
+ "android.hardware.common",
+ ],
stability: "vintf",
backend: {
java: {
diff --git a/common/fmq/aidl/aidl_api/android.hardware.common.fmq/current/android/hardware/common/fmq/GrantorDescriptor.aidl b/common/fmq/aidl/aidl_api/android.hardware.common.fmq/current/android/hardware/common/fmq/GrantorDescriptor.aidl
index 7ac1930..0327796 100644
--- a/common/fmq/aidl/aidl_api/android.hardware.common.fmq/current/android/hardware/common/fmq/GrantorDescriptor.aidl
+++ b/common/fmq/aidl/aidl_api/android.hardware.common.fmq/current/android/hardware/common/fmq/GrantorDescriptor.aidl
@@ -2,13 +2,14 @@
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
@@ -18,6 +19,7 @@
package android.hardware.common.fmq;
@VintfStability
parcelable GrantorDescriptor {
+ int fdIndex;
int offset;
long extent;
}
diff --git a/common/fmq/aidl/aidl_api/android.hardware.common.fmq/current/android/hardware/common/fmq/MQDescriptor.aidl b/common/fmq/aidl/aidl_api/android.hardware.common.fmq/current/android/hardware/common/fmq/MQDescriptor.aidl
index 2607369..56f1de3 100644
--- a/common/fmq/aidl/aidl_api/android.hardware.common.fmq/current/android/hardware/common/fmq/MQDescriptor.aidl
+++ b/common/fmq/aidl/aidl_api/android.hardware.common.fmq/current/android/hardware/common/fmq/MQDescriptor.aidl
@@ -2,13 +2,14 @@
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
@@ -19,7 +20,7 @@
@VintfStability
parcelable MQDescriptor {
android.hardware.common.fmq.GrantorDescriptor[] grantors;
- ParcelFileDescriptor fileDescriptor;
+ android.hardware.common.NativeHandle handle;
int quantum;
int flags;
}
diff --git a/common/fmq/aidl/aidl_api/android.hardware.common.fmq/current/android/hardware/common/fmq/SynchronizedReadWrite.aidl b/common/fmq/aidl/aidl_api/android.hardware.common.fmq/current/android/hardware/common/fmq/SynchronizedReadWrite.aidl
index 2142bdb..264171d 100644
--- a/common/fmq/aidl/aidl_api/android.hardware.common.fmq/current/android/hardware/common/fmq/SynchronizedReadWrite.aidl
+++ b/common/fmq/aidl/aidl_api/android.hardware.common.fmq/current/android/hardware/common/fmq/SynchronizedReadWrite.aidl
@@ -2,13 +2,14 @@
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
diff --git a/common/fmq/aidl/aidl_api/android.hardware.common.fmq/current/android/hardware/common/fmq/UnsynchronizedWrite.aidl b/common/fmq/aidl/aidl_api/android.hardware.common.fmq/current/android/hardware/common/fmq/UnsynchronizedWrite.aidl
index 1220674..eaf2ffd 100644
--- a/common/fmq/aidl/aidl_api/android.hardware.common.fmq/current/android/hardware/common/fmq/UnsynchronizedWrite.aidl
+++ b/common/fmq/aidl/aidl_api/android.hardware.common.fmq/current/android/hardware/common/fmq/UnsynchronizedWrite.aidl
@@ -2,13 +2,14 @@
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
diff --git a/common/fmq/aidl/android/hardware/common/fmq/GrantorDescriptor.aidl b/common/fmq/aidl/android/hardware/common/fmq/GrantorDescriptor.aidl
index ca69d94..672415e 100644
--- a/common/fmq/aidl/android/hardware/common/fmq/GrantorDescriptor.aidl
+++ b/common/fmq/aidl/android/hardware/common/fmq/GrantorDescriptor.aidl
@@ -22,6 +22,10 @@
@VintfStability
parcelable GrantorDescriptor {
/*
+ * Index of file descriptor for this grantor
+ */
+ int fdIndex;
+ /*
* The offset of this descriptor in the shared memory in bytes.
*/
int offset;
diff --git a/common/fmq/aidl/android/hardware/common/fmq/MQDescriptor.aidl b/common/fmq/aidl/android/hardware/common/fmq/MQDescriptor.aidl
index 82917d6..46622f0 100644
--- a/common/fmq/aidl/android/hardware/common/fmq/MQDescriptor.aidl
+++ b/common/fmq/aidl/android/hardware/common/fmq/MQDescriptor.aidl
@@ -16,6 +16,7 @@
package android.hardware.common.fmq;
+import android.hardware.common.NativeHandle;
import android.hardware.common.fmq.GrantorDescriptor;
/*
@@ -34,8 +35,11 @@
* for blocking operations in the shared memory.
*/
GrantorDescriptor[] grantors;
- /* File descriptor for shared memory used in the message queue */
- ParcelFileDescriptor fileDescriptor;
+ /*
+ * NativeHandle that contains the file descriptors for shared memory used
+ * in the message queue
+ */
+ NativeHandle handle;
/* Size of each item, T, in bytes */
int quantum;
/* EventFlag word for blocking operations */
diff --git a/common/support/Android.bp b/common/support/Android.bp
index 3bb4804..ce3aa3f 100644
--- a/common/support/Android.bp
+++ b/common/support/Android.bp
@@ -6,7 +6,7 @@
srcs: ["NativeHandle.cpp"],
export_include_dirs: ["include"],
shared_libs: [
- "android.hardware.common-unstable-ndk_platform",
+ "android.hardware.common-V2-ndk_platform",
"libcutils",
],
}
@@ -17,10 +17,10 @@
defaults: ["libbinder_ndk_host_user"],
srcs: ["test.cpp"],
static_libs: [
+ "android.hardware.common-V2-ndk_platform",
"libaidlcommonsupport",
],
shared_libs: [
- "android.hardware.common-unstable-ndk_platform",
"libcutils",
],
test_suites: ["general-tests"],
diff --git a/compatibility_matrices/compatibility_matrix.3.xml b/compatibility_matrices/compatibility_matrix.3.xml
index 5888ab9..608890b 100644
--- a/compatibility_matrices/compatibility_matrix.3.xml
+++ b/compatibility_matrices/compatibility_matrix.3.xml
@@ -310,6 +310,10 @@
<instance>slot2</instance>
<instance>slot3</instance>
</interface>
+ </hal>
+ <hal format="hidl" optional="true">
+ <name>android.hardware.radio</name>
+ <version>1.0-2</version>
<interface>
<name>ISap</name>
<instance>slot1</instance>
diff --git a/compatibility_matrices/compatibility_matrix.4.xml b/compatibility_matrices/compatibility_matrix.4.xml
index 5e1266e..e5e012c 100644
--- a/compatibility_matrices/compatibility_matrix.4.xml
+++ b/compatibility_matrices/compatibility_matrix.4.xml
@@ -432,7 +432,6 @@
<hal format="hidl" optional="true">
<name>android.hardware.tv.cec</name>
<version>1.0</version>
- <version>2.0</version>
<interface>
<name>IHdmiCec</name>
<instance>default</instance>
diff --git a/compatibility_matrices/compatibility_matrix.5.xml b/compatibility_matrices/compatibility_matrix.5.xml
index 8bc663d..96a3692 100644
--- a/compatibility_matrices/compatibility_matrix.5.xml
+++ b/compatibility_matrices/compatibility_matrix.5.xml
@@ -86,7 +86,7 @@
</hal>
<hal format="hidl" optional="true">
<name>android.hardware.biometrics.face</name>
- <version>1.0-1</version>
+ <version>1.0</version>
<interface>
<name>IBiometricsFace</name>
<instance>default</instance>
@@ -467,7 +467,6 @@
<hal format="hidl" optional="true">
<name>android.hardware.tv.cec</name>
<version>1.0</version>
- <version>2.0</version>
<interface>
<name>IHdmiCec</name>
<instance>default</instance>
diff --git a/compatibility_matrices/compatibility_matrix.current.xml b/compatibility_matrices/compatibility_matrix.current.xml
index a994b23..6631d8a 100644
--- a/compatibility_matrices/compatibility_matrix.current.xml
+++ b/compatibility_matrices/compatibility_matrix.current.xml
@@ -9,7 +9,6 @@
</hal>
<hal format="hidl" optional="false">
<name>android.hardware.audio</name>
- <!-- TODO(b/142480271): remove 6.0 when implemented on reference device. -->
<version>6.0</version>
<version>7.0</version>
<interface>
@@ -19,7 +18,6 @@
</hal>
<hal format="hidl" optional="false">
<name>android.hardware.audio.effect</name>
- <!-- TODO(b/142480271): remove 6.0 when implemented on reference device. -->
<version>6.0</version>
<version>7.0</version>
<interface>
@@ -27,6 +25,14 @@
<instance>default</instance>
</interface>
</hal>
+ <hal format="aidl" optional="true">
+ <name>android.hardware.authsecret</name>
+ <version>1</version>
+ <interface>
+ <name>IAuthSecret</name>
+ <instance>default</instance>
+ </interface>
+ </hal>
<hal format="hidl" optional="true">
<name>android.hardware.authsecret</name>
<version>1.0</version>
@@ -67,6 +73,7 @@
</hal>
<hal format="aidl" optional="true">
<name>android.hardware.automotive.occupant_awareness</name>
+ <version>1</version>
<interface>
<name>IOccupantAwareness</name>
<instance>default</instance>
@@ -76,7 +83,7 @@
<name>android.hardware.automotive.sv</name>
<version>1.0</version>
<interface>
- <name>ISurroundView</name>
+ <name>ISurroundViewService</name>
<instance>default</instance>
</interface>
</hal>
@@ -90,7 +97,7 @@
</hal>
<hal format="hidl" optional="true">
<name>android.hardware.biometrics.face</name>
- <version>1.0-1</version>
+ <version>1.0</version>
<interface>
<name>IBiometricsFace</name>
<instance>default</instance>
@@ -250,9 +257,9 @@
<instance>default</instance>
</interface>
</hal>
- <hal format="hidl" optional="true">
+ <hal format="aidl" optional="true">
<name>android.hardware.health.storage</name>
- <version>1.0</version>
+ <version>1</version>
<interface>
<name>IStorage</name>
<instance>default</instance>
@@ -260,11 +267,20 @@
</hal>
<hal format="aidl" optional="true">
<name>android.hardware.identity</name>
+ <version>1-3</version>
<interface>
<name>IIdentityCredentialStore</name>
<instance>default</instance>
</interface>
</hal>
+ <hal format="aidl" optional="true">
+ <name>android.hardware.oemlock</name>
+ <version>1</version>
+ <interface>
+ <name>IOemLock</name>
+ <instance>default</instance>
+ </interface>
+ </hal>
<hal format="hidl" optional="true">
<name>android.hardware.ir</name>
<version>1.0</version>
@@ -300,6 +316,7 @@
</hal>
<hal format="aidl" optional="true">
<name>android.hardware.security.keymint</name>
+ <version>1</version>
<interface>
<name>IKeyMintDevice</name>
<instance>default</instance>
@@ -307,7 +324,15 @@
</interface>
</hal>
<hal format="aidl" optional="true">
+ <name>android.hardware.security.keymint</name>
+ <interface>
+ <name>IRemotelyProvisionedComponent</name>
+ <instance>default</instance>
+ </interface>
+ </hal>
+ <hal format="aidl" optional="true">
<name>android.hardware.light</name>
+ <version>1</version>
<interface>
<name>ILights</name>
<instance>default</instance>
@@ -336,6 +361,7 @@
</hal>
<hal format="aidl" optional="true">
<name>android.hardware.memtrack</name>
+ <version>1</version>
<interface>
<name>IMemtrack</name>
<instance>default</instance>
@@ -357,6 +383,13 @@
<regex-instance>.*</regex-instance>
</interface>
</hal>
+ <hal format="aidl" optional="true">
+ <name>android.hardware.neuralnetworks</name>
+ <interface>
+ <name>IDevice</name>
+ <regex-instance>.*</regex-instance>
+ </interface>
+ </hal>
<hal format="hidl" optional="true">
<name>android.hardware.nfc</name>
<version>1.2</version>
@@ -375,14 +408,14 @@
</hal>
<hal format="aidl" optional="false">
<name>android.hardware.power</name>
+ <version>1</version>
<interface>
<name>IPower</name>
<instance>default</instance>
</interface>
</hal>
- <hal format="hidl" optional="true">
+ <hal format="aidl" optional="true">
<name>android.hardware.power.stats</name>
- <version>1.0</version>
<interface>
<name>IPowerStats</name>
<instance>default</instance>
@@ -418,6 +451,14 @@
</interface>
</hal>
<hal format="hidl" optional="true">
+ <name>android.hardware.radio.config</name>
+ <version>1.3</version>
+ <interface>
+ <name>IRadioConfig</name>
+ <instance>default</instance>
+ </interface>
+ </hal>
+ <hal format="hidl" optional="true">
<name>android.hardware.renderscript</name>
<version>1.0</version>
<interface>
@@ -427,6 +468,7 @@
</hal>
<hal format="aidl" optional="true">
<name>android.hardware.rebootescrow</name>
+ <version>1</version>
<interface>
<name>IRebootEscrow</name>
<instance>default</instance>
@@ -441,6 +483,22 @@
<regex-instance>SIM[1-9][0-9]*</regex-instance>
</interface>
</hal>
+ <hal format="aidl" optional="true">
+ <name>android.hardware.security.secureclock</name>
+ <version>1</version>
+ <interface>
+ <name>ISecureClock</name>
+ <instance>default</instance>
+ </interface>
+ </hal>
+ <hal format="aidl" optional="true">
+ <name>android.hardware.security.sharedsecret</name>
+ <version>1</version>
+ <interface>
+ <name>ISharedSecret</name>
+ <instance>default</instance>
+ </interface>
+ </hal>
<hal format="hidl" optional="true">
<name>android.hardware.sensors</name>
<version>1.0</version>
@@ -468,7 +526,7 @@
</hal>
<hal format="hidl" optional="true">
<name>android.hardware.tetheroffload.control</name>
- <version>1.0</version>
+ <version>1.1</version>
<interface>
<name>IOffloadControl</name>
<instance>default</instance>
@@ -485,7 +543,6 @@
<hal format="hidl" optional="true">
<name>android.hardware.tv.cec</name>
<version>1.0</version>
- <version>2.0</version>
<interface>
<name>IHdmiCec</name>
<instance>default</instance>
@@ -531,16 +588,16 @@
</interface>
</hal>
<hal format="hidl" optional="true">
- <name>android.hardware.vr</name>
+ <name>android.hardware.weaver</name>
<version>1.0</version>
<interface>
- <name>IVr</name>
+ <name>IWeaver</name>
<instance>default</instance>
</interface>
</hal>
- <hal format="hidl" optional="true">
+ <hal format="aidl" optional="true">
<name>android.hardware.weaver</name>
- <version>1.0</version>
+ <version>1</version>
<interface>
<name>IWeaver</name>
<instance>default</instance>
diff --git a/compatibility_matrices/exclude/fcm_exclude.cpp b/compatibility_matrices/exclude/fcm_exclude.cpp
index 849df94..6a4d251 100644
--- a/compatibility_matrices/exclude/fcm_exclude.cpp
+++ b/compatibility_matrices/exclude/fcm_exclude.cpp
@@ -66,7 +66,7 @@
// TODO(b/171260360) Remove when HAL definition is removed
"android.hardware.audio.effect@2.0",
"android.hardware.audio@2.0",
- // TODO(b/171260613) Remove when HAL definition is removed
+ // Health 1.0 HAL is deprecated. The top level interface are deleted.
"android.hardware.health@1.0",
// TODO(b/171260670) Remove when HAL definition is removed
"android.hardware.nfc@1.0",
diff --git a/drm/1.0/default/Android.bp b/drm/1.0/default/Android.bp
index 93b3278..d5063fb 100644
--- a/drm/1.0/default/Android.bp
+++ b/drm/1.0/default/Android.bp
@@ -46,7 +46,7 @@
android_hardware_drm_1_0_multilib {
name: "android.hardware.drm@1.0-multilib-lib",
- compile_multilib: "32",
+ compile_multilib: "prefer32",
soong_config_variables: {
TARGET_ENABLE_MEDIADRM_64: {
compile_multilib: "both",
@@ -56,7 +56,7 @@
android_hardware_drm_1_0_multilib {
name: "android.hardware.drm@1.0-multilib-exe",
- compile_multilib: "32",
+ compile_multilib: "prefer32",
soong_config_variables: {
TARGET_ENABLE_MEDIADRM_64: {
compile_multilib: "first",
diff --git a/graphics/allocator/2.0/default/OWNERS b/graphics/allocator/2.0/default/OWNERS
index 2a56b38..c9f24d0 100644
--- a/graphics/allocator/2.0/default/OWNERS
+++ b/graphics/allocator/2.0/default/OWNERS
@@ -1,4 +1,4 @@
# Graphics team
chrisforbes@google.com
-stoza@google.com
-vhau@google.com
+jreck@google.com
+lpy@google.com
diff --git a/graphics/allocator/2.0/utils/OWNERS b/graphics/allocator/2.0/utils/OWNERS
index 2a56b38..c9f24d0 100644
--- a/graphics/allocator/2.0/utils/OWNERS
+++ b/graphics/allocator/2.0/utils/OWNERS
@@ -1,4 +1,4 @@
# Graphics team
chrisforbes@google.com
-stoza@google.com
-vhau@google.com
+jreck@google.com
+lpy@google.com
diff --git a/graphics/composer/2.1/default/OWNERS b/graphics/composer/2.1/default/OWNERS
index db8fb80..709c4d1 100644
--- a/graphics/composer/2.1/default/OWNERS
+++ b/graphics/composer/2.1/default/OWNERS
@@ -1,6 +1,3 @@
# Graphics team
-courtneygo@google.com
-jessehall@google.com
+adyabr@google.com
lpy@google.com
-stoza@google.com
-vhau@google.com
diff --git a/graphics/composer/2.1/utils/OWNERS b/graphics/composer/2.1/utils/OWNERS
index 5acc631..7af69b4 100644
--- a/graphics/composer/2.1/utils/OWNERS
+++ b/graphics/composer/2.1/utils/OWNERS
@@ -1,5 +1,2 @@
-courtneygo@google.com
-jessehall@google.com
+adyabr@google.com
lpy@google.com
-stoza@google.com
-vhau@google.com
diff --git a/graphics/composer/2.2/default/OWNERS b/graphics/composer/2.2/default/OWNERS
index db8fb80..709c4d1 100644
--- a/graphics/composer/2.2/default/OWNERS
+++ b/graphics/composer/2.2/default/OWNERS
@@ -1,6 +1,3 @@
# Graphics team
-courtneygo@google.com
-jessehall@google.com
+adyabr@google.com
lpy@google.com
-stoza@google.com
-vhau@google.com
diff --git a/graphics/composer/2.2/utils/OWNERS b/graphics/composer/2.2/utils/OWNERS
index 3f1e82c..709c4d1 100644
--- a/graphics/composer/2.2/utils/OWNERS
+++ b/graphics/composer/2.2/utils/OWNERS
@@ -1,5 +1,3 @@
# Graphics team
-courtneygo@google.com
+adyabr@google.com
lpy@google.com
-stoza@google.com
-vhau@google.com
diff --git a/graphics/composer/2.2/vts/functional/OWNERS b/graphics/composer/2.2/vts/functional/OWNERS
index a17a50c..ea06752 100644
--- a/graphics/composer/2.2/vts/functional/OWNERS
+++ b/graphics/composer/2.2/vts/functional/OWNERS
@@ -1,8 +1,6 @@
# Graphics team
-courtneygo@google.com
+adyabr@google.com
lpy@google.com
-stoza@google.com
-vhau@google.com
# VTS team
yim@google.com
diff --git a/graphics/composer/2.3/default/OWNERS b/graphics/composer/2.3/default/OWNERS
index 820ebe6..709c4d1 100644
--- a/graphics/composer/2.3/default/OWNERS
+++ b/graphics/composer/2.3/default/OWNERS
@@ -1,5 +1,3 @@
# Graphics team
-jessehall@google.com
+adyabr@google.com
lpy@google.com
-stoza@google.com
-vhau@google.com
diff --git a/graphics/composer/2.3/utils/OWNERS b/graphics/composer/2.3/utils/OWNERS
index cc6d937..709c4d1 100644
--- a/graphics/composer/2.3/utils/OWNERS
+++ b/graphics/composer/2.3/utils/OWNERS
@@ -1,4 +1,3 @@
# Graphics team
+adyabr@google.com
lpy@google.com
-stoza@google.com
-vhau@google.com
diff --git a/graphics/composer/2.3/vts/functional/OWNERS b/graphics/composer/2.3/vts/functional/OWNERS
index b3ea6be..ea06752 100644
--- a/graphics/composer/2.3/vts/functional/OWNERS
+++ b/graphics/composer/2.3/vts/functional/OWNERS
@@ -1,7 +1,6 @@
# Graphics team
+adyabr@google.com
lpy@google.com
-stoza@google.com
-vhau@google.com
# VTS team
yim@google.com
diff --git a/graphics/composer/2.4/default/OWNERS b/graphics/composer/2.4/default/OWNERS
index cc6d937..709c4d1 100644
--- a/graphics/composer/2.4/default/OWNERS
+++ b/graphics/composer/2.4/default/OWNERS
@@ -1,4 +1,3 @@
# Graphics team
+adyabr@google.com
lpy@google.com
-stoza@google.com
-vhau@google.com
diff --git a/graphics/composer/2.4/utils/OWNERS b/graphics/composer/2.4/utils/OWNERS
index cc6d937..709c4d1 100644
--- a/graphics/composer/2.4/utils/OWNERS
+++ b/graphics/composer/2.4/utils/OWNERS
@@ -1,4 +1,3 @@
# Graphics team
+adyabr@google.com
lpy@google.com
-stoza@google.com
-vhau@google.com
diff --git a/graphics/composer/2.4/vts/functional/OWNERS b/graphics/composer/2.4/vts/functional/OWNERS
index b3ea6be..ea06752 100644
--- a/graphics/composer/2.4/vts/functional/OWNERS
+++ b/graphics/composer/2.4/vts/functional/OWNERS
@@ -1,7 +1,6 @@
# Graphics team
+adyabr@google.com
lpy@google.com
-stoza@google.com
-vhau@google.com
# VTS team
yim@google.com
diff --git a/graphics/mapper/2.0/default/OWNERS b/graphics/mapper/2.0/default/OWNERS
index 2a56b38..c9f24d0 100644
--- a/graphics/mapper/2.0/default/OWNERS
+++ b/graphics/mapper/2.0/default/OWNERS
@@ -1,4 +1,4 @@
# Graphics team
chrisforbes@google.com
-stoza@google.com
-vhau@google.com
+jreck@google.com
+lpy@google.com
diff --git a/graphics/mapper/2.0/utils/OWNERS b/graphics/mapper/2.0/utils/OWNERS
index 2a56b38..c9f24d0 100644
--- a/graphics/mapper/2.0/utils/OWNERS
+++ b/graphics/mapper/2.0/utils/OWNERS
@@ -1,4 +1,4 @@
# Graphics team
chrisforbes@google.com
-stoza@google.com
-vhau@google.com
+jreck@google.com
+lpy@google.com
diff --git a/graphics/mapper/2.0/vts/OWNERS b/graphics/mapper/2.0/vts/OWNERS
index 11b7d21..4177296 100644
--- a/graphics/mapper/2.0/vts/OWNERS
+++ b/graphics/mapper/2.0/vts/OWNERS
@@ -1,7 +1,7 @@
# Graphics team
chrisforbes@google.com
-stoza@google.com
-vhau@google.com
+jreck@google.com
+lpy@google.com
# VTS team
yim@google.com
diff --git a/graphics/mapper/2.1/default/OWNERS b/graphics/mapper/2.1/default/OWNERS
index 2a56b38..c9f24d0 100644
--- a/graphics/mapper/2.1/default/OWNERS
+++ b/graphics/mapper/2.1/default/OWNERS
@@ -1,4 +1,4 @@
# Graphics team
chrisforbes@google.com
-stoza@google.com
-vhau@google.com
+jreck@google.com
+lpy@google.com
diff --git a/graphics/mapper/2.1/utils/OWNERS b/graphics/mapper/2.1/utils/OWNERS
index 2a56b38..c9f24d0 100644
--- a/graphics/mapper/2.1/utils/OWNERS
+++ b/graphics/mapper/2.1/utils/OWNERS
@@ -1,4 +1,4 @@
# Graphics team
chrisforbes@google.com
-stoza@google.com
-vhau@google.com
+jreck@google.com
+lpy@google.com
diff --git a/graphics/mapper/2.1/vts/OWNERS b/graphics/mapper/2.1/vts/OWNERS
index 11b7d21..4177296 100644
--- a/graphics/mapper/2.1/vts/OWNERS
+++ b/graphics/mapper/2.1/vts/OWNERS
@@ -1,7 +1,7 @@
# Graphics team
chrisforbes@google.com
-stoza@google.com
-vhau@google.com
+jreck@google.com
+lpy@google.com
# VTS team
yim@google.com
diff --git a/graphics/mapper/3.0/utils/OWNERS b/graphics/mapper/3.0/utils/OWNERS
index 2a56b38..c9f24d0 100644
--- a/graphics/mapper/3.0/utils/OWNERS
+++ b/graphics/mapper/3.0/utils/OWNERS
@@ -1,4 +1,4 @@
# Graphics team
chrisforbes@google.com
-stoza@google.com
-vhau@google.com
+jreck@google.com
+lpy@google.com
diff --git a/graphics/mapper/3.0/vts/OWNERS b/graphics/mapper/3.0/vts/OWNERS
index 2a56b38..c9f24d0 100644
--- a/graphics/mapper/3.0/vts/OWNERS
+++ b/graphics/mapper/3.0/vts/OWNERS
@@ -1,4 +1,4 @@
# Graphics team
chrisforbes@google.com
-stoza@google.com
-vhau@google.com
+jreck@google.com
+lpy@google.com
diff --git a/graphics/mapper/4.0/utils/OWNERS b/graphics/mapper/4.0/utils/OWNERS
index 2a56b38..c9f24d0 100644
--- a/graphics/mapper/4.0/utils/OWNERS
+++ b/graphics/mapper/4.0/utils/OWNERS
@@ -1,4 +1,4 @@
# Graphics team
chrisforbes@google.com
-stoza@google.com
-vhau@google.com
+jreck@google.com
+lpy@google.com
diff --git a/graphics/mapper/4.0/vts/OWNERS b/graphics/mapper/4.0/vts/OWNERS
index 2a56b38..c9f24d0 100644
--- a/graphics/mapper/4.0/vts/OWNERS
+++ b/graphics/mapper/4.0/vts/OWNERS
@@ -1,4 +1,4 @@
# Graphics team
chrisforbes@google.com
-stoza@google.com
-vhau@google.com
+jreck@google.com
+lpy@google.com
diff --git a/graphics/mapper/4.0/vts/functional/Android.bp b/graphics/mapper/4.0/vts/functional/Android.bp
index 8bda425..2d39daa 100644
--- a/graphics/mapper/4.0/vts/functional/Android.bp
+++ b/graphics/mapper/4.0/vts/functional/Android.bp
@@ -19,7 +19,7 @@
defaults: ["VtsHalTargetTestDefaults"],
srcs: ["VtsHalGraphicsMapperV4_0TargetTest.cpp"],
static_libs: [
- "android.hardware.graphics.common-unstable-ndk_platform",
+ "android.hardware.graphics.common-V2-ndk_platform",
"android.hardware.graphics.mapper@4.0-vts",
"libgralloctypes",
"libsync",
diff --git a/health/1.0/Android.bp b/health/1.0/Android.bp
index 7845871..7786c08 100644
--- a/health/1.0/Android.bp
+++ b/health/1.0/Android.bp
@@ -5,7 +5,6 @@
root: "android.hardware",
srcs: [
"types.hal",
- "IHealth.hal",
],
interfaces: [
"android.hidl.base@1.0",
diff --git a/health/1.0/IHealth.hal b/health/1.0/IHealth.hal
deleted file mode 100644
index 3828589..0000000
--- a/health/1.0/IHealth.hal
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2016 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.health@1.0;
-
-interface IHealth {
- /**
- * This function lets you change healthd configuration from default if
- * desired. It must be called exactly once at startup time.
- *
- * The configuration values are described in 'struct HealthConfig'.
- * To use default configuration, simply return without modifying the
- * fields of the config parameter.
- *
- * @param default healthd configuration.
- */
- init(HealthConfig config) generates (HealthConfig configOut);
-
- /**
- * This function is a hook to update/change device's HealthInfo (as described
- * in 'struct HealthInfo').
- *
- * 'HealthInfo' describes device's battery and charging status, typically
- * read from kernel. These values may be modified in this call.
- *
- * @param Device Health info as described in 'struct HealthInfo'.
- * @return skipLogging Indication to the caller to add 'or' skip logging the health
- * information. Return 'true' to skip logging the update.
- * @return infoOut HealthInfo to be sent to client code. (May or may
- * not be modified).
- */
- update(HealthInfo info) generates (bool skipLogging, HealthInfo infoOut);
-
- /**
- * This function is called by healthd when framework queries for remaining
- * energy in the Battery through BatteryManager APIs.
- *
- * @return result Result of querying enery counter for the battery.
- * @return energy Battery remaining energy in nanowatt-hours.
- * Must be '0' if result is anything other than Result::SUCCESS.
- */
- energyCounter() generates (Result result, int64_t energy);
-};
diff --git a/health/1.0/default/Android.bp b/health/1.0/default/Android.bp
index aab9cc7..ff4b875 100644
--- a/health/1.0/default/Android.bp
+++ b/health/1.0/default/Android.bp
@@ -17,62 +17,3 @@
],
}
-
-cc_library_static {
- name: "android.hardware.health@1.0-impl-helper",
- vendor: true,
- srcs: ["Health.cpp"],
-
- header_libs: [
- "libbase_headers",
- "libhealthd_headers",
- ],
-
- shared_libs: [
- "libcutils",
- "libhidlbase",
- "liblog",
- "libutils",
- "android.hardware.health@1.0",
- ],
-
- static_libs: [
- "android.hardware.health@1.0-convert",
- ],
-}
-
-cc_library_shared {
- name: "android.hardware.health@1.0-impl",
- vendor: true,
- relative_install_path: "hw",
-
- static_libs: [
- "android.hardware.health@1.0-impl-helper",
- "android.hardware.health@1.0-convert",
- "libhealthd.default",
- ],
-
- shared_libs: [
- "libhidlbase",
- "libutils",
- "android.hardware.health@1.0",
- ],
-}
-
-cc_binary {
- name: "android.hardware.health@1.0-service",
- vendor: true,
- relative_install_path: "hw",
- init_rc: ["android.hardware.health@1.0-service.rc"],
- srcs: ["HealthService.cpp"],
-
- shared_libs: [
- "liblog",
- "libcutils",
- "libdl",
- "libbase",
- "libutils",
- "libhidlbase",
- "android.hardware.health@1.0",
- ],
-}
diff --git a/health/1.0/default/Health.cpp b/health/1.0/default/Health.cpp
deleted file mode 100644
index 1a02956..0000000
--- a/health/1.0/default/Health.cpp
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "health-hal"
-
-#include <Health.h>
-#include <include/hal_conversion.h>
-
-namespace android {
-namespace hardware {
-namespace health {
-namespace V1_0 {
-namespace implementation {
-
-using ::android::hardware::health::V1_0::hal_conversion::convertToHealthConfig;
-using ::android::hardware::health::V1_0::hal_conversion::convertFromHealthConfig;
-using ::android::hardware::health::V1_0::hal_conversion::convertToHealthInfo;
-using ::android::hardware::health::V1_0::hal_conversion::convertFromHealthInfo;
-
-// Methods from ::android::hardware::health::V1_0::IHealth follow.
-Return<void> Health::init(const HealthConfig& config, init_cb _hidl_cb) {
- struct healthd_config healthd_config = {};
- HealthConfig configOut;
-
- // To keep working with existing healthd static HALs,
- // convert the new HealthConfig to the old healthd_config
- // and back.
-
- convertFromHealthConfig(config, &healthd_config);
- healthd_board_init(&healthd_config);
- mGetEnergyCounter = healthd_config.energyCounter;
- convertToHealthConfig(&healthd_config, configOut);
-
- _hidl_cb(configOut);
-
- return Void();
-}
-
-Return<void> Health::update(const HealthInfo& info, update_cb _hidl_cb) {
- struct android::BatteryProperties p = {};
- HealthInfo infoOut;
-
- // To keep working with existing healthd static HALs,
- // convert the new HealthInfo to android::Batteryproperties
- // and back.
-
- convertFromHealthInfo(info, &p);
- int skipLogging = healthd_board_battery_update(&p);
- convertToHealthInfo(&p, infoOut);
-
- _hidl_cb(!!skipLogging, infoOut);
-
- return Void();
-}
-
-Return<void> Health::energyCounter(energyCounter_cb _hidl_cb) {
- int64_t energy = 0;
- Result result = Result::NOT_SUPPORTED;
-
- if (mGetEnergyCounter) {
- int status = mGetEnergyCounter(&energy);
- if (status == 0) {
- result = Result::SUCCESS;
- }
- }
-
- _hidl_cb(result, energy);
-
- return Void();
-}
-
-IHealth* HIDL_FETCH_IHealth(const char* /* name */) {
- return new Health();
-}
-
-} // namespace implementation
-} // namespace V1_0
-} // namespace health
-} // namespace hardware
-} // namespace android
diff --git a/health/1.0/default/Health.h b/health/1.0/default/Health.h
deleted file mode 100644
index ed364c1..0000000
--- a/health/1.0/default/Health.h
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#ifndef ANDROID_HARDWARE_HEALTH_V1_0_HEALTH_H
-#define ANDROID_HARDWARE_HEALTH_V1_0_HEALTH_H
-
-#include <android/hardware/health/1.0/IHealth.h>
-#include <hidl/Status.h>
-#include <hidl/MQDescriptor.h>
-#include <healthd/healthd.h>
-#include <utils/String8.h>
-
-namespace android {
-namespace hardware {
-namespace health {
-namespace V1_0 {
-namespace implementation {
-
-using ::android::hardware::health::V1_0::HealthInfo;
-using ::android::hardware::health::V1_0::HealthConfig;
-using ::android::hardware::health::V1_0::IHealth;
-using ::android::hardware::Return;
-using ::android::hardware::Void;
-using ::android::hardware::hidl_vec;
-using ::android::hardware::hidl_string;
-using ::android::sp;
-
-struct Health : public IHealth {
- // Methods from ::android::hardware::health::V1_0::IHealth follow.
- Return<void> init(const HealthConfig& config, init_cb _hidl_cb) override;
- Return<void> update(const HealthInfo& info, update_cb _hidl_cb) override;
- Return<void> energyCounter(energyCounter_cb _hidl_cb) override;
-private:
- std::function<int(int64_t *)> mGetEnergyCounter;
-};
-
-extern "C" IHealth* HIDL_FETCH_IHealth(const char* name);
-
-} // namespace implementation
-} // namespace V1_0
-} // namespace health
-} // namespace hardware
-} // namespace android
-
-#endif // ANDROID_HARDWARE_HEALTH_V1_0_HEALTH_H
diff --git a/health/1.0/default/HealthService.cpp b/health/1.0/default/HealthService.cpp
deleted file mode 100644
index 55848d2..0000000
--- a/health/1.0/default/HealthService.cpp
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "android.hardware.health@1.0-service"
-
-#include <android/hardware/health/1.0/IHealth.h>
-#include <hidl/LegacySupport.h>
-
-using android::hardware::health::V1_0::IHealth;
-using android::hardware::defaultPassthroughServiceImplementation;
-
-int main() {
- return defaultPassthroughServiceImplementation<IHealth>();
-}
diff --git a/health/1.0/default/README.md b/health/1.0/default/README.md
deleted file mode 100644
index 1ded7de..0000000
--- a/health/1.0/default/README.md
+++ /dev/null
@@ -1,66 +0,0 @@
-# Implement the 2.1 HAL instead!
-
-It is strongly recommended that you implement the 2.1 HAL directly. See
-`hardware/interfaces/health/2.1/README.md` for more details.
-
-# Implement Health 1.0 HAL
-
-1. Install common binderized service. The binderized service `dlopen()`s
- passthrough implementations on the device, so there is no need to write
- your own.
-
- ```mk
- # Install default binderized implementation to vendor.
- PRODUCT_PACKAGES += android.hardware.health@1.0-service
- ```
-
-1. Add proper VINTF manifest entry to your device manifest. Example:
-
- ```xml
- <hal format="hidl">
- <name>android.hardware.health</name>
- <transport>hwbinder</transport>
- <version>1.0</version>
- <interface>
- <name>IHealth</name>
- <instance>default</instance>
- </interface>
- </hal>
- ```
-
-1. Install the proper passthrough implemetation.
-
- 1. If you want to use the default implementation (with default `libhealthd`),
- add the following to `device.mk`:
-
- ```mk
- PRODUCT_PACKAGES += \
- android.hardware.health@1.0-impl
- ```
-
- 1. Otherwise, if you have a customized `libhealthd.<board>`:
-
- 1. Define your passthrough implementation. Example (replace `<device>`
- and `<board>` accordingly):
-
- ```bp
- cc_library_shared {
- name: "android.hardware.health@1.0-impl-<device>",
- vendor: true,
- relative_install_path: "hw",
-
- static_libs: [
- "android.hardware.health@1.0-impl-helper",
- "android.hardware.health@1.0-convert",
- "libhealthd.<board>",
- ],
- }
- ```
-
- 1. Add to `device.mk`.
-
- ```
- PRODUCT_PACKAGES += android.hardware.health@1.0-impl-<device>
- ```
-
- 1. Define appropriate SELinux permissions.
diff --git a/health/1.0/default/android.hardware.health@1.0-service.rc b/health/1.0/default/android.hardware.health@1.0-service.rc
deleted file mode 100644
index 569dc88..0000000
--- a/health/1.0/default/android.hardware.health@1.0-service.rc
+++ /dev/null
@@ -1,5 +0,0 @@
-service vendor.health-hal-1-0 /vendor/bin/hw/android.hardware.health@1.0-service
- class hal
- user system
- group system
- capabilities WAKE_ALARM BLOCK_SUSPEND
diff --git a/health/1.0/default/include/hal_conversion.h b/health/1.0/default/include/hal_conversion.h
index a92b208..a8ddb73 100644
--- a/health/1.0/default/include/hal_conversion.h
+++ b/health/1.0/default/include/hal_conversion.h
@@ -17,7 +17,7 @@
#ifndef HARDWARE_INTERFACES_HEALTH_V1_0_DEFAULT_INCLUDE_HAL_CONVERSION_H_
#define HARDWARE_INTERFACES_HEALTH_V1_0_DEFAULT_INCLUDE_HAL_CONVERSION_H_
-#include <android/hardware/health/1.0/IHealth.h>
+#include <android/hardware/health/1.0/types.h>
#include <healthd/healthd.h>
namespace android {
diff --git a/health/1.0/default/libhealthd/Android.bp b/health/1.0/default/libhealthd/Android.bp
deleted file mode 100644
index 43463eb..0000000
--- a/health/1.0/default/libhealthd/Android.bp
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright 2016 The Android Open Source Project
-
-cc_library_static {
- srcs: ["healthd_board_default.cpp"],
- name: "libhealthd.default",
- vendor_available: true,
- recovery_available: true,
- cflags: ["-Werror"],
- include_dirs: ["system/libbase/include"],
- header_libs: ["libhealthd_headers"],
-}
diff --git a/health/1.0/vts/functional/Android.bp b/health/1.0/vts/functional/Android.bp
deleted file mode 100644
index f4a04a7..0000000
--- a/health/1.0/vts/functional/Android.bp
+++ /dev/null
@@ -1,23 +0,0 @@
-//
-// Copyright (C) 2017 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-cc_test {
- name: "VtsHalHealthV1_0TargetTest",
- defaults: ["VtsHalTargetTestDefaults"],
- srcs: ["VtsHalHealthV1_0TargetTest.cpp"],
- static_libs: ["android.hardware.health@1.0"],
- test_suites: ["general-tests", "vts"],
-}
diff --git a/health/1.0/vts/functional/VtsHalHealthV1_0TargetTest.cpp b/health/1.0/vts/functional/VtsHalHealthV1_0TargetTest.cpp
deleted file mode 100644
index 8b3dcc1..0000000
--- a/health/1.0/vts/functional/VtsHalHealthV1_0TargetTest.cpp
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "health_hidl_hal_test"
-
-#include <android/hardware/health/1.0/IHealth.h>
-#include <android/hardware/health/1.0/types.h>
-#include <gtest/gtest.h>
-#include <hidl/GtestPrinter.h>
-#include <hidl/ServiceManagement.h>
-#include <log/log.h>
-
-using HealthConfig = ::android::hardware::health::V1_0::HealthConfig;
-using HealthInfo = ::android::hardware::health::V1_0::HealthInfo;
-using IHealth = ::android::hardware::health::V1_0::IHealth;
-using Result = ::android::hardware::health::V1_0::Result;
-
-using ::android::sp;
-
-class HealthHidlTest : public ::testing::TestWithParam<std::string> {
- public:
- virtual void SetUp() override {
- health = IHealth::getService(GetParam());
- ASSERT_NE(health, nullptr);
- health->init(config,
- [&](const auto& halConfigOut) { config = halConfigOut; });
- }
-
- sp<IHealth> health;
- HealthConfig config;
-};
-
-/**
- * Ensure EnergyCounter call returns positive energy counter or NOT_SUPPORTED
- */
-TEST_P(HealthHidlTest, TestEnergyCounter) {
- Result result;
- int64_t energy = 0;
- health->energyCounter([&](Result ret, int64_t energyOut) {
- result = ret;
- energy = energyOut;
- });
-
- ASSERT_TRUE(result == Result::SUCCESS || result == Result::NOT_SUPPORTED);
- ASSERT_TRUE(result != Result::SUCCESS || energy > 0);
-}
-
-GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(HealthHidlTest);
-INSTANTIATE_TEST_SUITE_P(
- PerInstance, HealthHidlTest,
- testing::ValuesIn(android::hardware::getAllHalInstanceNames(IHealth::descriptor)),
- android::hardware::PrintInstanceNameToString);
diff --git a/health/storage/1.0/default/Android.bp b/health/storage/1.0/default/Android.bp
index 3156dfe..3834244 100644
--- a/health/storage/1.0/default/Android.bp
+++ b/health/storage/1.0/default/Android.bp
@@ -38,6 +38,7 @@
],
static_libs: [
+ "libhealth_storage_impl_common",
"libfstab",
],
diff --git a/health/storage/1.0/default/Storage.cpp b/health/storage/1.0/default/Storage.cpp
index 561deaa..02b6a3d 100644
--- a/health/storage/1.0/default/Storage.cpp
+++ b/health/storage/1.0/default/Storage.cpp
@@ -18,11 +18,8 @@
#include <sstream>
-#include <android-base/chrono_utils.h>
-#include <android-base/file.h>
#include <android-base/logging.h>
-#include <android-base/strings.h>
-#include <fstab/fstab.h>
+#include <health-storage-impl/common.h>
namespace android {
namespace hardware {
@@ -31,69 +28,9 @@
namespace V1_0 {
namespace implementation {
-using base::ReadFileToString;
-using base::Timer;
-using base::Trim;
-using base::WriteStringToFd;
-using base::WriteStringToFile;
-using fs_mgr::Fstab;
-using fs_mgr::ReadDefaultFstab;
-
-std::string getGarbageCollectPath() {
- Fstab fstab;
- ReadDefaultFstab(&fstab);
-
- for (const auto& entry : fstab) {
- if (!entry.sysfs_path.empty()) {
- return entry.sysfs_path + "/manual_gc";
- }
- }
-
- return "";
-}
-
Return<void> Storage::garbageCollect(uint64_t timeoutSeconds,
const sp<IGarbageCollectCallback>& cb) {
- Result result = Result::SUCCESS;
- std::string path = getGarbageCollectPath();
-
- if (path.empty()) {
- LOG(WARNING) << "Cannot find Dev GC path";
- result = Result::UNKNOWN_ERROR;
- } else {
- Timer timer;
- LOG(INFO) << "Start Dev GC on " << path;
- while (1) {
- std::string require;
- if (!ReadFileToString(path, &require)) {
- PLOG(WARNING) << "Reading manual_gc failed in " << path;
- result = Result::IO_ERROR;
- break;
- }
- require = Trim(require);
- if (require == "" || require == "off" || require == "disabled") {
- LOG(DEBUG) << "No more to do Dev GC";
- break;
- }
- LOG(DEBUG) << "Trigger Dev GC on " << path;
- if (!WriteStringToFile("1", path)) {
- PLOG(WARNING) << "Start Dev GC failed on " << path;
- result = Result::IO_ERROR;
- break;
- }
- if (timer.duration() >= std::chrono::seconds(timeoutSeconds)) {
- LOG(WARNING) << "Dev GC timeout";
- // Timeout is not treated as an error. Try next time.
- break;
- }
- sleep(2);
- }
- LOG(INFO) << "Stop Dev GC on " << path;
- if (!WriteStringToFile("0", path)) {
- PLOG(WARNING) << "Stop Dev GC failed on " << path;
- result = Result::IO_ERROR;
- }
- }
+ Result result = GarbageCollect(timeoutSeconds);
if (cb != nullptr) {
auto ret = cb->onFinish(result);
@@ -110,28 +47,7 @@
}
int fd = handle->data[0];
- std::stringstream output;
-
- std::string path = getGarbageCollectPath();
- if (path.empty()) {
- output << "Cannot find Dev GC path";
- } else {
- std::string require;
-
- if (ReadFileToString(path, &require)) {
- output << path << ":" << require << std::endl;
- }
-
- if (WriteStringToFile("0", path)) {
- output << "stop success" << std::endl;
- }
- }
-
- if (!WriteStringToFd(output.str(), fd)) {
- PLOG(WARNING) << "debug: cannot write to fd";
- }
-
- fsync(fd);
+ DebugDump(fd);
return Void();
}
diff --git a/health/storage/1.0/vts/functional/Android.bp b/health/storage/1.0/vts/functional/Android.bp
index 2201031..731ad62 100644
--- a/health/storage/1.0/vts/functional/Android.bp
+++ b/health/storage/1.0/vts/functional/Android.bp
@@ -19,6 +19,9 @@
defaults: ["VtsHalTargetTestDefaults"],
srcs: ["VtsHalHealthStorageV1_0TargetTest.cpp"],
static_libs: ["android.hardware.health.storage@1.0"],
+ header_libs: [
+ "libhealth_storage_test_common_headers",
+ ],
shared_libs: [
"libhidlbase",
],
diff --git a/health/storage/1.0/vts/functional/VtsHalHealthStorageV1_0TargetTest.cpp b/health/storage/1.0/vts/functional/VtsHalHealthStorageV1_0TargetTest.cpp
index 24ddc5d..ddb6b5a 100644
--- a/health/storage/1.0/vts/functional/VtsHalHealthStorageV1_0TargetTest.cpp
+++ b/health/storage/1.0/vts/functional/VtsHalHealthStorageV1_0TargetTest.cpp
@@ -14,14 +14,17 @@
* limitations under the License.
*/
+#include <unistd.h>
+
+#include <thread>
+
#include <android-base/logging.h>
#include <android/hardware/health/storage/1.0/IStorage.h>
#include <gtest/gtest.h>
+#include <health-storage-test/common.h>
#include <hidl/GtestPrinter.h>
#include <hidl/HidlTransportSupport.h>
#include <hidl/ServiceManagement.h>
-#include <unistd.h>
-#include <thread>
namespace android {
namespace hardware {
@@ -29,61 +32,17 @@
namespace storage {
namespace V1_0 {
+using namespace ::android::hardware::health::storage::test;
using ::std::literals::chrono_literals::operator""ms;
#define ASSERT_OK(ret) ASSERT_TRUE(ret.isOk()) << ret.description()
-// Dev GC timeout. This is the timeout used by vold.
-const uint64_t kDevGcTimeoutSec = 120;
-const std::chrono::seconds kDevGcTimeout{kDevGcTimeoutSec};
-// Dev GC timeout tolerance. The HAL may not immediately return after the
-// timeout, so include an acceptable tolerance.
-const std::chrono::seconds kDevGcTolerance{3};
-// Time accounted for RPC calls.
-const std::chrono::milliseconds kRpcTime{1000};
-
-template <typename R>
-std::string toString(std::chrono::duration<R, std::milli> time) {
- return std::to_string(time.count()) + "ms";
-}
-
-/** An atomic boolean flag that indicates whether a task has finished. */
-class Flag {
- public:
- void onFinish() {
- std::unique_lock<std::mutex> lock(mMutex);
- onFinishLocked(&lock);
- }
- template <typename R, typename P>
- bool wait(std::chrono::duration<R, P> duration) {
- std::unique_lock<std::mutex> lock(mMutex);
- return waitLocked(&lock, duration);
- }
-
- protected:
- /** Will unlock. */
- void onFinishLocked(std::unique_lock<std::mutex>* lock) {
- mFinished = true;
- lock->unlock();
- mCv.notify_all();
- }
- template <typename R, typename P>
- bool waitLocked(std::unique_lock<std::mutex>* lock, std::chrono::duration<R, P> duration) {
- mCv.wait_for(*lock, duration, [this] { return mFinished; });
- return mFinished;
- }
-
- bool mFinished{false};
- std::mutex mMutex;
- std::condition_variable mCv;
-};
-
class GcCallback : public IGarbageCollectCallback, public Flag {
- public:
+ public:
Return<void> onFinish(Result result) override {
- std::unique_lock<std::mutex> lock(mMutex);
- mResult = result;
- Flag::onFinishLocked(&lock);
+ std::unique_lock<std::mutex> lock(mutex_);
+ result_ = result;
+ Flag::OnFinishLocked(&lock);
return Void();
}
@@ -93,13 +52,13 @@
*/
template <typename R, typename P>
void waitForResult(std::chrono::duration<R, P> timeout, Result expected) {
- std::unique_lock<std::mutex> lock(mMutex);
- ASSERT_TRUE(waitLocked(&lock, timeout)) << "timeout after " << toString(timeout);
- EXPECT_EQ(expected, mResult);
+ std::unique_lock<std::mutex> lock(mutex_);
+ ASSERT_TRUE(WaitLocked(&lock, timeout)) << "timeout after " << to_string(timeout);
+ EXPECT_EQ(expected, result_);
}
- private:
- Result mResult{Result::UNKNOWN_ERROR};
+ private:
+ Result result_{Result::UNKNOWN_ERROR};
};
class HealthStorageHidlTest : public ::testing::TestWithParam<std::string> {
@@ -127,10 +86,10 @@
auto pingFlag = std::make_shared<Flag>();
std::thread([service, pingFlag] {
service->ping();
- pingFlag->onFinish();
+ pingFlag->OnFinish();
})
.detach();
- return pingFlag->wait(timeout);
+ return pingFlag->Wait(timeout);
}
sp<IStorage> fs;
@@ -147,7 +106,7 @@
// Hold test process because HAL can be single-threaded and doing GC.
ASSERT_TRUE(ping(kDevGcTimeout + kDevGcTolerance + kRpcTime))
<< "Service must be available after "
- << toString(kDevGcTimeout + kDevGcTolerance + kRpcTime);
+ << to_string(kDevGcTimeout + kDevGcTolerance + kRpcTime);
}
/**
diff --git a/health/storage/aidl/Android.bp b/health/storage/aidl/Android.bp
new file mode 100644
index 0000000..c39a46d
--- /dev/null
+++ b/health/storage/aidl/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2021 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.
+
+aidl_interface {
+ name: "android.hardware.health.storage",
+ vendor_available: true,
+ srcs: ["android/hardware/health/storage/*.aidl"],
+ stability: "vintf",
+ backend: {
+ cpp: {
+ enabled: false,
+ },
+ java: {
+ enabled: false,
+ },
+ ndk: {
+ vndk: {
+ enabled: true,
+ },
+ },
+ },
+}
diff --git a/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/IGarbageCollectCallback.aidl b/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/IGarbageCollectCallback.aidl
new file mode 100644
index 0000000..0f382d7
--- /dev/null
+++ b/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/IGarbageCollectCallback.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// 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.health.storage;
+@VintfStability
+interface IGarbageCollectCallback {
+ oneway void onFinish(in android.hardware.health.storage.Result result);
+}
diff --git a/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/IStorage.aidl b/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/IStorage.aidl
new file mode 100644
index 0000000..61f838a
--- /dev/null
+++ b/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/IStorage.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// 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.health.storage;
+@VintfStability
+interface IStorage {
+ oneway void garbageCollect(in long timeoutSeconds, in android.hardware.health.storage.IGarbageCollectCallback callback);
+}
diff --git a/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/Result.aidl b/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/Result.aidl
new file mode 100644
index 0000000..a345808
--- /dev/null
+++ b/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/Result.aidl
@@ -0,0 +1,25 @@
+///////////////////////////////////////////////////////////////////////////////
+// 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.health.storage;
+@Backing(type="int") @VintfStability
+enum Result {
+ SUCCESS = 0,
+ IO_ERROR = 1,
+ UNKNOWN_ERROR = 2,
+}
diff --git a/health/storage/aidl/android/hardware/health/storage/IGarbageCollectCallback.aidl b/health/storage/aidl/android/hardware/health/storage/IGarbageCollectCallback.aidl
new file mode 100644
index 0000000..ccd1b44
--- /dev/null
+++ b/health/storage/aidl/android/hardware/health/storage/IGarbageCollectCallback.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 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.health.storage;
+
+import android.hardware.health.storage.Result;
+
+/**
+ * Callback interface to IStorage.garbageCollect.
+ */
+@VintfStability
+interface IGarbageCollectCallback {
+ /**
+ * When garbage collection has finished, the implementation must
+ * invoke this function to indicate the result of the garbage collection.
+ *
+ * @param out result Execution result. See documentation for Result for
+ * details.
+ */
+ oneway void onFinish(in Result result);
+}
diff --git a/health/storage/aidl/android/hardware/health/storage/IStorage.aidl b/health/storage/aidl/android/hardware/health/storage/IStorage.aidl
new file mode 100644
index 0000000..78992a2
--- /dev/null
+++ b/health/storage/aidl/android/hardware/health/storage/IStorage.aidl
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2021 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.health.storage;
+
+import android.hardware.health.storage.IGarbageCollectCallback;
+
+/**
+ * IStorage is an interface that provides operations on underlying storage
+ * devices, including flash memory.
+ */
+@VintfStability
+interface IStorage {
+ /**
+ * Start garbage collection on the driver of storage devices.
+ *
+ * Garbage collection must be started at regular intervals when it is a good
+ * time for a longer-running cleanup tasks, roughly daily.
+ *
+ * When garbage collection finishes or encounters an error before the
+ * specified timeout, the implementation must call IGarbageCollect.finish
+ * immediately with appropriate result.
+ *
+ * If garbage collection does not finish within the specified timeout,
+ * the implementation must stop garbage collection, and must not call
+ * IGarbageCollect.finish.
+ *
+ * @param timeoutSeconds timeout in seconds. The implementation must
+ * return after the timeout is reached.
+ *
+ * @param callback callback interface. Callback must be null if the client
+ * does not need to receive any callbacks.
+ *
+ */
+ oneway void garbageCollect(in long timeoutSeconds, in IGarbageCollectCallback callback);
+}
diff --git a/health/storage/aidl/android/hardware/health/storage/Result.aidl b/health/storage/aidl/android/hardware/health/storage/Result.aidl
new file mode 100644
index 0000000..73bb779
--- /dev/null
+++ b/health/storage/aidl/android/hardware/health/storage/Result.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 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.health.storage;
+
+/**
+ * Status values for HAL methods.
+ */
+@VintfStability
+@Backing(type="int")
+enum Result {
+ /**
+ * Execution of the method is successful.
+ */
+ SUCCESS = 0,
+ /**
+ * An IO error is encountered when the HAL communicates with the device.
+ */
+ IO_ERROR,
+ /**
+ * An unknown error is encountered.
+ */
+ UNKNOWN_ERROR,
+}
diff --git a/health/storage/aidl/default/Android.bp b/health/storage/aidl/default/Android.bp
new file mode 100644
index 0000000..b53bc35
--- /dev/null
+++ b/health/storage/aidl/default/Android.bp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+cc_defaults {
+ name: "libhealth_storage_impl_defaults",
+ vendor: true,
+ shared_libs: [
+ "libbase",
+ "libbinder_ndk",
+ "android.hardware.health.storage-V1-ndk_platform",
+ ],
+ static_libs: [
+ "libfstab",
+ "libhealth_storage_impl_common",
+ ],
+}
+
+cc_library_static {
+ name: "libhealth_storage_default_impl",
+ defaults: ["libhealth_storage_impl_defaults"],
+ srcs: [
+ "Storage.cpp",
+ ],
+ visibility: [
+ ":__subpackages__",
+ "//hardware/interfaces/tests/extension/health/storage:__subpackages__",
+ ],
+}
+
+cc_binary {
+ name: "android.hardware.health.storage-service.default",
+ defaults: ["libhealth_storage_impl_defaults"],
+ relative_install_path: "hw",
+ init_rc: ["health-storage-default.rc"],
+ vintf_fragments: ["health-storage-default.xml"],
+ srcs: ["main.cpp"],
+ static_libs: [
+ "libhealth_storage_default_impl",
+ ],
+}
diff --git a/health/storage/aidl/default/Storage.cpp b/health/storage/aidl/default/Storage.cpp
new file mode 100644
index 0000000..faa4ff6
--- /dev/null
+++ b/health/storage/aidl/default/Storage.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2021 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 "Storage.h"
+
+#include <sstream>
+
+#include <android-base/logging.h>
+#include <health-storage-impl/common.h>
+
+using ::android::hardware::health::storage::DebugDump;
+using ::android::hardware::health::storage::GarbageCollect;
+
+using HResult = android::hardware::health::storage::V1_0::Result;
+using AResult = aidl::android::hardware::health::storage::Result;
+// Ensure static_cast<AResult>(any HResult) works
+static_assert(static_cast<AResult>(HResult::SUCCESS) == AResult::SUCCESS);
+static_assert(static_cast<AResult>(HResult::IO_ERROR) == AResult::IO_ERROR);
+static_assert(static_cast<AResult>(HResult::UNKNOWN_ERROR) == AResult::UNKNOWN_ERROR);
+
+namespace aidl::android::hardware::health::storage {
+
+ndk::ScopedAStatus Storage::garbageCollect(
+ int64_t timeout_seconds, const std::shared_ptr<IGarbageCollectCallback>& callback) {
+ AResult result = static_cast<AResult>(GarbageCollect(static_cast<uint64_t>(timeout_seconds)));
+ if (callback != nullptr) {
+ auto status = callback->onFinish(result);
+ if (!status.isOk()) {
+ LOG(WARNING) << "Cannot return result " << toString(result)
+ << " to callback: " << status.getDescription();
+ }
+ }
+ return ndk::ScopedAStatus::ok();
+}
+
+binder_status_t Storage::dump(int fd, const char**, uint32_t) {
+ DebugDump(fd);
+ return STATUS_OK;
+}
+
+} // namespace aidl::android::hardware::health::storage
diff --git a/health/storage/aidl/default/Storage.h b/health/storage/aidl/default/Storage.h
new file mode 100644
index 0000000..049991b
--- /dev/null
+++ b/health/storage/aidl/default/Storage.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 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/health/storage/BnStorage.h>
+
+namespace aidl::android::hardware::health::storage {
+
+class Storage : public BnStorage {
+ ndk::ScopedAStatus garbageCollect(
+ int64_t timeout_seconds,
+ const std::shared_ptr<IGarbageCollectCallback>& callback) override;
+ binder_status_t dump(int fd, const char** args, uint32_t num_args) override;
+};
+
+} // namespace aidl::android::hardware::health::storage
diff --git a/health/storage/aidl/default/health-storage-default.rc b/health/storage/aidl/default/health-storage-default.rc
new file mode 100644
index 0000000..fc1cc8b
--- /dev/null
+++ b/health/storage/aidl/default/health-storage-default.rc
@@ -0,0 +1,7 @@
+service vendor.health-storage-default /vendor/bin/hw/android.hardware.health.storage-service.default
+ interface aidl android.hardware.health.storage.IStorage/default
+ oneshot
+ disabled
+ class hal
+ user system
+ group system
diff --git a/health/storage/aidl/default/health-storage-default.xml b/health/storage/aidl/default/health-storage-default.xml
new file mode 100644
index 0000000..14d4901
--- /dev/null
+++ b/health/storage/aidl/default/health-storage-default.xml
@@ -0,0 +1,7 @@
+<manifest version="1.0" type="device">
+ <hal format="aidl">
+ <name>android.hardware.health.storage</name>
+ <version>1</version>
+ <fqname>IStorage/default</fqname>
+ </hal>
+</manifest>
diff --git a/health/storage/aidl/default/main.cpp b/health/storage/aidl/default/main.cpp
new file mode 100644
index 0000000..186b64c
--- /dev/null
+++ b/health/storage/aidl/default/main.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include "Storage.h"
+
+using aidl::android::hardware::health::storage::Storage;
+using std::string_literals::operator""s;
+
+int main() {
+ ABinderProcess_setThreadPoolMaxThreadCount(0);
+
+ // make a default storage service
+ auto storage = ndk::SharedRefBase::make<Storage>();
+ const std::string name = Storage::descriptor + "/default"s;
+ CHECK_EQ(STATUS_OK,
+ AServiceManager_registerLazyService(storage->asBinder().get(), name.c_str()));
+
+ ABinderProcess_joinThreadPool();
+ return EXIT_FAILURE; // should not reach
+}
diff --git a/health/storage/aidl/vts/functional/Android.bp b/health/storage/aidl/vts/functional/Android.bp
new file mode 100644
index 0000000..0e7671d
--- /dev/null
+++ b/health/storage/aidl/vts/functional/Android.bp
@@ -0,0 +1,37 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_test {
+ name: "VtsHalHealthStorageTargetTest",
+ defaults: [
+ "VtsHalTargetTestDefaults",
+ "use_libaidlvintf_gtest_helper_static",
+ ],
+ srcs: [
+ "VtsHalHealthStorageTargetTest.cpp",
+ ],
+ shared_libs: [
+ "libbinder_ndk",
+ ],
+ static_libs: [
+ "android.hardware.health.storage-V1-ndk_platform",
+ ],
+ header_libs: [
+ "libhealth_storage_test_common_headers",
+ ],
+ test_suites: [
+ "vts",
+ ],
+ test_config: "VtsHalHealthStorageTargetTest.xml",
+}
diff --git a/health/storage/aidl/vts/functional/VtsHalHealthStorageTargetTest.cpp b/health/storage/aidl/vts/functional/VtsHalHealthStorageTargetTest.cpp
new file mode 100644
index 0000000..3b6b6b4
--- /dev/null
+++ b/health/storage/aidl/vts/functional/VtsHalHealthStorageTargetTest.cpp
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2021 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 <unistd.h>
+
+#include <chrono>
+#include <set>
+#include <string>
+#include <thread>
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/health/storage/BnGarbageCollectCallback.h>
+#include <aidl/android/hardware/health/storage/IStorage.h>
+#include <android-base/logging.h>
+#include <android/binder_ibinder.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <gtest/gtest.h>
+#include <health-storage-test/common.h>
+
+namespace aidl::android::hardware::health::storage {
+
+using namespace ::android::hardware::health::storage::test;
+using std::chrono_literals::operator""ms;
+
+#define ASSERT_OK(ret) ASSERT_TRUE(ret.isOk()) << ret.getDescription()
+#define EXPECT_OK(ret) EXPECT_TRUE(ret.isOk()) << ret.getDescription()
+
+class GcCallback : public BnGarbageCollectCallback, public Flag {
+ public:
+ ndk::ScopedAStatus onFinish(Result result) override {
+ std::unique_lock<std::mutex> lock(mutex_);
+ result_ = result;
+ OnFinishLocked(&lock);
+ return ndk::ScopedAStatus::ok();
+ }
+
+ /**
+ * Wait for a specific "timeout". If GC has finished, test that the result
+ * is equal to the "expected" value.
+ */
+ template <typename R, typename P>
+ void WaitForResult(std::chrono::duration<R, P> timeout, Result expected) {
+ std::unique_lock<std::mutex> lock(mutex_);
+ ASSERT_TRUE(WaitLocked(&lock, timeout)) << "timeout after " << to_string(timeout);
+ EXPECT_EQ(expected, result_);
+ }
+
+ private:
+ Result result_{Result::UNKNOWN_ERROR};
+};
+
+class HealthStorageAidl : public testing::TestWithParam<std::string> {
+ public:
+ virtual void SetUp() override {
+ std::string name = GetParam();
+ ASSERT_TRUE(AServiceManager_isDeclared(name.c_str())) << name;
+ ndk::SpAIBinder binder(AServiceManager_waitForService(name.c_str()));
+ ASSERT_NE(binder, nullptr);
+ storage_ = IStorage::fromBinder(binder);
+ ASSERT_NE(storage_, nullptr);
+ }
+
+ virtual void TearDown() override {
+ EXPECT_TRUE(ping(kRpcTime))
+ << "Service is not responsive; expect subsequent tests to fail.";
+ }
+
+ /**
+ * Ping the service and expect it to return after "timeout". Return true
+ * iff the service is responsive within "timeout".
+ */
+ template <typename R, typename P>
+ bool ping(std::chrono::duration<R, P> timeout) {
+ // Ensure the service is responsive after the test.
+ std::shared_ptr<IStorage> service = storage_;
+ auto ping_flag = std::make_shared<Flag>();
+ std::thread([service, ping_flag] {
+ EXPECT_EQ(STATUS_OK, AIBinder_ping(service->asBinder().get()));
+ ping_flag->OnFinish();
+ }).detach();
+ return ping_flag->Wait(timeout);
+ }
+
+ std::shared_ptr<IStorage> storage_;
+};
+
+/**
+ * Ensure garbage collection works on null callback.
+ */
+TEST_P(HealthStorageAidl, GcNullCallback) {
+ ASSERT_OK(storage_->garbageCollect(kDevGcTimeoutSec, nullptr));
+
+ // Hold test process because HAL can be single-threaded and doing GC.
+ ASSERT_TRUE(ping(kDevGcTimeout + kDevGcTolerance + kRpcTime))
+ << "Service must be available after "
+ << to_string(kDevGcTimeout + kDevGcTolerance + kRpcTime);
+}
+
+/**
+ * Ensure garbage collection works on non-null callback.
+ */
+TEST_P(HealthStorageAidl, GcNonNullCallback) {
+ std::shared_ptr<GcCallback> cb = ndk::SharedRefBase::make<GcCallback>();
+ ASSERT_OK(storage_->garbageCollect(kDevGcTimeoutSec, cb));
+ cb->WaitForResult(kDevGcTimeout + kDevGcTolerance + kRpcTime, Result::SUCCESS);
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(HealthStorageAidl);
+INSTANTIATE_TEST_SUITE_P(
+ HealthStorage, HealthStorageAidl,
+ testing::ValuesIn(::android::getAidlHalInstanceNames(IStorage::descriptor)),
+ ::android::PrintInstanceNameToString);
+
+} // namespace aidl::android::hardware::health::storage
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ ABinderProcess_setThreadPoolMaxThreadCount(1);
+ ABinderProcess_startThreadPool();
+ return RUN_ALL_TESTS();
+}
diff --git a/health/storage/aidl/vts/functional/VtsHalHealthStorageTargetTest.xml b/health/storage/aidl/vts/functional/VtsHalHealthStorageTargetTest.xml
new file mode 100644
index 0000000..f8a1c87
--- /dev/null
+++ b/health/storage/aidl/vts/functional/VtsHalHealthStorageTargetTest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+<configuration description="Runs VtsHalHealthStorageTargetTest.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-native" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push" value="VtsHalHealthStorageTargetTest->/data/local/tmp/VtsHalHealthStorageTargetTest" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="VtsHalHealthStorageTargetTest" />
+ <option name="native-test-timeout" value="3m" />
+ </test>
+</configuration>
diff --git a/health/storage/impl_common/Android.bp b/health/storage/impl_common/Android.bp
new file mode 100644
index 0000000..e1149c0
--- /dev/null
+++ b/health/storage/impl_common/Android.bp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+// Common implementation between HIDL and AIDL HAL.
+cc_library_static {
+ name: "libhealth_storage_impl_common",
+ vendor: true,
+ srcs: [
+ "impl_common.cpp",
+ ],
+ export_include_dirs: [
+ "include",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "libhidlbase",
+ "liblog",
+ "android.hardware.health.storage@1.0",
+ ],
+
+ static_libs: [
+ "libfstab",
+ ],
+
+ export_shared_lib_headers: [
+ "android.hardware.health.storage@1.0",
+ ],
+}
diff --git a/health/storage/impl_common/impl_common.cpp b/health/storage/impl_common/impl_common.cpp
new file mode 100644
index 0000000..1a7a7f1
--- /dev/null
+++ b/health/storage/impl_common/impl_common.cpp
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2021 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 <health-storage-impl/common.h>
+
+#include <android-base/chrono_utils.h>
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <fstab/fstab.h>
+
+using ::android::base::ReadFileToString;
+using ::android::base::Timer;
+using ::android::base::Trim;
+using ::android::base::WriteStringToFd;
+using ::android::base::WriteStringToFile;
+using ::android::fs_mgr::Fstab;
+using ::android::fs_mgr::ReadDefaultFstab;
+using ::android::hardware::health::storage::V1_0::Result;
+
+namespace android::hardware::health::storage {
+
+static std::string GetGarbageCollectPath() {
+ Fstab fstab;
+ ReadDefaultFstab(&fstab);
+
+ for (const auto& entry : fstab) {
+ if (!entry.sysfs_path.empty()) {
+ return entry.sysfs_path + "/manual_gc";
+ }
+ }
+
+ return "";
+}
+
+static std::string GetWriteBoosterPath() {
+ Fstab fstab;
+ ReadDefaultFstab(&fstab);
+
+ for (const auto& entry : fstab) {
+ if (!entry.sysfs_path.empty()) {
+ return entry.sysfs_path + "/attributes/wb_avail_buf";
+ }
+ }
+
+ return "";
+}
+
+Result GarbageCollect(uint64_t timeout_seconds) {
+ std::string gc_path = GetGarbageCollectPath();
+
+ if (gc_path.empty()) {
+ LOG(WARNING) << "Cannot find Dev GC path";
+ return Result::UNKNOWN_ERROR;
+ }
+
+ Result result = Result::SUCCESS;
+ Timer timer;
+ LOG(INFO) << "Start Dev GC on " << gc_path;
+ while (1) {
+ std::string require_gc;
+ if (!ReadFileToString(gc_path, &require_gc)) {
+ PLOG(WARNING) << "Reading manual_gc failed in " << gc_path;
+ result = Result::IO_ERROR;
+ break;
+ }
+ require_gc = Trim(require_gc);
+
+ std::string wb_path = GetWriteBoosterPath();
+ // Let's flush WB till 100% available
+ std::string wb_avail = "0x0000000A";
+ if (!wb_path.empty() && !ReadFileToString(wb_path, &wb_avail)) {
+ PLOG(WARNING) << "Reading wb_avail_buf failed in " << wb_path;
+ }
+ wb_avail = Trim(wb_avail);
+
+ if (require_gc == "disabled") {
+ LOG(DEBUG) << "Disabled Dev GC";
+ break;
+ }
+ if ((require_gc == "" || require_gc == "off") && wb_avail == "0x0000000A") {
+ LOG(DEBUG) << "No more to do Dev GC";
+ break;
+ }
+ LOG(DEBUG) << "Trigger Dev GC on " << gc_path << " having " << require_gc << ", WB on "
+ << wb_path << " having " << wb_avail;
+ if (!WriteStringToFile("1", gc_path)) {
+ PLOG(WARNING) << "Start Dev GC failed on " << gc_path;
+ result = Result::IO_ERROR;
+ break;
+ }
+ if (timer.duration() >= std::chrono::seconds(timeout_seconds)) {
+ LOG(WARNING) << "Dev GC timeout";
+ // Timeout is not treated as an error. Try next time.
+ break;
+ }
+ sleep(2);
+ }
+ LOG(INFO) << "Stop Dev GC on " << gc_path;
+ if (!WriteStringToFile("0", gc_path)) {
+ PLOG(WARNING) << "Stop Dev GC failed on " << gc_path;
+ result = Result::IO_ERROR;
+ }
+
+ return result;
+}
+
+void DebugDump(int fd) {
+ std::stringstream output;
+
+ std::string path = GetGarbageCollectPath();
+ if (path.empty()) {
+ output << "Cannot find Dev GC path";
+ } else {
+ std::string require_gc;
+
+ if (ReadFileToString(path, &require_gc)) {
+ output << path << ":" << require_gc << std::endl;
+ }
+
+ if (WriteStringToFile("0", path)) {
+ output << "stop success" << std::endl;
+ }
+ }
+ std::string wb_path = GetWriteBoosterPath();
+ if (wb_path.empty()) {
+ output << "Cannot find Dev WriteBooster path";
+ } else {
+ std::string wb_available;
+
+ if (ReadFileToString(wb_path, &wb_available)) {
+ output << wb_path << ":" << wb_available << std::endl;
+ }
+ }
+ if (!WriteStringToFd(output.str(), fd)) {
+ PLOG(WARNING) << "debug: cannot write to fd";
+ }
+
+ fsync(fd);
+}
+
+} // namespace android::hardware::health::storage
diff --git a/health/storage/impl_common/include/health-storage-impl/common.h b/health/storage/impl_common/include/health-storage-impl/common.h
new file mode 100644
index 0000000..c84a6a9
--- /dev/null
+++ b/health/storage/impl_common/include/health-storage-impl/common.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2021 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/hardware/health/storage/1.0/types.h>
+#include <string>
+
+namespace android::hardware::health::storage {
+
+// Run debug on fd
+void DebugDump(int fd);
+
+// Run garbage collection on GetGarbageCollectPath(). Blocks until garbage
+// collect finishes or |timeout_seconds| has reached.
+V1_0::Result GarbageCollect(uint64_t timeout_seconds);
+
+} // namespace android::hardware::health::storage
diff --git a/health/1.0/default/libhealthd/healthd_board_default.cpp b/health/storage/test_common/Android.bp
similarity index 64%
copy from health/1.0/default/libhealthd/healthd_board_default.cpp
copy to health/storage/test_common/Android.bp
index 127f98e..7c6bef4 100644
--- a/health/1.0/default/libhealthd/healthd_board_default.cpp
+++ b/health/storage/test_common/Android.bp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2021 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.
@@ -14,15 +14,7 @@
* limitations under the License.
*/
-#include <healthd/healthd.h>
-
-void healthd_board_init(struct healthd_config*)
-{
- // use defaults
-}
-
-int healthd_board_battery_update(struct android::BatteryProperties*)
-{
- // return 0 to log periodic polled battery status to kernel log
- return 0;
+cc_library_headers {
+ name: "libhealth_storage_test_common_headers",
+ export_include_dirs: ["include"],
}
diff --git a/health/storage/test_common/include/health-storage-test/common.h b/health/storage/test_common/include/health-storage-test/common.h
new file mode 100644
index 0000000..dfda830
--- /dev/null
+++ b/health/storage/test_common/include/health-storage-test/common.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2021 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 <chrono>
+#include <string>
+
+namespace android::hardware::health::storage::test {
+
+// Dev GC timeout. This is the timeout used by vold.
+const uint64_t kDevGcTimeoutSec = 120;
+const std::chrono::seconds kDevGcTimeout{kDevGcTimeoutSec};
+// Dev GC timeout tolerance. The HAL may not immediately return after the
+// timeout, so include an acceptable tolerance.
+const std::chrono::seconds kDevGcTolerance{3};
+// Time accounted for RPC calls.
+const std::chrono::milliseconds kRpcTime{1000};
+
+template <typename R>
+std::string to_string(std::chrono::duration<R, std::milli> time) {
+ return std::to_string(time.count()) + "ms";
+}
+
+/** An atomic boolean flag that indicates whether a task has finished. */
+class Flag {
+ public:
+ void OnFinish() {
+ std::unique_lock<std::mutex> lock(mutex_);
+ OnFinishLocked(&lock);
+ }
+ template <typename R, typename P>
+ bool Wait(std::chrono::duration<R, P> duration) {
+ std::unique_lock<std::mutex> lock(mutex_);
+ return WaitLocked(&lock, duration);
+ }
+
+ protected:
+ /** Will unlock. */
+ void OnFinishLocked(std::unique_lock<std::mutex>* lock) {
+ finished_ = true;
+ lock->unlock();
+ cv_.notify_all();
+ }
+ template <typename R, typename P>
+ bool WaitLocked(std::unique_lock<std::mutex>* lock, std::chrono::duration<R, P> duration) {
+ cv_.wait_for(*lock, duration, [this] { return finished_; });
+ return finished_;
+ }
+
+ bool finished_{false};
+ std::mutex mutex_;
+ std::condition_variable cv_;
+};
+
+} // namespace android::hardware::health::storage::test
diff --git a/health/utils/libhealth2impl/Health.cpp b/health/utils/libhealth2impl/Health.cpp
index f4684ae..035b36f 100644
--- a/health/utils/libhealth2impl/Health.cpp
+++ b/health/utils/libhealth2impl/Health.cpp
@@ -80,14 +80,14 @@
Return<Result> Health::update() {
Result result = Result::UNKNOWN;
- getHealthInfo_2_1([&](auto res, const auto& /* health_info */) {
+ getHealthInfo_2_1([&](auto res, const auto& health_info) {
result = res;
if (res != Result::SUCCESS) {
LOG(ERROR) << "Cannot call getHealthInfo_2_1: " << toString(res);
return;
}
- battery_monitor_.logValues();
+ BatteryMonitor::logValues(health_info, *healthd_config_);
});
return result;
}
diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/Certificate.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/Certificate.aidl
index 7e3002d..d8a8128 100644
--- a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/Certificate.aidl
+++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/Certificate.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/CipherSuite.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/CipherSuite.aidl
index 447203f..2685525 100644
--- a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/CipherSuite.aidl
+++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/CipherSuite.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/HardwareInformation.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/HardwareInformation.aidl
index e1296e0..f8d5a9e 100644
--- a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/HardwareInformation.aidl
+++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/HardwareInformation.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredential.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredential.aidl
index 88104d9..a097895 100644
--- a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredential.aidl
+++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredential.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
@@ -18,6 +33,9 @@
package android.hardware.identity;
@VintfStability
interface IIdentityCredential {
+ /**
+ * @deprecated use deleteCredentalWithChallenge() instead.
+ */
byte[] deleteCredential();
byte[] createEphemeralKeyPair();
void setReaderEphemeralPublicKey(in byte[] publicKey);
@@ -29,4 +47,7 @@
android.hardware.identity.Certificate generateSigningKeyPair(out byte[] signingKeyBlob);
void setRequestedNamespaces(in android.hardware.identity.RequestNamespace[] requestNamespaces);
void setVerificationToken(in android.hardware.keymaster.VerificationToken verificationToken);
+ byte[] deleteCredentialWithChallenge(in byte[] challenge);
+ byte[] proveOwnership(in byte[] challenge);
+ android.hardware.identity.IWritableIdentityCredential updateCredential();
}
diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredentialStore.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredentialStore.aidl
index 5dafb76..c6fb3c8 100644
--- a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredentialStore.aidl
+++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredentialStore.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IWritableIdentityCredential.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IWritableIdentityCredential.aidl
index c5ac9d6..a713462 100644
--- a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IWritableIdentityCredential.aidl
+++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IWritableIdentityCredential.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestDataItem.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestDataItem.aidl
index 24ec26a..c9c2b9f 100644
--- a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestDataItem.aidl
+++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestDataItem.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestNamespace.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestNamespace.aidl
index af00f3b..aaf1e20 100644
--- a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestNamespace.aidl
+++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestNamespace.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/SecureAccessControlProfile.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/SecureAccessControlProfile.aidl
index dfc1ad0..695fb3f 100644
--- a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/SecureAccessControlProfile.aidl
+++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/SecureAccessControlProfile.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
diff --git a/identity/aidl/android/hardware/identity/IIdentityCredential.aidl b/identity/aidl/android/hardware/identity/IIdentityCredential.aidl
index 702334d..d23f88c 100644
--- a/identity/aidl/android/hardware/identity/IIdentityCredential.aidl
+++ b/identity/aidl/android/hardware/identity/IIdentityCredential.aidl
@@ -19,6 +19,7 @@
import android.hardware.identity.Certificate;
import android.hardware.identity.RequestNamespace;
import android.hardware.identity.SecureAccessControlProfile;
+import android.hardware.identity.IWritableIdentityCredential;
import android.hardware.keymaster.HardwareAuthToken;
import android.hardware.keymaster.VerificationToken;
@@ -40,7 +41,11 @@
* After this method has been called, the persistent storage used for credentialData should
* be deleted.
*
- * @return a COSE_Sign1 signature described above.
+ * This method was deprecated in API version 3 because there's no challenge so freshness
+ * can't be checked. Use deleteCredentalWithChallenge() instead.
+ *
+ * @return a COSE_Sign1 signature described above
+ * @deprecated use deleteCredentalWithChallenge() instead.
*/
byte[] deleteCredential();
@@ -353,6 +358,18 @@
*
* - subjectPublicKeyInfo: must contain attested public key.
*
+ * As of API version 3, the certificate shall also have an X.509 extension at
+ * OID 1.3.6.1.4.1.11129.2.1.26 which shall contain an OCTET STRING with the
+ * bytes of the CBOR with the following CDDL:
+ *
+ * ProofOfBinding = [
+ * "ProofOfBinding",
+ * bstr, // Contains SHA-256(ProofOfProvisioning)
+ * ]
+ *
+ * This CBOR enables an issuer to determine the exact state of the credential it
+ * returns issuer-signed data for.
+ *
* @param out signingKeyBlob contains an AES-GCM-ENC(storageKey, R, signingKey, docType)
* where signingKey is an EC private key in uncompressed form. That is, the returned
* blob is an encrypted copy of the newly-generated private signing key.
@@ -381,4 +398,63 @@
* The verification token. This token is only valid if the timestamp field is non-zero.
*/
void setVerificationToken(in VerificationToken verificationToken);
+
+ /**
+ * Delete a credential.
+ *
+ * This method returns a COSE_Sign1 data structure signed by CredentialKey
+ * with payload set to the ProofOfDeletion CBOR below:
+ *
+ * ProofOfDeletion = [
+ * "ProofOfDeletion", ; tstr
+ * tstr, ; DocType
+ * bstr, ; Challenge
+ * bool ; true if this is a test credential, should
+ * ; always be false.
+ * ]
+ *
+ * After this method has been called, the persistent storage used for credentialData should
+ * be deleted.
+ *
+ * This method was introduced in API version 3.
+ *
+ * @param challenge a challenge set by the issuer to ensure freshness. Maximum size is 32 bytes
+ * and it may be empty. Fails with STATUS_INVALID_DATA if bigger than 32 bytes.
+ * @return a COSE_Sign1 signature described above.
+ */
+ byte[] deleteCredentialWithChallenge(in byte[] challenge);
+
+ /**
+ * Prove ownership of credential.
+ *
+ * This method returns a COSE_Sign1 data structure signed by CredentialKey with payload
+ * set to the ProofOfOwnership CBOR below.
+ *
+ * ProofOfOwnership = [
+ * "ProofOfOwnership", ; tstr
+ * tstr, ; DocType
+ * bstr, ; Challenge
+ * bool ; true if this is a test credential, should
+ * ; always be false.
+ * ]
+ *
+ * This method was introduced in API version 3.
+ *
+ * @param challenge a challenge set by the issuer to ensure freshness. Maximum size is 32 bytes
+ * and it may be empty. Fails with STATUS_INVALID_DATA if bigger than 32 bytes.
+ * @return a COSE_Sign1 signature described above.
+ */
+ byte[] proveOwnership(in byte[] challenge);
+
+ /**
+ * Called to start updating the credential with new data items.
+ *
+ * If the getAttestationCertificate() method is called on the returned object
+ * it fails with the error STATUS_FAILED.
+ *
+ * This method was introduced in API version 3.
+ *
+ * @return an IWritableIdentityCredential
+ */
+ IWritableIdentityCredential updateCredential();
}
diff --git a/identity/aidl/android/hardware/identity/IIdentityCredentialStore.aidl b/identity/aidl/android/hardware/identity/IIdentityCredentialStore.aidl
index 33e25b1..638be79 100644
--- a/identity/aidl/android/hardware/identity/IIdentityCredentialStore.aidl
+++ b/identity/aidl/android/hardware/identity/IIdentityCredentialStore.aidl
@@ -104,6 +104,11 @@
* All binder calls in the HAL may return a ServiceSpecificException with statuses from the
* STATUS_* integers defined in this interface. Each method states which status can be returned
* and under which circumstances.
+ *
+ * The API described here is API version 3 which corresponds to feature version 202101
+ * of the android.security.identity Framework API. An XML file declaring the feature
+ * android.hardware.identity_credential (or android.hardware.identity_credential.direct_access
+ * if implementing the Direct Access HAL) should be included declaring this feature version.
*/
@VintfStability
interface IIdentityCredentialStore {
@@ -230,6 +235,9 @@
* return argument of the same name in finishAddingEntries(), in
* IWritableIdentityCredential.
*
+ * Note that the format of credentialData may depend on the feature version.
+ * Implementations must support credentialData created by an earlier feature version.
+ *
* @return an IIdentityCredential interface that provides operations on the Credential.
*/
IIdentityCredential getCredential(in CipherSuite cipherSuite, in byte[] credentialData);
diff --git a/identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl b/identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl
index c48cb66..5f878ee 100644
--- a/identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl
+++ b/identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl
@@ -263,7 +263,9 @@
*
* where HBK is a unique hardware-bound key that has never existed outside of the secure
* environment (except it's all zeroes if testCredential is True) and CredentialKeys is
- * the CBOR-encoded structure (in CDDL notation):
+ * the CBOR-encoded structure (in CDDL notation) given below.
+ *
+ * In API versions 1 and 2 it was the following
*
* CredentialKeys = [
* bstr, ; storageKey, a 128-bit AES key
@@ -271,6 +273,17 @@
* ; in uncompressed form
* ]
*
+ * In API version 3 or later it must be the following
+ *
+ * CredentialKeys = [
+ * bstr, ; storageKey, a 128-bit AES key
+ * bstr ; credentialPrivKey, the private key for credentialKey
+ * ; in uncompressed form
+ * bstr ; SHA-256(ProofOfProvisioning)
+ * ]
+ *
+ * Additional elements may be added to the CredentialKeys array in future versions.
+ *
* @param out proofOfProvisioningSignature proves to the IA that the credential was imported
* into the secure hardware without alteration or error. When the final addEntry() call is
* made (when the number of provisioned entries equals the sum of the items in
@@ -321,4 +334,5 @@
* @param expectedProofOfProvisioningSize the expected size of ProofOfProvisioning.
*/
void setExpectedProofOfProvisioningSize(in int expectedProofOfProvisioningSize);
+
}
diff --git a/identity/aidl/default/Android.bp b/identity/aidl/default/Android.bp
index 2eb0faa..9659f57 100644
--- a/identity/aidl/default/Android.bp
+++ b/identity/aidl/default/Android.bp
@@ -1,3 +1,68 @@
+cc_library_static {
+ name: "android.hardware.identity-libeic-hal-common",
+ vendor_available: true,
+ srcs: [
+ "common/IdentityCredential.cpp",
+ "common/IdentityCredentialStore.cpp",
+ "common/WritableIdentityCredential.cpp",
+ ],
+ export_include_dirs: [
+ "common",
+ ],
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ "-Wno-deprecated-declarations",
+ ],
+ shared_libs: [
+ "liblog",
+ "libcrypto",
+ "libbinder_ndk",
+ "libkeymaster_messages",
+ ],
+ static_libs: [
+ "libbase",
+ "libcppbor",
+ "libutils",
+ "libsoft_attestation_cert",
+ "libkeymaster_portable",
+ "libsoft_attestation_cert",
+ "libpuresoftkeymasterdevice",
+ "android.hardware.identity-support-lib",
+ "android.hardware.identity-V3-ndk_platform",
+ "android.hardware.keymaster-V3-ndk_platform",
+ ],
+}
+
+cc_library_static {
+ name: "android.hardware.identity-libeic-library",
+ vendor_available: true,
+ srcs: [
+ "libeic/EicCbor.c",
+ "libeic/EicPresentation.c",
+ "libeic/EicProvisioning.c",
+ "EicOpsImpl.cc",
+ ],
+ export_include_dirs: [
+ "libeic",
+ ],
+ cflags: [
+ "-DEIC_COMPILATION",
+ "-Wall",
+ "-Wextra",
+ "-DEIC_DEBUG",
+ // Allow using C2x extensions such as omitting parameter names
+ "-Wno-c2x-extensions",
+ ],
+ shared_libs: [
+ "libbase",
+ "libcrypto",
+ ],
+ static_libs: [
+ "android.hardware.identity-support-lib",
+ ],
+}
+
cc_binary {
name: "android.hardware.identity-service.example",
relative_install_path: "hw",
@@ -7,23 +72,40 @@
cflags: [
"-Wall",
"-Wextra",
+ "-g",
],
shared_libs: [
- "libbase",
- "libbinder_ndk",
- "libcppbor",
- "libcrypto",
"liblog",
+ "libcrypto",
+ "libbinder_ndk",
+ "libkeymaster_messages",
+ ],
+ static_libs: [
+ "libbase",
+ "libcppbor",
"libutils",
+ "libsoft_attestation_cert",
+ "libkeymaster_portable",
+ "libsoft_attestation_cert",
+ "libpuresoftkeymasterdevice",
"android.hardware.identity-support-lib",
- "android.hardware.identity-ndk_platform",
- "android.hardware.keymaster-ndk_platform",
+ "android.hardware.identity-V3-ndk_platform",
+ "android.hardware.keymaster-V3-ndk_platform",
+ "android.hardware.identity-libeic-hal-common",
+ "android.hardware.identity-libeic-library",
],
srcs: [
- "IdentityCredential.cpp",
- "IdentityCredentialStore.cpp",
- "WritableIdentityCredential.cpp",
- "Util.cpp",
"service.cpp",
+ "FakeSecureHardwareProxy.cpp",
],
+ required: [
+ "android.hardware.identity_credential.xml",
+ ],
+}
+
+prebuilt_etc {
+ name: "android.hardware.identity_credential.xml",
+ sub_dir: "permissions",
+ vendor: true,
+ src: "android.hardware.identity_credential.xml",
}
diff --git a/identity/aidl/default/EicOpsImpl.cc b/identity/aidl/default/EicOpsImpl.cc
new file mode 100644
index 0000000..8ec4cc9
--- /dev/null
+++ b/identity/aidl/default/EicOpsImpl.cc
@@ -0,0 +1,515 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "EicOpsImpl"
+
+#include <optional>
+#include <tuple>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <string.h>
+
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <openssl/sha.h>
+
+#include <openssl/aes.h>
+#include <openssl/bn.h>
+#include <openssl/crypto.h>
+#include <openssl/ec.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/hkdf.h>
+#include <openssl/hmac.h>
+#include <openssl/objects.h>
+#include <openssl/pem.h>
+#include <openssl/pkcs12.h>
+#include <openssl/rand.h>
+#include <openssl/x509.h>
+#include <openssl/x509_vfy.h>
+
+#include "EicOps.h"
+
+using ::std::map;
+using ::std::optional;
+using ::std::string;
+using ::std::tuple;
+using ::std::vector;
+
+void* eicMemSet(void* s, int c, size_t n) {
+ return memset(s, c, n);
+}
+
+void* eicMemCpy(void* dest, const void* src, size_t n) {
+ return memcpy(dest, src, n);
+}
+
+size_t eicStrLen(const char* s) {
+ return strlen(s);
+}
+
+int eicCryptoMemCmp(const void* s1, const void* s2, size_t n) {
+ return CRYPTO_memcmp(s1, s2, n);
+}
+
+void eicOpsHmacSha256Init(EicHmacSha256Ctx* ctx, const uint8_t* key, size_t keySize) {
+ HMAC_CTX* realCtx = (HMAC_CTX*)ctx;
+ HMAC_CTX_init(realCtx);
+ if (HMAC_Init_ex(realCtx, key, keySize, EVP_sha256(), nullptr /* impl */) != 1) {
+ LOG(ERROR) << "Error initializing HMAC_CTX";
+ }
+}
+
+void eicOpsHmacSha256Update(EicHmacSha256Ctx* ctx, const uint8_t* data, size_t len) {
+ HMAC_CTX* realCtx = (HMAC_CTX*)ctx;
+ if (HMAC_Update(realCtx, data, len) != 1) {
+ LOG(ERROR) << "Error updating HMAC_CTX";
+ }
+}
+
+void eicOpsHmacSha256Final(EicHmacSha256Ctx* ctx, uint8_t digest[EIC_SHA256_DIGEST_SIZE]) {
+ HMAC_CTX* realCtx = (HMAC_CTX*)ctx;
+ unsigned int size = 0;
+ if (HMAC_Final(realCtx, digest, &size) != 1) {
+ LOG(ERROR) << "Error finalizing HMAC_CTX";
+ }
+ if (size != EIC_SHA256_DIGEST_SIZE) {
+ LOG(ERROR) << "Expected 32 bytes from HMAC_Final, got " << size;
+ }
+}
+
+void eicOpsSha256Init(EicSha256Ctx* ctx) {
+ SHA256_CTX* realCtx = (SHA256_CTX*)ctx;
+ SHA256_Init(realCtx);
+}
+
+void eicOpsSha256Update(EicSha256Ctx* ctx, const uint8_t* data, size_t len) {
+ SHA256_CTX* realCtx = (SHA256_CTX*)ctx;
+ SHA256_Update(realCtx, data, len);
+}
+
+void eicOpsSha256Final(EicSha256Ctx* ctx, uint8_t digest[EIC_SHA256_DIGEST_SIZE]) {
+ SHA256_CTX* realCtx = (SHA256_CTX*)ctx;
+ SHA256_Final(digest, realCtx);
+}
+
+bool eicOpsRandom(uint8_t* buf, size_t numBytes) {
+ optional<vector<uint8_t>> bytes = ::android::hardware::identity::support::getRandom(numBytes);
+ if (!bytes.has_value()) {
+ return false;
+ }
+ memcpy(buf, bytes.value().data(), numBytes);
+ return true;
+}
+
+bool eicOpsEncryptAes128Gcm(
+ const uint8_t* key, // Must be 16 bytes
+ const uint8_t* nonce, // Must be 12 bytes
+ const uint8_t* data, // May be NULL if size is 0
+ size_t dataSize,
+ const uint8_t* additionalAuthenticationData, // May be NULL if size is 0
+ size_t additionalAuthenticationDataSize, uint8_t* encryptedData) {
+ vector<uint8_t> cppKey;
+ cppKey.resize(16);
+ memcpy(cppKey.data(), key, 16);
+
+ vector<uint8_t> cppData;
+ cppData.resize(dataSize);
+ if (dataSize > 0) {
+ memcpy(cppData.data(), data, dataSize);
+ }
+
+ vector<uint8_t> cppAAD;
+ cppAAD.resize(additionalAuthenticationDataSize);
+ if (additionalAuthenticationDataSize > 0) {
+ memcpy(cppAAD.data(), additionalAuthenticationData, additionalAuthenticationDataSize);
+ }
+
+ vector<uint8_t> cppNonce;
+ cppNonce.resize(12);
+ memcpy(cppNonce.data(), nonce, 12);
+
+ optional<vector<uint8_t>> cppEncryptedData =
+ android::hardware::identity::support::encryptAes128Gcm(cppKey, cppNonce, cppData,
+ cppAAD);
+ if (!cppEncryptedData.has_value()) {
+ return false;
+ }
+
+ memcpy(encryptedData, cppEncryptedData.value().data(), cppEncryptedData.value().size());
+ return true;
+}
+
+// Decrypts |encryptedData| using |key| and |additionalAuthenticatedData|,
+// returns resulting plaintext in |data| must be of size |encryptedDataSize| - 28.
+//
+// The format of |encryptedData| must be as specified in the
+// encryptAes128Gcm() function.
+bool eicOpsDecryptAes128Gcm(const uint8_t* key, // Must be 16 bytes
+ const uint8_t* encryptedData, size_t encryptedDataSize,
+ const uint8_t* additionalAuthenticationData,
+ size_t additionalAuthenticationDataSize, uint8_t* data) {
+ vector<uint8_t> keyVec;
+ keyVec.resize(16);
+ memcpy(keyVec.data(), key, 16);
+
+ vector<uint8_t> encryptedDataVec;
+ encryptedDataVec.resize(encryptedDataSize);
+ if (encryptedDataSize > 0) {
+ memcpy(encryptedDataVec.data(), encryptedData, encryptedDataSize);
+ }
+
+ vector<uint8_t> aadVec;
+ aadVec.resize(additionalAuthenticationDataSize);
+ if (additionalAuthenticationDataSize > 0) {
+ memcpy(aadVec.data(), additionalAuthenticationData, additionalAuthenticationDataSize);
+ }
+
+ optional<vector<uint8_t>> decryptedDataVec =
+ android::hardware::identity::support::decryptAes128Gcm(keyVec, encryptedDataVec,
+ aadVec);
+ if (!decryptedDataVec.has_value()) {
+ eicDebug("Error decrypting data");
+ return false;
+ }
+ if (decryptedDataVec.value().size() != encryptedDataSize - 28) {
+ eicDebug("Decrypted data is size %zd, expected %zd", decryptedDataVec.value().size(),
+ encryptedDataSize - 28);
+ return false;
+ }
+
+ if (decryptedDataVec.value().size() > 0) {
+ memcpy(data, decryptedDataVec.value().data(), decryptedDataVec.value().size());
+ }
+ return true;
+}
+
+bool eicOpsCreateEcKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE],
+ uint8_t publicKey[EIC_P256_PUB_KEY_SIZE]) {
+ optional<vector<uint8_t>> keyPair = android::hardware::identity::support::createEcKeyPair();
+ if (!keyPair) {
+ eicDebug("Error creating EC keypair");
+ return false;
+ }
+ optional<vector<uint8_t>> privKey =
+ android::hardware::identity::support::ecKeyPairGetPrivateKey(keyPair.value());
+ if (!privKey) {
+ eicDebug("Error extracting private key");
+ return false;
+ }
+ if (privKey.value().size() != EIC_P256_PRIV_KEY_SIZE) {
+ eicDebug("Private key is %zd bytes, expected %zd", privKey.value().size(),
+ (size_t)EIC_P256_PRIV_KEY_SIZE);
+ return false;
+ }
+
+ optional<vector<uint8_t>> pubKey =
+ android::hardware::identity::support::ecKeyPairGetPublicKey(keyPair.value());
+ if (!pubKey) {
+ eicDebug("Error extracting public key");
+ return false;
+ }
+ // ecKeyPairGetPublicKey() returns 0x04 | x | y, we don't want the leading 0x04.
+ if (pubKey.value().size() != EIC_P256_PUB_KEY_SIZE + 1) {
+ eicDebug("Public key is %zd bytes long, expected %zd", pubKey.value().size(),
+ (size_t)EIC_P256_PRIV_KEY_SIZE + 1);
+ return false;
+ }
+
+ memcpy(privateKey, privKey.value().data(), EIC_P256_PRIV_KEY_SIZE);
+ memcpy(publicKey, pubKey.value().data() + 1, EIC_P256_PUB_KEY_SIZE);
+
+ return true;
+}
+
+bool eicOpsCreateCredentialKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE], const uint8_t* challenge,
+ size_t challengeSize, const uint8_t* applicationId,
+ size_t applicationIdSize, bool testCredential, uint8_t* cert,
+ size_t* certSize) {
+ vector<uint8_t> challengeVec(challengeSize);
+ memcpy(challengeVec.data(), challenge, challengeSize);
+
+ vector<uint8_t> applicationIdVec(applicationIdSize);
+ memcpy(applicationIdVec.data(), applicationId, applicationIdSize);
+
+ optional<std::pair<vector<uint8_t>, vector<vector<uint8_t>>>> ret =
+ android::hardware::identity::support::createEcKeyPairAndAttestation(
+ challengeVec, applicationIdVec, testCredential);
+ if (!ret) {
+ eicDebug("Error generating CredentialKey and attestation");
+ return false;
+ }
+
+ // Extract certificate chain.
+ vector<uint8_t> flatChain =
+ android::hardware::identity::support::certificateChainJoin(ret.value().second);
+ if (*certSize < flatChain.size()) {
+ eicDebug("Buffer for certificate is only %zd bytes long, need %zd bytes", *certSize,
+ flatChain.size());
+ return false;
+ }
+ memcpy(cert, flatChain.data(), flatChain.size());
+ *certSize = flatChain.size();
+
+ // Extract private key.
+ optional<vector<uint8_t>> privKey =
+ android::hardware::identity::support::ecKeyPairGetPrivateKey(ret.value().first);
+ if (!privKey) {
+ eicDebug("Error extracting private key");
+ return false;
+ }
+ if (privKey.value().size() != EIC_P256_PRIV_KEY_SIZE) {
+ eicDebug("Private key is %zd bytes, expected %zd", privKey.value().size(),
+ (size_t)EIC_P256_PRIV_KEY_SIZE);
+ return false;
+ }
+
+ memcpy(privateKey, privKey.value().data(), EIC_P256_PRIV_KEY_SIZE);
+
+ return true;
+}
+
+bool eicOpsSignEcKey(const uint8_t publicKey[EIC_P256_PUB_KEY_SIZE],
+ const uint8_t signingKey[EIC_P256_PRIV_KEY_SIZE], unsigned int serial,
+ const char* issuerName, const char* subjectName, time_t validityNotBefore,
+ time_t validityNotAfter, const uint8_t* proofOfBinding,
+ size_t proofOfBindingSize, uint8_t* cert, size_t* certSize) { // inout
+ vector<uint8_t> signingKeyVec(EIC_P256_PRIV_KEY_SIZE);
+ memcpy(signingKeyVec.data(), signingKey, EIC_P256_PRIV_KEY_SIZE);
+
+ vector<uint8_t> pubKeyVec(EIC_P256_PUB_KEY_SIZE + 1);
+ pubKeyVec[0] = 0x04;
+ memcpy(pubKeyVec.data() + 1, publicKey, EIC_P256_PUB_KEY_SIZE);
+
+ string serialDecimal = android::base::StringPrintf("%d", serial);
+
+ map<string, vector<uint8_t>> extensions;
+ if (proofOfBinding != nullptr) {
+ vector<uint8_t> proofOfBindingVec(proofOfBinding, proofOfBinding + proofOfBindingSize);
+ extensions["1.3.6.1.4.1.11129.2.1.26"] = proofOfBindingVec;
+ }
+
+ optional<vector<uint8_t>> certVec =
+ android::hardware::identity::support::ecPublicKeyGenerateCertificate(
+ pubKeyVec, signingKeyVec, serialDecimal, issuerName, subjectName,
+ validityNotBefore, validityNotAfter, extensions);
+ if (!certVec) {
+ eicDebug("Error generating certificate");
+ return false;
+ }
+
+ if (*certSize < certVec.value().size()) {
+ eicDebug("Buffer for certificate is only %zd bytes long, need %zd bytes", *certSize,
+ certVec.value().size());
+ return false;
+ }
+ memcpy(cert, certVec.value().data(), certVec.value().size());
+ *certSize = certVec.value().size();
+
+ return true;
+}
+
+bool eicOpsEcDsa(const uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE],
+ const uint8_t digestOfData[EIC_SHA256_DIGEST_SIZE],
+ uint8_t signature[EIC_ECDSA_P256_SIGNATURE_SIZE]) {
+ vector<uint8_t> privKeyVec(EIC_P256_PRIV_KEY_SIZE);
+ memcpy(privKeyVec.data(), privateKey, EIC_P256_PRIV_KEY_SIZE);
+
+ vector<uint8_t> digestVec(EIC_SHA256_DIGEST_SIZE);
+ memcpy(digestVec.data(), digestOfData, EIC_SHA256_DIGEST_SIZE);
+
+ optional<vector<uint8_t>> derSignature =
+ android::hardware::identity::support::signEcDsaDigest(privKeyVec, digestVec);
+ if (!derSignature) {
+ eicDebug("Error signing data");
+ return false;
+ }
+
+ ECDSA_SIG* sig;
+ const unsigned char* p = derSignature.value().data();
+ sig = d2i_ECDSA_SIG(nullptr, &p, derSignature.value().size());
+ if (sig == nullptr) {
+ eicDebug("Error decoding DER signature");
+ return false;
+ }
+
+ if (BN_bn2binpad(sig->r, signature, 32) != 32) {
+ eicDebug("Error encoding r");
+ return false;
+ }
+ if (BN_bn2binpad(sig->s, signature + 32, 32) != 32) {
+ eicDebug("Error encoding s");
+ return false;
+ }
+
+ return true;
+}
+
+static const uint8_t hbkTest[16] = {0};
+static const uint8_t hbkReal[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
+
+const uint8_t* eicOpsGetHardwareBoundKey(bool testCredential) {
+ if (testCredential) {
+ return hbkTest;
+ }
+ return hbkReal;
+}
+
+bool eicOpsValidateAuthToken(uint64_t /* challenge */, uint64_t /* secureUserId */,
+ uint64_t /* authenticatorId */, int /* hardwareAuthenticatorType */,
+ uint64_t /* timeStamp */, const uint8_t* /* mac */,
+ size_t /* macSize */, uint64_t /* verificationTokenChallenge */,
+ uint64_t /* verificationTokenTimeStamp */,
+ int /* verificationTokenSecurityLevel */,
+ const uint8_t* /* verificationTokenMac */,
+ size_t /* verificationTokenMacSize */) {
+ // Here's where we would validate the passed-in |authToken| to assure ourselves
+ // that it comes from the e.g. biometric hardware and wasn't made up by an attacker.
+ //
+ // However this involves calculating the MAC which requires access to the to
+ // a pre-shared key which we don't have...
+ //
+ return true;
+}
+
+bool eicOpsX509GetPublicKey(const uint8_t* x509Cert, size_t x509CertSize, uint8_t* publicKey,
+ size_t* publicKeySize) {
+ vector<uint8_t> chain;
+ chain.resize(x509CertSize);
+ memcpy(chain.data(), x509Cert, x509CertSize);
+ optional<vector<uint8_t>> res =
+ android::hardware::identity::support::certificateChainGetTopMostKey(chain);
+ if (!res) {
+ return false;
+ }
+ if (res.value().size() > *publicKeySize) {
+ eicDebug("Public key size is %zd but buffer only has room for %zd bytes",
+ res.value().size(), *publicKeySize);
+ return false;
+ }
+ *publicKeySize = res.value().size();
+ memcpy(publicKey, res.value().data(), *publicKeySize);
+ eicDebug("Extracted %zd bytes public key from %zd bytes X.509 cert", *publicKeySize,
+ x509CertSize);
+ return true;
+}
+
+bool eicOpsX509CertSignedByPublicKey(const uint8_t* x509Cert, size_t x509CertSize,
+ const uint8_t* publicKey, size_t publicKeySize) {
+ vector<uint8_t> certVec(x509Cert, x509Cert + x509CertSize);
+ vector<uint8_t> publicKeyVec(publicKey, publicKey + publicKeySize);
+ return android::hardware::identity::support::certificateSignedByPublicKey(certVec,
+ publicKeyVec);
+}
+
+bool eicOpsEcDsaVerifyWithPublicKey(const uint8_t* digest, size_t digestSize,
+ const uint8_t* signature, size_t signatureSize,
+ const uint8_t* publicKey, size_t publicKeySize) {
+ vector<uint8_t> digestVec(digest, digest + digestSize);
+ vector<uint8_t> signatureVec(signature, signature + signatureSize);
+ vector<uint8_t> publicKeyVec(publicKey, publicKey + publicKeySize);
+
+ vector<uint8_t> derSignature;
+ if (!android::hardware::identity::support::ecdsaSignatureCoseToDer(signatureVec,
+ derSignature)) {
+ LOG(ERROR) << "Error convering signature to DER format";
+ return false;
+ }
+
+ if (!android::hardware::identity::support::checkEcDsaSignature(digestVec, derSignature,
+ publicKeyVec)) {
+ LOG(ERROR) << "Signature check failed";
+ return false;
+ }
+ return true;
+}
+
+bool eicOpsEcdh(const uint8_t publicKey[EIC_P256_PUB_KEY_SIZE],
+ const uint8_t privateKey[EIC_P256_PUB_KEY_SIZE],
+ uint8_t sharedSecret[EIC_P256_COORDINATE_SIZE]) {
+ vector<uint8_t> pubKeyVec(EIC_P256_PUB_KEY_SIZE + 1);
+ pubKeyVec[0] = 0x04;
+ memcpy(pubKeyVec.data() + 1, publicKey, EIC_P256_PUB_KEY_SIZE);
+
+ vector<uint8_t> privKeyVec(EIC_P256_PRIV_KEY_SIZE);
+ memcpy(privKeyVec.data(), privateKey, EIC_P256_PRIV_KEY_SIZE);
+
+ optional<vector<uint8_t>> shared =
+ android::hardware::identity::support::ecdh(pubKeyVec, privKeyVec);
+ if (!shared) {
+ LOG(ERROR) << "Error performing ECDH";
+ return false;
+ }
+ if (shared.value().size() != EIC_P256_COORDINATE_SIZE) {
+ LOG(ERROR) << "Unexpected size of shared secret " << shared.value().size() << " expected "
+ << EIC_P256_COORDINATE_SIZE << " bytes";
+ return false;
+ }
+ memcpy(sharedSecret, shared.value().data(), EIC_P256_COORDINATE_SIZE);
+ return true;
+}
+
+bool eicOpsHkdf(const uint8_t* sharedSecret, size_t sharedSecretSize, const uint8_t* salt,
+ size_t saltSize, const uint8_t* info, size_t infoSize, uint8_t* output,
+ size_t outputSize) {
+ vector<uint8_t> sharedSecretVec(sharedSecretSize);
+ memcpy(sharedSecretVec.data(), sharedSecret, sharedSecretSize);
+ vector<uint8_t> saltVec(saltSize);
+ memcpy(saltVec.data(), salt, saltSize);
+ vector<uint8_t> infoVec(infoSize);
+ memcpy(infoVec.data(), info, infoSize);
+
+ optional<vector<uint8_t>> result = android::hardware::identity::support::hkdf(
+ sharedSecretVec, saltVec, infoVec, outputSize);
+ if (!result) {
+ LOG(ERROR) << "Error performing HKDF";
+ return false;
+ }
+ if (result.value().size() != outputSize) {
+ LOG(ERROR) << "Unexpected size of HKDF " << result.value().size() << " expected "
+ << outputSize;
+ return false;
+ }
+ memcpy(output, result.value().data(), outputSize);
+ return true;
+}
+
+#ifdef EIC_DEBUG
+
+void eicPrint(const char* format, ...) {
+ va_list args;
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+}
+
+void eicHexdump(const char* message, const uint8_t* data, size_t dataSize) {
+ vector<uint8_t> dataVec(dataSize);
+ memcpy(dataVec.data(), data, dataSize);
+ android::hardware::identity::support::hexdump(message, dataVec);
+}
+
+void eicCborPrettyPrint(const uint8_t* cborData, size_t cborDataSize, size_t maxBStrSize) {
+ vector<uint8_t> cborDataVec(cborDataSize);
+ memcpy(cborDataVec.data(), cborData, cborDataSize);
+ string str =
+ android::hardware::identity::support::cborPrettyPrint(cborDataVec, maxBStrSize, {});
+ fprintf(stderr, "%s\n", str.c_str());
+}
+
+#endif // EIC_DEBUG
diff --git a/identity/aidl/default/EicOpsImpl.h b/identity/aidl/default/EicOpsImpl.h
new file mode 100644
index 0000000..333cdce
--- /dev/null
+++ b/identity/aidl/default/EicOpsImpl.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_EIC_OPS_IMPL_H
+#define ANDROID_HARDWARE_IDENTITY_EIC_OPS_IMPL_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+// Add whatever includes are needed for definitions below.
+//
+
+#include <openssl/hmac.h>
+#include <openssl/sha.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Set the following defines to match the implementation of the supplied
+// eicOps*() operations. See EicOps.h for details.
+//
+
+#define EIC_SHA256_CONTEXT_SIZE sizeof(SHA256_CTX)
+
+#define EIC_HMAC_SHA256_CONTEXT_SIZE sizeof(HMAC_CTX)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANDROID_HARDWARE_IDENTITY_EMBEDDED_IC_H
diff --git a/identity/aidl/default/FakeSecureHardwareProxy.cpp b/identity/aidl/default/FakeSecureHardwareProxy.cpp
new file mode 100644
index 0000000..287ffb8
--- /dev/null
+++ b/identity/aidl/default/FakeSecureHardwareProxy.cpp
@@ -0,0 +1,347 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "FakeSecureHardwareProxy"
+
+#include "FakeSecureHardwareProxy.h"
+
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <string.h>
+
+#include <openssl/sha.h>
+
+#include <openssl/aes.h>
+#include <openssl/bn.h>
+#include <openssl/crypto.h>
+#include <openssl/ec.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/hkdf.h>
+#include <openssl/hmac.h>
+#include <openssl/objects.h>
+#include <openssl/pem.h>
+#include <openssl/pkcs12.h>
+#include <openssl/rand.h>
+#include <openssl/x509.h>
+#include <openssl/x509_vfy.h>
+
+#include <libeic.h>
+
+using ::std::optional;
+using ::std::string;
+using ::std::tuple;
+using ::std::vector;
+
+namespace android::hardware::identity {
+
+// ----------------------------------------------------------------------
+
+FakeSecureHardwareProvisioningProxy::FakeSecureHardwareProvisioningProxy() {}
+
+FakeSecureHardwareProvisioningProxy::~FakeSecureHardwareProvisioningProxy() {}
+
+bool FakeSecureHardwareProvisioningProxy::shutdown() {
+ LOG(INFO) << "FakeSecureHardwarePresentationProxy shutdown";
+ return true;
+}
+
+bool FakeSecureHardwareProvisioningProxy::initialize(bool testCredential) {
+ LOG(INFO) << "FakeSecureHardwareProvisioningProxy created, sizeof(EicProvisioning): "
+ << sizeof(EicProvisioning);
+ return eicProvisioningInit(&ctx_, testCredential);
+}
+
+bool FakeSecureHardwareProvisioningProxy::initializeForUpdate(
+ bool testCredential, string docType, vector<uint8_t> encryptedCredentialKeys) {
+ return eicProvisioningInitForUpdate(&ctx_, testCredential, docType.c_str(),
+ encryptedCredentialKeys.data(),
+ encryptedCredentialKeys.size());
+}
+
+// Returns public key certificate.
+optional<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::createCredentialKey(
+ const vector<uint8_t>& challenge, const vector<uint8_t>& applicationId) {
+ uint8_t publicKeyCert[4096];
+ size_t publicKeyCertSize = sizeof publicKeyCert;
+ if (!eicProvisioningCreateCredentialKey(&ctx_, challenge.data(), challenge.size(),
+ applicationId.data(), applicationId.size(),
+ publicKeyCert, &publicKeyCertSize)) {
+ return {};
+ }
+ vector<uint8_t> pubKeyCert(publicKeyCertSize);
+ memcpy(pubKeyCert.data(), publicKeyCert, publicKeyCertSize);
+ return pubKeyCert;
+}
+
+bool FakeSecureHardwareProvisioningProxy::startPersonalization(
+ int accessControlProfileCount, vector<int> entryCounts, const string& docType,
+ size_t expectedProofOfProvisioningSize) {
+ if (!eicProvisioningStartPersonalization(&ctx_, accessControlProfileCount, entryCounts.data(),
+ entryCounts.size(), docType.c_str(),
+ expectedProofOfProvisioningSize)) {
+ return false;
+ }
+ return true;
+}
+
+// Returns MAC (28 bytes).
+optional<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::addAccessControlProfile(
+ int id, const vector<uint8_t>& readerCertificate, bool userAuthenticationRequired,
+ uint64_t timeoutMillis, uint64_t secureUserId) {
+ vector<uint8_t> mac(28);
+ if (!eicProvisioningAddAccessControlProfile(
+ &ctx_, id, readerCertificate.data(), readerCertificate.size(),
+ userAuthenticationRequired, timeoutMillis, secureUserId, mac.data())) {
+ return {};
+ }
+ return mac;
+}
+
+bool FakeSecureHardwareProvisioningProxy::beginAddEntry(const vector<int>& accessControlProfileIds,
+ const string& nameSpace, const string& name,
+ uint64_t entrySize) {
+ uint8_t scratchSpace[512];
+ return eicProvisioningBeginAddEntry(&ctx_, accessControlProfileIds.data(),
+ accessControlProfileIds.size(), nameSpace.c_str(),
+ name.c_str(), entrySize, scratchSpace, sizeof scratchSpace);
+}
+
+// Returns encryptedContent.
+optional<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::addEntryValue(
+ const vector<int>& accessControlProfileIds, const string& nameSpace, const string& name,
+ const vector<uint8_t>& content) {
+ vector<uint8_t> eicEncryptedContent;
+ uint8_t scratchSpace[512];
+ eicEncryptedContent.resize(content.size() + 28);
+ if (!eicProvisioningAddEntryValue(
+ &ctx_, accessControlProfileIds.data(), accessControlProfileIds.size(),
+ nameSpace.c_str(), name.c_str(), content.data(), content.size(),
+ eicEncryptedContent.data(), scratchSpace, sizeof scratchSpace)) {
+ return {};
+ }
+ return eicEncryptedContent;
+}
+
+// Returns signatureOfToBeSigned (EIC_ECDSA_P256_SIGNATURE_SIZE bytes).
+optional<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::finishAddingEntries() {
+ vector<uint8_t> signatureOfToBeSigned(EIC_ECDSA_P256_SIGNATURE_SIZE);
+ if (!eicProvisioningFinishAddingEntries(&ctx_, signatureOfToBeSigned.data())) {
+ return {};
+ }
+ return signatureOfToBeSigned;
+}
+
+// Returns encryptedCredentialKeys.
+optional<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::finishGetCredentialData(
+ const string& docType) {
+ vector<uint8_t> encryptedCredentialKeys(116);
+ size_t size = encryptedCredentialKeys.size();
+ if (!eicProvisioningFinishGetCredentialData(&ctx_, docType.c_str(),
+ encryptedCredentialKeys.data(), &size)) {
+ return {};
+ }
+ encryptedCredentialKeys.resize(size);
+ return encryptedCredentialKeys;
+}
+
+// ----------------------------------------------------------------------
+
+FakeSecureHardwarePresentationProxy::FakeSecureHardwarePresentationProxy() {}
+
+FakeSecureHardwarePresentationProxy::~FakeSecureHardwarePresentationProxy() {}
+
+bool FakeSecureHardwarePresentationProxy::initialize(bool testCredential, string docType,
+ vector<uint8_t> encryptedCredentialKeys) {
+ LOG(INFO) << "FakeSecureHardwarePresentationProxy created, sizeof(EicPresentation): "
+ << sizeof(EicPresentation);
+ return eicPresentationInit(&ctx_, testCredential, docType.c_str(),
+ encryptedCredentialKeys.data(), encryptedCredentialKeys.size());
+}
+
+// Returns publicKeyCert (1st component) and signingKeyBlob (2nd component)
+optional<pair<vector<uint8_t>, vector<uint8_t>>>
+FakeSecureHardwarePresentationProxy::generateSigningKeyPair(string docType, time_t now) {
+ uint8_t publicKeyCert[512];
+ size_t publicKeyCertSize = sizeof(publicKeyCert);
+ vector<uint8_t> signingKeyBlob(60);
+
+ if (!eicPresentationGenerateSigningKeyPair(&ctx_, docType.c_str(), now, publicKeyCert,
+ &publicKeyCertSize, signingKeyBlob.data())) {
+ return {};
+ }
+
+ vector<uint8_t> cert;
+ cert.resize(publicKeyCertSize);
+ memcpy(cert.data(), publicKeyCert, publicKeyCertSize);
+
+ return std::make_pair(cert, signingKeyBlob);
+}
+
+// Returns private key
+optional<vector<uint8_t>> FakeSecureHardwarePresentationProxy::createEphemeralKeyPair() {
+ vector<uint8_t> priv(EIC_P256_PRIV_KEY_SIZE);
+ if (!eicPresentationCreateEphemeralKeyPair(&ctx_, priv.data())) {
+ return {};
+ }
+ return priv;
+}
+
+optional<uint64_t> FakeSecureHardwarePresentationProxy::createAuthChallenge() {
+ uint64_t challenge;
+ if (!eicPresentationCreateAuthChallenge(&ctx_, &challenge)) {
+ return {};
+ }
+ return challenge;
+}
+
+bool FakeSecureHardwarePresentationProxy::shutdown() {
+ LOG(INFO) << "FakeSecureHardwarePresentationProxy shutdown";
+ return true;
+}
+
+bool FakeSecureHardwarePresentationProxy::pushReaderCert(const vector<uint8_t>& certX509) {
+ return eicPresentationPushReaderCert(&ctx_, certX509.data(), certX509.size());
+}
+
+bool FakeSecureHardwarePresentationProxy::validateRequestMessage(
+ const vector<uint8_t>& sessionTranscript, const vector<uint8_t>& requestMessage,
+ int coseSignAlg, const vector<uint8_t>& readerSignatureOfToBeSigned) {
+ return eicPresentationValidateRequestMessage(
+ &ctx_, sessionTranscript.data(), sessionTranscript.size(), requestMessage.data(),
+ requestMessage.size(), coseSignAlg, readerSignatureOfToBeSigned.data(),
+ readerSignatureOfToBeSigned.size());
+}
+
+bool FakeSecureHardwarePresentationProxy::setAuthToken(
+ uint64_t challenge, uint64_t secureUserId, uint64_t authenticatorId,
+ int hardwareAuthenticatorType, uint64_t timeStamp, const vector<uint8_t>& mac,
+ uint64_t verificationTokenChallenge, uint64_t verificationTokenTimestamp,
+ int verificationTokenSecurityLevel, const vector<uint8_t>& verificationTokenMac) {
+ return eicPresentationSetAuthToken(&ctx_, challenge, secureUserId, authenticatorId,
+ hardwareAuthenticatorType, timeStamp, mac.data(), mac.size(),
+ verificationTokenChallenge, verificationTokenTimestamp,
+ verificationTokenSecurityLevel, verificationTokenMac.data(),
+ verificationTokenMac.size());
+}
+
+optional<bool> FakeSecureHardwarePresentationProxy::validateAccessControlProfile(
+ int id, const vector<uint8_t>& readerCertificate, bool userAuthenticationRequired,
+ int timeoutMillis, uint64_t secureUserId, const vector<uint8_t>& mac) {
+ bool accessGranted = false;
+ if (!eicPresentationValidateAccessControlProfile(&ctx_, id, readerCertificate.data(),
+ readerCertificate.size(),
+ userAuthenticationRequired, timeoutMillis,
+ secureUserId, mac.data(), &accessGranted)) {
+ return {};
+ }
+ return accessGranted;
+}
+
+bool FakeSecureHardwarePresentationProxy::startRetrieveEntries() {
+ return eicPresentationStartRetrieveEntries(&ctx_);
+}
+
+bool FakeSecureHardwarePresentationProxy::calcMacKey(
+ const vector<uint8_t>& sessionTranscript, const vector<uint8_t>& readerEphemeralPublicKey,
+ const vector<uint8_t>& signingKeyBlob, const string& docType,
+ unsigned int numNamespacesWithValues, size_t expectedProofOfProvisioningSize) {
+ if (signingKeyBlob.size() != 60) {
+ eicDebug("Unexpected size %zd of signingKeyBlob, expected 60", signingKeyBlob.size());
+ return false;
+ }
+ return eicPresentationCalcMacKey(&ctx_, sessionTranscript.data(), sessionTranscript.size(),
+ readerEphemeralPublicKey.data(), signingKeyBlob.data(),
+ docType.c_str(), numNamespacesWithValues,
+ expectedProofOfProvisioningSize);
+}
+
+AccessCheckResult FakeSecureHardwarePresentationProxy::startRetrieveEntryValue(
+ const string& nameSpace, const string& name, unsigned int newNamespaceNumEntries,
+ int32_t entrySize, const vector<int32_t>& accessControlProfileIds) {
+ uint8_t scratchSpace[512];
+ EicAccessCheckResult result = eicPresentationStartRetrieveEntryValue(
+ &ctx_, nameSpace.c_str(), name.c_str(), newNamespaceNumEntries, entrySize,
+ accessControlProfileIds.data(), accessControlProfileIds.size(), scratchSpace,
+ sizeof scratchSpace);
+ switch (result) {
+ case EIC_ACCESS_CHECK_RESULT_OK:
+ return AccessCheckResult::kOk;
+ case EIC_ACCESS_CHECK_RESULT_NO_ACCESS_CONTROL_PROFILES:
+ return AccessCheckResult::kNoAccessControlProfiles;
+ case EIC_ACCESS_CHECK_RESULT_FAILED:
+ return AccessCheckResult::kFailed;
+ case EIC_ACCESS_CHECK_RESULT_USER_AUTHENTICATION_FAILED:
+ return AccessCheckResult::kUserAuthenticationFailed;
+ case EIC_ACCESS_CHECK_RESULT_READER_AUTHENTICATION_FAILED:
+ return AccessCheckResult::kReaderAuthenticationFailed;
+ }
+ eicDebug("Unknown result with code %d, returning kFailed", (int)result);
+ return AccessCheckResult::kFailed;
+}
+
+optional<vector<uint8_t>> FakeSecureHardwarePresentationProxy::retrieveEntryValue(
+ const vector<uint8_t>& encryptedContent, const string& nameSpace, const string& name,
+ const vector<int32_t>& accessControlProfileIds) {
+ uint8_t scratchSpace[512];
+ vector<uint8_t> content;
+ content.resize(encryptedContent.size() - 28);
+ if (!eicPresentationRetrieveEntryValue(
+ &ctx_, encryptedContent.data(), encryptedContent.size(), content.data(),
+ nameSpace.c_str(), name.c_str(), accessControlProfileIds.data(),
+ accessControlProfileIds.size(), scratchSpace, sizeof scratchSpace)) {
+ return {};
+ }
+ return content;
+}
+
+optional<vector<uint8_t>> FakeSecureHardwarePresentationProxy::finishRetrieval() {
+ vector<uint8_t> mac(32);
+ size_t macSize = 32;
+ if (!eicPresentationFinishRetrieval(&ctx_, mac.data(), &macSize)) {
+ return {};
+ }
+ mac.resize(macSize);
+ return mac;
+}
+
+optional<vector<uint8_t>> FakeSecureHardwarePresentationProxy::deleteCredential(
+ const string& docType, const vector<uint8_t>& challenge, bool includeChallenge,
+ size_t proofOfDeletionCborSize) {
+ vector<uint8_t> signatureOfToBeSigned(EIC_ECDSA_P256_SIGNATURE_SIZE);
+ if (!eicPresentationDeleteCredential(&ctx_, docType.c_str(), challenge.data(), challenge.size(),
+ includeChallenge, proofOfDeletionCborSize,
+ signatureOfToBeSigned.data())) {
+ return {};
+ }
+ return signatureOfToBeSigned;
+}
+
+optional<vector<uint8_t>> FakeSecureHardwarePresentationProxy::proveOwnership(
+ const string& docType, bool testCredential, const vector<uint8_t>& challenge,
+ size_t proofOfOwnershipCborSize) {
+ vector<uint8_t> signatureOfToBeSigned(EIC_ECDSA_P256_SIGNATURE_SIZE);
+ if (!eicPresentationProveOwnership(&ctx_, docType.c_str(), testCredential, challenge.data(),
+ challenge.size(), proofOfOwnershipCborSize,
+ signatureOfToBeSigned.data())) {
+ return {};
+ }
+ return signatureOfToBeSigned;
+}
+
+} // namespace android::hardware::identity
diff --git a/identity/aidl/default/FakeSecureHardwareProxy.h b/identity/aidl/default/FakeSecureHardwareProxy.h
new file mode 100644
index 0000000..6852c1a
--- /dev/null
+++ b/identity/aidl/default/FakeSecureHardwareProxy.h
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_FAKESECUREHARDWAREPROXY_H
+#define ANDROID_HARDWARE_IDENTITY_FAKESECUREHARDWAREPROXY_H
+
+#include <libeic/libeic.h>
+
+#include "SecureHardwareProxy.h"
+
+namespace android::hardware::identity {
+
+// This implementation uses libEmbeddedIC in-process.
+//
+class FakeSecureHardwareProvisioningProxy : public SecureHardwareProvisioningProxy {
+ public:
+ FakeSecureHardwareProvisioningProxy();
+ virtual ~FakeSecureHardwareProvisioningProxy();
+
+ bool initialize(bool testCredential) override;
+
+ bool initializeForUpdate(bool testCredential, string docType,
+ vector<uint8_t> encryptedCredentialKeys) override;
+
+ bool shutdown() override;
+
+ // Returns public key certificate.
+ optional<vector<uint8_t>> createCredentialKey(const vector<uint8_t>& challenge,
+ const vector<uint8_t>& applicationId) override;
+
+ bool startPersonalization(int accessControlProfileCount, vector<int> entryCounts,
+ const string& docType,
+ size_t expectedProofOfProvisioningSize) override;
+
+ // Returns MAC (28 bytes).
+ optional<vector<uint8_t>> addAccessControlProfile(int id,
+ const vector<uint8_t>& readerCertificate,
+ bool userAuthenticationRequired,
+ uint64_t timeoutMillis,
+ uint64_t secureUserId) override;
+
+ bool beginAddEntry(const vector<int>& accessControlProfileIds, const string& nameSpace,
+ const string& name, uint64_t entrySize) override;
+
+ // Returns encryptedContent.
+ optional<vector<uint8_t>> addEntryValue(const vector<int>& accessControlProfileIds,
+ const string& nameSpace, const string& name,
+ const vector<uint8_t>& content) override;
+
+ // Returns signatureOfToBeSigned (EIC_ECDSA_P256_SIGNATURE_SIZE bytes).
+ optional<vector<uint8_t>> finishAddingEntries() override;
+
+ // Returns encryptedCredentialKeys (80 bytes).
+ optional<vector<uint8_t>> finishGetCredentialData(const string& docType) override;
+
+ protected:
+ EicProvisioning ctx_;
+};
+
+// This implementation uses libEmbeddedIC in-process.
+//
+class FakeSecureHardwarePresentationProxy : public SecureHardwarePresentationProxy {
+ public:
+ FakeSecureHardwarePresentationProxy();
+ virtual ~FakeSecureHardwarePresentationProxy();
+
+ bool initialize(bool testCredential, string docType,
+ vector<uint8_t> encryptedCredentialKeys) override;
+
+ // Returns publicKeyCert (1st component) and signingKeyBlob (2nd component)
+ optional<pair<vector<uint8_t>, vector<uint8_t>>> generateSigningKeyPair(string docType,
+ time_t now) override;
+
+ // Returns private key
+ optional<vector<uint8_t>> createEphemeralKeyPair() override;
+
+ optional<uint64_t> createAuthChallenge() override;
+
+ bool startRetrieveEntries() override;
+
+ bool setAuthToken(uint64_t challenge, uint64_t secureUserId, uint64_t authenticatorId,
+ int hardwareAuthenticatorType, uint64_t timeStamp, const vector<uint8_t>& mac,
+ uint64_t verificationTokenChallenge, uint64_t verificationTokenTimestamp,
+ int verificationTokenSecurityLevel,
+ const vector<uint8_t>& verificationTokenMac) override;
+
+ bool pushReaderCert(const vector<uint8_t>& certX509) override;
+
+ optional<bool> validateAccessControlProfile(int id, const vector<uint8_t>& readerCertificate,
+ bool userAuthenticationRequired, int timeoutMillis,
+ uint64_t secureUserId,
+ const vector<uint8_t>& mac) override;
+
+ bool validateRequestMessage(const vector<uint8_t>& sessionTranscript,
+ const vector<uint8_t>& requestMessage, int coseSignAlg,
+ const vector<uint8_t>& readerSignatureOfToBeSigned) override;
+
+ bool calcMacKey(const vector<uint8_t>& sessionTranscript,
+ const vector<uint8_t>& readerEphemeralPublicKey,
+ const vector<uint8_t>& signingKeyBlob, const string& docType,
+ unsigned int numNamespacesWithValues,
+ size_t expectedProofOfProvisioningSize) override;
+
+ AccessCheckResult startRetrieveEntryValue(
+ const string& nameSpace, const string& name, unsigned int newNamespaceNumEntries,
+ int32_t entrySize, const vector<int32_t>& accessControlProfileIds) override;
+
+ optional<vector<uint8_t>> retrieveEntryValue(
+ const vector<uint8_t>& encryptedContent, const string& nameSpace, const string& name,
+ const vector<int32_t>& accessControlProfileIds) override;
+
+ optional<vector<uint8_t>> finishRetrieval() override;
+
+ optional<vector<uint8_t>> deleteCredential(const string& docType,
+ const vector<uint8_t>& challenge,
+ bool includeChallenge,
+ size_t proofOfDeletionCborSize) override;
+
+ optional<vector<uint8_t>> proveOwnership(const string& docType, bool testCredential,
+ const vector<uint8_t>& challenge,
+ size_t proofOfOwnershipCborSize) override;
+
+ bool shutdown() override;
+
+ protected:
+ EicPresentation ctx_;
+};
+
+// Factory implementation.
+//
+class FakeSecureHardwareProxyFactory : public SecureHardwareProxyFactory {
+ public:
+ FakeSecureHardwareProxyFactory() {}
+ virtual ~FakeSecureHardwareProxyFactory() {}
+
+ sp<SecureHardwareProvisioningProxy> createProvisioningProxy() override {
+ return new FakeSecureHardwareProvisioningProxy();
+ }
+
+ sp<SecureHardwarePresentationProxy> createPresentationProxy() override {
+ return new FakeSecureHardwarePresentationProxy();
+ }
+};
+
+} // namespace android::hardware::identity
+
+#endif // ANDROID_HARDWARE_IDENTITY_FAKESECUREHARDWAREPROXY_H
diff --git a/identity/aidl/default/Util.cpp b/identity/aidl/default/Util.cpp
deleted file mode 100644
index 66b9f13..0000000
--- a/identity/aidl/default/Util.cpp
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "Util"
-
-#include "Util.h"
-
-#include <android/hardware/identity/support/IdentityCredentialSupport.h>
-
-#include <string.h>
-
-#include <android-base/logging.h>
-
-#include <cppbor.h>
-#include <cppbor_parse.h>
-
-namespace aidl::android::hardware::identity {
-
-using namespace ::android::hardware::identity;
-
-// This is not a very random HBK but that's OK because this is the SW
-// implementation where it can't be kept secret.
-vector<uint8_t> hardwareBoundKey = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
-
-const vector<uint8_t>& getHardwareBoundKey() {
- return hardwareBoundKey;
-}
-
-vector<uint8_t> secureAccessControlProfileEncodeCbor(const SecureAccessControlProfile& profile) {
- cppbor::Map map;
- map.add("id", profile.id);
-
- if (profile.readerCertificate.encodedCertificate.size() > 0) {
- map.add("readerCertificate", cppbor::Bstr(profile.readerCertificate.encodedCertificate));
- }
-
- if (profile.userAuthenticationRequired) {
- map.add("userAuthenticationRequired", profile.userAuthenticationRequired);
- map.add("timeoutMillis", profile.timeoutMillis);
- map.add("secureUserId", profile.secureUserId);
- }
-
- return map.encode();
-}
-
-optional<vector<uint8_t>> secureAccessControlProfileCalcMac(
- const SecureAccessControlProfile& profile, const vector<uint8_t>& storageKey) {
- vector<uint8_t> cborData = secureAccessControlProfileEncodeCbor(profile);
-
- optional<vector<uint8_t>> nonce = support::getRandom(12);
- if (!nonce) {
- return {};
- }
- optional<vector<uint8_t>> macO =
- support::encryptAes128Gcm(storageKey, nonce.value(), {}, cborData);
- if (!macO) {
- return {};
- }
- return macO.value();
-}
-
-bool secureAccessControlProfileCheckMac(const SecureAccessControlProfile& profile,
- const vector<uint8_t>& storageKey) {
- vector<uint8_t> cborData = secureAccessControlProfileEncodeCbor(profile);
-
- if (profile.mac.size() < support::kAesGcmIvSize) {
- return false;
- }
- vector<uint8_t> nonce =
- vector<uint8_t>(profile.mac.begin(), profile.mac.begin() + support::kAesGcmIvSize);
- optional<vector<uint8_t>> mac = support::encryptAes128Gcm(storageKey, nonce, {}, cborData);
- if (!mac) {
- return false;
- }
- if (mac.value() != profile.mac) {
- return false;
- }
- return true;
-}
-
-vector<uint8_t> entryCreateAdditionalData(const string& nameSpace, const string& name,
- const vector<int32_t> accessControlProfileIds) {
- cppbor::Map map;
- map.add("Namespace", nameSpace);
- map.add("Name", name);
-
- cppbor::Array acpIds;
- for (auto id : accessControlProfileIds) {
- acpIds.add(id);
- }
- map.add("AccessControlProfileIds", std::move(acpIds));
- return map.encode();
-}
-
-} // namespace aidl::android::hardware::identity
diff --git a/identity/aidl/default/Util.h b/identity/aidl/default/Util.h
deleted file mode 100644
index 9fccba2..0000000
--- a/identity/aidl/default/Util.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_HARDWARE_IDENTITY_UTIL_H
-#define ANDROID_HARDWARE_IDENTITY_UTIL_H
-
-#include <aidl/android/hardware/identity/BnIdentityCredential.h>
-#include <android/hardware/identity/support/IdentityCredentialSupport.h>
-
-#include <map>
-#include <optional>
-#include <set>
-#include <string>
-#include <vector>
-
-#include <cppbor/cppbor.h>
-
-namespace aidl::android::hardware::identity {
-
-using ::std::optional;
-using ::std::string;
-using ::std::vector;
-
-// Returns the hardware-bound AES-128 key.
-const vector<uint8_t>& getHardwareBoundKey();
-
-// Calculates the MAC for |profile| using |storageKey|.
-optional<vector<uint8_t>> secureAccessControlProfileCalcMac(
- const SecureAccessControlProfile& profile, const vector<uint8_t>& storageKey);
-
-// Checks authenticity of the MAC in |profile| using |storageKey|.
-bool secureAccessControlProfileCheckMac(const SecureAccessControlProfile& profile,
- const vector<uint8_t>& storageKey);
-
-// Creates the AdditionalData CBOR used in the addEntryValue() HIDL method.
-vector<uint8_t> entryCreateAdditionalData(const string& nameSpace, const string& name,
- const vector<int32_t> accessControlProfileIds);
-
-} // namespace aidl::android::hardware::identity
-
-#endif // ANDROID_HARDWARE_IDENTITY_UTIL_H
diff --git a/identity/aidl/default/android.hardware.identity_credential.xml b/identity/aidl/default/android.hardware.identity_credential.xml
new file mode 100644
index 0000000..5149792
--- /dev/null
+++ b/identity/aidl/default/android.hardware.identity_credential.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2021 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.
+-->
+<permissions>
+ <feature name="android.hardware.identity_credential" version="202101" />
+</permissions>
diff --git a/identity/aidl/default/IdentityCredential.cpp b/identity/aidl/default/common/IdentityCredential.cpp
similarity index 61%
rename from identity/aidl/default/IdentityCredential.cpp
rename to identity/aidl/default/common/IdentityCredential.cpp
index dfcd4f5..9477997 100644
--- a/identity/aidl/default/IdentityCredential.cpp
+++ b/identity/aidl/default/common/IdentityCredential.cpp
@@ -18,7 +18,6 @@
#include "IdentityCredential.h"
#include "IdentityCredentialStore.h"
-#include "Util.h"
#include <android/hardware/identity/support/IdentityCredentialSupport.h>
@@ -30,6 +29,9 @@
#include <cppbor.h>
#include <cppbor_parse.h>
+#include "FakeSecureHardwareProxy.h"
+#include "WritableIdentityCredential.h"
+
namespace aidl::android::hardware::identity {
using ::aidl::android::hardware::keymaster::Timestamp;
@@ -69,53 +71,52 @@
docType_ = docTypeItem->value();
testCredential_ = testCredentialItem->value();
- vector<uint8_t> hardwareBoundKey;
- if (testCredential_) {
- hardwareBoundKey = support::getTestHardwareBoundKey();
- } else {
- hardwareBoundKey = getHardwareBoundKey();
+ encryptedCredentialKeys_ = encryptedCredentialKeysItem->value();
+ if (!hwProxy_->initialize(testCredential_, docType_, encryptedCredentialKeys_)) {
+ LOG(ERROR) << "hwProxy->initialize failed";
+ return false;
}
- const vector<uint8_t>& encryptedCredentialKeys = encryptedCredentialKeysItem->value();
- const vector<uint8_t> docTypeVec(docType_.begin(), docType_.end());
- optional<vector<uint8_t>> decryptedCredentialKeys =
- support::decryptAes128Gcm(hardwareBoundKey, encryptedCredentialKeys, docTypeVec);
- if (!decryptedCredentialKeys) {
- LOG(ERROR) << "Error decrypting CredentialKeys";
- return IIdentityCredentialStore::STATUS_INVALID_DATA;
- }
-
- auto [dckItem, dckPos, dckMessage] = cppbor::parse(decryptedCredentialKeys.value());
- if (dckItem == nullptr) {
- LOG(ERROR) << "Decrypted CredentialKeys is not valid CBOR: " << dckMessage;
- return IIdentityCredentialStore::STATUS_INVALID_DATA;
- }
- const cppbor::Array* dckArrayItem = dckItem->asArray();
- if (dckArrayItem == nullptr || dckArrayItem->size() != 2) {
- LOG(ERROR) << "Decrypted CredentialKeys is not an array with two elements";
- return IIdentityCredentialStore::STATUS_INVALID_DATA;
- }
- const cppbor::Bstr* storageKeyItem = (*dckArrayItem)[0]->asBstr();
- const cppbor::Bstr* credentialPrivKeyItem = (*dckArrayItem)[1]->asBstr();
- if (storageKeyItem == nullptr || credentialPrivKeyItem == nullptr) {
- LOG(ERROR) << "CredentialKeys unexpected item types";
- return IIdentityCredentialStore::STATUS_INVALID_DATA;
- }
- storageKey_ = storageKeyItem->value();
- credentialPrivKey_ = credentialPrivKeyItem->value();
-
return IIdentityCredentialStore::STATUS_OK;
}
ndk::ScopedAStatus IdentityCredential::deleteCredential(
vector<uint8_t>* outProofOfDeletionSignature) {
- cppbor::Array array = {"ProofOfDeletion", docType_, testCredential_};
- vector<uint8_t> proofOfDeletion = array.encode();
+ return deleteCredentialCommon({}, false, outProofOfDeletionSignature);
+}
- optional<vector<uint8_t>> signature = support::coseSignEcDsa(credentialPrivKey_,
- proofOfDeletion, // payload
- {}, // additionalData
- {}); // certificateChain
+ndk::ScopedAStatus IdentityCredential::deleteCredentialWithChallenge(
+ const vector<uint8_t>& challenge, vector<uint8_t>* outProofOfDeletionSignature) {
+ return deleteCredentialCommon(challenge, true, outProofOfDeletionSignature);
+}
+
+ndk::ScopedAStatus IdentityCredential::deleteCredentialCommon(
+ const vector<uint8_t>& challenge, bool includeChallenge,
+ vector<uint8_t>* outProofOfDeletionSignature) {
+ if (challenge.size() > 32) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA, "Challenge too big"));
+ }
+
+ cppbor::Array array = {"ProofOfDeletion", docType_, testCredential_};
+ if (includeChallenge) {
+ array = {"ProofOfDeletion", docType_, challenge, testCredential_};
+ }
+
+ vector<uint8_t> proofOfDeletionCbor = array.encode();
+ vector<uint8_t> podDigest = support::sha256(proofOfDeletionCbor);
+
+ optional<vector<uint8_t>> signatureOfToBeSigned = hwProxy_->deleteCredential(
+ docType_, challenge, includeChallenge, proofOfDeletionCbor.size());
+ if (!signatureOfToBeSigned) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "Error signing ProofOfDeletion"));
+ }
+
+ optional<vector<uint8_t>> signature =
+ support::coseSignEcDsaWithSignature(signatureOfToBeSigned.value(),
+ proofOfDeletionCbor, // data
+ {}); // certificateChain
if (!signature) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_FAILED, "Error signing data"));
@@ -125,23 +126,61 @@
return ndk::ScopedAStatus::ok();
}
-ndk::ScopedAStatus IdentityCredential::createEphemeralKeyPair(vector<uint8_t>* outKeyPair) {
- optional<vector<uint8_t>> kp = support::createEcKeyPair();
- if (!kp) {
+ndk::ScopedAStatus IdentityCredential::proveOwnership(
+ const vector<uint8_t>& challenge, vector<uint8_t>* outProofOfOwnershipSignature) {
+ if (challenge.size() > 32) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error creating ephemeral key pair"));
+ IIdentityCredentialStore::STATUS_INVALID_DATA, "Challenge too big"));
+ }
+
+ cppbor::Array array;
+ array = {"ProofOfOwnership", docType_, challenge, testCredential_};
+ vector<uint8_t> proofOfOwnershipCbor = array.encode();
+ vector<uint8_t> podDigest = support::sha256(proofOfOwnershipCbor);
+
+ optional<vector<uint8_t>> signatureOfToBeSigned = hwProxy_->proveOwnership(
+ docType_, testCredential_, challenge, proofOfOwnershipCbor.size());
+ if (!signatureOfToBeSigned) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "Error signing ProofOfOwnership"));
+ }
+
+ optional<vector<uint8_t>> signature =
+ support::coseSignEcDsaWithSignature(signatureOfToBeSigned.value(),
+ proofOfOwnershipCbor, // data
+ {}); // certificateChain
+ if (!signature) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "Error signing data"));
+ }
+
+ *outProofOfOwnershipSignature = signature.value();
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredential::createEphemeralKeyPair(vector<uint8_t>* outKeyPair) {
+ optional<vector<uint8_t>> ephemeralPriv = hwProxy_->createEphemeralKeyPair();
+ if (!ephemeralPriv) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "Error creating ephemeral key"));
+ }
+ optional<vector<uint8_t>> keyPair = support::ecPrivateKeyToKeyPair(ephemeralPriv.value());
+ if (!keyPair) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "Error creating ephemeral key-pair"));
}
// Stash public key of this key-pair for later check in startRetrieval().
- optional<vector<uint8_t>> publicKey = support::ecKeyPairGetPublicKey(kp.value());
+ optional<vector<uint8_t>> publicKey = support::ecKeyPairGetPublicKey(keyPair.value());
if (!publicKey) {
+ LOG(ERROR) << "Error getting public part of ephemeral key pair";
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_FAILED,
"Error getting public part of ephemeral key pair"));
}
ephemeralPublicKey_ = publicKey.value();
- *outKeyPair = kp.value();
+ *outKeyPair = keyPair.value();
return ndk::ScopedAStatus::ok();
}
@@ -152,109 +191,15 @@
}
ndk::ScopedAStatus IdentityCredential::createAuthChallenge(int64_t* outChallenge) {
- uint64_t challenge = 0;
- while (challenge == 0) {
- optional<vector<uint8_t>> bytes = support::getRandom(8);
- if (!bytes) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED,
- "Error getting random data for challenge"));
- }
-
- challenge = 0;
- for (size_t n = 0; n < bytes.value().size(); n++) {
- challenge |= ((bytes.value())[n] << (n * 8));
- }
+ optional<uint64_t> challenge = hwProxy_->createAuthChallenge();
+ if (!challenge) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "Error generating challenge"));
}
-
- *outChallenge = challenge;
- authChallenge_ = challenge;
+ *outChallenge = challenge.value();
return ndk::ScopedAStatus::ok();
}
-// TODO: this could be a lot faster if we did all the splitting and pubkey extraction
-// ahead of time.
-bool checkReaderAuthentication(const SecureAccessControlProfile& profile,
- const vector<uint8_t>& readerCertificateChain) {
- optional<vector<uint8_t>> acpPubKey =
- support::certificateChainGetTopMostKey(profile.readerCertificate.encodedCertificate);
- if (!acpPubKey) {
- LOG(ERROR) << "Error extracting public key from readerCertificate in profile";
- return false;
- }
-
- optional<vector<vector<uint8_t>>> certificatesInChain =
- support::certificateChainSplit(readerCertificateChain);
- if (!certificatesInChain) {
- LOG(ERROR) << "Error splitting readerCertificateChain";
- return false;
- }
- for (const vector<uint8_t>& certInChain : certificatesInChain.value()) {
- optional<vector<uint8_t>> certPubKey = support::certificateChainGetTopMostKey(certInChain);
- if (!certPubKey) {
- LOG(ERROR)
- << "Error extracting public key from certificate in chain presented by reader";
- return false;
- }
- if (acpPubKey.value() == certPubKey.value()) {
- return true;
- }
- }
- return false;
-}
-
-bool checkUserAuthentication(const SecureAccessControlProfile& profile,
- const VerificationToken& verificationToken,
- const HardwareAuthToken& authToken, uint64_t authChallenge) {
- if (profile.secureUserId != authToken.userId) {
- LOG(ERROR) << "secureUserId in profile (" << profile.secureUserId
- << ") differs from userId in authToken (" << authToken.userId << ")";
- return false;
- }
-
- if (verificationToken.timestamp.milliSeconds == 0) {
- LOG(ERROR) << "VerificationToken is not set";
- return false;
- }
- if (authToken.timestamp.milliSeconds == 0) {
- LOG(ERROR) << "AuthToken is not set";
- return false;
- }
-
- if (profile.timeoutMillis == 0) {
- if (authToken.challenge == 0) {
- LOG(ERROR) << "No challenge in authToken";
- return false;
- }
-
- if (authToken.challenge != int64_t(authChallenge)) {
- LOG(ERROR) << "Challenge in authToken (" << uint64_t(authToken.challenge) << ") "
- << "doesn't match the challenge we created (" << authChallenge << ")";
- return false;
- }
- return true;
- }
-
- // Timeout-based user auth follows. The verification token conveys what the
- // time is right now in the environment which generated the auth token. This
- // is what makes it possible to do timeout-based checks.
- //
- const Timestamp now = verificationToken.timestamp;
- if (authToken.timestamp.milliSeconds > now.milliSeconds) {
- LOG(ERROR) << "Timestamp in authToken (" << authToken.timestamp.milliSeconds
- << ") is in the future (now: " << now.milliSeconds << ")";
- return false;
- }
- if (now.milliSeconds > authToken.timestamp.milliSeconds + profile.timeoutMillis) {
- LOG(ERROR) << "Deadline for authToken (" << authToken.timestamp.milliSeconds << " + "
- << profile.timeoutMillis << " = "
- << (authToken.timestamp.milliSeconds + profile.timeoutMillis)
- << ") is in the past (now: " << now.milliSeconds << ")";
- return false;
- }
- return true;
-}
-
ndk::ScopedAStatus IdentityCredential::setRequestedNamespaces(
const vector<RequestNamespace>& requestNamespaces) {
requestNamespaces_ = requestNamespaces;
@@ -284,6 +229,7 @@
}
if (numStartRetrievalCalls_ > 0) {
if (sessionTranscript_ != sessionTranscript) {
+ LOG(ERROR) << "Session Transcript changed";
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_SESSION_TRANSCRIPT_MISMATCH,
"Passed-in SessionTranscript doesn't match previously used SessionTranscript"));
@@ -291,6 +237,40 @@
}
sessionTranscript_ = sessionTranscript;
+ // This resets various state in the TA...
+ if (!hwProxy_->startRetrieveEntries()) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "Error starting retrieving entries"));
+ }
+
+ optional<vector<uint8_t>> signatureOfToBeSigned;
+ if (readerSignature.size() > 0) {
+ signatureOfToBeSigned = support::coseSignGetSignature(readerSignature);
+ if (!signatureOfToBeSigned) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
+ "Error extracting signatureOfToBeSigned from COSE_Sign1"));
+ }
+ }
+
+ // Feed the auth token to secure hardware.
+ if (!hwProxy_->setAuthToken(authToken.challenge, authToken.userId, authToken.authenticatorId,
+ int(authToken.authenticatorType), authToken.timestamp.milliSeconds,
+ authToken.mac, verificationToken_.challenge,
+ verificationToken_.timestamp.milliSeconds,
+ int(verificationToken_.securityLevel), verificationToken_.mac)) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA, "Invalid Auth Token"));
+ }
+
+ // We'll be feeding ACPs interleaved with certificates from the reader
+ // certificate chain...
+ vector<SecureAccessControlProfile> remainingAcps = accessControlProfiles;
+
+ // ... and we'll use those ACPs to build up a 32-bit mask indicating which
+ // of the possible 32 ACPs grants access.
+ uint32_t accessControlProfileMask = 0;
+
// If there is a signature, validate that it was made with the top-most key in the
// certificate chain embedded in the COSE_Sign1 structure.
optional<vector<uint8_t>> readerCertificateChain;
@@ -302,45 +282,113 @@
"Unable to get reader certificate chain from COSE_Sign1"));
}
- if (!support::certificateChainValidate(readerCertificateChain.value())) {
+ // First, feed all the reader certificates to the secure hardware. We start
+ // at the end..
+ optional<vector<vector<uint8_t>>> splitCerts =
+ support::certificateChainSplit(readerCertificateChain.value());
+ if (!splitCerts || splitCerts.value().size() == 0) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
- "Error validating reader certificate chain"));
+ "Error splitting certificate chain from COSE_Sign1"));
+ }
+ for (ssize_t n = splitCerts.value().size() - 1; n >= 0; --n) {
+ const vector<uint8_t>& x509Cert = splitCerts.value()[n];
+ if (!hwProxy_->pushReaderCert(x509Cert)) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
+ StringPrintf("Error validating reader certificate %zd", n).c_str()));
+ }
+
+ // If we have ACPs for that particular certificate, send them to the
+ // TA right now...
+ //
+ // Remember in this case certificate equality is done by comparing public keys,
+ // not bitwise comparison of the certificates.
+ //
+ optional<vector<uint8_t>> x509CertPubKey =
+ support::certificateChainGetTopMostKey(x509Cert);
+ if (!x509CertPubKey) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED,
+ StringPrintf("Error getting public key from reader certificate %zd", n)
+ .c_str()));
+ }
+ vector<SecureAccessControlProfile>::iterator it = remainingAcps.begin();
+ while (it != remainingAcps.end()) {
+ const SecureAccessControlProfile& profile = *it;
+ if (profile.readerCertificate.encodedCertificate.size() == 0) {
+ ++it;
+ continue;
+ }
+ optional<vector<uint8_t>> profilePubKey = support::certificateChainGetTopMostKey(
+ profile.readerCertificate.encodedCertificate);
+ if (!profilePubKey) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED,
+ "Error getting public key from profile"));
+ }
+ if (profilePubKey.value() == x509CertPubKey.value()) {
+ optional<bool> res = hwProxy_->validateAccessControlProfile(
+ profile.id, profile.readerCertificate.encodedCertificate,
+ profile.userAuthenticationRequired, profile.timeoutMillis,
+ profile.secureUserId, profile.mac);
+ if (!res) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "Error validating access control profile"));
+ }
+ if (res.value()) {
+ accessControlProfileMask |= (1 << profile.id);
+ }
+ it = remainingAcps.erase(it);
+ } else {
+ ++it;
+ }
+ }
}
- optional<vector<uint8_t>> readerPublicKey =
- support::certificateChainGetTopMostKey(readerCertificateChain.value());
- if (!readerPublicKey) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
- "Unable to get public key from reader certificate chain"));
- }
-
- const vector<uint8_t>& itemsRequestBytes = itemsRequest;
- vector<uint8_t> encodedReaderAuthentication =
- cppbor::Array()
- .add("ReaderAuthentication")
- .add(std::move(sessionTranscriptItem))
- .add(cppbor::Semantic(24, itemsRequestBytes))
- .encode();
- vector<uint8_t> encodedReaderAuthenticationBytes =
- cppbor::Semantic(24, encodedReaderAuthentication).encode();
- if (!support::coseCheckEcDsaSignature(readerSignature,
- encodedReaderAuthenticationBytes, // detached content
- readerPublicKey.value())) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
- "readerSignature check failed"));
+ // ... then pass the request message and have the TA check it's signed by the
+ // key in last certificate we pushed.
+ if (sessionTranscript.size() > 0 && itemsRequest.size() > 0 && readerSignature.size() > 0) {
+ optional<vector<uint8_t>> tbsSignature = support::coseSignGetSignature(readerSignature);
+ if (!tbsSignature) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
+ "Error extracting toBeSigned from COSE_Sign1"));
+ }
+ optional<int> coseSignAlg = support::coseSignGetAlg(readerSignature);
+ if (!coseSignAlg) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
+ "Error extracting signature algorithm from COSE_Sign1"));
+ }
+ if (!hwProxy_->validateRequestMessage(sessionTranscript, itemsRequest,
+ coseSignAlg.value(), tbsSignature.value())) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
+ "readerMessage is not signed by top-level certificate"));
+ }
}
}
- // Here's where we would validate the passed-in |authToken| to assure ourselves
- // that it comes from the e.g. biometric hardware and wasn't made up by an attacker.
- //
- // However this involves calculating the MAC. However this requires access
- // to the key needed to a pre-shared key which we don't have...
- //
+ // Feed remaining access control profiles...
+ for (const SecureAccessControlProfile& profile : remainingAcps) {
+ optional<bool> res = hwProxy_->validateAccessControlProfile(
+ profile.id, profile.readerCertificate.encodedCertificate,
+ profile.userAuthenticationRequired, profile.timeoutMillis, profile.secureUserId,
+ profile.mac);
+ if (!res) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "Error validating access control profile"));
+ }
+ if (res.value()) {
+ accessControlProfileMask |= (1 << profile.id);
+ }
+ }
+ // TODO: move this check to the TA
+#if 1
// To prevent replay-attacks, we check that the public part of the ephemeral
// key we previously created, is present in the DeviceEngagement part of
// SessionTranscript as a COSE_Key, in uncompressed form.
@@ -364,6 +412,7 @@
"SessionTranscript (make sure leading zeroes are not used)"));
}
}
+#endif
// itemsRequest: If non-empty, contains request data that may be signed by the
// reader. The content can be defined in the way appropriate for the
@@ -463,30 +512,6 @@
}
}
- // Validate all the access control profiles in the requestData.
- bool haveAuthToken = (authToken.timestamp.milliSeconds != int64_t(0));
- for (const auto& profile : accessControlProfiles) {
- if (!secureAccessControlProfileCheckMac(profile, storageKey_)) {
- LOG(ERROR) << "Error checking MAC for profile";
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_INVALID_DATA,
- "Error checking MAC for profile"));
- }
- int accessControlCheck = IIdentityCredentialStore::STATUS_OK;
- if (profile.userAuthenticationRequired) {
- if (!haveAuthToken ||
- !checkUserAuthentication(profile, verificationToken_, authToken, authChallenge_)) {
- accessControlCheck = IIdentityCredentialStore::STATUS_USER_AUTHENTICATION_FAILED;
- }
- } else if (profile.readerCertificate.encodedCertificate.size() > 0) {
- if (!readerCertificateChain ||
- !checkReaderAuthentication(profile, readerCertificateChain.value())) {
- accessControlCheck = IIdentityCredentialStore::STATUS_READER_AUTHENTICATION_FAILED;
- }
- }
- profileIdToAccessCheckResult_[profile.id] = accessControlCheck;
- }
-
deviceNameSpacesMap_ = cppbor::Map();
currentNameSpaceDeviceNameSpacesMap_ = cppbor::Map();
@@ -496,8 +521,36 @@
itemsRequest_ = itemsRequest;
signingKeyBlob_ = signingKeyBlob;
- // Finally, calculate the size of DeviceNameSpaces. We need to know it ahead of time.
- expectedDeviceNameSpacesSize_ = calcDeviceNameSpacesSize();
+ // calculate the size of DeviceNameSpaces. We need to know it ahead of time.
+ calcDeviceNameSpacesSize(accessControlProfileMask);
+
+ // Count the number of non-empty namespaces
+ size_t numNamespacesWithValues = 0;
+ for (size_t n = 0; n < expectedNumEntriesPerNamespace_.size(); n++) {
+ if (expectedNumEntriesPerNamespace_[n] > 0) {
+ numNamespacesWithValues += 1;
+ }
+ }
+
+ // Finally, pass info so the HMAC key can be derived and the TA can start
+ // creating the DeviceNameSpaces CBOR...
+ if (sessionTranscript_.size() > 0 && readerPublicKey_.size() > 0 && signingKeyBlob.size() > 0) {
+ // We expect the reader ephemeral public key to be same size and curve
+ // as the ephemeral key we generated (e.g. P-256 key), otherwise ECDH
+ // won't work. So its length should be 65 bytes and it should be
+ // starting with 0x04.
+ if (readerPublicKey_.size() != 65 || readerPublicKey_[0] != 0x04) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED,
+ "Reader public key is not in expected format"));
+ }
+ vector<uint8_t> pubKeyP256(readerPublicKey_.begin() + 1, readerPublicKey_.end());
+ if (!hwProxy_->calcMacKey(sessionTranscript_, pubKeyP256, signingKeyBlob, docType_,
+ numNamespacesWithValues, expectedDeviceNameSpacesSize_)) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "Error starting retrieving entries"));
+ }
+ }
numStartRetrievalCalls_ += 1;
return ndk::ScopedAStatus::ok();
@@ -520,7 +573,7 @@
return 1 + cborNumBytesForLength(value.size()) + value.size();
}
-size_t IdentityCredential::calcDeviceNameSpacesSize() {
+void IdentityCredential::calcDeviceNameSpacesSize(uint32_t accessControlProfileMask) {
/*
* This is how DeviceNameSpaces is defined:
*
@@ -539,7 +592,7 @@
* encoded.
*/
size_t ret = 0;
- size_t numNamespacesWithValues = 0;
+ vector<unsigned int> numEntriesPerNamespace;
for (const RequestNamespace& rns : requestNamespaces_) {
vector<RequestDataItem> itemsToInclude;
@@ -562,13 +615,9 @@
//
bool authorized = false;
for (auto id : rdi.accessControlProfileIds) {
- auto it = profileIdToAccessCheckResult_.find(id);
- if (it != profileIdToAccessCheckResult_.end()) {
- int accessControlForProfile = it->second;
- if (accessControlForProfile == IIdentityCredentialStore::STATUS_OK) {
- authorized = true;
- break;
- }
+ if (accessControlProfileMask & (1 << id)) {
+ authorized = true;
+ break;
}
}
if (!authorized) {
@@ -578,7 +627,10 @@
itemsToInclude.push_back(rdi);
}
- // If no entries are to be in the namespace, we don't include it...
+ numEntriesPerNamespace.push_back(itemsToInclude.size());
+
+ // If no entries are to be in the namespace, we don't include it in
+ // the CBOR...
if (itemsToInclude.size() == 0) {
continue;
}
@@ -597,15 +649,14 @@
// that.
ret += item.size;
}
-
- numNamespacesWithValues++;
}
- // Now that we now the nunber of namespaces with values, we know how many
+ // Now that we know the number of namespaces with values, we know how many
// bytes the DeviceNamespaces map in the beginning is going to take up.
- ret += 1 + cborNumBytesForLength(numNamespacesWithValues);
+ ret += 1 + cborNumBytesForLength(numEntriesPerNamespace.size());
- return ret;
+ expectedDeviceNameSpacesSize_ = ret;
+ expectedNumEntriesPerNamespace_ = numEntriesPerNamespace;
}
ndk::ScopedAStatus IdentityCredential::startRetrieveEntryValue(
@@ -626,9 +677,11 @@
"No more name spaces left to go through"));
}
+ bool newNamespace;
if (currentNameSpace_ == "") {
// First call.
currentNameSpace_ = nameSpace;
+ newNamespace = true;
}
if (nameSpace == currentNameSpace_) {
@@ -655,6 +708,7 @@
requestCountsRemaining_.erase(requestCountsRemaining_.begin());
currentNameSpace_ = nameSpace;
+ newNamespace = true;
}
// It's permissible to have an empty itemsRequest... but if non-empty you can
@@ -674,35 +728,52 @@
}
}
- // Enforce access control.
- //
- // Access is granted if at least one of the profiles grants access.
- //
- // If an item is configured without any profiles, access is denied.
- //
- int accessControl = IIdentityCredentialStore::STATUS_NO_ACCESS_CONTROL_PROFILES;
- for (auto id : accessControlProfileIds) {
- auto search = profileIdToAccessCheckResult_.find(id);
- if (search == profileIdToAccessCheckResult_.end()) {
+ unsigned int newNamespaceNumEntries = 0;
+ if (newNamespace) {
+ if (expectedNumEntriesPerNamespace_.size() == 0) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_INVALID_DATA,
- "Requested entry with unvalidated profile id"));
+ "No more populated name spaces left to go through"));
}
- int accessControlForProfile = search->second;
- if (accessControlForProfile == IIdentityCredentialStore::STATUS_OK) {
- accessControl = IIdentityCredentialStore::STATUS_OK;
- break;
- }
- accessControl = accessControlForProfile;
- }
- if (accessControl != IIdentityCredentialStore::STATUS_OK) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- int(accessControl), "Access control check failed"));
+ newNamespaceNumEntries = expectedNumEntriesPerNamespace_[0];
+ expectedNumEntriesPerNamespace_.erase(expectedNumEntriesPerNamespace_.begin());
}
- entryAdditionalData_ = entryCreateAdditionalData(nameSpace, name, accessControlProfileIds);
+ // Access control is enforced in the secure hardware.
+ //
+ // ... except for STATUS_NOT_IN_REQUEST_MESSAGE, that's handled above (TODO:
+ // consolidate).
+ //
+ AccessCheckResult res = hwProxy_->startRetrieveEntryValue(
+ nameSpace, name, newNamespaceNumEntries, entrySize, accessControlProfileIds);
+ switch (res) {
+ case AccessCheckResult::kOk:
+ /* Do nothing. */
+ break;
+ case AccessCheckResult::kFailed:
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED,
+ "Access control check failed (failed)"));
+ break;
+ case AccessCheckResult::kNoAccessControlProfiles:
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_NO_ACCESS_CONTROL_PROFILES,
+ "Access control check failed (no access control profiles)"));
+ break;
+ case AccessCheckResult::kUserAuthenticationFailed:
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_USER_AUTHENTICATION_FAILED,
+ "Access control check failed (user auth)"));
+ break;
+ case AccessCheckResult::kReaderAuthenticationFailed:
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_READER_AUTHENTICATION_FAILED,
+ "Access control check failed (reader auth)"));
+ break;
+ }
currentName_ = name;
+ currentAccessControlProfileIds_ = accessControlProfileIds;
entryRemainingBytes_ = entrySize;
entryValue_.resize(0);
@@ -711,8 +782,8 @@
ndk::ScopedAStatus IdentityCredential::retrieveEntryValue(const vector<uint8_t>& encryptedContent,
vector<uint8_t>* outContent) {
- optional<vector<uint8_t>> content =
- support::decryptAes128Gcm(storageKey_, encryptedContent, entryAdditionalData_);
+ optional<vector<uint8_t>> content = hwProxy_->retrieveEntryValue(
+ encryptedContent, currentNameSpace_, currentName_, currentAccessControlProfileIds_);
if (!content) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_INVALID_DATA, "Error decrypting data"));
@@ -777,28 +848,14 @@
optional<vector<uint8_t>> mac;
if (signingKeyBlob_.size() > 0 && sessionTranscript_.size() > 0 &&
readerPublicKey_.size() > 0) {
- vector<uint8_t> docTypeAsBlob(docType_.begin(), docType_.end());
- optional<vector<uint8_t>> signingKey =
- support::decryptAes128Gcm(storageKey_, signingKeyBlob_, docTypeAsBlob);
- if (!signingKey) {
+ optional<vector<uint8_t>> digestToBeMaced = hwProxy_->finishRetrieval();
+ if (!digestToBeMaced || digestToBeMaced.value().size() != 32) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_INVALID_DATA,
- "Error decrypting signingKeyBlob"));
+ "Error generating digestToBeMaced"));
}
-
- vector<uint8_t> sessionTranscriptBytes = cppbor::Semantic(24, sessionTranscript_).encode();
- optional<vector<uint8_t>> eMacKey =
- support::calcEMacKey(signingKey.value(), readerPublicKey_, sessionTranscriptBytes);
- if (!eMacKey) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error calculating EMacKey"));
- }
- mac = support::calcMac(sessionTranscript_, docType_, encodedDeviceNameSpaces,
- eMacKey.value());
- if (!mac) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error MACing data"));
- }
+ // Now construct COSE_Mac0 from the returned MAC...
+ mac = support::coseMacWithDigest(digestToBeMaced.value(), {} /* data */);
}
*outMac = mac.value_or(vector<uint8_t>({}));
@@ -808,56 +865,33 @@
ndk::ScopedAStatus IdentityCredential::generateSigningKeyPair(
vector<uint8_t>* outSigningKeyBlob, Certificate* outSigningKeyCertificate) {
- string serialDecimal = "1";
- string issuer = "Android Identity Credential Key";
- string subject = "Android Identity Credential Authentication Key";
- time_t validityNotBefore = time(nullptr);
- time_t validityNotAfter = validityNotBefore + 365 * 24 * 3600;
-
- optional<vector<uint8_t>> signingKeyPKCS8 = support::createEcKeyPair();
- if (!signingKeyPKCS8) {
+ time_t now = time(NULL);
+ optional<pair<vector<uint8_t>, vector<uint8_t>>> pair =
+ hwProxy_->generateSigningKeyPair(docType_, now);
+ if (!pair) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_FAILED, "Error creating signingKey"));
}
- optional<vector<uint8_t>> signingPublicKey =
- support::ecKeyPairGetPublicKey(signingKeyPKCS8.value());
- if (!signingPublicKey) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED,
- "Error getting public part of signingKey"));
- }
-
- optional<vector<uint8_t>> signingKey = support::ecKeyPairGetPrivateKey(signingKeyPKCS8.value());
- if (!signingKey) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED,
- "Error getting private part of signingKey"));
- }
-
- optional<vector<uint8_t>> certificate = support::ecPublicKeyGenerateCertificate(
- signingPublicKey.value(), credentialPrivKey_, serialDecimal, issuer, subject,
- validityNotBefore, validityNotAfter);
- if (!certificate) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error creating signingKey"));
- }
-
- optional<vector<uint8_t>> nonce = support::getRandom(12);
- if (!nonce) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error getting random"));
- }
- vector<uint8_t> docTypeAsBlob(docType_.begin(), docType_.end());
- optional<vector<uint8_t>> encryptedSigningKey = support::encryptAes128Gcm(
- storageKey_, nonce.value(), signingKey.value(), docTypeAsBlob);
- if (!encryptedSigningKey) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error encrypting signingKey"));
- }
- *outSigningKeyBlob = encryptedSigningKey.value();
*outSigningKeyCertificate = Certificate();
- outSigningKeyCertificate->encodedCertificate = certificate.value();
+ outSigningKeyCertificate->encodedCertificate = pair->first;
+
+ *outSigningKeyBlob = pair->second;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredential::updateCredential(
+ shared_ptr<IWritableIdentityCredential>* outWritableCredential) {
+ sp<SecureHardwareProvisioningProxy> hwProxy = hwProxyFactory_->createProvisioningProxy();
+ shared_ptr<WritableIdentityCredential> wc =
+ ndk::SharedRefBase::make<WritableIdentityCredential>(hwProxy, docType_,
+ testCredential_);
+ if (!wc->initializeForUpdate(encryptedCredentialKeys_)) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED,
+ "Error initializing WritableIdentityCredential for update"));
+ }
+ *outWritableCredential = wc;
return ndk::ScopedAStatus::ok();
}
diff --git a/identity/aidl/default/IdentityCredential.h b/identity/aidl/default/common/IdentityCredential.h
similarity index 75%
rename from identity/aidl/default/IdentityCredential.h
rename to identity/aidl/default/common/IdentityCredential.h
index a8a6409..9913b86 100644
--- a/identity/aidl/default/IdentityCredential.h
+++ b/identity/aidl/default/common/IdentityCredential.h
@@ -29,10 +29,15 @@
#include <cppbor/cppbor.h>
+#include "IdentityCredentialStore.h"
+#include "SecureHardwareProxy.h"
+
namespace aidl::android::hardware::identity {
using ::aidl::android::hardware::keymaster::HardwareAuthToken;
using ::aidl::android::hardware::keymaster::VerificationToken;
+using ::android::sp;
+using ::android::hardware::identity::SecureHardwarePresentationProxy;
using ::std::map;
using ::std::set;
using ::std::string;
@@ -40,10 +45,13 @@
class IdentityCredential : public BnIdentityCredential {
public:
- IdentityCredential(const vector<uint8_t>& credentialData)
- : credentialData_(credentialData),
+ IdentityCredential(sp<SecureHardwareProxyFactory> hwProxyFactory,
+ sp<SecureHardwarePresentationProxy> hwProxy,
+ const vector<uint8_t>& credentialData)
+ : hwProxyFactory_(hwProxyFactory),
+ hwProxy_(hwProxy),
+ credentialData_(credentialData),
numStartRetrievalCalls_(0),
- authChallenge_(0),
expectedDeviceNameSpacesSize_(0) {}
// Parses and decrypts credentialData_, return a status code from
@@ -52,6 +60,11 @@
// Methods from IIdentityCredential follow.
ndk::ScopedAStatus deleteCredential(vector<uint8_t>* outProofOfDeletionSignature) override;
+ ndk::ScopedAStatus deleteCredentialWithChallenge(
+ const vector<uint8_t>& challenge,
+ vector<uint8_t>* outProofOfDeletionSignature) override;
+ ndk::ScopedAStatus proveOwnership(const vector<uint8_t>& challenge,
+ vector<uint8_t>* outProofOfOwnershipSignature) override;
ndk::ScopedAStatus createEphemeralKeyPair(vector<uint8_t>* outKeyPair) override;
ndk::ScopedAStatus setReaderEphemeralPublicKey(const vector<uint8_t>& publicKey) override;
ndk::ScopedAStatus createAuthChallenge(int64_t* outChallenge) override;
@@ -73,16 +86,24 @@
ndk::ScopedAStatus generateSigningKeyPair(vector<uint8_t>* outSigningKeyBlob,
Certificate* outSigningKeyCertificate) override;
+ ndk::ScopedAStatus updateCredential(
+ shared_ptr<IWritableIdentityCredential>* outWritableCredential) override;
+
private:
+ ndk::ScopedAStatus deleteCredentialCommon(const vector<uint8_t>& challenge,
+ bool includeChallenge,
+ vector<uint8_t>* outProofOfDeletionSignature);
+
// Set by constructor
+ sp<SecureHardwareProxyFactory> hwProxyFactory_;
+ sp<SecureHardwarePresentationProxy> hwProxy_;
vector<uint8_t> credentialData_;
int numStartRetrievalCalls_;
// Set by initialize()
string docType_;
bool testCredential_;
- vector<uint8_t> storageKey_;
- vector<uint8_t> credentialPrivKey_;
+ vector<uint8_t> encryptedCredentialKeys_;
// Set by createEphemeralKeyPair()
vector<uint8_t> ephemeralPublicKey_;
@@ -90,9 +111,6 @@
// Set by setReaderEphemeralPublicKey()
vector<uint8_t> readerPublicKey_;
- // Set by createAuthChallenge()
- uint64_t authChallenge_;
-
// Set by setRequestedNamespaces()
vector<RequestNamespace> requestNamespaces_;
@@ -100,7 +118,6 @@
VerificationToken verificationToken_;
// Set at startRetrieval() time.
- map<int32_t, int> profileIdToAccessCheckResult_;
vector<uint8_t> signingKeyBlob_;
vector<uint8_t> sessionTranscript_;
vector<uint8_t> itemsRequest_;
@@ -111,15 +128,16 @@
// Calculated at startRetrieval() time.
size_t expectedDeviceNameSpacesSize_;
+ vector<unsigned int> expectedNumEntriesPerNamespace_;
// Set at startRetrieveEntryValue() time.
string currentNameSpace_;
string currentName_;
+ vector<int32_t> currentAccessControlProfileIds_;
size_t entryRemainingBytes_;
vector<uint8_t> entryValue_;
- vector<uint8_t> entryAdditionalData_;
- size_t calcDeviceNameSpacesSize();
+ void calcDeviceNameSpacesSize(uint32_t accessControlProfileMask);
};
} // namespace aidl::android::hardware::identity
diff --git a/identity/aidl/default/IdentityCredentialStore.cpp b/identity/aidl/default/common/IdentityCredentialStore.cpp
similarity index 89%
rename from identity/aidl/default/IdentityCredentialStore.cpp
rename to identity/aidl/default/common/IdentityCredentialStore.cpp
index 30dc6f3..e6b5466 100644
--- a/identity/aidl/default/IdentityCredentialStore.cpp
+++ b/identity/aidl/default/common/IdentityCredentialStore.cpp
@@ -39,8 +39,9 @@
ndk::ScopedAStatus IdentityCredentialStore::createCredential(
const string& docType, bool testCredential,
shared_ptr<IWritableIdentityCredential>* outWritableCredential) {
+ sp<SecureHardwareProvisioningProxy> hwProxy = hwProxyFactory_->createProvisioningProxy();
shared_ptr<WritableIdentityCredential> wc =
- ndk::SharedRefBase::make<WritableIdentityCredential>(docType, testCredential);
+ ndk::SharedRefBase::make<WritableIdentityCredential>(hwProxy, docType, testCredential);
if (!wc->initialize()) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_FAILED,
@@ -60,8 +61,9 @@
"Unsupported cipher suite"));
}
+ sp<SecureHardwarePresentationProxy> hwProxy = hwProxyFactory_->createPresentationProxy();
shared_ptr<IdentityCredential> credential =
- ndk::SharedRefBase::make<IdentityCredential>(credentialData);
+ ndk::SharedRefBase::make<IdentityCredential>(hwProxyFactory_, hwProxy, credentialData);
auto ret = credential->initialize();
if (ret != IIdentityCredentialStore::STATUS_OK) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
diff --git a/identity/aidl/default/IdentityCredentialStore.h b/identity/aidl/default/common/IdentityCredentialStore.h
similarity index 85%
rename from identity/aidl/default/IdentityCredentialStore.h
rename to identity/aidl/default/common/IdentityCredentialStore.h
index 4f3a421..d35e632 100644
--- a/identity/aidl/default/IdentityCredentialStore.h
+++ b/identity/aidl/default/common/IdentityCredentialStore.h
@@ -19,15 +19,20 @@
#include <aidl/android/hardware/identity/BnIdentityCredentialStore.h>
+#include "SecureHardwareProxy.h"
+
namespace aidl::android::hardware::identity {
+using ::android::sp;
+using ::android::hardware::identity::SecureHardwareProxyFactory;
using ::std::shared_ptr;
using ::std::string;
using ::std::vector;
class IdentityCredentialStore : public BnIdentityCredentialStore {
public:
- IdentityCredentialStore() {}
+ IdentityCredentialStore(sp<SecureHardwareProxyFactory> hwProxyFactory)
+ : hwProxyFactory_(hwProxyFactory) {}
// The GCM chunk size used by this implementation is 64 KiB.
static constexpr size_t kGcmChunkSize = 64 * 1024;
@@ -41,6 +46,9 @@
ndk::ScopedAStatus getCredential(CipherSuite cipherSuite, const vector<uint8_t>& credentialData,
shared_ptr<IIdentityCredential>* outCredential) override;
+
+ private:
+ sp<SecureHardwareProxyFactory> hwProxyFactory_;
};
} // namespace aidl::android::hardware::identity
diff --git a/identity/aidl/default/common/SecureHardwareProxy.h b/identity/aidl/default/common/SecureHardwareProxy.h
new file mode 100644
index 0000000..a1ed1ef
--- /dev/null
+++ b/identity/aidl/default/common/SecureHardwareProxy.h
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_SECUREHARDWAREPROXY_H
+#define ANDROID_HARDWARE_IDENTITY_SECUREHARDWAREPROXY_H
+
+#include <utils/RefBase.h>
+#include <optional>
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace android::hardware::identity {
+
+using ::android::RefBase;
+using ::std::optional;
+using ::std::pair;
+using ::std::string;
+using ::std::vector;
+
+// These classes are used to communicate with Secure Hardware. They mimic the
+// API in libEmbeddedIC 1:1 (except for using C++ types) as each call is intended
+// to be forwarded to the Secure Hardware.
+//
+// Instances are instantiated when a provisioning or presentation session
+// starts. When the session is complete, the shutdown() method is called.
+//
+
+// Forward declare.
+//
+class SecureHardwareProvisioningProxy;
+class SecureHardwarePresentationProxy;
+
+// This is a class used to create proxies.
+//
+class SecureHardwareProxyFactory : public RefBase {
+ public:
+ SecureHardwareProxyFactory() {}
+ virtual ~SecureHardwareProxyFactory() {}
+
+ virtual sp<SecureHardwareProvisioningProxy> createProvisioningProxy() = 0;
+ virtual sp<SecureHardwarePresentationProxy> createPresentationProxy() = 0;
+};
+
+// The proxy used for provisioning.
+//
+class SecureHardwareProvisioningProxy : public RefBase {
+ public:
+ SecureHardwareProvisioningProxy() {}
+ virtual ~SecureHardwareProvisioningProxy() {}
+
+ virtual bool initialize(bool testCredential) = 0;
+
+ virtual bool initializeForUpdate(bool testCredential, string docType,
+ vector<uint8_t> encryptedCredentialKeys) = 0;
+
+ // Returns public key certificate chain with attestation.
+ //
+ // This must return an entire certificate chain and its implementation must
+ // be coordinated with the implementation of eicOpsCreateCredentialKey() on
+ // the TA side (which may return just a single certificate or the entire
+ // chain).
+ virtual optional<vector<uint8_t>> createCredentialKey(const vector<uint8_t>& challenge,
+ const vector<uint8_t>& applicationId) = 0;
+
+ virtual bool startPersonalization(int accessControlProfileCount, vector<int> entryCounts,
+ const string& docType,
+ size_t expectedProofOfProvisioningSize) = 0;
+
+ // Returns MAC (28 bytes).
+ virtual optional<vector<uint8_t>> addAccessControlProfile(
+ int id, const vector<uint8_t>& readerCertificate, bool userAuthenticationRequired,
+ uint64_t timeoutMillis, uint64_t secureUserId) = 0;
+
+ virtual bool beginAddEntry(const vector<int>& accessControlProfileIds, const string& nameSpace,
+ const string& name, uint64_t entrySize) = 0;
+
+ // Returns encryptedContent.
+ virtual optional<vector<uint8_t>> addEntryValue(const vector<int>& accessControlProfileIds,
+ const string& nameSpace, const string& name,
+ const vector<uint8_t>& content) = 0;
+
+ // Returns signatureOfToBeSigned (EIC_ECDSA_P256_SIGNATURE_SIZE bytes).
+ virtual optional<vector<uint8_t>> finishAddingEntries() = 0;
+
+ // Returns encryptedCredentialKeys (80 bytes).
+ virtual optional<vector<uint8_t>> finishGetCredentialData(const string& docType) = 0;
+
+ virtual bool shutdown() = 0;
+};
+
+enum AccessCheckResult {
+ kOk,
+ kFailed,
+ kNoAccessControlProfiles,
+ kUserAuthenticationFailed,
+ kReaderAuthenticationFailed,
+};
+
+// The proxy used for presentation.
+//
+class SecureHardwarePresentationProxy : public RefBase {
+ public:
+ SecureHardwarePresentationProxy() {}
+ virtual ~SecureHardwarePresentationProxy() {}
+
+ virtual bool initialize(bool testCredential, string docType,
+ vector<uint8_t> encryptedCredentialKeys) = 0;
+
+ // Returns publicKeyCert (1st component) and signingKeyBlob (2nd component)
+ virtual optional<pair<vector<uint8_t>, vector<uint8_t>>> generateSigningKeyPair(string docType,
+ time_t now) = 0;
+
+ // Returns private key
+ virtual optional<vector<uint8_t>> createEphemeralKeyPair() = 0;
+
+ virtual optional<uint64_t> createAuthChallenge() = 0;
+
+ virtual bool startRetrieveEntries() = 0;
+
+ virtual bool setAuthToken(uint64_t challenge, uint64_t secureUserId, uint64_t authenticatorId,
+ int hardwareAuthenticatorType, uint64_t timeStamp,
+ const vector<uint8_t>& mac, uint64_t verificationTokenChallenge,
+ uint64_t verificationTokenTimestamp,
+ int verificationTokenSecurityLevel,
+ const vector<uint8_t>& verificationTokenMac) = 0;
+
+ virtual bool pushReaderCert(const vector<uint8_t>& certX509) = 0;
+
+ virtual optional<bool> validateAccessControlProfile(int id,
+ const vector<uint8_t>& readerCertificate,
+ bool userAuthenticationRequired,
+ int timeoutMillis, uint64_t secureUserId,
+ const vector<uint8_t>& mac) = 0;
+
+ virtual bool validateRequestMessage(const vector<uint8_t>& sessionTranscript,
+ const vector<uint8_t>& requestMessage, int coseSignAlg,
+ const vector<uint8_t>& readerSignatureOfToBeSigned) = 0;
+
+ virtual bool calcMacKey(const vector<uint8_t>& sessionTranscript,
+ const vector<uint8_t>& readerEphemeralPublicKey,
+ const vector<uint8_t>& signingKeyBlob, const string& docType,
+ unsigned int numNamespacesWithValues,
+ size_t expectedProofOfProvisioningSize) = 0;
+
+ virtual AccessCheckResult startRetrieveEntryValue(
+ const string& nameSpace, const string& name, unsigned int newNamespaceNumEntries,
+ int32_t entrySize, const vector<int32_t>& accessControlProfileIds) = 0;
+
+ virtual optional<vector<uint8_t>> retrieveEntryValue(
+ const vector<uint8_t>& encryptedContent, const string& nameSpace, const string& name,
+ const vector<int32_t>& accessControlProfileIds) = 0;
+
+ virtual optional<vector<uint8_t>> finishRetrieval();
+
+ virtual optional<vector<uint8_t>> deleteCredential(const string& docType,
+ const vector<uint8_t>& challenge,
+ bool includeChallenge,
+ size_t proofOfDeletionCborSize) = 0;
+
+ virtual optional<vector<uint8_t>> proveOwnership(const string& docType, bool testCredential,
+ const vector<uint8_t>& challenge,
+ size_t proofOfOwnershipCborSize) = 0;
+
+ virtual bool shutdown() = 0;
+};
+
+} // namespace android::hardware::identity
+
+#endif // ANDROID_HARDWARE_IDENTITY_SECUREHARDWAREPROXY_H
diff --git a/identity/aidl/default/WritableIdentityCredential.cpp b/identity/aidl/default/common/WritableIdentityCredential.cpp
similarity index 70%
rename from identity/aidl/default/WritableIdentityCredential.cpp
rename to identity/aidl/default/common/WritableIdentityCredential.cpp
index 141b4de..2d897c7 100644
--- a/identity/aidl/default/WritableIdentityCredential.cpp
+++ b/identity/aidl/default/common/WritableIdentityCredential.cpp
@@ -17,7 +17,6 @@
#define LOG_TAG "WritableIdentityCredential"
#include "WritableIdentityCredential.h"
-#include "IdentityCredentialStore.h"
#include <android/hardware/identity/support/IdentityCredentialSupport.h>
@@ -30,8 +29,8 @@
#include <utility>
#include "IdentityCredentialStore.h"
-#include "Util.h"
-#include "WritableIdentityCredential.h"
+
+#include "FakeSecureHardwareProxy.h"
namespace aidl::android::hardware::identity {
@@ -40,74 +39,68 @@
using namespace ::android::hardware::identity;
bool WritableIdentityCredential::initialize() {
- optional<vector<uint8_t>> random = support::getRandom(16);
- if (!random) {
- LOG(ERROR) << "Error creating storageKey";
+ if (!hwProxy_->initialize(testCredential_)) {
+ LOG(ERROR) << "hwProxy->initialize() failed";
return false;
}
- storageKey_ = random.value();
startPersonalizationCalled_ = false;
firstEntry_ = true;
return true;
}
-// This function generates the attestation certificate using the passed in
-// |attestationApplicationId| and |attestationChallenge|. It will generate an
-// attestation certificate with current time and expires one year from now. The
-// certificate shall contain all values as specified in hal.
+// Used when updating a credential. Returns false on failure.
+bool WritableIdentityCredential::initializeForUpdate(
+ const vector<uint8_t>& encryptedCredentialKeys) {
+ if (!hwProxy_->initializeForUpdate(testCredential_, docType_, encryptedCredentialKeys)) {
+ LOG(ERROR) << "hwProxy->initializeForUpdate() failed";
+ return false;
+ }
+ startPersonalizationCalled_ = false;
+ firstEntry_ = true;
+
+ return true;
+}
+
+WritableIdentityCredential::~WritableIdentityCredential() {}
+
ndk::ScopedAStatus WritableIdentityCredential::getAttestationCertificate(
- const vector<uint8_t>& attestationApplicationId, //
- const vector<uint8_t>& attestationChallenge, //
- vector<Certificate>* outCertificateChain) {
- if (!credentialPrivKey_.empty() || !credentialPubKey_.empty() || !certificateChain_.empty()) {
+ const vector<uint8_t>& attestationApplicationId,
+ const vector<uint8_t>& attestationChallenge, vector<Certificate>* outCertificateChain) {
+ if (getAttestationCertificateAlreadyCalled_) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_FAILED,
"Error attestation certificate previously generated"));
}
+ getAttestationCertificateAlreadyCalled_ = true;
+
if (attestationChallenge.empty()) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_INVALID_DATA, "Challenge can not be empty"));
}
- vector<uint8_t> challenge(attestationChallenge.begin(), attestationChallenge.end());
- vector<uint8_t> appId(attestationApplicationId.begin(), attestationApplicationId.end());
-
- optional<std::pair<vector<uint8_t>, vector<vector<uint8_t>>>> keyAttestationPair =
- support::createEcKeyPairAndAttestation(challenge, appId, testCredential_);
- if (!keyAttestationPair) {
- LOG(ERROR) << "Error creating credentialKey and attestation";
+ optional<vector<uint8_t>> certChain =
+ hwProxy_->createCredentialKey(attestationChallenge, attestationApplicationId);
+ if (!certChain) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_FAILED,
- "Error creating credentialKey and attestation"));
+ "Error generating attestation certificate chain"));
}
- vector<uint8_t> keyPair = keyAttestationPair.value().first;
- certificateChain_ = keyAttestationPair.value().second;
-
- optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair);
- if (!pubKey) {
+ optional<vector<vector<uint8_t>>> certs = support::certificateChainSplit(certChain.value());
+ if (!certs) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_FAILED,
- "Error getting public part of credentialKey"));
+ "Error splitting chain into separate certificates"));
}
- credentialPubKey_ = pubKey.value();
- optional<vector<uint8_t>> privKey = support::ecKeyPairGetPrivateKey(keyPair);
- if (!privKey) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED,
- "Error getting private part of credentialKey"));
- }
- credentialPrivKey_ = privKey.value();
-
- // convert from vector<vector<uint8_t>>> to vector<Certificate>*
*outCertificateChain = vector<Certificate>();
- for (const vector<uint8_t>& cert : certificateChain_) {
+ for (const vector<uint8_t>& cert : certs.value()) {
Certificate c = Certificate();
c.encodedCertificate = cert;
outCertificateChain->push_back(std::move(c));
}
+
return ndk::ScopedAStatus::ok();
}
@@ -123,8 +116,8 @@
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_FAILED, "startPersonalization called already"));
}
-
startPersonalizationCalled_ = true;
+
numAccessControlProfileRemaining_ = accessControlProfileCount;
remainingEntryCounts_ = entryCounts;
entryNameSpace_ = "";
@@ -133,6 +126,12 @@
signedDataNamespaces_ = cppbor::Map();
signedDataCurrentNamespace_ = cppbor::Array();
+ if (!hwProxy_->startPersonalization(accessControlProfileCount, entryCounts, docType_,
+ expectedProofOfProvisioningSize_)) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "eicStartPersonalization"));
+ }
+
return ndk::ScopedAStatus::ok();
}
@@ -140,8 +139,6 @@
int32_t id, const Certificate& readerCertificate, bool userAuthenticationRequired,
int64_t timeoutMillis, int64_t secureUserId,
SecureAccessControlProfile* outSecureAccessControlProfile) {
- SecureAccessControlProfile profile;
-
if (numAccessControlProfileRemaining_ == 0) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_INVALID_DATA,
@@ -169,25 +166,21 @@
"userAuthenticationRequired is false but timeout is non-zero"));
}
- // If |userAuthenticationRequired| is true, then |secureUserId| must be non-zero.
- if (userAuthenticationRequired && secureUserId == 0) {
+ optional<vector<uint8_t>> mac = hwProxy_->addAccessControlProfile(
+ id, readerCertificate.encodedCertificate, userAuthenticationRequired, timeoutMillis,
+ secureUserId);
+ if (!mac) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_INVALID_DATA,
- "userAuthenticationRequired is true but secureUserId is zero"));
+ IIdentityCredentialStore::STATUS_FAILED, "eicAddAccessControlProfile"));
}
+ SecureAccessControlProfile profile;
profile.id = id;
profile.readerCertificate = readerCertificate;
profile.userAuthenticationRequired = userAuthenticationRequired;
profile.timeoutMillis = timeoutMillis;
profile.secureUserId = secureUserId;
- optional<vector<uint8_t>> mac = secureAccessControlProfileCalcMac(profile, storageKey_);
- if (!mac) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error calculating MAC for profile"));
- }
profile.mac = mac.value();
-
cppbor::Map profileMap;
profileMap.add("id", profile.id);
if (profile.readerCertificate.encodedCertificate.size() > 0) {
@@ -261,14 +254,18 @@
remainingEntryCounts_[0] -= 1;
}
- entryAdditionalData_ = entryCreateAdditionalData(nameSpace, name, accessControlProfileIds);
-
entryRemainingBytes_ = entrySize;
entryNameSpace_ = nameSpace;
entryName_ = name;
entryAccessControlProfileIds_ = accessControlProfileIds;
entryBytes_.resize(0);
// LOG(INFO) << "name=" << name << " entrySize=" << entrySize;
+
+ if (!hwProxy_->beginAddEntry(accessControlProfileIds, nameSpace, name, entrySize)) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "eicBeginAddEntry"));
+ }
+
return ndk::ScopedAStatus::ok();
}
@@ -297,16 +294,11 @@
}
}
- optional<vector<uint8_t>> nonce = support::getRandom(12);
- if (!nonce) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error getting nonce"));
- }
- optional<vector<uint8_t>> encryptedContent =
- support::encryptAes128Gcm(storageKey_, nonce.value(), content, entryAdditionalData_);
+ optional<vector<uint8_t>> encryptedContent = hwProxy_->addEntryValue(
+ entryAccessControlProfileIds_, entryNameSpace_, entryName_, content);
if (!encryptedContent) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error encrypting content"));
+ IIdentityCredentialStore::STATUS_FAILED, "eicAddEntryValue"));
}
if (entryRemainingBytes_ == 0) {
@@ -332,50 +324,6 @@
return ndk::ScopedAStatus::ok();
}
-// Writes CBOR-encoded structure to |credentialKeys| containing |storageKey| and
-// |credentialPrivKey|.
-static bool generateCredentialKeys(const vector<uint8_t>& storageKey,
- const vector<uint8_t>& credentialPrivKey,
- vector<uint8_t>& credentialKeys) {
- if (storageKey.size() != 16) {
- LOG(ERROR) << "Size of storageKey is not 16";
- return false;
- }
-
- cppbor::Array array;
- array.add(cppbor::Bstr(storageKey));
- array.add(cppbor::Bstr(credentialPrivKey));
- credentialKeys = array.encode();
- return true;
-}
-
-// Writes CBOR-encoded structure to |credentialData| containing |docType|,
-// |testCredential| and |credentialKeys|. The latter element will be stored in
-// encrypted form, using |hardwareBoundKey| as the encryption key.
-bool generateCredentialData(const vector<uint8_t>& hardwareBoundKey, const string& docType,
- bool testCredential, const vector<uint8_t>& credentialKeys,
- vector<uint8_t>& credentialData) {
- optional<vector<uint8_t>> nonce = support::getRandom(12);
- if (!nonce) {
- LOG(ERROR) << "Error getting random";
- return false;
- }
- vector<uint8_t> docTypeAsVec(docType.begin(), docType.end());
- optional<vector<uint8_t>> credentialBlob = support::encryptAes128Gcm(
- hardwareBoundKey, nonce.value(), credentialKeys, docTypeAsVec);
- if (!credentialBlob) {
- LOG(ERROR) << "Error encrypting CredentialKeys blob";
- return false;
- }
-
- cppbor::Array array;
- array.add(docType);
- array.add(testCredential);
- array.add(cppbor::Bstr(credentialBlob.value()));
- credentialData = array.encode();
- return true;
-}
-
ndk::ScopedAStatus WritableIdentityCredential::finishAddingEntries(
vector<uint8_t>* outCredentialData, vector<uint8_t>* outProofOfProvisioningSignature) {
if (numAccessControlProfileRemaining_ != 0) {
@@ -411,31 +359,37 @@
.c_str()));
}
- optional<vector<uint8_t>> signature = support::coseSignEcDsa(credentialPrivKey_,
- encodedCbor, // payload
- {}, // additionalData
- {}); // certificateChain
+ optional<vector<uint8_t>> signatureOfToBeSigned = hwProxy_->finishAddingEntries();
+ if (!signatureOfToBeSigned) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "eicFinishAddingEntries"));
+ }
+
+ optional<vector<uint8_t>> signature =
+ support::coseSignEcDsaWithSignature(signatureOfToBeSigned.value(),
+ encodedCbor, // data
+ {}); // certificateChain
if (!signature) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_FAILED, "Error signing data"));
}
- vector<uint8_t> credentialKeys;
- if (!generateCredentialKeys(storageKey_, credentialPrivKey_, credentialKeys)) {
+ optional<vector<uint8_t>> encryptedCredentialKeys = hwProxy_->finishGetCredentialData(docType_);
+ if (!encryptedCredentialKeys) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error generating CredentialKeys"));
+ IIdentityCredentialStore::STATUS_FAILED,
+ "Error generating encrypted CredentialKeys"));
}
-
- vector<uint8_t> credentialData;
- if (!generateCredentialData(
- testCredential_ ? support::getTestHardwareBoundKey() : getHardwareBoundKey(),
- docType_, testCredential_, credentialKeys, credentialData)) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error generating CredentialData"));
- }
+ cppbor::Array array;
+ array.add(docType_);
+ array.add(testCredential_);
+ array.add(encryptedCredentialKeys.value());
+ vector<uint8_t> credentialData = array.encode();
*outCredentialData = credentialData;
*outProofOfProvisioningSignature = signature.value();
+ hwProxy_->shutdown();
+
return ndk::ScopedAStatus::ok();
}
diff --git a/identity/aidl/default/WritableIdentityCredential.h b/identity/aidl/default/common/WritableIdentityCredential.h
similarity index 78%
rename from identity/aidl/default/WritableIdentityCredential.h
rename to identity/aidl/default/common/WritableIdentityCredential.h
index 5645852..36ad430 100644
--- a/identity/aidl/default/WritableIdentityCredential.h
+++ b/identity/aidl/default/common/WritableIdentityCredential.h
@@ -23,21 +23,35 @@
#include <cppbor.h>
#include <set>
+#include "IdentityCredentialStore.h"
+#include "SecureHardwareProxy.h"
+
namespace aidl::android::hardware::identity {
+using ::android::sp;
+using ::android::hardware::identity::SecureHardwareProvisioningProxy;
using ::std::set;
using ::std::string;
using ::std::vector;
class WritableIdentityCredential : public BnWritableIdentityCredential {
public:
- WritableIdentityCredential(const string& docType, bool testCredential)
- : docType_(docType), testCredential_(testCredential) {}
+ // For a new credential, call initialize() right after construction.
+ //
+ // For an updated credential, call initializeForUpdate() right after construction.
+ //
+ WritableIdentityCredential(sp<SecureHardwareProvisioningProxy> hwProxy, const string& docType,
+ bool testCredential)
+ : hwProxy_(hwProxy), docType_(docType), testCredential_(testCredential) {}
- // Creates the Credential Key. Returns false on failure. Must be called
- // right after construction.
+ ~WritableIdentityCredential();
+
+ // Creates the Credential Key. Returns false on failure.
bool initialize();
+ // Used when updating a credential. Returns false on failure.
+ bool initializeForUpdate(const vector<uint8_t>& encryptedCredentialKeys);
+
// Methods from IWritableIdentityCredential follow.
ndk::ScopedAStatus getAttestationCertificate(const vector<uint8_t>& attestationApplicationId,
const vector<uint8_t>& attestationChallenge,
@@ -57,7 +71,6 @@
ndk::ScopedAStatus beginAddEntry(const vector<int32_t>& accessControlProfileIds,
const string& nameSpace, const string& name,
int32_t entrySize) override;
-
ndk::ScopedAStatus addEntryValue(const vector<uint8_t>& content,
vector<uint8_t>* outEncryptedContent) override;
@@ -66,18 +79,17 @@
vector<uint8_t>* outProofOfProvisioningSignature) override;
private:
+ // Set by constructor.
+ sp<SecureHardwareProvisioningProxy> hwProxy_;
string docType_;
bool testCredential_;
// This is set in initialize().
- vector<uint8_t> storageKey_;
bool startPersonalizationCalled_;
bool firstEntry_;
- // These are set in getAttestationCertificate().
- vector<uint8_t> credentialPrivKey_;
- vector<uint8_t> credentialPubKey_;
- vector<vector<uint8_t>> certificateChain_;
+ // This is set in getAttestationCertificate().
+ bool getAttestationCertificateAlreadyCalled_ = false;
// These fields are initialized during startPersonalization()
size_t numAccessControlProfileRemaining_;
@@ -92,7 +104,6 @@
// These fields are initialized during beginAddEntry()
size_t entryRemainingBytes_;
- vector<uint8_t> entryAdditionalData_;
string entryNameSpace_;
string entryName_;
vector<int32_t> entryAccessControlProfileIds_;
diff --git a/identity/aidl/default/identity-default.xml b/identity/aidl/default/identity-default.xml
index a47d354..a074250 100644
--- a/identity/aidl/default/identity-default.xml
+++ b/identity/aidl/default/identity-default.xml
@@ -1,6 +1,7 @@
<manifest version="1.0" type="device">
<hal format="aidl">
<name>android.hardware.identity</name>
+ <version>3</version>
<interface>
<name>IIdentityCredentialStore</name>
<instance>default</instance>
diff --git a/identity/aidl/default/libeic/EicCbor.c b/identity/aidl/default/libeic/EicCbor.c
new file mode 100644
index 0000000..fe131eb
--- /dev/null
+++ b/identity/aidl/default/libeic/EicCbor.c
@@ -0,0 +1,245 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "EicCbor.h"
+
+void eicCborInit(EicCbor* cbor, uint8_t* buffer, size_t bufferSize) {
+ eicMemSet(cbor, '\0', sizeof(EicCbor));
+ cbor->size = 0;
+ cbor->bufferSize = bufferSize;
+ cbor->buffer = buffer;
+ cbor->digestType = EIC_CBOR_DIGEST_TYPE_SHA256;
+ eicOpsSha256Init(&cbor->digester.sha256);
+}
+
+void eicCborInitHmacSha256(EicCbor* cbor, uint8_t* buffer, size_t bufferSize,
+ const uint8_t* hmacKey, size_t hmacKeySize) {
+ eicMemSet(cbor, '\0', sizeof(EicCbor));
+ cbor->size = 0;
+ cbor->bufferSize = bufferSize;
+ cbor->buffer = buffer;
+ cbor->digestType = EIC_CBOR_DIGEST_TYPE_HMAC_SHA256;
+ eicOpsHmacSha256Init(&cbor->digester.hmacSha256, hmacKey, hmacKeySize);
+}
+
+void eicCborEnableSecondaryDigesterSha256(EicCbor* cbor, EicSha256Ctx* sha256) {
+ cbor->secondaryDigesterSha256 = sha256;
+}
+
+void eicCborFinal(EicCbor* cbor, uint8_t digest[EIC_SHA256_DIGEST_SIZE]) {
+ switch (cbor->digestType) {
+ case EIC_CBOR_DIGEST_TYPE_SHA256:
+ eicOpsSha256Final(&cbor->digester.sha256, digest);
+ break;
+ case EIC_CBOR_DIGEST_TYPE_HMAC_SHA256:
+ eicOpsHmacSha256Final(&cbor->digester.hmacSha256, digest);
+ break;
+ }
+}
+
+void eicCborAppend(EicCbor* cbor, const uint8_t* data, size_t size) {
+ switch (cbor->digestType) {
+ case EIC_CBOR_DIGEST_TYPE_SHA256:
+ eicOpsSha256Update(&cbor->digester.sha256, data, size);
+ break;
+ case EIC_CBOR_DIGEST_TYPE_HMAC_SHA256:
+ eicOpsHmacSha256Update(&cbor->digester.hmacSha256, data, size);
+ break;
+ }
+ if (cbor->secondaryDigesterSha256 != NULL) {
+ eicOpsSha256Update(cbor->secondaryDigesterSha256, data, size);
+ }
+
+ if (cbor->size >= cbor->bufferSize) {
+ cbor->size += size;
+ return;
+ }
+
+ size_t numBytesLeft = cbor->bufferSize - cbor->size;
+ size_t numBytesToCopy = size;
+ if (numBytesToCopy > numBytesLeft) {
+ numBytesToCopy = numBytesLeft;
+ }
+ eicMemCpy(cbor->buffer + cbor->size, data, numBytesToCopy);
+
+ cbor->size += size;
+}
+
+size_t eicCborAdditionalLengthBytesFor(size_t size) {
+ if (size < 24) {
+ return 0;
+ } else if (size <= 0xff) {
+ return 1;
+ } else if (size <= 0xffff) {
+ return 2;
+ } else if (size <= 0xffffffff) {
+ return 4;
+ }
+ return 8;
+}
+
+void eicCborBegin(EicCbor* cbor, int majorType, size_t size) {
+ uint8_t data[9];
+
+ if (size < 24) {
+ data[0] = (majorType << 5) | size;
+ eicCborAppend(cbor, data, 1);
+ } else if (size <= 0xff) {
+ data[0] = (majorType << 5) | 24;
+ data[1] = size;
+ eicCborAppend(cbor, data, 2);
+ } else if (size <= 0xffff) {
+ data[0] = (majorType << 5) | 25;
+ data[1] = size >> 8;
+ data[2] = size & 0xff;
+ eicCborAppend(cbor, data, 3);
+ } else if (size <= 0xffffffff) {
+ data[0] = (majorType << 5) | 26;
+ data[1] = (size >> 24) & 0xff;
+ data[2] = (size >> 16) & 0xff;
+ data[3] = (size >> 8) & 0xff;
+ data[4] = size & 0xff;
+ eicCborAppend(cbor, data, 5);
+ } else {
+ data[0] = (majorType << 5) | 24;
+ data[1] = (((uint64_t)size) >> 56) & 0xff;
+ data[2] = (((uint64_t)size) >> 48) & 0xff;
+ data[3] = (((uint64_t)size) >> 40) & 0xff;
+ data[4] = (((uint64_t)size) >> 32) & 0xff;
+ data[5] = (((uint64_t)size) >> 24) & 0xff;
+ data[6] = (((uint64_t)size) >> 16) & 0xff;
+ data[7] = (((uint64_t)size) >> 8) & 0xff;
+ data[8] = ((uint64_t)size) & 0xff;
+ eicCborAppend(cbor, data, 9);
+ }
+}
+
+void eicCborAppendByteString(EicCbor* cbor, const uint8_t* data, size_t dataSize) {
+ eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, dataSize);
+ eicCborAppend(cbor, data, dataSize);
+}
+
+void eicCborAppendString(EicCbor* cbor, const char* str) {
+ size_t length = eicStrLen(str);
+ eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_STRING, length);
+ eicCborAppend(cbor, (const uint8_t*)str, length);
+}
+
+void eicCborAppendSimple(EicCbor* cbor, uint8_t simpleValue) {
+ eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_SIMPLE, simpleValue);
+}
+
+void eicCborAppendBool(EicCbor* cbor, bool value) {
+ uint8_t simpleValue = value ? EIC_CBOR_SIMPLE_VALUE_TRUE : EIC_CBOR_SIMPLE_VALUE_FALSE;
+ eicCborAppendSimple(cbor, simpleValue);
+}
+
+void eicCborAppendSemantic(EicCbor* cbor, uint64_t value) {
+ size_t encoded = value;
+ eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_SEMANTIC, encoded);
+}
+
+void eicCborAppendUnsigned(EicCbor* cbor, uint64_t value) {
+ size_t encoded = value;
+ eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_UNSIGNED, encoded);
+}
+
+void eicCborAppendNumber(EicCbor* cbor, int64_t value) {
+ if (value < 0) {
+ size_t encoded = -1 - value;
+ eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_NEGATIVE, encoded);
+ } else {
+ eicCborAppendUnsigned(cbor, value);
+ }
+}
+
+void eicCborAppendArray(EicCbor* cbor, size_t numElements) {
+ eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_ARRAY, numElements);
+}
+
+void eicCborAppendMap(EicCbor* cbor, size_t numPairs) {
+ eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_MAP, numPairs);
+}
+
+bool eicCborCalcAccessControl(EicCbor* cborBuilder, int id, const uint8_t* readerCertificate,
+ size_t readerCertificateSize, bool userAuthenticationRequired,
+ uint64_t timeoutMillis, uint64_t secureUserId) {
+ size_t numPairs = 1;
+ if (readerCertificateSize > 0) {
+ numPairs += 1;
+ }
+ if (userAuthenticationRequired) {
+ numPairs += 2;
+ if (secureUserId > 0) {
+ numPairs += 1;
+ }
+ }
+ eicCborAppendMap(cborBuilder, numPairs);
+ eicCborAppendString(cborBuilder, "id");
+ eicCborAppendUnsigned(cborBuilder, id);
+ if (readerCertificateSize > 0) {
+ eicCborAppendString(cborBuilder, "readerCertificate");
+ eicCborAppendByteString(cborBuilder, readerCertificate, readerCertificateSize);
+ }
+ if (userAuthenticationRequired) {
+ eicCborAppendString(cborBuilder, "userAuthenticationRequired");
+ eicCborAppendBool(cborBuilder, userAuthenticationRequired);
+ eicCborAppendString(cborBuilder, "timeoutMillis");
+ eicCborAppendUnsigned(cborBuilder, timeoutMillis);
+ if (secureUserId > 0) {
+ eicCborAppendString(cborBuilder, "secureUserId");
+ eicCborAppendUnsigned(cborBuilder, secureUserId);
+ }
+ }
+
+ if (cborBuilder->size > cborBuilder->bufferSize) {
+ eicDebug("Buffer for ACP CBOR is too small (%zd) - need %zd bytes", cborBuilder->bufferSize,
+ cborBuilder->size);
+ return false;
+ }
+
+ return true;
+}
+
+bool eicCborCalcEntryAdditionalData(const int* accessControlProfileIds,
+ size_t numAccessControlProfileIds, const char* nameSpace,
+ const char* name, uint8_t* cborBuffer, size_t cborBufferSize,
+ size_t* outAdditionalDataCborSize,
+ uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE]) {
+ EicCbor cborBuilder;
+
+ eicCborInit(&cborBuilder, cborBuffer, cborBufferSize);
+ eicCborAppendMap(&cborBuilder, 3);
+ eicCborAppendString(&cborBuilder, "Namespace");
+ eicCborAppendString(&cborBuilder, nameSpace);
+ eicCborAppendString(&cborBuilder, "Name");
+ eicCborAppendString(&cborBuilder, name);
+ eicCborAppendString(&cborBuilder, "AccessControlProfileIds");
+ eicCborAppendArray(&cborBuilder, numAccessControlProfileIds);
+ for (size_t n = 0; n < numAccessControlProfileIds; n++) {
+ eicCborAppendNumber(&cborBuilder, accessControlProfileIds[n]);
+ }
+ if (cborBuilder.size > cborBufferSize) {
+ eicDebug("Not enough space for additionalData - buffer is only %zd bytes, content is %zd",
+ cborBufferSize, cborBuilder.size);
+ return false;
+ }
+ if (outAdditionalDataCborSize != NULL) {
+ *outAdditionalDataCborSize = cborBuilder.size;
+ }
+ eicCborFinal(&cborBuilder, additionalDataSha256);
+ return true;
+}
diff --git a/identity/aidl/default/libeic/EicCbor.h b/identity/aidl/default/libeic/EicCbor.h
new file mode 100644
index 0000000..9c0f531
--- /dev/null
+++ b/identity/aidl/default/libeic/EicCbor.h
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if !defined(EIC_INSIDE_LIBEIC_H) && !defined(EIC_COMPILATION)
+#error "Never include this file directly, include libeic.h instead."
+#endif
+
+#ifndef ANDROID_HARDWARE_IDENTITY_EIC_CBOR_H
+#define ANDROID_HARDWARE_IDENTITY_EIC_CBOR_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "EicOps.h"
+
+typedef enum {
+ EIC_CBOR_DIGEST_TYPE_SHA256,
+ EIC_CBOR_DIGEST_TYPE_HMAC_SHA256,
+} EicCborDigestType;
+
+/* EicCbor is a utility class to build CBOR data structures and calculate
+ * digests on the fly.
+ */
+typedef struct {
+ // Contains the size of the built CBOR, even if it exceeds bufferSize (will
+ // never write to buffer beyond bufferSize though)
+ size_t size;
+
+ // The size of the buffer. Is zero if no data is recorded in which case
+ // only digesting is performed.
+ size_t bufferSize;
+
+ // Whether we're producing a SHA-256 or HMAC-SHA256 digest.
+ EicCborDigestType digestType;
+
+ // The SHA-256 digester object.
+ union {
+ EicSha256Ctx sha256;
+ EicHmacSha256Ctx hmacSha256;
+ } digester;
+
+ // The secondary digester, may be unset.
+ EicSha256Ctx* secondaryDigesterSha256;
+
+ // The buffer used for building up CBOR or NULL if bufferSize is 0.
+ uint8_t* buffer;
+} EicCbor;
+
+/* Initializes an EicCbor.
+ *
+ * The given buffer will be used, up to bufferSize.
+ *
+ * If bufferSize is 0, buffer may be NULL.
+ */
+void eicCborInit(EicCbor* cbor, uint8_t* buffer, size_t bufferSize);
+
+/* Like eicCborInit() but uses HMAC-SHA256 instead of SHA-256.
+ */
+void eicCborInitHmacSha256(EicCbor* cbor, uint8_t* buffer, size_t bufferSize,
+ const uint8_t* hmacKey, size_t hmacKeySize);
+
+/* Enables a secondary digester.
+ *
+ * May be enabled midway through processing, this can be used to e.g. calculate
+ * a digest of Sig_structure (for COSE_Sign1) and a separate digest of its
+ * payload.
+ */
+void eicCborEnableSecondaryDigesterSha256(EicCbor* cbor, EicSha256Ctx* sha256);
+
+/* Finishes building CBOR and returns the digest. */
+void eicCborFinal(EicCbor* cbor, uint8_t digest[EIC_SHA256_DIGEST_SIZE]);
+
+/* Appends CBOR data to the EicCbor. */
+void eicCborAppend(EicCbor* cbor, const uint8_t* data, size_t size);
+
+#define EIC_CBOR_MAJOR_TYPE_UNSIGNED 0
+#define EIC_CBOR_MAJOR_TYPE_NEGATIVE 1
+#define EIC_CBOR_MAJOR_TYPE_BYTE_STRING 2
+#define EIC_CBOR_MAJOR_TYPE_STRING 3
+#define EIC_CBOR_MAJOR_TYPE_ARRAY 4
+#define EIC_CBOR_MAJOR_TYPE_MAP 5
+#define EIC_CBOR_MAJOR_TYPE_SEMANTIC 6
+#define EIC_CBOR_MAJOR_TYPE_SIMPLE 7
+
+#define EIC_CBOR_SIMPLE_VALUE_FALSE 20
+#define EIC_CBOR_SIMPLE_VALUE_TRUE 21
+
+#define EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR 24
+
+/* Begins a new CBOR value. */
+void eicCborBegin(EicCbor* cbor, int majorType, size_t size);
+
+/* Appends a bytestring. */
+void eicCborAppendByteString(EicCbor* cbor, const uint8_t* data, size_t dataSize);
+
+/* Appends a NUL-terminated UTF-8 string. */
+void eicCborAppendString(EicCbor* cbor, const char* str);
+
+/* Appends a simple value. */
+void eicCborAppendSimple(EicCbor* cbor, uint8_t simpleValue);
+
+/* Appends a boolean. */
+void eicCborAppendBool(EicCbor* cbor, bool value);
+
+/* Appends a semantic */
+void eicCborAppendSemantic(EicCbor* cbor, uint64_t value);
+
+/* Appends an unsigned number. */
+void eicCborAppendUnsigned(EicCbor* cbor, uint64_t value);
+
+/* Appends a number. */
+void eicCborAppendNumber(EicCbor* cbor, int64_t value);
+
+/* Starts appending an array.
+ *
+ * After this numElements CBOR elements must follow.
+ */
+void eicCborAppendArray(EicCbor* cbor, size_t numElements);
+
+/* Starts appending a map.
+ *
+ * After this numPairs pairs of CBOR elements must follow.
+ */
+void eicCborAppendMap(EicCbor* cbor, size_t numPairs);
+
+/* Calculates how many bytes are needed to store a size. */
+size_t eicCborAdditionalLengthBytesFor(size_t size);
+
+bool eicCborCalcAccessControl(EicCbor* cborBuilder, int id, const uint8_t* readerCertificate,
+ size_t readerCertificateSize, bool userAuthenticationRequired,
+ uint64_t timeoutMillis, uint64_t secureUserId);
+
+bool eicCborCalcEntryAdditionalData(const int* accessControlProfileIds,
+ size_t numAccessControlProfileIds, const char* nameSpace,
+ const char* name, uint8_t* cborBuffer, size_t cborBufferSize,
+ size_t* outAdditionalDataCborSize,
+ uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE]);
+
+// The maximum size of an encoded Secure Access Control Profile that we
+// support. Since the SACP may contain a reader certificate chain these can get
+// pretty big.
+//
+// Currently we allocate space on the stack for this structure which is why we
+// have a maximum size. We can get rid of the maximum size by incrementally
+// building/verifying the SACP. TODO: actually do this.
+//
+#define EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE 512
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANDROID_HARDWARE_IDENTITY_EIC_CBOR_H
diff --git a/identity/aidl/default/libeic/EicOps.h b/identity/aidl/default/libeic/EicOps.h
new file mode 100644
index 0000000..d4fcf0e
--- /dev/null
+++ b/identity/aidl/default/libeic/EicOps.h
@@ -0,0 +1,302 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if !defined(EIC_INSIDE_LIBEIC_H) && !defined(EIC_COMPILATION)
+#error "Never include this file directly, include libeic.h instead."
+#endif
+
+#ifndef ANDROID_HARDWARE_IDENTITY_EIC_OPS_H
+#define ANDROID_HARDWARE_IDENTITY_EIC_OPS_H
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+// Uncomment or define if debug messages are needed.
+//
+//#define EIC_DEBUG
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// The following defines must be set to something appropriate
+//
+// EIC_SHA256_CONTEXT_SIZE - the size of EicSha256Ctx
+// EIC_HMAC_SHA256_CONTEXT_SIZE - the size of EicHmacSha256Ctx
+//
+// For example, if EicSha256Ctx is implemented using BoringSSL this would be defined
+// as sizeof(SHA256_CTX).
+//
+// We expect the implementation to provide a header file with the name
+// EicOpsImpl.h to do all this.
+//
+#include "EicOpsImpl.h"
+
+#define EIC_SHA256_DIGEST_SIZE 32
+
+// The size of a P-256 private key.
+//
+#define EIC_P256_PRIV_KEY_SIZE 32
+
+// The size of a P-256 public key in uncompressed form.
+//
+// The public key is stored in uncompressed form, first the X coordinate, then
+// the Y coordinate.
+//
+#define EIC_P256_PUB_KEY_SIZE 64
+
+// Size of one of the coordinates in a curve-point.
+//
+#define EIC_P256_COORDINATE_SIZE 32
+
+// The size of an ECSDA signature using P-256.
+//
+// The R and S values are stored here, first R then S.
+//
+#define EIC_ECDSA_P256_SIGNATURE_SIZE 64
+
+#define EIC_AES_128_KEY_SIZE 16
+
+// The following are definitions of implementation functions the
+// underlying platform must provide.
+//
+
+struct EicSha256Ctx {
+ uint8_t reserved[EIC_SHA256_CONTEXT_SIZE];
+};
+typedef struct EicSha256Ctx EicSha256Ctx;
+
+struct EicHmacSha256Ctx {
+ uint8_t reserved[EIC_HMAC_SHA256_CONTEXT_SIZE];
+};
+typedef struct EicHmacSha256Ctx EicHmacSha256Ctx;
+
+#ifdef EIC_DEBUG
+// Debug macro. Don't include a new-line in message.
+//
+#define eicDebug(...) \
+ do { \
+ eicPrint("%s:%d: ", __FILE__, __LINE__); \
+ eicPrint(__VA_ARGS__); \
+ eicPrint("\n"); \
+ } while (0)
+#else
+#define eicDebug(...) \
+ do { \
+ } while (0)
+#endif
+
+// Prints message which should include new-line character. Can be no-op.
+//
+// Don't use this from code, use eicDebug() instead.
+//
+#ifdef EIC_DEBUG
+void eicPrint(const char* format, ...);
+#else
+inline void eicPrint(const char*, ...) {}
+#endif
+
+// Dumps data as pretty-printed hex. Can be no-op.
+//
+#ifdef EIC_DEBUG
+void eicHexdump(const char* message, const uint8_t* data, size_t dataSize);
+#else
+inline void eicHexdump(const char*, const uint8_t*, size_t) {}
+#endif
+
+// Pretty-prints encoded CBOR. Can be no-op.
+//
+// If a byte-string is larger than |maxBStrSize| its contents will not be
+// printed, instead the value of the form "<bstr size=1099016
+// sha1=ef549cca331f73dfae2090e6a37c04c23f84b07b>" will be printed. Pass zero
+// for |maxBStrSize| to disable this.
+//
+#ifdef EIC_DEBUG
+void eicCborPrettyPrint(const uint8_t* cborData, size_t cborDataSize, size_t maxBStrSize);
+#else
+inline void eicCborPrettyPrint(const uint8_t*, size_t, size_t) {}
+#endif
+
+// Memory setting, see memset(3).
+void* eicMemSet(void* s, int c, size_t n);
+
+// Memory copying, see memcpy(3).
+void* eicMemCpy(void* dest, const void* src, size_t n);
+
+// String length, see strlen(3).
+size_t eicStrLen(const char* s);
+
+// Memory compare, see CRYPTO_memcmp(3SSL)
+//
+// It takes an amount of time dependent on len, but independent of the contents of the
+// memory regions pointed to by s1 and s2.
+//
+int eicCryptoMemCmp(const void* s1, const void* s2, size_t n);
+
+// Random number generation.
+bool eicOpsRandom(uint8_t* buf, size_t numBytes);
+
+// If |testCredential| is true, returns the 128-bit AES Hardware-Bound Key (16 bytes).
+//
+// Otherwise returns all zeroes (16 bytes).
+//
+const uint8_t* eicOpsGetHardwareBoundKey(bool testCredential);
+
+// Encrypts |data| with |key| and |additionalAuthenticatedData| using |nonce|,
+// returns the resulting (nonce || ciphertext || tag) in |encryptedData| which
+// must be of size |dataSize| + 28.
+bool eicOpsEncryptAes128Gcm(
+ const uint8_t* key, // Must be 16 bytes
+ const uint8_t* nonce, // Must be 12 bytes
+ const uint8_t* data, // May be NULL if size is 0
+ size_t dataSize,
+ const uint8_t* additionalAuthenticationData, // May be NULL if size is 0
+ size_t additionalAuthenticationDataSize, uint8_t* encryptedData);
+
+// Decrypts |encryptedData| using |key| and |additionalAuthenticatedData|,
+// returns resulting plaintext in |data| must be of size |encryptedDataSize| - 28.
+//
+// The format of |encryptedData| must be as specified in the
+// encryptAes128Gcm() function.
+bool eicOpsDecryptAes128Gcm(const uint8_t* key, // Must be 16 bytes
+ const uint8_t* encryptedData, size_t encryptedDataSize,
+ const uint8_t* additionalAuthenticationData,
+ size_t additionalAuthenticationDataSize, uint8_t* data);
+
+// Creates an EC key using the P-256 curve. The private key is written to
+// |privateKey|. The public key is written to |publicKey|.
+//
+bool eicOpsCreateEcKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE],
+ uint8_t publicKey[EIC_P256_PUB_KEY_SIZE]);
+
+// Generates CredentialKey plus an attestation certificate.
+//
+// The attestation certificate will be signed by the attestation keys the secure
+// area has been provisioned with. The given |challenge| and |applicationId|
+// will be used as will |testCredential|.
+//
+// The generated certificate will be in X.509 format and returned in |cert|
+// and |certSize| must be set to the size of this array and this function will
+// set it to the size of the certification chain on successfully return.
+//
+// This may return either a single certificate or an entire certificate
+// chain. If it returns only a single certificate, the implementation of
+// SecureHardwareProvisioningProxy::createCredentialKey() should amend the
+// remainder of the certificate chain on the HAL side.
+//
+bool eicOpsCreateCredentialKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE], const uint8_t* challenge,
+ size_t challengeSize, const uint8_t* applicationId,
+ size_t applicationIdSize, bool testCredential, uint8_t* cert,
+ size_t* certSize); // inout
+
+// Generate an X.509 certificate for the key identified by |publicKey| which
+// must be of the form returned by eicOpsCreateEcKey().
+//
+// If proofOfBinding is not NULL, it will be included as an OCTET_STRING
+// X.509 extension at OID 1.3.6.1.4.1.11129.2.1.26.
+//
+// The certificate will be signed by the key identified by |signingKey| which
+// must be of the form returned by eicOpsCreateEcKey().
+//
+bool eicOpsSignEcKey(const uint8_t publicKey[EIC_P256_PUB_KEY_SIZE],
+ const uint8_t signingKey[EIC_P256_PRIV_KEY_SIZE], unsigned int serial,
+ const char* issuerName, const char* subjectName, time_t validityNotBefore,
+ time_t validityNotAfter, const uint8_t* proofOfBinding,
+ size_t proofOfBindingSize, uint8_t* cert, size_t* certSize); // inout
+
+// Uses |privateKey| to create an ECDSA signature of some data (the SHA-256 must
+// be given by |digestOfData|). Returns the signature in |signature|.
+//
+bool eicOpsEcDsa(const uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE],
+ const uint8_t digestOfData[EIC_SHA256_DIGEST_SIZE],
+ uint8_t signature[EIC_ECDSA_P256_SIGNATURE_SIZE]);
+
+// Performs Elliptic Curve Diffie-Helman.
+//
+bool eicOpsEcdh(const uint8_t publicKey[EIC_P256_PUB_KEY_SIZE],
+ const uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE],
+ uint8_t sharedSecret[EIC_P256_COORDINATE_SIZE]);
+
+// Performs HKDF.
+//
+bool eicOpsHkdf(const uint8_t* sharedSecret, size_t sharedSecretSize, const uint8_t* salt,
+ size_t saltSize, const uint8_t* info, size_t infoSize, uint8_t* output,
+ size_t outputSize);
+
+// SHA-256 functions.
+void eicOpsSha256Init(EicSha256Ctx* ctx);
+void eicOpsSha256Update(EicSha256Ctx* ctx, const uint8_t* data, size_t len);
+void eicOpsSha256Final(EicSha256Ctx* ctx, uint8_t digest[EIC_SHA256_DIGEST_SIZE]);
+
+// HMAC SHA-256 functions.
+void eicOpsHmacSha256Init(EicHmacSha256Ctx* ctx, const uint8_t* key, size_t keySize);
+void eicOpsHmacSha256Update(EicHmacSha256Ctx* ctx, const uint8_t* data, size_t len);
+void eicOpsHmacSha256Final(EicHmacSha256Ctx* ctx, uint8_t digest[EIC_SHA256_DIGEST_SIZE]);
+
+// Extracts the public key in the given X.509 certificate.
+//
+// If the key is not an EC key, this function fails.
+//
+// Otherwise the public key is stored in uncompressed form in |publicKey| which
+// size should be set in |publicKeySize|. On successful return |publicKeySize|
+// is set to the length of the key. If there is not enough space, the function
+// fails.
+//
+// (The public key returned is not necessarily a P-256 key, even if it is note
+// that its size is not EIC_P256_PUBLIC_KEY_SIZE because of the leading 0x04.)
+//
+bool eicOpsX509GetPublicKey(const uint8_t* x509Cert, size_t x509CertSize, uint8_t* publicKey,
+ size_t* publicKeySize);
+
+// Checks that the X.509 certificate given by |x509Cert| is signed by the public
+// key given by |publicKey| which must be an EC key in uncompressed form (e.g.
+// same formatt as returned by eicOpsX509GetPublicKey()).
+//
+bool eicOpsX509CertSignedByPublicKey(const uint8_t* x509Cert, size_t x509CertSize,
+ const uint8_t* publicKey, size_t publicKeySize);
+
+// Checks that |signature| is a signature of some data (given by |digest|),
+// signed by the public key given by |publicKey|.
+//
+// The key must be an EC key in uncompressed form (e.g. same format as returned
+// by eicOpsX509GetPublicKey()).
+//
+// The format of the signature is the same encoding as the 'signature' field of
+// COSE_Sign1 - that is, it's the R and S integers both with the same length as
+// the key-size.
+//
+// The size of digest must match the size of the key.
+//
+bool eicOpsEcDsaVerifyWithPublicKey(const uint8_t* digest, size_t digestSize,
+ const uint8_t* signature, size_t signatureSize,
+ const uint8_t* publicKey, size_t publicKeySize);
+
+// Validates that the passed in data constitutes a valid auth- and verification tokens.
+//
+bool eicOpsValidateAuthToken(uint64_t challenge, uint64_t secureUserId, uint64_t authenticatorId,
+ int hardwareAuthenticatorType, uint64_t timeStamp, const uint8_t* mac,
+ size_t macSize, uint64_t verificationTokenChallenge,
+ uint64_t verificationTokenTimeStamp,
+ int verificationTokenSecurityLevel,
+ const uint8_t* verificationTokenMac, size_t verificationTokenMacSize);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANDROID_HARDWARE_IDENTITY_EIC_OPS_H
diff --git a/identity/aidl/default/libeic/EicPresentation.c b/identity/aidl/default/libeic/EicPresentation.c
new file mode 100644
index 0000000..5e9a280
--- /dev/null
+++ b/identity/aidl/default/libeic/EicPresentation.c
@@ -0,0 +1,850 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "EicPresentation.h"
+
+#include <inttypes.h>
+
+bool eicPresentationInit(EicPresentation* ctx, bool testCredential, const char* docType,
+ const uint8_t* encryptedCredentialKeys,
+ size_t encryptedCredentialKeysSize) {
+ uint8_t credentialKeys[86];
+ bool expectPopSha256 = false;
+
+ // For feature version 202009 it's 52 bytes long and for feature version 202101 it's 86
+ // bytes (the additional data is the ProofOfProvisioning SHA-256). We need
+ // to support loading all feature versions.
+ //
+ if (encryptedCredentialKeysSize == 52 + 28) {
+ /* do nothing */
+ } else if (encryptedCredentialKeysSize == 86 + 28) {
+ expectPopSha256 = true;
+ } else {
+ eicDebug("Unexpected size %zd for encryptedCredentialKeys", encryptedCredentialKeysSize);
+ return false;
+ }
+
+ eicMemSet(ctx, '\0', sizeof(EicPresentation));
+
+ if (!eicOpsDecryptAes128Gcm(eicOpsGetHardwareBoundKey(testCredential), encryptedCredentialKeys,
+ encryptedCredentialKeysSize,
+ // DocType is the additionalAuthenticatedData
+ (const uint8_t*)docType, eicStrLen(docType), credentialKeys)) {
+ eicDebug("Error decrypting CredentialKeys");
+ return false;
+ }
+
+ // It's supposed to look like this;
+ //
+ // Feature version 202009:
+ //
+ // CredentialKeys = [
+ // bstr, ; storageKey, a 128-bit AES key
+ // bstr, ; credentialPrivKey, the private key for credentialKey
+ // ]
+ //
+ // Feature version 202101:
+ //
+ // CredentialKeys = [
+ // bstr, ; storageKey, a 128-bit AES key
+ // bstr, ; credentialPrivKey, the private key for credentialKey
+ // bstr ; proofOfProvisioning SHA-256
+ // ]
+ //
+ // where storageKey is 16 bytes, credentialPrivateKey is 32 bytes, and proofOfProvisioning
+ // SHA-256 is 32 bytes.
+ //
+ if (credentialKeys[0] != (expectPopSha256 ? 0x83 : 0x82) || // array of two or three elements
+ credentialKeys[1] != 0x50 || // 16-byte bstr
+ credentialKeys[18] != 0x58 || credentialKeys[19] != 0x20) { // 32-byte bstr
+ eicDebug("Invalid CBOR for CredentialKeys");
+ return false;
+ }
+ if (expectPopSha256) {
+ if (credentialKeys[52] != 0x58 || credentialKeys[53] != 0x20) { // 32-byte bstr
+ eicDebug("Invalid CBOR for CredentialKeys");
+ return false;
+ }
+ }
+ eicMemCpy(ctx->storageKey, credentialKeys + 2, EIC_AES_128_KEY_SIZE);
+ eicMemCpy(ctx->credentialPrivateKey, credentialKeys + 20, EIC_P256_PRIV_KEY_SIZE);
+ ctx->testCredential = testCredential;
+ if (expectPopSha256) {
+ eicMemCpy(ctx->proofOfProvisioningSha256, credentialKeys + 54, EIC_SHA256_DIGEST_SIZE);
+ }
+ return true;
+}
+
+bool eicPresentationGenerateSigningKeyPair(EicPresentation* ctx, const char* docType, time_t now,
+ uint8_t* publicKeyCert, size_t* publicKeyCertSize,
+ uint8_t signingKeyBlob[60]) {
+ uint8_t signingKeyPriv[EIC_P256_PRIV_KEY_SIZE];
+ uint8_t signingKeyPub[EIC_P256_PUB_KEY_SIZE];
+ uint8_t cborBuf[64];
+
+ // Generate the ProofOfBinding CBOR to include in the X.509 certificate in
+ // IdentityCredentialAuthenticationKeyExtension CBOR. This CBOR is defined
+ // by the following CDDL
+ //
+ // ProofOfBinding = [
+ // "ProofOfBinding",
+ // bstr, // Contains the SHA-256 of ProofOfProvisioning
+ // ]
+ //
+ // This array may grow in the future if other information needs to be
+ // conveyed.
+ //
+ // The bytes of ProofOfBinding is is represented as an OCTET_STRING
+ // and stored at OID 1.3.6.1.4.1.11129.2.1.26.
+ //
+
+ EicCbor cbor;
+ eicCborInit(&cbor, cborBuf, sizeof cborBuf);
+ eicCborAppendArray(&cbor, 2);
+ eicCborAppendString(&cbor, "ProofOfBinding");
+ eicCborAppendByteString(&cbor, ctx->proofOfProvisioningSha256, EIC_SHA256_DIGEST_SIZE);
+ if (cbor.size > sizeof(cborBuf)) {
+ eicDebug("Exceeded buffer size");
+ return false;
+ }
+ const uint8_t* proofOfBinding = cborBuf;
+ size_t proofOfBindingSize = cbor.size;
+
+ if (!eicOpsCreateEcKey(signingKeyPriv, signingKeyPub)) {
+ eicDebug("Error creating signing key");
+ return false;
+ }
+
+ const int secondsInOneYear = 365 * 24 * 60 * 60;
+ time_t validityNotBefore = now;
+ time_t validityNotAfter = now + secondsInOneYear; // One year from now.
+ if (!eicOpsSignEcKey(signingKeyPub, ctx->credentialPrivateKey, 1,
+ "Android Identity Credential Key", // issuer CN
+ "Android Identity Credential Authentication Key", // subject CN
+ validityNotBefore, validityNotAfter, proofOfBinding, proofOfBindingSize,
+ publicKeyCert, publicKeyCertSize)) {
+ eicDebug("Error creating certificate for signing key");
+ return false;
+ }
+
+ uint8_t nonce[12];
+ if (!eicOpsRandom(nonce, 12)) {
+ eicDebug("Error getting random");
+ return false;
+ }
+ if (!eicOpsEncryptAes128Gcm(ctx->storageKey, nonce, signingKeyPriv, sizeof(signingKeyPriv),
+ // DocType is the additionalAuthenticatedData
+ (const uint8_t*)docType, eicStrLen(docType), signingKeyBlob)) {
+ eicDebug("Error encrypting signing key");
+ return false;
+ }
+
+ return true;
+}
+
+bool eicPresentationCreateEphemeralKeyPair(EicPresentation* ctx,
+ uint8_t ephemeralPrivateKey[EIC_P256_PRIV_KEY_SIZE]) {
+ uint8_t ephemeralPublicKey[EIC_P256_PUB_KEY_SIZE];
+ if (!eicOpsCreateEcKey(ctx->ephemeralPrivateKey, ephemeralPublicKey)) {
+ eicDebug("Error creating ephemeral key");
+ return false;
+ }
+ eicMemCpy(ephemeralPrivateKey, ctx->ephemeralPrivateKey, EIC_P256_PRIV_KEY_SIZE);
+ return true;
+}
+
+bool eicPresentationCreateAuthChallenge(EicPresentation* ctx, uint64_t* authChallenge) {
+ do {
+ if (!eicOpsRandom((uint8_t*)&(ctx->authChallenge), sizeof(uint64_t))) {
+ eicDebug("Failed generating random challenge");
+ return false;
+ }
+ } while (ctx->authChallenge == 0);
+ eicDebug("Created auth challenge %" PRIu64, ctx->authChallenge);
+ *authChallenge = ctx->authChallenge;
+ return true;
+}
+
+// From "COSE Algorithms" registry
+//
+#define COSE_ALG_ECDSA_256 -7
+
+bool eicPresentationValidateRequestMessage(EicPresentation* ctx, const uint8_t* sessionTranscript,
+ size_t sessionTranscriptSize,
+ const uint8_t* requestMessage, size_t requestMessageSize,
+ int coseSignAlg,
+ const uint8_t* readerSignatureOfToBeSigned,
+ size_t readerSignatureOfToBeSignedSize) {
+ if (ctx->readerPublicKeySize == 0) {
+ eicDebug("No public key for reader");
+ return false;
+ }
+
+ // Right now we only support ECDSA with SHA-256 (e.g. ES256).
+ //
+ if (coseSignAlg != COSE_ALG_ECDSA_256) {
+ eicDebug(
+ "COSE Signature algorithm for reader signature is %d, "
+ "only ECDSA with SHA-256 is supported right now",
+ coseSignAlg);
+ return false;
+ }
+
+ // What we're going to verify is the COSE ToBeSigned structure which
+ // looks like the following:
+ //
+ // Sig_structure = [
+ // context : "Signature" / "Signature1" / "CounterSignature",
+ // body_protected : empty_or_serialized_map,
+ // ? sign_protected : empty_or_serialized_map,
+ // external_aad : bstr,
+ // payload : bstr
+ // ]
+ //
+ // So we're going to build that CBOR...
+ //
+ EicCbor cbor;
+ eicCborInit(&cbor, NULL, 0);
+ eicCborAppendArray(&cbor, 4);
+ eicCborAppendString(&cbor, "Signature1");
+
+ // The COSE Encoded protected headers is just a single field with
+ // COSE_LABEL_ALG (1) -> coseSignAlg (e.g. -7). For simplicitly we just
+ // hard-code the CBOR encoding:
+ static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x26};
+ eicCborAppendByteString(&cbor, coseEncodedProtectedHeaders,
+ sizeof(coseEncodedProtectedHeaders));
+
+ // External_aad is the empty bstr
+ static const uint8_t externalAad[0] = {};
+ eicCborAppendByteString(&cbor, externalAad, sizeof(externalAad));
+
+ // For the payload, the _encoded_ form follows here. We handle this by simply
+ // opening a bstr, and then writing the CBOR. This requires us to know the
+ // size of said bstr, ahead of time... the CBOR to be written is
+ //
+ // ReaderAuthentication = [
+ // "ReaderAuthentication",
+ // SessionTranscript,
+ // ItemsRequestBytes
+ // ]
+ //
+ // ItemsRequestBytes = #6.24(bstr .cbor ItemsRequest)
+ //
+ // ReaderAuthenticationBytes = #6.24(bstr .cbor ReaderAuthentication)
+ //
+ // which is easily calculated below
+ //
+ size_t calculatedSize = 0;
+ calculatedSize += 1; // Array of size 3
+ calculatedSize += 1; // "ReaderAuthentication" less than 24 bytes
+ calculatedSize += sizeof("ReaderAuthentication") - 1; // Don't include trailing NUL
+ calculatedSize += sessionTranscriptSize; // Already CBOR encoded
+ calculatedSize += 2; // Semantic tag EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR (24)
+ calculatedSize += 1 + eicCborAdditionalLengthBytesFor(requestMessageSize);
+ calculatedSize += requestMessageSize;
+
+ // However note that we're authenticating ReaderAuthenticationBytes which
+ // is a tagged bstr of the bytes of ReaderAuthentication. So need to get
+ // that in front.
+ size_t rabCalculatedSize = 0;
+ rabCalculatedSize += 2; // Semantic tag EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR (24)
+ rabCalculatedSize += 1 + eicCborAdditionalLengthBytesFor(calculatedSize);
+ rabCalculatedSize += calculatedSize;
+
+ // Begin the bytestring for ReaderAuthenticationBytes;
+ eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, rabCalculatedSize);
+
+ eicCborAppendSemantic(&cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+
+ // Begins the bytestring for ReaderAuthentication;
+ eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, calculatedSize);
+
+ // And now that we know the size, let's fill it in...
+ //
+ size_t payloadOffset = cbor.size;
+ eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_ARRAY, 3);
+ eicCborAppendString(&cbor, "ReaderAuthentication");
+ eicCborAppend(&cbor, sessionTranscript, sessionTranscriptSize);
+ eicCborAppendSemantic(&cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+ eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, requestMessageSize);
+ eicCborAppend(&cbor, requestMessage, requestMessageSize);
+
+ if (cbor.size != payloadOffset + calculatedSize) {
+ eicDebug("CBOR size is %zd but we expected %zd", cbor.size, payloadOffset + calculatedSize);
+ return false;
+ }
+ uint8_t toBeSignedDigest[EIC_SHA256_DIGEST_SIZE];
+ eicCborFinal(&cbor, toBeSignedDigest);
+
+ if (!eicOpsEcDsaVerifyWithPublicKey(
+ toBeSignedDigest, EIC_SHA256_DIGEST_SIZE, readerSignatureOfToBeSigned,
+ readerSignatureOfToBeSignedSize, ctx->readerPublicKey, ctx->readerPublicKeySize)) {
+ eicDebug("Request message is not signed by public key");
+ return false;
+ }
+ ctx->requestMessageValidated = true;
+ return true;
+}
+
+// Validates the next certificate in the reader certificate chain.
+bool eicPresentationPushReaderCert(EicPresentation* ctx, const uint8_t* certX509,
+ size_t certX509Size) {
+ // If we had a previous certificate, use its public key to validate this certificate.
+ if (ctx->readerPublicKeySize > 0) {
+ if (!eicOpsX509CertSignedByPublicKey(certX509, certX509Size, ctx->readerPublicKey,
+ ctx->readerPublicKeySize)) {
+ eicDebug("Certificate is not signed by public key in the previous certificate");
+ return false;
+ }
+ }
+
+ // Store the key of this certificate, this is used to validate the next certificate
+ // and also ACPs with certificates that use the same public key...
+ ctx->readerPublicKeySize = EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE;
+ if (!eicOpsX509GetPublicKey(certX509, certX509Size, ctx->readerPublicKey,
+ &ctx->readerPublicKeySize)) {
+ eicDebug("Error extracting public key from certificate");
+ return false;
+ }
+ if (ctx->readerPublicKeySize == 0) {
+ eicDebug("Zero-length public key in certificate");
+ return false;
+ }
+
+ return true;
+}
+
+bool eicPresentationSetAuthToken(EicPresentation* ctx, uint64_t challenge, uint64_t secureUserId,
+ uint64_t authenticatorId, int hardwareAuthenticatorType,
+ uint64_t timeStamp, const uint8_t* mac, size_t macSize,
+ uint64_t verificationTokenChallenge,
+ uint64_t verificationTokenTimestamp,
+ int verificationTokenSecurityLevel,
+ const uint8_t* verificationTokenMac,
+ size_t verificationTokenMacSize) {
+ if (!eicOpsValidateAuthToken(
+ challenge, secureUserId, authenticatorId, hardwareAuthenticatorType, timeStamp, mac,
+ macSize, verificationTokenChallenge, verificationTokenTimestamp,
+ verificationTokenSecurityLevel, verificationTokenMac, verificationTokenMacSize)) {
+ return false;
+ }
+ ctx->authTokenChallenge = challenge;
+ ctx->authTokenSecureUserId = secureUserId;
+ ctx->authTokenTimestamp = timeStamp;
+ ctx->verificationTokenTimestamp = verificationTokenTimestamp;
+ return true;
+}
+
+static bool checkUserAuth(EicPresentation* ctx, bool userAuthenticationRequired, int timeoutMillis,
+ uint64_t secureUserId) {
+ if (!userAuthenticationRequired) {
+ return true;
+ }
+
+ if (secureUserId != ctx->authTokenSecureUserId) {
+ eicDebug("secureUserId in profile differs from userId in authToken");
+ return false;
+ }
+
+ if (timeoutMillis == 0) {
+ if (ctx->authTokenChallenge == 0) {
+ eicDebug("No challenge in authToken");
+ return false;
+ }
+
+ // If we didn't create a challenge, too bad but user auth with
+ // timeoutMillis set to 0 needs it.
+ if (ctx->authChallenge == 0) {
+ eicDebug("No challenge was created for this session");
+ return false;
+ }
+ if (ctx->authTokenChallenge != ctx->authChallenge) {
+ eicDebug("Challenge in authToken (%" PRIu64
+ ") doesn't match the challenge "
+ "that was created (%" PRIu64 ") for this session",
+ ctx->authTokenChallenge, ctx->authChallenge);
+ return false;
+ }
+ }
+
+ uint64_t now = ctx->verificationTokenTimestamp;
+ if (ctx->authTokenTimestamp > now) {
+ eicDebug("Timestamp in authToken is in the future");
+ return false;
+ }
+
+ if (timeoutMillis > 0) {
+ if (now > ctx->authTokenTimestamp + timeoutMillis) {
+ eicDebug("Deadline for authToken is in the past");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool checkReaderAuth(EicPresentation* ctx, const uint8_t* readerCertificate,
+ size_t readerCertificateSize) {
+ uint8_t publicKey[EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE];
+ size_t publicKeySize;
+
+ if (readerCertificateSize == 0) {
+ return true;
+ }
+
+ // Remember in this case certificate equality is done by comparing public
+ // keys, not bitwise comparison of the certificates.
+ //
+ publicKeySize = EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE;
+ if (!eicOpsX509GetPublicKey(readerCertificate, readerCertificateSize, publicKey,
+ &publicKeySize)) {
+ eicDebug("Error extracting public key from certificate");
+ return false;
+ }
+ if (publicKeySize == 0) {
+ eicDebug("Zero-length public key in certificate");
+ return false;
+ }
+
+ if ((ctx->readerPublicKeySize != publicKeySize) ||
+ (eicCryptoMemCmp(ctx->readerPublicKey, publicKey, ctx->readerPublicKeySize) != 0)) {
+ return false;
+ }
+ return true;
+}
+
+// Note: This function returns false _only_ if an error occurred check for access, _not_
+// whether access is granted. Whether access is granted is returned in |accessGranted|.
+//
+bool eicPresentationValidateAccessControlProfile(EicPresentation* ctx, int id,
+ const uint8_t* readerCertificate,
+ size_t readerCertificateSize,
+ bool userAuthenticationRequired, int timeoutMillis,
+ uint64_t secureUserId, const uint8_t mac[28],
+ bool* accessGranted) {
+ *accessGranted = false;
+
+ if (id < 0 || id >= 32) {
+ eicDebug("id value of %d is out of allowed range [0, 32[", id);
+ return false;
+ }
+
+ // Validate the MAC
+ uint8_t cborBuffer[EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE];
+ EicCbor cborBuilder;
+ eicCborInit(&cborBuilder, cborBuffer, EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE);
+ if (!eicCborCalcAccessControl(&cborBuilder, id, readerCertificate, readerCertificateSize,
+ userAuthenticationRequired, timeoutMillis, secureUserId)) {
+ return false;
+ }
+ if (!eicOpsDecryptAes128Gcm(ctx->storageKey, mac, 28, cborBuilder.buffer, cborBuilder.size,
+ NULL)) {
+ eicDebug("MAC for AccessControlProfile doesn't match");
+ return false;
+ }
+
+ bool passedUserAuth =
+ checkUserAuth(ctx, userAuthenticationRequired, timeoutMillis, secureUserId);
+ bool passedReaderAuth = checkReaderAuth(ctx, readerCertificate, readerCertificateSize);
+
+ ctx->accessControlProfileMaskValidated |= (1 << id);
+ if (readerCertificateSize > 0) {
+ ctx->accessControlProfileMaskUsesReaderAuth |= (1 << id);
+ }
+ if (!passedReaderAuth) {
+ ctx->accessControlProfileMaskFailedReaderAuth |= (1 << id);
+ }
+ if (!passedUserAuth) {
+ ctx->accessControlProfileMaskFailedUserAuth |= (1 << id);
+ }
+
+ if (passedUserAuth && passedReaderAuth) {
+ *accessGranted = true;
+ eicDebug("Access granted for id %d", id);
+ }
+ return true;
+}
+
+bool eicPresentationCalcMacKey(EicPresentation* ctx, const uint8_t* sessionTranscript,
+ size_t sessionTranscriptSize,
+ const uint8_t readerEphemeralPublicKey[EIC_P256_PUB_KEY_SIZE],
+ const uint8_t signingKeyBlob[60], const char* docType,
+ unsigned int numNamespacesWithValues,
+ size_t expectedDeviceNamespacesSize) {
+ uint8_t signingKeyPriv[EIC_P256_PRIV_KEY_SIZE];
+ if (!eicOpsDecryptAes128Gcm(ctx->storageKey, signingKeyBlob, 60, (const uint8_t*)docType,
+ eicStrLen(docType), signingKeyPriv)) {
+ eicDebug("Error decrypting signingKeyBlob");
+ return false;
+ }
+
+ uint8_t sharedSecret[EIC_P256_COORDINATE_SIZE];
+ if (!eicOpsEcdh(readerEphemeralPublicKey, signingKeyPriv, sharedSecret)) {
+ eicDebug("ECDH failed");
+ return false;
+ }
+
+ EicCbor cbor;
+ eicCborInit(&cbor, NULL, 0);
+ eicCborAppendSemantic(&cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+ eicCborAppendByteString(&cbor, sessionTranscript, sessionTranscriptSize);
+ uint8_t salt[EIC_SHA256_DIGEST_SIZE];
+ eicCborFinal(&cbor, salt);
+
+ const uint8_t info[7] = {'E', 'M', 'a', 'c', 'K', 'e', 'y'};
+ uint8_t derivedKey[32];
+ if (!eicOpsHkdf(sharedSecret, EIC_P256_COORDINATE_SIZE, salt, sizeof(salt), info, sizeof(info),
+ derivedKey, sizeof(derivedKey))) {
+ eicDebug("HKDF failed");
+ return false;
+ }
+
+ eicCborInitHmacSha256(&ctx->cbor, NULL, 0, derivedKey, sizeof(derivedKey));
+ ctx->buildCbor = true;
+
+ // What we're going to calculate the HMAC-SHA256 is the COSE ToBeMaced
+ // structure which looks like the following:
+ //
+ // MAC_structure = [
+ // context : "MAC" / "MAC0",
+ // protected : empty_or_serialized_map,
+ // external_aad : bstr,
+ // payload : bstr
+ // ]
+ //
+ eicCborAppendArray(&ctx->cbor, 4);
+ eicCborAppendString(&ctx->cbor, "MAC0");
+
+ // The COSE Encoded protected headers is just a single field with
+ // COSE_LABEL_ALG (1) -> COSE_ALG_HMAC_256_256 (5). For simplicitly we just
+ // hard-code the CBOR encoding:
+ static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x05};
+ eicCborAppendByteString(&ctx->cbor, coseEncodedProtectedHeaders,
+ sizeof(coseEncodedProtectedHeaders));
+
+ // We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
+ // so external_aad is the empty bstr
+ static const uint8_t externalAad[0] = {};
+ eicCborAppendByteString(&ctx->cbor, externalAad, sizeof(externalAad));
+
+ // For the payload, the _encoded_ form follows here. We handle this by simply
+ // opening a bstr, and then writing the CBOR. This requires us to know the
+ // size of said bstr, ahead of time... the CBOR to be written is
+ //
+ // DeviceAuthentication = [
+ // "DeviceAuthentication",
+ // SessionTranscript,
+ // DocType, ; DocType as used in Documents structure in OfflineResponse
+ // DeviceNameSpacesBytes
+ // ]
+ //
+ // DeviceNameSpacesBytes = #6.24(bstr .cbor DeviceNameSpaces)
+ //
+ // DeviceAuthenticationBytes = #6.24(bstr .cbor DeviceAuthentication)
+ //
+ // which is easily calculated below
+ //
+ size_t calculatedSize = 0;
+ calculatedSize += 1; // Array of size 4
+ calculatedSize += 1; // "DeviceAuthentication" less than 24 bytes
+ calculatedSize += sizeof("DeviceAuthentication") - 1; // Don't include trailing NUL
+ calculatedSize += sessionTranscriptSize; // Already CBOR encoded
+ size_t docTypeLen = eicStrLen(docType);
+ calculatedSize += 1 + eicCborAdditionalLengthBytesFor(docTypeLen) + docTypeLen;
+ calculatedSize += 2; // Semantic tag EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR (24)
+ calculatedSize += 1 + eicCborAdditionalLengthBytesFor(expectedDeviceNamespacesSize);
+ calculatedSize += expectedDeviceNamespacesSize;
+
+ // However note that we're authenticating DeviceAuthenticationBytes which
+ // is a tagged bstr of the bytes of DeviceAuthentication. So need to get
+ // that in front.
+ size_t dabCalculatedSize = 0;
+ dabCalculatedSize += 2; // Semantic tag EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR (24)
+ dabCalculatedSize += 1 + eicCborAdditionalLengthBytesFor(calculatedSize);
+ dabCalculatedSize += calculatedSize;
+
+ // Begin the bytestring for DeviceAuthenticationBytes;
+ eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, dabCalculatedSize);
+
+ eicCborAppendSemantic(&ctx->cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+
+ // Begins the bytestring for DeviceAuthentication;
+ eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, calculatedSize);
+
+ eicCborAppendArray(&ctx->cbor, 4);
+ eicCborAppendString(&ctx->cbor, "DeviceAuthentication");
+ eicCborAppend(&ctx->cbor, sessionTranscript, sessionTranscriptSize);
+ eicCborAppendString(&ctx->cbor, docType);
+
+ // For the payload, the _encoded_ form follows here. We handle this by simply
+ // opening a bstr, and then writing the CBOR. This requires us to know the
+ // size of said bstr, ahead of time.
+ eicCborAppendSemantic(&ctx->cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+ eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, expectedDeviceNamespacesSize);
+ ctx->expectedCborSizeAtEnd = expectedDeviceNamespacesSize + ctx->cbor.size;
+
+ eicCborAppendMap(&ctx->cbor, numNamespacesWithValues);
+ return true;
+}
+
+bool eicPresentationStartRetrieveEntries(EicPresentation* ctx) {
+ // HAL may use this object multiple times to retrieve data so need to reset various
+ // state objects here.
+ ctx->requestMessageValidated = false;
+ ctx->buildCbor = false;
+ ctx->accessControlProfileMaskValidated = 0;
+ ctx->accessControlProfileMaskUsesReaderAuth = 0;
+ ctx->accessControlProfileMaskFailedReaderAuth = 0;
+ ctx->accessControlProfileMaskFailedUserAuth = 0;
+ ctx->readerPublicKeySize = 0;
+ return true;
+}
+
+EicAccessCheckResult eicPresentationStartRetrieveEntryValue(
+ EicPresentation* ctx, const char* nameSpace, const char* name,
+ unsigned int newNamespaceNumEntries, int32_t /* entrySize */,
+ const int* accessControlProfileIds, size_t numAccessControlProfileIds,
+ uint8_t* scratchSpace, size_t scratchSpaceSize) {
+ uint8_t* additionalDataCbor = scratchSpace;
+ const size_t additionalDataCborBufSize = scratchSpaceSize;
+ size_t additionalDataCborSize;
+
+ if (newNamespaceNumEntries > 0) {
+ eicCborAppendString(&ctx->cbor, nameSpace);
+ eicCborAppendMap(&ctx->cbor, newNamespaceNumEntries);
+ }
+
+ // We'll need to calc and store a digest of additionalData to check that it's the same
+ // additionalData being passed in for every eicPresentationRetrieveEntryValue() call...
+ if (!eicCborCalcEntryAdditionalData(accessControlProfileIds, numAccessControlProfileIds,
+ nameSpace, name, additionalDataCbor,
+ additionalDataCborBufSize, &additionalDataCborSize,
+ ctx->additionalDataSha256)) {
+ return EIC_ACCESS_CHECK_RESULT_FAILED;
+ }
+
+ if (numAccessControlProfileIds == 0) {
+ return EIC_ACCESS_CHECK_RESULT_NO_ACCESS_CONTROL_PROFILES;
+ }
+
+ // Access is granted if at least one of the profiles grants access.
+ //
+ // If an item is configured without any profiles, access is denied.
+ //
+ EicAccessCheckResult result = EIC_ACCESS_CHECK_RESULT_FAILED;
+ for (size_t n = 0; n < numAccessControlProfileIds; n++) {
+ int id = accessControlProfileIds[n];
+ uint32_t idBitMask = (1 << id);
+
+ // If the access control profile wasn't validated, this is an error and we
+ // fail immediately.
+ bool validated = ((ctx->accessControlProfileMaskValidated & idBitMask) != 0);
+ if (!validated) {
+ eicDebug("No ACP for profile id %d", id);
+ return EIC_ACCESS_CHECK_RESULT_FAILED;
+ }
+
+ // Otherwise, we _did_ validate the profile. If none of the checks
+ // failed, we're done
+ bool failedUserAuth = ((ctx->accessControlProfileMaskFailedUserAuth & idBitMask) != 0);
+ bool failedReaderAuth = ((ctx->accessControlProfileMaskFailedReaderAuth & idBitMask) != 0);
+ if (!failedUserAuth && !failedReaderAuth) {
+ result = EIC_ACCESS_CHECK_RESULT_OK;
+ break;
+ }
+ // One of the checks failed, convey which one
+ if (failedUserAuth) {
+ result = EIC_ACCESS_CHECK_RESULT_USER_AUTHENTICATION_FAILED;
+ } else {
+ result = EIC_ACCESS_CHECK_RESULT_READER_AUTHENTICATION_FAILED;
+ }
+ }
+ eicDebug("Result %d for name %s", result, name);
+
+ if (result == EIC_ACCESS_CHECK_RESULT_OK) {
+ eicCborAppendString(&ctx->cbor, name);
+ }
+ return result;
+}
+
+// Note: |content| must be big enough to hold |encryptedContentSize| - 28 bytes.
+bool eicPresentationRetrieveEntryValue(EicPresentation* ctx, const uint8_t* encryptedContent,
+ size_t encryptedContentSize, uint8_t* content,
+ const char* nameSpace, const char* name,
+ const int* accessControlProfileIds,
+ size_t numAccessControlProfileIds, uint8_t* scratchSpace,
+ size_t scratchSpaceSize) {
+ uint8_t* additionalDataCbor = scratchSpace;
+ const size_t additionalDataCborBufSize = scratchSpaceSize;
+ size_t additionalDataCborSize;
+
+ uint8_t calculatedSha256[EIC_SHA256_DIGEST_SIZE];
+ if (!eicCborCalcEntryAdditionalData(accessControlProfileIds, numAccessControlProfileIds,
+ nameSpace, name, additionalDataCbor,
+ additionalDataCborBufSize, &additionalDataCborSize,
+ calculatedSha256)) {
+ return false;
+ }
+ if (eicCryptoMemCmp(calculatedSha256, ctx->additionalDataSha256, EIC_SHA256_DIGEST_SIZE) != 0) {
+ eicDebug("SHA-256 mismatch of additionalData");
+ return false;
+ }
+
+ if (!eicOpsDecryptAes128Gcm(ctx->storageKey, encryptedContent, encryptedContentSize,
+ additionalDataCbor, additionalDataCborSize, content)) {
+ eicDebug("Error decrypting content");
+ return false;
+ }
+
+ eicCborAppend(&ctx->cbor, content, encryptedContentSize - 28);
+
+ return true;
+}
+
+bool eicPresentationFinishRetrieval(EicPresentation* ctx, uint8_t* digestToBeMaced,
+ size_t* digestToBeMacedSize) {
+ if (!ctx->buildCbor) {
+ *digestToBeMacedSize = 0;
+ return true;
+ }
+ if (*digestToBeMacedSize != 32) {
+ return false;
+ }
+
+ // This verifies that the correct expectedDeviceNamespacesSize value was
+ // passed in at eicPresentationCalcMacKey() time.
+ if (ctx->cbor.size != ctx->expectedCborSizeAtEnd) {
+ eicDebug("CBOR size is %zd, was expecting %zd", ctx->cbor.size, ctx->expectedCborSizeAtEnd);
+ return false;
+ }
+ eicCborFinal(&ctx->cbor, digestToBeMaced);
+ return true;
+}
+
+bool eicPresentationDeleteCredential(EicPresentation* ctx, const char* docType,
+ const uint8_t* challenge, size_t challengeSize,
+ bool includeChallenge, size_t proofOfDeletionCborSize,
+ uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]) {
+ EicCbor cbor;
+
+ eicCborInit(&cbor, NULL, 0);
+
+ // What we're going to sign is the COSE ToBeSigned structure which
+ // looks like the following:
+ //
+ // Sig_structure = [
+ // context : "Signature" / "Signature1" / "CounterSignature",
+ // body_protected : empty_or_serialized_map,
+ // ? sign_protected : empty_or_serialized_map,
+ // external_aad : bstr,
+ // payload : bstr
+ // ]
+ //
+ eicCborAppendArray(&cbor, 4);
+ eicCborAppendString(&cbor, "Signature1");
+
+ // The COSE Encoded protected headers is just a single field with
+ // COSE_LABEL_ALG (1) -> COSE_ALG_ECSDA_256 (-7). For simplicitly we just
+ // hard-code the CBOR encoding:
+ static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x26};
+ eicCborAppendByteString(&cbor, coseEncodedProtectedHeaders,
+ sizeof(coseEncodedProtectedHeaders));
+
+ // We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
+ // so external_aad is the empty bstr
+ static const uint8_t externalAad[0] = {};
+ eicCborAppendByteString(&cbor, externalAad, sizeof(externalAad));
+
+ // For the payload, the _encoded_ form follows here. We handle this by simply
+ // opening a bstr, and then writing the CBOR. This requires us to know the
+ // size of said bstr, ahead of time.
+ eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, proofOfDeletionCborSize);
+
+ // Finally, the CBOR that we're actually signing.
+ eicCborAppendArray(&cbor, includeChallenge ? 4 : 3);
+ eicCborAppendString(&cbor, "ProofOfDeletion");
+ eicCborAppendString(&cbor, docType);
+ if (includeChallenge) {
+ eicCborAppendByteString(&cbor, challenge, challengeSize);
+ }
+ eicCborAppendBool(&cbor, ctx->testCredential);
+
+ uint8_t cborSha256[EIC_SHA256_DIGEST_SIZE];
+ eicCborFinal(&cbor, cborSha256);
+ if (!eicOpsEcDsa(ctx->credentialPrivateKey, cborSha256, signatureOfToBeSigned)) {
+ eicDebug("Error signing proofOfDeletion");
+ return false;
+ }
+
+ return true;
+}
+
+bool eicPresentationProveOwnership(EicPresentation* ctx, const char* docType, bool testCredential,
+ const uint8_t* challenge, size_t challengeSize,
+ size_t proofOfOwnershipCborSize,
+ uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]) {
+ EicCbor cbor;
+
+ eicCborInit(&cbor, NULL, 0);
+
+ // What we're going to sign is the COSE ToBeSigned structure which
+ // looks like the following:
+ //
+ // Sig_structure = [
+ // context : "Signature" / "Signature1" / "CounterSignature",
+ // body_protected : empty_or_serialized_map,
+ // ? sign_protected : empty_or_serialized_map,
+ // external_aad : bstr,
+ // payload : bstr
+ // ]
+ //
+ eicCborAppendArray(&cbor, 4);
+ eicCborAppendString(&cbor, "Signature1");
+
+ // The COSE Encoded protected headers is just a single field with
+ // COSE_LABEL_ALG (1) -> COSE_ALG_ECSDA_256 (-7). For simplicitly we just
+ // hard-code the CBOR encoding:
+ static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x26};
+ eicCborAppendByteString(&cbor, coseEncodedProtectedHeaders,
+ sizeof(coseEncodedProtectedHeaders));
+
+ // We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
+ // so external_aad is the empty bstr
+ static const uint8_t externalAad[0] = {};
+ eicCborAppendByteString(&cbor, externalAad, sizeof(externalAad));
+
+ // For the payload, the _encoded_ form follows here. We handle this by simply
+ // opening a bstr, and then writing the CBOR. This requires us to know the
+ // size of said bstr, ahead of time.
+ eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, proofOfOwnershipCborSize);
+
+ // Finally, the CBOR that we're actually signing.
+ eicCborAppendArray(&cbor, 4);
+ eicCborAppendString(&cbor, "ProofOfOwnership");
+ eicCborAppendString(&cbor, docType);
+ eicCborAppendByteString(&cbor, challenge, challengeSize);
+ eicCborAppendBool(&cbor, testCredential);
+
+ uint8_t cborSha256[EIC_SHA256_DIGEST_SIZE];
+ eicCborFinal(&cbor, cborSha256);
+ if (!eicOpsEcDsa(ctx->credentialPrivateKey, cborSha256, signatureOfToBeSigned)) {
+ eicDebug("Error signing proofOfDeletion");
+ return false;
+ }
+
+ return true;
+}
diff --git a/identity/aidl/default/libeic/EicPresentation.h b/identity/aidl/default/libeic/EicPresentation.h
new file mode 100644
index 0000000..7cad068
--- /dev/null
+++ b/identity/aidl/default/libeic/EicPresentation.h
@@ -0,0 +1,246 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if !defined(EIC_INSIDE_LIBEIC_H) && !defined(EIC_COMPILATION)
+#error "Never include this file directly, include libeic.h instead."
+#endif
+
+#ifndef ANDROID_HARDWARE_IDENTITY_EIC_PRESENTATION_H
+#define ANDROID_HARDWARE_IDENTITY_EIC_PRESENTATION_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "EicCbor.h"
+
+// The maximum size we support for public keys in reader certificates.
+#define EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE 65
+
+typedef struct {
+ int featureLevel;
+
+ uint8_t storageKey[EIC_AES_128_KEY_SIZE];
+ uint8_t credentialPrivateKey[EIC_P256_PRIV_KEY_SIZE];
+
+ uint8_t ephemeralPrivateKey[EIC_P256_PRIV_KEY_SIZE];
+
+ // The challenge generated with eicPresentationCreateAuthChallenge()
+ uint64_t authChallenge;
+
+ // Set by eicPresentationSetAuthToken() and contains the fields
+ // from the passed in authToken and verificationToken.
+ //
+ uint64_t authTokenChallenge;
+ uint64_t authTokenSecureUserId;
+ uint64_t authTokenTimestamp;
+ uint64_t verificationTokenTimestamp;
+
+ // The public key for the reader.
+ //
+ // (During the process of pushing reader certificates, this is also used to store
+ // the public key of the previously pushed certificate.)
+ //
+ uint8_t readerPublicKey[EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE];
+ size_t readerPublicKeySize;
+
+ // This is set to true only if eicPresentationValidateRequestMessage() successfully
+ // validated the requestMessage.
+ //
+ // Why even record this? Because there's no requirement the HAL actually calls that
+ // function and we validate ACPs before it's called... so it's possible that a
+ // compromised HAL could trick us into marking ACPs as authorized while they in fact
+ // aren't.
+ bool requestMessageValidated;
+ bool buildCbor;
+
+ // Set to true initialized as a test credential.
+ bool testCredential;
+
+ // These are bitmasks indicating which of the possible 32 access control profiles are
+ // authorized. They are built up by eicPresentationValidateAccessControlProfile().
+ //
+ uint32_t accessControlProfileMaskValidated; // True if the profile was validated.
+ uint32_t accessControlProfileMaskUsesReaderAuth; // True if the ACP is using reader auth
+ uint32_t accessControlProfileMaskFailedReaderAuth; // True if failed reader auth
+ uint32_t accessControlProfileMaskFailedUserAuth; // True if failed user auth
+
+ // SHA-256 for AdditionalData, updated for each entry.
+ uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE];
+
+ // SHA-256 of ProofOfProvisioning. Set to NUL-bytes or initialized from CredentialKeys data
+ // if credential was created with feature version 202101 or later.
+ uint8_t proofOfProvisioningSha256[EIC_SHA256_DIGEST_SIZE];
+
+ size_t expectedCborSizeAtEnd;
+ EicCbor cbor;
+} EicPresentation;
+
+bool eicPresentationInit(EicPresentation* ctx, bool testCredential, const char* docType,
+ const uint8_t* encryptedCredentialKeys,
+ size_t encryptedCredentialKeysSize);
+
+bool eicPresentationGenerateSigningKeyPair(EicPresentation* ctx, const char* docType, time_t now,
+ uint8_t* publicKeyCert, size_t* publicKeyCertSize,
+ uint8_t signingKeyBlob[60]);
+
+// Create an ephemeral key-pair.
+//
+// The private key is stored in |ctx->ephemeralPrivateKey| and also returned in
+// |ephemeralPrivateKey|.
+//
+bool eicPresentationCreateEphemeralKeyPair(EicPresentation* ctx,
+ uint8_t ephemeralPrivateKey[EIC_P256_PRIV_KEY_SIZE]);
+
+// Returns a non-zero challenge in |authChallenge|.
+bool eicPresentationCreateAuthChallenge(EicPresentation* ctx, uint64_t* authChallenge);
+
+// Starts retrieveing entries.
+//
+bool eicPresentationStartRetrieveEntries(EicPresentation* ctx);
+
+// Sets the auth-token.
+bool eicPresentationSetAuthToken(EicPresentation* ctx, uint64_t challenge, uint64_t secureUserId,
+ uint64_t authenticatorId, int hardwareAuthenticatorType,
+ uint64_t timeStamp, const uint8_t* mac, size_t macSize,
+ uint64_t verificationTokenChallenge,
+ uint64_t verificationTokenTimeStamp,
+ int verificationTokenSecurityLevel,
+ const uint8_t* verificationTokenMac,
+ size_t verificationTokenMacSize);
+
+// Function to push certificates in the reader certificate chain.
+//
+// This should start with the root certificate (e.g. the last in the chain) and
+// continue up the chain, ending with the certificate for the reader.
+//
+// Calls to this function should be interleaved with calls to the
+// eicPresentationValidateAccessControlProfile() function, see below.
+//
+bool eicPresentationPushReaderCert(EicPresentation* ctx, const uint8_t* certX509,
+ size_t certX509Size);
+
+// Checks an access control profile.
+//
+// Returns false if an error occurred while checking the profile (e.g. MAC doesn't check out).
+//
+// Returns in |accessGranted| whether access is granted.
+//
+// If |readerCertificate| is non-empty and the public key of one of those
+// certificates appear in the chain presented by the reader, this function must
+// be called after pushing that certificate using
+// eicPresentationPushReaderCert().
+//
+bool eicPresentationValidateAccessControlProfile(EicPresentation* ctx, int id,
+ const uint8_t* readerCertificate,
+ size_t readerCertificateSize,
+ bool userAuthenticationRequired, int timeoutMillis,
+ uint64_t secureUserId, const uint8_t mac[28],
+ bool* accessGranted);
+
+// Validates that the given requestMessage is signed by the public key in the
+// certificate last set with eicPresentationPushReaderCert().
+//
+// The format of the signature is the same encoding as the 'signature' field of
+// COSE_Sign1 - that is, it's the R and S integers both with the same length as
+// the key-size.
+//
+// Must be called after eicPresentationPushReaderCert() have been used to push
+// the final certificate. Which is the certificate of the reader itself.
+//
+bool eicPresentationValidateRequestMessage(EicPresentation* ctx, const uint8_t* sessionTranscript,
+ size_t sessionTranscriptSize,
+ const uint8_t* requestMessage, size_t requestMessageSize,
+ int coseSignAlg,
+ const uint8_t* readerSignatureOfToBeSigned,
+ size_t readerSignatureOfToBeSignedSize);
+
+typedef enum {
+ // Returned if access is granted.
+ EIC_ACCESS_CHECK_RESULT_OK,
+
+ // Returned if an error occurred checking for access.
+ EIC_ACCESS_CHECK_RESULT_FAILED,
+
+ // Returned if access was denied because item is configured without any
+ // access control profiles.
+ EIC_ACCESS_CHECK_RESULT_NO_ACCESS_CONTROL_PROFILES,
+
+ // Returned if access was denied because of user authentication.
+ EIC_ACCESS_CHECK_RESULT_USER_AUTHENTICATION_FAILED,
+
+ // Returned if access was denied because of reader authentication.
+ EIC_ACCESS_CHECK_RESULT_READER_AUTHENTICATION_FAILED,
+} EicAccessCheckResult;
+
+// Passes enough information to calculate the MACing key
+//
+bool eicPresentationCalcMacKey(EicPresentation* ctx, const uint8_t* sessionTranscript,
+ size_t sessionTranscriptSize,
+ const uint8_t readerEphemeralPublicKey[EIC_P256_PUB_KEY_SIZE],
+ const uint8_t signingKeyBlob[60], const char* docType,
+ unsigned int numNamespacesWithValues,
+ size_t expectedDeviceNamespacesSize);
+
+// The scratchSpace should be set to a buffer at least 512 bytes (ideally 1024
+// bytes, the bigger the better). It's done this way to avoid allocating stack
+// space.
+//
+EicAccessCheckResult eicPresentationStartRetrieveEntryValue(
+ EicPresentation* ctx, const char* nameSpace, const char* name,
+ unsigned int newNamespaceNumEntries, int32_t entrySize, const int* accessControlProfileIds,
+ size_t numAccessControlProfileIds, uint8_t* scratchSpace, size_t scratchSpaceSize);
+
+// Note: |content| must be big enough to hold |encryptedContentSize| - 28 bytes.
+//
+// The scratchSpace should be set to a buffer at least 512 bytes. It's done this way to
+// avoid allocating stack space.
+//
+bool eicPresentationRetrieveEntryValue(EicPresentation* ctx, const uint8_t* encryptedContent,
+ size_t encryptedContentSize, uint8_t* content,
+ const char* nameSpace, const char* name,
+ const int* accessControlProfileIds,
+ size_t numAccessControlProfileIds, uint8_t* scratchSpace,
+ size_t scratchSpaceSize);
+
+// Returns the HMAC-SHA256 of |ToBeMaced| as per RFC 8051 "6.3. How to Compute
+// and Verify a MAC".
+bool eicPresentationFinishRetrieval(EicPresentation* ctx, uint8_t* digestToBeMaced,
+ size_t* digestToBeMacedSize);
+
+// The data returned in |signatureOfToBeSigned| contains the ECDSA signature of
+// the ToBeSigned CBOR from RFC 8051 "4.4. Signing and Verification Process"
+// where content is set to the ProofOfDeletion CBOR.
+//
+bool eicPresentationDeleteCredential(EicPresentation* ctx, const char* docType,
+ const uint8_t* challenge, size_t challengeSize,
+ bool includeChallenge, size_t proofOfDeletionCborSize,
+ uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]);
+
+// The data returned in |signatureOfToBeSigned| contains the ECDSA signature of
+// the ToBeSigned CBOR from RFC 8051 "4.4. Signing and Verification Process"
+// where content is set to the ProofOfOwnership CBOR.
+//
+bool eicPresentationProveOwnership(EicPresentation* ctx, const char* docType, bool testCredential,
+ const uint8_t* challenge, size_t challengeSize,
+ size_t proofOfOwnershipCborSize,
+ uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANDROID_HARDWARE_IDENTITY_EIC_PRESENTATION_H
diff --git a/identity/aidl/default/libeic/EicProvisioning.c b/identity/aidl/default/libeic/EicProvisioning.c
new file mode 100644
index 0000000..3b4148e
--- /dev/null
+++ b/identity/aidl/default/libeic/EicProvisioning.c
@@ -0,0 +1,377 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "EicProvisioning.h"
+
+bool eicProvisioningInit(EicProvisioning* ctx, bool testCredential) {
+ eicMemSet(ctx, '\0', sizeof(EicProvisioning));
+ ctx->testCredential = testCredential;
+ if (!eicOpsRandom(ctx->storageKey, EIC_AES_128_KEY_SIZE)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool eicProvisioningInitForUpdate(EicProvisioning* ctx, bool testCredential, const char* docType,
+ const uint8_t* encryptedCredentialKeys,
+ size_t encryptedCredentialKeysSize) {
+ uint8_t credentialKeys[86];
+
+ // For feature version 202009 it's 52 bytes long and for feature version 202101 it's 86
+ // bytes (the additional data is the ProofOfProvisioning SHA-256). We need
+ // to support loading all feature versions.
+ //
+ bool expectPopSha256 = false;
+ if (encryptedCredentialKeysSize == 52 + 28) {
+ /* do nothing */
+ } else if (encryptedCredentialKeysSize == 86 + 28) {
+ expectPopSha256 = true;
+ } else {
+ eicDebug("Unexpected size %zd for encryptedCredentialKeys", encryptedCredentialKeysSize);
+ return false;
+ }
+
+ eicMemSet(ctx, '\0', sizeof(EicProvisioning));
+ ctx->testCredential = testCredential;
+
+ if (!eicOpsDecryptAes128Gcm(eicOpsGetHardwareBoundKey(testCredential), encryptedCredentialKeys,
+ encryptedCredentialKeysSize,
+ // DocType is the additionalAuthenticatedData
+ (const uint8_t*)docType, eicStrLen(docType), credentialKeys)) {
+ eicDebug("Error decrypting CredentialKeys");
+ return false;
+ }
+
+ // It's supposed to look like this;
+ //
+ // Feature version 202009:
+ //
+ // CredentialKeys = [
+ // bstr, ; storageKey, a 128-bit AES key
+ // bstr, ; credentialPrivKey, the private key for credentialKey
+ // ]
+ //
+ // Feature version 202101:
+ //
+ // CredentialKeys = [
+ // bstr, ; storageKey, a 128-bit AES key
+ // bstr, ; credentialPrivKey, the private key for credentialKey
+ // bstr ; proofOfProvisioning SHA-256
+ // ]
+ //
+ // where storageKey is 16 bytes, credentialPrivateKey is 32 bytes, and proofOfProvisioning
+ // SHA-256 is 32 bytes.
+ //
+ if (credentialKeys[0] != (expectPopSha256 ? 0x83 : 0x82) || // array of two or three elements
+ credentialKeys[1] != 0x50 || // 16-byte bstr
+ credentialKeys[18] != 0x58 || credentialKeys[19] != 0x20) { // 32-byte bstr
+ eicDebug("Invalid CBOR for CredentialKeys");
+ return false;
+ }
+ if (expectPopSha256) {
+ if (credentialKeys[52] != 0x58 || credentialKeys[53] != 0x20) { // 32-byte bstr
+ eicDebug("Invalid CBOR for CredentialKeys");
+ return false;
+ }
+ }
+ eicMemCpy(ctx->storageKey, credentialKeys + 2, EIC_AES_128_KEY_SIZE);
+ eicMemCpy(ctx->credentialPrivateKey, credentialKeys + 20, EIC_P256_PRIV_KEY_SIZE);
+ // Note: We don't care about the previous ProofOfProvisioning SHA-256
+ ctx->isUpdate = true;
+ return true;
+}
+
+bool eicProvisioningCreateCredentialKey(EicProvisioning* ctx, const uint8_t* challenge,
+ size_t challengeSize, const uint8_t* applicationId,
+ size_t applicationIdSize, uint8_t* publicKeyCert,
+ size_t* publicKeyCertSize) {
+ if (ctx->isUpdate) {
+ eicDebug("Cannot create CredentialKey on update");
+ return false;
+ }
+
+ if (!eicOpsCreateCredentialKey(ctx->credentialPrivateKey, challenge, challengeSize,
+ applicationId, applicationIdSize, ctx->testCredential,
+ publicKeyCert, publicKeyCertSize)) {
+ return false;
+ }
+ return true;
+}
+
+bool eicProvisioningStartPersonalization(EicProvisioning* ctx, int accessControlProfileCount,
+ const int* entryCounts, size_t numEntryCounts,
+ const char* docType,
+ size_t expectedProofOfProvisioningSize) {
+ if (numEntryCounts >= EIC_MAX_NUM_NAMESPACES) {
+ return false;
+ }
+ if (accessControlProfileCount >= EIC_MAX_NUM_ACCESS_CONTROL_PROFILE_IDS) {
+ return false;
+ }
+
+ ctx->numEntryCounts = numEntryCounts;
+ if (numEntryCounts > EIC_MAX_NUM_NAMESPACES) {
+ return false;
+ }
+ for (size_t n = 0; n < numEntryCounts; n++) {
+ if (entryCounts[n] >= 256) {
+ return false;
+ }
+ ctx->entryCounts[n] = entryCounts[n];
+ }
+ ctx->curNamespace = -1;
+ ctx->curNamespaceNumProcessed = 0;
+
+ eicCborInit(&ctx->cbor, NULL, 0);
+
+ // What we're going to sign is the COSE ToBeSigned structure which
+ // looks like the following:
+ //
+ // Sig_structure = [
+ // context : "Signature" / "Signature1" / "CounterSignature",
+ // body_protected : empty_or_serialized_map,
+ // ? sign_protected : empty_or_serialized_map,
+ // external_aad : bstr,
+ // payload : bstr
+ // ]
+ //
+ eicCborAppendArray(&ctx->cbor, 4);
+ eicCborAppendString(&ctx->cbor, "Signature1");
+
+ // The COSE Encoded protected headers is just a single field with
+ // COSE_LABEL_ALG (1) -> COSE_ALG_ECSDA_256 (-7). For simplicitly we just
+ // hard-code the CBOR encoding:
+ static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x26};
+ eicCborAppendByteString(&ctx->cbor, coseEncodedProtectedHeaders,
+ sizeof(coseEncodedProtectedHeaders));
+
+ // We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
+ // so external_aad is the empty bstr
+ static const uint8_t externalAad[0] = {};
+ eicCborAppendByteString(&ctx->cbor, externalAad, sizeof(externalAad));
+
+ // For the payload, the _encoded_ form follows here. We handle this by simply
+ // opening a bstr, and then writing the CBOR. This requires us to know the
+ // size of said bstr, ahead of time.
+ eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, expectedProofOfProvisioningSize);
+ ctx->expectedCborSizeAtEnd = expectedProofOfProvisioningSize + ctx->cbor.size;
+
+ eicOpsSha256Init(&ctx->proofOfProvisioningDigester);
+ eicCborEnableSecondaryDigesterSha256(&ctx->cbor, &ctx->proofOfProvisioningDigester);
+
+ eicCborAppendArray(&ctx->cbor, 5);
+ eicCborAppendString(&ctx->cbor, "ProofOfProvisioning");
+ eicCborAppendString(&ctx->cbor, docType);
+
+ eicCborAppendArray(&ctx->cbor, accessControlProfileCount);
+
+ return true;
+}
+
+bool eicProvisioningAddAccessControlProfile(EicProvisioning* ctx, int id,
+ const uint8_t* readerCertificate,
+ size_t readerCertificateSize,
+ bool userAuthenticationRequired, uint64_t timeoutMillis,
+ uint64_t secureUserId, uint8_t outMac[28]) {
+ uint8_t cborBuffer[EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE];
+ EicCbor cborBuilder;
+
+ eicCborInit(&cborBuilder, cborBuffer, EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE);
+
+ if (!eicCborCalcAccessControl(&cborBuilder, id, readerCertificate, readerCertificateSize,
+ userAuthenticationRequired, timeoutMillis, secureUserId)) {
+ return false;
+ }
+
+ // Calculate and return MAC
+ uint8_t nonce[12];
+ if (!eicOpsRandom(nonce, 12)) {
+ return false;
+ }
+ if (!eicOpsEncryptAes128Gcm(ctx->storageKey, nonce, NULL, 0, cborBuilder.buffer,
+ cborBuilder.size, outMac)) {
+ return false;
+ }
+
+ // The ACP CBOR in the provisioning receipt doesn't include secureUserId so build
+ // it again.
+ eicCborInit(&cborBuilder, cborBuffer, EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE);
+ if (!eicCborCalcAccessControl(&cborBuilder, id, readerCertificate, readerCertificateSize,
+ userAuthenticationRequired, timeoutMillis,
+ 0 /* secureUserId */)) {
+ return false;
+ }
+
+ // Append the CBOR from the local builder to the digester.
+ eicCborAppend(&ctx->cbor, cborBuilder.buffer, cborBuilder.size);
+
+ return true;
+}
+
+bool eicProvisioningBeginAddEntry(EicProvisioning* ctx, const int* accessControlProfileIds,
+ size_t numAccessControlProfileIds, const char* nameSpace,
+ const char* name, uint64_t entrySize, uint8_t* scratchSpace,
+ size_t scratchSpaceSize) {
+ uint8_t* additionalDataCbor = scratchSpace;
+ const size_t additionalDataCborBufSize = scratchSpaceSize;
+ size_t additionalDataCborSize;
+
+ // We'll need to calc and store a digest of additionalData to check that it's the same
+ // additionalData being passed in for every eicProvisioningAddEntryValue() call...
+ if (!eicCborCalcEntryAdditionalData(accessControlProfileIds, numAccessControlProfileIds,
+ nameSpace, name, additionalDataCbor,
+ additionalDataCborBufSize, &additionalDataCborSize,
+ ctx->additionalDataSha256)) {
+ return false;
+ }
+
+ if (ctx->curNamespace == -1) {
+ ctx->curNamespace = 0;
+ ctx->curNamespaceNumProcessed = 0;
+ // Opens the main map: { * Namespace => [ + Entry ] }
+ eicCborAppendMap(&ctx->cbor, ctx->numEntryCounts);
+ eicCborAppendString(&ctx->cbor, nameSpace);
+ // Opens the per-namespace array: [ + Entry ]
+ eicCborAppendArray(&ctx->cbor, ctx->entryCounts[ctx->curNamespace]);
+ }
+
+ if (ctx->curNamespaceNumProcessed == ctx->entryCounts[ctx->curNamespace]) {
+ ctx->curNamespace += 1;
+ ctx->curNamespaceNumProcessed = 0;
+ eicCborAppendString(&ctx->cbor, nameSpace);
+ // Opens the per-namespace array: [ + Entry ]
+ eicCborAppendArray(&ctx->cbor, ctx->entryCounts[ctx->curNamespace]);
+ }
+
+ eicCborAppendMap(&ctx->cbor, 3);
+ eicCborAppendString(&ctx->cbor, "name");
+ eicCborAppendString(&ctx->cbor, name);
+
+ ctx->curEntrySize = entrySize;
+ ctx->curEntryNumBytesReceived = 0;
+
+ eicCborAppendString(&ctx->cbor, "value");
+
+ ctx->curNamespaceNumProcessed += 1;
+ return true;
+}
+
+bool eicProvisioningAddEntryValue(EicProvisioning* ctx, const int* accessControlProfileIds,
+ size_t numAccessControlProfileIds, const char* nameSpace,
+ const char* name, const uint8_t* content, size_t contentSize,
+ uint8_t* outEncryptedContent, uint8_t* scratchSpace,
+ size_t scratchSpaceSize) {
+ uint8_t* additionalDataCbor = scratchSpace;
+ const size_t additionalDataCborBufSize = scratchSpaceSize;
+ size_t additionalDataCborSize;
+
+ uint8_t calculatedSha256[EIC_SHA256_DIGEST_SIZE];
+ if (!eicCborCalcEntryAdditionalData(accessControlProfileIds, numAccessControlProfileIds,
+ nameSpace, name, additionalDataCbor,
+ additionalDataCborBufSize, &additionalDataCborSize,
+ calculatedSha256)) {
+ return false;
+ }
+ if (eicCryptoMemCmp(calculatedSha256, ctx->additionalDataSha256, EIC_SHA256_DIGEST_SIZE) != 0) {
+ eicDebug("SHA-256 mismatch of additionalData");
+ return false;
+ }
+
+ eicCborAppend(&ctx->cbor, content, contentSize);
+
+ uint8_t nonce[12];
+ if (!eicOpsRandom(nonce, 12)) {
+ return false;
+ }
+ if (!eicOpsEncryptAes128Gcm(ctx->storageKey, nonce, content, contentSize, additionalDataCbor,
+ additionalDataCborSize, outEncryptedContent)) {
+ return false;
+ }
+
+ // If done with this entry, close the map
+ ctx->curEntryNumBytesReceived += contentSize;
+ if (ctx->curEntryNumBytesReceived == ctx->curEntrySize) {
+ eicCborAppendString(&ctx->cbor, "accessControlProfiles");
+ eicCborAppendArray(&ctx->cbor, numAccessControlProfileIds);
+ for (size_t n = 0; n < numAccessControlProfileIds; n++) {
+ eicCborAppendNumber(&ctx->cbor, accessControlProfileIds[n]);
+ }
+ }
+ return true;
+}
+
+bool eicProvisioningFinishAddingEntries(
+ EicProvisioning* ctx, uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]) {
+ uint8_t cborSha256[EIC_SHA256_DIGEST_SIZE];
+
+ eicCborAppendBool(&ctx->cbor, ctx->testCredential);
+ eicCborFinal(&ctx->cbor, cborSha256);
+
+ // This verifies that the correct expectedProofOfProvisioningSize value was
+ // passed in at eicStartPersonalization() time.
+ if (ctx->cbor.size != ctx->expectedCborSizeAtEnd) {
+ eicDebug("CBOR size is %zd, was expecting %zd", ctx->cbor.size, ctx->expectedCborSizeAtEnd);
+ return false;
+ }
+
+ if (!eicOpsEcDsa(ctx->credentialPrivateKey, cborSha256, signatureOfToBeSigned)) {
+ eicDebug("Error signing proofOfProvisioning");
+ return false;
+ }
+
+ return true;
+}
+
+bool eicProvisioningFinishGetCredentialData(EicProvisioning* ctx, const char* docType,
+ uint8_t* encryptedCredentialKeys,
+ size_t* encryptedCredentialKeysSize) {
+ EicCbor cbor;
+ uint8_t cborBuf[86];
+
+ if (*encryptedCredentialKeysSize < 86 + 28) {
+ eicDebug("encryptedCredentialKeysSize is %zd which is insufficient");
+ return false;
+ }
+
+ eicCborInit(&cbor, cborBuf, sizeof(cborBuf));
+ eicCborAppendArray(&cbor, 3);
+ eicCborAppendByteString(&cbor, ctx->storageKey, EIC_AES_128_KEY_SIZE);
+ eicCborAppendByteString(&cbor, ctx->credentialPrivateKey, EIC_P256_PRIV_KEY_SIZE);
+ uint8_t popSha256[EIC_SHA256_DIGEST_SIZE];
+ eicOpsSha256Final(&ctx->proofOfProvisioningDigester, popSha256);
+ eicCborAppendByteString(&cbor, popSha256, EIC_SHA256_DIGEST_SIZE);
+ if (cbor.size > sizeof(cborBuf)) {
+ eicDebug("Exceeded buffer size");
+ return false;
+ }
+
+ uint8_t nonce[12];
+ if (!eicOpsRandom(nonce, 12)) {
+ eicDebug("Error getting random");
+ return false;
+ }
+ if (!eicOpsEncryptAes128Gcm(
+ eicOpsGetHardwareBoundKey(ctx->testCredential), nonce, cborBuf, cbor.size,
+ // DocType is the additionalAuthenticatedData
+ (const uint8_t*)docType, eicStrLen(docType), encryptedCredentialKeys)) {
+ eicDebug("Error encrypting CredentialKeys");
+ return false;
+ }
+ *encryptedCredentialKeysSize = cbor.size + 28;
+
+ return true;
+}
diff --git a/identity/aidl/default/libeic/EicProvisioning.h b/identity/aidl/default/libeic/EicProvisioning.h
new file mode 100644
index 0000000..f064787
--- /dev/null
+++ b/identity/aidl/default/libeic/EicProvisioning.h
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if !defined(EIC_INSIDE_LIBEIC_H) && !defined(EIC_COMPILATION)
+#error "Never include this file directly, include libeic.h instead."
+#endif
+
+#ifndef ANDROID_HARDWARE_IDENTITY_EIC_PROVISIONING_H
+#define ANDROID_HARDWARE_IDENTITY_EIC_PROVISIONING_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "EicCbor.h"
+
+#define EIC_MAX_NUM_NAMESPACES 32
+#define EIC_MAX_NUM_ACCESS_CONTROL_PROFILE_IDS 32
+
+typedef struct {
+ // Set by eicCreateCredentialKey() OR eicProvisioningInitForUpdate()
+ uint8_t credentialPrivateKey[EIC_P256_PRIV_KEY_SIZE];
+
+ int numEntryCounts;
+ uint8_t entryCounts[EIC_MAX_NUM_NAMESPACES];
+
+ int curNamespace;
+ int curNamespaceNumProcessed;
+
+ size_t curEntrySize;
+ size_t curEntryNumBytesReceived;
+
+ // Set by eicProvisioningInit() OR eicProvisioningInitForUpdate()
+ uint8_t storageKey[EIC_AES_128_KEY_SIZE];
+
+ size_t expectedCborSizeAtEnd;
+
+ // SHA-256 for AdditionalData, updated for each entry.
+ uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE];
+
+ // Digester just for ProofOfProvisioning (without Sig_structure).
+ EicSha256Ctx proofOfProvisioningDigester;
+
+ EicCbor cbor;
+
+ bool testCredential;
+
+ // Set to true if this is an update.
+ bool isUpdate;
+} EicProvisioning;
+
+bool eicProvisioningInit(EicProvisioning* ctx, bool testCredential);
+
+bool eicProvisioningInitForUpdate(EicProvisioning* ctx, bool testCredential, const char* docType,
+ const uint8_t* encryptedCredentialKeys,
+ size_t encryptedCredentialKeysSize);
+
+bool eicProvisioningCreateCredentialKey(EicProvisioning* ctx, const uint8_t* challenge,
+ size_t challengeSize, const uint8_t* applicationId,
+ size_t applicationIdSize, uint8_t* publicKeyCert,
+ size_t* publicKeyCertSize);
+
+bool eicProvisioningStartPersonalization(EicProvisioning* ctx, int accessControlProfileCount,
+ const int* entryCounts, size_t numEntryCounts,
+ const char* docType,
+ size_t expectedProofOfProvisioningingSize);
+
+bool eicProvisioningAddAccessControlProfile(EicProvisioning* ctx, int id,
+ const uint8_t* readerCertificate,
+ size_t readerCertificateSize,
+ bool userAuthenticationRequired, uint64_t timeoutMillis,
+ uint64_t secureUserId, uint8_t outMac[28]);
+
+// The scratchSpace should be set to a buffer at least 512 bytes. It's done this way to
+// avoid allocating stack space.
+//
+bool eicProvisioningBeginAddEntry(EicProvisioning* ctx, const int* accessControlProfileIds,
+ size_t numAccessControlProfileIds, const char* nameSpace,
+ const char* name, uint64_t entrySize, uint8_t* scratchSpace,
+ size_t scratchSpaceSize);
+
+// The outEncryptedContent array must be contentSize + 28 bytes long.
+//
+// The scratchSpace should be set to a buffer at least 512 bytes. It's done this way to
+// avoid allocating stack space.
+//
+bool eicProvisioningAddEntryValue(EicProvisioning* ctx, const int* accessControlProfileIds,
+ size_t numAccessControlProfileIds, const char* nameSpace,
+ const char* name, const uint8_t* content, size_t contentSize,
+ uint8_t* outEncryptedContent, uint8_t* scratchSpace,
+ size_t scratchSpaceSize);
+
+// The data returned in |signatureOfToBeSigned| contains the ECDSA signature of
+// the ToBeSigned CBOR from RFC 8051 "4.4. Signing and Verification Process"
+// where content is set to the ProofOfProvisioninging CBOR.
+//
+bool eicProvisioningFinishAddingEntries(
+ EicProvisioning* ctx, uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]);
+
+//
+//
+// The |encryptedCredentialKeys| array is set to AES-GCM-ENC(HBK, R, CredentialKeys, docType)
+// where
+//
+// CredentialKeys = [
+// bstr, ; storageKey, a 128-bit AES key
+// bstr ; credentialPrivKey, the private key for credentialKey
+// bstr ; SHA-256(ProofOfProvisioning)
+// ]
+//
+// for feature version 202101. For feature version 202009 the third field was not present.
+//
+// Since |storageKey| is 16 bytes and |credentialPrivKey| is 32 bytes, the
+// encoded CBOR for CredentialKeys is 86 bytes and consequently
+// |encryptedCredentialKeys| will be no longer than 86 + 28 = 114 bytes.
+//
+bool eicProvisioningFinishGetCredentialData(EicProvisioning* ctx, const char* docType,
+ uint8_t* encryptedCredentialKeys,
+ size_t* encryptedCredentialKeysSize);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANDROID_HARDWARE_IDENTITY_EIC_PROVISIONING_H
diff --git a/identity/aidl/default/libeic/libeic.h b/identity/aidl/default/libeic/libeic.h
new file mode 100644
index 0000000..88abef8
--- /dev/null
+++ b/identity/aidl/default/libeic/libeic.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_LIBEIC_H
+#define ANDROID_HARDWARE_IDENTITY_LIBEIC_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* The EIC_INSIDE_LIBEIC_H preprocessor symbol is used to enforce
+ * library users to include only this file. All public interfaces, and
+ * only public interfaces, must be included here.
+ */
+#define EIC_INSIDE_LIBEIC_H
+#include "EicCbor.h"
+#include "EicOps.h"
+#include "EicPresentation.h"
+#include "EicProvisioning.h"
+#undef EIC_INSIDE_LIBEIC_H
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANDROID_HARDWARE_IDENTITY_LIBEIC_H
diff --git a/identity/aidl/default/service.cpp b/identity/aidl/default/service.cpp
index bf95df5..c290c08 100644
--- a/identity/aidl/default/service.cpp
+++ b/identity/aidl/default/service.cpp
@@ -22,20 +22,26 @@
#include "IdentityCredentialStore.h"
+#include "FakeSecureHardwareProxy.h"
+
+using ::android::sp;
using ::android::base::InitLogging;
using ::android::base::StderrLogger;
-using aidl::android::hardware::identity::IdentityCredentialStore;
+using ::aidl::android::hardware::identity::IdentityCredentialStore;
+using ::android::hardware::identity::FakeSecureHardwareProxyFactory;
+using ::android::hardware::identity::SecureHardwareProxyFactory;
int main(int /*argc*/, char* argv[]) {
InitLogging(argv, StderrLogger);
+ sp<SecureHardwareProxyFactory> hwProxyFactory = new FakeSecureHardwareProxyFactory();
+
ABinderProcess_setThreadPoolMaxThreadCount(0);
std::shared_ptr<IdentityCredentialStore> store =
- ndk::SharedRefBase::make<IdentityCredentialStore>();
+ ndk::SharedRefBase::make<IdentityCredentialStore>(hwProxyFactory);
const std::string instance = std::string() + IdentityCredentialStore::descriptor + "/default";
- LOG(INFO) << "instance: " << instance;
binder_status_t status = AServiceManager_addService(store->asBinder().get(), instance.c_str());
CHECK(status == STATUS_OK);
diff --git a/identity/aidl/vts/Android.bp b/identity/aidl/vts/Android.bp
index 03966de..f487a64 100644
--- a/identity/aidl/vts/Android.bp
+++ b/identity/aidl/vts/Android.bp
@@ -4,13 +4,21 @@
"VtsHalTargetTestDefaults",
"use_libaidlvintf_gtest_helper_static",
],
+ cflags: [
+ "-Wno-deprecated-declarations",
+ ],
srcs: [
- "VtsHalIdentityEndToEndTest.cpp",
"VtsIWritableIdentityCredentialTests.cpp",
- "VtsIdentityTestUtils.cpp",
+ "Util.cpp",
"VtsAttestationTests.cpp",
"UserAuthTests.cpp",
"ReaderAuthTests.cpp",
+ "DeleteCredentialTests.cpp",
+ "ProveOwnershipTests.cpp",
+ "UpdateCredentialTests.cpp",
+ "EndToEndTests.cpp",
+ "TestCredentialTests.cpp",
+ "AuthenticationKeyTests.cpp",
],
shared_libs: [
"libbinder",
@@ -22,9 +30,9 @@
"libpuresoftkeymasterdevice",
"android.hardware.keymaster@4.0",
"android.hardware.identity-support-lib",
- "android.hardware.identity-cpp",
- "android.hardware.keymaster-cpp",
- "android.hardware.keymaster-ndk_platform",
+ "android.hardware.identity-V3-cpp",
+ "android.hardware.keymaster-V3-cpp",
+ "android.hardware.keymaster-V3-ndk_platform",
"libkeymaster4support",
"libkeymaster4_1support",
],
diff --git a/identity/aidl/vts/AuthenticationKeyTests.cpp b/identity/aidl/vts/AuthenticationKeyTests.cpp
new file mode 100644
index 0000000..bda3e70
--- /dev/null
+++ b/identity/aidl/vts/AuthenticationKeyTests.cpp
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "TestCredentialTests"
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/keymaster/HardwareAuthToken.h>
+#include <aidl/android/hardware/keymaster/VerificationToken.h>
+#include <android-base/logging.h>
+#include <android/hardware/identity/IIdentityCredentialStore.h>
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <cppbor.h>
+#include <cppbor_parse.h>
+#include <gtest/gtest.h>
+#include <future>
+#include <map>
+#include <utility>
+
+#include "Util.h"
+
+namespace android::hardware::identity {
+
+using std::endl;
+using std::make_pair;
+using std::map;
+using std::optional;
+using std::pair;
+using std::string;
+using std::tie;
+using std::vector;
+
+using ::android::sp;
+using ::android::String16;
+using ::android::binder::Status;
+
+using ::android::hardware::keymaster::HardwareAuthToken;
+using ::android::hardware::keymaster::VerificationToken;
+
+class AuthenticationKeyTests : public testing::TestWithParam<string> {
+ public:
+ virtual void SetUp() override {
+ string halInstanceName = GetParam();
+ credentialStore_ = android::waitForDeclaredService<IIdentityCredentialStore>(
+ String16(halInstanceName.c_str()));
+ ASSERT_NE(credentialStore_, nullptr);
+ halApiVersion_ = credentialStore_->getInterfaceVersion();
+ }
+
+ sp<IIdentityCredentialStore> credentialStore_;
+ int halApiVersion_;
+};
+
+TEST_P(AuthenticationKeyTests, proofOfProvisionInAuthKeyCert) {
+ if (halApiVersion_ < 3) {
+ GTEST_SKIP() << "Need HAL API version 3, have " << halApiVersion_;
+ }
+
+ string docType = "org.iso.18013-5.2019.mdl";
+ sp<IWritableIdentityCredential> wc;
+ ASSERT_TRUE(credentialStore_
+ ->createCredential(docType,
+ true, // testCredential
+ &wc)
+ .isOk());
+
+ vector<uint8_t> attestationApplicationId = {};
+ vector<uint8_t> attestationChallenge = {1};
+ vector<Certificate> certChain;
+ ASSERT_TRUE(wc->getAttestationCertificate(attestationApplicationId, attestationChallenge,
+ &certChain)
+ .isOk());
+
+ optional<vector<uint8_t>> optCredentialPubKey =
+ support::certificateChainGetTopMostKey(certChain[0].encodedCertificate);
+ ASSERT_TRUE(optCredentialPubKey);
+ vector<uint8_t> credentialPubKey;
+ credentialPubKey = optCredentialPubKey.value();
+
+ size_t proofOfProvisioningSize = 112;
+ // Not in v1 HAL, may fail
+ wc->setExpectedProofOfProvisioningSize(proofOfProvisioningSize);
+
+ ASSERT_TRUE(wc->startPersonalization(1 /* numAccessControlProfiles */,
+ {1} /* numDataElementsPerNamespace */)
+ .isOk());
+
+ // Access control profile 0: open access - don't care about the returned SACP
+ SecureAccessControlProfile sacp;
+ ASSERT_TRUE(wc->addAccessControlProfile(1, {}, false, 0, 0, &sacp).isOk());
+
+ // Single entry - don't care about the returned encrypted data
+ vector<uint8_t> encryptedData;
+ vector<uint8_t> tstrLastName = cppbor::Tstr("Turing").encode();
+ ASSERT_TRUE(wc->beginAddEntry({1}, "ns", "Last name", tstrLastName.size()).isOk());
+ ASSERT_TRUE(wc->addEntryValue(tstrLastName, &encryptedData).isOk());
+
+ vector<uint8_t> proofOfProvisioningSignature;
+ vector<uint8_t> credentialData;
+ Status status = wc->finishAddingEntries(&credentialData, &proofOfProvisioningSignature);
+ EXPECT_TRUE(status.isOk()) << status.exceptionCode() << ": " << status.exceptionMessage();
+
+ optional<vector<uint8_t>> proofOfProvisioning =
+ support::coseSignGetPayload(proofOfProvisioningSignature);
+ ASSERT_TRUE(proofOfProvisioning);
+ string cborPretty = support::cborPrettyPrint(proofOfProvisioning.value(), 32, {});
+ EXPECT_EQ(
+ "[\n"
+ " 'ProofOfProvisioning',\n"
+ " 'org.iso.18013-5.2019.mdl',\n"
+ " [\n"
+ " {\n"
+ " 'id' : 1,\n"
+ " },\n"
+ " ],\n"
+ " {\n"
+ " 'ns' : [\n"
+ " {\n"
+ " 'name' : 'Last name',\n"
+ " 'value' : 'Turing',\n"
+ " 'accessControlProfiles' : [1, ],\n"
+ " },\n"
+ " ],\n"
+ " },\n"
+ " true,\n"
+ "]",
+ cborPretty);
+ // Make sure it's signed by the CredentialKey in the returned cert chain.
+ EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfProvisioningSignature,
+ {}, // Additional data
+ credentialPubKey));
+
+ // Now get a credential and have it create AuthenticationKey so we can check
+ // the certificate.
+ sp<IIdentityCredential> credential;
+ ASSERT_TRUE(credentialStore_
+ ->getCredential(
+ CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256,
+ credentialData, &credential)
+ .isOk());
+ vector<uint8_t> signingKeyBlob;
+ Certificate signingKeyCertificate;
+ ASSERT_TRUE(credential->generateSigningKeyPair(&signingKeyBlob, &signingKeyCertificate).isOk());
+ optional<vector<uint8_t>> signingPubKey =
+ support::certificateChainGetTopMostKey(signingKeyCertificate.encodedCertificate);
+ EXPECT_TRUE(signingPubKey);
+
+ // SHA-256(ProofOfProvisioning) is embedded in CBOR with the following CDDL
+ //
+ // ProofOfBinding = [
+ // "ProofOfBinding",
+ // bstr, // Contains the SHA-256 of ProofOfProvisioning
+ // ]
+ //
+ // Check that.
+ //
+ optional<vector<uint8_t>> proofOfBinding = support::certificateGetExtension(
+ signingKeyCertificate.encodedCertificate, "1.3.6.1.4.1.11129.2.1.26");
+ ASSERT_TRUE(proofOfBinding);
+ auto [item, _, message] = cppbor::parse(proofOfBinding.value());
+ ASSERT_NE(item, nullptr) << message;
+ const cppbor::Array* arrayItem = item->asArray();
+ ASSERT_NE(arrayItem, nullptr);
+ ASSERT_EQ(arrayItem->size(), 2);
+ const cppbor::Tstr* strItem = (*arrayItem)[0]->asTstr();
+ ASSERT_NE(strItem, nullptr);
+ EXPECT_EQ(strItem->value(), "ProofOfBinding");
+ const cppbor::Bstr* popSha256Item = (*arrayItem)[1]->asBstr();
+ ASSERT_NE(popSha256Item, nullptr);
+ EXPECT_EQ(popSha256Item->value(), support::sha256(proofOfProvisioning.value()));
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AuthenticationKeyTests);
+INSTANTIATE_TEST_SUITE_P(
+ Identity, AuthenticationKeyTests,
+ testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)),
+ android::PrintInstanceNameToString);
+
+} // namespace android::hardware::identity
diff --git a/identity/aidl/vts/DeleteCredentialTests.cpp b/identity/aidl/vts/DeleteCredentialTests.cpp
new file mode 100644
index 0000000..1d30067
--- /dev/null
+++ b/identity/aidl/vts/DeleteCredentialTests.cpp
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "DeleteCredentialTests"
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/keymaster/HardwareAuthToken.h>
+#include <aidl/android/hardware/keymaster/VerificationToken.h>
+#include <android-base/logging.h>
+#include <android/hardware/identity/IIdentityCredentialStore.h>
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <cppbor.h>
+#include <cppbor_parse.h>
+#include <gtest/gtest.h>
+#include <future>
+#include <map>
+#include <utility>
+
+#include "Util.h"
+
+namespace android::hardware::identity {
+
+using std::endl;
+using std::make_pair;
+using std::map;
+using std::optional;
+using std::pair;
+using std::string;
+using std::tie;
+using std::vector;
+
+using ::android::sp;
+using ::android::String16;
+using ::android::binder::Status;
+
+using ::android::hardware::keymaster::HardwareAuthToken;
+using ::android::hardware::keymaster::VerificationToken;
+
+class DeleteCredentialTests : public testing::TestWithParam<string> {
+ public:
+ virtual void SetUp() override {
+ credentialStore_ = android::waitForDeclaredService<IIdentityCredentialStore>(
+ String16(GetParam().c_str()));
+ ASSERT_NE(credentialStore_, nullptr);
+ halApiVersion_ = credentialStore_->getInterfaceVersion();
+ }
+
+ void provisionData();
+
+ // Set by provisionData
+ vector<uint8_t> credentialData_;
+ vector<uint8_t> credentialPubKey_;
+
+ sp<IIdentityCredentialStore> credentialStore_;
+ int halApiVersion_;
+};
+
+void DeleteCredentialTests::provisionData() {
+ string docType = "org.iso.18013-5.2019.mdl";
+ bool testCredential = true;
+ sp<IWritableIdentityCredential> wc;
+ ASSERT_TRUE(credentialStore_->createCredential(docType, testCredential, &wc).isOk());
+
+ vector<uint8_t> attestationApplicationId = {};
+ vector<uint8_t> attestationChallenge = {1};
+ vector<Certificate> certChain;
+ ASSERT_TRUE(wc->getAttestationCertificate(attestationApplicationId, attestationChallenge,
+ &certChain)
+ .isOk());
+
+ optional<vector<uint8_t>> optCredentialPubKey =
+ support::certificateChainGetTopMostKey(certChain[0].encodedCertificate);
+ ASSERT_TRUE(optCredentialPubKey);
+ credentialPubKey_ = optCredentialPubKey.value();
+
+ size_t proofOfProvisioningSize = 106;
+ // Not in v1 HAL, may fail
+ wc->setExpectedProofOfProvisioningSize(proofOfProvisioningSize);
+
+ ASSERT_TRUE(wc->startPersonalization(1 /* numAccessControlProfiles */,
+ {1} /* numDataElementsPerNamespace */)
+ .isOk());
+
+ // Access control profile 0: open access - don't care about the returned SACP
+ SecureAccessControlProfile sacp;
+ ASSERT_TRUE(wc->addAccessControlProfile(1, {}, false, 0, 0, &sacp).isOk());
+
+ // Single entry - don't care about the returned encrypted data
+ ASSERT_TRUE(wc->beginAddEntry({0}, "ns", "Some Data", 1).isOk());
+ vector<uint8_t> encryptedData;
+ ASSERT_TRUE(wc->addEntryValue({9}, &encryptedData).isOk());
+
+ vector<uint8_t> proofOfProvisioningSignature;
+ Status status = wc->finishAddingEntries(&credentialData_, &proofOfProvisioningSignature);
+ EXPECT_TRUE(status.isOk()) << status.exceptionCode() << ": " << status.exceptionMessage();
+}
+
+TEST_P(DeleteCredentialTests, Delete) {
+ provisionData();
+
+ sp<IIdentityCredential> credential;
+ ASSERT_TRUE(credentialStore_
+ ->getCredential(
+ CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256,
+ credentialData_, &credential)
+ .isOk());
+
+ vector<uint8_t> proofOfDeletionSignature;
+ ASSERT_TRUE(credential->deleteCredential(&proofOfDeletionSignature).isOk());
+ optional<vector<uint8_t>> proofOfDeletion =
+ support::coseSignGetPayload(proofOfDeletionSignature);
+ ASSERT_TRUE(proofOfDeletion);
+ string cborPretty = support::cborPrettyPrint(proofOfDeletion.value(), 32, {});
+ EXPECT_EQ("['ProofOfDeletion', 'org.iso.18013-5.2019.mdl', true, ]", cborPretty);
+ EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfDeletionSignature, {}, // Additional data
+ credentialPubKey_));
+}
+
+TEST_P(DeleteCredentialTests, DeleteWithChallenge) {
+ if (halApiVersion_ < 3) {
+ GTEST_SKIP() << "Need HAL API version 3, have " << halApiVersion_;
+ }
+
+ provisionData();
+
+ sp<IIdentityCredential> credential;
+ ASSERT_TRUE(credentialStore_
+ ->getCredential(
+ CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256,
+ credentialData_, &credential)
+ .isOk());
+
+ vector<uint8_t> challenge = {65, 66, 67};
+ vector<uint8_t> proofOfDeletionSignature;
+ ASSERT_TRUE(
+ credential->deleteCredentialWithChallenge(challenge, &proofOfDeletionSignature).isOk());
+ optional<vector<uint8_t>> proofOfDeletion =
+ support::coseSignGetPayload(proofOfDeletionSignature);
+ ASSERT_TRUE(proofOfDeletion);
+ string cborPretty = support::cborPrettyPrint(proofOfDeletion.value(), 32, {});
+ EXPECT_EQ("['ProofOfDeletion', 'org.iso.18013-5.2019.mdl', {0x41, 0x42, 0x43}, true, ]",
+ cborPretty);
+ EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfDeletionSignature, {}, // Additional data
+ credentialPubKey_));
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(DeleteCredentialTests);
+INSTANTIATE_TEST_SUITE_P(
+ Identity, DeleteCredentialTests,
+ testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)),
+ android::PrintInstanceNameToString);
+
+} // namespace android::hardware::identity
diff --git a/identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp b/identity/aidl/vts/EndToEndTests.cpp
similarity index 93%
rename from identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp
rename to identity/aidl/vts/EndToEndTests.cpp
index cdecb97..5798b4c 100644
--- a/identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp
+++ b/identity/aidl/vts/EndToEndTests.cpp
@@ -29,7 +29,7 @@
#include <map>
#include <tuple>
-#include "VtsIdentityTestUtils.h"
+#include "Util.h"
namespace android::hardware::identity {
@@ -50,18 +50,20 @@
using test_utils::validateAttestationCertificate;
-class IdentityAidl : public testing::TestWithParam<std::string> {
+class EndToEndTests : public testing::TestWithParam<std::string> {
public:
virtual void SetUp() override {
credentialStore_ = android::waitForDeclaredService<IIdentityCredentialStore>(
String16(GetParam().c_str()));
ASSERT_NE(credentialStore_, nullptr);
+ halApiVersion_ = credentialStore_->getInterfaceVersion();
}
sp<IIdentityCredentialStore> credentialStore_;
+ int halApiVersion_;
};
-TEST_P(IdentityAidl, hardwareInformation) {
+TEST_P(EndToEndTests, hardwareInformation) {
HardwareInformation info;
ASSERT_TRUE(credentialStore_->getHardwareInformation(&info).isOk());
ASSERT_GT(info.credentialStoreName.size(), 0);
@@ -69,20 +71,21 @@
ASSERT_GE(info.dataChunkSize, 256);
}
-tuple<bool, string, vector<uint8_t>, vector<uint8_t>> extractFromTestCredentialData(
- const vector<uint8_t>& credentialData) {
+tuple<bool, string, vector<uint8_t>, vector<uint8_t>, vector<uint8_t>>
+extractFromTestCredentialData(const vector<uint8_t>& credentialData) {
string docType;
vector<uint8_t> storageKey;
vector<uint8_t> credentialPrivKey;
+ vector<uint8_t> sha256Pop;
auto [item, _, message] = cppbor::parse(credentialData);
if (item == nullptr) {
- return make_tuple(false, docType, storageKey, credentialPrivKey);
+ return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop);
}
const cppbor::Array* arrayItem = item->asArray();
if (arrayItem == nullptr || arrayItem->size() != 3) {
- return make_tuple(false, docType, storageKey, credentialPrivKey);
+ return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop);
}
const cppbor::Tstr* docTypeItem = (*arrayItem)[0]->asTstr();
@@ -92,7 +95,7 @@
const cppbor::Bstr* encryptedCredentialKeysItem = (*arrayItem)[2]->asBstr();
if (docTypeItem == nullptr || testCredentialItem == nullptr ||
encryptedCredentialKeysItem == nullptr) {
- return make_tuple(false, docType, storageKey, credentialPrivKey);
+ return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop);
}
docType = docTypeItem->value();
@@ -103,28 +106,38 @@
optional<vector<uint8_t>> decryptedCredentialKeys =
support::decryptAes128Gcm(hardwareBoundKey, encryptedCredentialKeys, docTypeVec);
if (!decryptedCredentialKeys) {
- return make_tuple(false, docType, storageKey, credentialPrivKey);
+ return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop);
}
auto [dckItem, dckPos, dckMessage] = cppbor::parse(decryptedCredentialKeys.value());
if (dckItem == nullptr) {
- return make_tuple(false, docType, storageKey, credentialPrivKey);
+ return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop);
}
const cppbor::Array* dckArrayItem = dckItem->asArray();
- if (dckArrayItem == nullptr || dckArrayItem->size() != 2) {
- return make_tuple(false, docType, storageKey, credentialPrivKey);
+ if (dckArrayItem == nullptr) {
+ return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop);
+ }
+ if (dckArrayItem->size() < 2) {
+ return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop);
}
const cppbor::Bstr* storageKeyItem = (*dckArrayItem)[0]->asBstr();
const cppbor::Bstr* credentialPrivKeyItem = (*dckArrayItem)[1]->asBstr();
if (storageKeyItem == nullptr || credentialPrivKeyItem == nullptr) {
- return make_tuple(false, docType, storageKey, credentialPrivKey);
+ return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop);
}
storageKey = storageKeyItem->value();
credentialPrivKey = credentialPrivKeyItem->value();
- return make_tuple(true, docType, storageKey, credentialPrivKey);
+ if (dckArrayItem->size() == 3) {
+ const cppbor::Bstr* sha256PopItem = (*dckArrayItem)[2]->asBstr();
+ if (sha256PopItem == nullptr) {
+ return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop);
+ }
+ sha256Pop = sha256PopItem->value();
+ }
+ return make_tuple(true, docType, storageKey, credentialPrivKey, sha256Pop);
}
-TEST_P(IdentityAidl, createAndRetrieveCredential) {
+TEST_P(EndToEndTests, createAndRetrieveCredential) {
// First, generate a key-pair for the reader since its public key will be
// part of the request data.
vector<uint8_t> readerKey;
@@ -277,8 +290,9 @@
// Extract doctype, storage key, and credentialPrivKey from credentialData... this works
// only because we asked for a test-credential meaning that the HBK is all zeroes.
- auto [exSuccess, exDocType, exStorageKey, exCredentialPrivKey] =
+ auto [exSuccess, exDocType, exStorageKey, exCredentialPrivKey, exSha256Pop] =
extractFromTestCredentialData(credentialData);
+
ASSERT_TRUE(exSuccess);
ASSERT_EQ(exDocType, "org.iso.18013-5.2019.mdl");
// ... check that the public key derived from the private key matches what was
@@ -291,6 +305,13 @@
ASSERT_TRUE(exCredentialPubKey);
ASSERT_EQ(exCredentialPubKey.value(), credentialPubKey.value());
+ // Starting with API version 3 (feature version 202101) we require SHA-256(ProofOfProvisioning)
+ // to be in CredentialKeys (which is stored encrypted in CredentialData). Check
+ // that it's there with the expected value.
+ if (halApiVersion_ >= 3) {
+ ASSERT_EQ(exSha256Pop, support::sha256(proofOfProvisioning.value()));
+ }
+
// Now that the credential has been provisioned, read it back and check the
// correct data is returned.
sp<IIdentityCredential> credential;
@@ -498,13 +519,11 @@
EXPECT_EQ(mac, calculatedMac);
}
-GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(IdentityAidl);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EndToEndTests);
INSTANTIATE_TEST_SUITE_P(
- Identity, IdentityAidl,
+ Identity, EndToEndTests,
testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)),
android::PrintInstanceNameToString);
-// INSTANTIATE_TEST_SUITE_P(Identity, IdentityAidl,
-// testing::Values("android.hardware.identity.IIdentityCredentialStore/default"));
} // namespace android::hardware::identity
diff --git a/identity/aidl/vts/ProveOwnershipTests.cpp b/identity/aidl/vts/ProveOwnershipTests.cpp
new file mode 100644
index 0000000..d1a3d39
--- /dev/null
+++ b/identity/aidl/vts/ProveOwnershipTests.cpp
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "ProveOwnershipTests"
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/keymaster/HardwareAuthToken.h>
+#include <aidl/android/hardware/keymaster/VerificationToken.h>
+#include <android-base/logging.h>
+#include <android/hardware/identity/IIdentityCredentialStore.h>
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <cppbor.h>
+#include <cppbor_parse.h>
+#include <gtest/gtest.h>
+#include <future>
+#include <map>
+#include <utility>
+
+#include "Util.h"
+
+namespace android::hardware::identity {
+
+using std::endl;
+using std::make_pair;
+using std::map;
+using std::optional;
+using std::pair;
+using std::string;
+using std::tie;
+using std::vector;
+
+using ::android::sp;
+using ::android::String16;
+using ::android::binder::Status;
+
+using ::android::hardware::keymaster::HardwareAuthToken;
+using ::android::hardware::keymaster::VerificationToken;
+
+class ProveOwnershipTests : public testing::TestWithParam<string> {
+ public:
+ virtual void SetUp() override {
+ credentialStore_ = android::waitForDeclaredService<IIdentityCredentialStore>(
+ String16(GetParam().c_str()));
+ ASSERT_NE(credentialStore_, nullptr);
+ halApiVersion_ = credentialStore_->getInterfaceVersion();
+ }
+
+ void provisionData();
+
+ // Set by provisionData
+ vector<uint8_t> credentialData_;
+ vector<uint8_t> credentialPubKey_;
+
+ sp<IIdentityCredentialStore> credentialStore_;
+ int halApiVersion_;
+};
+
+void ProveOwnershipTests::provisionData() {
+ string docType = "org.iso.18013-5.2019.mdl";
+ bool testCredential = true;
+ sp<IWritableIdentityCredential> wc;
+ ASSERT_TRUE(credentialStore_->createCredential(docType, testCredential, &wc).isOk());
+
+ vector<uint8_t> attestationApplicationId = {};
+ vector<uint8_t> attestationChallenge = {1};
+ vector<Certificate> certChain;
+ ASSERT_TRUE(wc->getAttestationCertificate(attestationApplicationId, attestationChallenge,
+ &certChain)
+ .isOk());
+
+ optional<vector<uint8_t>> optCredentialPubKey =
+ support::certificateChainGetTopMostKey(certChain[0].encodedCertificate);
+ ASSERT_TRUE(optCredentialPubKey);
+ credentialPubKey_ = optCredentialPubKey.value();
+
+ size_t proofOfProvisioningSize = 106;
+ // Not in v1 HAL, may fail
+ wc->setExpectedProofOfProvisioningSize(proofOfProvisioningSize);
+
+ ASSERT_TRUE(wc->startPersonalization(1 /* numAccessControlProfiles */,
+ {1} /* numDataElementsPerNamespace */)
+ .isOk());
+
+ // Access control profile 0: open access - don't care about the returned SACP
+ SecureAccessControlProfile sacp;
+ ASSERT_TRUE(wc->addAccessControlProfile(1, {}, false, 0, 0, &sacp).isOk());
+
+ // Single entry - don't care about the returned encrypted data
+ ASSERT_TRUE(wc->beginAddEntry({0}, "ns", "Some Data", 1).isOk());
+ vector<uint8_t> encryptedData;
+ ASSERT_TRUE(wc->addEntryValue({9}, &encryptedData).isOk());
+
+ vector<uint8_t> proofOfProvisioningSignature;
+ Status status = wc->finishAddingEntries(&credentialData_, &proofOfProvisioningSignature);
+ EXPECT_TRUE(status.isOk()) << status.exceptionCode() << ": " << status.exceptionMessage();
+}
+
+TEST_P(ProveOwnershipTests, proveOwnership) {
+ if (halApiVersion_ < 3) {
+ GTEST_SKIP() << "Need HAL API version 3, have " << halApiVersion_;
+ }
+
+ provisionData();
+
+ sp<IIdentityCredential> credential;
+ ASSERT_TRUE(credentialStore_
+ ->getCredential(
+ CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256,
+ credentialData_, &credential)
+ .isOk());
+
+ vector<uint8_t> challenge = {17, 18};
+ vector<uint8_t> proofOfOwnershipSignature;
+ ASSERT_TRUE(credential->proveOwnership(challenge, &proofOfOwnershipSignature).isOk());
+ optional<vector<uint8_t>> proofOfOwnership =
+ support::coseSignGetPayload(proofOfOwnershipSignature);
+ ASSERT_TRUE(proofOfOwnership);
+ string cborPretty = support::cborPrettyPrint(proofOfOwnership.value(), 32, {});
+ EXPECT_EQ("['ProofOfOwnership', 'org.iso.18013-5.2019.mdl', {0x11, 0x12}, true, ]", cborPretty);
+ EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfOwnershipSignature, {}, // Additional data
+ credentialPubKey_));
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ProveOwnershipTests);
+INSTANTIATE_TEST_SUITE_P(
+ Identity, ProveOwnershipTests,
+ testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)),
+ android::PrintInstanceNameToString);
+
+} // namespace android::hardware::identity
diff --git a/identity/aidl/vts/ReaderAuthTests.cpp b/identity/aidl/vts/ReaderAuthTests.cpp
index 0a9fdc0..7656c8e 100644
--- a/identity/aidl/vts/ReaderAuthTests.cpp
+++ b/identity/aidl/vts/ReaderAuthTests.cpp
@@ -32,7 +32,7 @@
#include <map>
#include <utility>
-#include "VtsIdentityTestUtils.h"
+#include "Util.h"
namespace android::hardware::identity {
@@ -123,9 +123,9 @@
const vector<uint8_t>& signingKey) {
time_t validityNotBefore = 0;
time_t validityNotAfter = 0xffffffff;
- optional<vector<uint8_t>> cert =
- support::ecPublicKeyGenerateCertificate(publicKey, signingKey, "24601", "Issuer",
- "Subject", validityNotBefore, validityNotAfter);
+ optional<vector<uint8_t>> cert = support::ecPublicKeyGenerateCertificate(
+ publicKey, signingKey, "24601", "Issuer", "Subject", validityNotBefore,
+ validityNotAfter, {});
return cert.value();
}
diff --git a/identity/aidl/vts/TestCredentialTests.cpp b/identity/aidl/vts/TestCredentialTests.cpp
new file mode 100644
index 0000000..d53de3b
--- /dev/null
+++ b/identity/aidl/vts/TestCredentialTests.cpp
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "TestCredentialTests"
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/keymaster/HardwareAuthToken.h>
+#include <aidl/android/hardware/keymaster/VerificationToken.h>
+#include <android-base/logging.h>
+#include <android/hardware/identity/IIdentityCredentialStore.h>
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <cppbor.h>
+#include <cppbor_parse.h>
+#include <gtest/gtest.h>
+#include <future>
+#include <map>
+#include <utility>
+
+#include "Util.h"
+
+namespace android::hardware::identity {
+
+using std::endl;
+using std::make_pair;
+using std::map;
+using std::optional;
+using std::pair;
+using std::string;
+using std::tie;
+using std::vector;
+
+using ::android::sp;
+using ::android::String16;
+using ::android::binder::Status;
+
+using ::android::hardware::keymaster::HardwareAuthToken;
+using ::android::hardware::keymaster::VerificationToken;
+
+class TestCredentialTests : public testing::TestWithParam<string> {
+ public:
+ virtual void SetUp() override {
+ string halInstanceName = GetParam();
+ credentialStore_ = android::waitForDeclaredService<IIdentityCredentialStore>(
+ String16(halInstanceName.c_str()));
+ ASSERT_NE(credentialStore_, nullptr);
+ halApiVersion_ = credentialStore_->getInterfaceVersion();
+ }
+
+ sp<IIdentityCredentialStore> credentialStore_;
+ int halApiVersion_;
+};
+
+TEST_P(TestCredentialTests, testCredential) {
+ string docType = "org.iso.18013-5.2019.mdl";
+ sp<IWritableIdentityCredential> wc;
+ ASSERT_TRUE(credentialStore_
+ ->createCredential(docType,
+ true, // testCredential
+ &wc)
+ .isOk());
+
+ vector<uint8_t> attestationApplicationId = {};
+ vector<uint8_t> attestationChallenge = {1};
+ vector<Certificate> certChain;
+ ASSERT_TRUE(wc->getAttestationCertificate(attestationApplicationId, attestationChallenge,
+ &certChain)
+ .isOk());
+
+ optional<vector<uint8_t>> optCredentialPubKey =
+ support::certificateChainGetTopMostKey(certChain[0].encodedCertificate);
+ ASSERT_TRUE(optCredentialPubKey);
+ vector<uint8_t> credentialPubKey;
+ credentialPubKey = optCredentialPubKey.value();
+
+ size_t proofOfProvisioningSize = 112;
+ // Not in v1 HAL, may fail
+ wc->setExpectedProofOfProvisioningSize(proofOfProvisioningSize);
+
+ ASSERT_TRUE(wc->startPersonalization(1 /* numAccessControlProfiles */,
+ {1} /* numDataElementsPerNamespace */)
+ .isOk());
+
+ // Access control profile 0: open access - don't care about the returned SACP
+ SecureAccessControlProfile sacp;
+ ASSERT_TRUE(wc->addAccessControlProfile(1, {}, false, 0, 0, &sacp).isOk());
+
+ // Single entry - don't care about the returned encrypted data
+ vector<uint8_t> encryptedData;
+ vector<uint8_t> tstrLastName = cppbor::Tstr("Turing").encode();
+ ASSERT_TRUE(wc->beginAddEntry({1}, "ns", "Last name", tstrLastName.size()).isOk());
+ ASSERT_TRUE(wc->addEntryValue(tstrLastName, &encryptedData).isOk());
+
+ vector<uint8_t> proofOfProvisioningSignature;
+ vector<uint8_t> credentialData;
+ Status status = wc->finishAddingEntries(&credentialData, &proofOfProvisioningSignature);
+ EXPECT_TRUE(status.isOk()) << status.exceptionCode() << ": " << status.exceptionMessage();
+
+ optional<vector<uint8_t>> proofOfProvisioning =
+ support::coseSignGetPayload(proofOfProvisioningSignature);
+ ASSERT_TRUE(proofOfProvisioning);
+ string cborPretty = support::cborPrettyPrint(proofOfProvisioning.value(), 32, {});
+ EXPECT_EQ(
+ "[\n"
+ " 'ProofOfProvisioning',\n"
+ " 'org.iso.18013-5.2019.mdl',\n"
+ " [\n"
+ " {\n"
+ " 'id' : 1,\n"
+ " },\n"
+ " ],\n"
+ " {\n"
+ " 'ns' : [\n"
+ " {\n"
+ " 'name' : 'Last name',\n"
+ " 'value' : 'Turing',\n"
+ " 'accessControlProfiles' : [1, ],\n"
+ " },\n"
+ " ],\n"
+ " },\n"
+ " true,\n"
+ "]",
+ cborPretty);
+ // Make sure it's signed by the CredentialKey in the returned cert chain.
+ EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfProvisioningSignature,
+ {}, // Additional data
+ credentialPubKey));
+
+ // Now analyze credentialData..
+ auto [item, _, message] = cppbor::parse(credentialData);
+ ASSERT_NE(item, nullptr);
+ const cppbor::Array* arrayItem = item->asArray();
+ ASSERT_NE(arrayItem, nullptr);
+ ASSERT_EQ(arrayItem->size(), 3);
+ const cppbor::Tstr* docTypeItem = (*arrayItem)[0]->asTstr();
+ const cppbor::Bool* testCredentialItem =
+ ((*arrayItem)[1]->asSimple() != nullptr ? ((*arrayItem)[1]->asSimple()->asBool())
+ : nullptr);
+ EXPECT_EQ(docTypeItem->value(), docType);
+ EXPECT_EQ(testCredentialItem->value(), true);
+
+ vector<uint8_t> hardwareBoundKey = support::getTestHardwareBoundKey();
+ const cppbor::Bstr* encryptedCredentialKeysItem = (*arrayItem)[2]->asBstr();
+ const vector<uint8_t>& encryptedCredentialKeys = encryptedCredentialKeysItem->value();
+ const vector<uint8_t> docTypeVec(docType.begin(), docType.end());
+ optional<vector<uint8_t>> decryptedCredentialKeys =
+ support::decryptAes128Gcm(hardwareBoundKey, encryptedCredentialKeys, docTypeVec);
+ ASSERT_TRUE(decryptedCredentialKeys);
+ auto [dckItem, dckPos, dckMessage] = cppbor::parse(decryptedCredentialKeys.value());
+ ASSERT_NE(dckItem, nullptr) << dckMessage;
+ const cppbor::Array* dckArrayItem = dckItem->asArray();
+ ASSERT_NE(dckArrayItem, nullptr);
+ // In HAL API version 1 and 2 this array has two items, in version 3 and later it has three.
+ if (halApiVersion_ < 3) {
+ ASSERT_EQ(dckArrayItem->size(), 2);
+ } else {
+ ASSERT_EQ(dckArrayItem->size(), 3);
+ }
+ const cppbor::Bstr* storageKeyItem = (*dckArrayItem)[0]->asBstr();
+ const vector<uint8_t> storageKey = storageKeyItem->value();
+ // const cppbor::Bstr* credentialPrivKeyItem = (*dckArrayItem)[1]->asBstr();
+ // const vector<uint8_t> credentialPrivKey = credentialPrivKeyItem->value();
+
+ // Check storageKey can be used to decrypt |encryptedData| to |tstrLastName|
+ vector<uint8_t> additionalData = cppbor::Map()
+ .add("Namespace", "ns")
+ .add("Name", "Last name")
+ .add("AccessControlProfileIds", cppbor::Array().add(1))
+ .encode();
+ optional<vector<uint8_t>> decryptedDataItemValue =
+ support::decryptAes128Gcm(storageKey, encryptedData, additionalData);
+ ASSERT_TRUE(decryptedDataItemValue);
+ EXPECT_EQ(decryptedDataItemValue.value(), tstrLastName);
+
+ // Check that SHA-256(ProofOfProvisioning) matches (only in HAL API version 3)
+ if (halApiVersion_ >= 3) {
+ const cppbor::Bstr* popSha256Item = (*dckArrayItem)[2]->asBstr();
+ const vector<uint8_t> popSha256 = popSha256Item->value();
+ ASSERT_EQ(popSha256, support::sha256(proofOfProvisioning.value()));
+ }
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(TestCredentialTests);
+INSTANTIATE_TEST_SUITE_P(
+ Identity, TestCredentialTests,
+ testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)),
+ android::PrintInstanceNameToString);
+
+} // namespace android::hardware::identity
diff --git a/identity/aidl/vts/UpdateCredentialTests.cpp b/identity/aidl/vts/UpdateCredentialTests.cpp
new file mode 100644
index 0000000..9c5ca55
--- /dev/null
+++ b/identity/aidl/vts/UpdateCredentialTests.cpp
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "UpdateCredentialTests"
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/keymaster/HardwareAuthToken.h>
+#include <aidl/android/hardware/keymaster/VerificationToken.h>
+#include <android-base/logging.h>
+#include <android/hardware/identity/IIdentityCredentialStore.h>
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <cppbor.h>
+#include <cppbor_parse.h>
+#include <gtest/gtest.h>
+#include <future>
+#include <map>
+#include <utility>
+
+#include "Util.h"
+
+namespace android::hardware::identity {
+
+using std::endl;
+using std::make_pair;
+using std::map;
+using std::optional;
+using std::pair;
+using std::string;
+using std::tie;
+using std::vector;
+
+using ::android::sp;
+using ::android::String16;
+using ::android::binder::Status;
+
+using ::android::hardware::keymaster::HardwareAuthToken;
+using ::android::hardware::keymaster::VerificationToken;
+
+class UpdateCredentialTests : public testing::TestWithParam<string> {
+ public:
+ virtual void SetUp() override {
+ credentialStore_ = android::waitForDeclaredService<IIdentityCredentialStore>(
+ String16(GetParam().c_str()));
+ ASSERT_NE(credentialStore_, nullptr);
+ halApiVersion_ = credentialStore_->getInterfaceVersion();
+ }
+
+ void provisionData();
+
+ // Set by provisionData
+ vector<uint8_t> credentialData_;
+ vector<uint8_t> credentialPubKey_;
+
+ sp<IIdentityCredentialStore> credentialStore_;
+ int halApiVersion_;
+};
+
+void UpdateCredentialTests::provisionData() {
+ string docType = "org.iso.18013-5.2019.mdl";
+ bool testCredential = true;
+ sp<IWritableIdentityCredential> wc;
+ ASSERT_TRUE(credentialStore_->createCredential(docType, testCredential, &wc).isOk());
+
+ vector<uint8_t> attestationApplicationId = {};
+ vector<uint8_t> attestationChallenge = {1};
+ vector<Certificate> certChain;
+ ASSERT_TRUE(wc->getAttestationCertificate(attestationApplicationId, attestationChallenge,
+ &certChain)
+ .isOk());
+
+ optional<vector<uint8_t>> optCredentialPubKey =
+ support::certificateChainGetTopMostKey(certChain[0].encodedCertificate);
+ ASSERT_TRUE(optCredentialPubKey);
+ credentialPubKey_ = optCredentialPubKey.value();
+
+ size_t proofOfProvisioningSize = 112;
+ // Not in v1 HAL, may fail
+ wc->setExpectedProofOfProvisioningSize(proofOfProvisioningSize);
+
+ ASSERT_TRUE(wc->startPersonalization(1 /* numAccessControlProfiles */,
+ {1} /* numDataElementsPerNamespace */)
+ .isOk());
+
+ // Access control profile 0: open access - don't care about the returned SACP
+ SecureAccessControlProfile sacp;
+ ASSERT_TRUE(wc->addAccessControlProfile(1, {}, false, 0, 0, &sacp).isOk());
+
+ // Single entry - don't care about the returned encrypted data
+ vector<uint8_t> encryptedData;
+ vector<uint8_t> tstrLastName = cppbor::Tstr("Prince").encode();
+ ASSERT_TRUE(wc->beginAddEntry({1}, "ns", "Last name", tstrLastName.size()).isOk());
+ ASSERT_TRUE(wc->addEntryValue(tstrLastName, &encryptedData).isOk());
+
+ vector<uint8_t> proofOfProvisioningSignature;
+ Status status = wc->finishAddingEntries(&credentialData_, &proofOfProvisioningSignature);
+ EXPECT_TRUE(status.isOk()) << status.exceptionCode() << ": " << status.exceptionMessage();
+
+ optional<vector<uint8_t>> proofOfProvisioning =
+ support::coseSignGetPayload(proofOfProvisioningSignature);
+ ASSERT_TRUE(proofOfProvisioning);
+ string cborPretty = support::cborPrettyPrint(proofOfProvisioning.value(), 32, {});
+ EXPECT_EQ(
+ "[\n"
+ " 'ProofOfProvisioning',\n"
+ " 'org.iso.18013-5.2019.mdl',\n"
+ " [\n"
+ " {\n"
+ " 'id' : 1,\n"
+ " },\n"
+ " ],\n"
+ " {\n"
+ " 'ns' : [\n"
+ " {\n"
+ " 'name' : 'Last name',\n"
+ " 'value' : 'Prince',\n"
+ " 'accessControlProfiles' : [1, ],\n"
+ " },\n"
+ " ],\n"
+ " },\n"
+ " true,\n"
+ "]",
+ cborPretty);
+ // Make sure it's signed by the CredentialKey in the returned cert chain.
+ EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfProvisioningSignature,
+ {}, // Additional data
+ credentialPubKey_));
+}
+
+TEST_P(UpdateCredentialTests, updateCredential) {
+ if (halApiVersion_ < 3) {
+ GTEST_SKIP() << "Need HAL API version 3, have " << halApiVersion_;
+ }
+
+ provisionData();
+
+ sp<IIdentityCredential> credential;
+ ASSERT_TRUE(credentialStore_
+ ->getCredential(
+ CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256,
+ credentialData_, &credential)
+ .isOk());
+
+ sp<IWritableIdentityCredential> wc;
+ ASSERT_TRUE(credential->updateCredential(&wc).isOk());
+
+ // Getting an attestation cert should fail (because it's an update).
+ vector<uint8_t> attestationApplicationId = {};
+ vector<uint8_t> attestationChallenge = {1};
+ vector<Certificate> certChain;
+ Status result = wc->getAttestationCertificate(attestationApplicationId, attestationChallenge,
+ &certChain);
+ ASSERT_FALSE(result.isOk());
+ EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode());
+ EXPECT_EQ(IIdentityCredentialStore::STATUS_FAILED, result.serviceSpecificErrorCode());
+
+ // Now provision some new data...
+ //
+ size_t proofOfProvisioningSize = 117;
+ // Not in v1 HAL, may fail
+ wc->setExpectedProofOfProvisioningSize(proofOfProvisioningSize);
+
+ ASSERT_TRUE(wc->startPersonalization(1 /* numAccessControlProfiles */,
+ {1} /* numDataElementsPerNamespace */)
+ .isOk());
+
+ // Access control profile 0: open access - don't care about the returned SACP
+ SecureAccessControlProfile sacp;
+ ASSERT_TRUE(wc->addAccessControlProfile(2, {}, false, 0, 0, &sacp).isOk());
+
+ // Single entry - don't care about the returned encrypted data
+ vector<uint8_t> encryptedData;
+ vector<uint8_t> tstrLastName = cppbor::Tstr("T.A.F.K.A.P").encode();
+ ASSERT_TRUE(wc->beginAddEntry({2}, "ns", "Last name", tstrLastName.size()).isOk());
+ ASSERT_TRUE(wc->addEntryValue(tstrLastName, &encryptedData).isOk());
+
+ vector<uint8_t> proofOfProvisioningSignature;
+ Status status = wc->finishAddingEntries(&credentialData_, &proofOfProvisioningSignature);
+ EXPECT_TRUE(status.isOk()) << status.exceptionCode() << ": " << status.exceptionMessage();
+ optional<vector<uint8_t>> proofOfProvisioning =
+ support::coseSignGetPayload(proofOfProvisioningSignature);
+ ASSERT_TRUE(proofOfProvisioning);
+ string cborPretty = support::cborPrettyPrint(proofOfProvisioning.value(), 32, {});
+ EXPECT_EQ(
+ "[\n"
+ " 'ProofOfProvisioning',\n"
+ " 'org.iso.18013-5.2019.mdl',\n"
+ " [\n"
+ " {\n"
+ " 'id' : 2,\n"
+ " },\n"
+ " ],\n"
+ " {\n"
+ " 'ns' : [\n"
+ " {\n"
+ " 'name' : 'Last name',\n"
+ " 'value' : 'T.A.F.K.A.P',\n"
+ " 'accessControlProfiles' : [2, ],\n"
+ " },\n"
+ " ],\n"
+ " },\n"
+ " true,\n"
+ "]",
+ cborPretty);
+ // Make sure it's signed by the same CredentialKey we originally provisioned with.
+ EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfProvisioningSignature,
+ {}, // Additional data
+ credentialPubKey_));
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(UpdateCredentialTests);
+INSTANTIATE_TEST_SUITE_P(
+ Identity, UpdateCredentialTests,
+ testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)),
+ android::PrintInstanceNameToString);
+
+} // namespace android::hardware::identity
diff --git a/identity/aidl/vts/UserAuthTests.cpp b/identity/aidl/vts/UserAuthTests.cpp
index 327493c..ef89d1c 100644
--- a/identity/aidl/vts/UserAuthTests.cpp
+++ b/identity/aidl/vts/UserAuthTests.cpp
@@ -32,7 +32,7 @@
#include <map>
#include <utility>
-#include "VtsIdentityTestUtils.h"
+#include "Util.h"
namespace android::hardware::identity {
@@ -145,7 +145,7 @@
EXPECT_TRUE(status.isOk()) << status.exceptionCode() << ": " << status.exceptionMessage();
}
-// From ReaderAuthTest.cpp - TODO: consolidate with VtsIdentityTestUtils.h
+// From ReaderAuthTest.cpp - TODO: consolidate with Util.h
pair<vector<uint8_t>, vector<uint8_t>> generateReaderKey();
vector<uint8_t> generateReaderCert(const vector<uint8_t>& publicKey,
const vector<uint8_t>& signingKey);
diff --git a/identity/aidl/vts/VtsIdentityTestUtils.cpp b/identity/aidl/vts/Util.cpp
similarity index 98%
rename from identity/aidl/vts/VtsIdentityTestUtils.cpp
rename to identity/aidl/vts/Util.cpp
index 3b10651..1148cb0 100644
--- a/identity/aidl/vts/VtsIdentityTestUtils.cpp
+++ b/identity/aidl/vts/Util.cpp
@@ -14,15 +14,18 @@
* limitations under the License.
*/
-#define LOG_TAG "VtsIdentityTestUtils"
+#define LOG_TAG "Util"
-#include "VtsIdentityTestUtils.h"
+#include "Util.h"
+
+#include <android-base/logging.h>
#include <aidl/Gtest.h>
-#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
#include <keymaster/km_openssl/openssl_utils.h>
#include <keymasterV4_1/attestation_record.h>
#include <charconv>
+
#include <map>
namespace android::hardware::identity::test_utils {
@@ -35,6 +38,7 @@
using ::android::sp;
using ::android::String16;
+using ::android::base::StringPrintf;
using ::android::binder::Status;
using ::keymaster::X509_Ptr;
@@ -86,7 +90,7 @@
return support::ecPublicKeyGenerateCertificate(readerPublicKey.value(), readerKey.value(),
serialDecimal, issuer, subject,
- validityNotBefore, validityNotAfter);
+ validityNotBefore, validityNotAfter, {});
}
optional<vector<SecureAccessControlProfile>> addAccessControlProfiles(
diff --git a/identity/aidl/vts/VtsIdentityTestUtils.h b/identity/aidl/vts/Util.h
similarity index 99%
rename from identity/aidl/vts/VtsIdentityTestUtils.h
rename to identity/aidl/vts/Util.h
index 85c24f8..80e52a2 100644
--- a/identity/aidl/vts/VtsIdentityTestUtils.h
+++ b/identity/aidl/vts/Util.h
@@ -21,6 +21,7 @@
#include <android/hardware/identity/support/IdentityCredentialSupport.h>
#include <cppbor.h>
#include <cppbor_parse.h>
+#include <gtest/gtest.h>
namespace android::hardware::identity::test_utils {
diff --git a/identity/aidl/vts/VtsAttestationTests.cpp b/identity/aidl/vts/VtsAttestationTests.cpp
index 5529853..e12fe05 100644
--- a/identity/aidl/vts/VtsAttestationTests.cpp
+++ b/identity/aidl/vts/VtsAttestationTests.cpp
@@ -29,7 +29,7 @@
#include <future>
#include <map>
-#include "VtsIdentityTestUtils.h"
+#include "Util.h"
namespace android::hardware::identity {
diff --git a/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp b/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp
index 56e17ba..cc63c48 100644
--- a/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp
+++ b/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp
@@ -29,7 +29,7 @@
#include <future>
#include <map>
-#include "VtsIdentityTestUtils.h"
+#include "Util.h"
namespace android::hardware::identity {
@@ -182,7 +182,7 @@
false /* testCredential */));
// Verify set a large number of profile count and entry count is ok
- const vector<int32_t> entryCounts = {3000};
+ const vector<int32_t> entryCounts = {255};
writableCredential->setExpectedProofOfProvisioningSize(123456);
result = writableCredential->startPersonalization(25, entryCounts);
EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
diff --git a/identity/support/include/android/hardware/identity/support/IdentityCredentialSupport.h b/identity/support/include/android/hardware/identity/support/IdentityCredentialSupport.h
index 3aa5bb6..3b91de6 100644
--- a/identity/support/include/android/hardware/identity/support/IdentityCredentialSupport.h
+++ b/identity/support/include/android/hardware/identity/support/IdentityCredentialSupport.h
@@ -18,6 +18,7 @@
#define IDENTITY_SUPPORT_INCLUDE_IDENTITY_CREDENTIAL_UTILS_H_
#include <cstdint>
+#include <map>
#include <optional>
#include <string>
#include <tuple>
@@ -29,11 +30,12 @@
namespace identity {
namespace support {
+using ::std::map;
using ::std::optional;
+using ::std::pair;
using ::std::string;
using ::std::tuple;
using ::std::vector;
-using ::std::pair;
// The semantic tag for a bstr which includes Encoded CBOR (RFC 7049, section 2.4)
const int kSemanticTagEncodedCbor = 24;
@@ -221,6 +223,11 @@
//
optional<pair<time_t, time_t>> certificateGetValidity(const vector<uint8_t>& x509Certificate);
+// Looks for an extension with OID in |oidStr| which must be an stored as an OCTET STRING.
+//
+optional<vector<uint8_t>> certificateGetExtension(const vector<uint8_t>& x509Certificate,
+ const string& oidStr);
+
// Generates a X.509 certificate for |publicKey| (which must be in the format
// returned by ecKeyPairGetPublicKey()).
//
@@ -230,7 +237,8 @@
optional<vector<uint8_t>> ecPublicKeyGenerateCertificate(
const vector<uint8_t>& publicKey, const vector<uint8_t>& signingKey,
const string& serialDecimal, const string& issuer, const string& subject,
- time_t validityNotBefore, time_t validityNotAfter);
+ time_t validityNotBefore, time_t validityNotAfter,
+ const map<string, vector<uint8_t>>& extensions);
// Performs Elliptic-curve Diffie-Helman using |publicKey| (which must be in the
// format returned by ecKeyPairGetPublicKey()) and |privateKey| (which must be
diff --git a/identity/support/src/IdentityCredentialSupport.cpp b/identity/support/src/IdentityCredentialSupport.cpp
index 093120d..6418028 100644
--- a/identity/support/src/IdentityCredentialSupport.cpp
+++ b/identity/support/src/IdentityCredentialSupport.cpp
@@ -344,15 +344,22 @@
// Crypto functionality / abstraction.
// ---------------------------------------------------------------------------
-struct EVP_CIPHER_CTX_Deleter {
- void operator()(EVP_CIPHER_CTX* ctx) const {
- if (ctx != nullptr) {
- EVP_CIPHER_CTX_free(ctx);
- }
- }
-};
-
-using EvpCipherCtxPtr = unique_ptr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_Deleter>;
+using EvpCipherCtxPtr = bssl::UniquePtr<EVP_CIPHER_CTX>;
+using EC_KEY_Ptr = bssl::UniquePtr<EC_KEY>;
+using EVP_PKEY_Ptr = bssl::UniquePtr<EVP_PKEY>;
+using EVP_PKEY_CTX_Ptr = bssl::UniquePtr<EVP_PKEY_CTX>;
+using EC_GROUP_Ptr = bssl::UniquePtr<EC_GROUP>;
+using EC_POINT_Ptr = bssl::UniquePtr<EC_POINT>;
+using ECDSA_SIG_Ptr = bssl::UniquePtr<ECDSA_SIG>;
+using X509_Ptr = bssl::UniquePtr<X509>;
+using PKCS12_Ptr = bssl::UniquePtr<PKCS12>;
+using BIGNUM_Ptr = bssl::UniquePtr<BIGNUM>;
+using ASN1_INTEGER_Ptr = bssl::UniquePtr<ASN1_INTEGER>;
+using ASN1_TIME_Ptr = bssl::UniquePtr<ASN1_TIME>;
+using ASN1_OCTET_STRING_Ptr = bssl::UniquePtr<ASN1_OCTET_STRING>;
+using ASN1_OBJECT_Ptr = bssl::UniquePtr<ASN1_OBJECT>;
+using X509_NAME_Ptr = bssl::UniquePtr<X509_NAME>;
+using X509_EXTENSION_Ptr = bssl::UniquePtr<X509_EXTENSION>;
// bool getRandom(size_t numBytes, vector<uint8_t>& output) {
optional<vector<uint8_t>> getRandom(size_t numBytes) {
@@ -534,115 +541,6 @@
return encryptedData;
}
-struct EC_KEY_Deleter {
- void operator()(EC_KEY* key) const {
- if (key != nullptr) {
- EC_KEY_free(key);
- }
- }
-};
-using EC_KEY_Ptr = unique_ptr<EC_KEY, EC_KEY_Deleter>;
-
-struct EVP_PKEY_Deleter {
- void operator()(EVP_PKEY* key) const {
- if (key != nullptr) {
- EVP_PKEY_free(key);
- }
- }
-};
-using EVP_PKEY_Ptr = unique_ptr<EVP_PKEY, EVP_PKEY_Deleter>;
-
-struct EVP_PKEY_CTX_Deleter {
- void operator()(EVP_PKEY_CTX* ctx) const {
- if (ctx != nullptr) {
- EVP_PKEY_CTX_free(ctx);
- }
- }
-};
-using EVP_PKEY_CTX_Ptr = unique_ptr<EVP_PKEY_CTX, EVP_PKEY_CTX_Deleter>;
-
-struct EC_GROUP_Deleter {
- void operator()(EC_GROUP* group) const {
- if (group != nullptr) {
- EC_GROUP_free(group);
- }
- }
-};
-using EC_GROUP_Ptr = unique_ptr<EC_GROUP, EC_GROUP_Deleter>;
-
-struct EC_POINT_Deleter {
- void operator()(EC_POINT* point) const {
- if (point != nullptr) {
- EC_POINT_free(point);
- }
- }
-};
-
-using EC_POINT_Ptr = unique_ptr<EC_POINT, EC_POINT_Deleter>;
-
-struct ECDSA_SIG_Deleter {
- void operator()(ECDSA_SIG* sig) const {
- if (sig != nullptr) {
- ECDSA_SIG_free(sig);
- }
- }
-};
-using ECDSA_SIG_Ptr = unique_ptr<ECDSA_SIG, ECDSA_SIG_Deleter>;
-
-struct X509_Deleter {
- void operator()(X509* x509) const {
- if (x509 != nullptr) {
- X509_free(x509);
- }
- }
-};
-using X509_Ptr = unique_ptr<X509, X509_Deleter>;
-
-struct PKCS12_Deleter {
- void operator()(PKCS12* pkcs12) const {
- if (pkcs12 != nullptr) {
- PKCS12_free(pkcs12);
- }
- }
-};
-using PKCS12_Ptr = unique_ptr<PKCS12, PKCS12_Deleter>;
-
-struct BIGNUM_Deleter {
- void operator()(BIGNUM* bignum) const {
- if (bignum != nullptr) {
- BN_free(bignum);
- }
- }
-};
-using BIGNUM_Ptr = unique_ptr<BIGNUM, BIGNUM_Deleter>;
-
-struct ASN1_INTEGER_Deleter {
- void operator()(ASN1_INTEGER* value) const {
- if (value != nullptr) {
- ASN1_INTEGER_free(value);
- }
- }
-};
-using ASN1_INTEGER_Ptr = unique_ptr<ASN1_INTEGER, ASN1_INTEGER_Deleter>;
-
-struct ASN1_TIME_Deleter {
- void operator()(ASN1_TIME* value) const {
- if (value != nullptr) {
- ASN1_TIME_free(value);
- }
- }
-};
-using ASN1_TIME_Ptr = unique_ptr<ASN1_TIME, ASN1_TIME_Deleter>;
-
-struct X509_NAME_Deleter {
- void operator()(X509_NAME* value) const {
- if (value != nullptr) {
- X509_NAME_free(value);
- }
- }
-};
-using X509_NAME_Ptr = unique_ptr<X509_NAME, X509_NAME_Deleter>;
-
vector<uint8_t> certificateChainJoin(const vector<vector<uint8_t>>& certificateChain) {
vector<uint8_t> ret;
for (const vector<uint8_t>& certificate : certificateChain) {
@@ -935,9 +833,16 @@
optional<vector<vector<uint8_t>>> createAttestation(
const EVP_PKEY* key, const vector<uint8_t>& applicationId, const vector<uint8_t>& challenge,
uint64_t activeTimeMilliSeconds, uint64_t expireTimeMilliSeconds, bool isTestCredential) {
+ // Pretend to be implemented in a trusted environment just so we can pass
+ // the VTS tests. Of course, this is a pretend-only game since hopefully no
+ // relying party is ever going to trust our batch key and those keys above
+ // it.
+ ::keymaster::PureSoftKeymasterContext context(::keymaster::KmVersion::KEYMASTER_4_1,
+ KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT);
+
keymaster_error_t error;
::keymaster::CertificateChain attestation_chain =
- ::keymaster::getAttestationChain(KM_ALGORITHM_EC, &error);
+ context.GetAttestationChain(KM_ALGORITHM_EC, &error);
if (KM_ERROR_OK != error) {
LOG(ERROR) << "Error getting attestation chain " << error;
return {};
@@ -957,12 +862,6 @@
}
expireTimeMilliSeconds = bcNotAfter * 1000;
}
- const keymaster_key_blob_t* attestation_signing_key =
- ::keymaster::getAttestationKey(KM_ALGORITHM_EC, nullptr);
- if (attestation_signing_key == nullptr) {
- LOG(ERROR) << "Error getting attestation key";
- return {};
- }
::keymaster::X509_NAME_Ptr subjectName;
if (KM_ERROR_OK !=
@@ -976,8 +875,11 @@
i2d_X509_NAME(subjectName.get(), &subjectPtr);
+ uint64_t nowMilliSeconds = time(nullptr) * 1000;
::keymaster::AuthorizationSet auth_set(
::keymaster::AuthorizationSetBuilder()
+ .Authorization(::keymaster::TAG_CERTIFICATE_NOT_BEFORE, nowMilliSeconds)
+ .Authorization(::keymaster::TAG_CERTIFICATE_NOT_AFTER, expireTimeMilliSeconds)
.Authorization(::keymaster::TAG_ATTESTATION_CHALLENGE, challenge.data(),
challenge.size())
.Authorization(::keymaster::TAG_ACTIVE_DATETIME, activeTimeMilliSeconds)
@@ -1016,19 +918,11 @@
}
::keymaster::AuthorizationSet hwEnforced(hwEnforcedBuilder);
- // Pretend to be implemented in a trusted environment just so we can pass
- // the VTS tests. Of course, this is a pretend-only game since hopefully no
- // relying party is ever going to trust our batch key and those keys above
- // it.
- ::keymaster::PureSoftKeymasterContext context(::keymaster::KmVersion::KEYMASTER_4_1,
- KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT);
-
- ::keymaster::CertificateChain cert_chain_out = generate_attestation_from_EVP(
- key, swEnforced, hwEnforced, auth_set, context, move(attestation_chain),
- *attestation_signing_key, &error);
+ ::keymaster::CertificateChain cert_chain_out = generate_attestation(
+ key, swEnforced, hwEnforced, auth_set, {} /* attest_key */, context, &error);
if (KM_ERROR_OK != error) {
- LOG(ERROR) << "Error generate attestation from EVP key" << error;
+ LOG(ERROR) << "Error generating attestation from EVP key: " << error;
return {};
}
@@ -1221,8 +1115,19 @@
return {};
}
vector<uint8_t> privateKey;
- privateKey.resize(BN_num_bytes(bignum));
- BN_bn2bin(bignum, privateKey.data());
+
+ // Note that this may return fewer than 32 bytes so pad with zeroes since we
+ // want to always return 32 bytes.
+ size_t numBytes = BN_num_bytes(bignum);
+ if (numBytes > 32) {
+ LOG(ERROR) << "Size is " << numBytes << ", expected this to be 32 or less";
+ return {};
+ }
+ privateKey.resize(32);
+ for (size_t n = 0; n < 32 - numBytes; n++) {
+ privateKey[n] = 0x00;
+ }
+ BN_bn2bin(bignum, privateKey.data() + 32 - numBytes);
return privateKey;
}
@@ -1379,7 +1284,8 @@
optional<vector<uint8_t>> ecPublicKeyGenerateCertificate(
const vector<uint8_t>& publicKey, const vector<uint8_t>& signingKey,
const string& serialDecimal, const string& issuer, const string& subject,
- time_t validityNotBefore, time_t validityNotAfter) {
+ time_t validityNotBefore, time_t validityNotAfter,
+ const map<string, vector<uint8_t>>& extensions) {
auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
auto point = EC_POINT_Ptr(EC_POINT_new(group.get()));
if (EC_POINT_oct2point(group.get(), point.get(), publicKey.data(), publicKey.size(), nullptr) !=
@@ -1482,6 +1388,32 @@
return {};
}
+ for (auto const& [oidStr, blob] : extensions) {
+ ASN1_OBJECT_Ptr oid(
+ OBJ_txt2obj(oidStr.c_str(), 1)); // accept numerical dotted string form only
+ if (!oid.get()) {
+ LOG(ERROR) << "Error setting OID";
+ return {};
+ }
+ ASN1_OCTET_STRING_Ptr octetString(ASN1_OCTET_STRING_new());
+ if (!ASN1_OCTET_STRING_set(octetString.get(), blob.data(), blob.size())) {
+ LOG(ERROR) << "Error setting octet string for extension";
+ return {};
+ }
+
+ X509_EXTENSION_Ptr extension = X509_EXTENSION_Ptr(X509_EXTENSION_new());
+ extension.reset(X509_EXTENSION_create_by_OBJ(nullptr, oid.get(), 0 /* not critical */,
+ octetString.get()));
+ if (!extension.get()) {
+ LOG(ERROR) << "Error setting extension";
+ return {};
+ }
+ if (!X509_add_ext(x509.get(), extension.get(), -1)) {
+ LOG(ERROR) << "Error adding extension";
+ return {};
+ }
+ }
+
if (X509_sign(x509.get(), privPkey.get(), EVP_sha256()) == 0) {
LOG(ERROR) << "Error signing X509 certificate";
return {};
@@ -1650,6 +1582,44 @@
return publicKey;
}
+optional<vector<uint8_t>> certificateGetExtension(const vector<uint8_t>& x509Certificate,
+ const string& oidStr) {
+ vector<X509_Ptr> certs;
+ if (!parseX509Certificates(x509Certificate, certs)) {
+ return {};
+ }
+ if (certs.size() < 1) {
+ LOG(ERROR) << "No certificates in chain";
+ return {};
+ }
+
+ ASN1_OBJECT_Ptr oid(
+ OBJ_txt2obj(oidStr.c_str(), 1)); // accept numerical dotted string form only
+ if (!oid.get()) {
+ LOG(ERROR) << "Error setting OID";
+ return {};
+ }
+
+ int location = X509_get_ext_by_OBJ(certs[0].get(), oid.get(), -1 /* search from beginning */);
+ if (location == -1) {
+ return {};
+ }
+
+ X509_EXTENSION* ext = X509_get_ext(certs[0].get(), location);
+ if (ext == nullptr) {
+ return {};
+ }
+
+ ASN1_OCTET_STRING* octetString = X509_EXTENSION_get_data(ext);
+ if (octetString == nullptr) {
+ return {};
+ }
+ vector<uint8_t> result;
+ result.resize(octetString->length);
+ memcpy(result.data(), octetString->data, octetString->length);
+ return result;
+}
+
optional<pair<size_t, size_t>> certificateFindPublicKey(const vector<uint8_t>& x509Certificate) {
vector<X509_Ptr> certs;
if (!parseX509Certificates(x509Certificate, certs)) {
diff --git a/identity/support/tests/IdentityCredentialSupportTest.cpp b/identity/support/tests/IdentityCredentialSupportTest.cpp
index 266f263..509133c 100644
--- a/identity/support/tests/IdentityCredentialSupportTest.cpp
+++ b/identity/support/tests/IdentityCredentialSupportTest.cpp
@@ -271,7 +271,7 @@
optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair.value());
optional<vector<uint8_t>> cert = support::ecPublicKeyGenerateCertificate(
- pubKey.value(), privKey.value(), "0001", "someIssuer", "someSubject", 0, 0);
+ pubKey.value(), privKey.value(), "0001", "someIssuer", "someSubject", 0, 0, {});
certs.push_back(cert.value());
}
return support::certificateChainJoin(certs);
@@ -338,7 +338,7 @@
ASSERT_TRUE(pubKey);
optional<vector<uint8_t>> cert = support::ecPublicKeyGenerateCertificate(
- pubKey.value(), privKey.value(), "0001", "someIssuer", "someSubject", 0, 0);
+ pubKey.value(), privKey.value(), "0001", "someIssuer", "someSubject", 0, 0, {});
optional<vector<uint8_t>> extractedPubKey =
support::certificateChainGetTopMostKey(cert.value());
@@ -358,7 +358,7 @@
optional<vector<uint8_t>> otherPubKey = support::ecKeyPairGetPublicKey(keyPair.value());
ASSERT_TRUE(otherPubKey);
optional<vector<uint8_t>> otherCert = support::ecPublicKeyGenerateCertificate(
- otherPubKey.value(), privKey.value(), "0001", "someIssuer", "someSubject", 0, 0);
+ otherPubKey.value(), privKey.value(), "0001", "someIssuer", "someSubject", 0, 0, {});
// Now both cert and otherCert are two distinct certificates. Let's make a
// chain and check that certificateChainSplit() works as expected.
diff --git a/keymaster/4.1/default/OWNERS b/keymaster/4.1/default/OWNERS
index 335660d..2b2ad2a 100644
--- a/keymaster/4.1/default/OWNERS
+++ b/keymaster/4.1/default/OWNERS
@@ -1,2 +1,4 @@
+jbires@google.com
jdanis@google.com
swillden@google.com
+zeuthen@google.com
\ No newline at end of file
diff --git a/keymaster/4.1/support/OWNERS b/keymaster/4.1/support/OWNERS
index a9efe66..2b2ad2a 100644
--- a/keymaster/4.1/support/OWNERS
+++ b/keymaster/4.1/support/OWNERS
@@ -1,3 +1,4 @@
+jbires@google.com
jdanis@google.com
swillden@google.com
-jbires@google.com
+zeuthen@google.com
\ No newline at end of file
diff --git a/keymaster/4.1/vts/OWNERS b/keymaster/4.1/vts/OWNERS
index 335660d..2b2ad2a 100644
--- a/keymaster/4.1/vts/OWNERS
+++ b/keymaster/4.1/vts/OWNERS
@@ -1,2 +1,4 @@
+jbires@google.com
jdanis@google.com
swillden@google.com
+zeuthen@google.com
\ No newline at end of file
diff --git a/light/aidl/default/Android.bp b/light/aidl/default/Android.bp
index ae3f463..4e43ba9 100644
--- a/light/aidl/default/Android.bp
+++ b/light/aidl/default/Android.bp
@@ -7,7 +7,7 @@
shared_libs: [
"libbase",
"libbinder_ndk",
- "android.hardware.light-ndk_platform",
+ "android.hardware.light-V1-ndk_platform",
],
srcs: [
"Lights.cpp",
diff --git a/light/aidl/vts/functional/Android.bp b/light/aidl/vts/functional/Android.bp
index aa4719b..4c9356c 100644
--- a/light/aidl/vts/functional/Android.bp
+++ b/light/aidl/vts/functional/Android.bp
@@ -27,7 +27,7 @@
"libbinder",
],
static_libs: [
- "android.hardware.light-cpp",
+ "android.hardware.light-V1-cpp",
],
test_suites: [
"vts",
diff --git a/light/utils/Android.bp b/light/utils/Android.bp
index e901129..871f983 100644
--- a/light/utils/Android.bp
+++ b/light/utils/Android.bp
@@ -28,6 +28,6 @@
"libutils",
],
static_libs: [
- "android.hardware.light-cpp",
+ "android.hardware.light-V1-cpp",
],
}
diff --git a/media/omx/1.0/vts/functional/common/media_hidl_test_common.cpp b/media/omx/1.0/vts/functional/common/media_hidl_test_common.cpp
index 9184c56..ea29f03 100644
--- a/media/omx/1.0/vts/functional/common/media_hidl_test_common.cpp
+++ b/media/omx/1.0/vts/functional/common/media_hidl_test_common.cpp
@@ -215,6 +215,7 @@
ASSERT_NE(handle, nullptr);
*nStride = static_cast<int32_t>(stride);
+ buffer->handle = handle;
buffer->omxBuffer.nativeHandle = handle;
buffer->omxBuffer.attr.anwBuffer.width = nFrameWidth;
buffer->omxBuffer.attr.anwBuffer.height = nFrameHeight;
@@ -335,6 +336,18 @@
}
}
+// free buffers needed on a component port
+void freePortBuffers(android::Vector<BufferInfo>* buffArray, PortMode portMode, bool allocGrap) {
+ for (size_t i = 0; i < buffArray->size(); i++) {
+ if (portMode == PortMode::PRESET_ANW_BUFFER ||
+ (allocGrap && portMode == PortMode::DYNAMIC_ANW_BUFFER)) {
+ android::GraphicBufferAllocator& allocator = android::GraphicBufferAllocator::get();
+ android::status_t error = allocator.free((*buffArray)[i].handle);
+ ASSERT_EQ(error, android::NO_ERROR);
+ }
+ }
+}
+
// State Transition : Loaded -> Idle
// Note: This function does not make any background checks for this transition.
// The callee holds the reponsibility to ensure the legality of the transition.
@@ -399,11 +412,15 @@
// The callee holds the reponsibility to ensure the legality of the transition.
void changeStateIdletoLoaded(sp<IOmxNode> omxNode, sp<CodecObserver> observer,
android::Vector<BufferInfo>* iBuffer,
- android::Vector<BufferInfo>* oBuffer,
- OMX_U32 kPortIndexInput,
- OMX_U32 kPortIndexOutput) {
+ android::Vector<BufferInfo>* oBuffer, OMX_U32 kPortIndexInput,
+ OMX_U32 kPortIndexOutput, PortMode* portMode, bool allocGrap) {
android::hardware::media::omx::V1_0::Status status;
Message msg;
+ PortMode defaultPortMode[2], *pm;
+
+ defaultPortMode[0] = PortMode::PRESET_BYTE_BUFFER;
+ defaultPortMode[1] = PortMode::PRESET_BYTE_BUFFER;
+ pm = portMode ? portMode : defaultPortMode;
// set state to Loaded
status = omxNode->sendCommand(toRawCommandType(OMX_CommandStateSet),
@@ -446,6 +463,8 @@
ASSERT_EQ(msg.data.eventData.data1, OMX_CommandStateSet);
ASSERT_EQ(msg.data.eventData.data2, OMX_StateLoaded);
+ ASSERT_NO_FATAL_FAILURE(freePortBuffers(iBuffer, pm[0], allocGrap));
+ ASSERT_NO_FATAL_FAILURE(freePortBuffers(oBuffer, pm[1], allocGrap));
return;
}
diff --git a/media/omx/1.0/vts/functional/common/media_hidl_test_common.h b/media/omx/1.0/vts/functional/common/media_hidl_test_common.h
index b16c772..eddf83f 100644
--- a/media/omx/1.0/vts/functional/common/media_hidl_test_common.h
+++ b/media/omx/1.0/vts/functional/common/media_hidl_test_common.h
@@ -115,6 +115,7 @@
struct BufferInfo {
uint32_t id;
bufferOwner owner;
+ buffer_handle_t handle;
android::hardware::media::omx::V1_0::CodecBuffer omxBuffer;
::android::sp<IMemory> mMemory;
int32_t slot;
@@ -329,6 +330,9 @@
PortMode portMode = PortMode::PRESET_BYTE_BUFFER,
bool allocGrap = false);
+void freePortBuffers(android::Vector<BufferInfo>* buffArray, PortMode portMode,
+ bool allocGrap = false);
+
void changeStateLoadedtoIdle(sp<IOmxNode> omxNode, sp<CodecObserver> observer,
android::Vector<BufferInfo>* iBuffer,
android::Vector<BufferInfo>* oBuffer,
@@ -338,8 +342,9 @@
void changeStateIdletoLoaded(sp<IOmxNode> omxNode, sp<CodecObserver> observer,
android::Vector<BufferInfo>* iBuffer,
- android::Vector<BufferInfo>* oBuffer,
- OMX_U32 kPortIndexInput, OMX_U32 kPortIndexOutput);
+ android::Vector<BufferInfo>* oBuffer, OMX_U32 kPortIndexInput,
+ OMX_U32 kPortIndexOutput, PortMode* portMode = nullptr,
+ bool allocGrap = false);
void changeStateIdletoExecute(sp<IOmxNode> omxNode, sp<CodecObserver> observer);
diff --git a/media/omx/1.0/vts/functional/video/VtsHalMediaOmxV1_0TargetVideoDecTest.cpp b/media/omx/1.0/vts/functional/video/VtsHalMediaOmxV1_0TargetVideoDecTest.cpp
index 67b9895..d35ce65 100644
--- a/media/omx/1.0/vts/functional/video/VtsHalMediaOmxV1_0TargetVideoDecTest.cpp
+++ b/media/omx/1.0/vts/functional/video/VtsHalMediaOmxV1_0TargetVideoDecTest.cpp
@@ -451,6 +451,7 @@
status,
android::hardware::media::omx::V1_0::Status::TIMED_OUT);
+ ASSERT_NO_FATAL_FAILURE(freePortBuffers(oBuffer, oPortMode, true));
ASSERT_NO_FATAL_FAILURE(allocatePortBuffers(
omxNode, oBuffer, kPortIndexOutput, oPortMode, true));
status = observer->dequeueMessage(&msg, DEFAULT_TIMEOUT,
@@ -853,9 +854,9 @@
ASSERT_NO_FATAL_FAILURE(
changeStateExecutetoIdle(omxNode, observer, &iBuffer, &oBuffer));
// set state to executing
- ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer,
- &oBuffer, kPortIndexInput,
- kPortIndexOutput));
+ ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, &oBuffer,
+ kPortIndexInput, kPortIndexOutput, portMode,
+ true));
}
// Test for adaptive playback support
@@ -1001,9 +1002,9 @@
ASSERT_NO_FATAL_FAILURE(
changeStateExecutetoIdle(omxNode, observer, &iBuffer, &oBuffer));
// set state to executing
- ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer,
- &oBuffer, kPortIndexInput,
- kPortIndexOutput));
+ ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, &oBuffer,
+ kPortIndexInput, kPortIndexOutput, portMode,
+ true));
}
// end of sequence test
@@ -1067,9 +1068,9 @@
ASSERT_NO_FATAL_FAILURE(
changeStateExecutetoIdle(omxNode, observer, &iBuffer, &oBuffer));
// set state to executing
- ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer,
- &oBuffer, kPortIndexInput,
- kPortIndexOutput));
+ ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, &oBuffer,
+ kPortIndexInput, kPortIndexOutput, portMode,
+ true));
}
// end of sequence test
@@ -1188,9 +1189,9 @@
ASSERT_NO_FATAL_FAILURE(
changeStateExecutetoIdle(omxNode, observer, &iBuffer, &oBuffer));
// set state to executing
- ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer,
- &oBuffer, kPortIndexInput,
- kPortIndexOutput));
+ ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, &oBuffer,
+ kPortIndexInput, kPortIndexOutput, portMode,
+ true));
}
// end of sequence test
@@ -1295,9 +1296,9 @@
ASSERT_NO_FATAL_FAILURE(
changeStateExecutetoIdle(omxNode, observer, &iBuffer, &oBuffer));
// set state to executing
- ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer,
- &oBuffer, kPortIndexInput,
- kPortIndexOutput));
+ ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, &oBuffer,
+ kPortIndexInput, kPortIndexOutput, portMode,
+ true));
}
// test input/output port flush
@@ -1414,9 +1415,9 @@
ASSERT_NO_FATAL_FAILURE(
changeStateExecutetoIdle(omxNode, observer, &iBuffer, &oBuffer));
// set state to executing
- ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer,
- &oBuffer, kPortIndexInput,
- kPortIndexOutput));
+ ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, &oBuffer,
+ kPortIndexInput, kPortIndexOutput, portMode,
+ true));
}
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(VideoDecHidlTest);
diff --git a/media/omx/1.0/vts/functional/video/VtsHalMediaOmxV1_0TargetVideoEncTest.cpp b/media/omx/1.0/vts/functional/video/VtsHalMediaOmxV1_0TargetVideoEncTest.cpp
index 3c0734e..f24c6d1 100644
--- a/media/omx/1.0/vts/functional/video/VtsHalMediaOmxV1_0TargetVideoEncTest.cpp
+++ b/media/omx/1.0/vts/functional/video/VtsHalMediaOmxV1_0TargetVideoEncTest.cpp
@@ -1057,9 +1057,9 @@
ASSERT_NO_FATAL_FAILURE(changeStateExecutetoIdle(
omxNode, observer, &buffersource->iBuffer, &buffersource->oBuffer));
// set state to executing
- ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(
- omxNode, observer, &buffersource->iBuffer, &buffersource->oBuffer,
- kPortIndexInput, kPortIndexOutput));
+ ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &buffersource->iBuffer,
+ &buffersource->oBuffer, kPortIndexInput,
+ kPortIndexOutput, portMode));
// test for callbacks
EXPECT_EQ(buffersource->callback, 31);
}
@@ -1174,9 +1174,8 @@
ASSERT_NO_FATAL_FAILURE(
changeStateExecutetoIdle(omxNode, observer, &iBuffer, &oBuffer));
// set state to executing
- ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer,
- &oBuffer, kPortIndexInput,
- kPortIndexOutput));
+ ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, &oBuffer,
+ kPortIndexInput, kPortIndexOutput, portMode));
}
// test raw stream encode (input is ANW buffers)
@@ -1337,9 +1336,8 @@
changeStateExecutetoIdle(omxNode, observer, &iBuffer, &oBuffer));
EXPECT_EQ(portDef.nBufferCountActual, listener->freeBuffers);
// set state to executing
- ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer,
- &oBuffer, kPortIndexInput,
- kPortIndexOutput));
+ ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, &oBuffer,
+ kPortIndexInput, kPortIndexOutput, portMode));
returnval = producer->disconnect(
NATIVE_WINDOW_API_CPU, IGraphicBufferProducer::DisconnectMode::API);
@@ -1452,9 +1450,8 @@
changeStateExecutetoIdle(omxNode, observer, &iBuffer, &oBuffer));
EXPECT_EQ(portDef.nBufferCountActual, listener->freeBuffers);
// set state to executing
- ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer,
- &oBuffer, kPortIndexInput,
- kPortIndexOutput));
+ ASSERT_NO_FATAL_FAILURE(changeStateIdletoLoaded(omxNode, observer, &iBuffer, &oBuffer,
+ kPortIndexInput, kPortIndexOutput, portMode));
returnval = producer->disconnect(
NATIVE_WINDOW_API_CPU, IGraphicBufferProducer::DisconnectMode::API);
diff --git a/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/DeviceInfo.aidl b/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/DeviceInfo.aidl
index 00abff9..3e25c56 100644
--- a/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/DeviceInfo.aidl
+++ b/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/DeviceInfo.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
diff --git a/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/IMemtrack.aidl b/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/IMemtrack.aidl
index 844a1bb..2e2b68e 100644
--- a/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/IMemtrack.aidl
+++ b/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/IMemtrack.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
diff --git a/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/MemtrackRecord.aidl b/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/MemtrackRecord.aidl
index 09ecefc..0e15ce3 100644
--- a/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/MemtrackRecord.aidl
+++ b/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/MemtrackRecord.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
diff --git a/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/MemtrackType.aidl b/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/MemtrackType.aidl
index 7f3f939..b19869e 100644
--- a/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/MemtrackType.aidl
+++ b/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/MemtrackType.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
@@ -23,5 +38,4 @@
GRAPHICS = 2,
MULTIMEDIA = 3,
CAMERA = 4,
- NUM_TYPES = 5,
}
diff --git a/memtrack/aidl/android/hardware/memtrack/MemtrackType.aidl b/memtrack/aidl/android/hardware/memtrack/MemtrackType.aidl
index 715c6bf..5db735a 100644
--- a/memtrack/aidl/android/hardware/memtrack/MemtrackType.aidl
+++ b/memtrack/aidl/android/hardware/memtrack/MemtrackType.aidl
@@ -27,5 +27,4 @@
GRAPHICS = 2,
MULTIMEDIA = 3,
CAMERA = 4,
- NUM_TYPES,
}
diff --git a/memtrack/aidl/default/Android.bp b/memtrack/aidl/default/Android.bp
index 52f88c8..8d97bfc 100644
--- a/memtrack/aidl/default/Android.bp
+++ b/memtrack/aidl/default/Android.bp
@@ -21,7 +21,7 @@
shared_libs: [
"libbase",
"libbinder_ndk",
- "android.hardware.memtrack-ndk_platform",
+ "android.hardware.memtrack-V1-ndk_platform",
],
srcs: [
"main.cpp",
diff --git a/memtrack/aidl/default/Memtrack.cpp b/memtrack/aidl/default/Memtrack.cpp
index 7361719..49a6582 100644
--- a/memtrack/aidl/default/Memtrack.cpp
+++ b/memtrack/aidl/default/Memtrack.cpp
@@ -26,7 +26,8 @@
if (pid < 0) {
return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_ILLEGAL_ARGUMENT));
}
- if (type < MemtrackType::OTHER || type >= MemtrackType::NUM_TYPES) {
+ if (type != MemtrackType::OTHER && type != MemtrackType::GL && type != MemtrackType::GRAPHICS &&
+ type != MemtrackType::MULTIMEDIA && type != MemtrackType::CAMERA) {
return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION));
}
_aidl_return->clear();
@@ -35,6 +36,8 @@
ndk::ScopedAStatus Memtrack::getGpuDeviceInfo(std::vector<DeviceInfo>* _aidl_return) {
_aidl_return->clear();
+ DeviceInfo dev_info = {.id = 0, .name = "virtio_gpu"};
+ _aidl_return->emplace_back(dev_info);
return ndk::ScopedAStatus::ok();
}
diff --git a/memtrack/aidl/vts/Android.bp b/memtrack/aidl/vts/Android.bp
index ea36677..eff2a56 100644
--- a/memtrack/aidl/vts/Android.bp
+++ b/memtrack/aidl/vts/Android.bp
@@ -7,11 +7,12 @@
srcs: ["VtsHalMemtrackTargetTest.cpp"],
shared_libs: [
"libbinder_ndk",
+ "libvintf",
],
static_libs: [
- "android.hardware.memtrack-unstable-ndk_platform",
+ "android.hardware.memtrack-V1-ndk_platform",
],
test_suites: [
- "vts-core",
+ "vts",
],
}
diff --git a/memtrack/aidl/vts/VtsHalMemtrackTargetTest.cpp b/memtrack/aidl/vts/VtsHalMemtrackTargetTest.cpp
index 4d33101..8905f50 100644
--- a/memtrack/aidl/vts/VtsHalMemtrackTargetTest.cpp
+++ b/memtrack/aidl/vts/VtsHalMemtrackTargetTest.cpp
@@ -21,11 +21,15 @@
#include <aidl/android/hardware/memtrack/MemtrackType.h>
#include <android/binder_manager.h>
#include <android/binder_process.h>
+#include <vintf/VintfObject.h>
using aidl::android::hardware::memtrack::DeviceInfo;
using aidl::android::hardware::memtrack::IMemtrack;
using aidl::android::hardware::memtrack::MemtrackRecord;
using aidl::android::hardware::memtrack::MemtrackType;
+using android::vintf::KernelVersion;
+using android::vintf::RuntimeInfo;
+using android::vintf::VintfObject;
class MemtrackAidlTest : public testing::TestWithParam<std::string> {
public:
@@ -42,17 +46,19 @@
TEST_P(MemtrackAidlTest, GetMemoryInvalidPid) {
int pid = -1;
- MemtrackType type = MemtrackType::OTHER;
- std::vector<MemtrackRecord> records;
- auto status = memtrack_->getMemory(pid, type, &records);
+ for (MemtrackType type : ndk::enum_range<MemtrackType>()) {
+ std::vector<MemtrackRecord> records;
- EXPECT_EQ(status.getExceptionCode(), EX_ILLEGAL_ARGUMENT);
+ auto status = memtrack_->getMemory(pid, type, &records);
+
+ EXPECT_EQ(status.getExceptionCode(), EX_ILLEGAL_ARGUMENT);
+ }
}
TEST_P(MemtrackAidlTest, GetMemoryInvalidType) {
int pid = 1;
- MemtrackType type = MemtrackType::NUM_TYPES;
+ MemtrackType type = static_cast<MemtrackType>(-1);
std::vector<MemtrackRecord> records;
auto status = memtrack_->getMemory(pid, type, &records);
@@ -62,12 +68,13 @@
TEST_P(MemtrackAidlTest, GetMemory) {
int pid = 1;
- MemtrackType type = MemtrackType::OTHER;
- std::vector<MemtrackRecord> records;
+ for (MemtrackType type : ndk::enum_range<MemtrackType>()) {
+ std::vector<MemtrackRecord> records;
- auto status = memtrack_->getMemory(pid, type, &records);
+ auto status = memtrack_->getMemory(pid, type, &records);
- EXPECT_TRUE(status.isOk());
+ EXPECT_TRUE(status.isOk());
+ }
}
TEST_P(MemtrackAidlTest, GetGpuDeviceInfo) {
@@ -75,7 +82,23 @@
auto status = memtrack_->getGpuDeviceInfo(&device_info);
+ // Devices with < 5.4 kernels aren't required to provide an implementation of
+ // getGpuDeviceInfo(), and can return EX_UNSUPPORTED_OPERATION
+ if (status.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
+ KernelVersion min_kernel_version = KernelVersion(5, 4, 0);
+ KernelVersion kernel_version = VintfObject::GetInstance()
+ ->getRuntimeInfo(RuntimeInfo::FetchFlag::CPU_VERSION)
+ ->kernelVersion();
+ EXPECT_LT(kernel_version, min_kernel_version)
+ << "Devices with 5.4 or later kernels must implement getGpuDeviceInfo()";
+ return;
+ }
+
EXPECT_TRUE(status.isOk());
+ EXPECT_FALSE(device_info.empty());
+ for (auto device : device_info) {
+ EXPECT_FALSE(device.name.empty());
+ }
}
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MemtrackAidlTest);
diff --git a/neuralnetworks/1.0/utils/Android.bp b/neuralnetworks/1.0/utils/Android.bp
index 4d61fc0..d033617 100644
--- a/neuralnetworks/1.0/utils/Android.bp
+++ b/neuralnetworks/1.0/utils/Android.bp
@@ -32,3 +32,29 @@
"neuralnetworks_utils_hal_common",
],
}
+
+cc_test {
+ name: "neuralnetworks_utils_hal_1_0_test",
+ srcs: ["test/*.cpp"],
+ static_libs: [
+ "android.hardware.neuralnetworks@1.0",
+ "libgmock",
+ "libneuralnetworks_common",
+ "neuralnetworks_types",
+ "neuralnetworks_utils_hal_common",
+ "neuralnetworks_utils_hal_1_0",
+ ],
+ shared_libs: [
+ "android.hidl.allocator@1.0",
+ "android.hidl.memory@1.0",
+ "libbase",
+ "libcutils",
+ "libfmq",
+ "libhidlbase",
+ "libhidlmemory",
+ "liblog",
+ "libnativewindow",
+ "libutils",
+ ],
+ test_suites: ["general-tests"],
+}
diff --git a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Burst.h b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Burst.h
new file mode 100644
index 0000000..8329303
--- /dev/null
+++ b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Burst.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_BURST_H
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_BURST_H
+
+#include <nnapi/IBurst.h>
+#include <nnapi/IPreparedModel.h>
+#include <nnapi/Result.h>
+#include <nnapi/Types.h>
+
+#include <memory>
+#include <optional>
+#include <utility>
+
+// See hardware/interfaces/neuralnetworks/utils/README.md for more information on HIDL interface
+// lifetimes across processes and for protecting asynchronous calls across HIDL.
+
+namespace android::hardware::neuralnetworks::V1_0::utils {
+
+// Class that adapts nn::IPreparedModel to nn::IBurst.
+class Burst final : public nn::IBurst {
+ struct PrivateConstructorTag {};
+
+ public:
+ static nn::GeneralResult<std::shared_ptr<const Burst>> create(
+ nn::SharedPreparedModel preparedModel);
+
+ Burst(PrivateConstructorTag tag, nn::SharedPreparedModel preparedModel);
+
+ OptionalCacheHold cacheMemory(const nn::SharedMemory& memory) const override;
+
+ nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> execute(
+ const nn::Request& request, nn::MeasureTiming measure) const override;
+
+ private:
+ const nn::SharedPreparedModel kPreparedModel;
+};
+
+} // namespace android::hardware::neuralnetworks::V1_0::utils
+
+#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_BURST_H
diff --git a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Conversions.h b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Conversions.h
index d3d933b..5d4bdbc 100644
--- a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Conversions.h
+++ b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Conversions.h
@@ -36,7 +36,7 @@
GeneralResult<Operation> unvalidatedConvert(const hal::V1_0::Operation& operation);
GeneralResult<Model::OperandValues> unvalidatedConvert(
const hardware::hidl_vec<uint8_t>& operandValues);
-GeneralResult<Memory> unvalidatedConvert(const hardware::hidl_memory& memory);
+GeneralResult<SharedMemory> unvalidatedConvert(const hardware::hidl_memory& memory);
GeneralResult<Model> unvalidatedConvert(const hal::V1_0::Model& model);
GeneralResult<Request::Argument> unvalidatedConvert(
const hal::V1_0::RequestArgument& requestArgument);
@@ -65,7 +65,7 @@
nn::GeneralResult<Operation> unvalidatedConvert(const nn::Operation& operation);
nn::GeneralResult<hidl_vec<uint8_t>> unvalidatedConvert(
const nn::Model::OperandValues& operandValues);
-nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::Memory& memory);
+nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::SharedMemory& memory);
nn::GeneralResult<Model> unvalidatedConvert(const nn::Model& model);
nn::GeneralResult<RequestArgument> unvalidatedConvert(const nn::Request::Argument& requestArgument);
nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::Request::MemoryPool& memoryPool);
diff --git a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Device.h b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Device.h
index db3b2ad..4681b9e 100644
--- a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Device.h
+++ b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Device.h
@@ -52,6 +52,7 @@
const std::string& getVersionString() const override;
nn::Version getFeatureLevel() const override;
nn::DeviceType getType() const override;
+ bool isUpdatable() const override;
const std::vector<nn::Extension>& getSupportedExtensions() const override;
const nn::Capabilities& getCapabilities() const override;
std::pair<uint32_t, uint32_t> getNumberOfCacheFilesNeeded() const override;
diff --git a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/PreparedModel.h b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/PreparedModel.h
index 2de1828..8853eea 100644
--- a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/PreparedModel.h
+++ b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/PreparedModel.h
@@ -35,7 +35,8 @@
namespace android::hardware::neuralnetworks::V1_0::utils {
// Class that adapts V1_0::IPreparedModel to nn::IPreparedModel.
-class PreparedModel final : public nn::IPreparedModel {
+class PreparedModel final : public nn::IPreparedModel,
+ public std::enable_shared_from_this<PreparedModel> {
struct PrivateConstructorTag {};
public:
@@ -56,6 +57,8 @@
const nn::OptionalDuration& loopTimeoutDuration,
const nn::OptionalDuration& timeoutDurationAfterFence) const override;
+ nn::GeneralResult<nn::SharedBurst> configureExecutionBurst() const override;
+
std::any getUnderlyingResource() const override;
private:
diff --git a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Utils.h b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Utils.h
index 4cec545..b695f48 100644
--- a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Utils.h
+++ b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Utils.h
@@ -44,6 +44,12 @@
return result.has_value();
}
+template <typename Type>
+auto convertFromNonCanonical(const Type& nonCanonicalObject)
+ -> decltype(convert(nn::convert(nonCanonicalObject).value())) {
+ return convert(NN_TRY(nn::convert(nonCanonicalObject)));
+}
+
} // namespace android::hardware::neuralnetworks::V1_0::utils
#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_H
diff --git a/neuralnetworks/1.0/utils/src/Burst.cpp b/neuralnetworks/1.0/utils/src/Burst.cpp
new file mode 100644
index 0000000..971ad08
--- /dev/null
+++ b/neuralnetworks/1.0/utils/src/Burst.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Burst.h"
+
+#include <android-base/logging.h>
+#include <nnapi/IBurst.h>
+#include <nnapi/IPreparedModel.h>
+#include <nnapi/Result.h>
+#include <nnapi/Types.h>
+
+#include <memory>
+#include <optional>
+#include <utility>
+
+namespace android::hardware::neuralnetworks::V1_0::utils {
+
+nn::GeneralResult<std::shared_ptr<const Burst>> Burst::create(
+ nn::SharedPreparedModel preparedModel) {
+ if (preparedModel == nullptr) {
+ return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
+ << "V1_0::utils::Burst::create must have non-null preparedModel";
+ }
+
+ return std::make_shared<const Burst>(PrivateConstructorTag{}, std::move(preparedModel));
+}
+
+Burst::Burst(PrivateConstructorTag /*tag*/, nn::SharedPreparedModel preparedModel)
+ : kPreparedModel(std::move(preparedModel)) {
+ CHECK(kPreparedModel != nullptr);
+}
+
+Burst::OptionalCacheHold Burst::cacheMemory(const nn::SharedMemory& /*memory*/) const {
+ return nullptr;
+}
+
+nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> Burst::execute(
+ const nn::Request& request, nn::MeasureTiming measure) const {
+ return kPreparedModel->execute(request, measure, {}, {});
+}
+
+} // namespace android::hardware::neuralnetworks::V1_0::utils
diff --git a/neuralnetworks/1.0/utils/src/Conversions.cpp b/neuralnetworks/1.0/utils/src/Conversions.cpp
index fde7346..7a099cf 100644
--- a/neuralnetworks/1.0/utils/src/Conversions.cpp
+++ b/neuralnetworks/1.0/utils/src/Conversions.cpp
@@ -153,8 +153,8 @@
return Model::OperandValues(operandValues.data(), operandValues.size());
}
-GeneralResult<Memory> unvalidatedConvert(const hidl_memory& memory) {
- return createSharedMemoryFromHidlMemory(memory);
+GeneralResult<SharedMemory> unvalidatedConvert(const hidl_memory& memory) {
+ return hal::utils::createSharedMemoryFromHidlMemory(memory);
}
GeneralResult<Model> unvalidatedConvert(const hal::V1_0::Model& model) {
@@ -346,9 +346,8 @@
return hidl_vec<uint8_t>(operandValues.data(), operandValues.data() + operandValues.size());
}
-nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::Memory& memory) {
- return hidl_memory(memory.name, NN_TRY(hal::utils::hidlHandleFromSharedHandle(memory.handle)),
- memory.size);
+nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::SharedMemory& memory) {
+ return hal::utils::createHidlMemoryFromSharedMemory(memory);
}
nn::GeneralResult<Model> unvalidatedConvert(const nn::Model& model) {
@@ -392,7 +391,7 @@
}
nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::Request::MemoryPool& memoryPool) {
- return unvalidatedConvert(std::get<nn::Memory>(memoryPool));
+ return unvalidatedConvert(std::get<nn::SharedMemory>(memoryPool));
}
nn::GeneralResult<Request> unvalidatedConvert(const nn::Request& request) {
diff --git a/neuralnetworks/1.0/utils/src/Device.cpp b/neuralnetworks/1.0/utils/src/Device.cpp
index 93bd81a..bb31a26 100644
--- a/neuralnetworks/1.0/utils/src/Device.cpp
+++ b/neuralnetworks/1.0/utils/src/Device.cpp
@@ -106,6 +106,10 @@
return nn::DeviceType::OTHER;
}
+bool Device::isUpdatable() const {
+ return false;
+}
+
const std::vector<nn::Extension>& Device::getSupportedExtensions() const {
return kExtensions;
}
diff --git a/neuralnetworks/1.0/utils/src/PreparedModel.cpp b/neuralnetworks/1.0/utils/src/PreparedModel.cpp
index c0c22fb..858571d 100644
--- a/neuralnetworks/1.0/utils/src/PreparedModel.cpp
+++ b/neuralnetworks/1.0/utils/src/PreparedModel.cpp
@@ -16,6 +16,7 @@
#include "PreparedModel.h"
+#include "Burst.h"
#include "Callbacks.h"
#include "Conversions.h"
#include "Utils.h"
@@ -90,6 +91,10 @@
<< "IPreparedModel::executeFenced is not supported on 1.0 HAL service";
}
+nn::GeneralResult<nn::SharedBurst> PreparedModel::configureExecutionBurst() const {
+ return Burst::create(shared_from_this());
+}
+
std::any PreparedModel::getUnderlyingResource() const {
sp<V1_0::IPreparedModel> resource = kPreparedModel;
return resource;
diff --git a/neuralnetworks/1.0/utils/test/DeviceTest.cpp b/neuralnetworks/1.0/utils/test/DeviceTest.cpp
new file mode 100644
index 0000000..e881da2
--- /dev/null
+++ b/neuralnetworks/1.0/utils/test/DeviceTest.cpp
@@ -0,0 +1,524 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "MockDevice.h"
+#include "MockPreparedModel.h"
+
+#include <android/hardware/neuralnetworks/1.0/IDevice.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <nnapi/IDevice.h>
+#include <nnapi/TypeUtils.h>
+#include <nnapi/Types.h>
+#include <nnapi/hal/1.0/Device.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+
+namespace android::hardware::neuralnetworks::V1_0::utils {
+namespace {
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::InvokeWithoutArgs;
+
+const nn::Model kSimpleModel = {
+ .main = {.operands = {{.type = nn::OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .lifetime = nn::Operand::LifeTime::SUBGRAPH_INPUT},
+ {.type = nn::OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .lifetime = nn::Operand::LifeTime::SUBGRAPH_OUTPUT}},
+ .operations = {{.type = nn::OperationType::RELU, .inputs = {0}, .outputs = {1}}},
+ .inputIndexes = {0},
+ .outputIndexes = {1}}};
+
+const std::string kName = "Google-MockV1";
+const std::string kInvalidName = "";
+const sp<V1_0::IDevice> kInvalidDevice;
+constexpr V1_0::PerformanceInfo kNoPerformanceInfo = {
+ .execTime = std::numeric_limits<float>::max(),
+ .powerUsage = std::numeric_limits<float>::max()};
+
+template <typename... Args>
+auto makeCallbackReturn(Args&&... args) {
+ return [argPack = std::make_tuple(std::forward<Args>(args)...)](const auto& cb) {
+ std::apply(cb, argPack);
+ return Void();
+ };
+}
+
+sp<MockDevice> createMockDevice() {
+ const auto mockDevice = MockDevice::create();
+
+ // Setup default actions for each relevant call.
+ const auto getCapabilities_ret = makeCallbackReturn(
+ V1_0::ErrorStatus::NONE, V1_0::Capabilities{
+ .float32Performance = kNoPerformanceInfo,
+ .quantized8Performance = kNoPerformanceInfo,
+ });
+
+ // Setup default actions for each relevant call.
+ ON_CALL(*mockDevice, getCapabilities(_)).WillByDefault(Invoke(getCapabilities_ret));
+
+ // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the
+ // uninteresting methods calls.
+ EXPECT_CALL(*mockDevice, getCapabilities(_)).Times(testing::AnyNumber());
+
+ return mockDevice;
+}
+
+auto makePreparedModelReturn(V1_0::ErrorStatus launchStatus, V1_0::ErrorStatus returnStatus,
+ const sp<V1_0::utils::MockPreparedModel>& preparedModel) {
+ return [launchStatus, returnStatus, preparedModel](const V1_0::Model& /*model*/,
+ const sp<V1_0::IPreparedModelCallback>& cb)
+ -> hardware::Return<V1_0::ErrorStatus> {
+ cb->notify(returnStatus, preparedModel).isOk();
+ return launchStatus;
+ };
+}
+
+std::function<hardware::Status()> makeTransportFailure(status_t status) {
+ return [status] { return hardware::Status::fromStatusT(status); };
+}
+
+const auto makeGeneralTransportFailure = makeTransportFailure(NO_MEMORY);
+const auto makeDeadObjectFailure = makeTransportFailure(DEAD_OBJECT);
+
+} // namespace
+
+TEST(DeviceTest, invalidName) {
+ // run test
+ const auto device = MockDevice::create();
+ const auto result = Device::create(kInvalidName, device);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST(DeviceTest, invalidDevice) {
+ // run test
+ const auto result = Device::create(kName, kInvalidDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST(DeviceTest, getCapabilitiesError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto ret = makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE,
+ V1_0::Capabilities{
+ .float32Performance = kNoPerformanceInfo,
+ .quantized8Performance = kNoPerformanceInfo,
+ });
+ EXPECT_CALL(*mockDevice, getCapabilities(_)).Times(1).WillOnce(Invoke(ret));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getCapabilitiesTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getCapabilities(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getCapabilitiesDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getCapabilities(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, linkToDeathError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto ret = []() -> Return<bool> { return false; };
+ EXPECT_CALL(*mockDevice, linkToDeathRet()).Times(1).WillOnce(InvokeWithoutArgs(ret));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, linkToDeathTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, linkToDeathRet())
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, linkToDeathDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, linkToDeathRet())
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getName) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+
+ // run test
+ const auto& name = device->getName();
+
+ // verify result
+ EXPECT_EQ(name, kName);
+}
+
+TEST(DeviceTest, getFeatureLevel) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+
+ // run test
+ const auto featureLevel = device->getFeatureLevel();
+
+ // verify result
+ EXPECT_EQ(featureLevel, nn::Version::ANDROID_OC_MR1);
+}
+
+TEST(DeviceTest, getCachedData) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto result = Device::create(kName, mockDevice);
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ const auto& device = result.value();
+
+ // run test and verify results
+ EXPECT_EQ(device->getVersionString(), device->getVersionString());
+ EXPECT_EQ(device->getType(), device->getType());
+ EXPECT_EQ(device->getSupportedExtensions(), device->getSupportedExtensions());
+ EXPECT_EQ(device->getNumberOfCacheFilesNeeded(), device->getNumberOfCacheFilesNeeded());
+ EXPECT_EQ(device->getCapabilities(), device->getCapabilities());
+}
+
+TEST(DeviceTest, wait) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto ret = []() -> Return<void> { return {}; };
+ EXPECT_CALL(*mockDevice, ping()).Times(1).WillOnce(InvokeWithoutArgs(ret));
+ const auto device = Device::create(kName, mockDevice).value();
+
+ // run test
+ const auto result = device->wait();
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(DeviceTest, waitTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, ping())
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+ const auto device = Device::create(kName, mockDevice).value();
+
+ // run test
+ const auto result = device->wait();
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, waitDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, ping()).Times(1).WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+ const auto device = Device::create(kName, mockDevice).value();
+
+ // run test
+ const auto result = device->wait();
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getSupportedOperations) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ const auto ret = [](const auto& model, const auto& cb) {
+ cb(V1_0::ErrorStatus::NONE, std::vector<bool>(model.operations.size(), true));
+ return hardware::Void();
+ };
+ EXPECT_CALL(*mockDevice, getSupportedOperations(_, _)).Times(1).WillOnce(Invoke(ret));
+
+ // run test
+ const auto result = device->getSupportedOperations(kSimpleModel);
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ const auto& supportedOperations = result.value();
+ EXPECT_EQ(supportedOperations.size(), kSimpleModel.main.operations.size());
+ EXPECT_THAT(supportedOperations, Each(testing::IsTrue()));
+}
+
+TEST(DeviceTest, getSupportedOperationsError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ const auto ret = [](const auto& /*model*/, const auto& cb) {
+ cb(V1_0::ErrorStatus::GENERAL_FAILURE, {});
+ return hardware::Void();
+ };
+ EXPECT_CALL(*mockDevice, getSupportedOperations(_, _)).Times(1).WillOnce(Invoke(ret));
+
+ // run test
+ const auto result = device->getSupportedOperations(kSimpleModel);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getSupportedOperationsTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, getSupportedOperations(_, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = device->getSupportedOperations(kSimpleModel);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getSupportedOperationsDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, getSupportedOperations(_, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = device->getSupportedOperations(kSimpleModel);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, prepareModel) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ const auto mockPreparedModel = V1_0::utils::MockPreparedModel::create();
+ EXPECT_CALL(*mockDevice, prepareModel(_, _))
+ .Times(1)
+ .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::NONE,
+ V1_0::ErrorStatus::NONE, mockPreparedModel)));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ EXPECT_NE(result.value(), nullptr);
+}
+
+TEST(DeviceTest, prepareModelLaunchError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModel(_, _))
+ .Times(1)
+ .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::GENERAL_FAILURE,
+ V1_0::ErrorStatus::GENERAL_FAILURE, nullptr)));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelReturnError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModel(_, _))
+ .Times(1)
+ .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::NONE,
+ V1_0::ErrorStatus::GENERAL_FAILURE, nullptr)));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelNullptrError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModel(_, _))
+ .Times(1)
+ .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::NONE,
+ V1_0::ErrorStatus::NONE, nullptr)));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModel(_, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModel(_, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, prepareModelAsyncCrash) {
+ // setup test
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ const auto ret = [&mockDevice]() -> hardware::Return<V1_0::ErrorStatus> {
+ mockDevice->simulateCrash();
+ return V1_0::ErrorStatus::NONE;
+ };
+ EXPECT_CALL(*mockDevice, prepareModel(_, _)).Times(1).WillOnce(InvokeWithoutArgs(ret));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, prepareModelFromCacheNotSupported) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+
+ // run test
+ const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, allocateNotSupported) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+
+ // run test
+ const auto result = device->allocate({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+} // namespace android::hardware::neuralnetworks::V1_0::utils
diff --git a/neuralnetworks/1.0/utils/test/MockDevice.h b/neuralnetworks/1.0/utils/test/MockDevice.h
new file mode 100644
index 0000000..0fb59e3
--- /dev/null
+++ b/neuralnetworks/1.0/utils/test/MockDevice.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_TEST_MOCK_DEVICE
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_TEST_MOCK_DEVICE
+
+#include <android/hardware/neuralnetworks/1.0/IDevice.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <hidl/Status.h>
+
+namespace android::hardware::neuralnetworks::V1_0::utils {
+
+class MockDevice final : public IDevice {
+ public:
+ static sp<MockDevice> create();
+
+ // IBase methods below.
+ MOCK_METHOD(Return<void>, ping, (), (override));
+ MOCK_METHOD(Return<bool>, linkToDeathRet, ());
+ Return<bool> linkToDeath(const sp<hidl_death_recipient>& recipient, uint64_t /*cookie*/);
+
+ // V1_0 methods below.
+ MOCK_METHOD(Return<void>, getCapabilities, (getCapabilities_cb cb), (override));
+ MOCK_METHOD(Return<void>, getSupportedOperations,
+ (const V1_0::Model& model, getSupportedOperations_cb cb), (override));
+ MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModel,
+ (const V1_0::Model& model, const sp<V1_0::IPreparedModelCallback>& callback),
+ (override));
+ MOCK_METHOD(Return<V1_0::DeviceStatus>, getStatus, (), (override));
+
+ // Helper methods.
+ void simulateCrash();
+
+ private:
+ sp<hidl_death_recipient> mDeathRecipient;
+};
+
+inline sp<MockDevice> MockDevice::create() {
+ auto mockDevice = sp<MockDevice>::make();
+
+ // Setup default actions for each relevant call.
+ const auto ret = []() -> Return<bool> { return true; };
+
+ // Setup default actions for each relevant call.
+ ON_CALL(*mockDevice, linkToDeathRet()).WillByDefault(testing::Invoke(ret));
+
+ // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the
+ // uninteresting methods calls.
+ EXPECT_CALL(*mockDevice, linkToDeathRet()).Times(testing::AnyNumber());
+
+ return mockDevice;
+}
+
+inline Return<bool> MockDevice::linkToDeath(const sp<hidl_death_recipient>& recipient,
+ uint64_t /*cookie*/) {
+ mDeathRecipient = recipient;
+ return linkToDeathRet();
+}
+
+inline void MockDevice::simulateCrash() {
+ ASSERT_NE(nullptr, mDeathRecipient.get());
+
+ // Currently, the utils::Device will not use the `cookie` or `who` arguments, so we pass in 0
+ // and nullptr for these arguments instead. Normally, they are used by the hidl_death_recipient
+ // to determine which object is dead. However, the utils::Device code only pairs a single death
+ // recipient with a single HIDL interface object, so these arguments are redundant.
+ mDeathRecipient->serviceDied(0, nullptr);
+}
+
+} // namespace android::hardware::neuralnetworks::V1_0::utils
+
+#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_TEST_MOCK_DEVICE
diff --git a/neuralnetworks/1.0/utils/test/MockPreparedModel.h b/neuralnetworks/1.0/utils/test/MockPreparedModel.h
new file mode 100644
index 0000000..7a48a83
--- /dev/null
+++ b/neuralnetworks/1.0/utils/test/MockPreparedModel.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_TEST_MOCK_PREPARED_MODEL
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_TEST_MOCK_PREPARED_MODEL
+
+#include <android/hardware/neuralnetworks/1.0/IPreparedModel.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <hidl/HidlSupport.h>
+#include <hidl/Status.h>
+
+namespace android::hardware::neuralnetworks::V1_0::utils {
+
+class MockPreparedModel final : public IPreparedModel {
+ public:
+ static sp<MockPreparedModel> create();
+
+ // IBase methods below.
+ MOCK_METHOD(Return<void>, ping, (), (override));
+ MOCK_METHOD(Return<bool>, linkToDeathRet, ());
+ Return<bool> linkToDeath(const sp<hidl_death_recipient>& recipient,
+ uint64_t /*cookie*/) override;
+
+ // V1_0 methods below.
+ MOCK_METHOD(Return<V1_0::ErrorStatus>, execute,
+ (const V1_0::Request& request, const sp<V1_0::IExecutionCallback>& callback),
+ (override));
+
+ // Helper methods.
+ void simulateCrash();
+
+ private:
+ sp<hidl_death_recipient> mDeathRecipient;
+};
+
+inline sp<MockPreparedModel> MockPreparedModel::create() {
+ auto mockPreparedModel = sp<MockPreparedModel>::make();
+
+ // Setup default actions for each relevant call.
+ const auto ret = []() -> Return<bool> { return true; };
+
+ // Setup default actions for each relevant call.
+ ON_CALL(*mockPreparedModel, linkToDeathRet()).WillByDefault(testing::Invoke(ret));
+
+ // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the
+ // uninteresting methods calls.
+ EXPECT_CALL(*mockPreparedModel, linkToDeathRet()).Times(testing::AnyNumber());
+
+ return mockPreparedModel;
+}
+
+inline Return<bool> MockPreparedModel::linkToDeath(const sp<hidl_death_recipient>& recipient,
+ uint64_t /*cookie*/) {
+ mDeathRecipient = recipient;
+ return linkToDeathRet();
+}
+
+inline void MockPreparedModel::simulateCrash() {
+ ASSERT_NE(nullptr, mDeathRecipient.get());
+
+ // Currently, the utils::PreparedModel will not use the `cookie` or `who` arguments, so we pass
+ // in 0 and nullptr for these arguments instead. Normally, they are used by the
+ // hidl_death_recipient to determine which object is dead. However, the utils::PreparedModel
+ // code only pairs a single death recipient with a single HIDL interface object, so these
+ // arguments are redundant.
+ mDeathRecipient->serviceDied(0, nullptr);
+}
+
+} // namespace android::hardware::neuralnetworks::V1_0::utils
+
+#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_TEST_MOCK_PREPARED_MODEL
diff --git a/neuralnetworks/1.0/utils/test/PreparedModelTest.cpp b/neuralnetworks/1.0/utils/test/PreparedModelTest.cpp
new file mode 100644
index 0000000..a5cbc72
--- /dev/null
+++ b/neuralnetworks/1.0/utils/test/PreparedModelTest.cpp
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "MockPreparedModel.h"
+
+#include <android/hardware/neuralnetworks/1.0/IExecutionCallback.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <nnapi/IPreparedModel.h>
+#include <nnapi/TypeUtils.h>
+#include <nnapi/Types.h>
+#include <nnapi/hal/1.0/PreparedModel.h>
+
+#include <functional>
+#include <memory>
+
+namespace android::hardware::neuralnetworks::V1_0::utils {
+namespace {
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::InvokeWithoutArgs;
+
+const sp<V1_0::IPreparedModel> kInvalidPreparedModel;
+
+sp<MockPreparedModel> createMockPreparedModel() {
+ return MockPreparedModel::create();
+}
+
+auto makeExecute(V1_0::ErrorStatus launchStatus, V1_0::ErrorStatus returnStatus) {
+ return [launchStatus, returnStatus](
+ const V1_0::Request& /*request*/,
+ const sp<V1_0::IExecutionCallback>& cb) -> Return<V1_0::ErrorStatus> {
+ cb->notify(returnStatus);
+ return launchStatus;
+ };
+}
+
+std::function<hardware::Status()> makeTransportFailure(status_t status) {
+ return [status] { return hardware::Status::fromStatusT(status); };
+}
+
+const auto makeGeneralTransportFailure = makeTransportFailure(NO_MEMORY);
+const auto makeDeadObjectFailure = makeTransportFailure(DEAD_OBJECT);
+
+} // namespace
+
+TEST(PreparedModelTest, invalidPreparedModel) {
+ // run test
+ const auto result = PreparedModel::create(kInvalidPreparedModel);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, linkToDeathError) {
+ // setup call
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto ret = []() -> Return<bool> { return false; };
+ EXPECT_CALL(*mockPreparedModel, linkToDeathRet()).Times(1).WillOnce(InvokeWithoutArgs(ret));
+
+ // run test
+ const auto result = PreparedModel::create(mockPreparedModel);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, linkToDeathTransportFailure) {
+ // setup call
+ const auto mockPreparedModel = createMockPreparedModel();
+ EXPECT_CALL(*mockPreparedModel, linkToDeathRet())
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = PreparedModel::create(mockPreparedModel);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, linkToDeathDeadObject) {
+ // setup call
+ const auto mockPreparedModel = createMockPreparedModel();
+ EXPECT_CALL(*mockPreparedModel, linkToDeathRet())
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = PreparedModel::create(mockPreparedModel);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(PreparedModelTest, execute) {
+ // setup call
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel = PreparedModel::create(mockPreparedModel).value();
+ EXPECT_CALL(*mockPreparedModel, execute(_, _))
+ .Times(1)
+ .WillOnce(Invoke(makeExecute(V1_0::ErrorStatus::NONE, V1_0::ErrorStatus::NONE)));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ EXPECT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(PreparedModelTest, executeLaunchError) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel = PreparedModel::create(mockPreparedModel).value();
+ EXPECT_CALL(*mockPreparedModel, execute(_, _))
+ .Times(1)
+ .WillOnce(Invoke(makeExecute(V1_0::ErrorStatus::GENERAL_FAILURE,
+ V1_0::ErrorStatus::GENERAL_FAILURE)));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeReturnError) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel = PreparedModel::create(mockPreparedModel).value();
+ EXPECT_CALL(*mockPreparedModel, execute(_, _))
+ .Times(1)
+ .WillOnce(Invoke(
+ makeExecute(V1_0::ErrorStatus::NONE, V1_0::ErrorStatus::GENERAL_FAILURE)));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeTransportFailure) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel = PreparedModel::create(mockPreparedModel).value();
+ EXPECT_CALL(*mockPreparedModel, execute(_, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeDeadObject) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel = PreparedModel::create(mockPreparedModel).value();
+ EXPECT_CALL(*mockPreparedModel, execute(_, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(PreparedModelTest, executeCrash) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel = PreparedModel::create(mockPreparedModel).value();
+ const auto ret = [&mockPreparedModel]() -> hardware::Return<V1_0::ErrorStatus> {
+ mockPreparedModel->simulateCrash();
+ return V1_0::ErrorStatus::NONE;
+ };
+ EXPECT_CALL(*mockPreparedModel, execute(_, _)).Times(1).WillOnce(InvokeWithoutArgs(ret));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(PreparedModelTest, executeFencedNotSupported) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel = PreparedModel::create(mockPreparedModel).value();
+
+ // run test
+ const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+// TODO: test burst execution if/when it is added to nn::IPreparedModel.
+
+TEST(PreparedModelTest, getUnderlyingResource) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel = PreparedModel::create(mockPreparedModel).value();
+
+ // run test
+ const auto resource = preparedModel->getUnderlyingResource();
+
+ // verify resource
+ const sp<V1_0::IPreparedModel>* maybeMock = std::any_cast<sp<V1_0::IPreparedModel>>(&resource);
+ ASSERT_NE(maybeMock, nullptr);
+ EXPECT_EQ(maybeMock->get(), mockPreparedModel.get());
+}
+
+} // namespace android::hardware::neuralnetworks::V1_0::utils
diff --git a/neuralnetworks/1.0/vts/functional/AndroidTest.xml b/neuralnetworks/1.0/vts/functional/AndroidTest.xml
index 13671f9..9dd85ae 100644
--- a/neuralnetworks/1.0/vts/functional/AndroidTest.xml
+++ b/neuralnetworks/1.0/vts/functional/AndroidTest.xml
@@ -28,5 +28,6 @@
<test class="com.android.tradefed.testtype.GTest" >
<option name="native-test-device-path" value="/data/local/tmp" />
<option name="module-name" value="VtsHalNeuralnetworksV1_0TargetTest" />
+ <option name="native-test-timeout" value="20m" />
</test>
</configuration>
diff --git a/neuralnetworks/1.1/utils/Android.bp b/neuralnetworks/1.1/utils/Android.bp
index 909575b..fe0c80a 100644
--- a/neuralnetworks/1.1/utils/Android.bp
+++ b/neuralnetworks/1.1/utils/Android.bp
@@ -34,3 +34,31 @@
"neuralnetworks_utils_hal_common",
],
}
+
+cc_test {
+ name: "neuralnetworks_utils_hal_1_1_test",
+ srcs: ["test/*.cpp"],
+ static_libs: [
+ "android.hardware.neuralnetworks@1.0",
+ "android.hardware.neuralnetworks@1.1",
+ "libgmock",
+ "libneuralnetworks_common",
+ "neuralnetworks_types",
+ "neuralnetworks_utils_hal_common",
+ "neuralnetworks_utils_hal_1_0",
+ "neuralnetworks_utils_hal_1_1",
+ ],
+ shared_libs: [
+ "android.hidl.allocator@1.0",
+ "android.hidl.memory@1.0",
+ "libbase",
+ "libcutils",
+ "libfmq",
+ "libhidlbase",
+ "libhidlmemory",
+ "liblog",
+ "libnativewindow",
+ "libutils",
+ ],
+ test_suites: ["general-tests"],
+}
diff --git a/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Device.h b/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Device.h
index 5e224b5..3aec8ee 100644
--- a/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Device.h
+++ b/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Device.h
@@ -52,6 +52,7 @@
const std::string& getVersionString() const override;
nn::Version getFeatureLevel() const override;
nn::DeviceType getType() const override;
+ bool isUpdatable() const override;
const std::vector<nn::Extension>& getSupportedExtensions() const override;
const nn::Capabilities& getCapabilities() const override;
std::pair<uint32_t, uint32_t> getNumberOfCacheFilesNeeded() const override;
diff --git a/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Utils.h b/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Utils.h
index 052d88e..09597a3 100644
--- a/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Utils.h
+++ b/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Utils.h
@@ -47,6 +47,12 @@
return result.has_value();
}
+template <typename Type>
+auto convertFromNonCanonical(const Type& nonCanonicalObject)
+ -> decltype(convert(nn::convert(nonCanonicalObject).value())) {
+ return convert(NN_TRY(nn::convert(nonCanonicalObject)));
+}
+
} // namespace android::hardware::neuralnetworks::V1_1::utils
#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_H
diff --git a/neuralnetworks/1.1/utils/src/Conversions.cpp b/neuralnetworks/1.1/utils/src/Conversions.cpp
index b47f25a..07bf7bc 100644
--- a/neuralnetworks/1.1/utils/src/Conversions.cpp
+++ b/neuralnetworks/1.1/utils/src/Conversions.cpp
@@ -175,7 +175,7 @@
return V1_0::utils::unvalidatedConvert(operandValues);
}
-nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::Memory& memory) {
+nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::SharedMemory& memory) {
return V1_0::utils::unvalidatedConvert(memory);
}
diff --git a/neuralnetworks/1.1/utils/src/Device.cpp b/neuralnetworks/1.1/utils/src/Device.cpp
index 3197ef4..d2ef57f 100644
--- a/neuralnetworks/1.1/utils/src/Device.cpp
+++ b/neuralnetworks/1.1/utils/src/Device.cpp
@@ -106,6 +106,10 @@
return nn::DeviceType::UNKNOWN;
}
+bool Device::isUpdatable() const {
+ return false;
+}
+
const std::vector<nn::Extension>& Device::getSupportedExtensions() const {
return kExtensions;
}
diff --git a/neuralnetworks/1.1/utils/test/DeviceTest.cpp b/neuralnetworks/1.1/utils/test/DeviceTest.cpp
new file mode 100644
index 0000000..41e0e30
--- /dev/null
+++ b/neuralnetworks/1.1/utils/test/DeviceTest.cpp
@@ -0,0 +1,534 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "MockDevice.h"
+#include "MockPreparedModel.h"
+
+#include <android/hardware/neuralnetworks/1.1/IDevice.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <nnapi/IDevice.h>
+#include <nnapi/TypeUtils.h>
+#include <nnapi/Types.h>
+#include <nnapi/hal/1.1/Device.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+
+namespace android::hardware::neuralnetworks::V1_1::utils {
+namespace {
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::InvokeWithoutArgs;
+
+const nn::Model kSimpleModel = {
+ .main = {.operands = {{.type = nn::OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .lifetime = nn::Operand::LifeTime::SUBGRAPH_INPUT},
+ {.type = nn::OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .lifetime = nn::Operand::LifeTime::SUBGRAPH_OUTPUT}},
+ .operations = {{.type = nn::OperationType::RELU, .inputs = {0}, .outputs = {1}}},
+ .inputIndexes = {0},
+ .outputIndexes = {1}}};
+
+const std::string kName = "Google-MockV1";
+const std::string kInvalidName = "";
+const sp<V1_1::IDevice> kInvalidDevice;
+constexpr V1_0::PerformanceInfo kNoPerformanceInfo = {
+ .execTime = std::numeric_limits<float>::max(),
+ .powerUsage = std::numeric_limits<float>::max()};
+
+template <typename... Args>
+auto makeCallbackReturn(Args&&... args) {
+ return [argPack = std::make_tuple(std::forward<Args>(args)...)](const auto& cb) {
+ std::apply(cb, argPack);
+ return Void();
+ };
+}
+
+sp<MockDevice> createMockDevice() {
+ const auto mockDevice = MockDevice::create();
+
+ // Setup default actions for each relevant call.
+ const auto getCapabilities_ret =
+ makeCallbackReturn(V1_0::ErrorStatus::NONE,
+ V1_1::Capabilities{
+ .float32Performance = kNoPerformanceInfo,
+ .quantized8Performance = kNoPerformanceInfo,
+ .relaxedFloat32toFloat16Performance = kNoPerformanceInfo,
+ });
+
+ // Setup default actions for each relevant call.
+ ON_CALL(*mockDevice, getCapabilities_1_1(_)).WillByDefault(Invoke(getCapabilities_ret));
+
+ // Ensure that older calls are not used.
+ EXPECT_CALL(*mockDevice, getCapabilities(_)).Times(0);
+ EXPECT_CALL(*mockDevice, getSupportedOperations(_, _)).Times(0);
+ EXPECT_CALL(*mockDevice, prepareModel(_, _)).Times(0);
+
+ // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the
+ // uninteresting methods calls.
+ EXPECT_CALL(*mockDevice, getCapabilities_1_1(_)).Times(testing::AnyNumber());
+
+ return mockDevice;
+}
+
+auto makePreparedModelReturn(V1_0::ErrorStatus launchStatus, V1_0::ErrorStatus returnStatus,
+ const sp<V1_0::utils::MockPreparedModel>& preparedModel) {
+ return [launchStatus, returnStatus, preparedModel](const V1_1::Model& /*model*/,
+ V1_1::ExecutionPreference /*preference*/,
+ const sp<V1_0::IPreparedModelCallback>& cb)
+ -> hardware::Return<V1_0::ErrorStatus> {
+ cb->notify(returnStatus, preparedModel).isOk();
+ return launchStatus;
+ };
+}
+
+std::function<hardware::Status()> makeTransportFailure(status_t status) {
+ return [status] { return hardware::Status::fromStatusT(status); };
+}
+
+const auto makeGeneralTransportFailure = makeTransportFailure(NO_MEMORY);
+const auto makeDeadObjectFailure = makeTransportFailure(DEAD_OBJECT);
+
+} // namespace
+
+TEST(DeviceTest, invalidName) {
+ // run test
+ const auto device = MockDevice::create();
+ const auto result = Device::create(kInvalidName, device);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST(DeviceTest, invalidDevice) {
+ // run test
+ const auto result = Device::create(kName, kInvalidDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST(DeviceTest, getCapabilitiesError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto ret =
+ makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE,
+ V1_1::Capabilities{
+ .float32Performance = kNoPerformanceInfo,
+ .quantized8Performance = kNoPerformanceInfo,
+ .relaxedFloat32toFloat16Performance = kNoPerformanceInfo,
+ });
+ EXPECT_CALL(*mockDevice, getCapabilities_1_1(_)).Times(1).WillOnce(Invoke(ret));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getCapabilitiesTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getCapabilities_1_1(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getCapabilitiesDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getCapabilities_1_1(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, linkToDeathError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto ret = []() -> Return<bool> { return false; };
+ EXPECT_CALL(*mockDevice, linkToDeathRet()).Times(1).WillOnce(InvokeWithoutArgs(ret));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, linkToDeathTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, linkToDeathRet())
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, linkToDeathDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, linkToDeathRet())
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getName) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+
+ // run test
+ const auto& name = device->getName();
+
+ // verify result
+ EXPECT_EQ(name, kName);
+}
+
+TEST(DeviceTest, getFeatureLevel) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+
+ // run test
+ const auto featureLevel = device->getFeatureLevel();
+
+ // verify result
+ EXPECT_EQ(featureLevel, nn::Version::ANDROID_P);
+}
+
+TEST(DeviceTest, getCachedData) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto result = Device::create(kName, mockDevice);
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ const auto& device = result.value();
+
+ // run test and verify results
+ EXPECT_EQ(device->getVersionString(), device->getVersionString());
+ EXPECT_EQ(device->getType(), device->getType());
+ EXPECT_EQ(device->getSupportedExtensions(), device->getSupportedExtensions());
+ EXPECT_EQ(device->getNumberOfCacheFilesNeeded(), device->getNumberOfCacheFilesNeeded());
+ EXPECT_EQ(device->getCapabilities(), device->getCapabilities());
+}
+
+TEST(DeviceTest, wait) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto ret = []() -> Return<void> { return {}; };
+ EXPECT_CALL(*mockDevice, ping()).Times(1).WillOnce(InvokeWithoutArgs(ret));
+ const auto device = Device::create(kName, mockDevice).value();
+
+ // run test
+ const auto result = device->wait();
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(DeviceTest, waitTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, ping())
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+ const auto device = Device::create(kName, mockDevice).value();
+
+ // run test
+ const auto result = device->wait();
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, waitDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, ping()).Times(1).WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+ const auto device = Device::create(kName, mockDevice).value();
+
+ // run test
+ const auto result = device->wait();
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getSupportedOperations) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ const auto ret = [](const auto& model, const auto& cb) {
+ cb(V1_0::ErrorStatus::NONE, std::vector<bool>(model.operations.size(), true));
+ return hardware::Void();
+ };
+ EXPECT_CALL(*mockDevice, getSupportedOperations_1_1(_, _)).Times(1).WillOnce(Invoke(ret));
+
+ // run test
+ const auto result = device->getSupportedOperations(kSimpleModel);
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ const auto& supportedOperations = result.value();
+ EXPECT_EQ(supportedOperations.size(), kSimpleModel.main.operations.size());
+ EXPECT_THAT(supportedOperations, Each(testing::IsTrue()));
+}
+
+TEST(DeviceTest, getSupportedOperationsError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ const auto ret = [](const auto& /*model*/, const auto& cb) {
+ cb(V1_0::ErrorStatus::GENERAL_FAILURE, {});
+ return hardware::Void();
+ };
+ EXPECT_CALL(*mockDevice, getSupportedOperations_1_1(_, _)).Times(1).WillOnce(Invoke(ret));
+
+ // run test
+ const auto result = device->getSupportedOperations(kSimpleModel);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getSupportedOperationsTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, getSupportedOperations_1_1(_, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = device->getSupportedOperations(kSimpleModel);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getSupportedOperationsDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, getSupportedOperations_1_1(_, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = device->getSupportedOperations(kSimpleModel);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, prepareModel) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ const auto mockPreparedModel = V1_0::utils::MockPreparedModel::create();
+ EXPECT_CALL(*mockDevice, prepareModel_1_1(_, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::NONE,
+ V1_0::ErrorStatus::NONE, mockPreparedModel)));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ EXPECT_NE(result.value(), nullptr);
+}
+
+TEST(DeviceTest, prepareModelLaunchError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModel_1_1(_, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::GENERAL_FAILURE,
+ V1_0::ErrorStatus::GENERAL_FAILURE, nullptr)));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelReturnError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModel_1_1(_, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::NONE,
+ V1_0::ErrorStatus::GENERAL_FAILURE, nullptr)));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelNullptrError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModel_1_1(_, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::NONE,
+ V1_0::ErrorStatus::NONE, nullptr)));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModel_1_1(_, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModel_1_1(_, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, prepareModelAsyncCrash) {
+ // setup test
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ const auto ret = [&mockDevice]() -> hardware::Return<V1_0::ErrorStatus> {
+ mockDevice->simulateCrash();
+ return V1_0::ErrorStatus::NONE;
+ };
+ EXPECT_CALL(*mockDevice, prepareModel_1_1(_, _, _)).Times(1).WillOnce(InvokeWithoutArgs(ret));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, prepareModelFromCacheNotSupported) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+
+ // run test
+ const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, allocateNotSupported) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+
+ // run test
+ const auto result = device->allocate({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+} // namespace android::hardware::neuralnetworks::V1_1::utils
diff --git a/neuralnetworks/1.1/utils/test/MockDevice.h b/neuralnetworks/1.1/utils/test/MockDevice.h
new file mode 100644
index 0000000..3b92e58
--- /dev/null
+++ b/neuralnetworks/1.1/utils/test/MockDevice.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_TEST_MOCK_DEVICE
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_TEST_MOCK_DEVICE
+
+#include <android/hardware/neuralnetworks/1.1/IDevice.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <hidl/Status.h>
+
+namespace android::hardware::neuralnetworks::V1_1::utils {
+
+class MockDevice final : public IDevice {
+ public:
+ static sp<MockDevice> create();
+
+ // IBase methods below.
+ MOCK_METHOD(Return<void>, ping, (), (override));
+ MOCK_METHOD(Return<bool>, linkToDeathRet, ());
+ Return<bool> linkToDeath(const sp<hidl_death_recipient>& recipient, uint64_t /*cookie*/);
+
+ // V1_0 methods below.
+ MOCK_METHOD(Return<void>, getCapabilities, (getCapabilities_cb cb), (override));
+ MOCK_METHOD(Return<void>, getSupportedOperations,
+ (const V1_0::Model& model, getSupportedOperations_cb cb), (override));
+ MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModel,
+ (const V1_0::Model& model, const sp<V1_0::IPreparedModelCallback>& callback),
+ (override));
+ MOCK_METHOD(Return<V1_0::DeviceStatus>, getStatus, (), (override));
+
+ // V1_1 methods below.
+ MOCK_METHOD(Return<void>, getCapabilities_1_1, (getCapabilities_1_1_cb cb), (override));
+ MOCK_METHOD(Return<void>, getSupportedOperations_1_1,
+ (const V1_1::Model& model, getSupportedOperations_1_1_cb cb), (override));
+ MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModel_1_1,
+ (const V1_1::Model& model, V1_1::ExecutionPreference preference,
+ const sp<V1_0::IPreparedModelCallback>& callback),
+ (override));
+
+ // Helper methods.
+ void simulateCrash();
+
+ private:
+ sp<hidl_death_recipient> mDeathRecipient;
+};
+
+inline sp<MockDevice> MockDevice::create() {
+ auto mockDevice = sp<MockDevice>::make();
+
+ // Setup default actions for each relevant call.
+ const auto ret = []() -> Return<bool> { return true; };
+
+ // Setup default actions for each relevant call.
+ ON_CALL(*mockDevice, linkToDeathRet()).WillByDefault(testing::Invoke(ret));
+
+ // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the
+ // uninteresting methods calls.
+ EXPECT_CALL(*mockDevice, linkToDeathRet()).Times(testing::AnyNumber());
+
+ return mockDevice;
+}
+
+inline Return<bool> MockDevice::linkToDeath(const sp<hidl_death_recipient>& recipient,
+ uint64_t /*cookie*/) {
+ mDeathRecipient = recipient;
+ return linkToDeathRet();
+}
+
+inline void MockDevice::simulateCrash() {
+ ASSERT_NE(nullptr, mDeathRecipient.get());
+
+ // Currently, the utils::Device will not use the `cookie` or `who` arguments, so we pass in 0
+ // and nullptr for these arguments instead. Normally, they are used by the hidl_death_recipient
+ // to determine which object is dead. However, the utils::Device code only pairs a single death
+ // recipient with a single HIDL interface object, so these arguments are redundant.
+ mDeathRecipient->serviceDied(0, nullptr);
+}
+
+} // namespace android::hardware::neuralnetworks::V1_1::utils
+
+#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_TEST_MOCK_DEVICE
diff --git a/neuralnetworks/1.1/utils/test/MockPreparedModel.h b/neuralnetworks/1.1/utils/test/MockPreparedModel.h
new file mode 100644
index 0000000..aba731e
--- /dev/null
+++ b/neuralnetworks/1.1/utils/test/MockPreparedModel.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_TEST_MOCK_PREPARED_MODEL
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_TEST_MOCK_PREPARED_MODEL
+
+#include <android/hardware/neuralnetworks/1.0/IPreparedModel.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <hidl/HidlSupport.h>
+#include <hidl/Status.h>
+
+namespace android::hardware::neuralnetworks::V1_0::utils {
+
+class MockPreparedModel final : public IPreparedModel {
+ public:
+ static sp<MockPreparedModel> create();
+
+ // V1_0 methods below.
+ MOCK_METHOD(Return<V1_0::ErrorStatus>, execute,
+ (const V1_0::Request& request, const sp<V1_0::IExecutionCallback>& callback),
+ (override));
+};
+
+inline sp<MockPreparedModel> MockPreparedModel::create() {
+ return sp<MockPreparedModel>::make();
+}
+
+} // namespace android::hardware::neuralnetworks::V1_0::utils
+
+#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_UTILS_TEST_MOCK_PREPARED_MODEL
diff --git a/neuralnetworks/1.1/vts/functional/AndroidTest.xml b/neuralnetworks/1.1/vts/functional/AndroidTest.xml
index cfde60c..74001f9 100644
--- a/neuralnetworks/1.1/vts/functional/AndroidTest.xml
+++ b/neuralnetworks/1.1/vts/functional/AndroidTest.xml
@@ -28,5 +28,6 @@
<test class="com.android.tradefed.testtype.GTest" >
<option name="native-test-device-path" value="/data/local/tmp" />
<option name="module-name" value="VtsHalNeuralnetworksV1_1TargetTest" />
+ <option name="native-test-timeout" value="20m" />
</test>
</configuration>
diff --git a/neuralnetworks/1.2/utils/Android.bp b/neuralnetworks/1.2/utils/Android.bp
index 22e8659..6959056 100644
--- a/neuralnetworks/1.2/utils/Android.bp
+++ b/neuralnetworks/1.2/utils/Android.bp
@@ -18,6 +18,7 @@
name: "neuralnetworks_utils_hal_1_2",
defaults: ["neuralnetworks_utils_defaults"],
srcs: ["src/*"],
+ exclude_srcs: ["src/ExecutionBurst*"],
local_include_dirs: ["include/nnapi/hal/1.2/"],
export_include_dirs: ["include"],
cflags: ["-Wthread-safety"],
@@ -36,3 +37,33 @@
"neuralnetworks_utils_hal_common",
],
}
+
+cc_test {
+ name: "neuralnetworks_utils_hal_1_2_test",
+ srcs: ["test/*.cpp"],
+ static_libs: [
+ "android.hardware.neuralnetworks@1.0",
+ "android.hardware.neuralnetworks@1.1",
+ "android.hardware.neuralnetworks@1.2",
+ "libgmock",
+ "libneuralnetworks_common",
+ "neuralnetworks_types",
+ "neuralnetworks_utils_hal_common",
+ "neuralnetworks_utils_hal_1_0",
+ "neuralnetworks_utils_hal_1_1",
+ "neuralnetworks_utils_hal_1_2",
+ ],
+ shared_libs: [
+ "android.hidl.allocator@1.0",
+ "android.hidl.memory@1.0",
+ "libbase",
+ "libcutils",
+ "libfmq",
+ "libhidlbase",
+ "libhidlmemory",
+ "liblog",
+ "libnativewindow",
+ "libutils",
+ ],
+ test_suites: ["general-tests"],
+}
diff --git a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Device.h b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Device.h
index b4bef5e..489f857 100644
--- a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Device.h
+++ b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Device.h
@@ -71,6 +71,7 @@
const std::string& getVersionString() const override;
nn::Version getFeatureLevel() const override;
nn::DeviceType getType() const override;
+ bool isUpdatable() const override;
const std::vector<nn::Extension>& getSupportedExtensions() const override;
const nn::Capabilities& getCapabilities() const override;
std::pair<uint32_t, uint32_t> getNumberOfCacheFilesNeeded() const override;
diff --git a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstController.h b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstController.h
new file mode 100644
index 0000000..5356a91
--- /dev/null
+++ b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstController.h
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_FRAMEWORKS_ML_NN_COMMON_EXECUTION_BURST_CONTROLLER_H
+#define ANDROID_FRAMEWORKS_ML_NN_COMMON_EXECUTION_BURST_CONTROLLER_H
+
+#include "ExecutionBurstUtils.h"
+
+#include <android-base/macros.h>
+#include <android/hardware/neuralnetworks/1.0/types.h>
+#include <android/hardware/neuralnetworks/1.1/types.h>
+#include <android/hardware/neuralnetworks/1.2/IBurstCallback.h>
+#include <android/hardware/neuralnetworks/1.2/IBurstContext.h>
+#include <android/hardware/neuralnetworks/1.2/IPreparedModel.h>
+#include <android/hardware/neuralnetworks/1.2/types.h>
+#include <fmq/MessageQueue.h>
+#include <hidl/MQDescriptor.h>
+
+#include <atomic>
+#include <chrono>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <stack>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+namespace android::nn {
+
+/**
+ * The ExecutionBurstController class manages both the serialization and
+ * deserialization of data across FMQ, making it appear to the runtime as a
+ * regular synchronous inference. Additionally, this class manages the burst's
+ * memory cache.
+ */
+class ExecutionBurstController {
+ DISALLOW_IMPLICIT_CONSTRUCTORS(ExecutionBurstController);
+
+ public:
+ /**
+ * NN runtime burst callback object and memory cache.
+ *
+ * ExecutionBurstCallback associates a hidl_memory object with a slot number
+ * to be passed across FMQ. The ExecutionBurstServer can use this callback
+ * to retrieve this hidl_memory corresponding to the slot via HIDL.
+ *
+ * Whenever a hidl_memory object is copied, it will duplicate the underlying
+ * file descriptor. Because the NN runtime currently copies the hidl_memory
+ * on each execution, it is difficult to associate hidl_memory objects with
+ * previously cached hidl_memory objects. For this reason, callers of this
+ * class must pair each hidl_memory object with an associated key. For
+ * efficiency, if two hidl_memory objects represent the same underlying
+ * buffer, they must use the same key.
+ */
+ class ExecutionBurstCallback : public hardware::neuralnetworks::V1_2::IBurstCallback {
+ DISALLOW_COPY_AND_ASSIGN(ExecutionBurstCallback);
+
+ public:
+ ExecutionBurstCallback() = default;
+
+ hardware::Return<void> getMemories(const hardware::hidl_vec<int32_t>& slots,
+ getMemories_cb cb) override;
+
+ /**
+ * This function performs one of two different actions:
+ * 1) If a key corresponding to a memory resource is unrecognized by the
+ * ExecutionBurstCallback object, the ExecutionBurstCallback object
+ * will allocate a slot, bind the memory to the slot, and return the
+ * slot identifier.
+ * 2) If a key corresponding to a memory resource is recognized by the
+ * ExecutionBurstCallback object, the ExecutionBurstCallback object
+ * will return the existing slot identifier.
+ *
+ * @param memories Memory resources used in an inference.
+ * @param keys Unique identifiers where each element corresponds to a
+ * memory resource element in "memories".
+ * @return Unique slot identifiers where each returned slot element
+ * corresponds to a memory resource element in "memories".
+ */
+ std::vector<int32_t> getSlots(const hardware::hidl_vec<hardware::hidl_memory>& memories,
+ const std::vector<intptr_t>& keys);
+
+ /*
+ * This function performs two different actions:
+ * 1) Removes an entry from the cache (if present), including the local
+ * storage of the hidl_memory object. Note that this call does not
+ * free any corresponding hidl_memory object in ExecutionBurstServer,
+ * which is separately freed via IBurstContext::freeMemory.
+ * 2) Return whether a cache entry was removed and which slot was removed if
+ * found. If the key did not to correspond to any entry in the cache, a
+ * slot number of 0 is returned. The slot number and whether the entry
+ * existed is useful so the same slot can be freed in the
+ * ExecutionBurstServer's cache via IBurstContext::freeMemory.
+ */
+ std::pair<bool, int32_t> freeMemory(intptr_t key);
+
+ private:
+ int32_t getSlotLocked(const hardware::hidl_memory& memory, intptr_t key);
+ int32_t allocateSlotLocked();
+
+ std::mutex mMutex;
+ std::stack<int32_t, std::vector<int32_t>> mFreeSlots;
+ std::map<intptr_t, int32_t> mMemoryIdToSlot;
+ std::vector<hardware::hidl_memory> mMemoryCache;
+ };
+
+ /**
+ * Creates a burst controller on a prepared model.
+ *
+ * Prefer this over ExecutionBurstController's constructor.
+ *
+ * @param preparedModel Model prepared for execution to execute on.
+ * @param pollingTimeWindow How much time (in microseconds) the
+ * ExecutionBurstController is allowed to poll the FMQ before waiting on
+ * the blocking futex. Polling may result in lower latencies at the
+ * potential cost of more power usage.
+ * @return ExecutionBurstController Execution burst controller object.
+ */
+ static std::unique_ptr<ExecutionBurstController> create(
+ const sp<hardware::neuralnetworks::V1_2::IPreparedModel>& preparedModel,
+ std::chrono::microseconds pollingTimeWindow);
+
+ // prefer calling ExecutionBurstController::create
+ ExecutionBurstController(const std::shared_ptr<RequestChannelSender>& requestChannelSender,
+ const std::shared_ptr<ResultChannelReceiver>& resultChannelReceiver,
+ const sp<hardware::neuralnetworks::V1_2::IBurstContext>& burstContext,
+ const sp<ExecutionBurstCallback>& callback,
+ const sp<hardware::hidl_death_recipient>& deathHandler = nullptr);
+
+ // explicit destructor to unregister the death recipient
+ ~ExecutionBurstController();
+
+ /**
+ * Execute a request on a model.
+ *
+ * @param request Arguments to be executed on a model.
+ * @param measure Whether to collect timing measurements, either YES or NO
+ * @param memoryIds Identifiers corresponding to each memory object in the
+ * request's pools.
+ * @return A tuple of:
+ * - result code of the execution
+ * - dynamic output shapes from the execution
+ * - any execution time measurements of the execution
+ * - whether or not a failed burst execution should be re-run using a
+ * different path (e.g., IPreparedModel::executeSynchronously)
+ */
+ std::tuple<int, std::vector<hardware::neuralnetworks::V1_2::OutputShape>,
+ hardware::neuralnetworks::V1_2::Timing, bool>
+ compute(const hardware::neuralnetworks::V1_0::Request& request,
+ hardware::neuralnetworks::V1_2::MeasureTiming measure,
+ const std::vector<intptr_t>& memoryIds);
+
+ /**
+ * Propagate a user's freeing of memory to the service.
+ *
+ * @param key Key corresponding to the memory object.
+ */
+ void freeMemory(intptr_t key);
+
+ private:
+ std::mutex mMutex;
+ const std::shared_ptr<RequestChannelSender> mRequestChannelSender;
+ const std::shared_ptr<ResultChannelReceiver> mResultChannelReceiver;
+ const sp<hardware::neuralnetworks::V1_2::IBurstContext> mBurstContext;
+ const sp<ExecutionBurstCallback> mMemoryCache;
+ const sp<hardware::hidl_death_recipient> mDeathHandler;
+};
+
+} // namespace android::nn
+
+#endif // ANDROID_FRAMEWORKS_ML_NN_COMMON_EXECUTION_BURST_CONTROLLER_H
diff --git a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstServer.h b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstServer.h
new file mode 100644
index 0000000..2e109b2
--- /dev/null
+++ b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstServer.h
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_FRAMEWORKS_ML_NN_COMMON_EXECUTION_BURST_SERVER_H
+#define ANDROID_FRAMEWORKS_ML_NN_COMMON_EXECUTION_BURST_SERVER_H
+
+#include "ExecutionBurstUtils.h"
+
+#include <android-base/macros.h>
+#include <android/hardware/neuralnetworks/1.0/types.h>
+#include <android/hardware/neuralnetworks/1.1/types.h>
+#include <android/hardware/neuralnetworks/1.2/IBurstCallback.h>
+#include <android/hardware/neuralnetworks/1.2/IPreparedModel.h>
+#include <android/hardware/neuralnetworks/1.2/types.h>
+#include <fmq/MessageQueue.h>
+#include <hidl/MQDescriptor.h>
+
+#include <atomic>
+#include <chrono>
+#include <memory>
+#include <optional>
+#include <thread>
+#include <tuple>
+#include <vector>
+
+namespace android::nn {
+
+/**
+ * The ExecutionBurstServer class is responsible for waiting for and
+ * deserializing a request object from a FMQ, performing the inference, and
+ * serializing the result back across another FMQ.
+ */
+class ExecutionBurstServer : public hardware::neuralnetworks::V1_2::IBurstContext {
+ DISALLOW_IMPLICIT_CONSTRUCTORS(ExecutionBurstServer);
+
+ public:
+ /**
+ * IBurstExecutorWithCache is a callback object passed to
+ * ExecutionBurstServer's factory function that is used to perform an
+ * execution. Because some memory resources are needed across multiple
+ * executions, this object also contains a local cache that can directly be
+ * used in the execution.
+ *
+ * ExecutionBurstServer will never access its IBurstExecutorWithCache object
+ * with concurrent calls.
+ */
+ class IBurstExecutorWithCache {
+ DISALLOW_COPY_AND_ASSIGN(IBurstExecutorWithCache);
+
+ public:
+ IBurstExecutorWithCache() = default;
+ virtual ~IBurstExecutorWithCache() = default;
+
+ /**
+ * Checks if a cache entry specified by a slot is present in the cache.
+ *
+ * @param slot Identifier of the cache entry.
+ * @return 'true' if the cache entry is present in the cache, 'false'
+ * otherwise.
+ */
+ virtual bool isCacheEntryPresent(int32_t slot) const = 0;
+
+ /**
+ * Adds an entry specified by a slot to the cache.
+ *
+ * The caller of this function must ensure that the cache entry that is
+ * being added is not already present in the cache. This can be checked
+ * via isCacheEntryPresent.
+ *
+ * @param memory Memory resource to be cached.
+ * @param slot Slot identifier corresponding to the memory resource.
+ */
+ virtual void addCacheEntry(const hardware::hidl_memory& memory, int32_t slot) = 0;
+
+ /**
+ * Removes an entry specified by a slot from the cache.
+ *
+ * If the cache entry corresponding to the slot number does not exist,
+ * the call does nothing.
+ *
+ * @param slot Slot identifier corresponding to the memory resource.
+ */
+ virtual void removeCacheEntry(int32_t slot) = 0;
+
+ /**
+ * Perform an execution.
+ *
+ * @param request Request object with inputs and outputs specified.
+ * Request::pools is empty, and DataLocation::poolIndex instead
+ * refers to the 'slots' argument as if it were Request::pools.
+ * @param slots Slots corresponding to the cached memory entries to be
+ * used.
+ * @param measure Whether timing information is requested for the
+ * execution.
+ * @return Result of the execution, including the status of the
+ * execution, dynamic output shapes, and any timing information.
+ */
+ virtual std::tuple<hardware::neuralnetworks::V1_0::ErrorStatus,
+ hardware::hidl_vec<hardware::neuralnetworks::V1_2::OutputShape>,
+ hardware::neuralnetworks::V1_2::Timing>
+ execute(const hardware::neuralnetworks::V1_0::Request& request,
+ const std::vector<int32_t>& slots,
+ hardware::neuralnetworks::V1_2::MeasureTiming measure) = 0;
+ };
+
+ /**
+ * Create automated context to manage FMQ-based executions.
+ *
+ * This function is intended to be used by a service to automatically:
+ * 1) Receive data from a provided FMQ
+ * 2) Execute a model with the given information
+ * 3) Send the result to the created FMQ
+ *
+ * @param callback Callback used to retrieve memories corresponding to
+ * unrecognized slots.
+ * @param requestChannel Input FMQ channel through which the client passes the
+ * request to the service.
+ * @param resultChannel Output FMQ channel from which the client can retrieve
+ * the result of the execution.
+ * @param executorWithCache Object which maintains a local cache of the
+ * memory pools and executes using the cached memory pools.
+ * @param pollingTimeWindow How much time (in microseconds) the
+ * ExecutionBurstServer is allowed to poll the FMQ before waiting on
+ * the blocking futex. Polling may result in lower latencies at the
+ * potential cost of more power usage.
+ * @result IBurstContext Handle to the burst context.
+ */
+ static sp<ExecutionBurstServer> create(
+ const sp<hardware::neuralnetworks::V1_2::IBurstCallback>& callback,
+ const FmqRequestDescriptor& requestChannel, const FmqResultDescriptor& resultChannel,
+ std::shared_ptr<IBurstExecutorWithCache> executorWithCache,
+ std::chrono::microseconds pollingTimeWindow = std::chrono::microseconds{0});
+
+ /**
+ * Create automated context to manage FMQ-based executions.
+ *
+ * This function is intended to be used by a service to automatically:
+ * 1) Receive data from a provided FMQ
+ * 2) Execute a model with the given information
+ * 3) Send the result to the created FMQ
+ *
+ * @param callback Callback used to retrieve memories corresponding to
+ * unrecognized slots.
+ * @param requestChannel Input FMQ channel through which the client passes the
+ * request to the service.
+ * @param resultChannel Output FMQ channel from which the client can retrieve
+ * the result of the execution.
+ * @param preparedModel PreparedModel that the burst object was created from.
+ * IPreparedModel::executeSynchronously will be used to perform the
+ * execution.
+ * @param pollingTimeWindow How much time (in microseconds) the
+ * ExecutionBurstServer is allowed to poll the FMQ before waiting on
+ * the blocking futex. Polling may result in lower latencies at the
+ * potential cost of more power usage.
+ * @result IBurstContext Handle to the burst context.
+ */
+ static sp<ExecutionBurstServer> create(
+ const sp<hardware::neuralnetworks::V1_2::IBurstCallback>& callback,
+ const FmqRequestDescriptor& requestChannel, const FmqResultDescriptor& resultChannel,
+ hardware::neuralnetworks::V1_2::IPreparedModel* preparedModel,
+ std::chrono::microseconds pollingTimeWindow = std::chrono::microseconds{0});
+
+ ExecutionBurstServer(const sp<hardware::neuralnetworks::V1_2::IBurstCallback>& callback,
+ std::unique_ptr<RequestChannelReceiver> requestChannel,
+ std::unique_ptr<ResultChannelSender> resultChannel,
+ std::shared_ptr<IBurstExecutorWithCache> cachedExecutor);
+ ~ExecutionBurstServer();
+
+ // Used by the NN runtime to preemptively remove any stored memory.
+ hardware::Return<void> freeMemory(int32_t slot) override;
+
+ private:
+ // Ensures all cache entries contained in mExecutorWithCache are present in
+ // the cache. If they are not present, they are retrieved (via
+ // IBurstCallback::getMemories) and added to mExecutorWithCache.
+ //
+ // This method is locked via mMutex when it is called.
+ void ensureCacheEntriesArePresentLocked(const std::vector<int32_t>& slots);
+
+ // Work loop that will continue processing execution requests until the
+ // ExecutionBurstServer object is freed.
+ void task();
+
+ std::thread mWorker;
+ std::mutex mMutex;
+ std::atomic<bool> mTeardown{false};
+ const sp<hardware::neuralnetworks::V1_2::IBurstCallback> mCallback;
+ const std::unique_ptr<RequestChannelReceiver> mRequestChannelReceiver;
+ const std::unique_ptr<ResultChannelSender> mResultChannelSender;
+ const std::shared_ptr<IBurstExecutorWithCache> mExecutorWithCache;
+};
+
+} // namespace android::nn
+
+#endif // ANDROID_FRAMEWORKS_ML_NN_COMMON_EXECUTION_BURST_SERVER_H
diff --git a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstUtils.h b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstUtils.h
new file mode 100644
index 0000000..8a41591
--- /dev/null
+++ b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstUtils.h
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_EXECUTION_BURST_UTILS_H
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_EXECUTION_BURST_UTILS_H
+
+#include <android/hardware/neuralnetworks/1.0/types.h>
+#include <android/hardware/neuralnetworks/1.1/types.h>
+#include <android/hardware/neuralnetworks/1.2/types.h>
+#include <fmq/MessageQueue.h>
+#include <hidl/MQDescriptor.h>
+
+#include <atomic>
+#include <chrono>
+#include <memory>
+#include <optional>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+namespace android::hardware::neuralnetworks::V1_2::utils {
+
+/**
+ * Number of elements in the FMQ.
+ */
+constexpr const size_t kExecutionBurstChannelLength = 1024;
+
+using FmqRequestDescriptor = MQDescriptorSync<FmqRequestDatum>;
+using FmqResultDescriptor = MQDescriptorSync<FmqResultDatum>;
+
+/**
+ * Function to serialize a request.
+ *
+ * Prefer calling RequestChannelSender::send.
+ *
+ * @param request Request object without the pool information.
+ * @param measure Whether to collect timing information for the execution.
+ * @param memoryIds Slot identifiers corresponding to memory resources for the
+ * request.
+ * @return Serialized FMQ request data.
+ */
+std::vector<hardware::neuralnetworks::V1_2::FmqRequestDatum> serialize(
+ const hardware::neuralnetworks::V1_0::Request& request,
+ hardware::neuralnetworks::V1_2::MeasureTiming measure, const std::vector<int32_t>& slots);
+
+/**
+ * Deserialize the FMQ request data.
+ *
+ * The three resulting fields are the Request object (where Request::pools is
+ * empty), slot identifiers (which are stand-ins for Request::pools), and
+ * whether timing information must be collected for the run.
+ *
+ * @param data Serialized FMQ request data.
+ * @return Request object if successfully deserialized, std::nullopt otherwise.
+ */
+std::optional<std::tuple<hardware::neuralnetworks::V1_0::Request, std::vector<int32_t>,
+ hardware::neuralnetworks::V1_2::MeasureTiming>>
+deserialize(const std::vector<hardware::neuralnetworks::V1_2::FmqRequestDatum>& data);
+
+/**
+ * Function to serialize results.
+ *
+ * Prefer calling ResultChannelSender::send.
+ *
+ * @param errorStatus Status of the execution.
+ * @param outputShapes Dynamic shapes of the output tensors.
+ * @param timing Timing information of the execution.
+ * @return Serialized FMQ result data.
+ */
+std::vector<hardware::neuralnetworks::V1_2::FmqResultDatum> serialize(
+ hardware::neuralnetworks::V1_0::ErrorStatus errorStatus,
+ const std::vector<hardware::neuralnetworks::V1_2::OutputShape>& outputShapes,
+ hardware::neuralnetworks::V1_2::Timing timing);
+
+/**
+ * Deserialize the FMQ result data.
+ *
+ * The three resulting fields are the status of the execution, the dynamic
+ * shapes of the output tensors, and the timing information of the execution.
+ *
+ * @param data Serialized FMQ result data.
+ * @return Result object if successfully deserialized, std::nullopt otherwise.
+ */
+std::optional<std::tuple<hardware::neuralnetworks::V1_0::ErrorStatus,
+ std::vector<hardware::neuralnetworks::V1_2::OutputShape>,
+ hardware::neuralnetworks::V1_2::Timing>>
+deserialize(const std::vector<hardware::neuralnetworks::V1_2::FmqResultDatum>& data);
+
+/**
+ * Convert result code to error status.
+ *
+ * @param resultCode Result code to be converted.
+ * @return ErrorStatus Resultant error status.
+ */
+hardware::neuralnetworks::V1_0::ErrorStatus legacyConvertResultCodeToErrorStatus(int resultCode);
+
+/**
+ * RequestChannelSender is responsible for serializing the result packet of
+ * information, sending it on the result channel, and signaling that the data is
+ * available.
+ */
+class RequestChannelSender {
+ using FmqRequestDescriptor =
+ hardware::MQDescriptorSync<hardware::neuralnetworks::V1_2::FmqRequestDatum>;
+ using FmqRequestChannel =
+ hardware::MessageQueue<hardware::neuralnetworks::V1_2::FmqRequestDatum,
+ hardware::kSynchronizedReadWrite>;
+
+ public:
+ /**
+ * Create the sending end of a request channel.
+ *
+ * Prefer this call over the constructor.
+ *
+ * @param channelLength Number of elements in the FMQ.
+ * @return A pair of ResultChannelReceiver and the FMQ descriptor on
+ * successful creation, both nullptr otherwise.
+ */
+ static std::pair<std::unique_ptr<RequestChannelSender>, const FmqRequestDescriptor*> create(
+ size_t channelLength);
+
+ /**
+ * Send the request to the channel.
+ *
+ * @param request Request object without the pool information.
+ * @param measure Whether to collect timing information for the execution.
+ * @param memoryIds Slot identifiers corresponding to memory resources for
+ * the request.
+ * @return 'true' on successful send, 'false' otherwise.
+ */
+ bool send(const hardware::neuralnetworks::V1_0::Request& request,
+ hardware::neuralnetworks::V1_2::MeasureTiming measure,
+ const std::vector<int32_t>& slots);
+
+ /**
+ * Method to mark the channel as invalid, causing all future calls to
+ * RequestChannelSender::send to immediately return false without attempting
+ * to send a message across the FMQ.
+ */
+ void invalidate();
+
+ // prefer calling RequestChannelSender::send
+ bool sendPacket(const std::vector<hardware::neuralnetworks::V1_2::FmqRequestDatum>& packet);
+
+ RequestChannelSender(std::unique_ptr<FmqRequestChannel> fmqRequestChannel);
+
+ private:
+ const std::unique_ptr<FmqRequestChannel> mFmqRequestChannel;
+ std::atomic<bool> mValid{true};
+};
+
+/**
+ * RequestChannelReceiver is responsible for waiting on the channel until the
+ * packet is available, extracting the packet from the channel, and
+ * deserializing the packet.
+ *
+ * Because the receiver can wait on a packet that may never come (e.g., because
+ * the sending side of the packet has been closed), this object can be
+ * invalidated, unblocking the receiver.
+ */
+class RequestChannelReceiver {
+ using FmqRequestChannel =
+ hardware::MessageQueue<hardware::neuralnetworks::V1_2::FmqRequestDatum,
+ hardware::kSynchronizedReadWrite>;
+
+ public:
+ /**
+ * Create the receiving end of a request channel.
+ *
+ * Prefer this call over the constructor.
+ *
+ * @param requestChannel Descriptor for the request channel.
+ * @param pollingTimeWindow How much time (in microseconds) the
+ * RequestChannelReceiver is allowed to poll the FMQ before waiting on
+ * the blocking futex. Polling may result in lower latencies at the
+ * potential cost of more power usage.
+ * @return RequestChannelReceiver on successful creation, nullptr otherwise.
+ */
+ static std::unique_ptr<RequestChannelReceiver> create(
+ const FmqRequestDescriptor& requestChannel,
+ std::chrono::microseconds pollingTimeWindow);
+
+ /**
+ * Get the request from the channel.
+ *
+ * This method will block until either:
+ * 1) The packet has been retrieved, or
+ * 2) The receiver has been invalidated
+ *
+ * @return Request object if successfully received, std::nullopt if error or
+ * if the receiver object was invalidated.
+ */
+ std::optional<std::tuple<hardware::neuralnetworks::V1_0::Request, std::vector<int32_t>,
+ hardware::neuralnetworks::V1_2::MeasureTiming>>
+ getBlocking();
+
+ /**
+ * Method to mark the channel as invalid, unblocking any current or future
+ * calls to RequestChannelReceiver::getBlocking.
+ */
+ void invalidate();
+
+ RequestChannelReceiver(std::unique_ptr<FmqRequestChannel> fmqRequestChannel,
+ std::chrono::microseconds pollingTimeWindow);
+
+ private:
+ std::optional<std::vector<hardware::neuralnetworks::V1_2::FmqRequestDatum>> getPacketBlocking();
+
+ const std::unique_ptr<FmqRequestChannel> mFmqRequestChannel;
+ std::atomic<bool> mTeardown{false};
+ const std::chrono::microseconds kPollingTimeWindow;
+};
+
+/**
+ * ResultChannelSender is responsible for serializing the result packet of
+ * information, sending it on the result channel, and signaling that the data is
+ * available.
+ */
+class ResultChannelSender {
+ using FmqResultChannel = hardware::MessageQueue<hardware::neuralnetworks::V1_2::FmqResultDatum,
+ hardware::kSynchronizedReadWrite>;
+
+ public:
+ /**
+ * Create the sending end of a result channel.
+ *
+ * Prefer this call over the constructor.
+ *
+ * @param resultChannel Descriptor for the result channel.
+ * @return ResultChannelSender on successful creation, nullptr otherwise.
+ */
+ static std::unique_ptr<ResultChannelSender> create(const FmqResultDescriptor& resultChannel);
+
+ /**
+ * Send the result to the channel.
+ *
+ * @param errorStatus Status of the execution.
+ * @param outputShapes Dynamic shapes of the output tensors.
+ * @param timing Timing information of the execution.
+ * @return 'true' on successful send, 'false' otherwise.
+ */
+ bool send(hardware::neuralnetworks::V1_0::ErrorStatus errorStatus,
+ const std::vector<hardware::neuralnetworks::V1_2::OutputShape>& outputShapes,
+ hardware::neuralnetworks::V1_2::Timing timing);
+
+ // prefer calling ResultChannelSender::send
+ bool sendPacket(const std::vector<hardware::neuralnetworks::V1_2::FmqResultDatum>& packet);
+
+ ResultChannelSender(std::unique_ptr<FmqResultChannel> fmqResultChannel);
+
+ private:
+ const std::unique_ptr<FmqResultChannel> mFmqResultChannel;
+};
+
+/**
+ * ResultChannelReceiver is responsible for waiting on the channel until the
+ * packet is available, extracting the packet from the channel, and
+ * deserializing the packet.
+ *
+ * Because the receiver can wait on a packet that may never come (e.g., because
+ * the sending side of the packet has been closed), this object can be
+ * invalidated, unblocking the receiver.
+ */
+class ResultChannelReceiver {
+ using FmqResultDescriptor =
+ hardware::MQDescriptorSync<hardware::neuralnetworks::V1_2::FmqResultDatum>;
+ using FmqResultChannel = hardware::MessageQueue<hardware::neuralnetworks::V1_2::FmqResultDatum,
+ hardware::kSynchronizedReadWrite>;
+
+ public:
+ /**
+ * Create the receiving end of a result channel.
+ *
+ * Prefer this call over the constructor.
+ *
+ * @param channelLength Number of elements in the FMQ.
+ * @param pollingTimeWindow How much time (in microseconds) the
+ * ResultChannelReceiver is allowed to poll the FMQ before waiting on
+ * the blocking futex. Polling may result in lower latencies at the
+ * potential cost of more power usage.
+ * @return A pair of ResultChannelReceiver and the FMQ descriptor on
+ * successful creation, both nullptr otherwise.
+ */
+ static std::pair<std::unique_ptr<ResultChannelReceiver>, const FmqResultDescriptor*> create(
+ size_t channelLength, std::chrono::microseconds pollingTimeWindow);
+
+ /**
+ * Get the result from the channel.
+ *
+ * This method will block until either:
+ * 1) The packet has been retrieved, or
+ * 2) The receiver has been invalidated
+ *
+ * @return Result object if successfully received, std::nullopt if error or
+ * if the receiver object was invalidated.
+ */
+ std::optional<std::tuple<hardware::neuralnetworks::V1_0::ErrorStatus,
+ std::vector<hardware::neuralnetworks::V1_2::OutputShape>,
+ hardware::neuralnetworks::V1_2::Timing>>
+ getBlocking();
+
+ /**
+ * Method to mark the channel as invalid, unblocking any current or future
+ * calls to ResultChannelReceiver::getBlocking.
+ */
+ void invalidate();
+
+ // prefer calling ResultChannelReceiver::getBlocking
+ std::optional<std::vector<hardware::neuralnetworks::V1_2::FmqResultDatum>> getPacketBlocking();
+
+ ResultChannelReceiver(std::unique_ptr<FmqResultChannel> fmqResultChannel,
+ std::chrono::microseconds pollingTimeWindow);
+
+ private:
+ const std::unique_ptr<FmqResultChannel> mFmqResultChannel;
+ std::atomic<bool> mValid{true};
+ const std::chrono::microseconds kPollingTimeWindow;
+};
+
+} // namespace android::hardware::neuralnetworks::V1_2::utils
+
+#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_EXECUTION_BURST_UTILS_H
diff --git a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/PreparedModel.h b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/PreparedModel.h
index 6a56a82..fb11130 100644
--- a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/PreparedModel.h
+++ b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/PreparedModel.h
@@ -36,7 +36,8 @@
namespace android::hardware::neuralnetworks::V1_2::utils {
// Class that adapts V1_2::IPreparedModel to nn::IPreparedModel.
-class PreparedModel final : public nn::IPreparedModel {
+class PreparedModel final : public nn::IPreparedModel,
+ public std::enable_shared_from_this<PreparedModel> {
struct PrivateConstructorTag {};
public:
@@ -57,6 +58,8 @@
const nn::OptionalDuration& loopTimeoutDuration,
const nn::OptionalDuration& timeoutDurationAfterFence) const override;
+ nn::GeneralResult<nn::SharedBurst> configureExecutionBurst() const override;
+
std::any getUnderlyingResource() const override;
private:
diff --git a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Utils.h b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Utils.h
index c289fc8..3233114 100644
--- a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Utils.h
+++ b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Utils.h
@@ -54,6 +54,12 @@
return result.has_value();
}
+template <typename Type>
+auto convertFromNonCanonical(const Type& nonCanonicalObject)
+ -> decltype(convert(nn::convert(nonCanonicalObject).value())) {
+ return convert(NN_TRY(nn::convert(nonCanonicalObject)));
+}
+
} // namespace android::hardware::neuralnetworks::V1_2::utils
#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_H
diff --git a/neuralnetworks/1.2/utils/src/Callbacks.cpp b/neuralnetworks/1.2/utils/src/Callbacks.cpp
index fefa122..9f54bb1 100644
--- a/neuralnetworks/1.2/utils/src/Callbacks.cpp
+++ b/neuralnetworks/1.2/utils/src/Callbacks.cpp
@@ -43,6 +43,15 @@
namespace android::hardware::neuralnetworks::V1_2::utils {
namespace {
+nn::GeneralResult<nn::SharedPreparedModel> prepareModelCallback(
+ V1_0::ErrorStatus status, const sp<V1_0::IPreparedModel>& preparedModel) {
+ if (const auto dynamicPreparedModel =
+ V1_2::IPreparedModel::castFrom(preparedModel).withDefault(nullptr)) {
+ return V1_2::utils::prepareModelCallback(status, dynamicPreparedModel);
+ }
+ return V1_0::utils::prepareModelCallback(status, preparedModel);
+}
+
nn::GeneralResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>>
convertExecutionGeneralResultsHelper(const hidl_vec<OutputShape>& outputShapes,
const Timing& timing) {
@@ -72,7 +81,7 @@
Return<void> PreparedModelCallback::notify(V1_0::ErrorStatus status,
const sp<V1_0::IPreparedModel>& preparedModel) {
- mData.put(V1_0::utils::prepareModelCallback(status, preparedModel));
+ mData.put(prepareModelCallback(status, preparedModel));
return Void();
}
diff --git a/neuralnetworks/1.2/utils/src/Conversions.cpp b/neuralnetworks/1.2/utils/src/Conversions.cpp
index 062f6f7..7ae483e 100644
--- a/neuralnetworks/1.2/utils/src/Conversions.cpp
+++ b/neuralnetworks/1.2/utils/src/Conversions.cpp
@@ -304,7 +304,11 @@
}
GeneralResult<SharedHandle> unvalidatedConvert(const hidl_handle& hidlHandle) {
- return hal::utils::sharedHandleFromNativeHandle(hidlHandle.getNativeHandle());
+ if (hidlHandle.getNativeHandle() == nullptr) {
+ return nullptr;
+ }
+ auto handle = NN_TRY(hal::utils::sharedHandleFromNativeHandle(hidlHandle.getNativeHandle()));
+ return std::make_shared<const Handle>(std::move(handle));
}
GeneralResult<DeviceType> convert(const hal::V1_2::DeviceType& deviceType) {
@@ -365,7 +369,7 @@
return V1_0::utils::unvalidatedConvert(operandValues);
}
-nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::Memory& memory) {
+nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::SharedMemory& memory) {
return V1_0::utils::unvalidatedConvert(memory);
}
@@ -588,7 +592,10 @@
}
nn::GeneralResult<hidl_handle> unvalidatedConvert(const nn::SharedHandle& handle) {
- return hal::utils::hidlHandleFromSharedHandle(handle);
+ if (handle == nullptr) {
+ return {};
+ }
+ return hal::utils::hidlHandleFromSharedHandle(*handle);
}
nn::GeneralResult<DeviceType> convert(const nn::DeviceType& deviceType) {
diff --git a/neuralnetworks/1.2/utils/src/Device.cpp b/neuralnetworks/1.2/utils/src/Device.cpp
index 9fe0de2..1954dfa 100644
--- a/neuralnetworks/1.2/utils/src/Device.cpp
+++ b/neuralnetworks/1.2/utils/src/Device.cpp
@@ -199,6 +199,10 @@
return kDeviceType;
}
+bool Device::isUpdatable() const {
+ return false;
+}
+
const std::vector<nn::Extension>& Device::getSupportedExtensions() const {
return kExtensions;
}
diff --git a/neuralnetworks/1.2/utils/src/ExecutionBurstController.cpp b/neuralnetworks/1.2/utils/src/ExecutionBurstController.cpp
new file mode 100644
index 0000000..2265861
--- /dev/null
+++ b/neuralnetworks/1.2/utils/src/ExecutionBurstController.cpp
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "ExecutionBurstController"
+
+#include "ExecutionBurstController.h"
+
+#include <android-base/logging.h>
+
+#include <algorithm>
+#include <cstring>
+#include <limits>
+#include <memory>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "ExecutionBurstUtils.h"
+#include "HalInterfaces.h"
+#include "Tracing.h"
+#include "Utils.h"
+
+namespace android::nn {
+namespace {
+
+class BurstContextDeathHandler : public hardware::hidl_death_recipient {
+ public:
+ using Callback = std::function<void()>;
+
+ BurstContextDeathHandler(const Callback& onDeathCallback) : mOnDeathCallback(onDeathCallback) {
+ CHECK(onDeathCallback != nullptr);
+ }
+
+ void serviceDied(uint64_t /*cookie*/, const wp<hidl::base::V1_0::IBase>& /*who*/) override {
+ LOG(ERROR) << "BurstContextDeathHandler::serviceDied -- service unexpectedly died!";
+ mOnDeathCallback();
+ }
+
+ private:
+ const Callback mOnDeathCallback;
+};
+
+} // anonymous namespace
+
+hardware::Return<void> ExecutionBurstController::ExecutionBurstCallback::getMemories(
+ const hardware::hidl_vec<int32_t>& slots, getMemories_cb cb) {
+ std::lock_guard<std::mutex> guard(mMutex);
+
+ // get all memories
+ hardware::hidl_vec<hardware::hidl_memory> memories(slots.size());
+ std::transform(slots.begin(), slots.end(), memories.begin(), [this](int32_t slot) {
+ return slot < mMemoryCache.size() ? mMemoryCache[slot] : hardware::hidl_memory{};
+ });
+
+ // ensure all memories are valid
+ if (!std::all_of(memories.begin(), memories.end(),
+ [](const hardware::hidl_memory& memory) { return memory.valid(); })) {
+ cb(V1_0::ErrorStatus::INVALID_ARGUMENT, {});
+ return hardware::Void();
+ }
+
+ // return successful
+ cb(V1_0::ErrorStatus::NONE, std::move(memories));
+ return hardware::Void();
+}
+
+std::vector<int32_t> ExecutionBurstController::ExecutionBurstCallback::getSlots(
+ const hardware::hidl_vec<hardware::hidl_memory>& memories,
+ const std::vector<intptr_t>& keys) {
+ std::lock_guard<std::mutex> guard(mMutex);
+
+ // retrieve (or bind) all slots corresponding to memories
+ std::vector<int32_t> slots;
+ slots.reserve(memories.size());
+ for (size_t i = 0; i < memories.size(); ++i) {
+ slots.push_back(getSlotLocked(memories[i], keys[i]));
+ }
+ return slots;
+}
+
+std::pair<bool, int32_t> ExecutionBurstController::ExecutionBurstCallback::freeMemory(
+ intptr_t key) {
+ std::lock_guard<std::mutex> guard(mMutex);
+
+ auto iter = mMemoryIdToSlot.find(key);
+ if (iter == mMemoryIdToSlot.end()) {
+ return {false, 0};
+ }
+ const int32_t slot = iter->second;
+ mMemoryIdToSlot.erase(key);
+ mMemoryCache[slot] = {};
+ mFreeSlots.push(slot);
+ return {true, slot};
+}
+
+int32_t ExecutionBurstController::ExecutionBurstCallback::getSlotLocked(
+ const hardware::hidl_memory& memory, intptr_t key) {
+ auto iter = mMemoryIdToSlot.find(key);
+ if (iter == mMemoryIdToSlot.end()) {
+ const int32_t slot = allocateSlotLocked();
+ mMemoryIdToSlot[key] = slot;
+ mMemoryCache[slot] = memory;
+ return slot;
+ } else {
+ const int32_t slot = iter->second;
+ return slot;
+ }
+}
+
+int32_t ExecutionBurstController::ExecutionBurstCallback::allocateSlotLocked() {
+ constexpr size_t kMaxNumberOfSlots = std::numeric_limits<int32_t>::max();
+
+ // if there is a free slot, use it
+ if (mFreeSlots.size() > 0) {
+ const int32_t slot = mFreeSlots.top();
+ mFreeSlots.pop();
+ return slot;
+ }
+
+ // otherwise use a slot for the first time
+ CHECK(mMemoryCache.size() < kMaxNumberOfSlots) << "Exceeded maximum number of slots!";
+ const int32_t slot = static_cast<int32_t>(mMemoryCache.size());
+ mMemoryCache.emplace_back();
+
+ return slot;
+}
+
+std::unique_ptr<ExecutionBurstController> ExecutionBurstController::create(
+ const sp<V1_2::IPreparedModel>& preparedModel,
+ std::chrono::microseconds pollingTimeWindow) {
+ // check inputs
+ if (preparedModel == nullptr) {
+ LOG(ERROR) << "ExecutionBurstController::create passed a nullptr";
+ return nullptr;
+ }
+
+ // create callback object
+ sp<ExecutionBurstCallback> callback = new ExecutionBurstCallback();
+
+ // create FMQ objects
+ auto [requestChannelSenderTemp, requestChannelDescriptor] =
+ RequestChannelSender::create(kExecutionBurstChannelLength);
+ auto [resultChannelReceiverTemp, resultChannelDescriptor] =
+ ResultChannelReceiver::create(kExecutionBurstChannelLength, pollingTimeWindow);
+ std::shared_ptr<RequestChannelSender> requestChannelSender =
+ std::move(requestChannelSenderTemp);
+ std::shared_ptr<ResultChannelReceiver> resultChannelReceiver =
+ std::move(resultChannelReceiverTemp);
+
+ // check FMQ objects
+ if (!requestChannelSender || !resultChannelReceiver || !requestChannelDescriptor ||
+ !resultChannelDescriptor) {
+ LOG(ERROR) << "ExecutionBurstController::create failed to create FastMessageQueue";
+ return nullptr;
+ }
+
+ // configure burst
+ V1_0::ErrorStatus errorStatus;
+ sp<IBurstContext> burstContext;
+ const hardware::Return<void> ret = preparedModel->configureExecutionBurst(
+ callback, *requestChannelDescriptor, *resultChannelDescriptor,
+ [&errorStatus, &burstContext](V1_0::ErrorStatus status,
+ const sp<IBurstContext>& context) {
+ errorStatus = status;
+ burstContext = context;
+ });
+
+ // check burst
+ if (!ret.isOk()) {
+ LOG(ERROR) << "IPreparedModel::configureExecutionBurst failed with description "
+ << ret.description();
+ return nullptr;
+ }
+ if (errorStatus != V1_0::ErrorStatus::NONE) {
+ LOG(ERROR) << "IPreparedModel::configureExecutionBurst failed with status "
+ << toString(errorStatus);
+ return nullptr;
+ }
+ if (burstContext == nullptr) {
+ LOG(ERROR) << "IPreparedModel::configureExecutionBurst returned nullptr for burst";
+ return nullptr;
+ }
+
+ // create death handler object
+ BurstContextDeathHandler::Callback onDeathCallback = [requestChannelSender,
+ resultChannelReceiver] {
+ requestChannelSender->invalidate();
+ resultChannelReceiver->invalidate();
+ };
+ const sp<BurstContextDeathHandler> deathHandler = new BurstContextDeathHandler(onDeathCallback);
+
+ // linkToDeath registers a callback that will be invoked on service death to
+ // proactively handle service crashes. If the linkToDeath call fails,
+ // asynchronous calls are susceptible to hangs if the service crashes before
+ // providing the response.
+ const hardware::Return<bool> deathHandlerRet = burstContext->linkToDeath(deathHandler, 0);
+ if (!deathHandlerRet.isOk() || deathHandlerRet != true) {
+ LOG(ERROR) << "ExecutionBurstController::create -- Failed to register a death recipient "
+ "for the IBurstContext object.";
+ return nullptr;
+ }
+
+ // make and return controller
+ return std::make_unique<ExecutionBurstController>(requestChannelSender, resultChannelReceiver,
+ burstContext, callback, deathHandler);
+}
+
+ExecutionBurstController::ExecutionBurstController(
+ const std::shared_ptr<RequestChannelSender>& requestChannelSender,
+ const std::shared_ptr<ResultChannelReceiver>& resultChannelReceiver,
+ const sp<IBurstContext>& burstContext, const sp<ExecutionBurstCallback>& callback,
+ const sp<hardware::hidl_death_recipient>& deathHandler)
+ : mRequestChannelSender(requestChannelSender),
+ mResultChannelReceiver(resultChannelReceiver),
+ mBurstContext(burstContext),
+ mMemoryCache(callback),
+ mDeathHandler(deathHandler) {}
+
+ExecutionBurstController::~ExecutionBurstController() {
+ // It is safe to ignore any errors resulting from this unlinkToDeath call
+ // because the ExecutionBurstController object is already being destroyed
+ // and its underlying IBurstContext object is no longer being used by the NN
+ // runtime.
+ if (mDeathHandler) {
+ mBurstContext->unlinkToDeath(mDeathHandler).isOk();
+ }
+}
+
+static std::tuple<int, std::vector<V1_2::OutputShape>, V1_2::Timing, bool> getExecutionResult(
+ V1_0::ErrorStatus status, std::vector<V1_2::OutputShape> outputShapes, V1_2::Timing timing,
+ bool fallback) {
+ auto [n, checkedOutputShapes, checkedTiming] =
+ getExecutionResult(convertToV1_3(status), std::move(outputShapes), timing);
+ return {n, convertToV1_2(checkedOutputShapes), convertToV1_2(checkedTiming), fallback};
+}
+
+std::tuple<int, std::vector<V1_2::OutputShape>, V1_2::Timing, bool>
+ExecutionBurstController::compute(const V1_0::Request& request, V1_2::MeasureTiming measure,
+ const std::vector<intptr_t>& memoryIds) {
+ // This is the first point when we know an execution is occurring, so begin
+ // to collect systraces. Note that the first point we can begin collecting
+ // systraces in ExecutionBurstServer is when the RequestChannelReceiver
+ // realizes there is data in the FMQ, so ExecutionBurstServer collects
+ // systraces at different points in the code.
+ NNTRACE_FULL(NNTRACE_LAYER_IPC, NNTRACE_PHASE_EXECUTION, "ExecutionBurstController::compute");
+
+ std::lock_guard<std::mutex> guard(mMutex);
+
+ // send request packet
+ const std::vector<int32_t> slots = mMemoryCache->getSlots(request.pools, memoryIds);
+ const bool success = mRequestChannelSender->send(request, measure, slots);
+ if (!success) {
+ LOG(ERROR) << "Error sending FMQ packet";
+ // only use fallback execution path if the packet could not be sent
+ return getExecutionResult(V1_0::ErrorStatus::GENERAL_FAILURE, {}, kNoTiming12,
+ /*fallback=*/true);
+ }
+
+ // get result packet
+ const auto result = mResultChannelReceiver->getBlocking();
+ if (!result) {
+ LOG(ERROR) << "Error retrieving FMQ packet";
+ // only use fallback execution path if the packet could not be sent
+ return getExecutionResult(V1_0::ErrorStatus::GENERAL_FAILURE, {}, kNoTiming12,
+ /*fallback=*/false);
+ }
+
+ // unpack results and return (only use fallback execution path if the
+ // packet could not be sent)
+ auto [status, outputShapes, timing] = std::move(*result);
+ return getExecutionResult(status, std::move(outputShapes), timing, /*fallback=*/false);
+}
+
+void ExecutionBurstController::freeMemory(intptr_t key) {
+ std::lock_guard<std::mutex> guard(mMutex);
+
+ bool valid;
+ int32_t slot;
+ std::tie(valid, slot) = mMemoryCache->freeMemory(key);
+ if (valid) {
+ mBurstContext->freeMemory(slot).isOk();
+ }
+}
+
+} // namespace android::nn
diff --git a/neuralnetworks/1.2/utils/src/ExecutionBurstServer.cpp b/neuralnetworks/1.2/utils/src/ExecutionBurstServer.cpp
new file mode 100644
index 0000000..022548d
--- /dev/null
+++ b/neuralnetworks/1.2/utils/src/ExecutionBurstServer.cpp
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "ExecutionBurstServer"
+
+#include "ExecutionBurstServer.h"
+
+#include <android-base/logging.h>
+
+#include <algorithm>
+#include <cstring>
+#include <limits>
+#include <map>
+#include <memory>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "ExecutionBurstUtils.h"
+#include "HalInterfaces.h"
+#include "Tracing.h"
+
+namespace android::nn {
+namespace {
+
+// DefaultBurstExecutorWithCache adapts an IPreparedModel so that it can be
+// used as an IBurstExecutorWithCache. Specifically, the cache simply stores the
+// hidl_memory object, and the execution forwards calls to the provided
+// IPreparedModel's "executeSynchronously" method. With this class, hidl_memory
+// must be mapped and unmapped for each execution.
+class DefaultBurstExecutorWithCache : public ExecutionBurstServer::IBurstExecutorWithCache {
+ public:
+ DefaultBurstExecutorWithCache(V1_2::IPreparedModel* preparedModel)
+ : mpPreparedModel(preparedModel) {}
+
+ bool isCacheEntryPresent(int32_t slot) const override {
+ const auto it = mMemoryCache.find(slot);
+ return (it != mMemoryCache.end()) && it->second.valid();
+ }
+
+ void addCacheEntry(const hardware::hidl_memory& memory, int32_t slot) override {
+ mMemoryCache[slot] = memory;
+ }
+
+ void removeCacheEntry(int32_t slot) override { mMemoryCache.erase(slot); }
+
+ std::tuple<V1_0::ErrorStatus, hardware::hidl_vec<V1_2::OutputShape>, V1_2::Timing> execute(
+ const V1_0::Request& request, const std::vector<int32_t>& slots,
+ V1_2::MeasureTiming measure) override {
+ // convert slots to pools
+ hardware::hidl_vec<hardware::hidl_memory> pools(slots.size());
+ std::transform(slots.begin(), slots.end(), pools.begin(),
+ [this](int32_t slot) { return mMemoryCache[slot]; });
+
+ // create full request
+ V1_0::Request fullRequest = request;
+ fullRequest.pools = std::move(pools);
+
+ // setup execution
+ V1_0::ErrorStatus returnedStatus = V1_0::ErrorStatus::GENERAL_FAILURE;
+ hardware::hidl_vec<V1_2::OutputShape> returnedOutputShapes;
+ V1_2::Timing returnedTiming;
+ auto cb = [&returnedStatus, &returnedOutputShapes, &returnedTiming](
+ V1_0::ErrorStatus status,
+ const hardware::hidl_vec<V1_2::OutputShape>& outputShapes,
+ const V1_2::Timing& timing) {
+ returnedStatus = status;
+ returnedOutputShapes = outputShapes;
+ returnedTiming = timing;
+ };
+
+ // execute
+ const hardware::Return<void> ret =
+ mpPreparedModel->executeSynchronously(fullRequest, measure, cb);
+ if (!ret.isOk() || returnedStatus != V1_0::ErrorStatus::NONE) {
+ LOG(ERROR) << "IPreparedModelAdapter::execute -- Error executing";
+ return {returnedStatus, std::move(returnedOutputShapes), kNoTiming};
+ }
+
+ return std::make_tuple(returnedStatus, std::move(returnedOutputShapes), returnedTiming);
+ }
+
+ private:
+ V1_2::IPreparedModel* const mpPreparedModel;
+ std::map<int32_t, hardware::hidl_memory> mMemoryCache;
+};
+
+} // anonymous namespace
+
+// ExecutionBurstServer methods
+
+sp<ExecutionBurstServer> ExecutionBurstServer::create(
+ const sp<IBurstCallback>& callback, const MQDescriptorSync<FmqRequestDatum>& requestChannel,
+ const MQDescriptorSync<FmqResultDatum>& resultChannel,
+ std::shared_ptr<IBurstExecutorWithCache> executorWithCache,
+ std::chrono::microseconds pollingTimeWindow) {
+ // check inputs
+ if (callback == nullptr || executorWithCache == nullptr) {
+ LOG(ERROR) << "ExecutionBurstServer::create passed a nullptr";
+ return nullptr;
+ }
+
+ // create FMQ objects
+ std::unique_ptr<RequestChannelReceiver> requestChannelReceiver =
+ RequestChannelReceiver::create(requestChannel, pollingTimeWindow);
+ std::unique_ptr<ResultChannelSender> resultChannelSender =
+ ResultChannelSender::create(resultChannel);
+
+ // check FMQ objects
+ if (!requestChannelReceiver || !resultChannelSender) {
+ LOG(ERROR) << "ExecutionBurstServer::create failed to create FastMessageQueue";
+ return nullptr;
+ }
+
+ // make and return context
+ return new ExecutionBurstServer(callback, std::move(requestChannelReceiver),
+ std::move(resultChannelSender), std::move(executorWithCache));
+}
+
+sp<ExecutionBurstServer> ExecutionBurstServer::create(
+ const sp<IBurstCallback>& callback, const MQDescriptorSync<FmqRequestDatum>& requestChannel,
+ const MQDescriptorSync<FmqResultDatum>& resultChannel, V1_2::IPreparedModel* preparedModel,
+ std::chrono::microseconds pollingTimeWindow) {
+ // check relevant input
+ if (preparedModel == nullptr) {
+ LOG(ERROR) << "ExecutionBurstServer::create passed a nullptr";
+ return nullptr;
+ }
+
+ // adapt IPreparedModel to have caching
+ const std::shared_ptr<DefaultBurstExecutorWithCache> preparedModelAdapter =
+ std::make_shared<DefaultBurstExecutorWithCache>(preparedModel);
+
+ // make and return context
+ return ExecutionBurstServer::create(callback, requestChannel, resultChannel,
+ preparedModelAdapter, pollingTimeWindow);
+}
+
+ExecutionBurstServer::ExecutionBurstServer(
+ const sp<IBurstCallback>& callback, std::unique_ptr<RequestChannelReceiver> requestChannel,
+ std::unique_ptr<ResultChannelSender> resultChannel,
+ std::shared_ptr<IBurstExecutorWithCache> executorWithCache)
+ : mCallback(callback),
+ mRequestChannelReceiver(std::move(requestChannel)),
+ mResultChannelSender(std::move(resultChannel)),
+ mExecutorWithCache(std::move(executorWithCache)) {
+ // TODO: highly document the threading behavior of this class
+ mWorker = std::thread([this] { task(); });
+}
+
+ExecutionBurstServer::~ExecutionBurstServer() {
+ // set teardown flag
+ mTeardown = true;
+ mRequestChannelReceiver->invalidate();
+
+ // wait for task thread to end
+ mWorker.join();
+}
+
+hardware::Return<void> ExecutionBurstServer::freeMemory(int32_t slot) {
+ std::lock_guard<std::mutex> hold(mMutex);
+ mExecutorWithCache->removeCacheEntry(slot);
+ return hardware::Void();
+}
+
+void ExecutionBurstServer::ensureCacheEntriesArePresentLocked(const std::vector<int32_t>& slots) {
+ const auto slotIsKnown = [this](int32_t slot) {
+ return mExecutorWithCache->isCacheEntryPresent(slot);
+ };
+
+ // find unique unknown slots
+ std::vector<int32_t> unknownSlots = slots;
+ auto unknownSlotsEnd = unknownSlots.end();
+ std::sort(unknownSlots.begin(), unknownSlotsEnd);
+ unknownSlotsEnd = std::unique(unknownSlots.begin(), unknownSlotsEnd);
+ unknownSlotsEnd = std::remove_if(unknownSlots.begin(), unknownSlotsEnd, slotIsKnown);
+ unknownSlots.erase(unknownSlotsEnd, unknownSlots.end());
+
+ // quick-exit if all slots are known
+ if (unknownSlots.empty()) {
+ return;
+ }
+
+ V1_0::ErrorStatus errorStatus = V1_0::ErrorStatus::GENERAL_FAILURE;
+ std::vector<hardware::hidl_memory> returnedMemories;
+ auto cb = [&errorStatus, &returnedMemories](
+ V1_0::ErrorStatus status,
+ const hardware::hidl_vec<hardware::hidl_memory>& memories) {
+ errorStatus = status;
+ returnedMemories = memories;
+ };
+
+ const hardware::Return<void> ret = mCallback->getMemories(unknownSlots, cb);
+
+ if (!ret.isOk() || errorStatus != V1_0::ErrorStatus::NONE ||
+ returnedMemories.size() != unknownSlots.size()) {
+ LOG(ERROR) << "Error retrieving memories";
+ return;
+ }
+
+ // add memories to unknown slots
+ for (size_t i = 0; i < unknownSlots.size(); ++i) {
+ mExecutorWithCache->addCacheEntry(returnedMemories[i], unknownSlots[i]);
+ }
+}
+
+void ExecutionBurstServer::task() {
+ // loop until the burst object is being destroyed
+ while (!mTeardown) {
+ // receive request
+ auto arguments = mRequestChannelReceiver->getBlocking();
+
+ // if the request packet was not properly received, return a generic
+ // error and skip the execution
+ //
+ // if the burst is being torn down, skip the execution so the "task"
+ // function can end
+ if (!arguments) {
+ if (!mTeardown) {
+ mResultChannelSender->send(V1_0::ErrorStatus::GENERAL_FAILURE, {}, kNoTiming);
+ }
+ continue;
+ }
+
+ // otherwise begin tracing execution
+ NNTRACE_FULL(NNTRACE_LAYER_IPC, NNTRACE_PHASE_EXECUTION,
+ "ExecutionBurstServer getting memory, executing, and returning results");
+
+ // unpack the arguments; types are Request, std::vector<int32_t>, and
+ // MeasureTiming, respectively
+ const auto [requestWithoutPools, slotsOfPools, measure] = std::move(*arguments);
+
+ // ensure executor with cache has required memory
+ std::lock_guard<std::mutex> hold(mMutex);
+ ensureCacheEntriesArePresentLocked(slotsOfPools);
+
+ // perform computation; types are ErrorStatus, hidl_vec<OutputShape>,
+ // and Timing, respectively
+ const auto [errorStatus, outputShapes, returnedTiming] =
+ mExecutorWithCache->execute(requestWithoutPools, slotsOfPools, measure);
+
+ // return result
+ mResultChannelSender->send(errorStatus, outputShapes, returnedTiming);
+ }
+}
+
+} // namespace android::nn
diff --git a/neuralnetworks/1.2/utils/src/ExecutionBurstUtils.cpp b/neuralnetworks/1.2/utils/src/ExecutionBurstUtils.cpp
new file mode 100644
index 0000000..f0275f9
--- /dev/null
+++ b/neuralnetworks/1.2/utils/src/ExecutionBurstUtils.cpp
@@ -0,0 +1,749 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "ExecutionBurstUtils"
+
+#include "ExecutionBurstUtils.h"
+
+#include <android-base/logging.h>
+#include <android/hardware/neuralnetworks/1.0/types.h>
+#include <android/hardware/neuralnetworks/1.1/types.h>
+#include <android/hardware/neuralnetworks/1.2/types.h>
+#include <fmq/MessageQueue.h>
+#include <hidl/MQDescriptor.h>
+
+#include <atomic>
+#include <chrono>
+#include <memory>
+#include <thread>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+namespace android::hardware::neuralnetworks::V1_2::utils {
+namespace {
+
+constexpr V1_2::Timing kNoTiming = {std::numeric_limits<uint64_t>::max(),
+ std::numeric_limits<uint64_t>::max()};
+
+}
+
+// serialize a request into a packet
+std::vector<FmqRequestDatum> serialize(const V1_0::Request& request, V1_2::MeasureTiming measure,
+ const std::vector<int32_t>& slots) {
+ // count how many elements need to be sent for a request
+ size_t count = 2 + request.inputs.size() + request.outputs.size() + request.pools.size();
+ for (const auto& input : request.inputs) {
+ count += input.dimensions.size();
+ }
+ for (const auto& output : request.outputs) {
+ count += output.dimensions.size();
+ }
+
+ // create buffer to temporarily store elements
+ std::vector<FmqRequestDatum> data;
+ data.reserve(count);
+
+ // package packetInfo
+ {
+ FmqRequestDatum datum;
+ datum.packetInformation(
+ {/*.packetSize=*/static_cast<uint32_t>(count),
+ /*.numberOfInputOperands=*/static_cast<uint32_t>(request.inputs.size()),
+ /*.numberOfOutputOperands=*/static_cast<uint32_t>(request.outputs.size()),
+ /*.numberOfPools=*/static_cast<uint32_t>(request.pools.size())});
+ data.push_back(datum);
+ }
+
+ // package input data
+ for (const auto& input : request.inputs) {
+ // package operand information
+ FmqRequestDatum datum;
+ datum.inputOperandInformation(
+ {/*.hasNoValue=*/input.hasNoValue,
+ /*.location=*/input.location,
+ /*.numberOfDimensions=*/static_cast<uint32_t>(input.dimensions.size())});
+ data.push_back(datum);
+
+ // package operand dimensions
+ for (uint32_t dimension : input.dimensions) {
+ FmqRequestDatum datum;
+ datum.inputOperandDimensionValue(dimension);
+ data.push_back(datum);
+ }
+ }
+
+ // package output data
+ for (const auto& output : request.outputs) {
+ // package operand information
+ FmqRequestDatum datum;
+ datum.outputOperandInformation(
+ {/*.hasNoValue=*/output.hasNoValue,
+ /*.location=*/output.location,
+ /*.numberOfDimensions=*/static_cast<uint32_t>(output.dimensions.size())});
+ data.push_back(datum);
+
+ // package operand dimensions
+ for (uint32_t dimension : output.dimensions) {
+ FmqRequestDatum datum;
+ datum.outputOperandDimensionValue(dimension);
+ data.push_back(datum);
+ }
+ }
+
+ // package pool identifier
+ for (int32_t slot : slots) {
+ FmqRequestDatum datum;
+ datum.poolIdentifier(slot);
+ data.push_back(datum);
+ }
+
+ // package measureTiming
+ {
+ FmqRequestDatum datum;
+ datum.measureTiming(measure);
+ data.push_back(datum);
+ }
+
+ // return packet
+ return data;
+}
+
+// serialize result
+std::vector<FmqResultDatum> serialize(V1_0::ErrorStatus errorStatus,
+ const std::vector<V1_2::OutputShape>& outputShapes,
+ V1_2::Timing timing) {
+ // count how many elements need to be sent for a request
+ size_t count = 2 + outputShapes.size();
+ for (const auto& outputShape : outputShapes) {
+ count += outputShape.dimensions.size();
+ }
+
+ // create buffer to temporarily store elements
+ std::vector<FmqResultDatum> data;
+ data.reserve(count);
+
+ // package packetInfo
+ {
+ FmqResultDatum datum;
+ datum.packetInformation({/*.packetSize=*/static_cast<uint32_t>(count),
+ /*.errorStatus=*/errorStatus,
+ /*.numberOfOperands=*/static_cast<uint32_t>(outputShapes.size())});
+ data.push_back(datum);
+ }
+
+ // package output shape data
+ for (const auto& operand : outputShapes) {
+ // package operand information
+ FmqResultDatum::OperandInformation info{};
+ info.isSufficient = operand.isSufficient;
+ info.numberOfDimensions = static_cast<uint32_t>(operand.dimensions.size());
+
+ FmqResultDatum datum;
+ datum.operandInformation(info);
+ data.push_back(datum);
+
+ // package operand dimensions
+ for (uint32_t dimension : operand.dimensions) {
+ FmqResultDatum datum;
+ datum.operandDimensionValue(dimension);
+ data.push_back(datum);
+ }
+ }
+
+ // package executionTiming
+ {
+ FmqResultDatum datum;
+ datum.executionTiming(timing);
+ data.push_back(datum);
+ }
+
+ // return result
+ return data;
+}
+
+// deserialize request
+std::optional<std::tuple<V1_0::Request, std::vector<int32_t>, V1_2::MeasureTiming>> deserialize(
+ const std::vector<FmqRequestDatum>& data) {
+ using discriminator = FmqRequestDatum::hidl_discriminator;
+
+ size_t index = 0;
+
+ // validate packet information
+ if (data.size() == 0 || data[index].getDiscriminator() != discriminator::packetInformation) {
+ LOG(ERROR) << "FMQ Request packet ill-formed";
+ return std::nullopt;
+ }
+
+ // unpackage packet information
+ const FmqRequestDatum::PacketInformation& packetInfo = data[index].packetInformation();
+ index++;
+ const uint32_t packetSize = packetInfo.packetSize;
+ const uint32_t numberOfInputOperands = packetInfo.numberOfInputOperands;
+ const uint32_t numberOfOutputOperands = packetInfo.numberOfOutputOperands;
+ const uint32_t numberOfPools = packetInfo.numberOfPools;
+
+ // verify packet size
+ if (data.size() != packetSize) {
+ LOG(ERROR) << "FMQ Request packet ill-formed";
+ return std::nullopt;
+ }
+
+ // unpackage input operands
+ std::vector<V1_0::RequestArgument> inputs;
+ inputs.reserve(numberOfInputOperands);
+ for (size_t operand = 0; operand < numberOfInputOperands; ++operand) {
+ // validate input operand information
+ if (data[index].getDiscriminator() != discriminator::inputOperandInformation) {
+ LOG(ERROR) << "FMQ Request packet ill-formed";
+ return std::nullopt;
+ }
+
+ // unpackage operand information
+ const FmqRequestDatum::OperandInformation& operandInfo =
+ data[index].inputOperandInformation();
+ index++;
+ const bool hasNoValue = operandInfo.hasNoValue;
+ const V1_0::DataLocation location = operandInfo.location;
+ const uint32_t numberOfDimensions = operandInfo.numberOfDimensions;
+
+ // unpackage operand dimensions
+ std::vector<uint32_t> dimensions;
+ dimensions.reserve(numberOfDimensions);
+ for (size_t i = 0; i < numberOfDimensions; ++i) {
+ // validate dimension
+ if (data[index].getDiscriminator() != discriminator::inputOperandDimensionValue) {
+ LOG(ERROR) << "FMQ Request packet ill-formed";
+ return std::nullopt;
+ }
+
+ // unpackage dimension
+ const uint32_t dimension = data[index].inputOperandDimensionValue();
+ index++;
+
+ // store result
+ dimensions.push_back(dimension);
+ }
+
+ // store result
+ inputs.push_back(
+ {/*.hasNoValue=*/hasNoValue, /*.location=*/location, /*.dimensions=*/dimensions});
+ }
+
+ // unpackage output operands
+ std::vector<V1_0::RequestArgument> outputs;
+ outputs.reserve(numberOfOutputOperands);
+ for (size_t operand = 0; operand < numberOfOutputOperands; ++operand) {
+ // validate output operand information
+ if (data[index].getDiscriminator() != discriminator::outputOperandInformation) {
+ LOG(ERROR) << "FMQ Request packet ill-formed";
+ return std::nullopt;
+ }
+
+ // unpackage operand information
+ const FmqRequestDatum::OperandInformation& operandInfo =
+ data[index].outputOperandInformation();
+ index++;
+ const bool hasNoValue = operandInfo.hasNoValue;
+ const V1_0::DataLocation location = operandInfo.location;
+ const uint32_t numberOfDimensions = operandInfo.numberOfDimensions;
+
+ // unpackage operand dimensions
+ std::vector<uint32_t> dimensions;
+ dimensions.reserve(numberOfDimensions);
+ for (size_t i = 0; i < numberOfDimensions; ++i) {
+ // validate dimension
+ if (data[index].getDiscriminator() != discriminator::outputOperandDimensionValue) {
+ LOG(ERROR) << "FMQ Request packet ill-formed";
+ return std::nullopt;
+ }
+
+ // unpackage dimension
+ const uint32_t dimension = data[index].outputOperandDimensionValue();
+ index++;
+
+ // store result
+ dimensions.push_back(dimension);
+ }
+
+ // store result
+ outputs.push_back(
+ {/*.hasNoValue=*/hasNoValue, /*.location=*/location, /*.dimensions=*/dimensions});
+ }
+
+ // unpackage pools
+ std::vector<int32_t> slots;
+ slots.reserve(numberOfPools);
+ for (size_t pool = 0; pool < numberOfPools; ++pool) {
+ // validate input operand information
+ if (data[index].getDiscriminator() != discriminator::poolIdentifier) {
+ LOG(ERROR) << "FMQ Request packet ill-formed";
+ return std::nullopt;
+ }
+
+ // unpackage operand information
+ const int32_t poolId = data[index].poolIdentifier();
+ index++;
+
+ // store result
+ slots.push_back(poolId);
+ }
+
+ // validate measureTiming
+ if (data[index].getDiscriminator() != discriminator::measureTiming) {
+ LOG(ERROR) << "FMQ Request packet ill-formed";
+ return std::nullopt;
+ }
+
+ // unpackage measureTiming
+ const V1_2::MeasureTiming measure = data[index].measureTiming();
+ index++;
+
+ // validate packet information
+ if (index != packetSize) {
+ LOG(ERROR) << "FMQ Result packet ill-formed";
+ return std::nullopt;
+ }
+
+ // return request
+ V1_0::Request request = {/*.inputs=*/inputs, /*.outputs=*/outputs, /*.pools=*/{}};
+ return std::make_tuple(std::move(request), std::move(slots), measure);
+}
+
+// deserialize a packet into the result
+std::optional<std::tuple<V1_0::ErrorStatus, std::vector<V1_2::OutputShape>, V1_2::Timing>>
+deserialize(const std::vector<FmqResultDatum>& data) {
+ using discriminator = FmqResultDatum::hidl_discriminator;
+
+ std::vector<V1_2::OutputShape> outputShapes;
+ size_t index = 0;
+
+ // validate packet information
+ if (data.size() == 0 || data[index].getDiscriminator() != discriminator::packetInformation) {
+ LOG(ERROR) << "FMQ Result packet ill-formed";
+ return std::nullopt;
+ }
+
+ // unpackage packet information
+ const FmqResultDatum::PacketInformation& packetInfo = data[index].packetInformation();
+ index++;
+ const uint32_t packetSize = packetInfo.packetSize;
+ const V1_0::ErrorStatus errorStatus = packetInfo.errorStatus;
+ const uint32_t numberOfOperands = packetInfo.numberOfOperands;
+
+ // verify packet size
+ if (data.size() != packetSize) {
+ LOG(ERROR) << "FMQ Result packet ill-formed";
+ return std::nullopt;
+ }
+
+ // unpackage operands
+ for (size_t operand = 0; operand < numberOfOperands; ++operand) {
+ // validate operand information
+ if (data[index].getDiscriminator() != discriminator::operandInformation) {
+ LOG(ERROR) << "FMQ Result packet ill-formed";
+ return std::nullopt;
+ }
+
+ // unpackage operand information
+ const FmqResultDatum::OperandInformation& operandInfo = data[index].operandInformation();
+ index++;
+ const bool isSufficient = operandInfo.isSufficient;
+ const uint32_t numberOfDimensions = operandInfo.numberOfDimensions;
+
+ // unpackage operand dimensions
+ std::vector<uint32_t> dimensions;
+ dimensions.reserve(numberOfDimensions);
+ for (size_t i = 0; i < numberOfDimensions; ++i) {
+ // validate dimension
+ if (data[index].getDiscriminator() != discriminator::operandDimensionValue) {
+ LOG(ERROR) << "FMQ Result packet ill-formed";
+ return std::nullopt;
+ }
+
+ // unpackage dimension
+ const uint32_t dimension = data[index].operandDimensionValue();
+ index++;
+
+ // store result
+ dimensions.push_back(dimension);
+ }
+
+ // store result
+ outputShapes.push_back({/*.dimensions=*/dimensions, /*.isSufficient=*/isSufficient});
+ }
+
+ // validate execution timing
+ if (data[index].getDiscriminator() != discriminator::executionTiming) {
+ LOG(ERROR) << "FMQ Result packet ill-formed";
+ return std::nullopt;
+ }
+
+ // unpackage execution timing
+ const V1_2::Timing timing = data[index].executionTiming();
+ index++;
+
+ // validate packet information
+ if (index != packetSize) {
+ LOG(ERROR) << "FMQ Result packet ill-formed";
+ return std::nullopt;
+ }
+
+ // return result
+ return std::make_tuple(errorStatus, std::move(outputShapes), timing);
+}
+
+V1_0::ErrorStatus legacyConvertResultCodeToErrorStatus(int resultCode) {
+ return convertToV1_0(convertResultCodeToErrorStatus(resultCode));
+}
+
+// RequestChannelSender methods
+
+std::pair<std::unique_ptr<RequestChannelSender>, const FmqRequestDescriptor*>
+RequestChannelSender::create(size_t channelLength) {
+ std::unique_ptr<FmqRequestChannel> fmqRequestChannel =
+ std::make_unique<FmqRequestChannel>(channelLength, /*confEventFlag=*/true);
+ if (!fmqRequestChannel->isValid()) {
+ LOG(ERROR) << "Unable to create RequestChannelSender";
+ return {nullptr, nullptr};
+ }
+
+ const FmqRequestDescriptor* descriptor = fmqRequestChannel->getDesc();
+ return std::make_pair(std::make_unique<RequestChannelSender>(std::move(fmqRequestChannel)),
+ descriptor);
+}
+
+RequestChannelSender::RequestChannelSender(std::unique_ptr<FmqRequestChannel> fmqRequestChannel)
+ : mFmqRequestChannel(std::move(fmqRequestChannel)) {}
+
+bool RequestChannelSender::send(const V1_0::Request& request, V1_2::MeasureTiming measure,
+ const std::vector<int32_t>& slots) {
+ const std::vector<FmqRequestDatum> serialized = serialize(request, measure, slots);
+ return sendPacket(serialized);
+}
+
+bool RequestChannelSender::sendPacket(const std::vector<FmqRequestDatum>& packet) {
+ if (!mValid) {
+ return false;
+ }
+
+ if (packet.size() > mFmqRequestChannel->availableToWrite()) {
+ LOG(ERROR)
+ << "RequestChannelSender::sendPacket -- packet size exceeds size available in FMQ";
+ return false;
+ }
+
+ // Always send the packet with "blocking" because this signals the futex and
+ // unblocks the consumer if it is waiting on the futex.
+ return mFmqRequestChannel->writeBlocking(packet.data(), packet.size());
+}
+
+void RequestChannelSender::invalidate() {
+ mValid = false;
+}
+
+// RequestChannelReceiver methods
+
+std::unique_ptr<RequestChannelReceiver> RequestChannelReceiver::create(
+ const FmqRequestDescriptor& requestChannel, std::chrono::microseconds pollingTimeWindow) {
+ std::unique_ptr<FmqRequestChannel> fmqRequestChannel =
+ std::make_unique<FmqRequestChannel>(requestChannel);
+
+ if (!fmqRequestChannel->isValid()) {
+ LOG(ERROR) << "Unable to create RequestChannelReceiver";
+ return nullptr;
+ }
+ if (fmqRequestChannel->getEventFlagWord() == nullptr) {
+ LOG(ERROR)
+ << "RequestChannelReceiver::create was passed an MQDescriptor without an EventFlag";
+ return nullptr;
+ }
+
+ return std::make_unique<RequestChannelReceiver>(std::move(fmqRequestChannel),
+ pollingTimeWindow);
+}
+
+RequestChannelReceiver::RequestChannelReceiver(std::unique_ptr<FmqRequestChannel> fmqRequestChannel,
+ std::chrono::microseconds pollingTimeWindow)
+ : mFmqRequestChannel(std::move(fmqRequestChannel)), kPollingTimeWindow(pollingTimeWindow) {}
+
+std::optional<std::tuple<V1_0::Request, std::vector<int32_t>, V1_2::MeasureTiming>>
+RequestChannelReceiver::getBlocking() {
+ const auto packet = getPacketBlocking();
+ if (!packet) {
+ return std::nullopt;
+ }
+
+ return deserialize(*packet);
+}
+
+void RequestChannelReceiver::invalidate() {
+ mTeardown = true;
+
+ // force unblock
+ // ExecutionBurstServer is by default waiting on a request packet. If the
+ // client process destroys its burst object, the server may still be waiting
+ // on the futex. This force unblock wakes up any thread waiting on the
+ // futex.
+ // TODO: look for a different/better way to signal/notify the futex to wake
+ // up any thread waiting on it
+ FmqRequestDatum datum;
+ datum.packetInformation({/*.packetSize=*/0, /*.numberOfInputOperands=*/0,
+ /*.numberOfOutputOperands=*/0, /*.numberOfPools=*/0});
+ mFmqRequestChannel->writeBlocking(&datum, 1);
+}
+
+std::optional<std::vector<FmqRequestDatum>> RequestChannelReceiver::getPacketBlocking() {
+ if (mTeardown) {
+ return std::nullopt;
+ }
+
+ // First spend time polling if results are available in FMQ instead of
+ // waiting on the futex. Polling is more responsive (yielding lower
+ // latencies), but can take up more power, so only poll for a limited period
+ // of time.
+
+ auto& getCurrentTime = std::chrono::high_resolution_clock::now;
+ const auto timeToStopPolling = getCurrentTime() + kPollingTimeWindow;
+
+ while (getCurrentTime() < timeToStopPolling) {
+ // if class is being torn down, immediately return
+ if (mTeardown.load(std::memory_order_relaxed)) {
+ return std::nullopt;
+ }
+
+ // Check if data is available. If it is, immediately retrieve it and
+ // return.
+ const size_t available = mFmqRequestChannel->availableToRead();
+ if (available > 0) {
+ // This is the first point when we know an execution is occurring,
+ // so begin to collect systraces. Note that a similar systrace does
+ // not exist at the corresponding point in
+ // ResultChannelReceiver::getPacketBlocking because the execution is
+ // already in flight.
+ NNTRACE_FULL(NNTRACE_LAYER_IPC, NNTRACE_PHASE_EXECUTION,
+ "ExecutionBurstServer getting packet");
+ std::vector<FmqRequestDatum> packet(available);
+ const bool success = mFmqRequestChannel->read(packet.data(), available);
+ if (!success) {
+ LOG(ERROR) << "Error receiving packet";
+ return std::nullopt;
+ }
+ return std::make_optional(std::move(packet));
+ }
+ }
+
+ // If we get to this point, we either stopped polling because it was taking
+ // too long or polling was not allowed. Instead, perform a blocking call
+ // which uses a futex to save power.
+
+ // wait for request packet and read first element of request packet
+ FmqRequestDatum datum;
+ bool success = mFmqRequestChannel->readBlocking(&datum, 1);
+
+ // This is the first point when we know an execution is occurring, so begin
+ // to collect systraces. Note that a similar systrace does not exist at the
+ // corresponding point in ResultChannelReceiver::getPacketBlocking because
+ // the execution is already in flight.
+ NNTRACE_FULL(NNTRACE_LAYER_IPC, NNTRACE_PHASE_EXECUTION, "ExecutionBurstServer getting packet");
+
+ // retrieve remaining elements
+ // NOTE: all of the data is already available at this point, so there's no
+ // need to do a blocking wait to wait for more data. This is known because
+ // in FMQ, all writes are published (made available) atomically. Currently,
+ // the producer always publishes the entire packet in one function call, so
+ // if the first element of the packet is available, the remaining elements
+ // are also available.
+ const size_t count = mFmqRequestChannel->availableToRead();
+ std::vector<FmqRequestDatum> packet(count + 1);
+ std::memcpy(&packet.front(), &datum, sizeof(datum));
+ success &= mFmqRequestChannel->read(packet.data() + 1, count);
+
+ // terminate loop
+ if (mTeardown) {
+ return std::nullopt;
+ }
+
+ // ensure packet was successfully received
+ if (!success) {
+ LOG(ERROR) << "Error receiving packet";
+ return std::nullopt;
+ }
+
+ return std::make_optional(std::move(packet));
+}
+
+// ResultChannelSender methods
+
+std::unique_ptr<ResultChannelSender> ResultChannelSender::create(
+ const FmqResultDescriptor& resultChannel) {
+ std::unique_ptr<FmqResultChannel> fmqResultChannel =
+ std::make_unique<FmqResultChannel>(resultChannel);
+
+ if (!fmqResultChannel->isValid()) {
+ LOG(ERROR) << "Unable to create RequestChannelSender";
+ return nullptr;
+ }
+ if (fmqResultChannel->getEventFlagWord() == nullptr) {
+ LOG(ERROR) << "ResultChannelSender::create was passed an MQDescriptor without an EventFlag";
+ return nullptr;
+ }
+
+ return std::make_unique<ResultChannelSender>(std::move(fmqResultChannel));
+}
+
+ResultChannelSender::ResultChannelSender(std::unique_ptr<FmqResultChannel> fmqResultChannel)
+ : mFmqResultChannel(std::move(fmqResultChannel)) {}
+
+bool ResultChannelSender::send(V1_0::ErrorStatus errorStatus,
+ const std::vector<V1_2::OutputShape>& outputShapes,
+ V1_2::Timing timing) {
+ const std::vector<FmqResultDatum> serialized = serialize(errorStatus, outputShapes, timing);
+ return sendPacket(serialized);
+}
+
+bool ResultChannelSender::sendPacket(const std::vector<FmqResultDatum>& packet) {
+ if (packet.size() > mFmqResultChannel->availableToWrite()) {
+ LOG(ERROR)
+ << "ResultChannelSender::sendPacket -- packet size exceeds size available in FMQ";
+ const std::vector<FmqResultDatum> errorPacket =
+ serialize(V1_0::ErrorStatus::GENERAL_FAILURE, {}, kNoTiming);
+
+ // Always send the packet with "blocking" because this signals the futex
+ // and unblocks the consumer if it is waiting on the futex.
+ return mFmqResultChannel->writeBlocking(errorPacket.data(), errorPacket.size());
+ }
+
+ // Always send the packet with "blocking" because this signals the futex and
+ // unblocks the consumer if it is waiting on the futex.
+ return mFmqResultChannel->writeBlocking(packet.data(), packet.size());
+}
+
+// ResultChannelReceiver methods
+
+std::pair<std::unique_ptr<ResultChannelReceiver>, const FmqResultDescriptor*>
+ResultChannelReceiver::create(size_t channelLength, std::chrono::microseconds pollingTimeWindow) {
+ std::unique_ptr<FmqResultChannel> fmqResultChannel =
+ std::make_unique<FmqResultChannel>(channelLength, /*confEventFlag=*/true);
+ if (!fmqResultChannel->isValid()) {
+ LOG(ERROR) << "Unable to create ResultChannelReceiver";
+ return {nullptr, nullptr};
+ }
+
+ const FmqResultDescriptor* descriptor = fmqResultChannel->getDesc();
+ return std::make_pair(
+ std::make_unique<ResultChannelReceiver>(std::move(fmqResultChannel), pollingTimeWindow),
+ descriptor);
+}
+
+ResultChannelReceiver::ResultChannelReceiver(std::unique_ptr<FmqResultChannel> fmqResultChannel,
+ std::chrono::microseconds pollingTimeWindow)
+ : mFmqResultChannel(std::move(fmqResultChannel)), kPollingTimeWindow(pollingTimeWindow) {}
+
+std::optional<std::tuple<V1_0::ErrorStatus, std::vector<V1_2::OutputShape>, V1_2::Timing>>
+ResultChannelReceiver::getBlocking() {
+ const auto packet = getPacketBlocking();
+ if (!packet) {
+ return std::nullopt;
+ }
+
+ return deserialize(*packet);
+}
+
+void ResultChannelReceiver::invalidate() {
+ mValid = false;
+
+ // force unblock
+ // ExecutionBurstController waits on a result packet after sending a
+ // request. If the driver containing ExecutionBurstServer crashes, the
+ // controller may be waiting on the futex. This force unblock wakes up any
+ // thread waiting on the futex.
+ // TODO: look for a different/better way to signal/notify the futex to
+ // wake up any thread waiting on it
+ FmqResultDatum datum;
+ datum.packetInformation({/*.packetSize=*/0,
+ /*.errorStatus=*/V1_0::ErrorStatus::GENERAL_FAILURE,
+ /*.numberOfOperands=*/0});
+ mFmqResultChannel->writeBlocking(&datum, 1);
+}
+
+std::optional<std::vector<FmqResultDatum>> ResultChannelReceiver::getPacketBlocking() {
+ if (!mValid) {
+ return std::nullopt;
+ }
+
+ // First spend time polling if results are available in FMQ instead of
+ // waiting on the futex. Polling is more responsive (yielding lower
+ // latencies), but can take up more power, so only poll for a limited period
+ // of time.
+
+ auto& getCurrentTime = std::chrono::high_resolution_clock::now;
+ const auto timeToStopPolling = getCurrentTime() + kPollingTimeWindow;
+
+ while (getCurrentTime() < timeToStopPolling) {
+ // if class is being torn down, immediately return
+ if (!mValid.load(std::memory_order_relaxed)) {
+ return std::nullopt;
+ }
+
+ // Check if data is available. If it is, immediately retrieve it and
+ // return.
+ const size_t available = mFmqResultChannel->availableToRead();
+ if (available > 0) {
+ std::vector<FmqResultDatum> packet(available);
+ const bool success = mFmqResultChannel->read(packet.data(), available);
+ if (!success) {
+ LOG(ERROR) << "Error receiving packet";
+ return std::nullopt;
+ }
+ return std::make_optional(std::move(packet));
+ }
+ }
+
+ // If we get to this point, we either stopped polling because it was taking
+ // too long or polling was not allowed. Instead, perform a blocking call
+ // which uses a futex to save power.
+
+ // wait for result packet and read first element of result packet
+ FmqResultDatum datum;
+ bool success = mFmqResultChannel->readBlocking(&datum, 1);
+
+ // retrieve remaining elements
+ // NOTE: all of the data is already available at this point, so there's no
+ // need to do a blocking wait to wait for more data. This is known because
+ // in FMQ, all writes are published (made available) atomically. Currently,
+ // the producer always publishes the entire packet in one function call, so
+ // if the first element of the packet is available, the remaining elements
+ // are also available.
+ const size_t count = mFmqResultChannel->availableToRead();
+ std::vector<FmqResultDatum> packet(count + 1);
+ std::memcpy(&packet.front(), &datum, sizeof(datum));
+ success &= mFmqResultChannel->read(packet.data() + 1, count);
+
+ if (!mValid) {
+ return std::nullopt;
+ }
+
+ // ensure packet was successfully received
+ if (!success) {
+ LOG(ERROR) << "Error receiving packet";
+ return std::nullopt;
+ }
+
+ return std::make_optional(std::move(packet));
+}
+
+} // namespace android::hardware::neuralnetworks::V1_2::utils
diff --git a/neuralnetworks/1.2/utils/src/PreparedModel.cpp b/neuralnetworks/1.2/utils/src/PreparedModel.cpp
index 6d00082..6841c5e 100644
--- a/neuralnetworks/1.2/utils/src/PreparedModel.cpp
+++ b/neuralnetworks/1.2/utils/src/PreparedModel.cpp
@@ -27,6 +27,7 @@
#include <nnapi/IPreparedModel.h>
#include <nnapi/Result.h>
#include <nnapi/Types.h>
+#include <nnapi/hal/1.0/Burst.h>
#include <nnapi/hal/1.0/Conversions.h>
#include <nnapi/hal/CommonUtils.h>
#include <nnapi/hal/HandleError.h>
@@ -117,6 +118,10 @@
<< "IPreparedModel::executeFenced is not supported on 1.2 HAL service";
}
+nn::GeneralResult<nn::SharedBurst> PreparedModel::configureExecutionBurst() const {
+ return V1_0::utils::Burst::create(shared_from_this());
+}
+
std::any PreparedModel::getUnderlyingResource() const {
sp<V1_2::IPreparedModel> resource = kPreparedModel;
return resource;
diff --git a/neuralnetworks/1.2/utils/test/DeviceTest.cpp b/neuralnetworks/1.2/utils/test/DeviceTest.cpp
new file mode 100644
index 0000000..9c8adde
--- /dev/null
+++ b/neuralnetworks/1.2/utils/test/DeviceTest.cpp
@@ -0,0 +1,875 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "MockDevice.h"
+#include "MockPreparedModel.h"
+
+#include <android/hardware/neuralnetworks/1.2/IDevice.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <nnapi/IDevice.h>
+#include <nnapi/TypeUtils.h>
+#include <nnapi/Types.h>
+#include <nnapi/hal/1.2/Device.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+
+namespace android::hardware::neuralnetworks::V1_2::utils {
+namespace {
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::InvokeWithoutArgs;
+
+const nn::Model kSimpleModel = {
+ .main = {.operands = {{.type = nn::OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .lifetime = nn::Operand::LifeTime::SUBGRAPH_INPUT},
+ {.type = nn::OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .lifetime = nn::Operand::LifeTime::SUBGRAPH_OUTPUT}},
+ .operations = {{.type = nn::OperationType::RELU, .inputs = {0}, .outputs = {1}}},
+ .inputIndexes = {0},
+ .outputIndexes = {1}}};
+
+const std::string kName = "Google-MockV1";
+const std::string kInvalidName = "";
+const sp<V1_2::IDevice> kInvalidDevice;
+constexpr V1_0::PerformanceInfo kNoPerformanceInfo = {
+ .execTime = std::numeric_limits<float>::max(),
+ .powerUsage = std::numeric_limits<float>::max()};
+
+template <typename... Args>
+auto makeCallbackReturn(Args&&... args) {
+ return [argPack = std::make_tuple(std::forward<Args>(args)...)](const auto& cb) {
+ std::apply(cb, argPack);
+ return Void();
+ };
+}
+
+sp<MockDevice> createMockDevice() {
+ const auto mockDevice = MockDevice::create();
+
+ // Setup default actions for each relevant call.
+ const auto getVersionString_ret = makeCallbackReturn(V1_0::ErrorStatus::NONE, kName);
+ const auto getType_ret = makeCallbackReturn(V1_0::ErrorStatus::NONE, V1_2::DeviceType::OTHER);
+ const auto getSupportedExtensions_ret =
+ makeCallbackReturn(V1_0::ErrorStatus::NONE, hidl_vec<V1_2::Extension>{});
+ const auto getNumberOfCacheFilesNeeded_ret = makeCallbackReturn(
+ V1_0::ErrorStatus::NONE, nn::kMaxNumberOfCacheFiles, nn::kMaxNumberOfCacheFiles);
+ const auto getCapabilities_ret = makeCallbackReturn(
+ V1_0::ErrorStatus::NONE,
+ V1_2::Capabilities{
+ .relaxedFloat32toFloat16PerformanceScalar = kNoPerformanceInfo,
+ .relaxedFloat32toFloat16PerformanceTensor = kNoPerformanceInfo,
+ });
+
+ // Setup default actions for each relevant call.
+ ON_CALL(*mockDevice, getVersionString(_)).WillByDefault(Invoke(getVersionString_ret));
+ ON_CALL(*mockDevice, getType(_)).WillByDefault(Invoke(getType_ret));
+ ON_CALL(*mockDevice, getSupportedExtensions(_))
+ .WillByDefault(Invoke(getSupportedExtensions_ret));
+ ON_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_))
+ .WillByDefault(Invoke(getNumberOfCacheFilesNeeded_ret));
+ ON_CALL(*mockDevice, getCapabilities_1_2(_)).WillByDefault(Invoke(getCapabilities_ret));
+
+ // Ensure that older calls are not used.
+ EXPECT_CALL(*mockDevice, getCapabilities(_)).Times(0);
+ EXPECT_CALL(*mockDevice, getCapabilities_1_1(_)).Times(0);
+ EXPECT_CALL(*mockDevice, getSupportedOperations(_, _)).Times(0);
+ EXPECT_CALL(*mockDevice, getSupportedOperations_1_1(_, _)).Times(0);
+ EXPECT_CALL(*mockDevice, prepareModel(_, _)).Times(0);
+ EXPECT_CALL(*mockDevice, prepareModel_1_1(_, _, _)).Times(0);
+
+ // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the
+ // uninteresting methods calls.
+ EXPECT_CALL(*mockDevice, getVersionString(_)).Times(testing::AnyNumber());
+ EXPECT_CALL(*mockDevice, getType(_)).Times(testing::AnyNumber());
+ EXPECT_CALL(*mockDevice, getSupportedExtensions(_)).Times(testing::AnyNumber());
+ EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(testing::AnyNumber());
+ EXPECT_CALL(*mockDevice, getCapabilities_1_2(_)).Times(testing::AnyNumber());
+
+ return mockDevice;
+}
+
+auto makePreparedModelReturn(V1_0::ErrorStatus launchStatus, V1_0::ErrorStatus returnStatus,
+ const sp<MockPreparedModel>& preparedModel) {
+ return [launchStatus, returnStatus, preparedModel](
+ const V1_2::Model& /*model*/, V1_1::ExecutionPreference /*preference*/,
+ const hardware::hidl_vec<hardware::hidl_handle>& /*modelCache*/,
+ const hardware::hidl_vec<hardware::hidl_handle>& /*dataCache*/,
+ const CacheToken& /*token*/, const sp<V1_2::IPreparedModelCallback>& cb)
+ -> hardware::Return<V1_0::ErrorStatus> {
+ cb->notify_1_2(returnStatus, preparedModel).isOk();
+ return launchStatus;
+ };
+}
+auto makePreparedModelFromCacheReturn(V1_0::ErrorStatus launchStatus,
+ V1_0::ErrorStatus returnStatus,
+ const sp<MockPreparedModel>& preparedModel) {
+ return [launchStatus, returnStatus, preparedModel](
+ const hardware::hidl_vec<hardware::hidl_handle>& /*modelCache*/,
+ const hardware::hidl_vec<hardware::hidl_handle>& /*dataCache*/,
+ const CacheToken& /*token*/, const sp<V1_2::IPreparedModelCallback>& cb)
+ -> hardware::Return<V1_0::ErrorStatus> {
+ cb->notify_1_2(returnStatus, preparedModel).isOk();
+ return launchStatus;
+ };
+}
+
+std::function<hardware::Status()> makeTransportFailure(status_t status) {
+ return [status] { return hardware::Status::fromStatusT(status); };
+}
+
+const auto makeGeneralTransportFailure = makeTransportFailure(NO_MEMORY);
+const auto makeDeadObjectFailure = makeTransportFailure(DEAD_OBJECT);
+
+} // namespace
+
+TEST(DeviceTest, invalidName) {
+ // run test
+ const auto device = MockDevice::create();
+ const auto result = Device::create(kInvalidName, device);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST(DeviceTest, invalidDevice) {
+ // run test
+ const auto result = Device::create(kName, kInvalidDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST(DeviceTest, getVersionStringError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto ret = makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE, "");
+ EXPECT_CALL(*mockDevice, getVersionString(_)).Times(1).WillOnce(Invoke(ret));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getVersionStringTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getVersionString(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getVersionStringDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getVersionString(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getTypeError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto ret =
+ makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE, V1_2::DeviceType::OTHER);
+ EXPECT_CALL(*mockDevice, getType(_)).Times(1).WillOnce(Invoke(ret));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getTypeTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getType(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getTypeDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getType(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getSupportedExtensionsError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto ret =
+ makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE, hidl_vec<V1_2::Extension>{});
+ EXPECT_CALL(*mockDevice, getSupportedExtensions(_)).Times(1).WillOnce(Invoke(ret));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getSupportedExtensionsTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getSupportedExtensions(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getSupportedExtensionsDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getSupportedExtensions(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getNumberOfCacheFilesNeededError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto ret = makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE,
+ nn::kMaxNumberOfCacheFiles, nn::kMaxNumberOfCacheFiles);
+ EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(1).WillOnce(Invoke(ret));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, dataCacheFilesExceedsSpecifiedMax) {
+ // setup test
+ const auto mockDevice = createMockDevice();
+ const auto ret = makeCallbackReturn(V1_0::ErrorStatus::NONE, nn::kMaxNumberOfCacheFiles + 1,
+ nn::kMaxNumberOfCacheFiles);
+ EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(1).WillOnce(Invoke(ret));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, modelCacheFilesExceedsSpecifiedMax) {
+ // setup test
+ const auto mockDevice = createMockDevice();
+ const auto ret = makeCallbackReturn(V1_0::ErrorStatus::NONE, nn::kMaxNumberOfCacheFiles,
+ nn::kMaxNumberOfCacheFiles + 1);
+ EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(1).WillOnce(Invoke(ret));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getNumberOfCacheFilesNeededTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getNumberOfCacheFilesNeededDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getCapabilitiesError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto ret = makeCallbackReturn(
+ V1_0::ErrorStatus::GENERAL_FAILURE,
+ V1_2::Capabilities{
+ .relaxedFloat32toFloat16PerformanceScalar = kNoPerformanceInfo,
+ .relaxedFloat32toFloat16PerformanceTensor = kNoPerformanceInfo,
+ });
+ EXPECT_CALL(*mockDevice, getCapabilities_1_2(_)).Times(1).WillOnce(Invoke(ret));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getCapabilitiesTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getCapabilities_1_2(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getCapabilitiesDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getCapabilities_1_2(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, linkToDeathError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto ret = []() -> Return<bool> { return false; };
+ EXPECT_CALL(*mockDevice, linkToDeathRet()).Times(1).WillOnce(InvokeWithoutArgs(ret));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, linkToDeathTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, linkToDeathRet())
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, linkToDeathDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, linkToDeathRet())
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getName) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+
+ // run test
+ const auto& name = device->getName();
+
+ // verify result
+ EXPECT_EQ(name, kName);
+}
+
+TEST(DeviceTest, getFeatureLevel) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+
+ // run test
+ const auto featureLevel = device->getFeatureLevel();
+
+ // verify result
+ EXPECT_EQ(featureLevel, nn::Version::ANDROID_Q);
+}
+
+TEST(DeviceTest, getCachedData) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getVersionString(_)).Times(1);
+ EXPECT_CALL(*mockDevice, getType(_)).Times(1);
+ EXPECT_CALL(*mockDevice, getSupportedExtensions(_)).Times(1);
+ EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(1);
+ EXPECT_CALL(*mockDevice, getCapabilities_1_2(_)).Times(1);
+
+ const auto result = Device::create(kName, mockDevice);
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ const auto& device = result.value();
+
+ // run test and verify results
+ EXPECT_EQ(device->getVersionString(), device->getVersionString());
+ EXPECT_EQ(device->getType(), device->getType());
+ EXPECT_EQ(device->getSupportedExtensions(), device->getSupportedExtensions());
+ EXPECT_EQ(device->getNumberOfCacheFilesNeeded(), device->getNumberOfCacheFilesNeeded());
+ EXPECT_EQ(device->getCapabilities(), device->getCapabilities());
+}
+
+TEST(DeviceTest, wait) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto ret = []() -> Return<void> { return {}; };
+ EXPECT_CALL(*mockDevice, ping()).Times(1).WillOnce(InvokeWithoutArgs(ret));
+ const auto device = Device::create(kName, mockDevice).value();
+
+ // run test
+ const auto result = device->wait();
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(DeviceTest, waitTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, ping())
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+ const auto device = Device::create(kName, mockDevice).value();
+
+ // run test
+ const auto result = device->wait();
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, waitDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, ping()).Times(1).WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+ const auto device = Device::create(kName, mockDevice).value();
+
+ // run test
+ const auto result = device->wait();
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getSupportedOperations) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ const auto ret = [](const auto& model, const auto& cb) {
+ cb(V1_0::ErrorStatus::NONE, std::vector<bool>(model.operations.size(), true));
+ return hardware::Void();
+ };
+ EXPECT_CALL(*mockDevice, getSupportedOperations_1_2(_, _)).Times(1).WillOnce(Invoke(ret));
+
+ // run test
+ const auto result = device->getSupportedOperations(kSimpleModel);
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ const auto& supportedOperations = result.value();
+ EXPECT_EQ(supportedOperations.size(), kSimpleModel.main.operations.size());
+ EXPECT_THAT(supportedOperations, Each(testing::IsTrue()));
+}
+
+TEST(DeviceTest, getSupportedOperationsError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ const auto ret = [](const auto& /*model*/, const auto& cb) {
+ cb(V1_0::ErrorStatus::GENERAL_FAILURE, {});
+ return hardware::Void();
+ };
+ EXPECT_CALL(*mockDevice, getSupportedOperations_1_2(_, _)).Times(1).WillOnce(Invoke(ret));
+
+ // run test
+ const auto result = device->getSupportedOperations(kSimpleModel);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getSupportedOperationsTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, getSupportedOperations_1_2(_, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = device->getSupportedOperations(kSimpleModel);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getSupportedOperationsDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, getSupportedOperations_1_2(_, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = device->getSupportedOperations(kSimpleModel);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, prepareModel) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ const auto mockPreparedModel = MockPreparedModel::create();
+ EXPECT_CALL(*mockDevice, prepareModel_1_2(_, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::NONE,
+ V1_0::ErrorStatus::NONE, mockPreparedModel)));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ EXPECT_NE(result.value(), nullptr);
+}
+
+TEST(DeviceTest, prepareModelLaunchError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModel_1_2(_, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::GENERAL_FAILURE,
+ V1_0::ErrorStatus::GENERAL_FAILURE, nullptr)));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelReturnError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModel_1_2(_, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::NONE,
+ V1_0::ErrorStatus::GENERAL_FAILURE, nullptr)));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelNullptrError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModel_1_2(_, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::NONE,
+ V1_0::ErrorStatus::NONE, nullptr)));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModel_1_2(_, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModel_1_2(_, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, prepareModelAsyncCrash) {
+ // setup test
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ const auto ret = [&mockDevice]() -> hardware::Return<V1_0::ErrorStatus> {
+ mockDevice->simulateCrash();
+ return V1_0::ErrorStatus::NONE;
+ };
+ EXPECT_CALL(*mockDevice, prepareModel_1_2(_, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(ret));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, prepareModelFromCache) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ const auto mockPreparedModel = MockPreparedModel::create();
+ EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makePreparedModelFromCacheReturn(
+ V1_0::ErrorStatus::NONE, V1_0::ErrorStatus::NONE, mockPreparedModel)));
+
+ // run test
+ const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ EXPECT_NE(result.value(), nullptr);
+}
+
+TEST(DeviceTest, prepareModelFromCacheError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makePreparedModelFromCacheReturn(V1_0::ErrorStatus::GENERAL_FAILURE,
+ V1_0::ErrorStatus::GENERAL_FAILURE,
+ nullptr)));
+
+ // run test
+ const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelFromCacheNullptrError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makePreparedModelFromCacheReturn(V1_0::ErrorStatus::NONE,
+ V1_0::ErrorStatus::NONE, nullptr)));
+
+ // run test
+ const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelFromCacheTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelFromCacheDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, prepareModelFromCacheAsyncCrash) {
+ // setup test
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ const auto ret = [&mockDevice]() -> hardware::Return<V1_0::ErrorStatus> {
+ mockDevice->simulateCrash();
+ return V1_0::ErrorStatus::NONE;
+ };
+ EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(ret));
+
+ // run test
+ const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, allocateNotSupported) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+
+ // run test
+ const auto result = device->allocate({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+} // namespace android::hardware::neuralnetworks::V1_2::utils
diff --git a/neuralnetworks/1.2/utils/test/MockDevice.h b/neuralnetworks/1.2/utils/test/MockDevice.h
new file mode 100644
index 0000000..b459943
--- /dev/null
+++ b/neuralnetworks/1.2/utils/test/MockDevice.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_DEVICE
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_DEVICE
+
+#include <android/hardware/neuralnetworks/1.2/IDevice.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <hidl/Status.h>
+
+namespace android::hardware::neuralnetworks::V1_2::utils {
+
+using CacheToken =
+ hidl_array<uint8_t, static_cast<uint32_t>(V1_2::Constant::BYTE_SIZE_OF_CACHE_TOKEN)>;
+
+class MockDevice final : public IDevice {
+ public:
+ static sp<MockDevice> create();
+
+ // IBase methods below.
+ MOCK_METHOD(Return<void>, ping, (), (override));
+ MOCK_METHOD(Return<bool>, linkToDeathRet, ());
+ Return<bool> linkToDeath(const sp<hidl_death_recipient>& recipient, uint64_t /*cookie*/);
+
+ // V1_0 methods below.
+ MOCK_METHOD(Return<void>, getCapabilities, (getCapabilities_cb cb), (override));
+ MOCK_METHOD(Return<void>, getSupportedOperations,
+ (const V1_0::Model& model, getSupportedOperations_cb cb), (override));
+ MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModel,
+ (const V1_0::Model& model, const sp<V1_0::IPreparedModelCallback>& callback),
+ (override));
+ MOCK_METHOD(Return<V1_0::DeviceStatus>, getStatus, (), (override));
+
+ // V1_1 methods below.
+ MOCK_METHOD(Return<void>, getCapabilities_1_1, (getCapabilities_1_1_cb cb), (override));
+ MOCK_METHOD(Return<void>, getSupportedOperations_1_1,
+ (const V1_1::Model& model, getSupportedOperations_1_1_cb cb), (override));
+ MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModel_1_1,
+ (const V1_1::Model& model, V1_1::ExecutionPreference preference,
+ const sp<V1_0::IPreparedModelCallback>& callback),
+ (override));
+
+ // V1_2 methods below.
+ MOCK_METHOD(Return<void>, getVersionString, (getVersionString_cb cb), (override));
+ MOCK_METHOD(Return<void>, getType, (getType_cb cb), (override));
+ MOCK_METHOD(Return<void>, getCapabilities_1_2, (getCapabilities_1_2_cb cb), (override));
+ MOCK_METHOD(Return<void>, getSupportedExtensions, (getSupportedExtensions_cb cb), (override));
+ MOCK_METHOD(Return<void>, getSupportedOperations_1_2,
+ (const V1_2::Model& model, getSupportedOperations_1_2_cb cb), (override));
+ MOCK_METHOD(Return<void>, getNumberOfCacheFilesNeeded, (getNumberOfCacheFilesNeeded_cb cb),
+ (override));
+ MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModel_1_2,
+ (const V1_2::Model& model, V1_1::ExecutionPreference preference,
+ const hidl_vec<hidl_handle>& modelCache, const hidl_vec<hidl_handle>& dataCache,
+ const CacheToken& token, const sp<V1_2::IPreparedModelCallback>& callback),
+ (override));
+ MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModelFromCache,
+ (const hidl_vec<hidl_handle>& modelCache, const hidl_vec<hidl_handle>& dataCache,
+ const CacheToken& token, const sp<V1_2::IPreparedModelCallback>& callback),
+ (override));
+
+ // Helper methods.
+ void simulateCrash();
+
+ private:
+ sp<hidl_death_recipient> mDeathRecipient;
+};
+
+inline sp<MockDevice> MockDevice::create() {
+ auto mockDevice = sp<MockDevice>::make();
+
+ // Setup default actions for each relevant call.
+ const auto ret = []() -> Return<bool> { return true; };
+
+ // Setup default actions for each relevant call.
+ ON_CALL(*mockDevice, linkToDeathRet()).WillByDefault(testing::Invoke(ret));
+
+ // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the
+ // uninteresting methods calls.
+ EXPECT_CALL(*mockDevice, linkToDeathRet()).Times(testing::AnyNumber());
+
+ return mockDevice;
+}
+
+inline Return<bool> MockDevice::linkToDeath(const sp<hidl_death_recipient>& recipient,
+ uint64_t /*cookie*/) {
+ mDeathRecipient = recipient;
+ return linkToDeathRet();
+}
+
+inline void MockDevice::simulateCrash() {
+ ASSERT_NE(nullptr, mDeathRecipient.get());
+
+ // Currently, the utils::Device will not use the `cookie` or `who` arguments, so we pass in 0
+ // and nullptr for these arguments instead. Normally, they are used by the hidl_death_recipient
+ // to determine which object is dead. However, the utils::Device code only pairs a single death
+ // recipient with a single HIDL interface object, so these arguments are redundant.
+ mDeathRecipient->serviceDied(0, nullptr);
+}
+
+} // namespace android::hardware::neuralnetworks::V1_2::utils
+
+#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_DEVICE
diff --git a/neuralnetworks/1.2/utils/test/MockPreparedModel.h b/neuralnetworks/1.2/utils/test/MockPreparedModel.h
new file mode 100644
index 0000000..f5fd1f3
--- /dev/null
+++ b/neuralnetworks/1.2/utils/test/MockPreparedModel.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_PREPARED_MODEL
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_PREPARED_MODEL
+
+#include <android/hardware/neuralnetworks/1.2/IPreparedModel.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <hidl/HidlSupport.h>
+#include <hidl/Status.h>
+
+namespace android::hardware::neuralnetworks::V1_2::utils {
+
+class MockPreparedModel final : public IPreparedModel {
+ public:
+ static sp<MockPreparedModel> create();
+
+ // IBase methods below.
+ MOCK_METHOD(Return<void>, ping, (), (override));
+ MOCK_METHOD(Return<bool>, linkToDeathRet, ());
+ Return<bool> linkToDeath(const sp<hidl_death_recipient>& recipient,
+ uint64_t /*cookie*/) override;
+
+ // V1_0 methods below.
+ MOCK_METHOD(Return<V1_0::ErrorStatus>, execute,
+ (const V1_0::Request& request, const sp<V1_0::IExecutionCallback>& callback),
+ (override));
+
+ // V1_2 methods below.
+ MOCK_METHOD(Return<V1_0::ErrorStatus>, execute_1_2,
+ (const V1_0::Request& request, V1_2::MeasureTiming measure,
+ const sp<V1_2::IExecutionCallback>& callback),
+ (override));
+ MOCK_METHOD(Return<void>, executeSynchronously,
+ (const V1_0::Request& request, V1_2::MeasureTiming measure,
+ executeSynchronously_cb cb),
+ (override));
+ MOCK_METHOD(Return<void>, configureExecutionBurst,
+ (const sp<V1_2::IBurstCallback>& callback,
+ const MQDescriptorSync<V1_2::FmqRequestDatum>& requestChannel,
+ const MQDescriptorSync<V1_2::FmqResultDatum>& resultChannel,
+ configureExecutionBurst_cb cb),
+ (override));
+
+ // Helper methods.
+ void simulateCrash();
+
+ private:
+ sp<hidl_death_recipient> mDeathRecipient;
+};
+
+inline sp<MockPreparedModel> MockPreparedModel::create() {
+ auto mockPreparedModel = sp<MockPreparedModel>::make();
+
+ // Setup default actions for each relevant call.
+ const auto ret = []() -> Return<bool> { return true; };
+
+ // Setup default actions for each relevant call.
+ ON_CALL(*mockPreparedModel, linkToDeathRet()).WillByDefault(testing::Invoke(ret));
+
+ // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the
+ // uninteresting methods calls.
+ EXPECT_CALL(*mockPreparedModel, linkToDeathRet()).Times(testing::AnyNumber());
+
+ return mockPreparedModel;
+}
+
+inline Return<bool> MockPreparedModel::linkToDeath(const sp<hidl_death_recipient>& recipient,
+ uint64_t /*cookie*/) {
+ mDeathRecipient = recipient;
+ return linkToDeathRet();
+}
+
+inline void MockPreparedModel::simulateCrash() {
+ ASSERT_NE(nullptr, mDeathRecipient.get());
+
+ // Currently, the utils::PreparedModel will not use the `cookie` or `who` arguments, so we pass
+ // in 0 and nullptr for these arguments instead. Normally, they are used by the
+ // hidl_death_recipient to determine which object is dead. However, the utils::PreparedModel
+ // code only pairs a single death recipient with a single HIDL interface object, so these
+ // arguments are redundant.
+ mDeathRecipient->serviceDied(0, nullptr);
+}
+
+} // namespace android::hardware::neuralnetworks::V1_2::utils
+
+#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_PREPARED_MODEL
diff --git a/neuralnetworks/1.2/utils/test/PreparedModelTest.cpp b/neuralnetworks/1.2/utils/test/PreparedModelTest.cpp
new file mode 100644
index 0000000..5062ac9
--- /dev/null
+++ b/neuralnetworks/1.2/utils/test/PreparedModelTest.cpp
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "MockPreparedModel.h"
+
+#include <android/hardware/neuralnetworks/1.2/IExecutionCallback.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <nnapi/IPreparedModel.h>
+#include <nnapi/TypeUtils.h>
+#include <nnapi/Types.h>
+#include <nnapi/hal/1.2/PreparedModel.h>
+
+#include <functional>
+#include <memory>
+
+namespace android::hardware::neuralnetworks::V1_2::utils {
+namespace {
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::InvokeWithoutArgs;
+
+const sp<V1_2::IPreparedModel> kInvalidPreparedModel;
+constexpr auto kNoTiming = V1_2::Timing{.timeOnDevice = std::numeric_limits<uint64_t>::max(),
+ .timeInDriver = std::numeric_limits<uint64_t>::max()};
+
+sp<MockPreparedModel> createMockPreparedModel() {
+ const auto mockPreparedModel = MockPreparedModel::create();
+
+ // Ensure that older calls are not used.
+ EXPECT_CALL(*mockPreparedModel, execute(_, _)).Times(0);
+
+ return mockPreparedModel;
+}
+
+auto makeExecuteSynchronously(V1_0::ErrorStatus status,
+ const std::vector<V1_2::OutputShape>& outputShapes,
+ const V1_2::Timing& timing) {
+ return [status, outputShapes, timing](const V1_0::Request& /*request*/,
+ V1_2::MeasureTiming /*measureTiming*/,
+ const V1_2::IPreparedModel::executeSynchronously_cb& cb) {
+ cb(status, outputShapes, timing);
+ return hardware::Void();
+ };
+}
+auto makeExecuteAsynchronously(V1_0::ErrorStatus launchStatus, V1_0::ErrorStatus returnStatus,
+ const std::vector<V1_2::OutputShape>& outputShapes,
+ const V1_2::Timing& timing) {
+ return [launchStatus, returnStatus, outputShapes, timing](
+ const V1_0::Request& /*request*/, V1_2::MeasureTiming /*measureTiming*/,
+ const sp<V1_2::IExecutionCallback>& cb) -> Return<V1_0::ErrorStatus> {
+ cb->notify_1_2(returnStatus, outputShapes, timing);
+ return launchStatus;
+ };
+}
+
+std::function<hardware::Status()> makeTransportFailure(status_t status) {
+ return [status] { return hardware::Status::fromStatusT(status); };
+}
+
+const auto makeGeneralTransportFailure = makeTransportFailure(NO_MEMORY);
+const auto makeDeadObjectFailure = makeTransportFailure(DEAD_OBJECT);
+
+} // namespace
+
+TEST(PreparedModelTest, invalidPreparedModel) {
+ // run test
+ const auto result = PreparedModel::create(kInvalidPreparedModel, /*executeSynchronously=*/true);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, linkToDeathError) {
+ // setup call
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto ret = []() -> Return<bool> { return false; };
+ EXPECT_CALL(*mockPreparedModel, linkToDeathRet()).Times(1).WillOnce(InvokeWithoutArgs(ret));
+
+ // run test
+ const auto result = PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, linkToDeathTransportFailure) {
+ // setup call
+ const auto mockPreparedModel = createMockPreparedModel();
+ EXPECT_CALL(*mockPreparedModel, linkToDeathRet())
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, linkToDeathDeadObject) {
+ // setup call
+ const auto mockPreparedModel = createMockPreparedModel();
+ EXPECT_CALL(*mockPreparedModel, linkToDeathRet())
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(PreparedModelTest, executeSync) {
+ // setup call
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+ EXPECT_CALL(*mockPreparedModel, executeSynchronously(_, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makeExecuteSynchronously(V1_0::ErrorStatus::NONE, {}, kNoTiming)));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ EXPECT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(PreparedModelTest, executeSyncError) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+ EXPECT_CALL(*mockPreparedModel, executeSynchronously(_, _, _))
+ .Times(1)
+ .WillOnce(Invoke(
+ makeExecuteSynchronously(V1_0::ErrorStatus::GENERAL_FAILURE, {}, kNoTiming)));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeSyncTransportFailure) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+ EXPECT_CALL(*mockPreparedModel, executeSynchronously(_, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeSyncDeadObject) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+ EXPECT_CALL(*mockPreparedModel, executeSynchronously(_, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(PreparedModelTest, executeAsync) {
+ // setup call
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value();
+ EXPECT_CALL(*mockPreparedModel, execute_1_2(_, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makeExecuteAsynchronously(V1_0::ErrorStatus::NONE,
+ V1_0::ErrorStatus::NONE, {}, kNoTiming)));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ EXPECT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(PreparedModelTest, executeAsyncLaunchError) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value();
+ EXPECT_CALL(*mockPreparedModel, execute_1_2(_, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makeExecuteAsynchronously(V1_0::ErrorStatus::GENERAL_FAILURE,
+ V1_0::ErrorStatus::GENERAL_FAILURE, {},
+ kNoTiming)));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeAsyncReturnError) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value();
+ EXPECT_CALL(*mockPreparedModel, execute_1_2(_, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makeExecuteAsynchronously(
+ V1_0::ErrorStatus::NONE, V1_0::ErrorStatus::GENERAL_FAILURE, {}, kNoTiming)));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeAsyncTransportFailure) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value();
+ EXPECT_CALL(*mockPreparedModel, execute_1_2(_, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeAsyncDeadObject) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value();
+ EXPECT_CALL(*mockPreparedModel, execute_1_2(_, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(PreparedModelTest, executeAsyncCrash) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value();
+ const auto ret = [&mockPreparedModel]() -> hardware::Return<V1_0::ErrorStatus> {
+ mockPreparedModel->simulateCrash();
+ return V1_0::ErrorStatus::NONE;
+ };
+ EXPECT_CALL(*mockPreparedModel, execute_1_2(_, _, _)).Times(1).WillOnce(InvokeWithoutArgs(ret));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(PreparedModelTest, executeFencedNotSupported) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+
+ // run test
+ const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+// TODO: test burst execution if/when it is added to nn::IPreparedModel.
+
+TEST(PreparedModelTest, getUnderlyingResource) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+
+ // run test
+ const auto resource = preparedModel->getUnderlyingResource();
+
+ // verify resource
+ const sp<V1_2::IPreparedModel>* maybeMock = std::any_cast<sp<V1_2::IPreparedModel>>(&resource);
+ ASSERT_NE(maybeMock, nullptr);
+ EXPECT_EQ(maybeMock->get(), mockPreparedModel.get());
+}
+
+} // namespace android::hardware::neuralnetworks::V1_2::utils
diff --git a/neuralnetworks/1.3/utils/Android.bp b/neuralnetworks/1.3/utils/Android.bp
index d5d897d..41d9521 100644
--- a/neuralnetworks/1.3/utils/Android.bp
+++ b/neuralnetworks/1.3/utils/Android.bp
@@ -38,3 +38,35 @@
"neuralnetworks_utils_hal_common",
],
}
+
+cc_test {
+ name: "neuralnetworks_utils_hal_1_3_test",
+ srcs: ["test/*.cpp"],
+ static_libs: [
+ "android.hardware.neuralnetworks@1.0",
+ "android.hardware.neuralnetworks@1.1",
+ "android.hardware.neuralnetworks@1.2",
+ "android.hardware.neuralnetworks@1.3",
+ "libgmock",
+ "libneuralnetworks_common",
+ "neuralnetworks_types",
+ "neuralnetworks_utils_hal_common",
+ "neuralnetworks_utils_hal_1_0",
+ "neuralnetworks_utils_hal_1_1",
+ "neuralnetworks_utils_hal_1_2",
+ "neuralnetworks_utils_hal_1_3",
+ ],
+ shared_libs: [
+ "android.hidl.allocator@1.0",
+ "android.hidl.memory@1.0",
+ "libbase",
+ "libcutils",
+ "libfmq",
+ "libhidlbase",
+ "libhidlmemory",
+ "liblog",
+ "libnativewindow",
+ "libutils",
+ ],
+ test_suites: ["general-tests"],
+}
diff --git a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Buffer.h b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Buffer.h
index fda79c8..69e87f7 100644
--- a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Buffer.h
+++ b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Buffer.h
@@ -42,8 +42,8 @@
nn::Request::MemoryDomainToken getToken() const override;
- nn::GeneralResult<void> copyTo(const nn::Memory& dst) const override;
- nn::GeneralResult<void> copyFrom(const nn::Memory& src,
+ nn::GeneralResult<void> copyTo(const nn::SharedMemory& dst) const override;
+ nn::GeneralResult<void> copyFrom(const nn::SharedMemory& src,
const nn::Dimensions& dimensions) const override;
private:
diff --git a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Conversions.h b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Conversions.h
index 74a6534..8e1cdb8 100644
--- a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Conversions.h
+++ b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Conversions.h
@@ -59,7 +59,7 @@
GeneralResult<ErrorStatus> convert(const hal::V1_3::ErrorStatus& errorStatus);
GeneralResult<SharedHandle> convert(const hardware::hidl_handle& handle);
-GeneralResult<Memory> convert(const hardware::hidl_memory& memory);
+GeneralResult<SharedMemory> convert(const hardware::hidl_memory& memory);
GeneralResult<std::vector<BufferRole>> convert(
const hardware::hidl_vec<hal::V1_3::BufferRole>& bufferRoles);
@@ -100,7 +100,7 @@
nn::GeneralResult<ErrorStatus> convert(const nn::ErrorStatus& errorStatus);
nn::GeneralResult<hidl_handle> convert(const nn::SharedHandle& handle);
-nn::GeneralResult<hidl_memory> convert(const nn::Memory& memory);
+nn::GeneralResult<hidl_memory> convert(const nn::SharedMemory& memory);
nn::GeneralResult<hidl_vec<BufferRole>> convert(const std::vector<nn::BufferRole>& bufferRoles);
nn::GeneralResult<V1_0::DeviceStatus> convert(const nn::DeviceStatus& deviceStatus);
diff --git a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Device.h b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Device.h
index 84f606a..f36b6c0 100644
--- a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Device.h
+++ b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Device.h
@@ -54,6 +54,7 @@
const std::string& getVersionString() const override;
nn::Version getFeatureLevel() const override;
nn::DeviceType getType() const override;
+ bool isUpdatable() const override;
const std::vector<nn::Extension>& getSupportedExtensions() const override;
const nn::Capabilities& getCapabilities() const override;
std::pair<uint32_t, uint32_t> getNumberOfCacheFilesNeeded() const override;
diff --git a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/PreparedModel.h b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/PreparedModel.h
index 664d87a..690fecc 100644
--- a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/PreparedModel.h
+++ b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/PreparedModel.h
@@ -35,7 +35,8 @@
namespace android::hardware::neuralnetworks::V1_3::utils {
// Class that adapts V1_3::IPreparedModel to nn::IPreparedModel.
-class PreparedModel final : public nn::IPreparedModel {
+class PreparedModel final : public nn::IPreparedModel,
+ public std::enable_shared_from_this<PreparedModel> {
struct PrivateConstructorTag {};
public:
@@ -56,6 +57,8 @@
const nn::OptionalDuration& loopTimeoutDuration,
const nn::OptionalDuration& timeoutDurationAfterFence) const override;
+ nn::GeneralResult<nn::SharedBurst> configureExecutionBurst() const override;
+
std::any getUnderlyingResource() const override;
private:
diff --git a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Utils.h b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Utils.h
index 29b0c80..3ce412c 100644
--- a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Utils.h
+++ b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Utils.h
@@ -49,6 +49,12 @@
return result.has_value();
}
+template <typename Type>
+auto convertFromNonCanonical(const Type& nonCanonicalObject)
+ -> decltype(convert(nn::convert(nonCanonicalObject).value())) {
+ return convert(NN_TRY(nn::convert(nonCanonicalObject)));
+}
+
} // namespace android::hardware::neuralnetworks::V1_3::utils
#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_H
diff --git a/neuralnetworks/1.3/utils/src/Buffer.cpp b/neuralnetworks/1.3/utils/src/Buffer.cpp
index 614033e..ada5265 100644
--- a/neuralnetworks/1.3/utils/src/Buffer.cpp
+++ b/neuralnetworks/1.3/utils/src/Buffer.cpp
@@ -61,7 +61,7 @@
return kToken;
}
-nn::GeneralResult<void> Buffer::copyTo(const nn::Memory& dst) const {
+nn::GeneralResult<void> Buffer::copyTo(const nn::SharedMemory& dst) const {
const auto hidlDst = NN_TRY(convert(dst));
const auto ret = kBuffer->copyTo(hidlDst);
@@ -71,7 +71,7 @@
return {};
}
-nn::GeneralResult<void> Buffer::copyFrom(const nn::Memory& src,
+nn::GeneralResult<void> Buffer::copyFrom(const nn::SharedMemory& src,
const nn::Dimensions& dimensions) const {
const auto hidlSrc = NN_TRY(convert(src));
const auto hidlDimensions = hidl_vec<uint32_t>(dimensions);
diff --git a/neuralnetworks/1.3/utils/src/Callbacks.cpp b/neuralnetworks/1.3/utils/src/Callbacks.cpp
index af76e6a..8e9fb83 100644
--- a/neuralnetworks/1.3/utils/src/Callbacks.cpp
+++ b/neuralnetworks/1.3/utils/src/Callbacks.cpp
@@ -28,6 +28,7 @@
#include <nnapi/IPreparedModel.h>
#include <nnapi/Result.h>
#include <nnapi/Types.h>
+#include <nnapi/hal/1.0/Callbacks.h>
#include <nnapi/hal/1.0/Conversions.h>
#include <nnapi/hal/1.0/PreparedModel.h>
#include <nnapi/hal/1.2/Callbacks.h>
@@ -46,6 +47,20 @@
namespace android::hardware::neuralnetworks::V1_3::utils {
namespace {
+nn::GeneralResult<nn::SharedPreparedModel> prepareModelCallback(
+ V1_0::ErrorStatus status, const sp<V1_0::IPreparedModel>& preparedModel) {
+ if (const auto dynamicPreparedModel =
+ V1_3::IPreparedModel::castFrom(preparedModel).withDefault(nullptr)) {
+ const auto currentVersionStatus = NN_TRY(convertFromNonCanonical(status));
+ return V1_3::utils::prepareModelCallback(currentVersionStatus, dynamicPreparedModel);
+ }
+ if (const auto dynamicPreparedModel =
+ V1_2::IPreparedModel::castFrom(preparedModel).withDefault(nullptr)) {
+ return V1_2::utils::prepareModelCallback(status, dynamicPreparedModel);
+ }
+ return V1_0::utils::prepareModelCallback(status, preparedModel);
+}
+
nn::GeneralResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>>
convertExecutionGeneralResultsHelper(const hidl_vec<V1_2::OutputShape>& outputShapes,
const V1_2::Timing& timing) {
@@ -82,13 +97,13 @@
Return<void> PreparedModelCallback::notify(V1_0::ErrorStatus status,
const sp<V1_0::IPreparedModel>& preparedModel) {
- mData.put(V1_0::utils::prepareModelCallback(status, preparedModel));
+ mData.put(prepareModelCallback(status, preparedModel));
return Void();
}
Return<void> PreparedModelCallback::notify_1_2(V1_0::ErrorStatus status,
const sp<V1_2::IPreparedModel>& preparedModel) {
- mData.put(V1_2::utils::prepareModelCallback(status, preparedModel));
+ mData.put(prepareModelCallback(status, preparedModel));
return Void();
}
diff --git a/neuralnetworks/1.3/utils/src/Conversions.cpp b/neuralnetworks/1.3/utils/src/Conversions.cpp
index 8b7db2b..6e74a62 100644
--- a/neuralnetworks/1.3/utils/src/Conversions.cpp
+++ b/neuralnetworks/1.3/utils/src/Conversions.cpp
@@ -261,7 +261,7 @@
using Discriminator = hal::V1_3::Request::MemoryPool::hidl_discriminator;
switch (memoryPool.getDiscriminator()) {
case Discriminator::hidlMemory:
- return createSharedMemoryFromHidlMemory(memoryPool.hidlMemory());
+ return hal::utils::createSharedMemoryFromHidlMemory(memoryPool.hidlMemory());
case Discriminator::token:
return static_cast<Request::MemoryDomainToken>(memoryPool.token());
}
@@ -352,7 +352,7 @@
return validatedConvert(handle);
}
-GeneralResult<Memory> convert(const hardware::hidl_memory& memory) {
+GeneralResult<SharedMemory> convert(const hardware::hidl_memory& memory) {
return validatedConvert(memory);
}
@@ -386,7 +386,7 @@
return V1_2::utils::unvalidatedConvert(handle);
}
-nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::Memory& memory) {
+nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::SharedMemory& memory) {
return V1_0::utils::unvalidatedConvert(memory);
}
@@ -424,7 +424,7 @@
return unvalidatedConvertVec(arguments);
}
-nn::GeneralResult<Request::MemoryPool> makeMemoryPool(const nn::Memory& memory) {
+nn::GeneralResult<Request::MemoryPool> makeMemoryPool(const nn::SharedMemory& memory) {
Request::MemoryPool ret;
ret.hidlMemory(NN_TRY(unvalidatedConvert(memory)));
return ret;
@@ -677,7 +677,7 @@
return validatedConvert(handle);
}
-nn::GeneralResult<hidl_memory> convert(const nn::Memory& memory) {
+nn::GeneralResult<hidl_memory> convert(const nn::SharedMemory& memory) {
return validatedConvert(memory);
}
diff --git a/neuralnetworks/1.3/utils/src/Device.cpp b/neuralnetworks/1.3/utils/src/Device.cpp
index d710b85..87c9f32 100644
--- a/neuralnetworks/1.3/utils/src/Device.cpp
+++ b/neuralnetworks/1.3/utils/src/Device.cpp
@@ -150,6 +150,10 @@
return kDeviceType;
}
+bool Device::isUpdatable() const {
+ return false;
+}
+
const std::vector<nn::Extension>& Device::getSupportedExtensions() const {
return kExtensions;
}
diff --git a/neuralnetworks/1.3/utils/src/PreparedModel.cpp b/neuralnetworks/1.3/utils/src/PreparedModel.cpp
index 7b4b7ba..725e4f5 100644
--- a/neuralnetworks/1.3/utils/src/PreparedModel.cpp
+++ b/neuralnetworks/1.3/utils/src/PreparedModel.cpp
@@ -29,6 +29,7 @@
#include <nnapi/Result.h>
#include <nnapi/TypeUtils.h>
#include <nnapi/Types.h>
+#include <nnapi/hal/1.0/Burst.h>
#include <nnapi/hal/1.2/Conversions.h>
#include <nnapi/hal/CommonUtils.h>
#include <nnapi/hal/HandleError.h>
@@ -197,6 +198,10 @@
return std::make_pair(std::move(syncFence), std::move(callback));
}
+nn::GeneralResult<nn::SharedBurst> PreparedModel::configureExecutionBurst() const {
+ return V1_0::utils::Burst::create(shared_from_this());
+}
+
std::any PreparedModel::getUnderlyingResource() const {
sp<V1_3::IPreparedModel> resource = kPreparedModel;
return resource;
diff --git a/neuralnetworks/1.3/utils/test/BufferTest.cpp b/neuralnetworks/1.3/utils/test/BufferTest.cpp
new file mode 100644
index 0000000..d892b87
--- /dev/null
+++ b/neuralnetworks/1.3/utils/test/BufferTest.cpp
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "MockBuffer.h"
+
+#include <android/hardware/neuralnetworks/1.3/IBuffer.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <nnapi/IBuffer.h>
+#include <nnapi/SharedMemory.h>
+#include <nnapi/TypeUtils.h>
+#include <nnapi/Types.h>
+#include <nnapi/hal/1.3/Buffer.h>
+
+#include <functional>
+#include <memory>
+
+namespace android::hardware::neuralnetworks::V1_3::utils {
+namespace {
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::InvokeWithoutArgs;
+using ::testing::Return;
+
+const auto kMemory = nn::createSharedMemory(4).value();
+const sp<V1_3::IBuffer> kInvalidBuffer;
+constexpr auto kInvalidToken = nn::Request::MemoryDomainToken{0};
+constexpr auto kToken = nn::Request::MemoryDomainToken{1};
+
+std::function<hardware::Return<V1_3::ErrorStatus>()> makeFunctionReturn(V1_3::ErrorStatus status) {
+ return [status]() -> hardware::Return<V1_3::ErrorStatus> { return status; };
+}
+
+std::function<hardware::Status()> makeTransportFailure(status_t status) {
+ return [status] { return hardware::Status::fromStatusT(status); };
+}
+
+const auto makeSuccessful = makeFunctionReturn(V1_3::ErrorStatus::NONE);
+const auto makeGeneralError = makeFunctionReturn(V1_3::ErrorStatus::GENERAL_FAILURE);
+const auto makeGeneralTransportFailure = makeTransportFailure(NO_MEMORY);
+const auto makeDeadObjectFailure = makeTransportFailure(DEAD_OBJECT);
+
+} // namespace
+
+TEST(BufferTest, invalidBuffer) {
+ // run test
+ const auto result = Buffer::create(kInvalidBuffer, kToken);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(BufferTest, invalidToken) {
+ // setup call
+ const auto mockBuffer = MockBuffer::create();
+
+ // run test
+ const auto result = Buffer::create(mockBuffer, kInvalidToken);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(BufferTest, create) {
+ // setup call
+ const auto mockBuffer = MockBuffer::create();
+ const auto buffer = Buffer::create(mockBuffer, kToken).value();
+
+ // run test
+ const auto token = buffer->getToken();
+
+ // verify result
+ EXPECT_EQ(token, kToken);
+}
+
+TEST(BufferTest, copyTo) {
+ // setup call
+ const auto mockBuffer = MockBuffer::create();
+ const auto buffer = Buffer::create(mockBuffer, kToken).value();
+ EXPECT_CALL(*mockBuffer, copyTo(_)).Times(1).WillOnce(InvokeWithoutArgs(makeSuccessful));
+
+ // run test
+ const auto result = buffer->copyTo(kMemory);
+
+ // verify result
+ EXPECT_TRUE(result.has_value()) << result.error().message;
+}
+
+TEST(BufferTest, copyToError) {
+ // setup test
+ const auto mockBuffer = MockBuffer::create();
+ const auto buffer = Buffer::create(mockBuffer, kToken).value();
+ EXPECT_CALL(*mockBuffer, copyTo(_)).Times(1).WillOnce(InvokeWithoutArgs(makeGeneralError));
+
+ // run test
+ const auto result = buffer->copyTo(kMemory);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(BufferTest, copyToTransportFailure) {
+ // setup test
+ const auto mockBuffer = MockBuffer::create();
+ const auto buffer = Buffer::create(mockBuffer, kToken).value();
+ EXPECT_CALL(*mockBuffer, copyTo(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = buffer->copyTo(kMemory);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(BufferTest, copyToDeadObject) {
+ // setup test
+ const auto mockBuffer = MockBuffer::create();
+ const auto buffer = Buffer::create(mockBuffer, kToken).value();
+ EXPECT_CALL(*mockBuffer, copyTo(_)).Times(1).WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = buffer->copyTo(kMemory);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(BufferTest, copyFrom) {
+ // setup call
+ const auto mockBuffer = MockBuffer::create();
+ const auto buffer = Buffer::create(mockBuffer, kToken).value();
+ EXPECT_CALL(*mockBuffer, copyFrom(_, _)).Times(1).WillOnce(InvokeWithoutArgs(makeSuccessful));
+
+ // run test
+ const auto result = buffer->copyFrom(kMemory, {});
+
+ // verify result
+ EXPECT_TRUE(result.has_value());
+}
+
+TEST(BufferTest, copyFromError) {
+ // setup test
+ const auto mockBuffer = MockBuffer::create();
+ const auto buffer = Buffer::create(mockBuffer, kToken).value();
+ EXPECT_CALL(*mockBuffer, copyFrom(_, _)).Times(1).WillOnce(InvokeWithoutArgs(makeGeneralError));
+
+ // run test
+ const auto result = buffer->copyFrom(kMemory, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(BufferTest, copyFromTransportFailure) {
+ // setup test
+ const auto mockBuffer = MockBuffer::create();
+ const auto buffer = Buffer::create(mockBuffer, kToken).value();
+ EXPECT_CALL(*mockBuffer, copyFrom(_, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = buffer->copyFrom(kMemory, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(BufferTest, copyFromDeadObject) {
+ // setup test
+ const auto mockBuffer = MockBuffer::create();
+ const auto buffer = Buffer::create(mockBuffer, kToken).value();
+ EXPECT_CALL(*mockBuffer, copyFrom(_, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = buffer->copyFrom(kMemory, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+} // namespace android::hardware::neuralnetworks::V1_3::utils
diff --git a/neuralnetworks/1.3/utils/test/DeviceTest.cpp b/neuralnetworks/1.3/utils/test/DeviceTest.cpp
new file mode 100644
index 0000000..f260990
--- /dev/null
+++ b/neuralnetworks/1.3/utils/test/DeviceTest.cpp
@@ -0,0 +1,951 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "MockBuffer.h"
+#include "MockDevice.h"
+#include "MockPreparedModel.h"
+
+#include <android/hardware/neuralnetworks/1.3/IDevice.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <nnapi/IDevice.h>
+#include <nnapi/TypeUtils.h>
+#include <nnapi/Types.h>
+#include <nnapi/hal/1.3/Device.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+
+namespace android::hardware::neuralnetworks::V1_3::utils {
+namespace {
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::InvokeWithoutArgs;
+
+const nn::Model kSimpleModel = {
+ .main = {.operands = {{.type = nn::OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .lifetime = nn::Operand::LifeTime::SUBGRAPH_INPUT},
+ {.type = nn::OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .lifetime = nn::Operand::LifeTime::SUBGRAPH_OUTPUT}},
+ .operations = {{.type = nn::OperationType::RELU, .inputs = {0}, .outputs = {1}}},
+ .inputIndexes = {0},
+ .outputIndexes = {1}}};
+
+const std::string kName = "Google-MockV1";
+const std::string kInvalidName = "";
+const sp<V1_3::IDevice> kInvalidDevice;
+constexpr V1_0::PerformanceInfo kNoPerformanceInfo = {
+ .execTime = std::numeric_limits<float>::max(),
+ .powerUsage = std::numeric_limits<float>::max()};
+
+template <typename... Args>
+auto makeCallbackReturn(Args&&... args) {
+ return [argPack = std::make_tuple(std::forward<Args>(args)...)](const auto& cb) {
+ std::apply(cb, argPack);
+ return Void();
+ };
+}
+
+sp<MockDevice> createMockDevice() {
+ const auto mockDevice = MockDevice::create();
+
+ // Setup default actions for each relevant call.
+ const auto getVersionString_ret = makeCallbackReturn(V1_0::ErrorStatus::NONE, kName);
+ const auto getType_ret = makeCallbackReturn(V1_0::ErrorStatus::NONE, V1_2::DeviceType::OTHER);
+ const auto getSupportedExtensions_ret =
+ makeCallbackReturn(V1_0::ErrorStatus::NONE, hidl_vec<V1_2::Extension>{});
+ const auto getNumberOfCacheFilesNeeded_ret = makeCallbackReturn(
+ V1_0::ErrorStatus::NONE, nn::kMaxNumberOfCacheFiles, nn::kMaxNumberOfCacheFiles);
+ const auto getCapabilities_ret = makeCallbackReturn(
+ V1_3::ErrorStatus::NONE,
+ V1_3::Capabilities{
+ .relaxedFloat32toFloat16PerformanceScalar = kNoPerformanceInfo,
+ .relaxedFloat32toFloat16PerformanceTensor = kNoPerformanceInfo,
+ .ifPerformance = kNoPerformanceInfo,
+ .whilePerformance = kNoPerformanceInfo,
+ });
+
+ // Setup default actions for each relevant call.
+ ON_CALL(*mockDevice, getVersionString(_)).WillByDefault(Invoke(getVersionString_ret));
+ ON_CALL(*mockDevice, getType(_)).WillByDefault(Invoke(getType_ret));
+ ON_CALL(*mockDevice, getSupportedExtensions(_))
+ .WillByDefault(Invoke(getSupportedExtensions_ret));
+ ON_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_))
+ .WillByDefault(Invoke(getNumberOfCacheFilesNeeded_ret));
+ ON_CALL(*mockDevice, getCapabilities_1_3(_)).WillByDefault(Invoke(getCapabilities_ret));
+
+ // Ensure that older calls are not used.
+ EXPECT_CALL(*mockDevice, getCapabilities(_)).Times(0);
+ EXPECT_CALL(*mockDevice, getCapabilities_1_1(_)).Times(0);
+ EXPECT_CALL(*mockDevice, getCapabilities_1_2(_)).Times(0);
+ EXPECT_CALL(*mockDevice, getSupportedOperations(_, _)).Times(0);
+ EXPECT_CALL(*mockDevice, getSupportedOperations_1_1(_, _)).Times(0);
+ EXPECT_CALL(*mockDevice, prepareModel(_, _)).Times(0);
+ EXPECT_CALL(*mockDevice, prepareModel_1_1(_, _, _)).Times(0);
+ EXPECT_CALL(*mockDevice, getSupportedOperations_1_2(_, _)).Times(0);
+ EXPECT_CALL(*mockDevice, prepareModel_1_2(_, _, _, _, _, _)).Times(0);
+ EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _)).Times(0);
+
+ // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the
+ // uninteresting methods calls.
+ EXPECT_CALL(*mockDevice, getVersionString(_)).Times(testing::AnyNumber());
+ EXPECT_CALL(*mockDevice, getType(_)).Times(testing::AnyNumber());
+ EXPECT_CALL(*mockDevice, getSupportedExtensions(_)).Times(testing::AnyNumber());
+ EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(testing::AnyNumber());
+ EXPECT_CALL(*mockDevice, getCapabilities_1_3(_)).Times(testing::AnyNumber());
+
+ return mockDevice;
+}
+
+auto makePreparedModelReturn(V1_3::ErrorStatus launchStatus, V1_3::ErrorStatus returnStatus,
+ const sp<MockPreparedModel>& preparedModel) {
+ return [launchStatus, returnStatus, preparedModel](
+ const V1_3::Model& /*model*/, V1_1::ExecutionPreference /*preference*/,
+ V1_3::Priority /*priority*/, const V1_3::OptionalTimePoint& /*deadline*/,
+ const hardware::hidl_vec<hardware::hidl_handle>& /*modelCache*/,
+ const hardware::hidl_vec<hardware::hidl_handle>& /*dataCache*/,
+ const CacheToken& /*token*/, const sp<V1_3::IPreparedModelCallback>& cb)
+ -> hardware::Return<V1_3::ErrorStatus> {
+ cb->notify_1_3(returnStatus, preparedModel).isOk();
+ return launchStatus;
+ };
+}
+auto makePreparedModelFromCacheReturn(V1_3::ErrorStatus launchStatus,
+ V1_3::ErrorStatus returnStatus,
+ const sp<MockPreparedModel>& preparedModel) {
+ return [launchStatus, returnStatus, preparedModel](
+ const V1_3::OptionalTimePoint& /*deadline*/,
+ const hardware::hidl_vec<hardware::hidl_handle>& /*modelCache*/,
+ const hardware::hidl_vec<hardware::hidl_handle>& /*dataCache*/,
+ const CacheToken& /*token*/, const sp<V1_3::IPreparedModelCallback>& cb)
+ -> hardware::Return<V1_3::ErrorStatus> {
+ cb->notify_1_3(returnStatus, preparedModel).isOk();
+ return launchStatus;
+ };
+}
+auto makeAllocateReturn(ErrorStatus status, const sp<MockBuffer>& buffer, uint32_t token) {
+ return [status, buffer, token](
+ const V1_3::BufferDesc& /*desc*/,
+ const hardware::hidl_vec<sp<V1_3::IPreparedModel>>& /*preparedModels*/,
+ const hardware::hidl_vec<V1_3::BufferRole>& /*inputRoles*/,
+ const hardware::hidl_vec<V1_3::BufferRole>& /*outputRoles*/,
+ const V1_3::IDevice::allocate_cb& cb) -> hardware::Return<void> {
+ cb(status, buffer, token);
+ return hardware::Void();
+ };
+}
+
+std::function<hardware::Status()> makeTransportFailure(status_t status) {
+ return [status] { return hardware::Status::fromStatusT(status); };
+}
+
+const auto makeGeneralTransportFailure = makeTransportFailure(NO_MEMORY);
+const auto makeDeadObjectFailure = makeTransportFailure(DEAD_OBJECT);
+
+} // namespace
+
+TEST(DeviceTest, invalidName) {
+ // run test
+ const auto device = MockDevice::create();
+ const auto result = Device::create(kInvalidName, device);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST(DeviceTest, invalidDevice) {
+ // run test
+ const auto result = Device::create(kName, kInvalidDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST(DeviceTest, getVersionStringError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto ret = makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE, "");
+ EXPECT_CALL(*mockDevice, getVersionString(_)).Times(1).WillOnce(Invoke(ret));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getVersionStringTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getVersionString(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getVersionStringDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getVersionString(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getTypeError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto ret =
+ makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE, V1_2::DeviceType::OTHER);
+ EXPECT_CALL(*mockDevice, getType(_)).Times(1).WillOnce(Invoke(ret));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getTypeTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getType(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getTypeDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getType(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getSupportedExtensionsError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto ret =
+ makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE, hidl_vec<V1_2::Extension>{});
+ EXPECT_CALL(*mockDevice, getSupportedExtensions(_)).Times(1).WillOnce(Invoke(ret));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getSupportedExtensionsTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getSupportedExtensions(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getSupportedExtensionsDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getSupportedExtensions(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getNumberOfCacheFilesNeededError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto ret = makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE,
+ nn::kMaxNumberOfCacheFiles, nn::kMaxNumberOfCacheFiles);
+ EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(1).WillOnce(Invoke(ret));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, dataCacheFilesExceedsSpecifiedMax) {
+ // setup test
+ const auto mockDevice = createMockDevice();
+ const auto ret = makeCallbackReturn(V1_0::ErrorStatus::NONE, nn::kMaxNumberOfCacheFiles + 1,
+ nn::kMaxNumberOfCacheFiles);
+ EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(1).WillOnce(Invoke(ret));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, modelCacheFilesExceedsSpecifiedMax) {
+ // setup test
+ const auto mockDevice = createMockDevice();
+ const auto ret = makeCallbackReturn(V1_0::ErrorStatus::NONE, nn::kMaxNumberOfCacheFiles,
+ nn::kMaxNumberOfCacheFiles + 1);
+ EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(1).WillOnce(Invoke(ret));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getNumberOfCacheFilesNeededTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getNumberOfCacheFilesNeededDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getCapabilitiesError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto ret = makeCallbackReturn(
+ V1_3::ErrorStatus::GENERAL_FAILURE,
+ V1_3::Capabilities{
+ .relaxedFloat32toFloat16PerformanceScalar = kNoPerformanceInfo,
+ .relaxedFloat32toFloat16PerformanceTensor = kNoPerformanceInfo,
+ .ifPerformance = kNoPerformanceInfo,
+ .whilePerformance = kNoPerformanceInfo,
+ });
+ EXPECT_CALL(*mockDevice, getCapabilities_1_3(_)).Times(1).WillOnce(Invoke(ret));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getCapabilitiesTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getCapabilities_1_3(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getCapabilitiesDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getCapabilities_1_3(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, linkToDeathError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto ret = []() -> Return<bool> { return false; };
+ EXPECT_CALL(*mockDevice, linkToDeathRet()).Times(1).WillOnce(InvokeWithoutArgs(ret));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, linkToDeathTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, linkToDeathRet())
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, linkToDeathDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, linkToDeathRet())
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getName) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+
+ // run test
+ const auto& name = device->getName();
+
+ // verify result
+ EXPECT_EQ(name, kName);
+}
+
+TEST(DeviceTest, getFeatureLevel) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+
+ // run test
+ const auto featureLevel = device->getFeatureLevel();
+
+ // verify result
+ EXPECT_EQ(featureLevel, nn::Version::ANDROID_R);
+}
+
+TEST(DeviceTest, getCachedData) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getVersionString(_)).Times(1);
+ EXPECT_CALL(*mockDevice, getType(_)).Times(1);
+ EXPECT_CALL(*mockDevice, getSupportedExtensions(_)).Times(1);
+ EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(1);
+ EXPECT_CALL(*mockDevice, getCapabilities_1_3(_)).Times(1);
+
+ const auto result = Device::create(kName, mockDevice);
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ const auto& device = result.value();
+
+ // run test and verify results
+ EXPECT_EQ(device->getVersionString(), device->getVersionString());
+ EXPECT_EQ(device->getType(), device->getType());
+ EXPECT_EQ(device->getSupportedExtensions(), device->getSupportedExtensions());
+ EXPECT_EQ(device->getNumberOfCacheFilesNeeded(), device->getNumberOfCacheFilesNeeded());
+ EXPECT_EQ(device->getCapabilities(), device->getCapabilities());
+}
+
+TEST(DeviceTest, wait) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto ret = []() -> Return<void> { return {}; };
+ EXPECT_CALL(*mockDevice, ping()).Times(1).WillOnce(InvokeWithoutArgs(ret));
+ const auto device = Device::create(kName, mockDevice).value();
+
+ // run test
+ const auto result = device->wait();
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(DeviceTest, waitTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, ping())
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+ const auto device = Device::create(kName, mockDevice).value();
+
+ // run test
+ const auto result = device->wait();
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, waitDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, ping()).Times(1).WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+ const auto device = Device::create(kName, mockDevice).value();
+
+ // run test
+ const auto result = device->wait();
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getSupportedOperations) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ const auto ret = [](const auto& model, const auto& cb) {
+ cb(V1_3::ErrorStatus::NONE, std::vector<bool>(model.main.operations.size(), true));
+ return hardware::Void();
+ };
+ EXPECT_CALL(*mockDevice, getSupportedOperations_1_3(_, _)).Times(1).WillOnce(Invoke(ret));
+
+ // run test
+ const auto result = device->getSupportedOperations(kSimpleModel);
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ const auto& supportedOperations = result.value();
+ EXPECT_EQ(supportedOperations.size(), kSimpleModel.main.operations.size());
+ EXPECT_THAT(supportedOperations, Each(testing::IsTrue()));
+}
+
+TEST(DeviceTest, getSupportedOperationsError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ const auto ret = [](const auto& /*model*/, const auto& cb) {
+ cb(V1_3::ErrorStatus::GENERAL_FAILURE, {});
+ return hardware::Void();
+ };
+ EXPECT_CALL(*mockDevice, getSupportedOperations_1_3(_, _)).Times(1).WillOnce(Invoke(ret));
+
+ // run test
+ const auto result = device->getSupportedOperations(kSimpleModel);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getSupportedOperationsTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, getSupportedOperations_1_3(_, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = device->getSupportedOperations(kSimpleModel);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getSupportedOperationsDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, getSupportedOperations_1_3(_, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = device->getSupportedOperations(kSimpleModel);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, prepareModel) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ const auto mockPreparedModel = MockPreparedModel::create();
+ EXPECT_CALL(*mockDevice, prepareModel_1_3(_, _, _, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makePreparedModelReturn(V1_3::ErrorStatus::NONE,
+ V1_3::ErrorStatus::NONE, mockPreparedModel)));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ EXPECT_NE(result.value(), nullptr);
+}
+
+TEST(DeviceTest, prepareModelLaunchError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModel_1_3(_, _, _, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makePreparedModelReturn(V1_3::ErrorStatus::GENERAL_FAILURE,
+ V1_3::ErrorStatus::GENERAL_FAILURE, nullptr)));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelReturnError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModel_1_3(_, _, _, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makePreparedModelReturn(V1_3::ErrorStatus::NONE,
+ V1_3::ErrorStatus::GENERAL_FAILURE, nullptr)));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelNullptrError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModel_1_3(_, _, _, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makePreparedModelReturn(V1_3::ErrorStatus::NONE,
+ V1_3::ErrorStatus::NONE, nullptr)));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModel_1_3(_, _, _, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModel_1_3(_, _, _, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, prepareModelAsyncCrash) {
+ // setup test
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ const auto ret = [&mockDevice]() -> hardware::Return<V1_3::ErrorStatus> {
+ mockDevice->simulateCrash();
+ return V1_3::ErrorStatus::NONE;
+ };
+ EXPECT_CALL(*mockDevice, prepareModel_1_3(_, _, _, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(ret));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, prepareModelFromCache) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ const auto mockPreparedModel = MockPreparedModel::create();
+ EXPECT_CALL(*mockDevice, prepareModelFromCache_1_3(_, _, _, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makePreparedModelFromCacheReturn(
+ V1_3::ErrorStatus::NONE, V1_3::ErrorStatus::NONE, mockPreparedModel)));
+
+ // run test
+ const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ EXPECT_NE(result.value(), nullptr);
+}
+
+TEST(DeviceTest, prepareModelFromCacheError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModelFromCache_1_3(_, _, _, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makePreparedModelFromCacheReturn(V1_3::ErrorStatus::GENERAL_FAILURE,
+ V1_3::ErrorStatus::GENERAL_FAILURE,
+ nullptr)));
+
+ // run test
+ const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelFromCacheNullptrError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModelFromCache_1_3(_, _, _, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makePreparedModelFromCacheReturn(V1_3::ErrorStatus::NONE,
+ V1_3::ErrorStatus::NONE, nullptr)));
+
+ // run test
+ const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelFromCacheTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModelFromCache_1_3(_, _, _, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelFromCacheDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModelFromCache_1_3(_, _, _, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, prepareModelFromCacheAsyncCrash) {
+ // setup test
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ const auto ret = [&mockDevice]() -> hardware::Return<V1_3::ErrorStatus> {
+ mockDevice->simulateCrash();
+ return V1_3::ErrorStatus::NONE;
+ };
+ EXPECT_CALL(*mockDevice, prepareModelFromCache_1_3(_, _, _, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(ret));
+
+ // run test
+ const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, allocate) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ const auto mockBuffer = MockBuffer::create();
+ constexpr uint32_t token = 1;
+ EXPECT_CALL(*mockDevice, allocate(_, _, _, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makeAllocateReturn(ErrorStatus::NONE, mockBuffer, token)));
+
+ // run test
+ const auto result = device->allocate({}, {}, {}, {});
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ EXPECT_NE(result.value(), nullptr);
+}
+
+TEST(DeviceTest, allocateError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, allocate(_, _, _, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makeAllocateReturn(ErrorStatus::GENERAL_FAILURE, nullptr, 0)));
+
+ // run test
+ const auto result = device->allocate({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, allocateTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, allocate(_, _, _, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = device->allocate({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, allocateDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, allocate(_, _, _, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = device->allocate({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+} // namespace android::hardware::neuralnetworks::V1_3::utils
diff --git a/neuralnetworks/1.3/utils/test/MockBuffer.h b/neuralnetworks/1.3/utils/test/MockBuffer.h
new file mode 100644
index 0000000..fb31b51
--- /dev/null
+++ b/neuralnetworks/1.3/utils/test/MockBuffer.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_BUFFER
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_BUFFER
+
+#include <android/hardware/neuralnetworks/1.3/IBuffer.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <hidl/Status.h>
+
+namespace android::hardware::neuralnetworks::V1_3::utils {
+
+class MockBuffer final : public IBuffer {
+ public:
+ static sp<MockBuffer> create();
+
+ // V1_3 methods below.
+ MOCK_METHOD(Return<V1_3::ErrorStatus>, copyTo, (const hidl_memory& dst), (override));
+ MOCK_METHOD(Return<V1_3::ErrorStatus>, copyFrom,
+ (const hidl_memory& src, const hidl_vec<uint32_t>& dimensions), (override));
+};
+
+inline sp<MockBuffer> MockBuffer::create() {
+ return sp<MockBuffer>::make();
+}
+
+} // namespace android::hardware::neuralnetworks::V1_3::utils
+
+#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_BUFFER
diff --git a/neuralnetworks/1.3/utils/test/MockDevice.h b/neuralnetworks/1.3/utils/test/MockDevice.h
new file mode 100644
index 0000000..85d3750
--- /dev/null
+++ b/neuralnetworks/1.3/utils/test/MockDevice.h
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_DEVICE
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_DEVICE
+
+#include <android/hardware/neuralnetworks/1.3/IDevice.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <hidl/Status.h>
+
+namespace android::hardware::neuralnetworks::V1_3::utils {
+
+using CacheToken =
+ hidl_array<uint8_t, static_cast<uint32_t>(V1_2::Constant::BYTE_SIZE_OF_CACHE_TOKEN)>;
+
+class MockDevice final : public IDevice {
+ public:
+ static sp<MockDevice> create();
+
+ // IBase methods below.
+ MOCK_METHOD(Return<void>, ping, (), (override));
+ MOCK_METHOD(Return<bool>, linkToDeathRet, ());
+ Return<bool> linkToDeath(const sp<hidl_death_recipient>& recipient, uint64_t /*cookie*/);
+
+ // V1_0 methods below.
+ MOCK_METHOD(Return<void>, getCapabilities, (getCapabilities_cb cb), (override));
+ MOCK_METHOD(Return<void>, getSupportedOperations,
+ (const V1_0::Model& model, getSupportedOperations_cb cb), (override));
+ MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModel,
+ (const V1_0::Model& model, const sp<V1_0::IPreparedModelCallback>& callback),
+ (override));
+ MOCK_METHOD(Return<V1_0::DeviceStatus>, getStatus, (), (override));
+
+ // V1_1 methods below.
+ MOCK_METHOD(Return<void>, getCapabilities_1_1, (getCapabilities_1_1_cb cb), (override));
+ MOCK_METHOD(Return<void>, getSupportedOperations_1_1,
+ (const V1_1::Model& model, getSupportedOperations_1_1_cb cb), (override));
+ MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModel_1_1,
+ (const V1_1::Model& model, V1_1::ExecutionPreference preference,
+ const sp<V1_0::IPreparedModelCallback>& callback),
+ (override));
+
+ // V1_2 methods below.
+ MOCK_METHOD(Return<void>, getVersionString, (getVersionString_cb cb), (override));
+ MOCK_METHOD(Return<void>, getType, (getType_cb cb), (override));
+ MOCK_METHOD(Return<void>, getCapabilities_1_2, (getCapabilities_1_2_cb cb), (override));
+ MOCK_METHOD(Return<void>, getSupportedExtensions, (getSupportedExtensions_cb cb), (override));
+ MOCK_METHOD(Return<void>, getSupportedOperations_1_2,
+ (const V1_2::Model& model, getSupportedOperations_1_2_cb cb), (override));
+ MOCK_METHOD(Return<void>, getNumberOfCacheFilesNeeded, (getNumberOfCacheFilesNeeded_cb cb),
+ (override));
+ MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModel_1_2,
+ (const V1_2::Model& model, V1_1::ExecutionPreference preference,
+ const hidl_vec<hidl_handle>& modelCache, const hidl_vec<hidl_handle>& dataCache,
+ const CacheToken& token, const sp<V1_2::IPreparedModelCallback>& callback),
+ (override));
+ MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModelFromCache,
+ (const hidl_vec<hidl_handle>& modelCache, const hidl_vec<hidl_handle>& dataCache,
+ const CacheToken& token, const sp<V1_2::IPreparedModelCallback>& callback),
+ (override));
+
+ // V1_3 methods below.
+ MOCK_METHOD(Return<void>, getCapabilities_1_3, (getCapabilities_1_3_cb cb), (override));
+ MOCK_METHOD(Return<void>, getSupportedOperations_1_3,
+ (const V1_3::Model& model, getSupportedOperations_1_3_cb cb), (override));
+ MOCK_METHOD(Return<V1_3::ErrorStatus>, prepareModel_1_3,
+ (const V1_3::Model& model, V1_1::ExecutionPreference preference,
+ V1_3::Priority priority, const V1_3::OptionalTimePoint& deadline,
+ const hidl_vec<hidl_handle>& modelCache, const hidl_vec<hidl_handle>& dataCache,
+ const CacheToken& token, const sp<V1_3::IPreparedModelCallback>& callback),
+ (override));
+ MOCK_METHOD(Return<V1_3::ErrorStatus>, prepareModelFromCache_1_3,
+ (const V1_3::OptionalTimePoint& deadline, const hidl_vec<hidl_handle>& modelCache,
+ const hidl_vec<hidl_handle>& dataCache, const CacheToken& token,
+ const sp<V1_3::IPreparedModelCallback>& callback),
+ (override));
+ MOCK_METHOD(Return<void>, allocate,
+ (const V1_3::BufferDesc& desc,
+ const hidl_vec<sp<V1_3::IPreparedModel>>& preparedModels,
+ const hidl_vec<V1_3::BufferRole>& inputRoles,
+ const hidl_vec<V1_3::BufferRole>& outputRoles, allocate_cb cb),
+ (override));
+
+ // Helper methods.
+ void simulateCrash();
+
+ private:
+ sp<hidl_death_recipient> mDeathRecipient;
+};
+
+inline sp<MockDevice> MockDevice::create() {
+ auto mockDevice = sp<MockDevice>::make();
+
+ // Setup default actions for each relevant call.
+ const auto ret = []() -> Return<bool> { return true; };
+
+ // Setup default actions for each relevant call.
+ ON_CALL(*mockDevice, linkToDeathRet()).WillByDefault(testing::Invoke(ret));
+
+ // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the
+ // uninteresting methods calls.
+ EXPECT_CALL(*mockDevice, linkToDeathRet()).Times(testing::AnyNumber());
+
+ return mockDevice;
+}
+
+inline Return<bool> MockDevice::linkToDeath(const sp<hidl_death_recipient>& recipient,
+ uint64_t /*cookie*/) {
+ mDeathRecipient = recipient;
+ return linkToDeathRet();
+}
+
+inline void MockDevice::simulateCrash() {
+ ASSERT_NE(nullptr, mDeathRecipient.get());
+
+ // Currently, the utils::Device will not use the `cookie` or `who` arguments, so we pass in 0
+ // and nullptr for these arguments instead. Normally, they are used by the hidl_death_recipient
+ // to determine which object is dead. However, the utils::Device code only pairs a single death
+ // recipient with a single HIDL interface object, so these arguments are redundant.
+ mDeathRecipient->serviceDied(0, nullptr);
+}
+
+} // namespace android::hardware::neuralnetworks::V1_3::utils
+
+#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_DEVICE
diff --git a/neuralnetworks/1.3/utils/test/MockFencedExecutionCallback.h b/neuralnetworks/1.3/utils/test/MockFencedExecutionCallback.h
new file mode 100644
index 0000000..fc08a7f
--- /dev/null
+++ b/neuralnetworks/1.3/utils/test/MockFencedExecutionCallback.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_FENCED_EXECUTION_CALLBACK
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_FENCED_EXECUTION_CALLBACK
+
+#include <android/hardware/neuralnetworks/1.3/IFencedExecutionCallback.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <hidl/Status.h>
+
+namespace android::hardware::neuralnetworks::V1_3::utils {
+
+class MockFencedExecutionCallback final : public IFencedExecutionCallback {
+ public:
+ static sp<MockFencedExecutionCallback> create();
+
+ // V1_3 methods below.
+ MOCK_METHOD(Return<void>, getExecutionInfo, (IFencedExecutionCallback::getExecutionInfo_cb cb),
+ (override));
+};
+
+inline sp<MockFencedExecutionCallback> MockFencedExecutionCallback::create() {
+ return sp<MockFencedExecutionCallback>::make();
+}
+
+} // namespace android::hardware::neuralnetworks::V1_3::utils
+
+#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_FENCED_EXECUTION_CALLBACK
diff --git a/neuralnetworks/1.3/utils/test/MockPreparedModel.h b/neuralnetworks/1.3/utils/test/MockPreparedModel.h
new file mode 100644
index 0000000..e441524
--- /dev/null
+++ b/neuralnetworks/1.3/utils/test/MockPreparedModel.h
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_PREPARED_MODEL
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_PREPARED_MODEL
+
+#include <android/hardware/neuralnetworks/1.3/IPreparedModel.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <hidl/HidlSupport.h>
+#include <hidl/Status.h>
+
+namespace android::hardware::neuralnetworks::V1_3::utils {
+
+class MockPreparedModel final : public IPreparedModel {
+ public:
+ static sp<MockPreparedModel> create();
+
+ // IBase methods below.
+ MOCK_METHOD(Return<void>, ping, (), (override));
+ MOCK_METHOD(Return<bool>, linkToDeathRet, ());
+ Return<bool> linkToDeath(const sp<hidl_death_recipient>& recipient,
+ uint64_t /*cookie*/) override;
+
+ // V1_0 methods below.
+ MOCK_METHOD(Return<V1_0::ErrorStatus>, execute,
+ (const V1_0::Request& request, const sp<V1_0::IExecutionCallback>& callback),
+ (override));
+
+ // V1_2 methods below.
+ MOCK_METHOD(Return<V1_0::ErrorStatus>, execute_1_2,
+ (const V1_0::Request& request, V1_2::MeasureTiming measure,
+ const sp<V1_2::IExecutionCallback>& callback),
+ (override));
+ MOCK_METHOD(Return<void>, executeSynchronously,
+ (const V1_0::Request& request, V1_2::MeasureTiming measure,
+ executeSynchronously_cb cb),
+ (override));
+ MOCK_METHOD(Return<void>, configureExecutionBurst,
+ (const sp<V1_2::IBurstCallback>& callback,
+ const MQDescriptorSync<V1_2::FmqRequestDatum>& requestChannel,
+ const MQDescriptorSync<V1_2::FmqResultDatum>& resultChannel,
+ configureExecutionBurst_cb cb),
+ (override));
+
+ // V1_3 methods below.
+ MOCK_METHOD(Return<V1_3::ErrorStatus>, execute_1_3,
+ (const V1_3::Request& request, V1_2::MeasureTiming measure,
+ const V1_3::OptionalTimePoint& deadline,
+ const V1_3::OptionalTimeoutDuration& loopTimeoutDuration,
+ const sp<V1_3::IExecutionCallback>& callback),
+ (override));
+ MOCK_METHOD(Return<void>, executeSynchronously_1_3,
+ (const V1_3::Request& request, V1_2::MeasureTiming measure,
+ const V1_3::OptionalTimePoint& deadline,
+ const V1_3::OptionalTimeoutDuration& loopTimeoutDuration,
+ executeSynchronously_1_3_cb cb),
+ (override));
+ MOCK_METHOD(Return<void>, executeFenced,
+ (const V1_3::Request& request, const hidl_vec<hidl_handle>& waitFor,
+ V1_2::MeasureTiming measure, const V1_3::OptionalTimePoint& deadline,
+ const V1_3::OptionalTimeoutDuration& loopTimeoutDuration,
+ const V1_3::OptionalTimeoutDuration& duration, executeFenced_cb cb),
+ (override));
+
+ // Helper methods.
+ void simulateCrash();
+
+ private:
+ sp<hidl_death_recipient> mDeathRecipient;
+};
+
+inline sp<MockPreparedModel> MockPreparedModel::create() {
+ auto mockPreparedModel = sp<MockPreparedModel>::make();
+
+ // Setup default actions for each relevant call.
+ const auto ret = []() -> Return<bool> { return true; };
+
+ // Setup default actions for each relevant call.
+ ON_CALL(*mockPreparedModel, linkToDeathRet()).WillByDefault(testing::Invoke(ret));
+
+ // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the
+ // uninteresting methods calls.
+ EXPECT_CALL(*mockPreparedModel, linkToDeathRet()).Times(testing::AnyNumber());
+
+ return mockPreparedModel;
+}
+
+inline Return<bool> MockPreparedModel::linkToDeath(const sp<hidl_death_recipient>& recipient,
+ uint64_t /*cookie*/) {
+ mDeathRecipient = recipient;
+ return linkToDeathRet();
+}
+
+inline void MockPreparedModel::simulateCrash() {
+ ASSERT_NE(nullptr, mDeathRecipient.get());
+
+ // Currently, the utils::PreparedModel will not use the `cookie` or `who` arguments, so we pass
+ // in 0 and nullptr for these arguments instead. Normally, they are used by the
+ // hidl_death_recipient to determine which object is dead. However, the utils::PreparedModel
+ // code only pairs a single death recipient with a single HIDL interface object, so these
+ // arguments are redundant.
+ mDeathRecipient->serviceDied(0, nullptr);
+}
+
+} // namespace android::hardware::neuralnetworks::V1_3::utils
+
+#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_UTILS_TEST_MOCK_PREPARED_MODEL
diff --git a/neuralnetworks/1.3/utils/test/PreparedModelTest.cpp b/neuralnetworks/1.3/utils/test/PreparedModelTest.cpp
new file mode 100644
index 0000000..11796dd
--- /dev/null
+++ b/neuralnetworks/1.3/utils/test/PreparedModelTest.cpp
@@ -0,0 +1,470 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "MockFencedExecutionCallback.h"
+#include "MockPreparedModel.h"
+
+#include <android/hardware/neuralnetworks/1.3/IExecutionCallback.h>
+#include <android/hardware/neuralnetworks/1.3/IFencedExecutionCallback.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <nnapi/IPreparedModel.h>
+#include <nnapi/TypeUtils.h>
+#include <nnapi/Types.h>
+#include <nnapi/hal/1.3/PreparedModel.h>
+
+#include <functional>
+#include <memory>
+
+namespace android::hardware::neuralnetworks::V1_3::utils {
+namespace {
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::InvokeWithoutArgs;
+
+const sp<V1_3::IPreparedModel> kInvalidPreparedModel;
+constexpr auto kNoTiming = V1_2::Timing{.timeOnDevice = std::numeric_limits<uint64_t>::max(),
+ .timeInDriver = std::numeric_limits<uint64_t>::max()};
+
+sp<MockPreparedModel> createMockPreparedModel() {
+ const auto mockPreparedModel = MockPreparedModel::create();
+
+ // Ensure that older calls are not used.
+ EXPECT_CALL(*mockPreparedModel, execute(_, _)).Times(0);
+ EXPECT_CALL(*mockPreparedModel, execute_1_2(_, _, _)).Times(0);
+ EXPECT_CALL(*mockPreparedModel, executeSynchronously(_, _, _)).Times(0);
+
+ return mockPreparedModel;
+}
+
+auto makeExecuteSynchronously(V1_3::ErrorStatus status,
+ const std::vector<V1_2::OutputShape>& outputShapes,
+ const V1_2::Timing& timing) {
+ return [status, outputShapes, timing](
+ const V1_3::Request& /*request*/, V1_2::MeasureTiming /*measureTiming*/,
+ const V1_3::OptionalTimePoint& /*deadline*/,
+ const V1_3::OptionalTimeoutDuration& /*loopTimeoutDuration*/,
+ const V1_3::IPreparedModel::executeSynchronously_1_3_cb& cb) {
+ cb(status, outputShapes, timing);
+ return hardware::Void();
+ };
+}
+auto makeExecuteAsynchronously(V1_3::ErrorStatus launchStatus, V1_3::ErrorStatus returnStatus,
+ const std::vector<V1_2::OutputShape>& outputShapes,
+ const V1_2::Timing& timing) {
+ return [launchStatus, returnStatus, outputShapes, timing](
+ const V1_3::Request& /*request*/, V1_2::MeasureTiming /*measureTiming*/,
+ const V1_3::OptionalTimePoint& /*deadline*/,
+ const V1_3::OptionalTimeoutDuration& /*loopTimeoutDuration*/,
+ const sp<V1_3::IExecutionCallback>& cb) -> Return<V1_3::ErrorStatus> {
+ cb->notify_1_3(returnStatus, outputShapes, timing);
+ return launchStatus;
+ };
+}
+auto makeExecuteFencedReturn(V1_3::ErrorStatus status, const hardware::hidl_handle& syncFence,
+ const sp<V1_3::IFencedExecutionCallback>& dispatchCallback) {
+ return [status, syncFence, dispatchCallback](
+ const V1_3::Request& /*request*/,
+ const hardware::hidl_vec<hardware::hidl_handle>& /*waitFor*/,
+ V1_2::MeasureTiming /*measure*/, const V1_3::OptionalTimePoint& /*deadline*/,
+ const V1_3::OptionalTimeoutDuration& /*loopTimeoutDuration*/,
+ const V1_3::OptionalTimeoutDuration& /*duration*/,
+ const V1_3::IPreparedModel::executeFenced_cb& cb) {
+ cb(status, syncFence, dispatchCallback);
+ return hardware::Void();
+ };
+}
+auto makeExecuteFencedCallbackReturn(V1_3::ErrorStatus status, const V1_2::Timing& timingA,
+ const V1_2::Timing& timingB) {
+ return [status, timingA,
+ timingB](const V1_3::IFencedExecutionCallback::getExecutionInfo_cb& cb) {
+ cb(status, timingA, timingB);
+ return hardware::Void();
+ };
+}
+
+std::function<hardware::Status()> makeTransportFailure(status_t status) {
+ return [status] { return hardware::Status::fromStatusT(status); };
+}
+
+const auto makeGeneralTransportFailure = makeTransportFailure(NO_MEMORY);
+const auto makeDeadObjectFailure = makeTransportFailure(DEAD_OBJECT);
+
+} // namespace
+
+TEST(PreparedModelTest, invalidPreparedModel) {
+ // run test
+ const auto result = PreparedModel::create(kInvalidPreparedModel, /*executeSynchronously=*/true);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, linkToDeathError) {
+ // setup call
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto ret = []() -> Return<bool> { return false; };
+ EXPECT_CALL(*mockPreparedModel, linkToDeathRet()).Times(1).WillOnce(InvokeWithoutArgs(ret));
+
+ // run test
+ const auto result = PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, linkToDeathTransportFailure) {
+ // setup call
+ const auto mockPreparedModel = createMockPreparedModel();
+ EXPECT_CALL(*mockPreparedModel, linkToDeathRet())
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, linkToDeathDeadObject) {
+ // setup call
+ const auto mockPreparedModel = createMockPreparedModel();
+ EXPECT_CALL(*mockPreparedModel, linkToDeathRet())
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(PreparedModelTest, executeSync) {
+ // setup call
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+ EXPECT_CALL(*mockPreparedModel, executeSynchronously_1_3(_, _, _, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makeExecuteSynchronously(V1_3::ErrorStatus::NONE, {}, kNoTiming)));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ EXPECT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(PreparedModelTest, executeSyncError) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+ EXPECT_CALL(*mockPreparedModel, executeSynchronously_1_3(_, _, _, _, _))
+ .Times(1)
+ .WillOnce(Invoke(
+ makeExecuteSynchronously(V1_3::ErrorStatus::GENERAL_FAILURE, {}, kNoTiming)));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeSyncTransportFailure) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+ EXPECT_CALL(*mockPreparedModel, executeSynchronously_1_3(_, _, _, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeSyncDeadObject) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+ EXPECT_CALL(*mockPreparedModel, executeSynchronously_1_3(_, _, _, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(PreparedModelTest, executeAsync) {
+ // setup call
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value();
+ EXPECT_CALL(*mockPreparedModel, execute_1_3(_, _, _, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makeExecuteAsynchronously(V1_3::ErrorStatus::NONE,
+ V1_3::ErrorStatus::NONE, {}, kNoTiming)));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ EXPECT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(PreparedModelTest, executeAsyncLaunchError) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value();
+ EXPECT_CALL(*mockPreparedModel, execute_1_3(_, _, _, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makeExecuteAsynchronously(V1_3::ErrorStatus::GENERAL_FAILURE,
+ V1_3::ErrorStatus::GENERAL_FAILURE, {},
+ kNoTiming)));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeAsyncReturnError) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value();
+ EXPECT_CALL(*mockPreparedModel, execute_1_3(_, _, _, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makeExecuteAsynchronously(
+ V1_3::ErrorStatus::NONE, V1_3::ErrorStatus::GENERAL_FAILURE, {}, kNoTiming)));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeAsyncTransportFailure) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value();
+ EXPECT_CALL(*mockPreparedModel, execute_1_3(_, _, _, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeAsyncDeadObject) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value();
+ EXPECT_CALL(*mockPreparedModel, execute_1_3(_, _, _, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(PreparedModelTest, executeAsyncCrash) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value();
+ const auto ret = [&mockPreparedModel]() -> hardware::Return<V1_3::ErrorStatus> {
+ mockPreparedModel->simulateCrash();
+ return V1_3::ErrorStatus::NONE;
+ };
+ EXPECT_CALL(*mockPreparedModel, execute_1_3(_, _, _, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(ret));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(PreparedModelTest, executeFenced) {
+ // setup call
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+ const auto mockCallback = MockFencedExecutionCallback::create();
+ EXPECT_CALL(*mockCallback, getExecutionInfo(_))
+ .Times(1)
+ .WillOnce(Invoke(makeExecuteFencedCallbackReturn(V1_3::ErrorStatus::NONE, kNoTiming,
+ kNoTiming)));
+ EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makeExecuteFencedReturn(V1_3::ErrorStatus::NONE, {}, mockCallback)));
+
+ // run test
+ const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ const auto& [syncFence, callback] = result.value();
+ EXPECT_EQ(syncFence.syncWait({}), nn::SyncFence::FenceState::SIGNALED);
+ ASSERT_NE(callback, nullptr);
+
+ // get results from callback
+ const auto callbackResult = callback();
+ ASSERT_TRUE(callbackResult.has_value()) << "Failed with " << callbackResult.error().code << ": "
+ << callbackResult.error().message;
+}
+
+TEST(PreparedModelTest, executeFencedCallbackError) {
+ // setup call
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+ const auto mockCallback = MockFencedExecutionCallback::create();
+ EXPECT_CALL(*mockCallback, getExecutionInfo(_))
+ .Times(1)
+ .WillOnce(Invoke(makeExecuteFencedCallbackReturn(V1_3::ErrorStatus::GENERAL_FAILURE,
+ kNoTiming, kNoTiming)));
+ EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makeExecuteFencedReturn(V1_3::ErrorStatus::NONE, {}, mockCallback)));
+
+ // run test
+ const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ const auto& [syncFence, callback] = result.value();
+ EXPECT_NE(syncFence.syncWait({}), nn::SyncFence::FenceState::ACTIVE);
+ ASSERT_NE(callback, nullptr);
+
+ // verify callback failure
+ const auto callbackResult = callback();
+ ASSERT_FALSE(callbackResult.has_value());
+ EXPECT_EQ(callbackResult.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeFencedError) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+ EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(Invoke(
+ makeExecuteFencedReturn(V1_3::ErrorStatus::GENERAL_FAILURE, {}, nullptr)));
+
+ // run test
+ const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeFencedTransportFailure) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+ EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeFencedDeadObject) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+ EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+// TODO: test burst execution if/when it is added to nn::IPreparedModel.
+
+TEST(PreparedModelTest, getUnderlyingResource) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+
+ // run test
+ const auto resource = preparedModel->getUnderlyingResource();
+
+ // verify resource
+ const sp<V1_3::IPreparedModel>* maybeMock = std::any_cast<sp<V1_3::IPreparedModel>>(&resource);
+ ASSERT_NE(maybeMock, nullptr);
+ EXPECT_EQ(maybeMock->get(), mockPreparedModel.get());
+}
+
+} // namespace android::hardware::neuralnetworks::V1_3::utils
diff --git a/neuralnetworks/1.3/vts/functional/Android.bp b/neuralnetworks/1.3/vts/functional/Android.bp
index b17d445..ee753bb 100644
--- a/neuralnetworks/1.3/vts/functional/Android.bp
+++ b/neuralnetworks/1.3/vts/functional/Android.bp
@@ -57,6 +57,7 @@
"VtsHalNeuralNetworksV1_0_utils",
"VtsHalNeuralNetworksV1_2_utils",
"VtsHalNeuralNetworksV1_3_utils",
+ "android.hardware.neuralnetworks-V1-ndk_platform",
"android.hardware.neuralnetworks@1.0",
"android.hardware.neuralnetworks@1.1",
"android.hardware.neuralnetworks@1.2",
diff --git a/neuralnetworks/TEST_MAPPING b/neuralnetworks/TEST_MAPPING
index ca5041d..de84624 100644
--- a/neuralnetworks/TEST_MAPPING
+++ b/neuralnetworks/TEST_MAPPING
@@ -1,6 +1,21 @@
{
"presubmit": [
{
+ "name": "neuralnetworks_utils_hal_common_test"
+ },
+ {
+ "name": "neuralnetworks_utils_hal_1_0_test"
+ },
+ {
+ "name": "neuralnetworks_utils_hal_1_1_test"
+ },
+ {
+ "name": "neuralnetworks_utils_hal_1_2_test"
+ },
+ {
+ "name": "neuralnetworks_utils_hal_1_3_test"
+ },
+ {
"name": "VtsHalNeuralnetworksV1_0TargetTest",
"options": [
{
diff --git a/neuralnetworks/aidl/Android.bp b/neuralnetworks/aidl/Android.bp
new file mode 100644
index 0000000..0557e43
--- /dev/null
+++ b/neuralnetworks/aidl/Android.bp
@@ -0,0 +1,27 @@
+aidl_interface {
+ name: "android.hardware.neuralnetworks",
+ vendor_available: true,
+ srcs: [
+ "android/hardware/neuralnetworks/*.aidl",
+ ],
+ stability: "vintf",
+ imports: [
+ "android.hardware.common",
+ ],
+ backend: {
+ java: {
+ enabled: false,
+ },
+ cpp: {
+ enabled: false,
+ },
+ ndk: {
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.neuralnetworks",
+ "test_com.android.neuralnetworks",
+ ],
+ min_sdk_version: "30",
+ },
+ },
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/BufferDesc.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/BufferDesc.aidl
new file mode 100644
index 0000000..71b7758
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/BufferDesc.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@VintfStability
+parcelable BufferDesc {
+ int[] dimensions;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/BufferRole.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/BufferRole.aidl
new file mode 100644
index 0000000..c2d636c
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/BufferRole.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@VintfStability
+parcelable BufferRole {
+ int modelIndex;
+ int ioIndex;
+ float frequency;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Capabilities.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Capabilities.aidl
new file mode 100644
index 0000000..01cc753
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Capabilities.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@VintfStability
+parcelable Capabilities {
+ android.hardware.neuralnetworks.PerformanceInfo relaxedFloat32toFloat16PerformanceScalar;
+ android.hardware.neuralnetworks.PerformanceInfo relaxedFloat32toFloat16PerformanceTensor;
+ android.hardware.neuralnetworks.OperandPerformance[] operandPerformance;
+ android.hardware.neuralnetworks.PerformanceInfo ifPerformance;
+ android.hardware.neuralnetworks.PerformanceInfo whilePerformance;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/DataLocation.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/DataLocation.aidl
new file mode 100644
index 0000000..074cc09
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/DataLocation.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@VintfStability
+parcelable DataLocation {
+ int poolIndex;
+ long offset;
+ long length;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/DeviceBuffer.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/DeviceBuffer.aidl
new file mode 100644
index 0000000..7bc8aa7
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/DeviceBuffer.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@VintfStability
+parcelable DeviceBuffer {
+ android.hardware.neuralnetworks.IBuffer buffer;
+ int token;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/DeviceType.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/DeviceType.aidl
new file mode 100644
index 0000000..1abacc8
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/DeviceType.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@Backing(type="int") @VintfStability
+enum DeviceType {
+ OTHER = 1,
+ CPU = 2,
+ GPU = 3,
+ ACCELERATOR = 4,
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ErrorStatus.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ErrorStatus.aidl
new file mode 100644
index 0000000..873c584
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ErrorStatus.aidl
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@Backing(type="int") @VintfStability
+enum ErrorStatus {
+ NONE = 0,
+ DEVICE_UNAVAILABLE = 1,
+ GENERAL_FAILURE = 2,
+ OUTPUT_INSUFFICIENT_SIZE = 3,
+ INVALID_ARGUMENT = 4,
+ MISSED_DEADLINE_TRANSIENT = 5,
+ MISSED_DEADLINE_PERSISTENT = 6,
+ RESOURCE_EXHAUSTED_TRANSIENT = 7,
+ RESOURCE_EXHAUSTED_PERSISTENT = 8,
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExecutionPreference.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExecutionPreference.aidl
new file mode 100644
index 0000000..c4badc0
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExecutionPreference.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@Backing(type="int") @VintfStability
+enum ExecutionPreference {
+ LOW_POWER = 0,
+ FAST_SINGLE_ANSWER = 1,
+ SUSTAINED_SPEED = 2,
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExecutionResult.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExecutionResult.aidl
new file mode 100644
index 0000000..b99bb31
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExecutionResult.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 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.neuralnetworks;
+@VintfStability
+parcelable ExecutionResult {
+ boolean outputSufficientSize;
+ android.hardware.neuralnetworks.OutputShape[] outputShapes;
+ android.hardware.neuralnetworks.Timing timing;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Extension.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Extension.aidl
new file mode 100644
index 0000000..a7ae942
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Extension.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@VintfStability
+parcelable Extension {
+ String name;
+ android.hardware.neuralnetworks.ExtensionOperandTypeInformation[] operandTypes;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExtensionNameAndPrefix.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExtensionNameAndPrefix.aidl
new file mode 100644
index 0000000..4c25538
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExtensionNameAndPrefix.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@VintfStability
+parcelable ExtensionNameAndPrefix {
+ String name;
+ char prefix;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExtensionOperandTypeInformation.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExtensionOperandTypeInformation.aidl
new file mode 100644
index 0000000..b32b217
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/ExtensionOperandTypeInformation.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@VintfStability
+parcelable ExtensionOperandTypeInformation {
+ char type;
+ boolean isTensor;
+ int byteSize;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/FusedActivationFunc.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/FusedActivationFunc.aidl
new file mode 100644
index 0000000..2fee136
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/FusedActivationFunc.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@Backing(type="int") @VintfStability
+enum FusedActivationFunc {
+ NONE = 0,
+ RELU = 1,
+ RELU1 = 2,
+ RELU6 = 3,
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IBuffer.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IBuffer.aidl
new file mode 100644
index 0000000..2860692
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IBuffer.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@VintfStability
+interface IBuffer {
+ void copyFrom(in android.hardware.neuralnetworks.Memory src, in int[] dimensions);
+ void copyTo(in android.hardware.neuralnetworks.Memory dst);
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IDevice.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IDevice.aidl
new file mode 100644
index 0000000..4c5fd2f
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IDevice.aidl
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@VintfStability
+interface IDevice {
+ android.hardware.neuralnetworks.DeviceBuffer allocate(in android.hardware.neuralnetworks.BufferDesc desc, in android.hardware.neuralnetworks.IPreparedModelParcel[] preparedModels, in android.hardware.neuralnetworks.BufferRole[] inputRoles, in android.hardware.neuralnetworks.BufferRole[] outputRoles);
+ android.hardware.neuralnetworks.Capabilities getCapabilities();
+ android.hardware.neuralnetworks.NumberOfCacheFiles getNumberOfCacheFilesNeeded();
+ android.hardware.neuralnetworks.Extension[] getSupportedExtensions();
+ boolean[] getSupportedOperations(in android.hardware.neuralnetworks.Model model);
+ android.hardware.neuralnetworks.DeviceType getType();
+ String getVersionString();
+ void prepareModel(in android.hardware.neuralnetworks.Model model, in android.hardware.neuralnetworks.ExecutionPreference preference, in android.hardware.neuralnetworks.Priority priority, in long deadline, in ParcelFileDescriptor[] modelCache, in ParcelFileDescriptor[] dataCache, in byte[] token, in android.hardware.neuralnetworks.IPreparedModelCallback callback);
+ void prepareModelFromCache(in long deadline, in ParcelFileDescriptor[] modelCache, in ParcelFileDescriptor[] dataCache, in byte[] token, in android.hardware.neuralnetworks.IPreparedModelCallback callback);
+ const int BYTE_SIZE_OF_CACHE_TOKEN = 32;
+ const int MAX_NUMBER_OF_CACHE_FILES = 32;
+ const int EXTENSION_TYPE_HIGH_BITS_PREFIX = 15;
+ const int EXTENSION_TYPE_LOW_BITS_TYPE = 16;
+ const int OPERAND_TYPE_BASE_MAX = 65535;
+ const int OPERATION_TYPE_BASE_MAX = 65535;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IFencedExecutionCallback.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IFencedExecutionCallback.aidl
new file mode 100644
index 0000000..abe67b8
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IFencedExecutionCallback.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@VintfStability
+interface IFencedExecutionCallback {
+ android.hardware.neuralnetworks.ErrorStatus getExecutionInfo(out android.hardware.neuralnetworks.Timing timingLaunched, out android.hardware.neuralnetworks.Timing timingFenced);
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IPreparedModel.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IPreparedModel.aidl
new file mode 100644
index 0000000..3ca1550
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IPreparedModel.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@VintfStability
+interface IPreparedModel {
+ android.hardware.neuralnetworks.ExecutionResult executeSynchronously(in android.hardware.neuralnetworks.Request request, in boolean measureTiming, in long deadline, in long loopTimeoutDuration);
+ android.hardware.neuralnetworks.IFencedExecutionCallback executeFenced(in android.hardware.neuralnetworks.Request request, in ParcelFileDescriptor[] waitFor, in boolean measureTiming, in long deadline, in long loopTimeoutDuration, in long duration, out @nullable ParcelFileDescriptor syncFence);
+ const long DEFAULT_LOOP_TIMEOUT_DURATION_NS = 2000000000;
+ const long MAXIMUM_LOOP_TIMEOUT_DURATION_NS = 15000000000;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IPreparedModelCallback.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IPreparedModelCallback.aidl
new file mode 100644
index 0000000..8eaaab6
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IPreparedModelCallback.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@VintfStability
+interface IPreparedModelCallback {
+ void notify(in android.hardware.neuralnetworks.ErrorStatus status, in android.hardware.neuralnetworks.IPreparedModel preparedModel);
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IPreparedModelParcel.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IPreparedModelParcel.aidl
new file mode 100644
index 0000000..8388fda
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/IPreparedModelParcel.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@VintfStability
+parcelable IPreparedModelParcel {
+ android.hardware.neuralnetworks.IPreparedModel preparedModel;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Memory.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Memory.aidl
new file mode 100644
index 0000000..3b2f240
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Memory.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@VintfStability
+parcelable Memory {
+ android.hardware.common.NativeHandle handle;
+ long size;
+ String name;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Model.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Model.aidl
new file mode 100644
index 0000000..9d12e58
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Model.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@VintfStability
+parcelable Model {
+ android.hardware.neuralnetworks.Subgraph main;
+ android.hardware.neuralnetworks.Subgraph[] referenced;
+ byte[] operandValues;
+ android.hardware.neuralnetworks.Memory[] pools;
+ boolean relaxComputationFloat32toFloat16;
+ android.hardware.neuralnetworks.ExtensionNameAndPrefix[] extensionNameToPrefix;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/NumberOfCacheFiles.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/NumberOfCacheFiles.aidl
new file mode 100644
index 0000000..c1e87da
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/NumberOfCacheFiles.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@VintfStability
+parcelable NumberOfCacheFiles {
+ int numModelCache;
+ int numDataCache;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Operand.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Operand.aidl
new file mode 100644
index 0000000..bb78caa
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Operand.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@VintfStability
+parcelable Operand {
+ android.hardware.neuralnetworks.OperandType type;
+ int[] dimensions;
+ float scale;
+ int zeroPoint;
+ android.hardware.neuralnetworks.OperandLifeTime lifetime;
+ android.hardware.neuralnetworks.DataLocation location;
+ @nullable android.hardware.neuralnetworks.OperandExtraParams extraParams;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandExtraParams.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandExtraParams.aidl
new file mode 100644
index 0000000..3f6d93b
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandExtraParams.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@VintfStability
+union OperandExtraParams {
+ android.hardware.neuralnetworks.SymmPerChannelQuantParams channelQuant;
+ byte[] extension;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandLifeTime.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandLifeTime.aidl
new file mode 100644
index 0000000..d581ced
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandLifeTime.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@Backing(type="int") @VintfStability
+enum OperandLifeTime {
+ TEMPORARY_VARIABLE = 0,
+ SUBGRAPH_INPUT = 1,
+ SUBGRAPH_OUTPUT = 2,
+ CONSTANT_COPY = 3,
+ CONSTANT_POOL = 4,
+ NO_VALUE = 5,
+ SUBGRAPH = 6,
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandPerformance.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandPerformance.aidl
new file mode 100644
index 0000000..87fd3a6
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandPerformance.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@VintfStability
+parcelable OperandPerformance {
+ android.hardware.neuralnetworks.OperandType type;
+ android.hardware.neuralnetworks.PerformanceInfo info;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandType.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandType.aidl
new file mode 100644
index 0000000..186c13d
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperandType.aidl
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@Backing(type="int") @VintfStability
+enum OperandType {
+ FLOAT32 = 0,
+ INT32 = 1,
+ UINT32 = 2,
+ TENSOR_FLOAT32 = 3,
+ TENSOR_INT32 = 4,
+ TENSOR_QUANT8_ASYMM = 5,
+ BOOL = 6,
+ TENSOR_QUANT16_SYMM = 7,
+ TENSOR_FLOAT16 = 8,
+ TENSOR_BOOL8 = 9,
+ FLOAT16 = 10,
+ TENSOR_QUANT8_SYMM_PER_CHANNEL = 11,
+ TENSOR_QUANT16_ASYMM = 12,
+ TENSOR_QUANT8_SYMM = 13,
+ TENSOR_QUANT8_ASYMM_SIGNED = 14,
+ SUBGRAPH = 15,
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Operation.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Operation.aidl
new file mode 100644
index 0000000..fec83a8
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Operation.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@VintfStability
+parcelable Operation {
+ android.hardware.neuralnetworks.OperationType type;
+ int[] inputs;
+ int[] outputs;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperationType.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperationType.aidl
new file mode 100644
index 0000000..ad42b02
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OperationType.aidl
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@Backing(type="int") @VintfStability
+enum OperationType {
+ ADD = 0,
+ AVERAGE_POOL_2D = 1,
+ CONCATENATION = 2,
+ CONV_2D = 3,
+ DEPTHWISE_CONV_2D = 4,
+ DEPTH_TO_SPACE = 5,
+ DEQUANTIZE = 6,
+ EMBEDDING_LOOKUP = 7,
+ FLOOR = 8,
+ FULLY_CONNECTED = 9,
+ HASHTABLE_LOOKUP = 10,
+ L2_NORMALIZATION = 11,
+ L2_POOL_2D = 12,
+ LOCAL_RESPONSE_NORMALIZATION = 13,
+ LOGISTIC = 14,
+ LSH_PROJECTION = 15,
+ LSTM = 16,
+ MAX_POOL_2D = 17,
+ MUL = 18,
+ RELU = 19,
+ RELU1 = 20,
+ RELU6 = 21,
+ RESHAPE = 22,
+ RESIZE_BILINEAR = 23,
+ RNN = 24,
+ SOFTMAX = 25,
+ SPACE_TO_DEPTH = 26,
+ SVDF = 27,
+ TANH = 28,
+ BATCH_TO_SPACE_ND = 29,
+ DIV = 30,
+ MEAN = 31,
+ PAD = 32,
+ SPACE_TO_BATCH_ND = 33,
+ SQUEEZE = 34,
+ STRIDED_SLICE = 35,
+ SUB = 36,
+ TRANSPOSE = 37,
+ ABS = 38,
+ ARGMAX = 39,
+ ARGMIN = 40,
+ AXIS_ALIGNED_BBOX_TRANSFORM = 41,
+ BIDIRECTIONAL_SEQUENCE_LSTM = 42,
+ BIDIRECTIONAL_SEQUENCE_RNN = 43,
+ BOX_WITH_NMS_LIMIT = 44,
+ CAST = 45,
+ CHANNEL_SHUFFLE = 46,
+ DETECTION_POSTPROCESSING = 47,
+ EQUAL = 48,
+ EXP = 49,
+ EXPAND_DIMS = 50,
+ GATHER = 51,
+ GENERATE_PROPOSALS = 52,
+ GREATER = 53,
+ GREATER_EQUAL = 54,
+ GROUPED_CONV_2D = 55,
+ HEATMAP_MAX_KEYPOINT = 56,
+ INSTANCE_NORMALIZATION = 57,
+ LESS = 58,
+ LESS_EQUAL = 59,
+ LOG = 60,
+ LOGICAL_AND = 61,
+ LOGICAL_NOT = 62,
+ LOGICAL_OR = 63,
+ LOG_SOFTMAX = 64,
+ MAXIMUM = 65,
+ MINIMUM = 66,
+ NEG = 67,
+ NOT_EQUAL = 68,
+ PAD_V2 = 69,
+ POW = 70,
+ PRELU = 71,
+ QUANTIZE = 72,
+ QUANTIZED_16BIT_LSTM = 73,
+ RANDOM_MULTINOMIAL = 74,
+ REDUCE_ALL = 75,
+ REDUCE_ANY = 76,
+ REDUCE_MAX = 77,
+ REDUCE_MIN = 78,
+ REDUCE_PROD = 79,
+ REDUCE_SUM = 80,
+ ROI_ALIGN = 81,
+ ROI_POOLING = 82,
+ RSQRT = 83,
+ SELECT = 84,
+ SIN = 85,
+ SLICE = 86,
+ SPLIT = 87,
+ SQRT = 88,
+ TILE = 89,
+ TOPK_V2 = 90,
+ TRANSPOSE_CONV_2D = 91,
+ UNIDIRECTIONAL_SEQUENCE_LSTM = 92,
+ UNIDIRECTIONAL_SEQUENCE_RNN = 93,
+ RESIZE_NEAREST_NEIGHBOR = 94,
+ QUANTIZED_LSTM = 95,
+ IF = 96,
+ WHILE = 97,
+ ELU = 98,
+ HARD_SWISH = 99,
+ FILL = 100,
+ RANK = 101,
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OutputShape.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OutputShape.aidl
new file mode 100644
index 0000000..09a43f7
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/OutputShape.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@VintfStability
+parcelable OutputShape {
+ int[] dimensions;
+ boolean isSufficient;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/PerformanceInfo.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/PerformanceInfo.aidl
new file mode 100644
index 0000000..178946c
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/PerformanceInfo.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@VintfStability
+parcelable PerformanceInfo {
+ float execTime;
+ float powerUsage;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Priority.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Priority.aidl
new file mode 100644
index 0000000..d9b77fa
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Priority.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@Backing(type="int") @VintfStability
+enum Priority {
+ LOW = 0,
+ MEDIUM = 1,
+ HIGH = 2,
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Request.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Request.aidl
new file mode 100644
index 0000000..599b3f4
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Request.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@VintfStability
+parcelable Request {
+ android.hardware.neuralnetworks.RequestArgument[] inputs;
+ android.hardware.neuralnetworks.RequestArgument[] outputs;
+ android.hardware.neuralnetworks.RequestMemoryPool[] pools;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/RequestArgument.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/RequestArgument.aidl
new file mode 100644
index 0000000..91b9aa7
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/RequestArgument.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@VintfStability
+parcelable RequestArgument {
+ boolean hasNoValue;
+ android.hardware.neuralnetworks.DataLocation location;
+ int[] dimensions;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/RequestMemoryPool.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/RequestMemoryPool.aidl
new file mode 100644
index 0000000..3813b51
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/RequestMemoryPool.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@VintfStability
+union RequestMemoryPool {
+ android.hardware.neuralnetworks.Memory pool;
+ int token;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Subgraph.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Subgraph.aidl
new file mode 100644
index 0000000..dec976f
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Subgraph.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@VintfStability
+parcelable Subgraph {
+ android.hardware.neuralnetworks.Operand[] operands;
+ android.hardware.neuralnetworks.Operation[] operations;
+ int[] inputIndexes;
+ int[] outputIndexes;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/SymmPerChannelQuantParams.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/SymmPerChannelQuantParams.aidl
new file mode 100644
index 0000000..66fdfe7
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/SymmPerChannelQuantParams.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@VintfStability
+parcelable SymmPerChannelQuantParams {
+ float[] scales;
+ int channelDim;
+}
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Timing.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Timing.aidl
new file mode 100644
index 0000000..d0de34a
--- /dev/null
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/Timing.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.neuralnetworks;
+@VintfStability
+parcelable Timing {
+ long timeOnDevice;
+ long timeInDriver;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/BufferDesc.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/BufferDesc.aidl
new file mode 100644
index 0000000..bec7e86
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/BufferDesc.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.neuralnetworks;
+
+/**
+ * A buffer descriptor. Describes the properties of a buffer.
+ */
+@VintfStability
+parcelable BufferDesc {
+ /**
+ * Dimensions of the buffer. May have unknown dimensions or rank. A buffer with some number of
+ * unspecified dimensions is represented by setting each unspecified dimension to 0. A buffer
+ * with unspecified rank is represented by providing an empty dimensions vector.
+ */
+ int[] dimensions;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/BufferRole.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/BufferRole.aidl
new file mode 100644
index 0000000..0d7f678
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/BufferRole.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.neuralnetworks;
+
+/**
+ * Describes a role of an input or output to a prepared model.
+ */
+@VintfStability
+parcelable BufferRole {
+ /**
+ * The index of the IPreparedModel within the "preparedModel" argument passed in
+ * IDevice::allocate.
+ */
+ int modelIndex;
+ /**
+ * The index of the input or output operand.
+ */
+ int ioIndex;
+ /**
+ * A floating-point value within the range (0.0, 1.0]. Describes how likely the buffer is to be
+ * used in the specified role. This is provided as a hint to optimize the case when multiple
+ * roles prefer different buffer locations or data layouts.
+ */
+ float frequency;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/Capabilities.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/Capabilities.aidl
new file mode 100644
index 0000000..3802f1f
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/Capabilities.aidl
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.neuralnetworks.OperandPerformance;
+import android.hardware.neuralnetworks.PerformanceInfo;
+
+/**
+ * The capabilities of a driver.
+ *
+ * This represents performance of non-extension operations.
+ *
+ * Performance of an operation other than {@link OperationType::IF} and {@link OperationType::WHILE}
+ * comes from the type of its first operand.
+ */
+@VintfStability
+parcelable Capabilities {
+ /**
+ * Driver performance when operating on float32 data but performing calculations with range
+ * and/or precision as low as that of the IEEE 754 16-bit floating-point format.
+ */
+ PerformanceInfo relaxedFloat32toFloat16PerformanceScalar;
+ PerformanceInfo relaxedFloat32toFloat16PerformanceTensor;
+ /**
+ * Performance by operand type. Must be sorted by OperandType.
+ *
+ * If a particular {@link OperandType} is not present in operandPerformance, its performance is
+ * treated as { .execTime = FLT_MAX, .powerUsage = FLT_MAX }.
+ *
+ * Performance does not apply to {@link OperandType::SUBGRAPH}, and a driver must not report
+ * operand performance for {@link OperandType::SUBGRAPH}.
+ */
+ OperandPerformance[] operandPerformance;
+ /**
+ * Performance of an {@link OperationType::IF} operation is the sum of
+ * {@link Capabilities::ifPerformance} and the mean of performance for the two branch subgraphs,
+ * where performance for a subgraph is the sum of the performance of all operations within the
+ * subgraph.
+ */
+ PerformanceInfo ifPerformance;
+ /**
+ * Performance of a {@link OperationType::WHILE} operation is the sum of
+ * {@link Capabilities::whilePerformance}, performance for the condition subgraph and
+ * performance for the body subgraph, where performance for a subgraph is the sum of the
+ * performance of all operations within the subgraph.
+ */
+ PerformanceInfo whilePerformance;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/DataLocation.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/DataLocation.aidl
new file mode 100644
index 0000000..f6b5e0d
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/DataLocation.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.neuralnetworks;
+
+/**
+ * Describes the location of a data object.
+ */
+@VintfStability
+parcelable DataLocation {
+ /**
+ * The index of the memory pool where this location is found.
+ */
+ int poolIndex;
+ /**
+ * Offset in bytes from the start of the pool.
+ */
+ long offset;
+ /**
+ * The length of the data in bytes.
+ */
+ long length;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/DeviceBuffer.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/DeviceBuffer.aidl
new file mode 100644
index 0000000..07930a6
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/DeviceBuffer.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.neuralnetworks.IBuffer;
+
+/**
+ * A type that is used to represent a driver allocated buffer and token that corresponds to it.
+ */
+@VintfStability
+parcelable DeviceBuffer {
+ /**
+ * An IBuffer object used to interact with the device allocated buffer.
+ */
+ IBuffer buffer;
+ /**
+ * A positive token identifying the allocated buffer. The token is provided when referencing the
+ * buffer as one of the memory pools in the request of an execution. The token must not collide
+ * with the tokens of other IBuffer objects that are currently alive in the same driver service.
+ */
+ int token;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/DeviceType.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/DeviceType.aidl
new file mode 100644
index 0000000..815be64
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/DeviceType.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.neuralnetworks;
+
+/**
+ * Device types.
+ *
+ * The type of NNAPI device.
+ */
+@VintfStability
+@Backing(type="int")
+enum DeviceType {
+ /**
+ * The device does not fall into any category below.
+ */
+ OTHER = 1,
+ /**
+ * The device runs NNAPI models on single or multi-core CPU.
+ */
+ CPU = 2,
+ /**
+ * The device can run NNAPI models and also accelerate graphics APIs such as OpenGL ES and
+ * Vulkan.
+ */
+ GPU = 3,
+ /**
+ * Dedicated accelerator for Machine Learning workloads.
+ */
+ ACCELERATOR = 4,
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/ErrorStatus.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/ErrorStatus.aidl
new file mode 100644
index 0000000..c2752d9
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/ErrorStatus.aidl
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.neuralnetworks;
+
+/**
+ * Calls to neural networks AIDL interfaces may return a ServiceSpecificException with the following
+ * error codes.
+ */
+@VintfStability
+@Backing(type="int")
+enum ErrorStatus {
+ NONE,
+ DEVICE_UNAVAILABLE,
+ GENERAL_FAILURE,
+ OUTPUT_INSUFFICIENT_SIZE,
+ INVALID_ARGUMENT,
+ /**
+ * Failure because a deadline could not be met for a task, but future deadlines may still be met
+ * for the same task after a short delay.
+ */
+ MISSED_DEADLINE_TRANSIENT,
+ /**
+ * Failure because a deadline could not be met for a task, and future deadlines will likely also
+ * not be met for the same task even after a short delay.
+ */
+ MISSED_DEADLINE_PERSISTENT,
+ /**
+ * Failure because of a resource limitation within the driver, but future calls for the same
+ * task may still succeed after a short delay.
+ */
+ RESOURCE_EXHAUSTED_TRANSIENT,
+ /**
+ * Failure because of a resource limitation within the driver, and future calls for the same
+ * task will likely also fail even after a short delay.
+ */
+ RESOURCE_EXHAUSTED_PERSISTENT,
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/ExecutionPreference.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/ExecutionPreference.aidl
new file mode 100644
index 0000000..a3e3ce3
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/ExecutionPreference.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.neuralnetworks;
+
+/**
+ * Execution preferences.
+ */
+@VintfStability
+@Backing(type="int")
+enum ExecutionPreference {
+ /**
+ * Prefer executing in a way that minimizes battery drain. This is desirable for compilations
+ * that will be executed often.
+ */
+ LOW_POWER,
+ /**
+ * Prefer returning a single answer as fast as possible, even if this causes more power
+ * consumption.
+ */
+ FAST_SINGLE_ANSWER,
+ /**
+ * Prefer maximizing the throughput of successive frames, for example when processing successive
+ * frames coming from the camera.
+ */
+ SUSTAINED_SPEED,
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/ExecutionResult.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/ExecutionResult.aidl
new file mode 100644
index 0000000..1f88207
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/ExecutionResult.aidl
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2021 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.neuralnetworks;
+
+import android.hardware.neuralnetworks.ErrorStatus;
+import android.hardware.neuralnetworks.OutputShape;
+import android.hardware.neuralnetworks.Timing;
+
+/**
+ * A result from running a synchronous execution of a prepared model.
+ */
+@VintfStability
+parcelable ExecutionResult {
+ /**
+ * A value of "true" indicates that the execution was successful. A value of "false" indicates
+ * the execution failed because at least one output operand buffer was not large enough to store
+ * the corresponding output.
+ */
+ boolean outputSufficientSize;
+ /**
+ * A list of shape information of model output operands. The index in "outputShapes" corresponds
+ * to the index of the output operand in the Request outputs vector.
+ */
+ OutputShape[] outputShapes;
+ /**
+ * Duration of execution. Unless measure is true and the execution is successful, all times must
+ * be reported as -1. A driver may choose to report any time as -1, indicating that measurement
+ * is not available.
+ */
+ Timing timing;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/Extension.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/Extension.aidl
new file mode 100644
index 0000000..20109bd
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/Extension.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.neuralnetworks.ExtensionOperandTypeInformation;
+
+/**
+ * Information about an extension.
+ */
+@VintfStability
+parcelable Extension {
+ /**
+ * The extension name.
+ *
+ * The name must consist of lowercase latin letters, numbers, periods, and underscore signs. The
+ * name must contain at least one period.
+ *
+ * The name must start with the reverse domain name of the vendor.
+ *
+ * Example: com.google.test_extension
+ */
+ String name;
+ /**
+ * Information about operand types defined by the extension.
+ */
+ ExtensionOperandTypeInformation[] operandTypes;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/ExtensionNameAndPrefix.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/ExtensionNameAndPrefix.aidl
new file mode 100644
index 0000000..29be93f
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/ExtensionNameAndPrefix.aidl
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.neuralnetworks;
+
+/**
+ * The mapping between extension names and prefixes of operand and operation type values.
+ *
+ * An operand or operation whose numeric type value is above {@link IDevice::OPERAND_TYPE_BASE_MAX}
+ * or {@link IDevice::OPERATION_TYPE_BASE_MAX} respectively should be interpreted as an extension
+ * operand/operation. The low {@link IDevice::EXTENSION_TYPE_LOW_BITS_TYPE} bits of the value
+ * correspond to the type ID within the extension and the high
+ * {@link IDevice::EXTENSION_TYPE_HIGH_BITS_PREFIX} bits encode the "prefix", which maps uniquely to
+ * the extension name. The sign bit is always 0.
+ *
+ * For example, if a model contains an operation whose value is 0x7AAABBBB and extensionNameToPrefix
+ * contains an entry with prefix=0x7AAA and name="vendor.test.test_extension", then the operation
+ * should be interpreted as the operation 0xBBBB of the extension named vendor.test.test_extension.
+ *
+ * This is a one-to-one correspondence. That is, there must be at most one prefix corresponding to
+ * each extension name and at most one extension name corresponding to each prefix.
+ */
+@VintfStability
+parcelable ExtensionNameAndPrefix {
+ /**
+ * The extension name.
+ *
+ * See {@link Extension::name} for the format specification.
+ */
+ String name;
+ /**
+ * The extension prefix. Only the lowest 15 bits are used, so the value must be less than 32768.
+ */
+ char prefix;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/ExtensionOperandTypeInformation.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/ExtensionOperandTypeInformation.aidl
new file mode 100644
index 0000000..b8e3449
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/ExtensionOperandTypeInformation.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.neuralnetworks;
+
+/**
+ * Information about an extension operand type.
+ */
+@VintfStability
+parcelable ExtensionOperandTypeInformation {
+ /**
+ * The extension operand type.
+ */
+ char type;
+ /**
+ * Indicates whether the extension operand type represents a tensor or a scalar.
+ */
+ boolean isTensor;
+ /**
+ * The byte size of the operand (if scalar) or of a single element (if tensor).
+ */
+ int byteSize;
+}
diff --git a/health/1.0/default/libhealthd/healthd_board_default.cpp b/neuralnetworks/aidl/android/hardware/neuralnetworks/FusedActivationFunc.aidl
similarity index 64%
copy from health/1.0/default/libhealthd/healthd_board_default.cpp
copy to neuralnetworks/aidl/android/hardware/neuralnetworks/FusedActivationFunc.aidl
index 127f98e..861b6f0 100644
--- a/health/1.0/default/libhealthd/healthd_board_default.cpp
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/FusedActivationFunc.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,16 @@
* limitations under the License.
*/
-#include <healthd/healthd.h>
+package android.hardware.neuralnetworks;
-void healthd_board_init(struct healthd_config*)
-{
- // use defaults
-}
-
-int healthd_board_battery_update(struct android::BatteryProperties*)
-{
- // return 0 to log periodic polled battery status to kernel log
- return 0;
+/**
+ * Fused activation function types.
+ */
+@VintfStability
+@Backing(type="int")
+enum FusedActivationFunc {
+ NONE,
+ RELU,
+ RELU1,
+ RELU6,
}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/IBuffer.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/IBuffer.aidl
new file mode 100644
index 0000000..2915a22
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/IBuffer.aidl
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.neuralnetworks.Memory;
+
+/**
+ * This interface represents a device memory buffer.
+ */
+@VintfStability
+interface IBuffer {
+ /**
+ * Sets the content of this buffer from a shared memory region.
+ *
+ * @param src The source shared memory region.
+ * @param dimensions Updated dimensional information. If the dimensions of the IBuffer object
+ * are not fully specified, then the dimensions must be fully specified here.
+ * If the dimensions of the IBuffer object are fully specified, then the
+ * dimensions may be empty here. If dimensions.size() > 0, then all dimensions
+ * must be specified here, and any dimension that was specified in the IBuffer
+ * object must have the same value here.
+ * @throws ServiceSpecificException with one of the following ErrorStatus values:
+ * - DEVICE_UNAVAILABLE if driver is offline or busy
+ * - GENERAL_FAILURE if there is an unspecified error
+ * - INVALID_ARGUMENT if provided memory is invalid, or if the dimensions is invalid
+ */
+ void copyFrom(in Memory src, in int[] dimensions);
+
+ /**
+ * Retrieves the content of this buffer to a shared memory region.
+ *
+ * The IBuffer object must have been initialized before the call to IBuffer::copyTo. For more
+ * information on the state of the IBuffer object, refer to IDevice::allocate.
+ *
+ * @param dst The destination shared memory region.
+ * @throws ServiceSpecificException with one of the following ErrorStatus values:
+ * - DEVICE_UNAVAILABLE if driver is offline or busy
+ * - GENERAL_FAILURE if the IBuffer object is uninitialized, or there is an unspecified
+ * error
+ * - INVALID_ARGUMENT if provided memory is invalid
+ */
+ void copyTo(in Memory dst);
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/IDevice.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/IDevice.aidl
new file mode 100644
index 0000000..e17e0cd
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/IDevice.aidl
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.neuralnetworks.BufferDesc;
+import android.hardware.neuralnetworks.BufferRole;
+import android.hardware.neuralnetworks.Capabilities;
+import android.hardware.neuralnetworks.DeviceBuffer;
+import android.hardware.neuralnetworks.DeviceType;
+import android.hardware.neuralnetworks.ExecutionPreference;
+import android.hardware.neuralnetworks.Extension;
+import android.hardware.neuralnetworks.IPreparedModel;
+import android.hardware.neuralnetworks.IPreparedModelCallback;
+import android.hardware.neuralnetworks.IPreparedModelParcel;
+import android.hardware.neuralnetworks.Model;
+import android.hardware.neuralnetworks.NumberOfCacheFiles;
+import android.hardware.neuralnetworks.Priority;
+
+/**
+ * This interface represents a device driver.
+ */
+@VintfStability
+interface IDevice {
+ /**
+ * The byte size of the cache token.
+ */
+ const int BYTE_SIZE_OF_CACHE_TOKEN = 32;
+ /**
+ * The maximum number of files for each type of cache in compilation caching.
+ */
+ const int MAX_NUMBER_OF_CACHE_FILES = 32;
+
+ /**
+ * Numeric values of extension operand and operation types have the following structure:
+ * - The sign bit is always 0.
+ * - 15 high bits represent the "prefix", which corresponds uniquely to the extension name.
+ * - 16 low bits represent the type ID within the extension.
+ */
+ const int EXTENSION_TYPE_HIGH_BITS_PREFIX = 15;
+ const int EXTENSION_TYPE_LOW_BITS_TYPE = 16;
+ /**
+ * OperandType with any value above {@link IDevice::OPERAND_TYPE_BASE_MAX} must be interpreted
+ * as an extension type according to {@link Model::extensionNameToPrefix}.
+ */
+ const int OPERAND_TYPE_BASE_MAX = 0xFFFF;
+ /**
+ * OperationType with any value above {@link IDevice::OPERATION_TYPE_BASE_MAX} must be
+ * interpreted as an extension type according to {@link Model::extensionNameToPrefix}.
+ */
+ const int OPERATION_TYPE_BASE_MAX = 0xFFFF;
+
+ /**
+ * Allocates a driver-managed buffer with the properties specified by the buffer descriptor as
+ * well as the input and output roles.
+ *
+ * The allocate function must verify its inputs are correct. If there is an error, or if a
+ * certain role or property is not supported by the driver, the allocate function must return a
+ * service specific exception with an appropriate ErrorStatus. If the allocation is successful,
+ * this method must return a DeviceBuffer object with the produced IBuffer and a positive token
+ * identifying the allocated buffer. A successful allocation must accommodate all of the
+ * specified roles and buffer properties.
+ *
+ * The buffer is allocated to an uninitialized state. An uninitialized buffer may only be used
+ * in ways that are specified by outputRoles. A buffer is initialized after it is used as an
+ * output in a successful execution, or after a successful invocation of IBuffer::copyFrom on
+ * the buffer. An initialized buffer may be used according to all roles specified in inputRoles
+ * and outputRoles. A buffer will return to the uninitialized state if it is used as an output
+ * in a failed execution, or after a failed invocation of IBuffer::copyFrom on the buffer.
+ *
+ * The dimensions of the buffer can be deduced from the buffer descriptor as well as the
+ * dimensions of the corresponding model operands of the input and output roles. The dimensions
+ * or rank of the buffer may be unknown at this stage. As such, some driver services may only
+ * create a placeholder and defer the actual allocation until execution time. Note that the same
+ * buffer may be used for different shapes of outputs on different executions. When the buffer
+ * is used as an input, the input shape must be the same as the output shape from the last
+ * execution using this buffer as an output.
+ *
+ * The driver must apply proper validatation upon every usage of the buffer, and must fail the
+ * execution immediately if the usage is illegal.
+ *
+ * @param desc A buffer descriptor specifying the properties of the buffer to allocate.
+ * @param preparedModels A vector of IPreparedModel objects. Must only contain IPreparedModel
+ * objects from the same IDevice as this method is being invoked on.
+ * @param inputRoles A vector of roles with each specifying an input to a prepared model.
+ * @param outputRoles A vector of roles with each specifying an output to a prepared model. Each
+ * role specified in inputRoles and outputRoles must be unique. The
+ * corresponding model operands of the roles must have the same OperandType,
+ * scale, zero point, and ExtraParams. The dimensions of the operands and the
+ * dimensions specified in the buffer descriptor must be compatible with each
+ * other. Two dimensions are incompatible if there is at least one axis that
+ * is fully specified in both but has different values.
+ * @return DeviceBuffer object containing the allocated IBuffer object and a positive token that
+ * can be used to reference the buffer as one of the memory pools.
+ * @throws ServiceSpecificException with one of the following ErrorStatus values:
+ * - DEVICE_UNAVAILABLE if driver is offline or busy
+ * - GENERAL_FAILURE if a certain buffer property or a certain role is not supported,
+ * or if there is an unspecified error
+ * - INVALID_ARGUMENT if one of the input arguments is invalid
+ * - RESOURCE_EXHAUSTED_* if the task was aborted by the driver
+ */
+ DeviceBuffer allocate(in BufferDesc desc, in IPreparedModelParcel[] preparedModels,
+ in BufferRole[] inputRoles, in BufferRole[] outputRoles);
+
+ /**
+ * Gets the capabilities of a driver.
+ *
+ * @return Capabilities of the driver.
+ * @throws ServiceSpecificException with one of the following ErrorStatus values:
+ * - DEVICE_UNAVAILABLE if driver is offline or busy
+ * - GENERAL_FAILURE if there is an unspecified error
+ */
+ Capabilities getCapabilities();
+
+ /**
+ * Gets the caching requirements of the driver implementation.
+ *
+ * There are two types of cache file descriptors provided to the driver: model cache and data
+ * cache.
+ *
+ * The data cache is for caching constant data, possibly including preprocessed and transformed
+ * tensor buffers. Any modification to the data cache should have no worse effect than
+ * generating bad output values at execution time.
+ *
+ * The model cache is for caching security-sensitive data such as compiled executable machine
+ * code in the device's native binary format. A modification to the model cache may affect the
+ * driver's execution behavior, and a malicious client could make use of this to execute beyond
+ * the granted permission. Thus, the driver must always check whether the model cache is
+ * corrupted before preparing the model from cache.
+ *
+ * getNumberOfCacheFilesNeeded returns how many of each type of cache files the driver
+ * implementation needs to cache a single prepared model. Returning 0 for both types indicates
+ * compilation caching is not supported by this driver. The driver may still choose not to cache
+ * certain compiled models even if it reports that caching is supported.
+ *
+ * If the device reports that caching is not supported, the user may avoid calling
+ * IDevice::prepareModelFromCache or providing cache file descriptors to
+ * IDevice::prepareModel.
+ *
+ * @return NumberOfCacheFiles structure indicating how many files for model and data cache the
+ * driver needs to cache a single prepared model. It must be less than or equal to
+ * MAX_NUMBER_OF_CACHE_FILES.
+ * @throws ServiceSpecificException with one of the following ErrorStatus values:
+ * - DEVICE_UNAVAILABLE if driver is offline or busy
+ * - GENERAL_FAILURE if there is an unspecified error
+ */
+ NumberOfCacheFiles getNumberOfCacheFilesNeeded();
+
+ /**
+ * Gets information about extensions supported by the driver implementation.
+ *
+ * All extension operations and operands must be fully supported for the extension to appear in
+ * the list of supported extensions.
+ *
+ * @return A list of supported extensions.
+ * @throws ServiceSpecificException with one of the following ErrorStatus values:
+ * - DEVICE_UNAVAILABLE if driver is offline or busy
+ * - GENERAL_FAILURE if there is an unspecified error
+ */
+ Extension[] getSupportedExtensions();
+
+ /**
+ * Gets the supported operations in a model.
+ *
+ * getSupportedOperations indicates which operations of the top-level subgraph are fully
+ * supported by the vendor driver. If an operation may not be supported for any reason,
+ * getSupportedOperations must return false for that operation.
+ *
+ * The {@link OperationType::IF} and {@link OperationType::WHILE} operations may only be fully
+ * supported if the vendor driver fully supports all operations in the referenced subgraphs.
+ *
+ * @param model A model whose operations -- and their corresponding operands -- are to be
+ * verified by the driver.
+ * @return A list of supported operations, where true indicates the operation is supported and
+ * false indicates the operation is not supported. The index of "supported" corresponds with
+ * the index of the operation it is describing in the main subgraph.
+ * @throws ServiceSpecificException with one of the following ErrorStatus values:
+ * - DEVICE_UNAVAILABLE if driver is offline or busy
+ * - GENERAL_FAILURE if there is an unspecified error
+ * - INVALID_ARGUMENT if provided model is invalid
+ */
+ boolean[] getSupportedOperations(in Model model);
+
+ /**
+ * Get the type of a given device.
+ *
+ * The device type can be used to help application developers to distribute Machine Learning
+ * workloads and other workloads such as graphical rendering. E.g., for an app which renders AR
+ * scenes based on real time object detection results, the developer could choose an ACCELERATOR
+ * type device for ML workloads, and reserve GPU for graphical rendering.
+ *
+ * @return The DeviceType of the device. Please note, this is not a bitfield of DeviceTypes.
+ * Each device must only be of a single DeviceType.
+ * @throws ServiceSpecificException with one of the following ErrorStatus values:
+ * - DEVICE_UNAVAILABLE if driver is offline or busy
+ * - GENERAL_FAILURE if the query resulted in an unspecified error
+ */
+ DeviceType getType();
+
+ /**
+ * Get the version string of the driver implementation.
+ *
+ * The version string must be a unique token among the set of version strings of drivers of a
+ * specific device. The token identifies the device driver's implementation. The token must not
+ * be confused with the feature level which is solely defined by the interface version. This API
+ * is opaque to the Android framework, but the Android framework may use the information for
+ * debugging or to pass on to NNAPI applications.
+ *
+ * Application developers sometimes have specific requirements to ensure good user experiences,
+ * and they need more information to make intelligent decisions when the Android framework
+ * cannot. For example, combined with the device name and other information, the token can help
+ * NNAPI applications filter devices based on their needs:
+ * - An application demands a certain level of performance, but a specific version of the
+ * driver cannot meet that requirement because of a performance regression.
+ * The application can disallow the driver based on the version provided.
+ * - An application has a minimum precision requirement, but certain versions of
+ * the driver cannot meet that requirement because of bugs or certain optimizations.
+ * The application can filter out versions of these drivers.
+ *
+ * @return The version string of the device implementation. Must have nonzero length.
+ * @throws ServiceSpecificException with one of the following ErrorStatus values:
+ * - DEVICE_UNAVAILABLE if driver is offline or busy
+ * - GENERAL_FAILURE if the query resulted in an unspecified error
+ */
+ String getVersionString();
+
+ /**
+ * Asynchronously creates a prepared model for execution and optionally saves it into cache
+ * files.
+ *
+ * prepareModel is used to make any necessary transformations to or alternative representations
+ * to a model for execution, possibly including transformations on the constant data,
+ * optimization on the model's graph, or compilation into the device's native binary format. The
+ * model itself is not changed.
+ *
+ * Optionally, caching information may be provided for the driver to save the prepared model to
+ * cache files for faster model compilation time when the same model preparation is requested in
+ * the future. There are two types of cache file descriptors provided to the driver: model cache
+ * and data cache. For more information on the two types of cache, refer to
+ * getNumberOfCacheFilesNeeded.
+ *
+ * The file descriptors must be opened with read and write permission. A file may have any size,
+ * and the corresponding file descriptor may have any offset. The driver must truncate a file to
+ * zero size before writing to that file. The file descriptors may be closed by the client once
+ * the asynchronous preparation has finished. The driver must dup a file descriptor if it wants
+ * to get access to the cache file later.
+ *
+ * The model is prepared asynchronously with respect to the caller. The prepareModel function
+ * must verify the inputs to the preparedModel function related to preparing the model (as
+ * opposed to saving the prepared model to cache) are correct. If there is an error,
+ * prepareModel must immediately invoke the callback with the appropriate ErrorStatus value and
+ * nullptr for the IPreparedModel, then return a status with a service specific exception with
+ * the same ErrorStatus. If the inputs to the prepareModel function that are related to
+ * preparing the model are valid and there is no error, prepareModel must launch an asynchronous
+ * task to prepare the model in the background, and immediately return from prepareModel. If the
+ * asynchronous task fails to launch, prepareModel must immediately invoke the callback with
+ * ErrorStatus::GENERAL_FAILURE and nullptr for the IPreparedModel, then return a service
+ * specific exception with ErrorStatus::GENERAL_FAILURE.
+ *
+ * When the asynchronous task has finished preparing the model, it must immediately invoke the
+ * callback function provided as an input to prepareModel. If the model was prepared
+ * successfully, the callback object must be invoked with an error status of ErrorStatus::NONE
+ * and the produced IPreparedModel object. If an error occurred preparing the model, the
+ * callback object must be invoked with the appropriate ErrorStatus value and nullptr for the
+ * IPreparedModel.
+ *
+ * The model is prepared with a priority. This priority is relative to other prepared models
+ * owned by the same client. Higher priority executions may use more compute resources than
+ * lower priority executions, and may preempt or starve lower priority executions.
+ *
+ * prepareModel can be called with an optional deadline. If the model is not able to be prepared
+ * before the provided deadline, the model preparation may be aborted, and either
+ * {@link ErrorStatus::MISSED_DEADLINE_TRANSIENT} or {@link
+ * ErrorStatus::MISSED_DEADLINE_PERSISTENT} may be returned. The error due to an abort must be
+ * sent the same way as other errors, described above.
+ *
+ * Optionally, the driver may save the prepared model to cache during the asynchronous
+ * preparation. Any error that occurs when saving to cache must not affect the status of
+ * preparing the model. Even if the input arguments related to the cache may be invalid, or the
+ * driver may fail to save to cache, the prepareModel function must finish preparing the model.
+ * The driver may choose not to save to cache even if the caching information is provided and
+ * valid.
+ *
+ * The only information that may be unknown to the model at this stage is the shape of the
+ * tensors, which may only be known at execution time. As such, some driver services may return
+ * partially prepared models, where the prepared model may only be finished when it is paired
+ * with a set of inputs to the model. Note that the same prepared model object may be used with
+ * different shapes of inputs on different (possibly concurrent) executions.
+ *
+ * Multiple threads may call prepareModel on the same model concurrently.
+ *
+ * @param model The model to be prepared for execution.
+ * @param preference Indicates the intended execution behavior of a prepared model.
+ * @param priority The priority of the prepared model relative to other prepared models owned by
+ * the client.
+ * @param deadline The time by which the model is expected to be prepared. The time is measured
+ * in nanoseconds since epoch of the steady clock (as from
+ * std::chrono::steady_clock). If the model cannot be prepared by the deadline,
+ * the preparation may be aborted. Passing -1 means the deadline is omitted.
+ * Other negative values are invalid.
+ * @param modelCache A vector of file descriptors for the security-sensitive cache. The length
+ * of the vector must either be 0 indicating that caching information is not
+ * provided, or match the numModelCache returned from
+ * getNumberOfCacheFilesNeeded. The cache file descriptors will be provided in
+ * the same order when retrieving the preparedModel from cache files with
+ * prepareModelFromCache.
+ * @param dataCache A vector of file descriptors for the constants' cache. The length of the
+ * vector must either be 0 indicating that caching information is not provided,
+ * or match the numDataCache returned from getNumberOfCacheFilesNeeded. The
+ * cache file descriptors will be provided in the same order when retrieving
+ * the preparedModel from cache files with prepareModelFromCache.
+ * @param token A caching token of length BYTE_SIZE_OF_CACHE_TOKEN identifying the prepared
+ * model. The same token will be provided when retrieving the prepared model from
+ * the cache files with prepareModelFromCache. Tokens should be chosen to have a
+ * low rate of collision for a particular application. The driver cannot detect a
+ * collision; a collision will result in a failed execution or in a successful
+ * execution that produces incorrect output values. If both modelCache and
+ * dataCache are empty indicating that caching information is not provided, this
+ * token must be ignored.
+ * @param callback A callback object used to return the error status of preparing the model for
+ * execution and the prepared model if successful, nullptr otherwise. The
+ * callback object's notify function must be called exactly once, even if the
+ * model could not be prepared.
+ * @throws ServiceSpecificException with one of the following ErrorStatus values:
+ * - DEVICE_UNAVAILABLE if driver is offline or busy
+ * - GENERAL_FAILURE if there is an unspecified error
+ * - INVALID_ARGUMENT if one of the input arguments related to preparing the model is
+ * invalid
+ * - MISSED_DEADLINE_* if the preparation is aborted because the model cannot be prepared by
+ * the deadline
+ * - RESOURCE_EXHAUSTED_* if the task was aborted by the driver
+ */
+ void prepareModel(in Model model, in ExecutionPreference preference, in Priority priority,
+ in long deadline, in ParcelFileDescriptor[] modelCache,
+ in ParcelFileDescriptor[] dataCache, in byte[] token,
+ in IPreparedModelCallback callback);
+
+ /**
+ * Creates a prepared model from cache files for execution.
+ *
+ * prepareModelFromCache is used to retrieve a prepared model directly from cache files to avoid
+ * slow model compilation time. There are two types of cache file descriptors provided to the
+ * driver: model cache and data cache. For more information on the two types of cache files,
+ * refer to getNumberOfCacheFilesNeeded.
+ *
+ * The file descriptors must be opened with read and write permission. A file may have any size,
+ * and the corresponding file descriptor may have any offset. The driver must truncate a file to
+ * zero size before writing to that file. The file descriptors may be closed by the client once
+ * the asynchronous preparation has finished. The driver must dup a file descriptor if it wants
+ * to get access to the cache file later.
+ *
+ * The model is prepared asynchronously with respect to the caller. The prepareModelFromCache
+ * function must verify the inputs to the prepareModelFromCache function are correct, and that
+ * the security-sensitive cache has not been modified since it was last written by the driver.
+ * If there is an error, or if compilation caching is not supported, or if the
+ * security-sensitive cache has been modified, prepareModelFromCache must immediately invoke the
+ * callback with the appropriate ErrorStatus value and nullptr for the IPreparedModel, then
+ * return a status with a service specific exception with the same ErrorStatus. If the inputs to
+ * the prepareModelFromCache function are valid, the security-sensitive cache is not modified,
+ * and there is no error, prepareModelFromCache must launch an asynchronous task to prepare the
+ * model in the background, and immediately return from prepareModelFromCache. If the
+ * asynchronous task fails to launch, prepareModelFromCache must immediately invoke the callback
+ * with ErrorStatus::GENERAL_FAILURE and nullptr for the IPreparedModel, then return a service
+ * specific exception with ErrorStatus::GENERAL_FAILURE.
+ *
+ * When the asynchronous task has finished preparing the model, it must immediately invoke the
+ * callback function provided as an input to prepareModelFromCache. If the model was prepared
+ * successfully, the callback object must be invoked with an error status of ErrorStatus::NONE
+ * and the produced IPreparedModel object. If an error occurred preparing the model, the
+ * callback object must be invoked with the appropriate ErrorStatus value and nullptr for the
+ * IPreparedModel.
+ *
+ * prepareModelFromCache can be called with an optional deadline. If the model is not able to
+ * prepared before the provided deadline, the model preparation may be aborted, and either
+ * {@link ErrorStatus::MISSED_DEADLINE_TRANSIENT} or
+ * {@link ErrorStatus::MISSED_DEADLINE_PERSISTENT} may be returned. The error due to an abort
+ * must be sent the same way as other errors, described above.
+ *
+ * The only information that may be unknown to the model at this stage is the shape of the
+ * tensors, which may only be known at execution time. As such, some driver services may return
+ * partially prepared models, where the prepared model may only be finished when it is paired
+ * with a set of inputs to the model. Note that the same prepared model object may be used with
+ * different shapes of inputs on different (possibly concurrent) executions.
+ *
+ * @param deadline The time by which the model is expected to be prepared. The time is measured
+ * in nanoseconds since epoch of the steady clock (as from
+ * std::chrono::steady_clock). If the model cannot be prepared by the deadline,
+ * the preparation may be aborted. Passing -1 means the deadline is omitted.
+ * Other negative values are invalid.
+ * @param modelCache A vector of file descriptors for the security-sensitive cache. The length
+ * of the vector must match the numModelCache returned from
+ * getNumberOfCacheFilesNeeded. The cache file descriptors will be provided in
+ * the same order as with prepareModel.
+ * @param dataCache A vector of file descriptors for the constants' cache. The length of the
+ * vector must match the numDataCache returned from
+ * getNumberOfCacheFilesNeeded. The cache file descriptors will be provided in
+ * the same order as with prepareModel.
+ * @param token A caching token of length BYTE_SIZE_OF_CACHE_TOKEN identifying the prepared
+ * model. It is the same token provided when saving the cache files with
+ * prepareModel. Tokens should be chosen to have a low rate of collision for a
+ * particular application. The driver cannot detect a collision; a collision will
+ * result in a failed execution or in a successful execution that produces
+ * incorrect output values.
+ * @param callback A callback object used to return the error status of preparing the model for
+ * execution and the prepared model if successful, nullptr otherwise. The
+ * callback object's notify function must be called exactly once, even if the
+ * model could not be prepared.
+ * @throws ServiceSpecificException with one of the following ErrorStatus values:
+ * - DEVICE_UNAVAILABLE if driver is offline or busy
+ * - GENERAL_FAILURE if caching is not supported or if there is an unspecified error
+ * - INVALID_ARGUMENT if one of the input arguments is invalid
+ * - MISSED_DEADLINE_* if the preparation is aborted because the model cannot be prepared by
+ * the deadline
+ * - RESOURCE_EXHAUSTED_* if the task was aborted by the driver
+ */
+ void prepareModelFromCache(in long deadline, in ParcelFileDescriptor[] modelCache,
+ in ParcelFileDescriptor[] dataCache, in byte[] token,
+ in IPreparedModelCallback callback);
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/IFencedExecutionCallback.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/IFencedExecutionCallback.aidl
new file mode 100644
index 0000000..cb6db11
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/IFencedExecutionCallback.aidl
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.neuralnetworks.ErrorStatus;
+import android.hardware.neuralnetworks.Timing;
+
+/**
+ * IFencedExecutionCallback can be used to query the error status result and duration information
+ * from an IPreparedModel::executeFenced call.
+ */
+@VintfStability
+interface IFencedExecutionCallback {
+ /**
+ * The getExecutionInfo method is used by the clients to query error status result and duration
+ * information. The method must only be called after the actual evaluation has finished or
+ * resulted in an runtime error, as indicated by the status of the sync fence returned by the
+ * IPreparedModel::executeFenced call, otherwise GENERAL_FAILURE must be returned.
+ *
+ * @param out timingLaunched The duration starts when executeFenced is called and ends when
+ * executeFenced signals the returned syncFence. Unless measureTiming
+ * was set to true when launching the execution and status is NONE,
+ * all times must be reported as -1. A driver may choose to report any
+ * time as -1, indicating that particular measurement is not
+ * available.
+ * @param out timingFenced The duration starts when all waitFor sync fences have been signaled
+ * and ends when executeFenced signals the returned syncFence. Unless
+ * measureTiming was set to true when launching the execution and status
+ * is NONE, all times must be reported as -1. A driver may choose to
+ * report any time as -1, indicating that particular measurement is not
+ * available.
+ * @return Error status returned from the asynchronously dispatched execution must be:
+ * - NONE if the asynchronous execution was successful
+ * - DEVICE_UNAVAILABLE if driver is offline or busy
+ * - GENERAL_FAILURE if the asynchronous task resulted in an unspecified error
+ * - MISSED_DEADLINE_* if the execution is aborted because it cannot be completed by the
+ * deadline
+ * - RESOURCE_EXHAUSTED_* if the task was aborted by the driver
+ */
+ ErrorStatus getExecutionInfo(out Timing timingLaunched, out Timing timingFenced);
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModel.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModel.aidl
new file mode 100644
index 0000000..2414a4a
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModel.aidl
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.common.NativeHandle;
+import android.hardware.neuralnetworks.ErrorStatus;
+import android.hardware.neuralnetworks.ExecutionResult;
+import android.hardware.neuralnetworks.IFencedExecutionCallback;
+import android.hardware.neuralnetworks.Request;
+
+/**
+ * IPreparedModel describes a model that has been prepared for execution and is used to launch
+ * executions.
+ */
+@VintfStability
+interface IPreparedModel {
+ /**
+ * Each {@link OperationType::WHILE} operation in the model has an implicit execution timeout
+ * duration associated with it ("loop timeout duration"). This duration is configurable on a
+ * per-execution basis and must not exceed 15 seconds. The default value is 2 seconds. The units
+ * are nanoseconds.
+ */
+ const long DEFAULT_LOOP_TIMEOUT_DURATION_NS = 2000000000;
+ const long MAXIMUM_LOOP_TIMEOUT_DURATION_NS = 15000000000;
+
+ /**
+ * Performs a synchronous execution on a prepared model.
+ *
+ * The execution is performed synchronously with respect to the caller. executeSynchronously
+ * must verify the inputs to the function are correct, and the usages of memory pools allocated
+ * by IDevice::allocate are valid. If there is an error, executeSynchronously must immediately
+ * return a service specific exception with the appropriate ErrorStatus value. If the inputs to
+ * the function are valid and there is no error, executeSynchronously must perform the
+ * execution, and must not return until the execution is complete.
+ *
+ * The caller must not change the content of any data object referenced by 'request' (described
+ * by the {@link DataLocation} of a {@link RequestArgument}) until executeSynchronously returns.
+ * executeSynchronously must not change the content of any of the data objects corresponding to
+ * 'request' inputs.
+ *
+ * If the prepared model was prepared from a model wherein all tensor operands have fully
+ * specified dimensions, and the inputs to the function are valid, and at execution time every
+ * operation's input operands have legal values, then the execution should complete
+ * successfully: there must be no failure unless the device itself is in a bad state.
+ *
+ * executeSynchronously may be called with an optional deadline. If the execution is not able to
+ * be completed before the provided deadline, the execution may be aborted, and either
+ * {@link ErrorStatus::MISSED_DEADLINE_TRANSIENT} or {@link
+ * ErrorStatus::MISSED_DEADLINE_PERSISTENT} may be returned. The error due to an abort must be
+ * sent the same way as other errors, described above.
+ *
+ * Any number of calls to the execute* functions, in any combination, may be made concurrently,
+ * even on the same IPreparedModel object.
+ *
+ * @param request The input and output information on which the prepared model is to be
+ * executed.
+ * @param measure Specifies whether or not to measure duration of the execution. The duration
+ * runs from the time the driver sees the call to the executeSynchronously
+ * function to the time the driver returns from the function.
+ * @param deadline The time by which the execution is expected to complete. The time is measured
+ * in nanoseconds since epoch of the steady clock (as from
+ * std::chrono::steady_clock). If the execution cannot be finished by the
+ * deadline, the execution may be aborted. Passing -1 means the deadline is
+ * omitted. Other negative values are invalid.
+ * @param loopTimeoutDuration The maximum amount of time in nanoseconds that should be spent
+ * executing a {@link OperationType::WHILE} operation. If a loop
+ * condition model does not output false within this duration, the
+ * execution must be aborted. If -1 is provided, the maximum amount
+ * of time is {@link DEFAULT_LOOP_TIMEOUT_DURATION_NS}. Other
+ * negative values are invalid. When provided, the duration must not
+ * exceed {@link MAXIMUM_LOOP_TIMEOUT_DURATION_NS}.
+ * @return ExecutionResult parcelable, containing the status of the execution, output shapes and
+ * timing information.
+ * @throws ServiceSpecificException with one of the following ErrorStatus values:
+ * - DEVICE_UNAVAILABLE if driver is offline or busy
+ * - GENERAL_FAILURE if there is an unspecified error
+ * - INVALID_ARGUMENT if one of the input arguments is invalid
+ * - MISSED_DEADLINE_* if the execution is aborted because it cannot be completed by the
+ * deadline
+ * - RESOURCE_EXHAUSTED_* if the task was aborted by the driver
+ */
+ ExecutionResult executeSynchronously(in Request request, in boolean measureTiming,
+ in long deadline, in long loopTimeoutDuration);
+
+ /**
+ * Launch a fenced asynchronous execution on a prepared model.
+ *
+ * The execution is performed asynchronously with respect to the caller. executeFenced must
+ * verify the inputs to the function are correct, and the usages of memory pools allocated by
+ * IDevice::allocate are valid. If there is an error, executeFenced must immediately return a
+ * service specific exception with the corresponding ErrorStatus. If the inputs to the function
+ * are valid and there is no error, executeFenced must dispatch an asynchronous task to perform
+ * the execution in the background, assign a sync fence that will be signaled once the execution
+ * is completed and immediately return a callback that can be used by the client to query the
+ * duration and runtime error status. If the task has finished before the call returns,
+ * syncFence file descriptor may be set to -1. The execution must wait for all the sync fences
+ * (if any) in waitFor to be signaled before starting the actual execution.
+ *
+ * When the asynchronous task has finished its execution, it must immediately signal the
+ * syncFence returned from the executeFenced call. After the syncFence is signaled, the task
+ * must not modify the content of any data object referenced by 'request' (described by the
+ * {@link DataLocation} of a {@link RequestArgument}).
+ *
+ * executeFenced may be called with an optional deadline and an optional duration. If the
+ * execution is not able to be completed before the provided deadline or within the timeout
+ * duration (measured from when all sync fences in waitFor are signaled), whichever comes
+ * earlier, the execution may be aborted, and either
+ * {@link ErrorStatus::MISSED_DEADLINE_TRANSIENT} or {@link
+ * ErrorStatus::MISSED_DEADLINE_PERSISTENT} may be returned. The error due to an abort must be
+ * sent the same way as other errors, described above.
+ *
+ * If any of the sync fences in waitFor changes to error status after the executeFenced call
+ * succeeds, or the execution is aborted because it cannot finish before the deadline has been
+ * reached or the duration has elapsed, the driver must immediately set the returned syncFence
+ * to error status.
+ *
+ * Any number of calls to the execute* functions, in any combination, may be made concurrently,
+ * even on the same IPreparedModel object.
+ *
+ * @param request The input and output information on which the prepared model is to be
+ * executed. The outputs in the request must have fully specified dimensions.
+ * @param waitFor A vector of sync fence file descriptors. Execution must not start until all
+ * sync fences have been signaled.
+ * @param measure Specifies whether or not to measure duration of the execution.
+ * @param deadline The time by which the execution is expected to complete. The time is measured
+ * in nanoseconds since epoch of the steady clock (as from
+ * std::chrono::steady_clock).If the execution cannot be finished by the
+ * deadline, the execution may be aborted. Passing -1 means the deadline is
+ * omitted. Other negative values are invalid.
+ * @param loopTimeoutDuration The maximum amount of time in nanoseconds that should be spent
+ * executing a {@link OperationType::WHILE} operation. If a loop
+ * condition model does not output false within this duration, the
+ * execution must be aborted. If -1 is provided, the maximum amount
+ * of time is {@link DEFAULT_LOOP_TIMEOUT_DURATION_NS}. Other
+ * negative values are invalid. When provided, the duration must not
+ * exceed {@link MAXIMUM_LOOP_TIMEOUT_DURATION_NS}.
+ * @param duration The length of time in nanoseconds within which the execution is expected to
+ * complete after all sync fences in waitFor are signaled. If the execution
+ * cannot be finished within the duration, the execution may be aborted. Passing
+ * -1 means the duration is omitted. Other negative values are invalid.
+ * @param out syncFence The sync fence that will be signaled when the task is completed. The
+ * sync fence will be set to error if a critical error, e.g. hardware
+ * failure or kernel panic, occurs when doing execution.
+ * @return The IFencedExecutionCallback can be used to query information like duration and error
+ * status when the execution is completed.
+ * @throws ServiceSpecificException with one of the following ErrorStatus values:
+ * - DEVICE_UNAVAILABLE if driver is offline or busy
+ * - GENERAL_FAILURE if there is an unspecified error
+ * - INVALID_ARGUMENT if one of the input arguments is invalid, including fences in error
+ * states.
+ * - MISSED_DEADLINE_* if the execution is aborted because it cannot be completed by the
+ * deadline
+ * - RESOURCE_EXHAUSTED_* if the task was aborted by the driver
+ */
+ IFencedExecutionCallback executeFenced(in Request request, in ParcelFileDescriptor[] waitFor,
+ in boolean measureTiming, in long deadline, in long loopTimeoutDuration,
+ in long duration, out @nullable ParcelFileDescriptor syncFence);
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModelCallback.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModelCallback.aidl
new file mode 100644
index 0000000..29cca6d
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModelCallback.aidl
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.neuralnetworks.ErrorStatus;
+import android.hardware.neuralnetworks.IPreparedModel;
+
+/**
+ * IPreparedModelCallback must be used to return a prepared model produced by an asynchronous task
+ * launched from IDevice::prepareModel*.
+ */
+@VintfStability
+interface IPreparedModelCallback {
+ /**
+ * Notify must be invoked immediately after the asynchronous task holding this callback has
+ * finished preparing the model. If the model was successfully prepared, the method must be
+ * invoked with ErrorStatus::NONE and the prepared model. If the model was not able to be
+ * successfully prepared, the method must be invoked with the appropriate ErrorStatus and
+ * nullptr as the IPreparedModel. If the asynchronous task holding this callback fails to launch
+ * or if the model provided to IDevice::prepareModel is invalid, notify method must be invoked
+ * with the appropriate error as well as nullptr for the IPreparedModel.
+ *
+ * @param status Error status returned from the asynchronous model preparation task; must be:
+ * - NONE if the asynchronous task successfully prepared the model
+ * - DEVICE_UNAVAILABLE if driver is offline or busy
+ * - GENERAL_FAILURE if the asynchronous task resulted in an unspecified error
+ * - INVALID_ARGUMENT if one of the input arguments to prepareModel is invalid
+ * - MISSED_DEADLINE_* if the preparation is aborted because the model cannot be
+ * prepared by the deadline
+ * - RESOURCE_EXHAUSTED_* if the task was aborted by the driver
+ * @param preparedModel A model that has been asynchronously prepared for execution. If the
+ * model was unable to be prepared due to an error, nullptr must be passed
+ * in place of the IPreparedModel object.
+ */
+ void notify(in ErrorStatus status, in IPreparedModel preparedModel);
+}
diff --git a/health/1.0/default/libhealthd/healthd_board_default.cpp b/neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModelParcel.aidl
similarity index 64%
copy from health/1.0/default/libhealthd/healthd_board_default.cpp
copy to neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModelParcel.aidl
index 127f98e..878b0ad 100644
--- a/health/1.0/default/libhealthd/healthd_board_default.cpp
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/IPreparedModelParcel.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,14 @@
* limitations under the License.
*/
-#include <healthd/healthd.h>
+package android.hardware.neuralnetworks;
-void healthd_board_init(struct healthd_config*)
-{
- // use defaults
-}
+import android.hardware.neuralnetworks.IPreparedModel;
-int healthd_board_battery_update(struct android::BatteryProperties*)
-{
- // return 0 to log periodic polled battery status to kernel log
- return 0;
+/**
+ * A parcelable for passing a vector of IPreparedModel objects.
+ */
+@VintfStability
+parcelable IPreparedModelParcel {
+ IPreparedModel preparedModel;
}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/Memory.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/Memory.aidl
new file mode 100644
index 0000000..870f0ae
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/Memory.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.neuralnetworks;
+import android.hardware.common.NativeHandle;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * A type that is used to pass pieces of shared memory between processes.
+ * The type structure mimics hidl_memory type from HIDL.
+ */
+@VintfStability
+parcelable Memory {
+ NativeHandle handle;
+ long size;
+ String name;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/Model.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/Model.aidl
new file mode 100644
index 0000000..2f11dec
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/Model.aidl
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.neuralnetworks.ExtensionNameAndPrefix;
+import android.hardware.neuralnetworks.Memory;
+import android.hardware.neuralnetworks.Subgraph;
+
+/**
+ * A Neural Network Model.
+ *
+ * This includes not only the execution graph, but also constant data such as weights or scalars
+ * added at construction time. The only information that may not be known is the shape of the input
+ * tensors.
+ */
+@VintfStability
+parcelable Model {
+ /**
+ * The top-level subgraph.
+ */
+ Subgraph main;
+ /**
+ * Referenced subgraphs.
+ *
+ * Each subgraph is referenced by the main subgraph or at least one other referenced subgraph.
+ *
+ * There must be no reference cycles.
+ */
+ Subgraph[] referenced;
+ /**
+ * A byte buffer containing operand data that were copied into the model.
+ *
+ * An operand's value must be located here if and only if Operand::lifetime equals
+ * OperandLifeTime::CONSTANT_COPY.
+ */
+ byte[] operandValues;
+ /**
+ * A collection of shared memory pools containing operand values.
+ *
+ * An operand's value must be located here if and only if Operand::lifetime equals
+ * OperandLifeTime::CONSTANT_POOL.
+ */
+ Memory[] pools;
+ /**
+ * 'true' indicates TENSOR_FLOAT32 may be calculated with range and/or precision as low as that
+ * of the IEEE 754 16-bit floating-point format.
+ * 'false' indicates TENSOR_FLOAT32 must be calculated using at least the range and precision of
+ * the IEEE 754 32-bit floating-point format.
+ */
+ boolean relaxComputationFloat32toFloat16;
+ /**
+ * The mapping between extension names and prefixes of operand and operation type values.
+ */
+ ExtensionNameAndPrefix[] extensionNameToPrefix;
+}
diff --git a/health/1.0/default/libhealthd/healthd_board_default.cpp b/neuralnetworks/aidl/android/hardware/neuralnetworks/NumberOfCacheFiles.aidl
similarity index 63%
copy from health/1.0/default/libhealthd/healthd_board_default.cpp
copy to neuralnetworks/aidl/android/hardware/neuralnetworks/NumberOfCacheFiles.aidl
index 127f98e..1ca2676 100644
--- a/health/1.0/default/libhealthd/healthd_board_default.cpp
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/NumberOfCacheFiles.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,14 @@
* limitations under the License.
*/
-#include <healthd/healthd.h>
+package android.hardware.neuralnetworks;
-void healthd_board_init(struct healthd_config*)
-{
- // use defaults
-}
-
-int healthd_board_battery_update(struct android::BatteryProperties*)
-{
- // return 0 to log periodic polled battery status to kernel log
- return 0;
+/**
+ * Structure indicating how many files for model and numDataCache cache the driver needs to cache a
+ * single prepared model.
+ */
+@VintfStability
+parcelable NumberOfCacheFiles {
+ int numModelCache;
+ int numDataCache;
}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/Operand.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/Operand.aidl
new file mode 100644
index 0000000..4d2260f
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/Operand.aidl
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.neuralnetworks.DataLocation;
+import android.hardware.neuralnetworks.OperandExtraParams;
+import android.hardware.neuralnetworks.OperandLifeTime;
+import android.hardware.neuralnetworks.OperandType;
+
+/**
+ * Describes one operand of the model's graph.
+ */
+@VintfStability
+parcelable Operand {
+ /**
+ * The data type.
+ *
+ * Besides the values listed in {@link OperandType}, any value above
+ * {@link IDevice::OPERAND_TYPE_BASE_MAX} is possible and should be interpreted as an extension
+ * type according to {@link Model::extensionNameToPrefix}.
+ */
+ OperandType type;
+ /**
+ * Dimensions of the operand.
+ *
+ * For a scalar operand, dimensions.size() must be 0.
+ *
+ * A tensor operand with all dimensions specified has "fully specified" dimensions. Whenever
+ * possible (i.e., whenever the dimensions are known at model construction time), a tensor
+ * operand should have (but is not required to have) fully specified dimensions, in order to
+ * enable the best possible performance.
+ *
+ * If a tensor operand's dimensions are not fully specified, the dimensions of the operand are
+ * deduced from the operand dimensions and values of the operation for which that operand is an
+ * output or from the corresponding {@link OperationType::IF} or {@link OperationType::WHILE}
+ * operation input operand dimensions in the case of referenced subgraph input operands.
+ *
+ * In the following situations, a tensor operand's dimensions must be fully specified:
+ *
+ * . The operand has lifetime CONSTANT_COPY or CONSTANT_POOL.
+ *
+ * . The operand has lifetime SUBGRAPH_INPUT and belongs to the main subgraph. Fully
+ * specified dimensions must either be present in the Operand or they must be provided in
+ * the corresponding RequestArgument.
+ * EXCEPTION: If the input is optional and omitted (by setting the hasNoValue field of the
+ * corresponding RequestArgument to true) then it need not have fully specified
+ * dimensions.
+ *
+ * A tensor operand with some number of unspecified dimensions is represented by setting each
+ * unspecified dimension to 0.
+ *
+ * A tensor operand with unspecified rank is represented by providing an empty dimensions
+ * vector.
+ */
+ int[] dimensions;
+ /**
+ * Quantized scale of the operand.
+ *
+ * Must be 0 when not applicable to an operand type.
+ *
+ * See {@link OperandType}.
+ */
+ float scale;
+ /**
+ * Quantized zero-point offset of the operand.
+ *
+ * Must be 0 when not applicable to an operand type.
+ *
+ * See {@link OperandType}.
+ */
+ int zeroPoint;
+ /**
+ * How the operand is used.
+ */
+ OperandLifeTime lifetime;
+ /**
+ * Where to find the data for this operand.
+ * If the lifetime is TEMPORARY_VARIABLE, SUBGRAPH_INPUT, SUBGRAPH_OUTPUT, or NO_VALUE:
+ * - All the fields must be 0.
+ * If the lifetime is CONSTANT_COPY:
+ * - location.poolIndex is 0.
+ * - location.offset is the offset in bytes into Model.operandValues.
+ * - location.length is set.
+ * If the lifetime is CONSTANT_POOL:
+ * - location.poolIndex is set.
+ * - location.offset is the offset in bytes into the specified pool.
+ * - location.length is set.
+ * If the lifetime is SUBGRAPH:
+ * - location.poolIndex is 0.
+ * - location.offset is the index of the referenced subgraph in {@link Model::referenced}.
+ * - location.length is 0.
+ */
+ DataLocation location;
+ /**
+ * Additional parameters specific to a particular operand type.
+ */
+ @nullable OperandExtraParams extraParams;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandExtraParams.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandExtraParams.aidl
new file mode 100644
index 0000000..229754a
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandExtraParams.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.neuralnetworks.SymmPerChannelQuantParams;
+
+/**
+ * Parameters specific to a particular operand type.
+ */
+@VintfStability
+union OperandExtraParams {
+ /**
+ * Symmetric per-channel quantization parameters.
+ *
+ * Only applicable to operands of type TENSOR_QUANT8_SYMM_PER_CHANNEL.
+ */
+ SymmPerChannelQuantParams channelQuant;
+ /**
+ * Extension operand parameters.
+ *
+ * The framework treats this as an opaque data blob.
+ * The format is up to individual extensions.
+ */
+ byte[] extension;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandLifeTime.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandLifeTime.aidl
new file mode 100644
index 0000000..1d18149
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandLifeTime.aidl
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.neuralnetworks;
+
+/**
+ * How an operand is used.
+ */
+@VintfStability
+@Backing(type="int")
+enum OperandLifeTime {
+ /**
+ * The operand is internal to the model. It's created by an operation and consumed by other
+ * operations. It must be an output operand of exactly one operation.
+ */
+ TEMPORARY_VARIABLE,
+ /**
+ * The operand is an input of a subgraph. It must not be an output operand of any operation.
+ *
+ * An operand can't be both input and output of a subgraph.
+ */
+ SUBGRAPH_INPUT,
+ /**
+ * The operand is an output of a subgraph. It must be an output operand of exactly one
+ * operation.
+ *
+ * An operand can't be both input and output of a subgraph.
+ */
+ SUBGRAPH_OUTPUT,
+ /**
+ * The operand is a constant found in Model.operandValues. It must not be an output operand of
+ * any operation.
+ */
+ CONSTANT_COPY,
+ /**
+ * The operand is a constant that was specified via a Memory object. It must not be an output
+ * operand of any operation.
+ */
+ CONSTANT_POOL,
+ /**
+ * The operand does not have a value. This is valid only for optional arguments of operations.
+ */
+ NO_VALUE,
+ /**
+ * The operand is a reference to a subgraph. It must be an input to one or more
+ * {@link OperationType::IF} or {@link OperationType::WHILE} operations.
+ */
+ SUBGRAPH,
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandPerformance.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandPerformance.aidl
new file mode 100644
index 0000000..7fd86f9
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandPerformance.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.neuralnetworks.OperandType;
+import android.hardware.neuralnetworks.PerformanceInfo;
+
+/**
+ * Driver performance when operating on a particular data type. In the case of float32 data, this is
+ * used when the calculations are not relaxed.
+ */
+@VintfStability
+parcelable OperandPerformance {
+ OperandType type;
+ PerformanceInfo info;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandType.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandType.aidl
new file mode 100644
index 0000000..12edc0f
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/OperandType.aidl
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.neuralnetworks;
+
+/**
+ * Operand types.
+ *
+ * The type of an operand in a model.
+ *
+ * Types prefaced with TENSOR_* must be used for tensor data (i.e., tensors
+ * with at least one dimension). Types not prefaced by TENSOR_* represent
+ * scalar values and must have no dimensions.
+ */
+@VintfStability
+@Backing(type="int")
+enum OperandType {
+ /**
+ * A 32 bit floating point scalar value.
+ */
+ FLOAT32 = 0,
+ /**
+ * A signed 32 bit integer scalar value.
+ */
+ INT32 = 1,
+ /**
+ * An unsigned 32 bit integer scalar value.
+ */
+ UINT32 = 2,
+ /**
+ * A tensor of 32 bit floating point values.
+ */
+ TENSOR_FLOAT32 = 3,
+ /**
+ * A tensor of 32 bit integer values.
+ */
+ TENSOR_INT32 = 4,
+ /**
+ * A tensor of 8 bit unsigned integers that represent real numbers.
+ *
+ * Attached to this tensor are two numbers that can be used to convert the 8 bit integer to the
+ * real value and vice versa. These two numbers are:
+ * - scale: a 32 bit floating point value greater than zero.
+ * - zeroPoint: a 32 bit integer, in range [0, 255].
+ *
+ * The formula is:
+ * real_value = (integer_value - zeroPoint) * scale.
+ */
+ TENSOR_QUANT8_ASYMM = 5,
+ /**
+ * An 8 bit boolean scalar value.
+ *
+ * Values of this operand type are either true or false. A zero value represents false; any
+ * other value represents true.
+ */
+ BOOL = 6,
+ /**
+ * A tensor of 16 bit signed integers that represent real numbers.
+ *
+ * Attached to this tensor is a number representing real value scale that is used to convert the
+ * 16 bit number to a real value in the following way:
+ * realValue = integerValue * scale.
+ *
+ * scale is a 32 bit floating point with value greater than zero.
+ */
+ TENSOR_QUANT16_SYMM = 7,
+ /**
+ * A tensor of IEEE 754 16 bit floating point values.
+ */
+ TENSOR_FLOAT16 = 8,
+ /**
+ * A tensor of 8 bit boolean values.
+ *
+ * Values of this operand type are either true or false. A zero value represents false; any
+ * other value represents true.
+ */
+ TENSOR_BOOL8 = 9,
+ /**
+ * An IEEE 754 16 bit floating point scalar value.
+ */
+ FLOAT16 = 10,
+ /**
+ * A tensor of 8 bit signed integers that represent real numbers.
+ *
+ * This tensor is associated with additional fields that can be used to convert the 8 bit signed
+ * integer to the real value and vice versa. These fields are:
+ * - channelDim: a 32 bit unsigned integer indicating channel dimension.
+ * - scales: an array of positive 32 bit floating point values.
+ * The size of the scales array must be equal to dimensions[channelDim].
+ *
+ * {@link SymmPerChannelQuantParams} must hold the parameters for an Operand of this type.
+ * The channel dimension of this tensor must not be unknown (dimensions[channelDim] != 0).
+ *
+ * The formula is:
+ * realValue[..., C, ...] =
+ * integerValue[..., C, ...] * scales[C]
+ * where C is an index in the Channel dimension.
+ */
+ TENSOR_QUANT8_SYMM_PER_CHANNEL = 11,
+ /**
+ * A tensor of 16 bit unsigned integers that represent real numbers.
+ *
+ * Attached to this tensor are two numbers that can be used to convert the 16 bit integer to the
+ * real value and vice versa. These two numbers are:
+ * - scale: a 32 bit floating point value greater than zero.
+ * - zeroPoint: a 32 bit integer, in range [0, 65535].
+ *
+ * The formula is:
+ * real_value = (integer_value - zeroPoint) * scale.
+ */
+ TENSOR_QUANT16_ASYMM = 12,
+ /**
+ * A tensor of 8 bit signed integers that represent real numbers.
+ *
+ * Attached to this tensor is a number representing real value scale that is used to convert the
+ * 8 bit number to a real value in the following way:
+ * realValue = integerValue * scale.
+ *
+ * scale is a 32 bit floating point with value greater than zero.
+ */
+ TENSOR_QUANT8_SYMM = 13,
+ /**
+ * A tensor of 8 bit signed integers that represent real numbers.
+ *
+ * Attached to this tensor are two numbers that can be used to convert the 8 bit integer to the
+ * real value and vice versa. These two numbers are:
+ * - scale: a 32 bit floating point value greater than zero.
+ * - zeroPoint: a 32 bit integer, in range [-128, 127].
+ *
+ * The formula is:
+ * real_value = (integer_value - zeroPoint) * scale.
+ */
+ TENSOR_QUANT8_ASYMM_SIGNED = 14,
+ /**
+ * A reference to a subgraph.
+ *
+ * Must have the lifetime {@link OperandLifeTime::SUBGRAPH}.
+ */
+ SUBGRAPH = 15,
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/Operation.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/Operation.aidl
new file mode 100644
index 0000000..0c6032f
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/Operation.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.neuralnetworks.OperationType;
+
+/**
+ * Describes one operation of the model's graph.
+ */
+@VintfStability
+parcelable Operation {
+ /**
+ * The operation type.
+ *
+ * Besides the values listed in {@link OperationType}, any value above
+ * {@link IDevice::OPERATION_TYPE_BASE_MAX} is possible and should be interpreted as an
+ * extension type according to {@link Model::extensionNameToPrefix}.
+ */
+ OperationType type;
+ /**
+ * Describes the table that contains the indexes of the inputs of the operation. The offset is
+ * the index in the operandIndexes table.
+ */
+ int[] inputs;
+ /**
+ * Describes the table that contains the indexes of the outputs of the operation. The offset is
+ * the index in the operandIndexes table.
+ */
+ int[] outputs;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/OperationType.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/OperationType.aidl
new file mode 100644
index 0000000..3f49154
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/OperationType.aidl
@@ -0,0 +1,5131 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.neuralnetworks;
+
+/**
+ * Operation types.
+ *
+ * The type of an operation in a model.
+ */
+@VintfStability
+@Backing(type="int")
+enum OperationType {
+ /**
+ * Adds two tensors, element-wise.
+ *
+ * Takes two input tensors of identical {@link OperandType} and compatible
+ * dimensions. The output is the sum of both input tensors, optionally
+ * modified by an activation function.
+ *
+ * Two dimensions are compatible when:
+ * 1. they are equal, or
+ * 2. one of them is 1
+ *
+ * The size of the output is the maximum size along each dimension of the
+ * input operands. It starts with the trailing dimensions, and works its
+ * way forward.
+ *
+ * Example:
+ *
+ * input1.dimension = {4, 1, 2}
+ * input2.dimension = {5, 4, 3, 1}
+ * output.dimension = {5, 4, 3, 2}
+ *
+ * Since HAL version 1.2, generic zero-sized input tensor is supported. Zero
+ * dimension is only compatible with 0 or 1. The size of the output
+ * dimension is zero if either of corresponding input dimension is zero.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ * * {@link OperandType::TENSOR_INT32} (since HAL version 1.3)
+ *
+ * Supported tensor rank: up to 4
+ *
+ * Inputs:
+ * * 0: A tensor.
+ * * 1: A tensor of the same {@link OperandType}, and compatible dimensions
+ * as input0.
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scales and zeroPoint can be different from input0 scale and zeroPoint.
+ * * 2: An {@link OperandType::INT32} scalar, and has to be one of the
+ * {@link FusedActivationFunc} values. Specifies the activation to
+ * invoke on the result.
+ * For a {@link OperandType::TENSOR_INT32} tensor,
+ * the {@link FusedActivationFunc} must be "NONE".
+ *
+ * Outputs:
+ * * 0: The sum, a tensor of the same {@link OperandType} as input0.
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint can be different from inputs' scale and zeroPoint.
+ */
+ ADD = 0,
+ /**
+ * Performs a 2-D average pooling operation.
+ *
+ * The output dimensions are functions of the filter dimensions, stride, and
+ * padding.
+ *
+ * The values in the output tensor are computed as:
+ *
+ * output[b, i, j, channel] =
+ * sum_{di, dj}(
+ * input[b, strides[1] * i + di, strides[2] * j + dj, channel]
+ * ) / sum(1)
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+ * With the default data layout NHWC, the data is stored in the order of:
+ * [batch, height, width, channels]. Alternatively, the data layout could
+ * be NCHW, the data storage order of: [batch, channels, height, width].
+ * NCHW is supported since HAL version 1.2.
+ *
+ * Both explicit padding and implicit padding are supported.
+ *
+ * Inputs (explicit padding):
+ * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
+ * the input.
+ * Since HAL version 1.2, zero batches is supported for this tensor.
+ * * 1: An {@link OperandType::INT32} scalar, specifying the padding on
+ * the left, in the ‘width’ dimension.
+ * * 2: An {@link OperandType::INT32} scalar, specifying the padding on
+ * the right, in the ‘width’ dimension.
+ * * 3: An {@link OperandType::INT32} scalar, specifying the padding on
+ * the top, in the ‘height’ dimension.
+ * * 4: An {@link OperandType::INT32} scalar, specifying the padding on
+ * the bottom, in the ‘height’ dimension.
+ * * 5: An {@link OperandType::INT32} scalar, specifying the stride when
+ * walking through input in the ‘width’ dimension.
+ * * 6: An {@link OperandType::INT32} scalar, specifying the stride when
+ * walking through input in the ‘height’ dimension.
+ * * 7: An {@link OperandType::INT32} scalar, specifying the filter
+ * width.
+ * * 8: An {@link OperandType::INT32} scalar, specifying the filter
+ * height.
+ * * 9: An {@link OperandType::INT32} scalar, and has to be one of the
+ * {@link FusedActivationFunc} values. Specifies the activation to
+ * invoke on the result.
+ * * 10: An optional {@link OperandType::BOOL} scalar, default to false.
+ * Set to true to specify NCHW data layout for input0 and output0.
+ * Available since HAL version 1.2.
+ *
+ * Inputs (implicit padding):
+ * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
+ * the input.
+ * Since HAL version 1.2, zero batches is supported for this tensor.
+ * * 1: An {@link OperandType::INT32} scalar, specifying the implicit
+ * padding scheme, has to be one of the
+ * following values: {0 (NONE), 1 (SAME), 2 (VALID)}.
+ * * 2: An {@link OperandType::INT32} scalar, specifying the stride when
+ * walking through input in the ‘width’ dimension.
+ * * 3: An {@link OperandType::INT32} scalar, specifying the stride when
+ * walking through input in the ‘height’ dimension.
+ * * 4: An {@link OperandType::INT32} scalar, specifying the filter
+ * width.
+ * * 5: An {@link OperandType::INT32} scalar, specifying the filter
+ * height.
+ * * 6: An {@link OperandType::INT32} scalar, and has to be one of the
+ * {@link FusedActivationFunc} values. Specifies the activation to
+ * invoke on the result.
+ * * 7: An optional {@link OperandType::BOOL} scalar, default to false.
+ * Set to true to specify NCHW data layout for input0 and output0.
+ * Available since HAL version 1.2.
+ *
+ * Outputs:
+ * * 0: The output 4-D tensor, of shape
+ * [batches, out_height, out_width, depth].
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint must be the same as input0.
+ */
+ AVERAGE_POOL_2D = 1,
+ /**
+ * Concatenates the input tensors along the given dimension.
+ *
+ * The input tensors must have identical {@link OperandType} and the same
+ * dimensions except the dimension along the concatenation axis.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * (full support since HAL version 1.2, see the input section)
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: up to 4
+ *
+ * Inputs:
+ * * 0 ~ n-1: The list of n input tensors, of shape
+ * [D0, D1, ..., Daxis(i), ..., Dm].
+ * Before HAL version 1.2, all input tensors of
+ * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * must have the same scale and zeroPoint as the output tensor.
+ * Input tensors of
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}
+ * are allowed to have different scale and zeroPoint.
+ * Since HAL version 1.2, zero-sized tensors are supported.
+ * * n: An {@link OperandType::INT32} scalar, specifying the
+ * concatenation axis.
+ *
+ * Outputs:
+ * * 0: The output, a tensor of the same {@link OperandType} as the input
+ * tensors. The output shape is [D0, D1, ..., sum(Daxis(i)), ..., Dm].
+ * Since HAL version 1.2, for a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+ * the scale and zeroPoint values can be different from
+ * input tensors. Before HAL version 1.2 they have to be the same as for the input tensors.
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint values can be different from input tensors.
+ */
+ CONCATENATION = 2,
+ /**
+ * Performs a 2-D convolution operation.
+ *
+ * The CONV_2D op sweeps a 2-D filter that can mix channels together over a
+ * batch of images, applying the filter to each window of each image of the
+ * appropriate size.
+ *
+ * The output dimensions are functions of the filter dimensions, stride, and
+ * padding.
+ *
+ * The values in the output tensor are computed as:
+ *
+ * output[b, i, j, channel] =
+ * sum_{di, dj, k} (
+ * input[b, strides[1] * i + di, strides[2] * j + dj, k] *
+ * filter[channel, di, dj, k]
+ * ) + bias[channel]
+ *
+ * Supported tensor {@link OperandType} configurations:
+ * * 32 bit floating point:
+ * * * {@link OperandType::TENSOR_FLOAT32} for input, filter, output, and bias.
+ *
+ * * Quantized:
+ * * * {@link OperandType::TENSOR_QUANT8_ASYMM} for input, filter, and output.
+ * * * {@link OperandType::TENSOR_INT32} for bias (with scale set to
+ * * * input.scale * filter.scale).
+ *
+ * Available since HAL version 1.2:
+ * * 16 bit floating point:
+ * * * {@link OperandType::TENSOR_FLOAT16} for input, filter, output, and bias.
+ *
+ * * Quantized with symmetric per channel quantization for the filter:
+ * * * {@link OperandType::TENSOR_QUANT8_ASYMM} for input, and output.
+ * * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} for filter.
+ * * * {@link OperandType::TENSOR_INT32} for bias (scale set to 0.0,
+ * * * each value scaling is separate and equal to input.scale * filter.scales[channel]).
+ *
+ * Available since HAL version 1.3:
+ * * Quantized signed (since HAL version 1.3):
+ * * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} for input, filter, and output.
+ * * * {@link OperandType::TENSOR_INT32} for bias (with scale set to
+ * * * input.scale * filter.scale).
+ *
+ * * Quantized signed with filter symmetric per channel quantization (since HAL version 1.3):
+ * * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} for input, and output.
+ * * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} for filter.
+ * * * {@link OperandType::TENSOR_INT32} for bias (scale set to 0.0,
+ * * * each value scaling is separate and equal to input.scale * filter.scales[channel]).
+ *
+ * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+ * With the default data layout NHWC, the data is stored in the order of:
+ * [batch, height, width, channels]. Alternatively, the data layout could
+ * be NCHW, the data storage order of: [batch, channels, height, width].
+ * NCHW is supported since HAL version 1.2.
+ *
+ * Both explicit padding and implicit padding are supported.
+ *
+ * Inputs (explicit padding):
+ * * 0: A 4-D tensor, of shape [batches, height, width, depth_in],
+ * specifying the input.
+ * Since HAL version 1.2, zero batches is supported for this tensor.
+ * * 1: A 4-D tensor, of shape
+ * [depth_out, filter_height, filter_width, depth_in], specifying the
+ * filter.
+ * For tensor of type {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL}
+ * the channel dimension (SymmPerChannelQuantParams::channelDim)
+ * must be set to 0.
+ * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input
+ * tensor of type {@link OperandType::TENSOR_FLOAT32}
+ * or {@link OperandType::TENSOR_FLOAT16} the bias must be of the same type.
+ * For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * and {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED},
+ * the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint
+ * of 0 and bias_scale == input_scale * filter_scale.
+ * For filter tensor of {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL},
+ * the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint of 0
+ * and bias_scale of 0. The actual scale of each value 'i' is equal to
+ * bias_scale[i] = input_scale * filter_scale[i].
+ * * 3: An {@link OperandType::INT32} scalar, specifying the padding on
+ * the left, in the ‘width’ dimension.
+ * * 4: An {@link OperandType::INT32} scalar, specifying the padding on
+ * the right, in the ‘width’ dimension.
+ * * 5: An {@link OperandType::INT32} scalar, specifying the padding on
+ * the top, in the ‘height’ dimension.
+ * * 6: An {@link OperandType::INT32} scalar, specifying the padding on
+ * the bottom, in the ‘height’ dimension.
+ * * 7: An {@link OperandType::INT32} scalar, specifying the stride when
+ * walking through input in the ‘width’ dimension.
+ * * 8: An {@link OperandType::INT32} scalar, specifying the stride when
+ * walking through input in the ‘height’ dimension.
+ * * 9: An {@link OperandType::INT32} scalar, and has to be one of the
+ * {@link FusedActivationFunc} values. Specifies the activation to
+ * invoke on the result.
+ * * 10: An optional {@link OperandType::BOOL} scalar, default to false.
+ * Set to true to specify NCHW data layout for input0 and output0.
+ * Available since HAL version 1.2.
+ * * 11: An optional {@link OperandType::INT32} scalar, specifying the dilation
+ * factor for width. Defaults to 1. If set to k > 1, there will be k-1 skipped
+ * cells between each filter element on width dimension. If this input is set,
+ * input 12 (dilation factor for height) must be specified as well.
+ * Available since HAL version 1.2.
+ * * 12: An optional {@link OperandType::INT32} scalar, specifying the dilation
+ * factor for height. Defaults to 1. If set to k > 1, there will be k-1 skipped
+ * cells between each filter element on height dimension. If this input is set,
+ * input 11 (dilation factor for width) must be specified as well.
+ * Available since HAL version 1.2.
+ *
+ * Inputs (implicit padding):
+ * * 0: A 4-D tensor, of shape [batches, height, width, depth_in],
+ * specifying the input.
+ * Since HAL version 1.2, zero batches is supported for this tensor.
+ * * 1: A 4-D tensor, of shape
+ * [depth_out, filter_height, filter_width, depth_in], specifying the
+ * filter.
+ * For tensor of type {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL}
+ * the channel dimension (SymmPerChannelQuantParams::channelDim)
+ * must be set to 0.
+ * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input
+ * tensor of type {@link OperandType::TENSOR_FLOAT32}
+ * or {@link OperandType::TENSOR_FLOAT16} the bias must be of the same
+ * type.
+ * For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * and {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED},
+ * the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint
+ * of 0 and bias_scale == input_scale * filter_scale.
+ * For filter tensor of {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL},
+ * the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint of 0
+ * and bias_scale of 0. The actual scale of each value 'i' is equal to
+ * bias_scale[i] = input_scale * filter_scale[i].
+ * * 3: An {@link OperandType::INT32} scalar, specifying the implicit
+ * padding scheme, has to be one of the
+ * following values: {0 (NONE), 1 (SAME), 2 (VALID)}.
+ * * 4: An {@link OperandType::INT32} scalar, specifying the stride when
+ * walking through input in the ‘width’ dimension.
+ * * 5: An {@link OperandType::INT32} scalar, specifying the stride when
+ * walking through input in the ‘height’ dimension.
+ * * 6: An {@link OperandType::INT32} scalar, and has to be one of the
+ * {@link FusedActivationFunc} values. Specifies the activation to
+ * invoke on the result.
+ * * 7: An optional {@link OperandType::BOOL} scalar, default to false.
+ * Set to true to specify NCHW data layout for input0 and output0.
+ * Available since HAL version 1.2.
+ * * 8: An optional {@link OperandType::INT32} scalar, specifying the dilation
+ * factor for width. Defaults to 1. If set to k > 1, there will be k-1 skipped
+ * cells between each filter element on width dimension. If this input is set,
+ * input 9 (dilation factor for height) must be specified as well.
+ * Available since HAL version 1.2.
+ * * 9: An optional {@link OperandType::INT32} scalar, specifying the dilation
+ * factor for height. Defaults to 1. If set to k > 1, there will be k-1 skipped
+ * cells between each filter element on height dimension. If this input is set,
+ * input 8 (dilation factor for width) must be specified as well.
+ * Available since HAL version 1.2.
+ *
+ * Outputs:
+ * * 0: The output 4-D tensor, of shape
+ * [batches, out_height, out_width, depth_out].
+ * Before HAL version 1.2, for output tensor of {@link OperandType::TENSOR_QUANT8_ASYMM},
+ * the following condition must be satisfied: output_scale > input_scale * filter_scale
+ */
+ CONV_2D = 3,
+ /**
+ * Performs a depthwise 2-D convolution operation.
+ *
+ * Given an input tensor of shape [batches, height, width, depth_in] and a
+ * filter tensor of shape [1, filter_height, filter_width, depth_out]
+ * containing depth_out convolutional filters of depth 1, DEPTHWISE_CONV
+ * applies a different filter to each input channel (expanding from 1
+ * channel to channel_multiplier channels for each), then concatenates the
+ * results together.
+ *
+ * The output has depth_out = depth_in * depth_multiplier channels.
+ * The output dimensions are functions of the filter dimensions, stride, and
+ * padding.
+ *
+ * The values in the output tensor are computed as:
+ *
+ * output[b, i, j, k * channel_multiplier + q] =
+ * sum_{di, dj} (
+ * input[b, strides[1] * i + di, strides[2] * j + dj, k] *
+ * filter[1, di, dj, k * channel_multiplier + q]
+ * ) + bias[k * channel_multiplier + q]
+ *
+ * Supported tensor {@link OperandType} configurations:
+ * * 32 bit floating point:
+ * * * {@link OperandType::TENSOR_FLOAT32} for input, filter, output, and bias.
+ *
+ * * Quantized:
+ * * * {@link OperandType::TENSOR_QUANT8_ASYMM} for input, filter, and output.
+ * * * {@link OperandType::TENSOR_INT32} for bias (with scale set to
+ * * * input.scale * filter.scale).
+ *
+ * Available since HAL version 1.2:
+ * * 16 bit floating point:
+ * * * {@link OperandType::TENSOR_FLOAT16} for input, filter, output, and bias.
+ *
+ * * Quantized with symmetric per channel quantization for the filter:
+ * * * {@link OperandType::TENSOR_QUANT8_ASYMM} for input, and output.
+ * * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} for filter.
+ * * * {@link OperandType::TENSOR_INT32} for bias (scale set to 0.0,
+ * * * each value scaling is separate and equal to input.scale * filter.scales[channel]).
+ *
+ * Available since HAL version 1.3:
+ * * Quantized signed (since HAL version 1.3):
+ * * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} for input, filter, and output.
+ * * * {@link OperandType::TENSOR_INT32} for bias (with scale set to
+ * * * input.scale * filter.scale).
+ *
+ * * Quantized signed with filter symmetric per channel quantization (since HAL version 1.3):
+ * * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} for input, and output.
+ * * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} for filter.
+ * * * {@link OperandType::TENSOR_INT32} for bias (scale set to 0.0,
+ * * * each value scaling is separate and equal to input.scale * filter.scales[channel]).
+ *
+ * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+ * With the default data layout NHWC, the data is stored in the order of:
+ * [batch, height, width, channels]. Alternatively, the data layout could
+ * be NCHW, the data storage order of: [batch, channels, height, width].
+ * NCHW is supported since HAL version 1.2.
+ *
+ * Both explicit padding and implicit padding are supported.
+ *
+ * Inputs (explicit padding):
+ * * 0: A 4-D tensor, of shape [batches, height, width, depth_in],
+ * specifying the input.
+ * * 1: A 4-D tensor, of shape [1, filter_height, filter_width, depth_out],
+ * specifying the filter.
+ * For tensor of type {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL}
+ * the channel dimension (SymmPerChannelQuantParams::channelDim)
+ * must be set to 3.
+ * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input
+ * tensor of type {@link OperandType::TENSOR_FLOAT32}
+ * or {@link OperandType::TENSOR_FLOAT16} the bias must be of the same type.
+ * For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * and {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED},
+ * the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint
+ * of 0 and bias_scale == input_scale * filter_scale.
+ * For filter tensor of {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL},
+ * the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint of 0
+ * and bias_scale of 0. The actual scale of each value 'i' is equal to
+ * bias_scale[i] = input_scale * filter_scale[i].
+ * * 3: An {@link OperandType::INT32} scalar, specifying the padding on
+ * the left, in the ‘width’ dimension.
+ * * 4: An {@link OperandType::INT32} scalar, specifying the padding on
+ * the right, in the ‘width’ dimension.
+ * * 5: An {@link OperandType::INT32} scalar, specifying the padding on
+ * the top, in the ‘height’ dimension.
+ * * 6: An {@link OperandType::INT32} scalar, specifying the padding on
+ * the bottom, in the ‘height’ dimension.
+ * * 7: An {@link OperandType::INT32} scalar, specifying the stride when
+ * walking through input in the ‘width’ dimension.
+ * * 8: An {@link OperandType::INT32} scalar, specifying the stride when
+ * walking through input in the ‘height’ dimension.
+ * * 9: An {@link OperandType::INT32} scalar, specifying the depthwise
+ * multiplier.
+ * * 10: An {@link OperandType::INT32} scalar, and has to be one of the
+ * {@link FusedActivationFunc} values. Specifies the activation to
+ * invoke on the result.
+ * * 11: An optional {@link OperandType::BOOL} scalar, default to false.
+ * Set to true to specify NCHW data layout for input0 and output0.
+ * Available since HAL version 1.2.
+ * * 12: An optional {@link OperandType::INT32} scalar, specifying the dilation
+ * factor for width. Defaults to 1. If set to k > 1, there will be k-1 skipped
+ * cells between each filter element on width dimension. If this input is set,
+ * input 13 (dilation factor for height) must be specified as well.
+ * Available since HAL version 1.2.
+ * * 13: An optional {@link OperandType::INT32} scalar, specifying the dilation
+ * factor for height. Defaults to 1. If set to k > 1, there will be k-1 skipped
+ * cells between each filter element on height dimension. If this input is set,
+ * input 12 (dilation factor for width) must be specified as well.
+ * Available since HAL version 1.2.
+ *
+ * Inputs (implicit padding):
+ * * 0: A 4-D tensor, of shape [batches, height, width, depth_in],
+ * specifying the input.
+ * * 1: A 4-D tensor, of shape [1, filter_height, filter_width, depth_out],
+ * specifying the filter.
+ * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input
+ * tensor of type {@link OperandType::TENSOR_FLOAT32}
+ * or {@link OperandType::TENSOR_FLOAT16} the bias must be of the same type.
+ * For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * and {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED},
+ * the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint
+ * of 0 and bias_scale == input_scale * filter_scale.
+ * For filter tensor of {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL},
+ * the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint of 0
+ * and bias_scale of 0. The actual scale of each value 'i' is equal to
+ * bias_scale[i] = input_scale * filter_scale[i].
+ * * 3: An {@link OperandType::INT32} scalar, specifying the implicit
+ * padding scheme, has to be one of the
+ * following values: {0 (NONE), 1 (SAME), 2 (VALID)}.
+ * * 4: An {@link OperandType::INT32} scalar, specifying the stride when
+ * walking through input in the ‘width’ dimension.
+ * * 5: An {@link OperandType::INT32} scalar, specifying the stride when
+ * walking through input in the ‘height’ dimension.
+ * * 6: An {@link OperandType::INT32} scalar, specifying the depthwise
+ * multiplier.
+ * * 7: An {@link OperandType::INT32} scalar, and has to be one of the
+ * {@link FusedActivationFunc} values. Specifies the activation to
+ * invoke on the result.
+ * * 8: An optional {@link OperandType::BOOL} scalar, default to false.
+ * Set to true to specify NCHW data layout for input0 and output0.
+ * Available since HAL version 1.2.
+ * * 9: An optional {@link OperandType::INT32} scalar, specifying the dilation
+ * factor for width. Defaults to 1. If set to k > 1, there will be k-1 skipped
+ * cells between each filter element on width dimension. If this input is set,
+ * input 10 (dilation factor for height) must be specified as well.
+ * Available since HAL version 1.2.
+ * * 10: An optional {@link OperandType::INT32} scalar, specifying the dilation
+ * factor for height. Defaults to 1. If set to k > 1, there will be k-1 skipped
+ * cells between each filter element on height dimension. If this input is set,
+ * input 9 (dilation factor for width) must be specified as well.
+ * Available since HAL version 1.2.
+ *
+ * Outputs:
+ * * 0: The output 4-D tensor, of shape
+ * [batches, out_height, out_width, depth_out]. Before HAL version 1.2, for
+ * output tensor of {@link OperandType::TENSOR_QUANT8_ASYMM},
+ * the following condition must be satisfied:
+ * output_scale > input_scale * filter_scale
+ */
+ DEPTHWISE_CONV_2D = 4,
+ /**
+ * Rearranges data from depth into blocks of spatial data.
+ *
+ * More specifically, this op outputs a copy of the input tensor where
+ * values from the depth dimension are moved in spatial blocks to the height
+ * and width dimensions. The value block_size indicates the input block size
+ * and how the data is moved.
+ *
+ * Chunks of data of size block_size * block_size from depth are rearranged
+ * into non-overlapping blocks of size block_size x block_size.
+ *
+ * The width of the output tensor is input_depth * block_size, whereas the
+ * height is input_height * block_size. The depth of the input tensor must
+ * be divisible by block_size * block_size
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+ * With the default data layout NHWC, the data is stored in the order of:
+ * [batch, height, width, channels]. Alternatively, the data layout could
+ * be NCHW, the data storage order of: [batch, channels, height, width].
+ * NCHW is supported since HAL version 1.2.
+ *
+ * Inputs:
+ * * 0: A 4-D tensor, of shape [batches, height, width, depth_in],
+ * specifying the input.
+ * * 1: An {@link OperandType::INT32} scalar, specifying the block_size.
+ * block_size must be >=1 and block_size * block_size must be a divisor
+ * of the input depth.
+ * * 2: An optional {@link OperandType::BOOL} scalar, default to false.
+ * Set to true to specify NCHW data layout for input0 and output0.
+ * Available since HAL version 1.2.
+ *
+ * Outputs:
+ * * 0: The output 4-D tensor, of shape [batch, height*block_size,
+ * width*block_size, depth/(block_size*block_size)].
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint must be the same as input0.
+ */
+ DEPTH_TO_SPACE = 5,
+ /**
+ * Dequantizes the input tensor.
+ *
+ * The formula is:
+ *
+ * output = (input - zeroPoint) * scale.
+ *
+ * Supported input tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_SYMM} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported output tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}.
+ *
+ * Supported tensor rank: up to 4
+ *
+ * Inputs:
+ * * 0: A tensor.
+ * Since HAL version 1.2, this tensor may be zero-sized.
+ *
+ * Outputs:
+ * * 0: A tensor with the same shape as input0.
+ */
+ DEQUANTIZE = 6,
+ /**
+ * Looks up sub-tensors in the input tensor.
+ *
+ * This operator takes for input a tensor of values (Values) and
+ * a one-dimensional tensor of selection indices (Lookups).
+ * The output tensor is the concatenation of sub-tensors of Values as
+ * selected by Lookups.
+ *
+ * Think of Values as being sliced along its first dimension:
+ * The entries in Lookups select which slices are concatenated together
+ * to create the output tensor.
+ *
+ * For example, if Values has shape of [40, 200, 300] and
+ * Lookups has shape of [3], all three values found in Lookups are
+ * expected to be between 0 and 39. The resulting tensor must
+ * have shape of [3, 200, 300].
+ *
+ * If a value in Lookups is out of bounds, the operation must fail
+ * and an error must be reported.
+ *
+ * Supported value tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.3)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_INT32} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported value tensor rank: from 2
+ *
+ * Inputs:
+ * * 0: Lookups. A 1-D tensor of {@link OperandType::TENSOR_INT32}.
+ * The values are indices into the first dimension of Values.
+ * * 1: Values. An n-D tensor, where n >= 2, from which sub-tensors are
+ * extracted.
+ *
+ * Output:
+ * * 0: A n-D tensor with the same rank and shape as the Values
+ * tensor, except for the first dimension which has the same size
+ * as Lookups' only dimension.
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint must be the same as input1.
+ */
+ EMBEDDING_LOOKUP = 7,
+ /**
+ * Computes element-wise floor() on the input tensor.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ *
+ * Supported tensor rank: up to 4
+ *
+ * Inputs:
+ * * 0: A tensor.
+ *
+ * Outputs:
+ * * 0: The output tensor, of the same {@link OperandType} and dimensions as
+ * the input tensor.
+ */
+ FLOOR = 8,
+ /**
+ * Denotes a fully (densely) connected layer, which connects all elements
+ * in the input tensor with each element in the output tensor.
+ *
+ * This layer implements the operation:
+ *
+ * outputs = activation(inputs * weights’ + bias)
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: up to 4.
+ *
+ * Inputs:
+ * * 0: A tensor of at least rank 2, specifying the input. If rank is
+ * greater than 2, then it gets flattened to a 2-D Tensor. The
+ * (flattened) 2-D Tensor is reshaped (if necessary) to
+ * [batch_size, input_size], where "input_size" corresponds to the
+ * number of inputs to the layer, matching the second dimension of
+ * weights, and "batch_size" is calculated by dividing the number of
+ * elements by "input_size".
+ * Since HAL version 1.2, zero batch_size is supported for this tensor.
+ * * 1: A 2-D tensor, specifying the weights, of shape
+ * [num_units, input_size], where "num_units" corresponds to the number
+ * of output nodes.
+ * * 2: A 1-D tensor, of shape [num_units], specifying the bias. For input
+ * tensor of {@link OperandType::TENSOR_FLOAT32}, the bias should
+ * also be of {@link OperandType::TENSOR_FLOAT32}.
+ * For input tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * and {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED},
+ * the bias should be of {@link OperandType::TENSOR_INT32},
+ * with zeroPoint of 0 and bias_scale == input_scale * filter_scale.
+ * * 3: An {@link OperandType::INT32} scalar, and has to be one of the
+ * {@link FusedActivationFunc} values. Specifies the activation to
+ * invoke on the result.
+ *
+ * Outputs:
+ * * 0: The output tensor, of shape [batch_size, num_units]. Before HAL version 1.2, for
+ * output tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}, the following
+ * condition must be satisfied: output_scale > input_scale * filter_scale.
+ */
+ FULLY_CONNECTED = 9,
+ /**
+ * Looks up sub-tensors in the input tensor using a key-value map.
+ *
+ * This operator takes for input a tensor of values (Values),
+ * a one-dimensional tensor of selection values (Lookups) and
+ * a one-dimensional tensor that maps these values to Values
+ * indexes. The output tensor is the concatenation of sub-tensors of
+ * Values as selected by Lookups via Keys.
+ *
+ * Think of Values as being sliced along its outer-most dimension.
+ * The output is a concatenation of selected slices, with one slice
+ * for each entry of Lookups. The slice selected is the one at the
+ * same index as the Maps entry that matches the value in Lookups.
+ *
+ * For a hit, the corresponding sub-tensor of Values is included
+ * in the Output tensor. For a miss, the corresponding sub-tensor in
+ * Output must have zero values.
+ *
+ * For example, if Values has shape of [40, 200, 300],
+ * Keys should have a shape of [40]. If Lookups tensor has shape
+ * of [3], three slices are being concatenated, so the resulting tensor
+ * must have the shape of [3, 200, 300]. If the first entry in Lookups
+ * has the value 123456, that value must be located in Keys tensor.
+ * If the sixth entry of Keys contains 123456, the sixth slice of Values
+ * must be selected. If no entry in Keys has 123456, a slice of zeroes
+ * must be concatenated.
+ *
+ * Supported value tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_INT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ *
+ * Supported value tensor rank: from 2
+ *
+ * Inputs:
+ * * 0: Lookups. A 1-D {@link OperandType::TENSOR_INT32} tensor with
+ * shape [ k ].
+ * * 1: Keys. A 1-D {@link OperandType::TENSOR_INT32} tensor with shape
+ * [ n ]; Keys and Values pair represent a map, i.e., the ith element
+ * in Keys (Keys[i]) is the key to select the ith sub-tensor in Values
+ * (Values[i]), where 0 <= i <= n-1. Keys tensor *MUST* be sorted in
+ * ascending order.
+ * * 2: Values. A tensor with shape of [ n, … ]; i.e., the first dimension
+ * must be n.
+ *
+ * Outputs:
+ * * 0: Output. A tensor with shape [ k …].
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+ * the scale and zeroPoint must be the same as input2.
+ * * 1: Hits. A boolean tensor with shape [ k ] indicates whether the lookup
+ * hits (True) or not (False).
+ * Stored as {@link OperandType::TENSOR_QUANT8_ASYMM} with offset 0
+ * and scale 1.0f.
+ * A non-zero byte represents True, a hit. A zero indicates otherwise.
+ */
+ HASHTABLE_LOOKUP = 10,
+ /**
+ * Applies L2 normalization along the axis dimension.
+ *
+ * The values in the output tensor are computed as:
+ *
+ * output[batch, row, col, channel] =
+ * input[batch, row, col, channel] /
+ * sqrt(sum_{c} pow(input[batch, row, col, c], 2))
+ *
+ * By default the axis dimension is the last dimension of the input tensor.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: up to 4
+ * Tensors with rank less than 4 are only supported since HAL version 1.2.
+ *
+ * Inputs:
+ * * 0: An n-D tensor, specifying the tensor to be normalized.
+ * * 1: An optional {@link OperandType::INT32} scalar, default to -1,
+ * specifying the dimension normalization would be performed on.
+ * Negative index is used to specify axis from the end (e.g. -1 for
+ * the last axis). Must be in the range [-n, n).
+ * Available since HAL version 1.2.
+ *
+ * Outputs:
+ * * 0: A tensor of the same {@link OperandType} and same shape as input0.
+ * For {@link OperandType::TENSOR_QUANT8_ASYMM},
+ * the scale must be 1.f / 128 and the zeroPoint must be 128.
+ * For {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED},
+ * the scale must be 1.f / 128 and the zeroPoint must be 0.
+ *
+ * NOTE: Before HAL version 1.3, if the elements along an axis are all zeros,
+ * the result is undefined. Since HAL version 1.3, if the elements along an axis
+ * are all zeros, the result is logical zero.
+ */
+ L2_NORMALIZATION = 11,
+ /**
+ * Performs an 2-D L2 pooling operation.
+ *
+ * The output dimensions are functions of the filter dimensions, stride, and
+ * padding.
+ *
+ * The values in the output tensor are computed as:
+ *
+ * output[b, i, j, c] =
+ * sqrt(sum_{di, dj} pow(input[b, strides[1] * i + di, strides[2] * j + dj, c], 2) /
+ * sum(1))
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ *
+ * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+ * With the default data layout NHWC, the data is stored in the order of:
+ * [batch, height, width, channels]. Alternatively, the data layout could
+ * be NCHW, the data storage order of: [batch, channels, height, width].
+ * NCHW is supported since HAL version 1.2.
+ *
+ * Both explicit padding and implicit padding are supported.
+ *
+ * Inputs (explicit padding):
+ * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
+ * the input.
+ * Since HAL version 1.2, zero batches is supported for this tensor.
+ * * 1: An {@link OperandType::INT32} scalar, specifying the padding on
+ * the left, in the ‘width’ dimension.
+ * * 2: An {@link OperandType::INT32} scalar, specifying the padding on
+ * the right, in the ‘width’ dimension.
+ * * 3: An {@link OperandType::INT32} scalar, specifying the padding on
+ * the top, in the ‘height’ dimension.
+ * * 4: An {@link OperandType::INT32} scalar, specifying the padding on
+ * the bottom, in the ‘height’ dimension.
+ * * 5: An {@link OperandType::INT32} scalar, specifying the stride when
+ * walking through input in the ‘width’ dimension.
+ * * 6: An {@link OperandType::INT32} scalar, specifying the stride when
+ * walking through input in the ‘height’ dimension.
+ * * 7: An {@link OperandType::INT32} scalar, specifying the filter
+ * width.
+ * * 8: An {@link OperandType::INT32} scalar, specifying the filter
+ * height.
+ * * 9: An {@link OperandType::INT32} scalar, and has to be one of the
+ * {@link FusedActivationFunc} values. Specifies the activation to
+ * invoke on the result.
+ * * 10: An optional {@link OperandType::BOOL} scalar, default to false.
+ * Set to true to specify NCHW data layout for input0 and output0.
+ * Available since HAL version 1.2.
+ *
+ * Inputs (implicit padding):
+ * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
+ * the input.
+ * Since HAL version 1.2, zero batches is supported for this tensor.
+ * * 1: An {@link OperandType::INT32} scalar, specifying the implicit
+ * padding scheme, has to be one of the
+ * following values: {0 (NONE), 1 (SAME), 2 (VALID)}.
+ * * 2: An {@link OperandType::INT32} scalar, specifying the stride when
+ * walking through input in the ‘width’ dimension.
+ * * 3: An {@link OperandType::INT32} scalar, specifying the stride when
+ * walking through input in the ‘height’ dimension.
+ * * 4: An {@link OperandType::INT32} scalar, specifying the filter
+ * width.
+ * * 5: An {@link OperandType::INT32} scalar, specifying the filter
+ * height.
+ * * 6: An {@link OperandType::INT32} scalar, and has to be one of the
+ * {@link FusedActivationFunc} values. Specifies the activation to
+ * invoke on the result.
+ * * 7: An optional {@link OperandType::BOOL} scalar, default to false.
+ * Set to true to specify NCHW data layout for input0 and output0.
+ * Available since HAL version 1.2.
+ *
+ * Outputs:
+ * * 0: The output 4-D tensor, of shape
+ * [batches, out_height, out_width, depth].
+ */
+ L2_POOL_2D = 12,
+ /**
+ * Applies Local Response Normalization along the depth dimension.
+ *
+ * The 4-D input tensor is treated as a 3-D array of 1-D vectors (along the
+ * last dimension), and each vector is normalized independently. Within a
+ * given vector, each component is divided by the weighted, squared sum of
+ * inputs within depth_radius.
+ *
+ * The output is calculated using this formula:
+ *
+ * sqr_sum[a, b, c, d] = sum(
+ * pow(input[a, b, c, d - depth_radius : d + depth_radius + 1], 2))
+ * output = input / pow((bias + alpha * sqr_sum), beta)
+ *
+ * For input tensor with rank less than 4, independently normalizes each
+ * 1-D slice along specified dimension.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ *
+ * Supported tensor rank: up to 4
+ * Tensors with rank less than 4 are only supported since HAL version 1.2.
+ *
+ * Inputs:
+ * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
+ * the input.
+ * * 1: An {@link OperandType::INT32} scalar, specifying the radius of
+ * the normalization window.
+ * * 2: A scalar, specifying the bias, must not be zero.
+ * For input tensor of {@link OperandType::TENSOR_FLOAT16}, the bias
+ * value must be of {@link OperandType::FLOAT16}.
+ * For input tensor of {@link OperandType::TENSOR_FLOAT32}, the bias
+ * value must be of {@link OperandType::FLOAT32}.
+ * * 3: A scalar, specifying the scale factor, alpha.
+ * For input tensor of {@link OperandType::TENSOR_FLOAT16}, the
+ * alpha value must be of {@link OperandType::FLOAT16}.
+ * For input tensor of {@link OperandType::TENSOR_FLOAT32}, the
+ * alpha value must be of {@link OperandType::FLOAT32}.
+ * * 4: A scalar, specifying the exponent, beta.
+ * For input tensor of {@link OperandType::TENSOR_FLOAT16}, the beta
+ * value must be of {@link OperandType::FLOAT16}.
+ * For input tensor of {@link OperandType::TENSOR_FLOAT32}, the beta
+ * value must be of {@link OperandType::FLOAT32}.
+ * * 5: An optional {@link OperandType::INT32} scalar, default to -1,
+ * specifying the dimension normalization would be performed on.
+ * Negative index is used to specify axis from the end (e.g. -1 for
+ * the last axis). Must be in the range [-n, n).
+ * Available since HAL version 1.2.
+ *
+ * Outputs:
+ * * 0: The output tensor of same shape as input0.
+ */
+ LOCAL_RESPONSE_NORMALIZATION = 13,
+ /**
+ * Computes sigmoid activation on the input tensor element-wise.
+ *
+ * The output is calculated using this formula:
+ *
+ * output = 1 / (1 + exp(-input))
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: up to 4.
+ *
+ * Inputs:
+ * * 0: A tensor, specifying the input.
+ * Since HAL version 1.2, this tensor may be zero-sized.
+ *
+ * Outputs:
+ * * 0: The output tensor of same shape as input0.
+ * For {@link OperandType::TENSOR_QUANT8_ASYMM},
+ * the scale must be 1.f / 256 and the zeroPoint must be 0.
+ * For {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED},
+ * the scale must be 1.f / 256 and the zeroPoint must be -128.
+ */
+ LOGISTIC = 14,
+ /**
+ * Projects an input to a bit vector via locality senstive hashing.
+ *
+ * Supported input tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_INT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ *
+ * Supported input tensor rank: from 1
+ *
+ * Inputs:
+ * * 0: Hash functions. Dim.size == 2, DataType: Float.
+ * Tensor[0].Dim[0]: 15 of hash functions.
+ * Tensor[0].Dim[1]: 16 of projected output bits generated by each
+ * hash function.
+ * If the projection type is Sparse:
+ * Tensor[0].Dim[1] + ceil(log2(Tensor[0].Dim[0])) <= 32
+ *
+ * * 1: Input. Dim.size >= 1, no restriction on DataType.
+ * * 2: Weight. Optional. Dim.size == 1, DataType: Float.
+ * If not set, each input element is considered to have the same weight
+ * of 1.0.
+ * Tensor[1].Dim[0] == Tensor[2].Dim[0]
+ * * 3: Type:
+ * Sparse:
+ * Value LSHProjectionType_SPARSE(=3) (since HAL version 1.2).
+ * Computed bit vector is considered to be sparse.
+ * Each output element is an int32 made up of multiple bits
+ * computed from hash functions.
+ *
+ * NOTE: To avoid collisions across hash functions, an offset value
+ * of k * (1 << Tensor[0].Dim[1]) will be added to each signature,
+ * where k is the index of the hash function.
+ *
+ * Value LSHProjectionType_SPARSE_DEPRECATED(=1).
+ * Legacy behavior that does not include the offset value.
+ *
+ * Dense:
+ * Value LSHProjectionType_DENSE(=2).
+ * Computed bit vector is considered to be dense. Each output
+ * element represents a bit and can take the value of either
+ * 0 or 1.
+ *
+ * Outputs:
+ * * 0: If the projection type is Sparse:
+ * Output.Dim == { Tensor[0].Dim[0] }
+ * A tensor of int32 that represents hash signatures.
+ *
+ * If the projection type is Dense:
+ * Output.Dim == { Tensor[0].Dim[0] * Tensor[0].Dim[1] }
+ * A flattened tensor that represents projected bit vectors.
+ * The offset value for sparse projections was added in HAL version 1.2.
+ */
+ LSH_PROJECTION = 15,
+ /**
+ * Performs a single time step in a Long Short-Term Memory (LSTM) layer
+ *
+ * The LSTM operation is described by the following equations.
+ *
+ * \f{eqnarray*}{
+ * i_t =& \sigma(W_{xi}x_t+W_{hi}h_{t-1}+W_{ci}C_{t-1}+b_i) & \\
+ * f_t =& \sigma(W_{xf}x_t+W_{hf}h_{t-1}+W_{cf}C_{t-1}+b_f) & \\
+ * C_t =& clip(f_t \odot C_{t-1} + i_t \odot
+ * g(W_{xc}x_t+W_{hc}h_{t-1}+b_c),\ t_{cell}) & \\
+ * o_t =& \sigma(W_{xo}x_t+W_{ho}h_{t-1}+W_{co}C_t+b_o) & \\
+ * & & \\
+ * & clip(W_{proj}(o_t \odot g(C_t))+b_{proj},\ t_{proj})
+ * & if\ there\ is\ a\ projection; \\
+ * h_t =& & \\
+ * & o_t \odot g(C_t) & otherwise. \\
+ * \f}
+ * Where:
+ * * \f$x_t\f$ is the input,
+ * * \f$i_t\f$ is the input gate,
+ * * \f$f_t\f$ is the forget gate,
+ * * \f$C_t\f$ is the cell state,
+ * * \f$o_t\f$ is the output,
+ * * \f$h_t\f$ is the output state,
+ * * \f$\sigma\f$ is the logistic sigmoid function,
+ * * \f$g\f$ is the cell input and cell output activation function, usually
+ * \f$tahn\f$,
+ * * \f$W_{xi}\f$ is the input-to-input weight matrix,
+ * * \f$W_{hi}\f$ is the recurrent to input weight matrix,
+ * * \f$W_{ci}\f$ is the cell-to-input weight matrix,
+ * * \f$b_i\f$ is the input gate bias,
+ * * \f$W_{xf}\f$ is the input-to-forget weight matrix,
+ * * \f$W_{hf}\f$ is the recurrent-to-forget weight matrix,
+ * * \f$W_{cf}\f$ is the cell-to-forget weight matrix,
+ * * \f$b_f\f$ is the forget gate bias,
+ * * \f$W_{xc}\f$ is the input-to-cell weight matrix,
+ * * \f$W_{hc}\f$ is the recurrent-to-cell weight matrix,
+ * * \f$b_c\f$ is the cell bias,
+ * * \f$W_{xo}\f$ is the input-to-output weight matrix,
+ * * \f$W_{ho}\f$ is the recurrent-to-output weight matrix,
+ * * \f$W_{co}\f$ is the cell-to-output weight matrix,
+ * * \f$b_o\f$ is the output gate bias,
+ * * \f$W_{proj}\f$ is the projection weight matrix,
+ * * \f$b_{proj}\f$ is the projection bias,
+ * * \f$t_{cell}\f$ is the threshold for clipping the cell state, and
+ * * \f$t_{proj}\f$ is the threshold for clipping the projected output.
+ * * \f$\odot\f$ is the
+ * <a href="https://en.wikipedia.org/wiki/Hadamard_product_(matrices)">
+ * Hadamard product</a> that takes two matrices and produces another
+ * matrix, each element of which is the product of the corresponding
+ * elements of the input matrices.
+ *
+ * Since HAL version 1.2 LSTM supports layer normalization.
+ * In case layer normalization is used, the inputs to internal activation
+ * functions (sigmoid and \f$g\f$) are normalized, rescaled and recentered
+ * following an approach from section 3.1 from
+ * https://arxiv.org/pdf/1607.06450.pdf
+ *
+ * The operation has the following independently optional inputs:
+ * * The cell-to-input weights (\f$W_{ci}\f$), cell-to-forget weights
+ * (\f$W_{cf}\f$) and cell-to-output weights (\f$W_{co}\f$) either all
+ * have values or neither of them have values (i.e., all set to null). If
+ * they have values, the peephole optimization is used.
+ * * The input-to-input weights (\f$W_{xi}\f$), recurrent-to-input weights
+ * (\f$W_{hi}\f$) and input gate bias (\f$b_i\f$) either all have values,
+ * or none of them have values. If they have no values, coupling of input
+ * and forget gates (CIFG) is used, in which case the input gate
+ * (\f$i_t\f$) is calculated using the following equation instead.
+ * \f{eqnarray*}{
+ * i_t = 1 - f_t
+ * \f}
+ * In case peephole optimization is used and CIFG is not used
+ * cell-to-input (\f$W_{ci}\f$) weights must be present. Otherwise, the
+ * cell-to-input weights must have no value.
+ * * The projection weights (\f$W_{proj}\f$) is required only for the
+ * recurrent projection layer, and should otherwise have no value.
+ * * The projection bias (\f$b_{proj}\f$) may (but not required to) have a
+ * value if the recurrent projection layer exists, and should otherwise
+ * have no value.
+ * * (HAL version 1.2 or later) The four layer normalization weights either all have
+ * values or none of them have values. Additionally, if CIFG is used,
+ * input layer normalization weights tensor is omitted and the other layer
+ * normalization weights either all have values or none of them have
+ * values. Layer normalization is used when the values of all the layer
+ * normalization weights are present.
+ *
+ * References:
+ *
+ * The default non-peephole non-CIFG implementation is based on:
+ * http://www.bioinf.jku.at/publications/older/2604.pdf
+ * S. Hochreiter and J. Schmidhuber. "Long Short-Term Memory". Neural
+ * Computation, 9(8):1735-1780, 1997.
+ *
+ * The peephole implementation and projection layer is based on:
+ * https://research.google.com/pubs/archive/43905.pdf
+ * Hasim Sak, Andrew Senior, and Francoise Beaufays. "Long short-term memory
+ * recurrent neural network architectures for large scale acoustic
+ * modeling." INTERSPEECH, 2014.
+ * (However, the concept of peephole optimization was introduced in work
+ * prior to this paper.)
+ *
+ * The coupling of input and forget gate (CIFG) is based on:
+ * http://arxiv.org/pdf/1503.04069.pdf
+ * Greff et al. "LSTM: A Search Space Odyssey"
+ *
+ * The layer normalization is based on:
+ * https://arxiv.org/pdf/1607.06450.pdf
+ * Jimmy Ba et al. "Layer Normalization"
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ *
+ * All input and output tensors must be of the same type.
+ *
+ * Inputs:
+ * * 0: The input (\f$x_t\f$).
+ * A 2-D tensor of shape [batch_size, input_size], where “batch_size”
+ * corresponds to the batching dimension, and “input_size” is the size
+ * of the input.
+ * * 1: The input-to-input weights (\f$W_{xi}\f$). Optional.
+ * A 2-D tensor of shape [num_units, input_size], where “num_units”
+ * corresponds to the number of cell units.
+ * * 2: The input-to-forget weights (\f$W_{xf}\f$).
+ * A 2-D tensor of shape [num_units, input_size].
+ * * 3: The input-to-cell weights (\f$W_{xc}\f$).
+ * A 2-D tensor of shape [num_units, input_size].
+ * * 4: The input-to-output weights (\f$W_{xo}\f$).
+ * A 2-D tensor of shape [num_units, input_size].
+ * * 5: The recurrent-to-input weights (\f$W_{hi}\f$). Optional.
+ * A 2-D tensor of shape [num_units, output_size], where “output_size”
+ * corresponds to either the number of cell units (i.e., “num_units”),
+ * or the second dimension of the “projection_weights”, if defined.
+ * * 6: The recurrent-to-forget weights (\f$W_{hf}\f$).
+ * A 2-D tensor of shape [num_units, output_size].
+ * * 7: The recurrent-to-cell weights (\f$W_{hc}\f$).
+ * A 2-D tensor of shape [num_units, output_size].
+ * * 8: The recurrent-to-output weights (\f$W_{ho}\f$).
+ * A 2-D tensor of shape [num_units, output_size].
+ * * 9: The cell-to-input weights (\f$W_{ci}\f$). Optional.
+ * A 1-D tensor of shape [num_units].
+ * * 10:The cell-to-forget weights (\f$W_{cf}\f$). Optional.
+ * A 1-D tensor of shape [num_units].
+ * * 11:The cell-to-output weights (\f$W_{co}\f$). Optional.
+ * A 1-D tensor of shape [num_units].
+ * * 12:The input gate bias (\f$b_i\f$). Optional.
+ * A 1-D tensor of shape [num_units].
+ * * 13:The forget gate bias (\f$b_f\f$).
+ * A 1-D tensor of shape [num_units].
+ * * 14:The cell bias (\f$b_c\f$).
+ * A 1-D tensor of shape [num_units].
+ * * 15:The output gate bias (\f$b_o\f$).
+ * A 1-D tensor of shape [num_units].
+ * * 16:The projection weights (\f$W_{proj}\f$). Optional.
+ * A 2-D tensor of shape [output_size, num_units].
+ * * 17:The projection bias (\f$b_{proj}\f$). Optional.
+ * A 1-D tensor of shape [output_size].
+ * * 18:The output state (in) (\f$h_{t-1}\f$).
+ * A 2-D tensor of shape [batch_size, output_size].
+ * * 19:The cell state (in) (\f$C_{t-1}\f$).
+ * A 2-D tensor of shape [batch_size, num_units].
+ * * 20:The activation function (\f$g\f$).
+ * A value indicating the activation function:
+ * <ul>
+ * <li>0: None;
+ * <li>1: Relu;
+ * <li>3: Relu6;
+ * <li>4: Tanh;
+ * <li>6: Sigmoid.
+ * </ul>
+ * * 21:The clipping threshold (\f$t_{cell}\f$) for the cell state, such
+ * that values are bound within [-cell_clip, cell_clip]. If set to 0.0
+ * then clipping is disabled.
+ * Until HAL version 1.2 this scalar must be of type {@link
+ * OperandType::FLOAT32}. Since HAL version 1.2, if all the input
+ * tensors have type {@link OperandType::TENSOR_FLOAT32}, this
+ * scalar must be of the type {@link OperandType::FLOAT32},
+ * otherwise if all the input tensors have the type {@link
+ * OperandType::TENSOR_FLOAT16}, this scalar must be of type {@link
+ * OperandType::FLOAT16}.
+ * * 22:The clipping threshold (\f$t_{proj}\f$) for the output from the
+ * projection layer, such that values are bound within
+ * [-proj_clip, proj_clip]. If set to 0.0 then clipping is disabled.
+ * Until HAL version 1.2 this scalar must be of type {@link
+ * OperandType::FLOAT32}. Since HAL version 1.2, if all the input
+ * tensors have type {@link OperandType::TENSOR_FLOAT32}, this
+ * scalar must be of the type {@link OperandType::FLOAT32},
+ * otherwise if all the input tensors have the type {@link
+ * OperandType::TENSOR_FLOAT16}, this scalar must be of type {@link
+ * OperandType::FLOAT16}.
+ * Since HAL version 1.2 there are additional inputs to this op:
+ * * 23:The input layer normalization weights.
+ * A 1-D tensor of shape [num_units]. Used to rescale normalized inputs
+ * to activation at input gate.
+ * * 24:The forget layer normalization weights.
+ * A 1-D tensor of shape [num_units]. Used to rescale normalized inputs
+ * to activation at forget gate.
+ * * 25:The cell layer normalization weights.
+ * A 1-D tensor of shape [num_units]. Used to rescale normalized inputs
+ * to activation at cell gate.
+ * * 26:The output layer normalization weights.
+ * A 1-D tensor of shape [num_units]. Used to rescale normalized inputs
+ * to activation at output gate.
+ *
+ * Outputs:
+ * * 0: The scratch buffer.
+ * A 2-D tensor of shape [batch_size, num_units * 3] with CIFG, or
+ * [batch_size, num_units * 4] without CIFG.
+ * * 1: The output state (out) (\f$h_t\f$).
+ * A 2-D tensor of shape [batch_size, output_size].
+ * * 2: The cell state (out) (\f$C_t\f$).
+ * A 2-D tensor of shape [batch_size, num_units].
+ * * 3: The output (\f$o_t\f$).
+ * A 2-D tensor of shape [batch_size, output_size]. This is effectively
+ * the same as the current “output state (out)” value.
+ */
+ LSTM = 16,
+ /**
+ * Performs an 2-D max pooling operation.
+ *
+ * The output dimensions are functions of the filter dimensions, stride, and
+ * padding.
+ *
+ * The values in the output tensor are computed as:
+ *
+ * output[b, i, j, channel] =
+ * max_{di, dj} (
+ * input[b, strides[1] * i + di, strides[2] * j + dj, channel]
+ * )
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+ * With the default data layout NHWC, the data is stored in the order of:
+ * [batch, height, width, channels]. Alternatively, the data layout could
+ * be NCHW, the data storage order of: [batch, channels, height, width].
+ * NCHW is supported since HAL version 1.2.
+ *
+ * Both explicit padding and implicit padding are supported.
+ *
+ * Inputs (explicit padding):
+ * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
+ * the input.
+ * Since HAL version 1.2, zero batches is supported for this tensor.
+ * * 1: An {@link OperandType::INT32} scalar, specifying the padding on
+ * the left, in the ‘width’ dimension.
+ * * 2: An {@link OperandType::INT32} scalar, specifying the padding on
+ * the right, in the ‘width’ dimension.
+ * * 3: An {@link OperandType::INT32} scalar, specifying the padding on
+ * the top, in the ‘height’ dimension.
+ * * 4: An {@link OperandType::INT32} scalar, specifying the padding on
+ * the bottom, in the ‘height’ dimension.
+ * * 5: An {@link OperandType::INT32} scalar, specifying the stride when
+ * walking through input in the ‘width’ dimension.
+ * * 6: An {@link OperandType::INT32} scalar, specifying the stride when
+ * walking through input in the ‘height’ dimension.
+ * * 7: An {@link OperandType::INT32} scalar, specifying the filter
+ * width.
+ * * 8: An {@link OperandType::INT32} scalar, specifying the filter
+ * height.
+ * * 9: An {@link OperandType::INT32} scalar, and has to be one of the
+ * {@link FusedActivationFunc} values. Specifies the activation to
+ * invoke on the result.
+ * * 10: An optional {@link OperandType::BOOL} scalar, default to false.
+ * Set to true to specify NCHW data layout for input0 and output0.
+ * Available since HAL version 1.2.
+ *
+ * Inputs (implicit padding):
+ * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
+ * the input.
+ * Since HAL version 1.2, zero batches is supported for this tensor.
+ * * 1: An {@link OperandType::INT32} scalar, specifying the implicit
+ * padding scheme, has to be one of the
+ * following values: {0 (NONE), 1 (SAME), 2 (VALID)}.
+ * * 2: An {@link OperandType::INT32} scalar, specifying the stride when
+ * walking through input in the ‘width’ dimension.
+ * * 3: An {@link OperandType::INT32} scalar, specifying the stride when
+ * walking through input in the ‘height’ dimension.
+ * * 4: An {@link OperandType::INT32} scalar, specifying the filter
+ * width.
+ * * 5: An {@link OperandType::INT32} scalar, specifying the filter
+ * height.
+ * * 6: An {@link OperandType::INT32} scalar, and has to be one of the
+ * {@link FusedActivationFunc} values. Specifies the activation to
+ * invoke on the result.
+ * * 7: An optional {@link OperandType::BOOL} scalar, default to false.
+ * Set to true to specify NCHW data layout for input0 and output0.
+ * Available since HAL version 1.2.
+ *
+ * Outputs:
+ * * 0: The output 4-D tensor, of shape
+ * [batches, out_height, out_width, depth].
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint must be the same as input0.
+ */
+ MAX_POOL_2D = 17,
+ /**
+ * Multiplies two tensors, element-wise.
+ *
+ * Takes two input tensors of identical {@link OperandType} and compatible
+ * dimensions. The output is the product of both input tensors, optionally
+ * modified by an activation function.
+ *
+ * Two dimensions are compatible when:
+ * 1. they are equal, or
+ * 2. one of them is 1
+ *
+ * The size of the resulting output is the maximum size along each dimension
+ * of the input operands. It starts with the trailing dimensions, and works
+ * its way forward.
+ *
+ * Since HAL version 1.2, generic zero-sized input tensor is supported. Zero
+ * dimension is only compatible with 0 or 1. The size of the output
+ * dimension is zero if either of corresponding input dimension is zero.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ * * {@link OperandType::TENSOR_INT32} (since HAL version 1.3)
+ *
+ * Supported tensor rank: up to 4
+ *
+ * Inputs:
+ * * 0: A tensor.
+ * * 1: A tensor of the same {@link OperandType}, and compatible dimensions
+ * as input0.
+ * * 2: An {@link OperandType::INT32} scalar, and has to be one of the
+ * {@link FusedActivationFunc} values. Specifies the activation to
+ * invoke on the result.
+ * For a {@link OperandType::TENSOR_INT32} tensor,
+ * the {@link FusedActivationFunc} must be "NONE".
+ *
+ * Outputs:
+ * * 0: The product, a tensor of the same {@link OperandType} as input0.
+ * For output tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * and {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED},
+ * the following condition must be satisfied:
+ * output_scale > input1_scale * input2_scale.
+ */
+ MUL = 18,
+ /**
+ * Computes rectified linear activation on the input tensor element-wise.
+ *
+ * The output is calculated using this formula:
+ *
+ * output = max(0, input)
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: up to 4.
+ *
+ * Inputs:
+ * * 0: A tensor, specifying the input.
+ * Since HAL version 1.2, this tensor may be zero-sized.
+ *
+ * Outputs:
+ * * 0: The output tensor of same shape as input0.
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint must be the same as input0.
+ */
+ RELU = 19,
+ /**
+ * Computes rectified linear 1 activation on the input tensor element-wise.
+ *
+ * The output is calculated using this formula:
+ *
+ * output = min(1.f, max(-1.f, input))
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: up to 4.
+ *
+ * Inputs:
+ * * 0: A tensor, specifying the input.
+ * Since HAL version 1.2, this tensor may be zero-sized.
+ *
+ * Outputs:
+ * * 0: The output tensor of the same shape as input0.
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint must be the same as input0.
+ */
+ RELU1 = 20,
+ /**
+ * Computes rectified linear 6 activation on the input tensor element-wise.
+ *
+ * The output is calculated using this formula:
+ *
+ * output = min(6, max(0, input))
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: up to 4.
+ *
+ * Inputs:
+ * * 0: A tensor, specifying the input.
+ * Since HAL version 1.2, this tensor may be zero-sized.
+ *
+ * Outputs:
+ * * 0: The output tensor of same shape as input0.
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint must be the same as input0.
+ */
+ RELU6 = 21,
+ /**
+ * Reshapes a tensor.
+ *
+ * Given tensor, this operation returns a tensor that has the same values as
+ * tensor, but with a newly specified shape.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: up to 4.
+ *
+ * Inputs:
+ * * 0: A tensor, specifying the tensor to be reshaped.
+ * * 1: A 1-D tensor of {@link OperandType::TENSOR_INT32}, defining the
+ * shape of the output tensor. The number of elements implied by shape
+ * must be the same as the number of elements in the input tensor.
+ *
+ * If one component of shape is the special value -1, the size of that
+ * dimension is computed so that the total size remains constant. In
+ * particular, a shape of [-1] flattens into 1-D. At most one component
+ * of shape can be -1.
+ *
+ * Outputs:
+ * * 0: The output tensor, of shape specified by the input shape.
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint must be the same as input0.
+ */
+ RESHAPE = 22,
+ /**
+ * Resizes images to given size using the bilinear interpretation.
+ *
+ * Resized images must be distorted if their output aspect ratio is not the
+ * same as input aspect ratio. The corner pixels of output may not be the
+ * same as corner pixels of input.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+ * With the default data layout NHWC, the data is stored in the order of:
+ * [batch, height, width, channels]. Alternatively, the data layout could
+ * be NCHW, the data storage order of: [batch, channels, height, width].
+ * NCHW is supported since HAL version 1.2.
+ *
+ * Both resizing by shape and resizing by scale are supported.
+ *
+ * Inputs (resizing by shape):
+ * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
+ * the input.
+ * Since HAL version 1.2, zero batches is supported for this tensor.
+ * * 1: An {@link OperandType::INT32} scalar, specifying the output
+ * width of the output tensor.
+ * * 2: An {@link OperandType::INT32} scalar, specifying the output
+ * height of the output tensor.
+ * * 3: An optional {@link OperandType::BOOL} scalar, default to false.
+ * Set to true to specify NCHW data layout for input0 and output0.
+ * Available since HAL version 1.2.
+ * * 4: Align corners. An optional {@link OperandType::BOOL}
+ * scalar, default to false. If True, the centers of the 4 corner
+ * pixels of the input and output tensors are aligned, preserving the
+ * values at the corner pixels.
+ * Available since HAL version 1.3.
+ * * 5: Half pixel centers. An optional {@link OperandType::BOOL}
+ * scalar, default to false. If True, the pixel centers are assumed to
+ * be at (0.5, 0.5). This is the default behavior of image.resize in
+ * TF 2.0. If this parameter is True, then align_corners parameter
+ * must be False.
+ * Available since HAL version 1.3.
+ *
+ * Inputs (resizing by scale, since HAL version 1.2):
+ * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
+ * the input. Zero batches is supported for this tensor.
+ * * 1: A scalar, specifying width_scale, the scaling factor of the width
+ * dimension from the input tensor to the output tensor. The output
+ * width is calculated as new_width = floor(width * width_scale).
+ * The scalar must be of {@link OperandType::FLOAT16} if input0 is
+ * of {@link OperandType::TENSOR_FLOAT16} and of
+ * {@link OperandType::FLOAT32} otherwise.
+ * * 2: A scalar, specifying height_scale, the scaling factor of the height
+ * dimension from the input tensor to the output tensor. The output
+ * height is calculated as new_height = floor(height * height_scale).
+ * The scalar must be of {@link OperandType::FLOAT16} if input0 is
+ * of {@link OperandType::TENSOR_FLOAT16} and of
+ * {@link OperandType::FLOAT32} otherwise.
+ * * 3: An optional {@link OperandType::BOOL} scalar, default to false.
+ * Set to true to specify NCHW data layout for input0 and output0.
+ * * 4: Align corners. An optional {@link OperandType::BOOL}
+ * scalar, default to false. If True, the centers of the 4 corner
+ * pixels of the input and output tensors are aligned, preserving the
+ * values at the corner pixels.
+ * Available since HAL version 1.3.
+ * * 5: Half pixel centers. An optional {@link OperandType::BOOL}
+ * scalar, default to false. If True, the pixel centers are assumed to
+ * be at (0.5, 0.5). This is the default behavior of image.resize in
+ * TF 2.0. If this parameter is True, then align_corners parameter
+ * must be False.
+ * Available since HAL version 1.3.
+ *
+ * Outputs:
+ * * 0: The output 4-D tensor, of shape
+ * [batches, new_height, new_width, depth].
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint must be the same as input0.
+ */
+ RESIZE_BILINEAR = 23,
+ /**
+ * A basic recurrent neural network layer.
+ *
+ * This layer implements the operation:
+ * outputs = state = activation(inputs * input_weights +
+ * state * recurrent_weights + bias)
+ *
+ * Where:
+ * * “input_weights” is a weight matrix that multiplies the inputs;
+ * * “recurrent_weights” is a weight matrix that multiplies the current
+ * “state” which itself is the output from the previous time step
+ * computation;
+ * * “bias” is a bias vector (added to each output vector in the batch);
+ * * “activation” is the function passed as the “fused_activation_function”
+ * argument (if not “NONE”).
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ *
+ * The input tensors must all be the same type.
+ *
+ * Inputs:
+ * * 0: input.
+ * A 2-D tensor of shape [batch_size, input_size], where “batch_size”
+ * corresponds to the batching dimension, and “input_size” is the size
+ * of the input.
+ * * 1: weights.
+ * A 2-D tensor of shape [num_units, input_size], where “num_units”
+ * corresponds to the number of units.
+ * * 2: recurrent_weights.
+ * A 2-D tensor of shape [num_units, num_units], with columns
+ * corresponding to the weights from each unit.
+ * * 3: bias.
+ * A 1-D tensor of shape [num_units].
+ * * 4: hidden state (in).
+ * A 2-D tensor of shape [batch_size, num_units].
+ * * 5: fused_activation_function.
+ * An optional {@link FusedActivationFunc} value indicating the
+ * activation function. If “NONE” is specified then it results in a
+ * linear activation.
+ *
+ * Outputs:
+ * * 0: hidden state (out).
+ * A 2-D tensor of shape [batch_size, num_units].
+ *
+ * * 1: output.
+ * A 2-D tensor of shape [batch_size, num_units]. This is effectively
+ * the same as the current state value.
+ */
+ RNN = 24,
+ /**
+ * Computes the softmax activation on the input tensor element-wise, per
+ * batch, by normalizing the input vector so the maximum coefficient is
+ * zero.
+ *
+ * The output is calculated using this formula:
+ *
+ * output[batch, i] =
+ * exp((input[batch, i] - max(input[batch, :])) * beta) /
+ * sum_{k}{exp((input[batch, k] - max(input[batch, :])) * beta)}
+ *
+ * For input tensor with rank other than 2, the activation will be applied
+ * independently on each 1-D slice along specified dimension.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: up to 4.
+ * Tensors with rank other than 2 or 4 are only supported since HAL version 1.2.
+ *
+ * Inputs:
+ * * 0: A 2-D or 4-D tensor, specifying the tensor to be reshaped.
+ * Since HAL version 1.2, this tensor may be zero-sized.
+ * * 1: A scalar, specifying the positive scaling factor for the exponent,
+ * beta. If input0 is of {@link OperandType::TENSOR_FLOAT32},
+ * {@link OperandType::TENSOR_QUANT8_ASYMM} or
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}, the scalar
+ * must be of {@link OperandType::FLOAT32}.
+ * If input0 is of {@link OperandType::TENSOR_FLOAT16}, then the
+ * scalar must be of {@link OperandType::FLOAT16}.
+ * * 2: An optional {@link OperandType::INT32} scalar, default to -1,
+ * specifying the dimension the activation would be performed on.
+ * Negative index is used to specify axis from the end (e.g. -1 for
+ * the last axis). Must be in the range [-n, n).
+ * Available since HAL version 1.2.
+ *
+ * Outputs:
+ * * 0: The output tensor of same shape as input0.
+ * For {@link OperandType::TENSOR_QUANT8_ASYMM},
+ * the scale must be 1.f / 256 and the zeroPoint must be 0.
+ * For {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED},
+ * the scale must be 1.f / 256 and the zeroPoint must be -128.
+ */
+ SOFTMAX = 25,
+ /**
+ * Rearranges blocks of spatial data, into depth.
+ *
+ * More specifically, this op outputs a copy of the input tensor where
+ * values from the height and width dimensions are moved to the depth
+ * dimension. The value block_size indicates the input block size and how
+ * the data is moved.
+ *
+ * Chunks of data of size block_size * block_size from depth are rearranged
+ * into non-overlapping blocks of size block_size x block_size.
+ *
+ * The depth of the output tensor is input_depth * block_size * block_size.
+ * The input tensor's height and width must be divisible by block_size.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+ * With the default data layout NHWC, the data is stored in the order of:
+ * [batch, height, width, channels]. Alternatively, the data layout could
+ * be NCHW, the data storage order of: [batch, channels, height, width].
+ * NCHW is supported since HAL version 1.2.
+ *
+ * Inputs:
+ * * 0: A 4-D tensor, of shape [batches, height, width, depth_in],
+ * specifying the input.
+ * * 1: An {@link OperandType::INT32} scalar, specifying the block_size.
+ * block_size must be >=1 and block_size must be a divisor of both the
+ * input height and width.
+ * * 2: An optional {@link OperandType::BOOL} scalar, default to false.
+ * Set to true to specify NCHW data layout for input0 and output0.
+ * Available since HAL version 1.2.
+ *
+ * Outputs:
+ * * 0: The output 4-D tensor, of shape [batches, height/block_size,
+ * width/block_size, depth_in*block_size*block_size].
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint must be the same as input0.
+ */
+ SPACE_TO_DEPTH = 26,
+ /**
+ * SVDF op is a kind of stateful layer derived from the notion that a
+ * densely connected layer that's processing a sequence of input frames can
+ * be approximated by using a singular value decomposition of each of its
+ * nodes. The implementation is based on:
+ *
+ * https://research.google.com/pubs/archive/43813.pdf
+ *
+ * P. Nakkiran, R. Alvarez, R. Prabhavalkar, C. Parada.
+ * “Compressing Deep Neural Networks using a Rank-Constrained Topology”.
+ * INTERSPEECH, 2015.
+ *
+ * It processes the incoming input using a 2-stage filtering mechanism:
+ * * stage 1 performs filtering on the "features" dimension, whose outputs
+ * get pushed into a memory of fixed-size memory_size.
+ * * stage 2 performs filtering on the "time" dimension of the memory_size
+ * memoized outputs of stage 1.
+ *
+ * Specifically, for rank 1, this layer implements the operation:
+ *
+ * memory = push(conv1d(inputs, weights_feature, feature_dim,
+ * "PADDING_VALID"));
+ * outputs = activation(memory * weights_time + bias);
+ *
+ * Where:
+ * * “weights_feature” is a weights matrix that processes the inputs (by
+ * convolving the input with every “feature filter”), and whose outputs
+ * get pushed, stacked in order, into the fixed-size “memory” (the oldest
+ * entry gets dropped);
+ * * “weights_time” is a weights matrix that processes the “memory” (by a
+ * batched matrix multiplication on the num_units);
+ * * “bias” is an optional bias vector (added to each output vector in the
+ * batch); and
+ * * “activation” is the function passed as the “fused_activation_function”
+ * argument (if not “NONE”).
+ *
+ * Each rank adds a dimension to the weights matrices by means of stacking
+ * the filters.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ *
+ * All input tensors must be the same type.
+ *
+ * Inputs:
+ * * 0: input.
+ * A 2-D tensor of shape [batch_size, input_size], where “batch_size”
+ * corresponds to the batching dimension, and “input_size” is the size
+ * of the input.
+ * * 1: weights_feature.
+ * A 2-D tensor of shape [num_units, input_size], where “num_units”
+ * corresponds to the number of units.
+ * * 2: weights_time.
+ * A 2-D tensor of shape [num_units, memory_size], where “memory_size”
+ * corresponds to the fixed-size of the memory.
+ * * 3: bias.
+ * An optional 1-D tensor of shape [num_units].
+ * * 4: state (in).
+ * A 2-D tensor of shape [batch_size, (memory_size - 1) * num_units * rank].
+ * * 5: rank.
+ * The rank of the SVD approximation.
+ * * 6: fused_activation_function.
+ * An optional {@link FusedActivationFunc} value indicating the
+ * activation function. If “NONE” is specified then it results in a
+ * linear activation.
+ *
+ * Outputs:
+ * * 0: state (out).
+ * A 2-D tensor of the same {@link OperandType} as the inputs, with shape
+ * [batch_size, (memory_size - 1) * num_units * rank].
+ * * 1: output.
+ * A 2-D tensor of the same {@link OperandType} as the inputs, with shape
+ * [batch_size, num_units].
+ */
+ SVDF = 27,
+ /**
+ * Computes hyperbolic tangent of input tensor element-wise.
+ *
+ * The output is calculated using this formula:
+ *
+ * output = tanh(input)
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: up to 4.
+ *
+ * Inputs:
+ * * 0: A tensor, specifying the input.
+ * Since HAL version 1.2, this tensor may be zero-sized.
+ *
+ * Outputs:
+ * * 0: The output tensor of same shape as input0.
+ * For {@link OperandType::TENSOR_QUANT8_ASYMM},
+ * the scale must be 1.f / 128 and the zeroPoint must be 128.
+ * For {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED},
+ * the scale must be 1.f / 128 and the zeroPoint must be 0.
+ */
+ TANH = 28,
+ /**
+ * BatchToSpace for N-dimensional tensors.
+ *
+ * This operation reshapes the batch dimension (dimension 0) into M + 1
+ * dimensions of shape block_shape + [batch], interleaves these blocks back
+ * into the grid defined by the spatial dimensions [1, ..., M], to obtain a
+ * result with the same rank as the input.
+ *
+ * This is the reverse of SpaceToBatch.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+ * With the default data layout NHWC, the data is stored in the order of:
+ * [batch, height, width, channels]. Alternatively, the data layout could
+ * be NCHW, the data storage order of: [batch, channels, height, width].
+ * NCHW is supported since HAL version 1.2.
+ *
+ * Inputs:
+ * * 0: An n-D tensor, specifying the tensor to be reshaped
+ * * 1: A 1-D Tensor of {@link OperandType::TENSOR_INT32}, the block
+ * sizes for each spatial dimension of the input tensor. All values
+ * must be >= 1.
+ * * 2: An optional {@link OperandType::BOOL} scalar, default to false.
+ * Set to true to specify NCHW data layout for input0 and output0.
+ * Available since API level 29.
+ *
+ * Outputs:
+ * * 0: A tensor of the same {@link OperandType} as input0.
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint must be the same as input0.
+ */
+ BATCH_TO_SPACE_ND = 29,
+ /**
+ * Element-wise division of two tensors.
+ *
+ * Takes two input tensors of identical {@link OperandType} and compatible
+ * dimensions. The output is the result of dividing the first input tensor
+ * by the second, optionally modified by an activation function.
+ *
+ * For inputs of {@link OperandType::TENSOR_INT32}, performs
+ * "floor division" ("//" in Python). For example,
+ * 5 // 2 = 2
+ * -5 // 2 = -3
+ *
+ * Two dimensions are compatible when:
+ * 1. they are equal, or
+ * 2. one of them is 1
+ *
+ * The size of the output is the maximum size along each dimension of the
+ * input operands. It starts with the trailing dimensions, and works its way
+ * forward.
+ *
+ * Example:
+ * input1.dimension = {4, 1, 2}
+ * input2.dimension = {5, 4, 3, 1}
+ * output.dimension = {5, 4, 3, 2}
+ *
+ * Since HAL version 1.2, generic zero-sized input tensor is supported. Zero
+ * dimension is only compatible with 0 or 1. The size of the output
+ * dimension is zero if either of corresponding input dimension is zero.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_INT32} (since HAL version 1.3)
+ *
+ * Supported tensor rank: up to 4
+ *
+ * Inputs:
+ * * 0: An n-D tensor, specifying the first input.
+ * * 1: A tensor of the same {@link OperandType}, and compatible dimensions
+ * as input0.
+ * * 2: An {@link OperandType::INT32} scalar, and has to be one of the
+ * {@link FusedActivationFunc} values. Specifies the activation to
+ * invoke on the result.
+ * For a {@link OperandType::TENSOR_INT32} tensor,
+ * the {@link FusedActivationFunc} must be "NONE".
+ *
+ * Outputs:
+ * * 0: A tensor of the same {@link OperandType} as input0.
+ */
+ DIV = 30,
+ /**
+ * Computes the mean of elements across dimensions of a tensor.
+ *
+ * Reduces the input tensor along the given dimensions to reduce. Unless
+ * keep_dims is true, the rank of the tensor is reduced by 1 for each entry
+ * in axis. If keep_dims is true, the reduced dimensions are retained with
+ * length 1.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: up to 4
+ *
+ * Inputs:
+ * * 0: A tensor, specifying the input.
+ * * 1: A 1-D Tensor of {@link OperandType::TENSOR_INT32}. The dimensions
+ * to reduce. Must be in the range
+ * [-rank(input_tensor), rank(input_tensor)).
+ *
+ * NOTE: When the operation was introduced, the documentation
+ * incorrectly stated that if dimensions were empty, the operation
+ * would reduce across all dimensions. This behavior was never
+ * implemented.
+ *
+ * * 2: An {@link OperandType::INT32} scalar, keep_dims. If positive,
+ * retains reduced dimensions with length 1.
+ *
+ * Outputs:
+ * * 0: A tensor of the same {@link OperandType} as input0.
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint must be the same as input0.
+ * If all dimensions are reduced and keep_dims is false, the output
+ * shape is [1].
+ */
+ MEAN = 31,
+ /**
+ * Pads a tensor.
+ *
+ * This operation pads a tensor according to the specified paddings.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ * (full support since HAL version 1.2, see the output section)
+ *
+ * Supported tensor rank: up to 4
+ *
+ * Inputs:
+ * * 0: An n-D tensor, specifying the tensor to be padded.
+ * * 1: A 2-D Tensor of {@link OperandType::TENSOR_INT32}, the paddings
+ * for each spatial dimension of the input tensor. The shape of the
+ * tensor must be {rank(input0), 2}.
+ * padding[i, 0] specifies the number of elements to be padded in the
+ * front of dimension i.
+ * padding[i, 1] specifies the number of elements to be padded after the
+ * end of dimension i.
+ *
+ * Outputs:
+ * * 0: A tensor of the same {@link OperandType} as input0. The
+ * output tensor has the same rank as input0, and each
+ * dimension of the output tensor has the same size as the
+ * corresponding dimension of the input tensor plus the size
+ * of the padding:
+ * output0.dimension[i] =
+ * padding[i, 0] + input0.dimension[i] + padding[i, 1]
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint must be the same as input0.
+ *
+ * NOTE: Before HAL version 1.2, the pad value for
+ * {@link OperandType::TENSOR_QUANT8_ASYMM} is undefined.
+ * Since HAL version 1.2, the pad value is always the logical zero.
+ */
+ PAD = 32,
+ /**
+ * SpaceToBatch for N-Dimensional tensors.
+ *
+ * This operation divides "spatial" dimensions [1, ..., M] of the input into
+ * a grid of blocks of shape block_shape, and interleaves these blocks with
+ * the "batch" dimension (0) such that in the output, the spatial dimensions
+ * [1, ..., M] correspond to the position within the grid, and the batch
+ * dimension combines both the position within a spatial block and the
+ * original batch position. Prior to division into blocks, the spatial
+ * dimensions of the input are optionally zero padded according to paddings.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ * (full support since HAL version 1.2, see the output section)
+ *
+ * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+ * With the default data layout NHWC, the data is stored in the order of:
+ * [batch, height, width, channels]. Alternatively, the data layout could
+ * be NCHW, the data storage order of: [batch, channels, height, width].
+ * NCHW is supported since HAL version 1.2.
+ *
+ * Inputs:
+ * * 0: An n-D tensor, specifying the input.
+ * * 1: A 1-D Tensor of {@link OperandType::TENSOR_INT32}, the block
+ * sizes for each spatial dimension of the input tensor. All values
+ * must be >= 1.
+ * * 2: A 2-D Tensor of {@link OperandType::TENSOR_INT32}, the paddings
+ * for each spatial dimension of the input tensor. All values must be
+ * >= 0. The shape of the tensor must be {M, 2}, where M is the number
+ * of spatial dimensions.
+ * padding[i, 0] specifies the number of element to be padded in the
+ * front of dimension i.
+ * padding[i, 1] specifies the number of element to be padded after the
+ * end of dimension i.
+ * * 3: An optional {@link OperandType::BOOL} scalar, default to false.
+ * Set to true to specify NCHW data layout for input0 and output0.
+ * Available since HAL version 1.2.
+ *
+ * Outputs:
+ * * 0: A tensor of the same {@link OperandType} as input0.
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint must be the same as input0.
+ *
+ * NOTE: Before HAL version 1.2, the pad value for
+ * {@link OperandType::TENSOR_QUANT8_ASYMM} is undefined.
+ * Since HAL version 1.2, the pad value is always the logical zero.
+ */
+ SPACE_TO_BATCH_ND = 33,
+ /**
+ * Removes dimensions of size 1 from the shape of a tensor.
+ *
+ * Given a tensor input, this operation returns a tensor of the same
+ * {@link OperandType} with all dimensions of size 1 removed. If you don't
+ * want to remove all size 1 dimensions, you can remove specific size 1
+ * dimensions by specifying the axes (input1).
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: up to 4
+ *
+ * Inputs:
+ * * 0: An n-D tensor, the tensor to be squeezed.
+ * * 1: An optional 1-D tensor of {@link OperandType::TENSOR_INT32}. The
+ * dimensions to squeeze. If specified only squeezes the dimensions
+ * listed. Otherwise, squeezes all dimensions. The dimension index
+ * starts at 0. An error must be reported if squeezing a dimension that
+ * is not 1.
+ *
+ * Outputs:
+ * * 0: A tensor of the same {@link OperandType} as input0. Contains the
+ * same data as input, but has one or more dimensions of size 1
+ * removed.
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint must be the same as input0.
+ * If all input dimensions are equal to 1 and are to be squeezed, the
+ * output shape is [1].
+ */
+ SQUEEZE = 34,
+ /**
+ * Extracts a strided slice of a tensor.
+ *
+ * Roughly speaking, this op extracts a slice of size (end - begin) / stride
+ * from the given input tensor. Starting at the location specified by begin
+ * the slice continues by adding stride to the index until all dimensions
+ * are not less than end. Note that a stride can be negative, which causes a
+ * reverse slice.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: up to 4
+ *
+ * Inputs:
+ * * 0: An n-D tensor, specifying the tensor to be sliced.
+ * * 1: begin, a 1-D tensor of {@link OperandType::TENSOR_INT32}. The
+ * starts of the dimensions of the input tensor to be sliced. The
+ * length must be of rank(input0).
+ * * 2: end, a 1-D tensor of {@link OperandType::TENSOR_INT32}. The
+ * ends of the dimensions of the input tensor to be sliced. The length
+ * must be of rank(input0).
+ * * 3: strides, a 1-D tensor of {@link OperandType::TENSOR_INT32}. The
+ * strides of the dimensions of the input tensor to be sliced. The
+ * length must be of rank(input0). The entries must be non-zero.
+ * * 4: begin_mask, an {@link OperandType::INT32} scalar. If the ith bit
+ * of begin_mask is set, begin[i] is ignored and the fullest possible
+ * range in that dimension is used instead.
+ * * 5: end_mask, an {@link OperandType::INT32} scalar. If the ith bit of
+ * end_mask is set, end[i] is ignored and the fullest possible range in
+ * that dimension is used instead.
+ * * 6: shrink_axis_mask, an {@link OperandType::INT32} scalar. If the
+ * ith bit of shrink_axis_mask is set, the ith dimension specification
+ * shrinks the dimensionality by 1, taking on the value at index
+ * begin[i]. In this case, the ith specification must define a
+ * slice of size 1, e.g. begin[i] = x, end[i] = x + 1.
+ *
+ * Outputs:
+ * * 0: A tensor of the same {@link OperandType} as input0 and rank (n - k),
+ * where k is the number of bits set in shrink_axis_mask.
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint must be the same as input0.
+ * If shrink_axis_mask is true for all input dimensions, the output
+ * shape is [1].
+ */
+ STRIDED_SLICE = 35,
+ /**
+ * Element-wise subtraction of two tensors.
+ *
+ * Takes two input tensors of identical {@link OperandType} and compatible
+ * dimensions. The output is the result of subtracting the second input
+ * tensor from the first one, optionally modified by an activation function.
+ *
+ * Two dimensions are compatible when:
+ * 1. they are equal, or
+ * 2. one of them is 1
+ *
+ * The size of the output is the maximum size along each dimension of the
+ * input operands. It starts with the trailing dimensions, and works its way
+ * forward.
+ *
+ * Example:
+ * input1.dimension = {4, 1, 2}
+ * input2.dimension = {5, 4, 3, 1}
+ * output.dimension = {5, 4, 3, 2}
+ *
+ * Since HAL version 1.2, generic zero-sized input tensor is supported. Zero
+ * dimension is only compatible with 0 or 1. The size of the output
+ * dimension is zero if either of corresponding input dimension is zero.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ * * {@link OperandType::TENSOR_INT32} (since HAL version 1.3)
+ *
+ * Supported tensor rank: up to 4
+ *
+ * Inputs:
+ * * 0: An n-D tensor, specifying the first input.
+ * * 1: A tensor of the same {@link OperandType}, and compatible dimensions
+ * as input0.
+ * * 2: An {@link OperandType::INT32} scalar, and has to be one of the
+ * {@link FusedActivationFunc} values. Specifies the activation to
+ * invoke on the result.
+ * For a {@link OperandType::TENSOR_INT32} tensor,
+ * the {@link FusedActivationFunc} must be "NONE".
+ *
+ * Outputs:
+ * * 0: A tensor of the same {@link OperandType} as input0.
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint can be different from inputs' scale and zeroPoint.
+ */
+ SUB = 36,
+ /**
+ * Transposes the input tensor, permuting the dimensions according to the
+ * perm tensor.
+ *
+ * The returned tensor's dimension i corresponds to the input dimension
+ * perm[i]. If perm is not given, it is set to (n-1...0), where n is the
+ * rank of the input tensor. Hence by default, this operation performs a
+ * regular matrix transpose on 2-D input Tensors.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2)
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: up to 4
+ *
+ * Inputs:
+ * * 0: An n-D tensor, specifying the tensor to be transposed.
+ * Since HAL version 1.2, this tensor may be zero-sized.
+ * * 1: An optional 1-D Tensor of {@link OperandType::TENSOR_INT32},
+ * the permutation of the dimensions of the input tensor.
+ *
+ * Outputs:
+ * * 0: A tensor of the same {@link OperandType} as input0.
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint must be the same as input0.
+ */
+ TRANSPOSE = 37,
+ /**
+ * Computes the absolute value of a tensor, element-wise.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_INT32} (since HAL version 1.3)
+ *
+ * Supported tensor rank: from 1.
+ *
+ * Inputs:
+ * * 0: A tensor.
+ *
+ * Outputs:
+ * * 0: The output tensor of same shape as input0.
+ */
+ ABS = 38,
+ /**
+ * Returns the index of the largest element along an axis.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_INT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: from 1
+ *
+ * Inputs:
+ * * 0: An n-D tensor specifying the input. Must be non-empty.
+ * * 1: An {@link OperandType::INT32} scalar specifying the axis to
+ * reduce across. Negative index is used to specify axis from the
+ * end (e.g. -1 for the last axis). Must be in the range [-n, n).
+ *
+ * Outputs:
+ * * 0: An (n - 1)-D {@link OperandType::TENSOR_INT32} tensor.
+ * If input is 1-dimensional, the output shape is [1].
+ */
+ ARGMAX = 39,
+ /**
+ * Returns the index of the smallest element along an axis.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_INT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: from 1
+ *
+ * Inputs:
+ * * 0: An n-D tensor specifying the input. Must be non-empty.
+ * * 1: An {@link OperandType::INT32} scalar specifying the axis to
+ * reduce across. Negative index is used to specify axis from the
+ * end (e.g. -1 for the last axis). Must be in the range [-n, n).
+ *
+ * Outputs:
+ * * 0: An (n - 1)-D {@link OperandType::TENSOR_INT32} tensor.
+ * If input is 1-dimensional, the output shape is [1].
+ */
+ ARGMIN = 40,
+ /**
+ * Transform axis-aligned bounding box proposals using bounding box deltas.
+ *
+ * Given the positions of bounding box proposals and the corresponding
+ * bounding box deltas for each class, return the refined bounding box
+ * regions. The resulting bounding boxes are cliped against the edges of
+ * the image.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT16_ASYMM}
+ *
+ * Inputs:
+ * * 0: A 2-D Tensor of shape [num_rois, 4], specifying the locations of the
+ * bounding box proposals, each line with format [x1, y1, x2, y2].
+ * For tensor of type {@link OperandType::TENSOR_QUANT16_ASYMM},
+ * the zeroPoint must be 0 and the scale must be 0.125. Zero num_rois
+ * is supported for this tensor.
+ * * 1: A 2-D Tensor of shape [num_rois, num_classes * 4], specifying the
+ * bounding box delta for each region of interest and each class. The
+ * bounding box deltas are organized in the following order
+ * [dx, dy, dw, dh], where dx and dy is the relative correction factor
+ * for the center position of the bounding box with respect to the width
+ * and height, dw and dh is the log-scale relative correction factor
+ * for the width and height. For input0 of type
+ * {@link OperandType::TENSOR_QUANT16_ASYMM}, this tensor should be
+ * of {@link OperandType::TENSOR_QUANT8_ASYMM} or
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}. Zero num_rois is
+ * supported for this tensor.
+ * * 2: An 1-D {@link OperandType::TENSOR_INT32} tensor, of shape
+ * [num_rois], specifying the batch index of each box. Boxes with
+ * the same batch index are grouped together. Zero num_rois is
+ * supported for this tensor.
+ * * 3: A 2-D Tensor of shape [batches, 2], specifying the information of
+ * each image in the batch, each line with format
+ * [image_height, image_width].
+ *
+ * Outputs:
+ * * 0: A tensor of the same {@link OperandType} as input0, with shape
+ * [num_rois, num_classes * 4], specifying the coordinates of each
+ * output bounding box for each class, with format [x1, y1, x2, y2].
+ * For type of {@link OperandType::TENSOR_QUANT16_ASYMM}, the
+ * scale must be 0.125 and the zero point must be 0.
+ */
+ AXIS_ALIGNED_BBOX_TRANSFORM = 41,
+ /**
+ * A recurrent neural network layer that applies an LSTM cell to a
+ * sequence of inputs in forward and backward directions.
+ *
+ * The op supports cross-linking via an auxiliary input. Regular cell feeds
+ * one input into the two RNN cells in the following way:
+ *
+ * INPUT (INPUT_REVERSED)
+ * | |
+ * ---------------------
+ * | FW_LSTM BW_LSTM |
+ * ---------------------
+ * | |
+ * FW_OUT BW_OUT
+ *
+ * An op with cross-linking takes two inputs and feeds them into the RNN
+ * cells in the following way:
+ *
+ * AUX_INPUT (AUX_INPUT_REVERSED)
+ * | |
+ * INPUT | (INPUT_R'D.)|
+ * | | | |
+ * -----------------------
+ * | \ / \ / |
+ * | FW_LSTM BW_LSTM |
+ * -----------------------
+ * | |
+ * FW_OUT BW_OUT
+ *
+ * The cross-linking mode is enabled iff auxiliary input and auxiliary
+ * weights are present. While stacking this op on top of itself, this
+ * allows to connect both forward and backward outputs from previous cell
+ * to the next cell's input.
+ *
+ * Since HAL version 1.3 parallel linking mode is supported. The mode is
+ * enabled if auxiliary input is present but auxiliary weights are omitted.
+ * In this case, the cell feeds inputs into the RNN in the following way:
+ *
+ * INPUT (AUX_INPUT_REVERSED)
+ * | |
+ * ---------------------
+ * | FW_LSTM BW_LSTM |
+ * ---------------------
+ * | |
+ * FW_OUT BW_OUT
+ *
+ * While stacking this op on top of itself, this allows to connect both
+ * forward and backward outputs from previous cell to the next cell's
+ * corresponding inputs.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ *
+ * Supported tensor rank: 3, either time-major or batch-major.
+ *
+ * All input and output tensors must be of the same type.
+ *
+ * Inputs:
+ * * 0: The input.
+ * A 3-D tensor of shape:
+ * If time-major: [max_time, batch_size, input_size]
+ * If batch-major: [batch_size, max_time, input_size]
+ * where "max_time" is the number of timesteps (sequence length),
+ * "batch_size" corresponds to the batching dimension, and
+ * "input_size" is the size of the input.
+ * * 1: The forward input-to-input weights. Optional.
+ * A 2-D tensor of shape [fw_num_units, input_size], where “fw_num_units”
+ * corresponds to the number of forward cell units.
+ * * 2: The forward input-to-forget weights.
+ * A 2-D tensor of shape [fw_num_units, input_size].
+ * * 3: The forward input-to-cell weights.
+ * A 2-D tensor of shape [fw_num_units, input_size].
+ * * 4: The forward input-to-output weights.
+ * A 2-D tensor of shape [fw_num_units, input_size].
+ * * 5: The forward recurrent-to-input weights. Optional.
+ * A 2-D tensor of shape [fw_num_units, fw_output_size], where “fw_output_size”
+ * corresponds to either the number of cell units (i.e., fw_num_units),
+ * or the second dimension of the “fw_projection_weights”, if defined.
+ * * 6: The forward recurrent-to-forget weights.
+ * A 2-D tensor of shape [fw_num_units, fw_output_size].
+ * * 7: The forward recurrent-to-cell weights.
+ * A 2-D tensor of shape [fw_num_units, fw_output_size].
+ * * 8: The forward recurrent-to-output weights.
+ * A 2-D tensor of shape [fw_num_units, fw_output_size].
+ * * 9: The forward cell-to-input weights. Optional.
+ * A 1-D tensor of shape [fw_num_units].
+ * * 10: The forward cell-to-forget weights. Optional.
+ * A 1-D tensor of shape [fw_num_units].
+ * * 11: The forward cell-to-output weights. Optional.
+ * A 1-D tensor of shape [fw_num_units].
+ * * 12: The forward input gate bias. Optional.
+ * A 1-D tensor of shape [fw_num_units].
+ * * 13: The forward forget gate bias.
+ * A 1-D tensor of shape [fw_num_units].
+ * * 14: The forward cell gate bias.
+ * A 1-D tensor of shape [fw_num_units].
+ * * 15: The forward output gate bias.
+ * A 1-D tensor of shape [fw_num_units].
+ * * 16: The forward projection weights. Optional.
+ * A 2-D tensor of shape [fw_output_size, fw_num_units].
+ * * 17: The forward projection bias. Optional.
+ * A 1-D tensor of shape [fw_output_size].
+ * * 18: The backward input-to-input weights. Optional.
+ * A 2-D tensor of shape [bw_num_units, input_size], where “bw_num_units”
+ * corresponds to the number of backward cell units.
+ * * 19: The backward input-to-forget weights.
+ * A 2-D tensor of shape [bw_num_units, input_size].
+ * * 20: The backward input-to-cell weights.
+ * A 2-D tensor of shape [bw_num_units, input_size].
+ * * 21: The backward input-to-output weights.
+ * A 2-D tensor of shape [bw_num_units, input_size].
+ * * 22: The backward recurrent-to-input weights. Optional.
+ * A 2-D tensor of shape [bw_num_units, bw_output_size], where “bw_output_size”
+ * corresponds to either the number of cell units (i.e., “bw_num_units”),
+ * or the second dimension of the “bw_projection_weights”, if defined.
+ * * 23: The backward recurrent-to-forget weights.
+ * A 2-D tensor of shape [bw_num_units, bw_output_size].
+ * * 24: The backward recurrent-to-cell weights.
+ * A 2-D tensor of shape [bw_num_units, bw_output_size].
+ * * 25: The backward recurrent-to-output weights.
+ * A 2-D tensor of shape [bw_num_units, bw_output_size].
+ * * 26: The backward cell-to-input weights. Optional.
+ * A 1-D tensor of shape [bw_num_units].
+ * * 27: The backward cell-to-forget weights. Optional.
+ * A 1-D tensor of shape [bw_num_units].
+ * * 28: The backward cell-to-output weights. Optional.
+ * A 1-D tensor of shape [bw_num_units].
+ * * 29: The backward input gate bias. Optional.
+ * A 1-D tensor of shape [bw_num_units].
+ * * 30: The backward forget gate bias.
+ * A 1-D tensor of shape [bw_num_units].
+ * * 31: The backward cell gate bias.
+ * A 1-D tensor of shape [bw_num_units].
+ * * 32: The backward output gate bias.
+ * A 1-D tensor of shape [bw_num_units].
+ * * 33: The backward projection weights. Optional.
+ * A 2-D tensor of shape [bw_output_size, bw_num_units].
+ * * 34: The backward projection bias. Optional.
+ * A 1-D tensor of shape [bw_output_size].
+ * * 35: The forward input activation state.
+ * A 2-D tensor of shape [batch_size, bw_output_size].
+ * * 36: The forward input cell state.
+ * A 2-D tensor of shape [batch_size, bw_num_units].
+ * * 37: The backward input activation state.
+ * A 2-D tensor of shape [batch_size, bw_output_size].
+ * * 38: The backward input cell state.
+ * A 2-D tensor of shape [batch_size, bw_num_units].
+ * * 39: The auxiliary input. Optional.
+ * A 3-D tensor of shape [max_time, batch_size, aux_input_size],
+ * where “batch_size” corresponds to the batching dimension, and
+ * “aux_input_size” is the size of the auxiliary input. Optional. See
+ * the docs above for the usage modes explanation.
+ * * 40: The forward auxiliary input-to-input weights.
+ * Optional. See the docs above for the usage modes explanation.
+ * A 2-D tensor of shape [fw_num_units, aux_input_size].
+ * * 41: The forward auxiliary input-to-forget weights.
+ * Optional. See the docs above for the usage modes explanation.
+ * A 2-D tensor of shape [fw_num_units, aux_input_size].
+ * * 42: The forward auxiliary input-to-cell weights.
+ * Optional. See the docs above for the usage modes explanation.
+ * A 2-D tensor of shape [fw_num_units, aux_input_size].
+ * * 43: The forward auxiliary input-to-output weights.
+ * Optional. See the docs above for the usage modes explanation.
+ * A 2-D tensor of shape [fw_num_units, aux_input_size].
+ * * 44: The backward auxiliary input-to-input weights.
+ * Optional. See the docs above for the usage modes explanation.
+ * A 2-D tensor of shape [bw_num_units, aux_input_size].
+ * * 45: The backward auxiliary input-to-forget weights.
+ * Optional. See the docs above for the usage modes explanation.
+ * A 2-D tensor of shape [bw_num_units, aux_input_size].
+ * * 46: The backward auxiliary input-to-cell weights.
+ * Optional. See the docs above for the usage modes explanation.
+ * A 2-D tensor of shape [bw_num_units, aux_input_size].
+ * * 47: The backward auxiliary input-to-output weights.
+ * Optional. See the docs above for the usage modes explanation.
+ * A 2-D tensor of shape [bw_num_units, aux_input_size].
+ * * 48: The activation function.
+ * A value indicating the activation function:
+ * <ul>
+ * <li>0: None;
+ * <li>1: Relu;
+ * <li>3: Relu6;
+ * <li>4: Tanh;
+ * <li>6: Sigmoid.
+ * </ul>
+ * * 49: The clipping threshold for the cell state, such
+ * that values are bound within [-cell_clip, cell_clip]. If set to 0.0
+ * then clipping is disabled.
+ * If all the input tensors have type {@link OperandType::TENSOR_FLOAT32},
+ * this scalar must be of the type {@link OperandType::FLOAT32},
+ * otherwise if all the input tensors have the type
+ * {@link OperandType::TENSOR_FLOAT16}, this scalar must be
+ * of type {@link OperandType::FLOAT16}.
+ * * 50: The clipping threshold for the output from the
+ * projection layer, such that values are bound within
+ * [-proj_clip, proj_clip]. If set to 0.0 then clipping is disabled.
+ * If all the input tensors have type {@link OperandType::TENSOR_FLOAT32},
+ * this scalar must be of the type {@link OperandType::FLOAT32},
+ * otherwise if all the input tensors have the type
+ * {@link OperandType::TENSOR_FLOAT16}, this scalar must be
+ * of type {@link OperandType::FLOAT16}.
+ * * 51: merge_outputs
+ * An {@link OperandType::BOOL} scalar specifying if the outputs
+ * from forward and backward cells should be merged.
+ * * 52: time_major
+ * An {@link OperandType::BOOL} scalar specifying the shape format
+ * of input and output tensors.
+ * * 53: The forward input layer normalization weights. Optional.
+ * A 1-D tensor of shape [fw_num_units]. Used to rescale normalized inputs
+ * to activation at input gate.
+ * * 54: The forward forget layer normalization weights. Optional.
+ * A 1-D tensor of shape [fw_num_units]. Used to rescale normalized inputs
+ * to activation at forget gate.
+ * * 55: The forward cell layer normalization weights. Optional.
+ * A 1-D tensor of shape [fw_num_units]. Used to rescale normalized inputs
+ * to activation at cell gate.
+ * * 56: The forward output layer normalization weights. Optional.
+ * A 1-D tensor of shape [fw_num_units]. Used to rescale normalized inputs
+ * to activation at output gate.
+ * * 57: The backward input layer normalization weights. Optional.
+ * A 1-D tensor of shape [bw_num_units]. Used to rescale normalized inputs
+ * to activation at input gate.
+ * * 58: The backward forget layer normalization weights. Optional.
+ * A 1-D tensor of shape [bw_num_units]. Used to rescale normalized inputs
+ * to activation at forget gate.
+ * * 59: The backward cell layer normalization weights. Optional.
+ * A 1-D tensor of shape [bw_num_units]. Used to rescale normalized inputs
+ * to activation at cell gate.
+ * * 60: The backward output layer normalization weights. Optional.
+ * A 1-D tensor of shape [bw_num_units]. Used to rescale normalized inputs
+ * to activation at output gate.
+ *
+ * Outputs:
+ * * 0: The forward output.
+ * A 3-D tensor of shape:
+ * If time-major and not merge_outputs:
+ * [max_time, batch_size, fw_output_size]
+ * If time-major and merge_outputs:
+ * [max_time, batch_size, fw_output_size + bw_output_size]
+ * If batch-major and not merge_outputs:
+ * [batch_size, max_time, fw_output_size]
+ * If batch-major and merge_outputs:
+ * [batch_size, max_time, fw_output_size + bw_output_size]
+ * * 1: The backward output. Unused if merge_outputs is true.
+ * A 3-D tensor of shape:
+ * If time-major: [max_time, batch_size, bw_output_size]
+ * If batch-major: [batch_size, max_time, bw_output_size]
+ * * 2: The forward activation state output.
+ * A 2-D tensor of shape [batch_size, fw_output_size] containing an
+ * activation state from the last time step in the sequence. This
+ * output is optional and can be omitted. If this output is present
+ * then outputs 3-5 must be present as well.
+ * Available since HAL version 1.3.
+ * * 3: The forward cell state output.
+ * A tensor of shape [batch_size, fw_cell_size] containing a cell state
+ * from the last time step in the sequence. This output is optional
+ * and can be omitted. If this output is present
+ * then outputs 2, 4, 5 must be present as well.
+ * Available since HAL version 1.3.
+ * * 4: The backward activation state output.
+ * A 2-D tensor of shape [batch_size, bw_output_size] containing an
+ * activation state from the last time step in the sequence. This
+ * output is optional and can be omitted. If this output is present
+ * then outputs 2, 3, 5 must be present as well.
+ * Available since HAL version 1.3.
+ * * 5: The backward cell state output.
+ * A tensor of shape [batch_size, bw_cell_size] containing a cell state
+ * from the last time step in the sequence. This output is optional
+ * and can be omitted. If this output is present
+ * then outputs 2-4 must be present as well.
+ * Available since HAL version 1.3.
+ */
+ BIDIRECTIONAL_SEQUENCE_LSTM = 42,
+ /**
+ * A recurrent neural network layer that applies a basic RNN cell to a
+ * sequence of inputs in forward and backward directions.
+ *
+ * This Op unrolls the input along the sequence dimension, and implements
+ * the following operation for each element in the sequence s =
+ * 1...sequence_length:
+ * fw_outputs[s] = fw_state = activation(inputs[s] * fw_input_weights’ +
+ * fw_state * fw_recurrent_weights’ + fw_bias)
+ *
+ * And for each element in sequence t = sequence_length : 1
+ * bw_outputs[t] = bw_state = activation(inputs[t] * bw_input_weights’ +
+ * bw_state * bw_recurrent_weights’ + bw_bias)
+ *
+ * Where:
+ * * “{fw,bw}_input_weights” is a weight matrix that multiplies the inputs;
+ * * “{fw,bw}_recurrent_weights” is a weight matrix that multiplies the
+ * current “state” which itself is the output from the previous time step
+ * computation;
+ * * “{fw,bw}_bias” is a bias vector (added to each output vector in the
+ * batch);
+ * * “activation” is the function passed as the “fused_activation_function”
+ * argument (if not “NONE”).
+ *
+ * The op supports cross-linking via an auxiliary input. Regular cell feeds
+ * one input into the two RNN cells in the following way:
+ *
+ * INPUT (INPUT_REVERSED)
+ * | |
+ * ---------------------
+ * | FW_RNN BW_RNN |
+ * ---------------------
+ * | |
+ * FW_OUT BW_OUT
+ *
+ * An op with cross-linking takes two inputs and feeds them into the RNN
+ * cells in the following way:
+ *
+ * AUX_INPUT (AUX_INPUT_REVERSED)
+ * | |
+ * INPUT | (INPUT_R'D.)|
+ * | | | |
+ * -----------------------
+ * | \ / \ / |
+ * | FW_RNN BW_RNN |
+ * -----------------------
+ * | |
+ * FW_OUT BW_OUT
+ *
+ * The cross-linking mode is enabled iff auxiliary input and auxiliary
+ * weights are present. While stacking this op on top of itself, this
+ * allows to connect both forward and backward outputs from previous cell
+ * to the next cell's input.
+ *
+ * Since HAL version 1.3 parallel linking mode is supported. The mode is
+ * enabled if auxiliary input is present but auxiliary weights are omitted.
+ * In this case, the cell feeds inputs into the RNN in the following way:
+ *
+ * INPUT (AUX_INPUT_REVERSED)
+ * | |
+ * ---------------------
+ * | FW_RNN BW_RNN |
+ * ---------------------
+ * | |
+ * FW_OUT BW_OUT
+ *
+ * While stacking this op on top of itself, this allows to connect both
+ * forward and backward outputs from previous cell to the next cell's
+ * corresponding inputs.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ *
+ * The input tensors must all be the same type.
+ *
+ * Inputs:
+ * * 0: input.
+ * A 3-D tensor. The shape is defined by the input 6 (timeMajor). If
+ * it is set to true, then the input has a shape [maxTime, batchSize,
+ * inputSize], otherwise the input has a shape [batchSize, maxTime,
+ * inputSize].
+ * * 1: fwWeights.
+ * A 2-D tensor of shape [fwNumUnits, inputSize].
+ * * 2: fwRecurrentWeights.
+ * A 2-D tensor of shape [fwNumUnits, fwNumUnits].
+ * * 3: fwBias.
+ * A 1-D tensor of shape [fwNumUnits].
+ * * 4: fwHiddenState.
+ * A 2-D tensor of shape [batchSize, fwNumUnits]. Specifies a hidden
+ * state input for the first time step of the computation.
+ * * 5: bwWeights.
+ * A 2-D tensor of shape [bwNumUnits, inputSize].
+ * * 6: bwRecurrentWeights.
+ * A 2-D tensor of shape [bwNumUnits, bwNumUnits].
+ * * 7: bwBias.
+ * A 1-D tensor of shape [bwNumUnits].
+ * * 8: bwHiddenState
+ * A 2-D tensor of shape [batchSize, bwNumUnits]. Specifies a hidden
+ * state input for the first time step of the computation.
+ * * 9: auxInput.
+ * A 3-D tensor. The shape is defined by the input 6 (timeMajor). If
+ * it is set to true, then the input has a shape [maxTime, batchSize,
+ * auxInputSize], otherwise the input has a shape [batchSize, maxTime,
+ * auxInputSize]. Can be omitted. See the docs above for the usage
+ * modes explanation.
+ * * 10:fwAuxWeights.
+ * A 2-D tensor of shape [fwNumUnits, auxInputSize]. Can be omitted.
+ * See the docs above for the usage modes explanation.
+ * * 11:bwAuxWeights.
+ * A 2-D tensor of shape [bwNumUnits, auxInputSize]. Can be omitted.
+ * See the docs above for the usage modes explanation.
+ * * 12:fusedActivationFunction.
+ * A {@link FusedActivationFunc} value indicating the activation function. If
+ * “NONE” is specified then it results in a linear activation.
+ * * 13:timeMajor
+ * An {@link OperandType::BOOL} scalar specifying the shape format
+ * of input and output tensors.
+ * * 14:mergeOutputs
+ * An {@link OperandType::BOOL} scalar specifying if the outputs
+ * from forward and backward cells are separate (if set to false) or
+ * concatenated (if set to true).
+ * Outputs:
+ * * 0: fwOutput.
+ * A 3-D tensor. The first two dimensions of the shape are defined by
+ * the input 6 (timeMajor) and the third dimension is defined by the
+ * input 14 (mergeOutputs). If timeMajor is set to true, then the first
+ * two dimensions are [maxTime, batchSize], otherwise they are set to
+ * [batchSize, maxTime]. If mergeOutputs is set to true, then the third
+ * dimension is equal to (fwNumUnits + bwNumUnits), otherwise it is set
+ * to fwNumUnits.
+ * * 1: bwOutput.
+ * A 3-D tensor. If the input 14 (mergeOutputs) is set to true, then
+ * this tensor is not produced. The shape is defined by the input 6
+ * (timeMajor). If it is set to true, then the shape is set to
+ * [maxTime, batchSize, bwNumUnits], otherwise the shape is set to
+ * [batchSize, maxTime, bwNumUnits].
+ * * 2: The forward hidden state output.
+ * A 2-D tensor of shape [batchSize, fwNumUnits] containing a hidden
+ * state from the last time step in the sequence. This output is
+ * optional and can be omitted. If this output is present then output
+ * 3 must be present as well.
+ * Available since HAL version 1.3.
+ * * 3: The backward hidden state output.
+ * A 2-D tensor of shape [batchSize, bwNumUnits] containing a hidden
+ * state from the last time step in the sequence. This output is
+ * optional and can be omitted. If this output is present then output
+ * 2 must be present as well.
+ * Available since HAL version 1.3.
+ */
+ BIDIRECTIONAL_SEQUENCE_RNN = 43,
+ /**
+ * Greedily selects a subset of bounding boxes in descending order of score.
+ *
+ * This op applies NMS algorithm to each class. In each loop of execution,
+ * the box with maximum score gets selected and removed from the pending set.
+ * The scores of the rest of boxes are lowered according to the
+ * intersection-over-union (IOU) overlapping with the previously selected
+ * boxes and a specified NMS kernel method. Any boxes with score less
+ * than a threshold are removed from the pending set.
+ *
+ * Three NMS kernels are supported:
+ * * Hard: score_new = score_old * (1 if IoU < threshold else 0)
+ * * Linear: score_new = score_old * (1 if IoU < threshold else 1 - IoU)
+ * * Gaussian: score_new = score_old * exp(- IoU^2 / sigma)
+ *
+ * Axis-aligned bounding boxes are represented by its upper-left corner
+ * coordinate (x1,y1) and lower-right corner coordinate (x2,y2). A valid
+ * bounding box should satisfy x1 <= x2 and y1 <= y2.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Inputs:
+ * * 0: A 2-D Tensor of shape [num_rois, num_classes], specifying the score
+ * of each bounding box proposal. The boxes are grouped by batches in the
+ * first dimension. Zero num_rois is supported for this tensor.
+ * * 1: A 2-D Tensor specifying the bounding boxes of shape
+ * [num_rois, num_classes * 4], organized in the order [x1, y1, x2, y2].
+ * The boxes are grouped by batches in the first dimension. The sequential
+ * order of the boxes corresponds with input0. For input0 of type
+ * {@link OperandType::TENSOR_QUANT8_ASYMM}, this tensor should be of
+ * {@link OperandType::TENSOR_QUANT16_ASYMM}, with zeroPoint of 0 and
+ * scale of 0.125.
+ * For input0 of type {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED},
+ * this tensor should be of {@link OperandType::TENSOR_QUANT16_ASYMM},
+ * with zeroPoint of -128 and scale of 0.125.
+ * Zero num_rois is supported for this tensor.
+ * * 2: A 1-D {@link OperandType::TENSOR_INT32} tensor, of shape
+ * [num_rois], specifying the batch index of each box. Boxes with
+ * the same batch index are grouped together.
+ * * 3: An {@link OperandType::FLOAT32} scalar, score_threshold. Boxes
+ * with scores lower than the threshold are filtered before sending
+ * to the NMS algorithm.
+ * * 4: An {@link OperandType::INT32} scalar, specifying the maximum
+ * number of selected bounding boxes for each image. Set to a negative
+ * value for unlimited number of output bounding boxes.
+ * * 5: An {@link OperandType::INT32} scalar, specifying the NMS
+ * kernel method, options are 0:hard, 1:linear, 2:gaussian.
+ * * 6: An {@link OperandType::FLOAT32} scalar, specifying the IoU
+ * threshold in hard and linear NMS kernel. This field is ignored if
+ * gaussian kernel is selected.
+ * * 7: An {@link OperandType::FLOAT32} scalar, specifying the sigma in
+ * gaussian NMS kernel. This field is ignored if gaussian kernel is
+ * not selected.
+ * * 8: An {@link OperandType::FLOAT32} scalar, nms_score_threshold.
+ * Boxes with scores lower than the threshold are dropped during the
+ * score updating phase in soft NMS.
+ *
+ * Outputs:
+ * * 0: A 1-D Tensor of the same {@link OperandType} as input0, with shape
+ * [num_output_rois], specifying the score of each output box. The boxes
+ * are grouped by batches, but the sequential order in each batch is not
+ * guaranteed. For type of {@link OperandType::TENSOR_QUANT8_ASYMM},
+ * guaranteed. For type of {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * or {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED},
+ * the scale and zero point must be the same as input0.
+ * * 1: A 2-D Tensor of the same {@link OperandType} as input1, with shape
+ * [num_output_rois, 4], specifying the coordinates of each
+ * output bounding box with the same format as input1. The sequential
+ * order of the boxes corresponds with output0. For type of
+ * {@link OperandType::TENSOR_QUANT16_ASYMM}, the scale must be
+ * 0.125 and the zero point must be 0.
+ * * 2: A 1-D {@link OperandType::TENSOR_INT32} tensor, of shape
+ * [num_output_rois], specifying the class of each output box. The
+ * sequential order of the boxes corresponds with output0.
+ * * 3: A 1-D {@link OperandType::TENSOR_INT32} tensor, of shape
+ * [num_output_rois], specifying the batch index of each box. Boxes
+ * with the same batch index are grouped together.
+ */
+ BOX_WITH_NMS_LIMIT = 44,
+ /**
+ * Casts a tensor to a type.
+ *
+ * This operation ignores the scale and zeroPoint of quanized tensors,
+ * e.g. it treats a {@link OperandType::TENSOR_QUANT8_ASYMM} input
+ * as a tensor of uint8 values.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_INT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * Since HAL version 1.3, casting tensors of the following
+ * {@link OperandType} to the same {@link OperandType} is supported:
+ * * {@link OperandType::TENSOR_BOOL8}
+ * * {@link OperandType::TENSOR_INT32}
+ * * {@link OperandType::TENSOR_QUANT16_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT16_SYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}
+ * * {@link OperandType::TENSOR_QUANT8_SYMM}
+ *
+ * Supported tensor rank: from 1
+ *
+ * Inputs:
+ * * 0: A tensor.
+ *
+ * Outputs:
+ * * 0: A tensor with the same shape as input0.
+ */
+ CAST = 45,
+ /**
+ * Shuffle the channels of the input tensor.
+ *
+ * Given an input tensor and a integer value of num_groups, CHANNEL_SHUFFLE
+ * divide the channel dimension into num_groups groups, and reorganize the
+ * channels by grouping channels with the same index in each group.
+ *
+ * Along the channel dimension, the output is calculated using this formula:
+ *
+ * output_channel[k * num_groups + g] = input_channel[g * group_size + k]
+ *
+ * where group_size = num_channels / num_groups
+ *
+ * The number of channels must be divisible by num_groups.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: up to 4
+ *
+ * Inputs:
+ * * 0: An n-D tensor, specifying the tensor to be shuffled.
+ * * 1: An {@link OperandType::INT32} scalar, specifying the number of
+ * groups.
+ * * 2: An {@link OperandType::INT32} scalar, specifying the dimension
+ * channel shuffle would be performed on. Negative index is used to
+ * specify axis from the end (e.g. -1 for the last axis). Must be in
+ * the range [-n, n).
+ *
+ * Outputs:
+ * * 0: A tensor of the same {@link OperandType} and same shape as input0.
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint must be the same as input0.
+ */
+ CHANNEL_SHUFFLE = 46,
+ /**
+ * Apply postprocessing steps to bounding box detections.
+ *
+ * Bounding box detections are generated by applying transformation on a set
+ * of predefined anchors with the bounding box deltas from bounding box
+ * regression. A final step of hard NMS is applied to limit the number of
+ * returned boxes.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ *
+ * Inputs:
+ * * 0: A 3-D Tensor of shape [batches, num_anchors, num_classes], specifying
+ * the score of each anchor with each class. Class 0 for each
+ * [batches, num_anchors, 0] is background and will be ignored.
+ * * 1: A 3-D Tensor of shape [batches, num_anchors, length_box_encoding], with
+ * the first four values in length_box_encoding specifying the bounding
+ * box deltas. The box deltas are encoded in the order of [dy, dx, dh, dw],
+ * where dy and dx is the linear-scale relative correction factor for the
+ * center position of the bounding box with respect to the width and height,
+ * dh and dw is the log-scale relative correction factor for the width and
+ * height. All the entries in length_box_encoding beyond the first four
+ * values are ignored in this operation.
+ * * 2: A 2-D Tensor of shape [num_anchors, 4], specifying the shape of each
+ * predefined anchor, with format [ctr_y, ctr_x, h, w], where ctr_y and
+ * ctr_x are the center position of the box, and h and w are the height
+ * and the width.
+ * * 3: An {@link OperandType::FLOAT32} scalar, specifying the scaling
+ * factor for dy in bounding box deltas.
+ * * 4: An {@link OperandType::FLOAT32} scalar, specifying the scaling
+ * factor for dx in bounding box deltas.
+ * * 5: An {@link OperandType::FLOAT32} scalar, specifying the scaling
+ * factor for dh in bounding box deltas.
+ * * 6: An {@link OperandType::FLOAT32} scalar, specifying the scaling
+ * factor for dw in bounding box deltas.
+ * * 7: An {@link OperandType::BOOL} scalar, set to true to use regular
+ * multi-class NMS algorithm that do NMS separately for each class,
+ * set to false for a faster algorithm that only do one single NMS
+ * using the highest class score..
+ * * 8: An {@link OperandType::INT32} scalar, max_num_detections, specifying
+ * the maximum number of boxes for the output. Boxes with the lowest
+ * scores are discarded to meet the limit.
+ * * 9: An {@link OperandType::INT32} scalar, only used when input7 is
+ * set to false, specifying the maximum number of classes per detection.
+ * * 10: An {@link OperandType::INT32} scalar, only used when input7 is
+ * set to true, specifying the maximum number of detections when
+ * applying NMS algorithm for each single class.
+ * * 11: A scalar, score_threshold. Boxes with scores lower than the
+ * threshold are filtered before sending to the NMS algorithm. The
+ * scalar must be of {@link OperandType::FLOAT16} if input0 is of
+ * {@link OperandType::TENSOR_FLOAT16} and of
+ * {@link OperandType::FLOAT32} if input0 is of
+ * {@link OperandType::TENSOR_FLOAT32}.
+ * * 12: A scalar, specifying the IoU threshold for hard NMS. The scalar
+ * must be of {@link OperandType::FLOAT16} if input0 is of
+ * {@link OperandType::TENSOR_FLOAT16} and of
+ * {@link OperandType::FLOAT32} if input0 is of
+ * {@link OperandType::TENSOR_FLOAT32}.
+ * * 13: An {@link OperandType::BOOL} scalar, set to true to include
+ * background class in the list of label map for the output, set
+ * to false to not include the background. When the background
+ * class is included, it has label 0 and the output classes start
+ * at 1 in the label map, otherwise, the output classes start at 0.
+ *
+ * Outputs:
+ * * 0: A 2-D tensor of the same {@link OperandType} as input0, with shape
+ * [batches, max_num_detections], specifying the score of each output
+ * detections.
+ * * 1: A 3-D tensor of shape [batches, max_num_detections, 4], specifying the
+ * coordinates of each output bounding box, with format
+ * [y1, x1, y2, x2].
+ * * 2: A 2-D {@link OperandType::TENSOR_INT32} tensor, of shape
+ * [batches, max_num_detections], specifying the class label for each
+ * output detection.
+ * * 3: An 1-D {@link OperandType::TENSOR_INT32} tensor, of shape [batches],
+ * specifying the number of valid output detections for each batch.
+ */
+ DETECTION_POSTPROCESSING = 47,
+ /**
+ * For input tensors x and y, computes x == y elementwise.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_BOOL8}
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_INT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: from 1
+ *
+ * This operation supports broadcasting.
+ *
+ * Inputs:
+ * * 0: A tensor.
+ * * 1: A tensor of the same {@link OperandType} and dimensions compatible
+ * with input0.
+ *
+ * Outputs:
+ * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}.
+ */
+ EQUAL = 48,
+ /**
+ * Computes exponential of x element-wise.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ *
+ * Supported tensor rank: from 1.
+ *
+ * Inputs:
+ * * 0: A tensor.
+ *
+ * Outputs:
+ * * 0: The output tensor of same shape as input0.
+ */
+ EXP = 49,
+ /**
+ * Inserts a dimension of 1 into a tensor's shape.
+ *
+ * Given a tensor input, this operation inserts a dimension of 1 at the
+ * given dimension index of input's shape. The dimension index starts at
+ * zero; if you specify a negative dimension index, it is counted backward
+ * from the end.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_INT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: from 1
+ *
+ * Inputs:
+ * * 0: An n-D tensor.
+ * * 1: An {@link OperandType::INT32} scalar specifying the dimension
+ * index to expand. Must be in the range [-(n + 1), (n + 1)).
+ *
+ * Outputs:
+ * * 0: An (n + 1)-D tensor with the same {@link OperandType} and data as
+ * input0.
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint must be the same as input0.
+ */
+ EXPAND_DIMS = 50,
+ /**
+ * Gathers values along an axis.
+ *
+ * Produces an output tensor with shape
+ * input0.dimension[:axis] + indices.dimension + input0.dimension[axis + 1:]
+ * where:
+ * # Vector indices (output is rank(input0)).
+ * output[a_0, ..., a_n, i, b_0, ..., b_n] =
+ * input0[a_0, ..., a_n, indices[i], b_0, ..., b_n]
+ *
+ * # Higher rank indices (output is rank(input0) + rank(indices) - 1).
+ * output[a_0, ..., a_n, i, ..., j, b_0, ... b_n] =
+ * input0[a_0, ..., a_n, indices[i, ..., j], b_0, ..., b_n]
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_INT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: from 1
+ *
+ * Inputs:
+ * * 0: An n-D tensor from which to gather values.
+ * * 1: An {@link OperandType::INT32} scalar specifying the axis.
+ * Negative index is used to specify axis from the end
+ * (e.g. -1 for the last axis). Must be in the range [-n, n).
+ * * 2: A k-D tensor {@link OperandType::TENSOR_INT32} of indices.
+ * The values must be in the bounds of the corresponding dimensions
+ * of input0.
+ *
+ * Outputs:
+ * * 0: An (n + k - 1)-D tensor with the same {@link OperandType} as input0.
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint must be the same as input0.
+ */
+ GATHER = 51,
+ /**
+ * Generate aixs-aligned bounding box proposals.
+ *
+ * Bounding box proposals are generated by applying transformation on a set
+ * of predefined anchors with the bounding box deltas from bounding box
+ * regression. A final step of hard NMS is applied to limit the number of
+ * returned boxes.
+ *
+ * Axis-aligned bounding boxes are represented by its upper-left corner
+ * coordinate (x1,y1) and lower-right corner coordinate (x2,y2). A valid
+ * bounding box should satisfy x1 <= x2 and y1 <= y2.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Inputs:
+ * * 0: A 4-D Tensor specifying the score of each anchor at each
+ * location. With "NHWC" data layout, the tensor shape is
+ * [batches, height, width, num_anchors]. With "NCHW" data layout,
+ * the tensor shape is [batches, num_anchors, height, width].
+ * * 1: A 4-D Tensor specifying the bounding box deltas. With "NHWC" data
+ * layout, the tensor shape is [batches, height, width, num_anchors * 4].
+ * With "NCHW" data layout, the tensor shape is
+ * [batches, num_anchors * 4, height, width]. The box deltas are encoded
+ * in the order of [dx, dy, dw, dh], where dx and dy is the linear-scale
+ * relative correction factor for the center position of the bounding box
+ * with respect to the width and height, dw and dh is the log-scale
+ * relative correction factor for the width and height. The last
+ * dimensions is the channel dimension.
+ * * 2: A 2-D Tensor of shape [num_anchors, 4], specifying the shape of each
+ * predefined anchor, with format [x1, y1, x2, y2]. For input0 of type
+ * {@link OperandType::TENSOR_QUANT8_ASYMM} or
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}, this tensor should be of
+ * {@link OperandType::TENSOR_QUANT16_SYMM}, with scale of 0.125.
+ * * 3: A 2-D Tensor of shape [batches, 2], specifying the size of
+ * each image in the batch, with format [image_height, image_width].
+ * For input0 of type {@link OperandType::TENSOR_QUANT8_ASYMM} or
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}, this
+ * tensor should be of {@link OperandType::TENSOR_QUANT16_SYMM}, with
+ * scale of 0.125.
+ * * 4: An {@link OperandType::FLOAT32} scalar, specifying the ratio
+ * from the height of original image to the height of feature map.
+ * * 5: An {@link OperandType::FLOAT32} scalar, specifying the ratio
+ * from the width of original image to the width of feature map.
+ * * 6: An {@link OperandType::INT32} scalar, specifying the maximum
+ * number of boxes before going into the hard NMS algorithm. Boxes
+ * with the lowest scores are discarded to meet the limit. Set to
+ * a non-positive value for unlimited number.
+ * * 7: An {@link OperandType::INT32} scalar, specifying the maximum
+ * number of boxes returning from the hard NMS algorithm. Boxes
+ * with the lowest scores are discarded to meet the limit. Set to
+ * a non-positive value for unlimited number.
+ * * 8: An {@link OperandType::FLOAT32} scalar, specifying the IoU
+ * threshold for hard NMS.
+ * * 9: An {@link OperandType::FLOAT32} scalar, min_size. Boxes with
+ * height or width lower than the absolute threshold are filtered out.
+ * * 10: An {@link OperandType::BOOL} scalar, set to true to specify
+ * NCHW data layout for input0 and input1. Set to false for NHWC.
+ *
+ * Outputs:
+ * * 0: A tensor of the same {@link OperandType} as input0, of shape
+ * [num_output_rois], specifying the score of each output box.
+ * The boxes are grouped by batches, but the sequential order in
+ * each batch is not guaranteed. For type of
+ * {@link OperandType::TENSOR_QUANT8_ASYMM} or
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}, the scale and zero
+ * point must be the same as input0.
+ * * 1: A tensor of the same {@link OperandType} as input3, of shape
+ * [num_output_rois, 4], specifying the coordinates of each output
+ * bounding box for each class, with format [x1, y1, x2, y2].
+ * The sequential order of the boxes corresponds with output0.
+ * For type of {@link OperandType::TENSOR_QUANT16_ASYMM}, the
+ * scale must be 0.125 and the zero point must be 0.
+ * * 2: A 1-D {@link OperandType::TENSOR_INT32} tensor, of shape
+ * [num_output_rois], specifying the batch index of each box. Boxes
+ * with the same batch index are grouped together.
+ */
+ GENERATE_PROPOSALS = 52,
+ /**
+ * For input tensors x and y, computes x > y elementwise.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_BOOL8}
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_INT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: from 1
+ *
+ * This operation supports broadcasting.
+ *
+ * Inputs:
+ * * 0: A tensor.
+ * * 1: A tensor of the same {@link OperandType} and dimensions compatible
+ * with input0.
+ *
+ * Outputs:
+ * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}.
+ */
+ GREATER = 53,
+ /**
+ * For input tensors x and y, computes x >= y elementwise.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_BOOL8}
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_INT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: from 1
+ *
+ * This operation supports broadcasting.
+ *
+ * Inputs:
+ * * 0: A tensor.
+ * * 1: A tensor of the same {@link OperandType} and dimensions compatible
+ * with input0.
+ *
+ * Outputs:
+ * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}.
+ */
+ GREATER_EQUAL = 54,
+ /**
+ * Performs a grouped 2-D convolution operation.
+ *
+ * Given an input tensor of shape [batches, height, width, depth_in] and a
+ * filter tensor of shape [depth_out, filter_height, filter_width, depth_group]
+ * containing depth_out convolutional filters of depth depth_group, GROUPED_CONV
+ * applies a group of different filters to each input channel group, then
+ * concatenates the results together.
+ *
+ * Specifically, the input channels are divided into num_groups groups, each with
+ * depth depth_group, i.e. depth_in = num_groups * depth_group. The convolutional
+ * filters are also divided into num_groups groups, i.e. depth_out is divisible
+ * by num_groups. GROUPED_CONV applies each group of filters to the corresponding
+ * input channel group, and the result are concatenated together.
+ *
+ * The output dimensions are functions of the filter dimensions, stride, and
+ * padding.
+ *
+ * The values in the output tensor are computed as:
+ *
+ * output[b, i, j, g * channel_multiplier + q] =
+ * sum_{di, dj, dk} (
+ * input[b, strides[1] * i + di, strides[2] * j + dj,
+ * g * depth_group + dk] *
+ * filter[g * channel_multiplier + q, di, dj, dk]
+ * ) + bias[channel]
+ *
+ * where channel_multiplier = depth_out / num_groups
+ *
+ * Supported tensor {@link OperandType} configurations:
+ * * 16 bit floating point:
+ * * * {@link OperandType::TENSOR_FLOAT16} for input, filter, output, and bias.
+ *
+ * * 32 bit floating point:
+ * * * {@link OperandType::TENSOR_FLOAT32} for input, filter, output, and bias.
+ *
+ * * Quantized:
+ * * * {@link OperandType::TENSOR_QUANT8_ASYMM} for input, filter, and output.
+ * * * {@link OperandType::TENSOR_INT32} for bias (with scale set to
+ * * * input.scale * filter.scale).
+ *
+ * * Quantized signed (since HAL version 1.3):
+ * * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} for input, filter, and output.
+ * * * {@link OperandType::TENSOR_INT32} for bias (with scale set to
+ * * * input.scale * filter.scale).
+ *
+ * * Quantized with symmetric per channel quantization for the filter:
+ * * * {@link OperandType::TENSOR_QUANT8_ASYMM} for input, and output.
+ * * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} for filter.
+ * * * {@link OperandType::TENSOR_INT32} for bias (scale set to 0.0,
+ * * * each value scaling is separate and equal to input.scale * filter.scales[channel]).
+ *
+ * * Quantized signed with filter symmetric per channel quantization (since HAL version 1.3):
+ * * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} for input, and output.
+ * * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} for filter.
+ * * * {@link OperandType::TENSOR_INT32} for bias (scale set to 0.0,
+ * * * each value scaling is separate and equal to input.scale * filter.scales[channel]).
+ *
+ * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+ * With the default data layout NHWC, the data is stored in the order of:
+ * [batch, height, width, channels]. Alternatively, the data layout could
+ * be NCHW, the data storage order of: [batch, channels, height, width].
+ *
+ * Both explicit padding and implicit padding are supported.
+ *
+ * Inputs (explicit padding):
+ * * 0: A 4-D tensor, of shape [batches, height, width, depth_in],
+ * specifying the input, where depth_in = num_groups * depth_group.
+ * * 1: A 4-D tensor, of shape
+ * [depth_out, filter_height, filter_width, depth_group], specifying
+ * the filter, where depth_out must be divisible by num_groups. For
+ * tensor of type {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL}
+ * the channel dimension (channelDim at
+ * {@link SymmPerChannelQuantParams}) must be set to 0.
+ * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input
+ * tensor of type {@link OperandType::TENSOR_FLOAT32} or
+ * {@link OperandType::TENSOR_FLOAT16}, the bias must be of the same type.
+ * For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}
+ * the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint
+ * of 0 and bias_scale == input_scale * filter_scale. For filter tensor
+ * of {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL}, the bias
+ * should be of {@link OperandType::TENSOR_INT32}, with zeroPoint of
+ * 0 and bias_scale of 0. The actual scale of each value 'i' is equal to
+ * bias_scale[i] = input_scale * filter_scale[i].
+ * * 3: An {@link OperandType::INT32} scalar, specifying the padding on
+ * the left, in the ‘width’ dimension.
+ * * 4: An {@link OperandType::INT32} scalar, specifying the padding on
+ * the right, in the ‘width’ dimension.
+ * * 5: An {@link OperandType::INT32} scalar, specifying the padding on
+ * the top, in the ‘height’ dimension.
+ * * 6: An {@link OperandType::INT32} scalar, specifying the padding on
+ * the bottom, in the ‘height’ dimension.
+ * * 7: An {@link OperandType::INT32} scalar, specifying the stride when
+ * walking through input in the ‘width’ dimension.
+ * * 8: An {@link OperandType::INT32} scalar, specifying the stride when
+ * walking through input in the ‘height’ dimension.
+ * * 9: An {@link OperandType::INT32} scalar, specifying the number of
+ * groups.
+ * * 10: An {@link OperandType::INT32} scalar, and has to be one of the
+ * {@link FusedActivationFunc} values. Specifies the activation to
+ * invoke on the result.
+ * * 11: An {@link OperandType::BOOL} scalar, set to true to specify
+ * NCHW data layout for input0 and output0. Set to false for NHWC.
+ *
+ * Inputs (implicit padding):
+ * * 0: A 4-D tensor, of shape [batches, height, width, depth_in],
+ * specifying the input, where depth_in = num_groups * depth_group.
+ * * 1: A 4-D tensor, of shape
+ * [depth_out, filter_height, filter_width, depth_group], specifying
+ * the filter, where depth_out must be divisible by num_groups. For
+ * tensor of type {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL}
+ * the channel dimension (SymmPerChannelQuantParams::channelDim)
+ * must be set to 0.
+ * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input
+ * tensor of type {@link OperandType::TENSOR_FLOAT32} or
+ * {@link OperandType::TENSOR_FLOAT16}, the bias must be of the same
+ * {@link OperandType::TENSOR_FLOAT16}, the bias must be of the same type.
+ * For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}
+ * the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint
+ * of 0 and bias_scale == input_scale * filter_scale. For filter tensor
+ * of {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL}, the bias
+ * should be of {@link OperandType::TENSOR_INT32}, with zeroPoint of
+ * 0 and bias_scale of 0. The actual scale of each value 'i' is equal to
+ * bias_scale[i] = input_scale * filter_scale[i].
+ * * 3: An {@link OperandType::INT32} scalar, specifying the implicit
+ * padding scheme, has to be one of the
+ * following values: {0 (NONE), 1 (SAME), 2 (VALID)}.
+ * * 4: An {@link OperandType::INT32} scalar, specifying the stride when
+ * walking through input in the ‘width’ dimension.
+ * * 5: An {@link OperandType::INT32} scalar, specifying the stride when
+ * walking through input in the ‘height’ dimension.
+ * * 6: An {@link OperandType::INT32} scalar, specifying the number of
+ * groups.
+ * * 7: An {@link OperandType::INT32} scalar, and has to be one of the
+ * {@link FusedActivationFunc} values. Specifies the activation to
+ * invoke on the result.
+ * * 8: An {@link OperandType::BOOL} scalar, set to true to specify
+ * NCHW data layout for input0 and output0. Set to false for NHWC.
+ *
+ * Outputs:
+ * * 0: The output 4-D tensor, of shape
+ * [batches, out_height, out_width, depth_out].
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint can be different from inputs' scale and zeroPoint.
+ */
+ GROUPED_CONV_2D = 55,
+ /**
+ * Localize the maximum keypoints from heatmaps.
+ *
+ * This operation approximates the accurate maximum keypoint scores and
+ * indices after bicubic upscaling by using Taylor expansion up to the
+ * quadratic term.
+ *
+ * The bounding box is represented by its upper-left corner coordinate
+ * (x1,y1) and lower-right corner coordinate (x2,y2) in the original image.
+ * A valid bounding box should satisfy x1 <= x2 and y1 <= y2.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+ * With the default data layout NHWC, the data is stored in the order of:
+ * [batch, height, width, channels]. Alternatively, the data layout could
+ * be NCHW, the data storage order of: [batch, channels, height, width].
+ *
+ * Inputs:
+ * * 0: A 4-D Tensor of shape
+ * [num_boxes, heatmap_size, heatmap_size, num_keypoints],
+ * specifying the heatmaps, the height and width of heatmaps should
+ * be the same, and must be greater than or equal to 2.
+ * * 1: A 2-D Tensor of shape [num_boxes, 4], specifying the bounding boxes,
+ * each with format [x1, y1, x2, y2]. For input0 of type
+ * {@link OperandType::TENSOR_QUANT8_ASYMM}, this tensor should
+ * be of {@link OperandType::TENSOR_QUANT16_ASYMM}, with zeroPoint
+ * of 0 and scale of 0.125.
+ * For input0 of type
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}, this tensor
+ * should be of {@link OperandType::TENSOR_QUANT16_ASYMM}, with
+ * zeroPoint of -128 and scale of 0.125.
+ * * 2: An {@link OperandType::BOOL} scalar, set to true to specify
+ * NCHW data layout for input0. Set to false for NHWC.
+ *
+ * Outputs:
+ * * 0: A tensor of the same {@link OperandType} as input0, with shape
+ * [num_boxes, num_keypoints], specifying score of the keypoints.
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} or
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint can be different from input0 scale and zeroPoint.
+ * * 1: A tensor of the same {@link OperandType} as input1, with shape
+ * [num_boxes, num_keypoints, 2], specifying the location of
+ * the keypoints, the second dimension is organized as
+ * [keypoint_x, keypoint_y].
+ * For type of {@link OperandType::TENSOR_QUANT16_ASYMM}, the
+ * scale must be 0.125 and the zero point must be 0.
+ */
+ HEATMAP_MAX_KEYPOINT = 56,
+ /**
+ * Applies instance normalization to the input tensor.
+ *
+ * The values in the output tensor are computed as:
+ *
+ * output[b, h, w, c] =
+ * (input[b, h, w, c] - mean[b, c]) * gamma /
+ * sqrt(var[b, c] + epsilon) + beta
+ *
+ * Where the mean and variance are computed across the spatial dimensions:
+ *
+ * mean[b, c] =
+ * sum_{h, w}(input[b, h, w, c]) / sum(1)
+ *
+ * var[b, c] =
+ * sum_{h, w}(pow(input[b, h, w, c] - mean[b, c], 2)) / sum(1)
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ *
+ * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+ * With the default data layout NHWC, the data is stored in the order of:
+ * [batch, height, width, channels]. Alternatively, the data layout could
+ * be NCHW, the data storage order of: [batch, channels, height, width].
+ *
+ * Inputs:
+ * * 0: An n-D tensor, specifying the tensor to be normalized.
+ * * 1: A scalar, specifying gamma, the scale applied to the normalized
+ * tensor. The scalar must be of {@link OperandType::FLOAT16} if
+ * input0 is of {@link OperandType::TENSOR_FLOAT16} and of
+ * {@link OperandType::FLOAT32} if input0 is of
+ * {@link OperandType::TENSOR_FLOAT32}.
+ * * 2: A scalar, specifying beta, the offset applied to the normalized
+ * tensor. The scalar must be of {@link OperandType::FLOAT16} if
+ * input0 is of {@link OperandType::TENSOR_FLOAT16} and of
+ * {@link OperandType::FLOAT32} if input0 is of
+ * {@link OperandType::TENSOR_FLOAT32}.
+ * * 3: A scalar, specifying epsilon, the small value added to variance to
+ * avoid dividing by zero. The scalar must be of {@link OperandType::FLOAT16} if
+ * input0 is of {@link OperandType::TENSOR_FLOAT16} and of
+ * {@link OperandType::FLOAT32} if input0 is of
+ * {@link OperandType::TENSOR_FLOAT32}.
+ * * 4: An {@link OperandType::BOOL} scalar, set to true to specify
+ * NCHW data layout for input0 and output0. Set to false for NHWC.
+ *
+ * Outputs:
+ * * 0: A tensor of the same {@link OperandType} and same shape as input0.
+ */
+ INSTANCE_NORMALIZATION = 57,
+ /**
+ * For input tensors x and y, computes x < y elementwise.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_BOOL8}
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_INT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: from 1
+ *
+ * This operation supports broadcasting.
+ *
+ * Inputs:
+ * * 0: A tensor.
+ * * 1: A tensor of the same {@link OperandType} and dimensions compatible
+ * with input0.
+ *
+ * Outputs:
+ * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}.
+ */
+ LESS = 58,
+ /**
+ * For input tensors x and y, computes x <= y elementwise.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_BOOL8}
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_INT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: from 1
+ *
+ * This operation supports broadcasting.
+ *
+ * Inputs:
+ * * 0: A tensor.
+ * * 1: A tensor of the same {@link OperandType} and dimensions compatible
+ * with input0.
+ *
+ * Outputs:
+ * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}.
+ */
+ LESS_EQUAL = 59,
+ /**
+ * Computes natural logarithm of x element-wise.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ *
+ * Supported tensor rank: from 1.
+ *
+ * Inputs:
+ * * 0: A tensor.
+ *
+ * Outputs:
+ * * 0: The output tensor of same shape as input0.
+ */
+ LOG = 60,
+ /**
+ * Returns the truth value of x AND y element-wise.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_BOOL8}
+ *
+ * Supported tensor rank: from 1
+ *
+ * This operation supports broadcasting.
+ *
+ * Inputs:
+ * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}.
+ * * 1: A tensor of {@link OperandType::TENSOR_BOOL8} and dimensions
+ * compatible with input0.
+ *
+ * Outputs:
+ * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}.
+ */
+ LOGICAL_AND = 61,
+ /**
+ * Computes the truth value of NOT x element-wise.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_BOOL8}
+ *
+ * Supported tensor rank: from 1.
+ *
+ * Inputs:
+ * * 0: A tensor.
+ *
+ * Outputs:
+ * * 0: The output tensor of same shape as input0.
+ */
+ LOGICAL_NOT = 62,
+ /**
+ * Returns the truth value of x OR y element-wise.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_BOOL8}
+ *
+ * Supported tensor rank: from 1
+ *
+ * This operation supports broadcasting.
+ *
+ * Inputs:
+ * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}.
+ * * 1: A tensor of {@link OperandType::TENSOR_BOOL8} and dimensions
+ * compatible with input0.
+ *
+ * Outputs:
+ * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}.
+ */
+ LOGICAL_OR = 63,
+ /**
+ * Computes the log softmax activations given logits.
+ *
+ * The output is calculated using this formula:
+ *
+ * output = logits * beta - log(reduce_sum(exp(logits * beta), axis))
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ *
+ * Supported tensor rank: from 1.
+ *
+ * Inputs:
+ * * 0: A tensor specifying the input logits.
+ * * 1: A scalar, specifying the positive scaling factor for the exponent,
+ * beta.
+ * For input tensor of {@link OperandType::TENSOR_FLOAT16}, the beta
+ * value must be of {@link OperandType::FLOAT16}.
+ * For input tensor of {@link OperandType::TENSOR_FLOAT32}, the beta
+ * value must be of {@link OperandType::FLOAT32}.
+ * * 2: An {@link OperandType::INT32} scalar specifying the axis to
+ * reduce across. Negative index is used to specify axis from the
+ * end (e.g. -1 for the last axis). Must be in the range [-n, n).
+ *
+ * Outputs:
+ * * 0: The output tensor of the same {@link OperandType} and shape as
+ * input0.
+ */
+ LOG_SOFTMAX = 64,
+ /**
+ * Returns the element-wise maximum of two tensors.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_INT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: from 1.
+ *
+ * Inputs:
+ * * 0: A tensor.
+ * * 1: A tensor of the same {@link OperandType} and compatible dimensions
+ * with input0.
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+ * the scales and zeroPoint can be different from input0 scale and zeroPoint.
+ *
+ * Outputs:
+ * * 0: A tensor of the same {@link OperandType} as input0.
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+ * the scale and zeroPoint can be different from inputs' scale and zeroPoint.
+ */
+ MAXIMUM = 65,
+ /**
+ * Returns the element-wise minimum of two tensors.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_INT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: from 1.
+ *
+ * Inputs:
+ * * 0: A tensor.
+ * * 1: A tensor of the same {@link OperandType} and compatible dimensions
+ * with input0.
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+ * the scales and zeroPoint can be different from input0 scale and zeroPoint.
+ *
+ * Outputs:
+ * * 0: A tensor of the same {@link OperandType} as input0.
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+ * the scale and zeroPoint can be different from inputs' scale and zeroPoint.
+ */
+ MINIMUM = 66,
+ /**
+ * Computes numerical negative value element-wise.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_INT32}
+ *
+ * Supported tensor rank: from 1.
+ *
+ * Inputs:
+ * * 0: A tensor.
+ *
+ * Outputs:
+ * * 0: The output tensor of same shape as input0.
+ */
+ NEG = 67,
+ /**
+ * For input tensors x and y, computes x != y elementwise.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_BOOL8}
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_INT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: from 1
+ *
+ * This operation supports broadcasting.
+ *
+ * Inputs:
+ * * 0: A tensor.
+ * * 1: A tensor of the same {@link OperandType} and dimensions compatible
+ * with input0.
+ *
+ * Outputs:
+ * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}.
+ */
+ NOT_EQUAL = 68,
+ /**
+ * Pads a tensor with the given constant value according to the specified
+ * paddings.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: up to 4
+ *
+ * Inputs:
+ * * 0: An n-D tensor, specifying the tensor to be padded.
+ * * 1: A 2-D Tensor of {@link OperandType::TENSOR_INT32}, the paddings
+ * for each spatial dimension of the input tensor. The shape of the
+ * tensor must be {rank(input0), 2}.
+ * padding[i, 0] specifies the number of elements to be padded in the
+ * front of dimension i.
+ * padding[i, 1] specifies the number of elements to be padded after
+ * the end of dimension i.
+ * * 2: An scalar specifying the value to use for padding input0.
+ * For input tensor of {@link OperandType::TENSOR_FLOAT16}, the
+ * pad value must be of {@link OperandType::FLOAT16}.
+ * For input tensor of {@link OperandType::TENSOR_FLOAT32}, the
+ * pad value must be of {@link OperandType::FLOAT32}.
+ * For input tensor of {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED},
+ * the pad value must be of {@link OperandType::INT32}. The
+ * scale and zeroPoint are assumed to be the same as in input0.
+ *
+ * Outputs:
+ * * 0: A tensor of the same {@link OperandType} as input0. The
+ * output tensor has the same rank as input0, and each
+ * dimension of the output tensor has the same size as the
+ * corresponding dimension of the input tensor plus the size
+ * of the padding:
+ * output0.dimension[i] =
+ * padding[i, 0] + input0.dimension[i] + padding[i, 1]
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint must be the same as input0.
+ */
+ PAD_V2 = 69,
+ /**
+ * Computes the power of one value to another.
+ *
+ * Given a tensor base and a tensor exponent, this operation computes
+ * base^exponent elementwise.
+ *
+ * This operations supports broadcasting. The size of the output is the
+ * maximum size along each dimension of the input operands. It starts with
+ * the trailing dimensions, and works its way forward.
+ *
+ * For example:
+ * base.dimension = {4, 1, 2}
+ * exponent.dimension = {5, 4, 3, 1}
+ * output.dimension = {5, 4, 3, 2}
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ *
+ * Supported tensor rank: from 1
+ *
+ * Inputs:
+ * * 0: A tensor specifying the base.
+ * * 1: A tensor specifying the exponent.
+ *
+ * Outputs:
+ * * 0: An output tensor.
+ */
+ POW = 70,
+ /**
+ * Parametric Rectified Linear Unit.
+ *
+ * It follows: f(x) = alpha * x for x < 0, f(x) = x for x >= 0, where alpha
+ * is a learned array with the same {@link OperandType} and compatible
+ * dimensions as input x.
+ *
+ * Two dimensions are compatible when:
+ * 1. they are equal, or
+ * 2. one of them is 1
+ *
+ * The size of the output is the maximum size along each dimension of the
+ * input operands. It starts with the trailing dimensions, and works its way
+ * forward.
+ *
+ * Example:
+ * input.dimension = {4, 1, 2}
+ * alpha.dimension = {5, 4, 3, 1}
+ * output.dimension = {5, 4, 3, 2}
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: from 1
+ *
+ * Inputs:
+ * * 0: A tensor, specifying the input.
+ * * 1: A tensor of the same {@link OperandType}, and compatible dimensions
+ * as input0, specifying the alpha.
+ *
+ * Outputs:
+ * * 0: A tensor of the same {@link OperandType} as input0.
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scales and zeroPoint can be different from input0 scale and zeroPoint.
+ */
+ PRELU = 71,
+ /**
+ * Quantizes the input tensor.
+ *
+ * The formula for {@link OperandType::TENSOR_QUANT8_ASYMM} output tensor is:
+ *
+ * output = max(0, min(255, round(input / scale) + zeroPoint)
+ *
+ * The formula for {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} output
+ * tensor is:
+ *
+ * output = max(-128, min(127, round(input / scale) + zeroPoint)
+ *
+ * Supported input tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ *
+ * Supported output tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: from 1
+ *
+ * Inputs:
+ * * 0: A tensor, may be zero-sized.
+ *
+ * Outputs:
+ * * 0: The output tensor of same shape as input0, but with
+ * {@link OperandType::TENSOR_QUANT8_ASYMM} or.
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}.
+ */
+ QUANTIZE = 72,
+ /**
+ * A version of quantized LSTM, using 16 bit quantization for internal
+ * state.
+ *
+ * There is no projection layer, so cell state size is equal to the output
+ * size.
+ *
+ * Inputs:
+ * * 0: A 2-D tensor of type {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * and shape [numBatches, inputSize] specifying the input to the LSTM
+ * cell. Tensor is quantized with a fixed quantization range of
+ * [-1, 127/128] (scale = 1/128, zeroPoint = 128).
+ * * 1: The input-to-input weights.
+ * A 2-D tensor of type {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * and shape [outputSize, inputSize] specifying input-to-input part of
+ * weights for fully-connected layer inside the LSTM cell.
+ * Quantization zero point and scale must be the same across all the
+ * weights.
+ * * 2: The input-to-forget weights.
+ * A 2-D tensor of type {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * and shape [outputSize, inputSize] specifying input-to-forget part of
+ * weights for fully-connected layer inside the LSTM cell.
+ * Quantization zero point and scale must be the same across all the
+ * weights.
+ * * 3: The input-to-cell weights.
+ * A 2-D tensor of type {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * and shape [outputSize, inputSize] specifying input-to-cell part of
+ * weights for fully-connected layer inside the LSTM cell.
+ * Quantization zero point and scale must be the same across all the
+ * weights.
+ * * 4: The input-to-output weights.
+ * A 2-D tensor of type {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * and shape [outputSize, inputSize] specifying input-to-output part of
+ * weights for fully-connected layer inside the LSTM cell.
+ * Quantization zero point and scale must be the same across all the
+ * weights.
+ * * 5: The recurrent-to-input weights.
+ * A 2-D tensor of type {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * and shape [outputSize, outputSize] specifying recurrent-to-input part
+ * of weights for fully-connected layer inside the LSTM cell.
+ * Quantization zero point and scale must be the same across all the
+ * weights.
+ * * 6: The recurrent-to-forget weights.
+ * A 2-D tensor of type {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * and shape [outputSize, outputSize] specifying recurrent-to-forget
+ * part of weights for fully-connected layer inside the LSTM cell.
+ * Quantization zero point and scale must be the same across all the
+ * weights.
+ * * 7: The recurrent-to-cell weights.
+ * A 2-D tensor of type {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * and shape [outputSize, outputSize] specifying recurrent-to-cell part
+ * of weights for fully-connected layer inside the LSTM cell.
+ * Quantization zero point and scale must be the same across all the
+ * weights.
+ * * 8: The recurrent-to-output weights.
+ * A 2-D tensor of type {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * and shape [outputSize, outputSize] specifying recurrent-to-output
+ * part of weights for fully-connected layer inside the LSTM cell.
+ * Quantization zero point and scale must be the same across all the
+ * weights.
+ * * 9: The input gate bias.
+ * A 1-D tensor of type {@link OperandType::TENSOR_INT32} and shape
+ * [outputSize] specifying the bias for the fully-connected layer
+ * inside the LSTM cell. Bias is quantized with scale being a product
+ * of input and weights scales and zeroPoint equal to 0.
+ * * 10:The forget gate bias.
+ * A 1-D tensor of type {@link OperandType::TENSOR_INT32} and shape
+ * [outputSize] specifying the bias for the fully-connected layer
+ * inside the LSTM cell. Bias is quantized with scale being a product
+ * of input and weights scales and zeroPoint equal to 0.
+ * * 11:The cell bias.
+ * A 1-D tensor of type {@link OperandType::TENSOR_INT32} and shape
+ * [outputSize] specifying the bias for the fully-connected layer
+ * inside the LSTM cell. Bias is quantized with scale being a product
+ * of input and weights scales and zeroPoint equal to 0.
+ * * 12:The output gate bias.
+ * A 1-D tensor of type {@link OperandType::TENSOR_INT32} and shape
+ * [outputSize] specifying the bias for the fully-connected layer
+ * inside the LSTM cell. Bias is quantized with scale being a product
+ * of input and weights scales and zeroPoint equal to 0.
+ * * 13: A 2-D tensor of type {@link OperandType::TENSOR_QUANT16_SYMM}
+ * and shape [numBatches, outputSize] specifying the cell state from the
+ * previous time step of the LSTM cell. It is quantized using a
+ * quantization range of [-2^4, 2^4 * 32767/32768] (scale = 2^4 /
+ * 32768, zeroPoint = 0).
+ * * 14: A 2-D tensor of type {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * and shape [numBathes, outputSize] specifying the output of the LSTM
+ * cell from previous time-step. Tensor is quantized with a fixed
+ * quantization range of [-1, 127/128] (scale = 1/128, zeroPoint =
+ * 128).
+ *
+ *
+ * Outputs:
+ * * 0: A 2-D tensor of type {@link OperandType::TENSOR_QUANT16_SYMM}
+ * and shape [numBatches, outputSize] which contains a cell state from
+ * the current time step. Tensor is quantized using a quantization
+ * range of [-2^4, 2^4 * 32767/32768] (scale = 2^4 / 32768, zeroPoint =
+ * 0).
+ * * 1: A 2-D tensor of type {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * and shape [numBathes, outputSize] which contains the output value.
+ * Tensor is quantized with a fixed quantization range of [-1, 127/128]
+ * (scale = 1/128, zeroPoint = 128).
+ */
+ QUANTIZED_16BIT_LSTM = 73,
+ /**
+ * Draws samples from a multinomial distribution.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ *
+ * Inputs:
+ * * 0: A 2-D tensor with shape [batches, classes], specifying the
+ * unnormalized log-probabilities for all classes.
+ * * 1: A scalar {@link OperandType::INT32}, specifying the number of
+ * independent samples to draw for each row slice.
+ * * 2: A 1-D {@link OperandType::TENSOR_INT32} tensor with shape [2],
+ * specifying seeds used to initialize the random distribution. If both
+ * provided seeds are 0, both will be randomly generated.
+ * Outputs:
+ * * 0: A 2-D {@link OperandType::TENSOR_INT32} tensor with shape
+ * [batches, samples], containing the drawn samples.
+ */
+ RANDOM_MULTINOMIAL = 74,
+ /**
+ * Reduces a tensor by computing the "logical and" of elements along given
+ * dimensions.
+ *
+ * If keep_dims is true, the reduced dimensions are
+ * retained with length 1. Otherwise, the rank of the tensor is reduced by
+ * 1 for each entry in dimensions.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_BOOL8}
+ *
+ * Supported tensor rank: up to 4
+ *
+ * Inputs:
+ * * 0: An n-D tensor.
+ * * 1: A 1-D tensor of {@link OperandType::TENSOR_INT32}. The dimensions
+ * to reduce. Dimension values must be in the range [-n, n).
+ * * 2: An {@link OperandType::BOOL} scalar, keep_dims. If true,
+ * retains reduced dimensions with length 1.
+ *
+ * Outputs:
+ * * 0: A tensor of the same {@link OperandType} as input0.
+ * If all dimensions are reduced and keep_dims is false, the output
+ * shape is [1].
+ */
+ REDUCE_ALL = 75,
+ /**
+ * Reduces a tensor by computing the "logical or" of elements along given
+ * dimensions.
+ *
+ * If keep_dims is true, the reduced dimensions are
+ * retained with length 1. Otherwise, the rank of the tensor is reduced by
+ * 1 for each entry in dimensions.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_BOOL8}
+ *
+ * Supported tensor rank: up to 4
+ *
+ * Inputs:
+ * * 0: An n-D tensor.
+ * * 1: A 1-D tensor of {@link OperandType::TENSOR_INT32}. The dimensions
+ * to reduce. Dimension values must be in the range [-n, n).
+ * * 2: An {@link OperandType::BOOL} scalar, keep_dims. If true,
+ * retains reduced dimensions with length 1.
+ *
+ * Outputs:
+ * * 0: A tensor of the same {@link OperandType} as input0.
+ * If all dimensions are reduced and keep_dims is false, the output
+ * shape is [1].
+ */
+ REDUCE_ANY = 76,
+ /**
+ * Reduces a tensor by computing the maximum of elements along given
+ * dimensions.
+ *
+ * If keep_dims is true, the reduced dimensions are
+ * retained with length 1. Otherwise, the rank of the tensor is reduced by
+ * 1 for each entry in dimensions.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: up to 4
+ *
+ * Inputs:
+ * * 0: An n-D tensor.
+ * * 1: A 1-D tensor of {@link OperandType::TENSOR_INT32}. The dimensions
+ * to reduce. Dimension values must be in the range [-n, n).
+ * * 2: An {@link OperandType::BOOL} scalar, keep_dims. If true,
+ * retains reduced dimensions with length 1.
+ *
+ * Outputs:
+ * * 0: A tensor of the same {@link OperandType} as input0.
+ * If all dimensions are reduced and keep_dims is false, the output
+ * shape is [1].
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint must be the same as input0.
+ */
+ REDUCE_MAX = 77,
+ /**
+ * Reduces a tensor by computing the minimum of elements along given
+ * dimensions.
+ *
+ * If keep_dims is true, the reduced dimensions are
+ * retained with length 1. Otherwise, the rank of the tensor is reduced by
+ * 1 for each entry in dimensions.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: up to 4
+ *
+ * Inputs:
+ * * 0: An n-D tensor.
+ * * 1: A 1-D tensor of {@link OperandType::TENSOR_INT32}. The dimensions
+ * to reduce. Dimension values must be in the range [-n, n).
+ * * 2: An {@link OperandType::BOOL} scalar, keep_dims. If true,
+ * retains reduced dimensions with length 1.
+ *
+ * Outputs:
+ * * 0: A tensor of the same {@link OperandType} as input0.
+ * If all dimensions are reduced and keep_dims is false, the output
+ * shape is [1].
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint must be the same as input0.
+ */
+ REDUCE_MIN = 78,
+ /**
+ * Reduces a tensor by multiplying elements along given dimensions.
+ *
+ * If keep_dims is true, the reduced dimensions are
+ * retained with length 1. Otherwise, the rank of the tensor is reduced by
+ * 1 for each entry in dimensions.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ *
+ * Supported tensor rank: up to 4
+ *
+ * Inputs:
+ * * 0: An n-D tensor.
+ * * 1: A 1-D tensor of {@link OperandType::TENSOR_INT32}. The dimensions
+ * to reduce. Dimension values must be in the range [-n, n).
+ * * 2: An {@link OperandType::BOOL} scalar, keep_dims. If true,
+ * retains reduced dimensions with length 1.
+ *
+ * Outputs:
+ * * 0: A tensor of the same {@link OperandType} as input0.
+ * If all dimensions are reduced and keep_dims is false, the output
+ * shape is [1].
+ */
+ REDUCE_PROD = 79,
+ /**
+ * Reduces a tensor by summing elements along given dimensions.
+ *
+ * If keep_dims is true, the reduced dimensions are
+ * retained with length 1. Otherwise, the rank of the tensor is reduced by
+ * 1 for each entry in dimensions.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ *
+ * Supported tensor rank: up to 4
+ *
+ * Inputs:
+ * * 0: An n-D tensor.
+ * * 1: A 1-D tensor of {@link OperandType::TENSOR_INT32}. The dimensions
+ * to reduce. Dimension values must be in the range [-n, n).
+ * * 2: An {@link OperandType::BOOL} scalar, keep_dims. If true,
+ * retains reduced dimensions with length 1.
+ *
+ * Outputs:
+ * * 0: A tensor of the same {@link OperandType} as input0.
+ * If all dimensions are reduced and keep_dims is false, the output
+ * shape is [1].
+ */
+ REDUCE_SUM = 80,
+ /**
+ * Select and scale the feature map of each region of interest to a unified
+ * output size by average pooling sampling points from bilinear interpolation.
+ *
+ * The region of interest is represented by its upper-left corner coordinate
+ * (x1,y1) and lower-right corner coordinate (x2,y2) in the original image.
+ * A spatial scaling factor is applied to map into feature map coordinate.
+ * A valid region of interest should satisfy x1 <= x2 and y1 <= y2.
+ *
+ * No rounding is applied in this operation. The sampling points are unified
+ * distributed in the pooling bin and their values are calculated by bilinear
+ * interpolation.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+ * With the default data layout NHWC, the data is stored in the order of:
+ * [batch, height, width, channels]. Alternatively, the data layout could
+ * be NCHW, the data storage order of: [batch, channels, height, width].
+ *
+ * Inputs:
+ * * 0: A 4-D tensor, specifying the feature map.
+ * * 1: A 2-D Tensor of shape [num_rois, 4], specifying the locations of
+ * the regions of interest, each line with format [x1, y1, x2, y2].
+ * For input0 of type {@link OperandType::TENSOR_QUANT8_ASYMM},
+ * this tensor should be of {@link OperandType::TENSOR_QUANT16_ASYMM},
+ * with zeroPoint of 0 and scale of 0.125. Zero num_rois is
+ * supported for this tensor.
+ * * 2: An 1-D {@link OperandType::TENSOR_INT32} tensor, of shape
+ * [num_rois], specifying the batch index of each box. Boxes with
+ * the same batch index are grouped together. Zero num_rois is
+ * supported for this tensor.
+ * * 3: An {@link OperandType::INT32} scalar, specifying the output
+ * height of the output tensor.
+ * * 4: An {@link OperandType::INT32} scalar, specifying the output
+ * width of the output tensor.
+ * * 5: An {@link OperandType::FLOAT32} scalar, specifying the ratio
+ * from the height of original image to the height of feature map.
+ * * 6: An {@link OperandType::FLOAT32} scalar, specifying the ratio
+ * from the width of original image to the width of feature map.
+ * * 7: An {@link OperandType::INT32} scalar, specifying the number of
+ * sampling points in height dimension used to compute the output.
+ * Set to 0 for adaptive value of ceil(roi_height/out_height).
+ * * 8: An {@link OperandType::INT32} scalar, specifying the number of
+ * sampling points in width dimension used to compute the output.
+ * Set to 0 for adaptive value of ceil(roi_width/out_width).
+ * * 9: An {@link OperandType::BOOL} scalar, set to true to specify
+ * NCHW data layout for input0 and output0. Set to false for NHWC.
+ *
+ * Outputs:
+ * * 0: A tensor of the same {@link OperandType} as input0. The output
+ * shape is [num_rois, out_height, out_width, depth].
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint can be different from the input0 scale and zeroPoint.
+ */
+ ROI_ALIGN = 81,
+ /**
+ * Select and scale the feature map of each region of interest to a unified
+ * output size by max-pooling.
+ *
+ * The region of interest is represented by its upper-left corner coordinate
+ * (x1,y1) and lower-right corner coordinate (x2,y2) in the original image.
+ * A spatial scaling factor is applied to map into feature map coordinate.
+ * A valid region of interest should satisfy x1 <= x2 and y1 <= y2.
+ *
+ * Rounding is applied in this operation to ensure integer boundary for
+ * regions of interest and pooling bins.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+ * With the default data layout NHWC, the data is stored in the order of:
+ * [batch, height, width, channels]. Alternatively, the data layout could
+ * be NCHW, the data storage order of: [batch, channels, height, width].
+ *
+ * Inputs:
+ * * 0: A 4-D tensor, specifying the feature map.
+ * * 1: A 2-D Tensor of shape [num_rois, 4], specifying the locations of
+ * the regions of interest, each line with format [x1, y1, x2, y2].
+ * For input0 of type {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * this tensor should be of {@link OperandType::TENSOR_QUANT16_ASYMM},
+ * with zeroPoint of 0 and scale of 0.125.
+ * * 2: An 1-D {@link OperandType::TENSOR_INT32} tensor, of shape
+ * [num_rois], specifying the batch index of each box. Boxes with
+ * the same batch index are grouped together.
+ * * 3: An {@link OperandType::INT32} scalar, specifying the output
+ * height of the output tensor.
+ * * 4: An {@link OperandType::INT32} scalar, specifying the output
+ * width of the output tensor.
+ * * 5: An {@link OperandType::FLOAT32} scalar, specifying the ratio
+ * from the height of original image to the height of feature map.
+ * * 6: An {@link OperandType::FLOAT32} scalar, specifying the ratio
+ * from the width of original image to the width of feature map.
+ * * 7: An {@link OperandType::BOOL} scalar, set to true to specify
+ * NCHW data layout for input0 and output0. Set to false for NHWC.
+ *
+ * Outputs:
+ * * 0: A tensor of the same {@link OperandType} as input0. The output
+ * shape is [num_rois, out_height, out_width, depth].
+ * For input0 of type {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint must be the same as input0.
+ */
+ ROI_POOLING = 82,
+ /**
+ * Computes reciprocal of square root of x element-wise.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ *
+ * Supported tensor rank: from 1.
+ *
+ * Inputs:
+ * * 0: A tensor.
+ *
+ * Outputs:
+ * * 0: The output tensor of same shape as input0.
+ */
+ RSQRT = 83,
+ /**
+ * Using a tensor of booleans c and input tensors x and y select values
+ * elementwise from both input tensors:
+ *
+ * O[i] = C[i] ? x[i] : y[i].
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_INT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: from 1
+ *
+ * Inputs:
+ * * 0: A tensor of type {@link OperandType::TENSOR_BOOL8} acting as a
+ * mask that chooses, based on the value at each element, whether the
+ * corresponding element in the output should be taken from input1 (if
+ * true) or input2 (if false).
+ * * 1: An input tensor of the same shape as input0.
+ * * 2: An input tensor of the same shape and type as input1.
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * and {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scales and zeroPoint can be different from input1 scale and zeroPoint.
+ *
+ * Outputs:
+ * * 0: A tensor of the same type and shape as input1 and input2.
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor,
+ * the scale and zeroPoint can be different from inputs' scale and zeroPoint.
+ */
+ SELECT = 84,
+ /**
+ * Computes sin of x element-wise.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ *
+ * Supported tensor rank: from 1.
+ *
+ * Inputs:
+ * * 0: A tensor.
+ *
+ * Outputs:
+ * * 0: The output tensor of same shape as input0.
+ */
+ SIN = 85,
+ /**
+ * Extracts a slice of specified size from the input tensor starting at a
+ * specified location.
+ *
+ * The starting location is specified as a 1-D tensor containing offsets
+ * for each dimension. The size is specified as a 1-D tensor containing
+ * either size of a slice along corresponding dimension or -1. In the latter
+ * case, all the remaining elements in dimension are included in the slice.
+ *
+ * A sum of begin offset and a size of a slice must not exceed size of a
+ * corresponding dimension.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_INT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: from 1
+ *
+ * Inputs:
+ * * 0: An n-D tensor to take slice from, may be zero-sized.
+ * * 1: A 1-D tensor of type {@link OperandType::TENSOR_INT32} specifying
+ * the beginning indices of the slice in each dimension.
+ * * 2: A 1-D tensor of type {@link OperandType::TENSOR_INT32} specifying
+ * the size of the slice in each dimension.
+ *
+ * Outputs:
+ * * 0: An n-D tensor of the same type as the input containing the slice.
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * its scale and zeroPoint has to be same as the input0 scale and zeroPoint.
+ */
+ SLICE = 86,
+ /**
+ * Splits a tensor along a given axis into num_splits subtensors.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_INT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: from 1
+ *
+ * Inputs:
+ * * 0: An n-D tensor to split.
+ * * 1: An {@link OperandType::INT32} scalar specifying the axis along
+ * which to split.
+ * * 2: An {@link OperandType::INT32} scalar indicating the number of
+ * splits along given axis. Must evenly divide axis size.
+ *
+ * Outputs:
+ * * 0 ~ (num_splits - 1): Resulting subtensors.
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint must be the same as input0.
+ */
+ SPLIT = 87,
+ /**
+ * Computes square root of x element-wise.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ *
+ * Supported tensor rank: from 1.
+ *
+ * Inputs:
+ * * 0: A tensor.
+ *
+ * Outputs:
+ * * 0: The output tensor of same shape as input0.
+ */
+ SQRT = 88,
+ /**
+ * Constructs a tensor by tiling a given tensor.
+ *
+ * This operation creates a new tensor by replicating `input` `multiples`
+ * times. The output tensor's i-th dimension has `input.dims(i) * multiples[i]`
+ * elements, and the values of `input` are replicated `multiples[i]` times
+ * along the i-th dimension.
+ * For example, tiling `[a b c d]` by `[2]` produces `[a b c d a b c d]`.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_INT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: from 1
+ *
+ * Inputs:
+ * * 0: input, an n-D tensor specifying the input.
+ * * 1: multiples, a 1-D tensor of {@link OperandType::TENSOR_INT32}.
+ * The length of multiples must be n.
+ *
+ * Outputs:
+ * * 0: A tiled tensor of the same {@link OperandType} and rank as `input`.
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint must be the same as input0.
+ */
+ TILE = 89,
+ /**
+ * Finds values and indices of the k largest entries for the last dimension.
+ *
+ * Resulting values in each dimensions are sorted in descending order. If
+ * two values are equal, the one with larger index appears first.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_INT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: from 1
+ *
+ * Inputs:
+ * * 0: input, an n-D tensor specifying the input.
+ * * 1: k, an {@link OperandType::INT32} scalar, specifying the number of
+ * top elements to look for along the last dimension.
+ *
+ * Outputs:
+ * * 0: An n-D tensor of the same type as the input, containing the k
+ * largest elements along each last dimensional slice.
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint must be the same as input0.
+ * * 1: An n-D tensor of type {@link OperandType::TENSOR_INT32}
+ * containing the indices of values within the last dimension of input.
+ */
+ TOPK_V2 = 90,
+ /**
+ * Performs the transpose of 2-D convolution operation.
+ *
+ * This operation is sometimes called "deconvolution" after Deconvolutional
+ * Networks, but is actually the transpose (gradient) of
+ * {@link OperandType::CONV_2D} rather than an actual deconvolution.
+ *
+ * The output dimensions are functions of the filter dimensions, stride, and
+ * padding.
+ *
+ * Supported tensor {@link OperandType} configurations:
+ * * 16 bit floating point:
+ * * * {@link OperandType::TENSOR_FLOAT16} for input, filter, output, and bias.
+ *
+ * * 32 bit floating point:
+ * * * {@link OperandType::TENSOR_FLOAT32} for input, filter, output, and bias.
+ *
+ * * Quantized:
+ * * * {@link OperandType::TENSOR_QUANT8_ASYMM} for input, filter, and output.
+ * * * {@link OperandType::TENSOR_INT32} for bias (with scale set to
+ * * * input.scale * filter.scale).
+ *
+ * * Quantized with symmetric per channel quantization for the filter:
+ * * * {@link OperandType::TENSOR_QUANT8_ASYMM} for input, and output.
+ * * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} for filter.
+ * * * {@link OperandType::TENSOR_INT32} for bias (scale set to 0.0,
+ * * * each value scaling is separate and equal to input.scale * filter.scales[channel]).
+ *
+ * Available since HAL version 1.3:
+ * * Quantized signed (since HAL version 1.3):
+ * * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} for input, filter, and output.
+ * * * {@link OperandType::TENSOR_INT32} for bias (with scale set to
+ * * * input.scale * filter.scale).
+ *
+ * * Quantized signed with filter symmetric per channel quantization (since HAL version 1.3):
+ * * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} for input, and output.
+ * * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} for filter.
+ * * * {@link OperandType::TENSOR_INT32} for bias (scale set to 0.0,
+ * * * each value scaling is separate and equal to input.scale * filter.scales[channel]).
+ *
+ * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+ * With the default data layout NHWC, the data is stored in the order of:
+ * [batch, height, width, channels]. Alternatively, the data layout could
+ * be NCHW, the data storage order of: [batch, channels, height, width].
+ *
+ * Both explicit padding and implicit padding are supported.
+ *
+ * Inputs (explicit padding):
+ * * 0: A 4-D tensor, of shape [batches, height, width, depth_in],
+ * specifying the input.
+ * * 1: A 4-D tensor, of shape
+ * [depth_out, filter_height, filter_width, depth_in], specifying the
+ * filter. For tensor of type
+ * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} the channel
+ * dimension (SymmPerChannelQuantParams::channelDim) must be set to 0.
+ * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input
+ * tensor of type {@link OperandType::TENSOR_FLOAT32} or
+ * {@link OperandType::TENSOR_FLOAT16}, the bias must be of the
+ * same type.
+ * For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * and {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED},
+ * the bias should be of {@link OperandType::TENSOR_INT32},
+ * with zeroPoint of 0 and bias_scale == input_scale * filter_scale.
+ * For filter tensor of {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL},
+ * the bias must be of {@link OperandType::TENSOR_INT32}, with zeroPoint of 0
+ * and bias_scale of 0. The actual scale of each value 'i' is equal to
+ * bias_scale[i] = input_scale * filter_scale[i].
+ * * 3: An {@link OperandType::INT32} scalar, specifying the padding on
+ * the left, in the ‘width’ dimension.
+ * * 4: An {@link OperandType::INT32} scalar, specifying the padding on
+ * the right, in the ‘width’ dimension.
+ * * 5: An {@link OperandType::INT32} scalar, specifying the padding on
+ * the top, in the ‘height’ dimension.
+ * * 6: An {@link OperandType::INT32} scalar, specifying the padding on
+ * the bottom, in the ‘height’ dimension.
+ * * 7: An {@link OperandType::INT32} scalar, specifying the stride when
+ * walking through input in the ‘width’ dimension.
+ * * 8: An {@link OperandType::INT32} scalar, specifying the stride when
+ * walking through input in the ‘height’ dimension.
+ * * 9: An {@link OperandType::INT32} scalar, and has to be one of the
+ * {@link FusedActivationFunc} values. Specifies the activation to
+ * invoke on the result.
+ * * 10: An {@link OperandType::BOOL} scalar, set to true to specify
+ * NCHW data layout for input0 and output0. Set to false for NHWC.
+ *
+ * Inputs (implicit padding):
+ * * 0: A 4-D tensor, of shape [batches, height, width, depth_in],
+ * specifying the input.
+ * * 1: A 4-D tensor, of shape
+ * [depth_out, filter_height, filter_width, depth_in], specifying the
+ * filter. For tensor of type
+ * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} the channel
+ * dimension (SymmPerChannelQuantParams::channelDim) must be set to 0.
+ * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input
+ * tensor of type {@link OperandType::TENSOR_FLOAT32} or
+ * {@link OperandType::TENSOR_FLOAT16}, the bias should be of the
+ * same type.
+ * For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * and {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED},
+ * the bias should be of {@link OperandType::TENSOR_INT32},
+ * with zeroPoint of 0 and bias_scale == input_scale * filter_scale.
+ * For filter tensor of {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL},
+ * the bias must be of {@link OperandType::TENSOR_INT32}, with zeroPoint of 0
+ * and bias_scale of 0. The actual scale of each value 'i' is equal to
+ * bias_scale[i] = input_scale * filter_scale[i].
+ * * 3: An {@link OperandType::TENSOR_INT32} tensor, specifying the output
+ * tensor shape.
+ * * 4: An {@link OperandType::INT32} scalar, specifying the implicit
+ * padding scheme, has to be one of the
+ * following values: {0 (NONE), 1 (SAME), 2 (VALID)}.
+ * * 5: An {@link OperandType::INT32} scalar, specifying the stride when
+ * walking through input in the ‘width’ dimension.
+ * * 6: An {@link OperandType::INT32} scalar, specifying the stride when
+ * walking through input in the ‘height’ dimension.
+ * * 7: An {@link OperandType::INT32} scalar, and has to be one of the
+ * {@link FusedActivationFunc} values. Specifies the activation to
+ * invoke on the result.
+ * * 8: An {@link OperandType::BOOL} scalar, set to true to specify
+ * NCHW data layout for input0 and output0. Set to false for NHWC.
+ *
+ * Outputs:
+ * * 0: The output 4-D tensor, of shape
+ * [batches, out_height, out_width, depth_out].
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint can be different from inputs' scale and zeroPoint.
+ */
+ TRANSPOSE_CONV_2D = 91,
+ /**
+ * A recurrent neural network specified by an LSTM cell.
+ *
+ * Performs (fully) dynamic unrolling of input.
+ *
+ * This Op unrolls the input along the time dimension, and implements the
+ * following operation for each element in the sequence
+ * s = 1...sequence_length:
+ * outputs[s] = projection(state = activation(LSTMOp(inputs[s])))
+ *
+ * Where LSTMOp is the LSTM op as in {@link OperandType::LSTM},
+ * the "projection" is an optional projection layer from state and output
+ * and the “activation” is the function passed as the
+ * “fused_activation_function” argument (if not “NONE”).
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ *
+ * Supported tensor rank: 3, either time-major or batch-major.
+ *
+ * All input and output tensors must be of the same type.
+ *
+ * Inputs:
+ * * 0: The input (\f$x_t\f$).
+ * A 3-D tensor of shape:
+ * If time-major: [max_time, batch_size, input_size]
+ * If batch-major: [batch_size, max_time, input_size]
+ * where “max_time” is the number of timesteps (sequence length),
+ * “batch_size” corresponds to the batching dimension, and
+ * “input_size” is the size of the input.
+ * * 1: The input-to-input weights (\f$W_{xi}\f$). Optional.
+ * A 2-D tensor of shape [num_units, input_size], where “num_units”
+ * corresponds to the number of cell units.
+ * * 2: The input-to-forget weights (\f$W_{xf}\f$).
+ * A 2-D tensor of shape [num_units, input_size].
+ * * 3: The input-to-cell weights (\f$W_{xc}\f$).
+ * A 2-D tensor of shape [num_units, input_size].
+ * * 4: The input-to-output weights (\f$W_{xo}\f$).
+ * A 2-D tensor of shape [num_units, input_size].
+ * * 5: The recurrent-to-input weights (\f$W_{hi}\f$). Optional.
+ * A 2-D tensor of shape [num_units, output_size], where “output_size”
+ * corresponds to either the number of cell units (i.e., “num_units”),
+ * or the second dimension of the “projection_weights”, if defined.
+ * * 6: The recurrent-to-forget weights (\f$W_{hf}\f$).
+ * A 2-D tensor of shape [num_units, output_size].
+ * * 7: The recurrent-to-cell weights (\f$W_{hc}\f$).
+ * A 2-D tensor of shape [num_units, output_size].
+ * * 8: The recurrent-to-output weights (\f$W_{ho}\f$).
+ * A 2-D tensor of shape [num_units, output_size].
+ * * 9: The cell-to-input weights (\f$W_{ci}\f$). Optional.
+ * A 1-D tensor of shape [num_units].
+ * * 10:The cell-to-forget weights (\f$W_{cf}\f$). Optional.
+ * A 1-D tensor of shape [num_units].
+ * * 11:The cell-to-output weights (\f$W_{co}\f$). Optional.
+ * A 1-D tensor of shape [num_units].
+ * * 12:The input gate bias (\f$b_i\f$). Optional.
+ * A 1-D tensor of shape [num_units].
+ * * 13:The forget gate bias (\f$b_f\f$).
+ * A 1-D tensor of shape [num_units].
+ * * 14:The cell bias (\f$b_c\f$).
+ * A 1-D tensor of shape [num_units].
+ * * 15:The output gate bias (\f$b_o\f$).
+ * A 1-D tensor of shape [num_units].
+ * * 16:The projection weights (\f$W_{proj}\f$). Optional.
+ * A 2-D tensor of shape [output_size, num_units].
+ * * 17:The projection bias (\f$b_{proj}\f$). Optional.
+ * A 1-D tensor of shape [output_size].
+ * * 18:The output state (in) (\f$h_{t-1}\f$).
+ * A 2-D tensor of shape [batch_size, output_size].
+ * * 19:The cell state (in) (\f$C_{t-1}\f$).
+ * A 2-D tensor of shape [batch_size, num_units].
+ * * 20:The activation function (\f$g\f$).
+ * A value indicating the activation function:
+ * <ul>
+ * <li>0: None;
+ * <li>1: Relu;
+ * <li>3: Relu6;
+ * <li>4: Tanh;
+ * <li>6: Sigmoid.
+ * </ul>
+ * * 21:The clipping threshold (\f$t_{cell}\f$) for the cell state, such
+ * that values are bound within [-cell_clip, cell_clip]. If set to 0.0
+ * then clipping is disabled.
+ * * 22:The clipping threshold (\f$t_{proj}\f$) for the output from the
+ * projection layer, such that values are bound within
+ * [-proj_clip, proj_clip]. If set to 0.0 then clipping is disabled.
+ * * 23:Time-major if true, batch-major if false.
+ * * 24:The input layer normalization weights. Optional.
+ * A 1-D tensor of shape [num_units]. Used to rescale normalized inputs
+ * to activation at input gate.
+ * * 25:The forget layer normalization weights. Optional.
+ * A 1-D tensor of shape [num_units]. Used to rescale normalized inputs
+ * to activation at forget gate.
+ * * 26:The cell layer normalization weights. Optional.
+ * A 1-D tensor of shape [num_units]. Used to rescale normalized inputs
+ * to activation at cell gate.
+ * * 27:The output layer normalization weights. Optional.
+ * A 1-D tensor of shape [num_units]. Used to rescale normalized inputs
+ * to activation at output gate.
+ *
+ * Outputs:
+ * * 0: The output (\f$o_t\f$).
+ * A 3-D tensor of shape:
+ * If time-major: [max_time, batch_size, output_size]
+ * If batch-major: [batch_size, max_time, output_size]
+ * * 1: A tensor of shape [batch_size, output_size] containing a hidden
+ * state from the last time step in the sequence. This output is
+ * optional and can be omitted. If this output is present then
+ * output #2 must be present as well.
+ * Available since HAL version 1.3.
+ * * 2: A tensor of shape [batch_size, cell_size] containing a cell state
+ * from the last time step in the sequence. This output is optional
+ * and can be omitted.
+ * Available since HAL version 1.3.
+ */
+ UNIDIRECTIONAL_SEQUENCE_LSTM = 92,
+ /**
+ * A recurrent neural network layer that applies a basic RNN cell to a
+ * sequence of inputs.
+ *
+ * This layer unrolls the input along the sequence dimension, and implements
+ * the following operation
+ * for each element in the sequence s = 1...sequence_length:
+ * outputs[s] = state = activation(inputs[s] * input_weights’ + state *
+ * recurrent_weights’ + bias)
+ *
+ * Where:
+ * * “input_weights” is a weight matrix that multiplies the inputs;
+ * * “recurrent_weights” is a weight matrix that multiplies the current
+ * “state” which itself is the output from the previous time step
+ * computation;
+ * * “bias” is a bias vector (added to each output vector in the batch);
+ * * “activation” is the function passed as the “fused_activation_function”
+ * argument (if not “NONE”).
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ *
+ * The input tensors must all be the same type.
+ *
+ * Inputs:
+ * * 0: input.
+ * A 3-D tensor. The shape is defined by the input 6 (timeMajor). If
+ * it is set to 1, then the input has a shape [maxTime, batchSize,
+ * inputSize], otherwise the input has a shape [batchSize, maxTime,
+ * inputSize].
+ * * 1: weights.
+ * A 2-D tensor of shape [numUnits, inputSize].
+ * * 2: recurrent_weights.
+ * A 2-D tensor of shape [numUnits, numUnits].
+ * * 3: bias.
+ * A 1-D tensor of shape [numUnits].
+ * * 4: hidden state
+ * A 2-D tensor of shape [batchSize, numUnits]. Specifies a hidden
+ * state input for the first time step of the computation.
+ * * 5: fusedActivationFunction.
+ * A {@link FusedActivationFunc} value indicating the activation function. If
+ * “NONE” is specified then it results in a linear activation.
+ * * 6: timeMajor
+ * An {@link OperandType::INT32} scalar specifying the shape format
+ * of input and output tensors. Must be set to either 0 or 1.
+ * Outputs:
+ * * 0: output.
+ * A 3-D tensor. The shape is defined by the input 6 (timeMajor). If
+ * it is set to 1, then the output has a shape [maxTime, batchSize,
+ * numUnits], otherwise the output has a shape [batchSize, maxTime,
+ * numUnits].
+ * * 1: A tensor of shape [batchSize, numUnits] containing hidden state
+ * from the last time step in the sequence. This output is optional
+ * and can be omitted.
+ * Available since HAL version 1.3.
+ */
+ UNIDIRECTIONAL_SEQUENCE_RNN = 93,
+ /**
+ * Resizes images to given size using the nearest neighbor interpretation.
+ *
+ * Resized images must be distorted if their output aspect ratio is not the
+ * same as input aspect ratio. The corner pixels of output may not be the
+ * same as corner pixels of input.
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} (since HAL version 1.3)
+ *
+ * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+ * With the default data layout NHWC, the data is stored in the order of:
+ * [batch, height, width, channels]. Alternatively, the data layout could
+ * be NCHW, the data storage order of: [batch, channels, height, width].
+ *
+ * Both resizing by shape and resizing by scale are supported.
+ *
+ * Inputs (resizing by shape):
+ * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
+ * the input. Zero batches is supported for this tensor.
+ * * 1: An {@link OperandType::INT32} scalar, specifying the output
+ * width of the output tensor.
+ * * 2: An {@link OperandType::INT32} scalar, specifying the output
+ * height of the output tensor.
+ * * 3: An {@link OperandType::BOOL} scalar, default to false.
+ * Set to true to specify NCHW data layout for input0 and output0.
+ * * 4: Align corners. An optional {@link OperandType::BOOL}
+ * scalar, default to false. If True, the centers of the 4 corner
+ * pixels of the input and output tensors are aligned, preserving the
+ * values at the corner pixels.
+ * Available since HAL version 1.3.
+ * * 5: Half pixel centers. An optional {@link OperandType::BOOL}
+ * scalar, default to false. If True, the pixel centers are assumed to
+ * be at (0.5, 0.5). This is the default behavior of image.resize in
+ * TF 2.0. If this parameter is True, then align_corners parameter
+ * must be False.
+ * Available since HAL version 1.3.
+ *
+ * Inputs (resizing by scale):
+ * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
+ * the input. Zero batches is supported for this tensor.
+ * * 1: A scalar, specifying width_scale, the scaling factor of the width
+ * dimension from the input tensor to the output tensor. The output
+ * width is calculated as new_width = floor(width * width_scale).
+ * The scalar must be of {@link OperandType::FLOAT16} if input0 is
+ * of {@link OperandType::TENSOR_FLOAT16} and of
+ * {@link OperandType::FLOAT32} otherwise.
+ * * 2: A scalar, specifying height_scale, the scaling factor of the height
+ * dimension from the input tensor to the output tensor. The output
+ * height is calculated as new_height = floor(height * height_scale).
+ * The scalar must be of {@link OperandType::FLOAT16} if input0 is
+ * of {@link OperandType::TENSOR_FLOAT16} and of
+ * {@link OperandType::FLOAT32} otherwise.
+ * * 3: An {@link OperandType::BOOL} scalar, default to false.
+ * Set to true to specify NCHW data layout for input0 and output0.
+ * * 4: Align corners. An optional {@link OperandType::BOOL}
+ * scalar, default to false. If True, the centers of the 4 corner
+ * pixels of the input and output tensors are aligned, preserving the
+ * values at the corner pixels.
+ * Available since HAL version 1.3.
+ * * 5: Half pixel centers. An optional {@link OperandType::BOOL}
+ * scalar, default to false. If True, the pixel centers are assumed to
+ * be at (0.5, 0.5). This is the default behavior of image.resize in
+ * TF 2.0. If this parameter is True, then align_corners parameter
+ * must be False.
+ * Available since HAL version 1.3.
+ *
+ * Outputs:
+ * * 0: The output 4-D tensor, of shape
+ * [batches, new_height, new_width, depth].
+ * For a {@link OperandType::TENSOR_QUANT8_ASYMM} and
+ * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED} tensor,
+ * the scale and zeroPoint must be the same as input0.
+ */
+ RESIZE_NEAREST_NEIGHBOR = 94,
+ /**
+ * Quantized version of {@link OperationType::LSTM}.
+ *
+ * The input and the output use asymmetric quantized types, while the rest
+ * use symmetric ones.
+ *
+ * Inputs:
+ * * 0: The input to the LSTM cell.
+ * Type: {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}
+ * Shape: [batchSize, inputSize]
+ * * 1: The input-to-input weights. Optional.
+ * Type: {@link OperandType::TENSOR_QUANT8_SYMM}
+ * Shape: [numUnits, inputSize]
+ * * 2: The input-to-forget weights.
+ * Type: {@link OperandType::TENSOR_QUANT8_SYMM}
+ * Shape: [numUnits, inputSize]
+ * * 3: The input-to-cell weights.
+ * Type: {@link OperandType::TENSOR_QUANT8_SYMM}
+ * Shape: [numUnits, inputSize]
+ * * 4: The input-to-output weights.
+ * Type: {@link OperandType::TENSOR_QUANT8_SYMM}
+ * Shape: [numUnits, inputSize]
+ * * 5: The recurrent-to-input weights. Optional.
+ * Type: {@link OperandType::TENSOR_QUANT8_SYMM}
+ * Shape: [numUnits, outputSize]
+ * * 6: The recurrent-to-forget weights.
+ * Type: {@link OperandType::TENSOR_QUANT8_SYMM}
+ * Shape: [numUnits, outputSize]
+ * * 7: The recurrent-to-cell weights.
+ * Type: {@link OperandType::TENSOR_QUANT8_SYMM}
+ * Shape: [numUnits, outputSize]
+ * * 8: The recurrent-to-output weights.
+ * Type: {@link OperandType::TENSOR_QUANT8_SYMM}
+ * Shape: [numUnits, outputSize]
+ * * 9: The cell-to-input weights (for peephole). Optional.
+ * Type: {@link OperandType::TENSOR_QUANT16_SYMM}
+ * Shape: [numUnits]
+ * * 10: The cell-to-forget weights (for peephole). Optional.
+ * Type: {@link OperandType::TENSOR_QUANT16_SYMM}
+ * Shape: [numUnits]
+ * * 11: The cell-to-output weights (for peephole). Optional.
+ * Type: {@link OperandType::TENSOR_QUANT16_SYMM}
+ * Shape: [numUnits]
+ * * 12: The input gate bias. Quantized with scale being the
+ * product of input and weights scales and zeroPoint equal to 0.
+ * Optional.
+ * Type: {@link OperandType::TENSOR_INT32}
+ * Shape: [numUnits]
+ * * 13: The forget gate bias. Quantized with scale being the
+ * product of input and weights scales and zeroPoint equal to 0.
+ * Type: {@link OperandType::TENSOR_INT32}
+ * Shape: [numUnits]
+ * * 14: The cell bias. Quantized with scale being the
+ * product of input and weights scales and zeroPoint equal to 0.
+ * Type: {@link OperandType::TENSOR_INT32}
+ * Shape: [numUnits]
+ * * 15: The output gate bias. Quantized with scale being the
+ * product of input and weights scales and zeroPoint equal to 0.
+ * Type: {@link OperandType::TENSOR_INT32}
+ * Shape: [numUnits]
+ * * 16: The projection weights. Optional.
+ * Type: {@link OperandType::TENSOR_QUANT8_SYMM}
+ * Shape: [outputSize, numUnits]
+ * * 17: The projection bias. Quantized with scale being the
+ * product of input and weights scales and zeroPoint equal to 0.
+ * Optional.
+ * Type: {@link OperandType::TENSOR_INT32}
+ * Shape: [outputSize]
+ * * 18: The output from the previous time step.
+ * Type: {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}
+ * Shape: [batchSize, outputSize]
+ * * 19: The cell state from the previous time step.
+ * Type: {@link OperandType::TENSOR_QUANT16_SYMM}
+ * Shape: [batchSize, numUnits]
+ * * 20: The input layer normalization weights. Used to rescale
+ * normalized inputs to activation at input gate. Optional.
+ * Type: {@link OperandType::TENSOR_QUANT16_SYMM}
+ * Shape: [numUnits]
+ * * 21: The forget layer normalization weights. Used to
+ * rescale normalized inputs to activation at forget gate. Optional.
+ * Type: {@link OperandType::TENSOR_QUANT16_SYMM}
+ * Shape: [numUnits]
+ * * 22: The cell layer normalization weights. Used to rescale
+ * normalized inputs to activation at cell gate. Optional.
+ * Type: {@link OperandType::TENSOR_QUANT16_SYMM}
+ * Shape: [numUnits]
+ * * 23: The output layer normalization weights. Used to
+ * rescale normalized inputs to activation at output gate. Optional.
+ * Type: {@link OperandType::TENSOR_QUANT16_SYMM}
+ * Shape: [numUnits]
+ * * 24: The cell clip. If provided the cell state is clipped
+ * by this value prior to the cell output activation. Optional.
+ * Type: {@link OperandType::FLOAT32}.
+ * * 25: The projection clip. If provided and projection is enabled,
+ * this is used for clipping the projected values. Optional.
+ * Type: {@link OperandType::FLOAT32}.
+ * * 26: The scale of the intermediate result of matmul,
+ * i.e. input to layer normalization, at input gate.
+ * Type: {@link OperandType::FLOAT32}.
+ * * 27: The scale of the intermediate result of matmul,
+ * i.e. input to layer normalization, at forget gate.
+ * Type: {@link OperandType::FLOAT32}.
+ * * 28: The scale of the intermediate result of matmul,
+ * i.e. input to layer normalization, at cell gate.
+ * Type: {@link OperandType::FLOAT32}.
+ * * 29: The scale of the intermediate result of matmul,
+ * i.e. input to layer normalization, at output gate.
+ * Type: {@link OperandType::FLOAT32}.
+ * * 30: The zero point of the hidden state, i.e. input to
+ * projection.
+ * Type: {@link OperandType::INT32}.
+ * * 31: The scale of the hidden state, i.e. input to
+ * projection.
+ * Type: {@link OperandType::FLOAT32}.
+ *
+ * Outputs:
+ * * 0: The output state (out).
+ * Type: {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}
+ * Shape: [batchSize, outputSize]
+ * * 1: The cell state (out).
+ * Type: {@link OperandType::TENSOR_QUANT16_SYMM}
+ * Shape: [batchSize, numUnits]
+ * * 2: The output. This is effectively the same as the current
+ * "output state (out)" value.
+ * Type: {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}
+ * Shape: [batchSize, outputSize]
+ */
+ QUANTIZED_LSTM = 95,
+ /**
+ * Executes one of the two referenced subgraphs as determined by a boolean
+ * value.
+ *
+ * The inputs and outputs of the two referenced subgraphs must agree with the
+ * signature of this operation. That is, if the operation has (3 + n) inputs
+ * and m outputs, both subgraphs must have n inputs and m outputs with the same
+ * types, ranks, dimensions, scales,
+ * zeroPoints, and extraParams as the corresponding operation
+ * inputs and outputs.
+ * All of the operands mentioned must have fully specified dimensions.
+ *
+ * Inputs:
+ * * 0: A value of type {@link OperandType::TENSOR_BOOL8} and shape [1]
+ * that determines which of the two referenced subgraphs to execute.
+ * The operand must have fully specified dimensions.
+ * * 1: A {@link OperandType::SUBGRAPH} reference to the subgraph to be
+ * executed if the condition is true.
+ * * 2: A {@link OperandType::SUBGRAPH} reference to the subgraph to be
+ * executed if the condition is false.
+ * * 3 ~ (n + 2): Inputs to be passed to the subgraph selected for execution.
+ *
+ * Outputs:
+ * * 0 ~ (m - 1): Outputs produced by the selected subgraph.
+ */
+ IF = 96,
+ /**
+ * Executes the body subgraph until the condition subgraph outputs false.
+ *
+ * The inputs to this operation are the condition subgraph, the body subgraph,
+ * and operand values for the first iteration of the loop. The values are
+ * implicitly split into three groups of input-output, state-only, and
+ * input-only values, as described below.
+ *
+ * The outputs of this operation are the final values of input-output
+ * operands.
+ *
+ * Both the condition and body subgraph receive (m + k + n) inputs.
+ * * The first m (m >= 1) inputs are input-output operands. For the first
+ * iteration, these are initialized from the corresponding inputs of the
+ * WHILE operation. In subsequent iterations, their values come from the
+ * corresponding outputs of the body subgraph produced during the previous
+ * iteration.
+ * * The next k (k >= 0) inputs are state-only operands. They are similar to
+ * the input-output operands, except that their values are no longer
+ * available after the loop terminates.
+ * * The last n (n >= 0) inputs are input-only operands. Their values come
+ * from the corresponding inputs of the WHILE operation.
+ *
+ * The body subgraph produces (m + k) outputs.
+ * * The first m outputs are input-output operands. They become the outputs
+ * of the WHILE operation when a termination condition is reached.
+ * * The last k outputs are state-only operands. Their values are no longer
+ * available after the loop terminates.
+ *
+ * The numbers m, k, and n are inferred by the driver as follows:
+ * m = (WHILE operation output count)
+ * k = (body subgraph output count) - m
+ * n = (body subgraph input count) - m - k
+ *
+ * The pseudo-code below illustrates the flow of a WHILE operation with
+ * inputs condition, body, initial_input_output, initial_state, input_only
+ * (m = 1, k = 1, n = 1):
+ *
+ * input_output = initial_input_output
+ * state = initial_state
+ * while condition(input_output, state, input_only):
+ * input_output, state = body(input_output, state, input_only)
+ * return input_output
+ *
+ * Inputs:
+ * * 0: A {@link OperandType::SUBGRAPH} reference to the condition
+ * subgraph. The subgraph must have (m + k + n) inputs with
+ * the same types, ranks, dimensions,
+ * scales, zeroPoints, and extraParams as the
+ * corresponding inputs of the WHILE operation and exactly one output
+ * of {@link OperandType::TENSOR_BOOL8} and shape [1].
+ * All of the operands mentioned must have fully specified dimensions.
+ * * 1: A {@link OperandType::SUBGRAPH} reference to the body subgraph.
+ * The subgraph must have (m + k + n) inputs and (m + k) outputs with
+ * the same types, ranks, dimensions,
+ * scales, zeroPoints, and extraParams as the
+ * corresponding inputs and outputs of the WHILE operation.
+ * All of the operands mentioned must have fully specified dimensions.
+ * * (m inputs): Initial values for input-output operands.
+ * * (k inputs): Initial values for state-only operands.
+ * * (n inputs): Values for input-only operands.
+ *
+ * Outputs:
+ * * 0 ~ (m - 1): Outputs produced by the loop.
+ */
+ WHILE = 97,
+ /**
+ * Computes exponential linear activation on the input tensor element-wise.
+ *
+ * The output is calculated using the following formula:
+ *
+ * ELU(x) = max(0, x) + min(0, alpha * (exp(x) - 1))
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ *
+ * Supported tensor rank: from 1.
+ *
+ * Inputs:
+ * * 0: A tensor, specifying the input. May be zero-sized.
+ * * 1: A scalar, specifying the alpha parameter.
+ * For input tensor of {@link OperandType::TENSOR_FLOAT16},
+ * the alpha value must be of {@link OperandType::FLOAT16}.
+ * For input tensor of {@link OperandType::TENSOR_FLOAT32},
+ * the alpha value must be of {@link OperandType::FLOAT32}.
+ *
+ * Outputs:
+ * * 0: The output tensor of same shape and type as input0.
+ */
+ ELU = 98,
+ /**
+ * Computes hard-swish activation on the input tensor element-wise.
+ *
+ * Hard swish activation is introduced in
+ * https://arxiv.org/pdf/1905.02244.pdf
+ *
+ * The output is calculated using the following formula:
+ *
+ * h-swish(x) = x * max(0, min(6, (x + 3))) / 6
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}
+ *
+ * Supported tensor rank: from 1.
+ *
+ * Inputs:
+ * * 0: A tensor, specifying the input. May be zero-sized.
+ *
+ * Outputs:
+ * * 0: The output tensor of same shape and type as input0.
+ * Scale and zero point of this tensor may be different from the input
+ * tensor's parameters.
+ */
+ HARD_SWISH = 99,
+ /**
+ * Creates a tensor filled with a scalar value.
+ *
+ * Supported output tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_INT32}
+ *
+ * Supported tensor rank: from 1.
+ *
+ * Inputs:
+ * * 0: A 1-D tensor, specifying the desired output tensor shape.
+ * * 1: A scalar, specifying the value to fill the output tensors with.
+ * For output tensor of {@link OperandType::TENSOR_FLOAT16},
+ * the scalar must be of {@link OperandType::FLOAT16}.
+ * For output tensor of {@link OperandType::TENSOR_FLOAT32},
+ * the scalar must be of {@link OperandType::FLOAT32}.
+ * For output tensor of {@link OperandType::TENSOR_INT32},
+ * the scalar must be of {@link OperandType::INT32}.
+ *
+ * Outputs:
+ * * 0: The output tensor.
+ */
+ FILL = 100,
+ /**
+ * Returns the rank of a tensor.
+ *
+ * The rank of a tensor is the number of dimensions in it. Also known as
+ * "order", "degree", "ndims".
+ *
+ * Supported tensor {@link OperandType}:
+ * * {@link OperandType::TENSOR_FLOAT16}
+ * * {@link OperandType::TENSOR_FLOAT32}
+ * * {@link OperandType::TENSOR_INT32}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT16_SYMM}
+ * * {@link OperandType::TENSOR_BOOL8}
+ * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL}
+ * * {@link OperandType::TENSOR_QUANT16_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_SYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM_SIGNED}
+ *
+ * Supported tensor rank: from 1.
+ *
+ * Inputs:
+ * * 0: The input tensor.
+ *
+ * Outputs:
+ * * 0: A scalar of {@link OperandType::INT32}, specifying the rank
+ * of the input tensor.
+ */
+ RANK = 101,
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/OutputShape.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/OutputShape.aidl
new file mode 100644
index 0000000..f90a613
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/OutputShape.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.neuralnetworks;
+
+/**
+ * Describes the shape information of an output operand after execution.
+ */
+@VintfStability
+parcelable OutputShape {
+ /**
+ * Dimensions of the operand.
+ */
+ int[] dimensions;
+ /**
+ * Whether the provided buffer size is sufficient for the output.
+ */
+ boolean isSufficient;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/PerformanceInfo.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/PerformanceInfo.aidl
new file mode 100644
index 0000000..6915c67
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/PerformanceInfo.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.neuralnetworks;
+
+/**
+ * Performance information for the reference workload.
+ *
+ * Used by a driver to report its performance characteristics.
+ */
+@VintfStability
+parcelable PerformanceInfo {
+ /**
+ * Ratio of the time taken by the driver to execute the workload compared to the time the CPU
+ * would take for the same workload. A lower number is better.
+ */
+ float execTime;
+ /**
+ * Ratio of the energy used by the driver compared to what the CPU would use for doing the same
+ * workload. A lower number is better.
+ */
+ float powerUsage;
+}
diff --git a/health/1.0/default/libhealthd/healthd_board_default.cpp b/neuralnetworks/aidl/android/hardware/neuralnetworks/Priority.aidl
similarity index 64%
copy from health/1.0/default/libhealthd/healthd_board_default.cpp
copy to neuralnetworks/aidl/android/hardware/neuralnetworks/Priority.aidl
index 127f98e..7dbf8e9 100644
--- a/health/1.0/default/libhealthd/healthd_board_default.cpp
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/Priority.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,15 @@
* limitations under the License.
*/
-#include <healthd/healthd.h>
+package android.hardware.neuralnetworks;
-void healthd_board_init(struct healthd_config*)
-{
- // use defaults
-}
-
-int healthd_board_battery_update(struct android::BatteryProperties*)
-{
- // return 0 to log periodic polled battery status to kernel log
- return 0;
+/**
+ * Priority given to a prepared model for execution.
+ */
+@VintfStability
+@Backing(type="int")
+enum Priority {
+ LOW,
+ MEDIUM,
+ HIGH,
}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/Request.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/Request.aidl
new file mode 100644
index 0000000..dc138ba
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/Request.aidl
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.neuralnetworks.RequestArgument;
+import android.hardware.neuralnetworks.RequestMemoryPool;
+
+/**
+ * Inputs to be sent to and outputs to be retrieved from a prepared model.
+ *
+ * A Request serves two primary tasks:
+ * 1) Provides the input and output data to be used when executing the model.
+ * 2) Specifies any updates to the input operand metadata that were left unspecified at model
+ * preparation time.
+ *
+ * An output must not overlap with any other output, with an input, or with an operand of lifetime
+ * CONSTANT_POOL.
+ */
+@VintfStability
+parcelable Request {
+ /**
+ * Input data and information to be used in the execution of a prepared model.
+ *
+ * The index of the input corresponds to the index in Model.main.inputIndexes.
+ * E.g., input[i] corresponds to Model.main.inputIndexes[i].
+ */
+ RequestArgument[] inputs;
+ /**
+ * Output data and information to be used in the execution of a prepared model.
+ *
+ * The index of the output corresponds to the index in Model.main.outputIndexes.
+ * E.g., output[i] corresponds to Model.main.outputIndexes[i].
+ */
+ RequestArgument[] outputs;
+ /**
+ * A collection of memory pools containing operand data for both the inputs and the outputs to a
+ * model.
+ */
+ RequestMemoryPool[] pools;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/RequestArgument.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/RequestArgument.aidl
new file mode 100644
index 0000000..8dc9252
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/RequestArgument.aidl
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.neuralnetworks.DataLocation;
+
+/**
+ * Metadata information specifying the location of the input or output data and any updates to the
+ * input or output operand.
+ */
+@VintfStability
+parcelable RequestArgument {
+ /**
+ * If true, the argument does not have a value. This can be used for operations that take
+ * optional arguments. If true, the fields of location are set to 0 and the dimensions vector is
+ * left empty.
+ */
+ boolean hasNoValue;
+ /**
+ * The location within one of the memory pools passed in the Request.
+ */
+ DataLocation location;
+ /**
+ * Updated dimension information.
+ *
+ * If dimensions.size() > 0, dimension information was provided along with the argument. This
+ * can be the case for models that accept inputs of varying size. This can't change the rank,
+ * just the value of the dimensions that were unspecified in the model. If dimensions.size() >
+ * 0, then all dimensions must be specified here; and any dimension that was specified in the
+ * model must have the same value here.
+ *
+ * If the dimensions in the model are not fully specified, then they must be fully specified
+ * here, unless hasNoValue is set to true. If the dimensions in the model are fully specified,
+ * then either dimensions.size() may be 0, or the dimensions in the model must be identical to
+ * the dimensions here.
+ */
+ int[] dimensions;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/RequestMemoryPool.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/RequestMemoryPool.aidl
new file mode 100644
index 0000000..faca2fe
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/RequestMemoryPool.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.neuralnetworks.Memory;
+
+/**
+ * A memory pool.
+ */
+@VintfStability
+union RequestMemoryPool {
+ /**
+ * Specifies a client-managed shared memory pool.
+ */
+ Memory pool;
+ /**
+ * Specifies a driver-managed buffer. It is the token returned from IDevice::allocate, and is
+ * specific to the IDevice object.
+ */
+ int token;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/Subgraph.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/Subgraph.aidl
new file mode 100644
index 0000000..2e9c450
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/Subgraph.aidl
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.neuralnetworks;
+
+import android.hardware.neuralnetworks.Operand;
+import android.hardware.neuralnetworks.Operation;
+
+/**
+ * An excerpt of the execution graph.
+ */
+@VintfStability
+parcelable Subgraph {
+ /**
+ * All operands included in the subgraph.
+ */
+ Operand[] operands;
+ /**
+ * All operations included in the subgraph.
+ *
+ * The operations are sorted into execution order. Every operand with lifetime SUBGRAPH_OUTPUT
+ * or TEMPORARY_VARIABLE must be written before it is read.
+ */
+ Operation[] operations;
+ /**
+ * Input indexes of the subgraph. There must be at least one.
+ *
+ * Each value corresponds to the index of the operand in "operands".
+ */
+ int[] inputIndexes;
+ /**
+ * Output indexes of the subgraph. There must be at least one.
+ *
+ * Each value corresponds to the index of the operand in "operands".
+ */
+ int[] outputIndexes;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/SymmPerChannelQuantParams.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/SymmPerChannelQuantParams.aidl
new file mode 100644
index 0000000..eb47df0
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/SymmPerChannelQuantParams.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.neuralnetworks;
+
+/**
+ * Parameters for TENSOR_QUANT8_SYMM_PER_CHANNEL operand.
+ */
+@VintfStability
+parcelable SymmPerChannelQuantParams {
+ /**
+ * Array of scaling values for each channel. Each value must be greater than zero.
+ */
+ float[] scales;
+ /**
+ * Index of the channel dimension
+ */
+ int channelDim;
+}
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/Timing.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/Timing.aidl
new file mode 100644
index 0000000..8130e08
--- /dev/null
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/Timing.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.neuralnetworks;
+
+/**
+ * Timing information measured during execution. Each time is a duration from the beginning of some
+ * task to the end of that task, including time when that task is not active (for example, preempted
+ * by some other task, or waiting for some resource to become available).
+ *
+ * Times are measured in nanoseconds. When a time is not available, it must be reported as -1.
+ */
+@VintfStability
+parcelable Timing {
+ /**
+ * Execution time on device (not driver, which runs on host processor).
+ */
+ long timeOnDevice;
+ /**
+ * Execution time in driver (including time on device).
+ */
+ long timeInDriver;
+}
diff --git a/neuralnetworks/aidl/utils/Android.bp b/neuralnetworks/aidl/utils/Android.bp
new file mode 100644
index 0000000..147d401
--- /dev/null
+++ b/neuralnetworks/aidl/utils/Android.bp
@@ -0,0 +1,34 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_library_static {
+ name: "neuralnetworks_utils_hal_aidl",
+ defaults: ["neuralnetworks_utils_defaults"],
+ srcs: ["src/*"],
+ local_include_dirs: ["include/nnapi/hal/aidl/"],
+ export_include_dirs: ["include"],
+ static_libs: [
+ "libarect",
+ "neuralnetworks_types",
+ "neuralnetworks_utils_hal_common",
+ ],
+ shared_libs: [
+ "android.hardware.neuralnetworks-V1-ndk_platform",
+ "libbinder_ndk",
+ "libhidlbase",
+ "libnativewindow",
+ ],
+}
diff --git a/neuralnetworks/aidl/utils/OWNERS b/neuralnetworks/aidl/utils/OWNERS
new file mode 100644
index 0000000..e4feee3
--- /dev/null
+++ b/neuralnetworks/aidl/utils/OWNERS
@@ -0,0 +1,11 @@
+# Neuralnetworks team
+butlermichael@google.com
+dgross@google.com
+galarragas@google.com
+jeanluc@google.com
+levp@google.com
+miaowang@google.com
+pszczepaniak@google.com
+slavash@google.com
+vddang@google.com
+xusongw@google.com
diff --git a/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Conversions.h b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Conversions.h
new file mode 100644
index 0000000..1b2f69c
--- /dev/null
+++ b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Conversions.h
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_CONVERSIONS_H
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_CONVERSIONS_H
+
+#include <aidl/android/hardware/neuralnetworks/BufferDesc.h>
+#include <aidl/android/hardware/neuralnetworks/BufferRole.h>
+#include <aidl/android/hardware/neuralnetworks/Capabilities.h>
+#include <aidl/android/hardware/neuralnetworks/DataLocation.h>
+#include <aidl/android/hardware/neuralnetworks/DeviceType.h>
+#include <aidl/android/hardware/neuralnetworks/ErrorStatus.h>
+#include <aidl/android/hardware/neuralnetworks/ExecutionPreference.h>
+#include <aidl/android/hardware/neuralnetworks/Extension.h>
+#include <aidl/android/hardware/neuralnetworks/ExtensionNameAndPrefix.h>
+#include <aidl/android/hardware/neuralnetworks/ExtensionOperandTypeInformation.h>
+#include <aidl/android/hardware/neuralnetworks/Memory.h>
+#include <aidl/android/hardware/neuralnetworks/Model.h>
+#include <aidl/android/hardware/neuralnetworks/Operand.h>
+#include <aidl/android/hardware/neuralnetworks/OperandExtraParams.h>
+#include <aidl/android/hardware/neuralnetworks/OperandLifeTime.h>
+#include <aidl/android/hardware/neuralnetworks/OperandPerformance.h>
+#include <aidl/android/hardware/neuralnetworks/OperandType.h>
+#include <aidl/android/hardware/neuralnetworks/Operation.h>
+#include <aidl/android/hardware/neuralnetworks/OperationType.h>
+#include <aidl/android/hardware/neuralnetworks/OutputShape.h>
+#include <aidl/android/hardware/neuralnetworks/PerformanceInfo.h>
+#include <aidl/android/hardware/neuralnetworks/Priority.h>
+#include <aidl/android/hardware/neuralnetworks/Request.h>
+#include <aidl/android/hardware/neuralnetworks/RequestArgument.h>
+#include <aidl/android/hardware/neuralnetworks/RequestMemoryPool.h>
+#include <aidl/android/hardware/neuralnetworks/Subgraph.h>
+#include <aidl/android/hardware/neuralnetworks/SymmPerChannelQuantParams.h>
+#include <aidl/android/hardware/neuralnetworks/Timing.h>
+
+#include <nnapi/Result.h>
+#include <nnapi/Types.h>
+#include <nnapi/hal/CommonUtils.h>
+
+#include <vector>
+
+namespace android::nn {
+
+GeneralResult<OperandType> unvalidatedConvert(const aidl_hal::OperandType& operandType);
+GeneralResult<OperationType> unvalidatedConvert(const aidl_hal::OperationType& operationType);
+GeneralResult<DeviceType> unvalidatedConvert(const aidl_hal::DeviceType& deviceType);
+GeneralResult<Priority> unvalidatedConvert(const aidl_hal::Priority& priority);
+GeneralResult<Capabilities> unvalidatedConvert(const aidl_hal::Capabilities& capabilities);
+GeneralResult<Capabilities::OperandPerformance> unvalidatedConvert(
+ const aidl_hal::OperandPerformance& operandPerformance);
+GeneralResult<Capabilities::PerformanceInfo> unvalidatedConvert(
+ const aidl_hal::PerformanceInfo& performanceInfo);
+GeneralResult<DataLocation> unvalidatedConvert(const aidl_hal::DataLocation& location);
+GeneralResult<Operand> unvalidatedConvert(const aidl_hal::Operand& operand);
+GeneralResult<Operand::ExtraParams> unvalidatedConvert(
+ const std::optional<aidl_hal::OperandExtraParams>& optionalExtraParams);
+GeneralResult<Operand::LifeTime> unvalidatedConvert(
+ const aidl_hal::OperandLifeTime& operandLifeTime);
+GeneralResult<Operand::SymmPerChannelQuantParams> unvalidatedConvert(
+ const aidl_hal::SymmPerChannelQuantParams& symmPerChannelQuantParams);
+GeneralResult<Operation> unvalidatedConvert(const aidl_hal::Operation& operation);
+GeneralResult<Model> unvalidatedConvert(const aidl_hal::Model& model);
+GeneralResult<Model::ExtensionNameAndPrefix> unvalidatedConvert(
+ const aidl_hal::ExtensionNameAndPrefix& extensionNameAndPrefix);
+GeneralResult<Model::OperandValues> unvalidatedConvert(const std::vector<uint8_t>& operandValues);
+GeneralResult<Model::Subgraph> unvalidatedConvert(const aidl_hal::Subgraph& subgraph);
+GeneralResult<OutputShape> unvalidatedConvert(const aidl_hal::OutputShape& outputShape);
+GeneralResult<MeasureTiming> unvalidatedConvert(bool measureTiming);
+GeneralResult<SharedMemory> unvalidatedConvert(const aidl_hal::Memory& memory);
+GeneralResult<Timing> unvalidatedConvert(const aidl_hal::Timing& timing);
+GeneralResult<BufferDesc> unvalidatedConvert(const aidl_hal::BufferDesc& bufferDesc);
+GeneralResult<BufferRole> unvalidatedConvert(const aidl_hal::BufferRole& bufferRole);
+GeneralResult<Request> unvalidatedConvert(const aidl_hal::Request& request);
+GeneralResult<Request::Argument> unvalidatedConvert(
+ const aidl_hal::RequestArgument& requestArgument);
+GeneralResult<Request::MemoryPool> unvalidatedConvert(
+ const aidl_hal::RequestMemoryPool& memoryPool);
+GeneralResult<ErrorStatus> unvalidatedConvert(const aidl_hal::ErrorStatus& errorStatus);
+GeneralResult<ExecutionPreference> unvalidatedConvert(
+ const aidl_hal::ExecutionPreference& executionPreference);
+GeneralResult<Extension> unvalidatedConvert(const aidl_hal::Extension& extension);
+GeneralResult<Extension::OperandTypeInformation> unvalidatedConvert(
+ const aidl_hal::ExtensionOperandTypeInformation& operandTypeInformation);
+GeneralResult<SharedHandle> unvalidatedConvert(
+ const ::aidl::android::hardware::common::NativeHandle& handle);
+
+GeneralResult<ExecutionPreference> convert(
+ const aidl_hal::ExecutionPreference& executionPreference);
+GeneralResult<SharedMemory> convert(const aidl_hal::Memory& memory);
+GeneralResult<Model> convert(const aidl_hal::Model& model);
+GeneralResult<Operand> convert(const aidl_hal::Operand& operand);
+GeneralResult<OperandType> convert(const aidl_hal::OperandType& operandType);
+GeneralResult<Priority> convert(const aidl_hal::Priority& priority);
+GeneralResult<Request::MemoryPool> convert(const aidl_hal::RequestMemoryPool& memoryPool);
+GeneralResult<Request> convert(const aidl_hal::Request& request);
+
+GeneralResult<std::vector<Operation>> convert(const std::vector<aidl_hal::Operation>& outputShapes);
+GeneralResult<std::vector<SharedMemory>> convert(const std::vector<aidl_hal::Memory>& memories);
+
+GeneralResult<std::vector<uint32_t>> toUnsigned(const std::vector<int32_t>& vec);
+
+} // namespace android::nn
+
+namespace aidl::android::hardware::neuralnetworks::utils {
+
+namespace nn = ::android::nn;
+
+nn::GeneralResult<Memory> unvalidatedConvert(const nn::SharedMemory& memory);
+nn::GeneralResult<OutputShape> unvalidatedConvert(const nn::OutputShape& outputShape);
+nn::GeneralResult<ErrorStatus> unvalidatedConvert(const nn::ErrorStatus& errorStatus);
+
+nn::GeneralResult<Memory> convert(const nn::SharedMemory& memory);
+nn::GeneralResult<ErrorStatus> convert(const nn::ErrorStatus& errorStatus);
+nn::GeneralResult<std::vector<OutputShape>> convert(
+ const std::vector<nn::OutputShape>& outputShapes);
+
+nn::GeneralResult<std::vector<int32_t>> toSigned(const std::vector<uint32_t>& vec);
+
+} // namespace aidl::android::hardware::neuralnetworks::utils
+
+#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_CONVERSIONS_H
diff --git a/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Utils.h b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Utils.h
new file mode 100644
index 0000000..79b511d
--- /dev/null
+++ b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Utils.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_H
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_H
+
+#include "nnapi/hal/aidl/Conversions.h"
+
+#include <android-base/logging.h>
+#include <nnapi/Result.h>
+#include <nnapi/Types.h>
+#include <nnapi/Validation.h>
+
+namespace aidl::android::hardware::neuralnetworks::utils {
+
+constexpr auto kDefaultPriority = Priority::MEDIUM;
+constexpr auto kVersion = nn::Version::ANDROID_S;
+
+template <typename Type>
+nn::Result<void> validate(const Type& halObject) {
+ const auto maybeCanonical = nn::convert(halObject);
+ if (!maybeCanonical.has_value()) {
+ return nn::error() << maybeCanonical.error().message;
+ }
+ return {};
+}
+
+template <typename Type>
+bool valid(const Type& halObject) {
+ const auto result = utils::validate(halObject);
+ if (!result.has_value()) {
+ LOG(ERROR) << result.error();
+ }
+ return result.has_value();
+}
+
+nn::GeneralResult<Memory> clone(const Memory& memory);
+nn::GeneralResult<Request> clone(const Request& request);
+nn::GeneralResult<RequestMemoryPool> clone(const RequestMemoryPool& requestPool);
+nn::GeneralResult<Model> clone(const Model& model);
+
+} // namespace aidl::android::hardware::neuralnetworks::utils
+
+#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_H
diff --git a/neuralnetworks/aidl/utils/src/Assertions.cpp b/neuralnetworks/aidl/utils/src/Assertions.cpp
new file mode 100644
index 0000000..0e88091
--- /dev/null
+++ b/neuralnetworks/aidl/utils/src/Assertions.cpp
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2021 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 <aidl/android/hardware/neuralnetworks/DeviceType.h>
+#include <aidl/android/hardware/neuralnetworks/ErrorStatus.h>
+#include <aidl/android/hardware/neuralnetworks/ExecutionPreference.h>
+#include <aidl/android/hardware/neuralnetworks/FusedActivationFunc.h>
+#include <aidl/android/hardware/neuralnetworks/IDevice.h>
+#include <aidl/android/hardware/neuralnetworks/OperandLifeTime.h>
+#include <aidl/android/hardware/neuralnetworks/OperandType.h>
+#include <aidl/android/hardware/neuralnetworks/OperationType.h>
+#include <aidl/android/hardware/neuralnetworks/Priority.h>
+
+#include <ControlFlow.h>
+#include <nnapi/OperandTypes.h>
+#include <nnapi/OperationTypes.h>
+#include <nnapi/Types.h>
+#include <type_traits>
+
+namespace {
+
+#define COMPARE_ENUMS_TYPES(lhsType, rhsType) \
+ static_assert( \
+ std::is_same_v< \
+ std::underlying_type_t<::aidl::android::hardware::neuralnetworks::lhsType>, \
+ std::underlying_type_t<::android::nn::rhsType>>, \
+ "::aidl::android::hardware::neuralnetworks::" #lhsType \
+ " does not have the same underlying type as ::android::nn::" #rhsType)
+
+COMPARE_ENUMS_TYPES(OperandType, OperandType);
+COMPARE_ENUMS_TYPES(OperationType, OperationType);
+COMPARE_ENUMS_TYPES(Priority, Priority);
+COMPARE_ENUMS_TYPES(OperandLifeTime, Operand::LifeTime);
+COMPARE_ENUMS_TYPES(ErrorStatus, ErrorStatus);
+
+#undef COMPARE_ENUMS_TYPES
+
+#define COMPARE_ENUMS_FULL(lhsSymbol, rhsSymbol, lhsType, rhsType) \
+ static_assert( \
+ static_cast< \
+ std::underlying_type_t<::aidl::android::hardware::neuralnetworks::lhsType>>( \
+ ::aidl::android::hardware::neuralnetworks::lhsType::lhsSymbol) == \
+ static_cast<std::underlying_type_t<::android::nn::rhsType>>( \
+ ::android::nn::rhsType::rhsSymbol), \
+ "::aidl::android::hardware::neuralnetworks::" #lhsType "::" #lhsSymbol \
+ " does not match ::android::nn::" #rhsType "::" #rhsSymbol)
+
+#define COMPARE_ENUMS(symbol) COMPARE_ENUMS_FULL(symbol, symbol, OperandType, OperandType)
+
+COMPARE_ENUMS(FLOAT32);
+COMPARE_ENUMS(INT32);
+COMPARE_ENUMS(UINT32);
+COMPARE_ENUMS(TENSOR_FLOAT32);
+COMPARE_ENUMS(TENSOR_INT32);
+COMPARE_ENUMS(TENSOR_QUANT8_ASYMM);
+COMPARE_ENUMS(BOOL);
+COMPARE_ENUMS(TENSOR_QUANT16_SYMM);
+COMPARE_ENUMS(TENSOR_FLOAT16);
+COMPARE_ENUMS(TENSOR_BOOL8);
+COMPARE_ENUMS(FLOAT16);
+COMPARE_ENUMS(TENSOR_QUANT8_SYMM_PER_CHANNEL);
+COMPARE_ENUMS(TENSOR_QUANT16_ASYMM);
+COMPARE_ENUMS(TENSOR_QUANT8_SYMM);
+COMPARE_ENUMS(TENSOR_QUANT8_ASYMM_SIGNED);
+COMPARE_ENUMS(SUBGRAPH);
+
+#undef COMPARE_ENUMS
+
+#define COMPARE_ENUMS(symbol) COMPARE_ENUMS_FULL(symbol, symbol, OperationType, OperationType)
+
+COMPARE_ENUMS(ADD);
+COMPARE_ENUMS(AVERAGE_POOL_2D);
+COMPARE_ENUMS(CONCATENATION);
+COMPARE_ENUMS(CONV_2D);
+COMPARE_ENUMS(DEPTHWISE_CONV_2D);
+COMPARE_ENUMS(DEPTH_TO_SPACE);
+COMPARE_ENUMS(DEQUANTIZE);
+COMPARE_ENUMS(EMBEDDING_LOOKUP);
+COMPARE_ENUMS(FLOOR);
+COMPARE_ENUMS(FULLY_CONNECTED);
+COMPARE_ENUMS(HASHTABLE_LOOKUP);
+COMPARE_ENUMS(L2_NORMALIZATION);
+COMPARE_ENUMS(L2_POOL_2D);
+COMPARE_ENUMS(LOCAL_RESPONSE_NORMALIZATION);
+COMPARE_ENUMS(LOGISTIC);
+COMPARE_ENUMS(LSH_PROJECTION);
+COMPARE_ENUMS(LSTM);
+COMPARE_ENUMS(MAX_POOL_2D);
+COMPARE_ENUMS(MUL);
+COMPARE_ENUMS(RELU);
+COMPARE_ENUMS(RELU1);
+COMPARE_ENUMS(RELU6);
+COMPARE_ENUMS(RESHAPE);
+COMPARE_ENUMS(RESIZE_BILINEAR);
+COMPARE_ENUMS(RNN);
+COMPARE_ENUMS(SOFTMAX);
+COMPARE_ENUMS(SPACE_TO_DEPTH);
+COMPARE_ENUMS(SVDF);
+COMPARE_ENUMS(TANH);
+COMPARE_ENUMS(BATCH_TO_SPACE_ND);
+COMPARE_ENUMS(DIV);
+COMPARE_ENUMS(MEAN);
+COMPARE_ENUMS(PAD);
+COMPARE_ENUMS(SPACE_TO_BATCH_ND);
+COMPARE_ENUMS(SQUEEZE);
+COMPARE_ENUMS(STRIDED_SLICE);
+COMPARE_ENUMS(SUB);
+COMPARE_ENUMS(TRANSPOSE);
+COMPARE_ENUMS(ABS);
+COMPARE_ENUMS(ARGMAX);
+COMPARE_ENUMS(ARGMIN);
+COMPARE_ENUMS(AXIS_ALIGNED_BBOX_TRANSFORM);
+COMPARE_ENUMS(BIDIRECTIONAL_SEQUENCE_LSTM);
+COMPARE_ENUMS(BIDIRECTIONAL_SEQUENCE_RNN);
+COMPARE_ENUMS(BOX_WITH_NMS_LIMIT);
+COMPARE_ENUMS(CAST);
+COMPARE_ENUMS(CHANNEL_SHUFFLE);
+COMPARE_ENUMS(DETECTION_POSTPROCESSING);
+COMPARE_ENUMS(EQUAL);
+COMPARE_ENUMS(EXP);
+COMPARE_ENUMS(EXPAND_DIMS);
+COMPARE_ENUMS(GATHER);
+COMPARE_ENUMS(GENERATE_PROPOSALS);
+COMPARE_ENUMS(GREATER);
+COMPARE_ENUMS(GREATER_EQUAL);
+COMPARE_ENUMS(GROUPED_CONV_2D);
+COMPARE_ENUMS(HEATMAP_MAX_KEYPOINT);
+COMPARE_ENUMS(INSTANCE_NORMALIZATION);
+COMPARE_ENUMS(LESS);
+COMPARE_ENUMS(LESS_EQUAL);
+COMPARE_ENUMS(LOG);
+COMPARE_ENUMS(LOGICAL_AND);
+COMPARE_ENUMS(LOGICAL_NOT);
+COMPARE_ENUMS(LOGICAL_OR);
+COMPARE_ENUMS(LOG_SOFTMAX);
+COMPARE_ENUMS(MAXIMUM);
+COMPARE_ENUMS(MINIMUM);
+COMPARE_ENUMS(NEG);
+COMPARE_ENUMS(NOT_EQUAL);
+COMPARE_ENUMS(PAD_V2);
+COMPARE_ENUMS(POW);
+COMPARE_ENUMS(PRELU);
+COMPARE_ENUMS(QUANTIZE);
+COMPARE_ENUMS(QUANTIZED_16BIT_LSTM);
+COMPARE_ENUMS(RANDOM_MULTINOMIAL);
+COMPARE_ENUMS(REDUCE_ALL);
+COMPARE_ENUMS(REDUCE_ANY);
+COMPARE_ENUMS(REDUCE_MAX);
+COMPARE_ENUMS(REDUCE_MIN);
+COMPARE_ENUMS(REDUCE_PROD);
+COMPARE_ENUMS(REDUCE_SUM);
+COMPARE_ENUMS(ROI_ALIGN);
+COMPARE_ENUMS(ROI_POOLING);
+COMPARE_ENUMS(RSQRT);
+COMPARE_ENUMS(SELECT);
+COMPARE_ENUMS(SIN);
+COMPARE_ENUMS(SLICE);
+COMPARE_ENUMS(SPLIT);
+COMPARE_ENUMS(SQRT);
+COMPARE_ENUMS(TILE);
+COMPARE_ENUMS(TOPK_V2);
+COMPARE_ENUMS(TRANSPOSE_CONV_2D);
+COMPARE_ENUMS(UNIDIRECTIONAL_SEQUENCE_LSTM);
+COMPARE_ENUMS(UNIDIRECTIONAL_SEQUENCE_RNN);
+COMPARE_ENUMS(RESIZE_NEAREST_NEIGHBOR);
+COMPARE_ENUMS(QUANTIZED_LSTM);
+COMPARE_ENUMS(IF);
+COMPARE_ENUMS(WHILE);
+COMPARE_ENUMS(ELU);
+COMPARE_ENUMS(HARD_SWISH);
+COMPARE_ENUMS(FILL);
+COMPARE_ENUMS(RANK);
+
+#undef COMPARE_ENUMS
+
+#define COMPARE_ENUMS(symbol) COMPARE_ENUMS_FULL(symbol, symbol, Priority, Priority)
+
+COMPARE_ENUMS(LOW);
+COMPARE_ENUMS(MEDIUM);
+COMPARE_ENUMS(HIGH);
+
+#undef COMPARE_ENUMS
+
+#define COMPARE_ENUMS(lhsSymbol, rhsSymbol) \
+ COMPARE_ENUMS_FULL(lhsSymbol, rhsSymbol, OperandLifeTime, Operand::LifeTime)
+
+COMPARE_ENUMS(TEMPORARY_VARIABLE, TEMPORARY_VARIABLE);
+COMPARE_ENUMS(SUBGRAPH_INPUT, SUBGRAPH_INPUT);
+COMPARE_ENUMS(SUBGRAPH_OUTPUT, SUBGRAPH_OUTPUT);
+COMPARE_ENUMS(CONSTANT_COPY, CONSTANT_COPY);
+COMPARE_ENUMS(CONSTANT_POOL, CONSTANT_REFERENCE);
+COMPARE_ENUMS(NO_VALUE, NO_VALUE);
+COMPARE_ENUMS(SUBGRAPH, SUBGRAPH);
+
+#undef COMPARE_ENUMS
+
+#define COMPARE_ENUMS(symbol) COMPARE_ENUMS_FULL(symbol, symbol, ErrorStatus, ErrorStatus)
+
+COMPARE_ENUMS(NONE);
+COMPARE_ENUMS(DEVICE_UNAVAILABLE);
+COMPARE_ENUMS(GENERAL_FAILURE);
+COMPARE_ENUMS(OUTPUT_INSUFFICIENT_SIZE);
+COMPARE_ENUMS(INVALID_ARGUMENT);
+COMPARE_ENUMS(MISSED_DEADLINE_TRANSIENT);
+COMPARE_ENUMS(MISSED_DEADLINE_PERSISTENT);
+COMPARE_ENUMS(RESOURCE_EXHAUSTED_TRANSIENT);
+COMPARE_ENUMS(RESOURCE_EXHAUSTED_PERSISTENT);
+
+#undef COMPARE_ENUMS
+
+#define COMPARE_ENUMS(symbol) \
+ COMPARE_ENUMS_FULL(symbol, symbol, ExecutionPreference, ExecutionPreference)
+
+COMPARE_ENUMS(LOW_POWER);
+COMPARE_ENUMS(FAST_SINGLE_ANSWER);
+COMPARE_ENUMS(SUSTAINED_SPEED);
+
+#undef COMPARE_ENUMS
+
+#define COMPARE_ENUMS(symbol) COMPARE_ENUMS_FULL(symbol, symbol, DeviceType, DeviceType)
+
+COMPARE_ENUMS(OTHER);
+COMPARE_ENUMS(CPU);
+COMPARE_ENUMS(GPU);
+COMPARE_ENUMS(ACCELERATOR);
+
+#undef COMPARE_ENUMS
+
+#define COMPARE_ENUMS(symbol) \
+ COMPARE_ENUMS_FULL(symbol, symbol, FusedActivationFunc, FusedActivationFunc)
+
+COMPARE_ENUMS(NONE);
+COMPARE_ENUMS(RELU);
+COMPARE_ENUMS(RELU1);
+COMPARE_ENUMS(RELU6);
+
+#undef COMPARE_ENUMS
+
+#undef COMPARE_ENUMS_FULL
+
+#define COMPARE_CONSTANTS(halSymbol, canonicalSymbol) \
+ static_assert(::aidl::android::hardware::neuralnetworks::halSymbol == \
+ ::android::nn::canonicalSymbol);
+
+COMPARE_CONSTANTS(IDevice::BYTE_SIZE_OF_CACHE_TOKEN, kByteSizeOfCacheToken);
+COMPARE_CONSTANTS(IDevice::MAX_NUMBER_OF_CACHE_FILES, kMaxNumberOfCacheFiles);
+COMPARE_CONSTANTS(IDevice::EXTENSION_TYPE_HIGH_BITS_PREFIX, kExtensionPrefixBits - 1);
+COMPARE_CONSTANTS(IDevice::EXTENSION_TYPE_LOW_BITS_TYPE, kExtensionTypeBits);
+COMPARE_CONSTANTS(IPreparedModel::DEFAULT_LOOP_TIMEOUT_DURATION_NS,
+ operation_while::kTimeoutNsDefault);
+COMPARE_CONSTANTS(IPreparedModel::MAXIMUM_LOOP_TIMEOUT_DURATION_NS,
+ operation_while::kTimeoutNsMaximum);
+
+#undef COMPARE_CONSTANTS
+
+} // anonymous namespace
diff --git a/neuralnetworks/aidl/utils/src/Conversions.cpp b/neuralnetworks/aidl/utils/src/Conversions.cpp
new file mode 100644
index 0000000..db3504b
--- /dev/null
+++ b/neuralnetworks/aidl/utils/src/Conversions.cpp
@@ -0,0 +1,731 @@
+/*
+ * Copyright (C) 2021 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 "Conversions.h"
+
+#include <aidl/android/hardware/common/NativeHandle.h>
+#include <android-base/logging.h>
+#include <android/hardware_buffer.h>
+#include <cutils/native_handle.h>
+#include <nnapi/OperandTypes.h>
+#include <nnapi/OperationTypes.h>
+#include <nnapi/Result.h>
+#include <nnapi/SharedMemory.h>
+#include <nnapi/TypeUtils.h>
+#include <nnapi/Types.h>
+#include <nnapi/Validation.h>
+#include <nnapi/hal/CommonUtils.h>
+#include <nnapi/hal/HandleError.h>
+#include <vndk/hardware_buffer.h>
+
+#include <algorithm>
+#include <chrono>
+#include <functional>
+#include <iterator>
+#include <limits>
+#include <type_traits>
+#include <utility>
+
+#define VERIFY_NON_NEGATIVE(value) \
+ while (UNLIKELY(value < 0)) return NN_ERROR()
+
+namespace {
+
+template <typename Type>
+constexpr std::underlying_type_t<Type> underlyingType(Type value) {
+ return static_cast<std::underlying_type_t<Type>>(value);
+}
+
+constexpr auto kVersion = android::nn::Version::ANDROID_S;
+
+} // namespace
+
+namespace android::nn {
+namespace {
+
+using ::aidl::android::hardware::common::NativeHandle;
+
+constexpr auto validOperandType(nn::OperandType operandType) {
+ switch (operandType) {
+ case nn::OperandType::FLOAT32:
+ case nn::OperandType::INT32:
+ case nn::OperandType::UINT32:
+ case nn::OperandType::TENSOR_FLOAT32:
+ case nn::OperandType::TENSOR_INT32:
+ case nn::OperandType::TENSOR_QUANT8_ASYMM:
+ case nn::OperandType::BOOL:
+ case nn::OperandType::TENSOR_QUANT16_SYMM:
+ case nn::OperandType::TENSOR_FLOAT16:
+ case nn::OperandType::TENSOR_BOOL8:
+ case nn::OperandType::FLOAT16:
+ case nn::OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL:
+ case nn::OperandType::TENSOR_QUANT16_ASYMM:
+ case nn::OperandType::TENSOR_QUANT8_SYMM:
+ case nn::OperandType::TENSOR_QUANT8_ASYMM_SIGNED:
+ case nn::OperandType::SUBGRAPH:
+ return true;
+ case nn::OperandType::OEM:
+ case nn::OperandType::TENSOR_OEM_BYTE:
+ return false;
+ }
+ return nn::isExtension(operandType);
+}
+
+template <typename Input>
+using UnvalidatedConvertOutput =
+ std::decay_t<decltype(unvalidatedConvert(std::declval<Input>()).value())>;
+
+template <typename Type>
+GeneralResult<std::vector<UnvalidatedConvertOutput<Type>>> unvalidatedConvertVec(
+ const std::vector<Type>& arguments) {
+ std::vector<UnvalidatedConvertOutput<Type>> canonical;
+ canonical.reserve(arguments.size());
+ for (const auto& argument : arguments) {
+ canonical.push_back(NN_TRY(nn::unvalidatedConvert(argument)));
+ }
+ return canonical;
+}
+
+template <typename Type>
+GeneralResult<std::vector<UnvalidatedConvertOutput<Type>>> unvalidatedConvert(
+ const std::vector<Type>& arguments) {
+ return unvalidatedConvertVec(arguments);
+}
+
+template <typename Type>
+GeneralResult<UnvalidatedConvertOutput<Type>> validatedConvert(const Type& halObject) {
+ auto canonical = NN_TRY(nn::unvalidatedConvert(halObject));
+ const auto maybeVersion = validate(canonical);
+ if (!maybeVersion.has_value()) {
+ return error() << maybeVersion.error();
+ }
+ const auto version = maybeVersion.value();
+ if (version > kVersion) {
+ return NN_ERROR() << "Insufficient version: " << version << " vs required " << kVersion;
+ }
+ return canonical;
+}
+
+template <typename Type>
+GeneralResult<std::vector<UnvalidatedConvertOutput<Type>>> validatedConvert(
+ const std::vector<Type>& arguments) {
+ std::vector<UnvalidatedConvertOutput<Type>> canonical;
+ canonical.reserve(arguments.size());
+ for (const auto& argument : arguments) {
+ canonical.push_back(NN_TRY(validatedConvert(argument)));
+ }
+ return canonical;
+}
+
+GeneralResult<Handle> unvalidatedConvertHelper(const NativeHandle& aidlNativeHandle) {
+ std::vector<base::unique_fd> fds;
+ fds.reserve(aidlNativeHandle.fds.size());
+ for (const auto& fd : aidlNativeHandle.fds) {
+ const int dupFd = dup(fd.get());
+ if (dupFd == -1) {
+ // TODO(b/120417090): is ANEURALNETWORKS_UNEXPECTED_NULL the correct error to return
+ // here?
+ return NN_ERROR() << "Failed to dup the fd";
+ }
+ fds.emplace_back(dupFd);
+ }
+
+ return Handle{.fds = std::move(fds), .ints = aidlNativeHandle.ints};
+}
+
+struct NativeHandleDeleter {
+ void operator()(native_handle_t* handle) const {
+ if (handle) {
+ native_handle_close(handle);
+ native_handle_delete(handle);
+ }
+ }
+};
+
+using UniqueNativeHandle = std::unique_ptr<native_handle_t, NativeHandleDeleter>;
+
+static nn::GeneralResult<UniqueNativeHandle> nativeHandleFromAidlHandle(
+ const NativeHandle& handle) {
+ std::vector<base::unique_fd> fds;
+ fds.reserve(handle.fds.size());
+ for (const auto& fd : handle.fds) {
+ const int dupFd = dup(fd.get());
+ if (dupFd == -1) {
+ return NN_ERROR() << "Failed to dup the fd";
+ }
+ fds.emplace_back(dupFd);
+ }
+
+ constexpr size_t kIntMax = std::numeric_limits<int>::max();
+ CHECK_LE(handle.fds.size(), kIntMax);
+ CHECK_LE(handle.ints.size(), kIntMax);
+ native_handle_t* nativeHandle = native_handle_create(static_cast<int>(handle.fds.size()),
+ static_cast<int>(handle.ints.size()));
+ if (nativeHandle == nullptr) {
+ return NN_ERROR() << "Failed to create native_handle";
+ }
+ for (size_t i = 0; i < fds.size(); ++i) {
+ nativeHandle->data[i] = fds[i].release();
+ }
+ std::copy(handle.ints.begin(), handle.ints.end(), &nativeHandle->data[nativeHandle->numFds]);
+
+ return UniqueNativeHandle(nativeHandle);
+}
+
+} // anonymous namespace
+
+GeneralResult<OperandType> unvalidatedConvert(const aidl_hal::OperandType& operandType) {
+ VERIFY_NON_NEGATIVE(underlyingType(operandType)) << "Negative operand types are not allowed.";
+ return static_cast<OperandType>(operandType);
+}
+
+GeneralResult<OperationType> unvalidatedConvert(const aidl_hal::OperationType& operationType) {
+ VERIFY_NON_NEGATIVE(underlyingType(operationType))
+ << "Negative operation types are not allowed.";
+ return static_cast<OperationType>(operationType);
+}
+
+GeneralResult<DeviceType> unvalidatedConvert(const aidl_hal::DeviceType& deviceType) {
+ return static_cast<DeviceType>(deviceType);
+}
+
+GeneralResult<Priority> unvalidatedConvert(const aidl_hal::Priority& priority) {
+ return static_cast<Priority>(priority);
+}
+
+GeneralResult<Capabilities> unvalidatedConvert(const aidl_hal::Capabilities& capabilities) {
+ const bool validOperandTypes = std::all_of(
+ capabilities.operandPerformance.begin(), capabilities.operandPerformance.end(),
+ [](const aidl_hal::OperandPerformance& operandPerformance) {
+ const auto maybeType = unvalidatedConvert(operandPerformance.type);
+ return !maybeType.has_value() ? false : validOperandType(maybeType.value());
+ });
+ if (!validOperandTypes) {
+ return NN_ERROR() << "Invalid OperandType when unvalidatedConverting OperandPerformance in "
+ "Capabilities";
+ }
+
+ auto operandPerformance = NN_TRY(unvalidatedConvert(capabilities.operandPerformance));
+ auto table = NN_TRY(hal::utils::makeGeneralFailure(
+ Capabilities::OperandPerformanceTable::create(std::move(operandPerformance)),
+ nn::ErrorStatus::GENERAL_FAILURE));
+
+ return Capabilities{
+ .relaxedFloat32toFloat16PerformanceScalar = NN_TRY(
+ unvalidatedConvert(capabilities.relaxedFloat32toFloat16PerformanceScalar)),
+ .relaxedFloat32toFloat16PerformanceTensor = NN_TRY(
+ unvalidatedConvert(capabilities.relaxedFloat32toFloat16PerformanceTensor)),
+ .operandPerformance = std::move(table),
+ .ifPerformance = NN_TRY(unvalidatedConvert(capabilities.ifPerformance)),
+ .whilePerformance = NN_TRY(unvalidatedConvert(capabilities.whilePerformance)),
+ };
+}
+
+GeneralResult<Capabilities::OperandPerformance> unvalidatedConvert(
+ const aidl_hal::OperandPerformance& operandPerformance) {
+ return Capabilities::OperandPerformance{
+ .type = NN_TRY(unvalidatedConvert(operandPerformance.type)),
+ .info = NN_TRY(unvalidatedConvert(operandPerformance.info)),
+ };
+}
+
+GeneralResult<Capabilities::PerformanceInfo> unvalidatedConvert(
+ const aidl_hal::PerformanceInfo& performanceInfo) {
+ return Capabilities::PerformanceInfo{
+ .execTime = performanceInfo.execTime,
+ .powerUsage = performanceInfo.powerUsage,
+ };
+}
+
+GeneralResult<DataLocation> unvalidatedConvert(const aidl_hal::DataLocation& location) {
+ VERIFY_NON_NEGATIVE(location.poolIndex) << "DataLocation: pool index must not be negative";
+ VERIFY_NON_NEGATIVE(location.offset) << "DataLocation: offset must not be negative";
+ VERIFY_NON_NEGATIVE(location.length) << "DataLocation: length must not be negative";
+ if (location.offset > std::numeric_limits<uint32_t>::max()) {
+ return NN_ERROR() << "DataLocation: offset must be <= std::numeric_limits<uint32_t>::max()";
+ }
+ if (location.length > std::numeric_limits<uint32_t>::max()) {
+ return NN_ERROR() << "DataLocation: length must be <= std::numeric_limits<uint32_t>::max()";
+ }
+ return DataLocation{
+ .poolIndex = static_cast<uint32_t>(location.poolIndex),
+ .offset = static_cast<uint32_t>(location.offset),
+ .length = static_cast<uint32_t>(location.length),
+ };
+}
+
+GeneralResult<Operation> unvalidatedConvert(const aidl_hal::Operation& operation) {
+ return Operation{
+ .type = NN_TRY(unvalidatedConvert(operation.type)),
+ .inputs = NN_TRY(toUnsigned(operation.inputs)),
+ .outputs = NN_TRY(toUnsigned(operation.outputs)),
+ };
+}
+
+GeneralResult<Operand::LifeTime> unvalidatedConvert(
+ const aidl_hal::OperandLifeTime& operandLifeTime) {
+ return static_cast<Operand::LifeTime>(operandLifeTime);
+}
+
+GeneralResult<Operand> unvalidatedConvert(const aidl_hal::Operand& operand) {
+ return Operand{
+ .type = NN_TRY(unvalidatedConvert(operand.type)),
+ .dimensions = NN_TRY(toUnsigned(operand.dimensions)),
+ .scale = operand.scale,
+ .zeroPoint = operand.zeroPoint,
+ .lifetime = NN_TRY(unvalidatedConvert(operand.lifetime)),
+ .location = NN_TRY(unvalidatedConvert(operand.location)),
+ .extraParams = NN_TRY(unvalidatedConvert(operand.extraParams)),
+ };
+}
+
+GeneralResult<Operand::ExtraParams> unvalidatedConvert(
+ const std::optional<aidl_hal::OperandExtraParams>& optionalExtraParams) {
+ if (!optionalExtraParams.has_value()) {
+ return Operand::NoParams{};
+ }
+ const auto& extraParams = optionalExtraParams.value();
+ using Tag = aidl_hal::OperandExtraParams::Tag;
+ switch (extraParams.getTag()) {
+ case Tag::channelQuant:
+ return unvalidatedConvert(extraParams.get<Tag::channelQuant>());
+ case Tag::extension:
+ return extraParams.get<Tag::extension>();
+ }
+ return NN_ERROR() << "Unrecognized Operand::ExtraParams tag: "
+ << underlyingType(extraParams.getTag());
+}
+
+GeneralResult<Operand::SymmPerChannelQuantParams> unvalidatedConvert(
+ const aidl_hal::SymmPerChannelQuantParams& symmPerChannelQuantParams) {
+ VERIFY_NON_NEGATIVE(symmPerChannelQuantParams.channelDim)
+ << "Per-channel quantization channel dimension must not be negative.";
+ return Operand::SymmPerChannelQuantParams{
+ .scales = symmPerChannelQuantParams.scales,
+ .channelDim = static_cast<uint32_t>(symmPerChannelQuantParams.channelDim),
+ };
+}
+
+GeneralResult<Model> unvalidatedConvert(const aidl_hal::Model& model) {
+ return Model{
+ .main = NN_TRY(unvalidatedConvert(model.main)),
+ .referenced = NN_TRY(unvalidatedConvert(model.referenced)),
+ .operandValues = NN_TRY(unvalidatedConvert(model.operandValues)),
+ .pools = NN_TRY(unvalidatedConvert(model.pools)),
+ .relaxComputationFloat32toFloat16 = model.relaxComputationFloat32toFloat16,
+ .extensionNameToPrefix = NN_TRY(unvalidatedConvert(model.extensionNameToPrefix)),
+ };
+}
+
+GeneralResult<Model::Subgraph> unvalidatedConvert(const aidl_hal::Subgraph& subgraph) {
+ return Model::Subgraph{
+ .operands = NN_TRY(unvalidatedConvert(subgraph.operands)),
+ .operations = NN_TRY(unvalidatedConvert(subgraph.operations)),
+ .inputIndexes = NN_TRY(toUnsigned(subgraph.inputIndexes)),
+ .outputIndexes = NN_TRY(toUnsigned(subgraph.outputIndexes)),
+ };
+}
+
+GeneralResult<Model::ExtensionNameAndPrefix> unvalidatedConvert(
+ const aidl_hal::ExtensionNameAndPrefix& extensionNameAndPrefix) {
+ return Model::ExtensionNameAndPrefix{
+ .name = extensionNameAndPrefix.name,
+ .prefix = extensionNameAndPrefix.prefix,
+ };
+}
+
+GeneralResult<Extension> unvalidatedConvert(const aidl_hal::Extension& extension) {
+ return Extension{
+ .name = extension.name,
+ .operandTypes = NN_TRY(unvalidatedConvert(extension.operandTypes)),
+ };
+}
+
+GeneralResult<Extension::OperandTypeInformation> unvalidatedConvert(
+ const aidl_hal::ExtensionOperandTypeInformation& operandTypeInformation) {
+ VERIFY_NON_NEGATIVE(operandTypeInformation.byteSize)
+ << "Extension operand type byte size must not be negative";
+ return Extension::OperandTypeInformation{
+ .type = operandTypeInformation.type,
+ .isTensor = operandTypeInformation.isTensor,
+ .byteSize = static_cast<uint32_t>(operandTypeInformation.byteSize),
+ };
+}
+
+GeneralResult<OutputShape> unvalidatedConvert(const aidl_hal::OutputShape& outputShape) {
+ return OutputShape{
+ .dimensions = NN_TRY(toUnsigned(outputShape.dimensions)),
+ .isSufficient = outputShape.isSufficient,
+ };
+}
+
+GeneralResult<MeasureTiming> unvalidatedConvert(bool measureTiming) {
+ return measureTiming ? MeasureTiming::YES : MeasureTiming::NO;
+}
+
+static uint32_t roundUpToMultiple(uint32_t value, uint32_t multiple) {
+ return (value + multiple - 1) / multiple * multiple;
+}
+
+GeneralResult<SharedMemory> unvalidatedConvert(const aidl_hal::Memory& memory) {
+ VERIFY_NON_NEGATIVE(memory.size) << "Memory size must not be negative";
+ if (memory.size > std::numeric_limits<uint32_t>::max()) {
+ return NN_ERROR() << "Memory: size must be <= std::numeric_limits<size_t>::max()";
+ }
+
+ if (memory.name != "hardware_buffer_blob") {
+ return std::make_shared<const Memory>(Memory{
+ .handle = NN_TRY(unvalidatedConvertHelper(memory.handle)),
+ .size = static_cast<uint32_t>(memory.size),
+ .name = memory.name,
+ });
+ }
+
+ const auto size = static_cast<uint32_t>(memory.size);
+ const auto format = AHARDWAREBUFFER_FORMAT_BLOB;
+ const auto usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN;
+ const uint32_t width = size;
+ const uint32_t height = 1; // height is always 1 for BLOB mode AHardwareBuffer.
+ const uint32_t layers = 1; // layers is always 1 for BLOB mode AHardwareBuffer.
+
+ const UniqueNativeHandle handle = NN_TRY(nativeHandleFromAidlHandle(memory.handle));
+ const native_handle_t* nativeHandle = handle.get();
+
+ // AHardwareBuffer_createFromHandle() might fail because an allocator
+ // expects a specific stride value. In that case, we try to guess it by
+ // aligning the width to small powers of 2.
+ // TODO(b/174120849): Avoid stride assumptions.
+ AHardwareBuffer* hardwareBuffer = nullptr;
+ status_t status = UNKNOWN_ERROR;
+ for (uint32_t alignment : {1, 4, 32, 64, 128, 2, 8, 16}) {
+ const uint32_t stride = roundUpToMultiple(width, alignment);
+ AHardwareBuffer_Desc desc{
+ .width = width,
+ .height = height,
+ .layers = layers,
+ .format = format,
+ .usage = usage,
+ .stride = stride,
+ };
+ status = AHardwareBuffer_createFromHandle(&desc, nativeHandle,
+ AHARDWAREBUFFER_CREATE_FROM_HANDLE_METHOD_CLONE,
+ &hardwareBuffer);
+ if (status == NO_ERROR) {
+ break;
+ }
+ }
+ if (status != NO_ERROR) {
+ return NN_ERROR(ErrorStatus::GENERAL_FAILURE)
+ << "Can't create AHardwareBuffer from handle. Error: " << status;
+ }
+
+ return std::make_shared<const Memory>(Memory{
+ .handle = HardwareBufferHandle(hardwareBuffer, /*takeOwnership=*/true),
+ .size = static_cast<uint32_t>(memory.size),
+ .name = memory.name,
+ });
+}
+
+GeneralResult<Model::OperandValues> unvalidatedConvert(const std::vector<uint8_t>& operandValues) {
+ return Model::OperandValues(operandValues.data(), operandValues.size());
+}
+
+GeneralResult<BufferDesc> unvalidatedConvert(const aidl_hal::BufferDesc& bufferDesc) {
+ return BufferDesc{.dimensions = NN_TRY(toUnsigned(bufferDesc.dimensions))};
+}
+
+GeneralResult<BufferRole> unvalidatedConvert(const aidl_hal::BufferRole& bufferRole) {
+ VERIFY_NON_NEGATIVE(bufferRole.modelIndex) << "BufferRole: modelIndex must not be negative";
+ VERIFY_NON_NEGATIVE(bufferRole.ioIndex) << "BufferRole: ioIndex must not be negative";
+ return BufferRole{
+ .modelIndex = static_cast<uint32_t>(bufferRole.modelIndex),
+ .ioIndex = static_cast<uint32_t>(bufferRole.ioIndex),
+ .frequency = bufferRole.frequency,
+ };
+}
+
+GeneralResult<Request> unvalidatedConvert(const aidl_hal::Request& request) {
+ return Request{
+ .inputs = NN_TRY(unvalidatedConvert(request.inputs)),
+ .outputs = NN_TRY(unvalidatedConvert(request.outputs)),
+ .pools = NN_TRY(unvalidatedConvert(request.pools)),
+ };
+}
+
+GeneralResult<Request::Argument> unvalidatedConvert(const aidl_hal::RequestArgument& argument) {
+ const auto lifetime = argument.hasNoValue ? Request::Argument::LifeTime::NO_VALUE
+ : Request::Argument::LifeTime::POOL;
+ return Request::Argument{
+ .lifetime = lifetime,
+ .location = NN_TRY(unvalidatedConvert(argument.location)),
+ .dimensions = NN_TRY(toUnsigned(argument.dimensions)),
+ };
+}
+
+GeneralResult<Request::MemoryPool> unvalidatedConvert(
+ const aidl_hal::RequestMemoryPool& memoryPool) {
+ using Tag = aidl_hal::RequestMemoryPool::Tag;
+ switch (memoryPool.getTag()) {
+ case Tag::pool:
+ return unvalidatedConvert(memoryPool.get<Tag::pool>());
+ case Tag::token: {
+ const auto token = memoryPool.get<Tag::token>();
+ VERIFY_NON_NEGATIVE(token) << "Memory pool token must not be negative";
+ return static_cast<Request::MemoryDomainToken>(token);
+ }
+ }
+ return NN_ERROR() << "Invalid Request::MemoryPool tag " << underlyingType(memoryPool.getTag());
+}
+
+GeneralResult<ErrorStatus> unvalidatedConvert(const aidl_hal::ErrorStatus& status) {
+ switch (status) {
+ case aidl_hal::ErrorStatus::NONE:
+ case aidl_hal::ErrorStatus::DEVICE_UNAVAILABLE:
+ case aidl_hal::ErrorStatus::GENERAL_FAILURE:
+ case aidl_hal::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE:
+ case aidl_hal::ErrorStatus::INVALID_ARGUMENT:
+ case aidl_hal::ErrorStatus::MISSED_DEADLINE_TRANSIENT:
+ case aidl_hal::ErrorStatus::MISSED_DEADLINE_PERSISTENT:
+ case aidl_hal::ErrorStatus::RESOURCE_EXHAUSTED_TRANSIENT:
+ case aidl_hal::ErrorStatus::RESOURCE_EXHAUSTED_PERSISTENT:
+ return static_cast<ErrorStatus>(status);
+ }
+ return NN_ERROR() << "Invalid ErrorStatus " << underlyingType(status);
+}
+
+GeneralResult<ExecutionPreference> unvalidatedConvert(
+ const aidl_hal::ExecutionPreference& executionPreference) {
+ return static_cast<ExecutionPreference>(executionPreference);
+}
+
+GeneralResult<SharedHandle> unvalidatedConvert(const NativeHandle& aidlNativeHandle) {
+ return std::make_shared<const Handle>(NN_TRY(unvalidatedConvertHelper(aidlNativeHandle)));
+}
+
+GeneralResult<ExecutionPreference> convert(
+ const aidl_hal::ExecutionPreference& executionPreference) {
+ return validatedConvert(executionPreference);
+}
+
+GeneralResult<SharedMemory> convert(const aidl_hal::Memory& operand) {
+ return validatedConvert(operand);
+}
+
+GeneralResult<Model> convert(const aidl_hal::Model& model) {
+ return validatedConvert(model);
+}
+
+GeneralResult<Operand> convert(const aidl_hal::Operand& operand) {
+ return unvalidatedConvert(operand);
+}
+
+GeneralResult<OperandType> convert(const aidl_hal::OperandType& operandType) {
+ return unvalidatedConvert(operandType);
+}
+
+GeneralResult<Priority> convert(const aidl_hal::Priority& priority) {
+ return validatedConvert(priority);
+}
+
+GeneralResult<Request::MemoryPool> convert(const aidl_hal::RequestMemoryPool& memoryPool) {
+ return unvalidatedConvert(memoryPool);
+}
+
+GeneralResult<Request> convert(const aidl_hal::Request& request) {
+ return validatedConvert(request);
+}
+
+GeneralResult<std::vector<Operation>> convert(const std::vector<aidl_hal::Operation>& operations) {
+ return unvalidatedConvert(operations);
+}
+
+GeneralResult<std::vector<SharedMemory>> convert(const std::vector<aidl_hal::Memory>& memories) {
+ return validatedConvert(memories);
+}
+
+GeneralResult<std::vector<uint32_t>> toUnsigned(const std::vector<int32_t>& vec) {
+ if (!std::all_of(vec.begin(), vec.end(), [](int32_t v) { return v >= 0; })) {
+ return NN_ERROR() << "Negative value passed to conversion from signed to unsigned";
+ }
+ return std::vector<uint32_t>(vec.begin(), vec.end());
+}
+
+} // namespace android::nn
+
+namespace aidl::android::hardware::neuralnetworks::utils {
+namespace {
+
+template <typename Input>
+using UnvalidatedConvertOutput =
+ std::decay_t<decltype(unvalidatedConvert(std::declval<Input>()).value())>;
+
+template <typename Type>
+nn::GeneralResult<std::vector<UnvalidatedConvertOutput<Type>>> unvalidatedConvertVec(
+ const std::vector<Type>& arguments) {
+ std::vector<UnvalidatedConvertOutput<Type>> halObject(arguments.size());
+ for (size_t i = 0; i < arguments.size(); ++i) {
+ halObject[i] = NN_TRY(unvalidatedConvert(arguments[i]));
+ }
+ return halObject;
+}
+
+template <typename Type>
+nn::GeneralResult<UnvalidatedConvertOutput<Type>> validatedConvert(const Type& canonical) {
+ const auto maybeVersion = nn::validate(canonical);
+ if (!maybeVersion.has_value()) {
+ return nn::error() << maybeVersion.error();
+ }
+ const auto version = maybeVersion.value();
+ if (version > kVersion) {
+ return NN_ERROR() << "Insufficient version: " << version << " vs required " << kVersion;
+ }
+ return utils::unvalidatedConvert(canonical);
+}
+
+template <typename Type>
+nn::GeneralResult<std::vector<UnvalidatedConvertOutput<Type>>> validatedConvert(
+ const std::vector<Type>& arguments) {
+ std::vector<UnvalidatedConvertOutput<Type>> halObject(arguments.size());
+ for (size_t i = 0; i < arguments.size(); ++i) {
+ halObject[i] = NN_TRY(validatedConvert(arguments[i]));
+ }
+ return halObject;
+}
+
+nn::GeneralResult<common::NativeHandle> unvalidatedConvert(const nn::Handle& handle) {
+ common::NativeHandle aidlNativeHandle;
+ aidlNativeHandle.fds.reserve(handle.fds.size());
+ for (const auto& fd : handle.fds) {
+ const int dupFd = dup(fd.get());
+ if (dupFd == -1) {
+ // TODO(b/120417090): is ANEURALNETWORKS_UNEXPECTED_NULL the correct error to return
+ // here?
+ return NN_ERROR() << "Failed to dup the fd";
+ }
+ aidlNativeHandle.fds.emplace_back(dupFd);
+ }
+ aidlNativeHandle.ints = handle.ints;
+ return aidlNativeHandle;
+}
+
+static nn::GeneralResult<common::NativeHandle> aidlHandleFromNativeHandle(
+ const native_handle_t& handle) {
+ common::NativeHandle aidlNativeHandle;
+
+ aidlNativeHandle.fds.reserve(handle.numFds);
+ for (int i = 0; i < handle.numFds; ++i) {
+ const int dupFd = dup(handle.data[i]);
+ if (dupFd == -1) {
+ return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) << "Failed to dup the fd";
+ }
+ aidlNativeHandle.fds.emplace_back(dupFd);
+ }
+
+ aidlNativeHandle.ints = std::vector<int>(&handle.data[handle.numFds],
+ &handle.data[handle.numFds + handle.numInts]);
+
+ return aidlNativeHandle;
+}
+
+} // namespace
+
+nn::GeneralResult<common::NativeHandle> unvalidatedConvert(const nn::SharedHandle& sharedHandle) {
+ CHECK(sharedHandle != nullptr);
+ return unvalidatedConvert(*sharedHandle);
+}
+
+nn::GeneralResult<Memory> unvalidatedConvert(const nn::SharedMemory& memory) {
+ CHECK(memory != nullptr);
+ if (memory->size > std::numeric_limits<int64_t>::max()) {
+ return NN_ERROR() << "Memory size doesn't fit into int64_t.";
+ }
+ if (const auto* handle = std::get_if<nn::Handle>(&memory->handle)) {
+ return Memory{
+ .handle = NN_TRY(unvalidatedConvert(*handle)),
+ .size = static_cast<int64_t>(memory->size),
+ .name = memory->name,
+ };
+ }
+
+ const auto* ahwb = std::get<nn::HardwareBufferHandle>(memory->handle).get();
+ AHardwareBuffer_Desc bufferDesc;
+ AHardwareBuffer_describe(ahwb, &bufferDesc);
+
+ if (bufferDesc.format == AHARDWAREBUFFER_FORMAT_BLOB) {
+ CHECK_EQ(memory->size, bufferDesc.width);
+ CHECK_EQ(memory->name, "hardware_buffer_blob");
+ } else {
+ CHECK_EQ(memory->size, 0u);
+ CHECK_EQ(memory->name, "hardware_buffer");
+ }
+
+ const native_handle_t* nativeHandle = AHardwareBuffer_getNativeHandle(ahwb);
+ if (nativeHandle == nullptr) {
+ return NN_ERROR() << "unvalidatedConvert failed because AHardwareBuffer_getNativeHandle "
+ "returned nullptr";
+ }
+
+ return Memory{
+ .handle = NN_TRY(aidlHandleFromNativeHandle(*nativeHandle)),
+ .size = static_cast<int64_t>(memory->size),
+ .name = memory->name,
+ };
+}
+
+nn::GeneralResult<ErrorStatus> unvalidatedConvert(const nn::ErrorStatus& errorStatus) {
+ switch (errorStatus) {
+ case nn::ErrorStatus::NONE:
+ case nn::ErrorStatus::DEVICE_UNAVAILABLE:
+ case nn::ErrorStatus::GENERAL_FAILURE:
+ case nn::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE:
+ case nn::ErrorStatus::INVALID_ARGUMENT:
+ case nn::ErrorStatus::MISSED_DEADLINE_TRANSIENT:
+ case nn::ErrorStatus::MISSED_DEADLINE_PERSISTENT:
+ case nn::ErrorStatus::RESOURCE_EXHAUSTED_TRANSIENT:
+ case nn::ErrorStatus::RESOURCE_EXHAUSTED_PERSISTENT:
+ return static_cast<ErrorStatus>(errorStatus);
+ default:
+ return ErrorStatus::GENERAL_FAILURE;
+ }
+}
+
+nn::GeneralResult<OutputShape> unvalidatedConvert(const nn::OutputShape& outputShape) {
+ return OutputShape{.dimensions = NN_TRY(toSigned(outputShape.dimensions)),
+ .isSufficient = outputShape.isSufficient};
+}
+
+nn::GeneralResult<Memory> convert(const nn::SharedMemory& memory) {
+ return validatedConvert(memory);
+}
+
+nn::GeneralResult<ErrorStatus> convert(const nn::ErrorStatus& errorStatus) {
+ return validatedConvert(errorStatus);
+}
+
+nn::GeneralResult<std::vector<OutputShape>> convert(
+ const std::vector<nn::OutputShape>& outputShapes) {
+ return validatedConvert(outputShapes);
+}
+
+nn::GeneralResult<std::vector<int32_t>> toSigned(const std::vector<uint32_t>& vec) {
+ if (!std::all_of(vec.begin(), vec.end(),
+ [](uint32_t v) { return v <= std::numeric_limits<int32_t>::max(); })) {
+ return NN_ERROR() << "Vector contains a value that doesn't fit into int32_t.";
+ }
+ return std::vector<int32_t>(vec.begin(), vec.end());
+}
+
+} // namespace aidl::android::hardware::neuralnetworks::utils
diff --git a/neuralnetworks/aidl/utils/src/Utils.cpp b/neuralnetworks/aidl/utils/src/Utils.cpp
new file mode 100644
index 0000000..8d00e59
--- /dev/null
+++ b/neuralnetworks/aidl/utils/src/Utils.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2021 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 "Utils.h"
+
+#include <nnapi/Result.h>
+
+namespace aidl::android::hardware::neuralnetworks::utils {
+namespace {
+
+using ::android::nn::GeneralResult;
+
+template <typename Type>
+nn::GeneralResult<std::vector<Type>> cloneVec(const std::vector<Type>& arguments) {
+ std::vector<Type> clonedObjects;
+ clonedObjects.reserve(arguments.size());
+ for (const auto& argument : arguments) {
+ clonedObjects.push_back(NN_TRY(clone(argument)));
+ }
+ return clonedObjects;
+}
+
+template <typename Type>
+GeneralResult<std::vector<Type>> clone(const std::vector<Type>& arguments) {
+ return cloneVec(arguments);
+}
+
+} // namespace
+
+GeneralResult<Memory> clone(const Memory& memory) {
+ common::NativeHandle nativeHandle;
+ nativeHandle.ints = memory.handle.ints;
+ nativeHandle.fds.reserve(memory.handle.fds.size());
+ for (const auto& fd : memory.handle.fds) {
+ const int newFd = dup(fd.get());
+ if (newFd < 0) {
+ return NN_ERROR() << "Couldn't dup a file descriptor";
+ }
+ nativeHandle.fds.emplace_back(newFd);
+ }
+ return Memory{
+ .handle = std::move(nativeHandle),
+ .size = memory.size,
+ .name = memory.name,
+ };
+}
+
+GeneralResult<RequestMemoryPool> clone(const RequestMemoryPool& requestPool) {
+ using Tag = RequestMemoryPool::Tag;
+ switch (requestPool.getTag()) {
+ case Tag::pool:
+ return RequestMemoryPool::make<Tag::pool>(NN_TRY(clone(requestPool.get<Tag::pool>())));
+ case Tag::token:
+ return RequestMemoryPool::make<Tag::token>(requestPool.get<Tag::token>());
+ }
+ // Using explicit type conversion because std::variant inside the RequestMemoryPool confuses the
+ // compiler.
+ return (NN_ERROR() << "Unrecognized request pool tag: " << requestPool.getTag())
+ .
+ operator GeneralResult<RequestMemoryPool>();
+}
+
+GeneralResult<Request> clone(const Request& request) {
+ return Request{
+ .inputs = request.inputs,
+ .outputs = request.outputs,
+ .pools = NN_TRY(clone(request.pools)),
+ };
+}
+
+GeneralResult<Model> clone(const Model& model) {
+ return Model{
+ .main = model.main,
+ .referenced = model.referenced,
+ .operandValues = model.operandValues,
+ .pools = NN_TRY(clone(model.pools)),
+ .relaxComputationFloat32toFloat16 = model.relaxComputationFloat32toFloat16,
+ .extensionNameToPrefix = model.extensionNameToPrefix,
+ };
+}
+
+} // namespace aidl::android::hardware::neuralnetworks::utils
diff --git a/neuralnetworks/aidl/vts/OWNERS b/neuralnetworks/aidl/vts/OWNERS
new file mode 100644
index 0000000..6719a5b
--- /dev/null
+++ b/neuralnetworks/aidl/vts/OWNERS
@@ -0,0 +1,12 @@
+# Neuralnetworks team
+butlermichael@google.com
+dgross@google.com
+jeanluc@google.com
+levp@google.com
+miaowang@google.com
+mikie@google.com
+mks@google.com
+pszczepaniak@google.com
+slavash@google.com
+vddang@google.com
+xusongw@google.com
diff --git a/neuralnetworks/aidl/vts/functional/Android.bp b/neuralnetworks/aidl/vts/functional/Android.bp
new file mode 100644
index 0000000..aa7afbf
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/Android.bp
@@ -0,0 +1,68 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_test {
+ name: "VtsHalNeuralnetworksTargetTest",
+ defaults: [
+ "neuralnetworks_vts_functional_defaults",
+ "use_libaidlvintf_gtest_helper_static",
+ ],
+ srcs: [
+ "BasicTests.cpp",
+ "Callbacks.cpp",
+ "CompilationCachingTests.cpp",
+ "GeneratedTestHarness.cpp",
+ "MemoryDomainTests.cpp",
+ "QualityOfServiceTests.cpp",
+ "TestAssertions.cpp",
+ "TestMain.cpp",
+ "Utils.cpp",
+ "ValidateModel.cpp",
+ "ValidateRequest.cpp",
+ "VtsHalNeuralnetworks.cpp",
+ ],
+ shared_libs: [
+ "libbinder_ndk",
+ "libnativewindow",
+ "libvndksupport",
+ ],
+ static_libs: [
+ "android.hardware.common-V2-ndk_platform",
+ "android.hardware.neuralnetworks-V1-ndk_platform",
+ "android.hidl.allocator@1.0",
+ "android.hidl.memory@1.0",
+ "libgmock",
+ "libhidlmemory",
+ "libneuralnetworks_generated_test_harness",
+ "libneuralnetworks_utils",
+ "libsync",
+ "neuralnetworks_utils_hal_aidl",
+ ],
+ whole_static_libs: [
+ "neuralnetworks_generated_V1_0_example",
+ "neuralnetworks_generated_V1_1_example",
+ "neuralnetworks_generated_V1_2_example",
+ "neuralnetworks_generated_V1_3_example",
+ ],
+ header_libs: [
+ "libbase_headers",
+ "libneuralnetworks_headers",
+ ],
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
+}
diff --git a/neuralnetworks/aidl/vts/functional/AndroidTest.xml b/neuralnetworks/aidl/vts/functional/AndroidTest.xml
new file mode 100644
index 0000000..384d420
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/AndroidTest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs VtsHalNeuralnetworksTargetTest.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-native" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push" value="VtsHalNeuralnetworksTargetTest->/data/local/tmp/VtsHalNeuralnetworksTargetTest" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="VtsHalNeuralnetworksTargetTest" />
+ <option name="native-test-timeout" value="20m" />
+ </test>
+</configuration>
diff --git a/neuralnetworks/aidl/vts/functional/BasicTests.cpp b/neuralnetworks/aidl/vts/functional/BasicTests.cpp
new file mode 100644
index 0000000..b2f4507
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/BasicTests.cpp
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "neuralnetworks_aidl_hal_test"
+
+#include <aidl/android/hardware/neuralnetworks/Capabilities.h>
+#include <aidl/android/hardware/neuralnetworks/IDevice.h>
+#include <aidl/android/hardware/neuralnetworks/Operand.h>
+#include <aidl/android/hardware/neuralnetworks/OperandType.h>
+#include <aidl/android/hardware/neuralnetworks/Priority.h>
+#include <android/binder_interface_utils.h>
+
+#include "Utils.h"
+#include "VtsHalNeuralnetworks.h"
+
+namespace aidl::android::hardware::neuralnetworks::vts::functional {
+
+using implementation::PreparedModelCallback;
+
+// create device test
+TEST_P(NeuralNetworksAidlTest, CreateDevice) {}
+
+// initialization
+TEST_P(NeuralNetworksAidlTest, GetCapabilitiesTest) {
+ Capabilities capabilities;
+ const auto retStatus = kDevice->getCapabilities(&capabilities);
+ ASSERT_TRUE(retStatus.isOk());
+
+ auto isPositive = [](const PerformanceInfo& perf) {
+ return perf.execTime > 0.0f && perf.powerUsage > 0.0f;
+ };
+
+ EXPECT_TRUE(isPositive(capabilities.relaxedFloat32toFloat16PerformanceScalar));
+ EXPECT_TRUE(isPositive(capabilities.relaxedFloat32toFloat16PerformanceTensor));
+ const auto& opPerf = capabilities.operandPerformance;
+ EXPECT_TRUE(
+ std::all_of(opPerf.begin(), opPerf.end(),
+ [isPositive](const OperandPerformance& a) { return isPositive(a.info); }));
+ EXPECT_TRUE(std::is_sorted(opPerf.begin(), opPerf.end(),
+ [](const OperandPerformance& a, const OperandPerformance& b) {
+ return a.type < b.type;
+ }));
+ EXPECT_TRUE(std::all_of(opPerf.begin(), opPerf.end(), [](const OperandPerformance& a) {
+ return a.type != OperandType::SUBGRAPH;
+ }));
+ EXPECT_TRUE(isPositive(capabilities.ifPerformance));
+ EXPECT_TRUE(isPositive(capabilities.whilePerformance));
+}
+
+// detect cycle
+TEST_P(NeuralNetworksAidlTest, CycleTest) {
+ // opnd0 = TENSOR_FLOAT32 // model input
+ // opnd1 = TENSOR_FLOAT32 // model input
+ // opnd2 = INT32 // model input
+ // opnd3 = ADD(opnd0, opnd4, opnd2)
+ // opnd4 = ADD(opnd1, opnd3, opnd2)
+ // opnd5 = ADD(opnd4, opnd0, opnd2) // model output
+ //
+ // +-----+
+ // | |
+ // v |
+ // 3 = ADD(0, 4, 2) |
+ // | |
+ // +----------+ |
+ // | |
+ // v |
+ // 4 = ADD(1, 3, 2) |
+ // | |
+ // +----------------+
+ // |
+ // |
+ // +-------+
+ // |
+ // v
+ // 5 = ADD(4, 0, 2)
+
+ const std::vector<Operand> operands = {
+ {
+ // operands[0]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::SUBGRAPH_INPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[1]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::SUBGRAPH_INPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[2]
+ .type = OperandType::INT32,
+ .dimensions = {},
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::SUBGRAPH_INPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[3]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::TEMPORARY_VARIABLE,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[4]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::TEMPORARY_VARIABLE,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[5]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::SUBGRAPH_OUTPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ };
+
+ const std::vector<Operation> operations = {
+ {.type = OperationType::ADD, .inputs = {0, 4, 2}, .outputs = {3}},
+ {.type = OperationType::ADD, .inputs = {1, 3, 2}, .outputs = {4}},
+ {.type = OperationType::ADD, .inputs = {4, 0, 2}, .outputs = {5}},
+ };
+
+ Subgraph subgraph = {
+ .operands = operands,
+ .operations = operations,
+ .inputIndexes = {0, 1, 2},
+ .outputIndexes = {5},
+ };
+ const Model model = {
+ .main = std::move(subgraph),
+ .referenced = {},
+ .operandValues = {},
+ .pools = {},
+ };
+
+ // ensure that getSupportedOperations() checks model validity
+ std::vector<bool> supportedOps;
+ const auto supportedOpsStatus = kDevice->getSupportedOperations(model, &supportedOps);
+ ASSERT_FALSE(supportedOpsStatus.isOk());
+ ASSERT_EQ(supportedOpsStatus.getExceptionCode(), EX_SERVICE_SPECIFIC);
+ ASSERT_EQ(static_cast<ErrorStatus>(supportedOpsStatus.getServiceSpecificError()),
+ ErrorStatus::INVALID_ARGUMENT);
+
+ // ensure that prepareModel() checks model validity
+ auto preparedModelCallback = ndk::SharedRefBase::make<PreparedModelCallback>();
+ auto prepareLaunchStatus =
+ kDevice->prepareModel(model, ExecutionPreference::FAST_SINGLE_ANSWER, kDefaultPriority,
+ kNoDeadline, {}, {}, kEmptyCacheToken, preparedModelCallback);
+ // Note that preparation can fail for reasons other than an
+ // invalid model (invalid model should result in
+ // INVALID_ARGUMENT) -- for example, perhaps not all
+ // operations are supported, or perhaps the device hit some
+ // kind of capacity limit.
+ ASSERT_FALSE(prepareLaunchStatus.isOk());
+ EXPECT_EQ(prepareLaunchStatus.getExceptionCode(), EX_SERVICE_SPECIFIC);
+ EXPECT_NE(static_cast<ErrorStatus>(prepareLaunchStatus.getServiceSpecificError()),
+ ErrorStatus::NONE);
+
+ EXPECT_NE(preparedModelCallback->getStatus(), ErrorStatus::NONE);
+ EXPECT_EQ(preparedModelCallback->getPreparedModel(), nullptr);
+}
+
+} // namespace aidl::android::hardware::neuralnetworks::vts::functional
diff --git a/neuralnetworks/aidl/vts/functional/Callbacks.cpp b/neuralnetworks/aidl/vts/functional/Callbacks.cpp
new file mode 100644
index 0000000..ca2bb48
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/Callbacks.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "Callbacks"
+
+#include "Callbacks.h"
+
+#include <android-base/logging.h>
+#include <android/binder_auto_utils.h>
+#include <limits>
+
+namespace aidl::android::hardware::neuralnetworks::implementation {
+
+ndk::ScopedAStatus PreparedModelCallback::notify(
+ ErrorStatus errorStatus, const std::shared_ptr<IPreparedModel>& preparedModel) {
+ {
+ std::lock_guard<std::mutex> hold(mMutex);
+ // quick-return if object has already been notified
+ if (mNotified) {
+ return ndk::ScopedAStatus::ok();
+ }
+ // store results and mark as notified
+ mErrorStatus = errorStatus;
+ mPreparedModel = preparedModel;
+ mNotified = true;
+ }
+ mCondition.notify_all();
+ return ndk::ScopedAStatus::ok();
+}
+
+void PreparedModelCallback::wait() const {
+ std::unique_lock<std::mutex> lock(mMutex);
+ mCondition.wait(lock, [this] { return mNotified; });
+}
+
+ErrorStatus PreparedModelCallback::getStatus() const {
+ wait();
+ return mErrorStatus;
+}
+
+std::shared_ptr<IPreparedModel> PreparedModelCallback::getPreparedModel() const {
+ wait();
+ return mPreparedModel;
+}
+
+} // namespace aidl::android::hardware::neuralnetworks::implementation
diff --git a/neuralnetworks/aidl/vts/functional/Callbacks.h b/neuralnetworks/aidl/vts/functional/Callbacks.h
new file mode 100644
index 0000000..0eb4d5f
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/Callbacks.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_NEURALNETWORKS_AIDL_CALLBACKS_H
+#define ANDROID_HARDWARE_NEURALNETWORKS_AIDL_CALLBACKS_H
+
+#include <android-base/thread_annotations.h>
+#include <condition_variable>
+#include <mutex>
+
+#include <aidl/android/hardware/neuralnetworks/BnPreparedModelCallback.h>
+#include <aidl/android/hardware/neuralnetworks/ErrorStatus.h>
+#include <aidl/android/hardware/neuralnetworks/IPreparedModel.h>
+
+/*
+ * The Callback classes are used internally by the NeuralNetworks runtime to
+ * synchronize between different threads. An asynchronous task is launched
+ * paired with a callback object. When a client thread requires the output being
+ * generated by the asynchronous task, the client thread can wait for the result
+ * and be blocked until it has completed. Any wait may safely be called
+ * concurrently, even on the same callback object. When the asynchronous task
+ * has finished its workload, it must immediately call "notify". If the
+ * asynchronous task has failed to launch, the function that tried to launch the
+ * asynchronous task must immediately call "notify". This "notify" call
+ * awakens any client threads waiting on the callback object.
+ *
+ * These classes exist to enable synchronization across AIDL. When
+ * synchronization is only required in the same process, consider using
+ * std::future, std::mutex, std::condition_variable, or std::experimental::latch
+ * instead.
+ */
+
+namespace aidl::android::hardware::neuralnetworks::implementation {
+
+/**
+ * The PreparedModelCallback class is used to receive the error status of
+ * preparing a model as well as the prepared model from a task executing
+ * asynchronously with respect to the runtime. If a calling thread calls wait
+ * or get* on a PreparedModelCallback object and the corresponding asynchronous
+ * task has not finished preparing the model, the calling thread will block
+ * until the asynchronous task has called notify.
+ *
+ * If the callback object is notified more than once, only the results of the
+ * first call to notify are used, and the results from subsequent calls are
+ * discarded.
+ *
+ * This callback object is passed as an argument to IDevice::prepareModel*.
+ */
+class PreparedModelCallback : public BnPreparedModelCallback {
+ public:
+ /**
+ * IPreparedModelCallback::notify marks the callback object with the return
+ * status of the asynchronous model preparation along with the prepared
+ * model, and allows all prior and future wait calls on the
+ * PreparedModelCallback object to proceed.
+ *
+ * IPreparedModelCallback::notify must be called on a given PreparedModelCallback object.
+ *
+ * If the callback object is notified more than once, only the results of
+ * the first call to notify are used, and the results from subsequent calls
+ * are discarded.
+ *
+ * @param status Error status returned from asynchronously preparing the
+ * model; will be:
+ * - NONE if the asynchronous preparation was successful
+ * - DEVICE_UNAVAILABLE if driver is offline or busy
+ * - GENERAL_FAILURE if there is an unspecified error
+ * - INVALID_ARGUMENT if the input model is invalid
+ * @param preparedModel Returned model that has been prepared for execution,
+ * nullptr if the model was unable to be prepared.
+ */
+ ndk::ScopedAStatus notify(ErrorStatus status,
+ const std::shared_ptr<IPreparedModel>& preparedModel) override;
+
+ /**
+ * PreparedModelCallback::wait blocks until notify has been called on the
+ * callback object.
+ */
+ void wait() const;
+
+ /**
+ * Retrieves the error status returned from the asynchronous task launched
+ * by IDevice::prepareModel*. If IDevice::prepareModel* has not finished
+ * asynchronously preparing the model, this call will block until the
+ * asynchronous task notifies the object.
+ *
+ * @return status Error status returned from asynchronously preparing the
+ * model; will be:
+ * - NONE if the asynchronous preparation was successful
+ * - DEVICE_UNAVAILABLE if driver is offline or busy
+ * - GENERAL_FAILURE if there is an unspecified error
+ * - INVALID_ARGUMENT if the input model is invalid
+ */
+ ErrorStatus getStatus() const;
+
+ /**
+ * Retrieves the model that has been prepared for execution from the
+ * asynchronous task launched by IDevice::prepareModel*. If
+ * IDevice::prepareModel* has not finished asynchronously preparing the
+ * model, this call will block until the asynchronous task notifies the
+ * object.
+ *
+ * @return preparedModel Returned model that has been prepared for
+ * execution, nullptr if the model was unable to be prepared.
+ */
+ std::shared_ptr<IPreparedModel> getPreparedModel() const;
+
+ private:
+ mutable std::mutex mMutex;
+ mutable std::condition_variable mCondition;
+ bool mNotified GUARDED_BY(mMutex) = false;
+ ErrorStatus mErrorStatus = ErrorStatus::GENERAL_FAILURE;
+ std::shared_ptr<IPreparedModel> mPreparedModel;
+};
+
+} // namespace aidl::android::hardware::neuralnetworks::implementation
+
+#endif // ANDROID_HARDWARE_NEURALNETWORKS_AIDL_CALLBACKS_H
diff --git a/neuralnetworks/aidl/vts/functional/CompilationCachingTests.cpp b/neuralnetworks/aidl/vts/functional/CompilationCachingTests.cpp
new file mode 100644
index 0000000..e0b529f
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/CompilationCachingTests.cpp
@@ -0,0 +1,1177 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "neuralnetworks_aidl_hal_test"
+
+#include <android-base/logging.h>
+#include <android/binder_auto_utils.h>
+#include <android/binder_interface_utils.h>
+#include <android/binder_status.h>
+#include <fcntl.h>
+#include <ftw.h>
+#include <gtest/gtest.h>
+#include <hidlmemory/mapping.h>
+#include <unistd.h>
+
+#include <cstdio>
+#include <cstdlib>
+#include <iterator>
+#include <random>
+#include <thread>
+
+#include "Callbacks.h"
+#include "GeneratedTestHarness.h"
+#include "MemoryUtils.h"
+#include "TestHarness.h"
+#include "Utils.h"
+#include "VtsHalNeuralnetworks.h"
+
+// Forward declaration of the mobilenet generated test models in
+// frameworks/ml/nn/runtime/test/generated/.
+namespace generated_tests::mobilenet_224_gender_basic_fixed {
+const test_helper::TestModel& get_test_model();
+} // namespace generated_tests::mobilenet_224_gender_basic_fixed
+
+namespace generated_tests::mobilenet_quantized {
+const test_helper::TestModel& get_test_model();
+} // namespace generated_tests::mobilenet_quantized
+
+namespace aidl::android::hardware::neuralnetworks::vts::functional {
+
+using namespace test_helper;
+using implementation::PreparedModelCallback;
+
+namespace float32_model {
+
+constexpr auto get_test_model = generated_tests::mobilenet_224_gender_basic_fixed::get_test_model;
+
+} // namespace float32_model
+
+namespace quant8_model {
+
+constexpr auto get_test_model = generated_tests::mobilenet_quantized::get_test_model;
+
+} // namespace quant8_model
+
+namespace {
+
+enum class AccessMode { READ_WRITE, READ_ONLY, WRITE_ONLY };
+
+// Creates cache handles based on provided file groups.
+// The outer vector corresponds to handles and the inner vector is for fds held by each handle.
+void createCacheFds(const std::vector<std::string>& files, const std::vector<AccessMode>& mode,
+ std::vector<ndk::ScopedFileDescriptor>* fds) {
+ fds->clear();
+ fds->reserve(files.size());
+ for (uint32_t i = 0; i < files.size(); i++) {
+ const auto& file = files[i];
+ int fd;
+ if (mode[i] == AccessMode::READ_ONLY) {
+ fd = open(file.c_str(), O_RDONLY);
+ } else if (mode[i] == AccessMode::WRITE_ONLY) {
+ fd = open(file.c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
+ } else if (mode[i] == AccessMode::READ_WRITE) {
+ fd = open(file.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
+ } else {
+ FAIL();
+ }
+ ASSERT_GE(fd, 0);
+ fds->emplace_back(fd);
+ }
+}
+
+void createCacheFds(const std::vector<std::string>& files, AccessMode mode,
+ std::vector<ndk::ScopedFileDescriptor>* fds) {
+ createCacheFds(files, std::vector<AccessMode>(files.size(), mode), fds);
+}
+
+// Create a chain of broadcast operations. The second operand is always constant tensor [1].
+// For simplicity, activation scalar is shared. The second operand is not shared
+// in the model to let driver maintain a non-trivial size of constant data and the corresponding
+// data locations in cache.
+//
+// --------- activation --------
+// ↓ ↓ ↓ ↓
+// E.g. input -> ADD -> ADD -> ADD -> ... -> ADD -> output
+// ↑ ↑ ↑ ↑
+// [1] [1] [1] [1]
+//
+// This function assumes the operation is either ADD or MUL.
+template <typename CppType, TestOperandType operandType>
+TestModel createLargeTestModelImpl(TestOperationType op, uint32_t len) {
+ EXPECT_TRUE(op == TestOperationType::ADD || op == TestOperationType::MUL);
+
+ // Model operations and operands.
+ std::vector<TestOperation> operations(len);
+ std::vector<TestOperand> operands(len * 2 + 2);
+
+ // The activation scalar, value = 0.
+ operands[0] = {
+ .type = TestOperandType::INT32,
+ .dimensions = {},
+ .numberOfConsumers = len,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+ .data = TestBuffer::createFromVector<int32_t>({0}),
+ };
+
+ // The buffer value of the constant second operand. The logical value is always 1.0f.
+ CppType bufferValue;
+ // The scale of the first and second operand.
+ float scale1, scale2;
+ if (operandType == TestOperandType::TENSOR_FLOAT32) {
+ bufferValue = 1.0f;
+ scale1 = 0.0f;
+ scale2 = 0.0f;
+ } else if (op == TestOperationType::ADD) {
+ bufferValue = 1;
+ scale1 = 1.0f;
+ scale2 = 1.0f;
+ } else {
+ // To satisfy the constraint on quant8 MUL: input0.scale * input1.scale < output.scale,
+ // set input1 to have scale = 0.5f and bufferValue = 2, i.e. 1.0f in floating point.
+ bufferValue = 2;
+ scale1 = 1.0f;
+ scale2 = 0.5f;
+ }
+
+ for (uint32_t i = 0; i < len; i++) {
+ const uint32_t firstInputIndex = i * 2 + 1;
+ const uint32_t secondInputIndex = firstInputIndex + 1;
+ const uint32_t outputIndex = secondInputIndex + 1;
+
+ // The first operation input.
+ operands[firstInputIndex] = {
+ .type = operandType,
+ .dimensions = {1},
+ .numberOfConsumers = 1,
+ .scale = scale1,
+ .zeroPoint = 0,
+ .lifetime = (i == 0 ? TestOperandLifeTime::MODEL_INPUT
+ : TestOperandLifeTime::TEMPORARY_VARIABLE),
+ .data = (i == 0 ? TestBuffer::createFromVector<CppType>({1}) : TestBuffer()),
+ };
+
+ // The second operation input, value = 1.
+ operands[secondInputIndex] = {
+ .type = operandType,
+ .dimensions = {1},
+ .numberOfConsumers = 1,
+ .scale = scale2,
+ .zeroPoint = 0,
+ .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+ .data = TestBuffer::createFromVector<CppType>({bufferValue}),
+ };
+
+ // The operation. All operations share the same activation scalar.
+ // The output operand is created as an input in the next iteration of the loop, in the case
+ // of all but the last member of the chain; and after the loop as a model output, in the
+ // case of the last member of the chain.
+ operations[i] = {
+ .type = op,
+ .inputs = {firstInputIndex, secondInputIndex, /*activation scalar*/ 0},
+ .outputs = {outputIndex},
+ };
+ }
+
+ // For TestOperationType::ADD, output = 1 + 1 * len = len + 1
+ // For TestOperationType::MUL, output = 1 * 1 ^ len = 1
+ CppType outputResult = static_cast<CppType>(op == TestOperationType::ADD ? len + 1u : 1u);
+
+ // The model output.
+ operands.back() = {
+ .type = operandType,
+ .dimensions = {1},
+ .numberOfConsumers = 0,
+ .scale = scale1,
+ .zeroPoint = 0,
+ .lifetime = TestOperandLifeTime::MODEL_OUTPUT,
+ .data = TestBuffer::createFromVector<CppType>({outputResult}),
+ };
+
+ return {
+ .main = {.operands = std::move(operands),
+ .operations = std::move(operations),
+ .inputIndexes = {1},
+ .outputIndexes = {len * 2 + 1}},
+ .isRelaxed = false,
+ };
+}
+
+} // namespace
+
+// Tag for the compilation caching tests.
+class CompilationCachingTestBase : public testing::Test {
+ protected:
+ CompilationCachingTestBase(std::shared_ptr<IDevice> device, OperandType type)
+ : kDevice(std::move(device)), kOperandType(type) {}
+
+ void SetUp() override {
+ testing::Test::SetUp();
+ ASSERT_NE(kDevice.get(), nullptr);
+
+ // Create cache directory. The cache directory and a temporary cache file is always created
+ // to test the behavior of prepareModelFromCache, even when caching is not supported.
+ char cacheDirTemp[] = "/data/local/tmp/TestCompilationCachingXXXXXX";
+ char* cacheDir = mkdtemp(cacheDirTemp);
+ ASSERT_NE(cacheDir, nullptr);
+ mCacheDir = cacheDir;
+ mCacheDir.push_back('/');
+
+ NumberOfCacheFiles numCacheFiles;
+ const auto ret = kDevice->getNumberOfCacheFilesNeeded(&numCacheFiles);
+ ASSERT_TRUE(ret.isOk());
+
+ mNumModelCache = numCacheFiles.numModelCache;
+ mNumDataCache = numCacheFiles.numDataCache;
+ ASSERT_GE(mNumModelCache, 0) << "Invalid numModelCache: " << mNumModelCache;
+ ASSERT_GE(mNumDataCache, 0) << "Invalid numDataCache: " << mNumDataCache;
+ mIsCachingSupported = mNumModelCache > 0 || mNumDataCache > 0;
+
+ // Create empty cache files.
+ mTmpCache = mCacheDir + "tmp";
+ for (uint32_t i = 0; i < mNumModelCache; i++) {
+ mModelCache.push_back({mCacheDir + "model" + std::to_string(i)});
+ }
+ for (uint32_t i = 0; i < mNumDataCache; i++) {
+ mDataCache.push_back({mCacheDir + "data" + std::to_string(i)});
+ }
+ // Placeholder handles, use AccessMode::WRITE_ONLY for createCacheFds to create files.
+ std::vector<ndk::ScopedFileDescriptor> modelHandle, dataHandle, tmpHandle;
+ createCacheFds(mModelCache, AccessMode::WRITE_ONLY, &modelHandle);
+ createCacheFds(mDataCache, AccessMode::WRITE_ONLY, &dataHandle);
+ createCacheFds({mTmpCache}, AccessMode::WRITE_ONLY, &tmpHandle);
+
+ if (!mIsCachingSupported) {
+ LOG(INFO) << "NN VTS: Early termination of test because vendor service does not "
+ "support compilation caching.";
+ std::cout << "[ ] Early termination of test because vendor service does not "
+ "support compilation caching."
+ << std::endl;
+ }
+ }
+
+ void TearDown() override {
+ // If the test passes, remove the tmp directory. Otherwise, keep it for debugging purposes.
+ if (!testing::Test::HasFailure()) {
+ // Recursively remove the cache directory specified by mCacheDir.
+ auto callback = [](const char* entry, const struct stat*, int, struct FTW*) {
+ return remove(entry);
+ };
+ nftw(mCacheDir.c_str(), callback, 128, FTW_DEPTH | FTW_MOUNT | FTW_PHYS);
+ }
+ testing::Test::TearDown();
+ }
+
+ // Model and examples creators. According to kOperandType, the following methods will return
+ // either float32 model/examples or the quant8 variant.
+ TestModel createTestModel() {
+ if (kOperandType == OperandType::TENSOR_FLOAT32) {
+ return float32_model::get_test_model();
+ } else {
+ return quant8_model::get_test_model();
+ }
+ }
+
+ TestModel createLargeTestModel(OperationType op, uint32_t len) {
+ if (kOperandType == OperandType::TENSOR_FLOAT32) {
+ return createLargeTestModelImpl<float, TestOperandType::TENSOR_FLOAT32>(
+ static_cast<TestOperationType>(op), len);
+ } else {
+ return createLargeTestModelImpl<uint8_t, TestOperandType::TENSOR_QUANT8_ASYMM>(
+ static_cast<TestOperationType>(op), len);
+ }
+ }
+
+ // See if the service can handle the model.
+ bool isModelFullySupported(const Model& model) {
+ std::vector<bool> supportedOps;
+ const auto supportedCall = kDevice->getSupportedOperations(model, &supportedOps);
+ EXPECT_TRUE(supportedCall.isOk());
+ EXPECT_EQ(supportedOps.size(), model.main.operations.size());
+ if (!supportedCall.isOk() || supportedOps.size() != model.main.operations.size()) {
+ return false;
+ }
+ return std::all_of(supportedOps.begin(), supportedOps.end(),
+ [](bool valid) { return valid; });
+ }
+
+ void saveModelToCache(const Model& model,
+ const std::vector<ndk::ScopedFileDescriptor>& modelCache,
+ const std::vector<ndk::ScopedFileDescriptor>& dataCache,
+ std::shared_ptr<IPreparedModel>* preparedModel = nullptr) {
+ if (preparedModel != nullptr) *preparedModel = nullptr;
+
+ // Launch prepare model.
+ std::shared_ptr<PreparedModelCallback> preparedModelCallback =
+ ndk::SharedRefBase::make<PreparedModelCallback>();
+ std::vector<uint8_t> cacheToken(std::begin(mToken), std::end(mToken));
+ const auto prepareLaunchStatus = kDevice->prepareModel(
+ model, ExecutionPreference::FAST_SINGLE_ANSWER, kDefaultPriority, kNoDeadline,
+ modelCache, dataCache, cacheToken, preparedModelCallback);
+ ASSERT_TRUE(prepareLaunchStatus.isOk());
+
+ // Retrieve prepared model.
+ preparedModelCallback->wait();
+ ASSERT_EQ(preparedModelCallback->getStatus(), ErrorStatus::NONE);
+ if (preparedModel != nullptr) {
+ *preparedModel = preparedModelCallback->getPreparedModel();
+ }
+ }
+
+ bool checkEarlyTermination(ErrorStatus status) {
+ if (status == ErrorStatus::GENERAL_FAILURE) {
+ LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot "
+ "save the prepared model that it does not support.";
+ std::cout << "[ ] Early termination of test because vendor service cannot "
+ "save the prepared model that it does not support."
+ << std::endl;
+ return true;
+ }
+ return false;
+ }
+
+ bool checkEarlyTermination(const Model& model) {
+ if (!isModelFullySupported(model)) {
+ LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot "
+ "prepare model that it does not support.";
+ std::cout << "[ ] Early termination of test because vendor service cannot "
+ "prepare model that it does not support."
+ << std::endl;
+ return true;
+ }
+ return false;
+ }
+
+ void prepareModelFromCache(const std::vector<ndk::ScopedFileDescriptor>& modelCache,
+ const std::vector<ndk::ScopedFileDescriptor>& dataCache,
+ std::shared_ptr<IPreparedModel>* preparedModel,
+ ErrorStatus* status) {
+ // Launch prepare model from cache.
+ std::shared_ptr<PreparedModelCallback> preparedModelCallback =
+ ndk::SharedRefBase::make<PreparedModelCallback>();
+ std::vector<uint8_t> cacheToken(std::begin(mToken), std::end(mToken));
+ const auto prepareLaunchStatus = kDevice->prepareModelFromCache(
+ kNoDeadline, modelCache, dataCache, cacheToken, preparedModelCallback);
+ ASSERT_TRUE(prepareLaunchStatus.isOk() ||
+ prepareLaunchStatus.getExceptionCode() == EX_SERVICE_SPECIFIC)
+ << "prepareLaunchStatus: " << prepareLaunchStatus.getDescription();
+ if (!prepareLaunchStatus.isOk()) {
+ *preparedModel = nullptr;
+ *status = static_cast<ErrorStatus>(prepareLaunchStatus.getServiceSpecificError());
+ return;
+ }
+
+ // Retrieve prepared model.
+ preparedModelCallback->wait();
+ *status = preparedModelCallback->getStatus();
+ *preparedModel = preparedModelCallback->getPreparedModel();
+ }
+
+ // Absolute path to the temporary cache directory.
+ std::string mCacheDir;
+
+ // Groups of file paths for model and data cache in the tmp cache directory, initialized with
+ // size = mNum{Model|Data}Cache. The outer vector corresponds to handles and the inner vector is
+ // for fds held by each handle.
+ std::vector<std::string> mModelCache;
+ std::vector<std::string> mDataCache;
+
+ // A separate temporary file path in the tmp cache directory.
+ std::string mTmpCache;
+
+ uint8_t mToken[static_cast<uint32_t>(IDevice::BYTE_SIZE_OF_CACHE_TOKEN)] = {};
+ uint32_t mNumModelCache;
+ uint32_t mNumDataCache;
+ uint32_t mIsCachingSupported;
+
+ const std::shared_ptr<IDevice> kDevice;
+ // The primary data type of the testModel.
+ const OperandType kOperandType;
+};
+
+using CompilationCachingTestParam = std::tuple<NamedDevice, OperandType>;
+
+// A parameterized fixture of CompilationCachingTestBase. Every test will run twice, with the first
+// pass running with float32 models and the second pass running with quant8 models.
+class CompilationCachingTest : public CompilationCachingTestBase,
+ public testing::WithParamInterface<CompilationCachingTestParam> {
+ protected:
+ CompilationCachingTest()
+ : CompilationCachingTestBase(getData(std::get<NamedDevice>(GetParam())),
+ std::get<OperandType>(GetParam())) {}
+};
+
+TEST_P(CompilationCachingTest, CacheSavingAndRetrieval) {
+ // Create test HIDL model and compile.
+ const TestModel& testModel = createTestModel();
+ const Model model = createModel(testModel);
+ if (checkEarlyTermination(model)) return;
+ std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+
+ // Save the compilation to cache.
+ {
+ std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+ createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ saveModelToCache(model, modelCache, dataCache);
+ }
+
+ // Retrieve preparedModel from cache.
+ {
+ preparedModel = nullptr;
+ ErrorStatus status;
+ std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+ createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+ if (!mIsCachingSupported) {
+ ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE);
+ ASSERT_EQ(preparedModel, nullptr);
+ return;
+ } else if (checkEarlyTermination(status)) {
+ ASSERT_EQ(preparedModel, nullptr);
+ return;
+ } else {
+ ASSERT_EQ(status, ErrorStatus::NONE);
+ ASSERT_NE(preparedModel, nullptr);
+ }
+ }
+
+ // Execute and verify results.
+ EvaluatePreparedModel(kDevice, preparedModel, testModel, /*testKind=*/TestKind::GENERAL);
+}
+
+TEST_P(CompilationCachingTest, CacheSavingAndRetrievalNonZeroOffset) {
+ // Create test HIDL model and compile.
+ const TestModel& testModel = createTestModel();
+ const Model model = createModel(testModel);
+ if (checkEarlyTermination(model)) return;
+ std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+
+ // Save the compilation to cache.
+ {
+ std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+ createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ uint8_t placeholderBytes[] = {0, 0};
+ // Write a placeholder integer to the cache.
+ // The driver should be able to handle non-empty cache and non-zero fd offset.
+ for (uint32_t i = 0; i < modelCache.size(); i++) {
+ ASSERT_EQ(write(modelCache[i].get(), &placeholderBytes, sizeof(placeholderBytes)),
+ sizeof(placeholderBytes));
+ }
+ for (uint32_t i = 0; i < dataCache.size(); i++) {
+ ASSERT_EQ(write(dataCache[i].get(), &placeholderBytes, sizeof(placeholderBytes)),
+ sizeof(placeholderBytes));
+ }
+ saveModelToCache(model, modelCache, dataCache);
+ }
+
+ // Retrieve preparedModel from cache.
+ {
+ preparedModel = nullptr;
+ ErrorStatus status;
+ std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+ createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ uint8_t placeholderByte = 0;
+ // Advance the offset of each handle by one byte.
+ // The driver should be able to handle non-zero fd offset.
+ for (uint32_t i = 0; i < modelCache.size(); i++) {
+ ASSERT_GE(read(modelCache[i].get(), &placeholderByte, 1), 0);
+ }
+ for (uint32_t i = 0; i < dataCache.size(); i++) {
+ ASSERT_GE(read(dataCache[i].get(), &placeholderByte, 1), 0);
+ }
+ prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+ if (!mIsCachingSupported) {
+ ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE);
+ ASSERT_EQ(preparedModel, nullptr);
+ return;
+ } else if (checkEarlyTermination(status)) {
+ ASSERT_EQ(preparedModel, nullptr);
+ return;
+ } else {
+ ASSERT_EQ(status, ErrorStatus::NONE);
+ ASSERT_NE(preparedModel, nullptr);
+ }
+ }
+
+ // Execute and verify results.
+ EvaluatePreparedModel(kDevice, preparedModel, testModel, /*testKind=*/TestKind::GENERAL);
+}
+
+TEST_P(CompilationCachingTest, SaveToCacheInvalidNumCache) {
+ // Create test HIDL model and compile.
+ const TestModel& testModel = createTestModel();
+ const Model model = createModel(testModel);
+ if (checkEarlyTermination(model)) return;
+
+ // Test with number of model cache files greater than mNumModelCache.
+ {
+ std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+ // Pass an additional cache file for model cache.
+ mModelCache.push_back({mTmpCache});
+ createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ mModelCache.pop_back();
+ std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+ saveModelToCache(model, modelCache, dataCache, &preparedModel);
+ ASSERT_NE(preparedModel, nullptr);
+ // Execute and verify results.
+ EvaluatePreparedModel(kDevice, preparedModel, testModel, /*testKind=*/TestKind::GENERAL);
+ // Check if prepareModelFromCache fails.
+ preparedModel = nullptr;
+ ErrorStatus status;
+ prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+ if (status != ErrorStatus::INVALID_ARGUMENT) {
+ ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE);
+ }
+ ASSERT_EQ(preparedModel, nullptr);
+ }
+
+ // Test with number of model cache files smaller than mNumModelCache.
+ if (mModelCache.size() > 0) {
+ std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+ // Pop out the last cache file.
+ auto tmp = mModelCache.back();
+ mModelCache.pop_back();
+ createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ mModelCache.push_back(tmp);
+ std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+ saveModelToCache(model, modelCache, dataCache, &preparedModel);
+ ASSERT_NE(preparedModel, nullptr);
+ // Execute and verify results.
+ EvaluatePreparedModel(kDevice, preparedModel, testModel, /*testKind=*/TestKind::GENERAL);
+ // Check if prepareModelFromCache fails.
+ preparedModel = nullptr;
+ ErrorStatus status;
+ prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+ if (status != ErrorStatus::INVALID_ARGUMENT) {
+ ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE);
+ }
+ ASSERT_EQ(preparedModel, nullptr);
+ }
+
+ // Test with number of data cache files greater than mNumDataCache.
+ {
+ std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+ // Pass an additional cache file for data cache.
+ mDataCache.push_back({mTmpCache});
+ createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ mDataCache.pop_back();
+ std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+ saveModelToCache(model, modelCache, dataCache, &preparedModel);
+ ASSERT_NE(preparedModel, nullptr);
+ // Execute and verify results.
+ EvaluatePreparedModel(kDevice, preparedModel, testModel, /*testKind=*/TestKind::GENERAL);
+ // Check if prepareModelFromCache fails.
+ preparedModel = nullptr;
+ ErrorStatus status;
+ prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+ if (status != ErrorStatus::INVALID_ARGUMENT) {
+ ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE);
+ }
+ ASSERT_EQ(preparedModel, nullptr);
+ }
+
+ // Test with number of data cache files smaller than mNumDataCache.
+ if (mDataCache.size() > 0) {
+ std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+ // Pop out the last cache file.
+ auto tmp = mDataCache.back();
+ mDataCache.pop_back();
+ createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ mDataCache.push_back(tmp);
+ std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+ saveModelToCache(model, modelCache, dataCache, &preparedModel);
+ ASSERT_NE(preparedModel, nullptr);
+ // Execute and verify results.
+ EvaluatePreparedModel(kDevice, preparedModel, testModel, /*testKind=*/TestKind::GENERAL);
+ // Check if prepareModelFromCache fails.
+ preparedModel = nullptr;
+ ErrorStatus status;
+ prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+ if (status != ErrorStatus::INVALID_ARGUMENT) {
+ ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE);
+ }
+ ASSERT_EQ(preparedModel, nullptr);
+ }
+}
+
+TEST_P(CompilationCachingTest, PrepareModelFromCacheInvalidNumCache) {
+ // Create test HIDL model and compile.
+ const TestModel& testModel = createTestModel();
+ const Model model = createModel(testModel);
+ if (checkEarlyTermination(model)) return;
+
+ // Save the compilation to cache.
+ {
+ std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+ createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ saveModelToCache(model, modelCache, dataCache);
+ }
+
+ // Test with number of model cache files greater than mNumModelCache.
+ {
+ std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+ ErrorStatus status;
+ std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+ mModelCache.push_back({mTmpCache});
+ createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ mModelCache.pop_back();
+ prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+ if (status != ErrorStatus::GENERAL_FAILURE) {
+ ASSERT_EQ(status, ErrorStatus::INVALID_ARGUMENT);
+ }
+ ASSERT_EQ(preparedModel, nullptr);
+ }
+
+ // Test with number of model cache files smaller than mNumModelCache.
+ if (mModelCache.size() > 0) {
+ std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+ ErrorStatus status;
+ std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+ auto tmp = mModelCache.back();
+ mModelCache.pop_back();
+ createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ mModelCache.push_back(tmp);
+ prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+ if (status != ErrorStatus::GENERAL_FAILURE) {
+ ASSERT_EQ(status, ErrorStatus::INVALID_ARGUMENT);
+ }
+ ASSERT_EQ(preparedModel, nullptr);
+ }
+
+ // Test with number of data cache files greater than mNumDataCache.
+ {
+ std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+ ErrorStatus status;
+ std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+ mDataCache.push_back({mTmpCache});
+ createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ mDataCache.pop_back();
+ prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+ if (status != ErrorStatus::GENERAL_FAILURE) {
+ ASSERT_EQ(status, ErrorStatus::INVALID_ARGUMENT);
+ }
+ ASSERT_EQ(preparedModel, nullptr);
+ }
+
+ // Test with number of data cache files smaller than mNumDataCache.
+ if (mDataCache.size() > 0) {
+ std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+ ErrorStatus status;
+ std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+ auto tmp = mDataCache.back();
+ mDataCache.pop_back();
+ createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ mDataCache.push_back(tmp);
+ prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+ if (status != ErrorStatus::GENERAL_FAILURE) {
+ ASSERT_EQ(status, ErrorStatus::INVALID_ARGUMENT);
+ }
+ ASSERT_EQ(preparedModel, nullptr);
+ }
+}
+
+TEST_P(CompilationCachingTest, SaveToCacheInvalidAccessMode) {
+ // Create test HIDL model and compile.
+ const TestModel& testModel = createTestModel();
+ const Model model = createModel(testModel);
+ if (checkEarlyTermination(model)) return;
+ std::vector<AccessMode> modelCacheMode(mNumModelCache, AccessMode::READ_WRITE);
+ std::vector<AccessMode> dataCacheMode(mNumDataCache, AccessMode::READ_WRITE);
+
+ // Go through each handle in model cache, test with invalid access mode.
+ for (uint32_t i = 0; i < mNumModelCache; i++) {
+ std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+ modelCacheMode[i] = AccessMode::READ_ONLY;
+ createCacheFds(mModelCache, modelCacheMode, &modelCache);
+ createCacheFds(mDataCache, dataCacheMode, &dataCache);
+ modelCacheMode[i] = AccessMode::READ_WRITE;
+ std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+ saveModelToCache(model, modelCache, dataCache, &preparedModel);
+ ASSERT_NE(preparedModel, nullptr);
+ // Execute and verify results.
+ EvaluatePreparedModel(kDevice, preparedModel, testModel, /*testKind=*/TestKind::GENERAL);
+ // Check if prepareModelFromCache fails.
+ preparedModel = nullptr;
+ ErrorStatus status;
+ prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+ if (status != ErrorStatus::INVALID_ARGUMENT) {
+ ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE);
+ }
+ ASSERT_EQ(preparedModel, nullptr);
+ }
+
+ // Go through each handle in data cache, test with invalid access mode.
+ for (uint32_t i = 0; i < mNumDataCache; i++) {
+ std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+ dataCacheMode[i] = AccessMode::READ_ONLY;
+ createCacheFds(mModelCache, modelCacheMode, &modelCache);
+ createCacheFds(mDataCache, dataCacheMode, &dataCache);
+ dataCacheMode[i] = AccessMode::READ_WRITE;
+ std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+ saveModelToCache(model, modelCache, dataCache, &preparedModel);
+ ASSERT_NE(preparedModel, nullptr);
+ // Execute and verify results.
+ EvaluatePreparedModel(kDevice, preparedModel, testModel, /*testKind=*/TestKind::GENERAL);
+ // Check if prepareModelFromCache fails.
+ preparedModel = nullptr;
+ ErrorStatus status;
+ prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+ if (status != ErrorStatus::INVALID_ARGUMENT) {
+ ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE);
+ }
+ ASSERT_EQ(preparedModel, nullptr);
+ }
+}
+
+TEST_P(CompilationCachingTest, PrepareModelFromCacheInvalidAccessMode) {
+ // Create test HIDL model and compile.
+ const TestModel& testModel = createTestModel();
+ const Model model = createModel(testModel);
+ if (checkEarlyTermination(model)) return;
+ std::vector<AccessMode> modelCacheMode(mNumModelCache, AccessMode::READ_WRITE);
+ std::vector<AccessMode> dataCacheMode(mNumDataCache, AccessMode::READ_WRITE);
+
+ // Save the compilation to cache.
+ {
+ std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+ createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ saveModelToCache(model, modelCache, dataCache);
+ }
+
+ // Go through each handle in model cache, test with invalid access mode.
+ for (uint32_t i = 0; i < mNumModelCache; i++) {
+ std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+ ErrorStatus status;
+ std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+ modelCacheMode[i] = AccessMode::WRITE_ONLY;
+ createCacheFds(mModelCache, modelCacheMode, &modelCache);
+ createCacheFds(mDataCache, dataCacheMode, &dataCache);
+ modelCacheMode[i] = AccessMode::READ_WRITE;
+ prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+ ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE);
+ ASSERT_EQ(preparedModel, nullptr);
+ }
+
+ // Go through each handle in data cache, test with invalid access mode.
+ for (uint32_t i = 0; i < mNumDataCache; i++) {
+ std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+ ErrorStatus status;
+ std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+ dataCacheMode[i] = AccessMode::WRITE_ONLY;
+ createCacheFds(mModelCache, modelCacheMode, &modelCache);
+ createCacheFds(mDataCache, dataCacheMode, &dataCache);
+ dataCacheMode[i] = AccessMode::READ_WRITE;
+ prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+ ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE);
+ ASSERT_EQ(preparedModel, nullptr);
+ }
+}
+
+// Copy file contents between files.
+// The vector sizes must match.
+static void copyCacheFiles(const std::vector<std::string>& from,
+ const std::vector<std::string>& to) {
+ constexpr size_t kBufferSize = 1000000;
+ uint8_t buffer[kBufferSize];
+
+ ASSERT_EQ(from.size(), to.size());
+ for (uint32_t i = 0; i < from.size(); i++) {
+ int fromFd = open(from[i].c_str(), O_RDONLY);
+ int toFd = open(to[i].c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
+ ASSERT_GE(fromFd, 0);
+ ASSERT_GE(toFd, 0);
+
+ ssize_t readBytes;
+ while ((readBytes = read(fromFd, &buffer, kBufferSize)) > 0) {
+ ASSERT_EQ(write(toFd, &buffer, readBytes), readBytes);
+ }
+ ASSERT_GE(readBytes, 0);
+
+ close(fromFd);
+ close(toFd);
+ }
+}
+
+// Number of operations in the large test model.
+constexpr uint32_t kLargeModelSize = 100;
+constexpr uint32_t kNumIterationsTOCTOU = 100;
+
+TEST_P(CompilationCachingTest, SaveToCache_TOCTOU) {
+ if (!mIsCachingSupported) return;
+
+ // Create test models and check if fully supported by the service.
+ const TestModel testModelMul = createLargeTestModel(OperationType::MUL, kLargeModelSize);
+ const Model modelMul = createModel(testModelMul);
+ if (checkEarlyTermination(modelMul)) return;
+ const TestModel testModelAdd = createLargeTestModel(OperationType::ADD, kLargeModelSize);
+ const Model modelAdd = createModel(testModelAdd);
+ if (checkEarlyTermination(modelAdd)) return;
+
+ // Save the modelMul compilation to cache.
+ auto modelCacheMul = mModelCache;
+ for (auto& cache : modelCacheMul) {
+ cache.append("_mul");
+ }
+ {
+ std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+ createCacheFds(modelCacheMul, AccessMode::READ_WRITE, &modelCache);
+ createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ saveModelToCache(modelMul, modelCache, dataCache);
+ }
+
+ // Use a different token for modelAdd.
+ mToken[0]++;
+
+ // This test is probabilistic, so we run it multiple times.
+ for (uint32_t i = 0; i < kNumIterationsTOCTOU; i++) {
+ // Save the modelAdd compilation to cache.
+ {
+ std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+ createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+
+ // Spawn a thread to copy the cache content concurrently while saving to cache.
+ std::thread thread(copyCacheFiles, std::cref(modelCacheMul), std::cref(mModelCache));
+ saveModelToCache(modelAdd, modelCache, dataCache);
+ thread.join();
+ }
+
+ // Retrieve preparedModel from cache.
+ {
+ std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+ ErrorStatus status;
+ std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+ createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+
+ // The preparation may fail or succeed, but must not crash. If the preparation succeeds,
+ // the prepared model must be executed with the correct result and not crash.
+ if (status != ErrorStatus::NONE) {
+ ASSERT_EQ(preparedModel, nullptr);
+ } else {
+ ASSERT_NE(preparedModel, nullptr);
+ EvaluatePreparedModel(kDevice, preparedModel, testModelAdd,
+ /*testKind=*/TestKind::GENERAL);
+ }
+ }
+ }
+}
+
+TEST_P(CompilationCachingTest, PrepareFromCache_TOCTOU) {
+ if (!mIsCachingSupported) return;
+
+ // Create test models and check if fully supported by the service.
+ const TestModel testModelMul = createLargeTestModel(OperationType::MUL, kLargeModelSize);
+ const Model modelMul = createModel(testModelMul);
+ if (checkEarlyTermination(modelMul)) return;
+ const TestModel testModelAdd = createLargeTestModel(OperationType::ADD, kLargeModelSize);
+ const Model modelAdd = createModel(testModelAdd);
+ if (checkEarlyTermination(modelAdd)) return;
+
+ // Save the modelMul compilation to cache.
+ auto modelCacheMul = mModelCache;
+ for (auto& cache : modelCacheMul) {
+ cache.append("_mul");
+ }
+ {
+ std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+ createCacheFds(modelCacheMul, AccessMode::READ_WRITE, &modelCache);
+ createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ saveModelToCache(modelMul, modelCache, dataCache);
+ }
+
+ // Use a different token for modelAdd.
+ mToken[0]++;
+
+ // This test is probabilistic, so we run it multiple times.
+ for (uint32_t i = 0; i < kNumIterationsTOCTOU; i++) {
+ // Save the modelAdd compilation to cache.
+ {
+ std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+ createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ saveModelToCache(modelAdd, modelCache, dataCache);
+ }
+
+ // Retrieve preparedModel from cache.
+ {
+ std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+ ErrorStatus status;
+ std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+ createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+
+ // Spawn a thread to copy the cache content concurrently while preparing from cache.
+ std::thread thread(copyCacheFiles, std::cref(modelCacheMul), std::cref(mModelCache));
+ prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+ thread.join();
+
+ // The preparation may fail or succeed, but must not crash. If the preparation succeeds,
+ // the prepared model must be executed with the correct result and not crash.
+ if (status != ErrorStatus::NONE) {
+ ASSERT_EQ(preparedModel, nullptr);
+ } else {
+ ASSERT_NE(preparedModel, nullptr);
+ EvaluatePreparedModel(kDevice, preparedModel, testModelAdd,
+ /*testKind=*/TestKind::GENERAL);
+ }
+ }
+ }
+}
+
+TEST_P(CompilationCachingTest, ReplaceSecuritySensitiveCache) {
+ if (!mIsCachingSupported) return;
+
+ // Create test models and check if fully supported by the service.
+ const TestModel testModelMul = createLargeTestModel(OperationType::MUL, kLargeModelSize);
+ const Model modelMul = createModel(testModelMul);
+ if (checkEarlyTermination(modelMul)) return;
+ const TestModel testModelAdd = createLargeTestModel(OperationType::ADD, kLargeModelSize);
+ const Model modelAdd = createModel(testModelAdd);
+ if (checkEarlyTermination(modelAdd)) return;
+
+ // Save the modelMul compilation to cache.
+ auto modelCacheMul = mModelCache;
+ for (auto& cache : modelCacheMul) {
+ cache.append("_mul");
+ }
+ {
+ std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+ createCacheFds(modelCacheMul, AccessMode::READ_WRITE, &modelCache);
+ createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ saveModelToCache(modelMul, modelCache, dataCache);
+ }
+
+ // Use a different token for modelAdd.
+ mToken[0]++;
+
+ // Save the modelAdd compilation to cache.
+ {
+ std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+ createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ saveModelToCache(modelAdd, modelCache, dataCache);
+ }
+
+ // Replace the model cache of modelAdd with modelMul.
+ copyCacheFiles(modelCacheMul, mModelCache);
+
+ // Retrieve the preparedModel from cache, expect failure.
+ {
+ std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+ ErrorStatus status;
+ std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+ createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+ ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE);
+ ASSERT_EQ(preparedModel, nullptr);
+ }
+}
+
+// TODO(b/179270601): restore kNamedDeviceChoices.
+static const auto kOperandTypeChoices =
+ testing::Values(OperandType::TENSOR_FLOAT32, OperandType::TENSOR_QUANT8_ASYMM);
+
+std::string printCompilationCachingTest(
+ const testing::TestParamInfo<CompilationCachingTestParam>& info) {
+ const auto& [namedDevice, operandType] = info.param;
+ const std::string type = (operandType == OperandType::TENSOR_FLOAT32 ? "float32" : "quant8");
+ return gtestCompliantName(getName(namedDevice) + "_" + type);
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(CompilationCachingTest);
+INSTANTIATE_TEST_SUITE_P(TestCompilationCaching, CompilationCachingTest,
+ testing::Combine(testing::ValuesIn(getNamedDevices()),
+ kOperandTypeChoices),
+ printCompilationCachingTest);
+
+using CompilationCachingSecurityTestParam = std::tuple<NamedDevice, OperandType, uint32_t>;
+
+class CompilationCachingSecurityTest
+ : public CompilationCachingTestBase,
+ public testing::WithParamInterface<CompilationCachingSecurityTestParam> {
+ protected:
+ CompilationCachingSecurityTest()
+ : CompilationCachingTestBase(getData(std::get<NamedDevice>(GetParam())),
+ std::get<OperandType>(GetParam())) {}
+
+ void SetUp() {
+ CompilationCachingTestBase::SetUp();
+ generator.seed(kSeed);
+ }
+
+ // Get a random integer within a closed range [lower, upper].
+ template <typename T>
+ T getRandomInt(T lower, T upper) {
+ std::uniform_int_distribution<T> dis(lower, upper);
+ return dis(generator);
+ }
+
+ // Randomly flip one single bit of the cache entry.
+ void flipOneBitOfCache(const std::string& filename, bool* skip) {
+ FILE* pFile = fopen(filename.c_str(), "r+");
+ ASSERT_EQ(fseek(pFile, 0, SEEK_END), 0);
+ long int fileSize = ftell(pFile);
+ if (fileSize == 0) {
+ fclose(pFile);
+ *skip = true;
+ return;
+ }
+ ASSERT_EQ(fseek(pFile, getRandomInt(0l, fileSize - 1), SEEK_SET), 0);
+ int readByte = fgetc(pFile);
+ ASSERT_NE(readByte, EOF);
+ ASSERT_EQ(fseek(pFile, -1, SEEK_CUR), 0);
+ ASSERT_NE(fputc(static_cast<uint8_t>(readByte) ^ (1U << getRandomInt(0, 7)), pFile), EOF);
+ fclose(pFile);
+ *skip = false;
+ }
+
+ // Randomly append bytes to the cache entry.
+ void appendBytesToCache(const std::string& filename, bool* skip) {
+ FILE* pFile = fopen(filename.c_str(), "a");
+ uint32_t appendLength = getRandomInt(1, 256);
+ for (uint32_t i = 0; i < appendLength; i++) {
+ ASSERT_NE(fputc(getRandomInt<uint8_t>(0, 255), pFile), EOF);
+ }
+ fclose(pFile);
+ *skip = false;
+ }
+
+ enum class ExpectedResult { GENERAL_FAILURE, NOT_CRASH };
+
+ // Test if the driver behaves as expected when given corrupted cache or token.
+ // The modifier will be invoked after save to cache but before prepare from cache.
+ // The modifier accepts one pointer argument "skip" as the returning value, indicating
+ // whether the test should be skipped or not.
+ void testCorruptedCache(ExpectedResult expected, std::function<void(bool*)> modifier) {
+ const TestModel& testModel = createTestModel();
+ const Model model = createModel(testModel);
+ if (checkEarlyTermination(model)) return;
+
+ // Save the compilation to cache.
+ {
+ std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+ createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ saveModelToCache(model, modelCache, dataCache);
+ }
+
+ bool skip = false;
+ modifier(&skip);
+ if (skip) return;
+
+ // Retrieve preparedModel from cache.
+ {
+ std::shared_ptr<IPreparedModel> preparedModel = nullptr;
+ ErrorStatus status;
+ std::vector<ndk::ScopedFileDescriptor> modelCache, dataCache;
+ createCacheFds(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheFds(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+
+ switch (expected) {
+ case ExpectedResult::GENERAL_FAILURE:
+ ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE);
+ ASSERT_EQ(preparedModel, nullptr);
+ break;
+ case ExpectedResult::NOT_CRASH:
+ ASSERT_EQ(preparedModel == nullptr, status != ErrorStatus::NONE);
+ break;
+ default:
+ FAIL();
+ }
+ }
+ }
+
+ const uint32_t kSeed = std::get<uint32_t>(GetParam());
+ std::mt19937 generator;
+};
+
+TEST_P(CompilationCachingSecurityTest, CorruptedModelCache) {
+ if (!mIsCachingSupported) return;
+ for (uint32_t i = 0; i < mNumModelCache; i++) {
+ testCorruptedCache(ExpectedResult::GENERAL_FAILURE,
+ [this, i](bool* skip) { flipOneBitOfCache(mModelCache[i], skip); });
+ }
+}
+
+TEST_P(CompilationCachingSecurityTest, WrongLengthModelCache) {
+ if (!mIsCachingSupported) return;
+ for (uint32_t i = 0; i < mNumModelCache; i++) {
+ testCorruptedCache(ExpectedResult::GENERAL_FAILURE,
+ [this, i](bool* skip) { appendBytesToCache(mModelCache[i], skip); });
+ }
+}
+
+TEST_P(CompilationCachingSecurityTest, CorruptedDataCache) {
+ if (!mIsCachingSupported) return;
+ for (uint32_t i = 0; i < mNumDataCache; i++) {
+ testCorruptedCache(ExpectedResult::NOT_CRASH,
+ [this, i](bool* skip) { flipOneBitOfCache(mDataCache[i], skip); });
+ }
+}
+
+TEST_P(CompilationCachingSecurityTest, WrongLengthDataCache) {
+ if (!mIsCachingSupported) return;
+ for (uint32_t i = 0; i < mNumDataCache; i++) {
+ testCorruptedCache(ExpectedResult::NOT_CRASH,
+ [this, i](bool* skip) { appendBytesToCache(mDataCache[i], skip); });
+ }
+}
+
+TEST_P(CompilationCachingSecurityTest, WrongToken) {
+ if (!mIsCachingSupported) return;
+ testCorruptedCache(ExpectedResult::GENERAL_FAILURE, [this](bool* skip) {
+ // Randomly flip one single bit in mToken.
+ uint32_t ind =
+ getRandomInt(0u, static_cast<uint32_t>(IDevice::BYTE_SIZE_OF_CACHE_TOKEN) - 1);
+ mToken[ind] ^= (1U << getRandomInt(0, 7));
+ *skip = false;
+ });
+}
+
+std::string printCompilationCachingSecurityTest(
+ const testing::TestParamInfo<CompilationCachingSecurityTestParam>& info) {
+ const auto& [namedDevice, operandType, seed] = info.param;
+ const std::string type = (operandType == OperandType::TENSOR_FLOAT32 ? "float32" : "quant8");
+ return gtestCompliantName(getName(namedDevice) + "_" + type + "_" + std::to_string(seed));
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(CompilationCachingSecurityTest);
+INSTANTIATE_TEST_SUITE_P(TestCompilationCaching, CompilationCachingSecurityTest,
+ testing::Combine(testing::ValuesIn(getNamedDevices()), kOperandTypeChoices,
+ testing::Range(0U, 10U)),
+ printCompilationCachingSecurityTest);
+
+} // namespace aidl::android::hardware::neuralnetworks::vts::functional
diff --git a/neuralnetworks/aidl/vts/functional/GeneratedTestHarness.cpp b/neuralnetworks/aidl/vts/functional/GeneratedTestHarness.cpp
new file mode 100644
index 0000000..4beb828
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/GeneratedTestHarness.cpp
@@ -0,0 +1,925 @@
+/*
+ * Copyright (C) 2021 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 "GeneratedTestHarness.h"
+
+#include <aidl/android/hardware/neuralnetworks/ErrorStatus.h>
+#include <android-base/logging.h>
+#include <android/binder_auto_utils.h>
+#include <android/sync.h>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <chrono>
+#include <iostream>
+#include <iterator>
+#include <numeric>
+#include <vector>
+
+#include <MemoryUtils.h>
+#include <android/binder_status.h>
+#include <nnapi/Result.h>
+#include <nnapi/SharedMemory.h>
+#include <nnapi/Types.h>
+#include <nnapi/hal/aidl/Conversions.h>
+#include <nnapi/hal/aidl/Utils.h>
+
+#include "Callbacks.h"
+#include "TestHarness.h"
+#include "Utils.h"
+#include "VtsHalNeuralnetworks.h"
+
+namespace aidl::android::hardware::neuralnetworks::vts::functional {
+
+namespace nn = ::android::nn;
+using namespace test_helper;
+using implementation::PreparedModelCallback;
+
+namespace {
+
+enum class OutputType { FULLY_SPECIFIED, UNSPECIFIED, INSUFFICIENT, MISSED_DEADLINE };
+
+struct TestConfig {
+ Executor executor;
+ bool measureTiming;
+ OutputType outputType;
+ MemoryType memoryType;
+ // `reportSkipping` indicates if a test should print an info message in case
+ // it is skipped. The field is set to true by default and is set to false in
+ // quantization coupling tests to suppress skipping a test
+ bool reportSkipping;
+ TestConfig(Executor executor, bool measureTiming, OutputType outputType, MemoryType memoryType)
+ : executor(executor),
+ measureTiming(measureTiming),
+ outputType(outputType),
+ memoryType(memoryType),
+ reportSkipping(true) {}
+ TestConfig(Executor executor, bool measureTiming, OutputType outputType, MemoryType memoryType,
+ bool reportSkipping)
+ : executor(executor),
+ measureTiming(measureTiming),
+ outputType(outputType),
+ memoryType(memoryType),
+ reportSkipping(reportSkipping) {}
+};
+
+enum class IOType { INPUT, OUTPUT };
+
+class DeviceMemoryAllocator {
+ public:
+ DeviceMemoryAllocator(const std::shared_ptr<IDevice>& device,
+ const std::shared_ptr<IPreparedModel>& preparedModel,
+ const TestModel& testModel)
+ : kDevice(device), kPreparedModel(preparedModel), kTestModel(testModel) {}
+
+ // Allocate device memory for a target input/output operand.
+ // Return {IBuffer object, token} if successful.
+ // Return {nullptr, 0} if device memory is not supported.
+ template <IOType ioType>
+ std::pair<std::shared_ptr<IBuffer>, int32_t> allocate(uint32_t index) {
+ std::pair<std::shared_ptr<IBuffer>, int32_t> buffer;
+ allocateInternal<ioType>(index, &buffer);
+ return buffer;
+ }
+
+ private:
+ template <IOType ioType>
+ void allocateInternal(int32_t index, std::pair<std::shared_ptr<IBuffer>, int32_t>* result) {
+ ASSERT_NE(result, nullptr);
+
+ // Prepare arguments.
+ BufferRole role = {.modelIndex = 0, .ioIndex = index, .frequency = 1.0f};
+ std::vector<BufferRole> inputRoles, outputRoles;
+ if constexpr (ioType == IOType::INPUT) {
+ inputRoles = {role};
+ } else {
+ outputRoles = {role};
+ }
+
+ // Allocate device memory.
+ DeviceBuffer buffer;
+ IPreparedModelParcel parcel;
+ parcel.preparedModel = kPreparedModel;
+ const auto ret = kDevice->allocate({}, {parcel}, inputRoles, outputRoles, &buffer);
+
+ // Check allocation results.
+ if (ret.isOk()) {
+ ASSERT_NE(buffer.buffer, nullptr);
+ ASSERT_GT(buffer.token, 0);
+ } else {
+ ASSERT_EQ(ret.getExceptionCode(), EX_SERVICE_SPECIFIC);
+ ASSERT_EQ(static_cast<ErrorStatus>(ret.getServiceSpecificError()),
+ ErrorStatus::GENERAL_FAILURE);
+ buffer.buffer = nullptr;
+ buffer.token = 0;
+ }
+
+ // Initialize input data from TestBuffer.
+ if constexpr (ioType == IOType::INPUT) {
+ if (buffer.buffer != nullptr) {
+ // TestBuffer -> Shared memory.
+ const auto& testBuffer =
+ kTestModel.main.operands[kTestModel.main.inputIndexes[index]].data;
+ ASSERT_GT(testBuffer.size(), 0);
+ const auto sharedMemory = nn::createSharedMemory(testBuffer.size()).value();
+ const auto memory = utils::convert(sharedMemory).value();
+ const auto mapping = nn::map(sharedMemory).value();
+ uint8_t* inputPtr = static_cast<uint8_t*>(std::get<void*>(mapping.pointer));
+ ASSERT_NE(inputPtr, nullptr);
+ const uint8_t* begin = testBuffer.get<uint8_t>();
+ const uint8_t* end = begin + testBuffer.size();
+ std::copy(begin, end, inputPtr);
+
+ // Shared memory -> IBuffer.
+ auto ret = buffer.buffer->copyFrom(memory, {});
+ ASSERT_TRUE(ret.isOk());
+ }
+ }
+ *result = {std::move(buffer.buffer), buffer.token};
+ }
+
+ const std::shared_ptr<IDevice> kDevice;
+ const std::shared_ptr<IPreparedModel> kPreparedModel;
+ const TestModel& kTestModel;
+};
+
+Subgraph createSubgraph(const TestSubgraph& testSubgraph, uint32_t* constCopySize,
+ std::vector<const TestBuffer*>* constCopies, uint32_t* constRefSize,
+ std::vector<const TestBuffer*>* constReferences) {
+ CHECK(constCopySize != nullptr);
+ CHECK(constCopies != nullptr);
+ CHECK(constRefSize != nullptr);
+ CHECK(constReferences != nullptr);
+
+ // Operands.
+ std::vector<Operand> operands(testSubgraph.operands.size());
+ for (uint32_t i = 0; i < testSubgraph.operands.size(); i++) {
+ const auto& op = testSubgraph.operands[i];
+
+ DataLocation loc = {};
+ if (op.lifetime == TestOperandLifeTime::CONSTANT_COPY) {
+ loc = {
+ .poolIndex = 0,
+ .offset = *constCopySize,
+ .length = static_cast<int64_t>(op.data.size()),
+ };
+ constCopies->push_back(&op.data);
+ *constCopySize += op.data.alignedSize();
+ } else if (op.lifetime == TestOperandLifeTime::CONSTANT_REFERENCE) {
+ loc = {
+ .poolIndex = 0,
+ .offset = *constRefSize,
+ .length = static_cast<int64_t>(op.data.size()),
+ };
+ constReferences->push_back(&op.data);
+ *constRefSize += op.data.alignedSize();
+ } else if (op.lifetime == TestOperandLifeTime::SUBGRAPH) {
+ loc = {
+ .poolIndex = 0,
+ .offset = *op.data.get<uint32_t>(),
+ .length = 0,
+ };
+ }
+
+ std::optional<OperandExtraParams> extraParams;
+ if (op.type == TestOperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL) {
+ using Tag = OperandExtraParams::Tag;
+ extraParams = OperandExtraParams::make<Tag::channelQuant>(SymmPerChannelQuantParams{
+ .scales = op.channelQuant.scales,
+ .channelDim = static_cast<int32_t>(op.channelQuant.channelDim)});
+ }
+
+ operands[i] = {.type = static_cast<OperandType>(op.type),
+ .dimensions = utils::toSigned(op.dimensions).value(),
+ .scale = op.scale,
+ .zeroPoint = op.zeroPoint,
+ .lifetime = static_cast<OperandLifeTime>(op.lifetime),
+ .location = loc,
+ .extraParams = std::move(extraParams)};
+ }
+
+ // Operations.
+ std::vector<Operation> operations(testSubgraph.operations.size());
+ std::transform(testSubgraph.operations.begin(), testSubgraph.operations.end(),
+ operations.begin(), [](const TestOperation& op) -> Operation {
+ return {.type = static_cast<OperationType>(op.type),
+ .inputs = utils::toSigned(op.inputs).value(),
+ .outputs = utils::toSigned(op.outputs).value()};
+ });
+
+ return {.operands = std::move(operands),
+ .operations = std::move(operations),
+ .inputIndexes = utils::toSigned(testSubgraph.inputIndexes).value(),
+ .outputIndexes = utils::toSigned(testSubgraph.outputIndexes).value()};
+}
+
+void copyTestBuffers(const std::vector<const TestBuffer*>& buffers, uint8_t* output) {
+ uint32_t offset = 0;
+ for (const TestBuffer* buffer : buffers) {
+ const uint8_t* begin = buffer->get<uint8_t>();
+ const uint8_t* end = begin + buffer->size();
+ std::copy(begin, end, output + offset);
+ offset += buffer->alignedSize();
+ }
+}
+
+} // namespace
+
+void waitForSyncFence(int syncFd) {
+ constexpr int kInfiniteTimeout = -1;
+ ASSERT_GT(syncFd, 0);
+ int r = sync_wait(syncFd, kInfiniteTimeout);
+ ASSERT_GE(r, 0);
+}
+
+Model createModel(const TestModel& testModel) {
+ uint32_t constCopySize = 0;
+ uint32_t constRefSize = 0;
+ std::vector<const TestBuffer*> constCopies;
+ std::vector<const TestBuffer*> constReferences;
+
+ Subgraph mainSubgraph = createSubgraph(testModel.main, &constCopySize, &constCopies,
+ &constRefSize, &constReferences);
+ std::vector<Subgraph> refSubgraphs(testModel.referenced.size());
+ std::transform(testModel.referenced.begin(), testModel.referenced.end(), refSubgraphs.begin(),
+ [&constCopySize, &constCopies, &constRefSize,
+ &constReferences](const TestSubgraph& testSubgraph) {
+ return createSubgraph(testSubgraph, &constCopySize, &constCopies,
+ &constRefSize, &constReferences);
+ });
+
+ // Constant copies.
+ std::vector<uint8_t> operandValues(constCopySize);
+ copyTestBuffers(constCopies, operandValues.data());
+
+ // Shared memory.
+ std::vector<nn::SharedMemory> pools = {};
+ if (constRefSize > 0) {
+ const auto pool = nn::createSharedMemory(constRefSize).value();
+ pools.push_back(pool);
+
+ // load data
+ const auto mappedMemory = nn::map(pool).value();
+ uint8_t* mappedPtr = static_cast<uint8_t*>(std::get<void*>(mappedMemory.pointer));
+ CHECK(mappedPtr != nullptr);
+
+ copyTestBuffers(constReferences, mappedPtr);
+ }
+
+ std::vector<Memory> aidlPools;
+ aidlPools.reserve(pools.size());
+ for (auto& pool : pools) {
+ auto aidlPool = utils::convert(pool).value();
+ aidlPools.push_back(std::move(aidlPool));
+ }
+
+ return {.main = std::move(mainSubgraph),
+ .referenced = std::move(refSubgraphs),
+ .operandValues = std::move(operandValues),
+ .pools = std::move(aidlPools),
+ .relaxComputationFloat32toFloat16 = testModel.isRelaxed};
+}
+
+static bool isOutputSizeGreaterThanOne(const TestModel& testModel, uint32_t index) {
+ const auto byteSize = testModel.main.operands[testModel.main.outputIndexes[index]].data.size();
+ return byteSize > 1u;
+}
+
+static void makeOutputInsufficientSize(uint32_t outputIndex, Request* request) {
+ auto& length = request->outputs[outputIndex].location.length;
+ ASSERT_GT(length, 1u);
+ length -= 1u;
+}
+
+static void makeOutputDimensionsUnspecified(Model* model) {
+ for (auto i : model->main.outputIndexes) {
+ auto& dims = model->main.operands[i].dimensions;
+ std::fill(dims.begin(), dims.end(), 0);
+ }
+}
+
+// Manages the lifetime of memory resources used in an execution.
+class ExecutionContext {
+ public:
+ ExecutionContext(std::shared_ptr<IDevice> device, std::shared_ptr<IPreparedModel> preparedModel)
+ : kDevice(std::move(device)), kPreparedModel(std::move(preparedModel)) {}
+
+ std::optional<Request> createRequest(const TestModel& testModel, MemoryType memoryType);
+ std::vector<TestBuffer> getOutputBuffers(const TestModel& testModel,
+ const Request& request) const;
+
+ private:
+ // Get a TestBuffer with data copied from an IBuffer object.
+ void getBuffer(const std::shared_ptr<IBuffer>& buffer, size_t size,
+ TestBuffer* testBuffer) const;
+
+ static constexpr uint32_t kInputPoolIndex = 0;
+ static constexpr uint32_t kOutputPoolIndex = 1;
+ static constexpr uint32_t kDeviceMemoryBeginIndex = 2;
+
+ const std::shared_ptr<IDevice> kDevice;
+ const std::shared_ptr<IPreparedModel> kPreparedModel;
+ std::unique_ptr<TestMemoryBase> mInputMemory, mOutputMemory;
+ std::vector<std::shared_ptr<IBuffer>> mBuffers;
+};
+
+std::optional<Request> ExecutionContext::createRequest(const TestModel& testModel,
+ MemoryType memoryType) {
+ // Memory pools are organized as:
+ // - 0: Input shared memory pool
+ // - 1: Output shared memory pool
+ // - [2, 2+i): Input device memories
+ // - [2+i, 2+i+o): Output device memories
+ DeviceMemoryAllocator allocator(kDevice, kPreparedModel, testModel);
+ std::vector<int32_t> tokens;
+ mBuffers.clear();
+
+ // Model inputs.
+ std::vector<RequestArgument> inputs(testModel.main.inputIndexes.size());
+ size_t inputSize = 0;
+ for (uint32_t i = 0; i < testModel.main.inputIndexes.size(); i++) {
+ const auto& op = testModel.main.operands[testModel.main.inputIndexes[i]];
+ if (op.data.size() == 0) {
+ // Omitted input.
+ inputs[i] = {.hasNoValue = true};
+ continue;
+ } else if (memoryType == MemoryType::DEVICE) {
+ SCOPED_TRACE("Input index = " + std::to_string(i));
+ auto [buffer, token] = allocator.allocate<IOType::INPUT>(i);
+ if (buffer != nullptr) {
+ DataLocation loc = {.poolIndex = static_cast<int32_t>(mBuffers.size() +
+ kDeviceMemoryBeginIndex)};
+ mBuffers.push_back(std::move(buffer));
+ tokens.push_back(token);
+ inputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}};
+ continue;
+ }
+ }
+
+ // Reserve shared memory for input.
+ DataLocation loc = {.poolIndex = kInputPoolIndex,
+ .offset = static_cast<int64_t>(inputSize),
+ .length = static_cast<int64_t>(op.data.size())};
+ inputSize += op.data.alignedSize();
+ inputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}};
+ }
+
+ // Model outputs.
+ std::vector<RequestArgument> outputs(testModel.main.outputIndexes.size());
+ size_t outputSize = 0;
+ for (uint32_t i = 0; i < testModel.main.outputIndexes.size(); i++) {
+ const auto& op = testModel.main.operands[testModel.main.outputIndexes[i]];
+ if (memoryType == MemoryType::DEVICE) {
+ SCOPED_TRACE("Output index = " + std::to_string(i));
+ auto [buffer, token] = allocator.allocate<IOType::OUTPUT>(i);
+ if (buffer != nullptr) {
+ DataLocation loc = {.poolIndex = static_cast<int32_t>(mBuffers.size() +
+ kDeviceMemoryBeginIndex)};
+ mBuffers.push_back(std::move(buffer));
+ tokens.push_back(token);
+ outputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}};
+ continue;
+ }
+ }
+
+ // In the case of zero-sized output, we should at least provide a one-byte buffer.
+ // This is because zero-sized tensors are only supported internally to the driver, or
+ // reported in output shapes. It is illegal for the client to pre-specify a zero-sized
+ // tensor as model output. Otherwise, we will have two semantic conflicts:
+ // - "Zero dimension" conflicts with "unspecified dimension".
+ // - "Omitted operand buffer" conflicts with "zero-sized operand buffer".
+ size_t bufferSize = std::max<size_t>(op.data.size(), 1);
+
+ // Reserve shared memory for output.
+ DataLocation loc = {.poolIndex = kOutputPoolIndex,
+ .offset = static_cast<int64_t>(outputSize),
+ .length = static_cast<int64_t>(bufferSize)};
+ outputSize += op.data.size() == 0 ? TestBuffer::kAlignment : op.data.alignedSize();
+ outputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}};
+ }
+
+ if (memoryType == MemoryType::DEVICE && mBuffers.empty()) {
+ return std::nullopt;
+ }
+
+ // Memory pools.
+ if (memoryType == MemoryType::BLOB_AHWB) {
+ mInputMemory = TestBlobAHWB::create(std::max<size_t>(inputSize, 1));
+ mOutputMemory = TestBlobAHWB::create(std::max<size_t>(outputSize, 1));
+ } else {
+ mInputMemory = TestAshmem::create(std::max<size_t>(inputSize, 1));
+ mOutputMemory = TestAshmem::create(std::max<size_t>(outputSize, 1));
+ }
+ CHECK_NE(mInputMemory, nullptr);
+ CHECK_NE(mOutputMemory, nullptr);
+ std::vector<RequestMemoryPool> pools;
+ pools.reserve(kDeviceMemoryBeginIndex + mBuffers.size());
+
+ auto copiedInputMemory = utils::clone(*mInputMemory->getAidlMemory());
+ CHECK(copiedInputMemory.has_value()) << copiedInputMemory.error().message;
+ auto copiedOutputMemory = utils::clone(*mOutputMemory->getAidlMemory());
+ CHECK(copiedOutputMemory.has_value()) << copiedOutputMemory.error().message;
+
+ pools.push_back(RequestMemoryPool::make<RequestMemoryPool::Tag::pool>(
+ std::move(copiedInputMemory).value()));
+ pools.push_back(RequestMemoryPool::make<RequestMemoryPool::Tag::pool>(
+ std::move(copiedOutputMemory).value()));
+ for (const auto& token : tokens) {
+ pools.push_back(RequestMemoryPool::make<RequestMemoryPool::Tag::token>(token));
+ }
+
+ // Copy input data to the input shared memory pool.
+ uint8_t* inputPtr = mInputMemory->getPointer();
+ for (uint32_t i = 0; i < testModel.main.inputIndexes.size(); i++) {
+ if (!inputs[i].hasNoValue && inputs[i].location.poolIndex == kInputPoolIndex) {
+ const auto& op = testModel.main.operands[testModel.main.inputIndexes[i]];
+ const uint8_t* begin = op.data.get<uint8_t>();
+ const uint8_t* end = begin + op.data.size();
+ std::copy(begin, end, inputPtr + inputs[i].location.offset);
+ }
+ }
+ return Request{
+ .inputs = std::move(inputs), .outputs = std::move(outputs), .pools = std::move(pools)};
+}
+
+std::vector<TestBuffer> ExecutionContext::getOutputBuffers(const TestModel& testModel,
+ const Request& request) const {
+ // Copy out output results.
+ uint8_t* outputPtr = mOutputMemory->getPointer();
+ std::vector<TestBuffer> outputBuffers;
+ for (uint32_t i = 0; i < request.outputs.size(); i++) {
+ const auto& outputLoc = request.outputs[i].location;
+ if (outputLoc.poolIndex == kOutputPoolIndex) {
+ outputBuffers.emplace_back(outputLoc.length, outputPtr + outputLoc.offset);
+ } else {
+ const auto& op = testModel.main.operands[testModel.main.outputIndexes[i]];
+ if (op.data.size() == 0) {
+ outputBuffers.emplace_back(0, nullptr);
+ } else {
+ SCOPED_TRACE("Output index = " + std::to_string(i));
+ const uint32_t bufferIndex = outputLoc.poolIndex - kDeviceMemoryBeginIndex;
+ TestBuffer buffer;
+ getBuffer(mBuffers[bufferIndex], op.data.size(), &buffer);
+ outputBuffers.push_back(std::move(buffer));
+ }
+ }
+ }
+ return outputBuffers;
+}
+
+// Get a TestBuffer with data copied from an IBuffer object.
+void ExecutionContext::getBuffer(const std::shared_ptr<IBuffer>& buffer, size_t size,
+ TestBuffer* testBuffer) const {
+ // IBuffer -> Shared memory.
+ auto sharedMemory = nn::createSharedMemory(size).value();
+ auto aidlMemory = utils::convert(sharedMemory).value();
+ const auto ret = buffer->copyTo(aidlMemory);
+ ASSERT_TRUE(ret.isOk());
+
+ // Shared memory -> TestBuffer.
+ const auto outputMemory = nn::map(sharedMemory).value();
+ const uint8_t* outputPtr = std::visit(
+ [](auto* ptr) { return static_cast<const uint8_t*>(ptr); }, outputMemory.pointer);
+ ASSERT_NE(outputPtr, nullptr);
+ ASSERT_NE(testBuffer, nullptr);
+ *testBuffer = TestBuffer(size, outputPtr);
+}
+
+static bool hasZeroSizedOutput(const TestModel& testModel) {
+ return std::any_of(testModel.main.outputIndexes.begin(), testModel.main.outputIndexes.end(),
+ [&testModel](uint32_t index) {
+ return testModel.main.operands[index].data.size() == 0;
+ });
+}
+
+void EvaluatePreparedModel(const std::shared_ptr<IDevice>& device,
+ const std::shared_ptr<IPreparedModel>& preparedModel,
+ const TestModel& testModel, const TestConfig& testConfig,
+ bool* skipped = nullptr) {
+ if (skipped != nullptr) {
+ *skipped = false;
+ }
+ // If output0 does not have size larger than one byte, we can not test with insufficient buffer.
+ if (testConfig.outputType == OutputType::INSUFFICIENT &&
+ !isOutputSizeGreaterThanOne(testModel, 0)) {
+ return;
+ }
+
+ ExecutionContext context(device, preparedModel);
+ auto maybeRequest = context.createRequest(testModel, testConfig.memoryType);
+ // Skip if testing memory domain but no device memory has been allocated.
+ if (!maybeRequest.has_value()) {
+ return;
+ }
+
+ Request request = std::move(maybeRequest).value();
+
+ constexpr uint32_t kInsufficientOutputIndex = 0;
+ if (testConfig.outputType == OutputType::INSUFFICIENT) {
+ makeOutputInsufficientSize(kInsufficientOutputIndex, &request);
+ }
+
+ int64_t loopTimeoutDuration = kOmittedTimeoutDuration;
+ // OutputType::MISSED_DEADLINE is only used by
+ // TestKind::INTINITE_LOOP_TIMEOUT tests to verify that an infinite loop is
+ // aborted after a timeout.
+ if (testConfig.outputType == OutputType::MISSED_DEADLINE) {
+ // Override the default loop timeout duration with a small value to
+ // speed up test execution.
+ constexpr int64_t kMillisecond = 1'000'000;
+ loopTimeoutDuration = 1 * kMillisecond;
+ }
+
+ ErrorStatus executionStatus;
+ std::vector<OutputShape> outputShapes;
+ Timing timing = kNoTiming;
+ switch (testConfig.executor) {
+ case Executor::SYNC: {
+ SCOPED_TRACE("synchronous");
+
+ ExecutionResult executionResult;
+ // execute
+ const auto ret = preparedModel->executeSynchronously(request, testConfig.measureTiming,
+ kNoDeadline, loopTimeoutDuration,
+ &executionResult);
+ ASSERT_TRUE(ret.isOk() || ret.getExceptionCode() == EX_SERVICE_SPECIFIC)
+ << ret.getDescription();
+ if (ret.isOk()) {
+ executionStatus = executionResult.outputSufficientSize
+ ? ErrorStatus::NONE
+ : ErrorStatus::OUTPUT_INSUFFICIENT_SIZE;
+ outputShapes = std::move(executionResult.outputShapes);
+ timing = executionResult.timing;
+ } else {
+ executionStatus = static_cast<ErrorStatus>(ret.getServiceSpecificError());
+ }
+ break;
+ }
+ case Executor::FENCED: {
+ SCOPED_TRACE("fenced");
+ ErrorStatus result = ErrorStatus::NONE;
+ ndk::ScopedFileDescriptor syncFenceFd;
+ std::shared_ptr<IFencedExecutionCallback> fencedCallback;
+ auto ret = preparedModel->executeFenced(request, {}, testConfig.measureTiming,
+ kNoDeadline, loopTimeoutDuration, kNoDuration,
+ &syncFenceFd, &fencedCallback);
+ ASSERT_TRUE(ret.isOk() || ret.getExceptionCode() == EX_SERVICE_SPECIFIC)
+ << ret.getDescription();
+ if (!ret.isOk()) {
+ result = static_cast<ErrorStatus>(ret.getServiceSpecificError());
+ executionStatus = result;
+ } else if (syncFenceFd.get() != -1) {
+ std::vector<ndk::ScopedFileDescriptor> waitFor;
+ auto dupFd = dup(syncFenceFd.get());
+ ASSERT_NE(dupFd, -1);
+ waitFor.emplace_back(dupFd);
+ // If a sync fence is returned, try start another run waiting for the sync fence.
+ ret = preparedModel->executeFenced(request, waitFor, testConfig.measureTiming,
+ kNoDeadline, loopTimeoutDuration, kNoDuration,
+ &syncFenceFd, &fencedCallback);
+ ASSERT_TRUE(ret.isOk());
+ waitForSyncFence(syncFenceFd.get());
+ }
+ if (result == ErrorStatus::NONE) {
+ ASSERT_NE(fencedCallback, nullptr);
+ Timing timingFenced;
+ auto ret =
+ fencedCallback->getExecutionInfo(&timing, &timingFenced, &executionStatus);
+ ASSERT_TRUE(ret.isOk());
+ }
+ break;
+ }
+ default: {
+ FAIL() << "Unsupported execution mode for AIDL interface.";
+ }
+ }
+
+ if (testConfig.outputType != OutputType::FULLY_SPECIFIED &&
+ executionStatus == ErrorStatus::GENERAL_FAILURE) {
+ if (skipped != nullptr) {
+ *skipped = true;
+ }
+ if (!testConfig.reportSkipping) {
+ return;
+ }
+ LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot "
+ "execute model that it does not support.";
+ std::cout << "[ ] Early termination of test because vendor service cannot "
+ "execute model that it does not support."
+ << std::endl;
+ GTEST_SKIP();
+ }
+ if (!testConfig.measureTiming) {
+ EXPECT_EQ(timing, kNoTiming);
+ } else {
+ if (timing.timeOnDevice != -1 && timing.timeInDriver != -1) {
+ EXPECT_LE(timing.timeOnDevice, timing.timeInDriver);
+ }
+ }
+
+ switch (testConfig.outputType) {
+ case OutputType::FULLY_SPECIFIED:
+ if (testConfig.executor == Executor::FENCED && hasZeroSizedOutput(testModel)) {
+ // Executor::FENCED does not support zero-sized output.
+ ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, executionStatus);
+ return;
+ }
+ // If the model output operands are fully specified, outputShapes must be either
+ // either empty, or have the same number of elements as the number of outputs.
+ ASSERT_EQ(ErrorStatus::NONE, executionStatus);
+ ASSERT_TRUE(outputShapes.size() == 0 ||
+ outputShapes.size() == testModel.main.outputIndexes.size());
+ break;
+ case OutputType::UNSPECIFIED:
+ if (testConfig.executor == Executor::FENCED) {
+ // For Executor::FENCED, the output shape must be fully specified.
+ ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, executionStatus);
+ return;
+ }
+ // If the model output operands are not fully specified, outputShapes must have
+ // the same number of elements as the number of outputs.
+ ASSERT_EQ(ErrorStatus::NONE, executionStatus);
+ ASSERT_EQ(outputShapes.size(), testModel.main.outputIndexes.size());
+ break;
+ case OutputType::INSUFFICIENT:
+ if (testConfig.executor == Executor::FENCED) {
+ // For Executor::FENCED, the output shape must be fully specified.
+ ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, executionStatus);
+ return;
+ }
+ ASSERT_EQ(ErrorStatus::OUTPUT_INSUFFICIENT_SIZE, executionStatus);
+ ASSERT_EQ(outputShapes.size(), testModel.main.outputIndexes.size());
+ // Check that all returned output dimensions are at least as fully specified as the
+ // union of the information about the corresponding operand in the model and in the
+ // request. In this test, all model outputs have known rank with all dimensions
+ // unspecified, and no dimensional information is provided in the request.
+ for (uint32_t i = 0; i < outputShapes.size(); i++) {
+ ASSERT_EQ(outputShapes[i].isSufficient, i != kInsufficientOutputIndex);
+ const auto& actual = outputShapes[i].dimensions;
+ const auto& golden =
+ testModel.main.operands[testModel.main.outputIndexes[i]].dimensions;
+ ASSERT_EQ(actual.size(), golden.size());
+ for (uint32_t j = 0; j < actual.size(); j++) {
+ if (actual[j] == 0) continue;
+ EXPECT_EQ(actual[j], golden[j]) << "index: " << j;
+ }
+ }
+ return;
+ case OutputType::MISSED_DEADLINE:
+ ASSERT_TRUE(executionStatus == ErrorStatus::MISSED_DEADLINE_TRANSIENT ||
+ executionStatus == ErrorStatus::MISSED_DEADLINE_PERSISTENT)
+ << "executionStatus = " << executionStatus;
+ return;
+ }
+
+ // Go through all outputs, check returned output shapes.
+ for (uint32_t i = 0; i < outputShapes.size(); i++) {
+ EXPECT_TRUE(outputShapes[i].isSufficient);
+ const auto& expect = testModel.main.operands[testModel.main.outputIndexes[i]].dimensions;
+ const auto unsignedActual = nn::toUnsigned(outputShapes[i].dimensions);
+ ASSERT_TRUE(unsignedActual.has_value());
+ const std::vector<uint32_t>& actual = unsignedActual.value();
+ EXPECT_EQ(expect, actual);
+ }
+
+ // Retrieve execution results.
+ const std::vector<TestBuffer> outputs = context.getOutputBuffers(testModel, request);
+
+ // We want "close-enough" results.
+ checkResults(testModel, outputs);
+}
+
+void EvaluatePreparedModel(const std::shared_ptr<IDevice>& device,
+ const std::shared_ptr<IPreparedModel>& preparedModel,
+ const TestModel& testModel, TestKind testKind) {
+ std::vector<OutputType> outputTypesList;
+ std::vector<bool> measureTimingList;
+ std::vector<Executor> executorList;
+ std::vector<MemoryType> memoryTypeList;
+
+ switch (testKind) {
+ case TestKind::GENERAL: {
+ outputTypesList = {OutputType::FULLY_SPECIFIED};
+ measureTimingList = {false, true};
+ executorList = {Executor::SYNC};
+ memoryTypeList = {MemoryType::ASHMEM};
+ } break;
+ case TestKind::DYNAMIC_SHAPE: {
+ outputTypesList = {OutputType::UNSPECIFIED, OutputType::INSUFFICIENT};
+ measureTimingList = {false, true};
+ executorList = {Executor::SYNC, Executor::FENCED};
+ memoryTypeList = {MemoryType::ASHMEM};
+ } break;
+ case TestKind::MEMORY_DOMAIN: {
+ outputTypesList = {OutputType::FULLY_SPECIFIED};
+ measureTimingList = {false};
+ executorList = {Executor::SYNC, Executor::FENCED};
+ memoryTypeList = {MemoryType::BLOB_AHWB, MemoryType::DEVICE};
+ } break;
+ case TestKind::FENCED_COMPUTE: {
+ outputTypesList = {OutputType::FULLY_SPECIFIED};
+ measureTimingList = {false, true};
+ executorList = {Executor::FENCED};
+ memoryTypeList = {MemoryType::ASHMEM};
+ } break;
+ case TestKind::QUANTIZATION_COUPLING: {
+ LOG(FATAL) << "Wrong TestKind for EvaluatePreparedModel";
+ return;
+ } break;
+ case TestKind::INTINITE_LOOP_TIMEOUT: {
+ outputTypesList = {OutputType::MISSED_DEADLINE};
+ measureTimingList = {false, true};
+ executorList = {Executor::SYNC, Executor::FENCED};
+ memoryTypeList = {MemoryType::ASHMEM};
+ } break;
+ }
+
+ for (const OutputType outputType : outputTypesList) {
+ for (const bool measureTiming : measureTimingList) {
+ for (const Executor executor : executorList) {
+ for (const MemoryType memoryType : memoryTypeList) {
+ const TestConfig testConfig(executor, measureTiming, outputType, memoryType);
+ EvaluatePreparedModel(device, preparedModel, testModel, testConfig);
+ }
+ }
+ }
+ }
+}
+
+void EvaluatePreparedCoupledModels(const std::shared_ptr<IDevice>& device,
+ const std::shared_ptr<IPreparedModel>& preparedModel,
+ const TestModel& testModel,
+ const std::shared_ptr<IPreparedModel>& preparedCoupledModel,
+ const TestModel& coupledModel) {
+ const std::vector<OutputType> outputTypesList = {OutputType::FULLY_SPECIFIED};
+ const std::vector<bool> measureTimingList = {false, true};
+ const std::vector<Executor> executorList = {Executor::SYNC, Executor::FENCED};
+
+ for (const OutputType outputType : outputTypesList) {
+ for (const bool measureTiming : measureTimingList) {
+ for (const Executor executor : executorList) {
+ const TestConfig testConfig(executor, measureTiming, outputType, MemoryType::ASHMEM,
+ /*reportSkipping=*/false);
+ bool baseSkipped = false;
+ EvaluatePreparedModel(device, preparedModel, testModel, testConfig, &baseSkipped);
+ bool coupledSkipped = false;
+ EvaluatePreparedModel(device, preparedCoupledModel, coupledModel, testConfig,
+ &coupledSkipped);
+ ASSERT_EQ(baseSkipped, coupledSkipped);
+ if (baseSkipped) {
+ LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot "
+ "execute model that it does not support.";
+ std::cout << "[ ] Early termination of test because vendor service "
+ "cannot "
+ "execute model that it does not support."
+ << std::endl;
+ GTEST_SKIP();
+ }
+ }
+ }
+ }
+}
+
+void Execute(const std::shared_ptr<IDevice>& device, const TestModel& testModel,
+ TestKind testKind) {
+ Model model = createModel(testModel);
+ if (testKind == TestKind::DYNAMIC_SHAPE) {
+ makeOutputDimensionsUnspecified(&model);
+ }
+
+ std::shared_ptr<IPreparedModel> preparedModel;
+ switch (testKind) {
+ case TestKind::GENERAL:
+ case TestKind::DYNAMIC_SHAPE:
+ case TestKind::MEMORY_DOMAIN:
+ case TestKind::FENCED_COMPUTE:
+ case TestKind::INTINITE_LOOP_TIMEOUT: {
+ createPreparedModel(device, model, &preparedModel);
+ if (preparedModel == nullptr) return;
+ EvaluatePreparedModel(device, preparedModel, testModel, testKind);
+ } break;
+ case TestKind::QUANTIZATION_COUPLING: {
+ ASSERT_TRUE(testModel.hasQuant8CoupledOperands());
+ createPreparedModel(device, model, &preparedModel,
+ /*reportSkipping*/ false);
+ TestModel signedQuantizedModel = convertQuant8AsymmOperandsToSigned(testModel);
+ std::shared_ptr<IPreparedModel> preparedCoupledModel;
+ createPreparedModel(device, createModel(signedQuantizedModel), &preparedCoupledModel,
+ /*reportSkipping*/ false);
+ // If we couldn't prepare a model with unsigned quantization, we must
+ // fail to prepare a model with signed quantization as well.
+ if (preparedModel == nullptr) {
+ ASSERT_EQ(preparedCoupledModel, nullptr);
+ // If we failed to prepare both of the models, we can safely skip
+ // the test.
+ LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot "
+ "prepare model that it does not support.";
+ std::cout
+ << "[ ] Early termination of test because vendor service cannot "
+ "prepare model that it does not support."
+ << std::endl;
+ GTEST_SKIP();
+ }
+ ASSERT_NE(preparedCoupledModel, nullptr);
+ EvaluatePreparedCoupledModels(device, preparedModel, testModel, preparedCoupledModel,
+ signedQuantizedModel);
+ } break;
+ }
+}
+
+void GeneratedTestBase::SetUp() {
+ testing::TestWithParam<GeneratedTestParam>::SetUp();
+ ASSERT_NE(kDevice, nullptr);
+}
+
+std::vector<NamedModel> getNamedModels(const FilterFn& filter) {
+ return TestModelManager::get().getTestModels(filter);
+}
+
+std::vector<NamedModel> getNamedModels(const FilterNameFn& filter) {
+ return TestModelManager::get().getTestModels(filter);
+}
+
+std::string printGeneratedTest(const testing::TestParamInfo<GeneratedTestParam>& info) {
+ const auto& [namedDevice, namedModel] = info.param;
+ return gtestCompliantName(getName(namedDevice) + "_" + getName(namedModel));
+}
+
+// Tag for the generated tests
+class GeneratedTest : public GeneratedTestBase {};
+
+// Tag for the dynamic output shape tests
+class DynamicOutputShapeTest : public GeneratedTest {};
+
+// Tag for the memory domain tests
+class MemoryDomainTest : public GeneratedTest {};
+
+// Tag for the fenced compute tests
+class FencedComputeTest : public GeneratedTest {};
+
+// Tag for the dynamic output shape tests
+class QuantizationCouplingTest : public GeneratedTest {};
+
+// Tag for the loop timeout tests
+class InfiniteLoopTimeoutTest : public GeneratedTest {};
+
+TEST_P(GeneratedTest, Test) {
+ Execute(kDevice, kTestModel, TestKind::GENERAL);
+}
+
+TEST_P(DynamicOutputShapeTest, Test) {
+ Execute(kDevice, kTestModel, TestKind::DYNAMIC_SHAPE);
+}
+
+TEST_P(MemoryDomainTest, Test) {
+ Execute(kDevice, kTestModel, TestKind::MEMORY_DOMAIN);
+}
+
+TEST_P(FencedComputeTest, Test) {
+ Execute(kDevice, kTestModel, TestKind::FENCED_COMPUTE);
+}
+
+TEST_P(QuantizationCouplingTest, Test) {
+ Execute(kDevice, kTestModel, TestKind::QUANTIZATION_COUPLING);
+}
+
+TEST_P(InfiniteLoopTimeoutTest, Test) {
+ Execute(kDevice, kTestModel, TestKind::INTINITE_LOOP_TIMEOUT);
+}
+
+INSTANTIATE_GENERATED_TEST(GeneratedTest,
+ [](const TestModel& testModel) { return !testModel.expectFailure; });
+
+INSTANTIATE_GENERATED_TEST(DynamicOutputShapeTest, [](const TestModel& testModel) {
+ return !testModel.expectFailure && !testModel.hasScalarOutputs();
+});
+
+INSTANTIATE_GENERATED_TEST(MemoryDomainTest,
+ [](const TestModel& testModel) { return !testModel.expectFailure; });
+
+INSTANTIATE_GENERATED_TEST(FencedComputeTest,
+ [](const TestModel& testModel) { return !testModel.expectFailure; });
+
+INSTANTIATE_GENERATED_TEST(QuantizationCouplingTest, [](const TestModel& testModel) {
+ return !testModel.expectFailure && testModel.hasQuant8CoupledOperands() &&
+ testModel.main.operations.size() == 1;
+});
+
+INSTANTIATE_GENERATED_TEST(InfiniteLoopTimeoutTest, [](const TestModel& testModel) {
+ return testModel.isInfiniteLoopTimeoutTest();
+});
+
+} // namespace aidl::android::hardware::neuralnetworks::vts::functional
diff --git a/neuralnetworks/aidl/vts/functional/GeneratedTestHarness.h b/neuralnetworks/aidl/vts/functional/GeneratedTestHarness.h
new file mode 100644
index 0000000..ad40f06
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/GeneratedTestHarness.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_NEURALNETWORKS_AIDL_GENERATED_TEST_HARNESS_H
+#define ANDROID_HARDWARE_NEURALNETWORKS_AIDL_GENERATED_TEST_HARNESS_H
+
+#include <functional>
+#include <vector>
+
+#include <TestHarness.h>
+#include "Utils.h"
+#include "VtsHalNeuralnetworks.h"
+
+namespace aidl::android::hardware::neuralnetworks::vts::functional {
+
+using NamedModel = Named<const test_helper::TestModel*>;
+using GeneratedTestParam = std::tuple<NamedDevice, NamedModel>;
+
+class GeneratedTestBase : public testing::TestWithParam<GeneratedTestParam> {
+ protected:
+ void SetUp() override;
+ const std::shared_ptr<IDevice> kDevice = getData(std::get<NamedDevice>(GetParam()));
+ const test_helper::TestModel& kTestModel = *getData(std::get<NamedModel>(GetParam()));
+};
+
+using FilterFn = std::function<bool(const test_helper::TestModel&)>;
+std::vector<NamedModel> getNamedModels(const FilterFn& filter);
+
+using FilterNameFn = std::function<bool(const std::string&)>;
+std::vector<NamedModel> getNamedModels(const FilterNameFn& filter);
+
+std::string printGeneratedTest(const testing::TestParamInfo<GeneratedTestParam>& info);
+
+#define INSTANTIATE_GENERATED_TEST(TestSuite, filter) \
+ GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(TestSuite); \
+ INSTANTIATE_TEST_SUITE_P(TestGenerated, TestSuite, \
+ testing::Combine(testing::ValuesIn(getNamedDevices()), \
+ testing::ValuesIn(getNamedModels(filter))), \
+ printGeneratedTest)
+
+// Tag for the validation tests, instantiated in VtsHalNeuralnetworks.cpp.
+// TODO: Clean up the hierarchy for ValidationTest.
+class ValidationTest : public GeneratedTestBase {};
+
+Model createModel(const test_helper::TestModel& testModel);
+
+void PrepareModel(const std::shared_ptr<IDevice>& device, const Model& model,
+ std::shared_ptr<IPreparedModel>* preparedModel);
+
+enum class TestKind {
+ // Runs a test model and compares the results to a golden data
+ GENERAL,
+ // Same as GENERAL but sets dimensions for the output tensors to zeros
+ DYNAMIC_SHAPE,
+ // Same as GENERAL but use device memories for inputs and outputs
+ MEMORY_DOMAIN,
+ // Same as GENERAL but use executeFenced for exeuction
+ FENCED_COMPUTE,
+ // Tests if quantized model with TENSOR_QUANT8_ASYMM produces the same result
+ // (OK/SKIPPED/FAILED) as the model with all such tensors converted to
+ // TENSOR_QUANT8_ASYMM_SIGNED.
+ QUANTIZATION_COUPLING,
+ // Runs a test model and verifies that MISSED_DEADLINE_* is returned.
+ INTINITE_LOOP_TIMEOUT
+};
+
+void EvaluatePreparedModel(const std::shared_ptr<IDevice>& device,
+ const std::shared_ptr<IPreparedModel>& preparedModel,
+ const test_helper::TestModel& testModel, TestKind testKind);
+
+void waitForSyncFence(int syncFd);
+
+} // namespace aidl::android::hardware::neuralnetworks::vts::functional
+
+#endif // ANDROID_HARDWARE_NEURALNETWORKS_AIDL_GENERATED_TEST_HARNESS_H
diff --git a/neuralnetworks/aidl/vts/functional/LogTestCaseToLogcat.h b/neuralnetworks/aidl/vts/functional/LogTestCaseToLogcat.h
new file mode 100644
index 0000000..c9fd432
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/LogTestCaseToLogcat.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_NEURALNETWORKS_AIDL_LOG_TEST_CASE_TO_LOGCAT_H
+#define ANDROID_HARDWARE_NEURALNETWORKS_AIDL_LOG_TEST_CASE_TO_LOGCAT_H
+
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+
+namespace aidl::android::hardware::neuralnetworks {
+
+class LogTestCaseToLogcat : public ::testing::EmptyTestEventListener {
+ public:
+ void OnTestStart(const ::testing::TestInfo& test_info) override {
+ LOG(INFO) << "[Test Case] " << test_info.test_suite_name() << "." << test_info.name()
+ << " BEGIN";
+ }
+
+ void OnTestEnd(const ::testing::TestInfo& test_info) override {
+ LOG(INFO) << "[Test Case] " << test_info.test_suite_name() << "." << test_info.name()
+ << " END";
+ }
+};
+
+} // namespace aidl::android::hardware::neuralnetworks
+
+#endif // ANDROID_HARDWARE_NEURALNETWORKS_AIDL_LOG_TEST_CASE_TO_LOGCAT_H
diff --git a/neuralnetworks/aidl/vts/functional/MemoryDomainTests.cpp b/neuralnetworks/aidl/vts/functional/MemoryDomainTests.cpp
new file mode 100644
index 0000000..a37a0ca
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/MemoryDomainTests.cpp
@@ -0,0 +1,1176 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "neuralnetworks_aidl_hal_test"
+
+#include <android-base/logging.h>
+#include <android/binder_auto_utils.h>
+#include <android/binder_interface_utils.h>
+#include <android/binder_status.h>
+#include <gtest/gtest.h>
+
+#include <LegacyUtils.h>
+#include <TestHarness.h>
+#include <Utils.h>
+#include <nnapi/SharedMemory.h>
+#include <nnapi/hal/aidl/Conversions.h>
+#include <nnapi/hal/aidl/Utils.h>
+
+#include "AidlHalInterfaces.h"
+#include "Callbacks.h"
+#include "GeneratedTestHarness.h"
+#include "MemoryUtils.h"
+#include "Utils.h"
+#include "VtsHalNeuralnetworks.h"
+
+namespace aidl::android::hardware::neuralnetworks::vts::functional {
+
+using namespace test_helper;
+using implementation::PreparedModelCallback;
+
+namespace {
+
+// An AIDL driver is likely to support at least one of the following operand types.
+const std::vector<TestOperandType> kTestOperandTypeChoicesVector = {
+ TestOperandType::TENSOR_FLOAT32,
+ TestOperandType::TENSOR_FLOAT16,
+ TestOperandType::TENSOR_QUANT8_ASYMM,
+ TestOperandType::TENSOR_QUANT8_ASYMM_SIGNED,
+};
+const auto kTestOperandTypeChoices = testing::ValuesIn(kTestOperandTypeChoicesVector);
+// TODO(b/179270601): restore kNamedDeviceChoices
+
+bool isInChoices(TestOperandType type) {
+ return std::count(kTestOperandTypeChoicesVector.begin(), kTestOperandTypeChoicesVector.end(),
+ type) > 0;
+}
+
+bool isFloat(TestOperandType type) {
+ CHECK(isInChoices(type));
+ return type == TestOperandType::TENSOR_FLOAT32 || type == TestOperandType::TENSOR_FLOAT16;
+}
+
+// Create placeholder buffers for model constants as well as inputs and outputs.
+// We only care about the size here because we will not check accuracy in validation tests.
+void createDummyData(TestModel* testModel) {
+ for (auto& operand : testModel->main.operands) {
+ if (operand.data != nullptr) continue;
+ switch (operand.lifetime) {
+ case TestOperandLifeTime::SUBGRAPH_INPUT:
+ case TestOperandLifeTime::SUBGRAPH_OUTPUT:
+ case TestOperandLifeTime::CONSTANT_COPY:
+ case TestOperandLifeTime::CONSTANT_REFERENCE: {
+ const uint32_t size = nn::nonExtensionOperandSizeOfData(
+ static_cast<nn::OperandType>(operand.type), operand.dimensions);
+ operand.data = TestBuffer(size);
+ } break;
+ default:
+ break;
+ }
+ }
+}
+
+TestOperand createInt32Scalar(int32_t value) {
+ return {
+ .type = TestOperandType::INT32,
+ .dimensions = {},
+ .numberOfConsumers = 1,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = TestOperandLifeTime::CONSTANT_COPY,
+ .data = TestBuffer::createFromVector<int32_t>({value}),
+ };
+}
+
+// Construct a test model with multiple CONV_2D operations with the given operand as inputs.
+// The dimensions of the filters are chosen to ensure outputs has the same dimensions as inputs.
+// We choose CONV_2D operation because it is commonly supported by most drivers.
+TestModel createConvModel(const TestOperand& operand, uint32_t numOperations) {
+ CHECK(isInChoices(operand.type));
+
+ TestOperand weight = {.type = operand.type,
+ .dimensions = {operand.dimensions[3], 3, 3, operand.dimensions[3]},
+ .numberOfConsumers = 1,
+ .scale = isFloat(operand.type) ? 0.0f : 1.0f,
+ .zeroPoint = 0,
+ .lifetime = TestOperandLifeTime::CONSTANT_COPY};
+
+ TestOperand bias = {
+ .type = isFloat(operand.type) ? operand.type : TestOperandType::TENSOR_INT32,
+ .dimensions = {operand.dimensions[3]},
+ .numberOfConsumers = 1,
+ .scale = operand.scale * weight.scale,
+ .zeroPoint = 0,
+ .lifetime = TestOperandLifeTime::CONSTANT_COPY};
+
+ TestOperand output = operand;
+ output.numberOfConsumers = 0;
+ output.lifetime = TestOperandLifeTime::SUBGRAPH_OUTPUT;
+
+ const std::vector<TestOperand> operands = {
+ operand,
+ std::move(weight),
+ std::move(bias),
+ createInt32Scalar(1), // same padding
+ createInt32Scalar(1), // width stride
+ createInt32Scalar(1), // height stride
+ createInt32Scalar(0), // activation = NONE
+ std::move(output),
+ };
+
+ TestModel model;
+ for (uint32_t i = 0; i < numOperations; i++) {
+ model.main.operands.insert(model.main.operands.end(), operands.begin(), operands.end());
+ const uint32_t inputIndex = operands.size() * i;
+ const uint32_t outputIndex = inputIndex + operands.size() - 1;
+ std::vector<uint32_t> inputs(operands.size() - 1);
+ std::iota(inputs.begin(), inputs.end(), inputIndex);
+ model.main.operations.push_back({.type = TestOperationType::CONV_2D,
+ .inputs = std::move(inputs),
+ .outputs = {outputIndex}});
+ model.main.inputIndexes.push_back(inputIndex);
+ model.main.outputIndexes.push_back(outputIndex);
+ }
+ createDummyData(&model);
+ return model;
+}
+
+// Construct a test model with a single ADD operation with the given operand as input0 and input1.
+// This is to cover additional cases that the CONV_2D model does not support, e.g. arbitrary input
+// operand rank, scalar input operand. We choose ADD operation because it is commonly supported by
+// most drivers.
+TestModel createSingleAddModel(const TestOperand& operand) {
+ CHECK(isInChoices(operand.type));
+
+ TestOperand act = {
+ .type = TestOperandType::INT32,
+ .dimensions = {},
+ .numberOfConsumers = 1,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = TestOperandLifeTime::SUBGRAPH_INPUT,
+ };
+
+ TestOperand output = operand;
+ output.numberOfConsumers = 0;
+ output.lifetime = TestOperandLifeTime::SUBGRAPH_OUTPUT;
+
+ TestModel model = {
+ .main =
+ {
+ .operands =
+ {
+ operand,
+ operand,
+ std::move(act),
+ output,
+ },
+ .operations = {{.type = TestOperationType::ADD,
+ .inputs = {0, 1, 2},
+ .outputs = {3}}},
+ .inputIndexes = {0, 1, 2},
+ .outputIndexes = {3},
+ },
+ };
+ createDummyData(&model);
+ return model;
+}
+
+// A placeholder invalid IPreparedModel class for MemoryDomainAllocateTest.InvalidPreparedModel
+class InvalidPreparedModel : public BnPreparedModel {
+ public:
+ ndk::ScopedAStatus executeSynchronously(const Request&, bool, int64_t, int64_t,
+ ExecutionResult*) override {
+ return ndk::ScopedAStatus::fromServiceSpecificError(
+ static_cast<int32_t>(ErrorStatus::GENERAL_FAILURE));
+ }
+ ndk::ScopedAStatus executeFenced(const Request&, const std::vector<ndk::ScopedFileDescriptor>&,
+ bool, int64_t, int64_t, int64_t, ndk::ScopedFileDescriptor*,
+ std::shared_ptr<IFencedExecutionCallback>*) override {
+ return ndk::ScopedAStatus::fromServiceSpecificError(
+ static_cast<int32_t>(ErrorStatus::GENERAL_FAILURE));
+ }
+};
+
+template <typename... Args>
+std::vector<RequestMemoryPool> createRequestMemoryPools(const Args&... pools) {
+ std::vector<RequestMemoryPool> memoryPools;
+ memoryPools.reserve(sizeof...(Args));
+ // This fold operator calls push_back on each of the function arguments.
+ (memoryPools.push_back(utils::clone(pools).value()), ...);
+ return memoryPools;
+};
+
+} // namespace
+
+class MemoryDomainTestBase : public testing::Test {
+ protected:
+ MemoryDomainTestBase(std::shared_ptr<IDevice> device, TestOperandType type)
+ : kDevice(std::move(device)),
+ kTestOperandType(type),
+ kTestOperand(kTestOperandMap.at(type)),
+ kTestOperandDataSize(nn::nonExtensionOperandSizeOfData(static_cast<nn::OperandType>(type),
+ kTestOperand.dimensions)) {}
+
+ void SetUp() override {
+ testing::Test::SetUp();
+ ASSERT_NE(kDevice, nullptr);
+ }
+
+ std::shared_ptr<IPreparedModel> createConvPreparedModel(const TestOperand& testOperand,
+ uint32_t numOperations = 1) {
+ const TestModel testModel = createConvModel(testOperand, numOperations);
+ const Model model = createModel(testModel);
+ std::shared_ptr<IPreparedModel> preparedModel;
+ createPreparedModel(kDevice, model, &preparedModel, /*reportSkipping=*/false);
+ return preparedModel;
+ }
+
+ std::shared_ptr<IPreparedModel> createAddPreparedModel(const TestOperand& testOperand) {
+ const TestModel testModel = createSingleAddModel(testOperand);
+ const Model model = createModel(testModel);
+ std::shared_ptr<IPreparedModel> preparedModel;
+ createPreparedModel(kDevice, model, &preparedModel, /*reportSkipping=*/false);
+ return preparedModel;
+ }
+
+ static const std::map<TestOperandType, TestOperand> kTestOperandMap;
+
+ const std::shared_ptr<IDevice> kDevice;
+ const TestOperandType kTestOperandType;
+ const TestOperand& kTestOperand;
+ const uint32_t kTestOperandDataSize;
+};
+
+const std::map<TestOperandType, TestOperand> MemoryDomainTestBase::kTestOperandMap = {
+ {TestOperandType::TENSOR_FLOAT32,
+ {
+ .type = TestOperandType::TENSOR_FLOAT32,
+ .dimensions = {1, 32, 32, 8},
+ .numberOfConsumers = 1,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = TestOperandLifeTime::SUBGRAPH_INPUT,
+ }},
+ {TestOperandType::TENSOR_FLOAT16,
+ {
+ .type = TestOperandType::TENSOR_FLOAT16,
+ .dimensions = {1, 32, 32, 8},
+ .numberOfConsumers = 1,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = TestOperandLifeTime::SUBGRAPH_INPUT,
+ }},
+ {TestOperandType::TENSOR_QUANT8_ASYMM,
+ {
+ .type = TestOperandType::TENSOR_QUANT8_ASYMM,
+ .dimensions = {1, 32, 32, 8},
+ .numberOfConsumers = 1,
+ .scale = 0.5f,
+ .zeroPoint = 0,
+ .lifetime = TestOperandLifeTime::SUBGRAPH_INPUT,
+ }},
+ {TestOperandType::TENSOR_QUANT8_ASYMM_SIGNED,
+ {
+ .type = TestOperandType::TENSOR_QUANT8_ASYMM_SIGNED,
+ .dimensions = {1, 32, 32, 8},
+ .numberOfConsumers = 1,
+ .scale = 0.5f,
+ .zeroPoint = 0,
+ .lifetime = TestOperandLifeTime::SUBGRAPH_INPUT,
+ }},
+};
+
+using MemoryDomainAllocateTestParam = std::tuple<NamedDevice, TestOperandType>;
+class MemoryDomainAllocateTest : public MemoryDomainTestBase,
+ public testing::WithParamInterface<MemoryDomainAllocateTestParam> {
+ protected:
+ MemoryDomainAllocateTest()
+ : MemoryDomainTestBase(getData(std::get<NamedDevice>(GetParam())),
+ std::get<TestOperandType>(GetParam())) {}
+
+ struct AllocateTestArgs {
+ std::vector<int32_t> dimensions;
+ std::vector<std::shared_ptr<IPreparedModel>> preparedModels;
+ std::vector<BufferRole> inputRoles;
+ std::vector<BufferRole> outputRoles;
+ };
+
+ // Validation test for IDevice::allocate. The driver is expected to fail with INVALID_ARGUMENT,
+ // or GENERAL_FAILURE if memory domain is not supported.
+ void validateAllocate(AllocateTestArgs args) {
+ std::vector<IPreparedModelParcel> preparedModelParcels;
+ preparedModelParcels.reserve(args.preparedModels.size());
+ for (const auto& model : args.preparedModels) {
+ preparedModelParcels.push_back({.preparedModel = model});
+ }
+ DeviceBuffer buffer;
+ const auto ret =
+ kDevice->allocate({.dimensions = std::move(args.dimensions)}, preparedModelParcels,
+ args.inputRoles, args.outputRoles, &buffer);
+
+ ASSERT_EQ(ret.getExceptionCode(), EX_SERVICE_SPECIFIC);
+ ASSERT_TRUE(static_cast<ErrorStatus>(ret.getServiceSpecificError()) ==
+ ErrorStatus::INVALID_ARGUMENT ||
+ static_cast<ErrorStatus>(ret.getServiceSpecificError()) ==
+ ErrorStatus::GENERAL_FAILURE);
+ }
+
+ void testConflictOperands(const std::shared_ptr<IPreparedModel>& model1,
+ const std::shared_ptr<IPreparedModel>& model2) {
+ validateAllocate({
+ .preparedModels = {model1, model2},
+ .inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f},
+ {.modelIndex = 1, .ioIndex = 0, .frequency = 1.0f}},
+ });
+ validateAllocate({
+ .preparedModels = {model1, model2},
+ .inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}},
+ .outputRoles = {{.modelIndex = 1, .ioIndex = 0, .frequency = 1.0f}},
+ });
+ validateAllocate({
+ .preparedModels = {model1, model2},
+ .outputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f},
+ {.modelIndex = 1, .ioIndex = 0, .frequency = 1.0f}},
+ });
+ }
+};
+
+TEST_P(MemoryDomainAllocateTest, EmptyRole) {
+ // Test with empty prepared models and roles.
+ validateAllocate({});
+
+ auto preparedModel = createConvPreparedModel(kTestOperand);
+ if (preparedModel == nullptr) return;
+
+ // Test again with non-empty prepared models but empty roles.
+ validateAllocate({
+ .preparedModels = {preparedModel},
+ });
+}
+
+TEST_P(MemoryDomainAllocateTest, NullptrPreparedModel) {
+ // Test with nullptr prepared model as input role.
+ validateAllocate({
+ .preparedModels = {nullptr},
+ .inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}},
+ });
+
+ // Test with nullptr prepared model as output role.
+ validateAllocate({
+ .preparedModels = {nullptr},
+ .outputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}},
+ });
+}
+
+TEST_P(MemoryDomainAllocateTest, InvalidPreparedModel) {
+ std::shared_ptr<InvalidPreparedModel> invalidPreparedModel =
+ ndk::SharedRefBase::make<InvalidPreparedModel>();
+
+ // Test with invalid prepared model as input role.
+ validateAllocate({
+ .preparedModels = {invalidPreparedModel},
+ .inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}},
+ });
+
+ // Test with invalid prepared model as output role.
+ validateAllocate({
+ .preparedModels = {invalidPreparedModel},
+ .outputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}},
+ });
+}
+
+TEST_P(MemoryDomainAllocateTest, InvalidModelIndex) {
+ auto preparedModel = createConvPreparedModel(kTestOperand);
+ if (preparedModel == nullptr) return;
+
+ // This should fail, because the model index is out of bound.
+ validateAllocate({
+ .preparedModels = {preparedModel},
+ .inputRoles = {{.modelIndex = 1, .ioIndex = 0, .frequency = 1.0f}},
+ });
+
+ // This should fail, because the model index is out of bound.
+ validateAllocate({
+ .preparedModels = {preparedModel},
+ .outputRoles = {{.modelIndex = 1, .ioIndex = 0, .frequency = 1.0f}},
+ });
+}
+
+TEST_P(MemoryDomainAllocateTest, InvalidIOIndex) {
+ auto preparedModel = createConvPreparedModel(kTestOperand);
+ if (preparedModel == nullptr) return;
+
+ // This should fail, because the model only has one input.
+ validateAllocate({
+ .preparedModels = {preparedModel},
+ .inputRoles = {{.modelIndex = 0, .ioIndex = 1, .frequency = 1.0f}},
+ });
+
+ // This should fail, because the model only has one output.
+ validateAllocate({
+ .preparedModels = {preparedModel},
+ .outputRoles = {{.modelIndex = 0, .ioIndex = 1, .frequency = 1.0f}},
+ });
+}
+
+TEST_P(MemoryDomainAllocateTest, InvalidFrequency) {
+ auto preparedModel = createConvPreparedModel(kTestOperand);
+ if (preparedModel == nullptr) return;
+
+ for (float invalidFreq : {10.0f, 0.0f, -0.5f}) {
+ // Test with invalid frequency for input roles.
+ validateAllocate({
+ .preparedModels = {preparedModel},
+ .inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = invalidFreq}},
+ });
+ // Test with invalid frequency for output roles.
+ validateAllocate({
+ .preparedModels = {preparedModel},
+ .outputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = invalidFreq}},
+ });
+ }
+}
+
+TEST_P(MemoryDomainAllocateTest, SameRoleSpecifiedTwice) {
+ auto preparedModel = createConvPreparedModel(kTestOperand);
+ if (preparedModel == nullptr) return;
+
+ // Same role with same model index.
+ validateAllocate({
+ .preparedModels = {preparedModel},
+ .inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f},
+ {.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}},
+ });
+ validateAllocate({
+ .preparedModels = {preparedModel},
+ .outputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f},
+ {.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}},
+ });
+
+ // Different model indexes, but logically referring to the same role.
+ validateAllocate({
+ .preparedModels = {preparedModel, preparedModel},
+ .inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f},
+ {.modelIndex = 1, .ioIndex = 0, .frequency = 1.0f}},
+ });
+ validateAllocate({
+ .preparedModels = {preparedModel, preparedModel},
+ .outputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f},
+ {.modelIndex = 1, .ioIndex = 0, .frequency = 1.0f}},
+ });
+}
+
+TEST_P(MemoryDomainAllocateTest, ConflictOperandType) {
+ const std::map<TestOperandType, TestOperandType> conflictTypeMap = {
+ {TestOperandType::TENSOR_FLOAT32, TestOperandType::TENSOR_FLOAT16},
+ {TestOperandType::TENSOR_FLOAT16, TestOperandType::TENSOR_FLOAT32},
+ {TestOperandType::TENSOR_QUANT8_ASYMM, TestOperandType::TENSOR_QUANT8_ASYMM_SIGNED},
+ {TestOperandType::TENSOR_QUANT8_ASYMM_SIGNED, TestOperandType::TENSOR_QUANT8_ASYMM},
+ };
+
+ TestOperand conflictTestOperand = kTestOperand;
+ const auto it = conflictTypeMap.find(kTestOperandType);
+ ASSERT_FALSE(it == conflictTypeMap.end());
+ conflictTestOperand.type = it->second;
+
+ auto preparedModel = createConvPreparedModel(kTestOperand);
+ auto conflictPreparedModel = createConvPreparedModel(conflictTestOperand);
+ if (preparedModel == nullptr || conflictPreparedModel == nullptr) return;
+ testConflictOperands(preparedModel, conflictPreparedModel);
+}
+
+TEST_P(MemoryDomainAllocateTest, ConflictScale) {
+ if (isFloat(kTestOperandType)) return;
+
+ TestOperand conflictTestOperand = kTestOperand;
+ ASSERT_NE(conflictTestOperand.scale, 1.0f);
+ conflictTestOperand.scale = 1.0f;
+
+ auto preparedModel = createConvPreparedModel(kTestOperand);
+ auto conflictPreparedModel = createConvPreparedModel(conflictTestOperand);
+ if (preparedModel == nullptr || conflictPreparedModel == nullptr) return;
+ testConflictOperands(preparedModel, conflictPreparedModel);
+}
+
+TEST_P(MemoryDomainAllocateTest, ConflictZeroPoint) {
+ if (isFloat(kTestOperandType)) return;
+
+ TestOperand conflictTestOperand = kTestOperand;
+ ASSERT_NE(conflictTestOperand.zeroPoint, 10);
+ conflictTestOperand.zeroPoint = 10;
+
+ auto preparedModel = createConvPreparedModel(kTestOperand);
+ auto conflictPreparedModel = createConvPreparedModel(conflictTestOperand);
+ if (preparedModel == nullptr || conflictPreparedModel == nullptr) return;
+ testConflictOperands(preparedModel, conflictPreparedModel);
+}
+
+TEST_P(MemoryDomainAllocateTest, ConflictRankBetweenRoles) {
+ TestOperand conflictTestOperand = kTestOperand;
+ conflictTestOperand.dimensions.pop_back();
+
+ auto preparedModel = createAddPreparedModel(kTestOperand);
+ auto conflictPreparedModel = createAddPreparedModel(conflictTestOperand);
+ if (preparedModel == nullptr || conflictPreparedModel == nullptr) return;
+ testConflictOperands(preparedModel, conflictPreparedModel);
+}
+
+TEST_P(MemoryDomainAllocateTest, ConflictDimensionsBetweenRoles) {
+ TestOperand conflictTestOperand = kTestOperand;
+ conflictTestOperand.dimensions[0] = 4;
+
+ auto preparedModel = createConvPreparedModel(kTestOperand);
+ auto conflictPreparedModel = createConvPreparedModel(conflictTestOperand);
+ if (preparedModel == nullptr || conflictPreparedModel == nullptr) return;
+ testConflictOperands(preparedModel, conflictPreparedModel);
+}
+
+TEST_P(MemoryDomainAllocateTest, ConflictRankBetweenRoleAndDesc) {
+ auto preparedModel = createConvPreparedModel(kTestOperand);
+ if (preparedModel == nullptr) return;
+
+ auto badDimensions = utils::toSigned(kTestOperand.dimensions).value();
+ badDimensions.pop_back();
+
+ validateAllocate({
+ .dimensions = badDimensions,
+ .preparedModels = {preparedModel},
+ .inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}},
+ });
+ validateAllocate({
+ .dimensions = badDimensions,
+ .preparedModels = {preparedModel},
+ .outputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}},
+ });
+}
+
+TEST_P(MemoryDomainAllocateTest, ConflictDimensionsBetweenRoleAndDesc) {
+ auto preparedModel = createConvPreparedModel(kTestOperand);
+ if (preparedModel == nullptr) return;
+
+ auto badDimensions = utils::toSigned(kTestOperand.dimensions).value();
+ badDimensions[0] = 4;
+
+ validateAllocate({
+ .dimensions = badDimensions,
+ .preparedModels = {preparedModel},
+ .inputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}},
+ });
+ validateAllocate({
+ .dimensions = badDimensions,
+ .preparedModels = {preparedModel},
+ .outputRoles = {{.modelIndex = 0, .ioIndex = 0, .frequency = 1.0f}},
+ });
+}
+
+TEST_P(MemoryDomainAllocateTest, ConflictRankWithScalarRole) {
+ auto preparedModel = createAddPreparedModel(kTestOperand);
+ if (preparedModel == nullptr) return;
+
+ // This should fail, because the target operand is a scalar but a non-empty dimension is
+ // specified.
+ validateAllocate({
+ .dimensions = {1},
+ .preparedModels = {preparedModel},
+ .inputRoles = {{.modelIndex = 0, .ioIndex = 2, .frequency = 1.0f}},
+ });
+}
+
+std::string printMemoryDomainAllocateTest(
+ const testing::TestParamInfo<MemoryDomainAllocateTestParam>& info) {
+ const auto& [namedDevice, operandType] = info.param;
+ const std::string type = toString(static_cast<OperandType>(operandType));
+ return gtestCompliantName(getName(namedDevice) + "_" + type);
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MemoryDomainAllocateTest);
+INSTANTIATE_TEST_SUITE_P(TestMemoryDomain, MemoryDomainAllocateTest,
+ testing::Combine(testing::ValuesIn(getNamedDevices()),
+ kTestOperandTypeChoices),
+ printMemoryDomainAllocateTest);
+
+class MemoryDomainCopyTestBase : public MemoryDomainTestBase {
+ protected:
+ MemoryDomainCopyTestBase(std::shared_ptr<IDevice> device, TestOperandType type)
+ : MemoryDomainTestBase(std::move(device), type) {}
+
+ // Allocates device memory for roles of a single prepared model.
+ // Returns {IBuffer, token} if success; returns {nullptr, 0} if not supported.
+ DeviceBuffer allocateBuffer(const std::shared_ptr<IPreparedModel>& preparedModel,
+ const std::vector<int32_t>& inputIndexes,
+ const std::vector<int32_t>& outputIndexes,
+ const std::vector<int32_t>& dimensions) {
+ if (preparedModel == nullptr) {
+ return {.buffer = nullptr, .token = 0};
+ }
+
+ std::vector<BufferRole> inputRoles(inputIndexes.size()), outputRoles(outputIndexes.size());
+ auto trans = [](int32_t ind) -> BufferRole {
+ return {.modelIndex = 0, .ioIndex = ind, .frequency = 1.0f};
+ };
+ std::transform(inputIndexes.begin(), inputIndexes.end(), inputRoles.begin(), trans);
+ std::transform(outputIndexes.begin(), outputIndexes.end(), outputRoles.begin(), trans);
+
+ IPreparedModelParcel parcel;
+ parcel.preparedModel = preparedModel;
+
+ DeviceBuffer buffer;
+
+ const auto ret = kDevice->allocate({.dimensions = dimensions}, {parcel}, inputRoles,
+ outputRoles, &buffer);
+
+ if (!ret.isOk()) {
+ EXPECT_EQ(ret.getExceptionCode(), EX_SERVICE_SPECIFIC);
+ EXPECT_EQ(static_cast<ErrorStatus>(ret.getServiceSpecificError()),
+ ErrorStatus::GENERAL_FAILURE);
+ return DeviceBuffer{
+ .buffer = nullptr,
+ .token = 0,
+ };
+ }
+
+ EXPECT_NE(buffer.buffer, nullptr);
+ EXPECT_GT(buffer.token, 0);
+
+ return buffer;
+ }
+
+ DeviceBuffer allocateBuffer(const std::shared_ptr<IPreparedModel>& preparedModel,
+ const std::vector<int32_t>& inputIndexes,
+ const std::vector<int32_t>& outputIndexes) {
+ return allocateBuffer(preparedModel, inputIndexes, outputIndexes, {});
+ }
+
+ Memory allocateSharedMemory(uint32_t size) {
+ const auto sharedMemory = nn::createSharedMemory(size).value();
+ auto memory = utils::convert(sharedMemory).value();
+ EXPECT_EQ(memory.size, size);
+ return memory;
+ }
+
+ void testCopyFrom(const std::shared_ptr<IBuffer>& buffer, const Memory& memory,
+ const std::vector<int32_t>& dimensions, ErrorStatus expectedStatus) {
+ const auto ret = buffer->copyFrom(memory, dimensions);
+ if (expectedStatus == ErrorStatus::NONE) {
+ ASSERT_TRUE(ret.isOk());
+ } else {
+ ASSERT_EQ(ret.getExceptionCode(), EX_SERVICE_SPECIFIC);
+ ASSERT_EQ(expectedStatus, static_cast<ErrorStatus>(ret.getServiceSpecificError()));
+ }
+ }
+
+ void testCopyTo(const std::shared_ptr<IBuffer>& buffer, const Memory& memory,
+ ErrorStatus expectedStatus) {
+ const auto ret = buffer->copyTo(memory);
+ if (expectedStatus == ErrorStatus::NONE) {
+ ASSERT_TRUE(ret.isOk());
+ } else {
+ ASSERT_EQ(ret.getExceptionCode(), EX_SERVICE_SPECIFIC);
+ ASSERT_EQ(expectedStatus, static_cast<ErrorStatus>(ret.getServiceSpecificError()));
+ }
+ }
+
+ void initializeDeviceMemory(const std::shared_ptr<IBuffer>& buffer) {
+ Memory memory = allocateSharedMemory(kTestOperandDataSize);
+ ASSERT_EQ(memory.size, kTestOperandDataSize);
+ testCopyFrom(buffer, memory, utils::toSigned(kTestOperand.dimensions).value(),
+ ErrorStatus::NONE);
+ }
+};
+
+using MemoryDomainCopyTestParam = std::tuple<NamedDevice, TestOperandType>;
+class MemoryDomainCopyTest : public MemoryDomainCopyTestBase,
+ public testing::WithParamInterface<MemoryDomainCopyTestParam> {
+ protected:
+ MemoryDomainCopyTest()
+ : MemoryDomainCopyTestBase(getData(std::get<NamedDevice>(GetParam())),
+ std::get<TestOperandType>(GetParam())) {}
+};
+
+TEST_P(MemoryDomainCopyTest, CopyFrom_InvalidMemorySize) {
+ auto preparedModel = createConvPreparedModel(kTestOperand);
+ auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0});
+ if (buffer == nullptr) return;
+
+ uint32_t badMemorySize1 = kTestOperandDataSize / 2, badMemorySize2 = kTestOperandDataSize * 2;
+ Memory badMemory1 = allocateSharedMemory(badMemorySize1);
+ Memory badMemory2 = allocateSharedMemory(badMemorySize2);
+ testCopyFrom(buffer, badMemory1, {}, ErrorStatus::INVALID_ARGUMENT);
+ testCopyFrom(buffer, badMemory2, {}, ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST_P(MemoryDomainCopyTest, CopyFrom_InvalidMemorySize_DynamicShape) {
+ TestOperand testOperand = kTestOperand;
+ testOperand.dimensions[0] = 0;
+ auto preparedModel = createConvPreparedModel(testOperand);
+ auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0});
+ if (buffer == nullptr) return;
+
+ uint32_t badMemorySize1 = kTestOperandDataSize / 2, badMemorySize2 = kTestOperandDataSize * 2;
+ Memory badMemory1 = allocateSharedMemory(badMemorySize1);
+ Memory badMemory2 = allocateSharedMemory(badMemorySize2);
+ Memory goodMemory = allocateSharedMemory(kTestOperandDataSize);
+
+ const auto goodDimensions = utils::toSigned(kTestOperand.dimensions).value();
+ auto badDimensions = goodDimensions;
+ badDimensions[0] = 2;
+
+ testCopyFrom(buffer, badMemory1, goodDimensions, ErrorStatus::INVALID_ARGUMENT);
+ testCopyFrom(buffer, badMemory2, goodDimensions, ErrorStatus::INVALID_ARGUMENT);
+ testCopyFrom(buffer, goodMemory, goodDimensions, ErrorStatus::NONE);
+ testCopyFrom(buffer, goodMemory, badDimensions, ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST_P(MemoryDomainCopyTest, CopyFrom_InvalidDimensions) {
+ auto preparedModel = createConvPreparedModel(kTestOperand);
+ auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0});
+ if (buffer == nullptr) return;
+
+ Memory memory = allocateSharedMemory(kTestOperandDataSize);
+
+ const auto goodDimensions = utils::toSigned(kTestOperand.dimensions).value();
+ std::vector<int32_t> badDimensions = goodDimensions;
+ badDimensions.pop_back();
+ testCopyFrom(buffer, memory, badDimensions, ErrorStatus::INVALID_ARGUMENT);
+
+ badDimensions = goodDimensions;
+ badDimensions[0] = 2;
+ testCopyFrom(buffer, memory, badDimensions, ErrorStatus::INVALID_ARGUMENT);
+
+ badDimensions = goodDimensions;
+ badDimensions[0] = 0;
+ testCopyFrom(buffer, memory, badDimensions, ErrorStatus::INVALID_ARGUMENT);
+
+ testCopyFrom(buffer, memory, {}, ErrorStatus::NONE);
+ testCopyFrom(buffer, memory, goodDimensions, ErrorStatus::NONE);
+}
+
+TEST_P(MemoryDomainCopyTest, CopyFrom_InvalidDimensions_DynamicShape) {
+ TestOperand testOperand = kTestOperand;
+ testOperand.dimensions[0] = 0;
+ auto preparedModel = createConvPreparedModel(testOperand);
+ auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0});
+ if (buffer == nullptr) return;
+
+ Memory memory = allocateSharedMemory(kTestOperandDataSize);
+
+ const auto goodDimensions = utils::toSigned(kTestOperand.dimensions).value();
+ std::vector<int32_t> badDimensions = goodDimensions;
+ badDimensions.pop_back();
+ testCopyFrom(buffer, memory, badDimensions, ErrorStatus::INVALID_ARGUMENT);
+
+ badDimensions = goodDimensions;
+ badDimensions[0] = 2;
+ badDimensions[3] = 4;
+ testCopyFrom(buffer, memory, badDimensions, ErrorStatus::INVALID_ARGUMENT);
+
+ badDimensions = goodDimensions;
+ badDimensions[0] = 1;
+ badDimensions[3] = 0;
+ testCopyFrom(buffer, memory, badDimensions, ErrorStatus::INVALID_ARGUMENT);
+
+ testCopyFrom(buffer, memory, {}, ErrorStatus::INVALID_ARGUMENT);
+ testCopyFrom(buffer, memory, goodDimensions, ErrorStatus::NONE);
+}
+
+TEST_P(MemoryDomainCopyTest, CopyTo_UninitializedMemory) {
+ auto preparedModel = createConvPreparedModel(kTestOperand);
+ auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0});
+ if (buffer == nullptr) return;
+
+ Memory memory = allocateSharedMemory(kTestOperandDataSize);
+ testCopyTo(buffer, memory, ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST_P(MemoryDomainCopyTest, CopyTo_InvalidMemorySize) {
+ auto preparedModel = createConvPreparedModel(kTestOperand);
+ auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0});
+ if (buffer == nullptr) return;
+
+ uint32_t badMemorySize1 = kTestOperandDataSize / 2, badMemorySize2 = kTestOperandDataSize * 2;
+ Memory badMemory1 = allocateSharedMemory(badMemorySize1);
+ Memory badMemory2 = allocateSharedMemory(badMemorySize2);
+ Memory goodMemory = allocateSharedMemory(kTestOperandDataSize);
+
+ initializeDeviceMemory(buffer);
+ testCopyTo(buffer, badMemory1, ErrorStatus::INVALID_ARGUMENT);
+ testCopyTo(buffer, badMemory2, ErrorStatus::INVALID_ARGUMENT);
+ testCopyTo(buffer, goodMemory, ErrorStatus::NONE);
+}
+
+TEST_P(MemoryDomainCopyTest, CopyTo_InvalidMemorySize_DynamicShape) {
+ TestOperand testOperand = kTestOperand;
+ testOperand.dimensions[0] = 0;
+ auto preparedModel = createConvPreparedModel(testOperand);
+ auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0});
+ if (buffer == nullptr) return;
+
+ uint32_t badMemorySize1 = kTestOperandDataSize / 2, badMemorySize2 = kTestOperandDataSize * 2;
+ Memory badMemory1 = allocateSharedMemory(badMemorySize1);
+ Memory badMemory2 = allocateSharedMemory(badMemorySize2);
+ Memory goodMemory = allocateSharedMemory(kTestOperandDataSize);
+
+ initializeDeviceMemory(buffer);
+ testCopyTo(buffer, badMemory1, ErrorStatus::INVALID_ARGUMENT);
+ testCopyTo(buffer, badMemory2, ErrorStatus::INVALID_ARGUMENT);
+ testCopyTo(buffer, goodMemory, ErrorStatus::NONE);
+}
+
+std::string printMemoryDomainCopyTest(
+ const testing::TestParamInfo<MemoryDomainCopyTestParam>& info) {
+ const auto& [namedDevice, operandType] = info.param;
+ const std::string type = toString(static_cast<OperandType>(operandType));
+ return gtestCompliantName(getName(namedDevice) + "_" + type);
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MemoryDomainCopyTest);
+INSTANTIATE_TEST_SUITE_P(TestMemoryDomain, MemoryDomainCopyTest,
+ testing::Combine(testing::ValuesIn(getNamedDevices()),
+ kTestOperandTypeChoices),
+ printMemoryDomainCopyTest);
+
+using MemoryDomainExecutionTestParam = std::tuple<NamedDevice, TestOperandType, Executor>;
+class MemoryDomainExecutionTest
+ : public MemoryDomainCopyTestBase,
+ public testing::WithParamInterface<MemoryDomainExecutionTestParam> {
+ protected:
+ MemoryDomainExecutionTest()
+ : MemoryDomainCopyTestBase(getData(std::get<NamedDevice>(GetParam())),
+ std::get<TestOperandType>(GetParam())) {}
+
+ RequestMemoryPool createSharedMemoryPool(uint32_t size) {
+ return RequestMemoryPool(allocateSharedMemory(size));
+ }
+
+ RequestMemoryPool createDeviceMemoryPool(uint32_t token) {
+ return RequestMemoryPool(static_cast<int32_t>(token));
+ }
+
+ void testExecution(const std::shared_ptr<IPreparedModel>& preparedModel, const Request& request,
+ ErrorStatus expectedStatus) {
+ switch (kExecutor) {
+ case Executor::SYNC:
+ EXPECT_EQ(executeSync(preparedModel, request), expectedStatus);
+ break;
+ case Executor::FENCED:
+ EXPECT_EQ(executeFenced(preparedModel, request), expectedStatus);
+ break;
+ default:
+ ASSERT_TRUE(false);
+ }
+ }
+
+ ErrorStatus executeSync(const std::shared_ptr<IPreparedModel>& preparedModel,
+ const Request& request) {
+ ExecutionResult executionResult;
+ const auto ret = preparedModel->executeSynchronously(
+ request, false, kNoDeadline, kOmittedTimeoutDuration, &executionResult);
+
+ if (!ret.isOk()) {
+ EXPECT_EQ(ret.getExceptionCode(), EX_SERVICE_SPECIFIC);
+ return static_cast<ErrorStatus>(ret.getServiceSpecificError());
+ }
+ const ErrorStatus executionStatus = executionResult.outputSufficientSize
+ ? ErrorStatus::NONE
+ : ErrorStatus::OUTPUT_INSUFFICIENT_SIZE;
+ EXPECT_EQ(executionResult.timing, kNoTiming);
+ return executionStatus;
+ }
+
+ ErrorStatus executeFenced(const std::shared_ptr<IPreparedModel>& preparedModel,
+ const Request& request) {
+ ndk::ScopedFileDescriptor syncFence;
+ std::shared_ptr<IFencedExecutionCallback> fencedCallback;
+ const auto ret = preparedModel->executeFenced(request, {}, false, kNoDeadline,
+ kOmittedTimeoutDuration, kNoDuration,
+ &syncFence, &fencedCallback);
+ if (!ret.isOk()) {
+ EXPECT_EQ(ret.getExceptionCode(), EX_SERVICE_SPECIFIC);
+ return static_cast<ErrorStatus>(ret.getServiceSpecificError());
+ }
+ if (syncFence.get() != -1) {
+ waitForSyncFence(syncFence.get());
+ }
+ EXPECT_NE(fencedCallback, nullptr);
+
+ ErrorStatus executionStatus = ErrorStatus::GENERAL_FAILURE;
+ Timing time = kNoTiming;
+ Timing timeFenced = kNoTiming;
+ const auto retExecutionInfo =
+ fencedCallback->getExecutionInfo(&time, &timeFenced, &executionStatus);
+ EXPECT_TRUE(retExecutionInfo.isOk());
+ EXPECT_EQ(time, kNoTiming);
+ return executionStatus;
+ }
+
+ const Executor kExecutor = std::get<Executor>(GetParam());
+};
+
+TEST_P(MemoryDomainExecutionTest, InvalidToken) {
+ auto preparedModel = createConvPreparedModel(kTestOperand);
+ if (preparedModel == nullptr) return;
+
+ RequestMemoryPool sharedMemory = createSharedMemoryPool(kTestOperandDataSize);
+ RequestMemoryPool badDeviceMemory1 = createDeviceMemoryPool(0); // Invalid token.
+ RequestMemoryPool badDeviceMemory2 = createDeviceMemoryPool(100); // Unknown token.
+ RequestArgument sharedMemoryArg = {
+ .location = {.poolIndex = 0, .offset = 0, .length = kTestOperandDataSize}};
+ RequestArgument deviceMemoryArg = {.location = {.poolIndex = 1}};
+
+ testExecution(preparedModel,
+ {.inputs = {deviceMemoryArg},
+ .outputs = {sharedMemoryArg},
+ .pools = createRequestMemoryPools(sharedMemory, badDeviceMemory1)},
+ ErrorStatus::INVALID_ARGUMENT);
+ testExecution(preparedModel,
+ {.inputs = {deviceMemoryArg},
+ .outputs = {sharedMemoryArg},
+ .pools = createRequestMemoryPools(sharedMemory, badDeviceMemory2)},
+ ErrorStatus::INVALID_ARGUMENT);
+ testExecution(preparedModel,
+ {.inputs = {sharedMemoryArg},
+ .outputs = {deviceMemoryArg},
+ .pools = createRequestMemoryPools(sharedMemory, badDeviceMemory1)},
+ ErrorStatus::INVALID_ARGUMENT);
+ testExecution(preparedModel,
+ {.inputs = {sharedMemoryArg},
+ .outputs = {deviceMemoryArg},
+ .pools = createRequestMemoryPools(sharedMemory, badDeviceMemory2)},
+ ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST_P(MemoryDomainExecutionTest, InvalidPreparedModel) {
+ auto preparedModel = createConvPreparedModel(kTestOperand);
+ auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0});
+ if (buffer == nullptr) return;
+ auto badPreparedModel = createConvPreparedModel(kTestOperand);
+ if (badPreparedModel == nullptr) return;
+
+ RequestMemoryPool sharedMemory = createSharedMemoryPool(kTestOperandDataSize);
+ RequestMemoryPool deviceMemory = createDeviceMemoryPool(token);
+ RequestArgument sharedMemoryArg = {
+ .location = {.poolIndex = 0, .offset = 0, .length = kTestOperandDataSize}};
+ RequestArgument deviceMemoryArg = {.location = {.poolIndex = 1}};
+
+ // This should fail, because the buffer is not allocated for badPreparedModel.
+ initializeDeviceMemory(buffer);
+ testExecution(badPreparedModel,
+ {.inputs = {deviceMemoryArg},
+ .outputs = {sharedMemoryArg},
+ .pools = createRequestMemoryPools(sharedMemory, deviceMemory)},
+ ErrorStatus::INVALID_ARGUMENT);
+ testExecution(badPreparedModel,
+ {.inputs = {sharedMemoryArg},
+ .outputs = {deviceMemoryArg},
+ .pools = createRequestMemoryPools(sharedMemory, deviceMemory)},
+ ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST_P(MemoryDomainExecutionTest, InvalidIOIndex) {
+ auto preparedModel = createConvPreparedModel(kTestOperand, 2);
+ auto [buffer, token] = allocateBuffer(preparedModel, {0}, {});
+ if (buffer == nullptr) return;
+
+ RequestMemoryPool sharedMemory1 = createSharedMemoryPool(kTestOperandDataSize);
+ RequestMemoryPool sharedMemory2 = createSharedMemoryPool(kTestOperandDataSize);
+ RequestMemoryPool sharedMemory3 = createSharedMemoryPool(kTestOperandDataSize);
+ RequestMemoryPool deviceMemory = createDeviceMemoryPool(token);
+ RequestArgument sharedMemoryArg1 = {
+ .location = {.poolIndex = 0, .offset = 0, .length = kTestOperandDataSize}};
+ RequestArgument sharedMemoryArg2 = {
+ .location = {.poolIndex = 1, .offset = 0, .length = kTestOperandDataSize}};
+ RequestArgument sharedMemoryArg3 = {
+ .location = {.poolIndex = 2, .offset = 0, .length = kTestOperandDataSize}};
+ RequestArgument deviceMemoryArg = {.location = {.poolIndex = 3}};
+
+ // This should fail, because the device memory is not allocated for input 1.
+ initializeDeviceMemory(buffer);
+ testExecution(preparedModel,
+ {.inputs = {sharedMemoryArg1, deviceMemoryArg},
+ .outputs = {sharedMemoryArg2, sharedMemoryArg3},
+ .pools = createRequestMemoryPools(sharedMemory1, sharedMemory2, sharedMemory3,
+ deviceMemory)},
+ ErrorStatus::INVALID_ARGUMENT);
+
+ // This should fail, because the device memory is not allocated for output 1.
+ testExecution(preparedModel,
+ {.inputs = {sharedMemoryArg1, sharedMemoryArg2},
+ .outputs = {sharedMemoryArg3, deviceMemoryArg},
+ .pools = createRequestMemoryPools(sharedMemory1, sharedMemory2, sharedMemory3,
+ deviceMemory)},
+ ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST_P(MemoryDomainExecutionTest, InvalidIOType) {
+ auto preparedModel = createConvPreparedModel(kTestOperand);
+ auto [inputBuffer, inputToken] = allocateBuffer(preparedModel, {0}, {});
+ auto [outputBuffer, outputToken] = allocateBuffer(preparedModel, {}, {0});
+ if (inputBuffer == nullptr || outputBuffer == nullptr) return;
+
+ RequestMemoryPool sharedMemory = createSharedMemoryPool(kTestOperandDataSize);
+ RequestMemoryPool deviceMemory = createDeviceMemoryPool(inputToken);
+ RequestArgument sharedMemoryArg = {
+ .location = {.poolIndex = 0, .offset = 0, .length = kTestOperandDataSize}};
+ RequestArgument deviceMemoryArg = {.location = {.poolIndex = 1}};
+
+ // This should fail, because the device memory is allocated for input but used as output.
+ testExecution(preparedModel,
+ {.inputs = {sharedMemoryArg},
+ .outputs = {deviceMemoryArg},
+ .pools = createRequestMemoryPools(sharedMemory, deviceMemory)},
+ ErrorStatus::INVALID_ARGUMENT);
+
+ // This should fail, because the device memory is allocated for output but used as input.
+ deviceMemory.set<RequestMemoryPool::Tag::token>(outputToken);
+ initializeDeviceMemory(outputBuffer);
+ testExecution(preparedModel,
+ {.inputs = {deviceMemoryArg},
+ .outputs = {sharedMemoryArg},
+ .pools = createRequestMemoryPools(sharedMemory, deviceMemory)},
+ ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST_P(MemoryDomainExecutionTest, UninitializedMemory) {
+ auto preparedModel = createConvPreparedModel(kTestOperand);
+ auto [buffer, token] = allocateBuffer(preparedModel, {0}, {0});
+ if (buffer == nullptr) return;
+
+ RequestMemoryPool sharedMemory = createSharedMemoryPool(kTestOperandDataSize);
+ RequestMemoryPool deviceMemory = createDeviceMemoryPool(token);
+ RequestArgument sharedMemoryArg = {
+ .location = {.poolIndex = 0, .offset = 0, .length = kTestOperandDataSize}};
+ RequestArgument deviceMemoryArg = {.location = {.poolIndex = 1}};
+
+ // This should fail, because the device memory is not initialized.
+ testExecution(preparedModel,
+ {.inputs = {deviceMemoryArg},
+ .outputs = {sharedMemoryArg},
+ .pools = createRequestMemoryPools(sharedMemory, deviceMemory)},
+ ErrorStatus::GENERAL_FAILURE);
+
+ // This should initialize the device memory.
+ testExecution(preparedModel,
+ {.inputs = {sharedMemoryArg},
+ .outputs = {deviceMemoryArg},
+ .pools = createRequestMemoryPools(sharedMemory, deviceMemory)},
+ ErrorStatus::NONE);
+
+ // Test again with initialized device memory.
+ testExecution(preparedModel,
+ {.inputs = {deviceMemoryArg},
+ .outputs = {sharedMemoryArg},
+ .pools = createRequestMemoryPools(sharedMemory, deviceMemory)},
+ ErrorStatus::NONE);
+}
+
+TEST_P(MemoryDomainExecutionTest, SameRequestMultipleRoles) {
+ auto preparedModel = createConvPreparedModel(kTestOperand, 2);
+ auto [buffer, token] = allocateBuffer(preparedModel, {0, 1}, {0, 1});
+ if (buffer == nullptr) return;
+
+ RequestMemoryPool sharedMemory1 = createSharedMemoryPool(kTestOperandDataSize);
+ RequestMemoryPool sharedMemory2 = createSharedMemoryPool(kTestOperandDataSize);
+ RequestMemoryPool deviceMemory = createDeviceMemoryPool(token);
+ RequestArgument sharedMemoryArg1 = {
+ .location = {.poolIndex = 0, .offset = 0, .length = kTestOperandDataSize}};
+ RequestArgument sharedMemoryArg2 = {
+ .location = {.poolIndex = 1, .offset = 0, .length = kTestOperandDataSize}};
+ RequestArgument deviceMemoryArg = {.location = {.poolIndex = 2}};
+
+ // This should fail, because the same device memory cannot be used for both input and output.
+ initializeDeviceMemory(buffer);
+ testExecution(preparedModel,
+ {.inputs = {deviceMemoryArg, sharedMemoryArg1},
+ .outputs = {deviceMemoryArg, sharedMemoryArg2},
+ .pools = createRequestMemoryPools(sharedMemory1, sharedMemory2, deviceMemory)},
+ ErrorStatus::INVALID_ARGUMENT);
+
+ // This should fail, because the same device memory cannot be used for multiple outputs.
+ testExecution(preparedModel,
+ {.inputs = {sharedMemoryArg1, sharedMemoryArg2},
+ .outputs = {deviceMemoryArg, deviceMemoryArg},
+ .pools = createRequestMemoryPools(sharedMemory1, sharedMemory2, deviceMemory)},
+ ErrorStatus::INVALID_ARGUMENT);
+
+ // The same device memory can be used for multiple inputs.
+ initializeDeviceMemory(buffer);
+ testExecution(preparedModel,
+ {.inputs = {deviceMemoryArg, deviceMemoryArg},
+ .outputs = {sharedMemoryArg1, sharedMemoryArg2},
+ .pools = createRequestMemoryPools(sharedMemory1, sharedMemory2, deviceMemory)},
+ ErrorStatus::NONE);
+}
+
+TEST_P(MemoryDomainExecutionTest, InvalidDimensions) {
+ // FENCED execution does not support dynamic shape.
+ if (kExecutor == Executor::FENCED) return;
+
+ TestOperand testOperand = kTestOperand;
+ testOperand.dimensions[0] = 0;
+ auto preparedModel = createConvPreparedModel(testOperand);
+ auto deviceBuffer = allocateBuffer(preparedModel, {0}, {0},
+ utils::toSigned(kTestOperand.dimensions).value());
+ if (deviceBuffer.buffer == nullptr) return;
+
+ RequestMemoryPool sharedMemory = createSharedMemoryPool(kTestOperandDataSize);
+ RequestMemoryPool deviceMemory = createDeviceMemoryPool(deviceBuffer.token);
+ auto badDimensions = utils::toSigned(kTestOperand.dimensions).value();
+ badDimensions[0] = 2;
+ RequestArgument sharedMemoryArg = {
+ .location = {.poolIndex = 0, .offset = 0, .length = kTestOperandDataSize},
+ .dimensions = badDimensions};
+ RequestArgument deviceMemoryArg = {.location = {.poolIndex = 1}};
+ RequestArgument deviceMemoryArgWithBadDimensions = {.location = {.poolIndex = 1},
+ .dimensions = badDimensions};
+
+ initializeDeviceMemory(deviceBuffer.buffer);
+ testExecution(preparedModel,
+ {.inputs = {deviceMemoryArgWithBadDimensions},
+ .outputs = {sharedMemoryArg},
+ .pools = createRequestMemoryPools(sharedMemory, deviceMemory)},
+ ErrorStatus::INVALID_ARGUMENT);
+
+ testExecution(preparedModel,
+ {.inputs = {sharedMemoryArg},
+ .outputs = {deviceMemoryArgWithBadDimensions},
+ .pools = createRequestMemoryPools(sharedMemory, deviceMemory)},
+ ErrorStatus::INVALID_ARGUMENT);
+
+ testExecution(preparedModel,
+ {.inputs = {sharedMemoryArg},
+ .outputs = {deviceMemoryArg},
+ .pools = createRequestMemoryPools(sharedMemory, deviceMemory)},
+ ErrorStatus::GENERAL_FAILURE);
+}
+
+const auto kExecutorChoices = testing::Values(Executor::SYNC, Executor::FENCED);
+
+std::string printMemoryDomainExecutionTest(
+ const testing::TestParamInfo<MemoryDomainExecutionTestParam>& info) {
+ const auto& [namedDevice, operandType, executor] = info.param;
+ const std::string type = toString(static_cast<OperandType>(operandType));
+ const std::string executorStr = toString(executor);
+ return gtestCompliantName(getName(namedDevice) + "_" + type + "_" + executorStr);
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MemoryDomainExecutionTest);
+INSTANTIATE_TEST_SUITE_P(TestMemoryDomain, MemoryDomainExecutionTest,
+ testing::Combine(testing::ValuesIn(getNamedDevices()),
+ kTestOperandTypeChoices, kExecutorChoices),
+ printMemoryDomainExecutionTest);
+
+} // namespace aidl::android::hardware::neuralnetworks::vts::functional
diff --git a/neuralnetworks/aidl/vts/functional/QualityOfServiceTests.cpp b/neuralnetworks/aidl/vts/functional/QualityOfServiceTests.cpp
new file mode 100644
index 0000000..58db98f
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/QualityOfServiceTests.cpp
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/binder_enums.h>
+#include <android/binder_interface_utils.h>
+#include <android/binder_status.h>
+
+#include <nnapi/hal/aidl/Conversions.h>
+
+#include "Callbacks.h"
+#include "GeneratedTestHarness.h"
+#include "Utils.h"
+
+namespace aidl::android::hardware::neuralnetworks::vts::functional {
+
+using implementation::PreparedModelCallback;
+using test_helper::TestBuffer;
+using test_helper::TestModel;
+
+enum class DeadlineBoundType { NOW, UNLIMITED, SHORT };
+constexpr std::array<DeadlineBoundType, 3> deadlineBounds = {
+ DeadlineBoundType::NOW, DeadlineBoundType::UNLIMITED, DeadlineBoundType::SHORT};
+std::string toString(DeadlineBoundType type) {
+ switch (type) {
+ case DeadlineBoundType::NOW:
+ return "NOW";
+ case DeadlineBoundType::UNLIMITED:
+ return "UNLIMITED";
+ case DeadlineBoundType::SHORT:
+ return "SHORT";
+ }
+ LOG(FATAL) << "Unrecognized DeadlineBoundType: " << static_cast<int>(type);
+ return {};
+}
+
+constexpr auto kShortDuration = std::chrono::milliseconds{5};
+
+using Results = std::tuple<ErrorStatus, std::vector<OutputShape>, Timing>;
+using MaybeResults = std::optional<Results>;
+
+static int64_t makeDeadline(DeadlineBoundType deadlineBoundType) {
+ const auto getNanosecondsSinceEpoch = [](const auto& time) -> int64_t {
+ const auto timeSinceEpoch = time.time_since_epoch();
+ return std::chrono::duration_cast<std::chrono::nanoseconds>(timeSinceEpoch).count();
+ };
+
+ std::chrono::steady_clock::time_point timePoint;
+ switch (deadlineBoundType) {
+ case DeadlineBoundType::NOW:
+ timePoint = std::chrono::steady_clock::now();
+ break;
+ case DeadlineBoundType::UNLIMITED:
+ timePoint = std::chrono::steady_clock::time_point::max();
+ break;
+ case DeadlineBoundType::SHORT:
+ timePoint = std::chrono::steady_clock::now() + kShortDuration;
+ break;
+ }
+
+ return getNanosecondsSinceEpoch(timePoint);
+}
+
+void runPrepareModelTest(const std::shared_ptr<IDevice>& device, const Model& model,
+ Priority priority, std::optional<DeadlineBoundType> deadlineBound) {
+ int64_t deadline = kNoDeadline;
+ if (deadlineBound.has_value()) {
+ deadline = makeDeadline(deadlineBound.value());
+ }
+
+ // see if service can handle model
+ std::vector<bool> supportedOps;
+ const auto supportedCallStatus = device->getSupportedOperations(model, &supportedOps);
+ ASSERT_TRUE(supportedCallStatus.isOk());
+ ASSERT_NE(0ul, supportedOps.size());
+ const bool fullySupportsModel =
+ std::all_of(supportedOps.begin(), supportedOps.end(), [](bool valid) { return valid; });
+
+ // launch prepare model
+ const std::shared_ptr<PreparedModelCallback> preparedModelCallback =
+ ndk::SharedRefBase::make<PreparedModelCallback>();
+ const auto prepareLaunchStatus =
+ device->prepareModel(model, ExecutionPreference::FAST_SINGLE_ANSWER, priority, deadline,
+ {}, {}, kEmptyCacheToken, preparedModelCallback);
+ ASSERT_TRUE(prepareLaunchStatus.isOk())
+ << "prepareLaunchStatus: " << prepareLaunchStatus.getDescription();
+
+ // retrieve prepared model
+ preparedModelCallback->wait();
+ const ErrorStatus prepareReturnStatus = preparedModelCallback->getStatus();
+ const std::shared_ptr<IPreparedModel> preparedModel = preparedModelCallback->getPreparedModel();
+
+ // The getSupportedOperations call returns a list of operations that are guaranteed not to fail
+ // if prepareModel is called, and 'fullySupportsModel' is true i.f.f. the entire model is
+ // guaranteed. If a driver has any doubt that it can prepare an operation, it must return false.
+ // So here, if a driver isn't sure if it can support an operation, but reports that it
+ // successfully prepared the model, the test can continue.
+ if (!fullySupportsModel && prepareReturnStatus != ErrorStatus::NONE) {
+ ASSERT_EQ(nullptr, preparedModel.get());
+ return;
+ }
+
+ // verify return status
+ if (!deadlineBound.has_value()) {
+ EXPECT_EQ(ErrorStatus::NONE, prepareReturnStatus);
+ } else {
+ switch (deadlineBound.value()) {
+ case DeadlineBoundType::NOW:
+ case DeadlineBoundType::SHORT:
+ // Either the driver successfully completed the task or it
+ // aborted and returned MISSED_DEADLINE_*.
+ EXPECT_TRUE(prepareReturnStatus == ErrorStatus::NONE ||
+ prepareReturnStatus == ErrorStatus::MISSED_DEADLINE_TRANSIENT ||
+ prepareReturnStatus == ErrorStatus::MISSED_DEADLINE_PERSISTENT);
+ break;
+ case DeadlineBoundType::UNLIMITED:
+ // If an unlimited deadline is supplied, we expect the execution to
+ // proceed normally. In this case, check it normally by breaking out
+ // of the switch statement.
+ EXPECT_EQ(ErrorStatus::NONE, prepareReturnStatus);
+ break;
+ }
+ }
+ ASSERT_EQ(prepareReturnStatus == ErrorStatus::NONE, preparedModel.get() != nullptr);
+}
+
+void runPrepareModelTests(const std::shared_ptr<IDevice>& device, const Model& model) {
+ // test priority
+ for (auto priority : ndk::enum_range<Priority>{}) {
+ SCOPED_TRACE("priority: " + toString(priority));
+ if (priority == kDefaultPriority) continue;
+ runPrepareModelTest(device, model, priority, {});
+ }
+
+ // test deadline
+ for (auto deadlineBound : deadlineBounds) {
+ SCOPED_TRACE("deadlineBound: " + toString(deadlineBound));
+ runPrepareModelTest(device, model, kDefaultPriority, deadlineBound);
+ }
+}
+
+static MaybeResults executeSynchronously(const std::shared_ptr<IPreparedModel>& preparedModel,
+ const Request& request, int64_t deadline) {
+ SCOPED_TRACE("synchronous");
+ const bool measure = false;
+
+ // run execution
+ ExecutionResult executionResult;
+ const auto ret = preparedModel->executeSynchronously(request, measure, deadline,
+ kOmittedTimeoutDuration, &executionResult);
+ EXPECT_TRUE(ret.isOk() || ret.getExceptionCode() == EX_SERVICE_SPECIFIC)
+ << ret.getDescription();
+ if (!ret.isOk()) {
+ if (ret.getExceptionCode() != EX_SERVICE_SPECIFIC) {
+ return std::nullopt;
+ }
+ return MaybeResults(
+ {static_cast<ErrorStatus>(ret.getServiceSpecificError()), {}, kNoTiming});
+ }
+
+ // return results
+ return MaybeResults({executionResult.outputSufficientSize
+ ? ErrorStatus::NONE
+ : ErrorStatus::OUTPUT_INSUFFICIENT_SIZE,
+ std::move(executionResult.outputShapes), executionResult.timing});
+}
+
+void runExecutionTest(const std::shared_ptr<IPreparedModel>& preparedModel,
+ const TestModel& testModel, const Request& request,
+ const ExecutionContext& context, DeadlineBoundType deadlineBound) {
+ const auto deadline = makeDeadline(deadlineBound);
+
+ // Perform execution and unpack results.
+ const auto results = executeSynchronously(preparedModel, request, deadline);
+ if (!results.has_value()) return;
+ const auto& [status, outputShapes, timing] = results.value();
+
+ // Verify no timing information was returned
+ EXPECT_EQ(timing, kNoTiming);
+
+ // Validate deadline information if applicable.
+ switch (deadlineBound) {
+ case DeadlineBoundType::NOW:
+ case DeadlineBoundType::SHORT:
+ // Either the driver successfully completed the task or it
+ // aborted and returned MISSED_DEADLINE_*.
+ ASSERT_TRUE(status == ErrorStatus::NONE ||
+ status == ErrorStatus::MISSED_DEADLINE_TRANSIENT ||
+ status == ErrorStatus::MISSED_DEADLINE_PERSISTENT);
+ break;
+ case DeadlineBoundType::UNLIMITED:
+ // If an unlimited deadline is supplied, we expect the execution to
+ // proceed normally. In this case, check it normally by breaking out
+ // of the switch statement.
+ ASSERT_EQ(ErrorStatus::NONE, status);
+ break;
+ }
+
+ // If the model output operands are fully specified, outputShapes must be either
+ // either empty, or have the same number of elements as the number of outputs.
+ ASSERT_TRUE(outputShapes.size() == 0 ||
+ outputShapes.size() == testModel.main.outputIndexes.size());
+
+ // Go through all outputs, check returned output shapes.
+ for (uint32_t i = 0; i < outputShapes.size(); i++) {
+ EXPECT_TRUE(outputShapes[i].isSufficient);
+ const auto expect =
+ utils::toSigned(testModel.main.operands[testModel.main.outputIndexes[i]].dimensions)
+ .value();
+ const std::vector<int32_t>& actual = outputShapes[i].dimensions;
+ EXPECT_EQ(expect, actual);
+ }
+
+ // Retrieve execution results.
+ const std::vector<TestBuffer> outputs = context.getOutputBuffers(request);
+
+ // We want "close-enough" results.
+ if (status == ErrorStatus::NONE) {
+ checkResults(testModel, outputs);
+ }
+}
+
+void runExecutionTests(const std::shared_ptr<IPreparedModel>& preparedModel,
+ const TestModel& testModel, const Request& request,
+ const ExecutionContext& context) {
+ for (auto deadlineBound : deadlineBounds) {
+ runExecutionTest(preparedModel, testModel, request, context, deadlineBound);
+ }
+}
+
+void runTests(const std::shared_ptr<IDevice>& device, const TestModel& testModel) {
+ // setup
+ const Model model = createModel(testModel);
+
+ // run prepare model tests
+ runPrepareModelTests(device, model);
+
+ // prepare model
+ std::shared_ptr<IPreparedModel> preparedModel;
+ createPreparedModel(device, model, &preparedModel);
+ if (preparedModel == nullptr) return;
+
+ // run execution tests
+ ExecutionContext context;
+ const Request request = context.createRequest(testModel);
+ runExecutionTests(preparedModel, testModel, request, context);
+}
+
+class DeadlineTest : public GeneratedTestBase {};
+
+TEST_P(DeadlineTest, Test) {
+ runTests(kDevice, kTestModel);
+}
+
+INSTANTIATE_GENERATED_TEST(DeadlineTest,
+ [](const TestModel& testModel) { return !testModel.expectFailure; });
+
+} // namespace aidl::android::hardware::neuralnetworks::vts::functional
diff --git a/neuralnetworks/aidl/vts/functional/TestAssertions.cpp b/neuralnetworks/aidl/vts/functional/TestAssertions.cpp
new file mode 100644
index 0000000..a9e9456
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/TestAssertions.cpp
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2021 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 <aidl/android/hardware/neuralnetworks/IPreparedModel.h>
+#include <aidl/android/hardware/neuralnetworks/OperandType.h>
+#include <aidl/android/hardware/neuralnetworks/OperationType.h>
+
+#include <ControlFlow.h>
+#include <TestHarness.h>
+
+namespace aidl::android::hardware::neuralnetworks {
+
+namespace nn = ::android::nn;
+
+static_assert(static_cast<uint64_t>(IPreparedModel::DEFAULT_LOOP_TIMEOUT_DURATION_NS) ==
+ nn::operation_while::kTimeoutNsDefault);
+static_assert(static_cast<uint64_t>(IPreparedModel::MAXIMUM_LOOP_TIMEOUT_DURATION_NS) ==
+ nn::operation_while::kTimeoutNsMaximum);
+
+// Make sure that the HIDL enums are compatible with the values defined in
+// frameworks/ml/nn/tools/test_generator/test_harness/include/TestHarness.h.
+using namespace test_helper;
+#define CHECK_TEST_ENUM(EnumType, enumValue) \
+ static_assert(static_cast<EnumType>(Test##EnumType::enumValue) == EnumType::enumValue)
+
+CHECK_TEST_ENUM(OperandType, FLOAT32);
+CHECK_TEST_ENUM(OperandType, INT32);
+CHECK_TEST_ENUM(OperandType, UINT32);
+CHECK_TEST_ENUM(OperandType, TENSOR_FLOAT32);
+CHECK_TEST_ENUM(OperandType, TENSOR_INT32);
+CHECK_TEST_ENUM(OperandType, TENSOR_QUANT8_ASYMM);
+CHECK_TEST_ENUM(OperandType, BOOL);
+CHECK_TEST_ENUM(OperandType, TENSOR_QUANT16_SYMM);
+CHECK_TEST_ENUM(OperandType, TENSOR_FLOAT16);
+CHECK_TEST_ENUM(OperandType, TENSOR_BOOL8);
+CHECK_TEST_ENUM(OperandType, FLOAT16);
+CHECK_TEST_ENUM(OperandType, TENSOR_QUANT8_SYMM_PER_CHANNEL);
+CHECK_TEST_ENUM(OperandType, TENSOR_QUANT16_ASYMM);
+CHECK_TEST_ENUM(OperandType, TENSOR_QUANT8_SYMM);
+CHECK_TEST_ENUM(OperandType, TENSOR_QUANT8_ASYMM_SIGNED);
+
+CHECK_TEST_ENUM(OperationType, ADD);
+CHECK_TEST_ENUM(OperationType, AVERAGE_POOL_2D);
+CHECK_TEST_ENUM(OperationType, CONCATENATION);
+CHECK_TEST_ENUM(OperationType, CONV_2D);
+CHECK_TEST_ENUM(OperationType, DEPTHWISE_CONV_2D);
+CHECK_TEST_ENUM(OperationType, DEPTH_TO_SPACE);
+CHECK_TEST_ENUM(OperationType, DEQUANTIZE);
+CHECK_TEST_ENUM(OperationType, EMBEDDING_LOOKUP);
+CHECK_TEST_ENUM(OperationType, FLOOR);
+CHECK_TEST_ENUM(OperationType, FULLY_CONNECTED);
+CHECK_TEST_ENUM(OperationType, HASHTABLE_LOOKUP);
+CHECK_TEST_ENUM(OperationType, L2_NORMALIZATION);
+CHECK_TEST_ENUM(OperationType, L2_POOL_2D);
+CHECK_TEST_ENUM(OperationType, LOCAL_RESPONSE_NORMALIZATION);
+CHECK_TEST_ENUM(OperationType, LOGISTIC);
+CHECK_TEST_ENUM(OperationType, LSH_PROJECTION);
+CHECK_TEST_ENUM(OperationType, LSTM);
+CHECK_TEST_ENUM(OperationType, MAX_POOL_2D);
+CHECK_TEST_ENUM(OperationType, MUL);
+CHECK_TEST_ENUM(OperationType, RELU);
+CHECK_TEST_ENUM(OperationType, RELU1);
+CHECK_TEST_ENUM(OperationType, RELU6);
+CHECK_TEST_ENUM(OperationType, RESHAPE);
+CHECK_TEST_ENUM(OperationType, RESIZE_BILINEAR);
+CHECK_TEST_ENUM(OperationType, RNN);
+CHECK_TEST_ENUM(OperationType, SOFTMAX);
+CHECK_TEST_ENUM(OperationType, SPACE_TO_DEPTH);
+CHECK_TEST_ENUM(OperationType, SVDF);
+CHECK_TEST_ENUM(OperationType, TANH);
+CHECK_TEST_ENUM(OperationType, BATCH_TO_SPACE_ND);
+CHECK_TEST_ENUM(OperationType, DIV);
+CHECK_TEST_ENUM(OperationType, MEAN);
+CHECK_TEST_ENUM(OperationType, PAD);
+CHECK_TEST_ENUM(OperationType, SPACE_TO_BATCH_ND);
+CHECK_TEST_ENUM(OperationType, SQUEEZE);
+CHECK_TEST_ENUM(OperationType, STRIDED_SLICE);
+CHECK_TEST_ENUM(OperationType, SUB);
+CHECK_TEST_ENUM(OperationType, TRANSPOSE);
+CHECK_TEST_ENUM(OperationType, ABS);
+CHECK_TEST_ENUM(OperationType, ARGMAX);
+CHECK_TEST_ENUM(OperationType, ARGMIN);
+CHECK_TEST_ENUM(OperationType, AXIS_ALIGNED_BBOX_TRANSFORM);
+CHECK_TEST_ENUM(OperationType, BIDIRECTIONAL_SEQUENCE_LSTM);
+CHECK_TEST_ENUM(OperationType, BIDIRECTIONAL_SEQUENCE_RNN);
+CHECK_TEST_ENUM(OperationType, BOX_WITH_NMS_LIMIT);
+CHECK_TEST_ENUM(OperationType, CAST);
+CHECK_TEST_ENUM(OperationType, CHANNEL_SHUFFLE);
+CHECK_TEST_ENUM(OperationType, DETECTION_POSTPROCESSING);
+CHECK_TEST_ENUM(OperationType, EQUAL);
+CHECK_TEST_ENUM(OperationType, EXP);
+CHECK_TEST_ENUM(OperationType, EXPAND_DIMS);
+CHECK_TEST_ENUM(OperationType, GATHER);
+CHECK_TEST_ENUM(OperationType, GENERATE_PROPOSALS);
+CHECK_TEST_ENUM(OperationType, GREATER);
+CHECK_TEST_ENUM(OperationType, GREATER_EQUAL);
+CHECK_TEST_ENUM(OperationType, GROUPED_CONV_2D);
+CHECK_TEST_ENUM(OperationType, HEATMAP_MAX_KEYPOINT);
+CHECK_TEST_ENUM(OperationType, INSTANCE_NORMALIZATION);
+CHECK_TEST_ENUM(OperationType, LESS);
+CHECK_TEST_ENUM(OperationType, LESS_EQUAL);
+CHECK_TEST_ENUM(OperationType, LOG);
+CHECK_TEST_ENUM(OperationType, LOGICAL_AND);
+CHECK_TEST_ENUM(OperationType, LOGICAL_NOT);
+CHECK_TEST_ENUM(OperationType, LOGICAL_OR);
+CHECK_TEST_ENUM(OperationType, LOG_SOFTMAX);
+CHECK_TEST_ENUM(OperationType, MAXIMUM);
+CHECK_TEST_ENUM(OperationType, MINIMUM);
+CHECK_TEST_ENUM(OperationType, NEG);
+CHECK_TEST_ENUM(OperationType, NOT_EQUAL);
+CHECK_TEST_ENUM(OperationType, PAD_V2);
+CHECK_TEST_ENUM(OperationType, POW);
+CHECK_TEST_ENUM(OperationType, PRELU);
+CHECK_TEST_ENUM(OperationType, QUANTIZE);
+CHECK_TEST_ENUM(OperationType, QUANTIZED_16BIT_LSTM);
+CHECK_TEST_ENUM(OperationType, RANDOM_MULTINOMIAL);
+CHECK_TEST_ENUM(OperationType, REDUCE_ALL);
+CHECK_TEST_ENUM(OperationType, REDUCE_ANY);
+CHECK_TEST_ENUM(OperationType, REDUCE_MAX);
+CHECK_TEST_ENUM(OperationType, REDUCE_MIN);
+CHECK_TEST_ENUM(OperationType, REDUCE_PROD);
+CHECK_TEST_ENUM(OperationType, REDUCE_SUM);
+CHECK_TEST_ENUM(OperationType, ROI_ALIGN);
+CHECK_TEST_ENUM(OperationType, ROI_POOLING);
+CHECK_TEST_ENUM(OperationType, RSQRT);
+CHECK_TEST_ENUM(OperationType, SELECT);
+CHECK_TEST_ENUM(OperationType, SIN);
+CHECK_TEST_ENUM(OperationType, SLICE);
+CHECK_TEST_ENUM(OperationType, SPLIT);
+CHECK_TEST_ENUM(OperationType, SQRT);
+CHECK_TEST_ENUM(OperationType, TILE);
+CHECK_TEST_ENUM(OperationType, TOPK_V2);
+CHECK_TEST_ENUM(OperationType, TRANSPOSE_CONV_2D);
+CHECK_TEST_ENUM(OperationType, UNIDIRECTIONAL_SEQUENCE_LSTM);
+CHECK_TEST_ENUM(OperationType, UNIDIRECTIONAL_SEQUENCE_RNN);
+CHECK_TEST_ENUM(OperationType, RESIZE_NEAREST_NEIGHBOR);
+
+#undef CHECK_TEST_ENUM
+
+} // namespace aidl::android::hardware::neuralnetworks
diff --git a/neuralnetworks/aidl/vts/functional/TestMain.cpp b/neuralnetworks/aidl/vts/functional/TestMain.cpp
new file mode 100644
index 0000000..1d58608
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/TestMain.cpp
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/binder_process.h>
+#include <gtest/gtest.h>
+#include "LogTestCaseToLogcat.h"
+
+int main(int argc, char** argv) {
+ testing::InitGoogleTest(&argc, argv);
+ testing::UnitTest::GetInstance()->listeners().Append(
+ new aidl::android::hardware::neuralnetworks::LogTestCaseToLogcat());
+ ABinderProcess_startThreadPool();
+ return RUN_ALL_TESTS();
+}
diff --git a/neuralnetworks/aidl/vts/functional/Utils.cpp b/neuralnetworks/aidl/vts/functional/Utils.cpp
new file mode 100644
index 0000000..3c7f5f7
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/Utils.cpp
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2021 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 "Utils.h"
+
+#include <aidl/android/hardware/neuralnetworks/IPreparedModelParcel.h>
+#include <aidl/android/hardware/neuralnetworks/Operand.h>
+#include <aidl/android/hardware/neuralnetworks/OperandType.h>
+#include <android-base/logging.h>
+#include <android/binder_status.h>
+#include <android/hardware_buffer.h>
+
+#include <iostream>
+#include <limits>
+#include <numeric>
+
+#include <MemoryUtils.h>
+#include <nnapi/SharedMemory.h>
+#include <nnapi/hal/aidl/Conversions.h>
+#include <nnapi/hal/aidl/Utils.h>
+
+namespace aidl::android::hardware::neuralnetworks {
+
+using test_helper::TestBuffer;
+using test_helper::TestModel;
+
+uint32_t sizeOfData(OperandType type) {
+ switch (type) {
+ case OperandType::FLOAT32:
+ case OperandType::INT32:
+ case OperandType::UINT32:
+ case OperandType::TENSOR_FLOAT32:
+ case OperandType::TENSOR_INT32:
+ return 4;
+ case OperandType::TENSOR_QUANT16_SYMM:
+ case OperandType::TENSOR_FLOAT16:
+ case OperandType::FLOAT16:
+ case OperandType::TENSOR_QUANT16_ASYMM:
+ return 2;
+ case OperandType::TENSOR_QUANT8_ASYMM:
+ case OperandType::BOOL:
+ case OperandType::TENSOR_BOOL8:
+ case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL:
+ case OperandType::TENSOR_QUANT8_SYMM:
+ case OperandType::TENSOR_QUANT8_ASYMM_SIGNED:
+ return 1;
+ case OperandType::SUBGRAPH:
+ return 0;
+ default:
+ CHECK(false) << "Invalid OperandType " << static_cast<uint32_t>(type);
+ return 0;
+ }
+}
+
+static bool isTensor(OperandType type) {
+ switch (type) {
+ case OperandType::FLOAT32:
+ case OperandType::INT32:
+ case OperandType::UINT32:
+ case OperandType::FLOAT16:
+ case OperandType::BOOL:
+ case OperandType::SUBGRAPH:
+ return false;
+ case OperandType::TENSOR_FLOAT32:
+ case OperandType::TENSOR_INT32:
+ case OperandType::TENSOR_QUANT16_SYMM:
+ case OperandType::TENSOR_FLOAT16:
+ case OperandType::TENSOR_QUANT16_ASYMM:
+ case OperandType::TENSOR_QUANT8_ASYMM:
+ case OperandType::TENSOR_BOOL8:
+ case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL:
+ case OperandType::TENSOR_QUANT8_SYMM:
+ case OperandType::TENSOR_QUANT8_ASYMM_SIGNED:
+ return true;
+ default:
+ CHECK(false) << "Invalid OperandType " << static_cast<uint32_t>(type);
+ return false;
+ }
+}
+
+uint32_t sizeOfData(const Operand& operand) {
+ const uint32_t dataSize = sizeOfData(operand.type);
+ if (isTensor(operand.type) && operand.dimensions.size() == 0) return 0;
+ return std::accumulate(operand.dimensions.begin(), operand.dimensions.end(), dataSize,
+ std::multiplies<>{});
+}
+
+std::unique_ptr<TestAshmem> TestAshmem::create(uint32_t size) {
+ auto ashmem = std::make_unique<TestAshmem>(size);
+ return ashmem->mIsValid ? std::move(ashmem) : nullptr;
+}
+
+void TestAshmem::initialize(uint32_t size) {
+ mIsValid = false;
+ ASSERT_GT(size, 0);
+ const auto sharedMemory = nn::createSharedMemory(size).value();
+ mMappedMemory = nn::map(sharedMemory).value();
+ mPtr = static_cast<uint8_t*>(std::get<void*>(mMappedMemory.pointer));
+ CHECK_NE(mPtr, nullptr);
+ mAidlMemory = utils::convert(sharedMemory).value();
+ mIsValid = true;
+}
+
+std::unique_ptr<TestBlobAHWB> TestBlobAHWB::create(uint32_t size) {
+ auto ahwb = std::make_unique<TestBlobAHWB>(size);
+ return ahwb->mIsValid ? std::move(ahwb) : nullptr;
+}
+
+void TestBlobAHWB::initialize(uint32_t size) {
+ mIsValid = false;
+ ASSERT_GT(size, 0);
+ const auto usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN;
+ const AHardwareBuffer_Desc desc = {
+ .width = size,
+ .height = 1,
+ .layers = 1,
+ .format = AHARDWAREBUFFER_FORMAT_BLOB,
+ .usage = usage,
+ .stride = size,
+ };
+
+ ASSERT_EQ(AHardwareBuffer_allocate(&desc, &mAhwb), 0);
+ ASSERT_NE(mAhwb, nullptr);
+
+ const auto sharedMemory =
+ nn::createSharedMemoryFromAHWB(mAhwb, /*takeOwnership=*/false).value();
+ mMapping = nn::map(sharedMemory).value();
+ mPtr = static_cast<uint8_t*>(std::get<void*>(mMapping.pointer));
+ CHECK_NE(mPtr, nullptr);
+ mAidlMemory = utils::convert(sharedMemory).value();
+
+ mIsValid = true;
+}
+
+TestBlobAHWB::~TestBlobAHWB() {
+ if (mAhwb) {
+ AHardwareBuffer_unlock(mAhwb, nullptr);
+ AHardwareBuffer_release(mAhwb);
+ }
+}
+
+std::string gtestCompliantName(std::string name) {
+ // gtest test names must only contain alphanumeric characters
+ std::replace_if(
+ name.begin(), name.end(), [](char c) { return !std::isalnum(c); }, '_');
+ return name;
+}
+
+::std::ostream& operator<<(::std::ostream& os, ErrorStatus errorStatus) {
+ return os << toString(errorStatus);
+}
+
+Request ExecutionContext::createRequest(const TestModel& testModel, MemoryType memoryType) {
+ CHECK(memoryType == MemoryType::ASHMEM || memoryType == MemoryType::BLOB_AHWB);
+
+ // Model inputs.
+ std::vector<RequestArgument> inputs(testModel.main.inputIndexes.size());
+ size_t inputSize = 0;
+ for (uint32_t i = 0; i < testModel.main.inputIndexes.size(); i++) {
+ const auto& op = testModel.main.operands[testModel.main.inputIndexes[i]];
+ if (op.data.size() == 0) {
+ // Omitted input.
+ inputs[i] = {.hasNoValue = true};
+ } else {
+ DataLocation loc = {.poolIndex = kInputPoolIndex,
+ .offset = static_cast<int64_t>(inputSize),
+ .length = static_cast<int64_t>(op.data.size())};
+ inputSize += op.data.alignedSize();
+ inputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}};
+ }
+ }
+
+ // Model outputs.
+ std::vector<RequestArgument> outputs(testModel.main.outputIndexes.size());
+ size_t outputSize = 0;
+ for (uint32_t i = 0; i < testModel.main.outputIndexes.size(); i++) {
+ const auto& op = testModel.main.operands[testModel.main.outputIndexes[i]];
+
+ // In the case of zero-sized output, we should at least provide a one-byte buffer.
+ // This is because zero-sized tensors are only supported internally to the driver, or
+ // reported in output shapes. It is illegal for the client to pre-specify a zero-sized
+ // tensor as model output. Otherwise, we will have two semantic conflicts:
+ // - "Zero dimension" conflicts with "unspecified dimension".
+ // - "Omitted operand buffer" conflicts with "zero-sized operand buffer".
+ size_t bufferSize = std::max<size_t>(op.data.size(), 1);
+
+ DataLocation loc = {.poolIndex = kOutputPoolIndex,
+ .offset = static_cast<int64_t>(outputSize),
+ .length = static_cast<int64_t>(bufferSize)};
+ outputSize += op.data.size() == 0 ? TestBuffer::kAlignment : op.data.alignedSize();
+ outputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}};
+ }
+
+ // Allocate memory pools.
+ if (memoryType == MemoryType::ASHMEM) {
+ mInputMemory = TestAshmem::create(inputSize);
+ mOutputMemory = TestAshmem::create(outputSize);
+ } else {
+ mInputMemory = TestBlobAHWB::create(inputSize);
+ mOutputMemory = TestBlobAHWB::create(outputSize);
+ }
+ CHECK_NE(mInputMemory, nullptr);
+ CHECK_NE(mOutputMemory, nullptr);
+
+ auto copiedInputMemory = utils::clone(*mInputMemory->getAidlMemory());
+ CHECK(copiedInputMemory.has_value()) << copiedInputMemory.error().message;
+ auto copiedOutputMemory = utils::clone(*mOutputMemory->getAidlMemory());
+ CHECK(copiedOutputMemory.has_value()) << copiedOutputMemory.error().message;
+
+ std::vector<RequestMemoryPool> pools;
+ pools.push_back(RequestMemoryPool::make<RequestMemoryPool::Tag::pool>(
+ std::move(copiedInputMemory).value()));
+ pools.push_back(RequestMemoryPool::make<RequestMemoryPool::Tag::pool>(
+ std::move(copiedOutputMemory).value()));
+
+ // Copy input data to the memory pool.
+ uint8_t* inputPtr = mInputMemory->getPointer();
+ for (uint32_t i = 0; i < testModel.main.inputIndexes.size(); i++) {
+ const auto& op = testModel.main.operands[testModel.main.inputIndexes[i]];
+ if (op.data.size() > 0) {
+ const uint8_t* begin = op.data.get<uint8_t>();
+ const uint8_t* end = begin + op.data.size();
+ std::copy(begin, end, inputPtr + inputs[i].location.offset);
+ }
+ }
+
+ return {.inputs = std::move(inputs), .outputs = std::move(outputs), .pools = std::move(pools)};
+}
+
+std::vector<TestBuffer> ExecutionContext::getOutputBuffers(const Request& request) const {
+ // Copy out output results.
+ uint8_t* outputPtr = mOutputMemory->getPointer();
+ std::vector<TestBuffer> outputBuffers;
+ for (const auto& output : request.outputs) {
+ outputBuffers.emplace_back(output.location.length, outputPtr + output.location.offset);
+ }
+ return outputBuffers;
+}
+
+} // namespace aidl::android::hardware::neuralnetworks
diff --git a/neuralnetworks/aidl/vts/functional/Utils.h b/neuralnetworks/aidl/vts/functional/Utils.h
new file mode 100644
index 0000000..266301c
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/Utils.h
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_NEURALNETWORKS_AIDL_UTILS_H
+#define ANDROID_HARDWARE_NEURALNETWORKS_AIDL_UTILS_H
+
+#include <android-base/logging.h>
+#include <android/hardware_buffer.h>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <iosfwd>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <aidl/android/hardware/neuralnetworks/IDevice.h>
+#include <aidl/android/hardware/neuralnetworks/Memory.h>
+#include <aidl/android/hardware/neuralnetworks/Operand.h>
+#include <aidl/android/hardware/neuralnetworks/OperandType.h>
+#include <aidl/android/hardware/neuralnetworks/Priority.h>
+#include <aidl/android/hardware/neuralnetworks/Request.h>
+
+#include <TestHarness.h>
+#include <nnapi/SharedMemory.h>
+
+namespace aidl::android::hardware::neuralnetworks {
+
+namespace nn = ::android::nn;
+
+inline constexpr Priority kDefaultPriority = Priority::MEDIUM;
+
+inline constexpr Timing kNoTiming = {.timeOnDevice = -1, .timeInDriver = -1};
+inline constexpr int64_t kNoDeadline = -1;
+inline constexpr int64_t kOmittedTimeoutDuration = -1;
+inline constexpr int64_t kNoDuration = -1;
+inline const std::vector<uint8_t> kEmptyCacheToken(IDevice::BYTE_SIZE_OF_CACHE_TOKEN);
+
+// Returns the amount of space needed to store a value of the specified type.
+//
+// Aborts if the specified type is an extension type or OEM type.
+uint32_t sizeOfData(OperandType type);
+
+// Returns the amount of space needed to store a value of the dimensions and
+// type of this operand. For a non-extension, non-OEM tensor with unspecified
+// rank or at least one unspecified dimension, returns zero.
+//
+// Aborts if the specified type is an extension type or OEM type.
+uint32_t sizeOfData(const Operand& operand);
+
+// Convenience class to manage the lifetime of memory resources.
+class TestMemoryBase {
+ DISALLOW_COPY_AND_ASSIGN(TestMemoryBase);
+
+ public:
+ TestMemoryBase() = default;
+ virtual ~TestMemoryBase() = default;
+ uint8_t* getPointer() const { return mPtr; }
+ const Memory* getAidlMemory() const { return &mAidlMemory; }
+
+ protected:
+ uint8_t* mPtr = nullptr;
+ Memory mAidlMemory;
+ bool mIsValid = false;
+};
+
+class TestAshmem : public TestMemoryBase {
+ public:
+ static std::unique_ptr<TestAshmem> create(uint32_t size);
+
+ // Prefer TestAshmem::create.
+ // The constructor calls initialize, which constructs the memory resources. This is a workaround
+ // that gtest macros cannot be used directly in a constructor.
+ TestAshmem(uint32_t size) { initialize(size); }
+
+ private:
+ void initialize(uint32_t size);
+ nn::Mapping mMappedMemory;
+};
+
+class TestBlobAHWB : public TestMemoryBase {
+ public:
+ static std::unique_ptr<TestBlobAHWB> create(uint32_t size);
+
+ // Prefer TestBlobAHWB::create.
+ // The constructor calls initialize, which constructs the memory resources. This is a
+ // workaround that gtest macros cannot be used directly in a constructor.
+ TestBlobAHWB(uint32_t size) { initialize(size); }
+ ~TestBlobAHWB();
+
+ private:
+ void initialize(uint32_t size);
+ AHardwareBuffer* mAhwb = nullptr;
+ nn::Mapping mMapping;
+};
+
+enum class MemoryType { ASHMEM, BLOB_AHWB, DEVICE };
+
+// Manages the lifetime of memory resources used in an execution.
+class ExecutionContext {
+ DISALLOW_COPY_AND_ASSIGN(ExecutionContext);
+
+ public:
+ static constexpr uint32_t kInputPoolIndex = 0;
+ static constexpr uint32_t kOutputPoolIndex = 1;
+
+ ExecutionContext() = default;
+
+ // Create HIDL Request from the TestModel struct.
+ Request createRequest(const test_helper::TestModel& testModel,
+ MemoryType memoryType = MemoryType::ASHMEM);
+
+ // After execution, copy out output results from the output memory pool.
+ std::vector<test_helper::TestBuffer> getOutputBuffers(const Request& request) const;
+
+ private:
+ std::unique_ptr<TestMemoryBase> mInputMemory, mOutputMemory;
+};
+
+template <typename Type>
+using Named = std::pair<std::string, Type>;
+
+template <typename Type>
+const std::string& getName(const Named<Type>& namedData) {
+ return namedData.first;
+}
+
+template <typename Type>
+const Type& getData(const Named<Type>& namedData) {
+ return namedData.second;
+}
+
+std::string gtestCompliantName(std::string name);
+
+// pretty-print values for error messages
+::std::ostream& operator<<(::std::ostream& os, ErrorStatus errorStatus);
+
+} // namespace aidl::android::hardware::neuralnetworks
+
+#endif // ANDROID_HARDWARE_NEURALNETWORKS_AIDL_UTILS_H
diff --git a/neuralnetworks/aidl/vts/functional/ValidateModel.cpp b/neuralnetworks/aidl/vts/functional/ValidateModel.cpp
new file mode 100644
index 0000000..b84d981
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/ValidateModel.cpp
@@ -0,0 +1,1338 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "neuralnetworks_aidl_hal_test"
+
+#include <aidl/android/hardware/common/NativeHandle.h>
+#include <android/binder_auto_utils.h>
+#include <android/binder_enums.h>
+#include <android/binder_interface_utils.h>
+#include <nnapi/TypeUtils.h>
+#include <nnapi/hal/aidl/Conversions.h>
+#include <nnapi/hal/aidl/Utils.h>
+
+#include <optional>
+#include <type_traits>
+#include <utility>
+
+#include "Callbacks.h"
+#include "GeneratedTestHarness.h"
+#include "Utils.h"
+#include "VtsHalNeuralnetworks.h"
+
+namespace aidl::android::hardware::neuralnetworks::vts::functional {
+
+using common::NativeHandle;
+using implementation::PreparedModelCallback;
+
+using PrepareModelMutation = std::function<void(Model*, ExecutionPreference*, Priority*)>;
+
+///////////////////////// UTILITY FUNCTIONS /////////////////////////
+
+static void validateGetSupportedOperations(const std::shared_ptr<IDevice>& device,
+ const std::string& message, const Model& model) {
+ SCOPED_TRACE(message + " [getSupportedOperations]");
+
+ std::vector<bool> supported;
+ const auto retStatus = device->getSupportedOperations(model, &supported);
+
+ ASSERT_FALSE(retStatus.isOk());
+ ASSERT_EQ(retStatus.getExceptionCode(), EX_SERVICE_SPECIFIC);
+ ASSERT_EQ(static_cast<ErrorStatus>(retStatus.getServiceSpecificError()),
+ ErrorStatus::INVALID_ARGUMENT);
+}
+
+static void validatePrepareModel(const std::shared_ptr<IDevice>& device, const std::string& message,
+ const Model& model, ExecutionPreference preference,
+ Priority priority) {
+ SCOPED_TRACE(message + " [prepareModel]");
+
+ std::shared_ptr<PreparedModelCallback> preparedModelCallback =
+ ndk::SharedRefBase::make<PreparedModelCallback>();
+ const auto prepareLaunchStatus =
+ device->prepareModel(model, preference, priority, kNoDeadline, {}, {}, kEmptyCacheToken,
+ preparedModelCallback);
+ ASSERT_FALSE(prepareLaunchStatus.isOk());
+ ASSERT_EQ(prepareLaunchStatus.getExceptionCode(), EX_SERVICE_SPECIFIC);
+ ASSERT_EQ(static_cast<ErrorStatus>(prepareLaunchStatus.getServiceSpecificError()),
+ ErrorStatus::INVALID_ARGUMENT);
+
+ preparedModelCallback->wait();
+ ErrorStatus prepareReturnStatus = preparedModelCallback->getStatus();
+ ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, prepareReturnStatus);
+ std::shared_ptr<IPreparedModel> preparedModel = preparedModelCallback->getPreparedModel();
+ ASSERT_EQ(nullptr, preparedModel.get());
+}
+
+static bool validExecutionPreference(ExecutionPreference preference) {
+ return preference == ExecutionPreference::LOW_POWER ||
+ preference == ExecutionPreference::FAST_SINGLE_ANSWER ||
+ preference == ExecutionPreference::SUSTAINED_SPEED;
+}
+
+static bool validExecutionPriority(Priority priority) {
+ return priority == Priority::LOW || priority == Priority::MEDIUM || priority == Priority::HIGH;
+}
+
+// Primary validation function. This function will take a valid model, apply a
+// mutation to invalidate the model, the execution preference, or the priority,
+// then pass these to supportedOperations and/or prepareModel if that method is
+// called with an invalid argument.
+static void validate(const std::shared_ptr<IDevice>& device, const std::string& message,
+ const Model& originalModel, const PrepareModelMutation& mutate) {
+ Model model = utils::clone(originalModel).value();
+ ExecutionPreference preference = ExecutionPreference::FAST_SINGLE_ANSWER;
+ Priority priority = kDefaultPriority;
+ mutate(&model, &preference, &priority);
+
+ if (validExecutionPreference(preference) && validExecutionPriority(priority)) {
+ validateGetSupportedOperations(device, message, model);
+ }
+
+ validatePrepareModel(device, message, model, preference, priority);
+}
+
+static uint32_t addOperand(Model* model) {
+ model->main.operands.push_back({
+ .type = OperandType::INT32,
+ .dimensions = {},
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::SUBGRAPH_INPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ });
+ return model->main.operands.size() - 1;
+}
+
+static uint32_t addOperand(Model* model, OperandLifeTime lifetime) {
+ uint32_t index = addOperand(model);
+ model->main.operands[index].lifetime = lifetime;
+ return index;
+}
+
+// If we introduce a CONSTANT_COPY for an operand of size operandSize,
+// how much will this increase the size of the model? This assumes
+// that we can (re)use all of model.operandValues for the operand
+// value.
+static size_t constantCopyExtraSize(const Model& model, size_t operandSize) {
+ const size_t operandValuesSize = model.operandValues.size();
+ return (operandValuesSize < operandSize) ? (operandSize - operandValuesSize) : 0;
+}
+
+// Highly specialized utility routine for converting an operand to
+// CONSTANT_COPY lifetime.
+//
+// Expects that:
+// - operand has a known size
+// - operand->lifetime has already been set to CONSTANT_COPY
+// - operand->location has been zeroed out
+//
+// Does the following:
+// - initializes operand->location to point to the beginning of model->operandValues
+// - resizes model->operandValues (if necessary) to be large enough for the operand
+// value, padding it with zeroes on the end
+//
+// Potential problem:
+// By changing the operand to CONSTANT_COPY lifetime, this function is effectively initializing the
+// operand with unspecified (but deterministic) data. This means that the model may be invalidated
+// in two ways: not only is the lifetime of CONSTANT_COPY invalid, but the operand's value in the
+// graph may also be invalid (e.g., if the operand is used as an activation code and has an invalid
+// value). For now, this should be fine because it just means we're not testing what we think we're
+// testing in certain cases; but we can handwave this and assume we're probabilistically likely to
+// exercise the validation code over the span of the entire test set and operand space.
+//
+// Aborts if the specified operand type is an extension type or OEM type.
+static void becomeConstantCopy(Model* model, Operand* operand) {
+ // sizeOfData will abort if the specified type is an extension type or OEM type.
+ const size_t sizeOfOperand = sizeOfData(*operand);
+ EXPECT_NE(sizeOfOperand, size_t(0));
+ operand->location.poolIndex = 0;
+ operand->location.offset = 0;
+ operand->location.length = sizeOfOperand;
+ if (model->operandValues.size() < sizeOfOperand) {
+ model->operandValues.resize(sizeOfOperand);
+ }
+}
+
+// The sizeForBinder() functions estimate the size of the
+// representation of a value when sent to binder. It's probably a bit
+// of an under-estimate, because we don't know the size of the
+// metadata in the binder format (e.g., representation of the size of
+// a vector); but at least it adds up "big" things like vector
+// contents. However, it doesn't treat inter-field or end-of-struct
+// padding in a methodical way -- there's no attempt to be consistent
+// in whether or not padding in the native (C++) representation
+// contributes to the estimated size for the binder representation;
+// and there's no attempt to understand what padding (if any) is
+// needed in the binder representation.
+//
+// This assumes that non-metadata uses a fixed length encoding (e.g.,
+// a uint32_t is always encoded in sizeof(uint32_t) bytes, rather than
+// using an encoding whose length is related to the magnitude of the
+// encoded value).
+
+template <typename Type>
+static size_t sizeForBinder(const Type& val) {
+ static_assert(std::is_trivially_copyable_v<std::remove_reference_t<Type>>,
+ "expected a trivially copyable type");
+ return sizeof(val);
+}
+
+template <typename Type>
+static size_t sizeForBinder(const std::vector<Type>& vec) {
+ return std::accumulate(vec.begin(), vec.end(), 0,
+ [](size_t acc, const Type& x) { return acc + sizeForBinder(x); });
+}
+
+template <>
+size_t sizeForBinder(const SymmPerChannelQuantParams& symmPerChannelQuantParams) {
+ size_t size = 0;
+
+ size += sizeForBinder(symmPerChannelQuantParams.scales);
+ size += sizeForBinder(symmPerChannelQuantParams.channelDim);
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const std::optional<OperandExtraParams>& optionalExtraParams) {
+ if (!optionalExtraParams.has_value()) {
+ return 0;
+ }
+ const auto& extraParams = optionalExtraParams.value();
+ using Tag = OperandExtraParams::Tag;
+ switch (extraParams.getTag()) {
+ case Tag::channelQuant:
+ return sizeForBinder(extraParams.get<Tag::channelQuant>());
+ case Tag::extension:
+ return sizeForBinder(extraParams.get<Tag::extension>());
+ }
+ LOG(FATAL) << "Unrecognized extraParams tag: " << static_cast<int>(extraParams.getTag());
+ return 0;
+}
+
+template <>
+size_t sizeForBinder(const Operand& operand) {
+ size_t size = 0;
+
+ size += sizeForBinder(operand.type);
+ size += sizeForBinder(operand.dimensions);
+ size += sizeForBinder(operand.scale);
+ size += sizeForBinder(operand.zeroPoint);
+ size += sizeForBinder(operand.lifetime);
+ size += sizeForBinder(operand.location);
+ size += sizeForBinder(operand.extraParams);
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const Operation& operation) {
+ size_t size = 0;
+
+ size += sizeForBinder(operation.type);
+ size += sizeForBinder(operation.inputs);
+ size += sizeForBinder(operation.outputs);
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const std::string& name) {
+ return name.size();
+}
+
+template <>
+size_t sizeForBinder(const Memory& memory) {
+ // This is just a guess.
+
+ size_t size = 0;
+ const NativeHandle& handle = memory.handle;
+ size += sizeof(decltype(handle.fds)::value_type) * handle.fds.size();
+ size += sizeof(decltype(handle.ints)::value_type) * handle.ints.size();
+ size += sizeForBinder(memory.name);
+ size += sizeof(memory);
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const Subgraph& subgraph) {
+ size_t size = 0;
+
+ size += sizeForBinder(subgraph.operands);
+ size += sizeForBinder(subgraph.operations);
+ size += sizeForBinder(subgraph.inputIndexes);
+ size += sizeForBinder(subgraph.outputIndexes);
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const ExtensionNameAndPrefix& extensionNameToPrefix) {
+ size_t size = 0;
+
+ size += sizeForBinder(extensionNameToPrefix.name);
+ size += sizeForBinder(extensionNameToPrefix.prefix);
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const Model& model) {
+ size_t size = 0;
+
+ size += sizeForBinder(model.main);
+ size += sizeForBinder(model.referenced);
+ size += sizeForBinder(model.operandValues);
+ size += sizeForBinder(model.pools);
+ size += sizeForBinder(model.relaxComputationFloat32toFloat16);
+ size += sizeForBinder(model.extensionNameToPrefix);
+
+ return size;
+}
+
+// https://developer.android.com/reference/android/os/TransactionTooLargeException.html
+//
+// "The Binder transaction buffer has a limited fixed size,
+// currently 1Mb, which is shared by all transactions in progress
+// for the process."
+//
+// Will our representation fit under this limit? There are two complications:
+// - Our representation size is just approximate (see sizeForBinder()).
+// - This object may not be the only occupant of the Binder transaction buffer.
+// So we'll be very conservative: We want the representation size to be no
+// larger than half the transaction buffer size.
+//
+// If our representation grows large enough that it still fits within
+// the transaction buffer but combined with other transactions may
+// exceed the buffer size, then we may see intermittent HAL transport
+// errors.
+static bool exceedsBinderSizeLimit(size_t representationSize) {
+ // Instead of using this fixed buffer size, we might instead be able to use
+ // ProcessState::self()->getMmapSize(). However, this has a potential
+ // problem: The binder/mmap size of the current process does not necessarily
+ // indicate the binder/mmap size of the service (i.e., the other process).
+ // The only way it would be a good indication is if both the current process
+ // and the service use the default size.
+ static const size_t kHalfBufferSize = 1024 * 1024 / 2;
+
+ return representationSize > kHalfBufferSize;
+}
+
+///////////////////////// VALIDATE EXECUTION ORDER ////////////////////////////
+
+static void mutateExecutionOrderTest(const std::shared_ptr<IDevice>& device, const Model& model,
+ const std::vector<uint32_t>& numberOfConsumers) {
+ for (size_t operation = 0; operation < model.main.operations.size(); ++operation) {
+ const Operation& operationObj = model.main.operations[operation];
+ for (uint32_t input : operationObj.inputs) {
+ if (model.main.operands[input].lifetime == OperandLifeTime::TEMPORARY_VARIABLE ||
+ model.main.operands[input].lifetime == OperandLifeTime::SUBGRAPH_OUTPUT) {
+ // This operation reads an operand written by some
+ // other operation. Move this operation to the
+ // beginning of the sequence, ensuring that it reads
+ // the operand before that operand is written, thereby
+ // violating execution order rules.
+ const std::string message = "mutateExecutionOrderTest: operation " +
+ std::to_string(operation) + " is a reader";
+ validate(device, message, model,
+ [operation](Model* model, ExecutionPreference*, Priority*) {
+ auto& operations = model->main.operations;
+ std::rotate(operations.begin(), operations.begin() + operation,
+ operations.begin() + operation + 1);
+ });
+ break; // only need to do this once per operation
+ }
+ }
+ for (uint32_t output : operationObj.outputs) {
+ if (numberOfConsumers[output] > 0) {
+ // This operation writes an operand read by some other
+ // operation. Move this operation to the end of the
+ // sequence, ensuring that it writes the operand after
+ // that operand is read, thereby violating execution
+ // order rules.
+ const std::string message = "mutateExecutionOrderTest: operation " +
+ std::to_string(operation) + " is a writer";
+ validate(device, message, model,
+ [operation](Model* model, ExecutionPreference*, Priority*) {
+ auto& operations = model->main.operations;
+ std::rotate(operations.begin() + operation,
+ operations.begin() + operation + 1, operations.end());
+ });
+ break; // only need to do this once per operation
+ }
+ }
+ }
+}
+
+///////////////////////// VALIDATE MODEL OPERAND TYPE /////////////////////////
+
+static const int32_t invalidOperandTypes[] = {
+ -1,
+ static_cast<int32_t>(*(ndk::enum_range<OperandType>().end() - 1)) + 1,
+};
+
+static void mutateOperandTypeTest(const std::shared_ptr<IDevice>& device, const Model& model) {
+ for (size_t operand = 0; operand < model.main.operands.size(); ++operand) {
+ for (int32_t invalidOperandType : invalidOperandTypes) {
+ const std::string message = "mutateOperandTypeTest: operand " +
+ std::to_string(operand) + " set to value " +
+ std::to_string(invalidOperandType);
+ validate(device, message, model,
+ [operand, invalidOperandType](Model* model, ExecutionPreference*, Priority*) {
+ model->main.operands[operand].type =
+ static_cast<OperandType>(invalidOperandType);
+ });
+ }
+ }
+}
+
+///////////////////////// VALIDATE OPERAND RANK /////////////////////////
+
+static uint32_t getInvalidRank(OperandType type) {
+ switch (type) {
+ case OperandType::FLOAT16:
+ case OperandType::FLOAT32:
+ case OperandType::INT32:
+ case OperandType::UINT32:
+ case OperandType::BOOL:
+ return 1;
+ case OperandType::TENSOR_BOOL8:
+ case OperandType::TENSOR_FLOAT16:
+ case OperandType::TENSOR_FLOAT32:
+ case OperandType::TENSOR_INT32:
+ case OperandType::TENSOR_QUANT8_ASYMM:
+ case OperandType::TENSOR_QUANT8_SYMM:
+ case OperandType::TENSOR_QUANT16_ASYMM:
+ case OperandType::TENSOR_QUANT16_SYMM:
+ case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL:
+ return 0;
+ default:
+ return 0;
+ }
+}
+
+static void mutateOperandRankTest(const std::shared_ptr<IDevice>& device, const Model& model) {
+ for (size_t operand = 0; operand < model.main.operands.size(); ++operand) {
+ const uint32_t invalidRank = getInvalidRank(model.main.operands[operand].type);
+ if (invalidRank == 0) {
+ continue;
+ }
+ const std::string message = "mutateOperandRankTest: operand " + std::to_string(operand) +
+ " has rank of " + std::to_string(invalidRank);
+ validate(device, message, model,
+ [operand, invalidRank](Model* model, ExecutionPreference*, Priority*) {
+ model->main.operands[operand].dimensions =
+ std::vector<int32_t>(invalidRank, 0);
+ });
+ }
+}
+
+///////////////////////// VALIDATE OPERAND SCALE /////////////////////////
+
+static float getInvalidScale(OperandType type) {
+ switch (type) {
+ case OperandType::FLOAT16:
+ case OperandType::FLOAT32:
+ case OperandType::INT32:
+ case OperandType::UINT32:
+ case OperandType::BOOL:
+ case OperandType::TENSOR_BOOL8:
+ case OperandType::TENSOR_FLOAT16:
+ case OperandType::TENSOR_FLOAT32:
+ case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL:
+ case OperandType::SUBGRAPH:
+ return 1.0f;
+ case OperandType::TENSOR_INT32:
+ return -1.0f;
+ case OperandType::TENSOR_QUANT8_SYMM:
+ case OperandType::TENSOR_QUANT8_ASYMM:
+ case OperandType::TENSOR_QUANT16_ASYMM:
+ case OperandType::TENSOR_QUANT16_SYMM:
+ return 0.0f;
+ default:
+ return 0.0f;
+ }
+}
+
+static void mutateOperandScaleTest(const std::shared_ptr<IDevice>& device, const Model& model) {
+ for (size_t operand = 0; operand < model.main.operands.size(); ++operand) {
+ const float invalidScale = getInvalidScale(model.main.operands[operand].type);
+ const std::string message = "mutateOperandScaleTest: operand " + std::to_string(operand) +
+ " has scale of " + std::to_string(invalidScale);
+ validate(device, message, model,
+ [operand, invalidScale](Model* model, ExecutionPreference*, Priority*) {
+ model->main.operands[operand].scale = invalidScale;
+ });
+ }
+}
+
+///////////////////////// VALIDATE OPERAND ZERO POINT /////////////////////////
+
+static std::vector<int32_t> getInvalidZeroPoints(OperandType type) {
+ switch (type) {
+ case OperandType::FLOAT16:
+ case OperandType::FLOAT32:
+ case OperandType::INT32:
+ case OperandType::UINT32:
+ case OperandType::BOOL:
+ case OperandType::TENSOR_BOOL8:
+ case OperandType::TENSOR_FLOAT16:
+ case OperandType::TENSOR_FLOAT32:
+ case OperandType::TENSOR_INT32:
+ case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL:
+ case OperandType::SUBGRAPH:
+ return {1};
+ case OperandType::TENSOR_QUANT8_ASYMM:
+ return {-1, 256};
+ case OperandType::TENSOR_QUANT8_SYMM:
+ return {-129, -1, 1, 128};
+ case OperandType::TENSOR_QUANT16_ASYMM:
+ return {-1, 65536};
+ case OperandType::TENSOR_QUANT16_SYMM:
+ return {-32769, -1, 1, 32768};
+ default:
+ return {};
+ }
+}
+
+static void mutateOperandZeroPointTest(const std::shared_ptr<IDevice>& device, const Model& model) {
+ for (size_t operand = 0; operand < model.main.operands.size(); ++operand) {
+ const std::vector<int32_t> invalidZeroPoints =
+ getInvalidZeroPoints(model.main.operands[operand].type);
+ for (int32_t invalidZeroPoint : invalidZeroPoints) {
+ const std::string message = "mutateOperandZeroPointTest: operand " +
+ std::to_string(operand) + " has zero point of " +
+ std::to_string(invalidZeroPoint);
+ validate(device, message, model,
+ [operand, invalidZeroPoint](Model* model, ExecutionPreference*, Priority*) {
+ model->main.operands[operand].zeroPoint = invalidZeroPoint;
+ });
+ }
+ }
+}
+
+///////////////////////// VALIDATE OPERAND LIFETIME /////////////////////////////////////////////
+
+static std::vector<OperandLifeTime> getInvalidLifeTimes(const Model& model, size_t modelSize,
+ const Operand& operand) {
+ // TODO: Support OperandLifeTime::CONSTANT_REFERENCE as an invalid lifetime
+ // TODO: Support OperandLifeTime::NO_VALUE as an invalid lifetime
+
+ // Ways to get an invalid lifetime:
+ // - change whether a lifetime means an operand should have a writer
+ std::vector<OperandLifeTime> ret;
+ switch (operand.lifetime) {
+ case OperandLifeTime::SUBGRAPH_OUTPUT:
+ case OperandLifeTime::TEMPORARY_VARIABLE:
+ ret = {
+ OperandLifeTime::SUBGRAPH_INPUT,
+ OperandLifeTime::CONSTANT_COPY,
+ };
+ break;
+ case OperandLifeTime::CONSTANT_COPY:
+ case OperandLifeTime::CONSTANT_POOL:
+ case OperandLifeTime::SUBGRAPH_INPUT:
+ ret = {
+ OperandLifeTime::TEMPORARY_VARIABLE,
+ OperandLifeTime::SUBGRAPH_OUTPUT,
+ };
+ break;
+ case OperandLifeTime::NO_VALUE:
+ // Not enough information to know whether
+ // TEMPORARY_VARIABLE or CONSTANT_COPY would be invalid --
+ // is this operand written (then CONSTANT_COPY would be
+ // invalid) or not (then TEMPORARY_VARIABLE would be
+ // invalid)?
+ break;
+ case OperandLifeTime::SUBGRAPH:
+ break;
+ default:
+ ADD_FAILURE();
+ break;
+ }
+
+ const size_t operandSize = sizeOfData(operand); // will be zero if shape is unknown
+ if (!operandSize ||
+ exceedsBinderSizeLimit(modelSize + constantCopyExtraSize(model, operandSize))) {
+ // Unknown size or too-large size
+ ret.erase(std::remove(ret.begin(), ret.end(), OperandLifeTime::CONSTANT_COPY), ret.end());
+ }
+
+ return ret;
+}
+
+static void mutateOperandLifeTimeTest(const std::shared_ptr<IDevice>& device, const Model& model) {
+ const size_t modelSize = sizeForBinder(model);
+ for (size_t operand = 0; operand < model.main.operands.size(); ++operand) {
+ const std::vector<OperandLifeTime> invalidLifeTimes =
+ getInvalidLifeTimes(model, modelSize, model.main.operands[operand]);
+ for (OperandLifeTime invalidLifeTime : invalidLifeTimes) {
+ const std::string message = "mutateOperandLifetimeTest: operand " +
+ std::to_string(operand) + " has lifetime " +
+ toString(invalidLifeTime) + " instead of lifetime " +
+ toString(model.main.operands[operand].lifetime);
+ validate(device, message, model,
+ [operand, invalidLifeTime](Model* model, ExecutionPreference*, Priority*) {
+ static const DataLocation kZeroDataLocation = {};
+ Operand& operandObj = model->main.operands[operand];
+ switch (operandObj.lifetime) {
+ case OperandLifeTime::SUBGRAPH_INPUT: {
+ auto& inputs = model->main.inputIndexes;
+ inputs.erase(std::remove(inputs.begin(), inputs.end(), operand),
+ inputs.end());
+ break;
+ }
+ case OperandLifeTime::SUBGRAPH_OUTPUT: {
+ auto& outputs = model->main.outputIndexes;
+ outputs.erase(std::remove(outputs.begin(), outputs.end(), operand),
+ outputs.end());
+ break;
+ }
+ default:
+ break;
+ }
+ operandObj.lifetime = invalidLifeTime;
+ operandObj.location = kZeroDataLocation;
+ switch (invalidLifeTime) {
+ case OperandLifeTime::CONSTANT_COPY: {
+ becomeConstantCopy(model, &operandObj);
+ break;
+ }
+ case OperandLifeTime::SUBGRAPH_INPUT:
+ model->main.inputIndexes.push_back(operand);
+ break;
+ case OperandLifeTime::SUBGRAPH_OUTPUT:
+ model->main.outputIndexes.push_back(operand);
+ break;
+ default:
+ break;
+ }
+ });
+ }
+ }
+}
+
+///////////////////////// VALIDATE OPERAND INPUT-or-OUTPUT //////////////////////////////////////
+
+static std::optional<OperandLifeTime> getInputOutputLifeTime(const Model& model, size_t modelSize,
+ const Operand& operand) {
+ // Ways to get an invalid lifetime (with respect to model inputIndexes and outputIndexes):
+ // - change whether a lifetime means an operand is a model input, a model output, or neither
+ // - preserve whether or not a lifetime means an operand should have a writer
+ switch (operand.lifetime) {
+ case OperandLifeTime::CONSTANT_COPY:
+ case OperandLifeTime::CONSTANT_POOL:
+ return OperandLifeTime::SUBGRAPH_INPUT;
+ case OperandLifeTime::SUBGRAPH_INPUT: {
+ const size_t operandSize = sizeOfData(operand); // will be zero if shape is unknown
+ if (!operandSize ||
+ exceedsBinderSizeLimit(modelSize + constantCopyExtraSize(model, operandSize))) {
+ // Unknown size or too-large size
+ break;
+ }
+ return OperandLifeTime::CONSTANT_COPY;
+ }
+ case OperandLifeTime::SUBGRAPH_OUTPUT:
+ return OperandLifeTime::TEMPORARY_VARIABLE;
+ case OperandLifeTime::TEMPORARY_VARIABLE:
+ return OperandLifeTime::SUBGRAPH_OUTPUT;
+ case OperandLifeTime::NO_VALUE:
+ // Not enough information to know whether
+ // TEMPORARY_VARIABLE or CONSTANT_COPY would be an
+ // appropriate choice -- is this operand written (then
+ // TEMPORARY_VARIABLE would be appropriate) or not (then
+ // CONSTANT_COPY would be appropriate)?
+ break;
+ case OperandLifeTime::SUBGRAPH:
+ break;
+ default:
+ ADD_FAILURE();
+ break;
+ }
+
+ return std::nullopt;
+}
+
+static void mutateOperandInputOutputTest(const std::shared_ptr<IDevice>& device,
+ const Model& model) {
+ const size_t modelSize = sizeForBinder(model);
+ for (size_t operand = 0; operand < model.main.operands.size(); ++operand) {
+ const std::optional<OperandLifeTime> changedLifeTime =
+ getInputOutputLifeTime(model, modelSize, model.main.operands[operand]);
+ if (changedLifeTime) {
+ const std::string message = "mutateOperandInputOutputTest: operand " +
+ std::to_string(operand) + " has lifetime " +
+ toString(*changedLifeTime) + " instead of lifetime " +
+ toString(model.main.operands[operand].lifetime);
+ validate(device, message, model,
+ [operand, changedLifeTime](Model* model, ExecutionPreference*, Priority*) {
+ static const DataLocation kZeroDataLocation = {};
+ Operand& operandObj = model->main.operands[operand];
+ operandObj.lifetime = *changedLifeTime;
+ operandObj.location = kZeroDataLocation;
+ if (*changedLifeTime == OperandLifeTime::CONSTANT_COPY) {
+ becomeConstantCopy(model, &operandObj);
+ }
+ });
+ }
+ }
+}
+
+///////////////////////// VALIDATE OPERAND NUMBER OF WRITERS ////////////////////////////////////
+
+static void mutateOperandAddWriterTest(const std::shared_ptr<IDevice>& device, const Model& model) {
+ for (size_t operation = 0; operation < model.main.operations.size(); ++operation) {
+ for (size_t badOutputNum = 0;
+ badOutputNum < model.main.operations[operation].outputs.size(); ++badOutputNum) {
+ const uint32_t outputOperandIndex =
+ model.main.operations[operation].outputs[badOutputNum];
+ const std::string message = "mutateOperandAddWriterTest: operation " +
+ std::to_string(operation) + " writes to " +
+ std::to_string(outputOperandIndex);
+ // We'll insert a copy of the operation, all of whose
+ // OTHER output operands are newly-created -- i.e.,
+ // there'll only be a duplicate write of ONE of that
+ // operation's output operands.
+ validate(device, message, model,
+ [operation, badOutputNum](Model* model, ExecutionPreference*, Priority*) {
+ Operation newOperation = model->main.operations[operation];
+ for (size_t outputNum = 0; outputNum < newOperation.outputs.size();
+ ++outputNum) {
+ if (outputNum == badOutputNum) continue;
+
+ Operand operandValue =
+ model->main.operands[newOperation.outputs[outputNum]];
+ if (operandValue.lifetime == OperandLifeTime::SUBGRAPH_OUTPUT) {
+ operandValue.lifetime = OperandLifeTime::TEMPORARY_VARIABLE;
+ } else {
+ ASSERT_EQ(operandValue.lifetime,
+ OperandLifeTime::TEMPORARY_VARIABLE);
+ }
+ newOperation.outputs[outputNum] = model->main.operands.size();
+ model->main.operands.push_back(operandValue);
+ }
+ // Where do we insert the extra writer (a new
+ // operation)? It has to be later than all the
+ // writers of its inputs. The easiest thing to do
+ // is to insert it at the end of the operation
+ // sequence.
+ model->main.operations.push_back(newOperation);
+ });
+ }
+ }
+}
+
+///////////////////////// VALIDATE EXTRA ??? /////////////////////////
+
+// TODO: Operand::location
+
+///////////////////////// VALIDATE OPERATION OPERAND TYPE /////////////////////////
+
+static void mutateOperand(Operand* operand, OperandType type) {
+ Operand newOperand = *operand;
+ newOperand.type = type;
+ switch (type) {
+ case OperandType::FLOAT16:
+ case OperandType::FLOAT32:
+ case OperandType::INT32:
+ case OperandType::UINT32:
+ case OperandType::BOOL:
+ newOperand.dimensions = {};
+ newOperand.scale = 0.0f;
+ newOperand.zeroPoint = 0;
+ break;
+ case OperandType::TENSOR_BOOL8:
+ case OperandType::TENSOR_FLOAT16:
+ case OperandType::TENSOR_FLOAT32:
+ newOperand.dimensions = operand->dimensions.size() > 0 ? operand->dimensions
+ : std::vector<int32_t>({1});
+ newOperand.scale = 0.0f;
+ newOperand.zeroPoint = 0;
+ break;
+ case OperandType::TENSOR_INT32:
+ newOperand.dimensions = operand->dimensions.size() > 0 ? operand->dimensions
+ : std::vector<int32_t>({1});
+ newOperand.zeroPoint = 0;
+ break;
+ case OperandType::TENSOR_QUANT8_ASYMM:
+ case OperandType::TENSOR_QUANT8_SYMM:
+ case OperandType::TENSOR_QUANT16_ASYMM:
+ case OperandType::TENSOR_QUANT16_SYMM:
+ newOperand.dimensions = operand->dimensions.size() > 0 ? operand->dimensions
+ : std::vector<int32_t>({1});
+ newOperand.scale = operand->scale != 0.0f ? operand->scale : 1.0f;
+ break;
+ case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL: {
+ newOperand.dimensions = operand->dimensions.size() > 0 ? operand->dimensions
+ : std::vector<int32_t>({1});
+ newOperand.scale = 0.0f;
+ newOperand.zeroPoint = 0;
+
+ SymmPerChannelQuantParams channelQuant;
+ channelQuant.channelDim = 0;
+ channelQuant.scales = std::vector<float>(
+ operand->dimensions.size() > 0 ? static_cast<size_t>(operand->dimensions[0])
+ : 0);
+ for (size_t i = 0; i < channelQuant.scales.size(); ++i) {
+ channelQuant.scales[i] = 1.0f;
+ }
+ newOperand.extraParams->set<OperandExtraParams::Tag::channelQuant>(
+ std::move(channelQuant));
+ } break;
+ default:
+ break;
+ }
+ *operand = newOperand;
+}
+
+static bool mutateOperationOperandTypeSkip(size_t operand, OperandType type, const Model& model) {
+ if (type == model.main.operands[operand].type) {
+ return true;
+ }
+ for (const Operation& operation : model.main.operations) {
+ // Skip mutateOperationOperandTypeTest for the following operations.
+ // - LSH_PROJECTION's second argument is allowed to have any type.
+ // - ARGMIN and ARGMAX's first argument can be any of
+ // TENSOR_(FLOAT16|FLOAT32|INT32|QUANT8_ASYMM).
+ // - CAST's argument can be any of TENSOR_(FLOAT16|FLOAT32|INT32|QUANT8_ASYMM).
+ // - RANDOM_MULTINOMIAL's argument can be either TENSOR_FLOAT16 or TENSOR_FLOAT32.
+ // - DEQUANTIZE input can be any of
+ // TENSOR_(QUANT8_ASYMM|QUANT8_ASYMM_SIGNED|QUANT8_SYMM|QUANT8_SYMM_PER_CHANNEL),
+ // output can be of either TENSOR_FLOAT16 or TENSOR_FLOAT32.
+ // - QUANTIZE input can be either TENSOR_FLOAT16 or TENSOR_FLOAT32
+ // - CONV_2D filter type (arg 1) can be QUANT8_ASYMM or QUANT8_SYMM_PER_CHANNEL
+ // - DEPTHWISE_CONV_2D filter type (arg 1) can be QUANT8_ASYMM or QUANT8_SYMM_PER_CHANNEL
+ // - GROUPED_CONV_2D filter type (arg 1) can be QUANT8_ASYMM or QUANT8_SYMM_PER_CHANNEL
+ // - TRANSPOSE_CONV_2D filter type (arg 1) can be QUANT8_ASYMM or QUANT8_SYMM_PER_CHANNEL
+ // - AXIS_ALIGNED_BBOX_TRANSFORM bounding boxes (arg 1) can be of
+ // TENSOR_QUANT8_ASYMM or TENSOR_QUANT8_ASYMM_SIGNED.
+ // - RANK's input can have any TENSOR_* type.
+ switch (operation.type) {
+ case OperationType::LSH_PROJECTION: {
+ if (operand == operation.inputs[1]) {
+ return true;
+ }
+ } break;
+ case OperationType::CAST:
+ case OperationType::ARGMAX:
+ case OperationType::ARGMIN: {
+ if (type == OperandType::TENSOR_FLOAT16 || type == OperandType::TENSOR_FLOAT32 ||
+ type == OperandType::TENSOR_INT32 || type == OperandType::TENSOR_QUANT8_ASYMM ||
+ type == OperandType::TENSOR_QUANT8_ASYMM_SIGNED) {
+ return true;
+ }
+ } break;
+ case OperationType::QUANTIZE: {
+ if (operand == operation.inputs[0] &&
+ (type == OperandType::TENSOR_FLOAT16 || type == OperandType::TENSOR_FLOAT32)) {
+ return true;
+ }
+ if (operand == operation.outputs[0] &&
+ (type == OperandType::TENSOR_QUANT8_ASYMM ||
+ type == OperandType::TENSOR_QUANT8_ASYMM_SIGNED)) {
+ return true;
+ }
+ } break;
+ case OperationType::RANDOM_MULTINOMIAL: {
+ if (operand == operation.inputs[0] &&
+ (type == OperandType::TENSOR_FLOAT16 || type == OperandType::TENSOR_FLOAT32)) {
+ return true;
+ }
+ } break;
+ case OperationType::DEQUANTIZE: {
+ if (operand == operation.inputs[0] &&
+ (type == OperandType::TENSOR_QUANT8_ASYMM ||
+ type == OperandType::TENSOR_QUANT8_ASYMM_SIGNED ||
+ type == OperandType::TENSOR_QUANT8_SYMM ||
+ type == OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL)) {
+ return true;
+ }
+ if (operand == operation.outputs[0] &&
+ (type == OperandType::TENSOR_FLOAT16 || type == OperandType::TENSOR_FLOAT32)) {
+ return true;
+ }
+ } break;
+ case OperationType::TRANSPOSE_CONV_2D:
+ case OperationType::GROUPED_CONV_2D:
+ case OperationType::DEPTHWISE_CONV_2D:
+ case OperationType::CONV_2D: {
+ if (operand == operation.inputs[1] &&
+ (type == OperandType::TENSOR_QUANT8_ASYMM ||
+ type == OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL)) {
+ return true;
+ }
+ } break;
+ case OperationType::AXIS_ALIGNED_BBOX_TRANSFORM: {
+ if (operand == operation.inputs[1] &&
+ (type == OperandType::TENSOR_QUANT8_ASYMM ||
+ type == OperandType::TENSOR_QUANT8_ASYMM_SIGNED)) {
+ return true;
+ }
+ } break;
+ case OperationType::RANK: {
+ if (operand == operation.inputs[0] &&
+ (type == OperandType::TENSOR_FLOAT16 || type == OperandType::TENSOR_FLOAT32 ||
+ type == OperandType::TENSOR_INT32 ||
+ type == OperandType::TENSOR_QUANT8_ASYMM ||
+ type == OperandType::TENSOR_QUANT16_SYMM ||
+ type == OperandType::TENSOR_BOOL8 ||
+ type == OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL ||
+ type == OperandType::TENSOR_QUANT16_ASYMM ||
+ type == OperandType::TENSOR_QUANT8_SYMM ||
+ type == OperandType::TENSOR_QUANT8_ASYMM_SIGNED)) {
+ return true;
+ }
+ } break;
+ default:
+ break;
+ }
+ }
+ return false;
+}
+
+static void mutateOperationOperandTypeTest(const std::shared_ptr<IDevice>& device,
+ const Model& model) {
+ for (size_t operand = 0; operand < model.main.operands.size(); ++operand) {
+ for (OperandType invalidOperandType : ndk::enum_range<OperandType>()) {
+ if (mutateOperationOperandTypeSkip(operand, invalidOperandType, model)) {
+ continue;
+ }
+ const std::string message = "mutateOperationOperandTypeTest: operand " +
+ std::to_string(operand) + " set to type " +
+ toString(invalidOperandType);
+ validate(device, message, model,
+ [operand, invalidOperandType](Model* model, ExecutionPreference*, Priority*) {
+ mutateOperand(&model->main.operands[operand], invalidOperandType);
+ });
+ }
+ }
+}
+
+///////////////////////// VALIDATE MODEL OPERATION TYPE /////////////////////////
+
+static const int32_t invalidOperationTypes[] = {
+ -1,
+ static_cast<int32_t>(*(ndk::enum_range<OperationType>().end() - 1)) + 1,
+};
+
+static void mutateOperationTypeTest(const std::shared_ptr<IDevice>& device, const Model& model) {
+ for (size_t operation = 0; operation < model.main.operations.size(); ++operation) {
+ for (int32_t invalidOperationType : invalidOperationTypes) {
+ const std::string message = "mutateOperationTypeTest: operation " +
+ std::to_string(operation) + " set to value " +
+ std::to_string(invalidOperationType);
+ validate(device, message, model,
+ [operation, invalidOperationType](Model* model, ExecutionPreference*,
+ Priority*) {
+ model->main.operations[operation].type =
+ static_cast<OperationType>(invalidOperationType);
+ });
+ }
+ }
+}
+
+///////////////////////// VALIDATE MODEL OPERATION INPUT OPERAND INDEX /////////////////////////
+
+static void mutateOperationInputOperandIndexTest(const std::shared_ptr<IDevice>& device,
+ const Model& model) {
+ for (size_t operation = 0; operation < model.main.operations.size(); ++operation) {
+ const uint32_t invalidOperand = model.main.operands.size();
+ for (size_t input = 0; input < model.main.operations[operation].inputs.size(); ++input) {
+ const std::string message = "mutateOperationInputOperandIndexTest: operation " +
+ std::to_string(operation) + " input " +
+ std::to_string(input);
+ validate(device, message, model,
+ [operation, input, invalidOperand](Model* model, ExecutionPreference*,
+ Priority*) {
+ model->main.operations[operation].inputs[input] = invalidOperand;
+ });
+ }
+ }
+}
+
+///////////////////////// VALIDATE MODEL OPERATION OUTPUT OPERAND INDEX /////////////////////////
+
+static void mutateOperationOutputOperandIndexTest(const std::shared_ptr<IDevice>& device,
+ const Model& model) {
+ for (size_t operation = 0; operation < model.main.operations.size(); ++operation) {
+ const uint32_t invalidOperand = model.main.operands.size();
+ for (size_t output = 0; output < model.main.operations[operation].outputs.size();
+ ++output) {
+ const std::string message = "mutateOperationOutputOperandIndexTest: operation " +
+ std::to_string(operation) + " output " +
+ std::to_string(output);
+ validate(device, message, model,
+ [operation, output, invalidOperand](Model* model, ExecutionPreference*,
+ Priority*) {
+ model->main.operations[operation].outputs[output] = invalidOperand;
+ });
+ }
+ }
+}
+
+///////////////////////// VALIDATE MODEL OPERANDS WRITTEN ///////////////////////////////////////
+
+static void mutateOperationRemoveWriteTest(const std::shared_ptr<IDevice>& device,
+ const Model& model,
+ const std::vector<uint32_t>& numberOfConsumers) {
+ for (size_t operation = 0; operation < model.main.operations.size(); ++operation) {
+ for (size_t outputNum = 0; outputNum < model.main.operations[operation].outputs.size();
+ ++outputNum) {
+ const uint32_t outputOperandIndex = model.main.operations[operation].outputs[outputNum];
+ if (numberOfConsumers[outputOperandIndex] > 0) {
+ const std::string message = "mutateOperationRemoveWriteTest: operation " +
+ std::to_string(operation) + " writes to " +
+ std::to_string(outputOperandIndex);
+ validate(device, message, model,
+ [operation, outputNum](Model* model, ExecutionPreference*, Priority*) {
+ int32_t& outputOperandIndex =
+ model->main.operations[operation].outputs[outputNum];
+ Operand operandValue = model->main.operands[outputOperandIndex];
+ if (operandValue.lifetime == OperandLifeTime::SUBGRAPH_OUTPUT) {
+ operandValue.lifetime = OperandLifeTime::TEMPORARY_VARIABLE;
+ } else {
+ ASSERT_EQ(operandValue.lifetime,
+ OperandLifeTime::TEMPORARY_VARIABLE);
+ }
+ outputOperandIndex = model->main.operands.size();
+ model->main.operands.push_back(operandValue);
+ });
+ }
+ }
+ }
+}
+
+///////////////////////// REMOVE OPERAND FROM EVERYTHING /////////////////////////
+
+static void removeValueAndDecrementGreaterValues(std::vector<int32_t>* vec, uint32_t value) {
+ if (vec) {
+ // remove elements matching "value"
+ vec->erase(std::remove(vec->begin(), vec->end(), value), vec->end());
+
+ // decrement elements exceeding "value"
+ std::transform(vec->begin(), vec->end(), vec->begin(),
+ [value](uint32_t v) { return v > value ? v-- : v; });
+ }
+}
+
+static void removeOperand(Model* model, uint32_t index) {
+ model->main.operands.erase(model->main.operands.begin() + index);
+ for (Operation& operation : model->main.operations) {
+ removeValueAndDecrementGreaterValues(&operation.inputs, index);
+ removeValueAndDecrementGreaterValues(&operation.outputs, index);
+ }
+ removeValueAndDecrementGreaterValues(&model->main.inputIndexes, index);
+ removeValueAndDecrementGreaterValues(&model->main.outputIndexes, index);
+}
+
+static bool removeOperandSkip(size_t operandIndex, const Model& model,
+ const std::vector<uint32_t>& numberOfConsumers) {
+ if (numberOfConsumers[operandIndex] == 0) {
+ // Removing an unused operand has no effect.
+ return true;
+ }
+ for (const Operation& operation : model.main.operations) {
+ // Skip removeOperandTest for the following operations.
+ // - SPLIT's outputs are not checked during prepareModel.
+ if (operation.type == OperationType::SPLIT) {
+ for (const size_t index : operation.outputs) {
+ if (index == operandIndex) {
+ return true;
+ }
+ }
+ }
+ // BIDIRECTIONAL_SEQUENCE_LSTM and BIDIRECTIONAL_SEQUENCE_RNN can have
+ // either one, two, three or four outputs depending on their
+ // mergeOutputs parameter and if state outputs are provided.
+ // UNIDIRECTIONAL_SEQUENCE_LSTM and UNIDIRECTIONAL_SEQUENCE_RNN can have
+ // either one or three outputs depending on whether state outputs are
+ // provided.
+ if (operation.type == OperationType::UNIDIRECTIONAL_SEQUENCE_LSTM ||
+ operation.type == OperationType::UNIDIRECTIONAL_SEQUENCE_RNN ||
+ operation.type == OperationType::BIDIRECTIONAL_SEQUENCE_LSTM ||
+ operation.type == OperationType::BIDIRECTIONAL_SEQUENCE_RNN) {
+ for (const size_t index : operation.outputs) {
+ if (index == operandIndex) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+static void removeOperandTest(const std::shared_ptr<IDevice>& device, const Model& model,
+ const std::vector<uint32_t>& numberOfConsumers) {
+ for (size_t operand = 0; operand < model.main.operands.size(); ++operand) {
+ if (removeOperandSkip(operand, model, numberOfConsumers)) {
+ continue;
+ }
+ const std::string message = "removeOperandTest: operand " + std::to_string(operand);
+ validate(device, message, model, [operand](Model* model, ExecutionPreference*, Priority*) {
+ removeOperand(model, operand);
+ });
+ }
+}
+
+///////////////////////// REMOVE OPERATION /////////////////////////
+
+static void removeOperation(Model* model, uint32_t index) {
+ auto& operations = model->main.operations;
+ operations.erase(operations.begin() + index);
+}
+
+static void removeOperationTest(const std::shared_ptr<IDevice>& device, const Model& model) {
+ for (size_t operation = 0; operation < model.main.operations.size(); ++operation) {
+ const std::string message = "removeOperationTest: operation " + std::to_string(operation);
+ validate(device, message, model,
+ [operation](Model* model, ExecutionPreference*, Priority*) {
+ removeOperation(model, operation);
+ });
+ }
+}
+
+///////////////////////// REMOVE OPERATION INPUT /////////////////////////
+
+static bool removeOperationInputSkip(const Operation& op, size_t input) {
+ // Skip removeOperationInputTest for the following operations.
+ // - CONCATENATION has at least 2 inputs, with the last element being INT32.
+ // - CONV_2D, DEPTHWISE_CONV_2D, MAX_POOL_2D, AVERAGE_POOL_2D, L2_POOL_2D, RESIZE_BILINEAR,
+ // SPACE_TO_DEPTH, SPACE_TO_DEPTH, SPACE_TO_BATCH_ND, BATCH_TO_SPACE_ND can have an optional
+ // layout parameter.
+ // RESIZE_BILINEAR and RESIZE_NEAREST_NEIGHBOR can have optional
+ // align_corners and half_pixel_centers parameters.
+ // - L2_NORMALIZATION, LOCAL_RESPONSE_NORMALIZATION, SOFTMAX can have an optional axis
+ // parameter.
+ switch (op.type) {
+ case OperationType::CONCATENATION: {
+ if (op.inputs.size() > 2 && input != op.inputs.size() - 1) {
+ return true;
+ }
+ } break;
+ case OperationType::DEPTHWISE_CONV_2D: {
+ if ((op.inputs.size() == 12 && input == 11) || (op.inputs.size() == 9 && input == 8)) {
+ return true;
+ }
+ } break;
+ case OperationType::CONV_2D:
+ case OperationType::AVERAGE_POOL_2D:
+ case OperationType::MAX_POOL_2D:
+ case OperationType::L2_POOL_2D: {
+ if ((op.inputs.size() == 11 && input == 10) || (op.inputs.size() == 8 && input == 7)) {
+ return true;
+ }
+ } break;
+ case OperationType::RESIZE_BILINEAR: {
+ if (op.inputs.size() >= 4 && input >= 3) {
+ return true;
+ }
+ } break;
+ case OperationType::RESIZE_NEAREST_NEIGHBOR: {
+ if (op.inputs.size() >= 5 && input >= 3) {
+ return true;
+ }
+ } break;
+ case OperationType::SPACE_TO_DEPTH:
+ case OperationType::DEPTH_TO_SPACE:
+ case OperationType::BATCH_TO_SPACE_ND: {
+ if (op.inputs.size() == 3 && input == 2) {
+ return true;
+ }
+ } break;
+ case OperationType::SPACE_TO_BATCH_ND: {
+ if (op.inputs.size() == 4 && input == 3) {
+ return true;
+ }
+ } break;
+ case OperationType::L2_NORMALIZATION: {
+ if (op.inputs.size() == 2 && input == 1) {
+ return true;
+ }
+ } break;
+ case OperationType::LOCAL_RESPONSE_NORMALIZATION: {
+ if (op.inputs.size() == 6 && input == 5) {
+ return true;
+ }
+ } break;
+ case OperationType::SOFTMAX: {
+ if (op.inputs.size() == 3 && input == 2) {
+ return true;
+ }
+ } break;
+ default:
+ break;
+ }
+ return false;
+}
+
+static void removeOperationInputTest(const std::shared_ptr<IDevice>& device, const Model& model) {
+ for (size_t operation = 0; operation < model.main.operations.size(); ++operation) {
+ for (size_t input = 0; input < model.main.operations[operation].inputs.size(); ++input) {
+ const Operation& op = model.main.operations[operation];
+ if (removeOperationInputSkip(op, input)) {
+ continue;
+ }
+ const std::string message = "removeOperationInputTest: operation " +
+ std::to_string(operation) + ", input " +
+ std::to_string(input);
+ validate(device, message, model,
+ [operation, input](Model* model, ExecutionPreference*, Priority*) {
+ auto& inputs = model->main.operations[operation].inputs;
+ inputs.erase(inputs.begin() + input);
+ });
+ }
+ }
+}
+
+///////////////////////// REMOVE OPERATION OUTPUT /////////////////////////
+
+static void removeOperationOutputTest(const std::shared_ptr<IDevice>& device, const Model& model) {
+ for (size_t operation = 0; operation < model.main.operations.size(); ++operation) {
+ for (size_t output = 0; output < model.main.operations[operation].outputs.size();
+ ++output) {
+ const std::string message = "removeOperationOutputTest: operation " +
+ std::to_string(operation) + ", output " +
+ std::to_string(output);
+ validate(device, message, model,
+ [operation, output](Model* model, ExecutionPreference*, Priority*) {
+ auto& outputs = model->main.operations[operation].outputs;
+ outputs.erase(outputs.begin() + output);
+ });
+ }
+ }
+}
+
+///////////////////////// MODEL VALIDATION /////////////////////////
+
+// TODO: remove model input
+// TODO: remove model output
+// TODO: add unused operation
+
+///////////////////////// ADD OPERATION INPUT /////////////////////////
+
+static bool addOperationInputSkip(const Operation& op) {
+ // Skip addOperationInputTest for the following operations.
+ // - L2_NORMALIZATION, LOCAL_RESPONSE_NORMALIZATION, SOFTMAX can have an optional INT32 axis
+ // parameter.
+ if ((op.type == OperationType::L2_NORMALIZATION && op.inputs.size() == 1) ||
+ (op.type == OperationType::LOCAL_RESPONSE_NORMALIZATION && op.inputs.size() == 5) ||
+ (op.type == OperationType::SOFTMAX && op.inputs.size() == 2) ||
+ (op.type == OperationType::RESIZE_BILINEAR && op.inputs.size() < 6) ||
+ (op.type == OperationType::RESIZE_NEAREST_NEIGHBOR && op.inputs.size() < 6)) {
+ return true;
+ }
+ return false;
+}
+
+static void addOperationInputTest(const std::shared_ptr<IDevice>& device, const Model& model) {
+ for (size_t operation = 0; operation < model.main.operations.size(); ++operation) {
+ if (addOperationInputSkip(model.main.operations[operation])) {
+ continue;
+ }
+ const std::string message = "addOperationInputTest: operation " + std::to_string(operation);
+ validate(device, message, model,
+ [operation](Model* model, ExecutionPreference*, Priority*) {
+ uint32_t index = addOperand(model, OperandLifeTime::SUBGRAPH_INPUT);
+ model->main.operations[operation].inputs.push_back(index);
+ model->main.inputIndexes.push_back(index);
+ });
+ }
+}
+
+///////////////////////// ADD OPERATION OUTPUT /////////////////////////
+
+static void addOperationOutputTest(const std::shared_ptr<IDevice>& device, const Model& model) {
+ for (size_t operation = 0; operation < model.main.operations.size(); ++operation) {
+ const std::string message =
+ "addOperationOutputTest: operation " + std::to_string(operation);
+ validate(device, message, model,
+ [operation](Model* model, ExecutionPreference*, Priority*) {
+ uint32_t index = addOperand(model, OperandLifeTime::SUBGRAPH_OUTPUT);
+ model->main.operations[operation].outputs.push_back(index);
+ model->main.outputIndexes.push_back(index);
+ });
+ }
+}
+
+///////////////////////// VALIDATE EXECUTION PREFERENCE /////////////////////////
+
+static const int32_t invalidExecutionPreferences[] = {
+ static_cast<int32_t>(ExecutionPreference::LOW_POWER) - 1, // lower bound
+ static_cast<int32_t>(ExecutionPreference::SUSTAINED_SPEED) + 1, // upper bound
+};
+
+static void mutateExecutionPreferenceTest(const std::shared_ptr<IDevice>& device,
+ const Model& model) {
+ for (int32_t invalidPreference : invalidExecutionPreferences) {
+ const std::string message =
+ "mutateExecutionPreferenceTest: preference " + std::to_string(invalidPreference);
+ validate(device, message, model,
+ [invalidPreference](Model*, ExecutionPreference* preference, Priority*) {
+ *preference = static_cast<ExecutionPreference>(invalidPreference);
+ });
+ }
+}
+
+///////////////////////// VALIDATE PRIORITY /////////////////////////
+
+static const int32_t invalidPriorities[] = {
+ static_cast<int32_t>(Priority::LOW) - 1, // lower bound
+ static_cast<int32_t>(Priority::HIGH) + 1, // upper bound
+};
+
+static void mutateExecutionPriorityTest(const std::shared_ptr<IDevice>& device,
+ const Model& model) {
+ for (int32_t invalidPriority : invalidPriorities) {
+ const std::string message =
+ "mutatePriorityTest: priority " + std::to_string(invalidPriority);
+ validate(device, message, model,
+ [invalidPriority](Model*, ExecutionPreference*, Priority* priority) {
+ *priority = static_cast<Priority>(invalidPriority);
+ });
+ }
+}
+
+////////////////////////// ENTRY POINT //////////////////////////////
+
+void validateModel(const std::shared_ptr<IDevice>& device, const Model& model) {
+ const auto numberOfConsumers = nn::countNumberOfConsumers(
+ model.main.operands.size(), nn::convert(model.main.operations).value());
+ mutateExecutionOrderTest(device, model, numberOfConsumers);
+ mutateOperandTypeTest(device, model);
+ mutateOperandRankTest(device, model);
+ mutateOperandScaleTest(device, model);
+ mutateOperandZeroPointTest(device, model);
+ mutateOperandLifeTimeTest(device, model);
+ mutateOperandInputOutputTest(device, model);
+ mutateOperandAddWriterTest(device, model);
+ mutateOperationOperandTypeTest(device, model);
+ mutateOperationTypeTest(device, model);
+ mutateOperationInputOperandIndexTest(device, model);
+ mutateOperationOutputOperandIndexTest(device, model);
+ mutateOperationRemoveWriteTest(device, model, numberOfConsumers);
+ removeOperandTest(device, model, numberOfConsumers);
+ removeOperationTest(device, model);
+ removeOperationInputTest(device, model);
+ removeOperationOutputTest(device, model);
+ addOperationInputTest(device, model);
+ addOperationOutputTest(device, model);
+ mutateExecutionPreferenceTest(device, model);
+ mutateExecutionPriorityTest(device, model);
+}
+
+} // namespace aidl::android::hardware::neuralnetworks::vts::functional
diff --git a/neuralnetworks/aidl/vts/functional/ValidateRequest.cpp b/neuralnetworks/aidl/vts/functional/ValidateRequest.cpp
new file mode 100644
index 0000000..db8f429
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/ValidateRequest.cpp
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "neuralnetworks_aidl_hal_test"
+
+#include <android/binder_auto_utils.h>
+
+#include <chrono>
+
+#include <TestHarness.h>
+#include <nnapi/hal/aidl/Utils.h>
+
+#include "Callbacks.h"
+#include "GeneratedTestHarness.h"
+#include "Utils.h"
+#include "VtsHalNeuralnetworks.h"
+
+namespace aidl::android::hardware::neuralnetworks::vts::functional {
+
+using ExecutionMutation = std::function<void(Request*)>;
+
+///////////////////////// UTILITY FUNCTIONS /////////////////////////
+
+// Primary validation function. This function will take a valid request, apply a
+// mutation to it to invalidate the request, then pass it to interface calls
+// that use the request.
+static void validate(const std::shared_ptr<IPreparedModel>& preparedModel,
+ const std::string& message, const Request& originalRequest,
+ const ExecutionMutation& mutate) {
+ Request request = utils::clone(originalRequest).value();
+ mutate(&request);
+
+ // We'd like to test both with timing requested and without timing
+ // requested. Rather than running each test both ways, we'll decide whether
+ // to request timing by hashing the message. We do not use std::hash because
+ // it is not guaranteed stable across executions.
+ char hash = 0;
+ for (auto c : message) {
+ hash ^= c;
+ };
+ bool measure = (hash & 1);
+
+ // synchronous
+ {
+ SCOPED_TRACE(message + " [executeSynchronously]");
+ ExecutionResult executionResult;
+ const auto executeStatus = preparedModel->executeSynchronously(
+ request, measure, kNoDeadline, kOmittedTimeoutDuration, &executionResult);
+ ASSERT_FALSE(executeStatus.isOk());
+ ASSERT_EQ(executeStatus.getExceptionCode(), EX_SERVICE_SPECIFIC);
+ ASSERT_EQ(static_cast<ErrorStatus>(executeStatus.getServiceSpecificError()),
+ ErrorStatus::INVALID_ARGUMENT);
+ }
+
+ // fenced
+ {
+ SCOPED_TRACE(message + " [executeFenced]");
+ ndk::ScopedFileDescriptor syncFence;
+ std::shared_ptr<IFencedExecutionCallback> callback;
+ const auto executeStatus = preparedModel->executeFenced(request, {}, false, kNoDeadline,
+ kOmittedTimeoutDuration,
+ kNoDuration, &syncFence, &callback);
+ ASSERT_FALSE(executeStatus.isOk());
+ ASSERT_EQ(executeStatus.getExceptionCode(), EX_SERVICE_SPECIFIC);
+ ASSERT_EQ(static_cast<ErrorStatus>(executeStatus.getServiceSpecificError()),
+ ErrorStatus::INVALID_ARGUMENT);
+ }
+}
+
+///////////////////////// REMOVE INPUT ////////////////////////////////////
+
+static void removeInputTest(const std::shared_ptr<IPreparedModel>& preparedModel,
+ const Request& request) {
+ for (size_t input = 0; input < request.inputs.size(); ++input) {
+ const std::string message = "removeInput: removed input " + std::to_string(input);
+ validate(preparedModel, message, request, [input](Request* request) {
+ request->inputs.erase(request->inputs.begin() + input);
+ });
+ }
+}
+
+///////////////////////// REMOVE OUTPUT ////////////////////////////////////
+
+static void removeOutputTest(const std::shared_ptr<IPreparedModel>& preparedModel,
+ const Request& request) {
+ for (size_t output = 0; output < request.outputs.size(); ++output) {
+ const std::string message = "removeOutput: removed Output " + std::to_string(output);
+ validate(preparedModel, message, request, [output](Request* request) {
+ request->outputs.erase(request->outputs.begin() + output);
+ });
+ }
+}
+
+///////////////////////////// ENTRY POINT //////////////////////////////////
+
+void validateRequest(const std::shared_ptr<IPreparedModel>& preparedModel, const Request& request) {
+ removeInputTest(preparedModel, request);
+ removeOutputTest(preparedModel, request);
+}
+
+void validateRequestFailure(const std::shared_ptr<IPreparedModel>& preparedModel,
+ const Request& request) {
+ SCOPED_TRACE("Expecting request to fail [executeSynchronously]");
+ ExecutionResult executionResult;
+ const auto executeStatus = preparedModel->executeSynchronously(
+ request, false, kNoDeadline, kOmittedTimeoutDuration, &executionResult);
+
+ ASSERT_FALSE(executeStatus.isOk());
+ ASSERT_EQ(executeStatus.getExceptionCode(), EX_SERVICE_SPECIFIC);
+ ASSERT_NE(static_cast<ErrorStatus>(executeStatus.getServiceSpecificError()), ErrorStatus::NONE);
+}
+
+} // namespace aidl::android::hardware::neuralnetworks::vts::functional
diff --git a/neuralnetworks/aidl/vts/functional/VtsHalNeuralnetworks.cpp b/neuralnetworks/aidl/vts/functional/VtsHalNeuralnetworks.cpp
new file mode 100644
index 0000000..2d91b8e
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/VtsHalNeuralnetworks.cpp
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "neuralnetworks_aidl_hal_test"
+#include "VtsHalNeuralnetworks.h"
+
+#include <android-base/logging.h>
+#include <android/binder_auto_utils.h>
+#include <android/binder_interface_utils.h>
+#include <android/binder_manager.h>
+#include <android/binder_status.h>
+#include <gtest/gtest.h>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include <TestHarness.h>
+#include <aidl/Vintf.h>
+#include <nnapi/hal/aidl/Conversions.h>
+
+#include "Callbacks.h"
+#include "GeneratedTestHarness.h"
+#include "Utils.h"
+
+namespace aidl::android::hardware::neuralnetworks::vts::functional {
+
+using implementation::PreparedModelCallback;
+
+// internal helper function
+void createPreparedModel(const std::shared_ptr<IDevice>& device, const Model& model,
+ std::shared_ptr<IPreparedModel>* preparedModel, bool reportSkipping) {
+ ASSERT_NE(nullptr, preparedModel);
+ *preparedModel = nullptr;
+
+ // see if service can handle model
+ std::vector<bool> supportedOperations;
+ const auto supportedCallStatus = device->getSupportedOperations(model, &supportedOperations);
+ ASSERT_TRUE(supportedCallStatus.isOk());
+ ASSERT_NE(0ul, supportedOperations.size());
+ const bool fullySupportsModel = std::all_of(
+ supportedOperations.begin(), supportedOperations.end(), [](bool v) { return v; });
+
+ // launch prepare model
+ const std::shared_ptr<PreparedModelCallback> preparedModelCallback =
+ ndk::SharedRefBase::make<PreparedModelCallback>();
+ const auto prepareLaunchStatus =
+ device->prepareModel(model, ExecutionPreference::FAST_SINGLE_ANSWER, kDefaultPriority,
+ kNoDeadline, {}, {}, kEmptyCacheToken, preparedModelCallback);
+ ASSERT_TRUE(prepareLaunchStatus.isOk()) << prepareLaunchStatus.getDescription();
+
+ // retrieve prepared model
+ preparedModelCallback->wait();
+ const ErrorStatus prepareReturnStatus = preparedModelCallback->getStatus();
+ *preparedModel = preparedModelCallback->getPreparedModel();
+
+ // The getSupportedOperations call returns a list of operations that are guaranteed not to fail
+ // if prepareModel is called, and 'fullySupportsModel' is true i.f.f. the entire model is
+ // guaranteed. If a driver has any doubt that it can prepare an operation, it must return false.
+ // So here, if a driver isn't sure if it can support an operation, but reports that it
+ // successfully prepared the model, the test can continue.
+ if (!fullySupportsModel && prepareReturnStatus != ErrorStatus::NONE) {
+ ASSERT_EQ(nullptr, preparedModel->get());
+ if (!reportSkipping) {
+ return;
+ }
+ LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot prepare "
+ "model that it does not support.";
+ std::cout << "[ ] Early termination of test because vendor service cannot "
+ "prepare model that it does not support."
+ << std::endl;
+ GTEST_SKIP();
+ }
+
+ ASSERT_EQ(ErrorStatus::NONE, prepareReturnStatus);
+ ASSERT_NE(nullptr, preparedModel->get());
+}
+
+void NeuralNetworksAidlTest::SetUp() {
+ testing::TestWithParam<NeuralNetworksAidlTestParam>::SetUp();
+ ASSERT_NE(kDevice, nullptr);
+}
+
+static NamedDevice makeNamedDevice(const std::string& name) {
+ ndk::SpAIBinder binder(AServiceManager_getService(name.c_str()));
+ return {name, IDevice::fromBinder(binder)};
+}
+
+static std::vector<NamedDevice> getNamedDevicesImpl() {
+ // Retrieves the name of all service instances that implement IDevice,
+ // including any Lazy HAL instances.
+ const std::vector<std::string> names = ::android::getAidlHalInstanceNames(IDevice::descriptor);
+
+ // Get a handle to each device and pair it with its name.
+ std::vector<NamedDevice> namedDevices;
+ namedDevices.reserve(names.size());
+ std::transform(names.begin(), names.end(), std::back_inserter(namedDevices), makeNamedDevice);
+ return namedDevices;
+}
+
+const std::vector<NamedDevice>& getNamedDevices() {
+ const static std::vector<NamedDevice> devices = getNamedDevicesImpl();
+ return devices;
+}
+
+std::string printNeuralNetworksAidlTest(
+ const testing::TestParamInfo<NeuralNetworksAidlTestParam>& info) {
+ return gtestCompliantName(getName(info.param));
+}
+
+INSTANTIATE_DEVICE_TEST(NeuralNetworksAidlTest);
+
+// Forward declaration from ValidateModel.cpp
+void validateModel(const std::shared_ptr<IDevice>& device, const Model& model);
+// Forward declaration from ValidateRequest.cpp
+void validateRequest(const std::shared_ptr<IPreparedModel>& preparedModel, const Request& request);
+// Forward declaration from ValidateRequest.cpp
+void validateRequestFailure(const std::shared_ptr<IPreparedModel>& preparedModel,
+ const Request& request);
+
+void validateEverything(const std::shared_ptr<IDevice>& device, const Model& model,
+ const Request& request) {
+ validateModel(device, model);
+
+ // Create IPreparedModel.
+ std::shared_ptr<IPreparedModel> preparedModel;
+ createPreparedModel(device, model, &preparedModel);
+ if (preparedModel == nullptr) return;
+
+ validateRequest(preparedModel, request);
+ // HIDL also had test that expected executeFenced to fail on received null fd (-1). This is not
+ // allowed in AIDL and will result in EX_TRANSACTION_FAILED.
+}
+
+void validateFailure(const std::shared_ptr<IDevice>& device, const Model& model,
+ const Request& request) {
+ // TODO: Should this always succeed?
+ // What if the invalid input is part of the model (i.e., a parameter).
+ validateModel(device, model);
+
+ // Create IPreparedModel.
+ std::shared_ptr<IPreparedModel> preparedModel;
+ createPreparedModel(device, model, &preparedModel);
+ if (preparedModel == nullptr) return;
+
+ validateRequestFailure(preparedModel, request);
+}
+
+TEST_P(ValidationTest, Test) {
+ const Model model = createModel(kTestModel);
+ ExecutionContext context;
+ const Request request = context.createRequest(kTestModel);
+ if (kTestModel.expectFailure) {
+ validateFailure(kDevice, model, request);
+ } else {
+ validateEverything(kDevice, model, request);
+ }
+}
+
+INSTANTIATE_GENERATED_TEST(ValidationTest, [](const std::string& testName) {
+ // Skip validation for the "inputs_as_internal" and "all_tensors_as_inputs"
+ // generated tests.
+ return testName.find("inputs_as_internal") == std::string::npos &&
+ testName.find("all_tensors_as_inputs") == std::string::npos;
+});
+
+std::string toString(Executor executor) {
+ switch (executor) {
+ case Executor::ASYNC:
+ return "ASYNC";
+ case Executor::SYNC:
+ return "SYNC";
+ case Executor::BURST:
+ return "BURST";
+ case Executor::FENCED:
+ return "FENCED";
+ default:
+ CHECK(false);
+ }
+}
+
+} // namespace aidl::android::hardware::neuralnetworks::vts::functional
diff --git a/neuralnetworks/aidl/vts/functional/VtsHalNeuralnetworks.h b/neuralnetworks/aidl/vts/functional/VtsHalNeuralnetworks.h
new file mode 100644
index 0000000..9b81ee1
--- /dev/null
+++ b/neuralnetworks/aidl/vts/functional/VtsHalNeuralnetworks.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_NEURALNETWORKS_AIDL_VTS_HAL_NEURALNETWORKS_H
+#define ANDROID_HARDWARE_NEURALNETWORKS_AIDL_VTS_HAL_NEURALNETWORKS_H
+
+#include <gtest/gtest.h>
+#include <vector>
+
+#include <aidl/android/hardware/neuralnetworks/IDevice.h>
+
+#include "Callbacks.h"
+#include "Utils.h"
+
+namespace aidl::android::hardware::neuralnetworks::vts::functional {
+
+using NamedDevice = Named<std::shared_ptr<IDevice>>;
+using NeuralNetworksAidlTestParam = NamedDevice;
+
+class NeuralNetworksAidlTest : public testing::TestWithParam<NeuralNetworksAidlTestParam> {
+ protected:
+ void SetUp() override;
+ const std::shared_ptr<IDevice> kDevice = getData(GetParam());
+};
+
+const std::vector<NamedDevice>& getNamedDevices();
+
+std::string printNeuralNetworksAidlTest(
+ const testing::TestParamInfo<NeuralNetworksAidlTestParam>& info);
+
+#define INSTANTIATE_DEVICE_TEST(TestSuite) \
+ GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(TestSuite); \
+ INSTANTIATE_TEST_SUITE_P(PerInstance, TestSuite, testing::ValuesIn(getNamedDevices()), \
+ printNeuralNetworksAidlTest)
+
+// Create an IPreparedModel object. If the model cannot be prepared,
+// "preparedModel" will be nullptr instead.
+void createPreparedModel(const std::shared_ptr<IDevice>& device, const Model& model,
+ std::shared_ptr<IPreparedModel>* preparedModel,
+ bool reportSkipping = true);
+
+enum class Executor { ASYNC, SYNC, BURST, FENCED };
+
+std::string toString(Executor executor);
+
+} // namespace aidl::android::hardware::neuralnetworks::vts::functional
+
+#endif // ANDROID_HARDWARE_NEURALNETWORKS_AIDL_VTS_HAL_NEURALNETWORKS_H
diff --git a/neuralnetworks/utils/common/Android.bp b/neuralnetworks/utils/common/Android.bp
index 21562cf..50295f1 100644
--- a/neuralnetworks/utils/common/Android.bp
+++ b/neuralnetworks/utils/common/Android.bp
@@ -22,9 +22,36 @@
export_include_dirs: ["include"],
cflags: ["-Wthread-safety"],
static_libs: [
+ "libarect",
"neuralnetworks_types",
],
shared_libs: [
"libhidlbase",
+ "libnativewindow",
],
}
+
+cc_test {
+ name: "neuralnetworks_utils_hal_common_test",
+ srcs: ["test/*.cpp"],
+ static_libs: [
+ "android.hardware.neuralnetworks@1.0",
+ "libgmock",
+ "libneuralnetworks_common",
+ "neuralnetworks_types",
+ "neuralnetworks_utils_hal_common",
+ ],
+ shared_libs: [
+ "android.hidl.allocator@1.0",
+ "android.hidl.memory@1.0",
+ "libbase",
+ "libcutils",
+ "libfmq",
+ "libhidlbase",
+ "libhidlmemory",
+ "liblog",
+ "libnativewindow",
+ "libutils",
+ ],
+ test_suites: ["general-tests"],
+}
diff --git a/neuralnetworks/utils/common/include/nnapi/hal/CommonUtils.h b/neuralnetworks/utils/common/include/nnapi/hal/CommonUtils.h
index b3989e5..547f203 100644
--- a/neuralnetworks/utils/common/include/nnapi/hal/CommonUtils.h
+++ b/neuralnetworks/utils/common/include/nnapi/hal/CommonUtils.h
@@ -24,15 +24,21 @@
#include <functional>
#include <vector>
-// Shorthand
+// Shorthands
namespace android::hardware::neuralnetworks {
namespace hal = ::android::hardware::neuralnetworks;
} // namespace android::hardware::neuralnetworks
-// Shorthand
+// Shorthands
+namespace aidl::android::hardware::neuralnetworks {
+namespace aidl_hal = ::aidl::android::hardware::neuralnetworks;
+} // namespace aidl::android::hardware::neuralnetworks
+
+// Shorthands
namespace android::nn {
namespace hal = ::android::hardware::neuralnetworks;
-}
+namespace aidl_hal = ::aidl::android::hardware::neuralnetworks;
+} // namespace android::nn
namespace android::hardware::neuralnetworks::utils {
@@ -68,10 +74,12 @@
std::vector<uint32_t> countNumberOfConsumers(size_t numberOfOperands,
const std::vector<nn::Operation>& operations);
-nn::GeneralResult<nn::Memory> createSharedMemoryFromHidlMemory(const hidl_memory& memory);
+nn::GeneralResult<hidl_memory> createHidlMemoryFromSharedMemory(const nn::SharedMemory& memory);
+nn::GeneralResult<nn::SharedMemory> createSharedMemoryFromHidlMemory(const hidl_memory& memory);
-nn::GeneralResult<hidl_handle> hidlHandleFromSharedHandle(const nn::SharedHandle& handle);
-nn::GeneralResult<nn::SharedHandle> sharedHandleFromNativeHandle(const native_handle_t* handle);
+nn::GeneralResult<hidl_handle> hidlHandleFromSharedHandle(const nn::Handle& handle);
+nn::GeneralResult<nn::Handle> sharedHandleFromNativeHandle(const native_handle_t* handle);
+
nn::GeneralResult<hidl_vec<hidl_handle>> convertSyncFences(
const std::vector<nn::SyncFence>& fences);
diff --git a/neuralnetworks/utils/common/include/nnapi/hal/HandleError.h b/neuralnetworks/utils/common/include/nnapi/hal/HandleError.h
index 95a20a8..209b663 100644
--- a/neuralnetworks/utils/common/include/nnapi/hal/HandleError.h
+++ b/neuralnetworks/utils/common/include/nnapi/hal/HandleError.h
@@ -14,6 +14,9 @@
* limitations under the License.
*/
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_HANDLE_ERROR_H
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_HANDLE_ERROR_H
+
#include <android/hidl/base/1.0/IBase.h>
#include <hidl/HidlSupport.h>
#include <nnapi/Result.h>
@@ -50,7 +53,8 @@
})
template <typename Type>
-nn::GeneralResult<Type> makeGeneralFailure(nn::Result<Type> result, nn::ErrorStatus status) {
+nn::GeneralResult<Type> makeGeneralFailure(
+ nn::Result<Type> result, nn::ErrorStatus status = nn::ErrorStatus::GENERAL_FAILURE) {
if (!result.has_value()) {
return nn::error(status) << std::move(result).error();
}
@@ -75,7 +79,8 @@
}
template <typename Type>
-nn::ExecutionResult<Type> makeExecutionFailure(nn::Result<Type> result, nn::ErrorStatus status) {
+nn::ExecutionResult<Type> makeExecutionFailure(
+ nn::Result<Type> result, nn::ErrorStatus status = nn::ErrorStatus::GENERAL_FAILURE) {
return makeExecutionFailure(makeGeneralFailure(result, status));
}
@@ -86,4 +91,6 @@
} else \
return NN_ERROR(canonical)
-} // namespace android::hardware::neuralnetworks::utils
\ No newline at end of file
+} // namespace android::hardware::neuralnetworks::utils
+
+#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_HANDLE_ERROR_H
diff --git a/neuralnetworks/utils/common/include/nnapi/hal/InvalidBuffer.h b/neuralnetworks/utils/common/include/nnapi/hal/InvalidBuffer.h
index 8c04b88..0e98c2e 100644
--- a/neuralnetworks/utils/common/include/nnapi/hal/InvalidBuffer.h
+++ b/neuralnetworks/utils/common/include/nnapi/hal/InvalidBuffer.h
@@ -31,9 +31,9 @@
public:
nn::Request::MemoryDomainToken getToken() const override;
- nn::GeneralResult<void> copyTo(const nn::Memory& dst) const override;
+ nn::GeneralResult<void> copyTo(const nn::SharedMemory& dst) const override;
- nn::GeneralResult<void> copyFrom(const nn::Memory& src,
+ nn::GeneralResult<void> copyFrom(const nn::SharedMemory& src,
const nn::Dimensions& dimensions) const override;
};
diff --git a/neuralnetworks/utils/common/include/nnapi/hal/InvalidBurst.h b/neuralnetworks/utils/common/include/nnapi/hal/InvalidBurst.h
new file mode 100644
index 0000000..996858c
--- /dev/null
+++ b/neuralnetworks/utils/common/include/nnapi/hal/InvalidBurst.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_INVALID_BURST_H
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_INVALID_BURST_H
+
+#include <nnapi/IBurst.h>
+#include <nnapi/Result.h>
+#include <nnapi/Types.h>
+
+#include <memory>
+#include <optional>
+#include <utility>
+
+namespace android::hardware::neuralnetworks::utils {
+
+class InvalidBurst final : public nn::IBurst {
+ public:
+ OptionalCacheHold cacheMemory(const nn::SharedMemory& memory) const override;
+
+ nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> execute(
+ const nn::Request& request, nn::MeasureTiming measure) const override;
+};
+
+} // namespace android::hardware::neuralnetworks::utils
+
+#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_INVALID_BURST_H
diff --git a/neuralnetworks/utils/common/include/nnapi/hal/InvalidDevice.h b/neuralnetworks/utils/common/include/nnapi/hal/InvalidDevice.h
index 5e62b9a..d843526 100644
--- a/neuralnetworks/utils/common/include/nnapi/hal/InvalidDevice.h
+++ b/neuralnetworks/utils/common/include/nnapi/hal/InvalidDevice.h
@@ -32,7 +32,7 @@
class InvalidDevice final : public nn::IDevice {
public:
InvalidDevice(std::string name, std::string versionString, nn::Version featureLevel,
- nn::DeviceType type, std::vector<nn::Extension> extensions,
+ nn::DeviceType type, bool isUpdatable, std::vector<nn::Extension> extensions,
nn::Capabilities capabilities,
std::pair<uint32_t, uint32_t> numberOfCacheFilesNeeded);
@@ -40,6 +40,7 @@
const std::string& getVersionString() const override;
nn::Version getFeatureLevel() const override;
nn::DeviceType getType() const override;
+ bool isUpdatable() const override;
const std::vector<nn::Extension>& getSupportedExtensions() const override;
const nn::Capabilities& getCapabilities() const override;
std::pair<uint32_t, uint32_t> getNumberOfCacheFilesNeeded() const override;
@@ -70,6 +71,7 @@
const std::string kVersionString;
const nn::Version kFeatureLevel;
const nn::DeviceType kType;
+ const bool kIsUpdatable;
const std::vector<nn::Extension> kExtensions;
const nn::Capabilities kCapabilities;
const std::pair<uint32_t, uint32_t> kNumberOfCacheFilesNeeded;
diff --git a/neuralnetworks/utils/common/include/nnapi/hal/InvalidPreparedModel.h b/neuralnetworks/utils/common/include/nnapi/hal/InvalidPreparedModel.h
index 985cddb..3e1dca7 100644
--- a/neuralnetworks/utils/common/include/nnapi/hal/InvalidPreparedModel.h
+++ b/neuralnetworks/utils/common/include/nnapi/hal/InvalidPreparedModel.h
@@ -40,6 +40,8 @@
const nn::OptionalDuration& loopTimeoutDuration,
const nn::OptionalDuration& timeoutDurationAfterFence) const override;
+ nn::GeneralResult<nn::SharedBurst> configureExecutionBurst() const override;
+
std::any getUnderlyingResource() const override;
};
diff --git a/neuralnetworks/utils/common/include/nnapi/hal/ResilientBuffer.h b/neuralnetworks/utils/common/include/nnapi/hal/ResilientBuffer.h
index 9d5e3e6..c8ca6f2 100644
--- a/neuralnetworks/utils/common/include/nnapi/hal/ResilientBuffer.h
+++ b/neuralnetworks/utils/common/include/nnapi/hal/ResilientBuffer.h
@@ -42,13 +42,13 @@
nn::SharedBuffer buffer);
nn::SharedBuffer getBuffer() const;
- nn::SharedBuffer recover(const nn::IBuffer* failingBuffer, bool blocking) const;
+ nn::GeneralResult<nn::SharedBuffer> recover(const nn::IBuffer* failingBuffer) const;
nn::Request::MemoryDomainToken getToken() const override;
- nn::GeneralResult<void> copyTo(const nn::Memory& dst) const override;
+ nn::GeneralResult<void> copyTo(const nn::SharedMemory& dst) const override;
- nn::GeneralResult<void> copyFrom(const nn::Memory& src,
+ nn::GeneralResult<void> copyFrom(const nn::SharedMemory& src,
const nn::Dimensions& dimensions) const override;
private:
diff --git a/neuralnetworks/utils/common/include/nnapi/hal/ResilientBurst.h b/neuralnetworks/utils/common/include/nnapi/hal/ResilientBurst.h
new file mode 100644
index 0000000..3b87330
--- /dev/null
+++ b/neuralnetworks/utils/common/include/nnapi/hal/ResilientBurst.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_RESILIENT_BURST_H
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_RESILIENT_BURST_H
+
+#include <android-base/thread_annotations.h>
+#include <nnapi/IBurst.h>
+#include <nnapi/Result.h>
+#include <nnapi/Types.h>
+
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <utility>
+
+namespace android::hardware::neuralnetworks::utils {
+
+class ResilientBurst final : public nn::IBurst,
+ public std::enable_shared_from_this<ResilientBurst> {
+ struct PrivateConstructorTag {};
+
+ public:
+ using Factory = std::function<nn::GeneralResult<nn::SharedBurst>()>;
+
+ static nn::GeneralResult<std::shared_ptr<const ResilientBurst>> create(Factory makeBurst);
+
+ ResilientBurst(PrivateConstructorTag tag, Factory makeBurst, nn::SharedBurst burst);
+
+ nn::SharedBurst getBurst() const;
+ nn::GeneralResult<nn::SharedBurst> recover(const nn::IBurst* failingBurst) const;
+
+ OptionalCacheHold cacheMemory(const nn::SharedMemory& memory) const override;
+
+ nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> execute(
+ const nn::Request& request, nn::MeasureTiming measure) const override;
+
+ private:
+ const Factory kMakeBurst;
+ mutable std::mutex mMutex;
+ mutable nn::SharedBurst mBurst GUARDED_BY(mMutex);
+};
+
+} // namespace android::hardware::neuralnetworks::utils
+
+#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_RESILIENT_BURST_H
diff --git a/neuralnetworks/utils/common/include/nnapi/hal/ResilientDevice.h b/neuralnetworks/utils/common/include/nnapi/hal/ResilientDevice.h
index 84ae799..8199c52 100644
--- a/neuralnetworks/utils/common/include/nnapi/hal/ResilientDevice.h
+++ b/neuralnetworks/utils/common/include/nnapi/hal/ResilientDevice.h
@@ -53,6 +53,7 @@
const std::string& getVersionString() const override;
nn::Version getFeatureLevel() const override;
nn::DeviceType getType() const override;
+ bool isUpdatable() const override;
const std::vector<nn::Extension>& getSupportedExtensions() const override;
const nn::Capabilities& getCapabilities() const override;
std::pair<uint32_t, uint32_t> getNumberOfCacheFilesNeeded() const override;
diff --git a/neuralnetworks/utils/common/include/nnapi/hal/ResilientPreparedModel.h b/neuralnetworks/utils/common/include/nnapi/hal/ResilientPreparedModel.h
index faae673..a6c1b19 100644
--- a/neuralnetworks/utils/common/include/nnapi/hal/ResilientPreparedModel.h
+++ b/neuralnetworks/utils/common/include/nnapi/hal/ResilientPreparedModel.h
@@ -30,7 +30,8 @@
namespace android::hardware::neuralnetworks::utils {
-class ResilientPreparedModel final : public nn::IPreparedModel {
+class ResilientPreparedModel final : public nn::IPreparedModel,
+ public std::enable_shared_from_this<ResilientPreparedModel> {
struct PrivateConstructorTag {};
public:
@@ -43,8 +44,8 @@
nn::SharedPreparedModel preparedModel);
nn::SharedPreparedModel getPreparedModel() const;
- nn::SharedPreparedModel recover(const nn::IPreparedModel* failingPreparedModel,
- bool blocking) const;
+ nn::GeneralResult<nn::SharedPreparedModel> recover(
+ const nn::IPreparedModel* failingPreparedModel) const;
nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> execute(
const nn::Request& request, nn::MeasureTiming measure,
@@ -57,9 +58,14 @@
const nn::OptionalDuration& loopTimeoutDuration,
const nn::OptionalDuration& timeoutDurationAfterFence) const override;
+ nn::GeneralResult<nn::SharedBurst> configureExecutionBurst() const override;
+
std::any getUnderlyingResource() const override;
private:
+ bool isValidInternal() const EXCLUDES(mMutex);
+ nn::GeneralResult<nn::SharedBurst> configureExecutionBurstInternal() const;
+
const Factory kMakePreparedModel;
mutable std::mutex mMutex;
mutable nn::SharedPreparedModel mPreparedModel GUARDED_BY(mMutex);
diff --git a/neuralnetworks/utils/common/src/CommonUtils.cpp b/neuralnetworks/utils/common/src/CommonUtils.cpp
index c04c8df..7a5035f 100644
--- a/neuralnetworks/utils/common/src/CommonUtils.cpp
+++ b/neuralnetworks/utils/common/src/CommonUtils.cpp
@@ -20,11 +20,14 @@
#include <android-base/logging.h>
#include <android-base/unique_fd.h>
+#include <android/hardware_buffer.h>
+#include <hidl/HidlSupport.h>
#include <nnapi/Result.h>
#include <nnapi/SharedMemory.h>
#include <nnapi/TypeUtils.h>
#include <nnapi/Types.h>
#include <nnapi/Validation.h>
+#include <vndk/hardware_buffer.h>
#include <algorithm>
#include <any>
@@ -203,13 +206,13 @@
nn::GeneralResult<void> unflushDataFromSharedToPointer(
const nn::Request& request, const std::optional<nn::Request>& maybeRequestInShared) {
if (!maybeRequestInShared.has_value() || maybeRequestInShared->pools.empty() ||
- !std::holds_alternative<nn::Memory>(maybeRequestInShared->pools.back())) {
+ !std::holds_alternative<nn::SharedMemory>(maybeRequestInShared->pools.back())) {
return {};
}
const auto& requestInShared = *maybeRequestInShared;
// Map the memory.
- const auto& outputMemory = std::get<nn::Memory>(requestInShared.pools.back());
+ const auto& outputMemory = std::get<nn::SharedMemory>(requestInShared.pools.back());
const auto [pointer, size, context] = NN_TRY(map(outputMemory));
const uint8_t* constantPointer =
std::visit([](const auto& o) { return static_cast<const uint8_t*>(o); }, pointer);
@@ -248,44 +251,128 @@
return nn::countNumberOfConsumers(numberOfOperands, operations);
}
-nn::GeneralResult<hidl_handle> hidlHandleFromSharedHandle(const nn::SharedHandle& handle) {
- if (handle == nullptr) {
- return {};
+nn::GeneralResult<hidl_memory> createHidlMemoryFromSharedMemory(const nn::SharedMemory& memory) {
+ if (memory == nullptr) {
+ return NN_ERROR() << "Memory must be non-empty";
+ }
+ if (const auto* handle = std::get_if<nn::Handle>(&memory->handle)) {
+ return hidl_memory(memory->name, NN_TRY(hidlHandleFromSharedHandle(*handle)), memory->size);
}
+ const auto* ahwb = std::get<nn::HardwareBufferHandle>(memory->handle).get();
+ AHardwareBuffer_Desc bufferDesc;
+ AHardwareBuffer_describe(ahwb, &bufferDesc);
+
+ if (bufferDesc.format == AHARDWAREBUFFER_FORMAT_BLOB) {
+ CHECK_EQ(memory->size, bufferDesc.width);
+ CHECK_EQ(memory->name, "hardware_buffer_blob");
+ } else {
+ CHECK_EQ(memory->size, 0u);
+ CHECK_EQ(memory->name, "hardware_buffer");
+ }
+
+ const native_handle_t* nativeHandle = AHardwareBuffer_getNativeHandle(ahwb);
+ const hidl_handle hidlHandle(nativeHandle);
+ hidl_handle handle(hidlHandle);
+
+ return hidl_memory(memory->name, std::move(handle), memory->size);
+}
+
+static uint32_t roundUpToMultiple(uint32_t value, uint32_t multiple) {
+ return (value + multiple - 1) / multiple * multiple;
+}
+
+nn::GeneralResult<nn::SharedMemory> createSharedMemoryFromHidlMemory(const hidl_memory& memory) {
+ CHECK_LE(memory.size(), std::numeric_limits<uint32_t>::max());
+
+ if (memory.name() != "hardware_buffer_blob") {
+ return std::make_shared<const nn::Memory>(nn::Memory{
+ .handle = NN_TRY(sharedHandleFromNativeHandle(memory.handle())),
+ .size = static_cast<uint32_t>(memory.size()),
+ .name = memory.name(),
+ });
+ }
+
+ const auto size = memory.size();
+ const auto format = AHARDWAREBUFFER_FORMAT_BLOB;
+ const auto usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN;
+ const uint32_t width = size;
+ const uint32_t height = 1; // height is always 1 for BLOB mode AHardwareBuffer.
+ const uint32_t layers = 1; // layers is always 1 for BLOB mode AHardwareBuffer.
+
+ // AHardwareBuffer_createFromHandle() might fail because an allocator
+ // expects a specific stride value. In that case, we try to guess it by
+ // aligning the width to small powers of 2.
+ // TODO(b/174120849): Avoid stride assumptions.
+ AHardwareBuffer* hardwareBuffer = nullptr;
+ status_t status = UNKNOWN_ERROR;
+ for (uint32_t alignment : {1, 4, 32, 64, 128, 2, 8, 16}) {
+ const uint32_t stride = roundUpToMultiple(width, alignment);
+ AHardwareBuffer_Desc desc{
+ .width = width,
+ .height = height,
+ .layers = layers,
+ .format = format,
+ .usage = usage,
+ .stride = stride,
+ };
+ status = AHardwareBuffer_createFromHandle(&desc, memory.handle(),
+ AHARDWAREBUFFER_CREATE_FROM_HANDLE_METHOD_CLONE,
+ &hardwareBuffer);
+ if (status == NO_ERROR) {
+ break;
+ }
+ }
+ if (status != NO_ERROR) {
+ return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
+ << "Can't create AHardwareBuffer from handle. Error: " << status;
+ }
+
+ return std::make_shared<const nn::Memory>(nn::Memory{
+ .handle = nn::HardwareBufferHandle(hardwareBuffer, /*takeOwnership=*/true),
+ .size = static_cast<uint32_t>(memory.size()),
+ .name = memory.name(),
+ });
+}
+
+nn::GeneralResult<hidl_handle> hidlHandleFromSharedHandle(const nn::Handle& handle) {
std::vector<base::unique_fd> fds;
- fds.reserve(handle->fds.size());
- for (const auto& fd : handle->fds) {
- int dupFd = dup(fd);
+ fds.reserve(handle.fds.size());
+ for (const auto& fd : handle.fds) {
+ const int dupFd = dup(fd);
if (dupFd == -1) {
return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) << "Failed to dup the fd";
}
fds.emplace_back(dupFd);
}
- native_handle_t* nativeHandle = native_handle_create(handle->fds.size(), handle->ints.size());
+ constexpr size_t kIntMax = std::numeric_limits<int>::max();
+ CHECK_LE(handle.fds.size(), kIntMax);
+ CHECK_LE(handle.ints.size(), kIntMax);
+ native_handle_t* nativeHandle = native_handle_create(static_cast<int>(handle.fds.size()),
+ static_cast<int>(handle.ints.size()));
if (nativeHandle == nullptr) {
return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) << "Failed to create native_handle";
}
for (size_t i = 0; i < fds.size(); ++i) {
nativeHandle->data[i] = fds[i].release();
}
- std::copy(handle->ints.begin(), handle->ints.end(), &nativeHandle->data[nativeHandle->numFds]);
+ std::copy(handle.ints.begin(), handle.ints.end(), &nativeHandle->data[nativeHandle->numFds]);
hidl_handle hidlHandle;
hidlHandle.setTo(nativeHandle, /*shouldOwn=*/true);
return hidlHandle;
}
-nn::GeneralResult<nn::SharedHandle> sharedHandleFromNativeHandle(const native_handle_t* handle) {
+nn::GeneralResult<nn::Handle> sharedHandleFromNativeHandle(const native_handle_t* handle) {
if (handle == nullptr) {
- return nullptr;
+ return NN_ERROR() << "sharedHandleFromNativeHandle failed because handle is nullptr";
}
std::vector<base::unique_fd> fds;
fds.reserve(handle->numFds);
for (int i = 0; i < handle->numFds; ++i) {
- int dupFd = dup(handle->data[i]);
+ const int dupFd = dup(handle->data[i]);
if (dupFd == -1) {
return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) << "Failed to dup the fd";
}
@@ -295,18 +382,18 @@
std::vector<int> ints(&handle->data[handle->numFds],
&handle->data[handle->numFds + handle->numInts]);
- return std::make_shared<const nn::Handle>(nn::Handle{
- .fds = std::move(fds),
- .ints = std::move(ints),
- });
+ return nn::Handle{.fds = std::move(fds), .ints = std::move(ints)};
}
nn::GeneralResult<hidl_vec<hidl_handle>> convertSyncFences(
const std::vector<nn::SyncFence>& syncFences) {
hidl_vec<hidl_handle> handles(syncFences.size());
for (size_t i = 0; i < syncFences.size(); ++i) {
- handles[i] =
- NN_TRY(hal::utils::hidlHandleFromSharedHandle(syncFences[i].getSharedHandle()));
+ const auto& handle = syncFences[i].getSharedHandle();
+ if (handle == nullptr) {
+ return NN_ERROR() << "convertSyncFences failed because sync fence is empty";
+ }
+ handles[i] = NN_TRY(hidlHandleFromSharedHandle(*handle));
}
return handles;
}
diff --git a/neuralnetworks/utils/common/src/InvalidBuffer.cpp b/neuralnetworks/utils/common/src/InvalidBuffer.cpp
index c6f75d7..e73001d 100644
--- a/neuralnetworks/utils/common/src/InvalidBuffer.cpp
+++ b/neuralnetworks/utils/common/src/InvalidBuffer.cpp
@@ -30,11 +30,11 @@
return nn::Request::MemoryDomainToken{};
}
-nn::GeneralResult<void> InvalidBuffer::copyTo(const nn::Memory& /*dst*/) const {
+nn::GeneralResult<void> InvalidBuffer::copyTo(const nn::SharedMemory& /*dst*/) const {
return NN_ERROR() << "InvalidBuffer";
}
-nn::GeneralResult<void> InvalidBuffer::copyFrom(const nn::Memory& /*src*/,
+nn::GeneralResult<void> InvalidBuffer::copyFrom(const nn::SharedMemory& /*src*/,
const nn::Dimensions& /*dimensions*/) const {
return NN_ERROR() << "InvalidBuffer";
}
diff --git a/neuralnetworks/utils/common/src/InvalidBurst.cpp b/neuralnetworks/utils/common/src/InvalidBurst.cpp
new file mode 100644
index 0000000..81ca18d
--- /dev/null
+++ b/neuralnetworks/utils/common/src/InvalidBurst.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "InvalidBurst.h"
+
+#include <nnapi/IBurst.h>
+#include <nnapi/Result.h>
+#include <nnapi/Types.h>
+
+#include <memory>
+#include <optional>
+#include <utility>
+
+namespace android::hardware::neuralnetworks::utils {
+
+InvalidBurst::OptionalCacheHold InvalidBurst::cacheMemory(
+ const nn::SharedMemory& /*memory*/) const {
+ return nullptr;
+}
+
+nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> InvalidBurst::execute(
+ const nn::Request& /*request*/, nn::MeasureTiming /*measure*/) const {
+ return NN_ERROR() << "InvalidBurst";
+}
+
+} // namespace android::hardware::neuralnetworks::utils
diff --git a/neuralnetworks/utils/common/src/InvalidDevice.cpp b/neuralnetworks/utils/common/src/InvalidDevice.cpp
index 535ccb4..81bca7f 100644
--- a/neuralnetworks/utils/common/src/InvalidDevice.cpp
+++ b/neuralnetworks/utils/common/src/InvalidDevice.cpp
@@ -32,13 +32,14 @@
namespace android::hardware::neuralnetworks::utils {
InvalidDevice::InvalidDevice(std::string name, std::string versionString, nn::Version featureLevel,
- nn::DeviceType type, std::vector<nn::Extension> extensions,
- nn::Capabilities capabilities,
+ nn::DeviceType type, bool isUpdatable,
+ std::vector<nn::Extension> extensions, nn::Capabilities capabilities,
std::pair<uint32_t, uint32_t> numberOfCacheFilesNeeded)
: kName(std::move(name)),
kVersionString(std::move(versionString)),
kFeatureLevel(featureLevel),
kType(type),
+ kIsUpdatable(isUpdatable),
kExtensions(std::move(extensions)),
kCapabilities(std::move(capabilities)),
kNumberOfCacheFilesNeeded(numberOfCacheFilesNeeded) {}
@@ -59,6 +60,10 @@
return kType;
}
+bool InvalidDevice::isUpdatable() const {
+ return kIsUpdatable;
+}
+
const std::vector<nn::Extension>& InvalidDevice::getSupportedExtensions() const {
return kExtensions;
}
diff --git a/neuralnetworks/utils/common/src/InvalidPreparedModel.cpp b/neuralnetworks/utils/common/src/InvalidPreparedModel.cpp
index a46f4ac..9081e1f 100644
--- a/neuralnetworks/utils/common/src/InvalidPreparedModel.cpp
+++ b/neuralnetworks/utils/common/src/InvalidPreparedModel.cpp
@@ -42,6 +42,10 @@
return NN_ERROR() << "InvalidPreparedModel";
}
+nn::GeneralResult<nn::SharedBurst> InvalidPreparedModel::configureExecutionBurst() const {
+ return NN_ERROR() << "InvalidPreparedModel";
+}
+
std::any InvalidPreparedModel::getUnderlyingResource() const {
return {};
}
diff --git a/neuralnetworks/utils/common/src/ResilientBuffer.cpp b/neuralnetworks/utils/common/src/ResilientBuffer.cpp
index cf5496a..1904375 100644
--- a/neuralnetworks/utils/common/src/ResilientBuffer.cpp
+++ b/neuralnetworks/utils/common/src/ResilientBuffer.cpp
@@ -20,6 +20,7 @@
#include <android-base/thread_annotations.h>
#include <nnapi/IBuffer.h>
#include <nnapi/Result.h>
+#include <nnapi/TypeUtils.h>
#include <nnapi/Types.h>
#include <functional>
@@ -29,6 +30,34 @@
#include <vector>
namespace android::hardware::neuralnetworks::utils {
+namespace {
+
+template <typename FnType>
+auto protect(const ResilientBuffer& resilientBuffer, const FnType& fn)
+ -> decltype(fn(*resilientBuffer.getBuffer())) {
+ auto buffer = resilientBuffer.getBuffer();
+ auto result = fn(*buffer);
+
+ // Immediately return if device is not dead.
+ if (result.has_value() || result.error().code != nn::ErrorStatus::DEAD_OBJECT) {
+ return result;
+ }
+
+ // Attempt recovery and return if it fails.
+ auto maybeBuffer = resilientBuffer.recover(buffer.get());
+ if (!maybeBuffer.has_value()) {
+ const auto& [resultErrorMessage, resultErrorCode] = result.error();
+ const auto& [recoveryErrorMessage, recoveryErrorCode] = maybeBuffer.error();
+ return nn::error(resultErrorCode)
+ << resultErrorMessage << ", and failed to recover dead buffer with error "
+ << recoveryErrorCode << ": " << recoveryErrorMessage;
+ }
+ buffer = std::move(maybeBuffer).value();
+
+ return fn(*buffer);
+}
+
+} // namespace
nn::GeneralResult<std::shared_ptr<const ResilientBuffer>> ResilientBuffer::create(
Factory makeBuffer) {
@@ -53,9 +82,16 @@
std::lock_guard guard(mMutex);
return mBuffer;
}
-nn::SharedBuffer ResilientBuffer::recover(const nn::IBuffer* /*failingBuffer*/,
- bool /*blocking*/) const {
+nn::GeneralResult<nn::SharedBuffer> ResilientBuffer::recover(
+ const nn::IBuffer* failingBuffer) const {
std::lock_guard guard(mMutex);
+
+ // Another caller updated the failing prepared model.
+ if (mBuffer.get() != failingBuffer) {
+ return mBuffer;
+ }
+
+ mBuffer = NN_TRY(kMakeBuffer());
return mBuffer;
}
@@ -63,13 +99,17 @@
return getBuffer()->getToken();
}
-nn::GeneralResult<void> ResilientBuffer::copyTo(const nn::Memory& dst) const {
- return getBuffer()->copyTo(dst);
+nn::GeneralResult<void> ResilientBuffer::copyTo(const nn::SharedMemory& dst) const {
+ const auto fn = [&dst](const nn::IBuffer& buffer) { return buffer.copyTo(dst); };
+ return protect(*this, fn);
}
-nn::GeneralResult<void> ResilientBuffer::copyFrom(const nn::Memory& src,
+nn::GeneralResult<void> ResilientBuffer::copyFrom(const nn::SharedMemory& src,
const nn::Dimensions& dimensions) const {
- return getBuffer()->copyFrom(src, dimensions);
+ const auto fn = [&src, &dimensions](const nn::IBuffer& buffer) {
+ return buffer.copyFrom(src, dimensions);
+ };
+ return protect(*this, fn);
}
} // namespace android::hardware::neuralnetworks::utils
diff --git a/neuralnetworks/utils/common/src/ResilientBurst.cpp b/neuralnetworks/utils/common/src/ResilientBurst.cpp
new file mode 100644
index 0000000..5ca868b
--- /dev/null
+++ b/neuralnetworks/utils/common/src/ResilientBurst.cpp
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ResilientBurst.h"
+
+#include <android-base/logging.h>
+#include <android-base/thread_annotations.h>
+#include <nnapi/IBurst.h>
+#include <nnapi/Result.h>
+#include <nnapi/TypeUtils.h>
+#include <nnapi/Types.h>
+
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <utility>
+
+namespace android::hardware::neuralnetworks::utils {
+namespace {
+
+template <typename FnType>
+auto protect(const ResilientBurst& resilientBurst, const FnType& fn)
+ -> decltype(fn(*resilientBurst.getBurst())) {
+ auto burst = resilientBurst.getBurst();
+ auto result = fn(*burst);
+
+ // Immediately return if burst is not dead.
+ if (result.has_value() || result.error().code != nn::ErrorStatus::DEAD_OBJECT) {
+ return result;
+ }
+
+ // Attempt recovery and return if it fails.
+ auto maybeBurst = resilientBurst.recover(burst.get());
+ if (!maybeBurst.has_value()) {
+ auto [resultErrorMessage, resultErrorCode, resultOutputShapes] = std::move(result).error();
+ const auto& [recoveryErrorMessage, recoveryErrorCode] = maybeBurst.error();
+ return nn::error(resultErrorCode, std::move(resultOutputShapes))
+ << resultErrorMessage << ", and failed to recover dead burst object with error "
+ << recoveryErrorCode << ": " << recoveryErrorMessage;
+ }
+ burst = std::move(maybeBurst).value();
+
+ return fn(*burst);
+}
+
+} // namespace
+
+nn::GeneralResult<std::shared_ptr<const ResilientBurst>> ResilientBurst::create(Factory makeBurst) {
+ if (makeBurst == nullptr) {
+ return NN_ERROR(nn::ErrorStatus::INVALID_ARGUMENT)
+ << "utils::ResilientBurst::create must have non-empty makeBurst";
+ }
+ auto burst = NN_TRY(makeBurst());
+ CHECK(burst != nullptr);
+ return std::make_shared<ResilientBurst>(PrivateConstructorTag{}, std::move(makeBurst),
+ std::move(burst));
+}
+
+ResilientBurst::ResilientBurst(PrivateConstructorTag /*tag*/, Factory makeBurst,
+ nn::SharedBurst burst)
+ : kMakeBurst(std::move(makeBurst)), mBurst(std::move(burst)) {
+ CHECK(kMakeBurst != nullptr);
+ CHECK(mBurst != nullptr);
+}
+
+nn::SharedBurst ResilientBurst::getBurst() const {
+ std::lock_guard guard(mMutex);
+ return mBurst;
+}
+
+nn::GeneralResult<nn::SharedBurst> ResilientBurst::recover(const nn::IBurst* failingBurst) const {
+ std::lock_guard guard(mMutex);
+
+ // Another caller updated the failing burst.
+ if (mBurst.get() != failingBurst) {
+ return mBurst;
+ }
+
+ mBurst = NN_TRY(kMakeBurst());
+ return mBurst;
+}
+
+ResilientBurst::OptionalCacheHold ResilientBurst::cacheMemory(
+ const nn::SharedMemory& memory) const {
+ return getBurst()->cacheMemory(memory);
+}
+
+nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> ResilientBurst::execute(
+ const nn::Request& request, nn::MeasureTiming measure) const {
+ const auto fn = [&request, measure](const nn::IBurst& burst) {
+ return burst.execute(request, measure);
+ };
+ return protect(*this, fn);
+}
+
+} // namespace android::hardware::neuralnetworks::utils
diff --git a/neuralnetworks/utils/common/src/ResilientDevice.cpp b/neuralnetworks/utils/common/src/ResilientDevice.cpp
index 6ad3fad..13965af 100644
--- a/neuralnetworks/utils/common/src/ResilientDevice.cpp
+++ b/neuralnetworks/utils/common/src/ResilientDevice.cpp
@@ -122,12 +122,14 @@
};
if (compare(&IDevice::getName) || compare(&IDevice::getVersionString) ||
compare(&IDevice::getFeatureLevel) || compare(&IDevice::getType) ||
- compare(&IDevice::getSupportedExtensions) || compare(&IDevice::getCapabilities)) {
+ compare(&IDevice::isUpdatable) || compare(&IDevice::getSupportedExtensions) ||
+ compare(&IDevice::getCapabilities)) {
LOG(ERROR) << "Recovered device has different metadata than what is cached. Marking "
"IDevice object as invalid.";
device = std::make_shared<const InvalidDevice>(
- kName, kVersionString, mDevice->getFeatureLevel(), mDevice->getType(), kExtensions,
- kCapabilities, mDevice->getNumberOfCacheFilesNeeded());
+ kName, kVersionString, mDevice->getFeatureLevel(), mDevice->getType(),
+ mDevice->isUpdatable(), kExtensions, kCapabilities,
+ mDevice->getNumberOfCacheFilesNeeded());
mIsValid = false;
}
@@ -151,6 +153,10 @@
return getDevice()->getType();
}
+bool ResilientDevice::isUpdatable() const {
+ return getDevice()->isUpdatable();
+}
+
const std::vector<nn::Extension>& ResilientDevice::getSupportedExtensions() const {
return kExtensions;
}
@@ -180,6 +186,7 @@
const nn::Model& model, nn::ExecutionPreference preference, nn::Priority priority,
nn::OptionalTimePoint deadline, const std::vector<nn::SharedHandle>& modelCache,
const std::vector<nn::SharedHandle>& dataCache, const nn::CacheToken& token) const {
+#if 0
auto self = shared_from_this();
ResilientPreparedModel::Factory makePreparedModel = [device = std::move(self), model,
preference, priority, deadline, modelCache,
@@ -188,29 +195,41 @@
dataCache, token);
};
return ResilientPreparedModel::create(std::move(makePreparedModel));
+#else
+ return prepareModelInternal(model, preference, priority, deadline, modelCache, dataCache,
+ token);
+#endif
}
nn::GeneralResult<nn::SharedPreparedModel> ResilientDevice::prepareModelFromCache(
nn::OptionalTimePoint deadline, const std::vector<nn::SharedHandle>& modelCache,
const std::vector<nn::SharedHandle>& dataCache, const nn::CacheToken& token) const {
+#if 0
auto self = shared_from_this();
ResilientPreparedModel::Factory makePreparedModel = [device = std::move(self), deadline,
modelCache, dataCache, token] {
return device->prepareModelFromCacheInternal(deadline, modelCache, dataCache, token);
};
return ResilientPreparedModel::create(std::move(makePreparedModel));
+#else
+ return prepareModelFromCacheInternal(deadline, modelCache, dataCache, token);
+#endif
}
nn::GeneralResult<nn::SharedBuffer> ResilientDevice::allocate(
const nn::BufferDesc& desc, const std::vector<nn::SharedPreparedModel>& preparedModels,
const std::vector<nn::BufferRole>& inputRoles,
const std::vector<nn::BufferRole>& outputRoles) const {
+#if 0
auto self = shared_from_this();
ResilientBuffer::Factory makeBuffer = [device = std::move(self), desc, preparedModels,
inputRoles, outputRoles] {
return device->allocateInternal(desc, preparedModels, inputRoles, outputRoles);
};
return ResilientBuffer::create(std::move(makeBuffer));
+#else
+ return allocateInternal(desc, preparedModels, inputRoles, outputRoles);
+#endif
}
bool ResilientDevice::isValidInternal() const {
@@ -225,8 +244,8 @@
if (!isValidInternal()) {
return std::make_shared<const InvalidPreparedModel>();
}
- const auto fn = [&model, preference, priority, deadline, &modelCache, &dataCache,
- token](const nn::IDevice& device) {
+ const auto fn = [&model, preference, priority, &deadline, &modelCache, &dataCache,
+ &token](const nn::IDevice& device) {
return device.prepareModel(model, preference, priority, deadline, modelCache, dataCache,
token);
};
@@ -239,7 +258,7 @@
if (!isValidInternal()) {
return std::make_shared<const InvalidPreparedModel>();
}
- const auto fn = [deadline, &modelCache, &dataCache, token](const nn::IDevice& device) {
+ const auto fn = [&deadline, &modelCache, &dataCache, &token](const nn::IDevice& device) {
return device.prepareModelFromCache(deadline, modelCache, dataCache, token);
};
return protect(*this, fn, /*blocking=*/false);
diff --git a/neuralnetworks/utils/common/src/ResilientPreparedModel.cpp b/neuralnetworks/utils/common/src/ResilientPreparedModel.cpp
index b8acee1..5dd5f99 100644
--- a/neuralnetworks/utils/common/src/ResilientPreparedModel.cpp
+++ b/neuralnetworks/utils/common/src/ResilientPreparedModel.cpp
@@ -16,19 +16,52 @@
#include "ResilientPreparedModel.h"
+#include "InvalidBurst.h"
+#include "ResilientBurst.h"
+
#include <android-base/logging.h>
#include <android-base/thread_annotations.h>
#include <nnapi/IPreparedModel.h>
#include <nnapi/Result.h>
+#include <nnapi/TypeUtils.h>
#include <nnapi/Types.h>
#include <functional>
#include <memory>
#include <mutex>
+#include <sstream>
#include <utility>
#include <vector>
namespace android::hardware::neuralnetworks::utils {
+namespace {
+
+template <typename FnType>
+auto protect(const ResilientPreparedModel& resilientPreparedModel, const FnType& fn)
+ -> decltype(fn(*resilientPreparedModel.getPreparedModel())) {
+ auto preparedModel = resilientPreparedModel.getPreparedModel();
+ auto result = fn(*preparedModel);
+
+ // Immediately return if prepared model is not dead.
+ if (result.has_value() || result.error().code != nn::ErrorStatus::DEAD_OBJECT) {
+ return result;
+ }
+
+ // Attempt recovery and return if it fails.
+ auto maybePreparedModel = resilientPreparedModel.recover(preparedModel.get());
+ if (!maybePreparedModel.has_value()) {
+ const auto& [message, code] = maybePreparedModel.error();
+ std::ostringstream oss;
+ oss << ", and failed to recover dead prepared model with error " << code << ": " << message;
+ result.error().message += oss.str();
+ return result;
+ }
+ preparedModel = std::move(maybePreparedModel).value();
+
+ return fn(*preparedModel);
+}
+
+} // namespace
nn::GeneralResult<std::shared_ptr<const ResilientPreparedModel>> ResilientPreparedModel::create(
Factory makePreparedModel) {
@@ -55,9 +88,16 @@
return mPreparedModel;
}
-nn::SharedPreparedModel ResilientPreparedModel::recover(
- const nn::IPreparedModel* /*failingPreparedModel*/, bool /*blocking*/) const {
+nn::GeneralResult<nn::SharedPreparedModel> ResilientPreparedModel::recover(
+ const nn::IPreparedModel* failingPreparedModel) const {
std::lock_guard guard(mMutex);
+
+ // Another caller updated the failing prepared model.
+ if (mPreparedModel.get() != failingPreparedModel) {
+ return mPreparedModel;
+ }
+
+ mPreparedModel = NN_TRY(kMakePreparedModel());
return mPreparedModel;
}
@@ -65,7 +105,11 @@
ResilientPreparedModel::execute(const nn::Request& request, nn::MeasureTiming measure,
const nn::OptionalTimePoint& deadline,
const nn::OptionalDuration& loopTimeoutDuration) const {
- return getPreparedModel()->execute(request, measure, deadline, loopTimeoutDuration);
+ const auto fn = [&request, measure, &deadline,
+ &loopTimeoutDuration](const nn::IPreparedModel& preparedModel) {
+ return preparedModel.execute(request, measure, deadline, loopTimeoutDuration);
+ };
+ return protect(*this, fn);
}
nn::GeneralResult<std::pair<nn::SyncFence, nn::ExecuteFencedInfoCallback>>
@@ -75,12 +119,43 @@
const nn::OptionalTimePoint& deadline,
const nn::OptionalDuration& loopTimeoutDuration,
const nn::OptionalDuration& timeoutDurationAfterFence) const {
- return getPreparedModel()->executeFenced(request, waitFor, measure, deadline,
- loopTimeoutDuration, timeoutDurationAfterFence);
+ const auto fn = [&request, &waitFor, measure, &deadline, &loopTimeoutDuration,
+ &timeoutDurationAfterFence](const nn::IPreparedModel& preparedModel) {
+ return preparedModel.executeFenced(request, waitFor, measure, deadline, loopTimeoutDuration,
+ timeoutDurationAfterFence);
+ };
+ return protect(*this, fn);
+}
+
+nn::GeneralResult<nn::SharedBurst> ResilientPreparedModel::configureExecutionBurst() const {
+#if 0
+ auto self = shared_from_this();
+ ResilientBurst::Factory makeBurst =
+ [preparedModel = std::move(self)]() -> nn::GeneralResult<nn::SharedBurst> {
+ return preparedModel->configureExecutionBurst();
+ };
+ return ResilientBurst::create(std::move(makeBurst));
+#else
+ return configureExecutionBurstInternal();
+#endif
}
std::any ResilientPreparedModel::getUnderlyingResource() const {
return getPreparedModel()->getUnderlyingResource();
}
+bool ResilientPreparedModel::isValidInternal() const {
+ return true;
+}
+
+nn::GeneralResult<nn::SharedBurst> ResilientPreparedModel::configureExecutionBurstInternal() const {
+ if (!isValidInternal()) {
+ return std::make_shared<const InvalidBurst>();
+ }
+ const auto fn = [](const nn::IPreparedModel& preparedModel) {
+ return preparedModel.configureExecutionBurst();
+ };
+ return protect(*this, fn);
+}
+
} // namespace android::hardware::neuralnetworks::utils
diff --git a/neuralnetworks/utils/common/test/MockBuffer.h b/neuralnetworks/utils/common/test/MockBuffer.h
new file mode 100644
index 0000000..59d5700
--- /dev/null
+++ b/neuralnetworks/utils/common/test/MockBuffer.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_BUFFER
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_BUFFER
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <nnapi/IBuffer.h>
+#include <nnapi/Types.h>
+
+namespace android::nn {
+
+class MockBuffer final : public IBuffer {
+ public:
+ MOCK_METHOD(Request::MemoryDomainToken, getToken, (), (const, override));
+ MOCK_METHOD(GeneralResult<void>, copyTo, (const SharedMemory& dst), (const, override));
+ MOCK_METHOD(GeneralResult<void>, copyFrom,
+ (const SharedMemory& src, const Dimensions& dimensions), (const, override));
+};
+
+} // namespace android::nn
+
+#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_BUFFER
diff --git a/neuralnetworks/utils/common/test/MockDevice.h b/neuralnetworks/utils/common/test/MockDevice.h
new file mode 100644
index 0000000..5566968
--- /dev/null
+++ b/neuralnetworks/utils/common/test/MockDevice.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_DEVICE
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_DEVICE
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <nnapi/IDevice.h>
+
+namespace android::nn {
+
+class MockDevice final : public IDevice {
+ public:
+ MOCK_METHOD(const std::string&, getName, (), (const, override));
+ MOCK_METHOD(const std::string&, getVersionString, (), (const, override));
+ MOCK_METHOD(Version, getFeatureLevel, (), (const, override));
+ MOCK_METHOD(DeviceType, getType, (), (const, override));
+ MOCK_METHOD(bool, isUpdatable, (), (const, override));
+ MOCK_METHOD(const std::vector<Extension>&, getSupportedExtensions, (), (const, override));
+ MOCK_METHOD(const Capabilities&, getCapabilities, (), (const, override));
+ MOCK_METHOD((std::pair<uint32_t, uint32_t>), getNumberOfCacheFilesNeeded, (),
+ (const, override));
+ MOCK_METHOD(GeneralResult<void>, wait, (), (const, override));
+ MOCK_METHOD(GeneralResult<std::vector<bool>>, getSupportedOperations, (const Model& model),
+ (const, override));
+ MOCK_METHOD(GeneralResult<SharedPreparedModel>, prepareModel,
+ (const Model& model, ExecutionPreference preference, Priority priority,
+ OptionalTimePoint deadline, const std::vector<SharedHandle>& modelCache,
+ const std::vector<SharedHandle>& dataCache, const CacheToken& token),
+ (const, override));
+ MOCK_METHOD(GeneralResult<SharedPreparedModel>, prepareModelFromCache,
+ (OptionalTimePoint deadline, const std::vector<SharedHandle>& modelCache,
+ const std::vector<SharedHandle>& dataCache, const CacheToken& token),
+ (const, override));
+ MOCK_METHOD(GeneralResult<SharedBuffer>, allocate,
+ (const BufferDesc& desc, const std::vector<SharedPreparedModel>& preparedModels,
+ const std::vector<BufferRole>& inputRoles,
+ const std::vector<BufferRole>& outputRoles),
+ (const, override));
+};
+
+} // namespace android::nn
+
+#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_DEVICE
diff --git a/neuralnetworks/utils/common/test/MockPreparedModel.h b/neuralnetworks/utils/common/test/MockPreparedModel.h
new file mode 100644
index 0000000..418af61
--- /dev/null
+++ b/neuralnetworks/utils/common/test/MockPreparedModel.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_PREPARED_MODEL
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_PREPARED_MODEL
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <nnapi/IPreparedModel.h>
+
+namespace android::nn {
+
+class MockPreparedModel final : public IPreparedModel {
+ public:
+ MOCK_METHOD((ExecutionResult<std::pair<std::vector<OutputShape>, Timing>>), execute,
+ (const Request& request, MeasureTiming measure, const OptionalTimePoint& deadline,
+ const OptionalDuration& loopTimeoutDuration),
+ (const, override));
+ MOCK_METHOD((GeneralResult<std::pair<SyncFence, ExecuteFencedInfoCallback>>), executeFenced,
+ (const Request& request, const std::vector<SyncFence>& waitFor,
+ MeasureTiming measure, const OptionalTimePoint& deadline,
+ const OptionalDuration& loopTimeoutDuration,
+ const OptionalDuration& timeoutDurationAfterFence),
+ (const, override));
+ MOCK_METHOD(GeneralResult<SharedBurst>, configureExecutionBurst, (), (const, override));
+ MOCK_METHOD(std::any, getUnderlyingResource, (), (const, override));
+};
+
+} // namespace android::nn
+
+#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_TEST_MOCK_PREPARED_MODEL
diff --git a/neuralnetworks/utils/common/test/ResilientBufferTest.cpp b/neuralnetworks/utils/common/test/ResilientBufferTest.cpp
new file mode 100644
index 0000000..7afd020
--- /dev/null
+++ b/neuralnetworks/utils/common/test/ResilientBufferTest.cpp
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gmock/gmock.h>
+#include <nnapi/SharedMemory.h>
+#include <nnapi/TypeUtils.h>
+#include <nnapi/Types.h>
+#include <nnapi/hal/ResilientBuffer.h>
+#include <memory>
+#include <tuple>
+#include <utility>
+#include "MockBuffer.h"
+
+namespace android::hardware::neuralnetworks::utils {
+namespace {
+
+using ::testing::_;
+using ::testing::InvokeWithoutArgs;
+using ::testing::Return;
+
+constexpr auto kToken = nn::Request::MemoryDomainToken{1};
+
+using SharedMockBuffer = std::shared_ptr<const nn::MockBuffer>;
+using MockBufferFactory = ::testing::MockFunction<nn::GeneralResult<nn::SharedBuffer>()>;
+
+SharedMockBuffer createConfiguredMockBuffer() {
+ return std::make_shared<const nn::MockBuffer>();
+}
+
+std::tuple<std::shared_ptr<const nn::MockBuffer>, std::unique_ptr<MockBufferFactory>,
+ std::shared_ptr<const ResilientBuffer>>
+setup() {
+ auto mockBuffer = std::make_shared<const nn::MockBuffer>();
+
+ auto mockBufferFactory = std::make_unique<MockBufferFactory>();
+ EXPECT_CALL(*mockBufferFactory, Call()).Times(1).WillOnce(Return(mockBuffer));
+
+ auto buffer = ResilientBuffer::create(mockBufferFactory->AsStdFunction()).value();
+ return std::make_tuple(std::move(mockBuffer), std::move(mockBufferFactory), std::move(buffer));
+}
+
+constexpr auto makeError = [](nn::ErrorStatus status) {
+ return [status](const auto&... /*args*/) { return nn::error(status); };
+};
+const auto kReturnGeneralFailure = makeError(nn::ErrorStatus::GENERAL_FAILURE);
+const auto kReturnDeadObject = makeError(nn::ErrorStatus::DEAD_OBJECT);
+
+const auto kNoError = nn::GeneralResult<void>{};
+
+} // namespace
+
+TEST(ResilientBufferTest, invalidBufferFactory) {
+ // setup call
+ const auto invalidBufferFactory = ResilientBuffer::Factory{};
+
+ // run test
+ const auto result = ResilientBuffer::create(invalidBufferFactory);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST(ResilientBufferTest, bufferFactoryFailure) {
+ // setup call
+ const auto invalidBufferFactory = kReturnGeneralFailure;
+
+ // run test
+ const auto result = ResilientBuffer::create(invalidBufferFactory);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(ResilientBufferTest, getBuffer) {
+ // setup call
+ const auto [mockBuffer, mockBufferFactory, buffer] = setup();
+
+ // run test
+ const auto result = buffer->getBuffer();
+
+ // verify result
+ EXPECT_TRUE(result == mockBuffer);
+}
+
+TEST(ResilientBufferTest, getToken) {
+ // setup call
+ const auto [mockBuffer, mockBufferFactory, buffer] = setup();
+ EXPECT_CALL(*mockBuffer, getToken()).Times(1).WillOnce(Return(kToken));
+
+ // run test
+ const auto token = buffer->getToken();
+
+ // verify result
+ EXPECT_EQ(token, kToken);
+}
+
+TEST(ResilientBufferTest, copyTo) {
+ // setup call
+ const auto [mockBuffer, mockBufferFactory, buffer] = setup();
+ EXPECT_CALL(*mockBuffer, copyTo(_)).Times(1).WillOnce(Return(kNoError));
+
+ // run test
+ const nn::SharedMemory memory = std::make_shared<const nn::Memory>();
+ const auto result = buffer->copyTo(memory);
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientBufferTest, copyToError) {
+ // setup call
+ const auto [mockBuffer, mockBufferFactory, buffer] = setup();
+ EXPECT_CALL(*mockBuffer, copyTo(_)).Times(1).WillOnce(kReturnGeneralFailure);
+
+ // run test
+ const nn::SharedMemory memory = std::make_shared<const nn::Memory>();
+ const auto result = buffer->copyTo(memory);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(ResilientBufferTest, copyToDeadObjectFailedRecovery) {
+ // setup call
+ const auto [mockBuffer, mockBufferFactory, buffer] = setup();
+ EXPECT_CALL(*mockBuffer, copyTo(_)).Times(1).WillOnce(kReturnDeadObject);
+ EXPECT_CALL(*mockBufferFactory, Call()).Times(1).WillOnce(kReturnGeneralFailure);
+
+ // run test
+ const nn::SharedMemory memory = std::make_shared<const nn::Memory>();
+ const auto result = buffer->copyTo(memory);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(ResilientBufferTest, copyToDeadObjectSuccessfulRecovery) {
+ // setup call
+ const auto [mockBuffer, mockBufferFactory, buffer] = setup();
+ EXPECT_CALL(*mockBuffer, copyTo(_)).Times(1).WillOnce(kReturnDeadObject);
+ const auto recoveredMockBuffer = createConfiguredMockBuffer();
+ EXPECT_CALL(*recoveredMockBuffer, copyTo(_)).Times(1).WillOnce(Return(kNoError));
+ EXPECT_CALL(*mockBufferFactory, Call()).Times(1).WillOnce(Return(recoveredMockBuffer));
+
+ // run test
+ const nn::SharedMemory memory = std::make_shared<const nn::Memory>();
+ const auto result = buffer->copyTo(memory);
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientBufferTest, copyFrom) {
+ // setup call
+ const auto [mockBuffer, mockBufferFactory, buffer] = setup();
+ EXPECT_CALL(*mockBuffer, copyFrom(_, _)).Times(1).WillOnce(Return(kNoError));
+
+ // run test
+ const nn::SharedMemory memory = std::make_shared<const nn::Memory>();
+ const auto result = buffer->copyFrom(memory, {});
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientBufferTest, copyFromError) {
+ // setup call
+ const auto [mockBuffer, mockBufferFactory, buffer] = setup();
+ EXPECT_CALL(*mockBuffer, copyFrom(_, _)).Times(1).WillOnce(kReturnGeneralFailure);
+
+ // run test
+ const nn::SharedMemory memory = std::make_shared<const nn::Memory>();
+ const auto result = buffer->copyFrom(memory, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(ResilientBufferTest, copyFromDeadObjectFailedRecovery) {
+ // setup call
+ const auto [mockBuffer, mockBufferFactory, buffer] = setup();
+ EXPECT_CALL(*mockBuffer, copyFrom(_, _)).Times(1).WillOnce(kReturnDeadObject);
+ EXPECT_CALL(*mockBufferFactory, Call()).Times(1).WillOnce(kReturnGeneralFailure);
+
+ // run test
+ const nn::SharedMemory memory = std::make_shared<const nn::Memory>();
+ const auto result = buffer->copyFrom(memory, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(ResilientBufferTest, copyFromDeadObjectSuccessfulRecovery) {
+ // setup call
+ const auto [mockBuffer, mockBufferFactory, buffer] = setup();
+ EXPECT_CALL(*mockBuffer, copyFrom(_, _)).Times(1).WillOnce(kReturnDeadObject);
+ const auto recoveredMockBuffer = createConfiguredMockBuffer();
+ EXPECT_CALL(*recoveredMockBuffer, copyFrom(_, _)).Times(1).WillOnce(Return(kNoError));
+ EXPECT_CALL(*mockBufferFactory, Call()).Times(1).WillOnce(Return(recoveredMockBuffer));
+
+ // run test
+ const nn::SharedMemory memory = std::make_shared<const nn::Memory>();
+ const auto result = buffer->copyFrom(memory, {});
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientBufferTest, recover) {
+ // setup call
+ const auto [mockBuffer, mockBufferFactory, buffer] = setup();
+ const auto recoveredMockBuffer = createConfiguredMockBuffer();
+ EXPECT_CALL(*mockBufferFactory, Call()).Times(1).WillOnce(Return(recoveredMockBuffer));
+
+ // run test
+ const auto result = buffer->recover(mockBuffer.get());
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ EXPECT_TRUE(result.value() == recoveredMockBuffer);
+}
+
+TEST(ResilientBufferTest, recoverFailure) {
+ // setup call
+ const auto [mockBuffer, mockBufferFactory, buffer] = setup();
+ const auto recoveredMockBuffer = createConfiguredMockBuffer();
+ EXPECT_CALL(*mockBufferFactory, Call()).Times(1).WillOnce(kReturnGeneralFailure);
+
+ // run test
+ const auto result = buffer->recover(mockBuffer.get());
+
+ // verify result
+ EXPECT_FALSE(result.has_value());
+}
+
+TEST(ResilientBufferTest, someoneElseRecovered) {
+ // setup call
+ const auto [mockBuffer, mockBufferFactory, buffer] = setup();
+ const auto recoveredMockBuffer = createConfiguredMockBuffer();
+ EXPECT_CALL(*mockBufferFactory, Call()).Times(1).WillOnce(Return(recoveredMockBuffer));
+ buffer->recover(mockBuffer.get());
+
+ // run test
+ const auto result = buffer->recover(mockBuffer.get());
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ EXPECT_TRUE(result.value() == recoveredMockBuffer);
+}
+
+} // namespace android::hardware::neuralnetworks::utils
diff --git a/neuralnetworks/utils/common/test/ResilientDeviceTest.cpp b/neuralnetworks/utils/common/test/ResilientDeviceTest.cpp
new file mode 100644
index 0000000..3abd724
--- /dev/null
+++ b/neuralnetworks/utils/common/test/ResilientDeviceTest.cpp
@@ -0,0 +1,725 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gmock/gmock.h>
+#include <nnapi/TypeUtils.h>
+#include <nnapi/Types.h>
+#include <nnapi/hal/ResilientDevice.h>
+#include <tuple>
+#include <utility>
+#include "MockBuffer.h"
+#include "MockDevice.h"
+#include "MockPreparedModel.h"
+
+namespace android::hardware::neuralnetworks::utils {
+namespace {
+
+using ::testing::_;
+using ::testing::InvokeWithoutArgs;
+using ::testing::Return;
+
+using SharedMockDevice = std::shared_ptr<const nn::MockDevice>;
+using MockDeviceFactory = ::testing::MockFunction<nn::GeneralResult<nn::SharedDevice>(bool)>;
+
+const std::string kName = "Google-MockV1";
+const std::string kVersionString = "version1";
+const auto kExtensions = std::vector<nn::Extension>{};
+constexpr auto kNoInfo = std::numeric_limits<float>::max();
+constexpr auto kNoPerformanceInfo =
+ nn::Capabilities::PerformanceInfo{.execTime = kNoInfo, .powerUsage = kNoInfo};
+const auto kCapabilities = nn::Capabilities{
+ .relaxedFloat32toFloat16PerformanceScalar = kNoPerformanceInfo,
+ .relaxedFloat32toFloat16PerformanceTensor = kNoPerformanceInfo,
+ .operandPerformance = nn::Capabilities::OperandPerformanceTable::create({}).value(),
+ .ifPerformance = kNoPerformanceInfo,
+ .whilePerformance = kNoPerformanceInfo};
+constexpr auto kNumberOfCacheFilesNeeded = std::pair<uint32_t, uint32_t>(5, 3);
+
+SharedMockDevice createConfiguredMockDevice() {
+ auto mockDevice = std::make_shared<const nn::MockDevice>();
+
+ // Setup default actions for each relevant call.
+ constexpr auto getName_ret = []() -> const std::string& { return kName; };
+ constexpr auto getVersionString_ret = []() -> const std::string& { return kVersionString; };
+ constexpr auto kFeatureLevel = nn::Version::ANDROID_OC_MR1;
+ constexpr auto kDeviceType = nn::DeviceType::ACCELERATOR;
+ constexpr auto getSupportedExtensions_ret = []() -> const std::vector<nn::Extension>& {
+ return kExtensions;
+ };
+ constexpr auto getCapabilities_ret = []() -> const nn::Capabilities& { return kCapabilities; };
+
+ // Setup default actions for each relevant call.
+ ON_CALL(*mockDevice, getName()).WillByDefault(getName_ret);
+ ON_CALL(*mockDevice, getVersionString()).WillByDefault(getVersionString_ret);
+ ON_CALL(*mockDevice, getFeatureLevel()).WillByDefault(Return(kFeatureLevel));
+ ON_CALL(*mockDevice, getType()).WillByDefault(Return(kDeviceType));
+ ON_CALL(*mockDevice, getSupportedExtensions()).WillByDefault(getSupportedExtensions_ret);
+ ON_CALL(*mockDevice, getCapabilities()).WillByDefault(getCapabilities_ret);
+ ON_CALL(*mockDevice, getNumberOfCacheFilesNeeded())
+ .WillByDefault(Return(kNumberOfCacheFilesNeeded));
+
+ // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the
+ // uninteresting methods calls.
+ EXPECT_CALL(*mockDevice, getName()).Times(testing::AnyNumber());
+ EXPECT_CALL(*mockDevice, getVersionString()).Times(testing::AnyNumber());
+ EXPECT_CALL(*mockDevice, getFeatureLevel()).Times(testing::AnyNumber());
+ EXPECT_CALL(*mockDevice, getType()).Times(testing::AnyNumber());
+ EXPECT_CALL(*mockDevice, getSupportedExtensions()).Times(testing::AnyNumber());
+ EXPECT_CALL(*mockDevice, getCapabilities()).Times(testing::AnyNumber());
+ EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded()).Times(testing::AnyNumber());
+
+ return mockDevice;
+}
+
+std::tuple<SharedMockDevice, std::unique_ptr<MockDeviceFactory>,
+ std::shared_ptr<const ResilientDevice>>
+setup() {
+ auto mockDevice = createConfiguredMockDevice();
+
+ auto mockDeviceFactory = std::make_unique<MockDeviceFactory>();
+ EXPECT_CALL(*mockDeviceFactory, Call(true)).Times(1).WillOnce(Return(mockDevice));
+
+ auto device = ResilientDevice::create(mockDeviceFactory->AsStdFunction()).value();
+ return std::make_tuple(std::move(mockDevice), std::move(mockDeviceFactory), std::move(device));
+}
+
+constexpr auto makeError = [](nn::ErrorStatus status) {
+ return [status](const auto&... /*args*/) { return nn::error(status); };
+};
+const auto kReturnGeneralFailure = makeError(nn::ErrorStatus::GENERAL_FAILURE);
+const auto kReturnDeadObject = makeError(nn::ErrorStatus::DEAD_OBJECT);
+
+} // namespace
+
+TEST(ResilientDeviceTest, invalidDeviceFactory) {
+ // setup call
+ const auto invalidDeviceFactory = ResilientDevice::Factory{};
+
+ // run test
+ const auto result = ResilientDevice::create(invalidDeviceFactory);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST(ResilientDeviceTest, preparedModelFactoryFailure) {
+ // setup call
+ const auto invalidDeviceFactory = kReturnGeneralFailure;
+
+ // run test
+ const auto result = ResilientDevice::create(invalidDeviceFactory);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(ResilientDeviceTest, cachedData) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+
+ // run test and verify results
+ EXPECT_EQ(device->getName(), kName);
+ EXPECT_EQ(device->getVersionString(), kVersionString);
+ EXPECT_EQ(device->getSupportedExtensions(), kExtensions);
+ EXPECT_EQ(device->getCapabilities(), kCapabilities);
+}
+
+TEST(ResilientDeviceTest, getFeatureLevel) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ constexpr auto kFeatureLevel = nn::Version::ANDROID_OC_MR1;
+ EXPECT_CALL(*mockDevice, getFeatureLevel()).Times(1).WillOnce(Return(kFeatureLevel));
+
+ // run test
+ const auto featureLevel = device->getFeatureLevel();
+
+ // verify results
+ EXPECT_EQ(featureLevel, kFeatureLevel);
+}
+
+TEST(ResilientDeviceTest, getType) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ constexpr auto kDeviceType = nn::DeviceType::ACCELERATOR;
+ EXPECT_CALL(*mockDevice, getType()).Times(1).WillOnce(Return(kDeviceType));
+
+ // run test
+ const auto type = device->getType();
+
+ // verify results
+ EXPECT_EQ(type, kDeviceType);
+}
+
+TEST(ResilientDeviceTest, getNumberOfCacheFilesNeeded) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded())
+ .Times(1)
+ .WillOnce(Return(kNumberOfCacheFilesNeeded));
+
+ // run test
+ const auto numberOfCacheFilesNeeded = device->getNumberOfCacheFilesNeeded();
+
+ // verify results
+ EXPECT_EQ(numberOfCacheFilesNeeded, kNumberOfCacheFilesNeeded);
+}
+
+TEST(ResilientDeviceTest, getDevice) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+
+ // run test
+ const auto result = device->getDevice();
+
+ // verify result
+ EXPECT_TRUE(result == mockDevice);
+}
+
+TEST(ResilientDeviceTest, wait) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ EXPECT_CALL(*mockDevice, wait()).Times(1).WillOnce(Return(nn::GeneralResult<void>{}));
+
+ // run test
+ const auto result = device->wait();
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientDeviceTest, waitError) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ EXPECT_CALL(*mockDevice, wait()).Times(1).WillOnce(kReturnGeneralFailure);
+
+ // run test
+ const auto result = device->wait();
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(ResilientDeviceTest, waitDeadObjectFailedRecovery) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ EXPECT_CALL(*mockDevice, wait()).Times(1).WillOnce(kReturnDeadObject);
+ EXPECT_CALL(*mockDeviceFactory, Call(true)).Times(1).WillOnce(kReturnGeneralFailure);
+
+ // run test
+ const auto result = device->wait();
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(ResilientDeviceTest, waitDeadObjectSuccessfulRecovery) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ EXPECT_CALL(*mockDevice, wait()).Times(1).WillOnce(kReturnDeadObject);
+ const auto recoveredMockDevice = createConfiguredMockDevice();
+ EXPECT_CALL(*recoveredMockDevice, wait()).Times(1).WillOnce(Return(nn::GeneralResult<void>{}));
+ EXPECT_CALL(*mockDeviceFactory, Call(true)).Times(1).WillOnce(Return(recoveredMockDevice));
+
+ // run test
+ const auto result = device->wait();
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientDeviceTest, getSupportedOperations) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ EXPECT_CALL(*mockDevice, getSupportedOperations(_))
+ .Times(1)
+ .WillOnce(Return(nn::GeneralResult<std::vector<bool>>{}));
+
+ // run test
+ const auto result = device->getSupportedOperations({});
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientDeviceTest, getSupportedOperationsError) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ EXPECT_CALL(*mockDevice, getSupportedOperations(_)).Times(1).WillOnce(kReturnGeneralFailure);
+
+ // run test
+ const auto result = device->getSupportedOperations({});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(ResilientDeviceTest, getSupportedOperationsDeadObjectFailedRecovery) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ EXPECT_CALL(*mockDevice, getSupportedOperations(_)).Times(1).WillOnce(kReturnDeadObject);
+ EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(kReturnGeneralFailure);
+
+ // run test
+ const auto result = device->getSupportedOperations({});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(ResilientDeviceTest, getSupportedOperationsDeadObjectSuccessfulRecovery) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ EXPECT_CALL(*mockDevice, getSupportedOperations(_)).Times(1).WillOnce(kReturnDeadObject);
+ const auto recoveredMockDevice = createConfiguredMockDevice();
+ EXPECT_CALL(*recoveredMockDevice, getSupportedOperations(_))
+ .Times(1)
+ .WillOnce(Return(nn::GeneralResult<std::vector<bool>>{}));
+ EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice));
+
+ // run test
+ const auto result = device->getSupportedOperations({});
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientDeviceTest, prepareModel) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ const auto mockPreparedModel = std::make_shared<const nn::MockPreparedModel>();
+ EXPECT_CALL(*mockDevice, prepareModel(_, _, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(Return(mockPreparedModel));
+
+ // run test
+ const auto result = device->prepareModel({}, {}, {}, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientDeviceTest, prepareModelError) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ EXPECT_CALL(*mockDevice, prepareModel(_, _, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(kReturnGeneralFailure);
+
+ // run test
+ const auto result = device->prepareModel({}, {}, {}, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(ResilientDeviceTest, prepareModelDeadObjectFailedRecovery) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ EXPECT_CALL(*mockDevice, prepareModel(_, _, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(kReturnDeadObject);
+ EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(kReturnGeneralFailure);
+
+ // run test
+ const auto result = device->prepareModel({}, {}, {}, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(ResilientDeviceTest, prepareModelDeadObjectSuccessfulRecovery) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ EXPECT_CALL(*mockDevice, prepareModel(_, _, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(kReturnDeadObject);
+ const auto recoveredMockDevice = createConfiguredMockDevice();
+ const auto mockPreparedModel = std::make_shared<const nn::MockPreparedModel>();
+ EXPECT_CALL(*recoveredMockDevice, prepareModel(_, _, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(Return(mockPreparedModel));
+ EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice));
+
+ // run test
+ const auto result = device->prepareModel({}, {}, {}, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientDeviceTest, prepareModelFromCache) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ const auto mockPreparedModel = std::make_shared<const nn::MockPreparedModel>();
+ EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _))
+ .Times(1)
+ .WillOnce(Return(mockPreparedModel));
+
+ // run test
+ const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientDeviceTest, prepareModelFromCacheError) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _))
+ .Times(1)
+ .WillOnce(kReturnGeneralFailure);
+
+ // run test
+ const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(ResilientDeviceTest, prepareModelFromCacheDeadObjectFailedRecovery) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _))
+ .Times(1)
+ .WillOnce(kReturnDeadObject);
+ EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(kReturnGeneralFailure);
+
+ // run test
+ const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(ResilientDeviceTest, prepareModelFromCacheDeadObjectSuccessfulRecovery) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _))
+ .Times(1)
+ .WillOnce(kReturnDeadObject);
+ const auto recoveredMockDevice = createConfiguredMockDevice();
+ const auto mockPreparedModel = std::make_shared<const nn::MockPreparedModel>();
+ EXPECT_CALL(*recoveredMockDevice, prepareModelFromCache(_, _, _, _))
+ .Times(1)
+ .WillOnce(Return(mockPreparedModel));
+ EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice));
+
+ // run test
+ const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientDeviceTest, allocate) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ const auto mockBuffer = std::make_shared<const nn::MockBuffer>();
+ EXPECT_CALL(*mockDevice, allocate(_, _, _, _)).Times(1).WillOnce(Return(mockBuffer));
+
+ // run test
+ const auto result = device->allocate({}, {}, {}, {});
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientDeviceTest, allocateError) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ EXPECT_CALL(*mockDevice, allocate(_, _, _, _)).Times(1).WillOnce(kReturnGeneralFailure);
+
+ // run test
+ const auto result = device->allocate({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(ResilientDeviceTest, allocateDeadObjectFailedRecovery) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ EXPECT_CALL(*mockDevice, allocate(_, _, _, _)).Times(1).WillOnce(kReturnDeadObject);
+ EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(kReturnGeneralFailure);
+
+ // run test
+ const auto result = device->allocate({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(ResilientDeviceTest, allocateDeadObjectSuccessfulRecovery) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ EXPECT_CALL(*mockDevice, allocate(_, _, _, _)).Times(1).WillOnce(kReturnDeadObject);
+ const auto recoveredMockDevice = createConfiguredMockDevice();
+ const auto mockBuffer = std::make_shared<const nn::MockBuffer>();
+ EXPECT_CALL(*recoveredMockDevice, allocate(_, _, _, _)).Times(1).WillOnce(Return(mockBuffer));
+ EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice));
+
+ // run test
+ const auto result = device->allocate({}, {}, {}, {});
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientDeviceTest, recover) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ const auto recoveredMockDevice = createConfiguredMockDevice();
+ EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice));
+
+ // run test
+ const auto result = device->recover(mockDevice.get(), /*blocking=*/false);
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ EXPECT_TRUE(result.value() == recoveredMockDevice);
+}
+
+TEST(ResilientDeviceTest, recoverFailure) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ const auto recoveredMockDevice = createConfiguredMockDevice();
+ EXPECT_CALL(*mockDeviceFactory, Call(_)).Times(1).WillOnce(kReturnGeneralFailure);
+
+ // run test
+ const auto result = device->recover(mockDevice.get(), /*blocking=*/false);
+
+ // verify result
+ EXPECT_FALSE(result.has_value());
+}
+
+TEST(ResilientDeviceTest, someoneElseRecovered) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ const auto recoveredMockDevice = createConfiguredMockDevice();
+ EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice));
+ device->recover(mockDevice.get(), /*blocking=*/false);
+
+ // run test
+ const auto result = device->recover(mockDevice.get(), /*blocking=*/false);
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ EXPECT_TRUE(result.value() == recoveredMockDevice);
+}
+
+TEST(ResilientDeviceTest, recoverCacheMismatchGetName) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ const auto recoveredMockDevice = createConfiguredMockDevice();
+ const std::string kDifferentName = "Google-DifferentName";
+ const auto ret = [&kDifferentName]() -> const std::string& { return kDifferentName; };
+ EXPECT_CALL(*recoveredMockDevice, getName()).Times(1).WillOnce(ret);
+ EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice));
+
+ // run test
+ const auto result = device->recover(mockDevice.get(), /*blocking=*/false);
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ EXPECT_TRUE(result.value() != nullptr);
+ EXPECT_TRUE(result.value() != mockDevice);
+ EXPECT_TRUE(result.value() != recoveredMockDevice);
+}
+
+TEST(ResilientDeviceTest, recoverCacheMismatchGetVersionString) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ const auto recoveredMockDevice = createConfiguredMockDevice();
+ const std::string kDifferentVersionString = "differentversion";
+ const auto ret = [&kDifferentVersionString]() -> const std::string& {
+ return kDifferentVersionString;
+ };
+ EXPECT_CALL(*recoveredMockDevice, getVersionString()).Times(1).WillOnce(ret);
+ EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice));
+
+ // run test
+ const auto result = device->recover(mockDevice.get(), /*blocking=*/false);
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ EXPECT_TRUE(result.value() != nullptr);
+ EXPECT_TRUE(result.value() != mockDevice);
+ EXPECT_TRUE(result.value() != recoveredMockDevice);
+}
+
+TEST(ResilientDeviceTest, recoverCacheMismatchGetFeatureLevel) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ const auto recoveredMockDevice = createConfiguredMockDevice();
+ EXPECT_CALL(*recoveredMockDevice, getFeatureLevel())
+ .Times(1)
+ .WillOnce(Return(nn::Version::ANDROID_P));
+ EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice));
+
+ // run test
+ const auto result = device->recover(mockDevice.get(), /*blocking=*/false);
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ EXPECT_TRUE(result.value() != nullptr);
+ EXPECT_TRUE(result.value() != mockDevice);
+ EXPECT_TRUE(result.value() != recoveredMockDevice);
+}
+
+TEST(ResilientDeviceTest, recoverCacheMismatchGetType) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ const auto recoveredMockDevice = createConfiguredMockDevice();
+ EXPECT_CALL(*recoveredMockDevice, getType()).Times(1).WillOnce(Return(nn::DeviceType::GPU));
+ EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice));
+
+ // run test
+ const auto result = device->recover(mockDevice.get(), /*blocking=*/false);
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ EXPECT_TRUE(result.value() != nullptr);
+ EXPECT_TRUE(result.value() != mockDevice);
+ EXPECT_TRUE(result.value() != recoveredMockDevice);
+}
+
+TEST(ResilientDeviceTest, recoverCacheMismatchGetSupportedExtensions) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ const auto recoveredMockDevice = createConfiguredMockDevice();
+ const auto kDifferentExtensions =
+ std::vector<nn::Extension>{nn::Extension{.name = "", .operandTypes = {}}};
+ const auto ret = [&kDifferentExtensions]() -> const std::vector<nn::Extension>& {
+ return kDifferentExtensions;
+ };
+ EXPECT_CALL(*recoveredMockDevice, getSupportedExtensions()).Times(1).WillOnce(ret);
+ EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice));
+
+ // run test
+ const auto result = device->recover(mockDevice.get(), /*blocking=*/false);
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ EXPECT_TRUE(result.value() != nullptr);
+ EXPECT_TRUE(result.value() != mockDevice);
+ EXPECT_TRUE(result.value() != recoveredMockDevice);
+}
+
+TEST(ResilientDeviceTest, recoverCacheMismatchGetCapabilities) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ const auto recoveredMockDevice = createConfiguredMockDevice();
+ const auto kDifferentCapabilities = nn::Capabilities{
+ .relaxedFloat32toFloat16PerformanceTensor = {.execTime = 0.5f, .powerUsage = 0.5f},
+ .operandPerformance = nn::Capabilities::OperandPerformanceTable::create({}).value()};
+ const auto ret = [&kDifferentCapabilities]() -> const nn::Capabilities& {
+ return kDifferentCapabilities;
+ };
+ EXPECT_CALL(*recoveredMockDevice, getCapabilities()).Times(1).WillOnce(ret);
+ EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice));
+
+ // run test
+ const auto result = device->recover(mockDevice.get(), /*blocking=*/false);
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ EXPECT_TRUE(result.value() != nullptr);
+ EXPECT_TRUE(result.value() != mockDevice);
+ EXPECT_TRUE(result.value() != recoveredMockDevice);
+}
+
+TEST(ResilientDeviceTest, recoverCacheMismatchInvalidPrepareModel) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ const auto recoveredMockDevice = createConfiguredMockDevice();
+ EXPECT_CALL(*recoveredMockDevice, getType()).Times(1).WillOnce(Return(nn::DeviceType::GPU));
+ EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice));
+ device->recover(mockDevice.get(), /*blocking=*/false);
+
+ // run test
+ auto result = device->prepareModel({}, {}, {}, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ EXPECT_TRUE(result.value() != nullptr);
+}
+
+TEST(ResilientDeviceTest, recoverCacheMismatchInvalidPrepareModelFromCache) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ const auto recoveredMockDevice = createConfiguredMockDevice();
+ EXPECT_CALL(*recoveredMockDevice, getType()).Times(1).WillOnce(Return(nn::DeviceType::GPU));
+ EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice));
+ device->recover(mockDevice.get(), /*blocking=*/false);
+
+ // run test
+ auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ EXPECT_TRUE(result.value() != nullptr);
+}
+
+TEST(ResilientDeviceTest, recoverCacheMismatchInvalidAllocate) {
+ // setup call
+ const auto [mockDevice, mockDeviceFactory, device] = setup();
+ const auto recoveredMockDevice = createConfiguredMockDevice();
+ EXPECT_CALL(*recoveredMockDevice, getType()).Times(1).WillOnce(Return(nn::DeviceType::GPU));
+ EXPECT_CALL(*mockDeviceFactory, Call(false)).Times(1).WillOnce(Return(recoveredMockDevice));
+ device->recover(mockDevice.get(), /*blocking=*/false);
+
+ // run test
+ auto result = device->allocate({}, {}, {}, {});
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ EXPECT_TRUE(result.value() != nullptr);
+}
+
+} // namespace android::hardware::neuralnetworks::utils
diff --git a/neuralnetworks/utils/common/test/ResilientPreparedModelTest.cpp b/neuralnetworks/utils/common/test/ResilientPreparedModelTest.cpp
new file mode 100644
index 0000000..6d86e10
--- /dev/null
+++ b/neuralnetworks/utils/common/test/ResilientPreparedModelTest.cpp
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gmock/gmock.h>
+#include <nnapi/TypeUtils.h>
+#include <nnapi/Types.h>
+#include <nnapi/hal/ResilientPreparedModel.h>
+#include <utility>
+#include "MockPreparedModel.h"
+
+namespace android::hardware::neuralnetworks::utils {
+namespace {
+
+using ::testing::_;
+using ::testing::InvokeWithoutArgs;
+using ::testing::Return;
+
+using SharedMockPreparedModel = std::shared_ptr<const nn::MockPreparedModel>;
+using MockPreparedModelFactory =
+ ::testing::MockFunction<nn::GeneralResult<nn::SharedPreparedModel>()>;
+
+SharedMockPreparedModel createConfiguredMockPreparedModel() {
+ return std::make_shared<const nn::MockPreparedModel>();
+}
+
+std::tuple<std::shared_ptr<const nn::MockPreparedModel>, std::unique_ptr<MockPreparedModelFactory>,
+ std::shared_ptr<const ResilientPreparedModel>>
+setup() {
+ auto mockPreparedModel = std::make_shared<const nn::MockPreparedModel>();
+
+ auto mockPreparedModelFactory = std::make_unique<MockPreparedModelFactory>();
+ EXPECT_CALL(*mockPreparedModelFactory, Call()).Times(1).WillOnce(Return(mockPreparedModel));
+
+ auto buffer = ResilientPreparedModel::create(mockPreparedModelFactory->AsStdFunction()).value();
+ return std::make_tuple(std::move(mockPreparedModel), std::move(mockPreparedModelFactory),
+ std::move(buffer));
+}
+
+constexpr auto makeError = [](nn::ErrorStatus status) {
+ return [status](const auto&... /*args*/) { return nn::error(status); };
+};
+const auto kReturnGeneralFailure = makeError(nn::ErrorStatus::GENERAL_FAILURE);
+const auto kReturnDeadObject = makeError(nn::ErrorStatus::DEAD_OBJECT);
+
+const auto kNoExecutionError =
+ nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>>{};
+const auto kNoFencedExecutionError =
+ nn::GeneralResult<std::pair<nn::SyncFence, nn::ExecuteFencedInfoCallback>>(
+ std::make_pair(nn::SyncFence::createAsSignaled(), nullptr));
+
+struct FakeResource {};
+
+} // namespace
+
+TEST(ResilientPreparedModelTest, invalidPreparedModelFactory) {
+ // setup call
+ const auto invalidPreparedModelFactory = ResilientPreparedModel::Factory{};
+
+ // run test
+ const auto result = ResilientPreparedModel::create(invalidPreparedModelFactory);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST(ResilientPreparedModelTest, preparedModelFactoryFailure) {
+ // setup call
+ const auto invalidPreparedModelFactory = kReturnGeneralFailure;
+
+ // run test
+ const auto result = ResilientPreparedModel::create(invalidPreparedModelFactory);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(ResilientPreparedModelTest, getPreparedModel) {
+ // setup call
+ const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup();
+
+ // run test
+ const auto result = preparedModel->getPreparedModel();
+
+ // verify result
+ EXPECT_TRUE(result == mockPreparedModel);
+}
+
+TEST(ResilientPreparedModelTest, execute) {
+ // setup call
+ const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup();
+ EXPECT_CALL(*mockPreparedModel, execute(_, _, _, _))
+ .Times(1)
+ .WillOnce(Return(kNoExecutionError));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientPreparedModelTest, executeError) {
+ // setup call
+ const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup();
+ EXPECT_CALL(*mockPreparedModel, execute(_, _, _, _)).Times(1).WillOnce(kReturnGeneralFailure);
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(ResilientPreparedModelTest, executeDeadObjectFailedRecovery) {
+ // setup call
+ const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup();
+ EXPECT_CALL(*mockPreparedModel, execute(_, _, _, _)).Times(1).WillOnce(kReturnDeadObject);
+ constexpr auto ret = [] { return nn::error(nn::ErrorStatus::GENERAL_FAILURE); };
+ EXPECT_CALL(*mockPreparedModelFactory, Call()).Times(1).WillOnce(ret);
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(ResilientPreparedModelTest, executeDeadObjectSuccessfulRecovery) {
+ // setup call
+ const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup();
+ EXPECT_CALL(*mockPreparedModel, execute(_, _, _, _)).Times(1).WillOnce(kReturnDeadObject);
+ const auto recoveredMockPreparedModel = createConfiguredMockPreparedModel();
+ EXPECT_CALL(*recoveredMockPreparedModel, execute(_, _, _, _))
+ .Times(1)
+ .WillOnce(Return(kNoExecutionError));
+ EXPECT_CALL(*mockPreparedModelFactory, Call())
+ .Times(1)
+ .WillOnce(Return(recoveredMockPreparedModel));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientPreparedModelTest, executeFenced) {
+ // setup call
+ const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup();
+ EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(Return(kNoFencedExecutionError));
+
+ // run test
+ const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientPreparedModelTest, executeFencedError) {
+ // setup call
+ const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup();
+ EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(kReturnGeneralFailure);
+
+ // run test
+ const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(ResilientPreparedModelTest, executeFencedDeadObjectFailedRecovery) {
+ // setup call
+ const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup();
+ EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(kReturnDeadObject);
+ EXPECT_CALL(*mockPreparedModelFactory, Call()).Times(1).WillOnce(kReturnGeneralFailure);
+
+ // run test
+ const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(ResilientPreparedModelTest, executeFencedDeadObjectSuccessfulRecovery) {
+ // setup call
+ const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup();
+ EXPECT_CALL(*mockPreparedModel, executeFenced(_, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(kReturnDeadObject);
+ const auto recoveredMockPreparedModel = createConfiguredMockPreparedModel();
+ EXPECT_CALL(*recoveredMockPreparedModel, executeFenced(_, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(Return(kNoFencedExecutionError));
+ EXPECT_CALL(*mockPreparedModelFactory, Call())
+ .Times(1)
+ .WillOnce(Return(recoveredMockPreparedModel));
+
+ // run test
+ const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(ResilientPreparedModelTest, getUnderlyingResource) {
+ // setup call
+ const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup();
+ EXPECT_CALL(*mockPreparedModel, getUnderlyingResource())
+ .Times(1)
+ .WillOnce(Return(FakeResource{}));
+
+ // run test
+ const auto resource = preparedModel->getUnderlyingResource();
+
+ // verify resource
+ const FakeResource* maybeFakeResource = std::any_cast<FakeResource>(&resource);
+ EXPECT_NE(maybeFakeResource, nullptr);
+}
+
+TEST(ResilientPreparedModelTest, recover) {
+ // setup call
+ const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup();
+ const auto recoveredMockPreparedModel = createConfiguredMockPreparedModel();
+ EXPECT_CALL(*mockPreparedModelFactory, Call())
+ .Times(1)
+ .WillOnce(Return(recoveredMockPreparedModel));
+
+ // run test
+ const auto result = preparedModel->recover(mockPreparedModel.get());
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ EXPECT_TRUE(result.value() == recoveredMockPreparedModel);
+}
+
+TEST(ResilientPreparedModelTest, recoverFailure) {
+ // setup call
+ const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup();
+ const auto recoveredMockPreparedModel = createConfiguredMockPreparedModel();
+ EXPECT_CALL(*mockPreparedModelFactory, Call()).Times(1).WillOnce(kReturnGeneralFailure);
+
+ // run test
+ const auto result = preparedModel->recover(mockPreparedModel.get());
+
+ // verify result
+ EXPECT_FALSE(result.has_value());
+}
+
+TEST(ResilientPreparedModelTest, someoneElseRecovered) {
+ // setup call
+ const auto [mockPreparedModel, mockPreparedModelFactory, preparedModel] = setup();
+ const auto recoveredMockPreparedModel = createConfiguredMockPreparedModel();
+ EXPECT_CALL(*mockPreparedModelFactory, Call())
+ .Times(1)
+ .WillOnce(Return(recoveredMockPreparedModel));
+ preparedModel->recover(mockPreparedModel.get());
+
+ // run test
+ const auto result = preparedModel->recover(mockPreparedModel.get());
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ EXPECT_TRUE(result.value() == recoveredMockPreparedModel);
+}
+
+} // namespace android::hardware::neuralnetworks::utils
diff --git a/oemlock/aidl/Android.bp b/oemlock/aidl/Android.bp
new file mode 100644
index 0000000..bfc99e7
--- /dev/null
+++ b/oemlock/aidl/Android.bp
@@ -0,0 +1,16 @@
+aidl_interface {
+ name: "android.hardware.oemlock",
+ vendor_available: true,
+ srcs: ["android/hardware/oemlock/*.aidl"],
+ stability: "vintf",
+ backend: {
+ java: {
+ platform_apis: true,
+ },
+ ndk: {
+ vndk: {
+ enabled: true,
+ },
+ },
+ },
+}
diff --git a/oemlock/aidl/aidl_api/android.hardware.oemlock/current/android/hardware/oemlock/IOemLock.aidl b/oemlock/aidl/aidl_api/android.hardware.oemlock/current/android/hardware/oemlock/IOemLock.aidl
new file mode 100644
index 0000000..e3c974d
--- /dev/null
+++ b/oemlock/aidl/aidl_api/android.hardware.oemlock/current/android/hardware/oemlock/IOemLock.aidl
@@ -0,0 +1,27 @@
+///////////////////////////////////////////////////////////////////////////////
+// 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.oemlock;
+@VintfStability
+interface IOemLock {
+ String getName();
+ boolean isOemUnlockAllowedByCarrier();
+ boolean isOemUnlockAllowedByDevice();
+ android.hardware.oemlock.OemLockSecureStatus setOemUnlockAllowedByCarrier(in boolean allowed, in byte[] signature);
+ void setOemUnlockAllowedByDevice(in boolean allowed);
+}
diff --git a/oemlock/aidl/aidl_api/android.hardware.oemlock/current/android/hardware/oemlock/OemLockSecureStatus.aidl b/oemlock/aidl/aidl_api/android.hardware.oemlock/current/android/hardware/oemlock/OemLockSecureStatus.aidl
new file mode 100644
index 0000000..9d1327d
--- /dev/null
+++ b/oemlock/aidl/aidl_api/android.hardware.oemlock/current/android/hardware/oemlock/OemLockSecureStatus.aidl
@@ -0,0 +1,25 @@
+///////////////////////////////////////////////////////////////////////////////
+// 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.oemlock;
+@Backing(type="int") @VintfStability
+enum OemLockSecureStatus {
+ OK = 0,
+ FAILED = 1,
+ INVALID_SIGNATURE = 2,
+}
diff --git a/oemlock/aidl/android/hardware/oemlock/IOemLock.aidl b/oemlock/aidl/android/hardware/oemlock/IOemLock.aidl
new file mode 100644
index 0000000..674ff85
--- /dev/null
+++ b/oemlock/aidl/android/hardware/oemlock/IOemLock.aidl
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.oemlock;
+
+import android.hardware.oemlock.OemLockSecureStatus;
+
+/*
+ * The OEM lock prevents the bootloader from allowing the device to be flashed.
+ *
+ * Both the carrier and the device itself have a say as to whether OEM unlock is
+ * allowed and both must agree that is allowed in order for unlock to be
+ * possible.
+ */
+@VintfStability
+interface IOemLock {
+ /**
+ * Returns a vendor specific identifier of the HAL.
+ *
+ * The name returned must not be interpreted by the framework but must be
+ * passed to vendor code which may use it to identify the security protocol
+ * used by setOemUnlockAllowedByCarrier. This allows the vendor to identify
+ * the protocol without having to maintain a device-to-protocol mapping.
+ *
+ * @return name of the implementation and STATUS_OK if get name successfully
+ */
+ String getName();
+
+ /**
+ * Returns whether OEM unlock is allowed by the carrier.
+ *
+ * @return the current state(allowed/not allowed) of the flag
+ * and STATUS_OK if the flag was successfully read.
+ */
+ boolean isOemUnlockAllowedByCarrier();
+
+ /**
+ * Returns whether OEM unlock ia allowed by the device.
+ *
+ * @return the current state(allowed/not allowed) of the flag
+ * and STATUS_OK if the flag was successfully read.
+ */
+ boolean isOemUnlockAllowedByDevice();
+
+ /**
+ * Updates whether OEM unlock is allowed by the carrier.
+ *
+ * The implementation may require a vendor defined signature to prove the
+ * validity of this request in order to harden its security.
+ *
+ * @param allowed is the new value of the flag.
+ * @param signature to prove validity of this request or empty if not
+ * required.
+ * @return OK if the flag was successfully updated,
+ * INVALID_SIGNATURE if a signature is required but the wrong one
+ * was provided
+ * FAILED if the update was otherwise unsuccessful.
+ */
+ OemLockSecureStatus setOemUnlockAllowedByCarrier(in boolean allowed, in byte[] signature);
+
+ /**
+ * Updates whether OEM unlock is allowed by the device.
+ *
+ * @param allowed the new value of the flag.
+ * @return STATUS_OK if the flag was successfully updated.
+ */
+ void setOemUnlockAllowedByDevice(in boolean allowed);
+}
diff --git a/oemlock/aidl/android/hardware/oemlock/OemLockSecureStatus.aidl b/oemlock/aidl/android/hardware/oemlock/OemLockSecureStatus.aidl
new file mode 100644
index 0000000..3c11377
--- /dev/null
+++ b/oemlock/aidl/android/hardware/oemlock/OemLockSecureStatus.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.oemlock;
+
+@VintfStability
+@Backing(type="int")
+enum OemLockSecureStatus {
+ /**
+ * The operation completed successfully.
+ */
+ OK,
+ /**
+ * The operation encountered a problem.
+ */
+ FAILED,
+ /**
+ * An invalid signature was provided so the operation was not performed.
+ */
+ INVALID_SIGNATURE,
+}
diff --git a/oemlock/aidl/default/Android.bp b/oemlock/aidl/default/Android.bp
new file mode 100644
index 0000000..464b0a3
--- /dev/null
+++ b/oemlock/aidl/default/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_binary {
+ name: "android.hardware.oemlock-service.example",
+ relative_install_path: "hw",
+ init_rc: ["android.hardware.oemlock-service.example.rc"],
+ vintf_fragments: ["android.hardware.oemlock-service.example.xml"],
+ vendor: true,
+ srcs: [
+ "service.cpp",
+ "OemLock.cpp",
+ ],
+ shared_libs: [
+ "android.hardware.oemlock-V1-ndk_platform",
+ "libbase",
+ "libbinder_ndk",
+ ],
+}
diff --git a/oemlock/aidl/default/OemLock.cpp b/oemlock/aidl/default/OemLock.cpp
new file mode 100644
index 0000000..646b532
--- /dev/null
+++ b/oemlock/aidl/default/OemLock.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "OemLock.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace oemlock {
+
+// Methods from ::android::hardware::oemlock::IOemLock follow.
+
+::ndk::ScopedAStatus OemLock::getName(std::string *out_name) {
+ (void)out_name;
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus OemLock::setOemUnlockAllowedByCarrier(bool in_allowed, const std::vector<uint8_t> &in_signature, OemLockSecureStatus *_aidl_return) {
+ (void)in_allowed;
+ (void)in_signature;
+ (void)_aidl_return;
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus OemLock::isOemUnlockAllowedByCarrier(bool *out_allowed) {
+ (void)out_allowed;
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus OemLock::setOemUnlockAllowedByDevice(bool in_allowed) {
+ (void)in_allowed;
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus OemLock::isOemUnlockAllowedByDevice(bool *out_allowed) {
+ (void)out_allowed;
+ return ::ndk::ScopedAStatus::ok();
+}
+
+} // namespace oemlock
+} // namespace hardware
+} // namespace android
+} // aidl
diff --git a/oemlock/aidl/default/OemLock.h b/oemlock/aidl/default/OemLock.h
new file mode 100644
index 0000000..b0df414
--- /dev/null
+++ b/oemlock/aidl/default/OemLock.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/oemlock/BnOemLock.h>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace oemlock {
+
+using ::aidl::android::hardware::oemlock::IOemLock;
+using ::aidl::android::hardware::oemlock::OemLockSecureStatus;
+
+struct OemLock : public BnOemLock {
+public:
+ OemLock() = default;
+
+ // Methods from ::android::hardware::oemlock::IOemLock follow.
+ ::ndk::ScopedAStatus getName(std::string* out_name) override;
+ ::ndk::ScopedAStatus isOemUnlockAllowedByCarrier(bool* out_allowed) override;
+ ::ndk::ScopedAStatus isOemUnlockAllowedByDevice(bool* out_allowed) override;
+ ::ndk::ScopedAStatus setOemUnlockAllowedByCarrier(bool in_allowed, const std::vector<uint8_t>& in_signature, OemLockSecureStatus* _aidl_return) override;
+ ::ndk::ScopedAStatus setOemUnlockAllowedByDevice(bool in_allowed) override;
+};
+
+} // namespace oemlock
+} // namespace hardware
+} // namespace android
+} // aidl
diff --git a/oemlock/aidl/default/android.hardware.oemlock-service.example.rc b/oemlock/aidl/default/android.hardware.oemlock-service.example.rc
new file mode 100644
index 0000000..57b79d3
--- /dev/null
+++ b/oemlock/aidl/default/android.hardware.oemlock-service.example.rc
@@ -0,0 +1,4 @@
+service vendor.oemlock_default /vendor/bin/hw/android.hardware.oemlock-service.example
+ class hal
+ user hsm
+ group hsm
diff --git a/oemlock/aidl/default/android.hardware.oemlock-service.example.xml b/oemlock/aidl/default/android.hardware.oemlock-service.example.xml
new file mode 100644
index 0000000..b9f137f
--- /dev/null
+++ b/oemlock/aidl/default/android.hardware.oemlock-service.example.xml
@@ -0,0 +1,9 @@
+<manifest version="1.0" type="device">
+ <hal format="aidl">
+ <name>android.hardware.oemlock</name>
+ <interface>
+ <name>IOemLock</name>
+ <instance>default</instance>
+ </interface>
+ </hal>
+</manifest>
diff --git a/oemlock/aidl/default/service.cpp b/oemlock/aidl/default/service.cpp
new file mode 100644
index 0000000..af828a0
--- /dev/null
+++ b/oemlock/aidl/default/service.cpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include "OemLock.h"
+
+using ::aidl::android::hardware::oemlock::OemLock;
+
+int main() {
+ ABinderProcess_setThreadPoolMaxThreadCount(0);
+ std::shared_ptr<OemLock> oemlock = ndk::SharedRefBase::make<OemLock>();
+
+ const std::string instance = std::string() + OemLock::descriptor + "/default";
+ binder_status_t status = AServiceManager_addService(oemlock->asBinder().get(), instance.c_str());
+ CHECK(status == STATUS_OK);
+
+ ABinderProcess_joinThreadPool();
+ return -1; // Should never be reached
+}
diff --git a/oemlock/aidl/vts/Android.bp b/oemlock/aidl/vts/Android.bp
new file mode 100644
index 0000000..18b53c2
--- /dev/null
+++ b/oemlock/aidl/vts/Android.bp
@@ -0,0 +1,33 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_test {
+ name: "VtsHalOemLockTargetTest",
+ defaults: [
+ "VtsHalTargetTestDefaults",
+ "use_libaidlvintf_gtest_helper_static",
+ ],
+ srcs: ["VtsHalOemLockTargetTest.cpp"],
+ shared_libs: [
+ "libbinder_ndk",
+ "libbase",
+ ],
+ static_libs: ["android.hardware.oemlock-V1-ndk_platform"],
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
+}
diff --git a/oemlock/aidl/vts/OWNERS b/oemlock/aidl/vts/OWNERS
new file mode 100644
index 0000000..40d95e4
--- /dev/null
+++ b/oemlock/aidl/vts/OWNERS
@@ -0,0 +1,2 @@
+chengyouho@google.com
+frankwoo@google.com
diff --git a/oemlock/aidl/vts/VtsHalOemLockTargetTest.cpp b/oemlock/aidl/vts/VtsHalOemLockTargetTest.cpp
new file mode 100644
index 0000000..6bf6298
--- /dev/null
+++ b/oemlock/aidl/vts/VtsHalOemLockTargetTest.cpp
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+
+#include <aidl/android/hardware/oemlock/IOemLock.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+using ::aidl::android::hardware::oemlock::IOemLock;
+using ::aidl::android::hardware::oemlock::OemLockSecureStatus;
+
+using ndk::SpAIBinder;
+
+struct OemLockAidlTest : public ::testing::TestWithParam<std::string> {
+ virtual void SetUp() override {
+ oemlock = IOemLock::fromBinder(
+ SpAIBinder(AServiceManager_waitForService(GetParam().c_str())));
+ ASSERT_NE(oemlock, nullptr);
+ }
+
+ virtual void TearDown() override {}
+
+ std::shared_ptr<IOemLock> oemlock;
+};
+
+/*
+ * Check the name can be retrieved
+ */
+TEST_P(OemLockAidlTest, GetName) {
+ std::string name;
+
+ const auto ret = oemlock->getName(&name);
+
+ ASSERT_TRUE(ret.isOk());
+ // Any value acceptable
+};
+
+/*
+ * Check the unlock allowed by device state can be queried
+ */
+TEST_P(OemLockAidlTest, QueryUnlockAllowedByDevice) {
+ bool allowed;
+
+ const auto ret = oemlock->isOemUnlockAllowedByDevice(&allowed);
+
+ ASSERT_TRUE(ret.isOk());
+ // Any value acceptable
+}
+
+/*
+ * Check unlock allowed by device state can be toggled
+ */
+TEST_P(OemLockAidlTest, AllowedByDeviceCanBeToggled) {
+ bool allowed;
+
+ // Get the original state so it can be restored
+ const auto get_ret = oemlock->isOemUnlockAllowedByDevice(&allowed);
+ ASSERT_TRUE(get_ret.isOk());
+ const bool originallyAllowed = allowed;
+
+ // Toggle the state
+ const auto set_ret = oemlock->setOemUnlockAllowedByDevice(!originallyAllowed);
+ ASSERT_TRUE(set_ret.isOk());
+
+ const auto check_set_ret = oemlock->isOemUnlockAllowedByDevice(&allowed);
+ ASSERT_TRUE(check_set_ret.isOk());
+ ASSERT_EQ(allowed, !originallyAllowed);
+
+ // Restore the state
+ const auto restore_ret = oemlock->setOemUnlockAllowedByDevice(originallyAllowed);
+ ASSERT_TRUE(restore_ret.isOk());
+
+ const auto check_restore_ret = oemlock->isOemUnlockAllowedByDevice(&allowed);
+ ASSERT_TRUE(check_restore_ret.isOk());
+ ASSERT_EQ(allowed, originallyAllowed);
+}
+
+/*
+ * Check the unlock allowed by device state can be queried
+ */
+TEST_P(OemLockAidlTest, QueryUnlockAllowedByCarrier) {
+ bool allowed;
+
+ const auto ret = oemlock->isOemUnlockAllowedByCarrier(&allowed);
+
+ ASSERT_TRUE(ret.isOk());
+ // Any value acceptable
+}
+
+/*
+ * Attempt to check unlock allowed by carrier can be toggled
+ *
+ * The implementation may involve a signature which cannot be tested here. That
+ * is a valid implementation so the test will pass. If there is no signature
+ * required, the test will toggle the value.
+ */
+TEST_P(OemLockAidlTest, CarrierUnlock) {
+ const std::vector<uint8_t> noSignature = {};
+ bool allowed;
+ OemLockSecureStatus secure_status;
+
+ // Get the original state so it can be restored
+ const auto get_ret = oemlock->isOemUnlockAllowedByCarrier(&allowed);
+ ASSERT_TRUE(get_ret.isOk());
+ const bool originallyAllowed = allowed;
+
+ if (originallyAllowed) {
+ // Only applied to locked devices
+ return;
+ }
+
+ // Toggle the state
+ const auto set_ret = oemlock->setOemUnlockAllowedByCarrier(!originallyAllowed, noSignature, &secure_status);
+ ASSERT_TRUE(set_ret.isOk());
+ ASSERT_NE(secure_status, OemLockSecureStatus::FAILED);
+ const auto set_status = secure_status;
+
+ const auto check_set_ret = oemlock->isOemUnlockAllowedByCarrier(&allowed);
+ ASSERT_TRUE(check_set_ret.isOk());
+
+ if (set_status == OemLockSecureStatus::INVALID_SIGNATURE) {
+ // Signature is required so we cannot toggle the value in the test, but this is allowed
+ ASSERT_EQ(allowed, originallyAllowed);
+ return;
+ }
+
+ ASSERT_EQ(set_status, OemLockSecureStatus::OK);
+ ASSERT_EQ(allowed, !originallyAllowed);
+
+ // Restore the state
+ const auto restore_ret = oemlock->setOemUnlockAllowedByCarrier(originallyAllowed, noSignature, &secure_status);
+ ASSERT_TRUE(restore_ret.isOk());
+ ASSERT_EQ(secure_status, OemLockSecureStatus::OK);
+
+ const auto check_restore_ret = oemlock->isOemUnlockAllowedByCarrier(&allowed);
+ ASSERT_TRUE(check_restore_ret.isOk());
+ ASSERT_EQ(allowed, originallyAllowed);
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(OemLockAidlTest);
+INSTANTIATE_TEST_SUITE_P(
+ PerInstance, OemLockAidlTest,
+ testing::ValuesIn(android::getAidlHalInstanceNames(IOemLock::descriptor)),
+ android::PrintInstanceNameToString);
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ ABinderProcess_setThreadPoolMaxThreadCount(1);
+ ABinderProcess_startThreadPool();
+ return RUN_ALL_TESTS();
+}
diff --git a/power/aidl/default/Android.bp b/power/aidl/default/Android.bp
index 07cd368..de04bcd 100644
--- a/power/aidl/default/Android.bp
+++ b/power/aidl/default/Android.bp
@@ -21,7 +21,7 @@
shared_libs: [
"libbase",
"libbinder_ndk",
- "android.hardware.power-ndk_platform",
+ "android.hardware.power-V1-ndk_platform",
],
srcs: [
"main.cpp",
diff --git a/power/aidl/vts/Android.bp b/power/aidl/vts/Android.bp
index 28b08c7..008073b 100644
--- a/power/aidl/vts/Android.bp
+++ b/power/aidl/vts/Android.bp
@@ -23,7 +23,7 @@
"libbinder",
],
static_libs: [
- "android.hardware.power-cpp",
+ "android.hardware.power-V1-cpp",
],
test_suites: [
"vts",
diff --git a/power/stats/aidl/Android.bp b/power/stats/aidl/Android.bp
new file mode 100644
index 0000000..1688b12
--- /dev/null
+++ b/power/stats/aidl/Android.bp
@@ -0,0 +1,35 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+aidl_interface {
+ name: "android.hardware.power.stats",
+ vendor_available: true,
+ srcs: [
+ "android/hardware/power/stats/*.aidl",
+ ],
+ stability: "vintf",
+ backend: {
+ java: {
+ platform_apis: true,
+ },
+ ndk: {
+ vndk: {
+ enabled: true,
+ },
+ },
+ cpp: {
+ enabled: false,
+ },
+ },
+}
diff --git a/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/Channel.aidl b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/Channel.aidl
new file mode 100644
index 0000000..e0c372b
--- /dev/null
+++ b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/Channel.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.power.stats;
+@VintfStability
+parcelable Channel {
+ int id;
+ @utf8InCpp String name;
+ @utf8InCpp String subsystem;
+}
diff --git a/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumer.aidl b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumer.aidl
new file mode 100644
index 0000000..cd9239e
--- /dev/null
+++ b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumer.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.power.stats;
+@VintfStability
+parcelable EnergyConsumer {
+ int id;
+ int ordinal;
+ android.hardware.power.stats.EnergyConsumerType type = android.hardware.power.stats.EnergyConsumerType.OTHER;
+ @utf8InCpp String name;
+}
diff --git a/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumerAttribution.aidl b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumerAttribution.aidl
new file mode 100644
index 0000000..2fdc1a5
--- /dev/null
+++ b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumerAttribution.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.power.stats;
+@VintfStability
+parcelable EnergyConsumerAttribution {
+ int uid;
+ long energyUWs;
+}
diff --git a/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumerResult.aidl b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumerResult.aidl
new file mode 100644
index 0000000..4937aef
--- /dev/null
+++ b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumerResult.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.power.stats;
+@VintfStability
+parcelable EnergyConsumerResult {
+ int id;
+ long timestampMs;
+ long energyUWs;
+ android.hardware.power.stats.EnergyConsumerAttribution[] attribution;
+}
diff --git a/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumerType.aidl b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumerType.aidl
new file mode 100644
index 0000000..ce3e1f5
--- /dev/null
+++ b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumerType.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.power.stats;
+@VintfStability
+enum EnergyConsumerType {
+ OTHER = 0,
+ BLUETOOTH = 1,
+ CPU_CLUSTER = 2,
+ DISPLAY = 3,
+ GNSS = 4,
+ MOBILE_RADIO = 5,
+ WIFI = 6,
+}
diff --git a/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyMeasurement.aidl b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyMeasurement.aidl
new file mode 100644
index 0000000..2d6ae16
--- /dev/null
+++ b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyMeasurement.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.power.stats;
+@VintfStability
+parcelable EnergyMeasurement {
+ int id;
+ long timestampMs;
+ long durationMs;
+ long energyUWs;
+}
diff --git a/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/IPowerStats.aidl b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/IPowerStats.aidl
new file mode 100644
index 0000000..5b11695
--- /dev/null
+++ b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/IPowerStats.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.power.stats;
+@VintfStability
+interface IPowerStats {
+ android.hardware.power.stats.PowerEntity[] getPowerEntityInfo();
+ android.hardware.power.stats.StateResidencyResult[] getStateResidency(in int[] powerEntityIds);
+ android.hardware.power.stats.EnergyConsumer[] getEnergyConsumerInfo();
+ android.hardware.power.stats.EnergyConsumerResult[] getEnergyConsumed(in int[] energyConsumerIds);
+ android.hardware.power.stats.Channel[] getEnergyMeterInfo();
+ android.hardware.power.stats.EnergyMeasurement[] readEnergyMeter(in int[] channelIds);
+}
diff --git a/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/PowerEntity.aidl b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/PowerEntity.aidl
new file mode 100644
index 0000000..38a62ac
--- /dev/null
+++ b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/PowerEntity.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.power.stats;
+@VintfStability
+parcelable PowerEntity {
+ int id;
+ @utf8InCpp String name;
+ android.hardware.power.stats.State[] states;
+}
diff --git a/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/State.aidl b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/State.aidl
new file mode 100644
index 0000000..1c5d7c1
--- /dev/null
+++ b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/State.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.power.stats;
+@VintfStability
+parcelable State {
+ int id;
+ @utf8InCpp String name;
+}
diff --git a/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/StateResidency.aidl b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/StateResidency.aidl
new file mode 100644
index 0000000..7982f02
--- /dev/null
+++ b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/StateResidency.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.power.stats;
+@VintfStability
+parcelable StateResidency {
+ int id;
+ long totalTimeInStateMs;
+ long totalStateEntryCount;
+ long lastEntryTimestampMs;
+}
diff --git a/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/StateResidencyResult.aidl b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/StateResidencyResult.aidl
new file mode 100644
index 0000000..5bc548e
--- /dev/null
+++ b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/StateResidencyResult.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.power.stats;
+@VintfStability
+parcelable StateResidencyResult {
+ int id;
+ android.hardware.power.stats.StateResidency[] stateResidencyData;
+}
diff --git a/power/stats/aidl/android/hardware/power/stats/Channel.aidl b/power/stats/aidl/android/hardware/power/stats/Channel.aidl
new file mode 100644
index 0000000..5e8962e
--- /dev/null
+++ b/power/stats/aidl/android/hardware/power/stats/Channel.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.power.stats;
+
+@VintfStability
+parcelable Channel {
+ /**
+ * Unique ID of this Channel
+ */
+ int id;
+ /**
+ * Unique name of this Channel. Vendor/device specific. Opaque to framework
+ */
+ @utf8InCpp String name;
+
+ /**
+ * Name of the subsystem associated with this Channel. Opaque to framework
+ */
+ @utf8InCpp String subsystem;
+}
+
diff --git a/power/stats/aidl/android/hardware/power/stats/EnergyConsumer.aidl b/power/stats/aidl/android/hardware/power/stats/EnergyConsumer.aidl
new file mode 100644
index 0000000..ec616f2
--- /dev/null
+++ b/power/stats/aidl/android/hardware/power/stats/EnergyConsumer.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.power.stats;
+
+import android.hardware.power.stats.EnergyConsumerType;
+
+@VintfStability
+parcelable EnergyConsumer {
+ /**
+ * Unique ID of this EnergyConsumer
+ */
+ int id;
+
+ /**
+ * For a group of EnergyConsumers of the same logical type, sorting by ordinal
+ * gives their physical order. Ordinals must be consecutive integers starting from 0.
+ */
+ int ordinal;
+
+ /* Type of this EnergyConsumer */
+ EnergyConsumerType type = EnergyConsumerType.OTHER;
+
+ /**
+ * Unique name of this EnergyConsumer. Vendor/device specific. Opaque to framework
+ */
+ @utf8InCpp String name;
+}
diff --git a/health/1.0/default/libhealthd/healthd_board_default.cpp b/power/stats/aidl/android/hardware/power/stats/EnergyConsumerAttribution.aidl
similarity index 60%
copy from health/1.0/default/libhealthd/healthd_board_default.cpp
copy to power/stats/aidl/android/hardware/power/stats/EnergyConsumerAttribution.aidl
index 127f98e..5767de1 100644
--- a/health/1.0/default/libhealthd/healthd_board_default.cpp
+++ b/power/stats/aidl/android/hardware/power/stats/EnergyConsumerAttribution.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,16 @@
* limitations under the License.
*/
-#include <healthd/healthd.h>
+package android.hardware.power.stats;
-void healthd_board_init(struct healthd_config*)
-{
- // use defaults
-}
-
-int healthd_board_battery_update(struct android::BatteryProperties*)
-{
- // return 0 to log periodic polled battery status to kernel log
- return 0;
+@VintfStability
+parcelable EnergyConsumerAttribution {
+ /**
+ * Android ID / Linux UID, the accumulated energy is attributed to
+ */
+ int uid;
+ /**
+ * Accumulated energy since boot in microwatt-seconds (uWs) for this AID
+ */
+ long energyUWs;
}
diff --git a/power/stats/aidl/android/hardware/power/stats/EnergyConsumerResult.aidl b/power/stats/aidl/android/hardware/power/stats/EnergyConsumerResult.aidl
new file mode 100644
index 0000000..12d2042
--- /dev/null
+++ b/power/stats/aidl/android/hardware/power/stats/EnergyConsumerResult.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.power.stats;
+
+import android.hardware.power.stats.EnergyConsumerAttribution;
+
+@VintfStability
+parcelable EnergyConsumerResult {
+ /**
+ * ID of the EnergyConsumer associated with this result
+ */
+ int id;
+ /**
+ * Time since boot in milliseconds
+ */
+ long timestampMs;
+ /**
+ * Total accumulated energy since boot in microwatt-seconds (uWs)
+ */
+ long energyUWs;
+ /**
+ * Optional attributed energy per Android ID / Linux UID for this EnergyConsumer.
+ * Sum total of attributed energy must be less than or equal to total accumulated energy.
+ */
+ EnergyConsumerAttribution[] attribution;
+}
+
diff --git a/health/1.0/default/libhealthd/healthd_board_default.cpp b/power/stats/aidl/android/hardware/power/stats/EnergyConsumerType.aidl
similarity index 64%
rename from health/1.0/default/libhealthd/healthd_board_default.cpp
rename to power/stats/aidl/android/hardware/power/stats/EnergyConsumerType.aidl
index 127f98e..d871ced 100644
--- a/health/1.0/default/libhealthd/healthd_board_default.cpp
+++ b/power/stats/aidl/android/hardware/power/stats/EnergyConsumerType.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,16 @@
* limitations under the License.
*/
-#include <healthd/healthd.h>
+package android.hardware.power.stats;
-void healthd_board_init(struct healthd_config*)
-{
- // use defaults
-}
-
-int healthd_board_battery_update(struct android::BatteryProperties*)
-{
- // return 0 to log periodic polled battery status to kernel log
- return 0;
+/* Indicates the type of an energy consumer reported by the Power Stats HAL */
+@VintfStability
+enum EnergyConsumerType {
+ OTHER,
+ BLUETOOTH,
+ CPU_CLUSTER,
+ DISPLAY,
+ GNSS,
+ MOBILE_RADIO,
+ WIFI,
}
diff --git a/power/stats/aidl/android/hardware/power/stats/EnergyMeasurement.aidl b/power/stats/aidl/android/hardware/power/stats/EnergyMeasurement.aidl
new file mode 100644
index 0000000..d3e8f46
--- /dev/null
+++ b/power/stats/aidl/android/hardware/power/stats/EnergyMeasurement.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.power.stats;
+
+@VintfStability
+parcelable EnergyMeasurement {
+ /**
+ * ID of the Channel associated with this measurement
+ */
+ int id;
+ /**
+ * Approximate time of data capture in millseconds since boot
+ */
+ long timestampMs;
+ /**
+ * Duration in milliseconds that energy has been accumulated
+ */
+ long durationMs;
+ /**
+ * Accumulated energy in microwatt-seconds (uWs)
+ */
+ long energyUWs;
+}
+
diff --git a/power/stats/aidl/android/hardware/power/stats/IPowerStats.aidl b/power/stats/aidl/android/hardware/power/stats/IPowerStats.aidl
new file mode 100644
index 0000000..7a95f74
--- /dev/null
+++ b/power/stats/aidl/android/hardware/power/stats/IPowerStats.aidl
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.power.stats;
+
+import android.hardware.power.stats.Channel;
+import android.hardware.power.stats.EnergyConsumer;
+import android.hardware.power.stats.EnergyConsumerResult;
+import android.hardware.power.stats.EnergyMeasurement;
+import android.hardware.power.stats.PowerEntity;
+import android.hardware.power.stats.StateResidencyResult;
+
+@VintfStability
+interface IPowerStats {
+ /**
+ * Return information related to all supported PowerEntity(s) for which state residency data
+ * is available.
+ *
+ * A PowerEntity is defined as a platform subsystem, peripheral, or power domain that impacts
+ * the total device power consumption.
+ *
+ * @return List of information on each PowerEntity
+ */
+ PowerEntity[] getPowerEntityInfo();
+
+ /**
+ * Reports the accumulated state residency for each requested PowerEntity.
+ *
+ * Each PowerEntity may reside in one of multiple states. It may also
+ * transition from one state to another. StateResidency is defined as
+ * an accumulation of time that a PowerEntity resided in each
+ * of its possible states, the number of times that each state was
+ * entered, and a timestamp corresponding to the last time that state
+ * was entered.
+ *
+ * Data is accumulated starting at device boot.
+ *
+ * @param powerEntityIds List of IDs of PowerEntities for which data is requested.
+ * Passing an empty list will return state residency for all available PowerEntitys.
+ * ID of each PowerEntity is contained in PowerEntityInfo.
+ *
+ * @return StateResidency since boot for each requested PowerEntity
+ *
+ * Returns the following service-specific exceptions in order of highest priority:
+ * - STATUS_BAD_VALUE if an invalid powerEntityId is provided
+ * - STATUS_FAILED_TRANSACTION if any StateResidencyResult fails to be returned
+ */
+ StateResidencyResult[] getStateResidency(in int[] powerEntityIds);
+
+ /**
+ * Return the list EnergyConsumers for which energy consumption data is available.
+ *
+ * An EnergyConsumer is a device subsystem or peripheral that consumes energy. Energy
+ * consumption data may be used by framework for the purpose of power attribution.
+ *
+ * @return List of EnergyConsumers that are available.
+ */
+ EnergyConsumer[] getEnergyConsumerInfo();
+
+ /**
+ * Reports the energy consumed since boot by each requested EnergyConsumer.
+ *
+ * @param energyConsumerIds List of IDs of EnergyConsumers for which data is requested.
+ * Passing an empty list will return state residency for all available EnergyConsumers.
+ *
+ * @return Energy consumed since boot for each requested EnergyConsumer
+ *
+ * Returns the following service-specific exceptions in order of highest priority:
+ * - STATUS_BAD_VALUE if an invalid energyConsumerId is provided
+ * - STATUS_FAILED_TRANSACTION if any EnergyConsumerResult fails to be returned
+ */
+ EnergyConsumerResult[] getEnergyConsumed(in int[] energyConsumerIds);
+
+ /**
+ * Return information related to all channels monitored by Energy Meters.
+ *
+ * An Energy Meter is a device that monitors energy and may support monitoring multiple
+ * channels simultaneously. A channel may correspond a bus, sense resistor, or power rail.
+ *
+ * @return Channels monitored by Energy Meters.
+ */
+ Channel[] getEnergyMeterInfo();
+
+ /**
+ * Reports accumulated energy for each specified channel.
+ *
+ * @param channelIds IDs of channels for which data is requested.
+ * Passing an empty list will return energy measurements for all available channels.
+ * ID of each channel is contained in ChannelInfo.
+ *
+ * @return Energy measured since boot for each requested channel
+ *
+ * Returns the following service-specific exceptions in order of highest priority:
+ * - STATUS_BAD_VALUE if an invalid channelId is provided
+ * - STATUS_FAILED_TRANSACTION if any EnergyMeasurement fails to be returned
+ */
+ EnergyMeasurement[] readEnergyMeter(in int[] channelIds);
+}
diff --git a/power/stats/aidl/android/hardware/power/stats/PowerEntity.aidl b/power/stats/aidl/android/hardware/power/stats/PowerEntity.aidl
new file mode 100644
index 0000000..6844a4c
--- /dev/null
+++ b/power/stats/aidl/android/hardware/power/stats/PowerEntity.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.power.stats;
+
+import android.hardware.power.stats.State;
+
+@VintfStability
+parcelable PowerEntity {
+ /**
+ * Unique ID of this PowerEntity
+ */
+ int id;
+ /**
+ * Unique name of this PowerEntity. Vendor/device specific. Opaque to framework
+ */
+ @utf8InCpp String name;
+ /**
+ * List of states that this PowerEntity may reside in
+ */
+ State[] states;
+}
\ No newline at end of file
diff --git a/health/1.0/default/libhealthd/healthd_board_default.cpp b/power/stats/aidl/android/hardware/power/stats/State.aidl
similarity index 60%
copy from health/1.0/default/libhealthd/healthd_board_default.cpp
copy to power/stats/aidl/android/hardware/power/stats/State.aidl
index 127f98e..33a9f70 100644
--- a/health/1.0/default/libhealthd/healthd_board_default.cpp
+++ b/power/stats/aidl/android/hardware/power/stats/State.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,18 @@
* limitations under the License.
*/
-#include <healthd/healthd.h>
+package android.hardware.power.stats;
-void healthd_board_init(struct healthd_config*)
-{
- // use defaults
+@VintfStability
+parcelable State {
+ /**
+ * Unique (for a given PowerEntity) ID of this State
+ */
+ int id;
+ /**
+ * Unique (for a given PowerEntity) name of the state. Vendor/device specific.
+ * Opaque to framework
+ */
+ @utf8InCpp String name;
}
-int healthd_board_battery_update(struct android::BatteryProperties*)
-{
- // return 0 to log periodic polled battery status to kernel log
- return 0;
-}
diff --git a/power/stats/aidl/android/hardware/power/stats/StateResidency.aidl b/power/stats/aidl/android/hardware/power/stats/StateResidency.aidl
new file mode 100644
index 0000000..4162517
--- /dev/null
+++ b/power/stats/aidl/android/hardware/power/stats/StateResidency.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.power.stats;
+
+/**
+ * Contains residency data for a single state
+ */
+@VintfStability
+parcelable StateResidency {
+ /**
+ * ID of the state associated with this residency
+ */
+ int id;
+ /**
+ * Total time in milliseconds that the corresponding PowerEntity resided
+ * in this state since boot
+ */
+ long totalTimeInStateMs;
+ /**
+ * Total number of times that the state was entered since boot
+ */
+ long totalStateEntryCount;
+ /**
+ * Last time this state was entered. Time in milliseconds since boot
+ */
+ long lastEntryTimestampMs;
+}
\ No newline at end of file
diff --git a/power/stats/aidl/android/hardware/power/stats/StateResidencyResult.aidl b/power/stats/aidl/android/hardware/power/stats/StateResidencyResult.aidl
new file mode 100644
index 0000000..949879c
--- /dev/null
+++ b/power/stats/aidl/android/hardware/power/stats/StateResidencyResult.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.power.stats;
+
+import android.hardware.power.stats.StateResidency;
+
+@VintfStability
+parcelable StateResidencyResult {
+ /**
+ * ID of the PowerEntity associated with this result
+ */
+ int id;
+ /**
+ * Residency for each state in the PowerEntity's state space
+ */
+ StateResidency[] stateResidencyData;
+}
+
diff --git a/power/stats/aidl/default/Android.bp b/power/stats/aidl/default/Android.bp
new file mode 100644
index 0000000..595ecd6
--- /dev/null
+++ b/power/stats/aidl/default/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_binary {
+ name: "android.hardware.power.stats-service.example",
+ relative_install_path: "hw",
+ init_rc: ["power.stats-default.rc"],
+ vintf_fragments: ["power.stats-default.xml"],
+ vendor: true,
+ shared_libs: [
+ "libbase",
+ "libbinder_ndk",
+ "android.hardware.power.stats-V1-ndk_platform",
+ ],
+ srcs: [
+ "main.cpp",
+ "PowerStats.cpp",
+ ],
+}
diff --git a/power/stats/aidl/default/PowerStats.cpp b/power/stats/aidl/default/PowerStats.cpp
new file mode 100644
index 0000000..0ffbd08
--- /dev/null
+++ b/power/stats/aidl/default/PowerStats.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PowerStats.h"
+
+#include <android-base/logging.h>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace power {
+namespace stats {
+
+ndk::ScopedAStatus PowerStats::getPowerEntityInfo(std::vector<PowerEntity>* _aidl_return) {
+ (void)_aidl_return;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus PowerStats::getStateResidency(const std::vector<int32_t>& in_powerEntityIds,
+ std::vector<StateResidencyResult>* _aidl_return) {
+ (void)in_powerEntityIds;
+ (void)_aidl_return;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus PowerStats::getEnergyConsumerInfo(std::vector<EnergyConsumer>* _aidl_return) {
+ (void)_aidl_return;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus PowerStats::getEnergyConsumed(const std::vector<int32_t>& in_energyConsumerIds,
+ std::vector<EnergyConsumerResult>* _aidl_return) {
+ (void)in_energyConsumerIds;
+ (void)_aidl_return;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus PowerStats::getEnergyMeterInfo(std::vector<Channel>* _aidl_return) {
+ (void)_aidl_return;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus PowerStats::readEnergyMeter(const std::vector<int32_t>& in_channelIds,
+ std::vector<EnergyMeasurement>* _aidl_return) {
+ (void)in_channelIds;
+ (void)_aidl_return;
+ return ndk::ScopedAStatus::ok();
+}
+
+} // namespace stats
+} // namespace power
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/power/stats/aidl/default/PowerStats.h b/power/stats/aidl/default/PowerStats.h
new file mode 100644
index 0000000..cb98e55
--- /dev/null
+++ b/power/stats/aidl/default/PowerStats.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/power/stats/BnPowerStats.h>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace power {
+namespace stats {
+
+class PowerStats : public BnPowerStats {
+ public:
+ PowerStats() = default;
+ // Methods from aidl::android::hardware::power::stats::IPowerStats
+ ndk::ScopedAStatus getPowerEntityInfo(std::vector<PowerEntity>* _aidl_return) override;
+ ndk::ScopedAStatus getStateResidency(const std::vector<int32_t>& in_powerEntityIds,
+ std::vector<StateResidencyResult>* _aidl_return) override;
+ ndk::ScopedAStatus getEnergyConsumerInfo(std::vector<EnergyConsumer>* _aidl_return) override;
+ ndk::ScopedAStatus getEnergyConsumed(const std::vector<int32_t>& in_energyConsumerIds,
+ std::vector<EnergyConsumerResult>* _aidl_return) override;
+ ndk::ScopedAStatus getEnergyMeterInfo(std::vector<Channel>* _aidl_return) override;
+ ndk::ScopedAStatus readEnergyMeter(const std::vector<int32_t>& in_channelIds,
+ std::vector<EnergyMeasurement>* _aidl_return) override;
+};
+
+} // namespace stats
+} // namespace power
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/power/stats/aidl/default/main.cpp b/power/stats/aidl/default/main.cpp
new file mode 100644
index 0000000..0469b4c
--- /dev/null
+++ b/power/stats/aidl/default/main.cpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PowerStats.h"
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+using aidl::android::hardware::power::stats::PowerStats;
+
+int main() {
+ ABinderProcess_setThreadPoolMaxThreadCount(0);
+ std::shared_ptr<PowerStats> p = ndk::SharedRefBase::make<PowerStats>();
+
+ const std::string instance = std::string() + PowerStats::descriptor + "/default";
+ binder_status_t status = AServiceManager_addService(p->asBinder().get(), instance.c_str());
+ CHECK(status == STATUS_OK);
+
+ ABinderProcess_joinThreadPool();
+ return EXIT_FAILURE; // should not reach
+}
diff --git a/power/stats/aidl/default/power.stats-default.rc b/power/stats/aidl/default/power.stats-default.rc
new file mode 100644
index 0000000..6ff6754
--- /dev/null
+++ b/power/stats/aidl/default/power.stats-default.rc
@@ -0,0 +1,4 @@
+service vendor.power.stats-default /vendor/bin/hw/android.hardware.power.stats-service.example
+ class hal
+ user system
+ group system
diff --git a/power/stats/aidl/default/power.stats-default.xml b/power/stats/aidl/default/power.stats-default.xml
new file mode 100644
index 0000000..3b1a216
--- /dev/null
+++ b/power/stats/aidl/default/power.stats-default.xml
@@ -0,0 +1,6 @@
+<manifest version="1.0" type="device">
+ <hal format="aidl">
+ <name>android.hardware.power.stats</name>
+ <fqname>IPowerStats/default</fqname>
+ </hal>
+</manifest>
diff --git a/power/stats/aidl/vts/Android.bp b/power/stats/aidl/vts/Android.bp
new file mode 100644
index 0000000..31fb990
--- /dev/null
+++ b/power/stats/aidl/vts/Android.bp
@@ -0,0 +1,32 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_test {
+ name: "VtsHalPowerStatsTargetTest",
+ defaults: [
+ "VtsHalTargetTestDefaults",
+ "use_libaidlvintf_gtest_helper_static",
+ ],
+ srcs: ["VtsHalPowerStatsTargetTest.cpp"],
+ shared_libs: [
+ "libbinder_ndk",
+ ],
+ static_libs: [
+ "android.hardware.power.stats-V1-ndk_platform",
+ ],
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
+}
diff --git a/power/stats/aidl/vts/VtsHalPowerStatsTargetTest.cpp b/power/stats/aidl/vts/VtsHalPowerStatsTargetTest.cpp
new file mode 100644
index 0000000..bed3fdf
--- /dev/null
+++ b/power/stats/aidl/vts/VtsHalPowerStatsTargetTest.cpp
@@ -0,0 +1,423 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+
+#include <aidl/android/hardware/power/stats/IPowerStats.h>
+#include <android-base/properties.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include <algorithm>
+#include <iterator>
+#include <random>
+#include <unordered_map>
+
+using aidl::android::hardware::power::stats::Channel;
+using aidl::android::hardware::power::stats::EnergyConsumer;
+using aidl::android::hardware::power::stats::EnergyConsumerAttribution;
+using aidl::android::hardware::power::stats::EnergyConsumerResult;
+using aidl::android::hardware::power::stats::EnergyConsumerType;
+using aidl::android::hardware::power::stats::EnergyMeasurement;
+using aidl::android::hardware::power::stats::IPowerStats;
+using aidl::android::hardware::power::stats::PowerEntity;
+using aidl::android::hardware::power::stats::State;
+using aidl::android::hardware::power::stats::StateResidency;
+using aidl::android::hardware::power::stats::StateResidencyResult;
+
+using ndk::SpAIBinder;
+
+class PowerStatsAidl : public testing::TestWithParam<std::string> {
+ public:
+ virtual void SetUp() override {
+ powerstats = IPowerStats::fromBinder(
+ SpAIBinder(AServiceManager_waitForService(GetParam().c_str())));
+ ASSERT_NE(nullptr, powerstats.get());
+ }
+
+ template <typename T>
+ std::vector<T> getRandomSubset(std::vector<T> const& collection);
+
+ void testNameValid(const std::string& name);
+
+ template <typename T, typename S>
+ void testUnique(std::vector<T> const& collection, S T::*field);
+
+ template <typename T, typename S, typename R>
+ void testMatching(std::vector<T> const& c1, R T::*f1, std::vector<S> const& c2, R S::*f2);
+
+ std::shared_ptr<IPowerStats> powerstats;
+};
+
+// Returns a random subset from a collection
+template <typename T>
+std::vector<T> PowerStatsAidl::getRandomSubset(std::vector<T> const& collection) {
+ if (collection.empty()) {
+ return {};
+ }
+
+ std::vector<T> selected;
+ std::sample(collection.begin(), collection.end(), std::back_inserter(selected),
+ rand() % collection.size() + 1, std::mt19937{std::random_device{}()});
+
+ return selected;
+}
+
+// Tests whether a name is valid
+void PowerStatsAidl::testNameValid(const std::string& name) {
+ EXPECT_NE(name, "");
+}
+
+// Tests whether the fields in a given collection are unique
+template <typename T, typename S>
+void PowerStatsAidl::testUnique(std::vector<T> const& collection, S T::*field) {
+ std::set<S> cSet;
+ for (auto const& elem : collection) {
+ EXPECT_TRUE(cSet.insert(elem.*field).second);
+ }
+}
+
+template <typename T, typename S, typename R>
+void PowerStatsAidl::testMatching(std::vector<T> const& c1, R T::*f1, std::vector<S> const& c2,
+ R S::*f2) {
+ std::set<R> c1fields, c2fields;
+ for (auto elem : c1) {
+ c1fields.insert(elem.*f1);
+ }
+
+ for (auto elem : c2) {
+ c2fields.insert(elem.*f2);
+ }
+
+ EXPECT_EQ(c1fields, c2fields);
+}
+
+// Each PowerEntity must have a valid name
+TEST_P(PowerStatsAidl, ValidatePowerEntityNames) {
+ std::vector<PowerEntity> infos;
+ ASSERT_TRUE(powerstats->getPowerEntityInfo(&infos).isOk());
+
+ for (auto info : infos) {
+ testNameValid(info.name);
+ }
+}
+
+// Each power entity must have a unique name
+TEST_P(PowerStatsAidl, ValidatePowerEntityUniqueNames) {
+ std::vector<PowerEntity> entities;
+ ASSERT_TRUE(powerstats->getPowerEntityInfo(&entities).isOk());
+
+ testUnique(entities, &PowerEntity::name);
+}
+
+// Each PowerEntity must have a unique ID
+TEST_P(PowerStatsAidl, ValidatePowerEntityIds) {
+ std::vector<PowerEntity> entities;
+ ASSERT_TRUE(powerstats->getPowerEntityInfo(&entities).isOk());
+
+ testUnique(entities, &PowerEntity::id);
+}
+
+// Each power entity must have at least one state
+TEST_P(PowerStatsAidl, ValidateStateSize) {
+ std::vector<PowerEntity> entities;
+ ASSERT_TRUE(powerstats->getPowerEntityInfo(&entities).isOk());
+
+ for (auto entity : entities) {
+ EXPECT_GT(entity.states.size(), 0);
+ }
+}
+
+// Each state must have a valid name
+TEST_P(PowerStatsAidl, ValidateStateNames) {
+ std::vector<PowerEntity> entities;
+ ASSERT_TRUE(powerstats->getPowerEntityInfo(&entities).isOk());
+
+ for (auto entity : entities) {
+ for (auto state : entity.states) {
+ testNameValid(state.name);
+ }
+ }
+}
+
+// Each state must have a name that is unique to the given PowerEntity
+TEST_P(PowerStatsAidl, ValidateStateUniqueNames) {
+ std::vector<PowerEntity> entities;
+ ASSERT_TRUE(powerstats->getPowerEntityInfo(&entities).isOk());
+
+ for (auto entity : entities) {
+ testUnique(entity.states, &State::name);
+ }
+}
+
+// Each state must have an ID that is unique to the given PowerEntity
+TEST_P(PowerStatsAidl, ValidateStateUniqueIds) {
+ std::vector<PowerEntity> entities;
+ ASSERT_TRUE(powerstats->getPowerEntityInfo(&entities).isOk());
+
+ for (auto entity : entities) {
+ testUnique(entity.states, &State::id);
+ }
+}
+
+// State residency must return a valid status
+TEST_P(PowerStatsAidl, TestGetStateResidency) {
+ std::vector<StateResidencyResult> results;
+ ASSERT_TRUE(powerstats->getStateResidency({}, &results).isOk());
+}
+
+// State residency must return all results
+TEST_P(PowerStatsAidl, TestGetStateResidencyAllResults) {
+ std::vector<PowerEntity> entities;
+ ASSERT_TRUE(powerstats->getPowerEntityInfo(&entities).isOk());
+
+ std::vector<StateResidencyResult> results;
+ ASSERT_TRUE(powerstats->getStateResidency({}, &results).isOk());
+
+ testMatching(entities, &PowerEntity::id, results, &StateResidencyResult::id);
+}
+
+// Each result must contain all state residencies
+TEST_P(PowerStatsAidl, TestGetStateResidencyAllStateResidencies) {
+ std::vector<PowerEntity> entities;
+ ASSERT_TRUE(powerstats->getPowerEntityInfo(&entities).isOk());
+
+ std::vector<StateResidencyResult> results;
+ ASSERT_TRUE(powerstats->getStateResidency({}, &results).isOk());
+
+ for (auto entity : entities) {
+ auto it = std::find_if(results.begin(), results.end(),
+ [&entity](const auto& x) { return x.id == entity.id; });
+ ASSERT_NE(it, results.end());
+
+ testMatching(entity.states, &State::id, it->stateResidencyData, &StateResidency::id);
+ }
+}
+
+// State residency must return results for each requested power entity
+TEST_P(PowerStatsAidl, TestGetStateResidencySelectedResults) {
+ std::vector<PowerEntity> entities;
+ ASSERT_TRUE(powerstats->getPowerEntityInfo(&entities).isOk());
+ if (entities.empty()) {
+ return;
+ }
+
+ std::vector<PowerEntity> selectedEntities = getRandomSubset(entities);
+ std::vector<int32_t> selectedIds;
+ for (auto const& entity : selectedEntities) {
+ selectedIds.push_back(entity.id);
+ }
+
+ std::vector<StateResidencyResult> selectedResults;
+ ASSERT_TRUE(powerstats->getStateResidency(selectedIds, &selectedResults).isOk());
+
+ testMatching(selectedEntities, &PowerEntity::id, selectedResults, &StateResidencyResult::id);
+}
+
+// Energy meter info must return a valid status
+TEST_P(PowerStatsAidl, TestGetEnergyMeterInfo) {
+ std::vector<Channel> info;
+ ASSERT_TRUE(powerstats->getEnergyMeterInfo(&info).isOk());
+}
+
+// Each channel must have a valid name and subsystem
+TEST_P(PowerStatsAidl, ValidateChannelNames) {
+ std::vector<Channel> channels;
+ ASSERT_TRUE(powerstats->getEnergyMeterInfo(&channels).isOk());
+ for (auto channel : channels) {
+ testNameValid(channel.name);
+ testNameValid(channel.subsystem);
+ }
+}
+
+// Each channel must have a unique name
+TEST_P(PowerStatsAidl, ValidateChannelUniqueNames) {
+ std::vector<Channel> channels;
+ ASSERT_TRUE(powerstats->getEnergyMeterInfo(&channels).isOk());
+
+ testUnique(channels, &Channel::name);
+}
+
+// Each channel must have a unique ID
+TEST_P(PowerStatsAidl, ValidateChannelUniqueIds) {
+ std::vector<Channel> channels;
+ ASSERT_TRUE(powerstats->getEnergyMeterInfo(&channels).isOk());
+
+ testUnique(channels, &Channel::id);
+}
+
+// Reading energy meter must return a valid status
+TEST_P(PowerStatsAidl, TestReadEnergyMeter) {
+ std::vector<EnergyMeasurement> data;
+ ASSERT_TRUE(powerstats->readEnergyMeter({}, &data).isOk());
+}
+
+// Reading energy meter must return results for all available channels
+TEST_P(PowerStatsAidl, TestGetAllEnergyMeasurements) {
+ std::vector<Channel> channels;
+ ASSERT_TRUE(powerstats->getEnergyMeterInfo(&channels).isOk());
+
+ std::vector<EnergyMeasurement> measurements;
+ ASSERT_TRUE(powerstats->readEnergyMeter({}, &measurements).isOk());
+
+ testMatching(channels, &Channel::id, measurements, &EnergyMeasurement::id);
+}
+
+// Reading energy must must return results for each selected channel
+TEST_P(PowerStatsAidl, TestGetSelectedEnergyMeasurements) {
+ std::vector<Channel> channels;
+ ASSERT_TRUE(powerstats->getEnergyMeterInfo(&channels).isOk());
+ if (channels.empty()) {
+ return;
+ }
+
+ std::vector<Channel> selectedChannels = getRandomSubset(channels);
+ std::vector<int32_t> selectedIds;
+ for (auto const& channel : selectedChannels) {
+ selectedIds.push_back(channel.id);
+ }
+
+ std::vector<EnergyMeasurement> selectedMeasurements;
+ ASSERT_TRUE(powerstats->readEnergyMeter(selectedIds, &selectedMeasurements).isOk());
+
+ testMatching(selectedChannels, &Channel::id, selectedMeasurements, &EnergyMeasurement::id);
+}
+
+// Energy consumer info must return a valid status
+TEST_P(PowerStatsAidl, TestGetEnergyConsumerInfo) {
+ std::vector<EnergyConsumer> consumers;
+ ASSERT_TRUE(powerstats->getEnergyConsumerInfo(&consumers).isOk());
+}
+
+// Each energy consumer must have a unique id
+TEST_P(PowerStatsAidl, TestGetEnergyConsumerUniqueId) {
+ std::vector<EnergyConsumer> consumers;
+ ASSERT_TRUE(powerstats->getEnergyConsumerInfo(&consumers).isOk());
+
+ testUnique(consumers, &EnergyConsumer::id);
+}
+
+// Each energy consumer must have a valid name
+TEST_P(PowerStatsAidl, ValidateEnergyConsumerNames) {
+ std::vector<EnergyConsumer> consumers;
+ ASSERT_TRUE(powerstats->getEnergyConsumerInfo(&consumers).isOk());
+
+ for (auto consumer : consumers) {
+ testNameValid(consumer.name);
+ }
+}
+
+// Each energy consumer must have a unique name
+TEST_P(PowerStatsAidl, ValidateEnergyConsumerUniqueNames) {
+ std::vector<EnergyConsumer> consumers;
+ ASSERT_TRUE(powerstats->getEnergyConsumerInfo(&consumers).isOk());
+
+ testUnique(consumers, &EnergyConsumer::name);
+}
+
+// Energy consumers of the same type must have ordinals that are 0,1,2,..., N - 1
+TEST_P(PowerStatsAidl, ValidateEnergyConsumerOrdinals) {
+ std::vector<EnergyConsumer> consumers;
+ ASSERT_TRUE(powerstats->getEnergyConsumerInfo(&consumers).isOk());
+
+ std::unordered_map<EnergyConsumerType, std::set<int32_t>> ordinalMap;
+
+ // Ordinals must be unique for each type
+ for (auto consumer : consumers) {
+ EXPECT_TRUE(ordinalMap[consumer.type].insert(consumer.ordinal).second);
+ }
+
+ // Min ordinal must be 0, max ordinal must be N - 1
+ for (const auto& [unused, ordinals] : ordinalMap) {
+ EXPECT_EQ(0, *std::min_element(ordinals.begin(), ordinals.end()));
+ EXPECT_EQ(ordinals.size() - 1, *std::max_element(ordinals.begin(), ordinals.end()));
+ }
+}
+
+// Energy consumed must return a valid status
+TEST_P(PowerStatsAidl, TestGetEnergyConsumed) {
+ std::vector<EnergyConsumerResult> results;
+ ASSERT_TRUE(powerstats->getEnergyConsumed({}, &results).isOk());
+}
+
+// Energy consumed must return data for all energy consumers
+TEST_P(PowerStatsAidl, TestGetAllEnergyConsumed) {
+ std::vector<EnergyConsumer> consumers;
+ ASSERT_TRUE(powerstats->getEnergyConsumerInfo(&consumers).isOk());
+
+ std::vector<EnergyConsumerResult> results;
+ ASSERT_TRUE(powerstats->getEnergyConsumed({}, &results).isOk());
+
+ testMatching(consumers, &EnergyConsumer::id, results, &EnergyConsumerResult::id);
+}
+
+// Energy consumed must return data for each selected energy consumer
+TEST_P(PowerStatsAidl, TestGetSelectedEnergyConsumed) {
+ std::vector<EnergyConsumer> consumers;
+ ASSERT_TRUE(powerstats->getEnergyConsumerInfo(&consumers).isOk());
+ if (consumers.empty()) {
+ return;
+ }
+
+ std::vector<EnergyConsumer> selectedConsumers = getRandomSubset(consumers);
+ std::vector<int32_t> selectedIds;
+ for (auto const& consumer : selectedConsumers) {
+ selectedIds.push_back(consumer.id);
+ }
+
+ std::vector<EnergyConsumerResult> selectedResults;
+ ASSERT_TRUE(powerstats->getEnergyConsumed(selectedIds, &selectedResults).isOk());
+
+ testMatching(selectedConsumers, &EnergyConsumer::id, selectedResults,
+ &EnergyConsumerResult::id);
+}
+
+// Energy consumed attribution uids must be unique for a given energy consumer
+TEST_P(PowerStatsAidl, ValidateEnergyConsumerAttributionUniqueUids) {
+ std::vector<EnergyConsumerResult> results;
+ ASSERT_TRUE(powerstats->getEnergyConsumed({}, &results).isOk());
+
+ for (auto result : results) {
+ testUnique(result.attribution, &EnergyConsumerAttribution::uid);
+ }
+}
+
+// Energy consumed total energy >= sum total of uid-attributed energy
+TEST_P(PowerStatsAidl, TestGetEnergyConsumedAttributedEnergy) {
+ std::vector<EnergyConsumerResult> results;
+ ASSERT_TRUE(powerstats->getEnergyConsumed({}, &results).isOk());
+
+ for (auto result : results) {
+ int64_t totalAttributedEnergyUWs = 0;
+ for (auto attribution : result.attribution) {
+ totalAttributedEnergyUWs += attribution.energyUWs;
+ }
+ EXPECT_TRUE(result.energyUWs >= totalAttributedEnergyUWs);
+ }
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(PowerStatsAidl);
+INSTANTIATE_TEST_SUITE_P(
+ PowerStats, PowerStatsAidl,
+ testing::ValuesIn(android::getAidlHalInstanceNames(IPowerStats::descriptor)),
+ android::PrintInstanceNameToString);
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ ABinderProcess_setThreadPoolMaxThreadCount(1);
+ ABinderProcess_startThreadPool();
+ return RUN_ALL_TESTS();
+}
diff --git a/radio/1.0/vts/functional/vts_test_util.h b/radio/1.0/vts/functional/vts_test_util.h
index 1625f11..218e823 100644
--- a/radio/1.0/vts/functional/vts_test_util.h
+++ b/radio/1.0/vts/functional/vts_test_util.h
@@ -35,6 +35,12 @@
static constexpr const char* FEATURE_VOICE_CALL = "android.software.connectionservice";
+static constexpr const char* FEATURE_TELEPHONY = "android.hardware.telephony";
+
+static constexpr const char* FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm";
+
+static constexpr const char* FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma";
+
/*
* Generate random serial number for radio test
*/
diff --git a/radio/1.4/vts/functional/radio_hidl_hal_api.cpp b/radio/1.4/vts/functional/radio_hidl_hal_api.cpp
index 1b254a1..b0b984c 100644
--- a/radio/1.4/vts/functional/radio_hidl_hal_api.cpp
+++ b/radio/1.4/vts/functional/radio_hidl_hal_api.cpp
@@ -34,6 +34,10 @@
if (!deviceSupportsFeature(FEATURE_VOICE_CALL)) {
ALOGI("Skipping emergencyDial because voice call is not supported in device");
return;
+ } else if (!deviceSupportsFeature(FEATURE_TELEPHONY_GSM) &&
+ !deviceSupportsFeature(FEATURE_TELEPHONY_CDMA)) {
+ ALOGI("Skipping emergencyDial because gsm/cdma radio is not supported in device");
+ return;
} else {
ALOGI("Running emergencyDial because voice call is supported in device");
}
@@ -86,6 +90,10 @@
if (!deviceSupportsFeature(FEATURE_VOICE_CALL)) {
ALOGI("Skipping emergencyDial because voice call is not supported in device");
return;
+ } else if (!deviceSupportsFeature(FEATURE_TELEPHONY_GSM) &&
+ !deviceSupportsFeature(FEATURE_TELEPHONY_CDMA)) {
+ ALOGI("Skipping emergencyDial because gsm/cdma radio is not supported in device");
+ return;
} else {
ALOGI("Running emergencyDial because voice call is supported in device");
}
@@ -138,6 +146,10 @@
if (!deviceSupportsFeature(FEATURE_VOICE_CALL)) {
ALOGI("Skipping emergencyDial because voice call is not supported in device");
return;
+ } else if (!deviceSupportsFeature(FEATURE_TELEPHONY_GSM) &&
+ !deviceSupportsFeature(FEATURE_TELEPHONY_CDMA)) {
+ ALOGI("Skipping emergencyDial because gsm/cdma radio is not supported in device");
+ return;
} else {
ALOGI("Running emergencyDial because voice call is supported in device");
}
diff --git a/radio/1.5/vts/functional/radio_hidl_hal_api.cpp b/radio/1.5/vts/functional/radio_hidl_hal_api.cpp
index 7166654..0b49b36 100644
--- a/radio/1.5/vts/functional/radio_hidl_hal_api.cpp
+++ b/radio/1.5/vts/functional/radio_hidl_hal_api.cpp
@@ -236,7 +236,12 @@
ALOGI("setSignalStrengthReportingCriteria_1_5_NGRAN_SSRSRP, rspInfo.error = %s\n",
toString(radioRsp_v1_5->rspInfo.error).c_str());
- ASSERT_TRUE(CheckAnyOfErrors(radioRsp_v1_5->rspInfo.error, {RadioError::NONE}));
+
+ // Allow REQUEST_NOT_SUPPORTED because some non-5G device may not support NGRAN for
+ // setSignalStrengthReportingCriteria_1_5()
+ ASSERT_TRUE(
+ CheckAnyOfErrors(radioRsp_v1_5->rspInfo.error,
+ {RadioError::NONE, RadioError::REQUEST_NOT_SUPPORTED}));
}
/*
@@ -261,7 +266,12 @@
ALOGI("setSignalStrengthReportingCriteria_1_5_NGRAN_SSRSRQ, rspInfo.error = %s\n",
toString(radioRsp_v1_5->rspInfo.error).c_str());
- ASSERT_TRUE(CheckAnyOfErrors(radioRsp_v1_5->rspInfo.error, {RadioError::NONE}));
+
+ // Allow REQUEST_NOT_SUPPORTED because some non-5G device may not support NGRAN for
+ // setSignalStrengthReportingCriteria_1_5()
+ ASSERT_TRUE(
+ CheckAnyOfErrors(radioRsp_v1_5->rspInfo.error,
+ {RadioError::NONE, RadioError::REQUEST_NOT_SUPPORTED}));
}
/*
@@ -307,7 +317,12 @@
ALOGI("setSignalStrengthReportingCriteria_1_5_NGRAN_SSSINR, rspInfo.error = %s\n",
toString(radioRsp_v1_5->rspInfo.error).c_str());
- ASSERT_TRUE(CheckAnyOfErrors(radioRsp_v1_5->rspInfo.error, {RadioError::NONE}));
+
+ // Allow REQUEST_NOT_SUPPORTED because some non-5G device may not support NGRAN for
+ // setSignalStrengthReportingCriteria_1_5()
+ ASSERT_TRUE(
+ CheckAnyOfErrors(radioRsp_v1_5->rspInfo.error,
+ {RadioError::NONE, RadioError::REQUEST_NOT_SUPPORTED}));
}
/*
diff --git a/radio/1.6/IRadio.hal b/radio/1.6/IRadio.hal
index 3dc80b9..c83de29 100644
--- a/radio/1.6/IRadio.hal
+++ b/radio/1.6/IRadio.hal
@@ -18,9 +18,12 @@
import @1.0::CdmaSmsMessage;
+import @1.0::Dial;
import @1.0::GsmSmsMessage;
import @1.1::CardPowerState;
import @1.2::DataRequestReason;
+import @1.4::EmergencyCallRouting;
+import @1.4::EmergencyServiceCategory;
import @1.4::RadioAccessFamily;
import @1.5::IRadio;
import @1.5::AccessNetwork;
@@ -117,6 +120,9 @@
* @param pduSessionId The pdu session id to be used for this data call. A value of 0 means
* no pdu session id was attached to this call.
* Reference: 3GPP TS 24.007 section 11.2.3.1b
+ * @param sliceInfo SliceInfo to be used for the data connection when a handover occurs from
+ * EPDG to 5G. It is valid only when accessNetwork is AccessNetwork:NGRAN. If the slice
+ * passed from EPDG is rejected, then the data failure cause must be DataCallFailCause:SLICE_REJECTED.
*
* Response function is IRadioResponse.setupDataCallResponse_1_6()
*
@@ -125,7 +131,7 @@
oneway setupDataCall_1_6(int32_t serial, AccessNetwork accessNetwork,
DataProfileInfo dataProfileInfo, bool roamingAllowed,
DataRequestReason reason, vec<LinkAddress> addresses, vec<string> dnses,
- int32_t pduSessionId);
+ int32_t pduSessionId, OptionalSliceInfo sliceInfo);
/**
* Send an SMS message
@@ -324,6 +330,9 @@
* Instruct the radio to *only* accept the types of network provided. This
* is stronger than setPreferredNetworkType which is a suggestion.
*
+ * In case of an emergency call, the modem is authorized to bypass this
+ * restriction.
+ *
* @param serial Serial number of request.
* @param networkTypeBitmap a 32-bit bearer bitmap of RadioAccessFamily
*
@@ -372,6 +381,64 @@
int64_t completionDurationMillis);
/**
+ * Initiate emergency voice call, with zero or more emergency service category(s), zero or
+ * more emergency Uniform Resource Names (URN), and routing information for handling the call.
+ * Android uses this request to make its emergency call instead of using @1.0::IRadio.dial
+ * if the 'address' in the 'dialInfo' field is identified as an emergency number by Android.
+ *
+ * In multi-sim scenario, if the emergency number is from a specific subscription, this radio
+ * request can still be sent out on the other subscription as long as routing is set to
+ * @1.4::EmergencyNumberRouting#EMERGENCY. This radio request will not be sent on an inactive
+ * (PIN/PUK locked) subscription unless both subscriptions are PIN/PUK locked. In this case,
+ * the request will be sent on the primary subscription.
+ *
+ * Some countries or carriers require some emergency numbers that must be handled with normal
+ * call routing if possible or emergency routing. 1) if the 'routing' field is specified as
+ * @1.4::EmergencyNumberRouting#NORMAL, the implementation must try the full radio service to
+ * use normal call routing to handle the call; if service cannot support normal routing, the
+ * implementation must use emergency routing to handle the call. 2) if 'routing' is specified
+ * as @1.4::EmergencyNumberRouting#EMERGENCY, the implementation must use emergency routing to
+ * handle the call. 3) if 'routing' is specified as @1.4::EmergencyNumberRouting#UNKNOWN,
+ * Android does not know how to handle the call.
+ *
+ * If the dialed emergency number does not have a specified emergency service category, the
+ * 'categories' field is set to @1.4::EmergencyServiceCategory#UNSPECIFIED; if the dialed
+ * emergency number does not have specified emergency Uniform Resource Names, the 'urns' field
+ * is set to an empty list. If the underlying technology used to request emergency services
+ * does not support the emergency service category or emergency uniform resource names, the
+ * field 'categories' or 'urns' may be ignored.
+ *
+ * In the scenarios that the 'address' in the 'dialInfo' field has other functions besides the
+ * emergency number function, if the 'hasKnownUserIntentEmergency' field is true, the user's
+ * intent for this dial request is emergency call, and the modem must treat this as an actual
+ * emergency dial; if the 'hasKnownUserIntentEmergency' field is false, Android does not know
+ * user's intent for this call.
+ *
+ * If 'isTesting' is true, this request is for testing purpose, and must not be sent to a real
+ * emergency service; otherwise it's for a real emergency call request.
+ *
+ * Reference: 3gpp 22.101, Section 10 - Emergency Calls;
+ * 3gpp 23.167, Section 6 - Functional description;
+ * 3gpp 24.503, Section 5.1.6.8.1 - General;
+ * RFC 5031
+ *
+ * @param serial Serial number of request.
+ * @param dialInfo the same @1.0::Dial information used by @1.0::IRadio.dial.
+ * @param categories bitfield<@1.4::EmergencyServiceCategory> the Emergency Service Category(s)
+ * of the call.
+ * @param urns the emergency Uniform Resource Names (URN)
+ * @param routing @1.4::EmergencyCallRouting the emergency call routing information.
+ * @param hasKnownUserIntentEmergency Flag indicating if user's intent for the emergency call
+ * is known.
+ * @param isTesting Flag indicating if this request is for testing purpose.
+ *
+ * Response function is IRadioResponse.emergencyDialResponse()
+ */
+ oneway emergencyDial_1_6(int32_t serial, Dial dialInfo,
+ bitfield<EmergencyServiceCategory> categories, vec<string> urns,
+ EmergencyCallRouting routing, bool hasKnownUserIntentEmergency, bool isTesting);
+
+ /**
* Get which bands the modem's background scan is acting on.
*
* @param serial Serial number of request.
@@ -386,7 +453,7 @@
* cell information isn't known then the appropriate unknown value will be returned.
* This does not cause or change the rate of unsolicited cellInfoList().
*
- * This is identitcal to getCellInfoList in V1.0, but it requests updated version of CellInfo.
+ * This is identical to getCellInfoList in V1.0, but it requests updated version of CellInfo.
*
* @param serial Serial number of request.
*
diff --git a/radio/1.6/IRadioIndication.hal b/radio/1.6/IRadioIndication.hal
index 1b56d40..a53d7c1 100644
--- a/radio/1.6/IRadioIndication.hal
+++ b/radio/1.6/IRadioIndication.hal
@@ -23,6 +23,7 @@
import @1.6::NetworkScanResult;
import @1.6::SignalStrength;
import @1.6::SetupDataCallResult;
+import @1.6::PhysicalChannelConfig;
/**
* Interface declaring unsolicited radio indications.
@@ -101,4 +102,15 @@
* CellInfo.
*/
oneway networkScanResult_1_6(RadioIndicationType type, NetworkScanResult result);
+
+ /**
+ * Indicates physical channel configurations.
+ *
+ * An empty configs list indicates that the radio is in idle mode.
+ *
+ * @param type Type of radio indication
+ * @param configs Vector of PhysicalChannelConfigs
+ */
+ oneway currentPhysicalChannelConfigs_1_6(RadioIndicationType type,
+ vec<PhysicalChannelConfig> configs);
};
diff --git a/radio/1.6/IRadioResponse.hal b/radio/1.6/IRadioResponse.hal
index 6ac86c3..a1ad207 100644
--- a/radio/1.6/IRadioResponse.hal
+++ b/radio/1.6/IRadioResponse.hal
@@ -19,6 +19,7 @@
import @1.0::SendSmsResult;
import @1.4::RadioAccessFamily;
import @1.5::IRadioResponse;
+import @1.5::RadioAccessSpecifier;
import @1.6::Call;
import @1.6::CellInfo;
import @1.6::RegStateResult;
@@ -341,6 +342,7 @@
/**
* @param info Response info struct containing response type, serial no. and error
+ * @param specifiers List of RadioAccessSpecifiers that are scanned.
*
* Valid errors returned:
* RadioError:NONE
@@ -348,7 +350,8 @@
* RadioError:INTERNAL_ERR
* RadioError:INVALID_ARGUMENTS
*/
- oneway getSystemSelectionChannelsResponse(RadioResponseInfo info);
+ oneway getSystemSelectionChannelsResponse(
+ RadioResponseInfo info, vec<RadioAccessSpecifier> specifiers);
/**
* This is identical to getCellInfoListResponse_1_5 but uses an updated version of CellInfo.
diff --git a/radio/1.6/types.hal b/radio/1.6/types.hal
index f4dc0bd..8560b3d 100644
--- a/radio/1.6/types.hal
+++ b/radio/1.6/types.hal
@@ -23,7 +23,10 @@
import @1.0::RadioError;
import @1.0::RadioResponseType;
import @1.0::RegState;
+import @1.1::EutranBands;
+import @1.1::GeranBands;
import @1.1::ScanStatus;
+import @1.1::UtranBands;
import @1.2::Call;
import @1.2::CellInfoCdma;
import @1.2::CellConnectionStatus;
@@ -41,6 +44,7 @@
import @1.5::CellInfoWcdma;
import @1.5::CellInfoTdscdma;
import @1.5::LinkAddress;
+import @1.5::NgranBands;
import @1.5::RegStateResult.AccessTechnologySpecificInfo.Cdma2000RegistrationInfo;
import @1.5::RegStateResult.AccessTechnologySpecificInfo.EutranRegistrationInfo;
import @1.5::RegistrationFailCause;
@@ -281,7 +285,7 @@
* suggestion. 0 indicates retry should be performed immediately. 0x7fffffffffffffff indicates
* the device should not retry data setup anymore.
*/
- uint64_t suggestedRetryTime;
+ int64_t suggestedRetryTime;
/** Context ID, uniquely identifies this data connection. */
int32_t cid;
@@ -347,11 +351,17 @@
/**
* The allocated pdu session id for this data call.
- * A value of -1 means no pdu session id was attached to this call.
+ * A value of 0 means no pdu session id was attached to this call.
*
* Reference: 3GPP TS 24.007 section 11.2.3.1b
*/
int32_t pduSessionId;
+
+ /**
+ * Slice used for this data call. It is valid only when this data call is on
+ * AccessNetwork:NGRAN.
+ */
+ OptionalSliceInfo sliceInfo;
};
/**
@@ -722,6 +732,14 @@
*/
NrVopsInfo nrVopsInfo;
} ngranInfo;
+
+ struct GeranRegistrationInfo {
+ /**
+ * True if the dual transfer mode is supported.
+ * Refer to 3GPP TS 44.108 section 3.4.25.3
+ */
+ bool dtmSupported;
+ } geranInfo;
} accessTechnologySpecificInfo;
};
@@ -733,3 +751,147 @@
*/
string forwardedNumber;
};
+
+struct PhysicalChannelConfig {
+ /** Connection status for cell. Valid values are PRIMARY_SERVING and SECONDARY_SERVING */
+ CellConnectionStatus status;
+
+ /** The radio technology for this physical channel */
+ RadioTechnology rat;
+
+ /** Downlink Absolute Radio Frequency Channel Number */
+ int32_t downlinkChannelNumber;
+
+ /** Uplink Absolute Radio Frequency Channel Number */
+ int32_t uplinkChannelNumber;
+
+ /** Downlink cell bandwidth, in kHz */
+ int32_t cellBandwidthDownlink;
+
+ /** Uplink cell bandwidth, in kHz */
+ int32_t cellBandwidthUplink;
+
+ /**
+ * A list of data calls mapped to this physical channel. The context id must match the cid of
+ * @1.5::SetupDataCallResult. An empty list means the physical channel has no data call mapped
+ * to it.
+ */
+ vec<int32_t> contextIds;
+
+ /**
+ * The physical cell identifier for this cell.
+ *
+ * In UTRAN, this value is primary scrambling code. The range is [0, 511].
+ * Reference: 3GPP TS 25.213 section 5.2.2.
+ *
+ * In EUTRAN, this value is physical layer cell identity. The range is [0, 503].
+ * Reference: 3GPP TS 36.211 section 6.11.
+ *
+ * In 5G RAN, this value is physical layer cell identity. The range is [0, 1007].
+ * Reference: 3GPP TS 38.211 section 7.4.2.1.
+ */
+ uint32_t physicalCellId;
+
+ /**
+ * The frequency band to scan.
+ */
+ safe_union Band {
+ /** Valid only if radioAccessNetwork = GERAN. */
+ GeranBands geranBand;
+ /** Valid only if radioAccessNetwork = UTRAN. */
+ UtranBands utranBand;
+ /** Valid only if radioAccessNetwork = EUTRAN. */
+ EutranBands eutranBand;
+ /** Valid only if radioAccessNetwork = NGRAN. */
+ NgranBands ngranBand;
+ } band;
+};
+
+/**
+ * Extended from @1.5 NgranBands
+ * IRadio 1.6 supports NGRAN bands up to V16.5.0
+ */
+enum NgranBands : @1.5::NgranBands {
+ /** 3GPP TS 38.101-1, Table 5.2-1: FR1 bands */
+ BAND_26 = 26,
+ BAND_46 = 46,
+ BAND_53 = 53,
+ BAND_96 = 96,
+};
+
+/**
+ * This safe_union represents an optional slice info
+ */
+safe_union OptionalSliceInfo {
+ Monostate noinit;
+ SliceInfo value;
+};
+
+/**
+ * This struct represents a S-NSSAI as defined in 3GPP TS 24.501.
+ */
+struct SliceInfo {
+ /**
+ * The type of service provided by the slice.
+ *
+ * see: 3GPP TS 24.501 Section 9.11.2.8.
+ */
+ SliceServiceType sst;
+
+ /**
+ * Slice differentiator is the identifier of a slice that has
+ * SliceServiceType as SST. A value of -1 indicates that there is
+ * no corresponding SliceInfo of the HPLMN.
+ *
+ * see: 3GPP TS 24.501 Section 9.11.2.8.
+ */
+ int32_t sliceDifferentiator;
+
+ /**
+ * This SST corresponds to a SliceInfo (S-NSSAI) of the HPLMN; the SST is
+ * mapped to this value.
+ *
+ * see: 3GPP TS 24.501 Section 9.11.2.8.
+ */
+ SliceServiceType mappedHplmnSst;
+
+ /**
+ * Present only if both sliceDifferentiator and mappedHplmnSst are also
+ * present. This SD corresponds to a SliceInfo (S-NSSAI) of the HPLMN;
+ * sliceDifferentiator is mapped to this value. A value of -1 indicates that
+ * there is no corresponding SliceInfo of the HPLMN.
+ *
+ * see: 3GPP TS 24.501 Section 9.11.2.8.
+ */
+ int32_t mappedHplmnSD;
+};
+
+/**
+ * Slice/Service Type as defined in 3GPP TS 23.501.
+ */
+enum SliceServiceType : uint8_t {
+ /* Not specified */
+ NONE = 0,
+
+ /* Slice suitable for the handling of 5G enhanced Mobile Broadband */
+ EMBB = 1,
+
+ /**
+ * Slice suitable for the handling of ultra-reliable low latency
+ * communications
+ */
+ URLLC = 2,
+
+ /* Slice suitable for the handling of massive IoT */
+ MIOT = 3,
+};
+
+/**
+ * Expose more setup data call failures.
+ */
+enum DataCallFailCause : @1.4::DataCallFailCause {
+ /**
+ * Data call fail due to the slice not being allowed for the data call.
+ */
+ SLICE_REJECTED = 0x8CC,
+};
diff --git a/radio/1.6/vts/functional/radio_hidl_hal_api.cpp b/radio/1.6/vts/functional/radio_hidl_hal_api.cpp
index 47babed..44900b8 100644
--- a/radio/1.6/vts/functional/radio_hidl_hal_api.cpp
+++ b/radio/1.6/vts/functional/radio_hidl_hal_api.cpp
@@ -56,8 +56,12 @@
::android::hardware::radio::V1_2::DataRequestReason reason =
::android::hardware::radio::V1_2::DataRequestReason::NORMAL;
- Return<void> res = radio_v1_6->setupDataCall_1_6(serial, accessNetwork, dataProfileInfo,
- roamingAllowed, reason, addresses, dnses, -1);
+ ::android::hardware::radio::V1_6::OptionalSliceInfo optionalSliceInfo;
+ memset(&optionalSliceInfo, 0, sizeof(optionalSliceInfo));
+
+ Return<void> res =
+ radio_v1_6->setupDataCall_1_6(serial, accessNetwork, dataProfileInfo, roamingAllowed,
+ reason, addresses, dnses, -1, optionalSliceInfo);
ASSERT_OK(res);
EXPECT_EQ(std::cv_status::no_timeout, wait());
@@ -408,6 +412,167 @@
}
/*
+ * Test IRadio.emergencyDial() for the response returned.
+ */
+TEST_P(RadioHidlTest_v1_6, emergencyDial_1_6) {
+ if (!deviceSupportsFeature(FEATURE_VOICE_CALL)) {
+ ALOGI("Skipping emergencyDial because voice call is not supported in device");
+ return;
+ } else {
+ ALOGI("Running emergencyDial because voice call is supported in device");
+ }
+
+ serial = GetRandomSerialNumber();
+
+ ::android::hardware::radio::V1_0::Dial dialInfo;
+ dialInfo.address = hidl_string("911");
+ int categories = static_cast<int>(
+ ::android::hardware::radio::V1_4::EmergencyServiceCategory::UNSPECIFIED);
+ std::vector<hidl_string> urns = {""};
+ ::android::hardware::radio::V1_4::EmergencyCallRouting routing =
+ ::android::hardware::radio::V1_4::EmergencyCallRouting::UNKNOWN;
+
+ Return<void> res =
+ radio_v1_6->emergencyDial_1_6(serial, dialInfo, categories, urns, routing, true, true);
+ ASSERT_OK(res);
+ EXPECT_EQ(std::cv_status::no_timeout, wait());
+ EXPECT_EQ(RadioResponseType::SOLICITED, radioRsp_v1_6->rspInfo_v1_0.type);
+ EXPECT_EQ(serial, radioRsp_v1_6->rspInfo_v1_0.serial);
+
+ ALOGI("emergencyDial, rspInfo_v1_0.error = %s\n",
+ toString(radioRsp_v1_6->rspInfo_v1_0.error).c_str());
+
+ ::android::hardware::radio::V1_0::RadioError rspEmergencyDial =
+ radioRsp_v1_6->rspInfo_v1_0.error;
+ // In DSDS or TSTS, we only check the result if the current slot is IN_SERVICE
+ // or Emergency_Only.
+ if (isDsDsEnabled() || isTsTsEnabled()) {
+ serial = GetRandomSerialNumber();
+ radio_v1_6->getVoiceRegistrationState(serial);
+ EXPECT_EQ(std::cv_status::no_timeout, wait());
+ if (isVoiceEmergencyOnly(radioRsp_v1_6->voiceRegResp.regState) ||
+ isVoiceInService(radioRsp_v1_6->voiceRegResp.regState)) {
+ EXPECT_EQ(::android::hardware::radio::V1_0::RadioError::NONE, rspEmergencyDial);
+ }
+ } else {
+ EXPECT_EQ(::android::hardware::radio::V1_0::RadioError::NONE, rspEmergencyDial);
+ }
+
+ // Give some time for modem to establish the emergency call channel.
+ sleep(MODEM_EMERGENCY_CALL_ESTABLISH_TIME);
+
+ // Disconnect all the potential established calls to prevent them affecting other tests.
+ clearPotentialEstablishedCalls();
+}
+
+/*
+ * Test IRadio.emergencyDial() with specified service and its response returned.
+ */
+TEST_P(RadioHidlTest_v1_6, emergencyDial_1_6_withServices) {
+ if (!deviceSupportsFeature(FEATURE_VOICE_CALL)) {
+ ALOGI("Skipping emergencyDial because voice call is not supported in device");
+ return;
+ } else {
+ ALOGI("Running emergencyDial because voice call is supported in device");
+ }
+
+ serial = GetRandomSerialNumber();
+
+ ::android::hardware::radio::V1_0::Dial dialInfo;
+ dialInfo.address = hidl_string("911");
+ int categories =
+ static_cast<int>(::android::hardware::radio::V1_4::EmergencyServiceCategory::AMBULANCE);
+ std::vector<hidl_string> urns = {"urn:service:sos.ambulance"};
+ ::android::hardware::radio::V1_4::EmergencyCallRouting routing =
+ ::android::hardware::radio::V1_4::EmergencyCallRouting::UNKNOWN;
+
+ Return<void> res =
+ radio_v1_6->emergencyDial_1_6(serial, dialInfo, categories, urns, routing, true, true);
+ ASSERT_OK(res);
+ EXPECT_EQ(std::cv_status::no_timeout, wait());
+ EXPECT_EQ(RadioResponseType::SOLICITED, radioRsp_v1_6->rspInfo_v1_0.type);
+ EXPECT_EQ(serial, radioRsp_v1_6->rspInfo_v1_0.serial);
+
+ ALOGI("emergencyDial_withServices, rspInfo_v1_0.error = %s\n",
+ toString(radioRsp_v1_6->rspInfo_v1_0.error).c_str());
+ ::android::hardware::radio::V1_0::RadioError rspEmergencyDial =
+ radioRsp_v1_6->rspInfo_v1_0.error;
+
+ // In DSDS or TSTS, we only check the result if the current slot is IN_SERVICE
+ // or Emergency_Only.
+ if (isDsDsEnabled() || isTsTsEnabled()) {
+ serial = GetRandomSerialNumber();
+ radio_v1_6->getVoiceRegistrationState(serial);
+ EXPECT_EQ(std::cv_status::no_timeout, wait());
+ if (isVoiceEmergencyOnly(radioRsp_v1_6->voiceRegResp.regState) ||
+ isVoiceInService(radioRsp_v1_6->voiceRegResp.regState)) {
+ EXPECT_EQ(::android::hardware::radio::V1_0::RadioError::NONE, rspEmergencyDial);
+ }
+ } else {
+ EXPECT_EQ(::android::hardware::radio::V1_0::RadioError::NONE, rspEmergencyDial);
+ }
+ // Give some time for modem to establish the emergency call channel.
+ sleep(MODEM_EMERGENCY_CALL_ESTABLISH_TIME);
+
+ // Disconnect all the potential established calls to prevent them affecting other tests.
+ clearPotentialEstablishedCalls();
+}
+
+/*
+ * Test IRadio.emergencyDial() with known emergency call routing and its response returned.
+ */
+TEST_P(RadioHidlTest_v1_6, emergencyDial_1_6_withEmergencyRouting) {
+ if (!deviceSupportsFeature(FEATURE_VOICE_CALL)) {
+ ALOGI("Skipping emergencyDial because voice call is not supported in device");
+ return;
+ } else {
+ ALOGI("Running emergencyDial because voice call is supported in device");
+ }
+
+ serial = GetRandomSerialNumber();
+
+ ::android::hardware::radio::V1_0::Dial dialInfo;
+ dialInfo.address = hidl_string("911");
+ int categories = static_cast<int>(
+ ::android::hardware::radio::V1_4::EmergencyServiceCategory::UNSPECIFIED);
+ std::vector<hidl_string> urns = {""};
+ ::android::hardware::radio::V1_4::EmergencyCallRouting routing =
+ ::android::hardware::radio::V1_4::EmergencyCallRouting::EMERGENCY;
+
+ Return<void> res =
+ radio_v1_6->emergencyDial_1_6(serial, dialInfo, categories, urns, routing, true, true);
+ ASSERT_OK(res);
+ EXPECT_EQ(std::cv_status::no_timeout, wait());
+ EXPECT_EQ(RadioResponseType::SOLICITED, radioRsp_v1_6->rspInfo_v1_0.type);
+ EXPECT_EQ(serial, radioRsp_v1_6->rspInfo_v1_0.serial);
+
+ ALOGI("emergencyDial_withEmergencyRouting, rspInfo_v1_0.error = %s\n",
+ toString(radioRsp_v1_6->rspInfo_v1_0.error).c_str());
+ ::android::hardware::radio::V1_0::RadioError rspEmergencyDial =
+ radioRsp_v1_6->rspInfo_v1_0.error;
+
+ // In DSDS or TSTS, we only check the result if the current slot is IN_SERVICE
+ // or Emergency_Only.
+ if (isDsDsEnabled() || isTsTsEnabled()) {
+ serial = GetRandomSerialNumber();
+ radio_v1_6->getVoiceRegistrationState(serial);
+ EXPECT_EQ(std::cv_status::no_timeout, wait());
+ if (isVoiceEmergencyOnly(radioRsp_v1_6->voiceRegResp.regState) ||
+ isVoiceInService(radioRsp_v1_6->voiceRegResp.regState)) {
+ EXPECT_EQ(::android::hardware::radio::V1_0::RadioError::NONE, rspEmergencyDial);
+ }
+ } else {
+ EXPECT_EQ(::android::hardware::radio::V1_0::RadioError::NONE, rspEmergencyDial);
+ }
+
+ // Give some time for modem to establish the emergency call channel.
+ sleep(MODEM_EMERGENCY_CALL_ESTABLISH_TIME);
+
+ // Disconnect all the potential established calls to prevent them affecting other tests.
+ clearPotentialEstablishedCalls();
+}
+
+/*
* Test IRadio.getCurrentCalls_1_6() for the response returned.
*/
TEST_P(RadioHidlTest_v1_6, getCurrentCalls_1_6) {
diff --git a/radio/1.6/vts/functional/radio_hidl_hal_test.cpp b/radio/1.6/vts/functional/radio_hidl_hal_test.cpp
index 79c3cde..59f7682 100644
--- a/radio/1.6/vts/functional/radio_hidl_hal_test.cpp
+++ b/radio/1.6/vts/functional/radio_hidl_hal_test.cpp
@@ -74,6 +74,29 @@
return status;
}
+void RadioHidlTest_v1_6::clearPotentialEstablishedCalls() {
+ // Get the current call Id to hangup the established emergency call.
+ serial = GetRandomSerialNumber();
+ radio_v1_6->getCurrentCalls_1_6(serial);
+ EXPECT_EQ(std::cv_status::no_timeout, wait());
+
+ // Hang up to disconnect the established call channels.
+ for (const ::android::hardware::radio::V1_6::Call& call : radioRsp_v1_6->currentCalls) {
+ serial = GetRandomSerialNumber();
+ radio_v1_6->hangup(serial, call.base.base.index);
+ ALOGI("Hang up to disconnect the established call channel: %d", call.base.base.index);
+ EXPECT_EQ(std::cv_status::no_timeout, wait());
+ // Give some time for modem to disconnect the established call channel.
+ sleep(MODEM_EMERGENCY_CALL_DISCONNECT_TIME);
+ }
+
+ // Verify there are no more current calls.
+ serial = GetRandomSerialNumber();
+ radio_v1_6->getCurrentCalls_1_6(serial);
+ EXPECT_EQ(std::cv_status::no_timeout, wait());
+ EXPECT_EQ(0, radioRsp_v1_6->currentCalls.size());
+}
+
void RadioHidlTest_v1_6::updateSimCardStatus() {
serial = GetRandomSerialNumber();
radio_v1_6->getIccCardStatus(serial);
diff --git a/radio/1.6/vts/functional/radio_hidl_hal_utils_v1_6.h b/radio/1.6/vts/functional/radio_hidl_hal_utils_v1_6.h
index fbcd7a9..b94cd96 100644
--- a/radio/1.6/vts/functional/radio_hidl_hal_utils_v1_6.h
+++ b/radio/1.6/vts/functional/radio_hidl_hal_utils_v1_6.h
@@ -70,7 +70,8 @@
::android::hardware::radio::V1_6::RadioResponseInfo rspInfo;
// Call
- hidl_vec<::android::hardware::radio::V1_2::Call> currentCalls;
+ hidl_vec<::android::hardware::radio::V1_6::Call> currentCalls;
+ ::android::hardware::radio::V1_2::VoiceRegStateResult voiceRegResp;
// Sms
SendSmsResult sendSmsResult;
@@ -803,7 +804,8 @@
const ::android::hardware::radio::V1_6::RadioResponseInfo& info);
Return<void> getSystemSelectionChannelsResponse(
- const ::android::hardware::radio::V1_6::RadioResponseInfo& info);
+ const ::android::hardware::radio::V1_6::RadioResponseInfo& info,
+ const hidl_vec<::android::hardware::radio::V1_5::RadioAccessSpecifier>& specifier);
Return<void> getSignalStrengthResponse_1_6(
const ::android::hardware::radio::V1_6::RadioResponseInfo& info,
@@ -857,6 +859,11 @@
const ::android::hardware::hidl_vec<::android::hardware::radio::V1_6::CellInfo>&
records);
+ Return<void> currentPhysicalChannelConfigs_1_6(
+ RadioIndicationType type,
+ const ::android::hardware::hidl_vec<
+ ::android::hardware::radio::V1_6::PhysicalChannelConfig>& configs);
+
/* 1.5 Api */
Return<void> uiccApplicationsEnablementChanged(RadioIndicationType type, bool enabled);
diff --git a/radio/1.6/vts/functional/radio_indication.cpp b/radio/1.6/vts/functional/radio_indication.cpp
index bfc54c0..e7a9680 100644
--- a/radio/1.6/vts/functional/radio_indication.cpp
+++ b/radio/1.6/vts/functional/radio_indication.cpp
@@ -30,6 +30,13 @@
return Void();
}
+Return<void> RadioIndication_v1_6::currentPhysicalChannelConfigs_1_6(
+ RadioIndicationType /*type*/,
+ const ::android::hardware::hidl_vec<
+ ::android::hardware::radio::V1_6::PhysicalChannelConfig>& /*configs*/) {
+ return Void();
+}
+
/* 1.5 Apis */
Return<void> RadioIndication_v1_6::uiccApplicationsEnablementChanged(RadioIndicationType /*type*/,
bool /*enabled*/) {
diff --git a/radio/1.6/vts/functional/radio_response.cpp b/radio/1.6/vts/functional/radio_response.cpp
index 7c5cf6d..feb9e03 100644
--- a/radio/1.6/vts/functional/radio_response.cpp
+++ b/radio/1.6/vts/functional/radio_response.cpp
@@ -816,8 +816,11 @@
}
Return<void> RadioResponse_v1_6::getVoiceRegistrationStateResponse_1_2(
- const ::android::hardware::radio::V1_0::RadioResponseInfo& /*info*/,
- const ::android::hardware::radio::V1_2::VoiceRegStateResult& /*voiceRegResponse*/) {
+ const ::android::hardware::radio::V1_0::RadioResponseInfo& info,
+ const ::android::hardware::radio::V1_2::VoiceRegStateResult& voiceRegResponse) {
+ rspInfo_v1_0 = info;
+ voiceRegResp = voiceRegResponse;
+ parent_v1_6.notify(info.serial);
return Void();
}
@@ -1186,7 +1189,8 @@
}
Return<void> RadioResponse_v1_6::getSystemSelectionChannelsResponse(
- const ::android::hardware::radio::V1_6::RadioResponseInfo& info) {
+ const ::android::hardware::radio::V1_6::RadioResponseInfo& info,
+ const hidl_vec<::android::hardware::radio::V1_5::RadioAccessSpecifier>& /*specifier*/) {
rspInfo = info;
parent_v1_6.notify(info.serial);
return Void();
@@ -1210,8 +1214,9 @@
Return<void> RadioResponse_v1_6::getCurrentCallsResponse_1_6(
const ::android::hardware::radio::V1_6::RadioResponseInfo& info,
- const ::android::hardware::hidl_vec<::android::hardware::radio::V1_6::Call>& /*calls*/) {
+ const ::android::hardware::hidl_vec<::android::hardware::radio::V1_6::Call>& calls) {
rspInfo = info;
+ currentCalls = calls;
parent_v1_6.notify(info.serial);
return Void();
}
diff --git a/radio/config/1.3/Android.bp b/radio/config/1.3/Android.bp
new file mode 100644
index 0000000..ace0de9
--- /dev/null
+++ b/radio/config/1.3/Android.bp
@@ -0,0 +1,21 @@
+// This file is autogenerated by hidl-gen -Landroidbp.
+
+hidl_interface {
+ name: "android.hardware.radio.config@1.3",
+ root: "android.hardware",
+ srcs: [
+ "types.hal",
+ "IRadioConfig.hal",
+ "IRadioConfigResponse.hal",
+ ],
+ interfaces: [
+ "android.hardware.radio.config@1.0",
+ "android.hardware.radio.config@1.1",
+ "android.hardware.radio.config@1.2",
+ "android.hardware.radio@1.0",
+ "android.hardware.radio@1.6",
+ "android.hidl.base@1.0",
+ ],
+ gen_java: true,
+ system_ext_specific: true,
+}
diff --git a/radio/config/1.3/IRadioConfig.hal b/radio/config/1.3/IRadioConfig.hal
new file mode 100644
index 0000000..83bcf92
--- /dev/null
+++ b/radio/config/1.3/IRadioConfig.hal
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *
+ * This interface is used by telephony and telecom to talk to cellular radio for the purpose of
+ * radio configuration, and it is not associated with any specific modem or slot.
+ * All the functions have minimum one parameter:
+ * serial: which corresponds to serial no. of request. Serial numbers must only be memorized for the
+ * duration of a method call. If clients provide colliding serials (including passing the same
+ * serial to different methods), multiple responses (one for each method call) must still be served.
+ */
+
+package android.hardware.radio.config@1.3;
+
+import @1.1::IRadioConfig;
+import IRadioConfigResponse;
+
+interface IRadioConfig extends @1.1::IRadioConfig {
+ /**
+ * Gets the available Radio Hal capabilities on the current device.
+ *
+ * This is called once per device boot up.
+ *
+ * @param serial Serial number of request
+ *
+ * Response callback is
+ * IRadioConfigResponse.getHalDeviceCapabilitiesResponse()
+ */
+ oneway getHalDeviceCapabilities(int32_t serial);
+};
diff --git a/radio/config/1.3/IRadioConfigResponse.hal b/radio/config/1.3/IRadioConfigResponse.hal
new file mode 100644
index 0000000..863754f
--- /dev/null
+++ b/radio/config/1.3/IRadioConfigResponse.hal
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.radio.config@1.3;
+
+import android.hardware.radio@1.6::RadioResponseInfo;
+import @1.2::IRadioConfigResponse;
+import HalDeviceCapabilities;
+
+/**
+ * Interface declaring response functions to solicited radio config requests.
+ */
+interface IRadioConfigResponse extends @1.2::IRadioConfigResponse {
+ /**
+ * @param info Response info struct containing response type, serial no. and error
+ * @param capabilities Capabilities struct containing the capabilities of the
+ * device related to the Radio HAL
+ *
+ * Valid errors returned:
+ * RadioError:NONE
+ * RadioError:RADIO_NOT_AVAILABLE
+ * RadioError:INTERNAL_ERR
+ */
+ oneway getHalDeviceCapabilitiesResponse(RadioResponseInfo info,
+ HalDeviceCapabilities capabilities);
+};
diff --git a/health/1.0/default/libhealthd/healthd_board_default.cpp b/radio/config/1.3/types.hal
similarity index 60%
copy from health/1.0/default/libhealthd/healthd_board_default.cpp
copy to radio/config/1.3/types.hal
index 127f98e..ba964bf 100644
--- a/health/1.0/default/libhealthd/healthd_board_default.cpp
+++ b/radio/config/1.3/types.hal
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,15 @@
* limitations under the License.
*/
-#include <healthd/healthd.h>
+package android.hardware.radio.config@1.3;
-void healthd_board_init(struct healthd_config*)
-{
- // use defaults
-}
-
-int healthd_board_battery_update(struct android::BatteryProperties*)
-{
- // return 0 to log periodic polled battery status to kernel log
- return 0;
-}
+/**
+ * Contains the device capabilities with respect to the Radio HAL.
+ */
+struct HalDeviceCapabilities {
+ /**
+ * True indicates that the modem is missing features within the current
+ * version of the Radio HAL.
+ */
+ bool modemReducedFeatureSet1;
+};
diff --git a/radio/config/1.3/vts/functional/Android.bp b/radio/config/1.3/vts/functional/Android.bp
new file mode 100644
index 0000000..abd081f
--- /dev/null
+++ b/radio/config/1.3/vts/functional/Android.bp
@@ -0,0 +1,39 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_test {
+ name: "VtsHalRadioConfigV1_3TargetTest",
+ defaults: ["VtsHalTargetTestDefaults"],
+ srcs: [
+ "radio_config_hidl_hal_api.cpp",
+ "radio_config_hidl_hal_test.cpp",
+ "radio_config_response.cpp",
+ "radio_config_indication.cpp",
+ "VtsHalRadioConfigV1_3TargetTest.cpp",
+ ],
+ static_libs: [
+ "RadioVtsTestUtilBase",
+ "android.hardware.radio.config@1.0",
+ "android.hardware.radio.config@1.1",
+ "android.hardware.radio.config@1.2",
+ "android.hardware.radio.config@1.3",
+ ],
+ header_libs: ["radio.util.header@1.0"],
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
+}
diff --git a/radio/config/1.3/vts/functional/VtsHalRadioConfigV1_3TargetTest.cpp b/radio/config/1.3/vts/functional/VtsHalRadioConfigV1_3TargetTest.cpp
new file mode 100644
index 0000000..5772d08
--- /dev/null
+++ b/radio/config/1.3/vts/functional/VtsHalRadioConfigV1_3TargetTest.cpp
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <radio_config_hidl_hal_utils.h>
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(RadioConfigHidlTest);
+INSTANTIATE_TEST_SUITE_P(
+ PerInstance, RadioConfigHidlTest,
+ testing::ValuesIn(android::hardware::getAllHalInstanceNames(IRadioConfig::descriptor)),
+ android::hardware::PrintInstanceNameToString);
diff --git a/radio/config/1.3/vts/functional/radio_config_hidl_hal_api.cpp b/radio/config/1.3/vts/functional/radio_config_hidl_hal_api.cpp
new file mode 100644
index 0000000..8df02dd
--- /dev/null
+++ b/radio/config/1.3/vts/functional/radio_config_hidl_hal_api.cpp
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <radio_config_hidl_hal_utils.h>
+
+#define ASSERT_OK(ret) ASSERT_TRUE(ret.isOk())
+
+/*
+ * Test IRadioConfig.getHalDeviceCapabilities()
+ */
+TEST_P(RadioConfigHidlTest, getHalDeviceCapabilities) {
+ const int serial = GetRandomSerialNumber();
+ Return<void> res = radioConfig->getHalDeviceCapabilities(serial);
+ ASSERT_OK(res);
+ ALOGI("getHalDeviceCapabilities, rspInfo.error = %s\n",
+ toString(radioConfigRsp->rspInfo.error).c_str());
+}
diff --git a/radio/config/1.3/vts/functional/radio_config_hidl_hal_test.cpp b/radio/config/1.3/vts/functional/radio_config_hidl_hal_test.cpp
new file mode 100644
index 0000000..de8365a
--- /dev/null
+++ b/radio/config/1.3/vts/functional/radio_config_hidl_hal_test.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <radio_config_hidl_hal_utils.h>
+
+void RadioConfigHidlTest::SetUp() {
+ radioConfig = IRadioConfig::getService(GetParam());
+ if (radioConfig == NULL) {
+ sleep(60);
+ radioConfig = IRadioConfig::getService(GetParam());
+ }
+ ASSERT_NE(nullptr, radioConfig.get());
+
+ radioConfigRsp = new (std::nothrow) RadioConfigResponse(*this);
+ ASSERT_NE(nullptr, radioConfigRsp.get());
+
+ count_ = 0;
+
+ radioConfig->setResponseFunctions(radioConfigRsp, nullptr);
+}
+
+/*
+ * Notify that the response message is received.
+ */
+void RadioConfigHidlTest::notify(int receivedSerial) {
+ std::unique_lock<std::mutex> lock(mtx_);
+ if (serial == receivedSerial) {
+ count_++;
+ cv_.notify_one();
+ }
+}
+
+/*
+ * Wait till the response message is notified or till TIMEOUT_PERIOD.
+ */
+std::cv_status RadioConfigHidlTest::wait() {
+ std::unique_lock<std::mutex> lock(mtx_);
+
+ std::cv_status status = std::cv_status::no_timeout;
+ auto now = std::chrono::system_clock::now();
+ while (count_ == 0) {
+ status = cv_.wait_until(lock, now + std::chrono::seconds(TIMEOUT_PERIOD));
+ if (status == std::cv_status::timeout) {
+ return status;
+ }
+ }
+ count_--;
+ return status;
+}
diff --git a/radio/config/1.3/vts/functional/radio_config_hidl_hal_utils.h b/radio/config/1.3/vts/functional/radio_config_hidl_hal_utils.h
new file mode 100644
index 0000000..439eb70
--- /dev/null
+++ b/radio/config/1.3/vts/functional/radio_config_hidl_hal_utils.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+
+#include <chrono>
+#include <condition_variable>
+#include <mutex>
+
+#include <android/hardware/radio/config/1.1/IRadioConfig.h>
+#include <android/hardware/radio/config/1.1/types.h>
+#include <android/hardware/radio/config/1.2/IRadioConfigIndication.h>
+#include <android/hardware/radio/config/1.2/IRadioConfigResponse.h>
+#include <android/hardware/radio/config/1.2/types.h>
+#include <android/hardware/radio/config/1.3/IRadioConfig.h>
+#include <android/hardware/radio/config/1.3/IRadioConfigResponse.h>
+#include <android/hardware/radio/config/1.3/types.h>
+#include <gtest/gtest.h>
+#include <hidl/GtestPrinter.h>
+#include <hidl/ServiceManagement.h>
+#include <log/log.h>
+
+#include "vts_test_util.h"
+
+using namespace ::android::hardware::radio::config::V1_2;
+
+using ::android::sp;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::radio::config::V1_1::ModemsConfig;
+using ::android::hardware::radio::config::V1_1::PhoneCapability;
+using ::android::hardware::radio::config::V1_2::SimSlotStatus;
+using ::android::hardware::radio::config::V1_3::HalDeviceCapabilities;
+using ::android::hardware::radio::config::V1_3::IRadioConfig;
+using ::android::hardware::radio::V1_0::RadioResponseInfo;
+
+#define TIMEOUT_PERIOD 75
+#define RADIO_SERVICE_NAME "slot1"
+
+class RadioConfigHidlTest;
+
+/* Callback class for radio config response */
+class RadioConfigResponse : public IRadioConfigResponse {
+ protected:
+ RadioConfigHidlTest& parent;
+
+ public:
+ RadioResponseInfo rspInfo;
+ PhoneCapability phoneCap;
+
+ RadioConfigResponse(RadioConfigHidlTest& parent);
+ virtual ~RadioConfigResponse() = default;
+
+ Return<void> getSimSlotsStatusResponse(
+ const RadioResponseInfo& info,
+ const ::android::hardware::hidl_vec<
+ ::android::hardware::radio::config::V1_0::SimSlotStatus>& slotStatus);
+
+ Return<void> getSimSlotsStatusResponse_1_2(
+ const RadioResponseInfo& info,
+ const ::android::hardware::hidl_vec<SimSlotStatus>& slotStatus);
+
+ Return<void> setSimSlotsMappingResponse(const RadioResponseInfo& info);
+
+ Return<void> getPhoneCapabilityResponse(const RadioResponseInfo& info,
+ const PhoneCapability& phoneCapability);
+
+ Return<void> setPreferredDataModemResponse(const RadioResponseInfo& info);
+
+ Return<void> getModemsConfigResponse(const RadioResponseInfo& info,
+ const ModemsConfig& mConfig);
+
+ Return<void> setModemsConfigResponse(const RadioResponseInfo& info);
+
+ Return<void> getHalDeviceCapabilitiesResponse(
+ const ::android::hardware::radio::V1_6::RadioResponseInfo& info,
+ const HalDeviceCapabilities& halDeviceCapabilities);
+};
+
+/* Callback class for radio config indication */
+class RadioConfigIndication : public IRadioConfigIndication {
+ protected:
+ RadioConfigHidlTest& parent;
+
+ public:
+ RadioConfigIndication(RadioConfigHidlTest& parent);
+ virtual ~RadioConfigIndication() = default;
+
+ Return<void> simSlotsStatusChanged_1_2(
+ ::android::hardware::radio::V1_0::RadioIndicationType type,
+ const ::android::hardware::hidl_vec<SimSlotStatus>& slotStatus);
+};
+
+// The main test class for Radio config HIDL.
+class RadioConfigHidlTest : public ::testing::TestWithParam<std::string> {
+ protected:
+ std::mutex mtx_;
+ std::condition_variable cv_;
+ int count_;
+
+ public:
+ virtual void SetUp() override;
+
+ /* Used as a mechanism to inform the test about data/event callback */
+ void notify(int receivedSerial);
+
+ /* Test code calls this function to wait for response */
+ std::cv_status wait();
+
+ void updateSimCardStatus();
+
+ /* Serial number for radio request */
+ int serial;
+
+ /* radio config service handle */
+ sp<IRadioConfig> radioConfig;
+
+ /* radio config response handle */
+ sp<RadioConfigResponse> radioConfigRsp;
+};
diff --git a/radio/config/1.3/vts/functional/radio_config_indication.cpp b/radio/config/1.3/vts/functional/radio_config_indication.cpp
new file mode 100644
index 0000000..6fa443c
--- /dev/null
+++ b/radio/config/1.3/vts/functional/radio_config_indication.cpp
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <radio_config_hidl_hal_utils.h>
+
+RadioConfigIndication::RadioConfigIndication(RadioConfigHidlTest& parent) : parent(parent) {}
+
+Return<void> RadioConfigIndication::simSlotsStatusChanged_1_2(
+ ::android::hardware::radio::V1_0::RadioIndicationType /*type*/,
+ const ::android::hardware::hidl_vec<SimSlotStatus>& /*slotStatus*/) {
+ return Void();
+}
diff --git a/radio/config/1.3/vts/functional/radio_config_response.cpp b/radio/config/1.3/vts/functional/radio_config_response.cpp
new file mode 100644
index 0000000..2a8b28b
--- /dev/null
+++ b/radio/config/1.3/vts/functional/radio_config_response.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <radio_config_hidl_hal_utils.h>
+
+// SimSlotStatus slotStatus;
+
+RadioConfigResponse::RadioConfigResponse(RadioConfigHidlTest& parent) : parent(parent) {}
+
+Return<void> RadioConfigResponse::getSimSlotsStatusResponse(
+ const ::android::hardware::radio::V1_0::RadioResponseInfo& /* info */,
+ const ::android::hardware::hidl_vec<
+ ::android::hardware::radio::config::V1_0::SimSlotStatus>& /* slotStatus */) {
+ return Void();
+}
+
+Return<void> RadioConfigResponse::getSimSlotsStatusResponse_1_2(
+ const ::android::hardware::radio::V1_0::RadioResponseInfo& /* info */,
+ const ::android::hardware::hidl_vec<SimSlotStatus>& /* slotStatus */) {
+ return Void();
+}
+
+Return<void> RadioConfigResponse::setSimSlotsMappingResponse(
+ const ::android::hardware::radio::V1_0::RadioResponseInfo& /* info */) {
+ return Void();
+}
+
+Return<void> RadioConfigResponse::getPhoneCapabilityResponse(
+ const ::android::hardware::radio::V1_0::RadioResponseInfo& info,
+ const PhoneCapability& phoneCapability) {
+ rspInfo = info;
+ phoneCap = phoneCapability;
+ parent.notify(info.serial);
+ return Void();
+}
+
+Return<void> RadioConfigResponse::setPreferredDataModemResponse(
+ const ::android::hardware::radio::V1_0::RadioResponseInfo& /* info */) {
+ return Void();
+}
+
+Return<void> RadioConfigResponse::getModemsConfigResponse(
+ const ::android::hardware::radio::V1_0::RadioResponseInfo& /* info */,
+ const ModemsConfig& /* mConfig */) {
+ return Void();
+}
+
+Return<void> RadioConfigResponse::setModemsConfigResponse(
+ const ::android::hardware::radio::V1_0::RadioResponseInfo& /* info */) {
+ return Void();
+}
+
+Return<void> RadioConfigResponse::getHalDeviceCapabilitiesResponse(
+ const ::android::hardware::radio::V1_6::RadioResponseInfo& /* info */,
+ const ::android::hardware::radio::config::V1_3::HalDeviceCapabilities& /* capabilities */) {
+ return Void();
+}
\ No newline at end of file
diff --git a/rebootescrow/aidl/default/Android.bp b/rebootescrow/aidl/default/Android.bp
index b77272f..e6a4e7a 100644
--- a/rebootescrow/aidl/default/Android.bp
+++ b/rebootescrow/aidl/default/Android.bp
@@ -20,7 +20,7 @@
shared_libs: [
"libbase",
"libbinder_ndk",
- "android.hardware.rebootescrow-ndk_platform",
+ "android.hardware.rebootescrow-V1-ndk_platform",
],
export_include_dirs: ["include"],
srcs: [
@@ -47,7 +47,7 @@
shared_libs: [
"libbase",
"libbinder_ndk",
- "android.hardware.rebootescrow-ndk_platform",
+ "android.hardware.rebootescrow-V1-ndk_platform",
],
static_libs: [
"libhadamardutils",
diff --git a/rebootescrow/aidl/vts/functional/Android.bp b/rebootescrow/aidl/vts/functional/Android.bp
index 2cc0068..abd4937 100644
--- a/rebootescrow/aidl/vts/functional/Android.bp
+++ b/rebootescrow/aidl/vts/functional/Android.bp
@@ -25,7 +25,7 @@
"libbinder",
],
static_libs: [
- "android.hardware.rebootescrow-cpp",
+ "android.hardware.rebootescrow-V1-cpp",
],
test_suites: [
"vts",
diff --git a/security/keymint/aidl/Android.bp b/security/keymint/aidl/Android.bp
index b5adac9..5652827 100644
--- a/security/keymint/aidl/Android.bp
+++ b/security/keymint/aidl/Android.bp
@@ -4,6 +4,9 @@
srcs: [
"android/hardware/security/keymint/*.aidl",
],
+ imports: [
+ "android.hardware.security.secureclock",
+ ],
stability: "vintf",
backend: {
java: {
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Algorithm.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Algorithm.aidl
index 46e0ae0..29ff8f8 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Algorithm.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Algorithm.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/AttestationKey.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/AttestationKey.aidl
new file mode 100644
index 0000000..893b016
--- /dev/null
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/AttestationKey.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 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.security.keymint;
+@RustDerive(Clone=true, Eq=true, Hash=true, Ord=true, PartialEq=true, PartialOrd=true) @VintfStability
+parcelable AttestationKey {
+ byte[] keyBlob;
+ android.hardware.security.keymint.KeyParameter[] attestKeyParams;
+ byte[] issuerSubjectName;
+}
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/BeginResult.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/BeginResult.aidl
index ed96485..4421619 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/BeginResult.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/BeginResult.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/BlockMode.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/BlockMode.aidl
index dddc9d8..e9652c3 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/BlockMode.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/BlockMode.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ByteArray.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ByteArray.aidl
index 3d18a26..34f2749 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ByteArray.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ByteArray.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Certificate.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Certificate.aidl
index 9e0f8dc..5d1cc68 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Certificate.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Certificate.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Digest.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Digest.aidl
index 8fc4d42..5055d75 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Digest.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Digest.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/EcCurve.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/EcCurve.aidl
index 7c3f2f3..1a7e9b5 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/EcCurve.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/EcCurve.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ErrorCode.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ErrorCode.aidl
index cdcb08d..3faba48 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ErrorCode.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ErrorCode.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
@@ -94,6 +109,12 @@
ATTESTATION_IDS_NOT_PROVISIONED = -75,
INVALID_OPERATION = -76,
STORAGE_KEY_UNSUPPORTED = -77,
+ INCOMPATIBLE_MGF_DIGEST = -78,
+ UNSUPPORTED_MGF_DIGEST = -79,
+ MISSING_NOT_BEFORE = -80,
+ MISSING_NOT_AFTER = -81,
+ MISSING_ISSUER_SUBJECT = -82,
+ INVALID_ISSUER_SUBJECT = -83,
UNIMPLEMENTED = -100,
VERSION_MISMATCH = -101,
UNKNOWN_ERROR = -1000,
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/HardwareAuthToken.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/HardwareAuthToken.aidl
index 9ea24f5..bd304f1 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/HardwareAuthToken.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/HardwareAuthToken.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
@@ -16,12 +31,12 @@
// later when a module using the interface is updated, e.g., Mainline modules.
package android.hardware.security.keymint;
-@VintfStability
+@RustDerive(Clone=true, Eq=true, Hash=true, Ord=true, PartialEq=true, PartialOrd=true) @VintfStability
parcelable HardwareAuthToken {
long challenge;
long userId;
long authenticatorId;
android.hardware.security.keymint.HardwareAuthenticatorType authenticatorType;
- android.hardware.security.keymint.Timestamp timestamp;
+ android.hardware.security.secureclock.Timestamp timestamp;
byte[] mac;
}
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/HardwareAuthenticatorType.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/HardwareAuthenticatorType.aidl
index aef5ee0..ae64110 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/HardwareAuthenticatorType.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/HardwareAuthenticatorType.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IKeyMintDevice.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IKeyMintDevice.aidl
index 3d08cfe..d3c6910 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IKeyMintDevice.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IKeyMintDevice.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
@@ -19,15 +34,16 @@
@VintfStability
interface IKeyMintDevice {
android.hardware.security.keymint.KeyMintHardwareInfo getHardwareInfo();
- android.hardware.security.keymint.VerificationToken verifyAuthorization(in long challenge, in android.hardware.security.keymint.HardwareAuthToken token);
void addRngEntropy(in byte[] data);
- void generateKey(in android.hardware.security.keymint.KeyParameter[] keyParams, out android.hardware.security.keymint.ByteArray generatedKeyBlob, out android.hardware.security.keymint.KeyCharacteristics generatedKeyCharacteristics, out android.hardware.security.keymint.Certificate[] outCertChain);
- void importKey(in android.hardware.security.keymint.KeyParameter[] inKeyParams, in android.hardware.security.keymint.KeyFormat inKeyFormat, in byte[] inKeyData, out android.hardware.security.keymint.ByteArray outImportedKeyBlob, out android.hardware.security.keymint.KeyCharacteristics outImportedKeyCharacteristics, out android.hardware.security.keymint.Certificate[] outCertChain);
- void importWrappedKey(in byte[] inWrappedKeyData, in byte[] inWrappingKeyBlob, in byte[] inMaskingKey, in android.hardware.security.keymint.KeyParameter[] inUnwrappingParams, in long inPasswordSid, in long inBiometricSid, out android.hardware.security.keymint.ByteArray outImportedKeyBlob, out android.hardware.security.keymint.KeyCharacteristics outImportedKeyCharacteristics);
- byte[] upgradeKey(in byte[] inKeyBlobToUpgrade, in android.hardware.security.keymint.KeyParameter[] inUpgradeParams);
- void deleteKey(in byte[] inKeyBlob);
+ android.hardware.security.keymint.KeyCreationResult generateKey(in android.hardware.security.keymint.KeyParameter[] keyParams, in @nullable android.hardware.security.keymint.AttestationKey attestationKey);
+ android.hardware.security.keymint.KeyCreationResult importKey(in android.hardware.security.keymint.KeyParameter[] keyParams, in android.hardware.security.keymint.KeyFormat keyFormat, in byte[] keyData, in @nullable android.hardware.security.keymint.AttestationKey attestationKey);
+ android.hardware.security.keymint.KeyCreationResult importWrappedKey(in byte[] wrappedKeyData, in byte[] wrappingKeyBlob, in byte[] maskingKey, in android.hardware.security.keymint.KeyParameter[] unwrappingParams, in long passwordSid, in long biometricSid);
+ byte[] upgradeKey(in byte[] keyBlobToUpgrade, in android.hardware.security.keymint.KeyParameter[] upgradeParams);
+ void deleteKey(in byte[] keyBlob);
void deleteAllKeys();
void destroyAttestationIds();
- android.hardware.security.keymint.BeginResult begin(in android.hardware.security.keymint.KeyPurpose inPurpose, in byte[] inKeyBlob, in android.hardware.security.keymint.KeyParameter[] inParams, in android.hardware.security.keymint.HardwareAuthToken inAuthToken);
+ android.hardware.security.keymint.BeginResult begin(in android.hardware.security.keymint.KeyPurpose purpose, in byte[] keyBlob, in android.hardware.security.keymint.KeyParameter[] params, in android.hardware.security.keymint.HardwareAuthToken authToken);
+ void deviceLocked(in boolean passwordOnly, in @nullable android.hardware.security.secureclock.TimeStampToken timestampToken);
+ void earlyBootEnded();
const int AUTH_TOKEN_MAC_LENGTH = 32;
}
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IKeyMintOperation.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IKeyMintOperation.aidl
index 8e3b0fc..a9b9a05 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IKeyMintOperation.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IKeyMintOperation.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
@@ -18,7 +33,7 @@
package android.hardware.security.keymint;
@VintfStability
interface IKeyMintOperation {
- int update(in @nullable android.hardware.security.keymint.KeyParameterArray inParams, in @nullable byte[] input, in @nullable android.hardware.security.keymint.HardwareAuthToken inAuthToken, in @nullable android.hardware.security.keymint.VerificationToken inVerificationToken, out @nullable android.hardware.security.keymint.KeyParameterArray outParams, out @nullable android.hardware.security.keymint.ByteArray output);
- byte[] finish(in @nullable android.hardware.security.keymint.KeyParameterArray inParams, in @nullable byte[] input, in @nullable byte[] inSignature, in @nullable android.hardware.security.keymint.HardwareAuthToken authToken, in @nullable android.hardware.security.keymint.VerificationToken inVerificationToken, out @nullable android.hardware.security.keymint.KeyParameterArray outParams);
+ int update(in @nullable android.hardware.security.keymint.KeyParameterArray inParams, in @nullable byte[] input, in @nullable android.hardware.security.keymint.HardwareAuthToken inAuthToken, in @nullable android.hardware.security.secureclock.TimeStampToken inTimeStampToken, out @nullable android.hardware.security.keymint.KeyParameterArray outParams, out @nullable android.hardware.security.keymint.ByteArray output);
+ byte[] finish(in @nullable android.hardware.security.keymint.KeyParameterArray inParams, in @nullable byte[] input, in @nullable byte[] inSignature, in @nullable android.hardware.security.keymint.HardwareAuthToken authToken, in @nullable android.hardware.security.secureclock.TimeStampToken inTimeStampToken, out @nullable android.hardware.security.keymint.KeyParameterArray outParams);
void abort();
}
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl
new file mode 100644
index 0000000..a864c3c
--- /dev/null
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.security.keymint;
+@VintfStability
+interface IRemotelyProvisionedComponent {
+ byte[] generateEcdsaP256KeyPair(in boolean testMode, out android.hardware.security.keymint.MacedPublicKey macedPublicKey);
+ void generateCertificateRequest(in boolean testMode, in android.hardware.security.keymint.MacedPublicKey[] keysToSign, in byte[] endpointEncryptionCertChain, in byte[] challenge, out byte[] keysToSignMac, out android.hardware.security.keymint.ProtectedData protectedData);
+ const int STATUS_FAILED = 1;
+ const int STATUS_INVALID_MAC = 2;
+ const int STATUS_PRODUCTION_KEY_IN_TEST_REQUEST = 3;
+ const int STATUS_TEST_KEY_IN_PRODUCTION_REQUEST = 4;
+ const int STATUS_INVALID_EEK = 5;
+}
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyCharacteristics.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyCharacteristics.aidl
index fb4214c..b430da9 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyCharacteristics.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyCharacteristics.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
@@ -18,6 +33,6 @@
package android.hardware.security.keymint;
@VintfStability
parcelable KeyCharacteristics {
- android.hardware.security.keymint.KeyParameter[] softwareEnforced;
- android.hardware.security.keymint.KeyParameter[] hardwareEnforced;
+ android.hardware.security.keymint.SecurityLevel securityLevel;
+ android.hardware.security.keymint.KeyParameter[] authorizations;
}
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyCreationResult.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyCreationResult.aidl
new file mode 100644
index 0000000..4139436
--- /dev/null
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyCreationResult.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 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.security.keymint;
+@VintfStability
+parcelable KeyCreationResult {
+ byte[] keyBlob;
+ android.hardware.security.keymint.KeyCharacteristics[] keyCharacteristics;
+ android.hardware.security.keymint.Certificate[] certificateChain;
+}
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyFormat.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyFormat.aidl
index f701c80..1ad7c51 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyFormat.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyFormat.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyMintHardwareInfo.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyMintHardwareInfo.aidl
index 5e9f7ae..d06312a 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyMintHardwareInfo.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyMintHardwareInfo.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
@@ -16,10 +31,11 @@
// later when a module using the interface is updated, e.g., Mainline modules.
package android.hardware.security.keymint;
-@VintfStability
+@RustDerive(Clone=true, Eq=true, Hash=true, Ord=true, PartialEq=true, PartialOrd=true) @VintfStability
parcelable KeyMintHardwareInfo {
int versionNumber;
android.hardware.security.keymint.SecurityLevel securityLevel;
@utf8InCpp String keyMintName;
@utf8InCpp String keyMintAuthorName;
+ boolean timestampTokenRequired;
}
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyOrigin.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyOrigin.aidl
index 9728bf9..acaf60d 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyOrigin.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyOrigin.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameter.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameter.aidl
index 4985768..f534952 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameter.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameter.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameterArray.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameterArray.aidl
index 2c3b768..2706623 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameterArray.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameterArray.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameterValue.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameterValue.aidl
index ecf20ad..c79614a 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameterValue.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameterValue.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyPurpose.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyPurpose.aidl
index a6fd8c3..61bb7e4 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyPurpose.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyPurpose.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
@@ -23,4 +38,6 @@
SIGN = 2,
VERIFY = 3,
WRAP_KEY = 5,
+ AGREE_KEY = 6,
+ ATTEST_KEY = 7,
}
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/MacedPublicKey.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/MacedPublicKey.aidl
new file mode 100644
index 0000000..b4caeed
--- /dev/null
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/MacedPublicKey.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.security.keymint;
+@VintfStability
+parcelable MacedPublicKey {
+ byte[] macedKey;
+}
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/PaddingMode.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/PaddingMode.aidl
index 2ecfa1e..96b63e1 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/PaddingMode.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/PaddingMode.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ProtectedData.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ProtectedData.aidl
new file mode 100644
index 0000000..46f602f
--- /dev/null
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ProtectedData.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 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.security.keymint;
+@VintfStability
+parcelable ProtectedData {
+ byte[] protectedData;
+}
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/SecurityLevel.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/SecurityLevel.aidl
index 601693f..c720d6d 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/SecurityLevel.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/SecurityLevel.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
@@ -21,4 +36,5 @@
SOFTWARE = 0,
TRUSTED_ENVIRONMENT = 1,
STRONGBOX = 2,
+ KEYSTORE = 100,
}
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Tag.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Tag.aidl
index 38eb6e6..03982e3 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Tag.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Tag.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
@@ -30,6 +45,7 @@
EC_CURVE = 268435466,
RSA_PUBLIC_EXPONENT = 1342177480,
INCLUDE_UNIQUE_ID = 1879048394,
+ RSA_OAEP_MGF_DIGEST = 536871115,
BLOB_USAGE_REQUIREMENTS = 268435757,
BOOTLOADER_ONLY = 1879048494,
ROLLBACK_RESISTANCE = 1879048495,
@@ -40,6 +56,7 @@
USAGE_EXPIRE_DATETIME = 1610613138,
MIN_SECONDS_BETWEEN_OPS = 805306771,
MAX_USES_PER_BOOT = 805306772,
+ USAGE_COUNT_LIMIT = 805306773,
USER_ID = 805306869,
USER_SECURE_ID = -1610612234,
NO_AUTH_REQUIRED = 1879048695,
@@ -77,4 +94,8 @@
MAC_LENGTH = 805307371,
RESET_SINCE_ID_ROTATION = 1879049196,
CONFIRMATION_TOKEN = -1879047187,
+ CERTIFICATE_SERIAL = -2147482642,
+ CERTIFICATE_SUBJECT = -1879047185,
+ CERTIFICATE_NOT_BEFORE = 1610613744,
+ CERTIFICATE_NOT_AFTER = 1610613745,
}
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/TagType.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/TagType.aidl
index bb2766c..75a19a3 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/TagType.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/TagType.aidl
@@ -1,14 +1,29 @@
-///////////////////////////////////////////////////////////////////////////////
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
+// 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 changes to the AIDL files built
+// 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
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Timestamp.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Timestamp.aidl
deleted file mode 100644
index 4d5b659..0000000
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Timestamp.aidl
+++ /dev/null
@@ -1,22 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files 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.security.keymint;
-@VintfStability
-parcelable Timestamp {
- long milliSeconds;
-}
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/VerificationToken.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/VerificationToken.aidl
deleted file mode 100644
index 5c76816..0000000
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/VerificationToken.aidl
+++ /dev/null
@@ -1,25 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files 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.security.keymint;
-@VintfStability
-parcelable VerificationToken {
- long challenge;
- android.hardware.security.keymint.Timestamp timestamp;
- android.hardware.security.keymint.SecurityLevel securityLevel;
- byte[] mac;
-}
diff --git a/security/keymint/aidl/android/hardware/security/keymint/AttestationKey.aidl b/security/keymint/aidl/android/hardware/security/keymint/AttestationKey.aidl
new file mode 100644
index 0000000..8167ceb
--- /dev/null
+++ b/security/keymint/aidl/android/hardware/security/keymint/AttestationKey.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 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.security.keymint;
+
+import android.hardware.security.keymint.KeyParameter;
+
+/**
+ * Contains a key blob with Tag::ATTEST_KEY that can be used to sign an attestation certificate,
+ * and the DER-encoded X.501 Subject Name that will be placed in the Issuer field of the attestation
+ * certificate.
+ */
+@VintfStability
+@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true)
+parcelable AttestationKey {
+ byte[] keyBlob;
+ KeyParameter[] attestKeyParams;
+ byte[] issuerSubjectName;
+}
diff --git a/security/keymint/aidl/android/hardware/security/keymint/Certificate.aidl b/security/keymint/aidl/android/hardware/security/keymint/Certificate.aidl
index a953859..0e5d898 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/Certificate.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/Certificate.aidl
@@ -17,9 +17,8 @@
package android.hardware.security.keymint;
/**
- * This encodes the IKeyMintDevice attestation generated certificate.
+ * This encodes an IKeyMintDevice certificate, generated for a KeyMint asymmetric public key.
*/
-
@VintfStability
parcelable Certificate {
/**
diff --git a/security/keymint/aidl/android/hardware/security/keymint/ErrorCode.aidl b/security/keymint/aidl/android/hardware/security/keymint/ErrorCode.aidl
index fb24ad1..5765130 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/ErrorCode.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/ErrorCode.aidl
@@ -42,7 +42,7 @@
INVALID_AUTHORIZATION_TIMEOUT = -16,
UNSUPPORTED_KEY_FORMAT = -17,
INCOMPATIBLE_KEY_FORMAT = -18,
- UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM = -19, /** For PKCS8 & PKCS12 */
+ UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM = -19, /** For PKCS8 & PKCS12 */
UNSUPPORTED_KEY_VERIFICATION_ALGORITHM = -20, /** For PKCS8 & PKCS12 */
INVALID_INPUT_LENGTH = -21,
KEY_EXPORT_OPTIONS_INVALID = -22,
@@ -99,6 +99,12 @@
ATTESTATION_IDS_NOT_PROVISIONED = -75,
INVALID_OPERATION = -76,
STORAGE_KEY_UNSUPPORTED = -77,
+ INCOMPATIBLE_MGF_DIGEST = -78,
+ UNSUPPORTED_MGF_DIGEST = -79,
+ MISSING_NOT_BEFORE = -80,
+ MISSING_NOT_AFTER = -81,
+ MISSING_ISSUER_SUBJECT = -82,
+ INVALID_ISSUER_SUBJECT = -83,
UNIMPLEMENTED = -100,
VERSION_MISMATCH = -101,
diff --git a/security/keymint/aidl/android/hardware/security/keymint/HardwareAuthToken.aidl b/security/keymint/aidl/android/hardware/security/keymint/HardwareAuthToken.aidl
index 12d615f..417a0b1 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/HardwareAuthToken.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/HardwareAuthToken.aidl
@@ -16,7 +16,7 @@
package android.hardware.security.keymint;
-import android.hardware.security.keymint.Timestamp;
+import android.hardware.security.secureclock.Timestamp;
import android.hardware.security.keymint.HardwareAuthenticatorType;
/**
@@ -29,6 +29,7 @@
* appropriate for a given key operation.
*/
@VintfStability
+@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true)
parcelable HardwareAuthToken {
/**
* challenge is a value that's used to enable authentication tokens to authorize specific
diff --git a/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl b/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl
index 4944acb..13e98af 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl
@@ -16,18 +16,18 @@
package android.hardware.security.keymint;
+import android.hardware.security.keymint.AttestationKey;
import android.hardware.security.keymint.BeginResult;
import android.hardware.security.keymint.ByteArray;
-import android.hardware.security.keymint.Certificate;
import android.hardware.security.keymint.HardwareAuthToken;
import android.hardware.security.keymint.IKeyMintOperation;
-import android.hardware.security.keymint.KeyCharacteristics;
+import android.hardware.security.keymint.KeyCreationResult;
import android.hardware.security.keymint.KeyFormat;
-import android.hardware.security.keymint.KeyParameter;
import android.hardware.security.keymint.KeyMintHardwareInfo;
+import android.hardware.security.keymint.KeyParameter;
import android.hardware.security.keymint.KeyPurpose;
import android.hardware.security.keymint.SecurityLevel;
-import android.hardware.security.keymint.VerificationToken;
+import android.hardware.security.secureclock.TimeStampToken;
/**
* KeyMint device definition.
@@ -126,16 +126,22 @@
* attacker can use them at will (though they're more secure than keys which can be
* exfiltrated). Therefore, IKeyMintDevice must enforce access controls.
*
- * Access controls are defined as an "authorization list" of tag/value pairs. Authorization tags
- * are 32-bit integers from the Tag enum, and the values are a variety of types, defined in the
- * TagType enum. Some tags may be repeated to specify multiple values. Whether a tag may be
- * repeated is specified in the documentation for the tag and in the TagType. When a key is
- * created or imported, the caller specifies an authorization list. The IKeyMintDevice must divide
- * the caller-provided authorizations into two lists, those it enforces in tee secure zone and
- * those enforced in the strongBox hardware. These two lists are returned as the "teeEnforced"
- * and "strongboxEnforced" elements of the KeyCharacteristics struct. Note that software enforced
- * authorization list entries are not returned because they are not enforced by keymint. The
- * IKeyMintDevice must also add the following authorizations to the appropriate list:
+ * Access controls are defined as "authorization lists" of tag/value pairs. Authorization tags are
+ * 32-bit integers from the Tag enum, and the values are a variety of types, defined in the TagType
+ * enum. Some tags may be repeated to specify multiple values. Whether a tag may be repeated is
+ * specified in the documentation for the tag and in the TagType. When a key is created or
+ * imported, the caller specifies a `key_description` authorization list. The IKeyMintDevice must
+ * determine which tags it can and cannot enforce, and at what SecurityLevel, and return an array of
+ * `KeyCharacteristics` structures that contains everything it will enforce, associated with the
+ * appropriate security level, which is one of SOFTWARE, TRUSTED_ENVIRONMENT and STRONGBOX.
+ * Typically, implementations will only return a single KeyCharacteristics structure, because
+ * everything they enforce is enforced at the same security level. There may be cases, however, for
+ * which multiple security levels are relevant. One example is that of a StrongBox IKeyMintDevice
+ * that relies on a TEE to enforce biometric user authentication. In that case, the generate/import
+ * methods must return two KeyCharacteristics structs, one with SecurityLevel::TRUSTED_ENVIRONMENT
+ * and the biometric authentication-related tags, and another with SecurityLevel::STRONGBOX and
+ * everything else. The IKeyMintDevice must also add the following authorizations to the
+ * appropriate list:
*
* o Tag::OS_VERSION
* o Tag::OS_PATCHLEVEL
@@ -148,26 +154,27 @@
* The caller must always provide the current date time in the keyParameter CREATION_DATETIME
* tags.
*
- * All authorization tags and their values, both teeEnforced and strongboxEnforced, including
- * unknown tags, must be cryptographically bound to the private/secret key material such that any
- * modification of the portion of the key blob that contains the authorization list makes it
- * impossible for the secure environment to obtain the private/secret key material. The
- * recommended approach to meet this requirement is to use the full set of authorization tags
- * associated with a key as input to a secure key derivation function used to derive a key that
- * is used to encrypt the private/secret key material.
+ * All authorization tags and their values enforced by an IKeyMintDevice must be cryptographically
+ * bound to the private/secret key material such that any modification of the portion of the key
+ * blob that contains the authorization list makes it impossible for the secure environment to
+ * obtain the private/secret key material. The recommended approach to meet this requirement is to
+ * use the full set of authorization tags associated with a key as input to a secure key derivation
+ * function used to derive a key (the KEK) that is used to encrypt the private/secret key material.
+ * Note that it is NOT acceptable to use a static KEK to encrypt the private/secret key material
+ * with an AEAD cipher mode, using the enforced authorization tags as AAD. This is because
+ * Tag::APPLICATION_DATA must not be included in the authorization tags stored in the key blob, but
+ * must be provided by the caller for every use. Assuming the Tag::APPLICATION_DATA value has
+ * sufficient entropy, this provides a cryptographic guarantee that an attacker cannot use a key
+ * without knowing the Tag::APPLICATION_DATA value, even if they compromise the IKeyMintDevice.
*
- * IKeyMintDevice implementations ignore any tags they cannot enforce and do not return them
- * in KeyCharacteristics. For example, Tag::ORIGINATION_EXPIRE_DATETIME provides the date and
- * time after which a key may not be used to encrypt or sign new messages. Unless the
- * IKeyMintDevice has access to a secure source of current date/time information, it is not
- * possible for the IKeyMintDevice to enforce this tag. An IKeyMintDevice implementation will
- * not rely on the non-secure world's notion of time, because it could be controlled by an
- * attacker. Similarly, it cannot rely on GPSr time, even if it has exclusive control of the
- * GPSr, because that might be spoofed by attacker RF signals.
- *
- * IKeyMintDevices do not use or enforce any tags they place in the softwareEnforced
- * list. The IKeyMintDevice caller must enforce them, and it is unnecessary to enforce them
- * twice.
+ * IKeyMintDevice implementations must ignore any tags they cannot enforce and must not return them
+ * in KeyCharacteristics. For example, Tag::ORIGINATION_EXPIRE_DATETIME provides the date and time
+ * after which a key may not be used to encrypt or sign new messages. Unless the IKeyMintDevice has
+ * access to a secure source of current date/time information, it is not possible for the
+ * IKeyMintDevice to enforce this tag. An IKeyMintDevice implementation will not rely on the
+ * non-secure world's notion of time, because it could be controlled by an attacker. Similarly, it
+ * cannot rely on GPSr time, even if it has exclusive control of the GPSr, because that might be
+ * spoofed by attacker RF signals.
*
* Some tags must be enforced by the IKeyMintDevice. See the detailed documentation on each Tag
* in Tag.aidl.
@@ -217,34 +224,6 @@
KeyMintHardwareInfo getHardwareInfo();
/**
- * Verify authorizations for another IKeyMintDevice instance.
- *
- * On systems with both a StrongBox and a TEE IKeyMintDevice instance it is sometimes useful
- * to ask the TEE KeyMintDevice to verify authorizations for a key hosted in StrongBox.
- *
- * For every StrongBox operation, Keystore is required to call this method on the TEE KeyMint,
- * passing in the StrongBox key's hardwareEnforced authorization list and the challenge
- * returned by StrongBox begin(). Keystore must then pass the VerificationToken to the
- * subsequent invocations of StrongBox update() and finish().
- *
- * StrongBox implementations must return ErrorCode::UNIMPLEMENTED.
- *
- * @param the challenge returned by StrongBox's keyMint's begin().
- *
- * @param authToken A HardwareAuthToken if needed to authorize key usage.
- *
- * @return error ErrorCode::OK on success or ErrorCode::UNIMPLEMENTED if the KeyMintDevice is
- * a StrongBox. If the IKeyMintDevice cannot verify one or more elements of
- * parametersToVerify it must not return an error code, but just omit the unverified
- * parameter from the VerificationToken.
- *
- * @return token the verification token. See VerificationToken in VerificationToken.aidl for
- * details.
- */
- VerificationToken verifyAuthorization(in long challenge,
- in HardwareAuthToken token);
-
- /**
* Adds entropy to the RNG used by KeyMint. Entropy added through this method must not be the
* only source of entropy used, and a secure mixing function must be used to mix the entropy
* provided by this method with internally-generated entropy. The mixing function must be
@@ -337,38 +316,18 @@
* provided in params. See above for detailed specifications of which tags are required
* for which types of keys.
*
- * @return generatedKeyBlob Opaque descriptor of the generated key. The recommended
- * implementation strategy is to include an encrypted copy of the key material, wrapped
- * in a key unavailable outside secure hardware.
+ * @param attestationKey, if provided, specifies the key that must be used to sign the
+ * attestation certificate. If `keyParams` does not contain a Tag::ATTESTATION_CHALLENGE
+ * but `attestationKey` is non-null, the IKeyMintDevice must return
+ * ErrorCode::INVALID_ARGUMENT. If the provided AttestationKey does not contain a key
+ * blob containing an asymmetric key with KeyPurpose::ATTEST_KEY, the IKeyMintDevice must
+ * return ErrorCode::INVALID_PURPOSE. If the provided AttestationKey has an empty issuer
+ * subject name, the IKeyMintDevice must return ErrorCode::INVALID_ARGUMENT.
*
- * @return generatedKeyCharacteristics Description of the generated key, divided into two sets:
- * hardware-enforced and software-enforced. The description here applies equally
- * to the key characteristics lists returned by generateKey, importKey and
- * importWrappedKey. The characteristics returned by this parameter completely
- * describe the type and usage of the specified key.
- *
- * The rule that IKeyMintDevice implementations must use for deciding whether a
- * given tag belongs in the hardware-enforced or software-enforced list is that if
- * the meaning of the tag is fully assured by secure hardware, it is hardware
- * enforced. Otherwise, it's software enforced.
- *
- * @return outCertChain If the key is an asymmetric key, and proper keyparameters for
- * attestation (such as challenge) is provided, then this parameter will return the
- * attestation certificate. If the signing of the attestation certificate is from a
- * factory key, additional certificates back to the root attestation certificate will
- * also be provided. Clients will need to check root certificate against a known-good
- * value. The certificates must be DER-encoded. Caller needs to provide
- * CREATION_DATETIME as one of the attestation parameters, otherwise the attestation
- * certificate will not contain the creation datetime. The first certificate in the
- * vector is the attestation for the generated key itself, the next certificate is
- * the key that signs the first certificate, and so forth. The last certificate in
- * the chain is the root certificate. If the key is a symmetric key, then no
- * certificate will be returned and this variable will return empty. TODO: change
- * certificate return to a single certificate and make it nullable b/163604282.
+ * @return The result of key creation. See KeyCreationResult.aidl.
*/
- void generateKey(in KeyParameter[] keyParams, out ByteArray generatedKeyBlob,
- out KeyCharacteristics generatedKeyCharacteristics,
- out Certificate[] outCertChain);
+ KeyCreationResult generateKey(
+ in KeyParameter[] keyParams, in @nullable AttestationKey attestationKey);
/**
* Imports key material into an IKeyMintDevice. Key definition parameters and return values
@@ -396,29 +355,18 @@
*
* @param inKeyData The key material to import, in the format specified in keyFormat.
*
- * @return outImportedKeyBlob descriptor of the imported key. The format of the keyblob will
- * be the google specified keyblob format.
+ * @param attestationKey, if provided, specifies the key that must be used to sign the
+ * attestation certificate. If `keyParams` does not contain a Tag::ATTESTATION_CHALLENGE
+ * but `attestationKey` is non-null, the IKeyMintDevice must return
+ * ErrorCode::INVALID_ARGUMENT. If the provided AttestationKey does not contain a key
+ * blob containing an asymmetric key with KeyPurpose::ATTEST_KEY, the IKeyMintDevice must
+ * return ErrorCode::INVALID_PURPOSE. If the provided AttestationKey has an empty issuer
+ * subject name, the IKeyMintDevice must return ErrorCode::INVALID_ARGUMENT.
*
- * @return outImportedKeyCharacteristics Description of the generated key. See the
- * keyCharacteristics description in generateKey.
- *
- * @return outCertChain If the key is an asymmetric key, and proper keyparameters for
- * attestation (such as challenge) is provided, then this parameter will return the
- * attestation certificate. If the signing of the attestation certificate is from a
- * factory key, additional certificates back to the root attestation certificate will
- * also be provided. Clients will need to check root certificate against a known-good
- * value. The certificates must be DER-encoded. Caller needs to provide
- * CREATION_DATETIME as one of the attestation parameters, otherwise the attestation
- * certificate will not contain the creation datetime. The first certificate in the
- * vector is the attestation for the generated key itself, the next certificate is
- * the key that signs the first certificate, and so forth. The last certificate in
- * the chain is the root certificate. If the key is a symmetric key, then no
- * certificate will be returned and this variable will return empty.
+ * @return The result of key creation. See KeyCreationResult.aidl.
*/
- void importKey(in KeyParameter[] inKeyParams, in KeyFormat inKeyFormat,
- in byte[] inKeyData, out ByteArray outImportedKeyBlob,
- out KeyCharacteristics outImportedKeyCharacteristics,
- out Certificate[] outCertChain);
+ KeyCreationResult importKey(in KeyParameter[] keyParams, in KeyFormat keyFormat,
+ in byte[] keyData, in @nullable AttestationKey attestationKey);
/**
* Securely imports a key, or key pair, returning a key blob and a description of the imported
@@ -474,45 +422,35 @@
* 5. Perform the equivalent of calling importKey(keyParams, keyFormat, keyData), except
* that the origin tag should be set to SECURELY_IMPORTED.
*
- * @param inWrappingKeyBlob The opaque key descriptor returned by generateKey() or importKey().
+ * @param wrappingKeyBlob The opaque key descriptor returned by generateKey() or importKey().
* This key must have been created with Purpose::WRAP_KEY.
*
- * @param inMaskingKey The 32-byte value XOR'd with the transport key in the SecureWrappedKey
+ * @param maskingKey The 32-byte value XOR'd with the transport key in the SecureWrappedKey
* structure.
*
- * @param inUnwrappingParams must contain any parameters needed to perform the unwrapping
- * operation. For example, if the wrapping key is an AES key the block and padding
- * modes must be specified in this argument.
+ * @param unwrappingParams must contain any parameters needed to perform the unwrapping
+ * operation. For example, if the wrapping key is an AES key the block and padding modes
+ * must be specified in this argument.
*
- * @param inPasswordSid specifies the password secure ID (SID) of the user that owns the key
- * being installed. If the authorization list in wrappedKeyData contains a
- * Tag::USER_SECURE_IDwith a value that has the HardwareAuthenticatorType::PASSWORD
- * bit set, the constructed key must be bound to the SID value provided by this
- * argument. If the wrappedKeyData does not contain such a tag and value, this argument
- * must be ignored.
+ * @param passwordSid specifies the password secure ID (SID) of the user that owns the key being
+ * installed. If the authorization list in wrappedKeyData contains a
+ * Tag::USER_SECURE_IDwith a value that has the HardwareAuthenticatorType::PASSWORD bit
+ * set, the constructed key must be bound to the SID value provided by this argument. If
+ * the wrappedKeyData does not contain such a tag and value, this argument must be
+ * ignored.
*
- * @param inBiometricSid specifies the biometric secure ID (SID) of the user that owns the key
+ * @param biometricSid specifies the biometric secure ID (SID) of the user that owns the key
* being installed. If the authorization list in wrappedKeyData contains a
* Tag::USER_SECURE_ID with a value that has the HardwareAuthenticatorType::FINGERPRINT
* bit set, the constructed key must be bound to the SID value provided by this argument.
* If the wrappedKeyData does not contain such a tag and value, this argument must be
* ignored.
*
- * @return outImportedKeyBlob Opaque descriptor of the imported key. It is recommended that
- * the keyBlob contain a copy of the key material, wrapped in a key unavailable outside
- * secure hardware.
- *
- * @return outImportedKeyCharacteristics Description of the generated key. See the description
- * of keyCharacteristics parameter in generateKey.
+ * @return The result of key creation. See KeyCreationResult.aidl.
*/
- void importWrappedKey(in byte[] inWrappedKeyData,
- in byte[] inWrappingKeyBlob,
- in byte[] inMaskingKey,
- in KeyParameter[] inUnwrappingParams,
- in long inPasswordSid,
- in long inBiometricSid,
- out ByteArray outImportedKeyBlob,
- out KeyCharacteristics outImportedKeyCharacteristics);
+ KeyCreationResult importWrappedKey(in byte[] wrappedKeyData, in byte[] wrappingKeyBlob,
+ in byte[] maskingKey, in KeyParameter[] unwrappingParams, in long passwordSid,
+ in long biometricSid);
/**
* Upgrades an old key blob. Keys can become "old" in two ways: IKeyMintDevice can be
@@ -547,7 +485,7 @@
* @return A new key blob that references the same key as keyBlobToUpgrade, but is in the new
* format, or has the new version data.
*/
- byte[] upgradeKey(in byte[] inKeyBlobToUpgrade, in KeyParameter[] inUpgradeParams);
+ byte[] upgradeKey(in byte[] keyBlobToUpgrade, in KeyParameter[] upgradeParams);
/**
* Deletes the key, or key pair, associated with the key blob. Calling this function on
@@ -557,7 +495,7 @@
*
* @param inKeyBlob The opaque descriptor returned by generateKey() or importKey();
*/
- void deleteKey(in byte[] inKeyBlob);
+ void deleteKey(in byte[] keyBlob);
/**
* Deletes all keys in the hardware keystore. Used when keystore is reset completely. After
@@ -759,9 +697,9 @@
* values less than the key's minimum length, begin() must return ErrorCode::INVALID_MAC_LENGTH.
*
* @param inPurpose The purpose of the operation, one of KeyPurpose::ENCRYPT,
- * KeyPurpose::DECRYPT, KeyPurpose::SIGN or KeyPurpose::VERIFY. Note that for AEAD
- * modes, encryption and decryption imply signing and verification, respectively, but
- * must be specified as KeyPurpose::ENCRYPT and KeyPurpose::DECRYPT.
+ * KeyPurpose::DECRYPT, KeyPurpose::SIGN, KeyPurpose::VERIFY, or KeyPurpose::AGREE_KEY.
+ * Note that for AEAD modes, encryption and decryption imply signing and verification,
+ * respectively, but must be specified as KeyPurpose::ENCRYPT and KeyPurpose::DECRYPT.
*
* @param inKeyBlob The opaque key descriptor returned by generateKey() or importKey(). The key
* must have a purpose compatible with purpose and all of its usage requirements must be
@@ -783,8 +721,44 @@
* from operations that generate an IV or nonce, and IKeyMintOperation object pointer
* which is used to perform update(), finish() or abort() operations.
*/
- BeginResult begin(in KeyPurpose inPurpose,
- in byte[] inKeyBlob,
- in KeyParameter[] inParams,
- in HardwareAuthToken inAuthToken);
+ BeginResult begin(in KeyPurpose purpose, in byte[] keyBlob, in KeyParameter[] params,
+ in HardwareAuthToken authToken);
+
+ /**
+ * Called by client to notify the IKeyMintDevice that the device is now locked, and keys with
+ * the UNLOCKED_DEVICE_REQUIRED tag should no longer be usable. When this function is called,
+ * the IKeyMintDevice should note the current timestamp, and attempts to use
+ * UNLOCKED_DEVICE_REQUIRED keys must be rejected with Error::DEVICE_LOCKED until an
+ * authentication token with a later timestamp is presented. If the `passwordOnly' argument is
+ * set to true the sufficiently-recent authentication token must indicate that the user
+ * authenticated with a password, not a biometric.
+ *
+ * Note that the IKeyMintDevice UNLOCKED_DEVICE_REQUIRED semantics are slightly different from
+ * the UNLOCKED_DEVICE_REQUIRED semantics enforced by keystore. Keystore handles device locking
+ * on a per-user basis. Because auth tokens do not contain an Android user ID, it's not
+ * possible to replicate the keystore enformcement logic in IKeyMintDevice. So from the
+ * IKeyMintDevice perspective, any user unlock unlocks all UNLOCKED_DEVICE_REQUIRED keys.
+ * Keystore will continue enforcing the per-user device locking.
+ *
+ * @param passwordOnly specifies whether the device must be unlocked with a password, rather
+ * than a biometric, before UNLOCKED_DEVICE_REQUIRED keys can be used.
+ *
+ * @param timestampToken is used by StrongBox implementations of IKeyMintDevice. It
+ * provides the StrongBox IKeyMintDevice with a fresh, MACed timestamp which it can use as the
+ * device-lock time, for future comparison against auth tokens when operations using
+ * UNLOCKED_DEVICE_REQUIRED keys are attempted. Unless the auth token timestamp is newer than
+ * the timestamp in the timestampToken, the device is still considered to be locked.
+ * Crucially, if a StrongBox IKeyMintDevice receives a deviceLocked() call with a timestampToken
+ * timestamp that is less than the timestamp in the last deviceLocked() call, it must ignore the
+ * new timestamp. TEE IKeyMintDevice implementations will receive an empty timestampToken (zero
+ * values and empty vectors) and should use their own clock as the device-lock time.
+ */
+ void deviceLocked(in boolean passwordOnly, in @nullable TimeStampToken timestampToken);
+
+ /**
+ * Called by client to notify the IKeyMintDevice that the device has left the early boot
+ * state, and that keys with the EARLY_BOOT_ONLY tag may no longer be used. All attempts to use
+ * an EARLY_BOOT_ONLY key after this method is called must fail with Error::INVALID_KEY_BLOB.
+ */
+ void earlyBootEnded();
}
diff --git a/security/keymint/aidl/android/hardware/security/keymint/IKeyMintOperation.aidl b/security/keymint/aidl/android/hardware/security/keymint/IKeyMintOperation.aidl
index 24960cc..8c49602 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/IKeyMintOperation.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/IKeyMintOperation.aidl
@@ -20,7 +20,7 @@
import android.hardware.security.keymint.HardwareAuthToken;
import android.hardware.security.keymint.KeyParameter;
import android.hardware.security.keymint.KeyParameterArray;
-import android.hardware.security.keymint.VerificationToken;
+import android.hardware.security.secureclock.TimeStampToken;
@VintfStability
interface IKeyMintOperation {
@@ -119,10 +119,9 @@
* @param input Data to be processed. Note that update() may or may not consume all of the data
* provided. See return value.
*
- * @param verificationToken Verification token, used to prove that another IKeymasterDevice HAL
- * has verified some parameters, and to deliver the other HAL's current timestamp, if
- * needed. If not provided, all fields must be initialized to zero and vectors must be
- * empty.
+ * @param inTimeStampToken timestamp token, certifies the freshness of an auth token in case
+ * the security domain of this KeyMint instance has a different clock than the
+ * authenticator issuing the auth token.
*
* @return error Returns ErrorCode encountered in keymint as service specific errors. See the
* ErrorCode enum in ErrorCode.aidl.
@@ -141,7 +140,7 @@
int update(in @nullable KeyParameterArray inParams,
in @nullable byte[] input,
in @nullable HardwareAuthToken inAuthToken,
- in @nullable VerificationToken inVerificationToken,
+ in @nullable TimeStampToken inTimeStampToken,
out @nullable KeyParameterArray outParams,
out @nullable ByteArray output);
@@ -241,9 +240,9 @@
*
* @param authToken Authentication token. Can be nullable if not provided.
*
- * @param verificationToken Verification token, used to prove that another IKeyMintDevice HAL
- * has verified some parameters, and to deliver the other HAL's current timestamp, if
- * needed. Can be nullable if not needed.
+ * @param inTimeStampToken timestamp token, certifies the freshness of an auth token in case
+ * the security domain of this KeyMint instance has a different clock than the
+ * authenticator issuing the auth token.
*
* @return outParams Any output parameters generated by finish().
*
@@ -252,7 +251,7 @@
byte[] finish(in @nullable KeyParameterArray inParams, in @nullable byte[] input,
in @nullable byte[] inSignature,
in @nullable HardwareAuthToken authToken,
- in @nullable VerificationToken inVerificationToken,
+ in @nullable TimeStampToken inTimeStampToken,
out @nullable KeyParameterArray outParams);
/**
diff --git a/security/keymint/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl b/security/keymint/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl
new file mode 100644
index 0000000..1b09e9d
--- /dev/null
+++ b/security/keymint/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.security.keymint;
+
+import android.hardware.security.keymint.MacedPublicKey;
+import android.hardware.security.keymint.ProtectedData;
+
+/**
+ * An IRemotelyProvisionedComponent is a secure-side component for which certificates can be
+ * remotely provisioned. It provides an interface for generating asymmetric key pairs and then
+ * creating a CertificateRequest that contains the generated public keys, plus other information to
+ * authenticate the request origin. The CertificateRequest can be sent to a server, which can
+ * validate the request and create certificates.
+ *
+ * This interface does not provide any way to use the generated and certified key pairs. It's
+ * intended to be implemented by a HAL service that does other things with keys (e.g. Keymint).
+ *
+ * The root of trust for secure provisioning is something called the "Boot Certificate Chain", or
+ * BCC. The BCC is a chain of public key certificates, represented as COSE_Sign1 objects containing
+ * COSE_Key representations of the public keys. The "root" of the BCC is a self-signed certificate
+ * for a device-unique public key, denoted DK_pub. All public keys in the BCC are device-unique. The
+ * public key from each certificate in the chain is used to sign the next certificate in the
+ * chain. The final, "leaf" certificate contains a public key, denoted KM_pub, whose corresponding
+ * private key, denoted KM_priv, is available for use by the IRemotelyProvisionedComponent.
+ *
+ * BCC Design
+ * ==========
+ *
+ * The BCC is designed to mirror the boot stages of a device, and to prove the content and integrity
+ * of each firmware image. In a proper BCC, each boot stage hashes its own private key with the code
+ * and any relevant configuration parameters of the next stage to produce a key pair for the next
+ * stage. Each stage also uses its own private key to sign the public key of the next stage,
+ * including in the certificate the hash of the next firmware stage, then loads the next stage,
+ * passing the private key and certificate to it in a manner that does not leak the private key to
+ * later boot stages. The BCC root key pair is generated by immutable code (e.g. ROM), from a
+ * device-unique secret. After the device-unique secret is used, it must be made unavailable to any
+ * later boot stage.
+ *
+ * In this way, booting the device incrementally builds a certificate chain that (a) identifies and
+ * validates the integrity of every stage and (b) contains a set of public keys that correspond to
+ * private keys, one known to each stage. Any stage can compute the secrets of all later stages
+ * (given the necessary input), but no stage can compute the secret of any preceding stage. Updating
+ * the firmware or configuration of any stage changes the key pair of that stage, and of all
+ * subsequent stages, and no attacker who compromised the previous version of the updated firmware
+ * can know or predict the post-update key pairs.
+ *
+ * The first BCC certificate is special because its contained public key, DK_pub, will never change,
+ * making it a permanent, device-unique identifier. Although the remaining keys in the BCC are also
+ * device-unique, they are not necessarily permanent, since they can change when the device software
+ * is updated.
+ *
+ * When the provisioning server receives a message signed by KM_priv and containing a BCC that
+ * chains from DK_pub to KM_pub, it can be certain that (barring vulnerabilities in some boot
+ * stage), the CertificateRequest came from the device associated with DK_pub, running the specific
+ * software identified by the certificates in the BCC. If the server has some mechanism for knowing
+ * which the DK_pub values of "valid" devices, it can determine whether signing certificates is
+ * appropriate.
+ *
+ * Degenerate BCCs
+ * ===============
+ *
+ * While a proper BCC, as described above, reflects the complete boot sequence from boot ROM to the
+ * secure area image of the IRemotelyProvisionedComponent, it's also possible to use a "degenerate"
+ * BCC which consists only of a single, self-signed certificate containing the public key of a
+ * hardware-bound key pair. This is an appopriate solution for devices which haven't implemented
+ * everything necessary to produce a proper BCC, but can derive a unique key pair in the secure
+ * area. In this degenerate case, DK_pub is the same as KM_pub.
+ *
+ * BCC Privacy
+ * ===========
+ *
+ * Because the BCC constitutes an unspoofable, device-unique identifier, special care is taken to
+ * prevent its availability to entities who may wish to track devices. Two precautions are taken:
+ *
+ * 1. The BCC is never exported from the IRemotelyProvisionedComponent except in encrypted
+ * form. The portion of the CertificateRequest that contains the BCC is encrypted using an
+ * Endpoint Encryption Key (EEK). The EEK is provided in the form of a certificate chain whose
+ * root must be pre-provisioned into the secure area (hardcoding the roots into the secure area
+ * firmware image is a recommended approach). Multiple roots may be provisioned. If the provided
+ * EEK does not chain back to this already-known root, the IRemotelyProvisionedComponent must
+ * reject it.
+ *
+ * 2. Precaution 1 above ensures that only an entity with a valid EEK private key can decrypt the
+ * BCC. To make it feasible to build a provisioning server which cannot use the BCC to track
+ * devices, the CertificateRequest is structured so that the server can be partitioned into two
+ * components. The "decrypter" decrypts the BCC, verifies DK_pub and the device's right to
+ * receive provisioned certificates, but does not see the public keys to be signed or the
+ * resulting certificates. The "certifier" gets informed of the results of the decrypter's
+ * validation and sees the public keys to be signed and resulting certificates, but does not see
+ * the BCC.
+ *
+ * Test Mode
+ * =========
+ *
+ * The IRemotelyProvisionedComponent supports a test mode, allowing the generation of test key pairs
+ * and test CertificateRequests. Test keys/requests are annotated as such, and the BCC used for test
+ * CertificateRequests must contain freshly-generated keys, not the real BCC key pairs.
+ */
+@VintfStability
+interface IRemotelyProvisionedComponent {
+ const int STATUS_FAILED = 1;
+ const int STATUS_INVALID_MAC = 2;
+ const int STATUS_PRODUCTION_KEY_IN_TEST_REQUEST = 3;
+ const int STATUS_TEST_KEY_IN_PRODUCTION_REQUEST = 4;
+ const int STATUS_INVALID_EEK = 5;
+
+ /**
+ * generateKeyPair generates a new ECDSA P-256 key pair that can be certified. Note that this
+ * method only generates ECDSA P-256 key pairs, but the interface can be extended to add methods
+ * for generating keys for other algorithms, if necessary.
+ *
+ * @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.
+ *
+ * @param out MacedPublicKey macedPublicKey contains the public key of the generated key pair,
+ * MACed so that generateCertificateRequest can easily verify, without the
+ * privateKeyHandle, that the contained public key is for remote certification.
+ *
+ * @return data representing a handle to the private key. The format is implementation-defined,
+ * but note that specific services may define a required format.
+ */
+ byte[] generateEcdsaP256KeyPair(in boolean testMode, out MacedPublicKey macedPublicKey);
+
+ /**
+ * generateCertificateRequest creates a certificate request to be sent to the provisioning
+ * server.
+ *
+ * @param in boolean testMode indicates whether the generated certificate request is for testing
+ * only.
+ *
+ * @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.
+ *
+ * If testMode is true, the keysToCertify array must contain only keys flagged as test
+ * keys. Otherwise, the method must return STATUS_PRODUCTION_KEY_IN_TEST_REQUEST.
+ *
+ * If testMode is false, the keysToCertify array must not contain any keys flagged as
+ * test keys. Otherwise, the method must return STATUS_TEST_KEY_IN_PRODUCTION_REQUEST.
+ *
+ * @param in endpointEncryptionKey contains an X25519 public key which will be used to encrypt
+ * the BCC. For flexibility, this is represented as a certificate chain, represented as a
+ * CBOR array of COSE_Sign1 objects, ordered from root to leaf. The leaf contains the
+ * X25519 encryption key, each other element is an Ed25519 key signing the next in the
+ * chain. The root is self-signed.
+ *
+ * EekChain = [ + SignedSignatureKey, SignedEek ]
+ *
+ * SignedSignatureKey = [ // COSE_Sign1
+ * protected: bstr .cbor {
+ * 1 : -8, // Algorithm : EdDSA
+ * },
+ * unprotected: bstr .size 0
+ * payload: bstr .cbor SignatureKey,
+ * signature: bstr PureEd25519(.cbor SignatureKeySignatureInput)
+ * ]
+ *
+ * SignatureKey = { // COSE_Key
+ * 1 : 1, // Key type : Octet Key Pair
+ * 3 : -8, // Algorithm : EdDSA
+ * -1 : 6, // Curve : Ed25519
+ * -2 : bstr // Ed25519 public key
+ * }
+ *
+ * SignatureKeySignatureInput = [
+ * context: "Signature1",
+ * body_protected: bstr .cbor {
+ * 1 : -8, // Algorithm : EdDSA
+ * },
+ * external_aad: bstr .size 0,
+ * payload: bstr .cbor SignatureKey
+ * ]
+ *
+ * SignedEek = [ // COSE_Sign1
+ * protected: bstr .cbor {
+ * 1 : -8, // Algorithm : EdDSA
+ * },
+ * unprotected: bstr .size 0
+ * payload: bstr .cbor Eek,
+ * signature: bstr PureEd25519(.cbor EekSignatureInput)
+ * ]
+ *
+ * Eek = { // COSE_Key
+ * 1 : 1, // Key type : Octet Key Pair
+ * 2 : bstr // KID : EEK ID
+ * 3 : -25, // Algorithm : ECDH-ES + HKDF-256
+ * -1 : 4, // Curve : X25519
+ * -2 : bstr // Ed25519 public key
+ * }
+ *
+ * EekSignatureInput = [
+ * context: "Signature1",
+ * body_protected: bstr .cbor {
+ * 1 : -8, // Algorithm : EdDSA
+ * },
+ * external_aad: bstr .size 0,
+ * payload: bstr .cbor Eek
+ * ]
+ *
+ * If the contents of endpointEncryptionKey do not match the SignedEek structure above,
+ * the method must return STATUS_INVALID_EEK.
+ *
+ * If testMode is true, the method must ignore the length and content of the signatures
+ * in the chain, which implies that it must not attempt to validate the signature.
+ *
+ * If testMode is false, the method must validate the chain signatures, and must verify
+ * that the public key in the root certifictate is in its pre-configured set of
+ * authorized EEK root keys. If the public key is not in the database, or if signature
+ * verification fails, the method must return STATUS_INVALID_EEK.
+ *
+ * @param in challenge contains a byte string from the provisioning server that must be signed
+ * by the secure area. See the description of the 'signature' output parameter for
+ * details.
+ *
+ * @param out keysToSignMac contains the MAC of KeysToSign in the CertificateRequest
+ * structure. Specifically, it contains:
+ *
+ * HMAC-256(EK_mac, .cbor KeysToMacStructure)
+ *
+ * Where EK_mac is an ephemeral MAC key, found in ProtectedData (see below). The MACed
+ * data is the "tag" field of a COSE_Mac0 structure like:
+ *
+ * MacedKeys = [ // COSE_Mac0
+ * protected : bstr .cbor {
+ * 1 : 5, // Algorithm : HMAC-256
+ * },
+ * unprotected : bstr .size 0,
+ * // Payload is PublicKeys from keysToSign argument, in provided order.
+ * payload: bstr .cbor [ * PublicKey ],
+ * tag: bstr
+ * ]
+ *
+ * KeysToMacStructure = [
+ * context : "MAC0",
+ * protected : bstr .cbor { 1 : 5 }, // Algorithm : HMAC-256
+ * external_aad : bstr .size 0,
+ * // Payload is PublicKeys from keysToSign argument, in provided order.
+ * payload : bstr .cbor [ * PublicKey ]
+ * ]
+ *
+ * @param out ProtectedData contains the encrypted BCC and the ephemeral MAC key used to
+ * authenticate the keysToSign (see keysToSignMac output argument).
+ */
+ void generateCertificateRequest(in boolean testMode, in MacedPublicKey[] keysToSign,
+ in byte[] endpointEncryptionCertChain, in byte[] challenge, out byte[] keysToSignMac,
+ out ProtectedData protectedData);
+}
diff --git a/security/keymint/aidl/android/hardware/security/keymint/KeyCharacteristics.aidl b/security/keymint/aidl/android/hardware/security/keymint/KeyCharacteristics.aidl
index 0801868..edd4d8f 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/KeyCharacteristics.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/KeyCharacteristics.aidl
@@ -17,25 +17,20 @@
package android.hardware.security.keymint;
import android.hardware.security.keymint.KeyParameter;
+import android.hardware.security.keymint.SecurityLevel;
/**
- * KeyCharacteristics defines the attributes of a key, including cryptographic parameters, and usage
- * restrictions. It consits of two vectors of KeyParameters, one for "softwareEnforced" attributes
- * and one for "hardwareEnforced" attributes.
+ * KeyCharacteristics defines the attributes of a key that are enforced by KeyMint, and the security
+ * level (see SecurityLevel.aidl) of that enforcement.
*
- * KeyCharacteristics objects are returned by generateKey, importKey, importWrappedKey and
- * getKeyCharacteristics. The IKeyMintDevice secure environment is responsible for allocating the
- * parameters, all of which are Tags with associated values, to the correct vector. The
- * hardwareEnforced vector must contain only those attributes which are enforced by secure hardware.
- * All others should be in the softwareEnforced vector. See the definitions of individual Tag enums
- * for specification of which must be hardware-enforced, which may be software-enforced and which
- * must never appear in KeyCharacteristics.
+ * The `generateKey` `importKey` and `importWrappedKey` methods each return an array of
+ * KeyCharacteristics, specifying the security levels of enforcement and the authorizations
+ * enforced. Note that enforcement at a given security level means that the semantics of the tag
+ * and value are fully enforced. See the definition of individual tags for specifications of what
+ * must be enforced.
*/
@VintfStability
parcelable KeyCharacteristics {
- /* TODO(seleneh) get rid of the software enforced in keymint. replace hardware enforced with
- * tee enforced and strongbox enforced.
- */
- KeyParameter[] softwareEnforced;
- KeyParameter[] hardwareEnforced;
+ SecurityLevel securityLevel;
+ KeyParameter[] authorizations;
}
diff --git a/security/keymint/aidl/android/hardware/security/keymint/KeyCreationResult.aidl b/security/keymint/aidl/android/hardware/security/keymint/KeyCreationResult.aidl
new file mode 100644
index 0000000..69bec2d7
--- /dev/null
+++ b/security/keymint/aidl/android/hardware/security/keymint/KeyCreationResult.aidl
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2021 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.security.keymint;
+
+import android.hardware.security.keymint.Certificate;
+import android.hardware.security.keymint.KeyCharacteristics;
+
+/**
+ * This structure is returned when a new key is created with generateKey(), importKey() or
+ * importWrappedKey().
+ */
+@VintfStability
+parcelable KeyCreationResult {
+ /**
+ * `keyBlob` is an descriptor of the generated/imported key key.
+ */
+ byte[] keyBlob;
+
+ /**
+ * `keyCharacteristics` is a description of the generated key in the form of authorization lists
+ * associated with security levels. The rules that IKeyMintDevice implementations must use for
+ * deciding whether a given tag from `keyParams` argument to the generation/import method should
+ * be returned in `keyCharacteristics` are:
+ *
+ * - If the semantics of the tag are fully enforced by the IKeyMintDevice, without any
+ * assistance from components running at other security levels, it should be included in an
+ * entry with the SecurityLevel of the IKeyMintDevice.
+ * - If the semantics of the tag are fully enforced, but with the assistance of components
+ * running at another SecurityLevel, it should be included in an entry with the minimum
+ * SecurityLevel of the involved components. For example if a StrongBox IKeyMintDevice relies
+ * on a TEE to validate biometric authentication, biometric authentication tags go in an entry
+ * with SecurityLevel::TRUSTED_ENVIRONMENT.
+ * - If the semantics are not enforced by KeyMint at all, SecurityLevel::KEYSTORE is used to
+ * indicate that Keystore should enforce. Note that in Keymaster (predecessor to KeyMint),
+ * these tags would have been in SecurityLevel::SOFTWARE.
+ */
+ KeyCharacteristics[] keyCharacteristics;
+
+ /**
+ * If the generated/imported key is an asymmetric key, `certificateChain` will contain a chain
+ * of one or more certificates. If the key parameters provided to the generate/import method
+ * contains Tag::ATTESTATION_CHALLENGE the first certificate will contain an attestation
+ * extension, and will be signed by a factory-installed attestation key and followed by a chain
+ * of certificates leading to an authoritative root. If there is no attestation challenge, only
+ * one certificate will be returned, and it will be self-signed or contain a fake signature,
+ * depending on whether the key has KeyPurpose::SIGN. If the generated key is symmetric,
+ * certificateChain will be empty.
+ */
+ Certificate[] certificateChain;
+}
diff --git a/security/keymint/aidl/android/hardware/security/keymint/KeyMintHardwareInfo.aidl b/security/keymint/aidl/android/hardware/security/keymint/KeyMintHardwareInfo.aidl
index d3d7368..2fcaf4c 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/KeyMintHardwareInfo.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/KeyMintHardwareInfo.aidl
@@ -22,6 +22,7 @@
* KeyMintHardwareInfo is the hardware information returned by calling KeyMint getHardwareInfo()
*/
@VintfStability
+@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true)
parcelable KeyMintHardwareInfo {
/**
* Implementation version of the keymint hardware. The version number is implementation
@@ -44,4 +45,11 @@
* same author.
*/
@utf8InCpp String keyMintAuthorName;
+
+ /* The timestampTokenRequired is a boolean flag, which when true reflects that IKeyMintDevice
+ * instance will expect a valid TimeStampToken with various operations. This will typically
+ * required by the StrongBox implementations that generally don't have secure clock hardware to
+ * generate timestamp tokens.
+ */
+ boolean timestampTokenRequired;
}
diff --git a/security/keymint/aidl/android/hardware/security/keymint/KeyPurpose.aidl b/security/keymint/aidl/android/hardware/security/keymint/KeyPurpose.aidl
index cb4682e..978a027 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/KeyPurpose.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/KeyPurpose.aidl
@@ -16,12 +16,11 @@
package android.hardware.security.keymint;
-
/**
* Possible purposes of a key (or pair).
*/
@VintfStability
-@Backing(type = "int")
+@Backing(type="int")
enum KeyPurpose {
/* Usable with RSA, EC and AES keys. */
ENCRYPT = 0,
@@ -39,5 +38,10 @@
/* Usable with wrapping keys. */
WRAP_KEY = 5,
- /* TODO(seleneh) add AGREE_KEY and ATTEST_KEY and their corresponding codes and tests later*/
+ /* Key Agreement, usable with EC keys. */
+ AGREE_KEY = 6,
+
+ /* Usable as an attestation signing key. Keys with this purpose must not have any other
+ * purpose. */
+ ATTEST_KEY = 7,
}
diff --git a/security/keymint/aidl/android/hardware/security/keymint/MacedPublicKey.aidl b/security/keymint/aidl/android/hardware/security/keymint/MacedPublicKey.aidl
new file mode 100644
index 0000000..da85a50
--- /dev/null
+++ b/security/keymint/aidl/android/hardware/security/keymint/MacedPublicKey.aidl
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.security.keymint;
+
+/**
+ * MacedPublicKey contains a CBOR-encoded public key, MACed by an IRemotelyProvisionedComponent, to
+ * prove that the key pair was generated by that component.
+ */
+@VintfStability
+parcelable MacedPublicKey {
+ /**
+ * key is a COSE_Mac0 structure containing the new public key. It's MACed by a key available
+ * only to the secure environment, as proof that the public key was generated by that
+ * environment. In CDDL, assuming the contained key is an Ed25519 public key:
+ *
+ * MacedPublicKey = [ // COSE_Mac0
+ * protected: bstr .cbor { 1 : 5}, // Algorithm : HMAC-256
+ * unprotected: bstr .size 0,
+ * payload : bstr .cbor PublicKey,
+ * tag : bstr HMAC-256(K_mac, MAC_structure)
+ * ]
+ *
+ * PublicKey = { // COSE_Key
+ * 1 : 1, // Key type : octet key pair
+ * 3 : -8 // Algorithm : EdDSA
+ * -1 : 6, // Curve : Ed25519
+ * -2 : bstr // X coordinate, little-endian
+ * ? -70000 : nil // Presence indicates this is a test key. If set, K_mac is
+ * // all zeros.
+ * },
+ *
+ * MAC_structure = [
+ * context : "MAC0",
+ * protected : bstr .cbor { 1 : 5 },
+ * external_aad : bstr .size 0,
+ * payload : bstr .cbor PublicKey
+ * ]
+ *
+ * if a non-Ed25519 public key were contained, the contents of the PublicKey map would change a
+ * little; see RFC 8152 for details.
+ */
+ byte[] macedKey;
+}
diff --git a/security/keymint/aidl/android/hardware/security/keymint/ProtectedData.aidl b/security/keymint/aidl/android/hardware/security/keymint/ProtectedData.aidl
new file mode 100644
index 0000000..1ec3bf0
--- /dev/null
+++ b/security/keymint/aidl/android/hardware/security/keymint/ProtectedData.aidl
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2021 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.security.keymint;
+
+/**
+ * ProtectedData contains the encrypted BCC and the ephemeral MAC key used to
+ * authenticate the keysToSign (see keysToSignMac output argument).
+ */
+@VintfStability
+parcelable ProtectedData {
+ /**
+ * ProtectedData is a COSE_Encrypt structure, specified by the following CDDL
+ *
+ * ProtectedData = [ // COSE_Encrypt
+ * protected: bstr .cbor {
+ * 1 : 3 // Algorithm : AES-GCM 256
+ * },
+ * unprotected: {
+ * 5 : bstr .size 12 // IV
+ * },
+ * ciphertext: bstr, // AES-GCM-128(K, .cbor ProtectedDataPayload)
+ * recipients : [
+ * [ // COSE_Recipient
+ * protected : bstr .cbor {
+ * 1 : -25 // Algorithm : ECDH-ES + HKDF-256
+ * },
+ * unprotected : {
+ * -1 : { // COSE_Key
+ * 1 : 1, // Key type : Octet Key Pair
+ * -1 : 4, // Curve : X25519
+ * -2 : bstr // Sender X25519 public key
+ * }
+ * 4 : bstr, // KID : EEK ID
+ * },
+ * ciphertext : nil
+ * ]
+ * ]
+ * ]
+ *
+ * K = HKDF-256(ECDH(EEK_pub, Ephemeral_priv), Context)
+ *
+ * Context = [ // COSE_KDF_Context
+ * AlgorithmID : 3 // AES-GCM 256
+ * PartyUInfo : [
+ * identity : bstr "client"
+ * nonce : bstr .size 0,
+ * other : bstr // Ephemeral pubkey
+ * ],
+ * PartyVInfo : [
+ * identity : bstr "server",
+ * nonce : bstr .size 0,
+ * other : bstr // EEK pubkey
+ * ],
+ * SuppPubInfo : [
+ * 128, // Output key length
+ * protected : bstr .size 0
+ * ]
+ * ]
+ *
+ * ProtectedDataPayload [
+ * SignedMac,
+ * Bcc,
+ * ]
+ *
+ * SignedMac = [ // COSE_Sign1
+ * bstr .cbor { // Protected params
+ * 1 : -8, // Algorithm : EdDSA
+ * },
+ * bstr .size 0, // Unprotected params
+ * bstr .size 32, // MAC key
+ * bstr PureEd25519(DK_priv, .cbor SignedMac_structure)
+ * ]
+ *
+ * SignedMac_structure = [
+ * "Signature1",
+ * bstr .cbor { // Protected params
+ * 1 : -8, // Algorithm : EdDSA
+ * },
+ * bstr .cbor SignedMacAad
+ * bstr .size 32 // MAC key
+ * ]
+ *
+ * SignedMacAad = [
+ * challenge : bstr,
+ * DeviceInfo
+ * ]
+ *
+ * Bcc = [
+ * PubKey, // DK_pub
+ * + BccEntry, // Root -> leaf (KM_pub)
+ * ]
+ *
+ * BccPayload = { // CWT
+ * 1 : tstr, // Issuer
+ * 2 : tstr, // Subject
+ * // See the Open Profile for DICE for details on these fields.
+ * ? -4670545 : bstr, // Code Hash
+ * ? -4670546 : bstr, // Code Descriptor
+ * ? -4670547 : bstr, // Configuration Hash
+ * ? -4670548 : bstr .cbor { // Configuration Descriptor
+ * ? -70002 : tstr, // Component name
+ * ? -70003 : int, // Firmware version
+ * ? -70004 : null, // Resettable
+ * },
+ * ? -4670549 : bstr, // Authority Hash
+ * ? -4670550 : bstr, // Authority Descriptor
+ * ? -4670551 : bstr, // Mode
+ * -4670552 : bstr .cbor PubKey // Subject Public Key
+ * -4670553 : bstr // Key Usage
+ * }
+ *
+ * BccEntry = [ // COSE_Sign1
+ * protected: bstr .cbor {
+ * 1 : -8, // Algorithm : EdDSA
+ * },
+ * unprotected: bstr .size 0,
+ * payload: bstr .cbor BccPayload,
+ * // First entry in the chain is signed by DK_pub, the others are each signed by their
+ * // immediate predecessor. See RFC 8032 for signature representation.
+ * signature: bstr .cbor PureEd25519(SigningKey, bstr .cbor BccEntryInput)
+ * ]
+ *
+ * PubKey = { // COSE_Key
+ * 1 : 1, // Key type : octet key pair
+ * 3 : -8, // Algorithm : EdDSA
+ * 4 : 2, // Ops: Verify
+ * -1 : 6, // Curve : Ed25519
+ * -2 : bstr // X coordinate, little-endian
+ * }
+ *
+ * BccEntryInput = [
+ * context: "Signature1",
+ * protected: bstr .cbor {
+ * 1 : -8, // Algorithm : EdDSA
+ * },
+ * external_aad: bstr .size 0,
+ * payload: bstr .cbor BccPayload
+ * ]
+ *
+ * DeviceInfo = {
+ * ? "brand" : tstr,
+ * ? "manufacturer" : tstr,
+ * ? "product" : tstr,
+ * ? "model" : tstr,
+ * ? "board" : tstr,
+ * ? "vb_state" : "green" / "yellow" / "orange",
+ * ? "bootloader_state" : "locked" / "unlocked",
+ * ? "os_version" : tstr,
+ * ? "system_patch_level" : uint, // YYYYMMDD
+ * ? "boot_patch_level" : uint, // YYYYMMDD
+ * ? "vendor_patch_level" : uint, // YYYYMMDD
+ * }
+ */
+ byte[] protectedData;
+}
diff --git a/security/keymint/aidl/android/hardware/security/keymint/SecurityLevel.aidl b/security/keymint/aidl/android/hardware/security/keymint/SecurityLevel.aidl
index 10363e9..c63859c 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/SecurityLevel.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/SecurityLevel.aidl
@@ -17,16 +17,59 @@
package android.hardware.security.keymint;
/**
- * Device security levels.
+ * Device security levels. These enum values are used in two ways:
+ *
+ * 1. Returned from IKeyMintDevice::getHardwareInfo to identify the security level of the
+ * IKeyMintDevice. This characterizes the sort of environment in which the KeyMint
+ * implementation runs, and therefore the security of its operations.
+ *
+ * 2. Associated with individual KeyMint authorization Tags in KeyCharacteristics or in attestation
+ * certificates. This specifies the security level of the weakest environment involved in
+ * enforcing that particular tag, i.e. the sort of security environment an attacker would have
+ * to subvert in order to break the enforcement of that tag.
*/
@VintfStability
@Backing(type="int")
enum SecurityLevel {
+ /**
+ * The SOFTWARE security level represents a KeyMint implementation that runs in an Android
+ * process, or a tag enforced by such an implementation. An attacker who can compromise that
+ * process, or obtain root, or subvert the kernel on the device can defeat it.
+ *
+ * Note that the distinction between SOFTWARE and KEYSTORE is only relevant on-device. For
+ * attestation purposes, these categories are combined into the software-enforced authorization
+ * list.
+ */
SOFTWARE = 0,
+
+ /**
+ * The TRUSTED_ENVIRONMENT security level represents a KeyMint implementation that runs in an
+ * Android process, or a tag enforced by such an implementation. An attacker who completely
+ * compromises Android, including the Linux kernel, does not have the ability to subvert it. At
+ * attacker who can find an exploit that gains them control of the trusted environment, or who
+ * has access to the physical device and can mount a sophisticated hardware attack, may be able
+ * to defeat it.
+ */
TRUSTED_ENVIRONMENT = 1,
/**
- * STRONGBOX specifies that the secure hardware satisfies the requirements specified in CDD
- * 9.11.2.
+ * The STRONGBOX security level represents a KeyMint implementation that runs in security
+ * hardware that satisfies the requirements specified in CDD 9.11.2. Roughly speaking, these
+ * are discrete, security-focus computing environments that are hardened against physical and
+ * side channel attack, and have had their security formally validated by a competent
+ * penetration testing lab.
*/
STRONGBOX = 2,
+
+ /**
+ * KeyMint implementations must never return the KEYSTORE security level from getHardwareInfo.
+ * It is used to specify tags that are not enforced by the IKeyMintDevice, but are instead
+ * to be enforced by Keystore. An attacker who can subvert the keystore process or gain root or
+ * subvert the kernel can prevent proper enforcement of these tags.
+ *
+ *
+ * Note that the distinction between SOFTWARE and KEYSTORE is only relevant on-device. When
+ * KeyMint generates an attestation certificate, these categories are combined into the
+ * software-enforced authorization list.
+ */
+ KEYSTORE = 100
}
diff --git a/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl b/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl
index 3bc3f16..4f58cbe 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl
@@ -27,7 +27,7 @@
* data are stored in KeyParameter.
*/
@VintfStability
-@Backing(type = "int")
+@Backing(type="int")
enum Tag {
/**
* Tag::INVALID should never be set. It means you hit an error.
@@ -82,7 +82,6 @@
*/
BLOCK_MODE = (2 << 28) /* TagType:ENUM_REP */ | 4,
-
/**
* Tag::DIGEST specifies the digest algorithms that may be used with the key to perform signing
* and verification operations. This tag is relevant to RSA, ECDSA and HMAC keys. Possible
@@ -188,6 +187,22 @@
INCLUDE_UNIQUE_ID = (7 << 28) /* TagType:BOOL */ | 202,
/**
+ * Tag::RSA_OAEP_MGF_DIGEST specifies the MGF1 digest algorithms that may be used with
+ * RSA encryption/decryption with OAEP padding. If the key characteristics supports OAEP
+ * and this tag is absent then SHA1 digest is selected by default for MGF1.
+ *
+ * This tag is repeatable for key generation/import. If this tag is present in the key
+ * characteristics with one or more values from @4.0::Digest, then for RSA cipher
+ * operations with OAEP Padding, the caller must specify a digest in the additionalParams
+ * argument of begin operation. If this tag is missing or the specified digest is not in
+ * the digests associated with the key then begin operation must fail with
+ * ErrorCode::INCOMPATIBLE_MGF_DIGEST.
+ *
+ * Must be hardware-enforced.
+ */
+ RSA_OAEP_MGF_DIGEST = (2 << 28) /* TagType:ENUM_REP */ | 203,
+
+ /**
* TODO(seleneh) this tag needs to be deleted from all codes.
*
* Tag::BLOB_USAGE_REQUIREMENTS specifies the necessary system environment conditions for the
@@ -317,6 +332,35 @@
MAX_USES_PER_BOOT = (3 << 28) /* TagType:UINT */ | 404,
/**
+ * Tag::USAGE_COUNT_LIMIT specifies the number of times that a key may be used. This can be
+ * used to limit the use of a key.
+ *
+ * The value is a 32-bit integer representing the current number of attempts left.
+ *
+ * When initializing a limited use key, the value of this tag represents the maximum usage
+ * limit for that key. After the key usage is exhausted, the key blob should be invalidated by
+ * finish() call. Any subsequent attempts to use the key must result in a failure with
+ * ErrorCode::INVALID_KEY_BLOB returned by IKeyMintDevice.
+ *
+ * At this point, if the caller specifies count > 1, it is not expected that any TEE will be
+ * able to enforce this feature in the hardware due to limited resources of secure
+ * storage. In this case, the tag with the value of maximum usage must be added to the key
+ * characteristics with SecurityLevel::KEYSTORE by the IKeyMintDevice.
+ *
+ * On the other hand, if the caller specifies count = 1, some TEEs may have the ability
+ * to enforce this feature in the hardware with its secure storage. If the IKeyMintDevice
+ * implementation can enforce this feature, the tag with value = 1 must be added to the key
+ * characteristics with the SecurityLevel of the IKeyMintDevice. If the IKeyMintDevice can't
+ * enforce this feature even when the count = 1, the tag must be added to the key
+ * characteristics with the SecurityLevel::KEYSTORE.
+ *
+ * When the key is attested, this tag with the same value must also be added to the attestation
+ * record. This tag must have the same SecurityLevel as the tag that is added to the key
+ * characteristics.
+ */
+ USAGE_COUNT_LIMIT = (3 << 28) | 405, /* TagType:UINT */
+
+ /**
* Tag::USER_ID specifies the ID of the Android user that is permitted to use the key.
*
* Must not be hardware-enforced.
@@ -452,7 +496,8 @@
*/
TRUSTED_USER_PRESENCE_REQUIRED = (7 << 28) /* TagType:BOOL */ | 507,
- /** Tag::TRUSTED_CONFIRMATION_REQUIRED is only applicable to keys with KeyPurpose SIGN, and
+ /**
+ * Tag::TRUSTED_CONFIRMATION_REQUIRED is only applicable to keys with KeyPurpose SIGN, and
* specifies that this key must not be usable unless the user provides confirmation of the data
* to be signed. Confirmation is proven to keyMint via an approval token. See
* CONFIRMATION_TOKEN, as well as the ConfirmatinUI HAL.
@@ -496,10 +541,10 @@
/**
* Tag::APPLICATION_DATA. When provided to generateKey or importKey, this tag specifies data
- * that is necessary during all uses of the key. In particular, calls to exportKey() and
- * getKeyCharacteristics() must provide the same value to the appData parameter, and calls to
- * begin must provide this tag and the same associated data as part of the inParams set. If
- * the correct data is not provided, the method must return ErrorCode::INVALID_KEY_BLOB.
+ * that is necessary during all uses of the key. In particular, calls to begin() and
+ * exportKey() must provide the same value to the appData parameter, and calls to begin must
+ * provide this tag and the same associated data as part of the inParams set. If the correct
+ * data is not provided, the method must return ErrorCode::INVALID_KEY_BLOB.
*
* The content of this tag msut be bound to the key cryptographically, meaning it must not be
* possible for an adversary who has access to all of the secure world secrets but does not have
@@ -888,4 +933,35 @@
* Must never appear in KeyCharacteristics.
*/
CONFIRMATION_TOKEN = (9 << 28) /* TagType:BYTES */ | 1005,
+
+ /**
+ * Tag::CERTIFICATE_SERIAL specifies the serial number to be assigned to the
+ * attestation certificate to be generated for the given key. This parameter should only
+ * be passed to keyMint in the attestation parameters during generateKey() and importKey().
+ */
+ CERTIFICATE_SERIAL = (8 << 28) /* TagType:BIGNUM */ | 1006,
+
+ /**
+ * Tag::CERTIFICATE_SUBJECT the certificate subject. The value is a DER encoded X509 NAME.
+ * This value is used when generating a self signed certificates. This tag may be specified
+ * during generateKey and importKey. If not provided the subject name shall default to
+ * <TODO default subject here>.
+ */
+ CERTIFICATE_SUBJECT = (9 << 28) /* TagType:BYTES */ | 1007,
+
+ /**
+ * Tag::CERTIFICATE_NOT_BEFORE the beginning of the validity of the certificate in UNIX epoch
+ * time in seconds. This value is used when generating attestation or self signed certificates.
+ * ErrorCode::MISSING_NOT_BEFORE must be returned if this tag is not provided if this tag is
+ * not provided to generateKey or importKey.
+ */
+ CERTIFICATE_NOT_BEFORE = (6 << 28) /* TagType:DATE */ | 1008,
+
+ /**
+ * Tag::CERTIFICATE_NOT_AFTER the end of the validity of the certificate in UNIX epoch
+ * time in seconds. This value is used when generating attestation or self signed certificates.
+ * ErrorCode::MISSING_NOT_AFTER must be returned if this tag is not provided to generateKey
+ * or importKey.
+ */
+ CERTIFICATE_NOT_AFTER = (6 << 28) /* TagType:DATE */ | 1009,
}
diff --git a/security/keymint/aidl/android/hardware/security/keymint/VerificationToken.aidl b/security/keymint/aidl/android/hardware/security/keymint/VerificationToken.aidl
deleted file mode 100644
index f76e6a8..0000000
--- a/security/keymint/aidl/android/hardware/security/keymint/VerificationToken.aidl
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hardware.security.keymint;
-
-import android.hardware.security.keymint.SecurityLevel;
-import android.hardware.security.keymint.Timestamp;
-
-/**
- * VerificationToken instances are used for secure environments to authenticate one another.
- *
- * This version of the parcelable currently don't use the parametersVerified field since it's not
- * needed for time-based verification. This can be added in a later version, if needed.
- */
-@VintfStability
-parcelable VerificationToken {
- /**
- * The operation handle, used to ensure freshness.
- */
- long challenge;
-
- /**
- * The current time of the secure environment that generates the VerificationToken. This can be
- * checked against auth tokens generated by the same secure environment, which avoids needing to
- * synchronize clocks.
- */
- Timestamp timestamp;
-
- /**
- * SecurityLevel of the secure environment that generated the token.
- */
- SecurityLevel securityLevel;
-
- /**
- * 32-byte HMAC-SHA256 of the above values, computed as:
- *
- * HMAC(H,
- * "Auth Verification" || challenge || timestamp || securityLevel)
- *
- * where:
- *
- * ``HMAC'' is the shared HMAC key (see computeSharedHmac() in IKeyMint).
- *
- * ``||'' represents concatenation
- *
- * The representation of challenge and timestamp is as 64-bit unsigned integers in big-endian
- * order. securityLevel is represented as a 32-bit unsigned integer in big-endian order.
- */
- byte[] mac;
-}
diff --git a/security/keymint/aidl/default/Android.bp b/security/keymint/aidl/default/Android.bp
index 79697c4..e160548 100644
--- a/security/keymint/aidl/default/Android.bp
+++ b/security/keymint/aidl/default/Android.bp
@@ -2,25 +2,57 @@
name: "android.hardware.security.keymint-service",
relative_install_path: "hw",
init_rc: ["android.hardware.security.keymint-service.rc"],
- vintf_fragments: ["android.hardware.security.keymint-service.xml"],
+ vintf_fragments: [
+ "android.hardware.security.keymint-service.xml",
+ "android.hardware.security.sharedsecret-service.xml",
+ "android.hardware.security.secureclock-service.xml",
+ ],
vendor: true,
cflags: [
"-Wall",
"-Wextra",
],
shared_libs: [
- "android.hardware.security.keymint-unstable-ndk_platform",
+ "android.hardware.security.keymint-V1-ndk_platform",
+ "android.hardware.security.sharedsecret-V1-ndk_platform",
+ "android.hardware.security.secureclock-V1-ndk_platform",
"libbase",
"libbinder_ndk",
- "libcppbor",
+ "libcppbor_external",
"libcrypto",
"libkeymaster_portable",
"libkeymint",
"liblog",
"libpuresoftkeymasterdevice",
+ "libremote_provisioner",
"libutils",
],
srcs: [
"service.cpp",
],
}
+
+cc_library {
+ name: "libremote_provisioner",
+ vendor_available: true,
+ static_libs: [
+ "libkeymint_remote_prov_support",
+ ],
+ shared_libs: [
+ "android.hardware.security.keymint-V1-ndk_platform",
+ "libbinder_ndk",
+ "libcppbor_external",
+ "libcppcose",
+ "libcrypto",
+ "libkeymaster_portable",
+ "libkeymint",
+ "liblog",
+ "libpuresoftkeymasterdevice",
+ ],
+ export_include_dirs: [
+ ".",
+ ],
+ srcs: [
+ "RemotelyProvisionedComponent.cpp",
+ ],
+}
diff --git a/security/keymint/aidl/default/RemotelyProvisionedComponent.cpp b/security/keymint/aidl/default/RemotelyProvisionedComponent.cpp
new file mode 100644
index 0000000..f2651fb
--- /dev/null
+++ b/security/keymint/aidl/default/RemotelyProvisionedComponent.cpp
@@ -0,0 +1,430 @@
+/*
+ * Copyright (C) 2021 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 "RemotelyProvisionedComponent.h"
+
+#include <assert.h>
+#include <variant>
+
+#include <cppbor.h>
+#include <cppbor_parse.h>
+
+#include <KeyMintUtils.h>
+#include <cppcose/cppcose.h>
+#include <keymaster/keymaster_configuration.h>
+#include <remote_prov/remote_prov_utils.h>
+
+#include <openssl/bn.h>
+#include <openssl/ec.h>
+#include <openssl/rand.h>
+#include <openssl/x509.h>
+
+namespace aidl::android::hardware::security::keymint {
+
+using ::std::string;
+using ::std::tuple;
+using ::std::unique_ptr;
+using ::std::variant;
+using ::std::vector;
+using bytevec = ::std::vector<uint8_t>;
+
+using namespace cppcose;
+using namespace keymaster;
+
+namespace {
+
+constexpr auto STATUS_FAILED = RemotelyProvisionedComponent::STATUS_FAILED;
+constexpr auto STATUS_INVALID_EEK = RemotelyProvisionedComponent::STATUS_INVALID_EEK;
+constexpr auto STATUS_INVALID_MAC = RemotelyProvisionedComponent::STATUS_INVALID_MAC;
+constexpr uint32_t kAffinePointLength = 32;
+struct AStatusDeleter {
+ void operator()(AStatus* p) { AStatus_delete(p); }
+};
+
+// TODO(swillden): Remove the dependency on AStatus stuff. The COSE lib should use something like
+// StatusOr, but it shouldn't depend on AStatus.
+class Status {
+ public:
+ Status() {}
+ Status(int32_t errCode, const std::string& errMsg)
+ : status_(AStatus_fromServiceSpecificErrorWithMessage(errCode, errMsg.c_str())) {}
+ explicit Status(const std::string& errMsg)
+ : status_(AStatus_fromServiceSpecificErrorWithMessage(STATUS_FAILED, errMsg.c_str())) {}
+ Status(AStatus* status) : status_(status) {}
+ Status(Status&&) = default;
+ Status(const Status&) = delete;
+
+ operator ::ndk::ScopedAStatus() && { return ndk::ScopedAStatus(status_.release()); }
+
+ bool isOk() { return !status_; }
+
+ // Don't call getMessage() unless isOk() returns false;
+ const char* getMessage() const { return AStatus_getMessage(status_.get()); }
+
+ private:
+ std::unique_ptr<AStatus, AStatusDeleter> status_;
+};
+
+template <typename T>
+class StatusOr {
+ public:
+ StatusOr(AStatus* status) : status_(status) {}
+ StatusOr(Status status) : status_(std::move(status)) {}
+ StatusOr(T val) : value_(std::move(val)) {}
+
+ bool isOk() { return status_.isOk(); }
+
+ T* operator->() & {
+ assert(isOk());
+ return &value_.value();
+ }
+ T& operator*() & {
+ assert(isOk());
+ return value_.value();
+ }
+ T&& operator*() && {
+ assert(isOk());
+ return std::move(value_).value();
+ }
+
+ const char* getMessage() const {
+ assert(!isOk());
+ return status_.getMessage();
+ }
+
+ Status moveError() {
+ assert(!isOk());
+ return std::move(status_);
+ }
+
+ T moveValue() { return std::move(value_).value(); }
+
+ private:
+ Status status_;
+ std::optional<T> value_;
+};
+
+StatusOr<std::pair<bytevec /* EEK pub */, bytevec /* EEK ID */>> validateAndExtractEekPubAndId(
+ bool testMode, const bytevec& endpointEncryptionCertChain) {
+ auto [item, newPos, errMsg] = cppbor::parse(endpointEncryptionCertChain);
+
+ if (!item || !item->asArray()) {
+ return Status("Error parsing EEK chain" + errMsg);
+ }
+
+ const cppbor::Array* certArr = item->asArray();
+ bytevec lastPubKey;
+ for (int i = 0; i < certArr->size(); ++i) {
+ auto cosePubKey = verifyAndParseCoseSign1(testMode, certArr->get(i)->asArray(),
+ std::move(lastPubKey), bytevec{} /* AAD */);
+ if (!cosePubKey) {
+ return Status(STATUS_INVALID_EEK,
+ "Failed to validate EEK chain: " + cosePubKey.moveMessage());
+ }
+ lastPubKey = *std::move(cosePubKey);
+ }
+
+ auto eek = CoseKey::parseX25519(lastPubKey, true /* requireKid */);
+ if (!eek) return Status(STATUS_INVALID_EEK, "Failed to get EEK: " + eek.moveMessage());
+
+ return std::make_pair(eek->getBstrValue(CoseKey::PUBKEY_X).value(),
+ eek->getBstrValue(CoseKey::KEY_ID).value());
+}
+
+StatusOr<bytevec /* pubkeys */> validateAndExtractPubkeys(bool testMode,
+ const vector<MacedPublicKey>& keysToSign,
+ const bytevec& macKey) {
+ auto pubKeysToMac = cppbor::Array();
+ for (auto& keyToSign : keysToSign) {
+ auto [macedKeyItem, _, coseMacErrMsg] = cppbor::parse(keyToSign.macedKey);
+ if (!macedKeyItem || !macedKeyItem->asArray() ||
+ macedKeyItem->asArray()->size() != kCoseMac0EntryCount) {
+ return Status("Invalid COSE_Mac0 structure");
+ }
+
+ auto protectedParms = macedKeyItem->asArray()->get(kCoseMac0ProtectedParams)->asBstr();
+ auto unprotectedParms = macedKeyItem->asArray()->get(kCoseMac0UnprotectedParams)->asBstr();
+ auto payload = macedKeyItem->asArray()->get(kCoseMac0Payload)->asBstr();
+ auto tag = macedKeyItem->asArray()->get(kCoseMac0Tag)->asBstr();
+ if (!protectedParms || !unprotectedParms || !payload || !tag) {
+ return Status("Invalid COSE_Mac0 contents");
+ }
+
+ auto [protectedMap, __, errMsg] = cppbor::parse(protectedParms);
+ if (!protectedMap || !protectedMap->asMap()) {
+ return Status("Invalid Mac0 protected: " + errMsg);
+ }
+ auto& algo = protectedMap->asMap()->get(ALGORITHM);
+ if (!algo || !algo->asInt() || algo->asInt()->value() != HMAC_256) {
+ return Status("Unsupported Mac0 algorithm");
+ }
+
+ auto pubKey = CoseKey::parse(payload->value(), EC2, ES256, P256);
+ if (!pubKey) return Status(pubKey.moveMessage());
+
+ bool testKey = static_cast<bool>(pubKey->getMap().get(CoseKey::TEST_KEY));
+ if (testMode && !testKey) {
+ return Status(BnRemotelyProvisionedComponent::STATUS_PRODUCTION_KEY_IN_TEST_REQUEST,
+ "Production key in test request");
+ } else if (!testMode && testKey) {
+ return Status(BnRemotelyProvisionedComponent::STATUS_TEST_KEY_IN_PRODUCTION_REQUEST,
+ "Test key in production request");
+ }
+
+ auto macTag = generateCoseMac0Mac(macKey, {} /* external_aad */, payload->value());
+ if (!macTag) return Status(STATUS_INVALID_MAC, macTag.moveMessage());
+ if (macTag->size() != tag->value().size() ||
+ CRYPTO_memcmp(macTag->data(), tag->value().data(), macTag->size()) != 0) {
+ return Status(STATUS_INVALID_MAC, "MAC tag mismatch");
+ }
+
+ pubKeysToMac.add(pubKey->moveMap());
+ }
+
+ return pubKeysToMac.encode();
+}
+
+StatusOr<std::pair<bytevec, bytevec>> buildCosePublicKeyFromKmCert(
+ const keymaster_blob_t* km_cert) {
+ if (km_cert == nullptr) {
+ return Status(STATUS_FAILED, "km_cert is a nullptr");
+ }
+ const uint8_t* temp = km_cert->data;
+ X509* cert = d2i_X509(NULL, &temp, km_cert->data_length);
+ if (cert == nullptr) {
+ return Status(STATUS_FAILED, "d2i_X509 returned null when attempting to get the cert.");
+ }
+ EVP_PKEY* pubKey = X509_get_pubkey(cert);
+ if (pubKey == nullptr) {
+ return Status(STATUS_FAILED, "Boringssl failed to get the public key from the cert");
+ }
+ EC_KEY* ecKey = EVP_PKEY_get0_EC_KEY(pubKey);
+ if (ecKey == nullptr) {
+ return Status(STATUS_FAILED,
+ "The key in the certificate returned from GenerateKey is not "
+ "an EC key.");
+ }
+ const EC_POINT* jacobian_coords = EC_KEY_get0_public_key(ecKey);
+ BIGNUM x;
+ BIGNUM y;
+ BN_CTX* ctx = BN_CTX_new();
+ if (ctx == nullptr) {
+ return Status(STATUS_FAILED, "Memory allocation failure for BN_CTX");
+ }
+ if (!EC_POINT_get_affine_coordinates_GFp(EC_KEY_get0_group(ecKey), jacobian_coords, &x, &y,
+ ctx)) {
+ return Status(STATUS_FAILED, "Failed to get affine coordinates");
+ }
+ bytevec x_bytestring(kAffinePointLength);
+ bytevec y_bytestring(kAffinePointLength);
+ if (BN_bn2binpad(&x, x_bytestring.data(), kAffinePointLength) != kAffinePointLength) {
+ return Status(STATUS_FAILED, "Wrote incorrect number of bytes for x coordinate");
+ }
+ if (BN_bn2binpad(&y, y_bytestring.data(), kAffinePointLength) != kAffinePointLength) {
+ return Status(STATUS_FAILED, "Wrote incorrect number of bytes for y coordinate");
+ }
+ BN_CTX_free(ctx);
+ return std::make_pair(x_bytestring, y_bytestring);
+}
+
+cppbor::Array buildCertReqRecipients(const bytevec& pubkey, const bytevec& kid) {
+ return cppbor::Array() // Array of recipients
+ .add(cppbor::Array() // Recipient
+ .add(cppbor::Map() // Protected
+ .add(ALGORITHM, ECDH_ES_HKDF_256)
+ .canonicalize()
+ .encode())
+ .add(cppbor::Map() // Unprotected
+ .add(COSE_KEY, cppbor::Map()
+ .add(CoseKey::KEY_TYPE, OCTET_KEY_PAIR)
+ .add(CoseKey::CURVE, cppcose::X25519)
+ .add(CoseKey::PUBKEY_X, pubkey)
+ .canonicalize())
+ .add(KEY_ID, kid)
+ .canonicalize())
+ .add(cppbor::Null())); // No ciphertext
+}
+
+static keymaster_key_param_t kKeyMintEcdsaP256Params[] = {
+ Authorization(TAG_PURPOSE, KM_PURPOSE_SIGN), Authorization(TAG_ALGORITHM, KM_ALGORITHM_EC),
+ Authorization(TAG_KEY_SIZE, 256), Authorization(TAG_DIGEST, KM_DIGEST_SHA_2_256),
+ Authorization(TAG_EC_CURVE, KM_EC_CURVE_P_256), Authorization(TAG_NO_AUTH_REQUIRED),
+ // The certificate generated by KM will be discarded, these values don't matter.
+ Authorization(TAG_CERTIFICATE_NOT_BEFORE, 0), Authorization(TAG_CERTIFICATE_NOT_AFTER, 0)};
+
+} // namespace
+
+RemotelyProvisionedComponent::RemotelyProvisionedComponent(
+ std::shared_ptr<keymint::AndroidKeyMintDevice> keymint) {
+ std::tie(devicePrivKey_, bcc_) = generateBcc();
+ impl_ = keymint->getKeymasterImpl();
+}
+
+RemotelyProvisionedComponent::~RemotelyProvisionedComponent() {}
+
+ScopedAStatus RemotelyProvisionedComponent::generateEcdsaP256KeyPair(bool testMode,
+ MacedPublicKey* macedPublicKey,
+ bytevec* privateKeyHandle) {
+ // TODO(jbires): The following should move from ->GenerateKey to ->GenerateRKPKey and everything
+ // after the GenerateKey call should basically be moved into that new function call
+ // as well once the issue with libcppbor in system/keymaster is sorted out
+ GenerateKeyRequest request(impl_->message_version());
+ request.key_description.Reinitialize(kKeyMintEcdsaP256Params,
+ array_length(kKeyMintEcdsaP256Params));
+ GenerateKeyResponse response(impl_->message_version());
+ impl_->GenerateKey(request, &response);
+ if (response.error != KM_ERROR_OK) {
+ return km_utils::kmError2ScopedAStatus(response.error);
+ }
+
+ if (response.certificate_chain.entry_count != 1) {
+ // Error: Need the single non-signed certificate with the public key in it.
+ return Status(STATUS_FAILED,
+ "Expected to receive a single certificate from GenerateKey. Instead got: " +
+ std::to_string(response.certificate_chain.entry_count));
+ }
+ auto affineCoords = buildCosePublicKeyFromKmCert(response.certificate_chain.begin());
+ if (!affineCoords.isOk()) return affineCoords.moveError();
+ cppbor::Map cosePublicKeyMap = cppbor::Map()
+ .add(CoseKey::KEY_TYPE, EC2)
+ .add(CoseKey::ALGORITHM, ES256)
+ .add(CoseKey::CURVE, cppcose::P256)
+ .add(CoseKey::PUBKEY_X, affineCoords->first)
+ .add(CoseKey::PUBKEY_Y, affineCoords->second);
+ if (testMode) {
+ cosePublicKeyMap.add(CoseKey::TEST_KEY, cppbor::Null());
+ }
+
+ bytevec cosePublicKey = cosePublicKeyMap.canonicalize().encode();
+
+ auto macedKey = constructCoseMac0(testMode ? remote_prov::kTestMacKey : macKey_,
+ {} /* externalAad */, cosePublicKey);
+ if (!macedKey) return Status(macedKey.moveMessage());
+
+ macedPublicKey->macedKey = macedKey->encode();
+ *privateKeyHandle = km_utils::kmBlob2vector(response.key_blob);
+ return ScopedAStatus::ok();
+}
+
+ScopedAStatus RemotelyProvisionedComponent::generateCertificateRequest(
+ bool testMode, const vector<MacedPublicKey>& keysToSign,
+ const bytevec& endpointEncCertChain, const bytevec& challenge, bytevec* keysToSignMac,
+ ProtectedData* protectedData) {
+ auto pubKeysToSign = validateAndExtractPubkeys(testMode, keysToSign,
+ testMode ? remote_prov::kTestMacKey : macKey_);
+ if (!pubKeysToSign.isOk()) return pubKeysToSign.moveError();
+
+ bytevec ephemeralMacKey = remote_prov::randomBytes(SHA256_DIGEST_LENGTH);
+
+ auto pubKeysToSignMac = generateCoseMac0Mac(ephemeralMacKey, bytevec{}, *pubKeysToSign);
+ if (!pubKeysToSignMac) return Status(pubKeysToSignMac.moveMessage());
+ *keysToSignMac = *std::move(pubKeysToSignMac);
+
+ bytevec devicePrivKey;
+ cppbor::Array bcc;
+ if (testMode) {
+ std::tie(devicePrivKey, bcc) = generateBcc();
+ } else {
+ devicePrivKey = devicePrivKey_;
+ bcc = bcc_.clone();
+ }
+
+ auto signedMac = constructCoseSign1(devicePrivKey /* Signing key */, //
+ ephemeralMacKey /* Payload */,
+ cppbor::Array() /* AAD */
+ .add(challenge)
+ .add(createDeviceInfo())
+ .encode());
+ if (!signedMac) return Status(signedMac.moveMessage());
+
+ bytevec ephemeralPrivKey(X25519_PRIVATE_KEY_LEN);
+ bytevec ephemeralPubKey(X25519_PUBLIC_VALUE_LEN);
+ X25519_keypair(ephemeralPubKey.data(), ephemeralPrivKey.data());
+
+ auto eek = validateAndExtractEekPubAndId(testMode, endpointEncCertChain);
+ if (!eek.isOk()) return eek.moveError();
+
+ auto sessionKey = x25519_HKDF_DeriveKey(ephemeralPubKey, ephemeralPrivKey, eek->first,
+ true /* senderIsA */);
+ if (!sessionKey) return Status(sessionKey.moveMessage());
+
+ auto coseEncrypted =
+ constructCoseEncrypt(*sessionKey, remote_prov::randomBytes(kAesGcmNonceLength),
+ cppbor::Array() // payload
+ .add(signedMac.moveValue())
+ .add(std::move(bcc))
+ .encode(),
+ {}, // aad
+ buildCertReqRecipients(ephemeralPubKey, eek->second));
+
+ if (!coseEncrypted) return Status(coseEncrypted.moveMessage());
+ protectedData->protectedData = coseEncrypted->encode();
+
+ return ScopedAStatus::ok();
+}
+
+bytevec RemotelyProvisionedComponent::deriveBytesFromHbk(const string& context,
+ size_t numBytes) const {
+ bytevec fakeHbk(32, 0);
+ bytevec result(numBytes);
+
+ // TODO(swillden): Figure out if HKDF can fail. It doesn't seem like it should be able to,
+ // but the function does return an error code.
+ HKDF(result.data(), numBytes, //
+ EVP_sha256(), //
+ fakeHbk.data(), fakeHbk.size(), //
+ nullptr /* salt */, 0 /* salt len */, //
+ reinterpret_cast<const uint8_t*>(context.data()), context.size());
+
+ return result;
+}
+
+bytevec RemotelyProvisionedComponent::createDeviceInfo() const {
+ return cppbor::Map().encode();
+}
+
+std::pair<bytevec /* privKey */, cppbor::Array /* BCC */>
+RemotelyProvisionedComponent::generateBcc() {
+ bytevec privKey(ED25519_PRIVATE_KEY_LEN);
+ bytevec pubKey(ED25519_PUBLIC_KEY_LEN);
+
+ ED25519_keypair(pubKey.data(), privKey.data());
+
+ auto coseKey = cppbor::Map()
+ .add(CoseKey::KEY_TYPE, OCTET_KEY_PAIR)
+ .add(CoseKey::ALGORITHM, EDDSA)
+ .add(CoseKey::CURVE, ED25519)
+ .add(CoseKey::KEY_OPS, VERIFY)
+ .add(CoseKey::PUBKEY_X, pubKey)
+ .canonicalize()
+ .encode();
+ auto sign1Payload = cppbor::Map()
+ .add(1 /* Issuer */, "Issuer")
+ .add(2 /* Subject */, "Subject")
+ .add(-4670552 /* Subject Pub Key */, coseKey)
+ .add(-4670553 /* Key Usage */,
+ std::vector<uint8_t>(0x05) /* Big endian order */)
+ .canonicalize()
+ .encode();
+ auto coseSign1 = constructCoseSign1(privKey, /* signing key */
+ cppbor::Map(), /* extra protected */
+ sign1Payload, {} /* AAD */);
+ assert(coseSign1);
+
+ return {privKey, cppbor::Array().add(coseKey).add(coseSign1.moveValue())};
+}
+
+} // namespace aidl::android::hardware::security::keymint
diff --git a/security/keymint/aidl/default/RemotelyProvisionedComponent.h b/security/keymint/aidl/default/RemotelyProvisionedComponent.h
new file mode 100644
index 0000000..e8d2343
--- /dev/null
+++ b/security/keymint/aidl/default/RemotelyProvisionedComponent.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <AndroidKeyMintDevice.h>
+#include <aidl/android/hardware/security/keymint/BnRemotelyProvisionedComponent.h>
+#include <aidl/android/hardware/security/keymint/SecurityLevel.h>
+#include <cppbor.h>
+#include <keymaster/UniquePtr.h>
+#include <keymaster/android_keymaster.h>
+
+namespace aidl::android::hardware::security::keymint {
+
+using ::ndk::ScopedAStatus;
+
+class RemotelyProvisionedComponent : public BnRemotelyProvisionedComponent {
+ public:
+ explicit RemotelyProvisionedComponent(std::shared_ptr<keymint::AndroidKeyMintDevice> keymint);
+ virtual ~RemotelyProvisionedComponent();
+
+ ScopedAStatus generateEcdsaP256KeyPair(bool testMode, MacedPublicKey* macedPublicKey,
+ std::vector<uint8_t>* privateKeyHandle) override;
+
+ ScopedAStatus generateCertificateRequest(bool testMode,
+ const std::vector<MacedPublicKey>& keysToSign,
+ const std::vector<uint8_t>& endpointEncCertChain,
+ const std::vector<uint8_t>& challenge,
+ std::vector<uint8_t>* keysToSignMac,
+ ProtectedData* protectedData) override;
+
+ private:
+ // TODO(swillden): Move these into an appropriate Context class.
+ std::vector<uint8_t> deriveBytesFromHbk(const std::string& context, size_t numBytes) const;
+ std::vector<uint8_t> createDeviceInfo() const;
+ std::pair<std::vector<uint8_t>, cppbor::Array> generateBcc();
+
+ std::vector<uint8_t> macKey_ = deriveBytesFromHbk("Key to MAC public keys", 32);
+ std::vector<uint8_t> devicePrivKey_;
+ cppbor::Array bcc_;
+ std::shared_ptr<::keymaster::AndroidKeymaster> impl_;
+};
+
+} // namespace aidl::android::hardware::security::keymint
diff --git a/security/keymint/aidl/default/android.hardware.security.keymint-service.xml b/security/keymint/aidl/default/android.hardware.security.keymint-service.xml
index 73d15a8..4aa05ef 100644
--- a/security/keymint/aidl/default/android.hardware.security.keymint-service.xml
+++ b/security/keymint/aidl/default/android.hardware.security.keymint-service.xml
@@ -3,4 +3,8 @@
<name>android.hardware.security.keymint</name>
<fqname>IKeyMintDevice/default</fqname>
</hal>
+ <hal format="aidl">
+ <name>android.hardware.security.keymint</name>
+ <fqname>IRemotelyProvisionedComponent/default</fqname>
+ </hal>
</manifest>
diff --git a/security/keymint/aidl/default/android.hardware.security.secureclock-service.xml b/security/keymint/aidl/default/android.hardware.security.secureclock-service.xml
new file mode 100644
index 0000000..c0ff775
--- /dev/null
+++ b/security/keymint/aidl/default/android.hardware.security.secureclock-service.xml
@@ -0,0 +1,6 @@
+<manifest version="1.0" type="device">
+ <hal format="aidl">
+ <name>android.hardware.security.secureclock</name>
+ <fqname>ISecureClock/default</fqname>
+ </hal>
+</manifest>
diff --git a/security/keymint/aidl/default/android.hardware.security.sharedsecret-service.xml b/security/keymint/aidl/default/android.hardware.security.sharedsecret-service.xml
new file mode 100644
index 0000000..d37981f
--- /dev/null
+++ b/security/keymint/aidl/default/android.hardware.security.sharedsecret-service.xml
@@ -0,0 +1,6 @@
+<manifest version="1.0" type="device">
+ <hal format="aidl">
+ <name>android.hardware.security.sharedsecret</name>
+ <fqname>ISharedSecret/default</fqname>
+ </hal>
+</manifest>
diff --git a/security/keymint/aidl/default/service.cpp b/security/keymint/aidl/default/service.cpp
index a710535..bcebbaf 100644
--- a/security/keymint/aidl/default/service.cpp
+++ b/security/keymint/aidl/default/service.cpp
@@ -21,25 +21,42 @@
#include <android/binder_process.h>
#include <AndroidKeyMintDevice.h>
+#include <AndroidSecureClock.h>
+#include <AndroidSharedSecret.h>
#include <keymaster/soft_keymaster_logger.h>
+#include "RemotelyProvisionedComponent.h"
+
using aidl::android::hardware::security::keymint::AndroidKeyMintDevice;
+using aidl::android::hardware::security::keymint::RemotelyProvisionedComponent;
using aidl::android::hardware::security::keymint::SecurityLevel;
+using aidl::android::hardware::security::secureclock::AndroidSecureClock;
+using aidl::android::hardware::security::sharedsecret::AndroidSharedSecret;
+
+template <typename T, class... Args>
+std::shared_ptr<T> addService(Args&&... args) {
+ std::shared_ptr<T> ser = ndk::SharedRefBase::make<T>(std::forward<Args>(args)...);
+ auto instanceName = std::string(T::descriptor) + "/default";
+ LOG(INFO) << "adding keymint service instance: " << instanceName;
+ binder_status_t status =
+ AServiceManager_addService(ser->asBinder().get(), instanceName.c_str());
+ CHECK(status == STATUS_OK);
+ return ser;
+}
int main() {
// Zero threads seems like a useless pool, but below we'll join this thread to it, increasing
// the pool size to 1.
ABinderProcess_setThreadPoolMaxThreadCount(0);
+ // Add Keymint Service
std::shared_ptr<AndroidKeyMintDevice> keyMint =
- ndk::SharedRefBase::make<AndroidKeyMintDevice>(SecurityLevel::SOFTWARE);
-
- keymaster::SoftKeymasterLogger logger;
- const auto instanceName = std::string(AndroidKeyMintDevice::descriptor) + "/default";
- LOG(INFO) << "instance: " << instanceName;
- binder_status_t status =
- AServiceManager_addService(keyMint->asBinder().get(), instanceName.c_str());
- CHECK(status == STATUS_OK);
-
+ addService<AndroidKeyMintDevice>(SecurityLevel::SOFTWARE);
+ // Add Secure Clock Service
+ addService<AndroidSecureClock>(keyMint);
+ // Add Shared Secret Service
+ addService<AndroidSharedSecret>(keyMint);
+ // Add Remotely Provisioned Component Service
+ addService<RemotelyProvisionedComponent>(keyMint);
ABinderProcess_joinThreadPool();
return EXIT_FAILURE; // should not reach
}
diff --git a/security/keymint/aidl/vts/functional/Android.bp b/security/keymint/aidl/vts/functional/Android.bp
index c7cc380..24fe616 100644
--- a/security/keymint/aidl/vts/functional/Android.bp
+++ b/security/keymint/aidl/vts/functional/Android.bp
@@ -21,8 +21,8 @@
"use_libaidlvintf_gtest_helper_static",
],
srcs: [
+ "AttestKeyTest.cpp",
"KeyMintTest.cpp",
- "VerificationTokenTest.cpp",
],
shared_libs: [
"libbinder_ndk",
@@ -31,7 +31,8 @@
"libkeymint_support",
],
static_libs: [
- "android.hardware.security.keymint-unstable-ndk_platform",
+ "android.hardware.security.keymint-V1-ndk_platform",
+ "android.hardware.security.secureclock-V1-ndk_platform",
"libcppbor_external",
"libkeymint_vts_test_utils",
],
@@ -60,7 +61,38 @@
"libkeymint_support",
],
static_libs: [
- "android.hardware.security.keymint-unstable-ndk_platform",
- "libcppbor",
+ "android.hardware.security.keymint-V1-ndk_platform",
+ "android.hardware.security.secureclock-V1-ndk_platform",
+ "libcppbor_external",
+ ],
+}
+
+cc_test {
+ name: "VtsHalRemotelyProvisionedComponentTargetTest",
+ defaults: [
+ "VtsHalTargetTestDefaults",
+ "use_libaidlvintf_gtest_helper_static",
+ ],
+ srcs: [
+ "VtsRemotelyProvisionedComponentTests.cpp",
+ ],
+ shared_libs: [
+ "libbinder_ndk",
+ "libcppbor_external",
+ "libcrypto",
+ "libkeymaster_portable",
+ "libpuresoftkeymasterdevice",
+ ],
+ static_libs: [
+ "android.hardware.security.keymint-V1-ndk_platform",
+ "libcppcose",
+ "libgmock_ndk",
+ "libremote_provisioner",
+ "libkeymint",
+ "libkeymint_remote_prov_support",
+ ],
+ test_suites: [
+ "general-tests",
+ "vts",
],
}
diff --git a/security/keymint/aidl/vts/functional/AttestKeyTest.cpp b/security/keymint/aidl/vts/functional/AttestKeyTest.cpp
new file mode 100644
index 0000000..7e7a466
--- /dev/null
+++ b/security/keymint/aidl/vts/functional/AttestKeyTest.cpp
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "keymint_1_attest_key_test"
+#include <cutils/log.h>
+
+#include <keymint_support/key_param_output.h>
+#include <keymint_support/openssl_utils.h>
+
+#include "KeyMintAidlTestBase.h"
+
+namespace aidl::android::hardware::security::keymint::test {
+
+namespace {
+
+vector<uint8_t> make_name_from_str(const string& name) {
+ X509_NAME_Ptr x509_name(X509_NAME_new());
+ EXPECT_TRUE(x509_name.get() != nullptr);
+ if (!x509_name) return {};
+
+ EXPECT_EQ(1, X509_NAME_add_entry_by_txt(x509_name.get(), //
+ "CN", //
+ MBSTRING_ASC,
+ reinterpret_cast<const uint8_t*>(name.c_str()),
+ -1, // len
+ -1, // loc
+ 0 /* set */));
+
+ int len = i2d_X509_NAME(x509_name.get(), nullptr /* only return length */);
+ EXPECT_GT(len, 0);
+
+ vector<uint8_t> retval(len);
+ uint8_t* p = retval.data();
+ i2d_X509_NAME(x509_name.get(), &p);
+
+ return retval;
+}
+
+bool IsSelfSigned(const vector<Certificate>& chain) {
+ if (chain.size() != 1) return false;
+ return ChainSignaturesAreValid(chain);
+}
+
+} // namespace
+
+using AttestKeyTest = KeyMintAidlTestBase;
+
+TEST_P(AttestKeyTest, AllRsaSizes) {
+ for (auto size : ValidKeySizes(Algorithm::RSA)) {
+ /*
+ * Create attestaton key.
+ */
+ AttestationKey attest_key;
+ vector<KeyCharacteristics> attest_key_characteristics;
+ vector<Certificate> attest_key_cert_chain;
+ ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+ .RsaSigningKey(size, 65537)
+ .AttestKey()
+ .SetDefaultValidity(),
+ {} /* attestation signing key */, &attest_key.keyBlob,
+ &attest_key_characteristics, &attest_key_cert_chain));
+
+ EXPECT_EQ(attest_key_cert_chain.size(), 1);
+ EXPECT_TRUE(IsSelfSigned(attest_key_cert_chain)) << "Failed on size " << size;
+
+ /*
+ * Use attestation key to sign RSA key
+ */
+ attest_key.issuerSubjectName = make_name_from_str("Android Keystore Key");
+ vector<uint8_t> attested_key_blob;
+ vector<KeyCharacteristics> attested_key_characteristics;
+ vector<Certificate> attested_key_cert_chain;
+ EXPECT_EQ(ErrorCode::OK,
+ GenerateKey(AuthorizationSetBuilder()
+ .RsaSigningKey(2048, 65537)
+ .Authorization(TAG_NO_AUTH_REQUIRED)
+ .AttestationChallenge("foo")
+ .AttestationApplicationId("bar")
+ .SetDefaultValidity(),
+ attest_key, &attested_key_blob, &attested_key_characteristics,
+ &attested_key_cert_chain));
+
+ CheckedDeleteKey(&attested_key_blob);
+
+ AuthorizationSet hw_enforced = HwEnforcedAuthorizations(attested_key_characteristics);
+ AuthorizationSet sw_enforced = SwEnforcedAuthorizations(attested_key_characteristics);
+ EXPECT_TRUE(verify_attestation_record("foo", "bar", sw_enforced, hw_enforced, SecLevel(),
+ attested_key_cert_chain[0].encodedCertificate));
+
+ // Attestation by itself is not valid (last entry is not self-signed).
+ EXPECT_FALSE(ChainSignaturesAreValid(attested_key_cert_chain));
+
+ // Appending the attest_key chain to the attested_key_chain should yield a valid chain.
+ if (attest_key_cert_chain.size() > 0) {
+ attested_key_cert_chain.push_back(attest_key_cert_chain[0]);
+ }
+ EXPECT_TRUE(ChainSignaturesAreValid(attested_key_cert_chain));
+
+ /*
+ * Use attestation key to sign EC key
+ */
+ EXPECT_EQ(ErrorCode::OK,
+ GenerateKey(AuthorizationSetBuilder()
+ .EcdsaSigningKey(EcCurve::P_256)
+ .Authorization(TAG_NO_AUTH_REQUIRED)
+ .AttestationChallenge("foo")
+ .AttestationApplicationId("bar")
+ .SetDefaultValidity(),
+ attest_key, &attested_key_blob, &attested_key_characteristics,
+ &attested_key_cert_chain));
+
+ CheckedDeleteKey(&attested_key_blob);
+ CheckedDeleteKey(&attest_key.keyBlob);
+
+ hw_enforced = HwEnforcedAuthorizations(attested_key_characteristics);
+ sw_enforced = SwEnforcedAuthorizations(attested_key_characteristics);
+ EXPECT_TRUE(verify_attestation_record("foo", "bar", sw_enforced, hw_enforced, SecLevel(),
+ attested_key_cert_chain[0].encodedCertificate));
+
+ // Attestation by itself is not valid (last entry is not self-signed).
+ EXPECT_FALSE(ChainSignaturesAreValid(attested_key_cert_chain));
+
+ // Appending the attest_key chain to the attested_key_chain should yield a valid chain.
+ if (attest_key_cert_chain.size() > 0) {
+ attested_key_cert_chain.push_back(attest_key_cert_chain[0]);
+ }
+ EXPECT_TRUE(ChainSignaturesAreValid(attested_key_cert_chain));
+
+ // Bail early if anything failed.
+ if (HasFailure()) return;
+ }
+}
+
+TEST_P(AttestKeyTest, AllEcCurves) {
+ for (auto curve : ValidCurves()) {
+ /*
+ * Create attestaton key.
+ */
+ AttestationKey attest_key;
+ vector<KeyCharacteristics> attest_key_characteristics;
+ vector<Certificate> attest_key_cert_chain;
+ ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+ .EcdsaSigningKey(curve)
+ .AttestKey()
+ .SetDefaultValidity(),
+ {} /* attestation siging key */, &attest_key.keyBlob,
+ &attest_key_characteristics, &attest_key_cert_chain));
+
+ EXPECT_EQ(attest_key_cert_chain.size(), 1);
+ EXPECT_TRUE(IsSelfSigned(attest_key_cert_chain)) << "Failed on curve " << curve;
+
+ /*
+ * Use attestation key to sign RSA key
+ */
+ attest_key.issuerSubjectName = make_name_from_str("Android Keystore Key");
+ vector<uint8_t> attested_key_blob;
+ vector<KeyCharacteristics> attested_key_characteristics;
+ vector<Certificate> attested_key_cert_chain;
+ EXPECT_EQ(ErrorCode::OK,
+ GenerateKey(AuthorizationSetBuilder()
+ .RsaSigningKey(2048, 65537)
+ .Authorization(TAG_NO_AUTH_REQUIRED)
+ .AttestationChallenge("foo")
+ .AttestationApplicationId("bar")
+ .SetDefaultValidity(),
+ attest_key, &attested_key_blob, &attested_key_characteristics,
+ &attested_key_cert_chain));
+
+ CheckedDeleteKey(&attested_key_blob);
+
+ AuthorizationSet hw_enforced = HwEnforcedAuthorizations(attested_key_characteristics);
+ AuthorizationSet sw_enforced = SwEnforcedAuthorizations(attested_key_characteristics);
+ EXPECT_TRUE(verify_attestation_record("foo", "bar", sw_enforced, hw_enforced, SecLevel(),
+ attested_key_cert_chain[0].encodedCertificate));
+
+ // Attestation by itself is not valid (last entry is not self-signed).
+ EXPECT_FALSE(ChainSignaturesAreValid(attested_key_cert_chain));
+
+ // Appending the attest_key chain to the attested_key_chain should yield a valid chain.
+ if (attest_key_cert_chain.size() > 0) {
+ attested_key_cert_chain.push_back(attest_key_cert_chain[0]);
+ }
+ EXPECT_TRUE(ChainSignaturesAreValid(attested_key_cert_chain));
+
+ /*
+ * Use attestation key to sign EC key
+ */
+ EXPECT_EQ(ErrorCode::OK,
+ GenerateKey(AuthorizationSetBuilder()
+ .EcdsaSigningKey(EcCurve::P_256)
+ .Authorization(TAG_NO_AUTH_REQUIRED)
+ .AttestationChallenge("foo")
+ .AttestationApplicationId("bar")
+ .SetDefaultValidity(),
+ attest_key, &attested_key_blob, &attested_key_characteristics,
+ &attested_key_cert_chain));
+
+ CheckedDeleteKey(&attested_key_blob);
+ CheckedDeleteKey(&attest_key.keyBlob);
+
+ hw_enforced = HwEnforcedAuthorizations(attested_key_characteristics);
+ sw_enforced = SwEnforcedAuthorizations(attested_key_characteristics);
+ EXPECT_TRUE(verify_attestation_record("foo", "bar", sw_enforced, hw_enforced, SecLevel(),
+ attested_key_cert_chain[0].encodedCertificate));
+
+ // Attestation by itself is not valid (last entry is not self-signed).
+ EXPECT_FALSE(ChainSignaturesAreValid(attested_key_cert_chain));
+
+ // Appending the attest_key chain to the attested_key_chain should yield a valid chain.
+ if (attest_key_cert_chain.size() > 0) {
+ attested_key_cert_chain.push_back(attest_key_cert_chain[0]);
+ }
+ EXPECT_TRUE(ChainSignaturesAreValid(attested_key_cert_chain));
+
+ // Bail early if anything failed.
+ if (HasFailure()) return;
+ }
+}
+
+INSTANTIATE_KEYMINT_AIDL_TEST(AttestKeyTest);
+
+} // namespace aidl::android::hardware::security::keymint::test
diff --git a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp
index 94bc199..d61a081 100644
--- a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp
+++ b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp
@@ -17,32 +17,131 @@
#include "KeyMintAidlTestBase.h"
#include <chrono>
+#include <unordered_set>
#include <vector>
#include <android-base/logging.h>
#include <android/binder_manager.h>
+#include <cutils/properties.h>
+#include <openssl/mem.h>
+#include <keymint_support/attestation_record.h>
#include <keymint_support/key_param_output.h>
#include <keymint_support/keymint_utils.h>
+#include <keymint_support/openssl_utils.h>
namespace aidl::android::hardware::security::keymint {
using namespace std::literals::chrono_literals;
using std::endl;
using std::optional;
+using std::unique_ptr;
+using ::testing::AssertionFailure;
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
::std::ostream& operator<<(::std::ostream& os, const AuthorizationSet& set) {
if (set.size() == 0)
os << "(Empty)" << ::std::endl;
else {
os << "\n";
- for (size_t i = 0; i < set.size(); ++i) os << set[i] << ::std::endl;
+ for (auto& entry : set) os << entry << ::std::endl;
}
return os;
}
namespace test {
+namespace {
+typedef KeyMintAidlTestBase::KeyData KeyData;
+// Predicate for testing basic characteristics validity in generation or import.
+bool KeyCharacteristicsBasicallyValid(SecurityLevel secLevel,
+ const vector<KeyCharacteristics>& key_characteristics) {
+ if (key_characteristics.empty()) return false;
+
+ std::unordered_set<SecurityLevel> levels_seen;
+ for (auto& entry : key_characteristics) {
+ if (entry.authorizations.empty()) return false;
+
+ // Just ignore the SecurityLevel::KEYSTORE as the KM won't do any enforcement on this.
+ if (entry.securityLevel == SecurityLevel::KEYSTORE) continue;
+
+ if (levels_seen.find(entry.securityLevel) != levels_seen.end()) return false;
+ levels_seen.insert(entry.securityLevel);
+
+ // Generally, we should only have one entry, at the same security level as the KM
+ // instance. There is an exception: StrongBox KM can have some authorizations that are
+ // enforced by the TEE.
+ bool isExpectedSecurityLevel = secLevel == entry.securityLevel ||
+ (secLevel == SecurityLevel::STRONGBOX &&
+ entry.securityLevel == SecurityLevel::TRUSTED_ENVIRONMENT);
+
+ if (!isExpectedSecurityLevel) return false;
+ }
+ return true;
+}
+
+// Extract attestation record from cert. Returned object is still part of cert; don't free it
+// separately.
+ASN1_OCTET_STRING* get_attestation_record(X509* certificate) {
+ ASN1_OBJECT_Ptr oid(OBJ_txt2obj(kAttestionRecordOid, 1 /* dotted string format */));
+ EXPECT_TRUE(!!oid.get());
+ if (!oid.get()) return nullptr;
+
+ int location = X509_get_ext_by_OBJ(certificate, oid.get(), -1 /* search from beginning */);
+ EXPECT_NE(-1, location) << "Attestation extension not found in certificate";
+ if (location == -1) return nullptr;
+
+ X509_EXTENSION* attest_rec_ext = X509_get_ext(certificate, location);
+ EXPECT_TRUE(!!attest_rec_ext)
+ << "Found attestation extension but couldn't retrieve it? Probably a BoringSSL bug.";
+ if (!attest_rec_ext) return nullptr;
+
+ ASN1_OCTET_STRING* attest_rec = X509_EXTENSION_get_data(attest_rec_ext);
+ EXPECT_TRUE(!!attest_rec) << "Attestation extension contained no data";
+ return attest_rec;
+}
+
+bool avb_verification_enabled() {
+ char value[PROPERTY_VALUE_MAX];
+ return property_get("ro.boot.vbmeta.device_state", value, "") != 0;
+}
+
+char nibble2hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+
+// Attestations don't contain everything in key authorization lists, so we need to filter the key
+// lists to produce the lists that we expect to match the attestations.
+auto kTagsToFilter = {
+ Tag::BLOB_USAGE_REQUIREMENTS, //
+ Tag::CREATION_DATETIME, //
+ Tag::EC_CURVE,
+ Tag::HARDWARE_TYPE,
+ Tag::INCLUDE_UNIQUE_ID,
+};
+
+AuthorizationSet filtered_tags(const AuthorizationSet& set) {
+ AuthorizationSet filtered;
+ std::remove_copy_if(
+ set.begin(), set.end(), std::back_inserter(filtered), [](const auto& entry) -> bool {
+ return std::find(kTagsToFilter.begin(), kTagsToFilter.end(), entry.tag) !=
+ kTagsToFilter.end();
+ });
+ return filtered;
+}
+
+string x509NameToStr(X509_NAME* name) {
+ char* s = X509_NAME_oneline(name, nullptr, 0);
+ string retval(s);
+ OPENSSL_free(s);
+ return retval;
+}
+
+} // namespace
+
+bool KeyMintAidlTestBase::arm_deleteAllKeys = false;
+bool KeyMintAidlTestBase::dump_Attestations = false;
+
ErrorCode KeyMintAidlTestBase::GetReturnErrorCode(const Status& result) {
if (result.isOk()) return ErrorCode::OK;
@@ -78,65 +177,83 @@
}
ErrorCode KeyMintAidlTestBase::GenerateKey(const AuthorizationSet& key_desc,
- vector<uint8_t>* keyBlob, KeyCharacteristics* keyChar) {
- EXPECT_NE(keyBlob, nullptr) << "Key blob pointer must not be null. Test bug";
- EXPECT_NE(keyChar, nullptr)
+ const optional<AttestationKey>& attest_key,
+ vector<uint8_t>* key_blob,
+ vector<KeyCharacteristics>* key_characteristics,
+ vector<Certificate>* cert_chain) {
+ EXPECT_NE(key_blob, nullptr) << "Key blob pointer must not be null. Test bug";
+ EXPECT_NE(key_characteristics, nullptr)
<< "Previous characteristics not deleted before generating key. Test bug.";
- // Aidl does not clear these output parameters if the function returns
- // error. This is different from hal where output parameter is always
- // cleared due to hal returning void. So now we need to do our own clearing
- // of the output variables prior to calling keyMint aidl libraries.
- keyBlob->clear();
- keyChar->softwareEnforced.clear();
- keyChar->hardwareEnforced.clear();
- certChain_.clear();
-
- Status result;
- ByteArray blob;
-
- result = keymint_->generateKey(key_desc.vector_data(), &blob, keyChar, &certChain_);
-
- // On result, blob & characteristics should be empty.
+ KeyCreationResult creationResult;
+ Status result = keymint_->generateKey(key_desc.vector_data(), attest_key, &creationResult);
if (result.isOk()) {
- if (SecLevel() != SecurityLevel::SOFTWARE) {
- EXPECT_GT(keyChar->hardwareEnforced.size(), 0);
+ EXPECT_PRED2(KeyCharacteristicsBasicallyValid, SecLevel(),
+ creationResult.keyCharacteristics);
+ EXPECT_GT(creationResult.keyBlob.size(), 0);
+ *key_blob = std::move(creationResult.keyBlob);
+ *key_characteristics = std::move(creationResult.keyCharacteristics);
+ *cert_chain = std::move(creationResult.certificateChain);
+
+ auto algorithm = key_desc.GetTagValue(TAG_ALGORITHM);
+ EXPECT_TRUE(algorithm);
+ if (algorithm &&
+ (algorithm.value() == Algorithm::RSA || algorithm.value() == Algorithm::EC)) {
+ EXPECT_GE(cert_chain->size(), 1);
+ if (key_desc.Contains(TAG_ATTESTATION_CHALLENGE)) {
+ if (attest_key) {
+ EXPECT_EQ(cert_chain->size(), 1);
+ } else {
+ EXPECT_GT(cert_chain->size(), 1);
+ }
+ }
+ } else {
+ // For symmetric keys there should be no certificates.
+ EXPECT_EQ(cert_chain->size(), 0);
}
- EXPECT_GT(keyChar->softwareEnforced.size(), 0);
- // TODO(seleneh) in a later version where we return @nullable
- // single Certificate, check non-null single certificate is always
- // non-empty.
- *keyBlob = blob.data;
}
return GetReturnErrorCode(result);
}
-ErrorCode KeyMintAidlTestBase::GenerateKey(const AuthorizationSet& key_desc) {
- return GenerateKey(key_desc, &key_blob_, &key_characteristics_);
+ErrorCode KeyMintAidlTestBase::GenerateKey(const AuthorizationSet& key_desc,
+ const optional<AttestationKey>& attest_key) {
+ return GenerateKey(key_desc, attest_key, &key_blob_, &key_characteristics_, &cert_chain_);
}
ErrorCode KeyMintAidlTestBase::ImportKey(const AuthorizationSet& key_desc, KeyFormat format,
const string& key_material, vector<uint8_t>* key_blob,
- KeyCharacteristics* key_characteristics) {
+ vector<KeyCharacteristics>* key_characteristics) {
Status result;
- certChain_.clear();
- key_characteristics->softwareEnforced.clear();
- key_characteristics->hardwareEnforced.clear();
+ cert_chain_.clear();
+ key_characteristics->clear();
key_blob->clear();
- ByteArray blob;
+ KeyCreationResult creationResult;
result = keymint_->importKey(key_desc.vector_data(), format,
- vector<uint8_t>(key_material.begin(), key_material.end()), &blob,
- key_characteristics, &certChain_);
+ vector<uint8_t>(key_material.begin(), key_material.end()),
+ {} /* attestationSigningKeyBlob */, &creationResult);
if (result.isOk()) {
- if (SecLevel() != SecurityLevel::SOFTWARE) {
- EXPECT_GT(key_characteristics->hardwareEnforced.size(), 0);
+ EXPECT_PRED2(KeyCharacteristicsBasicallyValid, SecLevel(),
+ creationResult.keyCharacteristics);
+ EXPECT_GT(creationResult.keyBlob.size(), 0);
+
+ *key_blob = std::move(creationResult.keyBlob);
+ *key_characteristics = std::move(creationResult.keyCharacteristics);
+ cert_chain_ = std::move(creationResult.certificateChain);
+
+ auto algorithm = key_desc.GetTagValue(TAG_ALGORITHM);
+ EXPECT_TRUE(algorithm);
+ if (algorithm &&
+ (algorithm.value() == Algorithm::RSA || algorithm.value() == Algorithm::EC)) {
+ EXPECT_GE(cert_chain_.size(), 1);
+ if (key_desc.Contains(TAG_ATTESTATION_CHALLENGE)) EXPECT_GT(cert_chain_.size(), 1);
+ } else {
+ // For symmetric keys there should be no certificates.
+ EXPECT_EQ(cert_chain_.size(), 0);
}
- EXPECT_GT(key_characteristics->softwareEnforced.size(), 0);
- *key_blob = blob.data;
}
return GetReturnErrorCode(result);
@@ -151,25 +268,39 @@
const AuthorizationSet& wrapping_key_desc,
string masking_key,
const AuthorizationSet& unwrapping_params) {
- Status result;
EXPECT_EQ(ErrorCode::OK, ImportKey(wrapping_key_desc, KeyFormat::PKCS8, wrapping_key));
- ByteArray outBlob;
- key_characteristics_.softwareEnforced.clear();
- key_characteristics_.hardwareEnforced.clear();
+ key_characteristics_.clear();
- result = keymint_->importWrappedKey(vector<uint8_t>(wrapped_key.begin(), wrapped_key.end()),
- key_blob_,
- vector<uint8_t>(masking_key.begin(), masking_key.end()),
- unwrapping_params.vector_data(), 0 /* passwordSid */,
- 0 /* biometricSid */, &outBlob, &key_characteristics_);
+ KeyCreationResult creationResult;
+ Status result = keymint_->importWrappedKey(
+ vector<uint8_t>(wrapped_key.begin(), wrapped_key.end()), key_blob_,
+ vector<uint8_t>(masking_key.begin(), masking_key.end()),
+ unwrapping_params.vector_data(), 0 /* passwordSid */, 0 /* biometricSid */,
+ &creationResult);
if (result.isOk()) {
- key_blob_ = outBlob.data;
- if (SecLevel() != SecurityLevel::SOFTWARE) {
- EXPECT_GT(key_characteristics_.hardwareEnforced.size(), 0);
+ EXPECT_PRED2(KeyCharacteristicsBasicallyValid, SecLevel(),
+ creationResult.keyCharacteristics);
+ EXPECT_GT(creationResult.keyBlob.size(), 0);
+
+ key_blob_ = std::move(creationResult.keyBlob);
+ key_characteristics_ = std::move(creationResult.keyCharacteristics);
+ cert_chain_ = std::move(creationResult.certificateChain);
+
+ AuthorizationSet allAuths;
+ for (auto& entry : key_characteristics_) {
+ allAuths.push_back(AuthorizationSet(entry.authorizations));
}
- EXPECT_GT(key_characteristics_.softwareEnforced.size(), 0);
+ auto algorithm = allAuths.GetTagValue(TAG_ALGORITHM);
+ EXPECT_TRUE(algorithm);
+ if (algorithm &&
+ (algorithm.value() == Algorithm::RSA || algorithm.value() == Algorithm::EC)) {
+ EXPECT_GE(cert_chain_.size(), 1);
+ } else {
+ // For symmetric keys there should be no certificates.
+ EXPECT_EQ(cert_chain_.size(), 0);
+ }
}
return GetReturnErrorCode(result);
@@ -397,6 +528,34 @@
}
}
+auto KeyMintAidlTestBase::ProcessMessage(const vector<uint8_t>& key_blob, KeyPurpose operation,
+ const string& message, const AuthorizationSet& in_params)
+ -> std::tuple<ErrorCode, string, AuthorizationSet /* out_params */> {
+ AuthorizationSet begin_out_params;
+ ErrorCode result = Begin(operation, key_blob, in_params, &begin_out_params);
+ AuthorizationSet out_params(std::move(begin_out_params));
+ if (result != ErrorCode::OK) {
+ return {result, {}, out_params};
+ }
+
+ string output;
+ int32_t consumed = 0;
+ AuthorizationSet update_params;
+ AuthorizationSet update_out_params;
+ result = Update(update_params, message, &update_out_params, &output, &consumed);
+ out_params.push_back(update_out_params);
+ if (result != ErrorCode::OK) {
+ return {result, output, out_params};
+ }
+
+ string unused;
+ AuthorizationSet finish_params;
+ AuthorizationSet finish_out_params;
+ result = Finish(finish_params, message.substr(consumed), unused, &finish_out_params, &output);
+ out_params.push_back(finish_out_params);
+ return {result, output, out_params};
+}
+
string KeyMintAidlTestBase::ProcessMessage(const vector<uint8_t>& key_blob, KeyPurpose operation,
const string& message, const AuthorizationSet& in_params,
AuthorizationSet* out_params) {
@@ -754,6 +913,310 @@
return {};
}
+static const vector<KeyParameter> kEmptyAuthList{};
+
+const vector<KeyParameter>& KeyMintAidlTestBase::SecLevelAuthorizations(
+ const vector<KeyCharacteristics>& key_characteristics) {
+ auto found = std::find_if(key_characteristics.begin(), key_characteristics.end(),
+ [this](auto& entry) { return entry.securityLevel == SecLevel(); });
+ return (found == key_characteristics.end()) ? kEmptyAuthList : found->authorizations;
+}
+
+const vector<KeyParameter>& KeyMintAidlTestBase::SecLevelAuthorizations(
+ const vector<KeyCharacteristics>& key_characteristics, SecurityLevel securityLevel) {
+ auto found = std::find_if(
+ key_characteristics.begin(), key_characteristics.end(),
+ [securityLevel](auto& entry) { return entry.securityLevel == securityLevel; });
+ return (found == key_characteristics.end()) ? kEmptyAuthList : found->authorizations;
+}
+
+AuthorizationSet KeyMintAidlTestBase::HwEnforcedAuthorizations(
+ const vector<KeyCharacteristics>& key_characteristics) {
+ AuthorizationSet authList;
+ for (auto& entry : key_characteristics) {
+ if (entry.securityLevel == SecurityLevel::STRONGBOX ||
+ entry.securityLevel == SecurityLevel::TRUSTED_ENVIRONMENT) {
+ authList.push_back(AuthorizationSet(entry.authorizations));
+ }
+ }
+ return authList;
+}
+
+AuthorizationSet KeyMintAidlTestBase::SwEnforcedAuthorizations(
+ const vector<KeyCharacteristics>& key_characteristics) {
+ AuthorizationSet authList;
+ for (auto& entry : key_characteristics) {
+ if (entry.securityLevel == SecurityLevel::SOFTWARE ||
+ entry.securityLevel == SecurityLevel::KEYSTORE) {
+ authList.push_back(AuthorizationSet(entry.authorizations));
+ }
+ }
+ return authList;
+}
+
+ErrorCode KeyMintAidlTestBase::UseAesKey(const vector<uint8_t>& aesKeyBlob) {
+ auto [result, ciphertext, out_params] = ProcessMessage(
+ aesKeyBlob, KeyPurpose::ENCRYPT, "1234567890123456",
+ AuthorizationSetBuilder().BlockMode(BlockMode::ECB).Padding(PaddingMode::NONE));
+ return result;
+}
+
+ErrorCode KeyMintAidlTestBase::UseHmacKey(const vector<uint8_t>& hmacKeyBlob) {
+ auto [result, mac, out_params] = ProcessMessage(
+ hmacKeyBlob, KeyPurpose::SIGN, "1234567890123456",
+ AuthorizationSetBuilder().Authorization(TAG_MAC_LENGTH, 128).Digest(Digest::SHA_2_256));
+ return result;
+}
+
+ErrorCode KeyMintAidlTestBase::UseRsaKey(const vector<uint8_t>& rsaKeyBlob) {
+ std::string message(2048 / 8, 'a');
+ auto [result, signature, out_params] = ProcessMessage(
+ rsaKeyBlob, KeyPurpose::SIGN, message,
+ AuthorizationSetBuilder().Digest(Digest::NONE).Padding(PaddingMode::NONE));
+ return result;
+}
+
+ErrorCode KeyMintAidlTestBase::UseEcdsaKey(const vector<uint8_t>& ecdsaKeyBlob) {
+ auto [result, signature, out_params] =
+ ProcessMessage(ecdsaKeyBlob, KeyPurpose::SIGN, "a",
+ AuthorizationSetBuilder().Digest(Digest::SHA_2_256));
+ return result;
+}
+
+bool verify_attestation_record(const string& challenge, //
+ const string& app_id, //
+ AuthorizationSet expected_sw_enforced, //
+ AuthorizationSet expected_hw_enforced, //
+ SecurityLevel security_level,
+ const vector<uint8_t>& attestation_cert) {
+ X509_Ptr cert(parse_cert_blob(attestation_cert));
+ EXPECT_TRUE(!!cert.get());
+ if (!cert.get()) return false;
+
+ ASN1_OCTET_STRING* attest_rec = get_attestation_record(cert.get());
+ EXPECT_TRUE(!!attest_rec);
+ if (!attest_rec) return false;
+
+ AuthorizationSet att_sw_enforced;
+ AuthorizationSet att_hw_enforced;
+ uint32_t att_attestation_version;
+ uint32_t att_keymaster_version;
+ SecurityLevel att_attestation_security_level;
+ SecurityLevel att_keymaster_security_level;
+ vector<uint8_t> att_challenge;
+ vector<uint8_t> att_unique_id;
+ vector<uint8_t> att_app_id;
+
+ auto error = parse_attestation_record(attest_rec->data, //
+ attest_rec->length, //
+ &att_attestation_version, //
+ &att_attestation_security_level, //
+ &att_keymaster_version, //
+ &att_keymaster_security_level, //
+ &att_challenge, //
+ &att_sw_enforced, //
+ &att_hw_enforced, //
+ &att_unique_id);
+ EXPECT_EQ(ErrorCode::OK, error);
+ if (error != ErrorCode::OK) return false;
+
+ EXPECT_GE(att_attestation_version, 3U);
+
+ expected_sw_enforced.push_back(TAG_ATTESTATION_APPLICATION_ID,
+ vector<uint8_t>(app_id.begin(), app_id.end()));
+
+ EXPECT_GE(att_keymaster_version, 4U);
+ EXPECT_EQ(security_level, att_keymaster_security_level);
+ EXPECT_EQ(security_level, att_attestation_security_level);
+
+ EXPECT_EQ(challenge.length(), att_challenge.size());
+ EXPECT_EQ(0, memcmp(challenge.data(), att_challenge.data(), challenge.length()));
+
+ char property_value[PROPERTY_VALUE_MAX] = {};
+ // TODO(b/136282179): When running under VTS-on-GSI the TEE-backed
+ // keymaster implementation will report YYYYMM dates instead of YYYYMMDD
+ // for the BOOT_PATCH_LEVEL.
+ if (avb_verification_enabled()) {
+ for (int i = 0; i < att_hw_enforced.size(); i++) {
+ if (att_hw_enforced[i].tag == TAG_BOOT_PATCHLEVEL ||
+ att_hw_enforced[i].tag == TAG_VENDOR_PATCHLEVEL) {
+ std::string date =
+ std::to_string(att_hw_enforced[i].value.get<KeyParameterValue::dateTime>());
+ // strptime seems to require delimiters, but the tag value will
+ // be YYYYMMDD
+ date.insert(6, "-");
+ date.insert(4, "-");
+ EXPECT_EQ(date.size(), 10);
+ struct tm time;
+ strptime(date.c_str(), "%Y-%m-%d", &time);
+
+ // Day of the month (0-31)
+ EXPECT_GE(time.tm_mday, 0);
+ EXPECT_LT(time.tm_mday, 32);
+ // Months since Jan (0-11)
+ EXPECT_GE(time.tm_mon, 0);
+ EXPECT_LT(time.tm_mon, 12);
+ // Years since 1900
+ EXPECT_GT(time.tm_year, 110);
+ EXPECT_LT(time.tm_year, 200);
+ }
+ }
+ }
+
+ // Check to make sure boolean values are properly encoded. Presence of a boolean tag
+ // indicates true. A provided boolean tag that can be pulled back out of the certificate
+ // indicates correct encoding. No need to check if it's in both lists, since the
+ // AuthorizationSet compare below will handle mismatches of tags.
+ if (security_level == SecurityLevel::SOFTWARE) {
+ EXPECT_TRUE(expected_sw_enforced.Contains(TAG_NO_AUTH_REQUIRED));
+ } else {
+ EXPECT_TRUE(expected_hw_enforced.Contains(TAG_NO_AUTH_REQUIRED));
+ }
+
+ // Alternatively this checks the opposite - a false boolean tag (one that isn't provided in
+ // the authorization list during key generation) isn't being attested to in the certificate.
+ EXPECT_FALSE(expected_sw_enforced.Contains(TAG_TRUSTED_USER_PRESENCE_REQUIRED));
+ EXPECT_FALSE(att_sw_enforced.Contains(TAG_TRUSTED_USER_PRESENCE_REQUIRED));
+ EXPECT_FALSE(expected_hw_enforced.Contains(TAG_TRUSTED_USER_PRESENCE_REQUIRED));
+ EXPECT_FALSE(att_hw_enforced.Contains(TAG_TRUSTED_USER_PRESENCE_REQUIRED));
+
+ if (att_hw_enforced.Contains(TAG_ALGORITHM, Algorithm::EC)) {
+ // For ECDSA keys, either an EC_CURVE or a KEY_SIZE can be specified, but one must be.
+ EXPECT_TRUE(att_hw_enforced.Contains(TAG_EC_CURVE) ||
+ att_hw_enforced.Contains(TAG_KEY_SIZE));
+ }
+
+ // Test root of trust elements
+ vector<uint8_t> verified_boot_key;
+ VerifiedBoot verified_boot_state;
+ bool device_locked;
+ vector<uint8_t> verified_boot_hash;
+ error = parse_root_of_trust(attest_rec->data, attest_rec->length, &verified_boot_key,
+ &verified_boot_state, &device_locked, &verified_boot_hash);
+ EXPECT_EQ(ErrorCode::OK, error);
+
+ if (avb_verification_enabled()) {
+ EXPECT_NE(property_get("ro.boot.vbmeta.digest", property_value, ""), 0);
+ string prop_string(property_value);
+ EXPECT_EQ(prop_string.size(), 64);
+ EXPECT_EQ(prop_string, bin2hex(verified_boot_hash));
+
+ EXPECT_NE(property_get("ro.boot.vbmeta.device_state", property_value, ""), 0);
+ if (!strcmp(property_value, "unlocked")) {
+ EXPECT_FALSE(device_locked);
+ } else {
+ EXPECT_TRUE(device_locked);
+ }
+
+ // Check that the device is locked if not debuggable, e.g., user build
+ // images in CTS. For VTS, debuggable images are used to allow adb root
+ // and the device is unlocked.
+ if (!property_get_bool("ro.debuggable", false)) {
+ EXPECT_TRUE(device_locked);
+ } else {
+ EXPECT_FALSE(device_locked);
+ }
+ }
+
+ // Verified boot key should be all 0's if the boot state is not verified or self signed
+ std::string empty_boot_key(32, '\0');
+ std::string verified_boot_key_str((const char*)verified_boot_key.data(),
+ verified_boot_key.size());
+ EXPECT_NE(property_get("ro.boot.verifiedbootstate", property_value, ""), 0);
+ if (!strcmp(property_value, "green")) {
+ EXPECT_EQ(verified_boot_state, VerifiedBoot::VERIFIED);
+ EXPECT_NE(0, memcmp(verified_boot_key.data(), empty_boot_key.data(),
+ verified_boot_key.size()));
+ } else if (!strcmp(property_value, "yellow")) {
+ EXPECT_EQ(verified_boot_state, VerifiedBoot::SELF_SIGNED);
+ EXPECT_NE(0, memcmp(verified_boot_key.data(), empty_boot_key.data(),
+ verified_boot_key.size()));
+ } else if (!strcmp(property_value, "orange")) {
+ EXPECT_EQ(verified_boot_state, VerifiedBoot::UNVERIFIED);
+ EXPECT_EQ(0, memcmp(verified_boot_key.data(), empty_boot_key.data(),
+ verified_boot_key.size()));
+ } else if (!strcmp(property_value, "red")) {
+ EXPECT_EQ(verified_boot_state, VerifiedBoot::FAILED);
+ } else {
+ EXPECT_EQ(verified_boot_state, VerifiedBoot::UNVERIFIED);
+ EXPECT_NE(0, memcmp(verified_boot_key.data(), empty_boot_key.data(),
+ verified_boot_key.size()));
+ }
+
+ att_sw_enforced.Sort();
+ expected_sw_enforced.Sort();
+ auto a = filtered_tags(expected_sw_enforced);
+ auto b = filtered_tags(att_sw_enforced);
+ EXPECT_EQ(a, b);
+
+ att_hw_enforced.Sort();
+ expected_hw_enforced.Sort();
+ EXPECT_EQ(filtered_tags(expected_hw_enforced), filtered_tags(att_hw_enforced));
+
+ return true;
+}
+
+string bin2hex(const vector<uint8_t>& data) {
+ string retval;
+ retval.reserve(data.size() * 2 + 1);
+ for (uint8_t byte : data) {
+ retval.push_back(nibble2hex[0x0F & (byte >> 4)]);
+ retval.push_back(nibble2hex[0x0F & byte]);
+ }
+ return retval;
+}
+
+AssertionResult ChainSignaturesAreValid(const vector<Certificate>& chain) {
+ std::stringstream cert_data;
+
+ for (size_t i = 0; i < chain.size(); ++i) {
+ cert_data << bin2hex(chain[i].encodedCertificate) << std::endl;
+
+ X509_Ptr key_cert(parse_cert_blob(chain[i].encodedCertificate));
+ X509_Ptr signing_cert;
+ if (i < chain.size() - 1) {
+ signing_cert = parse_cert_blob(chain[i + 1].encodedCertificate);
+ } else {
+ signing_cert = parse_cert_blob(chain[i].encodedCertificate);
+ }
+ if (!key_cert.get() || !signing_cert.get()) return AssertionFailure() << cert_data.str();
+
+ EVP_PKEY_Ptr signing_pubkey(X509_get_pubkey(signing_cert.get()));
+ if (!signing_pubkey.get()) return AssertionFailure() << cert_data.str();
+
+ if (!X509_verify(key_cert.get(), signing_pubkey.get())) {
+ return AssertionFailure()
+ << "Verification of certificate " << i << " failed "
+ << "OpenSSL error string: " << ERR_error_string(ERR_get_error(), NULL) << '\n'
+ << cert_data.str();
+ }
+
+ string cert_issuer = x509NameToStr(X509_get_issuer_name(key_cert.get()));
+ string signer_subj = x509NameToStr(X509_get_subject_name(signing_cert.get()));
+ if (cert_issuer != signer_subj) {
+ return AssertionFailure() << "Cert " << i << " has wrong issuer.\n" << cert_data.str();
+ }
+
+ if (i == 0) {
+ string cert_sub = x509NameToStr(X509_get_subject_name(key_cert.get()));
+ if ("/CN=Android Keystore Key" != cert_sub) {
+ return AssertionFailure()
+ << "Leaf cert has wrong subject, should be CN=Android Keystore Key, was "
+ << cert_sub << '\n'
+ << cert_data.str();
+ }
+ }
+ }
+
+ if (KeyMintAidlTestBase::dump_Attestations) std::cout << cert_data.str();
+ return AssertionSuccess();
+}
+
+X509_Ptr parse_cert_blob(const vector<uint8_t>& blob) {
+ const uint8_t* p = blob.data();
+ return X509_Ptr(d2i_X509(nullptr /* allocate new */, &p, blob.size()));
+}
+
} // namespace test
} // namespace aidl::android::hardware::security::keymint
diff --git a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h
index f73c26d..452d2b6 100644
--- a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h
+++ b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h
@@ -21,26 +21,43 @@
#include <binder/IServiceManager.h>
#include <binder/ProcessState.h>
#include <gtest/gtest.h>
+#include <openssl/x509.h>
#include <aidl/android/hardware/security/keymint/ErrorCode.h>
#include <aidl/android/hardware/security/keymint/IKeyMintDevice.h>
#include <keymint_support/authorization_set.h>
+#include <keymint_support/openssl_utils.h>
-namespace aidl::android::hardware::security::keymint::test {
+namespace aidl::android::hardware::security::keymint {
+
+::std::ostream& operator<<(::std::ostream& os, const AuthorizationSet& set);
+
+inline bool operator==(const keymint::AuthorizationSet& a, const keymint::AuthorizationSet& b) {
+ return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin());
+}
+
+namespace test {
using ::android::sp;
using Status = ::ndk::ScopedAStatus;
+using ::std::optional;
using ::std::shared_ptr;
using ::std::string;
using ::std::vector;
constexpr uint64_t kOpHandleSentinel = 0xFFFFFFFFFFFFFFFF;
-::std::ostream& operator<<(::std::ostream& os, const AuthorizationSet& set);
-
class KeyMintAidlTestBase : public ::testing::TestWithParam<string> {
public:
+ struct KeyData {
+ vector<uint8_t> blob;
+ vector<KeyCharacteristics> characteristics;
+ };
+
+ static bool arm_deleteAllKeys;
+ static bool dump_Attestations;
+
void SetUp() override;
void TearDown() override {
if (key_blob_.size()) {
@@ -55,14 +72,22 @@
uint32_t os_patch_level() { return os_patch_level_; }
ErrorCode GetReturnErrorCode(const Status& result);
- ErrorCode GenerateKey(const AuthorizationSet& key_desc, vector<uint8_t>* key_blob,
- KeyCharacteristics* key_characteristics);
- ErrorCode GenerateKey(const AuthorizationSet& key_desc);
+ ErrorCode GenerateKey(const AuthorizationSet& key_desc, vector<uint8_t>* key_blob,
+ vector<KeyCharacteristics>* key_characteristics) {
+ return GenerateKey(key_desc, std::nullopt /* attest_key */, key_blob, key_characteristics,
+ &cert_chain_);
+ }
+ ErrorCode GenerateKey(const AuthorizationSet& key_desc,
+ const optional<AttestationKey>& attest_key, vector<uint8_t>* key_blob,
+ vector<KeyCharacteristics>* key_characteristics,
+ vector<Certificate>* cert_chain);
+ ErrorCode GenerateKey(const AuthorizationSet& key_desc,
+ const optional<AttestationKey>& attest_key = std::nullopt);
ErrorCode ImportKey(const AuthorizationSet& key_desc, KeyFormat format,
const string& key_material, vector<uint8_t>* key_blob,
- KeyCharacteristics* key_characteristics);
+ vector<KeyCharacteristics>* key_characteristics);
ErrorCode ImportKey(const AuthorizationSet& key_desc, KeyFormat format,
const string& key_material);
@@ -104,7 +129,9 @@
string ProcessMessage(const vector<uint8_t>& key_blob, KeyPurpose operation,
const string& message, const AuthorizationSet& in_params,
AuthorizationSet* out_params);
-
+ std::tuple<ErrorCode, std::string /* processedMessage */, AuthorizationSet /* out_params */>
+ ProcessMessage(const vector<uint8_t>& key_blob, KeyPurpose operation,
+ const std::string& message, const AuthorizationSet& in_params);
string SignMessage(const vector<uint8_t>& key_blob, const string& message,
const AuthorizationSet& params);
string SignMessage(const string& message, const AuthorizationSet& params);
@@ -147,8 +174,58 @@
std::pair<ErrorCode, vector<uint8_t>> UpgradeKey(const vector<uint8_t>& key_blob);
- bool IsSecure() { return securityLevel_ != SecurityLevel::SOFTWARE; }
- SecurityLevel SecLevel() { return securityLevel_; }
+ template <typename TagType>
+ std::tuple<KeyData /* aesKey */, KeyData /* hmacKey */, KeyData /* rsaKey */,
+ KeyData /* ecdsaKey */>
+ CreateTestKeys(TagType tagToTest, ErrorCode expectedReturn) {
+ /* AES */
+ KeyData aesKeyData;
+ ErrorCode errorCode = GenerateKey(AuthorizationSetBuilder()
+ .AesEncryptionKey(128)
+ .Authorization(tagToTest)
+ .BlockMode(BlockMode::ECB)
+ .Padding(PaddingMode::NONE)
+ .Authorization(TAG_NO_AUTH_REQUIRED),
+ &aesKeyData.blob, &aesKeyData.characteristics);
+ EXPECT_EQ(expectedReturn, errorCode);
+
+ /* HMAC */
+ KeyData hmacKeyData;
+ errorCode = GenerateKey(AuthorizationSetBuilder()
+ .HmacKey(128)
+ .Authorization(tagToTest)
+ .Digest(Digest::SHA_2_256)
+ .Authorization(TAG_MIN_MAC_LENGTH, 128)
+ .Authorization(TAG_NO_AUTH_REQUIRED),
+ &hmacKeyData.blob, &hmacKeyData.characteristics);
+ EXPECT_EQ(expectedReturn, errorCode);
+
+ /* RSA */
+ KeyData rsaKeyData;
+ errorCode = GenerateKey(AuthorizationSetBuilder()
+ .RsaSigningKey(2048, 65537)
+ .Authorization(tagToTest)
+ .Digest(Digest::NONE)
+ .Padding(PaddingMode::NONE)
+ .Authorization(TAG_NO_AUTH_REQUIRED)
+ .SetDefaultValidity(),
+ &rsaKeyData.blob, &rsaKeyData.characteristics);
+ EXPECT_EQ(expectedReturn, errorCode);
+
+ /* ECDSA */
+ KeyData ecdsaKeyData;
+ errorCode = GenerateKey(AuthorizationSetBuilder()
+ .EcdsaSigningKey(256)
+ .Authorization(tagToTest)
+ .Digest(Digest::SHA_2_256)
+ .Authorization(TAG_NO_AUTH_REQUIRED)
+ .SetDefaultValidity(),
+ &ecdsaKeyData.blob, &ecdsaKeyData.characteristics);
+ EXPECT_EQ(expectedReturn, errorCode);
+ return {aesKeyData, hmacKeyData, rsaKeyData, ecdsaKeyData};
+ }
+ bool IsSecure() const { return securityLevel_ != SecurityLevel::SOFTWARE; }
+ SecurityLevel SecLevel() const { return securityLevel_; }
vector<uint32_t> ValidKeySizes(Algorithm algorithm);
vector<uint32_t> InvalidKeySizes(Algorithm algorithm);
@@ -164,9 +241,26 @@
}
std::shared_ptr<IKeyMintOperation> op_;
- vector<Certificate> certChain_;
+ vector<Certificate> cert_chain_;
vector<uint8_t> key_blob_;
- KeyCharacteristics key_characteristics_;
+ vector<KeyCharacteristics> key_characteristics_;
+
+ const vector<KeyParameter>& SecLevelAuthorizations(
+ const vector<KeyCharacteristics>& key_characteristics);
+ inline const vector<KeyParameter>& SecLevelAuthorizations() {
+ return SecLevelAuthorizations(key_characteristics_);
+ }
+ const vector<KeyParameter>& SecLevelAuthorizations(
+ const vector<KeyCharacteristics>& key_characteristics, SecurityLevel securityLevel);
+
+ AuthorizationSet HwEnforcedAuthorizations(
+ const vector<KeyCharacteristics>& key_characteristics);
+ AuthorizationSet SwEnforcedAuthorizations(
+ const vector<KeyCharacteristics>& key_characteristics);
+ ErrorCode UseAesKey(const vector<uint8_t>& aesKeyBlob);
+ ErrorCode UseHmacKey(const vector<uint8_t>& hmacKeyBlob);
+ ErrorCode UseRsaKey(const vector<uint8_t>& rsaKeyBlob);
+ ErrorCode UseEcdsaKey(const vector<uint8_t>& ecdsaKeyBlob);
private:
std::shared_ptr<IKeyMintDevice> keymint_;
@@ -179,9 +273,21 @@
long challenge_;
};
+bool verify_attestation_record(const string& challenge, //
+ const string& app_id, //
+ AuthorizationSet expected_sw_enforced, //
+ AuthorizationSet expected_hw_enforced, //
+ SecurityLevel security_level,
+ const vector<uint8_t>& attestation_cert);
+string bin2hex(const vector<uint8_t>& data);
+X509_Ptr parse_cert_blob(const vector<uint8_t>& blob);
+::testing::AssertionResult ChainSignaturesAreValid(const vector<Certificate>& chain);
+
#define INSTANTIATE_KEYMINT_AIDL_TEST(name) \
INSTANTIATE_TEST_SUITE_P(PerInstance, name, \
testing::ValuesIn(KeyMintAidlTestBase::build_params()), \
::android::PrintInstanceNameToString)
-} // namespace aidl::android::hardware::security::keymint::test
+} // namespace test
+
+} // namespace aidl::android::hardware::security::keymint
diff --git a/security/keymint/aidl/vts/functional/KeyMintTest.cpp b/security/keymint/aidl/vts/functional/KeyMintTest.cpp
index 3060153..71aae90 100644
--- a/security/keymint/aidl/vts/functional/KeyMintTest.cpp
+++ b/security/keymint/aidl/vts/functional/KeyMintTest.cpp
@@ -14,41 +14,30 @@
* limitations under the License.
*/
-#define LOG_TAG "keymint_5_test"
+#define LOG_TAG "keymint_1_test"
#include <cutils/log.h>
#include <signal.h>
#include <iostream>
+#include <openssl/ec.h>
#include <openssl/evp.h>
#include <openssl/mem.h>
-#include <openssl/x509.h>
+#include <openssl/x509v3.h>
#include <cutils/properties.h>
#include <aidl/android/hardware/security/keymint/KeyFormat.h>
-#include <keymint_support/attestation_record.h>
#include <keymint_support/key_param_output.h>
#include <keymint_support/openssl_utils.h>
#include "KeyMintAidlTestBase.h"
-static bool arm_deleteAllKeys = false;
-static bool dump_Attestations = false;
-
using aidl::android::hardware::security::keymint::AuthorizationSet;
using aidl::android::hardware::security::keymint::KeyCharacteristics;
using aidl::android::hardware::security::keymint::KeyFormat;
-namespace aidl::android::hardware::security::keymint {
-
-bool operator==(const keymint::AuthorizationSet& a, const keymint::AuthorizationSet& b) {
- return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin());
-}
-
-} // namespace aidl::android::hardware::security::keymint
-
namespace std {
using namespace aidl::android::hardware::security::keymint;
@@ -56,18 +45,16 @@
template <>
struct std::equal_to<KeyCharacteristics> {
bool operator()(const KeyCharacteristics& a, const KeyCharacteristics& b) const {
- // This isn't very efficient. Oh, well.
- AuthorizationSet a_sw(a.softwareEnforced);
- AuthorizationSet b_sw(b.softwareEnforced);
- AuthorizationSet a_tee(b.hardwareEnforced);
- AuthorizationSet b_tee(b.hardwareEnforced);
+ if (a.securityLevel != b.securityLevel) return false;
- a_sw.Sort();
- b_sw.Sort();
- a_tee.Sort();
- b_tee.Sort();
+ // this isn't very efficient. Oh, well.
+ AuthorizationSet a_auths(a.authorizations);
+ AuthorizationSet b_auths(b.authorizations);
- return ((a_sw == b_sw) && (a_tee == b_tee));
+ a_auths.Sort();
+ b_auths.Sort();
+
+ return a_auths == b_auths;
}
};
@@ -78,7 +65,8 @@
namespace {
template <TagType tag_type, Tag tag, typename ValueT>
-bool contains(vector<KeyParameter>& set, TypedTag<tag_type, tag> ttag, ValueT expected_value) {
+bool contains(const vector<KeyParameter>& set, TypedTag<tag_type, tag> ttag,
+ ValueT expected_value) {
auto it = std::find_if(set.begin(), set.end(), [&](const KeyParameter& param) {
if (auto p = authorizationValue(ttag, param)) {
return *p == expected_value;
@@ -89,7 +77,7 @@
}
template <TagType tag_type, Tag tag>
-bool contains(vector<KeyParameter>& set, TypedTag<tag_type, tag>) {
+bool contains(const vector<KeyParameter>& set, TypedTag<tag_type, tag>) {
auto it = std::find_if(set.begin(), set.end(),
[&](const KeyParameter& param) { return param.tag == tag; });
return (it != set.end());
@@ -182,10 +170,6 @@
void operator()(RSA* p) { RSA_free(p); }
};
-/* TODO(seleneh) add attestation verification codes like verify_chain() and
- * attestation tests after we decided on the keymint 1 attestation changes.
- */
-
std::string make_string(const uint8_t* data, size_t length) {
return std::string(reinterpret_cast<const char*>(data), length);
}
@@ -229,19 +213,20 @@
class NewKeyGenerationTest : public KeyMintAidlTestBase {
protected:
- void CheckBaseParams(const KeyCharacteristics& keyCharacteristics) {
+ void CheckBaseParams(const vector<KeyCharacteristics>& keyCharacteristics) {
// TODO(swillden): Distinguish which params should be in which auth list.
- AuthorizationSet auths(keyCharacteristics.hardwareEnforced);
- auths.push_back(AuthorizationSet(keyCharacteristics.softwareEnforced));
+ AuthorizationSet auths;
+ for (auto& entry : keyCharacteristics) {
+ auths.push_back(AuthorizationSet(entry.authorizations));
+ }
EXPECT_TRUE(auths.Contains(TAG_ORIGIN, KeyOrigin::GENERATED));
EXPECT_TRUE(auths.Contains(TAG_PURPOSE, KeyPurpose::SIGN));
EXPECT_TRUE(auths.Contains(TAG_PURPOSE, KeyPurpose::VERIFY));
- // Verify that App ID, App data and ROT are NOT included.
+ // Verify that App data and ROT are NOT included.
EXPECT_FALSE(auths.Contains(TAG_ROOT_OF_TRUST));
- EXPECT_FALSE(auths.Contains(TAG_APPLICATION_ID));
EXPECT_FALSE(auths.Contains(TAG_APPLICATION_DATA));
// Check that some unexpected tags/values are NOT present.
@@ -249,15 +234,13 @@
EXPECT_FALSE(auths.Contains(TAG_PURPOSE, KeyPurpose::DECRYPT));
EXPECT_FALSE(auths.Contains(TAG_AUTH_TIMEOUT, 301U));
- // Now check that unspecified, defaulted tags are correct.
- EXPECT_TRUE(auths.Contains(TAG_CREATION_DATETIME));
+ auto os_ver = auths.GetTagValue(TAG_OS_VERSION);
+ ASSERT_TRUE(os_ver);
+ EXPECT_EQ(*os_ver, os_version());
- EXPECT_TRUE(auths.Contains(TAG_OS_VERSION, os_version()))
- << "OS version is " << os_version() << " key reported "
- << auths.GetTagValue(TAG_OS_VERSION)->get();
- EXPECT_TRUE(auths.Contains(TAG_OS_PATCHLEVEL, os_patch_level()))
- << "OS patch level is " << os_patch_level() << " key reported "
- << auths.GetTagValue(TAG_OS_PATCHLEVEL)->get();
+ auto os_pl = auths.GetTagValue(TAG_OS_PATCHLEVEL);
+ ASSERT_TRUE(os_pl);
+ EXPECT_EQ(*os_pl, os_patch_level());
}
};
@@ -270,22 +253,18 @@
TEST_P(NewKeyGenerationTest, Rsa) {
for (auto key_size : ValidKeySizes(Algorithm::RSA)) {
vector<uint8_t> key_blob;
- KeyCharacteristics key_characteristics;
+ vector<KeyCharacteristics> key_characteristics;
ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
.RsaSigningKey(key_size, 65537)
.Digest(Digest::NONE)
- .Padding(PaddingMode::NONE),
+ .Padding(PaddingMode::NONE)
+ .SetDefaultValidity(),
&key_blob, &key_characteristics));
ASSERT_GT(key_blob.size(), 0U);
CheckBaseParams(key_characteristics);
- AuthorizationSet crypto_params;
- if (IsSecure()) {
- crypto_params = key_characteristics.hardwareEnforced;
- } else {
- crypto_params = key_characteristics.softwareEnforced;
- }
+ AuthorizationSet crypto_params = SecLevelAuthorizations(key_characteristics);
EXPECT_TRUE(crypto_params.Contains(TAG_ALGORITHM, Algorithm::RSA));
EXPECT_TRUE(crypto_params.Contains(TAG_KEY_SIZE, key_size))
@@ -297,6 +276,148 @@
}
/*
+ * NewKeyGenerationTest.RsaWithAttestation
+ *
+ * Verifies that keymint can generate all required RSA key sizes, and that the resulting keys
+ * have correct characteristics.
+ */
+TEST_P(NewKeyGenerationTest, RsaWithAttestation) {
+ for (auto key_size : ValidKeySizes(Algorithm::RSA)) {
+ auto challenge = "hello";
+ auto app_id = "foo";
+
+ vector<uint8_t> key_blob;
+ vector<KeyCharacteristics> key_characteristics;
+ ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+ .RsaSigningKey(key_size, 65537)
+ .Digest(Digest::NONE)
+ .Padding(PaddingMode::NONE)
+ .AttestationChallenge(challenge)
+ .AttestationApplicationId(app_id)
+ .Authorization(TAG_NO_AUTH_REQUIRED)
+ .SetDefaultValidity(),
+ &key_blob, &key_characteristics));
+
+ ASSERT_GT(key_blob.size(), 0U);
+ CheckBaseParams(key_characteristics);
+
+ AuthorizationSet crypto_params = SecLevelAuthorizations(key_characteristics);
+
+ EXPECT_TRUE(crypto_params.Contains(TAG_ALGORITHM, Algorithm::RSA));
+ EXPECT_TRUE(crypto_params.Contains(TAG_KEY_SIZE, key_size))
+ << "Key size " << key_size << "missing";
+ EXPECT_TRUE(crypto_params.Contains(TAG_RSA_PUBLIC_EXPONENT, 65537U));
+
+ EXPECT_TRUE(ChainSignaturesAreValid(cert_chain_));
+ ASSERT_GT(cert_chain_.size(), 0);
+
+ AuthorizationSet hw_enforced = HwEnforcedAuthorizations(key_characteristics);
+ AuthorizationSet sw_enforced = SwEnforcedAuthorizations(key_characteristics);
+ EXPECT_TRUE(verify_attestation_record(challenge, app_id, //
+ sw_enforced, hw_enforced, SecLevel(),
+ cert_chain_[0].encodedCertificate));
+
+ CheckedDeleteKey(&key_blob);
+ }
+}
+
+/*
+ * NewKeyGenerationTest.LimitedUsageRsa
+ *
+ * Verifies that KeyMint can generate all required RSA key sizes with limited usage, and that the
+ * resulting keys have correct characteristics.
+ */
+TEST_P(NewKeyGenerationTest, LimitedUsageRsa) {
+ for (auto key_size : ValidKeySizes(Algorithm::RSA)) {
+ vector<uint8_t> key_blob;
+ vector<KeyCharacteristics> key_characteristics;
+ ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+ .RsaSigningKey(key_size, 65537)
+ .Digest(Digest::NONE)
+ .Padding(PaddingMode::NONE)
+ .Authorization(TAG_USAGE_COUNT_LIMIT, 1)
+ .SetDefaultValidity(),
+ &key_blob, &key_characteristics));
+
+ ASSERT_GT(key_blob.size(), 0U);
+ CheckBaseParams(key_characteristics);
+
+ AuthorizationSet crypto_params = SecLevelAuthorizations(key_characteristics);
+
+ EXPECT_TRUE(crypto_params.Contains(TAG_ALGORITHM, Algorithm::RSA));
+ EXPECT_TRUE(crypto_params.Contains(TAG_KEY_SIZE, key_size))
+ << "Key size " << key_size << "missing";
+ EXPECT_TRUE(crypto_params.Contains(TAG_RSA_PUBLIC_EXPONENT, 65537U));
+
+ // Check the usage count limit tag appears in the authorizations.
+ AuthorizationSet auths;
+ for (auto& entry : key_characteristics) {
+ auths.push_back(AuthorizationSet(entry.authorizations));
+ }
+ EXPECT_TRUE(auths.Contains(TAG_USAGE_COUNT_LIMIT, 1U))
+ << "key usage count limit " << 1U << " missing";
+
+ CheckedDeleteKey(&key_blob);
+ }
+}
+
+/*
+ * NewKeyGenerationTest.LimitedUsageRsaWithAttestation
+ *
+ * Verifies that KeyMint can generate all required RSA key sizes with limited usage, and that the
+ * resulting keys have correct characteristics and attestation.
+ */
+TEST_P(NewKeyGenerationTest, LimitedUsageRsaWithAttestation) {
+ for (auto key_size : ValidKeySizes(Algorithm::RSA)) {
+ auto challenge = "hello";
+ auto app_id = "foo";
+
+ vector<uint8_t> key_blob;
+ vector<KeyCharacteristics> key_characteristics;
+ ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+ .RsaSigningKey(key_size, 65537)
+ .Digest(Digest::NONE)
+ .Padding(PaddingMode::NONE)
+ .AttestationChallenge(challenge)
+ .AttestationApplicationId(app_id)
+ .Authorization(TAG_NO_AUTH_REQUIRED)
+ .Authorization(TAG_USAGE_COUNT_LIMIT, 1)
+ .SetDefaultValidity(),
+ &key_blob, &key_characteristics));
+
+ ASSERT_GT(key_blob.size(), 0U);
+ CheckBaseParams(key_characteristics);
+
+ AuthorizationSet crypto_params = SecLevelAuthorizations(key_characteristics);
+
+ EXPECT_TRUE(crypto_params.Contains(TAG_ALGORITHM, Algorithm::RSA));
+ EXPECT_TRUE(crypto_params.Contains(TAG_KEY_SIZE, key_size))
+ << "Key size " << key_size << "missing";
+ EXPECT_TRUE(crypto_params.Contains(TAG_RSA_PUBLIC_EXPONENT, 65537U));
+
+ // Check the usage count limit tag appears in the authorizations.
+ AuthorizationSet auths;
+ for (auto& entry : key_characteristics) {
+ auths.push_back(AuthorizationSet(entry.authorizations));
+ }
+ EXPECT_TRUE(auths.Contains(TAG_USAGE_COUNT_LIMIT, 1U))
+ << "key usage count limit " << 1U << " missing";
+
+ // Check the usage count limit tag also appears in the attestation.
+ EXPECT_TRUE(ChainSignaturesAreValid(cert_chain_));
+ ASSERT_GT(cert_chain_.size(), 0);
+
+ AuthorizationSet hw_enforced = HwEnforcedAuthorizations(key_characteristics);
+ AuthorizationSet sw_enforced = SwEnforcedAuthorizations(key_characteristics);
+ EXPECT_TRUE(verify_attestation_record(challenge, app_id, //
+ sw_enforced, hw_enforced, SecLevel(),
+ cert_chain_[0].encodedCertificate));
+
+ CheckedDeleteKey(&key_blob);
+ }
+}
+
+/*
* NewKeyGenerationTest.NoInvalidRsaSizes
*
* Verifies that keymint cannot generate any RSA key sizes that are designated as invalid.
@@ -304,12 +425,13 @@
TEST_P(NewKeyGenerationTest, NoInvalidRsaSizes) {
for (auto key_size : InvalidKeySizes(Algorithm::RSA)) {
vector<uint8_t> key_blob;
- KeyCharacteristics key_characteristics;
+ vector<KeyCharacteristics> key_characteristics;
ASSERT_EQ(ErrorCode::UNSUPPORTED_KEY_SIZE,
GenerateKey(AuthorizationSetBuilder()
.RsaSigningKey(key_size, 65537)
.Digest(Digest::NONE)
- .Padding(PaddingMode::NONE),
+ .Padding(PaddingMode::NONE)
+ .SetDefaultValidity(),
&key_blob, &key_characteristics));
}
}
@@ -325,7 +447,8 @@
GenerateKey(AuthorizationSetBuilder()
.Authorization(TAG_ALGORITHM, Algorithm::RSA)
.Authorization(TAG_RSA_PUBLIC_EXPONENT, 3U)
- .SigningKey()));
+ .SigningKey()
+ .SetDefaultValidity()));
}
/*
@@ -337,20 +460,16 @@
TEST_P(NewKeyGenerationTest, Ecdsa) {
for (auto key_size : ValidKeySizes(Algorithm::EC)) {
vector<uint8_t> key_blob;
- KeyCharacteristics key_characteristics;
- ASSERT_EQ(ErrorCode::OK,
- GenerateKey(
- AuthorizationSetBuilder().EcdsaSigningKey(key_size).Digest(Digest::NONE),
- &key_blob, &key_characteristics));
+ vector<KeyCharacteristics> key_characteristics;
+ ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+ .EcdsaSigningKey(key_size)
+ .Digest(Digest::NONE)
+ .SetDefaultValidity(),
+ &key_blob, &key_characteristics));
ASSERT_GT(key_blob.size(), 0U);
CheckBaseParams(key_characteristics);
- AuthorizationSet crypto_params;
- if (IsSecure()) {
- crypto_params = key_characteristics.hardwareEnforced;
- } else {
- crypto_params = key_characteristics.softwareEnforced;
- }
+ AuthorizationSet crypto_params = SecLevelAuthorizations(key_characteristics);
EXPECT_TRUE(crypto_params.Contains(TAG_ALGORITHM, Algorithm::EC));
EXPECT_TRUE(crypto_params.Contains(TAG_KEY_SIZE, key_size))
@@ -361,6 +480,44 @@
}
/*
+ * NewKeyGenerationTest.LimitedUsageEcdsa
+ *
+ * Verifies that KeyMint can generate all required EC key sizes with limited usage, and that the
+ * resulting keys have correct characteristics.
+ */
+TEST_P(NewKeyGenerationTest, LimitedUsageEcdsa) {
+ for (auto key_size : ValidKeySizes(Algorithm::EC)) {
+ vector<uint8_t> key_blob;
+ vector<KeyCharacteristics> key_characteristics;
+ ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+ .EcdsaSigningKey(key_size)
+ .Digest(Digest::NONE)
+ .Authorization(TAG_USAGE_COUNT_LIMIT, 1)
+ .SetDefaultValidity(),
+ &key_blob, &key_characteristics));
+
+ ASSERT_GT(key_blob.size(), 0U);
+ CheckBaseParams(key_characteristics);
+
+ AuthorizationSet crypto_params = SecLevelAuthorizations(key_characteristics);
+
+ EXPECT_TRUE(crypto_params.Contains(TAG_ALGORITHM, Algorithm::EC));
+ EXPECT_TRUE(crypto_params.Contains(TAG_KEY_SIZE, key_size))
+ << "Key size " << key_size << "missing";
+
+ // Check the usage count limit tag appears in the authorizations.
+ AuthorizationSet auths;
+ for (auto& entry : key_characteristics) {
+ auths.push_back(AuthorizationSet(entry.authorizations));
+ }
+ EXPECT_TRUE(auths.Contains(TAG_USAGE_COUNT_LIMIT, 1U))
+ << "key usage count limit " << 1U << " missing";
+
+ CheckedDeleteKey(&key_blob);
+ }
+}
+
+/*
* NewKeyGenerationTest.EcdsaDefaultSize
*
* Verifies that failing to specify a key size for EC key generation returns
@@ -371,7 +528,8 @@
GenerateKey(AuthorizationSetBuilder()
.Authorization(TAG_ALGORITHM, Algorithm::EC)
.SigningKey()
- .Digest(Digest::NONE)));
+ .Digest(Digest::NONE)
+ .SetDefaultValidity()));
}
/*
@@ -383,15 +541,18 @@
TEST_P(NewKeyGenerationTest, EcdsaInvalidSize) {
for (auto key_size : InvalidKeySizes(Algorithm::EC)) {
vector<uint8_t> key_blob;
- KeyCharacteristics key_characteristics;
- ASSERT_EQ(ErrorCode::UNSUPPORTED_KEY_SIZE,
- GenerateKey(
- AuthorizationSetBuilder().EcdsaSigningKey(key_size).Digest(Digest::NONE),
- &key_blob, &key_characteristics));
+ vector<KeyCharacteristics> key_characteristics;
+ ASSERT_EQ(ErrorCode::UNSUPPORTED_KEY_SIZE, GenerateKey(AuthorizationSetBuilder()
+ .EcdsaSigningKey(key_size)
+ .Digest(Digest::NONE)
+ .SetDefaultValidity(),
+ &key_blob, &key_characteristics));
}
- ASSERT_EQ(ErrorCode::UNSUPPORTED_KEY_SIZE,
- GenerateKey(AuthorizationSetBuilder().EcdsaSigningKey(190).Digest(Digest::NONE)));
+ ASSERT_EQ(ErrorCode::UNSUPPORTED_KEY_SIZE, GenerateKey(AuthorizationSetBuilder()
+ .EcdsaSigningKey(190)
+ .Digest(Digest::NONE)
+ .SetDefaultValidity()));
}
/*
@@ -407,7 +568,8 @@
GenerateKey(AuthorizationSetBuilder()
.EcdsaSigningKey(224)
.Authorization(TAG_EC_CURVE, EcCurve::P_256)
- .Digest(Digest::NONE)));
+ .Digest(Digest::NONE)
+ .SetDefaultValidity()));
}
/*
@@ -418,8 +580,10 @@
TEST_P(NewKeyGenerationTest, EcdsaAllValidSizes) {
auto valid_sizes = ValidKeySizes(Algorithm::EC);
for (size_t size : valid_sizes) {
- EXPECT_EQ(ErrorCode::OK,
- GenerateKey(AuthorizationSetBuilder().EcdsaSigningKey(size).Digest(Digest::NONE)))
+ EXPECT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+ .EcdsaSigningKey(size)
+ .Digest(Digest::NONE)
+ .SetDefaultValidity()))
<< "Failed to generate size: " << size;
CheckedDeleteKey();
}
@@ -438,8 +602,10 @@
digest = Digest::SHA_2_512;
}
for (auto curve : ValidCurves()) {
- EXPECT_EQ(ErrorCode::OK,
- GenerateKey(AuthorizationSetBuilder().EcdsaSigningKey(curve).Digest(digest)))
+ EXPECT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+ .EcdsaSigningKey(curve)
+ .Digest(digest)
+ .SetDefaultValidity()))
<< "Failed to generate key on curve: " << curve;
CheckedDeleteKey();
}
@@ -454,7 +620,7 @@
TEST_P(NewKeyGenerationTest, Hmac) {
for (auto digest : ValidDigests(false /* withNone */, true /* withMD5 */)) {
vector<uint8_t> key_blob;
- KeyCharacteristics key_characteristics;
+ vector<KeyCharacteristics> key_characteristics;
constexpr size_t key_size = 128;
ASSERT_EQ(ErrorCode::OK,
GenerateKey(
@@ -465,17 +631,48 @@
ASSERT_GT(key_blob.size(), 0U);
CheckBaseParams(key_characteristics);
- AuthorizationSet hardwareEnforced = key_characteristics.hardwareEnforced;
- AuthorizationSet softwareEnforced = key_characteristics.softwareEnforced;
- if (IsSecure()) {
- EXPECT_TRUE(hardwareEnforced.Contains(TAG_ALGORITHM, Algorithm::HMAC));
- EXPECT_TRUE(hardwareEnforced.Contains(TAG_KEY_SIZE, key_size))
- << "Key size " << key_size << "missing";
- } else {
- EXPECT_TRUE(softwareEnforced.Contains(TAG_ALGORITHM, Algorithm::HMAC));
- EXPECT_TRUE(softwareEnforced.Contains(TAG_KEY_SIZE, key_size))
- << "Key size " << key_size << "missing";
+ AuthorizationSet crypto_params = SecLevelAuthorizations(key_characteristics);
+ EXPECT_TRUE(crypto_params.Contains(TAG_ALGORITHM, Algorithm::HMAC));
+ EXPECT_TRUE(crypto_params.Contains(TAG_KEY_SIZE, key_size))
+ << "Key size " << key_size << "missing";
+
+ CheckedDeleteKey(&key_blob);
+ }
+}
+
+/*
+ * NewKeyGenerationTest.LimitedUsageHmac
+ *
+ * Verifies that KeyMint supports all required digests with limited usage Hmac, and that the
+ * resulting keys have correct characteristics.
+ */
+TEST_P(NewKeyGenerationTest, LimitedUsageHmac) {
+ for (auto digest : ValidDigests(false /* withNone */, true /* withMD5 */)) {
+ vector<uint8_t> key_blob;
+ vector<KeyCharacteristics> key_characteristics;
+ constexpr size_t key_size = 128;
+ ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+ .HmacKey(key_size)
+ .Digest(digest)
+ .Authorization(TAG_MIN_MAC_LENGTH, 128)
+ .Authorization(TAG_USAGE_COUNT_LIMIT, 1),
+ &key_blob, &key_characteristics));
+
+ ASSERT_GT(key_blob.size(), 0U);
+ CheckBaseParams(key_characteristics);
+
+ AuthorizationSet crypto_params = SecLevelAuthorizations(key_characteristics);
+ EXPECT_TRUE(crypto_params.Contains(TAG_ALGORITHM, Algorithm::HMAC));
+ EXPECT_TRUE(crypto_params.Contains(TAG_KEY_SIZE, key_size))
+ << "Key size " << key_size << "missing";
+
+ // Check the usage count limit tag appears in the authorizations.
+ AuthorizationSet auths;
+ for (auto& entry : key_characteristics) {
+ auths.push_back(AuthorizationSet(entry.authorizations));
}
+ EXPECT_TRUE(auths.Contains(TAG_USAGE_COUNT_LIMIT, 1U))
+ << "key usage count limit " << 1U << " missing";
CheckedDeleteKey(&key_blob);
}
@@ -591,7 +788,8 @@
.RsaSigningKey(2048, 65537)
.Digest(Digest::NONE)
.Padding(PaddingMode::NONE)
- .Authorization(TAG_NO_AUTH_REQUIRED)));
+ .Authorization(TAG_NO_AUTH_REQUIRED)
+ .SetDefaultValidity()));
string message = "12345678901234567890123456789012";
string signature = SignMessage(
message, AuthorizationSetBuilder().Digest(Digest::NONE).Padding(PaddingMode::NONE));
@@ -600,7 +798,7 @@
/*
* SigningOperationsTest.RsaUseRequiresCorrectAppIdAppData
*
- * Verifies that using an RSA key requires the correct app ID/data.
+ * Verifies that using an RSA key requires the correct app data.
*/
TEST_P(SigningOperationsTest, RsaUseRequiresCorrectAppIdAppData) {
ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
@@ -609,7 +807,8 @@
.Digest(Digest::NONE)
.Padding(PaddingMode::NONE)
.Authorization(TAG_APPLICATION_ID, "clientid")
- .Authorization(TAG_APPLICATION_DATA, "appdata")));
+ .Authorization(TAG_APPLICATION_DATA, "appdata")
+ .SetDefaultValidity()));
EXPECT_EQ(ErrorCode::INVALID_KEY_BLOB,
Begin(KeyPurpose::SIGN,
AuthorizationSetBuilder().Digest(Digest::NONE).Padding(PaddingMode::NONE)));
@@ -645,7 +844,8 @@
.RsaSigningKey(2048, 65537)
.Digest(Digest::SHA_2_256)
.Padding(PaddingMode::RSA_PSS)
- .Authorization(TAG_NO_AUTH_REQUIRED)));
+ .Authorization(TAG_NO_AUTH_REQUIRED)
+ .SetDefaultValidity()));
// Use large message, which won't work without digesting.
string message(1024, 'a');
string signature = SignMessage(
@@ -664,7 +864,8 @@
.RsaSigningKey(2048, 65537)
.Digest(Digest::NONE)
.Authorization(TAG_NO_AUTH_REQUIRED)
- .Padding(PaddingMode::NONE)));
+ .Padding(PaddingMode::NONE)
+ .SetDefaultValidity()));
string message = "12345678901234567890123456789012";
string signature;
@@ -683,13 +884,13 @@
*/
TEST_P(SigningOperationsTest, NoUserConfirmation) {
if (SecLevel() == SecurityLevel::STRONGBOX) return;
- ASSERT_EQ(ErrorCode::OK,
- GenerateKey(AuthorizationSetBuilder()
- .RsaSigningKey(1024, 65537)
- .Digest(Digest::NONE)
- .Padding(PaddingMode::NONE)
- .Authorization(TAG_NO_AUTH_REQUIRED)
- .Authorization(TAG_TRUSTED_CONFIRMATION_REQUIRED)));
+ ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+ .RsaSigningKey(1024, 65537)
+ .Digest(Digest::NONE)
+ .Padding(PaddingMode::NONE)
+ .Authorization(TAG_NO_AUTH_REQUIRED)
+ .Authorization(TAG_TRUSTED_CONFIRMATION_REQUIRED)
+ .SetDefaultValidity()));
const string message = "12345678901234567890123456789012";
EXPECT_EQ(ErrorCode::OK,
@@ -709,7 +910,8 @@
.RsaSigningKey(2048, 65537)
.Digest(Digest::SHA_2_256)
.Authorization(TAG_NO_AUTH_REQUIRED)
- .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN)));
+ .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN)
+ .SetDefaultValidity()));
string message(1024, 'a');
string signature = SignMessage(message, AuthorizationSetBuilder()
.Digest(Digest::SHA_2_256)
@@ -726,7 +928,8 @@
.RsaSigningKey(2048, 65537)
.Digest(Digest::NONE)
.Authorization(TAG_NO_AUTH_REQUIRED)
- .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN)));
+ .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN)
+ .SetDefaultValidity()));
string message(53, 'a');
string signature = SignMessage(message, AuthorizationSetBuilder()
.Digest(Digest::NONE)
@@ -744,7 +947,8 @@
.RsaSigningKey(2048, 65537)
.Digest(Digest::NONE)
.Authorization(TAG_NO_AUTH_REQUIRED)
- .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN)));
+ .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN)
+ .SetDefaultValidity()));
string message(257, 'a');
EXPECT_EQ(ErrorCode::OK,
@@ -774,7 +978,8 @@
.RsaSigningKey(1024, 65537)
.Digest(Digest::SHA_2_512)
.Authorization(TAG_NO_AUTH_REQUIRED)
- .Padding(PaddingMode::RSA_PSS)));
+ .Padding(PaddingMode::RSA_PSS)
+ .SetDefaultValidity()));
EXPECT_EQ(ErrorCode::INCOMPATIBLE_DIGEST,
Begin(KeyPurpose::SIGN, AuthorizationSetBuilder()
.Digest(Digest::SHA_2_512)
@@ -792,7 +997,8 @@
.RsaSigningKey(2048, 65537)
.Digest(Digest::NONE)
.Authorization(TAG_NO_AUTH_REQUIRED)
- .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN)));
+ .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN)
+ .SetDefaultValidity()));
// One byte too long
string message(2048 / 8 + 1, 'a');
ASSERT_EQ(ErrorCode::OK,
@@ -826,7 +1032,8 @@
.RsaSigningKey(2048, 65537)
.Digest(Digest::NONE)
.Authorization(TAG_NO_AUTH_REQUIRED)
- .Padding(PaddingMode::NONE)));
+ .Padding(PaddingMode::NONE)
+ .SetDefaultValidity()));
ASSERT_EQ(ErrorCode::OK,
Begin(KeyPurpose::SIGN,
@@ -851,7 +1058,8 @@
.RsaSigningKey(2048, 65537)
.Authorization(TAG_NO_AUTH_REQUIRED)
.Digest(Digest::SHA_2_256 /* supported digest */)
- .Padding(PaddingMode::PKCS7)));
+ .Padding(PaddingMode::PKCS7)
+ .SetDefaultValidity()));
ASSERT_EQ(
ErrorCode::UNSUPPORTED_PADDING_MODE,
Begin(KeyPurpose::SIGN,
@@ -868,7 +1076,8 @@
.RsaSigningKey(2048, 65537)
.Authorization(TAG_NO_AUTH_REQUIRED)
.Digest(Digest::NONE)
- .Padding(PaddingMode::RSA_PSS)));
+ .Padding(PaddingMode::RSA_PSS)
+ .SetDefaultValidity()));
ASSERT_EQ(ErrorCode::INCOMPATIBLE_DIGEST,
Begin(KeyPurpose::SIGN,
AuthorizationSetBuilder().Digest(Digest::NONE).Padding(PaddingMode::RSA_PSS)));
@@ -889,7 +1098,8 @@
.RsaKey(2048, 65537)
.Authorization(TAG_NO_AUTH_REQUIRED)
.SigningKey()
- .Digest(Digest::NONE)));
+ .Digest(Digest::NONE)
+ .SetDefaultValidity()));
ASSERT_EQ(ErrorCode::UNSUPPORTED_PADDING_MODE,
Begin(KeyPurpose::SIGN, AuthorizationSetBuilder().Digest(Digest::NONE)));
}
@@ -904,7 +1114,8 @@
.Authorization(TAG_NO_AUTH_REQUIRED)
.RsaSigningKey(2048, 65537)
.Digest(Digest::NONE)
- .Padding(PaddingMode::NONE)));
+ .Padding(PaddingMode::NONE)
+ .SetDefaultValidity()));
// Barely shorter
string message(2048 / 8 - 1, 'a');
@@ -925,7 +1136,8 @@
.Authorization(TAG_NO_AUTH_REQUIRED)
.RsaEncryptionKey(2048, 65537)
.Digest(Digest::NONE)
- .Padding(PaddingMode::NONE)));
+ .Padding(PaddingMode::NONE)
+ .SetDefaultValidity()));
ASSERT_EQ(ErrorCode::INCOMPATIBLE_PURPOSE,
Begin(KeyPurpose::SIGN,
AuthorizationSetBuilder().Digest(Digest::NONE).Padding(PaddingMode::NONE)));
@@ -942,7 +1154,8 @@
.Authorization(TAG_NO_AUTH_REQUIRED)
.RsaSigningKey(2048, 65537)
.Digest(Digest::NONE)
- .Padding(PaddingMode::NONE)));
+ .Padding(PaddingMode::NONE)
+ .SetDefaultValidity()));
// Largest possible message will always be larger than the public modulus.
string message(2048 / 8, static_cast<char>(0xff));
@@ -965,7 +1178,8 @@
ErrorCode error = GenerateKey(AuthorizationSetBuilder()
.Authorization(TAG_NO_AUTH_REQUIRED)
.EcdsaSigningKey(key_size)
- .Digest(digest));
+ .Digest(digest)
+ .SetDefaultValidity());
EXPECT_EQ(ErrorCode::OK, error) << "Failed to generate ECDSA key with size " << key_size
<< " and digest " << digest;
if (error != ErrorCode::OK) continue;
@@ -988,7 +1202,8 @@
ErrorCode error = GenerateKey(AuthorizationSetBuilder()
.Authorization(TAG_NO_AUTH_REQUIRED)
.EcdsaSigningKey(curve)
- .Digest(Digest::SHA_2_256));
+ .Digest(Digest::SHA_2_256)
+ .SetDefaultValidity());
EXPECT_EQ(ErrorCode::OK, error) << "Failed to generate ECDSA key with curve " << curve;
if (error != ErrorCode::OK) continue;
@@ -1010,7 +1225,8 @@
ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
.Authorization(TAG_NO_AUTH_REQUIRED)
.EcdsaSigningKey(256)
- .Digest(Digest::NONE)));
+ .Digest(Digest::NONE)
+ .SetDefaultValidity()));
string message(1 * 1024, 'a');
SignMessage(message, AuthorizationSetBuilder().Digest(Digest::NONE));
}
@@ -1026,7 +1242,8 @@
.EcdsaSigningKey(256)
.Digest(Digest::NONE)
.Authorization(TAG_APPLICATION_ID, "clientid")
- .Authorization(TAG_APPLICATION_DATA, "appdata")));
+ .Authorization(TAG_APPLICATION_DATA, "appdata")
+ .SetDefaultValidity()));
EXPECT_EQ(ErrorCode::INVALID_KEY_BLOB,
Begin(KeyPurpose::SIGN, AuthorizationSetBuilder().Digest(Digest::NONE)));
AbortIfNeeded();
@@ -1215,7 +1432,8 @@
.Authorization(TAG_NO_AUTH_REQUIRED)
.RsaSigningKey(2048, 65537)
.Digest(Digest::NONE)
- .Padding(PaddingMode::NONE)));
+ .Padding(PaddingMode::NONE)
+ .SetDefaultValidity()));
string message = "12345678901234567890123456789012";
string signature = SignMessage(
message, AuthorizationSetBuilder().Digest(Digest::NONE).Padding(PaddingMode::NONE));
@@ -1235,7 +1453,8 @@
.Digest(ValidDigests(true /* withNone */, true /* withMD5 */))
.Padding(PaddingMode::NONE)
.Padding(PaddingMode::RSA_PSS)
- .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN);
+ .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN)
+ .SetDefaultValidity();
ASSERT_EQ(ErrorCode::OK, GenerateKey(authorizations));
@@ -1332,7 +1551,8 @@
ErrorCode error = GenerateKey(AuthorizationSetBuilder()
.Authorization(TAG_NO_AUTH_REQUIRED)
.EcdsaSigningKey(curve)
- .Digest(digests));
+ .Digest(digests)
+ .SetDefaultValidity());
EXPECT_EQ(ErrorCode::OK, error) << "Failed to generate key for EC curve " << curve;
if (error != ErrorCode::OK) {
continue;
@@ -1412,7 +1632,7 @@
string key_material = "HelloThisIsAKey";
vector<uint8_t> signing_key, verification_key;
- KeyCharacteristics signing_key_chars, verification_key_chars;
+ vector<KeyCharacteristics> signing_key_chars, verification_key_chars;
EXPECT_EQ(ErrorCode::OK,
ImportKey(AuthorizationSetBuilder()
.Authorization(TAG_NO_AUTH_REQUIRED)
@@ -1466,28 +1686,22 @@
template <TagType tag_type, Tag tag, typename ValueT>
void CheckCryptoParam(TypedTag<tag_type, tag> ttag, ValueT expected) {
SCOPED_TRACE("CheckCryptoParam");
- if (IsSecure()) {
- EXPECT_TRUE(contains(key_characteristics_.hardwareEnforced, ttag, expected))
- << "Tag " << tag << " with value " << expected << " not found";
- EXPECT_FALSE(contains(key_characteristics_.softwareEnforced, ttag))
- << "Tag " << tag << " found";
- } else {
- EXPECT_TRUE(contains(key_characteristics_.softwareEnforced, ttag, expected))
- << "Tag " << tag << " with value " << expected << " not found";
- EXPECT_FALSE(contains(key_characteristics_.hardwareEnforced, ttag))
- << "Tag " << tag << " found";
+ for (auto& entry : key_characteristics_) {
+ if (entry.securityLevel == SecLevel()) {
+ EXPECT_TRUE(contains(entry.authorizations, ttag, expected))
+ << "Tag " << tag << " with value " << expected
+ << " not found at security level" << entry.securityLevel;
+ } else {
+ EXPECT_FALSE(contains(entry.authorizations, ttag, expected))
+ << "Tag " << tag << " found at security level " << entry.securityLevel;
+ }
}
}
void CheckOrigin() {
SCOPED_TRACE("CheckOrigin");
- if (IsSecure()) {
- EXPECT_TRUE(contains(key_characteristics_.hardwareEnforced, TAG_ORIGIN,
- KeyOrigin::IMPORTED));
- } else {
- EXPECT_TRUE(contains(key_characteristics_.softwareEnforced, TAG_ORIGIN,
- KeyOrigin::IMPORTED));
- }
+ // Origin isn't a crypto param, but it always lives with them.
+ return CheckCryptoParam(TAG_ORIGIN, KeyOrigin::IMPORTED);
}
};
@@ -1501,7 +1715,8 @@
.Authorization(TAG_NO_AUTH_REQUIRED)
.RsaSigningKey(1024, 65537)
.Digest(Digest::SHA_2_256)
- .Padding(PaddingMode::RSA_PSS),
+ .Padding(PaddingMode::RSA_PSS)
+ .SetDefaultValidity(),
KeyFormat::PKCS8, rsa_key));
CheckCryptoParam(TAG_ALGORITHM, Algorithm::RSA);
@@ -1528,7 +1743,8 @@
ImportKey(AuthorizationSetBuilder()
.RsaSigningKey(2048 /* Doesn't match key */, 65537)
.Digest(Digest::NONE)
- .Padding(PaddingMode::NONE),
+ .Padding(PaddingMode::NONE)
+ .SetDefaultValidity(),
KeyFormat::PKCS8, rsa_key));
}
@@ -1543,7 +1759,8 @@
ImportKey(AuthorizationSetBuilder()
.RsaSigningKey(1024, 3 /* Doesn't match key */)
.Digest(Digest::NONE)
- .Padding(PaddingMode::NONE),
+ .Padding(PaddingMode::NONE)
+ .SetDefaultValidity(),
KeyFormat::PKCS8, rsa_key));
}
@@ -1556,7 +1773,8 @@
ASSERT_EQ(ErrorCode::OK, ImportKey(AuthorizationSetBuilder()
.Authorization(TAG_NO_AUTH_REQUIRED)
.EcdsaSigningKey(256)
- .Digest(Digest::SHA_2_256),
+ .Digest(Digest::SHA_2_256)
+ .SetDefaultValidity(),
KeyFormat::PKCS8, ec_256_key));
CheckCryptoParam(TAG_ALGORITHM, Algorithm::EC);
@@ -1582,7 +1800,8 @@
ASSERT_EQ(ErrorCode::OK, ImportKey(AuthorizationSetBuilder()
.Authorization(TAG_NO_AUTH_REQUIRED)
.EcdsaSigningKey(256)
- .Digest(Digest::SHA_2_256),
+ .Digest(Digest::SHA_2_256)
+ .SetDefaultValidity(),
KeyFormat::PKCS8, ec_256_key_rfc5915));
CheckCryptoParam(TAG_ALGORITHM, Algorithm::EC);
@@ -1607,7 +1826,8 @@
ASSERT_EQ(ErrorCode::OK, ImportKey(AuthorizationSetBuilder()
.Authorization(TAG_NO_AUTH_REQUIRED)
.EcdsaSigningKey(256)
- .Digest(Digest::SHA_2_256),
+ .Digest(Digest::SHA_2_256)
+ .SetDefaultValidity(),
KeyFormat::PKCS8, ec_256_key_sec1));
CheckCryptoParam(TAG_ALGORITHM, Algorithm::EC);
@@ -1633,7 +1853,8 @@
ASSERT_EQ(ErrorCode::OK, ImportKey(AuthorizationSetBuilder()
.Authorization(TAG_NO_AUTH_REQUIRED)
.EcdsaSigningKey(521)
- .Digest(Digest::SHA_2_256),
+ .Digest(Digest::SHA_2_256)
+ .SetDefaultValidity(),
KeyFormat::PKCS8, ec_521_key));
CheckCryptoParam(TAG_ALGORITHM, Algorithm::EC);
@@ -1658,7 +1879,8 @@
ASSERT_EQ(ErrorCode::IMPORT_PARAMETER_MISMATCH,
ImportKey(AuthorizationSetBuilder()
.EcdsaSigningKey(224 /* Doesn't match key */)
- .Digest(Digest::NONE),
+ .Digest(Digest::NONE)
+ .SetDefaultValidity(),
KeyFormat::PKCS8, ec_256_key));
}
@@ -1672,7 +1894,8 @@
ASSERT_EQ(ErrorCode::IMPORT_PARAMETER_MISMATCH,
ImportKey(AuthorizationSetBuilder()
.EcdsaSigningKey(EcCurve::P_224 /* Doesn't match key */)
- .Digest(Digest::NONE),
+ .Digest(Digest::NONE)
+ .SetDefaultValidity(),
KeyFormat::PKCS8, ec_256_key));
}
@@ -1793,7 +2016,8 @@
.RsaEncryptionKey(2048, 65537)
.Digest(Digest::SHA_2_256)
.Padding(PaddingMode::RSA_OAEP)
- .Authorization(TAG_PURPOSE, KeyPurpose::WRAP_KEY);
+ .Authorization(TAG_PURPOSE, KeyPurpose::WRAP_KEY)
+ .SetDefaultValidity();
ASSERT_EQ(ErrorCode::OK,
ImportWrappedKey(wrapped_key, wrapping_key, wrapping_key_desc, zero_masking_key,
@@ -1813,7 +2037,8 @@
.RsaEncryptionKey(2048, 65537)
.Digest(Digest::SHA_2_256)
.Padding(PaddingMode::RSA_OAEP)
- .Authorization(TAG_PURPOSE, KeyPurpose::WRAP_KEY);
+ .Authorization(TAG_PURPOSE, KeyPurpose::WRAP_KEY)
+ .SetDefaultValidity();
ASSERT_EQ(ErrorCode::OK,
ImportWrappedKey(wrapped_key_masked, wrapping_key, wrapping_key_desc, masking_key,
@@ -1827,7 +2052,8 @@
.RsaEncryptionKey(2048, 65537)
.Digest(Digest::SHA_2_256)
.Padding(PaddingMode::RSA_OAEP)
- .Authorization(TAG_PURPOSE, KeyPurpose::WRAP_KEY);
+ .Authorization(TAG_PURPOSE, KeyPurpose::WRAP_KEY)
+ .SetDefaultValidity();
ASSERT_EQ(
ErrorCode::VERIFICATION_FAILED,
@@ -1841,7 +2067,8 @@
auto wrapping_key_desc = AuthorizationSetBuilder()
.RsaEncryptionKey(2048, 65537)
.Digest(Digest::SHA_2_256)
- .Padding(PaddingMode::RSA_OAEP);
+ .Padding(PaddingMode::RSA_OAEP)
+ .SetDefaultValidity();
ASSERT_EQ(
ErrorCode::INCOMPATIBLE_PURPOSE,
@@ -1864,7 +2091,8 @@
ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
.Authorization(TAG_NO_AUTH_REQUIRED)
.RsaEncryptionKey(2048, 65537)
- .Padding(PaddingMode::NONE)));
+ .Padding(PaddingMode::NONE)
+ .SetDefaultValidity()));
string message = string(2048 / 8, 'a');
auto params = AuthorizationSetBuilder().Padding(PaddingMode::NONE);
@@ -1887,7 +2115,8 @@
ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
.Authorization(TAG_NO_AUTH_REQUIRED)
.RsaEncryptionKey(2048, 65537)
- .Padding(PaddingMode::NONE)));
+ .Padding(PaddingMode::NONE)
+ .SetDefaultValidity()));
string message = "1";
auto params = AuthorizationSetBuilder().Padding(PaddingMode::NONE);
@@ -1916,7 +2145,8 @@
ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
.Authorization(TAG_NO_AUTH_REQUIRED)
.RsaEncryptionKey(2048, 65537)
- .Padding(PaddingMode::NONE)));
+ .Padding(PaddingMode::NONE)
+ .SetDefaultValidity()));
string message(2048 / 8 + 1, 'a');
@@ -1949,7 +2179,8 @@
.Authorization(TAG_NO_AUTH_REQUIRED)
.RsaEncryptionKey(key_size, 65537)
.Padding(PaddingMode::RSA_OAEP)
- .Digest(digests)));
+ .Digest(digests)
+ .SetDefaultValidity()));
string message = "Hello";
@@ -1997,7 +2228,8 @@
.Authorization(TAG_NO_AUTH_REQUIRED)
.RsaEncryptionKey(2048, 65537)
.Padding(PaddingMode::RSA_OAEP)
- .Digest(Digest::NONE)));
+ .Digest(Digest::NONE)
+ .SetDefaultValidity()));
string message = "Hello World!";
auto params = AuthorizationSetBuilder().Padding(PaddingMode::RSA_OAEP).Digest(Digest::NONE);
@@ -2017,7 +2249,8 @@
.Authorization(TAG_NO_AUTH_REQUIRED)
.RsaEncryptionKey(1024, 65537)
.Padding(PaddingMode::RSA_OAEP)
- .Digest(Digest::SHA_2_224, Digest::SHA_2_256)));
+ .Digest(Digest::SHA_2_224, Digest::SHA_2_256)
+ .SetDefaultValidity()));
string message = "Hello World!";
string ciphertext = EncryptMessage(
message,
@@ -2042,7 +2275,8 @@
.Authorization(TAG_NO_AUTH_REQUIRED)
.RsaEncryptionKey(2048, 65537)
.Padding(PaddingMode::RSA_OAEP)
- .Digest(Digest::SHA_2_256)));
+ .Digest(Digest::SHA_2_256)
+ .SetDefaultValidity()));
constexpr size_t digest_size = 256 /* SHA_2_256 */ / 8;
constexpr size_t oaep_overhead = 2 * digest_size + 2;
string message(2048 / 8 - oaep_overhead + 1, 'a');
@@ -2056,6 +2290,110 @@
}
/*
+ * EncryptionOperationsTest.RsaOaepWithMGFDigestSuccess
+ *
+ * Verifies that RSA-OAEP encryption operations work, with all SHA 256 digests and all type of MGF1
+ * digests.
+ */
+TEST_P(EncryptionOperationsTest, RsaOaepWithMGFDigestSuccess) {
+ auto digests = ValidDigests(false /* withNone */, true /* withMD5 */);
+
+ size_t key_size = 2048; // Need largish key for SHA-512 test.
+ ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+ .OaepMGFDigest(digests)
+ .Authorization(TAG_NO_AUTH_REQUIRED)
+ .RsaEncryptionKey(key_size, 65537)
+ .Padding(PaddingMode::RSA_OAEP)
+ .Digest(Digest::SHA_2_256)
+ .SetDefaultValidity()));
+
+ string message = "Hello";
+
+ for (auto digest : digests) {
+ auto params = AuthorizationSetBuilder()
+ .Authorization(TAG_RSA_OAEP_MGF_DIGEST, digest)
+ .Digest(Digest::SHA_2_256)
+ .Padding(PaddingMode::RSA_OAEP);
+ string ciphertext1 = EncryptMessage(message, params);
+ if (HasNonfatalFailure()) std::cout << "-->" << digest << std::endl;
+ EXPECT_EQ(key_size / 8, ciphertext1.size());
+
+ string ciphertext2 = EncryptMessage(message, params);
+ EXPECT_EQ(key_size / 8, ciphertext2.size());
+
+ // OAEP randomizes padding so every result should be different (with astronomically high
+ // probability).
+ EXPECT_NE(ciphertext1, ciphertext2);
+
+ string plaintext1 = DecryptMessage(ciphertext1, params);
+ EXPECT_EQ(message, plaintext1) << "RSA-OAEP failed with digest " << digest;
+ string plaintext2 = DecryptMessage(ciphertext2, params);
+ EXPECT_EQ(message, plaintext2) << "RSA-OAEP failed with digest " << digest;
+
+ // Decrypting corrupted ciphertext should fail.
+ size_t offset_to_corrupt = random() % ciphertext1.size();
+ char corrupt_byte;
+ do {
+ corrupt_byte = static_cast<char>(random() % 256);
+ } while (corrupt_byte == ciphertext1[offset_to_corrupt]);
+ ciphertext1[offset_to_corrupt] = corrupt_byte;
+
+ EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::DECRYPT, params));
+ string result;
+ EXPECT_EQ(ErrorCode::UNKNOWN_ERROR, Finish(ciphertext1, &result));
+ EXPECT_EQ(0U, result.size());
+ }
+}
+
+/*
+ * EncryptionOperationsTest.RsaOaepWithMGFIncompatibleDigest
+ *
+ * Verifies that RSA-OAEP encryption operations fail in the correct way when asked to operate
+ * with incompatible MGF digest.
+ */
+TEST_P(EncryptionOperationsTest, RsaOaepWithMGFIncompatibleDigest) {
+ ASSERT_EQ(ErrorCode::OK,
+ GenerateKey(AuthorizationSetBuilder()
+ .Authorization(TAG_RSA_OAEP_MGF_DIGEST, Digest::SHA_2_256)
+ .Authorization(TAG_NO_AUTH_REQUIRED)
+ .RsaEncryptionKey(2048, 65537)
+ .Padding(PaddingMode::RSA_OAEP)
+ .Digest(Digest::SHA_2_256)
+ .SetDefaultValidity()));
+ string message = "Hello World!";
+
+ auto params = AuthorizationSetBuilder()
+ .Padding(PaddingMode::RSA_OAEP)
+ .Digest(Digest::SHA_2_256)
+ .Authorization(TAG_RSA_OAEP_MGF_DIGEST, Digest::SHA_2_224);
+ EXPECT_EQ(ErrorCode::INCOMPATIBLE_MGF_DIGEST, Begin(KeyPurpose::ENCRYPT, params));
+}
+
+/*
+ * EncryptionOperationsTest.RsaOaepWithMGFUnsupportedDigest
+ *
+ * Verifies that RSA-OAEP encryption operations fail in the correct way when asked to operate
+ * with unsupported MGF digest.
+ */
+TEST_P(EncryptionOperationsTest, RsaOaepWithMGFUnsupportedDigest) {
+ ASSERT_EQ(ErrorCode::OK,
+ GenerateKey(AuthorizationSetBuilder()
+ .Authorization(TAG_RSA_OAEP_MGF_DIGEST, Digest::SHA_2_256)
+ .Authorization(TAG_NO_AUTH_REQUIRED)
+ .RsaEncryptionKey(2048, 65537)
+ .Padding(PaddingMode::RSA_OAEP)
+ .Digest(Digest::SHA_2_256)
+ .SetDefaultValidity()));
+ string message = "Hello World!";
+
+ auto params = AuthorizationSetBuilder()
+ .Padding(PaddingMode::RSA_OAEP)
+ .Digest(Digest::SHA_2_256)
+ .Authorization(TAG_RSA_OAEP_MGF_DIGEST, Digest::NONE);
+ EXPECT_EQ(ErrorCode::UNSUPPORTED_MGF_DIGEST, Begin(KeyPurpose::ENCRYPT, params));
+}
+
+/*
* EncryptionOperationsTest.RsaPkcs1Success
*
* Verifies that RSA PKCS encryption/decrypts works.
@@ -2064,7 +2402,8 @@
ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
.Authorization(TAG_NO_AUTH_REQUIRED)
.RsaEncryptionKey(2048, 65537)
- .Padding(PaddingMode::RSA_PKCS1_1_5_ENCRYPT)));
+ .Padding(PaddingMode::RSA_PKCS1_1_5_ENCRYPT)
+ .SetDefaultValidity()));
string message = "Hello World!";
auto params = AuthorizationSetBuilder().Padding(PaddingMode::RSA_PKCS1_1_5_ENCRYPT);
@@ -2103,7 +2442,8 @@
ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
.Authorization(TAG_NO_AUTH_REQUIRED)
.RsaEncryptionKey(2048, 65537)
- .Padding(PaddingMode::RSA_PKCS1_1_5_ENCRYPT)));
+ .Padding(PaddingMode::RSA_PKCS1_1_5_ENCRYPT)
+ .SetDefaultValidity()));
string message(2048 / 8 - 10, 'a');
auto params = AuthorizationSetBuilder().Padding(PaddingMode::RSA_PKCS1_1_5_ENCRYPT);
@@ -2123,7 +2463,8 @@
ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
.Authorization(TAG_NO_AUTH_REQUIRED)
.EcdsaSigningKey(256)
- .Digest(Digest::NONE)));
+ .Digest(Digest::NONE)
+ .SetDefaultValidity()));
auto params = AuthorizationSetBuilder().Digest(Digest::NONE);
ASSERT_EQ(ErrorCode::UNSUPPORTED_PURPOSE, Begin(KeyPurpose::ENCRYPT, params));
ASSERT_EQ(ErrorCode::UNSUPPORTED_PURPOSE, Begin(KeyPurpose::DECRYPT, params));
@@ -3760,7 +4101,7 @@
}
/*
- * MaxOperationsTest.TestLimitAes
+ * MaxOperationsTest.TestLimitRsa
*
* Verifies that the max uses per boot tag works correctly with RSA keys.
*/
@@ -3771,7 +4112,8 @@
.Authorization(TAG_NO_AUTH_REQUIRED)
.RsaSigningKey(1024, 65537)
.NoDigestOrPadding()
- .Authorization(TAG_MAX_USES_PER_BOOT, 3)));
+ .Authorization(TAG_MAX_USES_PER_BOOT, 3)
+ .SetDefaultValidity()));
string message = "1234567890123456";
@@ -3787,6 +4129,239 @@
INSTANTIATE_KEYMINT_AIDL_TEST(MaxOperationsTest);
+typedef KeyMintAidlTestBase UsageCountLimitTest;
+
+/*
+ * UsageCountLimitTest.TestSingleUseAes
+ *
+ * Verifies that the usage count limit tag = 1 works correctly with AES keys.
+ */
+TEST_P(UsageCountLimitTest, TestSingleUseAes) {
+ if (SecLevel() == SecurityLevel::STRONGBOX) return;
+
+ ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+ .Authorization(TAG_NO_AUTH_REQUIRED)
+ .AesEncryptionKey(128)
+ .EcbMode()
+ .Padding(PaddingMode::NONE)
+ .Authorization(TAG_USAGE_COUNT_LIMIT, 1)));
+
+ // Check the usage count limit tag appears in the authorizations.
+ AuthorizationSet auths;
+ for (auto& entry : key_characteristics_) {
+ auths.push_back(AuthorizationSet(entry.authorizations));
+ }
+ EXPECT_TRUE(auths.Contains(TAG_USAGE_COUNT_LIMIT, 1U))
+ << "key usage count limit " << 1U << " missing";
+
+ string message = "1234567890123456";
+ auto params = AuthorizationSetBuilder().EcbMode().Padding(PaddingMode::NONE);
+
+ AuthorizationSet hardware_auths = HwEnforcedAuthorizations(key_characteristics_);
+ AuthorizationSet keystore_auths =
+ SecLevelAuthorizations(key_characteristics_, SecurityLevel::KEYSTORE);
+
+ // First usage of AES key should work.
+ EncryptMessage(message, params);
+
+ if (hardware_auths.Contains(TAG_USAGE_COUNT_LIMIT, 1U)) {
+ // Usage count limit tag is enforced by hardware. After using the key, the key blob
+ // must be invalidated from secure storage (such as RPMB partition).
+ EXPECT_EQ(ErrorCode::INVALID_KEY_BLOB, Begin(KeyPurpose::ENCRYPT, params));
+ } else {
+ // Usage count limit tag is enforced by keystore, keymint does nothing.
+ EXPECT_TRUE(keystore_auths.Contains(TAG_USAGE_COUNT_LIMIT, 1U));
+ EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::ENCRYPT, params));
+ }
+}
+
+/*
+ * UsageCountLimitTest.TestLimitedUseAes
+ *
+ * Verifies that the usage count limit tag > 1 works correctly with AES keys.
+ */
+TEST_P(UsageCountLimitTest, TestLimitedUseAes) {
+ if (SecLevel() == SecurityLevel::STRONGBOX) return;
+
+ ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+ .Authorization(TAG_NO_AUTH_REQUIRED)
+ .AesEncryptionKey(128)
+ .EcbMode()
+ .Padding(PaddingMode::NONE)
+ .Authorization(TAG_USAGE_COUNT_LIMIT, 3)));
+
+ // Check the usage count limit tag appears in the authorizations.
+ AuthorizationSet auths;
+ for (auto& entry : key_characteristics_) {
+ auths.push_back(AuthorizationSet(entry.authorizations));
+ }
+ EXPECT_TRUE(auths.Contains(TAG_USAGE_COUNT_LIMIT, 3U))
+ << "key usage count limit " << 3U << " missing";
+
+ string message = "1234567890123456";
+ auto params = AuthorizationSetBuilder().EcbMode().Padding(PaddingMode::NONE);
+
+ AuthorizationSet hardware_auths = HwEnforcedAuthorizations(key_characteristics_);
+ AuthorizationSet keystore_auths =
+ SecLevelAuthorizations(key_characteristics_, SecurityLevel::KEYSTORE);
+
+ EncryptMessage(message, params);
+ EncryptMessage(message, params);
+ EncryptMessage(message, params);
+
+ if (hardware_auths.Contains(TAG_USAGE_COUNT_LIMIT, 3U)) {
+ // Usage count limit tag is enforced by hardware. After using the key, the key blob
+ // must be invalidated from secure storage (such as RPMB partition).
+ EXPECT_EQ(ErrorCode::INVALID_KEY_BLOB, Begin(KeyPurpose::ENCRYPT, params));
+ } else {
+ // Usage count limit tag is enforced by keystore, keymint does nothing.
+ EXPECT_TRUE(keystore_auths.Contains(TAG_USAGE_COUNT_LIMIT, 3U));
+ EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::ENCRYPT, params));
+ }
+}
+
+/*
+ * UsageCountLimitTest.TestSingleUseRsa
+ *
+ * Verifies that the usage count limit tag = 1 works correctly with RSA keys.
+ */
+TEST_P(UsageCountLimitTest, TestSingleUseRsa) {
+ if (SecLevel() == SecurityLevel::STRONGBOX) return;
+
+ ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+ .Authorization(TAG_NO_AUTH_REQUIRED)
+ .RsaSigningKey(1024, 65537)
+ .NoDigestOrPadding()
+ .Authorization(TAG_USAGE_COUNT_LIMIT, 1)
+ .SetDefaultValidity()));
+
+ // Check the usage count limit tag appears in the authorizations.
+ AuthorizationSet auths;
+ for (auto& entry : key_characteristics_) {
+ auths.push_back(AuthorizationSet(entry.authorizations));
+ }
+ EXPECT_TRUE(auths.Contains(TAG_USAGE_COUNT_LIMIT, 1U))
+ << "key usage count limit " << 1U << " missing";
+
+ string message = "1234567890123456";
+ auto params = AuthorizationSetBuilder().NoDigestOrPadding();
+
+ AuthorizationSet hardware_auths = HwEnforcedAuthorizations(key_characteristics_);
+ AuthorizationSet keystore_auths =
+ SecLevelAuthorizations(key_characteristics_, SecurityLevel::KEYSTORE);
+
+ // First usage of RSA key should work.
+ SignMessage(message, params);
+
+ if (hardware_auths.Contains(TAG_USAGE_COUNT_LIMIT, 1U)) {
+ // Usage count limit tag is enforced by hardware. After using the key, the key blob
+ // must be invalidated from secure storage (such as RPMB partition).
+ EXPECT_EQ(ErrorCode::INVALID_KEY_BLOB, Begin(KeyPurpose::SIGN, params));
+ } else {
+ // Usage count limit tag is enforced by keystore, keymint does nothing.
+ EXPECT_TRUE(keystore_auths.Contains(TAG_USAGE_COUNT_LIMIT, 1U));
+ EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::SIGN, params));
+ }
+}
+
+/*
+ * UsageCountLimitTest.TestLimitUseRsa
+ *
+ * Verifies that the usage count limit tag > 1 works correctly with RSA keys.
+ */
+TEST_P(UsageCountLimitTest, TestLimitUseRsa) {
+ if (SecLevel() == SecurityLevel::STRONGBOX) return;
+
+ ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+ .Authorization(TAG_NO_AUTH_REQUIRED)
+ .RsaSigningKey(1024, 65537)
+ .NoDigestOrPadding()
+ .Authorization(TAG_USAGE_COUNT_LIMIT, 3)
+ .SetDefaultValidity()));
+
+ // Check the usage count limit tag appears in the authorizations.
+ AuthorizationSet auths;
+ for (auto& entry : key_characteristics_) {
+ auths.push_back(AuthorizationSet(entry.authorizations));
+ }
+ EXPECT_TRUE(auths.Contains(TAG_USAGE_COUNT_LIMIT, 3U))
+ << "key usage count limit " << 3U << " missing";
+
+ string message = "1234567890123456";
+ auto params = AuthorizationSetBuilder().NoDigestOrPadding();
+
+ AuthorizationSet hardware_auths = HwEnforcedAuthorizations(key_characteristics_);
+ AuthorizationSet keystore_auths =
+ SecLevelAuthorizations(key_characteristics_, SecurityLevel::KEYSTORE);
+
+ SignMessage(message, params);
+ SignMessage(message, params);
+ SignMessage(message, params);
+
+ if (hardware_auths.Contains(TAG_USAGE_COUNT_LIMIT, 3U)) {
+ // Usage count limit tag is enforced by hardware. After using the key, the key blob
+ // must be invalidated from secure storage (such as RPMB partition).
+ EXPECT_EQ(ErrorCode::INVALID_KEY_BLOB, Begin(KeyPurpose::SIGN, params));
+ } else {
+ // Usage count limit tag is enforced by keystore, keymint does nothing.
+ EXPECT_TRUE(keystore_auths.Contains(TAG_USAGE_COUNT_LIMIT, 3U));
+ EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::SIGN, params));
+ }
+}
+
+/*
+ * UsageCountLimitTest.TestSingleUseKeyAndRollbackResistance
+ *
+ * Verifies that when rollback resistance is supported by the KeyMint implementation with
+ * the secure hardware, the single use key with usage count limit tag = 1 must also be enforced
+ * in hardware.
+ */
+TEST_P(UsageCountLimitTest, TestSingleUseKeyAndRollbackResistance) {
+ if (SecLevel() == SecurityLevel::STRONGBOX) return;
+
+ auto error = GenerateKey(AuthorizationSetBuilder()
+ .RsaSigningKey(2048, 65537)
+ .Digest(Digest::NONE)
+ .Padding(PaddingMode::NONE)
+ .Authorization(TAG_NO_AUTH_REQUIRED)
+ .Authorization(TAG_ROLLBACK_RESISTANCE)
+ .SetDefaultValidity());
+ ASSERT_TRUE(error == ErrorCode::ROLLBACK_RESISTANCE_UNAVAILABLE || error == ErrorCode::OK);
+
+ if (error == ErrorCode::OK) {
+ // Rollback resistance is supported by KeyMint, verify it is enforced in hardware.
+ AuthorizationSet hardwareEnforced(SecLevelAuthorizations());
+ ASSERT_TRUE(hardwareEnforced.Contains(TAG_ROLLBACK_RESISTANCE));
+ ASSERT_EQ(ErrorCode::OK, DeleteKey());
+
+ // The KeyMint should also enforce single use key in hardware when it supports rollback
+ // resistance.
+ ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+ .Authorization(TAG_NO_AUTH_REQUIRED)
+ .RsaSigningKey(1024, 65537)
+ .NoDigestOrPadding()
+ .Authorization(TAG_USAGE_COUNT_LIMIT, 1)
+ .SetDefaultValidity()));
+
+ // Check the usage count limit tag appears in the hardware authorizations.
+ AuthorizationSet hardware_auths = HwEnforcedAuthorizations(key_characteristics_);
+ EXPECT_TRUE(hardware_auths.Contains(TAG_USAGE_COUNT_LIMIT, 1U))
+ << "key usage count limit " << 1U << " missing";
+
+ string message = "1234567890123456";
+ auto params = AuthorizationSetBuilder().NoDigestOrPadding();
+
+ // First usage of RSA key should work.
+ SignMessage(message, params);
+
+ // Usage count limit tag is enforced by hardware. After using the key, the key blob
+ // must be invalidated from secure storage (such as RPMB partition).
+ EXPECT_EQ(ErrorCode::INVALID_KEY_BLOB, Begin(KeyPurpose::SIGN, params));
+ }
+}
+
+INSTANTIATE_KEYMINT_AIDL_TEST(UsageCountLimitTest);
+
typedef KeyMintAidlTestBase AddEntropyTest;
/*
@@ -3820,16 +4395,6 @@
INSTANTIATE_KEYMINT_AIDL_TEST(AddEntropyTest);
-typedef KeyMintAidlTestBase AttestationTest;
-
-/*
- * AttestationTest.RsaAttestation
- *
- * Verifies that attesting to RSA keys works and generates the expected output.
- */
-// TODO(seleneh) add attestation tests back after decided on the new attestation
-// behavior under generateKey and importKey
-
typedef KeyMintAidlTestBase KeyDeletionTest;
/**
@@ -3844,12 +4409,13 @@
.Digest(Digest::NONE)
.Padding(PaddingMode::NONE)
.Authorization(TAG_NO_AUTH_REQUIRED)
- .Authorization(TAG_ROLLBACK_RESISTANCE));
+ .Authorization(TAG_ROLLBACK_RESISTANCE)
+ .SetDefaultValidity());
ASSERT_TRUE(error == ErrorCode::ROLLBACK_RESISTANCE_UNAVAILABLE || error == ErrorCode::OK);
// Delete must work if rollback protection is implemented
if (error == ErrorCode::OK) {
- AuthorizationSet hardwareEnforced(key_characteristics_.hardwareEnforced);
+ AuthorizationSet hardwareEnforced(SecLevelAuthorizations());
ASSERT_TRUE(hardwareEnforced.Contains(TAG_ROLLBACK_RESISTANCE));
ASSERT_EQ(ErrorCode::OK, DeleteKey(true /* keep key blob */));
@@ -3877,13 +4443,14 @@
.Digest(Digest::NONE)
.Padding(PaddingMode::NONE)
.Authorization(TAG_NO_AUTH_REQUIRED)
- .Authorization(TAG_ROLLBACK_RESISTANCE));
+ .Authorization(TAG_ROLLBACK_RESISTANCE)
+ .SetDefaultValidity());
ASSERT_TRUE(error == ErrorCode::ROLLBACK_RESISTANCE_UNAVAILABLE || error == ErrorCode::OK);
// Delete must work if rollback protection is implemented
if (error == ErrorCode::OK) {
- AuthorizationSet hardwareEnforced(key_characteristics_.hardwareEnforced);
- ASSERT_TRUE(hardwareEnforced.Contains(TAG_ROLLBACK_RESISTANCE));
+ AuthorizationSet enforced(SecLevelAuthorizations());
+ ASSERT_TRUE(enforced.Contains(TAG_ROLLBACK_RESISTANCE));
// Delete the key we don't care about the result at this point.
DeleteKey();
@@ -3918,7 +4485,7 @@
// Delete must work if rollback protection is implemented
if (error == ErrorCode::OK) {
- AuthorizationSet hardwareEnforced(key_characteristics_.hardwareEnforced);
+ AuthorizationSet hardwareEnforced(SecLevelAuthorizations());
ASSERT_TRUE(hardwareEnforced.Contains(TAG_ROLLBACK_RESISTANCE));
ASSERT_EQ(ErrorCode::OK, DeleteAllKeys());
@@ -3972,7 +4539,8 @@
ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
.Authorization(TAG_NO_AUTH_REQUIRED)
.RsaEncryptionKey(2048, 65537)
- .Padding(PaddingMode::NONE)));
+ .Padding(PaddingMode::NONE)
+ .SetDefaultValidity()));
auto params = AuthorizationSetBuilder().Padding(PaddingMode::NONE);
constexpr size_t max_operations = 100; // set to arbituary large number
@@ -4043,17 +4611,238 @@
INSTANTIATE_KEYMINT_AIDL_TEST(TransportLimitTest);
+typedef KeyMintAidlTestBase KeyAgreementTest;
+
+int CurveToOpenSslCurveName(EcCurve curve) {
+ switch (curve) {
+ case EcCurve::P_224:
+ return NID_secp224r1;
+ case EcCurve::P_256:
+ return NID_X9_62_prime256v1;
+ case EcCurve::P_384:
+ return NID_secp384r1;
+ case EcCurve::P_521:
+ return NID_secp521r1;
+ }
+}
+
+/*
+ * KeyAgreementTest.Ecdh
+ *
+ * Verifies that ECDH works for all curves
+ */
+TEST_P(KeyAgreementTest, Ecdh) {
+ // Because it's possible to use this API with keys on different curves, we
+ // check all N^2 combinations where N is the number of supported
+ // curves.
+ //
+ // This is not a big deal as N is 4 so we only do 16 runs. If we end up with a
+ // lot more curves we can be smart about things and just pick |otherCurve| so
+ // it's not |curve| and that way we end up with only 2*N runs
+ //
+ for (auto curve : ValidCurves()) {
+ for (auto localCurve : ValidCurves()) {
+ // Generate EC key locally (with access to private key material)
+ auto ecKey = EC_KEY_Ptr(EC_KEY_new());
+ int curveName = CurveToOpenSslCurveName(localCurve);
+ auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(curveName));
+ ASSERT_NE(group, nullptr);
+ ASSERT_EQ(EC_KEY_set_group(ecKey.get(), group.get()), 1);
+ ASSERT_EQ(EC_KEY_generate_key(ecKey.get()), 1);
+ auto pkey = EVP_PKEY_Ptr(EVP_PKEY_new());
+ ASSERT_EQ(EVP_PKEY_set1_EC_KEY(pkey.get(), ecKey.get()), 1);
+
+ // Get encoded form of the public part of the locally generated key...
+ unsigned char* p = nullptr;
+ int encodedPublicKeySize = i2d_PUBKEY(pkey.get(), &p);
+ ASSERT_GT(encodedPublicKeySize, 0);
+ vector<uint8_t> encodedPublicKey(
+ reinterpret_cast<const uint8_t*>(p),
+ reinterpret_cast<const uint8_t*>(p + encodedPublicKeySize));
+ OPENSSL_free(p);
+
+ // Generate EC key in KeyMint (only access to public key material)
+ vector<uint8_t> challenge = {0x41, 0x42};
+ EXPECT_EQ(
+ ErrorCode::OK,
+ GenerateKey(AuthorizationSetBuilder()
+ .Authorization(TAG_NO_AUTH_REQUIRED)
+ .Authorization(TAG_EC_CURVE, curve)
+ .Authorization(TAG_PURPOSE, KeyPurpose::AGREE_KEY)
+ .Authorization(TAG_ALGORITHM, Algorithm::EC)
+ .Authorization(TAG_ATTESTATION_APPLICATION_ID, {0x61, 0x62})
+ .Authorization(TAG_ATTESTATION_CHALLENGE, challenge)
+ .SetDefaultValidity()))
+ << "Failed to generate key";
+ ASSERT_GT(cert_chain_.size(), 0);
+ X509_Ptr kmKeyCert(parse_cert_blob(cert_chain_[0].encodedCertificate));
+ ASSERT_NE(kmKeyCert, nullptr);
+ // Check that keyAgreement (bit 4) is set in KeyUsage
+ EXPECT_TRUE((X509_get_key_usage(kmKeyCert.get()) & X509v3_KU_KEY_AGREEMENT) != 0);
+ auto kmPkey = EVP_PKEY_Ptr(X509_get_pubkey(kmKeyCert.get()));
+ ASSERT_NE(kmPkey, nullptr);
+ if (dump_Attestations) {
+ for (size_t n = 0; n < cert_chain_.size(); n++) {
+ std::cout << bin2hex(cert_chain_[n].encodedCertificate) << std::endl;
+ }
+ }
+
+ // Now that we have the two keys, we ask KeyMint to perform ECDH...
+ if (curve != localCurve) {
+ // If the keys are using different curves KeyMint should fail with
+ // ErrorCode:INVALID_ARGUMENT. Check that.
+ EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::AGREE_KEY, AuthorizationSetBuilder()));
+ string ZabFromKeyMintStr;
+ EXPECT_EQ(ErrorCode::INVALID_ARGUMENT,
+ Finish(string(encodedPublicKey.begin(), encodedPublicKey.end()),
+ &ZabFromKeyMintStr));
+
+ } else {
+ // Otherwise if the keys are using the same curve, it should work.
+ EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::AGREE_KEY, AuthorizationSetBuilder()));
+ string ZabFromKeyMintStr;
+ EXPECT_EQ(ErrorCode::OK,
+ Finish(string(encodedPublicKey.begin(), encodedPublicKey.end()),
+ &ZabFromKeyMintStr));
+ vector<uint8_t> ZabFromKeyMint(ZabFromKeyMintStr.begin(), ZabFromKeyMintStr.end());
+
+ // Perform local ECDH between the two keys so we can check if we get the same Zab..
+ auto ctx = EVP_PKEY_CTX_Ptr(EVP_PKEY_CTX_new(pkey.get(), nullptr));
+ ASSERT_NE(ctx, nullptr);
+ ASSERT_EQ(EVP_PKEY_derive_init(ctx.get()), 1);
+ ASSERT_EQ(EVP_PKEY_derive_set_peer(ctx.get(), kmPkey.get()), 1);
+ size_t ZabFromTestLen = 0;
+ ASSERT_EQ(EVP_PKEY_derive(ctx.get(), nullptr, &ZabFromTestLen), 1);
+ vector<uint8_t> ZabFromTest;
+ ZabFromTest.resize(ZabFromTestLen);
+ ASSERT_EQ(EVP_PKEY_derive(ctx.get(), ZabFromTest.data(), &ZabFromTestLen), 1);
+
+ EXPECT_EQ(ZabFromKeyMint, ZabFromTest);
+ }
+
+ CheckedDeleteKey();
+ }
+ }
+}
+
+INSTANTIATE_KEYMINT_AIDL_TEST(KeyAgreementTest);
+
+typedef KeyMintAidlTestBase EarlyBootKeyTest;
+
+TEST_P(EarlyBootKeyTest, CreateEarlyBootKeys) {
+ auto [aesKeyData, hmacKeyData, rsaKeyData, ecdsaKeyData] =
+ CreateTestKeys(TAG_EARLY_BOOT_ONLY, ErrorCode::OK);
+
+ CheckedDeleteKey(&aesKeyData.blob);
+ CheckedDeleteKey(&hmacKeyData.blob);
+ CheckedDeleteKey(&rsaKeyData.blob);
+ CheckedDeleteKey(&ecdsaKeyData.blob);
+}
+
+// This is a more comprenhensive test, but it can only be run on a machine which is still in early
+// boot stage, which no proper Android device is by the time we can run VTS. To use this,
+// un-disable it and modify vold to remove the call to earlyBootEnded(). Running the test will end
+// early boot, so you'll have to reboot between runs.
+TEST_P(EarlyBootKeyTest, DISABLED_FullTest) {
+ auto [aesKeyData, hmacKeyData, rsaKeyData, ecdsaKeyData] =
+ CreateTestKeys(TAG_EARLY_BOOT_ONLY, ErrorCode::OK);
+ // TAG_EARLY_BOOT_ONLY should be in hw-enforced.
+ EXPECT_TRUE(HwEnforcedAuthorizations(aesKeyData.characteristics).Contains(TAG_EARLY_BOOT_ONLY));
+ EXPECT_TRUE(
+ HwEnforcedAuthorizations(hmacKeyData.characteristics).Contains(TAG_EARLY_BOOT_ONLY));
+ EXPECT_TRUE(HwEnforcedAuthorizations(rsaKeyData.characteristics).Contains(TAG_EARLY_BOOT_ONLY));
+ EXPECT_TRUE(
+ HwEnforcedAuthorizations(ecdsaKeyData.characteristics).Contains(TAG_EARLY_BOOT_ONLY));
+
+ // Should be able to use keys, since early boot has not ended
+ EXPECT_EQ(ErrorCode::OK, UseAesKey(aesKeyData.blob));
+ EXPECT_EQ(ErrorCode::OK, UseHmacKey(hmacKeyData.blob));
+ EXPECT_EQ(ErrorCode::OK, UseRsaKey(rsaKeyData.blob));
+ EXPECT_EQ(ErrorCode::OK, UseEcdsaKey(ecdsaKeyData.blob));
+
+ // End early boot
+ ErrorCode earlyBootResult = GetReturnErrorCode(keyMint().earlyBootEnded());
+ EXPECT_EQ(earlyBootResult, ErrorCode::OK);
+
+ // Should not be able to use already-created keys.
+ EXPECT_EQ(ErrorCode::EARLY_BOOT_ENDED, UseAesKey(aesKeyData.blob));
+ EXPECT_EQ(ErrorCode::EARLY_BOOT_ENDED, UseHmacKey(hmacKeyData.blob));
+ EXPECT_EQ(ErrorCode::EARLY_BOOT_ENDED, UseRsaKey(rsaKeyData.blob));
+ EXPECT_EQ(ErrorCode::EARLY_BOOT_ENDED, UseEcdsaKey(ecdsaKeyData.blob));
+
+ CheckedDeleteKey(&aesKeyData.blob);
+ CheckedDeleteKey(&hmacKeyData.blob);
+ CheckedDeleteKey(&rsaKeyData.blob);
+ CheckedDeleteKey(&ecdsaKeyData.blob);
+
+ // Should not be able to create new keys
+ std::tie(aesKeyData, hmacKeyData, rsaKeyData, ecdsaKeyData) =
+ CreateTestKeys(TAG_EARLY_BOOT_ONLY, ErrorCode::EARLY_BOOT_ENDED);
+
+ CheckedDeleteKey(&aesKeyData.blob);
+ CheckedDeleteKey(&hmacKeyData.blob);
+ CheckedDeleteKey(&rsaKeyData.blob);
+ CheckedDeleteKey(&ecdsaKeyData.blob);
+}
+INSTANTIATE_KEYMINT_AIDL_TEST(EarlyBootKeyTest);
+
+typedef KeyMintAidlTestBase UnlockedDeviceRequiredTest;
+
+// This may be a problematic test. It can't be run repeatedly without unlocking the device in
+// between runs... and on most test devices there are no enrolled credentials so it can't be
+// unlocked at all, meaning the only way to get the test to pass again on a properly-functioning
+// device is to reboot it. For that reason, this is disabled by default. It can be used as part of
+// a manual test process, which includes unlocking between runs, which is why it's included here.
+// Well, that and the fact that it's the only test we can do without also making calls into the
+// Gatekeeper HAL. We haven't written any cross-HAL tests, and don't know what all of the
+// implications might be, so that may or may not be a solution.
+TEST_P(UnlockedDeviceRequiredTest, DISABLED_KeysBecomeUnusable) {
+ auto [aesKeyData, hmacKeyData, rsaKeyData, ecdsaKeyData] =
+ CreateTestKeys(TAG_UNLOCKED_DEVICE_REQUIRED, ErrorCode::OK);
+
+ EXPECT_EQ(ErrorCode::OK, UseAesKey(aesKeyData.blob));
+ EXPECT_EQ(ErrorCode::OK, UseHmacKey(hmacKeyData.blob));
+ EXPECT_EQ(ErrorCode::OK, UseRsaKey(rsaKeyData.blob));
+ EXPECT_EQ(ErrorCode::OK, UseEcdsaKey(ecdsaKeyData.blob));
+
+ ErrorCode rc = GetReturnErrorCode(
+ keyMint().deviceLocked(false /* passwordOnly */, {} /* verificationToken */));
+ ASSERT_EQ(ErrorCode::OK, rc);
+ EXPECT_EQ(ErrorCode::DEVICE_LOCKED, UseAesKey(aesKeyData.blob));
+ EXPECT_EQ(ErrorCode::DEVICE_LOCKED, UseHmacKey(hmacKeyData.blob));
+ EXPECT_EQ(ErrorCode::DEVICE_LOCKED, UseRsaKey(rsaKeyData.blob));
+ EXPECT_EQ(ErrorCode::DEVICE_LOCKED, UseEcdsaKey(ecdsaKeyData.blob));
+
+ CheckedDeleteKey(&aesKeyData.blob);
+ CheckedDeleteKey(&hmacKeyData.blob);
+ CheckedDeleteKey(&rsaKeyData.blob);
+ CheckedDeleteKey(&ecdsaKeyData.blob);
+}
+INSTANTIATE_KEYMINT_AIDL_TEST(UnlockedDeviceRequiredTest);
+
} // namespace aidl::android::hardware::security::keymint::test
int main(int argc, char** argv) {
+ std::cout << "Testing ";
+ auto halInstances =
+ aidl::android::hardware::security::keymint::test::KeyMintAidlTestBase::build_params();
+ std::cout << "HAL instances:\n";
+ for (auto& entry : halInstances) {
+ std::cout << " " << entry << '\n';
+ }
+
::testing::InitGoogleTest(&argc, argv);
for (int i = 1; i < argc; ++i) {
if (argv[i][0] == '-') {
if (std::string(argv[i]) == "--arm_deleteAllKeys") {
- arm_deleteAllKeys = true;
+ aidl::android::hardware::security::keymint::test::KeyMintAidlTestBase::
+ arm_deleteAllKeys = true;
}
if (std::string(argv[i]) == "--dump_attestations") {
- dump_Attestations = true;
+ aidl::android::hardware::security::keymint::test::KeyMintAidlTestBase::
+ dump_Attestations = true;
+ } else {
+ std::cout << "NOT dumping attestations" << std::endl;
}
}
}
diff --git a/security/keymint/aidl/vts/functional/VerificationTokenTest.cpp b/security/keymint/aidl/vts/functional/VerificationTokenTest.cpp
deleted file mode 100644
index 0b1eccd..0000000
--- a/security/keymint/aidl/vts/functional/VerificationTokenTest.cpp
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "KeyMintAidlTestBase.h"
-
-namespace aidl::android::hardware::security::keymint::test {
-
-class VerificationTokenTest : public KeyMintAidlTestBase {
- protected:
- struct VerifyAuthorizationResult {
- ErrorCode error;
- VerificationToken token;
- };
-
- VerifyAuthorizationResult verifyAuthorization(uint64_t operationHandle,
- const HardwareAuthToken& authToken) {
- VerifyAuthorizationResult result;
-
- Status err;
- err = keyMint().verifyAuthorization(operationHandle, //
- authToken, //
- &result.token);
-
- result.error = GetReturnErrorCode(err);
- return result;
- }
-
- uint64_t getTime() {
- struct timespec timespec;
- EXPECT_EQ(0, clock_gettime(CLOCK_BOOTTIME, ×pec));
- return timespec.tv_sec * 1000 + timespec.tv_nsec / 1000000;
- }
-
- int sleep_ms(uint32_t milliseconds) {
- struct timespec sleep_time = {static_cast<time_t>(milliseconds / 1000),
- static_cast<long>(milliseconds % 1000) * 1000000};
- while (sleep_time.tv_sec || sleep_time.tv_nsec) {
- if (nanosleep(&sleep_time /* to wait */,
- &sleep_time /* remaining (on interrruption) */) == 0) {
- sleep_time = {};
- } else {
- if (errno != EINTR) return errno;
- }
- }
- return 0;
- }
-};
-
-/*
- * VerificationTokens exist to facilitate cross-KeyMint verification of requirements. As
- * such, the precise capabilities required will vary depending on the specific vendor
- * implementations. Essentially, VerificationTokens are a "hook" to enable vendor
- * implementations to communicate, so the precise usage is defined by those vendors. The only
- * thing we really can test is that tokens can be created by TEE keyMints, and that the
- * timestamps increase as expected.
- */
-TEST_P(VerificationTokenTest, TestCreation) {
- auto result1 = verifyAuthorization(1 /* operation handle */, HardwareAuthToken());
- auto result1_time = getTime();
-
- if (SecLevel() == SecurityLevel::STRONGBOX) {
- // StrongBox should not implement verifyAuthorization.
- EXPECT_EQ(ErrorCode::UNIMPLEMENTED, result1.error);
- return;
- }
-
- ASSERT_EQ(ErrorCode::OK, result1.error);
- EXPECT_EQ(1U, result1.token.challenge);
- EXPECT_EQ(SecLevel(), result1.token.securityLevel);
- EXPECT_GT(result1.token.timestamp.milliSeconds, 0U);
-
- constexpr uint32_t time_to_sleep = 200;
- sleep_ms(time_to_sleep);
-
- auto result2 = verifyAuthorization(2 /* operation handle */, HardwareAuthToken());
-
- auto result2_time = getTime();
- ASSERT_EQ(ErrorCode::OK, result2.error);
- EXPECT_EQ(2U, result2.token.challenge);
- EXPECT_EQ(SecLevel(), result2.token.securityLevel);
-
- auto host_time_delta = result2_time - result1_time;
-
- EXPECT_GE(host_time_delta, time_to_sleep)
- << "We slept for " << time_to_sleep << " ms, the clock must have advanced by that much";
- EXPECT_LE(host_time_delta, time_to_sleep + 20)
- << "The verifyAuthorization call took " << (host_time_delta - time_to_sleep)
- << " ms? That's awful!";
-
- auto km_time_delta =
- result2.token.timestamp.milliSeconds - result1.token.timestamp.milliSeconds;
-
- // If not too much else is going on on the system, the time delta should be quite close. Allow
- // 2 ms of slop just to avoid test flakiness.
- //
- // TODO(swillden): see if we can output values so they can be gathered across many runs and
- // report if times aren't nearly always <1ms apart.
- EXPECT_LE(host_time_delta, km_time_delta + 2);
- EXPECT_LE(km_time_delta, host_time_delta + 2);
- ASSERT_EQ(result1.token.mac.size(), result2.token.mac.size());
- ASSERT_NE(0,
- memcmp(result1.token.mac.data(), result2.token.mac.data(), result1.token.mac.size()));
-}
-
-/*
- * Test that the mac changes when the time stamp changes. This is does not guarantee that the time
- * stamp is included in the mac but on failure we know that it is not. Other than in the test
- * case above we call verifyAuthorization with the exact same set of parameters.
- */
-TEST_P(VerificationTokenTest, MacChangesOnChangingTimestamp) {
- auto result1 = verifyAuthorization(0 /* operation handle */, HardwareAuthToken());
- auto result1_time = getTime();
-
- if (SecLevel() == SecurityLevel::STRONGBOX) {
- // StrongBox should not implement verifyAuthorization.
- EXPECT_EQ(ErrorCode::UNIMPLEMENTED, result1.error);
- return;
- }
-
- EXPECT_EQ(ErrorCode::OK, result1.error);
- EXPECT_EQ(0U, result1.token.challenge);
- EXPECT_EQ(SecLevel(), result1.token.securityLevel);
- EXPECT_GT(result1.token.timestamp.milliSeconds, 0U);
-
- constexpr uint32_t time_to_sleep = 200;
- sleep_ms(time_to_sleep);
-
- auto result2 = verifyAuthorization(0 /* operation handle */, HardwareAuthToken());
- // ASSERT_TRUE(result2.callSuccessful);
- auto result2_time = getTime();
- EXPECT_EQ(ErrorCode::OK, result2.error);
- EXPECT_EQ(0U, result2.token.challenge);
- EXPECT_EQ(SecLevel(), result2.token.securityLevel);
-
- auto host_time_delta = result2_time - result1_time;
-
- EXPECT_GE(host_time_delta, time_to_sleep)
- << "We slept for " << time_to_sleep << " ms, the clock must have advanced by that much";
- EXPECT_LE(host_time_delta, time_to_sleep + 20)
- << "The verifyAuthorization call took " << (host_time_delta - time_to_sleep)
- << " ms? That's awful!";
-
- auto km_time_delta =
- result2.token.timestamp.milliSeconds - result1.token.timestamp.milliSeconds;
-
- EXPECT_LE(host_time_delta, km_time_delta + 2);
- EXPECT_LE(km_time_delta, host_time_delta + 2);
- ASSERT_EQ(result1.token.mac.size(), result2.token.mac.size());
- ASSERT_NE(0,
- memcmp(result1.token.mac.data(), result2.token.mac.data(), result1.token.mac.size()));
-}
-
-INSTANTIATE_KEYMINT_AIDL_TEST(VerificationTokenTest);
-
-} // namespace aidl::android::hardware::security::keymint::test
diff --git a/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp b/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp
new file mode 100644
index 0000000..45f9df6
--- /dev/null
+++ b/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "VtsRemotelyProvisionableComponentTests"
+
+#include <RemotelyProvisionedComponent.h>
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.h>
+#include <aidl/android/hardware/security/keymint/SecurityLevel.h>
+#include <android/binder_manager.h>
+#include <cppbor_parse.h>
+#include <cppcose/cppcose.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <keymaster/keymaster_configuration.h>
+#include <remote_prov/remote_prov_utils.h>
+
+namespace aidl::android::hardware::security::keymint::test {
+
+using ::std::string;
+using ::std::vector;
+
+namespace {
+
+#define INSTANTIATE_REM_PROV_AIDL_TEST(name) \
+ INSTANTIATE_TEST_SUITE_P( \
+ PerInstance, name, \
+ testing::ValuesIn(VtsRemotelyProvisionedComponentTests::build_params()), \
+ ::android::PrintInstanceNameToString)
+
+using bytevec = std::vector<uint8_t>;
+using testing::MatchesRegex;
+using namespace remote_prov;
+using namespace keymaster;
+
+bytevec string_to_bytevec(const char* s) {
+ const uint8_t* p = reinterpret_cast<const uint8_t*>(s);
+ return bytevec(p, p + strlen(s));
+}
+
+} // namespace
+
+class VtsRemotelyProvisionedComponentTests : public testing::TestWithParam<std::string> {
+ public:
+ virtual void SetUp() override {
+ if (AServiceManager_isDeclared(GetParam().c_str())) {
+ ::ndk::SpAIBinder binder(AServiceManager_waitForService(GetParam().c_str()));
+ provisionable_ = IRemotelyProvisionedComponent::fromBinder(binder);
+ }
+ ASSERT_NE(provisionable_, nullptr);
+ }
+
+ static vector<string> build_params() {
+ auto params = ::android::getAidlHalInstanceNames(IRemotelyProvisionedComponent::descriptor);
+ return params;
+ }
+
+ protected:
+ std::shared_ptr<IRemotelyProvisionedComponent> provisionable_;
+};
+
+using GenerateKeyTests = VtsRemotelyProvisionedComponentTests;
+
+INSTANTIATE_REM_PROV_AIDL_TEST(GenerateKeyTests);
+
+/**
+ * Generate and validate a production-mode key. MAC tag can't be verified.
+ */
+TEST_P(GenerateKeyTests, DISABLED_generateEcdsaP256Key_prodMode) {
+ MacedPublicKey macedPubKey;
+ bytevec privateKeyBlob;
+ bool testMode = false;
+ auto status = provisionable_->generateEcdsaP256KeyPair(testMode, &macedPubKey, &privateKeyBlob);
+ ASSERT_TRUE(status.isOk());
+
+ auto [coseMac0, _, mac0ParseErr] = cppbor::parse(macedPubKey.macedKey);
+ ASSERT_TRUE(coseMac0) << "COSE Mac0 parse failed " << mac0ParseErr;
+
+ ASSERT_NE(coseMac0->asArray(), nullptr);
+ ASSERT_EQ(coseMac0->asArray()->size(), kCoseMac0EntryCount);
+
+ auto protParms = coseMac0->asArray()->get(kCoseMac0ProtectedParams)->asBstr();
+ ASSERT_NE(protParms, nullptr);
+ ASSERT_EQ(cppbor::prettyPrint(protParms->value()), "{\n 1 : 5,\n}");
+
+ auto unprotParms = coseMac0->asArray()->get(kCoseMac0UnprotectedParams)->asBstr();
+ ASSERT_NE(unprotParms, nullptr);
+ ASSERT_EQ(unprotParms->value().size(), 0);
+
+ auto payload = coseMac0->asArray()->get(kCoseMac0Payload)->asBstr();
+ ASSERT_NE(payload, nullptr);
+ auto [parsedPayload, __, payloadParseErr] = cppbor::parse(payload->value());
+ ASSERT_TRUE(parsedPayload) << "Key parse failed: " << payloadParseErr;
+ EXPECT_THAT(cppbor::prettyPrint(parsedPayload.get()),
+ MatchesRegex("{\n"
+ " 1 : 2,\n"
+ " 3 : -7,\n"
+ " -1 : 1,\n"
+ // The regex {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}} matches a sequence of
+ // 32 hexadecimal bytes, enclosed in braces and separated by commas.
+ // In this case, some Ed25519 public key.
+ " -2 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n"
+ " -3 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n"
+ "}"));
+
+ auto coseMac0Tag = coseMac0->asArray()->get(kCoseMac0Tag)->asBstr();
+ ASSERT_TRUE(coseMac0Tag);
+ auto extractedTag = coseMac0Tag->value();
+ EXPECT_EQ(extractedTag.size(), 32U);
+
+ // Compare with tag generated with kTestMacKey. Shouldn't match.
+ auto testTag = cppcose::generateCoseMac0Mac(remote_prov::kTestMacKey, {} /* external_aad */,
+ payload->value());
+ ASSERT_TRUE(testTag) << "Tag calculation failed: " << testTag.message();
+
+ EXPECT_NE(*testTag, extractedTag);
+}
+
+/**
+ * Generate and validate a test-mode key.
+ */
+TEST_P(GenerateKeyTests, DISABLED_generateEcdsaP256Key_testMode) {
+ MacedPublicKey macedPubKey;
+ bytevec privateKeyBlob;
+ bool testMode = true;
+ auto status = provisionable_->generateEcdsaP256KeyPair(testMode, &macedPubKey, &privateKeyBlob);
+ ASSERT_TRUE(status.isOk());
+
+ auto [coseMac0, _, mac0ParseErr] = cppbor::parse(macedPubKey.macedKey);
+ ASSERT_TRUE(coseMac0) << "COSE Mac0 parse failed " << mac0ParseErr;
+
+ ASSERT_NE(coseMac0->asArray(), nullptr);
+ ASSERT_EQ(coseMac0->asArray()->size(), kCoseMac0EntryCount);
+
+ auto protParms = coseMac0->asArray()->get(kCoseMac0ProtectedParams)->asBstr();
+ ASSERT_NE(protParms, nullptr);
+ ASSERT_EQ(cppbor::prettyPrint(protParms->value()), "{\n 1 : 5,\n}");
+
+ auto unprotParms = coseMac0->asArray()->get(kCoseMac0UnprotectedParams)->asBstr();
+ ASSERT_NE(unprotParms, nullptr);
+ ASSERT_EQ(unprotParms->value().size(), 0);
+
+ auto payload = coseMac0->asArray()->get(kCoseMac0Payload)->asBstr();
+ ASSERT_NE(payload, nullptr);
+ auto [parsedPayload, __, payloadParseErr] = cppbor::parse(payload->value());
+ ASSERT_TRUE(parsedPayload) << "Key parse failed: " << payloadParseErr;
+ EXPECT_THAT(cppbor::prettyPrint(parsedPayload.get()),
+ MatchesRegex("{\n"
+ " 1 : 2,\n"
+ " 3 : -7,\n"
+ " -1 : 1,\n"
+ // The regex {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}} matches a sequence of
+ // 32 hexadecimal bytes, enclosed in braces and separated by commas.
+ // In this case, some Ed25519 public key.
+ " -2 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n"
+ " -3 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n"
+ " -70000 : null,\n"
+ "}"));
+
+ auto coseMac0Tag = coseMac0->asArray()->get(kCoseMac0Tag)->asBstr();
+ ASSERT_TRUE(coseMac0);
+ auto extractedTag = coseMac0Tag->value();
+ EXPECT_EQ(extractedTag.size(), 32U);
+
+ // Compare with tag generated with kTestMacKey. Should match.
+ auto testTag = cppcose::generateCoseMac0Mac(remote_prov::kTestMacKey, {} /* external_aad */,
+ payload->value());
+ ASSERT_TRUE(testTag) << testTag.message();
+
+ EXPECT_EQ(*testTag, extractedTag);
+}
+
+class CertificateRequestTest : public VtsRemotelyProvisionedComponentTests {
+ protected:
+ CertificateRequestTest() : eekId_(string_to_bytevec("eekid")) {
+ auto chain = generateEekChain(3, eekId_);
+ EXPECT_TRUE(chain) << chain.message();
+ if (chain) eekChain_ = chain.moveValue();
+ }
+
+ void generateKeys(bool testMode, size_t numKeys) {
+ keysToSign_ = std::vector<MacedPublicKey>(numKeys);
+ cborKeysToSign_ = cppbor::Array();
+
+ for (auto& key : keysToSign_) {
+ bytevec privateKeyBlob;
+ auto status = provisionable_->generateEcdsaP256KeyPair(testMode, &key, &privateKeyBlob);
+ ASSERT_TRUE(status.isOk()) << status.getMessage();
+
+ auto [parsedMacedKey, _, parseErr] = cppbor::parse(key.macedKey);
+ ASSERT_TRUE(parsedMacedKey) << "Failed parsing MACed key: " << parseErr;
+ ASSERT_TRUE(parsedMacedKey->asArray()) << "COSE_Mac0 not an array?";
+ ASSERT_EQ(parsedMacedKey->asArray()->size(), kCoseMac0EntryCount);
+
+ auto& payload = parsedMacedKey->asArray()->get(kCoseMac0Payload);
+ ASSERT_TRUE(payload);
+ ASSERT_TRUE(payload->asBstr());
+
+ cborKeysToSign_.add(cppbor::EncodedItem(payload->asBstr()->value()));
+ }
+ }
+
+ bytevec eekId_;
+ EekChain eekChain_;
+ std::vector<MacedPublicKey> keysToSign_;
+ cppbor::Array cborKeysToSign_;
+};
+
+/**
+ * Generate an empty certificate request in test mode, and decrypt and verify the structure and
+ * content.
+ */
+TEST_P(CertificateRequestTest, DISABLED_EmptyRequest_testMode) {
+ bool testMode = true;
+ bytevec keysToSignMac;
+ ProtectedData protectedData;
+ auto challenge = randomBytes(32);
+ auto status = provisionable_->generateCertificateRequest(testMode, {} /* keysToSign */,
+ eekChain_.chain, challenge,
+ &keysToSignMac, &protectedData);
+ ASSERT_TRUE(status.isOk()) << status.getMessage();
+
+ auto [parsedProtectedData, _, protDataErrMsg] = cppbor::parse(protectedData.protectedData);
+ ASSERT_TRUE(parsedProtectedData) << protDataErrMsg;
+ ASSERT_TRUE(parsedProtectedData->asArray());
+ ASSERT_EQ(parsedProtectedData->asArray()->size(), kCoseEncryptEntryCount);
+
+ auto senderPubkey = getSenderPubKeyFromCoseEncrypt(parsedProtectedData);
+ ASSERT_TRUE(senderPubkey) << senderPubkey.message();
+ EXPECT_EQ(senderPubkey->second, eekId_);
+
+ auto sessionKey = x25519_HKDF_DeriveKey(eekChain_.last_pubkey, eekChain_.last_privkey,
+ senderPubkey->first, false /* senderIsA */);
+ ASSERT_TRUE(sessionKey) << sessionKey.message();
+
+ auto protectedDataPayload =
+ decryptCoseEncrypt(*sessionKey, parsedProtectedData.get(), bytevec{} /* aad */);
+ ASSERT_TRUE(protectedDataPayload) << protectedDataPayload.message();
+
+ auto [parsedPayload, __, payloadErrMsg] = cppbor::parse(*protectedDataPayload);
+ ASSERT_TRUE(parsedPayload) << "Failed to parse payload: " << payloadErrMsg;
+ ASSERT_TRUE(parsedPayload->asArray());
+ EXPECT_EQ(parsedPayload->asArray()->size(), 2U);
+
+ auto& signedMac = parsedPayload->asArray()->get(0);
+ auto& bcc = parsedPayload->asArray()->get(1);
+ ASSERT_TRUE(signedMac && signedMac->asArray());
+ ASSERT_TRUE(bcc && bcc->asArray());
+
+ // BCC is [ pubkey, + BccEntry]
+ auto bccContents = validateBcc(bcc->asArray());
+ ASSERT_TRUE(bccContents) << "\n" << bccContents.message() << "\n" << prettyPrint(bcc.get());
+ ASSERT_GT(bccContents->size(), 0U);
+
+ auto& signingKey = bccContents->back().pubKey;
+ auto macKey = verifyAndParseCoseSign1(testMode, signedMac->asArray(), signingKey,
+ cppbor::Array() // DeviceInfo
+ .add(challenge) //
+ .add(cppbor::Map())
+ .encode());
+ ASSERT_TRUE(macKey) << macKey.message();
+
+ auto coseMac0 = cppbor::Array()
+ .add(cppbor::Map() // protected
+ .add(ALGORITHM, HMAC_256)
+ .canonicalize()
+ .encode())
+ .add(cppbor::Bstr()) // unprotected
+ .add(cppbor::Array().encode()) // payload (keysToSign)
+ .add(std::move(keysToSignMac)); // tag
+
+ auto macPayload = verifyAndParseCoseMac0(&coseMac0, *macKey);
+ ASSERT_TRUE(macPayload) << macPayload.message();
+}
+
+/**
+ * Generate an empty certificate request in prod mode. Generation will fail because we don't have a
+ * valid GEEK.
+ *
+ * TODO(swillden): Get a valid GEEK and use it so the generation can succeed, though we won't be
+ * able to decrypt.
+ */
+TEST_P(CertificateRequestTest, DISABLED_EmptyRequest_prodMode) {
+ bool testMode = false;
+ bytevec keysToSignMac;
+ ProtectedData protectedData;
+ auto challenge = randomBytes(32);
+ auto status = provisionable_->generateCertificateRequest(testMode, {} /* keysToSign */,
+ eekChain_.chain, challenge,
+ &keysToSignMac, &protectedData);
+ ASSERT_FALSE(status.isOk());
+ ASSERT_EQ(status.getServiceSpecificError(), BnRemotelyProvisionedComponent::STATUS_INVALID_EEK);
+}
+
+/**
+ * Generate a non-empty certificate request in test mode. Decrypt, parse and validate the contents.
+ */
+TEST_P(CertificateRequestTest, DISABLED_NonEmptyRequest_testMode) {
+ bool testMode = true;
+ generateKeys(testMode, 4 /* numKeys */);
+
+ bytevec keysToSignMac;
+ ProtectedData protectedData;
+ auto challenge = randomBytes(32);
+ auto status = provisionable_->generateCertificateRequest(
+ testMode, keysToSign_, eekChain_.chain, challenge, &keysToSignMac, &protectedData);
+ ASSERT_TRUE(status.isOk()) << status.getMessage();
+
+ auto [parsedProtectedData, _, protDataErrMsg] = cppbor::parse(protectedData.protectedData);
+ ASSERT_TRUE(parsedProtectedData) << protDataErrMsg;
+ ASSERT_TRUE(parsedProtectedData->asArray());
+ ASSERT_EQ(parsedProtectedData->asArray()->size(), kCoseEncryptEntryCount);
+
+ auto senderPubkey = getSenderPubKeyFromCoseEncrypt(parsedProtectedData);
+ ASSERT_TRUE(senderPubkey) << senderPubkey.message();
+ EXPECT_EQ(senderPubkey->second, eekId_);
+
+ auto sessionKey = x25519_HKDF_DeriveKey(eekChain_.last_pubkey, eekChain_.last_privkey,
+ senderPubkey->first, false /* senderIsA */);
+ ASSERT_TRUE(sessionKey) << sessionKey.message();
+
+ auto protectedDataPayload =
+ decryptCoseEncrypt(*sessionKey, parsedProtectedData.get(), bytevec{} /* aad */);
+ ASSERT_TRUE(protectedDataPayload) << protectedDataPayload.message();
+
+ auto [parsedPayload, __, payloadErrMsg] = cppbor::parse(*protectedDataPayload);
+ ASSERT_TRUE(parsedPayload) << "Failed to parse payload: " << payloadErrMsg;
+ ASSERT_TRUE(parsedPayload->asArray());
+ EXPECT_EQ(parsedPayload->asArray()->size(), 2U);
+
+ auto& signedMac = parsedPayload->asArray()->get(0);
+ auto& bcc = parsedPayload->asArray()->get(1);
+ ASSERT_TRUE(signedMac && signedMac->asArray());
+ ASSERT_TRUE(bcc);
+
+ auto bccContents = validateBcc(bcc->asArray());
+ ASSERT_TRUE(bccContents) << "\n" << prettyPrint(bcc.get());
+ ASSERT_GT(bccContents->size(), 0U);
+
+ auto& signingKey = bccContents->back().pubKey;
+ auto macKey = verifyAndParseCoseSign1(testMode, signedMac->asArray(), signingKey,
+ cppbor::Array() // DeviceInfo
+ .add(challenge) //
+ .add(cppbor::Array())
+ .encode());
+ ASSERT_TRUE(macKey) << macKey.message();
+
+ auto coseMac0 = cppbor::Array()
+ .add(cppbor::Map() // protected
+ .add(ALGORITHM, HMAC_256)
+ .canonicalize()
+ .encode())
+ .add(cppbor::Bstr()) // unprotected
+ .add(cborKeysToSign_.encode()) // payload
+ .add(std::move(keysToSignMac)); // tag
+
+ auto macPayload = verifyAndParseCoseMac0(&coseMac0, *macKey);
+ ASSERT_TRUE(macPayload) << macPayload.message();
+}
+
+/**
+ * Generate a non-empty certificate request in prod mode. Must fail because we don't have a valid
+ * GEEK.
+ *
+ * TODO(swillden): Get a valid GEEK and use it so the generation can succeed, though we won't be
+ * able to decrypt.
+ */
+TEST_P(CertificateRequestTest, DISABLED_NonEmptyRequest_prodMode) {
+ bool testMode = false;
+ generateKeys(testMode, 4 /* numKeys */);
+
+ bytevec keysToSignMac;
+ ProtectedData protectedData;
+ auto challenge = randomBytes(32);
+ auto status = provisionable_->generateCertificateRequest(
+ testMode, keysToSign_, eekChain_.chain, challenge, &keysToSignMac, &protectedData);
+ ASSERT_FALSE(status.isOk());
+ ASSERT_EQ(status.getServiceSpecificError(), BnRemotelyProvisionedComponent::STATUS_INVALID_EEK);
+}
+
+/**
+ * Generate a non-empty certificate request in test mode, with prod keys. Must fail with
+ * STATUS_PRODUCTION_KEY_IN_TEST_REQUEST.
+ */
+TEST_P(CertificateRequestTest, DISABLED_NonEmptyRequest_prodKeyInTestCert) {
+ generateKeys(false /* testMode */, 2 /* numKeys */);
+
+ bytevec keysToSignMac;
+ ProtectedData protectedData;
+ auto challenge = randomBytes(32);
+ auto status = provisionable_->generateCertificateRequest(true /* testMode */, keysToSign_,
+ eekChain_.chain, challenge,
+ &keysToSignMac, &protectedData);
+ ASSERT_FALSE(status.isOk());
+ ASSERT_EQ(status.getServiceSpecificError(),
+ BnRemotelyProvisionedComponent::STATUS_PRODUCTION_KEY_IN_TEST_REQUEST);
+}
+
+/**
+ * Generate a non-empty certificate request in prod mode, with test keys. Must fail with
+ * STATUS_TEST_KEY_IN_PRODUCTION_REQUEST.
+ */
+TEST_P(CertificateRequestTest, DISABLED_NonEmptyRequest_testKeyInProdCert) {
+ generateKeys(true /* testMode */, 2 /* numKeys */);
+
+ bytevec keysToSignMac;
+ ProtectedData protectedData;
+ auto status = provisionable_->generateCertificateRequest(
+ false /* testMode */, keysToSign_, eekChain_.chain, randomBytes(32) /* challenge */,
+ &keysToSignMac, &protectedData);
+ ASSERT_FALSE(status.isOk());
+ ASSERT_EQ(status.getServiceSpecificError(),
+ BnRemotelyProvisionedComponent::STATUS_TEST_KEY_IN_PRODUCTION_REQUEST);
+}
+
+INSTANTIATE_REM_PROV_AIDL_TEST(CertificateRequestTest);
+
+} // namespace aidl::android::hardware::security::keymint::test
diff --git a/security/keymint/aidl/vts/performance/Android.bp b/security/keymint/aidl/vts/performance/Android.bp
new file mode 100644
index 0000000..03240c3
--- /dev/null
+++ b/security/keymint/aidl/vts/performance/Android.bp
@@ -0,0 +1,38 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_benchmark {
+ name: "VtsAidlKeyMintBenchmarkTest",
+ defaults: [
+ "VtsHalTargetTestDefaults",
+ "use_libaidlvintf_gtest_helper_static",
+ ],
+ srcs: [
+ "KeyMintBenchmark.cpp",
+ ],
+ shared_libs: [
+ "libbinder_ndk",
+ "libcrypto",
+ "libkeymint",
+ "libkeymint_support",
+ ],
+ static_libs: [
+ "android.hardware.security.keymint-V1-ndk_platform",
+ "android.hardware.security.secureclock-V1-ndk_platform",
+ "libcppbor_external",
+ "libchrome",
+ ],
+}
diff --git a/security/keymint/aidl/vts/performance/KeyMintBenchmark.cpp b/security/keymint/aidl/vts/performance/KeyMintBenchmark.cpp
new file mode 100644
index 0000000..f87ca78
--- /dev/null
+++ b/security/keymint/aidl/vts/performance/KeyMintBenchmark.cpp
@@ -0,0 +1,714 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "keymint_benchmark"
+
+#include <base/command_line.h>
+#include <benchmark/benchmark.h>
+#include <iostream>
+
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/security/keymint/ErrorCode.h>
+#include <aidl/android/hardware/security/keymint/IKeyMintDevice.h>
+#include <android/binder_manager.h>
+#include <binder/IServiceManager.h>
+#include <keymint_support/authorization_set.h>
+
+#define SMALL_MESSAGE_SIZE 64
+#define MEDIUM_MESSAGE_SIZE 1024
+#define LARGE_MESSAGE_SIZE 131072
+
+namespace aidl::android::hardware::security::keymint::test {
+
+::std::ostream& operator<<(::std::ostream& os, const keymint::AuthorizationSet& set);
+
+using ::android::sp;
+using Status = ::ndk::ScopedAStatus;
+using ::std::optional;
+using ::std::shared_ptr;
+using ::std::string;
+using ::std::vector;
+
+class KeyMintBenchmarkTest {
+ public:
+ KeyMintBenchmarkTest() {
+ message_cache_.push_back(string(SMALL_MESSAGE_SIZE, 'x'));
+ message_cache_.push_back(string(MEDIUM_MESSAGE_SIZE, 'x'));
+ message_cache_.push_back(string(LARGE_MESSAGE_SIZE, 'x'));
+ }
+
+ static KeyMintBenchmarkTest* newInstance(const char* instanceName) {
+ if (AServiceManager_isDeclared(instanceName)) {
+ ::ndk::SpAIBinder binder(AServiceManager_waitForService(instanceName));
+ KeyMintBenchmarkTest* test = new KeyMintBenchmarkTest();
+ test->InitializeKeyMint(IKeyMintDevice::fromBinder(binder));
+ return test;
+ } else {
+ return nullptr;
+ }
+ }
+
+ int getError() { return static_cast<int>(error_); }
+
+ const string& GenerateMessage(int size) {
+ for (const string& message : message_cache_) {
+ if (message.size() == size) {
+ return message;
+ }
+ }
+ string message = string(size, 'x');
+ message_cache_.push_back(message);
+ return std::move(message);
+ }
+
+ optional<BlockMode> getBlockMode(string transform) {
+ if (transform.find("/ECB") != string::npos) {
+ return BlockMode::ECB;
+ } else if (transform.find("/CBC") != string::npos) {
+ return BlockMode::CBC;
+ } else if (transform.find("/CTR") != string::npos) {
+ return BlockMode::CTR;
+ } else if (transform.find("/GCM") != string::npos) {
+ return BlockMode::GCM;
+ }
+ return {};
+ }
+
+ PaddingMode getPadding(string transform, bool sign) {
+ if (transform.find("/PKCS7") != string::npos) {
+ return PaddingMode::PKCS7;
+ } else if (transform.find("/PSS") != string::npos) {
+ return PaddingMode::RSA_PSS;
+ } else if (transform.find("/OAEP") != string::npos) {
+ return PaddingMode::RSA_OAEP;
+ } else if (transform.find("/PKCS1") != string::npos) {
+ return sign ? PaddingMode::RSA_PKCS1_1_5_SIGN : PaddingMode::RSA_PKCS1_1_5_ENCRYPT;
+ } else if (sign && transform.find("RSA") != string::npos) {
+ // RSA defaults to PKCS1 for sign
+ return PaddingMode::RSA_PKCS1_1_5_SIGN;
+ }
+ return PaddingMode::NONE;
+ }
+
+ optional<Algorithm> getAlgorithm(string transform) {
+ if (transform.find("AES") != string::npos) {
+ return Algorithm::AES;
+ } else if (transform.find("Hmac") != string::npos) {
+ return Algorithm::HMAC;
+ } else if (transform.find("DESede") != string::npos) {
+ return Algorithm::TRIPLE_DES;
+ } else if (transform.find("RSA") != string::npos) {
+ return Algorithm::RSA;
+ } else if (transform.find("EC") != string::npos) {
+ return Algorithm::EC;
+ }
+ std::cerr << "Can't find algorithm for " << transform << std::endl;
+ return {};
+ }
+
+ Digest getDigest(string transform) {
+ if (transform.find("MD5") != string::npos) {
+ return Digest::MD5;
+ } else if (transform.find("SHA1") != string::npos ||
+ transform.find("SHA-1") != string::npos) {
+ return Digest::SHA1;
+ } else if (transform.find("SHA224") != string::npos) {
+ return Digest::SHA_2_224;
+ } else if (transform.find("SHA256") != string::npos) {
+ return Digest::SHA_2_256;
+ } else if (transform.find("SHA384") != string::npos) {
+ return Digest::SHA_2_384;
+ } else if (transform.find("SHA512") != string::npos) {
+ return Digest::SHA_2_512;
+ } else if (transform.find("RSA") != string::npos &&
+ transform.find("OAEP") != string::npos) {
+ return Digest::SHA1;
+ } else if (transform.find("Hmac") != string::npos) {
+ return Digest::SHA_2_256;
+ }
+ return Digest::NONE;
+ }
+
+ bool GenerateKey(string transform, int keySize, bool sign = false) {
+ if (transform == key_transform_) {
+ return true;
+ } else if (key_transform_ != "") {
+ // Deleting old key first
+ key_transform_ = "";
+ if (DeleteKey() != ErrorCode::OK) {
+ return false;
+ }
+ }
+ std::optional<Algorithm> algorithm = getAlgorithm(transform);
+ if (!algorithm) {
+ std::cerr << "Error: invalid algorithm " << transform << std::endl;
+ return false;
+ }
+ key_transform_ = transform;
+ AuthorizationSetBuilder authSet = AuthorizationSetBuilder()
+ .Authorization(TAG_NO_AUTH_REQUIRED)
+ .Authorization(TAG_PURPOSE, KeyPurpose::ENCRYPT)
+ .Authorization(TAG_PURPOSE, KeyPurpose::DECRYPT)
+ .Authorization(TAG_PURPOSE, KeyPurpose::SIGN)
+ .Authorization(TAG_PURPOSE, KeyPurpose::VERIFY)
+ .Authorization(TAG_KEY_SIZE, keySize)
+ .Authorization(TAG_ALGORITHM, algorithm.value())
+ .Digest(getDigest(transform))
+ .Padding(getPadding(transform, sign));
+ std::optional<BlockMode> blockMode = getBlockMode(transform);
+ if (blockMode) {
+ authSet.BlockMode(blockMode.value());
+ if (blockMode == BlockMode::GCM) {
+ authSet.Authorization(TAG_MIN_MAC_LENGTH, 128);
+ }
+ }
+ if (algorithm == Algorithm::HMAC) {
+ authSet.Authorization(TAG_MIN_MAC_LENGTH, 128);
+ }
+ if (algorithm == Algorithm::RSA) {
+ authSet.Authorization(TAG_RSA_PUBLIC_EXPONENT, 65537U);
+ authSet.SetDefaultValidity();
+ }
+ if (algorithm == Algorithm::EC) {
+ authSet.SetDefaultValidity();
+ }
+ error_ = GenerateKey(authSet);
+ return error_ == ErrorCode::OK;
+ }
+
+ AuthorizationSet getOperationParams(string transform, bool sign = false) {
+ AuthorizationSetBuilder builder = AuthorizationSetBuilder()
+ .Padding(getPadding(transform, sign))
+ .Digest(getDigest(transform));
+ std::optional<BlockMode> blockMode = getBlockMode(transform);
+ if (sign && (transform.find("Hmac") != string::npos)) {
+ builder.Authorization(TAG_MAC_LENGTH, 128);
+ }
+ if (blockMode) {
+ builder.BlockMode(*blockMode);
+ if (blockMode == BlockMode::GCM) {
+ builder.Authorization(TAG_MAC_LENGTH, 128);
+ }
+ }
+ return std::move(builder);
+ }
+
+ optional<string> Process(const string& message, const AuthorizationSet& /*in_params*/,
+ AuthorizationSet* out_params, const string& signature = "") {
+ static const int HIDL_BUFFER_LIMIT = 1 << 14; // 16KB
+ ErrorCode result;
+
+ // Update
+ AuthorizationSet update_params;
+ AuthorizationSet update_out_params;
+ string output;
+ string aidl_output;
+ int32_t input_consumed = 0;
+ int32_t aidl_input_consumed = 0;
+ while (message.length() - input_consumed > 0) {
+ result = Update(update_params, message.substr(input_consumed, HIDL_BUFFER_LIMIT),
+ &update_out_params, &aidl_output, &aidl_input_consumed);
+ if (result != ErrorCode::OK) {
+ error_ = result;
+ return {};
+ }
+ output.append(aidl_output);
+ input_consumed += aidl_input_consumed;
+ aidl_output.clear();
+ }
+
+ // Finish
+ AuthorizationSet finish_params;
+ AuthorizationSet finish_out_params;
+ result = Finish(finish_params, message.substr(input_consumed), signature,
+ &finish_out_params, &aidl_output);
+ if (result != ErrorCode::OK) {
+ error_ = result;
+ return {};
+ }
+ output.append(aidl_output);
+ out_params->push_back(finish_out_params);
+ return output;
+ }
+
+ ErrorCode DeleteKey() {
+ Status result = keymint_->deleteKey(key_blob_);
+ key_blob_ = vector<uint8_t>();
+ return GetReturnErrorCode(result);
+ }
+
+ ErrorCode Begin(KeyPurpose purpose, const AuthorizationSet& in_params,
+ AuthorizationSet* out_params) {
+ Status result;
+ BeginResult out;
+ result = keymint_->begin(purpose, key_blob_, in_params.vector_data(), HardwareAuthToken(),
+ &out);
+ if (result.isOk()) {
+ *out_params = out.params;
+ op_ = out.operation;
+ }
+ return GetReturnErrorCode(result);
+ }
+
+ SecurityLevel securityLevel_;
+ string name_;
+
+ private:
+ ErrorCode GenerateKey(const AuthorizationSet& key_desc,
+ const optional<AttestationKey>& attest_key = std::nullopt) {
+ key_blob_.clear();
+ KeyCreationResult creationResult;
+ Status result = keymint_->generateKey(key_desc.vector_data(), attest_key, &creationResult);
+ if (result.isOk()) {
+ key_blob_ = std::move(creationResult.keyBlob);
+ creationResult.keyCharacteristics.clear();
+ creationResult.certificateChain.clear();
+ }
+ return GetReturnErrorCode(result);
+ }
+
+ void InitializeKeyMint(std::shared_ptr<IKeyMintDevice> keyMint) {
+ if (!keyMint) {
+ std::cerr << "Trying initialize nullptr in InitializeKeyMint" << std::endl;
+ return;
+ }
+ keymint_ = std::move(keyMint);
+ KeyMintHardwareInfo info;
+ Status result = keymint_->getHardwareInfo(&info);
+ if (!result.isOk()) {
+ std::cerr << "InitializeKeyMint: getHardwareInfo failed with "
+ << result.getServiceSpecificError() << std::endl;
+ }
+ securityLevel_ = info.securityLevel;
+ name_.assign(info.keyMintName.begin(), info.keyMintName.end());
+ }
+
+ ErrorCode Finish(const AuthorizationSet& in_params, const string& input,
+ const string& signature, AuthorizationSet* out_params, string* output) {
+ Status result;
+ if (!op_) {
+ std::cerr << "Finish: Operation is nullptr" << std::endl;
+ return ErrorCode::UNEXPECTED_NULL_POINTER;
+ }
+ KeyParameterArray key_params;
+ key_params.params = in_params.vector_data();
+
+ KeyParameterArray in_keyParams;
+ in_keyParams.params = in_params.vector_data();
+
+ std::optional<KeyParameterArray> out_keyParams;
+ std::optional<vector<uint8_t>> o_put;
+
+ vector<uint8_t> oPut;
+ result = op_->finish(in_keyParams, vector<uint8_t>(input.begin(), input.end()),
+ vector<uint8_t>(signature.begin(), signature.end()), {}, {},
+ &out_keyParams, &oPut);
+
+ if (result.isOk()) {
+ if (out_keyParams) {
+ out_params->push_back(AuthorizationSet(out_keyParams->params));
+ }
+ output->append(oPut.begin(), oPut.end());
+ }
+ op_.reset();
+ return GetReturnErrorCode(result);
+ }
+
+ ErrorCode Update(const AuthorizationSet& in_params, const string& input,
+ AuthorizationSet* out_params, string* output, int32_t* input_consumed) {
+ Status result;
+ if (!op_) {
+ std::cerr << "Update: Operation is nullptr" << std::endl;
+ return ErrorCode::UNEXPECTED_NULL_POINTER;
+ }
+
+ KeyParameterArray key_params;
+ key_params.params = in_params.vector_data();
+
+ KeyParameterArray in_keyParams;
+ in_keyParams.params = in_params.vector_data();
+
+ std::optional<KeyParameterArray> out_keyParams;
+ std::optional<ByteArray> o_put;
+ result = op_->update(in_keyParams, vector<uint8_t>(input.begin(), input.end()), {}, {},
+ &out_keyParams, &o_put, input_consumed);
+
+ if (result.isOk()) {
+ if (o_put) {
+ output->append(o_put->data.begin(), o_put->data.end());
+ }
+
+ if (out_keyParams) {
+ out_params->push_back(AuthorizationSet(out_keyParams->params));
+ }
+ }
+
+ return GetReturnErrorCode(result);
+ }
+
+ ErrorCode GetReturnErrorCode(const Status& result) {
+ error_ = static_cast<ErrorCode>(result.getServiceSpecificError());
+ if (result.isOk()) return ErrorCode::OK;
+
+ if (result.getExceptionCode() == EX_SERVICE_SPECIFIC) {
+ return static_cast<ErrorCode>(result.getServiceSpecificError());
+ }
+
+ return ErrorCode::UNKNOWN_ERROR;
+ }
+
+ std::shared_ptr<IKeyMintOperation> op_;
+ vector<Certificate> cert_chain_;
+ vector<uint8_t> key_blob_;
+ vector<KeyCharacteristics> key_characteristics_;
+ std::shared_ptr<IKeyMintDevice> keymint_;
+ std::vector<string> message_cache_;
+ std::string key_transform_;
+ ErrorCode error_;
+};
+
+KeyMintBenchmarkTest* keymintTest;
+
+static void settings(benchmark::internal::Benchmark* benchmark) {
+ benchmark->Unit(benchmark::kMillisecond);
+}
+
+static void addDefaultLabel(benchmark::State& state) {
+ std::string secLevel;
+ switch (keymintTest->securityLevel_) {
+ case SecurityLevel::STRONGBOX:
+ secLevel = "STRONGBOX";
+ break;
+ case SecurityLevel::SOFTWARE:
+ secLevel = "SOFTWARE";
+ break;
+ case SecurityLevel::TRUSTED_ENVIRONMENT:
+ secLevel = "TEE";
+ break;
+ case SecurityLevel::KEYSTORE:
+ secLevel = "KEYSTORE";
+ break;
+ }
+ state.SetLabel("hardware_name:" + keymintTest->name_ + " sec_level:" + secLevel);
+}
+
+// clang-format off
+#define BENCHMARK_KM(func, transform, keySize) \
+ BENCHMARK_CAPTURE(func, transform/keySize, #transform "/" #keySize, keySize)->Apply(settings);
+#define BENCHMARK_KM_MSG(func, transform, keySize, msgSize) \
+ BENCHMARK_CAPTURE(func, transform/keySize/msgSize, #transform "/" #keySize "/" #msgSize, \
+ keySize, msgSize) \
+ ->Apply(settings);
+
+#define BENCHMARK_KM_ALL_MSGS(func, transform, keySize) \
+ BENCHMARK_KM_MSG(func, transform, keySize, SMALL_MESSAGE_SIZE) \
+ BENCHMARK_KM_MSG(func, transform, keySize, MEDIUM_MESSAGE_SIZE) \
+ BENCHMARK_KM_MSG(func, transform, keySize, LARGE_MESSAGE_SIZE)
+
+#define BENCHMARK_KM_CIPHER(transform, keySize, msgSize) \
+ BENCHMARK_KM_MSG(encrypt, transform, keySize, msgSize) \
+ BENCHMARK_KM_MSG(decrypt, transform, keySize, msgSize)
+
+#define BENCHMARK_KM_CIPHER_ALL_MSGS(transform, keySize) \
+ BENCHMARK_KM_ALL_MSGS(encrypt, transform, keySize) \
+ BENCHMARK_KM_ALL_MSGS(decrypt, transform, keySize)
+
+#define BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, keySize) \
+ BENCHMARK_KM_ALL_MSGS(sign, transform, keySize) \
+ BENCHMARK_KM_ALL_MSGS(verify, transform, keySize)
+// clang-format on
+
+/*
+ * ============= KeyGen TESTS ==================
+ */
+static void keygen(benchmark::State& state, string transform, int keySize) {
+ addDefaultLabel(state);
+ for (auto _ : state) {
+ if (!keymintTest->GenerateKey(transform, keySize)) {
+ state.SkipWithError(
+ ("Key generation error, " + std::to_string(keymintTest->getError())).c_str());
+ }
+ state.PauseTiming();
+
+ keymintTest->DeleteKey();
+ state.ResumeTiming();
+ }
+}
+
+BENCHMARK_KM(keygen, AES, 128);
+BENCHMARK_KM(keygen, AES, 256);
+
+BENCHMARK_KM(keygen, RSA, 2048);
+BENCHMARK_KM(keygen, RSA, 3072);
+BENCHMARK_KM(keygen, RSA, 4096);
+
+BENCHMARK_KM(keygen, EC, 224);
+BENCHMARK_KM(keygen, EC, 256);
+BENCHMARK_KM(keygen, EC, 384);
+BENCHMARK_KM(keygen, EC, 521);
+
+BENCHMARK_KM(keygen, DESede, 168);
+
+BENCHMARK_KM(keygen, Hmac, 64);
+BENCHMARK_KM(keygen, Hmac, 128);
+BENCHMARK_KM(keygen, Hmac, 256);
+BENCHMARK_KM(keygen, Hmac, 512);
+
+/*
+ * ============= SIGNATURE TESTS ==================
+ */
+
+static void sign(benchmark::State& state, string transform, int keySize, int msgSize) {
+ addDefaultLabel(state);
+ if (!keymintTest->GenerateKey(transform, keySize, true)) {
+ state.SkipWithError(
+ ("Key generation error, " + std::to_string(keymintTest->getError())).c_str());
+ return;
+ }
+
+ auto in_params = keymintTest->getOperationParams(transform, true);
+ AuthorizationSet out_params;
+ string message = keymintTest->GenerateMessage(msgSize);
+
+ for (auto _ : state) {
+ state.PauseTiming();
+ ErrorCode error = keymintTest->Begin(KeyPurpose::SIGN, in_params, &out_params);
+ if (error != ErrorCode::OK) {
+ state.SkipWithError(
+ ("Error beginning sign, " + std::to_string(keymintTest->getError())).c_str());
+ return;
+ }
+ state.ResumeTiming();
+ out_params.Clear();
+ if (!keymintTest->Process(message, in_params, &out_params)) {
+ state.SkipWithError(("Sign error, " + std::to_string(keymintTest->getError())).c_str());
+ break;
+ }
+ }
+}
+
+static void verify(benchmark::State& state, string transform, int keySize, int msgSize) {
+ addDefaultLabel(state);
+ if (!keymintTest->GenerateKey(transform, keySize, true)) {
+ state.SkipWithError(
+ ("Key generation error, " + std::to_string(keymintTest->getError())).c_str());
+ return;
+ }
+ AuthorizationSet out_params;
+ auto in_params = keymintTest->getOperationParams(transform, true);
+ string message = keymintTest->GenerateMessage(msgSize);
+ ErrorCode error = keymintTest->Begin(KeyPurpose::SIGN, in_params, &out_params);
+ if (error != ErrorCode::OK) {
+ state.SkipWithError(
+ ("Error beginning sign, " + std::to_string(keymintTest->getError())).c_str());
+ return;
+ }
+ std::optional<string> signature = keymintTest->Process(message, in_params, &out_params);
+ if (!signature) {
+ state.SkipWithError(("Sign error, " + std::to_string(keymintTest->getError())).c_str());
+ return;
+ }
+ out_params.Clear();
+ if (transform.find("Hmac") != string::npos) {
+ in_params = keymintTest->getOperationParams(transform, false);
+ }
+ for (auto _ : state) {
+ state.PauseTiming();
+ error = keymintTest->Begin(KeyPurpose::VERIFY, in_params, &out_params);
+ if (error != ErrorCode::OK) {
+ state.SkipWithError(
+ ("Verify begin error, " + std::to_string(keymintTest->getError())).c_str());
+ return;
+ }
+ state.ResumeTiming();
+ if (!keymintTest->Process(message, in_params, &out_params, *signature)) {
+ state.SkipWithError(
+ ("Verify error, " + std::to_string(keymintTest->getError())).c_str());
+ break;
+ }
+ }
+}
+
+// clang-format off
+#define BENCHMARK_KM_SIGNATURE_ALL_HMAC_KEYS(transform) \
+ BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, 64) \
+ BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, 128) \
+ BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, 256) \
+ BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, 512)
+
+BENCHMARK_KM_SIGNATURE_ALL_HMAC_KEYS(HmacSHA1)
+BENCHMARK_KM_SIGNATURE_ALL_HMAC_KEYS(HmacSHA256)
+BENCHMARK_KM_SIGNATURE_ALL_HMAC_KEYS(HmacSHA224)
+BENCHMARK_KM_SIGNATURE_ALL_HMAC_KEYS(HmacSHA256)
+BENCHMARK_KM_SIGNATURE_ALL_HMAC_KEYS(HmacSHA384)
+BENCHMARK_KM_SIGNATURE_ALL_HMAC_KEYS(HmacSHA512)
+
+#define BENCHMARK_KM_SIGNATURE_ALL_ECDSA_KEYS(transform) \
+ BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, 224) \
+ BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, 256) \
+ BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, 384) \
+ BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, 521)
+
+BENCHMARK_KM_SIGNATURE_ALL_ECDSA_KEYS(NONEwithECDSA);
+BENCHMARK_KM_SIGNATURE_ALL_ECDSA_KEYS(SHA1withECDSA);
+BENCHMARK_KM_SIGNATURE_ALL_ECDSA_KEYS(SHA224withECDSA);
+BENCHMARK_KM_SIGNATURE_ALL_ECDSA_KEYS(SHA256withECDSA);
+BENCHMARK_KM_SIGNATURE_ALL_ECDSA_KEYS(SHA384withECDSA);
+BENCHMARK_KM_SIGNATURE_ALL_ECDSA_KEYS(SHA512withECDSA);
+
+#define BENCHMARK_KM_SIGNATURE_ALL_RSA_KEYS(transform) \
+ BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, 2048) \
+ BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, 3072) \
+ BENCHMARK_KM_SIGNATURE_ALL_MSGS(transform, 4096)
+
+BENCHMARK_KM_SIGNATURE_ALL_RSA_KEYS(MD5withRSA);
+BENCHMARK_KM_SIGNATURE_ALL_RSA_KEYS(SHA1withRSA);
+BENCHMARK_KM_SIGNATURE_ALL_RSA_KEYS(SHA224withRSA);
+BENCHMARK_KM_SIGNATURE_ALL_RSA_KEYS(SHA384withRSA);
+BENCHMARK_KM_SIGNATURE_ALL_RSA_KEYS(SHA512withRSA);
+
+BENCHMARK_KM_SIGNATURE_ALL_RSA_KEYS(MD5withRSA/PSS);
+BENCHMARK_KM_SIGNATURE_ALL_RSA_KEYS(SHA1withRSA/PSS);
+BENCHMARK_KM_SIGNATURE_ALL_RSA_KEYS(SHA224withRSA/PSS);
+BENCHMARK_KM_SIGNATURE_ALL_RSA_KEYS(SHA384withRSA/PSS);
+BENCHMARK_KM_SIGNATURE_ALL_RSA_KEYS(SHA512withRSA/PSS);
+// clang-format on
+
+/*
+ * ============= CIPHER TESTS ==================
+ */
+
+static void encrypt(benchmark::State& state, string transform, int keySize, int msgSize) {
+ addDefaultLabel(state);
+ if (!keymintTest->GenerateKey(transform, keySize)) {
+ state.SkipWithError(
+ ("Key generation error, " + std::to_string(keymintTest->getError())).c_str());
+ return;
+ }
+ auto in_params = keymintTest->getOperationParams(transform);
+ AuthorizationSet out_params;
+ string message = keymintTest->GenerateMessage(msgSize);
+
+ for (auto _ : state) {
+ state.PauseTiming();
+ auto error = keymintTest->Begin(KeyPurpose::ENCRYPT, in_params, &out_params);
+ if (error != ErrorCode::OK) {
+ state.SkipWithError(
+ ("Encryption begin error, " + std::to_string(keymintTest->getError())).c_str());
+ return;
+ }
+ out_params.Clear();
+ state.ResumeTiming();
+ if (!keymintTest->Process(message, in_params, &out_params)) {
+ state.SkipWithError(
+ ("Encryption error, " + std::to_string(keymintTest->getError())).c_str());
+ break;
+ }
+ }
+}
+
+static void decrypt(benchmark::State& state, string transform, int keySize, int msgSize) {
+ addDefaultLabel(state);
+ if (!keymintTest->GenerateKey(transform, keySize)) {
+ state.SkipWithError(
+ ("Key generation error, " + std::to_string(keymintTest->getError())).c_str());
+ return;
+ }
+ AuthorizationSet out_params;
+ AuthorizationSet in_params = keymintTest->getOperationParams(transform);
+ string message = keymintTest->GenerateMessage(msgSize);
+ auto error = keymintTest->Begin(KeyPurpose::ENCRYPT, in_params, &out_params);
+ if (error != ErrorCode::OK) {
+ state.SkipWithError(
+ ("Encryption begin error, " + std::to_string(keymintTest->getError())).c_str());
+ return;
+ }
+ auto encryptedMessage = keymintTest->Process(message, in_params, &out_params);
+ if (!encryptedMessage) {
+ state.SkipWithError(
+ ("Encryption error, " + std::to_string(keymintTest->getError())).c_str());
+ return;
+ }
+ in_params.push_back(out_params);
+ out_params.Clear();
+ for (auto _ : state) {
+ state.PauseTiming();
+ error = keymintTest->Begin(KeyPurpose::DECRYPT, in_params, &out_params);
+ if (error != ErrorCode::OK) {
+ state.SkipWithError(
+ ("Decryption begin error, " + std::to_string(keymintTest->getError())).c_str());
+ return;
+ }
+ state.ResumeTiming();
+ if (!keymintTest->Process(*encryptedMessage, in_params, &out_params)) {
+ state.SkipWithError(
+ ("Decryption error, " + std::to_string(keymintTest->getError())).c_str());
+ break;
+ }
+ }
+}
+
+// clang-format off
+// AES
+#define BENCHMARK_KM_CIPHER_ALL_AES_KEYS(transform) \
+ BENCHMARK_KM_CIPHER_ALL_MSGS(transform, 128) \
+ BENCHMARK_KM_CIPHER_ALL_MSGS(transform, 256)
+
+BENCHMARK_KM_CIPHER_ALL_AES_KEYS(AES/CBC/NoPadding);
+BENCHMARK_KM_CIPHER_ALL_AES_KEYS(AES/CBC/PKCS7Padding);
+BENCHMARK_KM_CIPHER_ALL_AES_KEYS(AES/CTR/NoPadding);
+BENCHMARK_KM_CIPHER_ALL_AES_KEYS(AES/ECB/NoPadding);
+BENCHMARK_KM_CIPHER_ALL_AES_KEYS(AES/ECB/PKCS7Padding);
+BENCHMARK_KM_CIPHER_ALL_AES_KEYS(AES/GCM/NoPadding);
+
+// Triple DES
+BENCHMARK_KM_CIPHER_ALL_MSGS(DESede/CBC/NoPadding, 168);
+BENCHMARK_KM_CIPHER_ALL_MSGS(DESede/CBC/PKCS7Padding, 168);
+BENCHMARK_KM_CIPHER_ALL_MSGS(DESede/ECB/NoPadding, 168);
+BENCHMARK_KM_CIPHER_ALL_MSGS(DESede/ECB/PKCS7Padding, 168);
+
+#define BENCHMARK_KM_CIPHER_ALL_RSA_KEYS(transform, msgSize) \
+ BENCHMARK_KM_CIPHER(transform, 2048, msgSize) \
+ BENCHMARK_KM_CIPHER(transform, 3072, msgSize) \
+ BENCHMARK_KM_CIPHER(transform, 4096, msgSize)
+
+BENCHMARK_KM_CIPHER_ALL_RSA_KEYS(RSA/ECB/NoPadding, SMALL_MESSAGE_SIZE);
+BENCHMARK_KM_CIPHER_ALL_RSA_KEYS(RSA/ECB/PKCS1Padding, SMALL_MESSAGE_SIZE);
+BENCHMARK_KM_CIPHER_ALL_RSA_KEYS(RSA/ECB/OAEPPadding, SMALL_MESSAGE_SIZE);
+
+// clang-format on
+} // namespace aidl::android::hardware::security::keymint::test
+
+int main(int argc, char** argv) {
+ ::benchmark::Initialize(&argc, argv);
+ base::CommandLine::Init(argc, argv);
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ auto service_name = command_line->GetSwitchValueASCII("service_name");
+ if (service_name.empty()) {
+ service_name =
+ std::string(
+ aidl::android::hardware::security::keymint::IKeyMintDevice::descriptor) +
+ "/default";
+ }
+ std::cerr << service_name << std::endl;
+ aidl::android::hardware::security::keymint::test::keymintTest =
+ aidl::android::hardware::security::keymint::test::KeyMintBenchmarkTest::newInstance(
+ service_name.c_str());
+ if (!aidl::android::hardware::security::keymint::test::keymintTest) {
+ return 1;
+ }
+ ::benchmark::RunSpecifiedBenchmarks();
+}
diff --git a/security/keymint/aidl/vts/performance/README b/security/keymint/aidl/vts/performance/README
new file mode 100644
index 0000000..1221ad8
--- /dev/null
+++ b/security/keymint/aidl/vts/performance/README
@@ -0,0 +1,28 @@
+# KeyMint Benchmark
+
+The KeyMint Benchmark is a standalone tool for measuring the performance of
+ KeyMint implementations.
+
+## Building
+
+Build:
+`m VtsAidlKeyMintBenchmarkTest`
+
+Transfer to device/emulator:
+`adb sync data`
+
+The benchmark executable will be located at
+ `data/benchmarktest/VtsAidlKeyMintBenchmarkTest` on the device.
+
+## Usage
+
+KeyMint Benchmark is built on [Google microbenchmark
+library](https://github.com/google/benchmark). All of the commandline arguments
+provided by the microbenchmark library are valid, such as
+`--benchmark_filter=<regex>` or `benchmark_out_format={json|console|csv}`.
+In addition to the command line arguments provided by microbenchmark,
+`--service_name=<service_name>` is provided to allow specification of the KeyMint
+fully qualified service name, e.g. specify
+`--service_name=android.hardware.security.keymint.IKeyMintDevice/default` to
+benchmark default implementation of KeyMint.
+
diff --git a/security/keymint/support/Android.bp b/security/keymint/support/Android.bp
index 0cfa798..0255874 100644
--- a/security/keymint/support/Android.bp
+++ b/security/keymint/support/Android.bp
@@ -31,9 +31,46 @@
"include",
],
shared_libs: [
- "android.hardware.security.keymint-unstable-ndk_platform",
+ "android.hardware.security.keymint-V1-ndk_platform",
"libbase",
"libcrypto",
"libutils",
],
}
+
+cc_library {
+ name: "libkeymint_remote_prov_support",
+ vendor_available: true,
+ srcs: [
+ "remote_prov_utils.cpp",
+ ],
+ export_include_dirs: [
+ "include",
+ ],
+ shared_libs: [
+ "libcppcose",
+ "libcppbor_external",
+ "libcrypto",
+ ],
+}
+
+cc_library {
+ name: "libcppcose",
+ vendor_available: true,
+ srcs: [
+ "cppcose.cpp",
+ ],
+ export_include_dirs: [
+ "include",
+ ],
+ shared_libs: [
+ "libbinder_ndk",
+ "libcppbor_external",
+ "libcrypto",
+ "liblog",
+ ],
+ static_libs: [
+ // TODO(swillden): Remove keymint NDK
+ "android.hardware.security.keymint-V1-ndk_platform",
+ ],
+}
diff --git a/security/keymint/support/attestation_record.cpp b/security/keymint/support/attestation_record.cpp
index 596b097..a48f770 100644
--- a/security/keymint/support/attestation_record.cpp
+++ b/security/keymint/support/attestation_record.cpp
@@ -97,6 +97,7 @@
ASN1_NULL* device_unique_attestation;
ASN1_NULL* storage_key;
ASN1_NULL* identity_credential;
+ ASN1_INTEGER* usage_count_limit;
} KM_AUTH_LIST;
ASN1_SEQUENCE(KM_AUTH_LIST) = {
@@ -143,7 +144,8 @@
ASN1_EXP_OPT(KM_AUTH_LIST, storage_key, ASN1_NULL, TAG_STORAGE_KEY.maskedTag()),
ASN1_EXP_OPT(KM_AUTH_LIST, identity_credential, ASN1_NULL,
TAG_IDENTITY_CREDENTIAL_KEY.maskedTag()),
-
+ ASN1_EXP_OPT(KM_AUTH_LIST, usage_count_limit, ASN1_INTEGER,
+ TAG_USAGE_COUNT_LIMIT.maskedTag()),
} ASN1_SEQUENCE_END(KM_AUTH_LIST);
IMPLEMENT_ASN1_FUNCTIONS(KM_AUTH_LIST);
@@ -285,6 +287,7 @@
copyAuthTag(record->device_unique_attestation, TAG_DEVICE_UNIQUE_ATTESTATION, auth_list);
copyAuthTag(record->storage_key, TAG_STORAGE_KEY, auth_list);
copyAuthTag(record->identity_credential, TAG_IDENTITY_CREDENTIAL_KEY, auth_list);
+ copyAuthTag(record->usage_count_limit, TAG_USAGE_COUNT_LIMIT, auth_list);
return ErrorCode::OK;
}
diff --git a/security/keymint/support/authorization_set.cpp b/security/keymint/support/authorization_set.cpp
index f98851c..25eace3 100644
--- a/security/keymint/support/authorization_set.cpp
+++ b/security/keymint/support/authorization_set.cpp
@@ -191,6 +191,10 @@
return Authorization(TAG_PURPOSE, KeyPurpose::DECRYPT);
}
+AuthorizationSetBuilder& AuthorizationSetBuilder::AttestKey() {
+ return Authorization(TAG_PURPOSE, KeyPurpose::ATTEST_KEY);
+}
+
AuthorizationSetBuilder& AuthorizationSetBuilder::NoDigestOrPadding() {
Authorization(TAG_DIGEST, Digest::NONE);
return Authorization(TAG_PADDING, PaddingMode::NONE);
@@ -227,6 +231,14 @@
return *this;
}
+AuthorizationSetBuilder& AuthorizationSetBuilder::OaepMGFDigest(
+ const std::vector<android::hardware::security::keymint::Digest>& digests) {
+ for (auto digest : digests) {
+ push_back(TAG_RSA_OAEP_MGF_DIGEST, digest);
+ }
+ return *this;
+}
+
AuthorizationSetBuilder& AuthorizationSetBuilder::Padding(
std::initializer_list<PaddingMode> paddingModes) {
for (auto paddingMode : paddingModes) {
@@ -235,4 +247,12 @@
return *this;
}
+AuthorizationSetBuilder& AuthorizationSetBuilder::SetDefaultValidity() {
+ // Per RFC 5280 4.1.2.5, an undefined expiration (not-after) field should be set to
+ // GeneralizedTime 999912312359559, which is 253402300799000 ms from Jan 1, 1970.
+ constexpr uint64_t kUndefinedExpirationDateTime = 253402300799000;
+ Authorization(TAG_CERTIFICATE_NOT_BEFORE, 0);
+ return Authorization(TAG_CERTIFICATE_NOT_AFTER, kUndefinedExpirationDateTime);
+}
+
} // namespace aidl::android::hardware::security::keymint
diff --git a/security/keymint/support/cppcose.cpp b/security/keymint/support/cppcose.cpp
new file mode 100644
index 0000000..c626ade
--- /dev/null
+++ b/security/keymint/support/cppcose.cpp
@@ -0,0 +1,467 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cppcose/cppcose.h>
+
+#include <stdio.h>
+#include <iostream>
+
+#include <cppbor.h>
+#include <cppbor_parse.h>
+
+#include <openssl/err.h>
+
+namespace cppcose {
+
+namespace {
+
+ErrMsgOr<bssl::UniquePtr<EVP_CIPHER_CTX>> aesGcmInitAndProcessAad(const bytevec& key,
+ const bytevec& nonce,
+ const bytevec& aad,
+ bool encrypt) {
+ if (key.size() != kAesGcmKeySize) return "Invalid key size";
+
+ bssl::UniquePtr<EVP_CIPHER_CTX> ctx(EVP_CIPHER_CTX_new());
+ if (!ctx) return "Failed to allocate cipher context";
+
+ if (!EVP_CipherInit_ex(ctx.get(), EVP_aes_256_gcm(), nullptr /* engine */, key.data(),
+ nonce.data(), encrypt ? 1 : 0)) {
+ return "Failed to initialize cipher";
+ }
+
+ int outlen;
+ if (!aad.empty() && !EVP_CipherUpdate(ctx.get(), nullptr /* out; null means AAD */, &outlen,
+ aad.data(), aad.size())) {
+ return "Failed to process AAD";
+ }
+
+ return std::move(ctx);
+}
+
+} // namespace
+
+ErrMsgOr<bytevec> generateCoseMac0Mac(const bytevec& macKey, const bytevec& externalAad,
+ const bytevec& payload) {
+ auto macStructure = cppbor::Array()
+ .add("MAC0")
+ .add(cppbor::Map().add(ALGORITHM, HMAC_256).canonicalize().encode())
+ .add(externalAad)
+ .add(payload)
+ .encode();
+
+ bytevec macTag(SHA256_DIGEST_LENGTH);
+ uint8_t* out = macTag.data();
+ unsigned int outLen;
+ out = HMAC(EVP_sha256(), //
+ macKey.data(), macKey.size(), //
+ macStructure.data(), macStructure.size(), //
+ out, &outLen);
+
+ assert(out != nullptr && outLen == macTag.size());
+ if (out == nullptr || outLen != macTag.size()) {
+ return "Error computing public key MAC";
+ }
+
+ return macTag;
+}
+
+ErrMsgOr<cppbor::Array> constructCoseMac0(const bytevec& macKey, const bytevec& externalAad,
+ const bytevec& payload) {
+ auto tag = generateCoseMac0Mac(macKey, externalAad, payload);
+ if (!tag) return tag.moveMessage();
+
+ return cppbor::Array()
+ .add(cppbor::Map().add(ALGORITHM, HMAC_256).canonicalize().encode())
+ .add(cppbor::Bstr() /* unprotected */)
+ .add(payload)
+ .add(tag.moveValue());
+}
+
+ErrMsgOr<bytevec /* payload */> parseCoseMac0(const cppbor::Item* macItem) {
+ auto mac = macItem ? macItem->asArray() : nullptr;
+ if (!mac || mac->size() != kCoseMac0EntryCount) {
+ return "Invalid COSE_Mac0";
+ }
+
+ auto protectedParms = mac->get(kCoseMac0ProtectedParams)->asBstr();
+ auto unprotectedParms = mac->get(kCoseMac0UnprotectedParams)->asBstr();
+ auto payload = mac->get(kCoseMac0Payload)->asBstr();
+ auto tag = mac->get(kCoseMac0Tag)->asBstr();
+ if (!protectedParms || !unprotectedParms || !payload || !tag) {
+ return "Invalid COSE_Mac0 contents";
+ }
+
+ return payload->value();
+}
+
+ErrMsgOr<bytevec /* payload */> verifyAndParseCoseMac0(const cppbor::Item* macItem,
+ const bytevec& macKey) {
+ auto mac = macItem ? macItem->asArray() : nullptr;
+ if (!mac || mac->size() != kCoseMac0EntryCount) {
+ return "Invalid COSE_Mac0";
+ }
+
+ auto protectedParms = mac->get(kCoseMac0ProtectedParams)->asBstr();
+ auto unprotectedParms = mac->get(kCoseMac0UnprotectedParams)->asBstr();
+ auto payload = mac->get(kCoseMac0Payload)->asBstr();
+ auto tag = mac->get(kCoseMac0Tag)->asBstr();
+ if (!protectedParms || !unprotectedParms || !payload || !tag) {
+ return "Invalid COSE_Mac0 contents";
+ }
+
+ auto [protectedMap, _, errMsg] = cppbor::parse(protectedParms);
+ if (!protectedMap || !protectedMap->asMap()) {
+ return "Invalid Mac0 protected: " + errMsg;
+ }
+ auto& algo = protectedMap->asMap()->get(ALGORITHM);
+ if (!algo || !algo->asInt() || algo->asInt()->value() != HMAC_256) {
+ return "Unsupported Mac0 algorithm";
+ }
+
+ auto macTag = generateCoseMac0Mac(macKey, {} /* external_aad */, payload->value());
+ if (!macTag) return macTag.moveMessage();
+
+ if (macTag->size() != tag->value().size() ||
+ CRYPTO_memcmp(macTag->data(), tag->value().data(), macTag->size()) != 0) {
+ return "MAC tag mismatch";
+ }
+
+ return payload->value();
+}
+
+ErrMsgOr<bytevec> createCoseSign1Signature(const bytevec& key, const bytevec& protectedParams,
+ const bytevec& payload, const bytevec& aad) {
+ bytevec signatureInput = cppbor::Array()
+ .add("Signature1") //
+ .add(protectedParams)
+ .add(aad)
+ .add(payload)
+ .encode();
+
+ if (key.size() != ED25519_PRIVATE_KEY_LEN) return "Invalid signing key";
+ bytevec signature(ED25519_SIGNATURE_LEN);
+ if (!ED25519_sign(signature.data(), signatureInput.data(), signatureInput.size(), key.data())) {
+ return "Signing failed";
+ }
+
+ return signature;
+}
+
+ErrMsgOr<cppbor::Array> constructCoseSign1(const bytevec& key, cppbor::Map protectedParams,
+ const bytevec& payload, const bytevec& aad) {
+ bytevec protParms = protectedParams.add(ALGORITHM, EDDSA).canonicalize().encode();
+ auto signature = createCoseSign1Signature(key, protParms, payload, aad);
+ if (!signature) return signature.moveMessage();
+
+ return cppbor::Array()
+ .add(protParms)
+ .add(bytevec{} /* unprotected parameters */)
+ .add(payload)
+ .add(*signature);
+}
+
+ErrMsgOr<cppbor::Array> constructCoseSign1(const bytevec& key, const bytevec& payload,
+ const bytevec& aad) {
+ return constructCoseSign1(key, {} /* protectedParams */, payload, aad);
+}
+
+ErrMsgOr<bytevec> verifyAndParseCoseSign1(bool ignoreSignature, const cppbor::Array* coseSign1,
+ const bytevec& signingCoseKey, const bytevec& aad) {
+ if (!coseSign1 || coseSign1->size() != kCoseSign1EntryCount) {
+ return "Invalid COSE_Sign1";
+ }
+
+ const cppbor::Bstr* protectedParams = coseSign1->get(kCoseSign1ProtectedParams)->asBstr();
+ const cppbor::Bstr* unprotectedParams = coseSign1->get(kCoseSign1UnprotectedParams)->asBstr();
+ const cppbor::Bstr* payload = coseSign1->get(kCoseSign1Payload)->asBstr();
+ const cppbor::Bstr* signature = coseSign1->get(kCoseSign1Signature)->asBstr();
+
+ if (!protectedParams || !unprotectedParams || !payload || !signature) {
+ return "Invalid COSE_Sign1";
+ }
+
+ auto [parsedProtParams, _, errMsg] = cppbor::parse(protectedParams);
+ if (!parsedProtParams) {
+ return errMsg + " when parsing protected params.";
+ }
+ if (!parsedProtParams->asMap()) {
+ return "Protected params must be a map";
+ }
+
+ auto& algorithm = parsedProtParams->asMap()->get(ALGORITHM);
+ if (!algorithm || !algorithm->asInt() || algorithm->asInt()->value() != EDDSA) {
+ return "Unsupported signature algorithm";
+ }
+
+ if (!ignoreSignature) {
+ bool selfSigned = signingCoseKey.empty();
+ auto key = CoseKey::parseEd25519(selfSigned ? payload->value() : signingCoseKey);
+ if (!key) return "Bad signing key: " + key.moveMessage();
+
+ bytevec signatureInput = cppbor::Array()
+ .add("Signature1")
+ .add(*protectedParams)
+ .add(aad)
+ .add(*payload)
+ .encode();
+
+ if (!ED25519_verify(signatureInput.data(), signatureInput.size(), signature->value().data(),
+ key->getBstrValue(CoseKey::PUBKEY_X)->data())) {
+ return "Signature verification failed";
+ }
+ }
+
+ return payload->value();
+}
+
+ErrMsgOr<bytevec> createCoseEncryptCiphertext(const bytevec& key, const bytevec& nonce,
+ const bytevec& protectedParams,
+ const bytevec& plaintextPayload, const bytevec& aad) {
+ auto ciphertext = aesGcmEncrypt(key, nonce,
+ cppbor::Array() // Enc strucure as AAD
+ .add("Encrypt") // Context
+ .add(protectedParams) // Protected
+ .add(aad) // External AAD
+ .encode(),
+ plaintextPayload);
+
+ if (!ciphertext) return ciphertext.moveMessage();
+ return ciphertext.moveValue();
+}
+
+ErrMsgOr<cppbor::Array> constructCoseEncrypt(const bytevec& key, const bytevec& nonce,
+ const bytevec& plaintextPayload, const bytevec& aad,
+ cppbor::Array recipients) {
+ auto encryptProtectedHeader = cppbor::Map() //
+ .add(ALGORITHM, AES_GCM_256)
+ .canonicalize()
+ .encode();
+
+ auto ciphertext =
+ createCoseEncryptCiphertext(key, nonce, encryptProtectedHeader, plaintextPayload, aad);
+ if (!ciphertext) return ciphertext.moveMessage();
+
+ return cppbor::Array()
+ .add(encryptProtectedHeader) // Protected
+ .add(cppbor::Map().add(IV, nonce).canonicalize()) // Unprotected
+ .add(*ciphertext) // Payload
+ .add(std::move(recipients));
+}
+
+ErrMsgOr<std::pair<bytevec /* pubkey */, bytevec /* key ID */>> getSenderPubKeyFromCoseEncrypt(
+ const cppbor::Item* coseEncrypt) {
+ if (!coseEncrypt || !coseEncrypt->asArray() ||
+ coseEncrypt->asArray()->size() != kCoseEncryptEntryCount) {
+ return "Invalid COSE_Encrypt";
+ }
+
+ auto& recipients = coseEncrypt->asArray()->get(kCoseEncryptRecipients);
+ if (!recipients || !recipients->asArray() || recipients->asArray()->size() != 1) {
+ return "Invalid recipients list";
+ }
+
+ auto& recipient = recipients->asArray()->get(0);
+ if (!recipient || !recipient->asArray() || recipient->asArray()->size() != 3) {
+ return "Invalid COSE_recipient";
+ }
+
+ auto& ciphertext = recipient->asArray()->get(2);
+ if (!ciphertext->asSimple() || !ciphertext->asSimple()->asNull()) {
+ return "Unexpected value in recipients ciphertext field " +
+ cppbor::prettyPrint(ciphertext.get());
+ }
+
+ auto& protParms = recipient->asArray()->get(0);
+ if (!protParms || !protParms->asBstr()) return "Invalid protected params";
+ auto [parsedProtParms, _, errMsg] = cppbor::parse(protParms->asBstr());
+ if (!parsedProtParms) return "Failed to parse protected params: " + errMsg;
+ if (!parsedProtParms->asMap()) return "Invalid protected params";
+
+ auto& algorithm = parsedProtParms->asMap()->get(ALGORITHM);
+ if (!algorithm || !algorithm->asInt() || algorithm->asInt()->value() != ECDH_ES_HKDF_256) {
+ return "Invalid algorithm";
+ }
+
+ auto& unprotParms = recipient->asArray()->get(1);
+ if (!unprotParms || !unprotParms->asMap()) return "Invalid unprotected params";
+
+ auto& senderCoseKey = unprotParms->asMap()->get(COSE_KEY);
+ if (!senderCoseKey || !senderCoseKey->asMap()) return "Invalid sender COSE_Key";
+
+ auto& keyType = senderCoseKey->asMap()->get(CoseKey::KEY_TYPE);
+ if (!keyType || !keyType->asInt() || keyType->asInt()->value() != OCTET_KEY_PAIR) {
+ return "Invalid key type";
+ }
+
+ auto& curve = senderCoseKey->asMap()->get(CoseKey::CURVE);
+ if (!curve || !curve->asInt() || curve->asInt()->value() != X25519) {
+ return "Unsupported curve";
+ }
+
+ auto& pubkey = senderCoseKey->asMap()->get(CoseKey::PUBKEY_X);
+ if (!pubkey || !pubkey->asBstr() ||
+ pubkey->asBstr()->value().size() != X25519_PUBLIC_VALUE_LEN) {
+ return "Invalid X25519 public key";
+ }
+
+ auto& key_id = unprotParms->asMap()->get(KEY_ID);
+ if (key_id && key_id->asBstr()) {
+ return std::make_pair(pubkey->asBstr()->value(), key_id->asBstr()->value());
+ }
+
+ // If no key ID, just return an empty vector.
+ return std::make_pair(pubkey->asBstr()->value(), bytevec{});
+}
+
+ErrMsgOr<bytevec> decryptCoseEncrypt(const bytevec& key, const cppbor::Item* coseEncrypt,
+ const bytevec& external_aad) {
+ if (!coseEncrypt || !coseEncrypt->asArray() ||
+ coseEncrypt->asArray()->size() != kCoseEncryptEntryCount) {
+ return "Invalid COSE_Encrypt";
+ }
+
+ auto& protParms = coseEncrypt->asArray()->get(kCoseEncryptProtectedParams);
+ auto& unprotParms = coseEncrypt->asArray()->get(kCoseEncryptUnprotectedParams);
+ auto& ciphertext = coseEncrypt->asArray()->get(kCoseEncryptPayload);
+ auto& recipients = coseEncrypt->asArray()->get(kCoseEncryptRecipients);
+
+ if (!protParms || !protParms->asBstr() || !unprotParms || !ciphertext || !recipients) {
+ return "Invalid COSE_Encrypt";
+ }
+
+ auto [parsedProtParams, _, errMsg] = cppbor::parse(protParms->asBstr()->value());
+ if (!parsedProtParams) {
+ return errMsg + " when parsing protected params.";
+ }
+ if (!parsedProtParams->asMap()) {
+ return "Protected params must be a map";
+ }
+
+ auto& algorithm = parsedProtParams->asMap()->get(ALGORITHM);
+ if (!algorithm || !algorithm->asInt() || algorithm->asInt()->value() != AES_GCM_256) {
+ return "Unsupported encryption algorithm";
+ }
+
+ if (!unprotParms->asMap() || unprotParms->asMap()->size() != 1) {
+ return "Invalid unprotected params";
+ }
+
+ auto& nonce = unprotParms->asMap()->get(IV);
+ if (!nonce || !nonce->asBstr() || nonce->asBstr()->value().size() != kAesGcmNonceLength) {
+ return "Invalid nonce";
+ }
+
+ if (!ciphertext->asBstr()) return "Invalid ciphertext";
+
+ auto aad = cppbor::Array() // Enc strucure as AAD
+ .add("Encrypt") // Context
+ .add(protParms->asBstr()->value()) // Protected
+ .add(external_aad) // External AAD
+ .encode();
+
+ return aesGcmDecrypt(key, nonce->asBstr()->value(), aad, ciphertext->asBstr()->value());
+}
+
+ErrMsgOr<bytevec> x25519_HKDF_DeriveKey(const bytevec& pubKeyA, const bytevec& privKeyA,
+ const bytevec& pubKeyB, bool senderIsA) {
+ bytevec rawSharedKey(X25519_SHARED_KEY_LEN);
+ if (!::X25519(rawSharedKey.data(), privKeyA.data(), pubKeyB.data())) {
+ return "ECDH operation failed";
+ }
+
+ bytevec kdfContext = cppbor::Array()
+ .add(AES_GCM_256)
+ .add(cppbor::Array() // Sender Info
+ .add(cppbor::Bstr("client"))
+ .add(bytevec{} /* nonce */)
+ .add(senderIsA ? pubKeyA : pubKeyB))
+ .add(cppbor::Array() // Recipient Info
+ .add(cppbor::Bstr("server"))
+ .add(bytevec{} /* nonce */)
+ .add(senderIsA ? pubKeyB : pubKeyA))
+ .add(cppbor::Array() // SuppPubInfo
+ .add(128) // output key length
+ .add(bytevec{})) // protected
+ .encode();
+
+ bytevec retval(SHA256_DIGEST_LENGTH);
+ bytevec salt{};
+ if (!HKDF(retval.data(), retval.size(), //
+ EVP_sha256(), //
+ rawSharedKey.data(), rawSharedKey.size(), //
+ salt.data(), salt.size(), //
+ kdfContext.data(), kdfContext.size())) {
+ return "ECDH HKDF failed";
+ }
+
+ return retval;
+}
+
+ErrMsgOr<bytevec> aesGcmEncrypt(const bytevec& key, const bytevec& nonce, const bytevec& aad,
+ const bytevec& plaintext) {
+ auto ctx = aesGcmInitAndProcessAad(key, nonce, aad, true /* encrypt */);
+ if (!ctx) return ctx.moveMessage();
+
+ bytevec ciphertext(plaintext.size() + kAesGcmTagSize);
+ int outlen;
+ if (!EVP_CipherUpdate(ctx->get(), ciphertext.data(), &outlen, plaintext.data(),
+ plaintext.size())) {
+ return "Failed to encrypt plaintext";
+ }
+ assert(plaintext.size() == outlen);
+
+ if (!EVP_CipherFinal_ex(ctx->get(), ciphertext.data() + outlen, &outlen)) {
+ return "Failed to finalize encryption";
+ }
+ assert(outlen == 0);
+
+ if (!EVP_CIPHER_CTX_ctrl(ctx->get(), EVP_CTRL_GCM_GET_TAG, kAesGcmTagSize,
+ ciphertext.data() + plaintext.size())) {
+ return "Failed to retrieve tag";
+ }
+
+ return ciphertext;
+}
+
+ErrMsgOr<bytevec> aesGcmDecrypt(const bytevec& key, const bytevec& nonce, const bytevec& aad,
+ const bytevec& ciphertextWithTag) {
+ auto ctx = aesGcmInitAndProcessAad(key, nonce, aad, false /* encrypt */);
+ if (!ctx) return ctx.moveMessage();
+
+ if (ciphertextWithTag.size() < kAesGcmTagSize) return "Missing tag";
+
+ bytevec plaintext(ciphertextWithTag.size() - kAesGcmTagSize);
+ int outlen;
+ if (!EVP_CipherUpdate(ctx->get(), plaintext.data(), &outlen, ciphertextWithTag.data(),
+ ciphertextWithTag.size() - kAesGcmTagSize)) {
+ return "Failed to decrypt plaintext";
+ }
+ assert(plaintext.size() == outlen);
+
+ bytevec tag(ciphertextWithTag.end() - kAesGcmTagSize, ciphertextWithTag.end());
+ if (!EVP_CIPHER_CTX_ctrl(ctx->get(), EVP_CTRL_GCM_SET_TAG, kAesGcmTagSize, tag.data())) {
+ return "Failed to set tag: " + std::to_string(ERR_peek_last_error());
+ }
+
+ if (!EVP_CipherFinal_ex(ctx->get(), nullptr, &outlen)) {
+ return "Failed to finalize encryption";
+ }
+ assert(outlen == 0);
+
+ return plaintext;
+}
+
+} // namespace cppcose
diff --git a/security/keymint/support/include/cppcose/cppcose.h b/security/keymint/support/include/cppcose/cppcose.h
new file mode 100644
index 0000000..a936bfd
--- /dev/null
+++ b/security/keymint/support/include/cppcose/cppcose.h
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include <cppbor.h>
+#include <cppbor_parse.h>
+
+#include <openssl/cipher.h>
+#include <openssl/curve25519.h>
+#include <openssl/digest.h>
+#include <openssl/hkdf.h>
+#include <openssl/hmac.h>
+#include <openssl/mem.h>
+#include <openssl/sha.h>
+
+namespace cppcose {
+
+using bytevec = std::vector<uint8_t>;
+
+constexpr int kCoseSign1EntryCount = 4;
+constexpr int kCoseSign1ProtectedParams = 0;
+constexpr int kCoseSign1UnprotectedParams = 1;
+constexpr int kCoseSign1Payload = 2;
+constexpr int kCoseSign1Signature = 3;
+
+constexpr int kCoseMac0EntryCount = 4;
+constexpr int kCoseMac0ProtectedParams = 0;
+constexpr int kCoseMac0UnprotectedParams = 1;
+constexpr int kCoseMac0Payload = 2;
+constexpr int kCoseMac0Tag = 3;
+
+constexpr int kCoseEncryptEntryCount = 4;
+constexpr int kCoseEncryptProtectedParams = 0;
+constexpr int kCoseEncryptUnprotectedParams = 1;
+constexpr int kCoseEncryptPayload = 2;
+constexpr int kCoseEncryptRecipients = 3;
+
+enum Label : int {
+ ALGORITHM = 1,
+ KEY_ID = 4,
+ IV = 5,
+ COSE_KEY = -1,
+};
+
+enum CoseKeyAlgorithm : int {
+ AES_GCM_256 = 3,
+ HMAC_256 = 5,
+ ES256 = -7, // ECDSA with SHA-256
+ EDDSA = -8,
+ ECDH_ES_HKDF_256 = -25,
+};
+
+enum CoseKeyCurve : int { P256 = 1, X25519 = 4, ED25519 = 6 };
+enum CoseKeyType : int { OCTET_KEY_PAIR = 1, EC2 = 2, SYMMETRIC_KEY = 4 };
+enum CoseKeyOps : int { SIGN = 1, VERIFY = 2, ENCRYPT = 3, DECRYPT = 4 };
+
+constexpr int kAesGcmNonceLength = 12;
+constexpr int kAesGcmTagSize = 16;
+constexpr int kAesGcmKeySize = 32;
+
+template <typename T>
+class ErrMsgOr {
+ public:
+ ErrMsgOr(std::string errMsg) : errMsg_(std::move(errMsg)) {}
+ ErrMsgOr(const char* errMsg) : errMsg_(errMsg) {}
+ ErrMsgOr(T val) : value_(std::move(val)) {}
+
+ operator bool() const { return value_.has_value(); }
+
+ T* operator->() & {
+ assert(value_);
+ return &value_.value();
+ }
+ T& operator*() & {
+ assert(value_);
+ return value_.value();
+ };
+ T&& operator*() && {
+ assert(value_);
+ return std::move(value_).value();
+ };
+
+ const std::string& message() { return errMsg_; }
+ std::string moveMessage() { return std::move(errMsg_); }
+
+ T moveValue() {
+ assert(value_);
+ return std::move(value_).value();
+ }
+
+ private:
+ std::string errMsg_;
+ std::optional<T> value_;
+};
+
+class CoseKey {
+ public:
+ CoseKey() {}
+ CoseKey(const CoseKey&) = delete;
+ CoseKey(CoseKey&&) = default;
+
+ enum Label : int {
+ KEY_TYPE = 1,
+ KEY_ID = 2,
+ ALGORITHM = 3,
+ KEY_OPS = 4,
+ CURVE = -1,
+ PUBKEY_X = -2,
+ PUBKEY_Y = -3,
+ PRIVATE_KEY = -4,
+ TEST_KEY = -70000 // Application-defined
+ };
+
+ static ErrMsgOr<CoseKey> parse(const bytevec& coseKey) {
+ auto [parsedKey, _, errMsg] = cppbor::parse(coseKey);
+ if (!parsedKey) return errMsg + " when parsing key";
+ if (!parsedKey->asMap()) return "CoseKey must be a map";
+ return CoseKey(static_cast<cppbor::Map*>(parsedKey.release()));
+ }
+
+ static ErrMsgOr<CoseKey> parse(const bytevec& coseKey, CoseKeyType expectedKeyType,
+ CoseKeyAlgorithm expectedAlgorithm, CoseKeyCurve expectedCurve) {
+ auto key = parse(coseKey);
+ if (!key) return key;
+
+ if (!key->checkIntValue(CoseKey::KEY_TYPE, expectedKeyType) ||
+ !key->checkIntValue(CoseKey::ALGORITHM, expectedAlgorithm) ||
+ !key->checkIntValue(CoseKey::CURVE, expectedCurve)) {
+ return "Unexpected key type:";
+ }
+
+ return key;
+ }
+
+ static ErrMsgOr<CoseKey> parseEd25519(const bytevec& coseKey) {
+ auto key = parse(coseKey, OCTET_KEY_PAIR, EDDSA, ED25519);
+ if (!key) return key;
+
+ auto& pubkey = key->getMap().get(PUBKEY_X);
+ if (!pubkey || !pubkey->asBstr() ||
+ pubkey->asBstr()->value().size() != ED25519_PUBLIC_KEY_LEN) {
+ return "Invalid Ed25519 public key";
+ }
+
+ return key;
+ }
+
+ static ErrMsgOr<CoseKey> parseX25519(const bytevec& coseKey, bool requireKid) {
+ auto key = parse(coseKey, OCTET_KEY_PAIR, ECDH_ES_HKDF_256, X25519);
+ if (!key) return key;
+
+ auto& pubkey = key->getMap().get(PUBKEY_X);
+ if (!pubkey || !pubkey->asBstr() ||
+ pubkey->asBstr()->value().size() != X25519_PUBLIC_VALUE_LEN) {
+ return "Invalid X25519 public key";
+ }
+
+ auto& kid = key->getMap().get(KEY_ID);
+ if (requireKid && (!kid || !kid->asBstr())) {
+ return "Missing KID";
+ }
+
+ return key;
+ }
+
+ static ErrMsgOr<CoseKey> parseP256(const bytevec& coseKey) {
+ auto key = parse(coseKey, EC2, ES256, P256);
+ if (!key) return key;
+
+ auto& pubkey_x = key->getMap().get(PUBKEY_X);
+ auto& pubkey_y = key->getMap().get(PUBKEY_Y);
+ if (!pubkey_x || !pubkey_y || !pubkey_x->asBstr() || !pubkey_y->asBstr() ||
+ pubkey_x->asBstr()->value().size() != 32 || pubkey_y->asBstr()->value().size() != 32) {
+ return "Invalid P256 public key";
+ }
+
+ return key;
+ }
+
+ std::optional<int> getIntValue(Label label) {
+ const auto& value = key_->get(label);
+ if (!value || !value->asInt()) return {};
+ return value->asInt()->value();
+ }
+
+ std::optional<bytevec> getBstrValue(Label label) {
+ const auto& value = key_->get(label);
+ if (!value || !value->asBstr()) return {};
+ return value->asBstr()->value();
+ }
+
+ const cppbor::Map& getMap() const { return *key_; }
+ cppbor::Map&& moveMap() { return std::move(*key_); }
+
+ bool checkIntValue(Label label, int expectedValue) {
+ const auto& value = key_->get(label);
+ return value && value->asInt() && value->asInt()->value() == expectedValue;
+ }
+
+ void add(Label label, int value) { key_->add(label, value); }
+ void add(Label label, bytevec value) { key_->add(label, std::move(value)); }
+
+ bytevec encode() { return key_->canonicalize().encode(); }
+
+ private:
+ CoseKey(cppbor::Map* parsedKey) : key_(parsedKey) {}
+
+ // This is the full parsed key structure.
+ std::unique_ptr<cppbor::Map> key_;
+};
+
+ErrMsgOr<bytevec> generateCoseMac0Mac(const bytevec& macKey, const bytevec& externalAad,
+ const bytevec& payload);
+ErrMsgOr<cppbor::Array> constructCoseMac0(const bytevec& macKey, const bytevec& externalAad,
+ const bytevec& payload);
+ErrMsgOr<bytevec /* payload */> parseCoseMac0(const cppbor::Item* macItem);
+ErrMsgOr<bytevec /* payload */> verifyAndParseCoseMac0(const cppbor::Item* macItem,
+ const bytevec& macKey);
+
+ErrMsgOr<bytevec> createCoseSign1Signature(const bytevec& key, const bytevec& protectedParams,
+ const bytevec& payload, const bytevec& aad);
+ErrMsgOr<cppbor::Array> constructCoseSign1(const bytevec& key, const bytevec& payload,
+ const bytevec& aad);
+ErrMsgOr<cppbor::Array> constructCoseSign1(const bytevec& key, cppbor::Map extraProtectedFields,
+ const bytevec& payload, const bytevec& aad);
+/**
+ * Verify and parse a COSE_Sign1 message, returning the payload.
+ *
+ * @param ignoreSignature indicates whether signature verification should be skipped. If true, no
+ * verification of the signature will be done.
+ *
+ * @param coseSign1 is the COSE_Sign1 to verify and parse.
+ *
+ * @param signingCoseKey is a CBOR-encoded COSE_Key to use to verify the signature. The bytevec may
+ * be empty, in which case the function assumes that coseSign1's payload is the COSE_Key to
+ * use, i.e. that coseSign1 is a self-signed "certificate".
+ */
+ErrMsgOr<bytevec /* payload */> verifyAndParseCoseSign1(bool ignoreSignature,
+ const cppbor::Array* coseSign1,
+ const bytevec& signingCoseKey,
+ const bytevec& aad);
+
+ErrMsgOr<bytevec> createCoseEncryptCiphertext(const bytevec& key, const bytevec& nonce,
+ const bytevec& protectedParams, const bytevec& aad);
+ErrMsgOr<cppbor::Array> constructCoseEncrypt(const bytevec& key, const bytevec& nonce,
+ const bytevec& plaintextPayload, const bytevec& aad,
+ cppbor::Array recipients);
+ErrMsgOr<std::pair<bytevec /* pubkey */, bytevec /* key ID */>> getSenderPubKeyFromCoseEncrypt(
+ const cppbor::Item* encryptItem);
+inline ErrMsgOr<std::pair<bytevec /* pubkey */, bytevec /* key ID */>>
+getSenderPubKeyFromCoseEncrypt(const std::unique_ptr<cppbor::Item>& encryptItem) {
+ return getSenderPubKeyFromCoseEncrypt(encryptItem.get());
+}
+
+ErrMsgOr<bytevec /* plaintextPayload */> decryptCoseEncrypt(const bytevec& key,
+ const cppbor::Item* encryptItem,
+ const bytevec& aad);
+
+ErrMsgOr<bytevec> x25519_HKDF_DeriveKey(const bytevec& senderPubKey, const bytevec& senderPrivKey,
+ const bytevec& recipientPubKey, bool senderIsA);
+
+ErrMsgOr<bytevec /* ciphertextWithTag */> aesGcmEncrypt(const bytevec& key, const bytevec& nonce,
+ const bytevec& aad,
+ const bytevec& plaintext);
+ErrMsgOr<bytevec /* plaintext */> aesGcmDecrypt(const bytevec& key, const bytevec& nonce,
+ const bytevec& aad,
+ const bytevec& ciphertextWithTag);
+
+} // namespace cppcose
diff --git a/security/keymint/support/include/keymint_support/authorization_set.h b/security/keymint/support/include/keymint_support/authorization_set.h
index 4ff1705..ca51b08 100644
--- a/security/keymint/support/include/keymint_support/authorization_set.h
+++ b/security/keymint/support/include/keymint_support/authorization_set.h
@@ -259,6 +259,12 @@
size - 1); // drop the terminating '\0'
}
+ template <Tag tag>
+ AuthorizationSetBuilder& Authorization(TypedTag<TagType::BYTES, tag> ttag,
+ const std::string& data) {
+ return Authorization(ttag, reinterpret_cast<const uint8_t*>(data.data()), data.size());
+ }
+
AuthorizationSetBuilder& Authorizations(const AuthorizationSet& set) {
for (const auto& entry : set) {
push_back(entry);
@@ -282,6 +288,7 @@
AuthorizationSetBuilder& SigningKey();
AuthorizationSetBuilder& EncryptionKey();
+ AuthorizationSetBuilder& AttestKey();
AuthorizationSetBuilder& NoDigestOrPadding();
@@ -290,9 +297,26 @@
AuthorizationSetBuilder& GcmModeMacLen(uint32_t macLength);
AuthorizationSetBuilder& BlockMode(std::initializer_list<BlockMode> blockModes);
+ AuthorizationSetBuilder& OaepMGFDigest(const std::vector<Digest>& digests);
AuthorizationSetBuilder& Digest(std::vector<Digest> digests);
AuthorizationSetBuilder& Padding(std::initializer_list<PaddingMode> paddings);
+ AuthorizationSetBuilder& SetDefaultValidity();
+
+ AuthorizationSetBuilder& AttestationChallenge(const std::string& challenge) {
+ return Authorization(TAG_ATTESTATION_CHALLENGE, challenge);
+ }
+ AuthorizationSetBuilder& AttestationChallenge(std::vector<uint8_t> challenge) {
+ return Authorization(TAG_ATTESTATION_CHALLENGE, challenge);
+ }
+
+ AuthorizationSetBuilder& AttestationApplicationId(const std::string& id) {
+ return Authorization(TAG_ATTESTATION_APPLICATION_ID, id);
+ }
+ AuthorizationSetBuilder& AttestationApplicationId(std::vector<uint8_t> id) {
+ return Authorization(TAG_ATTESTATION_APPLICATION_ID, id);
+ }
+
template <typename... T>
AuthorizationSetBuilder& BlockMode(T&&... a) {
return BlockMode({std::forward<T>(a)...});
diff --git a/security/keymint/support/include/keymint_support/key_param_output.h b/security/keymint/support/include/keymint_support/key_param_output.h
index 5f004fe..c2b0029 100644
--- a/security/keymint/support/include/keymint_support/key_param_output.h
+++ b/security/keymint/support/include/keymint_support/key_param_output.h
@@ -84,8 +84,10 @@
::std::ostream& operator<<(::std::ostream& os, const KeyParameter& param);
inline ::std::ostream& operator<<(::std::ostream& os, const KeyCharacteristics& value) {
- return os << "SW: " << value.softwareEnforced << ::std::endl
- << "HW: " << value.hardwareEnforced << ::std::endl;
+ for (auto& entry : value.authorizations) {
+ os << value.securityLevel << ": " << entry;
+ }
+ return os;
}
inline ::std::ostream& operator<<(::std::ostream& os, KeyPurpose value) {
diff --git a/security/keymint/support/include/keymint_support/keymint_tags.h b/security/keymint/support/include/keymint_support/keymint_tags.h
index d932b40..479a11d 100644
--- a/security/keymint/support/include/keymint_support/keymint_tags.h
+++ b/security/keymint/support/include/keymint_support/keymint_tags.h
@@ -119,11 +119,17 @@
DECLARE_TYPED_TAG(TRUSTED_USER_PRESENCE_REQUIRED);
DECLARE_TYPED_TAG(UNIQUE_ID);
DECLARE_TYPED_TAG(UNLOCKED_DEVICE_REQUIRED);
+DECLARE_TYPED_TAG(USAGE_COUNT_LIMIT);
DECLARE_TYPED_TAG(USAGE_EXPIRE_DATETIME);
DECLARE_TYPED_TAG(USER_AUTH_TYPE);
DECLARE_TYPED_TAG(USER_ID);
DECLARE_TYPED_TAG(USER_SECURE_ID);
DECLARE_TYPED_TAG(VENDOR_PATCHLEVEL);
+DECLARE_TYPED_TAG(RSA_OAEP_MGF_DIGEST);
+DECLARE_TYPED_TAG(CERTIFICATE_SERIAL);
+DECLARE_TYPED_TAG(CERTIFICATE_SUBJECT);
+DECLARE_TYPED_TAG(CERTIFICATE_NOT_BEFORE);
+DECLARE_TYPED_TAG(CERTIFICATE_NOT_AFTER);
#undef DECLARE_TYPED_TAG
@@ -134,15 +140,15 @@
TAG_INVALID_t, TAG_KEY_SIZE_t, TAG_MAC_LENGTH_t, TAG_CALLER_NONCE_t, TAG_MIN_MAC_LENGTH_t,
TAG_RSA_PUBLIC_EXPONENT_t, TAG_INCLUDE_UNIQUE_ID_t, TAG_ACTIVE_DATETIME_t,
TAG_ORIGINATION_EXPIRE_DATETIME_t, TAG_USAGE_EXPIRE_DATETIME_t,
- TAG_MIN_SECONDS_BETWEEN_OPS_t, TAG_MAX_USES_PER_BOOT_t, TAG_USER_ID_t, TAG_USER_SECURE_ID_t,
- TAG_NO_AUTH_REQUIRED_t, TAG_AUTH_TIMEOUT_t, TAG_ALLOW_WHILE_ON_BODY_t,
- TAG_UNLOCKED_DEVICE_REQUIRED_t, TAG_APPLICATION_ID_t, TAG_APPLICATION_DATA_t,
- TAG_CREATION_DATETIME_t, TAG_ROLLBACK_RESISTANCE_t, TAG_HARDWARE_TYPE_t,
- TAG_ROOT_OF_TRUST_t, TAG_ASSOCIATED_DATA_t, TAG_NONCE_t, TAG_BOOTLOADER_ONLY_t,
- TAG_OS_VERSION_t, TAG_OS_PATCHLEVEL_t, TAG_UNIQUE_ID_t, TAG_ATTESTATION_CHALLENGE_t,
- TAG_ATTESTATION_APPLICATION_ID_t, TAG_ATTESTATION_ID_BRAND_t, TAG_ATTESTATION_ID_DEVICE_t,
- TAG_ATTESTATION_ID_PRODUCT_t, TAG_ATTESTATION_ID_MANUFACTURER_t, TAG_ATTESTATION_ID_MODEL_t,
- TAG_ATTESTATION_ID_SERIAL_t, TAG_ATTESTATION_ID_IMEI_t, TAG_ATTESTATION_ID_MEID_t,
+ TAG_MIN_SECONDS_BETWEEN_OPS_t, TAG_MAX_USES_PER_BOOT_t, TAG_USAGE_COUNT_LIMIT_t,
+ TAG_USER_ID_t, TAG_USER_SECURE_ID_t, TAG_NO_AUTH_REQUIRED_t, TAG_AUTH_TIMEOUT_t,
+ TAG_ALLOW_WHILE_ON_BODY_t, TAG_UNLOCKED_DEVICE_REQUIRED_t, TAG_APPLICATION_ID_t,
+ TAG_APPLICATION_DATA_t, TAG_CREATION_DATETIME_t, TAG_ROLLBACK_RESISTANCE_t,
+ TAG_HARDWARE_TYPE_t, TAG_ROOT_OF_TRUST_t, TAG_ASSOCIATED_DATA_t, TAG_NONCE_t,
+ TAG_BOOTLOADER_ONLY_t, TAG_OS_VERSION_t, TAG_OS_PATCHLEVEL_t, TAG_UNIQUE_ID_t,
+ TAG_ATTESTATION_CHALLENGE_t, TAG_ATTESTATION_APPLICATION_ID_t, TAG_ATTESTATION_ID_BRAND_t,
+ TAG_ATTESTATION_ID_DEVICE_t, TAG_ATTESTATION_ID_PRODUCT_t,
+ TAG_ATTESTATION_ID_MANUFACTURER_t, TAG_ATTESTATION_ID_MODEL_t,
TAG_RESET_SINCE_ID_ROTATION_t, TAG_PURPOSE_t, TAG_ALGORITHM_t, TAG_BLOCK_MODE_t,
TAG_DIGEST_t, TAG_PADDING_t, TAG_ORIGIN_t, TAG_USER_AUTH_TYPE_t, TAG_EC_CURVE_t,
TAG_BOOT_PATCHLEVEL_t, TAG_VENDOR_PATCHLEVEL_t, TAG_TRUSTED_CONFIRMATION_REQUIRED_t,
@@ -239,6 +245,7 @@
MAKE_TAG_ENUM_VALUE_ACCESSOR(TAG_PURPOSE, keyPurpose)
MAKE_TAG_ENUM_VALUE_ACCESSOR(TAG_USER_AUTH_TYPE, hardwareAuthenticatorType)
MAKE_TAG_ENUM_VALUE_ACCESSOR(TAG_HARDWARE_TYPE, securityLevel)
+MAKE_TAG_ENUM_VALUE_ACCESSOR(TAG_RSA_OAEP_MGF_DIGEST, digest)
#undef MAKE_TAG_ENUM_VALUE_ACCESSOR
@@ -323,7 +330,9 @@
inline std::optional<
std::reference_wrapper<const typename TypedTag2ValueType<TypedTag<tag_type, tag>>::type>>
authorizationValue(TypedTag<tag_type, tag> ttag, const KeyParameter& param) {
- if (TypedTag2ValueType<TypedTag<tag_type, tag>>::unionTag != param.value.getTag()) return {};
+ // We only check if the parameter has the correct tag here; accessTagValue checks if the correct
+ // union field was initialized.
+ if (tag != param.tag) return {};
return accessTagValue(ttag, param);
}
diff --git a/security/keymint/support/include/keymint_support/openssl_utils.h b/security/keymint/support/include/keymint_support/openssl_utils.h
index 9ae7e52..a0212aa 100644
--- a/security/keymint/support/include/keymint_support/openssl_utils.h
+++ b/security/keymint/support/include/keymint_support/openssl_utils.h
@@ -34,10 +34,14 @@
typedef std::unique_ptr<type, UniquePtrDeleter<type, type##_free>> type##_Ptr;
MAKE_OPENSSL_PTR_TYPE(ASN1_OBJECT)
+MAKE_OPENSSL_PTR_TYPE(BN_CTX)
+MAKE_OPENSSL_PTR_TYPE(EC_GROUP)
+MAKE_OPENSSL_PTR_TYPE(EC_KEY)
MAKE_OPENSSL_PTR_TYPE(EVP_PKEY)
+MAKE_OPENSSL_PTR_TYPE(EVP_PKEY_CTX)
MAKE_OPENSSL_PTR_TYPE(RSA)
MAKE_OPENSSL_PTR_TYPE(X509)
-MAKE_OPENSSL_PTR_TYPE(BN_CTX)
+MAKE_OPENSSL_PTR_TYPE(X509_NAME)
typedef std::unique_ptr<BIGNUM, UniquePtrDeleter<BIGNUM, BN_free>> BIGNUM_Ptr;
diff --git a/security/keymint/support/include/remote_prov/remote_prov_utils.h b/security/keymint/support/include/remote_prov/remote_prov_utils.h
new file mode 100644
index 0000000..5e205a2
--- /dev/null
+++ b/security/keymint/support/include/remote_prov/remote_prov_utils.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2019, 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 <vector>
+
+#include <cppcose/cppcose.h>
+
+namespace aidl::android::hardware::security::keymint::remote_prov {
+
+using bytevec = std::vector<uint8_t>;
+using namespace cppcose;
+
+extern bytevec kTestMacKey;
+
+/**
+ * Generates random bytes.
+ */
+bytevec randomBytes(size_t numBytes);
+
+struct EekChain {
+ bytevec chain;
+ bytevec last_pubkey;
+ bytevec last_privkey;
+};
+
+/**
+ * Generates an X25518 EEK with the specified eekId and an Ed25519 chain of the
+ * specified length. All keys are generated randomly.
+ */
+ErrMsgOr<EekChain> generateEekChain(size_t length, const bytevec& eekId);
+
+struct BccEntryData {
+ bytevec pubKey;
+};
+
+/**
+ * Validates the provided CBOR-encoded BCC, returning a vector of BccEntryData
+ * structs containing the BCC entry contents. If an entry contains no firmware
+ * digest, the corresponding BccEntryData.firmwareDigest will have length zero
+ * (there's no way to distinguish between an empty and missing firmware digest,
+ * which seems fine).
+ */
+ErrMsgOr<std::vector<BccEntryData>> validateBcc(const cppbor::Array* bcc);
+
+} // namespace aidl::android::hardware::security::keymint::remote_prov
diff --git a/security/keymint/support/remote_prov_utils.cpp b/security/keymint/support/remote_prov_utils.cpp
new file mode 100644
index 0000000..111cb30
--- /dev/null
+++ b/security/keymint/support/remote_prov_utils.cpp
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2019, 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 <remote_prov/remote_prov_utils.h>
+
+#include <openssl/rand.h>
+
+#include <cppbor.h>
+
+namespace aidl::android::hardware::security::keymint::remote_prov {
+
+bytevec kTestMacKey(32 /* count */, 0 /* byte value */);
+
+bytevec randomBytes(size_t numBytes) {
+ bytevec retval(numBytes);
+ RAND_bytes(retval.data(), numBytes);
+ return retval;
+}
+
+ErrMsgOr<EekChain> generateEekChain(size_t length, const bytevec& eekId) {
+ auto eekChain = cppbor::Array();
+
+ bytevec prev_priv_key;
+ for (size_t i = 0; i < length - 1; ++i) {
+ bytevec pub_key(ED25519_PUBLIC_KEY_LEN);
+ bytevec priv_key(ED25519_PRIVATE_KEY_LEN);
+
+ ED25519_keypair(pub_key.data(), priv_key.data());
+
+ // The first signing key is self-signed.
+ if (prev_priv_key.empty()) prev_priv_key = priv_key;
+
+ auto coseSign1 = constructCoseSign1(prev_priv_key,
+ cppbor::Map() /* payload CoseKey */
+ .add(CoseKey::KEY_TYPE, OCTET_KEY_PAIR)
+ .add(CoseKey::ALGORITHM, EDDSA)
+ .add(CoseKey::CURVE, ED25519)
+ .add(CoseKey::PUBKEY_X, pub_key)
+ .canonicalize()
+ .encode(),
+ {} /* AAD */);
+ if (!coseSign1) return coseSign1.moveMessage();
+ eekChain.add(coseSign1.moveValue());
+ }
+
+ bytevec pub_key(X25519_PUBLIC_VALUE_LEN);
+ bytevec priv_key(X25519_PRIVATE_KEY_LEN);
+ X25519_keypair(pub_key.data(), priv_key.data());
+
+ auto coseSign1 = constructCoseSign1(prev_priv_key,
+ cppbor::Map() /* payload CoseKey */
+ .add(CoseKey::KEY_TYPE, OCTET_KEY_PAIR)
+ .add(CoseKey::KEY_ID, eekId)
+ .add(CoseKey::ALGORITHM, ECDH_ES_HKDF_256)
+ .add(CoseKey::CURVE, cppcose::X25519)
+ .add(CoseKey::PUBKEY_X, pub_key)
+ .canonicalize()
+ .encode(),
+ {} /* AAD */);
+ if (!coseSign1) return coseSign1.moveMessage();
+ eekChain.add(coseSign1.moveValue());
+
+ return EekChain{eekChain.encode(), pub_key, priv_key};
+}
+
+ErrMsgOr<bytevec> verifyAndParseCoseSign1Cwt(bool ignoreSignature, const cppbor::Array* coseSign1,
+ const bytevec& signingCoseKey, const bytevec& aad) {
+ if (!coseSign1 || coseSign1->size() != kCoseSign1EntryCount) {
+ return "Invalid COSE_Sign1";
+ }
+
+ const cppbor::Bstr* protectedParams = coseSign1->get(kCoseSign1ProtectedParams)->asBstr();
+ const cppbor::Bstr* unprotectedParams = coseSign1->get(kCoseSign1UnprotectedParams)->asBstr();
+ const cppbor::Bstr* payload = coseSign1->get(kCoseSign1Payload)->asBstr();
+ const cppbor::Bstr* signature = coseSign1->get(kCoseSign1Signature)->asBstr();
+
+ if (!protectedParams || !unprotectedParams || !payload || !signature) {
+ return "Invalid COSE_Sign1";
+ }
+
+ auto [parsedProtParams, _, errMsg] = cppbor::parse(protectedParams);
+ if (!parsedProtParams) {
+ return errMsg + " when parsing protected params.";
+ }
+ if (!parsedProtParams->asMap()) {
+ return "Protected params must be a map";
+ }
+
+ auto& algorithm = parsedProtParams->asMap()->get(ALGORITHM);
+ if (!algorithm || !algorithm->asInt() || algorithm->asInt()->value() != EDDSA) {
+ return "Unsupported signature algorithm";
+ }
+
+ // TODO(jbires): Handle CWTs as the CoseSign1 payload in a less hacky way. Since the CWT payload
+ // is extremely remote provisioning specific, probably just make a separate
+ // function there.
+ auto [parsedPayload, __, payloadErrMsg] = cppbor::parse(payload);
+ if (!parsedPayload) return payloadErrMsg + " when parsing key";
+ if (!parsedPayload->asMap()) return "CWT must be a map";
+ auto serializedKey = parsedPayload->asMap()->get(-4670552)->clone();
+ if (!serializedKey || !serializedKey->asBstr()) return "Could not find key entry";
+
+ if (!ignoreSignature) {
+ bool selfSigned = signingCoseKey.empty();
+ auto key = CoseKey::parseEd25519(selfSigned ? serializedKey->asBstr()->value()
+ : signingCoseKey);
+ if (!key) return "Bad signing key: " + key.moveMessage();
+
+ bytevec signatureInput = cppbor::Array()
+ .add("Signature1")
+ .add(*protectedParams)
+ .add(aad)
+ .add(*payload)
+ .encode();
+
+ if (!ED25519_verify(signatureInput.data(), signatureInput.size(), signature->value().data(),
+ key->getBstrValue(CoseKey::PUBKEY_X)->data())) {
+ return "Signature verification failed";
+ }
+ }
+
+ return serializedKey->asBstr()->value();
+}
+ErrMsgOr<std::vector<BccEntryData>> validateBcc(const cppbor::Array* bcc) {
+ if (!bcc || bcc->size() == 0) return "Invalid BCC";
+
+ std::vector<BccEntryData> result;
+
+ bytevec prevKey;
+ // TODO(jbires): Actually process the pubKey at the start of the new bcc entry
+ for (size_t i = 1; i < bcc->size(); ++i) {
+ const cppbor::Array* entry = bcc->get(i)->asArray();
+ if (!entry || entry->size() != kCoseSign1EntryCount) {
+ return "Invalid BCC entry " + std::to_string(i) + ": " + prettyPrint(entry);
+ }
+ auto payload = verifyAndParseCoseSign1Cwt(false /* ignoreSignature */, entry,
+ std::move(prevKey), bytevec{} /* AAD */);
+ if (!payload) {
+ return "Failed to verify entry " + std::to_string(i) + ": " + payload.moveMessage();
+ }
+
+ auto& certProtParms = entry->get(kCoseSign1ProtectedParams);
+ if (!certProtParms || !certProtParms->asBstr()) return "Invalid prot params";
+ auto [parsedProtParms, _, errMsg] = cppbor::parse(certProtParms->asBstr()->value());
+ if (!parsedProtParms || !parsedProtParms->asMap()) return "Invalid prot params";
+
+ result.push_back(BccEntryData{*payload});
+
+ // This entry's public key is the signing key for the next entry.
+ prevKey = payload.moveValue();
+ }
+
+ return result;
+}
+
+} // namespace aidl::android::hardware::security::keymint::remote_prov
diff --git a/security/secureclock/aidl/Android.bp b/security/secureclock/aidl/Android.bp
new file mode 100644
index 0000000..5a6d7ae
--- /dev/null
+++ b/security/secureclock/aidl/Android.bp
@@ -0,0 +1,21 @@
+aidl_interface {
+ name: "android.hardware.security.secureclock",
+ vendor_available: true,
+ srcs: [
+ "android/hardware/security/secureclock/*.aidl",
+ ],
+ stability: "vintf",
+ backend: {
+ java: {
+ sdk_version: "module_current",
+ },
+ ndk: {
+ vndk: {
+ enabled: true,
+ },
+ },
+ rust: {
+ enabled: true,
+ },
+ },
+}
diff --git a/security/secureclock/aidl/OWNERS b/security/secureclock/aidl/OWNERS
new file mode 100644
index 0000000..a93b171
--- /dev/null
+++ b/security/secureclock/aidl/OWNERS
@@ -0,0 +1,4 @@
+jbires@google.com
+jdanis@google.com
+seleneh@google.com
+swillden@google.com
diff --git a/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/ISecureClock.aidl b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/ISecureClock.aidl
new file mode 100644
index 0000000..3778897
--- /dev/null
+++ b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/ISecureClock.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * 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.security.secureclock;
+@VintfStability
+interface ISecureClock {
+ android.hardware.security.secureclock.TimeStampToken generateTimeStamp(in long challenge);
+ const String TIME_STAMP_MAC_LABEL = "Auth Verification";
+}
diff --git a/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/TimeStampToken.aidl b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/TimeStampToken.aidl
new file mode 100644
index 0000000..00a8bb2
--- /dev/null
+++ b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/TimeStampToken.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.security.secureclock;
+@RustDerive(Clone=true, Eq=true, Hash=true, Ord=true, PartialEq=true, PartialOrd=true) @VintfStability
+parcelable TimeStampToken {
+ long challenge;
+ android.hardware.security.secureclock.Timestamp timestamp;
+ byte[] mac;
+}
diff --git a/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/Timestamp.aidl b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/Timestamp.aidl
new file mode 100644
index 0000000..bebeb5c
--- /dev/null
+++ b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/Timestamp.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.security.secureclock;
+@RustDerive(Clone=true, Eq=true, Hash=true, Ord=true, PartialEq=true, PartialOrd=true) @VintfStability
+parcelable Timestamp {
+ long milliSeconds;
+}
diff --git a/security/secureclock/aidl/android/hardware/security/secureclock/ISecureClock.aidl b/security/secureclock/aidl/android/hardware/security/secureclock/ISecureClock.aidl
new file mode 100644
index 0000000..577dd8f
--- /dev/null
+++ b/security/secureclock/aidl/android/hardware/security/secureclock/ISecureClock.aidl
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * limitations under the License.
+ */
+
+package android.hardware.security.secureclock;
+import android.hardware.security.secureclock.TimeStampToken;
+
+/**
+ * Secure Clock definition.
+ *
+ * An ISecureClock provides a keymint service to generate secure timestamp using a secure platform.
+ * The secure time stamp contains time in milliseconds. This time stamp also contains a 256-bit MAC
+ * which provides integrity protection. The MAC is generated using HMAC-SHA-256 and a shared
+ * secret. The shared secret must be available to secure clock service by implementing
+ * ISharedSecret aidl. Note: ISecureClock depends on the shared secret, without which the secure
+ * time stamp token cannot be generated.
+ */
+
+@VintfStability
+interface ISecureClock {
+ /**
+ * String used as context in the HMAC computation signing the generated time stamp.
+ * See TimeStampToken.mac for details.
+ */
+ const String TIME_STAMP_MAC_LABEL = "Auth Verification";
+
+ /**
+ * Generates an authenticated timestamp.
+ *
+ * @param A challenge value provided by the relying party. It will be included in the generated
+ * TimeStampToken to ensure freshness. The relying service must ensure that the
+ * challenge cannot be specified or predicted by an attacker.
+ *
+ * @return the TimeStampToken, see the definition for details.
+ */
+ TimeStampToken generateTimeStamp(in long challenge);
+}
diff --git a/security/secureclock/aidl/android/hardware/security/secureclock/TimeStampToken.aidl b/security/secureclock/aidl/android/hardware/security/secureclock/TimeStampToken.aidl
new file mode 100644
index 0000000..dd95732
--- /dev/null
+++ b/security/secureclock/aidl/android/hardware/security/secureclock/TimeStampToken.aidl
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.security.secureclock;
+
+import android.hardware.security.secureclock.Timestamp;
+
+/**
+ * TimeStampToken instances are used for secure environments that requires secure time information.
+ */
+
+@VintfStability
+@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true)
+parcelable TimeStampToken {
+ /**
+ * The challenge that was provided as argument to ISecureClock.generateTimeStamp by the client.
+ */
+ long challenge;
+
+ /**
+ * The current time of the secure environment that generates the TimeStampToken.
+ */
+ Timestamp timestamp;
+
+ /**
+ * 32-byte HMAC-SHA256 of the above values, computed as:
+ *
+ * HMAC(H,
+ * ISecureClock.TIME_STAMP_MAC_LABEL || challenge || timestamp || securityLevel )
+ *
+ * where:
+ *
+ * ``ISecureClock.TIME_STAMP_MAC_LABEL'' is a sting constant defined in ISecureClock.aidl.
+ *
+ * ``H'' is the shared HMAC key (see computeSharedHmac() in ISharedSecret).
+ *
+ * ``||'' represents concatenation
+ *
+ * The representation of challenge and timestamp is as 64-bit unsigned integers in big-endian
+ * order. SecurityLevel is represented as a 32-bit unsigned integer in big-endian order as
+ * described in android.hardware.security.keymint.SecurityLevel. It represents the security
+ * level of the secure clock environment.
+ */
+ byte[] mac;
+}
diff --git a/security/keymint/aidl/android/hardware/security/keymint/Timestamp.aidl b/security/secureclock/aidl/android/hardware/security/secureclock/Timestamp.aidl
similarity index 88%
rename from security/keymint/aidl/android/hardware/security/keymint/Timestamp.aidl
rename to security/secureclock/aidl/android/hardware/security/secureclock/Timestamp.aidl
index ebb3684..27758e1 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/Timestamp.aidl
+++ b/security/secureclock/aidl/android/hardware/security/secureclock/Timestamp.aidl
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.hardware.security.keymint;
+package android.hardware.security.secureclock;
/**
* Time in milliseconds since some arbitrary point in time. Time must be monotonically increasing,
@@ -23,6 +23,7 @@
* by setting the clock to zero during each boot, and then counting time accurately).
*/
@VintfStability
+@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true)
parcelable Timestamp {
long milliSeconds;
}
diff --git a/security/secureclock/aidl/vts/functional/Android.bp b/security/secureclock/aidl/vts/functional/Android.bp
new file mode 100644
index 0000000..1619eab
--- /dev/null
+++ b/security/secureclock/aidl/vts/functional/Android.bp
@@ -0,0 +1,43 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_test {
+ name: "VtsAidlSecureClockTargetTest",
+ defaults: [
+ "VtsHalTargetTestDefaults",
+ "use_libaidlvintf_gtest_helper_static",
+ ],
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ ],
+ srcs: [
+ "SecureClockAidlTest.cpp",
+ ],
+ shared_libs: [
+ "libbinder_ndk",
+ "libcrypto",
+ "libkeymint",
+ ],
+ static_libs: [
+ "android.hardware.security.keymint-V1-ndk_platform",
+ "android.hardware.security.secureclock-V1-ndk_platform",
+ ],
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
+}
diff --git a/security/secureclock/aidl/vts/functional/AndroidTest.xml b/security/secureclock/aidl/vts/functional/AndroidTest.xml
new file mode 100644
index 0000000..4861c7c
--- /dev/null
+++ b/security/secureclock/aidl/vts/functional/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs VtsAidlSecureClockTargetTest.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-native" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push"
+ value="VtsAidlSecureClockTargetTest->/data/local/tmp/VtsAidlSecureClockTargetTest" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="VtsAidlSecureClockTargetTest" />
+ <option name="native-test-timeout" value="900000"/>
+ </test>
+</configuration>
diff --git a/security/secureclock/aidl/vts/functional/SecureClockAidlTest.cpp b/security/secureclock/aidl/vts/functional/SecureClockAidlTest.cpp
new file mode 100644
index 0000000..9ca1ee8
--- /dev/null
+++ b/security/secureclock/aidl/vts/functional/SecureClockAidlTest.cpp
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "secureclock_test"
+#include <android-base/logging.h>
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/security/keymint/ErrorCode.h>
+#include <aidl/android/hardware/security/secureclock/ISecureClock.h>
+#include <android/binder_manager.h>
+#include <binder/ProcessState.h>
+#include <gtest/gtest.h>
+#include <vector>
+
+namespace aidl::android::hardware::security::secureclock::test {
+using Status = ::ndk::ScopedAStatus;
+using ::aidl::android::hardware::security::keymint::ErrorCode;
+using ::std::shared_ptr;
+using ::std::string;
+using ::std::vector;
+
+class SecureClockAidlTest : public ::testing::TestWithParam<string> {
+ public:
+ struct TimestampTokenResult {
+ ErrorCode error;
+ TimeStampToken token;
+ };
+
+ TimestampTokenResult getTimestampToken(int64_t in_challenge) {
+ TimestampTokenResult result;
+ result.error =
+ GetReturnErrorCode(secureClock_->generateTimeStamp(in_challenge, &result.token));
+ return result;
+ }
+
+ uint64_t getTime() {
+ struct timespec timespec;
+ EXPECT_EQ(0, clock_gettime(CLOCK_BOOTTIME, ×pec));
+ return timespec.tv_sec * 1000 + timespec.tv_nsec / 1000000;
+ }
+
+ int sleep_ms(uint32_t milliseconds) {
+ struct timespec sleep_time = {static_cast<time_t>(milliseconds / 1000),
+ static_cast<long>(milliseconds % 1000) * 1000000};
+ while (sleep_time.tv_sec || sleep_time.tv_nsec) {
+ if (nanosleep(&sleep_time /* to wait */,
+ &sleep_time /* remaining (on interrruption) */) == 0) {
+ sleep_time = {};
+ } else {
+ if (errno != EINTR) return errno;
+ }
+ }
+ return 0;
+ }
+
+ ErrorCode GetReturnErrorCode(const Status& result) {
+ if (result.isOk()) return ErrorCode::OK;
+
+ if (result.getExceptionCode() == EX_SERVICE_SPECIFIC) {
+ return static_cast<ErrorCode>(result.getServiceSpecificError());
+ }
+
+ return ErrorCode::UNKNOWN_ERROR;
+ }
+
+ void InitializeSecureClock(std::shared_ptr<ISecureClock> secureClock) {
+ ASSERT_NE(secureClock, nullptr);
+ secureClock_ = secureClock;
+ }
+
+ ISecureClock& secureClock() { return *secureClock_; }
+
+ static vector<string> build_params() {
+ auto params = ::android::getAidlHalInstanceNames(ISecureClock::descriptor);
+ return params;
+ }
+
+ void SetUp() override {
+ if (AServiceManager_isDeclared(GetParam().c_str())) {
+ ::ndk::SpAIBinder binder(AServiceManager_waitForService(GetParam().c_str()));
+ InitializeSecureClock(ISecureClock::fromBinder(binder));
+ } else {
+ InitializeSecureClock(nullptr);
+ }
+ }
+
+ void TearDown() override {}
+
+ private:
+ std::shared_ptr<ISecureClock> secureClock_;
+};
+
+/*
+ * The precise capabilities required to generate TimeStampToken will vary depending on the specific
+ * vendor implementations. The only thing we really can test is that tokens can be created by
+ * secureclock services, and that the timestamps increase as expected.
+ */
+TEST_P(SecureClockAidlTest, TestCreation) {
+ auto result1 = getTimestampToken(1 /* challenge */);
+ auto result1_time = getTime();
+ EXPECT_EQ(ErrorCode::OK, result1.error);
+ EXPECT_EQ(1U, result1.token.challenge);
+ EXPECT_GT(result1.token.timestamp.milliSeconds, 0U);
+
+ unsigned long time_to_sleep = 200;
+ sleep_ms(time_to_sleep);
+
+ auto result2 = getTimestampToken(2 /* challenge */);
+ auto result2_time = getTime();
+ EXPECT_EQ(ErrorCode::OK, result2.error);
+ EXPECT_EQ(2U, result2.token.challenge);
+ EXPECT_GT(result2.token.timestamp.milliSeconds, 0U);
+
+ auto host_time_delta = result2_time - result1_time;
+
+ EXPECT_GE(host_time_delta, time_to_sleep)
+ << "We slept for " << time_to_sleep << " ms, the clock must have advanced by that much";
+ EXPECT_LE(host_time_delta, time_to_sleep + 100)
+ << "The getTimestampToken call took " << (host_time_delta - time_to_sleep)
+ << " ms? That's awful!";
+ EXPECT_GE(result2.token.timestamp.milliSeconds, result1.token.timestamp.milliSeconds);
+ unsigned long km_time_delta =
+ result2.token.timestamp.milliSeconds - result1.token.timestamp.milliSeconds;
+ // 20 ms of slop just to avoid test flakiness.
+ EXPECT_LE(host_time_delta, km_time_delta + 20);
+ EXPECT_LE(km_time_delta, host_time_delta + 20);
+ ASSERT_EQ(result1.token.mac.size(), result2.token.mac.size());
+ ASSERT_NE(0,
+ memcmp(result1.token.mac.data(), result2.token.mac.data(), result1.token.mac.size()));
+}
+
+/*
+ * Test that the mac changes when the time stamp changes. This is does not guarantee that the time
+ * stamp is included in the mac but on failure we know that it is not. Other than in the test
+ * case above we call getTimestampToken with the exact same set of parameters.
+ */
+TEST_P(SecureClockAidlTest, MacChangesOnChangingTimestamp) {
+ auto result1 = getTimestampToken(0 /* challenge */);
+ auto result1_time = getTime();
+ EXPECT_EQ(ErrorCode::OK, result1.error);
+ EXPECT_EQ(0U, result1.token.challenge);
+ EXPECT_GT(result1.token.timestamp.milliSeconds, 0U);
+
+ unsigned long time_to_sleep = 200;
+ sleep_ms(time_to_sleep);
+
+ auto result2 = getTimestampToken(1 /* challenge */);
+ auto result2_time = getTime();
+ EXPECT_EQ(ErrorCode::OK, result2.error);
+ EXPECT_EQ(1U, result2.token.challenge);
+ EXPECT_GT(result2.token.timestamp.milliSeconds, 0U);
+
+ auto host_time_delta = result2_time - result1_time;
+
+ EXPECT_GE(host_time_delta, time_to_sleep)
+ << "We slept for " << time_to_sleep << " ms, the clock must have advanced by that much";
+ EXPECT_LE(host_time_delta, time_to_sleep + 100)
+ << "The getTimestampToken call took " << (host_time_delta - time_to_sleep)
+ << " ms? That's awful!";
+
+ EXPECT_GE(result2.token.timestamp.milliSeconds, result1.token.timestamp.milliSeconds);
+ unsigned long km_time_delta =
+ result2.token.timestamp.milliSeconds - result1.token.timestamp.milliSeconds;
+
+ EXPECT_LE(host_time_delta, km_time_delta + 20);
+ EXPECT_LE(km_time_delta, host_time_delta + 20);
+ ASSERT_EQ(result1.token.mac.size(), result2.token.mac.size());
+ ASSERT_NE(0,
+ memcmp(result1.token.mac.data(), result2.token.mac.data(), result1.token.mac.size()));
+}
+
+INSTANTIATE_TEST_SUITE_P(PerInstance, SecureClockAidlTest,
+ testing::ValuesIn(SecureClockAidlTest::build_params()),
+ ::android::PrintInstanceNameToString);
+} // namespace aidl::android::hardware::security::secureclock::test
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
\ No newline at end of file
diff --git a/security/sharedsecret/aidl/Android.bp b/security/sharedsecret/aidl/Android.bp
new file mode 100644
index 0000000..ab44110
--- /dev/null
+++ b/security/sharedsecret/aidl/Android.bp
@@ -0,0 +1,21 @@
+aidl_interface {
+ name: "android.hardware.security.sharedsecret",
+ vendor_available: true,
+ srcs: [
+ "android/hardware/security/sharedsecret/*.aidl",
+ ],
+ stability: "vintf",
+ backend: {
+ java: {
+ sdk_version: "module_current",
+ },
+ ndk: {
+ vndk: {
+ enabled: true,
+ },
+ },
+ rust: {
+ enabled: true,
+ },
+ },
+}
diff --git a/security/sharedsecret/aidl/OWNERS b/security/sharedsecret/aidl/OWNERS
new file mode 100644
index 0000000..a93b171
--- /dev/null
+++ b/security/sharedsecret/aidl/OWNERS
@@ -0,0 +1,4 @@
+jbires@google.com
+jdanis@google.com
+seleneh@google.com
+swillden@google.com
diff --git a/security/sharedsecret/aidl/aidl_api/android.hardware.security.sharedsecret/current/android/hardware/security/sharedsecret/ISharedSecret.aidl b/security/sharedsecret/aidl/aidl_api/android.hardware.security.sharedsecret/current/android/hardware/security/sharedsecret/ISharedSecret.aidl
new file mode 100644
index 0000000..2509936
--- /dev/null
+++ b/security/sharedsecret/aidl/aidl_api/android.hardware.security.sharedsecret/current/android/hardware/security/sharedsecret/ISharedSecret.aidl
@@ -0,0 +1,26 @@
+///////////////////////////////////////////////////////////////////////////////
+// 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.security.sharedsecret;
+@VintfStability
+interface ISharedSecret {
+ android.hardware.security.sharedsecret.SharedSecretParameters getSharedSecretParameters();
+ byte[] computeSharedSecret(in android.hardware.security.sharedsecret.SharedSecretParameters[] params);
+ const String KEY_AGREEMENT_LABEL = "KeymasterSharedMac";
+ const String KEY_CHECK_LABEL = "Keymaster HMAC Verification";
+}
diff --git a/security/sharedsecret/aidl/aidl_api/android.hardware.security.sharedsecret/current/android/hardware/security/sharedsecret/SharedSecretParameters.aidl b/security/sharedsecret/aidl/aidl_api/android.hardware.security.sharedsecret/current/android/hardware/security/sharedsecret/SharedSecretParameters.aidl
new file mode 100644
index 0000000..9b65046
--- /dev/null
+++ b/security/sharedsecret/aidl/aidl_api/android.hardware.security.sharedsecret/current/android/hardware/security/sharedsecret/SharedSecretParameters.aidl
@@ -0,0 +1,24 @@
+///////////////////////////////////////////////////////////////////////////////
+// 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.security.sharedsecret;
+@VintfStability
+parcelable SharedSecretParameters {
+ byte[] seed;
+ byte[] nonce;
+}
diff --git a/security/sharedsecret/aidl/android/hardware/security/sharedsecret/ISharedSecret.aidl b/security/sharedsecret/aidl/android/hardware/security/sharedsecret/ISharedSecret.aidl
new file mode 100644
index 0000000..906303f
--- /dev/null
+++ b/security/sharedsecret/aidl/android/hardware/security/sharedsecret/ISharedSecret.aidl
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * limitations under the License.
+ */
+
+package android.hardware.security.sharedsecret;
+import android.hardware.security.sharedsecret.SharedSecretParameters;
+
+/**
+ * Shared Secret definition.
+ *
+ * An ISharedSecret enables any service that implements this interface to establish a shared secret
+ * with one or more other services such as ISecureClock, TEE IKeymintDevice, StrongBox
+ * IKeymintDevice, etc. The shared secret is a 256-bit HMAC key and it is further used to generate
+ * secure tokens with integrity protection. There are two steps to establish a shared secret between
+ * the collaborating services:
+ *
+ * Step 1: During Android startup the system calls each service that implements this interface to
+ * get the shared secret parameters. This is done using getSharedSecretParameters method defined
+ * below.
+ * Step 2: The system lexicographically sorts the shared secret parameters received from each
+ * service and then sends these sorted parameter list to each service in a computeSharedSecret
+ * method defined below. The services individually computes the shared secret and returns back
+ * the 32 byte sharing check hash value generated by using the computed shared secret.
+ * Step 3: The system collects sharing check hash values from each service and evaluates them. If
+ * they are all equal, then the shared secret generation is considered to be successful else it is
+ * considered to have failed.
+ */
+
+@VintfStability
+interface ISharedSecret {
+ /**
+ * String used as label in the shared key derivation. See computeSharedSecret below.
+ */
+ const String KEY_AGREEMENT_LABEL = "KeymasterSharedMac";
+
+ /**
+ * String used as context in the computation of the sharingCheck. See computeSharedSecret
+ * below.
+ */
+ const String KEY_CHECK_LABEL = "Keymaster HMAC Verification";
+
+ /**
+ * This method is the first step in the process for agreeing on a shared key. It is called by
+ * Android during startup. The system calls it on each of the HAL instances and collects the
+ * results in preparation for the second step.
+ *
+ * @return The SharedSecretParameters to use. As specified in the SharedSecretParameters
+ * documentation, the seed must contain the same value in every invocation
+ * of the method on a given device, and the nonce must return the same value for every
+ * invocation during a boot session.
+ */
+ SharedSecretParameters getSharedSecretParameters();
+
+ /**
+ * This method is the second and final step in the process for agreeing on a shared key. It is
+ * called by Android during startup. The system calls it on each of the keymint services, and
+ * sends to it all of the SharedSecretParameters returned by all keymint services.
+ *
+ * This method computes the shared 32-byte HMAC key ``H'' as follows (all keymint services
+ * instances perform the same computation to arrive at the same result):
+ *
+ * H = CKDF(key = K,
+ * context = P1 || P2 || ... || Pn,
+ * label = KEY_AGREEMENT_LABEL)
+ *
+ * where:
+ *
+ * ``CKDF'' is the standard AES-CMAC KDF from NIST SP 800-108 in counter mode (see Section
+ * 5.1 of the referenced publication). ``key'', ``context'', and ``label'' are
+ * defined in the standard. The counter is prefixed and length L appended, as shown
+ * in the construction on page 12 of the standard. The label string is UTF-8 encoded.
+ *
+ * ``K'' is a pre-established shared secret, set up during factory reset. The mechanism for
+ * establishing this shared secret is implementation-defined.Any method of securely
+ * establishing K that ensures that an attacker cannot obtain or derive its value is
+ * acceptable.
+ *
+ * CRITICAL SECURITY REQUIREMENT: All keys created by a IKeymintDevice instance must
+ * be cryptographically bound to the value of K, such that establishing a new K
+ * permanently destroys them.
+ *
+ * ``||'' represents concatenation.
+ *
+ * ``Pi'' is the i'th SharedSecretParameters value in the params vector. Encoding of an
+ * SharedSecretParameters is the concatenation of its two fields, i.e. seed || nonce.
+ *
+ * Note that the label "KeymasterSharedMac" is the 18-byte UTF-8 encoding of the string.
+ *
+ * @param params is an array of SharedSecretParameters The lexicographically sorted
+ * SharedSecretParameters data returned by all keymint services when getSharedSecretParameters
+ * was called.
+ *
+ * @return sharingCheck A 32-byte value used to verify that all the keymint services have
+ * computed the same shared HMAC key. The sharingCheck value is computed as follows:
+ *
+ * sharingCheck = HMAC(H, KEY_CHECK_LABEL)
+ *
+ * The string is UTF-8 encoded, 27 bytes in length. If the returned values of all
+ * keymint services don't match, clients must assume that HMAC agreement
+ * failed.
+ */
+ byte[] computeSharedSecret(in SharedSecretParameters[] params);
+}
diff --git a/security/sharedsecret/aidl/android/hardware/security/sharedsecret/SharedSecretParameters.aidl b/security/sharedsecret/aidl/android/hardware/security/sharedsecret/SharedSecretParameters.aidl
new file mode 100644
index 0000000..691b3f1
--- /dev/null
+++ b/security/sharedsecret/aidl/android/hardware/security/sharedsecret/SharedSecretParameters.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.security.sharedsecret;
+
+/**
+ * SharedSecretParameters holds the data used in the process of establishing a shared secret i.e.
+ * HMAC key between multiple keymint services. These parameters are returned in by
+ * getSharedSecretParameters() and send to computeShareSecret(). See the named methods in
+ * ISharedSecret for details of usage.
+ */
+
+@VintfStability
+parcelable SharedSecretParameters {
+ /**
+ * Either empty or contains a non zero persistent value that is associated with the pre-shared
+ * HMAC agreement key. It is either empty or 32 bytes in length.
+ */
+ byte[] seed;
+
+ /**
+ * A 32-byte value which is guaranteed to be different each time
+ * getSharedSecretParameters() is called. Probabilistic uniqueness (i.e. random) is acceptable,
+ * though a stronger uniqueness guarantee (e.g. counter) is recommended where possible.
+ */
+ byte[] nonce;
+}
diff --git a/security/sharedsecret/aidl/vts/functional/Android.bp b/security/sharedsecret/aidl/vts/functional/Android.bp
new file mode 100644
index 0000000..76bf7ff
--- /dev/null
+++ b/security/sharedsecret/aidl/vts/functional/Android.bp
@@ -0,0 +1,43 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_test {
+ name: "VtsAidlSharedSecretTargetTest",
+ defaults: [
+ "VtsHalTargetTestDefaults",
+ "use_libaidlvintf_gtest_helper_static",
+ ],
+ srcs: [
+ "SharedSecretAidlTest.cpp",
+ ],
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ ],
+ shared_libs: [
+ "libbinder_ndk",
+ "libcrypto",
+ "libkeymint",
+ ],
+ static_libs: [
+ "android.hardware.security.keymint-V1-ndk_platform",
+ "android.hardware.security.sharedsecret-V1-ndk_platform",
+ ],
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
+}
diff --git a/security/sharedsecret/aidl/vts/functional/AndroidTest.xml b/security/sharedsecret/aidl/vts/functional/AndroidTest.xml
new file mode 100644
index 0000000..c6697bc
--- /dev/null
+++ b/security/sharedsecret/aidl/vts/functional/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs VtsAidlSharedSecretTargetTest.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-native" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push"
+ value="VtsAidlSharedSecretTargetTest->/data/local/tmp/VtsAidlSharedSecretTargetTest" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="VtsAidlSharedSecretTargetTest" />
+ <option name="native-test-timeout" value="900000"/>
+ </test>
+</configuration>
diff --git a/security/sharedsecret/aidl/vts/functional/SharedSecretAidlTest.cpp b/security/sharedsecret/aidl/vts/functional/SharedSecretAidlTest.cpp
new file mode 100644
index 0000000..83f6ef3
--- /dev/null
+++ b/security/sharedsecret/aidl/vts/functional/SharedSecretAidlTest.cpp
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "sharedsecret_test"
+#include <android-base/logging.h>
+
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/security/keymint/ErrorCode.h>
+#include <aidl/android/hardware/security/sharedsecret/ISharedSecret.h>
+#include <android/binder_manager.h>
+#include <gtest/gtest.h>
+#include <vector>
+
+namespace aidl::android::hardware::security::sharedsecret::test {
+using ::aidl::android::hardware::security::keymint::ErrorCode;
+using ::std::shared_ptr;
+using ::std::vector;
+using Status = ::ndk::ScopedAStatus;
+
+class SharedSecretAidlTest : public ::testing::Test {
+ public:
+ struct GetParamsResult {
+ ErrorCode error;
+ SharedSecretParameters params;
+ auto tie() { return std::tie(error, params); }
+ };
+
+ struct ComputeResult {
+ ErrorCode error;
+ vector<uint8_t> sharing_check;
+ auto tie() { return std::tie(error, sharing_check); }
+ };
+
+ GetParamsResult getSharedSecretParameters(shared_ptr<ISharedSecret>& sharedSecret) {
+ SharedSecretParameters params;
+ auto error = GetReturnErrorCode(sharedSecret->getSharedSecretParameters(¶ms));
+ EXPECT_EQ(ErrorCode::OK, error);
+ GetParamsResult result;
+ result.tie() = std::tie(error, params);
+ return result;
+ }
+
+ vector<SharedSecretParameters> getAllSharedSecretParameters() {
+ vector<SharedSecretParameters> paramsVec;
+ for (auto& sharedSecret : allSharedSecrets_) {
+ auto result = getSharedSecretParameters(sharedSecret);
+ EXPECT_EQ(ErrorCode::OK, result.error);
+ if (result.error == ErrorCode::OK) paramsVec.push_back(std::move(result.params));
+ }
+ return paramsVec;
+ }
+
+ ComputeResult computeSharedSecret(shared_ptr<ISharedSecret>& sharedSecret,
+ const vector<SharedSecretParameters>& params) {
+ std::vector<uint8_t> sharingCheck;
+ auto error = GetReturnErrorCode(sharedSecret->computeSharedSecret(params, &sharingCheck));
+ ComputeResult result;
+ result.tie() = std::tie(error, sharingCheck);
+ return result;
+ }
+
+ vector<ComputeResult> computeAllSharedSecrets(const vector<SharedSecretParameters>& params) {
+ vector<ComputeResult> result;
+ for (auto& sharedSecret : allSharedSecrets_) {
+ result.push_back(computeSharedSecret(sharedSecret, params));
+ }
+ return result;
+ }
+
+ vector<vector<uint8_t>> copyNonces(const vector<SharedSecretParameters>& paramsVec) {
+ vector<vector<uint8_t>> nonces;
+ for (auto& param : paramsVec) {
+ nonces.push_back(param.nonce);
+ }
+ return nonces;
+ }
+
+ void verifyResponses(const vector<uint8_t>& expected, const vector<ComputeResult>& responses) {
+ for (auto& response : responses) {
+ EXPECT_EQ(ErrorCode::OK, response.error);
+ EXPECT_EQ(expected, response.sharing_check) << "Sharing check values should match.";
+ }
+ }
+
+ ErrorCode GetReturnErrorCode(const Status& result) {
+ if (result.isOk()) return ErrorCode::OK;
+ if (result.getExceptionCode() == EX_SERVICE_SPECIFIC) {
+ return static_cast<ErrorCode>(result.getServiceSpecificError());
+ }
+ return ErrorCode::UNKNOWN_ERROR;
+ }
+
+ static shared_ptr<ISharedSecret> getSharedSecretService(const char* name) {
+ if (AServiceManager_isDeclared(name)) {
+ ::ndk::SpAIBinder binder(AServiceManager_waitForService(name));
+ return ISharedSecret::fromBinder(binder);
+ }
+ return nullptr;
+ }
+
+ const vector<shared_ptr<ISharedSecret>>& allSharedSecrets() { return allSharedSecrets_; }
+
+ static void SetUpTestCase() {
+ if (allSharedSecrets_.empty()) {
+ auto names = ::android::getAidlHalInstanceNames(ISharedSecret::descriptor);
+ for (const auto& name : names) {
+ auto servicePtr = getSharedSecretService(name.c_str());
+ if (servicePtr != nullptr) allSharedSecrets_.push_back(std::move(servicePtr));
+ }
+ }
+ }
+ static void TearDownTestCase() {}
+ void SetUp() override {}
+ void TearDown() override {}
+
+ private:
+ static vector<shared_ptr<ISharedSecret>> allSharedSecrets_;
+};
+
+vector<shared_ptr<ISharedSecret>> SharedSecretAidlTest::allSharedSecrets_;
+
+TEST_F(SharedSecretAidlTest, GetParameters) {
+ auto sharedSecrets = allSharedSecrets();
+ for (auto sharedSecret : sharedSecrets) {
+ auto result1 = getSharedSecretParameters(sharedSecret);
+ EXPECT_EQ(ErrorCode::OK, result1.error);
+ auto result2 = getSharedSecretParameters(sharedSecret);
+ EXPECT_EQ(ErrorCode::OK, result2.error);
+ ASSERT_EQ(result1.params.seed, result2.params.seed)
+ << "A given shared secret service should always return the same seed.";
+ ASSERT_EQ(result1.params.nonce, result2.params.nonce)
+ << "A given shared secret service should always return the same nonce until "
+ "restart.";
+ }
+}
+
+TEST_F(SharedSecretAidlTest, ComputeSharedSecret) {
+ auto params = getAllSharedSecretParameters();
+ ASSERT_EQ(allSharedSecrets().size(), params.size())
+ << "One or more shared secret services failed to provide parameters.";
+ auto nonces = copyNonces(params);
+ EXPECT_EQ(allSharedSecrets().size(), nonces.size());
+ std::sort(nonces.begin(), nonces.end());
+ std::unique(nonces.begin(), nonces.end());
+ EXPECT_EQ(allSharedSecrets().size(), nonces.size());
+
+ auto responses = computeAllSharedSecrets(params);
+ ASSERT_GT(responses.size(), 0U);
+ verifyResponses(responses[0].sharing_check, responses);
+
+ // Do it a second time. Should get the same answers.
+ params = getAllSharedSecretParameters();
+ ASSERT_EQ(allSharedSecrets().size(), params.size())
+ << "One or more shared secret services failed to provide parameters.";
+
+ responses = computeAllSharedSecrets(params);
+ ASSERT_GT(responses.size(), 0U);
+ ASSERT_EQ(32U, responses[0].sharing_check.size());
+ verifyResponses(responses[0].sharing_check, responses);
+}
+
+template <class F>
+class final_action {
+ public:
+ explicit final_action(F f) : f_(std::move(f)) {}
+ ~final_action() { f_(); }
+
+ private:
+ F f_;
+};
+
+template <class F>
+inline final_action<F> finally(const F& f) {
+ return final_action<F>(f);
+}
+
+TEST_F(SharedSecretAidlTest, ComputeSharedSecretCorruptNonce) {
+ auto fixup_hmac = finally([&]() { computeAllSharedSecrets(getAllSharedSecretParameters()); });
+
+ auto params = getAllSharedSecretParameters();
+ ASSERT_EQ(allSharedSecrets().size(), params.size())
+ << "One or more shared secret services failed to provide parameters.";
+
+ // All should be well in the normal case
+ auto responses = computeAllSharedSecrets(params);
+
+ ASSERT_GT(responses.size(), 0U);
+ vector<uint8_t> correct_response = responses[0].sharing_check;
+ verifyResponses(correct_response, responses);
+
+ // Pick a random param, a random byte within the param's nonce, and a random bit within
+ // the byte. Flip that bit.
+ size_t param_to_tweak = rand() % params.size();
+ uint8_t byte_to_tweak = rand() % sizeof(params[param_to_tweak].nonce);
+ uint8_t bit_to_tweak = rand() % 8;
+ params[param_to_tweak].nonce[byte_to_tweak] ^= (1 << bit_to_tweak);
+
+ responses = computeAllSharedSecrets(params);
+ for (size_t i = 0; i < responses.size(); ++i) {
+ if (i == param_to_tweak) {
+ EXPECT_EQ(ErrorCode::INVALID_ARGUMENT, responses[i].error)
+ << "Shared secret service that provided tweaked param should fail to compute "
+ "shared secret";
+ } else {
+ EXPECT_EQ(ErrorCode::OK, responses[i].error) << "Others should succeed";
+ EXPECT_NE(correct_response, responses[i].sharing_check)
+ << "Others should calculate a different shared secret, due to the tweaked "
+ "nonce.";
+ }
+ }
+}
+
+TEST_F(SharedSecretAidlTest, ComputeSharedSecretCorruptSeed) {
+ auto fixup_hmac = finally([&]() { computeAllSharedSecrets(getAllSharedSecretParameters()); });
+ auto params = getAllSharedSecretParameters();
+ ASSERT_EQ(allSharedSecrets().size(), params.size())
+ << "One or more shared secret service failed to provide parameters.";
+
+ // All should be well in the normal case
+ auto responses = computeAllSharedSecrets(params);
+
+ ASSERT_GT(responses.size(), 0U);
+ vector<uint8_t> correct_response = responses[0].sharing_check;
+ verifyResponses(correct_response, responses);
+
+ // Pick a random param and modify the seed. We just increase the seed length by 1. It doesn't
+ // matter what value is in the additional byte; it changes the seed regardless.
+ auto param_to_tweak = rand() % params.size();
+ auto& to_tweak = params[param_to_tweak].seed;
+ ASSERT_TRUE(to_tweak.size() == 32 || to_tweak.size() == 0);
+ if (!to_tweak.size()) {
+ to_tweak.resize(32); // Contents don't matter; a little randomization is nice.
+ }
+ to_tweak[0]++;
+
+ responses = computeAllSharedSecrets(params);
+ for (size_t i = 0; i < responses.size(); ++i) {
+ if (i == param_to_tweak) {
+ EXPECT_EQ(ErrorCode::INVALID_ARGUMENT, responses[i].error)
+ << "Shared secret service that provided tweaked param should fail to compute "
+ "shared secret";
+ } else {
+ EXPECT_EQ(ErrorCode::OK, responses[i].error) << "Others should succeed";
+ EXPECT_NE(correct_response, responses[i].sharing_check)
+ << "Others should calculate a different shared secret, due to the tweaked "
+ "nonce.";
+ }
+ }
+}
+} // namespace aidl::android::hardware::security::sharedsecret::test
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/sensors/common/default/2.X/Sensor.cpp b/sensors/common/default/2.X/Sensor.cpp
index 642fc89..1a7c628 100644
--- a/sensors/common/default/2.X/Sensor.cpp
+++ b/sensors/common/default/2.X/Sensor.cpp
@@ -313,7 +313,7 @@
mSensorInfo.maxRange = 1000.0f * M_PI / 180.0f;
mSensorInfo.resolution = 1000.0f * M_PI / (180.0f * 32768.0f);
mSensorInfo.power = 0.001f;
- mSensorInfo.minDelay = 2.5f * 1000; // microseconds
+ mSensorInfo.minDelay = 10 * 1000; // microseconds
mSensorInfo.maxDelay = kDefaultMaxDelayUs;
mSensorInfo.fifoReservedEventCount = 0;
mSensorInfo.fifoMaxEventCount = 0;
diff --git a/tests/extension/vibrator/aidl/client/Android.bp b/tests/extension/vibrator/aidl/client/Android.bp
index b0d8238..108d000 100644
--- a/tests/extension/vibrator/aidl/client/Android.bp
+++ b/tests/extension/vibrator/aidl/client/Android.bp
@@ -14,11 +14,11 @@
shared_libs: [
"libbinder",
"libutils",
- "android.hardware.vibrator-cpp",
- "android.hardware.tests.extension.vibrator-cpp",
+ "android.hardware.vibrator-V1-cpp",
+ "android.hardware.tests.extension.vibrator-V1-cpp",
"libbinder_ndk",
- "android.hardware.vibrator-ndk_platform",
- "android.hardware.tests.extension.vibrator-ndk_platform",
+ "android.hardware.vibrator-V1-ndk_platform",
+ "android.hardware.tests.extension.vibrator-V1-ndk_platform",
],
}
diff --git a/tests/extension/vibrator/aidl/default/Android.bp b/tests/extension/vibrator/aidl/default/Android.bp
index ed40d25..fd19f13 100644
--- a/tests/extension/vibrator/aidl/default/Android.bp
+++ b/tests/extension/vibrator/aidl/default/Android.bp
@@ -19,7 +19,7 @@
shared_libs: [
"libbase",
"libbinder_ndk",
- "android.hardware.vibrator-ndk_platform",
- "android.hardware.tests.extension.vibrator-ndk_platform",
+ "android.hardware.vibrator-V1-ndk_platform",
+ "android.hardware.tests.extension.vibrator-V1-ndk_platform",
],
}
diff --git a/tests/lazy/1.1/ILazy.hal b/tests/lazy/1.1/ILazy.hal
index a15e0e3..eb48fd3 100644
--- a/tests/lazy/1.1/ILazy.hal
+++ b/tests/lazy/1.1/ILazy.hal
@@ -18,4 +18,12 @@
import android.hardware.tests.lazy@1.0;
-interface ILazy extends @1.0::ILazy {};
+interface ILazy extends @1.0::ILazy {
+ /**
+ * Ask the process hosting the service to install a callback that notifies if there are
+ * services with clients.
+ * For testing purposes, this callback exercises the code to unregister/re-register
+ * the services and eventually shuts down the process.
+ */
+ setCustomActiveServicesCallback();
+};
diff --git a/tests/msgq/1.0/ITestMsgQ.hal b/tests/msgq/1.0/ITestMsgQ.hal
index bd10237..0cf9c7c 100644
--- a/tests/msgq/1.0/ITestMsgQ.hal
+++ b/tests/msgq/1.0/ITestMsgQ.hal
@@ -41,12 +41,15 @@
*
* @param configureFmq The server sets up a new unsynchronized FMQ if
* this parameter is true.
+ * @param userFd True to initialize the message queue with a user supplied
+ * file descriptor for the ring buffer.
+ * False to let the message queue use a single FD for everything.
*
* @return ret True if successful.
* @return mqDesc This structure describes the unsynchronized FMQ that was
* set up by the service. Client can use it to set up the FMQ at its end.
*/
- getFmqUnsyncWrite(bool configureFmq) generates(bool ret, fmq_unsync<int32_t> mqDesc);
+ getFmqUnsyncWrite(bool configureFmq, bool userFd) generates(bool ret, fmq_unsync<int32_t> mqDesc);
/**
* This method request the service to write into the synchronized read/write
diff --git a/tests/msgq/1.0/default/Android.bp b/tests/msgq/1.0/default/Android.bp
index 6e7cd44..a0d6f31 100644
--- a/tests/msgq/1.0/default/Android.bp
+++ b/tests/msgq/1.0/default/Android.bp
@@ -91,9 +91,10 @@
// These are static libs only for testing purposes and portability. Shared
// libs should be used on device.
static_libs: [
+ "android.hardware.common-V2-ndk_platform",
+ "android.hardware.common.fmq-V1-ndk_platform",
"android.hardware.tests.msgq@1.0",
"android.fmq.test-ndk_platform",
- "android.hardware.common.fmq-unstable-ndk_platform",
],
whole_static_libs: [
"android.hardware.tests.msgq@1.0-impl",
diff --git a/tests/msgq/1.0/default/TestMsgQ.cpp b/tests/msgq/1.0/default/TestMsgQ.cpp
index 4473737..4726ffe 100644
--- a/tests/msgq/1.0/default/TestMsgQ.cpp
+++ b/tests/msgq/1.0/default/TestMsgQ.cpp
@@ -41,10 +41,19 @@
return true;
}
-Return<void> TestMsgQ::getFmqUnsyncWrite(bool configureFmq, getFmqUnsyncWrite_cb _hidl_cb) {
+Return<void> TestMsgQ::getFmqUnsyncWrite(bool configureFmq, bool userFd,
+ getFmqUnsyncWrite_cb _hidl_cb) {
if (configureFmq) {
static constexpr size_t kNumElementsInQueue = 1024;
- mFmqUnsynchronized.reset(new (std::nothrow) MessageQueueUnsync(kNumElementsInQueue));
+ static constexpr size_t kElementSizeBytes = sizeof(int32_t);
+ android::base::unique_fd ringbufferFd;
+ if (userFd) {
+ ringbufferFd.reset(
+ ::ashmem_create_region("UnsyncWrite", kNumElementsInQueue * kElementSizeBytes));
+ }
+ mFmqUnsynchronized.reset(new (std::nothrow) MessageQueueUnsync(
+ kNumElementsInQueue, false, std::move(ringbufferFd),
+ kNumElementsInQueue * kElementSizeBytes));
}
if ((mFmqUnsynchronized == nullptr) ||
(mFmqUnsynchronized->isValid() == false)) {
diff --git a/tests/msgq/1.0/default/TestMsgQ.h b/tests/msgq/1.0/default/TestMsgQ.h
index 8a204b7..49675fe 100644
--- a/tests/msgq/1.0/default/TestMsgQ.h
+++ b/tests/msgq/1.0/default/TestMsgQ.h
@@ -56,7 +56,8 @@
// Methods from ::android::hardware::tests::msgq::V1_0::ITestMsgQ follow.
Return<bool> configureFmqSyncReadWrite(const MQDescriptorSync<int32_t>& mqDesc) override;
- Return<void> getFmqUnsyncWrite(bool configureFmq, getFmqUnsyncWrite_cb _hidl_cb) override;
+ Return<void> getFmqUnsyncWrite(bool configureFmq, bool userFd,
+ getFmqUnsyncWrite_cb _hidl_cb) override;
Return<bool> requestWriteFmqSync(int32_t count) override;
Return<bool> requestReadFmqSync(int32_t count) override;
Return<bool> requestWriteFmqUnsync(int32_t count) override;
diff --git a/tetheroffload/control/1.0/vts/functional/Android.bp b/tetheroffload/control/1.0/vts/functional/Android.bp
index c397df4..ddf3826 100644
--- a/tetheroffload/control/1.0/vts/functional/Android.bp
+++ b/tetheroffload/control/1.0/vts/functional/Android.bp
@@ -30,3 +30,17 @@
"vts",
],
}
+
+cc_test_library {
+ name: "VtsHalTetheroffloadControlV1_0TargetTest-lib",
+ defaults: ["VtsHalTargetTestDefaults"],
+ export_include_dirs: ["include"],
+ static_libs: [
+ "android.hardware.tetheroffload.config@1.0",
+ "android.hardware.tetheroffload.control@1.0",
+ ],
+ srcs: [
+ "OffloadControlTestBase.cpp",
+ "OffloadControlTestUtils.cpp",
+ ],
+}
diff --git a/tetheroffload/control/1.0/vts/functional/OffloadControlTestBase.cpp b/tetheroffload/control/1.0/vts/functional/OffloadControlTestBase.cpp
index bd0dad7..e392e96 100644
--- a/tetheroffload/control/1.0/vts/functional/OffloadControlTestBase.cpp
+++ b/tetheroffload/control/1.0/vts/functional/OffloadControlTestBase.cpp
@@ -53,29 +53,6 @@
ASSERT_TRUE(ret.isOk());
}
-void OffloadControlTestBase::prepareControlHal() {
- control = createControl(std::get<1>(GetParam()));
- ASSERT_NE(nullptr, control.get()) << "Could not get HIDL instance";
-
- control_cb = new TetheringOffloadCallback();
- ASSERT_NE(nullptr, control_cb.get()) << "Could not get get offload callback";
-}
-
-void OffloadControlTestBase::initOffload(const bool expected_result) {
- auto init_cb = [&](bool success, std::string errMsg) {
- std::string msg = StringPrintf("Unexpectedly %s to init offload: %s",
- success ? "succeeded" : "failed", errMsg.c_str());
- ASSERT_EQ(expected_result, success) << msg;
- };
- const Return<void> ret = control->initOffload(control_cb, init_cb);
- ASSERT_TRUE(ret.isOk());
-}
-
-void OffloadControlTestBase::setupControlHal() {
- prepareControlHal();
- initOffload(true);
-}
-
void OffloadControlTestBase::stopOffload(const ExpectBoolean value) {
auto cb = [&](bool success, const hidl_string& errMsg) {
switch (value) {
diff --git a/tetheroffload/control/1.0/vts/functional/include/OffloadControlTestBase.h b/tetheroffload/control/1.0/vts/functional/include/OffloadControlTestBase.h
index 004019a..994c808 100644
--- a/tetheroffload/control/1.0/vts/functional/include/OffloadControlTestBase.h
+++ b/tetheroffload/control/1.0/vts/functional/include/OffloadControlTestBase.h
@@ -36,7 +36,6 @@
using android::hardware::Return;
using android::hardware::Void;
using android::hardware::tetheroffload::config::V1_0::IOffloadConfig;
-using android::hardware::tetheroffload::control::V1_0::IOffloadControl;
using android::hardware::tetheroffload::control::V1_0::ITetheringOffloadCallback;
using android::hardware::tetheroffload::control::V1_0::NatTimeoutUpdate;
using android::hardware::tetheroffload::control::V1_0::OffloadCallbackEvent;
@@ -64,17 +63,21 @@
// Called once in setup stage to retrieve correct version of
// IOffloadControl object.
- virtual sp<IOffloadControl> createControl(const std::string& serviceName) = 0;
+ virtual sp<android::hardware::tetheroffload::control::V1_0::IOffloadControl> createControl(
+ const std::string& serviceName) = 0;
// The IOffloadConfig HAL is tested more thoroughly elsewhere. Here the
// class just setup everything correctly and verify basic readiness.
void setupConfigHal();
- void prepareControlHal();
+ virtual void prepareControlHal() = 0;
- void initOffload(const bool expected_result);
+ virtual void initOffload(const bool expected_result) = 0;
- void setupControlHal();
+ void setupControlHal() {
+ prepareControlHal();
+ initOffload(true);
+ };
void stopOffload(const ExpectBoolean value);
@@ -100,6 +103,6 @@
};
sp<IOffloadConfig> config;
- sp<IOffloadControl> control;
+ sp<android::hardware::tetheroffload::control::V1_0::IOffloadControl> control;
sp<TetheringOffloadCallback> control_cb;
};
\ No newline at end of file
diff --git a/tetheroffload/control/1.0/vts/functional/include/OffloadControlTestV1_0.h b/tetheroffload/control/1.0/vts/functional/include/OffloadControlTestV1_0.h
index 7492f8a..9189d71 100644
--- a/tetheroffload/control/1.0/vts/functional/include/OffloadControlTestV1_0.h
+++ b/tetheroffload/control/1.0/vts/functional/include/OffloadControlTestV1_0.h
@@ -26,8 +26,28 @@
prepareControlHal();
}
- virtual sp<IOffloadControl> createControl(const std::string& serviceName) override {
- return IOffloadControl::getService(serviceName);
+ virtual sp<android::hardware::tetheroffload::control::V1_0::IOffloadControl> createControl(
+ const std::string& serviceName) override {
+ return android::hardware::tetheroffload::control::V1_0::IOffloadControl::getService(
+ serviceName);
+ }
+
+ virtual void prepareControlHal() override {
+ control = createControl(std::get<1>(GetParam()));
+ ASSERT_NE(nullptr, control.get()) << "Could not get HIDL instance";
+
+ control_cb = new TetheringOffloadCallback();
+ ASSERT_NE(nullptr, control_cb.get()) << "Could not get get offload callback";
+ }
+
+ virtual void initOffload(const bool expected_result) override {
+ auto init_cb = [&](bool success, std::string errMsg) {
+ std::string msg = StringPrintf("Unexpectedly %s to init offload: %s",
+ success ? "succeeded" : "failed", errMsg.c_str());
+ ASSERT_EQ(expected_result, success) << msg;
+ };
+ const Return<void> ret = control->initOffload(control_cb, init_cb);
+ ASSERT_TRUE(ret.isOk());
}
};
diff --git a/tetheroffload/control/1.1/Android.bp b/tetheroffload/control/1.1/Android.bp
new file mode 100644
index 0000000..18c8ea9
--- /dev/null
+++ b/tetheroffload/control/1.1/Android.bp
@@ -0,0 +1,16 @@
+// This file is autogenerated by hidl-gen -Landroidbp.
+
+hidl_interface {
+ name: "android.hardware.tetheroffload.control@1.1",
+ root: "android.hardware",
+ srcs: [
+ "types.hal",
+ "IOffloadControl.hal",
+ "ITetheringOffloadCallback.hal",
+ ],
+ interfaces: [
+ "android.hardware.tetheroffload.control@1.0",
+ "android.hidl.base@1.0",
+ ],
+ gen_java: true,
+}
diff --git a/tetheroffload/control/1.1/IOffloadControl.hal b/tetheroffload/control/1.1/IOffloadControl.hal
new file mode 100644
index 0000000..2eb54c8
--- /dev/null
+++ b/tetheroffload/control/1.1/IOffloadControl.hal
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.tetheroffload.control@1.1;
+
+import @1.0::IOffloadControl;
+
+/**
+ * Interface used to control the lifecycle of tethering offload. Note that callbacks of 1.1 HAL
+ * can be registered with the existing callback registration methods from 1.0 HAL.
+ */
+interface IOffloadControl extends @1.0::IOffloadControl {
+ /**
+ * Instruct hardware to send callbacks after certain number of bytes have been transferred in
+ * either direction on this upstream interface.
+ *
+ * The specified quota bytes must be applied to all traffic on the given upstream interface.
+ * This includes hardware forwarded traffic, software forwarded traffic, and AP-originated
+ * traffic. IPv4 and IPv6 traffic both count towards the same quota. IP headers are included
+ * in the byte count quota, but, link-layer headers are not.
+ *
+ * This API may only be called while offload is occurring on this upstream. The hardware
+ * management process MUST NOT store the values when offload is not started and applies once
+ * offload is started. This is because the quota values would likely become stale over
+ * time and would not reflect any new traffic that has occurred.
+ *
+ * This API replaces {@link @1.0::IOffloadControl::setDataLimit}, the framework will always
+ * call setDataWarningAndLimit on 1.1 implementations, and setDataLimit on 1.0 implementations.
+ * Thus, no interaction between the two APIs need to be addressed.
+ *
+ * The specified quota bytes MUST replace any previous quotas set by
+ * {@code setDataWarningAndLimit} specified on the same interface. It may be interpreted as
+ * "tell me when either <warningBytes> or <limitBytes> bytes have been transferred
+ * (in either direction), and stop offload when <limitBytes> bytes have been transferred,
+ * starting now and counting from zero on <upstream>."
+ *
+ * Once the {@code warningBytes} is reached, the callback registered in initOffload must be
+ * called with {@code OFFLOAD_WARNING_REACHED} to indicate this event. Once the event fires
+ * for this upstream, no further {@code OFFLOAD_WARNING_REACHED} event will be fired for this
+ * upstream unless this method is called again with the same interface. Note that there is
+ * no need to call initOffload again to resume offload if stopOffload was not called by the
+ * client.
+ *
+ * Similarly, Once the {@code limitBytes} is reached, the callback registered in initOffload
+ * must be called with {@code OFFLOAD_STOPPED_LIMIT_REACHED} to indicate this event. Once
+ * the event fires for this upstream, no further {@code OFFLOAD_STOPPED_LIMIT_REACHED}
+ * event will be fired for this upstream unless this method is called again with the same
+ * interface. However, unlike {@code warningBytes}, when {@code limitBytes} is reached,
+ * all offload must be stopped. If offload is desired again, the hardware management
+ * process must be completely reprogrammed by calling setUpstreamParameters and
+ * addDownstream again.
+ *
+ * Note that when one of the quota bytes is reached, the other one is still considered valid
+ * unless this method is called again with the same interface.
+ *
+ * @param upstream Upstream interface name that quota must apply to.
+ * @param warningBytes The quota of warning, defined as the number of bytes, starting from
+ * zero and counting from now.
+ * @param limitBytes The quota of limit, defined as the number of bytes, starting from zero
+ * and counting from now.
+ *
+ * @return success true if quota is applied, false otherwise
+ * @return errMsg a human readable string if error has occurred.
+ */
+ setDataWarningAndLimit(string upstream, uint64_t warningBytes, uint64_t limitBytes)
+ generates (bool success, string errMsg);
+};
diff --git a/tetheroffload/control/1.1/ITetheringOffloadCallback.hal b/tetheroffload/control/1.1/ITetheringOffloadCallback.hal
new file mode 100644
index 0000000..7a7d56d
--- /dev/null
+++ b/tetheroffload/control/1.1/ITetheringOffloadCallback.hal
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.tetheroffload.control@1.1;
+
+import @1.0::ITetheringOffloadCallback;
+import OffloadCallbackEvent;
+
+/**
+ * Callback providing information about status of hardware management process
+ * as well as providing a way to keep offloaded connections from timing out.
+ */
+interface ITetheringOffloadCallback extends @1.0::ITetheringOffloadCallback {
+ /**
+ * Called when an asynchronous event is generated by the hardware
+ * management process. Events which are common for 1.0 and 1.1 HAL
+ * MUST be fired on both 1.0 and 1.1 callback.
+ */
+ oneway onEvent_1_1(OffloadCallbackEvent event);
+};
diff --git a/tetheroffload/control/1.1/types.hal b/tetheroffload/control/1.1/types.hal
new file mode 100644
index 0000000..30e6af3
--- /dev/null
+++ b/tetheroffload/control/1.1/types.hal
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.tetheroffload.control@1.1;
+
+import @1.0::OffloadCallbackEvent;
+
+enum OffloadCallbackEvent : @1.0::OffloadCallbackEvent {
+ /**
+ * This event is fired when the quota, applied in setDataWarning, has expired. It is
+ * recommended that the client query for statistics immediately after receiving this event.
+ * Note that hardware acceleration must not be stopped upon receiving this event.
+ */
+ OFFLOAD_WARNING_REACHED = 6,
+};
diff --git a/tetheroffload/control/1.1/vts/functional/Android.bp b/tetheroffload/control/1.1/vts/functional/Android.bp
new file mode 100644
index 0000000..ab29350
--- /dev/null
+++ b/tetheroffload/control/1.1/vts/functional/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_test {
+ name: "VtsHalTetheroffloadControlV1_1TargetTest",
+ defaults: ["VtsHalTargetTestDefaults"],
+ srcs: ["VtsHalTetheroffloadControlV1_1TargetTest.cpp"],
+ local_include_dirs: ["include"],
+ static_libs: [
+ "android.hardware.tetheroffload.config@1.0",
+ "android.hardware.tetheroffload.control@1.0",
+ "android.hardware.tetheroffload.control@1.1",
+ "VtsHalTetheroffloadControlV1_0TargetTest-lib",
+ ],
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
+}
diff --git a/tetheroffload/control/1.1/vts/functional/VtsHalTetheroffloadControlV1_1TargetTest.cpp b/tetheroffload/control/1.1/vts/functional/VtsHalTetheroffloadControlV1_1TargetTest.cpp
new file mode 100644
index 0000000..b8c9e53
--- /dev/null
+++ b/tetheroffload/control/1.1/vts/functional/VtsHalTetheroffloadControlV1_1TargetTest.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <OffloadControlTestV1_1.h>
+#include <gtest/gtest.h>
+#include <hidl/GtestPrinter.h>
+#include <hidl/ServiceManagement.h>
+
+using android::hardware::tetheroffload::control::V1_1::IOffloadControl;
+
+const hidl_string TEST_IFACE("rmnet_data0");
+
+// Check that calling setDataWarningAndLimit() without first having called initOffload() returns
+// false.
+TEST_P(OffloadControlTestV1_1_HalNotStarted, SetDataWarningAndLimitWithoutInitReturnsFalse) {
+ const Return<void> ret = getControlV1_1()->setDataWarningAndLimit(TEST_IFACE, 5000ULL, 5000ULL,
+ ASSERT_FALSE_CALLBACK);
+ EXPECT_TRUE(ret.isOk());
+}
+
+/*
+ * Tests for IOffloadControl::setDataWarningAndLimit().
+ */
+
+// Test that setDataWarningAndLimit() for an empty interface name fails.
+TEST_P(OffloadControlTestV1_1_HalStarted, SetDataWarningAndLimitEmptyUpstreamIfaceFails) {
+ const Return<void> ret = getControlV1_1()->setDataWarningAndLimit(
+ hidl_string(""), 12345ULL, 67890ULL, ASSERT_FALSE_CALLBACK);
+ EXPECT_TRUE(ret.isOk());
+}
+
+// TEST_IFACE is presumed to exist on the device and be up. No packets
+// are ever actually caused to be forwarded.
+TEST_P(OffloadControlTestV1_1_HalStarted, SetDataWarningAndLimitNonZeroOk) {
+ const Return<void> ret = getControlV1_1()->setDataWarningAndLimit(TEST_IFACE, 4000ULL, 5000ULL,
+ ASSERT_TRUE_CALLBACK);
+ EXPECT_TRUE(ret.isOk());
+}
+
+// TEST_IFACE is presumed to exist on the device and be up. No packets
+// are ever actually caused to be forwarded.
+TEST_P(OffloadControlTestV1_1_HalStarted, SetDataWarningAndLimitZeroOk) {
+ const Return<void> ret =
+ getControlV1_1()->setDataWarningAndLimit(TEST_IFACE, 0ULL, 0ULL, ASSERT_TRUE_CALLBACK);
+ EXPECT_TRUE(ret.isOk());
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(OffloadControlTestV1_1_HalNotStarted);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(OffloadControlTestV1_1_HalStarted);
+
+INSTANTIATE_TEST_CASE_P(
+ PerInstance, OffloadControlTestV1_1_HalNotStarted,
+ testing::Combine(testing::ValuesIn(android::hardware::getAllHalInstanceNames(
+ IOffloadConfig::descriptor)),
+ testing::ValuesIn(android::hardware::getAllHalInstanceNames(
+ IOffloadControl::descriptor))),
+ android::hardware::PrintInstanceTupleNameToString<>);
+
+INSTANTIATE_TEST_CASE_P(
+ PerInstance, OffloadControlTestV1_1_HalStarted,
+ testing::Combine(testing::ValuesIn(android::hardware::getAllHalInstanceNames(
+ IOffloadConfig::descriptor)),
+ testing::ValuesIn(android::hardware::getAllHalInstanceNames(
+ IOffloadControl::descriptor))),
+ android::hardware::PrintInstanceTupleNameToString<>);
diff --git a/tetheroffload/control/1.1/vts/functional/include/OffloadControlTestV1_1.h b/tetheroffload/control/1.1/vts/functional/include/OffloadControlTestV1_1.h
new file mode 100644
index 0000000..a3bc1b4
--- /dev/null
+++ b/tetheroffload/control/1.1/vts/functional/include/OffloadControlTestV1_1.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <OffloadControlTestV1_0.h>
+#include <android/hardware/tetheroffload/control/1.1/IOffloadControl.h>
+#include <android/hardware/tetheroffload/control/1.1/ITetheringOffloadCallback.h>
+#include <gtest/gtest.h>
+
+constexpr char kCallbackOnEvent_1_1[] = "onEvent_1_1";
+
+class TetheringOffloadCallbackArgsV1_1 {
+ public:
+ android::hardware::tetheroffload::control::V1_1::OffloadCallbackEvent last_event;
+};
+
+class OffloadControlTestV1_1_HalNotStarted : public OffloadControlTestV1_0_HalNotStarted {
+ public:
+ virtual sp<android::hardware::tetheroffload::control::V1_0::IOffloadControl> createControl(
+ const std::string& serviceName) override {
+ return android::hardware::tetheroffload::control::V1_1::IOffloadControl::getService(
+ serviceName);
+ };
+
+ void prepareControlHal() override {
+ control = createControl(std::get<1>(GetParam()));
+ ASSERT_NE(nullptr, control.get()) << "Could not get HIDL instance";
+
+ control_cb_1_1 = new TetheringOffloadCallbackV1_1();
+ ASSERT_NE(nullptr, control_cb_1_1.get()) << "Could not get offload callback";
+ };
+
+ void initOffload(const bool expected_result) override {
+ auto init_cb = [&](bool success, std::string errMsg) {
+ std::string msg = StringPrintf("Unexpectedly %s to init offload: %s",
+ success ? "succeeded" : "failed", errMsg.c_str());
+ ASSERT_EQ(expected_result, success) << msg;
+ };
+ auto control = getControlV1_1();
+ ASSERT_NE(control, nullptr);
+ const Return<void> ret = control->initOffload(control_cb_1_1, init_cb);
+ ASSERT_TRUE(ret.isOk());
+ };
+
+ sp<android::hardware::tetheroffload::control::V1_1::IOffloadControl> getControlV1_1() {
+ // The cast is safe since only devices with V1.1+ HAL will be enumerated and pass in to the
+ // test.
+ return android::hardware::tetheroffload::control::V1_1::IOffloadControl::castFrom(control)
+ .withDefault(nullptr);
+ };
+
+ // Callback class for both new events.
+ class TetheringOffloadCallbackV1_1
+ : public testing::VtsHalHidlTargetCallbackBase<TetheringOffloadCallbackArgsV1_1>,
+ public android::hardware::tetheroffload::control::V1_1::ITetheringOffloadCallback {
+ public:
+ Return<void> onEvent_1_1(
+ android::hardware::tetheroffload::control::V1_1::OffloadCallbackEvent event)
+ override {
+ const TetheringOffloadCallbackArgsV1_1 args{.last_event = event};
+ NotifyFromCallback(kCallbackOnEvent_1_1, args);
+ return Void();
+ };
+
+ Return<void> onEvent([[maybe_unused]] OffloadCallbackEvent event) override {
+ // Tested only in IOffloadControl 1.0.
+ return Void();
+ };
+
+ Return<void> updateTimeout([[maybe_unused]] const NatTimeoutUpdate& params) override {
+ // Tested only in IOffloadControl 1.0.
+ return Void();
+ };
+ };
+
+ sp<TetheringOffloadCallbackV1_1> control_cb_1_1;
+};
+
+class OffloadControlTestV1_1_HalStarted : public OffloadControlTestV1_1_HalNotStarted {
+ public:
+ virtual void SetUp() override {
+ setupConfigHal();
+ setupControlHal();
+ }
+};
\ No newline at end of file
diff --git a/tv/cec/2.0/Android.bp b/tv/cec/2.0/Android.bp
deleted file mode 100644
index 5463b6d..0000000
--- a/tv/cec/2.0/Android.bp
+++ /dev/null
@@ -1,16 +0,0 @@
-// This file is autogenerated by hidl-gen -Landroidbp.
-
-hidl_interface {
- name: "android.hardware.tv.cec@2.0",
- root: "android.hardware",
- srcs: [
- "types.hal",
- "IHdmiCec.hal",
- "IHdmiCecCallback.hal",
- ],
- interfaces: [
- "android.hidl.base@1.0",
- "android.hidl.safe_union@1.0",
- ],
- gen_java: true,
-}
diff --git a/tv/cec/2.0/IHdmiCec.hal b/tv/cec/2.0/IHdmiCec.hal
deleted file mode 100644
index 0723bad..0000000
--- a/tv/cec/2.0/IHdmiCec.hal
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * Copyright (C) 2019 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.tv.cec@2.0;
-
-import IHdmiCecCallback;
-
-/**
- * HDMI-CEC HAL interface definition.
- */
-interface IHdmiCec {
- /**
- * Passes Primary Device Type that must be used in this system.
- *
- * HAL must use it to allocate logical address as specified in CEC section
- * 11.3.2 of the CEC spec 2.0b. Then CEC commands addressed the given
- * logical address can be filtered in.
- * This method shall be able to be called up to twice to support two Primary
- * Device Type as specified in CEC Table 11-8 of the CEC spec 2.0b.
- *
- * @param deviceType that must be used in this system. It must be a valid
- * value in CecDeviceType for the call to succeed.
- * @return result Result status of the operation. SUCCESS if successful,
- * FAILURE_INVALID_ARGS if the given device type is invalid,
- * FAILURE_BUSY if device or resource is busy
- */
- @callflow(next={"*"})
- addDeviceType(CecDeviceType deviceType) generates (Result result);
-
- /**
- * Clears all Primary Device Types.
- *
- * It is used when the system plan to reconfigure Primary Device Type,
- * hence to tell HAL to release all logical address associated to them,
- * and change the state back to the beginning.
- */
- @callflow(next="addDeviceType")
- @exit
- clearDeviceTypes();
-
- /**
- * Set All Device Types for a Primary Device Type.
- *
- * This value must be used in REPORT_FEATURES message to response
- * GIVE_FEATURES message in HAL.
- *
- * @param allDeviceTypes device all device types for a Primary Device Type.
- */
- @callflow(next="addDeviceType")
- setAllDeviceTypes(CecAllDeviceTypes allDeviceTypes);
-
- /**
- * Set Device Features for a Primary Device Type.
- *
- * This value must be used in REPORT_FEATURES message to response
- * GIVE_FEATURES message in HAL.
- *
- * @param deviceType The device Primary Device Type.
- * @param deviceFeatures device features for a Primary Device Type.
- */
- @callflow(next="addDeviceType")
- setDeviceFeatures(CecDeviceType deviceType,
- CecDeviceFeatures deviceFeatures);
-
- /**
- * Set Remote Control Profile for a Primary Device Type.
- *
- * This value must be used in REPORT_FEATURES message to response
- * GIVE_FEATURES message in HAL.
- *
- * @param deviceType The device Primary Device Type.
- * @param rcProliles remote control profiles for a Primary Device Type.
- */
- @callflow(next="addDeviceType")
- setRcProfile(CecDeviceType deviceType, CecRcProfile rcProfile);
-
- /**
- * Retrieve CEC device information.
- *
- * CEC section 11.3 of the CEC spec 2.0b specify that a device should not
- * ask for static information that another device has already supplied.
- * Therefore, CEC 2.0 software stack need a map to store all cec
- * devices’ information of current CEC network.
- * The device information is broadcasted by a device after it allocates a
- * logical address. Messages used to send out these information are
- * REPORT_FEATURES, REPORT_PHYSICAL_ADDRESS, DEVICE_VENDOR_ID.
- * The spec also requires less than 1 second between REPORT_FEATURES and
- * REPORT_PHYSICAL_ADDRESS message, and less than 2 second between
- * REPORT_PHYSICAL_ADDRESS and DEVICE_VENDOR_ID. An Implementation of
- * device information map in hal can help to meet the timing constraints.
- * Logical addressing is part of the process to build this map, so the
- * implementation shall include allocating logical address too.
- * Whenever a device plug/unplug, the topology of CEC network changes.
- * The hal implementation shall update devices’ information map, and
- * send out onTopologyEvent to Android system. Then Android system
- * will use readDeviceInfo to retreive latest devices’ information of CEC
- * network.
- * If SYSTEM_CEC_CONTROL is false, the hal implementation need continue to
- * maintain and update device information map, and send out pending
- * onTopologyEvent to Android system when SYSTEM_CEC_CONTROL is
- * changed to true.
- *
- * @param logicalAddress logical address of CEC device.
- * @param physicalAddress physical address of CEC device.
- * @return CecDeviceInfo from device information map.
- * @return result Result status of the operation. SUCCESS if successful,
- * FAILURE_INVALID_ARGS if logical or physical address is invalid.
- * FAILURE_INVALID_STATE if device information isn't available yet.
- */
- @callflow(next="onTopologyChangeEvent")
- readDeviceInfo(CecLogicalAddress logicalAddress,
- CecPhysicalAddress physicalAddress)
- generates (Result result, CecDeviceInfo deviceInfo);
-
- /**
- * Transmits HDMI-CEC message to other HDMI device.
- *
- * The method must be designed to return in a certain amount of time and not
- * hanging forever. This method MUST complete with in 1 second.
- *
- * It must try retransmission at least once as specified in the section '7.1
- * Frame Re-transmissions' of the CEC Spec 1.4b.
- *
- * @param message CEC message to be sent to other HDMI device.
- * @return result Result status of the operation. SUCCESS if successful,
- * NACK if the sent message is not acknowledged,
- * BUSY if the CEC bus is busy.
- */
- @callflow(next="*")
- sendMessage(CecMessage message) generates (SendMessageResult result);
-
- /**
- * Set the callback
- *
- * It is used by the framework to receive CecMessages, HDMI hotplug event
- * and topology update event. Only one callback client is supported.
- *
- * @param callback Callback object to pass hdmi events to the system. The
- * previously registered callback must be replaced with this one.
- */
- @callflow(next={"*"})
- @entry
- setCallback(IHdmiCecCallback callback);
-
- /**
- * Gets the hdmi port information of underlying hardware.
- *
- * @return infos The list of HDMI port information
- */
- @callflow(next={"*"})
- getPortInfo() generates (vec<HdmiPortInfo> infos);
-
- /**
- * Sets flags controlling the way HDMI-CEC service works down to HAL
- * implementation. Those flags must be used in case the feature needs update
- * in HAL itself, firmware or microcontroller.
- *
- * @param key The key of the option to be updated with a new value.
- * @param value Value to be set.
- */
- @callflow(next="*")
- setOption(OptionKey key, bool value);
-
- /**
- * Passes the updated language information of Android system. Contains
- * three-letter code as defined in ISO/FDIS 639-2. Must be used for HAL to
- * respond to <Get Menu Language> while in standby mode.
- *
- * @param language Three-letter code defined in ISO/FDIS 639-2. Must be
- * lowercase letters. (e.g., eng for English)
- */
- @callflow(next="*")
- setLanguage(string language);
-
- /**
- * Configures ARC circuit in the hardware logic to start or stop the
- * feature.
- *
- * @param portId Port id to be configured.
- * @param enable Flag must be either true to start the feature or false to
- * stop it.
- */
- @callflow(next="*")
- enableAudioReturnChannel(HdmiPortId portId, bool enable);
-
- /**
- * Gets the connection status of the specified port.
- *
- * It's specified in CEC section 10.8 of the CEC spec 2.0b
- *
- * @param portId Port id to be inspected for the connection status.
- * @return status True if a device is connected, otherwise false.
- */
- @callflow(next="*")
- isConnected(HdmiPortId portId) generates (bool connected);
-};
diff --git a/tv/cec/2.0/IHdmiCecCallback.hal b/tv/cec/2.0/IHdmiCecCallback.hal
deleted file mode 100644
index 1a8a489..0000000
--- a/tv/cec/2.0/IHdmiCecCallback.hal
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2019 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.tv.cec@2.0;
-
-interface IHdmiCecCallback {
- /**
- * The callback function that must be called by HAL implementation to notify
- * the system of new CEC message arrival.
- */
- oneway onCecMessage(CecMessage message);
-
- /**
- * The callback function that must be called by HAL implementation to notify
- * the system of new hotplug event.
- */
- oneway onHotplugEvent(HotplugEvent event);
-
- /**
- * The callback function must be called by HAL implementation to notify the
- * system whenever CEC device information of CEC network change.
- * HAL shall be ready for readDeviceInfo call before invoke this callback.
- * This event is triggered by topology change of whole CEC network. It's
- * different from HotplugEvent which is triggered between devices which are
- * connected directly through HDMI cable.
- */
- oneway onTopologyEvent(CecTopologyEvent event);
-};
diff --git a/tv/cec/2.0/default/Android.bp b/tv/cec/2.0/default/Android.bp
deleted file mode 100644
index d3d5342..0000000
--- a/tv/cec/2.0/default/Android.bp
+++ /dev/null
@@ -1,40 +0,0 @@
-cc_library_shared {
- name: "android.hardware.tv.cec@2.0-impl",
- defaults: ["hidl_defaults"],
- vendor: true,
- relative_install_path: "hw",
- srcs: ["HdmiCec.cpp"],
-
- shared_libs: [
- "libhidlbase",
- "liblog",
- "libbase",
- "libutils",
- "libhardware",
- "android.hardware.tv.cec@2.0",
- ],
-
-}
-
-cc_binary {
- name: "android.hardware.tv.cec@2.0-service",
- vintf_fragments: ["android.hardware.tv.cec@2.0-service.xml"],
- defaults: ["hidl_defaults"],
- relative_install_path: "hw",
- vendor: true,
- init_rc: ["android.hardware.tv.cec@2.0-service.rc"],
- srcs: ["service.cpp"],
-
- shared_libs: [
- "liblog",
- "libcutils",
- "libdl",
- "libbase",
- "libutils",
- "libhardware_legacy",
- "libhardware",
- "libhidlbase",
- "android.hardware.tv.cec@2.0",
- ],
-
-}
diff --git a/tv/cec/2.0/default/HdmiCec.cpp b/tv/cec/2.0/default/HdmiCec.cpp
deleted file mode 100644
index f451719..0000000
--- a/tv/cec/2.0/default/HdmiCec.cpp
+++ /dev/null
@@ -1,421 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "android.hardware.tv.cec@2.0-impl"
-#include <android-base/logging.h>
-
-#include <hardware/hardware.h>
-#include <hardware/hdmi_cec.h>
-#include "HdmiCec.h"
-
-namespace android {
-namespace hardware {
-namespace tv {
-namespace cec {
-namespace V2_0 {
-namespace implementation {
-
-static_assert(CEC_DEVICE_INACTIVE == static_cast<int>(CecDeviceType::INACTIVE),
- "CecDeviceType::INACTIVE must match legacy value.");
-static_assert(CEC_DEVICE_TV == static_cast<int>(CecDeviceType::TV),
- "CecDeviceType::TV must match legacy value.");
-static_assert(CEC_DEVICE_RECORDER == static_cast<int>(CecDeviceType::RECORDER),
- "CecDeviceType::RECORDER must match legacy value.");
-static_assert(CEC_DEVICE_TUNER == static_cast<int>(CecDeviceType::TUNER),
- "CecDeviceType::TUNER must match legacy value.");
-static_assert(CEC_DEVICE_PLAYBACK == static_cast<int>(CecDeviceType::PLAYBACK),
- "CecDeviceType::PLAYBACK must match legacy value.");
-static_assert(CEC_DEVICE_AUDIO_SYSTEM == static_cast<int>(CecDeviceType::AUDIO_SYSTEM),
- "CecDeviceType::AUDIO_SYSTEM must match legacy value.");
-/* TODO: Adjust for cec@2.0
-static_assert(CEC_DEVICE_MAX == static_cast<int>(CecDeviceType::MAX),
- "CecDeviceType::MAX must match legacy value.");
-*/
-static_assert(CEC_ADDR_TV == static_cast<int>(CecLogicalAddress::TV),
- "CecLogicalAddress::TV must match legacy value.");
-static_assert(CEC_ADDR_RECORDER_1 == static_cast<int>(CecLogicalAddress::RECORDER_1),
- "CecLogicalAddress::RECORDER_1 must match legacy value.");
-static_assert(CEC_ADDR_RECORDER_2 == static_cast<int>(CecLogicalAddress::RECORDER_2),
- "CecLogicalAddress::RECORDER_2 must match legacy value.");
-static_assert(CEC_ADDR_TUNER_1 == static_cast<int>(CecLogicalAddress::TUNER_1),
- "CecLogicalAddress::TUNER_1 must match legacy value.");
-static_assert(CEC_ADDR_PLAYBACK_1 == static_cast<int>(CecLogicalAddress::PLAYBACK_1),
- "CecLogicalAddress::PLAYBACK_1 must match legacy value.");
-static_assert(CEC_ADDR_AUDIO_SYSTEM == static_cast<int>(CecLogicalAddress::AUDIO_SYSTEM),
- "CecLogicalAddress::AUDIO_SYSTEM must match legacy value.");
-static_assert(CEC_ADDR_TUNER_2 == static_cast<int>(CecLogicalAddress::TUNER_2),
- "CecLogicalAddress::TUNER_2 must match legacy value.");
-static_assert(CEC_ADDR_TUNER_3 == static_cast<int>(CecLogicalAddress::TUNER_3),
- "CecLogicalAddress::TUNER_3 must match legacy value.");
-static_assert(CEC_ADDR_PLAYBACK_2 == static_cast<int>(CecLogicalAddress::PLAYBACK_2),
- "CecLogicalAddress::PLAYBACK_2 must match legacy value.");
-static_assert(CEC_ADDR_RECORDER_3 == static_cast<int>(CecLogicalAddress::RECORDER_3),
- "CecLogicalAddress::RECORDER_3 must match legacy value.");
-static_assert(CEC_ADDR_TUNER_4 == static_cast<int>(CecLogicalAddress::TUNER_4),
- "CecLogicalAddress::TUNER_4 must match legacy value.");
-static_assert(CEC_ADDR_PLAYBACK_3 == static_cast<int>(CecLogicalAddress::PLAYBACK_3),
- "CecLogicalAddress::PLAYBACK_3 must match legacy value.");
-/* TODO: Adjust for cec@2.0
-static_assert(CEC_ADDR_FREE_USE == static_cast<int>(CecLogicalAddress::FREE_USE),
- "CecLogicalAddress::FREE_USE must match legacy value.");
-*/
-static_assert(CEC_ADDR_UNREGISTERED == static_cast<int>(CecLogicalAddress::UNREGISTERED),
- "CecLogicalAddress::UNREGISTERED must match legacy value.");
-static_assert(CEC_ADDR_BROADCAST == static_cast<int>(CecLogicalAddress::BROADCAST),
- "CecLogicalAddress::BROADCAST must match legacy value.");
-
-static_assert(CEC_MESSAGE_FEATURE_ABORT == static_cast<int>(CecMessageType::FEATURE_ABORT),
- "CecMessageType::FEATURE_ABORT must match legacy value.");
-static_assert(CEC_MESSAGE_IMAGE_VIEW_ON == static_cast<int>(CecMessageType::IMAGE_VIEW_ON),
- "CecMessageType::IMAGE_VIEW_ON must match legacy value.");
-static_assert(CEC_MESSAGE_TUNER_STEP_INCREMENT ==
- static_cast<int>(CecMessageType::TUNER_STEP_INCREMENT),
- "CecMessageType::TUNER_STEP_INCREMENT must match legacy value.");
-static_assert(CEC_MESSAGE_TUNER_STEP_DECREMENT ==
- static_cast<int>(CecMessageType::TUNER_STEP_DECREMENT),
- "CecMessageType::TUNER_STEP_DECREMENT must match legacy value.");
-static_assert(CEC_MESSAGE_TUNER_DEVICE_STATUS ==
- static_cast<int>(CecMessageType::TUNER_DEVICE_STATUS),
- "CecMessageType::TUNER_DEVICE_STATUS must match legacy value.");
-static_assert(CEC_MESSAGE_GIVE_TUNER_DEVICE_STATUS ==
- static_cast<int>(CecMessageType::GIVE_TUNER_DEVICE_STATUS),
- "CecMessageType::GIVE_TUNER_DEVICE_STATUS must match legacy value.");
-static_assert(CEC_MESSAGE_RECORD_ON == static_cast<int>(CecMessageType::RECORD_ON),
- "CecMessageType::RECORD_ON must match legacy value.");
-static_assert(CEC_MESSAGE_RECORD_STATUS == static_cast<int>(CecMessageType::RECORD_STATUS),
- "CecMessageType::RECORD_STATUS must match legacy value.");
-static_assert(CEC_MESSAGE_RECORD_OFF == static_cast<int>(CecMessageType::RECORD_OFF),
- "CecMessageType::RECORD_OFF must match legacy value.");
-static_assert(CEC_MESSAGE_TEXT_VIEW_ON == static_cast<int>(CecMessageType::TEXT_VIEW_ON),
- "CecMessageType::TEXT_VIEW_ON must match legacy value.");
-static_assert(CEC_MESSAGE_RECORD_TV_SCREEN == static_cast<int>(CecMessageType::RECORD_TV_SCREEN),
- "CecMessageType::RECORD_TV_SCREEN must match legacy value.");
-static_assert(CEC_MESSAGE_GIVE_DECK_STATUS == static_cast<int>(CecMessageType::GIVE_DECK_STATUS),
- "CecMessageType::GIVE_DECK_STATUS must match legacy value.");
-static_assert(CEC_MESSAGE_STANDBY == static_cast<int>(CecMessageType::STANDBY),
- "CecMessageType::STANDBY must match legacy value.");
-static_assert(CEC_MESSAGE_PLAY == static_cast<int>(CecMessageType::PLAY),
- "CecMessageType::PLAY must match legacy value.");
-static_assert(CEC_MESSAGE_DECK_CONTROL == static_cast<int>(CecMessageType::DECK_CONTROL),
- "CecMessageType::DECK_CONTROL must match legacy value.");
-static_assert(CEC_MESSAGE_TIMER_CLEARED_STATUS ==
- static_cast<int>(CecMessageType::TIMER_CLEARED_STATUS),
- "CecMessageType::TIMER_CLEARED_STATUS must match legacy value.");
-static_assert(CEC_MESSAGE_USER_CONTROL_PRESSED ==
- static_cast<int>(CecMessageType::USER_CONTROL_PRESSED),
- "CecMessageType::USER_CONTROL_PRESSED must match legacy value.");
-static_assert(CEC_MESSAGE_USER_CONTROL_RELEASED ==
- static_cast<int>(CecMessageType::USER_CONTROL_RELEASED),
- "CecMessageType::USER_CONTROL_RELEASED must match legacy value.");
-static_assert(CEC_MESSAGE_GIVE_OSD_NAME == static_cast<int>(CecMessageType::GIVE_OSD_NAME),
- "CecMessageType::GIVE_OSD_NAME must match legacy value.");
-static_assert(CEC_MESSAGE_SET_OSD_NAME == static_cast<int>(CecMessageType::SET_OSD_NAME),
- "CecMessageType::SET_OSD_NAME must match legacy value.");
-static_assert(CEC_MESSAGE_SYSTEM_AUDIO_MODE_REQUEST ==
- static_cast<int>(CecMessageType::SYSTEM_AUDIO_MODE_REQUEST),
- "CecMessageType::SYSTEM_AUDIO_MODE_REQUEST must match legacy value.");
-static_assert(CEC_MESSAGE_GIVE_AUDIO_STATUS == static_cast<int>(CecMessageType::GIVE_AUDIO_STATUS),
- "CecMessageType::GIVE_AUDIO_STATUS must match legacy value.");
-static_assert(CEC_MESSAGE_SET_SYSTEM_AUDIO_MODE ==
- static_cast<int>(CecMessageType::SET_SYSTEM_AUDIO_MODE),
- "CecMessageType::SET_SYSTEM_AUDIO_MODE must match legacy value.");
-static_assert(CEC_MESSAGE_REPORT_AUDIO_STATUS ==
- static_cast<int>(CecMessageType::REPORT_AUDIO_STATUS),
- "CecMessageType::REPORT_AUDIO_STATUS must match legacy value.");
-static_assert(CEC_MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS ==
- static_cast<int>(CecMessageType::GIVE_SYSTEM_AUDIO_MODE_STATUS),
- "CecMessageType::GIVE_SYSTEM_AUDIO_MODE_STATUS must match legacy value.");
-static_assert(CEC_MESSAGE_SYSTEM_AUDIO_MODE_STATUS ==
- static_cast<int>(CecMessageType::SYSTEM_AUDIO_MODE_STATUS),
- "CecMessageType::SYSTEM_AUDIO_MODE_STATUS must match legacy value.");
-static_assert(CEC_MESSAGE_ROUTING_CHANGE == static_cast<int>(CecMessageType::ROUTING_CHANGE),
- "CecMessageType::ROUTING_CHANGE must match legacy value.");
-static_assert(CEC_MESSAGE_ROUTING_INFORMATION ==
- static_cast<int>(CecMessageType::ROUTING_INFORMATION),
- "CecMessageType::ROUTING_INFORMATION must match legacy value.");
-static_assert(CEC_MESSAGE_ACTIVE_SOURCE == static_cast<int>(CecMessageType::ACTIVE_SOURCE),
- "CecMessageType::ACTIVE_SOURCE must match legacy value.");
-static_assert(CEC_MESSAGE_GIVE_PHYSICAL_ADDRESS ==
- static_cast<int>(CecMessageType::GIVE_PHYSICAL_ADDRESS),
- "CecMessageType::GIVE_PHYSICAL_ADDRESS must match legacy value.");
-static_assert(CEC_MESSAGE_REPORT_PHYSICAL_ADDRESS ==
- static_cast<int>(CecMessageType::REPORT_PHYSICAL_ADDRESS),
- "CecMessageType::REPORT_PHYSICAL_ADDRESS must match legacy value.");
-static_assert(CEC_MESSAGE_REQUEST_ACTIVE_SOURCE ==
- static_cast<int>(CecMessageType::REQUEST_ACTIVE_SOURCE),
- "CecMessageType::REQUEST_ACTIVE_SOURCE must match legacy value.");
-static_assert(CEC_MESSAGE_SET_STREAM_PATH == static_cast<int>(CecMessageType::SET_STREAM_PATH),
- "CecMessageType::SET_STREAM_PATH must match legacy value.");
-static_assert(CEC_MESSAGE_DEVICE_VENDOR_ID == static_cast<int>(CecMessageType::DEVICE_VENDOR_ID),
- "CecMessageType::DEVICE_VENDOR_ID must match legacy value.");
-static_assert(CEC_MESSAGE_VENDOR_COMMAND == static_cast<int>(CecMessageType::VENDOR_COMMAND),
- "CecMessageType::VENDOR_COMMAND must match legacy value.");
-static_assert(CEC_MESSAGE_VENDOR_REMOTE_BUTTON_DOWN ==
- static_cast<int>(CecMessageType::VENDOR_REMOTE_BUTTON_DOWN),
- "CecMessageType::VENDOR_REMOTE_BUTTON_DOWN must match legacy value.");
-static_assert(CEC_MESSAGE_VENDOR_REMOTE_BUTTON_UP ==
- static_cast<int>(CecMessageType::VENDOR_REMOTE_BUTTON_UP),
- "CecMessageType::VENDOR_REMOTE_BUTTON_UP must match legacy value.");
-static_assert(CEC_MESSAGE_GIVE_DEVICE_VENDOR_ID ==
- static_cast<int>(CecMessageType::GIVE_DEVICE_VENDOR_ID),
- "CecMessageType::GIVE_DEVICE_VENDOR_ID must match legacy value.");
-static_assert(CEC_MESSAGE_MENU_REQUEST == static_cast<int>(CecMessageType::MENU_REQUEST),
- "CecMessageType::MENU_REQUEST must match legacy value.");
-static_assert(CEC_MESSAGE_MENU_STATUS == static_cast<int>(CecMessageType::MENU_STATUS),
- "CecMessageType::MENU_STATUS must match legacy value.");
-static_assert(CEC_MESSAGE_GIVE_DEVICE_POWER_STATUS ==
- static_cast<int>(CecMessageType::GIVE_DEVICE_POWER_STATUS),
- "CecMessageType::GIVE_DEVICE_POWER_STATUS must match legacy value.");
-static_assert(CEC_MESSAGE_REPORT_POWER_STATUS ==
- static_cast<int>(CecMessageType::REPORT_POWER_STATUS),
- "CecMessageType::REPORT_POWER_STATUS must match legacy value.");
-static_assert(CEC_MESSAGE_GET_MENU_LANGUAGE == static_cast<int>(CecMessageType::GET_MENU_LANGUAGE),
- "CecMessageType::GET_MENU_LANGUAGE must match legacy value.");
-static_assert(CEC_MESSAGE_SELECT_ANALOG_SERVICE ==
- static_cast<int>(CecMessageType::SELECT_ANALOG_SERVICE),
- "CecMessageType::SELECT_ANALOG_SERVICE must match legacy value.");
-static_assert(CEC_MESSAGE_SELECT_DIGITAL_SERVICE ==
- static_cast<int>(CecMessageType::SELECT_DIGITAL_SERVICE),
- "CecMessageType::SELECT_DIGITAL_SERVICE must match legacy value.");
-static_assert(CEC_MESSAGE_SET_DIGITAL_TIMER == static_cast<int>(CecMessageType::SET_DIGITAL_TIMER),
- "CecMessageType::SET_DIGITAL_TIMER must match legacy value.");
-static_assert(CEC_MESSAGE_CLEAR_DIGITAL_TIMER ==
- static_cast<int>(CecMessageType::CLEAR_DIGITAL_TIMER),
- "CecMessageType::CLEAR_DIGITAL_TIMER must match legacy value.");
-static_assert(CEC_MESSAGE_SET_AUDIO_RATE == static_cast<int>(CecMessageType::SET_AUDIO_RATE),
- "CecMessageType::SET_AUDIO_RATE must match legacy value.");
-static_assert(CEC_MESSAGE_INACTIVE_SOURCE == static_cast<int>(CecMessageType::INACTIVE_SOURCE),
- "CecMessageType::INACTIVE_SOURCE must match legacy value.");
-static_assert(CEC_MESSAGE_CEC_VERSION == static_cast<int>(CecMessageType::CEC_VERSION),
- "CecMessageType::CEC_VERSION must match legacy value.");
-static_assert(CEC_MESSAGE_GET_CEC_VERSION == static_cast<int>(CecMessageType::GET_CEC_VERSION),
- "CecMessageType::GET_CEC_VERSION must match legacy value.");
-static_assert(CEC_MESSAGE_VENDOR_COMMAND_WITH_ID ==
- static_cast<int>(CecMessageType::VENDOR_COMMAND_WITH_ID),
- "CecMessageType::VENDOR_COMMAND_WITH_ID must match legacy value.");
-static_assert(CEC_MESSAGE_CLEAR_EXTERNAL_TIMER ==
- static_cast<int>(CecMessageType::CLEAR_EXTERNAL_TIMER),
- "CecMessageType::CLEAR_EXTERNAL_TIMER must match legacy value.");
-static_assert(CEC_MESSAGE_SET_EXTERNAL_TIMER ==
- static_cast<int>(CecMessageType::SET_EXTERNAL_TIMER),
- "CecMessageType::SET_EXTERNAL_TIMER must match legacy value.");
-static_assert(CEC_MESSAGE_INITIATE_ARC == static_cast<int>(CecMessageType::INITIATE_ARC),
- "CecMessageType::INITIATE_ARC must match legacy value.");
-static_assert(CEC_MESSAGE_REPORT_ARC_INITIATED ==
- static_cast<int>(CecMessageType::REPORT_ARC_INITIATED),
- "CecMessageType::REPORT_ARC_INITIATED must match legacy value.");
-static_assert(CEC_MESSAGE_REPORT_ARC_TERMINATED ==
- static_cast<int>(CecMessageType::REPORT_ARC_TERMINATED),
- "CecMessageType::REPORT_ARC_TERMINATED must match legacy value.");
-static_assert(CEC_MESSAGE_REQUEST_ARC_INITIATION ==
- static_cast<int>(CecMessageType::REQUEST_ARC_INITIATION),
- "CecMessageType::REQUEST_ARC_INITIATION must match legacy value.");
-static_assert(CEC_MESSAGE_REQUEST_ARC_TERMINATION ==
- static_cast<int>(CecMessageType::REQUEST_ARC_TERMINATION),
- "CecMessageType::REQUEST_ARC_TERMINATION must match legacy value.");
-static_assert(CEC_MESSAGE_TERMINATE_ARC == static_cast<int>(CecMessageType::TERMINATE_ARC),
- "CecMessageType::TERMINATE_ARC must match legacy value.");
-static_assert(CEC_MESSAGE_ABORT == static_cast<int>(CecMessageType::ABORT),
- "CecMessageType::ABORT must match legacy value.");
-
-static_assert(ABORT_UNRECOGNIZED_MODE == static_cast<int>(AbortReason::UNRECOGNIZED_MODE),
- "AbortReason::UNRECOGNIZED_MODE must match legacy value.");
-static_assert(ABORT_NOT_IN_CORRECT_MODE == static_cast<int>(AbortReason::NOT_IN_CORRECT_MODE),
- "AbortReason::NOT_IN_CORRECT_MODE must match legacy value.");
-static_assert(ABORT_CANNOT_PROVIDE_SOURCE == static_cast<int>(AbortReason::CANNOT_PROVIDE_SOURCE),
- "AbortReason::CANNOT_PROVIDE_SOURCE must match legacy value.");
-static_assert(ABORT_INVALID_OPERAND == static_cast<int>(AbortReason::INVALID_OPERAND),
- "AbortReason::INVALID_OPERAND must match legacy value.");
-static_assert(ABORT_REFUSED == static_cast<int>(AbortReason::REFUSED),
- "AbortReason::REFUSED must match legacy value.");
-static_assert(ABORT_UNABLE_TO_DETERMINE == static_cast<int>(AbortReason::UNABLE_TO_DETERMINE),
- "AbortReason::UNABLE_TO_DETERMINE must match legacy value.");
-
-static_assert(HDMI_RESULT_SUCCESS == static_cast<int>(SendMessageResult::SUCCESS),
- "SendMessageResult::SUCCESS must match legacy value.");
-static_assert(HDMI_RESULT_NACK == static_cast<int>(SendMessageResult::NACK),
- "SendMessageResult::NACK must match legacy value.");
-static_assert(HDMI_RESULT_BUSY == static_cast<int>(SendMessageResult::BUSY),
- "SendMessageResult::BUSY must match legacy value.");
-static_assert(HDMI_RESULT_FAIL == static_cast<int>(SendMessageResult::FAIL),
- "SendMessageResult::FAIL must match legacy value.");
-
-static_assert(HDMI_INPUT == static_cast<int>(HdmiPortType::INPUT),
- "HdmiPortType::INPUT must match legacy value.");
-static_assert(HDMI_OUTPUT == static_cast<int>(HdmiPortType::OUTPUT),
- "HdmiPortType::OUTPUT must match legacy value.");
-
-static_assert(HDMI_OPTION_WAKEUP == static_cast<int>(OptionKey::WAKEUP),
- "OptionKey::WAKEUP must match legacy value.");
-static_assert(HDMI_OPTION_ENABLE_CEC == static_cast<int>(OptionKey::ENABLE_CEC),
- "OptionKey::ENABLE_CEC must match legacy value.");
-static_assert(HDMI_OPTION_SYSTEM_CEC_CONTROL == static_cast<int>(OptionKey::SYSTEM_CEC_CONTROL),
- "OptionKey::SYSTEM_CEC_CONTROL must match legacy value.");
-
-sp<IHdmiCecCallback> HdmiCec::mCallback = nullptr;
-
-HdmiCec::HdmiCec(hdmi_cec_device_t* device) : mDevice(device) {}
-
-// Methods from ::android::hardware::tv::cec::V2_0::IHdmiCec follow.
-Return<Result> HdmiCec::addDeviceType(CecDeviceType deviceType) {
- // TODO implement
- if (deviceType <= CecDeviceType::MAX) {
- return Result::SUCCESS;
- } else {
- return Result::FAILURE_INVALID_ARGS;
- }
-}
-
-Return<void> HdmiCec::clearDeviceTypes() {
- // TODO implement
- return Void();
-}
-
-Return<void> HdmiCec::setAllDeviceTypes(CecAllDeviceTypes allDeviceTypes) {
- // TODO implement
- if (allDeviceTypes == 1) {
- }
- return Void();
-}
-
-Return<void> HdmiCec::setDeviceFeatures(CecDeviceType deviceType,
- CecDeviceFeatures /* deviceFeatures */) {
- // TODO implement
- if (deviceType != CecDeviceType::MAX) {
- }
- return Void();
-}
-
-Return<void> HdmiCec::setRcProfile(CecDeviceType deviceType, const CecRcProfile& /* rcProfile */) {
- // TODO implement
- if (deviceType != CecDeviceType::MAX) {
- }
- return Void();
-}
-
-Return<void> HdmiCec::readDeviceInfo(CecLogicalAddress logicalAddress,
- CecPhysicalAddress physicalAddress,
- const readDeviceInfo_cb _hidl_cb) {
- // TODO implement
- CecDeviceInfo deviceInfo;
-
- if (logicalAddress == CecLogicalAddress::TV) {
- _hidl_cb(Result::SUCCESS, deviceInfo);
- if (physicalAddress) {
- }
- }
- return Void();
-}
-
-Return<SendMessageResult> HdmiCec::sendMessage(const CecMessage& message) {
- cec_message_t legacyMessage{
- .initiator = static_cast<cec_logical_address_t>(message.initiator),
- .destination = static_cast<cec_logical_address_t>(message.destination),
- .length = message.body.size(),
- };
- for (size_t i = 0; i < message.body.size(); ++i) {
- legacyMessage.body[i] = static_cast<unsigned char>(message.body[i]);
- }
- return static_cast<SendMessageResult>(mDevice->send_message(mDevice, &legacyMessage));
-}
-
-Return<void> HdmiCec::setCallback(const sp<IHdmiCecCallback>& callback) {
- if (mCallback != nullptr) {
- mCallback->unlinkToDeath(this);
- mCallback = nullptr;
- }
-
- if (callback != nullptr) {
- mCallback = callback;
- mCallback->linkToDeath(this, 0 /*cookie*/);
- mDevice->register_event_callback(mDevice, eventCallback, nullptr);
- }
- return Void();
-}
-
-Return<void> HdmiCec::getPortInfo(getPortInfo_cb _hidl_cb) {
- struct hdmi_port_info* legacyPorts;
- int numPorts;
- hidl_vec<HdmiPortInfo> portInfos;
- mDevice->get_port_info(mDevice, &legacyPorts, &numPorts);
- portInfos.resize(numPorts);
- for (int i = 0; i < numPorts; ++i) {
- portInfos[i] = {.type = static_cast<HdmiPortType>(legacyPorts[i].type),
- .portId = static_cast<HdmiPortId>(legacyPorts[i].port_id),
- .cecSupported = legacyPorts[i].cec_supported != 0,
- .arcSupported = legacyPorts[i].arc_supported != 0,
- .physicalAddress = legacyPorts[i].physical_address};
- }
- _hidl_cb(portInfos);
- return Void();
-}
-
-Return<void> HdmiCec::setOption(OptionKey key, bool value) {
- mDevice->set_option(mDevice, static_cast<int>(key), value ? 1 : 0);
- return Void();
-}
-
-Return<void> HdmiCec::setLanguage(const hidl_string& language) {
- if (language.size() != 3) {
- LOG(ERROR) << "Wrong language code: expected 3 letters, but it was " << language.size()
- << ".";
- return Void();
- }
- const char* languageStr = language.c_str();
- int convertedLanguage = ((languageStr[0] & 0xFF) << 16) | ((languageStr[1] & 0xFF) << 8) |
- (languageStr[2] & 0xFF);
- mDevice->set_option(mDevice, HDMI_OPTION_SET_LANG, convertedLanguage);
- return Void();
-}
-
-Return<void> HdmiCec::enableAudioReturnChannel(HdmiPortId portId, bool enable) {
- mDevice->set_audio_return_channel(mDevice, portId, enable ? 1 : 0);
- return Void();
-}
-
-Return<bool> HdmiCec::isConnected(HdmiPortId portId) {
- return mDevice->is_connected(mDevice, portId) > 0;
-}
-
-IHdmiCec* HIDL_FETCH_IHdmiCec(const char* hal) {
- hdmi_cec_device_t* hdmi_cec_device;
- int ret = 0;
- const hw_module_t* hw_module = nullptr;
-
- ret = hw_get_module(HDMI_CEC_HARDWARE_MODULE_ID, &hw_module);
- if (ret == 0) {
- ret = hdmi_cec_open(hw_module, &hdmi_cec_device);
- if (ret != 0) {
- LOG(ERROR) << "hdmi_cec_open " << hal << " failed: " << ret;
- }
- } else {
- LOG(ERROR) << "hw_get_module " << hal << " failed: " << ret;
- }
-
- if (ret == 0) {
- return new HdmiCec(hdmi_cec_device);
- } else {
- LOG(ERROR) << "Passthrough failed to load legacy HAL.";
- return nullptr;
- }
-}
-
-} // namespace implementation
-} // namespace V2_0
-} // namespace cec
-} // namespace tv
-} // namespace hardware
-} // namespace android
diff --git a/tv/cec/2.0/default/HdmiCec.h b/tv/cec/2.0/default/HdmiCec.h
deleted file mode 100644
index ab54770..0000000
--- a/tv/cec/2.0/default/HdmiCec.h
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_HARDWARE_TV_CEC_V2_0_HDMICEC_H
-#define ANDROID_HARDWARE_TV_CEC_V2_0_HDMICEC_H
-
-#include <algorithm>
-
-#include <android/hardware/tv/cec/2.0/IHdmiCec.h>
-#include <hardware/hardware.h>
-#include <hardware/hdmi_cec.h>
-#include <hidl/Status.h>
-
-#include <hidl/MQDescriptor.h>
-namespace android {
-namespace hardware {
-namespace tv {
-namespace cec {
-namespace V2_0 {
-namespace implementation {
-
-using ::android::sp;
-using ::android::hardware::hidl_string;
-using ::android::hardware::hidl_vec;
-using ::android::hardware::Return;
-using ::android::hardware::Void;
-using ::android::hardware::tv::cec::V2_0::CecLogicalAddress;
-using ::android::hardware::tv::cec::V2_0::CecMessage;
-using ::android::hardware::tv::cec::V2_0::CecPhysicalAddress;
-using ::android::hardware::tv::cec::V2_0::HdmiPortId;
-using ::android::hardware::tv::cec::V2_0::HdmiPortInfo;
-using ::android::hardware::tv::cec::V2_0::IHdmiCec;
-using ::android::hardware::tv::cec::V2_0::IHdmiCecCallback;
-using ::android::hardware::tv::cec::V2_0::MaxLength;
-using ::android::hardware::tv::cec::V2_0::OptionKey;
-using ::android::hardware::tv::cec::V2_0::Result;
-using ::android::hardware::tv::cec::V2_0::SendMessageResult;
-
-struct HdmiCec : public IHdmiCec, public hidl_death_recipient {
- HdmiCec(hdmi_cec_device_t* device);
- // Methods from ::android::hardware::tv::cec::V2_0::IHdmiCec follow.
- Return<Result> addDeviceType(CecDeviceType deviceType) override;
- Return<void> clearDeviceTypes() override;
- Return<void> setAllDeviceTypes(CecAllDeviceTypes allDeviceTypes) override;
- Return<void> setDeviceFeatures(CecDeviceType deviceType,
- CecDeviceFeatures /* deviceFeatures */) override;
- Return<void> setRcProfile(CecDeviceType deviceType,
- const CecRcProfile& /* rcProfile */) override;
- Return<void> readDeviceInfo(CecLogicalAddress logicalAddress,
- CecPhysicalAddress physicalAddress,
- const readDeviceInfo_cb _hidl_cb) override;
- Return<SendMessageResult> sendMessage(const CecMessage& message) override;
- Return<void> setCallback(const sp<IHdmiCecCallback>& callback) override;
- Return<void> getPortInfo(getPortInfo_cb _hidl_cb) override;
- Return<void> setOption(OptionKey key, bool value) override;
- Return<void> setLanguage(const hidl_string& language) override;
- Return<void> enableAudioReturnChannel(HdmiPortId portId, bool enable) override;
- Return<bool> isConnected(HdmiPortId portId) override;
-
- static void eventCallback(const hdmi_event_t* event, void* /* arg */) {
- if (mCallback != nullptr && event != nullptr) {
- if (event->type == HDMI_EVENT_CEC_MESSAGE) {
- size_t length =
- std::min(event->cec.length, static_cast<size_t>(MaxLength::MESSAGE_BODY));
- CecMessage cecMessage{
- .initiator = static_cast<CecLogicalAddress>(event->cec.initiator),
- .destination = static_cast<CecLogicalAddress>(event->cec.destination),
- };
- cecMessage.body.resize(length);
- for (size_t i = 0; i < length; ++i) {
- cecMessage.body[i] = static_cast<uint8_t>(event->cec.body[i]);
- }
- mCallback->onCecMessage(cecMessage);
- } else if (event->type == HDMI_EVENT_HOT_PLUG) {
- HotplugEvent hotplugEvent{
- .connected = event->hotplug.connected > 0,
- .portId = static_cast<HdmiPortId>(event->hotplug.port_id)};
- mCallback->onHotplugEvent(hotplugEvent);
- }
- }
- }
-
- virtual void serviceDied(uint64_t /*cookie*/,
- const wp<::android::hidl::base::V1_0::IBase>& /*who*/) {
- setCallback(nullptr);
- }
-
- private:
- static sp<IHdmiCecCallback> mCallback;
- const hdmi_cec_device_t* mDevice;
-};
-
-extern "C" IHdmiCec* HIDL_FETCH_IHdmiCec(const char* name);
-
-} // namespace implementation
-} // namespace V2_0
-} // namespace cec
-} // namespace tv
-} // namespace hardware
-} // namespace android
-
-#endif // ANDROID_HARDWARE_TV_CEC_V2_0_HDMICEC_H
diff --git a/tv/cec/2.0/default/OWNERS b/tv/cec/2.0/default/OWNERS
deleted file mode 100644
index 1b3d095..0000000
--- a/tv/cec/2.0/default/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-nchalko@google.com
-amyjojo@google.com
-shubang@google.com
-quxiangfang@google.com
diff --git a/tv/cec/2.0/default/android.hardware.tv.cec@2.0-service.rc b/tv/cec/2.0/default/android.hardware.tv.cec@2.0-service.rc
deleted file mode 100644
index 1e8cd80..0000000
--- a/tv/cec/2.0/default/android.hardware.tv.cec@2.0-service.rc
+++ /dev/null
@@ -1,4 +0,0 @@
-service vendor.cec-hal-2-0 /vendor/bin/hw/android.hardware.tv.cec@2.0-service
- class hal
- user system
- group system
diff --git a/tv/cec/2.0/default/android.hardware.tv.cec@2.0-service.xml b/tv/cec/2.0/default/android.hardware.tv.cec@2.0-service.xml
deleted file mode 100644
index 61fb1bb..0000000
--- a/tv/cec/2.0/default/android.hardware.tv.cec@2.0-service.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<manifest version="1.0" type="device">
- <hal format="hidl">
- <name>android.hardware.tv.cec</name>
- <transport>hwbinder</transport>
- <version>2.0</version>
- <interface>
- <name>IHdmiCec</name>
- <instance>default</instance>
- </interface>
- </hal>
-</manifest>
diff --git a/tv/cec/2.0/default/service.cpp b/tv/cec/2.0/default/service.cpp
deleted file mode 100644
index dacc38c..0000000
--- a/tv/cec/2.0/default/service.cpp
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "android.hardware.tv.cec@2.0-service"
-
-#include <android/hardware/tv/cec/2.0/IHdmiCec.h>
-#include <hidl/LegacySupport.h>
-
-using android::hardware::defaultPassthroughServiceImplementation;
-using android::hardware::tv::cec::V2_0::IHdmiCec;
-
-int main() {
- return defaultPassthroughServiceImplementation<IHdmiCec>();
-}
diff --git a/tv/cec/2.0/types.hal b/tv/cec/2.0/types.hal
deleted file mode 100644
index cad6c39..0000000
--- a/tv/cec/2.0/types.hal
+++ /dev/null
@@ -1,548 +0,0 @@
-/*
- * Copyright (C) 2019 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.tv.cec@2.0;
-
-import android.hidl.safe_union@1.0;
-
-/**
- * CEC device type as specified in CEC Table 11-7 of the CEC spec 2.0b.
- */
-enum CecDeviceType : int32_t {
- INACTIVE = -1,
- TV = 0,
- RECORDER = 1,
- TUNER = 3,
- PLAYBACK = 4,
- AUDIO_SYSTEM = 5,
- PURE_CEC_SWITCH = 6,
- PROCESSOR = 7,
- MAX = PROCESSOR,
-};
-
-/**
- * CEC logical address as specified in CEC Table 11-9 of the CEC spec 2.0b.
- */
-enum CecLogicalAddress : int32_t {
- TV = 0,
- RECORDER_1 = 1,
- RECORDER_2 = 2,
- TUNER_1 = 3,
- PLAYBACK_1 = 4,
- AUDIO_SYSTEM = 5,
- TUNER_2 = 6,
- TUNER_3 = 7,
- PLAYBACK_2 = 8,
- RECORDER_3 = 9,
- TUNER_4 = 10,
- PLAYBACK_3 = 11,
- BACKUP_1 = 12, // backup1 for Playback/Recording/Tuner/Processor device
- BACKUP_2 = 13, // backup2 for Playback/Recording/Tuner/Processor device
- SPECIFIC_USE = 14,
- UNREGISTERED = 15, // as Initiator address
- BROADCAST = 15, // as Destination address
-};
-
-/**
- * HDMI CEC message types.
- *
- * The assigned values represent opcode used in CEC frame as specified in
- * Section 11.10 of the CEC spec 2.0b on top of Section CEC 15 of the CEC
- * Spec 1.4b.
- */
-enum CecMessageType : int32_t {
- FEATURE_ABORT = 0x00,
- IMAGE_VIEW_ON = 0x04,
- TUNER_STEP_INCREMENT = 0x05,
- TUNER_STEP_DECREMENT = 0x06,
- TUNER_DEVICE_STATUS = 0x07,
- GIVE_TUNER_DEVICE_STATUS = 0x08,
- RECORD_ON = 0x09,
- RECORD_STATUS = 0x0A,
- RECORD_OFF = 0x0B,
- TEXT_VIEW_ON = 0x0D,
- RECORD_TV_SCREEN = 0x0F,
- GIVE_DECK_STATUS = 0x1A,
- DECK_STATUS = 0x1B,
- SET_MENU_LANGUAGE = 0x32,
- CLEAR_ANALOG_TIMER = 0x33,
- SET_ANALOG_TIMER = 0x34,
- TIMER_STATUS = 0x35,
- STANDBY = 0x36,
- PLAY = 0x41,
- DECK_CONTROL = 0x42,
- TIMER_CLEARED_STATUS = 0x43,
- USER_CONTROL_PRESSED = 0x44,
- USER_CONTROL_RELEASED = 0x45,
- GIVE_OSD_NAME = 0x46,
- SET_OSD_NAME = 0x47,
- SET_OSD_STRING = 0x64,
- SET_TIMER_PROGRAM_TITLE = 0x67,
- SYSTEM_AUDIO_MODE_REQUEST = 0x70,
- GIVE_AUDIO_STATUS = 0x71,
- SET_SYSTEM_AUDIO_MODE = 0x72,
- REPORT_AUDIO_STATUS = 0x7A,
- GIVE_SYSTEM_AUDIO_MODE_STATUS = 0x7D,
- SYSTEM_AUDIO_MODE_STATUS = 0x7E,
- ROUTING_CHANGE = 0x80,
- ROUTING_INFORMATION = 0x81,
- ACTIVE_SOURCE = 0x82,
- GIVE_PHYSICAL_ADDRESS = 0x83,
- REPORT_PHYSICAL_ADDRESS = 0x84,
- REQUEST_ACTIVE_SOURCE = 0x85,
- SET_STREAM_PATH = 0x86,
- DEVICE_VENDOR_ID = 0x87,
- VENDOR_COMMAND = 0x89,
- VENDOR_REMOTE_BUTTON_DOWN = 0x8A,
- VENDOR_REMOTE_BUTTON_UP = 0x8B,
- GIVE_DEVICE_VENDOR_ID = 0x8C,
- MENU_REQUEST = 0x8D,
- MENU_STATUS = 0x8E,
- GIVE_DEVICE_POWER_STATUS = 0x8F,
- REPORT_POWER_STATUS = 0x90,
- GET_MENU_LANGUAGE = 0x91,
- SELECT_ANALOG_SERVICE = 0x92,
- SELECT_DIGITAL_SERVICE = 0x93,
- SET_DIGITAL_TIMER = 0x97,
- CLEAR_DIGITAL_TIMER = 0x99,
- SET_AUDIO_RATE = 0x9A,
- INACTIVE_SOURCE = 0x9D,
- CEC_VERSION = 0x9E,
- GET_CEC_VERSION = 0x9F,
- VENDOR_COMMAND_WITH_ID = 0xA0,
- CLEAR_EXTERNAL_TIMER = 0xA1,
- SET_EXTERNAL_TIMER = 0xA2,
- REPORT_SHORT_AUDIO_DESCRIPTOR = 0xA3,
- REQUEST_SHORT_AUDIO_DESCRIPTOR = 0xA4,
- GIVE_FEATURES = 0XA5,
- REPORT_FEATURES = 0xA6,
- REQUEST_CURRENT_LATENCY = 0xA7,
- REPORT_CURRENT_LATENCY = 0xA8,
- INITIATE_ARC = 0xC0,
- REPORT_ARC_INITIATED = 0xC1,
- REPORT_ARC_TERMINATED = 0xC2,
- REQUEST_ARC_INITIATION = 0xC3,
- REQUEST_ARC_TERMINATION = 0xC4,
- TERMINATE_ARC = 0xC5,
- ABORT = 0xFF,
- POLLING_MESSAGE = 0xFFFFFF00, // used for cec polling message
-};
-
-/**
- * Abort Reason as specified in CEC Table 29 of the CEC spec 1.4b.
- */
-enum AbortReason : int32_t {
- UNRECOGNIZED_MODE = 0,
- NOT_IN_CORRECT_MODE = 1,
- CANNOT_PROVIDE_SOURCE = 2,
- INVALID_OPERAND = 3,
- REFUSED = 4,
- UNABLE_TO_DETERMINE = 5,
-};
-
-enum MaxLength : int32_t {
- MESSAGE_BODY = 14,
-};
-
-struct CecMessage {
- /** logical address of sender */
- CecLogicalAddress initiator;
-
- /** logical address of receiver */
- CecLogicalAddress destination;
-
- /** cec message type */
- CecMessageType cecMessageType;
-
- /**
- * The maximum size of body is 14 (MaxLength::MESSAGE_BODY) as specified in
- * the section 6 of the CEC Spec 1.4b. Overflowed data must be ignored.
- */
- vec<uint8_t> body;
-};
-
-/**
- * error code used for send_message.
- */
-enum SendMessageResult : int32_t {
- SUCCESS = 0,
- NACK = 1, // not acknowledged
- BUSY = 2, // bus is busy
- FAIL = 3,
-};
-
-/**
- * CEC All Device Type Value as specified in Table 11-30 of the CEC spec 2.0b.
- */
-enum CecAllDeviceTypeValue : uint8_t {
- RESERVED_DEVICE_2 = 1 << 0,
- RESERVED_DEVICE_1 = 1 << 1,
- CEC_SWITCH_DEVICE = 1 << 2,
- AUDIO_DEVICE = 1 << 3,
- PLAYBACK_DEVICE = 1 << 4,
- TUNER_DEVICE = 1 << 5,
- RECORDING_DEVICE = 1 << 6,
- TV_DEVICE = 1 << 7,
-};
-
-/**
- * CEC All Device Types
- *
- * It is a combination of all supported type from CecAllDeviceTypeValue.
- * For example a record with tuner functionalitye,
- * cecAllDeviceTypes = ((CecAllDeviceTypeValue::RECORDING_DEVICE)
- * |(CecAllDeviceTypeValue::TUNER_DEVICE))
- */
-typedef bitfield<CecAllDeviceTypeValue> CecAllDeviceTypes;
-
-/**
- * CEC Versions as specified in CEC Table 11-30 of the CEC spec 2.0b.
- */
-enum CecVersion : int32_t {
- V_1_3_A = 0x04,
- V_1_4 = 0x05, // indicate CEC 1.4, 1.4a or 1.4b
- V_2_0 = 0x06,
-};
-
-/**
- * Device Feature
- *
- * It is specified in CEC Table 11-30 of the CEC spec 2.0b. As a uint32 there
- * is room for future extensions aka DeviceFeature2 through DeviceFeature4.
- */
-enum CecDeviceFeature : uint32_t {
- RESERVED = 1 << 0,
- SOURCE_SUPPORT_ARC_RX = 1 << 1,
- SINK_SUPPORT_ARC_TX = 1 << 2,
- SOURCE_SUPPORT_SET_AUDIO_RATE = 1 << 3,
- SUPPORT_CONTROLLED_BY_DECK = 1 << 4,
- TV_SUPPORT_SET_OSD_STRINGS = 1 << 5,
- TV_SUPPORT_RECORD_TV_SCREEN = 1 << 6,
-};
-
-/**
- * CEC Device Features
- *
- * It is a combination of all supported features from CecDeviceFeature.
- * For example a TV with OSD and ARC capabilities,
- * CecDeviceFeatures = ((CecDeviceFeature::TV_SUPPORT_SET_OSD_STRINGS)
- * |(CecDeviceFeature::SINK_SUPPORT_ARC_TX))
- */
-typedef bitfield<CecDeviceFeature> CecDeviceFeatures;
-
-/**
- * Remote Control Profile
- *
- * It is specified in CEC Table 11-30 of the CEC spec 2.0b.
- */
-enum CecRcProfileId : uint8_t {
- NONE = 0, // TV doesn’t support any of these profiles
- RC_PROFILE_1 = 0x02, // minimalistic zapper (low button count)
- RC_PROFILE_2 = 0x06, // intermediate between profile 1 and profile 3
- RC_PROFILE_3 = 0x0A, // typical TV remote
- RC_PROFILE_4 = 0x0E, // extended form of profile 3
-};
-
-/**
- * Remote Control Profile Source
- *
- * It is specified in CEC Table 11-30 of the CEC spec 2.0b.
- */
-enum CecRcProfileSource : uint8_t {
- MEDIA_CONTEXT_SENSITIVE = 1 << 0, // source can handle UI command 0x11
- MEDIA_TO = 1 << 1, // source can handle UI command 0x10
- CONTENTS = 1 << 2, // source can handle UI command 0x0B
- DEVICE_SETUP = 1 << 3, // source can handle UI command 0x0A
- DEVICE_ROOT = 1 << 4, // source can handle UI command 0x09
- SOURCE_FLAG = 1 << 6, // Indicate the profile is for source
-};
-
-/**
- * Remote Control Profile for either TV or Source.
- */
-safe_union CecRcProfile1 {
- /** CEC remote control profile for TV. */
- CecRcProfileId profileId;
-
- /* CEC remote control profile for source
- *
- * It is a combination of all supported profiles from CecRcProfileSource.
- * For example a playback device support root menu and setup menu,
- * profileSource = ((CecRcProfileSource::DEVICE_ROOT)
- * |(CecRcProfileSource::DEVICE_SETUP)
- * |(CecRcProfileSource::SOURCE_FLAG))
- */
- bitfield<CecRcProfileSource> profileSource;
-};
-
-/**
- * CEC Remote Control Profiles
- *
- * CEC 2.0 only use one byte to represent Remote Control Profile.
- */
-struct CecRcProfile {
- CecRcProfile1 rcProfile1;
-};
-
-/**
- * CEC device power states as specified in CEC Table 11-10 of the CEC spec 2.0b
- */
-enum CecPowerState : int8_t {
- ON = 0,
- STANDBY = 1,
- ON_TO_STANDBY = 2,
- STANDBY_TO_ON = 4,
- UNKNOWN = 0xFF, // some devices may not report power status
-};
-
-/** CEC physical address of device */
-typedef uint16_t CecPhysicalAddress;
-
-/**
- * CEC device information
- *
- * It is initially built during addressing specified in CEC section 11.3 of
- * the CEC spec 2.0b. It may be updated with cec devices's status changed.
- */
-struct CecDeviceInfo {
- /** CEC version which device supports */
- CecVersion version;
-
- /** CEC device primary type */
- CecDeviceType devceType;
-
- /** CEC all device types */
- CecAllDeviceTypes allDeviceTypes;
-
- /** CEC device features */
- CecDeviceFeatures deviceFeatures;
-
- /** CEC Device Remote Control Profile */
- CecRcProfile rcProfile;
-
- /** CEC Device Vendor ID */
- uint32_t vendorId;
-
- /** logical address of device */
- CecLogicalAddress logicalAddress;
-
- /** physical of device */
- CecPhysicalAddress physicalAddress;
-
- /** power status of device */
- CecPowerState powerState;
-};
-
-/**
- * Topology Event Type.
- */
-enum CecTopologyEventType : int32_t {
- DEVICE_ADDED,
- DEVICE_REMOVED,
- DEVICE_UPDATED,
-};
-
-/**
- * Topology Event.
- */
-struct CecTopologyEvent {
- CecTopologyEventType eventType;
- CecLogicalAddress logicalAddress;
- CecPhysicalAddress physicalAddress;
-
- /** true if the event is about the device which the system run on */
- bool isHostDevice;
-};
-
-
-/**
- * CEC UI Command Codes as specified in CEC Table 11-31 of the CEC spec 2.0b
- */
-enum CecUICommandCodes : int32_t {
- SELECT_OK = 0x00,
- UP = 0x01,
- DOWN = 0x02,
- LEFT = 0x03,
- RIGHT = 0x04,
- RIGHT_UP = 0x05,
- RIGHT_DOWN = 0x06,
- LEFT_UP = 0x07,
- LEFT_DOWN = 0x08,
- DEVICE_ROOT_MENU = 0x09,
- DEVICE_SETUP_MENU = 0x0A,
- CONTENTS_MENU = 0x0B,
- FAVORITE_MENU = 0x0C,
- BACK = 0x0D,
- MEDIA_TOP_MENU = 0x10,
- MEDIA_CONTEXT_SENSITIVE_MENU = 0x11,
- NUMBER_ENTRY_MODE = 0x1D,
- NUMBER_11 = 0x1E,
- NUMBER_12 = 0x1F,
- NUMBER_0 = 0x20, // or NUMBER 10
- NUMBER_1 = 0x21,
- NUMBER_2 = 0x22,
- NUMBER_3 = 0x23,
- NUMBER_4 = 0x24,
- NUMBER_5 = 0x25,
- NUMBER_6 = 0x26,
- NUMBER_7 = 0x27,
- NUMBER_8 = 0x28,
- NUMBER_9 = 0x29,
- DOT = 0x2A,
- ENTER = 0x2B,
- CLEAR = 0x2C,
- NEXT_FAVORITE = 0x2F,
- CHANNEL_UP = 0x30,
- CHANNEL_DOWN = 0x31,
- PREVIOUS_CHANNEL = 0x32,
- SOUND_SELECT = 0x33,
- INPUT_SELECT = 0x34,
- DISPLAY_INFORMATION = 0x35,
- HELP = 0x36,
- PAGE_UP = 0x37,
- PAGE_DOWN = 0x38,
- POWER = 0x40,
- VOLUME_UP = 0x41,
- VOLUME_DOWN = 0x42,
- MUTE = 0x43,
- PLAY = 0x44,
- STOP = 0x45,
- PAUSE = 0x46,
- RECORD = 0x47,
- REWIND = 0x48,
- FAST_FORWARD = 0x49,
- EJECT = 0x4A,
- SKIP_FORWARD = 0x4B,
- SKIP_BACKWARD = 0x4C,
- STOP_RECORD = 0x4D,
- PAUSE_RECORD = 0x4E,
- ANGLE = 0x50,
- SUB_PICTURE = 0x51,
- VIDEO_ON_DEMAND = 0x52,
- ELECTRONIC_PROGRAM_GUIDE = 0x53,
- TIMER_PROGRAMMING = 0x54,
- INITIAL_CONFIGURATION = 0x55,
- SELECT_BROADCAST_TYPE = 0x56,
- SELECT_SOUND_PRESENTATION = 0x57,
- AUDIO_DESCRIPTION = 0x58,
- INTERNET = 0x59,
- THREE_DIMENSIONAL_MODE = 0x5A,
- PLAY_FUNCTION = 0x60,
- PAUSE_PLAY_FUNCTION = 0x61,
- RECORD_FUNCTION = 0x62,
- PAUSE_RECORD_FUNCTION = 0x63,
- STOP_FUNCTION = 0x64,
- MUTE_FUNCTION = 0x65,
- RESTORE_VOLUME_FUNCTION = 0x66,
- TUNE_FUNCTION = 0x67,
- SELECT_MEDIA_FUNCTION = 0x68,
- SELECT_AV_INPUT_FUNCTION = 0x69,
- SELECT_AUDIO_INPUT_FUNCTION = 0x6A,
- POWER_TOGGLE_FUNCTION = 0x6B,
- POWER_OFF_FUNCTION = 0x6C,
- POWER_ON_FUNCTION = 0x6D,
- F1 = 0x71, // BLUE
- F2 = 0x72, // RED
- F3 = 0x73, // GREEN
- F4 = 0x74, // YELLOW
- F5 = 0x75,
- DATA = 0x76,
-};
-
-/**
- * HDMI port type.
- */
-enum HdmiPortType : int32_t {
- INPUT = 0,
- OUTPUT = 1,
-};
-
-/**
- * Options used for IHdmiCec.setOption()
- */
-enum OptionKey : int32_t {
- /**
- * When set to false, HAL does not wake up the system upon receiving <Image
- * View On> or <Text View On>. Used when user changes the TV settings to
- * disable the auto TV on functionality.
- * Deprecated since <Image View On> and <Text View On> become mandatory
- * featrues for CEC device. Use ENABLE_CEC OptionKey to disable CEC
- * functionality instead.
- * True by Default
- */
- WAKEUP = 1,
-
- /**
- * When set to false, all the CEC commands are discarded. if logical address
- * is ever used, it shall be released. Used when user changes the TV
- * settings to disable CEC functionality.
- * True by default.
- *
- */
- ENABLE_CEC = 2,
-
- /**
- * Setting this flag to false means Android system must stop handling CEC
- * service and yield the control over to the microprocessor that is powered
- * on through the standby mode.The microprocessor shall keep current logical
- * and physical address. It shall response POLLING_MESSAGE, GIVE_FEATURES,
- * GIVE_DEVICE_POWER_STATUS,GIVE_DEVICE_VENDOR_ID and GIVE_PHYSICAL_ADDRESS
- * to allow other CEC devices to build CEC devices map specified in CEC
- * section 11.3 of the CEC spec 2.0b.
- * When set to true, the system must gain the control over, hence telling
- * the microprocessor to start forwarding CEC messages to Android system.
- * For example, this may be called when system goes in and out of
- * standby mode to notify the microprocessor that it should start/stop
- * handling CEC commands on behalf of the system.
- * True by default.
- */
- SYSTEM_CEC_CONTROL = 3,
-
- /* Option 4 not used */
-};
-
-/**
- * Hdmi port ID.
- *
- * It shall start from 1 which corresponds to HDMI "port 1".
- */
-typedef uint32_t HdmiPortId;
-
-/** Hdmi hotplug event */
-struct HotplugEvent {
- bool connected;
- HdmiPortId portId;
-};
-
-/**
- * HDMI port descriptor
- */
-struct HdmiPortInfo {
- HdmiPortType type;
- HdmiPortId portId;
- bool cecSupported;
- bool arcSupported;
- CecPhysicalAddress physicalAddress;
-};
-
-enum Result : int32_t {
- SUCCESS = 0,
- FAILURE_UNKNOWN = 1,
- FAILURE_INVALID_ARGS = 2,
- FAILURE_INVALID_STATE = 3,
- FAILURE_NOT_SUPPORTED = 4,
- FAILURE_BUSY = 5,
-};
diff --git a/tv/input/1.0/default/TvInput.cpp b/tv/input/1.0/default/TvInput.cpp
index 4ea1dec..7583a67 100644
--- a/tv/input/1.0/default/TvInput.cpp
+++ b/tv/input/1.0/default/TvInput.cpp
@@ -142,7 +142,7 @@
// static
void TvInput::notify(struct tv_input_device* __unused, tv_input_event_t* event,
- void* __unused) {
+ void* optionalStatus) {
if (mCallback != nullptr && event != nullptr) {
// Capturing is no longer supported.
if (event->type >= TV_INPUT_EVENT_CAPTURE_SUCCEEDED) {
@@ -154,7 +154,17 @@
tvInputEvent.deviceInfo.type = static_cast<TvInputType>(
event->device_info.type);
tvInputEvent.deviceInfo.portId = event->device_info.hdmi.port_id;
- tvInputEvent.deviceInfo.cableConnectionStatus = CableConnectionStatus::UNKNOWN;
+ CableConnectionStatus connectionStatus = CableConnectionStatus::UNKNOWN;
+ if (optionalStatus != nullptr &&
+ ((event->type == TV_INPUT_EVENT_STREAM_CONFIGURATIONS_CHANGED) ||
+ (event->type == TV_INPUT_EVENT_DEVICE_AVAILABLE))) {
+ int newStatus = *reinterpret_cast<int*>(optionalStatus);
+ if (newStatus <= static_cast<int>(CableConnectionStatus::DISCONNECTED) &&
+ newStatus >= static_cast<int>(CableConnectionStatus::UNKNOWN)) {
+ connectionStatus = static_cast<CableConnectionStatus>(newStatus);
+ }
+ }
+ tvInputEvent.deviceInfo.cableConnectionStatus = connectionStatus;
// TODO: Ensure the legacy audio type code is the same once audio HAL default
// implementation is ready.
tvInputEvent.deviceInfo.audioType = static_cast<AudioDevice>(
diff --git a/tv/tuner/1.0/vts/functional/DvrTests.cpp b/tv/tuner/1.0/vts/functional/DvrTests.cpp
index 0dfc032..ba21189 100644
--- a/tv/tuner/1.0/vts/functional/DvrTests.cpp
+++ b/tv/tuner/1.0/vts/functional/DvrTests.cpp
@@ -55,6 +55,7 @@
uint8_t* buffer;
ALOGW("[vts] playback thread loop start %s", mInputDataFile.c_str());
if (fd < 0) {
+ EXPECT_TRUE(fd >= 0) << "Failed to open: " + mInputDataFile;
mPlaybackThreadRunning = false;
ALOGW("[vts] Error %s", strerror(errno));
}
@@ -178,7 +179,7 @@
// Our current implementation filter the data and write it into the filter FMQ
// immediately after the DATA_READY from the VTS/framework
if (!readRecordFMQ()) {
- ALOGD("[vts] record data failed to be filtered. Ending thread");
+ ALOGW("[vts] record data failed to be filtered. Ending thread");
mRecordThreadRunning = false;
break;
}
diff --git a/tv/tuner/1.0/vts/functional/FilterTests.cpp b/tv/tuner/1.0/vts/functional/FilterTests.cpp
index 0ecdf73..a354c78 100644
--- a/tv/tuner/1.0/vts/functional/FilterTests.cpp
+++ b/tv/tuner/1.0/vts/functional/FilterTests.cpp
@@ -70,6 +70,10 @@
}
bool FilterCallback::readFilterEventData() {
+ if (mFilterMQ == NULL) {
+ ALOGW("[vts] FMQ is not configured and does not need to be tested.");
+ return true;
+ }
bool result = false;
DemuxFilterEvent filterEvent = mFilterEvent;
ALOGW("[vts] reading from filter FMQ or buffer %d", mFilterId);
@@ -218,7 +222,11 @@
return AssertionResult(status == Result::SUCCESS);
}
-AssertionResult FilterTests::getFilterMQDescriptor(uint32_t filterId) {
+AssertionResult FilterTests::getFilterMQDescriptor(uint32_t filterId, bool getMqDesc) {
+ if (!getMqDesc) {
+ ALOGE("[vts] Filter does not need FMQ.");
+ return success();
+ }
Result status;
EXPECT_TRUE(mFilters[filterId]) << "Test with getNewlyOpenedFilterId first.";
EXPECT_TRUE(mFilterCallbacks[filterId]) << "Test with getNewlyOpenedFilterId first.";
@@ -279,16 +287,14 @@
AssertionResult FilterTests::closeFilter(uint32_t filterId) {
EXPECT_TRUE(mFilters[filterId]) << "Test with getNewlyOpenedFilterId first.";
Result status = mFilters[filterId]->close();
- if (status == Result::SUCCESS) {
- for (int i = 0; i < mUsedFilterIds.size(); i++) {
- if (mUsedFilterIds[i] == filterId) {
- mUsedFilterIds.erase(mUsedFilterIds.begin() + i);
- break;
- }
+ for (int i = 0; i < mUsedFilterIds.size(); i++) {
+ if (mUsedFilterIds[i] == filterId) {
+ mUsedFilterIds.erase(mUsedFilterIds.begin() + i);
+ break;
}
- mFilterCallbacks.erase(filterId);
- mFilters.erase(filterId);
}
+ mFilterCallbacks.erase(filterId);
+ mFilters.erase(filterId);
return AssertionResult(status == Result::SUCCESS);
}
diff --git a/tv/tuner/1.0/vts/functional/FilterTests.h b/tv/tuner/1.0/vts/functional/FilterTests.h
index a76a6b9..75c59b3 100644
--- a/tv/tuner/1.0/vts/functional/FilterTests.h
+++ b/tv/tuner/1.0/vts/functional/FilterTests.h
@@ -157,7 +157,7 @@
AssertionResult getTimeStamp();
AssertionResult getNewlyOpenedFilterId(uint32_t& filterId);
AssertionResult configFilter(DemuxFilterSettings setting, uint32_t filterId);
- AssertionResult getFilterMQDescriptor(uint32_t filterId);
+ AssertionResult getFilterMQDescriptor(uint32_t filterId, bool getMqDesc);
AssertionResult setFilterDataSource(uint32_t sourceFilterId, uint32_t sinkFilterId);
AssertionResult setFilterDataSourceToDemux(uint32_t filterId);
AssertionResult startFilter(uint32_t filterId);
diff --git a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp
index 2be68b8..22ba271 100644
--- a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp
+++ b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp
@@ -48,7 +48,7 @@
ASSERT_TRUE(mFilterTests.openFilterInDemux(filterConf.type, filterConf.bufferSize));
ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId(filterId));
ASSERT_TRUE(mFilterTests.configFilter(filterConf.settings, filterId));
- ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId));
+ ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId, filterConf.getMqDesc));
ASSERT_TRUE(mFilterTests.startFilter(filterId));
ASSERT_TRUE(mFilterTests.stopFilter(filterId));
ASSERT_TRUE(mFilterTests.closeFilter(filterId));
@@ -75,6 +75,9 @@
void TunerBroadcastHidlTest::broadcastSingleFilterTest(FilterConfig filterConf,
FrontendConfig frontendConf) {
+ if (!frontendConf.enable) {
+ return;
+ }
uint32_t feId;
uint32_t demuxId;
sp<IDemux> demux;
@@ -99,7 +102,7 @@
ASSERT_TRUE(mFilterTests.openFilterInDemux(filterConf.type, filterConf.bufferSize));
ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId(filterId));
ASSERT_TRUE(mFilterTests.configFilter(filterConf.settings, filterId));
- ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId));
+ ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId, filterConf.getMqDesc));
ASSERT_TRUE(mFilterTests.startFilter(filterId));
// tune test
ASSERT_TRUE(mFrontendTests.tuneFrontend(frontendConf, true /*testWithDemux*/));
@@ -145,7 +148,7 @@
ASSERT_TRUE(mFilterTests.openFilterInDemux(filterConf.type, filterConf.bufferSize));
ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId(filterId));
ASSERT_TRUE(mFilterTests.configFilter(filterConf.settings, filterId));
- ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId));
+ ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId, filterConf.getMqDesc));
mDvrTests.startPlaybackInputThread(dvrConf.playbackInputFile, dvrConf.settings.playback());
ASSERT_TRUE(mDvrTests.startDvrPlayback());
ASSERT_TRUE(mFilterTests.startFilter(filterId));
@@ -160,6 +163,9 @@
void TunerRecordHidlTest::recordSingleFilterTest(FilterConfig filterConf,
FrontendConfig frontendConf, DvrConfig dvrConf) {
+ if (!frontendConf.enable) {
+ return;
+ }
uint32_t feId;
uint32_t demuxId;
sp<IDemux> demux;
@@ -184,7 +190,7 @@
ASSERT_TRUE(mFilterTests.openFilterInDemux(filterConf.type, filterConf.bufferSize));
ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId(filterId));
ASSERT_TRUE(mFilterTests.configFilter(filterConf.settings, filterId));
- ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId));
+ ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId, filterConf.getMqDesc));
filter = mFilterTests.getFilterById(filterId);
ASSERT_TRUE(filter != nullptr);
mDvrTests.startRecordOutputThread(dvrConf.settings.record());
@@ -247,7 +253,7 @@
ASSERT_TRUE(mFilterTests.openFilterInDemux(filterConf.type, filterConf.bufferSize));
ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId(filterId));
ASSERT_TRUE(mFilterTests.configFilter(filterConf.settings, filterId));
- ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId));
+ ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId, filterConf.getMqDesc));
filter = mFilterTests.getFilterById(filterId);
ASSERT_TRUE(filter != nullptr);
ASSERT_TRUE(mDvrTests.attachFilterToDvr(filter));
@@ -265,6 +271,9 @@
void TunerDescramblerHidlTest::scrambledBroadcastTest(set<struct FilterConfig> mediaFilterConfs,
FrontendConfig frontendConf,
DescramblerConfig descConfig) {
+ if (!frontendConf.enable) {
+ return;
+ }
uint32_t feId;
uint32_t demuxId;
sp<IDemux> demux;
@@ -328,17 +337,17 @@
TEST_P(TunerFrontendHidlTest, TuneFrontend) {
description("Tune one Frontend with specific setting and check Lock event");
- mFrontendTests.tuneTest(frontendArray[DVBT]);
+ mFrontendTests.tuneTest(frontendArray[defaultFrontend]);
}
TEST_P(TunerFrontendHidlTest, AutoScanFrontend) {
description("Run an auto frontend scan with specific setting and check lock scanMessage");
- mFrontendTests.scanTest(frontendScanArray[SCAN_DVBT], FrontendScanType::SCAN_AUTO);
+ mFrontendTests.scanTest(frontendScanArray[defaultScanFrontend], FrontendScanType::SCAN_AUTO);
}
TEST_P(TunerFrontendHidlTest, BlindScanFrontend) {
description("Run an blind frontend scan with specific setting and check lock scanMessage");
- mFrontendTests.scanTest(frontendScanArray[SCAN_DVBT], FrontendScanType::SCAN_BLIND);
+ mFrontendTests.scanTest(frontendScanArray[defaultScanFrontend], FrontendScanType::SCAN_BLIND);
}
TEST_P(TunerLnbHidlTest, OpenLnbByName) {
@@ -374,7 +383,7 @@
uint32_t feId;
uint32_t demuxId;
sp<IDemux> demux;
- mFrontendTests.getFrontendIdByType(frontendArray[DVBT].type, feId);
+ mFrontendTests.getFrontendIdByType(frontendArray[defaultFrontend].type, feId);
ASSERT_TRUE(feId != INVALID_ID);
ASSERT_TRUE(mFrontendTests.openFrontendById(feId));
ASSERT_TRUE(mFrontendTests.setFrontendCallback());
@@ -394,7 +403,7 @@
uint32_t avSyncHwId;
sp<IFilter> mediaFilter;
- mFrontendTests.getFrontendIdByType(frontendArray[DVBT].type, feId);
+ mFrontendTests.getFrontendIdByType(frontendArray[defaultFrontend].type, feId);
ASSERT_TRUE(feId != INVALID_ID);
ASSERT_TRUE(mFrontendTests.openFrontendById(feId));
ASSERT_TRUE(mFrontendTests.setFrontendCallback());
@@ -422,7 +431,7 @@
TEST_P(TunerFilterHidlTest, StartFilterInDemux) {
description("Open and start a filter in Demux.");
// TODO use paramterized tests
- configSingleFilterInDemuxTest(filterArray[TS_VIDEO0], frontendArray[DVBT]);
+ configSingleFilterInDemuxTest(filterArray[TS_VIDEO0], frontendArray[defaultFrontend]);
}
TEST_P(TunerFilterHidlTest, SetFilterLinkage) {
@@ -463,22 +472,22 @@
TEST_P(TunerBroadcastHidlTest, BroadcastDataFlowVideoFilterTest) {
description("Test Video Filter functionality in Broadcast use case.");
- broadcastSingleFilterTest(filterArray[TS_VIDEO1], frontendArray[DVBT]);
+ broadcastSingleFilterTest(filterArray[TS_VIDEO1], frontendArray[defaultFrontend]);
}
TEST_P(TunerBroadcastHidlTest, BroadcastDataFlowAudioFilterTest) {
description("Test Audio Filter functionality in Broadcast use case.");
- broadcastSingleFilterTest(filterArray[TS_AUDIO0], frontendArray[DVBT]);
+ broadcastSingleFilterTest(filterArray[TS_AUDIO0], frontendArray[defaultFrontend]);
}
TEST_P(TunerBroadcastHidlTest, BroadcastDataFlowSectionFilterTest) {
description("Test Section Filter functionality in Broadcast use case.");
- broadcastSingleFilterTest(filterArray[TS_SECTION0], frontendArray[DVBT]);
+ broadcastSingleFilterTest(filterArray[TS_SECTION0], frontendArray[defaultFrontend]);
}
TEST_P(TunerBroadcastHidlTest, IonBufferTest) {
description("Test the av filter data bufferring.");
- broadcastSingleFilterTest(filterArray[TS_VIDEO0], frontendArray[DVBT]);
+ broadcastSingleFilterTest(filterArray[TS_VIDEO0], frontendArray[defaultFrontend]);
}
TEST_P(TunerBroadcastHidlTest, LnbBroadcastDataFlowVideoFilterTest) {
@@ -494,13 +503,14 @@
TEST_P(TunerRecordHidlTest, AttachFiltersToRecordTest) {
description("Attach a single filter to the record dvr test.");
// TODO use paramterized tests
- attachSingleFilterToRecordDvrTest(filterArray[TS_RECORD0], frontendArray[DVBT],
+ attachSingleFilterToRecordDvrTest(filterArray[TS_RECORD0], frontendArray[defaultFrontend],
dvrArray[DVR_RECORD0]);
}
TEST_P(TunerRecordHidlTest, RecordDataFlowWithTsRecordFilterTest) {
description("Feed ts data from frontend to recording and test with ts record filter");
- recordSingleFilterTest(filterArray[TS_RECORD0], frontendArray[DVBT], dvrArray[DVR_RECORD0]);
+ recordSingleFilterTest(filterArray[TS_RECORD0], frontendArray[defaultFrontend],
+ dvrArray[DVR_RECORD0]);
}
TEST_P(TunerRecordHidlTest, LnbRecordDataFlowWithTsRecordFilterTest) {
@@ -513,7 +523,7 @@
uint32_t feId;
uint32_t demuxId;
sp<IDemux> demux;
- mFrontendTests.getFrontendIdByType(frontendArray[DVBT].type, feId);
+ mFrontendTests.getFrontendIdByType(frontendArray[defaultFrontend].type, feId);
ASSERT_TRUE(feId != INVALID_ID);
ASSERT_TRUE(mFrontendTests.openFrontendById(feId));
ASSERT_TRUE(mFrontendTests.setFrontendCallback());
@@ -530,7 +540,7 @@
set<FilterConfig> filterConfs;
filterConfs.insert(filterArray[TS_AUDIO0]);
filterConfs.insert(filterArray[TS_VIDEO1]);
- scrambledBroadcastTest(filterConfs, frontendArray[DVBT], descramblerArray[DESC_0]);
+ scrambledBroadcastTest(filterConfs, frontendArray[defaultFrontend], descramblerArray[DESC_0]);
}
INSTANTIATE_TEST_SUITE_P(
diff --git a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TestConfigurations.h b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TestConfigurations.h
index 6c68e35..92a8130 100644
--- a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TestConfigurations.h
+++ b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TestConfigurations.h
@@ -55,6 +55,7 @@
using namespace std;
+const uint32_t FMQ_SIZE_512K = 0x80000;
const uint32_t FMQ_SIZE_1M = 0x100000;
const uint32_t FMQ_SIZE_4M = 0x400000;
const uint32_t FMQ_SIZE_16M = 0x1000000;
@@ -134,6 +135,7 @@
uint32_t bufferSize;
DemuxFilterType type;
DemuxFilterSettings settings;
+ bool getMqDesc;
bool operator<(const FilterConfig& /*c*/) const { return false; }
};
@@ -144,6 +146,7 @@
};
struct FrontendConfig {
+ bool enable;
bool isSoftwareFe;
FrontendType type;
FrontendSettings settings;
@@ -191,6 +194,8 @@
static DvrConfig dvrArray[DVR_MAX];
static DescramblerConfig descramblerArray[DESC_MAX];
static vector<string> goldenOutputFiles;
+static int defaultFrontend = DVBT;
+static int defaultScanFrontend = SCAN_DVBT;
/** Configuration array for the frontend tune test */
inline void initFrontendConfig() {
@@ -216,7 +221,9 @@
frontendArray[DVBT].tuneStatusTypes = types;
frontendArray[DVBT].expectTuneStatuses = statuses;
frontendArray[DVBT].isSoftwareFe = true;
+ frontendArray[DVBS].enable = true;
frontendArray[DVBS].type = FrontendType::DVBS;
+ frontendArray[DVBS].enable = true;
frontendArray[DVBS].isSoftwareFe = true;
};
@@ -283,6 +290,7 @@
.isRaw = false,
.streamId = 0xbd,
});
+ filterArray[TS_PES0].getMqDesc = true;
// TS PCR filter setting
filterArray[TS_PCR0].type.mainType = DemuxFilterMainType::TS;
filterArray[TS_PCR0].type.subType.tsFilterType(DemuxTsFilterType::PCR);
@@ -303,6 +311,7 @@
filterArray[TS_SECTION0].settings.ts().filterSettings.section({
.isRaw = false,
});
+ filterArray[TS_SECTION0].getMqDesc = true;
// TS RECORD filter setting
filterArray[TS_RECORD0].type.mainType = DemuxFilterMainType::TS;
filterArray[TS_RECORD0].type.subType.tsFilterType(DemuxTsFilterType::RECORD);
diff --git a/vibrator/aidl/default/Android.bp b/vibrator/aidl/default/Android.bp
index 9e6d9cf..70175dd 100644
--- a/vibrator/aidl/default/Android.bp
+++ b/vibrator/aidl/default/Android.bp
@@ -4,7 +4,7 @@
shared_libs: [
"libbase",
"libbinder_ndk",
- "android.hardware.vibrator-ndk_platform",
+ "android.hardware.vibrator-V1-ndk_platform",
],
export_include_dirs: ["include"],
srcs: ["Vibrator.cpp"],
@@ -23,7 +23,7 @@
shared_libs: [
"libbase",
"libbinder_ndk",
- "android.hardware.vibrator-ndk_platform",
+ "android.hardware.vibrator-V1-ndk_platform",
],
static_libs: [
"libvibratorexampleimpl",
diff --git a/vibrator/aidl/vts/Android.bp b/vibrator/aidl/vts/Android.bp
index 28cb4d9..a06dae3 100644
--- a/vibrator/aidl/vts/Android.bp
+++ b/vibrator/aidl/vts/Android.bp
@@ -9,7 +9,7 @@
"libbinder",
],
static_libs: [
- "android.hardware.vibrator-cpp",
+ "android.hardware.vibrator-V1-cpp",
],
test_suites: [
"general-tests",
diff --git a/weaver/aidl/Android.bp b/weaver/aidl/Android.bp
new file mode 100644
index 0000000..5637e0a
--- /dev/null
+++ b/weaver/aidl/Android.bp
@@ -0,0 +1,16 @@
+aidl_interface {
+ name: "android.hardware.weaver",
+ vendor_available: true,
+ srcs: ["android/hardware/weaver/*.aidl"],
+ stability: "vintf",
+ backend: {
+ java: {
+ platform_apis: true,
+ },
+ ndk: {
+ vndk: {
+ enabled: true,
+ },
+ },
+ },
+}
diff --git a/weaver/aidl/aidl_api/android.hardware.weaver/current/android/hardware/weaver/IWeaver.aidl b/weaver/aidl/aidl_api/android.hardware.weaver/current/android/hardware/weaver/IWeaver.aidl
new file mode 100644
index 0000000..29bd9a9
--- /dev/null
+++ b/weaver/aidl/aidl_api/android.hardware.weaver/current/android/hardware/weaver/IWeaver.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.weaver;
+@VintfStability
+interface IWeaver {
+ android.hardware.weaver.WeaverConfig getConfig();
+ android.hardware.weaver.WeaverReadResponse read(in int slotId, in byte[] key);
+ void write(in int slotId, in byte[] key, in byte[] value);
+ const int STATUS_FAILED = 1;
+ const int INCORRECT_KEY = 2;
+ const int THROTTLE = 3;
+}
diff --git a/weaver/aidl/aidl_api/android.hardware.weaver/current/android/hardware/weaver/WeaverConfig.aidl b/weaver/aidl/aidl_api/android.hardware.weaver/current/android/hardware/weaver/WeaverConfig.aidl
new file mode 100644
index 0000000..239cdac
--- /dev/null
+++ b/weaver/aidl/aidl_api/android.hardware.weaver/current/android/hardware/weaver/WeaverConfig.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.weaver;
+@VintfStability
+parcelable WeaverConfig {
+ long slots;
+ long keySize;
+ long valueSize;
+}
diff --git a/weaver/aidl/aidl_api/android.hardware.weaver/current/android/hardware/weaver/WeaverReadResponse.aidl b/weaver/aidl/aidl_api/android.hardware.weaver/current/android/hardware/weaver/WeaverReadResponse.aidl
new file mode 100644
index 0000000..7e5db59
--- /dev/null
+++ b/weaver/aidl/aidl_api/android.hardware.weaver/current/android/hardware/weaver/WeaverReadResponse.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// 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.weaver;
+@VintfStability
+parcelable WeaverReadResponse {
+ long timeout;
+ byte[] value;
+}
diff --git a/weaver/aidl/android/hardware/weaver/IWeaver.aidl b/weaver/aidl/android/hardware/weaver/IWeaver.aidl
new file mode 100644
index 0000000..ebbfabe
--- /dev/null
+++ b/weaver/aidl/android/hardware/weaver/IWeaver.aidl
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.weaver;
+
+import android.hardware.weaver.WeaverConfig;
+import android.hardware.weaver.WeaverReadResponse;
+
+/**
+ * Weaver provides secure storage of secret values that may only be read if the
+ * corresponding key has been presented.
+ *
+ * The storage must be secure as the device's user authentication and encryption
+ * relies on the security of these values. The cardinality of the domains of the
+ * key and value must be suitably large such that they cannot be easily guessed.
+ *
+ * Weaver is structured as an array of slots, each containing a key-value pair.
+ * Slots are uniquely identified by an ID in the range [0, `getConfig().slots`).
+ */
+@VintfStability
+interface IWeaver {
+ /**
+ * Retrieves the config information for this implementation of Weaver.
+ *
+ * The config is static i.e. every invocation returns the same information.
+ *
+ * @return config data for this implementation of Weaver if status is OK,
+ * otherwise undefined.
+ */
+ WeaverConfig getConfig();
+
+ /**
+ * Read binder calls may return a ServiceSpecificException with the following error codes.
+ */
+ const int STATUS_FAILED = 1;
+ const int INCORRECT_KEY = 2;
+ const int THROTTLE = 3;
+
+ /**
+ * Attempts to retrieve the value stored in the identified slot.
+ *
+ * The value is only returned if the provided key matches the key stored in
+ * the slot. The value is never returned if the wrong key is provided.
+ *
+ * Throttling must be used to limit the frequency of failed read attempts.
+ * The value is only returned when throttling is not active, even if the
+ * correct key is provided. If called when throttling is active, the time
+ * until the next attempt can be made is returned.
+ *
+ * Service status return:
+ *
+ * OK if the value was successfully read from slot.
+ * INCORRECT_KEY if the key does not match the key in the slot.
+ * THROTTLE if throttling is active.
+ * STATUS_FAILED if the read was unsuccessful for another reason.
+ *
+ * @param slotId of the slot to read from, this must be positive to be valid.
+ * @param key that is stored in the slot.
+ * @return The WeaverReadResponse for this read request. If the status is OK,
+ * value is set to the value in the slot and timeout is 0. Otherwise, value is
+ * empty and timeout is set accordingly.
+ */
+ WeaverReadResponse read(in int slotId, in byte[] key);
+
+ /**
+ * Overwrites the identified slot with the provided key and value.
+ *
+ * The new values are written regardless of the current state of the slot in
+ * order to remain idempotent.
+ *
+ * Service status return:
+ *
+ * OK if the write was successfully completed.
+ * FAILED if the write was unsuccessful.
+ *
+ * @param slotId of the slot to write to.
+ * @param key to write to the slot.
+ * @param value to write to slot.
+ */
+ void write(in int slotId, in byte[] key, in byte[] value);
+}
diff --git a/health/1.0/default/libhealthd/healthd_board_default.cpp b/weaver/aidl/android/hardware/weaver/WeaverConfig.aidl
similarity index 61%
copy from health/1.0/default/libhealthd/healthd_board_default.cpp
copy to weaver/aidl/android/hardware/weaver/WeaverConfig.aidl
index 127f98e..75d961e 100644
--- a/health/1.0/default/libhealthd/healthd_board_default.cpp
+++ b/weaver/aidl/android/hardware/weaver/WeaverConfig.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,21 @@
* limitations under the License.
*/
-#include <healthd/healthd.h>
+package android.hardware.weaver;
-void healthd_board_init(struct healthd_config*)
-{
- // use defaults
+@VintfStability
+parcelable WeaverConfig {
+ /**
+ * The number of slots available.
+ */
+ long slots;
+ /**
+ * The number of bytes used for a key.
+ */
+ long keySize;
+ /**
+ * The number of bytes used for a value.
+ */
+ long valueSize;
}
-int healthd_board_battery_update(struct android::BatteryProperties*)
-{
- // return 0 to log periodic polled battery status to kernel log
- return 0;
-}
diff --git a/health/1.0/default/libhealthd/healthd_board_default.cpp b/weaver/aidl/android/hardware/weaver/WeaverReadResponse.aidl
similarity index 61%
copy from health/1.0/default/libhealthd/healthd_board_default.cpp
copy to weaver/aidl/android/hardware/weaver/WeaverReadResponse.aidl
index 127f98e..ec006e8 100644
--- a/health/1.0/default/libhealthd/healthd_board_default.cpp
+++ b/weaver/aidl/android/hardware/weaver/WeaverReadResponse.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,17 @@
* limitations under the License.
*/
-#include <healthd/healthd.h>
+package android.hardware.weaver;
-void healthd_board_init(struct healthd_config*)
-{
- // use defaults
+@VintfStability
+parcelable WeaverReadResponse {
+ /**
+ * The time to wait, in milliseconds, before making the next request.
+ */
+ long timeout;
+ /**
+ * The value read from the slot or empty if the value was not read.
+ */
+ byte[] value;
}
-int healthd_board_battery_update(struct android::BatteryProperties*)
-{
- // return 0 to log periodic polled battery status to kernel log
- return 0;
-}
diff --git a/weaver/aidl/default/Android.bp b/weaver/aidl/default/Android.bp
new file mode 100644
index 0000000..8440670
--- /dev/null
+++ b/weaver/aidl/default/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_binary {
+ name: "android.hardware.weaver-service.example",
+ relative_install_path: "hw",
+ init_rc: ["android.hardware.weaver-service.example.rc"],
+ vintf_fragments: ["android.hardware.weaver-service.example.xml"],
+ vendor: true,
+ srcs: [
+ "service.cpp",
+ "Weaver.cpp",
+ ],
+ shared_libs: [
+ "android.hardware.weaver-V1-ndk_platform",
+ "libbase",
+ "libbinder_ndk",
+ ],
+}
diff --git a/weaver/aidl/default/Weaver.cpp b/weaver/aidl/default/Weaver.cpp
new file mode 100644
index 0000000..56d9c4d
--- /dev/null
+++ b/weaver/aidl/default/Weaver.cpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Weaver.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace weaver {
+
+// Methods from ::android::hardware::weaver::IWeaver follow.
+
+::ndk::ScopedAStatus Weaver::getConfig(WeaverConfig* out_config) {
+ (void)out_config;
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Weaver::read(int32_t in_slotId, const std::vector<uint8_t>& in_key, WeaverReadResponse* out_response) {
+ (void)in_slotId;
+ (void)in_key;
+ (void)out_response;
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Weaver::write(int32_t in_slotId, const std::vector<uint8_t>& in_key, const std::vector<uint8_t>& in_value) {
+ (void)in_slotId;
+ (void)in_key;
+ (void)in_value;
+ return ::ndk::ScopedAStatus::ok();
+}
+
+} //namespace weaver
+} //namespace hardware
+} //namespace android
+} //namespace aidl
diff --git a/weaver/aidl/default/Weaver.h b/weaver/aidl/default/Weaver.h
new file mode 100644
index 0000000..b50018e
--- /dev/null
+++ b/weaver/aidl/default/Weaver.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/weaver/BnWeaver.h>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace weaver {
+
+using ::aidl::android::hardware::weaver::WeaverConfig;
+using ::aidl::android::hardware::weaver::WeaverReadResponse;
+
+struct Weaver : public BnWeaver {
+public:
+ Weaver() = default;
+
+ // Methods from ::android::hardware::weaver::IWeaver follow.
+ ::ndk::ScopedAStatus getConfig(WeaverConfig* _aidl_return) override;
+ ::ndk::ScopedAStatus read(int32_t in_slotId, const std::vector<uint8_t>& in_key, WeaverReadResponse* _aidl_return) override;
+ ::ndk::ScopedAStatus write(int32_t in_slotId, const std::vector<uint8_t>& in_key, const std::vector<uint8_t>& in_value) override;
+};
+
+} // namespace weaver
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/weaver/aidl/default/android.hardware.weaver-service.example.rc b/weaver/aidl/default/android.hardware.weaver-service.example.rc
new file mode 100644
index 0000000..ec77774
--- /dev/null
+++ b/weaver/aidl/default/android.hardware.weaver-service.example.rc
@@ -0,0 +1,4 @@
+service vendor.weaver_default /vendor/bin/hw/android.hardware.weaver-service.example
+ class hal
+ user hsm
+ group hsm
diff --git a/weaver/aidl/default/android.hardware.weaver-service.example.xml b/weaver/aidl/default/android.hardware.weaver-service.example.xml
new file mode 100644
index 0000000..ed291cd
--- /dev/null
+++ b/weaver/aidl/default/android.hardware.weaver-service.example.xml
@@ -0,0 +1,10 @@
+<manifest version="1.0" type="device">
+ <hal format="aidl">
+ <name>android.hardware.weaver</name>
+ <version>1</version>
+ <interface>
+ <name>IWeaver</name>
+ <instance>default</instance>
+ </interface>
+ </hal>
+</manifest>
diff --git a/weaver/aidl/default/service.cpp b/weaver/aidl/default/service.cpp
new file mode 100644
index 0000000..1495bc9
--- /dev/null
+++ b/weaver/aidl/default/service.cpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include "Weaver.h"
+
+using ::aidl::android::hardware::weaver::Weaver;
+
+int main() {
+ ABinderProcess_setThreadPoolMaxThreadCount(0);
+ std::shared_ptr<Weaver> weaver = ndk::SharedRefBase::make<Weaver>();
+
+ const std::string instance = std::string() + Weaver::descriptor + "/default";
+ binder_status_t status = AServiceManager_addService(weaver->asBinder().get(), instance.c_str());
+ CHECK(status == STATUS_OK);
+
+ ABinderProcess_joinThreadPool();
+ return -1; // Should never be reached
+}
diff --git a/weaver/aidl/vts/Android.bp b/weaver/aidl/vts/Android.bp
new file mode 100644
index 0000000..7daad8d
--- /dev/null
+++ b/weaver/aidl/vts/Android.bp
@@ -0,0 +1,33 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_test {
+ name: "VtsHalWeaverTargetTest",
+ defaults: [
+ "VtsHalTargetTestDefaults",
+ "use_libaidlvintf_gtest_helper_static",
+ ],
+ srcs: ["VtsHalWeaverTargetTest.cpp"],
+ shared_libs: [
+ "libbinder_ndk",
+ "libbase",
+ ],
+ static_libs: ["android.hardware.weaver-V1-ndk_platform"],
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
+}
diff --git a/weaver/aidl/vts/OWNERS b/weaver/aidl/vts/OWNERS
new file mode 100644
index 0000000..40d95e4
--- /dev/null
+++ b/weaver/aidl/vts/OWNERS
@@ -0,0 +1,2 @@
+chengyouho@google.com
+frankwoo@google.com
diff --git a/weaver/aidl/vts/VtsHalWeaverTargetTest.cpp b/weaver/aidl/vts/VtsHalWeaverTargetTest.cpp
new file mode 100644
index 0000000..7d8daa2
--- /dev/null
+++ b/weaver/aidl/vts/VtsHalWeaverTargetTest.cpp
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+
+#include <aidl/android/hardware/weaver/IWeaver.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include <limits>
+
+using ::aidl::android::hardware::weaver::IWeaver;
+using ::aidl::android::hardware::weaver::WeaverConfig;
+using ::aidl::android::hardware::weaver::WeaverReadResponse;
+
+using ::ndk::SpAIBinder;
+
+const std::vector<uint8_t> KEY{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
+const std::vector<uint8_t> WRONG_KEY{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+const std::vector<uint8_t> VALUE{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
+const std::vector<uint8_t> OTHER_VALUE{0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 255, 255};
+
+struct WeaverAidlTest : public ::testing::TestWithParam<std::string> {
+ virtual void SetUp() override {
+ weaver = IWeaver::fromBinder(
+ SpAIBinder(AServiceManager_waitForService(GetParam().c_str())));
+ ASSERT_NE(weaver, nullptr);
+ }
+
+ virtual void TearDown() override {}
+
+ std::shared_ptr<IWeaver> weaver;
+};
+
+/*
+ * Checks config values are suitably large
+ */
+TEST_P(WeaverAidlTest, GetConfig) {
+ WeaverConfig config;
+
+ auto ret = weaver->getConfig(&config);
+
+ ASSERT_TRUE(ret.isOk());
+
+ EXPECT_GE(config.slots, 16u);
+ EXPECT_GE(config.keySize, 16u);
+ EXPECT_GE(config.valueSize, 16u);
+}
+
+/*
+ * Gets the config twice and checks they are the same
+ */
+TEST_P(WeaverAidlTest, GettingConfigMultipleTimesGivesSameResult) {
+ WeaverConfig config1;
+ WeaverConfig config2;
+
+ auto ret = weaver->getConfig(&config1);
+ ASSERT_TRUE(ret.isOk());
+
+ ret = weaver->getConfig(&config2);
+ ASSERT_TRUE(ret.isOk());
+
+ EXPECT_EQ(config1, config2);
+}
+
+/*
+ * Gets the number of slots from the config and writes a key and value to the last one
+ */
+TEST_P(WeaverAidlTest, WriteToLastSlot) {
+ WeaverConfig config;
+ const auto configRet = weaver->getConfig(&config);
+
+ ASSERT_TRUE(configRet.isOk());
+
+ const uint32_t lastSlot = config.slots - 1;
+ const auto writeRet = weaver->write(lastSlot, KEY, VALUE);
+ ASSERT_TRUE(writeRet.isOk());
+}
+
+/*
+ * Writes a key and value to a slot
+ * Reads the slot with the same key and receives the value that was previously written
+ */
+TEST_P(WeaverAidlTest, WriteFollowedByReadGivesTheSameValue) {
+ constexpr uint32_t slotId = 0;
+ const auto ret = weaver->write(slotId, KEY, VALUE);
+ ASSERT_TRUE(ret.isOk());
+
+ WeaverReadResponse response;
+ std::vector<uint8_t> readValue;
+ uint32_t timeout;
+ const auto readRet = weaver->read(slotId, KEY, &response);
+
+ readValue = response.value;
+ timeout = response.timeout;
+
+ ASSERT_TRUE(readRet.isOk());
+ EXPECT_EQ(readValue, VALUE);
+ EXPECT_EQ(timeout, 0u);
+}
+
+/*
+ * Writes a key and value to a slot
+ * Overwrites the slot with a new key and value
+ * Reads the slot with the new key and receives the new value
+ */
+TEST_P(WeaverAidlTest, OverwritingSlotUpdatesTheValue) {
+ constexpr uint32_t slotId = 0;
+ const auto initialWriteRet = weaver->write(slotId, WRONG_KEY, VALUE);
+ ASSERT_TRUE(initialWriteRet.isOk());
+
+ const auto overwriteRet = weaver->write(slotId, KEY, OTHER_VALUE);
+ ASSERT_TRUE(overwriteRet.isOk());
+
+ WeaverReadResponse response;
+ std::vector<uint8_t> readValue;
+ uint32_t timeout;
+ const auto readRet = weaver->read(slotId, KEY, &response);
+
+ readValue = response.value;
+ timeout = response.timeout;
+
+ ASSERT_TRUE(readRet.isOk());
+ EXPECT_EQ(readValue, OTHER_VALUE);
+ EXPECT_EQ(timeout, 0u);
+}
+
+/*
+ * Writes a key and value to a slot
+ * Reads the slot with a different key so does not receive the value
+ */
+TEST_P(WeaverAidlTest, WriteFollowedByReadWithWrongKeyDoesNotGiveTheValue) {
+ constexpr uint32_t slotId = 0;
+ const auto ret = weaver->write(slotId, KEY, VALUE);
+ ASSERT_TRUE(ret.isOk());
+
+ WeaverReadResponse response;
+ std::vector<uint8_t> readValue;
+ const auto readRet =
+ weaver->read(slotId, WRONG_KEY, &response);
+
+ readValue = response.value;
+
+ ASSERT_FALSE(readRet.isOk());
+ ASSERT_EQ(EX_SERVICE_SPECIFIC, readRet.getExceptionCode());
+ ASSERT_EQ(IWeaver::INCORRECT_KEY, readRet.getServiceSpecificError());
+ EXPECT_TRUE(readValue.empty());
+}
+
+/*
+ * Writing to an invalid slot fails
+ */
+TEST_P(WeaverAidlTest, WritingToInvalidSlotFails) {
+ WeaverConfig config;
+ const auto configRet = weaver->getConfig(&config);
+ ASSERT_TRUE(configRet.isOk());
+
+ if (config.slots == std::numeric_limits<uint32_t>::max()) {
+ // If there are no invalid slots then pass
+ return;
+ }
+
+ const auto writeRet = weaver->write(config.slots, KEY, VALUE);
+ ASSERT_FALSE(writeRet.isOk());
+}
+
+/*
+ * Reading from an invalid slot fails rather than incorrect key
+ */
+TEST_P(WeaverAidlTest, ReadingFromInvalidSlotFails) {
+ WeaverConfig config;
+ const auto configRet = weaver->getConfig(&config);
+ ASSERT_TRUE(configRet.isOk());
+
+ if (config.slots == std::numeric_limits<uint32_t>::max()) {
+ // If there are no invalid slots then pass
+ return;
+ }
+
+ WeaverReadResponse response;
+ std::vector<uint8_t> readValue;
+ uint32_t timeout;
+ const auto readRet =
+ weaver->read(config.slots, KEY, &response);
+
+ readValue = response.value;
+ timeout = response.timeout;
+
+ ASSERT_FALSE(readRet.isOk());
+ ASSERT_EQ(EX_SERVICE_SPECIFIC, readRet.getExceptionCode());
+ ASSERT_EQ(IWeaver::STATUS_FAILED, readRet.getServiceSpecificError());
+ EXPECT_TRUE(readValue.empty());
+ EXPECT_EQ(timeout, 0u);
+}
+
+/*
+ * Writing a key that is too large fails
+ */
+TEST_P(WeaverAidlTest, WriteWithTooLargeKeyFails) {
+ WeaverConfig config;
+ const auto configRet = weaver->getConfig(&config);
+ ASSERT_TRUE(configRet.isOk());
+
+ std::vector<uint8_t> bigKey(config.keySize + 1);
+
+ constexpr uint32_t slotId = 0;
+ const auto writeRet = weaver->write(slotId, bigKey, VALUE);
+ ASSERT_FALSE(writeRet.isOk());
+}
+
+/*
+ * Writing a value that is too large fails
+ */
+TEST_P(WeaverAidlTest, WriteWithTooLargeValueFails) {
+ WeaverConfig config;
+ const auto configRet = weaver->getConfig(&config);
+ ASSERT_TRUE(configRet.isOk());
+
+ std::vector<uint8_t> bigValue(config.valueSize + 1);
+
+ constexpr uint32_t slotId = 0;
+ const auto writeRet = weaver->write(slotId, KEY, bigValue);
+ ASSERT_FALSE(writeRet.isOk());
+}
+
+/*
+ * Reading with a key that is loo large fails
+ */
+TEST_P(WeaverAidlTest, ReadWithTooLargeKeyFails) {
+ WeaverConfig config;
+ const auto configRet = weaver->getConfig(&config);
+ ASSERT_TRUE(configRet.isOk());
+
+ std::vector<uint8_t> bigKey(config.keySize + 1);
+
+ constexpr uint32_t slotId = 0;
+ WeaverReadResponse response;
+ std::vector<uint8_t> readValue;
+ uint32_t timeout;
+ const auto readRet =
+ weaver->read(slotId, bigKey, &response);
+
+ readValue = response.value;
+ timeout = response.timeout;
+
+ ASSERT_FALSE(readRet.isOk());
+ ASSERT_EQ(EX_SERVICE_SPECIFIC, readRet.getExceptionCode());
+ ASSERT_EQ(IWeaver::STATUS_FAILED, readRet.getServiceSpecificError());
+ EXPECT_TRUE(readValue.empty());
+ EXPECT_EQ(timeout, 0u);
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(WeaverAidlTest);
+INSTANTIATE_TEST_SUITE_P(
+ PerInstance, WeaverAidlTest,
+ testing::ValuesIn(android::getAidlHalInstanceNames(IWeaver::descriptor)),
+ android::PrintInstanceNameToString);
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ ABinderProcess_setThreadPoolMaxThreadCount(1);
+ ABinderProcess_startThreadPool();
+ return RUN_ALL_TESTS();
+}
diff --git a/wifi/1.4/default/wifi_legacy_hal.h b/wifi/1.4/default/wifi_legacy_hal.h
index 9964460..822f83a 100644
--- a/wifi/1.4/default/wifi_legacy_hal.h
+++ b/wifi/1.4/default/wifi_legacy_hal.h
@@ -23,15 +23,9 @@
#include <thread>
#include <vector>
+#include <hardware_legacy/wifi_hal.h>
#include <wifi_system/interface_tool.h>
-// HACK: The include inside the namespace below also transitively includes a
-// bunch of libc headers into the namespace, which leads to functions like
-// socketpair being defined in
-// android::hardware::wifi::V1_1::implementation::legacy_hal. Include this one
-// particular header as a hacky workaround until that's fixed.
-#include <sys/socket.h>
-
namespace android {
namespace hardware {
namespace wifi {
@@ -40,9 +34,274 @@
// This is in a separate namespace to prevent typename conflicts between
// the legacy HAL types and the HIDL interface types.
namespace legacy_hal {
-// Wrap all the types defined inside the legacy HAL header files inside this
+// Import all the types defined inside the legacy HAL header files into this
// namespace.
-#include <hardware_legacy/wifi_hal.h>
+using ::FRAME_TYPE_80211_MGMT;
+using ::FRAME_TYPE_ETHERNET_II;
+using ::FRAME_TYPE_UNKNOWN;
+using ::NAN_CHANNEL_24G_BAND;
+using ::NAN_CHANNEL_5G_BAND_HIGH;
+using ::NAN_CHANNEL_5G_BAND_LOW;
+using ::NAN_DISABLE_RANGE_REPORT;
+using ::NAN_DO_NOT_USE_SRF;
+using ::NAN_DP_CHANNEL_NOT_REQUESTED;
+using ::NAN_DP_CONFIG_NO_SECURITY;
+using ::NAN_DP_CONFIG_SECURITY;
+using ::NAN_DP_END;
+using ::NAN_DP_FORCE_CHANNEL_SETUP;
+using ::NAN_DP_INITIATOR_RESPONSE;
+using ::NAN_DP_INTERFACE_CREATE;
+using ::NAN_DP_INTERFACE_DELETE;
+using ::NAN_DP_REQUEST_ACCEPT;
+using ::NAN_DP_REQUEST_CHANNEL_SETUP;
+using ::NAN_DP_REQUEST_REJECT;
+using ::NAN_DP_RESPONDER_RESPONSE;
+using ::NAN_GET_CAPABILITIES;
+using ::NAN_MATCH_ALG_MATCH_CONTINUOUS;
+using ::NAN_MATCH_ALG_MATCH_NEVER;
+using ::NAN_MATCH_ALG_MATCH_ONCE;
+using ::NAN_PUBLISH_TYPE_SOLICITED;
+using ::NAN_PUBLISH_TYPE_UNSOLICITED;
+using ::NAN_PUBLISH_TYPE_UNSOLICITED_SOLICITED;
+using ::NAN_RANGING_AUTO_RESPONSE_DISABLE;
+using ::NAN_RANGING_AUTO_RESPONSE_ENABLE;
+using ::NAN_RANGING_DISABLE;
+using ::NAN_RANGING_ENABLE;
+using ::NAN_RESPONSE_BEACON_SDF_PAYLOAD;
+using ::NAN_RESPONSE_CONFIG;
+using ::NAN_RESPONSE_DISABLED;
+using ::NAN_RESPONSE_ENABLED;
+using ::NAN_RESPONSE_ERROR;
+using ::NAN_RESPONSE_PUBLISH;
+using ::NAN_RESPONSE_PUBLISH_CANCEL;
+using ::NAN_RESPONSE_STATS;
+using ::NAN_RESPONSE_SUBSCRIBE;
+using ::NAN_RESPONSE_SUBSCRIBE_CANCEL;
+using ::NAN_RESPONSE_TCA;
+using ::NAN_RESPONSE_TRANSMIT_FOLLOWUP;
+using ::NAN_SECURITY_KEY_INPUT_PASSPHRASE;
+using ::NAN_SECURITY_KEY_INPUT_PASSPHRASE;
+using ::NAN_SECURITY_KEY_INPUT_PMK;
+using ::NAN_SECURITY_KEY_INPUT_PMK;
+using ::NAN_SERVICE_ACCEPT_POLICY_ALL;
+using ::NAN_SERVICE_ACCEPT_POLICY_NONE;
+using ::NAN_SRF_ATTR_BLOOM_FILTER;
+using ::NAN_SRF_ATTR_PARTIAL_MAC_ADDR;
+using ::NAN_SRF_INCLUDE_DO_NOT_RESPOND;
+using ::NAN_SRF_INCLUDE_RESPOND;
+using ::NAN_SSI_NOT_REQUIRED_IN_MATCH_IND;
+using ::NAN_SSI_REQUIRED_IN_MATCH_IND;
+using ::NAN_STATUS_ALREADY_ENABLED;
+using ::NAN_STATUS_FOLLOWUP_QUEUE_FULL;
+using ::NAN_STATUS_INTERNAL_FAILURE;
+using ::NAN_STATUS_INVALID_NDP_ID;
+using ::NAN_STATUS_INVALID_PARAM;
+using ::NAN_STATUS_INVALID_PUBLISH_SUBSCRIBE_ID;
+using ::NAN_STATUS_INVALID_REQUESTOR_INSTANCE_ID;
+using ::NAN_STATUS_NAN_NOT_ALLOWED;
+using ::NAN_STATUS_NO_OTA_ACK;
+using ::NAN_STATUS_NO_RESOURCE_AVAILABLE;
+using ::NAN_STATUS_PROTOCOL_FAILURE;
+using ::NAN_STATUS_SUCCESS;
+using ::NAN_STATUS_UNSUPPORTED_CONCURRENCY_NAN_DISABLED;
+using ::NAN_SUBSCRIBE_TYPE_ACTIVE;
+using ::NAN_SUBSCRIBE_TYPE_PASSIVE;
+using ::NAN_TRANSMIT_IN_DW;
+using ::NAN_TRANSMIT_IN_FAW;
+using ::NAN_TX_PRIORITY_HIGH;
+using ::NAN_TX_PRIORITY_NORMAL;
+using ::NAN_TX_TYPE_BROADCAST;
+using ::NAN_TX_TYPE_UNICAST;
+using ::NAN_USE_SRF;
+using ::NanBeaconSdfPayloadInd;
+using ::NanCapabilities;
+using ::NanChannelInfo;
+using ::NanConfigRequest;
+using ::NanDataPathChannelCfg;
+using ::NanDataPathConfirmInd;
+using ::NanDataPathEndInd;
+using ::NanDataPathIndicationResponse;
+using ::NanDataPathInitiatorRequest;
+using ::NanDataPathRequestInd;
+using ::NanDataPathScheduleUpdateInd;
+using ::NanDisabledInd;
+using ::NanDiscEngEventInd;
+using ::NanEnableRequest;
+using ::NanFollowupInd;
+using ::NanMatchAlg;
+using ::NanMatchExpiredInd;
+using ::NanMatchInd;
+using ::NanPublishCancelRequest;
+using ::NanPublishRequest;
+using ::NanPublishTerminatedInd;
+using ::NanPublishType;
+using ::NanRangeReportInd;
+using ::NanRangeRequestInd;
+using ::NanResponseMsg;
+using ::NanSRFType;
+using ::NanStatusType;
+using ::NanSubscribeCancelRequest;
+using ::NanSubscribeRequest;
+using ::NanSubscribeTerminatedInd;
+using ::NanSubscribeType;
+using ::NanTransmitFollowupInd;
+using ::NanTransmitFollowupRequest;
+using ::NanTxType;
+using ::ROAMING_DISABLE;
+using ::ROAMING_ENABLE;
+using ::RTT_PEER_AP;
+using ::RTT_PEER_NAN;
+using ::RTT_PEER_P2P_CLIENT;
+using ::RTT_PEER_P2P_GO;
+using ::RTT_PEER_STA;
+using ::RTT_STATUS_ABORTED;
+using ::RTT_STATUS_FAILURE;
+using ::RTT_STATUS_FAIL_AP_ON_DIFF_CHANNEL;
+using ::RTT_STATUS_FAIL_BUSY_TRY_LATER;
+using ::RTT_STATUS_FAIL_FTM_PARAM_OVERRIDE;
+using ::RTT_STATUS_FAIL_INVALID_TS;
+using ::RTT_STATUS_FAIL_NOT_SCHEDULED_YET;
+using ::RTT_STATUS_FAIL_NO_CAPABILITY;
+using ::RTT_STATUS_FAIL_NO_RSP;
+using ::RTT_STATUS_FAIL_PROTOCOL;
+using ::RTT_STATUS_FAIL_REJECTED;
+using ::RTT_STATUS_FAIL_SCHEDULE;
+using ::RTT_STATUS_FAIL_TM_TIMEOUT;
+using ::RTT_STATUS_INVALID_REQ;
+using ::RTT_STATUS_NAN_RANGING_CONCURRENCY_NOT_SUPPORTED;
+using ::RTT_STATUS_NAN_RANGING_PROTOCOL_FAILURE;
+using ::RTT_STATUS_NO_WIFI;
+using ::RTT_STATUS_SUCCESS;
+using ::RTT_TYPE_1_SIDED;
+using ::RTT_TYPE_2_SIDED;
+using ::RX_PKT_FATE_DRV_DROP_FILTER;
+using ::RX_PKT_FATE_DRV_DROP_INVALID;
+using ::RX_PKT_FATE_DRV_DROP_NOBUFS;
+using ::RX_PKT_FATE_DRV_DROP_OTHER;
+using ::RX_PKT_FATE_DRV_QUEUED;
+using ::RX_PKT_FATE_FW_DROP_FILTER;
+using ::RX_PKT_FATE_FW_DROP_INVALID;
+using ::RX_PKT_FATE_FW_DROP_NOBUFS;
+using ::RX_PKT_FATE_FW_DROP_OTHER;
+using ::RX_PKT_FATE_FW_QUEUED;
+using ::RX_PKT_FATE_SUCCESS;
+using ::TX_PKT_FATE_ACKED;
+using ::TX_PKT_FATE_DRV_DROP_INVALID;
+using ::TX_PKT_FATE_DRV_DROP_NOBUFS;
+using ::TX_PKT_FATE_DRV_DROP_OTHER;
+using ::TX_PKT_FATE_DRV_QUEUED;
+using ::TX_PKT_FATE_FW_DROP_INVALID;
+using ::TX_PKT_FATE_FW_DROP_NOBUFS;
+using ::TX_PKT_FATE_FW_DROP_OTHER;
+using ::TX_PKT_FATE_FW_QUEUED;
+using ::TX_PKT_FATE_SENT;
+using ::WIFI_AC_BE;
+using ::WIFI_AC_BK;
+using ::WIFI_AC_VI;
+using ::WIFI_AC_VO;
+using ::WIFI_BAND_A;
+using ::WIFI_BAND_ABG;
+using ::WIFI_BAND_ABG_WITH_DFS;
+using ::WIFI_BAND_A_DFS;
+using ::WIFI_BAND_A_WITH_DFS;
+using ::WIFI_BAND_BG;
+using ::WIFI_BAND_UNSPECIFIED;
+using ::WIFI_CHAN_WIDTH_10;
+using ::WIFI_CHAN_WIDTH_160;
+using ::WIFI_CHAN_WIDTH_20;
+using ::WIFI_CHAN_WIDTH_40;
+using ::WIFI_CHAN_WIDTH_5;
+using ::WIFI_CHAN_WIDTH_5;
+using ::WIFI_CHAN_WIDTH_80;
+using ::WIFI_CHAN_WIDTH_80P80;
+using ::WIFI_CHAN_WIDTH_INVALID;
+using ::WIFI_ERROR_BUSY;
+using ::WIFI_ERROR_INVALID_ARGS;
+using ::WIFI_ERROR_INVALID_REQUEST_ID;
+using ::WIFI_ERROR_NONE;
+using ::WIFI_ERROR_NOT_AVAILABLE;
+using ::WIFI_ERROR_NOT_SUPPORTED;
+using ::WIFI_ERROR_OUT_OF_MEMORY;
+using ::WIFI_ERROR_TIMED_OUT;
+using ::WIFI_ERROR_TOO_MANY_REQUESTS;
+using ::WIFI_ERROR_UNINITIALIZED;
+using ::WIFI_ERROR_UNKNOWN;
+using ::WIFI_INTERFACE_TYPE_AP;
+using ::WIFI_INTERFACE_TYPE_NAN;
+using ::WIFI_INTERFACE_TYPE_P2P;
+using ::WIFI_INTERFACE_TYPE_STA;
+using ::WIFI_LATENCY_MODE_LOW;
+using ::WIFI_LATENCY_MODE_NORMAL;
+using ::WIFI_LOGGER_CONNECT_EVENT_SUPPORTED;
+using ::WIFI_LOGGER_DRIVER_DUMP_SUPPORTED;
+using ::WIFI_LOGGER_MEMORY_DUMP_SUPPORTED;
+using ::WIFI_LOGGER_PACKET_FATE_SUPPORTED;
+using ::WIFI_LOGGER_POWER_EVENT_SUPPORTED;
+using ::WIFI_LOGGER_WAKE_LOCK_SUPPORTED;
+using ::WIFI_MOTION_EXPECTED;
+using ::WIFI_MOTION_NOT_EXPECTED;
+using ::WIFI_MOTION_UNKNOWN;
+using ::WIFI_POWER_SCENARIO_ON_BODY_CELL_OFF;
+using ::WIFI_POWER_SCENARIO_ON_BODY_CELL_ON;
+using ::WIFI_POWER_SCENARIO_ON_HEAD_CELL_OFF;
+using ::WIFI_POWER_SCENARIO_ON_HEAD_CELL_ON;
+using ::WIFI_POWER_SCENARIO_VOICE_CALL;
+using ::WIFI_RTT_BW_10;
+using ::WIFI_RTT_BW_160;
+using ::WIFI_RTT_BW_20;
+using ::WIFI_RTT_BW_40;
+using ::WIFI_RTT_BW_5;
+using ::WIFI_RTT_BW_80;
+using ::WIFI_RTT_PREAMBLE_HE;
+using ::WIFI_RTT_PREAMBLE_HT;
+using ::WIFI_RTT_PREAMBLE_LEGACY;
+using ::WIFI_RTT_PREAMBLE_VHT;
+using ::WIFI_SCAN_FLAG_INTERRUPTED;
+using ::WIFI_SUCCESS;
+using ::WLAN_MAC_2_4_BAND;
+using ::WLAN_MAC_5_0_BAND;
+using ::WLAN_MAC_6_0_BAND;
+using ::frame_info;
+using ::frame_type;
+using ::fw_roaming_state_t;
+using ::mac_addr;
+using ::rtt_peer_type;
+using ::ssid_t;
+using ::transaction_id;
+using ::wifi_band;
+using ::wifi_cached_scan_results;
+using ::wifi_channel_info;
+using ::wifi_channel_stat;
+using ::wifi_channel_width;
+using ::wifi_error;
+using ::wifi_gscan_capabilities;
+using ::wifi_information_element;
+using ::wifi_interface_type;
+using ::wifi_latency_mode;
+using ::wifi_lci_information;
+using ::wifi_lcr_information;
+using ::wifi_motion_pattern;
+using ::wifi_power_scenario;
+using ::wifi_rate;
+using ::wifi_request_id;
+using ::wifi_ring_buffer_status;
+using ::wifi_roaming_capabilities;
+using ::wifi_roaming_config;
+using ::wifi_rtt_bw;
+using ::wifi_rtt_capabilities;
+using ::wifi_rtt_config;
+using ::wifi_rtt_preamble;
+using ::wifi_rtt_responder;
+using ::wifi_rtt_result;
+using ::wifi_rtt_status;
+using ::wifi_rtt_type;
+using ::wifi_rx_packet_fate;
+using ::wifi_rx_report;
+using ::wifi_scan_bucket_spec;
+using ::wifi_scan_cmd_params;
+using ::wifi_scan_result;
+using ::wifi_tx_packet_fate;
+using ::wifi_tx_report;
// APF capabilities supported by the iface.
struct PacketFilterCapabilities {
diff --git a/wifi/1.4/default/wifi_legacy_hal_stubs.h b/wifi/1.4/default/wifi_legacy_hal_stubs.h
index 577a545..c709680 100644
--- a/wifi/1.4/default/wifi_legacy_hal_stubs.h
+++ b/wifi/1.4/default/wifi_legacy_hal_stubs.h
@@ -17,13 +17,14 @@
#ifndef WIFI_LEGACY_HAL_STUBS_H_
#define WIFI_LEGACY_HAL_STUBS_H_
+#include <hardware_legacy/wifi_hal.h>
+
namespace android {
namespace hardware {
namespace wifi {
namespace V1_4 {
namespace implementation {
namespace legacy_hal {
-#include <hardware_legacy/wifi_hal.h>
bool initHalFuncTableWithStubs(wifi_hal_fn* hal_fn);
} // namespace legacy_hal