Merge "[Telephony] Implement PhysicalChannelConfig in HAL 1.6"
diff --git a/audio/7.0/IDevice.hal b/audio/7.0/IDevice.hal
index e30e545..d9e0ad2 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,
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 0951a9e..78cb51b 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/IStreamOutEventCallback.hal b/audio/7.0/IStreamOutEventCallback.hal
index 52e65d3..4de767f 100644
--- a/audio/7.0/IStreamOutEventCallback.hal
+++ b/audio/7.0/IStreamOutEventCallback.hal
@@ -42,7 +42,7 @@
* is TEST(metadata_tests, compatibility_R) [3]. The test for R compatibility for JNI
* marshalling is android.media.cts.AudioMetadataTest#testCompatibilityR [4].
*
- * R (audio HAL 7.0) defined keys are as follows [2]:
+ * R (audio HAL 6.0) defined keys are as follows [2]:
* "bitrate", int32
* "channel-mask", int32
* "mime", string
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..c0042db 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,11 @@
return isOutputDevice(stringToAudioDevice(device));
}
-static inline bool isVendorExtension(const std::string& device) {
+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 s.size() > vendorPrefix.size() && s.substr(0, vendorPrefix.size()) == vendorPrefix &&
+ std::all_of(s.begin() + vendorPrefix.size(), s.end(),
[](unsigned char c) { return c == '_' || std::isalnum(c); });
}
diff --git a/audio/common/7.0/types.hal b/audio/common/7.0/types.hal
index ed56c73..ed6d94f 100644
--- a/audio/common/7.0/types.hal
+++ b/audio/common/7.0/types.hal
@@ -270,14 +270,29 @@
*/
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;
AudioContentType contentType;
+ /** Tags from AudioTrack audio atttributes */
+ vec<AudioTag> tags;
+ AudioChannelMask channelMask;
/**
* Positive linear gain applied to the track samples. 0 being muted and 1 is no attenuation,
* 2 means double amplification...
@@ -294,6 +309,9 @@
/** Metadata of a record track for a StreamIn. */
struct RecordTrackMetadata {
AudioSource source;
+ /** Tags from AudioTrack audio atttributes */
+ vec<AudioTag> tags;
+ AudioChannelMask channelMask;
/**
* Positive linear gain applied to the track samples. 0 being muted and 1 is no attenuation,
* 2 means double amplification...
diff --git a/audio/common/all-versions/default/7.0/HidlUtils.cpp b/audio/common/all-versions/default/7.0/HidlUtils.cpp
index c985a70..de19faf 100644
--- a/audio/common/all-versions/default/7.0/HidlUtils.cpp
+++ b/audio/common/all-versions/default/7.0/HidlUtils.cpp
@@ -21,6 +21,7 @@
#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"
@@ -306,7 +307,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 +325,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;
}
@@ -800,6 +810,47 @@
return result;
}
+status_t HidlUtils::audioTagsFromHal(const char* halTags, hidl_vec<AudioTag>* tags) {
+ std::vector<std::string> strTags = utils::splitString(halTags, sAudioTagSeparator);
+ status_t result = NO_ERROR;
+ tags->resize(strTags.size());
+ size_t to = 0;
+ for (size_t from = 0; from < strTags.size(); ++from) {
+ if (xsd::isVendorExtension(strTags[from])) {
+ (*tags)[to++] = strTags[from];
+ } else {
+ 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 {
+ 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;
+}
+
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/HidlUtils.h b/audio/common/all-versions/default/HidlUtils.h
index a0bd1bc..8e9275c 100644
--- a/audio/common/all-versions/default/HidlUtils.h
+++ b/audio/common/all-versions/default/HidlUtils.h
@@ -77,6 +77,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,
@@ -107,6 +109,8 @@
AudioStreamType* streamType);
static status_t audioStreamTypeToHal(const AudioStreamType& streamType,
audio_stream_type_t* halStreamType);
+ static status_t audioTagsFromHal(const char* halTags, hidl_vec<AudioTag>* tags);
+ static status_t audioTagsToHal(const hidl_vec<AudioTag>& tags, char* halTags);
private:
static status_t audioIndexChannelMaskFromHal(audio_channel_mask_t halChannelMask,
@@ -126,6 +130,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.
diff --git a/audio/common/all-versions/default/tests/hidlutils_tests.cpp b/audio/common/all-versions/default/tests/hidlutils_tests.cpp
index 22571c0..fef88b4 100644
--- a/audio/common/all-versions/default/tests/hidlutils_tests.cpp
+++ b/audio/common/all-versions/default/tests/hidlutils_tests.cpp
@@ -589,16 +589,29 @@
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;
+ audio_config_t halConfig;
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigToHal(config, &halConfig));
+ AudioConfig configBack;
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigFromHal(halConfig, false /*isInput*/, &configBack));
+ EXPECT_EQ(config, configBack);
+}
+
+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.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;
@@ -707,3 +720,43 @@
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(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(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(halTwoTags, &twoTagsBack));
+ EXPECT_EQ(twoTags, twoTagsBack);
+}
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/Conversions.cpp b/audio/core/all-versions/default/Conversions.cpp
index 8e0a140..f1752cc 100644
--- a/audio/core/all-versions/default/Conversions.cpp
+++ b/audio/core/all-versions/default/Conversions.cpp
@@ -32,14 +32,10 @@
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
+#define CONVERT_CHECKED(expr, result) \
+ if (status_t status = (expr); status != NO_ERROR) { \
+ result = status; \
+ }
status_t deviceAddressToHal(const DeviceAddress& device, audio_devices_t* halDeviceType,
char* halDeviceAddress) {
@@ -97,6 +93,52 @@
}
return status;
}
+
+status_t sinkMetadataToHal(const SinkMetadata& sinkMetadata,
+ std::vector<record_track_metadata>* halTracks) {
+ status_t result = NO_ERROR;
+ if (halTracks != nullptr) {
+ halTracks->reserve(sinkMetadata.tracks.size());
+ }
+ for (auto& metadata : sinkMetadata.tracks) {
+ record_track_metadata halTrackMetadata{.gain = metadata.gain};
+ CONVERT_CHECKED(HidlUtils::audioSourceToHal(metadata.source, &halTrackMetadata.source),
+ result);
+#if MAJOR_VERSION >= 5
+ if (metadata.destination.getDiscriminator() ==
+ RecordTrackMetadata::Destination::hidl_discriminator::device) {
+ CONVERT_CHECKED(
+ deviceAddressToHal(metadata.destination.device(), &halTrackMetadata.dest_device,
+ halTrackMetadata.dest_device_address),
+ result);
+ }
+#endif
+ if (halTracks != nullptr) {
+ halTracks->push_back(std::move(halTrackMetadata));
+ }
+ }
+ return result;
+}
+
+status_t 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& metadata : sourceMetadata.tracks) {
+ playback_track_metadata_t halTrackMetadata{.gain = metadata.gain};
+ CONVERT_CHECKED(HidlUtils::audioUsageToHal(metadata.usage, &halTrackMetadata.usage),
+ result);
+ CONVERT_CHECKED(HidlUtils::audioContentTypeToHal(metadata.contentType,
+ &halTrackMetadata.content_type),
+ result);
+ if (halTracks != nullptr) {
+ halTracks->push_back(std::move(halTrackMetadata));
+ }
+ }
+ return result;
+}
#endif // MAJOR_VERSION >= 4
#if MAJOR_VERSION >= 7
@@ -135,6 +177,50 @@
}
return success;
}
+
+status_t sinkMetadataToHalV7(const SinkMetadata& sinkMetadata,
+ 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(bases.size());
+ }
+ auto baseIter = std::make_move_iterator(bases.begin());
+ for (auto& metadata : sinkMetadata.tracks) {
+ record_track_metadata_v7_t halTrackMetadata;
+ CONVERT_CHECKED(HidlUtils::audioChannelMaskToHal(metadata.channelMask,
+ &halTrackMetadata.channel_mask),
+ result);
+ CONVERT_CHECKED(HidlUtils::audioTagsToHal(metadata.tags, halTrackMetadata.tags), result);
+ if (halTracks != nullptr) {
+ halTrackMetadata.base = std::move(*baseIter++);
+ halTracks->push_back(std::move(halTrackMetadata));
+ }
+ }
+ return result;
+}
+
+status_t sourceMetadataToHalV7(const SourceMetadata& sourceMetadata,
+ 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(bases.size());
+ }
+ auto baseIter = std::make_move_iterator(bases.begin());
+ for (auto& metadata : sourceMetadata.tracks) {
+ playback_track_metadata_v7_t halTrackMetadata;
+ CONVERT_CHECKED(HidlUtils::audioChannelMaskToHal(metadata.channelMask,
+ &halTrackMetadata.channel_mask),
+ result);
+ CONVERT_CHECKED(HidlUtils::audioTagsToHal(metadata.tags, halTrackMetadata.tags), result);
+ if (halTracks != nullptr) {
+ halTrackMetadata.base = std::move(*baseIter++);
+ halTracks->push_back(std::move(halTrackMetadata));
+ }
+ }
+ return result;
+}
#endif
} // namespace implementation
diff --git a/audio/core/all-versions/default/Device.cpp b/audio/core/all-versions/default/Device.cpp
index bb69f0b..05c1066 100644
--- a/audio/core/all-versions/default/Device.cpp
+++ b/audio/core/all-versions/default/Device.cpp
@@ -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,7 +154,9 @@
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];
@@ -186,7 +189,9 @@
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];
@@ -248,6 +253,14 @@
#endif
const SourceMetadata& sourceMetadata,
openOutputStream_cb _hidl_cb) {
+#if MAJOR_VERSION <= 6
+ if (status_t status = sourceMetadataToHal(sourceMetadata, nullptr); status != NO_ERROR) {
+#else
+ if (status_t status = sourceMetadataToHalV7(sourceMetadata, 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 +284,15 @@
// 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 = sinkMetadataToHal(sinkMetadata, nullptr); status != NO_ERROR) {
+#else
+ if (status_t status = sinkMetadataToHalV7(sinkMetadata, nullptr); status != NO_ERROR) {
+#endif
+ _hidl_cb(analyzeStatus("sinkMetadataToHal", status), nullptr, AudioConfig{});
return Void();
}
// Pick the first one as the main.
diff --git a/audio/core/all-versions/default/Stream.cpp b/audio/core/all-versions/default/Stream.cpp
index c74079d..f964cbb 100644
--- a/audio/core/all-versions/default/Stream.cpp
+++ b/audio/core/all-versions/default/Stream.cpp
@@ -17,6 +17,7 @@
#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"
@@ -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.
diff --git a/audio/core/all-versions/default/StreamIn.cpp b/audio/core/all-versions/default/StreamIn.cpp
index ead7204..2c5e9f1 100644
--- a/audio/core/all-versions/default/StreamIn.cpp
+++ b/audio/core/all-versions/default/StreamIn.cpp
@@ -478,31 +478,70 @@
}
#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)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 = sinkMetadataToHalV7(sinkMetadata, &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 = sinkMetadataToHalV7(sinkMetadata, &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;
diff --git a/audio/core/all-versions/default/StreamOut.cpp b/audio/core/all-versions/default/StreamOut.cpp
index 5633cbb..ffd3b6b 100644
--- a/audio/core/all-versions/default/StreamOut.cpp
+++ b/audio/core/all-versions/default/StreamOut.cpp
@@ -17,6 +17,7 @@
#define LOG_TAG "StreamOutHAL"
#include "core/default/StreamOut.h"
+#include "core/default/Conversions.h"
#include "core/default/Util.h"
//#define LOG_NDEBUG 0
@@ -585,26 +586,71 @@
}
#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)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 = sourceMetadataToHalV7(sourceMetadata, &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 = sourceMetadataToHalV7(sourceMetadata, &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
}
diff --git a/audio/core/all-versions/default/include/core/default/Conversions.h b/audio/core/all-versions/default/include/core/default/Conversions.h
index 2372771..61720d5 100644
--- a/audio/core/all-versions/default/include/core/default/Conversions.h
+++ b/audio/core/all-versions/default/include/core/default/Conversions.h
@@ -35,12 +35,6 @@
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,
@@ -49,6 +43,10 @@
#if MAJOR_VERSION >= 4
bool halToMicrophoneCharacteristics(MicrophoneInfo* pDst,
const struct audio_microphone_characteristic_t& src);
+status_t sinkMetadataToHal(const SinkMetadata& sinkMetadata,
+ std::vector<record_track_metadata>* halTracks);
+status_t sourceMetadataToHal(const SourceMetadata& sourceMetadata,
+ std::vector<playback_track_metadata_t>* halTracks);
#endif
#if MAJOR_VERSION <= 6
@@ -69,6 +67,11 @@
#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);
+// Overloading isn't convenient when passing a nullptr.
+status_t sinkMetadataToHalV7(const SinkMetadata& sinkMetadata,
+ std::vector<record_track_metadata_v7_t>* halTracks);
+status_t sourceMetadataToHalV7(const SourceMetadata& sourceMetadata,
+ std::vector<playback_track_metadata_v7_t>* halTracks);
#endif
} // namespace implementation
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..2a4d226 100644
--- a/audio/core/all-versions/default/include/core/default/Device.h
+++ b/audio/core/all-versions/default/include/core/default/Device.h
@@ -146,6 +146,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 +163,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/StreamIn.h b/audio/core/all-versions/default/include/core/default/StreamIn.h
index b861c6c..651b3a6 100644
--- a/audio/core/all-versions/default/include/core/default/StreamIn.h
+++ b/audio/core/all-versions/default/include/core/default/StreamIn.h
@@ -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..b8e8515 100644
--- a/audio/core/all-versions/default/include/core/default/StreamOut.h
+++ b/audio/core/all-versions/default/include/core/default/StreamOut.h
@@ -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/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp b/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp
index 9a4a8b2..5076dcf 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),
+ {} /* tags */,
+ toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO),
+ volume}}};
+ 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),
+ {},
+ toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO),
+ 0.1},
+ {toString(xsd::AudioUsage::AUDIO_USAGE_VOICE_COMMUNICATION),
+ toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_SPEECH),
+ {}, // tags
+ toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_MONO),
+ 1.0},
+ {toString(xsd::AudioUsage::AUDIO_USAGE_ALARM),
+ toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_SONIFICATION),
+ {}, // tags
+ toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO),
+ 0.0},
+ {toString(xsd::AudioUsage::AUDIO_USAGE_ASSISTANT),
+ toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_UNKNOWN),
+ {},
+ toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_MONO),
+ 0.3}}}
+ ));
+ // 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..0f0cdcf 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(SingleConfigOutputStream);
-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(SingleConfigInputStream);
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..0f52a90 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,433 @@
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(InputBufferSizeInvalidConfig);
+
+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));
+}
+
+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 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 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(InputStreamInvalidConfig);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(InputStreamInvalidAddress);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(InputStreamInvalidMetadata);
+
+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);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(OutputStreamInvalidConfig);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(OutputStreamInvalidAddress);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(OutputStreamInvalidMetadata);
+
+#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, const AudioConfigBase& base) {
+ AudioConfigBase invalidFormat = base;
+ invalidFormat.format = "random_string";
+ ASSERT_RESULT(invalidArgsOrNotSupported, stream->setAudioProperties(invalidFormat));
+
+ AudioConfigBase invalidChannelMask = base;
+ invalidChannelMask.channelMask = "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(), audioConfig.base));
+
+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..f145b60 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,6 +928,8 @@
const SourceMetadata initMetadata = {
{ { toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC),
+ {},
+ toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO),
1 /* gain */ } }};
#endif
};
@@ -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
};
diff --git a/audio/effect/all-versions/vts/functional/VtsHalAudioEffectTargetTest.cpp b/audio/effect/all-versions/vts/functional/VtsHalAudioEffectTargetTest.cpp
index d39fbcd..35ff869 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
@@ -309,6 +311,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 = "random_string";
+ result.push_back(std::move(invalidFormat));
+ EffectBufferConfig invalidChannelMask = src;
+ invalidChannelMask.base.channelMask = "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&) {});
@@ -413,6 +456,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 +521,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 +543,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..d598344
--- /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-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..83a85b2
--- /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-ndk_platform"],
+ shared_libs: ["libbinder_ndk"],
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
+ require_root: true,
+}
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/audiocontrol/aidl/default/Android.bp b/automotive/audiocontrol/aidl/default/Android.bp
index faf7ad2..427709a 100644
--- a/automotive/audiocontrol/aidl/default/Android.bp
+++ b/automotive/audiocontrol/aidl/default/Android.bp
@@ -22,15 +22,18 @@
generated_sources: ["audio_policy_configuration_V7_0"],
header_libs: ["libxsdc-utils"],
shared_libs: [
+ "android.frameworks.automotive.powerpolicy-ndk_platform",
"android.hardware.automotive.audiocontrol-ndk_platform",
"libbase",
"libbinder_ndk",
- "liblog",
"libcutils",
+ "liblog",
+ "libpowerpolicyclient",
"libxml2",
],
srcs: [
"AudioControl.cpp",
"main.cpp",
+ "PowerPolicyClient.cpp",
],
}
diff --git a/automotive/audiocontrol/aidl/default/PowerPolicyClient.cpp b/automotive/audiocontrol/aidl/default/PowerPolicyClient.cpp
new file mode 100644
index 0000000..7657337
--- /dev/null
+++ b/automotive/audiocontrol/aidl/default/PowerPolicyClient.cpp
@@ -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.
+ */
+
+#include "PowerPolicyClient.h"
+#include "AudioControl.h"
+
+#include <android-base/logging.h>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace audiocontrol {
+
+namespace aafap = aidl::android::frameworks::automotive::powerpolicy;
+
+using aafap::CarPowerPolicy;
+using aafap::CarPowerPolicyFilter;
+using aafap::PowerComponent;
+using ::android::frameworks::automotive::powerpolicy::hasComponent;
+using ::ndk::ScopedAStatus;
+
+namespace {
+
+constexpr PowerComponent kAudioComponent = PowerComponent::AUDIO;
+
+} // namespace
+
+PowerPolicyClient::PowerPolicyClient(std::shared_ptr<AudioControl> audioControl)
+ : mAudioControl(audioControl) {}
+
+void PowerPolicyClient::onInitFailed() {
+ LOG(ERROR) << "Initializing power policy client failed";
+}
+
+std::vector<PowerComponent> PowerPolicyClient::getComponentsOfInterest() {
+ std::vector<PowerComponent> components{kAudioComponent};
+ return components;
+}
+
+ScopedAStatus PowerPolicyClient::onPolicyChanged(const CarPowerPolicy& powerPolicy) {
+ if (hasComponent(powerPolicy.enabledComponents, kAudioComponent)) {
+ LOG(DEBUG) << "Power policy: Audio component is enabled";
+ // TODO(b/173719953): Do something when AUDIO is enabled.
+ } else if (hasComponent(powerPolicy.disabledComponents, kAudioComponent)) {
+ LOG(DEBUG) << "Power policy: Audio component is disabled";
+ // TODO(b/173719953): Do something when AUDIO is disabled.
+ }
+ return ScopedAStatus::ok();
+}
+
+} // namespace audiocontrol
+} // namespace automotive
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/automotive/audiocontrol/aidl/default/PowerPolicyClient.h b/automotive/audiocontrol/aidl/default/PowerPolicyClient.h
new file mode 100644
index 0000000..0b4d5b6
--- /dev/null
+++ b/automotive/audiocontrol/aidl/default/PowerPolicyClient.h
@@ -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.
+ */
+
+#ifndef AUTOMOTIVE_AUDIOCONTROL_AIDL_DEFAULT_POWERPOLICYCLIENT_H_
+#define AUTOMOTIVE_AUDIOCONTROL_AIDL_DEFAULT_POWERPOLICYCLIENT_H_
+
+#include "PowerPolicyClientBase.h"
+
+#include <memory>
+
+namespace aidl::android::hardware::automotive::audiocontrol {
+
+class AudioControl;
+
+class PowerPolicyClient
+ : public ::android::frameworks::automotive::powerpolicy::PowerPolicyClientBase {
+ public:
+ explicit PowerPolicyClient(std::shared_ptr<AudioControl> audioControl);
+
+ void onInitFailed();
+ std::vector<::aidl::android::frameworks::automotive::powerpolicy::PowerComponent>
+ getComponentsOfInterest() override;
+ ::ndk::ScopedAStatus onPolicyChanged(
+ const ::aidl::android::frameworks::automotive::powerpolicy::CarPowerPolicy&) override;
+
+ private:
+ std::shared_ptr<AudioControl> mAudioControl;
+};
+
+} // namespace aidl::android::hardware::automotive::audiocontrol
+
+#endif // AUTOMOTIVE_AUDIOCONTROL_AIDL_DEFAULT_POWERPOLICYCLIENT_H_
diff --git a/automotive/audiocontrol/aidl/default/main.cpp b/automotive/audiocontrol/aidl/default/main.cpp
index 996665f..9b259fc 100644
--- a/automotive/audiocontrol/aidl/default/main.cpp
+++ b/automotive/audiocontrol/aidl/default/main.cpp
@@ -15,22 +15,28 @@
*/
#include "AudioControl.h"
+#include "PowerPolicyClient.h"
#include <android-base/logging.h>
#include <android/binder_manager.h>
#include <android/binder_process.h>
using aidl::android::hardware::automotive::audiocontrol::AudioControl;
+using aidl::android::hardware::automotive::audiocontrol::PowerPolicyClient;
int main() {
ABinderProcess_setThreadPoolMaxThreadCount(0);
- std::shared_ptr<AudioControl> audioControl = ndk::SharedRefBase::make<AudioControl>();
+ std::shared_ptr<AudioControl> audioControl = ::ndk::SharedRefBase::make<AudioControl>();
const std::string instance = std::string() + AudioControl::descriptor + "/default";
binder_status_t status =
AServiceManager_addService(audioControl->asBinder().get(), instance.c_str());
CHECK(status == STATUS_OK);
+ std::shared_ptr<PowerPolicyClient> powerPolicyClient =
+ ::ndk::SharedRefBase::make<PowerPolicyClient>(audioControl);
+ powerPolicyClient->init();
+
ABinderProcess_joinThreadPool();
return EXIT_FAILURE; // should not reach
}
diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h b/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h
index 8ef2b60..73513f4 100644
--- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h
+++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h
@@ -1153,6 +1153,47 @@
.changeMode = VehiclePropertyChangeMode::ON_CHANGE,
},
},
+ {
+ .config =
+ {
+ .prop = toInt(VehicleProperty::CLUSTER_SWITCH_UI),
+ .access = VehiclePropertyAccess::READ,
+ .changeMode = VehiclePropertyChangeMode::ON_CHANGE,
+ },
+ },
+ {
+ .config =
+ {
+ .prop = toInt(VehicleProperty::CLUSTER_DISPLAY_STATE),
+ .access = VehiclePropertyAccess::READ,
+ .changeMode = VehiclePropertyChangeMode::ON_CHANGE,
+ },
+ },
+ {
+ .config =
+ {
+ .prop = toInt(VehicleProperty::CLUSTER_REPORT_STATE),
+ .access = VehiclePropertyAccess::WRITE,
+ .changeMode = VehiclePropertyChangeMode::ON_CHANGE,
+ .configArray = {0, 0, 0, 9, 0, 0, 0, 0, 16},
+ },
+ },
+ {
+ .config =
+ {
+ .prop = toInt(VehicleProperty::CLUSTER_REQUEST_DISPLAY),
+ .access = VehiclePropertyAccess::WRITE,
+ .changeMode = VehiclePropertyChangeMode::ON_CHANGE,
+ },
+ },
+ {
+ .config =
+ {
+ .prop = toInt(VehicleProperty::CLUSTER_NAVIGATION_STATE_LEGACY),
+ .access = VehiclePropertyAccess::WRITE,
+ .changeMode = VehiclePropertyChangeMode::ON_CHANGE,
+ },
+ },
};
} // impl
diff --git a/automotive/vehicle/2.0/types.hal b/automotive/vehicle/2.0/types.hal
index 931fee4..ed75e1d 100644
--- a/automotive/vehicle/2.0/types.hal
+++ b/automotive/vehicle/2.0/types.hal
@@ -2773,7 +2773,7 @@
*
* int32[0]: 42 // request id
* int32[1]: 11 // Android id of the created user
- * int32[2]: 3 // Android flags (ephemeral guest) of the created user
+ * int32[2]: 6 // Android flags (ephemeral guest) of the created user
* int32[3]: 10 // current user
* int32[4]: 0 // current user flags (none)
* int32[5]: 3 // number of users
@@ -2782,7 +2782,7 @@
* int32[8]: 10 // 2nd user (user 10)
* int32[9]: 0 // 2nd user flags (none)
* int32[19]: 11 // 3rd user (user 11)
- * int32[11]: 3 // 3rd user flags (ephemeral guest)
+ * int32[11]: 6 // 3rd user flags (ephemeral guest)
* string: "ElGuesto" // name of the new user
*
* Then if the request succeeded, the HAL would return:
@@ -3027,6 +3027,115 @@
| VehiclePropertyGroup:SYSTEM
| VehiclePropertyType:INT64
| VehicleArea:GLOBAL),
+
+ /**
+ * Starts the ClusterUI in cluster display.
+ *
+ * int32[0]: the type of ClusterUI to show
+ * 0 indicates ClusterHome, that is a home screen of cluster display, and provides
+ * the default UI and a kind of launcher functionality for cluster display.
+ * the other values are followed by OEM's definition.
+ *
+ * @change_mode VehiclePropertyChangeMode:ON_CHANGE
+ * @access VehiclePropertyAccess:READ
+ */
+ CLUSTER_SWITCH_UI = (
+ 0x0F34
+ | VehiclePropertyGroup:SYSTEM
+ | VehiclePropertyType:INT32
+ | VehicleArea:GLOBAL),
+
+ /**
+ * Changes the state of the cluster display.
+ *
+ * int32[0]: on/off: 0 - off, 1 - on, -1 - don't care
+ * int32[1]: width: positive number - actual width in pixels
+ -1 - don't care (should set "don't care" both width and height)
+ * int32[2]: height: ditto with width
+ * int32[3]: Inset - left: positive number - actual left inset value in pixels
+ -1 - don't care (should set "don't care" all Inset fields)
+ * int32[4]: Inset - top
+ * int32[5]: Inset - right
+ * int32[6]: Inset - bottom
+ *
+ * @change_mode VehiclePropertyChangeMode:ON_CHANGE
+ * @access VehiclePropertyAccess:READ
+ */
+ CLUSTER_DISPLAY_STATE = (
+ 0x0F35
+ | VehiclePropertyGroup:SYSTEM
+ | VehiclePropertyType:INT32_VEC
+ | VehicleArea:GLOBAL),
+
+ /**
+ * Reports the current display state and ClusterUI state.
+ *
+ * ClusterHome will send this message when it handles CLUSTER_SWITCH_UI, CLUSTER_DISPLAY_STATE.
+ *
+ * In addition, ClusterHome should send this message when it starts for the first time.
+ * When ClusterOS receives this message and if the internal expectation is different with the
+ * received message, then it should send CLUSTER_SWITCH_UI, CLUSTER_DISPLAY_STATE again to
+ * match the state.
+ *
+ * int32[0]: on/off: 0 - off, 1 - on
+ * int32[1]: width
+ * int32[2]: height
+ * int32[3]: Inset - left
+ * int32[4]: Inset - top
+ * int32[5]: Inset - right
+ * int32[6]: Inset - bottom
+ * int32[7]: the type of ClusterUI in the fullscreen or main screen.
+ * 0 indicates ClusterHome.
+ * the other values are followed by OEM's definition.
+ * int32[8]: the type of ClusterUI in sub screen if the currently two UIs are shown.
+ * -1 indicates the area isn't used any more.
+ * bytes: the array to represent the availability of ClusterUI.
+ * 0 indicates non-available and 1 indicates available.
+ * For example, let's assume a car supports 3 UI like HOME, MAPS, CALL and it only supports
+ * CALL UI only when the cellular network is available. Then, if the nework is avaibale,
+ * it'll send [1 1 1], and if it's out of network, it'll send [1 1 0].
+ *
+ * @change_mode VehiclePropertyChangeMode:ON_CHANGE
+ * @access VehiclePropertyAccess:WRITE
+ */
+ CLUSTER_REPORT_STATE = (
+ 0x0F36
+ | VehiclePropertyGroup:SYSTEM
+ | VehiclePropertyType:MIXED
+ | VehicleArea:GLOBAL),
+
+ /**
+ * Requests to change the cluster display state to show some ClusterUI.
+ *
+ * When the current display state is off and ClusterHome sends this message to ClusterOS to
+ * request to turn the display on to show some specific ClusterUI.
+ * ClusterOS should response this with CLUSTER_DISPLAY_STATE.
+ *
+ * int32[0]: the type of ClusterUI to show
+ *
+ * @change_mode VehiclePropertyChangeMode:ON_CHANGE
+ * @access VehiclePropertyAccess:WRITE
+ */
+ CLUSTER_REQUEST_DISPLAY = (
+ 0x0F37
+ | VehiclePropertyGroup:SYSTEM
+ | VehiclePropertyType:INT32
+ | VehicleArea:GLOBAL),
+
+ /**
+ * Informs the current navigation state.
+ *
+ * bytes: the serialized message of NavigationStateProto.
+ *
+ * @change_mode VehiclePropertyChangeMode:ON_CHANGE
+ * @access VehiclePropertyAccess:WRITE
+ */
+ CLUSTER_NAVIGATION_STATE_LEGACY = (
+ 0x0F38
+ | VehiclePropertyGroup:SYSTEM
+ | VehiclePropertyType:BYTES
+ | VehicleArea:GLOBAL),
+
};
/**
@@ -3691,7 +3800,10 @@
* events.
*/
struct VehiclePropValue {
- /** Time is elapsed nanoseconds since boot */
+ /**
+ * Time is elapsed nanoseconds since boot. It's equivalent to
+ * {@code SystemClock.elapsedRealtimeNano()}.
+ */
int64_t timestamp;
/**
diff --git a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/AcquiredInfo.aidl b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/AcquiredInfo.aidl
index a05fad9..56d097d 100644
--- a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/AcquiredInfo.aidl
+++ b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/AcquiredInfo.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/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/Error.aidl b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/Error.aidl
index 15fcbf9..cba6ec5 100644
--- a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/Error.aidl
+++ b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/Error.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/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/FaceSensorType.aidl b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/FaceSensorType.aidl
index 943129e..b08c345 100644
--- a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/FaceSensorType.aidl
+++ b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/FaceSensorType.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/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/IFace.aidl b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/IFace.aidl
index 518fb14..65afaa4 100644
--- a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/IFace.aidl
+++ b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/IFace.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/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/ISession.aidl b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/ISession.aidl
index 5d9a430..7d70971 100644
--- a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/ISession.aidl
+++ b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/ISession.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/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/ISessionCallback.aidl b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/ISessionCallback.aidl
index caf65ae..4cea0f0 100644
--- a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/ISessionCallback.aidl
+++ b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/ISessionCallback.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
@@ -33,5 +34,5 @@
void onEnrollmentsEnumerated(in int[] enrollmentIds);
void onEnrollmentsRemoved(in int[] enrollmentIds);
void onAuthenticatorIdRetrieved(in long authenticatorId);
- void onAuthenticatorIdInvalidated();
+ void onAuthenticatorIdInvalidated(in long newAuthenticatorId);
}
diff --git a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/SensorProps.aidl b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/SensorProps.aidl
index 365ae58..a52829c 100644
--- a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/SensorProps.aidl
+++ b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/SensorProps.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/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/SessionState.aidl b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/SessionState.aidl
index 8e5139b..12a5eab 100644
--- a/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/SessionState.aidl
+++ b/biometrics/face/aidl/aidl_api/android.hardware.biometrics.face/current/android/hardware/biometrics/face/SessionState.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/biometrics/face/aidl/android/hardware/biometrics/face/ISessionCallback.aidl b/biometrics/face/aidl/android/hardware/biometrics/face/ISessionCallback.aidl
index fd4a648..d59ce2e 100644
--- a/biometrics/face/aidl/android/hardware/biometrics/face/ISessionCallback.aidl
+++ b/biometrics/face/aidl/android/hardware/biometrics/face/ISessionCallback.aidl
@@ -195,6 +195,9 @@
* SessionState::INVALIDATING_AUTHENTICATOR_ID.
*
* See ISession#invalidateAuthenticatorId for more information.
+ *
+ * @param newAuthenticatorId The new entropy-encoded random identifier associated with the
+ * current set of enrollments.
*/
- void onAuthenticatorIdInvalidated();
+ void onAuthenticatorIdInvalidated(in long newAuthenticatorId);
}
diff --git a/biometrics/face/aidl/vts/VtsHalBiometricsFaceTargetTest.cpp b/biometrics/face/aidl/vts/VtsHalBiometricsFaceTargetTest.cpp
index 5f10306..f6d0aab 100644
--- a/biometrics/face/aidl/vts/VtsHalBiometricsFaceTargetTest.cpp
+++ b/biometrics/face/aidl/vts/VtsHalBiometricsFaceTargetTest.cpp
@@ -103,7 +103,9 @@
return ndk::ScopedAStatus::ok();
}
- ndk::ScopedAStatus onAuthenticatorIdInvalidated() override { return ndk::ScopedAStatus::ok(); }
+ ndk::ScopedAStatus onAuthenticatorIdInvalidated(int64_t /*newAuthenticatorId*/) override {
+ return ndk::ScopedAStatus::ok();
+ }
private:
std::promise<SessionCallbackInvocation> invocation_promise_;
diff --git a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/AcquiredInfo.aidl b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/AcquiredInfo.aidl
index df30dca..d5f2ed2 100644
--- a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/AcquiredInfo.aidl
+++ b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/AcquiredInfo.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/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/Error.aidl b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/Error.aidl
index 6bd71b2..aec499f 100644
--- a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/Error.aidl
+++ b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/Error.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/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/FingerprintSensorType.aidl b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/FingerprintSensorType.aidl
index 14bfece..784e1d0 100644
--- a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/FingerprintSensorType.aidl
+++ b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/FingerprintSensorType.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/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/IFingerprint.aidl b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/IFingerprint.aidl
index c5c6786..51b4c63 100644
--- a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/IFingerprint.aidl
+++ b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/IFingerprint.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/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/ISession.aidl b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/ISession.aidl
index 4df7981..185d86f 100644
--- a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/ISession.aidl
+++ b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/ISession.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/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/ISessionCallback.aidl b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/ISessionCallback.aidl
index b7a48a5..cf663a5 100644
--- a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/ISessionCallback.aidl
+++ b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/ISessionCallback.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
@@ -33,5 +34,5 @@
void onEnrollmentsEnumerated(in int[] enrollmentIds);
void onEnrollmentsRemoved(in int[] enrollmentIds);
void onAuthenticatorIdRetrieved(in long authenticatorId);
- void onAuthenticatorIdInvalidated();
+ void onAuthenticatorIdInvalidated(in long newAuthenticatorId);
}
diff --git a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/SensorProps.aidl b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/SensorProps.aidl
index 8c779ab..f8a40a9 100644
--- a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/SensorProps.aidl
+++ b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/SensorProps.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/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/SessionState.aidl b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/SessionState.aidl
index e9daef1..3453f93 100644
--- a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/SessionState.aidl
+++ b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/SessionState.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/biometrics/fingerprint/aidl/android/hardware/biometrics/fingerprint/ISessionCallback.aidl b/biometrics/fingerprint/aidl/android/hardware/biometrics/fingerprint/ISessionCallback.aidl
index 4387f98..fde1df7 100644
--- a/biometrics/fingerprint/aidl/android/hardware/biometrics/fingerprint/ISessionCallback.aidl
+++ b/biometrics/fingerprint/aidl/android/hardware/biometrics/fingerprint/ISessionCallback.aidl
@@ -195,6 +195,9 @@
* SessionState::INVALIDATING_AUTHENTICATOR_ID.
*
* See ISession#invalidateAuthenticatorId for more information.
+ *
+ * @param newAuthenticatorId The new entropy-encoded random identifier associated with the
+ * current set of enrollments.
*/
- void onAuthenticatorIdInvalidated();
+ void onAuthenticatorIdInvalidated(in long newAuthenticatorId);
}
diff --git a/biometrics/fingerprint/aidl/vts/VtsHalBiometricsFingerprintTargetTest.cpp b/biometrics/fingerprint/aidl/vts/VtsHalBiometricsFingerprintTargetTest.cpp
index adb98ac..ddbc0fe 100644
--- a/biometrics/fingerprint/aidl/vts/VtsHalBiometricsFingerprintTargetTest.cpp
+++ b/biometrics/fingerprint/aidl/vts/VtsHalBiometricsFingerprintTargetTest.cpp
@@ -110,7 +110,7 @@
return ndk::ScopedAStatus::ok();
}
- ndk::ScopedAStatus onAuthenticatorIdInvalidated() override {
+ ndk::ScopedAStatus onAuthenticatorIdInvalidated(int64_t /*newAuthenticatorId*/) override {
return ndk::ScopedAStatus::ok();
}
diff --git a/bluetooth/audio/2.1/default/LeAudioAudioProvider.cpp b/bluetooth/audio/2.1/default/LeAudioAudioProvider.cpp
index 1fa2dce..9c2b4fe 100644
--- a/bluetooth/audio/2.1/default/LeAudioAudioProvider.cpp
+++ b/bluetooth/audio/2.1/default/LeAudioAudioProvider.cpp
@@ -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 */
diff --git a/bluetooth/audio/2.1/default/session/BluetoothAudioSupportedCodecsDB.cpp b/bluetooth/audio/2.1/default/session/BluetoothAudioSupportedCodecsDB.cpp
index d15db49..0937f44 100644
--- a/bluetooth/audio/2.1/default/session/BluetoothAudioSupportedCodecsDB.cpp
+++ b/bluetooth/audio/2.1/default/session/BluetoothAudioSupportedCodecsDB.cpp
@@ -409,12 +409,14 @@
}
bool IsSoftwarePcmConfigurationValid_2_1(const PcmParameters& pcm_config) {
- if ((pcm_config.sampleRate != SampleRate::RATE_44100 &&
- pcm_config.sampleRate != SampleRate::RATE_48000 &&
+ if ((pcm_config.sampleRate != SampleRate::RATE_96000 &&
pcm_config.sampleRate != SampleRate::RATE_88200 &&
- pcm_config.sampleRate != SampleRate::RATE_96000 &&
+ pcm_config.sampleRate != SampleRate::RATE_48000 &&
+ pcm_config.sampleRate != SampleRate::RATE_44100 &&
+ pcm_config.sampleRate != SampleRate::RATE_32000 &&
+ pcm_config.sampleRate != SampleRate::RATE_24000 &&
pcm_config.sampleRate != SampleRate::RATE_16000 &&
- pcm_config.sampleRate != SampleRate::RATE_24000) ||
+ pcm_config.sampleRate != SampleRate::RATE_8000) ||
(pcm_config.bitsPerSample != BitsPerSample::BITS_16 &&
pcm_config.bitsPerSample != BitsPerSample::BITS_24 &&
pcm_config.bitsPerSample != BitsPerSample::BITS_32) ||
diff --git a/bluetooth/audio/2.1/vts/functional/VtsHalBluetoothAudioV2_1TargetTest.cpp b/bluetooth/audio/2.1/vts/functional/VtsHalBluetoothAudioV2_1TargetTest.cpp
index 37d1281..95903d1 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};
@@ -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};
diff --git a/cas/1.0/vts/functional/Android.bp b/cas/1.0/vts/functional/Android.bp
index 82dc568..a065b85 100644
--- a/cas/1.0/vts/functional/Android.bp
+++ b/cas/1.0/vts/functional/Android.bp
@@ -20,6 +20,8 @@
srcs: ["VtsHalCasV1_0TargetTest.cpp"],
static_libs: [
"android.hardware.cas@1.0",
+ "android.hardware.cas@1.1",
+ "android.hardware.cas@1.2",
"android.hardware.cas.native@1.0",
"android.hidl.allocator@1.0",
"android.hidl.memory@1.0",
diff --git a/cas/1.0/vts/functional/VtsHalCasV1_0TargetTest.cpp b/cas/1.0/vts/functional/VtsHalCasV1_0TargetTest.cpp
index 7f5d988..df0c859 100644
--- a/cas/1.0/vts/functional/VtsHalCasV1_0TargetTest.cpp
+++ b/cas/1.0/vts/functional/VtsHalCasV1_0TargetTest.cpp
@@ -22,6 +22,7 @@
#include <android/hardware/cas/1.0/IDescramblerBase.h>
#include <android/hardware/cas/1.0/IMediaCasService.h>
#include <android/hardware/cas/1.0/types.h>
+#include <android/hardware/cas/1.2/IMediaCasService.h>
#include <android/hardware/cas/native/1.0/IDescrambler.h>
#include <android/hardware/cas/native/1.0/types.h>
#include <binder/MemoryDealer.h>
@@ -212,6 +213,10 @@
class MediaCasHidlTest : public testing::TestWithParam<std::string> {
public:
virtual void SetUp() override {
+ if (android::hardware::cas::V1_2::IMediaCasService::getService(GetParam()) == nullptr) {
+ ALOGI("Descrambler is need to be tested before cas@1.2.");
+ mIsTestDescrambler = true;
+ }
mService = IMediaCasService::getService(GetParam());
ASSERT_NE(mService, nullptr);
}
@@ -226,6 +231,7 @@
sp<ICas> mMediaCas;
sp<IDescramblerBase> mDescramblerBase;
sp<MediaCasListener> mCasListener;
+ bool mIsTestDescrambler = false;
typedef struct _OobInputTestParams {
const SubSample* subSamples;
uint32_t numSubSamples;
@@ -270,8 +276,14 @@
auto descramblerStatus = mService->createDescrambler(caSystemId);
if (!descramblerStatus.isOk()) {
- return ::testing::AssertionFailure();
+ if (mIsTestDescrambler) {
+ return ::testing::AssertionFailure();
+ } else {
+ ALOGI("Skip Descrambler test since it's not required in cas@1.2.");
+ return ::testing::AssertionSuccess();
+ }
}
+ mIsTestDescrambler = true;
mDescramblerBase = descramblerStatus;
return ::testing::AssertionResult(mDescramblerBase != nullptr);
}
@@ -494,14 +506,15 @@
returnStatus = mMediaCas->setSessionPrivateData(streamSessionId, hidlPvtData);
EXPECT_TRUE(returnStatus.isOk());
EXPECT_EQ(Status::OK, returnStatus);
+ if (mIsTestDescrambler) {
+ returnStatus = mDescramblerBase->setMediaCasSession(sessionId);
+ EXPECT_TRUE(returnStatus.isOk());
+ EXPECT_EQ(Status::OK, returnStatus);
- returnStatus = mDescramblerBase->setMediaCasSession(sessionId);
- EXPECT_TRUE(returnStatus.isOk());
- EXPECT_EQ(Status::OK, returnStatus);
-
- returnStatus = mDescramblerBase->setMediaCasSession(streamSessionId);
- EXPECT_TRUE(returnStatus.isOk());
- EXPECT_EQ(Status::OK, returnStatus);
+ returnStatus = mDescramblerBase->setMediaCasSession(streamSessionId);
+ EXPECT_TRUE(returnStatus.isOk());
+ EXPECT_EQ(Status::OK, returnStatus);
+ }
hidl_vec<uint8_t> hidlNullPtr;
hidlNullPtr.setToExternal(static_cast<uint8_t*>(nullptr), 0);
@@ -543,29 +556,32 @@
EXPECT_TRUE(returnStatus.isOk());
EXPECT_EQ(Status::OK, returnStatus);
- EXPECT_FALSE(mDescramblerBase->requiresSecureDecoderComponent("video/avc"));
+ if (mIsTestDescrambler) {
+ EXPECT_FALSE(mDescramblerBase->requiresSecureDecoderComponent("video/avc"));
- sp<IDescrambler> descrambler;
- descrambler = IDescrambler::castFrom(mDescramblerBase);
- ASSERT_NE(descrambler, nullptr);
+ sp<IDescrambler> descrambler;
+ descrambler = IDescrambler::castFrom(mDescramblerBase);
+ ASSERT_NE(descrambler, nullptr);
- Status descrambleStatus = Status::OK;
- sp<IMemory> dataMemory;
+ Status descrambleStatus = Status::OK;
+ sp<IMemory> dataMemory;
- ASSERT_TRUE(descrambleTestInputBuffer(descrambler, &descrambleStatus, &dataMemory));
- EXPECT_EQ(Status::OK, descrambleStatus);
+ ASSERT_TRUE(descrambleTestInputBuffer(descrambler, &descrambleStatus, &dataMemory));
+ EXPECT_EQ(Status::OK, descrambleStatus);
- ASSERT_NE(nullptr, dataMemory.get());
- uint8_t* opBuffer = static_cast<uint8_t*>(static_cast<void*>(dataMemory->unsecurePointer()));
+ ASSERT_NE(nullptr, dataMemory.get());
+ uint8_t* opBuffer =
+ static_cast<uint8_t*>(static_cast<void*>(dataMemory->unsecurePointer()));
- int compareResult =
- memcmp(static_cast<const void*>(opBuffer), static_cast<const void*>(kOutRefBinaryBuffer),
- sizeof(kOutRefBinaryBuffer));
- EXPECT_EQ(0, compareResult);
+ int compareResult =
+ memcmp(static_cast<const void*>(opBuffer),
+ static_cast<const void*>(kOutRefBinaryBuffer), sizeof(kOutRefBinaryBuffer));
+ EXPECT_EQ(0, compareResult);
- returnStatus = mDescramblerBase->release();
- EXPECT_TRUE(returnStatus.isOk());
- EXPECT_EQ(Status::OK, returnStatus);
+ returnStatus = mDescramblerBase->release();
+ EXPECT_TRUE(returnStatus.isOk());
+ EXPECT_EQ(Status::OK, returnStatus);
+ }
returnStatus = mMediaCas->release();
EXPECT_TRUE(returnStatus.isOk());
@@ -590,13 +606,15 @@
EXPECT_TRUE(returnStatus.isOk());
EXPECT_EQ(Status::OK, returnStatus);
- returnStatus = mDescramblerBase->setMediaCasSession(sessionId);
- EXPECT_TRUE(returnStatus.isOk());
- EXPECT_EQ(Status::ERROR_CAS_SESSION_NOT_OPENED, returnStatus);
+ if (mIsTestDescrambler) {
+ returnStatus = mDescramblerBase->setMediaCasSession(sessionId);
+ EXPECT_TRUE(returnStatus.isOk());
+ EXPECT_EQ(Status::ERROR_CAS_SESSION_NOT_OPENED, returnStatus);
- returnStatus = mDescramblerBase->setMediaCasSession(streamSessionId);
- EXPECT_TRUE(returnStatus.isOk());
- EXPECT_EQ(Status::ERROR_CAS_SESSION_NOT_OPENED, returnStatus);
+ returnStatus = mDescramblerBase->setMediaCasSession(streamSessionId);
+ EXPECT_TRUE(returnStatus.isOk());
+ EXPECT_EQ(Status::ERROR_CAS_SESSION_NOT_OPENED, returnStatus);
+ }
}
TEST_P(MediaCasHidlTest, TestClearKeyErrors) {
@@ -654,38 +672,40 @@
EXPECT_TRUE(returnStatus.isOk());
EXPECT_EQ(Status::ERROR_CAS_UNKNOWN, returnStatus);
- /*
- * Test MediaDescrambler error codes
- */
- // setMediaCasSession should fail with an invalid session id
- returnStatus = mDescramblerBase->setMediaCasSession(invalidSessionId);
- EXPECT_TRUE(returnStatus.isOk());
- EXPECT_EQ(Status::ERROR_CAS_SESSION_NOT_OPENED, returnStatus);
+ if (mIsTestDescrambler) {
+ /*
+ * Test MediaDescrambler error codes
+ */
+ // setMediaCasSession should fail with an invalid session id
+ returnStatus = mDescramblerBase->setMediaCasSession(invalidSessionId);
+ EXPECT_TRUE(returnStatus.isOk());
+ EXPECT_EQ(Status::ERROR_CAS_SESSION_NOT_OPENED, returnStatus);
- // descramble should fail without a valid session
- sp<IDescrambler> descrambler;
- descrambler = IDescrambler::castFrom(mDescramblerBase);
- ASSERT_NE(descrambler, nullptr);
+ // descramble should fail without a valid session
+ sp<IDescrambler> descrambler;
+ descrambler = IDescrambler::castFrom(mDescramblerBase);
+ ASSERT_NE(descrambler, nullptr);
- Status descrambleStatus = Status::OK;
- sp<IMemory> dataMemory;
+ Status descrambleStatus = Status::OK;
+ sp<IMemory> dataMemory;
- ASSERT_TRUE(descrambleTestInputBuffer(descrambler, &descrambleStatus, &dataMemory));
- EXPECT_EQ(Status::ERROR_CAS_DECRYPT_UNIT_NOT_INITIALIZED, descrambleStatus);
+ ASSERT_TRUE(descrambleTestInputBuffer(descrambler, &descrambleStatus, &dataMemory));
+ EXPECT_EQ(Status::ERROR_CAS_DECRYPT_UNIT_NOT_INITIALIZED, descrambleStatus);
- // Now set a valid session, should still fail because no valid ecm is processed
- returnStatus = mDescramblerBase->setMediaCasSession(sessionId);
- EXPECT_TRUE(returnStatus.isOk());
- EXPECT_EQ(Status::OK, returnStatus);
+ // Now set a valid session, should still fail because no valid ecm is processed
+ returnStatus = mDescramblerBase->setMediaCasSession(sessionId);
+ EXPECT_TRUE(returnStatus.isOk());
+ EXPECT_EQ(Status::OK, returnStatus);
- ASSERT_TRUE(descrambleTestInputBuffer(descrambler, &descrambleStatus, &dataMemory));
- EXPECT_EQ(Status::ERROR_CAS_DECRYPT, descrambleStatus);
+ ASSERT_TRUE(descrambleTestInputBuffer(descrambler, &descrambleStatus, &dataMemory));
+ EXPECT_EQ(Status::ERROR_CAS_DECRYPT, descrambleStatus);
- // Verify that requiresSecureDecoderComponent handles empty mime
- EXPECT_FALSE(mDescramblerBase->requiresSecureDecoderComponent(""));
+ // Verify that requiresSecureDecoderComponent handles empty mime
+ EXPECT_FALSE(mDescramblerBase->requiresSecureDecoderComponent(""));
- // Verify that requiresSecureDecoderComponent handles invalid mime
- EXPECT_FALSE(mDescramblerBase->requiresSecureDecoderComponent("bad"));
+ // Verify that requiresSecureDecoderComponent handles invalid mime
+ EXPECT_FALSE(mDescramblerBase->requiresSecureDecoderComponent("bad"));
+ }
}
TEST_P(MediaCasHidlTest, TestClearKeyOobFails) {
@@ -700,9 +720,11 @@
std::vector<uint8_t> sessionId;
ASSERT_TRUE(openCasSession(&sessionId));
- returnStatus = mDescramblerBase->setMediaCasSession(sessionId);
- EXPECT_TRUE(returnStatus.isOk());
- EXPECT_EQ(Status::OK, returnStatus);
+ if (mIsTestDescrambler) {
+ returnStatus = mDescramblerBase->setMediaCasSession(sessionId);
+ EXPECT_TRUE(returnStatus.isOk());
+ EXPECT_EQ(Status::OK, returnStatus);
+ }
hidl_vec<uint8_t> hidlEcm;
hidlEcm.setToExternal(const_cast<uint8_t*>(kEcmBinaryBuffer), sizeof(kEcmBinaryBuffer));
@@ -710,126 +732,104 @@
EXPECT_TRUE(returnStatus.isOk());
EXPECT_EQ(Status::OK, returnStatus);
- sp<IDescrambler> descrambler = IDescrambler::castFrom(mDescramblerBase);
- ASSERT_NE(nullptr, descrambler.get());
+ if (mIsTestDescrambler) {
+ sp<IDescrambler> descrambler = IDescrambler::castFrom(mDescramblerBase);
+ ASSERT_NE(nullptr, descrambler.get());
- Status descrambleStatus = Status::OK;
+ Status descrambleStatus = Status::OK;
- // test invalid src buffer offset
- ASSERT_TRUE(descrambleTestOobInput(
- descrambler,
- &descrambleStatus,
- {
- .subSamples = kSubSamples,
- .numSubSamples = sizeof(kSubSamples)/sizeof(SubSample),
- .imemSizeActual = sizeof(kInBinaryBuffer),
- .imemOffset = 0xcccccc,
- .imemSize = sizeof(kInBinaryBuffer),
- .srcOffset = 0,
- .dstOffset = 0
- }));
- EXPECT_EQ(Status::BAD_VALUE, descrambleStatus);
+ // test invalid src buffer offset
+ ASSERT_TRUE(
+ descrambleTestOobInput(descrambler, &descrambleStatus,
+ {.subSamples = kSubSamples,
+ .numSubSamples = sizeof(kSubSamples) / sizeof(SubSample),
+ .imemSizeActual = sizeof(kInBinaryBuffer),
+ .imemOffset = 0xcccccc,
+ .imemSize = sizeof(kInBinaryBuffer),
+ .srcOffset = 0,
+ .dstOffset = 0}));
+ EXPECT_EQ(Status::BAD_VALUE, descrambleStatus);
- // test invalid src buffer size
- ASSERT_TRUE(descrambleTestOobInput(
- descrambler,
- &descrambleStatus,
- {
- .subSamples = kSubSamples,
- .numSubSamples = sizeof(kSubSamples)/sizeof(SubSample),
- .imemSizeActual = sizeof(kInBinaryBuffer),
- .imemOffset = 0,
- .imemSize = 0xcccccc,
- .srcOffset = 0,
- .dstOffset = 0
- }));
- EXPECT_EQ(Status::BAD_VALUE, descrambleStatus);
+ // test invalid src buffer size
+ ASSERT_TRUE(
+ descrambleTestOobInput(descrambler, &descrambleStatus,
+ {.subSamples = kSubSamples,
+ .numSubSamples = sizeof(kSubSamples) / sizeof(SubSample),
+ .imemSizeActual = sizeof(kInBinaryBuffer),
+ .imemOffset = 0,
+ .imemSize = 0xcccccc,
+ .srcOffset = 0,
+ .dstOffset = 0}));
+ EXPECT_EQ(Status::BAD_VALUE, descrambleStatus);
- // test invalid src buffer size
- ASSERT_TRUE(descrambleTestOobInput(
- descrambler,
- &descrambleStatus,
- {
- .subSamples = kSubSamples,
- .numSubSamples = sizeof(kSubSamples)/sizeof(SubSample),
- .imemSizeActual = sizeof(kInBinaryBuffer),
- .imemOffset = 1,
- .imemSize = (uint64_t)-1,
- .srcOffset = 0,
- .dstOffset = 0
- }));
- EXPECT_EQ(Status::BAD_VALUE, descrambleStatus);
+ // test invalid src buffer size
+ ASSERT_TRUE(
+ descrambleTestOobInput(descrambler, &descrambleStatus,
+ {.subSamples = kSubSamples,
+ .numSubSamples = sizeof(kSubSamples) / sizeof(SubSample),
+ .imemSizeActual = sizeof(kInBinaryBuffer),
+ .imemOffset = 1,
+ .imemSize = (uint64_t)-1,
+ .srcOffset = 0,
+ .dstOffset = 0}));
+ EXPECT_EQ(Status::BAD_VALUE, descrambleStatus);
- // test invalid srcOffset
- ASSERT_TRUE(descrambleTestOobInput(
- descrambler,
- &descrambleStatus,
- {
- .subSamples = kSubSamples,
- .numSubSamples = sizeof(kSubSamples)/sizeof(SubSample),
- .imemSizeActual = sizeof(kInBinaryBuffer),
- .imemOffset = 0,
- .imemSize = sizeof(kInBinaryBuffer),
- .srcOffset = 0xcccccc,
- .dstOffset = 0
- }));
- EXPECT_EQ(Status::BAD_VALUE, descrambleStatus);
+ // test invalid srcOffset
+ ASSERT_TRUE(
+ descrambleTestOobInput(descrambler, &descrambleStatus,
+ {.subSamples = kSubSamples,
+ .numSubSamples = sizeof(kSubSamples) / sizeof(SubSample),
+ .imemSizeActual = sizeof(kInBinaryBuffer),
+ .imemOffset = 0,
+ .imemSize = sizeof(kInBinaryBuffer),
+ .srcOffset = 0xcccccc,
+ .dstOffset = 0}));
+ EXPECT_EQ(Status::BAD_VALUE, descrambleStatus);
- // test invalid dstOffset
- ASSERT_TRUE(descrambleTestOobInput(
- descrambler,
- &descrambleStatus,
- {
- .subSamples = kSubSamples,
- .numSubSamples = sizeof(kSubSamples)/sizeof(SubSample),
- .imemSizeActual = sizeof(kInBinaryBuffer),
- .imemOffset = 0,
- .imemSize = sizeof(kInBinaryBuffer),
- .srcOffset = 0,
- .dstOffset = 0xcccccc
- }));
- EXPECT_EQ(Status::BAD_VALUE, descrambleStatus);
+ // test invalid dstOffset
+ ASSERT_TRUE(
+ descrambleTestOobInput(descrambler, &descrambleStatus,
+ {.subSamples = kSubSamples,
+ .numSubSamples = sizeof(kSubSamples) / sizeof(SubSample),
+ .imemSizeActual = sizeof(kInBinaryBuffer),
+ .imemOffset = 0,
+ .imemSize = sizeof(kInBinaryBuffer),
+ .srcOffset = 0,
+ .dstOffset = 0xcccccc}));
+ EXPECT_EQ(Status::BAD_VALUE, descrambleStatus);
- // test detection of oob subsample sizes
- const SubSample invalidSubSamples1[] =
- {{162, 0}, {0, 184}, {0, 0xdddddd}};
+ // test detection of oob subsample sizes
+ const SubSample invalidSubSamples1[] = {{162, 0}, {0, 184}, {0, 0xdddddd}};
- ASSERT_TRUE(descrambleTestOobInput(
- descrambler,
- &descrambleStatus,
- {
- .subSamples = invalidSubSamples1,
- .numSubSamples = sizeof(invalidSubSamples1)/sizeof(SubSample),
- .imemSizeActual = sizeof(kInBinaryBuffer),
- .imemOffset = 0,
- .imemSize = sizeof(kInBinaryBuffer),
- .srcOffset = 0,
- .dstOffset = 0
- }));
- EXPECT_EQ(Status::BAD_VALUE, descrambleStatus);
+ ASSERT_TRUE(descrambleTestOobInput(
+ descrambler, &descrambleStatus,
+ {.subSamples = invalidSubSamples1,
+ .numSubSamples = sizeof(invalidSubSamples1) / sizeof(SubSample),
+ .imemSizeActual = sizeof(kInBinaryBuffer),
+ .imemOffset = 0,
+ .imemSize = sizeof(kInBinaryBuffer),
+ .srcOffset = 0,
+ .dstOffset = 0}));
+ EXPECT_EQ(Status::BAD_VALUE, descrambleStatus);
- // test detection of overflowing subsample sizes
- const SubSample invalidSubSamples2[] =
- {{162, 0}, {0, 184}, {2, (uint32_t)-1}};
+ // test detection of overflowing subsample sizes
+ const SubSample invalidSubSamples2[] = {{162, 0}, {0, 184}, {2, (uint32_t)-1}};
- ASSERT_TRUE(descrambleTestOobInput(
- descrambler,
- &descrambleStatus,
- {
- .subSamples = invalidSubSamples2,
- .numSubSamples = sizeof(invalidSubSamples2)/sizeof(SubSample),
- .imemSizeActual = sizeof(kInBinaryBuffer),
- .imemOffset = 0,
- .imemSize = sizeof(kInBinaryBuffer),
- .srcOffset = 0,
- .dstOffset = 0
- }));
- EXPECT_EQ(Status::BAD_VALUE, descrambleStatus);
+ ASSERT_TRUE(descrambleTestOobInput(
+ descrambler, &descrambleStatus,
+ {.subSamples = invalidSubSamples2,
+ .numSubSamples = sizeof(invalidSubSamples2) / sizeof(SubSample),
+ .imemSizeActual = sizeof(kInBinaryBuffer),
+ .imemOffset = 0,
+ .imemSize = sizeof(kInBinaryBuffer),
+ .srcOffset = 0,
+ .dstOffset = 0}));
+ EXPECT_EQ(Status::BAD_VALUE, descrambleStatus);
- returnStatus = mDescramblerBase->release();
- EXPECT_TRUE(returnStatus.isOk());
- EXPECT_EQ(Status::OK, returnStatus);
-
+ returnStatus = mDescramblerBase->release();
+ EXPECT_TRUE(returnStatus.isOk());
+ EXPECT_EQ(Status::OK, returnStatus);
+ }
returnStatus = mMediaCas->release();
EXPECT_TRUE(returnStatus.isOk());
EXPECT_EQ(Status::OK, returnStatus);
diff --git a/cas/1.1/vts/functional/Android.bp b/cas/1.1/vts/functional/Android.bp
index de223c8..0647d12a 100644
--- a/cas/1.1/vts/functional/Android.bp
+++ b/cas/1.1/vts/functional/Android.bp
@@ -21,6 +21,7 @@
static_libs: [
"android.hardware.cas@1.0",
"android.hardware.cas@1.1",
+ "android.hardware.cas@1.2",
"android.hardware.cas.native@1.0",
"android.hidl.allocator@1.0",
"android.hidl.memory@1.0",
diff --git a/cas/1.1/vts/functional/VtsHalCasV1_1TargetTest.cpp b/cas/1.1/vts/functional/VtsHalCasV1_1TargetTest.cpp
index b657f07..6797506 100644
--- a/cas/1.1/vts/functional/VtsHalCasV1_1TargetTest.cpp
+++ b/cas/1.1/vts/functional/VtsHalCasV1_1TargetTest.cpp
@@ -22,6 +22,7 @@
#include <android/hardware/cas/1.1/ICas.h>
#include <android/hardware/cas/1.1/ICasListener.h>
#include <android/hardware/cas/1.1/IMediaCasService.h>
+#include <android/hardware/cas/1.2/IMediaCasService.h>
#include <android/hardware/cas/native/1.0/IDescrambler.h>
#include <android/hardware/cas/native/1.0/types.h>
#include <binder/MemoryDealer.h>
@@ -255,6 +256,10 @@
class MediaCasHidlTest : public testing::TestWithParam<std::string> {
public:
virtual void SetUp() override {
+ if (android::hardware::cas::V1_2::IMediaCasService::getService(GetParam()) == nullptr) {
+ ALOGI("Descrambler is need to be tested before cas@1.2.");
+ mIsTestDescrambler = true;
+ }
mService = IMediaCasService::getService(GetParam());
ASSERT_NE(mService, nullptr);
}
@@ -269,6 +274,7 @@
sp<ICas> mMediaCas;
sp<IDescramblerBase> mDescramblerBase;
sp<MediaCasListener> mCasListener;
+ bool mIsTestDescrambler = false;
typedef struct _OobInputTestParams {
const SubSample* subSamples;
uint32_t numSubSamples;
@@ -311,8 +317,15 @@
auto descramblerStatus = mService->createDescrambler(caSystemId);
if (!descramblerStatus.isOk()) {
- return ::testing::AssertionFailure();
+ if (mIsTestDescrambler) {
+ return ::testing::AssertionFailure();
+ } else {
+ ALOGI("Skip Descrambler test since it's not required in cas@1.2.");
+ return ::testing::AssertionSuccess();
+ }
}
+ mIsTestDescrambler = true;
+
mDescramblerBase = descramblerStatus;
return ::testing::AssertionResult(mDescramblerBase != nullptr);
}
@@ -468,13 +481,15 @@
EXPECT_TRUE(returnStatus.isOk());
EXPECT_EQ(Status::OK, returnStatus);
- returnStatus = mDescramblerBase->setMediaCasSession(sessionId);
- EXPECT_TRUE(returnStatus.isOk());
- EXPECT_EQ(Status::OK, returnStatus);
+ if (mIsTestDescrambler) {
+ returnStatus = mDescramblerBase->setMediaCasSession(sessionId);
+ EXPECT_TRUE(returnStatus.isOk());
+ EXPECT_EQ(Status::OK, returnStatus);
- returnStatus = mDescramblerBase->setMediaCasSession(streamSessionId);
- EXPECT_TRUE(returnStatus.isOk());
- EXPECT_EQ(Status::OK, returnStatus);
+ returnStatus = mDescramblerBase->setMediaCasSession(streamSessionId);
+ EXPECT_TRUE(returnStatus.isOk());
+ EXPECT_EQ(Status::OK, returnStatus);
+ }
hidl_vec<uint8_t> hidlNullPtr;
hidlNullPtr.setToExternal(static_cast<uint8_t*>(nullptr), 0);
@@ -518,29 +533,31 @@
EXPECT_TRUE(returnStatus.isOk());
EXPECT_EQ(Status::OK, returnStatus);
- EXPECT_FALSE(mDescramblerBase->requiresSecureDecoderComponent("video/avc"));
+ if (mIsTestDescrambler) {
+ EXPECT_FALSE(mDescramblerBase->requiresSecureDecoderComponent("video/avc"));
- sp<IDescrambler> descrambler;
- descrambler = IDescrambler::castFrom(mDescramblerBase);
- ASSERT_NE(descrambler, nullptr);
+ sp<IDescrambler> descrambler;
+ descrambler = IDescrambler::castFrom(mDescramblerBase);
+ ASSERT_NE(descrambler, nullptr);
- Status descrambleStatus = Status::OK;
- sp<IMemory> dataMemory;
+ Status descrambleStatus = Status::OK;
+ sp<IMemory> dataMemory;
- ASSERT_TRUE(descrambleTestInputBuffer(descrambler, &descrambleStatus, &dataMemory));
- EXPECT_EQ(Status::OK, descrambleStatus);
+ ASSERT_TRUE(descrambleTestInputBuffer(descrambler, &descrambleStatus, &dataMemory));
+ EXPECT_EQ(Status::OK, descrambleStatus);
- ASSERT_NE(nullptr, dataMemory.get());
- uint8_t* opBuffer = static_cast<uint8_t*>(static_cast<void*>(dataMemory->unsecurePointer()));
+ ASSERT_NE(nullptr, dataMemory.get());
+ uint8_t* opBuffer = static_cast<uint8_t*>(static_cast<void*>(dataMemory->unsecurePointer()));
- int compareResult =
- memcmp(static_cast<const void*>(opBuffer),
- static_cast<const void*>(kOutRefBinaryBuffer), sizeof(kOutRefBinaryBuffer));
- EXPECT_EQ(0, compareResult);
+ int compareResult =
+ memcmp(static_cast<const void*>(opBuffer),
+ static_cast<const void*>(kOutRefBinaryBuffer), sizeof(kOutRefBinaryBuffer));
+ EXPECT_EQ(0, compareResult);
- returnStatus = mDescramblerBase->release();
- EXPECT_TRUE(returnStatus.isOk());
- EXPECT_EQ(Status::OK, returnStatus);
+ returnStatus = mDescramblerBase->release();
+ EXPECT_TRUE(returnStatus.isOk());
+ EXPECT_EQ(Status::OK, returnStatus);
+ }
returnStatus = mMediaCas->release();
EXPECT_TRUE(returnStatus.isOk());
diff --git a/cas/1.2/vts/functional/VtsHalCasV1_2TargetTest.cpp b/cas/1.2/vts/functional/VtsHalCasV1_2TargetTest.cpp
index f436b8b..333dea6 100644
--- a/cas/1.2/vts/functional/VtsHalCasV1_2TargetTest.cpp
+++ b/cas/1.2/vts/functional/VtsHalCasV1_2TargetTest.cpp
@@ -311,6 +311,7 @@
sp<ICas> mMediaCas;
sp<IDescramblerBase> mDescramblerBase;
sp<MediaCasListener> mCasListener;
+ bool mIsTestDescrambler = false;
typedef struct _OobInputTestParams {
const SubSample* subSamples;
uint32_t numSubSamples;
@@ -355,8 +356,11 @@
auto descramblerStatus = mService->createDescrambler(caSystemId);
if (!descramblerStatus.isOk()) {
- return ::testing::AssertionFailure();
+ ALOGI("Skip Descrambler test since it's not required in cas@1.2.");
+ return ::testing::AssertionSuccess();
}
+ mIsTestDescrambler = true;
+
mDescramblerBase = descramblerStatus;
return ::testing::AssertionResult(mDescramblerBase != nullptr);
}
@@ -512,14 +516,15 @@
EXPECT_TRUE(returnStatus.isOk());
EXPECT_EQ(Status::OK, returnStatus);
- returnStatus = mDescramblerBase->setMediaCasSession(sessionId);
- EXPECT_TRUE(returnStatus.isOk());
- EXPECT_EQ(Status::OK, returnStatus);
+ if (mIsTestDescrambler) {
+ returnStatus = mDescramblerBase->setMediaCasSession(sessionId);
+ EXPECT_TRUE(returnStatus.isOk());
+ EXPECT_EQ(Status::OK, returnStatus);
- returnStatus = mDescramblerBase->setMediaCasSession(streamSessionId);
- EXPECT_TRUE(returnStatus.isOk());
- EXPECT_EQ(Status::OK, returnStatus);
-
+ returnStatus = mDescramblerBase->setMediaCasSession(streamSessionId);
+ EXPECT_TRUE(returnStatus.isOk());
+ EXPECT_EQ(Status::OK, returnStatus);
+ }
hidl_vec<uint8_t> hidlNullPtr;
hidlNullPtr.setToExternal(static_cast<uint8_t*>(nullptr), 0);
returnStatus = mMediaCas->refreshEntitlements(3, hidlNullPtr);
@@ -566,29 +571,31 @@
EXPECT_TRUE(returnStatus.isOk());
EXPECT_EQ(Status::OK, returnStatus);
- EXPECT_FALSE(mDescramblerBase->requiresSecureDecoderComponent("video/avc"));
+ if (mIsTestDescrambler) {
+ EXPECT_FALSE(mDescramblerBase->requiresSecureDecoderComponent("video/avc"));
- sp<IDescrambler> descrambler;
- descrambler = IDescrambler::castFrom(mDescramblerBase);
- ASSERT_NE(descrambler, nullptr);
+ sp<IDescrambler> descrambler;
+ descrambler = IDescrambler::castFrom(mDescramblerBase);
+ ASSERT_NE(descrambler, nullptr);
- Status descrambleStatus = Status::OK;
- sp<IMemory> dataMemory;
+ Status descrambleStatus = Status::OK;
+ sp<IMemory> dataMemory;
- ASSERT_TRUE(descrambleTestInputBuffer(descrambler, &descrambleStatus, &dataMemory));
- EXPECT_EQ(Status::OK, descrambleStatus);
+ ASSERT_TRUE(descrambleTestInputBuffer(descrambler, &descrambleStatus, &dataMemory));
+ EXPECT_EQ(Status::OK, descrambleStatus);
- ASSERT_NE(nullptr, dataMemory.get());
- uint8_t* opBuffer = static_cast<uint8_t*>(static_cast<void*>(dataMemory->unsecurePointer()));
+ ASSERT_NE(nullptr, dataMemory.get());
+ uint8_t* opBuffer = static_cast<uint8_t*>(static_cast<void*>(dataMemory->unsecurePointer()));
- int compareResult =
- memcmp(static_cast<const void*>(opBuffer),
- static_cast<const void*>(kOutRefBinaryBuffer), sizeof(kOutRefBinaryBuffer));
- EXPECT_EQ(0, compareResult);
+ int compareResult =
+ memcmp(static_cast<const void*>(opBuffer),
+ static_cast<const void*>(kOutRefBinaryBuffer), sizeof(kOutRefBinaryBuffer));
+ EXPECT_EQ(0, compareResult);
- returnStatus = mDescramblerBase->release();
- EXPECT_TRUE(returnStatus.isOk());
- EXPECT_EQ(Status::OK, returnStatus);
+ returnStatus = mDescramblerBase->release();
+ EXPECT_TRUE(returnStatus.isOk());
+ EXPECT_EQ(Status::OK, returnStatus);
+ }
returnStatus = mMediaCas->release();
EXPECT_TRUE(returnStatus.isOk());
diff --git a/compatibility_matrices/compatibility_matrix.current.xml b/compatibility_matrices/compatibility_matrix.current.xml
index e12d920..b8676b0 100644
--- a/compatibility_matrices/compatibility_matrix.current.xml
+++ b/compatibility_matrices/compatibility_matrix.current.xml
@@ -27,6 +27,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>
@@ -280,11 +288,20 @@
</hal>
<hal format="aidl" optional="true">
<name>android.hardware.identity</name>
+ <version>1-2</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>
@@ -468,6 +485,20 @@
<regex-instance>SIM[1-9][0-9]*</regex-instance>
</interface>
</hal>
+ <hal format="aidl" optional="true">
+ <name>android.hardware.security.secureclock</name>
+ <interface>
+ <name>ISecureClock</name>
+ <instance>default</instance>
+ </interface>
+ </hal>
+ <hal format="aidl" optional="true">
+ <name>android.hardware.security.sharedsecret</name>
+ <interface>
+ <name>ISharedSecret</name>
+ <instance>default</instance>
+ </interface>
+ </hal>
<hal format="hidl" optional="true">
<name>android.hardware.sensors</name>
<version>1.0</version>
@@ -552,6 +583,7 @@
</hal>
<hal format="aidl" optional="true">
<name>android.hardware.vibrator</name>
+ <version>1-2</version>
<interface>
<name>IVibrator</name>
<instance>default</instance>
@@ -559,20 +591,13 @@
</hal>
<hal format="aidl" optional="true">
<name>android.hardware.vibrator</name>
+ <version>1-2</version>
<interface>
<name>IVibratorManager</name>
<instance>default</instance>
</interface>
</hal>
<hal format="hidl" optional="true">
- <name>android.hardware.vr</name>
- <version>1.0</version>
- <interface>
- <name>IVr</name>
- <instance>default</instance>
- </interface>
- </hal>
- <hal format="hidl" optional="true">
<name>android.hardware.weaver</name>
<version>1.0</version>
<interface>
diff --git a/contexthub/1.0/IContexthub.hal b/contexthub/1.0/IContexthub.hal
index 8dccd67..950e081 100644
--- a/contexthub/1.0/IContexthub.hal
+++ b/contexthub/1.0/IContexthub.hal
@@ -41,7 +41,7 @@
* registration.
*
* @param hubId identifier for the hub
- * callback an implementation of the IContextHubCallbacks
+ * @param callback an implementation of the IContextHubCallbacks
*
* @return result OK on success
* BAD_VALUE if parameters are not sane
@@ -53,7 +53,7 @@
* Send a message to a hub
*
* @param hubId identifier for hub to send message to
- * msg message to be sent
+ * @param msg message to be sent
*
* @return result OK if successful, error code otherwise
* BAD_VALUE if parameters are not sane
@@ -77,9 +77,9 @@
* device.
*
* @param hubId identifer of the contextHub
- * appBinary contains the binary representation of the nanoApp, plus
+ * @param appBinary contains the binary representation of the nanoApp, plus
* metadata
- * transactionId transactionId for this call
+ * @param transactionId transactionId for this call
*
* @return result OK if transation started
* BAD_VALUE if parameters are not sane
@@ -101,8 +101,8 @@
* Unloading a nanoapp must not take more than 5 seconds.
*
* @param hubId identifer of the contextHub
- * appId appIdentifier returned by the HAL
- * msg message to be sent
+ * @param appId appIdentifier returned by the HAL
+ * @param msg message to be sent
*
* @return result OK if transation started
* BAD_VALUE if parameters are not sane
@@ -122,8 +122,8 @@
* Enabling a nanoapp must not take more than 5 seconds.
*
* @param hubId identifer of the contextHub
- * appId appIdentifier returned by the HAL
- * msg message to be sent
+ * @param appId appIdentifier returned by the HAL
+ * @param msg message to be sent
*
* @return result OK if transation started
* BAD_VALUE if parameters are not sane
@@ -143,8 +143,8 @@
* Disabling a nanoapp must not take more than 5 seconds.
*
* @param hubId identifer of the contextHub
- * appId appIdentifier returned by the HAL
- * msg message to be sent
+ * @param appId appIdentifier returned by the HAL
+ * @param msg message to be sent
*
* @return result OK if transation started
* BAD_VALUE if parameters are not sane
diff --git a/contexthub/1.0/IContexthubCallback.hal b/contexthub/1.0/IContexthubCallback.hal
index 264f84c..70750f8 100644
--- a/contexthub/1.0/IContexthubCallback.hal
+++ b/contexthub/1.0/IContexthubCallback.hal
@@ -22,7 +22,7 @@
* implementation to allow the HAL to send asynchronous messages back
* to the service and registered clients of the ContextHub service.
*
- * @params msg : message
+ * @param msg message being sent from the contexthub
*
*/
handleClientMsg(ContextHubMsg msg);
@@ -32,9 +32,9 @@
* implementation to allow the HAL to send the response for a
* transaction.
*
- * @params txnId : transaction id whose result is being sent
- * passed in by the service at start of transacation.
- * result: result of transaction.
+ * @param txnId transaction id whose result is being sent
+ * passed in by the service at start of transacation.
+ * @param result result of transaction.
*
*/
handleTxnResult(uint32_t txnId, TransactionResult result);
@@ -44,7 +44,7 @@
* implementation to allow the HAL to send an asynchronous event
* to the ContextHub service.
*
- * @params msg : message
+ * @param evt event being sent from the contexthub
*
*/
handleHubEvent(AsyncEventType evt);
@@ -55,8 +55,8 @@
* that a nanp-app has aborted.
* This method must be called when a nanoapp invokes chreAbort(...)).
*
- * @params appId : app identifier
- * : abortCode code passed by the nanoApp.
+ * @param appId app identifier
+ * @param abortCode code passed by the nanoApp.
*
* Also see chreAbort(...)
*
@@ -68,12 +68,11 @@
* implementation to allow the HAL to send information about the
* currently loaded and active nanoapps on the hub.
*
- * @params appInfo : vector of HubAppinfo structure for each nanoApp
- * on the hub that can be enabled, disabled and
- * unloaded by the service. Any nanoApps that cannot
- * be controlled by the service must not be reported.
- * All nanoApps that can be controlled by the service
- * must be reported.
+ * @param appInfo vector of HubAppinfo structure for each nanoApp on the
+ * hub that can be enabled, disabled and unloaded by the
+ * service. Any nanoApps that cannot be controlled by the
+ * service must not be reported. All nanoApps that can be
+ * controlled by the service must be reported.
*/
handleAppsInfo(vec<HubAppInfo> appInfo);
};
diff --git a/contexthub/1.1/default/Contexthub.cpp b/contexthub/1.1/default/Contexthub.cpp
index e7fde84..2d4fbae 100644
--- a/contexthub/1.1/default/Contexthub.cpp
+++ b/contexthub/1.1/default/Contexthub.cpp
@@ -23,10 +23,29 @@
namespace V1_1 {
namespace implementation {
+using ::android::hardware::contexthub::V1_0::Result;
+
Return<void> Contexthub::onSettingChanged(Setting /*setting*/, SettingValue /*newValue*/) {
return Void();
}
+Return<Result> Contexthub::registerCallback(uint32_t hubId, const sp<IContexthubCallback>& cb) {
+ if (hubId == kMockHubId) {
+ mCallback = cb;
+ return Result::OK;
+ }
+ return Result::BAD_PARAMS;
+}
+
+Return<Result> Contexthub::queryApps(uint32_t hubId) {
+ if (hubId == kMockHubId && mCallback != nullptr) {
+ std::vector<HubAppInfo> nanoapps;
+ mCallback->handleAppsInfo(nanoapps);
+ return Result::OK;
+ }
+ return Result::BAD_PARAMS;
+}
+
} // namespace implementation
} // namespace V1_1
} // namespace contexthub
diff --git a/contexthub/1.1/default/Contexthub.h b/contexthub/1.1/default/Contexthub.h
index 1468fcf..648749e 100644
--- a/contexthub/1.1/default/Contexthub.h
+++ b/contexthub/1.1/default/Contexthub.h
@@ -27,9 +27,19 @@
class Contexthub
: public ::android::hardware::contexthub::V1_X::implementation::ContextHub<IContexthub> {
+ using Result = ::android::hardware::contexthub::V1_0::Result;
+
public:
+ // Methods from V1_0::IContexthub
+ Return<Result> registerCallback(uint32_t hubId, const sp<IContexthubCallback>& cb) override;
+
+ Return<Result> queryApps(uint32_t hubId) override;
+
// Methods from V1_1::IContexthub
Return<void> onSettingChanged(Setting setting, SettingValue newValue) override;
+
+ private:
+ sp<IContexthubCallback> mCallback;
};
} // namespace implementation
diff --git a/contexthub/1.2/Android.bp b/contexthub/1.2/Android.bp
index e819482..9722a97 100644
--- a/contexthub/1.2/Android.bp
+++ b/contexthub/1.2/Android.bp
@@ -6,6 +6,7 @@
srcs: [
"types.hal",
"IContexthub.hal",
+ "IContexthubCallback.hal",
],
interfaces: [
"android.hardware.contexthub@1.0",
diff --git a/contexthub/1.2/IContexthub.hal b/contexthub/1.2/IContexthub.hal
index 819fc1d..3488b74 100644
--- a/contexthub/1.2/IContexthub.hal
+++ b/contexthub/1.2/IContexthub.hal
@@ -16,11 +16,41 @@
package android.hardware.contexthub@1.2;
+import @1.0::Result;
import @1.1::IContexthub;
import @1.1::SettingValue;
+import IContexthubCallback;
interface IContexthub extends @1.1::IContexthub {
/**
+ * Register a callback for the HAL implementation to send asynchronous
+ * messages to the service from a context hub. There can be a maximum of
+ * one callback registered with the HAL. A call to this function when a
+ * callback has already been registered must override the previous
+ * registration.
+ *
+ * @param hubId identifier for the hub
+ * @param callback an implementation of the IContextHubCallbacks
+ *
+ * @return result OK on success
+ * BAD_VALUE if parameters are not valid
+ *
+ */
+ registerCallback_1_2(uint32_t hubId, IContexthubCallback cb) generates (Result result);
+
+ /**
+ * Send a message to a hub
+ *
+ * @param hubId identifier for hub to send message to
+ * @param msg message to be sent
+ *
+ * @return result OK if successful, error code otherwise
+ * BAD_VALUE if parameters are not valid
+ * TRANSACTION_FAILED if message send failed
+ */
+ sendMessageToHub_1_2(uint32_t hubId, ContextHubMsg msg) generates (Result result);
+
+ /**
* Notification sent by the framework to indicate that the user
* has changed a setting.
*
diff --git a/contexthub/1.2/IContexthubCallback.hal b/contexthub/1.2/IContexthubCallback.hal
new file mode 100644
index 0000000..0236160
--- /dev/null
+++ b/contexthub/1.2/IContexthubCallback.hal
@@ -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.
+ */
+
+package android.hardware.contexthub@1.2;
+
+import @1.0::IContexthubCallback;
+
+interface IContexthubCallback extends @1.0::IContexthubCallback {
+ /**
+ * This callback is passed by the Contexthub service to the HAL
+ * implementation to allow the HAL to send asynchronous messages back
+ * to the service and registered clients of the ContextHub service.
+ *
+ * @param msg message that should be delivered to host app clients
+ *
+ */
+ handleClientMsg_1_2(ContextHubMsg msg);
+
+ /**
+ * This callback is passed by the Contexthub service to the HAL
+ * implementation to allow the HAL to send information about the
+ * currently loaded and active nanoapps on the hub.
+ *
+ * @param appInfo vector of HubAppinfo structure for each nanoApp
+ * on the hub that can be enabled, disabled and
+ * unloaded by the service. Any nanoApps that cannot
+ * be controlled by the service must not be reported.
+ * All nanoApps that can be controlled by the service
+ * must be reported.
+ */
+ handleAppsInfo_1_2(vec<HubAppInfo> appInfo);
+};
diff --git a/contexthub/1.2/default/Android.bp b/contexthub/1.2/default/Android.bp
index 49b54fc..0a31325 100644
--- a/contexthub/1.2/default/Android.bp
+++ b/contexthub/1.2/default/Android.bp
@@ -41,6 +41,7 @@
],
header_libs: [
"android.hardware.contexthub@1.X-common-impl",
+ "android.hardware.contexthub@1.X-common-utils",
],
vintf_fragments: ["android.hardware.contexthub@1.2.xml"],
}
diff --git a/contexthub/1.2/default/Contexthub.cpp b/contexthub/1.2/default/Contexthub.cpp
index d7ac7bf..db0c5bc 100644
--- a/contexthub/1.2/default/Contexthub.cpp
+++ b/contexthub/1.2/default/Contexthub.cpp
@@ -23,6 +23,43 @@
namespace V1_2 {
namespace implementation {
+using ::android::hardware::contexthub::V1_0::Result;
+using ::android::hardware::contexthub::V1_X::implementation::IContextHubCallbackWrapperV1_0;
+using ::android::hardware::contexthub::V1_X::implementation::IContextHubCallbackWrapperV1_2;
+
+Return<Result> Contexthub::registerCallback(uint32_t hubId,
+ const sp<V1_0::IContexthubCallback>& cb) {
+ if (hubId == kMockHubId) {
+ mCallback = new IContextHubCallbackWrapperV1_0(cb);
+ return Result::OK;
+ }
+ return Result::BAD_PARAMS;
+}
+
+Return<Result> Contexthub::queryApps(uint32_t hubId) {
+ if (hubId == kMockHubId && mCallback != nullptr) {
+ std::vector<V1_2::HubAppInfo> nanoapps;
+ mCallback->handleAppsInfo(nanoapps);
+ return Result::OK;
+ }
+ return Result::BAD_PARAMS;
+}
+
+Return<Result> Contexthub::registerCallback_1_2(uint32_t hubId,
+ const sp<V1_2::IContexthubCallback>& cb) {
+ if (hubId == kMockHubId) {
+ mCallback = new IContextHubCallbackWrapperV1_2(cb);
+ return Result::OK;
+ }
+ return Result::BAD_PARAMS;
+}
+
+// We don't expose any nanoapps, therefore all nanoapp-related API calls return with BAD_PARAMS
+Return<Result> Contexthub::sendMessageToHub_1_2(uint32_t /* hubId */,
+ const ContextHubMsg& /* msg */) {
+ return Result::BAD_PARAMS;
+}
+
Return<void> Contexthub::onSettingChanged(SettingV1_1 /*setting*/, SettingValue /*newValue*/) {
return Void();
}
diff --git a/contexthub/1.2/default/Contexthub.h b/contexthub/1.2/default/Contexthub.h
index d2f8d69..8b89824 100644
--- a/contexthub/1.2/default/Contexthub.h
+++ b/contexthub/1.2/default/Contexthub.h
@@ -16,6 +16,7 @@
#pragma once
#include "ContextHub.h"
+#include "IContextHubCallbackWrapper.h"
#include <android/hardware/contexthub/1.2/IContexthub.h>
@@ -27,15 +28,34 @@
class Contexthub
: public ::android::hardware::contexthub::V1_X::implementation::ContextHub<IContexthub> {
+ using ContextHubMsg = ::android::hardware::contexthub::V1_2::ContextHubMsg;
+ using IContexthubCallback = ::android::hardware::contexthub::V1_2::IContexthubCallback;
+ using IContextHubCallbackWrapperBase =
+ ::android::hardware::contexthub::V1_X::implementation::IContextHubCallbackWrapperBase;
+ using Result = ::android::hardware::contexthub::V1_0::Result;
using SettingValue = ::android::hardware::contexthub::V1_1::SettingValue;
using SettingV1_1 = ::android::hardware::contexthub::V1_1::Setting;
public:
+ // Methods from V1_0::IContexthub
+ Return<Result> registerCallback(uint32_t hubId,
+ const sp<V1_0::IContexthubCallback>& cb) override;
+
+ Return<Result> queryApps(uint32_t hubId) override;
+
// Methods from V1_1::IContexthub
Return<void> onSettingChanged(SettingV1_1 setting, SettingValue newValue) override;
// Methods from V1_2::IContexthub
Return<void> onSettingChanged_1_2(Setting setting, SettingValue newValue) override;
+
+ Return<Result> registerCallback_1_2(uint32_t hubId,
+ const sp<V1_2::IContexthubCallback>& cb) override;
+
+ Return<Result> sendMessageToHub_1_2(uint32_t hubId, const ContextHubMsg& msg) override;
+
+ private:
+ sp<IContextHubCallbackWrapperBase> mCallback;
};
} // namespace implementation
diff --git a/contexthub/1.2/types.hal b/contexthub/1.2/types.hal
index 38f9f7a..5a11efe 100644
--- a/contexthub/1.2/types.hal
+++ b/contexthub/1.2/types.hal
@@ -16,6 +16,8 @@
package android.hardware.contexthub@1.2;
+import @1.0::ContextHubMsg;
+import @1.0::HubAppInfo;
import @1.1::Setting;
/**
@@ -31,4 +33,43 @@
*/
WIFI_AVAILABLE,
AIRPLANE_MODE,
+
+ /**
+ * Indicates if the microphone access was turned off globally by the user,
+ * in which case audio data cannot be used and propagated by CHRE.
+ */
+ GLOBAL_MIC_DISABLE,
+};
+
+struct ContextHubMsg {
+ @1.0::ContextHubMsg msg_1_0;
+
+ /**
+ * The list of Android permissions that the sender of this message has at
+ * the time the message was sent.
+ *
+ * The HAL MUST drop messages to nanoapps if this list of permissions is not
+ * a superset of those of the receiving nanoapp(s).
+ *
+ * The framework MUST drop messages to host apps that don't have a superset
+ * of the permissions that the sending nanoapp is using.
+ */
+ vec<string> permissions;
+};
+
+struct HubAppInfo {
+ @1.0::HubAppInfo info_1_0;
+
+ /**
+ * The list of Android permissions used by this nanoapp. This list MUST
+ * correspond to the permissions required for an equivalent Android app to
+ * sample similar signals through the Android framework.
+ *
+ * For example, if a nanoapp used location-based signals, the permissions
+ * list MUST contains android.permission.ACCESS_FINE_LOCATION and
+ * android.permission.ACCESS_BACKGROUND_LOCATION. If it were to also list to
+ * audio data, it would require adding android.permission.RECORD_AUDIO to
+ * this list.
+ */
+ vec<string> permissions;
};
diff --git a/contexthub/1.2/vts/functional/VtsHalContexthubV1_2TargetTest.cpp b/contexthub/1.2/vts/functional/VtsHalContexthubV1_2TargetTest.cpp
index 77883c2..782edae 100644
--- a/contexthub/1.2/vts/functional/VtsHalContexthubV1_2TargetTest.cpp
+++ b/contexthub/1.2/vts/functional/VtsHalContexthubV1_2TargetTest.cpp
@@ -62,6 +62,13 @@
ASSERT_OK(registerCallback(nullptr));
}
+TEST_P(ContexthubHidlTest, TestOnGlobalMicDisableSettingChanged) {
+ ASSERT_OK(registerCallback(new ContexthubCallbackBase()));
+ hubApi->onSettingChanged_1_2(Setting::GLOBAL_MIC_DISABLE, SettingValue::DISABLED);
+ hubApi->onSettingChanged_1_2(Setting::GLOBAL_MIC_DISABLE, SettingValue::ENABLED);
+ ASSERT_OK(registerCallback(nullptr));
+}
+
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ContexthubHidlTest);
INSTANTIATE_TEST_SUITE_P(HubIdSpecificTests, ContexthubHidlTest, testing::ValuesIn(kTestParameters),
android::hardware::PrintInstanceTupleNameToString<>);
diff --git a/contexthub/common/default/1.X/ContextHub.h b/contexthub/common/default/1.X/ContextHub.h
index 73d0631..00f74af 100644
--- a/contexthub/common/default/1.X/ContextHub.h
+++ b/contexthub/common/default/1.X/ContextHub.h
@@ -60,14 +60,6 @@
return Void();
}
- Return<Result> registerCallback(uint32_t hubId, const sp<IContexthubCallback>& cb) override {
- if (hubId == kMockHubId) {
- mCallback = cb;
- return Result::OK;
- }
- return Result::BAD_PARAMS;
- }
-
// We don't expose any nanoapps, therefore all nanoapp-related API calls return with BAD_PARAMS
Return<Result> sendMessageToHub(uint32_t /*hubId*/, const ContextHubMsg& /*msg*/) override {
return Result::BAD_PARAMS;
@@ -93,19 +85,8 @@
return Result::BAD_PARAMS;
}
- Return<Result> queryApps(uint32_t hubId) override {
- if (hubId == kMockHubId && mCallback != nullptr) {
- std::vector<HubAppInfo> nanoapps;
- mCallback->handleAppsInfo(nanoapps);
- return Result::OK;
- }
- return Result::BAD_PARAMS;
- }
-
- private:
+ protected:
static constexpr uint32_t kMockHubId = 0;
-
- sp<IContexthubCallback> mCallback;
};
} // namespace implementation
diff --git a/contexthub/common/default/1.X/OWNERS b/contexthub/common/default/1.X/OWNERS
new file mode 100644
index 0000000..90c2330
--- /dev/null
+++ b/contexthub/common/default/1.X/OWNERS
@@ -0,0 +1,3 @@
+arthuri@google.com
+bduddie@google.com
+stange@google.com
diff --git a/contexthub/common/default/1.X/utils/Android.bp b/contexthub/common/default/1.X/utils/Android.bp
new file mode 100644
index 0000000..c74b647
--- /dev/null
+++ b/contexthub/common/default/1.X/utils/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_library_headers {
+ name: "android.hardware.contexthub@1.X-common-utils",
+ vendor_available: true,
+ defaults: ["hidl_defaults"],
+ export_include_dirs: ["."],
+ shared_libs: [
+ "android.hardware.contexthub@1.0",
+ "android.hardware.contexthub@1.1",
+ "android.hardware.contexthub@1.2",
+ "libbinder",
+ "libcutils",
+ "libhidlbase",
+ "libutils",
+ ],
+}
diff --git a/contexthub/common/default/1.X/utils/IContextHubCallbackWrapper.h b/contexthub/common/default/1.X/utils/IContextHubCallbackWrapper.h
new file mode 100644
index 0000000..df78438
--- /dev/null
+++ b/contexthub/common/default/1.X/utils/IContextHubCallbackWrapper.h
@@ -0,0 +1,123 @@
+/*
+ * 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_CONTEXTHUB_V1_X_ICONTEXTHUBCALLBACKWRAPPER_H
+#define ANDROID_HARDWARE_CONTEXTHUB_V1_X_ICONTEXTHUBCALLBACKWRAPPER_H
+
+#include "android/hardware/contexthub/1.0/IContexthub.h"
+#include "android/hardware/contexthub/1.0/IContexthubCallback.h"
+#include "android/hardware/contexthub/1.0/types.h"
+#include "android/hardware/contexthub/1.2/IContexthubCallback.h"
+#include "android/hardware/contexthub/1.2/types.h"
+
+#include <utils/LightRefBase.h>
+
+#include <cassert>
+
+namespace android {
+namespace hardware {
+namespace contexthub {
+namespace V1_X {
+namespace implementation {
+
+inline V1_0::ContextHubMsg convertToOldMsg(V1_2::ContextHubMsg msg) {
+ return msg.msg_1_0;
+}
+
+inline hidl_vec<V1_0::HubAppInfo> convertToOldAppInfo(hidl_vec<V1_2::HubAppInfo> appInfos) {
+ hidl_vec<V1_0::HubAppInfo> convertedInfo(appInfos.size());
+ for (int i = 0; i < appInfos.size(); ++i) {
+ convertedInfo[i] = appInfos[i].info_1_0;
+ }
+
+ return convertedInfo;
+}
+
+/**
+ * The IContexthubCallback classes below abstract away the common logic between both the V1.0, and
+ * V1.2 versions of the Contexthub HAL callback interface. This allows users of these classes to
+ * only care about the HAL version at init time and then interact with either version of the
+ * callback without worrying about the class type by utilizing the base class.
+ */
+class IContextHubCallbackWrapperBase : public VirtualLightRefBase {
+ public:
+ virtual Return<void> handleClientMsg(V1_2::ContextHubMsg msg) = 0;
+
+ virtual Return<void> handleTxnResult(uint32_t txnId, V1_0::TransactionResult result) = 0;
+
+ virtual Return<void> handleHubEvent(V1_0::AsyncEventType evt) = 0;
+
+ virtual Return<void> handleAppAbort(uint64_t appId, uint32_t abortCode) = 0;
+
+ virtual Return<void> handleAppsInfo(hidl_vec<V1_2::HubAppInfo> appInfo) = 0;
+};
+
+template <typename T>
+class ContextHubCallbackWrapper : public IContextHubCallbackWrapperBase {
+ public:
+ ContextHubCallbackWrapper(sp<T> callback) : mCallback(callback){};
+
+ virtual Return<void> handleClientMsg(V1_2::ContextHubMsg msg) override {
+ return mCallback->handleClientMsg(convertToOldMsg(msg));
+ }
+
+ virtual Return<void> handleTxnResult(uint32_t txnId, V1_0::TransactionResult result) override {
+ return mCallback->handleTxnResult(txnId, result);
+ }
+
+ virtual Return<void> handleHubEvent(V1_0::AsyncEventType evt) override {
+ return mCallback->handleHubEvent(evt);
+ }
+
+ virtual Return<void> handleAppAbort(uint64_t appId, uint32_t abortCode) override {
+ return mCallback->handleAppAbort(appId, abortCode);
+ }
+
+ virtual Return<void> handleAppsInfo(hidl_vec<V1_2::HubAppInfo> appInfo) override {
+ return mCallback->handleAppsInfo(convertToOldAppInfo(appInfo));
+ }
+
+ protected:
+ sp<T> mCallback;
+};
+
+class IContextHubCallbackWrapperV1_0 : public ContextHubCallbackWrapper<V1_0::IContexthubCallback> {
+ public:
+ IContextHubCallbackWrapperV1_0(sp<V1_0::IContexthubCallback> callback)
+ : ContextHubCallbackWrapper(callback){};
+};
+
+class IContextHubCallbackWrapperV1_2 : public ContextHubCallbackWrapper<V1_2::IContexthubCallback> {
+ public:
+ IContextHubCallbackWrapperV1_2(sp<V1_2::IContexthubCallback> callback)
+ : ContextHubCallbackWrapper(callback){};
+
+ Return<void> handleClientMsg(V1_2::ContextHubMsg msg) override {
+ return mCallback->handleClientMsg_1_2(msg);
+ }
+
+ Return<void> handleAppsInfo(hidl_vec<V1_2::HubAppInfo> appInfo) override {
+ return mCallback->handleAppsInfo_1_2(appInfo);
+ }
+};
+
+} // namespace implementation
+} // namespace V1_X
+} // namespace contexthub
+} // namespace hardware
+} // namespace android
+
+#endif // ANDROID_HARDWARE_CONTEXTHUB_V1_X_ICONTEXTHUBCALLBACKWRAPPER_H
\ No newline at end of file
diff --git a/current.txt b/current.txt
index 8623fc0..bf6829a 100644
--- a/current.txt
+++ b/current.txt
@@ -767,6 +767,8 @@
98592d193a717066facf91428426e5abe211e3bd718bc372e29fb944ddbe6e7c android.hardware.wifi.supplicant@1.3::types
# ABI preserving changes to HALs during Android S
+e042522daa4b5f7fd4a0a19bcdadb93c79a1b04c09ef2c9813a3a8941032f3f5 android.hardware.contexthub@1.0::IContexthub
+c2f64133b83ede65c9939ef97ab5bd867b73faf3dba0e7e69f77c3c43d9e487e android.hardware.contexthub@1.0::IContexthubCallback
1ca372cd67d197df099e87616a613ba6ede6552638a603e18f86c8834302c3d1 android.hardware.gnss@1.0::IGnssMeasurementCallback
6a271e493907e8ba20912e42771bd0d99ae45431a851d5675ef9496d02510a34 android.hardware.gnss@1.1::IGnssMeasurementCallback
2c331a9605f3a08d9c1e0a36169ca57758bc43c11a78ef3f3730509885e52c15 android.hardware.graphics.composer@2.4::IComposerClient
@@ -787,6 +789,4 @@
# HALs released in Android S
# NOTE: waiting to freeze HALs until later in the release
-# NOTE: new HALs are recommended to be in AIDL
-6e64b33f1b720b66b0deb5e08dee37a99deaa94e2e9ebf7806703cabab56e21d android.hardware.contexthub@1.2::IContexthub
-3fb83f4539cab2c7bf9fdbecf7265d1c1dd6e8de9694046fe512b493c127ccea android.hardware.contexthub@1.2::types
+# NOTE: new HALs are recommended to be in AIDL
\ No newline at end of file
diff --git a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/BlocklistedSource.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/BlocklistedSource.aidl
index 89f5d53..0302676 100644
--- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/BlocklistedSource.aidl
+++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/BlocklistedSource.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/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/CorrelationVector.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/CorrelationVector.aidl
new file mode 100644
index 0000000..1f713fa
--- /dev/null
+++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/CorrelationVector.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.gnss;
+@VintfStability
+parcelable CorrelationVector {
+ int frequencyOffsetMps;
+ double samplingWidthM;
+ double samplingStartM;
+ int[] magnitude;
+}
diff --git a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/ElapsedRealtime.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/ElapsedRealtime.aidl
index a0e8de4..933f659 100644
--- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/ElapsedRealtime.aidl
+++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/ElapsedRealtime.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/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssClock.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssClock.aidl
index 42b940e..53ac0ef 100644
--- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssClock.aidl
+++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssClock.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/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssConstellationType.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssConstellationType.aidl
index 30d0227..18fdfa9 100644
--- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssConstellationType.aidl
+++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssConstellationType.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/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssData.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssData.aidl
index 7ffabd2..73ead10 100644
--- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssData.aidl
+++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssData.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/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssMeasurement.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssMeasurement.aidl
index 73d8a86..3d287e4 100644
--- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssMeasurement.aidl
+++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssMeasurement.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
@@ -42,6 +43,8 @@
double fullInterSignalBiasUncertaintyNs;
double satelliteInterSignalBiasNs;
double satelliteInterSignalBiasUncertaintyNs;
+ android.hardware.gnss.SatellitePvt satellitePvt;
+ android.hardware.gnss.CorrelationVector[] correlationVectors;
const int HAS_SNR = 1;
const int HAS_CARRIER_FREQUENCY = 512;
const int HAS_CARRIER_CYCLES = 1024;
@@ -52,6 +55,8 @@
const int HAS_FULL_ISB_UNCERTAINTY = 131072;
const int HAS_SATELLITE_ISB = 262144;
const int HAS_SATELLITE_ISB_UNCERTAINTY = 524288;
+ const int HAS_SATELLITE_PVT = 1048576;
+ const int HAS_CORRELATION_VECTOR = 2097152;
const int STATE_UNKNOWN = 0;
const int STATE_CODE_LOCK = 1;
const int STATE_BIT_SYNC = 2;
diff --git a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssMultipathIndicator.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssMultipathIndicator.aidl
index 75ca3af..5da60f7 100644
--- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssMultipathIndicator.aidl
+++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssMultipathIndicator.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/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssPowerStats.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssPowerStats.aidl
index d385fd4..358b570 100644
--- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssPowerStats.aidl
+++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssPowerStats.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/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssSignalType.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssSignalType.aidl
index f10b943..b2a498d 100644
--- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssSignalType.aidl
+++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssSignalType.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/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnss.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnss.aidl
index 10ac150..bd6f1ff 100644
--- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnss.aidl
+++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnss.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/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssCallback.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssCallback.aidl
index 62870d6..a04ad66 100644
--- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssCallback.aidl
+++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssCallback.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
@@ -20,4 +21,6 @@
interface IGnssCallback {
void gnssSetCapabilitiesCb(in int capabilities);
const int CAPABILITY_SATELLITE_BLOCKLIST = 1;
+ const int CAPABILITY_CORRELATION_VECTOR = 4096;
+ const int CAPABILITY_SATELLITE_PVT = 8192;
}
diff --git a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssConfiguration.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssConfiguration.aidl
index 5af30cf..eb4ad82 100644
--- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssConfiguration.aidl
+++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssConfiguration.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/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssMeasurementCallback.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssMeasurementCallback.aidl
index e05e9b9..764b896 100644
--- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssMeasurementCallback.aidl
+++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssMeasurementCallback.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/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssMeasurementInterface.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssMeasurementInterface.aidl
index 9576205..7cb7395 100644
--- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssMeasurementInterface.aidl
+++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssMeasurementInterface.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,6 @@
package android.hardware.gnss;
@VintfStability
interface IGnssMeasurementInterface {
- void setCallback(in android.hardware.gnss.IGnssMeasurementCallback callback, in boolean enableFullTracking);
+ void setCallback(in android.hardware.gnss.IGnssMeasurementCallback callback, in boolean enableFullTracking, in boolean enableCorrVecOutputs);
void close();
}
diff --git a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssPowerIndication.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssPowerIndication.aidl
index 843489e..c44903e 100644
--- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssPowerIndication.aidl
+++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssPowerIndication.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/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssPowerIndicationCallback.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssPowerIndicationCallback.aidl
index 5281d29..12e6762 100644
--- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssPowerIndicationCallback.aidl
+++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssPowerIndicationCallback.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/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssPsds.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssPsds.aidl
index ddef928..cae2ea6 100644
--- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssPsds.aidl
+++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssPsds.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/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssPsdsCallback.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssPsdsCallback.aidl
index 8413d2c..6888632 100644
--- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssPsdsCallback.aidl
+++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssPsdsCallback.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/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/PsdsType.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/PsdsType.aidl
index 9d1984e..d348c63 100644
--- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/PsdsType.aidl
+++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/PsdsType.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/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/SatelliteClockInfo.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/SatelliteClockInfo.aidl
new file mode 100644
index 0000000..bdba667
--- /dev/null
+++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/SatelliteClockInfo.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.gnss;
+@VintfStability
+parcelable SatelliteClockInfo {
+ double satHardwareCodeBiasMeters;
+ double satTimeCorrectionMeters;
+ double satClkDriftMps;
+}
diff --git a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/SatellitePositionEcef.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/SatellitePositionEcef.aidl
new file mode 100644
index 0000000..550fa4d
--- /dev/null
+++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/SatellitePositionEcef.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.gnss;
+@VintfStability
+parcelable SatellitePositionEcef {
+ double posXMeters;
+ double posYMeters;
+ double posZMeters;
+ double ureMeters;
+}
diff --git a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/SatellitePvt.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/SatellitePvt.aidl
new file mode 100644
index 0000000..4ff025e
--- /dev/null
+++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/SatellitePvt.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.gnss;
+@VintfStability
+parcelable SatellitePvt {
+ android.hardware.gnss.SatellitePositionEcef satPosEcef;
+ android.hardware.gnss.SatelliteVelocityEcef satVelEcef;
+ android.hardware.gnss.SatelliteClockInfo satClockInfo;
+ double ionoDelayMeters;
+ double tropoDelayMeters;
+}
diff --git a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/SatelliteVelocityEcef.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/SatelliteVelocityEcef.aidl
new file mode 100644
index 0000000..7db7ee6
--- /dev/null
+++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/SatelliteVelocityEcef.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.gnss;
+@VintfStability
+parcelable SatelliteVelocityEcef {
+ double velXMps;
+ double velYMps;
+ double velZMps;
+ double ureRateMps;
+}
diff --git a/gnss/aidl/android/hardware/gnss/CorrelationVector.aidl b/gnss/aidl/android/hardware/gnss/CorrelationVector.aidl
new file mode 100644
index 0000000..22a80ce
--- /dev/null
+++ b/gnss/aidl/android/hardware/gnss/CorrelationVector.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.gnss;
+
+/**
+ * Contains info about the correlation output of incoming GNSS signal and a local copy of
+ * its corresponding spreading code at a given frequency offset.
+ */
+@VintfStability
+parcelable CorrelationVector {
+
+ /**
+ * Frequency offset from reported pseudorange rate for this Correlation Vector.
+ */
+ int frequencyOffsetMps;
+
+ /**
+ * Space between correlation samples in meters.
+ */
+ double samplingWidthM;
+
+ /**
+ * Offset of the first sampling bin in meters.
+ * The following sampling bins are located at positive offsets from this value as follows:
+ * samplingStartM, samplingStartM + samplingWidthM, ... , samplingStartM +
+ * (magnitude.size-1) * samplingWidthM.
+ */
+ double samplingStartM;
+
+ /**
+ * Normalized correlation magnitude values from -1 to 1, the reported value must be encoded as
+ * signed 16 bit integer where 1 is represented by 32767 and -1 is represented by -32768.
+ *
+ * The length of the array is defined by the GNSS chipset.
+ */
+ int[] magnitude;
+}
\ No newline at end of file
diff --git a/gnss/aidl/android/hardware/gnss/GnssMeasurement.aidl b/gnss/aidl/android/hardware/gnss/GnssMeasurement.aidl
index ce88647..2c56a41 100644
--- a/gnss/aidl/android/hardware/gnss/GnssMeasurement.aidl
+++ b/gnss/aidl/android/hardware/gnss/GnssMeasurement.aidl
@@ -16,8 +16,10 @@
package android.hardware.gnss;
+import android.hardware.gnss.CorrelationVector;
import android.hardware.gnss.GnssSignalType;
import android.hardware.gnss.GnssMultipathIndicator;
+import android.hardware.gnss.SatellitePvt;
/**
* Represents a GNSS Measurement, it contains raw and computed information.
@@ -57,6 +59,14 @@
* GnssMeasurement.
*/
const int HAS_SATELLITE_ISB_UNCERTAINTY = 1 << 19;
+ /**
+ * Bit mask indicating a valid satellite PVT is stored in the GnssMeasurement.
+ */
+ const int HAS_SATELLITE_PVT = 1 << 20;
+ /**
+ * Bit mask indicating valid correlation vectors are stored in the GnssMeasurement.
+ */
+ const int HAS_CORRELATION_VECTOR = 1 << 21;
/**
* A bitfield of flags indicating the validity of the fields in this GnssMeasurement. The bit
@@ -612,4 +622,21 @@
* 1-sigma uncertainty associated with the satellite inter-signal bias in nanoseconds.
*/
double satelliteInterSignalBiasUncertaintyNs;
+
+ /**
+ * The GNSS satellite position, velocity and time information at the signal transmission time
+ * receivedSvTimeInNs.
+ *
+ * If the data is available, gnssMeasurementFlags must contain HAS_SATELLITE_PVT.
+ */
+ SatellitePvt satellitePvt;
+
+ /**
+ * A list of Correlation Vectors with each vector corresponding to a frequency offset.
+ *
+ * To represent correlation values over a 2D spaces (delay and frequency), a CorrelationVector
+ * is required per frequency offset, and each CorrelationVector contains correlation values
+ * at equally spaced spatial offsets.
+ */
+ CorrelationVector[] correlationVectors;
}
\ No newline at end of file
diff --git a/gnss/aidl/android/hardware/gnss/IGnssCallback.aidl b/gnss/aidl/android/hardware/gnss/IGnssCallback.aidl
index a46a018..81e0638 100644
--- a/gnss/aidl/android/hardware/gnss/IGnssCallback.aidl
+++ b/gnss/aidl/android/hardware/gnss/IGnssCallback.aidl
@@ -27,9 +27,15 @@
@VintfStability
interface IGnssCallback {
- /** Capability bit mask indicating GNSS supports blocklisting satellites */
+ /** Capability bit mask indicating that GNSS supports blocklisting satellites */
const int CAPABILITY_SATELLITE_BLOCKLIST = 1 << 0;
+ /** Capability bit mask indicating that GNSS supports correlation vector */
+ const int CAPABILITY_CORRELATION_VECTOR = 1 << 12;
+
+ /** Capability bit mask indicating that GNSS supports satellite PVT */
+ const int CAPABILITY_SATELLITE_PVT = 1 << 13;
+
/**
* Callback to inform framework of the GNSS HAL implementation's capabilities.
*
diff --git a/gnss/aidl/android/hardware/gnss/IGnssMeasurementInterface.aidl b/gnss/aidl/android/hardware/gnss/IGnssMeasurementInterface.aidl
index fdeebde..04cdf64 100644
--- a/gnss/aidl/android/hardware/gnss/IGnssMeasurementInterface.aidl
+++ b/gnss/aidl/android/hardware/gnss/IGnssMeasurementInterface.aidl
@@ -37,12 +37,16 @@
* The GNSS chipset is allowed to consume more power in this mode. If false, API must
* optimize power via duty cycling, constellations and frequency limits, etc.
*
+ * @param enableCorrVecOutputs If true, enable correlation vectors as part of the raw GNSS
+ * measurements outputs. If false, disable correlation vectors.
+ *
* @return initRet Returns SUCCESS if successful. Returns ERROR_ALREADY_INIT if a callback has
* already been registered without a corresponding call to 'close'. Returns ERROR_GENERIC
* for any other error. The HAL must not generate any other updates upon returning this
* error code.
*/
- void setCallback(in IGnssMeasurementCallback callback, in boolean enableFullTracking);
+ void setCallback(in IGnssMeasurementCallback callback, in boolean enableFullTracking,
+ in boolean enableCorrVecOutputs);
/**
* Stops updates from the HAL, and unregisters the callback routines. After a call to close(),
diff --git a/gnss/aidl/android/hardware/gnss/SatelliteClockInfo.aidl b/gnss/aidl/android/hardware/gnss/SatelliteClockInfo.aidl
new file mode 100644
index 0000000..844fd1c
--- /dev/null
+++ b/gnss/aidl/android/hardware/gnss/SatelliteClockInfo.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.gnss;
+
+/**
+ * Contains estimates of the satellite clock info.
+ */
+@VintfStability
+parcelable SatelliteClockInfo {
+ /**
+ * Satellite hardware code bias of the reported code type w.r.t
+ * ionosphere-free measurement in meters.
+ */
+ double satHardwareCodeBiasMeters;
+
+ /**
+ * Satellite time correction for ionospheric-free signal measurement
+ * (meters). The satellite clock correction for the given signal type
+ * = satTimeCorrectionMeters - satHardwareCodeBiasMeters.
+ */
+ double satTimeCorrectionMeters;
+
+ /** Satellite clock drift (meters per second). */
+ double satClkDriftMps;
+}
\ No newline at end of file
diff --git a/gnss/aidl/android/hardware/gnss/SatellitePositionEcef.aidl b/gnss/aidl/android/hardware/gnss/SatellitePositionEcef.aidl
new file mode 100644
index 0000000..4b3615e
--- /dev/null
+++ b/gnss/aidl/android/hardware/gnss/SatellitePositionEcef.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.gnss;
+
+/**
+ * Contains estimates of the satellite position fields in ECEF coordinate frame.
+ */
+@VintfStability
+parcelable SatellitePositionEcef {
+ /** Satellite position X in WGS84 ECEF (meters). */
+ double posXMeters;
+
+ /** Satellite position Y in WGS84 ECEF (meters). */
+ double posYMeters;
+
+ /** Satellite position Z in WGS84 ECEF (meters). */
+ double posZMeters;
+
+ /**
+ * The Signal in Space User Range Error (URE) (meters).
+ *
+ * It covers satellite position and clock errors projected to the pseudorange measurements.
+ */
+ double ureMeters;
+}
\ No newline at end of file
diff --git a/gnss/aidl/android/hardware/gnss/SatellitePvt.aidl b/gnss/aidl/android/hardware/gnss/SatellitePvt.aidl
new file mode 100644
index 0000000..ea55f0c
--- /dev/null
+++ b/gnss/aidl/android/hardware/gnss/SatellitePvt.aidl
@@ -0,0 +1,49 @@
+/*
+ * 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.gnss;
+
+import android.hardware.gnss.SatellitePositionEcef;
+import android.hardware.gnss.SatelliteVelocityEcef;
+import android.hardware.gnss.SatelliteClockInfo;
+
+/**
+ * Contains estimates of the satellite position, velocity and time in the
+ * ECEF coordinate frame.
+ */
+@VintfStability
+parcelable SatellitePvt {
+ /**
+ * Satellite position in WGS84 ECEF. See comments of
+ * SatellitePositionEcef for units.
+ */
+ SatellitePositionEcef satPosEcef;
+
+ /**
+ * Satellite velocity in WGS84 ECEF. See comments of
+ * SatelliteVelocityEcef for units.
+ */
+ SatelliteVelocityEcef satVelEcef;
+
+ /** Satellite clock bias and drift info. */
+ SatelliteClockInfo satClockInfo;
+
+ /** Ionospheric delay in meters. */
+ double ionoDelayMeters;
+
+ /** Tropospheric delay in meters. */
+ double tropoDelayMeters;
+}
\ No newline at end of file
diff --git a/gnss/aidl/android/hardware/gnss/SatelliteVelocityEcef.aidl b/gnss/aidl/android/hardware/gnss/SatelliteVelocityEcef.aidl
new file mode 100644
index 0000000..25ece3a
--- /dev/null
+++ b/gnss/aidl/android/hardware/gnss/SatelliteVelocityEcef.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.gnss;
+
+/**
+ * Contains estimates of the satellite velocity fields in the ECEF coordinate frame.
+ */
+@VintfStability
+parcelable SatelliteVelocityEcef {
+ /** Satellite velocity X in WGS84 ECEF (meters per second). */
+ double velXMps;
+
+ /** Satellite velocity Y in WGS84 ECEF (meters per second). */
+ double velYMps;
+
+ /** Satellite velocity Z in WGS84 ECEF (meters per second). */
+ double velZMps;
+
+ /**
+ * The Signal in Space User Range Error Rate (URE Rate) (meters per second).
+ *
+ * It covers satellite velocity error and Satellite clock drift
+ * projected to the pseudorange rate measurements.
+ */
+ double ureRateMps;
+}
\ No newline at end of file
diff --git a/gnss/aidl/default/Gnss.cpp b/gnss/aidl/default/Gnss.cpp
index 02bad60..435afa3 100644
--- a/gnss/aidl/default/Gnss.cpp
+++ b/gnss/aidl/default/Gnss.cpp
@@ -36,7 +36,10 @@
sGnssCallback = callback;
- int capabilities = (int)IGnssCallback::CAPABILITY_SATELLITE_BLOCKLIST;
+ int capabilities = (int)(IGnssCallback::CAPABILITY_SATELLITE_BLOCKLIST |
+ IGnssCallback::CAPABILITY_SATELLITE_PVT |
+ IGnssCallback::CAPABILITY_CORRELATION_VECTOR);
+
auto status = sGnssCallback->gnssSetCapabilitiesCb(capabilities);
if (!status.isOk()) {
ALOGE("%s: Unable to invoke callback.gnssSetCapabilities", __func__);
diff --git a/gnss/aidl/default/GnssMeasurementInterface.cpp b/gnss/aidl/default/GnssMeasurementInterface.cpp
index d726d95..cae9499 100644
--- a/gnss/aidl/default/GnssMeasurementInterface.cpp
+++ b/gnss/aidl/default/GnssMeasurementInterface.cpp
@@ -34,8 +34,10 @@
}
ndk::ScopedAStatus GnssMeasurementInterface::setCallback(
- const std::shared_ptr<IGnssMeasurementCallback>& callback, const bool enableFullTracking) {
- ALOGD("setCallback: enableFullTracking: %d", (int)enableFullTracking);
+ const std::shared_ptr<IGnssMeasurementCallback>& callback, const bool enableFullTracking,
+ const bool enableCorrVecOutputs) {
+ ALOGD("setCallback: enableFullTracking: %d enableCorrVecOutputs: %d", (int)enableFullTracking,
+ (int)enableCorrVecOutputs);
std::unique_lock<std::mutex> lock(mMutex);
sCallback = callback;
@@ -43,7 +45,7 @@
ALOGW("GnssMeasurement callback already set. Resetting the callback...");
stop();
}
- start();
+ start(enableCorrVecOutputs);
return ndk::ScopedAStatus::ok();
}
@@ -56,12 +58,12 @@
return ndk::ScopedAStatus::ok();
}
-void GnssMeasurementInterface::start() {
+void GnssMeasurementInterface::start(const bool enableCorrVecOutputs) {
ALOGD("start");
mIsActive = true;
- mThread = std::thread([this]() {
+ mThread = std::thread([this, enableCorrVecOutputs]() {
while (mIsActive == true) {
- auto measurement = Utils::getMockMeasurement();
+ auto measurement = Utils::getMockMeasurement(enableCorrVecOutputs);
this->reportMeasurement(measurement);
std::this_thread::sleep_for(std::chrono::milliseconds(mMinIntervalMillis));
diff --git a/gnss/aidl/default/GnssMeasurementInterface.h b/gnss/aidl/default/GnssMeasurementInterface.h
index 69cd871..db63515 100644
--- a/gnss/aidl/default/GnssMeasurementInterface.h
+++ b/gnss/aidl/default/GnssMeasurementInterface.h
@@ -29,11 +29,12 @@
GnssMeasurementInterface();
~GnssMeasurementInterface();
ndk::ScopedAStatus setCallback(const std::shared_ptr<IGnssMeasurementCallback>& callback,
- const bool enableFullTracking) override;
+ const bool enableFullTracking,
+ const bool enableCorrVecOutputs) override;
ndk::ScopedAStatus close() override;
private:
- void start();
+ void start(const bool enableCorrVecOutputs);
void stop();
void reportMeasurement(const GnssData&);
diff --git a/gnss/aidl/vts/gnss_hal_test_cases.cpp b/gnss/aidl/vts/gnss_hal_test_cases.cpp
index 857c742..ae0551d 100644
--- a/gnss/aidl/vts/gnss_hal_test_cases.cpp
+++ b/gnss/aidl/vts/gnss_hal_test_cases.cpp
@@ -69,14 +69,21 @@
* 2. Sets a GnssMeasurementCallback, waits for a measurement, and verifies fields are valid.
*/
TEST_P(GnssHalTest, TestGnssMeasurementExtension) {
+ const bool kIsCorrelationVectorSupported = aidl_gnss_cb_->last_capabilities_ &
+ (int)GnssCallbackAidl::CAPABILITY_CORRELATION_VECTOR;
const int kFirstGnssMeasurementTimeoutSeconds = 10;
+
+ bool has_capability_satpvt = false;
+
sp<IGnssMeasurementInterface> iGnssMeasurement;
auto status = aidl_gnss_hal_->getExtensionGnssMeasurement(&iGnssMeasurement);
ASSERT_TRUE(status.isOk());
ASSERT_TRUE(iGnssMeasurement != nullptr);
auto callback = sp<GnssMeasurementCallbackAidl>::make();
- status = iGnssMeasurement->setCallback(callback, /* enableFullTracking= */ true);
+ status =
+ iGnssMeasurement->setCallback(callback, /* enableFullTracking= */ true,
+ /* enableCorrVecOutputs */ kIsCorrelationVectorSupported);
ASSERT_TRUE(status.isOk());
android::hardware::gnss::GnssData lastMeasurement;
@@ -102,6 +109,10 @@
GnssClock::HAS_FULL_BIAS | GnssClock::HAS_BIAS |
GnssClock::HAS_BIAS_UNCERTAINTY | GnssClock::HAS_DRIFT |
GnssClock::HAS_DRIFT_UNCERTAINTY));
+
+ if (aidl_gnss_cb_->last_capabilities_ & (int)GnssCallbackAidl::CAPABILITY_SATELLITE_PVT) {
+ has_capability_satpvt = true;
+ }
for (const auto& measurement : lastMeasurement.measurements) {
ASSERT_TRUE(
measurement.flags >= 0 &&
@@ -112,7 +123,41 @@
GnssMeasurement::HAS_AUTOMATIC_GAIN_CONTROL |
GnssMeasurement::HAS_FULL_ISB | GnssMeasurement::HAS_FULL_ISB_UNCERTAINTY |
GnssMeasurement::HAS_SATELLITE_ISB |
- GnssMeasurement::HAS_SATELLITE_ISB_UNCERTAINTY));
+ GnssMeasurement::HAS_SATELLITE_ISB_UNCERTAINTY |
+ GnssMeasurement::HAS_SATELLITE_PVT |
+ GnssMeasurement::HAS_CORRELATION_VECTOR));
+
+ if ((measurement.flags & GnssMeasurement::HAS_SATELLITE_PVT) &&
+ (has_capability_satpvt == true)) {
+ ASSERT_TRUE(measurement.satellitePvt.satPosEcef.posXMeters >= -43000000 &&
+ measurement.satellitePvt.satPosEcef.posXMeters <= 43000000);
+ ASSERT_TRUE(measurement.satellitePvt.satPosEcef.posYMeters >= -43000000 &&
+ measurement.satellitePvt.satPosEcef.posYMeters <= 43000000);
+ ASSERT_TRUE(measurement.satellitePvt.satPosEcef.posZMeters >= -43000000 &&
+ measurement.satellitePvt.satPosEcef.posZMeters <= 43000000);
+ ASSERT_TRUE(measurement.satellitePvt.satPosEcef.ureMeters > 0);
+ ASSERT_TRUE(measurement.satellitePvt.satVelEcef.velXMps >= -4000 &&
+ measurement.satellitePvt.satVelEcef.velXMps <= 4000);
+ ASSERT_TRUE(measurement.satellitePvt.satVelEcef.velYMps >= -4000 &&
+ measurement.satellitePvt.satVelEcef.velYMps <= 4000);
+ ASSERT_TRUE(measurement.satellitePvt.satVelEcef.velZMps >= -4000 &&
+ measurement.satellitePvt.satVelEcef.velZMps <= 4000);
+ ASSERT_TRUE(measurement.satellitePvt.satVelEcef.ureRateMps > 0);
+ }
+
+ if (kIsCorrelationVectorSupported &&
+ measurement.flags & GnssMeasurement::HAS_CORRELATION_VECTOR) {
+ ASSERT_TRUE(measurement.correlationVectors.size() > 0);
+ for (const auto& correlationVector : measurement.correlationVectors) {
+ ASSERT_GE(correlationVector.frequencyOffsetMps, 0);
+ ASSERT_GT(correlationVector.samplingWidthM, 0);
+ ASSERT_GE(correlationVector.samplingStartM, 0);
+ ASSERT_TRUE(correlationVector.magnitude.size() > 0);
+ for (const auto& magnitude : correlationVector.magnitude) {
+ ASSERT_TRUE(magnitude >= -32768 && magnitude <= 32767);
+ }
+ }
+ }
}
status = iGnssMeasurement->close();
diff --git a/gnss/common/utils/default/Utils.cpp b/gnss/common/utils/default/Utils.cpp
index e383d51..ccc7145 100644
--- a/gnss/common/utils/default/Utils.cpp
+++ b/gnss/common/utils/default/Utils.cpp
@@ -140,7 +140,7 @@
return gnssData;
}
-GnssData Utils::getMockMeasurement() {
+GnssData Utils::getMockMeasurement(const bool enableCorrVecOutputs) {
aidl::android::hardware::gnss::GnssSignalType signalType = {
.constellation = aidl::android::hardware::gnss::GnssConstellationType::GLONASS,
.carrierFrequencyHz = 1.59975e+09,
@@ -152,10 +152,10 @@
GnssMeasurement::HAS_CARRIER_PHASE_UNCERTAINTY |
GnssMeasurement::HAS_FULL_ISB | GnssMeasurement::HAS_FULL_ISB_UNCERTAINTY |
GnssMeasurement::HAS_SATELLITE_ISB |
- GnssMeasurement::HAS_SATELLITE_ISB_UNCERTAINTY,
+ GnssMeasurement::HAS_SATELLITE_ISB_UNCERTAINTY |
+ GnssMeasurement::HAS_SATELLITE_PVT,
.svid = 13,
.signalType = signalType,
- .timeOffsetNs = 0.0,
.receivedSvTimeInNs = 8195997131077,
.receivedSvTimeUncertaintyInNs = 15,
.antennaCN0DbHz = 30.0,
@@ -175,7 +175,20 @@
.fullInterSignalBiasUncertaintyNs = 792.0,
.satelliteInterSignalBiasNs = 233.9,
.satelliteInterSignalBiasUncertaintyNs = 921.2,
- };
+ .satellitePvt = {.satPosEcef = {.posXMeters = 10442993.1153328,
+ .posYMeters = -19926932.8051666,
+ .posZMeters = -12034295.0216203,
+ .ureMeters = 1000.2345678},
+ .satVelEcef = {.velXMps = -478.667183715732,
+ .velYMps = 1580.68371984114,
+ .velZMps = -3030.52994449997,
+ .ureRateMps = 10.2345678},
+ .satClockInfo = {.satHardwareCodeBiasMeters = 1.396983861923e-09,
+ .satTimeCorrectionMeters = -7113.08964331,
+ .satClkDriftMps = 0},
+ .ionoDelayMeters = 3.069949602639317e-08,
+ .tropoDelayMeters = 3.882265204404031},
+ .correlationVectors = {}};
GnssClock clock = {.gnssClockFlags = GnssClock::HAS_FULL_BIAS | GnssClock::HAS_FULL_BIAS |
GnssClock::HAS_BIAS_UNCERTAINTY | GnssClock::HAS_DRIFT |
@@ -196,6 +209,21 @@
// or don't set the field.
.timeUncertaintyNs = 1020400};
+ if (enableCorrVecOutputs) {
+ aidl::android::hardware::gnss::CorrelationVector correlationVector1 = {
+ .frequencyOffsetMps = 10,
+ .samplingWidthM = 30,
+ .samplingStartM = 0,
+ .magnitude = {0, 5000, 10000, 5000, 0, 0, 3000, 0}};
+ aidl::android::hardware::gnss::CorrelationVector correlationVector2 = {
+ .frequencyOffsetMps = 20,
+ .samplingWidthM = 30,
+ .samplingStartM = 0,
+ .magnitude = {0, 3000, 5000, 3000, 0, 0, 1000, 0}};
+ measurement.correlationVectors = {correlationVector1, correlationVector2};
+ measurement.flags |= GnssMeasurement::HAS_CORRELATION_VECTOR;
+ }
+
GnssData gnssData = {
.measurements = {measurement}, .clock = clock, .elapsedRealtime = timestamp};
return gnssData;
@@ -228,7 +256,8 @@
.verticalAccuracyMeters = kMockVerticalAccuracyMeters,
.speedAccuracyMetersPerSecond = kMockSpeedAccuracyMetersPerSecond,
.bearingAccuracyDegrees = kMockBearingAccuracyDegrees,
- .timestamp = kMockTimestamp};
+ .timestamp =
+ static_cast<int64_t>(kMockTimestamp + ::android::elapsedRealtimeNano() / 1e6)};
return location;
}
diff --git a/gnss/common/utils/default/include/Utils.h b/gnss/common/utils/default/include/Utils.h
index 0ca1b00..771d39d 100644
--- a/gnss/common/utils/default/include/Utils.h
+++ b/gnss/common/utils/default/include/Utils.h
@@ -30,7 +30,8 @@
namespace common {
struct Utils {
- static aidl::android::hardware::gnss::GnssData getMockMeasurement();
+ static aidl::android::hardware::gnss::GnssData getMockMeasurement(
+ const bool enableCorrVecOutputs);
static V2_0::IGnssMeasurementCallback::GnssData getMockMeasurementV2_0();
static V2_1::IGnssMeasurementCallback::GnssData getMockMeasurementV2_1();
static V2_0::GnssLocation getMockLocationV2_0();
diff --git a/graphics/composer/2.4/vts/functional/VtsHalGraphicsComposerV2_4TargetTest.cpp b/graphics/composer/2.4/vts/functional/VtsHalGraphicsComposerV2_4TargetTest.cpp
index d2d6d7d..25c990b 100644
--- a/graphics/composer/2.4/vts/functional/VtsHalGraphicsComposerV2_4TargetTest.cpp
+++ b/graphics/composer/2.4/vts/functional/VtsHalGraphicsComposerV2_4TargetTest.cpp
@@ -19,6 +19,8 @@
#include <algorithm>
#include <regex>
#include <thread>
+#include <unordered_map>
+#include <utility>
#include <android-base/logging.h>
#include <android-base/properties.h>
@@ -285,6 +287,59 @@
}
}
+TEST_P(GraphicsComposerHidlTest, GetDisplayAttribute_2_4_ConfigsInAGroupDifferOnlyByVsyncPeriod) {
+ struct Resolution {
+ int32_t width, height;
+ };
+ struct Dpi {
+ int32_t x, y;
+ };
+ for (const auto& display : mDisplays) {
+ std::vector<Config> configs = mComposerClient->getDisplayConfigs(display.get());
+ std::unordered_map<int32_t, Resolution> configGroupToResolutionMap;
+ std::unordered_map<int32_t, Dpi> configGroupToDpiMap;
+ for (auto config : configs) {
+ const auto configGroup = mComposerClient->getDisplayAttribute_2_4(
+ display.get(), config, IComposerClient::Attribute::CONFIG_GROUP);
+ const auto width = mComposerClient->getDisplayAttribute_2_4(
+ display.get(), config, IComposerClient::Attribute::WIDTH);
+ const auto height = mComposerClient->getDisplayAttribute_2_4(
+ display.get(), config, IComposerClient::Attribute::HEIGHT);
+ if (configGroupToResolutionMap.find(configGroup) == configGroupToResolutionMap.end()) {
+ configGroupToResolutionMap[configGroup] = {width, height};
+ }
+ EXPECT_EQ(configGroupToResolutionMap[configGroup].width, width);
+ EXPECT_EQ(configGroupToResolutionMap[configGroup].height, height);
+
+ int32_t dpiX = -1;
+ mComposerClient->getRaw()->getDisplayAttribute_2_4(
+ display.get(), config, IComposerClient::Attribute::DPI_X,
+ [&](const auto& tmpError, const auto& value) {
+ if (tmpError == Error::NONE) {
+ dpiX = value;
+ }
+ });
+ int32_t dpiY = -1;
+ mComposerClient->getRaw()->getDisplayAttribute_2_4(
+ display.get(), config, IComposerClient::Attribute::DPI_Y,
+ [&](const auto& tmpError, const auto& value) {
+ if (tmpError == Error::NONE) {
+ dpiY = value;
+ }
+ });
+ if (dpiX == -1 && dpiY == -1) {
+ continue;
+ }
+
+ if (configGroupToDpiMap.find(configGroup) == configGroupToDpiMap.end()) {
+ configGroupToDpiMap[configGroup] = {dpiX, dpiY};
+ }
+ EXPECT_EQ(configGroupToDpiMap[configGroup].x, dpiX);
+ EXPECT_EQ(configGroupToDpiMap[configGroup].y, dpiY);
+ }
+ }
+}
+
TEST_P(GraphicsComposerHidlTest, getDisplayVsyncPeriod_BadDisplay) {
VsyncPeriodNanos vsyncPeriodNanos;
EXPECT_EQ(Error::BAD_DISPLAY,
diff --git a/identity/aidl/default/Android.bp b/identity/aidl/default/Android.bp
index 2eb0faa..7f342d0 100644
--- a/identity/aidl/default/Android.bp
+++ b/identity/aidl/default/Android.bp
@@ -1,3 +1,67 @@
+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",
+ ],
+ 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-ndk_platform",
+ "android.hardware.keymaster-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 +71,30 @@
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-libeic-hal-common",
+ "android.hardware.identity-libeic-library",
],
srcs: [
- "IdentityCredential.cpp",
- "IdentityCredentialStore.cpp",
- "WritableIdentityCredential.cpp",
- "Util.cpp",
"service.cpp",
+ "FakeSecureHardwareProxy.cpp",
],
}
diff --git a/identity/aidl/default/EicOpsImpl.cc b/identity/aidl/default/EicOpsImpl.cc
new file mode 100644
index 0000000..3f2ec8b
--- /dev/null
+++ b/identity/aidl/default/EicOpsImpl.cc
@@ -0,0 +1,506 @@
+/*
+ * 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::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 not %zd bytes long as expected", (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("Private 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 not %zd bytes long as expected", (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, 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);
+
+ std::string serialDecimal = android::base::StringPrintf("%d", serial);
+
+ optional<vector<uint8_t>> certVec =
+ android::hardware::identity::support::ecPublicKeyGenerateCertificate(
+ pubKeyVec, signingKeyVec, serialDecimal, issuerName, subjectName,
+ validityNotBefore, validityNotAfter);
+ 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..de6762f
--- /dev/null
+++ b/identity/aidl/default/FakeSecureHardwareProxy.cpp
@@ -0,0 +1,324 @@
+/*
+ * 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);
+}
+
+// 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 (80 bytes).
+optional<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::finishGetCredentialData(
+ const string& docType) {
+ vector<uint8_t> encryptedCredentialKeys(80);
+ if (!eicProvisioningFinishGetCredentialData(&ctx_, docType.c_str(),
+ encryptedCredentialKeys.data())) {
+ return {};
+ }
+ 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());
+}
+
+// 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, size_t proofOfDeletionCborSize) {
+ vector<uint8_t> signatureOfToBeSigned(EIC_ECDSA_P256_SIGNATURE_SIZE);
+ if (!eicPresentationDeleteCredential(&ctx_, docType.c_str(), proofOfDeletionCborSize,
+ 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..b858dd4
--- /dev/null
+++ b/identity/aidl/default/FakeSecureHardwareProxy.h
@@ -0,0 +1,151 @@
+/*
+ * 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 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,
+ size_t proofOfDeletionCborSize) 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/IdentityCredential.cpp b/identity/aidl/default/common/IdentityCredential.cpp
similarity index 65%
rename from identity/aidl/default/IdentityCredential.cpp
rename to identity/aidl/default/common/IdentityCredential.cpp
index dfcd4f5..270fcfa 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,8 @@
#include <cppbor.h>
#include <cppbor_parse.h>
+#include "FakeSecureHardwareProxy.h"
+
namespace aidl::android::hardware::identity {
using ::aidl::android::hardware::keymaster::Timestamp;
@@ -69,40 +70,17 @@
docType_ = docTypeItem->value();
testCredential_ = testCredentialItem->value();
- vector<uint8_t> hardwareBoundKey;
- if (testCredential_) {
- hardwareBoundKey = support::getTestHardwareBoundKey();
- } else {
- hardwareBoundKey = getHardwareBoundKey();
- }
-
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";
+
+ if (encryptedCredentialKeys.size() != 80) {
+ LOG(ERROR) << "Unexpected size for encrypted 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;
+ if (!hwProxy_->initialize(testCredential_, docType_, encryptedCredentialKeys)) {
+ LOG(ERROR) << "hwProxy->initialize failed";
+ return false;
}
- 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;
}
@@ -110,12 +88,20 @@
ndk::ScopedAStatus IdentityCredential::deleteCredential(
vector<uint8_t>* outProofOfDeletionSignature) {
cppbor::Array array = {"ProofOfDeletion", docType_, testCredential_};
- vector<uint8_t> proofOfDeletion = array.encode();
+ vector<uint8_t> proofOfDeletionCbor = array.encode();
+ vector<uint8_t> podDigest = support::sha256(proofOfDeletionCbor);
- optional<vector<uint8_t>> signature = support::coseSignEcDsa(credentialPrivKey_,
- proofOfDeletion, // payload
- {}, // additionalData
- {}); // certificateChain
+ optional<vector<uint8_t>> signatureOfToBeSigned =
+ hwProxy_->deleteCredential(docType_, 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"));
@@ -126,22 +112,28 @@
}
ndk::ScopedAStatus IdentityCredential::createEphemeralKeyPair(vector<uint8_t>* outKeyPair) {
- optional<vector<uint8_t>> kp = support::createEcKeyPair();
- if (!kp) {
+ optional<vector<uint8_t>> ephemeralPriv = hwProxy_->createEphemeralKeyPair();
+ if (!ephemeralPriv) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error creating ephemeral key pair"));
+ 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 +144,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 +182,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 +190,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 +235,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 +365,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 +465,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 +474,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 +526,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 +545,7 @@
* encoded.
*/
size_t ret = 0;
- size_t numNamespacesWithValues = 0;
+ vector<unsigned int> numEntriesPerNamespace;
for (const RequestNamespace& rns : requestNamespaces_) {
vector<RequestDataItem> itemsToInclude;
@@ -562,13 +568,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 +580,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 +602,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 +630,11 @@
"No more name spaces left to go through"));
}
+ bool newNamespace;
if (currentNameSpace_ == "") {
// First call.
currentNameSpace_ = nameSpace;
+ newNamespace = true;
}
if (nameSpace == currentNameSpace_) {
@@ -655,6 +661,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 +681,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 +735,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 +801,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 +818,18 @@
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();
}
diff --git a/identity/aidl/default/IdentityCredential.h b/identity/aidl/default/common/IdentityCredential.h
similarity index 88%
rename from identity/aidl/default/IdentityCredential.h
rename to identity/aidl/default/common/IdentityCredential.h
index a8a6409..2281821 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,11 @@
class IdentityCredential : public BnIdentityCredential {
public:
- IdentityCredential(const vector<uint8_t>& credentialData)
- : credentialData_(credentialData),
+ IdentityCredential(sp<SecureHardwarePresentationProxy> hwProxy,
+ const vector<uint8_t>& credentialData)
+ : hwProxy_(hwProxy),
+ credentialData_(credentialData),
numStartRetrievalCalls_(0),
- authChallenge_(0),
expectedDeviceNameSpacesSize_(0) {}
// Parses and decrypts credentialData_, return a status code from
@@ -75,14 +81,13 @@
private:
// Set by constructor
+ sp<SecureHardwarePresentationProxy> hwProxy_;
vector<uint8_t> credentialData_;
int numStartRetrievalCalls_;
// Set by initialize()
string docType_;
bool testCredential_;
- vector<uint8_t> storageKey_;
- vector<uint8_t> credentialPrivKey_;
// Set by createEphemeralKeyPair()
vector<uint8_t> ephemeralPublicKey_;
@@ -90,9 +95,6 @@
// Set by setReaderEphemeralPublicKey()
vector<uint8_t> readerPublicKey_;
- // Set by createAuthChallenge()
- uint64_t authChallenge_;
-
// Set by setRequestedNamespaces()
vector<RequestNamespace> requestNamespaces_;
@@ -100,7 +102,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 +112,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 90%
rename from identity/aidl/default/IdentityCredentialStore.cpp
rename to identity/aidl/default/common/IdentityCredentialStore.cpp
index 30dc6f3..13f91aa 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>(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..b89ad87
--- /dev/null
+++ b/identity/aidl/default/common/SecureHardwareProxy.h
@@ -0,0 +1,174 @@
+/*
+ * 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;
+
+ // 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,
+ size_t proofOfDeletionCborSize) = 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..1328f36 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,55 @@
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.
+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 +103,8 @@
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_FAILED, "startPersonalization called already"));
}
-
startPersonalizationCalled_ = true;
+
numAccessControlProfileRemaining_ = accessControlProfileCount;
remainingEntryCounts_ = entryCounts;
entryNameSpace_ = "";
@@ -133,6 +113,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 +126,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 +153,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 +241,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 +281,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 +311,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 +346,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 85%
rename from identity/aidl/default/WritableIdentityCredential.h
rename to identity/aidl/default/common/WritableIdentityCredential.h
index 5645852..c6f0628 100644
--- a/identity/aidl/default/WritableIdentityCredential.h
+++ b/identity/aidl/default/common/WritableIdentityCredential.h
@@ -23,16 +23,24 @@
#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) {}
+ WritableIdentityCredential(sp<SecureHardwareProvisioningProxy> hwProxy, const string& docType,
+ bool testCredential)
+ : hwProxy_(hwProxy), docType_(docType), testCredential_(testCredential) {}
+
+ ~WritableIdentityCredential();
// Creates the Credential Key. Returns false on failure. Must be called
// right after construction.
@@ -57,7 +65,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 +73,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 +98,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..37d5b81 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>2</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..ec049b1
--- /dev/null
+++ b/identity/aidl/default/libeic/EicCbor.c
@@ -0,0 +1,236 @@
+/*
+ * 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) {
+ 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) {
+ cbor->size = 0;
+ cbor->bufferSize = bufferSize;
+ cbor->buffer = buffer;
+ cbor->digestType = EIC_CBOR_DIGEST_TYPE_HMAC_SHA256;
+ eicOpsHmacSha256Init(&cbor->digester.hmacSha256, hmacKey, hmacKeySize);
+}
+
+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->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..4686b38
--- /dev/null
+++ b/identity/aidl/default/libeic/EicCbor.h
@@ -0,0 +1,156 @@
+/*
+ * 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 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);
+
+/* 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..da4dabf
--- /dev/null
+++ b/identity/aidl/default/libeic/EicOps.h
@@ -0,0 +1,299 @@
+/*
+ * 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().
+//
+// 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, 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..d3f5556
--- /dev/null
+++ b/identity/aidl/default/libeic/EicPresentation.c
@@ -0,0 +1,728 @@
+/*
+ * 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[80]) {
+ uint8_t credentialKeys[52];
+
+ eicMemSet(ctx, '\0', sizeof(EicPresentation));
+
+ if (!eicOpsDecryptAes128Gcm(eicOpsGetHardwareBoundKey(testCredential), encryptedCredentialKeys,
+ 80,
+ // DocType is the additionalAuthenticatedData
+ (const uint8_t*)docType, eicStrLen(docType), credentialKeys)) {
+ eicDebug("Error decrypting CredentialKeys");
+ return false;
+ }
+
+ // It's supposed to look like this;
+ //
+ // CredentialKeys = [
+ // bstr, ; storageKey, a 128-bit AES key
+ // bstr ; credentialPrivKey, the private key for credentialKey
+ // ]
+ //
+ // where storageKey is 16 bytes and credentialPrivateKey is 32 bytes.
+ //
+ // So the first two bytes will be 0x82 0x50 indicating resp. an array of two elements
+ // and a bstr of 16 elements. Sixteen bytes later (offset 18 and 19) there will be
+ // a bstr of 32 bytes. It's encoded as two bytes 0x58 and 0x20.
+ //
+ if (credentialKeys[0] != 0x82 || credentialKeys[1] != 0x50 || credentialKeys[18] != 0x58 ||
+ credentialKeys[19] != 0x20) {
+ 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;
+ 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];
+
+ 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, 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,
+ 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, 3);
+ eicCborAppendString(&cbor, "ProofOfDeletion");
+ eicCborAppendString(&cbor, docType);
+ 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;
+}
diff --git a/identity/aidl/default/libeic/EicPresentation.h b/identity/aidl/default/libeic/EicPresentation.h
new file mode 100644
index 0000000..d798962
--- /dev/null
+++ b/identity/aidl/default/libeic/EicPresentation.h
@@ -0,0 +1,229 @@
+/*
+ * 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 {
+ 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];
+
+ size_t expectedCborSizeAtEnd;
+ EicCbor cbor;
+} EicPresentation;
+
+bool eicPresentationInit(EicPresentation* ctx, bool testCredential, const char* docType,
+ const uint8_t encryptedCredentialKeys[80]);
+
+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,
+ size_t proofOfDeletionCborSize,
+ 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..f16605c
--- /dev/null
+++ b/identity/aidl/default/libeic/EicProvisioning.c
@@ -0,0 +1,290 @@
+/*
+ * 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 eicProvisioningCreateCredentialKey(EicProvisioning* ctx, const uint8_t* challenge,
+ size_t challengeSize, const uint8_t* applicationId,
+ size_t applicationIdSize, uint8_t* publicKeyCert,
+ size_t* publicKeyCertSize) {
+ 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;
+
+ 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[80]) {
+ EicCbor cbor;
+ uint8_t cborBuf[52];
+
+ eicCborInit(&cbor, cborBuf, sizeof(cborBuf));
+ eicCborAppendArray(&cbor, 2);
+ eicCborAppendByteString(&cbor, ctx->storageKey, EIC_AES_128_KEY_SIZE);
+ eicCborAppendByteString(&cbor, ctx->credentialPrivateKey, EIC_P256_PRIV_KEY_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;
+ }
+
+ return true;
+}
diff --git a/identity/aidl/default/libeic/EicProvisioning.h b/identity/aidl/default/libeic/EicProvisioning.h
new file mode 100644
index 0000000..836d16e
--- /dev/null
+++ b/identity/aidl/default/libeic/EicProvisioning.h
@@ -0,0 +1,123 @@
+/*
+ * 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.
+ 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;
+
+ 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];
+
+ EicCbor cbor;
+
+ bool testCredential;
+} EicProvisioning;
+
+bool eicProvisioningInit(EicProvisioning* ctx, bool testCredential);
+
+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
+// ]
+//
+// Since |storageKey| is 16 bytes and |credentialPrivKey| is 32 bytes, the
+// encoded CBOR for CredentialKeys is 52 bytes and consequently
+// |encryptedCredentialKeys| will be 52 + 28 = 80 bytes.
+//
+bool eicProvisioningFinishGetCredentialData(EicProvisioning* ctx, const char* docType,
+ uint8_t encryptedCredentialKeys[80]);
+
+#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/VtsIWritableIdentityCredentialTests.cpp b/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp
index 56e17ba..1629a0c 100644
--- a/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp
+++ b/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp
@@ -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/src/IdentityCredentialSupport.cpp b/identity/support/src/IdentityCredentialSupport.cpp
index 77b795b..093120d 100644
--- a/identity/support/src/IdentityCredentialSupport.cpp
+++ b/identity/support/src/IdentityCredentialSupport.cpp
@@ -935,18 +935,19 @@
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) {
- const keymaster_cert_chain_t* attestation_chain =
- ::keymaster::getAttestationChain(KM_ALGORITHM_EC, nullptr);
- if (attestation_chain == nullptr) {
- LOG(ERROR) << "Error getting attestation chain";
+ keymaster_error_t error;
+ ::keymaster::CertificateChain attestation_chain =
+ ::keymaster::getAttestationChain(KM_ALGORITHM_EC, &error);
+ if (KM_ERROR_OK != error) {
+ LOG(ERROR) << "Error getting attestation chain " << error;
return {};
}
if (expireTimeMilliSeconds == 0) {
- if (attestation_chain->entry_count < 1) {
+ if (attestation_chain.entry_count < 1) {
LOG(ERROR) << "Expected at least one entry in attestation chain";
return {};
}
- keymaster_blob_t* bcBlob = &(attestation_chain->entries[0]);
+ keymaster_blob_t* bcBlob = &(attestation_chain.entries[0]);
const uint8_t* bcData = bcBlob->data;
auto bc = X509_Ptr(d2i_X509(nullptr, &bcData, bcBlob->data_length));
time_t bcNotAfter;
@@ -1015,34 +1016,30 @@
}
::keymaster::AuthorizationSet hwEnforced(hwEnforcedBuilder);
- keymaster_error_t error;
- ::keymaster::CertChainPtr cert_chain_out;
-
// 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);
- error = generate_attestation_from_EVP(key, swEnforced, hwEnforced, auth_set, context,
- *attestation_chain, *attestation_signing_key,
- &cert_chain_out);
+ ::keymaster::CertificateChain cert_chain_out = generate_attestation_from_EVP(
+ key, swEnforced, hwEnforced, auth_set, context, move(attestation_chain),
+ *attestation_signing_key, &error);
- if (KM_ERROR_OK != error || !cert_chain_out) {
+ if (KM_ERROR_OK != error) {
LOG(ERROR) << "Error generate attestation from EVP key" << error;
return {};
}
- // translate certificate format from keymaster_cert_chain_t to vector<uint8_t>.
+ // translate certificate format from keymaster_cert_chain_t to vector<vector<uint8_t>>.
vector<vector<uint8_t>> attestationCertificate;
- for (int i = 0; i < cert_chain_out->entry_count; i++) {
+ for (int i = 0; i < cert_chain_out.entry_count; i++) {
attestationCertificate.insert(
attestationCertificate.end(),
vector<uint8_t>(
- cert_chain_out->entries[i].data,
- cert_chain_out->entries[i].data + cert_chain_out->entries[i].data_length));
+ cert_chain_out.entries[i].data,
+ cert_chain_out.entries[i].data + cert_chain_out.entries[i].data_length));
}
return attestationCertificate;
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/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..f2cbe93
--- /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::Memory& 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/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/src/Burst.cpp b/neuralnetworks/1.0/utils/src/Burst.cpp
new file mode 100644
index 0000000..384bd9b
--- /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::Memory& /*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/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.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/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.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/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/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/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/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/utils/common/Android.bp b/neuralnetworks/utils/common/Android.bp
index 21562cf..6c491ae 100644
--- a/neuralnetworks/utils/common/Android.bp
+++ b/neuralnetworks/utils/common/Android.bp
@@ -28,3 +28,28 @@
"libhidlbase",
],
}
+
+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/InvalidBurst.h b/neuralnetworks/utils/common/include/nnapi/hal/InvalidBurst.h
new file mode 100644
index 0000000..83e60b6
--- /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::Memory& 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..d2c2469 100644
--- a/neuralnetworks/utils/common/include/nnapi/hal/ResilientBuffer.h
+++ b/neuralnetworks/utils/common/include/nnapi/hal/ResilientBuffer.h
@@ -42,7 +42,7 @@
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;
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..0df287f
--- /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::Memory& 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/InvalidBurst.cpp b/neuralnetworks/utils/common/src/InvalidBurst.cpp
new file mode 100644
index 0000000..4ca6603
--- /dev/null
+++ b/neuralnetworks/utils/common/src/InvalidBurst.cpp
@@ -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.
+ */
+
+#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::Memory& /*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..47abbe2 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;
}
@@ -64,12 +100,16 @@
}
nn::GeneralResult<void> ResilientBuffer::copyTo(const nn::Memory& dst) const {
- return getBuffer()->copyTo(dst);
+ 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,
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..0d3cb33
--- /dev/null
+++ b/neuralnetworks/utils/common/src/ResilientBurst.cpp
@@ -0,0 +1,109 @@
+/*
+ * 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::Memory& 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..c5405fb
--- /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 Memory& dst), (const, override));
+ MOCK_METHOD(GeneralResult<void>, copyFrom, (const Memory& 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..deb9b7c
--- /dev/null
+++ b/neuralnetworks/utils/common/test/ResilientBufferTest.cpp
@@ -0,0 +1,266 @@
+/*
+ * 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/ResilientBuffer.h>
+#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 auto result = buffer->copyTo({});
+
+ // 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 auto result = buffer->copyTo({});
+
+ // 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 auto result = buffer->copyTo({});
+
+ // 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 auto result = buffer->copyTo({});
+
+ // 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 auto result = buffer->copyFrom({}, {});
+
+ // 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 auto result = buffer->copyFrom({}, {});
+
+ // 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 auto result = buffer->copyFrom({}, {});
+
+ // 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 auto result = buffer->copyFrom({}, {});
+
+ // 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..b9872d7
--- /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-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..a13dbe2
--- /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-ndk_platform"],
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
+}
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/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..0a7cff7
--- /dev/null
+++ b/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats/EnergyConsumerAttribution.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// 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.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
index 6289a08..815316e 100644
--- 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
@@ -21,4 +21,5 @@
android.hardware.power.stats.EnergyConsumerId energyConsumerId;
long timestampMs;
long energyUWs;
+ android.hardware.power.stats.EnergyConsumerAttribution[] attribution;
}
diff --git a/power/stats/aidl/android/hardware/power/stats/EnergyConsumerAttribution.aidl b/power/stats/aidl/android/hardware/power/stats/EnergyConsumerAttribution.aidl
new file mode 100644
index 0000000..e07204a
--- /dev/null
+++ b/power/stats/aidl/android/hardware/power/stats/EnergyConsumerAttribution.aidl
@@ -0,0 +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.
+ */
+
+package android.hardware.power.stats;
+
+@VintfStability
+parcelable EnergyConsumerAttribution {
+ /**
+ * Android ID / Linux UID, the accumulated energy should be 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
index d2b902a..e8d3dd9 100644
--- a/power/stats/aidl/android/hardware/power/stats/EnergyConsumerResult.aidl
+++ b/power/stats/aidl/android/hardware/power/stats/EnergyConsumerResult.aidl
@@ -17,6 +17,7 @@
package android.hardware.power.stats;
import android.hardware.power.stats.EnergyConsumerId;
+import android.hardware.power.stats.EnergyConsumerAttribution;
@VintfStability
parcelable EnergyConsumerResult {
@@ -32,5 +33,9 @@
* Accumulated energy since boot in microwatt-seconds (uWs)
*/
long energyUWs;
+ /**
+ * Optional attribution per UID for this EnergyConsumer.
+ */
+ EnergyConsumerAttribution[] attribution;
}
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.6/types.hal b/radio/1.6/types.hal
index 3563e14..d475ed3 100644
--- a/radio/1.6/types.hal
+++ b/radio/1.6/types.hal
@@ -285,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;
@@ -351,7 +351,7 @@
/**
* 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
*/
@@ -415,7 +415,8 @@
* This bandwidth estimate shall be the estimated maximum sustainable link bandwidth
* (as would be measured at the Upper PDCP or SNDCP SAP). This is valid only
* in if device is connected to both primary and secodary in dual connected
- * mode. This must be filled with -1 if secondary is not connected.
+ * mode. This must be filled with -1 if secondary is not connected or if
+ * modem does not support this feature.
*/
uint32_t secondaryDownlinkCapacityKbps;
@@ -424,7 +425,8 @@
* This bandwidth estimate shall be the estimated
* maximum sustainable link bandwidth (as would be measured at the Upper PDCP or SNDCP SAP).
* This is valid only in if device is connected to both primary and secodary in dual connected
- * mode.This must be filled with -1 if secondary is not connected.
+ * mode.This must be filled with -1 if secondary is not connected or if modem
+ * does not support this feature.
*/
uint32_t secondaryUplinkCapacityKbps;
};
diff --git a/radio/config/1.3/types.hal b/radio/config/1.3/types.hal
index bedb709..ba964bf 100644
--- a/radio/config/1.3/types.hal
+++ b/radio/config/1.3/types.hal
@@ -19,4 +19,10 @@
/**
* Contains the device capabilities with respect to the Radio HAL.
*/
-struct HalDeviceCapabilities {};
+struct HalDeviceCapabilities {
+ /**
+ * True indicates that the modem is missing features within the current
+ * version of the Radio HAL.
+ */
+ bool modemReducedFeatureSet1;
+};
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..a6c3e65 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
@@ -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/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..84395af 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
@@ -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/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..e914823 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
@@ -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/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..cef8eca 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
@@ -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/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..2277831 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
@@ -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/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..2e583ce 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
@@ -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/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..b372822 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
@@ -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/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..aa8c071 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
@@ -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
@@ -94,6 +95,8 @@
ATTESTATION_IDS_NOT_PROVISIONED = -75,
INVALID_OPERATION = -76,
STORAGE_KEY_UNSUPPORTED = -77,
+ INCOMPATIBLE_MGF_DIGEST = -78,
+ UNSUPPORTED_MGF_DIGEST = -79,
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..0d43d8d 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
@@ -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/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..9ab00c1 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
@@ -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/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..07c2844 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
@@ -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
@@ -21,9 +22,9 @@
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);
+ android.hardware.security.keymint.KeyCreationResult generateKey(in android.hardware.security.keymint.KeyParameter[] keyParams);
+ android.hardware.security.keymint.KeyCreationResult importKey(in android.hardware.security.keymint.KeyParameter[] keyParams, in android.hardware.security.keymint.KeyFormat keyFormat, in byte[] keyData);
+ 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[] inKeyBlobToUpgrade, in android.hardware.security.keymint.KeyParameter[] inUpgradeParams);
void deleteKey(in byte[] inKeyBlob);
void deleteAllKeys();
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..08aa00a 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
@@ -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/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..49ea8af 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
@@ -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,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..4b9ac79
--- /dev/null
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyCreationResult.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.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..4eb5a78 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
@@ -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/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..0390ec9 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
@@ -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/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..e84cf74 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
@@ -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/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..6829a2b 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
@@ -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/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..882ca89 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
@@ -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/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..6c11a92 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
@@ -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/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..ff8d85a 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
@@ -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/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..6c61312 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
@@ -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/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..c4812ed 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
@@ -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/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..ce12fed 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
@@ -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
@@ -30,6 +31,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,
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..41c8832 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
@@ -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/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
index 4d5b659..963e66e 100644
--- 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
@@ -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/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
index 5c76816..7dc556c 100644
--- 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
@@ -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/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..b20601d 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/ErrorCode.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/ErrorCode.aidl
@@ -99,6 +99,8 @@
ATTESTATION_IDS_NOT_PROVISIONED = -75,
INVALID_OPERATION = -76,
STORAGE_KEY_UNSUPPORTED = -77,
+ INCOMPATIBLE_MGF_DIGEST = -78,
+ UNSUPPORTED_MGF_DIGEST = -79,
UNIMPLEMENTED = -100,
VERSION_MISMATCH = -101,
diff --git a/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl b/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl
index 4944acb..820e135 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl
@@ -18,10 +18,9 @@
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;
@@ -126,16 +125,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 +153,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.
@@ -337,38 +343,9 @@
* 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.
- *
- * @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);
/**
* Imports key material into an IKeyMintDevice. Key definition parameters and return values
@@ -396,29 +373,10 @@
*
* @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.
- *
- * @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);
/**
* Securely imports a key, or key pair, returning a key blob and a description of the imported
@@ -474,45 +432,38 @@
* 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
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..b149ac9
--- /dev/null
+++ b/security/keymint/aidl/android/hardware/security/keymint/KeyCreationResult.aidl
@@ -0,0 +1,62 @@
+/*
+ * 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 IKeyMintDevice cannot fully enforce the semantics of the tag, it should be omitted.
+ * - 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.
+ */
+ 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/Tag.aidl b/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl
index 3bc3f16..f92bf00 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl
@@ -187,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.
*
@@ -496,10 +512,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
diff --git a/security/keymint/aidl/vts/functional/AndroidTest.xml b/security/keymint/aidl/vts/functional/AndroidTest.xml
index 43e7a8a..de543f1 100644
--- a/security/keymint/aidl/vts/functional/AndroidTest.xml
+++ b/security/keymint/aidl/vts/functional/AndroidTest.xml
@@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<configuration description="Runs VtsAidlKeyMintV1_0TargetTest.">
+<configuration description="Runs VtsAidlKeyMintTargetTest.">
<option name="test-suite-tag" value="apct" />
<option name="test-suite-tag" value="apct-native" />
@@ -22,12 +22,13 @@
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
<option name="cleanup" value="true" />
- <option name="push" value="VtsAidlKeyMintV1_0TargetTest->/data/local/tmp/VtsAidlKeyMintV1_0TargetTest" />
+ <option name="push"
+ value="VtsAidlKeyMintTargetTest->/data/local/tmp/VtsAidlKeyMintTargetTest" />
</target_preparer>
<test class="com.android.tradefed.testtype.GTest" >
<option name="native-test-device-path" value="/data/local/tmp" />
- <option name="module-name" value="VtsAidlKeyMintV1_0TargetTest" />
+ <option name="module-name" value="VtsAidlKeyMintTargetTest" />
<option name="native-test-timeout" value="900000"/>
</test>
</configuration>
diff --git a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp
index 94bc199..93a216f 100644
--- a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp
+++ b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp
@@ -17,6 +17,7 @@
#include "KeyMintAidlTestBase.h"
#include <chrono>
+#include <unordered_set>
#include <vector>
#include <android-base/logging.h>
@@ -43,6 +44,34 @@
namespace test {
+namespace {
+
+// 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;
+
+ 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;
+}
+
+} // namespace
+
ErrorCode KeyMintAidlTestBase::GetReturnErrorCode(const Status& result) {
if (result.isOk()) return ErrorCode::OK;
@@ -78,35 +107,30 @@
}
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)
+ vector<uint8_t>* key_blob,
+ vector<KeyCharacteristics>* key_characteristics) {
+ 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();
+ key_blob->clear();
+ key_characteristics->clear();
+ cert_chain_.clear();
- Status result;
- ByteArray blob;
+ KeyCreationResult creationResult;
+ Status result = keymint_->generateKey(key_desc.vector_data(), &creationResult);
- result = keymint_->generateKey(key_desc.vector_data(), &blob, keyChar, &certChain_);
-
- // On result, blob & characteristics should be empty.
if (result.isOk()) {
- if (SecLevel() != SecurityLevel::SOFTWARE) {
- EXPECT_GT(keyChar->hardwareEnforced.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;
+ 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);
}
return GetReturnErrorCode(result);
@@ -118,25 +142,26 @@
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()),
+ &creationResult);
if (result.isOk()) {
- if (SecLevel() != SecurityLevel::SOFTWARE) {
- EXPECT_GT(key_characteristics->hardwareEnforced.size(), 0);
- }
- EXPECT_GT(key_characteristics->softwareEnforced.size(), 0);
- *key_blob = blob.data;
+ 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);
}
return GetReturnErrorCode(result);
@@ -151,25 +176,25 @@
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_GT(key_characteristics_.softwareEnforced.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);
}
return GetReturnErrorCode(result);
@@ -754,6 +779,15 @@
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;
+}
+
} // 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..f36c397 100644
--- a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h
+++ b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h
@@ -56,13 +56,13 @@
ErrorCode GetReturnErrorCode(const Status& result);
ErrorCode GenerateKey(const AuthorizationSet& key_desc, vector<uint8_t>* key_blob,
- KeyCharacteristics* key_characteristics);
+ vector<KeyCharacteristics>* key_characteristics);
ErrorCode GenerateKey(const AuthorizationSet& key_desc);
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);
@@ -147,8 +147,8 @@
std::pair<ErrorCode, vector<uint8_t>> UpgradeKey(const vector<uint8_t>& key_blob);
- bool IsSecure() { return securityLevel_ != SecurityLevel::SOFTWARE; }
- SecurityLevel SecLevel() { return securityLevel_; }
+ 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 +164,15 @@
}
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_);
+ }
private:
std::shared_ptr<IKeyMintDevice> keymint_;
diff --git a/security/keymint/aidl/vts/functional/KeyMintTest.cpp b/security/keymint/aidl/vts/functional/KeyMintTest.cpp
index 3060153..bd36b8e 100644
--- a/security/keymint/aidl/vts/functional/KeyMintTest.cpp
+++ b/security/keymint/aidl/vts/functional/KeyMintTest.cpp
@@ -56,18 +56,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;
}
};
@@ -229,19 +227,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 +248,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,7 +267,7 @@
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)
@@ -280,12 +277,7 @@
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))
@@ -304,7 +296,7 @@
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)
@@ -337,7 +329,7 @@
TEST_P(NewKeyGenerationTest, Ecdsa) {
for (auto key_size : ValidKeySizes(Algorithm::EC)) {
vector<uint8_t> key_blob;
- KeyCharacteristics key_characteristics;
+ vector<KeyCharacteristics> key_characteristics;
ASSERT_EQ(ErrorCode::OK,
GenerateKey(
AuthorizationSetBuilder().EcdsaSigningKey(key_size).Digest(Digest::NONE),
@@ -345,12 +337,7 @@
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))
@@ -383,7 +370,7 @@
TEST_P(NewKeyGenerationTest, EcdsaInvalidSize) {
for (auto key_size : InvalidKeySizes(Algorithm::EC)) {
vector<uint8_t> key_blob;
- KeyCharacteristics key_characteristics;
+ vector<KeyCharacteristics> key_characteristics;
ASSERT_EQ(ErrorCode::UNSUPPORTED_KEY_SIZE,
GenerateKey(
AuthorizationSetBuilder().EcdsaSigningKey(key_size).Digest(Digest::NONE),
@@ -454,7 +441,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 +452,10 @@
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);
}
@@ -600,7 +580,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()
@@ -1412,7 +1392,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 +1446,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);
}
};
@@ -2056,6 +2030,107 @@
}
/*
+ * 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)));
+
+ 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)));
+ 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)));
+ 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.
@@ -3849,7 +3924,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, DeleteKey(true /* keep key blob */));
@@ -3882,8 +3957,8 @@
// 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 +3993,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());
diff --git a/security/keymint/support/authorization_set.cpp b/security/keymint/support/authorization_set.cpp
index f98851c..3d44dff 100644
--- a/security/keymint/support/authorization_set.cpp
+++ b/security/keymint/support/authorization_set.cpp
@@ -227,6 +227,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) {
diff --git a/security/keymint/support/include/keymint_support/authorization_set.h b/security/keymint/support/include/keymint_support/authorization_set.h
index 4ff1705..596bb89 100644
--- a/security/keymint/support/include/keymint_support/authorization_set.h
+++ b/security/keymint/support/include/keymint_support/authorization_set.h
@@ -290,6 +290,7 @@
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);
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..76aecb7 100644
--- a/security/keymint/support/include/keymint_support/keymint_tags.h
+++ b/security/keymint/support/include/keymint_support/keymint_tags.h
@@ -124,6 +124,7 @@
DECLARE_TYPED_TAG(USER_ID);
DECLARE_TYPED_TAG(USER_SECURE_ID);
DECLARE_TYPED_TAG(VENDOR_PATCHLEVEL);
+DECLARE_TYPED_TAG(RSA_OAEP_MGF_DIGEST);
#undef DECLARE_TYPED_TAG
@@ -239,6 +240,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
diff --git a/security/secureclock/aidl/Android.bp b/security/secureclock/aidl/Android.bp
new file mode 100644
index 0000000..7d26a9b
--- /dev/null
+++ b/security/secureclock/aidl/Android.bp
@@ -0,0 +1,24 @@
+aidl_interface {
+ name: "android.hardware.security.secureclock",
+ vendor_available: true,
+ srcs: [
+ "android/hardware/security/secureclock/*.aidl",
+ ],
+ stability: "vintf",
+ imports: [
+ "android.hardware.security.keymint",
+ ],
+ backend: {
+ java: {
+ sdk_version: "module_current",
+ },
+ ndk: {
+ vndk: {
+ enabled: true,
+ },
+ },
+ rust: {
+ enabled: true,
+ },
+ },
+}
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..c16b312
--- /dev/null
+++ b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/ISecureClock.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.secureclock;
+@VintfStability
+interface ISecureClock {
+ android.hardware.security.secureclock.TimeStampToken generateTimeStamp(in long challenge);
+ const String TIME_STAMP_MAC_LABEL = "Time 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..c23ddca
--- /dev/null
+++ b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/TimeStampToken.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.secureclock;
+@VintfStability
+parcelable TimeStampToken {
+ long challenge;
+ android.hardware.security.keymint.Timestamp timestamp;
+ android.hardware.security.keymint.SecurityLevel securityLevel;
+ byte[] mac;
+}
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..7d416dd
--- /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 = "Time 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..76a2d28
--- /dev/null
+++ b/security/secureclock/aidl/android/hardware/security/secureclock/TimeStampToken.aidl
@@ -0,0 +1,61 @@
+/*
+ * 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.keymint.SecurityLevel;
+import android.hardware.security.keymint.Timestamp;
+
+/**
+ * TimeStampToken instances are used for secure environments that requires secure time information.
+ */
+
+@VintfStability
+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;
+
+ /**
+ * SecurityLevel of the secure environment that generated the token.
+ */
+ SecurityLevel securityLevel;
+
+ /**
+ * 32-byte HMAC-SHA256 of the above values, computed as:
+ *
+ * HMAC(H,
+ * ISecureClock.TIME_STAMP_MAC_LABEL || challenge || timestamp)
+ *
+ * where:
+ *
+ * ``ISecureClock.TIME_STAMP_MAC_LABEL'' is a sting constant defined in ISecureClock.aidl.
+ *
+ * ``H'' is the shared HMAC key (see computeSharedHmac() in ISharedHmacSecret).
+ *
+ * ``||'' 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/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/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/tests/lazy/1.1/ILazy.hal b/tests/lazy/1.1/ILazy.hal
index a15e0e3..b0a6a2a 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
+ * it when the number of active (i.e. with clients) services changes.
+ * For testing purposes, this callback exercises the code to unregister/re-register
+ * the services and eventually shuts down the process.
+ */
+ setCustomActiveServicesCountCallback();
+};
diff --git a/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/CompositePrimitive.aidl b/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/CompositePrimitive.aidl
index 6ab7ac5..3071dce3 100644
--- a/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/CompositePrimitive.aidl
+++ b/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/CompositePrimitive.aidl
@@ -26,4 +26,5 @@
SLOW_RISE = 5,
QUICK_FALL = 6,
LIGHT_TICK = 7,
+ LOW_TICK = 8,
}
diff --git a/vibrator/aidl/android/hardware/vibrator/CompositePrimitive.aidl b/vibrator/aidl/android/hardware/vibrator/CompositePrimitive.aidl
index 8e82db0..5314898 100644
--- a/vibrator/aidl/android/hardware/vibrator/CompositePrimitive.aidl
+++ b/vibrator/aidl/android/hardware/vibrator/CompositePrimitive.aidl
@@ -70,4 +70,11 @@
* Support is required.
*/
LIGHT_TICK,
+ /**
+ * This very short low frequency effect should produce a light crisp sensation intended
+ * to be used repetitively for dynamic feedback.
+ *
+ * Support is required.
+ */
+ LOW_TICK,
}
diff --git a/vibrator/aidl/default/Vibrator.cpp b/vibrator/aidl/default/Vibrator.cpp
index c446afd..1021e62 100644
--- a/vibrator/aidl/default/Vibrator.cpp
+++ b/vibrator/aidl/default/Vibrator.cpp
@@ -119,6 +119,7 @@
CompositePrimitive::THUD, CompositePrimitive::SPIN,
CompositePrimitive::QUICK_RISE, CompositePrimitive::SLOW_RISE,
CompositePrimitive::QUICK_FALL, CompositePrimitive::LIGHT_TICK,
+ CompositePrimitive::LOW_TICK,
};
return ndk::ScopedAStatus::ok();
}
diff --git a/vibrator/aidl/default/vibrator-default.xml b/vibrator/aidl/default/vibrator-default.xml
index 9f9cd40..b5bd3dd 100644
--- a/vibrator/aidl/default/vibrator-default.xml
+++ b/vibrator/aidl/default/vibrator-default.xml
@@ -1,10 +1,12 @@
<manifest version="1.0" type="device">
<hal format="aidl">
<name>android.hardware.vibrator</name>
+ <version>2</version>
<fqname>IVibrator/default</fqname>
</hal>
<hal format="aidl">
<name>android.hardware.vibrator</name>
+ <version>2</version>
<fqname>IVibratorManager/default</fqname>
</hal>
</manifest>
diff --git a/wifi/1.5/IWifiChip.hal b/wifi/1.5/IWifiChip.hal
index 80f2ca4..b2960cf 100644
--- a/wifi/1.5/IWifiChip.hal
+++ b/wifi/1.5/IWifiChip.hal
@@ -216,4 +216,22 @@
setCoexUnsafeChannels(
vec<CoexUnsafeChannel> unsafeChannels, bitfield<CoexRestriction> restrictions)
generates (WifiStatus status);
+
+ /**
+ * Set country code for this Wifi chip.
+ *
+ * Country code is global setting across the Wifi chip and not Wifi
+ * interface (STA or AP) specific. Legacy HAL API's for country code in
+ * @1.0::ISupplicantStaIface::setCountryCode &
+ * @1.0::IWifiApIface:setCountryCode are deprecated in favor of this
+ * chip level API.
+ *
+ * @param code 2 byte country code (as defined in ISO 3166) to set.
+ * @return status Status of the operation.
+ * Possible status codes:
+ * |WifiStatusCode.SUCCESS|,
+ * |WifiStatusCode.FAILURE_UNKNOWN|,
+ * |WifiStatusCode.FAILURE_IFACE_INVALID|
+ */
+ setCountryCode(int8_t[2] code) generates (WifiStatus status);
};
diff --git a/wifi/1.5/default/wifi_chip.cpp b/wifi/1.5/default/wifi_chip.cpp
index 6dd400c..fbb4a52 100644
--- a/wifi/1.5/default/wifi_chip.cpp
+++ b/wifi/1.5/default/wifi_chip.cpp
@@ -731,6 +731,13 @@
hidl_status_cb, unsafeChannels, restrictions);
}
+Return<void> WifiChip::setCountryCode(const hidl_array<int8_t, 2>& code,
+ setCountryCode_cb hidl_status_cb) {
+ return validateAndCall(this, WifiStatusCode::ERROR_WIFI_IFACE_INVALID,
+ &WifiChip::setCountryCodeInternal, hidl_status_cb,
+ code);
+}
+
void WifiChip::invalidateAndRemoveAllIfaces() {
invalidateAndClearBridgedApAll();
invalidateAndClearAll(ap_ifaces_);
@@ -1031,7 +1038,7 @@
const std::string& ifname, const std::string& ifInstanceName) {
legacy_hal::wifi_error legacy_status;
const auto iface = findUsingName(ap_ifaces_, ifname);
- if (!iface.get() || !ifInstanceName.empty()) {
+ if (!iface.get() || ifInstanceName.empty()) {
return createWifiStatus(WifiStatusCode::ERROR_INVALID_ARGS);
}
// Requires to remove one of the instance in bridge mode
@@ -1478,6 +1485,12 @@
return createWifiStatusFromLegacyError(legacy_status);
}
+WifiStatus WifiChip::setCountryCodeInternal(const std::array<int8_t, 2>& code) {
+ auto legacy_status =
+ legacy_hal_.lock()->setCountryCode(getFirstActiveWlanIfaceName(), code);
+ return createWifiStatusFromLegacyError(legacy_status);
+}
+
WifiStatus WifiChip::handleChipConfiguration(
/* NONNULL */ std::unique_lock<std::recursive_mutex>* lock,
ChipModeId mode_id) {
@@ -1795,7 +1808,16 @@
std::string WifiChip::getFirstActiveWlanIfaceName() {
if (sta_ifaces_.size() > 0) return sta_ifaces_[0]->getName();
- if (ap_ifaces_.size() > 0) return ap_ifaces_[0]->getName();
+ if (ap_ifaces_.size() > 0) {
+ // If the first active wlan iface is bridged iface.
+ // Return first instance name.
+ for (auto const& it : br_ifaces_ap_instances_) {
+ if (it.first == ap_ifaces_[0]->getName()) {
+ return it.second[0];
+ }
+ }
+ return ap_ifaces_[0]->getName();
+ }
// This could happen if the chip call is made before any STA/AP
// iface is created. Default to wlan0 for such cases.
LOG(WARNING) << "No active wlan interfaces in use! Using default";
diff --git a/wifi/1.5/default/wifi_chip.h b/wifi/1.5/default/wifi_chip.h
index 95c122d..7d7a9b5 100644
--- a/wifi/1.5/default/wifi_chip.h
+++ b/wifi/1.5/default/wifi_chip.h
@@ -178,6 +178,8 @@
const hidl_vec<CoexUnsafeChannel>& unsafe_channels,
hidl_bitfield<IfaceType> restrictions,
setCoexUnsafeChannels_cb hidl_status_cb) override;
+ Return<void> setCountryCode(const hidl_array<int8_t, 2>& code,
+ setCountryCode_cb _hidl_cb) override;
private:
void invalidateAndRemoveAllIfaces();
@@ -258,7 +260,7 @@
WifiStatus setMultiStaUseCaseInternal(MultiStaUseCase use_case);
WifiStatus setCoexUnsafeChannelsInternal(
std::vector<CoexUnsafeChannel> unsafe_channels, uint32_t restrictions);
-
+ WifiStatus setCountryCodeInternal(const std::array<int8_t, 2>& code);
WifiStatus handleChipConfiguration(
std::unique_lock<std::recursive_mutex>* lock, ChipModeId mode_id);
WifiStatus registerDebugRingBufferCallback();
diff --git a/wifi/1.5/default/wifi_legacy_hal.cpp b/wifi/1.5/default/wifi_legacy_hal.cpp
index e1a5a8c..3e65ee0 100644
--- a/wifi/1.5/default/wifi_legacy_hal.cpp
+++ b/wifi/1.5/default/wifi_legacy_hal.cpp
@@ -1630,6 +1630,12 @@
configId);
}
+wifi_error WifiLegacyHal::setDtimConfig(const std::string& iface_name,
+ uint32_t multiplier) {
+ return global_func_table_.wifi_set_dtim_config(getIfaceHandle(iface_name),
+ multiplier);
+}
+
void WifiLegacyHal::invalidate() {
global_handle_ = nullptr;
iface_name_to_handle_.clear();
diff --git a/wifi/1.5/default/wifi_legacy_hal.h b/wifi/1.5/default/wifi_legacy_hal.h
index 11efe7e..0cc1cff 100644
--- a/wifi/1.5/default/wifi_legacy_hal.h
+++ b/wifi/1.5/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,280 @@
// 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_DUAL_STA_NON_TRANSIENT_UNBIASED;
+using ::WIFI_DUAL_STA_TRANSIENT_PREFER_PRIMARY;
+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_coex_restriction;
+using ::wifi_coex_unsafe_channel;
+using ::wifi_error;
+using ::wifi_gscan_capabilities;
+using ::wifi_hal_fn;
+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_multi_sta_use_case;
+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 {
@@ -425,6 +690,9 @@
wifi_error twtClearStats(const std::string& iface_name, uint8_t configId);
+ wifi_error setDtimConfig(const std::string& iface_name,
+ uint32_t multiplier);
+
private:
// Retrieve interface handles for all the available interfaces.
wifi_error retrieveIfaceHandles();
diff --git a/wifi/1.5/default/wifi_legacy_hal_stubs.cpp b/wifi/1.5/default/wifi_legacy_hal_stubs.cpp
index 4b005d6..7ba5d9b 100644
--- a/wifi/1.5/default/wifi_legacy_hal_stubs.cpp
+++ b/wifi/1.5/default/wifi_legacy_hal_stubs.cpp
@@ -158,6 +158,7 @@
populateStubFor(&hal_fn->wifi_twt_info_frame_request);
populateStubFor(&hal_fn->wifi_twt_get_stats);
populateStubFor(&hal_fn->wifi_twt_clear_stats);
+ populateStubFor(&hal_fn->wifi_set_dtim_config);
return true;
}
} // namespace legacy_hal
diff --git a/wifi/1.5/default/wifi_legacy_hal_stubs.h b/wifi/1.5/default/wifi_legacy_hal_stubs.h
index 7e4eb0a..480389b 100644
--- a/wifi/1.5/default/wifi_legacy_hal_stubs.h
+++ b/wifi/1.5/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_5 {
namespace implementation {
namespace legacy_hal {
-#include <hardware_legacy/wifi_hal.h>
bool initHalFuncTableWithStubs(wifi_hal_fn* hal_fn);
} // namespace legacy_hal
diff --git a/wifi/1.5/vts/functional/Android.bp b/wifi/1.5/vts/functional/Android.bp
index 2d8b412..118822a 100644
--- a/wifi/1.5/vts/functional/Android.bp
+++ b/wifi/1.5/vts/functional/Android.bp
@@ -14,6 +14,27 @@
// limitations under the License.
//
+cc_library_static {
+ name: "VtsHalWifiV1_5TargetTestUtil",
+ defaults: ["VtsHalTargetTestDefaults"],
+ srcs: [
+ "wifi_hidl_test_utils_1_5.cpp",
+ ],
+ export_include_dirs: [
+ ".",
+ ],
+ shared_libs: [
+ "libnativehelper",
+ ],
+ static_libs: [
+ "VtsHalWifiV1_0TargetTestUtil",
+ "android.hardware.wifi@1.0",
+ "android.hardware.wifi@1.3",
+ "android.hardware.wifi@1.5",
+ "libwifi-system-iface",
+ ],
+}
+
cc_test {
name: "VtsHalWifiV1_5TargetTest",
defaults: ["VtsHalTargetTestDefaults"],
@@ -67,9 +88,11 @@
defaults: ["VtsHalTargetTestDefaults"],
srcs: [
"wifi_chip_hidl_ap_test.cpp",
+ "wifi_ap_iface_hidl_test.cpp",
],
static_libs: [
"VtsHalWifiV1_0TargetTestUtil",
+ "VtsHalWifiV1_5TargetTestUtil",
"android.hardware.wifi@1.0",
"android.hardware.wifi@1.1",
"android.hardware.wifi@1.2",
diff --git a/wifi/1.5/vts/functional/wifi_ap_iface_hidl_test.cpp b/wifi/1.5/vts/functional/wifi_ap_iface_hidl_test.cpp
new file mode 100644
index 0000000..e47b14d
--- /dev/null
+++ b/wifi/1.5/vts/functional/wifi_ap_iface_hidl_test.cpp
@@ -0,0 +1,97 @@
+/*
+ * 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 <VtsCoreUtil.h>
+#include <VtsHalHidlTargetCallbackBase.h>
+#include <android-base/logging.h>
+
+#undef NAN // NAN is defined in bionic/libc/include/math.h:38
+
+#include <android/hardware/wifi/1.4/IWifiChipEventCallback.h>
+#include <android/hardware/wifi/1.5/IWifi.h>
+#include <android/hardware/wifi/1.5/IWifiApIface.h>
+#include <android/hardware/wifi/1.5/IWifiChip.h>
+#include <gtest/gtest.h>
+#include <hidl/GtestPrinter.h>
+#include <hidl/ServiceManagement.h>
+
+#include "wifi_hidl_call_util.h"
+#include "wifi_hidl_test_utils.h"
+#include "wifi_hidl_test_utils_1_5.h"
+
+using ::android::sp;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::wifi::V1_0::ChipModeId;
+using ::android::hardware::wifi::V1_0::IfaceType;
+using ::android::hardware::wifi::V1_0::IWifiIface;
+using ::android::hardware::wifi::V1_0::WifiDebugRingBufferStatus;
+using ::android::hardware::wifi::V1_0::WifiStatus;
+using ::android::hardware::wifi::V1_0::WifiStatusCode;
+using ::android::hardware::wifi::V1_4::IWifiChipEventCallback;
+using ::android::hardware::wifi::V1_5::IWifiApIface;
+using ::android::hardware::wifi::V1_5::IWifiChip;
+
+/**
+ * Fixture for IWifiChip tests that are conditioned on SoftAP support.
+ */
+class WifiApIfaceHidlTest : public ::testing::TestWithParam<std::string> {
+ public:
+ virtual void SetUp() override {
+ isBridgedSupport_ = testing::checkSubstringInCommandOutput(
+ "/system/bin/cmd wifi get-softap-supported-features",
+ "wifi_softap_bridged_ap_supported");
+ // Make sure to start with a clean state
+ stopWifi(GetInstanceName());
+ }
+
+ virtual void TearDown() override { stopWifi(GetInstanceName()); }
+
+ protected:
+ bool isBridgedSupport_ = false;
+ std::string GetInstanceName() { return GetParam(); }
+};
+
+/*
+ * resetToFactoryMacAddress
+ */
+TEST_P(WifiApIfaceHidlTest, resetToFactoryMacAddressInBridgedModeTest) {
+ if (!isBridgedSupport_) GTEST_SKIP() << "Missing Bridged AP support";
+ sp<IWifiApIface> wifi_ap_iface =
+ getBridgedWifiApIface_1_5(GetInstanceName());
+ ASSERT_NE(nullptr, wifi_ap_iface.get());
+ const auto& status = HIDL_INVOKE(wifi_ap_iface, resetToFactoryMacAddress);
+ EXPECT_EQ(WifiStatusCode::SUCCESS, status.code);
+}
+
+/*
+ * resetToFactoryMacAddress
+ */
+TEST_P(WifiApIfaceHidlTest, resetToFactoryMacAddressTest) {
+ sp<IWifiApIface> wifi_ap_iface = getWifiApIface_1_5(GetInstanceName());
+ ASSERT_NE(nullptr, wifi_ap_iface.get());
+ const auto& status = HIDL_INVOKE(wifi_ap_iface, resetToFactoryMacAddress);
+ EXPECT_EQ(WifiStatusCode::SUCCESS, status.code);
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(WifiApIfaceHidlTest);
+INSTANTIATE_TEST_SUITE_P(
+ PerInstance, WifiApIfaceHidlTest,
+ testing::ValuesIn(android::hardware::getAllHalInstanceNames(
+ ::android::hardware::wifi::V1_5::IWifi::descriptor)),
+ android::hardware::PrintInstanceNameToString);
diff --git a/wifi/1.5/vts/functional/wifi_chip_hidl_ap_test.cpp b/wifi/1.5/vts/functional/wifi_chip_hidl_ap_test.cpp
index 395d317..922c9a7 100644
--- a/wifi/1.5/vts/functional/wifi_chip_hidl_ap_test.cpp
+++ b/wifi/1.5/vts/functional/wifi_chip_hidl_ap_test.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include <VtsCoreUtil.h>
#include <VtsHalHidlTargetCallbackBase.h>
#include <android-base/logging.h>
@@ -51,6 +52,9 @@
class WifiChipHidlTest : public ::testing::TestWithParam<std::string> {
public:
virtual void SetUp() override {
+ isBridgedSupport_ = testing::checkSubstringInCommandOutput(
+ "/system/bin/cmd wifi get-softap-supported-features",
+ "wifi_softap_bridged_ap_supported");
// Make sure to start with a clean state
stopWifi(GetInstanceName());
@@ -61,6 +65,7 @@
virtual void TearDown() override { stopWifi(GetInstanceName()); }
protected:
+ bool isBridgedSupport_ = false;
// Helper function to configure the Chip in one of the supported modes.
// Most of the non-mode-configuration-related methods require chip
// to be first configured.
@@ -71,19 +76,12 @@
return mode_id;
}
- WifiStatusCode createApIface(sp<IWifiApIface>* ap_iface) {
- configureChipForIfaceType(IfaceType::AP, true);
- const auto& status_and_iface = HIDL_INVOKE(wifi_chip_, createApIface);
- *ap_iface = IWifiApIface::castFrom(status_and_iface.second);
- return status_and_iface.first.code;
- }
-
- WifiStatusCode createBridgedApIface(sp<IWifiApIface>* ap_iface) {
+ void createBridgedApIface(sp<IWifiApIface>* ap_iface) {
configureChipForIfaceType(IfaceType::AP, true);
const auto& status_and_iface =
HIDL_INVOKE(wifi_chip_, createBridgedApIface);
*ap_iface = status_and_iface.second;
- return status_and_iface.first.code;
+ EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_iface.first.code);
}
sp<IWifiChip> wifi_chip_;
@@ -92,19 +90,25 @@
std::string GetInstanceName() { return GetParam(); }
};
-// TODO: b/173999527. Add test for bridged API.
-
-/*
- * resetToFactoryMacAddress
+/**
+ * createBridgedApIface & removeIfaceInstanceFromBridgedApIface
*/
-TEST_P(WifiChipHidlTest, resetToFactoryMacAddressTest) {
+TEST_P(WifiChipHidlTest,
+ createBridgedApIfaceAndremoveIfaceInstanceFromBridgedApIfaceTest) {
+ if (!isBridgedSupport_) GTEST_SKIP() << "Missing Bridged AP support";
sp<IWifiApIface> wifi_ap_iface;
- const auto& status_code = createApIface(&wifi_ap_iface);
- if (status_code != WifiStatusCode::SUCCESS) {
- EXPECT_EQ(WifiStatusCode::ERROR_NOT_SUPPORTED, status_code);
- }
- const auto& status = HIDL_INVOKE(wifi_ap_iface, resetToFactoryMacAddress);
- EXPECT_EQ(WifiStatusCode::SUCCESS, status.code);
+ createBridgedApIface(&wifi_ap_iface);
+ ASSERT_NE(nullptr, wifi_ap_iface.get());
+ const auto& status_and_name = HIDL_INVOKE(wifi_ap_iface, getName);
+ EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_name.first.code);
+ // TODO: b/173999527, add API to get instance name to replace it.
+ std::string br_name = status_and_name.second; // ap_br_ is the pre-fix
+ std::string instance_name =
+ br_name.substr(6, br_name.length()); // remove the pre-fex
+ const auto& status_code =
+ HIDL_INVOKE(wifi_chip_, removeIfaceInstanceFromBridgedApIface, br_name,
+ instance_name);
+ EXPECT_EQ(WifiStatusCode::SUCCESS, status_code.code);
}
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(WifiChipHidlTest);
diff --git a/wifi/1.5/vts/functional/wifi_chip_hidl_test.cpp b/wifi/1.5/vts/functional/wifi_chip_hidl_test.cpp
index a065721..36a8448 100644
--- a/wifi/1.5/vts/functional/wifi_chip_hidl_test.cpp
+++ b/wifi/1.5/vts/functional/wifi_chip_hidl_test.cpp
@@ -173,6 +173,20 @@
}
}
+/*
+ * SetCountryCode:
+ * Ensures that a call to set the country code will return with a success
+ * status code.
+ */
+TEST_P(WifiChipHidlTest, setCountryCode) {
+ const android::hardware::hidl_array<int8_t, 2> kCountryCode{
+ std::array<int8_t, 2>{{0x55, 0x53}}};
+
+ configureChipForIfaceType(IfaceType::STA, true);
+ EXPECT_EQ(WifiStatusCode::SUCCESS,
+ HIDL_INVOKE(wifi_chip_, setCountryCode, kCountryCode).code);
+}
+
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(WifiChipHidlTest);
INSTANTIATE_TEST_SUITE_P(
PerInstance, WifiChipHidlTest,
diff --git a/wifi/1.5/vts/functional/wifi_hidl_test_utils_1_5.cpp b/wifi/1.5/vts/functional/wifi_hidl_test_utils_1_5.cpp
new file mode 100644
index 0000000..f1da2ea
--- /dev/null
+++ b/wifi/1.5/vts/functional/wifi_hidl_test_utils_1_5.cpp
@@ -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.
+ */
+
+#include <VtsHalHidlTargetCallbackBase.h>
+#include <android-base/logging.h>
+
+#undef NAN // NAN is defined in bionic/libc/include/math.h:38
+
+#include <android/hardware/wifi/1.5/IWifi.h>
+#include <android/hardware/wifi/1.5/IWifiApIface.h>
+#include <android/hardware/wifi/1.5/IWifiChip.h>
+#include <gtest/gtest.h>
+#include <hidl/GtestPrinter.h>
+#include <hidl/ServiceManagement.h>
+
+#include "wifi_hidl_call_util.h"
+#include "wifi_hidl_test_utils.h"
+
+using ::android::sp;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::wifi::V1_0::ChipModeId;
+using ::android::hardware::wifi::V1_0::IfaceType;
+using ::android::hardware::wifi::V1_5::IWifiApIface;
+using ::android::hardware::wifi::V1_5::IWifiChip;
+
+sp<IWifiChip> getWifiChip_1_5(const std::string& instance_name) {
+ return IWifiChip::castFrom(getWifiChip(instance_name));
+}
+
+sp<IWifiApIface> getWifiApIface_1_5(const std::string& instance_name) {
+ ChipModeId mode_id;
+ sp<IWifiChip> wifi_chip_ = getWifiChip_1_5(instance_name);
+ configureChipToSupportIfaceType(wifi_chip_, IfaceType::AP, &mode_id);
+ const auto& status_and_iface = HIDL_INVOKE(wifi_chip_, createApIface);
+ return IWifiApIface::castFrom(status_and_iface.second);
+}
+
+sp<IWifiApIface> getBridgedWifiApIface_1_5(const std::string& instance_name) {
+ ChipModeId mode_id;
+ sp<IWifiChip> wifi_chip_ = getWifiChip_1_5(instance_name);
+ configureChipToSupportIfaceType(wifi_chip_, IfaceType::AP, &mode_id);
+ const auto& status_and_iface =
+ HIDL_INVOKE(wifi_chip_, createBridgedApIface);
+ return IWifiApIface::castFrom(status_and_iface.second);
+}
diff --git a/wifi/1.5/vts/functional/wifi_hidl_test_utils_1_5.h b/wifi/1.5/vts/functional/wifi_hidl_test_utils_1_5.h
new file mode 100644
index 0000000..1b8b737
--- /dev/null
+++ b/wifi/1.5/vts/functional/wifi_hidl_test_utils_1_5.h
@@ -0,0 +1,35 @@
+/*
+ * 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/wifi/1.5/IWifi.h>
+#include <android/hardware/wifi/1.5/IWifiApIface.h>
+#include <android/hardware/wifi/1.5/IWifiChip.h>
+
+#include <getopt.h>
+
+#include <VtsHalHidlTargetTestEnvBase.h>
+// Helper functions to obtain references to the various HIDL interface objects.
+// Note: We only have a single instance of each of these objects currently.
+// These helper functions should be modified to return vectors if we support
+// multiple instances.
+android::sp<android::hardware::wifi::V1_5::IWifiChip> getWifiChip_1_5(
+ const std::string& instance_name);
+android::sp<android::hardware::wifi::V1_5::IWifiApIface> getWifiApIface_1_5(
+ const std::string& instance_name);
+android::sp<android::hardware::wifi::V1_5::IWifiApIface>
+getBridgedWifiApIface_1_5(const std::string& instance_name);
diff --git a/wifi/hostapd/1.3/vts/functional/Android.bp b/wifi/hostapd/1.3/vts/functional/Android.bp
index 07cebb0..ed18bb6 100644
--- a/wifi/hostapd/1.3/vts/functional/Android.bp
+++ b/wifi/hostapd/1.3/vts/functional/Android.bp
@@ -22,12 +22,18 @@
],
static_libs: [
"VtsHalWifiV1_0TargetTestUtil",
+ "VtsHalWifiV1_5TargetTestUtil",
"VtsHalWifiHostapdV1_0TargetTestUtil",
"android.hardware.wifi.hostapd@1.0",
"android.hardware.wifi.hostapd@1.1",
"android.hardware.wifi.hostapd@1.2",
"android.hardware.wifi.hostapd@1.3",
"android.hardware.wifi@1.0",
+ "android.hardware.wifi@1.1",
+ "android.hardware.wifi@1.2",
+ "android.hardware.wifi@1.3",
+ "android.hardware.wifi@1.4",
+ "android.hardware.wifi@1.5",
"libgmock",
"libwifi-system",
"libwifi-system-iface",
diff --git a/wifi/hostapd/1.3/vts/functional/hostapd_hidl_test.cpp b/wifi/hostapd/1.3/vts/functional/hostapd_hidl_test.cpp
index a22252c..4e63c56 100644
--- a/wifi/hostapd/1.3/vts/functional/hostapd_hidl_test.cpp
+++ b/wifi/hostapd/1.3/vts/functional/hostapd_hidl_test.cpp
@@ -28,6 +28,7 @@
#include "hostapd_hidl_call_util.h"
#include "hostapd_hidl_test_utils.h"
+#include "wifi_hidl_test_utils_1_5.h"
using ::android::sp;
using ::android::hardware::hidl_string;
@@ -38,6 +39,8 @@
using ::android::hardware::wifi::hostapd::V1_2::Ieee80211ReasonCode;
using ::android::hardware::wifi::hostapd::V1_3::IHostapd;
using ::android::hardware::wifi::V1_0::IWifi;
+using ::android::hardware::wifi::V1_0::WifiStatusCode;
+using ::android::hardware::wifi::V1_5::IWifiApIface;
namespace {
constexpr unsigned char kNwSsid[] = {'t', 'e', 's', 't', '1',
@@ -71,16 +74,39 @@
isWpa3SaeSupport_ = testing::checkSubstringInCommandOutput(
"/system/bin/cmd wifi get-softap-supported-features",
"wifi_softap_wpa3_sae_supported");
+ isBridgedSupport_ = testing::checkSubstringInCommandOutput(
+ "/system/bin/cmd wifi get-softap-supported-features",
+ "wifi_softap_bridged_ap_supported");
}
virtual void TearDown() override {
HIDL_INVOKE_VOID_WITHOUT_ARGUMENTS(hostapd_, terminate);
stopHostapd(wifi_instance_name_);
+ // Wait 3 seconds to allow driver processing load/unload between two
+ // test cases.
+ sleep(3);
}
protected:
bool isWpa3SaeSupport_ = false;
bool isAcsSupport_ = false;
+ bool isBridgedSupport_ = false;
+
+ std::string setupApIfaceAndGetName(bool isBridged) {
+ sp<IWifiApIface> wifi_ap_iface;
+ if (isBridged) {
+ wifi_ap_iface = getBridgedWifiApIface_1_5(wifi_instance_name_);
+ } else {
+ wifi_ap_iface = getWifiApIface_1_5(wifi_instance_name_);
+ }
+ EXPECT_NE(nullptr, wifi_ap_iface.get());
+
+ const auto& status_and_name = HIDL_INVOKE(wifi_ap_iface, getName);
+ EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_name.first.code);
+ return status_and_name.second;
+ }
+
+ // TODO: b/177483254, remove it after fix wlan1 failure case.
std::string getPrimaryWlanIfaceName() {
std::array<char, PROPERTY_VALUE_MAX> buffer;
auto res = property_get("ro.vendor.wifi.sap.interface", buffer.data(),
@@ -90,7 +116,7 @@
return buffer.data();
}
- IHostapd::IfaceParams getIfaceParamsWithoutAcs() {
+ IHostapd::IfaceParams getIfaceParamsWithoutAcs(std::string iface_name) {
::android::hardware::wifi::hostapd::V1_0::IHostapd::IfaceParams
iface_params;
::android::hardware::wifi::hostapd::V1_1::IHostapd::IfaceParams
@@ -106,7 +132,7 @@
::android::hardware::wifi::hostapd::V1_3::IHostapd::ChannelParams
channelParams_1_3;
- iface_params.ifaceName = getPrimaryWlanIfaceName();
+ iface_params.ifaceName = iface_name;
iface_params.hwModeParams.enable80211N = true;
iface_params.hwModeParams.enable80211AC = false;
iface_params.channelParams.enableAcs = false;
@@ -133,9 +159,41 @@
return iface_params_1_3;
}
- IHostapd::IfaceParams getIfaceParamsWithAcs() {
+ IHostapd::IfaceParams getIfaceParamsWithBridgedModeACS(
+ std::string iface_name) {
// First get the settings for WithoutAcs and then make changes
- IHostapd::IfaceParams iface_params_1_3 = getIfaceParamsWithoutAcs();
+ IHostapd::IfaceParams iface_params_1_3 =
+ getIfaceParamsWithoutAcs(iface_name);
+ iface_params_1_3.V1_2.V1_1.V1_0.channelParams.enableAcs = true;
+ iface_params_1_3.V1_2.V1_1.V1_0.channelParams.acsShouldExcludeDfs =
+ true;
+
+ std::vector<
+ ::android::hardware::wifi::hostapd::V1_3::IHostapd::ChannelParams>
+ vec_channelParams;
+
+ vec_channelParams.push_back(iface_params_1_3.channelParamsList[0]);
+
+ ::android::hardware::wifi::hostapd::V1_3::IHostapd::ChannelParams
+ second_channelParams_1_3;
+ second_channelParams_1_3.channel = 0;
+ second_channelParams_1_3.enableAcs = true;
+ second_channelParams_1_3.bandMask = 0;
+ second_channelParams_1_3.bandMask |= IHostapd::BandMask::BAND_5_GHZ;
+ second_channelParams_1_3.V1_2 = iface_params_1_3.V1_2.channelParams;
+ second_channelParams_1_3.V1_2.bandMask = 0;
+ second_channelParams_1_3.V1_2.bandMask |=
+ IHostapd::BandMask::BAND_5_GHZ;
+ vec_channelParams.push_back(second_channelParams_1_3);
+
+ iface_params_1_3.channelParamsList = vec_channelParams;
+ return iface_params_1_3;
+ }
+
+ IHostapd::IfaceParams getIfaceParamsWithAcs(std::string iface_name) {
+ // First get the settings for WithoutAcs and then make changes
+ IHostapd::IfaceParams iface_params_1_3 =
+ getIfaceParamsWithoutAcs(iface_name);
iface_params_1_3.V1_2.V1_1.V1_0.channelParams.enableAcs = true;
iface_params_1_3.V1_2.V1_1.V1_0.channelParams.acsShouldExcludeDfs =
true;
@@ -153,8 +211,10 @@
return iface_params_1_3;
}
- IHostapd::IfaceParams getIfaceParamsWithAcsAndFreqRange() {
- IHostapd::IfaceParams iface_params_1_3 = getIfaceParamsWithAcs();
+ IHostapd::IfaceParams getIfaceParamsWithAcsAndFreqRange(
+ std::string iface_name) {
+ IHostapd::IfaceParams iface_params_1_3 =
+ getIfaceParamsWithAcs(iface_name);
::android::hardware::wifi::hostapd::V1_2::IHostapd::AcsFrequencyRange
acsFrequencyRange;
acsFrequencyRange.start = 2412;
@@ -170,9 +230,10 @@
return iface_params_1_3;
}
- IHostapd::IfaceParams getIfaceParamsWithAcsAndInvalidFreqRange() {
+ IHostapd::IfaceParams getIfaceParamsWithAcsAndInvalidFreqRange(
+ std::string iface_name) {
IHostapd::IfaceParams iface_params_1_3 =
- getIfaceParamsWithAcsAndFreqRange();
+ getIfaceParamsWithAcsAndFreqRange(iface_name);
iface_params_1_3.V1_2.channelParams.acsChannelFreqRangesMhz[0].start =
222;
iface_params_1_3.V1_2.channelParams.acsChannelFreqRangesMhz[0].end =
@@ -250,8 +311,10 @@
return nw_params_1_3;
}
- IHostapd::IfaceParams getIfaceParamsWithInvalidChannel() {
- IHostapd::IfaceParams iface_params_1_3 = getIfaceParamsWithoutAcs();
+ IHostapd::IfaceParams getIfaceParamsWithInvalidChannel(
+ std::string iface_name) {
+ IHostapd::IfaceParams iface_params_1_3 =
+ getIfaceParamsWithoutAcs(iface_name);
iface_params_1_3.V1_2.V1_1.V1_0.channelParams.channel =
kIfaceInvalidChannel;
iface_params_1_3.channelParamsList[0].channel =
@@ -271,8 +334,11 @@
*/
TEST_P(HostapdHidlTest, AddPskAccessPointWithAcs) {
if (!isAcsSupport_) GTEST_SKIP() << "Missing ACS support";
+ // TODO: Use setupApIfaceAndGetName after fixing b/177483254
+ // std::string ifname = setupApIfaceAndGetName(false);
+ std::string ifname = getPrimaryWlanIfaceName();
auto status = HIDL_INVOKE(hostapd_, addAccessPoint_1_3,
- getIfaceParamsWithAcs(), getPskNwParams());
+ getIfaceParamsWithAcs(ifname), getPskNwParams());
EXPECT_EQ(HostapdStatusCode::SUCCESS, status.code);
}
@@ -282,9 +348,12 @@
*/
TEST_P(HostapdHidlTest, AddPskAccessPointWithAcsAndFreqRange) {
if (!isAcsSupport_) GTEST_SKIP() << "Missing ACS support";
- auto status =
- HIDL_INVOKE(hostapd_, addAccessPoint_1_3,
- getIfaceParamsWithAcsAndFreqRange(), getPskNwParams());
+ // TODO: Use setupApIfaceAndGetName after fixing b/177483254
+ // std::string ifname = setupApIfaceAndGetName(false);
+ std::string ifname = getPrimaryWlanIfaceName();
+ auto status = HIDL_INVOKE(hostapd_, addAccessPoint_1_3,
+ getIfaceParamsWithAcsAndFreqRange(ifname),
+ getPskNwParams());
EXPECT_EQ(HostapdStatusCode::SUCCESS, status.code);
}
@@ -294,8 +363,11 @@
*/
TEST_P(HostapdHidlTest, AddPskAccessPointWithAcsAndInvalidFreqRange) {
if (!isAcsSupport_) GTEST_SKIP() << "Missing ACS support";
+ // TODO: Use setupApIfaceAndGetName after fixing b/177483254
+ // std::string ifname = setupApIfaceAndGetName(false);
+ std::string ifname = getPrimaryWlanIfaceName();
auto status = HIDL_INVOKE(hostapd_, addAccessPoint_1_3,
- getIfaceParamsWithAcsAndInvalidFreqRange(),
+ getIfaceParamsWithAcsAndInvalidFreqRange(ifname),
getPskNwParams());
EXPECT_NE(HostapdStatusCode::SUCCESS, status.code);
}
@@ -306,8 +378,11 @@
*/
TEST_P(HostapdHidlTest, AddOpenAccessPointWithAcs) {
if (!isAcsSupport_) GTEST_SKIP() << "Missing ACS support";
+ // TODO: Use setupApIfaceAndGetName after fixing b/177483254
+ // std::string ifname = setupApIfaceAndGetName(false);
+ std::string ifname = getPrimaryWlanIfaceName();
auto status = HIDL_INVOKE(hostapd_, addAccessPoint_1_3,
- getIfaceParamsWithAcs(), getOpenNwParams());
+ getIfaceParamsWithAcs(ifname), getOpenNwParams());
EXPECT_EQ(HostapdStatusCode::SUCCESS, status.code);
}
@@ -316,8 +391,12 @@
* Access point creation should pass.
*/
TEST_P(HostapdHidlTest, AddPskAccessPointWithoutAcs) {
- auto status = HIDL_INVOKE(hostapd_, addAccessPoint_1_3,
- getIfaceParamsWithoutAcs(), getPskNwParams());
+ // TODO: Use setupApIfaceAndGetName after fixing b/177483254
+ // std::string ifname = setupApIfaceAndGetName(false);
+ std::string ifname = getPrimaryWlanIfaceName();
+ auto status =
+ HIDL_INVOKE(hostapd_, addAccessPoint_1_3,
+ getIfaceParamsWithoutAcs(ifname), getPskNwParams());
EXPECT_EQ(HostapdStatusCode::SUCCESS, status.code);
}
@@ -326,9 +405,12 @@
* Access point creation should pass.
*/
TEST_P(HostapdHidlTest, AddPskAccessPointWithoutAcsAndNonMetered) {
- auto status =
- HIDL_INVOKE(hostapd_, addAccessPoint_1_3, getIfaceParamsWithoutAcs(),
- getPskNwParamsWithNonMetered());
+ // TODO: Use setupApIfaceAndGetName after fixing b/177483254
+ // std::string ifname = setupApIfaceAndGetName(false);
+ std::string ifname = getPrimaryWlanIfaceName();
+ auto status = HIDL_INVOKE(hostapd_, addAccessPoint_1_3,
+ getIfaceParamsWithoutAcs(ifname),
+ getPskNwParamsWithNonMetered());
EXPECT_EQ(HostapdStatusCode::SUCCESS, status.code);
}
@@ -337,8 +419,12 @@
* Access point creation should pass.
*/
TEST_P(HostapdHidlTest, AddOpenAccessPointWithoutAcs) {
- auto status = HIDL_INVOKE(hostapd_, addAccessPoint_1_3,
- getIfaceParamsWithoutAcs(), getOpenNwParams());
+ // TODO: Use setupApIfaceAndGetName after fixing b/177483254
+ // std::string ifname = setupApIfaceAndGetName(false);
+ std::string ifname = getPrimaryWlanIfaceName();
+ auto status =
+ HIDL_INVOKE(hostapd_, addAccessPoint_1_3,
+ getIfaceParamsWithoutAcs(ifname), getOpenNwParams());
EXPECT_EQ(HostapdStatusCode::SUCCESS, status.code);
}
@@ -348,9 +434,12 @@
*/
TEST_P(HostapdHidlTest, AddSaeTransitionAccessPointWithoutAcs) {
if (!isWpa3SaeSupport_) GTEST_SKIP() << "Missing SAE support";
- auto status =
- HIDL_INVOKE(hostapd_, addAccessPoint_1_3, getIfaceParamsWithoutAcs(),
- getSaeTransitionNwParams());
+ // TODO: Use setupApIfaceAndGetName after fixing b/177483254
+ // std::string ifname = setupApIfaceAndGetName(false);
+ std::string ifname = getPrimaryWlanIfaceName();
+ auto status = HIDL_INVOKE(hostapd_, addAccessPoint_1_3,
+ getIfaceParamsWithoutAcs(ifname),
+ getSaeTransitionNwParams());
EXPECT_EQ(HostapdStatusCode::SUCCESS, status.code);
}
@@ -360,8 +449,12 @@
*/
TEST_P(HostapdHidlTest, AddSAEAccessPointWithoutAcs) {
if (!isWpa3SaeSupport_) GTEST_SKIP() << "Missing SAE support";
- auto status = HIDL_INVOKE(hostapd_, addAccessPoint_1_3,
- getIfaceParamsWithoutAcs(), getSaeNwParams());
+ // TODO: Use setupApIfaceAndGetName after fixing b/177483254
+ // std::string ifname = setupApIfaceAndGetName(false);
+ std::string ifname = getPrimaryWlanIfaceName();
+ auto status =
+ HIDL_INVOKE(hostapd_, addAccessPoint_1_3,
+ getIfaceParamsWithoutAcs(ifname), getSaeNwParams());
EXPECT_EQ(HostapdStatusCode::SUCCESS, status.code);
}
@@ -371,11 +464,14 @@
*/
TEST_P(HostapdHidlTest, RemoveAccessPointWithAcs) {
if (!isAcsSupport_) GTEST_SKIP() << "Missing ACS support";
- auto status_1_2 = HIDL_INVOKE(hostapd_, addAccessPoint_1_3,
- getIfaceParamsWithAcs(), getPskNwParams());
+ // TODO: Use setupApIfaceAndGetName after fixing b/177483254
+ // std::string ifname = setupApIfaceAndGetName(false);
+ std::string ifname = getPrimaryWlanIfaceName();
+ auto status_1_2 =
+ HIDL_INVOKE(hostapd_, addAccessPoint_1_3, getIfaceParamsWithAcs(ifname),
+ getPskNwParams());
EXPECT_EQ(HostapdStatusCode::SUCCESS, status_1_2.code);
- auto status =
- HIDL_INVOKE(hostapd_, removeAccessPoint, getPrimaryWlanIfaceName());
+ auto status = HIDL_INVOKE(hostapd_, removeAccessPoint, ifname);
EXPECT_EQ(
android::hardware::wifi::hostapd::V1_0::HostapdStatusCode::SUCCESS,
status.code);
@@ -386,11 +482,14 @@
* Access point creation & removal should pass.
*/
TEST_P(HostapdHidlTest, RemoveAccessPointWithoutAcs) {
- auto status_1_2 = HIDL_INVOKE(hostapd_, addAccessPoint_1_3,
- getIfaceParamsWithoutAcs(), getPskNwParams());
+ // TODO: Use setupApIfaceAndGetName after fixing b/177483254
+ // std::string ifname = setupApIfaceAndGetName(false);
+ std::string ifname = getPrimaryWlanIfaceName();
+ auto status_1_2 =
+ HIDL_INVOKE(hostapd_, addAccessPoint_1_3,
+ getIfaceParamsWithoutAcs(ifname), getPskNwParams());
EXPECT_EQ(HostapdStatusCode::SUCCESS, status_1_2.code);
- auto status =
- HIDL_INVOKE(hostapd_, removeAccessPoint, getPrimaryWlanIfaceName());
+ auto status = HIDL_INVOKE(hostapd_, removeAccessPoint, ifname);
EXPECT_EQ(
android::hardware::wifi::hostapd::V1_0::HostapdStatusCode::SUCCESS,
status.code);
@@ -401,9 +500,12 @@
* Access point creation should fail.
*/
TEST_P(HostapdHidlTest, AddPskAccessPointWithInvalidChannel) {
+ // TODO: Use setupApIfaceAndGetName after fixing b/177483254
+ // std::string ifname = setupApIfaceAndGetName(false);
+ std::string ifname = getPrimaryWlanIfaceName();
auto status =
HIDL_INVOKE(hostapd_, addAccessPoint_1_3,
- getIfaceParamsWithInvalidChannel(), getPskNwParams());
+ getIfaceParamsWithInvalidChannel(ifname), getPskNwParams());
EXPECT_NE(HostapdStatusCode::SUCCESS, status.code);
}
@@ -412,9 +514,12 @@
* Access point creation should fail.
*/
TEST_P(HostapdHidlTest, AddInvalidPskAccessPointWithoutAcs) {
+ // TODO: Use setupApIfaceAndGetName after fixing b/177483254
+ // std::string ifname = setupApIfaceAndGetName(false);
+ std::string ifname = getPrimaryWlanIfaceName();
auto status =
- HIDL_INVOKE(hostapd_, addAccessPoint_1_3, getIfaceParamsWithoutAcs(),
- getInvalidPskNwParams());
+ HIDL_INVOKE(hostapd_, addAccessPoint_1_3,
+ getIfaceParamsWithoutAcs(ifname), getInvalidPskNwParams());
EXPECT_NE(HostapdStatusCode::SUCCESS, status.code);
}
@@ -424,9 +529,12 @@
*/
TEST_P(HostapdHidlTest, AddInvalidSaeTransitionAccessPointWithoutAcs) {
if (!isWpa3SaeSupport_) GTEST_SKIP() << "Missing SAE support";
- auto status =
- HIDL_INVOKE(hostapd_, addAccessPoint_1_3, getIfaceParamsWithoutAcs(),
- getInvalidSaeTransitionNwParams());
+ // TODO: Use setupApIfaceAndGetName after fixing b/177483254
+ // std::string ifname = setupApIfaceAndGetName(false);
+ std::string ifname = getPrimaryWlanIfaceName();
+ auto status = HIDL_INVOKE(hostapd_, addAccessPoint_1_3,
+ getIfaceParamsWithoutAcs(ifname),
+ getInvalidSaeTransitionNwParams());
EXPECT_NE(HostapdStatusCode::SUCCESS, status.code);
}
@@ -436,9 +544,12 @@
*/
TEST_P(HostapdHidlTest, AddInvalidSaeAccessPointWithoutAcs) {
if (!isWpa3SaeSupport_) GTEST_SKIP() << "Missing SAE support";
+ // TODO: Use setupApIfaceAndGetName after fixing b/177483254
+ // std::string ifname = setupApIfaceAndGetName(false);
+ std::string ifname = getPrimaryWlanIfaceName();
auto status =
- HIDL_INVOKE(hostapd_, addAccessPoint_1_3, getIfaceParamsWithoutAcs(),
- getInvalidSaeNwParams());
+ HIDL_INVOKE(hostapd_, addAccessPoint_1_3,
+ getIfaceParamsWithoutAcs(ifname), getInvalidSaeNwParams());
EXPECT_NE(HostapdStatusCode::SUCCESS, status.code);
}
@@ -447,21 +558,30 @@
* when hotspot interface available.
*/
TEST_P(HostapdHidlTest, DisconnectClientWhenIfacAvailable) {
+ // TODO: Use setupApIfaceAndGetName after fixing b/177483254
+ // std::string ifname = setupApIfaceAndGetName(false);
+ std::string ifname = getPrimaryWlanIfaceName();
auto status_1_2 =
- HIDL_INVOKE(hostapd_, addAccessPoint_1_3, getIfaceParamsWithoutAcs(),
- getOpenNwParams());
+ HIDL_INVOKE(hostapd_, addAccessPoint_1_3,
+ getIfaceParamsWithoutAcs(ifname), getOpenNwParams());
EXPECT_EQ(HostapdStatusCode::SUCCESS, status_1_2.code);
- status_1_2 =
- HIDL_INVOKE(hostapd_, forceClientDisconnect, getPrimaryWlanIfaceName(),
- kTestZeroMacAddr, kTestDisconnectReasonCode);
+ status_1_2 = HIDL_INVOKE(hostapd_, forceClientDisconnect, ifname,
+ kTestZeroMacAddr, kTestDisconnectReasonCode);
EXPECT_EQ(HostapdStatusCode::FAILURE_CLIENT_UNKNOWN, status_1_2.code);
}
/**
* AddAccessPointWithDualBandConfig should pass
*/
-// TODO: Add it after VendorHal ready & add feature support check.
+TEST_P(HostapdHidlTest, AddAccessPointWithDualBandConfig) {
+ if (!isBridgedSupport_) GTEST_SKIP() << "Missing Bridged AP support";
+ std::string ifname = setupApIfaceAndGetName(true);
+ auto status_1_2 = HIDL_INVOKE(hostapd_, addAccessPoint_1_3,
+ getIfaceParamsWithBridgedModeACS(ifname),
+ getOpenNwParams());
+ EXPECT_EQ(HostapdStatusCode::SUCCESS, status_1_2.code);
+}
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(HostapdHidlTest);
INSTANTIATE_TEST_CASE_P(
diff --git a/wifi/supplicant/1.4/ISupplicantStaIfaceCallback.hal b/wifi/supplicant/1.4/ISupplicantStaIfaceCallback.hal
index efcebda..c6f05fb 100644
--- a/wifi/supplicant/1.4/ISupplicantStaIfaceCallback.hal
+++ b/wifi/supplicant/1.4/ISupplicantStaIfaceCallback.hal
@@ -20,7 +20,9 @@
import @1.0::ISupplicantStaIfaceCallback.Hs20AnqpData;
import @1.3::ISupplicantStaIfaceCallback;
import @1.0::ISupplicantStaIfaceCallback.State;
+import @1.0::ISupplicantStaIfaceCallback.StatusCode;
import @1.0::Bssid;
+import @1.0::Ssid;
/**
* Callback Interface exposed by the supplicant service
@@ -32,6 +34,19 @@
*/
interface ISupplicantStaIfaceCallback extends @1.3::ISupplicantStaIfaceCallback {
/**
+ * MBO spec v1.2, 4.2.4 Table 14: MBO Association disallowed reason code attribute
+ * values.
+ */
+ enum MboAssocDisallowedReasonCode : uint8_t {
+ RESERVED = 0,
+ UNSPECIFIED = 1,
+ MAX_NUM_STA_ASSOCIATED = 2,
+ AIR_INTERFACE_OVERLOADED = 3,
+ AUTH_SERVER_OVERLOADED = 4,
+ INSUFFICIENT_RSSI = 5,
+ };
+
+ /**
* ANQP data for IEEE Std 802.11-2016.
* The format of the data within these elements follows the IEEE
* Std 802.11-2016 standard, section 9.4.5.
@@ -49,6 +64,83 @@
};
/**
+ * OceRssiBasedAssocRejectAttr is extracted from (Re-)Association response
+ * frame from an OCE AP to indicate that the AP has rejected the
+ * (Re-)Association request on the basis of insufficient RSSI.
+ * Refer OCE spec v1.0 section 4.2.2 Table 7.
+ */
+ struct OceRssiBasedAssocRejectAttr {
+ /*
+ * Delta RSSI - The difference in dB between the minimum RSSI at which
+ * the AP would accept a (Re-)Association request from the STA before
+ * Retry Delay expires and the AP's measurement of the RSSI at which the
+ * (Re-)Association request was received.
+ */
+ uint32_t deltaRssi;
+
+ /*
+ * Retry Delay - The time period in seconds for which the AP will not
+ * accept any subsequent (Re-)Association requests from the STA, unless
+ * the received RSSI has improved by Delta RSSI.
+ */
+ uint32_t retryDelayS;
+ };
+
+ /**
+ * Association Rejection related information.
+ */
+ struct AssociationRejectionData {
+ /**
+ * SSID of the AP that rejected the association.
+ */
+ Ssid ssid;
+
+ /**
+ * BSSID of the AP that rejected the association.
+ */
+ Bssid bssid;
+
+ /*
+ * 802.11 code to indicate the reject reason.
+ * Refer to section 8.4.1.9 of IEEE 802.11 spec.
+ */
+ StatusCode statusCode;
+
+ /*
+ * Flag to indicate that failure is due to timeout rather than
+ * explicit rejection response from the AP.
+ */
+ bool timedOut;
+
+ /**
+ * Flag to indicate that MboAssocDisallowedReasonCode is present
+ * in the (Re-)Association response frame.
+ */
+ bool isMboAssocDisallowedReasonCodePresent;
+
+ /**
+ * mboAssocDisallowedReason is extracted from MBO association disallowed attribute
+ * in (Re-)Association response frame to indicate that the AP is not accepting new
+ * associations.
+ * Refer MBO spec v1.2 section 4.2.4 Table 13 for the details of reason code.
+ * The value is undefined if isMboAssocDisallowedReasonCodePresent is false.
+ */
+ MboAssocDisallowedReasonCode mboAssocDisallowedReason;
+
+ /**
+ * Flag to indicate that OceRssiBasedAssocRejectAttr is present
+ * in the (Re-)Association response frame.
+ */
+ bool isOceRssiBasedAssocRejectAttrPresent;
+
+ /*
+ * OCE RSSI-based (Re-)Association rejection attribute.
+ * The contents are undefined if isOceRssiBasedAssocRejectAttrPresent is false.
+ */
+ OceRssiBasedAssocRejectAttr oceRssiBasedAssocRejectData;
+ };
+
+ /**
* Used to indicate a Hotspot 2.0 terms and conditions acceptance is requested from the user
* before allowing the device to get internet access.
*
@@ -68,4 +160,12 @@
* All the fields in this struct must be empty if the query failed.
*/
oneway onAnqpQueryDone_1_4(Bssid bssid, AnqpData data, Hs20AnqpData hs20Data);
+
+ /**
+ * Used to indicate an association rejection received from the AP
+ * to which the connection is being attempted.
+ *
+ * @param assocRejectData Association Rejection related information.
+ */
+ oneway onAssociationRejected_1_4(AssociationRejectionData assocRejectData);
};
diff --git a/wifi/supplicant/1.4/vts/functional/supplicant_sta_iface_hidl_test.cpp b/wifi/supplicant/1.4/vts/functional/supplicant_sta_iface_hidl_test.cpp
index ccd469d..1794a39 100644
--- a/wifi/supplicant/1.4/vts/functional/supplicant_sta_iface_hidl_test.cpp
+++ b/wifi/supplicant/1.4/vts/functional/supplicant_sta_iface_hidl_test.cpp
@@ -232,6 +232,11 @@
override {
return Void();
}
+ Return<void> onAssociationRejected_1_4(
+ const ISupplicantStaIfaceCallback::AssociationRejectionData& /* data */)
+ override {
+ return Void();
+ }
};
/*