Merge "Increase NNAPI VTS single-test timeout to 20m."
diff --git a/OWNERS b/OWNERS
index 433bbb7..3a1a038 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,9 +1,14 @@
per-file *.hal,*.aidl,OWNERS = set noparent
-per-file *.hal,*.aidl,OWNERS = elsk@google.com,malchev@google.com,smoreland@google.com
+per-file *.hal,*.aidl,OWNERS = devinmoore@google.com,elsk@google.com,malchev@google.com,smoreland@google.com
+# Android Native API Council
+devinmoore@google.com
elsk@google.com
-maco@google.com
malchev@google.com
smoreland@google.com
-yim@google.com # vts tests
-guangzhu@google.com # vts tests
+
+# historical/backup
+maco@google.com
+
+# vts tests
+guangzhu@google.com
diff --git a/audio/7.0/IDevice.hal b/audio/7.0/IDevice.hal
index e30e545..e423f29 100644
--- a/audio/7.0/IDevice.hal
+++ b/audio/7.0/IDevice.hal
@@ -103,6 +103,11 @@
* If the stream can not be opened with the proposed audio config,
* HAL must provide suggested values for the audio config.
*
+ * Note: INVALID_ARGUMENTS is returned both in the case when the
+ * HAL can not use the provided config and in the case when
+ * the value of any argument is invalid. In the latter case the
+ * HAL must provide a default initialized suggested config.
+ *
* @param ioHandle handle assigned by AudioFlinger.
* @param device device type and (if needed) address.
* @param config stream configuration.
@@ -111,7 +116,8 @@
May be used by implementations to configure hardware effects.
* @return retval operation completion status.
* @return outStream created output stream.
- * @return suggestedConfig in case of invalid parameters, suggested config.
+ * @return suggestedConfig in the case of rejection of the proposed config,
+ * a config suggested by the HAL.
*/
openOutputStream(
AudioIoHandle ioHandle,
@@ -128,6 +134,11 @@
* If the stream can not be opened with the proposed audio config,
* HAL must provide suggested values for the audio config.
*
+ * Note: INVALID_ARGUMENTS is returned both in the case when the
+ * HAL can not use the provided config and in the case when
+ * the value of any argument is invalid. In the latter case the
+ * HAL must provide a default initialized suggested config.
+ *
* @param ioHandle handle assigned by AudioFlinger.
* @param device device type and (if needed) address.
* @param config stream configuration.
@@ -136,7 +147,8 @@
* May be used by implementations to configure processing effects.
* @return retval operation completion status.
* @return inStream in case of success, created input stream.
- * @return suggestedConfig in case of invalid parameters, suggested config.
+ * @return suggestedConfig in the case of rejection of the proposed config,
+ * a config suggested by the HAL.
*/
openInputStream(
AudioIoHandle ioHandle,
@@ -162,6 +174,9 @@
* Creates an audio patch between several source and sink ports. The handle
* is allocated by the HAL and must be unique for this audio HAL module.
*
+ * Optional method. HAL must support it if 'supportsAudioPatches' returns
+ * 'true'.
+ *
* @param sources patch sources.
* @param sinks patch sinks.
* @return retval operation completion status.
@@ -177,6 +192,9 @@
* as the HAL module can figure out a way of switching the route without
* causing audio disruption.
*
+ * Optional method. HAL must support it if 'supportsAudioPatches' returns
+ * 'true'.
+ *
* @param previousPatch handle of the previous patch to update.
* @param sources new patch sources.
* @param sinks new patch sinks.
@@ -192,6 +210,9 @@
/**
* Release an audio patch.
*
+ * Optional method. HAL must support it if 'supportsAudioPatches' returns
+ * 'true'.
+ *
* @param patch patch handle.
* @return retval operation completion status.
*/
diff --git a/audio/7.0/IStream.hal b/audio/7.0/IStream.hal
index 4fe8218..ab9aa7d 100644
--- a/audio/7.0/IStream.hal
+++ b/audio/7.0/IStream.hal
@@ -66,9 +66,10 @@
* Retrieves basic stream configuration: sample rate, audio format,
* channel mask.
*
+ * @return retval operation completion status.
* @return config basic stream configuration.
*/
- getAudioProperties() generates (AudioConfigBase config);
+ getAudioProperties() generates (Result retval, AudioConfigBase config);
/**
* Sets stream parameters. Only sets parameters that are specified.
diff --git a/audio/7.0/IStreamIn.hal b/audio/7.0/IStreamIn.hal
index 0a3f24b..bf9ae52 100644
--- a/audio/7.0/IStreamIn.hal
+++ b/audio/7.0/IStreamIn.hal
@@ -41,6 +41,18 @@
setGain(float gain) generates (Result retval);
/**
+ * Called when the metadata of the stream's sink has been changed.
+ * Optional method
+ *
+ * @param sinkMetadata Description of the audio that is suggested by the clients.
+ * @return retval operation completion status.
+ * If any of the metadata fields contains an invalid value,
+ * returns INVALID_ARGUMENTS.
+ * If method isn't supported by the HAL returns NOT_SUPPORTED.
+ */
+ updateSinkMetadata(SinkMetadata sinkMetadata) generates (Result retval);
+
+ /**
* Commands that can be executed on the driver reader thread.
*/
enum ReadCommand : int32_t {
@@ -82,12 +94,6 @@
};
/**
- * Called when the metadata of the stream's sink has been changed.
- * @param sinkMetadata Description of the audio that is suggested by the clients.
- */
- updateSinkMetadata(SinkMetadata sinkMetadata);
-
- /**
* Set up required transports for receiving audio buffers from the driver.
*
* The transport consists of three message queues:
diff --git a/audio/7.0/IStreamOut.hal b/audio/7.0/IStreamOut.hal
index 38d750f..4daab26 100644
--- a/audio/7.0/IStreamOut.hal
+++ b/audio/7.0/IStreamOut.hal
@@ -45,6 +45,18 @@
setVolume(float left, float right) generates (Result retval);
/**
+ * Called when the metadata of the stream's source has been changed.
+ * Optional method
+ *
+ * @param sourceMetadata Description of the audio that is played by the clients.
+ * @return retval operation completion status.
+ * If any of the metadata fields contains an invalid value,
+ * returns INVALID_ARGUMENTS.
+ * If method isn't supported by the HAL returns NOT_SUPPORTED.
+ */
+ updateSourceMetadata(SourceMetadata sourceMetadata) generates (Result retval);
+
+ /**
* Commands that can be executed on the driver writer thread.
*/
enum WriteCommand : int32_t {
@@ -77,12 +89,6 @@
};
/**
- * Called when the metadata of the stream's source has been changed.
- * @param sourceMetadata Description of the audio that is played by the clients.
- */
- updateSourceMetadata(SourceMetadata sourceMetadata);
-
- /**
* Set up required transports for passing audio buffers to the driver.
*
* The transport consists of three message queues:
diff --git a/audio/7.0/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 414eede..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); });
}
@@ -225,6 +224,10 @@
return stringToAudioChannelMask(mask) == AudioChannelMask::UNKNOWN;
}
+static inline bool isUnknownAudioContentType(const std::string& contentType) {
+ return stringToAudioContentType(contentType) == AudioContentType::UNKNOWN;
+}
+
static inline bool isUnknownAudioDevice(const std::string& device) {
return stringToAudioDevice(device) == AudioDevice::UNKNOWN && !isVendorExtension(device);
}
@@ -237,6 +240,10 @@
return stringToAudioGainMode(mode) == AudioGainMode::UNKNOWN;
}
+static inline bool isUnknownAudioInOutFlag(const std::string& flag) {
+ return stringToAudioInOutFlag(flag) == AudioInOutFlag::UNKNOWN;
+}
+
static inline bool isUnknownAudioSource(const std::string& source) {
return stringToAudioSource(source) == AudioSource::UNKNOWN;
}
diff --git a/audio/common/7.0/example/Effect.cpp b/audio/common/7.0/example/Effect.cpp
index 9d5ab31..27f28c6 100644
--- a/audio/common/7.0/example/Effect.cpp
+++ b/audio/common/7.0/example/Effect.cpp
@@ -107,14 +107,14 @@
}
Return<void> Effect::getConfig(getConfig_cb _hidl_cb) {
- const EffectConfig config = {{} /* inputCfg */,
- // outputCfg
- {{} /* buffer */,
- 48000 /* samplingRateHz */,
- toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO),
- toString(xsd::AudioFormat::AUDIO_FORMAT_PCM_16_BIT),
- EffectBufferAccess::ACCESS_ACCUMULATE,
- 0 /* mask */}};
+ const EffectConfig config = {
+ {} /* inputCfg */,
+ // outputCfg
+ {{} /* buffer */,
+ {toString(xsd::AudioFormat::AUDIO_FORMAT_PCM_16_BIT), 48000 /* samplingRateHz */,
+ toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO)}, /* base */
+ EffectBufferAccess::ACCESS_ACCUMULATE,
+ 0 /* mask */}};
_hidl_cb(Result::OK, config);
return Void();
}
diff --git a/audio/common/7.0/types.hal b/audio/common/7.0/types.hal
index ed56c73..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 1a66282..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"
@@ -95,6 +96,25 @@
return NO_ERROR;
}
+status_t HidlUtils::audioChannelMasksFromHal(const std::vector<std::string>& halChannelMasks,
+ hidl_vec<AudioChannelMask>* channelMasks) {
+ hidl_vec<AudioChannelMask> tempChannelMasks;
+ tempChannelMasks.resize(halChannelMasks.size());
+ size_t tempPos = 0;
+ for (const auto& halChannelMask : halChannelMasks) {
+ if (!halChannelMask.empty() && !xsd::isUnknownAudioChannelMask(halChannelMask)) {
+ tempChannelMasks[tempPos++] = halChannelMask;
+ }
+ }
+ if (tempPos == tempChannelMasks.size()) {
+ *channelMasks = std::move(tempChannelMasks);
+ } else {
+ *channelMasks = hidl_vec<AudioChannelMask>(tempChannelMasks.begin(),
+ tempChannelMasks.begin() + tempPos);
+ }
+ return halChannelMasks.size() == channelMasks->size() ? NO_ERROR : BAD_VALUE;
+}
+
status_t HidlUtils::audioChannelMaskToHal(const AudioChannelMask& channelMask,
audio_channel_mask_t* halChannelMask) {
if (!xsd::isUnknownAudioChannelMask(channelMask) &&
@@ -127,6 +147,28 @@
return result;
}
+status_t HidlUtils::audioContentTypeFromHal(const audio_content_type_t halContentType,
+ AudioContentType* contentType) {
+ *contentType = audio_content_type_to_string(halContentType);
+ if (!contentType->empty() && !xsd::isUnknownAudioContentType(*contentType)) {
+ return NO_ERROR;
+ }
+ ALOGE("Unknown audio content type value 0x%X", halContentType);
+ *contentType = toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_UNKNOWN);
+ return BAD_VALUE;
+}
+
+status_t HidlUtils::audioContentTypeToHal(const AudioContentType& contentType,
+ audio_content_type_t* halContentType) {
+ if (!xsd::isUnknownAudioContentType(contentType) &&
+ audio_content_type_from_string(contentType.c_str(), halContentType)) {
+ return NO_ERROR;
+ }
+ ALOGE("Unknown audio content type \"%s\"", contentType.c_str());
+ *halContentType = AUDIO_CONTENT_TYPE_UNKNOWN;
+ return BAD_VALUE;
+}
+
status_t HidlUtils::audioDeviceTypeFromHal(audio_devices_t halDevice, AudioDevice* device) {
*device = audio_device_to_string(halDevice);
if (!device->empty() && !xsd::isUnknownAudioDevice(*device)) {
@@ -155,6 +197,24 @@
return BAD_VALUE;
}
+status_t HidlUtils::audioFormatsFromHal(const std::vector<std::string>& halFormats,
+ hidl_vec<AudioFormat>* formats) {
+ hidl_vec<AudioFormat> tempFormats;
+ tempFormats.resize(halFormats.size());
+ size_t tempPos = 0;
+ for (const auto& halFormat : halFormats) {
+ if (!halFormat.empty() && !xsd::isUnknownAudioFormat(halFormat)) {
+ tempFormats[tempPos++] = halFormat;
+ }
+ }
+ if (tempPos == tempFormats.size()) {
+ *formats = std::move(tempFormats);
+ } else {
+ *formats = hidl_vec<AudioFormat>(tempFormats.begin(), tempFormats.begin() + tempPos);
+ }
+ return halFormats.size() == formats->size() ? NO_ERROR : BAD_VALUE;
+}
+
status_t HidlUtils::audioFormatToHal(const AudioFormat& format, audio_format_t* halFormat) {
if (!xsd::isUnknownAudioFormat(format) && audio_format_from_string(format.c_str(), halFormat)) {
return NO_ERROR;
@@ -247,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;
}
@@ -260,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;
}
@@ -741,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/Android.bp b/audio/common/all-versions/default/Android.bp
index b83a58a..45f0b8f 100644
--- a/audio/common/all-versions/default/Android.bp
+++ b/audio/common/all-versions/default/Android.bp
@@ -43,6 +43,7 @@
name: "android.hardware.audio.common-util@2-6",
srcs: [
"HidlUtils.cpp",
+ "HidlUtilsCommon.cpp",
"UuidUtils.cpp",
],
}
@@ -132,6 +133,7 @@
defaults: ["android.hardware.audio.common-util_default"],
srcs: [
"7.0/HidlUtils.cpp",
+ "HidlUtilsCommon.cpp",
"UuidUtils.cpp",
],
shared_libs: [
diff --git a/audio/common/all-versions/default/HidlUtils.cpp b/audio/common/all-versions/default/HidlUtils.cpp
index ab3c1c7..c0dcd80 100644
--- a/audio/common/all-versions/default/HidlUtils.cpp
+++ b/audio/common/all-versions/default/HidlUtils.cpp
@@ -28,7 +28,7 @@
namespace CPP_VERSION {
namespace implementation {
-status_t HidlUtils::audioConfigFromHal(const audio_config_t& halConfig, AudioConfig* config) {
+status_t HidlUtils::audioConfigFromHal(const audio_config_t& halConfig, bool, AudioConfig* config) {
config->sampleRateHz = halConfig.sample_rate;
config->channelMask = EnumBitfield<AudioChannelMask>(halConfig.channel_mask);
config->format = AudioFormat(halConfig.format);
@@ -47,8 +47,8 @@
return NO_ERROR;
}
-void HidlUtils::audioGainConfigFromHal(const struct audio_gain_config& halConfig,
- AudioGainConfig* config) {
+status_t HidlUtils::audioGainConfigFromHal(const struct audio_gain_config& halConfig, bool,
+ AudioGainConfig* config) {
config->index = halConfig.index;
config->mode = EnumBitfield<AudioGainMode>(halConfig.mode);
config->channelMask = EnumBitfield<AudioChannelMask>(halConfig.channel_mask);
@@ -56,6 +56,7 @@
config->values[i] = halConfig.values[i];
}
config->rampDurationMs = halConfig.ramp_duration_ms;
+ return NO_ERROR;
}
status_t HidlUtils::audioGainConfigToHal(const AudioGainConfig& config,
@@ -71,7 +72,7 @@
return NO_ERROR;
}
-void HidlUtils::audioGainFromHal(const struct audio_gain& halGain, AudioGain* gain) {
+status_t HidlUtils::audioGainFromHal(const struct audio_gain& halGain, bool, AudioGain* gain) {
gain->mode = EnumBitfield<AudioGainMode>(halGain.mode);
gain->channelMask = EnumBitfield<AudioChannelMask>(halGain.channel_mask);
gain->minValue = halGain.min_value;
@@ -80,6 +81,7 @@
gain->stepValue = halGain.step_value;
gain->minRampMs = halGain.min_ramp_ms;
gain->maxRampMs = halGain.max_ramp_ms;
+ return NO_ERROR;
}
status_t HidlUtils::audioGainToHal(const AudioGain& gain, struct audio_gain* halGain) {
@@ -182,7 +184,7 @@
config->sampleRateHz = halConfig.sample_rate;
config->channelMask = EnumBitfield<AudioChannelMask>(halConfig.channel_mask);
config->format = AudioFormat(halConfig.format);
- audioGainConfigFromHal(halConfig.gain, &config->gain);
+ audioGainConfigFromHal(halConfig.gain, false /*isInput--ignored*/, &config->gain);
switch (halConfig.type) {
case AUDIO_PORT_TYPE_NONE:
break;
@@ -272,7 +274,7 @@
}
port->gains.resize(halPort.num_gains);
for (size_t i = 0; i < halPort.num_gains; ++i) {
- audioGainFromHal(halPort.gains[i], &port->gains[i]);
+ audioGainFromHal(halPort.gains[i], false /*isInput--ignored*/, &port->gains[i]);
}
audioPortConfigFromHal(halPort.active_config, &port->activeConfig);
switch (halPort.type) {
@@ -351,6 +353,18 @@
return NO_ERROR;
}
+#if MAJOR_VERSION >= 5
+status_t HidlUtils::deviceAddressToHal(const DeviceAddress& device, audio_devices_t* halDeviceType,
+ char* halDeviceAddress) {
+ return deviceAddressToHalImpl(device, halDeviceType, halDeviceAddress);
+}
+
+status_t HidlUtils::deviceAddressFromHal(audio_devices_t halDeviceType,
+ const char* halDeviceAddress, DeviceAddress* device) {
+ return deviceAddressFromHalImpl(halDeviceType, halDeviceAddress, device);
+}
+#endif
+
} // namespace implementation
} // namespace CPP_VERSION
} // namespace common
diff --git a/audio/common/all-versions/default/HidlUtils.h b/audio/common/all-versions/default/HidlUtils.h
index 4e609ca..8e9275c 100644
--- a/audio/common/all-versions/default/HidlUtils.h
+++ b/audio/common/all-versions/default/HidlUtils.h
@@ -23,8 +23,6 @@
#include <system/audio.h>
-using ::android::hardware::hidl_vec;
-
namespace android {
namespace hardware {
namespace audio {
@@ -32,25 +30,25 @@
namespace CPP_VERSION {
namespace implementation {
+using ::android::hardware::hidl_vec;
using namespace ::android::hardware::audio::common::CPP_VERSION;
struct HidlUtils {
-#if MAJOR_VERSION < 7
- static status_t audioConfigFromHal(const audio_config_t& halConfig, AudioConfig* config);
- static void audioGainConfigFromHal(const struct audio_gain_config& halConfig,
- AudioGainConfig* config);
- static void audioGainFromHal(const struct audio_gain& halGain, AudioGain* gain);
-#else
static status_t audioConfigFromHal(const audio_config_t& halConfig, bool isInput,
AudioConfig* config);
+ static status_t audioConfigToHal(const AudioConfig& config, audio_config_t* halConfig);
+#if MAJOR_VERSION >= 4
+ static status_t audioContentTypeFromHal(const audio_content_type_t halContentType,
+ AudioContentType* contentType);
+ static status_t audioContentTypeToHal(const AudioContentType& contentType,
+ audio_content_type_t* halContentType);
+#endif
static status_t audioGainConfigFromHal(const struct audio_gain_config& halConfig, bool isInput,
AudioGainConfig* config);
- static status_t audioGainFromHal(const struct audio_gain& halGain, bool isInput,
- AudioGain* gain);
-#endif
- static status_t audioConfigToHal(const AudioConfig& config, audio_config_t* halConfig);
static status_t audioGainConfigToHal(const AudioGainConfig& config,
struct audio_gain_config* halConfig);
+ static status_t audioGainFromHal(const struct audio_gain& halGain, bool isInput,
+ AudioGain* gain);
static status_t audioGainToHal(const AudioGain& gain, struct audio_gain* halGain);
static status_t audioUsageFromHal(audio_usage_t halUsage, AudioUsage* usage);
static status_t audioUsageToHal(const AudioUsage& usage, audio_usage_t* halUsage);
@@ -64,43 +62,27 @@
struct audio_port_config* halConfig);
static status_t audioPortConfigsFromHal(unsigned int numHalConfigs,
const struct audio_port_config* halConfigs,
- hidl_vec<AudioPortConfig>* configs) {
- status_t result = NO_ERROR;
- configs->resize(numHalConfigs);
- for (unsigned int i = 0; i < numHalConfigs; ++i) {
- if (status_t status = audioPortConfigFromHal(halConfigs[i], &(*configs)[i]);
- status != NO_ERROR) {
- result = status;
- }
- }
- return result;
- }
+ hidl_vec<AudioPortConfig>* configs);
static status_t audioPortConfigsToHal(const hidl_vec<AudioPortConfig>& configs,
- std::unique_ptr<audio_port_config[]>* halConfigs) {
- status_t result = NO_ERROR;
- halConfigs->reset(new audio_port_config[configs.size()]);
- for (size_t i = 0; i < configs.size(); ++i) {
- if (status_t status = audioPortConfigToHal(configs[i], &(*halConfigs)[i]);
- status != NO_ERROR) {
- result = status;
- }
- }
- return result;
- }
-
- // PLEASE DO NOT USE, will be removed in a couple of days
- static std::unique_ptr<audio_port_config[]> audioPortConfigsToHal(
- const hidl_vec<AudioPortConfig>& configs) {
- std::unique_ptr<audio_port_config[]> halConfigs;
- (void)audioPortConfigsToHal(configs, &halConfigs);
- return halConfigs;
- }
-
+ std::unique_ptr<audio_port_config[]>* halConfigs);
static status_t audioPortFromHal(const struct audio_port& halPort, AudioPort* port);
static status_t audioPortToHal(const AudioPort& port, struct audio_port* halPort);
+ static status_t audioSourceFromHal(audio_source_t halSource, AudioSource* source);
+ static status_t audioSourceToHal(const AudioSource& source, audio_source_t* halSource);
+#if MAJOR_VERSION >= 5
+ static status_t deviceAddressToHal(const DeviceAddress& device, audio_devices_t* halDeviceType,
+ char* halDeviceAddress);
+ static status_t deviceAddressFromHal(audio_devices_t halDeviceType,
+ const char* halDeviceAddress, DeviceAddress* device);
+#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,
+ hidl_vec<AudioChannelMask>* channelMasks);
static status_t audioChannelMaskToHal(const AudioChannelMask& channelMask,
audio_channel_mask_t* halChannelMask);
static status_t audioConfigBaseFromHal(const audio_config_base_t& halConfigBase, bool isInput,
@@ -110,6 +92,8 @@
static status_t audioDeviceTypeFromHal(audio_devices_t halDevice, AudioDevice* device);
static status_t audioDeviceTypeToHal(const AudioDevice& device, audio_devices_t* halDevice);
static status_t audioFormatFromHal(audio_format_t halFormat, AudioFormat* format);
+ static status_t audioFormatsFromHal(const std::vector<std::string>& halFormats,
+ hidl_vec<AudioFormat>* formats);
static status_t audioFormatToHal(const AudioFormat& format, audio_format_t* halFormat);
static status_t audioGainModeMaskFromHal(audio_gain_mode_t halGainModeMask,
hidl_vec<AudioGainMode>* gainModeMask);
@@ -121,16 +105,12 @@
AudioProfile* profile);
static status_t audioProfileToHal(const AudioProfile& profile,
struct audio_profile* halProfile);
- static status_t audioSourceFromHal(audio_source_t halSource, AudioSource* source);
- static status_t audioSourceToHal(const AudioSource& source, audio_source_t* halSource);
static status_t audioStreamTypeFromHal(audio_stream_type_t halStreamType,
AudioStreamType* streamType);
static status_t audioStreamTypeToHal(const AudioStreamType& streamType,
audio_stream_type_t* halStreamType);
- static status_t deviceAddressToHal(const DeviceAddress& device, audio_devices_t* halDeviceType,
- char* halDeviceAddress);
- static status_t deviceAddressFromHal(audio_devices_t halDeviceType,
- const char* halDeviceAddress, DeviceAddress* device);
+ 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,
@@ -150,9 +130,115 @@
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.
+ // To avoid duplicating code, the implementations of deviceAddressTo/FromHal
+ // are defined as templates. These templates can be only used directly by V4
+ // and below.
+#if MAJOR_VERSION >= 5
+ private:
#endif
+ template <typename DA>
+ static status_t deviceAddressToHalImpl(const DA& device, audio_devices_t* halDeviceType,
+ char* halDeviceAddress);
+ template <typename DA>
+ static status_t deviceAddressFromHalImpl(audio_devices_t halDeviceType,
+ const char* halDeviceAddress, DA* device);
};
+#if MAJOR_VERSION <= 6
+#if MAJOR_VERSION >= 4
+inline status_t HidlUtils::audioContentTypeFromHal(const audio_content_type_t halContentType,
+ AudioContentType* contentType) {
+ *contentType = AudioContentType(halContentType);
+ return NO_ERROR;
+}
+
+inline status_t HidlUtils::audioContentTypeToHal(const AudioContentType& contentType,
+ audio_content_type_t* halContentType) {
+ *halContentType = static_cast<audio_content_type_t>(contentType);
+ return NO_ERROR;
+}
+#endif
+
+inline status_t HidlUtils::audioSourceFromHal(audio_source_t halSource, AudioSource* source) {
+ *source = AudioSource(halSource);
+ return NO_ERROR;
+}
+
+inline status_t HidlUtils::audioSourceToHal(const AudioSource& source, audio_source_t* halSource) {
+ *halSource = static_cast<audio_source_t>(source);
+ return NO_ERROR;
+}
+
+template <typename DA>
+status_t HidlUtils::deviceAddressToHalImpl(const DA& device, audio_devices_t* halDeviceType,
+ char* halDeviceAddress) {
+ *halDeviceType = static_cast<audio_devices_t>(device.device);
+ memset(halDeviceAddress, 0, AUDIO_DEVICE_MAX_ADDRESS_LEN);
+ if (audio_is_a2dp_out_device(*halDeviceType) || audio_is_a2dp_in_device(*halDeviceType)) {
+ snprintf(halDeviceAddress, AUDIO_DEVICE_MAX_ADDRESS_LEN, "%02X:%02X:%02X:%02X:%02X:%02X",
+ device.address.mac[0], device.address.mac[1], device.address.mac[2],
+ device.address.mac[3], device.address.mac[4], device.address.mac[5]);
+ } else if (*halDeviceType == AUDIO_DEVICE_OUT_IP || *halDeviceType == AUDIO_DEVICE_IN_IP) {
+ snprintf(halDeviceAddress, AUDIO_DEVICE_MAX_ADDRESS_LEN, "%d.%d.%d.%d",
+ device.address.ipv4[0], device.address.ipv4[1], device.address.ipv4[2],
+ device.address.ipv4[3]);
+ } else if (audio_is_usb_out_device(*halDeviceType) || audio_is_usb_in_device(*halDeviceType)) {
+ snprintf(halDeviceAddress, AUDIO_DEVICE_MAX_ADDRESS_LEN, "card=%d;device=%d",
+ device.address.alsa.card, device.address.alsa.device);
+ } else if (*halDeviceType == AUDIO_DEVICE_OUT_BUS || *halDeviceType == AUDIO_DEVICE_IN_BUS) {
+ snprintf(halDeviceAddress, AUDIO_DEVICE_MAX_ADDRESS_LEN, "%s", device.busAddress.c_str());
+ } else if (*halDeviceType == AUDIO_DEVICE_OUT_REMOTE_SUBMIX ||
+ *halDeviceType == AUDIO_DEVICE_IN_REMOTE_SUBMIX) {
+ snprintf(halDeviceAddress, AUDIO_DEVICE_MAX_ADDRESS_LEN, "%s",
+ device.rSubmixAddress.c_str());
+ }
+ return NO_ERROR;
+}
+
+template <typename DA>
+status_t HidlUtils::deviceAddressFromHalImpl(audio_devices_t halDeviceType,
+ const char* halDeviceAddress, DA* device) {
+ if (device == nullptr) {
+ return BAD_VALUE;
+ }
+ device->device = AudioDevice(halDeviceType);
+ if (halDeviceAddress == nullptr ||
+ strnlen(halDeviceAddress, AUDIO_DEVICE_MAX_ADDRESS_LEN) == 0) {
+ return NO_ERROR;
+ }
+
+ if (audio_is_a2dp_out_device(halDeviceType) || audio_is_a2dp_in_device(halDeviceType)) {
+ int status =
+ sscanf(halDeviceAddress, "%hhX:%hhX:%hhX:%hhX:%hhX:%hhX", &device->address.mac[0],
+ &device->address.mac[1], &device->address.mac[2], &device->address.mac[3],
+ &device->address.mac[4], &device->address.mac[5]);
+ return status == 6 ? OK : BAD_VALUE;
+ } else if (halDeviceType == AUDIO_DEVICE_OUT_IP || halDeviceType == AUDIO_DEVICE_IN_IP) {
+ int status = sscanf(halDeviceAddress, "%hhu.%hhu.%hhu.%hhu", &device->address.ipv4[0],
+ &device->address.ipv4[1], &device->address.ipv4[2],
+ &device->address.ipv4[3]);
+ return status == 4 ? OK : BAD_VALUE;
+ } else if (audio_is_usb_out_device(halDeviceType) || audio_is_usb_in_device(halDeviceType)) {
+ int status = sscanf(halDeviceAddress, "card=%d;device=%d", &device->address.alsa.card,
+ &device->address.alsa.device);
+ return status == 2 ? OK : BAD_VALUE;
+ } else if (halDeviceType == AUDIO_DEVICE_OUT_BUS || halDeviceType == AUDIO_DEVICE_IN_BUS) {
+ device->busAddress = halDeviceAddress;
+ return OK;
+ } else if (halDeviceType == AUDIO_DEVICE_OUT_REMOTE_SUBMIX ||
+ halDeviceType == AUDIO_DEVICE_IN_REMOTE_SUBMIX) {
+ device->rSubmixAddress = halDeviceAddress;
+ return OK;
+ }
+ device->busAddress = halDeviceAddress;
+ return NO_ERROR;
+}
+#endif // MAJOR_VERSION <= 6
+
} // namespace implementation
} // namespace CPP_VERSION
} // namespace common
diff --git a/audio/common/all-versions/default/HidlUtilsCommon.cpp b/audio/common/all-versions/default/HidlUtilsCommon.cpp
new file mode 100644
index 0000000..d2da193
--- /dev/null
+++ b/audio/common/all-versions/default/HidlUtilsCommon.cpp
@@ -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.
+ */
+
+#include "HidlUtils.h"
+
+namespace android {
+namespace hardware {
+namespace audio {
+namespace common {
+namespace CPP_VERSION {
+namespace implementation {
+
+status_t HidlUtils::audioPortConfigsFromHal(unsigned int numHalConfigs,
+ const struct audio_port_config* halConfigs,
+ hidl_vec<AudioPortConfig>* configs) {
+ status_t result = NO_ERROR;
+ configs->resize(numHalConfigs);
+ for (unsigned int i = 0; i < numHalConfigs; ++i) {
+ if (status_t status = audioPortConfigFromHal(halConfigs[i], &(*configs)[i]);
+ status != NO_ERROR) {
+ result = status;
+ }
+ }
+ return result;
+}
+
+status_t HidlUtils::audioPortConfigsToHal(const hidl_vec<AudioPortConfig>& configs,
+ std::unique_ptr<audio_port_config[]>* halConfigs) {
+ status_t result = NO_ERROR;
+ halConfigs->reset(new audio_port_config[configs.size()]);
+ for (size_t i = 0; i < configs.size(); ++i) {
+ if (status_t status = audioPortConfigToHal(configs[i], &(*halConfigs)[i]);
+ status != NO_ERROR) {
+ result = status;
+ }
+ }
+ return result;
+}
+
+} // namespace implementation
+} // namespace CPP_VERSION
+} // namespace common
+} // namespace audio
+} // namespace hardware
+} // namespace android
diff --git a/audio/common/all-versions/default/tests/hidlutils_tests.cpp b/audio/common/all-versions/default/tests/hidlutils_tests.cpp
index bfc99e6..fef88b4 100644
--- a/audio/common/all-versions/default/tests/hidlutils_tests.cpp
+++ b/audio/common/all-versions/default/tests/hidlutils_tests.cpp
@@ -28,6 +28,7 @@
#include <xsdc/XsdcSupport.h>
using namespace android;
+using ::android::hardware::hidl_vec;
using namespace ::android::hardware::audio::common::CPP_VERSION;
using ::android::hardware::audio::common::CPP_VERSION::implementation::HidlUtils;
namespace xsd {
@@ -36,6 +37,8 @@
static constexpr audio_channel_mask_t kInvalidHalChannelMask =
static_cast<audio_channel_mask_t>(0xFFFFFFFFU);
+static constexpr audio_content_type_t kInvalidHalContentType =
+ static_cast<audio_content_type_t>(0xFFFFFFFFU);
static constexpr audio_devices_t kInvalidHalDevice = static_cast<audio_devices_t>(0xFFFFFFFFU);
static constexpr audio_format_t kInvalidHalFormat = static_cast<audio_format_t>(0xFFFFFFFFU);
static constexpr audio_gain_mode_t kInvalidHalGainMode =
@@ -117,6 +120,34 @@
}
}
+TEST(HidlUtils, ConvertInvalidChannelMasksFromHal) {
+ std::vector<std::string> validAndInvalidChannelMasks = {
+ toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO), "random string", ""};
+ hidl_vec<AudioChannelMask> validChannelMask;
+ EXPECT_EQ(BAD_VALUE,
+ HidlUtils::audioChannelMasksFromHal(validAndInvalidChannelMasks, &validChannelMask));
+ EXPECT_EQ(1, validChannelMask.size());
+ EXPECT_EQ(validAndInvalidChannelMasks[0], validChannelMask[0]);
+
+ std::vector<std::string> invalidChannelMasks = {"random string", ""};
+ hidl_vec<AudioChannelMask> empty;
+ EXPECT_EQ(BAD_VALUE, HidlUtils::audioChannelMasksFromHal(invalidChannelMasks, &empty));
+ EXPECT_EQ(0, empty.size());
+}
+
+TEST(HidlUtils, ConvertChannelMasksFromHal) {
+ std::vector<std::string> allHalChannelMasks;
+ for (const auto enumVal : xsdc_enum_range<xsd::AudioChannelMask>{}) {
+ allHalChannelMasks.push_back(toString(enumVal));
+ }
+ hidl_vec<AudioChannelMask> allChannelMasks;
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioChannelMasksFromHal(allHalChannelMasks, &allChannelMasks));
+ EXPECT_EQ(allHalChannelMasks.size(), allChannelMasks.size());
+ for (size_t i = 0; i < allHalChannelMasks.size(); ++i) {
+ EXPECT_EQ(allHalChannelMasks[i], allChannelMasks[i]);
+ }
+}
+
TEST(HidlUtils, ConvertInvalidConfigBase) {
AudioConfigBase invalid;
EXPECT_EQ(BAD_VALUE, HidlUtils::audioConfigBaseFromHal({.sample_rate = 0,
@@ -147,6 +178,26 @@
EXPECT_EQ(configBase, configBaseBack);
}
+TEST(HidlUtils, ConvertInvalidContentType) {
+ AudioContentType invalid;
+ EXPECT_EQ(BAD_VALUE, HidlUtils::audioContentTypeFromHal(kInvalidHalContentType, &invalid));
+ audio_content_type_t halInvalid;
+ EXPECT_EQ(BAD_VALUE, HidlUtils::audioContentTypeToHal("random string", &halInvalid));
+}
+
+TEST(HidlUtils, ConvertContentType) {
+ for (const auto enumVal : xsdc_enum_range<xsd::AudioContentType>{}) {
+ const AudioContentType contentType = toString(enumVal);
+ audio_content_type_t halContentType;
+ AudioContentType contentTypeBack;
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioContentTypeToHal(contentType, &halContentType))
+ << "Conversion of \"" << contentType << "\" failed";
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioContentTypeFromHal(halContentType, &contentTypeBack))
+ << "Conversion of content type " << halContentType << " failed";
+ EXPECT_EQ(contentType, contentTypeBack);
+ }
+}
+
TEST(HidlUtils, ConvertInvalidDeviceType) {
AudioDevice invalid;
EXPECT_EQ(BAD_VALUE, HidlUtils::audioDeviceTypeFromHal(kInvalidHalDevice, &invalid));
@@ -314,6 +365,33 @@
}
}
+TEST(HidlUtils, ConvertInvalidFormatsFromHal) {
+ std::vector<std::string> validAndInvalidFormats = {
+ toString(xsd::AudioFormat::AUDIO_FORMAT_PCM_16_BIT), "random string", ""};
+ hidl_vec<AudioFormat> validFormat;
+ EXPECT_EQ(BAD_VALUE, HidlUtils::audioFormatsFromHal(validAndInvalidFormats, &validFormat));
+ EXPECT_EQ(1, validFormat.size());
+ EXPECT_EQ(validAndInvalidFormats[0], validFormat[0]);
+
+ std::vector<std::string> invalidFormats = {"random string", ""};
+ hidl_vec<AudioFormat> empty;
+ EXPECT_EQ(BAD_VALUE, HidlUtils::audioFormatsFromHal(invalidFormats, &empty));
+ EXPECT_EQ(0, empty.size());
+}
+
+TEST(HidlUtils, ConvertFormatsFromHal) {
+ std::vector<std::string> allHalFormats;
+ for (const auto enumVal : xsdc_enum_range<xsd::AudioFormat>{}) {
+ allHalFormats.push_back(toString(enumVal));
+ }
+ hidl_vec<AudioFormat> allFormats;
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioFormatsFromHal(allHalFormats, &allFormats));
+ EXPECT_EQ(allHalFormats.size(), allFormats.size());
+ for (size_t i = 0; i < allHalFormats.size(); ++i) {
+ EXPECT_EQ(allHalFormats[i], allFormats[i]);
+ }
+}
+
TEST(HidlUtils, ConvertInvalidGainModeMask) {
hidl_vec<AudioGainMode> invalid;
EXPECT_EQ(BAD_VALUE, HidlUtils::audioGainModeMaskFromHal(kInvalidHalGainMode, &invalid));
@@ -511,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;
@@ -629,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/Android.bp b/audio/core/all-versions/default/Android.bp
index 6be0628..e0f0860 100644
--- a/audio/core/all-versions/default/Android.bp
+++ b/audio/core/all-versions/default/Android.bp
@@ -125,13 +125,15 @@
}
cc_library_shared {
- enabled: false,
name: "android.hardware.audio@7.0-impl",
defaults: ["android.hardware.audio-impl_default"],
shared_libs: [
"android.hardware.audio@7.0",
"android.hardware.audio.common@7.0",
+ "android.hardware.audio.common@7.0-enums",
"android.hardware.audio.common@7.0-util",
+ "libbase",
+ "libxml2",
],
cflags: [
"-DMAJOR_VERSION=7",
diff --git a/audio/core/all-versions/default/Conversions.cpp b/audio/core/all-versions/default/Conversions.cpp
index 28d8f78..f1752cc 100644
--- a/audio/core/all-versions/default/Conversions.cpp
+++ b/audio/core/all-versions/default/Conversions.cpp
@@ -18,8 +18,11 @@
#include <stdio.h>
+#if MAJOR_VERSION >= 7
+#include <android_audio_policy_configuration_V7_0-enums.h>
+#endif
+#include <HidlUtils.h>
#include <log/log.h>
-#include <media/AudioContainers.h>
namespace android {
namespace hardware {
@@ -27,73 +30,32 @@
namespace CPP_VERSION {
namespace implementation {
-// TODO(mnaganov): Use method from HidlUtils for V7
-std::string deviceAddressToHal(const DeviceAddress& address) {
- // HAL assumes that the address is NUL-terminated.
- char halAddress[AUDIO_DEVICE_MAX_ADDRESS_LEN];
- memset(halAddress, 0, sizeof(halAddress));
- audio_devices_t halDevice = static_cast<audio_devices_t>(address.device);
- if (getAudioDeviceOutAllA2dpSet().count(halDevice) > 0 ||
- halDevice == AUDIO_DEVICE_IN_BLUETOOTH_A2DP) {
- snprintf(halAddress, sizeof(halAddress), "%02X:%02X:%02X:%02X:%02X:%02X",
- address.address.mac[0], address.address.mac[1], address.address.mac[2],
- address.address.mac[3], address.address.mac[4], address.address.mac[5]);
- } else if (halDevice == AUDIO_DEVICE_OUT_IP || halDevice == AUDIO_DEVICE_IN_IP) {
- snprintf(halAddress, sizeof(halAddress), "%d.%d.%d.%d", address.address.ipv4[0],
- address.address.ipv4[1], address.address.ipv4[2], address.address.ipv4[3]);
- } else if (getAudioDeviceOutAllUsbSet().count(halDevice) > 0 ||
- getAudioDeviceInAllUsbSet().count(halDevice) > 0) {
- snprintf(halAddress, sizeof(halAddress), "card=%d;device=%d", address.address.alsa.card,
- address.address.alsa.device);
- } else if (halDevice == AUDIO_DEVICE_OUT_BUS || halDevice == AUDIO_DEVICE_IN_BUS) {
- snprintf(halAddress, sizeof(halAddress), "%s", address.busAddress.c_str());
- } else if (halDevice == AUDIO_DEVICE_OUT_REMOTE_SUBMIX ||
- halDevice == AUDIO_DEVICE_IN_REMOTE_SUBMIX) {
- snprintf(halAddress, sizeof(halAddress), "%s", address.rSubmixAddress.c_str());
+using ::android::hardware::audio::common::CPP_VERSION::implementation::HidlUtils;
+
+#define CONVERT_CHECKED(expr, result) \
+ if (status_t status = (expr); status != NO_ERROR) { \
+ result = status; \
}
- return halAddress;
+
+status_t deviceAddressToHal(const DeviceAddress& device, audio_devices_t* halDeviceType,
+ char* halDeviceAddress) {
+#if MAJOR_VERSION >= 5
+ return HidlUtils::deviceAddressToHal(device, halDeviceType, halDeviceAddress);
+#else
+ return HidlUtils::deviceAddressToHalImpl(device, halDeviceType, halDeviceAddress);
+#endif
+}
+
+status_t deviceAddressFromHal(audio_devices_t halDeviceType, const char* halDeviceAddress,
+ DeviceAddress* device) {
+#if MAJOR_VERSION >= 5
+ return HidlUtils::deviceAddressFromHal(halDeviceType, halDeviceAddress, device);
+#else
+ return HidlUtils::deviceAddressFromHalImpl(halDeviceType, halDeviceAddress, device);
+#endif
}
#if MAJOR_VERSION >= 4
-status_t deviceAddressFromHal(audio_devices_t device, const char* halAddress,
- DeviceAddress* address) {
- if (address == nullptr) {
- return BAD_VALUE;
- }
- address->device = AudioDevice(device);
- if (halAddress == nullptr || strnlen(halAddress, AUDIO_DEVICE_MAX_ADDRESS_LEN) == 0) {
- return OK;
- }
-
- if (getAudioDeviceOutAllA2dpSet().count(device) > 0 ||
- device == AUDIO_DEVICE_IN_BLUETOOTH_A2DP) {
- int status =
- sscanf(halAddress, "%hhX:%hhX:%hhX:%hhX:%hhX:%hhX", &address->address.mac[0],
- &address->address.mac[1], &address->address.mac[2], &address->address.mac[3],
- &address->address.mac[4], &address->address.mac[5]);
- return status == 6 ? OK : BAD_VALUE;
- } else if (device == AUDIO_DEVICE_OUT_IP || device == AUDIO_DEVICE_IN_IP) {
- int status =
- sscanf(halAddress, "%hhu.%hhu.%hhu.%hhu", &address->address.ipv4[0],
- &address->address.ipv4[1], &address->address.ipv4[2], &address->address.ipv4[3]);
- return status == 4 ? OK : BAD_VALUE;
- } else if (getAudioDeviceOutAllUsbSet().count(device) > 0 ||
- getAudioDeviceInAllUsbSet().count(device) > 0) {
- int status = sscanf(halAddress, "card=%d;device=%d", &address->address.alsa.card,
- &address->address.alsa.device);
- return status == 2 ? OK : BAD_VALUE;
- } else if (device == AUDIO_DEVICE_OUT_BUS || device == AUDIO_DEVICE_IN_BUS) {
- address->busAddress = halAddress;
- return OK;
- } else if (device == AUDIO_DEVICE_OUT_REMOTE_SUBMIX ||
- device == AUDIO_DEVICE_IN_REMOTE_SUBMIX) {
- address->rSubmixAddress = halAddress;
- return OK;
- }
- address->busAddress = halAddress;
- return OK;
-}
-
bool halToMicrophoneCharacteristics(MicrophoneInfo* pDst,
const struct audio_microphone_characteristic_t& src) {
bool status = false;
@@ -131,6 +93,134 @@
}
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
+namespace xsd {
+using namespace ::android::audio::policy::configuration::V7_0;
+}
+
+bool audioInputFlagsToHal(const hidl_vec<AudioInOutFlag>& flags, audio_input_flags_t* halFlags) {
+ bool success = true;
+ *halFlags = {};
+ for (const auto& flag : flags) {
+ audio_input_flags_t halFlag;
+ if (!xsd::isUnknownAudioInOutFlag(flag) &&
+ audio_input_flag_from_string(flag.c_str(), &halFlag)) {
+ *halFlags = static_cast<audio_input_flags_t>(*halFlags | halFlag);
+ } else {
+ ALOGE("Unknown audio input flag \"%s\"", flag.c_str());
+ success = false;
+ }
+ }
+ return success;
+}
+
+bool audioOutputFlagsToHal(const hidl_vec<AudioInOutFlag>& flags, audio_output_flags_t* halFlags) {
+ bool success = true;
+ *halFlags = {};
+ for (const auto& flag : flags) {
+ audio_output_flags_t halFlag;
+ if (!xsd::isUnknownAudioInOutFlag(flag) &&
+ audio_output_flag_from_string(flag.c_str(), &halFlag)) {
+ *halFlags = static_cast<audio_output_flags_t>(*halFlags | halFlag);
+ } else {
+ ALOGE("Unknown audio output flag \"%s\"", flag.c_str());
+ success = false;
+ }
+ }
+ return success;
+}
+
+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 3c28159..7caed44 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();
@@ -150,63 +151,80 @@
std::tuple<Result, sp<IStreamOut>> Device::openOutputStreamImpl(int32_t ioHandle,
const DeviceAddress& device,
const AudioConfig& config,
- AudioOutputFlagBitfield flags,
+ 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;
- ALOGV(
- "open_output_stream handle: %d devices: %x flags: %#x "
- "srate: %d format %#x channels %x address %s",
- ioHandle, static_cast<audio_devices_t>(device.device),
- static_cast<audio_output_flags_t>(flags), halConfig.sample_rate, halConfig.format,
- halConfig.channel_mask, deviceAddressToHal(device).c_str());
- int status =
- mDevice->open_output_stream(mDevice, ioHandle, static_cast<audio_devices_t>(device.device),
- static_cast<audio_output_flags_t>(flags), &halConfig,
- &halStream, deviceAddressToHal(device).c_str());
+ audio_devices_t halDevice;
+ char halDeviceAddress[AUDIO_DEVICE_MAX_ADDRESS_LEN];
+ if (deviceAddressToHal(device, &halDevice, halDeviceAddress) != NO_ERROR) {
+ return {Result::INVALID_ARGUMENTS, nullptr};
+ }
+ audio_output_flags_t halFlags;
+ if (!audioOutputFlagsToHal(flags, &halFlags)) {
+ return {Result::INVALID_ARGUMENTS, nullptr};
+ }
+ ALOGV("open_output_stream handle: %d devices: %x flags: %#x "
+ "srate: %d format %#x channels %x address %s",
+ ioHandle, halDevice, halFlags, halConfig.sample_rate, halConfig.format,
+ halConfig.channel_mask, halDeviceAddress);
+ int status = mDevice->open_output_stream(mDevice, ioHandle, halDevice, halFlags, &halConfig,
+ &halStream, halDeviceAddress);
ALOGV("open_output_stream status %d stream %p", status, halStream);
sp<IStreamOut> streamOut;
if (status == OK) {
streamOut = new StreamOut(this, halStream);
++mOpenedStreamsCount;
}
- status_t convertStatus = HidlUtils::audioConfigFromHal(halConfig, suggestedConfig);
+ status_t convertStatus =
+ HidlUtils::audioConfigFromHal(halConfig, false /*isInput*/, suggestedConfig);
ALOGW_IF(convertStatus != OK, "%s: suggested config with incompatible fields", __func__);
return {analyzeStatus("open_output_stream", status, {EINVAL} /*ignore*/), streamOut};
}
std::tuple<Result, sp<IStreamIn>> Device::openInputStreamImpl(
- int32_t ioHandle, const DeviceAddress& device, const AudioConfig& config,
- AudioInputFlagBitfield flags, AudioSource source, AudioConfig* suggestedConfig) {
+ 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;
- ALOGV(
- "open_input_stream handle: %d devices: %x flags: %#x "
- "srate: %d format %#x channels %x address %s source %d",
- ioHandle, static_cast<audio_devices_t>(device.device),
- static_cast<audio_input_flags_t>(flags), halConfig.sample_rate, halConfig.format,
- halConfig.channel_mask, deviceAddressToHal(device).c_str(),
- static_cast<audio_source_t>(source));
- int status = mDevice->open_input_stream(
- mDevice, ioHandle, static_cast<audio_devices_t>(device.device), &halConfig, &halStream,
- static_cast<audio_input_flags_t>(flags), deviceAddressToHal(device).c_str(),
- static_cast<audio_source_t>(source));
+ audio_devices_t halDevice;
+ char halDeviceAddress[AUDIO_DEVICE_MAX_ADDRESS_LEN];
+ if (deviceAddressToHal(device, &halDevice, halDeviceAddress) != NO_ERROR) {
+ return {Result::INVALID_ARGUMENTS, nullptr};
+ }
+ audio_input_flags_t halFlags;
+ audio_source_t halSource;
+ if (!audioInputFlagsToHal(flags, &halFlags) ||
+ HidlUtils::audioSourceToHal(source, &halSource) != NO_ERROR) {
+ return {Result::INVALID_ARGUMENTS, nullptr};
+ }
+ ALOGV("open_input_stream handle: %d devices: %x flags: %#x "
+ "srate: %d format %#x channels %x address %s source %d",
+ ioHandle, halDevice, halFlags, halConfig.sample_rate, halConfig.format,
+ halConfig.channel_mask, halDeviceAddress, halSource);
+ int status = mDevice->open_input_stream(mDevice, ioHandle, halDevice, &halConfig, &halStream,
+ halFlags, halDeviceAddress, halSource);
ALOGV("open_input_stream status %d stream %p", status, halStream);
sp<IStreamIn> streamIn;
if (status == OK) {
streamIn = new StreamIn(this, halStream);
++mOpenedStreamsCount;
}
- status_t convertStatus = HidlUtils::audioConfigFromHal(halConfig, suggestedConfig);
+ status_t convertStatus =
+ HidlUtils::audioConfigFromHal(halConfig, true /*isInput*/, suggestedConfig);
ALOGW_IF(convertStatus != OK, "%s: suggested config with incompatible fields", __func__);
return {analyzeStatus("open_input_stream", status, {EINVAL} /*ignore*/), streamIn};
}
#if MAJOR_VERSION == 2
Return<void> Device::openOutputStream(int32_t ioHandle, const DeviceAddress& device,
- const AudioConfig& config, AudioOutputFlagBitfield flags,
+ const AudioConfig& config, AudioOutputFlags flags,
openOutputStream_cb _hidl_cb) {
AudioConfig suggestedConfig;
auto [result, streamOut] =
@@ -216,7 +234,7 @@
}
Return<void> Device::openInputStream(int32_t ioHandle, const DeviceAddress& device,
- const AudioConfig& config, AudioInputFlagBitfield flags,
+ const AudioConfig& config, AudioInputFlags flags,
AudioSource source, openInputStream_cb _hidl_cb) {
AudioConfig suggestedConfig;
auto [result, streamIn] =
@@ -227,9 +245,22 @@
#elif MAJOR_VERSION >= 4
Return<void> Device::openOutputStream(int32_t ioHandle, const DeviceAddress& device,
- const AudioConfig& config, AudioOutputFlagBitfield flags,
+ const AudioConfig& config,
+#if MAJOR_VERSION <= 6
+ AudioOutputFlags flags,
+#else
+ const AudioOutputFlags& flags,
+#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);
@@ -241,14 +272,27 @@
}
Return<void> Device::openInputStream(int32_t ioHandle, const DeviceAddress& device,
- const AudioConfig& config, AudioInputFlagBitfield flags,
+ const AudioConfig& config,
+#if MAJOR_VERSION <= 6
+ AudioInputFlags flags,
+#else
+ const AudioInputFlags& flags,
+#endif
const SinkMetadata& sinkMetadata,
openInputStream_cb _hidl_cb) {
if (sinkMetadata.tracks.size() == 0) {
// 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.
@@ -271,9 +315,7 @@
Return<void> Device::createAudioPatch(const hidl_vec<AudioPortConfig>& sources,
const hidl_vec<AudioPortConfig>& sinks,
createAudioPatch_cb _hidl_cb) {
- auto [retval, patch] = createOrUpdateAudioPatch(
- static_cast<AudioPatchHandle>(AudioHandleConsts::AUDIO_PATCH_HANDLE_NONE), sources,
- sinks);
+ auto [retval, patch] = createOrUpdateAudioPatch(AudioPatchHandle{}, sources, sinks);
_hidl_cb(retval, patch);
return Void();
}
@@ -283,11 +325,17 @@
const hidl_vec<AudioPortConfig>& sinks) {
Result retval(Result::NOT_SUPPORTED);
if (version() >= AUDIO_DEVICE_API_VERSION_3_0) {
- std::unique_ptr<audio_port_config[]> halSources;
- HidlUtils::audioPortConfigsToHal(sources, &halSources);
- std::unique_ptr<audio_port_config[]> halSinks;
- HidlUtils::audioPortConfigsToHal(sinks, &halSinks);
audio_patch_handle_t halPatch = static_cast<audio_patch_handle_t>(patch);
+ std::unique_ptr<audio_port_config[]> halSources;
+ if (status_t status = HidlUtils::audioPortConfigsToHal(sources, &halSources);
+ status != NO_ERROR) {
+ return {analyzeStatus("audioPortConfigsToHal;sources", status), patch};
+ }
+ std::unique_ptr<audio_port_config[]> halSinks;
+ if (status_t status = HidlUtils::audioPortConfigsToHal(sinks, &halSinks);
+ status != NO_ERROR) {
+ return {analyzeStatus("audioPortConfigsToHal;sinks", status), patch};
+ }
retval = analyzeStatus("create_audio_patch",
mDevice->create_audio_patch(mDevice, sources.size(), &halSources[0],
sinks.size(), &halSinks[0], &halPatch));
@@ -322,7 +370,10 @@
Return<Result> Device::setAudioPortConfig(const AudioPortConfig& config) {
if (version() >= AUDIO_DEVICE_API_VERSION_3_0) {
struct audio_port_config halPortConfig;
- HidlUtils::audioPortConfigToHal(config, &halPortConfig);
+ if (status_t status = HidlUtils::audioPortConfigToHal(config, &halPortConfig);
+ status != NO_ERROR) {
+ return analyzeStatus("audioPortConfigToHal", status);
+ }
return analyzeStatus("set_audio_port_config",
mDevice->set_audio_port_config(mDevice, &halPortConfig));
}
@@ -454,7 +505,7 @@
const hidl_vec<AudioPortConfig>& sources,
const hidl_vec<AudioPortConfig>& sinks,
createAudioPatch_cb _hidl_cb) {
- if (previousPatch != static_cast<int32_t>(AudioHandleConsts::AUDIO_PATCH_HANDLE_NONE)) {
+ if (previousPatch != static_cast<int32_t>(AudioPatchHandle{})) {
auto [retval, patch] = createOrUpdateAudioPatch(previousPatch, sources, sinks);
_hidl_cb(retval, patch);
} else {
diff --git a/audio/core/all-versions/default/ParametersUtil.cpp b/audio/core/all-versions/default/ParametersUtil.cpp
index 0c8e28a..694eb73 100644
--- a/audio/core/all-versions/default/ParametersUtil.cpp
+++ b/audio/core/all-versions/default/ParametersUtil.cpp
@@ -149,9 +149,15 @@
}
return setParams(params);
}
+
Result ParametersUtil::setParam(const char* name, const DeviceAddress& address) {
- AudioParameter params(String8(deviceAddressToHal(address).c_str()));
- params.addInt(String8(name), int(address.device));
+ audio_devices_t halDeviceType;
+ char halDeviceAddress[AUDIO_DEVICE_MAX_ADDRESS_LEN];
+ if (deviceAddressToHal(address, &halDeviceType, halDeviceAddress) != NO_ERROR) {
+ return Result::INVALID_ARGUMENTS;
+ }
+ AudioParameter params{String8(halDeviceAddress)};
+ params.addInt(String8(name), halDeviceType);
return setParams(params);
}
diff --git a/audio/core/all-versions/default/PrimaryDevice.cpp b/audio/core/all-versions/default/PrimaryDevice.cpp
index 11c1c5a..fe56177 100644
--- a/audio/core/all-versions/default/PrimaryDevice.cpp
+++ b/audio/core/all-versions/default/PrimaryDevice.cpp
@@ -73,28 +73,36 @@
#if MAJOR_VERSION == 2
Return<void> PrimaryDevice::openOutputStream(int32_t ioHandle, const DeviceAddress& device,
- const AudioConfig& config,
- AudioOutputFlagBitfield flags,
+ const AudioConfig& config, AudioOutputFlags flags,
openOutputStream_cb _hidl_cb) {
return mDevice->openOutputStream(ioHandle, device, config, flags, _hidl_cb);
}
Return<void> PrimaryDevice::openInputStream(int32_t ioHandle, const DeviceAddress& device,
- const AudioConfig& config, AudioInputFlagBitfield flags,
+ const AudioConfig& config, AudioInputFlags flags,
AudioSource source, openInputStream_cb _hidl_cb) {
return mDevice->openInputStream(ioHandle, device, config, flags, source, _hidl_cb);
}
#elif MAJOR_VERSION >= 4
Return<void> PrimaryDevice::openOutputStream(int32_t ioHandle, const DeviceAddress& device,
const AudioConfig& config,
- AudioOutputFlagBitfield flags,
+#if MAJOR_VERSION <= 6
+ AudioOutputFlags flags,
+#else
+ const AudioOutputFlags& flags,
+#endif
const SourceMetadata& sourceMetadata,
openOutputStream_cb _hidl_cb) {
return mDevice->openOutputStream(ioHandle, device, config, flags, sourceMetadata, _hidl_cb);
}
Return<void> PrimaryDevice::openInputStream(int32_t ioHandle, const DeviceAddress& device,
- const AudioConfig& config, AudioInputFlagBitfield flags,
+ const AudioConfig& config,
+#if MAJOR_VERSION <= 6
+ AudioInputFlags flags,
+#else
+ const AudioInputFlags& flags,
+#endif
const SinkMetadata& sinkMetadata,
openInputStream_cb _hidl_cb) {
return mDevice->openInputStream(ioHandle, device, config, flags, sinkMetadata, _hidl_cb);
diff --git a/audio/core/all-versions/default/Stream.cpp b/audio/core/all-versions/default/Stream.cpp
index 74e5945..f964cbb 100644
--- a/audio/core/all-versions/default/Stream.cpp
+++ b/audio/core/all-versions/default/Stream.cpp
@@ -17,12 +17,14 @@
#define LOG_TAG "StreamHAL"
#include "core/default/Stream.h"
+#include "common/all-versions/HidlSupport.h"
#include "common/all-versions/default/EffectMap.h"
#include "core/default/Conversions.h"
#include "core/default/Util.h"
#include <inttypes.h>
+#include <HidlUtils.h>
#include <android/log.h>
#include <hardware/audio.h>
#include <hardware/audio_effect.h>
@@ -35,7 +37,12 @@
namespace CPP_VERSION {
namespace implementation {
-Stream::Stream(audio_stream_t* stream) : mStream(stream) {}
+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.
+}
Stream::~Stream() {
mStream = nullptr;
@@ -78,6 +85,7 @@
return mStream->get_buffer_size(mStream);
}
+#if MAJOR_VERSION <= 6
Return<uint32_t> Stream::getSampleRate() {
return mStream->get_sample_rate(mStream);
}
@@ -201,6 +209,96 @@
return Void();
}
+#else // MAJOR_VERSION <= 6
+
+Return<void> Stream::getSupportedProfiles(getSupportedProfiles_cb _hidl_cb) {
+ String8 halListValue;
+ Result result = getParam(AudioParameter::keyStreamSupportedFormats, &halListValue);
+ hidl_vec<AudioProfile> profiles;
+ if (result != Result::OK) {
+ _hidl_cb(result, profiles);
+ return Void();
+ }
+ // 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 =
+ splitString(halListValue.string(), AUDIO_PARAMETER_VALUE_LIST_SEPARATOR[0]);
+ hidl_vec<AudioFormat> formats;
+ (void)HidlUtils::audioFormatsFromHal(halFormats, &formats);
+ std::vector<AudioProfile> tempProfiles;
+ for (const auto& format : formats) {
+ audio_format_t halFormat;
+ if (status_t status = HidlUtils::audioFormatToHal(format, &halFormat); status != NO_ERROR) {
+ continue;
+ }
+ AudioParameter context;
+ context.addInt(String8(AUDIO_PARAMETER_STREAM_FORMAT), int(halFormat));
+ // Query supported sample rates for the format.
+ result = getParam(AudioParameter::keyStreamSupportedSamplingRates, &halListValue, context);
+ if (result != Result::OK) break;
+ std::vector<std::string> halSampleRates =
+ 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) {
+ sampleRates[i] = std::stoi(halSampleRates[i]);
+ }
+ // Query supported channel masks for the format.
+ result = getParam(AudioParameter::keyStreamSupportedChannels, &halListValue, context);
+ if (result != Result::OK) break;
+ std::vector<std::string> halChannelMasks =
+ splitString(halListValue.string(), AUDIO_PARAMETER_VALUE_LIST_SEPARATOR[0]);
+ hidl_vec<AudioChannelMask> channelMasks;
+ (void)HidlUtils::audioChannelMasksFromHal(halChannelMasks, &channelMasks);
+ // Create a profile.
+ if (channelMasks.size() != 0 && sampleRates.size() != 0) {
+ tempProfiles.push_back({.format = format,
+ .sampleRates = std::move(sampleRates),
+ .channelMasks = std::move(channelMasks)});
+ }
+ }
+ // Legacy get_parameter does not return a status_t, thus can not advertise of failure.
+ // Note that the method must not return an empty list if this capability is supported.
+ if (!tempProfiles.empty()) {
+ profiles = tempProfiles;
+ } else {
+ result = Result::NOT_SUPPORTED;
+ }
+ _hidl_cb(result, profiles);
+ return Void();
+}
+
+Return<void> Stream::getAudioProperties(getAudioProperties_cb _hidl_cb) {
+ audio_config_base_t halConfigBase = {mStream->get_sample_rate(mStream),
+ mStream->get_channels(mStream),
+ mStream->get_format(mStream)};
+ AudioConfigBase configBase = {};
+ status_t status = HidlUtils::audioConfigBaseFromHal(halConfigBase, mIsInput, &configBase);
+ _hidl_cb(Stream::analyzeStatus("get_audio_properties", status), configBase);
+ return Void();
+}
+
+Return<Result> Stream::setAudioProperties(const AudioConfigBase& config) {
+ audio_config_base_t halConfigBase = {};
+ status_t status = HidlUtils::audioConfigBaseToHal(config, &halConfigBase);
+ if (status != NO_ERROR) {
+ return Stream::analyzeStatus("set_audio_properties", status);
+ }
+ if (Result result = setParam(AudioParameter::keySamplingRate,
+ static_cast<int>(halConfigBase.sample_rate));
+ result != Result::OK) {
+ return result;
+ }
+ if (Result result =
+ setParam(AudioParameter::keyChannels, static_cast<int>(halConfigBase.channel_mask));
+ result != Result::OK) {
+ return result;
+ }
+ return setParam(AudioParameter::keyFormat, static_cast<int>(halConfigBase.format));
+}
+
+#endif // MAJOR_VERSION <= 6
+
Return<Result> Stream::addEffect(uint64_t effectId) {
effect_handle_t halEffect = EffectMap::getInstance().get(effectId);
if (halEffect != NULL) {
@@ -257,12 +355,14 @@
}
#elif MAJOR_VERSION >= 4
Return<void> Stream::getDevices(getDevices_cb _hidl_cb) {
- int device = 0;
- Result retval = getParam(AudioParameter::keyRouting, &device);
+ int halDevice = 0;
+ Result retval = getParam(AudioParameter::keyRouting, &halDevice);
hidl_vec<DeviceAddress> devices;
if (retval == Result::OK) {
devices.resize(1);
- devices[0].device = static_cast<AudioDevice>(device);
+ retval = Stream::analyzeStatus("get_devices",
+ deviceAddressFromHal(static_cast<audio_devices_t>(halDevice),
+ nullptr, &devices[0]));
}
_hidl_cb(retval, devices);
return Void();
@@ -273,14 +373,13 @@
if (devices.size() > 1) {
return Result::NOT_SUPPORTED;
}
- DeviceAddress address;
+ DeviceAddress address{};
if (devices.size() == 1) {
address = devices[0];
- } else {
- address.device = AudioDevice::NONE;
}
return setParam(AudioParameter::keyRouting, address);
}
+
Return<void> Stream::getParameters(const hidl_vec<ParameterValue>& context,
const hidl_vec<hidl_string>& keys, getParameters_cb _hidl_cb) {
getParametersImpl(context, keys, _hidl_cb);
diff --git a/audio/core/all-versions/default/StreamIn.cpp b/audio/core/all-versions/default/StreamIn.cpp
index f1152ca..2c5e9f1 100644
--- a/audio/core/all-versions/default/StreamIn.cpp
+++ b/audio/core/all-versions/default/StreamIn.cpp
@@ -24,11 +24,12 @@
//#define LOG_NDEBUG 0
#define ATRACE_TAG ATRACE_TAG_AUDIO
+#include <HidlUtils.h>
#include <android/log.h>
#include <hardware/audio.h>
#include <utils/Trace.h>
-#include <memory>
#include <cmath>
+#include <memory>
namespace android {
namespace hardware {
@@ -36,6 +37,8 @@
namespace CPP_VERSION {
namespace implementation {
+using ::android::hardware::audio::common::CPP_VERSION::implementation::HidlUtils;
+
namespace {
class ReadThread : public Thread {
@@ -141,7 +144,7 @@
StreamIn::StreamIn(const sp<Device>& device, audio_stream_in_t* stream)
: mDevice(device),
mStream(stream),
- mStreamCommon(new Stream(&stream->common)),
+ mStreamCommon(new Stream(true /*isInput*/, &stream->common)),
mStreamMmap(new StreamMmap<audio_stream_in_t>(stream)),
mEfGroup(nullptr),
mStopReadThread(false) {}
@@ -177,6 +180,7 @@
return mStreamCommon->getBufferSize();
}
+#if MAJOR_VERSION <= 6
Return<uint32_t> StreamIn::getSampleRate() {
return mStreamCommon->getSampleRate();
}
@@ -223,6 +227,18 @@
return mStreamCommon->setFormat(format);
}
+#else
+
+Return<void> StreamIn::getSupportedProfiles(getSupportedProfiles_cb _hidl_cb) {
+ return mStreamCommon->getSupportedProfiles(_hidl_cb);
+}
+
+Return<Result> StreamIn::setAudioProperties(const AudioConfigBase& config) {
+ return mStreamCommon->setAudioProperties(config);
+}
+
+#endif // MAJOR_VERSION <= 6
+
Return<void> StreamIn::getAudioProperties(getAudioProperties_cb _hidl_cb) {
return mStreamCommon->getAudioProperties(_hidl_cb);
}
@@ -321,9 +337,11 @@
Return<void> StreamIn::getAudioSource(getAudioSource_cb _hidl_cb) {
int halSource;
Result retval = mStreamCommon->getParam(AudioParameter::keyInputSource, &halSource);
- AudioSource source(AudioSource::DEFAULT);
+ AudioSource source = {};
if (retval == Result::OK) {
- source = AudioSource(halSource);
+ retval = Stream::analyzeStatus(
+ "get_audio_source",
+ HidlUtils::audioSourceFromHal(static_cast<audio_source_t>(halSource), &source));
}
_hidl_cb(retval, source);
return Void();
@@ -340,7 +358,11 @@
Return<void> StreamIn::prepareForReading(uint32_t frameSize, uint32_t framesCount,
prepareForReading_cb _hidl_cb) {
status_t status;
+#if MAJOR_VERSION <= 6
ThreadInfo threadInfo = {0, 0};
+#else
+ int32_t threadInfo = 0;
+#endif
// Wrap the _hidl_cb to return an error
auto sendError = [&threadInfo, &_hidl_cb](Result result) {
@@ -410,8 +432,12 @@
mStatusMQ = std::move(tempStatusMQ);
mReadThread = tempReadThread.release();
mEfGroup = tempElfGroup.release();
+#if MAJOR_VERSION <= 6
threadInfo.pid = getpid();
threadInfo.tid = mReadThread->getTid();
+#else
+ threadInfo = mReadThread->getTid();
+#endif
_hidl_cb(Result::OK, *mCommandMQ->getDesc(), *mDataMQ->getDesc(), *mStatusMQ->getDesc(),
threadInfo);
return Void();
@@ -452,34 +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 = {
- .source = static_cast<audio_source_t>(metadata.source), .gain = metadata.gain};
-#if MAJOR_VERSION >= 5
- if (metadata.destination.getDiscriminator() ==
- RecordTrackMetadata::Destination::hidl_discriminator::device) {
- halTrackMetadata.dest_device =
- static_cast<audio_devices_t>(metadata.destination.device().device);
- strncpy(halTrackMetadata.dest_device_address,
- deviceAddressToHal(metadata.destination.device()).c_str(),
- AUDIO_DEVICE_MAX_ADDRESS_LEN);
+#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 007eb45..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
@@ -26,6 +27,7 @@
#include <memory>
+#include <HidlUtils.h>
#include <android/log.h>
#include <hardware/audio.h>
#include <utils/Trace.h>
@@ -36,6 +38,8 @@
namespace CPP_VERSION {
namespace implementation {
+using ::android::hardware::audio::common::CPP_VERSION::implementation::HidlUtils;
+
namespace {
class WriteThread : public Thread {
@@ -142,7 +146,7 @@
StreamOut::StreamOut(const sp<Device>& device, audio_stream_out_t* stream)
: mDevice(device),
mStream(stream),
- mStreamCommon(new Stream(&stream->common)),
+ mStreamCommon(new Stream(false /*isInput*/, &stream->common)),
mStreamMmap(new StreamMmap<audio_stream_out_t>(stream)),
mEfGroup(nullptr),
mStopWriteThread(false) {}
@@ -182,6 +186,7 @@
return mStreamCommon->getBufferSize();
}
+#if MAJOR_VERSION <= 6
Return<uint32_t> StreamOut::getSampleRate() {
return mStreamCommon->getSampleRate();
}
@@ -228,6 +233,18 @@
return mStreamCommon->setFormat(format);
}
+#else
+
+Return<void> StreamOut::getSupportedProfiles(getSupportedProfiles_cb _hidl_cb) {
+ return mStreamCommon->getSupportedProfiles(_hidl_cb);
+}
+
+Return<Result> StreamOut::setAudioProperties(const AudioConfigBase& config) {
+ return mStreamCommon->setAudioProperties(config);
+}
+
+#endif // MAJOR_VERSION <= 6
+
Return<void> StreamOut::getAudioProperties(getAudioProperties_cb _hidl_cb) {
return mStreamCommon->getAudioProperties(_hidl_cb);
}
@@ -327,7 +344,11 @@
Return<void> StreamOut::prepareForWriting(uint32_t frameSize, uint32_t framesCount,
prepareForWriting_cb _hidl_cb) {
status_t status;
+#if MAJOR_VERSION <= 6
ThreadInfo threadInfo = {0, 0};
+#else
+ int32_t threadInfo = 0;
+#endif
// Wrap the _hidl_cb to return an error
auto sendError = [&threadInfo, &_hidl_cb](Result result) {
@@ -396,8 +417,12 @@
mStatusMQ = std::move(tempStatusMQ);
mWriteThread = tempWriteThread.release();
mEfGroup = tempElfGroup.release();
+#if MAJOR_VERSION <= 6
threadInfo.pid = getpid();
threadInfo.tid = mWriteThread->getTid();
+#else
+ threadInfo = mWriteThread->getTid();
+#endif
_hidl_cb(Result::OK, *mCommandMQ->getDesc(), *mDataMQ->getDesc(), *mStatusMQ->getDesc(),
threadInfo);
return Void();
@@ -561,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;
+#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);
}
- std::vector<playback_track_metadata> halTracks;
- halTracks.reserve(sourceMetadata.tracks.size());
- for (auto& metadata : sourceMetadata.tracks) {
- halTracks.push_back({
- .usage = static_cast<audio_usage_t>(metadata.usage),
- .content_type = static_cast<audio_content_type_t>(metadata.contentType),
- .gain = metadata.gain,
- });
- }
+#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 cb7914f..61720d5 100644
--- a/audio/core/all-versions/default/include/core/default/Conversions.h
+++ b/audio/core/all-versions/default/include/core/default/Conversions.h
@@ -23,20 +23,55 @@
#include <system/audio.h>
+#include <VersionUtils.h>
+
namespace android {
namespace hardware {
namespace audio {
namespace CPP_VERSION {
namespace implementation {
+using ::android::hardware::hidl_vec;
using namespace ::android::hardware::audio::common::CPP_VERSION;
using namespace ::android::hardware::audio::CPP_VERSION;
-std::string deviceAddressToHal(const DeviceAddress& address);
+status_t deviceAddressToHal(const DeviceAddress& device, audio_devices_t* halDeviceType,
+ char* halDeviceAddress);
+status_t deviceAddressFromHal(audio_devices_t halDeviceType, const char* halDeviceAddress,
+ DeviceAddress* device);
#if MAJOR_VERSION >= 4
bool halToMicrophoneCharacteristics(MicrophoneInfo* pDst,
const struct audio_microphone_characteristic_t& src);
+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
+using AudioInputFlags =
+ ::android::hardware::audio::common::CPP_VERSION::implementation::AudioInputFlagBitfield;
+using AudioOutputFlags =
+ ::android::hardware::audio::common::CPP_VERSION::implementation::AudioOutputFlagBitfield;
+
+inline bool audioInputFlagsToHal(AudioInputFlags flags, audio_input_flags_t* halFlags) {
+ *halFlags = static_cast<audio_input_flags_t>(flags);
+ return true;
+}
+
+inline bool audioOutputFlagsToHal(AudioOutputFlags flags, audio_output_flags_t* halFlags) {
+ *halFlags = static_cast<audio_output_flags_t>(flags);
+ return true;
+}
+#else
+bool audioInputFlagsToHal(const hidl_vec<AudioInOutFlag>& flags, audio_input_flags_t* halFlags);
+bool audioOutputFlagsToHal(const hidl_vec<AudioInOutFlag>& flags, audio_output_flags_t* halFlags);
+// 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 907acd7..2a4d226 100644
--- a/audio/core/all-versions/default/include/core/default/Device.h
+++ b/audio/core/all-versions/default/include/core/default/Device.h
@@ -44,8 +44,13 @@
using ::android::hardware::Return;
using ::android::hardware::Void;
#if MAJOR_VERSION <= 6
-using ::android::hardware::audio::common::CPP_VERSION::implementation::AudioInputFlagBitfield;
-using ::android::hardware::audio::common::CPP_VERSION::implementation::AudioOutputFlagBitfield;
+using AudioInputFlags =
+ ::android::hardware::audio::common::CPP_VERSION::implementation::AudioInputFlagBitfield;
+using AudioOutputFlags =
+ ::android::hardware::audio::common::CPP_VERSION::implementation::AudioOutputFlagBitfield;
+#else
+using AudioInputFlags = hidl_vec<::android::hardware::audio::CPP_VERSION::AudioInOutFlag>;
+using AudioOutputFlags = hidl_vec<::android::hardware::audio::CPP_VERSION::AudioInOutFlag>;
#endif
using namespace ::android::hardware::audio::common::CPP_VERSION;
using namespace ::android::hardware::audio::CPP_VERSION;
@@ -67,28 +72,36 @@
std::tuple<Result, sp<IStreamOut>> openOutputStreamImpl(int32_t ioHandle,
const DeviceAddress& device,
const AudioConfig& config,
- AudioOutputFlagBitfield flags,
+ const AudioOutputFlags& flags,
AudioConfig* suggestedConfig);
std::tuple<Result, sp<IStreamIn>> openInputStreamImpl(
- int32_t ioHandle, const DeviceAddress& device, const AudioConfig& config,
- AudioInputFlagBitfield flags, AudioSource source, AudioConfig* suggestedConfig);
-#if MAJOR_VERSION == 2
+ int32_t ioHandle, const DeviceAddress& device, const AudioConfig& config,
+ const AudioInputFlags& flags, AudioSource source, AudioConfig* suggestedConfig);
+
Return<void> openOutputStream(int32_t ioHandle, const DeviceAddress& device,
- const AudioConfig& config, AudioOutputFlagBitfield flags,
- openOutputStream_cb _hidl_cb) override;
- Return<void> openInputStream(int32_t ioHandle, const DeviceAddress& device,
- const AudioConfig& config, AudioInputFlagBitfield flags,
- AudioSource source, openInputStream_cb _hidl_cb) override;
-#elif MAJOR_VERSION >= 4
- Return<void> openOutputStream(int32_t ioHandle, const DeviceAddress& device,
- const AudioConfig& config, AudioOutputFlagBitfield flags,
- const SourceMetadata& sourceMetadata,
- openOutputStream_cb _hidl_cb) override;
- Return<void> openInputStream(int32_t ioHandle, const DeviceAddress& device,
- const AudioConfig& config, AudioInputFlagBitfield flags,
- const SinkMetadata& sinkMetadata,
- openInputStream_cb _hidl_cb) override;
+ const AudioConfig& config,
+#if MAJOR_VERSION <= 6
+ AudioOutputFlags flags,
+#else
+ const AudioOutputFlags& flags,
#endif
+#if MAJOR_VERSION >= 4
+ const SourceMetadata& sourceMetadata,
+#endif
+ openOutputStream_cb _hidl_cb) override;
+ Return<void> openInputStream(int32_t ioHandle, const DeviceAddress& device,
+ const AudioConfig& config,
+#if MAJOR_VERSION <= 6
+ AudioInputFlags flags,
+#else
+ const AudioInputFlags& flags,
+#endif
+#if MAJOR_VERSION == 2
+ AudioSource source,
+#elif MAJOR_VERSION >= 4
+ const SinkMetadata& sinkMetadata,
+#endif
+ openInputStream_cb _hidl_cb) override;
Return<bool> supportsAudioPatches() override;
Return<void> createAudioPatch(const hidl_vec<AudioPortConfig>& sources,
@@ -133,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;
@@ -148,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/PrimaryDevice.h b/audio/core/all-versions/default/include/core/default/PrimaryDevice.h
index ccdb7b2..5f65acf 100644
--- a/audio/core/all-versions/default/include/core/default/PrimaryDevice.h
+++ b/audio/core/all-versions/default/include/core/default/PrimaryDevice.h
@@ -54,21 +54,29 @@
getInputBufferSize_cb _hidl_cb) override;
Return<void> openOutputStream(int32_t ioHandle, const DeviceAddress& device,
- const AudioConfig& config, AudioOutputFlagBitfield flags,
+ const AudioConfig& config,
+#if MAJOR_VERSION <= 6
+ AudioOutputFlags flags,
+#else
+ const AudioOutputFlags& flags,
+#endif
#if MAJOR_VERSION >= 4
const SourceMetadata& sourceMetadata,
#endif
openOutputStream_cb _hidl_cb) override;
-
Return<void> openInputStream(int32_t ioHandle, const DeviceAddress& device,
- const AudioConfig& config, AudioInputFlagBitfield flags,
- AudioSource source, openInputStream_cb _hidl_cb);
-#if MAJOR_VERSION >= 4
- Return<void> openInputStream(int32_t ioHandle, const DeviceAddress& device,
- const AudioConfig& config, AudioInputFlagBitfield flags,
- const SinkMetadata& sinkMetadata,
- openInputStream_cb _hidl_cb) override;
+ const AudioConfig& config,
+#if MAJOR_VERSION <= 6
+ AudioInputFlags flags,
+#else
+ const AudioInputFlags& flags,
#endif
+#if MAJOR_VERSION == 2
+ AudioSource source,
+#elif MAJOR_VERSION >= 4
+ const SinkMetadata& sinkMetadata,
+#endif
+ openInputStream_cb _hidl_cb) override;
Return<bool> supportsAudioPatches() override;
Return<void> createAudioPatch(const hidl_vec<AudioPortConfig>& sources,
diff --git a/audio/core/all-versions/default/include/core/default/Stream.h b/audio/core/all-versions/default/include/core/default/Stream.h
index ce0003b..0865992 100644
--- a/audio/core/all-versions/default/include/core/default/Stream.h
+++ b/audio/core/all-versions/default/include/core/default/Stream.h
@@ -41,12 +41,14 @@
using ::android::hardware::hidl_vec;
using ::android::hardware::Return;
using ::android::hardware::Void;
+#if MAJOR_VERSION <= 6
using ::android::hardware::audio::common::CPP_VERSION::implementation::AudioChannelBitfield;
+#endif
using namespace ::android::hardware::audio::common::CPP_VERSION;
using namespace ::android::hardware::audio::CPP_VERSION;
struct Stream : public IStream, public ParametersUtil {
- explicit Stream(audio_stream_t* stream);
+ Stream(bool isInput, audio_stream_t* stream);
/** 1GiB is the maximum buffer size the HAL client is allowed to request.
* This value has been chosen to be under SIZE_MAX and still big enough
@@ -59,6 +61,7 @@
Return<uint64_t> getFrameSize() override;
Return<uint64_t> getFrameCount() override;
Return<uint64_t> getBufferSize() override;
+#if MAJOR_VERSION <= 6
Return<uint32_t> getSampleRate() override;
#if MAJOR_VERSION == 2
Return<void> getSupportedSampleRates(getSupportedSampleRates_cb _hidl_cb) override;
@@ -72,6 +75,10 @@
Return<AudioFormat> getFormat() override;
Return<void> getSupportedFormats(getSupportedFormats_cb _hidl_cb) override;
Return<Result> setFormat(AudioFormat format) override;
+#else
+ Return<void> getSupportedProfiles(getSupportedProfiles_cb _hidl_cb) override;
+ Return<Result> setAudioProperties(const AudioConfigBase& config) override;
+#endif // MAJOR_VERSION <= 6
Return<void> getAudioProperties(getAudioProperties_cb _hidl_cb) override;
Return<Result> addEffect(uint64_t effectId) override;
Return<Result> removeEffect(uint64_t effectId) override;
@@ -110,13 +117,14 @@
const std::vector<int>& ignoreErrors);
private:
- audio_stream_t* mStream;
+ const bool mIsInput;
+ audio_stream_t* mStream;
- virtual ~Stream();
+ virtual ~Stream();
- // Methods from ParametersUtil.
- char* halGetParameters(const char* keys) override;
- int halSetParameters(const char* keysAndValues) override;
+ // Methods from ParametersUtil.
+ char* halGetParameters(const char* keys) override;
+ int halSetParameters(const char* keysAndValues) override;
};
template <typename T>
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 24f9944..651b3a6 100644
--- a/audio/core/all-versions/default/include/core/default/StreamIn.h
+++ b/audio/core/all-versions/default/include/core/default/StreamIn.h
@@ -56,6 +56,7 @@
Return<uint64_t> getFrameSize() override;
Return<uint64_t> getFrameCount() override;
Return<uint64_t> getBufferSize() override;
+#if MAJOR_VERSION <= 6
Return<uint32_t> getSampleRate() override;
#if MAJOR_VERSION == 2
Return<void> getSupportedSampleRates(getSupportedSampleRates_cb _hidl_cb) override;
@@ -69,6 +70,10 @@
Return<AudioFormat> getFormat() override;
Return<void> getSupportedFormats(getSupportedFormats_cb _hidl_cb) override;
Return<Result> setFormat(AudioFormat format) override;
+#else
+ Return<void> getSupportedProfiles(getSupportedProfiles_cb _hidl_cb) override;
+ Return<Result> setAudioProperties(const AudioConfigBase& config) override;
+#endif // MAJOR_VERSION <= 6
Return<void> getAudioProperties(getAudioProperties_cb _hidl_cb) override;
Return<Result> addEffect(uint64_t effectId) override;
Return<Result> removeEffect(uint64_t effectId) override;
@@ -109,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;
@@ -119,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 e647da9..b8e8515 100644
--- a/audio/core/all-versions/default/include/core/default/StreamOut.h
+++ b/audio/core/all-versions/default/include/core/default/StreamOut.h
@@ -56,6 +56,7 @@
Return<uint64_t> getFrameSize() override;
Return<uint64_t> getFrameCount() override;
Return<uint64_t> getBufferSize() override;
+#if MAJOR_VERSION <= 6
Return<uint32_t> getSampleRate() override;
#if MAJOR_VERSION == 2
Return<void> getSupportedSampleRates(getSupportedSampleRates_cb _hidl_cb) override;
@@ -69,6 +70,10 @@
Return<AudioFormat> getFormat() override;
Return<void> getSupportedFormats(getSupportedFormats_cb _hidl_cb) override;
Return<Result> setFormat(AudioFormat format) override;
+#else
+ Return<void> getSupportedProfiles(getSupportedProfiles_cb _hidl_cb) override;
+ Return<Result> setAudioProperties(const AudioConfigBase& config) override;
+#endif // MAJOR_VERSION <= 6
Return<void> getAudioProperties(getAudioProperties_cb _hidl_cb) override;
Return<Result> addEffect(uint64_t effectId) override;
Return<Result> removeEffect(uint64_t effectId) override;
@@ -118,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;
@@ -138,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/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp b/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp
index 1612d3c..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) {
@@ -140,20 +143,23 @@
#if MAJOR_VERSION <= 6
using AD = AudioDevice;
for (auto deviceType : {AD::OUT_HDMI, AD::OUT_WIRED_HEADPHONE, AD::IN_USB_HEADSET}) {
+ SCOPED_TRACE("device=" + ::testing::PrintToString(deviceType));
#elif MAJOR_VERSION >= 7
using AD = xsd::AudioDevice;
- for (auto deviceType :
- {toString(AD::AUDIO_DEVICE_OUT_HDMI), toString(AD::AUDIO_DEVICE_OUT_WIRED_HEADPHONE),
- toString(AD::AUDIO_DEVICE_IN_USB_HEADSET)}) {
+ for (auto deviceType : {AD::AUDIO_DEVICE_OUT_HDMI, AD::AUDIO_DEVICE_OUT_WIRED_HEADPHONE,
+ AD::AUDIO_DEVICE_IN_USB_HEADSET}) {
+ SCOPED_TRACE("device=" + toString(deviceType));
#endif
- SCOPED_TRACE("device=" + ::testing::PrintToString(deviceType));
for (bool state : {true, false}) {
SCOPED_TRACE("state=" + ::testing::PrintToString(state));
DeviceAddress address = {};
#if MAJOR_VERSION <= 6
address.device = deviceType;
#elif MAJOR_VERSION >= 7
- address.deviceType = deviceType;
+ address.deviceType = toString(deviceType);
+ if (deviceType == AD::AUDIO_DEVICE_IN_USB_HEADSET) {
+ address.address.alsa({0, 0});
+ }
#endif
auto ret = getDevice()->setConnectedState(address, state);
ASSERT_TRUE(ret.isOk());
@@ -231,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;
}
}
@@ -254,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) {
@@ -266,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..0ebe4c2 100644
--- a/audio/core/all-versions/vts/functional/6.0/AudioPrimaryHidlHalTest.cpp
+++ b/audio/core/all-versions/vts/functional/6.0/AudioPrimaryHidlHalTest.cpp
@@ -18,137 +18,131 @@
#include "5.0/AudioPrimaryHidlHalTest.cpp"
#if MAJOR_VERSION <= 6
-const std::vector<DeviceConfigParameter>& getOutputDeviceConfigParameters() {
- static std::vector<DeviceConfigParameter> parameters = [] {
- std::vector<DeviceConfigParameter> result;
- for (const auto& device : getDeviceParameters()) {
- auto module =
- getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device));
- for (const auto& ioProfile : module->getOutputProfiles()) {
- for (const auto& profile : ioProfile->getAudioProfiles()) {
- const auto& channels = profile->getChannels();
- const auto& sampleRates = profile->getSampleRates();
- auto configs = ConfigHelper::combineAudioConfig(
- std::vector<audio_channel_mask_t>(channels.begin(), channels.end()),
- std::vector<uint32_t>(sampleRates.begin(), sampleRates.end()),
- profile->getFormat());
- auto flags = ioProfile->getFlags();
- for (auto& config : configs) {
- // Some combinations of flags declared in the config file require special
- // treatment.
- if (flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) {
- config.offloadInfo.sampleRateHz = config.sampleRateHz;
- config.offloadInfo.channelMask = config.channelMask;
- config.offloadInfo.format = config.format;
- config.offloadInfo.streamType = AudioStreamType::MUSIC;
- config.offloadInfo.bitRatePerSecond = 320;
- config.offloadInfo.durationMicroseconds = -1;
- config.offloadInfo.bitWidth = 16;
- config.offloadInfo.bufferSize = 256; // arbitrary value
- config.offloadInfo.usage = AudioUsage::MEDIA;
- result.emplace_back(device, config,
- AudioOutputFlag(AudioOutputFlag::COMPRESS_OFFLOAD |
- AudioOutputFlag::DIRECT));
- } else {
- if (flags & AUDIO_OUTPUT_FLAG_PRIMARY) { // ignore the flag
- flags &= ~AUDIO_OUTPUT_FLAG_PRIMARY;
- }
- result.emplace_back(device, config, AudioOutputFlag(flags));
+static std::vector<DeviceConfigParameter> generateOutputDeviceConfigParameters(
+ bool oneProfilePerDevice) {
+ std::vector<DeviceConfigParameter> result;
+ for (const auto& device : getDeviceParameters()) {
+ auto module =
+ getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device));
+ for (const auto& ioProfile : module->getOutputProfiles()) {
+ for (const auto& profile : ioProfile->getAudioProfiles()) {
+ const auto& channels = profile->getChannels();
+ const auto& sampleRates = profile->getSampleRates();
+ auto configs = ConfigHelper::combineAudioConfig(
+ std::vector<audio_channel_mask_t>(channels.begin(), channels.end()),
+ std::vector<uint32_t>(sampleRates.begin(), sampleRates.end()),
+ profile->getFormat());
+ auto flags = ioProfile->getFlags();
+ for (auto& config : configs) {
+ // Some combinations of flags declared in the config file require special
+ // treatment.
+ if (flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) {
+ config.offloadInfo.sampleRateHz = config.sampleRateHz;
+ config.offloadInfo.channelMask = config.channelMask;
+ config.offloadInfo.format = config.format;
+ config.offloadInfo.streamType = AudioStreamType::MUSIC;
+ config.offloadInfo.bitRatePerSecond = 320;
+ config.offloadInfo.durationMicroseconds = -1;
+ config.offloadInfo.bitWidth = 16;
+ config.offloadInfo.bufferSize = 256; // arbitrary value
+ config.offloadInfo.usage = AudioUsage::MEDIA;
+ result.emplace_back(device, config,
+ AudioOutputFlag(AudioOutputFlag::COMPRESS_OFFLOAD |
+ AudioOutputFlag::DIRECT));
+ } else {
+ if (flags & AUDIO_OUTPUT_FLAG_PRIMARY) { // ignore the flag
+ flags &= ~AUDIO_OUTPUT_FLAG_PRIMARY;
}
+ result.emplace_back(device, config, AudioOutputFlag(flags));
}
+ if (oneProfilePerDevice) break;
}
+ if (oneProfilePerDevice) break;
}
+ if (oneProfilePerDevice) break;
}
- return result;
- }();
+ }
+ return result;
+}
+
+const std::vector<DeviceConfigParameter>& getOutputDeviceConfigParameters() {
+ static std::vector<DeviceConfigParameter> parameters =
+ generateOutputDeviceConfigParameters(false);
return parameters;
}
-const std::vector<DeviceConfigParameter>& getInputDeviceConfigParameters() {
- static std::vector<DeviceConfigParameter> parameters = [] {
- std::vector<DeviceConfigParameter> result;
- for (const auto& device : getDeviceParameters()) {
- auto module =
- getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device));
- for (const auto& ioProfile : module->getInputProfiles()) {
- for (const auto& profile : ioProfile->getAudioProfiles()) {
- const auto& channels = profile->getChannels();
- const auto& sampleRates = profile->getSampleRates();
- auto configs = ConfigHelper::combineAudioConfig(
- std::vector<audio_channel_mask_t>(channels.begin(), channels.end()),
- std::vector<uint32_t>(sampleRates.begin(), sampleRates.end()),
- profile->getFormat());
- for (const auto& config : configs) {
- result.emplace_back(device, config, AudioInputFlag(ioProfile->getFlags()));
- }
+const std::vector<DeviceConfigParameter>& getOutputDeviceSingleConfigParameters() {
+ static std::vector<DeviceConfigParameter> parameters =
+ generateOutputDeviceConfigParameters(true);
+ return parameters;
+}
+
+static std::vector<DeviceConfigParameter> generateInputDeviceConfigParameters(
+ bool oneProfilePerDevice) {
+ std::vector<DeviceConfigParameter> result;
+ for (const auto& device : getDeviceParameters()) {
+ auto module =
+ getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device));
+ for (const auto& ioProfile : module->getInputProfiles()) {
+ for (const auto& profile : ioProfile->getAudioProfiles()) {
+ const auto& channels = profile->getChannels();
+ const auto& sampleRates = profile->getSampleRates();
+ auto configs = ConfigHelper::combineAudioConfig(
+ std::vector<audio_channel_mask_t>(channels.begin(), channels.end()),
+ std::vector<uint32_t>(sampleRates.begin(), sampleRates.end()),
+ profile->getFormat());
+ for (const auto& config : configs) {
+ result.emplace_back(device, config, AudioInputFlag(ioProfile->getFlags()));
+ if (oneProfilePerDevice) break;
}
+ if (oneProfilePerDevice) break;
}
+ if (oneProfilePerDevice) break;
}
- return result;
- }();
+ }
+ return result;
+}
+
+const std::vector<DeviceConfigParameter>& getInputDeviceConfigParameters() {
+ static std::vector<DeviceConfigParameter> parameters =
+ generateInputDeviceConfigParameters(false);
+ return parameters;
+}
+
+const std::vector<DeviceConfigParameter>& getInputDeviceSingleConfigParameters() {
+ static std::vector<DeviceConfigParameter> parameters =
+ generateInputDeviceConfigParameters(true);
return parameters;
}
#endif // MAJOR_VERSION <= 6
-TEST_P(AudioHidlDeviceTest, CloseDeviceWithOpenedOutputStreams) {
- doc::test("Verify that a device can't be closed if there are streams opened");
-#if MAJOR_VERSION <= 6
- DeviceAddress address{.device = AudioDevice::OUT_DEFAULT};
- SourceMetadata initMetadata = {{{AudioUsage::MEDIA, AudioContentType::MUSIC, 1 /* gain */}}};
- auto flags = hidl_bitfield<AudioOutputFlag>(AudioOutputFlag::NONE);
-#elif MAJOR_VERSION >= 7
- DeviceAddress address{.deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_OUT_DEFAULT)};
- SourceMetadata initMetadata = {
- {{toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
- toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC), 1 /* gain */}}};
- hidl_vec<AudioInOutFlag> flags;
-#endif
- AudioConfig config{};
- sp<IStreamOut> stream;
- StreamHelper<IStreamOut> helper(stream);
- AudioConfig suggestedConfig{};
- ASSERT_NO_FATAL_FAILURE(helper.open(
- [&](AudioIoHandle handle, AudioConfig config, auto cb) {
- return getDevice()->openOutputStream(handle, address, config, flags, initMetadata,
- cb);
- },
- config, &res, &suggestedConfig));
+class SingleConfigOutputStreamTest : public OutputStreamTest {};
+TEST_P(SingleConfigOutputStreamTest, CloseDeviceWithOpenedOutputStreams) {
+ doc::test("Verify that a device can't be closed if there are output streams opened");
+ // Opening of the stream is done in SetUp.
ASSERT_RESULT(Result::INVALID_STATE, getDevice()->close());
- ASSERT_NO_FATAL_FAILURE(helper.close(true /*clear*/, &res));
+ ASSERT_OK(closeStream(true /*clear*/));
ASSERT_OK(getDevice()->close());
ASSERT_TRUE(resetDevice());
}
+INSTANTIATE_TEST_CASE_P(SingleConfigOutputStream, SingleConfigOutputStreamTest,
+ ::testing::ValuesIn(getOutputDeviceSingleConfigParameters()),
+ &DeviceConfigParameterToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SingleConfigOutputStreamTest);
-TEST_P(AudioHidlDeviceTest, CloseDeviceWithOpenedInputStreams) {
- doc::test("Verify that a device can't be closed if there are streams opened");
- if (!getCachedPolicyConfig().haveInputProfilesInModule(getDeviceName())) {
- GTEST_SKIP() << "Device doesn't have input profiles";
- }
-#if MAJOR_VERSION <= 6
- DeviceAddress address{.device = AudioDevice::IN_DEFAULT};
- SinkMetadata initMetadata = {{{.source = AudioSource::MIC, .gain = 1}}};
- auto flags = hidl_bitfield<AudioInputFlag>(AudioInputFlag::NONE);
-#elif MAJOR_VERSION >= 7
- DeviceAddress address{.deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_IN_DEFAULT)};
- SinkMetadata initMetadata = {
- {{.source = toString(xsd::AudioSource::AUDIO_SOURCE_MIC), .gain = 1}}};
- hidl_vec<AudioInOutFlag> flags;
-#endif
- AudioConfig config{};
- sp<IStreamIn> stream;
- StreamHelper<IStreamIn> helper(stream);
- AudioConfig suggestedConfig{};
- ASSERT_NO_FATAL_FAILURE(helper.open(
- [&](AudioIoHandle handle, AudioConfig config, auto cb) {
- return getDevice()->openInputStream(handle, address, config, flags, initMetadata,
- cb);
- },
- config, &res, &suggestedConfig));
+class SingleConfigInputStreamTest : public InputStreamTest {};
+TEST_P(SingleConfigInputStreamTest, CloseDeviceWithOpenedInputStreams) {
+ doc::test("Verify that a device can't be closed if there are input streams opened");
+ // Opening of the stream is done in SetUp.
ASSERT_RESULT(Result::INVALID_STATE, getDevice()->close());
- ASSERT_NO_FATAL_FAILURE(helper.close(true /*clear*/, &res));
+ ASSERT_OK(closeStream(true /*clear*/));
ASSERT_OK(getDevice()->close());
ASSERT_TRUE(resetDevice());
}
+INSTANTIATE_TEST_CASE_P(SingleConfigInputStream, SingleConfigInputStreamTest,
+ ::testing::ValuesIn(getInputDeviceSingleConfigParameters()),
+ &DeviceConfigParameterToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SingleConfigInputStreamTest);
TEST_P(AudioPatchHidlTest, UpdatePatchInvalidHandle) {
doc::test("Verify that passing an invalid handle to updateAudioPatch is checked");
diff --git a/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp b/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp
index 941c4bd..7fca610 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,52 +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
- auto xsdFlags = mixPort.getFlags();
- const bool isOffload =
- std::find(xsdFlags.begin(), xsdFlags.end(),
- xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) !=
- xsdFlags.end();
- std::vector<AudioInOutFlag> flags;
- 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;
@@ -88,28 +193,563 @@
return parameters;
}
+static std::vector<DeviceConfigParameter> generateInputDeviceConfigParameters(
+ bool oneProfilePerDevice) {
+ std::vector<DeviceConfigParameter> result;
+ for (const auto& device : getDeviceParameters()) {
+ auto module =
+ getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device));
+ if (!module || !module->getFirstMixPorts()) break;
+ for (const auto& mixPort : module->getFirstMixPorts()->getMixPort()) {
+ if (mixPort.getRole() != xsd::Role::sink) continue; // not an input profile
+ std::vector<AudioInOutFlag> flags;
+ if (mixPort.hasFlags()) {
+ std::transform(mixPort.getFlags().begin(), mixPort.getFlags().end(),
+ std::back_inserter(flags), [](auto flag) { return toString(flag); });
+ }
+ for (const auto& profile : mixPort.getProfile()) {
+ auto configs = combineAudioConfig(profile.getChannelMasks(),
+ profile.getSamplingRates(), profile.getFormat());
+ for (const auto& config : configs) {
+ result.emplace_back(device, config, flags);
+ if (oneProfilePerDevice) break;
+ }
+ if (oneProfilePerDevice) break;
+ }
+ if (oneProfilePerDevice) break;
+ }
+ }
+ return result;
+}
+
const std::vector<DeviceConfigParameter>& getInputDeviceConfigParameters() {
- static std::vector<DeviceConfigParameter> parameters = [] {
+ static std::vector<DeviceConfigParameter> parameters =
+ generateInputDeviceConfigParameters(false);
+ return parameters;
+}
+
+const std::vector<DeviceConfigParameter>& getInputDeviceSingleConfigParameters() {
+ static std::vector<DeviceConfigParameter> parameters =
+ generateInputDeviceConfigParameters(true);
+ return parameters;
+}
+
+const std::vector<DeviceConfigParameter>& getInputDeviceInvalidConfigParameters(
+ bool generateInvalidFlags = true) {
+ static std::vector<DeviceConfigParameter> parameters = [&] {
std::vector<DeviceConfigParameter> result;
for (const auto& device : getDeviceParameters()) {
auto module =
getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device));
+ if (!module || !module->getFirstMixPorts()) break;
+ bool hasConfig = false;
for (const auto& mixPort : module->getFirstMixPorts()->getMixPort()) {
if (mixPort.getRole() != xsd::Role::sink) continue; // not an input profile
- std::vector<AudioInOutFlag> flags;
- std::transform(mixPort.getFlags().begin(), mixPort.getFlags().end(), flags.begin(),
- [](auto flag) { return toString(flag); });
+ std::vector<AudioInOutFlag> validFlags;
+ if (mixPort.hasFlags()) {
+ std::transform(mixPort.getFlags().begin(), mixPort.getFlags().end(),
+ std::back_inserter(validFlags),
+ [](auto flag) { return toString(flag); });
+ }
for (const auto& profile : mixPort.getProfile()) {
- auto configs =
- combineAudioConfig(profile.getChannelMasks(),
- profile.getSamplingRates(), profile.getFormat());
- for (const auto& config : configs) {
+ if (!profile.hasFormat() || !profile.hasSamplingRates() ||
+ !profile.hasChannelMasks())
+ continue;
+ AudioConfigBase validBase = {
+ profile.getFormat(),
+ static_cast<uint32_t>(profile.getSamplingRates()[0]),
+ toString(profile.getChannelMasks()[0])};
+ {
+ AudioConfig config{.base = validBase};
+ config.base.channelMask = "random_string";
+ result.emplace_back(device, config, validFlags);
+ }
+ {
+ AudioConfig config{.base = validBase};
+ config.base.format = "random_string";
+ result.emplace_back(device, config, validFlags);
+ }
+ if (generateInvalidFlags) {
+ AudioConfig config{.base = validBase};
+ std::vector<AudioInOutFlag> flags = {"random_string", ""};
result.emplace_back(device, config, flags);
}
+ hasConfig = true;
+ break;
}
+ if (hasConfig) break;
}
}
return result;
}();
return parameters;
}
+
+class InvalidInputConfigNoFlagsTest : public AudioHidlTestWithDeviceConfigParameter {};
+TEST_P(InvalidInputConfigNoFlagsTest, InputBufferSizeTest) {
+ doc::test("Verify that invalid config is rejected by IDevice::getInputBufferSize method.");
+ uint64_t bufferSize;
+ ASSERT_OK(getDevice()->getInputBufferSize(getConfig(), returnIn(res, bufferSize)));
+ EXPECT_EQ(Result::INVALID_ARGUMENTS, res);
+}
+INSTANTIATE_TEST_CASE_P(
+ InputBufferSizeInvalidConfig, InvalidInputConfigNoFlagsTest,
+ ::testing::ValuesIn(getInputDeviceInvalidConfigParameters(false /*generateInvalidFlags*/)),
+ &DeviceConfigParameterToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(InvalidInputConfigNoFlagsTest);
+
+static const DeviceAddress& getValidInputDeviceAddress() {
+ static const DeviceAddress valid = {
+ .deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_IN_DEFAULT)};
+ return valid;
+}
+
+static const DeviceAddress& getValidOutputDeviceAddress() {
+ static const DeviceAddress valid = {
+ .deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_OUT_DEFAULT)};
+ return valid;
+}
+
+static const DeviceAddress& getInvalidDeviceAddress() {
+ static const DeviceAddress valid = {.deviceType = "random_string"};
+ return valid;
+}
+
+TEST_P(AudioHidlDeviceTest, SetConnectedStateInvalidDeviceAddress) {
+ doc::test("Check that invalid device address is rejected by IDevice::setConnectedState");
+ EXPECT_RESULT(Result::INVALID_ARGUMENTS,
+ getDevice()->setConnectedState(getInvalidDeviceAddress(), true));
+ EXPECT_RESULT(Result::INVALID_ARGUMENTS,
+ getDevice()->setConnectedState(getInvalidDeviceAddress(), false));
+}
+
+static std::vector<AudioPortConfig>& generatePortConfigs(bool valid) {
+ enum { // Note: This is for convenience when deriving "invalid" configs from "valid".
+ PORT_CONF_MINIMAL,
+ PORT_CONF_WITH_GAIN,
+ PORT_CONF_EXT_DEVICE,
+ PORT_CONF_EXT_MIX_SOURCE,
+ PORT_CONF_EXT_MIX_SINK,
+ PORT_CONF_EXT_SESSION
+ };
+ static std::vector<AudioPortConfig> valids = [] {
+ std::vector<AudioPortConfig> result;
+ result.reserve(PORT_CONF_EXT_SESSION + 1);
+ result.push_back(AudioPortConfig{});
+ AudioPortConfig configWithGain{};
+ configWithGain.gain.config(AudioGainConfig{
+ .index = 0,
+ .mode = {toString(xsd::AudioGainMode::AUDIO_GAIN_MODE_JOINT)},
+ .channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_MONO),
+ .rampDurationMs = 1});
+ configWithGain.gain.config().values.resize(1);
+ configWithGain.gain.config().values[0] = 1000;
+ result.push_back(std::move(configWithGain));
+ AudioPortConfig configWithPortExtDevice{};
+ configWithPortExtDevice.ext.device(getValidOutputDeviceAddress());
+ result.push_back(std::move(configWithPortExtDevice));
+ AudioPortConfig configWithPortExtMixSource{};
+ configWithPortExtMixSource.ext.mix({});
+ configWithPortExtMixSource.ext.mix().useCase.stream(
+ toString(xsd::AudioStreamType::AUDIO_STREAM_VOICE_CALL));
+ result.push_back(std::move(configWithPortExtMixSource));
+ AudioPortConfig configWithPortExtMixSink{};
+ configWithPortExtMixSink.ext.mix({});
+ configWithPortExtMixSink.ext.mix().useCase.source(
+ toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT));
+ result.push_back(std::move(configWithPortExtMixSink));
+ AudioPortConfig configWithPortExtSession{};
+ configWithPortExtSession.ext.session(
+ static_cast<AudioSession>(AudioSessionConsts::OUTPUT_MIX));
+ result.push_back(std::move(configWithPortExtSession));
+ return result;
+ }();
+ static std::vector<AudioPortConfig> invalids = [&] {
+ std::vector<AudioPortConfig> result;
+ AudioPortConfig invalidBaseChannelMask = valids[PORT_CONF_MINIMAL];
+ invalidBaseChannelMask.base.channelMask = "random_string";
+ result.push_back(std::move(invalidBaseChannelMask));
+ AudioPortConfig invalidBaseFormat = valids[PORT_CONF_MINIMAL];
+ invalidBaseFormat.base.format = "random_string";
+ result.push_back(std::move(invalidBaseFormat));
+ AudioPortConfig invalidGainMode = valids[PORT_CONF_WITH_GAIN];
+ invalidGainMode.gain.config().mode = {{"random_string"}};
+ result.push_back(std::move(invalidGainMode));
+ AudioPortConfig invalidGainChannelMask = valids[PORT_CONF_WITH_GAIN];
+ invalidGainChannelMask.gain.config().channelMask = "random_string";
+ result.push_back(std::move(invalidGainChannelMask));
+ AudioPortConfig invalidDeviceType = valids[PORT_CONF_EXT_DEVICE];
+ invalidDeviceType.ext.device().deviceType = "random_string";
+ result.push_back(std::move(invalidDeviceType));
+ AudioPortConfig invalidStreamType = valids[PORT_CONF_EXT_MIX_SOURCE];
+ invalidStreamType.ext.mix().useCase.stream() = "random_string";
+ result.push_back(std::move(invalidStreamType));
+ AudioPortConfig invalidSource = valids[PORT_CONF_EXT_MIX_SINK];
+ invalidSource.ext.mix().useCase.source() = "random_string";
+ result.push_back(std::move(invalidSource));
+ return result;
+ }();
+ return valid ? valids : invalids;
+}
+
+TEST_P(AudioHidlDeviceTest, SetAudioPortConfigInvalidArguments) {
+ doc::test("Check that invalid port configs are rejected by IDevice::setAudioPortConfig");
+ for (const auto& invalidConfig : generatePortConfigs(false /*valid*/)) {
+ EXPECT_RESULT(invalidArgsOrNotSupported, getDevice()->setAudioPortConfig(invalidConfig))
+ << ::testing::PrintToString(invalidConfig);
+ }
+}
+
+TEST_P(AudioPatchHidlTest, CreatePatchInvalidArguments) {
+ doc::test("Check that invalid port configs are rejected by IDevice::createAudioPatch");
+ // Note that HAL actually might reject the proposed source / sink combo
+ // due to other reasons than presence of invalid enum-strings.
+ // TODO: Come up with a way to guarantee validity of a source / sink combo.
+ for (const auto& validSource : generatePortConfigs(true /*valid*/)) {
+ for (const auto& invalidSink : generatePortConfigs(false /*valid*/)) {
+ AudioPatchHandle handle;
+ EXPECT_OK(getDevice()->createAudioPatch(hidl_vec<AudioPortConfig>{validSource},
+ hidl_vec<AudioPortConfig>{invalidSink},
+ returnIn(res, handle)));
+ EXPECT_EQ(Result::INVALID_ARGUMENTS, res)
+ << "Source: " << ::testing::PrintToString(validSource)
+ << "; Sink: " << ::testing::PrintToString(invalidSink);
+ }
+ }
+ for (const auto& validSink : generatePortConfigs(true /*valid*/)) {
+ for (const auto& invalidSource : generatePortConfigs(false /*valid*/)) {
+ AudioPatchHandle handle;
+ EXPECT_OK(getDevice()->createAudioPatch(hidl_vec<AudioPortConfig>{invalidSource},
+ hidl_vec<AudioPortConfig>{validSink},
+ returnIn(res, handle)));
+ EXPECT_EQ(Result::INVALID_ARGUMENTS, res)
+ << "Source: " << ::testing::PrintToString(invalidSource)
+ << "; Sink: " << ::testing::PrintToString(validSink);
+ }
+ }
+}
+
+TEST_P(AudioPatchHidlTest, UpdatePatchInvalidArguments) {
+ doc::test("Check that invalid port configs are rejected by IDevice::updateAudioPatch");
+ // Note that HAL actually might reject the proposed source / sink combo
+ // due to other reasons than presence of invalid enum-strings.
+ // TODO: Come up with a way to guarantee validity of a source / sink combo.
+ for (const auto& validSource : generatePortConfigs(true /*valid*/)) {
+ for (const auto& invalidSink : generatePortConfigs(false /*valid*/)) {
+ AudioPatchHandle handle{};
+ EXPECT_OK(getDevice()->updateAudioPatch(handle, hidl_vec<AudioPortConfig>{validSource},
+ hidl_vec<AudioPortConfig>{invalidSink},
+ returnIn(res, handle)));
+ EXPECT_EQ(Result::INVALID_ARGUMENTS, res)
+ << "Source: " << ::testing::PrintToString(validSource)
+ << "; Sink: " << ::testing::PrintToString(invalidSink);
+ }
+ }
+ for (const auto& validSink : generatePortConfigs(true /*valid*/)) {
+ for (const auto& invalidSource : generatePortConfigs(false /*valid*/)) {
+ AudioPatchHandle handle{};
+ EXPECT_OK(getDevice()->updateAudioPatch(
+ handle, hidl_vec<AudioPortConfig>{invalidSource},
+ hidl_vec<AudioPortConfig>{validSink}, returnIn(res, handle)));
+ EXPECT_EQ(Result::INVALID_ARGUMENTS, res)
+ << "Source: " << ::testing::PrintToString(invalidSource)
+ << "; Sink: " << ::testing::PrintToString(validSink);
+ }
+ }
+}
+
+enum { PARAM_DEVICE_CONFIG, PARAM_ADDRESS, PARAM_METADATA };
+enum { INDEX_SINK, INDEX_SOURCE };
+using SinkOrSourceMetadata = std::variant<SinkMetadata, SourceMetadata>;
+using StreamOpenParameter = std::tuple<DeviceConfigParameter, DeviceAddress, SinkOrSourceMetadata>;
+
+static std::string StreamOpenParameterToString(
+ const ::testing::TestParamInfo<StreamOpenParameter>& info) {
+ return DeviceConfigParameterToString(::testing::TestParamInfo<DeviceConfigParameter>{
+ std::get<PARAM_DEVICE_CONFIG>(info.param), info.index}) +
+ "__" +
+ SanitizeStringForGTestName(
+ ::testing::PrintToString(std::get<PARAM_ADDRESS>(info.param))) +
+ "__" +
+ SanitizeStringForGTestName(std::visit(
+ [](auto&& arg) -> std::string { return ::testing::PrintToString(arg); },
+ std::get<PARAM_METADATA>(info.param)));
+}
+
+class StreamOpenTest : public HidlTest, public ::testing::WithParamInterface<StreamOpenParameter> {
+ protected:
+ void SetUp() override {
+ ASSERT_NO_FATAL_FAILURE(HidlTest::SetUp()); // setup base
+ ASSERT_TRUE(getDevicesFactory() != nullptr);
+ ASSERT_TRUE(getDevice() != nullptr);
+ }
+ const std::string& getFactoryName() const override {
+ return std::get<PARAM_FACTORY_NAME>(
+ std::get<PARAM_DEVICE>(std::get<PARAM_DEVICE_CONFIG>(GetParam())));
+ }
+ const std::string& getDeviceName() const override {
+ return std::get<PARAM_DEVICE_NAME>(
+ std::get<PARAM_DEVICE>(std::get<PARAM_DEVICE_CONFIG>(GetParam())));
+ }
+ const AudioConfig& getConfig() const {
+ return std::get<PARAM_CONFIG>(std::get<PARAM_DEVICE_CONFIG>(GetParam()));
+ }
+ const hidl_vec<AudioInOutFlag> getFlags() const {
+ return std::get<PARAM_FLAGS>(std::get<PARAM_DEVICE_CONFIG>(GetParam()));
+ }
+ const DeviceAddress& getDeviceAddress() const { return std::get<PARAM_ADDRESS>(GetParam()); }
+ bool isParamForInputStream() const {
+ return std::get<PARAM_METADATA>(GetParam()).index() == INDEX_SINK;
+ }
+ const SinkMetadata& getSinkMetadata() const {
+ return std::get<INDEX_SINK>(std::get<PARAM_METADATA>(GetParam()));
+ }
+ const SourceMetadata& getSourceMetadata() const {
+ return std::get<INDEX_SOURCE>(std::get<PARAM_METADATA>(GetParam()));
+ }
+};
+
+static const RecordTrackMetadata& getValidRecordTrackMetadata() {
+ static const RecordTrackMetadata valid = {
+ .source = toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT), .gain = 1};
+ return valid;
+}
+
+static const RecordTrackMetadata& getValidRecordTrackMetadataWithDest() {
+ static const RecordTrackMetadata valid = {
+ .source = toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT),
+ .gain = 1,
+ .destination = [] {
+ RecordTrackMetadata::Destination dest;
+ dest.device(getValidOutputDeviceAddress());
+ return dest;
+ }()};
+ return valid;
+}
+
+static const RecordTrackMetadata& getInvalidSourceRecordTrackMetadata() {
+ static const RecordTrackMetadata invalid = {.source = "random_string", .gain = 1};
+ return invalid;
+}
+
+static const RecordTrackMetadata& getRecordTrackMetadataWithInvalidDest() {
+ static const RecordTrackMetadata invalid = {
+ .source = toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT),
+ .gain = 1,
+ .destination = [] {
+ RecordTrackMetadata::Destination dest;
+ dest.device(getInvalidDeviceAddress());
+ return dest;
+ }()};
+ return invalid;
+}
+
+static const RecordTrackMetadata& getInvalidChannelMaskRecordTrackMetadata() {
+ static const RecordTrackMetadata invalid = {
+ .source = toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT),
+ .gain = 1,
+ .channelMask = "random_string"};
+ return invalid;
+}
+
+static const RecordTrackMetadata& getInvalidTagsRecordTrackMetadata() {
+ static const RecordTrackMetadata invalid = {
+ .source = toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT),
+ .gain = 1,
+ .tags = {{"random_string"}}};
+ return invalid;
+}
+
+static const PlaybackTrackMetadata& getValidPlaybackTrackMetadata() {
+ static const PlaybackTrackMetadata valid = {
+ .usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
+ .contentType = toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC),
+ .gain = 1};
+ return valid;
+}
+
+static const PlaybackTrackMetadata& getInvalidUsagePlaybackTrackMetadata() {
+ static const PlaybackTrackMetadata invalid = {
+ .usage = "random_string",
+ .contentType = toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC),
+ .gain = 1};
+ return invalid;
+}
+
+static const PlaybackTrackMetadata& getInvalidContentTypePlaybackTrackMetadata() {
+ static const PlaybackTrackMetadata invalid = {
+ .usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
+ .contentType = "random_string",
+ .gain = 1};
+ return invalid;
+}
+
+static const PlaybackTrackMetadata& getInvalidChannelMaskPlaybackTrackMetadata() {
+ static const PlaybackTrackMetadata invalid = {
+ .usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
+ .contentType = toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC),
+ .gain = 1,
+ .channelMask = "random_string"};
+ return invalid;
+}
+
+static const PlaybackTrackMetadata& getInvalidTagsPlaybackTrackMetadata() {
+ static const PlaybackTrackMetadata invalid = {
+ .usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
+ .contentType = toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC),
+ .gain = 1,
+ .tags = {{"random_string"}}};
+ return invalid;
+}
+
+static const std::vector<SourceMetadata>& getInvalidSourceMetadatas() {
+ static const std::vector<SourceMetadata> invalids = {
+ SourceMetadata{.tracks = {{getInvalidUsagePlaybackTrackMetadata()}}},
+ SourceMetadata{.tracks = {{getInvalidContentTypePlaybackTrackMetadata()}}},
+ SourceMetadata{.tracks = {{getInvalidChannelMaskPlaybackTrackMetadata()}}},
+ SourceMetadata{.tracks = {{getInvalidTagsPlaybackTrackMetadata()}}},
+ SourceMetadata{.tracks = {{getValidPlaybackTrackMetadata(),
+ getInvalidUsagePlaybackTrackMetadata()}}},
+ SourceMetadata{.tracks = {{getValidPlaybackTrackMetadata(),
+ getInvalidContentTypePlaybackTrackMetadata()}}},
+ SourceMetadata{.tracks = {{getValidPlaybackTrackMetadata(),
+ getInvalidChannelMaskPlaybackTrackMetadata()}}},
+ SourceMetadata{.tracks = {{getValidPlaybackTrackMetadata(),
+ getInvalidTagsPlaybackTrackMetadata()}}}};
+ return invalids;
+}
+static const std::vector<SinkMetadata>& getInvalidSinkMetadatas() {
+ static const std::vector<SinkMetadata> invalids = {
+ SinkMetadata{.tracks = {{getInvalidSourceRecordTrackMetadata()}}},
+ SinkMetadata{.tracks = {{getRecordTrackMetadataWithInvalidDest()}}},
+ SinkMetadata{.tracks = {{getInvalidChannelMaskRecordTrackMetadata()}}},
+ SinkMetadata{.tracks = {{getInvalidTagsRecordTrackMetadata()}}},
+ SinkMetadata{.tracks = {{getValidRecordTrackMetadata(),
+ getInvalidSourceRecordTrackMetadata()}}},
+ SinkMetadata{.tracks = {{getValidRecordTrackMetadata(),
+ getRecordTrackMetadataWithInvalidDest()}}},
+ SinkMetadata{.tracks = {{getValidRecordTrackMetadata(),
+ getInvalidChannelMaskRecordTrackMetadata()}}},
+ SinkMetadata{.tracks = {{getValidRecordTrackMetadata(),
+ getInvalidTagsRecordTrackMetadata()}}}};
+ return invalids;
+}
+template <typename T>
+static inline std::vector<SinkOrSourceMetadata> wrapMetadata(const std::vector<T>& metadata) {
+ return std::vector<SinkOrSourceMetadata>{metadata.begin(), metadata.end()};
+}
+
+TEST_P(StreamOpenTest, OpenInputOrOutputStreamTest) {
+ doc::test(
+ "Verify that invalid arguments are rejected by "
+ "IDevice::open{Input|Output}Stream method.");
+ AudioConfig suggestedConfig{};
+ if (isParamForInputStream()) {
+ sp<IStreamIn> stream;
+ ASSERT_OK(getDevice()->openInputStream(AudioIoHandle{}, getDeviceAddress(), getConfig(),
+ getFlags(), getSinkMetadata(),
+ returnIn(res, stream, suggestedConfig)));
+ ASSERT_TRUE(stream == nullptr);
+ } else {
+ sp<IStreamOut> stream;
+ ASSERT_OK(getDevice()->openOutputStream(AudioIoHandle{}, getDeviceAddress(), getConfig(),
+ getFlags(), getSourceMetadata(),
+ returnIn(res, stream, suggestedConfig)));
+ ASSERT_TRUE(stream == nullptr);
+ }
+ EXPECT_EQ(Result::INVALID_ARGUMENTS, res);
+ EXPECT_EQ(AudioConfig{}, suggestedConfig);
+}
+INSTANTIATE_TEST_CASE_P(
+ InputStreamInvalidConfig, StreamOpenTest,
+ ::testing::Combine(::testing::ValuesIn(getInputDeviceInvalidConfigParameters()),
+ ::testing::Values(getValidInputDeviceAddress()),
+ ::testing::Values(SinkMetadata{
+ .tracks = {{getValidRecordTrackMetadata(),
+ getValidRecordTrackMetadataWithDest()}}})),
+ &StreamOpenParameterToString);
+INSTANTIATE_TEST_CASE_P(
+ InputStreamInvalidAddress, StreamOpenTest,
+ ::testing::Combine(::testing::ValuesIn(getInputDeviceSingleConfigParameters()),
+ ::testing::Values(getInvalidDeviceAddress()),
+ ::testing::Values(SinkMetadata{
+ .tracks = {{getValidRecordTrackMetadata(),
+ getValidRecordTrackMetadataWithDest()}}})),
+ &StreamOpenParameterToString);
+INSTANTIATE_TEST_CASE_P(
+ InputStreamInvalidMetadata, StreamOpenTest,
+ ::testing::Combine(::testing::ValuesIn(getInputDeviceSingleConfigParameters()),
+ ::testing::Values(getValidInputDeviceAddress()),
+ ::testing::ValuesIn(wrapMetadata(getInvalidSinkMetadatas()))),
+ &StreamOpenParameterToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(StreamOpenTest);
+
+INSTANTIATE_TEST_CASE_P(
+ OutputStreamInvalidConfig, StreamOpenTest,
+ ::testing::Combine(::testing::ValuesIn(getOutputDeviceInvalidConfigParameters()),
+ ::testing::Values(getValidOutputDeviceAddress()),
+ ::testing::Values(SourceMetadata{
+ .tracks = {{getValidPlaybackTrackMetadata()}}})),
+ &StreamOpenParameterToString);
+INSTANTIATE_TEST_CASE_P(
+ OutputStreamInvalidAddress, StreamOpenTest,
+ ::testing::Combine(::testing::ValuesIn(getOutputDeviceSingleConfigParameters()),
+ ::testing::Values(getInvalidDeviceAddress()),
+ ::testing::Values(SourceMetadata{
+ .tracks = {{getValidPlaybackTrackMetadata()}}})),
+ &StreamOpenParameterToString);
+INSTANTIATE_TEST_CASE_P(
+ OutputStreamInvalidMetadata, StreamOpenTest,
+ ::testing::Combine(::testing::ValuesIn(getOutputDeviceSingleConfigParameters()),
+ ::testing::Values(getValidOutputDeviceAddress()),
+ ::testing::ValuesIn(wrapMetadata(getInvalidSourceMetadatas()))),
+ &StreamOpenParameterToString);
+
+#define TEST_SINGLE_CONFIG_IO_STREAM(test_name, documentation, code) \
+ TEST_P(SingleConfigInputStreamTest, test_name) { \
+ doc::test(documentation); \
+ code; \
+ } \
+ TEST_P(SingleConfigOutputStreamTest, test_name) { \
+ doc::test(documentation); \
+ code; \
+ }
+
+static void testSetDevicesInvalidDeviceAddress(IStream* stream) {
+ ASSERT_RESULT(Result::INVALID_ARGUMENTS, stream->setDevices({getInvalidDeviceAddress()}));
+}
+TEST_SINGLE_CONFIG_IO_STREAM(
+ SetDevicesInvalidDeviceAddress,
+ "Verify that invalid device address is rejected by IStream::setDevices",
+ areAudioPatchesSupported() ? doc::partialTest("Audio patches are supported")
+ : testSetDevicesInvalidDeviceAddress(stream.get()));
+
+static void testSetAudioPropertiesInvalidArguments(IStream* stream, 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/7.0/PolicyConfig.h b/audio/core/all-versions/vts/functional/7.0/PolicyConfig.h
index d790b34..7d88642 100644
--- a/audio/core/all-versions/vts/functional/7.0/PolicyConfig.h
+++ b/audio/core/all-versions/vts/functional/7.0/PolicyConfig.h
@@ -33,10 +33,14 @@
if (mConfig) {
mStatus = OK;
mPrimaryModule = getModuleFromName(DeviceManager::kPrimaryDevice);
- for (const auto& module : mConfig->getFirstModules()->get_module()) {
- auto attachedDevices = module.getFirstAttachedDevices()->getItem();
- if (!attachedDevices.empty()) {
- mModulesWithDevicesNames.insert(module.getName());
+ if (mConfig->getFirstModules()) {
+ for (const auto& module : mConfig->getFirstModules()->get_module()) {
+ if (module.getFirstAttachedDevices()) {
+ auto attachedDevices = module.getFirstAttachedDevices()->getItem();
+ if (!attachedDevices.empty()) {
+ mModulesWithDevicesNames.insert(module.getName());
+ }
+ }
}
}
}
@@ -52,7 +56,7 @@
}
const std::string& getFilePath() const { return mFilePath; }
const xsd::Module* getModuleFromName(const std::string& name) const {
- if (mConfig) {
+ if (mConfig && mConfig->getFirstModules()) {
for (const auto& module : mConfig->getFirstModules()->get_module()) {
if (module.getName() == name) return &module;
}
@@ -65,8 +69,10 @@
}
bool haveInputProfilesInModule(const std::string& name) const {
auto module = getModuleFromName(name);
- for (const auto& mixPort : module->getFirstMixPorts()->getMixPort()) {
- if (mixPort.getRole() == xsd::Role::sink) return true;
+ if (module && module->getFirstMixPorts()) {
+ for (const auto& mixPort : module->getFirstMixPorts()->getMixPort()) {
+ if (mixPort.getRole() == xsd::Role::sink) return true;
+ }
}
return false;
}
diff --git a/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h b/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h
index 1ead47c..f145b60 100644
--- a/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h
+++ b/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h
@@ -156,6 +156,21 @@
return *policyConfig;
}
+TEST(CheckConfig, audioPolicyConfigurationValidation) {
+ const auto factories = ::android::hardware::getAllHalInstanceNames(IDevicesFactory::descriptor);
+ if (factories.size() == 0) {
+ GTEST_SKIP() << "Skipping audioPolicyConfigurationValidation because no factory instances "
+ "are found.";
+ }
+ RecordProperty("description",
+ "Verify that the audio policy configuration file "
+ "is valid according to the schema");
+
+ const char* xsd = "/data/local/tmp/audio_policy_configuration_" STRINGIFY(CPP_VERSION) ".xsd";
+ EXPECT_ONE_VALID_XML_MULTIPLE_LOCATIONS(kConfigFileName,
+ android::audio_get_configuration_paths(), xsd);
+}
+
//////////////////////////////////////////////////////////////////////////////
//////////////////// Test parameter types and definitions ////////////////////
//////////////////////////////////////////////////////////////////////////////
@@ -231,21 +246,6 @@
}
};
-TEST(CheckConfig, audioPolicyConfigurationValidation) {
- auto deviceParameters = getDeviceParametersForFactoryTests();
- if (deviceParameters.size() == 0) {
- GTEST_SKIP() << "Skipping audioPolicyConfigurationValidation because no device parameter "
- "is found.";
- }
- RecordProperty("description",
- "Verify that the audio policy configuration file "
- "is valid according to the schema");
-
- const char* xsd = "/data/local/tmp/audio_policy_configuration_" STRINGIFY(CPP_VERSION) ".xsd";
- EXPECT_ONE_VALID_XML_MULTIPLE_LOCATIONS(kConfigFileName,
- android::audio_get_configuration_paths(), xsd);
-}
-
class AudioPolicyConfigTest : public AudioHidlTestWithDeviceParameter {
public:
void SetUp() override {
@@ -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
};
@@ -1179,9 +1186,11 @@
EXPECT_EQ(expectedConfig.channelMask, mask);
EXPECT_EQ(expectedConfig.format, format);
#elif MAJOR_VERSION >= 7
+ Result res;
AudioConfigBase actualConfig{};
- auto ret = stream->getAudioProperties(returnIn(actualConfig));
+ auto ret = stream->getAudioProperties(returnIn(res, actualConfig));
EXPECT_TRUE(ret.isOk());
+ EXPECT_EQ(Result::OK, res);
EXPECT_EQ(expectedConfig.base.sampleRateHz, actualConfig.sampleRateHz);
EXPECT_EQ(expectedConfig.base.channelMask, actualConfig.channelMask);
EXPECT_EQ(expectedConfig.base.format, actualConfig.format);
diff --git a/audio/effect/7.0/IVirtualizerEffect.hal b/audio/effect/7.0/IVirtualizerEffect.hal
index 141b4e6..5d11435 100644
--- a/audio/effect/7.0/IVirtualizerEffect.hal
+++ b/audio/effect/7.0/IVirtualizerEffect.hal
@@ -46,23 +46,38 @@
*/
getStrength() generates (Result retval, uint16_t strength);
- struct SpeakerAngle {
+ struct SpeakerAngles {
/** Speaker channel mask */
- vec<AudioChannelMask> mask;
- // all angles are expressed in degrees and
- // are relative to the listener.
- int16_t azimuth; // 0 is the direction the listener faces
- // 180 is behind the listener
- // -90 is to their left
- int16_t elevation; // 0 is the horizontal plane
- // +90 is above the listener, -90 is below
+ AudioChannelMask mask;
+ /**
+ * Horizontal speaker position angles for each channel ordered from LSb
+ * to MSb in the channel mask. The number of values is the number of
+ * channels in the channel mask.
+ *
+ * All angles are expressed in degrees and are relative to the listener.
+ * - 0 is the direction the listener faces;
+ * - 180 is behind the listener;
+ * - -90 is to their left.
+ */
+ vec<int16_t> azimuth;
+ /**
+ * Vertical speaker position angles for each channel ordered from LSb
+ * to MSb in the channel mask. The number of values is the number of
+ * channels in the channel mask.
+ *
+ * All angles are expressed in degrees and are relative to the listener.
+ * - 0 is the horizontal plane of the listener;
+ * - +90 is above the listener;
+ * - -90 is below the listener.
+ */
+ vec<int16_t> elevation;
};
/**
* Retrieves virtual speaker angles for the given channel mask on the
* specified device.
*/
- getVirtualSpeakerAngles(vec<AudioChannelMask> mask, DeviceAddress device)
- generates (Result retval, vec<SpeakerAngle> speakerAngles);
+ getVirtualSpeakerAngles(AudioChannelMask mask, DeviceAddress device)
+ generates (Result retval, SpeakerAngles speakerAngles);
/**
* Forces the virtualizer effect for the given output device.
diff --git a/audio/effect/7.0/types.hal b/audio/effect/7.0/types.hal
index b0a0709..c4cb213 100644
--- a/audio/effect/7.0/types.hal
+++ b/audio/effect/7.0/types.hal
@@ -271,9 +271,7 @@
*/
struct EffectBufferConfig {
AudioBuffer buffer;
- uint32_t samplingRateHz;
- AudioChannelMask channels;
- AudioFormat format;
+ AudioConfigBase base;
EffectBufferAccess accessMode;
bitfield<EffectConfigParameters> mask;
};
@@ -292,9 +290,9 @@
struct EffectAuxChannelsConfig {
/** Channel mask for main channels. */
- vec<AudioChannelMask> mainChannels;
+ AudioChannelMask mainChannels;
/** Channel mask for auxiliary channels. */
- vec<AudioChannelMask> auxChannels;
+ AudioChannelMask auxChannels;
};
struct EffectOffloadParameter {
diff --git a/audio/effect/all-versions/default/AcousticEchoCancelerEffect.cpp b/audio/effect/all-versions/default/AcousticEchoCancelerEffect.cpp
index 137ea24..c1a8b55 100644
--- a/audio/effect/all-versions/default/AcousticEchoCancelerEffect.cpp
+++ b/audio/effect/all-versions/default/AcousticEchoCancelerEffect.cpp
@@ -31,9 +31,7 @@
namespace implementation {
AcousticEchoCancelerEffect::AcousticEchoCancelerEffect(effect_handle_t handle)
- : mEffect(new Effect(handle)) {}
-
-AcousticEchoCancelerEffect::~AcousticEchoCancelerEffect() {}
+ : mEffect(new Effect(true /*isInput*/, handle)) {}
// Methods from ::android::hardware::audio::effect::CPP_VERSION::IEffect follow.
Return<Result> AcousticEchoCancelerEffect::init() {
@@ -58,10 +56,32 @@
return mEffect->disable();
}
+#if MAJOR_VERSION <= 6
+Return<Result> AcousticEchoCancelerEffect::setAudioSource(AudioSource source) {
+ return mEffect->setAudioSource(source);
+}
+
Return<Result> AcousticEchoCancelerEffect::setDevice(AudioDeviceBitfield device) {
return mEffect->setDevice(device);
}
+Return<Result> AcousticEchoCancelerEffect::setInputDevice(AudioDeviceBitfield device) {
+ return mEffect->setInputDevice(device);
+}
+#else
+Return<Result> AcousticEchoCancelerEffect::setAudioSource(const AudioSource& source) {
+ return mEffect->setAudioSource(source);
+}
+
+Return<Result> AcousticEchoCancelerEffect::setDevice(const DeviceAddress& device) {
+ return mEffect->setDevice(device);
+}
+
+Return<Result> AcousticEchoCancelerEffect::setInputDevice(const DeviceAddress& device) {
+ return mEffect->setInputDevice(device);
+}
+#endif
+
Return<void> AcousticEchoCancelerEffect::setAndGetVolume(const hidl_vec<uint32_t>& volumes,
setAndGetVolume_cb _hidl_cb) {
return mEffect->setAndGetVolume(volumes, _hidl_cb);
@@ -82,10 +102,6 @@
return mEffect->setConfigReverse(config, inputBufferProvider, outputBufferProvider);
}
-Return<Result> AcousticEchoCancelerEffect::setInputDevice(AudioDeviceBitfield device) {
- return mEffect->setInputDevice(device);
-}
-
Return<void> AcousticEchoCancelerEffect::getConfig(getConfig_cb _hidl_cb) {
return mEffect->getConfig(_hidl_cb);
}
@@ -108,10 +124,6 @@
return mEffect->setAuxChannelsConfig(config);
}
-Return<Result> AcousticEchoCancelerEffect::setAudioSource(AudioSource source) {
- return mEffect->setAudioSource(source);
-}
-
Return<Result> AcousticEchoCancelerEffect::offload(const EffectOffloadParameter& param) {
return mEffect->offload(param);
}
diff --git a/audio/effect/all-versions/default/AcousticEchoCancelerEffect.h b/audio/effect/all-versions/default/AcousticEchoCancelerEffect.h
index 971f64d..d7a84f2 100644
--- a/audio/effect/all-versions/default/AcousticEchoCancelerEffect.h
+++ b/audio/effect/all-versions/default/AcousticEchoCancelerEffect.h
@@ -53,7 +53,15 @@
Return<Result> reset() override;
Return<Result> enable() override;
Return<Result> disable() override;
+#if MAJOR_VERSION <= 6
+ Return<Result> setAudioSource(AudioSource source) override;
Return<Result> setDevice(AudioDeviceBitfield device) override;
+ Return<Result> setInputDevice(AudioDeviceBitfield device) override;
+#else
+ Return<Result> setAudioSource(const AudioSource& source) override;
+ Return<Result> setDevice(const DeviceAddress& device) override;
+ Return<Result> setInputDevice(const DeviceAddress& device) override;
+#endif
Return<void> setAndGetVolume(const hidl_vec<uint32_t>& volumes,
setAndGetVolume_cb _hidl_cb) override;
Return<Result> volumeChangeNotification(const hidl_vec<uint32_t>& volumes) override;
@@ -61,14 +69,12 @@
Return<Result> setConfigReverse(
const EffectConfig& config, const sp<IEffectBufferProviderCallback>& inputBufferProvider,
const sp<IEffectBufferProviderCallback>& outputBufferProvider) override;
- Return<Result> setInputDevice(AudioDeviceBitfield device) override;
Return<void> getConfig(getConfig_cb _hidl_cb) override;
Return<void> getConfigReverse(getConfigReverse_cb _hidl_cb) override;
Return<void> getSupportedAuxChannelsConfigs(
uint32_t maxConfigs, getSupportedAuxChannelsConfigs_cb _hidl_cb) override;
Return<void> getAuxChannelsConfig(getAuxChannelsConfig_cb _hidl_cb) override;
Return<Result> setAuxChannelsConfig(const EffectAuxChannelsConfig& config) override;
- Return<Result> setAudioSource(AudioSource source) override;
Return<Result> offload(const EffectOffloadParameter& param) override;
Return<void> getDescriptor(getDescriptor_cb _hidl_cb) override;
Return<void> prepareForProcessing(prepareForProcessing_cb _hidl_cb) override;
@@ -98,7 +104,7 @@
private:
sp<Effect> mEffect;
- virtual ~AcousticEchoCancelerEffect();
+ virtual ~AcousticEchoCancelerEffect() = default;
};
} // namespace implementation
diff --git a/audio/effect/all-versions/default/Android.bp b/audio/effect/all-versions/default/Android.bp
index 1c3dc74..a0cd612 100644
--- a/audio/effect/all-versions/default/Android.bp
+++ b/audio/effect/all-versions/default/Android.bp
@@ -105,7 +105,6 @@
}
cc_library_shared {
- enabled: false,
name: "android.hardware.audio.effect@7.0-impl",
defaults: ["android.hardware.audio.effect-impl_default"],
shared_libs: [
diff --git a/audio/effect/all-versions/default/AutomaticGainControlEffect.cpp b/audio/effect/all-versions/default/AutomaticGainControlEffect.cpp
index 655a4cd..110b1b6 100644
--- a/audio/effect/all-versions/default/AutomaticGainControlEffect.cpp
+++ b/audio/effect/all-versions/default/AutomaticGainControlEffect.cpp
@@ -30,9 +30,7 @@
namespace implementation {
AutomaticGainControlEffect::AutomaticGainControlEffect(effect_handle_t handle)
- : mEffect(new Effect(handle)) {}
-
-AutomaticGainControlEffect::~AutomaticGainControlEffect() {}
+ : mEffect(new Effect(true /*isInput*/, handle)) {}
void AutomaticGainControlEffect::propertiesFromHal(
const t_agc_settings& halProperties, IAutomaticGainControlEffect::AllProperties* properties) {
@@ -71,10 +69,32 @@
return mEffect->disable();
}
+#if MAJOR_VERSION <= 6
+Return<Result> AutomaticGainControlEffect::setAudioSource(AudioSource source) {
+ return mEffect->setAudioSource(source);
+}
+
Return<Result> AutomaticGainControlEffect::setDevice(AudioDeviceBitfield device) {
return mEffect->setDevice(device);
}
+Return<Result> AutomaticGainControlEffect::setInputDevice(AudioDeviceBitfield device) {
+ return mEffect->setInputDevice(device);
+}
+#else
+Return<Result> AutomaticGainControlEffect::setAudioSource(const AudioSource& source) {
+ return mEffect->setAudioSource(source);
+}
+
+Return<Result> AutomaticGainControlEffect::setDevice(const DeviceAddress& device) {
+ return mEffect->setDevice(device);
+}
+
+Return<Result> AutomaticGainControlEffect::setInputDevice(const DeviceAddress& device) {
+ return mEffect->setInputDevice(device);
+}
+#endif
+
Return<void> AutomaticGainControlEffect::setAndGetVolume(const hidl_vec<uint32_t>& volumes,
setAndGetVolume_cb _hidl_cb) {
return mEffect->setAndGetVolume(volumes, _hidl_cb);
@@ -95,10 +115,6 @@
return mEffect->setConfigReverse(config, inputBufferProvider, outputBufferProvider);
}
-Return<Result> AutomaticGainControlEffect::setInputDevice(AudioDeviceBitfield device) {
- return mEffect->setInputDevice(device);
-}
-
Return<void> AutomaticGainControlEffect::getConfig(getConfig_cb _hidl_cb) {
return mEffect->getConfig(_hidl_cb);
}
@@ -121,10 +137,6 @@
return mEffect->setAuxChannelsConfig(config);
}
-Return<Result> AutomaticGainControlEffect::setAudioSource(AudioSource source) {
- return mEffect->setAudioSource(source);
-}
-
Return<Result> AutomaticGainControlEffect::offload(const EffectOffloadParameter& param) {
return mEffect->offload(param);
}
diff --git a/audio/effect/all-versions/default/AutomaticGainControlEffect.h b/audio/effect/all-versions/default/AutomaticGainControlEffect.h
index 67e260a..f30d7a5 100644
--- a/audio/effect/all-versions/default/AutomaticGainControlEffect.h
+++ b/audio/effect/all-versions/default/AutomaticGainControlEffect.h
@@ -55,7 +55,15 @@
Return<Result> reset() override;
Return<Result> enable() override;
Return<Result> disable() override;
+#if MAJOR_VERSION <= 6
+ Return<Result> setAudioSource(AudioSource source) override;
Return<Result> setDevice(AudioDeviceBitfield device) override;
+ Return<Result> setInputDevice(AudioDeviceBitfield device) override;
+#else
+ Return<Result> setAudioSource(const AudioSource& source) override;
+ Return<Result> setDevice(const DeviceAddress& device) override;
+ Return<Result> setInputDevice(const DeviceAddress& device) override;
+#endif
Return<void> setAndGetVolume(const hidl_vec<uint32_t>& volumes,
setAndGetVolume_cb _hidl_cb) override;
Return<Result> volumeChangeNotification(const hidl_vec<uint32_t>& volumes) override;
@@ -63,14 +71,12 @@
Return<Result> setConfigReverse(
const EffectConfig& config, const sp<IEffectBufferProviderCallback>& inputBufferProvider,
const sp<IEffectBufferProviderCallback>& outputBufferProvider) override;
- Return<Result> setInputDevice(AudioDeviceBitfield device) override;
Return<void> getConfig(getConfig_cb _hidl_cb) override;
Return<void> getConfigReverse(getConfigReverse_cb _hidl_cb) override;
Return<void> getSupportedAuxChannelsConfigs(
uint32_t maxConfigs, getSupportedAuxChannelsConfigs_cb _hidl_cb) override;
Return<void> getAuxChannelsConfig(getAuxChannelsConfig_cb _hidl_cb) override;
Return<Result> setAuxChannelsConfig(const EffectAuxChannelsConfig& config) override;
- Return<Result> setAudioSource(AudioSource source) override;
Return<Result> offload(const EffectOffloadParameter& param) override;
Return<void> getDescriptor(getDescriptor_cb _hidl_cb) override;
Return<void> prepareForProcessing(prepareForProcessing_cb _hidl_cb) override;
@@ -107,7 +113,7 @@
private:
sp<Effect> mEffect;
- virtual ~AutomaticGainControlEffect();
+ virtual ~AutomaticGainControlEffect() = default;
void propertiesFromHal(const t_agc_settings& halProperties,
IAutomaticGainControlEffect::AllProperties* properties);
diff --git a/audio/effect/all-versions/default/BassBoostEffect.cpp b/audio/effect/all-versions/default/BassBoostEffect.cpp
index 04fd486..33fea3b 100644
--- a/audio/effect/all-versions/default/BassBoostEffect.cpp
+++ b/audio/effect/all-versions/default/BassBoostEffect.cpp
@@ -30,9 +30,8 @@
namespace CPP_VERSION {
namespace implementation {
-BassBoostEffect::BassBoostEffect(effect_handle_t handle) : mEffect(new Effect(handle)) {}
-
-BassBoostEffect::~BassBoostEffect() {}
+BassBoostEffect::BassBoostEffect(effect_handle_t handle)
+ : mEffect(new Effect(false /*isInput*/, handle)) {}
// Methods from ::android::hardware::audio::effect::CPP_VERSION::IEffect follow.
Return<Result> BassBoostEffect::init() {
@@ -57,10 +56,32 @@
return mEffect->disable();
}
+#if MAJOR_VERSION <= 6
+Return<Result> BassBoostEffect::setAudioSource(AudioSource source) {
+ return mEffect->setAudioSource(source);
+}
+
Return<Result> BassBoostEffect::setDevice(AudioDeviceBitfield device) {
return mEffect->setDevice(device);
}
+Return<Result> BassBoostEffect::setInputDevice(AudioDeviceBitfield device) {
+ return mEffect->setInputDevice(device);
+}
+#else
+Return<Result> BassBoostEffect::setAudioSource(const AudioSource& source) {
+ return mEffect->setAudioSource(source);
+}
+
+Return<Result> BassBoostEffect::setDevice(const DeviceAddress& device) {
+ return mEffect->setDevice(device);
+}
+
+Return<Result> BassBoostEffect::setInputDevice(const DeviceAddress& device) {
+ return mEffect->setInputDevice(device);
+}
+#endif
+
Return<void> BassBoostEffect::setAndGetVolume(const hidl_vec<uint32_t>& volumes,
setAndGetVolume_cb _hidl_cb) {
return mEffect->setAndGetVolume(volumes, _hidl_cb);
@@ -80,10 +101,6 @@
return mEffect->setConfigReverse(config, inputBufferProvider, outputBufferProvider);
}
-Return<Result> BassBoostEffect::setInputDevice(AudioDeviceBitfield device) {
- return mEffect->setInputDevice(device);
-}
-
Return<void> BassBoostEffect::getConfig(getConfig_cb _hidl_cb) {
return mEffect->getConfig(_hidl_cb);
}
@@ -105,10 +122,6 @@
return mEffect->setAuxChannelsConfig(config);
}
-Return<Result> BassBoostEffect::setAudioSource(AudioSource source) {
- return mEffect->setAudioSource(source);
-}
-
Return<Result> BassBoostEffect::offload(const EffectOffloadParameter& param) {
return mEffect->offload(param);
}
diff --git a/audio/effect/all-versions/default/BassBoostEffect.h b/audio/effect/all-versions/default/BassBoostEffect.h
index b89bb22..48f586c 100644
--- a/audio/effect/all-versions/default/BassBoostEffect.h
+++ b/audio/effect/all-versions/default/BassBoostEffect.h
@@ -55,7 +55,15 @@
Return<Result> reset() override;
Return<Result> enable() override;
Return<Result> disable() override;
+#if MAJOR_VERSION <= 6
+ Return<Result> setAudioSource(AudioSource source) override;
Return<Result> setDevice(AudioDeviceBitfield device) override;
+ Return<Result> setInputDevice(AudioDeviceBitfield device) override;
+#else
+ Return<Result> setAudioSource(const AudioSource& source) override;
+ Return<Result> setDevice(const DeviceAddress& device) override;
+ Return<Result> setInputDevice(const DeviceAddress& device) override;
+#endif
Return<void> setAndGetVolume(const hidl_vec<uint32_t>& volumes,
setAndGetVolume_cb _hidl_cb) override;
Return<Result> volumeChangeNotification(const hidl_vec<uint32_t>& volumes) override;
@@ -63,14 +71,12 @@
Return<Result> setConfigReverse(
const EffectConfig& config, const sp<IEffectBufferProviderCallback>& inputBufferProvider,
const sp<IEffectBufferProviderCallback>& outputBufferProvider) override;
- Return<Result> setInputDevice(AudioDeviceBitfield device) override;
Return<void> getConfig(getConfig_cb _hidl_cb) override;
Return<void> getConfigReverse(getConfigReverse_cb _hidl_cb) override;
Return<void> getSupportedAuxChannelsConfigs(
uint32_t maxConfigs, getSupportedAuxChannelsConfigs_cb _hidl_cb) override;
Return<void> getAuxChannelsConfig(getAuxChannelsConfig_cb _hidl_cb) override;
Return<Result> setAuxChannelsConfig(const EffectAuxChannelsConfig& config) override;
- Return<Result> setAudioSource(AudioSource source) override;
Return<Result> offload(const EffectOffloadParameter& param) override;
Return<void> getDescriptor(getDescriptor_cb _hidl_cb) override;
Return<void> prepareForProcessing(prepareForProcessing_cb _hidl_cb) override;
@@ -100,7 +106,7 @@
private:
sp<Effect> mEffect;
- virtual ~BassBoostEffect();
+ virtual ~BassBoostEffect() = default;
};
} // namespace implementation
diff --git a/audio/effect/all-versions/default/DownmixEffect.cpp b/audio/effect/all-versions/default/DownmixEffect.cpp
index c001a5f..f324cff 100644
--- a/audio/effect/all-versions/default/DownmixEffect.cpp
+++ b/audio/effect/all-versions/default/DownmixEffect.cpp
@@ -30,9 +30,8 @@
namespace CPP_VERSION {
namespace implementation {
-DownmixEffect::DownmixEffect(effect_handle_t handle) : mEffect(new Effect(handle)) {}
-
-DownmixEffect::~DownmixEffect() {}
+DownmixEffect::DownmixEffect(effect_handle_t handle)
+ : mEffect(new Effect(false /*isInput*/, handle)) {}
// Methods from ::android::hardware::audio::effect::CPP_VERSION::IEffect follow.
Return<Result> DownmixEffect::init() {
@@ -57,10 +56,32 @@
return mEffect->disable();
}
+#if MAJOR_VERSION <= 6
+Return<Result> DownmixEffect::setAudioSource(AudioSource source) {
+ return mEffect->setAudioSource(source);
+}
+
Return<Result> DownmixEffect::setDevice(AudioDeviceBitfield device) {
return mEffect->setDevice(device);
}
+Return<Result> DownmixEffect::setInputDevice(AudioDeviceBitfield device) {
+ return mEffect->setInputDevice(device);
+}
+#else
+Return<Result> DownmixEffect::setAudioSource(const AudioSource& source) {
+ return mEffect->setAudioSource(source);
+}
+
+Return<Result> DownmixEffect::setDevice(const DeviceAddress& device) {
+ return mEffect->setDevice(device);
+}
+
+Return<Result> DownmixEffect::setInputDevice(const DeviceAddress& device) {
+ return mEffect->setInputDevice(device);
+}
+#endif
+
Return<void> DownmixEffect::setAndGetVolume(const hidl_vec<uint32_t>& volumes,
setAndGetVolume_cb _hidl_cb) {
return mEffect->setAndGetVolume(volumes, _hidl_cb);
@@ -80,10 +101,6 @@
return mEffect->setConfigReverse(config, inputBufferProvider, outputBufferProvider);
}
-Return<Result> DownmixEffect::setInputDevice(AudioDeviceBitfield device) {
- return mEffect->setInputDevice(device);
-}
-
Return<void> DownmixEffect::getConfig(getConfig_cb _hidl_cb) {
return mEffect->getConfig(_hidl_cb);
}
@@ -105,10 +122,6 @@
return mEffect->setAuxChannelsConfig(config);
}
-Return<Result> DownmixEffect::setAudioSource(AudioSource source) {
- return mEffect->setAudioSource(source);
-}
-
Return<Result> DownmixEffect::offload(const EffectOffloadParameter& param) {
return mEffect->offload(param);
}
diff --git a/audio/effect/all-versions/default/DownmixEffect.h b/audio/effect/all-versions/default/DownmixEffect.h
index 40e462e..caacd06 100644
--- a/audio/effect/all-versions/default/DownmixEffect.h
+++ b/audio/effect/all-versions/default/DownmixEffect.h
@@ -53,7 +53,15 @@
Return<Result> reset() override;
Return<Result> enable() override;
Return<Result> disable() override;
+#if MAJOR_VERSION <= 6
+ Return<Result> setAudioSource(AudioSource source) override;
Return<Result> setDevice(AudioDeviceBitfield device) override;
+ Return<Result> setInputDevice(AudioDeviceBitfield device) override;
+#else
+ Return<Result> setAudioSource(const AudioSource& source) override;
+ Return<Result> setDevice(const DeviceAddress& device) override;
+ Return<Result> setInputDevice(const DeviceAddress& device) override;
+#endif
Return<void> setAndGetVolume(const hidl_vec<uint32_t>& volumes,
setAndGetVolume_cb _hidl_cb) override;
Return<Result> volumeChangeNotification(const hidl_vec<uint32_t>& volumes) override;
@@ -61,14 +69,12 @@
Return<Result> setConfigReverse(
const EffectConfig& config, const sp<IEffectBufferProviderCallback>& inputBufferProvider,
const sp<IEffectBufferProviderCallback>& outputBufferProvider) override;
- Return<Result> setInputDevice(AudioDeviceBitfield device) override;
Return<void> getConfig(getConfig_cb _hidl_cb) override;
Return<void> getConfigReverse(getConfigReverse_cb _hidl_cb) override;
Return<void> getSupportedAuxChannelsConfigs(
uint32_t maxConfigs, getSupportedAuxChannelsConfigs_cb _hidl_cb) override;
Return<void> getAuxChannelsConfig(getAuxChannelsConfig_cb _hidl_cb) override;
Return<Result> setAuxChannelsConfig(const EffectAuxChannelsConfig& config) override;
- Return<Result> setAudioSource(AudioSource source) override;
Return<Result> offload(const EffectOffloadParameter& param) override;
Return<void> getDescriptor(getDescriptor_cb _hidl_cb) override;
Return<void> prepareForProcessing(prepareForProcessing_cb _hidl_cb) override;
@@ -97,7 +103,7 @@
private:
sp<Effect> mEffect;
- virtual ~DownmixEffect();
+ virtual ~DownmixEffect() = default;
};
} // namespace implementation
diff --git a/audio/effect/all-versions/default/Effect.cpp b/audio/effect/all-versions/default/Effect.cpp
index 406a571..edd364c 100644
--- a/audio/effect/all-versions/default/Effect.cpp
+++ b/audio/effect/all-versions/default/Effect.cpp
@@ -27,6 +27,7 @@
#define ATRACE_TAG ATRACE_TAG_AUDIO
+#include <HidlUtils.h>
#include <android/log.h>
#include <media/EffectsFactoryApi.h>
#include <utils/Trace.h>
@@ -40,7 +41,10 @@
namespace CPP_VERSION {
namespace implementation {
+#if MAJOR_VERSION <= 6
using ::android::hardware::audio::common::CPP_VERSION::implementation::AudioChannelBitfield;
+#endif
+using ::android::hardware::audio::common::CPP_VERSION::implementation::HidlUtils;
namespace {
@@ -136,9 +140,12 @@
const char* Effect::sContextResultOfCommand = "returned status";
const char* Effect::sContextCallToCommand = "error";
const char* Effect::sContextCallFunction = sContextCallToCommand;
+const char* Effect::sContextConversion = "conversion";
-Effect::Effect(effect_handle_t handle)
- : mHandle(handle), mEfGroup(nullptr), mStopProcessThread(false) {}
+Effect::Effect(bool isInput, effect_handle_t handle)
+ : mIsInput(isInput), mHandle(handle), mEfGroup(nullptr), mStopProcessThread(false) {
+ (void)mIsInput; // prevent 'unused field' warnings in pre-V7 versions.
+}
Effect::~Effect() {
ATRACE_CALL();
@@ -180,7 +187,8 @@
return halData;
}
-// static
+#if MAJOR_VERSION <= 6
+
void Effect::effectAuxChannelsConfigFromHal(const channel_config_t& halConfig,
EffectAuxChannelsConfig* config) {
config->mainChannels = AudioChannelBitfield(halConfig.main_channels);
@@ -194,7 +202,6 @@
halConfig->aux_channels = static_cast<audio_channel_mask_t>(config.auxChannels);
}
-// static
void Effect::effectBufferConfigFromHal(const buffer_config_t& halConfig,
EffectBufferConfig* config) {
config->buffer.id = 0;
@@ -223,7 +230,56 @@
halConfig->mask = static_cast<uint8_t>(config.mask);
}
+#else // MAJOR_VERSION <= 6
+
+void Effect::effectAuxChannelsConfigFromHal(const channel_config_t& halConfig,
+ EffectAuxChannelsConfig* config) {
+ (void)HidlUtils::audioChannelMaskFromHal(halConfig.main_channels, mIsInput,
+ &config->mainChannels);
+ (void)HidlUtils::audioChannelMaskFromHal(halConfig.aux_channels, mIsInput,
+ &config->auxChannels);
+}
+
// static
+void Effect::effectAuxChannelsConfigToHal(const EffectAuxChannelsConfig& config,
+ channel_config_t* halConfig) {
+ (void)HidlUtils::audioChannelMaskToHal(config.mainChannels, &halConfig->main_channels);
+ (void)HidlUtils::audioChannelMaskToHal(config.auxChannels, &halConfig->aux_channels);
+}
+
+void Effect::effectBufferConfigFromHal(const buffer_config_t& halConfig,
+ EffectBufferConfig* config) {
+ config->buffer.id = 0;
+ config->buffer.frameCount = 0;
+ audio_config_base_t halConfigBase = {halConfig.samplingRate,
+ static_cast<audio_channel_mask_t>(halConfig.channels),
+ static_cast<audio_format_t>(halConfig.format)};
+ (void)HidlUtils::audioConfigBaseFromHal(halConfigBase, mIsInput, &config->base);
+ config->accessMode = EffectBufferAccess(halConfig.accessMode);
+ config->mask = static_cast<decltype(config->mask)>(halConfig.mask);
+}
+
+// static
+void Effect::effectBufferConfigToHal(const EffectBufferConfig& config, buffer_config_t* halConfig) {
+ // Note: setting the buffers directly is considered obsolete. They need to be set
+ // using 'setProcessBuffers'.
+ halConfig->buffer.frameCount = 0;
+ halConfig->buffer.raw = nullptr;
+ audio_config_base_t halConfigBase;
+ (void)HidlUtils::audioConfigBaseToHal(config.base, &halConfigBase);
+ halConfig->samplingRate = halConfigBase.sample_rate;
+ halConfig->channels = halConfigBase.channel_mask;
+ halConfig->format = halConfigBase.format;
+ // Note: The framework code does not use BP.
+ halConfig->bufferProvider.cookie = nullptr;
+ halConfig->bufferProvider.getBuffer = nullptr;
+ halConfig->bufferProvider.releaseBuffer = nullptr;
+ halConfig->accessMode = static_cast<uint8_t>(config.accessMode);
+ halConfig->mask = static_cast<uint8_t>(config.mask);
+}
+
+#endif // MAJOR_VERSION <= 6
+
void Effect::effectConfigFromHal(const effect_config_t& halConfig, EffectConfig* config) {
effectBufferConfigFromHal(halConfig.inputCfg, &config->inputCfg);
effectBufferConfigFromHal(halConfig.outputCfg, &config->outputCfg);
@@ -507,11 +563,65 @@
return sendCommandReturningStatus(EFFECT_CMD_DISABLE, "DISABLE");
}
+Return<Result> Effect::setAudioSource(
+#if MAJOR_VERSION <= 6
+ AudioSource source
+#else
+ const AudioSource& source
+#endif
+) {
+ audio_source_t halSource;
+ if (status_t status = HidlUtils::audioSourceToHal(source, &halSource); status == NO_ERROR) {
+ uint32_t halSourceParam = static_cast<uint32_t>(halSource);
+ return sendCommand(EFFECT_CMD_SET_AUDIO_SOURCE, "SET_AUDIO_SOURCE", sizeof(uint32_t),
+ &halSourceParam);
+ } else {
+ return analyzeStatus(__func__, "audioSourceToHal", sContextConversion, status);
+ }
+}
+
+#if MAJOR_VERSION <= 6
+
Return<Result> Effect::setDevice(AudioDeviceBitfield device) {
uint32_t halDevice = static_cast<uint32_t>(device);
return sendCommand(EFFECT_CMD_SET_DEVICE, "SET_DEVICE", sizeof(uint32_t), &halDevice);
}
+Return<Result> Effect::setInputDevice(AudioDeviceBitfield device) {
+ uint32_t halDevice = static_cast<uint32_t>(device);
+ return sendCommand(EFFECT_CMD_SET_INPUT_DEVICE, "SET_INPUT_DEVICE", sizeof(uint32_t),
+ &halDevice);
+}
+
+#else // MAJOR_VERSION <= 6
+
+Return<Result> Effect::setDevice(const DeviceAddress& device) {
+ audio_devices_t halDevice;
+ char halDeviceAddress[AUDIO_DEVICE_MAX_ADDRESS_LEN];
+ if (status_t status = HidlUtils::deviceAddressToHal(device, &halDevice, halDeviceAddress);
+ status == NO_ERROR) {
+ uint32_t halDeviceParam = static_cast<uint32_t>(halDevice);
+ return sendCommand(EFFECT_CMD_SET_DEVICE, "SET_DEVICE", sizeof(uint32_t), &halDeviceParam);
+ } else {
+ return analyzeStatus(__func__, "deviceAddressToHal", sContextConversion, status);
+ }
+}
+
+Return<Result> Effect::setInputDevice(const DeviceAddress& device) {
+ audio_devices_t halDevice;
+ char halDeviceAddress[AUDIO_DEVICE_MAX_ADDRESS_LEN];
+ if (status_t status = HidlUtils::deviceAddressToHal(device, &halDevice, halDeviceAddress);
+ status == NO_ERROR) {
+ uint32_t halDeviceParam = static_cast<uint32_t>(halDevice);
+ return sendCommand(EFFECT_CMD_SET_INPUT_DEVICE, "SET_INPUT_DEVICE", sizeof(uint32_t),
+ &halDeviceParam);
+ } else {
+ return analyzeStatus(__func__, "deviceAddressToHal", sContextConversion, status);
+ }
+}
+
+#endif // MAJOR_VERSION <= 6
+
Return<void> Effect::setAndGetVolume(const hidl_vec<uint32_t>& volumes,
setAndGetVolume_cb _hidl_cb) {
uint32_t halDataSize;
@@ -546,12 +656,6 @@
inputBufferProvider, outputBufferProvider);
}
-Return<Result> Effect::setInputDevice(AudioDeviceBitfield device) {
- uint32_t halDevice = static_cast<uint32_t>(device);
- return sendCommand(EFFECT_CMD_SET_INPUT_DEVICE, "SET_INPUT_DEVICE", sizeof(uint32_t),
- &halDevice);
-}
-
Return<void> Effect::getConfig(getConfig_cb _hidl_cb) {
getConfigImpl(EFFECT_CMD_GET_CONFIG, "GET_CONFIG", _hidl_cb);
return Void();
@@ -598,12 +702,6 @@
"SET_FEATURE_CONFIG AUX_CHANNELS", halCmd.size(), &halCmd[0]);
}
-Return<Result> Effect::setAudioSource(AudioSource source) {
- uint32_t halSource = static_cast<uint32_t>(source);
- return sendCommand(EFFECT_CMD_SET_AUDIO_SOURCE, "SET_AUDIO_SOURCE", sizeof(uint32_t),
- &halSource);
-}
-
Return<Result> Effect::offload(const EffectOffloadParameter& param) {
effect_offload_param_t halParam;
effectOffloadParamToHal(param, &halParam);
diff --git a/audio/effect/all-versions/default/Effect.h b/audio/effect/all-versions/default/Effect.h
index 181e542..9aa47ea 100644
--- a/audio/effect/all-versions/default/Effect.h
+++ b/audio/effect/all-versions/default/Effect.h
@@ -47,7 +47,9 @@
using ::android::hardware::hidl_vec;
using ::android::hardware::Return;
using ::android::hardware::Void;
+#if MAJOR_VERSION <= 6
using ::android::hardware::audio::common::CPP_VERSION::implementation::AudioDeviceBitfield;
+#endif
using namespace ::android::hardware::audio::common::CPP_VERSION;
using namespace ::android::hardware::audio::effect::CPP_VERSION;
@@ -56,7 +58,7 @@
using GetParameterSuccessCallback =
std::function<void(uint32_t valueSize, const void* valueData)>;
- explicit Effect(effect_handle_t handle);
+ Effect(bool isInput, effect_handle_t handle);
// Methods from ::android::hardware::audio::effect::CPP_VERSION::IEffect follow.
Return<Result> init() override;
@@ -66,7 +68,15 @@
Return<Result> reset() override;
Return<Result> enable() override;
Return<Result> disable() override;
+#if MAJOR_VERSION <= 6
+ Return<Result> setAudioSource(AudioSource source) override;
Return<Result> setDevice(AudioDeviceBitfield device) override;
+ Return<Result> setInputDevice(AudioDeviceBitfield device) override;
+#else
+ Return<Result> setAudioSource(const AudioSource& source) override;
+ Return<Result> setDevice(const DeviceAddress& device) override;
+ Return<Result> setInputDevice(const DeviceAddress& device) override;
+#endif
Return<void> setAndGetVolume(const hidl_vec<uint32_t>& volumes,
setAndGetVolume_cb _hidl_cb) override;
Return<Result> volumeChangeNotification(const hidl_vec<uint32_t>& volumes) override;
@@ -74,14 +84,12 @@
Return<Result> setConfigReverse(
const EffectConfig& config, const sp<IEffectBufferProviderCallback>& inputBufferProvider,
const sp<IEffectBufferProviderCallback>& outputBufferProvider) override;
- Return<Result> setInputDevice(AudioDeviceBitfield device) override;
Return<void> getConfig(getConfig_cb _hidl_cb) override;
Return<void> getConfigReverse(getConfigReverse_cb _hidl_cb) override;
Return<void> getSupportedAuxChannelsConfigs(
uint32_t maxConfigs, getSupportedAuxChannelsConfigs_cb _hidl_cb) override;
Return<void> getAuxChannelsConfig(getAuxChannelsConfig_cb _hidl_cb) override;
Return<Result> setAuxChannelsConfig(const EffectAuxChannelsConfig& config) override;
- Return<Result> setAudioSource(AudioSource source) override;
Return<Result> offload(const EffectOffloadParameter& param) override;
Return<void> getDescriptor(getDescriptor_cb _hidl_cb) override;
Return<void> prepareForProcessing(prepareForProcessing_cb _hidl_cb) override;
@@ -104,6 +112,10 @@
Return<void> debug(const hidl_handle& fd, const hidl_vec<hidl_string>& options) override;
// Utility methods for extending interfaces.
+ static const char* sContextConversion;
+
+ Result analyzeStatus(const char* funcName, const char* subFuncName,
+ const char* contextDescription, status_t status);
template <typename T>
Return<void> getIntegerParam(uint32_t paramId,
std::function<void(Result retval, T paramValue)> cb) {
@@ -170,6 +182,7 @@
static const char* sContextCallToCommand;
static const char* sContextCallFunction;
+ const bool mIsInput;
effect_handle_t mHandle;
sp<AudioBufferWrapper> mInBuffer;
sp<AudioBufferWrapper> mOutBuffer;
@@ -186,15 +199,14 @@
static size_t alignedSizeIn(size_t s);
template <typename T>
std::unique_ptr<uint8_t[]> hidlVecToHal(const hidl_vec<T>& vec, uint32_t* halDataSize);
- static void effectAuxChannelsConfigFromHal(const channel_config_t& halConfig,
- EffectAuxChannelsConfig* config);
+ void effectAuxChannelsConfigFromHal(const channel_config_t& halConfig,
+ EffectAuxChannelsConfig* config);
static void effectAuxChannelsConfigToHal(const EffectAuxChannelsConfig& config,
channel_config_t* halConfig);
- static void effectBufferConfigFromHal(const buffer_config_t& halConfig,
- EffectBufferConfig* config);
+ void effectBufferConfigFromHal(const buffer_config_t& halConfig, EffectBufferConfig* config);
static void effectBufferConfigToHal(const EffectBufferConfig& config,
buffer_config_t* halConfig);
- static void effectConfigFromHal(const effect_config_t& halConfig, EffectConfig* config);
+ void effectConfigFromHal(const effect_config_t& halConfig, EffectConfig* config);
static void effectConfigToHal(const EffectConfig& config, effect_config_t* halConfig);
static void effectOffloadParamToHal(const EffectOffloadParameter& offload,
effect_offload_param_t* halOffload);
@@ -202,8 +214,6 @@
uint32_t valueSize, const void** valueData);
Result analyzeCommandStatus(const char* commandName, const char* context, status_t status);
- Result analyzeStatus(const char* funcName, const char* subFuncName,
- const char* contextDescription, status_t status);
void getConfigImpl(int commandCode, const char* commandName, GetConfigCallback cb);
Result getCurrentConfigImpl(uint32_t featureId, uint32_t configSize,
GetCurrentConfigSuccessCallback onSuccess);
diff --git a/audio/effect/all-versions/default/EffectsFactory.cpp b/audio/effect/all-versions/default/EffectsFactory.cpp
index b265d3d..1ea990b 100644
--- a/audio/effect/all-versions/default/EffectsFactory.cpp
+++ b/audio/effect/all-versions/default/EffectsFactory.cpp
@@ -82,7 +82,9 @@
} else if (memcmp(halUuid, SL_IID_VISUALIZATION, sizeof(effect_uuid_t)) == 0) {
return new VisualizerEffect(handle);
}
- return new Effect(handle);
+ const bool isInput =
+ (halDescriptor.flags & EFFECT_FLAG_TYPE_PRE_PROC) == EFFECT_FLAG_TYPE_PRE_PROC;
+ return new Effect(isInput, handle);
}
// Methods from ::android::hardware::audio::effect::CPP_VERSION::IEffectsFactory follow.
diff --git a/audio/effect/all-versions/default/EnvironmentalReverbEffect.cpp b/audio/effect/all-versions/default/EnvironmentalReverbEffect.cpp
index 78122d4..e95a267 100644
--- a/audio/effect/all-versions/default/EnvironmentalReverbEffect.cpp
+++ b/audio/effect/all-versions/default/EnvironmentalReverbEffect.cpp
@@ -31,9 +31,7 @@
namespace implementation {
EnvironmentalReverbEffect::EnvironmentalReverbEffect(effect_handle_t handle)
- : mEffect(new Effect(handle)) {}
-
-EnvironmentalReverbEffect::~EnvironmentalReverbEffect() {}
+ : mEffect(new Effect(false /*isInput*/, handle)) {}
void EnvironmentalReverbEffect::propertiesFromHal(
const t_reverb_settings& halProperties, IEnvironmentalReverbEffect::AllProperties* properties) {
@@ -86,10 +84,32 @@
return mEffect->disable();
}
+#if MAJOR_VERSION <= 6
+Return<Result> EnvironmentalReverbEffect::setAudioSource(AudioSource source) {
+ return mEffect->setAudioSource(source);
+}
+
Return<Result> EnvironmentalReverbEffect::setDevice(AudioDeviceBitfield device) {
return mEffect->setDevice(device);
}
+Return<Result> EnvironmentalReverbEffect::setInputDevice(AudioDeviceBitfield device) {
+ return mEffect->setInputDevice(device);
+}
+#else
+Return<Result> EnvironmentalReverbEffect::setAudioSource(const AudioSource& source) {
+ return mEffect->setAudioSource(source);
+}
+
+Return<Result> EnvironmentalReverbEffect::setDevice(const DeviceAddress& device) {
+ return mEffect->setDevice(device);
+}
+
+Return<Result> EnvironmentalReverbEffect::setInputDevice(const DeviceAddress& device) {
+ return mEffect->setInputDevice(device);
+}
+#endif
+
Return<void> EnvironmentalReverbEffect::setAndGetVolume(const hidl_vec<uint32_t>& volumes,
setAndGetVolume_cb _hidl_cb) {
return mEffect->setAndGetVolume(volumes, _hidl_cb);
@@ -110,10 +130,6 @@
return mEffect->setConfigReverse(config, inputBufferProvider, outputBufferProvider);
}
-Return<Result> EnvironmentalReverbEffect::setInputDevice(AudioDeviceBitfield device) {
- return mEffect->setInputDevice(device);
-}
-
Return<void> EnvironmentalReverbEffect::getConfig(getConfig_cb _hidl_cb) {
return mEffect->getConfig(_hidl_cb);
}
@@ -136,10 +152,6 @@
return mEffect->setAuxChannelsConfig(config);
}
-Return<Result> EnvironmentalReverbEffect::setAudioSource(AudioSource source) {
- return mEffect->setAudioSource(source);
-}
-
Return<Result> EnvironmentalReverbEffect::offload(const EffectOffloadParameter& param) {
return mEffect->offload(param);
}
diff --git a/audio/effect/all-versions/default/EnvironmentalReverbEffect.h b/audio/effect/all-versions/default/EnvironmentalReverbEffect.h
index bb422d4..9694b5d 100644
--- a/audio/effect/all-versions/default/EnvironmentalReverbEffect.h
+++ b/audio/effect/all-versions/default/EnvironmentalReverbEffect.h
@@ -57,7 +57,15 @@
Return<Result> reset() override;
Return<Result> enable() override;
Return<Result> disable() override;
+#if MAJOR_VERSION <= 6
+ Return<Result> setAudioSource(AudioSource source) override;
Return<Result> setDevice(AudioDeviceBitfield device) override;
+ Return<Result> setInputDevice(AudioDeviceBitfield device) override;
+#else
+ Return<Result> setAudioSource(const AudioSource& source) override;
+ Return<Result> setDevice(const DeviceAddress& device) override;
+ Return<Result> setInputDevice(const DeviceAddress& device) override;
+#endif
Return<void> setAndGetVolume(const hidl_vec<uint32_t>& volumes,
setAndGetVolume_cb _hidl_cb) override;
Return<Result> volumeChangeNotification(const hidl_vec<uint32_t>& volumes) override;
@@ -65,14 +73,12 @@
Return<Result> setConfigReverse(
const EffectConfig& config, const sp<IEffectBufferProviderCallback>& inputBufferProvider,
const sp<IEffectBufferProviderCallback>& outputBufferProvider) override;
- Return<Result> setInputDevice(AudioDeviceBitfield device) override;
Return<void> getConfig(getConfig_cb _hidl_cb) override;
Return<void> getConfigReverse(getConfigReverse_cb _hidl_cb) override;
Return<void> getSupportedAuxChannelsConfigs(
uint32_t maxConfigs, getSupportedAuxChannelsConfigs_cb _hidl_cb) override;
Return<void> getAuxChannelsConfig(getAuxChannelsConfig_cb _hidl_cb) override;
Return<Result> setAuxChannelsConfig(const EffectAuxChannelsConfig& config) override;
- Return<Result> setAudioSource(AudioSource source) override;
Return<Result> offload(const EffectOffloadParameter& param) override;
Return<void> getDescriptor(getDescriptor_cb _hidl_cb) override;
Return<void> prepareForProcessing(prepareForProcessing_cb _hidl_cb) override;
@@ -125,7 +131,7 @@
private:
sp<Effect> mEffect;
- virtual ~EnvironmentalReverbEffect();
+ virtual ~EnvironmentalReverbEffect() = default;
void propertiesFromHal(const t_reverb_settings& halProperties,
IEnvironmentalReverbEffect::AllProperties* properties);
diff --git a/audio/effect/all-versions/default/EqualizerEffect.cpp b/audio/effect/all-versions/default/EqualizerEffect.cpp
index 1b983ec..fffe8cd 100644
--- a/audio/effect/all-versions/default/EqualizerEffect.cpp
+++ b/audio/effect/all-versions/default/EqualizerEffect.cpp
@@ -31,9 +31,8 @@
namespace CPP_VERSION {
namespace implementation {
-EqualizerEffect::EqualizerEffect(effect_handle_t handle) : mEffect(new Effect(handle)) {}
-
-EqualizerEffect::~EqualizerEffect() {}
+EqualizerEffect::EqualizerEffect(effect_handle_t handle)
+ : mEffect(new Effect(false /*isInput*/, handle)) {}
void EqualizerEffect::propertiesFromHal(const t_equalizer_settings& halProperties,
IEqualizerEffect::AllProperties* properties) {
@@ -80,10 +79,32 @@
return mEffect->disable();
}
+#if MAJOR_VERSION <= 6
+Return<Result> EqualizerEffect::setAudioSource(AudioSource source) {
+ return mEffect->setAudioSource(source);
+}
+
Return<Result> EqualizerEffect::setDevice(AudioDeviceBitfield device) {
return mEffect->setDevice(device);
}
+Return<Result> EqualizerEffect::setInputDevice(AudioDeviceBitfield device) {
+ return mEffect->setInputDevice(device);
+}
+#else
+Return<Result> EqualizerEffect::setAudioSource(const AudioSource& source) {
+ return mEffect->setAudioSource(source);
+}
+
+Return<Result> EqualizerEffect::setDevice(const DeviceAddress& device) {
+ return mEffect->setDevice(device);
+}
+
+Return<Result> EqualizerEffect::setInputDevice(const DeviceAddress& device) {
+ return mEffect->setInputDevice(device);
+}
+#endif
+
Return<void> EqualizerEffect::setAndGetVolume(const hidl_vec<uint32_t>& volumes,
setAndGetVolume_cb _hidl_cb) {
return mEffect->setAndGetVolume(volumes, _hidl_cb);
@@ -103,10 +124,6 @@
return mEffect->setConfigReverse(config, inputBufferProvider, outputBufferProvider);
}
-Return<Result> EqualizerEffect::setInputDevice(AudioDeviceBitfield device) {
- return mEffect->setInputDevice(device);
-}
-
Return<void> EqualizerEffect::getConfig(getConfig_cb _hidl_cb) {
return mEffect->getConfig(_hidl_cb);
}
@@ -128,10 +145,6 @@
return mEffect->setAuxChannelsConfig(config);
}
-Return<Result> EqualizerEffect::setAudioSource(AudioSource source) {
- return mEffect->setAudioSource(source);
-}
-
Return<Result> EqualizerEffect::offload(const EffectOffloadParameter& param) {
return mEffect->offload(param);
}
diff --git a/audio/effect/all-versions/default/EqualizerEffect.h b/audio/effect/all-versions/default/EqualizerEffect.h
index b1cbefd..7a6bc0a 100644
--- a/audio/effect/all-versions/default/EqualizerEffect.h
+++ b/audio/effect/all-versions/default/EqualizerEffect.h
@@ -57,7 +57,15 @@
Return<Result> reset() override;
Return<Result> enable() override;
Return<Result> disable() override;
+#if MAJOR_VERSION <= 6
+ Return<Result> setAudioSource(AudioSource source) override;
Return<Result> setDevice(AudioDeviceBitfield device) override;
+ Return<Result> setInputDevice(AudioDeviceBitfield device) override;
+#else
+ Return<Result> setAudioSource(const AudioSource& source) override;
+ Return<Result> setDevice(const DeviceAddress& device) override;
+ Return<Result> setInputDevice(const DeviceAddress& device) override;
+#endif
Return<void> setAndGetVolume(const hidl_vec<uint32_t>& volumes,
setAndGetVolume_cb _hidl_cb) override;
Return<Result> volumeChangeNotification(const hidl_vec<uint32_t>& volumes) override;
@@ -65,14 +73,12 @@
Return<Result> setConfigReverse(
const EffectConfig& config, const sp<IEffectBufferProviderCallback>& inputBufferProvider,
const sp<IEffectBufferProviderCallback>& outputBufferProvider) override;
- Return<Result> setInputDevice(AudioDeviceBitfield device) override;
Return<void> getConfig(getConfig_cb _hidl_cb) override;
Return<void> getConfigReverse(getConfigReverse_cb _hidl_cb) override;
Return<void> getSupportedAuxChannelsConfigs(
uint32_t maxConfigs, getSupportedAuxChannelsConfigs_cb _hidl_cb) override;
Return<void> getAuxChannelsConfig(getAuxChannelsConfig_cb _hidl_cb) override;
Return<Result> setAuxChannelsConfig(const EffectAuxChannelsConfig& config) override;
- Return<Result> setAudioSource(AudioSource source) override;
Return<Result> offload(const EffectOffloadParameter& param) override;
Return<void> getDescriptor(getDescriptor_cb _hidl_cb) override;
Return<void> prepareForProcessing(prepareForProcessing_cb _hidl_cb) override;
@@ -111,7 +117,7 @@
private:
sp<Effect> mEffect;
- virtual ~EqualizerEffect();
+ virtual ~EqualizerEffect() = default;
void propertiesFromHal(const t_equalizer_settings& halProperties,
IEqualizerEffect::AllProperties* properties);
diff --git a/audio/effect/all-versions/default/LoudnessEnhancerEffect.cpp b/audio/effect/all-versions/default/LoudnessEnhancerEffect.cpp
index ebd5197..c7add86 100644
--- a/audio/effect/all-versions/default/LoudnessEnhancerEffect.cpp
+++ b/audio/effect/all-versions/default/LoudnessEnhancerEffect.cpp
@@ -33,9 +33,7 @@
namespace implementation {
LoudnessEnhancerEffect::LoudnessEnhancerEffect(effect_handle_t handle)
- : mEffect(new Effect(handle)) {}
-
-LoudnessEnhancerEffect::~LoudnessEnhancerEffect() {}
+ : mEffect(new Effect(false /*isInput*/, handle)) {}
// Methods from ::android::hardware::audio::effect::CPP_VERSION::IEffect follow.
Return<Result> LoudnessEnhancerEffect::init() {
@@ -60,10 +58,32 @@
return mEffect->disable();
}
+#if MAJOR_VERSION <= 6
+Return<Result> LoudnessEnhancerEffect::setAudioSource(AudioSource source) {
+ return mEffect->setAudioSource(source);
+}
+
Return<Result> LoudnessEnhancerEffect::setDevice(AudioDeviceBitfield device) {
return mEffect->setDevice(device);
}
+Return<Result> LoudnessEnhancerEffect::setInputDevice(AudioDeviceBitfield device) {
+ return mEffect->setInputDevice(device);
+}
+#else
+Return<Result> LoudnessEnhancerEffect::setAudioSource(const AudioSource& source) {
+ return mEffect->setAudioSource(source);
+}
+
+Return<Result> LoudnessEnhancerEffect::setDevice(const DeviceAddress& device) {
+ return mEffect->setDevice(device);
+}
+
+Return<Result> LoudnessEnhancerEffect::setInputDevice(const DeviceAddress& device) {
+ return mEffect->setInputDevice(device);
+}
+#endif
+
Return<void> LoudnessEnhancerEffect::setAndGetVolume(const hidl_vec<uint32_t>& volumes,
setAndGetVolume_cb _hidl_cb) {
return mEffect->setAndGetVolume(volumes, _hidl_cb);
@@ -83,10 +103,6 @@
return mEffect->setConfigReverse(config, inputBufferProvider, outputBufferProvider);
}
-Return<Result> LoudnessEnhancerEffect::setInputDevice(AudioDeviceBitfield device) {
- return mEffect->setInputDevice(device);
-}
-
Return<void> LoudnessEnhancerEffect::getConfig(getConfig_cb _hidl_cb) {
return mEffect->getConfig(_hidl_cb);
}
@@ -108,10 +124,6 @@
return mEffect->setAuxChannelsConfig(config);
}
-Return<Result> LoudnessEnhancerEffect::setAudioSource(AudioSource source) {
- return mEffect->setAudioSource(source);
-}
-
Return<Result> LoudnessEnhancerEffect::offload(const EffectOffloadParameter& param) {
return mEffect->offload(param);
}
diff --git a/audio/effect/all-versions/default/LoudnessEnhancerEffect.h b/audio/effect/all-versions/default/LoudnessEnhancerEffect.h
index 8baf128..6d80207 100644
--- a/audio/effect/all-versions/default/LoudnessEnhancerEffect.h
+++ b/audio/effect/all-versions/default/LoudnessEnhancerEffect.h
@@ -53,7 +53,15 @@
Return<Result> reset() override;
Return<Result> enable() override;
Return<Result> disable() override;
+#if MAJOR_VERSION <= 6
+ Return<Result> setAudioSource(AudioSource source) override;
Return<Result> setDevice(AudioDeviceBitfield device) override;
+ Return<Result> setInputDevice(AudioDeviceBitfield device) override;
+#else
+ Return<Result> setAudioSource(const AudioSource& source) override;
+ Return<Result> setDevice(const DeviceAddress& device) override;
+ Return<Result> setInputDevice(const DeviceAddress& device) override;
+#endif
Return<void> setAndGetVolume(const hidl_vec<uint32_t>& volumes,
setAndGetVolume_cb _hidl_cb) override;
Return<Result> volumeChangeNotification(const hidl_vec<uint32_t>& volumes) override;
@@ -61,14 +69,12 @@
Return<Result> setConfigReverse(
const EffectConfig& config, const sp<IEffectBufferProviderCallback>& inputBufferProvider,
const sp<IEffectBufferProviderCallback>& outputBufferProvider) override;
- Return<Result> setInputDevice(AudioDeviceBitfield device) override;
Return<void> getConfig(getConfig_cb _hidl_cb) override;
Return<void> getConfigReverse(getConfigReverse_cb _hidl_cb) override;
Return<void> getSupportedAuxChannelsConfigs(
uint32_t maxConfigs, getSupportedAuxChannelsConfigs_cb _hidl_cb) override;
Return<void> getAuxChannelsConfig(getAuxChannelsConfig_cb _hidl_cb) override;
Return<Result> setAuxChannelsConfig(const EffectAuxChannelsConfig& config) override;
- Return<Result> setAudioSource(AudioSource source) override;
Return<Result> offload(const EffectOffloadParameter& param) override;
Return<void> getDescriptor(getDescriptor_cb _hidl_cb) override;
Return<void> prepareForProcessing(prepareForProcessing_cb _hidl_cb) override;
@@ -98,7 +104,7 @@
private:
sp<Effect> mEffect;
- virtual ~LoudnessEnhancerEffect();
+ virtual ~LoudnessEnhancerEffect() = default;
};
} // namespace implementation
diff --git a/audio/effect/all-versions/default/NoiseSuppressionEffect.cpp b/audio/effect/all-versions/default/NoiseSuppressionEffect.cpp
index d01bbe5..9e75237 100644
--- a/audio/effect/all-versions/default/NoiseSuppressionEffect.cpp
+++ b/audio/effect/all-versions/default/NoiseSuppressionEffect.cpp
@@ -30,9 +30,7 @@
namespace implementation {
NoiseSuppressionEffect::NoiseSuppressionEffect(effect_handle_t handle)
- : mEffect(new Effect(handle)) {}
-
-NoiseSuppressionEffect::~NoiseSuppressionEffect() {}
+ : mEffect(new Effect(true /*isInput*/, handle)) {}
void NoiseSuppressionEffect::propertiesFromHal(const t_ns_settings& halProperties,
INoiseSuppressionEffect::AllProperties* properties) {
@@ -69,10 +67,32 @@
return mEffect->disable();
}
+#if MAJOR_VERSION <= 6
+Return<Result> NoiseSuppressionEffect::setAudioSource(AudioSource source) {
+ return mEffect->setAudioSource(source);
+}
+
Return<Result> NoiseSuppressionEffect::setDevice(AudioDeviceBitfield device) {
return mEffect->setDevice(device);
}
+Return<Result> NoiseSuppressionEffect::setInputDevice(AudioDeviceBitfield device) {
+ return mEffect->setInputDevice(device);
+}
+#else
+Return<Result> NoiseSuppressionEffect::setAudioSource(const AudioSource& source) {
+ return mEffect->setAudioSource(source);
+}
+
+Return<Result> NoiseSuppressionEffect::setDevice(const DeviceAddress& device) {
+ return mEffect->setDevice(device);
+}
+
+Return<Result> NoiseSuppressionEffect::setInputDevice(const DeviceAddress& device) {
+ return mEffect->setInputDevice(device);
+}
+#endif
+
Return<void> NoiseSuppressionEffect::setAndGetVolume(const hidl_vec<uint32_t>& volumes,
setAndGetVolume_cb _hidl_cb) {
return mEffect->setAndGetVolume(volumes, _hidl_cb);
@@ -92,10 +112,6 @@
return mEffect->setConfigReverse(config, inputBufferProvider, outputBufferProvider);
}
-Return<Result> NoiseSuppressionEffect::setInputDevice(AudioDeviceBitfield device) {
- return mEffect->setInputDevice(device);
-}
-
Return<void> NoiseSuppressionEffect::getConfig(getConfig_cb _hidl_cb) {
return mEffect->getConfig(_hidl_cb);
}
@@ -117,10 +133,6 @@
return mEffect->setAuxChannelsConfig(config);
}
-Return<Result> NoiseSuppressionEffect::setAudioSource(AudioSource source) {
- return mEffect->setAudioSource(source);
-}
-
Return<Result> NoiseSuppressionEffect::offload(const EffectOffloadParameter& param) {
return mEffect->offload(param);
}
diff --git a/audio/effect/all-versions/default/NoiseSuppressionEffect.h b/audio/effect/all-versions/default/NoiseSuppressionEffect.h
index c49bf7b..6cc45b9 100644
--- a/audio/effect/all-versions/default/NoiseSuppressionEffect.h
+++ b/audio/effect/all-versions/default/NoiseSuppressionEffect.h
@@ -55,7 +55,15 @@
Return<Result> reset() override;
Return<Result> enable() override;
Return<Result> disable() override;
+#if MAJOR_VERSION <= 6
+ Return<Result> setAudioSource(AudioSource source) override;
Return<Result> setDevice(AudioDeviceBitfield device) override;
+ Return<Result> setInputDevice(AudioDeviceBitfield device) override;
+#else
+ Return<Result> setAudioSource(const AudioSource& source) override;
+ Return<Result> setDevice(const DeviceAddress& device) override;
+ Return<Result> setInputDevice(const DeviceAddress& device) override;
+#endif
Return<void> setAndGetVolume(const hidl_vec<uint32_t>& volumes,
setAndGetVolume_cb _hidl_cb) override;
Return<Result> volumeChangeNotification(const hidl_vec<uint32_t>& volumes) override;
@@ -63,14 +71,12 @@
Return<Result> setConfigReverse(
const EffectConfig& config, const sp<IEffectBufferProviderCallback>& inputBufferProvider,
const sp<IEffectBufferProviderCallback>& outputBufferProvider) override;
- Return<Result> setInputDevice(AudioDeviceBitfield device) override;
Return<void> getConfig(getConfig_cb _hidl_cb) override;
Return<void> getConfigReverse(getConfigReverse_cb _hidl_cb) override;
Return<void> getSupportedAuxChannelsConfigs(
uint32_t maxConfigs, getSupportedAuxChannelsConfigs_cb _hidl_cb) override;
Return<void> getAuxChannelsConfig(getAuxChannelsConfig_cb _hidl_cb) override;
Return<Result> setAuxChannelsConfig(const EffectAuxChannelsConfig& config) override;
- Return<Result> setAudioSource(AudioSource source) override;
Return<Result> offload(const EffectOffloadParameter& param) override;
Return<void> getDescriptor(getDescriptor_cb _hidl_cb) override;
Return<void> prepareForProcessing(prepareForProcessing_cb _hidl_cb) override;
@@ -105,7 +111,7 @@
private:
sp<Effect> mEffect;
- virtual ~NoiseSuppressionEffect();
+ virtual ~NoiseSuppressionEffect() = default;
void propertiesFromHal(const t_ns_settings& halProperties,
INoiseSuppressionEffect::AllProperties* properties);
diff --git a/audio/effect/all-versions/default/PresetReverbEffect.cpp b/audio/effect/all-versions/default/PresetReverbEffect.cpp
index 4a2a3a4..1ae8492 100644
--- a/audio/effect/all-versions/default/PresetReverbEffect.cpp
+++ b/audio/effect/all-versions/default/PresetReverbEffect.cpp
@@ -30,9 +30,8 @@
namespace CPP_VERSION {
namespace implementation {
-PresetReverbEffect::PresetReverbEffect(effect_handle_t handle) : mEffect(new Effect(handle)) {}
-
-PresetReverbEffect::~PresetReverbEffect() {}
+PresetReverbEffect::PresetReverbEffect(effect_handle_t handle)
+ : mEffect(new Effect(false /*isInput*/, handle)) {}
// Methods from ::android::hardware::audio::effect::CPP_VERSION::IEffect follow.
Return<Result> PresetReverbEffect::init() {
@@ -57,10 +56,32 @@
return mEffect->disable();
}
+#if MAJOR_VERSION <= 6
+Return<Result> PresetReverbEffect::setAudioSource(AudioSource source) {
+ return mEffect->setAudioSource(source);
+}
+
Return<Result> PresetReverbEffect::setDevice(AudioDeviceBitfield device) {
return mEffect->setDevice(device);
}
+Return<Result> PresetReverbEffect::setInputDevice(AudioDeviceBitfield device) {
+ return mEffect->setInputDevice(device);
+}
+#else
+Return<Result> PresetReverbEffect::setAudioSource(const AudioSource& source) {
+ return mEffect->setAudioSource(source);
+}
+
+Return<Result> PresetReverbEffect::setDevice(const DeviceAddress& device) {
+ return mEffect->setDevice(device);
+}
+
+Return<Result> PresetReverbEffect::setInputDevice(const DeviceAddress& device) {
+ return mEffect->setInputDevice(device);
+}
+#endif
+
Return<void> PresetReverbEffect::setAndGetVolume(const hidl_vec<uint32_t>& volumes,
setAndGetVolume_cb _hidl_cb) {
return mEffect->setAndGetVolume(volumes, _hidl_cb);
@@ -80,10 +101,6 @@
return mEffect->setConfigReverse(config, inputBufferProvider, outputBufferProvider);
}
-Return<Result> PresetReverbEffect::setInputDevice(AudioDeviceBitfield device) {
- return mEffect->setInputDevice(device);
-}
-
Return<void> PresetReverbEffect::getConfig(getConfig_cb _hidl_cb) {
return mEffect->getConfig(_hidl_cb);
}
@@ -105,10 +122,6 @@
return mEffect->setAuxChannelsConfig(config);
}
-Return<Result> PresetReverbEffect::setAudioSource(AudioSource source) {
- return mEffect->setAudioSource(source);
-}
-
Return<Result> PresetReverbEffect::offload(const EffectOffloadParameter& param) {
return mEffect->offload(param);
}
diff --git a/audio/effect/all-versions/default/PresetReverbEffect.h b/audio/effect/all-versions/default/PresetReverbEffect.h
index 58a6829..eb55e20 100644
--- a/audio/effect/all-versions/default/PresetReverbEffect.h
+++ b/audio/effect/all-versions/default/PresetReverbEffect.h
@@ -53,7 +53,15 @@
Return<Result> reset() override;
Return<Result> enable() override;
Return<Result> disable() override;
+#if MAJOR_VERSION <= 6
+ Return<Result> setAudioSource(AudioSource source) override;
Return<Result> setDevice(AudioDeviceBitfield device) override;
+ Return<Result> setInputDevice(AudioDeviceBitfield device) override;
+#else
+ Return<Result> setAudioSource(const AudioSource& source) override;
+ Return<Result> setDevice(const DeviceAddress& device) override;
+ Return<Result> setInputDevice(const DeviceAddress& device) override;
+#endif
Return<void> setAndGetVolume(const hidl_vec<uint32_t>& volumes,
setAndGetVolume_cb _hidl_cb) override;
Return<Result> volumeChangeNotification(const hidl_vec<uint32_t>& volumes) override;
@@ -61,14 +69,12 @@
Return<Result> setConfigReverse(
const EffectConfig& config, const sp<IEffectBufferProviderCallback>& inputBufferProvider,
const sp<IEffectBufferProviderCallback>& outputBufferProvider) override;
- Return<Result> setInputDevice(AudioDeviceBitfield device) override;
Return<void> getConfig(getConfig_cb _hidl_cb) override;
Return<void> getConfigReverse(getConfigReverse_cb _hidl_cb) override;
Return<void> getSupportedAuxChannelsConfigs(
uint32_t maxConfigs, getSupportedAuxChannelsConfigs_cb _hidl_cb) override;
Return<void> getAuxChannelsConfig(getAuxChannelsConfig_cb _hidl_cb) override;
Return<Result> setAuxChannelsConfig(const EffectAuxChannelsConfig& config) override;
- Return<Result> setAudioSource(AudioSource source) override;
Return<Result> offload(const EffectOffloadParameter& param) override;
Return<void> getDescriptor(getDescriptor_cb _hidl_cb) override;
Return<void> prepareForProcessing(prepareForProcessing_cb _hidl_cb) override;
@@ -98,7 +104,7 @@
private:
sp<Effect> mEffect;
- virtual ~PresetReverbEffect();
+ virtual ~PresetReverbEffect() = default;
};
} // namespace implementation
diff --git a/audio/effect/all-versions/default/VirtualizerEffect.cpp b/audio/effect/all-versions/default/VirtualizerEffect.cpp
index 1b69a90..1dce181 100644
--- a/audio/effect/all-versions/default/VirtualizerEffect.cpp
+++ b/audio/effect/all-versions/default/VirtualizerEffect.cpp
@@ -19,7 +19,9 @@
#include "VirtualizerEffect.h"
#include <memory.h>
+#include <stdlib.h>
+#include <HidlUtils.h>
#include <android/log.h>
#include <system/audio_effects/effect_virtualizer.h>
@@ -32,19 +34,10 @@
namespace CPP_VERSION {
namespace implementation {
-VirtualizerEffect::VirtualizerEffect(effect_handle_t handle) : mEffect(new Effect(handle)) {}
+using ::android::hardware::audio::common::CPP_VERSION::implementation::HidlUtils;
-VirtualizerEffect::~VirtualizerEffect() {}
-
-void VirtualizerEffect::speakerAnglesFromHal(const int32_t* halAngles, uint32_t channelCount,
- hidl_vec<SpeakerAngle>& speakerAngles) {
- speakerAngles.resize(channelCount);
- for (uint32_t i = 0; i < channelCount; ++i) {
- speakerAngles[i].mask = AudioChannelBitfield(*halAngles++);
- speakerAngles[i].azimuth = *halAngles++;
- speakerAngles[i].elevation = *halAngles++;
- }
-}
+VirtualizerEffect::VirtualizerEffect(effect_handle_t handle)
+ : mEffect(new Effect(false /*isInput*/, handle)) {}
// Methods from ::android::hardware::audio::effect::CPP_VERSION::IEffect follow.
Return<Result> VirtualizerEffect::init() {
@@ -69,10 +62,32 @@
return mEffect->disable();
}
+#if MAJOR_VERSION <= 6
+Return<Result> VirtualizerEffect::setAudioSource(AudioSource source) {
+ return mEffect->setAudioSource(source);
+}
+
Return<Result> VirtualizerEffect::setDevice(AudioDeviceBitfield device) {
return mEffect->setDevice(device);
}
+Return<Result> VirtualizerEffect::setInputDevice(AudioDeviceBitfield device) {
+ return mEffect->setInputDevice(device);
+}
+#else
+Return<Result> VirtualizerEffect::setAudioSource(const AudioSource& source) {
+ return mEffect->setAudioSource(source);
+}
+
+Return<Result> VirtualizerEffect::setDevice(const DeviceAddress& device) {
+ return mEffect->setDevice(device);
+}
+
+Return<Result> VirtualizerEffect::setInputDevice(const DeviceAddress& device) {
+ return mEffect->setInputDevice(device);
+}
+#endif
+
Return<void> VirtualizerEffect::setAndGetVolume(const hidl_vec<uint32_t>& volumes,
setAndGetVolume_cb _hidl_cb) {
return mEffect->setAndGetVolume(volumes, _hidl_cb);
@@ -92,10 +107,6 @@
return mEffect->setConfigReverse(config, inputBufferProvider, outputBufferProvider);
}
-Return<Result> VirtualizerEffect::setInputDevice(AudioDeviceBitfield device) {
- return mEffect->setInputDevice(device);
-}
-
Return<void> VirtualizerEffect::getConfig(getConfig_cb _hidl_cb) {
return mEffect->getConfig(_hidl_cb);
}
@@ -117,10 +128,6 @@
return mEffect->setAuxChannelsConfig(config);
}
-Return<Result> VirtualizerEffect::setAudioSource(AudioSource source) {
- return mEffect->setAudioSource(source);
-}
-
Return<Result> VirtualizerEffect::offload(const EffectOffloadParameter& param) {
return mEffect->offload(param);
}
@@ -192,43 +199,117 @@
return mEffect->getIntegerParam(VIRTUALIZER_PARAM_STRENGTH, _hidl_cb);
}
-Return<void> VirtualizerEffect::getVirtualSpeakerAngles(AudioChannelBitfield mask,
- AudioDevice device,
- getVirtualSpeakerAngles_cb _hidl_cb) {
- uint32_t channelCount =
- audio_channel_count_from_out_mask(static_cast<audio_channel_mask_t>(mask));
+Return<void> VirtualizerEffect::getVirtualSpeakerAngles(
+#if MAJOR_VERSION <= 6
+ AudioChannelBitfield mask, AudioDevice device, getVirtualSpeakerAngles_cb _hidl_cb) {
+ audio_channel_mask_t halChannelMask = static_cast<audio_channel_mask_t>(mask);
+ audio_devices_t halDeviceType = static_cast<audio_devices_t>(device);
+#else
+ const AudioChannelMask& mask, const DeviceAddress& device,
+ getVirtualSpeakerAngles_cb _hidl_cb) {
+ audio_channel_mask_t halChannelMask;
+ if (status_t status = HidlUtils::audioChannelMaskToHal(mask, &halChannelMask);
+ status != NO_ERROR) {
+ _hidl_cb(mEffect->analyzeStatus(__func__, "audioChannelMaskToHal",
+ Effect::sContextConversion, status),
+ SpeakerAngles{});
+ return Void();
+ }
+ audio_devices_t halDeviceType;
+ char halDeviceAddress[AUDIO_DEVICE_MAX_ADDRESS_LEN];
+ if (status_t status = HidlUtils::deviceAddressToHal(device, &halDeviceType, halDeviceAddress);
+ status != NO_ERROR) {
+ _hidl_cb(mEffect->analyzeStatus(__func__, "deviceAddressToHal", Effect::sContextConversion,
+ status),
+ SpeakerAngles{});
+ return Void();
+ }
+#endif
+ uint32_t channelCount = audio_channel_count_from_out_mask(halChannelMask);
size_t halSpeakerAnglesSize = sizeof(int32_t) * 3 * channelCount;
- uint32_t halParam[3] = {VIRTUALIZER_PARAM_VIRTUAL_SPEAKER_ANGLES,
- static_cast<audio_channel_mask_t>(mask),
- static_cast<audio_devices_t>(device)};
- hidl_vec<SpeakerAngle> speakerAngles;
+ uint32_t halParam[3] = {VIRTUALIZER_PARAM_VIRTUAL_SPEAKER_ANGLES, halChannelMask,
+ halDeviceType};
+ SpeakerAngles speakerAngles;
+ status_t status = NO_ERROR;
Result retval = mEffect->getParameterImpl(
- sizeof(halParam), halParam, halSpeakerAnglesSize,
- [&](uint32_t valueSize, const void* valueData) {
- if (valueSize > halSpeakerAnglesSize) {
- valueSize = halSpeakerAnglesSize;
- } else if (valueSize < halSpeakerAnglesSize) {
- channelCount = valueSize / (sizeof(int32_t) * 3);
- }
- speakerAnglesFromHal(reinterpret_cast<const int32_t*>(valueData), channelCount,
- speakerAngles);
- });
+ sizeof(halParam), halParam, halSpeakerAnglesSize,
+ [&](uint32_t valueSize, const void* valueData) {
+ if (valueSize < halSpeakerAnglesSize) {
+ channelCount = valueSize / (sizeof(int32_t) * 3);
+ }
+ status = speakerAnglesFromHal(reinterpret_cast<const int32_t*>(valueData),
+ channelCount, speakerAngles);
+ });
+ if (retval == Result::OK) {
+ retval = mEffect->analyzeStatus(__func__, "speakerAnglesFromHal", "", status);
+ }
_hidl_cb(retval, speakerAngles);
return Void();
}
-Return<Result> VirtualizerEffect::forceVirtualizationMode(AudioDevice device) {
- return mEffect->setParam(VIRTUALIZER_PARAM_FORCE_VIRTUALIZATION_MODE,
- static_cast<audio_devices_t>(device));
-}
-
Return<void> VirtualizerEffect::getVirtualizationMode(getVirtualizationMode_cb _hidl_cb) {
uint32_t halMode = 0;
Result retval = mEffect->getParam(VIRTUALIZER_PARAM_FORCE_VIRTUALIZATION_MODE, halMode);
+#if MAJOR_VERSION <= 6
_hidl_cb(retval, AudioDevice(halMode));
+#else
+ DeviceAddress device;
+ (void)HidlUtils::deviceAddressFromHal(static_cast<audio_devices_t>(halMode), nullptr, &device);
+ _hidl_cb(retval, device);
+#endif
return Void();
}
+Return<Result> VirtualizerEffect::forceVirtualizationMode(
+#if MAJOR_VERSION <= 6
+ AudioDevice device) {
+ audio_devices_t halDeviceType = static_cast<audio_devices_t>(device);
+#else
+ const DeviceAddress& device) {
+ audio_devices_t halDeviceType;
+ char halDeviceAddress[AUDIO_DEVICE_MAX_ADDRESS_LEN];
+ (void)HidlUtils::deviceAddressToHal(device, &halDeviceType, halDeviceAddress);
+#endif
+ return mEffect->setParam(VIRTUALIZER_PARAM_FORCE_VIRTUALIZATION_MODE, halDeviceType);
+}
+
+#if MAJOR_VERSION <= 6
+// static
+status_t VirtualizerEffect::speakerAnglesFromHal(const int32_t* halAngles, uint32_t channelCount,
+ hidl_vec<SpeakerAngle>& speakerAngles) {
+ speakerAngles.resize(channelCount);
+ for (uint32_t i = 0; i < channelCount; ++i) {
+ speakerAngles[i].mask = AudioChannelBitfield(*halAngles++);
+ speakerAngles[i].azimuth = *halAngles++;
+ speakerAngles[i].elevation = *halAngles++;
+ }
+ return NO_ERROR;
+}
+#else
+static int compare_channels(const void* lhs, const void* rhs) {
+ return *(int32_t*)lhs - *(int32_t*)rhs;
+}
+
+// static
+status_t VirtualizerEffect::speakerAnglesFromHal(const int32_t* halAngles, uint32_t channelCount,
+ SpeakerAngles& speakerAngles) {
+ speakerAngles.azimuth.resize(channelCount);
+ speakerAngles.elevation.resize(channelCount);
+ int32_t halAnglesSorted[channelCount * 3];
+ memcpy(halAnglesSorted, halAngles, sizeof(halAnglesSorted));
+ // Ensure that channels are ordered from LSb to MSb.
+ qsort(halAnglesSorted, channelCount, sizeof(int32_t) * 3, compare_channels);
+ audio_channel_mask_t halMask = AUDIO_CHANNEL_NONE;
+ int32_t* halAnglesPtr = halAnglesSorted;
+ for (uint32_t i = 0; i < channelCount; ++i) {
+ halMask = static_cast<audio_channel_mask_t>(halMask | *halAnglesPtr++);
+ speakerAngles.azimuth[i] = *halAnglesPtr++;
+ speakerAngles.elevation[i] = *halAnglesPtr++;
+ }
+ return HidlUtils::audioChannelMaskFromHal(halMask, false /*isInput*/, &speakerAngles.mask);
+}
+#endif
+
} // namespace implementation
} // namespace CPP_VERSION
} // namespace effect
diff --git a/audio/effect/all-versions/default/VirtualizerEffect.h b/audio/effect/all-versions/default/VirtualizerEffect.h
index c630b2e..3ed06d1 100644
--- a/audio/effect/all-versions/default/VirtualizerEffect.h
+++ b/audio/effect/all-versions/default/VirtualizerEffect.h
@@ -39,7 +39,9 @@
using ::android::hardware::hidl_vec;
using ::android::hardware::Return;
using ::android::hardware::Void;
+#if MAJOR_VERSION <= 6
using ::android::hardware::audio::common::CPP_VERSION::implementation::AudioChannelBitfield;
+#endif
using namespace ::android::hardware::audio::common::CPP_VERSION;
using namespace ::android::hardware::audio::effect::CPP_VERSION;
@@ -54,7 +56,15 @@
Return<Result> reset() override;
Return<Result> enable() override;
Return<Result> disable() override;
+#if MAJOR_VERSION <= 6
+ Return<Result> setAudioSource(AudioSource source) override;
Return<Result> setDevice(AudioDeviceBitfield device) override;
+ Return<Result> setInputDevice(AudioDeviceBitfield device) override;
+#else
+ Return<Result> setAudioSource(const AudioSource& source) override;
+ Return<Result> setDevice(const DeviceAddress& device) override;
+ Return<Result> setInputDevice(const DeviceAddress& device) override;
+#endif
Return<void> setAndGetVolume(const hidl_vec<uint32_t>& volumes,
setAndGetVolume_cb _hidl_cb) override;
Return<Result> volumeChangeNotification(const hidl_vec<uint32_t>& volumes) override;
@@ -62,14 +72,12 @@
Return<Result> setConfigReverse(
const EffectConfig& config, const sp<IEffectBufferProviderCallback>& inputBufferProvider,
const sp<IEffectBufferProviderCallback>& outputBufferProvider) override;
- Return<Result> setInputDevice(AudioDeviceBitfield device) override;
Return<void> getConfig(getConfig_cb _hidl_cb) override;
Return<void> getConfigReverse(getConfigReverse_cb _hidl_cb) override;
Return<void> getSupportedAuxChannelsConfigs(
uint32_t maxConfigs, getSupportedAuxChannelsConfigs_cb _hidl_cb) override;
Return<void> getAuxChannelsConfig(getAuxChannelsConfig_cb _hidl_cb) override;
Return<Result> setAuxChannelsConfig(const EffectAuxChannelsConfig& config) override;
- Return<Result> setAudioSource(AudioSource source) override;
Return<Result> offload(const EffectOffloadParameter& param) override;
Return<void> getDescriptor(getDescriptor_cb _hidl_cb) override;
Return<void> prepareForProcessing(prepareForProcessing_cb _hidl_cb) override;
@@ -96,18 +104,27 @@
Return<bool> isStrengthSupported() override;
Return<Result> setStrength(uint16_t strength) override;
Return<void> getStrength(getStrength_cb _hidl_cb) override;
+ Return<void> getVirtualizationMode(getVirtualizationMode_cb _hidl_cb) override;
+#if MAJOR_VERSION <= 6
Return<void> getVirtualSpeakerAngles(AudioChannelBitfield mask, AudioDevice device,
getVirtualSpeakerAngles_cb _hidl_cb) override;
Return<Result> forceVirtualizationMode(AudioDevice device) override;
- Return<void> getVirtualizationMode(getVirtualizationMode_cb _hidl_cb) override;
+#else
+ Return<void> getVirtualSpeakerAngles(const AudioChannelMask& mask, const DeviceAddress& device,
+ getVirtualSpeakerAngles_cb _hidl_cb) override;
+ Return<Result> forceVirtualizationMode(const DeviceAddress& device) override;
+#endif
- private:
+ private:
sp<Effect> mEffect;
- virtual ~VirtualizerEffect();
+ virtual ~VirtualizerEffect() = default;
- void speakerAnglesFromHal(const int32_t* halAngles, uint32_t channelCount,
- hidl_vec<SpeakerAngle>& speakerAngles);
+#if MAJOR_VERSION <= 6
+ using SpeakerAngles = hidl_vec<SpeakerAngle>;
+#endif
+ static status_t speakerAnglesFromHal(const int32_t* halAngles, uint32_t channelCount,
+ SpeakerAngles& speakerAngles);
};
} // namespace implementation
diff --git a/audio/effect/all-versions/default/VisualizerEffect.cpp b/audio/effect/all-versions/default/VisualizerEffect.cpp
index ae533bf..80c8637 100644
--- a/audio/effect/all-versions/default/VisualizerEffect.cpp
+++ b/audio/effect/all-versions/default/VisualizerEffect.cpp
@@ -31,9 +31,9 @@
namespace implementation {
VisualizerEffect::VisualizerEffect(effect_handle_t handle)
- : mEffect(new Effect(handle)), mCaptureSize(0), mMeasurementMode(MeasurementMode::NONE) {}
-
-VisualizerEffect::~VisualizerEffect() {}
+ : mEffect(new Effect(false /*isInput*/, handle)),
+ mCaptureSize(0),
+ mMeasurementMode(MeasurementMode::NONE) {}
// Methods from ::android::hardware::audio::effect::CPP_VERSION::IEffect follow.
Return<Result> VisualizerEffect::init() {
@@ -58,10 +58,32 @@
return mEffect->disable();
}
+#if MAJOR_VERSION <= 6
+Return<Result> VisualizerEffect::setAudioSource(AudioSource source) {
+ return mEffect->setAudioSource(source);
+}
+
Return<Result> VisualizerEffect::setDevice(AudioDeviceBitfield device) {
return mEffect->setDevice(device);
}
+Return<Result> VisualizerEffect::setInputDevice(AudioDeviceBitfield device) {
+ return mEffect->setInputDevice(device);
+}
+#else
+Return<Result> VisualizerEffect::setAudioSource(const AudioSource& source) {
+ return mEffect->setAudioSource(source);
+}
+
+Return<Result> VisualizerEffect::setDevice(const DeviceAddress& device) {
+ return mEffect->setDevice(device);
+}
+
+Return<Result> VisualizerEffect::setInputDevice(const DeviceAddress& device) {
+ return mEffect->setInputDevice(device);
+}
+#endif
+
Return<void> VisualizerEffect::setAndGetVolume(const hidl_vec<uint32_t>& volumes,
setAndGetVolume_cb _hidl_cb) {
return mEffect->setAndGetVolume(volumes, _hidl_cb);
@@ -81,10 +103,6 @@
return mEffect->setConfigReverse(config, inputBufferProvider, outputBufferProvider);
}
-Return<Result> VisualizerEffect::setInputDevice(AudioDeviceBitfield device) {
- return mEffect->setInputDevice(device);
-}
-
Return<void> VisualizerEffect::getConfig(getConfig_cb _hidl_cb) {
return mEffect->getConfig(_hidl_cb);
}
@@ -106,10 +124,6 @@
return mEffect->setAuxChannelsConfig(config);
}
-Return<Result> VisualizerEffect::setAudioSource(AudioSource source) {
- return mEffect->setAudioSource(source);
-}
-
Return<Result> VisualizerEffect::offload(const EffectOffloadParameter& param) {
return mEffect->offload(param);
}
diff --git a/audio/effect/all-versions/default/VisualizerEffect.h b/audio/effect/all-versions/default/VisualizerEffect.h
index 315f844..3ae4b08 100644
--- a/audio/effect/all-versions/default/VisualizerEffect.h
+++ b/audio/effect/all-versions/default/VisualizerEffect.h
@@ -53,7 +53,15 @@
Return<Result> reset() override;
Return<Result> enable() override;
Return<Result> disable() override;
+#if MAJOR_VERSION <= 6
+ Return<Result> setAudioSource(AudioSource source) override;
Return<Result> setDevice(AudioDeviceBitfield device) override;
+ Return<Result> setInputDevice(AudioDeviceBitfield device) override;
+#else
+ Return<Result> setAudioSource(const AudioSource& source) override;
+ Return<Result> setDevice(const DeviceAddress& device) override;
+ Return<Result> setInputDevice(const DeviceAddress& device) override;
+#endif
Return<void> setAndGetVolume(const hidl_vec<uint32_t>& volumes,
setAndGetVolume_cb _hidl_cb) override;
Return<Result> volumeChangeNotification(const hidl_vec<uint32_t>& volumes) override;
@@ -61,14 +69,12 @@
Return<Result> setConfigReverse(
const EffectConfig& config, const sp<IEffectBufferProviderCallback>& inputBufferProvider,
const sp<IEffectBufferProviderCallback>& outputBufferProvider) override;
- Return<Result> setInputDevice(AudioDeviceBitfield device) override;
Return<void> getConfig(getConfig_cb _hidl_cb) override;
Return<void> getConfigReverse(getConfigReverse_cb _hidl_cb) override;
Return<void> getSupportedAuxChannelsConfigs(
uint32_t maxConfigs, getSupportedAuxChannelsConfigs_cb _hidl_cb) override;
Return<void> getAuxChannelsConfig(getAuxChannelsConfig_cb _hidl_cb) override;
Return<Result> setAuxChannelsConfig(const EffectAuxChannelsConfig& config) override;
- Return<Result> setAudioSource(AudioSource source) override;
Return<Result> offload(const EffectOffloadParameter& param) override;
Return<void> getDescriptor(getDescriptor_cb _hidl_cb) override;
Return<void> prepareForProcessing(prepareForProcessing_cb _hidl_cb) override;
@@ -107,7 +113,7 @@
uint16_t mCaptureSize;
MeasurementMode mMeasurementMode;
- virtual ~VisualizerEffect();
+ virtual ~VisualizerEffect() = default;
};
} // namespace implementation
diff --git a/audio/effect/all-versions/vts/functional/VtsHalAudioEffectTargetTest.cpp b/audio/effect/all-versions/vts/functional/VtsHalAudioEffectTargetTest.cpp
index 199a8a5..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
@@ -263,7 +265,7 @@
static_cast<audio_channel_mask_t>(currentConfig.outputCfg.channels));
#else
*channelCount = android::audio::policy::configuration::V7_0::getChannelCount(
- currentConfig.outputCfg.channels);
+ currentConfig.outputCfg.base.channelMask);
ASSERT_NE(*channelCount, 0);
#endif
}
@@ -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&) {});
@@ -353,8 +396,14 @@
}
inline bool operator==(const EffectBufferConfig& lhs, const EffectBufferConfig& rhs) {
- return lhs.buffer == rhs.buffer && lhs.samplingRateHz == rhs.samplingRateHz &&
- lhs.channels == rhs.channels && lhs.format == rhs.format &&
+ return lhs.buffer == rhs.buffer &&
+#if MAJOR_VERSION <= 6
+ lhs.samplingRateHz == rhs.samplingRateHz && lhs.channels == rhs.channels &&
+ lhs.format == rhs.format &&
+#else
+ lhs.base.sampleRateHz == rhs.base.sampleRateHz &&
+ lhs.base.channelMask == rhs.base.channelMask && lhs.base.format == rhs.base.format &&
+#endif
lhs.accessMode == rhs.accessMode && lhs.mask == rhs.mask;
}
@@ -407,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
@@ -462,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
@@ -473,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/OWNERS b/authsecret/aidl/vts/OWNERS
new file mode 100644
index 0000000..40d95e4
--- /dev/null
+++ b/authsecret/aidl/vts/OWNERS
@@ -0,0 +1,2 @@
+chengyouho@google.com
+frankwoo@google.com
diff --git a/authsecret/aidl/vts/VtsHalAuthSecretTargetTest.cpp b/authsecret/aidl/vts/VtsHalAuthSecretTargetTest.cpp
new file mode 100644
index 0000000..31c2834
--- /dev/null
+++ b/authsecret/aidl/vts/VtsHalAuthSecretTargetTest.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+
+#include <aidl/android/hardware/authsecret/IAuthSecret.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+using ::aidl::android::hardware::authsecret::IAuthSecret;
+
+using ::ndk::SpAIBinder;
+
+/**
+ * There is no expected behaviour that can be tested so these tests check the
+ * HAL doesn't crash with different execution orders.
+ */
+class AuthSecretAidlTest : public testing::TestWithParam<std::string> {
+ public:
+ virtual void SetUp() override {
+ authsecret = IAuthSecret::fromBinder(
+ SpAIBinder(AServiceManager_waitForService(GetParam().c_str())));
+ ASSERT_NE(authsecret, nullptr);
+
+ // Notify LSS to generate PIN code '1234' and corresponding secret.
+ (void)system("cmd lock_settings set-pin 1234");
+
+ // All tests must enroll the correct secret first as this cannot be changed
+ // without a factory reset and the order of tests could change.
+ authsecret->setPrimaryUserCredential(CORRECT_SECRET);
+ }
+
+ static void TearDownTestSuite() {
+ // clean up PIN code after testing
+ (void)system("cmd lock_settings clear --old 1234");
+ }
+
+ std::shared_ptr<IAuthSecret> authsecret;
+ std::vector<uint8_t> CORRECT_SECRET{61, 93, 124, 240, 5, 0, 7, 201, 9, 129, 11, 12, 0, 14, 0, 16};
+ std::vector<uint8_t> WRONG_SECRET{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
+};
+
+/* Provision the primary user with a secret. */
+TEST_P(AuthSecretAidlTest, provisionPrimaryUserCredential) {
+ // Secret provisioned by SetUp()
+}
+
+/* Provision the primary user with a secret and pass the secret again. */
+TEST_P(AuthSecretAidlTest, provisionPrimaryUserCredentialAndPassAgain) {
+ // Secret provisioned by SetUp()
+ authsecret->setPrimaryUserCredential(CORRECT_SECRET);
+}
+
+/* Provision the primary user with a secret and pass the secret again repeatedly. */
+TEST_P(AuthSecretAidlTest, provisionPrimaryUserCredentialAndPassAgainMultipleTimes) {
+ // Secret provisioned by SetUp()
+ constexpr int N = 5;
+ for (int i = 0; i < N; ++i) {
+ authsecret->setPrimaryUserCredential(CORRECT_SECRET);
+ }
+}
+
+/* Provision the primary user with a secret and then pass the wrong secret. This
+ * should never happen and is an framework bug if it does. As the secret is
+ * wrong, the HAL implementation may not be able to function correctly but it
+ * should fail gracefully. */
+TEST_P(AuthSecretAidlTest, provisionPrimaryUserCredentialAndWrongSecret) {
+ // Secret provisioned by SetUp()
+ authsecret->setPrimaryUserCredential(WRONG_SECRET);
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AuthSecretAidlTest);
+INSTANTIATE_TEST_SUITE_P(
+ PerInstance, AuthSecretAidlTest,
+ testing::ValuesIn(android::getAidlHalInstanceNames(IAuthSecret::descriptor)),
+ android::PrintInstanceNameToString);
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ ABinderProcess_setThreadPoolMaxThreadCount(1);
+ ABinderProcess_startThreadPool();
+ return RUN_ALL_TESTS();
+}
diff --git a/bluetooth/1.0/default/test/bluetooth_address_test.cc b/bluetooth/1.0/default/test/bluetooth_address_test.cc
index ee52d33..422d6a3 100644
--- a/bluetooth/1.0/default/test/bluetooth_address_test.cc
+++ b/bluetooth/1.0/default/test/bluetooth_address_test.cc
@@ -14,7 +14,6 @@
// limitations under the License.
//
-#include <cutils/properties.h>
#include <errno.h>
#include <fcntl.h>
#include <gtest/gtest.h>
@@ -39,12 +38,6 @@
"00:00:00:00:00:00";
constexpr uint8_t kZeros_bytes[BluetoothAddress::kBytes] = {0x00, 0x00, 0x00,
0x00, 0x00, 0x00};
-constexpr char kTestAddrBad1[BluetoothAddress::kStringLength + 1] =
- "bb:aa:dd:00:00:01";
-constexpr uint8_t kTestAddrBad1_bytes[BluetoothAddress::kBytes] = {
- 0xbb, 0xaa, 0xdd, 0x00, 0x00, 0x01};
-
-constexpr char kAddrPath[] = "/tmp/my_address_in_a_file.txt";
class BluetoothAddressTest : public ::testing::Test {
public:
@@ -120,45 +113,6 @@
EXPECT_FALSE(memcmp(addrA, addrB, BluetoothAddress::kStringLength) == 0);
}
-TEST_F(BluetoothAddressTest, get_local_address) {
- EXPECT_TRUE(property_set(PERSIST_BDADDR_PROPERTY, "") == 0);
- EXPECT_TRUE(property_set(FACTORY_BDADDR_PROPERTY, "") == 0);
- uint8_t address[BluetoothAddress::kBytes];
-
- // File contains a non-zero Address.
- FileWriteString(kAddrPath, kTestAddr1);
- EXPECT_TRUE(property_set(PROPERTY_BT_BDADDR_PATH, kAddrPath) == 0);
- EXPECT_TRUE(BluetoothAddress::get_local_address(address));
- EXPECT_TRUE(memcmp(address, kTestAddr1_bytes, BluetoothAddress::kBytes) == 0);
-
- // File contains a zero address. A random address will be generated.
- FileWriteString(kAddrPath, kZeros);
- EXPECT_TRUE(property_set(PROPERTY_BT_BDADDR_PATH, kAddrPath) == 0);
- EXPECT_TRUE(property_set(PERSIST_BDADDR_PROPERTY, kTestAddrBad1) == 0);
- EXPECT_TRUE(BluetoothAddress::get_local_address(address));
- EXPECT_TRUE(memcmp(address, kZeros_bytes, BluetoothAddress::kBytes) != 0);
- char prop[PROP_VALUE_MAX] = "Before reading";
- EXPECT_TRUE(property_get(PERSIST_BDADDR_PROPERTY, prop, NULL) ==
- BluetoothAddress::kStringLength);
- char address_str[BluetoothAddress::kStringLength + 1];
- BluetoothAddress::bytes_to_string(address, address_str);
- EXPECT_TRUE(memcmp(address_str, prop, BluetoothAddress::kStringLength) == 0);
-
- // Factory property contains an address.
- EXPECT_TRUE(property_set(PERSIST_BDADDR_PROPERTY, kTestAddrBad1) == 0);
- EXPECT_TRUE(property_set(FACTORY_BDADDR_PROPERTY, kTestAddr1) == 0);
- EXPECT_TRUE(BluetoothAddress::get_local_address(address));
- EXPECT_TRUE(memcmp(address, kTestAddr1_bytes, BluetoothAddress::kBytes) == 0);
-
- // Persistent property contains an address.
- memcpy(address, kTestAddrBad1_bytes, BluetoothAddress::kBytes);
- EXPECT_TRUE(property_set(PERSIST_BDADDR_PROPERTY, kTestAddr1) == 0);
- EXPECT_TRUE(property_set(FACTORY_BDADDR_PROPERTY, "") == 0);
- EXPECT_TRUE(property_set(PROPERTY_BT_BDADDR_PATH, "") == 0);
- EXPECT_TRUE(BluetoothAddress::get_local_address(address));
- EXPECT_TRUE(memcmp(address, kTestAddr1_bytes, BluetoothAddress::kBytes) == 0);
-}
-
} // namespace implementation
} // namespace V1_0
} // namespace bluetooth
diff --git a/bluetooth/audio/2.1/default/BluetoothAudioProvider.cpp b/bluetooth/audio/2.1/default/BluetoothAudioProvider.cpp
index 092038b..73fe06c 100644
--- a/bluetooth/audio/2.1/default/BluetoothAudioProvider.cpp
+++ b/bluetooth/audio/2.1/default/BluetoothAudioProvider.cpp
@@ -57,14 +57,14 @@
if (audioConfig.getDiscriminator() ==
V2_0::AudioConfiguration::hidl_discriminator::pcmConfig) {
- audioConfig_2_1.pcmConfig() = {
+ audioConfig_2_1.pcmConfig({
.sampleRate =
static_cast<SampleRate>(audioConfig.pcmConfig().sampleRate),
.channelMode = audioConfig.pcmConfig().channelMode,
.bitsPerSample = audioConfig.pcmConfig().bitsPerSample,
- .dataIntervalUs = 0};
+ .dataIntervalUs = 0});
} else {
- audioConfig_2_1.codecConfig() = audioConfig.codecConfig();
+ audioConfig_2_1.codecConfig(audioConfig.codecConfig());
}
return startSession_2_1(hostIf, audioConfig_2_1, _hidl_cb);
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/broadcastradio/2.0/vts/functional/VtsHalBroadcastradioV2_0TargetTest.cpp b/broadcastradio/2.0/vts/functional/VtsHalBroadcastradioV2_0TargetTest.cpp
index ca57243..ce50f25 100644
--- a/broadcastradio/2.0/vts/functional/VtsHalBroadcastradioV2_0TargetTest.cpp
+++ b/broadcastradio/2.0/vts/functional/VtsHalBroadcastradioV2_0TargetTest.cpp
@@ -415,7 +415,7 @@
TEST_P(BroadcastRadioHalTest, FmTune) {
ASSERT_TRUE(openSession());
- uint64_t freq = 100100; // 100.1 FM
+ uint64_t freq = 90900; // 90.9 FM
auto sel = make_selector_amfm(freq);
/* TODO(b/69958777): there is a race condition between tune() and onCurrentProgramInfoChanged
diff --git a/common/TEST_MAPPING b/common/TEST_MAPPING
new file mode 100644
index 0000000..7dd29e5
--- /dev/null
+++ b/common/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "libaidlcommonsupport_test"
+ }
+ ]
+}
diff --git a/common/support/Android.bp b/common/support/Android.bp
new file mode 100644
index 0000000..3bb4804
--- /dev/null
+++ b/common/support/Android.bp
@@ -0,0 +1,27 @@
+cc_library_static {
+ name: "libaidlcommonsupport",
+ vendor_available: true,
+ host_supported: true,
+ defaults: ["libbinder_ndk_host_user"],
+ srcs: ["NativeHandle.cpp"],
+ export_include_dirs: ["include"],
+ shared_libs: [
+ "android.hardware.common-unstable-ndk_platform",
+ "libcutils",
+ ],
+}
+
+cc_test {
+ name: "libaidlcommonsupport_test",
+ host_supported: true,
+ defaults: ["libbinder_ndk_host_user"],
+ srcs: ["test.cpp"],
+ static_libs: [
+ "libaidlcommonsupport",
+ ],
+ shared_libs: [
+ "android.hardware.common-unstable-ndk_platform",
+ "libcutils",
+ ],
+ test_suites: ["general-tests"],
+}
diff --git a/common/support/NativeHandle.cpp b/common/support/NativeHandle.cpp
new file mode 100644
index 0000000..321d7a8
--- /dev/null
+++ b/common/support/NativeHandle.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 <aidlcommonsupport/NativeHandle.h>
+
+#include <fcntl.h>
+
+namespace android {
+
+using aidl::android::hardware::common::NativeHandle;
+
+static native_handle_t* fromAidl(const NativeHandle& handle, bool doDup) {
+ native_handle_t* to = native_handle_create(handle.fds.size(), handle.ints.size());
+ if (!to) return nullptr;
+
+ for (size_t i = 0; i < handle.fds.size(); i++) {
+ int fd = handle.fds[i].get();
+ to->data[i] = doDup ? fcntl(fd, F_DUPFD_CLOEXEC, 0) : fd;
+ }
+ memcpy(to->data + handle.fds.size(), handle.ints.data(), handle.ints.size() * sizeof(int));
+ return to;
+}
+
+native_handle_t* makeFromAidl(const NativeHandle& handle) {
+ return fromAidl(handle, false /* doDup */);
+}
+native_handle_t* dupFromAidl(const NativeHandle& handle) {
+ return fromAidl(handle, true /* doDup */);
+}
+
+static NativeHandle toAidl(const native_handle_t* handle, bool doDup) {
+ NativeHandle to;
+
+ to.fds = std::vector<ndk::ScopedFileDescriptor>(handle->numFds);
+ for (size_t i = 0; i < handle->numFds; i++) {
+ int fd = handle->data[i];
+ to.fds.at(i).set(doDup ? fcntl(fd, F_DUPFD_CLOEXEC, 0) : fd);
+ }
+
+ to.ints = std::vector<int32_t>(handle->data + handle->numFds,
+ handle->data + handle->numFds + handle->numInts);
+ return to;
+}
+
+NativeHandle makeToAidl(const native_handle_t* handle) {
+ return toAidl(handle, false /* doDup */);
+}
+
+NativeHandle dupToAidl(const native_handle_t* handle) {
+ return toAidl(handle, true /* doDup */);
+}
+
+} // namespace android
diff --git a/common/support/include/aidlcommonsupport/NativeHandle.h b/common/support/include/aidlcommonsupport/NativeHandle.h
new file mode 100644
index 0000000..10eecba
--- /dev/null
+++ b/common/support/include/aidlcommonsupport/NativeHandle.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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/common/NativeHandle.h>
+#include <cutils/native_handle.h>
+
+namespace android {
+
+/**
+ * Creates a libcutils native handle from an AIDL native handle, but it does not
+ * dup internally, so it will contain the same FDs as the handle itself. The
+ * result should be deleted with native_handle_delete.
+ */
+native_handle_t* makeFromAidl(const aidl::android::hardware::common::NativeHandle& handle);
+
+/**
+ * Creates a libcutils native handle from an AIDL native handle with a dup
+ * internally. It's expected the handle is cleaned up with native_handle_close
+ * and native_handle_delete.
+ */
+native_handle_t* dupFromAidl(const aidl::android::hardware::common::NativeHandle& handle);
+
+/**
+ * Creates an AIDL native handle from a libcutils native handle, but does not
+ * dup internally, so the result will contain the same FDs as the handle itself.
+ *
+ * Warning: this passes ownership of the FDs to the ScopedFileDescriptor
+ * objects.
+ */
+aidl::android::hardware::common::NativeHandle makeToAidl(const native_handle_t* handle);
+
+/**
+ * Creates an AIDL native handle from a libcutils native handle with a dup
+ * internally.
+ */
+aidl::android::hardware::common::NativeHandle dupToAidl(const native_handle_t* handle);
+
+} // namespace android
diff --git a/common/support/test.cpp b/common/support/test.cpp
new file mode 100644
index 0000000..2359277
--- /dev/null
+++ b/common/support/test.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <aidlcommonsupport/NativeHandle.h>
+#include <gtest/gtest.h>
+
+namespace android {
+
+using aidl::android::hardware::common::NativeHandle;
+using ndk::ScopedFileDescriptor;
+
+static void checkEq(const NativeHandle& aidl, native_handle_t* libcutils, bool exceptFds) {
+ ASSERT_NE(libcutils, nullptr);
+ ASSERT_EQ(libcutils->numFds, aidl.fds.size());
+
+ for (size_t i = 0; i < libcutils->numFds; i++) {
+ int afd = aidl.fds.at(i).get();
+ int lfd = libcutils->data[i];
+
+ EXPECT_GE(afd, 0) << "Invalid fd at index " << i;
+ EXPECT_GE(lfd, 0) << "Invalid fd at index " << i;
+
+ if (exceptFds) {
+ EXPECT_NE(afd, lfd) << "Index matched at " << i << " but should be dup'd fd";
+ } else {
+ EXPECT_EQ(afd, lfd) << "Index mismatched at " << i << " but should be same fd";
+ }
+ }
+
+ ASSERT_EQ(libcutils->numInts, aidl.ints.size());
+
+ for (size_t i = 0; i < libcutils->numInts; i++) {
+ int afd = aidl.ints.at(i);
+ int lfd = libcutils->data[libcutils->numFds + i];
+
+ EXPECT_EQ(afd, lfd) << "Index mismatch at " << i;
+ }
+}
+
+static NativeHandle makeTestAidlHandle() {
+ NativeHandle handle = {
+ .fds = std::vector<ScopedFileDescriptor>(2),
+ .ints = {1, 2, 3, 4},
+ };
+ handle.fds[0].set(dup(0));
+ handle.fds[1].set(dup(0));
+ return handle;
+}
+
+TEST(ConvertNativeHandle, MakeFromAidlEmpty) {
+ NativeHandle handle;
+ native_handle_t* to = makeFromAidl(handle);
+ checkEq(handle, to, false /*exceptFds*/);
+ // no native_handle_close b/c fds are owned by NativeHandle
+ EXPECT_EQ(0, native_handle_delete(to));
+}
+
+TEST(ConvertNativeHandle, MakeFromAidl) {
+ NativeHandle handle = makeTestAidlHandle();
+ native_handle_t* to = makeFromAidl(handle);
+ checkEq(handle, to, false /*exceptFds*/);
+ // no native_handle_close b/c fds are owned by NativeHandle
+ EXPECT_EQ(0, native_handle_delete(to));
+}
+
+TEST(ConvertNativeHandle, DupFromAidlEmpty) {
+ NativeHandle handle;
+ native_handle_t* to = dupFromAidl(handle);
+ checkEq(handle, to, true /*exceptFds*/);
+ EXPECT_EQ(0, native_handle_close(to));
+ EXPECT_EQ(0, native_handle_delete(to));
+}
+
+TEST(ConvertNativeHandle, DupFromAidl) {
+ NativeHandle handle = makeTestAidlHandle();
+ native_handle_t* to = dupFromAidl(handle);
+ checkEq(handle, to, true /*exceptFds*/);
+ EXPECT_EQ(0, native_handle_close(to));
+ EXPECT_EQ(0, native_handle_delete(to));
+}
+
+static native_handle_t* makeTestLibcutilsHandle() {
+ native_handle_t* handle = native_handle_create(2, 4);
+ handle->data[0] = dup(0);
+ handle->data[1] = dup(0);
+ handle->data[2] = 1;
+ handle->data[3] = 2;
+ handle->data[4] = 3;
+ handle->data[5] = 4;
+ return handle;
+}
+
+TEST(ConvertNativeHandle, MakeToAidlEmpty) {
+ native_handle_t* handle = native_handle_create(0, 0);
+ NativeHandle to = makeToAidl(handle);
+ checkEq(to, handle, false /*exceptFds*/);
+ // no native_handle_close b/c fds are owned by NativeHandle now
+ EXPECT_EQ(0, native_handle_delete(handle));
+}
+
+TEST(ConvertNativeHandle, MakeToAidl) {
+ native_handle_t* handle = makeTestLibcutilsHandle();
+ NativeHandle to = makeToAidl(handle);
+ checkEq(to, handle, false /*exceptFds*/);
+ // no native_handle_close b/c fds are owned by NativeHandle now
+ EXPECT_EQ(0, native_handle_delete(handle));
+}
+
+TEST(ConvertNativeHandle, DupToAidlEmpty) {
+ native_handle_t* handle = native_handle_create(0, 0);
+ NativeHandle to = dupToAidl(handle);
+ checkEq(to, handle, true /*exceptFds*/);
+ EXPECT_EQ(0, native_handle_close(handle));
+ EXPECT_EQ(0, native_handle_delete(handle));
+}
+
+TEST(ConvertNativeHandle, DupToAidl) {
+ native_handle_t* handle = makeTestLibcutilsHandle();
+ NativeHandle to = dupToAidl(handle);
+ checkEq(to, handle, true /*exceptFds*/);
+ EXPECT_EQ(0, native_handle_close(handle));
+ EXPECT_EQ(0, native_handle_delete(handle));
+}
+
+} // namespace android
diff --git a/compatibility_matrices/Android.bp b/compatibility_matrices/Android.bp
index b2a815f..de73201 100644
--- a/compatibility_matrices/Android.bp
+++ b/compatibility_matrices/Android.bp
@@ -60,5 +60,6 @@
kernel_configs: [
"kernel_config_current_4.19",
"kernel_config_current_5.4",
+ "kernel_config_current_5.10",
],
}
diff --git a/compatibility_matrices/compatibility_matrix.current.xml b/compatibility_matrices/compatibility_matrix.current.xml
index e9df02f..a8d9a1b 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>
@@ -250,9 +258,9 @@
<instance>default</instance>
</interface>
</hal>
- <hal format="hidl" optional="true">
+ <hal format="aidl" optional="true">
<name>android.hardware.health.storage</name>
- <version>1.0</version>
+ <version>1</version>
<interface>
<name>IStorage</name>
<instance>default</instance>
@@ -260,11 +268,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>
@@ -334,6 +351,13 @@
<instance>default</instance>
</interface>
</hal>
+ <hal format="aidl" optional="true">
+ <name>android.hardware.memtrack</name>
+ <interface>
+ <name>IMemtrack</name>
+ <instance>default</instance>
+ </interface>
+ </hal>
<hal format="hidl" optional="true">
<name>android.hardware.memtrack</name>
<version>1.0</version>
@@ -411,6 +435,14 @@
</interface>
</hal>
<hal format="hidl" optional="true">
+ <name>android.hardware.radio.config</name>
+ <version>1.3</version>
+ <interface>
+ <name>IRadioConfig</name>
+ <instance>default</instance>
+ </interface>
+ </hal>
+ <hal format="hidl" optional="true">
<name>android.hardware.renderscript</name>
<version>1.0</version>
<interface>
@@ -434,6 +466,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>
@@ -524,16 +570,16 @@
</interface>
</hal>
<hal format="hidl" optional="true">
- <name>android.hardware.vr</name>
+ <name>android.hardware.weaver</name>
<version>1.0</version>
<interface>
- <name>IVr</name>
+ <name>IWeaver</name>
<instance>default</instance>
</interface>
</hal>
- <hal format="hidl" optional="true">
+ <hal format="aidl" optional="true">
<name>android.hardware.weaver</name>
- <version>1.0</version>
+ <version>1</version>
<interface>
<name>IWeaver</name>
<instance>default</instance>
diff --git a/graphics/composer/2.4/vts/functional/VtsHalGraphicsComposerV2_4TargetTest.cpp b/graphics/composer/2.4/vts/functional/VtsHalGraphicsComposerV2_4TargetTest.cpp
index 8077f08..50f282f 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/health/1.0/Android.bp b/health/1.0/Android.bp
index 7845871..7786c08 100644
--- a/health/1.0/Android.bp
+++ b/health/1.0/Android.bp
@@ -5,7 +5,6 @@
root: "android.hardware",
srcs: [
"types.hal",
- "IHealth.hal",
],
interfaces: [
"android.hidl.base@1.0",
diff --git a/health/1.0/IHealth.hal b/health/1.0/IHealth.hal
deleted file mode 100644
index 3828589..0000000
--- a/health/1.0/IHealth.hal
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hardware.health@1.0;
-
-interface IHealth {
- /**
- * This function lets you change healthd configuration from default if
- * desired. It must be called exactly once at startup time.
- *
- * The configuration values are described in 'struct HealthConfig'.
- * To use default configuration, simply return without modifying the
- * fields of the config parameter.
- *
- * @param default healthd configuration.
- */
- init(HealthConfig config) generates (HealthConfig configOut);
-
- /**
- * This function is a hook to update/change device's HealthInfo (as described
- * in 'struct HealthInfo').
- *
- * 'HealthInfo' describes device's battery and charging status, typically
- * read from kernel. These values may be modified in this call.
- *
- * @param Device Health info as described in 'struct HealthInfo'.
- * @return skipLogging Indication to the caller to add 'or' skip logging the health
- * information. Return 'true' to skip logging the update.
- * @return infoOut HealthInfo to be sent to client code. (May or may
- * not be modified).
- */
- update(HealthInfo info) generates (bool skipLogging, HealthInfo infoOut);
-
- /**
- * This function is called by healthd when framework queries for remaining
- * energy in the Battery through BatteryManager APIs.
- *
- * @return result Result of querying enery counter for the battery.
- * @return energy Battery remaining energy in nanowatt-hours.
- * Must be '0' if result is anything other than Result::SUCCESS.
- */
- energyCounter() generates (Result result, int64_t energy);
-};
diff --git a/health/1.0/default/Android.bp b/health/1.0/default/Android.bp
index aab9cc7..ff4b875 100644
--- a/health/1.0/default/Android.bp
+++ b/health/1.0/default/Android.bp
@@ -17,62 +17,3 @@
],
}
-
-cc_library_static {
- name: "android.hardware.health@1.0-impl-helper",
- vendor: true,
- srcs: ["Health.cpp"],
-
- header_libs: [
- "libbase_headers",
- "libhealthd_headers",
- ],
-
- shared_libs: [
- "libcutils",
- "libhidlbase",
- "liblog",
- "libutils",
- "android.hardware.health@1.0",
- ],
-
- static_libs: [
- "android.hardware.health@1.0-convert",
- ],
-}
-
-cc_library_shared {
- name: "android.hardware.health@1.0-impl",
- vendor: true,
- relative_install_path: "hw",
-
- static_libs: [
- "android.hardware.health@1.0-impl-helper",
- "android.hardware.health@1.0-convert",
- "libhealthd.default",
- ],
-
- shared_libs: [
- "libhidlbase",
- "libutils",
- "android.hardware.health@1.0",
- ],
-}
-
-cc_binary {
- name: "android.hardware.health@1.0-service",
- vendor: true,
- relative_install_path: "hw",
- init_rc: ["android.hardware.health@1.0-service.rc"],
- srcs: ["HealthService.cpp"],
-
- shared_libs: [
- "liblog",
- "libcutils",
- "libdl",
- "libbase",
- "libutils",
- "libhidlbase",
- "android.hardware.health@1.0",
- ],
-}
diff --git a/health/1.0/default/Health.cpp b/health/1.0/default/Health.cpp
deleted file mode 100644
index 1a02956..0000000
--- a/health/1.0/default/Health.cpp
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "health-hal"
-
-#include <Health.h>
-#include <include/hal_conversion.h>
-
-namespace android {
-namespace hardware {
-namespace health {
-namespace V1_0 {
-namespace implementation {
-
-using ::android::hardware::health::V1_0::hal_conversion::convertToHealthConfig;
-using ::android::hardware::health::V1_0::hal_conversion::convertFromHealthConfig;
-using ::android::hardware::health::V1_0::hal_conversion::convertToHealthInfo;
-using ::android::hardware::health::V1_0::hal_conversion::convertFromHealthInfo;
-
-// Methods from ::android::hardware::health::V1_0::IHealth follow.
-Return<void> Health::init(const HealthConfig& config, init_cb _hidl_cb) {
- struct healthd_config healthd_config = {};
- HealthConfig configOut;
-
- // To keep working with existing healthd static HALs,
- // convert the new HealthConfig to the old healthd_config
- // and back.
-
- convertFromHealthConfig(config, &healthd_config);
- healthd_board_init(&healthd_config);
- mGetEnergyCounter = healthd_config.energyCounter;
- convertToHealthConfig(&healthd_config, configOut);
-
- _hidl_cb(configOut);
-
- return Void();
-}
-
-Return<void> Health::update(const HealthInfo& info, update_cb _hidl_cb) {
- struct android::BatteryProperties p = {};
- HealthInfo infoOut;
-
- // To keep working with existing healthd static HALs,
- // convert the new HealthInfo to android::Batteryproperties
- // and back.
-
- convertFromHealthInfo(info, &p);
- int skipLogging = healthd_board_battery_update(&p);
- convertToHealthInfo(&p, infoOut);
-
- _hidl_cb(!!skipLogging, infoOut);
-
- return Void();
-}
-
-Return<void> Health::energyCounter(energyCounter_cb _hidl_cb) {
- int64_t energy = 0;
- Result result = Result::NOT_SUPPORTED;
-
- if (mGetEnergyCounter) {
- int status = mGetEnergyCounter(&energy);
- if (status == 0) {
- result = Result::SUCCESS;
- }
- }
-
- _hidl_cb(result, energy);
-
- return Void();
-}
-
-IHealth* HIDL_FETCH_IHealth(const char* /* name */) {
- return new Health();
-}
-
-} // namespace implementation
-} // namespace V1_0
-} // namespace health
-} // namespace hardware
-} // namespace android
diff --git a/health/1.0/default/Health.h b/health/1.0/default/Health.h
deleted file mode 100644
index ed364c1..0000000
--- a/health/1.0/default/Health.h
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#ifndef ANDROID_HARDWARE_HEALTH_V1_0_HEALTH_H
-#define ANDROID_HARDWARE_HEALTH_V1_0_HEALTH_H
-
-#include <android/hardware/health/1.0/IHealth.h>
-#include <hidl/Status.h>
-#include <hidl/MQDescriptor.h>
-#include <healthd/healthd.h>
-#include <utils/String8.h>
-
-namespace android {
-namespace hardware {
-namespace health {
-namespace V1_0 {
-namespace implementation {
-
-using ::android::hardware::health::V1_0::HealthInfo;
-using ::android::hardware::health::V1_0::HealthConfig;
-using ::android::hardware::health::V1_0::IHealth;
-using ::android::hardware::Return;
-using ::android::hardware::Void;
-using ::android::hardware::hidl_vec;
-using ::android::hardware::hidl_string;
-using ::android::sp;
-
-struct Health : public IHealth {
- // Methods from ::android::hardware::health::V1_0::IHealth follow.
- Return<void> init(const HealthConfig& config, init_cb _hidl_cb) override;
- Return<void> update(const HealthInfo& info, update_cb _hidl_cb) override;
- Return<void> energyCounter(energyCounter_cb _hidl_cb) override;
-private:
- std::function<int(int64_t *)> mGetEnergyCounter;
-};
-
-extern "C" IHealth* HIDL_FETCH_IHealth(const char* name);
-
-} // namespace implementation
-} // namespace V1_0
-} // namespace health
-} // namespace hardware
-} // namespace android
-
-#endif // ANDROID_HARDWARE_HEALTH_V1_0_HEALTH_H
diff --git a/health/1.0/default/HealthService.cpp b/health/1.0/default/HealthService.cpp
deleted file mode 100644
index 55848d2..0000000
--- a/health/1.0/default/HealthService.cpp
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "android.hardware.health@1.0-service"
-
-#include <android/hardware/health/1.0/IHealth.h>
-#include <hidl/LegacySupport.h>
-
-using android::hardware::health::V1_0::IHealth;
-using android::hardware::defaultPassthroughServiceImplementation;
-
-int main() {
- return defaultPassthroughServiceImplementation<IHealth>();
-}
diff --git a/health/1.0/default/README.md b/health/1.0/default/README.md
deleted file mode 100644
index 1ded7de..0000000
--- a/health/1.0/default/README.md
+++ /dev/null
@@ -1,66 +0,0 @@
-# Implement the 2.1 HAL instead!
-
-It is strongly recommended that you implement the 2.1 HAL directly. See
-`hardware/interfaces/health/2.1/README.md` for more details.
-
-# Implement Health 1.0 HAL
-
-1. Install common binderized service. The binderized service `dlopen()`s
- passthrough implementations on the device, so there is no need to write
- your own.
-
- ```mk
- # Install default binderized implementation to vendor.
- PRODUCT_PACKAGES += android.hardware.health@1.0-service
- ```
-
-1. Add proper VINTF manifest entry to your device manifest. Example:
-
- ```xml
- <hal format="hidl">
- <name>android.hardware.health</name>
- <transport>hwbinder</transport>
- <version>1.0</version>
- <interface>
- <name>IHealth</name>
- <instance>default</instance>
- </interface>
- </hal>
- ```
-
-1. Install the proper passthrough implemetation.
-
- 1. If you want to use the default implementation (with default `libhealthd`),
- add the following to `device.mk`:
-
- ```mk
- PRODUCT_PACKAGES += \
- android.hardware.health@1.0-impl
- ```
-
- 1. Otherwise, if you have a customized `libhealthd.<board>`:
-
- 1. Define your passthrough implementation. Example (replace `<device>`
- and `<board>` accordingly):
-
- ```bp
- cc_library_shared {
- name: "android.hardware.health@1.0-impl-<device>",
- vendor: true,
- relative_install_path: "hw",
-
- static_libs: [
- "android.hardware.health@1.0-impl-helper",
- "android.hardware.health@1.0-convert",
- "libhealthd.<board>",
- ],
- }
- ```
-
- 1. Add to `device.mk`.
-
- ```
- PRODUCT_PACKAGES += android.hardware.health@1.0-impl-<device>
- ```
-
- 1. Define appropriate SELinux permissions.
diff --git a/health/1.0/default/android.hardware.health@1.0-service.rc b/health/1.0/default/android.hardware.health@1.0-service.rc
deleted file mode 100644
index 569dc88..0000000
--- a/health/1.0/default/android.hardware.health@1.0-service.rc
+++ /dev/null
@@ -1,5 +0,0 @@
-service vendor.health-hal-1-0 /vendor/bin/hw/android.hardware.health@1.0-service
- class hal
- user system
- group system
- capabilities WAKE_ALARM BLOCK_SUSPEND
diff --git a/health/1.0/default/include/hal_conversion.h b/health/1.0/default/include/hal_conversion.h
index a92b208..a8ddb73 100644
--- a/health/1.0/default/include/hal_conversion.h
+++ b/health/1.0/default/include/hal_conversion.h
@@ -17,7 +17,7 @@
#ifndef HARDWARE_INTERFACES_HEALTH_V1_0_DEFAULT_INCLUDE_HAL_CONVERSION_H_
#define HARDWARE_INTERFACES_HEALTH_V1_0_DEFAULT_INCLUDE_HAL_CONVERSION_H_
-#include <android/hardware/health/1.0/IHealth.h>
+#include <android/hardware/health/1.0/types.h>
#include <healthd/healthd.h>
namespace android {
diff --git a/health/1.0/default/libhealthd/Android.bp b/health/1.0/default/libhealthd/Android.bp
deleted file mode 100644
index 43463eb..0000000
--- a/health/1.0/default/libhealthd/Android.bp
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright 2016 The Android Open Source Project
-
-cc_library_static {
- srcs: ["healthd_board_default.cpp"],
- name: "libhealthd.default",
- vendor_available: true,
- recovery_available: true,
- cflags: ["-Werror"],
- include_dirs: ["system/libbase/include"],
- header_libs: ["libhealthd_headers"],
-}
diff --git a/health/1.0/vts/functional/Android.bp b/health/1.0/vts/functional/Android.bp
deleted file mode 100644
index f4a04a7..0000000
--- a/health/1.0/vts/functional/Android.bp
+++ /dev/null
@@ -1,23 +0,0 @@
-//
-// Copyright (C) 2017 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-cc_test {
- name: "VtsHalHealthV1_0TargetTest",
- defaults: ["VtsHalTargetTestDefaults"],
- srcs: ["VtsHalHealthV1_0TargetTest.cpp"],
- static_libs: ["android.hardware.health@1.0"],
- test_suites: ["general-tests", "vts"],
-}
diff --git a/health/1.0/vts/functional/VtsHalHealthV1_0TargetTest.cpp b/health/1.0/vts/functional/VtsHalHealthV1_0TargetTest.cpp
deleted file mode 100644
index 8b3dcc1..0000000
--- a/health/1.0/vts/functional/VtsHalHealthV1_0TargetTest.cpp
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "health_hidl_hal_test"
-
-#include <android/hardware/health/1.0/IHealth.h>
-#include <android/hardware/health/1.0/types.h>
-#include <gtest/gtest.h>
-#include <hidl/GtestPrinter.h>
-#include <hidl/ServiceManagement.h>
-#include <log/log.h>
-
-using HealthConfig = ::android::hardware::health::V1_0::HealthConfig;
-using HealthInfo = ::android::hardware::health::V1_0::HealthInfo;
-using IHealth = ::android::hardware::health::V1_0::IHealth;
-using Result = ::android::hardware::health::V1_0::Result;
-
-using ::android::sp;
-
-class HealthHidlTest : public ::testing::TestWithParam<std::string> {
- public:
- virtual void SetUp() override {
- health = IHealth::getService(GetParam());
- ASSERT_NE(health, nullptr);
- health->init(config,
- [&](const auto& halConfigOut) { config = halConfigOut; });
- }
-
- sp<IHealth> health;
- HealthConfig config;
-};
-
-/**
- * Ensure EnergyCounter call returns positive energy counter or NOT_SUPPORTED
- */
-TEST_P(HealthHidlTest, TestEnergyCounter) {
- Result result;
- int64_t energy = 0;
- health->energyCounter([&](Result ret, int64_t energyOut) {
- result = ret;
- energy = energyOut;
- });
-
- ASSERT_TRUE(result == Result::SUCCESS || result == Result::NOT_SUPPORTED);
- ASSERT_TRUE(result != Result::SUCCESS || energy > 0);
-}
-
-GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(HealthHidlTest);
-INSTANTIATE_TEST_SUITE_P(
- PerInstance, HealthHidlTest,
- testing::ValuesIn(android::hardware::getAllHalInstanceNames(IHealth::descriptor)),
- android::hardware::PrintInstanceNameToString);
diff --git a/health/storage/1.0/default/Android.bp b/health/storage/1.0/default/Android.bp
index 3156dfe..3834244 100644
--- a/health/storage/1.0/default/Android.bp
+++ b/health/storage/1.0/default/Android.bp
@@ -38,6 +38,7 @@
],
static_libs: [
+ "libhealth_storage_impl_common",
"libfstab",
],
diff --git a/health/storage/1.0/default/Storage.cpp b/health/storage/1.0/default/Storage.cpp
index 561deaa..02b6a3d 100644
--- a/health/storage/1.0/default/Storage.cpp
+++ b/health/storage/1.0/default/Storage.cpp
@@ -18,11 +18,8 @@
#include <sstream>
-#include <android-base/chrono_utils.h>
-#include <android-base/file.h>
#include <android-base/logging.h>
-#include <android-base/strings.h>
-#include <fstab/fstab.h>
+#include <health-storage-impl/common.h>
namespace android {
namespace hardware {
@@ -31,69 +28,9 @@
namespace V1_0 {
namespace implementation {
-using base::ReadFileToString;
-using base::Timer;
-using base::Trim;
-using base::WriteStringToFd;
-using base::WriteStringToFile;
-using fs_mgr::Fstab;
-using fs_mgr::ReadDefaultFstab;
-
-std::string getGarbageCollectPath() {
- Fstab fstab;
- ReadDefaultFstab(&fstab);
-
- for (const auto& entry : fstab) {
- if (!entry.sysfs_path.empty()) {
- return entry.sysfs_path + "/manual_gc";
- }
- }
-
- return "";
-}
-
Return<void> Storage::garbageCollect(uint64_t timeoutSeconds,
const sp<IGarbageCollectCallback>& cb) {
- Result result = Result::SUCCESS;
- std::string path = getGarbageCollectPath();
-
- if (path.empty()) {
- LOG(WARNING) << "Cannot find Dev GC path";
- result = Result::UNKNOWN_ERROR;
- } else {
- Timer timer;
- LOG(INFO) << "Start Dev GC on " << path;
- while (1) {
- std::string require;
- if (!ReadFileToString(path, &require)) {
- PLOG(WARNING) << "Reading manual_gc failed in " << path;
- result = Result::IO_ERROR;
- break;
- }
- require = Trim(require);
- if (require == "" || require == "off" || require == "disabled") {
- LOG(DEBUG) << "No more to do Dev GC";
- break;
- }
- LOG(DEBUG) << "Trigger Dev GC on " << path;
- if (!WriteStringToFile("1", path)) {
- PLOG(WARNING) << "Start Dev GC failed on " << path;
- result = Result::IO_ERROR;
- break;
- }
- if (timer.duration() >= std::chrono::seconds(timeoutSeconds)) {
- LOG(WARNING) << "Dev GC timeout";
- // Timeout is not treated as an error. Try next time.
- break;
- }
- sleep(2);
- }
- LOG(INFO) << "Stop Dev GC on " << path;
- if (!WriteStringToFile("0", path)) {
- PLOG(WARNING) << "Stop Dev GC failed on " << path;
- result = Result::IO_ERROR;
- }
- }
+ Result result = GarbageCollect(timeoutSeconds);
if (cb != nullptr) {
auto ret = cb->onFinish(result);
@@ -110,28 +47,7 @@
}
int fd = handle->data[0];
- std::stringstream output;
-
- std::string path = getGarbageCollectPath();
- if (path.empty()) {
- output << "Cannot find Dev GC path";
- } else {
- std::string require;
-
- if (ReadFileToString(path, &require)) {
- output << path << ":" << require << std::endl;
- }
-
- if (WriteStringToFile("0", path)) {
- output << "stop success" << std::endl;
- }
- }
-
- if (!WriteStringToFd(output.str(), fd)) {
- PLOG(WARNING) << "debug: cannot write to fd";
- }
-
- fsync(fd);
+ DebugDump(fd);
return Void();
}
diff --git a/health/storage/1.0/vts/functional/Android.bp b/health/storage/1.0/vts/functional/Android.bp
index 2201031..731ad62 100644
--- a/health/storage/1.0/vts/functional/Android.bp
+++ b/health/storage/1.0/vts/functional/Android.bp
@@ -19,6 +19,9 @@
defaults: ["VtsHalTargetTestDefaults"],
srcs: ["VtsHalHealthStorageV1_0TargetTest.cpp"],
static_libs: ["android.hardware.health.storage@1.0"],
+ header_libs: [
+ "libhealth_storage_test_common_headers",
+ ],
shared_libs: [
"libhidlbase",
],
diff --git a/health/storage/1.0/vts/functional/VtsHalHealthStorageV1_0TargetTest.cpp b/health/storage/1.0/vts/functional/VtsHalHealthStorageV1_0TargetTest.cpp
index 24ddc5d..ddb6b5a 100644
--- a/health/storage/1.0/vts/functional/VtsHalHealthStorageV1_0TargetTest.cpp
+++ b/health/storage/1.0/vts/functional/VtsHalHealthStorageV1_0TargetTest.cpp
@@ -14,14 +14,17 @@
* limitations under the License.
*/
+#include <unistd.h>
+
+#include <thread>
+
#include <android-base/logging.h>
#include <android/hardware/health/storage/1.0/IStorage.h>
#include <gtest/gtest.h>
+#include <health-storage-test/common.h>
#include <hidl/GtestPrinter.h>
#include <hidl/HidlTransportSupport.h>
#include <hidl/ServiceManagement.h>
-#include <unistd.h>
-#include <thread>
namespace android {
namespace hardware {
@@ -29,61 +32,17 @@
namespace storage {
namespace V1_0 {
+using namespace ::android::hardware::health::storage::test;
using ::std::literals::chrono_literals::operator""ms;
#define ASSERT_OK(ret) ASSERT_TRUE(ret.isOk()) << ret.description()
-// Dev GC timeout. This is the timeout used by vold.
-const uint64_t kDevGcTimeoutSec = 120;
-const std::chrono::seconds kDevGcTimeout{kDevGcTimeoutSec};
-// Dev GC timeout tolerance. The HAL may not immediately return after the
-// timeout, so include an acceptable tolerance.
-const std::chrono::seconds kDevGcTolerance{3};
-// Time accounted for RPC calls.
-const std::chrono::milliseconds kRpcTime{1000};
-
-template <typename R>
-std::string toString(std::chrono::duration<R, std::milli> time) {
- return std::to_string(time.count()) + "ms";
-}
-
-/** An atomic boolean flag that indicates whether a task has finished. */
-class Flag {
- public:
- void onFinish() {
- std::unique_lock<std::mutex> lock(mMutex);
- onFinishLocked(&lock);
- }
- template <typename R, typename P>
- bool wait(std::chrono::duration<R, P> duration) {
- std::unique_lock<std::mutex> lock(mMutex);
- return waitLocked(&lock, duration);
- }
-
- protected:
- /** Will unlock. */
- void onFinishLocked(std::unique_lock<std::mutex>* lock) {
- mFinished = true;
- lock->unlock();
- mCv.notify_all();
- }
- template <typename R, typename P>
- bool waitLocked(std::unique_lock<std::mutex>* lock, std::chrono::duration<R, P> duration) {
- mCv.wait_for(*lock, duration, [this] { return mFinished; });
- return mFinished;
- }
-
- bool mFinished{false};
- std::mutex mMutex;
- std::condition_variable mCv;
-};
-
class GcCallback : public IGarbageCollectCallback, public Flag {
- public:
+ public:
Return<void> onFinish(Result result) override {
- std::unique_lock<std::mutex> lock(mMutex);
- mResult = result;
- Flag::onFinishLocked(&lock);
+ std::unique_lock<std::mutex> lock(mutex_);
+ result_ = result;
+ Flag::OnFinishLocked(&lock);
return Void();
}
@@ -93,13 +52,13 @@
*/
template <typename R, typename P>
void waitForResult(std::chrono::duration<R, P> timeout, Result expected) {
- std::unique_lock<std::mutex> lock(mMutex);
- ASSERT_TRUE(waitLocked(&lock, timeout)) << "timeout after " << toString(timeout);
- EXPECT_EQ(expected, mResult);
+ std::unique_lock<std::mutex> lock(mutex_);
+ ASSERT_TRUE(WaitLocked(&lock, timeout)) << "timeout after " << to_string(timeout);
+ EXPECT_EQ(expected, result_);
}
- private:
- Result mResult{Result::UNKNOWN_ERROR};
+ private:
+ Result result_{Result::UNKNOWN_ERROR};
};
class HealthStorageHidlTest : public ::testing::TestWithParam<std::string> {
@@ -127,10 +86,10 @@
auto pingFlag = std::make_shared<Flag>();
std::thread([service, pingFlag] {
service->ping();
- pingFlag->onFinish();
+ pingFlag->OnFinish();
})
.detach();
- return pingFlag->wait(timeout);
+ return pingFlag->Wait(timeout);
}
sp<IStorage> fs;
@@ -147,7 +106,7 @@
// Hold test process because HAL can be single-threaded and doing GC.
ASSERT_TRUE(ping(kDevGcTimeout + kDevGcTolerance + kRpcTime))
<< "Service must be available after "
- << toString(kDevGcTimeout + kDevGcTolerance + kRpcTime);
+ << to_string(kDevGcTimeout + kDevGcTolerance + kRpcTime);
}
/**
diff --git a/health/storage/aidl/Android.bp b/health/storage/aidl/Android.bp
new file mode 100644
index 0000000..c39a46d
--- /dev/null
+++ b/health/storage/aidl/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+aidl_interface {
+ name: "android.hardware.health.storage",
+ vendor_available: true,
+ srcs: ["android/hardware/health/storage/*.aidl"],
+ stability: "vintf",
+ backend: {
+ cpp: {
+ enabled: false,
+ },
+ java: {
+ enabled: false,
+ },
+ ndk: {
+ vndk: {
+ enabled: true,
+ },
+ },
+ },
+}
diff --git a/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/IGarbageCollectCallback.aidl b/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/IGarbageCollectCallback.aidl
new file mode 100644
index 0000000..0f382d7
--- /dev/null
+++ b/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/IGarbageCollectCallback.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.health.storage;
+@VintfStability
+interface IGarbageCollectCallback {
+ oneway void onFinish(in android.hardware.health.storage.Result result);
+}
diff --git a/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/IStorage.aidl b/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/IStorage.aidl
new file mode 100644
index 0000000..61f838a
--- /dev/null
+++ b/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/IStorage.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.health.storage;
+@VintfStability
+interface IStorage {
+ oneway void garbageCollect(in long timeoutSeconds, in android.hardware.health.storage.IGarbageCollectCallback callback);
+}
diff --git a/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/Result.aidl b/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/Result.aidl
new file mode 100644
index 0000000..a345808
--- /dev/null
+++ b/health/storage/aidl/aidl_api/android.hardware.health.storage/current/android/hardware/health/storage/Result.aidl
@@ -0,0 +1,25 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.health.storage;
+@Backing(type="int") @VintfStability
+enum Result {
+ SUCCESS = 0,
+ IO_ERROR = 1,
+ UNKNOWN_ERROR = 2,
+}
diff --git a/health/storage/aidl/android/hardware/health/storage/IGarbageCollectCallback.aidl b/health/storage/aidl/android/hardware/health/storage/IGarbageCollectCallback.aidl
new file mode 100644
index 0000000..ccd1b44
--- /dev/null
+++ b/health/storage/aidl/android/hardware/health/storage/IGarbageCollectCallback.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.health.storage;
+
+import android.hardware.health.storage.Result;
+
+/**
+ * Callback interface to IStorage.garbageCollect.
+ */
+@VintfStability
+interface IGarbageCollectCallback {
+ /**
+ * When garbage collection has finished, the implementation must
+ * invoke this function to indicate the result of the garbage collection.
+ *
+ * @param out result Execution result. See documentation for Result for
+ * details.
+ */
+ oneway void onFinish(in Result result);
+}
diff --git a/health/storage/aidl/android/hardware/health/storage/IStorage.aidl b/health/storage/aidl/android/hardware/health/storage/IStorage.aidl
new file mode 100644
index 0000000..78992a2
--- /dev/null
+++ b/health/storage/aidl/android/hardware/health/storage/IStorage.aidl
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.health.storage;
+
+import android.hardware.health.storage.IGarbageCollectCallback;
+
+/**
+ * IStorage is an interface that provides operations on underlying storage
+ * devices, including flash memory.
+ */
+@VintfStability
+interface IStorage {
+ /**
+ * Start garbage collection on the driver of storage devices.
+ *
+ * Garbage collection must be started at regular intervals when it is a good
+ * time for a longer-running cleanup tasks, roughly daily.
+ *
+ * When garbage collection finishes or encounters an error before the
+ * specified timeout, the implementation must call IGarbageCollect.finish
+ * immediately with appropriate result.
+ *
+ * If garbage collection does not finish within the specified timeout,
+ * the implementation must stop garbage collection, and must not call
+ * IGarbageCollect.finish.
+ *
+ * @param timeoutSeconds timeout in seconds. The implementation must
+ * return after the timeout is reached.
+ *
+ * @param callback callback interface. Callback must be null if the client
+ * does not need to receive any callbacks.
+ *
+ */
+ oneway void garbageCollect(in long timeoutSeconds, in IGarbageCollectCallback callback);
+}
diff --git a/health/storage/aidl/android/hardware/health/storage/Result.aidl b/health/storage/aidl/android/hardware/health/storage/Result.aidl
new file mode 100644
index 0000000..73bb779
--- /dev/null
+++ b/health/storage/aidl/android/hardware/health/storage/Result.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.health.storage;
+
+/**
+ * Status values for HAL methods.
+ */
+@VintfStability
+@Backing(type="int")
+enum Result {
+ /**
+ * Execution of the method is successful.
+ */
+ SUCCESS = 0,
+ /**
+ * An IO error is encountered when the HAL communicates with the device.
+ */
+ IO_ERROR,
+ /**
+ * An unknown error is encountered.
+ */
+ UNKNOWN_ERROR,
+}
diff --git a/health/storage/aidl/default/Android.bp b/health/storage/aidl/default/Android.bp
new file mode 100644
index 0000000..68a8ee2
--- /dev/null
+++ b/health/storage/aidl/default/Android.bp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+cc_defaults {
+ name: "libhealth_storage_impl_defaults",
+ vendor: true,
+ shared_libs: [
+ "libbase",
+ "libbinder_ndk",
+ "android.hardware.health.storage-unstable-ndk_platform",
+ ],
+ static_libs: [
+ "libfstab",
+ "libhealth_storage_impl_common",
+ ],
+}
+
+cc_library_static {
+ name: "libhealth_storage_default_impl",
+ defaults: ["libhealth_storage_impl_defaults"],
+ srcs: [
+ "Storage.cpp",
+ ],
+ visibility: [
+ ":__subpackages__",
+ "//hardware/interfaces/tests/extension/health/storage:__subpackages__",
+ ],
+}
+
+cc_binary {
+ name: "android.hardware.health.storage-service.default",
+ defaults: ["libhealth_storage_impl_defaults"],
+ relative_install_path: "hw",
+ init_rc: ["health-storage-default.rc"],
+ vintf_fragments: ["health-storage-default.xml"],
+ srcs: ["main.cpp"],
+ static_libs: [
+ "libhealth_storage_default_impl",
+ ],
+}
diff --git a/health/storage/aidl/default/Storage.cpp b/health/storage/aidl/default/Storage.cpp
new file mode 100644
index 0000000..faa4ff6
--- /dev/null
+++ b/health/storage/aidl/default/Storage.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Storage.h"
+
+#include <sstream>
+
+#include <android-base/logging.h>
+#include <health-storage-impl/common.h>
+
+using ::android::hardware::health::storage::DebugDump;
+using ::android::hardware::health::storage::GarbageCollect;
+
+using HResult = android::hardware::health::storage::V1_0::Result;
+using AResult = aidl::android::hardware::health::storage::Result;
+// Ensure static_cast<AResult>(any HResult) works
+static_assert(static_cast<AResult>(HResult::SUCCESS) == AResult::SUCCESS);
+static_assert(static_cast<AResult>(HResult::IO_ERROR) == AResult::IO_ERROR);
+static_assert(static_cast<AResult>(HResult::UNKNOWN_ERROR) == AResult::UNKNOWN_ERROR);
+
+namespace aidl::android::hardware::health::storage {
+
+ndk::ScopedAStatus Storage::garbageCollect(
+ int64_t timeout_seconds, const std::shared_ptr<IGarbageCollectCallback>& callback) {
+ AResult result = static_cast<AResult>(GarbageCollect(static_cast<uint64_t>(timeout_seconds)));
+ if (callback != nullptr) {
+ auto status = callback->onFinish(result);
+ if (!status.isOk()) {
+ LOG(WARNING) << "Cannot return result " << toString(result)
+ << " to callback: " << status.getDescription();
+ }
+ }
+ return ndk::ScopedAStatus::ok();
+}
+
+binder_status_t Storage::dump(int fd, const char**, uint32_t) {
+ DebugDump(fd);
+ return STATUS_OK;
+}
+
+} // namespace aidl::android::hardware::health::storage
diff --git a/health/storage/aidl/default/Storage.h b/health/storage/aidl/default/Storage.h
new file mode 100644
index 0000000..049991b
--- /dev/null
+++ b/health/storage/aidl/default/Storage.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/health/storage/BnStorage.h>
+
+namespace aidl::android::hardware::health::storage {
+
+class Storage : public BnStorage {
+ ndk::ScopedAStatus garbageCollect(
+ int64_t timeout_seconds,
+ const std::shared_ptr<IGarbageCollectCallback>& callback) override;
+ binder_status_t dump(int fd, const char** args, uint32_t num_args) override;
+};
+
+} // namespace aidl::android::hardware::health::storage
diff --git a/health/storage/aidl/default/health-storage-default.rc b/health/storage/aidl/default/health-storage-default.rc
new file mode 100644
index 0000000..fc1cc8b
--- /dev/null
+++ b/health/storage/aidl/default/health-storage-default.rc
@@ -0,0 +1,7 @@
+service vendor.health-storage-default /vendor/bin/hw/android.hardware.health.storage-service.default
+ interface aidl android.hardware.health.storage.IStorage/default
+ oneshot
+ disabled
+ class hal
+ user system
+ group system
diff --git a/health/storage/aidl/default/health-storage-default.xml b/health/storage/aidl/default/health-storage-default.xml
new file mode 100644
index 0000000..14d4901
--- /dev/null
+++ b/health/storage/aidl/default/health-storage-default.xml
@@ -0,0 +1,7 @@
+<manifest version="1.0" type="device">
+ <hal format="aidl">
+ <name>android.hardware.health.storage</name>
+ <version>1</version>
+ <fqname>IStorage/default</fqname>
+ </hal>
+</manifest>
diff --git a/health/storage/aidl/default/main.cpp b/health/storage/aidl/default/main.cpp
new file mode 100644
index 0000000..186b64c
--- /dev/null
+++ b/health/storage/aidl/default/main.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include "Storage.h"
+
+using aidl::android::hardware::health::storage::Storage;
+using std::string_literals::operator""s;
+
+int main() {
+ ABinderProcess_setThreadPoolMaxThreadCount(0);
+
+ // make a default storage service
+ auto storage = ndk::SharedRefBase::make<Storage>();
+ const std::string name = Storage::descriptor + "/default"s;
+ CHECK_EQ(STATUS_OK,
+ AServiceManager_registerLazyService(storage->asBinder().get(), name.c_str()));
+
+ ABinderProcess_joinThreadPool();
+ return EXIT_FAILURE; // should not reach
+}
diff --git a/health/storage/aidl/vts/functional/Android.bp b/health/storage/aidl/vts/functional/Android.bp
new file mode 100644
index 0000000..86b72a7
--- /dev/null
+++ b/health/storage/aidl/vts/functional/Android.bp
@@ -0,0 +1,37 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_test {
+ name: "VtsHalHealthStorageTargetTest",
+ defaults: [
+ "VtsHalTargetTestDefaults",
+ "use_libaidlvintf_gtest_helper_static",
+ ],
+ srcs: [
+ "VtsHalHealthStorageTargetTest.cpp",
+ ],
+ shared_libs: [
+ "libbinder_ndk",
+ ],
+ static_libs: [
+ "android.hardware.health.storage-ndk_platform",
+ ],
+ header_libs: [
+ "libhealth_storage_test_common_headers",
+ ],
+ test_suites: [
+ "vts",
+ ],
+ test_config: "VtsHalHealthStorageTargetTest.xml",
+}
diff --git a/health/storage/aidl/vts/functional/VtsHalHealthStorageTargetTest.cpp b/health/storage/aidl/vts/functional/VtsHalHealthStorageTargetTest.cpp
new file mode 100644
index 0000000..3b6b6b4
--- /dev/null
+++ b/health/storage/aidl/vts/functional/VtsHalHealthStorageTargetTest.cpp
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <unistd.h>
+
+#include <chrono>
+#include <set>
+#include <string>
+#include <thread>
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/health/storage/BnGarbageCollectCallback.h>
+#include <aidl/android/hardware/health/storage/IStorage.h>
+#include <android-base/logging.h>
+#include <android/binder_ibinder.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <gtest/gtest.h>
+#include <health-storage-test/common.h>
+
+namespace aidl::android::hardware::health::storage {
+
+using namespace ::android::hardware::health::storage::test;
+using std::chrono_literals::operator""ms;
+
+#define ASSERT_OK(ret) ASSERT_TRUE(ret.isOk()) << ret.getDescription()
+#define EXPECT_OK(ret) EXPECT_TRUE(ret.isOk()) << ret.getDescription()
+
+class GcCallback : public BnGarbageCollectCallback, public Flag {
+ public:
+ ndk::ScopedAStatus onFinish(Result result) override {
+ std::unique_lock<std::mutex> lock(mutex_);
+ result_ = result;
+ OnFinishLocked(&lock);
+ return ndk::ScopedAStatus::ok();
+ }
+
+ /**
+ * Wait for a specific "timeout". If GC has finished, test that the result
+ * is equal to the "expected" value.
+ */
+ template <typename R, typename P>
+ void WaitForResult(std::chrono::duration<R, P> timeout, Result expected) {
+ std::unique_lock<std::mutex> lock(mutex_);
+ ASSERT_TRUE(WaitLocked(&lock, timeout)) << "timeout after " << to_string(timeout);
+ EXPECT_EQ(expected, result_);
+ }
+
+ private:
+ Result result_{Result::UNKNOWN_ERROR};
+};
+
+class HealthStorageAidl : public testing::TestWithParam<std::string> {
+ public:
+ virtual void SetUp() override {
+ std::string name = GetParam();
+ ASSERT_TRUE(AServiceManager_isDeclared(name.c_str())) << name;
+ ndk::SpAIBinder binder(AServiceManager_waitForService(name.c_str()));
+ ASSERT_NE(binder, nullptr);
+ storage_ = IStorage::fromBinder(binder);
+ ASSERT_NE(storage_, nullptr);
+ }
+
+ virtual void TearDown() override {
+ EXPECT_TRUE(ping(kRpcTime))
+ << "Service is not responsive; expect subsequent tests to fail.";
+ }
+
+ /**
+ * Ping the service and expect it to return after "timeout". Return true
+ * iff the service is responsive within "timeout".
+ */
+ template <typename R, typename P>
+ bool ping(std::chrono::duration<R, P> timeout) {
+ // Ensure the service is responsive after the test.
+ std::shared_ptr<IStorage> service = storage_;
+ auto ping_flag = std::make_shared<Flag>();
+ std::thread([service, ping_flag] {
+ EXPECT_EQ(STATUS_OK, AIBinder_ping(service->asBinder().get()));
+ ping_flag->OnFinish();
+ }).detach();
+ return ping_flag->Wait(timeout);
+ }
+
+ std::shared_ptr<IStorage> storage_;
+};
+
+/**
+ * Ensure garbage collection works on null callback.
+ */
+TEST_P(HealthStorageAidl, GcNullCallback) {
+ ASSERT_OK(storage_->garbageCollect(kDevGcTimeoutSec, nullptr));
+
+ // Hold test process because HAL can be single-threaded and doing GC.
+ ASSERT_TRUE(ping(kDevGcTimeout + kDevGcTolerance + kRpcTime))
+ << "Service must be available after "
+ << to_string(kDevGcTimeout + kDevGcTolerance + kRpcTime);
+}
+
+/**
+ * Ensure garbage collection works on non-null callback.
+ */
+TEST_P(HealthStorageAidl, GcNonNullCallback) {
+ std::shared_ptr<GcCallback> cb = ndk::SharedRefBase::make<GcCallback>();
+ ASSERT_OK(storage_->garbageCollect(kDevGcTimeoutSec, cb));
+ cb->WaitForResult(kDevGcTimeout + kDevGcTolerance + kRpcTime, Result::SUCCESS);
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(HealthStorageAidl);
+INSTANTIATE_TEST_SUITE_P(
+ HealthStorage, HealthStorageAidl,
+ testing::ValuesIn(::android::getAidlHalInstanceNames(IStorage::descriptor)),
+ ::android::PrintInstanceNameToString);
+
+} // namespace aidl::android::hardware::health::storage
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ ABinderProcess_setThreadPoolMaxThreadCount(1);
+ ABinderProcess_startThreadPool();
+ return RUN_ALL_TESTS();
+}
diff --git a/health/storage/aidl/vts/functional/VtsHalHealthStorageTargetTest.xml b/health/storage/aidl/vts/functional/VtsHalHealthStorageTargetTest.xml
new file mode 100644
index 0000000..f8a1c87
--- /dev/null
+++ b/health/storage/aidl/vts/functional/VtsHalHealthStorageTargetTest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs VtsHalHealthStorageTargetTest.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-native" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push" value="VtsHalHealthStorageTargetTest->/data/local/tmp/VtsHalHealthStorageTargetTest" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="VtsHalHealthStorageTargetTest" />
+ <option name="native-test-timeout" value="3m" />
+ </test>
+</configuration>
diff --git a/health/storage/impl_common/Android.bp b/health/storage/impl_common/Android.bp
new file mode 100644
index 0000000..e1149c0
--- /dev/null
+++ b/health/storage/impl_common/Android.bp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Common implementation between HIDL and AIDL HAL.
+cc_library_static {
+ name: "libhealth_storage_impl_common",
+ vendor: true,
+ srcs: [
+ "impl_common.cpp",
+ ],
+ export_include_dirs: [
+ "include",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "libhidlbase",
+ "liblog",
+ "android.hardware.health.storage@1.0",
+ ],
+
+ static_libs: [
+ "libfstab",
+ ],
+
+ export_shared_lib_headers: [
+ "android.hardware.health.storage@1.0",
+ ],
+}
diff --git a/health/storage/impl_common/impl_common.cpp b/health/storage/impl_common/impl_common.cpp
new file mode 100644
index 0000000..6e753d4
--- /dev/null
+++ b/health/storage/impl_common/impl_common.cpp
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <health-storage-impl/common.h>
+
+#include <android-base/chrono_utils.h>
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <fstab/fstab.h>
+
+using ::android::base::ReadFileToString;
+using ::android::base::Timer;
+using ::android::base::Trim;
+using ::android::base::WriteStringToFd;
+using ::android::base::WriteStringToFile;
+using ::android::fs_mgr::Fstab;
+using ::android::fs_mgr::ReadDefaultFstab;
+using ::android::hardware::health::storage::V1_0::Result;
+
+namespace android::hardware::health::storage {
+
+static std::string GetGarbageCollectPath() {
+ Fstab fstab;
+ ReadDefaultFstab(&fstab);
+
+ for (const auto& entry : fstab) {
+ if (!entry.sysfs_path.empty()) {
+ return entry.sysfs_path + "/manual_gc";
+ }
+ }
+
+ return "";
+}
+
+Result GarbageCollect(uint64_t timeout_seconds) {
+ std::string path = GetGarbageCollectPath();
+
+ if (path.empty()) {
+ LOG(WARNING) << "Cannot find Dev GC path";
+ return Result::UNKNOWN_ERROR;
+ }
+
+ Result result = Result::SUCCESS;
+ Timer timer;
+ LOG(INFO) << "Start Dev GC on " << path;
+ while (1) {
+ std::string require;
+ if (!ReadFileToString(path, &require)) {
+ PLOG(WARNING) << "Reading manual_gc failed in " << path;
+ result = Result::IO_ERROR;
+ break;
+ }
+ require = Trim(require);
+ if (require == "" || require == "off" || require == "disabled") {
+ LOG(DEBUG) << "No more to do Dev GC";
+ break;
+ }
+ LOG(DEBUG) << "Trigger Dev GC on " << path;
+ if (!WriteStringToFile("1", path)) {
+ PLOG(WARNING) << "Start Dev GC failed on " << path;
+ result = Result::IO_ERROR;
+ break;
+ }
+ if (timer.duration() >= std::chrono::seconds(timeout_seconds)) {
+ LOG(WARNING) << "Dev GC timeout";
+ // Timeout is not treated as an error. Try next time.
+ break;
+ }
+ sleep(2);
+ }
+ LOG(INFO) << "Stop Dev GC on " << path;
+ if (!WriteStringToFile("0", path)) {
+ PLOG(WARNING) << "Stop Dev GC failed on " << path;
+ result = Result::IO_ERROR;
+ }
+
+ return result;
+}
+
+void DebugDump(int fd) {
+ std::stringstream output;
+
+ std::string path = GetGarbageCollectPath();
+ if (path.empty()) {
+ output << "Cannot find Dev GC path";
+ } else {
+ std::string require;
+
+ if (ReadFileToString(path, &require)) {
+ output << path << ":" << require << std::endl;
+ }
+
+ if (WriteStringToFile("0", path)) {
+ output << "stop success" << std::endl;
+ }
+ }
+
+ if (!WriteStringToFd(output.str(), fd)) {
+ PLOG(WARNING) << "debug: cannot write to fd";
+ }
+
+ fsync(fd);
+}
+
+} // namespace android::hardware::health::storage
diff --git a/health/storage/impl_common/include/health-storage-impl/common.h b/health/storage/impl_common/include/health-storage-impl/common.h
new file mode 100644
index 0000000..c84a6a9
--- /dev/null
+++ b/health/storage/impl_common/include/health-storage-impl/common.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android/hardware/health/storage/1.0/types.h>
+#include <string>
+
+namespace android::hardware::health::storage {
+
+// Run debug on fd
+void DebugDump(int fd);
+
+// Run garbage collection on GetGarbageCollectPath(). Blocks until garbage
+// collect finishes or |timeout_seconds| has reached.
+V1_0::Result GarbageCollect(uint64_t timeout_seconds);
+
+} // namespace android::hardware::health::storage
diff --git a/health/1.0/default/libhealthd/healthd_board_default.cpp b/health/storage/test_common/Android.bp
similarity index 64%
rename from health/1.0/default/libhealthd/healthd_board_default.cpp
rename to health/storage/test_common/Android.bp
index 127f98e..7c6bef4 100644
--- a/health/1.0/default/libhealthd/healthd_board_default.cpp
+++ b/health/storage/test_common/Android.bp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,7 @@
* limitations under the License.
*/
-#include <healthd/healthd.h>
-
-void healthd_board_init(struct healthd_config*)
-{
- // use defaults
-}
-
-int healthd_board_battery_update(struct android::BatteryProperties*)
-{
- // return 0 to log periodic polled battery status to kernel log
- return 0;
+cc_library_headers {
+ name: "libhealth_storage_test_common_headers",
+ export_include_dirs: ["include"],
}
diff --git a/health/storage/test_common/include/health-storage-test/common.h b/health/storage/test_common/include/health-storage-test/common.h
new file mode 100644
index 0000000..dfda830
--- /dev/null
+++ b/health/storage/test_common/include/health-storage-test/common.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+#include <string>
+
+namespace android::hardware::health::storage::test {
+
+// Dev GC timeout. This is the timeout used by vold.
+const uint64_t kDevGcTimeoutSec = 120;
+const std::chrono::seconds kDevGcTimeout{kDevGcTimeoutSec};
+// Dev GC timeout tolerance. The HAL may not immediately return after the
+// timeout, so include an acceptable tolerance.
+const std::chrono::seconds kDevGcTolerance{3};
+// Time accounted for RPC calls.
+const std::chrono::milliseconds kRpcTime{1000};
+
+template <typename R>
+std::string to_string(std::chrono::duration<R, std::milli> time) {
+ return std::to_string(time.count()) + "ms";
+}
+
+/** An atomic boolean flag that indicates whether a task has finished. */
+class Flag {
+ public:
+ void OnFinish() {
+ std::unique_lock<std::mutex> lock(mutex_);
+ OnFinishLocked(&lock);
+ }
+ template <typename R, typename P>
+ bool Wait(std::chrono::duration<R, P> duration) {
+ std::unique_lock<std::mutex> lock(mutex_);
+ return WaitLocked(&lock, duration);
+ }
+
+ protected:
+ /** Will unlock. */
+ void OnFinishLocked(std::unique_lock<std::mutex>* lock) {
+ finished_ = true;
+ lock->unlock();
+ cv_.notify_all();
+ }
+ template <typename R, typename P>
+ bool WaitLocked(std::unique_lock<std::mutex>* lock, std::chrono::duration<R, P> duration) {
+ cv_.wait_for(*lock, duration, [this] { return finished_; });
+ return finished_;
+ }
+
+ bool finished_{false};
+ std::mutex mutex_;
+ std::condition_variable cv_;
+};
+
+} // namespace android::hardware::health::storage::test
diff --git a/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/memtrack/aidl/Android.bp b/memtrack/aidl/Android.bp
new file mode 100644
index 0000000..fe4d01b
--- /dev/null
+++ b/memtrack/aidl/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.
+
+aidl_interface {
+ name: "android.hardware.memtrack",
+ vendor_available: true,
+ srcs: ["android/hardware/memtrack/*.aidl"],
+ stability: "vintf",
+ backend: {
+ cpp: {
+ enabled: false,
+ },
+ java: {
+ enabled: false,
+ },
+ ndk: {
+ vndk: {
+ enabled: true,
+ },
+ },
+ },
+}
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Timestamp.aidl b/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/DeviceInfo.aidl
similarity index 91%
rename from security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Timestamp.aidl
rename to memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/DeviceInfo.aidl
index 4d5b659..00abff9 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Timestamp.aidl
+++ b/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/DeviceInfo.aidl
@@ -15,8 +15,9 @@
// 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;
+package android.hardware.memtrack;
@VintfStability
-parcelable Timestamp {
- long milliSeconds;
+parcelable DeviceInfo {
+ int id;
+ @utf8InCpp String name;
}
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Timestamp.aidl b/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/IMemtrack.aidl
similarity index 81%
copy from security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Timestamp.aidl
copy to memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/IMemtrack.aidl
index 4d5b659..844a1bb 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Timestamp.aidl
+++ b/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/IMemtrack.aidl
@@ -15,8 +15,9 @@
// 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;
+package android.hardware.memtrack;
@VintfStability
-parcelable Timestamp {
- long milliSeconds;
+interface IMemtrack {
+ android.hardware.memtrack.MemtrackRecord[] getMemory(in int pid, in android.hardware.memtrack.MemtrackType type);
+ android.hardware.memtrack.DeviceInfo[] getGpuDeviceInfo();
}
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Timestamp.aidl b/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/MemtrackRecord.aidl
similarity index 72%
copy from security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Timestamp.aidl
copy to memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/MemtrackRecord.aidl
index 4d5b659..09ecefc 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Timestamp.aidl
+++ b/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/MemtrackRecord.aidl
@@ -15,8 +15,18 @@
// 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;
+package android.hardware.memtrack;
@VintfStability
-parcelable Timestamp {
- long milliSeconds;
+parcelable MemtrackRecord {
+ int flags;
+ long sizeInBytes;
+ const int FLAG_SMAPS_ACCOUNTED = 2;
+ const int FLAG_SMAPS_UNACCOUNTED = 4;
+ const int FLAG_SHARED = 8;
+ const int FLAG_SHARED_PSS = 16;
+ const int FLAG_PRIVATE = 32;
+ const int FLAG_SYSTEM = 64;
+ const int FLAG_DEDICATED = 128;
+ const int FLAG_NONSECURE = 256;
+ const int FLAG_SECURE = 512;
}
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Timestamp.aidl b/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/MemtrackType.aidl
similarity index 84%
copy from security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Timestamp.aidl
copy to memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/MemtrackType.aidl
index 4d5b659..7f3f939 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Timestamp.aidl
+++ b/memtrack/aidl/aidl_api/android.hardware.memtrack/current/android/hardware/memtrack/MemtrackType.aidl
@@ -15,8 +15,13 @@
// with such a backward incompatible change, it has a high risk of breaking
// later when a module using the interface is updated, e.g., Mainline modules.
-package android.hardware.security.keymint;
-@VintfStability
-parcelable Timestamp {
- long milliSeconds;
+package android.hardware.memtrack;
+@Backing(type="int") @VintfStability
+enum MemtrackType {
+ OTHER = 0,
+ GL = 1,
+ GRAPHICS = 2,
+ MULTIMEDIA = 3,
+ CAMERA = 4,
+ NUM_TYPES = 5,
}
diff --git a/health/1.0/default/libhealthd/healthd_board_default.cpp b/memtrack/aidl/android/hardware/memtrack/DeviceInfo.aidl
similarity index 64%
copy from health/1.0/default/libhealthd/healthd_board_default.cpp
copy to memtrack/aidl/android/hardware/memtrack/DeviceInfo.aidl
index 127f98e..bcba544 100644
--- a/health/1.0/default/libhealthd/healthd_board_default.cpp
+++ b/memtrack/aidl/android/hardware/memtrack/DeviceInfo.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,14 @@
* limitations under the License.
*/
-#include <healthd/healthd.h>
+package android.hardware.memtrack;
-void healthd_board_init(struct healthd_config*)
-{
- // use defaults
+/*
+ * Contains the device name and the device id.
+ */
+@VintfStability
+parcelable DeviceInfo {
+ int id;
+ @utf8InCpp String name;
}
-int healthd_board_battery_update(struct android::BatteryProperties*)
-{
- // return 0 to log periodic polled battery status to kernel log
- return 0;
-}
diff --git a/memtrack/aidl/android/hardware/memtrack/IMemtrack.aidl b/memtrack/aidl/android/hardware/memtrack/IMemtrack.aidl
new file mode 100644
index 0000000..18587ee
--- /dev/null
+++ b/memtrack/aidl/android/hardware/memtrack/IMemtrack.aidl
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.memtrack;
+
+import android.hardware.memtrack.DeviceInfo;
+import android.hardware.memtrack.MemtrackRecord;
+import android.hardware.memtrack.MemtrackType;
+
+/**
+ * The Memory Tracker HAL is designed to return information about
+ * device-specific memory usage.
+ * The primary goal is to be able to track memory that is not
+ * trackable in any other way, for example texture memory that is allocated by
+ * a process, but not mapped in to that process's address space.
+ * A secondary goal is to be able to categorize memory used by a process into
+ * GL, graphics, etc. All memory sizes must be in real memory usage,
+ * accounting for stride, bit depth, rounding up to page size, etc.
+ *
+ * Constructor for the interface should be used to perform memtrack management
+ * setup actions and must be called once before any calls to getMemory().
+ */
+@VintfStability
+interface IMemtrack {
+ /**
+ * getMemory() populates MemtrackRecord vector with the sizes of memory
+ * plus associated flags for that memory.
+ *
+ * A process collecting memory statistics will call getMemory for each
+ * combination of pid and memory type. For each memory type that it
+ * recognizes, the HAL must fill out an array of memtrack_record
+ * structures breaking down the statistics of that memory type as much as
+ * possible. For example,
+ * getMemory(<pid>, GL) might return:
+ * { { 4096, ACCOUNTED | PRIVATE | SYSTEM },
+ * { 40960, UNACCOUNTED | PRIVATE | SYSTEM },
+ * { 8192, ACCOUNTED | PRIVATE | DEDICATED },
+ * { 8192, UNACCOUNTED | PRIVATE | DEDICATED } }
+ * If the HAL cannot differentiate between SYSTEM and DEDICATED memory, it
+ * could return:
+ * { { 12288, ACCOUNTED | PRIVATE },
+ * { 49152, UNACCOUNTED | PRIVATE } }
+ *
+ * Memory must not overlap between types. For example, a graphics buffer
+ * that has been mapped into the GPU as a surface must show up when
+ * GRAPHICS is requested and not when GL
+ * is requested.
+ *
+ * @param pid process for which memory information is requested
+ * @param type memory type that information is being requested about
+ * @return vector of MemtrackRecord containing memory information
+ */
+ MemtrackRecord[] getMemory(in int pid, in MemtrackType type);
+
+ /**
+ * getGpuDeviceInfo() populates DeviceInfo with the ID and name
+ * of each GPU device.
+ *
+ * For example, getGpuDeviceInfor, might return:
+ * { { 0, <gpu-device-name> },
+ * { 1, <gpu-device-name> } }
+ *
+ * This information is used to identify GPU devices for GPU specific
+ * memory accounting (e.g. DMA buffer usage).
+ *
+ * @return vector of DeviceInfo populated for all GPU devices.
+ */
+ DeviceInfo[] getGpuDeviceInfo();
+}
diff --git a/memtrack/aidl/android/hardware/memtrack/MemtrackRecord.aidl b/memtrack/aidl/android/hardware/memtrack/MemtrackRecord.aidl
new file mode 100644
index 0000000..dae026e
--- /dev/null
+++ b/memtrack/aidl/android/hardware/memtrack/MemtrackRecord.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.memtrack;
+
+/*
+ * Each record consists of the size of the memory used by the process and
+ * flags indicate all the MemtrackFlags that are valid for this record.
+ */
+@VintfStability
+parcelable MemtrackRecord {
+ /* Memtrack Flags */
+ const int FLAG_SMAPS_ACCOUNTED = 1 << 1;
+ const int FLAG_SMAPS_UNACCOUNTED = 1 << 2;
+ const int FLAG_SHARED = 1 << 3;
+ const int FLAG_SHARED_PSS = 1 << 4;
+ const int FLAG_PRIVATE = 1 << 5;
+ const int FLAG_SYSTEM = 1 << 6;
+ const int FLAG_DEDICATED = 1 << 7;
+ const int FLAG_NONSECURE = 1 << 8;
+ const int FLAG_SECURE = 1 << 9;
+
+ /* Bitfield indicating all flags that are valid for this record */
+ int flags;
+
+ long sizeInBytes;
+}
+
diff --git a/health/1.0/default/libhealthd/healthd_board_default.cpp b/memtrack/aidl/android/hardware/memtrack/MemtrackType.aidl
similarity index 64%
copy from health/1.0/default/libhealthd/healthd_board_default.cpp
copy to memtrack/aidl/android/hardware/memtrack/MemtrackType.aidl
index 127f98e..715c6bf 100644
--- a/health/1.0/default/libhealthd/healthd_board_default.cpp
+++ b/memtrack/aidl/android/hardware/memtrack/MemtrackType.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,18 @@
* limitations under the License.
*/
-#include <healthd/healthd.h>
+package android.hardware.memtrack;
-void healthd_board_init(struct healthd_config*)
-{
- // use defaults
-}
-
-int healthd_board_battery_update(struct android::BatteryProperties*)
-{
- // return 0 to log periodic polled battery status to kernel log
- return 0;
+/**
+ * Tags which define the usage of the memory buffers.
+ */
+@VintfStability
+@Backing(type="int")
+enum MemtrackType {
+ OTHER = 0,
+ GL = 1,
+ GRAPHICS = 2,
+ MULTIMEDIA = 3,
+ CAMERA = 4,
+ NUM_TYPES,
}
diff --git a/memtrack/aidl/default/Android.bp b/memtrack/aidl/default/Android.bp
new file mode 100644
index 0000000..52f88c8
--- /dev/null
+++ b/memtrack/aidl/default/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_binary {
+ name: "android.hardware.memtrack-service.example",
+ relative_install_path: "hw",
+ init_rc: ["memtrack-default.rc"],
+ vintf_fragments: ["memtrack-default.xml"],
+ vendor: true,
+ shared_libs: [
+ "libbase",
+ "libbinder_ndk",
+ "android.hardware.memtrack-ndk_platform",
+ ],
+ srcs: [
+ "main.cpp",
+ "Memtrack.cpp",
+ ],
+}
diff --git a/memtrack/aidl/default/Memtrack.cpp b/memtrack/aidl/default/Memtrack.cpp
new file mode 100644
index 0000000..7361719
--- /dev/null
+++ b/memtrack/aidl/default/Memtrack.cpp
@@ -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.
+ */
+
+#include "Memtrack.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace memtrack {
+
+ndk::ScopedAStatus Memtrack::getMemory(int pid, MemtrackType type,
+ std::vector<MemtrackRecord>* _aidl_return) {
+ if (pid < 0) {
+ return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_ILLEGAL_ARGUMENT));
+ }
+ if (type < MemtrackType::OTHER || type >= MemtrackType::NUM_TYPES) {
+ return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION));
+ }
+ _aidl_return->clear();
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Memtrack::getGpuDeviceInfo(std::vector<DeviceInfo>* _aidl_return) {
+ _aidl_return->clear();
+ return ndk::ScopedAStatus::ok();
+}
+
+} // namespace memtrack
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/memtrack/aidl/default/Memtrack.h b/memtrack/aidl/default/Memtrack.h
new file mode 100644
index 0000000..f2ef60e
--- /dev/null
+++ b/memtrack/aidl/default/Memtrack.h
@@ -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.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/memtrack/BnMemtrack.h>
+#include <aidl/android/hardware/memtrack/DeviceInfo.h>
+#include <aidl/android/hardware/memtrack/MemtrackRecord.h>
+#include <aidl/android/hardware/memtrack/MemtrackType.h>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace memtrack {
+
+class Memtrack : public BnMemtrack {
+ ndk::ScopedAStatus getMemory(int pid, MemtrackType type,
+ std::vector<MemtrackRecord>* _aidl_return) override;
+
+ ndk::ScopedAStatus getGpuDeviceInfo(std::vector<DeviceInfo>* _aidl_return) override;
+};
+
+} // namespace memtrack
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/memtrack/aidl/default/main.cpp b/memtrack/aidl/default/main.cpp
new file mode 100644
index 0000000..d063d2a
--- /dev/null
+++ b/memtrack/aidl/default/main.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Memtrack.h"
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+using aidl::android::hardware::memtrack::Memtrack;
+
+int main() {
+ ABinderProcess_setThreadPoolMaxThreadCount(0);
+ std::shared_ptr<Memtrack> memtrack = ndk::SharedRefBase::make<Memtrack>();
+
+ const std::string instance = std::string() + Memtrack::descriptor + "/default";
+ binder_status_t status =
+ AServiceManager_addService(memtrack->asBinder().get(), instance.c_str());
+ CHECK(status == STATUS_OK);
+
+ ABinderProcess_joinThreadPool();
+ return EXIT_FAILURE; // Unreachable
+}
diff --git a/memtrack/aidl/default/memtrack-default.rc b/memtrack/aidl/default/memtrack-default.rc
new file mode 100644
index 0000000..1725cd0
--- /dev/null
+++ b/memtrack/aidl/default/memtrack-default.rc
@@ -0,0 +1,4 @@
+service vendor.memtrack-default /vendor/bin/hw/android.hardware.memtrack-service.example
+ class hal
+ user nobody
+ group system
diff --git a/memtrack/aidl/default/memtrack-default.xml b/memtrack/aidl/default/memtrack-default.xml
new file mode 100644
index 0000000..3e3e0f6
--- /dev/null
+++ b/memtrack/aidl/default/memtrack-default.xml
@@ -0,0 +1,7 @@
+<manifest version="1.0" type="device">
+ <hal format="aidl">
+ <name>android.hardware.memtrack</name>
+ <fqname>IMemtrack/default</fqname>
+ </hal>
+</manifest>
+
diff --git a/memtrack/aidl/vts/Android.bp b/memtrack/aidl/vts/Android.bp
new file mode 100644
index 0000000..ea36677
--- /dev/null
+++ b/memtrack/aidl/vts/Android.bp
@@ -0,0 +1,17 @@
+cc_test {
+ name: "VtsHalMemtrackTargetTest",
+ defaults: [
+ "VtsHalTargetTestDefaults",
+ "use_libaidlvintf_gtest_helper_static",
+ ],
+ srcs: ["VtsHalMemtrackTargetTest.cpp"],
+ shared_libs: [
+ "libbinder_ndk",
+ ],
+ static_libs: [
+ "android.hardware.memtrack-unstable-ndk_platform",
+ ],
+ test_suites: [
+ "vts-core",
+ ],
+}
diff --git a/memtrack/aidl/vts/VtsHalMemtrackTargetTest.cpp b/memtrack/aidl/vts/VtsHalMemtrackTargetTest.cpp
new file mode 100644
index 0000000..4d33101
--- /dev/null
+++ b/memtrack/aidl/vts/VtsHalMemtrackTargetTest.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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/memtrack/DeviceInfo.h>
+#include <aidl/android/hardware/memtrack/IMemtrack.h>
+#include <aidl/android/hardware/memtrack/MemtrackType.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+using aidl::android::hardware::memtrack::DeviceInfo;
+using aidl::android::hardware::memtrack::IMemtrack;
+using aidl::android::hardware::memtrack::MemtrackRecord;
+using aidl::android::hardware::memtrack::MemtrackType;
+
+class MemtrackAidlTest : public testing::TestWithParam<std::string> {
+ public:
+ virtual void SetUp() override {
+ const auto instance = GetParam();
+ ASSERT_TRUE(AServiceManager_isDeclared(instance.c_str()));
+ auto memtrackBinder = ndk::SpAIBinder(AServiceManager_waitForService(instance.c_str()));
+ memtrack_ = IMemtrack::fromBinder(memtrackBinder);
+ ASSERT_NE(memtrack_, nullptr);
+ }
+
+ std::shared_ptr<IMemtrack> memtrack_;
+};
+
+TEST_P(MemtrackAidlTest, GetMemoryInvalidPid) {
+ int pid = -1;
+ MemtrackType type = MemtrackType::OTHER;
+ std::vector<MemtrackRecord> records;
+
+ auto status = memtrack_->getMemory(pid, type, &records);
+
+ EXPECT_EQ(status.getExceptionCode(), EX_ILLEGAL_ARGUMENT);
+}
+
+TEST_P(MemtrackAidlTest, GetMemoryInvalidType) {
+ int pid = 1;
+ MemtrackType type = MemtrackType::NUM_TYPES;
+ std::vector<MemtrackRecord> records;
+
+ auto status = memtrack_->getMemory(pid, type, &records);
+
+ EXPECT_EQ(status.getExceptionCode(), EX_UNSUPPORTED_OPERATION);
+}
+
+TEST_P(MemtrackAidlTest, GetMemory) {
+ int pid = 1;
+ MemtrackType type = MemtrackType::OTHER;
+ std::vector<MemtrackRecord> records;
+
+ auto status = memtrack_->getMemory(pid, type, &records);
+
+ EXPECT_TRUE(status.isOk());
+}
+
+TEST_P(MemtrackAidlTest, GetGpuDeviceInfo) {
+ std::vector<DeviceInfo> device_info;
+
+ auto status = memtrack_->getGpuDeviceInfo(&device_info);
+
+ EXPECT_TRUE(status.isOk());
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MemtrackAidlTest);
+INSTANTIATE_TEST_SUITE_P(PerInstance, MemtrackAidlTest,
+ testing::ValuesIn(android::getAidlHalInstanceNames(IMemtrack::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/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/Callbacks.h b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Callbacks.h
index 65b75e5..3b32e1d 100644
--- a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Callbacks.h
+++ b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Callbacks.h
@@ -27,8 +27,31 @@
#include <nnapi/hal/ProtectCallback.h>
#include <nnapi/hal/TransferValue.h>
+// 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 {
+// Converts the results of IDevice::getSupportedOperations* to the NN canonical format. On success,
+// this function returns with the supported operations as indicated by a driver. On failure, this
+// function returns with the appropriate nn::GeneralError.
+nn::GeneralResult<std::vector<bool>> supportedOperationsCallback(
+ ErrorStatus status, const hidl_vec<bool>& supportedOperations);
+
+// Converts the results of IDevice::prepareModel* to the NN canonical format. On success, this
+// function returns with a non-null nn::SharedPreparedModel with a feature level of
+// nn::Version::ANDROID_OC_MR1. On failure, this function returns with the appropriate
+// nn::GeneralError.
+nn::GeneralResult<nn::SharedPreparedModel> prepareModelCallback(
+ ErrorStatus status, const sp<IPreparedModel>& preparedModel);
+
+// Converts the results of IDevice::execute* to the NN canonical format. On success, this function
+// returns with an empty output shape vector and no timing information. On failure, this function
+// returns with the appropriate nn::ExecutionError.
+nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> executionCallback(
+ ErrorStatus status);
+
+// A HIDL callback class to receive the results of IDevice::prepareModel asynchronously.
class PreparedModelCallback final : public IPreparedModelCallback,
public hal::utils::IProtectedCallback {
public:
@@ -41,11 +64,10 @@
Data get();
private:
- void notifyInternal(Data result);
-
hal::utils::TransferValue<Data> mData;
};
+// A HIDL callback class to receive the results of IDevice::execute asynchronously.
class ExecutionCallback final : public IExecutionCallback, public hal::utils::IProtectedCallback {
public:
using Data = nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>>;
@@ -57,8 +79,6 @@
Data get();
private:
- void notifyInternal(Data result);
-
hal::utils::TransferValue<Data> mData;
};
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 ee103ba..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
@@ -32,8 +32,12 @@
#include <string>
#include <vector>
+// 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 V1_0::IDevice to nn::IDevice.
class Device final : public nn::IDevice {
struct PrivateConstructorTag {};
@@ -48,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 31f366d..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
@@ -29,9 +29,14 @@
#include <utility>
#include <vector>
+// 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 PreparedModel final : public nn::IPreparedModel {
+// Class that adapts V1_0::IPreparedModel to nn::IPreparedModel.
+class PreparedModel final : public nn::IPreparedModel,
+ public std::enable_shared_from_this<PreparedModel> {
struct PrivateConstructorTag {};
public:
@@ -44,13 +49,15 @@
nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> execute(
const nn::Request& request, nn::MeasureTiming measure,
const nn::OptionalTimePoint& deadline,
- const nn::OptionalTimeoutDuration& loopTimeoutDuration) const override;
+ const nn::OptionalDuration& loopTimeoutDuration) const override;
nn::GeneralResult<std::pair<nn::SyncFence, nn::ExecuteFencedInfoCallback>> executeFenced(
const nn::Request& request, const std::vector<nn::SyncFence>& waitFor,
nn::MeasureTiming measure, const nn::OptionalTimePoint& deadline,
- const nn::OptionalTimeoutDuration& loopTimeoutDuration,
- const nn::OptionalTimeoutDuration& timeoutDurationAfterFence) const override;
+ 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/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/Callbacks.cpp b/neuralnetworks/1.0/utils/src/Callbacks.cpp
index b1259c3..ea3ea56 100644
--- a/neuralnetworks/1.0/utils/src/Callbacks.cpp
+++ b/neuralnetworks/1.0/utils/src/Callbacks.cpp
@@ -27,69 +27,62 @@
#include <nnapi/Result.h>
#include <nnapi/Types.h>
#include <nnapi/hal/CommonUtils.h>
+#include <nnapi/hal/HandleError.h>
#include <nnapi/hal/ProtectCallback.h>
#include <nnapi/hal/TransferValue.h>
#include <utility>
-namespace android::hardware::neuralnetworks::V1_0::utils {
-namespace {
+// See hardware/interfaces/neuralnetworks/utils/README.md for more information on HIDL interface
+// lifetimes across processes and for protecting asynchronous calls across HIDL.
-nn::GeneralResult<nn::SharedPreparedModel> convertPreparedModel(
- const sp<IPreparedModel>& preparedModel) {
- return NN_TRY(utils::PreparedModel::create(preparedModel));
+namespace android::hardware::neuralnetworks::V1_0::utils {
+
+nn::GeneralResult<std::vector<bool>> supportedOperationsCallback(
+ ErrorStatus status, const hidl_vec<bool>& supportedOperations) {
+ HANDLE_HAL_STATUS(status) << "get supported operations failed with " << toString(status);
+ return supportedOperations;
}
-} // namespace
+nn::GeneralResult<nn::SharedPreparedModel> prepareModelCallback(
+ ErrorStatus status, const sp<IPreparedModel>& preparedModel) {
+ HANDLE_HAL_STATUS(status) << "model preparation failed with " << toString(status);
+ return NN_TRY(PreparedModel::create(preparedModel));
+}
+
+nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> executionCallback(
+ ErrorStatus status) {
+ HANDLE_HAL_STATUS(status) << "execution failed with " << toString(status);
+ return {};
+}
Return<void> PreparedModelCallback::notify(ErrorStatus status,
const sp<IPreparedModel>& preparedModel) {
- if (status != ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- notifyInternal(NN_ERROR(canonical) << "preparedModel failed with " << toString(status));
- } else if (preparedModel == nullptr) {
- notifyInternal(NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
- << "Returned preparedModel is nullptr");
- } else {
- notifyInternal(convertPreparedModel(preparedModel));
- }
+ mData.put(prepareModelCallback(status, preparedModel));
return Void();
}
void PreparedModelCallback::notifyAsDeadObject() {
- notifyInternal(NN_ERROR(nn::ErrorStatus::DEAD_OBJECT) << "Dead object");
+ mData.put(NN_ERROR(nn::ErrorStatus::DEAD_OBJECT) << "Dead object");
}
PreparedModelCallback::Data PreparedModelCallback::get() {
return mData.take();
}
-void PreparedModelCallback::notifyInternal(PreparedModelCallback::Data result) {
- mData.put(std::move(result));
-}
-
// ExecutionCallback methods begin here
Return<void> ExecutionCallback::notify(ErrorStatus status) {
- if (status != ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- notifyInternal(NN_ERROR(canonical) << "execute failed with " << toString(status));
- } else {
- notifyInternal({});
- }
+ mData.put(executionCallback(status));
return Void();
}
void ExecutionCallback::notifyAsDeadObject() {
- notifyInternal(NN_ERROR(nn::ErrorStatus::DEAD_OBJECT) << "Dead object");
+ mData.put(NN_ERROR(nn::ErrorStatus::DEAD_OBJECT) << "Dead object");
}
ExecutionCallback::Data ExecutionCallback::get() {
return mData.take();
}
-void ExecutionCallback::notifyInternal(ExecutionCallback::Data result) {
- mData.put(std::move(result));
-}
-
} // 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 285c515..bb31a26 100644
--- a/neuralnetworks/1.0/utils/src/Device.cpp
+++ b/neuralnetworks/1.0/utils/src/Device.cpp
@@ -31,6 +31,7 @@
#include <nnapi/hal/CommonUtils.h>
#include <nnapi/hal/HandleError.h>
#include <nnapi/hal/ProtectCallback.h>
+#include <nnapi/hal/TransferValue.h>
#include <functional>
#include <memory>
@@ -38,27 +39,27 @@
#include <string>
#include <vector>
+// 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 {
namespace {
-nn::GeneralResult<nn::Capabilities> initCapabilities(V1_0::IDevice* device) {
+nn::GeneralResult<nn::Capabilities> capabilitiesCallback(ErrorStatus status,
+ const Capabilities& capabilities) {
+ HANDLE_HAL_STATUS(status) << "getting capabilities failed with " << toString(status);
+ return nn::convert(capabilities);
+}
+
+nn::GeneralResult<nn::Capabilities> getCapabilitiesFrom(V1_0::IDevice* device) {
CHECK(device != nullptr);
- nn::GeneralResult<nn::Capabilities> result = NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
- << "uninitialized";
- const auto cb = [&result](ErrorStatus status, const Capabilities& capabilities) {
- if (status != ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- result = NN_ERROR(canonical) << "getCapabilities failed with " << toString(status);
- } else {
- result = nn::convert(capabilities);
- }
- };
+ auto cb = hal::utils::CallbackValue(capabilitiesCallback);
const auto ret = device->getCapabilities(cb);
HANDLE_TRANSPORT_FAILURE(ret);
- return result;
+ return cb.take();
}
} // namespace
@@ -74,7 +75,7 @@
<< "V1_0::utils::Device::create must have non-null device";
}
- auto capabilities = NN_TRY(initCapabilities(device.get()));
+ auto capabilities = NN_TRY(getCapabilitiesFrom(device.get()));
auto deathHandler = NN_TRY(hal::utils::DeathHandler::create(device));
return std::make_shared<const Device>(PrivateConstructorTag{}, std::move(name),
@@ -105,6 +106,10 @@
return nn::DeviceType::OTHER;
}
+bool Device::isUpdatable() const {
+ return false;
+}
+
const std::vector<nn::Extension>& Device::getSupportedExtensions() const {
return kExtensions;
}
@@ -131,27 +136,12 @@
const auto hidlModel = NN_TRY(convert(modelInShared));
- nn::GeneralResult<std::vector<bool>> result = NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
- << "uninitialized";
- auto cb = [&result, &model](ErrorStatus status, const hidl_vec<bool>& supportedOperations) {
- if (status != ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- result = NN_ERROR(canonical)
- << "getSupportedOperations failed with " << toString(status);
- } else if (supportedOperations.size() != model.main.operations.size()) {
- result = NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
- << "getSupportedOperations returned vector of size "
- << supportedOperations.size() << " but expected "
- << model.main.operations.size();
- } else {
- result = supportedOperations;
- }
- };
+ auto cb = hal::utils::CallbackValue(supportedOperationsCallback);
const auto ret = kDevice->getSupportedOperations(hidlModel, cb);
HANDLE_TRANSPORT_FAILURE(ret);
- return result;
+ return cb.take();
}
nn::GeneralResult<nn::SharedPreparedModel> Device::prepareModel(
@@ -170,10 +160,7 @@
const auto ret = kDevice->prepareModel(hidlModel, cb);
const auto status = HANDLE_TRANSPORT_FAILURE(ret);
- if (status != ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- return NN_ERROR(canonical) << "prepareModel failed with " << toString(status);
- }
+ HANDLE_HAL_STATUS(status) << "model preparation failed with " << toString(status);
return cb->get();
}
diff --git a/neuralnetworks/1.0/utils/src/PreparedModel.cpp b/neuralnetworks/1.0/utils/src/PreparedModel.cpp
index 46dd3f8..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"
@@ -34,13 +35,15 @@
#include <utility>
#include <vector>
+// 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 {
nn::GeneralResult<std::shared_ptr<const PreparedModel>> PreparedModel::create(
sp<V1_0::IPreparedModel> preparedModel) {
if (preparedModel == nullptr) {
- return NN_ERROR(nn::ErrorStatus::INVALID_ARGUMENT)
- << "V1_0::utils::PreparedModel::create must have non-null preparedModel";
+ return NN_ERROR() << "V1_0::utils::PreparedModel::create must have non-null preparedModel";
}
auto deathHandler = NN_TRY(hal::utils::DeathHandler::create(preparedModel));
@@ -55,7 +58,7 @@
nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> PreparedModel::execute(
const nn::Request& request, nn::MeasureTiming /*measure*/,
const nn::OptionalTimePoint& /*deadline*/,
- const nn::OptionalTimeoutDuration& /*loopTimeoutDuration*/) const {
+ const nn::OptionalDuration& /*loopTimeoutDuration*/) const {
// Ensure that request is ready for IPC.
std::optional<nn::Request> maybeRequestInShared;
const nn::Request& requestInShared = NN_TRY(hal::utils::makeExecutionFailure(
@@ -68,10 +71,7 @@
const auto ret = kPreparedModel->execute(hidlRequest, cb);
const auto status = HANDLE_TRANSPORT_FAILURE(ret);
- if (status != ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- return NN_ERROR(canonical) << "execute failed with " << toString(status);
- }
+ HANDLE_HAL_STATUS(status) << "execution failed with " << toString(status);
auto result = NN_TRY(cb->get());
NN_TRY(hal::utils::makeExecutionFailure(
@@ -81,15 +81,20 @@
}
nn::GeneralResult<std::pair<nn::SyncFence, nn::ExecuteFencedInfoCallback>>
-PreparedModel::executeFenced(
- const nn::Request& /*request*/, const std::vector<nn::SyncFence>& /*waitFor*/,
- nn::MeasureTiming /*measure*/, const nn::OptionalTimePoint& /*deadline*/,
- const nn::OptionalTimeoutDuration& /*loopTimeoutDuration*/,
- const nn::OptionalTimeoutDuration& /*timeoutDurationAfterFence*/) const {
+PreparedModel::executeFenced(const nn::Request& /*request*/,
+ const std::vector<nn::SyncFence>& /*waitFor*/,
+ nn::MeasureTiming /*measure*/,
+ const nn::OptionalTimePoint& /*deadline*/,
+ const nn::OptionalDuration& /*loopTimeoutDuration*/,
+ const nn::OptionalDuration& /*timeoutDurationAfterFence*/) const {
return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
<< "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/Conversions.h b/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Conversions.h
index f646462..5d0769f 100644
--- a/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Conversions.h
+++ b/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Conversions.h
@@ -51,6 +51,10 @@
nn::GeneralResult<Model> convert(const nn::Model& model);
nn::GeneralResult<ExecutionPreference> convert(const nn::ExecutionPreference& executionPreference);
+nn::GeneralResult<V1_0::DeviceStatus> convert(const nn::DeviceStatus& deviceStatus);
+nn::GeneralResult<V1_0::Request> convert(const nn::Request& request);
+nn::GeneralResult<V1_0::ErrorStatus> convert(const nn::ErrorStatus& status);
+
} // namespace android::hardware::neuralnetworks::V1_1::utils
#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_1_CONVERSIONS_H
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 c1e95fe1a..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
@@ -32,8 +32,12 @@
#include <string>
#include <vector>
+// 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_1::utils {
+// Class that adapts V1_1::IDevice to nn::IDevice.
class Device final : public nn::IDevice {
struct PrivateConstructorTag {};
@@ -48,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/Conversions.cpp b/neuralnetworks/1.1/utils/src/Conversions.cpp
index 359f68a..b47f25a 100644
--- a/neuralnetworks/1.1/utils/src/Conversions.cpp
+++ b/neuralnetworks/1.1/utils/src/Conversions.cpp
@@ -275,4 +275,16 @@
return validatedConvert(executionPreference);
}
+nn::GeneralResult<V1_0::DeviceStatus> convert(const nn::DeviceStatus& deviceStatus) {
+ return V1_0::utils::convert(deviceStatus);
+}
+
+nn::GeneralResult<V1_0::Request> convert(const nn::Request& request) {
+ return V1_0::utils::convert(request);
+}
+
+nn::GeneralResult<V1_0::ErrorStatus> convert(const nn::ErrorStatus& status) {
+ return V1_0::utils::convert(status);
+}
+
} // namespace android::hardware::neuralnetworks::V1_1::utils
diff --git a/neuralnetworks/1.1/utils/src/Device.cpp b/neuralnetworks/1.1/utils/src/Device.cpp
index f73d3f8..d2ef57f 100644
--- a/neuralnetworks/1.1/utils/src/Device.cpp
+++ b/neuralnetworks/1.1/utils/src/Device.cpp
@@ -39,27 +39,27 @@
#include <string>
#include <vector>
+// 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_1::utils {
namespace {
-nn::GeneralResult<nn::Capabilities> initCapabilities(V1_1::IDevice* device) {
+nn::GeneralResult<nn::Capabilities> capabilitiesCallback(V1_0::ErrorStatus status,
+ const Capabilities& capabilities) {
+ HANDLE_HAL_STATUS(status) << "getting capabilities failed with " << toString(status);
+ return nn::convert(capabilities);
+}
+
+nn::GeneralResult<nn::Capabilities> getCapabilitiesFrom(V1_1::IDevice* device) {
CHECK(device != nullptr);
- nn::GeneralResult<nn::Capabilities> result = NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
- << "uninitialized";
- const auto cb = [&result](V1_0::ErrorStatus status, const Capabilities& capabilities) {
- if (status != V1_0::ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- result = NN_ERROR(canonical) << "getCapabilities_1_1 failed with " << toString(status);
- } else {
- result = nn::convert(capabilities);
- }
- };
+ auto cb = hal::utils::CallbackValue(capabilitiesCallback);
const auto ret = device->getCapabilities_1_1(cb);
HANDLE_TRANSPORT_FAILURE(ret);
- return result;
+ return cb.take();
}
} // namespace
@@ -75,7 +75,7 @@
<< "V1_1::utils::Device::create must have non-null device";
}
- auto capabilities = NN_TRY(initCapabilities(device.get()));
+ auto capabilities = NN_TRY(getCapabilitiesFrom(device.get()));
auto deathHandler = NN_TRY(hal::utils::DeathHandler::create(device));
return std::make_shared<const Device>(PrivateConstructorTag{}, std::move(name),
@@ -106,6 +106,10 @@
return nn::DeviceType::UNKNOWN;
}
+bool Device::isUpdatable() const {
+ return false;
+}
+
const std::vector<nn::Extension>& Device::getSupportedExtensions() const {
return kExtensions;
}
@@ -132,28 +136,12 @@
const auto hidlModel = NN_TRY(convert(modelInShared));
- nn::GeneralResult<std::vector<bool>> result = NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
- << "uninitialized";
- auto cb = [&result, &model](V1_0::ErrorStatus status,
- const hidl_vec<bool>& supportedOperations) {
- if (status != V1_0::ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- result = NN_ERROR(canonical)
- << "getSupportedOperations_1_1 failed with " << toString(status);
- } else if (supportedOperations.size() != model.main.operations.size()) {
- result = NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
- << "getSupportedOperations_1_1 returned vector of size "
- << supportedOperations.size() << " but expected "
- << model.main.operations.size();
- } else {
- result = supportedOperations;
- }
- };
+ auto cb = hal::utils::CallbackValue(V1_0::utils::supportedOperationsCallback);
const auto ret = kDevice->getSupportedOperations_1_1(hidlModel, cb);
HANDLE_TRANSPORT_FAILURE(ret);
- return result;
+ return cb.take();
}
nn::GeneralResult<nn::SharedPreparedModel> Device::prepareModel(
@@ -173,10 +161,7 @@
const auto ret = kDevice->prepareModel_1_1(hidlModel, hidlPreference, cb);
const auto status = HANDLE_TRANSPORT_FAILURE(ret);
- if (status != V1_0::ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- return NN_ERROR(canonical) << "prepareModel failed with " << toString(status);
- }
+ HANDLE_HAL_STATUS(status) << "model preparation failed with " << toString(status);
return cb->get();
}
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/Callbacks.h b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Callbacks.h
index bc7d92a..ba3c1ba 100644
--- a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Callbacks.h
+++ b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Callbacks.h
@@ -31,8 +31,24 @@
#include <nnapi/hal/ProtectCallback.h>
#include <nnapi/hal/TransferValue.h>
+// 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_2::utils {
+// Converts the results of IDevice::prepareModel* to the NN canonical format. On success, this
+// function returns with a non-null nn::SharedPreparedModel with a feature level of
+// nn::Version::ANDROID_Q. On failure, this function returns with the appropriate nn::GeneralError.
+nn::GeneralResult<nn::SharedPreparedModel> prepareModelCallback(
+ V1_0::ErrorStatus status, const sp<IPreparedModel>& preparedModel);
+
+// Converts the results of IDevice::execute* to the NN canonical format. On success, this function
+// returns with the output shapes and the timing information. On failure, this function returns with
+// the appropriate nn::ExecutionError.
+nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> executionCallback(
+ V1_0::ErrorStatus status, const hidl_vec<OutputShape>& outputShapes, const Timing& timing);
+
+// A HIDL callback class to receive the results of IDevice::prepareModel* asynchronously.
class PreparedModelCallback final : public IPreparedModelCallback,
public hal::utils::IProtectedCallback {
public:
@@ -48,11 +64,10 @@
Data get();
private:
- void notifyInternal(Data result);
-
hal::utils::TransferValue<Data> mData;
};
+// A HIDL callback class to receive the results of IDevice::execute_1_2 asynchronously.
class ExecutionCallback final : public IExecutionCallback, public hal::utils::IProtectedCallback {
public:
using Data = nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>>;
@@ -66,8 +81,6 @@
Data get();
private:
- void notifyInternal(Data result);
-
hal::utils::TransferValue<Data> mData;
};
diff --git a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Conversions.h b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Conversions.h
index 5dcbc0b..6fd1337 100644
--- a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Conversions.h
+++ b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Conversions.h
@@ -97,6 +97,12 @@
nn::GeneralResult<hidl_vec<hidl_handle>> convert(const std::vector<nn::SharedHandle>& handles);
nn::GeneralResult<hidl_vec<OutputShape>> convert(const std::vector<nn::OutputShape>& outputShapes);
+nn::GeneralResult<V1_0::DeviceStatus> convert(const nn::DeviceStatus& deviceStatus);
+nn::GeneralResult<V1_0::Request> convert(const nn::Request& request);
+nn::GeneralResult<V1_0::ErrorStatus> convert(const nn::ErrorStatus& status);
+nn::GeneralResult<V1_1::ExecutionPreference> convert(
+ const nn::ExecutionPreference& executionPreference);
+
} // namespace android::hardware::neuralnetworks::V1_2::utils
#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_CONVERSIONS_H
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 a68830d..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
@@ -32,14 +32,29 @@
#include <string>
#include <vector>
+// 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_2::utils {
-nn::GeneralResult<std::string> initVersionString(V1_2::IDevice* device);
-nn::GeneralResult<nn::DeviceType> initDeviceType(V1_2::IDevice* device);
-nn::GeneralResult<std::vector<nn::Extension>> initExtensions(V1_2::IDevice* device);
-nn::GeneralResult<std::pair<uint32_t, uint32_t>> initNumberOfCacheFilesNeeded(
+// Retrieves the version string from the provided device object. On failure, this function returns
+// with the appropriate nn::GeneralError.
+nn::GeneralResult<std::string> getVersionStringFrom(V1_2::IDevice* device);
+
+// Retrieves the device type from the provided device object. On failure, this function returns with
+// the appropriate nn::GeneralError.
+nn::GeneralResult<nn::DeviceType> getDeviceTypeFrom(V1_2::IDevice* device);
+
+// Retrieves the extensions supported by the provided device object. On failure, this function
+// returns with the appropriate nn::GeneralError.
+nn::GeneralResult<std::vector<nn::Extension>> getSupportedExtensionsFrom(V1_2::IDevice* device);
+
+// Retrieves the number of model cache files and data cache files needed by the provided device
+// object. On failure, this function returns with the appropriate nn::GeneralError.
+nn::GeneralResult<std::pair<uint32_t, uint32_t>> getNumberOfCacheFilesNeededFrom(
V1_2::IDevice* device);
+// Class that adapts V1_2::IDevice to nn::IDevice.
class Device final : public nn::IDevice {
struct PrivateConstructorTag {};
@@ -56,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 65e1e8a..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
@@ -30,28 +30,35 @@
#include <utility>
#include <vector>
+// 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_2::utils {
-class PreparedModel final : public nn::IPreparedModel {
+// Class that adapts V1_2::IPreparedModel to nn::IPreparedModel.
+class PreparedModel final : public nn::IPreparedModel,
+ public std::enable_shared_from_this<PreparedModel> {
struct PrivateConstructorTag {};
public:
static nn::GeneralResult<std::shared_ptr<const PreparedModel>> create(
- sp<V1_2::IPreparedModel> preparedModel);
+ sp<V1_2::IPreparedModel> preparedModel, bool executeSynchronously);
- PreparedModel(PrivateConstructorTag tag, sp<V1_2::IPreparedModel> preparedModel,
- hal::utils::DeathHandler deathHandler);
+ PreparedModel(PrivateConstructorTag tag, bool executeSynchronously,
+ sp<V1_2::IPreparedModel> preparedModel, hal::utils::DeathHandler deathHandler);
nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> execute(
const nn::Request& request, nn::MeasureTiming measure,
const nn::OptionalTimePoint& deadline,
- const nn::OptionalTimeoutDuration& loopTimeoutDuration) const override;
+ const nn::OptionalDuration& loopTimeoutDuration) const override;
nn::GeneralResult<std::pair<nn::SyncFence, nn::ExecuteFencedInfoCallback>> executeFenced(
const nn::Request& request, const std::vector<nn::SyncFence>& waitFor,
nn::MeasureTiming measure, const nn::OptionalTimePoint& deadline,
- const nn::OptionalTimeoutDuration& loopTimeoutDuration,
- const nn::OptionalTimeoutDuration& timeoutDurationAfterFence) const override;
+ const nn::OptionalDuration& loopTimeoutDuration,
+ const nn::OptionalDuration& timeoutDurationAfterFence) const override;
+
+ nn::GeneralResult<nn::SharedBurst> configureExecutionBurst() const override;
std::any getUnderlyingResource() const override;
@@ -61,6 +68,7 @@
nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> executeAsynchronously(
const V1_0::Request& request, MeasureTiming measure) const;
+ const bool kExecuteSynchronously;
const sp<V1_2::IPreparedModel> kPreparedModel;
const hal::utils::DeathHandler kDeathHandler;
};
diff --git a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Utils.h b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Utils.h
index 70149a2..c289fc8 100644
--- a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Utils.h
+++ b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Utils.h
@@ -30,6 +30,8 @@
namespace android::hardware::neuralnetworks::V1_2::utils {
+using CacheToken = hidl_array<uint8_t, static_cast<size_t>(Constant::BYTE_SIZE_OF_CACHE_TOKEN)>;
+
constexpr auto kDefaultMesaureTiming = MeasureTiming::NO;
constexpr auto kNoTiming = Timing{.timeOnDevice = std::numeric_limits<uint64_t>::max(),
.timeInDriver = std::numeric_limits<uint64_t>::max()};
diff --git a/neuralnetworks/1.2/utils/src/Callbacks.cpp b/neuralnetworks/1.2/utils/src/Callbacks.cpp
index 39f88c2..fefa122 100644
--- a/neuralnetworks/1.2/utils/src/Callbacks.cpp
+++ b/neuralnetworks/1.2/utils/src/Callbacks.cpp
@@ -27,6 +27,7 @@
#include <nnapi/IPreparedModel.h>
#include <nnapi/Result.h>
#include <nnapi/Types.h>
+#include <nnapi/hal/1.0/Callbacks.h>
#include <nnapi/hal/1.0/Conversions.h>
#include <nnapi/hal/1.0/PreparedModel.h>
#include <nnapi/hal/CommonUtils.h>
@@ -36,107 +37,79 @@
#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_2::utils {
namespace {
-nn::GeneralResult<nn::SharedPreparedModel> convertPreparedModel(
- const sp<V1_0::IPreparedModel>& preparedModel) {
- return NN_TRY(V1_0::utils::PreparedModel::create(preparedModel));
-}
-
-nn::GeneralResult<nn::SharedPreparedModel> convertPreparedModel(
- const sp<IPreparedModel>& preparedModel) {
- return NN_TRY(utils::PreparedModel::create(preparedModel));
-}
-
nn::GeneralResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>>
convertExecutionGeneralResultsHelper(const hidl_vec<OutputShape>& outputShapes,
const Timing& timing) {
return std::make_pair(NN_TRY(nn::convert(outputShapes)), NN_TRY(nn::convert(timing)));
}
-nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>>
-convertExecutionGeneralResults(const hidl_vec<OutputShape>& outputShapes, const Timing& timing) {
+} // namespace
+
+nn::GeneralResult<nn::SharedPreparedModel> prepareModelCallback(
+ V1_0::ErrorStatus status, const sp<IPreparedModel>& preparedModel) {
+ HANDLE_HAL_STATUS(status) << "model preparation failed with " << toString(status);
+ return NN_TRY(PreparedModel::create(preparedModel, /*executeSynchronously=*/true));
+}
+
+nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> executionCallback(
+ V1_0::ErrorStatus status, const hidl_vec<OutputShape>& outputShapes, const Timing& timing) {
+ if (status == V1_0::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE) {
+ auto canonicalOutputShapes =
+ nn::convert(outputShapes).value_or(std::vector<nn::OutputShape>{});
+ return NN_ERROR(nn::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE, std::move(canonicalOutputShapes))
+ << "execution failed with " << toString(status);
+ }
+ HANDLE_HAL_STATUS(status) << "execution failed with " << toString(status);
return hal::utils::makeExecutionFailure(
convertExecutionGeneralResultsHelper(outputShapes, timing));
}
-} // namespace
-
Return<void> PreparedModelCallback::notify(V1_0::ErrorStatus status,
const sp<V1_0::IPreparedModel>& preparedModel) {
- if (status != V1_0::ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- notifyInternal(NN_ERROR(canonical) << "preparedModel failed with " << toString(status));
- } else if (preparedModel == nullptr) {
- notifyInternal(NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
- << "Returned preparedModel is nullptr");
- } else {
- notifyInternal(convertPreparedModel(preparedModel));
- }
+ mData.put(V1_0::utils::prepareModelCallback(status, preparedModel));
return Void();
}
Return<void> PreparedModelCallback::notify_1_2(V1_0::ErrorStatus status,
const sp<IPreparedModel>& preparedModel) {
- if (status != V1_0::ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- notifyInternal(NN_ERROR(canonical) << "preparedModel failed with " << toString(status));
- } else if (preparedModel == nullptr) {
- notifyInternal(NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
- << "Returned preparedModel is nullptr");
- } else {
- notifyInternal(convertPreparedModel(preparedModel));
- }
+ mData.put(prepareModelCallback(status, preparedModel));
return Void();
}
void PreparedModelCallback::notifyAsDeadObject() {
- notifyInternal(NN_ERROR(nn::ErrorStatus::DEAD_OBJECT) << "Dead object");
+ mData.put(NN_ERROR(nn::ErrorStatus::DEAD_OBJECT) << "Dead object");
}
PreparedModelCallback::Data PreparedModelCallback::get() {
return mData.take();
}
-void PreparedModelCallback::notifyInternal(PreparedModelCallback::Data result) {
- mData.put(std::move(result));
-}
-
// ExecutionCallback methods begin here
Return<void> ExecutionCallback::notify(V1_0::ErrorStatus status) {
- if (status != V1_0::ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- notifyInternal(NN_ERROR(canonical) << "execute failed with " << toString(status));
- } else {
- notifyInternal({});
- }
+ mData.put(V1_0::utils::executionCallback(status));
return Void();
}
Return<void> ExecutionCallback::notify_1_2(V1_0::ErrorStatus status,
const hidl_vec<OutputShape>& outputShapes,
const Timing& timing) {
- if (status != V1_0::ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- notifyInternal(NN_ERROR(canonical) << "execute failed with " << toString(status));
- } else {
- notifyInternal(convertExecutionGeneralResults(outputShapes, timing));
- }
+ mData.put(executionCallback(status, outputShapes, timing));
return Void();
}
void ExecutionCallback::notifyAsDeadObject() {
- notifyInternal(NN_ERROR(nn::ErrorStatus::DEAD_OBJECT) << "Dead object");
+ mData.put(NN_ERROR(nn::ErrorStatus::DEAD_OBJECT) << "Dead object");
}
ExecutionCallback::Data ExecutionCallback::get() {
return mData.take();
}
-void ExecutionCallback::notifyInternal(ExecutionCallback::Data result) {
- mData.put(std::move(result));
-}
-
} // namespace android::hardware::neuralnetworks::V1_2::utils
diff --git a/neuralnetworks/1.2/utils/src/Conversions.cpp b/neuralnetworks/1.2/utils/src/Conversions.cpp
index f11474f..062f6f7 100644
--- a/neuralnetworks/1.2/utils/src/Conversions.cpp
+++ b/neuralnetworks/1.2/utils/src/Conversions.cpp
@@ -26,6 +26,7 @@
#include <nnapi/Types.h>
#include <nnapi/Validation.h>
#include <nnapi/hal/1.0/Conversions.h>
+#include <nnapi/hal/1.1/Conversions.h>
#include <nnapi/hal/CommonUtils.h>
#include <nnapi/hal/HandleError.h>
@@ -43,7 +44,9 @@
return static_cast<std::underlying_type_t<Type>>(value);
}
+using HalDuration = std::chrono::duration<uint64_t, std::micro>;
constexpr auto kVersion = android::nn::Version::ANDROID_Q;
+constexpr uint64_t kNoTiming = std::numeric_limits<uint64_t>::max();
} // namespace
@@ -270,7 +273,18 @@
}
GeneralResult<Timing> unvalidatedConvert(const hal::V1_2::Timing& timing) {
- return Timing{.timeOnDevice = timing.timeOnDevice, .timeInDriver = timing.timeInDriver};
+ constexpr uint64_t kMaxTiming = std::chrono::floor<HalDuration>(Duration::max()).count();
+ constexpr auto convertTiming = [](uint64_t halTiming) -> OptionalDuration {
+ if (halTiming == kNoTiming) {
+ return {};
+ }
+ if (halTiming > kMaxTiming) {
+ return Duration::max();
+ }
+ return HalDuration{halTiming};
+ };
+ return Timing{.timeOnDevice = convertTiming(timing.timeOnDevice),
+ .timeInDriver = convertTiming(timing.timeInDriver)};
}
GeneralResult<Extension> unvalidatedConvert(const hal::V1_2::Extension& extension) {
@@ -547,7 +561,14 @@
}
nn::GeneralResult<Timing> unvalidatedConvert(const nn::Timing& timing) {
- return Timing{.timeOnDevice = timing.timeOnDevice, .timeInDriver = timing.timeInDriver};
+ constexpr auto convertTiming = [](nn::OptionalDuration canonicalTiming) -> uint64_t {
+ if (!canonicalTiming.has_value()) {
+ return kNoTiming;
+ }
+ return std::chrono::ceil<HalDuration>(*canonicalTiming).count();
+ };
+ return Timing{.timeOnDevice = convertTiming(timing.timeOnDevice),
+ .timeInDriver = convertTiming(timing.timeInDriver)};
}
nn::GeneralResult<Extension> unvalidatedConvert(const nn::Extension& extension) {
@@ -602,4 +623,21 @@
return validatedConvert(outputShapes);
}
+nn::GeneralResult<V1_0::DeviceStatus> convert(const nn::DeviceStatus& deviceStatus) {
+ return V1_1::utils::convert(deviceStatus);
+}
+
+nn::GeneralResult<V1_0::Request> convert(const nn::Request& request) {
+ return V1_1::utils::convert(request);
+}
+
+nn::GeneralResult<V1_0::ErrorStatus> convert(const nn::ErrorStatus& status) {
+ return V1_1::utils::convert(status);
+}
+
+nn::GeneralResult<V1_1::ExecutionPreference> convert(
+ const nn::ExecutionPreference& executionPreference) {
+ return V1_1::utils::convert(executionPreference);
+}
+
} // namespace android::hardware::neuralnetworks::V1_2::utils
diff --git a/neuralnetworks/1.2/utils/src/Device.cpp b/neuralnetworks/1.2/utils/src/Device.cpp
index 0061065..1954dfa 100644
--- a/neuralnetworks/1.2/utils/src/Device.cpp
+++ b/neuralnetworks/1.2/utils/src/Device.cpp
@@ -41,112 +41,108 @@
#include <string>
#include <vector>
+// 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_2::utils {
namespace {
-nn::GeneralResult<nn::Capabilities> initCapabilities(V1_2::IDevice* device) {
+nn::GeneralResult<nn::Capabilities> capabilitiesCallback(V1_0::ErrorStatus status,
+ const Capabilities& capabilities) {
+ HANDLE_HAL_STATUS(status) << "getting capabilities failed with " << toString(status);
+ return nn::convert(capabilities);
+}
+
+nn::GeneralResult<std::string> versionStringCallback(V1_0::ErrorStatus status,
+ const hidl_string& versionString) {
+ HANDLE_HAL_STATUS(status) << "getVersionString failed with " << toString(status);
+ return versionString;
+}
+
+nn::GeneralResult<nn::DeviceType> deviceTypeCallback(V1_0::ErrorStatus status,
+ DeviceType deviceType) {
+ HANDLE_HAL_STATUS(status) << "getDeviceType failed with " << toString(status);
+ return nn::convert(deviceType);
+}
+
+nn::GeneralResult<std::vector<nn::Extension>> supportedExtensionsCallback(
+ V1_0::ErrorStatus status, const hidl_vec<Extension>& extensions) {
+ HANDLE_HAL_STATUS(status) << "getExtensions failed with " << toString(status);
+ return nn::convert(extensions);
+}
+
+nn::GeneralResult<std::pair<uint32_t, uint32_t>> numberOfCacheFilesNeededCallback(
+ V1_0::ErrorStatus status, uint32_t numModelCache, uint32_t numDataCache) {
+ HANDLE_HAL_STATUS(status) << "getNumberOfCacheFilesNeeded failed with " << toString(status);
+ if (numModelCache > nn::kMaxNumberOfCacheFiles) {
+ return NN_ERROR() << "getNumberOfCacheFilesNeeded returned numModelCache files greater "
+ "than allowed max ("
+ << numModelCache << " vs " << nn::kMaxNumberOfCacheFiles << ")";
+ }
+ if (numDataCache > nn::kMaxNumberOfCacheFiles) {
+ return NN_ERROR() << "getNumberOfCacheFilesNeeded returned numDataCache files greater "
+ "than allowed max ("
+ << numDataCache << " vs " << nn::kMaxNumberOfCacheFiles << ")";
+ }
+ return std::make_pair(numModelCache, numDataCache);
+}
+
+nn::GeneralResult<nn::Capabilities> getCapabilitiesFrom(V1_2::IDevice* device) {
CHECK(device != nullptr);
- nn::GeneralResult<nn::Capabilities> result = NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
- << "uninitialized";
- const auto cb = [&result](V1_0::ErrorStatus status, const Capabilities& capabilities) {
- if (status != V1_0::ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- result = NN_ERROR(canonical) << "getCapabilities_1_2 failed with " << toString(status);
- } else {
- result = nn::convert(capabilities);
- }
- };
+ auto cb = hal::utils::CallbackValue(capabilitiesCallback);
const auto ret = device->getCapabilities_1_2(cb);
HANDLE_TRANSPORT_FAILURE(ret);
- return result;
+ return cb.take();
}
} // namespace
-nn::GeneralResult<std::string> initVersionString(V1_2::IDevice* device) {
+nn::GeneralResult<std::string> getVersionStringFrom(V1_2::IDevice* device) {
CHECK(device != nullptr);
- nn::GeneralResult<std::string> result = NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
- << "uninitialized";
- const auto cb = [&result](V1_0::ErrorStatus status, const hidl_string& versionString) {
- if (status != V1_0::ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- result = NN_ERROR(canonical) << "getVersionString failed with " << toString(status);
- } else {
- result = versionString;
- }
- };
+ auto cb = hal::utils::CallbackValue(versionStringCallback);
const auto ret = device->getVersionString(cb);
HANDLE_TRANSPORT_FAILURE(ret);
- return result;
+ return cb.take();
}
-nn::GeneralResult<nn::DeviceType> initDeviceType(V1_2::IDevice* device) {
+nn::GeneralResult<nn::DeviceType> getDeviceTypeFrom(V1_2::IDevice* device) {
CHECK(device != nullptr);
- nn::GeneralResult<nn::DeviceType> result = NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
- << "uninitialized";
- const auto cb = [&result](V1_0::ErrorStatus status, DeviceType deviceType) {
- if (status != V1_0::ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- result = NN_ERROR(canonical) << "getDeviceType failed with " << toString(status);
- } else {
- result = nn::convert(deviceType);
- }
- };
+ auto cb = hal::utils::CallbackValue(deviceTypeCallback);
const auto ret = device->getType(cb);
HANDLE_TRANSPORT_FAILURE(ret);
- return result;
+ return cb.take();
}
-nn::GeneralResult<std::vector<nn::Extension>> initExtensions(V1_2::IDevice* device) {
+nn::GeneralResult<std::vector<nn::Extension>> getSupportedExtensionsFrom(V1_2::IDevice* device) {
CHECK(device != nullptr);
- nn::GeneralResult<std::vector<nn::Extension>> result =
- NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) << "uninitialized";
- const auto cb = [&result](V1_0::ErrorStatus status, const hidl_vec<Extension>& extensions) {
- if (status != V1_0::ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- result = NN_ERROR(canonical) << "getExtensions failed with " << toString(status);
- } else {
- result = nn::convert(extensions);
- }
- };
+ auto cb = hal::utils::CallbackValue(supportedExtensionsCallback);
const auto ret = device->getSupportedExtensions(cb);
HANDLE_TRANSPORT_FAILURE(ret);
- return result;
+ return cb.take();
}
-nn::GeneralResult<std::pair<uint32_t, uint32_t>> initNumberOfCacheFilesNeeded(
+nn::GeneralResult<std::pair<uint32_t, uint32_t>> getNumberOfCacheFilesNeededFrom(
V1_2::IDevice* device) {
CHECK(device != nullptr);
- nn::GeneralResult<std::pair<uint32_t, uint32_t>> result =
- NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) << "uninitialized";
- const auto cb = [&result](V1_0::ErrorStatus status, uint32_t numModelCache,
- uint32_t numDataCache) {
- if (status != V1_0::ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- result = NN_ERROR(canonical)
- << "getNumberOfCacheFilesNeeded failed with " << toString(status);
- } else {
- result = std::make_pair(numModelCache, numDataCache);
- }
- };
+ auto cb = hal::utils::CallbackValue(numberOfCacheFilesNeededCallback);
const auto ret = device->getNumberOfCacheFilesNeeded(cb);
HANDLE_TRANSPORT_FAILURE(ret);
- return result;
+ return cb.take();
}
nn::GeneralResult<std::shared_ptr<const Device>> Device::create(std::string name,
@@ -160,11 +156,11 @@
<< "V1_2::utils::Device::create must have non-null device";
}
- auto versionString = NN_TRY(initVersionString(device.get()));
- const auto deviceType = NN_TRY(initDeviceType(device.get()));
- auto extensions = NN_TRY(initExtensions(device.get()));
- auto capabilities = NN_TRY(initCapabilities(device.get()));
- const auto numberOfCacheFilesNeeded = NN_TRY(initNumberOfCacheFilesNeeded(device.get()));
+ auto versionString = NN_TRY(getVersionStringFrom(device.get()));
+ const auto deviceType = NN_TRY(getDeviceTypeFrom(device.get()));
+ auto extensions = NN_TRY(getSupportedExtensionsFrom(device.get()));
+ auto capabilities = NN_TRY(getCapabilitiesFrom(device.get()));
+ const auto numberOfCacheFilesNeeded = NN_TRY(getNumberOfCacheFilesNeededFrom(device.get()));
auto deathHandler = NN_TRY(hal::utils::DeathHandler::create(device));
return std::make_shared<const Device>(
@@ -203,6 +199,10 @@
return kDeviceType;
}
+bool Device::isUpdatable() const {
+ return false;
+}
+
const std::vector<nn::Extension>& Device::getSupportedExtensions() const {
return kExtensions;
}
@@ -229,28 +229,12 @@
const auto hidlModel = NN_TRY(convert(modelInShared));
- nn::GeneralResult<std::vector<bool>> result = NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
- << "uninitialized";
- auto cb = [&result, &model](V1_0::ErrorStatus status,
- const hidl_vec<bool>& supportedOperations) {
- if (status != V1_0::ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- result = NN_ERROR(canonical)
- << "getSupportedOperations_1_2 failed with " << toString(status);
- } else if (supportedOperations.size() != model.main.operations.size()) {
- result = NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
- << "getSupportedOperations_1_2 returned vector of size "
- << supportedOperations.size() << " but expected "
- << model.main.operations.size();
- } else {
- result = supportedOperations;
- }
- };
+ auto cb = hal::utils::CallbackValue(V1_0::utils::supportedOperationsCallback);
const auto ret = kDevice->getSupportedOperations_1_2(hidlModel, cb);
HANDLE_TRANSPORT_FAILURE(ret);
- return result;
+ return cb.take();
}
nn::GeneralResult<nn::SharedPreparedModel> Device::prepareModel(
@@ -263,10 +247,10 @@
NN_TRY(hal::utils::flushDataFromPointerToShared(&model, &maybeModelInShared));
const auto hidlModel = NN_TRY(convert(modelInShared));
- const auto hidlPreference = NN_TRY(V1_1::utils::convert(preference));
+ const auto hidlPreference = NN_TRY(convert(preference));
const auto hidlModelCache = NN_TRY(convert(modelCache));
const auto hidlDataCache = NN_TRY(convert(dataCache));
- const auto hidlToken = token;
+ const auto hidlToken = CacheToken{token};
const auto cb = sp<PreparedModelCallback>::make();
const auto scoped = kDeathHandler.protectCallback(cb.get());
@@ -274,10 +258,7 @@
const auto ret = kDevice->prepareModel_1_2(hidlModel, hidlPreference, hidlModelCache,
hidlDataCache, hidlToken, cb);
const auto status = HANDLE_TRANSPORT_FAILURE(ret);
- if (status != V1_0::ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- return NN_ERROR(canonical) << "prepareModel_1_2 failed with " << toString(status);
- }
+ HANDLE_HAL_STATUS(status) << "model preparation failed with " << toString(status);
return cb->get();
}
@@ -287,17 +268,14 @@
const std::vector<nn::SharedHandle>& dataCache, const nn::CacheToken& token) const {
const auto hidlModelCache = NN_TRY(convert(modelCache));
const auto hidlDataCache = NN_TRY(convert(dataCache));
- const auto hidlToken = token;
+ const auto hidlToken = CacheToken{token};
const auto cb = sp<PreparedModelCallback>::make();
const auto scoped = kDeathHandler.protectCallback(cb.get());
const auto ret = kDevice->prepareModelFromCache(hidlModelCache, hidlDataCache, hidlToken, cb);
const auto status = HANDLE_TRANSPORT_FAILURE(ret);
- if (status != V1_0::ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- return NN_ERROR(canonical) << "prepareModelFromCache failed with " << toString(status);
- }
+ HANDLE_HAL_STATUS(status) << "model preparation from cache failed with " << toString(status);
return cb->get();
}
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 dad9a7e..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>
@@ -37,55 +38,37 @@
#include <utility>
#include <vector>
+// 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_2::utils {
-namespace {
-
-nn::GeneralResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>>
-convertExecutionResultsHelper(const hidl_vec<OutputShape>& outputShapes, const Timing& timing) {
- return std::make_pair(NN_TRY(nn::convert(outputShapes)), NN_TRY(nn::convert(timing)));
-}
-
-nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> convertExecutionResults(
- const hidl_vec<OutputShape>& outputShapes, const Timing& timing) {
- return hal::utils::makeExecutionFailure(convertExecutionResultsHelper(outputShapes, timing));
-}
-
-} // namespace
nn::GeneralResult<std::shared_ptr<const PreparedModel>> PreparedModel::create(
- sp<V1_2::IPreparedModel> preparedModel) {
+ sp<V1_2::IPreparedModel> preparedModel, bool executeSynchronously) {
if (preparedModel == nullptr) {
- return NN_ERROR(nn::ErrorStatus::INVALID_ARGUMENT)
- << "V1_2::utils::PreparedModel::create must have non-null preparedModel";
+ return NN_ERROR() << "V1_2::utils::PreparedModel::create must have non-null preparedModel";
}
auto deathHandler = NN_TRY(hal::utils::DeathHandler::create(preparedModel));
- return std::make_shared<const PreparedModel>(PrivateConstructorTag{}, std::move(preparedModel),
- std::move(deathHandler));
+ return std::make_shared<const PreparedModel>(PrivateConstructorTag{}, executeSynchronously,
+ std::move(preparedModel), std::move(deathHandler));
}
-PreparedModel::PreparedModel(PrivateConstructorTag /*tag*/, sp<V1_2::IPreparedModel> preparedModel,
+PreparedModel::PreparedModel(PrivateConstructorTag /*tag*/, bool executeSynchronously,
+ sp<V1_2::IPreparedModel> preparedModel,
hal::utils::DeathHandler deathHandler)
- : kPreparedModel(std::move(preparedModel)), kDeathHandler(std::move(deathHandler)) {}
+ : kExecuteSynchronously(executeSynchronously),
+ kPreparedModel(std::move(preparedModel)),
+ kDeathHandler(std::move(deathHandler)) {}
nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>>
PreparedModel::executeSynchronously(const V1_0::Request& request, MeasureTiming measure) const {
- nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> result =
- NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) << "uninitialized";
- const auto cb = [&result](V1_0::ErrorStatus status, const hidl_vec<OutputShape>& outputShapes,
- const Timing& timing) {
- if (status != V1_0::ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- result = NN_ERROR(canonical) << "executeSynchronously failed with " << toString(status);
- } else {
- result = convertExecutionResults(outputShapes, timing);
- }
- };
+ auto cb = hal::utils::CallbackValue(executionCallback);
const auto ret = kPreparedModel->executeSynchronously(request, measure, cb);
HANDLE_TRANSPORT_FAILURE(ret);
- return result;
+ return cb.take();
}
nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>>
@@ -95,9 +78,8 @@
const auto ret = kPreparedModel->execute_1_2(request, measure, cb);
const auto status = HANDLE_TRANSPORT_FAILURE(ret);
- if (status != V1_0::ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- return NN_ERROR(canonical) << "execute failed with " << toString(status);
+ if (status != V1_0::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE) {
+ HANDLE_HAL_STATUS(status) << "execution failed with " << toString(status);
}
return cb->get();
@@ -106,51 +88,42 @@
nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> PreparedModel::execute(
const nn::Request& request, nn::MeasureTiming measure,
const nn::OptionalTimePoint& /*deadline*/,
- const nn::OptionalTimeoutDuration& /*loopTimeoutDuration*/) const {
+ const nn::OptionalDuration& /*loopTimeoutDuration*/) const {
// Ensure that request is ready for IPC.
std::optional<nn::Request> maybeRequestInShared;
const nn::Request& requestInShared = NN_TRY(hal::utils::makeExecutionFailure(
hal::utils::flushDataFromPointerToShared(&request, &maybeRequestInShared)));
- const auto hidlRequest =
- NN_TRY(hal::utils::makeExecutionFailure(V1_0::utils::convert(requestInShared)));
+ const auto hidlRequest = NN_TRY(hal::utils::makeExecutionFailure(convert(requestInShared)));
const auto hidlMeasure = NN_TRY(hal::utils::makeExecutionFailure(convert(measure)));
- nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> result =
- NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) << "uninitialized";
- const bool preferSynchronous = true;
+ auto result = kExecuteSynchronously ? executeSynchronously(hidlRequest, hidlMeasure)
+ : executeAsynchronously(hidlRequest, hidlMeasure);
+ auto [outputShapes, timing] = NN_TRY(std::move(result));
- // Execute synchronously if allowed.
- if (preferSynchronous) {
- result = executeSynchronously(hidlRequest, hidlMeasure);
- }
+ NN_TRY(hal::utils::makeExecutionFailure(
+ hal::utils::unflushDataFromSharedToPointer(request, maybeRequestInShared)));
- // Run asymchronous execution if execution has not already completed.
- if (!result.has_value()) {
- result = executeAsynchronously(hidlRequest, hidlMeasure);
- }
-
- // Flush output buffers if suxcessful execution.
- if (result.has_value()) {
- NN_TRY(hal::utils::makeExecutionFailure(
- hal::utils::unflushDataFromSharedToPointer(request, maybeRequestInShared)));
- }
-
- return result;
+ return std::make_pair(std::move(outputShapes), timing);
}
nn::GeneralResult<std::pair<nn::SyncFence, nn::ExecuteFencedInfoCallback>>
-PreparedModel::executeFenced(
- const nn::Request& /*request*/, const std::vector<nn::SyncFence>& /*waitFor*/,
- nn::MeasureTiming /*measure*/, const nn::OptionalTimePoint& /*deadline*/,
- const nn::OptionalTimeoutDuration& /*loopTimeoutDuration*/,
- const nn::OptionalTimeoutDuration& /*timeoutDurationAfterFence*/) const {
+PreparedModel::executeFenced(const nn::Request& /*request*/,
+ const std::vector<nn::SyncFence>& /*waitFor*/,
+ nn::MeasureTiming /*measure*/,
+ const nn::OptionalTimePoint& /*deadline*/,
+ const nn::OptionalDuration& /*loopTimeoutDuration*/,
+ const nn::OptionalDuration& /*timeoutDurationAfterFence*/) const {
return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
<< "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_0::IPreparedModel> resource = kPreparedModel;
+ sp<V1_2::IPreparedModel> resource = kPreparedModel;
return resource;
}
diff --git a/neuralnetworks/1.2/utils/test/DeviceTest.cpp b/neuralnetworks/1.2/utils/test/DeviceTest.cpp
new file mode 100644
index 0000000..9c8adde
--- /dev/null
+++ b/neuralnetworks/1.2/utils/test/DeviceTest.cpp
@@ -0,0 +1,875 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "MockDevice.h"
+#include "MockPreparedModel.h"
+
+#include <android/hardware/neuralnetworks/1.2/IDevice.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <nnapi/IDevice.h>
+#include <nnapi/TypeUtils.h>
+#include <nnapi/Types.h>
+#include <nnapi/hal/1.2/Device.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+
+namespace android::hardware::neuralnetworks::V1_2::utils {
+namespace {
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::InvokeWithoutArgs;
+
+const nn::Model kSimpleModel = {
+ .main = {.operands = {{.type = nn::OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .lifetime = nn::Operand::LifeTime::SUBGRAPH_INPUT},
+ {.type = nn::OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .lifetime = nn::Operand::LifeTime::SUBGRAPH_OUTPUT}},
+ .operations = {{.type = nn::OperationType::RELU, .inputs = {0}, .outputs = {1}}},
+ .inputIndexes = {0},
+ .outputIndexes = {1}}};
+
+const std::string kName = "Google-MockV1";
+const std::string kInvalidName = "";
+const sp<V1_2::IDevice> kInvalidDevice;
+constexpr V1_0::PerformanceInfo kNoPerformanceInfo = {
+ .execTime = std::numeric_limits<float>::max(),
+ .powerUsage = std::numeric_limits<float>::max()};
+
+template <typename... Args>
+auto makeCallbackReturn(Args&&... args) {
+ return [argPack = std::make_tuple(std::forward<Args>(args)...)](const auto& cb) {
+ std::apply(cb, argPack);
+ return Void();
+ };
+}
+
+sp<MockDevice> createMockDevice() {
+ const auto mockDevice = MockDevice::create();
+
+ // Setup default actions for each relevant call.
+ const auto getVersionString_ret = makeCallbackReturn(V1_0::ErrorStatus::NONE, kName);
+ const auto getType_ret = makeCallbackReturn(V1_0::ErrorStatus::NONE, V1_2::DeviceType::OTHER);
+ const auto getSupportedExtensions_ret =
+ makeCallbackReturn(V1_0::ErrorStatus::NONE, hidl_vec<V1_2::Extension>{});
+ const auto getNumberOfCacheFilesNeeded_ret = makeCallbackReturn(
+ V1_0::ErrorStatus::NONE, nn::kMaxNumberOfCacheFiles, nn::kMaxNumberOfCacheFiles);
+ const auto getCapabilities_ret = makeCallbackReturn(
+ V1_0::ErrorStatus::NONE,
+ V1_2::Capabilities{
+ .relaxedFloat32toFloat16PerformanceScalar = kNoPerformanceInfo,
+ .relaxedFloat32toFloat16PerformanceTensor = kNoPerformanceInfo,
+ });
+
+ // Setup default actions for each relevant call.
+ ON_CALL(*mockDevice, getVersionString(_)).WillByDefault(Invoke(getVersionString_ret));
+ ON_CALL(*mockDevice, getType(_)).WillByDefault(Invoke(getType_ret));
+ ON_CALL(*mockDevice, getSupportedExtensions(_))
+ .WillByDefault(Invoke(getSupportedExtensions_ret));
+ ON_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_))
+ .WillByDefault(Invoke(getNumberOfCacheFilesNeeded_ret));
+ ON_CALL(*mockDevice, getCapabilities_1_2(_)).WillByDefault(Invoke(getCapabilities_ret));
+
+ // Ensure that older calls are not used.
+ EXPECT_CALL(*mockDevice, getCapabilities(_)).Times(0);
+ EXPECT_CALL(*mockDevice, getCapabilities_1_1(_)).Times(0);
+ EXPECT_CALL(*mockDevice, getSupportedOperations(_, _)).Times(0);
+ EXPECT_CALL(*mockDevice, getSupportedOperations_1_1(_, _)).Times(0);
+ EXPECT_CALL(*mockDevice, prepareModel(_, _)).Times(0);
+ EXPECT_CALL(*mockDevice, prepareModel_1_1(_, _, _)).Times(0);
+
+ // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the
+ // uninteresting methods calls.
+ EXPECT_CALL(*mockDevice, getVersionString(_)).Times(testing::AnyNumber());
+ EXPECT_CALL(*mockDevice, getType(_)).Times(testing::AnyNumber());
+ EXPECT_CALL(*mockDevice, getSupportedExtensions(_)).Times(testing::AnyNumber());
+ EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(testing::AnyNumber());
+ EXPECT_CALL(*mockDevice, getCapabilities_1_2(_)).Times(testing::AnyNumber());
+
+ return mockDevice;
+}
+
+auto makePreparedModelReturn(V1_0::ErrorStatus launchStatus, V1_0::ErrorStatus returnStatus,
+ const sp<MockPreparedModel>& preparedModel) {
+ return [launchStatus, returnStatus, preparedModel](
+ const V1_2::Model& /*model*/, V1_1::ExecutionPreference /*preference*/,
+ const hardware::hidl_vec<hardware::hidl_handle>& /*modelCache*/,
+ const hardware::hidl_vec<hardware::hidl_handle>& /*dataCache*/,
+ const CacheToken& /*token*/, const sp<V1_2::IPreparedModelCallback>& cb)
+ -> hardware::Return<V1_0::ErrorStatus> {
+ cb->notify_1_2(returnStatus, preparedModel).isOk();
+ return launchStatus;
+ };
+}
+auto makePreparedModelFromCacheReturn(V1_0::ErrorStatus launchStatus,
+ V1_0::ErrorStatus returnStatus,
+ const sp<MockPreparedModel>& preparedModel) {
+ return [launchStatus, returnStatus, preparedModel](
+ const hardware::hidl_vec<hardware::hidl_handle>& /*modelCache*/,
+ const hardware::hidl_vec<hardware::hidl_handle>& /*dataCache*/,
+ const CacheToken& /*token*/, const sp<V1_2::IPreparedModelCallback>& cb)
+ -> hardware::Return<V1_0::ErrorStatus> {
+ cb->notify_1_2(returnStatus, preparedModel).isOk();
+ return launchStatus;
+ };
+}
+
+std::function<hardware::Status()> makeTransportFailure(status_t status) {
+ return [status] { return hardware::Status::fromStatusT(status); };
+}
+
+const auto makeGeneralTransportFailure = makeTransportFailure(NO_MEMORY);
+const auto makeDeadObjectFailure = makeTransportFailure(DEAD_OBJECT);
+
+} // namespace
+
+TEST(DeviceTest, invalidName) {
+ // run test
+ const auto device = MockDevice::create();
+ const auto result = Device::create(kInvalidName, device);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST(DeviceTest, invalidDevice) {
+ // run test
+ const auto result = Device::create(kName, kInvalidDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT);
+}
+
+TEST(DeviceTest, getVersionStringError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto ret = makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE, "");
+ EXPECT_CALL(*mockDevice, getVersionString(_)).Times(1).WillOnce(Invoke(ret));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getVersionStringTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getVersionString(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getVersionStringDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getVersionString(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getTypeError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto ret =
+ makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE, V1_2::DeviceType::OTHER);
+ EXPECT_CALL(*mockDevice, getType(_)).Times(1).WillOnce(Invoke(ret));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getTypeTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getType(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getTypeDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getType(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getSupportedExtensionsError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto ret =
+ makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE, hidl_vec<V1_2::Extension>{});
+ EXPECT_CALL(*mockDevice, getSupportedExtensions(_)).Times(1).WillOnce(Invoke(ret));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getSupportedExtensionsTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getSupportedExtensions(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getSupportedExtensionsDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getSupportedExtensions(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getNumberOfCacheFilesNeededError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto ret = makeCallbackReturn(V1_0::ErrorStatus::GENERAL_FAILURE,
+ nn::kMaxNumberOfCacheFiles, nn::kMaxNumberOfCacheFiles);
+ EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(1).WillOnce(Invoke(ret));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, dataCacheFilesExceedsSpecifiedMax) {
+ // setup test
+ const auto mockDevice = createMockDevice();
+ const auto ret = makeCallbackReturn(V1_0::ErrorStatus::NONE, nn::kMaxNumberOfCacheFiles + 1,
+ nn::kMaxNumberOfCacheFiles);
+ EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(1).WillOnce(Invoke(ret));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, modelCacheFilesExceedsSpecifiedMax) {
+ // setup test
+ const auto mockDevice = createMockDevice();
+ const auto ret = makeCallbackReturn(V1_0::ErrorStatus::NONE, nn::kMaxNumberOfCacheFiles,
+ nn::kMaxNumberOfCacheFiles + 1);
+ EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(1).WillOnce(Invoke(ret));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getNumberOfCacheFilesNeededTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getNumberOfCacheFilesNeededDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getCapabilitiesError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto ret = makeCallbackReturn(
+ V1_0::ErrorStatus::GENERAL_FAILURE,
+ V1_2::Capabilities{
+ .relaxedFloat32toFloat16PerformanceScalar = kNoPerformanceInfo,
+ .relaxedFloat32toFloat16PerformanceTensor = kNoPerformanceInfo,
+ });
+ EXPECT_CALL(*mockDevice, getCapabilities_1_2(_)).Times(1).WillOnce(Invoke(ret));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getCapabilitiesTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getCapabilities_1_2(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getCapabilitiesDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getCapabilities_1_2(_))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, linkToDeathError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto ret = []() -> Return<bool> { return false; };
+ EXPECT_CALL(*mockDevice, linkToDeathRet()).Times(1).WillOnce(InvokeWithoutArgs(ret));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, linkToDeathTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, linkToDeathRet())
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, linkToDeathDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, linkToDeathRet())
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = Device::create(kName, mockDevice);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getName) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+
+ // run test
+ const auto& name = device->getName();
+
+ // verify result
+ EXPECT_EQ(name, kName);
+}
+
+TEST(DeviceTest, getFeatureLevel) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+
+ // run test
+ const auto featureLevel = device->getFeatureLevel();
+
+ // verify result
+ EXPECT_EQ(featureLevel, nn::Version::ANDROID_Q);
+}
+
+TEST(DeviceTest, getCachedData) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, getVersionString(_)).Times(1);
+ EXPECT_CALL(*mockDevice, getType(_)).Times(1);
+ EXPECT_CALL(*mockDevice, getSupportedExtensions(_)).Times(1);
+ EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(1);
+ EXPECT_CALL(*mockDevice, getCapabilities_1_2(_)).Times(1);
+
+ const auto result = Device::create(kName, mockDevice);
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ const auto& device = result.value();
+
+ // run test and verify results
+ EXPECT_EQ(device->getVersionString(), device->getVersionString());
+ EXPECT_EQ(device->getType(), device->getType());
+ EXPECT_EQ(device->getSupportedExtensions(), device->getSupportedExtensions());
+ EXPECT_EQ(device->getNumberOfCacheFilesNeeded(), device->getNumberOfCacheFilesNeeded());
+ EXPECT_EQ(device->getCapabilities(), device->getCapabilities());
+}
+
+TEST(DeviceTest, wait) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto ret = []() -> Return<void> { return {}; };
+ EXPECT_CALL(*mockDevice, ping()).Times(1).WillOnce(InvokeWithoutArgs(ret));
+ const auto device = Device::create(kName, mockDevice).value();
+
+ // run test
+ const auto result = device->wait();
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(DeviceTest, waitTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, ping())
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+ const auto device = Device::create(kName, mockDevice).value();
+
+ // run test
+ const auto result = device->wait();
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, waitDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ EXPECT_CALL(*mockDevice, ping()).Times(1).WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+ const auto device = Device::create(kName, mockDevice).value();
+
+ // run test
+ const auto result = device->wait();
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, getSupportedOperations) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ const auto ret = [](const auto& model, const auto& cb) {
+ cb(V1_0::ErrorStatus::NONE, std::vector<bool>(model.operations.size(), true));
+ return hardware::Void();
+ };
+ EXPECT_CALL(*mockDevice, getSupportedOperations_1_2(_, _)).Times(1).WillOnce(Invoke(ret));
+
+ // run test
+ const auto result = device->getSupportedOperations(kSimpleModel);
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ const auto& supportedOperations = result.value();
+ EXPECT_EQ(supportedOperations.size(), kSimpleModel.main.operations.size());
+ EXPECT_THAT(supportedOperations, Each(testing::IsTrue()));
+}
+
+TEST(DeviceTest, getSupportedOperationsError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ const auto ret = [](const auto& /*model*/, const auto& cb) {
+ cb(V1_0::ErrorStatus::GENERAL_FAILURE, {});
+ return hardware::Void();
+ };
+ EXPECT_CALL(*mockDevice, getSupportedOperations_1_2(_, _)).Times(1).WillOnce(Invoke(ret));
+
+ // run test
+ const auto result = device->getSupportedOperations(kSimpleModel);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getSupportedOperationsTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, getSupportedOperations_1_2(_, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = device->getSupportedOperations(kSimpleModel);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, getSupportedOperationsDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, getSupportedOperations_1_2(_, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = device->getSupportedOperations(kSimpleModel);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, prepareModel) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ const auto mockPreparedModel = MockPreparedModel::create();
+ EXPECT_CALL(*mockDevice, prepareModel_1_2(_, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::NONE,
+ V1_0::ErrorStatus::NONE, mockPreparedModel)));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ EXPECT_NE(result.value(), nullptr);
+}
+
+TEST(DeviceTest, prepareModelLaunchError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModel_1_2(_, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::GENERAL_FAILURE,
+ V1_0::ErrorStatus::GENERAL_FAILURE, nullptr)));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelReturnError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModel_1_2(_, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::NONE,
+ V1_0::ErrorStatus::GENERAL_FAILURE, nullptr)));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelNullptrError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModel_1_2(_, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makePreparedModelReturn(V1_0::ErrorStatus::NONE,
+ V1_0::ErrorStatus::NONE, nullptr)));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModel_1_2(_, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModel_1_2(_, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, prepareModelAsyncCrash) {
+ // setup test
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ const auto ret = [&mockDevice]() -> hardware::Return<V1_0::ErrorStatus> {
+ mockDevice->simulateCrash();
+ return V1_0::ErrorStatus::NONE;
+ };
+ EXPECT_CALL(*mockDevice, prepareModel_1_2(_, _, _, _, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(ret));
+
+ // run test
+ const auto result = device->prepareModel(kSimpleModel, nn::ExecutionPreference::DEFAULT,
+ nn::Priority::DEFAULT, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, prepareModelFromCache) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ const auto mockPreparedModel = MockPreparedModel::create();
+ EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makePreparedModelFromCacheReturn(
+ V1_0::ErrorStatus::NONE, V1_0::ErrorStatus::NONE, mockPreparedModel)));
+
+ // run test
+ const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+ // verify result
+ ASSERT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+ EXPECT_NE(result.value(), nullptr);
+}
+
+TEST(DeviceTest, prepareModelFromCacheError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makePreparedModelFromCacheReturn(V1_0::ErrorStatus::GENERAL_FAILURE,
+ V1_0::ErrorStatus::GENERAL_FAILURE,
+ nullptr)));
+
+ // run test
+ const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelFromCacheNullptrError) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makePreparedModelFromCacheReturn(V1_0::ErrorStatus::NONE,
+ V1_0::ErrorStatus::NONE, nullptr)));
+
+ // run test
+ const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelFromCacheTransportFailure) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(DeviceTest, prepareModelFromCacheDeadObject) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, prepareModelFromCacheAsyncCrash) {
+ // setup test
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+ const auto ret = [&mockDevice]() -> hardware::Return<V1_0::ErrorStatus> {
+ mockDevice->simulateCrash();
+ return V1_0::ErrorStatus::NONE;
+ };
+ EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(ret));
+
+ // run test
+ const auto result = device->prepareModelFromCache({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(DeviceTest, allocateNotSupported) {
+ // setup call
+ const auto mockDevice = createMockDevice();
+ const auto device = Device::create(kName, mockDevice).value();
+
+ // run test
+ const auto result = device->allocate({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+} // namespace android::hardware::neuralnetworks::V1_2::utils
diff --git a/neuralnetworks/1.2/utils/test/MockDevice.h b/neuralnetworks/1.2/utils/test/MockDevice.h
new file mode 100644
index 0000000..b459943
--- /dev/null
+++ b/neuralnetworks/1.2/utils/test/MockDevice.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_DEVICE
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_DEVICE
+
+#include <android/hardware/neuralnetworks/1.2/IDevice.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <hidl/Status.h>
+
+namespace android::hardware::neuralnetworks::V1_2::utils {
+
+using CacheToken =
+ hidl_array<uint8_t, static_cast<uint32_t>(V1_2::Constant::BYTE_SIZE_OF_CACHE_TOKEN)>;
+
+class MockDevice final : public IDevice {
+ public:
+ static sp<MockDevice> create();
+
+ // IBase methods below.
+ MOCK_METHOD(Return<void>, ping, (), (override));
+ MOCK_METHOD(Return<bool>, linkToDeathRet, ());
+ Return<bool> linkToDeath(const sp<hidl_death_recipient>& recipient, uint64_t /*cookie*/);
+
+ // V1_0 methods below.
+ MOCK_METHOD(Return<void>, getCapabilities, (getCapabilities_cb cb), (override));
+ MOCK_METHOD(Return<void>, getSupportedOperations,
+ (const V1_0::Model& model, getSupportedOperations_cb cb), (override));
+ MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModel,
+ (const V1_0::Model& model, const sp<V1_0::IPreparedModelCallback>& callback),
+ (override));
+ MOCK_METHOD(Return<V1_0::DeviceStatus>, getStatus, (), (override));
+
+ // V1_1 methods below.
+ MOCK_METHOD(Return<void>, getCapabilities_1_1, (getCapabilities_1_1_cb cb), (override));
+ MOCK_METHOD(Return<void>, getSupportedOperations_1_1,
+ (const V1_1::Model& model, getSupportedOperations_1_1_cb cb), (override));
+ MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModel_1_1,
+ (const V1_1::Model& model, V1_1::ExecutionPreference preference,
+ const sp<V1_0::IPreparedModelCallback>& callback),
+ (override));
+
+ // V1_2 methods below.
+ MOCK_METHOD(Return<void>, getVersionString, (getVersionString_cb cb), (override));
+ MOCK_METHOD(Return<void>, getType, (getType_cb cb), (override));
+ MOCK_METHOD(Return<void>, getCapabilities_1_2, (getCapabilities_1_2_cb cb), (override));
+ MOCK_METHOD(Return<void>, getSupportedExtensions, (getSupportedExtensions_cb cb), (override));
+ MOCK_METHOD(Return<void>, getSupportedOperations_1_2,
+ (const V1_2::Model& model, getSupportedOperations_1_2_cb cb), (override));
+ MOCK_METHOD(Return<void>, getNumberOfCacheFilesNeeded, (getNumberOfCacheFilesNeeded_cb cb),
+ (override));
+ MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModel_1_2,
+ (const V1_2::Model& model, V1_1::ExecutionPreference preference,
+ const hidl_vec<hidl_handle>& modelCache, const hidl_vec<hidl_handle>& dataCache,
+ const CacheToken& token, const sp<V1_2::IPreparedModelCallback>& callback),
+ (override));
+ MOCK_METHOD(Return<V1_0::ErrorStatus>, prepareModelFromCache,
+ (const hidl_vec<hidl_handle>& modelCache, const hidl_vec<hidl_handle>& dataCache,
+ const CacheToken& token, const sp<V1_2::IPreparedModelCallback>& callback),
+ (override));
+
+ // Helper methods.
+ void simulateCrash();
+
+ private:
+ sp<hidl_death_recipient> mDeathRecipient;
+};
+
+inline sp<MockDevice> MockDevice::create() {
+ auto mockDevice = sp<MockDevice>::make();
+
+ // Setup default actions for each relevant call.
+ const auto ret = []() -> Return<bool> { return true; };
+
+ // Setup default actions for each relevant call.
+ ON_CALL(*mockDevice, linkToDeathRet()).WillByDefault(testing::Invoke(ret));
+
+ // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the
+ // uninteresting methods calls.
+ EXPECT_CALL(*mockDevice, linkToDeathRet()).Times(testing::AnyNumber());
+
+ return mockDevice;
+}
+
+inline Return<bool> MockDevice::linkToDeath(const sp<hidl_death_recipient>& recipient,
+ uint64_t /*cookie*/) {
+ mDeathRecipient = recipient;
+ return linkToDeathRet();
+}
+
+inline void MockDevice::simulateCrash() {
+ ASSERT_NE(nullptr, mDeathRecipient.get());
+
+ // Currently, the utils::Device will not use the `cookie` or `who` arguments, so we pass in 0
+ // and nullptr for these arguments instead. Normally, they are used by the hidl_death_recipient
+ // to determine which object is dead. However, the utils::Device code only pairs a single death
+ // recipient with a single HIDL interface object, so these arguments are redundant.
+ mDeathRecipient->serviceDied(0, nullptr);
+}
+
+} // namespace android::hardware::neuralnetworks::V1_2::utils
+
+#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_DEVICE
diff --git a/neuralnetworks/1.2/utils/test/MockPreparedModel.h b/neuralnetworks/1.2/utils/test/MockPreparedModel.h
new file mode 100644
index 0000000..f5fd1f3
--- /dev/null
+++ b/neuralnetworks/1.2/utils/test/MockPreparedModel.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_PREPARED_MODEL
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_PREPARED_MODEL
+
+#include <android/hardware/neuralnetworks/1.2/IPreparedModel.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <hidl/HidlSupport.h>
+#include <hidl/Status.h>
+
+namespace android::hardware::neuralnetworks::V1_2::utils {
+
+class MockPreparedModel final : public IPreparedModel {
+ public:
+ static sp<MockPreparedModel> create();
+
+ // IBase methods below.
+ MOCK_METHOD(Return<void>, ping, (), (override));
+ MOCK_METHOD(Return<bool>, linkToDeathRet, ());
+ Return<bool> linkToDeath(const sp<hidl_death_recipient>& recipient,
+ uint64_t /*cookie*/) override;
+
+ // V1_0 methods below.
+ MOCK_METHOD(Return<V1_0::ErrorStatus>, execute,
+ (const V1_0::Request& request, const sp<V1_0::IExecutionCallback>& callback),
+ (override));
+
+ // V1_2 methods below.
+ MOCK_METHOD(Return<V1_0::ErrorStatus>, execute_1_2,
+ (const V1_0::Request& request, V1_2::MeasureTiming measure,
+ const sp<V1_2::IExecutionCallback>& callback),
+ (override));
+ MOCK_METHOD(Return<void>, executeSynchronously,
+ (const V1_0::Request& request, V1_2::MeasureTiming measure,
+ executeSynchronously_cb cb),
+ (override));
+ MOCK_METHOD(Return<void>, configureExecutionBurst,
+ (const sp<V1_2::IBurstCallback>& callback,
+ const MQDescriptorSync<V1_2::FmqRequestDatum>& requestChannel,
+ const MQDescriptorSync<V1_2::FmqResultDatum>& resultChannel,
+ configureExecutionBurst_cb cb),
+ (override));
+
+ // Helper methods.
+ void simulateCrash();
+
+ private:
+ sp<hidl_death_recipient> mDeathRecipient;
+};
+
+inline sp<MockPreparedModel> MockPreparedModel::create() {
+ auto mockPreparedModel = sp<MockPreparedModel>::make();
+
+ // Setup default actions for each relevant call.
+ const auto ret = []() -> Return<bool> { return true; };
+
+ // Setup default actions for each relevant call.
+ ON_CALL(*mockPreparedModel, linkToDeathRet()).WillByDefault(testing::Invoke(ret));
+
+ // These EXPECT_CALL(...).Times(testing::AnyNumber()) calls are to suppress warnings on the
+ // uninteresting methods calls.
+ EXPECT_CALL(*mockPreparedModel, linkToDeathRet()).Times(testing::AnyNumber());
+
+ return mockPreparedModel;
+}
+
+inline Return<bool> MockPreparedModel::linkToDeath(const sp<hidl_death_recipient>& recipient,
+ uint64_t /*cookie*/) {
+ mDeathRecipient = recipient;
+ return linkToDeathRet();
+}
+
+inline void MockPreparedModel::simulateCrash() {
+ ASSERT_NE(nullptr, mDeathRecipient.get());
+
+ // Currently, the utils::PreparedModel will not use the `cookie` or `who` arguments, so we pass
+ // in 0 and nullptr for these arguments instead. Normally, they are used by the
+ // hidl_death_recipient to determine which object is dead. However, the utils::PreparedModel
+ // code only pairs a single death recipient with a single HIDL interface object, so these
+ // arguments are redundant.
+ mDeathRecipient->serviceDied(0, nullptr);
+}
+
+} // namespace android::hardware::neuralnetworks::V1_2::utils
+
+#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_TEST_MOCK_PREPARED_MODEL
diff --git a/neuralnetworks/1.2/utils/test/PreparedModelTest.cpp b/neuralnetworks/1.2/utils/test/PreparedModelTest.cpp
new file mode 100644
index 0000000..5062ac9
--- /dev/null
+++ b/neuralnetworks/1.2/utils/test/PreparedModelTest.cpp
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "MockPreparedModel.h"
+
+#include <android/hardware/neuralnetworks/1.2/IExecutionCallback.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <nnapi/IPreparedModel.h>
+#include <nnapi/TypeUtils.h>
+#include <nnapi/Types.h>
+#include <nnapi/hal/1.2/PreparedModel.h>
+
+#include <functional>
+#include <memory>
+
+namespace android::hardware::neuralnetworks::V1_2::utils {
+namespace {
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::InvokeWithoutArgs;
+
+const sp<V1_2::IPreparedModel> kInvalidPreparedModel;
+constexpr auto kNoTiming = V1_2::Timing{.timeOnDevice = std::numeric_limits<uint64_t>::max(),
+ .timeInDriver = std::numeric_limits<uint64_t>::max()};
+
+sp<MockPreparedModel> createMockPreparedModel() {
+ const auto mockPreparedModel = MockPreparedModel::create();
+
+ // Ensure that older calls are not used.
+ EXPECT_CALL(*mockPreparedModel, execute(_, _)).Times(0);
+
+ return mockPreparedModel;
+}
+
+auto makeExecuteSynchronously(V1_0::ErrorStatus status,
+ const std::vector<V1_2::OutputShape>& outputShapes,
+ const V1_2::Timing& timing) {
+ return [status, outputShapes, timing](const V1_0::Request& /*request*/,
+ V1_2::MeasureTiming /*measureTiming*/,
+ const V1_2::IPreparedModel::executeSynchronously_cb& cb) {
+ cb(status, outputShapes, timing);
+ return hardware::Void();
+ };
+}
+auto makeExecuteAsynchronously(V1_0::ErrorStatus launchStatus, V1_0::ErrorStatus returnStatus,
+ const std::vector<V1_2::OutputShape>& outputShapes,
+ const V1_2::Timing& timing) {
+ return [launchStatus, returnStatus, outputShapes, timing](
+ const V1_0::Request& /*request*/, V1_2::MeasureTiming /*measureTiming*/,
+ const sp<V1_2::IExecutionCallback>& cb) -> Return<V1_0::ErrorStatus> {
+ cb->notify_1_2(returnStatus, outputShapes, timing);
+ return launchStatus;
+ };
+}
+
+std::function<hardware::Status()> makeTransportFailure(status_t status) {
+ return [status] { return hardware::Status::fromStatusT(status); };
+}
+
+const auto makeGeneralTransportFailure = makeTransportFailure(NO_MEMORY);
+const auto makeDeadObjectFailure = makeTransportFailure(DEAD_OBJECT);
+
+} // namespace
+
+TEST(PreparedModelTest, invalidPreparedModel) {
+ // run test
+ const auto result = PreparedModel::create(kInvalidPreparedModel, /*executeSynchronously=*/true);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, linkToDeathError) {
+ // setup call
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto ret = []() -> Return<bool> { return false; };
+ EXPECT_CALL(*mockPreparedModel, linkToDeathRet()).Times(1).WillOnce(InvokeWithoutArgs(ret));
+
+ // run test
+ const auto result = PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, linkToDeathTransportFailure) {
+ // setup call
+ const auto mockPreparedModel = createMockPreparedModel();
+ EXPECT_CALL(*mockPreparedModel, linkToDeathRet())
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, linkToDeathDeadObject) {
+ // setup call
+ const auto mockPreparedModel = createMockPreparedModel();
+ EXPECT_CALL(*mockPreparedModel, linkToDeathRet())
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true);
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(PreparedModelTest, executeSync) {
+ // setup call
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+ EXPECT_CALL(*mockPreparedModel, executeSynchronously(_, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makeExecuteSynchronously(V1_0::ErrorStatus::NONE, {}, kNoTiming)));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ EXPECT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(PreparedModelTest, executeSyncError) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+ EXPECT_CALL(*mockPreparedModel, executeSynchronously(_, _, _))
+ .Times(1)
+ .WillOnce(Invoke(
+ makeExecuteSynchronously(V1_0::ErrorStatus::GENERAL_FAILURE, {}, kNoTiming)));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeSyncTransportFailure) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+ EXPECT_CALL(*mockPreparedModel, executeSynchronously(_, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeSyncDeadObject) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+ EXPECT_CALL(*mockPreparedModel, executeSynchronously(_, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(PreparedModelTest, executeAsync) {
+ // setup call
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value();
+ EXPECT_CALL(*mockPreparedModel, execute_1_2(_, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makeExecuteAsynchronously(V1_0::ErrorStatus::NONE,
+ V1_0::ErrorStatus::NONE, {}, kNoTiming)));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ EXPECT_TRUE(result.has_value())
+ << "Failed with " << result.error().code << ": " << result.error().message;
+}
+
+TEST(PreparedModelTest, executeAsyncLaunchError) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value();
+ EXPECT_CALL(*mockPreparedModel, execute_1_2(_, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makeExecuteAsynchronously(V1_0::ErrorStatus::GENERAL_FAILURE,
+ V1_0::ErrorStatus::GENERAL_FAILURE, {},
+ kNoTiming)));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeAsyncReturnError) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value();
+ EXPECT_CALL(*mockPreparedModel, execute_1_2(_, _, _))
+ .Times(1)
+ .WillOnce(Invoke(makeExecuteAsynchronously(
+ V1_0::ErrorStatus::NONE, V1_0::ErrorStatus::GENERAL_FAILURE, {}, kNoTiming)));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeAsyncTransportFailure) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value();
+ EXPECT_CALL(*mockPreparedModel, execute_1_2(_, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+TEST(PreparedModelTest, executeAsyncDeadObject) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value();
+ EXPECT_CALL(*mockPreparedModel, execute_1_2(_, _, _))
+ .Times(1)
+ .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(PreparedModelTest, executeAsyncCrash) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/false).value();
+ const auto ret = [&mockPreparedModel]() -> hardware::Return<V1_0::ErrorStatus> {
+ mockPreparedModel->simulateCrash();
+ return V1_0::ErrorStatus::NONE;
+ };
+ EXPECT_CALL(*mockPreparedModel, execute_1_2(_, _, _)).Times(1).WillOnce(InvokeWithoutArgs(ret));
+
+ // run test
+ const auto result = preparedModel->execute({}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
+}
+
+TEST(PreparedModelTest, executeFencedNotSupported) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+
+ // run test
+ const auto result = preparedModel->executeFenced({}, {}, {}, {}, {}, {});
+
+ // verify result
+ ASSERT_FALSE(result.has_value());
+ EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
+}
+
+// TODO: test burst execution if/when it is added to nn::IPreparedModel.
+
+TEST(PreparedModelTest, getUnderlyingResource) {
+ // setup test
+ const auto mockPreparedModel = createMockPreparedModel();
+ const auto preparedModel =
+ PreparedModel::create(mockPreparedModel, /*executeSynchronously=*/true).value();
+
+ // run test
+ const auto resource = preparedModel->getUnderlyingResource();
+
+ // verify resource
+ const sp<V1_2::IPreparedModel>* maybeMock = std::any_cast<sp<V1_2::IPreparedModel>>(&resource);
+ ASSERT_NE(maybeMock, nullptr);
+ EXPECT_EQ(maybeMock->get(), mockPreparedModel.get());
+}
+
+} // namespace android::hardware::neuralnetworks::V1_2::utils
diff --git a/neuralnetworks/1.3/utils/Android.bp b/neuralnetworks/1.3/utils/Android.bp
index d5d897d..41d9521 100644
--- a/neuralnetworks/1.3/utils/Android.bp
+++ b/neuralnetworks/1.3/utils/Android.bp
@@ -38,3 +38,35 @@
"neuralnetworks_utils_hal_common",
],
}
+
+cc_test {
+ name: "neuralnetworks_utils_hal_1_3_test",
+ srcs: ["test/*.cpp"],
+ static_libs: [
+ "android.hardware.neuralnetworks@1.0",
+ "android.hardware.neuralnetworks@1.1",
+ "android.hardware.neuralnetworks@1.2",
+ "android.hardware.neuralnetworks@1.3",
+ "libgmock",
+ "libneuralnetworks_common",
+ "neuralnetworks_types",
+ "neuralnetworks_utils_hal_common",
+ "neuralnetworks_utils_hal_1_0",
+ "neuralnetworks_utils_hal_1_1",
+ "neuralnetworks_utils_hal_1_2",
+ "neuralnetworks_utils_hal_1_3",
+ ],
+ shared_libs: [
+ "android.hidl.allocator@1.0",
+ "android.hidl.memory@1.0",
+ "libbase",
+ "libcutils",
+ "libfmq",
+ "libhidlbase",
+ "libhidlmemory",
+ "liblog",
+ "libnativewindow",
+ "libutils",
+ ],
+ test_suites: ["general-tests"],
+}
diff --git a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Buffer.h b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Buffer.h
index 637179d..fda79c8 100644
--- a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Buffer.h
+++ b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Buffer.h
@@ -24,8 +24,12 @@
#include <nnapi/Types.h>
#include <memory>
+// See hardware/interfaces/neuralnetworks/utils/README.md for more information on HIDL interface
+// lifetimes across processes.
+
namespace android::hardware::neuralnetworks::V1_3::utils {
+// Class that adapts V1_3::IBuffer to nn::IBuffer.
class Buffer final : public nn::IBuffer {
struct PrivateConstructorTag {};
diff --git a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Callbacks.h b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Callbacks.h
index d46b111..643172e 100644
--- a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Callbacks.h
+++ b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Callbacks.h
@@ -34,8 +34,31 @@
#include <nnapi/hal/ProtectCallback.h>
#include <nnapi/hal/TransferValue.h>
+// 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_3::utils {
+// Converts the results of IDevice::getSupportedOperations* to the NN canonical format. On success,
+// this function returns with the supported operations as indicated by a driver. On failure, this
+// function returns with the appropriate nn::GeneralError.
+nn::GeneralResult<std::vector<bool>> supportedOperationsCallback(
+ ErrorStatus status, const hidl_vec<bool>& supportedOperations);
+
+// Converts the results of IDevice::prepareModel* to the NN canonical format. On success, this
+// function returns with a non-null nn::SharedPreparedModel with a feature level of
+// nn::Version::ANDROID_R. On failure, this function returns with the appropriate nn::GeneralError.
+nn::GeneralResult<nn::SharedPreparedModel> prepareModelCallback(
+ ErrorStatus status, const sp<IPreparedModel>& preparedModel);
+
+// Converts the results of IDevice::execute* to the NN canonical format. On success, this function
+// returns with the output shapes and the timing information. On failure, this function returns with
+// the appropriate nn::ExecutionError.
+nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> executionCallback(
+ ErrorStatus status, const hidl_vec<V1_2::OutputShape>& outputShapes,
+ const V1_2::Timing& timing);
+
+// A HIDL callback class to receive the results of IDevice::prepareModel* asynchronously.
class PreparedModelCallback final : public IPreparedModelCallback,
public hal::utils::IProtectedCallback {
public:
@@ -52,11 +75,10 @@
Data get();
private:
- void notifyInternal(Data result);
-
hal::utils::TransferValue<Data> mData;
};
+// A HIDL callback class to receive the results of IDevice::execute_1_3 asynchronously.
class ExecutionCallback final : public IExecutionCallback, public hal::utils::IProtectedCallback {
public:
using Data = nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>>;
@@ -73,8 +95,6 @@
Data get();
private:
- void notifyInternal(Data result);
-
hal::utils::TransferValue<Data> mData;
};
diff --git a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Conversions.h b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Conversions.h
index 9653a05..74a6534 100644
--- a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Conversions.h
+++ b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Conversions.h
@@ -44,7 +44,7 @@
const hal::V1_3::Request::MemoryPool& memoryPool);
GeneralResult<OptionalTimePoint> unvalidatedConvert(
const hal::V1_3::OptionalTimePoint& optionalTimePoint);
-GeneralResult<OptionalTimeoutDuration> unvalidatedConvert(
+GeneralResult<OptionalDuration> unvalidatedConvert(
const hal::V1_3::OptionalTimeoutDuration& optionalTimeoutDuration);
GeneralResult<ErrorStatus> unvalidatedConvert(const hal::V1_3::ErrorStatus& errorStatus);
@@ -54,7 +54,7 @@
GeneralResult<BufferDesc> convert(const hal::V1_3::BufferDesc& bufferDesc);
GeneralResult<Request> convert(const hal::V1_3::Request& request);
GeneralResult<OptionalTimePoint> convert(const hal::V1_3::OptionalTimePoint& optionalTimePoint);
-GeneralResult<OptionalTimeoutDuration> convert(
+GeneralResult<OptionalDuration> convert(
const hal::V1_3::OptionalTimeoutDuration& optionalTimeoutDuration);
GeneralResult<ErrorStatus> convert(const hal::V1_3::ErrorStatus& errorStatus);
@@ -86,7 +86,7 @@
nn::GeneralResult<OptionalTimePoint> unvalidatedConvert(
const nn::OptionalTimePoint& optionalTimePoint);
nn::GeneralResult<OptionalTimeoutDuration> unvalidatedConvert(
- const nn::OptionalTimeoutDuration& optionalTimeoutDuration);
+ const nn::OptionalDuration& optionalTimeoutDuration);
nn::GeneralResult<ErrorStatus> unvalidatedConvert(const nn::ErrorStatus& errorStatus);
nn::GeneralResult<Priority> convert(const nn::Priority& priority);
@@ -96,13 +96,24 @@
nn::GeneralResult<Request> convert(const nn::Request& request);
nn::GeneralResult<OptionalTimePoint> convert(const nn::OptionalTimePoint& optionalTimePoint);
nn::GeneralResult<OptionalTimeoutDuration> convert(
- const nn::OptionalTimeoutDuration& optionalTimeoutDuration);
+ const nn::OptionalDuration& optionalTimeoutDuration);
nn::GeneralResult<ErrorStatus> convert(const nn::ErrorStatus& errorStatus);
nn::GeneralResult<hidl_handle> convert(const nn::SharedHandle& handle);
nn::GeneralResult<hidl_memory> convert(const nn::Memory& memory);
nn::GeneralResult<hidl_vec<BufferRole>> convert(const std::vector<nn::BufferRole>& bufferRoles);
+nn::GeneralResult<V1_0::DeviceStatus> convert(const nn::DeviceStatus& deviceStatus);
+nn::GeneralResult<V1_1::ExecutionPreference> convert(
+ const nn::ExecutionPreference& executionPreference);
+nn::GeneralResult<hidl_vec<V1_2::Extension>> convert(const std::vector<nn::Extension>& extensions);
+nn::GeneralResult<hidl_vec<hidl_handle>> convert(const std::vector<nn::SharedHandle>& handles);
+nn::GeneralResult<hidl_vec<V1_2::OutputShape>> convert(
+ const std::vector<nn::OutputShape>& outputShapes);
+nn::GeneralResult<V1_2::DeviceType> convert(const nn::DeviceType& deviceType);
+nn::GeneralResult<V1_2::MeasureTiming> convert(const nn::MeasureTiming& measureTiming);
+nn::GeneralResult<V1_2::Timing> convert(const nn::Timing& timing);
+
} // namespace android::hardware::neuralnetworks::V1_3::utils
#endif // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_CONVERSIONS_H
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 0f5234b..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
@@ -32,8 +32,12 @@
#include <string>
#include <vector>
+// 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_3::utils {
+// Class that adapts V1_3::IDevice to nn::IDevice.
class Device final : public nn::IDevice {
struct PrivateConstructorTag {};
@@ -50,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 e0d69dd..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
@@ -29,28 +29,35 @@
#include <utility>
#include <vector>
+// 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_3::utils {
-class PreparedModel final : public nn::IPreparedModel {
+// Class that adapts V1_3::IPreparedModel to nn::IPreparedModel.
+class PreparedModel final : public nn::IPreparedModel,
+ public std::enable_shared_from_this<PreparedModel> {
struct PrivateConstructorTag {};
public:
static nn::GeneralResult<std::shared_ptr<const PreparedModel>> create(
- sp<V1_3::IPreparedModel> preparedModel);
+ sp<V1_3::IPreparedModel> preparedModel, bool executeSynchronously);
- PreparedModel(PrivateConstructorTag tag, sp<V1_3::IPreparedModel> preparedModel,
- hal::utils::DeathHandler deathHandler);
+ PreparedModel(PrivateConstructorTag tag, bool executeSynchronously,
+ sp<V1_3::IPreparedModel> preparedModel, hal::utils::DeathHandler deathHandler);
nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> execute(
const nn::Request& request, nn::MeasureTiming measure,
const nn::OptionalTimePoint& deadline,
- const nn::OptionalTimeoutDuration& loopTimeoutDuration) const override;
+ const nn::OptionalDuration& loopTimeoutDuration) const override;
nn::GeneralResult<std::pair<nn::SyncFence, nn::ExecuteFencedInfoCallback>> executeFenced(
const nn::Request& request, const std::vector<nn::SyncFence>& waitFor,
nn::MeasureTiming measure, const nn::OptionalTimePoint& deadline,
- const nn::OptionalTimeoutDuration& loopTimeoutDuration,
- const nn::OptionalTimeoutDuration& timeoutDurationAfterFence) const override;
+ const nn::OptionalDuration& loopTimeoutDuration,
+ const nn::OptionalDuration& timeoutDurationAfterFence) const override;
+
+ nn::GeneralResult<nn::SharedBurst> configureExecutionBurst() const override;
std::any getUnderlyingResource() const override;
@@ -62,6 +69,7 @@
const Request& request, V1_2::MeasureTiming measure, const OptionalTimePoint& deadline,
const OptionalTimeoutDuration& loopTimeoutDuration) const;
+ const bool kExecuteSynchronously;
const sp<V1_3::IPreparedModel> kPreparedModel;
const hal::utils::DeathHandler kDeathHandler;
};
diff --git a/neuralnetworks/1.3/utils/src/Buffer.cpp b/neuralnetworks/1.3/utils/src/Buffer.cpp
index ffdeccd..614033e 100644
--- a/neuralnetworks/1.3/utils/src/Buffer.cpp
+++ b/neuralnetworks/1.3/utils/src/Buffer.cpp
@@ -33,17 +33,18 @@
#include <memory>
#include <utility>
+// See hardware/interfaces/neuralnetworks/utils/README.md for more information on HIDL interface
+// lifetimes across processes.
+
namespace android::hardware::neuralnetworks::V1_3::utils {
nn::GeneralResult<std::shared_ptr<const Buffer>> Buffer::create(
sp<V1_3::IBuffer> buffer, nn::Request::MemoryDomainToken token) {
if (buffer == nullptr) {
- return NN_ERROR(nn::ErrorStatus::INVALID_ARGUMENT)
- << "V1_3::utils::Buffer::create must have non-null buffer";
+ return NN_ERROR() << "V1_3::utils::Buffer::create must have non-null buffer";
}
if (token == static_cast<nn::Request::MemoryDomainToken>(0)) {
- return NN_ERROR(nn::ErrorStatus::INVALID_ARGUMENT)
- << "V1_3::utils::Buffer::create must have non-zero token";
+ return NN_ERROR() << "V1_3::utils::Buffer::create must have non-zero token";
}
return std::make_shared<const Buffer>(PrivateConstructorTag{}, std::move(buffer), token);
@@ -65,10 +66,7 @@
const auto ret = kBuffer->copyTo(hidlDst);
const auto status = HANDLE_TRANSPORT_FAILURE(ret);
- if (status != ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- return NN_ERROR(canonical) << "IBuffer::copyTo failed with " << toString(status);
- }
+ HANDLE_HAL_STATUS(status) << "IBuffer::copyTo failed with " << toString(status);
return {};
}
@@ -80,10 +78,7 @@
const auto ret = kBuffer->copyFrom(hidlSrc, hidlDimensions);
const auto status = HANDLE_TRANSPORT_FAILURE(ret);
- if (status != ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- return NN_ERROR(canonical) << "IBuffer::copyFrom failed with " << toString(status);
- }
+ HANDLE_HAL_STATUS(status) << "IBuffer::copyFrom failed with " << toString(status);
return {};
}
diff --git a/neuralnetworks/1.3/utils/src/Callbacks.cpp b/neuralnetworks/1.3/utils/src/Callbacks.cpp
index e3c6074..af76e6a 100644
--- a/neuralnetworks/1.3/utils/src/Callbacks.cpp
+++ b/neuralnetworks/1.3/utils/src/Callbacks.cpp
@@ -30,6 +30,7 @@
#include <nnapi/Types.h>
#include <nnapi/hal/1.0/Conversions.h>
#include <nnapi/hal/1.0/PreparedModel.h>
+#include <nnapi/hal/1.2/Callbacks.h>
#include <nnapi/hal/1.2/Conversions.h>
#include <nnapi/hal/1.2/PreparedModel.h>
#include <nnapi/hal/CommonUtils.h>
@@ -39,139 +40,99 @@
#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_3::utils {
namespace {
-nn::GeneralResult<nn::SharedPreparedModel> convertPreparedModel(
- const sp<V1_0::IPreparedModel>& preparedModel) {
- return NN_TRY(V1_0::utils::PreparedModel::create(preparedModel));
-}
-
-nn::GeneralResult<nn::SharedPreparedModel> convertPreparedModel(
- const sp<V1_2::IPreparedModel>& preparedModel) {
- return NN_TRY(V1_2::utils::PreparedModel::create(preparedModel));
-}
-
-nn::GeneralResult<nn::SharedPreparedModel> convertPreparedModel(
- const sp<IPreparedModel>& preparedModel) {
- return NN_TRY(utils::PreparedModel::create(preparedModel));
-}
-
nn::GeneralResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>>
convertExecutionGeneralResultsHelper(const hidl_vec<V1_2::OutputShape>& outputShapes,
const V1_2::Timing& timing) {
return std::make_pair(NN_TRY(nn::convert(outputShapes)), NN_TRY(nn::convert(timing)));
}
-nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>>
-convertExecutionGeneralResults(const hidl_vec<V1_2::OutputShape>& outputShapes,
- const V1_2::Timing& timing) {
+} // namespace
+
+nn::GeneralResult<std::vector<bool>> supportedOperationsCallback(
+ ErrorStatus status, const hidl_vec<bool>& supportedOperations) {
+ HANDLE_HAL_STATUS(status) << "get supported operations failed with " << toString(status);
+ return supportedOperations;
+}
+
+nn::GeneralResult<nn::SharedPreparedModel> prepareModelCallback(
+ ErrorStatus status, const sp<IPreparedModel>& preparedModel) {
+ HANDLE_HAL_STATUS(status) << "model preparation failed with " << toString(status);
+ return NN_TRY(PreparedModel::create(preparedModel, /*executeSynchronously=*/true));
+}
+
+nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> executionCallback(
+ ErrorStatus status, const hidl_vec<V1_2::OutputShape>& outputShapes,
+ const V1_2::Timing& timing) {
+ if (status == ErrorStatus::OUTPUT_INSUFFICIENT_SIZE) {
+ auto canonicalOutputShapes =
+ nn::convert(outputShapes).value_or(std::vector<nn::OutputShape>{});
+ return NN_ERROR(nn::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE, std::move(canonicalOutputShapes))
+ << "execution failed with " << toString(status);
+ }
+ HANDLE_HAL_STATUS(status) << "execution failed with " << toString(status);
return hal::utils::makeExecutionFailure(
convertExecutionGeneralResultsHelper(outputShapes, timing));
}
-} // namespace
-
Return<void> PreparedModelCallback::notify(V1_0::ErrorStatus status,
const sp<V1_0::IPreparedModel>& preparedModel) {
- if (status != V1_0::ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- notifyInternal(NN_ERROR(canonical) << "preparedModel failed with " << toString(status));
- } else if (preparedModel == nullptr) {
- notifyInternal(NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
- << "Returned preparedModel is nullptr");
- } else {
- notifyInternal(convertPreparedModel(preparedModel));
- }
+ mData.put(V1_0::utils::prepareModelCallback(status, preparedModel));
return Void();
}
Return<void> PreparedModelCallback::notify_1_2(V1_0::ErrorStatus status,
const sp<V1_2::IPreparedModel>& preparedModel) {
- if (status != V1_0::ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- notifyInternal(NN_ERROR(canonical) << "preparedModel failed with " << toString(status));
- } else if (preparedModel == nullptr) {
- notifyInternal(NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
- << "Returned preparedModel is nullptr");
- } else {
- notifyInternal(convertPreparedModel(preparedModel));
- }
+ mData.put(V1_2::utils::prepareModelCallback(status, preparedModel));
return Void();
}
Return<void> PreparedModelCallback::notify_1_3(ErrorStatus status,
const sp<IPreparedModel>& preparedModel) {
- if (status != ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- notifyInternal(NN_ERROR(canonical) << "preparedModel failed with " << toString(status));
- } else if (preparedModel == nullptr) {
- notifyInternal(NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
- << "Returned preparedModel is nullptr");
- } else {
- notifyInternal(convertPreparedModel(preparedModel));
- }
+ mData.put(prepareModelCallback(status, preparedModel));
return Void();
}
void PreparedModelCallback::notifyAsDeadObject() {
- notifyInternal(NN_ERROR(nn::ErrorStatus::DEAD_OBJECT) << "Dead object");
+ mData.put(NN_ERROR(nn::ErrorStatus::DEAD_OBJECT) << "Dead object");
}
PreparedModelCallback::Data PreparedModelCallback::get() {
return mData.take();
}
-void PreparedModelCallback::notifyInternal(PreparedModelCallback::Data result) {
- mData.put(std::move(result));
-}
-
// ExecutionCallback methods begin here
Return<void> ExecutionCallback::notify(V1_0::ErrorStatus status) {
- if (status != V1_0::ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- notifyInternal(NN_ERROR(canonical) << "execute failed with " << toString(status));
- } else {
- notifyInternal({});
- }
+ mData.put(V1_0::utils::executionCallback(status));
return Void();
}
Return<void> ExecutionCallback::notify_1_2(V1_0::ErrorStatus status,
const hidl_vec<V1_2::OutputShape>& outputShapes,
const V1_2::Timing& timing) {
- if (status != V1_0::ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- notifyInternal(NN_ERROR(canonical) << "execute failed with " << toString(status));
- } else {
- notifyInternal(convertExecutionGeneralResults(outputShapes, timing));
- }
+ mData.put(V1_2::utils::executionCallback(status, outputShapes, timing));
return Void();
}
Return<void> ExecutionCallback::notify_1_3(ErrorStatus status,
const hidl_vec<V1_2::OutputShape>& outputShapes,
const V1_2::Timing& timing) {
- if (status != ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- notifyInternal(NN_ERROR(canonical) << "execute failed with " << toString(status));
- } else {
- notifyInternal(convertExecutionGeneralResults(outputShapes, timing));
- }
+ mData.put(executionCallback(status, outputShapes, timing));
return Void();
}
void ExecutionCallback::notifyAsDeadObject() {
- notifyInternal(NN_ERROR(nn::ErrorStatus::DEAD_OBJECT) << "Dead object");
+ mData.put(NN_ERROR(nn::ErrorStatus::DEAD_OBJECT) << "Dead object");
}
ExecutionCallback::Data ExecutionCallback::get() {
return mData.take();
}
-void ExecutionCallback::notifyInternal(ExecutionCallback::Data result) {
- mData.put(std::move(result));
-}
-
} // namespace android::hardware::neuralnetworks::V1_3::utils
diff --git a/neuralnetworks/1.3/utils/src/Conversions.cpp b/neuralnetworks/1.3/utils/src/Conversions.cpp
index 949dd0d..8b7db2b 100644
--- a/neuralnetworks/1.3/utils/src/Conversions.cpp
+++ b/neuralnetworks/1.3/utils/src/Conversions.cpp
@@ -272,47 +272,26 @@
GeneralResult<OptionalTimePoint> unvalidatedConvert(
const hal::V1_3::OptionalTimePoint& optionalTimePoint) {
- constexpr auto kTimePointMaxCount = TimePoint::max().time_since_epoch().count();
- const auto makeTimePoint = [](uint64_t count) -> GeneralResult<OptionalTimePoint> {
- if (count > kTimePointMaxCount) {
- return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
- << "Unable to unvalidatedConvert OptionalTimePoint because the count exceeds "
- "the max";
- }
- const auto nanoseconds = std::chrono::nanoseconds{count};
- return TimePoint{nanoseconds};
- };
-
using Discriminator = hal::V1_3::OptionalTimePoint::hidl_discriminator;
switch (optionalTimePoint.getDiscriminator()) {
case Discriminator::none:
- return std::nullopt;
+ return {};
case Discriminator::nanosecondsSinceEpoch:
- return makeTimePoint(optionalTimePoint.nanosecondsSinceEpoch());
+ return TimePoint{Duration{optionalTimePoint.nanosecondsSinceEpoch()}};
}
return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
<< "Invalid OptionalTimePoint discriminator "
<< underlyingType(optionalTimePoint.getDiscriminator());
}
-GeneralResult<OptionalTimeoutDuration> unvalidatedConvert(
+GeneralResult<OptionalDuration> unvalidatedConvert(
const hal::V1_3::OptionalTimeoutDuration& optionalTimeoutDuration) {
- constexpr auto kTimeoutDurationMaxCount = TimeoutDuration::max().count();
- const auto makeTimeoutDuration = [](uint64_t count) -> GeneralResult<OptionalTimeoutDuration> {
- if (count > kTimeoutDurationMaxCount) {
- return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
- << "Unable to unvalidatedConvert OptionalTimeoutDuration because the count "
- "exceeds the max";
- }
- return TimeoutDuration{count};
- };
-
using Discriminator = hal::V1_3::OptionalTimeoutDuration::hidl_discriminator;
switch (optionalTimeoutDuration.getDiscriminator()) {
case Discriminator::none:
- return std::nullopt;
+ return {};
case Discriminator::nanoseconds:
- return makeTimeoutDuration(optionalTimeoutDuration.nanoseconds());
+ return Duration(optionalTimeoutDuration.nanoseconds());
}
return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
<< "Invalid OptionalTimeoutDuration discriminator "
@@ -360,7 +339,7 @@
return validatedConvert(optionalTimePoint);
}
-GeneralResult<OptionalTimeoutDuration> convert(
+GeneralResult<OptionalDuration> convert(
const hal::V1_3::OptionalTimeoutDuration& optionalTimeoutDuration) {
return validatedConvert(optionalTimeoutDuration);
}
@@ -629,27 +608,16 @@
OptionalTimePoint ret;
if (optionalTimePoint.has_value()) {
const auto count = optionalTimePoint.value().time_since_epoch().count();
- if (count < 0) {
- return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
- << "Unable to unvalidatedConvert OptionalTimePoint because time since epoch "
- "count is "
- "negative";
- }
ret.nanosecondsSinceEpoch(count);
}
return ret;
}
nn::GeneralResult<OptionalTimeoutDuration> unvalidatedConvert(
- const nn::OptionalTimeoutDuration& optionalTimeoutDuration) {
+ const nn::OptionalDuration& optionalTimeoutDuration) {
OptionalTimeoutDuration ret;
if (optionalTimeoutDuration.has_value()) {
const auto count = optionalTimeoutDuration.value().count();
- if (count < 0) {
- return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
- << "Unable to unvalidatedConvert OptionalTimeoutDuration because count is "
- "negative";
- }
ret.nanoseconds(count);
}
return ret;
@@ -697,7 +665,7 @@
}
nn::GeneralResult<OptionalTimeoutDuration> convert(
- const nn::OptionalTimeoutDuration& optionalTimeoutDuration) {
+ const nn::OptionalDuration& optionalTimeoutDuration) {
return validatedConvert(optionalTimeoutDuration);
}
@@ -717,4 +685,38 @@
return validatedConvert(bufferRoles);
}
+nn::GeneralResult<V1_0::DeviceStatus> convert(const nn::DeviceStatus& deviceStatus) {
+ return V1_2::utils::convert(deviceStatus);
+}
+
+nn::GeneralResult<V1_1::ExecutionPreference> convert(
+ const nn::ExecutionPreference& executionPreference) {
+ return V1_2::utils::convert(executionPreference);
+}
+
+nn::GeneralResult<hidl_vec<V1_2::Extension>> convert(const std::vector<nn::Extension>& extensions) {
+ return V1_2::utils::convert(extensions);
+}
+
+nn::GeneralResult<hidl_vec<hidl_handle>> convert(const std::vector<nn::SharedHandle>& handles) {
+ return V1_2::utils::convert(handles);
+}
+
+nn::GeneralResult<hidl_vec<V1_2::OutputShape>> convert(
+ const std::vector<nn::OutputShape>& outputShapes) {
+ return V1_2::utils::convert(outputShapes);
+}
+
+nn::GeneralResult<V1_2::DeviceType> convert(const nn::DeviceType& deviceType) {
+ return V1_2::utils::convert(deviceType);
+}
+
+nn::GeneralResult<V1_2::MeasureTiming> convert(const nn::MeasureTiming& measureTiming) {
+ return V1_2::utils::convert(measureTiming);
+}
+
+nn::GeneralResult<V1_2::Timing> convert(const nn::Timing& timing) {
+ return V1_2::utils::convert(timing);
+}
+
} // namespace android::hardware::neuralnetworks::V1_3::utils
diff --git a/neuralnetworks/1.3/utils/src/Device.cpp b/neuralnetworks/1.3/utils/src/Device.cpp
index 82837ba..87c9f32 100644
--- a/neuralnetworks/1.3/utils/src/Device.cpp
+++ b/neuralnetworks/1.3/utils/src/Device.cpp
@@ -36,6 +36,7 @@
#include <nnapi/hal/1.1/Conversions.h>
#include <nnapi/hal/1.2/Conversions.h>
#include <nnapi/hal/1.2/Device.h>
+#include <nnapi/hal/1.2/Utils.h>
#include <nnapi/hal/CommonUtils.h>
#include <nnapi/hal/HandleError.h>
#include <nnapi/hal/ProtectCallback.h>
@@ -47,6 +48,9 @@
#include <string>
#include <vector>
+// 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_3::utils {
namespace {
@@ -66,29 +70,27 @@
return hidlPreparedModels;
}
-nn::GeneralResult<nn::SharedBuffer> convert(
- nn::GeneralResult<std::shared_ptr<const Buffer>> result) {
- return NN_TRY(std::move(result));
+nn::GeneralResult<nn::Capabilities> capabilitiesCallback(ErrorStatus status,
+ const Capabilities& capabilities) {
+ HANDLE_HAL_STATUS(status) << "getting capabilities failed with " << toString(status);
+ return nn::convert(capabilities);
}
-nn::GeneralResult<nn::Capabilities> initCapabilities(V1_3::IDevice* device) {
+nn::GeneralResult<nn::Capabilities> getCapabilitiesFrom(V1_3::IDevice* device) {
CHECK(device != nullptr);
- nn::GeneralResult<nn::Capabilities> result = NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
- << "uninitialized";
- const auto cb = [&result](ErrorStatus status, const Capabilities& capabilities) {
- if (status != ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- result = NN_ERROR(canonical) << "getCapabilities_1_3 failed with " << toString(status);
- } else {
- result = nn::convert(capabilities);
- }
- };
+ auto cb = hal::utils::CallbackValue(capabilitiesCallback);
const auto ret = device->getCapabilities_1_3(cb);
HANDLE_TRANSPORT_FAILURE(ret);
- return result;
+ return cb.take();
+}
+
+nn::GeneralResult<nn::SharedBuffer> allocationCallback(ErrorStatus status,
+ const sp<IBuffer>& buffer, uint32_t token) {
+ HANDLE_HAL_STATUS(status) << "IDevice::allocate failed with " << toString(status);
+ return Buffer::create(buffer, static_cast<nn::Request::MemoryDomainToken>(token));
}
} // namespace
@@ -104,12 +106,12 @@
<< "V1_3::utils::Device::create must have non-null device";
}
- auto versionString = NN_TRY(V1_2::utils::initVersionString(device.get()));
- const auto deviceType = NN_TRY(V1_2::utils::initDeviceType(device.get()));
- auto extensions = NN_TRY(V1_2::utils::initExtensions(device.get()));
- auto capabilities = NN_TRY(initCapabilities(device.get()));
+ auto versionString = NN_TRY(V1_2::utils::getVersionStringFrom(device.get()));
+ const auto deviceType = NN_TRY(V1_2::utils::getDeviceTypeFrom(device.get()));
+ auto extensions = NN_TRY(V1_2::utils::getSupportedExtensionsFrom(device.get()));
+ auto capabilities = NN_TRY(getCapabilitiesFrom(device.get()));
const auto numberOfCacheFilesNeeded =
- NN_TRY(V1_2::utils::initNumberOfCacheFilesNeeded(device.get()));
+ NN_TRY(V1_2::utils::getNumberOfCacheFilesNeededFrom(device.get()));
auto deathHandler = NN_TRY(hal::utils::DeathHandler::create(device));
return std::make_shared<const Device>(
@@ -148,6 +150,10 @@
return kDeviceType;
}
+bool Device::isUpdatable() const {
+ return false;
+}
+
const std::vector<nn::Extension>& Device::getSupportedExtensions() const {
return kExtensions;
}
@@ -174,27 +180,12 @@
const auto hidlModel = NN_TRY(convert(modelInShared));
- nn::GeneralResult<std::vector<bool>> result = NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
- << "uninitialized";
- auto cb = [&result, &model](ErrorStatus status, const hidl_vec<bool>& supportedOperations) {
- if (status != ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- result = NN_ERROR(canonical)
- << "IDevice::getSupportedOperations_1_3 failed with " << toString(status);
- } else if (supportedOperations.size() != model.main.operations.size()) {
- result = NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
- << "IDevice::getSupportedOperations_1_3 returned vector of size "
- << supportedOperations.size() << " but expected "
- << model.main.operations.size();
- } else {
- result = supportedOperations;
- }
- };
+ auto cb = hal::utils::CallbackValue(supportedOperationsCallback);
const auto ret = kDevice->getSupportedOperations_1_3(hidlModel, cb);
HANDLE_TRANSPORT_FAILURE(ret);
- return result;
+ return cb.take();
}
nn::GeneralResult<nn::SharedPreparedModel> Device::prepareModel(
@@ -207,12 +198,12 @@
NN_TRY(hal::utils::flushDataFromPointerToShared(&model, &maybeModelInShared));
const auto hidlModel = NN_TRY(convert(modelInShared));
- const auto hidlPreference = NN_TRY(V1_1::utils::convert(preference));
+ const auto hidlPreference = NN_TRY(convert(preference));
const auto hidlPriority = NN_TRY(convert(priority));
const auto hidlDeadline = NN_TRY(convert(deadline));
- const auto hidlModelCache = NN_TRY(V1_2::utils::convert(modelCache));
- const auto hidlDataCache = NN_TRY(V1_2::utils::convert(dataCache));
- const auto hidlToken = token;
+ const auto hidlModelCache = NN_TRY(convert(modelCache));
+ const auto hidlDataCache = NN_TRY(convert(dataCache));
+ const auto hidlToken = V1_2::utils::CacheToken{token};
const auto cb = sp<PreparedModelCallback>::make();
const auto scoped = kDeathHandler.protectCallback(cb.get());
@@ -221,10 +212,7 @@
kDevice->prepareModel_1_3(hidlModel, hidlPreference, hidlPriority, hidlDeadline,
hidlModelCache, hidlDataCache, hidlToken, cb);
const auto status = HANDLE_TRANSPORT_FAILURE(ret);
- if (status != ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- return NN_ERROR(canonical) << "prepareModel_1_3 failed with " << toString(status);
- }
+ HANDLE_HAL_STATUS(status) << "model preparation failed with " << toString(status);
return cb->get();
}
@@ -233,9 +221,9 @@
nn::OptionalTimePoint deadline, const std::vector<nn::SharedHandle>& modelCache,
const std::vector<nn::SharedHandle>& dataCache, const nn::CacheToken& token) const {
const auto hidlDeadline = NN_TRY(convert(deadline));
- const auto hidlModelCache = NN_TRY(V1_2::utils::convert(modelCache));
- const auto hidlDataCache = NN_TRY(V1_2::utils::convert(dataCache));
- const auto hidlToken = token;
+ const auto hidlModelCache = NN_TRY(convert(modelCache));
+ const auto hidlDataCache = NN_TRY(convert(dataCache));
+ const auto hidlToken = V1_2::utils::CacheToken{token};
const auto cb = sp<PreparedModelCallback>::make();
const auto scoped = kDeathHandler.protectCallback(cb.get());
@@ -243,10 +231,7 @@
const auto ret = kDevice->prepareModelFromCache_1_3(hidlDeadline, hidlModelCache, hidlDataCache,
hidlToken, cb);
const auto status = HANDLE_TRANSPORT_FAILURE(ret);
- if (status != ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- return NN_ERROR(canonical) << "prepareModelFromCache_1_3 failed with " << toString(status);
- }
+ HANDLE_HAL_STATUS(status) << "model preparation from cache failed with " << toString(status);
return cb->get();
}
@@ -260,27 +245,13 @@
const auto hidlInputRoles = NN_TRY(convert(inputRoles));
const auto hidlOutputRoles = NN_TRY(convert(outputRoles));
- nn::GeneralResult<nn::SharedBuffer> result = NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
- << "uninitialized";
- auto cb = [&result](ErrorStatus status, const sp<IBuffer>& buffer, uint32_t token) {
- if (status != ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- result = NN_ERROR(canonical) << "IDevice::allocate failed with " << toString(status);
- } else if (buffer == nullptr) {
- result = NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) << "Returned buffer is nullptr";
- } else if (token == 0) {
- result = NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) << "Returned token is invalid (0)";
- } else {
- result = convert(
- Buffer::create(buffer, static_cast<nn::Request::MemoryDomainToken>(token)));
- }
- };
+ auto cb = hal::utils::CallbackValue(allocationCallback);
const auto ret =
kDevice->allocate(hidlDesc, hidlPreparedModels, hidlInputRoles, hidlOutputRoles, cb);
HANDLE_TRANSPORT_FAILURE(ret);
- return result;
+ return cb.take();
}
} // namespace android::hardware::neuralnetworks::V1_3::utils
diff --git a/neuralnetworks/1.3/utils/src/PreparedModel.cpp b/neuralnetworks/1.3/utils/src/PreparedModel.cpp
index 49b9b0b..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>
@@ -39,28 +40,23 @@
#include <utility>
#include <vector>
+// 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_3::utils {
namespace {
-nn::GeneralResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>>
-convertExecutionResultsHelper(const hidl_vec<V1_2::OutputShape>& outputShapes,
- const V1_2::Timing& timing) {
- return std::make_pair(NN_TRY(nn::convert(outputShapes)), NN_TRY(nn::convert(timing)));
-}
-
-nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> convertExecutionResults(
- const hidl_vec<V1_2::OutputShape>& outputShapes, const V1_2::Timing& timing) {
- return hal::utils::makeExecutionFailure(convertExecutionResultsHelper(outputShapes, timing));
-}
-
nn::GeneralResult<std::pair<nn::Timing, nn::Timing>> convertFencedExecutionCallbackResults(
- const V1_2::Timing& timingLaunched, const V1_2::Timing& timingFenced) {
+ ErrorStatus status, const V1_2::Timing& timingLaunched, const V1_2::Timing& timingFenced) {
+ HANDLE_HAL_STATUS(status) << "fenced execution callback info failed with " << toString(status);
return std::make_pair(NN_TRY(nn::convert(timingLaunched)), NN_TRY(nn::convert(timingFenced)));
}
-nn::GeneralResult<std::pair<nn::SyncFence, nn::ExecuteFencedInfoCallback>>
-convertExecuteFencedResults(const hidl_handle& syncFence,
- const sp<IFencedExecutionCallback>& callback) {
+nn::GeneralResult<std::pair<nn::SyncFence, nn::ExecuteFencedInfoCallback>> fencedExecutionCallback(
+ ErrorStatus status, const hidl_handle& syncFence,
+ const sp<IFencedExecutionCallback>& callback) {
+ HANDLE_HAL_STATUS(status) << "fenced execution failed with " << toString(status);
+
auto resultSyncFence = nn::SyncFence::createAsSignaled();
if (syncFence.getNativeHandle() != nullptr) {
auto sharedHandle = NN_TRY(nn::convert(syncFence));
@@ -75,23 +71,12 @@
// Create callback which can be used to retrieve the execution error status and timings.
nn::ExecuteFencedInfoCallback resultCallback =
[callback]() -> nn::GeneralResult<std::pair<nn::Timing, nn::Timing>> {
- nn::GeneralResult<std::pair<nn::Timing, nn::Timing>> result =
- NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) << "uninitialized";
- auto cb = [&result](ErrorStatus status, const V1_2::Timing& timingLaunched,
- const V1_2::Timing& timingFenced) {
- if (status != ErrorStatus::NONE) {
- const auto canonical =
- nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- result = NN_ERROR(canonical) << "getExecutionInfo failed with " << toString(status);
- } else {
- result = convertFencedExecutionCallbackResults(timingLaunched, timingFenced);
- }
- };
+ auto cb = hal::utils::CallbackValue(convertFencedExecutionCallbackResults);
const auto ret = callback->getExecutionInfo(cb);
HANDLE_TRANSPORT_FAILURE(ret);
- return result;
+ return cb.take();
};
return std::make_pair(std::move(resultSyncFence), std::move(resultCallback));
@@ -100,42 +85,34 @@
} // namespace
nn::GeneralResult<std::shared_ptr<const PreparedModel>> PreparedModel::create(
- sp<V1_3::IPreparedModel> preparedModel) {
+ sp<V1_3::IPreparedModel> preparedModel, bool executeSynchronously) {
if (preparedModel == nullptr) {
- return NN_ERROR(nn::ErrorStatus::INVALID_ARGUMENT)
- << "V1_3::utils::PreparedModel::create must have non-null preparedModel";
+ return NN_ERROR() << "V1_3::utils::PreparedModel::create must have non-null preparedModel";
}
auto deathHandler = NN_TRY(hal::utils::DeathHandler::create(preparedModel));
- return std::make_shared<const PreparedModel>(PrivateConstructorTag{}, std::move(preparedModel),
- std::move(deathHandler));
+ return std::make_shared<const PreparedModel>(PrivateConstructorTag{}, executeSynchronously,
+ std::move(preparedModel), std::move(deathHandler));
}
-PreparedModel::PreparedModel(PrivateConstructorTag /*tag*/, sp<V1_3::IPreparedModel> preparedModel,
+PreparedModel::PreparedModel(PrivateConstructorTag /*tag*/, bool executeSynchronously,
+ sp<V1_3::IPreparedModel> preparedModel,
hal::utils::DeathHandler deathHandler)
- : kPreparedModel(std::move(preparedModel)), kDeathHandler(std::move(deathHandler)) {}
+ : kExecuteSynchronously(executeSynchronously),
+ kPreparedModel(std::move(preparedModel)),
+ kDeathHandler(std::move(deathHandler)) {}
nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>>
PreparedModel::executeSynchronously(const Request& request, V1_2::MeasureTiming measure,
const OptionalTimePoint& deadline,
const OptionalTimeoutDuration& loopTimeoutDuration) const {
- nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> result =
- NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) << "uninitialized";
- const auto cb = [&result](ErrorStatus status, const hidl_vec<V1_2::OutputShape>& outputShapes,
- const V1_2::Timing& timing) {
- if (status != ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- result = NN_ERROR(canonical) << "executeSynchronously failed with " << toString(status);
- } else {
- result = convertExecutionResults(outputShapes, timing);
- }
- };
+ auto cb = hal::utils::CallbackValue(executionCallback);
const auto ret = kPreparedModel->executeSynchronously_1_3(request, measure, deadline,
loopTimeoutDuration, cb);
HANDLE_TRANSPORT_FAILURE(ret);
- return result;
+ return cb.take();
}
nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>>
@@ -148,9 +125,8 @@
const auto ret =
kPreparedModel->execute_1_3(request, measure, deadline, loopTimeoutDuration, cb);
const auto status = HANDLE_TRANSPORT_FAILURE(ret);
- if (status != ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- return NN_ERROR(canonical) << "executeAsynchronously failed with " << toString(status);
+ if (status != ErrorStatus::OUTPUT_INSUFFICIENT_SIZE) {
+ HANDLE_HAL_STATUS(status) << "execution failed with " << toString(status);
}
return cb->get();
@@ -159,49 +135,36 @@
nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> PreparedModel::execute(
const nn::Request& request, nn::MeasureTiming measure,
const nn::OptionalTimePoint& deadline,
- const nn::OptionalTimeoutDuration& loopTimeoutDuration) const {
+ const nn::OptionalDuration& loopTimeoutDuration) const {
// Ensure that request is ready for IPC.
std::optional<nn::Request> maybeRequestInShared;
const nn::Request& requestInShared = NN_TRY(hal::utils::makeExecutionFailure(
hal::utils::flushDataFromPointerToShared(&request, &maybeRequestInShared)));
const auto hidlRequest = NN_TRY(hal::utils::makeExecutionFailure(convert(requestInShared)));
- const auto hidlMeasure =
- NN_TRY(hal::utils::makeExecutionFailure(V1_2::utils::convert(measure)));
+ const auto hidlMeasure = NN_TRY(hal::utils::makeExecutionFailure(convert(measure)));
const auto hidlDeadline = NN_TRY(hal::utils::makeExecutionFailure(convert(deadline)));
const auto hidlLoopTimeoutDuration =
NN_TRY(hal::utils::makeExecutionFailure(convert(loopTimeoutDuration)));
- nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> result =
- NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) << "uninitialized";
- const bool preferSynchronous = true;
+ auto result = kExecuteSynchronously
+ ? executeSynchronously(hidlRequest, hidlMeasure, hidlDeadline,
+ hidlLoopTimeoutDuration)
+ : executeAsynchronously(hidlRequest, hidlMeasure, hidlDeadline,
+ hidlLoopTimeoutDuration);
+ auto [outputShapes, timing] = NN_TRY(std::move(result));
- // Execute synchronously if allowed.
- if (preferSynchronous) {
- result = executeSynchronously(hidlRequest, hidlMeasure, hidlDeadline,
- hidlLoopTimeoutDuration);
- }
+ NN_TRY(hal::utils::makeExecutionFailure(
+ hal::utils::unflushDataFromSharedToPointer(request, maybeRequestInShared)));
- // Run asymchronous execution if execution has not already completed.
- if (!result.has_value()) {
- result = executeAsynchronously(hidlRequest, hidlMeasure, hidlDeadline,
- hidlLoopTimeoutDuration);
- }
-
- // Flush output buffers if suxcessful execution.
- if (result.has_value()) {
- NN_TRY(hal::utils::makeExecutionFailure(
- hal::utils::unflushDataFromSharedToPointer(request, maybeRequestInShared)));
- }
-
- return result;
+ return std::make_pair(std::move(outputShapes), timing);
}
nn::GeneralResult<std::pair<nn::SyncFence, nn::ExecuteFencedInfoCallback>>
PreparedModel::executeFenced(const nn::Request& request, const std::vector<nn::SyncFence>& waitFor,
nn::MeasureTiming measure, const nn::OptionalTimePoint& deadline,
- const nn::OptionalTimeoutDuration& loopTimeoutDuration,
- const nn::OptionalTimeoutDuration& timeoutDurationAfterFence) const {
+ const nn::OptionalDuration& loopTimeoutDuration,
+ const nn::OptionalDuration& timeoutDurationAfterFence) const {
// Ensure that request is ready for IPC.
std::optional<nn::Request> maybeRequestInShared;
const nn::Request& requestInShared =
@@ -209,28 +172,18 @@
const auto hidlRequest = NN_TRY(convert(requestInShared));
const auto hidlWaitFor = NN_TRY(hal::utils::convertSyncFences(waitFor));
- const auto hidlMeasure = NN_TRY(V1_2::utils::convert(measure));
+ const auto hidlMeasure = NN_TRY(convert(measure));
const auto hidlDeadline = NN_TRY(convert(deadline));
const auto hidlLoopTimeoutDuration = NN_TRY(convert(loopTimeoutDuration));
const auto hidlTimeoutDurationAfterFence = NN_TRY(convert(timeoutDurationAfterFence));
- nn::GeneralResult<std::pair<nn::SyncFence, nn::ExecuteFencedInfoCallback>> result =
- NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE) << "uninitialized";
- auto cb = [&result](ErrorStatus status, const hidl_handle& syncFence,
- const sp<IFencedExecutionCallback>& callback) {
- if (status != ErrorStatus::NONE) {
- const auto canonical = nn::convert(status).value_or(nn::ErrorStatus::GENERAL_FAILURE);
- result = NN_ERROR(canonical) << "executeFenced failed with " << toString(status);
- } else {
- result = convertExecuteFencedResults(syncFence, callback);
- }
- };
+ auto cb = hal::utils::CallbackValue(fencedExecutionCallback);
const auto ret = kPreparedModel->executeFenced(hidlRequest, hidlWaitFor, hidlMeasure,
hidlDeadline, hidlLoopTimeoutDuration,
hidlTimeoutDurationAfterFence, cb);
HANDLE_TRANSPORT_FAILURE(ret);
- auto [syncFence, callback] = NN_TRY(std::move(result));
+ auto [syncFence, callback] = NN_TRY(cb.take());
// If executeFenced required the request memory to be moved into shared memory, block here until
// the fenced execution has completed and flush the memory back.
@@ -245,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/README.md b/neuralnetworks/utils/README.md
index 0dee103..45ca0b4 100644
--- a/neuralnetworks/utils/README.md
+++ b/neuralnetworks/utils/README.md
@@ -1,11 +1,11 @@
# NNAPI Conversions
`convert` fails if either the source type or the destination type is invalid, and it yields a valid
-object if the conversion succeeds. For example, let's say that an enumeration in the current
-version has fewer possible values than the "same" canonical enumeration, such as `OperationType`.
-The new value of `HARD_SWISH` (introduced in Android R / NN HAL 1.3) does not map to any valid
-existing value in `OperationType`, but an older value of `ADD` (introduced in Android OC-MR1 / NN
-HAL 1.0) is valid. This can be seen in the following model conversions:
+object if the conversion succeeds. For example, let's say that an enumeration in the current version
+has fewer possible values than the "same" canonical enumeration, such as `OperationType`. The new
+value of `HARD_SWISH` (introduced in Android R / NN HAL 1.3) does not map to any valid existing
+value in `OperationType`, but an older value of `ADD` (introduced in Android OC-MR1 / NN HAL 1.0) is
+valid. This can be seen in the following model conversions:
```cpp
// Unsuccessful conversion
@@ -48,3 +48,50 @@
`unvalidatedConvert` functions operate on types that are either used in a HIDL method call directly
(i.e., not as a nested class) or used in a subsequent version of the NN HAL. Prefer using `convert`
over `unvalidatedConvert`.
+
+# HIDL Interface Lifetimes across Processes
+
+Some notes about HIDL interface objects and lifetimes across processes:
+
+All HIDL interface objects inherit from `IBase`, which itself inherits from `::android::RefBase`. As
+such, all HIDL interface objects are reference counted and must be owned through `::android::sp` (or
+referenced through `::android::wp`). Allocating `RefBase` objects on the stack will log errors and
+may result in crashes, and deleting a `RefBase` object through another means (e.g., "delete",
+"free", or RAII-cleanup through `std::unique_ptr` or some equivalent) will result in double-free
+and/or use-after-free undefined behavior.
+
+HIDL/Binder manages the reference count of HIDL interface objects automatically across processes. If
+a process that references (but did not create) the HIDL interface object dies, HIDL/Binder ensures
+any reference count it held is properly released. (Caveat: it might be possible that HIDL/Binder
+behave strangely with `::android::wp` references.)
+
+If the process which created the HIDL interface object dies, any call on this object from another
+process will result in a HIDL transport error with the code `DEAD_OBJECT`.
+
+# Protecting Asynchronous Calls across HIDL
+
+Some notes about asynchronous calls across HIDL:
+
+For synchronous calls across HIDL, if an error occurs after the function was called but before it
+returns, HIDL will return a transport error. For example, if the message cannot be delivered to the
+server process or if the server process dies before returning a result, HIDL will return from the
+function with the appropriate transport error in the `Return<>` object, which can be queried with
+`Return<>::isOk()`, `Return<>::isDeadObject()`, `Return<>::description()`, etc.
+
+However, HIDL offers no such error management in the case of asynchronous calls. By default, if the
+client launches an asynchronous task and the server fails to return a result through the callback,
+the client will be left waiting indefinitely for a result it will never receive.
+
+In the NNAPI, `IDevice::prepareModel*` and `IPreparedModel::execute*` (but not
+`IPreparedModel::executeSynchronously*`) are asynchronous calls across HIDL. Specifically, these
+asynchronous functions are called with a HIDL interface callback object (`IPrepareModelCallback` for
+`IDevice::prepareModel*` and `IExecutionCallback` for `IPreparedModel::execute*`) and are expected
+to quickly return, and the results are returned at a later time through these callback objects.
+
+To protect against the case when the server dies after the asynchronous task was called successfully
+but before the results could be returned, HIDL provides an object called a "`hidl_death_recipient`,"
+which can be used to detect when an interface object (and more generally, the server process) has
+died. nnapi/hal/ProtectCallback.h's `DeathHandler` uses `hidl_death_recipient`s to detect when the
+driver process has died, and `DeathHandler` will unblock any thread waiting on the results of an
+`IProtectedCallback` callback object that may otherwise not be signaled. In order for this to work,
+the `IProtectedCallback` object must have been registered via `DeathHandler::protectCallback()`.
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/CommonUtils.h b/neuralnetworks/utils/common/include/nnapi/hal/CommonUtils.h
index 43bb0c6..b3989e5 100644
--- a/neuralnetworks/utils/common/include/nnapi/hal/CommonUtils.h
+++ b/neuralnetworks/utils/common/include/nnapi/hal/CommonUtils.h
@@ -44,9 +44,18 @@
bool hasNoPointerData(const nn::Model& model);
bool hasNoPointerData(const nn::Request& request);
-// Relocate pointer-based data to shared memory.
+// Relocate pointer-based data to shared memory. If `model` has no Operand::LifeTime::POINTER data,
+// the function returns with a reference to `model`. If `model` has Operand::LifeTime::POINTER data,
+// the model is copied to `maybeModelInSharedOut` with the POINTER data relocated to a memory pool,
+// and the function returns with a reference to `*maybeModelInSharedOut`.
nn::GeneralResult<std::reference_wrapper<const nn::Model>> flushDataFromPointerToShared(
const nn::Model* model, std::optional<nn::Model>* maybeModelInSharedOut);
+
+// Relocate pointer-based data to shared memory. If `request` has no
+// Request::Argument::LifeTime::POINTER data, the function returns with a reference to `request`. If
+// `request` has Request::Argument::LifeTime::POINTER data, the request is copied to
+// `maybeRequestInSharedOut` with the POINTER data relocated to a memory pool, and the function
+// returns with a reference to `*maybeRequestInSharedOut`.
nn::GeneralResult<std::reference_wrapper<const nn::Request>> flushDataFromPointerToShared(
const nn::Request* request, std::optional<nn::Request>* maybeRequestInSharedOut);
diff --git a/neuralnetworks/utils/common/include/nnapi/hal/HandleError.h b/neuralnetworks/utils/common/include/nnapi/hal/HandleError.h
index 78b2a12..95a20a8 100644
--- a/neuralnetworks/utils/common/include/nnapi/hal/HandleError.h
+++ b/neuralnetworks/utils/common/include/nnapi/hal/HandleError.h
@@ -79,4 +79,11 @@
return makeExecutionFailure(makeGeneralFailure(result, status));
}
+#define HANDLE_HAL_STATUS(status) \
+ if (const auto canonical = ::android::nn::convert(status).value_or( \
+ ::android::nn::ErrorStatus::GENERAL_FAILURE); \
+ canonical == ::android::nn::ErrorStatus::NONE) { \
+ } else \
+ return NN_ERROR(canonical)
+
} // namespace android::hardware::neuralnetworks::utils
\ No newline at end of file
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 4b32b4e..3e1dca7 100644
--- a/neuralnetworks/utils/common/include/nnapi/hal/InvalidPreparedModel.h
+++ b/neuralnetworks/utils/common/include/nnapi/hal/InvalidPreparedModel.h
@@ -32,13 +32,15 @@
nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> execute(
const nn::Request& request, nn::MeasureTiming measure,
const nn::OptionalTimePoint& deadline,
- const nn::OptionalTimeoutDuration& loopTimeoutDuration) const override;
+ const nn::OptionalDuration& loopTimeoutDuration) const override;
nn::GeneralResult<std::pair<nn::SyncFence, nn::ExecuteFencedInfoCallback>> executeFenced(
const nn::Request& request, const std::vector<nn::SyncFence>& waitFor,
nn::MeasureTiming measure, const nn::OptionalTimePoint& deadline,
- const nn::OptionalTimeoutDuration& loopTimeoutDuration,
- const nn::OptionalTimeoutDuration& timeoutDurationAfterFence) const override;
+ 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/ProtectCallback.h b/neuralnetworks/utils/common/include/nnapi/hal/ProtectCallback.h
index 85bd613..c921885 100644
--- a/neuralnetworks/utils/common/include/nnapi/hal/ProtectCallback.h
+++ b/neuralnetworks/utils/common/include/nnapi/hal/ProtectCallback.h
@@ -28,6 +28,9 @@
#include <mutex>
#include <vector>
+// 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::utils {
class IProtectedCallback {
diff --git a/neuralnetworks/utils/common/include/nnapi/hal/ResilientBuffer.h b/neuralnetworks/utils/common/include/nnapi/hal/ResilientBuffer.h
index 996ec1e..d2c2469 100644
--- a/neuralnetworks/utils/common/include/nnapi/hal/ResilientBuffer.h
+++ b/neuralnetworks/utils/common/include/nnapi/hal/ResilientBuffer.h
@@ -34,7 +34,7 @@
struct PrivateConstructorTag {};
public:
- using Factory = std::function<nn::GeneralResult<nn::SharedBuffer>(bool blocking)>;
+ using Factory = std::function<nn::GeneralResult<nn::SharedBuffer>()>;
static nn::GeneralResult<std::shared_ptr<const ResilientBuffer>> create(Factory makeBuffer);
@@ -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 4bfed6c..8199c52 100644
--- a/neuralnetworks/utils/common/include/nnapi/hal/ResilientDevice.h
+++ b/neuralnetworks/utils/common/include/nnapi/hal/ResilientDevice.h
@@ -46,13 +46,14 @@
nn::Capabilities capabilities, nn::SharedDevice device);
nn::SharedDevice getDevice() const EXCLUDES(mMutex);
- nn::SharedDevice recover(const nn::IDevice* failingDevice, bool blocking) const
- EXCLUDES(mMutex);
+ nn::GeneralResult<nn::SharedDevice> recover(const nn::IDevice* failingDevice,
+ bool blocking) const EXCLUDES(mMutex);
const std::string& getName() const override;
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;
@@ -81,17 +82,14 @@
private:
bool isValidInternal() const EXCLUDES(mMutex);
nn::GeneralResult<nn::SharedPreparedModel> prepareModelInternal(
- bool blocking, const nn::Model& model, nn::ExecutionPreference preference,
- nn::Priority priority, nn::OptionalTimePoint deadline,
- const std::vector<nn::SharedHandle>& modelCache,
+ 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;
nn::GeneralResult<nn::SharedPreparedModel> prepareModelFromCacheInternal(
- bool blocking, nn::OptionalTimePoint deadline,
- const std::vector<nn::SharedHandle>& modelCache,
+ nn::OptionalTimePoint deadline, const std::vector<nn::SharedHandle>& modelCache,
const std::vector<nn::SharedHandle>& dataCache, const nn::CacheToken& token) const;
nn::GeneralResult<nn::SharedBuffer> allocateInternal(
- bool blocking, const nn::BufferDesc& desc,
- const std::vector<nn::SharedPreparedModel>& preparedModels,
+ const nn::BufferDesc& desc, const std::vector<nn::SharedPreparedModel>& preparedModels,
const std::vector<nn::BufferRole>& inputRoles,
const std::vector<nn::BufferRole>& outputRoles) const;
diff --git a/neuralnetworks/utils/common/include/nnapi/hal/ResilientPreparedModel.h b/neuralnetworks/utils/common/include/nnapi/hal/ResilientPreparedModel.h
index c2940d1..a6c1b19 100644
--- a/neuralnetworks/utils/common/include/nnapi/hal/ResilientPreparedModel.h
+++ b/neuralnetworks/utils/common/include/nnapi/hal/ResilientPreparedModel.h
@@ -30,11 +30,12 @@
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:
- using Factory = std::function<nn::GeneralResult<nn::SharedPreparedModel>(bool blocking)>;
+ using Factory = std::function<nn::GeneralResult<nn::SharedPreparedModel>()>;
static nn::GeneralResult<std::shared_ptr<const ResilientPreparedModel>> create(
Factory makePreparedModel);
@@ -43,23 +44,28 @@
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,
const nn::OptionalTimePoint& deadline,
- const nn::OptionalTimeoutDuration& loopTimeoutDuration) const override;
+ const nn::OptionalDuration& loopTimeoutDuration) const override;
nn::GeneralResult<std::pair<nn::SyncFence, nn::ExecuteFencedInfoCallback>> executeFenced(
const nn::Request& request, const std::vector<nn::SyncFence>& waitFor,
nn::MeasureTiming measure, const nn::OptionalTimePoint& deadline,
- const nn::OptionalTimeoutDuration& loopTimeoutDuration,
- const nn::OptionalTimeoutDuration& timeoutDurationAfterFence) const override;
+ 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/include/nnapi/hal/TransferValue.h b/neuralnetworks/utils/common/include/nnapi/hal/TransferValue.h
index 7103c6b..6679afe 100644
--- a/neuralnetworks/utils/common/include/nnapi/hal/TransferValue.h
+++ b/neuralnetworks/utils/common/include/nnapi/hal/TransferValue.h
@@ -17,19 +17,60 @@
#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_TRANSFER_VALUE_H
#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_TRANSFER_VALUE_H
+#include <android-base/logging.h>
#include <android-base/thread_annotations.h>
#include <condition_variable>
+#include <functional>
#include <mutex>
#include <optional>
+#include <type_traits>
namespace android::hardware::neuralnetworks::utils {
-// This class is thread safe.
+// This class adapts a function pointer and offers two affordances:
+// 1) This class object can be used to generate a callback (via the implicit conversion operator)
+// that can be used to send the result to `CallbackValue` when called.
+// 2) This class object can be used to retrieve the result of the callback with `take`.
+//
+// This class is thread compatible.
+template <typename ReturnType, typename... ArgTypes>
+class CallbackValue final {
+ public:
+ using FunctionType = std::add_pointer_t<ReturnType(ArgTypes...)>;
+ using CallbackType = std::function<void(ArgTypes...)>;
+
+ explicit CallbackValue(FunctionType fn);
+
+ // Creates a callback that forwards its arguments to `mFunction` and stores the result in
+ // `mReturnValue`.
+ /*implicit*/ operator CallbackType(); // NOLINT(google-explicit-constructor)
+
+ // Take the result of calling `mFunction`.
+ // Precondition: mReturnValue.has_value()
+ // Postcondition: !mReturnValue.has_value()
+ [[nodiscard]] ReturnType take();
+
+ private:
+ std::optional<ReturnType> mReturnValue;
+ FunctionType mFunction;
+};
+
+// Deduction guidelines for CallbackValue when constructed with a function pointer.
+template <typename ReturnType, typename... ArgTypes>
+CallbackValue(ReturnType (*)(ArgTypes...))->CallbackValue<ReturnType, ArgTypes...>;
+
+// Thread-safe container to pass a value between threads.
template <typename Type>
class TransferValue final {
public:
+ // Put the value in `TransferValue`. If `TransferValue` already has a value, this function is a
+ // no-op.
void put(Type object) const;
+
+ // Take the value stored in `TransferValue`. If no value is available, this function will block
+ // until the value becomes available.
+ // Postcondition: !mObject.has_value()
[[nodiscard]] Type take() const;
private:
@@ -38,7 +79,23 @@
mutable std::optional<Type> mObject GUARDED_BY(mMutex);
};
-// template implementation
+// template implementations
+
+template <typename ReturnType, typename... ArgTypes>
+CallbackValue<ReturnType, ArgTypes...>::CallbackValue(FunctionType fn) : mFunction(fn) {}
+
+template <typename ReturnType, typename... ArgTypes>
+CallbackValue<ReturnType, ArgTypes...>::operator CallbackType() {
+ return [this](ArgTypes... args) { mReturnValue = mFunction(args...); };
+}
+
+template <typename ReturnType, typename... ArgTypes>
+ReturnType CallbackValue<ReturnType, ArgTypes...>::take() {
+ CHECK(mReturnValue.has_value());
+ std::optional<ReturnType> object;
+ std::swap(object, mReturnValue);
+ return std::move(object).value();
+}
template <typename Type>
void TransferValue<Type>::put(Type object) const {
@@ -56,6 +113,7 @@
std::unique_lock lock(mMutex);
base::ScopedLockAssertion lockAssertion(mMutex);
mCondition.wait(lock, [this]() REQUIRES(mMutex) { return mObject.has_value(); });
+ CHECK(mObject.has_value());
std::optional<Type> object;
std::swap(object, mObject);
return std::move(object).value();
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 9ae7a63..9081e1f 100644
--- a/neuralnetworks/utils/common/src/InvalidPreparedModel.cpp
+++ b/neuralnetworks/utils/common/src/InvalidPreparedModel.cpp
@@ -29,7 +29,7 @@
nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>>
InvalidPreparedModel::execute(const nn::Request& /*request*/, nn::MeasureTiming /*measure*/,
const nn::OptionalTimePoint& /*deadline*/,
- const nn::OptionalTimeoutDuration& /*loopTimeoutDuration*/) const {
+ const nn::OptionalDuration& /*loopTimeoutDuration*/) const {
return NN_ERROR() << "InvalidPreparedModel";
}
@@ -37,8 +37,12 @@
InvalidPreparedModel::executeFenced(
const nn::Request& /*request*/, const std::vector<nn::SyncFence>& /*waitFor*/,
nn::MeasureTiming /*measure*/, const nn::OptionalTimePoint& /*deadline*/,
- const nn::OptionalTimeoutDuration& /*loopTimeoutDuration*/,
- const nn::OptionalTimeoutDuration& /*timeoutDurationAfterFence*/) const {
+ const nn::OptionalDuration& /*loopTimeoutDuration*/,
+ const nn::OptionalDuration& /*timeoutDurationAfterFence*/) const {
+ return NN_ERROR() << "InvalidPreparedModel";
+}
+
+nn::GeneralResult<nn::SharedBurst> InvalidPreparedModel::configureExecutionBurst() const {
return NN_ERROR() << "InvalidPreparedModel";
}
diff --git a/neuralnetworks/utils/common/src/ResilientBuffer.cpp b/neuralnetworks/utils/common/src/ResilientBuffer.cpp
index 984295b..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) {
@@ -36,7 +65,7 @@
return NN_ERROR(nn::ErrorStatus::INVALID_ARGUMENT)
<< "utils::ResilientBuffer::create must have non-empty makeBuffer";
}
- auto buffer = NN_TRY(makeBuffer(/*blocking=*/true));
+ auto buffer = NN_TRY(makeBuffer());
CHECK(buffer != nullptr);
return std::make_shared<const ResilientBuffer>(PrivateConstructorTag{}, std::move(makeBuffer),
std::move(buffer));
@@ -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 2f83c5c..13965af 100644
--- a/neuralnetworks/utils/common/src/ResilientDevice.cpp
+++ b/neuralnetworks/utils/common/src/ResilientDevice.cpp
@@ -49,7 +49,17 @@
return result;
}
- device = resilientDevice.recover(device.get(), blocking);
+ // Attempt recovery and return if it fails.
+ auto maybeDevice = resilientDevice.recover(device.get(), blocking);
+ if (!maybeDevice.has_value()) {
+ const auto& [resultErrorMessage, resultErrorCode] = result.error();
+ const auto& [recoveryErrorMessage, recoveryErrorCode] = maybeDevice.error();
+ return nn::error(resultErrorCode)
+ << resultErrorMessage << ", and failed to recover dead device with error "
+ << recoveryErrorCode << ": " << recoveryErrorMessage;
+ }
+ device = std::move(maybeDevice).value();
+
return fn(*device);
}
@@ -94,7 +104,8 @@
return mDevice;
}
-nn::SharedDevice ResilientDevice::recover(const nn::IDevice* failingDevice, bool blocking) const {
+nn::GeneralResult<nn::SharedDevice> ResilientDevice::recover(const nn::IDevice* failingDevice,
+ bool blocking) const {
std::lock_guard guard(mMutex);
// Another caller updated the failing device.
@@ -102,13 +113,7 @@
return mDevice;
}
- auto maybeDevice = kMakeDevice(blocking);
- if (!maybeDevice.has_value()) {
- const auto& [message, code] = maybeDevice.error();
- LOG(ERROR) << "Failed to recover dead device with error " << code << ": " << message;
- return mDevice;
- }
- auto device = std::move(maybeDevice).value();
+ auto device = NN_TRY(kMakeDevice(blocking));
// If recovered device has different metadata than what is cached (i.e., because it was
// updated), mark the device as invalid and preserve the cached data.
@@ -117,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;
}
@@ -146,6 +153,10 @@
return getDevice()->getType();
}
+bool ResilientDevice::isUpdatable() const {
+ return getDevice()->isUpdatable();
+}
+
const std::vector<nn::Extension>& ResilientDevice::getSupportedExtensions() const {
return kExtensions;
}
@@ -175,40 +186,50 @@
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, dataCache,
- token](bool blocking) -> nn::GeneralResult<nn::SharedPreparedModel> {
- return device->prepareModelInternal(blocking, model, preference, priority, deadline,
- modelCache, dataCache, token);
+ ResilientPreparedModel::Factory makePreparedModel = [device = std::move(self), model,
+ preference, priority, deadline, modelCache,
+ dataCache, token] {
+ return device->prepareModelInternal(model, preference, priority, deadline, modelCache,
+ 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](bool blocking) -> nn::GeneralResult<nn::SharedPreparedModel> {
- return device->prepareModelFromCacheInternal(blocking, deadline, modelCache, dataCache,
- token);
+ 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](bool blocking) -> nn::GeneralResult<nn::SharedBuffer> {
- return device->allocateInternal(blocking, desc, preparedModels, inputRoles, outputRoles);
+ 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 {
@@ -217,37 +238,34 @@
}
nn::GeneralResult<nn::SharedPreparedModel> ResilientDevice::prepareModelInternal(
- bool blocking, const nn::Model& model, nn::ExecutionPreference preference,
- nn::Priority priority, nn::OptionalTimePoint deadline,
- const std::vector<nn::SharedHandle>& modelCache,
+ 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 (!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);
};
- return protect(*this, fn, blocking);
+ return protect(*this, fn, /*blocking=*/false);
}
nn::GeneralResult<nn::SharedPreparedModel> ResilientDevice::prepareModelFromCacheInternal(
- bool blocking, nn::OptionalTimePoint deadline,
- const std::vector<nn::SharedHandle>& modelCache,
+ nn::OptionalTimePoint deadline, const std::vector<nn::SharedHandle>& modelCache,
const std::vector<nn::SharedHandle>& dataCache, const nn::CacheToken& token) const {
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);
+ return protect(*this, fn, /*blocking=*/false);
}
nn::GeneralResult<nn::SharedBuffer> ResilientDevice::allocateInternal(
- bool blocking, const nn::BufferDesc& desc,
- const std::vector<nn::SharedPreparedModel>& preparedModels,
+ const nn::BufferDesc& desc, const std::vector<nn::SharedPreparedModel>& preparedModels,
const std::vector<nn::BufferRole>& inputRoles,
const std::vector<nn::BufferRole>& outputRoles) const {
if (!isValidInternal()) {
@@ -256,7 +274,7 @@
const auto fn = [&desc, &preparedModels, &inputRoles, &outputRoles](const nn::IDevice& device) {
return device.allocate(desc, preparedModels, inputRoles, outputRoles);
};
- return protect(*this, fn, blocking);
+ return protect(*this, fn, /*blocking=*/false);
}
} // namespace android::hardware::neuralnetworks::utils
diff --git a/neuralnetworks/utils/common/src/ResilientPreparedModel.cpp b/neuralnetworks/utils/common/src/ResilientPreparedModel.cpp
index 1c9ecba..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) {
@@ -36,7 +69,7 @@
return NN_ERROR(nn::ErrorStatus::INVALID_ARGUMENT)
<< "utils::ResilientPreparedModel::create must have non-empty makePreparedModel";
}
- auto preparedModel = NN_TRY(makePreparedModel(/*blocking=*/true));
+ auto preparedModel = NN_TRY(makePreparedModel());
CHECK(preparedModel != nullptr);
return std::make_shared<ResilientPreparedModel>(
PrivateConstructorTag{}, std::move(makePreparedModel), std::move(preparedModel));
@@ -55,31 +88,74 @@
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;
}
nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>>
ResilientPreparedModel::execute(const nn::Request& request, nn::MeasureTiming measure,
const nn::OptionalTimePoint& deadline,
- const nn::OptionalTimeoutDuration& loopTimeoutDuration) const {
- return getPreparedModel()->execute(request, measure, deadline, loopTimeoutDuration);
+ const nn::OptionalDuration& loopTimeoutDuration) const {
+ 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>>
-ResilientPreparedModel::executeFenced(
- const nn::Request& request, const std::vector<nn::SyncFence>& waitFor,
- nn::MeasureTiming measure, const nn::OptionalTimePoint& deadline,
- const nn::OptionalTimeoutDuration& loopTimeoutDuration,
- const nn::OptionalTimeoutDuration& timeoutDurationAfterFence) const {
- return getPreparedModel()->executeFenced(request, waitFor, measure, deadline,
- loopTimeoutDuration, timeoutDurationAfterFence);
+ResilientPreparedModel::executeFenced(const nn::Request& request,
+ const std::vector<nn::SyncFence>& waitFor,
+ nn::MeasureTiming measure,
+ const nn::OptionalTimePoint& deadline,
+ const nn::OptionalDuration& loopTimeoutDuration,
+ const nn::OptionalDuration& timeoutDurationAfterFence) const {
+ 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/nfc/1.0/vts/functional/AndroidTest.xml b/nfc/1.0/vts/functional/AndroidTest.xml
index 364672b..76aca48 100644
--- a/nfc/1.0/vts/functional/AndroidTest.xml
+++ b/nfc/1.0/vts/functional/AndroidTest.xml
@@ -28,6 +28,6 @@
<test class="com.android.tradefed.testtype.GTest" >
<option name="native-test-device-path" value="/data/local/tmp" />
<option name="module-name" value="VtsHalNfcV1_0TargetTest" />
- <option name="native-test-timeout" value="180000"/>
+ <option name="native-test-timeout" value="600000"/>
</test>
</configuration>
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/OWNERS b/oemlock/aidl/vts/OWNERS
new file mode 100644
index 0000000..40d95e4
--- /dev/null
+++ b/oemlock/aidl/vts/OWNERS
@@ -0,0 +1,2 @@
+chengyouho@google.com
+frankwoo@google.com
diff --git a/oemlock/aidl/vts/VtsHalOemLockTargetTest.cpp b/oemlock/aidl/vts/VtsHalOemLockTargetTest.cpp
new file mode 100644
index 0000000..6bf6298
--- /dev/null
+++ b/oemlock/aidl/vts/VtsHalOemLockTargetTest.cpp
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+
+#include <aidl/android/hardware/oemlock/IOemLock.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+using ::aidl::android::hardware::oemlock::IOemLock;
+using ::aidl::android::hardware::oemlock::OemLockSecureStatus;
+
+using ndk::SpAIBinder;
+
+struct OemLockAidlTest : public ::testing::TestWithParam<std::string> {
+ virtual void SetUp() override {
+ oemlock = IOemLock::fromBinder(
+ SpAIBinder(AServiceManager_waitForService(GetParam().c_str())));
+ ASSERT_NE(oemlock, nullptr);
+ }
+
+ virtual void TearDown() override {}
+
+ std::shared_ptr<IOemLock> oemlock;
+};
+
+/*
+ * Check the name can be retrieved
+ */
+TEST_P(OemLockAidlTest, GetName) {
+ std::string name;
+
+ const auto ret = oemlock->getName(&name);
+
+ ASSERT_TRUE(ret.isOk());
+ // Any value acceptable
+};
+
+/*
+ * Check the unlock allowed by device state can be queried
+ */
+TEST_P(OemLockAidlTest, QueryUnlockAllowedByDevice) {
+ bool allowed;
+
+ const auto ret = oemlock->isOemUnlockAllowedByDevice(&allowed);
+
+ ASSERT_TRUE(ret.isOk());
+ // Any value acceptable
+}
+
+/*
+ * Check unlock allowed by device state can be toggled
+ */
+TEST_P(OemLockAidlTest, AllowedByDeviceCanBeToggled) {
+ bool allowed;
+
+ // Get the original state so it can be restored
+ const auto get_ret = oemlock->isOemUnlockAllowedByDevice(&allowed);
+ ASSERT_TRUE(get_ret.isOk());
+ const bool originallyAllowed = allowed;
+
+ // Toggle the state
+ const auto set_ret = oemlock->setOemUnlockAllowedByDevice(!originallyAllowed);
+ ASSERT_TRUE(set_ret.isOk());
+
+ const auto check_set_ret = oemlock->isOemUnlockAllowedByDevice(&allowed);
+ ASSERT_TRUE(check_set_ret.isOk());
+ ASSERT_EQ(allowed, !originallyAllowed);
+
+ // Restore the state
+ const auto restore_ret = oemlock->setOemUnlockAllowedByDevice(originallyAllowed);
+ ASSERT_TRUE(restore_ret.isOk());
+
+ const auto check_restore_ret = oemlock->isOemUnlockAllowedByDevice(&allowed);
+ ASSERT_TRUE(check_restore_ret.isOk());
+ ASSERT_EQ(allowed, originallyAllowed);
+}
+
+/*
+ * Check the unlock allowed by device state can be queried
+ */
+TEST_P(OemLockAidlTest, QueryUnlockAllowedByCarrier) {
+ bool allowed;
+
+ const auto ret = oemlock->isOemUnlockAllowedByCarrier(&allowed);
+
+ ASSERT_TRUE(ret.isOk());
+ // Any value acceptable
+}
+
+/*
+ * Attempt to check unlock allowed by carrier can be toggled
+ *
+ * The implementation may involve a signature which cannot be tested here. That
+ * is a valid implementation so the test will pass. If there is no signature
+ * required, the test will toggle the value.
+ */
+TEST_P(OemLockAidlTest, CarrierUnlock) {
+ const std::vector<uint8_t> noSignature = {};
+ bool allowed;
+ OemLockSecureStatus secure_status;
+
+ // Get the original state so it can be restored
+ const auto get_ret = oemlock->isOemUnlockAllowedByCarrier(&allowed);
+ ASSERT_TRUE(get_ret.isOk());
+ const bool originallyAllowed = allowed;
+
+ if (originallyAllowed) {
+ // Only applied to locked devices
+ return;
+ }
+
+ // Toggle the state
+ const auto set_ret = oemlock->setOemUnlockAllowedByCarrier(!originallyAllowed, noSignature, &secure_status);
+ ASSERT_TRUE(set_ret.isOk());
+ ASSERT_NE(secure_status, OemLockSecureStatus::FAILED);
+ const auto set_status = secure_status;
+
+ const auto check_set_ret = oemlock->isOemUnlockAllowedByCarrier(&allowed);
+ ASSERT_TRUE(check_set_ret.isOk());
+
+ if (set_status == OemLockSecureStatus::INVALID_SIGNATURE) {
+ // Signature is required so we cannot toggle the value in the test, but this is allowed
+ ASSERT_EQ(allowed, originallyAllowed);
+ return;
+ }
+
+ ASSERT_EQ(set_status, OemLockSecureStatus::OK);
+ ASSERT_EQ(allowed, !originallyAllowed);
+
+ // Restore the state
+ const auto restore_ret = oemlock->setOemUnlockAllowedByCarrier(originallyAllowed, noSignature, &secure_status);
+ ASSERT_TRUE(restore_ret.isOk());
+ ASSERT_EQ(secure_status, OemLockSecureStatus::OK);
+
+ const auto check_restore_ret = oemlock->isOemUnlockAllowedByCarrier(&allowed);
+ ASSERT_TRUE(check_restore_ret.isOk());
+ ASSERT_EQ(allowed, originallyAllowed);
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(OemLockAidlTest);
+INSTANTIATE_TEST_SUITE_P(
+ PerInstance, OemLockAidlTest,
+ testing::ValuesIn(android::getAidlHalInstanceNames(IOemLock::descriptor)),
+ android::PrintInstanceNameToString);
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ ABinderProcess_setThreadPoolMaxThreadCount(1);
+ ABinderProcess_startThreadPool();
+ return RUN_ALL_TESTS();
+}
diff --git a/radio/1.0/vts/functional/vts_test_util.h b/radio/1.0/vts/functional/vts_test_util.h
index 1625f11..218e823 100644
--- a/radio/1.0/vts/functional/vts_test_util.h
+++ b/radio/1.0/vts/functional/vts_test_util.h
@@ -35,6 +35,12 @@
static constexpr const char* FEATURE_VOICE_CALL = "android.software.connectionservice";
+static constexpr const char* FEATURE_TELEPHONY = "android.hardware.telephony";
+
+static constexpr const char* FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm";
+
+static constexpr const char* FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma";
+
/*
* Generate random serial number for radio test
*/
diff --git a/radio/1.4/vts/functional/radio_hidl_hal_api.cpp b/radio/1.4/vts/functional/radio_hidl_hal_api.cpp
index 1b254a1..b0b984c 100644
--- a/radio/1.4/vts/functional/radio_hidl_hal_api.cpp
+++ b/radio/1.4/vts/functional/radio_hidl_hal_api.cpp
@@ -34,6 +34,10 @@
if (!deviceSupportsFeature(FEATURE_VOICE_CALL)) {
ALOGI("Skipping emergencyDial because voice call is not supported in device");
return;
+ } else if (!deviceSupportsFeature(FEATURE_TELEPHONY_GSM) &&
+ !deviceSupportsFeature(FEATURE_TELEPHONY_CDMA)) {
+ ALOGI("Skipping emergencyDial because gsm/cdma radio is not supported in device");
+ return;
} else {
ALOGI("Running emergencyDial because voice call is supported in device");
}
@@ -86,6 +90,10 @@
if (!deviceSupportsFeature(FEATURE_VOICE_CALL)) {
ALOGI("Skipping emergencyDial because voice call is not supported in device");
return;
+ } else if (!deviceSupportsFeature(FEATURE_TELEPHONY_GSM) &&
+ !deviceSupportsFeature(FEATURE_TELEPHONY_CDMA)) {
+ ALOGI("Skipping emergencyDial because gsm/cdma radio is not supported in device");
+ return;
} else {
ALOGI("Running emergencyDial because voice call is supported in device");
}
@@ -138,6 +146,10 @@
if (!deviceSupportsFeature(FEATURE_VOICE_CALL)) {
ALOGI("Skipping emergencyDial because voice call is not supported in device");
return;
+ } else if (!deviceSupportsFeature(FEATURE_TELEPHONY_GSM) &&
+ !deviceSupportsFeature(FEATURE_TELEPHONY_CDMA)) {
+ ALOGI("Skipping emergencyDial because gsm/cdma radio is not supported in device");
+ return;
} else {
ALOGI("Running emergencyDial because voice call is supported in device");
}
diff --git a/radio/1.5/vts/functional/radio_hidl_hal_api.cpp b/radio/1.5/vts/functional/radio_hidl_hal_api.cpp
index 7166654..0b49b36 100644
--- a/radio/1.5/vts/functional/radio_hidl_hal_api.cpp
+++ b/radio/1.5/vts/functional/radio_hidl_hal_api.cpp
@@ -236,7 +236,12 @@
ALOGI("setSignalStrengthReportingCriteria_1_5_NGRAN_SSRSRP, rspInfo.error = %s\n",
toString(radioRsp_v1_5->rspInfo.error).c_str());
- ASSERT_TRUE(CheckAnyOfErrors(radioRsp_v1_5->rspInfo.error, {RadioError::NONE}));
+
+ // Allow REQUEST_NOT_SUPPORTED because some non-5G device may not support NGRAN for
+ // setSignalStrengthReportingCriteria_1_5()
+ ASSERT_TRUE(
+ CheckAnyOfErrors(radioRsp_v1_5->rspInfo.error,
+ {RadioError::NONE, RadioError::REQUEST_NOT_SUPPORTED}));
}
/*
@@ -261,7 +266,12 @@
ALOGI("setSignalStrengthReportingCriteria_1_5_NGRAN_SSRSRQ, rspInfo.error = %s\n",
toString(radioRsp_v1_5->rspInfo.error).c_str());
- ASSERT_TRUE(CheckAnyOfErrors(radioRsp_v1_5->rspInfo.error, {RadioError::NONE}));
+
+ // Allow REQUEST_NOT_SUPPORTED because some non-5G device may not support NGRAN for
+ // setSignalStrengthReportingCriteria_1_5()
+ ASSERT_TRUE(
+ CheckAnyOfErrors(radioRsp_v1_5->rspInfo.error,
+ {RadioError::NONE, RadioError::REQUEST_NOT_SUPPORTED}));
}
/*
@@ -307,7 +317,12 @@
ALOGI("setSignalStrengthReportingCriteria_1_5_NGRAN_SSSINR, rspInfo.error = %s\n",
toString(radioRsp_v1_5->rspInfo.error).c_str());
- ASSERT_TRUE(CheckAnyOfErrors(radioRsp_v1_5->rspInfo.error, {RadioError::NONE}));
+
+ // Allow REQUEST_NOT_SUPPORTED because some non-5G device may not support NGRAN for
+ // setSignalStrengthReportingCriteria_1_5()
+ ASSERT_TRUE(
+ CheckAnyOfErrors(radioRsp_v1_5->rspInfo.error,
+ {RadioError::NONE, RadioError::REQUEST_NOT_SUPPORTED}));
}
/*
diff --git a/radio/1.6/IRadio.hal b/radio/1.6/IRadio.hal
index 929c624..3dc80b9 100644
--- a/radio/1.6/IRadio.hal
+++ b/radio/1.6/IRadio.hal
@@ -327,12 +327,26 @@
* @param serial Serial number of request.
* @param networkTypeBitmap a 32-bit bearer bitmap of RadioAccessFamily
*
- * Response callbask is IRadioResponse.setNetworkTypeBitmapResponse()
+ * Response callback is IRadioResponse.setNetworkTypeBitmapResponse()
*/
oneway setAllowedNetworkTypeBitmap(
uint32_t serial, bitfield<RadioAccessFamily> networkTypeBitmap);
/**
+ * Requests bitmap representing the currently allowed network types.
+ *
+ * Requests the bitmap set by the corresponding method
+ * setAllowedNetworkTypeBitmap, which sets a strict set of RATs for the
+ * radio to use. Differs from getPreferredNetworkType and getPreferredNetworkTypeBitmap
+ * in that those request *preferences*.
+ *
+ * @param serial Serial number of request.
+ *
+ * Response callback is IRadioResponse.getNetworkTypeBitmapResponse()
+ */
+ oneway getAllowedNetworkTypeBitmap(uint32_t serial);
+
+ /**
* Control data throttling at modem.
* - DataThrottlingAction:NO_DATA_THROTTLING should clear any existing
* data throttling within the requested completion window.
diff --git a/radio/1.6/IRadioIndication.hal b/radio/1.6/IRadioIndication.hal
index 1b56d40..a53d7c1 100644
--- a/radio/1.6/IRadioIndication.hal
+++ b/radio/1.6/IRadioIndication.hal
@@ -23,6 +23,7 @@
import @1.6::NetworkScanResult;
import @1.6::SignalStrength;
import @1.6::SetupDataCallResult;
+import @1.6::PhysicalChannelConfig;
/**
* Interface declaring unsolicited radio indications.
@@ -101,4 +102,15 @@
* CellInfo.
*/
oneway networkScanResult_1_6(RadioIndicationType type, NetworkScanResult result);
+
+ /**
+ * Indicates physical channel configurations.
+ *
+ * An empty configs list indicates that the radio is in idle mode.
+ *
+ * @param type Type of radio indication
+ * @param configs Vector of PhysicalChannelConfigs
+ */
+ oneway currentPhysicalChannelConfigs_1_6(RadioIndicationType type,
+ vec<PhysicalChannelConfig> configs);
};
diff --git a/radio/1.6/IRadioResponse.hal b/radio/1.6/IRadioResponse.hal
index 0d27479..6ac86c3 100644
--- a/radio/1.6/IRadioResponse.hal
+++ b/radio/1.6/IRadioResponse.hal
@@ -17,6 +17,7 @@
package android.hardware.radio@1.6;
import @1.0::SendSmsResult;
+import @1.4::RadioAccessFamily;
import @1.5::IRadioResponse;
import @1.6::Call;
import @1.6::CellInfo;
@@ -311,6 +312,23 @@
oneway setAllowedNetworkTypeBitmapResponse(RadioResponseInfo info);
/**
+ * Callback of IRadio.getAllowedNetworkTypeBitmap(int, bitfield<RadioAccessFamily>)
+ *
+ * Valid errors returned:
+ * RadioError:NONE
+ * RadioError:RADIO_NOT_AVAILABLE
+ * RadioError:OPERATION_NOT_ALLOWED
+ * RadioError:MODE_NOT_SUPPORTED
+ * RadioError:INTERNAL_ERR
+ * RadioError:INVALID_ARGUMENTS
+ * RadioError:MODEM_ERR
+ * RadioError:REQUEST_NOT_SUPPORTED
+ * RadioError:NO_RESOURCES
+ */
+ oneway getAllowedNetworkTypeBitmapResponse(
+ RadioResponseInfo info, bitfield<RadioAccessFamily> networkTypeBitmap);
+
+ /**
* @param info Response info struct containing response type, serial no. and error
*
* Valid errors returned:
diff --git a/radio/1.6/types.hal b/radio/1.6/types.hal
index f4dc0bd..6dd8315 100644
--- a/radio/1.6/types.hal
+++ b/radio/1.6/types.hal
@@ -23,7 +23,10 @@
import @1.0::RadioError;
import @1.0::RadioResponseType;
import @1.0::RegState;
+import @1.1::EutranBands;
+import @1.1::GeranBands;
import @1.1::ScanStatus;
+import @1.1::UtranBands;
import @1.2::Call;
import @1.2::CellInfoCdma;
import @1.2::CellConnectionStatus;
@@ -41,6 +44,7 @@
import @1.5::CellInfoWcdma;
import @1.5::CellInfoTdscdma;
import @1.5::LinkAddress;
+import @1.5::NgranBands;
import @1.5::RegStateResult.AccessTechnologySpecificInfo.Cdma2000RegistrationInfo;
import @1.5::RegStateResult.AccessTechnologySpecificInfo.EutranRegistrationInfo;
import @1.5::RegistrationFailCause;
@@ -281,7 +285,7 @@
* suggestion. 0 indicates retry should be performed immediately. 0x7fffffffffffffff indicates
* the device should not retry data setup anymore.
*/
- uint64_t suggestedRetryTime;
+ int64_t suggestedRetryTime;
/** Context ID, uniquely identifies this data connection. */
int32_t cid;
@@ -347,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
*/
@@ -733,3 +737,70 @@
*/
string forwardedNumber;
};
+
+struct PhysicalChannelConfig {
+ /** Connection status for cell. Valid values are PRIMARY_SERVING and SECONDARY_SERVING */
+ CellConnectionStatus status;
+
+ /** The radio technology for this physical channel */
+ RadioTechnology rat;
+
+ /** Downlink Absolute Radio Frequency Channel Number */
+ int32_t downlinkChannelNumber;
+
+ /** Uplink Absolute Radio Frequency Channel Number */
+ int32_t uplinkChannelNumber;
+
+ /** Downlink cell bandwidth, in kHz */
+ int32_t cellBandwidthDownlink;
+
+ /** Uplink cell bandwidth, in kHz */
+ int32_t cellBandwidthUplink;
+
+ /**
+ * A list of data calls mapped to this physical channel. The context id must match the cid of
+ * @1.5::SetupDataCallResult. An empty list means the physical channel has no data call mapped
+ * to it.
+ */
+ vec<int32_t> contextIds;
+
+ /**
+ * The physical cell identifier for this cell.
+ *
+ * In UTRAN, this value is primary scrambling code. The range is [0, 511].
+ * Reference: 3GPP TS 25.213 section 5.2.2.
+ *
+ * In EUTRAN, this value is physical layer cell identity. The range is [0, 503].
+ * Reference: 3GPP TS 36.211 section 6.11.
+ *
+ * In 5G RAN, this value is physical layer cell identity. The range is [0, 1007].
+ * Reference: 3GPP TS 38.211 section 7.4.2.1.
+ */
+ uint32_t physicalCellId;
+
+ /**
+ * The frequency band to scan.
+ */
+ safe_union Band {
+ /** Valid only if radioAccessNetwork = GERAN. */
+ GeranBands geranBand;
+ /** Valid only if radioAccessNetwork = UTRAN. */
+ UtranBands utranBand;
+ /** Valid only if radioAccessNetwork = EUTRAN. */
+ EutranBands eutranBand;
+ /** Valid only if radioAccessNetwork = NGRAN. */
+ NgranBands ngranBand;
+ } band;
+};
+
+/**
+ * Extended from @1.5 NgranBands
+ * IRadio 1.6 supports NGRAN bands up to V16.5.0
+ */
+enum NgranBands : @1.5::NgranBands {
+ /** 3GPP TS 38.101-1, Table 5.2-1: FR1 bands */
+ BAND_26 = 26,
+ BAND_46 = 46,
+ BAND_53 = 53,
+ BAND_96 = 96,
+};
diff --git a/radio/1.6/vts/functional/radio_hidl_hal_utils_v1_6.h b/radio/1.6/vts/functional/radio_hidl_hal_utils_v1_6.h
index dc7bad3..5fcfa3b 100644
--- a/radio/1.6/vts/functional/radio_hidl_hal_utils_v1_6.h
+++ b/radio/1.6/vts/functional/radio_hidl_hal_utils_v1_6.h
@@ -793,6 +793,12 @@
Return<void> setAllowedNetworkTypeBitmapResponse(
const ::android::hardware::radio::V1_6::RadioResponseInfo& info);
+ Return<void> getAllowedNetworkTypeBitmapResponse(
+ const ::android::hardware::radio::V1_6::RadioResponseInfo& info,
+ const ::android::hardware::hidl_bitfield<
+ ::android::hardware::radio::V1_4::RadioAccessFamily>
+ networkTypeBitmap);
+
Return<void> setDataThrottlingResponse(
const ::android::hardware::radio::V1_6::RadioResponseInfo& info);
@@ -851,6 +857,11 @@
const ::android::hardware::hidl_vec<::android::hardware::radio::V1_6::CellInfo>&
records);
+ Return<void> currentPhysicalChannelConfigs_1_6(
+ RadioIndicationType type,
+ const ::android::hardware::hidl_vec<
+ ::android::hardware::radio::V1_6::PhysicalChannelConfig>& configs);
+
/* 1.5 Api */
Return<void> uiccApplicationsEnablementChanged(RadioIndicationType type, bool enabled);
diff --git a/radio/1.6/vts/functional/radio_indication.cpp b/radio/1.6/vts/functional/radio_indication.cpp
index bfc54c0..e7a9680 100644
--- a/radio/1.6/vts/functional/radio_indication.cpp
+++ b/radio/1.6/vts/functional/radio_indication.cpp
@@ -30,6 +30,13 @@
return Void();
}
+Return<void> RadioIndication_v1_6::currentPhysicalChannelConfigs_1_6(
+ RadioIndicationType /*type*/,
+ const ::android::hardware::hidl_vec<
+ ::android::hardware::radio::V1_6::PhysicalChannelConfig>& /*configs*/) {
+ return Void();
+}
+
/* 1.5 Apis */
Return<void> RadioIndication_v1_6::uiccApplicationsEnablementChanged(RadioIndicationType /*type*/,
bool /*enabled*/) {
diff --git a/radio/1.6/vts/functional/radio_response.cpp b/radio/1.6/vts/functional/radio_response.cpp
index 4a78f2b..7c5cf6d 100644
--- a/radio/1.6/vts/functional/radio_response.cpp
+++ b/radio/1.6/vts/functional/radio_response.cpp
@@ -1157,6 +1157,14 @@
return Void();
}
+Return<void> RadioResponse_v1_6::getAllowedNetworkTypeBitmapResponse(
+ const ::android::hardware::radio::V1_6::RadioResponseInfo& /*info*/,
+ const ::android::hardware::hidl_bitfield<
+ ::android::hardware::radio::V1_4::RadioAccessFamily>
+ /*networkTypeBitmap*/) {
+ return Void();
+}
+
Return<void> RadioResponse_v1_6::setDataThrottlingResponse(
const ::android::hardware::radio::V1_6::RadioResponseInfo& info) {
rspInfo = info;
diff --git a/radio/config/1.3/Android.bp b/radio/config/1.3/Android.bp
new file mode 100644
index 0000000..ace0de9
--- /dev/null
+++ b/radio/config/1.3/Android.bp
@@ -0,0 +1,21 @@
+// This file is autogenerated by hidl-gen -Landroidbp.
+
+hidl_interface {
+ name: "android.hardware.radio.config@1.3",
+ root: "android.hardware",
+ srcs: [
+ "types.hal",
+ "IRadioConfig.hal",
+ "IRadioConfigResponse.hal",
+ ],
+ interfaces: [
+ "android.hardware.radio.config@1.0",
+ "android.hardware.radio.config@1.1",
+ "android.hardware.radio.config@1.2",
+ "android.hardware.radio@1.0",
+ "android.hardware.radio@1.6",
+ "android.hidl.base@1.0",
+ ],
+ gen_java: true,
+ system_ext_specific: true,
+}
diff --git a/radio/config/1.3/IRadioConfig.hal b/radio/config/1.3/IRadioConfig.hal
new file mode 100644
index 0000000..83bcf92
--- /dev/null
+++ b/radio/config/1.3/IRadioConfig.hal
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *
+ * This interface is used by telephony and telecom to talk to cellular radio for the purpose of
+ * radio configuration, and it is not associated with any specific modem or slot.
+ * All the functions have minimum one parameter:
+ * serial: which corresponds to serial no. of request. Serial numbers must only be memorized for the
+ * duration of a method call. If clients provide colliding serials (including passing the same
+ * serial to different methods), multiple responses (one for each method call) must still be served.
+ */
+
+package android.hardware.radio.config@1.3;
+
+import @1.1::IRadioConfig;
+import IRadioConfigResponse;
+
+interface IRadioConfig extends @1.1::IRadioConfig {
+ /**
+ * Gets the available Radio Hal capabilities on the current device.
+ *
+ * This is called once per device boot up.
+ *
+ * @param serial Serial number of request
+ *
+ * Response callback is
+ * IRadioConfigResponse.getHalDeviceCapabilitiesResponse()
+ */
+ oneway getHalDeviceCapabilities(int32_t serial);
+};
diff --git a/radio/config/1.3/IRadioConfigResponse.hal b/radio/config/1.3/IRadioConfigResponse.hal
new file mode 100644
index 0000000..863754f
--- /dev/null
+++ b/radio/config/1.3/IRadioConfigResponse.hal
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.radio.config@1.3;
+
+import android.hardware.radio@1.6::RadioResponseInfo;
+import @1.2::IRadioConfigResponse;
+import HalDeviceCapabilities;
+
+/**
+ * Interface declaring response functions to solicited radio config requests.
+ */
+interface IRadioConfigResponse extends @1.2::IRadioConfigResponse {
+ /**
+ * @param info Response info struct containing response type, serial no. and error
+ * @param capabilities Capabilities struct containing the capabilities of the
+ * device related to the Radio HAL
+ *
+ * Valid errors returned:
+ * RadioError:NONE
+ * RadioError:RADIO_NOT_AVAILABLE
+ * RadioError:INTERNAL_ERR
+ */
+ oneway getHalDeviceCapabilitiesResponse(RadioResponseInfo info,
+ HalDeviceCapabilities capabilities);
+};
diff --git a/health/1.0/default/libhealthd/healthd_board_default.cpp b/radio/config/1.3/types.hal
similarity index 63%
copy from health/1.0/default/libhealthd/healthd_board_default.cpp
copy to radio/config/1.3/types.hal
index 127f98e..bedb709 100644
--- a/health/1.0/default/libhealthd/healthd_board_default.cpp
+++ b/radio/config/1.3/types.hal
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,9 @@
* limitations under the License.
*/
-#include <healthd/healthd.h>
+package android.hardware.radio.config@1.3;
-void healthd_board_init(struct healthd_config*)
-{
- // use defaults
-}
-
-int healthd_board_battery_update(struct android::BatteryProperties*)
-{
- // return 0 to log periodic polled battery status to kernel log
- return 0;
-}
+/**
+ * Contains the device capabilities with respect to the Radio HAL.
+ */
+struct HalDeviceCapabilities {};
diff --git a/radio/config/1.3/vts/functional/Android.bp b/radio/config/1.3/vts/functional/Android.bp
new file mode 100644
index 0000000..abd081f
--- /dev/null
+++ b/radio/config/1.3/vts/functional/Android.bp
@@ -0,0 +1,39 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_test {
+ name: "VtsHalRadioConfigV1_3TargetTest",
+ defaults: ["VtsHalTargetTestDefaults"],
+ srcs: [
+ "radio_config_hidl_hal_api.cpp",
+ "radio_config_hidl_hal_test.cpp",
+ "radio_config_response.cpp",
+ "radio_config_indication.cpp",
+ "VtsHalRadioConfigV1_3TargetTest.cpp",
+ ],
+ static_libs: [
+ "RadioVtsTestUtilBase",
+ "android.hardware.radio.config@1.0",
+ "android.hardware.radio.config@1.1",
+ "android.hardware.radio.config@1.2",
+ "android.hardware.radio.config@1.3",
+ ],
+ header_libs: ["radio.util.header@1.0"],
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
+}
diff --git a/radio/config/1.3/vts/functional/VtsHalRadioConfigV1_3TargetTest.cpp b/radio/config/1.3/vts/functional/VtsHalRadioConfigV1_3TargetTest.cpp
new file mode 100644
index 0000000..5772d08
--- /dev/null
+++ b/radio/config/1.3/vts/functional/VtsHalRadioConfigV1_3TargetTest.cpp
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <radio_config_hidl_hal_utils.h>
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(RadioConfigHidlTest);
+INSTANTIATE_TEST_SUITE_P(
+ PerInstance, RadioConfigHidlTest,
+ testing::ValuesIn(android::hardware::getAllHalInstanceNames(IRadioConfig::descriptor)),
+ android::hardware::PrintInstanceNameToString);
diff --git a/radio/config/1.3/vts/functional/radio_config_hidl_hal_api.cpp b/radio/config/1.3/vts/functional/radio_config_hidl_hal_api.cpp
new file mode 100644
index 0000000..8df02dd
--- /dev/null
+++ b/radio/config/1.3/vts/functional/radio_config_hidl_hal_api.cpp
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <radio_config_hidl_hal_utils.h>
+
+#define ASSERT_OK(ret) ASSERT_TRUE(ret.isOk())
+
+/*
+ * Test IRadioConfig.getHalDeviceCapabilities()
+ */
+TEST_P(RadioConfigHidlTest, getHalDeviceCapabilities) {
+ const int serial = GetRandomSerialNumber();
+ Return<void> res = radioConfig->getHalDeviceCapabilities(serial);
+ ASSERT_OK(res);
+ ALOGI("getHalDeviceCapabilities, rspInfo.error = %s\n",
+ toString(radioConfigRsp->rspInfo.error).c_str());
+}
diff --git a/radio/config/1.3/vts/functional/radio_config_hidl_hal_test.cpp b/radio/config/1.3/vts/functional/radio_config_hidl_hal_test.cpp
new file mode 100644
index 0000000..de8365a
--- /dev/null
+++ b/radio/config/1.3/vts/functional/radio_config_hidl_hal_test.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <radio_config_hidl_hal_utils.h>
+
+void RadioConfigHidlTest::SetUp() {
+ radioConfig = IRadioConfig::getService(GetParam());
+ if (radioConfig == NULL) {
+ sleep(60);
+ radioConfig = IRadioConfig::getService(GetParam());
+ }
+ ASSERT_NE(nullptr, radioConfig.get());
+
+ radioConfigRsp = new (std::nothrow) RadioConfigResponse(*this);
+ ASSERT_NE(nullptr, radioConfigRsp.get());
+
+ count_ = 0;
+
+ radioConfig->setResponseFunctions(radioConfigRsp, nullptr);
+}
+
+/*
+ * Notify that the response message is received.
+ */
+void RadioConfigHidlTest::notify(int receivedSerial) {
+ std::unique_lock<std::mutex> lock(mtx_);
+ if (serial == receivedSerial) {
+ count_++;
+ cv_.notify_one();
+ }
+}
+
+/*
+ * Wait till the response message is notified or till TIMEOUT_PERIOD.
+ */
+std::cv_status RadioConfigHidlTest::wait() {
+ std::unique_lock<std::mutex> lock(mtx_);
+
+ std::cv_status status = std::cv_status::no_timeout;
+ auto now = std::chrono::system_clock::now();
+ while (count_ == 0) {
+ status = cv_.wait_until(lock, now + std::chrono::seconds(TIMEOUT_PERIOD));
+ if (status == std::cv_status::timeout) {
+ return status;
+ }
+ }
+ count_--;
+ return status;
+}
diff --git a/radio/config/1.3/vts/functional/radio_config_hidl_hal_utils.h b/radio/config/1.3/vts/functional/radio_config_hidl_hal_utils.h
new file mode 100644
index 0000000..439eb70
--- /dev/null
+++ b/radio/config/1.3/vts/functional/radio_config_hidl_hal_utils.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+
+#include <chrono>
+#include <condition_variable>
+#include <mutex>
+
+#include <android/hardware/radio/config/1.1/IRadioConfig.h>
+#include <android/hardware/radio/config/1.1/types.h>
+#include <android/hardware/radio/config/1.2/IRadioConfigIndication.h>
+#include <android/hardware/radio/config/1.2/IRadioConfigResponse.h>
+#include <android/hardware/radio/config/1.2/types.h>
+#include <android/hardware/radio/config/1.3/IRadioConfig.h>
+#include <android/hardware/radio/config/1.3/IRadioConfigResponse.h>
+#include <android/hardware/radio/config/1.3/types.h>
+#include <gtest/gtest.h>
+#include <hidl/GtestPrinter.h>
+#include <hidl/ServiceManagement.h>
+#include <log/log.h>
+
+#include "vts_test_util.h"
+
+using namespace ::android::hardware::radio::config::V1_2;
+
+using ::android::sp;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::radio::config::V1_1::ModemsConfig;
+using ::android::hardware::radio::config::V1_1::PhoneCapability;
+using ::android::hardware::radio::config::V1_2::SimSlotStatus;
+using ::android::hardware::radio::config::V1_3::HalDeviceCapabilities;
+using ::android::hardware::radio::config::V1_3::IRadioConfig;
+using ::android::hardware::radio::V1_0::RadioResponseInfo;
+
+#define TIMEOUT_PERIOD 75
+#define RADIO_SERVICE_NAME "slot1"
+
+class RadioConfigHidlTest;
+
+/* Callback class for radio config response */
+class RadioConfigResponse : public IRadioConfigResponse {
+ protected:
+ RadioConfigHidlTest& parent;
+
+ public:
+ RadioResponseInfo rspInfo;
+ PhoneCapability phoneCap;
+
+ RadioConfigResponse(RadioConfigHidlTest& parent);
+ virtual ~RadioConfigResponse() = default;
+
+ Return<void> getSimSlotsStatusResponse(
+ const RadioResponseInfo& info,
+ const ::android::hardware::hidl_vec<
+ ::android::hardware::radio::config::V1_0::SimSlotStatus>& slotStatus);
+
+ Return<void> getSimSlotsStatusResponse_1_2(
+ const RadioResponseInfo& info,
+ const ::android::hardware::hidl_vec<SimSlotStatus>& slotStatus);
+
+ Return<void> setSimSlotsMappingResponse(const RadioResponseInfo& info);
+
+ Return<void> getPhoneCapabilityResponse(const RadioResponseInfo& info,
+ const PhoneCapability& phoneCapability);
+
+ Return<void> setPreferredDataModemResponse(const RadioResponseInfo& info);
+
+ Return<void> getModemsConfigResponse(const RadioResponseInfo& info,
+ const ModemsConfig& mConfig);
+
+ Return<void> setModemsConfigResponse(const RadioResponseInfo& info);
+
+ Return<void> getHalDeviceCapabilitiesResponse(
+ const ::android::hardware::radio::V1_6::RadioResponseInfo& info,
+ const HalDeviceCapabilities& halDeviceCapabilities);
+};
+
+/* Callback class for radio config indication */
+class RadioConfigIndication : public IRadioConfigIndication {
+ protected:
+ RadioConfigHidlTest& parent;
+
+ public:
+ RadioConfigIndication(RadioConfigHidlTest& parent);
+ virtual ~RadioConfigIndication() = default;
+
+ Return<void> simSlotsStatusChanged_1_2(
+ ::android::hardware::radio::V1_0::RadioIndicationType type,
+ const ::android::hardware::hidl_vec<SimSlotStatus>& slotStatus);
+};
+
+// The main test class for Radio config HIDL.
+class RadioConfigHidlTest : public ::testing::TestWithParam<std::string> {
+ protected:
+ std::mutex mtx_;
+ std::condition_variable cv_;
+ int count_;
+
+ public:
+ virtual void SetUp() override;
+
+ /* Used as a mechanism to inform the test about data/event callback */
+ void notify(int receivedSerial);
+
+ /* Test code calls this function to wait for response */
+ std::cv_status wait();
+
+ void updateSimCardStatus();
+
+ /* Serial number for radio request */
+ int serial;
+
+ /* radio config service handle */
+ sp<IRadioConfig> radioConfig;
+
+ /* radio config response handle */
+ sp<RadioConfigResponse> radioConfigRsp;
+};
diff --git a/radio/config/1.3/vts/functional/radio_config_indication.cpp b/radio/config/1.3/vts/functional/radio_config_indication.cpp
new file mode 100644
index 0000000..6fa443c
--- /dev/null
+++ b/radio/config/1.3/vts/functional/radio_config_indication.cpp
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <radio_config_hidl_hal_utils.h>
+
+RadioConfigIndication::RadioConfigIndication(RadioConfigHidlTest& parent) : parent(parent) {}
+
+Return<void> RadioConfigIndication::simSlotsStatusChanged_1_2(
+ ::android::hardware::radio::V1_0::RadioIndicationType /*type*/,
+ const ::android::hardware::hidl_vec<SimSlotStatus>& /*slotStatus*/) {
+ return Void();
+}
diff --git a/radio/config/1.3/vts/functional/radio_config_response.cpp b/radio/config/1.3/vts/functional/radio_config_response.cpp
new file mode 100644
index 0000000..2a8b28b
--- /dev/null
+++ b/radio/config/1.3/vts/functional/radio_config_response.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <radio_config_hidl_hal_utils.h>
+
+// SimSlotStatus slotStatus;
+
+RadioConfigResponse::RadioConfigResponse(RadioConfigHidlTest& parent) : parent(parent) {}
+
+Return<void> RadioConfigResponse::getSimSlotsStatusResponse(
+ const ::android::hardware::radio::V1_0::RadioResponseInfo& /* info */,
+ const ::android::hardware::hidl_vec<
+ ::android::hardware::radio::config::V1_0::SimSlotStatus>& /* slotStatus */) {
+ return Void();
+}
+
+Return<void> RadioConfigResponse::getSimSlotsStatusResponse_1_2(
+ const ::android::hardware::radio::V1_0::RadioResponseInfo& /* info */,
+ const ::android::hardware::hidl_vec<SimSlotStatus>& /* slotStatus */) {
+ return Void();
+}
+
+Return<void> RadioConfigResponse::setSimSlotsMappingResponse(
+ const ::android::hardware::radio::V1_0::RadioResponseInfo& /* info */) {
+ return Void();
+}
+
+Return<void> RadioConfigResponse::getPhoneCapabilityResponse(
+ const ::android::hardware::radio::V1_0::RadioResponseInfo& info,
+ const PhoneCapability& phoneCapability) {
+ rspInfo = info;
+ phoneCap = phoneCapability;
+ parent.notify(info.serial);
+ return Void();
+}
+
+Return<void> RadioConfigResponse::setPreferredDataModemResponse(
+ const ::android::hardware::radio::V1_0::RadioResponseInfo& /* info */) {
+ return Void();
+}
+
+Return<void> RadioConfigResponse::getModemsConfigResponse(
+ const ::android::hardware::radio::V1_0::RadioResponseInfo& /* info */,
+ const ModemsConfig& /* mConfig */) {
+ return Void();
+}
+
+Return<void> RadioConfigResponse::setModemsConfigResponse(
+ const ::android::hardware::radio::V1_0::RadioResponseInfo& /* info */) {
+ return Void();
+}
+
+Return<void> RadioConfigResponse::getHalDeviceCapabilitiesResponse(
+ const ::android::hardware::radio::V1_6::RadioResponseInfo& /* info */,
+ const ::android::hardware::radio::config::V1_3::HalDeviceCapabilities& /* capabilities */) {
+ return Void();
+}
\ No newline at end of file
diff --git a/security/keymint/aidl/Android.bp b/security/keymint/aidl/Android.bp
index b5adac9..5652827 100644
--- a/security/keymint/aidl/Android.bp
+++ b/security/keymint/aidl/Android.bp
@@ -4,6 +4,9 @@
srcs: [
"android/hardware/security/keymint/*.aidl",
],
+ imports: [
+ "android.hardware.security.secureclock",
+ ],
stability: "vintf",
backend: {
java: {
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Algorithm.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/Algorithm.aidl
index 46e0ae0..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..ad5bf39 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
@@ -22,6 +23,6 @@
long userId;
long authenticatorId;
android.hardware.security.keymint.HardwareAuthenticatorType authenticatorType;
- android.hardware.security.keymint.Timestamp timestamp;
+ android.hardware.security.secureclock.Timestamp timestamp;
byte[] mac;
}
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/HardwareAuthenticatorType.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/HardwareAuthenticatorType.aidl
index aef5ee0..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..c95145d 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
@@ -19,11 +20,10 @@
@VintfStability
interface IKeyMintDevice {
android.hardware.security.keymint.KeyMintHardwareInfo getHardwareInfo();
- android.hardware.security.keymint.VerificationToken verifyAuthorization(in long challenge, in android.hardware.security.keymint.HardwareAuthToken token);
void addRngEntropy(in byte[] data);
- void generateKey(in android.hardware.security.keymint.KeyParameter[] keyParams, out android.hardware.security.keymint.ByteArray generatedKeyBlob, out android.hardware.security.keymint.KeyCharacteristics generatedKeyCharacteristics, out android.hardware.security.keymint.Certificate[] outCertChain);
- void importKey(in android.hardware.security.keymint.KeyParameter[] inKeyParams, in android.hardware.security.keymint.KeyFormat inKeyFormat, in byte[] inKeyData, out android.hardware.security.keymint.ByteArray outImportedKeyBlob, out android.hardware.security.keymint.KeyCharacteristics outImportedKeyCharacteristics, out android.hardware.security.keymint.Certificate[] outCertChain);
- void importWrappedKey(in byte[] inWrappedKeyData, in byte[] inWrappingKeyBlob, in byte[] inMaskingKey, in android.hardware.security.keymint.KeyParameter[] inUnwrappingParams, in long inPasswordSid, in long inBiometricSid, out android.hardware.security.keymint.ByteArray outImportedKeyBlob, out android.hardware.security.keymint.KeyCharacteristics outImportedKeyCharacteristics);
+ 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..e6ab4c8 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
@@ -18,7 +19,7 @@
package android.hardware.security.keymint;
@VintfStability
interface IKeyMintOperation {
- int update(in @nullable android.hardware.security.keymint.KeyParameterArray inParams, in @nullable byte[] input, in @nullable android.hardware.security.keymint.HardwareAuthToken inAuthToken, in @nullable android.hardware.security.keymint.VerificationToken inVerificationToken, out @nullable android.hardware.security.keymint.KeyParameterArray outParams, out @nullable android.hardware.security.keymint.ByteArray output);
- byte[] finish(in @nullable android.hardware.security.keymint.KeyParameterArray inParams, in @nullable byte[] input, in @nullable byte[] inSignature, in @nullable android.hardware.security.keymint.HardwareAuthToken authToken, in @nullable android.hardware.security.keymint.VerificationToken inVerificationToken, out @nullable android.hardware.security.keymint.KeyParameterArray outParams);
+ int update(in @nullable android.hardware.security.keymint.KeyParameterArray inParams, in @nullable byte[] input, in @nullable android.hardware.security.keymint.HardwareAuthToken inAuthToken, in @nullable android.hardware.security.secureclock.TimeStampToken inTimeStampToken, out @nullable android.hardware.security.keymint.KeyParameterArray outParams, out @nullable android.hardware.security.keymint.ByteArray output);
+ byte[] finish(in @nullable android.hardware.security.keymint.KeyParameterArray inParams, in @nullable byte[] input, in @nullable byte[] inSignature, in @nullable android.hardware.security.keymint.HardwareAuthToken authToken, in @nullable android.hardware.security.secureclock.TimeStampToken inTimeStampToken, out @nullable android.hardware.security.keymint.KeyParameterArray outParams);
void abort();
}
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/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/KeyDerivationFunction.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyDerivationFunction.aidl
deleted file mode 100644
index 83b7e6e..0000000
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyDerivationFunction.aidl
+++ /dev/null
@@ -1,27 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.hardware.security.keymint;
-@Backing(type="int") @VintfStability
-enum KeyDerivationFunction {
- NONE = 0,
- RFC5869_SHA256 = 1,
- ISO18033_2_KDF1_SHA1 = 2,
- ISO18033_2_KDF1_SHA256 = 3,
- ISO18033_2_KDF2_SHA1 = 4,
- ISO18033_2_KDF2_SHA256 = 5,
-}
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 91f83e4..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
@@ -16,11 +17,8 @@
// later when a module using the interface is updated, e.g., Mainline modules.
package android.hardware.security.keymint;
-@VintfStability
+@RustDerive(Clone=true, Eq=true, Hash=true, Ord=true, PartialEq=true, PartialOrd=true) @VintfStability
parcelable KeyParameter {
android.hardware.security.keymint.Tag tag;
- boolean boolValue;
- int integer;
- long longInteger;
- byte[] blob;
+ android.hardware.security.keymint.KeyParameterValue value;
}
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
new file mode 100644
index 0000000..6c11a92
--- /dev/null
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/KeyParameterValue.aidl
@@ -0,0 +1,37 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.security.keymint;
+@RustDerive(Clone=true, Eq=true, Hash=true, Ord=true, PartialEq=true, PartialOrd=true) @VintfStability
+union KeyParameterValue {
+ int invalid;
+ android.hardware.security.keymint.Algorithm algorithm;
+ android.hardware.security.keymint.BlockMode blockMode;
+ android.hardware.security.keymint.PaddingMode paddingMode;
+ android.hardware.security.keymint.Digest digest;
+ android.hardware.security.keymint.EcCurve ecCurve;
+ android.hardware.security.keymint.KeyOrigin origin;
+ android.hardware.security.keymint.KeyPurpose keyPurpose;
+ android.hardware.security.keymint.HardwareAuthenticatorType hardwareAuthenticatorType;
+ android.hardware.security.keymint.SecurityLevel securityLevel;
+ boolean boolValue;
+ int integer;
+ long longInteger;
+ long dateTime;
+ byte[] blob;
+}
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/VerificationToken.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/VerificationToken.aidl
deleted file mode 100644
index 5c76816..0000000
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/VerificationToken.aidl
+++ /dev/null
@@ -1,25 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.hardware.security.keymint;
-@VintfStability
-parcelable VerificationToken {
- long challenge;
- android.hardware.security.keymint.Timestamp timestamp;
- android.hardware.security.keymint.SecurityLevel securityLevel;
- byte[] mac;
-}
diff --git a/security/keymint/aidl/android/hardware/security/keymint/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/HardwareAuthToken.aidl b/security/keymint/aidl/android/hardware/security/keymint/HardwareAuthToken.aidl
index 12d615f..1067540 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/HardwareAuthToken.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/HardwareAuthToken.aidl
@@ -16,7 +16,7 @@
package android.hardware.security.keymint;
-import android.hardware.security.keymint.Timestamp;
+import android.hardware.security.secureclock.Timestamp;
import android.hardware.security.keymint.HardwareAuthenticatorType;
/**
diff --git a/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl b/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl
index 4944acb..d5f7a1f 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl
@@ -18,16 +18,15 @@
import android.hardware.security.keymint.BeginResult;
import android.hardware.security.keymint.ByteArray;
-import android.hardware.security.keymint.Certificate;
import android.hardware.security.keymint.HardwareAuthToken;
import android.hardware.security.keymint.IKeyMintOperation;
-import android.hardware.security.keymint.KeyCharacteristics;
+import android.hardware.security.keymint.KeyCreationResult;
import android.hardware.security.keymint.KeyFormat;
import android.hardware.security.keymint.KeyParameter;
import android.hardware.security.keymint.KeyMintHardwareInfo;
import android.hardware.security.keymint.KeyPurpose;
import android.hardware.security.keymint.SecurityLevel;
-import android.hardware.security.keymint.VerificationToken;
+import android.hardware.security.secureclock.TimeStampToken;
/**
* KeyMint device definition.
@@ -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.
@@ -217,34 +223,6 @@
KeyMintHardwareInfo getHardwareInfo();
/**
- * Verify authorizations for another IKeyMintDevice instance.
- *
- * On systems with both a StrongBox and a TEE IKeyMintDevice instance it is sometimes useful
- * to ask the TEE KeyMintDevice to verify authorizations for a key hosted in StrongBox.
- *
- * For every StrongBox operation, Keystore is required to call this method on the TEE KeyMint,
- * passing in the StrongBox key's hardwareEnforced authorization list and the challenge
- * returned by StrongBox begin(). Keystore must then pass the VerificationToken to the
- * subsequent invocations of StrongBox update() and finish().
- *
- * StrongBox implementations must return ErrorCode::UNIMPLEMENTED.
- *
- * @param the challenge returned by StrongBox's keyMint's begin().
- *
- * @param authToken A HardwareAuthToken if needed to authorize key usage.
- *
- * @return error ErrorCode::OK on success or ErrorCode::UNIMPLEMENTED if the KeyMintDevice is
- * a StrongBox. If the IKeyMintDevice cannot verify one or more elements of
- * parametersToVerify it must not return an error code, but just omit the unverified
- * parameter from the VerificationToken.
- *
- * @return token the verification token. See VerificationToken in VerificationToken.aidl for
- * details.
- */
- VerificationToken verifyAuthorization(in long challenge,
- in HardwareAuthToken token);
-
- /**
* Adds entropy to the RNG used by KeyMint. Entropy added through this method must not be the
* only source of entropy used, and a secure mixing function must be used to mix the entropy
* provided by this method with internally-generated entropy. The mixing function must be
@@ -337,38 +315,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 +345,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 +404,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/IKeyMintOperation.aidl b/security/keymint/aidl/android/hardware/security/keymint/IKeyMintOperation.aidl
index 24960cc..8c49602 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/IKeyMintOperation.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/IKeyMintOperation.aidl
@@ -20,7 +20,7 @@
import android.hardware.security.keymint.HardwareAuthToken;
import android.hardware.security.keymint.KeyParameter;
import android.hardware.security.keymint.KeyParameterArray;
-import android.hardware.security.keymint.VerificationToken;
+import android.hardware.security.secureclock.TimeStampToken;
@VintfStability
interface IKeyMintOperation {
@@ -119,10 +119,9 @@
* @param input Data to be processed. Note that update() may or may not consume all of the data
* provided. See return value.
*
- * @param verificationToken Verification token, used to prove that another IKeymasterDevice HAL
- * has verified some parameters, and to deliver the other HAL's current timestamp, if
- * needed. If not provided, all fields must be initialized to zero and vectors must be
- * empty.
+ * @param inTimeStampToken timestamp token, certifies the freshness of an auth token in case
+ * the security domain of this KeyMint instance has a different clock than the
+ * authenticator issuing the auth token.
*
* @return error Returns ErrorCode encountered in keymint as service specific errors. See the
* ErrorCode enum in ErrorCode.aidl.
@@ -141,7 +140,7 @@
int update(in @nullable KeyParameterArray inParams,
in @nullable byte[] input,
in @nullable HardwareAuthToken inAuthToken,
- in @nullable VerificationToken inVerificationToken,
+ in @nullable TimeStampToken inTimeStampToken,
out @nullable KeyParameterArray outParams,
out @nullable ByteArray output);
@@ -241,9 +240,9 @@
*
* @param authToken Authentication token. Can be nullable if not provided.
*
- * @param verificationToken Verification token, used to prove that another IKeyMintDevice HAL
- * has verified some parameters, and to deliver the other HAL's current timestamp, if
- * needed. Can be nullable if not needed.
+ * @param inTimeStampToken timestamp token, certifies the freshness of an auth token in case
+ * the security domain of this KeyMint instance has a different clock than the
+ * authenticator issuing the auth token.
*
* @return outParams Any output parameters generated by finish().
*
@@ -252,7 +251,7 @@
byte[] finish(in @nullable KeyParameterArray inParams, in @nullable byte[] input,
in @nullable byte[] inSignature,
in @nullable HardwareAuthToken authToken,
- in @nullable VerificationToken inVerificationToken,
+ in @nullable TimeStampToken inTimeStampToken,
out @nullable KeyParameterArray outParams);
/**
diff --git a/security/keymint/aidl/android/hardware/security/keymint/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/KeyDerivationFunction.aidl b/security/keymint/aidl/android/hardware/security/keymint/KeyDerivationFunction.aidl
deleted file mode 100644
index e166ab6..0000000
--- a/security/keymint/aidl/android/hardware/security/keymint/KeyDerivationFunction.aidl
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hardware.security.keymint;
-
-/**
- * Key derivation functions, mostly used in ECIES.
- */
-@VintfStability
-@Backing(type="int")
-enum KeyDerivationFunction {
- /** Do not apply a key derivation function; use the raw agreed key */
- NONE = 0,
- /** HKDF defined in RFC 5869 with SHA256 */
- RFC5869_SHA256 = 1,
- /** KDF1 defined in ISO 18033-2 with SHA1 */
- ISO18033_2_KDF1_SHA1 = 2,
- /** KDF1 defined in ISO 18033-2 with SHA256 */
- ISO18033_2_KDF1_SHA256 = 3,
- /** KDF2 defined in ISO 18033-2 with SHA1 */
- ISO18033_2_KDF2_SHA1 = 4,
- /** KDF2 defined in ISO 18033-2 with SHA256 */
- ISO18033_2_KDF2_SHA256 = 5,
-}
diff --git a/security/keymint/aidl/android/hardware/security/keymint/KeyParameter.aidl b/security/keymint/aidl/android/hardware/security/keymint/KeyParameter.aidl
index 938064c..f3ed96b 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/KeyParameter.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/KeyParameter.aidl
@@ -16,40 +16,16 @@
package android.hardware.security.keymint;
-import android.hardware.security.keymint.Algorithm;
-import android.hardware.security.keymint.BlockMode;
-import android.hardware.security.keymint.Digest;
-import android.hardware.security.keymint.EcCurve;
-import android.hardware.security.keymint.HardwareAuthenticatorType;
-import android.hardware.security.keymint.KeyDerivationFunction;
-import android.hardware.security.keymint.KeyOrigin;
-import android.hardware.security.keymint.KeyPurpose;
-import android.hardware.security.keymint.PaddingMode;
-import android.hardware.security.keymint.SecurityLevel;
import android.hardware.security.keymint.Tag;
-
+import android.hardware.security.keymint.KeyParameterValue;
/**
* Identifies the key authorization parameters to be used with keyMint. This is usually
* provided as an array of KeyParameters to IKeyMintDevice or Operation.
- *
- * TODO(seleneh): Union was not supported in aidl when this cl is first drafted. So we just had
- * the Tags, and bool, int, long, int[], and we will cast to the appropate types base on the
- * Tag value. We need to update this defination to distingish Algorithm, BlockMode,
- * PaddingMode, KeyOrigin...etc later, as union support is recently added to aidl.
- * b/173253030
*/
@VintfStability
+@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true)
parcelable KeyParameter {
- /**
- * Identify what type of key parameter this parcelable actually holds, and based on the type
- * of tag is int, long, bool, or byte[], one of the fields below will be referenced.
- */
Tag tag;
-
- boolean boolValue;
- int integer;
- long longInteger;
- // TODO: change this to nullable.
- byte[] blob;
+ KeyParameterValue value;
}
diff --git a/security/keymint/aidl/android/hardware/security/keymint/KeyParameterValue.aidl b/security/keymint/aidl/android/hardware/security/keymint/KeyParameterValue.aidl
new file mode 100644
index 0000000..a4f5154
--- /dev/null
+++ b/security/keymint/aidl/android/hardware/security/keymint/KeyParameterValue.aidl
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.security.keymint;
+
+import android.hardware.security.keymint.Algorithm;
+import android.hardware.security.keymint.BlockMode;
+import android.hardware.security.keymint.Digest;
+import android.hardware.security.keymint.EcCurve;
+import android.hardware.security.keymint.HardwareAuthenticatorType;
+import android.hardware.security.keymint.KeyOrigin;
+import android.hardware.security.keymint.KeyPurpose;
+import android.hardware.security.keymint.PaddingMode;
+import android.hardware.security.keymint.SecurityLevel;
+
+@VintfStability
+@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true)
+union KeyParameterValue {
+
+ /* Represents an invalid value type. */
+ int invalid;
+
+ /* Enum types */
+ Algorithm algorithm;
+ BlockMode blockMode;
+ PaddingMode paddingMode;
+ Digest digest;
+ EcCurve ecCurve;
+ KeyOrigin origin;
+ KeyPurpose keyPurpose;
+ HardwareAuthenticatorType hardwareAuthenticatorType;
+ SecurityLevel securityLevel;
+
+ /* Other types */
+ boolean boolValue; // Always true, if present.
+ int integer;
+ long longInteger;
+ long dateTime;
+
+ byte[] blob;
+}
diff --git a/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl b/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl
index 532bc5d..f92bf00 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl
@@ -46,7 +46,7 @@
*
* Must be hardware-enforced.
*/
- PURPOSE = (2 << 28) | 1, /* TagType:ENUM_REP */
+ PURPOSE = (2 << 28) /* TagType:ENUM_REP */ | 1,
/**
* Tag::ALGORITHM specifies the cryptographic algorithm with which the key is used. This tag
@@ -55,7 +55,7 @@
*
* Must be hardware-enforced.
*/
- ALGORITHM = (1 << 28) | 2, /* TagType:ENUM */
+ ALGORITHM = (1 << 28) /* TagType:ENUM */ | 2,
/**
* Tag::KEY_SIZE pecifies the size, in bits, of the key, measuring in the normal way for the
@@ -67,7 +67,7 @@
*
* Must be hardware-enforced.
*/
- KEY_SIZE = (3 << 28) | 3, /* TagType:UINT */
+ KEY_SIZE = (3 << 28) /* TagType:UINT */ | 3,
/**
* Tag::BLOCK_MODE specifies the block cipher mode(s) with which the key may be used. This tag
@@ -80,8 +80,8 @@
*
* Must be hardware-enforced.
*/
- BLOCK_MODE = (2 << 28) | 4,
- /* BlockMode. */ /* TagType:ENUM_REP */
+ BLOCK_MODE = (2 << 28) /* TagType:ENUM_REP */ | 4,
+
/**
* Tag::DIGEST specifies the digest algorithms that may be used with the key to perform signing
@@ -95,7 +95,7 @@
*
* Must be hardware-enforced.
*/
- DIGEST = (2 << 28) | 5, /* TagType:ENUM_REP */
+ DIGEST = (2 << 28) /* TagType:ENUM_REP */ | 5,
/**
* Tag::PADDING specifies the padding modes that may be used with the key. This tag is relevant
@@ -123,7 +123,7 @@
*
* Must be hardware-enforced.
*/
- PADDING = (2 << 28) | 6, /* TagType:ENUM_REP */
+ PADDING = (2 << 28) /* TagType:ENUM_REP */ | 6,
/**
* Tag::CALLER_NONCE specifies that the caller can provide a nonce for nonce-requiring
@@ -136,7 +136,7 @@
*
* Must be hardware-enforced.
*/
- CALLER_NONCE = (7 << 28) | 7, /* TagType:BOOL */
+ CALLER_NONCE = (7 << 28) /* TagType:BOOL */ | 7,
/**
* Tag::MIN_MAC_LENGTH specifies the minimum length of MAC that can be requested or verified
@@ -149,7 +149,7 @@
*
* Must be hardware-enforced.
*/
- MIN_MAC_LENGTH = (3 << 28) | 8, /* TagType:UINT */
+ MIN_MAC_LENGTH = (3 << 28) /* TagType:UINT */ | 8,
// Tag 9 reserved
@@ -160,7 +160,7 @@
*
* Must be hardware-enforced.
*/
- EC_CURVE = (1 << 28) | 10, /* TagType:ENUM */
+ EC_CURVE = (1 << 28) /* TagType:ENUM */ | 10,
/**
* Tag::RSA_PUBLIC_EXPONENT specifies the value of the public exponent for an RSA key pair.
@@ -174,7 +174,7 @@
*
* Must be hardware-enforced.
*/
- RSA_PUBLIC_EXPONENT = (5 << 28) | 200, /* TagType:ULONG */
+ RSA_PUBLIC_EXPONENT = (5 << 28) /* TagType:ULONG */ | 200,
// Tag 201 reserved
@@ -185,7 +185,23 @@
*
* Must be hardware-enforced.
*/
- INCLUDE_UNIQUE_ID = (7 << 28) | 202, /* TagType:BOOL */
+ 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.
@@ -202,7 +218,7 @@
*
* Must be hardware-enforced.
*/
- BLOB_USAGE_REQUIREMENTS = (1 << 28) | 301, /* TagType:ENUM */
+ BLOB_USAGE_REQUIREMENTS = (1 << 28) /* TagType:ENUM */ | 301,
/**
* Tag::BOOTLOADER_ONLY specifies only the bootloader can use the key.
@@ -212,7 +228,7 @@
*
* Must be hardware-enforced.
*/
- BOOTLOADER_ONLY = (7 << 28) | 302, /* TagType:BOOL */
+ BOOTLOADER_ONLY = (7 << 28) /* TagType:BOOL */ | 302,
/**
* Tag::ROLLBACK_RESISTANCE specifies that the key has rollback resistance, meaning that when
@@ -227,16 +243,16 @@
*
* Must be hardwared-enforced.
*/
- ROLLBACK_RESISTANCE = (7 << 28) | 303, /* TagType:BOOL */
+ ROLLBACK_RESISTANCE = (7 << 28) /* TagType:BOOL */ | 303,
// Reserved for future use.
- HARDWARE_TYPE = (1 << 28) | 304, /* TagType:ENUM */
+ HARDWARE_TYPE = (1 << 28) /* TagType:ENUM */ | 304,
/**
* Keys tagged with EARLY_BOOT_ONLY may only be used, or created, during early boot, until
* IKeyMintDevice::earlyBootEnded() is called.
*/
- EARLY_BOOT_ONLY = (7 << 28) | 305, /* TagType:BOOL */
+ EARLY_BOOT_ONLY = (7 << 28) /* TagType:BOOL */ | 305,
/**
* Tag::ACTIVE_DATETIME specifies the date and time at which the key becomes active, in
@@ -245,8 +261,7 @@
*
* Need not be hardware-enforced.
*/
- ACTIVE_DATETIME = (6 << 28) | 400,
- /* Start of validity. */ /* TagType:DATE */
+ ACTIVE_DATETIME = (6 << 28) /* TagType:DATE */ | 400,
/**
* Tag::ORIGINATION_EXPIRE_DATETIME specifies the date and time at which the key expires for
@@ -258,7 +273,7 @@
*
* Need not be hardware-enforced.
*/
- ORIGINATION_EXPIRE_DATETIME = (6 << 28) | 401, /* TagType:DATE */
+ ORIGINATION_EXPIRE_DATETIME = (6 << 28) /* TagType:DATE */ | 401,
/**
* Tag::USAGE_EXPIRE_DATETIME specifies the date and time at which the key expires for
@@ -270,7 +285,7 @@
*
* Need not be hardware-enforced.
*/
- USAGE_EXPIRE_DATETIME = (6 << 28) | 402, /* TagType:DATE */
+ USAGE_EXPIRE_DATETIME = (6 << 28) /* TagType:DATE */ | 402,
/**
* TODO(seleneh) this tag need to be deleted.
@@ -295,7 +310,7 @@
*
* Must be hardware-enforced.
*/
- MIN_SECONDS_BETWEEN_OPS = (3 << 28) | 403, /* TagType:UINT */
+ MIN_SECONDS_BETWEEN_OPS = (3 << 28) /* TagType:UINT */ | 403,
/**
* Tag::MAX_USES_PER_BOOT specifies the maximum number of times that a key may be used between
@@ -315,14 +330,14 @@
*
* Must be hardware-enforced.
*/
- MAX_USES_PER_BOOT = (3 << 28) | 404, /* TagType:UINT */
+ MAX_USES_PER_BOOT = (3 << 28) /* TagType:UINT */ | 404,
/**
* Tag::USER_ID specifies the ID of the Android user that is permitted to use the key.
*
* Must not be hardware-enforced.
*/
- USER_ID = (3 << 28) | 501, /* TagType:UINT */
+ USER_ID = (3 << 28) /* TagType:UINT */ | 501,
/**
* Tag::USER_SECURE_ID specifies that a key may only be used under a particular secure user
@@ -355,7 +370,7 @@
*
* Must be hardware-enforced.
*/
- USER_SECURE_ID = (10 << 28) | 502, /* TagType:ULONG_REP */
+ USER_SECURE_ID = (10 << 28) /* TagType:ULONG_REP */ | 502,
/**
* Tag::NO_AUTH_REQUIRED specifies that no authentication is required to use this key. This tag
@@ -363,7 +378,7 @@
*
* Must be hardware-enforced.
*/
- NO_AUTH_REQUIRED = (7 << 28) | 503, /* TagType:BOOL */
+ NO_AUTH_REQUIRED = (7 << 28) /* TagType:BOOL */ | 503,
/**
* Tag::USER_AUTH_TYPE specifies the types of user authenticators that may be used to authorize
@@ -382,7 +397,7 @@
*
* Must be hardware-enforced.
*/
- USER_AUTH_TYPE = (1 << 28) | 504, /* TagType:ENUM */
+ USER_AUTH_TYPE = (1 << 28) /* TagType:ENUM */ | 504,
/**
* Tag::AUTH_TIMEOUT specifies the time in seconds for which the key is authorized for use,
@@ -396,7 +411,7 @@
*
* Must be hardware-enforced.
*/
- AUTH_TIMEOUT = (3 << 28) | 505, /* TagType:UINT */
+ AUTH_TIMEOUT = (3 << 28) /* TagType:UINT */ | 505,
/**
* Tag::ALLOW_WHILE_ON_BODY specifies that the key may be used after authentication timeout if
@@ -404,7 +419,7 @@
*
* Cannot be hardware-enforced.
*/
- ALLOW_WHILE_ON_BODY = (7 << 28) | 506, /* TagType:BOOL */
+ ALLOW_WHILE_ON_BODY = (7 << 28) /* TagType:BOOL */ | 506,
/**
* TRUSTED_USER_PRESENCE_REQUIRED is an optional feature that specifies that this key must be
@@ -451,7 +466,7 @@
*
* Must be hardware-enforced.
*/
- TRUSTED_USER_PRESENCE_REQUIRED = (7 << 28) | 507, /* TagType:BOOL */
+ TRUSTED_USER_PRESENCE_REQUIRED = (7 << 28) /* TagType:BOOL */ | 507,
/** Tag::TRUSTED_CONFIRMATION_REQUIRED is only applicable to keys with KeyPurpose SIGN, and
* specifies that this key must not be usable unless the user provides confirmation of the data
@@ -464,7 +479,7 @@
*
* Must be hardware-enforced.
*/
- TRUSTED_CONFIRMATION_REQUIRED = (7 << 28) | 508, /* TagType:BOOL */
+ TRUSTED_CONFIRMATION_REQUIRED = (7 << 28) /* TagType:BOOL */ | 508,
/**
* Tag::UNLOCKED_DEVICE_REQUIRED specifies that the key may only be used when the device is
@@ -472,7 +487,7 @@
*
* Must be software-enforced.
*/
- UNLOCKED_DEVICE_REQUIRED = (7 << 28) | 509, /* TagType:BOOL */
+ UNLOCKED_DEVICE_REQUIRED = (7 << 28) /* TagType:BOOL */ | 509,
/**
* Tag::APPLICATION_ID. When provided to generateKey or importKey, this tag specifies data
@@ -488,7 +503,7 @@
*
* Must never appear in KeyCharacteristics.
*/
- APPLICATION_ID = (9 << 28) | 601, /* TagType:BYTES */
+ APPLICATION_ID = (9 << 28) /* TagType:BYTES */ | 601,
/*
* Semantically unenforceable tags, either because they have no specific meaning or because
@@ -497,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
@@ -509,7 +524,7 @@
*
* Must never appear in KeyCharacteristics.
*/
- APPLICATION_DATA = (9 << 28) | 700, /* TagType:BYTES */
+ APPLICATION_DATA = (9 << 28) /* TagType:BYTES */ | 700,
/**
* Tag::CREATION_DATETIME specifies the date and time the key was created, in milliseconds since
@@ -518,7 +533,7 @@
* Tag::CREATED is informational only, and not enforced by anything. Must be in the
* software-enforced list, if provided.
*/
- CREATION_DATETIME = (6 << 28) | 701, /* TagType:DATE */
+ CREATION_DATETIME = (6 << 28) /* TagType:DATE */ | 701,
/**
* Tag::ORIGIN specifies where the key was created, if known. This tag must not be specified
@@ -527,7 +542,7 @@
*
* Must be hardware-enforced.
*/
- ORIGIN = (1 << 28) | 702, /* TagType:ENUM */
+ ORIGIN = (1 << 28) /* TagType:ENUM */ | 702,
// 703 is unused.
@@ -539,7 +554,7 @@
*
* Must never appear in KeyCharacteristics.
*/
- ROOT_OF_TRUST = (9 << 28) | 704, /* TagType:BYTES */
+ ROOT_OF_TRUST = (9 << 28) /* TagType:BYTES */ | 704,
/**
* Tag::OS_VERSION specifies the system OS version with which the key may be used. This tag is
@@ -562,7 +577,7 @@
*
* Must be hardware-enforced.
*/
- OS_VERSION = (3 << 28) | 705, /* TagType:UINT */
+ OS_VERSION = (3 << 28) /* TagType:UINT */ | 705,
/**
* Tag::OS_PATCHLEVEL specifies the system security patch level with which the key may be used.
@@ -583,7 +598,7 @@
*
* Must be hardware-enforced.
*/
- OS_PATCHLEVEL = (3 << 28) | 706, /* TagType:UINT */
+ OS_PATCHLEVEL = (3 << 28) /* TagType:UINT */ | 706,
/**
* Tag::UNIQUE_ID specifies a unique, time-based identifier. This tag is never provided to or
@@ -617,7 +632,7 @@
*
* Must be hardware-enforced.
*/
- UNIQUE_ID = (9 << 28) | 707, /* TagType:BYTES */
+ UNIQUE_ID = (9 << 28) /* TagType:BYTES */ | 707,
/**
* Tag::ATTESTATION_CHALLENGE is used to deliver a "challenge" value to the attestKey() method,
@@ -626,7 +641,7 @@
*
* Must never appear in KeyCharacteristics.
*/
- ATTESTATION_CHALLENGE = (9 << 28) | 708, /* TagType:BYTES */
+ ATTESTATION_CHALLENGE = (9 << 28) /* TagType:BYTES */ | 708,
/**
* Tag::ATTESTATION_APPLICATION_ID identifies the set of applications which may use a key, used
@@ -652,7 +667,7 @@
*
* Cannot be hardware-enforced.
*/
- ATTESTATION_APPLICATION_ID = (9 << 28) | 709, /* TagType:BYTES */
+ ATTESTATION_APPLICATION_ID = (9 << 28) /* TagType:BYTES */ | 709,
/**
* Tag::ATTESTATION_ID_BRAND provides the device's brand name, as returned by Build.BRAND in
@@ -665,7 +680,7 @@
*
* Must never appear in KeyCharacteristics.
*/
- ATTESTATION_ID_BRAND = (9 << 28) | 710, /* TagType:BYTES */
+ ATTESTATION_ID_BRAND = (9 << 28) /* TagType:BYTES */ | 710,
/**
* Tag::ATTESTATION_ID_DEVICE provides the device's device name, as returned by Build.DEVICE in
@@ -678,7 +693,7 @@
*
* Must never appear in KeyCharacteristics.
*/
- ATTESTATION_ID_DEVICE = (9 << 28) | 711, /* TagType:BYTES */
+ ATTESTATION_ID_DEVICE = (9 << 28) /* TagType:BYTES */ | 711,
/**
* Tag::ATTESTATION_ID_PRODUCT provides the device's product name, as returned by Build.PRODUCT
@@ -691,7 +706,7 @@
*
* Must never appear in KeyCharacteristics.
*/
- ATTESTATION_ID_PRODUCT = (9 << 28) | 712, /* TagType:BYTES */
+ ATTESTATION_ID_PRODUCT = (9 << 28) /* TagType:BYTES */ | 712,
/**
* Tag::ATTESTATION_ID_SERIAL the device's serial number. This field must be set only when
@@ -703,7 +718,7 @@
*
* Must never appear in KeyCharacteristics.
*/
- ATTESTATION_ID_SERIAL = (9 << 28) | 713, /* TagType:BYTES */
+ ATTESTATION_ID_SERIAL = (9 << 28) /* TagType:BYTES */ | 713,
/**
* Tag::ATTESTATION_ID_IMEI provides the IMEIs for all radios on the device to attestKey().
@@ -715,7 +730,7 @@
*
* Must never appear in KeyCharacteristics.
*/
- ATTESTATION_ID_IMEI = (9 << 28) | 714, /* TagType:BYTES */
+ ATTESTATION_ID_IMEI = (9 << 28) /* TagType:BYTES */ | 714,
/**
* Tag::ATTESTATION_ID_MEID provides the MEIDs for all radios on the device to attestKey().
@@ -727,7 +742,7 @@
*
* Must never appear in KeyCharacteristics.
*/
- ATTESTATION_ID_MEID = (9 << 28) | 715, /* TagType:BYTES */
+ ATTESTATION_ID_MEID = (9 << 28) /* TagType:BYTES */ | 715,
/**
* Tag::ATTESTATION_ID_MANUFACTURER provides the device's manufacturer name, as returned by
@@ -740,7 +755,7 @@
*
* Must never appear in KeyCharacteristics.
*/
- ATTESTATION_ID_MANUFACTURER = (9 << 28) | 716, /* TagType:BYTES */
+ ATTESTATION_ID_MANUFACTURER = (9 << 28) /* TagType:BYTES */ | 716,
/**
* Tag::ATTESTATION_ID_MODEL provides the device's model name, as returned by Build.MODEL in
@@ -753,7 +768,7 @@
*
* Must never appear in KeyCharacteristics.
*/
- ATTESTATION_ID_MODEL = (9 << 28) | 717, /* TagType:BYTES */
+ ATTESTATION_ID_MODEL = (9 << 28) /* TagType:BYTES */ | 717,
/**
* Tag::VENDOR_PATCHLEVEL specifies the vendor image security patch level with which the key may
@@ -775,7 +790,7 @@
*
* Must be hardware-enforced.
*/
- VENDOR_PATCHLEVEL = (3 << 28) | 718, /* TagType:UINT */
+ VENDOR_PATCHLEVEL = (3 << 28) /* TagType:UINT */ | 718,
/**
* Tag::BOOT_PATCHLEVEL specifies the boot image (kernel) security patch level with which the
@@ -795,7 +810,7 @@
*
* Must be hardware-enforced.
*/
- BOOT_PATCHLEVEL = (3 << 28) | 719, /* TagType:UINT */
+ BOOT_PATCHLEVEL = (3 << 28) /* TagType:UINT */ | 719,
/**
* DEVICE_UNIQUE_ATTESTATION is an argument to IKeyMintDevice::attestKey(). It indicates that
@@ -811,7 +826,7 @@
* IKeyMintDevice implementations that support device-unique attestation MUST add the
* DEVICE_UNIQUE_ATTESTATION tag to device-unique attestations.
*/
- DEVICE_UNIQUE_ATTESTATION = (7 << 28) | 720, /* TagType:BOOL */
+ DEVICE_UNIQUE_ATTESTATION = (7 << 28) /* TagType:BOOL */ | 720,
/**
* IDENTITY_CREDENTIAL_KEY is never used by IKeyMintDevice, is not a valid argument to key
@@ -819,7 +834,7 @@
* attestation. It is used in attestations produced by the IIdentityCredential HAL when that
* HAL attests to Credential Keys. IIdentityCredential produces KeyMint-style attestations.
*/
- IDENTITY_CREDENTIAL_KEY = (7 << 28) | 721, /* TagType:BOOL */
+ IDENTITY_CREDENTIAL_KEY = (7 << 28) /* TagType:BOOL */ | 721,
/**
* To prevent keys from being compromised if an attacker acquires read access to system / kernel
@@ -836,7 +851,7 @@
* ErrorCode::INVALID_OPERATION is returned when a key with Tag::STORAGE_KEY is provided to
* begin().
*/
- STORAGE_KEY = (7 << 28) | 722, /* TagType:BOOL */
+ STORAGE_KEY = (7 << 28) /* TagType:BOOL */ | 722,
/**
* Tag::ASSOCIATED_DATA Provides "associated data" for AES-GCM encryption or decryption. This
@@ -845,7 +860,7 @@
*
* Must never appear KeyCharacteristics.
*/
- ASSOCIATED_DATA = (9 << 28) | 1000, /* TagType:BYTES */
+ ASSOCIATED_DATA = (9 << 28) /* TagType:BYTES */ | 1000,
/**
* Tag::NONCE is used to provide or return a nonce or Initialization Vector (IV) for AES-GCM,
@@ -860,7 +875,7 @@
*
* Must never appear in KeyCharacteristics.
*/
- NONCE = (9 << 28) | 1001, /* TagType:BYTES */
+ NONCE = (9 << 28) /* TagType:BYTES */ | 1001,
/**
* Tag::MAC_LENGTH provides the requested length of a MAC or GCM authentication tag, in bits.
@@ -871,7 +886,7 @@
*
* Must never appear in KeyCharacteristics.
*/
- MAC_LENGTH = (3 << 28) | 1003, /* TagType:UINT */
+ MAC_LENGTH = (3 << 28) /* TagType:UINT */ | 1003,
/**
* Tag::RESET_SINCE_ID_ROTATION specifies whether the device has been factory reset since the
@@ -879,7 +894,7 @@
*
* Must never appear in KeyCharacteristics.
*/
- RESET_SINCE_ID_ROTATION = (7 << 28) | 1004, /* TagType:BOOL */
+ RESET_SINCE_ID_ROTATION = (7 << 28) /* TagType:BOOL */ | 1004,
/**
* Tag::CONFIRMATION_TOKEN is used to deliver a cryptographic token proving that the user
@@ -888,5 +903,5 @@
*
* Must never appear in KeyCharacteristics.
*/
- CONFIRMATION_TOKEN = (9 << 28) | 1005, /* TagType:BYTES */
+ CONFIRMATION_TOKEN = (9 << 28) /* TagType:BYTES */ | 1005,
}
diff --git a/security/keymint/aidl/android/hardware/security/keymint/VerificationToken.aidl b/security/keymint/aidl/android/hardware/security/keymint/VerificationToken.aidl
deleted file mode 100644
index f76e6a8..0000000
--- a/security/keymint/aidl/android/hardware/security/keymint/VerificationToken.aidl
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hardware.security.keymint;
-
-import android.hardware.security.keymint.SecurityLevel;
-import android.hardware.security.keymint.Timestamp;
-
-/**
- * VerificationToken instances are used for secure environments to authenticate one another.
- *
- * This version of the parcelable currently don't use the parametersVerified field since it's not
- * needed for time-based verification. This can be added in a later version, if needed.
- */
-@VintfStability
-parcelable VerificationToken {
- /**
- * The operation handle, used to ensure freshness.
- */
- long challenge;
-
- /**
- * The current time of the secure environment that generates the VerificationToken. This can be
- * checked against auth tokens generated by the same secure environment, which avoids needing to
- * synchronize clocks.
- */
- Timestamp timestamp;
-
- /**
- * SecurityLevel of the secure environment that generated the token.
- */
- SecurityLevel securityLevel;
-
- /**
- * 32-byte HMAC-SHA256 of the above values, computed as:
- *
- * HMAC(H,
- * "Auth Verification" || challenge || timestamp || securityLevel)
- *
- * where:
- *
- * ``HMAC'' is the shared HMAC key (see computeSharedHmac() in IKeyMint).
- *
- * ``||'' represents concatenation
- *
- * The representation of challenge and timestamp is as 64-bit unsigned integers in big-endian
- * order. securityLevel is represented as a 32-bit unsigned integer in big-endian order.
- */
- byte[] mac;
-}
diff --git a/security/keymint/aidl/default/Android.bp b/security/keymint/aidl/default/Android.bp
index 491a2c1..79697c4 100644
--- a/security/keymint/aidl/default/Android.bp
+++ b/security/keymint/aidl/default/Android.bp
@@ -9,7 +9,7 @@
"-Wextra",
],
shared_libs: [
- "android.hardware.security.keymint-ndk_platform",
+ "android.hardware.security.keymint-unstable-ndk_platform",
"libbase",
"libbinder_ndk",
"libcppbor",
diff --git a/security/keymint/aidl/vts/functional/Android.bp b/security/keymint/aidl/vts/functional/Android.bp
index ef7adb1..17a4613 100644
--- a/security/keymint/aidl/vts/functional/Android.bp
+++ b/security/keymint/aidl/vts/functional/Android.bp
@@ -22,16 +22,16 @@
],
srcs: [
"KeyMintTest.cpp",
- "VerificationTokenTest.cpp",
],
shared_libs: [
- "libbinder",
+ "libbinder_ndk",
"libcrypto",
"libkeymint",
"libkeymint_support",
],
static_libs: [
- "android.hardware.security.keymint-cpp",
+ "android.hardware.security.keymint-unstable-ndk_platform",
+ "android.hardware.security.secureclock-unstable-ndk_platform",
"libcppbor_external",
"libkeymint_vts_test_utils",
],
@@ -54,13 +54,14 @@
".",
],
shared_libs: [
- "libbinder",
+ "libbinder_ndk",
"libcrypto",
"libkeymint",
"libkeymint_support",
],
static_libs: [
- "android.hardware.security.keymint-cpp",
+ "android.hardware.security.keymint-unstable-ndk_platform",
+ "android.hardware.security.secureclock-unstable-ndk_platform",
"libcppbor",
],
}
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 ea3a329..766c02d 100644
--- a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp
+++ b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp
@@ -17,14 +17,16 @@
#include "KeyMintAidlTestBase.h"
#include <chrono>
+#include <unordered_set>
#include <vector>
#include <android-base/logging.h>
+#include <android/binder_manager.h>
#include <keymint_support/key_param_output.h>
#include <keymint_support/keymint_utils.h>
-namespace android::hardware::security::keymint {
+namespace aidl::android::hardware::security::keymint {
using namespace std::literals::chrono_literals;
using std::endl;
@@ -35,26 +37,54 @@
os << "(Empty)" << ::std::endl;
else {
os << "\n";
- for (size_t i = 0; i < set.size(); ++i) os << set[i] << ::std::endl;
+ for (auto& entry : set) os << entry << ::std::endl;
}
return os;
}
namespace test {
-ErrorCode KeyMintAidlTestBase::GetReturnErrorCode(Status result) {
+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;
- if (result.exceptionCode() == binder::Status::EX_SERVICE_SPECIFIC) {
- return static_cast<ErrorCode>(result.serviceSpecificErrorCode());
+ if (result.getExceptionCode() == EX_SERVICE_SPECIFIC) {
+ return static_cast<ErrorCode>(result.getServiceSpecificError());
}
return ErrorCode::UNKNOWN_ERROR;
}
-void KeyMintAidlTestBase::InitializeKeyMint(sp<IKeyMintDevice> keyMint) {
+void KeyMintAidlTestBase::InitializeKeyMint(std::shared_ptr<IKeyMintDevice> keyMint) {
ASSERT_NE(keyMint, nullptr);
- keymint_ = keyMint;
+ keymint_ = std::move(keyMint);
KeyMintHardwareInfo info;
ASSERT_TRUE(keymint_->getHardwareInfo(&info).isOk());
@@ -68,40 +98,50 @@
}
void KeyMintAidlTestBase::SetUp() {
- InitializeKeyMint(
- android::waitForDeclaredService<IKeyMintDevice>(String16(GetParam().c_str())));
+ if (AServiceManager_isDeclared(GetParam().c_str())) {
+ ::ndk::SpAIBinder binder(AServiceManager_waitForService(GetParam().c_str()));
+ InitializeKeyMint(IKeyMintDevice::fromBinder(binder));
+ } else {
+ InitializeKeyMint(nullptr);
+ }
}
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_PRED2(KeyCharacteristicsBasicallyValid, SecLevel(),
+ creationResult.keyCharacteristics);
+ EXPECT_GT(creationResult.keyBlob.size(), 0);
+ *key_blob = std::move(creationResult.keyBlob);
+ *key_characteristics = std::move(creationResult.keyCharacteristics);
+ cert_chain_ = std::move(creationResult.certificateChain);
+
+ auto algorithm = key_desc.GetTagValue(TAG_ALGORITHM);
+ EXPECT_TRUE(algorithm);
+ if (algorithm &&
+ (algorithm.value() == Algorithm::RSA || algorithm.value() == Algorithm::EC)) {
+ EXPECT_GE(cert_chain_.size(), 1);
+ if (key_desc.Contains(TAG_ATTESTATION_CHALLENGE)) EXPECT_GT(cert_chain_.size(), 1);
+ } else {
+ // For symmetric keys there should be no certificates.
+ EXPECT_EQ(cert_chain_.size(), 0);
}
- EXPECT_GT(keyChar->softwareEnforced.size(), 0);
- // TODO(seleneh) in a later version where we return @nullable
- // single Certificate, check non-null single certificate is always
- // non-empty.
- *keyBlob = blob.data;
}
return GetReturnErrorCode(result);
@@ -113,25 +153,37 @@
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_PRED2(KeyCharacteristicsBasicallyValid, SecLevel(),
+ creationResult.keyCharacteristics);
+ EXPECT_GT(creationResult.keyBlob.size(), 0);
+
+ *key_blob = std::move(creationResult.keyBlob);
+ *key_characteristics = std::move(creationResult.keyCharacteristics);
+ cert_chain_ = std::move(creationResult.certificateChain);
+
+ auto algorithm = key_desc.GetTagValue(TAG_ALGORITHM);
+ EXPECT_TRUE(algorithm);
+ if (algorithm &&
+ (algorithm.value() == Algorithm::RSA || algorithm.value() == Algorithm::EC)) {
+ EXPECT_GE(cert_chain_.size(), 1);
+ if (key_desc.Contains(TAG_ATTESTATION_CHALLENGE)) EXPECT_GT(cert_chain_.size(), 1);
+ } else {
+ // For symmetric keys there should be no certificates.
+ EXPECT_EQ(cert_chain_.size(), 0);
}
- EXPECT_GT(key_characteristics->softwareEnforced.size(), 0);
- *key_blob = blob.data;
}
return GetReturnErrorCode(result);
@@ -146,25 +198,39 @@
const AuthorizationSet& wrapping_key_desc,
string masking_key,
const AuthorizationSet& unwrapping_params) {
- Status result;
EXPECT_EQ(ErrorCode::OK, ImportKey(wrapping_key_desc, KeyFormat::PKCS8, wrapping_key));
- ByteArray outBlob;
- key_characteristics_.softwareEnforced.clear();
- key_characteristics_.hardwareEnforced.clear();
+ key_characteristics_.clear();
- result = keymint_->importWrappedKey(vector<uint8_t>(wrapped_key.begin(), wrapped_key.end()),
- key_blob_,
- vector<uint8_t>(masking_key.begin(), masking_key.end()),
- unwrapping_params.vector_data(), 0 /* passwordSid */,
- 0 /* biometricSid */, &outBlob, &key_characteristics_);
+ KeyCreationResult creationResult;
+ Status result = keymint_->importWrappedKey(
+ vector<uint8_t>(wrapped_key.begin(), wrapped_key.end()), key_blob_,
+ vector<uint8_t>(masking_key.begin(), masking_key.end()),
+ unwrapping_params.vector_data(), 0 /* passwordSid */, 0 /* biometricSid */,
+ &creationResult);
if (result.isOk()) {
- key_blob_ = outBlob.data;
- if (SecLevel() != SecurityLevel::SOFTWARE) {
- EXPECT_GT(key_characteristics_.hardwareEnforced.size(), 0);
+ EXPECT_PRED2(KeyCharacteristicsBasicallyValid, SecLevel(),
+ creationResult.keyCharacteristics);
+ EXPECT_GT(creationResult.keyBlob.size(), 0);
+
+ key_blob_ = std::move(creationResult.keyBlob);
+ key_characteristics_ = std::move(creationResult.keyCharacteristics);
+ cert_chain_ = std::move(creationResult.certificateChain);
+
+ AuthorizationSet allAuths;
+ for (auto& entry : key_characteristics_) {
+ allAuths.push_back(AuthorizationSet(entry.authorizations));
}
- EXPECT_GT(key_characteristics_.softwareEnforced.size(), 0);
+ auto algorithm = allAuths.GetTagValue(TAG_ALGORITHM);
+ EXPECT_TRUE(algorithm);
+ if (algorithm &&
+ (algorithm.value() == Algorithm::RSA || algorithm.value() == Algorithm::EC)) {
+ EXPECT_GE(cert_chain_.size(), 1);
+ } else {
+ // For symmetric keys there should be no certificates.
+ EXPECT_EQ(cert_chain_.size(), 0);
+ }
}
return GetReturnErrorCode(result);
@@ -176,7 +242,7 @@
*key_blob = vector<uint8_t>();
}
- EXPECT_TRUE(result.isOk()) << result.serviceSpecificErrorCode() << endl;
+ EXPECT_TRUE(result.isOk()) << result.getServiceSpecificError() << endl;
return GetReturnErrorCode(result);
}
@@ -186,7 +252,7 @@
ErrorCode KeyMintAidlTestBase::DeleteAllKeys() {
Status result = keymint_->deleteAllKeys();
- EXPECT_TRUE(result.isOk()) << result.serviceSpecificErrorCode() << endl;
+ EXPECT_TRUE(result.isOk()) << result.getServiceSpecificError() << endl;
return GetReturnErrorCode(result);
}
@@ -201,7 +267,8 @@
ErrorCode KeyMintAidlTestBase::Begin(KeyPurpose purpose, const vector<uint8_t>& key_blob,
const AuthorizationSet& in_params,
- AuthorizationSet* out_params, sp<IKeyMintOperation>& op) {
+ AuthorizationSet* out_params,
+ std::shared_ptr<IKeyMintOperation>& op) {
SCOPED_TRACE("Begin");
Status result;
BeginResult out;
@@ -326,7 +393,7 @@
output->append(oPut.begin(), oPut.end());
}
- op_.clear(); // So dtor doesn't Abort().
+ op_.reset();
return GetReturnErrorCode(result);
}
@@ -358,7 +425,7 @@
return result;
}
-ErrorCode KeyMintAidlTestBase::Abort(const sp<IKeyMintOperation>& op) {
+ErrorCode KeyMintAidlTestBase::Abort(const std::shared_ptr<IKeyMintOperation>& op) {
SCOPED_TRACE("Abort");
EXPECT_NE(op, nullptr);
@@ -368,7 +435,7 @@
Status retval = op->abort();
EXPECT_TRUE(retval.isOk());
- return static_cast<ErrorCode>(retval.serviceSpecificErrorCode());
+ return static_cast<ErrorCode>(retval.getServiceSpecificError());
}
ErrorCode KeyMintAidlTestBase::Abort() {
@@ -380,14 +447,14 @@
}
Status retval = op_->abort();
- return static_cast<ErrorCode>(retval.serviceSpecificErrorCode());
+ return static_cast<ErrorCode>(retval.getServiceSpecificError());
}
void KeyMintAidlTestBase::AbortIfNeeded() {
SCOPED_TRACE("AbortIfNeeded");
if (op_) {
EXPECT_EQ(ErrorCode::OK, Abort());
- op_.clear();
+ op_.reset();
}
}
@@ -522,7 +589,7 @@
AuthorizationSet finish_out_params;
EXPECT_EQ(ErrorCode::OK, Finish(finish_params, message.substr(consumed), signature,
&finish_out_params, &output));
- op_.clear();
+ op_.reset();
EXPECT_TRUE(output.empty());
}
@@ -571,8 +638,8 @@
string ciphertext = EncryptMessage(message, params, &out_params);
EXPECT_EQ(1U, out_params.size());
auto ivVal = out_params.GetTagValue(TAG_NONCE);
- EXPECT_TRUE(ivVal.isOk());
- if (ivVal.isOk()) *iv_out = ivVal.value();
+ EXPECT_TRUE(ivVal);
+ if (ivVal) *iv_out = *ivVal;
return ciphertext;
}
@@ -748,6 +815,33 @@
return {};
}
+static const vector<KeyParameter> kEmptyAuthList{};
+
+const vector<KeyParameter>& KeyMintAidlTestBase::SecLevelAuthorizations(
+ const vector<KeyCharacteristics>& key_characteristics) {
+ auto found = std::find_if(key_characteristics.begin(), key_characteristics.end(),
+ [this](auto& entry) { return entry.securityLevel == SecLevel(); });
+ return (found == key_characteristics.end()) ? kEmptyAuthList : found->authorizations;
+}
+
+const vector<KeyParameter>& KeyMintAidlTestBase::HwEnforcedAuthorizations(
+ const vector<KeyCharacteristics>& key_characteristics) {
+ auto found =
+ std::find_if(key_characteristics.begin(), key_characteristics.end(), [](auto& entry) {
+ return entry.securityLevel == SecurityLevel::STRONGBOX ||
+ entry.securityLevel == SecurityLevel::TRUSTED_ENVIRONMENT;
+ });
+ return (found == key_characteristics.end()) ? kEmptyAuthList : found->authorizations;
+}
+
+const vector<KeyParameter>& KeyMintAidlTestBase::SwEnforcedAuthorizations(
+ const vector<KeyCharacteristics>& key_characteristics) {
+ auto found = std::find_if(
+ key_characteristics.begin(), key_characteristics.end(),
+ [](auto& entry) { return entry.securityLevel == SecurityLevel::SOFTWARE; });
+ return (found == key_characteristics.end()) ? kEmptyAuthList : found->authorizations;
+}
+
} // namespace test
-} // namespace android::hardware::security::keymint
+} // 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 76effcf..c1a1dd9 100644
--- a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h
+++ b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h
@@ -14,33 +14,33 @@
* limitations under the License.
*/
-#ifndef VTS_KEYMINT_AIDL_TEST_UTILS_H
-#define VTS_KEYMINT_AIDL_TEST_UTILS_H
-
#pragma once
#include <aidl/Gtest.h>
#include <aidl/Vintf.h>
-#include <android/hardware/security/keymint/ErrorCode.h>
-#include <android/hardware/security/keymint/IKeyMintDevice.h>
#include <binder/IServiceManager.h>
#include <binder/ProcessState.h>
#include <gtest/gtest.h>
+#include <aidl/android/hardware/security/keymint/ErrorCode.h>
+#include <aidl/android/hardware/security/keymint/IKeyMintDevice.h>
+
#include <keymint_support/authorization_set.h>
-namespace android::hardware::security::keymint::test {
+namespace aidl::android::hardware::security::keymint {
+
+::std::ostream& operator<<(::std::ostream& os, const AuthorizationSet& set);
+
+namespace test {
using ::android::sp;
-using binder::Status;
+using Status = ::ndk::ScopedAStatus;
using ::std::shared_ptr;
using ::std::string;
using ::std::vector;
constexpr uint64_t kOpHandleSentinel = 0xFFFFFFFFFFFFFFFF;
-::std::ostream& operator<<(::std::ostream& os, const AuthorizationSet& set);
-
class KeyMintAidlTestBase : public ::testing::TestWithParam<string> {
public:
void SetUp() override;
@@ -51,20 +51,20 @@
AbortIfNeeded();
}
- void InitializeKeyMint(sp<IKeyMintDevice> keyMint);
+ void InitializeKeyMint(std::shared_ptr<IKeyMintDevice> keyMint);
IKeyMintDevice& keyMint() { return *keymint_; }
uint32_t os_version() { return os_version_; }
uint32_t os_patch_level() { return os_patch_level_; }
- ErrorCode GetReturnErrorCode(Status result);
+ 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);
@@ -82,7 +82,7 @@
ErrorCode Begin(KeyPurpose purpose, const vector<uint8_t>& key_blob,
const AuthorizationSet& in_params, AuthorizationSet* out_params,
- sp<IKeyMintOperation>& op);
+ std::shared_ptr<IKeyMintOperation>& op);
ErrorCode Begin(KeyPurpose purpose, const vector<uint8_t>& key_blob,
const AuthorizationSet& in_params, AuthorizationSet* out_params);
ErrorCode Begin(KeyPurpose purpose, const AuthorizationSet& in_params,
@@ -100,7 +100,7 @@
ErrorCode Finish(string* output) { return Finish(string(), output); }
ErrorCode Abort();
- ErrorCode Abort(const sp<IKeyMintOperation>& op);
+ ErrorCode Abort(const shared_ptr<IKeyMintOperation>& op);
void AbortIfNeeded();
string ProcessMessage(const vector<uint8_t>& key_blob, KeyPurpose operation,
@@ -149,8 +149,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);
@@ -161,17 +161,27 @@
vector<Digest> ValidDigests(bool withNone, bool withMD5);
static vector<string> build_params() {
- auto params = android::getAidlHalInstanceNames(IKeyMintDevice::descriptor);
+ auto params = ::android::getAidlHalInstanceNames(IKeyMintDevice::descriptor);
return params;
}
- sp<IKeyMintOperation> op_;
- vector<Certificate> certChain_;
+ std::shared_ptr<IKeyMintOperation> op_;
+ vector<Certificate> cert_chain_;
vector<uint8_t> key_blob_;
- KeyCharacteristics key_characteristics_;
+ vector<KeyCharacteristics> key_characteristics_;
+
+ const vector<KeyParameter>& SecLevelAuthorizations(
+ const vector<KeyCharacteristics>& key_characteristics);
+ inline const vector<KeyParameter>& SecLevelAuthorizations() {
+ return SecLevelAuthorizations(key_characteristics_);
+ }
+ const vector<KeyParameter>& HwEnforcedAuthorizations(
+ const vector<KeyCharacteristics>& key_characteristics);
+ const vector<KeyParameter>& SwEnforcedAuthorizations(
+ const vector<KeyCharacteristics>& key_characteristics);
private:
- sp<IKeyMintDevice> keymint_;
+ std::shared_ptr<IKeyMintDevice> keymint_;
uint32_t os_version_;
uint32_t os_patch_level_;
@@ -184,8 +194,8 @@
#define INSTANTIATE_KEYMINT_AIDL_TEST(name) \
INSTANTIATE_TEST_SUITE_P(PerInstance, name, \
testing::ValuesIn(KeyMintAidlTestBase::build_params()), \
- android::PrintInstanceNameToString)
+ ::android::PrintInstanceNameToString)
-} // namespace android::hardware::security::keymint::test
+} // namespace test
-#endif // VTS_KEYMINT_AIDL_TEST_UTILS_H
+} // namespace aidl::android::hardware::security::keymint
diff --git a/security/keymint/aidl/vts/functional/KeyMintTest.cpp b/security/keymint/aidl/vts/functional/KeyMintTest.cpp
index f9423a2..e7c94f3 100644
--- a/security/keymint/aidl/vts/functional/KeyMintTest.cpp
+++ b/security/keymint/aidl/vts/functional/KeyMintTest.cpp
@@ -26,7 +26,7 @@
#include <cutils/properties.h>
-#include <android/hardware/security/keymint/KeyFormat.h>
+#include <aidl/android/hardware/security/keymint/KeyFormat.h>
#include <keymint_support/attestation_record.h>
#include <keymint_support/key_param_output.h>
@@ -37,50 +37,51 @@
static bool arm_deleteAllKeys = false;
static bool dump_Attestations = false;
-using android::hardware::security::keymint::AuthorizationSet;
-using android::hardware::security::keymint::KeyCharacteristics;
-using android::hardware::security::keymint::KeyFormat;
+using aidl::android::hardware::security::keymint::AuthorizationSet;
+using aidl::android::hardware::security::keymint::KeyCharacteristics;
+using aidl::android::hardware::security::keymint::KeyFormat;
-namespace android::hardware::security::keymint {
+namespace aidl::android::hardware::security::keymint {
bool operator==(const keymint::AuthorizationSet& a, const keymint::AuthorizationSet& b) {
return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin());
}
-} // namespace android::hardware::security::keymint
+} // namespace aidl::android::hardware::security::keymint
namespace std {
-using namespace android::hardware::security::keymint;
+using namespace aidl::android::hardware::security::keymint;
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;
}
};
} // namespace std
-namespace android::hardware::security::keymint::test {
+namespace aidl::android::hardware::security::keymint::test {
namespace {
template <TagType tag_type, Tag tag, typename ValueT>
bool contains(vector<KeyParameter>& set, TypedTag<tag_type, tag> ttag, ValueT expected_value) {
auto it = std::find_if(set.begin(), set.end(), [&](const KeyParameter& param) {
- return param.tag == tag && accessTagValue(ttag, param) == expected_value;
+ if (auto p = authorizationValue(ttag, param)) {
+ return *p == expected_value;
+ }
+ return false;
});
return (it != set.end());
}
@@ -179,9 +180,280 @@
void operator()(RSA* p) { RSA_free(p); }
};
-/* TODO(seleneh) add attestation verification codes like verify_chain() and
- * attestation tests after we decided on the keymint 1 attestation changes.
- */
+char nibble2hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+
+string bin2hex(const vector<uint8_t>& data) {
+ string retval;
+ retval.reserve(data.size() * 2 + 1);
+ for (uint8_t byte : data) {
+ retval.push_back(nibble2hex[0x0F & (byte >> 4)]);
+ retval.push_back(nibble2hex[0x0F & byte]);
+ }
+ return retval;
+}
+
+X509* parse_cert_blob(const vector<uint8_t>& blob) {
+ const uint8_t* p = blob.data();
+ return d2i_X509(nullptr, &p, blob.size());
+}
+
+bool verify_chain(const vector<Certificate>& chain) {
+ for (size_t i = 0; i < chain.size(); ++i) {
+ X509_Ptr key_cert(parse_cert_blob(chain[i].encodedCertificate));
+ X509_Ptr signing_cert;
+ if (i < chain.size() - 1) {
+ signing_cert.reset(parse_cert_blob(chain[i + 1].encodedCertificate));
+ } else {
+ signing_cert.reset(parse_cert_blob(chain[i].encodedCertificate));
+ }
+ EXPECT_TRUE(!!key_cert.get() && !!signing_cert.get());
+ if (!key_cert.get() || !signing_cert.get()) return false;
+
+ EVP_PKEY_Ptr signing_pubkey(X509_get_pubkey(signing_cert.get()));
+ EXPECT_TRUE(!!signing_pubkey.get());
+ if (!signing_pubkey.get()) return false;
+
+ EXPECT_EQ(1, X509_verify(key_cert.get(), signing_pubkey.get()))
+ << "Verification of certificate " << i << " failed "
+ << "OpenSSL error string: " << ERR_error_string(ERR_get_error(), NULL);
+
+ char* cert_issuer = //
+ X509_NAME_oneline(X509_get_issuer_name(key_cert.get()), nullptr, 0);
+ char* signer_subj =
+ X509_NAME_oneline(X509_get_subject_name(signing_cert.get()), nullptr, 0);
+ EXPECT_STREQ(cert_issuer, signer_subj) << "Cert " << i << " has wrong issuer.";
+ if (i == 0) {
+ char* cert_sub = X509_NAME_oneline(X509_get_subject_name(key_cert.get()), nullptr, 0);
+ EXPECT_STREQ("/CN=Android Keystore Key", cert_sub)
+ << "Cert " << i << " has wrong subject.";
+ OPENSSL_free(cert_sub);
+ }
+
+ OPENSSL_free(cert_issuer);
+ OPENSSL_free(signer_subj);
+
+ if (dump_Attestations) std::cout << bin2hex(chain[i].encodedCertificate) << std::endl;
+ }
+
+ return true;
+}
+
+// Extract attestation record from cert. Returned object is still part of cert; don't free it
+// separately.
+ASN1_OCTET_STRING* get_attestation_record(X509* certificate) {
+ ASN1_OBJECT_Ptr oid(OBJ_txt2obj(kAttestionRecordOid, 1 /* dotted string format */));
+ EXPECT_TRUE(!!oid.get());
+ if (!oid.get()) return nullptr;
+
+ int location = X509_get_ext_by_OBJ(certificate, oid.get(), -1 /* search from beginning */);
+ EXPECT_NE(-1, location) << "Attestation extension not found in certificate";
+ if (location == -1) return nullptr;
+
+ X509_EXTENSION* attest_rec_ext = X509_get_ext(certificate, location);
+ EXPECT_TRUE(!!attest_rec_ext)
+ << "Found attestation extension but couldn't retrieve it? Probably a BoringSSL bug.";
+ if (!attest_rec_ext) return nullptr;
+
+ ASN1_OCTET_STRING* attest_rec = X509_EXTENSION_get_data(attest_rec_ext);
+ EXPECT_TRUE(!!attest_rec) << "Attestation extension contained no data";
+ return attest_rec;
+}
+
+bool tag_in_list(const KeyParameter& entry) {
+ // Attestations don't contain everything in key authorization lists, so we need to filter
+ // the key lists to produce the lists that we expect to match the attestations.
+ auto tag_list = {
+ Tag::BLOB_USAGE_REQUIREMENTS, //
+ Tag::CREATION_DATETIME, //
+ Tag::EC_CURVE,
+ Tag::HARDWARE_TYPE,
+ Tag::INCLUDE_UNIQUE_ID,
+ };
+ return std::find(tag_list.begin(), tag_list.end(), entry.tag) != tag_list.end();
+}
+
+AuthorizationSet filtered_tags(const AuthorizationSet& set) {
+ AuthorizationSet filtered;
+ std::remove_copy_if(set.begin(), set.end(), std::back_inserter(filtered), tag_in_list);
+ return filtered;
+}
+
+bool avb_verification_enabled() {
+ char value[PROPERTY_VALUE_MAX];
+ return property_get("ro.boot.vbmeta.device_state", value, "") != 0;
+}
+
+bool verify_attestation_record(const string& challenge, //
+ const string& app_id, //
+ AuthorizationSet expected_sw_enforced, //
+ AuthorizationSet expected_hw_enforced, //
+ SecurityLevel security_level,
+ const vector<uint8_t>& attestation_cert) {
+ X509_Ptr cert(parse_cert_blob(attestation_cert));
+ EXPECT_TRUE(!!cert.get());
+ if (!cert.get()) return false;
+
+ ASN1_OCTET_STRING* attest_rec = get_attestation_record(cert.get());
+ EXPECT_TRUE(!!attest_rec);
+ if (!attest_rec) return false;
+
+ AuthorizationSet att_sw_enforced;
+ AuthorizationSet att_hw_enforced;
+ uint32_t att_attestation_version;
+ uint32_t att_keymaster_version;
+ SecurityLevel att_attestation_security_level;
+ SecurityLevel att_keymaster_security_level;
+ vector<uint8_t> att_challenge;
+ vector<uint8_t> att_unique_id;
+ vector<uint8_t> att_app_id;
+
+ auto error = parse_attestation_record(attest_rec->data, //
+ attest_rec->length, //
+ &att_attestation_version, //
+ &att_attestation_security_level, //
+ &att_keymaster_version, //
+ &att_keymaster_security_level, //
+ &att_challenge, //
+ &att_sw_enforced, //
+ &att_hw_enforced, //
+ &att_unique_id);
+ EXPECT_EQ(ErrorCode::OK, error);
+ if (error != ErrorCode::OK) return false;
+
+ EXPECT_GE(att_attestation_version, 3U);
+
+ expected_sw_enforced.push_back(TAG_ATTESTATION_APPLICATION_ID,
+ vector<uint8_t>(app_id.begin(), app_id.end()));
+
+ EXPECT_GE(att_keymaster_version, 4U);
+ EXPECT_EQ(security_level, att_keymaster_security_level);
+ EXPECT_EQ(security_level, att_attestation_security_level);
+
+ EXPECT_EQ(challenge.length(), att_challenge.size());
+ EXPECT_EQ(0, memcmp(challenge.data(), att_challenge.data(), challenge.length()));
+
+ char property_value[PROPERTY_VALUE_MAX] = {};
+ // TODO(b/136282179): When running under VTS-on-GSI the TEE-backed
+ // keymaster implementation will report YYYYMM dates instead of YYYYMMDD
+ // for the BOOT_PATCH_LEVEL.
+ if (avb_verification_enabled()) {
+ for (int i = 0; i < att_hw_enforced.size(); i++) {
+ if (att_hw_enforced[i].tag == TAG_BOOT_PATCHLEVEL ||
+ att_hw_enforced[i].tag == TAG_VENDOR_PATCHLEVEL) {
+ std::string date =
+ std::to_string(att_hw_enforced[i].value.get<KeyParameterValue::dateTime>());
+ // strptime seems to require delimiters, but the tag value will
+ // be YYYYMMDD
+ date.insert(6, "-");
+ date.insert(4, "-");
+ EXPECT_EQ(date.size(), 10);
+ struct tm time;
+ strptime(date.c_str(), "%Y-%m-%d", &time);
+
+ // Day of the month (0-31)
+ EXPECT_GE(time.tm_mday, 0);
+ EXPECT_LT(time.tm_mday, 32);
+ // Months since Jan (0-11)
+ EXPECT_GE(time.tm_mon, 0);
+ EXPECT_LT(time.tm_mon, 12);
+ // Years since 1900
+ EXPECT_GT(time.tm_year, 110);
+ EXPECT_LT(time.tm_year, 200);
+ }
+ }
+ }
+
+ // Check to make sure boolean values are properly encoded. Presence of a boolean tag indicates
+ // true. A provided boolean tag that can be pulled back out of the certificate indicates correct
+ // encoding. No need to check if it's in both lists, since the AuthorizationSet compare below
+ // will handle mismatches of tags.
+ if (security_level == SecurityLevel::SOFTWARE) {
+ EXPECT_TRUE(expected_sw_enforced.Contains(TAG_NO_AUTH_REQUIRED));
+ } else {
+ EXPECT_TRUE(expected_hw_enforced.Contains(TAG_NO_AUTH_REQUIRED));
+ }
+
+ // Alternatively this checks the opposite - a false boolean tag (one that isn't provided in
+ // the authorization list during key generation) isn't being attested to in the certificate.
+ EXPECT_FALSE(expected_sw_enforced.Contains(TAG_TRUSTED_USER_PRESENCE_REQUIRED));
+ EXPECT_FALSE(att_sw_enforced.Contains(TAG_TRUSTED_USER_PRESENCE_REQUIRED));
+ EXPECT_FALSE(expected_hw_enforced.Contains(TAG_TRUSTED_USER_PRESENCE_REQUIRED));
+ EXPECT_FALSE(att_hw_enforced.Contains(TAG_TRUSTED_USER_PRESENCE_REQUIRED));
+
+ if (att_hw_enforced.Contains(TAG_ALGORITHM, Algorithm::EC)) {
+ // For ECDSA keys, either an EC_CURVE or a KEY_SIZE can be specified, but one must be.
+ EXPECT_TRUE(att_hw_enforced.Contains(TAG_EC_CURVE) ||
+ att_hw_enforced.Contains(TAG_KEY_SIZE));
+ }
+
+ // Test root of trust elements
+ vector<uint8_t> verified_boot_key;
+ VerifiedBoot verified_boot_state;
+ bool device_locked;
+ vector<uint8_t> verified_boot_hash;
+ error = parse_root_of_trust(attest_rec->data, attest_rec->length, &verified_boot_key,
+ &verified_boot_state, &device_locked, &verified_boot_hash);
+ EXPECT_EQ(ErrorCode::OK, error);
+
+ if (avb_verification_enabled()) {
+ EXPECT_NE(property_get("ro.boot.vbmeta.digest", property_value, ""), 0);
+ string prop_string(property_value);
+ EXPECT_EQ(prop_string.size(), 64);
+ EXPECT_EQ(prop_string, bin2hex(verified_boot_hash));
+
+ EXPECT_NE(property_get("ro.boot.vbmeta.device_state", property_value, ""), 0);
+ if (!strcmp(property_value, "unlocked")) {
+ EXPECT_FALSE(device_locked);
+ } else {
+ EXPECT_TRUE(device_locked);
+ }
+
+ // Check that the device is locked if not debuggable, e.g., user build
+ // images in CTS. For VTS, debuggable images are used to allow adb root
+ // and the device is unlocked.
+ if (!property_get_bool("ro.debuggable", false)) {
+ EXPECT_TRUE(device_locked);
+ } else {
+ EXPECT_FALSE(device_locked);
+ }
+ }
+
+ // Verified boot key should be all 0's if the boot state is not verified or self signed
+ std::string empty_boot_key(32, '\0');
+ std::string verified_boot_key_str((const char*)verified_boot_key.data(),
+ verified_boot_key.size());
+ EXPECT_NE(property_get("ro.boot.verifiedbootstate", property_value, ""), 0);
+ if (!strcmp(property_value, "green")) {
+ EXPECT_EQ(verified_boot_state, VerifiedBoot::VERIFIED);
+ EXPECT_NE(0, memcmp(verified_boot_key.data(), empty_boot_key.data(),
+ verified_boot_key.size()));
+ } else if (!strcmp(property_value, "yellow")) {
+ EXPECT_EQ(verified_boot_state, VerifiedBoot::SELF_SIGNED);
+ EXPECT_NE(0, memcmp(verified_boot_key.data(), empty_boot_key.data(),
+ verified_boot_key.size()));
+ } else if (!strcmp(property_value, "orange")) {
+ EXPECT_EQ(verified_boot_state, VerifiedBoot::UNVERIFIED);
+ EXPECT_EQ(0, memcmp(verified_boot_key.data(), empty_boot_key.data(),
+ verified_boot_key.size()));
+ } else if (!strcmp(property_value, "red")) {
+ EXPECT_EQ(verified_boot_state, VerifiedBoot::FAILED);
+ } else {
+ EXPECT_EQ(verified_boot_state, VerifiedBoot::UNVERIFIED);
+ EXPECT_NE(0, memcmp(verified_boot_key.data(), empty_boot_key.data(),
+ verified_boot_key.size()));
+ }
+
+ att_sw_enforced.Sort();
+ expected_sw_enforced.Sort();
+ EXPECT_EQ(filtered_tags(expected_sw_enforced), filtered_tags(att_sw_enforced));
+
+ att_hw_enforced.Sort();
+ expected_hw_enforced.Sort();
+ EXPECT_EQ(filtered_tags(expected_hw_enforced), filtered_tags(att_hw_enforced));
+
+ return true;
+}
std::string make_string(const uint8_t* data, size_t length) {
return std::string(reinterpret_cast<const char*>(data), length);
@@ -226,19 +498,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.
@@ -246,15 +519,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);
- EXPECT_TRUE(auths.Contains(TAG_OS_PATCHLEVEL, os_patch_level()))
- << "OS patch level is " << os_patch_level() << " key reported "
- << auths.GetTagValue(TAG_OS_PATCHLEVEL);
+ auto os_pl = auths.GetTagValue(TAG_OS_PATCHLEVEL);
+ ASSERT_TRUE(os_pl);
+ EXPECT_EQ(*os_pl, os_patch_level());
}
};
@@ -267,7 +538,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)
@@ -277,12 +548,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))
@@ -294,6 +560,51 @@
}
/*
+ * NewKeyGenerationTest.Rsa
+ *
+ * Verifies that keymint can generate all required RSA key sizes, and that the resulting keys
+ * have correct characteristics.
+ */
+TEST_P(NewKeyGenerationTest, RsaWithAttestation) {
+ for (auto key_size : ValidKeySizes(Algorithm::RSA)) {
+ auto challenge = "hello";
+ auto app_id = "foo";
+
+ vector<uint8_t> key_blob;
+ vector<KeyCharacteristics> key_characteristics;
+ ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+ .RsaSigningKey(key_size, 65537)
+ .Digest(Digest::NONE)
+ .Padding(PaddingMode::NONE)
+ .AttestationChallenge(challenge)
+ .AttestationApplicationId(app_id)
+ .Authorization(TAG_NO_AUTH_REQUIRED),
+ &key_blob, &key_characteristics));
+
+ ASSERT_GT(key_blob.size(), 0U);
+ CheckBaseParams(key_characteristics);
+
+ AuthorizationSet crypto_params = SecLevelAuthorizations(key_characteristics);
+
+ EXPECT_TRUE(crypto_params.Contains(TAG_ALGORITHM, Algorithm::RSA));
+ EXPECT_TRUE(crypto_params.Contains(TAG_KEY_SIZE, key_size))
+ << "Key size " << key_size << "missing";
+ EXPECT_TRUE(crypto_params.Contains(TAG_RSA_PUBLIC_EXPONENT, 65537U));
+
+ EXPECT_TRUE(verify_chain(cert_chain_));
+ ASSERT_GT(cert_chain_.size(), 0);
+
+ AuthorizationSet hw_enforced = HwEnforcedAuthorizations(key_characteristics);
+ AuthorizationSet sw_enforced = SwEnforcedAuthorizations(key_characteristics);
+ EXPECT_TRUE(verify_attestation_record(challenge, app_id, //
+ sw_enforced, hw_enforced, SecLevel(),
+ cert_chain_[0].encodedCertificate));
+
+ CheckedDeleteKey(&key_blob);
+ }
+}
+
+/*
* NewKeyGenerationTest.NoInvalidRsaSizes
*
* Verifies that keymint cannot generate any RSA key sizes that are designated as invalid.
@@ -301,7 +612,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)
@@ -334,7 +645,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),
@@ -342,12 +653,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))
@@ -380,7 +686,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),
@@ -451,7 +757,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(
@@ -462,17 +768,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);
}
@@ -597,7 +896,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()
@@ -834,7 +1133,7 @@
EXPECT_EQ(ErrorCode::INVALID_OPERATION_HANDLE, Abort());
// Set to sentinel, so TearDown() doesn't try to abort again.
- op_.clear();
+ op_.reset();
}
/*
@@ -1409,7 +1708,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)
@@ -1463,28 +1762,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);
}
};
@@ -2053,6 +2346,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.
@@ -2333,8 +2727,8 @@
vector<uint8_t> CopyIv(const AuthorizationSet& set) {
auto iv = set.GetTagValue(TAG_NONCE);
- EXPECT_TRUE(iv.isOk());
- return iv.value();
+ EXPECT_TRUE(iv);
+ return iv->get();
}
/*
@@ -2459,13 +2853,13 @@
case BlockMode::CBC:
case BlockMode::GCM:
case BlockMode::CTR:
- ASSERT_TRUE(iv.isOk()) << "No IV for block mode " << block_mode;
- EXPECT_EQ(block_mode == BlockMode::GCM ? 12U : 16U, iv.value().size());
- params.push_back(TAG_NONCE, iv.value());
+ ASSERT_TRUE(iv) << "No IV for block mode " << block_mode;
+ EXPECT_EQ(block_mode == BlockMode::GCM ? 12U : 16U, iv->get().size());
+ params.push_back(TAG_NONCE, iv->get());
break;
case BlockMode::ECB:
- EXPECT_FALSE(iv.isOk()) << "ECB mode should not generate IV";
+ EXPECT_FALSE(iv) << "ECB mode should not generate IV";
break;
}
@@ -2649,9 +3043,9 @@
AuthorizationSet out_params;
string ciphertext = EncryptMessage(message, params, &out_params);
EXPECT_EQ(message.size(), ciphertext.size());
- EXPECT_EQ(16U, out_params.GetTagValue(TAG_NONCE).value().size());
+ EXPECT_EQ(16U, out_params.GetTagValue(TAG_NONCE)->get().size());
- params.push_back(TAG_NONCE, out_params.GetTagValue(TAG_NONCE).value());
+ params.push_back(TAG_NONCE, out_params.GetTagValue(TAG_NONCE)->get());
string plaintext = DecryptMessage(ciphertext, params);
EXPECT_EQ(message, plaintext);
@@ -2697,9 +3091,9 @@
AuthorizationSet out_params;
string ciphertext = EncryptMessage(message, params, &out_params);
EXPECT_EQ(message.size(), ciphertext.size());
- EXPECT_EQ(16U, out_params.GetTagValue(TAG_NONCE).value().size());
+ EXPECT_EQ(16U, out_params.GetTagValue(TAG_NONCE)->get().size());
- params.push_back(TAG_NONCE, out_params.GetTagValue(TAG_NONCE).value());
+ params.push_back(TAG_NONCE, out_params.GetTagValue(TAG_NONCE)->get());
string plaintext = DecryptMessage(ciphertext, params);
EXPECT_EQ(message, plaintext);
@@ -2893,7 +3287,7 @@
AuthorizationSet begin_out_params;
EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::ENCRYPT, params, &begin_out_params));
EXPECT_EQ(1U, begin_out_params.size());
- ASSERT_TRUE(begin_out_params.GetTagValue(TAG_NONCE).isOk());
+ ASSERT_TRUE(begin_out_params.GetTagValue(TAG_NONCE));
AuthorizationSet finish_out_params;
string ciphertext;
@@ -3115,7 +3509,7 @@
EXPECT_EQ(ErrorCode::INVALID_TAG,
Update(update_params, "", &update_out_params, &ciphertext, &input_consumed));
- op_.clear();
+ op_.reset();
}
/*
@@ -3817,16 +4211,6 @@
INSTANTIATE_KEYMINT_AIDL_TEST(AddEntropyTest);
-typedef KeyMintAidlTestBase AttestationTest;
-
-/*
- * AttestationTest.RsaAttestation
- *
- * Verifies that attesting to RSA keys works and generates the expected output.
- */
-// TODO(seleneh) add attestation tests back after decided on the new attestation
-// behavior under generateKey and importKey
-
typedef KeyMintAidlTestBase KeyDeletionTest;
/**
@@ -3846,7 +4230,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 */));
@@ -3879,8 +4263,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();
@@ -3915,7 +4299,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());
@@ -3973,7 +4357,7 @@
auto params = AuthorizationSetBuilder().Padding(PaddingMode::NONE);
constexpr size_t max_operations = 100; // set to arbituary large number
- sp<IKeyMintOperation> op_handles[max_operations];
+ std::shared_ptr<IKeyMintOperation> op_handles[max_operations];
AuthorizationSet out_params;
ErrorCode result;
size_t i;
@@ -4040,7 +4424,7 @@
INSTANTIATE_KEYMINT_AIDL_TEST(TransportLimitTest);
-} // namespace android::hardware::security::keymint::test
+} // namespace aidl::android::hardware::security::keymint::test
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
diff --git a/security/keymint/aidl/vts/functional/VerificationTokenTest.cpp b/security/keymint/aidl/vts/functional/VerificationTokenTest.cpp
deleted file mode 100644
index 6d3a34e..0000000
--- a/security/keymint/aidl/vts/functional/VerificationTokenTest.cpp
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "KeyMintAidlTestBase.h"
-
-namespace android::hardware::security::keymint::test {
-
-class VerificationTokenTest : public KeyMintAidlTestBase {
- protected:
- struct VerifyAuthorizationResult {
- ErrorCode error;
- VerificationToken token;
- };
-
- VerifyAuthorizationResult verifyAuthorization(uint64_t operationHandle,
- const HardwareAuthToken& authToken) {
- VerifyAuthorizationResult result;
-
- Status err;
- err = keyMint().verifyAuthorization(operationHandle, //
- authToken, //
- &result.token);
-
- result.error = GetReturnErrorCode(err);
- return result;
- }
-
- uint64_t getTime() {
- struct timespec timespec;
- EXPECT_EQ(0, clock_gettime(CLOCK_BOOTTIME, ×pec));
- return timespec.tv_sec * 1000 + timespec.tv_nsec / 1000000;
- }
-
- int sleep_ms(uint32_t milliseconds) {
- struct timespec sleep_time = {static_cast<time_t>(milliseconds / 1000),
- static_cast<long>(milliseconds % 1000) * 1000000};
- while (sleep_time.tv_sec || sleep_time.tv_nsec) {
- if (nanosleep(&sleep_time /* to wait */,
- &sleep_time /* remaining (on interrruption) */) == 0) {
- sleep_time = {};
- } else {
- if (errno != EINTR) return errno;
- }
- }
- return 0;
- }
-};
-
-/*
- * VerificationTokens exist to facilitate cross-KeyMint verification of requirements. As
- * such, the precise capabilities required will vary depending on the specific vendor
- * implementations. Essentially, VerificationTokens are a "hook" to enable vendor
- * implementations to communicate, so the precise usage is defined by those vendors. The only
- * thing we really can test is that tokens can be created by TEE keyMints, and that the
- * timestamps increase as expected.
- */
-TEST_P(VerificationTokenTest, TestCreation) {
- auto result1 = verifyAuthorization(1 /* operation handle */, HardwareAuthToken());
- auto result1_time = getTime();
-
- if (SecLevel() == SecurityLevel::STRONGBOX) {
- // StrongBox should not implement verifyAuthorization.
- EXPECT_EQ(ErrorCode::UNIMPLEMENTED, result1.error);
- return;
- }
-
- ASSERT_EQ(ErrorCode::OK, result1.error);
- EXPECT_EQ(1U, result1.token.challenge);
- EXPECT_EQ(SecLevel(), result1.token.securityLevel);
- EXPECT_GT(result1.token.timestamp.milliSeconds, 0U);
-
- constexpr uint32_t time_to_sleep = 200;
- sleep_ms(time_to_sleep);
-
- auto result2 = verifyAuthorization(2 /* operation handle */, HardwareAuthToken());
-
- auto result2_time = getTime();
- ASSERT_EQ(ErrorCode::OK, result2.error);
- EXPECT_EQ(2U, result2.token.challenge);
- EXPECT_EQ(SecLevel(), result2.token.securityLevel);
-
- auto host_time_delta = result2_time - result1_time;
-
- EXPECT_GE(host_time_delta, time_to_sleep)
- << "We slept for " << time_to_sleep << " ms, the clock must have advanced by that much";
- EXPECT_LE(host_time_delta, time_to_sleep + 20)
- << "The verifyAuthorization call took " << (host_time_delta - time_to_sleep)
- << " ms? That's awful!";
-
- auto km_time_delta =
- result2.token.timestamp.milliSeconds - result1.token.timestamp.milliSeconds;
-
- // If not too much else is going on on the system, the time delta should be quite close. Allow
- // 2 ms of slop just to avoid test flakiness.
- //
- // TODO(swillden): see if we can output values so they can be gathered across many runs and
- // report if times aren't nearly always <1ms apart.
- EXPECT_LE(host_time_delta, km_time_delta + 2);
- EXPECT_LE(km_time_delta, host_time_delta + 2);
- ASSERT_EQ(result1.token.mac.size(), result2.token.mac.size());
- ASSERT_NE(0,
- memcmp(result1.token.mac.data(), result2.token.mac.data(), result1.token.mac.size()));
-}
-
-/*
- * Test that the mac changes when the time stamp changes. This is does not guarantee that the time
- * stamp is included in the mac but on failure we know that it is not. Other than in the test
- * case above we call verifyAuthorization with the exact same set of parameters.
- */
-TEST_P(VerificationTokenTest, MacChangesOnChangingTimestamp) {
- auto result1 = verifyAuthorization(0 /* operation handle */, HardwareAuthToken());
- auto result1_time = getTime();
-
- if (SecLevel() == SecurityLevel::STRONGBOX) {
- // StrongBox should not implement verifyAuthorization.
- EXPECT_EQ(ErrorCode::UNIMPLEMENTED, result1.error);
- return;
- }
-
- EXPECT_EQ(ErrorCode::OK, result1.error);
- EXPECT_EQ(0U, result1.token.challenge);
- EXPECT_EQ(SecLevel(), result1.token.securityLevel);
- EXPECT_GT(result1.token.timestamp.milliSeconds, 0U);
-
- constexpr uint32_t time_to_sleep = 200;
- sleep_ms(time_to_sleep);
-
- auto result2 = verifyAuthorization(0 /* operation handle */, HardwareAuthToken());
- // ASSERT_TRUE(result2.callSuccessful);
- auto result2_time = getTime();
- EXPECT_EQ(ErrorCode::OK, result2.error);
- EXPECT_EQ(0U, result2.token.challenge);
- EXPECT_EQ(SecLevel(), result2.token.securityLevel);
-
- auto host_time_delta = result2_time - result1_time;
-
- EXPECT_GE(host_time_delta, time_to_sleep)
- << "We slept for " << time_to_sleep << " ms, the clock must have advanced by that much";
- EXPECT_LE(host_time_delta, time_to_sleep + 20)
- << "The verifyAuthorization call took " << (host_time_delta - time_to_sleep)
- << " ms? That's awful!";
-
- auto km_time_delta =
- result2.token.timestamp.milliSeconds - result1.token.timestamp.milliSeconds;
-
- EXPECT_LE(host_time_delta, km_time_delta + 2);
- EXPECT_LE(km_time_delta, host_time_delta + 2);
- ASSERT_EQ(result1.token.mac.size(), result2.token.mac.size());
- ASSERT_NE(0,
- memcmp(result1.token.mac.data(), result2.token.mac.data(), result1.token.mac.size()));
-}
-
-INSTANTIATE_KEYMINT_AIDL_TEST(VerificationTokenTest);
-
-} // namespace android::hardware::security::keymint::test
diff --git a/security/keymint/support/Android.bp b/security/keymint/support/Android.bp
index ddac92f..0cfa798 100644
--- a/security/keymint/support/Android.bp
+++ b/security/keymint/support/Android.bp
@@ -31,7 +31,7 @@
"include",
],
shared_libs: [
- "android.hardware.security.keymint-cpp",
+ "android.hardware.security.keymint-unstable-ndk_platform",
"libbase",
"libcrypto",
"libutils",
diff --git a/security/keymint/support/attestation_record.cpp b/security/keymint/support/attestation_record.cpp
index afdb208..596b097 100644
--- a/security/keymint/support/attestation_record.cpp
+++ b/security/keymint/support/attestation_record.cpp
@@ -18,6 +18,9 @@
#include <assert.h>
+#include <aidl/android/hardware/security/keymint/Tag.h>
+#include <aidl/android/hardware/security/keymint/TagType.h>
+
#include <android-base/logging.h>
#include <openssl/asn1t.h>
@@ -25,15 +28,12 @@
#include <openssl/evp.h>
#include <openssl/x509.h>
-#include <android/hardware/security/keymint/Tag.h>
-#include <android/hardware/security/keymint/TagType.h>
-
#include <keymint_support/authorization_set.h>
#include <keymint_support/openssl_utils.h>
#define AT __FILE__ ":" << __LINE__
-namespace android::hardware::security::keymint {
+namespace aidl::android::hardware::security::keymint {
struct stack_st_ASN1_TYPE_Delete {
void operator()(stack_st_ASN1_TYPE* p) { sk_ASN1_TYPE_free(p); }
@@ -326,9 +326,8 @@
}
ErrorCode parse_root_of_trust(const uint8_t* asn1_key_desc, size_t asn1_key_desc_len,
- vector<uint8_t>* verified_boot_key,
- keymint_verified_boot_t* verified_boot_state, bool* device_locked,
- vector<uint8_t>* verified_boot_hash) {
+ vector<uint8_t>* verified_boot_key, VerifiedBoot* verified_boot_state,
+ bool* device_locked, vector<uint8_t>* verified_boot_hash) {
if (!verified_boot_key || !verified_boot_state || !device_locked || !verified_boot_hash) {
LOG(ERROR) << AT << "null pointer input(s)";
return ErrorCode::INVALID_ARGUMENT;
@@ -358,8 +357,8 @@
verified_boot_key->resize(vb_key->length);
memcpy(verified_boot_key->data(), vb_key->data, vb_key->length);
- *verified_boot_state = static_cast<keymint_verified_boot_t>(
- ASN1_ENUMERATED_get(root_of_trust->verified_boot_state));
+ *verified_boot_state =
+ static_cast<VerifiedBoot>(ASN1_ENUMERATED_get(root_of_trust->verified_boot_state));
if (!verified_boot_state) {
LOG(ERROR) << AT << " Failed verified boot state parsing";
return ErrorCode::INVALID_ARGUMENT;
@@ -381,4 +380,4 @@
return ErrorCode::OK; // KM_ERROR_OK;
}
-} // namespace android::hardware::security::keymint
+} // namespace aidl::android::hardware::security::keymint
diff --git a/security/keymint/support/authorization_set.cpp b/security/keymint/support/authorization_set.cpp
index aa9638f..3d44dff 100644
--- a/security/keymint/support/authorization_set.cpp
+++ b/security/keymint/support/authorization_set.cpp
@@ -16,19 +16,13 @@
#include <keymint_support/authorization_set.h>
-#include <assert.h>
-#include <sstream>
+#include <aidl/android/hardware/security/keymint/Algorithm.h>
+#include <aidl/android/hardware/security/keymint/BlockMode.h>
+#include <aidl/android/hardware/security/keymint/Digest.h>
+#include <aidl/android/hardware/security/keymint/KeyParameter.h>
+#include <aidl/android/hardware/security/keymint/KeyPurpose.h>
-#include <android-base/logging.h>
-
-#include <android/hardware/security/keymint/Algorithm.h>
-#include <android/hardware/security/keymint/BlockMode.h>
-#include <android/hardware/security/keymint/Digest.h>
-#include <android/hardware/security/keymint/KeyParameter.h>
-#include <android/hardware/security/keymint/KeyPurpose.h>
-#include <android/hardware/security/keymint/TagType.h>
-
-namespace android::hardware::security::keymint {
+namespace aidl::android::hardware::security::keymint {
void AuthorizationSet::Sort() {
std::sort(data_.begin(), data_.end());
@@ -76,16 +70,6 @@
}
}
-void AuthorizationSet::Filter(std::function<bool(const KeyParameter&)> doKeep) {
- std::vector<KeyParameter> result;
- for (auto& param : data_) {
- if (doKeep(param)) {
- result.push_back(std::move(param));
- }
- }
- std::swap(data_, result);
-}
-
KeyParameter& AuthorizationSet::operator[](int at) {
return data_[at];
}
@@ -122,283 +106,11 @@
return false;
}
-NullOr<const KeyParameter&> AuthorizationSet::GetEntry(Tag tag) const {
+std::optional<std::reference_wrapper<const KeyParameter>> AuthorizationSet::GetEntry(
+ Tag tag) const {
int pos = find(tag);
if (pos == -1) return {};
- return data_[pos];
-}
-
-/**
- * Persistent format is:
- * | 32 bit indirect_size |
- * --------------------------------
- * | indirect_size bytes of data | this is where the blob data is stored
- * --------------------------------
- * | 32 bit element_count | number of entries
- * | 32 bit elements_size | total bytes used by entries (entries have variable length)
- * --------------------------------
- * | elementes_size bytes of data | where the elements are stored
- */
-
-/**
- * Persistent format of blobs and bignums:
- * | 32 bit tag |
- * | 32 bit blob_length |
- * | 32 bit indirect_offset |
- */
-
-struct OutStreams {
- std::ostream& indirect;
- std::ostream& elements;
- size_t skipped;
-};
-
-OutStreams& serializeParamValue(OutStreams& out, const vector<uint8_t>& blob) {
- uint32_t buffer;
-
- // write blob_length
- auto blob_length = blob.size();
- if (blob_length > std::numeric_limits<uint32_t>::max()) {
- out.elements.setstate(std::ios_base::badbit);
- return out;
- }
- buffer = blob_length;
- out.elements.write(reinterpret_cast<const char*>(&buffer), sizeof(uint32_t));
-
- // write indirect_offset
- auto offset = out.indirect.tellp();
- if (offset < 0 || offset > std::numeric_limits<uint32_t>::max() ||
- uint32_t(offset) + uint32_t(blob_length) < uint32_t(offset)) { // overflow check
- out.elements.setstate(std::ios_base::badbit);
- return out;
- }
- buffer = offset;
- out.elements.write(reinterpret_cast<const char*>(&buffer), sizeof(uint32_t));
-
- // write blob to indirect stream
- if (blob_length) out.indirect.write(reinterpret_cast<const char*>(&blob[0]), blob_length);
-
- return out;
-}
-
-template <typename T>
-OutStreams& serializeParamValue(OutStreams& out, const T& value) {
- out.elements.write(reinterpret_cast<const char*>(&value), sizeof(T));
- return out;
-}
-
-OutStreams& serialize(TAG_INVALID_t&&, OutStreams& out, const KeyParameter&) {
- // skip invalid entries.
- ++out.skipped;
- return out;
-}
-template <typename T>
-OutStreams& serialize(T ttag, OutStreams& out, const KeyParameter& param) {
- out.elements.write(reinterpret_cast<const char*>(¶m.tag), sizeof(int32_t));
- return serializeParamValue(out, accessTagValue(ttag, param));
-}
-
-template <typename... T>
-struct choose_serializer;
-template <typename... Tags>
-struct choose_serializer<MetaList<Tags...>> {
- static OutStreams& serialize(OutStreams& out, const KeyParameter& param) {
- return choose_serializer<Tags...>::serialize(out, param);
- }
-};
-
-template <>
-struct choose_serializer<> {
- static OutStreams& serialize(OutStreams& out, const KeyParameter& param) {
- LOG(WARNING) << "Trying to serialize unknown tag " << unsigned(param.tag)
- << ". Did you forget to add it to all_tags_t?";
- ++out.skipped;
- return out;
- }
-};
-
-template <TagType tag_type, Tag tag, typename... Tail>
-struct choose_serializer<android::hardware::security::keymint::TypedTag<tag_type, tag>, Tail...> {
- static OutStreams& serialize(OutStreams& out, const KeyParameter& param) {
- if (param.tag == tag) {
- return android::hardware::security::keymint::serialize(TypedTag<tag_type, tag>(), out,
- param);
- } else {
- return choose_serializer<Tail...>::serialize(out, param);
- }
- }
-};
-
-OutStreams& serialize(OutStreams& out, const KeyParameter& param) {
- return choose_serializer<all_tags_t>::serialize(out, param);
-}
-
-std::ostream& serialize(std::ostream& out, const std::vector<KeyParameter>& params) {
- std::stringstream indirect;
- std::stringstream elements;
- OutStreams streams = {indirect, elements, 0};
- for (const auto& param : params) {
- serialize(streams, param);
- }
- if (indirect.bad() || elements.bad()) {
- out.setstate(std::ios_base::badbit);
- return out;
- }
- auto pos = indirect.tellp();
- if (pos < 0 || pos > std::numeric_limits<uint32_t>::max()) {
- out.setstate(std::ios_base::badbit);
- return out;
- }
- uint32_t indirect_size = pos;
- pos = elements.tellp();
- if (pos < 0 || pos > std::numeric_limits<uint32_t>::max()) {
- out.setstate(std::ios_base::badbit);
- return out;
- }
- uint32_t elements_size = pos;
- uint32_t element_count = params.size() - streams.skipped;
-
- out.write(reinterpret_cast<const char*>(&indirect_size), sizeof(uint32_t));
-
- pos = out.tellp();
- if (indirect_size) out << indirect.rdbuf();
- assert(out.tellp() - pos == indirect_size);
-
- out.write(reinterpret_cast<const char*>(&element_count), sizeof(uint32_t));
- out.write(reinterpret_cast<const char*>(&elements_size), sizeof(uint32_t));
-
- pos = out.tellp();
- if (elements_size) out << elements.rdbuf();
- assert(out.tellp() - pos == elements_size);
-
- return out;
-}
-
-struct InStreams {
- std::istream& indirect;
- std::istream& elements;
- size_t invalids;
-};
-
-InStreams& deserializeParamValue(InStreams& in, vector<uint8_t>* blob) {
- uint32_t blob_length = 0;
- uint32_t offset = 0;
- in.elements.read(reinterpret_cast<char*>(&blob_length), sizeof(uint32_t));
- blob->resize(blob_length);
- in.elements.read(reinterpret_cast<char*>(&offset), sizeof(uint32_t));
- in.indirect.seekg(offset);
- in.indirect.read(reinterpret_cast<char*>(&(*blob)[0]), blob->size());
- return in;
-}
-
-template <typename T>
-InStreams& deserializeParamValue(InStreams& in, T* value) {
- in.elements.read(reinterpret_cast<char*>(value), sizeof(T));
- return in;
-}
-
-InStreams& deserialize(TAG_INVALID_t&&, InStreams& in, KeyParameter*) {
- // there should be no invalid KeyParameters but if handle them as zero sized.
- ++in.invalids;
- return in;
-}
-
-template <typename T>
-InStreams& deserialize(T&& ttag, InStreams& in, KeyParameter* param) {
- return deserializeParamValue(in, &accessTagValue(ttag, *param));
-}
-
-template <typename... T>
-struct choose_deserializer;
-template <typename... Tags>
-struct choose_deserializer<MetaList<Tags...>> {
- static InStreams& deserialize(InStreams& in, KeyParameter* param) {
- return choose_deserializer<Tags...>::deserialize(in, param);
- }
-};
-template <>
-struct choose_deserializer<> {
- static InStreams& deserialize(InStreams& in, KeyParameter*) {
- // encountered an unknown tag -> fail parsing
- in.elements.setstate(std::ios_base::badbit);
- return in;
- }
-};
-template <TagType tag_type, Tag tag, typename... Tail>
-struct choose_deserializer<TypedTag<tag_type, tag>, Tail...> {
- static InStreams& deserialize(InStreams& in, KeyParameter* param) {
- if (param->tag == tag) {
- return android::hardware::security::keymint::deserialize(TypedTag<tag_type, tag>(), in,
- param);
- } else {
- return choose_deserializer<Tail...>::deserialize(in, param);
- }
- }
-};
-
-InStreams& deserialize(InStreams& in, KeyParameter* param) {
- in.elements.read(reinterpret_cast<char*>(¶m->tag), sizeof(Tag));
- return choose_deserializer<all_tags_t>::deserialize(in, param);
-}
-
-std::istream& deserialize(std::istream& in, std::vector<KeyParameter>* params) {
- uint32_t indirect_size = 0;
- in.read(reinterpret_cast<char*>(&indirect_size), sizeof(uint32_t));
- std::string indirect_buffer(indirect_size, '\0');
- if (indirect_buffer.size() != indirect_size) {
- in.setstate(std::ios_base::badbit);
- return in;
- }
- in.read(&indirect_buffer[0], indirect_buffer.size());
-
- uint32_t element_count = 0;
- in.read(reinterpret_cast<char*>(&element_count), sizeof(uint32_t));
- uint32_t elements_size = 0;
- in.read(reinterpret_cast<char*>(&elements_size), sizeof(uint32_t));
-
- std::string elements_buffer(elements_size, '\0');
- if (elements_buffer.size() != elements_size) {
- in.setstate(std::ios_base::badbit);
- return in;
- }
- in.read(&elements_buffer[0], elements_buffer.size());
-
- if (in.bad()) return in;
-
- // TODO write one-shot stream buffer to avoid copying here
- std::stringstream indirect(indirect_buffer);
- std::stringstream elements(elements_buffer);
- InStreams streams = {indirect, elements, 0};
-
- params->resize(element_count);
-
- for (uint32_t i = 0; i < element_count; ++i) {
- deserialize(streams, &(*params)[i]);
- }
-
- /*
- * There are legacy blobs which have invalid tags in them due to a bug during serialization.
- * This makes sure that invalid tags are filtered from the result before it is returned.
- */
- if (streams.invalids > 0) {
- std::vector<KeyParameter> filtered(element_count - streams.invalids);
- auto ifiltered = filtered.begin();
- for (auto& p : *params) {
- if (p.tag != Tag::INVALID) {
- *ifiltered++ = std::move(p);
- }
- }
- *params = std::move(filtered);
- }
- return in;
-}
-
-void AuthorizationSet::Serialize(std::ostream* out) const {
- serialize(*out, data_);
-}
-
-void AuthorizationSet::Deserialize(std::istream* in) {
- deserialize(*in, &data_);
+ return std::reference_wrapper(data_[pos]);
}
AuthorizationSetBuilder& AuthorizationSetBuilder::RsaKey(uint32_t key_size,
@@ -501,7 +213,7 @@
}
AuthorizationSetBuilder& AuthorizationSetBuilder::BlockMode(
- std::initializer_list<android::hardware::security::keymint::BlockMode> blockModes) {
+ std::initializer_list<aidl::android::hardware::security::keymint::BlockMode> blockModes) {
for (auto mode : blockModes) {
push_back(TAG_BLOCK_MODE, mode);
}
@@ -515,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) {
@@ -523,4 +243,4 @@
return *this;
}
-} // namespace android::hardware::security::keymint
+} // namespace aidl::android::hardware::security::keymint
diff --git a/security/keymint/support/include/keymint_support/attestation_record.h b/security/keymint/support/include/keymint_support/attestation_record.h
index d71624c..bc76c93 100644
--- a/security/keymint/support/include/keymint_support/attestation_record.h
+++ b/security/keymint/support/include/keymint_support/attestation_record.h
@@ -16,14 +16,14 @@
#pragma once
-#include <android/hardware/security/keymint/ErrorCode.h>
-#include <android/hardware/security/keymint/IKeyMintDevice.h>
+#include <aidl/android/hardware/security/keymint/ErrorCode.h>
+#include <aidl/android/hardware/security/keymint/IKeyMintDevice.h>
#include <keymint_support/attestation_record.h>
#include <keymint_support/authorization_set.h>
#include <keymint_support/openssl_utils.h>
-namespace android::hardware::security::keymint {
+namespace aidl::android::hardware::security::keymint {
class AuthorizationSet;
@@ -43,18 +43,18 @@
*/
static const char kAttestionRecordOid[] = "1.3.6.1.4.1.11129.2.1.17";
-enum keymint_verified_boot_t {
- KM_VERIFIED_BOOT_VERIFIED = 0,
- KM_VERIFIED_BOOT_SELF_SIGNED = 1,
- KM_VERIFIED_BOOT_UNVERIFIED = 2,
- KM_VERIFIED_BOOT_FAILED = 3,
+enum class VerifiedBoot : uint8_t {
+ VERIFIED = 0,
+ SELF_SIGNED = 1,
+ UNVERIFIED = 2,
+ FAILED = 3,
};
struct RootOfTrust {
SecurityLevel security_level;
vector<uint8_t> verified_boot_key;
vector<uint8_t> verified_boot_hash;
- keymint_verified_boot_t verified_boot_state;
+ VerifiedBoot verified_boot_state;
bool device_locked;
};
@@ -81,7 +81,7 @@
ErrorCode parse_root_of_trust(const uint8_t* asn1_key_desc, size_t asn1_key_desc_len,
std::vector<uint8_t>* verified_boot_key,
- keymint_verified_boot_t* verified_boot_state, bool* device_locked,
+ VerifiedBoot* verified_boot_state, bool* device_locked,
std::vector<uint8_t>* verified_boot_hash);
-} // namespace android::hardware::security::keymint
+} // namespace aidl::android::hardware::security::keymint
diff --git a/security/keymint/support/include/keymint_support/authorization_set.h b/security/keymint/support/include/keymint_support/authorization_set.h
index 97e1022..1407c5f 100644
--- a/security/keymint/support/include/keymint_support/authorization_set.h
+++ b/security/keymint/support/include/keymint_support/authorization_set.h
@@ -14,28 +14,26 @@
* limitations under the License.
*/
-#ifndef SYSTEM_SECURITY_KEYSTORE_KM4_AUTHORIZATION_SET_H_
-#define SYSTEM_SECURITY_KEYSTORE_KM4_AUTHORIZATION_SET_H_
+#pragma once
#include <vector>
-#include <android/hardware/security/keymint/BlockMode.h>
-#include <android/hardware/security/keymint/Digest.h>
-#include <android/hardware/security/keymint/EcCurve.h>
-#include <android/hardware/security/keymint/PaddingMode.h>
+#include <aidl/android/hardware/security/keymint/BlockMode.h>
+#include <aidl/android/hardware/security/keymint/Digest.h>
+#include <aidl/android/hardware/security/keymint/EcCurve.h>
+#include <aidl/android/hardware/security/keymint/PaddingMode.h>
#include <keymint_support/keymint_tags.h>
-namespace android::hardware::security::keymint {
+namespace aidl::android::hardware::security::keymint {
using std::vector;
class AuthorizationSetBuilder;
/**
- * An ordered collection of KeyParameters. It provides memory ownership and some convenient
- * functionality for sorting, deduplicating, joining, and subtracting sets of KeyParameters.
- * For serialization, wrap the backing store of this structure in a vector<KeyParameter>.
+ * A collection of KeyParameters. It provides memory ownership and some convenient functionality for
+ * sorting, deduplicating, joining, and subtracting sets of KeyParameters.
*/
class AuthorizationSet {
public:
@@ -138,19 +136,16 @@
/**
* Returns iterator (pointer) to beginning of elems array, to enable STL-style iteration
*/
- std::vector<KeyParameter>::const_iterator begin() const { return data_.begin(); }
+ auto begin() { return data_.begin(); }
+ auto begin() const { return data_.begin(); }
/**
* Returns iterator (pointer) one past end of elems array, to enable STL-style iteration
*/
- std::vector<KeyParameter>::const_iterator end() const { return data_.end(); }
+ auto end() { return data_.end(); }
+ auto end() const { return data_.end(); }
/**
- * Modifies this Authorization set such that it only keeps the entries for which doKeep
- * returns true.
- */
- void Filter(std::function<bool(const KeyParameter&)> doKeep);
- /**
* Returns the nth element of the set.
* Like for std::vector::operator[] there is no range check performed. Use of out of range
* indices is undefined.
@@ -173,7 +168,7 @@
bool Contains(TypedTag<tag_type, tag> ttag, const ValueT& value) const {
for (const auto& param : data_) {
auto entry = authorizationValue(ttag, param);
- if (entry.isOk() && static_cast<ValueT>(entry.value()) == value) return true;
+ if (entry && static_cast<ValueT>(*entry) == value) return true;
}
return false;
}
@@ -183,9 +178,9 @@
size_t GetTagCount(Tag tag) const;
template <typename T>
- inline NullOr<const typename TypedTag2ValueType<T>::type&> GetTagValue(T tag) const {
+ inline auto GetTagValue(T tag) const -> decltype(authorizationValue(tag, KeyParameter())) {
auto entry = GetEntry(tag);
- if (entry.isOk()) return authorizationValue(tag, entry.value());
+ if (entry) return authorizationValue(tag, *entry);
return {};
}
@@ -223,11 +218,8 @@
return result;
}
- void Serialize(std::ostream* out) const;
- void Deserialize(std::istream* in);
-
private:
- NullOr<const KeyParameter&> GetEntry(Tag tag) const;
+ std::optional<std::reference_wrapper<const KeyParameter>> GetEntry(Tag tag) const;
std::vector<KeyParameter> data_;
};
@@ -267,6 +259,12 @@
size - 1); // drop the terminating '\0'
}
+ template <Tag tag>
+ AuthorizationSetBuilder& Authorization(TypedTag<TagType::BYTES, tag> ttag,
+ const std::string& data) {
+ return Authorization(ttag, reinterpret_cast<const uint8_t*>(data.data()), data.size());
+ }
+
AuthorizationSetBuilder& Authorizations(const AuthorizationSet& set) {
for (const auto& entry : set) {
push_back(entry);
@@ -298,9 +296,24 @@
AuthorizationSetBuilder& GcmModeMacLen(uint32_t macLength);
AuthorizationSetBuilder& BlockMode(std::initializer_list<BlockMode> blockModes);
+ AuthorizationSetBuilder& OaepMGFDigest(const std::vector<Digest>& digests);
AuthorizationSetBuilder& Digest(std::vector<Digest> digests);
AuthorizationSetBuilder& Padding(std::initializer_list<PaddingMode> paddings);
+ AuthorizationSetBuilder& AttestationChallenge(const std::string& challenge) {
+ return Authorization(TAG_ATTESTATION_CHALLENGE, challenge);
+ }
+ AuthorizationSetBuilder& AttestationChallenge(std::vector<uint8_t> challenge) {
+ return Authorization(TAG_ATTESTATION_CHALLENGE, challenge);
+ }
+
+ AuthorizationSetBuilder& AttestationApplicationId(const std::string& id) {
+ return Authorization(TAG_ATTESTATION_APPLICATION_ID, id);
+ }
+ AuthorizationSetBuilder& AttestationApplicationId(std::vector<uint8_t> id) {
+ return Authorization(TAG_ATTESTATION_APPLICATION_ID, id);
+ }
+
template <typename... T>
AuthorizationSetBuilder& BlockMode(T&&... a) {
return BlockMode({std::forward<T>(a)...});
@@ -315,6 +328,4 @@
}
};
-} // namespace android::hardware::security::keymint
-
-#endif // SYSTEM_SECURITY_KEYSTORE_KM4_AUTHORIZATION_SET_H_
+} // namespace aidl::android::hardware::security::keymint
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 82c9689..c2b0029 100644
--- a/security/keymint/support/include/keymint_support/key_param_output.h
+++ b/security/keymint/support/include/keymint_support/key_param_output.h
@@ -14,30 +14,29 @@
* limitations under the License.
*/
-#ifndef HARDWARE_INTERFACES_KEYMINT_SUPPORT_INCLUDE_KEY_PARAM_OUTPUT_H_
-#define HARDWARE_INTERFACES_KEYMINT_SUPPORT_INCLUDE_KEY_PARAM_OUTPUT_H_
+#pragma once
#include <iostream>
#include <vector>
-#include <android/hardware/security/keymint/Algorithm.h>
-#include <android/hardware/security/keymint/BlockMode.h>
-#include <android/hardware/security/keymint/Digest.h>
-#include <android/hardware/security/keymint/EcCurve.h>
-#include <android/hardware/security/keymint/ErrorCode.h>
-#include <android/hardware/security/keymint/HardwareAuthenticatorType.h>
-#include <android/hardware/security/keymint/KeyCharacteristics.h>
-#include <android/hardware/security/keymint/KeyOrigin.h>
-#include <android/hardware/security/keymint/KeyParameter.h>
-#include <android/hardware/security/keymint/KeyPurpose.h>
-#include <android/hardware/security/keymint/PaddingMode.h>
-#include <android/hardware/security/keymint/SecurityLevel.h>
-#include <android/hardware/security/keymint/Tag.h>
-#include <android/hardware/security/keymint/TagType.h>
+#include <aidl/android/hardware/security/keymint/Algorithm.h>
+#include <aidl/android/hardware/security/keymint/BlockMode.h>
+#include <aidl/android/hardware/security/keymint/Digest.h>
+#include <aidl/android/hardware/security/keymint/EcCurve.h>
+#include <aidl/android/hardware/security/keymint/ErrorCode.h>
+#include <aidl/android/hardware/security/keymint/HardwareAuthenticatorType.h>
+#include <aidl/android/hardware/security/keymint/KeyCharacteristics.h>
+#include <aidl/android/hardware/security/keymint/KeyOrigin.h>
+#include <aidl/android/hardware/security/keymint/KeyParameter.h>
+#include <aidl/android/hardware/security/keymint/KeyPurpose.h>
+#include <aidl/android/hardware/security/keymint/PaddingMode.h>
+#include <aidl/android/hardware/security/keymint/SecurityLevel.h>
+#include <aidl/android/hardware/security/keymint/Tag.h>
+#include <aidl/android/hardware/security/keymint/TagType.h>
#include "keymint_tags.h"
-namespace android::hardware::security::keymint {
+namespace aidl::android::hardware::security::keymint {
inline ::std::ostream& operator<<(::std::ostream& os, Algorithm value) {
return os << toString(value);
@@ -72,7 +71,7 @@
}
template <typename ValueT>
-::std::ostream& operator<<(::std::ostream& os, const NullOr<ValueT>& value) {
+::std::ostream& operator<<(::std::ostream& os, const std::optional<ValueT>& value) {
if (!value.isOk()) {
os << "(value not present)";
} else {
@@ -85,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) {
@@ -97,6 +98,4 @@
return os << toString(tag);
}
-} // namespace android::hardware::security::keymint
-
-#endif // HARDWARE_INTERFACES_KEYMINT_SUPPORT_INCLUDE_KEY_PARAM_OUTPUT_H_
+} // namespace aidl::android::hardware::security::keymint
diff --git a/security/keymint/support/include/keymint_support/keymint_tags.h b/security/keymint/support/include/keymint_support/keymint_tags.h
index f23e4f2..76aecb7 100644
--- a/security/keymint/support/include/keymint_support/keymint_tags.h
+++ b/security/keymint/support/include/keymint_support/keymint_tags.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,40 +14,32 @@
* limitations under the License.
*/
-#ifndef HARDWARE_INTERFACES_KEYMINT_SUPPORT_INCLUDE_KEYMINT_TAGS_H_
-#define HARDWARE_INTERFACES_KEYMINT_SUPPORT_INCLUDE_KEYMINT_TAGS_H_
+#pragma once
-#include <android/hardware/security/keymint/Algorithm.h>
-#include <android/hardware/security/keymint/BlockMode.h>
-#include <android/hardware/security/keymint/Digest.h>
-#include <android/hardware/security/keymint/EcCurve.h>
-#include <android/hardware/security/keymint/HardwareAuthenticatorType.h>
-#include <android/hardware/security/keymint/KeyOrigin.h>
-#include <android/hardware/security/keymint/KeyParameter.h>
-#include <android/hardware/security/keymint/KeyPurpose.h>
-#include <android/hardware/security/keymint/PaddingMode.h>
-#include <android/hardware/security/keymint/SecurityLevel.h>
-#include <android/hardware/security/keymint/Tag.h>
-#include <android/hardware/security/keymint/TagType.h>
+#include <aidl/android/hardware/security/keymint/Algorithm.h>
+#include <aidl/android/hardware/security/keymint/BlockMode.h>
+#include <aidl/android/hardware/security/keymint/Digest.h>
+#include <aidl/android/hardware/security/keymint/EcCurve.h>
+#include <aidl/android/hardware/security/keymint/HardwareAuthenticatorType.h>
+#include <aidl/android/hardware/security/keymint/KeyOrigin.h>
+#include <aidl/android/hardware/security/keymint/KeyParameter.h>
+#include <aidl/android/hardware/security/keymint/KeyPurpose.h>
+#include <aidl/android/hardware/security/keymint/PaddingMode.h>
+#include <aidl/android/hardware/security/keymint/SecurityLevel.h>
+#include <aidl/android/hardware/security/keymint/Tag.h>
+#include <aidl/android/hardware/security/keymint/TagType.h>
-namespace android::hardware::security::keymint {
-
-// The following create the numeric values that KM_TAG_PADDING and KM_TAG_DIGEST used to have. We
-// need these old values to be able to support old keys that use them.
-// TODO(seleneh) we should delete this code when we stop supporting keymaster1
-// and deletes it.
-static const int32_t KM_TAG_DIGEST_OLD = static_cast<int32_t>(TagType::ENUM) | 5;
-static const int32_t KM_TAG_PADDING_OLD = static_cast<int32_t>(TagType::ENUM) | 7;
+namespace aidl::android::hardware::security::keymint {
constexpr TagType typeFromTag(Tag tag) {
return static_cast<TagType>(static_cast<uint32_t>(tag) & static_cast<uint32_t>(0xf0000000));
}
/**
- * TypedTag is a templatized version of Tag, which provides compile-time checking of
- * keymint tag types. Instances are convertible to Tag, so they can be used wherever
- * Tag is expected, and because they encode the tag type it's possible to create
- * function overloads that only operate on tags with a particular type.
+ * TypedTag is a templatized version of Tag, which provides compile-time checking of KeyMint tag
+ * types. Instances are convertible to Tag, so they can be used wherever Tag is expected, and
+ * because they encode the tag type it's possible to create function overloads that only operate on
+ * tags with a particular type.
*/
template <TagType tag_type, Tag tag>
struct TypedTag {
@@ -66,6 +58,10 @@
typedef TypedTag<typeFromTag(tag), tag> type;
};
+#ifdef DECLARE_TYPED_TAG
+#undef DECLARE_TYPED_TAG
+#endif
+
#define DECLARE_TYPED_TAG(name) \
typedef typename Tag2TypedTag<Tag::name>::type TAG_##name##_t; \
static TAG_##name##_t TAG_##name;
@@ -80,9 +76,12 @@
DECLARE_TYPED_TAG(ATTESTATION_CHALLENGE);
DECLARE_TYPED_TAG(ATTESTATION_ID_BRAND);
DECLARE_TYPED_TAG(ATTESTATION_ID_DEVICE);
-DECLARE_TYPED_TAG(ATTESTATION_ID_PRODUCT);
+DECLARE_TYPED_TAG(ATTESTATION_ID_IMEI);
DECLARE_TYPED_TAG(ATTESTATION_ID_MANUFACTURER);
+DECLARE_TYPED_TAG(ATTESTATION_ID_MEID);
+DECLARE_TYPED_TAG(ATTESTATION_ID_PRODUCT);
DECLARE_TYPED_TAG(ATTESTATION_ID_MODEL);
+DECLARE_TYPED_TAG(ATTESTATION_ID_SERIAL);
DECLARE_TYPED_TAG(AUTH_TIMEOUT);
DECLARE_TYPED_TAG(BLOCK_MODE);
DECLARE_TYPED_TAG(BOOTLOADER_ONLY);
@@ -125,6 +124,9 @@
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
template <typename... Elems>
struct MetaList {};
@@ -141,6 +143,7 @@
TAG_OS_VERSION_t, TAG_OS_PATCHLEVEL_t, TAG_UNIQUE_ID_t, TAG_ATTESTATION_CHALLENGE_t,
TAG_ATTESTATION_APPLICATION_ID_t, TAG_ATTESTATION_ID_BRAND_t, TAG_ATTESTATION_ID_DEVICE_t,
TAG_ATTESTATION_ID_PRODUCT_t, TAG_ATTESTATION_ID_MANUFACTURER_t, TAG_ATTESTATION_ID_MODEL_t,
+ TAG_ATTESTATION_ID_SERIAL_t, TAG_ATTESTATION_ID_IMEI_t, TAG_ATTESTATION_ID_MEID_t,
TAG_RESET_SINCE_ID_ROTATION_t, TAG_PURPOSE_t, TAG_ALGORITHM_t, TAG_BLOCK_MODE_t,
TAG_DIGEST_t, TAG_PADDING_t, TAG_ORIGIN_t, TAG_USER_AUTH_TYPE_t, TAG_EC_CURVE_t,
TAG_BOOT_PATCHLEVEL_t, TAG_VENDOR_PATCHLEVEL_t, TAG_TRUSTED_CONFIRMATION_REQUIRED_t,
@@ -149,72 +152,122 @@
template <typename TypedTagType>
struct TypedTag2ValueType;
-#define MAKE_TAG_VALUE_ACCESSOR(tag_type, field_name) \
- template <Tag tag> \
- struct TypedTag2ValueType<TypedTag<tag_type, tag>> { \
- typedef decltype(static_cast<KeyParameter*>(nullptr)->field_name) type; \
- }; \
- template <Tag tag> \
- inline auto accessTagValue(TypedTag<tag_type, tag>, const KeyParameter& param) \
- ->const decltype(param.field_name)& { \
- return param.field_name; \
- } \
- template <Tag tag> \
- inline auto accessTagValue(TypedTag<tag_type, tag>, KeyParameter& param) \
- ->decltype(param.field_name)& { \
- return param.field_name; \
+#ifdef MAKE_TAG_VALUE_ACCESSOR
+#undef MAKE_TAG_VALUE_ACCESSOR
+#endif
+
+#define MAKE_TAG_VALUE_ACCESSOR(tag_type, field_name) \
+ template <Tag tag> \
+ struct TypedTag2ValueType<TypedTag<tag_type, tag>> { \
+ using type = std::remove_reference< \
+ decltype(static_cast<KeyParameterValue*>(nullptr) \
+ ->get<KeyParameterValue::field_name>())>::type; \
+ static constexpr KeyParameterValue::Tag unionTag = KeyParameterValue::field_name; \
+ }; \
+ template <Tag tag> \
+ inline std::optional<std::reference_wrapper< \
+ const typename TypedTag2ValueType<TypedTag<tag_type, tag>>::type>> \
+ accessTagValue(TypedTag<tag_type, tag>, const KeyParameter& param) { \
+ if (param.value.getTag() == KeyParameterValue::field_name) { \
+ return std::optional( \
+ std::reference_wrapper(param.value.get<KeyParameterValue::field_name>())); \
+ } else { \
+ return std::nullopt; \
+ } \
+ } \
+ template <Tag tag> \
+ inline std::optional< \
+ std::reference_wrapper<typename TypedTag2ValueType<TypedTag<tag_type, tag>>::type>> \
+ accessTagValue(TypedTag<tag_type, tag>, KeyParameter& param) { \
+ if (param.value.getTag() == KeyParameterValue::field_name) { \
+ return std::optional( \
+ std::reference_wrapper(param.value.get<KeyParameterValue::field_name>())); \
+ } else { \
+ return std::nullopt; \
+ } \
}
MAKE_TAG_VALUE_ACCESSOR(TagType::ULONG, longInteger)
MAKE_TAG_VALUE_ACCESSOR(TagType::ULONG_REP, longInteger)
-MAKE_TAG_VALUE_ACCESSOR(TagType::DATE, longInteger)
+MAKE_TAG_VALUE_ACCESSOR(TagType::DATE, dateTime)
MAKE_TAG_VALUE_ACCESSOR(TagType::UINT, integer)
MAKE_TAG_VALUE_ACCESSOR(TagType::UINT_REP, integer)
MAKE_TAG_VALUE_ACCESSOR(TagType::BOOL, boolValue)
MAKE_TAG_VALUE_ACCESSOR(TagType::BYTES, blob)
MAKE_TAG_VALUE_ACCESSOR(TagType::BIGNUM, blob)
-// TODO(seleneh) change these MAKE_TAG_ENUM_VALUE_ACCESSOR back to the 2 parameter
-// version when aidl supports union
-#define MAKE_TAG_ENUM_VALUE_ACCESSOR(typed_tag, field_name, field_type) \
- template <> \
- struct TypedTag2ValueType<decltype(typed_tag)> { \
- typedef field_type type; \
- }; \
- inline auto accessTagValue(decltype(typed_tag), const KeyParameter& param) \
- ->const field_type& { \
- return *reinterpret_cast<const field_type*>(¶m.field_name); \
- } \
- inline auto accessTagValue(decltype(typed_tag), KeyParameter& param)->field_type& { \
- return *reinterpret_cast<field_type*>(¶m.field_name); \
+#undef MAKE_TAG_VALUE_ACCESSOR
+
+#ifdef MAKE_TAG_ENUM_VALUE_ACCESSOR
+#undef MAKE_TAG_ENUM_VALUE_ACCESSOR
+#endif
+
+#define MAKE_TAG_ENUM_VALUE_ACCESSOR(typed_tag, field_name) \
+ template <> \
+ struct TypedTag2ValueType<decltype(typed_tag)> { \
+ using type = std::remove_reference< \
+ decltype(static_cast<KeyParameterValue*>(nullptr) \
+ ->get<KeyParameterValue::field_name>())>::type; \
+ static constexpr KeyParameterValue::Tag unionTag = KeyParameterValue::field_name; \
+ }; \
+ inline std::optional< \
+ std::reference_wrapper<const typename TypedTag2ValueType<decltype(typed_tag)>::type>> \
+ accessTagValue(decltype(typed_tag), const KeyParameter& param) { \
+ if (param.value.getTag() == KeyParameterValue::field_name) { \
+ return std::optional( \
+ std::reference_wrapper(param.value.get<KeyParameterValue::field_name>())); \
+ } else { \
+ return std::nullopt; \
+ } \
+ } \
+ inline std::optional< \
+ std::reference_wrapper<typename TypedTag2ValueType<decltype(typed_tag)>::type>> \
+ accessTagValue(decltype(typed_tag), KeyParameter& param) { \
+ if (param.value.getTag() == KeyParameterValue::field_name) { \
+ return std::optional( \
+ std::reference_wrapper(param.value.get<KeyParameterValue::field_name>())); \
+ } else { \
+ return std::nullopt; \
+ } \
}
-MAKE_TAG_ENUM_VALUE_ACCESSOR(TAG_ALGORITHM, integer, Algorithm)
-MAKE_TAG_ENUM_VALUE_ACCESSOR(TAG_BLOCK_MODE, integer, BlockMode)
-MAKE_TAG_ENUM_VALUE_ACCESSOR(TAG_DIGEST, integer, Digest)
-MAKE_TAG_ENUM_VALUE_ACCESSOR(TAG_EC_CURVE, integer, EcCurve)
-MAKE_TAG_ENUM_VALUE_ACCESSOR(TAG_ORIGIN, integer, KeyOrigin)
-MAKE_TAG_ENUM_VALUE_ACCESSOR(TAG_PADDING, integer, PaddingMode)
-MAKE_TAG_ENUM_VALUE_ACCESSOR(TAG_PURPOSE, integer, KeyPurpose)
-MAKE_TAG_ENUM_VALUE_ACCESSOR(TAG_USER_AUTH_TYPE, integer, HardwareAuthenticatorType)
-MAKE_TAG_ENUM_VALUE_ACCESSOR(TAG_HARDWARE_TYPE, integer, SecurityLevel)
+MAKE_TAG_ENUM_VALUE_ACCESSOR(TAG_ALGORITHM, algorithm)
+MAKE_TAG_ENUM_VALUE_ACCESSOR(TAG_BLOCK_MODE, blockMode)
+MAKE_TAG_ENUM_VALUE_ACCESSOR(TAG_DIGEST, digest)
+MAKE_TAG_ENUM_VALUE_ACCESSOR(TAG_EC_CURVE, ecCurve)
+MAKE_TAG_ENUM_VALUE_ACCESSOR(TAG_ORIGIN, origin)
+MAKE_TAG_ENUM_VALUE_ACCESSOR(TAG_PADDING, paddingMode)
+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
template <TagType tag_type, Tag tag, typename ValueT>
inline KeyParameter makeKeyParameter(TypedTag<tag_type, tag> ttag, ValueT&& value) {
- KeyParameter param;
- param.tag = tag;
- param.longInteger = 0;
- accessTagValue(ttag, param) = std::forward<ValueT>(value);
- return param;
+ KeyParameter retval;
+ retval.tag = tag;
+ retval.value = KeyParameterValue::make<TypedTag2ValueType<decltype(ttag)>::unionTag>(
+ std::forward<ValueT>(value));
+ return retval;
}
// the boolean case
template <Tag tag>
inline KeyParameter makeKeyParameter(TypedTag<TagType::BOOL, tag>) {
- KeyParameter param;
- param.tag = tag;
- param.boolValue = true;
- return param;
+ KeyParameter retval;
+ retval.tag = tag;
+ retval.value = KeyParameterValue::make<KeyParameterValue::boolValue>(true);
+ return retval;
+}
+
+// the invalid case
+inline KeyParameter makeKeyParameter(TypedTag<TagType::INVALID, Tag::INVALID>) {
+ KeyParameter retval;
+ retval.tag = Tag::INVALID;
+ retval.value = KeyParameterValue::make<KeyParameterValue::invalid>(0);
+ return retval;
}
template <typename... Pack>
@@ -247,92 +300,33 @@
return makeKeyParameter(ttag, std::forward<Args>(args)...);
}
-/**
- * This class wraps a (mostly return) value and stores whether or not the wrapped value is valid out
- * of band. Note that if the wrapped value is a reference it is unsafe to access the value if
- * !isOk(). If the wrapped type is a pointer or value and !isOk(), it is still safe to access the
- * wrapped value. In this case the pointer will be NULL though, and the value will be default
- * constructed.
- *
- * TODO(seleneh) replace this with std::optional.
- */
-template <typename ValueT>
-class NullOr {
- using internal_t = std::conditional_t<std::is_lvalue_reference<ValueT>::value,
- std::remove_reference_t<ValueT>*, ValueT>;
-
- struct pointer_initializer {
- static std::nullptr_t init() { return nullptr; }
- };
- struct value_initializer {
- static ValueT init() { return ValueT(); }
- };
- struct value_pointer_deref_t {
- static ValueT& deref(ValueT& v) { return v; }
- };
- struct reference_deref_t {
- static auto& deref(internal_t v) { return *v; }
- };
- using initializer_t = std::conditional_t<std::is_lvalue_reference<ValueT>::value ||
- std::is_pointer<ValueT>::value,
- pointer_initializer, value_initializer>;
- using deref_t = std::conditional_t<std::is_lvalue_reference<ValueT>::value, reference_deref_t,
- value_pointer_deref_t>;
-
- public:
- NullOr() : value_(initializer_t::init()), null_(true) {}
- template <typename T>
- NullOr(T&& value, typename std::enable_if<
- !std::is_lvalue_reference<ValueT>::value &&
- std::is_same<std::decay_t<ValueT>, std::decay_t<T>>::value,
- int>::type = 0)
- : value_(std::forward<ValueT>(value)), null_(false) {}
- template <typename T>
- NullOr(T& value, typename std::enable_if<
- std::is_lvalue_reference<ValueT>::value &&
- std::is_same<std::decay_t<ValueT>, std::decay_t<T>>::value,
- int>::type = 0)
- : value_(&value), null_(false) {}
-
- bool isOk() const { return !null_; }
-
- const ValueT& value() const& { return deref_t::deref(value_); }
- ValueT& value() & { return deref_t::deref(value_); }
- ValueT&& value() && { return std::move(deref_t::deref(value_)); }
-
- private:
- internal_t value_;
- bool null_;
-};
-
template <typename T>
std::remove_reference_t<T> NullOrOr(T&& v) {
- if (v.isOk()) return v;
+ if (v) return v;
return {};
}
template <typename Head, typename... Tail>
std::remove_reference_t<Head> NullOrOr(Head&& head, Tail&&... tail) {
- if (head.isOk()) return head;
+ if (head) return head;
return NullOrOr(std::forward<Tail>(tail)...);
}
template <typename Default, typename Wrapped>
-std::remove_reference_t<Wrapped> defaultOr(NullOr<Wrapped>&& optional, Default&& def) {
+std::remove_reference_t<Wrapped> defaultOr(std::optional<Wrapped>&& optional, Default&& def) {
static_assert(std::is_convertible<std::remove_reference_t<Default>,
std::remove_reference_t<Wrapped>>::value,
- "Type of default value must match the type wrapped by NullOr");
- if (optional.isOk()) return optional.value();
+ "Type of default value must match the type wrapped by std::optional");
+ if (optional) return *optional;
return def;
}
template <TagType tag_type, Tag tag>
-inline NullOr<const typename TypedTag2ValueType<TypedTag<tag_type, tag>>::type&> authorizationValue(
- TypedTag<tag_type, tag> ttag, const KeyParameter& param) {
- if (tag != param.tag) return {};
+inline std::optional<
+ std::reference_wrapper<const typename TypedTag2ValueType<TypedTag<tag_type, tag>>::type>>
+authorizationValue(TypedTag<tag_type, tag> ttag, const KeyParameter& param) {
+ if (TypedTag2ValueType<TypedTag<tag_type, tag>>::unionTag != param.value.getTag()) return {};
return accessTagValue(ttag, param);
}
-} // namespace android::hardware::security::keymint
-
-#endif // HARDWARE_INTERFACES_KEYMINT_SUPPORT_INCLUDE_KEYMINT_TAGS_H_
+} // namespace aidl::android::hardware::security::keymint
diff --git a/security/keymint/support/include/keymint_support/keymint_utils.h b/security/keymint/support/include/keymint_support/keymint_utils.h
index fda1b6c..53d5b96 100644
--- a/security/keymint/support/include/keymint_support/keymint_utils.h
+++ b/security/keymint/support/include/keymint_support/keymint_utils.h
@@ -16,12 +16,9 @@
#pragma once
-#ifndef HARDWARE_INTERFACES_KEYMINT_10_SUPPORT_KEYMINT_UTILS_H_
-#define HARDWARE_INTERFACES_KEYMINT_10_SUPPORT_KEYMINT_UTILS_H_
+#include <aidl/android/hardware/security/keymint/HardwareAuthToken.h>
-#include <android/hardware/security/keymint/HardwareAuthToken.h>
-
-namespace android::hardware::security::keymint {
+namespace aidl::android::hardware::security::keymint {
using std::vector;
@@ -42,6 +39,4 @@
uint32_t getOsVersion();
uint32_t getOsPatchlevel();
-} // namespace android::hardware::security::keymint
-
-#endif // HARDWARE_INTERFACES_KEYMINT_10_SUPPORT_KEYMINT_UTILS_H_
+} // namespace aidl::android::hardware::security::keymint
diff --git a/security/keymint/support/include/keymint_support/openssl_utils.h b/security/keymint/support/include/keymint_support/openssl_utils.h
index cb09968..9ae7e52 100644
--- a/security/keymint/support/include/keymint_support/openssl_utils.h
+++ b/security/keymint/support/include/keymint_support/openssl_utils.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 The Android Open Source Project
+ * Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,14 @@
* limitations under the License.
*/
-#ifndef HARDWARE_INTERFACES_KEYMINT_1_0_SUPPORT_OPENSSL_UTILS_H_
-#define HARDWARE_INTERFACES_KEYMINT_1_0_SUPPORT_OPENSSL_UTILS_H_
+#pragma once
-#include <android/hardware/security/keymint/Digest.h>
+#include <aidl/android/hardware/security/keymint/Digest.h>
#include <openssl/evp.h>
#include <openssl/x509.h>
-namespace android::hardware::security::keymint {
+namespace aidl::android::hardware::security::keymint {
template <typename T, void (*F)(T*)>
struct UniquePtrDeleter {
@@ -62,6 +61,4 @@
return nullptr;
}
-} // namespace android::hardware::security::keymint
-
-#endif // HARDWARE_INTERFACES_KEYMINT_1_0_SUPPORT_OPENSSL_UTILS_H_
+} // namespace aidl::android::hardware::security::keymint
diff --git a/security/keymint/support/key_param_output.cpp b/security/keymint/support/key_param_output.cpp
index b699b22..0950eb6 100644
--- a/security/keymint/support/key_param_output.cpp
+++ b/security/keymint/support/key_param_output.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,7 +20,7 @@
#include <keymint_support/keymint_tags.h>
-namespace android::hardware::security::keymint {
+namespace aidl::android::hardware::security::keymint {
using ::std::endl;
using ::std::ostream;
@@ -35,38 +35,8 @@
return os;
}
-// TODO(seleneh) update this to a parsing that looks at each tags individually
-// such as ALGORITHM BLOCK_MODE when aidl union support is added.
ostream& operator<<(ostream& os, const KeyParameter& param) {
- os << param.tag << ": ";
- switch (typeFromTag(param.tag)) {
- case TagType::INVALID:
- return os << " Invalid";
- case TagType::ENUM_REP:
- case TagType::ENUM:
- case TagType::UINT_REP:
- case TagType::UINT:
- return os << param.integer;
- case TagType::ULONG_REP:
- case TagType::ULONG:
- case TagType::DATE:
- return os << param.longInteger;
- case TagType::BOOL:
- return os << "true";
- case TagType::BIGNUM:
- os << " Bignum: ";
- for (size_t i = 0; i < param.blob.size(); ++i) {
- os << std::hex << ::std::setw(2) << static_cast<int>(param.blob[i]) << ::std::dec;
- }
- return os;
- case TagType::BYTES:
- os << " Bytes: ";
- for (size_t i = 0; i < param.blob.size(); ++i) {
- os << ::std::hex << ::std::setw(2) << static_cast<int>(param.blob[i]) << ::std::dec;
- }
- return os;
- }
- return os << "UNKNOWN TAG TYPE!";
+ return os << param.toString();
}
-} // namespace android::hardware::security::keymint
+} // namespace aidl::android::hardware::security::keymint
diff --git a/security/keymint/support/keymint_utils.cpp b/security/keymint/support/keymint_utils.cpp
index cd4cca2..e73d602 100644
--- a/security/keymint/support/keymint_utils.cpp
+++ b/security/keymint/support/keymint_utils.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,11 +18,8 @@
#include <android-base/properties.h>
#include <hardware/hw_auth_token.h>
-#include <keymint_support/keymint_utils.h>
-#include <arpa/inet.h>
-
-namespace android::hardware::security::keymint {
+namespace aidl::android::hardware::security::keymint {
namespace {
@@ -111,4 +108,4 @@
return getOsPatchlevel(patchlevel.c_str());
}
-} // namespace android::hardware::security::keymint
+} // namespace aidl::android::hardware::security::keymint
diff --git a/security/secureclock/aidl/Android.bp b/security/secureclock/aidl/Android.bp
new file mode 100644
index 0000000..5a6d7ae
--- /dev/null
+++ b/security/secureclock/aidl/Android.bp
@@ -0,0 +1,21 @@
+aidl_interface {
+ name: "android.hardware.security.secureclock",
+ vendor_available: true,
+ srcs: [
+ "android/hardware/security/secureclock/*.aidl",
+ ],
+ stability: "vintf",
+ backend: {
+ java: {
+ sdk_version: "module_current",
+ },
+ ndk: {
+ vndk: {
+ enabled: true,
+ },
+ },
+ rust: {
+ enabled: true,
+ },
+ },
+}
diff --git a/security/secureclock/aidl/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..51b1824
--- /dev/null
+++ b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/TimeStampToken.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.secureclock;
+@VintfStability
+parcelable TimeStampToken {
+ long challenge;
+ android.hardware.security.secureclock.Timestamp timestamp;
+ byte[] mac;
+}
diff --git a/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/Timestamp.aidl b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/Timestamp.aidl
new file mode 100644
index 0000000..50b8b9f
--- /dev/null
+++ b/security/secureclock/aidl/aidl_api/android.hardware.security.secureclock/current/android/hardware/security/secureclock/Timestamp.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.security.secureclock;
+@VintfStability
+parcelable Timestamp {
+ long milliSeconds;
+}
diff --git a/security/secureclock/aidl/android/hardware/security/secureclock/ISecureClock.aidl b/security/secureclock/aidl/android/hardware/security/secureclock/ISecureClock.aidl
new file mode 100644
index 0000000..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..b24d335
--- /dev/null
+++ b/security/secureclock/aidl/android/hardware/security/secureclock/TimeStampToken.aidl
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.security.secureclock;
+
+import android.hardware.security.secureclock.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;
+
+ /**
+ * 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/keymint/aidl/android/hardware/security/keymint/Timestamp.aidl b/security/secureclock/aidl/android/hardware/security/secureclock/Timestamp.aidl
similarity index 95%
rename from security/keymint/aidl/android/hardware/security/keymint/Timestamp.aidl
rename to security/secureclock/aidl/android/hardware/security/secureclock/Timestamp.aidl
index ebb3684..7bd1f9e 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/Timestamp.aidl
+++ b/security/secureclock/aidl/android/hardware/security/secureclock/Timestamp.aidl
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.hardware.security.keymint;
+package android.hardware.security.secureclock;
/**
* Time in milliseconds since some arbitrary point in time. Time must be monotonically increasing,
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..eb48fd3 100644
--- a/tests/lazy/1.1/ILazy.hal
+++ b/tests/lazy/1.1/ILazy.hal
@@ -18,4 +18,12 @@
import android.hardware.tests.lazy@1.0;
-interface ILazy extends @1.0::ILazy {};
+interface ILazy extends @1.0::ILazy {
+ /**
+ * Ask the process hosting the service to install a callback that notifies if there are
+ * services with clients.
+ * For testing purposes, this callback exercises the code to unregister/re-register
+ * the services and eventually shuts down the process.
+ */
+ setCustomActiveServicesCallback();
+};
diff --git a/tv/input/1.0/default/TvInput.cpp b/tv/input/1.0/default/TvInput.cpp
index 4ea1dec..7583a67 100644
--- a/tv/input/1.0/default/TvInput.cpp
+++ b/tv/input/1.0/default/TvInput.cpp
@@ -142,7 +142,7 @@
// static
void TvInput::notify(struct tv_input_device* __unused, tv_input_event_t* event,
- void* __unused) {
+ void* optionalStatus) {
if (mCallback != nullptr && event != nullptr) {
// Capturing is no longer supported.
if (event->type >= TV_INPUT_EVENT_CAPTURE_SUCCEEDED) {
@@ -154,7 +154,17 @@
tvInputEvent.deviceInfo.type = static_cast<TvInputType>(
event->device_info.type);
tvInputEvent.deviceInfo.portId = event->device_info.hdmi.port_id;
- tvInputEvent.deviceInfo.cableConnectionStatus = CableConnectionStatus::UNKNOWN;
+ CableConnectionStatus connectionStatus = CableConnectionStatus::UNKNOWN;
+ if (optionalStatus != nullptr &&
+ ((event->type == TV_INPUT_EVENT_STREAM_CONFIGURATIONS_CHANGED) ||
+ (event->type == TV_INPUT_EVENT_DEVICE_AVAILABLE))) {
+ int newStatus = *reinterpret_cast<int*>(optionalStatus);
+ if (newStatus <= static_cast<int>(CableConnectionStatus::DISCONNECTED) &&
+ newStatus >= static_cast<int>(CableConnectionStatus::UNKNOWN)) {
+ connectionStatus = static_cast<CableConnectionStatus>(newStatus);
+ }
+ }
+ tvInputEvent.deviceInfo.cableConnectionStatus = connectionStatus;
// TODO: Ensure the legacy audio type code is the same once audio HAL default
// implementation is ready.
tvInputEvent.deviceInfo.audioType = static_cast<AudioDevice>(
diff --git a/tv/tuner/1.0/vts/functional/DvrTests.cpp b/tv/tuner/1.0/vts/functional/DvrTests.cpp
index 0dfc032..ba21189 100644
--- a/tv/tuner/1.0/vts/functional/DvrTests.cpp
+++ b/tv/tuner/1.0/vts/functional/DvrTests.cpp
@@ -55,6 +55,7 @@
uint8_t* buffer;
ALOGW("[vts] playback thread loop start %s", mInputDataFile.c_str());
if (fd < 0) {
+ EXPECT_TRUE(fd >= 0) << "Failed to open: " + mInputDataFile;
mPlaybackThreadRunning = false;
ALOGW("[vts] Error %s", strerror(errno));
}
@@ -178,7 +179,7 @@
// Our current implementation filter the data and write it into the filter FMQ
// immediately after the DATA_READY from the VTS/framework
if (!readRecordFMQ()) {
- ALOGD("[vts] record data failed to be filtered. Ending thread");
+ ALOGW("[vts] record data failed to be filtered. Ending thread");
mRecordThreadRunning = false;
break;
}
diff --git a/tv/tuner/1.0/vts/functional/FilterTests.cpp b/tv/tuner/1.0/vts/functional/FilterTests.cpp
index 0ecdf73..a354c78 100644
--- a/tv/tuner/1.0/vts/functional/FilterTests.cpp
+++ b/tv/tuner/1.0/vts/functional/FilterTests.cpp
@@ -70,6 +70,10 @@
}
bool FilterCallback::readFilterEventData() {
+ if (mFilterMQ == NULL) {
+ ALOGW("[vts] FMQ is not configured and does not need to be tested.");
+ return true;
+ }
bool result = false;
DemuxFilterEvent filterEvent = mFilterEvent;
ALOGW("[vts] reading from filter FMQ or buffer %d", mFilterId);
@@ -218,7 +222,11 @@
return AssertionResult(status == Result::SUCCESS);
}
-AssertionResult FilterTests::getFilterMQDescriptor(uint32_t filterId) {
+AssertionResult FilterTests::getFilterMQDescriptor(uint32_t filterId, bool getMqDesc) {
+ if (!getMqDesc) {
+ ALOGE("[vts] Filter does not need FMQ.");
+ return success();
+ }
Result status;
EXPECT_TRUE(mFilters[filterId]) << "Test with getNewlyOpenedFilterId first.";
EXPECT_TRUE(mFilterCallbacks[filterId]) << "Test with getNewlyOpenedFilterId first.";
@@ -279,16 +287,14 @@
AssertionResult FilterTests::closeFilter(uint32_t filterId) {
EXPECT_TRUE(mFilters[filterId]) << "Test with getNewlyOpenedFilterId first.";
Result status = mFilters[filterId]->close();
- if (status == Result::SUCCESS) {
- for (int i = 0; i < mUsedFilterIds.size(); i++) {
- if (mUsedFilterIds[i] == filterId) {
- mUsedFilterIds.erase(mUsedFilterIds.begin() + i);
- break;
- }
+ for (int i = 0; i < mUsedFilterIds.size(); i++) {
+ if (mUsedFilterIds[i] == filterId) {
+ mUsedFilterIds.erase(mUsedFilterIds.begin() + i);
+ break;
}
- mFilterCallbacks.erase(filterId);
- mFilters.erase(filterId);
}
+ mFilterCallbacks.erase(filterId);
+ mFilters.erase(filterId);
return AssertionResult(status == Result::SUCCESS);
}
diff --git a/tv/tuner/1.0/vts/functional/FilterTests.h b/tv/tuner/1.0/vts/functional/FilterTests.h
index a76a6b9..75c59b3 100644
--- a/tv/tuner/1.0/vts/functional/FilterTests.h
+++ b/tv/tuner/1.0/vts/functional/FilterTests.h
@@ -157,7 +157,7 @@
AssertionResult getTimeStamp();
AssertionResult getNewlyOpenedFilterId(uint32_t& filterId);
AssertionResult configFilter(DemuxFilterSettings setting, uint32_t filterId);
- AssertionResult getFilterMQDescriptor(uint32_t filterId);
+ AssertionResult getFilterMQDescriptor(uint32_t filterId, bool getMqDesc);
AssertionResult setFilterDataSource(uint32_t sourceFilterId, uint32_t sinkFilterId);
AssertionResult setFilterDataSourceToDemux(uint32_t filterId);
AssertionResult startFilter(uint32_t filterId);
diff --git a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp
index 2be68b8..22ba271 100644
--- a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp
+++ b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp
@@ -48,7 +48,7 @@
ASSERT_TRUE(mFilterTests.openFilterInDemux(filterConf.type, filterConf.bufferSize));
ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId(filterId));
ASSERT_TRUE(mFilterTests.configFilter(filterConf.settings, filterId));
- ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId));
+ ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId, filterConf.getMqDesc));
ASSERT_TRUE(mFilterTests.startFilter(filterId));
ASSERT_TRUE(mFilterTests.stopFilter(filterId));
ASSERT_TRUE(mFilterTests.closeFilter(filterId));
@@ -75,6 +75,9 @@
void TunerBroadcastHidlTest::broadcastSingleFilterTest(FilterConfig filterConf,
FrontendConfig frontendConf) {
+ if (!frontendConf.enable) {
+ return;
+ }
uint32_t feId;
uint32_t demuxId;
sp<IDemux> demux;
@@ -99,7 +102,7 @@
ASSERT_TRUE(mFilterTests.openFilterInDemux(filterConf.type, filterConf.bufferSize));
ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId(filterId));
ASSERT_TRUE(mFilterTests.configFilter(filterConf.settings, filterId));
- ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId));
+ ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId, filterConf.getMqDesc));
ASSERT_TRUE(mFilterTests.startFilter(filterId));
// tune test
ASSERT_TRUE(mFrontendTests.tuneFrontend(frontendConf, true /*testWithDemux*/));
@@ -145,7 +148,7 @@
ASSERT_TRUE(mFilterTests.openFilterInDemux(filterConf.type, filterConf.bufferSize));
ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId(filterId));
ASSERT_TRUE(mFilterTests.configFilter(filterConf.settings, filterId));
- ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId));
+ ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId, filterConf.getMqDesc));
mDvrTests.startPlaybackInputThread(dvrConf.playbackInputFile, dvrConf.settings.playback());
ASSERT_TRUE(mDvrTests.startDvrPlayback());
ASSERT_TRUE(mFilterTests.startFilter(filterId));
@@ -160,6 +163,9 @@
void TunerRecordHidlTest::recordSingleFilterTest(FilterConfig filterConf,
FrontendConfig frontendConf, DvrConfig dvrConf) {
+ if (!frontendConf.enable) {
+ return;
+ }
uint32_t feId;
uint32_t demuxId;
sp<IDemux> demux;
@@ -184,7 +190,7 @@
ASSERT_TRUE(mFilterTests.openFilterInDemux(filterConf.type, filterConf.bufferSize));
ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId(filterId));
ASSERT_TRUE(mFilterTests.configFilter(filterConf.settings, filterId));
- ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId));
+ ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId, filterConf.getMqDesc));
filter = mFilterTests.getFilterById(filterId);
ASSERT_TRUE(filter != nullptr);
mDvrTests.startRecordOutputThread(dvrConf.settings.record());
@@ -247,7 +253,7 @@
ASSERT_TRUE(mFilterTests.openFilterInDemux(filterConf.type, filterConf.bufferSize));
ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId(filterId));
ASSERT_TRUE(mFilterTests.configFilter(filterConf.settings, filterId));
- ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId));
+ ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId, filterConf.getMqDesc));
filter = mFilterTests.getFilterById(filterId);
ASSERT_TRUE(filter != nullptr);
ASSERT_TRUE(mDvrTests.attachFilterToDvr(filter));
@@ -265,6 +271,9 @@
void TunerDescramblerHidlTest::scrambledBroadcastTest(set<struct FilterConfig> mediaFilterConfs,
FrontendConfig frontendConf,
DescramblerConfig descConfig) {
+ if (!frontendConf.enable) {
+ return;
+ }
uint32_t feId;
uint32_t demuxId;
sp<IDemux> demux;
@@ -328,17 +337,17 @@
TEST_P(TunerFrontendHidlTest, TuneFrontend) {
description("Tune one Frontend with specific setting and check Lock event");
- mFrontendTests.tuneTest(frontendArray[DVBT]);
+ mFrontendTests.tuneTest(frontendArray[defaultFrontend]);
}
TEST_P(TunerFrontendHidlTest, AutoScanFrontend) {
description("Run an auto frontend scan with specific setting and check lock scanMessage");
- mFrontendTests.scanTest(frontendScanArray[SCAN_DVBT], FrontendScanType::SCAN_AUTO);
+ mFrontendTests.scanTest(frontendScanArray[defaultScanFrontend], FrontendScanType::SCAN_AUTO);
}
TEST_P(TunerFrontendHidlTest, BlindScanFrontend) {
description("Run an blind frontend scan with specific setting and check lock scanMessage");
- mFrontendTests.scanTest(frontendScanArray[SCAN_DVBT], FrontendScanType::SCAN_BLIND);
+ mFrontendTests.scanTest(frontendScanArray[defaultScanFrontend], FrontendScanType::SCAN_BLIND);
}
TEST_P(TunerLnbHidlTest, OpenLnbByName) {
@@ -374,7 +383,7 @@
uint32_t feId;
uint32_t demuxId;
sp<IDemux> demux;
- mFrontendTests.getFrontendIdByType(frontendArray[DVBT].type, feId);
+ mFrontendTests.getFrontendIdByType(frontendArray[defaultFrontend].type, feId);
ASSERT_TRUE(feId != INVALID_ID);
ASSERT_TRUE(mFrontendTests.openFrontendById(feId));
ASSERT_TRUE(mFrontendTests.setFrontendCallback());
@@ -394,7 +403,7 @@
uint32_t avSyncHwId;
sp<IFilter> mediaFilter;
- mFrontendTests.getFrontendIdByType(frontendArray[DVBT].type, feId);
+ mFrontendTests.getFrontendIdByType(frontendArray[defaultFrontend].type, feId);
ASSERT_TRUE(feId != INVALID_ID);
ASSERT_TRUE(mFrontendTests.openFrontendById(feId));
ASSERT_TRUE(mFrontendTests.setFrontendCallback());
@@ -422,7 +431,7 @@
TEST_P(TunerFilterHidlTest, StartFilterInDemux) {
description("Open and start a filter in Demux.");
// TODO use paramterized tests
- configSingleFilterInDemuxTest(filterArray[TS_VIDEO0], frontendArray[DVBT]);
+ configSingleFilterInDemuxTest(filterArray[TS_VIDEO0], frontendArray[defaultFrontend]);
}
TEST_P(TunerFilterHidlTest, SetFilterLinkage) {
@@ -463,22 +472,22 @@
TEST_P(TunerBroadcastHidlTest, BroadcastDataFlowVideoFilterTest) {
description("Test Video Filter functionality in Broadcast use case.");
- broadcastSingleFilterTest(filterArray[TS_VIDEO1], frontendArray[DVBT]);
+ broadcastSingleFilterTest(filterArray[TS_VIDEO1], frontendArray[defaultFrontend]);
}
TEST_P(TunerBroadcastHidlTest, BroadcastDataFlowAudioFilterTest) {
description("Test Audio Filter functionality in Broadcast use case.");
- broadcastSingleFilterTest(filterArray[TS_AUDIO0], frontendArray[DVBT]);
+ broadcastSingleFilterTest(filterArray[TS_AUDIO0], frontendArray[defaultFrontend]);
}
TEST_P(TunerBroadcastHidlTest, BroadcastDataFlowSectionFilterTest) {
description("Test Section Filter functionality in Broadcast use case.");
- broadcastSingleFilterTest(filterArray[TS_SECTION0], frontendArray[DVBT]);
+ broadcastSingleFilterTest(filterArray[TS_SECTION0], frontendArray[defaultFrontend]);
}
TEST_P(TunerBroadcastHidlTest, IonBufferTest) {
description("Test the av filter data bufferring.");
- broadcastSingleFilterTest(filterArray[TS_VIDEO0], frontendArray[DVBT]);
+ broadcastSingleFilterTest(filterArray[TS_VIDEO0], frontendArray[defaultFrontend]);
}
TEST_P(TunerBroadcastHidlTest, LnbBroadcastDataFlowVideoFilterTest) {
@@ -494,13 +503,14 @@
TEST_P(TunerRecordHidlTest, AttachFiltersToRecordTest) {
description("Attach a single filter to the record dvr test.");
// TODO use paramterized tests
- attachSingleFilterToRecordDvrTest(filterArray[TS_RECORD0], frontendArray[DVBT],
+ attachSingleFilterToRecordDvrTest(filterArray[TS_RECORD0], frontendArray[defaultFrontend],
dvrArray[DVR_RECORD0]);
}
TEST_P(TunerRecordHidlTest, RecordDataFlowWithTsRecordFilterTest) {
description("Feed ts data from frontend to recording and test with ts record filter");
- recordSingleFilterTest(filterArray[TS_RECORD0], frontendArray[DVBT], dvrArray[DVR_RECORD0]);
+ recordSingleFilterTest(filterArray[TS_RECORD0], frontendArray[defaultFrontend],
+ dvrArray[DVR_RECORD0]);
}
TEST_P(TunerRecordHidlTest, LnbRecordDataFlowWithTsRecordFilterTest) {
@@ -513,7 +523,7 @@
uint32_t feId;
uint32_t demuxId;
sp<IDemux> demux;
- mFrontendTests.getFrontendIdByType(frontendArray[DVBT].type, feId);
+ mFrontendTests.getFrontendIdByType(frontendArray[defaultFrontend].type, feId);
ASSERT_TRUE(feId != INVALID_ID);
ASSERT_TRUE(mFrontendTests.openFrontendById(feId));
ASSERT_TRUE(mFrontendTests.setFrontendCallback());
@@ -530,7 +540,7 @@
set<FilterConfig> filterConfs;
filterConfs.insert(filterArray[TS_AUDIO0]);
filterConfs.insert(filterArray[TS_VIDEO1]);
- scrambledBroadcastTest(filterConfs, frontendArray[DVBT], descramblerArray[DESC_0]);
+ scrambledBroadcastTest(filterConfs, frontendArray[defaultFrontend], descramblerArray[DESC_0]);
}
INSTANTIATE_TEST_SUITE_P(
diff --git a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TestConfigurations.h b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TestConfigurations.h
index 6c68e35..92a8130 100644
--- a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TestConfigurations.h
+++ b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TestConfigurations.h
@@ -55,6 +55,7 @@
using namespace std;
+const uint32_t FMQ_SIZE_512K = 0x80000;
const uint32_t FMQ_SIZE_1M = 0x100000;
const uint32_t FMQ_SIZE_4M = 0x400000;
const uint32_t FMQ_SIZE_16M = 0x1000000;
@@ -134,6 +135,7 @@
uint32_t bufferSize;
DemuxFilterType type;
DemuxFilterSettings settings;
+ bool getMqDesc;
bool operator<(const FilterConfig& /*c*/) const { return false; }
};
@@ -144,6 +146,7 @@
};
struct FrontendConfig {
+ bool enable;
bool isSoftwareFe;
FrontendType type;
FrontendSettings settings;
@@ -191,6 +194,8 @@
static DvrConfig dvrArray[DVR_MAX];
static DescramblerConfig descramblerArray[DESC_MAX];
static vector<string> goldenOutputFiles;
+static int defaultFrontend = DVBT;
+static int defaultScanFrontend = SCAN_DVBT;
/** Configuration array for the frontend tune test */
inline void initFrontendConfig() {
@@ -216,7 +221,9 @@
frontendArray[DVBT].tuneStatusTypes = types;
frontendArray[DVBT].expectTuneStatuses = statuses;
frontendArray[DVBT].isSoftwareFe = true;
+ frontendArray[DVBS].enable = true;
frontendArray[DVBS].type = FrontendType::DVBS;
+ frontendArray[DVBS].enable = true;
frontendArray[DVBS].isSoftwareFe = true;
};
@@ -283,6 +290,7 @@
.isRaw = false,
.streamId = 0xbd,
});
+ filterArray[TS_PES0].getMqDesc = true;
// TS PCR filter setting
filterArray[TS_PCR0].type.mainType = DemuxFilterMainType::TS;
filterArray[TS_PCR0].type.subType.tsFilterType(DemuxTsFilterType::PCR);
@@ -303,6 +311,7 @@
filterArray[TS_SECTION0].settings.ts().filterSettings.section({
.isRaw = false,
});
+ filterArray[TS_SECTION0].getMqDesc = true;
// TS RECORD filter setting
filterArray[TS_RECORD0].type.mainType = DemuxFilterMainType::TS;
filterArray[TS_RECORD0].type.subType.tsFilterType(DemuxTsFilterType::RECORD);
diff --git a/weaver/aidl/Android.bp b/weaver/aidl/Android.bp
new file mode 100644
index 0000000..5637e0a
--- /dev/null
+++ b/weaver/aidl/Android.bp
@@ -0,0 +1,16 @@
+aidl_interface {
+ name: "android.hardware.weaver",
+ vendor_available: true,
+ srcs: ["android/hardware/weaver/*.aidl"],
+ stability: "vintf",
+ backend: {
+ java: {
+ platform_apis: true,
+ },
+ ndk: {
+ vndk: {
+ enabled: true,
+ },
+ },
+ },
+}
diff --git a/weaver/aidl/aidl_api/android.hardware.weaver/current/android/hardware/weaver/IWeaver.aidl b/weaver/aidl/aidl_api/android.hardware.weaver/current/android/hardware/weaver/IWeaver.aidl
new file mode 100644
index 0000000..29bd9a9
--- /dev/null
+++ b/weaver/aidl/aidl_api/android.hardware.weaver/current/android/hardware/weaver/IWeaver.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.weaver;
+@VintfStability
+interface IWeaver {
+ android.hardware.weaver.WeaverConfig getConfig();
+ android.hardware.weaver.WeaverReadResponse read(in int slotId, in byte[] key);
+ void write(in int slotId, in byte[] key, in byte[] value);
+ const int STATUS_FAILED = 1;
+ const int INCORRECT_KEY = 2;
+ const int THROTTLE = 3;
+}
diff --git a/weaver/aidl/aidl_api/android.hardware.weaver/current/android/hardware/weaver/WeaverConfig.aidl b/weaver/aidl/aidl_api/android.hardware.weaver/current/android/hardware/weaver/WeaverConfig.aidl
new file mode 100644
index 0000000..239cdac
--- /dev/null
+++ b/weaver/aidl/aidl_api/android.hardware.weaver/current/android/hardware/weaver/WeaverConfig.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.weaver;
+@VintfStability
+parcelable WeaverConfig {
+ long slots;
+ long keySize;
+ long valueSize;
+}
diff --git a/weaver/aidl/aidl_api/android.hardware.weaver/current/android/hardware/weaver/WeaverReadResponse.aidl b/weaver/aidl/aidl_api/android.hardware.weaver/current/android/hardware/weaver/WeaverReadResponse.aidl
new file mode 100644
index 0000000..7e5db59
--- /dev/null
+++ b/weaver/aidl/aidl_api/android.hardware.weaver/current/android/hardware/weaver/WeaverReadResponse.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.weaver;
+@VintfStability
+parcelable WeaverReadResponse {
+ long timeout;
+ byte[] value;
+}
diff --git a/weaver/aidl/android/hardware/weaver/IWeaver.aidl b/weaver/aidl/android/hardware/weaver/IWeaver.aidl
new file mode 100644
index 0000000..ebbfabe
--- /dev/null
+++ b/weaver/aidl/android/hardware/weaver/IWeaver.aidl
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.weaver;
+
+import android.hardware.weaver.WeaverConfig;
+import android.hardware.weaver.WeaverReadResponse;
+
+/**
+ * Weaver provides secure storage of secret values that may only be read if the
+ * corresponding key has been presented.
+ *
+ * The storage must be secure as the device's user authentication and encryption
+ * relies on the security of these values. The cardinality of the domains of the
+ * key and value must be suitably large such that they cannot be easily guessed.
+ *
+ * Weaver is structured as an array of slots, each containing a key-value pair.
+ * Slots are uniquely identified by an ID in the range [0, `getConfig().slots`).
+ */
+@VintfStability
+interface IWeaver {
+ /**
+ * Retrieves the config information for this implementation of Weaver.
+ *
+ * The config is static i.e. every invocation returns the same information.
+ *
+ * @return config data for this implementation of Weaver if status is OK,
+ * otherwise undefined.
+ */
+ WeaverConfig getConfig();
+
+ /**
+ * Read binder calls may return a ServiceSpecificException with the following error codes.
+ */
+ const int STATUS_FAILED = 1;
+ const int INCORRECT_KEY = 2;
+ const int THROTTLE = 3;
+
+ /**
+ * Attempts to retrieve the value stored in the identified slot.
+ *
+ * The value is only returned if the provided key matches the key stored in
+ * the slot. The value is never returned if the wrong key is provided.
+ *
+ * Throttling must be used to limit the frequency of failed read attempts.
+ * The value is only returned when throttling is not active, even if the
+ * correct key is provided. If called when throttling is active, the time
+ * until the next attempt can be made is returned.
+ *
+ * Service status return:
+ *
+ * OK if the value was successfully read from slot.
+ * INCORRECT_KEY if the key does not match the key in the slot.
+ * THROTTLE if throttling is active.
+ * STATUS_FAILED if the read was unsuccessful for another reason.
+ *
+ * @param slotId of the slot to read from, this must be positive to be valid.
+ * @param key that is stored in the slot.
+ * @return The WeaverReadResponse for this read request. If the status is OK,
+ * value is set to the value in the slot and timeout is 0. Otherwise, value is
+ * empty and timeout is set accordingly.
+ */
+ WeaverReadResponse read(in int slotId, in byte[] key);
+
+ /**
+ * Overwrites the identified slot with the provided key and value.
+ *
+ * The new values are written regardless of the current state of the slot in
+ * order to remain idempotent.
+ *
+ * Service status return:
+ *
+ * OK if the write was successfully completed.
+ * FAILED if the write was unsuccessful.
+ *
+ * @param slotId of the slot to write to.
+ * @param key to write to the slot.
+ * @param value to write to slot.
+ */
+ void write(in int slotId, in byte[] key, in byte[] value);
+}
diff --git a/health/1.0/default/libhealthd/healthd_board_default.cpp b/weaver/aidl/android/hardware/weaver/WeaverConfig.aidl
similarity index 61%
copy from health/1.0/default/libhealthd/healthd_board_default.cpp
copy to weaver/aidl/android/hardware/weaver/WeaverConfig.aidl
index 127f98e..75d961e 100644
--- a/health/1.0/default/libhealthd/healthd_board_default.cpp
+++ b/weaver/aidl/android/hardware/weaver/WeaverConfig.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,21 @@
* limitations under the License.
*/
-#include <healthd/healthd.h>
+package android.hardware.weaver;
-void healthd_board_init(struct healthd_config*)
-{
- // use defaults
+@VintfStability
+parcelable WeaverConfig {
+ /**
+ * The number of slots available.
+ */
+ long slots;
+ /**
+ * The number of bytes used for a key.
+ */
+ long keySize;
+ /**
+ * The number of bytes used for a value.
+ */
+ long valueSize;
}
-int healthd_board_battery_update(struct android::BatteryProperties*)
-{
- // return 0 to log periodic polled battery status to kernel log
- return 0;
-}
diff --git a/health/1.0/default/libhealthd/healthd_board_default.cpp b/weaver/aidl/android/hardware/weaver/WeaverReadResponse.aidl
similarity index 61%
copy from health/1.0/default/libhealthd/healthd_board_default.cpp
copy to weaver/aidl/android/hardware/weaver/WeaverReadResponse.aidl
index 127f98e..ec006e8 100644
--- a/health/1.0/default/libhealthd/healthd_board_default.cpp
+++ b/weaver/aidl/android/hardware/weaver/WeaverReadResponse.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,17 @@
* limitations under the License.
*/
-#include <healthd/healthd.h>
+package android.hardware.weaver;
-void healthd_board_init(struct healthd_config*)
-{
- // use defaults
+@VintfStability
+parcelable WeaverReadResponse {
+ /**
+ * The time to wait, in milliseconds, before making the next request.
+ */
+ long timeout;
+ /**
+ * The value read from the slot or empty if the value was not read.
+ */
+ byte[] value;
}
-int healthd_board_battery_update(struct android::BatteryProperties*)
-{
- // return 0 to log periodic polled battery status to kernel log
- return 0;
-}
diff --git a/weaver/aidl/default/Android.bp b/weaver/aidl/default/Android.bp
new file mode 100644
index 0000000..d936828
--- /dev/null
+++ b/weaver/aidl/default/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_binary {
+ name: "android.hardware.weaver-service.example",
+ relative_install_path: "hw",
+ init_rc: ["android.hardware.weaver-service.example.rc"],
+ vintf_fragments: ["android.hardware.weaver-service.example.xml"],
+ vendor: true,
+ srcs: [
+ "service.cpp",
+ "Weaver.cpp",
+ ],
+ shared_libs: [
+ "android.hardware.weaver-ndk_platform",
+ "libbase",
+ "libbinder_ndk",
+ ],
+}
diff --git a/weaver/aidl/default/Weaver.cpp b/weaver/aidl/default/Weaver.cpp
new file mode 100644
index 0000000..56d9c4d
--- /dev/null
+++ b/weaver/aidl/default/Weaver.cpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Weaver.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace weaver {
+
+// Methods from ::android::hardware::weaver::IWeaver follow.
+
+::ndk::ScopedAStatus Weaver::getConfig(WeaverConfig* out_config) {
+ (void)out_config;
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Weaver::read(int32_t in_slotId, const std::vector<uint8_t>& in_key, WeaverReadResponse* out_response) {
+ (void)in_slotId;
+ (void)in_key;
+ (void)out_response;
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Weaver::write(int32_t in_slotId, const std::vector<uint8_t>& in_key, const std::vector<uint8_t>& in_value) {
+ (void)in_slotId;
+ (void)in_key;
+ (void)in_value;
+ return ::ndk::ScopedAStatus::ok();
+}
+
+} //namespace weaver
+} //namespace hardware
+} //namespace android
+} //namespace aidl
diff --git a/weaver/aidl/default/Weaver.h b/weaver/aidl/default/Weaver.h
new file mode 100644
index 0000000..b50018e
--- /dev/null
+++ b/weaver/aidl/default/Weaver.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/weaver/BnWeaver.h>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace weaver {
+
+using ::aidl::android::hardware::weaver::WeaverConfig;
+using ::aidl::android::hardware::weaver::WeaverReadResponse;
+
+struct Weaver : public BnWeaver {
+public:
+ Weaver() = default;
+
+ // Methods from ::android::hardware::weaver::IWeaver follow.
+ ::ndk::ScopedAStatus getConfig(WeaverConfig* _aidl_return) override;
+ ::ndk::ScopedAStatus read(int32_t in_slotId, const std::vector<uint8_t>& in_key, WeaverReadResponse* _aidl_return) override;
+ ::ndk::ScopedAStatus write(int32_t in_slotId, const std::vector<uint8_t>& in_key, const std::vector<uint8_t>& in_value) override;
+};
+
+} // namespace weaver
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/weaver/aidl/default/android.hardware.weaver-service.example.rc b/weaver/aidl/default/android.hardware.weaver-service.example.rc
new file mode 100644
index 0000000..ec77774
--- /dev/null
+++ b/weaver/aidl/default/android.hardware.weaver-service.example.rc
@@ -0,0 +1,4 @@
+service vendor.weaver_default /vendor/bin/hw/android.hardware.weaver-service.example
+ class hal
+ user hsm
+ group hsm
diff --git a/weaver/aidl/default/android.hardware.weaver-service.example.xml b/weaver/aidl/default/android.hardware.weaver-service.example.xml
new file mode 100644
index 0000000..ed291cd
--- /dev/null
+++ b/weaver/aidl/default/android.hardware.weaver-service.example.xml
@@ -0,0 +1,10 @@
+<manifest version="1.0" type="device">
+ <hal format="aidl">
+ <name>android.hardware.weaver</name>
+ <version>1</version>
+ <interface>
+ <name>IWeaver</name>
+ <instance>default</instance>
+ </interface>
+ </hal>
+</manifest>
diff --git a/weaver/aidl/default/service.cpp b/weaver/aidl/default/service.cpp
new file mode 100644
index 0000000..1495bc9
--- /dev/null
+++ b/weaver/aidl/default/service.cpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include "Weaver.h"
+
+using ::aidl::android::hardware::weaver::Weaver;
+
+int main() {
+ ABinderProcess_setThreadPoolMaxThreadCount(0);
+ std::shared_ptr<Weaver> weaver = ndk::SharedRefBase::make<Weaver>();
+
+ const std::string instance = std::string() + Weaver::descriptor + "/default";
+ binder_status_t status = AServiceManager_addService(weaver->asBinder().get(), instance.c_str());
+ CHECK(status == STATUS_OK);
+
+ ABinderProcess_joinThreadPool();
+ return -1; // Should never be reached
+}
diff --git a/weaver/aidl/vts/Android.bp b/weaver/aidl/vts/Android.bp
new file mode 100644
index 0000000..d7e3ab7
--- /dev/null
+++ b/weaver/aidl/vts/Android.bp
@@ -0,0 +1,33 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_test {
+ name: "VtsHalWeaverTargetTest",
+ defaults: [
+ "VtsHalTargetTestDefaults",
+ "use_libaidlvintf_gtest_helper_static",
+ ],
+ srcs: ["VtsHalWeaverTargetTest.cpp"],
+ shared_libs: [
+ "libbinder_ndk",
+ "libbase",
+ ],
+ static_libs: ["android.hardware.weaver-ndk_platform"],
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
+}
diff --git a/weaver/aidl/vts/OWNERS b/weaver/aidl/vts/OWNERS
new file mode 100644
index 0000000..40d95e4
--- /dev/null
+++ b/weaver/aidl/vts/OWNERS
@@ -0,0 +1,2 @@
+chengyouho@google.com
+frankwoo@google.com
diff --git a/weaver/aidl/vts/VtsHalWeaverTargetTest.cpp b/weaver/aidl/vts/VtsHalWeaverTargetTest.cpp
new file mode 100644
index 0000000..7d8daa2
--- /dev/null
+++ b/weaver/aidl/vts/VtsHalWeaverTargetTest.cpp
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+
+#include <aidl/android/hardware/weaver/IWeaver.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include <limits>
+
+using ::aidl::android::hardware::weaver::IWeaver;
+using ::aidl::android::hardware::weaver::WeaverConfig;
+using ::aidl::android::hardware::weaver::WeaverReadResponse;
+
+using ::ndk::SpAIBinder;
+
+const std::vector<uint8_t> KEY{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
+const std::vector<uint8_t> WRONG_KEY{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+const std::vector<uint8_t> VALUE{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
+const std::vector<uint8_t> OTHER_VALUE{0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 255, 255};
+
+struct WeaverAidlTest : public ::testing::TestWithParam<std::string> {
+ virtual void SetUp() override {
+ weaver = IWeaver::fromBinder(
+ SpAIBinder(AServiceManager_waitForService(GetParam().c_str())));
+ ASSERT_NE(weaver, nullptr);
+ }
+
+ virtual void TearDown() override {}
+
+ std::shared_ptr<IWeaver> weaver;
+};
+
+/*
+ * Checks config values are suitably large
+ */
+TEST_P(WeaverAidlTest, GetConfig) {
+ WeaverConfig config;
+
+ auto ret = weaver->getConfig(&config);
+
+ ASSERT_TRUE(ret.isOk());
+
+ EXPECT_GE(config.slots, 16u);
+ EXPECT_GE(config.keySize, 16u);
+ EXPECT_GE(config.valueSize, 16u);
+}
+
+/*
+ * Gets the config twice and checks they are the same
+ */
+TEST_P(WeaverAidlTest, GettingConfigMultipleTimesGivesSameResult) {
+ WeaverConfig config1;
+ WeaverConfig config2;
+
+ auto ret = weaver->getConfig(&config1);
+ ASSERT_TRUE(ret.isOk());
+
+ ret = weaver->getConfig(&config2);
+ ASSERT_TRUE(ret.isOk());
+
+ EXPECT_EQ(config1, config2);
+}
+
+/*
+ * Gets the number of slots from the config and writes a key and value to the last one
+ */
+TEST_P(WeaverAidlTest, WriteToLastSlot) {
+ WeaverConfig config;
+ const auto configRet = weaver->getConfig(&config);
+
+ ASSERT_TRUE(configRet.isOk());
+
+ const uint32_t lastSlot = config.slots - 1;
+ const auto writeRet = weaver->write(lastSlot, KEY, VALUE);
+ ASSERT_TRUE(writeRet.isOk());
+}
+
+/*
+ * Writes a key and value to a slot
+ * Reads the slot with the same key and receives the value that was previously written
+ */
+TEST_P(WeaverAidlTest, WriteFollowedByReadGivesTheSameValue) {
+ constexpr uint32_t slotId = 0;
+ const auto ret = weaver->write(slotId, KEY, VALUE);
+ ASSERT_TRUE(ret.isOk());
+
+ WeaverReadResponse response;
+ std::vector<uint8_t> readValue;
+ uint32_t timeout;
+ const auto readRet = weaver->read(slotId, KEY, &response);
+
+ readValue = response.value;
+ timeout = response.timeout;
+
+ ASSERT_TRUE(readRet.isOk());
+ EXPECT_EQ(readValue, VALUE);
+ EXPECT_EQ(timeout, 0u);
+}
+
+/*
+ * Writes a key and value to a slot
+ * Overwrites the slot with a new key and value
+ * Reads the slot with the new key and receives the new value
+ */
+TEST_P(WeaverAidlTest, OverwritingSlotUpdatesTheValue) {
+ constexpr uint32_t slotId = 0;
+ const auto initialWriteRet = weaver->write(slotId, WRONG_KEY, VALUE);
+ ASSERT_TRUE(initialWriteRet.isOk());
+
+ const auto overwriteRet = weaver->write(slotId, KEY, OTHER_VALUE);
+ ASSERT_TRUE(overwriteRet.isOk());
+
+ WeaverReadResponse response;
+ std::vector<uint8_t> readValue;
+ uint32_t timeout;
+ const auto readRet = weaver->read(slotId, KEY, &response);
+
+ readValue = response.value;
+ timeout = response.timeout;
+
+ ASSERT_TRUE(readRet.isOk());
+ EXPECT_EQ(readValue, OTHER_VALUE);
+ EXPECT_EQ(timeout, 0u);
+}
+
+/*
+ * Writes a key and value to a slot
+ * Reads the slot with a different key so does not receive the value
+ */
+TEST_P(WeaverAidlTest, WriteFollowedByReadWithWrongKeyDoesNotGiveTheValue) {
+ constexpr uint32_t slotId = 0;
+ const auto ret = weaver->write(slotId, KEY, VALUE);
+ ASSERT_TRUE(ret.isOk());
+
+ WeaverReadResponse response;
+ std::vector<uint8_t> readValue;
+ const auto readRet =
+ weaver->read(slotId, WRONG_KEY, &response);
+
+ readValue = response.value;
+
+ ASSERT_FALSE(readRet.isOk());
+ ASSERT_EQ(EX_SERVICE_SPECIFIC, readRet.getExceptionCode());
+ ASSERT_EQ(IWeaver::INCORRECT_KEY, readRet.getServiceSpecificError());
+ EXPECT_TRUE(readValue.empty());
+}
+
+/*
+ * Writing to an invalid slot fails
+ */
+TEST_P(WeaverAidlTest, WritingToInvalidSlotFails) {
+ WeaverConfig config;
+ const auto configRet = weaver->getConfig(&config);
+ ASSERT_TRUE(configRet.isOk());
+
+ if (config.slots == std::numeric_limits<uint32_t>::max()) {
+ // If there are no invalid slots then pass
+ return;
+ }
+
+ const auto writeRet = weaver->write(config.slots, KEY, VALUE);
+ ASSERT_FALSE(writeRet.isOk());
+}
+
+/*
+ * Reading from an invalid slot fails rather than incorrect key
+ */
+TEST_P(WeaverAidlTest, ReadingFromInvalidSlotFails) {
+ WeaverConfig config;
+ const auto configRet = weaver->getConfig(&config);
+ ASSERT_TRUE(configRet.isOk());
+
+ if (config.slots == std::numeric_limits<uint32_t>::max()) {
+ // If there are no invalid slots then pass
+ return;
+ }
+
+ WeaverReadResponse response;
+ std::vector<uint8_t> readValue;
+ uint32_t timeout;
+ const auto readRet =
+ weaver->read(config.slots, KEY, &response);
+
+ readValue = response.value;
+ timeout = response.timeout;
+
+ ASSERT_FALSE(readRet.isOk());
+ ASSERT_EQ(EX_SERVICE_SPECIFIC, readRet.getExceptionCode());
+ ASSERT_EQ(IWeaver::STATUS_FAILED, readRet.getServiceSpecificError());
+ EXPECT_TRUE(readValue.empty());
+ EXPECT_EQ(timeout, 0u);
+}
+
+/*
+ * Writing a key that is too large fails
+ */
+TEST_P(WeaverAidlTest, WriteWithTooLargeKeyFails) {
+ WeaverConfig config;
+ const auto configRet = weaver->getConfig(&config);
+ ASSERT_TRUE(configRet.isOk());
+
+ std::vector<uint8_t> bigKey(config.keySize + 1);
+
+ constexpr uint32_t slotId = 0;
+ const auto writeRet = weaver->write(slotId, bigKey, VALUE);
+ ASSERT_FALSE(writeRet.isOk());
+}
+
+/*
+ * Writing a value that is too large fails
+ */
+TEST_P(WeaverAidlTest, WriteWithTooLargeValueFails) {
+ WeaverConfig config;
+ const auto configRet = weaver->getConfig(&config);
+ ASSERT_TRUE(configRet.isOk());
+
+ std::vector<uint8_t> bigValue(config.valueSize + 1);
+
+ constexpr uint32_t slotId = 0;
+ const auto writeRet = weaver->write(slotId, KEY, bigValue);
+ ASSERT_FALSE(writeRet.isOk());
+}
+
+/*
+ * Reading with a key that is loo large fails
+ */
+TEST_P(WeaverAidlTest, ReadWithTooLargeKeyFails) {
+ WeaverConfig config;
+ const auto configRet = weaver->getConfig(&config);
+ ASSERT_TRUE(configRet.isOk());
+
+ std::vector<uint8_t> bigKey(config.keySize + 1);
+
+ constexpr uint32_t slotId = 0;
+ WeaverReadResponse response;
+ std::vector<uint8_t> readValue;
+ uint32_t timeout;
+ const auto readRet =
+ weaver->read(slotId, bigKey, &response);
+
+ readValue = response.value;
+ timeout = response.timeout;
+
+ ASSERT_FALSE(readRet.isOk());
+ ASSERT_EQ(EX_SERVICE_SPECIFIC, readRet.getExceptionCode());
+ ASSERT_EQ(IWeaver::STATUS_FAILED, readRet.getServiceSpecificError());
+ EXPECT_TRUE(readValue.empty());
+ EXPECT_EQ(timeout, 0u);
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(WeaverAidlTest);
+INSTANTIATE_TEST_SUITE_P(
+ PerInstance, WeaverAidlTest,
+ testing::ValuesIn(android::getAidlHalInstanceNames(IWeaver::descriptor)),
+ android::PrintInstanceNameToString);
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ ABinderProcess_setThreadPoolMaxThreadCount(1);
+ ABinderProcess_startThreadPool();
+ return RUN_ALL_TESTS();
+}